feat: add CSV export for approved orders && add non-delivery reason modal for livreurs

This commit is contained in:
2026-05-03 13:23:20 +02:00
parent a273484659
commit 7dc2686f6c
8 changed files with 233 additions and 14 deletions
@@ -39,7 +39,10 @@ import {
generateLivreurLinkToken,
unlinkLivreurTelegram,
getDeliveryNavLink,
reportDeliveryIssue,
ISSUE_LABELS,
} from "../../api/api_delivery";
import type { IssueType } from "../../api/api_delivery";
import { geocodeAddress, calculateRoute } from "../../api/tomtom";
import type { RouteInfo } from "../../api/tomtom";
import type { DeliveryStatus, DeliveryItem, QueueInfo } from "../../api/types";
@@ -118,8 +121,9 @@ export default function DashboardScreen() {
const [cancelModal, setCancelModal] = useState<{
visible: boolean;
deliveryId: number | null;
reason: string;
}>({ visible: false, deliveryId: null, reason: "" });
issueType: IssueType | null;
description: string;
}>({ visible: false, deliveryId: null, issueType: null, description: "" });
const STATUS_COLORS: Record<string, string> = useMemo(
() => ({
@@ -502,17 +506,25 @@ export default function DashboardScreen() {
};
const handleCancelDelivery = async () => {
if (!cancelModal.deliveryId) return;
if (!cancelModal.deliveryId || !cancelModal.issueType) return;
const lat = lastCoords?.lat || 0;
const lng = lastCoords?.lng || 0;
const issueLabel = ISSUE_LABELS[cancelModal.issueType];
const res = await updateDeliveryStatus(
cancelModal.deliveryId,
"cancelled",
lat,
lng,
cancelModal.reason || "Client absent",
cancelModal.description || issueLabel,
);
setCancelModal({ visible: false, deliveryId: null, reason: "" });
if (res.success) {
await reportDeliveryIssue(
cancelModal.deliveryId,
cancelModal.issueType,
cancelModal.description,
);
}
setCancelModal({ visible: false, deliveryId: null, issueType: null, description: "" });
if (res.success) {
showSuccess("Succès", "Livraison annulée");
loadData();
@@ -736,7 +748,8 @@ export default function DashboardScreen() {
setCancelModal({
visible: true,
deliveryId: item.id,
reason: "",
issueType: null,
description: "",
})
}
style={{ flex: 1, backgroundColor: colors.danger }}
@@ -1860,24 +1873,51 @@ export default function DashboardScreen() {
<DetailsModal
visible={cancelModal.visible}
onClose={() =>
setCancelModal({ visible: false, deliveryId: null, reason: "" })
setCancelModal({ visible: false, deliveryId: null, issueType: null, description: "" })
}
title="Annuler la livraison"
title="Motif de non-livraison"
icon="close-circle-outline"
>
<Text style={[styles.cancelInput, { color: colors.textSecondary, fontSize: 13, marginBottom: spacing.s, backgroundColor: "transparent", borderWidth: 0, padding: 0 }]}>
Sélectionnez un motif
</Text>
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: spacing.s, marginBottom: spacing.m }}>
{(Object.keys(ISSUE_LABELS) as IssueType[]).map((type) => {
const selected = cancelModal.issueType === type;
return (
<TouchableOpacity
key={type}
onPress={() => setCancelModal((prev) => ({ ...prev, issueType: type }))}
style={{
paddingHorizontal: spacing.m,
paddingVertical: spacing.s,
borderRadius: 20,
borderWidth: 1,
borderColor: selected ? colors.danger : colors.border,
backgroundColor: selected ? colors.danger + "22" : colors.bgCard,
}}
>
<Text style={{ color: selected ? colors.danger : colors.textSecondary, fontSize: 13 }}>
{ISSUE_LABELS[type]}
</Text>
</TouchableOpacity>
);
})}
</View>
<TextInput
style={styles.cancelInput}
placeholder="Motif (ex: client absent)..."
placeholder="Précisions (facultatif)..."
placeholderTextColor={colors.textMuted}
value={cancelModal.reason}
value={cancelModal.description}
onChangeText={(t) =>
setCancelModal((prev) => ({ ...prev, reason: t }))
setCancelModal((prev) => ({ ...prev, description: t }))
}
multiline
/>
<TouchableOpacity
style={styles.cancelConfirmBtn}
style={[styles.cancelConfirmBtn, !cancelModal.issueType && { opacity: 0.4 }]}
onPress={handleCancelDelivery}
disabled={!cancelModal.issueType}
>
<Text style={styles.cancelConfirmText}>Confirmer l'annulation</Text>
</TouchableOpacity>