From e09bbb05b9904788d0bfdd70552c7ca6d1084f72 Mon Sep 17 00:00:00 2001 From: Xor290 Date: Fri, 15 May 2026 19:13:13 +0200 Subject: [PATCH] feat: chrono and add penality --- backend/gestion/handlers/deleviry.go | 18 +- .../src/screens/delivery/DashboardScreen.tsx | 370 +++++++++++++++--- 2 files changed, 324 insertions(+), 64 deletions(-) diff --git a/backend/gestion/handlers/deleviry.go b/backend/gestion/handlers/deleviry.go index 0e0091d2..ad0508b5 100644 --- a/backend/gestion/handlers/deleviry.go +++ b/backend/gestion/handlers/deleviry.go @@ -33,7 +33,7 @@ func GetMyDeliveries(c *gin.Context) { commands, err := database.GetDeliveryPersonCommands(usernameStr, status) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur récupération", + "error": "Erreur récupération", }) return } @@ -188,7 +188,7 @@ func UpdateDeliveryStatus(c *gin.Context) { if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ - "error": "Données invalides", + "error": "Données invalides", }) return } @@ -258,7 +258,7 @@ func UpdateDeliveryStatus(c *gin.Context) { // Mettre à jour le statut if err := database.UpdateCommandStatus(commandID, req.Status); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Erreur mise à jour", + "error": "Erreur mise à jour", }) return } @@ -269,6 +269,18 @@ func UpdateDeliveryStatus(c *gin.Context) { cancelMsg = "Annulé par le livreur" } 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 diff --git a/frontend-admin/src/screens/delivery/DashboardScreen.tsx b/frontend-admin/src/screens/delivery/DashboardScreen.tsx index 2c3d96c8..368f0ce8 100644 --- a/frontend-admin/src/screens/delivery/DashboardScreen.tsx +++ b/frontend-admin/src/screens/delivery/DashboardScreen.tsx @@ -115,9 +115,9 @@ export default function DashboardScreen() { useState(null); // Telegram - const [tgLinked, setTgLinked] = useState(false); - const [tgEnabled, setTgEnabled] = useState(false); - const [tgLoading, setTgLoading] = useState(false); + const [tgLinked, setTgLinked] = useState(false); + const [tgEnabled, setTgEnabled] = useState(false); + const [tgLoading, setTgLoading] = useState(false); const [cancelModal, setCancelModal] = useState<{ visible: boolean; deliveryId: number | null; @@ -125,6 +125,12 @@ export default function DashboardScreen() { description: string; }>({ visible: false, deliveryId: null, issueType: null, description: "" }); + const ABSENT_TIMEOUT_SECS = 300; // 5 minutes + const arrivedAtRef = useRef>({}); + const [elapsedSeconds, setElapsedSeconds] = useState< + Record + >({}); + const STATUS_COLORS: Record = useMemo( () => ({ available: colors.success, @@ -276,11 +282,21 @@ export default function DashboardScreen() { 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 = - enriched.find( - (d) => - d.status === "en_route", - ) || enriched.find((d) => d.status === "assigned"); + enriched.find((d) => d.status === "en_route") || + enriched.find((d) => d.status === "assigned"); if (activeDelivery && activeDelivery.adresse) { calcRoute(activeDelivery.adresse); } @@ -292,7 +308,10 @@ export default function DashboardScreen() { useEffect(() => { loadData(); - getLivreurTelegramStatus().then((s) => { setTgLinked(s.linked); setTgEnabled(s.enabled); }); + getLivreurTelegramStatus().then((s) => { + setTgLinked(s.linked); + setTgEnabled(s.enabled); + }); }, [loadData]); useEffect(() => { @@ -302,6 +321,18 @@ export default function DashboardScreen() { return () => clearInterval(interval); }, [loadData]); + useEffect(() => { + const interval = setInterval(() => { + const now = Date.now(); + const updated: Record = {}; + 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 useEffect(() => { if (lastCoords && pendingRouteAddress.current) { @@ -524,7 +555,12 @@ export default function DashboardScreen() { cancelModal.description, ); } - setCancelModal({ visible: false, deliveryId: null, issueType: null, description: "" }); + setCancelModal({ + visible: false, + deliveryId: null, + issueType: null, + description: "", + }); if (res.success) { showSuccess("Succès", "Livraison annulée"); 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 res = await getDeliveryNavLink(deliveryId); - const link = res.success && res.waze_app - ? res.waze_app - : `waze://?q=${encodeURIComponent(address)}&navigate=yes`; + const link = + res.success && res.waze_app + ? res.waze_app + : `waze://?q=${encodeURIComponent(address)}&navigate=yes`; Linking.openURL(link); }; @@ -661,10 +722,20 @@ export default function DashboardScreen() { {(item.referral_used ?? 0) > 0 && ( - + Parrainage client - + -{(item.referral_used ?? 0).toFixed(2)}€ @@ -678,8 +749,14 @@ export default function DashboardScreen() { {item.total_prix ?? 0}€ {(item.referral_used ?? 0) > 0 && ( - - Parrainage: -{(item.referral_used ?? 0).toFixed(2)}€ + + Parrainage: - + {(item.referral_used ?? 0).toFixed(2)}€ )} @@ -735,27 +812,47 @@ export default function DashboardScreen() { }} /> )} - {item.status === "arrived" && ( - -