This commit is contained in:
@@ -156,6 +156,15 @@ export const getAdminStats = async (): Promise<AdminStats> => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getAdminDailyDetail = async (
|
||||||
|
date: string,
|
||||||
|
): Promise<DailyDetail> => {
|
||||||
|
const { data } = await apiClient.get(`${V2}/admin/protected/stats/daily`, {
|
||||||
|
params: { date },
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
export const resetAdminStats = async (
|
export const resetAdminStats = async (
|
||||||
section: StatSection,
|
section: StatSection,
|
||||||
): Promise<{ success: boolean; reset_at: string }> => {
|
): Promise<{ success: boolean; reset_at: string }> => {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
StyleSheet,
|
StyleSheet,
|
||||||
RefreshControl,
|
RefreshControl,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
|
Modal,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import AlertModal from "../../components/ui/AlertModal";
|
import AlertModal from "../../components/ui/AlertModal";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
@@ -18,6 +19,7 @@ import {
|
|||||||
getAdminStats,
|
getAdminStats,
|
||||||
resetAdminStats,
|
resetAdminStats,
|
||||||
getAdminStatsByMonth,
|
getAdminStatsByMonth,
|
||||||
|
getAdminDailyDetail,
|
||||||
} from "../../api/api_admin";
|
} from "../../api/api_admin";
|
||||||
import type {
|
import type {
|
||||||
AdminStats,
|
AdminStats,
|
||||||
@@ -380,46 +382,15 @@ function SectionResetBtn({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Section détail du jour ────────────────────────────────────────────────────
|
// ── Contenu détaillé d'un jour (catégories → produits) ───────────────────────
|
||||||
function DailyDetailSection({ daily }: { daily: DailyDetail }) {
|
// Réutilisé à la fois par la section "Activité du jour" (aujourd'hui) et par
|
||||||
|
// la modal de détail d'un jour cliqué dans l'historique mensuel.
|
||||||
|
function DailyDetailContent({ daily }: { daily: DailyDetail }) {
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
const hasData = daily.categories.length > 0;
|
const hasData = daily.categories.length > 0;
|
||||||
|
|
||||||
return (
|
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é */}
|
{/* Mini-résumé */}
|
||||||
<View style={ddStyles.chipRow}>
|
<View style={ddStyles.chipRow}>
|
||||||
<View
|
<View
|
||||||
@@ -510,7 +481,7 @@ function DailyDetailSection({ daily }: { daily: DailyDetail }) {
|
|||||||
|
|
||||||
{!hasData ? (
|
{!hasData ? (
|
||||||
<Text style={[ddStyles.empty, { color: colors.textMuted }]}>
|
<Text style={[ddStyles.empty, { color: colors.textMuted }]}>
|
||||||
Aucune commande aujourd'hui
|
Aucune commande ce jour-là
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
daily.categories.map((cat: DailyCategoryDetail, ci: number) => {
|
daily.categories.map((cat: DailyCategoryDetail, ci: number) => {
|
||||||
@@ -676,6 +647,50 @@ function DailyDetailSection({ daily }: { daily: DailyDetail }) {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Section détail du jour (aujourd'hui, dans le flux principal) ────────────
|
||||||
|
function DailyDetailSection({ daily }: { daily: DailyDetail }) {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
<DailyDetailContent daily={daily} />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -747,6 +762,159 @@ const ddStyles = StyleSheet.create({
|
|||||||
barFill: { height: "100%", borderRadius: 3 },
|
barFill: { height: "100%", borderRadius: 3 },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Modal — détail d'un jour cliqué dans l'historique mensuel ───────────────
|
||||||
|
function DayDetailModal({
|
||||||
|
visible,
|
||||||
|
dateLabel,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
visible: boolean;
|
||||||
|
dateLabel: string | null; // "2026-06-05"
|
||||||
|
onClose: () => void;
|
||||||
|
}) {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
const [detail, setDetail] = useState<DailyDetail | null>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [errored, setErrored] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!visible || !dateLabel) return;
|
||||||
|
setLoading(true);
|
||||||
|
setErrored(false);
|
||||||
|
getAdminDailyDetail(dateLabel)
|
||||||
|
.then((data) => setDetail(data))
|
||||||
|
.catch(() => setErrored(true))
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
}, [visible, dateLabel]);
|
||||||
|
|
||||||
|
const prettyDate = useMemo(() => {
|
||||||
|
if (!dateLabel) return "";
|
||||||
|
const [y, m, d] = dateLabel.split("-").map(Number);
|
||||||
|
return new Date(y, m - 1, d).toLocaleDateString("fr-FR", {
|
||||||
|
weekday: "long",
|
||||||
|
day: "2-digit",
|
||||||
|
month: "long",
|
||||||
|
year: "numeric",
|
||||||
|
});
|
||||||
|
}, [dateLabel]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
visible={visible}
|
||||||
|
transparent
|
||||||
|
animationType="fade"
|
||||||
|
onRequestClose={onClose}
|
||||||
|
>
|
||||||
|
<View style={dayModalStyles.overlay}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
dayModalStyles.sheet,
|
||||||
|
{
|
||||||
|
backgroundColor: colors.bgCard,
|
||||||
|
borderColor: colors.borderLight,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<View style={dayModalStyles.header}>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: spacing.s,
|
||||||
|
flex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Ionicons
|
||||||
|
name="calendar-outline"
|
||||||
|
size={16}
|
||||||
|
color={CHART_ACCENT}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
dayModalStyles.title,
|
||||||
|
{ color: colors.textPrimary },
|
||||||
|
]}
|
||||||
|
numberOfLines={1}
|
||||||
|
>
|
||||||
|
{prettyDate}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={onClose}
|
||||||
|
style={dayModalStyles.closeBtn}
|
||||||
|
>
|
||||||
|
<Ionicons
|
||||||
|
name="close"
|
||||||
|
size={18}
|
||||||
|
color={colors.textMuted}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
style={{ maxHeight: 480 }}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
color: colors.textMuted,
|
||||||
|
fontSize: fontSize.sm,
|
||||||
|
textAlign: "center",
|
||||||
|
paddingVertical: spacing.l,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Chargement...
|
||||||
|
</Text>
|
||||||
|
) : errored ? (
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
color: CHART_RED,
|
||||||
|
fontSize: fontSize.sm,
|
||||||
|
textAlign: "center",
|
||||||
|
paddingVertical: spacing.l,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Impossible de charger le détail de ce jour
|
||||||
|
</Text>
|
||||||
|
) : detail ? (
|
||||||
|
<DailyDetailContent daily={detail} />
|
||||||
|
) : null}
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const dayModalStyles = StyleSheet.create({
|
||||||
|
overlay: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: "rgba(0,0,0,0.55)",
|
||||||
|
justifyContent: "center",
|
||||||
|
padding: spacing.l,
|
||||||
|
},
|
||||||
|
sheet: {
|
||||||
|
borderRadius: borderRadius.md,
|
||||||
|
borderWidth: 1,
|
||||||
|
padding: spacing.l,
|
||||||
|
maxHeight: "85%",
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: spacing.m,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: fontSize.md,
|
||||||
|
fontWeight: "700",
|
||||||
|
textTransform: "capitalize",
|
||||||
|
},
|
||||||
|
closeBtn: {
|
||||||
|
padding: spacing.xs,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// ── Helpers de mois (clé "YYYY-MM") ───────────────────────────────────────────
|
// ── Helpers de mois (clé "YYYY-MM") ───────────────────────────────────────────
|
||||||
function monthKey(date: Date): string {
|
function monthKey(date: Date): string {
|
||||||
const y = date.getFullYear();
|
const y = date.getFullYear();
|
||||||
@@ -773,6 +941,7 @@ function MonthlyHistorySection() {
|
|||||||
const [monthly, setMonthly] = useState<MonthlyStats | null>(null);
|
const [monthly, setMonthly] = useState<MonthlyStats | null>(null);
|
||||||
const [loadingMonth, setLoadingMonth] = useState(false);
|
const [loadingMonth, setLoadingMonth] = useState(false);
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
const [selectedDay, setSelectedDay] = useState<string | null>(null);
|
||||||
|
|
||||||
const currentMonthKey = monthKey(new Date());
|
const currentMonthKey = monthKey(new Date());
|
||||||
const isCurrentMonth = monthKeyState === currentMonthKey;
|
const isCurrentMonth = monthKeyState === currentMonthKey;
|
||||||
@@ -1087,7 +1256,7 @@ function MonthlyHistorySection() {
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Liste détaillée jour par jour */}
|
{/* Liste détaillée jour par jour — cliquable */}
|
||||||
{[...days].reverse().map((d) => {
|
{[...days].reverse().map((d) => {
|
||||||
const isBest = best && d.day === best.day;
|
const isBest = best && d.day === best.day;
|
||||||
const pct =
|
const pct =
|
||||||
@@ -1097,9 +1266,15 @@ function MonthlyHistorySection() {
|
|||||||
d.count > 0 ? 3 : 0,
|
d.count > 0 ? 3 : 0,
|
||||||
)
|
)
|
||||||
: 0;
|
: 0;
|
||||||
|
const hasData = d.count > 0;
|
||||||
return (
|
return (
|
||||||
<View
|
<TouchableOpacity
|
||||||
key={d.day}
|
key={d.day}
|
||||||
|
onPress={() =>
|
||||||
|
hasData && setSelectedDay(d.day)
|
||||||
|
}
|
||||||
|
disabled={!hasData}
|
||||||
|
activeOpacity={0.6}
|
||||||
style={{
|
style={{
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@@ -1111,7 +1286,15 @@ function MonthlyHistorySection() {
|
|||||||
style={{
|
style={{
|
||||||
width: 42,
|
width: 42,
|
||||||
fontSize: fontSize.xs,
|
fontSize: fontSize.xs,
|
||||||
color: colors.textMuted,
|
color: hasData
|
||||||
|
? colors.textPrimary
|
||||||
|
: colors.textMuted,
|
||||||
|
fontWeight: hasData
|
||||||
|
? "600"
|
||||||
|
: "400",
|
||||||
|
textDecorationLine: hasData
|
||||||
|
? "underline"
|
||||||
|
: "none",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{d.label}
|
{d.label}
|
||||||
@@ -1179,7 +1362,14 @@ function MonthlyHistorySection() {
|
|||||||
color={CHART_AMBER}
|
color={CHART_AMBER}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</View>
|
{hasData && (
|
||||||
|
<Ionicons
|
||||||
|
name="chevron-forward"
|
||||||
|
size={12}
|
||||||
|
color={colors.textMuted}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
@@ -1220,6 +1410,12 @@ function MonthlyHistorySection() {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<DayDetailModal
|
||||||
|
visible={selectedDay !== null}
|
||||||
|
dateLabel={selectedDay}
|
||||||
|
onClose={() => setSelectedDay(null)}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user