// Gantt view: horizontal timeline of all projects & tasks const GTIcons = window.Icons; const { parseDateKey: gtDate, toDateKey: gtDateKey, relativeDate: gtRel } = window.PMStore; const ZOOM_OPTS = [ { id: 'week', label: '2 Hafta', days: 14 }, { id: 'month', label: '1 Ay', days: 30 }, { id: 'quarter', label: '3 Ay', days: 90 }, ]; function addDaysGT(d, n) { const r = new Date(d); r.setDate(r.getDate() + n); return r; } function startOfDayGT(d) { const r = new Date(d); r.setHours(0,0,0,0); return r; } function sameMonthYear(d) { return d.toLocaleDateString('tr-TR', { month: 'short', year: '2-digit' }); } function GanttView({ state, onSelectTask }) { const [zoom, setZoom] = React.useState('month'); const [projectFilter, setPF] = React.useState('all'); const [showCompleted, setShowComp] = React.useState(false); const [offset, setOffset] = React.useState(0); // day offset for panning const zoomCfg = ZOOM_OPTS.find(z => z.id === zoom) || ZOOM_OPTS[1]; const today = startOfDayGT(new Date()); const rangeStart = addDaysGT(today, -Math.round(zoomCfg.days * 0.3) + offset); const rangeEnd = addDaysGT(rangeStart, zoomCfg.days); const totalDays = zoomCfg.days; const projects = (state.projects || []).filter(p => !p.archived && (projectFilter === 'all' || p.id === projectFilter)); const allTasks = state.tasks || []; const dayPct = (d) => Math.max(0, Math.min(100, ((startOfDayGT(d) - rangeStart) / (1000 * 60 * 60 * 24 * totalDays)) * 100)); // Day column headers const dayHeaders = React.useMemo(() => { const headers = []; let prev = ''; for (let i = 0; i <= totalDays; i++) { const d = addDaysGT(rangeStart, i); const label = d.getDate() === 1 || i === 0 ? sameMonthYear(d) : ''; headers.push({ d, label, isToday: startOfDayGT(d).getTime() === today.getTime() }); } return headers; }, [zoom, offset]); const todayPct = dayPct(today); return (
{/* Toolbar */}
{ZOOM_OPTS.map(z => ( ))}
{/* Chart area */}
{/* Left panel: project + task names */}
Görev
{projects.map(p => { const pTasks = allTasks.filter(t => t.projectId === p.id && !t.archived && (showCompleted || t.status !== 'done') && t.due ); if (!pTasks.length) return null; return (
{p.name}
{pTasks.map(t => (
onSelectTask(t.id)} title={t.title}> {t.title}
))}
); })}
{/* Right panel: timeline */}
{/* Date headers */}
{dayHeaders.filter((_, i) => zoom === 'week' || i % (zoom === 'month' ? 3 : 7) === 0).map((h, i) => (
{h.label || h.d.getDate()}
))}
{/* Grid + task bars */}
{/* Today line */} {todayPct >= 0 && todayPct <= 100 && (
)} {projects.map(p => { const pTasks = allTasks.filter(t => t.projectId === p.id && !t.archived && (showCompleted || t.status !== 'done') && t.due ); if (!pTasks.length) return null; return (
{pTasks.map(t => { const dueD = gtDate(t.due); const startD = t.createdAt ? startOfDayGT(new Date(t.createdAt)) : startOfDayGT(dueD); const left = dayPct(startD); const right = dayPct(dueD); const width = Math.max(right - left, 0.8); const overdue = t.status !== 'done' && dueD < today; return (
{width > 0.1 && (
onSelectTask(t.id)} title={`${t.title} — ${gtRel(t.due)}`} > {t.title}
)} {/* Due date dot if bar too narrow */} {width <= 0.1 && (
onSelectTask(t.id)} title={t.title} /> )}
); })} ); })}
{projects.every(p => !allTasks.some(t => t.projectId === p.id && t.due)) && (
Teslim tarihi olan görev bulunamadı
Görevlere teslim tarihi ekleyin.
)}
); } window.GanttView = GanttView;