// Kapasite Görünümü — Haftalık iş yükü planlama const CapIcons = window.Icons; const CAP_WEEKS = 10; // kaç hafta gösterilsin function capStartOfDay(d) { const r = new Date(d); r.setHours(0,0,0,0); return r; } function capStartOfWeek(d) { const r = capStartOfDay(d); const day = (r.getDay() + 6) % 7; // Pazartesi = 0 r.setDate(r.getDate() - day); return r; } function capAddDays(d, n) { const r = new Date(d); r.setDate(r.getDate() + n); return capStartOfDay(r); } function capDateKey(d) { return d.toISOString().slice(0, 10); } function capFmtShort(d) { return d.toLocaleDateString('tr-TR', { day: 'numeric', month: 'short' }); } function capFmtMonth(d) { return d.toLocaleDateString('tr-TR', { month: 'long', year: 'numeric' }); } function capWeekNo(d) { const jan1 = new Date(d.getFullYear(), 0, 1); return Math.ceil(((d - jan1) / 86400000 + jan1.getDay() + 1) / 7); } function capLoadLevel(count, blocked) { if (blocked) return { label: 'Kapalı', tone: 'blocked', pct: 100 }; if (count === 0) return { label: 'Boş', tone: 'empty', pct: 0 }; if (count <= 3) return { label: 'Müsait', tone: 'light', pct: Math.round(count / 3 * 40) }; if (count <= 6) return { label: 'Dengeli', tone: 'ok', pct: Math.round(40 + (count - 3) / 3 * 35) }; if (count <= 9) return { label: 'Yoğun', tone: 'busy', pct: Math.round(75 + (count - 6) / 3 * 20) }; return { label: 'Dolu', tone: 'full', pct: 100 }; } function CapWeekCard({ week, tasks, blocked, onToggleBlock, onSelectTask, projById, isCurrentWeek }) { const [expanded, setExpanded] = React.useState(false); const load = capLoadLevel(tasks.length, blocked); const weekEnd = capAddDays(week, 6); const toneColors = { blocked: { bg: 'var(--bg-sunken)', bar: 'var(--text-faint)', badge: '#64748b' }, empty: { bg: 'var(--bg-elev)', bar: 'var(--accent)', badge: 'var(--accent)' }, light: { bg: 'var(--bg-elev)', bar: '#22c55e', badge: '#16a34a' }, ok: { bg: 'var(--bg-elev)', bar: '#f59e0b', badge: '#d97706' }, busy: { bg: 'var(--bg-elev)', bar: '#f97316', badge: '#ea580c' }, full: { bg: 'var(--bg-elev)', bar: '#ef4444', badge: '#dc2626' }, }; const colors = toneColors[load.tone]; return (
{/* Üst satır */}
!blocked && setExpanded(v => !v)}>
Hafta {capWeekNo(week)} {capFmtShort(week)} — {capFmtShort(weekEnd)} {isCurrentWeek && Bu Hafta}
{load.label} {!blocked && ( {tasks.length} görev )} {!blocked && tasks.length > 0 && ( )}
{/* Yük çubuğu */} {!blocked && (
)} {/* Günler */} {!blocked && (
{Array.from({ length: 7 }, (_, i) => { const day = capAddDays(week, i); const key = capDateKey(day); const dayTasks = tasks.filter(t => t.due === key); const dayNames = ['Pzt','Sal','Çar','Per','Cum','Cmt','Paz']; const isToday = capDateKey(day) === capDateKey(new Date()); const isWeekend = i >= 5; return (
{dayNames[i]} {day.getDate()} {dayTasks.length > 0 && ( {dayTasks.length} )}
); })}
)} {/* Görev listesi (açılınca) */} {expanded && !blocked && tasks.length > 0 && (
{tasks.map(t => { const proj = projById[t.projectId]; const isOverdue = t.due < capDateKey(new Date()) && t.status !== 'done'; return (
onSelectTask(t.id)}> {t.title} {proj?.name || ''} {t.due}
); })}
)}
); } function CapacityView({ state, onSelectTask, updateWidgets }) { const tasks = (state.tasks || []).filter(t => t.status !== 'done' && t.due); const projects = state.projects || []; const projById = Object.fromEntries(projects.map(p => [p.id, p])); const widgets = state.widgets || {}; const blockedWeeks = widgets.blockedWeeks || []; const today = capStartOfDay(new Date()); const weekStart = capStartOfWeek(today); const weeks = Array.from({ length: CAP_WEEKS }, (_, i) => capAddDays(weekStart, i * 7)); const toggleBlock = (weekKey) => { const next = blockedWeeks.includes(weekKey) ? blockedWeeks.filter(w => w !== weekKey) : [...blockedWeeks, weekKey]; updateWidgets({ blockedWeeks: next }); }; // Özet hesapla const firstFree = weeks.find(w => { const key = capDateKey(w); if (blockedWeeks.includes(key)) return false; const wTasks = tasks.filter(t => t.due >= key && t.due <= capDateKey(capAddDays(w, 6))); return wTasks.length <= 3; }); const busiestWeek = weeks.reduce((max, w) => { const key = capDateKey(w); const count = tasks.filter(t => t.due >= key && t.due <= capDateKey(capAddDays(w, 6))).length; return count > (max.count || 0) ? { week: w, count } : max; }, {}); // Ay başlıkları let lastMonth = null; return (

Kapasite Planlaması

Önümüzdeki {CAP_WEEKS} haftalık iş yükü ve müsaitlik görünümü

{firstFree && (
İlk müsait: {capFmtShort(firstFree)}
)} {busiestWeek.week && busiestWeek.count > 0 && (
En yoğun: {capFmtShort(busiestWeek.week)} ({busiestWeek.count} görev)
)}
{weeks.map(w => { const key = capDateKey(w); const month = capFmtMonth(w); const showMonth = month !== lastMonth; lastMonth = month; const wEnd = capAddDays(w, 6); const wTasks = tasks.filter(t => t.due >= key && t.due <= capDateKey(wEnd)); const isCurrentWeek = capDateKey(weekStart) === key; return ( {showMonth && (
{month}
)} toggleBlock(key)} onSelectTask={onSelectTask} projById={projById} isCurrentWeek={isCurrentWeek} />
); })}
); } window.CapacityView = CapacityView;