From e4f5b09c12393a9be63e519ee074cffea5d63d43 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Fri, 11 Sep 2026 16:37:33 +0530 Subject: [PATCH] fix(posts): added countBy to group posts numbers --- .../pages/superadmin/SuperAdminDashboard.tsx | 48 ++++++++++---- handlers/superadmin.go | 62 +++++++++++++++++++ 2 files changed, 99 insertions(+), 11 deletions(-) diff --git a/app/src/pages/superadmin/SuperAdminDashboard.tsx b/app/src/pages/superadmin/SuperAdminDashboard.tsx index e2d10b8..4744eb9 100644 --- a/app/src/pages/superadmin/SuperAdminDashboard.tsx +++ b/app/src/pages/superadmin/SuperAdminDashboard.tsx @@ -132,8 +132,15 @@ interface PostsPage { hasMore: boolean; nextOffset: number; total: number; + /** Whole-table counts keyed by status, from the API. */ + statusCounts: Record; + /** Whole-table counts keyed by type_of_post, from the API. */ + typeCounts: Record; } +const asCounts = (v: unknown): Record => + v && typeof v === 'object' ? (v as Record) : {}; + async function fetchPosts(endpoint: string, offset = 0): Promise { const res = await fetch(`${endpoint}?offset=${offset}`, { credentials: 'include' }); if (!res.ok) { @@ -153,6 +160,8 @@ async function fetchPosts(endpoint: string, offset = 0): Promise { hasMore: Boolean(json.has_more), nextOffset: typeof json.next_offset === 'number' ? json.next_offset : offset + posts.length, total: typeof json.total_posts === 'number' ? json.total_posts : offset + posts.length, + statusCounts: asCounts(json.status_counts), + typeCounts: asCounts(json.type_counts), }; } @@ -349,9 +358,23 @@ interface InsightsProps { function Insights({ data, activeSource, onSourceSelect, activeStage, onStageSelect }: InsightsProps) { const loaded = [...data.faculty.posts, ...data.warden.posts, ...data.centrehead.posts]; const grandTotal = SECTIONS.reduce((a, s) => a + data[s.key].total, 0); - const resolved = loaded.filter(p => p.status.toLowerCase() === 'resolved_all').length; const comments = loaded.reduce((a, p) => a + (p.comments?.length ?? 0), 0); + // Whole-table counts, summed across the three sources. + const sumCounts = (pick: (page: PostsPage) => Record) => { + const out: Record = {}; + for (const s of SECTIONS) { + for (const [k, v] of Object.entries(pick(data[s.key]))) { + const key = k.toLowerCase(); + out[key] = (out[key] ?? 0) + v; + } + } + return out; + }; + const statusTotals = sumCounts(p => p.statusCounts); + const typeTotals = sumCounts(p => p.typeCounts); + const resolved = statusTotals['resolved_all'] ?? 0; + const sourceSegments: DonutSegment[] = SECTIONS.map(s => ({ key: s.key, label: s.label.replace(' Posts', ''), @@ -361,21 +384,22 @@ function Insights({ data, activeSource, onSourceSelect, activeStage, onStageSele })); const stageSegments: DonutSegment[] = STAGES.map(st => { - const inStage = loaded.filter(p => stageOf(p.status) === st.key); - const pending = inStage.filter(p => p.status.toLowerCase().startsWith('pending')).length; - const detail = st.statuses.length > 1 ? `${pending} pending · ${inStage.length - pending} resolved` : undefined; - return { key: st.key, label: st.label, value: inStage.length, color: st.color, detail }; + const count = (status: string) => statusTotals[status] ?? 0; + const value = st.statuses.reduce((a, status) => a + count(status), 0); + const pending = st.statuses.filter(x => x.startsWith('pending')).reduce((a, status) => a + count(status), 0); + const detail = st.statuses.length > 1 ? `${pending} pending · ${value - pending} resolved` : undefined; + return { key: st.key, label: st.label, value, color: st.color, detail }; }); - const civil = loaded.filter(p => p.type_of_post.toLowerCase() === 'civil').length; - const electrical = loaded.length - civil; + const civil = typeTotals['civil'] ?? 0; + const electrical = typeTotals['electrical'] ?? 0; return (
- +
@@ -390,15 +414,15 @@ function Insights({ data, activeSource, onSourceSelect, activeStage, onStageSele /> onStageSelect(key as Stage | null)} /> @@ -448,6 +472,8 @@ export function SuperAdminDashboard() { hasMore: page.hasMore, nextOffset: page.nextOffset, total: page.total, + statusCounts: page.statusCounts, + typeCounts: page.typeCounts, }, } : prev); } catch (err) { diff --git a/handlers/superadmin.go b/handlers/superadmin.go index 7126142..08b2b87 100644 --- a/handlers/superadmin.go +++ b/handlers/superadmin.go @@ -22,6 +22,26 @@ func pageOffset(c *gin.Context) int { return offset } +// countBy groups the rows of model by column and returns value -> count. +func countBy(db *gorm.DB, model any, column string) (map[string]int64, error) { + var rows []struct { + Key string + Count int64 + } + err := db.Model(model). + Select(column + " AS key, COUNT(*) AS count"). + Group(column). + Scan(&rows).Error + if err != nil { + return nil, err + } + out := make(map[string]int64, len(rows)) + for _, r := range rows { + out[r.Key] = r.Count + } + return out, nil +} + // SuperAdminGetFacultyPosts fetches faculty authored posts with a limit // of 25 latest ones. func (h *SuperAdminHandler) SuperAdminGetFacultyPosts(c *gin.Context) { @@ -55,6 +75,18 @@ func (h *SuperAdminHandler) SuperAdminGetFacultyPosts(c *gin.Context) { return } + // whole-table breakdowns, so the client can chart every post, not just the loaded page. + statusCounts, err := countBy(h.DB, &models.FacultyPost{}, "status") + if err != nil { + c.JSON(500, gin.H{"error": "failed to count posts at the moment."}) + return + } + typeCounts, err := countBy(h.DB, &models.FacultyPost{}, "type_of_post") + if err != nil { + c.JSON(500, gin.H{"error": "failed to count posts at the moment."}) + return + } + result = h.DB.Order("created_at DESC"). Offset(offset). Limit(superAdminPageSize + 1). @@ -85,6 +117,8 @@ func (h *SuperAdminHandler) SuperAdminGetFacultyPosts(c *gin.Context) { "has_more": hasMore, "next_offset": offset + len(posts), "total_posts": total, + "status_counts": statusCounts, + "type_counts": typeCounts, }) } @@ -121,6 +155,18 @@ func (h *SuperAdminHandler) SuperAdminGetWardenPosts(c *gin.Context) { return } + // whole-table breakdowns, so the client can chart every post, not just the loaded page. + statusCounts, err := countBy(h.DB, &models.WardenPost{}, "status") + if err != nil { + c.JSON(500, gin.H{"error": "failed to count posts at the moment."}) + return + } + typeCounts, err := countBy(h.DB, &models.WardenPost{}, "type_of_post") + if err != nil { + c.JSON(500, gin.H{"error": "failed to count posts at the moment."}) + return + } + result = h.DB.Order("created_at DESC"). Offset(offset). Limit(superAdminPageSize + 1). @@ -151,6 +197,8 @@ func (h *SuperAdminHandler) SuperAdminGetWardenPosts(c *gin.Context) { "has_more": hasMore, "next_offset": offset + len(posts), "total_posts": total, + "status_counts": statusCounts, + "type_counts": typeCounts, }) } @@ -186,6 +234,18 @@ func (h *SuperAdminHandler) SuperAdminGetCentreheadPosts(c *gin.Context) { return } + // whole-table breakdowns, so the client can chart every post, not just the loaded page. + statusCounts, err := countBy(h.DB, &models.CentreheadPost{}, "status") + if err != nil { + c.JSON(500, gin.H{"error": "failed to count posts at the moment."}) + return + } + typeCounts, err := countBy(h.DB, &models.CentreheadPost{}, "type_of_post") + if err != nil { + c.JSON(500, gin.H{"error": "failed to count posts at the moment."}) + return + } + result = h.DB.Order("created_at DESC"). Offset(offset). Limit(superAdminPageSize + 1). @@ -216,5 +276,7 @@ func (h *SuperAdminHandler) SuperAdminGetCentreheadPosts(c *gin.Context) { "has_more": hasMore, "next_offset": offset + len(posts), "total_posts": total, + "status_counts": statusCounts, + "type_counts": typeCounts, }) }