diff --git a/frontend-admin/src/api/api_admin.ts b/frontend-admin/src/api/api_admin.ts index cbb2cc9b..6d352e5f 100644 --- a/frontend-admin/src/api/api_admin.ts +++ b/frontend-admin/src/api/api_admin.ts @@ -104,6 +104,28 @@ export interface ProductQuantityBreakdown { quantities: QuantityStat[]; } +export interface DailyProductItem { + product_id: number; + name: string; + quantity: number; + order_count: number; + revenue: number; +} +export interface DailyCategoryDetail { + category: string; + category_color: string; + total_quantity: number; + total_revenue: number; + products: DailyProductItem[]; +} +export interface DailyDetail { + date: string; + total_orders: number; + total_quantity: number; + total_revenue: number; + categories: DailyCategoryDetail[]; +} + export type StatSection = "commandes" | "revenus" | "produits" | "heures" | "jours" | "doses"; export interface AdminStats { @@ -114,6 +136,7 @@ export interface AdminStats { by_hour: HourStat[]; top_products: ProductStat[]; by_quantity: ProductQuantityBreakdown[]; + daily_detail?: DailyDetail; reset_at_commandes?: string; reset_at_revenus?: string; reset_at_produits?: string; diff --git a/frontend-admin/src/screens/admin/StatsScreen.tsx b/frontend-admin/src/screens/admin/StatsScreen.tsx index 88633bb9..1b7e288a 100644 --- a/frontend-admin/src/screens/admin/StatsScreen.tsx +++ b/frontend-admin/src/screens/admin/StatsScreen.tsx @@ -15,7 +15,7 @@ import { spacing, fontSize, borderRadius } from "../../theme"; import { shadows } from "../../theme/shadows"; import LoadingSpinner from "../../components/ui/LoadingSpinner"; import { getAdminStats, resetAdminStats } from "../../api/api_admin"; -import type { AdminStats, WeekdayStat, DayStat, DayRevenueStat, HourStat, ProductStat, ProductQuantityBreakdown, StatSection } from "../../api/api_admin"; +import type { AdminStats, WeekdayStat, DayStat, DayRevenueStat, HourStat, ProductStat, ProductQuantityBreakdown, StatSection, DailyDetail, DailyCategoryDetail } from "../../api/api_admin"; // ── Palette graphiques ──────────────────────────────────────────────────────── const CHART_ACCENT = "#6366f1"; @@ -186,6 +186,123 @@ function SectionResetBtn({ onPress, loading, resetAt }: { onPress: () => void; l ); } +// ── Section détail du jour ──────────────────────────────────────────────────── +function DailyDetailSection({ daily }: { daily: DailyDetail }) { + const { colors } = useTheme(); + const hasData = daily.categories.length > 0; + + return ( + + {/* En-tête */} + + + + Activité du jour + + {daily.date} + + + {/* Mini-résumé */} + + + + {daily.total_orders} + commandes + + + + {daily.total_quantity}g + vendues + + + + {fmtEuro(daily.total_revenue)} + revenus + + + + {!hasData ? ( + Aucune commande aujourd'hui + ) : ( + daily.categories.map((cat: DailyCategoryDetail, ci: number) => { + const catMax = Math.max(...cat.products.map((p) => p.quantity), 1); + const isLast = ci === daily.categories.length - 1; + return ( + + {/* En-tête catégorie */} + + + + {cat.category} + + + {cat.total_quantity}g + {fmtEuro(cat.total_revenue)} + + + + {/* Lignes produits */} + {cat.products.map((prod, pi) => { + const pct = catMax > 0 ? Math.max((prod.quantity / catMax) * 100, prod.quantity > 0 ? 3 : 0) : 0; + const isTopInCat = pi === 0; + return ( + + + + {prod.name} + + + + {prod.quantity}g · {prod.order_count} cmd + + + {fmtEuro(prod.revenue)} + + + + + + + + ); + })} + + ); + }) + )} + + ); +} + +const ddStyles = StyleSheet.create({ + card: { borderRadius: borderRadius.md, padding: spacing.l, marginBottom: spacing.m, borderWidth: 1 }, + header: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", marginBottom: spacing.m }, + title: { fontSize: fontSize.md, fontWeight: "600" }, + date: { fontSize: fontSize.xs, fontWeight: "500" }, + chipRow: { flexDirection: "row", gap: spacing.s, marginBottom: spacing.l }, + chip: { flex: 1, alignItems: "center", gap: 3, paddingVertical: spacing.s, borderRadius: borderRadius.sm, borderWidth: 1 }, + chipVal: { fontSize: fontSize.sm, fontWeight: "700" }, + chipLbl: { fontSize: 9, fontWeight: "500" }, + empty: { textAlign: "center", fontSize: fontSize.sm, paddingVertical: spacing.l }, + catBlock: { marginBottom: spacing.l, paddingBottom: spacing.l, borderBottomWidth: 1 }, + catHeader: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", marginBottom: spacing.s, paddingBottom: spacing.xs }, + catDot: { width: 10, height: 10, borderRadius: 5 }, + catName: { fontSize: fontSize.sm, fontWeight: "700" }, + catMeta: { fontSize: fontSize.xs }, + catRevenue: { fontSize: fontSize.xs, fontWeight: "700" }, + prodRow: { marginBottom: spacing.s }, + prodTopLine:{ flexDirection: "row", alignItems: "center", justifyContent: "space-between", marginBottom: 4 }, + prodName: { flex: 1, fontSize: fontSize.xs, marginRight: spacing.s }, + prodMeta: { flexDirection: "row", alignItems: "center", gap: spacing.m }, + prodQty: { fontSize: fontSize.xs }, + prodRevenue:{ fontSize: fontSize.xs, fontWeight: "600", minWidth: 52, textAlign: "right" }, + bar: { height: 5, borderRadius: 3, overflow: "hidden" }, + barFill: { height: "100%", borderRadius: 3 }, +}); + // ── Sélecteur de période top produits ──────────────────────────────────────── type ProdSort = "quantity" | "orders" | "revenue"; @@ -325,6 +442,11 @@ export default function StatsScreen() { /> + {/* ── Activité du jour ── */} + {stats?.daily_detail && ( + + )} + {/* ── Évolution 30 jours (commandes) ── */}