feat: chrono and add penality
This commit is contained in:
@@ -33,7 +33,7 @@ func GetMyDeliveries(c *gin.Context) {
|
|||||||
commands, err := database.GetDeliveryPersonCommands(usernameStr, status)
|
commands, err := database.GetDeliveryPersonCommands(usernameStr, status)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur récupération",
|
"error": "Erreur récupération",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -188,7 +188,7 @@ func UpdateDeliveryStatus(c *gin.Context) {
|
|||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "Données invalides",
|
"error": "Données invalides",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -258,7 +258,7 @@ func UpdateDeliveryStatus(c *gin.Context) {
|
|||||||
// Mettre à jour le statut
|
// Mettre à jour le statut
|
||||||
if err := database.UpdateCommandStatus(commandID, req.Status); err != nil {
|
if err := database.UpdateCommandStatus(commandID, req.Status); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Erreur mise à jour",
|
"error": "Erreur mise à jour",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -269,6 +269,18 @@ func UpdateDeliveryStatus(c *gin.Context) {
|
|||||||
cancelMsg = "Annulé par le livreur"
|
cancelMsg = "Annulé par le livreur"
|
||||||
}
|
}
|
||||||
database.SetCommandCancelReason(commandID, fmt.Sprintf("[Livreur: %s] %s", usernameStr, cancelMsg))
|
database.SetCommandCancelReason(commandID, fmt.Sprintf("[Livreur: %s] %s", usernameStr, cancelMsg))
|
||||||
|
|
||||||
|
prevStatus, _ := command["status"].(string)
|
||||||
|
if prevStatus == "arrived" || prevStatus == "livre" {
|
||||||
|
clientUsername, _ := command["username"].(string)
|
||||||
|
if clientUsername != "" {
|
||||||
|
if penalty, err := database.ApplyCancellationPenalty(clientUsername); err == nil {
|
||||||
|
log.Printf("⚠️ [CANCEL_LIVREUR] Amende %d appliquée à %s (client absent)", penalty, clientUsername)
|
||||||
|
} else {
|
||||||
|
log.Printf("⚠️ [CANCEL_LIVREUR] Erreur application amende pour %s: %v", clientUsername, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ SI PASSAGE EN "EN_ROUTE" → CALCULER ET DÉFINIR L'ETA
|
// ✅ SI PASSAGE EN "EN_ROUTE" → CALCULER ET DÉFINIR L'ETA
|
||||||
|
|||||||
@@ -115,9 +115,9 @@ export default function DashboardScreen() {
|
|||||||
useState<EnrichedDelivery | null>(null);
|
useState<EnrichedDelivery | null>(null);
|
||||||
|
|
||||||
// Telegram
|
// Telegram
|
||||||
const [tgLinked, setTgLinked] = useState(false);
|
const [tgLinked, setTgLinked] = useState(false);
|
||||||
const [tgEnabled, setTgEnabled] = useState(false);
|
const [tgEnabled, setTgEnabled] = useState(false);
|
||||||
const [tgLoading, setTgLoading] = useState(false);
|
const [tgLoading, setTgLoading] = useState(false);
|
||||||
const [cancelModal, setCancelModal] = useState<{
|
const [cancelModal, setCancelModal] = useState<{
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
deliveryId: number | null;
|
deliveryId: number | null;
|
||||||
@@ -125,6 +125,12 @@ export default function DashboardScreen() {
|
|||||||
description: string;
|
description: string;
|
||||||
}>({ visible: false, deliveryId: null, issueType: null, description: "" });
|
}>({ visible: false, deliveryId: null, issueType: null, description: "" });
|
||||||
|
|
||||||
|
const ABSENT_TIMEOUT_SECS = 300; // 5 minutes
|
||||||
|
const arrivedAtRef = useRef<Record<number, number>>({});
|
||||||
|
const [elapsedSeconds, setElapsedSeconds] = useState<
|
||||||
|
Record<number, number>
|
||||||
|
>({});
|
||||||
|
|
||||||
const STATUS_COLORS: Record<string, string> = useMemo(
|
const STATUS_COLORS: Record<string, string> = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
available: colors.success,
|
available: colors.success,
|
||||||
@@ -276,11 +282,21 @@ export default function DashboardScreen() {
|
|||||||
|
|
||||||
setDeliveries(enriched);
|
setDeliveries(enriched);
|
||||||
|
|
||||||
|
// Enregistrer le timestamp d'arrivée pour les livraisons "arrived"
|
||||||
|
for (const d of enriched) {
|
||||||
|
if (
|
||||||
|
d.status === "arrived" ||
|
||||||
|
(d.status === "livre" && !arrivedAtRef.current[d.id])
|
||||||
|
) {
|
||||||
|
arrivedAtRef.current[d.id] = Date.now();
|
||||||
|
} else if (d.status !== "arrived" && d.status !== "livre") {
|
||||||
|
delete arrivedAtRef.current[d.id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const activeDelivery =
|
const activeDelivery =
|
||||||
enriched.find(
|
enriched.find((d) => d.status === "en_route") ||
|
||||||
(d) =>
|
enriched.find((d) => d.status === "assigned");
|
||||||
d.status === "en_route",
|
|
||||||
) || enriched.find((d) => d.status === "assigned");
|
|
||||||
if (activeDelivery && activeDelivery.adresse) {
|
if (activeDelivery && activeDelivery.adresse) {
|
||||||
calcRoute(activeDelivery.adresse);
|
calcRoute(activeDelivery.adresse);
|
||||||
}
|
}
|
||||||
@@ -292,7 +308,10 @@ export default function DashboardScreen() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadData();
|
loadData();
|
||||||
getLivreurTelegramStatus().then((s) => { setTgLinked(s.linked); setTgEnabled(s.enabled); });
|
getLivreurTelegramStatus().then((s) => {
|
||||||
|
setTgLinked(s.linked);
|
||||||
|
setTgEnabled(s.enabled);
|
||||||
|
});
|
||||||
}, [loadData]);
|
}, [loadData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -302,6 +321,18 @@ export default function DashboardScreen() {
|
|||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [loadData]);
|
}, [loadData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
const now = Date.now();
|
||||||
|
const updated: Record<number, number> = {};
|
||||||
|
for (const [id, ts] of Object.entries(arrivedAtRef.current)) {
|
||||||
|
updated[Number(id)] = Math.floor((now - ts) / 1000);
|
||||||
|
}
|
||||||
|
setElapsedSeconds(updated);
|
||||||
|
}, 1000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Quand GPS devient disponible, rejouer la route en attente
|
// Quand GPS devient disponible, rejouer la route en attente
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lastCoords && pendingRouteAddress.current) {
|
if (lastCoords && pendingRouteAddress.current) {
|
||||||
@@ -524,7 +555,12 @@ export default function DashboardScreen() {
|
|||||||
cancelModal.description,
|
cancelModal.description,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
setCancelModal({ visible: false, deliveryId: null, issueType: null, description: "" });
|
setCancelModal({
|
||||||
|
visible: false,
|
||||||
|
deliveryId: null,
|
||||||
|
issueType: null,
|
||||||
|
description: "",
|
||||||
|
});
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
showSuccess("Succès", "Livraison annulée");
|
showSuccess("Succès", "Livraison annulée");
|
||||||
loadData();
|
loadData();
|
||||||
@@ -533,11 +569,36 @@ export default function DashboardScreen() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleClientAbsent = async (deliveryId: number) => {
|
||||||
|
const lat = lastCoords?.lat || 0;
|
||||||
|
const lng = lastCoords?.lng || 0;
|
||||||
|
const res = await updateDeliveryStatus(
|
||||||
|
deliveryId,
|
||||||
|
"cancelled",
|
||||||
|
lat,
|
||||||
|
lng,
|
||||||
|
"Client absent",
|
||||||
|
);
|
||||||
|
if (res.success) {
|
||||||
|
await reportDeliveryIssue(
|
||||||
|
deliveryId,
|
||||||
|
"client_absent",
|
||||||
|
"Client non présent après attente",
|
||||||
|
);
|
||||||
|
delete arrivedAtRef.current[deliveryId];
|
||||||
|
showSuccess("Commande annulée", "Une amende a été appliquée au client");
|
||||||
|
loadData();
|
||||||
|
} else {
|
||||||
|
showError("Erreur", res.error || "Erreur");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const openNavigation = async (deliveryId: number, address: string) => {
|
const openNavigation = async (deliveryId: number, address: string) => {
|
||||||
const res = await getDeliveryNavLink(deliveryId);
|
const res = await getDeliveryNavLink(deliveryId);
|
||||||
const link = res.success && res.waze_app
|
const link =
|
||||||
? res.waze_app
|
res.success && res.waze_app
|
||||||
: `waze://?q=${encodeURIComponent(address)}&navigate=yes`;
|
? res.waze_app
|
||||||
|
: `waze://?q=${encodeURIComponent(address)}&navigate=yes`;
|
||||||
Linking.openURL(link);
|
Linking.openURL(link);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -661,10 +722,20 @@ export default function DashboardScreen() {
|
|||||||
</View>
|
</View>
|
||||||
{(item.referral_used ?? 0) > 0 && (
|
{(item.referral_used ?? 0) > 0 && (
|
||||||
<View style={[styles.totalRow, { marginTop: 2 }]}>
|
<View style={[styles.totalRow, { marginTop: 2 }]}>
|
||||||
<Text style={[styles.totalLabel, { color: colors.success }]}>
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.totalLabel,
|
||||||
|
{ color: colors.success },
|
||||||
|
]}
|
||||||
|
>
|
||||||
Parrainage client
|
Parrainage client
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[styles.totalValue, { color: colors.success }]}>
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.totalValue,
|
||||||
|
{ color: colors.success },
|
||||||
|
]}
|
||||||
|
>
|
||||||
-{(item.referral_used ?? 0).toFixed(2)}€
|
-{(item.referral_used ?? 0).toFixed(2)}€
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -678,8 +749,14 @@ export default function DashboardScreen() {
|
|||||||
{item.total_prix ?? 0}€
|
{item.total_prix ?? 0}€
|
||||||
</Text>
|
</Text>
|
||||||
{(item.referral_used ?? 0) > 0 && (
|
{(item.referral_used ?? 0) > 0 && (
|
||||||
<Text style={[styles.priceOnly, { color: colors.success, marginTop: 2 }]}>
|
<Text
|
||||||
Parrainage: -{(item.referral_used ?? 0).toFixed(2)}€
|
style={[
|
||||||
|
styles.priceOnly,
|
||||||
|
{ color: colors.success, marginTop: 2 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
Parrainage: -
|
||||||
|
{(item.referral_used ?? 0).toFixed(2)}€
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -735,27 +812,47 @@ export default function DashboardScreen() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{item.status === "arrived" && (
|
{item.status === "arrived" && (() => {
|
||||||
<View style={{ flexDirection: "row", gap: spacing.s, marginTop: spacing.s }}>
|
const elapsed = elapsedSeconds[item.id] || 0;
|
||||||
<Button
|
const remaining = Math.max(0, ABSENT_TIMEOUT_SECS - elapsed);
|
||||||
title="Terminer"
|
const showAbsent = elapsed >= ABSENT_TIMEOUT_SECS;
|
||||||
onPress={() => handleCompleteDelivery(item.id)}
|
const mm = String(Math.floor(remaining / 60)).padStart(2, "0");
|
||||||
style={{ flex: 1, backgroundColor: colors.accent }}
|
const ss = String(remaining % 60).padStart(2, "0");
|
||||||
/>
|
return (
|
||||||
<Button
|
<View style={{ marginTop: spacing.s, gap: spacing.s }}>
|
||||||
title="Annuler"
|
<View style={{ flexDirection: "row", gap: spacing.s }}>
|
||||||
onPress={() =>
|
<Button
|
||||||
setCancelModal({
|
title="Terminer"
|
||||||
visible: true,
|
onPress={() => handleCompleteDelivery(item.id)}
|
||||||
deliveryId: item.id,
|
style={{ flex: 1, backgroundColor: colors.accent }}
|
||||||
issueType: null,
|
/>
|
||||||
description: "",
|
<Button
|
||||||
})
|
title="Annuler"
|
||||||
}
|
onPress={() =>
|
||||||
style={{ flex: 1, backgroundColor: colors.danger }}
|
setCancelModal({
|
||||||
/>
|
visible: true,
|
||||||
|
deliveryId: item.id,
|
||||||
|
issueType: null,
|
||||||
|
description: "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
style={{ flex: 1, backgroundColor: colors.danger }}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
{showAbsent ? (
|
||||||
|
<Button
|
||||||
|
title="Client absent"
|
||||||
|
onPress={() => handleClientAbsent(item.id)}
|
||||||
|
style={{ backgroundColor: colors.danger }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Text style={{ color: colors.textMuted, fontSize: fontSize.sm, textAlign: "center" }}>
|
||||||
|
Client absent dans {mm}:{ss}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
)}
|
);
|
||||||
|
})()}
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -777,7 +874,10 @@ export default function DashboardScreen() {
|
|||||||
const handleUnlinkTelegram = async () => {
|
const handleUnlinkTelegram = async () => {
|
||||||
await unlinkLivreurTelegram();
|
await unlinkLivreurTelegram();
|
||||||
setTgLinked(false);
|
setTgLinked(false);
|
||||||
showSuccess("Telegram délié", "Vous ne recevrez plus de notifications Telegram.");
|
showSuccess(
|
||||||
|
"Telegram délié",
|
||||||
|
"Vous ne recevrez plus de notifications Telegram.",
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderHeader = () => (
|
const renderHeader = () => (
|
||||||
@@ -904,26 +1004,119 @@ export default function DashboardScreen() {
|
|||||||
|
|
||||||
{/* Carte Telegram */}
|
{/* Carte Telegram */}
|
||||||
{tgEnabled && (
|
{tgEnabled && (
|
||||||
<View style={{ marginHorizontal: spacing.l, marginBottom: spacing.m, backgroundColor: colors.bgCard, borderRadius: borderRadius.md, padding: spacing.l, borderWidth: 1, borderColor: colors.borderLight }}>
|
<View
|
||||||
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.s, marginBottom: spacing.s }}>
|
style={{
|
||||||
<Ionicons name="paper-plane-outline" size={18} color="#2AABEE" />
|
marginHorizontal: spacing.l,
|
||||||
<Text style={{ color: colors.textPrimary, fontSize: fontSize.md, fontWeight: "600" }}>Notifications Telegram</Text>
|
marginBottom: spacing.m,
|
||||||
|
backgroundColor: colors.bgCard,
|
||||||
|
borderRadius: borderRadius.md,
|
||||||
|
padding: spacing.l,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.borderLight,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: spacing.s,
|
||||||
|
marginBottom: spacing.s,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Ionicons
|
||||||
|
name="paper-plane-outline"
|
||||||
|
size={18}
|
||||||
|
color="#2AABEE"
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
color: colors.textPrimary,
|
||||||
|
fontSize: fontSize.md,
|
||||||
|
fontWeight: "600",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Notifications Telegram
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
{tgLinked ? (
|
{tgLinked ? (
|
||||||
<View>
|
<View>
|
||||||
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.s, marginBottom: spacing.s }}>
|
<View
|
||||||
<Ionicons name="checkmark-circle" size={14} color={colors.success} />
|
style={{
|
||||||
<Text style={{ color: colors.success, fontSize: fontSize.sm }}>Compte lié</Text>
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: spacing.s,
|
||||||
|
marginBottom: spacing.s,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Ionicons
|
||||||
|
name="checkmark-circle"
|
||||||
|
size={14}
|
||||||
|
color={colors.success}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
color: colors.success,
|
||||||
|
fontSize: fontSize.sm,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Compte lié
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<TouchableOpacity onPress={handleUnlinkTelegram} style={{ flexDirection: "row", alignItems: "center", gap: spacing.s, padding: spacing.s, borderRadius: borderRadius.sm, borderWidth: 1, borderColor: colors.danger + "66" }}>
|
<TouchableOpacity
|
||||||
<Ionicons name="unlink-outline" size={14} color={colors.danger} />
|
onPress={handleUnlinkTelegram}
|
||||||
<Text style={{ color: colors.danger, fontSize: fontSize.sm }}>Délier</Text>
|
style={{
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: spacing.s,
|
||||||
|
padding: spacing.s,
|
||||||
|
borderRadius: borderRadius.sm,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.danger + "66",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Ionicons
|
||||||
|
name="unlink-outline"
|
||||||
|
size={14}
|
||||||
|
color={colors.danger}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
color: colors.danger,
|
||||||
|
fontSize: fontSize.sm,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Délier
|
||||||
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<TouchableOpacity onPress={handleLinkTelegram} disabled={tgLoading} style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", gap: spacing.s, padding: spacing.m, borderRadius: borderRadius.sm, backgroundColor: "#2AABEE" }}>
|
<TouchableOpacity
|
||||||
<Ionicons name="paper-plane-outline" size={14} color="#fff" />
|
onPress={handleLinkTelegram}
|
||||||
<Text style={{ color: "#fff", fontSize: fontSize.sm, fontWeight: "600" }}>{tgLoading ? "Génération..." : "Lier Telegram"}</Text>
|
disabled={tgLoading}
|
||||||
|
style={{
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
gap: spacing.s,
|
||||||
|
padding: spacing.m,
|
||||||
|
borderRadius: borderRadius.sm,
|
||||||
|
backgroundColor: "#2AABEE",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Ionicons
|
||||||
|
name="paper-plane-outline"
|
||||||
|
size={14}
|
||||||
|
color="#fff"
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
color: "#fff",
|
||||||
|
fontSize: fontSize.sm,
|
||||||
|
fontWeight: "600",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tgLoading ? "Génération..." : "Lier Telegram"}
|
||||||
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
@@ -1797,10 +1990,20 @@ export default function DashboardScreen() {
|
|||||||
</View>
|
</View>
|
||||||
{(detailsDelivery?.referral_used ?? 0) > 0 && (
|
{(detailsDelivery?.referral_used ?? 0) > 0 && (
|
||||||
<View style={[styles.detailTotalRow, { marginTop: 4 }]}>
|
<View style={[styles.detailTotalRow, { marginTop: 4 }]}>
|
||||||
<Text style={[styles.detailTotalLabel, { color: colors.success }]}>
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.detailTotalLabel,
|
||||||
|
{ color: colors.success },
|
||||||
|
]}
|
||||||
|
>
|
||||||
Parrainage client
|
Parrainage client
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[styles.detailTotalValue, { color: colors.success }]}>
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.detailTotalValue,
|
||||||
|
{ color: colors.success },
|
||||||
|
]}
|
||||||
|
>
|
||||||
-{detailsDelivery?.referral_used?.toFixed(2)}€
|
-{detailsDelivery?.referral_used?.toFixed(2)}€
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -1873,31 +2076,71 @@ export default function DashboardScreen() {
|
|||||||
<DetailsModal
|
<DetailsModal
|
||||||
visible={cancelModal.visible}
|
visible={cancelModal.visible}
|
||||||
onClose={() =>
|
onClose={() =>
|
||||||
setCancelModal({ visible: false, deliveryId: null, issueType: null, description: "" })
|
setCancelModal({
|
||||||
|
visible: false,
|
||||||
|
deliveryId: null,
|
||||||
|
issueType: null,
|
||||||
|
description: "",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
title="Motif de non-livraison"
|
title="Motif de non-livraison"
|
||||||
icon="close-circle-outline"
|
icon="close-circle-outline"
|
||||||
>
|
>
|
||||||
<Text style={[styles.cancelInput, { color: colors.textSecondary, fontSize: 13, marginBottom: spacing.s, backgroundColor: "transparent", borderWidth: 0, padding: 0 }]}>
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.cancelInput,
|
||||||
|
{
|
||||||
|
color: colors.textSecondary,
|
||||||
|
fontSize: 13,
|
||||||
|
marginBottom: spacing.s,
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
borderWidth: 0,
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
Sélectionnez un motif
|
Sélectionnez un motif
|
||||||
</Text>
|
</Text>
|
||||||
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: spacing.s, marginBottom: spacing.m }}>
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: "row",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
gap: spacing.s,
|
||||||
|
marginBottom: spacing.m,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{(Object.keys(ISSUE_LABELS) as IssueType[]).map((type) => {
|
{(Object.keys(ISSUE_LABELS) as IssueType[]).map((type) => {
|
||||||
const selected = cancelModal.issueType === type;
|
const selected = cancelModal.issueType === type;
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
key={type}
|
key={type}
|
||||||
onPress={() => setCancelModal((prev) => ({ ...prev, issueType: type }))}
|
onPress={() =>
|
||||||
|
setCancelModal((prev) => ({
|
||||||
|
...prev,
|
||||||
|
issueType: type,
|
||||||
|
}))
|
||||||
|
}
|
||||||
style={{
|
style={{
|
||||||
paddingHorizontal: spacing.m,
|
paddingHorizontal: spacing.m,
|
||||||
paddingVertical: spacing.s,
|
paddingVertical: spacing.s,
|
||||||
borderRadius: 20,
|
borderRadius: 20,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: selected ? colors.danger : colors.border,
|
borderColor: selected
|
||||||
backgroundColor: selected ? colors.danger + "22" : colors.bgCard,
|
? colors.danger
|
||||||
|
: colors.border,
|
||||||
|
backgroundColor: selected
|
||||||
|
? colors.danger + "22"
|
||||||
|
: colors.bgCard,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text style={{ color: selected ? colors.danger : colors.textSecondary, fontSize: 13 }}>
|
<Text
|
||||||
|
style={{
|
||||||
|
color: selected
|
||||||
|
? colors.danger
|
||||||
|
: colors.textSecondary,
|
||||||
|
fontSize: 13,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{ISSUE_LABELS[type]}
|
{ISSUE_LABELS[type]}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
@@ -1915,11 +2158,16 @@ export default function DashboardScreen() {
|
|||||||
multiline
|
multiline
|
||||||
/>
|
/>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.cancelConfirmBtn, !cancelModal.issueType && { opacity: 0.4 }]}
|
style={[
|
||||||
|
styles.cancelConfirmBtn,
|
||||||
|
!cancelModal.issueType && { opacity: 0.4 },
|
||||||
|
]}
|
||||||
onPress={handleCancelDelivery}
|
onPress={handleCancelDelivery}
|
||||||
disabled={!cancelModal.issueType}
|
disabled={!cancelModal.issueType}
|
||||||
>
|
>
|
||||||
<Text style={styles.cancelConfirmText}>Confirmer l'annulation</Text>
|
<Text style={styles.cancelConfirmText}>
|
||||||
|
Confirmer l'annulation
|
||||||
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</DetailsModal>
|
</DetailsModal>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user