diff --git a/frontend-admin/src/api/api_admin.ts b/frontend-admin/src/api/api_admin.ts index fd954514..700335b8 100644 --- a/frontend-admin/src/api/api_admin.ts +++ b/frontend-admin/src/api/api_admin.ts @@ -656,3 +656,33 @@ export const deleteCategoryAdmin = async ( }; } }; + +// ============================================ +// PARAMÈTRES GLOBAUX +// ============================================ + +export interface AppSettings { + penalties_enabled: boolean; + points_categories: string[]; + points_separated: boolean; +} + +export const getSettings = async (): Promise<{ success: boolean; settings?: AppSettings; error?: string }> => { + try { + const { data } = await apiClient.get(`${V2}/admin/protected/settings`); + return { success: true, settings: data.settings }; + } catch (error: any) { + return { success: false, error: error.response?.data?.error || "Erreur" }; + } +}; + +export const updateSettings = async ( + settings: AppSettings, +): Promise<{ success: boolean; settings?: AppSettings; error?: string }> => { + try { + const { data } = await apiClient.put(`${V2}/admin/protected/settings`, settings); + return { success: true, settings: data.settings }; + } catch (error: any) { + return { success: false, error: error.response?.data?.error || "Erreur" }; + } +}; diff --git a/frontend-admin/src/navigation/AdminNavigator.tsx b/frontend-admin/src/navigation/AdminNavigator.tsx index 013f9b16..19488f70 100644 --- a/frontend-admin/src/navigation/AdminNavigator.tsx +++ b/frontend-admin/src/navigation/AdminNavigator.tsx @@ -18,6 +18,7 @@ import CategoriesScreen from "../screens/admin/CategoriesScreen"; import DeliveryScreen from "../screens/admin/DeliveryScreen"; import AlertsScreen from "../screens/admin/AlertsScreen"; import AddressScreen from "../screens/admin/AddressScreen"; +import SettingsScreen from "../screens/admin/SettingsScreen"; const Tab = createBottomTabNavigator(); const Stack = createNativeStackNavigator(); @@ -185,6 +186,20 @@ function AdminTabs() { ), }} /> + ( + + ), + }} + /> ); } diff --git a/frontend-admin/src/navigation/types.ts b/frontend-admin/src/navigation/types.ts index bee3b9cd..9aa8b954 100644 --- a/frontend-admin/src/navigation/types.ts +++ b/frontend-admin/src/navigation/types.ts @@ -14,6 +14,7 @@ export type AdminTabParamList = { Delivery: undefined; Alerts: undefined; Addresses: undefined; + Settings: undefined; }; export type AdminStackParamList = { diff --git a/frontend-admin/src/screens/admin/SettingsScreen.tsx b/frontend-admin/src/screens/admin/SettingsScreen.tsx new file mode 100644 index 00000000..495bfe15 --- /dev/null +++ b/frontend-admin/src/screens/admin/SettingsScreen.tsx @@ -0,0 +1,314 @@ +import React, { useState, useEffect, useCallback } from "react"; +import { + View, + Text, + StyleSheet, + ScrollView, + Switch, + TouchableOpacity, + ActivityIndicator, +} from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +import { spacing, fontSize, borderRadius } from "../../theme"; +import { useTheme } from "../../context/ThemeContext"; +import { getSettings, updateSettings, getCategories } from "../../api/api_admin"; +import type { AppSettings, Category } from "../../api/api_admin"; +import AlertModal from "../../components/ui/AlertModal"; +import { useAlert } from "../../hooks/useAlert"; + +export default function SettingsScreen() { + const { colors } = useTheme(); + const { alert, showError, showSuccess, hideAlert } = useAlert(); + + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [settings, setSettings] = useState({ + penalties_enabled: true, + points_categories: [], + points_separated: true, + }); + const [categories, setCategories] = useState([]); + + const loadData = useCallback(async () => { + setLoading(true); + const [settingsRes, categoriesRes] = await Promise.all([ + getSettings(), + getCategories(), + ]); + if (settingsRes.success && settingsRes.settings) { + setSettings({ + ...settingsRes.settings, + points_categories: settingsRes.settings.points_categories ?? [], + }); + } + if (categoriesRes) { + setCategories(categoriesRes.filter((c) => !c.is_coming_soon)); + } + setLoading(false); + }, []); + + useEffect(() => { + loadData(); + }, [loadData]); + + const toggleCategory = (name: string) => { + setSettings((prev) => { + const cats = prev.points_categories ?? []; + if (cats.includes(name)) { + return { ...prev, points_categories: cats.filter((c) => c !== name) }; + } + return { ...prev, points_categories: [...cats, name] }; + }); + }; + + const handleSave = async () => { + setSaving(true); + const res = await updateSettings(settings); + setSaving(false); + if (res.success) { + showSuccess("Succès", "Paramètres sauvegardés"); + } else { + showError("Erreur", res.error || "Erreur lors de la sauvegarde"); + } + }; + + const s = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.bg, + }, + content: { + padding: spacing.l, + paddingBottom: spacing.xl * 2, + }, + section: { + backgroundColor: colors.bgSecondary, + borderRadius: borderRadius.l, + marginBottom: spacing.l, + overflow: "hidden", + }, + sectionTitle: { + fontSize: fontSize.s, + fontWeight: "700", + color: colors.textMuted, + textTransform: "uppercase", + letterSpacing: 0.8, + paddingHorizontal: spacing.l, + paddingTop: spacing.l, + paddingBottom: spacing.s, + }, + row: { + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + paddingHorizontal: spacing.l, + paddingVertical: spacing.m, + borderTopWidth: 1, + borderTopColor: colors.border, + }, + rowFirst: { + borderTopWidth: 0, + }, + rowLeft: { + flex: 1, + marginRight: spacing.m, + }, + rowLabel: { + fontSize: fontSize.m, + color: colors.text, + fontWeight: "600", + }, + rowDesc: { + fontSize: fontSize.s, + color: colors.textMuted, + marginTop: 2, + }, + categoryRow: { + flexDirection: "row", + alignItems: "center", + paddingHorizontal: spacing.l, + paddingVertical: spacing.m, + borderTopWidth: 1, + borderTopColor: colors.border, + gap: spacing.m, + }, + categoryRowFirst: { + borderTopWidth: 0, + }, + checkBox: { + width: 22, + height: 22, + borderRadius: 6, + borderWidth: 2, + borderColor: colors.accent, + alignItems: "center", + justifyContent: "center", + }, + checkBoxChecked: { + backgroundColor: colors.accent, + }, + categoryName: { + fontSize: fontSize.m, + color: colors.text, + flex: 1, + }, + allCatsHint: { + fontSize: fontSize.s, + color: colors.textMuted, + fontStyle: "italic", + paddingHorizontal: spacing.l, + paddingBottom: spacing.m, + }, + saveButton: { + backgroundColor: colors.accent, + borderRadius: borderRadius.l, + paddingVertical: spacing.m, + alignItems: "center", + flexDirection: "row", + justifyContent: "center", + gap: spacing.s, + marginTop: spacing.s, + }, + saveButtonText: { + color: "#fff", + fontSize: fontSize.m, + fontWeight: "700", + }, + }); + + if (loading) { + return ( + + + + ); + } + + const selectedCats = settings.points_categories ?? []; + const allSelected = selectedCats.length === 0; + + return ( + + + {/* Section amendes */} + + Amendes + + + Amendes activées + + Bloquer les commandes si le client a une amende non payée + + + + setSettings((prev) => ({ ...prev, penalties_enabled: v })) + } + trackColor={{ false: colors.border, true: colors.accent }} + thumbColor="#fff" + /> + + + + {/* Section points */} + + Système de points + + + + Points séparés par catégorie + + Activé : weed → point, zipette → point_zipette{"\n"} + Désactivé : tous les points dans un seul compteur + + + + setSettings((prev) => ({ ...prev, points_separated: v })) + } + trackColor={{ false: colors.border, true: colors.accent }} + thumbColor="#fff" + /> + + + + {/* Section catégories éligibles aux points */} + + Catégories éligibles aux points + {allSelected && ( + + Aucune sélection = comportement par défaut (toutes les catégories sauf gros&semi) + + )} + {categories.map((cat, index) => { + const checked = selectedCats.includes(cat.name); + return ( + toggleCategory(cat.name)} + > + + {checked && ( + + )} + + + {cat.name} + + ); + })} + {categories.length === 0 && ( + + Aucune catégorie disponible + + )} + + + + {saving ? ( + + ) : ( + <> + + Sauvegarder + + )} + + + + + + ); +}