197 lines
6.3 KiB
TypeScript
197 lines
6.3 KiB
TypeScript
import React, { useState, useEffect, useCallback, useMemo } from "react";
|
|
import {
|
|
View,
|
|
Text,
|
|
StyleSheet,
|
|
ScrollView,
|
|
RefreshControl,
|
|
} from "react-native";
|
|
import { Ionicons } from "@expo/vector-icons";
|
|
import { spacing, fontSize, borderRadius } from "../../theme";
|
|
import { shadows } from "../../theme/shadows";
|
|
import { useTheme } from "../../context/ThemeContext";
|
|
import { useAuth } from "../../auth/AuthContext";
|
|
import {
|
|
getAllCommands,
|
|
getAllClients,
|
|
getAvailableDeliveryPersons,
|
|
getCommandCountByStatus,
|
|
} from "../../api/api_admin";
|
|
import LoadingSpinner from "../../components/ui/LoadingSpinner";
|
|
|
|
interface StatCard {
|
|
label: string;
|
|
value: number;
|
|
icon: keyof typeof Ionicons.glyphMap;
|
|
color: string;
|
|
}
|
|
|
|
export default function DashboardScreen() {
|
|
const { colors } = useTheme();
|
|
const { username } = useAuth();
|
|
const [stats, setStats] = useState<StatCard[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [refreshing, setRefreshing] = useState(false);
|
|
|
|
const loadStats = useCallback(async () => {
|
|
try {
|
|
const [allCmd, pending, enRoute, completed, clients, livreurs] =
|
|
await Promise.all([
|
|
getAllCommands().then((r) => r.count),
|
|
getCommandCountByStatus("pending"),
|
|
getCommandCountByStatus("en_route"),
|
|
getCommandCountByStatus("approved"),
|
|
getAllClients()
|
|
.then((c) => c.length)
|
|
.catch(() => 0),
|
|
getAvailableDeliveryPersons()
|
|
.then((r) => r.count)
|
|
.catch(() => 0),
|
|
]);
|
|
|
|
setStats([
|
|
{
|
|
label: "Total commandes",
|
|
value: allCmd,
|
|
icon: "receipt-outline",
|
|
color: colors.accent,
|
|
},
|
|
{
|
|
label: "En attente",
|
|
value: pending,
|
|
icon: "time-outline",
|
|
color: colors.warning,
|
|
},
|
|
{
|
|
label: "En route",
|
|
value: enRoute,
|
|
icon: "navigate-outline",
|
|
color: colors.info,
|
|
},
|
|
{
|
|
label: "Terminées",
|
|
value: completed,
|
|
icon: "checkmark-circle-outline",
|
|
color: colors.success,
|
|
},
|
|
{
|
|
label: "Clients",
|
|
value: clients,
|
|
icon: "people-outline",
|
|
color: colors.accentLight,
|
|
},
|
|
{
|
|
label: "Livreurs",
|
|
value: livreurs,
|
|
icon: "bicycle-outline",
|
|
color: colors.categoryGros,
|
|
},
|
|
]);
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
setLoading(false);
|
|
}, [colors]);
|
|
|
|
useEffect(() => {
|
|
loadStats();
|
|
}, [loadStats]);
|
|
|
|
const onRefresh = async () => {
|
|
setRefreshing(true);
|
|
await loadStats();
|
|
setRefreshing(false);
|
|
};
|
|
|
|
const styles = useMemo(
|
|
() =>
|
|
StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: colors.bgPrimary,
|
|
padding: spacing.l,
|
|
},
|
|
welcome: {
|
|
fontSize: fontSize.xl,
|
|
fontWeight: "bold",
|
|
color: colors.textWhite,
|
|
marginBottom: spacing.xs,
|
|
},
|
|
subtitle: {
|
|
fontSize: fontSize.md,
|
|
color: colors.textSecondary,
|
|
marginBottom: spacing.xl,
|
|
},
|
|
grid: {
|
|
flexDirection: "row",
|
|
flexWrap: "wrap",
|
|
gap: spacing.m,
|
|
},
|
|
card: {
|
|
width: "47%",
|
|
backgroundColor: colors.bgCard,
|
|
borderRadius: borderRadius.md,
|
|
padding: spacing.l,
|
|
borderWidth: 1,
|
|
borderColor: colors.borderLight,
|
|
alignItems: "center",
|
|
},
|
|
iconCircle: {
|
|
width: 48,
|
|
height: 48,
|
|
borderRadius: 24,
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
marginBottom: spacing.s,
|
|
},
|
|
cardValue: {
|
|
fontSize: fontSize.xxl,
|
|
fontWeight: "bold",
|
|
color: colors.textWhite,
|
|
},
|
|
cardLabel: {
|
|
fontSize: fontSize.sm,
|
|
color: colors.textSecondary,
|
|
marginTop: spacing.xs,
|
|
textAlign: "center",
|
|
},
|
|
}),
|
|
[colors],
|
|
);
|
|
|
|
if (loading) return <LoadingSpinner message="Chargement..." />;
|
|
|
|
return (
|
|
<ScrollView
|
|
style={styles.container}
|
|
refreshControl={
|
|
<RefreshControl
|
|
refreshing={refreshing}
|
|
onRefresh={onRefresh}
|
|
tintColor={colors.accent}
|
|
/>
|
|
}
|
|
>
|
|
<Text style={styles.welcome}>Bonjour, {username}</Text>
|
|
<Text style={styles.subtitle}>Vue d'ensemble</Text>
|
|
|
|
<View style={styles.grid}>
|
|
{stats.map((s, i) => (
|
|
<View key={i} style={[styles.card, shadows.md]}>
|
|
<View
|
|
style={[
|
|
styles.iconCircle,
|
|
{ backgroundColor: s.color + "20" },
|
|
]}
|
|
>
|
|
<Ionicons name={s.icon} size={24} color={s.color} />
|
|
</View>
|
|
<Text style={styles.cardValue}>{s.value}</Text>
|
|
<Text style={styles.cardLabel}>{s.label}</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
</ScrollView>
|
|
);
|
|
}
|