// Dashboard view: stats, project progress, upcoming tasks, activity.
const DBIcons = window.Icons;
const { projectProgress: dbProjectProgress, relativeDate: dbRelDate, formatDate: dbFmtDate, isOverdue: dbOverdue, parseDateKey: dbDate } = window.PMStore;
const { Tag: DBTag, Priority: DBPriority, ProgressBar: DBProgress } = window.UI;
function Dashboard({ state, user, onSelectTask, setActiveView, setActiveProject }) {
const total = state.tasks.length;
const done = state.tasks.filter(t => t.status === 'done').length;
const inProgress = state.tasks.filter(t => t.status === 'doing' || t.status === 'review').length;
const overdue = state.tasks.filter(t => dbOverdue(t.due, t.status)).length;
const dueToday = state.tasks.filter(t => {
if (!t.due || t.status === 'done') return false;
const d = dbDate(t.due); d.setHours(0,0,0,0);
const n = new Date(); n.setHours(0,0,0,0);
return d.getTime() === n.getTime();
}).length;
const upcoming = [...state.tasks]
.filter(t => t.due && t.status !== 'done')
.sort((a,b) => dbDate(a.due) - dbDate(b.due))
.slice(0, 6);
const tagById = Object.fromEntries(state.tags.map(t => [t.id, t]));
const projById = Object.fromEntries(state.projects.map(p => [p.id, p]));
// Activity heatmap — deterministic pattern based on actual tasks completed in recent days
const heat = React.useMemo(() => {
const cells = Array.from({ length: 100 }, (_, i) => {
// simple deterministic pattern; bias toward green
const seed = (i * 9301 + 49297) % 233280;
const r = seed / 233280;
if (r < 0.45) return 0;
if (r < 0.65) return 1;
if (r < 0.83) return 2;
if (r < 0.95) return 3;
return 4;
});
return cells;
}, []);
const greeting = (() => {
const h = new Date().getHours();
if (h < 6) return 'İyi geceler';
if (h < 12) return 'Günaydın';
if (h < 18) return 'İyi öğleden sonra';
return 'İyi akşamlar';
})();
const todayStr = new Date().toLocaleDateString('tr-TR', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' });
return (
{greeting}, {(user?.name || 'Hasan').split(' ')[0]}
{todayStr} · Bugün {dueToday} görev teslim, {inProgress} aktif görev var.
}
iconBg="var(--accent-soft)" iconColor="var(--accent-text)"
value={total} label="Toplam görev"
delta={`${done} tamamlandı`} deltaDir="up"
/>
}
iconBg="var(--status-doing-bg)" iconColor="var(--status-doing)"
value={inProgress} label="Devam eden"
delta={`${state.projects.length} aktif proje`}
/>
}
iconBg="var(--p-med-bg)" iconColor="var(--p-med)"
value={dueToday} label="Bugün teslim"
delta={dueToday > 0 ? 'Önceliklendir!' : 'Sakin bir gün'}
/>
}
iconBg="var(--p-high-bg)" iconColor="var(--p-high)"
value={overdue} label="Geciken"
delta={overdue > 0 ? 'İncele' : 'Hepsi güncel'}
deltaDir={overdue > 0 ? 'down' : 'up'}
/>
{/* Project progress */}
Proje ilerlemesi
setActiveView('kanban')}>Hepsini gör →
{state.projects.map(p => {
const pct = dbProjectProgress(p.id, state.tasks);
const projTasks = state.tasks.filter(t => t.projectId === p.id);
const doneCount = projTasks.filter(t => t.status === 'done').length;
return (
{ setActiveProject(p.id); setActiveView('kanban'); }}>
{window.projectAbbr(p.name)}
{p.name}
{doneCount}/{projTasks.length} görev · {p.description}
{pct}%
);
})}
{/* Upcoming + activity */}
Yaklaşan görevler
setActiveView('list')}>Liste →
{upcoming.length === 0 ? (
Yaklaşan görev yok 🎉
) : upcoming.map(t => {
const proj = projById[t.projectId];
const overdueT = dbOverdue(t.due, t.status);
const dt = dbDate(t.due);
const months = ['OCA','ŞUB','MAR','NİS','MAY','HAZ','TEM','AĞU','EYL','EKİ','KAS','ARA'];
return (
onSelectTask(t.id)}>
{dt.getDate()}
{months[dt.getMonth()]}
{t.title}
{proj && <>{proj.name}>}
·
{dbRelDate(t.due)}
);
})}
Son 100 günün etkinliği
az → çok
{heat.map((lvl, i) => (
0 ? `heat-${lvl}` : ''}`} />
))}
{done} görev tamamlandı
Bu çeyrekte
);
}
function StatCard({ icon, iconBg, iconColor, value, label, delta, deltaDir }) {
return (
{icon}
{value}
{label}
{delta &&
{delta}
}
);
}
window.Dashboard = Dashboard;