diff --git a/frontend-admin/src/api/api_admin.ts b/frontend-admin/src/api/api_admin.ts index 854d9cd1..469e78fb 100644 --- a/frontend-admin/src/api/api_admin.ts +++ b/frontend-admin/src/api/api_admin.ts @@ -476,6 +476,33 @@ export const deleteProductMediaAdmin = async ( return { success: true, message: data.message }; }; +// ============================================ +// ============================================ +// ADDRESSES +// ============================================ + +export const addAddress = async ( + invalidAddress: string, + correctAddress: string, +): Promise<{ success: boolean; message: string }> => { + const { data } = await apiClient.post(`${V2}/admin/protected/add/address`, { + invalid_address: invalidAddress, + correct_address: correctAddress, + }); + return { success: true, message: data.message }; +}; + +export const deleteAddress = async ( + invalidAddress: string, + correctAddress: string, +): Promise<{ success: boolean; message: string }> => { + const { data } = await apiClient.delete( + `${V2}/admin/protected/delete/address`, + { data: { invalid_address: invalidAddress, correct_address: correctAddress } }, + ); + return { success: true, message: data.message }; +}; + // ============================================ // STATS HELPERS // ============================================ diff --git a/frontend-admin/src/navigation/AdminNavigator.tsx b/frontend-admin/src/navigation/AdminNavigator.tsx index 611edf94..f8f10e77 100644 --- a/frontend-admin/src/navigation/AdminNavigator.tsx +++ b/frontend-admin/src/navigation/AdminNavigator.tsx @@ -16,6 +16,7 @@ import UsersScreen from "../screens/admin/UsersScreen"; import ProductsScreen from "../screens/admin/ProductsScreen"; import DeliveryScreen from "../screens/admin/DeliveryScreen"; import AlertsScreen from "../screens/admin/AlertsScreen"; +import AddressScreen from "../screens/admin/AddressScreen"; const Tab = createBottomTabNavigator(); const Stack = createNativeStackNavigator(); @@ -155,6 +156,20 @@ function AdminTabs() { ), }} /> + ( + + ), + }} + /> ); } diff --git a/frontend-admin/src/navigation/types.ts b/frontend-admin/src/navigation/types.ts index d65e589a..9f852635 100644 --- a/frontend-admin/src/navigation/types.ts +++ b/frontend-admin/src/navigation/types.ts @@ -12,6 +12,7 @@ export type AdminTabParamList = { Products: undefined; Delivery: undefined; Alerts: undefined; + Addresses: undefined; }; export type AdminStackParamList = { diff --git a/frontend-admin/src/screens/admin/AddressScreen.tsx b/frontend-admin/src/screens/admin/AddressScreen.tsx new file mode 100644 index 00000000..533d3abb --- /dev/null +++ b/frontend-admin/src/screens/admin/AddressScreen.tsx @@ -0,0 +1,217 @@ +import React, { useState, useCallback, useMemo } from "react"; +import { + View, + Text, + StyleSheet, + FlatList, + TouchableOpacity, +} from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +import { spacing, fontSize } from "../../theme"; +import { useTheme } from "../../context/ThemeContext"; +import { addAddress, deleteAddress } from "../../api/api_admin"; +import { useAlert } from "../../hooks/useAlert"; +import Card from "../../components/ui/Card"; +import Modal from "../../components/ui/Modal"; +import TextInput from "../../components/ui/TextInput"; +import Button from "../../components/ui/Button"; +import AlertModal from "../../components/ui/AlertModal"; + +type AddressEntry = { + invalid_address: string; + correct_address: string; +}; + +export default function AddressScreen() { + const { colors } = useTheme(); + const { alert, showError, showSuccess, showConfirm, hideAlert } = useAlert(); + + const [addresses, setAddresses] = useState([]); + const [addModal, setAddModal] = useState(false); + const [invalidInput, setInvalidInput] = useState(""); + const [correctInput, setCorrectInput] = useState(""); + const [saving, setSaving] = useState(false); + + const openAddModal = () => { + setInvalidInput(""); + setCorrectInput(""); + setAddModal(true); + }; + + const handleAdd = useCallback(async () => { + const inv = invalidInput.trim(); + const cor = correctInput.trim(); + if (!inv || !cor) { + showError("Erreur", "Les deux champs sont obligatoires."); + return; + } + setSaving(true); + try { + await addAddress(inv, cor); + setAddresses((prev) => [ + ...prev, + { invalid_address: inv, correct_address: cor }, + ]); + setAddModal(false); + showSuccess("Succès", "Adresse ajoutée avec succès."); + } catch (e: any) { + showError("Erreur", e.response?.data?.error ?? e.message); + } finally { + setSaving(false); + } + }, [invalidInput, correctInput, showError, showSuccess]); + + const handleDelete = useCallback( + (item: AddressEntry) => { + showConfirm( + "Supprimer", + `Supprimer la correction :\n"${item.invalid_address}" → "${item.correct_address}" ?`, + async () => { + try { + await deleteAddress(item.invalid_address, item.correct_address); + setAddresses((prev) => + prev.filter( + (a) => + a.invalid_address !== item.invalid_address || + a.correct_address !== item.correct_address, + ), + ); + showSuccess("Supprimé", "Adresse supprimée."); + } catch (e: any) { + showError("Erreur", e.response?.data?.error ?? e.message); + } + }, + "Supprimer", + ); + }, + [showConfirm, showError, showSuccess], + ); + + const styles = useMemo( + () => + StyleSheet.create({ + container: { flex: 1, backgroundColor: colors.bgPrimary }, + header: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + padding: spacing.l, + paddingBottom: spacing.s, + }, + title: { + color: colors.textWhite, + fontSize: fontSize.lg, + fontWeight: "700", + }, + addBtn: { + backgroundColor: colors.accent, + borderRadius: 20, + padding: spacing.s, + }, + invalid: { + color: colors.danger, + fontSize: fontSize.sm, + fontWeight: "600", + }, + correct: { + color: colors.success, + fontSize: fontSize.sm, + marginTop: 4, + }, + arrow: { + color: colors.textMuted, + fontSize: fontSize.xs, + marginVertical: 2, + }, + row: { + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + }, + empty: { + color: colors.textMuted, + textAlign: "center", + marginTop: spacing.xxl, + fontSize: fontSize.md, + }, + }), + [colors], + ); + + const renderItem = ({ item }: { item: AddressEntry }) => ( + + + + {item.invalid_address} + + {item.correct_address} + + handleDelete(item)}> + + + + + ); + + return ( + + + Corrections d'adresses + + + + + + `${item.invalid_address}-${i}`} + renderItem={renderItem} + contentContainerStyle={{ padding: spacing.l, paddingTop: spacing.s }} + ListEmptyComponent={ + + Aucune correction.{"\n"}Appuyez sur + pour en ajouter une. + + } + /> + + setAddModal(false)} + title="Ajouter une correction" + icon="map-outline" + > + + +