`;
return row;
}
function escapeHtml(value) {
return String(value).replace(/[&<>"']/g, char => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[char]));
}
function buildText() {
const lines = [];
const selected = [...state.values()].filter(item => item.selected || item.qty || item.note);
lines.push(`PURCHASE LIST - ${formatDate(el.date.value)}`);
if (el.filledBy.value.trim()) lines.push(`Filled by: ${el.filledBy.value.trim()}`);
if (el.eventName.value.trim()) lines.push(`Event/client: ${el.eventName.value.trim()}`);
lines.push("");
categories.forEach(([category]) => {
const group = selected.filter(item => item.category === category);
if (!group.length) return;
lines.push(category.toUpperCase());
group.forEach(item => {
const amount = [item.qty, item.unit].filter(Boolean).join(" ");
const note = item.note ? ` (${item.note})` : "";
lines.push(`- ${item.english}${amount ? `: ${amount}` : ""}${note}`);
});
lines.push("");
});
if (!selected.length) {
lines.push("No items selected.");
}
return lines.join("\n").trim();
}
function formatDate(value) {
if (!value) return today();
const [year, month, day] = value.split("-");
return `${day}.${month}.${year}`;
}
function updateSummary() {
const selected = [...state.values()].filter(item => item.selected || item.qty || item.note);
el.output.value = buildText();
el.stats.textContent = `${selected.length} item${selected.length === 1 ? "" : "s"} selected`;
el.mobileStats.textContent = `${selected.length} item${selected.length === 1 ? "" : "s"} selected`;
}
function updateRowFromEvent(target) {
const row = target.closest(".row");
if (!row) return;
const item = state.get(row.dataset.id);
item.selected = row.querySelector(".check").checked;
item.qty = row.querySelector(".qty").value.trim();
item.unit = row.querySelector(".unit").value;
item.note = row.querySelector(".note").value.trim();
if (target.classList.contains("qty") && item.qty) item.selected = true;
if (target.classList.contains("note") && item.note) item.selected = true;
row.querySelector(".check").checked = item.selected;
row.classList.toggle("active", item.selected);
updateSummary();
}
async function copyOutput() {
const text = el.output.value;
try {
await navigator.clipboard.writeText(text);
} catch (error) {
el.output.focus();
el.output.select();
document.execCommand("copy");
}
toast("Copied");
}
function toast(message) {
el.toast.textContent = message;
clearTimeout(toast.timer);
toast.timer = setTimeout(() => { el.toast.textContent = ""; }, 1800);
}
function clearAll() {
if (!confirm("Clear all selected items?")) return;
state.forEach(item => {
item.selected = false;
item.qty = "";
item.unit = "";
item.note = "";
});
renderList();
updateSummary();
toast("Cleared");
}
function downloadText() {
const blob = new Blob([el.output.value], { type: "text/plain;charset=utf-8" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `purchase-list-${el.date.value || today()}.txt`;
a.click();
URL.revokeObjectURL(url);
}
document.addEventListener("input", event => {
if (event.target.matches(".qty, .note")) updateRowFromEvent(event.target);
if (event.target.matches("#date, #filledBy, #eventName")) updateSummary();
if (event.target.matches("#search")) renderList();
});
document.addEventListener("change", event => {
if (event.target.matches(".check, .unit")) updateRowFromEvent(event.target);
});
el.tabs.addEventListener("click", event => {
const button = event.target.closest("[data-category]");
if (!button) return;
activeCategory = button.dataset.category;
renderTabs();
renderList();
});
document.querySelector("#copyTop").addEventListener("click", copyOutput);
document.querySelector("#copySide").addEventListener("click", copyOutput);
document.querySelector("#copyMobile").addEventListener("click", copyOutput);
document.querySelector("#downloadTxt").addEventListener("click", downloadText);
document.querySelector("#clearAll").addEventListener("click", clearAll);
document.querySelector("#printPage").addEventListener("click", () => window.print());
initState();
renderTabs();
renderList();
updateSummary();