add: new button in admin & cabine
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
FlatList,
|
||||
TouchableOpacity,
|
||||
RefreshControl,
|
||||
ScrollView,
|
||||
} from "react-native";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
@@ -17,6 +18,10 @@ import {
|
||||
getAvailableDeliveryPersons,
|
||||
assignDeliveryPerson,
|
||||
updateCommandStatus,
|
||||
getCommandItems,
|
||||
notifyClientToDescend,
|
||||
confirmReceptionAdmin,
|
||||
deleteCommand,
|
||||
} from "../../api/api_admin";
|
||||
import type { CommandResponse } from "../../api/types";
|
||||
import type { AdminStackParamList } from "../../navigation/types";
|
||||
@@ -24,6 +29,7 @@ import StatusBadge from "../../components/StatusBadge";
|
||||
import LoadingSpinner from "../../components/ui/LoadingSpinner";
|
||||
import Modal from "../../components/ui/Modal";
|
||||
import Button from "../../components/ui/Button";
|
||||
import Card from "../../components/ui/Card";
|
||||
import AlertModal from "../../components/ui/AlertModal";
|
||||
import { useAlert } from "../../hooks/useAlert";
|
||||
|
||||
@@ -47,12 +53,27 @@ export default function OrdersScreen() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [filter, setFilter] = useState("all");
|
||||
const { alert, showError, showSuccess, showConfirm, hideAlert } = useAlert();
|
||||
|
||||
const [assignModal, setAssignModal] = useState<{
|
||||
visible: boolean;
|
||||
commandId: number | null;
|
||||
}>({ visible: false, commandId: null });
|
||||
const [livreurs, setLivreurs] = useState<any[]>([]);
|
||||
const { alert, showError, hideAlert } = useAlert();
|
||||
|
||||
const [itemsModal, setItemsModal] = useState<{
|
||||
visible: boolean;
|
||||
commandId: number | null;
|
||||
items: any[];
|
||||
commandInfo: any;
|
||||
clientInfo: any;
|
||||
}>({
|
||||
visible: false,
|
||||
commandId: null,
|
||||
items: [],
|
||||
commandInfo: null,
|
||||
clientInfo: null,
|
||||
});
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
try {
|
||||
@@ -105,6 +126,75 @@ export default function OrdersScreen() {
|
||||
}
|
||||
};
|
||||
|
||||
const openItems = async (commandId: number) => {
|
||||
try {
|
||||
const result = await getCommandItems(commandId);
|
||||
setItemsModal({
|
||||
visible: true,
|
||||
commandId,
|
||||
items: result.items,
|
||||
commandInfo: result.command_info,
|
||||
clientInfo: result.client_info,
|
||||
});
|
||||
} catch (e: any) {
|
||||
showError("Erreur", e.message);
|
||||
}
|
||||
};
|
||||
|
||||
const closeItemsModal = () =>
|
||||
setItemsModal({
|
||||
visible: false,
|
||||
commandId: null,
|
||||
items: [],
|
||||
commandInfo: null,
|
||||
clientInfo: null,
|
||||
});
|
||||
|
||||
const handleNotifyClient = async (commandId: number) => {
|
||||
try {
|
||||
await notifyClientToDescend(commandId);
|
||||
showSuccess("Notification envoyée", "Le client a été prévenu de descendre");
|
||||
} catch (e: any) {
|
||||
showError("Erreur", e.message);
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirmReception = (commandId: number) => {
|
||||
showConfirm(
|
||||
"Confirmer la commande",
|
||||
`Confirmer la réception de la commande #${commandId} ?`,
|
||||
async () => {
|
||||
try {
|
||||
const res = await confirmReceptionAdmin(commandId);
|
||||
showSuccess(
|
||||
"Réception confirmée",
|
||||
`${res.points_earned} point(s) attribués au client ${res.client_username}`,
|
||||
);
|
||||
await loadData();
|
||||
} catch (e: any) {
|
||||
showError("Erreur", e.message);
|
||||
}
|
||||
},
|
||||
"Confirmer",
|
||||
);
|
||||
};
|
||||
|
||||
const handleDelete = (commandId: number) => {
|
||||
showConfirm(
|
||||
"Supprimer",
|
||||
`Supprimer la commande #${commandId} ?`,
|
||||
async () => {
|
||||
try {
|
||||
await deleteCommand(commandId);
|
||||
await loadData();
|
||||
} catch (e: any) {
|
||||
showError("Erreur", e.message);
|
||||
}
|
||||
},
|
||||
"Supprimer",
|
||||
);
|
||||
};
|
||||
|
||||
const styles = useMemo(
|
||||
() =>
|
||||
StyleSheet.create({
|
||||
@@ -132,25 +222,6 @@ export default function OrdersScreen() {
|
||||
fontSize: fontSize.sm,
|
||||
},
|
||||
filterTextActive: { color: colors.textWhite },
|
||||
card: {
|
||||
backgroundColor: colors.bgCard,
|
||||
borderRadius: borderRadius.md,
|
||||
padding: spacing.l,
|
||||
marginBottom: spacing.m,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.borderLight,
|
||||
},
|
||||
cardHeader: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: spacing.s,
|
||||
},
|
||||
orderId: {
|
||||
fontSize: fontSize.lg,
|
||||
fontWeight: "bold",
|
||||
color: colors.textWhite,
|
||||
},
|
||||
cardText: {
|
||||
color: colors.textSecondary,
|
||||
fontSize: fontSize.sm,
|
||||
@@ -161,8 +232,20 @@ export default function OrdersScreen() {
|
||||
fontSize: fontSize.xs,
|
||||
marginTop: spacing.s,
|
||||
},
|
||||
orderId: {
|
||||
fontSize: fontSize.lg,
|
||||
fontWeight: "bold",
|
||||
color: colors.textWhite,
|
||||
},
|
||||
row: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: spacing.s,
|
||||
},
|
||||
actions: {
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
gap: spacing.s,
|
||||
marginTop: spacing.m,
|
||||
},
|
||||
@@ -182,42 +265,101 @@ export default function OrdersScreen() {
|
||||
gap: spacing.m,
|
||||
},
|
||||
livreurName: { color: colors.textWhite, fontSize: fontSize.md },
|
||||
|
||||
// Modal items
|
||||
modalSummary: {
|
||||
backgroundColor: colors.bgCard,
|
||||
borderRadius: borderRadius.sm,
|
||||
padding: spacing.m,
|
||||
marginBottom: spacing.m,
|
||||
gap: spacing.xs,
|
||||
},
|
||||
modalSummaryRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: spacing.s,
|
||||
},
|
||||
modalSummaryText: {
|
||||
color: colors.textSecondary,
|
||||
fontSize: fontSize.sm,
|
||||
flex: 1,
|
||||
},
|
||||
modalTotal: {
|
||||
color: colors.accent,
|
||||
fontSize: fontSize.md,
|
||||
fontWeight: "700",
|
||||
marginTop: spacing.xs,
|
||||
},
|
||||
itemCard: {
|
||||
backgroundColor: colors.bgCard,
|
||||
borderRadius: borderRadius.sm,
|
||||
marginBottom: spacing.s,
|
||||
overflow: "hidden",
|
||||
},
|
||||
itemCardBody: {
|
||||
padding: spacing.m,
|
||||
},
|
||||
itemName: {
|
||||
color: colors.textWhite,
|
||||
fontSize: fontSize.md,
|
||||
fontWeight: "600",
|
||||
marginBottom: 2,
|
||||
},
|
||||
itemMeta: {
|
||||
color: colors.textMuted,
|
||||
fontSize: fontSize.xs,
|
||||
},
|
||||
}),
|
||||
[colors],
|
||||
);
|
||||
|
||||
const renderOrder = ({ item }: { item: CommandResponse }) => (
|
||||
<TouchableOpacity
|
||||
style={styles.card}
|
||||
onPress={() =>
|
||||
navigation.navigate("OrderDetail", { orderId: item.id })
|
||||
}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={styles.cardHeader}>
|
||||
<Text style={styles.orderId}>#{item.id}</Text>
|
||||
<StatusBadge status={item.status} />
|
||||
</View>
|
||||
<Text style={styles.cardText}>Client: {item.username}</Text>
|
||||
<Text style={styles.cardText}>Adresse: {item.adresse}</Text>
|
||||
<Text style={styles.cardText}>
|
||||
Total: {item.total_prix.toFixed(2)} €
|
||||
</Text>
|
||||
{item.livreur_assign && (
|
||||
<Card style={{ marginBottom: spacing.m }}>
|
||||
<TouchableOpacity
|
||||
onPress={() =>
|
||||
navigation.navigate("OrderDetail", { orderId: item.id })
|
||||
}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.orderId}>#{item.id}</Text>
|
||||
<StatusBadge status={item.status} />
|
||||
</View>
|
||||
<Text style={styles.cardText}>Client: {item.username}</Text>
|
||||
<Text style={styles.cardText}>Adresse: {item.adresse}</Text>
|
||||
<Text style={styles.cardText}>
|
||||
Livreur: {item.livreur_assign}
|
||||
Total: {item.total_prix.toFixed(2)} €
|
||||
</Text>
|
||||
)}
|
||||
<Text style={styles.cardDate}>
|
||||
{new Date(item.created_at).toLocaleString("fr-FR")}
|
||||
</Text>
|
||||
{item.livreur_assign && (
|
||||
<Text style={styles.cardText}>
|
||||
Livreur: {item.livreur_assign}
|
||||
</Text>
|
||||
)}
|
||||
<Text style={styles.cardDate}>
|
||||
{new Date(item.created_at).toLocaleString("fr-FR")}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<View style={styles.actions}>
|
||||
<Button
|
||||
title="Voir items"
|
||||
onPress={() => openItems(item.id)}
|
||||
size="sm"
|
||||
variant="primary"
|
||||
/>
|
||||
{!["approved", "cancelled"].includes(item.status) && (
|
||||
<Button
|
||||
title="Le client est là"
|
||||
onPress={() => handleNotifyClient(item.id)}
|
||||
size="sm"
|
||||
variant="warning"
|
||||
/>
|
||||
)}
|
||||
{!["approved", "cancelled"].includes(item.status) && (
|
||||
<Button
|
||||
title="Assigner"
|
||||
onPress={() => openAssignModal(item.id)}
|
||||
size="sm"
|
||||
variant="primary"
|
||||
variant="secondary"
|
||||
/>
|
||||
)}
|
||||
{item.status === "assigned" && (
|
||||
@@ -228,8 +370,22 @@ export default function OrdersScreen() {
|
||||
variant="secondary"
|
||||
/>
|
||||
)}
|
||||
{item.status === "livre" && (
|
||||
<Button
|
||||
title="Confirmer"
|
||||
onPress={() => handleConfirmReception(item.id)}
|
||||
size="sm"
|
||||
variant="success"
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
title="Supprimer"
|
||||
onPress={() => handleDelete(item.id)}
|
||||
size="sm"
|
||||
variant="danger"
|
||||
/>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</Card>
|
||||
);
|
||||
|
||||
if (loading) return <LoadingSpinner message="Chargement commandes..." />;
|
||||
@@ -277,12 +433,81 @@ export default function OrdersScreen() {
|
||||
<Text style={styles.empty}>Aucune commande</Text>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Modal items */}
|
||||
<Modal
|
||||
visible={itemsModal.visible}
|
||||
onClose={closeItemsModal}
|
||||
title={`Commande #${itemsModal.commandId}`}
|
||||
icon="receipt-outline"
|
||||
>
|
||||
<ScrollView showsVerticalScrollIndicator={false} bounces={false}>
|
||||
{(itemsModal.commandInfo || itemsModal.clientInfo) && (
|
||||
<View style={styles.modalSummary}>
|
||||
{itemsModal.clientInfo?.username && (
|
||||
<View style={styles.modalSummaryRow}>
|
||||
<Ionicons
|
||||
name="person-outline"
|
||||
size={14}
|
||||
color={colors.textMuted}
|
||||
/>
|
||||
<Text style={styles.modalSummaryText}>
|
||||
{itemsModal.clientInfo.prenom}{" "}
|
||||
{itemsModal.clientInfo.nom} ·{" "}
|
||||
{itemsModal.clientInfo.username}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
{(itemsModal.commandInfo?.address ||
|
||||
itemsModal.commandInfo?.adresse) && (
|
||||
<View style={styles.modalSummaryRow}>
|
||||
<Ionicons
|
||||
name="location-outline"
|
||||
size={14}
|
||||
color={colors.textMuted}
|
||||
/>
|
||||
<Text style={styles.modalSummaryText}>
|
||||
{itemsModal.commandInfo.address ||
|
||||
itemsModal.commandInfo.adresse}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
{itemsModal.commandInfo?.total_prix != null && (
|
||||
<Text style={styles.modalTotal}>
|
||||
{Number(
|
||||
itemsModal.commandInfo.total_prix,
|
||||
).toFixed(2)}{" "}
|
||||
€
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
{itemsModal.items.map((item: any) => (
|
||||
<View key={item.id} style={styles.itemCard}>
|
||||
<View style={styles.itemCardBody}>
|
||||
<Text style={styles.itemName}>
|
||||
{item.produit ?? item.product_name}
|
||||
</Text>
|
||||
<Text style={styles.itemMeta}>
|
||||
Qté: {item.quantite ?? item.quantity} ·{" "}
|
||||
{(item.prix ?? item.price)?.toFixed(2)} €
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
{itemsModal.items.length === 0 && (
|
||||
<Text style={styles.empty}>Aucun item</Text>
|
||||
)}
|
||||
</ScrollView>
|
||||
</Modal>
|
||||
|
||||
{/* Modal assignation */}
|
||||
<Modal
|
||||
visible={assignModal.visible}
|
||||
onClose={() =>
|
||||
setAssignModal({ visible: false, commandId: null })
|
||||
}
|
||||
title="Assigner un livreur"
|
||||
title={`Assigner commande #${assignModal.commandId}`}
|
||||
icon="bicycle-outline"
|
||||
>
|
||||
{livreurs.map((l) => (
|
||||
@@ -303,12 +528,16 @@ export default function OrdersScreen() {
|
||||
<Text style={styles.empty}>Aucun livreur disponible</Text>
|
||||
)}
|
||||
</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