diff --git a/backend/gestion/db/db_clients.go b/backend/gestion/db/db_clients.go index 7bfeec07..dabef00e 100644 --- a/backend/gestion/db/db_clients.go +++ b/backend/gestion/db/db_clients.go @@ -446,6 +446,35 @@ func (d *Database) CheckClientCanOrder(username string) (bool, float64, error) { return true, 0, nil } +func (d *Database) ResetClientPoint(username string, resetCancellationsPoint bool) error { + var query string + if resetCancellationsPoint { + query = `UPDATE clients SET point = 0, point_zipette = 0, updated_at = CURRENT_TIMESTAMP WHERE username = $1` + } else { + query = `UPDATE clients SET point = 0, updated_at = CURRENT_TIMESTAMP WHERE username = $1` + } + + result, err := d.Exec(query, username) + if err != nil { + log.Printf("❌ [ResetClientPointAdmin] Erreur UPDATE: %v", err) + return fmt.Errorf("erreur reset points: %w", err) + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return fmt.Errorf("erreur vérification: %w", err) + } + if rowsAffected == 0 { + return fmt.Errorf("client non trouvé") + } + + // Invalider le cache Redis du client + cacheKey := fmt.Sprintf("client:%s", username) + Redis.Del(RedisCtx, cacheKey) + + return nil +} + func (d *Database) ResetClientPenalties(username string, resetCancellationsCount bool) error { log.Printf("🔄 [ResetClientPenalties] Reset pour %s (reset_count=%v)", username, resetCancellationsCount) @@ -477,8 +506,6 @@ func (d *Database) ResetClientPenalties(username string, resetCancellationsCount return fmt.Errorf("client non trouvé") } - log.Printf("✅ [ResetClientPenalties] Reset effectué pour %s", username) - // Invalider le cache Redis du client cacheKey := fmt.Sprintf("client:%s", username) Redis.Del(RedisCtx, cacheKey) diff --git a/backend/gestion/handlers/redis_services.go b/backend/gestion/handlers/redis_services.go index 3bc9cc9a..88919caa 100644 --- a/backend/gestion/handlers/redis_services.go +++ b/backend/gestion/handlers/redis_services.go @@ -935,9 +935,41 @@ func GetPenaltiesStats(c *gin.Context) { }) } +func ResetClientPointAdmin(c *gin.Context) { + userRole := c.GetString("role") + if userRole != "admin" && userRole != "cabine" { + c.JSON(http.StatusForbidden, gin.H{"error": "Accès refusé"}) + return + } + + username := c.Param("username") + if username == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "Username requis"}) + return + } + var req struct { + ResetCancellationsPoints bool `json:"reset_cancellations_points"` + } + + if err := c.ShouldBindJSON(&req); err != nil { + req.ResetCancellationsPoints = false + } + + database := c.MustGet("database").(*db.Database) + + err := database.ResetClientPoint(username, req.ResetCancellationsPoints) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Erreur lors de la réinitialisation", + "details": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Points réinitialisés"}) +} + // ResetClientPenaltiesAdmin réinitialise les pénalités d'un client (Admin seulement) -// POST /api/v2/admin/protected/client/:username/penalties/reset -// Body: {"reset_cancellations_count": false} func ResetClientPenaltiesAdmin(c *gin.Context) { userRole := c.GetString("role") if userRole != "admin" && userRole != "cabine" { diff --git a/backend/gestion/routes/routes.go b/backend/gestion/routes/routes.go index 35e62021..6355a6b3 100644 --- a/backend/gestion/routes/routes.go +++ b/backend/gestion/routes/routes.go @@ -225,10 +225,10 @@ func SetupRoutes(router *gin.Engine, database *db.Database, geoService *services cabineGroupV1.DELETE("/commands/:id", handlers.DeleteCommandByCabine) // ⭐ NOUVEAU - ANNULATION PAR CABINE cabineGroupV1.GET("/commands/cancelled", handlers.GetAllCancelledOrders) - cabineGroupV1.POST("/penalty", handlers.ApplyClientPenalty) // Appliquer pénalité cabineGroupV1.GET("/client/:username/penalties", handlers.GetClientPenaltiesAdmin) // Voir pénalités client cabineGroupV1.POST("/client/:username/penalties/reset", handlers.ResetClientPenaltiesAdmin) // Reset pénalités - cabineGroupV1.GET("/penalties/all", handlers.GetAllClientsWithPenalties) // Liste clients avec pénalités + cabineGroupV1.POST("/client/:username/point/reset", handlers.ResetClientPointAdmin) + cabineGroupV1.GET("/penalties/all", handlers.GetAllClientsWithPenalties) // Liste clients avec pénalités cabineGroupV1.GET("/penalties/stats", handlers.GetPenaltiesStats) // ============================================ diff --git a/frontend-admin/src/api/api_cabine.ts b/frontend-admin/src/api/api_cabine.ts index f531e4eb..de002c46 100644 --- a/frontend-admin/src/api/api_cabine.ts +++ b/frontend-admin/src/api/api_cabine.ts @@ -62,6 +62,17 @@ export const resetClientPenalties = async (clientUsername: string) => { return { success: true, message: data.message }; }; +export const resetClientPoints = async ( + clientUsername: string, + resetCancellationsPoints: boolean = false, +) => { + const { data } = await apiClient.post( + `${API}/client/${clientUsername}/point/reset`, + { reset_cancellations_points: resetCancellationsPoints }, + ); + return { success: true, message: data.message }; +}; + export const getAllClientsWithPenalties = async () => { const { data } = await apiClient.get(`${API}/penalties/all`); return { diff --git a/frontend-admin/src/screens/cabine/DeliveryScreen.tsx b/frontend-admin/src/screens/cabine/DeliveryScreen.tsx index 5a19990a..f69762c3 100644 --- a/frontend-admin/src/screens/cabine/DeliveryScreen.tsx +++ b/frontend-admin/src/screens/cabine/DeliveryScreen.tsx @@ -17,7 +17,6 @@ import { Dimensions, } from "react-native"; import { Ionicons } from "@expo/vector-icons"; -import MapView, { Marker, Polyline, PROVIDER_DEFAULT } from "react-native-maps"; import { spacing, fontSize, borderRadius } from "../../theme"; import { useTheme } from "../../context/ThemeContext"; import { @@ -31,6 +30,10 @@ import { STATUS_LABELS, getStatusColors } from "../../utils/constants"; import LoadingSpinner from "../../components/ui/LoadingSpinner"; import Card from "../../components/ui/Card"; import Badge from "../../components/ui/Badge"; +import TomTomMap, { + TomTomMapRef, + TomTomMarker, +} from "../../components/TomTomMap"; const MAP_HEIGHT = 280; @@ -48,17 +51,14 @@ export default function DeliveryScreen() { const [refreshing, setRefreshing] = useState(false); // Map - const mapRef = useRef(null); - const fullscreenMapRef = useRef(null); + const mapRef = useRef(null); + const fullscreenMapRef = useRef(null); const [mapFullscreen, setMapFullscreen] = useState(false); // Selected livreur route const [selectedLivreur, setSelectedLivreur] = useState(null); const [routeInfo, setRouteInfo] = useState(null); - const [destinationCoords, setDestinationCoords] = useState( - null, - ); const [routeLoading, setRouteLoading] = useState(false); const loadData = useCallback(async () => { @@ -93,7 +93,6 @@ export default function DeliveryScreen() { async (livreur: DeliveryPerson) => { setSelectedLivreur(livreur); setRouteInfo(null); - setDestinationCoords(null); if (!livreur.stats.current_command) return; @@ -110,44 +109,26 @@ export default function DeliveryScreen() { return; } + const origin: LatLng = { + latitude: livreur.location.latitude, + longitude: livreur.location.longitude, + }; + const dest = await geocodeAddress(cmdAddress); if (!dest) { setRouteLoading(false); return; } - setDestinationCoords(dest); - const origin: LatLng = { - latitude: livreur.location.latitude, - longitude: livreur.location.longitude, - }; const result = await calculateRoute(origin, dest); if (result) { setRouteInfo(result.route); const ref = mapFullscreen ? fullscreenMapRef : mapRef; - if (ref.current) { - ref.current.fitToCoordinates( - [ - { - latitude: origin.latitude, - longitude: origin.longitude, - }, - { - latitude: dest.latitude, - longitude: dest.longitude, - }, - ], - { - edgePadding: { - top: 80, - right: 60, - bottom: 80, - left: 60, - }, - animated: true, - }, - ); - } + ref.current?.fitToCoordinates([origin, dest]); + ref.current?.calcRoute(origin, dest); + } else { + const ref = mapFullscreen ? fullscreenMapRef : mapRef; + ref.current?.calcRoute(origin, dest); } } catch { /* silent */ @@ -160,88 +141,26 @@ export default function DeliveryScreen() { const clearRoute = () => { setSelectedLivreur(null); setRouteInfo(null); - setDestinationCoords(null); + mapRef.current?.clearRoute(); + fullscreenMapRef.current?.clearRoute(); }; - const fitAllMarkers = (ref: React.RefObject) => { - if (ref.current && livreursWithGPS.length > 0) { - ref.current.fitToCoordinates( - livreursWithGPS.map((l) => ({ - latitude: l.location.latitude, - longitude: l.location.longitude, - })), - { - edgePadding: { top: 60, right: 60, bottom: 60, left: 60 }, - animated: true, - }, - ); - } - }; - - // Render map markers – point bleu pour chaque livreur - const renderMapMarkers = () => - livreursWithGPS.map((l) => { - const isSelected = selectedLivreur?.username === l.username; - return ( - trackLivreur(l)} - > - - - - - - - ); - }); - - const renderRouteOverlay = () => { - if (!destinationCoords) return null; - return ( - <> - - - - - - - - {routeInfo && routeInfo.coordinates.length > 0 && ( - - )} - - ); - }; + const livreurMarkers = useMemo( + () => + livreursWithGPS.map((l) => ({ + id: l.username, + latitude: l.location.latitude, + longitude: l.location.longitude, + color: + selectedLivreur?.username === l.username + ? "#2196F3" + : statusColors[l.status] || "#888", + label: l.username, + description: `${STATUS_LABELS[l.status] || l.status}${l.stats.current_command ? ` · Cmd #${l.stats.current_command}` : ""}`, + isSelected: selectedLivreur?.username === l.username, + })), + [livreursWithGPS, selectedLivreur, statusColors], + ); const renderLivreur = ({ item }: { item: DeliveryPerson }) => { const isSelected = selectedLivreur?.username === item.username; @@ -334,22 +253,28 @@ export default function DeliveryScreen() { {livreursWithGPS.length > 0 && ( - fitAllMarkers(mapRef)} - showsUserLocation={false} - > - {renderMapMarkers()} - {renderRouteOverlay()} - + initialZoom={13} + onMarkerPress={(id) => { + const livreur = livreursWithGPS.find( + (l) => l.username === id, + ); + if (livreur) { + if (selectedLivreur?.username === id) { + clearRoute(); + } else { + trackLivreur(livreur); + } + } + }} + /> {routeInfo && selectedLivreur && ( @@ -392,7 +317,7 @@ export default function DeliveryScreen() { fitAllMarkers(mapRef)} + onPress={() => mapRef.current?.fitAllMarkers()} >