({ project, tasks })
| 11 | }; |
| 12 | |
| 13 | const ProjectAnalytics = ({ project, tasks }) => { |
| 14 | const { stats, statusData, typeData, priorityData } = useMemo(() => { |
| 15 | const now = new Date(); |
| 16 | const total = tasks.length; |
| 17 | |
| 18 | const stats = { |
| 19 | total, |
| 20 | completed: 0, |
| 21 | inProgress: 0, |
| 22 | todo: 0, |
| 23 | overdue: 0, |
| 24 | }; |
| 25 | |
| 26 | const statusMap = { TODO: 0, IN_PROGRESS: 0, DONE: 0 }; |
| 27 | const typeMap = { TASK: 0, BUG: 0, FEATURE: 0, IMPROVEMENT: 0, OTHER: 0 }; |
| 28 | const priorityMap = { LOW: 0, MEDIUM: 0, HIGH: 0 }; |
| 29 | |
| 30 | tasks.forEach((t) => { |
| 31 | if (t.status === "DONE") stats.completed++; |
| 32 | if (t.status === "IN_PROGRESS") stats.inProgress++; |
| 33 | if (t.status === "TODO") stats.todo++; |
| 34 | if (new Date(t.due_date) < now && t.status !== "DONE") stats.overdue++; |
| 35 | |
| 36 | if (statusMap[t.status] !== undefined) statusMap[t.status]++; |
| 37 | if (typeMap[t.type] !== undefined) typeMap[t.type]++; |
| 38 | if (priorityMap[t.priority] !== undefined) priorityMap[t.priority]++; |
| 39 | }); |
| 40 | |
| 41 | return { |
| 42 | stats, |
| 43 | statusData: Object.entries(statusMap).map(([k, v]) => ({ name: k.replace("_", " "), value: v })), |
| 44 | typeData: Object.entries(typeMap).filter(([_, v]) => v > 0).map(([k, v]) => ({ name: k, value: v })), |
| 45 | priorityData: Object.entries(priorityMap).map(([k, v]) => ({ |
| 46 | name: k, |
| 47 | value: v, |
| 48 | percentage: total > 0 ? Math.round((v / total) * 100) : 0, |
| 49 | })), |
| 50 | }; |
| 51 | }, [tasks]); |
| 52 | |
| 53 | const completionRate = stats.total ? Math.round((stats.completed / stats.total) * 100) : 0; |
| 54 | |
| 55 | const metrics = [ |
| 56 | { |
| 57 | label: "Completion Rate", |
| 58 | value: `${completionRate}%`, |
| 59 | color: "text-emerald-600 dark:text-emerald-400", |
| 60 | icon: <CheckCircle className="size-5 text-emerald-600 dark:text-emerald-400" />, |
| 61 | bg: "bg-emerald-200 dark:bg-emerald-500/10", |
| 62 | }, |
| 63 | { |
| 64 | label: "Active Tasks", |
| 65 | value: stats.inProgress, |
| 66 | color: "text-blue-600 dark:text-blue-400", |
| 67 | icon: <Clock className="size-5 text-blue-600 dark:text-blue-400" />, |
| 68 | bg: "bg-blue-200 dark:bg-blue-500/10", |
| 69 | }, |
| 70 | { |
nothing calls this directly
no outgoing calls
no test coverage detected