chore: add mobile app

This commit is contained in:
2026-02-07 22:33:02 +01:00
parent db34640acc
commit e89384ad4e
29327 changed files with 3886944 additions and 12 deletions
@@ -0,0 +1,196 @@
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>
);
}