// Megosztott nézetek: Dashboard, Ütemterv (Gantt), Dokumentumok const Dashboard = ({ user }) => { const [, force] = React.useReducer(x => x + 1, 0); const [edit, setEdit] = React.useState(false); const [openPhaseId, setOpenPhase] = React.useState(null); const [editTask, setEditTask] = React.useState(null); const [newTaskFor, setNewTaskFor] = React.useState(null); const [editPhase, setEditPhase] = React.useState(null); const [newPhase, setNewPhase] = React.useState(false); const [confirmDel, setConfirmDel] = React.useState(null); const totalBudget = window.totalBudget(); const totalSpent = window.totalSpent(); const totalPending= window.totalPending(); const totalPaid = window.totalPaid(); const isAdmin = user?.role === 'admin'; return (
Projekt

Hogy állunk

{isAdmin && ( )}
{/* Pénzügyi áttekintés */}
Összköltségvetés
{window.formatHufShort(totalBudget)} Ft
Elköltve
{Math.round(totalSpent / totalBudget * 100)}%
{/* Fázisok bontva */}

Fázisonként

{edit && ( )}
{window.PHASES.map(p => { const total = window.phaseBudget(p.id); const spent = window.phaseSpent(p.id); if (total === 0 && !edit) return null; const tasks = window.PARENT_TASKS.filter(t => t.phaseId === p.id); const isOpen = edit && openPhaseId === p.id; return (
{window.formatHufShort(spent)}
/ {window.formatHufShort(total)}
{edit && (
)}
{isOpen && (
{tasks.length === 0 && (
Még nincs tétel ebben a fázisban.
)} {tasks.map(t => { const sp = window.parentTaskSpent(t.id); return (
{t.name}
{window.formatHufShort(t.budget)} Ft
); })}
)}
); })}
{/* Edit sheets — admin */} {newTaskFor && ( setNewTaskFor(null)} onSave={async (data) => { try { await window.api.parentTasks.save({ phase_id: newTaskFor.id, name: data.name, budget: data.budget }); await window.refreshData(); } catch (err) { alert(window.apiErrorMessage(err)); } setNewTaskFor(null); force(); }}/> )} {editTask && ( setEditTask(null)} onSave={async (data) => { try { await window.api.parentTasks.save({ id: editTask.task.id, phase_id: editTask.phase.id, name: data.name, budget: data.budget }); await window.refreshData(); } catch (err) { alert(window.apiErrorMessage(err)); } editTask.task.name = data.name; editTask.task.budget = data.budget; setEditTask(null); force(); }}/> )} {newPhase && ( setNewPhase(false)} onSave={async (data) => { try { await window.api.phases.save(data); await window.refreshData(); } catch (err) { alert(window.apiErrorMessage(err)); } setNewPhase(false); force(); }}/> )} {editPhase && ( setEditPhase(null)} onSave={async (data) => { try { await window.api.phases.save({ id: editPhase.id, ...data }); await window.refreshData(); } catch (err) { alert(window.apiErrorMessage(err)); } Object.assign(editPhase, data); setEditPhase(null); force(); }}/> )} {confirmDel && ( setConfirmDel(null)} title="Törlés">
Biztosan törölni szeretnéd: {confirmDel.target.name}? {confirmDel.kind === 'phase' && (
A fázishoz tartozó {window.PARENT_TASKS.filter(t => t.phaseId === confirmDel.target.id).length} tétel is törlődik.
)}
)}
); }; const Mini = ({ label, value, color = 'var(--ink)' }) => (
{label}
{window.formatHufShort(value)}
); // === Ütemterv (Gantt mobil-barát) === const Timeline = ({ user }) => { const [, force] = React.useReducer(x => x + 1, 0); const [edit, setEdit] = React.useState(false); const [editPhase, setEditPhase] = React.useState(null); const [newPhase, setNewPhase] = React.useState(false); const isAdmin = user?.role === 'admin'; const today = new Date(window.TODAY); const phases = edit ? window.PHASES : window.PHASES.filter(p => window.phaseBudget(p.id) > 0); if (phases.length === 0) { return (

Ütemterv

Még nincs fázis.
); } // Időablak: legkorábbi start → legkésőbbi end const dates = phases.flatMap(p => [new Date(p.start), new Date(p.end)]); const minD = new Date(Math.min(...dates)); const maxD = new Date(Math.max(...dates)); const totalDays = (maxD - minD) / 86400000; // Hónap-fejléc const months = []; let cur = new Date(minD.getFullYear(), minD.getMonth(), 1); while (cur <= maxD) { months.push(new Date(cur)); cur = new Date(cur.getFullYear(), cur.getMonth() + 1, 1); } const monthLabels = ['jan', 'feb', 'már', 'ápr', 'máj', 'jún', 'júl', 'aug', 'szept', 'okt', 'nov', 'dec']; const todayPct = ((today - minD) / 86400000) / totalDays * 100; return (

