From 83384db014700d387ba6ca53a0dcecc063dfce87 Mon Sep 17 00:00:00 2001 From: Trey t Date: Sat, 6 Dec 2025 12:09:27 -0600 Subject: [PATCH] Improve admin dashboard task status overview MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add all task statuses: Pending, In Progress, On Hold, Completed - Add active, archived, cancelled, and new_30d task counts - Fix completed query to filter non-archived/non-cancelled tasks - Update dashboard UI with better layout showing all statuses - Show cancelled and archived counts in footer row 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- admin/src/app/(dashboard)/page.tsx | 78 ++++++++++++++++---- admin/src/types/models.ts | 10 ++- internal/admin/handlers/dashboard_handler.go | 64 ++++++++++++---- 3 files changed, 122 insertions(+), 30 deletions(-) diff --git a/admin/src/app/(dashboard)/page.tsx b/admin/src/app/(dashboard)/page.tsx index 003cff2..c17b8f2 100644 --- a/admin/src/app/(dashboard)/page.tsx +++ b/admin/src/app/(dashboard)/page.tsx @@ -23,6 +23,10 @@ import { AlertTriangle, CheckCircle, Clock, + PlayCircle, + XCircle, + Archive, + PauseCircle, } from 'lucide-react'; export default function DashboardPage() { @@ -89,7 +93,7 @@ export default function DashboardPage() { {isLoading ? '-' : stats?.tasks.total.toLocaleString()}

- {isLoading ? '' : `${stats?.tasks.pending} pending, ${stats?.tasks.completed} completed`} + {isLoading ? '' : `${stats?.tasks.active} active, ${stats?.tasks.new_30d} new this month`}

@@ -178,35 +182,79 @@ export default function DashboardPage() { Task Status Overview - Current task distribution + + {isLoading ? 'Loading...' : `${stats?.tasks.total} total tasks across all residences`} + -
+
+ {/* Overdue - Highlighted at top */} + {!isLoading && (stats?.tasks.overdue ?? 0) > 0 && ( +
+ +
+
+ {stats?.tasks.overdue} +
+
Overdue
+
+
+ )} + + {/* Active statuses */}
- +
-
+
{isLoading ? '-' : stats?.tasks.pending}
Pending
-
- + +
+
-
+
+ {isLoading ? '-' : stats?.tasks.in_progress} +
+
In Progress
+
+
+ +
+ +
+
+ {isLoading ? '-' : stats?.tasks.on_hold} +
+
On Hold
+
+
+ +
+ +
+
{isLoading ? '-' : stats?.tasks.completed}
Completed
-
- -
-
- {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