// phase6-pages.jsx — Решта модулів: Гант, Архів, Матеріали, Лікарняні, Аванси, Бонуси, Історія ЗП, АН, Акти, ТУ, ДСНС, КЕП
// ============ ГАНТ ============
function GanttPage({ tasks }) {
const objects = window.DATA.OBJECTS;
const stages = window.DATA.STAGES;
return (
Гант-діаграма
часова шкала всіх об'єктів
травень 2026 — грудень 2026
Місяць
Квартал
Рік
сьогодні · 26 травня 2026
Об'єкт / етап
{["Тра", "Чер", "Лип", "Сер", "Вер", "Жов", "Лис", "Гру"].map((m, i) => (
{m}
))}
{objects.map(obj => {
const objStages = stages.map(s => {
const planned = obj.plan?.[s.id] || 0;
const isCompleted = obj.completedStages?.includes(s.id);
const isActive = obj.activeStage === s.id;
return { stage: s, planned, isCompleted, isActive };
}).filter(s => s.planned > 0 || s.isCompleted);
return (
{obj.code}
{obj.name}
до {obj.deadline}
{objStages.map((s, i) => {
const widthPct = 8 + Math.random() * 20;
const startPct = i === 0 ? 0 : objStages.slice(0, i).reduce((sum) => sum + 12, 0);
return (
{s.stage.short}
);
})}
);
})}
);
}
// ============ АРХІВ ПРОЄКТІВ ============
function ArchivePage({ role }) {
const archived = [
{ code: "УКП-2023-12", name: "ЖК «Дніпровські вежі»", grade: "СС2", client: "ТОВ «РіверБуд»", year: 2023, finalSum: 12400000, drive: true },
{ code: "УКП-2023-08", name: "Школа №201", grade: "СС2", client: "Управління освіти м. Києва", year: 2023, finalSum: 3800000, drive: true },
{ code: "УКП-2022-22", name: "Дитсадок «Веселка»", grade: "СС2", client: "Управління освіти", year: 2022, finalSum: 2900000, drive: true },
{ code: "УКП-2022-15", name: "Котедж в Лютежі", grade: "СС1", client: "Іванов В.І.", year: 2022, finalSum: 380000, drive: true },
{ code: "УКП-2022-05", name: "Реконструкція бібліотеки", grade: "СС2", client: "Мінкультури", year: 2022, finalSum: 1200000, drive: true },
{ code: "УКП-2021-18", name: "Складський комплекс «ЛогіКс»", grade: "СС2", client: "ТОВ «ЛогіКс»", year: 2021, finalSum: 4500000, drive: true },
];
return (
Архів проєктів
{archived.length} завершених
довідкова база
усі матеріали в Drive
Як використовувати: архів — це портфоліо для тендерів і референси для нових проєктів.
Кожен проєкт містить всі креслення, експертизи, фотофіксацію в Drive. Можна копіювати рішення
для подібних об'єктів.
Шифр
Назва
Клас
Замовник
Рік
Підсумкова сума
{archived.map(a => (
{a.code}
{a.name}
{a.grade}
{a.client}
{a.year}
{window.formatMoney(a.finalSum)} ₴
Відкрити Drive ↗
))}
);
}
// ============ БАЗА МАТЕРІАЛІВ ============
function MaterialsPage({ role }) {
const materials = [
{ name: "Бетон М300", unit: "м³", price: 4500, supplier: "ТОВ «Бетон+»", updated: "2026-05-20", group: "Бетон" },
{ name: "Бетон М400", unit: "м³", price: 5200, supplier: "ТОВ «Бетон+»", updated: "2026-05-20", group: "Бетон" },
{ name: "Арматура А500С Ø12", unit: "т", price: 38500, supplier: "Метінвест", updated: "2026-05-15", group: "Метал" },
{ name: "Арматура А500С Ø16", unit: "т", price: 36800, supplier: "Метінвест", updated: "2026-05-15", group: "Метал" },
{ name: "Цегла керамічна М150", unit: "тис.шт", price: 18500, supplier: "Слобожанська цегла", updated: "2026-05-10", group: "Стіни" },
{ name: "Газобетон D500", unit: "м³", price: 3200, supplier: "Стоунлайт", updated: "2026-05-12", group: "Стіни" },
{ name: "Утеплювач Penoplex 50мм", unit: "м³", price: 2800, supplier: "Епіцентр", updated: "2026-05-08", group: "Утеплення" },
{ name: "Мінвата Rockwool 100мм", unit: "м³", price: 3600, supplier: "Rockwool", updated: "2026-05-08", group: "Утеплення" },
{ name: "Покрівля метал.черепиця", unit: "м²", price: 480, supplier: "Прушинські", updated: "2026-05-05", group: "Покрівля" },
{ name: "Вікно металопластикове Rehau", unit: "м²", price: 4200, supplier: "Rehau", updated: "2026-05-18", group: "Вікна" },
];
const totalGroups = [...new Set(materials.map(m => m.group))];
return (
База матеріалів і цін
{materials.length} позицій
{totalGroups.length} груп
ціни оновлено · {window.formatDate("2026-05-20")}
Для кошторисів: регулярно оновлювана база цін постачальників для швидкого формування
кошторисів і тендерних пропозицій. Можна імпортувати з Excel або синхронізувати з постачальниками.
Усі
{totalGroups.map(g => {g} )}
Додати матеріал
Матеріал
Група
Одиниця
Ціна, ₴
Постачальник
Оновлено
{materials.map((m, i) => (
{m.name}
{m.group}
{m.unit}
{window.formatMoney(m.price)} ₴
{m.supplier}
{window.formatDate(m.updated)}
))}
);
}
// ============ АВАНСИ (ЗП) ============
function AdvancePayPage({ role }) {
const team = window.DATA.TEAM;
const today = new Date("2026-05-26");
return (
Авансові виплати
аванс на середину місяця · 15 числа
40% від місячного окладу
Найближчий аванс
15.06
через 20 днів
Сума авансу
{window.formatMoney(80000)} ₴
40% × штатних
Останній аванс
15.05
сплачено вчасно
Працівник Тип Оклад Аванс 40% Дата виплати Статус
{team.filter(p => p.employment !== "contractor").map(p => {
const base = p.baseSalary * (p.partRate || 1);
const adv = Math.round(base * 0.4);
return (
{p.initials}
{p.name}
{p.role}
{p.employment === "staff" ? "Штат" : "Часткова"}
{window.formatMoney(base)} ₴
{window.formatMoney(adv)} ₴
15.06.2026
заплановано
);
})}
);
}
// ============ ЛІКАРНЯНІ ============
function SickPayPage({ role }) {
const sicks = [
{ who: "tm", from: "2026-05-12", to: "2026-05-14", days: 3, status: "approved", certNumber: "АА-2837401", basis: "лікарняний лист", payable: 1850 },
{ who: "ok", from: "2026-02-08", to: "2026-02-12", days: 5, status: "approved", certNumber: "АА-2745192", basis: "лікарняний лист", payable: 4200, paidDate: "2026-02-20" },
{ who: "ap", from: "2025-11-20", to: "2025-11-24", days: 5, status: "approved", certNumber: "АА-2598437", basis: "лікарняний лист", payable: 2900, paidDate: "2025-12-05" },
];
return (
Лікарняні
оплата перших 5 днів — компанія
з 6-го дня — ФСС
Як рахується: перші 5 днів оплачуються роботодавцем у розмірі 60-100% середньоденного заробітку
(залежно від стажу). З 6-го дня — Фонд соціального страхування.
Працівник Період Днів № листка До виплати Статус
{sicks.map((s, i) => {
const p = window.getTeam(s.who);
return (
{p?.initials}
{p?.name}
{window.formatDate(s.from)} – {window.formatDate(s.to)}
{s.days}
{s.certNumber}
{window.formatMoney(s.payable)} ₴
{s.paidDate
? сплачено
: до виплати
}
);
})}
);
}
// ============ 13-ТА І РІЧНІ ============
function BonusPayPage({ role }) {
const team = window.DATA.TEAM;
return (
13-та зарплата і річні бонуси
фінансові пакети наприкінці року
правила задає директор · KPI-залежні
Бонусний пул 2026
{window.formatMoney(420000)} ₴
~15% від чистого прибутку
Дата виплати
20.12
перед Новим роком
Учасників
{team.filter(p => p.employment !== "contractor").length}
штат і часткові
Працівник Базовий оклад KPI коефіцієнт 13-та Річний бонус Разом
{team.filter(p => p.employment !== "contractor").map(p => {
const base = p.baseSalary * (p.partRate || 1);
const kpi = 0.85 + Math.random() * 0.3;
const thirteenth = Math.round(base);
const bonus = Math.round(base * kpi);
return (
{p.initials}
{p.name}
{window.formatMoney(base)} ₴
×{kpi.toFixed(2)}
{window.formatMoney(thirteenth)} ₴
{window.formatMoney(bonus)} ₴
{window.formatMoney(thirteenth + bonus)} ₴
);
})}
);
}
// ============ ІСТОРІЯ ВИПЛАТ ============
function SalaryHistoryPage({ role }) {
const team = window.DATA.TEAM;
const months = ["Грудень 2025", "Січень 2026", "Лютий 2026", "Березень 2026", "Квітень 2026"];
return (
Історія виплат
усі ЗП-виплати по людях
зберігається назавжди
Уся команда
{team.map(p => {p.name} )}
2026 рік 2025 рік
Експорт Excel
Працівник
{months.map(m => {m} )}
Усього
{team.map(p => {
const monthly = p.baseSalary * (p.partRate || 1);
const total = monthly * 5;
return (
{months.map(m => {window.formatMoney(Math.round(monthly * (0.95 + Math.random() * 0.1)))} )}
{window.formatMoney(total)} ₴
);
})}
);
}
// ============ ЖУРНАЛ АВТ. НАГЛЯДУ ============
function SupervisionJournalPage({ role }) {
const entries = [
{ date: "2026-05-26", obj: "ukp-89", who: "ok", title: "Перевірка вузлів примикання покрівлі", photos: 8, remarks: 2 },
{ date: "2026-04-15", obj: "ukp-89", who: "ok", title: "Контроль монтажу вентиляції", photos: 12, remarks: 0 },
{ date: "2026-03-22", obj: "ukp-89", who: "ap", title: "Зварювальні роботи каркасу", photos: 15, remarks: 1 },
{ date: "2026-02-10", obj: "ukp-89", who: "ok", title: "Бетонування плит перекриття", photos: 22, remarks: 0 },
{ date: "2026-01-18", obj: "ukp-89", who: "ap", title: "Армування фундаментів", photos: 18, remarks: 3 },
];
return (
Журнал авторського нагляду
записи виїздів
з фотофіксацією і зауваженнями
Як працює: кожен виїзд на майданчик автоматично створює запис з фото з телефону,
текстовими нотатками і списком зауважень. Юридично значимо — підписується КЕП.
{entries.map((e, i) => {
const obj = window.getObject(e.obj);
const p = window.getTeam(e.who);
return (
{obj?.code}
АН
{window.formatDate(e.date)}
{e.title}
{obj?.name}
{p?.name}
зауваження
0 ? "var(--late)" : "var(--c-green-deep)", fontSize: 14, fontWeight: 600}}>{e.remarks > 0 ? `${e.remarks}` : "немає"}
Відкрити
);
})}
);
}
// ============ АКТИ ВИКОНАНИХ РОБІТ ============
function CompletionActsPage({ role }) {
const [printAct, setPrintAct] = React.useState(null);
const acts = [
{ num: "47-АКТ-03", date: "2026-05-22", obj: "ukp-47", stage: "Здача робочого проєкту, етап 1", amount: 1260000, status: "signed", signedDate: "2026-05-24" },
{ num: "32-АКТ-01", date: "2025-11-15", obj: "ukp-32", stage: "Аванс після тендеру", amount: 930000, status: "signed", signedDate: "2025-11-18" },
{ num: "51-АКТ-02", date: "2026-04-18", obj: "ukp-51", stage: "Ескізний проєкт", amount: 425000, status: "signed", signedDate: "2026-04-20" },
{ num: "47-АКТ-04", date: "2026-05-25", obj: "ukp-47", stage: "Здача КЖ", amount: 840000, status: "draft" },
{ num: "89-АКТ-04", date: "2026-05-26", obj: "ukp-89", stage: "Авт. нагляд (травень)", amount: 40000, status: "pending" },
];
return (
Акти виконаних робіт
{acts.length} актів
{acts.filter(a => a.status === "signed").length} підписано
{acts.filter(a => a.status === "pending" || a.status === "draft").length} в процесі
№ акта Об'єкт Етап / робота Дата Сума Статус
{acts.map(a => {
const obj = window.getObject(a.obj);
const tone = a.status === "signed" ? "live" : a.status === "pending" ? "warn" : "neutral";
const label = a.status === "signed" ? "Підписано" : a.status === "pending" ? "Очікує підпису" : "Чернетка";
return (
{a.num}
{obj?.name}
{obj?.code}
{a.stage}
{window.formatDate(a.date)}
{window.formatMoney(a.amount)} ₴
{label}
{a.signedDate && {window.formatDate(a.signedDate)}
}
{ e.stopPropagation(); setPrintAct(a); }}>
Друк
Drive ↗
);
})}
{printAct &&
setPrintAct(null)} />}
);
}
// ============ ТЕХНІЧНІ УМОВИ ============
function TuPage({ role }) {
const tu = []; // демо-приклади прибрано — реальні ТУ заводитимуться пізніше
return (
Технічні умови (ТУ)
{tu.length} запитів
{tu.filter(t => t.status === "received").length} отримано
{tu.filter(t => t.status === "pending" || t.status === "in_progress").length} в процесі
Тип ТУ Об'єкт Орган Дата запиту Дата отримання Статус
{tu.map((t, i) => {
const obj = window.getObject(t.obj);
const tone = t.status === "received" ? "live" : t.status === "pending" ? "warn" : "neutral";
const label = t.status === "received" ? "Отримано" : t.status === "pending" ? "Простій" : "В процесі";
return (
{t.type}
{t.number && {t.number}
}
{obj?.code}
{obj?.name}
{t.organization}
{window.formatDate(t.requestDate)}
{t.receivedDate ? window.formatDate(t.receivedDate) : "—"}
{label} {t.note && {t.note}
}
);
})}
);
}
// ============ ДСНС / УЗГОДЖЕННЯ ============
function DsnsPage({ role }) {
const approvals = []; // демо-приклади прибрано — реальні узгодження заводитимуться пізніше
return (
Узгодження з органами
ДСНС · СЕС · архітектура · сільради
{approvals.filter(a => a.status === "approved").length} погоджено · {approvals.filter(a => a.status !== "approved").length} в процесі
Об'єкт Орган Подано Відповідь Статус
{approvals.map((a, i) => {
const obj = window.getObject(a.obj);
const tone = a.status === "approved" ? "live" : a.status === "pending" ? "warn" : "neutral";
const label = a.status === "approved" ? "Узгоджено" : a.status === "pending" ? "Простій" : "В процесі";
return (
{obj?.name}
{obj?.code}
{a.body}
{a.number && {a.number}
}
{window.formatDate(a.submittedDate)}
{a.responseDate ? window.formatDate(a.responseDate) : "—"}
{label} {a.note && {a.note}
}
Drive ↗
);
})}
);
}
// ============ КЕП-СЕРТИФІКАТИ ============
function KepPage({ role }) {
const certs = window.DATA.TEAM
.filter(p => p.kep)
.map(p => ({ person: p, ...p.kep, st: window.kepStatus(p.kep.validUntil) }))
.sort((a, b) => a.validUntil.localeCompare(b.validUntil));
const active = certs.filter(c => c.st.state === "active").length;
const expiring = certs.filter(c => c.st.state === "expiring").length;
const expired = certs.filter(c => c.st.state === "expired").length;
return (
КЕП-сертифікати команди
{active} активних
{expiring > 0 && <>{expiring} завершуються >}
{expired > 0 && <>{expired} прострочено >}
Чому критично: КЕП потрібен для підписання проєктної документації, договорів,
подачі в ЄДЕССБ. Прострочений КЕП = неможливість працювати. Сповіщення за 30 днів до завершення.
Дані синхронізовані з профілями у розділі «Команда».
Фахівець Тип КЕП Провайдер Дійсний до Залишок Статус
{certs.map((c, i) => {
const p = c.person;
const danger = c.st.state !== "active";
return (
{p?.initials}
{p?.name}
{p?.role}
{c.type}
{c.provider}
{window.formatDate(c.validUntil)}
{c.st.state === "expired" ? `прострочено ${Math.abs(c.st.days)} дн.` : `${c.st.days} дн.`}
{c.st.label}
{danger ? "Оновити" : "Drive ↗"}
);
})}
);
}
window.GanttPage = GanttPage;
window.ArchivePage = ArchivePage;
window.MaterialsPage = MaterialsPage;
window.AdvancePayPage = AdvancePayPage;
window.SickPayPage = SickPayPage;
window.BonusPayPage = BonusPayPage;
window.SalaryHistoryPage = SalaryHistoryPage;
window.SupervisionJournalPage = SupervisionJournalPage;
window.CompletionActsPage = CompletionActsPage;
window.TuPage = TuPage;
window.DsnsPage = DsnsPage;
window.KepPage = KepPage;