// create-project-modal.jsx — модалка створення проєкту з шаблону function CreateProjectModal({ template, onClose, onCreated }) { React.useEffect(() => { const onKey = (e) => e.key === "Escape" && onClose(); window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [onClose]); // Авто-код: УКП-2026-XX (NN+1 від останнього) const objects = window.DATA.OBJECTS || []; const year = new Date().getFullYear(); const maxNum = objects.reduce((m, o) => { const match = o.code && o.code.match(/(\d+)$/); return match ? Math.max(m, parseInt(match[1], 10)) : m; }, 0); const autoCode = `УКП-${year}-${String(maxNum + 1).padStart(2, "0")}`; const clients = window.DATA.CLIENTS || []; const team = window.DATA.TEAM || []; const isDesign = template.direction === "design"; // Чи має шаблон етап експертизи (схема намірів — не має) const hasExpertise = template.stageTemplateIds.some(id => { const st = window.getStageTemplate(id); return st && st.stage === "expertise"; }); const [form, setForm] = React.useState({ code: autoCode, name: "", clientId: clients[0]?.id || "", clientNew: "", address: "", deadline: "", contract: "", gipId: team.find(t => t.role && t.role.includes("ГІП"))?.id || team[0]?.id || "", grade: template.objectGrade || (isDesign ? "СС2" : ""), expertiseScope: template.expertiseScope || (isDesign && hasExpertise ? "full" : ""), teamIds: team.find(t => t.role && t.role.includes("ГІП")) ? [team.find(t => t.role && t.role.includes("ГІП")).id] : [], }); const update = (k, v) => setForm(prev => ({ ...prev, [k]: v })); // Підрахунок задач const stages = template.stageTemplateIds.map(id => window.getStageTemplate(id)).filter(Boolean); const totalTasks = stages.reduce((s, st) => s + st.taskTemplateIds.length, 0); const canSubmit = form.name.trim().length > 0 && (form.clientId || form.clientNew.trim().length > 0) && form.deadline; const onSubmit = () => { if (!canSubmit) return; const newObj = window.createObjectFromTemplate(template, form); onCreated && onCreated(newObj.id); }; return ( <>
Створити проєкт з шаблону
{template.name}
update("code", e.target.value)} />
Авто-генерується. Можна змінити.
update("name", e.target.value)} placeholder="Напр.: ЖК «Подільський», Школа №14" autoFocus />
{!form.clientId && ( update("clientNew", e.target.value)} placeholder="Назва нового замовника" style={{marginTop: 6}} /> )}
update("address", e.target.value)} placeholder="Київ, Подільський р-н" />
update("deadline", e.target.value)} />
update("contract", e.target.value)} placeholder="Напр.: 8.4 млн ₴" />
{isDesign && ( <>
{hasExpertise && (
)} )}
{team.map(p => ( ))}
{team.map(p => { const isOn = form.teamIds.includes(p.id); return ( ); })}

Що буде створено

{stages.length} {window.plural(stages.length, "етап", "етапи", "етапів")} · {totalTasks} {window.plural(totalTasks, "задача", "задачі", "задач")}
{stages.map((st, i) => (
{st.name} {st.taskTemplateIds.length} {window.plural(st.taskTemplateIds.length, "задача", "задачі", "задач")}
))}
); } // =================================================================== // CREATE OBJECT FROM TEMPLATE — мутує window.DATA + сповіщає React // =================================================================== window.createObjectFromTemplate = function(template, form) { // 1) Створюємо об'єкт const newId = "obj-" + Math.random().toString(36).slice(2, 8); // Знаходимо/створюємо клієнта let clientName = ""; if (form.clientId) { const c = window.DATA.CLIENTS.find(c => c.id === form.clientId); clientName = c ? c.name : form.clientId; } else if (form.clientNew) { clientName = form.clientNew; // Додаємо нового клієнта const newClient = { id: "c-" + Math.random().toString(36).slice(2, 6), name: form.clientNew, short: form.clientNew, }; window.DATA.CLIENTS = [newClient, ...window.DATA.CLIENTS]; } // Підрахунок plan: задачі по кожному етапу const plan = {}; // Підбираємо stage template для експертизи відповідно до обраного користувачем обсягу const expertiseStageMap = { estimate: "stp-expertise-estimate-only", strength: "stp-expertise-strength", full: "stp-expertise-full", }; const desiredExpertiseStage = expertiseStageMap[form.expertiseScope]; const stageTplIds = template.stageTemplateIds.map(id => { const st = window.getStageTemplate(id); if (st && st.stage === "expertise" && desiredExpertiseStage && desiredExpertiseStage !== id) { return desiredExpertiseStage; } return id; }); const stages = stageTplIds.map(id => window.getStageTemplate(id)).filter(Boolean); stages.forEach(st => { plan[st.stage] = (plan[st.stage] || 0) + st.taskTemplateIds.length; }); // Дедлайн у форматі "ДД.ММ" let deadlineLabel = form.deadline; if (form.deadline) { const d = new Date(form.deadline); if (!isNaN(d.getTime())) { deadlineLabel = String(d.getDate()).padStart(2, "0") + "." + String(d.getMonth() + 1).padStart(2, "0"); } } const newObj = { id: newId, code: form.code, name: form.name, client: clientName, address: form.address, grade: form.grade || undefined, expertiseScope: form.expertiseScope || undefined, activeStage: stages[0]?.stage || "project", revision: "Ред. 1", deadline: deadlineLabel, contract: form.contract, team: Array.from(new Set([form.gipId, ...(form.teamIds || [])].filter(Boolean))), plan, createdFromTemplate: template.id, createdAt: new Date().toISOString(), }; // 2) Створюємо задачі const tasks = []; stages.forEach(st => { st.taskTemplateIds.forEach((ttId, idx) => { const tt = window.getTaskTemplate(ttId); if (!tt) return; // Призначення: якщо роль gip → беремо ГІПа з форми let assignee = null; if (tt.assigneeRole === "gip") assignee = form.gipId; // інакше null — поки не призначено tasks.push({ id: "t-" + Math.random().toString(36).slice(2, 8), title: tt.title, obj: newId, stage: st.stage, assignee: assignee, section: tt.section, estimate: tt.estDays + " дн", status: "todo", templateTaskId: tt.id, templateStageId: st.id, deadlineOffset: tt.deadlineOffset, subtasks: tt.subtasks ? tt.subtasks.map(s => ({ title: s.title, done: false })) : undefined, }); }); }); // 3) Мутуємо глобальні дані window.DATA.OBJECTS = [newObj, ...window.DATA.OBJECTS]; // Для геттера getObject (якщо є мапа) — оновити при наступному читанні // 4) Викликаємо хук React, щоб app пере-рендерився if (window.__erpStore) { window.__erpStore.addObject(newObj, tasks); } return newObj; }; window.CreateProjectModal = CreateProjectModal;