import React, { useState, useEffect, useCallback, useMemo } from "react"; import { View, Text, StyleSheet, FlatList, RefreshControl, ScrollView, TouchableOpacity, TextInput, } from "react-native"; import { Ionicons } from "@expo/vector-icons"; import { spacing, fontSize, borderRadius } from "../../theme"; import { useTheme } from "../../context/ThemeContext"; import { getAllCommands } from "../../api/api_admin"; import { getCommandItems, deleteCommand, confirmReceptionCabine, notifyClientToDescendCabine, getCabineLivreursList, assignDeliveryPersonByCabine, proposeAddressChangeCabine, } from "../../api/api_cabine"; import type { CommandResponse } from "../../api/types"; import StatusBadge from "../../components/StatusBadge"; import LoadingSpinner from "../../components/ui/LoadingSpinner"; import Modal from "../../components/ui/Modal"; import Card from "../../components/ui/Card"; import AlertModal from "../../components/ui/AlertModal"; import { useAlert } from "../../hooks/useAlert"; const STATUS_COLORS: Record = { pending: "#F59E0B", }; const STATUS_ICONS: Record = { pending: "time-outline", }; const STATUS_LABELS: Record = { pending: "En attente", }; export default function OrdersScreen() { const { colors } = useTheme(); const [commands, setCommands] = useState([]); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [openMenuId, setOpenMenuId] = useState(null); const { alert, showError, showSuccess, showConfirm, hideAlert } = useAlert(); const [itemsModal, setItemsModal] = useState<{ visible: boolean; commandId: number | null; items: any[]; commandInfo: any; clientInfo: any; }>({ visible: false, commandId: null, items: [], commandInfo: null, clientInfo: null, }); const [assignModal, setAssignModal] = useState<{ visible: boolean; commandId: number | null; }>({ visible: false, commandId: null }); const [addressModal, setAddressModal] = useState<{ visible: boolean; commandId: number | null; input: string; }>({ visible: false, commandId: null, input: "" }); const [livreurs, setLivreurs] = useState< { id: number; username: string }[] >([]); const loadData = useCallback(async () => { try { const result = await getAllCommands(); setCommands( result.commands.filter( (c: CommandResponse) => !["approved", "cancelled"].includes(c.status), ), ); } catch { /* ignore */ } setLoading(false); }, []); useEffect(() => { loadData(); }, [loadData]); useEffect(() => { const interval = setInterval(() => { loadData(); }, 30000); return () => clearInterval(interval); }, [loadData]); const onRefresh = async () => { setRefreshing(true); await loadData(); setRefreshing(false); }; const openItems = async (commandId: number) => { setOpenMenuId(null); try { const result = await getCommandItems(commandId); setItemsModal({ visible: true, commandId, items: result.items, commandInfo: result.command_info, clientInfo: result.client_info, }); } catch (e: any) { showError("Erreur", e.message); } }; const handleConfirmReception = (commandId: number) => { setOpenMenuId(null); showConfirm( "Confirmer la réception", `Confirmer la réception de la commande #${commandId} au nom du client ?`, async () => { try { await confirmReceptionCabine(commandId); await loadData(); } catch (e: any) { showError("Erreur", e.message); } }, "Confirmer", ); }; const handleNotifyClient = async (commandId: number) => { setOpenMenuId(null); try { await notifyClientToDescendCabine(commandId); showSuccess( "Notification envoyée", "Le client a été prévenu de descendre", ); } catch (e: any) { showError("Erreur", e.message); } }; const handleProposeAddress = async () => { if (!addressModal.commandId || !addressModal.input.trim()) return; try { await proposeAddressChangeCabine( addressModal.commandId, addressModal.input.trim(), ); setAddressModal({ visible: false, commandId: null, input: "" }); showSuccess( "Proposition envoyée", "Le client a été notifié de la nouvelle adresse proposée", ); } catch (e: any) { showError("Erreur", e.message); } }; const handleDelete = (commandId: number) => { setOpenMenuId(null); showConfirm( "Supprimer", `Supprimer la commande #${commandId} ?`, async () => { try { await deleteCommand(commandId); await loadData(); } catch (e: any) { showError("Erreur", e.message); } }, "Supprimer", ); }; const closeModal = () => setItemsModal({ visible: false, commandId: null, items: [], commandInfo: null, clientInfo: null, }); const openAssignModal = async (commandId: number) => { setOpenMenuId(null); try { const list = await getCabineLivreursList(); setLivreurs(list); setAssignModal({ visible: true, commandId }); } catch { showError("Erreur", "Impossible de charger les livreurs"); } }; const handleAssign = async (livreurUsername: string) => { if (!assignModal.commandId) return; try { await assignDeliveryPersonByCabine( assignModal.commandId, livreurUsername, ); setAssignModal({ visible: false, commandId: null }); await loadData(); } catch (e: any) { showError("Erreur", e.message); } }; const styles = useMemo( () => StyleSheet.create({ container: { flex: 1, backgroundColor: colors.bgPrimary }, row: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: spacing.s, }, orderId: { fontSize: fontSize.lg, fontWeight: "bold", color: colors.textWhite, }, info: { color: colors.textSecondary, fontSize: fontSize.sm, marginTop: 2, }, empty: { color: colors.textMuted, textAlign: "center", marginTop: spacing.xl, }, // Select actions selectBtn: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", marginTop: spacing.m, paddingHorizontal: spacing.m, paddingVertical: spacing.s, backgroundColor: colors.bgPrimary, borderRadius: borderRadius.sm, borderWidth: 1, borderColor: colors.border, }, selectBtnText: { color: colors.textSecondary, fontSize: fontSize.sm, }, dropdown: { marginTop: 2, backgroundColor: colors.bgCard, borderRadius: borderRadius.sm, borderWidth: 1, borderColor: colors.border, overflow: "hidden", }, dropdownItem: { flexDirection: "row", alignItems: "center", gap: spacing.s, paddingHorizontal: spacing.m, paddingVertical: spacing.m, borderBottomWidth: 1, borderBottomColor: colors.border, }, dropdownItemLast: { borderBottomWidth: 0, }, dropdownText: { color: colors.textWhite, fontSize: fontSize.sm, }, dropdownTextDanger: { color: colors.danger, fontSize: fontSize.sm, }, addressInput: { backgroundColor: colors.bgPrimary, borderWidth: 1, borderColor: colors.border, borderRadius: borderRadius.sm, color: colors.textWhite, paddingHorizontal: spacing.m, paddingVertical: spacing.m, fontSize: fontSize.md, marginBottom: spacing.m, }, addressConfirmBtn: { backgroundColor: colors.accent, borderRadius: borderRadius.sm, paddingVertical: spacing.m, alignItems: "center", }, addressConfirmText: { color: colors.textWhite, fontSize: fontSize.md, fontWeight: "700", }, // Modal summary modalSummary: { backgroundColor: colors.bgCard, borderRadius: borderRadius.sm, padding: spacing.m, marginBottom: spacing.m, gap: spacing.xs, }, modalSummaryRow: { flexDirection: "row", alignItems: "center", gap: spacing.s, }, modalSummaryText: { color: colors.textSecondary, fontSize: fontSize.sm, flex: 1, }, modalTotal: { color: colors.accent, fontSize: fontSize.md, fontWeight: "700", marginTop: spacing.xs, }, progressLabel: { color: colors.textMuted, fontSize: fontSize.xs, marginBottom: spacing.xs, }, itemCard: { backgroundColor: colors.bgCard, borderRadius: borderRadius.sm, marginBottom: spacing.s, overflow: "hidden", }, itemCardAccent: { height: 3 }, itemCardBody: { padding: spacing.m, flexDirection: "row", alignItems: "flex-start", gap: spacing.m, }, itemIndex: { width: 28, height: 28, borderRadius: 14, backgroundColor: colors.bgPrimary, justifyContent: "center", alignItems: "center", marginTop: 2, }, itemIndexText: { color: colors.textMuted, fontSize: fontSize.xs, fontWeight: "700", }, itemInfo: { flex: 1 }, itemName: { color: colors.textWhite, fontSize: fontSize.md, fontWeight: "600", marginBottom: 2, }, itemMeta: { color: colors.textMuted, fontSize: fontSize.xs, marginBottom: spacing.s, }, itemStatusRow: { flexDirection: "row", alignItems: "center", gap: 5, }, itemStatusText: { fontSize: fontSize.xs, fontWeight: "600" }, 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 }) => { const isOpen = openMenuId === item.id; type Action = { label: string; icon: keyof typeof Ionicons.glyphMap; onPress: () => void; danger?: boolean; condition?: boolean; }; const actions: Action[] = ( [ { label: "Voir items", icon: "receipt-outline" as keyof typeof Ionicons.glyphMap, onPress: () => openItems(item.id), }, { label: "Proposer adresse", icon: "location-outline" as keyof typeof Ionicons.glyphMap, onPress: () => { setOpenMenuId(null); setAddressModal({ visible: true, commandId: item.id, input: "", }); }, }, { label: "Le livreur est là", icon: "notifications-outline" as keyof typeof Ionicons.glyphMap, onPress: () => handleNotifyClient(item.id), }, { label: "Assigner livreur", icon: "bicycle-outline" as keyof typeof Ionicons.glyphMap, onPress: () => openAssignModal(item.id), }, { label: "Confirmer réception", icon: "checkmark-circle-outline" as keyof typeof Ionicons.glyphMap, onPress: () => handleConfirmReception(item.id), condition: item.status === "livre", }, { label: "Supprimer", icon: "trash-outline" as keyof typeof Ionicons.glyphMap, onPress: () => handleDelete(item.id), danger: true, }, ] as Action[] ).filter((a) => a.condition !== false); return ( #{item.id} Client: {item.username} Adresse: {item.adresse} Total: {item.total_prix.toFixed(2)} € setOpenMenuId(isOpen ? null : item.id)} > Actions {isOpen && ( {actions.map((action, index) => ( {action.label} ))} )} ); }; const totalCount = itemsModal.items.length; if (loading) return ; return ( item.id.toString()} renderItem={renderOrder} refreshControl={ } contentContainerStyle={{ padding: spacing.l }} ListEmptyComponent={ Aucune commande active } /> {(itemsModal.commandInfo || itemsModal.clientInfo) && ( {itemsModal.clientInfo?.username && ( {itemsModal.clientInfo.prenom}{" "} {itemsModal.clientInfo.nom} ·{" "} {itemsModal.clientInfo.username} )} {(itemsModal.commandInfo?.address || itemsModal.commandInfo?.adresse) && ( {itemsModal.commandInfo.address || itemsModal.commandInfo.adresse} )} {itemsModal.commandInfo?.total_prix != null && ( {Number( itemsModal.commandInfo.total_prix, ).toFixed(2)}{" "} € )} {(itemsModal.commandInfo?.referral_used ?? 0) > 0 && ( Parrainage: - {Number( itemsModal.commandInfo.referral_used, ).toFixed(2)}{" "} € )} )} {totalCount > 0 && ( {totalCount} article{totalCount > 1 ? "s" : ""} )} {itemsModal.items.map((item: any, index: number) => { const statusColor = STATUS_COLORS[item.status] || colors.textMuted; return ( {index + 1} {item.produit ?? item.product_name} Qté:{" "} {item.quantite ?? item.quantity} ·{" "} {(item.prix ?? item.price)?.toFixed( 2, )}{" "} € {STATUS_LABELS[item.status] || item.status} ); })} {itemsModal.items.length === 0 && ( Aucun item )} setAssignModal({ visible: false, commandId: null }) } title={`Assigner commande #${assignModal.commandId}`} icon="bicycle-outline" > {livreurs.map((l) => ( handleAssign(l.username)} > {l.username} ))} {livreurs.length === 0 && ( Aucun livreur disponible )} {/* Modal proposition adresse */} setAddressModal({ visible: false, commandId: null, input: "", }) } title={`Proposer adresse — commande #${addressModal.commandId}`} icon="location-outline" > setAddressModal((prev) => ({ ...prev, input: t })) } multiline /> Envoyer la proposition ); }