// orders-page.jsx — Кадрові накази: журнал реєстрації + майстер створення. // Нумерація НК-0N-2026. Друк через window.OrderPrintDoc. function OrdStatusTag({ status }) { const s = window.ORDER_STATUS[status] || window.ORDER_STATUS.draft; return {s.label}; } // ——— дрібні поля форми ——— function OWField({ label, children, wide }) { return ( ); } function OrderWizard({ onClose }) { const team = window.DATA.TEAM; const staff = team.filter(p => p.employment !== "contractor"); const SYS = window.SYS_DATE || "2026-05-29"; const [step, setStep] = React.useState(1); const [type, setType] = React.useState(null); const firstPid = staff[0] ? staff[0].id : ""; const [pid, setPid] = React.useState(firstPid); const [basis, setBasis] = React.useState(""); const [f, setF] = React.useState({}); const set = (k, v) => setF(prev => ({ ...prev, [k]: v })); // дефолти полів під тип + обрану людину const seedFields = (tp, personId) => { const p = window.getTeam(personId) || {}; if (tp === "hire") return { name: "", position: "", dept: "Проєктний відділ", salary: 30000, partRate: 1, from: SYS, probation: 3, contract: "Безстроковий трудовий договір" }; if (tp === "dismiss") return { date: SYS, ground: "art38", compDays: p.vacationBalance || 0 }; if (tp === "vacation") return { kind: "щорічну основну", days: 10, from: SYS, to: SYS, period: "за робочий рік 2025–2026" }; if (tp === "bonus") return { mode: "amount", amount: 10000, percent: 20, reason: "за сумлінне виконання посадових обов'язків" }; if (tp === "transfer") return { posOld: p.positionOfficial || p.role || "", posNew: "", salaryNew: (p.baseSalary || 0), from: SYS }; return {}; }; const pickType = (tp) => { setType(tp); setF(seedFields(tp, pid)); setBasis(defaultBasis(tp)); setStep(2); }; const defaultBasis = (tp) => ({ hire: "Заява про прийняття на роботу, трудовий договір", dismiss: "Заява про звільнення за власним бажанням", vacation: "Заява, графік відпусток на 2026 р.", bonus: "Подання директора", transfer: "Заява / службова записка", }[tp] || ""); // при зміні людини — перерахувати залежні дефолти const onPid = (id) => { setPid(id); if (type) setF(prev => ({ ...prev, ...((type === "transfer" || type === "dismiss") ? seedFields(type, id) : {}) })); }; const valid = (() => { if (!type) return false; if (type === "hire") return f.name && f.name.trim() && f.position && f.position.trim(); return !!pid; })(); const submit = () => { const order = { id: "ord-" + Date.now(), no: window.nextOrderNo(), date: SYS, type, personId: type === "hire" ? null : pid, status: "draft", basis, f: { ...f }, }; if (type === "bonus") { if (f.mode === "amount") delete order.f.percent; else delete order.f.amount; } window.addOrder(order); onClose(order); }; const num = window.formatMoney; return (
onClose(null)}>
Новий кадровий наказ
№ {window.nextOrderNo()} · {window.formatDate(SYS)}
{step === 1 && (
Оберіть тип наказу
{window.ORDER_TYPE_ORDER.map(tp => { const T = window.ORDER_TYPES[tp]; return ( ); })}
)} {step === 2 && type && (
{window.ORDER_TYPES[type].label}
{/* Працівник */} {type === "hire" ? ( set("name", e.target.value)} placeholder="Ім'я Прізвище" /> set("position", e.target.value)} placeholder="напр. Інженер-проєктувальник" /> set("dept", e.target.value)} /> set("salary", +e.target.value)} /> set("from", e.target.value)} /> ) : ( )} {/* Поля під тип */} {type === "dismiss" && ( set("date", e.target.value)} /> set("compDays", +e.target.value)} /> )} {type === "vacation" && ( set("days", +e.target.value)} /> set("from", e.target.value)} /> set("to", e.target.value)} /> {(() => { const p = window.getTeam(pid); return p && p.vacationBalance != null ?
Залишок відпустки: {p.vacationBalance} к. дн.
: null; })()}
)} {type === "bonus" && ( {f.mode === "percent" ? set("percent", +e.target.value)} /> : set("amount", +e.target.value)} />} set("reason", e.target.value)} /> )} {type === "transfer" && ( set("posOld", e.target.value)} /> set("posNew", e.target.value)} placeholder="напр. Провідний інженер-проєктувальник" /> set("salaryNew", +e.target.value)} /> set("from", e.target.value)} /> )} setBasis(e.target.value)} />
Створиться чернетка — текст можна відредагувати у бланку перед друком.
)}
); } function OrdersPage({ role }) { const st = window.useOrders(); const [wizard, setWizard] = React.useState(false); const [printOrder, setPrintOrder] = React.useState(null); const [filter, setFilter] = React.useState("all"); const canEdit = role === "director" || role === "accountant"; const list = [...st.list].sort((a, b) => (a.no < b.no ? 1 : -1)); const shown = filter === "all" ? list : list.filter(o => o.type === filter); const byType = {}; window.ORDER_TYPE_ORDER.forEach(t => byType[t] = list.filter(o => o.type === t).length); const drafts = list.filter(o => o.status === "draft").length; const sign = (o) => window.updateOrder(o.id, { status: "signed", ackDate: o.ackDate || window.SYS_DATE }); return (

Кадрові накази

журнал реєстрації по особовому складу нумерація НК-0N-2026 {list.length} наказів за рік{drafts > 0 ? ` · ${drafts} чернеток` : ""}
{/* Зведення по типах */}
{window.ORDER_TYPE_ORDER.map(t => { const T = window.ORDER_TYPES[t]; return ( ); })}
{/* Тулбар */}
{filter !== "all" && }
{canEdit && }
{/* Журнал */} {shown.map(o => { const T = window.ORDER_TYPES[o.type] || {}; const p = window.getTeam(o.personId); const who = p ? p.name : (o.f && o.f.name) || "—"; return ( ); })}
№ наказу Дата Тип · працівник Зміст Підстава Статус
{o.no} {window.formatDate(o.date)}
{T.label}
{who}
{window.orderSummary(o)} {o.basis}
{canEdit && o.status === "draft" && }
Журнал реєстрації наказів з особового складу ведеться наскрізно (НК-0N-2026). Бланк формується автоматично з картки працівника (ПІБ, посада, оклад), текст редагується перед друком. Підпис директора, печатка (М.П.) і блок ознайомлення — на бланку. КЕП та інтеграція з кадровим архівом — на бекенді.
{wizard && { setWizard(false); if (o) setPrintOrder(o); }} />} {printOrder && setPrintOrder(null)} />}
); } window.OrdersPage = OrdersPage;