package handlers import ( "fmt" "gestion/db" "gestion/models" "net/http" "github.com/gin-gonic/gin" ) var weekdayNames = []string{"Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"} // GetAdminStats returns aggregated order & product statistics for the admin dashboard. func GetAdminStats(c *gin.Context) { database := c.MustGet("database").(*db.Database) gdb := database.GDB // ── Commandes par jour de la semaine (all time, non annulées) ────────────── var wdRows []models.WeekdayRow gdb.Raw(` SELECT EXTRACT(DOW FROM created_at)::int AS dow, COUNT(*) AS count FROM commandes WHERE status != 'cancelled' GROUP BY dow ORDER BY dow `).Scan(&wdRows) byWeekday := make([]gin.H, 7) wdMap := make(map[int]int, len(wdRows)) for _, r := range wdRows { wdMap[r.DOW] = r.Count } peakCount, peakWeekday := 0, "" for i := 0; i < 7; i++ { cnt := wdMap[i] byWeekday[i] = gin.H{"weekday": weekdayNames[i], "count": cnt} if cnt > peakCount { peakCount = cnt peakWeekday = weekdayNames[i] } } // ── Commandes par jour sur 30 jours ─────────────────────────────────────── var dayRows []models.DayRow gdb.Raw(` SELECT DATE(created_at) AS day, COUNT(*) AS count FROM commandes WHERE created_at >= NOW() - INTERVAL '30 days' AND status != 'cancelled' GROUP BY DATE(created_at) ORDER BY day `).Scan(&dayRows) byDay := make([]gin.H, len(dayRows)) for i, r := range dayRows { byDay[i] = gin.H{ "day": r.Day.Format("2006-01-02"), "label": r.Day.Format("02/01"), "count": r.Count, } } // ── Revenus par jour sur 30 jours (commandes approuvées) ───────────────── var dayRevRows []models.DayRevenueRow gdb.Raw(` SELECT DATE(created_at) AS day, COALESCE(SUM(total_prix - COALESCE(referral_used, 0)), 0) AS revenue FROM commandes WHERE created_at >= NOW() - INTERVAL '30 days' AND status = 'approved' GROUP BY DATE(created_at) ORDER BY day `).Scan(&dayRevRows) byDayRevenue := make([]gin.H, len(dayRevRows)) for i, r := range dayRevRows { byDayRevenue[i] = gin.H{ "day": r.Day.Format("2006-01-02"), "label": r.Day.Format("02/01"), "revenue": r.Revenue, } } // ── Commandes & revenus par heure (all time, non annulées) ─────────────── var hourRows []models.HourRow gdb.Raw(` SELECT EXTRACT(HOUR FROM created_at)::int AS hour, COUNT(*) AS count, COALESCE(SUM(total_prix - COALESCE(referral_used, 0)), 0) AS revenue FROM commandes WHERE status != 'cancelled' GROUP BY hour ORDER BY hour `).Scan(&hourRows) hourMap := make(map[int]models.HourRow, len(hourRows)) for _, r := range hourRows { hourMap[r.Hour] = r } byHour := make([]gin.H, 24) for h := 0; h < 24; h++ { r := hourMap[h] byHour[h] = gin.H{ "hour": h, "label": fmt.Sprintf("%02dh", h), "count": r.Count, "revenue": r.Revenue, } } // ── Top produits (quantité vendue, commandes terminées) ─────────────────── var prodRows []models.ProductRow gdb.Raw(` SELECT ci.product_id, ci.produit AS name, SUM(ci.quantite) AS total_quantity, COUNT(DISTINCT ci.command_id) AS order_count, SUM(ci.prix) AS revenue, COALESCE(p.category, '') AS category, COALESCE(cat.color, '#7c3aed') AS category_color FROM command_items ci JOIN commandes c ON c.id = ci.command_id LEFT JOIN products p ON p.id = ci.product_id LEFT JOIN categories cat ON cat.name = p.category WHERE c.status != 'cancelled' GROUP BY ci.product_id, ci.produit, p.category, cat.color ORDER BY total_quantity DESC LIMIT 15 `).Scan(&prodRows) topProducts := make([]gin.H, len(prodRows)) topProductName := "" for i, r := range prodRows { topProducts[i] = gin.H{ "product_id": r.ProductID, "name": r.Name, "quantity": r.Quantity, "order_count": r.OrderCount, "revenue": r.Revenue, "category": r.Category, "category_color": r.CategoryColor, } if i == 0 { topProductName = r.Name } } // ── Répartition des doses/quantités par produit ─────────────────────────── var qtyRows []models.QuantityBreakdownRow gdb.Raw(` SELECT ci.product_id, ci.produit AS product_name, ci.quantite AS quantity, COUNT(DISTINCT ci.command_id) AS order_count, SUM(ci.quantite) AS total_sold, SUM(ci.prix) AS revenue, COALESCE(cat.color, '#7c3aed') AS category_color FROM command_items ci JOIN commandes c ON c.id = ci.command_id LEFT JOIN products p ON p.id = ci.product_id LEFT JOIN categories cat ON cat.name = p.category WHERE c.status != 'cancelled' GROUP BY ci.product_id, ci.produit, ci.quantite, cat.color ORDER BY ci.product_id, COUNT(DISTINCT ci.command_id) DESC `).Scan(&qtyRows) type productGroup struct { ProductID int Name string CategoryColor string TotalOrders int Quantities []gin.H } var groups []productGroup groupIdx := map[int]int{} for _, r := range qtyRows { idx, ok := groupIdx[r.ProductID] if !ok { idx = len(groups) groups = append(groups, productGroup{ ProductID: r.ProductID, Name: r.ProductName, CategoryColor: r.CategoryColor, }) groupIdx[r.ProductID] = idx } groups[idx].TotalOrders += r.OrderCount groups[idx].Quantities = append(groups[idx].Quantities, gin.H{ "quantity": r.Quantity, "order_count": r.OrderCount, "total_sold": r.TotalSold, "revenue": r.Revenue, }) } // Trier par total de commandes décroissant, garder 15 max for i := 0; i < len(groups)-1; i++ { for j := i + 1; j < len(groups); j++ { if groups[j].TotalOrders > groups[i].TotalOrders { groups[i], groups[j] = groups[j], groups[i] } } } if len(groups) > 15 { groups = groups[:15] } byQuantity := make([]gin.H, len(groups)) for i, g := range groups { byQuantity[i] = gin.H{ "product_id": g.ProductID, "name": g.Name, "category_color": g.CategoryColor, "total_orders": g.TotalOrders, "quantities": g.Quantities, } } // ── Résumé global ───────────────────────────────────────────────────────── var totalOrders int64 var totalRevenue float64 gdb.Raw(`SELECT COUNT(*) FROM commandes WHERE status != 'cancelled'`).Scan(&totalOrders) gdb.Raw(`SELECT COALESCE(SUM(total_prix - COALESCE(referral_used, 0)), 0) FROM commandes WHERE status = 'approved'`).Scan(&totalRevenue) avgPerDay := 0.0 if totalOrders > 0 { // average over the last 30 days with data var activeDays int64 gdb.Raw(` SELECT COUNT(DISTINCT DATE(created_at)) FROM commandes WHERE created_at >= NOW() - INTERVAL '30 days' AND status != 'cancelled' `).Scan(&activeDays) if activeDays > 0 { var last30Count int64 gdb.Raw(` SELECT COUNT(*) FROM commandes WHERE created_at >= NOW() - INTERVAL '30 days' AND status != 'cancelled' `).Scan(&last30Count) avgPerDay = float64(last30Count) / float64(activeDays) } } c.JSON(http.StatusOK, gin.H{ "summary": gin.H{ "total_orders": totalOrders, "total_revenue": totalRevenue, "peak_weekday": peakWeekday, "top_product": topProductName, "avg_per_day": avgPerDay, }, "by_weekday": byWeekday, "by_day_30": byDay, "by_day_revenue": byDayRevenue, "by_hour": byHour, "top_products": topProducts, "by_quantity": byQuantity, }) }