feat: add stats page for admin
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"gestion/db"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type weekdayRow struct {
|
||||
DOW int `gorm:"column:dow"`
|
||||
Count int `gorm:"column:count"`
|
||||
}
|
||||
|
||||
type dayRow struct {
|
||||
Day time.Time `gorm:"column:day"`
|
||||
Count int `gorm:"column:count"`
|
||||
}
|
||||
|
||||
type productRow struct {
|
||||
ProductID int `gorm:"column:product_id"`
|
||||
Name string `gorm:"column:name"`
|
||||
Quantity float64 `gorm:"column:total_quantity"`
|
||||
OrderCount int `gorm:"column:order_count"`
|
||||
Revenue float64 `gorm:"column:revenue"`
|
||||
}
|
||||
|
||||
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 []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 []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,
|
||||
}
|
||||
}
|
||||
|
||||
// ── Top produits (quantité vendue, commandes terminées) ───────────────────
|
||||
var prodRows []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 * ci.quantite) AS revenue
|
||||
FROM command_items ci
|
||||
JOIN commandes c ON c.id = ci.command_id
|
||||
WHERE c.status != 'cancelled'
|
||||
GROUP BY ci.product_id, ci.produit
|
||||
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,
|
||||
}
|
||||
if i == 0 {
|
||||
topProductName = r.Name
|
||||
}
|
||||
}
|
||||
|
||||
// ── 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), 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,
|
||||
"top_products": topProducts,
|
||||
})
|
||||
}
|
||||
@@ -194,6 +194,11 @@ func SetupRoutes(router *gin.Engine, database *db.Database, geoService *services
|
||||
adminGroupV2.POST("/categories", handlers.CreateCategory)
|
||||
adminGroupV2.PUT("/categories/:id", handlers.UpdateCategory)
|
||||
adminGroupV2.DELETE("/categories/:id", handlers.DeleteCategory)
|
||||
// ============================================
|
||||
// STATISTIQUES ADMIN
|
||||
// ============================================
|
||||
adminGroupV2.GET("/stats", handlers.GetAdminStats)
|
||||
|
||||
// ============================================
|
||||
// COMMANDES - GESTION DE BASE
|
||||
// ============================================
|
||||
|
||||
Reference in New Issue
Block a user