chore: build
Frontend Admin - EAS Build / build (push) Failing after 58m6s

This commit is contained in:
2026-06-23 20:45:58 +02:00
parent a9c53c3b62
commit bafed46be6
2 changed files with 146 additions and 1 deletions
+23
View File
@@ -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;
@@ -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 (
<View style={[ddStyles.card, { backgroundColor: colors.bgCard, borderColor: colors.borderLight }]}>
{/* En-tête */}
<View style={ddStyles.header}>
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.s }}>
<Ionicons name="today-outline" size={16} color={CHART_ACCENT} />
<Text style={[ddStyles.title, { color: colors.textPrimary }]}>Activité du jour</Text>
</View>
<Text style={[ddStyles.date, { color: colors.textMuted }]}>{daily.date}</Text>
</View>
{/* Mini-résumé */}
<View style={ddStyles.chipRow}>
<View style={[ddStyles.chip, { backgroundColor: CHART_ACCENT + "18", borderColor: CHART_ACCENT + "44" }]}>
<Ionicons name="receipt-outline" size={13} color={CHART_ACCENT} />
<Text style={[ddStyles.chipVal, { color: colors.textPrimary }]}>{daily.total_orders}</Text>
<Text style={[ddStyles.chipLbl, { color: colors.textMuted }]}>commandes</Text>
</View>
<View style={[ddStyles.chip, { backgroundColor: CHART_BLUE + "18", borderColor: CHART_BLUE + "44" }]}>
<Ionicons name="scale-outline" size={13} color={CHART_BLUE} />
<Text style={[ddStyles.chipVal, { color: colors.textPrimary }]}>{daily.total_quantity}g</Text>
<Text style={[ddStyles.chipLbl, { color: colors.textMuted }]}>vendues</Text>
</View>
<View style={[ddStyles.chip, { backgroundColor: CHART_GREEN + "18", borderColor: CHART_GREEN + "44" }]}>
<Ionicons name="cash-outline" size={13} color={CHART_GREEN} />
<Text style={[ddStyles.chipVal, { color: colors.textPrimary }]}>{fmtEuro(daily.total_revenue)}</Text>
<Text style={[ddStyles.chipLbl, { color: colors.textMuted }]}>revenus</Text>
</View>
</View>
{!hasData ? (
<Text style={[ddStyles.empty, { color: colors.textMuted }]}>Aucune commande aujourd'hui</Text>
) : (
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 (
<View key={cat.category} style={[ddStyles.catBlock, isLast && { marginBottom: 0, borderBottomWidth: 0 }]}>
{/* En-tête catégorie */}
<View style={ddStyles.catHeader}>
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.s }}>
<View style={[ddStyles.catDot, { backgroundColor: cat.category_color }]} />
<Text style={[ddStyles.catName, { color: colors.textPrimary }]}>{cat.category}</Text>
</View>
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.m }}>
<Text style={[ddStyles.catMeta, { color: colors.textMuted }]}>{cat.total_quantity}g</Text>
<Text style={[ddStyles.catRevenue, { color: cat.category_color }]}>{fmtEuro(cat.total_revenue)}</Text>
</View>
</View>
{/* 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 (
<View key={prod.product_id} style={[ddStyles.prodRow, pi === cat.products.length - 1 && { marginBottom: 0 }]}>
<View style={ddStyles.prodTopLine}>
<Text
style={[ddStyles.prodName, { color: isTopInCat ? colors.textPrimary : colors.textSecondary, fontWeight: isTopInCat ? "600" : "400" }]}
numberOfLines={1}
>
{prod.name}
</Text>
<View style={ddStyles.prodMeta}>
<Text style={[ddStyles.prodQty, { color: colors.textMuted }]}>
{prod.quantity}g · {prod.order_count} cmd
</Text>
<Text style={[ddStyles.prodRevenue, { color: isTopInCat ? cat.category_color : colors.textMuted }]}>
{fmtEuro(prod.revenue)}
</Text>
</View>
</View>
<View style={[ddStyles.bar, { backgroundColor: colors.borderLight }]}>
<View style={[ddStyles.barFill, { width: `${pct}%`, backgroundColor: cat.category_color, opacity: isTopInCat ? 1 : 0.45 }]} />
</View>
</View>
);
})}
</View>
);
})
)}
</View>
);
}
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() {
/>
</View>
{/* ── Activité du jour ── */}
{stats?.daily_detail && (
<DailyDetailSection daily={stats.daily_detail} />
)}
{/* ── Évolution 30 jours (commandes) ── */}
<Section title="30 derniers jours — commandes" icon="bar-chart-outline">
<SectionResetBtn