Ütemterv

{isAdmin && ( )}
{edit ? 'Kattints egy fázisra a kezdő/záró dátum vagy név módosításához.' : 'Tervezett ütemezés. A piros sávok lemaradásban vannak a mai naphoz képest.'}
{/* Hónap fejléc */}
{months.map((m, i) => { const pct = ((m - minD) / 86400000) / totalDays * 100; return (
{monthLabels[m.getMonth()]} {String(m.getFullYear()).slice(2)}
); })} {/* Today marker */}
{/* Sávok */}
{phases.map((p, i) => { const start = new Date(p.start); const end = new Date(p.end); const startPct = ((start - minD) / 86400000) / totalDays * 100; const widthPct = ((end - start) / 86400000) / totalDays * 100; const overdue = today > end && window.phaseSpent(p.id) < window.phaseBudget(p.id) * 0.95; const inProgress = today >= start && today <= end; const done = window.phaseSpent(p.id) >= window.phaseBudget(p.id) * 0.95; const progressPct = window.phaseBudget(p.id) > 0 ? Math.min(100, (window.phaseSpent(p.id) / window.phaseBudget(p.id)) * 100) : 0; return (
setEditPhase(p) : undefined} style={{ display: 'flex', alignItems: 'center', padding: '6px 12px', gap: 10, borderBottom: i < phases.length - 1 ? '1px solid var(--line-2)' : 'none', cursor: edit ? 'pointer' : 'default' }}>
{p.code} {p.name} {edit && }
{/* Today line */}
{/* Bar */}
); })}
{/* Legenda */}
kész folyamatban lemaradásban ma ({window.formatDate(window.TODAY)})
{edit && ( )} {editPhase && ( setEditPhase(null)} onSave={async (data) => { try { await window.api.phases.save({ id: editPhase.id, ...data }); await window.refreshData(); } catch (err) { alert(window.apiErrorMessage(err)); } Object.assign(editPhase, data); setEditPhase(null); force(); }}/> )} {newPhase && ( setNewPhase(false)} onSave={async (data) => { try { await window.api.phases.save(data); await window.refreshData(); } catch (err) { alert(window.apiErrorMessage(err)); } setNewPhase(false); force(); }}/> )}
); }; // === Dokumentumok === const Documents = ({ user }) => { const [filter, setFilter] = React.useState('all'); const [showUpload, setShowUpload] = React.useState(false); const tags = ['all', ...Object.keys(window.TAG_COLORS)]; const docs = filter === 'all' ? window.DOCUMENTS : window.DOCUMENTS.filter(d => d.tag === filter); return (

Dokumentumok

{/* Tag szűrő */}
{tags.map(t => { const color = window.TAG_COLORS[t]; const active = filter === t; return ( ); })}
{docs.map(d => { const u = window.getUser(d.uploadedBy); const tagColor = window.TAG_COLORS[d.tag]; return (
{(d.type || '').toUpperCase()}
{d.name}
{d.tag} {u?.name?.split(' ')[0]} · {window.relativeDay(d.uploadedAt)} · {d.size}
); })}
{showUpload && setShowUpload(false)}/>}
); }; const DocumentUploadSheet = ({ onClose }) => { const [file, setFile] = React.useState(null); const [name, setName] = React.useState(''); const [tag, setTag] = React.useState(Object.keys(window.TAG_COLORS)[0] || ''); const [busy, setBusy] = React.useState(false); const fileRef = React.useRef(null); const onPick = (e) => { const f = e.target.files?.[0]; if (!f) return; setFile(f); if (!name) setName(f.name.replace(/\.[^.]+$/, '')); }; const submit = async () => { if (!file || !name.trim() || !tag) return; setBusy(true); try { const fd = new FormData(); fd.append('file', file); fd.append('name', name.trim()); fd.append('tag', tag); await window.api.documents.upload(fd); await window.refreshData(); onClose(); } catch (err) { alert(window.apiErrorMessage(err)); } finally { setBusy(false); } }; return (
setName(e.target.value)} placeholder="Pl. Belsőépítészeti koncepció v4"/>
{Object.keys(window.TAG_COLORS).map(t => { const c = window.TAG_COLORS[t]; const active = tag === t; return ( ); })}
); }; Object.assign(window, { Dashboard, Timeline, Documents, DocumentUploadSheet, Mini });