import React, { useState, useEffect, useMemo } from "react"; import { View, Text, ScrollView, Image, StyleSheet } from "react-native"; import { useRoute } from "@react-navigation/native"; import type { RouteProp } from "@react-navigation/native"; import { Ionicons } from "@expo/vector-icons"; import { getCommandItemsWithDetails, getProductById, formatOrderDate, formatPrice, } from "../../api/api"; import type { ClientStackParamList } from "../../navigation/types"; import StatusBadge from "../../components/StatusBadge"; import LoadingSpinner from "../../components/ui/LoadingSpinner"; import Card from "../../components/ui/Card"; import { useTheme } from "../../context/ThemeContext"; import { spacing, borderRadius, fontSize, fontWeight } from "../../theme"; import { API_BASE_URL } from "../../api/client"; type Route = RouteProp; const TIMELINE_STEPS = [ { key: "pending", label: "Confirmee", icon: "checkmark-circle-outline" as const, }, { key: "assigned", label: "Assignee", icon: "person-outline" as const }, { key: "en_route", label: "En route", icon: "bicycle-outline" as const }, { key: "arrived", label: "Arrivee", icon: "flag-outline" as const }, { key: "livre", label: "Livree", icon: "cube-outline" as const }, { key: "approved", label: "Terminee", icon: "shield-checkmark-outline" as const, }, ]; const STATUS_INDEX: Record = { pending: 0, assigned: 1, en_route: 2, arrived: 3, livre: 4, delivered: 4, approved: 5, cancelled: -1, }; export default function OrderDetailsScreen() { const { params } = useRoute(); const { colors } = useTheme(); const [order, setOrder] = useState(null); const [products, setProducts] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { (async () => { try { const res = await getCommandItemsWithDetails(params.orderId); if (res.success && res.data) { const data = res.data; const cmd = data.command || data.command_info || data; if (cmd.command_status && !cmd.status) cmd.status = cmd.command_status; if (cmd.command_address && !cmd.delivery_address) cmd.delivery_address = cmd.command_address; if (data.client_info) { cmd.client_nom = cmd.client_nom || data.client_info.nom; cmd.client_prenom = cmd.client_prenom || data.client_info.prenom; cmd.client_telephone = cmd.client_telephone || data.client_info.telephone; } setOrder(cmd); const items = data.items || data.products || []; const enriched = await Promise.all( items.map(async (item: any) => { try { const pRes = await getProductById( item.product_id || item.id, ); const p = pRes?.data || pRes?.product || pRes; const img = p?.media?.find( (m: any) => m.type === "image", ); return { ...item, imageUri: img ? `${API_BASE_URL}${img.url}` : undefined, }; } catch { return item; } }), ); setProducts(enriched); } else { setError("Commande introuvable"); } } catch { setError("Erreur de chargement"); } finally { setLoading(false); } })(); }, [params.orderId]); const styles = useMemo( () => StyleSheet.create({ container: { flex: 1, backgroundColor: colors.bgPrimary }, content: { padding: spacing.l, paddingBottom: spacing.xxxl }, center: { flex: 1, justifyContent: "center", alignItems: "center", backgroundColor: colors.bgPrimary, }, errorText: { color: colors.danger, fontSize: fontSize.md }, headerCard: { backgroundColor: colors.bgCard, borderRadius: borderRadius.md, padding: spacing.xl, marginBottom: spacing.l, borderWidth: 1, borderColor: colors.borderLight, }, headerRow: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: spacing.s, }, title: { color: colors.textWhite, fontSize: fontSize.xl, fontWeight: fontWeight.bold, }, date: { color: colors.textMuted, fontSize: fontSize.sm }, sectionTitle: { color: colors.textSecondary, fontSize: fontSize.sm, fontWeight: fontWeight.medium, textTransform: "uppercase", letterSpacing: 1, marginBottom: spacing.l, }, timeline: { flexDirection: "row", justifyContent: "space-between", alignItems: "flex-start", }, timelineStep: { alignItems: "center", flex: 1, position: "relative", }, timelineDot: { width: 32, height: 32, borderRadius: 16, backgroundColor: colors.bgInput, justifyContent: "center", alignItems: "center", borderWidth: 2, borderColor: colors.border, zIndex: 1, }, timelineDotCompleted: { backgroundColor: colors.success, borderColor: colors.successDark, }, timelineDotCurrent: { backgroundColor: colors.accent, borderColor: colors.accentLight, shadowColor: colors.accent, shadowOffset: { width: 0, height: 0 }, shadowOpacity: 0.5, shadowRadius: 8, elevation: 6, }, timelineLabel: { color: colors.textMuted, fontSize: 10, marginTop: spacing.xs, textAlign: "center", }, timelineLabelCompleted: { color: colors.success, fontWeight: fontWeight.medium, }, timelineLabelCurrent: { color: colors.accent, fontWeight: fontWeight.bold, }, timelineLine: { position: "absolute", top: 15, left: "58%", right: "-42%", height: 2, backgroundColor: colors.border, zIndex: 0, }, timelineLineCompleted: { backgroundColor: colors.success }, timelineLineCurrent: { backgroundColor: colors.accent }, currentBadge: { marginTop: 4, alignItems: "center" }, currentDot: { width: 6, height: 6, borderRadius: 3, backgroundColor: colors.accent, }, cancelledBanner: { alignItems: "center", gap: spacing.m, paddingVertical: spacing.l, }, cancelledIconCircle: { width: 56, height: 56, borderRadius: 28, backgroundColor: "rgba(239,68,68,0.12)", justifyContent: "center", alignItems: "center", }, cancelledText: { color: colors.danger, fontSize: fontSize.md, fontWeight: fontWeight.semibold, }, infoRow: { flexDirection: "row", alignItems: "center", gap: spacing.s, marginBottom: spacing.m, }, infoText: { color: colors.textPrimary, fontSize: fontSize.md, flex: 1, }, productRow: { flexDirection: "row", alignItems: "center", paddingVertical: spacing.m, }, productRowBorder: { borderTopWidth: 1, borderTopColor: colors.borderLight, }, productImage: { width: 50, height: 50, borderRadius: borderRadius.sm, }, productImagePlaceholder: { width: 50, height: 50, borderRadius: borderRadius.sm, backgroundColor: colors.bgInput, justifyContent: "center", alignItems: "center", }, productInfo: { flex: 1, marginLeft: spacing.m }, productName: { color: colors.textWhite, fontSize: fontSize.md, fontWeight: fontWeight.medium, }, productQty: { color: colors.textMuted, fontSize: fontSize.sm, marginTop: 2, }, productPrice: { color: colors.success, fontSize: fontSize.md, fontWeight: fontWeight.bold, }, totalRow: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", }, totalLabel: { color: colors.textSecondary, fontSize: fontSize.lg, fontWeight: fontWeight.semibold, }, totalValue: { color: colors.success, fontSize: fontSize.xxl, fontWeight: fontWeight.bold, }, }), [colors], ); if (loading) return ; if (error || !order) { return ( {error || "Commande introuvable"} ); } const status = order.status || "pending"; const currentIdx = STATUS_INDEX[status] ?? -1; const isCancelled = status === "cancelled"; const address = order.delivery_address || order.adresse || "N/A"; const total = order.total || order.total_prix || products.reduce((s: number, p: any) => s + (p.prix || p.price || 0), 0); return ( Commande #{order.client_order_number} {formatOrderDate(order.created_at)} Suivi {isCancelled ? ( Commande annulee ) : ( {TIMELINE_STEPS.map((step, idx) => { const isFinished = currentIdx >= TIMELINE_STEPS.length - 1; const completed = isFinished ? true : idx < currentIdx; const current = isFinished ? false : idx === currentIdx; const lineCompleted = isFinished ? true : idx < currentIdx; return ( {idx < TIMELINE_STEPS.length - 1 && ( )} {completed ? ( ) : ( )} {step.label} {current && ( )} ); })} )} Livraison {address} {order.livreur_assign && ( {order.livreur_assign} )} {(order.client_prenom || order.first_name) && ( {order.first_name || order.client_prenom}{" "} {order.last_name || order.client_nom} )} {(order.phone || order.client_telephone) && ( {order.phone || order.client_telephone} )} Produits ({products.length}) {products.map((product, idx) => ( 0 && styles.productRowBorder, ]} > {product.imageUri ? ( ) : ( )} {product.produit || product.product_name || product.name_product || "Produit"} {product.quantite || product.quantity || 0}g {formatPrice(product.prix || product.price || 0)} ))} Total {formatPrice(total)} ); }