diff --git a/frontend-admin/src/api/api_admin.ts b/frontend-admin/src/api/api_admin.ts index 515bc581..93ccd1d2 100644 --- a/frontend-admin/src/api/api_admin.ts +++ b/frontend-admin/src/api/api_admin.ts @@ -747,6 +747,16 @@ export const DEFAULT_DELIVERY_SCHEDULE: DeliverySchedule = { saturday: { ...DEFAULT_DAY }, sunday: { ...DEFAULT_DAY }, }; +export interface CategoryRoute { + deliveryman_username: string; + categories: string[]; +} + +export interface DeliveryModeConfig { + mode: "single" | "category_based"; + category_routes: CategoryRoute[]; +} + export interface PenaltyTier { min_cancel: number; amount: number; @@ -768,6 +778,7 @@ export interface AppSettings { nowpayments_currencies: string[]; telegram_bot_token: string; telegram_bot_username: string; + delivery_mode: DeliveryModeConfig; } export const getSettings = async (): Promise<{ diff --git a/frontend-admin/src/screens/admin/SettingsScreen.tsx b/frontend-admin/src/screens/admin/SettingsScreen.tsx index 48a0762c..ee184107 100644 --- a/frontend-admin/src/screens/admin/SettingsScreen.tsx +++ b/frontend-admin/src/screens/admin/SettingsScreen.tsx @@ -14,8 +14,8 @@ import { useNavigation } from "@react-navigation/native"; import { Ionicons } from "@expo/vector-icons"; import { spacing, fontSize, borderRadius } from "../../theme"; import { useTheme } from "../../context/ThemeContext"; -import { getSettings, updateSettings, getCategories, DEFAULT_DELIVERY_SCHEDULE, DEFAULT_POSTAL_ZONES } from "../../api/api_admin"; -import type { AppSettings, Category, PointsTier, PointsPool, DaySchedule, DeliverySchedule, PostalZone } from "../../api/api_admin"; +import { getSettings, updateSettings, getCategories, getAvailableDeliveryPersons, DEFAULT_DELIVERY_SCHEDULE, DEFAULT_POSTAL_ZONES } from "../../api/api_admin"; +import type { AppSettings, Category, CategoryRoute, DeliveryModeConfig, PointsTier, PointsPool, DaySchedule, DeliverySchedule, PostalZone } from "../../api/api_admin"; import AlertModal from "../../components/ui/AlertModal"; import { useAlert } from "../../hooks/useAlert"; @@ -667,10 +667,12 @@ export default function SettingsScreen() { nowpayments_currencies: [], telegram_bot_token: "", telegram_bot_username: "", + delivery_mode: { mode: "single" as const, category_routes: [] }, }); const [showApiKey, setShowApiKey] = useState(false); const [showIpnSecret, setShowIpnSecret] = useState(false); const [categories, setCategories] = useState([]); + const [livreurs, setLivreurs] = useState([]); // Refs pour l'auto-sauvegarde au départ de la page const settingsRef = useRef(settings); @@ -699,9 +701,10 @@ export default function SettingsScreen() { const loadData = useCallback(async () => { setLoading(true); isLoaded.current = false; - const [settingsRes, categoriesRes] = await Promise.all([ + const [settingsRes, categoriesRes, livreursRes] = await Promise.all([ getSettings(), getCategories(), + getAvailableDeliveryPersons(), ]); if (settingsRes.success && settingsRes.settings) { setSettings({ @@ -709,11 +712,15 @@ export default function SettingsScreen() { points_pools: settingsRes.settings.points_pools ?? [], delivery_schedule: settingsRes.settings.delivery_schedule ?? DEFAULT_DELIVERY_SCHEDULE, postal_zones: settingsRes.settings.postal_zones ?? DEFAULT_POSTAL_ZONES, + delivery_mode: settingsRes.settings.delivery_mode ?? { mode: "single", category_routes: [] }, }); } if (categoriesRes) { setCategories(categoriesRes.filter((c) => !c.is_coming_soon)); } + if (livreursRes.success) { + setLivreurs(livreursRes.livreurs.map((l: any) => l.username)); + } setLoading(false); // Marquer comme chargé après un tick pour que le useEffect de settings ne // considère pas le setSettings initial comme une modification utilisateur @@ -1425,6 +1432,131 @@ export default function SettingsScreen() { + {/* ============================================ */} + {/* 🚚 MODE DE LIVRAISON */} + {/* ============================================ */} + + Mode de livraison + + Choisissez comment les commandes sont assignées aux livreurs. + + + {/* Sélection du mode */} + + {(["single", "category_based"] as const).map((mode) => { + const selected = settings.delivery_mode.mode === mode; + const label = mode === "single" ? "Livreur unique" : "Par catégorie"; + const desc = mode === "single" ? "Toutes les commandes → même livreur" : "Chaque livreur gère ses catégories"; + return ( + setSettings((p) => ({ + ...p, + delivery_mode: { ...p.delivery_mode, mode }, + }))} + style={{ + flex: 1, + padding: spacing.m, + borderRadius: borderRadius.sm, + borderWidth: 2, + borderColor: selected ? "#10b981" : colors.border, + backgroundColor: selected ? "#10b98118" : colors.bgSecondary, + }} + > + {label} + {desc} + + ); + })} + + + {/* Configuration des routes par catégorie */} + {settings.delivery_mode.mode === "category_based" && ( + + + Assignation livreur → catégories + + + {settings.delivery_mode.category_routes.map((route, idx) => ( + + {/* Sélection livreur */} + Livreur + + {livreurs.length === 0 ? ( + Aucun livreur actif + ) : livreurs.map((username) => { + const sel = route.deliveryman_username === username; + return ( + setSettings((p) => { + const routes = [...p.delivery_mode.category_routes]; + routes[idx] = { ...routes[idx], deliveryman_username: username }; + return { ...p, delivery_mode: { ...p.delivery_mode, category_routes: routes } }; + })} + style={{ paddingHorizontal: spacing.m, paddingVertical: spacing.xs, borderRadius: borderRadius.full, borderWidth: 1.5, borderColor: sel ? "#10b981" : colors.border, backgroundColor: sel ? "#10b98122" : "transparent" }} + > + {username} + + ); + })} + + + {/* Sélection catégories */} + Catégories assignées + + {categories.map((cat) => { + const sel = route.categories.includes(cat.name); + return ( + setSettings((p) => { + const routes = [...p.delivery_mode.category_routes]; + const cats = routes[idx].categories.includes(cat.name) + ? routes[idx].categories.filter((c) => c !== cat.name) + : [...routes[idx].categories, cat.name]; + routes[idx] = { ...routes[idx], categories: cats }; + return { ...p, delivery_mode: { ...p.delivery_mode, category_routes: routes } }; + })} + style={{ paddingHorizontal: spacing.m, paddingVertical: spacing.xs, borderRadius: borderRadius.full, borderWidth: 1.5, borderColor: sel ? "#3b82f6" : colors.border, backgroundColor: sel ? "#3b82f622" : "transparent" }} + > + {cat.name} + + ); + })} + + + {/* Supprimer route */} + setSettings((p) => { + const routes = p.delivery_mode.category_routes.filter((_, i) => i !== idx); + return { ...p, delivery_mode: { ...p.delivery_mode, category_routes: routes } }; + })} + style={{ marginTop: spacing.m, flexDirection: "row", alignItems: "center", gap: spacing.xs }} + > + + Supprimer cette règle + + + ))} + + setSettings((p) => ({ + ...p, + delivery_mode: { + ...p.delivery_mode, + category_routes: [...p.delivery_mode.category_routes, { deliveryman_username: "", categories: [] }], + }, + }))} + style={{ flexDirection: "row", alignItems: "center", gap: spacing.xs, paddingVertical: spacing.m }} + > + + Ajouter une règle + + + )} + +