260 lines
9.0 KiB
TypeScript
260 lines
9.0 KiB
TypeScript
import React, { useState, useEffect, useCallback, useMemo } from "react";
|
|
import {
|
|
View,
|
|
Text,
|
|
StyleSheet,
|
|
FlatList,
|
|
TouchableOpacity,
|
|
RefreshControl,
|
|
} from "react-native";
|
|
import { Ionicons } from "@expo/vector-icons";
|
|
import { spacing, fontSize } from "../../theme";
|
|
import { useTheme } from "../../context/ThemeContext";
|
|
import { addAddress, deleteAddress, getAllAddresses } 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 [loading, setLoading] = useState(true);
|
|
const [refreshing, setRefreshing] = useState(false);
|
|
const [addModal, setAddModal] = useState(false);
|
|
const [invalidInput, setInvalidInput] = useState("");
|
|
const [correctInput, setCorrectInput] = useState("");
|
|
const [saving, setSaving] = useState(false);
|
|
|
|
const loadAddresses = useCallback(async () => {
|
|
try {
|
|
const result = await getAllAddresses();
|
|
setAddresses(result);
|
|
} catch (e: any) {
|
|
showError("Erreur", e.response?.data?.error ?? e.message);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [showError]);
|
|
|
|
useEffect(() => {
|
|
loadAddresses();
|
|
}, [loadAddresses]);
|
|
|
|
const onRefresh = async () => {
|
|
setRefreshing(true);
|
|
await loadAddresses();
|
|
setRefreshing(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.md,
|
|
fontWeight: "600",
|
|
},
|
|
correct: {
|
|
color: colors.success,
|
|
fontSize: fontSize.md,
|
|
fontWeight: "600",
|
|
marginTop: 6,
|
|
},
|
|
label: {
|
|
fontSize: fontSize.xs,
|
|
fontWeight: "700",
|
|
letterSpacing: 0.5,
|
|
marginBottom: 2,
|
|
},
|
|
labelInvalid: {
|
|
color: colors.danger,
|
|
},
|
|
labelCorrect: {
|
|
color: colors.success,
|
|
},
|
|
arrow: {
|
|
color: colors.textMuted,
|
|
fontSize: fontSize.lg,
|
|
marginVertical: 4,
|
|
},
|
|
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 }}
|
|
refreshControl={
|
|
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
|
|
}
|
|
ListEmptyComponent={
|
|
<Text style={styles.empty}>
|
|
{loading
|
|
? "Chargement..."
|
|
: "Aucune correction.\nAppuyez 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>
|
|
);
|
|
}
|