Files
projet_gestion_commande/frontend-admin/src/screens/admin/OrdersScreen.tsx
T
2026-02-07 22:33:02 +01:00

316 lines
11 KiB
TypeScript

import React, { useState, useEffect, useCallback, useMemo } from "react";
import {
View,
Text,
StyleSheet,
FlatList,
TouchableOpacity,
RefreshControl,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { useNavigation } from "@react-navigation/native";
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { spacing, fontSize, borderRadius } from "../../theme";
import { useTheme } from "../../context/ThemeContext";
import {
getAllCommands,
getAvailableDeliveryPersons,
assignDeliveryPerson,
updateCommandStatus,
} from "../../api/api_admin";
import type { CommandResponse } from "../../api/types";
import type { AdminStackParamList } from "../../navigation/types";
import StatusBadge from "../../components/StatusBadge";
import LoadingSpinner from "../../components/ui/LoadingSpinner";
import Modal from "../../components/ui/Modal";
import Button from "../../components/ui/Button";
import AlertModal from "../../components/ui/AlertModal";
import { useAlert } from "../../hooks/useAlert";
type Nav = NativeStackNavigationProp<AdminStackParamList>;
const STATUS_FILTERS = [
"all",
"pending",
"assigned",
"en_route",
"arrived",
"livre",
"approved",
"cancelled",
];
export default function OrdersScreen() {
const { colors } = useTheme();
const navigation = useNavigation<Nav>();
const [commands, setCommands] = useState<CommandResponse[]>([]);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [filter, setFilter] = useState("all");
const [assignModal, setAssignModal] = useState<{
visible: boolean;
commandId: number | null;
}>({ visible: false, commandId: null });
const [livreurs, setLivreurs] = useState<any[]>([]);
const { alert, showError, hideAlert } = useAlert();
const loadData = useCallback(async () => {
try {
const status = filter === "all" ? undefined : filter;
const result = await getAllCommands(status);
setCommands(result.commands);
} catch {
/* ignore */
}
setLoading(false);
}, [filter]);
useEffect(() => {
loadData();
}, [loadData]);
const onRefresh = async () => {
setRefreshing(true);
await loadData();
setRefreshing(false);
};
const openAssignModal = async (commandId: number) => {
try {
const result = await getAvailableDeliveryPersons();
setLivreurs(result.livreurs);
setAssignModal({ visible: true, commandId });
} catch {
showError("Erreur", "Impossible de charger les livreurs");
}
};
const handleAssign = async (livreurUsername: string) => {
if (!assignModal.commandId) return;
try {
await assignDeliveryPerson(assignModal.commandId, livreurUsername);
setAssignModal({ visible: false, commandId: null });
await loadData();
} catch (e: any) {
showError("Erreur", e.message);
}
};
const handleStatusUpdate = async (commandId: number, newStatus: string) => {
try {
await updateCommandStatus(commandId, newStatus);
await loadData();
} catch (e: any) {
showError("Erreur", e.message);
}
};
const styles = useMemo(
() =>
StyleSheet.create({
container: { flex: 1, backgroundColor: colors.bgPrimary },
filterList: {
maxHeight: 50,
paddingHorizontal: spacing.l,
paddingVertical: spacing.s,
},
filterBtn: {
paddingHorizontal: spacing.l,
paddingVertical: spacing.s,
backgroundColor: colors.bgCard,
borderRadius: borderRadius.xl,
marginRight: spacing.s,
borderWidth: 1,
borderColor: colors.border,
},
filterActive: {
backgroundColor: colors.accent,
borderColor: colors.accent,
},
filterText: {
color: colors.textSecondary,
fontSize: fontSize.sm,
},
filterTextActive: { color: colors.textWhite },
card: {
backgroundColor: colors.bgCard,
borderRadius: borderRadius.md,
padding: spacing.l,
marginBottom: spacing.m,
borderWidth: 1,
borderColor: colors.borderLight,
},
cardHeader: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: spacing.s,
},
orderId: {
fontSize: fontSize.lg,
fontWeight: "bold",
color: colors.textWhite,
},
cardText: {
color: colors.textSecondary,
fontSize: fontSize.sm,
marginTop: 2,
},
cardDate: {
color: colors.textMuted,
fontSize: fontSize.xs,
marginTop: spacing.s,
},
actions: {
flexDirection: "row",
gap: spacing.s,
marginTop: spacing.m,
},
empty: {
color: colors.textMuted,
textAlign: "center",
marginTop: spacing.xxl,
fontSize: fontSize.md,
},
livreurItem: {
flexDirection: "row",
alignItems: "center",
padding: spacing.m,
backgroundColor: colors.bgCard,
borderRadius: borderRadius.sm,
marginBottom: spacing.s,
gap: spacing.m,
},
livreurName: { color: colors.textWhite, fontSize: fontSize.md },
}),
[colors],
);
const renderOrder = ({ item }: { item: CommandResponse }) => (
<TouchableOpacity
style={styles.card}
onPress={() =>
navigation.navigate("OrderDetail", { orderId: item.id })
}
activeOpacity={0.7}
>
<View style={styles.cardHeader}>
<Text style={styles.orderId}>#{item.id}</Text>
<StatusBadge status={item.status} />
</View>
<Text style={styles.cardText}>Client: {item.username}</Text>
<Text style={styles.cardText}>Adresse: {item.adresse}</Text>
<Text style={styles.cardText}>
Total: {item.total_prix.toFixed(2)}
</Text>
{item.livreur_assign && (
<Text style={styles.cardText}>
Livreur: {item.livreur_assign}
</Text>
)}
<Text style={styles.cardDate}>
{new Date(item.created_at).toLocaleString("fr-FR")}
</Text>
<View style={styles.actions}>
{item.status === "pending" && (
<Button
title="Assigner"
onPress={() => openAssignModal(item.id)}
size="sm"
variant="primary"
/>
)}
{item.status === "assigned" && (
<Button
title="En route"
onPress={() => handleStatusUpdate(item.id, "en_route")}
size="sm"
variant="secondary"
/>
)}
</View>
</TouchableOpacity>
);
if (loading) return <LoadingSpinner message="Chargement commandes..." />;
return (
<View style={styles.container}>
<FlatList
horizontal
data={STATUS_FILTERS}
keyExtractor={(i) => i}
renderItem={({ item }) => (
<TouchableOpacity
style={[
styles.filterBtn,
filter === item && styles.filterActive,
]}
onPress={() => setFilter(item)}
>
<Text
style={[
styles.filterText,
filter === item && styles.filterTextActive,
]}
>
{item === "all" ? "Tous" : item}
</Text>
</TouchableOpacity>
)}
style={styles.filterList}
showsHorizontalScrollIndicator={false}
/>
<FlatList
data={commands}
keyExtractor={(item) => item.id.toString()}
renderItem={renderOrder}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
tintColor={colors.accent}
/>
}
contentContainerStyle={{ padding: spacing.l, paddingTop: 0 }}
ListEmptyComponent={
<Text style={styles.empty}>Aucune commande</Text>
}
/>
<Modal
visible={assignModal.visible}
onClose={() =>
setAssignModal({ visible: false, commandId: null })
}
title="Assigner un livreur"
icon="bicycle-outline"
>
{livreurs.map((l) => (
<TouchableOpacity
key={l.username}
style={styles.livreurItem}
onPress={() => handleAssign(l.username)}
>
<Ionicons
name="person-outline"
size={20}
color={colors.accent}
/>
<Text style={styles.livreurName}>{l.username}</Text>
</TouchableOpacity>
))}
{livreurs.length === 0 && (
<Text style={styles.empty}>Aucun livreur disponible</Text>
)}
</Modal>
<AlertModal
visible={alert.visible}
type={alert.type}
title={alert.title}
message={alert.message}
onClose={hideAlert}
/>
</View>
);
}