-
-
-
- {isLoading ? '-' : stats?.tasks.overdue}
-
-
Overdue Tasks
+
+ {/* Archived and Cancelled - smaller at bottom */}
+
+
+
+
+ {isLoading ? '-' : stats?.tasks.cancelled} cancelled
+
+
+
+
+
+ {isLoading ? '-' : stats?.tasks.archived} archived
+
diff --git a/admin/src/types/models.ts b/admin/src/types/models.ts
index 475ac9a..46b16bd 100644
--- a/admin/src/types/models.ts
+++ b/admin/src/types/models.ts
@@ -517,9 +517,15 @@ export interface DashboardStats {
};
tasks: {
total: number;
- pending: number;
- completed: number;
+ active: number;
+ archived: number;
+ cancelled: number;
overdue: number;
+ new_30d: number;
+ pending: number;
+ in_progress: number;
+ completed: number;
+ on_hold: number;
};
contractors: {
total: number;
diff --git a/internal/admin/handlers/dashboard_handler.go b/internal/admin/handlers/dashboard_handler.go
index 9cc1bff..4d89633 100644
--- a/internal/admin/handlers/dashboard_handler.go
+++ b/internal/admin/handlers/dashboard_handler.go
@@ -48,10 +48,24 @@ type ResidenceStats struct {
// TaskStats holds task-related statistics
type TaskStats struct {
- Total int64 `json:"total"`
- Pending int64 `json:"pending"`
- Completed int64 `json:"completed"`
- Overdue int64 `json:"overdue"`
+ Total int64 `json:"total"`
+ Active int64 `json:"active"` // Non-archived, non-cancelled
+ Archived int64 `json:"archived"`
+ Cancelled int64 `json:"cancelled"`
+ Overdue int64 `json:"overdue"`
+ New30d int64 `json:"new_30d"`
+ // By status
+ Pending int64 `json:"pending"`
+ InProgress int64 `json:"in_progress"`
+ Completed int64 `json:"completed"`
+ OnHold int64 `json:"on_hold"`
+}
+
+// TaskStatusCount holds a single status count
+type TaskStatusCount struct {
+ StatusID uint `json:"status_id"`
+ StatusName string `json:"status_name"`
+ Count int64 `json:"count"`
}
// ContractorStats holds contractor-related statistics
@@ -103,17 +117,41 @@ func (h *AdminDashboardHandler) GetStats(c *gin.Context) {
// Task stats
h.db.Model(&models.Task{}).Count(&stats.Tasks.Total)
- h.db.Model(&models.Task{}).Where("is_cancelled = ? AND is_archived = ?", false, false).
- Joins("JOIN task_taskstatus ON task_taskstatus.id = task_task.status_id").
- Where("task_taskstatus.name IN ?", []string{"pending", "in_progress"}).
- Count(&stats.Tasks.Pending)
+ h.db.Model(&models.Task{}).Where("is_cancelled = ? AND is_archived = ?", false, false).Count(&stats.Tasks.Active)
+ h.db.Model(&models.Task{}).Where("is_archived = ?", true).Count(&stats.Tasks.Archived)
+ h.db.Model(&models.Task{}).Where("is_cancelled = ?", true).Count(&stats.Tasks.Cancelled)
+ h.db.Model(&models.Task{}).Where("created_at >= ?", thirtyDaysAgo).Count(&stats.Tasks.New30d)
+
+ // Task counts by status (using LEFT JOIN to handle tasks with no status)
h.db.Model(&models.Task{}).
- Joins("JOIN task_taskstatus ON task_taskstatus.id = task_task.status_id").
- Where("task_taskstatus.name = ?", "completed").
+ Where("is_cancelled = ? AND is_archived = ?", false, false).
+ Joins("LEFT JOIN task_taskstatus ON task_taskstatus.id = task_task.status_id").
+ Where("LOWER(task_taskstatus.name) = ? OR task_taskstatus.id IS NULL", "pending").
+ Count(&stats.Tasks.Pending)
+
+ h.db.Model(&models.Task{}).
+ Where("is_cancelled = ? AND is_archived = ?", false, false).
+ Joins("LEFT JOIN task_taskstatus ON task_taskstatus.id = task_task.status_id").
+ Where("LOWER(task_taskstatus.name) = ?", "in progress").
+ Count(&stats.Tasks.InProgress)
+
+ h.db.Model(&models.Task{}).
+ Where("is_cancelled = ? AND is_archived = ?", false, false).
+ Joins("LEFT JOIN task_taskstatus ON task_taskstatus.id = task_task.status_id").
+ Where("LOWER(task_taskstatus.name) = ?", "completed").
Count(&stats.Tasks.Completed)
- h.db.Model(&models.Task{}).Where("due_date < ? AND is_cancelled = ? AND is_archived = ?", now, false, false).
- Joins("JOIN task_taskstatus ON task_taskstatus.id = task_task.status_id").
- Where("task_taskstatus.name NOT IN ?", []string{"completed", "cancelled"}).
+
+ h.db.Model(&models.Task{}).
+ Where("is_cancelled = ? AND is_archived = ?", false, false).
+ Joins("LEFT JOIN task_taskstatus ON task_taskstatus.id = task_task.status_id").
+ Where("LOWER(task_taskstatus.name) = ?", "on hold").
+ Count(&stats.Tasks.OnHold)
+
+ // Overdue: past due date, not completed, not cancelled, not archived
+ h.db.Model(&models.Task{}).
+ Where("next_due_date < ? AND is_cancelled = ? AND is_archived = ?", now, false, false).
+ Joins("LEFT JOIN task_taskstatus ON task_taskstatus.id = task_task.status_id").
+ Where("LOWER(task_taskstatus.name) NOT IN ? OR task_taskstatus.id IS NULL", []string{"completed", "cancelled"}).
Count(&stats.Tasks.Overdue)
// Contractor stats