diff --git a/backend/gestion/db/db_clients.go b/backend/gestion/db/db_clients.go index e7a6702a..cefd85e6 100644 --- a/backend/gestion/db/db_clients.go +++ b/backend/gestion/db/db_clients.go @@ -130,7 +130,6 @@ func (d *Database) GetAllClients() ([]*models.Client, error) { func (d *Database) UpdateClient(client *models.Client) error { result := d.GDB.Model(&models.Client{}).Where("id = ?", client.ID).Updates(map[string]any{ "username": client.Username, - "password": client.Password, "nom": client.Nom, "prenom": client.Prenom, "telephone": client.Telephone, diff --git a/backend/gestion/handlers/commands.go b/backend/gestion/handlers/commands.go index bcbe99b8..6b79218c 100644 --- a/backend/gestion/handlers/commands.go +++ b/backend/gestion/handlers/commands.go @@ -1047,7 +1047,8 @@ func DeleteCommandItem(c *gin.Context) { func UpdateCommandStatusAdmin(c *gin.Context) { database := c.MustGet("database").(*db.Database) - if c.GetString("role") != "admin" { + role := c.GetString("role") + if role != "admin" && role != "cabine" { c.JSON(http.StatusForbidden, gin.H{"error": "Accès refusé"}) return } @@ -1068,7 +1069,7 @@ func UpdateCommandStatusAdmin(c *gin.Context) { allowed := map[string]bool{ "pending": true, "assigned": true, "en_route": true, - "livre": true, "approved": true, "cancelled": true, + "arrived": true, "livre": true, "approved": true, "cancelled": true, } if !allowed[req.Status] { c.JSON(http.StatusBadRequest, gin.H{"error": "Statut invalide: " + req.Status}) @@ -1080,7 +1081,7 @@ func UpdateCommandStatusAdmin(c *gin.Context) { return } - log.Printf("✅ [STATUS_ADMIN] Cmd %d → %s", commandID, req.Status) + log.Printf("✅ [STATUS_ADMIN] Cmd %d → %s (par %s)", commandID, req.Status, role) c.JSON(http.StatusOK, gin.H{ "success": true, "message": "Statut mis à jour", diff --git a/backend/gestion/handlers/deleviry.go b/backend/gestion/handlers/deleviry.go index a3aab941..7c598f96 100644 --- a/backend/gestion/handlers/deleviry.go +++ b/backend/gestion/handlers/deleviry.go @@ -345,18 +345,28 @@ func UpdateDeliveryStatus(c *gin.Context) { var clientMsg string switch req.Status { case "en_route": - if etaMinutes > 0 { + notifETA := etaMinutes + if notifETA == 0 { + if etaData, err := database.GetCommandETA(commandID); err == nil { + if v, ok := etaData["total_eta_minutes"]; ok { + if n, err2 := strconv.Atoi(v); err2 == nil && n > 0 { + notifETA = n + } + } + } + } + if notifETA > 0 { var etaStr string - if etaMinutes >= 60 { - h := etaMinutes / 60 - m := etaMinutes % 60 + if notifETA >= 60 { + h := notifETA / 60 + m := notifETA % 60 if m > 0 { etaStr = fmt.Sprintf("%dh%02d", h, m) } else { etaStr = fmt.Sprintf("%dh", h) } } else { - etaStr = fmt.Sprintf("%d min", etaMinutes) + etaStr = fmt.Sprintf("%d min", notifETA) } clientMsg = fmt.Sprintf("Un livreur vient de prendre en charge ta commande #%d, il est en route (~%s)", database.GetClientOrderID(commandID), etaStr) } else { diff --git a/backend/gestion/handlers/update_profile.go b/backend/gestion/handlers/update_profile.go index 76246373..7465f1d8 100644 --- a/backend/gestion/handlers/update_profile.go +++ b/backend/gestion/handlers/update_profile.go @@ -236,7 +236,8 @@ func UpdateClientByAdmin(c *gin.Context) { log.Printf("✏️ [UPDATE_CLIENT_ADMIN] Username modifié: %s", req.Username) } - // Mise à jour du mot de passe + // Mise à jour du mot de passe (opération séparée pour garantir l'écriture) + var newHashedPassword string if req.Password != "" { if len(req.Password) < 8 { c.JSON(http.StatusBadRequest, gin.H{"error": "Le mot de passe doit contenir au moins 8 caractères"}) @@ -248,7 +249,7 @@ func UpdateClientByAdmin(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur traitement mot de passe"}) return } - client.Password = string(hashed) + newHashedPassword = string(hashed) hasChanges = true log.Printf("✏️ [UPDATE_CLIENT_ADMIN] Mot de passe modifié") } @@ -323,13 +324,23 @@ func UpdateClientByAdmin(c *gin.Context) { log.Printf("📊 [UPDATE_CLIENT_ADMIN] État avant save - Command: %d, Amende: %.2f", client.Command, client.Amende) - // Sauvegarder les modifications + // Sauvegarder les modifications de profil (hors mot de passe) if err := database.UpdateClient(client); err != nil { - log.Printf("❌ [UPDATE_CLIENT_ADMIN] Erreur mise à jour: %v", err) + log.Printf("❌ [UPDATE_CLIENT_ADMIN] Erreur mise à jour profil: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur lors de la mise à jour"}) return } + // Mettre à jour le mot de passe séparément si demandé + if newHashedPassword != "" { + if err := database.UpdateClientPasswordAndClearFlag(clientID, newHashedPassword); err != nil { + log.Printf("❌ [UPDATE_CLIENT_ADMIN] Erreur mise à jour mot de passe: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur lors de la mise à jour du mot de passe"}) + return + } + log.Printf("✅ [UPDATE_CLIENT_ADMIN] Mot de passe mis à jour pour client ID=%d", clientID) + } + log.Printf("✅ [UPDATE_CLIENT_ADMIN] Client mis à jour par admin: %s (ID=%d)", client.Username, client.ID) c.JSON(http.StatusOK, gin.H{ diff --git a/backend/gestion/handlers/validation_deleviry.go b/backend/gestion/handlers/validation_deleviry.go index 006d8639..8aee1110 100644 --- a/backend/gestion/handlers/validation_deleviry.go +++ b/backend/gestion/handlers/validation_deleviry.go @@ -352,7 +352,14 @@ func StartDelivery(c *gin.Context) { if clientUsername, _ := command["username"].(string); clientUsername != "" { var msg string etaMinutes := 0 - if req.Latitude != 0 && req.Longitude != 0 { + if etaData, err := database.GetCommandETA(commandID); err == nil { + if v, ok := etaData["total_eta_minutes"]; ok { + if n, err2 := strconv.Atoi(v); err2 == nil && n > 0 { + etaMinutes = n + } + } + } + if etaMinutes == 0 && req.Latitude != 0 && req.Longitude != 0 { destLat, _ := command["dest_latitude"].(float64) destLon, _ := command["dest_longitude"].(float64) if destLat != 0 && destLon != 0 { diff --git a/backend/gestion/routes/routes.go b/backend/gestion/routes/routes.go index ac0225a5..d4c3ba38 100644 --- a/backend/gestion/routes/routes.go +++ b/backend/gestion/routes/routes.go @@ -302,6 +302,7 @@ func SetupRoutes(router *gin.Engine, database *db.Database, geoService *services cabineGroupV1.POST("/commands/:id/confirm-reception", handlers.StaffApproveDelivery) cabineGroupV1.POST("/commands/:id/assign", handlers.AssignDeliveryPerson) cabineGroupV1.POST("/commands/:id/notify-client", handlers.NotifyClientToDescend) + cabineGroupV1.PUT("/commands/:id/status", handlers.UpdateCommandStatusAdmin) cabineGroupV1.PUT("/items/:item_id/status", handlers.UpdateItemStatus) cabineGroupV1.GET("/commands/:id/deliveryman/location", handlers.GetDeliverymanLocationForCommand) cabineGroupV1.GET("/all/deliveryman", handlers.GetAllDeliveryMen) diff --git a/frontend-prep/public/config.js b/frontend-prep/public/config.js new file mode 100644 index 00000000..430cf25e --- /dev/null +++ b/frontend-prep/public/config.js @@ -0,0 +1,4 @@ +// Surchargé par le ConfigMap Helm en production +window.__APP_CONFIG__ = { + apiUrl: "", +}; diff --git a/frontend-prep/src/App.css b/frontend-prep/src/App.css index 19781a5a..bd9433a4 100644 --- a/frontend-prep/src/App.css +++ b/frontend-prep/src/App.css @@ -7,10 +7,6 @@ html, body, #root { overflow-x: hidden; } -/* Ne pas forcer de backgroundColor global pour permettre à login d'avoir son propre style */ -body, html { - background-color: transparent !important; -} #root { width: 100%; diff --git a/frontend-prep/src/api/api.ts b/frontend-prep/src/api/api.ts index 6253b65e..01a395f4 100644 --- a/frontend-prep/src/api/api.ts +++ b/frontend-prep/src/api/api.ts @@ -2010,7 +2010,10 @@ export const updateMyProfile = async (fields: { // 🤖 TELEGRAM // ============================================ -export const getTelegramStatus = async (): Promise<{ linked: boolean; enabled: boolean }> => { +export const getTelegramStatus = async (): Promise<{ + linked: boolean; + enabled: boolean; +}> => { const token = getAuthToken(); if (!token) return { linked: false, enabled: false }; try { @@ -2048,5 +2051,7 @@ export const unlinkTelegram = async (): Promise => { method: "DELETE", headers: { Authorization: `Bearer ${token}` }, }); - } catch { /* silencieux */ } + } catch { + /* silencieux */ + } }; diff --git a/frontend-prep/src/index.css b/frontend-prep/src/index.css index f7083de4..662235f7 100644 --- a/frontend-prep/src/index.css +++ b/frontend-prep/src/index.css @@ -16,12 +16,38 @@ text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - /* Empêcher le zoom sur les inputs pour iOS */ font-size: 16px; + + --bg: #09090b; + --surface: #111115; + --surface-2: #1c1c22; + --border: rgba(255, 255, 255, 0.07); + --primary: #8b5cf6; + --primary-soft: rgba(139, 92, 246, 0.12); + --primary-glow: rgba(139, 92, 246, 0.25); + --cyan: #22d3ee; + --cyan-soft: rgba(34, 211, 238, 0.1); + --red: #ef4444; + --text: #f4f4f5; + --text-muted: #71717a; + --radius: 10px; + --transition: 0.22s cubic-bezier(0.4, 0, 0.2, 1); } [data-theme="light"] { color-scheme: light; + --bg: #f4f4f8; + --surface: #ffffff; + --surface-2: #ebebf2; + --border: rgba(0, 0, 0, 0.08); + --primary: #7c3aed; + --primary-soft: rgba(124, 58, 237, 0.08); + --primary-glow: rgba(124, 58, 237, 0.18); + --cyan: #0891b2; + --cyan-soft: rgba(8, 145, 178, 0.1); + --red: #dc2626; + --text: #18181b; + --text-muted: #52525b; } html, body { diff --git a/frontend-prep/src/pages/User/Checkout.tsx b/frontend-prep/src/pages/User/Checkout.tsx index bf923c8b..24ea2afe 100644 --- a/frontend-prep/src/pages/User/Checkout.tsx +++ b/frontend-prep/src/pages/User/Checkout.tsx @@ -122,10 +122,11 @@ function Checkout() { if (savedAddress) setAddress(savedAddress); if (savedPhone) setPhone(savedPhone); - // Pré-remplir nom depuis le backend + // Pré-remplir nom/prénom depuis le backend getMyProfile().then((res) => { if (res.success && res.client) { if (res.client.nom) setLastName(res.client.nom); + if (res.client.prenom) setFirstName(res.client.prenom); } }); }, []); diff --git a/frontend-prep/vite.config.ts b/frontend-prep/vite.config.ts index 244e5ef5..bce3586c 100644 --- a/frontend-prep/vite.config.ts +++ b/frontend-prep/vite.config.ts @@ -1,6 +1,8 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; +const DEV_API_TARGET = "http://localhost:8080"; + export default defineConfig({ plugins: [react()], build: { @@ -9,11 +11,11 @@ export default defineConfig({ server: { proxy: { "/api": { - target: "https://5.181.0.112.nip.io", + target: DEV_API_TARGET, changeOrigin: true, }, "/uploads": { - target: "https://5.181.0.112.nip.io", + target: DEV_API_TARGET, changeOrigin: true, }, },