// warehouse-pages.jsx — Склад: реєстр майна + інвентаризація // ============ QR-ЕТИКЕТКА (плейсхолдер) ============ // Детермінований патерн зі квадратів на основі інв. номера. function AssetQR({ code, size = 88 }) { const grid = 9; const cells = React.useMemo(() => { let h = 2166136261; for (let i = 0; i < code.length; i++) { h ^= code.charCodeAt(i); h = Math.imul(h, 16777619); } const out = []; let x = h >>> 0; for (let i = 0; i < grid * grid; i++) { x ^= x << 13; x ^= x >>> 17; x ^= x << 5; x >>>= 0; out.push(x % 100 < 48); } // три кутові маркери як у QR const mark = (r, c) => { for (let dr = 0; dr < 3; dr++) for (let dc = 0; dc < 3; dc++) out[(r+dr)*grid+(c+dc)] = !(dr===1&&dc===1) || true; }; [[0,0],[0,grid-3],[grid-3,0]].forEach(([r,c]) => { for (let dr=0; dr<3; dr++) for (let dc=0; dc<3; dc++) { const edge = dr===0||dr===2||dc===0||dc===2; out[(r+dr)*grid+(c+dc)] = edge; } }); return out; }, [code]); return (
{cells.map((on, i) => )}
); } // ============ СКЛАД / МАЙНО ============ function WarehousePage({ role }) { const assets = window.DATA.ASSETS; const cats = window.ASSET_CATEGORIES; const [filterCat, setFilterCat] = React.useState("all"); const [filterStatus, setFilterStatus] = React.useState("all"); const [openId, setOpenId] = React.useState(null); const live = assets.filter(a => a.status !== "written_off"); const totalCost = live.reduce((s, a) => s + a.cost, 0); const totalResidual = live.reduce((s, a) => s + window.computeDepreciation(a).residual, 0); const inRepair = assets.filter(a => a.status === "repair").length; const writtenOff = assets.filter(a => a.status === "written_off").length; let shown = assets; if (filterCat !== "all") shown = shown.filter(a => a.cat === filterCat); if (filterStatus !== "all") shown = shown.filter(a => a.status === filterStatus); shown = [...shown].sort((a, b) => a.invNo.localeCompare(b.invNo, "uk")); const catStats = cats.map(c => { const list = live.filter(a => a.cat === c.id); return { cat: c, count: list.length, cost: list.reduce((s, a) => s + a.cost, 0) }; }); return (

Майно компанії

{live.length} {window.plural(live.length, "одиниця", "одиниці", "одиниць")} в обліку первісна {window.formatMoney(totalCost)} ₴ залишкова {window.formatMoney(totalResidual)} ₴
Одиниць в обліку
{live.length}
{cats.length} категорії
Первісна вартість
{window.formatMoney(totalCost)}
сумарно по балансу
Залишкова вартість
{window.formatMoney(totalResidual)}
з урахуванням зносу
На ремонті
{inRepair}
{inRepair ? "потребує уваги" : "усе справне"}
Списано
{writtenOff}
в архіві актів
{/* Категорії + схема інвентарних номерів */}

Категорії та інвентарні номери

номер присвоюється автоматично за маскою
{catStats.map(s => ( ))}
{/* Фільтр + таблиця */}
{filterCat !== "all" && ( )}
{shown.map(a => { const cat = window.getAssetCategory(a.cat); const holder = window.getTeam(a.holder); const dep = window.computeDepreciation(a); const st = window.ASSET_STATUS[a.status]; return ( setOpenId(a.id)}> ); })}
Інв. № Найменування Категорія МВО Локація Залишкова Знос Статус
{a.invNo}
{a.name}
{a.model}
{cat.short} {holder ?
{holder.initials} {holder.name.split(" ")[0]}
: }
{window.ASSET_LOCATIONS[a.location]} {dep.depreciable ? {window.formatMoney(dep.residual)} ₴ : } {dep.depreciable ?
{dep.wearPct}%
: не амортизується}
{st.label}
{openId && setOpenId(null)} />}
); } function AssetDrawer({ asset, onClose }) { React.useEffect(() => { const onKey = (e) => e.key === "Escape" && onClose(); window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [onClose]); const cat = window.getAssetCategory(asset.cat); const holder = window.getTeam(asset.holder); const dep = window.computeDepreciation(asset); const st = window.ASSET_STATUS[asset.status]; const warrantyActive = asset.warrantyUntil && asset.warrantyUntil !== "—" && new Date(asset.warrantyUntil) >= new Date(window.SYS_DATE || "2026-05-29"); return ( <>
); } window.WarehousePage = WarehousePage; window.AssetQR = AssetQR;