chore: add new design for new routes
This commit is contained in:
@@ -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
|
||||
// ============================================
|
||||
|
||||
@@ -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<AdminTabParamList>();
|
||||
const Stack = createNativeStackNavigator<AdminStackParamList>();
|
||||
@@ -155,6 +156,20 @@ function AdminTabs() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="Addresses"
|
||||
component={AddressScreen}
|
||||
options={{
|
||||
title: "Adresses",
|
||||
tabBarIcon: ({ color, size }) => (
|
||||
<Ionicons
|
||||
name="map-outline"
|
||||
size={size}
|
||||
color={color}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Tab.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ export type AdminTabParamList = {
|
||||
Products: undefined;
|
||||
Delivery: undefined;
|
||||
Alerts: undefined;
|
||||
Addresses: undefined;
|
||||
};
|
||||
|
||||
export type AdminStackParamList = {
|
||||
|
||||
@@ -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<AddressEntry[]>([]);
|
||||
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 }) => (
|
||||
<Card style={{ marginBottom: spacing.m }}>
|
||||
<View style={styles.row}>
|
||||
<View style={{ flex: 1, marginRight: spacing.m }}>
|
||||
<Text style={styles.invalid}>{item.invalid_address}</Text>
|
||||
<Text style={styles.arrow}>↓</Text>
|
||||
<Text style={styles.correct}>{item.correct_address}</Text>
|
||||
</View>
|
||||
<TouchableOpacity onPress={() => handleDelete(item)}>
|
||||
<Ionicons name="trash-outline" size={22} color={colors.danger} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</Card>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>Corrections d'adresses</Text>
|
||||
<TouchableOpacity style={styles.addBtn} onPress={openAddModal}>
|
||||
<Ionicons name="add" size={22} color={colors.textWhite} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<FlatList
|
||||
data={addresses}
|
||||
keyExtractor={(item, i) => `${item.invalid_address}-${i}`}
|
||||
renderItem={renderItem}
|
||||
contentContainerStyle={{ padding: spacing.l, paddingTop: spacing.s }}
|
||||
ListEmptyComponent={
|
||||
<Text style={styles.empty}>
|
||||
Aucune correction.{"\n"}Appuyez sur + pour en ajouter une.
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
visible={addModal}
|
||||
onClose={() => setAddModal(false)}
|
||||
title="Ajouter une correction"
|
||||
icon="map-outline"
|
||||
>
|
||||
<TextInput
|
||||
label="Adresse invalide"
|
||||
value={invalidInput}
|
||||
onChangeText={setInvalidInput}
|
||||
placeholder="Ex: 10 rue de la paix"
|
||||
autoCapitalize="none"
|
||||
/>
|
||||
<TextInput
|
||||
label="Adresse correcte"
|
||||
value={correctInput}
|
||||
onChangeText={setCorrectInput}
|
||||
placeholder="Ex: 10 Rue de la Paix, 75001 Paris"
|
||||
autoCapitalize="none"
|
||||
/>
|
||||
<Button
|
||||
title="Ajouter"
|
||||
onPress={handleAdd}
|
||||
loading={saving}
|
||||
style={{ marginTop: spacing.m }}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<AlertModal
|
||||
visible={alert.visible}
|
||||
type={alert.type}
|
||||
title={alert.title}
|
||||
message={alert.message}
|
||||
onClose={hideAlert}
|
||||
onConfirm={alert.onConfirm}
|
||||
confirmText={alert.confirmText}
|
||||
cancelText={alert.cancelText}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user