// 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;