diff --git a/frontend-admin/src/api/api_admin.ts b/frontend-admin/src/api/api_admin.ts index 6f2882de..601bdd39 100644 --- a/frontend-admin/src/api/api_admin.ts +++ b/frontend-admin/src/api/api_admin.ts @@ -704,6 +704,12 @@ export interface PointsTier { points: number; } +export interface PostalZone { + name: string; + min_amount: number; + codes: string[]; +} + export interface DaySchedule { enabled: boolean; open_time: string; // "09:00" @@ -720,6 +726,12 @@ export interface DeliverySchedule { sunday: DaySchedule; } +export const DEFAULT_POSTAL_ZONES: PostalZone[] = [ + { name: "Zone 30€", min_amount: 30, codes: ["44000", "44100", "44200", "44300"] }, + { name: "Zone 50€", min_amount: 50, codes: ["44400", "44880", "44120", "44230", "44115", "44980", "44470", "44240", "44700", "44800", "44340", "44620", "44830"] }, + { name: "Zone 100€", min_amount: 100, codes: ["44860", "44220", "44118", "44710", "44690", "44119"] }, +]; + export const DEFAULT_DAY: DaySchedule = { enabled: true, open_time: "09:00", close_time: "20:00" }; export const DEFAULT_DELIVERY_SCHEDULE: DeliverySchedule = { @@ -741,6 +753,7 @@ export interface AppSettings { points_total_tiers: PointsTier[]; referral_enabled: boolean; delivery_schedule: DeliverySchedule; + postal_zones: PostalZone[]; } export const getSettings = async (): Promise<{ diff --git a/frontend-admin/src/screens/admin/SettingsScreen.tsx b/frontend-admin/src/screens/admin/SettingsScreen.tsx index bc022a6f..df4ab72f 100644 --- a/frontend-admin/src/screens/admin/SettingsScreen.tsx +++ b/frontend-admin/src/screens/admin/SettingsScreen.tsx @@ -12,8 +12,8 @@ import { import { Ionicons } from "@expo/vector-icons"; import { spacing, fontSize, borderRadius } from "../../theme"; import { useTheme } from "../../context/ThemeContext"; -import { getSettings, updateSettings, getCategories, DEFAULT_DELIVERY_SCHEDULE } from "../../api/api_admin"; -import type { AppSettings, Category, PointsTier, DaySchedule, DeliverySchedule } from "../../api/api_admin"; +import { getSettings, updateSettings, getCategories, DEFAULT_DELIVERY_SCHEDULE, DEFAULT_POSTAL_ZONES } from "../../api/api_admin"; +import type { AppSettings, Category, PointsTier, DaySchedule, DeliverySchedule, PostalZone } from "../../api/api_admin"; import AlertModal from "../../components/ui/AlertModal"; import { useAlert } from "../../hooks/useAlert"; @@ -119,6 +119,226 @@ function DeliveryScheduleSection({ ); } +// ────────────────────────────────────────────────────────────── +// Composant éditeur de zones postales +// ────────────────────────────────────────────────────────────── +function PostalZonesSection({ + zones, + onChange, + colors, + s, +}: { + zones: PostalZone[]; + onChange: (zones: PostalZone[]) => void; + colors: any; + s: any; +}) { + const [expandedIndex, setExpandedIndex] = React.useState(null); + + const addZone = () => { + const next = [...zones, { name: "Nouvelle zone", min_amount: 0, codes: [] }]; + onChange(next); + setExpandedIndex(next.length - 1); + }; + + const removeZone = (i: number) => { + onChange(zones.filter((_, idx) => idx !== i)); + if (expandedIndex === i) setExpandedIndex(null); + }; + + const updateZone = (i: number, patch: Partial) => { + onChange(zones.map((z, idx) => (idx === i ? { ...z, ...patch } : z))); + }; + + const addCode = (i: number, code: string) => { + const trimmed = code.trim(); + if (!trimmed || zones[i].codes.includes(trimmed)) return; + updateZone(i, { codes: [...zones[i].codes, trimmed] }); + }; + + const removeCode = (zoneIdx: number, codeIdx: number) => { + updateZone(zoneIdx, { + codes: zones[zoneIdx].codes.filter((_, idx) => idx !== codeIdx), + }); + }; + + return ( + + Zones de livraison + + Définissez les zones de livraison et leur montant minimum de commande. + + + {zones.map((zone, i) => { + const expanded = expandedIndex === i; + return ( + + {/* En-tête zone */} + setExpandedIndex(expanded ? null : i)} + style={{ + flexDirection: "row", + alignItems: "center", + paddingHorizontal: spacing.l, + paddingVertical: spacing.m, + gap: spacing.s, + }} + activeOpacity={0.7} + > + + + {zone.name || "Zone sans nom"} + + + {zone.min_amount > 0 ? `${zone.min_amount}€ min` : "Sans min"} + + + {zone.codes.length} codes + + removeZone(i)} style={{ marginLeft: spacing.s }}> + + + + + {/* Détail zone */} + {expanded && ( + + {/* Nom */} + + Nom + updateZone(i, { name: v })} + placeholder="Ex: Zone centre-ville" + placeholderTextColor={colors.textMuted} + /> + + {/* Minimum */} + + Min € + { + const n = parseFloat(v); + if (!isNaN(n)) updateZone(i, { min_amount: n }); + }} + keyboardType="decimal-pad" + placeholder="0" + placeholderTextColor={colors.textMuted} + /> + + {/* Codes postaux */} + Codes postaux : + + {zone.codes.map((code, ci) => ( + removeCode(i, ci)} + style={{ + flexDirection: "row", + alignItems: "center", + backgroundColor: colors.accent + "20", + borderRadius: borderRadius.sm, + paddingHorizontal: spacing.s, + paddingVertical: 4, + gap: 4, + }} + > + + {code} + + + + ))} + addCode(i, code)} + colors={colors} + s={s} + /> + + + )} + + ); + })} + + {zones.length === 0 && ( + Aucune zone définie — toutes les commandes seront acceptées sans minimum. + )} + + + + + Ajouter une zone + + + + ); +} + +function PostalCodeInput({ + onAdd, + colors, + s, +}: { + onAdd: (code: string) => void; + colors: any; + s: any; +}) { + const [value, setValue] = React.useState(""); + return ( + + { + if (value.length === 5) { + onAdd(value); + setValue(""); + } + }} + /> + { + if (value.length === 5) { + onAdd(value); + setValue(""); + } + }} + style={{ + backgroundColor: colors.accent, + borderRadius: borderRadius.sm, + padding: 6, + }} + > + + + + ); +} + // ────────────────────────────────────────────────────────────── // Composant éditeur de paliers // ────────────────────────────────────────────────────────────── @@ -270,6 +490,7 @@ export default function SettingsScreen() { points_total_tiers: [], referral_enabled: true, delivery_schedule: DEFAULT_DELIVERY_SCHEDULE, + postal_zones: DEFAULT_POSTAL_ZONES, }); const [categories, setCategories] = useState([]); @@ -287,6 +508,7 @@ export default function SettingsScreen() { points_categories_total: settingsRes.settings.points_categories_total ?? [], points_total_tiers: settingsRes.settings.points_total_tiers ?? [], delivery_schedule: settingsRes.settings.delivery_schedule ?? DEFAULT_DELIVERY_SCHEDULE, + postal_zones: settingsRes.settings.postal_zones ?? DEFAULT_POSTAL_ZONES, }); } if (categoriesRes) { @@ -683,6 +905,14 @@ export default function SettingsScreen() { s={s} /> + {/* Zones de livraison */} + setSettings((p) => ({ ...p, postal_zones: zones }))} + colors={colors} + s={s} + /> + Livreur )} - {confirmationData?.queue_info && ( - - - - Position: {confirmationData.queue_info.position}{" "} - - {confirmationData.queue_info.estimated_wait} - - - )}