`;
$("offer-form").innerHTML = html;
bindFieldHandlers();
bindOrderHandlers();
initDatePickers();
}
function rerenderOrderCard() {
const wrap = $("order-card-wrap");
if (!wrap) return;
wrap.innerHTML = orderCardHtml();
bindOrderHandlers();
initDatePickers();
}
function initDatePickers() {
if (typeof flatpickr === "undefined") return;
if (flatpickr.l10ns && flatpickr.l10ns.cs) flatpickr.localize(flatpickr.l10ns.cs);
document.querySelectorAll(".fp-date").forEach((el) => {
if (el._flatpickr) return;
flatpickr(el, {
dateFormat: "d.m.Y", // matches the PDF / store format
allowInput: true,
disableMobile: true,
onChange: () => {
state.order[el.dataset.order] = el.value;
el.classList.toggle("is-empty", el.value === "");
},
});
});
}
function renderItemRow(item, i) {
return `
`;
}
function bindFieldHandlers() {
$("offer-form").querySelectorAll("input[data-key]").forEach((el) => {
el.addEventListener("input", () => {
const key = el.dataset.key;
const parent = el.dataset.parent;
if (parent) {
if (!state.data[parent]) state.data[parent] = {};
state.data[parent][key] = el.value;
} else {
state.data[key] = el.value;
}
el.classList.toggle("is-empty", el.value === "");
});
});
const itemsBody = $("items-body");
if (itemsBody) {
itemsBody.addEventListener("input", (e) => {
const inp = e.target.closest("input");
if (!inp) return;
const i = +inp.closest("tr").dataset.row;
const field = inp.dataset.field;
if (!state.data.line_items) state.data.line_items = [];
if (!state.data.line_items[i]) state.data.line_items[i] = {};
const numFields = ["quantity", "unit_price", "total_price"];
state.data.line_items[i][field] =
numFields.includes(field) ? parseNum(inp.value) : inp.value;
});
itemsBody.addEventListener("click", (e) => {
const btn = e.target.closest('[data-action="delete-item"]');
if (!btn) return;
const i = +btn.closest("tr").dataset.row;
state.data.line_items.splice(i, 1);
renderForm();
});
}
const addItem = $("add-item-btn");
if (addItem) {
addItem.addEventListener("click", () => {
if (!state.data.line_items) state.data.line_items = [];
state.data.line_items.push({name: "", supplier_id: "", quantity: null,
unit: "", unit_price: null, total_price: null});
renderForm();
});
}
}
function parseNum(s) {
if (s === "" || s == null) return null;
const n = parseFloat(String(s).replace(",", ".").replace(/\s/g, ""));
return Number.isFinite(n) ? n : null;
}
function fmtNum(v) {
if (v === null || v === undefined || v === "") return "";
return String(v);
}
// ── Export ──
$("export-btn").addEventListener("click", async () => {
if (!state.jobId) return;
await fetch(`api/save/${state.jobId}`, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({data: state.data}),
}).catch(() => {});
window.location.href = `api/export/${state.jobId}`;
});
$("restart-btn").addEventListener("click", () => {
state = { jobId: null, data: null };
fileInput.value = "";
$("offer-form").innerHTML = "";
show("upload");
});
// ── Objednávka (order form) ──────────────────────────────────
function sumTotals() {
return (state.data.line_items || []).reduce(
(a, it) => a + (Number(it.total_price) || 0), 0);
}
async function initOrder() {
const d = state.data;
const offerNo = d.offer_number || "";
state.order = {
objednavka_number: "",
kod_zakazky: "",
nazev_zakazky: "",
dodavatel: d.supplier || {},
text: "Objednáváme u vás zboží dle cenové nabídky " + offerNo,
prilohy: offerNo,
termin_dodani_text: "Co nejdříve",
misto_dodani: "",
kontaktni_osoba: "",
splatnost: "dle VNP nebo VSP",
zaruka: "",
poznamka: "",
vystavil: "",
popis: "",
zpusob_platby: "Bankovním převodem",
zpusob_odberu: "-",
zpusob_dopravy: "-",
forma_objednavky: "mailem",
termin_dodani: "",
fakturujte: "s DPH",
};
state.projects = [];
state.orderHistory = {};
try {
const r = await fetch("api/projects");
if (r.ok) {
const j = await r.json();
state.projects = j.projects || [];
if (j.next_order_number) state.order.objednavka_number = j.next_order_number;
}
} catch (_) {}
}
// Body fields. `hist` = remembered per project, shown with a history
// quick-pick (most-used first).
const ORDER_FIELDS = [
["text", "Text", true, false],
["termin_dodani_text", "Termín dodání (text)", false, false],
["misto_dodani", "Místo dodání", true, true],
["kontaktni_osoba", "Kontaktní osoba v místě dodání", true, true],
["splatnost", "Splatnost", false, true],
["zaruka", "Záruka", false, true],
["poznamka", "Poznámka", false, false],
["prilohy", "Přílohy", false, false],
["vystavil", "Vystavil", false, true],
["popis", "Popis", false, true],
];
function fieldHtml(key, label, wide, hist) {
return hist ? historyField(key, label, wide) : textField(key, label, wide);
}
// ── date helpers (PDF/store use DD.MM.YYYY, uses ISO) ──
function toDateInput(s) {
s = (s == null ? "" : String(s)).trim();
let m = s.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/);
if (m) return `${m[3]}-${m[2].padStart(2,"0")}-${m[1].padStart(2,"0")}`;
if (/^\d{4}-\d{2}-\d{2}$/.test(s)) return s;
return "";
}
function fromDateInput(s) {
const m = (s || "").match(/^(\d{4})-(\d{2})-(\d{2})$/);
return m ? `${+m[3]}.${+m[2]}.${m[1]}` : (s || "");
}
// Combobox: type freely OR pick a previously-used value from a themed
// dropdown attached to the field; each option can be deleted from history.
function comboMenuHtml(key, filter) {
const hist = (state.orderHistory && state.orderHistory[key]) || [];
const f = (filter || "").toLowerCase();
const items = hist.filter((h) => !f || String(h.value).toLowerCase().includes(f));
if (!items.length) return '
Žádné dříve použité
';
return items.map((h) => `
${escapeHtml(h.value)}
${h.count}×
`).join("");
}
function historyField(key, label, wide) {
const v = state.order[key];
const cur = v == null ? "" : String(v);
const hasHist = ((state.orderHistory && state.orderHistory[key]) || []).length > 0;
return `
${hasHist ? '' : ""}
${comboMenuHtml(key)}
`;
}
// Dodací a platební podmínky — selectable presets (editable: type or pick).
const TERMS_FIELDS = [
["zpusob_platby", "Způsob platby", ["Bankovním převodem", "Hotově", "Kartou", "Zálohově", "Dobírka"]],
["forma_objednavky", "Forma objednávky", ["mailem", "telefonicky", "osobně", "písemně"]],
["zpusob_odberu", "Způsob odběru", ["-", "Osobní odběr", "Dodání na stavbu", "Dopravcem"]],
["termin_dodani", "Termín dodání", []],
["zpusob_dopravy", "Způsob dopravy", ["-", "Dodavatel", "Vlastní", "Dopravce"]],
["fakturujte", "Fakturujte", ["s DPH", "bez DPH"]],
];
function textField(key, label, wide) {
const v = state.order[key];
const isEmpty = v === null || v === undefined || v === "";
return `
`;
}
function comboField(key, label, options) {
const v = state.order[key];
const cur = v == null ? "" : String(v);
// Termín dodání → flatpickr date picker (value stays DD.MM.YYYY).
if (key === "termin_dodani") {
return `
`;
}
// Real dropdown: history (most-used first) + presets + any custom value.
const histVals = ((state.orderHistory && state.orderHistory[key]) || [])
.map((h) => h.value);
const vals = [];
for (const x of [...histVals, ...(options || []), cur]) {
if (x && !vals.includes(x)) vals.push(x);
}
const opts = ['']
.concat(vals.map((o) =>
``))
.join("");
return `
`;
}
function projectOptions() {
const cur = state.order.kod_zakazky || "";
const opts = [''];
for (const p of state.projects || []) {
const sel = p.kod === cur ? " selected" : "";
const label = p.nazev ? `${p.kod} — ${p.nazev}` : p.kod;
opts.push(``);
}
return opts.join("");
}
function orderCardHtml() {
return `
Objednávka (vyplní se do PDF)
Zakázku vyberte z nabídky (nebo přidejte přes
Spravovat zakázky). Pole jako Místo dodání,
kontaktní osoba či Vystavil se pamatují podle zakázky. Číslo objednávky
se předvyplní jako poslední + 1. Cena celkem z položek:
${fmtCzk(sumTotals())}.