chore: add quantity stat
This commit is contained in:
@@ -90,6 +90,20 @@ export interface ProductStat {
|
||||
category_color: string;
|
||||
}
|
||||
|
||||
export interface QuantityStat {
|
||||
quantity: number;
|
||||
order_count: number;
|
||||
total_sold: number;
|
||||
revenue: number;
|
||||
}
|
||||
export interface ProductQuantityBreakdown {
|
||||
product_id: number;
|
||||
name: string;
|
||||
category_color: string;
|
||||
total_orders: number;
|
||||
quantities: QuantityStat[];
|
||||
}
|
||||
|
||||
export interface AdminStats {
|
||||
summary: StatsSummary;
|
||||
by_weekday: WeekdayStat[];
|
||||
@@ -97,6 +111,7 @@ export interface AdminStats {
|
||||
by_day_revenue: DayRevenueStat[];
|
||||
by_hour: HourStat[];
|
||||
top_products: ProductStat[];
|
||||
by_quantity: ProductQuantityBreakdown[];
|
||||
}
|
||||
|
||||
export const getAdminStats = async (): Promise<AdminStats> => {
|
||||
|
||||
@@ -14,7 +14,7 @@ import { spacing, fontSize, borderRadius } from "../../theme";
|
||||
import { shadows } from "../../theme/shadows";
|
||||
import LoadingSpinner from "../../components/ui/LoadingSpinner";
|
||||
import { getAdminStats } from "../../api/api_admin";
|
||||
import type { AdminStats, WeekdayStat, DayStat, DayRevenueStat, HourStat, ProductStat } from "../../api/api_admin";
|
||||
import type { AdminStats, WeekdayStat, DayStat, DayRevenueStat, HourStat, ProductStat, ProductQuantityBreakdown } from "../../api/api_admin";
|
||||
|
||||
// ── Palette graphiques ────────────────────────────────────────────────────────
|
||||
const CHART_ACCENT = "#6366f1";
|
||||
@@ -372,6 +372,64 @@ export default function StatsScreen() {
|
||||
)}
|
||||
</Section>
|
||||
|
||||
{/* ── Doses les plus populaires par produit ── */}
|
||||
{(() => {
|
||||
const qtyData: ProductQuantityBreakdown[] = (stats?.by_quantity ?? []).filter(
|
||||
(p) => p.quantities.length >= 1,
|
||||
);
|
||||
if (!qtyData.length) return null;
|
||||
return (
|
||||
<Section title="Doses populaires par produit" icon="flask-outline">
|
||||
{qtyData.map((product) => {
|
||||
const peakCount = product.quantities[0]?.order_count ?? 1;
|
||||
return (
|
||||
<View
|
||||
key={product.product_id}
|
||||
style={{
|
||||
marginBottom: spacing.m,
|
||||
paddingBottom: spacing.m,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.borderLight,
|
||||
}}
|
||||
>
|
||||
{/* Nom du produit avec pastille couleur */}
|
||||
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.xs, marginBottom: spacing.s }}>
|
||||
<View style={{ width: 10, height: 10, borderRadius: 5, backgroundColor: product.category_color }} />
|
||||
<Text style={{ color: colors.textPrimary, fontSize: fontSize.sm, fontWeight: "600" }} numberOfLines={1}>
|
||||
{product.name}
|
||||
</Text>
|
||||
</View>
|
||||
{/* Barre par dose */}
|
||||
{product.quantities.map((q, i) => {
|
||||
const pct = peakCount > 0 ? Math.max((q.order_count / peakCount) * 100, q.order_count > 0 ? 2 : 0) : 0;
|
||||
const isPeak = i === 0;
|
||||
const label = Number.isInteger(q.quantity)
|
||||
? `${q.quantity}g`
|
||||
: `${q.quantity}g`;
|
||||
return (
|
||||
<View key={q.quantity} style={{ flexDirection: "row", alignItems: "center", marginBottom: 4, gap: spacing.s }}>
|
||||
<Text style={{ width: 44, fontSize: fontSize.xs, color: isPeak ? product.category_color : colors.textMuted, fontWeight: isPeak ? "700" : "400" }}>
|
||||
{label}
|
||||
</Text>
|
||||
<View style={{ flex: 1, height: 14, borderRadius: 3, backgroundColor: colors.borderLight, overflow: "hidden" }}>
|
||||
<View style={{ width: `${pct}%`, height: "100%", borderRadius: 3, backgroundColor: isPeak ? product.category_color : product.category_color + "66" }} />
|
||||
</View>
|
||||
<Text style={{ width: 54, fontSize: fontSize.xs, textAlign: "right", color: isPeak ? product.category_color : colors.textMuted, fontWeight: isPeak ? "700" : "400" }}>
|
||||
{q.order_count} cmd
|
||||
</Text>
|
||||
{isPeak && (
|
||||
<Ionicons name="flame" size={11} color={CHART_AMBER} />
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</Section>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* ── Top produits ── */}
|
||||
<Section title="Top produits" icon="cube-outline">
|
||||
{/* Sélecteur tri */}
|
||||
|
||||
Reference in New Issue
Block a user