This commit is contained in:
@@ -156,6 +156,15 @@ export const getAdminStats = async (): Promise<AdminStats> => {
|
||||
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 (
|
||||
section: StatSection,
|
||||
): Promise<{ success: boolean; reset_at: string }> => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
StyleSheet,
|
||||
RefreshControl,
|
||||
TouchableOpacity,
|
||||
Modal,
|
||||
} from "react-native";
|
||||
import AlertModal from "../../components/ui/AlertModal";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
@@ -18,6 +19,7 @@ import {
|
||||
getAdminStats,
|
||||
resetAdminStats,
|
||||
getAdminStatsByMonth,
|
||||
getAdminDailyDetail,
|
||||
} from "../../api/api_admin";
|
||||
import type {
|
||||
AdminStats,
|
||||
@@ -380,46 +382,15 @@ function SectionResetBtn({
|
||||
);
|
||||
}
|
||||
|
||||
// ── Section détail du jour ────────────────────────────────────────────────────
|
||||
function DailyDetailSection({ daily }: { daily: DailyDetail }) {
|
||||
// ── Contenu détaillé d'un jour (catégories → produits) ───────────────────────
|
||||
// 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 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
|
||||
@@ -510,7 +481,7 @@ function DailyDetailSection({ daily }: { daily: DailyDetail }) {
|
||||
|
||||
{!hasData ? (
|
||||
<Text style={[ddStyles.empty, { color: colors.textMuted }]}>
|
||||
Aucune commande aujourd'hui
|
||||
Aucune commande ce jour-là
|
||||
</Text>
|
||||
) : (
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -747,6 +762,159 @@ const ddStyles = StyleSheet.create({
|
||||
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") ───────────────────────────────────────────
|
||||
function monthKey(date: Date): string {
|
||||
const y = date.getFullYear();
|
||||
@@ -773,6 +941,7 @@ function MonthlyHistorySection() {
|
||||
const [monthly, setMonthly] = useState<MonthlyStats | null>(null);
|
||||
const [loadingMonth, setLoadingMonth] = useState(false);
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [selectedDay, setSelectedDay] = useState<string | null>(null);
|
||||
|
||||
const currentMonthKey = monthKey(new Date());
|
||||
const isCurrentMonth = monthKeyState === currentMonthKey;
|
||||
@@ -1087,7 +1256,7 @@ function MonthlyHistorySection() {
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Liste détaillée jour par jour */}
|
||||
{/* Liste détaillée jour par jour — cliquable */}
|
||||
{[...days].reverse().map((d) => {
|
||||
const isBest = best && d.day === best.day;
|
||||
const pct =
|
||||
@@ -1097,9 +1266,15 @@ function MonthlyHistorySection() {
|
||||
d.count > 0 ? 3 : 0,
|
||||
)
|
||||
: 0;
|
||||
const hasData = d.count > 0;
|
||||
return (
|
||||
<View
|
||||
<TouchableOpacity
|
||||
key={d.day}
|
||||
onPress={() =>
|
||||
hasData && setSelectedDay(d.day)
|
||||
}
|
||||
disabled={!hasData}
|
||||
activeOpacity={0.6}
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
@@ -1111,7 +1286,15 @@ function MonthlyHistorySection() {
|
||||
style={{
|
||||
width: 42,
|
||||
fontSize: fontSize.xs,
|
||||
color: colors.textMuted,
|
||||
color: hasData
|
||||
? colors.textPrimary
|
||||
: colors.textMuted,
|
||||
fontWeight: hasData
|
||||
? "600"
|
||||
: "400",
|
||||
textDecorationLine: hasData
|
||||
? "underline"
|
||||
: "none",
|
||||
}}
|
||||
>
|
||||
{d.label}
|
||||
@@ -1179,7 +1362,14 @@ function MonthlyHistorySection() {
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user