diff --git a/backend/gestion/db/db_clients.go b/backend/gestion/db/db_clients.go index 061274fc..97ad7ce7 100644 --- a/backend/gestion/db/db_clients.go +++ b/backend/gestion/db/db_clients.go @@ -867,36 +867,25 @@ func (d *Database) CalculateAndAddPointsForCommandTx(tx *sql.Tx, commandID int, settings = DefaultSettings() } - // Construire les sets de catégories par pool - weedCats := make(map[string]bool) - for _, cat := range settings.PointsCategoriesWeed { - weedCats[strings.ToLower(cat)] = true - } - zipetteCats := make(map[string]bool) - for _, cat := range settings.PointsCategoriesZipette { - zipetteCats[strings.ToLower(cat)] = true - } - // pool "total" → toujours compté dans le pool weed (point) - totalCats := make(map[string]bool) - for _, cat := range settings.PointsCategoriesTotal { - totalCats[strings.ToLower(cat)] = true + pools := settings.PointsPools + if len(pools) == 0 { + log.Printf("ℹ️ [CalcPointsTx] Aucun pool configuré → 0 points") + return 0, "", nil } - // Si aucune catégorie configurée → pas de points - if !settings.PointsSeparated { - // Mode non-séparé : seul le pool Total est actif - if len(weedCats) == 0 && len(zipetteCats) == 0 && len(totalCats) == 0 { - log.Printf("ℹ️ [CalcPointsTx] Mode non-séparé : aucune catégorie configurée → 0 points") - return 0, "", nil - } - } else { - // Mode séparé : seuls W et Z sont actifs (T ignoré) - if len(weedCats) == 0 && len(zipetteCats) == 0 { - log.Printf("ℹ️ [CalcPointsTx] Mode séparé : aucune catégorie W/Z configurée → 0 points") - return 0, "", nil + // Construire la map catégorie → index de pool + catToPool := make(map[string]int) + for i, pool := range pools { + for _, cat := range pool.Categories { + catToPool[strings.ToLower(cat)] = i } } + if len(catToPool) == 0 { + log.Printf("ℹ️ [CalcPointsTx] Aucune catégorie assignée aux pools → 0 points") + return 0, "", nil + } + // ✅ ÉTAPE 1: Récupérer tous les items de la commande avec leurs catégories rows, err := tx.Query(` SELECT ci.quantite, ci.prix, COALESCE(p.category, '') as category @@ -911,9 +900,7 @@ func (d *Database) CalculateAndAddPointsForCommandTx(tx *sql.Tx, commandID int, defer rows.Close() var itemCount int - totalPrixWeed := 0.0 - totalPrixZipette := 0.0 - totalPrixTotal := 0.0 + poolTotals := make([]float64, len(pools)) for rows.Next() { var quantite, prix float64 @@ -924,12 +911,8 @@ func (d *Database) CalculateAndAddPointsForCommandTx(tx *sql.Tx, commandID int, } itemCount++ catLower := strings.ToLower(category) - if weedCats[catLower] { - totalPrixWeed += prix - } else if zipetteCats[catLower] { - totalPrixZipette += prix - } else if totalCats[catLower] { - totalPrixTotal += prix + if poolIdx, ok := catToPool[catLower]; ok { + poolTotals[poolIdx] += prix } } @@ -943,58 +926,49 @@ func (d *Database) CalculateAndAddPointsForCommandTx(tx *sql.Tx, commandID int, return 0, "", nil } - log.Printf("📊 [CalcPointsTx] %d items - weed: %.2f€, zipette: %.2f€, total: %.2f€", - itemCount, totalPrixWeed, totalPrixZipette, totalPrixTotal) + for i, t := range poolTotals { + log.Printf("📊 [CalcPointsTx] Pool[%d] (%s): %.2f€", i, pools[i].Name, t) + } - // ✅ ÉTAPE 2: Calculer les points + // ✅ ÉTAPE 2: Calculer les points par pool et mettre à jour les colonnes DB + // Pool[0] → colonne `point`, Pool[1] → colonne `point_zipette` var totalPoints int var pointCategory string var result sql.Result - if !settings.PointsSeparated { - // ── Mode non-séparé : Barème Total appliqué sur W + Z + T ────────────── - allTotal := totalPrixWeed + totalPrixZipette + totalPrixTotal - totalPoints = CalcPointsFromTiers(allTotal, settings.PointsTotalTiers) - pointCategory = "total" + pts0 := CalcPointsFromTiers(poolTotals[0], pools[0].Tiers) + pts1 := 0 + if len(pools) >= 2 { + pts1 = CalcPointsFromTiers(poolTotals[1], pools[1].Tiers) + } + totalPoints = pts0 + pts1 - log.Printf("💰 [CalcPointsTx] Mode non-séparé - total: %.2f€ → %d pts (barème Total)", - allTotal, totalPoints) + log.Printf("💰 [CalcPointsTx] pool[0]=%d pts, pool[1]=%d pts", pts0, pts1) - if totalPoints == 0 { - return 0, "", nil - } + if totalPoints == 0 { + return 0, "", nil + } + if pts0 > 0 && pts1 > 0 { + pointCategory = pools[0].Name + " & " + pools[1].Name + } else if pts1 > 0 { + pointCategory = pools[1].Name + } else { + pointCategory = pools[0].Name + } + + if len(pools) == 1 || pts1 == 0 { result, err = tx.Exec(` UPDATE clients SET point = point + $1, updated_at = CURRENT_TIMESTAMP WHERE username = $2 - `, totalPoints, username) + `, pts0, username) } else { - // ── Mode séparé : Barèmes W et Z, pool T ignoré ──────────────────────── - pointsWeed := CalcPointsFromTiers(totalPrixWeed, settings.PointsWeedTiers) - pointsZipette := CalcPointsFromTiers(totalPrixZipette, settings.PointsZipetteTiers) - totalPoints = pointsWeed + pointsZipette - - log.Printf("💰 [CalcPointsTx] Mode séparé - weed: %d pts, zipette: %d pts", - pointsWeed, pointsZipette) - - if totalPoints == 0 { - return 0, "", nil - } - - if pointsWeed > 0 && pointsZipette > 0 { - pointCategory = "mixed" - } else if pointsZipette > 0 { - pointCategory = "zipette&co" - } else { - pointCategory = "weed&hash" - } - result, err = tx.Exec(` UPDATE clients SET point = point + $1, point_zipette = point_zipette + $2, updated_at = CURRENT_TIMESTAMP WHERE username = $3 - `, pointsWeed, pointsZipette, username) + `, pts0, pts1, username) } if err != nil { diff --git a/backend/gestion/db/db_settings.go b/backend/gestion/db/db_settings.go index 8a2bef76..90b31d3e 100644 --- a/backend/gestion/db/db_settings.go +++ b/backend/gestion/db/db_settings.go @@ -57,48 +57,57 @@ func CalcPointsFromTiers(total float64, tiers []PointsTier) int { return 0 } +// PointsPool représente un type de point personnalisable par l'admin +// Pool[0] → colonne `point`, Pool[1] → colonne `point_zipette` +type PointsPool struct { + Key string `json:"key"` // identifiant interne (ex: "pool_0") + Name string `json:"name"` // nom affiché (ex: "Cannabis", "Accessoires") + Categories []string `json:"categories"` // catégories de produits assignées à ce pool + Tiers []PointsTier `json:"tiers"` // barème de points +} + // AppSettings contient les paramètres globaux de l'application type AppSettings struct { - PenaltiesEnabled bool `json:"penalties_enabled"` - ShowAmendeScore bool `json:"show_amende_score"` // afficher le score d'amendes aux clients/cabine - PointsEnabled bool `json:"points_enabled"` // afficher/activer le système de points - PointsCategoriesWeed []string `json:"points_categories_weed"` // catégories → pool point (weed) - PointsCategoriesZipette []string `json:"points_categories_zipette"` // catégories → pool point_zipette - PointsCategoriesTotal []string `json:"points_categories_total"` // catégories → pool total (point, sans séparation) - PointsSeparated bool `json:"points_separated"` // true = weed/zipette séparés, false = tout dans point - // Barème de points par paliers configurables - PointsWeedTiers []PointsTier `json:"points_weed_tiers"` - PointsZipetteTiers []PointsTier `json:"points_zipette_tiers"` - PointsTotalTiers []PointsTier `json:"points_total_tiers"` - ReferralEnabled bool `json:"referral_enabled"` // activer/désactiver le système de parrainage - DeliverySchedule DeliverySchedule `json:"delivery_schedule"` // horaires de livraison par jour - PostalZones []PostalZone `json:"postal_zones"` // zones de livraison avec minimum de commande + PenaltiesEnabled bool `json:"penalties_enabled"` + ShowAmendeScore bool `json:"show_amende_score"` // afficher le score d'amendes aux clients/cabine + PointsEnabled bool `json:"points_enabled"` // afficher/activer le système de points + PointsPools []PointsPool `json:"points_pools"` // types de points personnalisés + ReferralEnabled bool `json:"referral_enabled"` // activer/désactiver le système de parrainage + DeliverySchedule DeliverySchedule `json:"delivery_schedule"` // horaires de livraison par jour + PostalZones []PostalZone `json:"postal_zones"` // zones de livraison avec minimum de commande } // DefaultSettings retourne les paramètres par défaut func DefaultSettings() AppSettings { return AppSettings{ - PenaltiesEnabled: true, - ShowAmendeScore: true, - PointsEnabled: true, - ReferralEnabled: true, - PointsCategoriesWeed: []string{}, - PointsCategoriesZipette: []string{}, - PointsCategoriesTotal: []string{}, - PointsSeparated: true, - PointsWeedTiers: []PointsTier{ - {Min: 30, Max: 50, Points: 1}, - {Min: 60, Max: 150, Points: 2}, - {Min: 160, Max: 300, Points: 3}, - {Min: 310, Max: 400, Points: 5}, - {Min: 401, Max: 0, Points: 10}, + PenaltiesEnabled: true, + ShowAmendeScore: true, + PointsEnabled: true, + ReferralEnabled: true, + PointsPools: []PointsPool{ + { + Key: "pool_0", + Name: "Pool 1", + Categories: []string{}, + Tiers: []PointsTier{ + {Min: 30, Max: 50, Points: 1}, + {Min: 60, Max: 150, Points: 2}, + {Min: 160, Max: 300, Points: 3}, + {Min: 310, Max: 400, Points: 5}, + {Min: 401, Max: 0, Points: 10}, + }, + }, + { + Key: "pool_1", + Name: "Pool 2", + Categories: []string{}, + Tiers: []PointsTier{ + {Min: 30, Max: 100, Points: 1}, + {Min: 110, Max: 200, Points: 2}, + {Min: 210, Max: 0, Points: 3}, + }, + }, }, - PointsZipetteTiers: []PointsTier{ - {Min: 30, Max: 100, Points: 1}, - {Min: 110, Max: 200, Points: 2}, - {Min: 210, Max: 0, Points: 3}, - }, - PointsTotalTiers: []PointsTier{}, DeliverySchedule: DefaultDeliverySchedule(), PostalZones: []PostalZone{ {Name: "Zone 30€", MinAmount: 30, Codes: []string{"44000", "44100", "44200", "44300"}}, @@ -135,37 +144,10 @@ func (d *Database) GetSettings() (AppSettings, error) { settings.ShowAmendeScore = value == "true" case "points_enabled": settings.PointsEnabled = value == "true" - case "points_categories_weed": - var cats []string - if err := json.Unmarshal([]byte(value), &cats); err == nil { - settings.PointsCategoriesWeed = cats - } - case "points_categories_zipette": - var cats []string - if err := json.Unmarshal([]byte(value), &cats); err == nil { - settings.PointsCategoriesZipette = cats - } - case "points_categories_total": - var cats []string - if err := json.Unmarshal([]byte(value), &cats); err == nil { - settings.PointsCategoriesTotal = cats - } - case "points_separated": - settings.PointsSeparated = value == "true" - case "points_weed_tiers": - var tiers []PointsTier - if err := json.Unmarshal([]byte(value), &tiers); err == nil { - settings.PointsWeedTiers = tiers - } - case "points_zipette_tiers": - var tiers []PointsTier - if err := json.Unmarshal([]byte(value), &tiers); err == nil { - settings.PointsZipetteTiers = tiers - } - case "points_total_tiers": - var tiers []PointsTier - if err := json.Unmarshal([]byte(value), &tiers); err == nil { - settings.PointsTotalTiers = tiers + case "points_pools": + var pools []PointsPool + if err := json.Unmarshal([]byte(value), &pools); err == nil { + settings.PointsPools = pools } case "referral_enabled": settings.ReferralEnabled = value == "true" @@ -192,27 +174,23 @@ func (d *Database) UpdateSettings(s AppSettings) error { } return "false" } - if s.PointsCategoriesWeed == nil { - s.PointsCategoriesWeed = []string{} + + if s.PointsPools == nil { + s.PointsPools = []PointsPool{} } - if s.PointsCategoriesZipette == nil { - s.PointsCategoriesZipette = []string{} - } - if s.PointsCategoriesTotal == nil { - s.PointsCategoriesTotal = []string{} + // S'assurer que chaque pool a des slices non-nil + for i := range s.PointsPools { + if s.PointsPools[i].Categories == nil { + s.PointsPools[i].Categories = []string{} + } + if s.PointsPools[i].Tiers == nil { + s.PointsPools[i].Tiers = []PointsTier{} + } } - weedJSON, err := json.Marshal(s.PointsCategoriesWeed) + poolsJSON, err := json.Marshal(s.PointsPools) if err != nil { - return fmt.Errorf("erreur sérialisation weed: %w", err) - } - zipetteJSON, err := json.Marshal(s.PointsCategoriesZipette) - if err != nil { - return fmt.Errorf("erreur sérialisation zipette: %w", err) - } - totalJSON, err := json.Marshal(s.PointsCategoriesTotal) - if err != nil { - return fmt.Errorf("erreur sérialisation total: %w", err) + return fmt.Errorf("erreur sérialisation pools: %w", err) } tx, err := d.Begin() @@ -224,39 +202,11 @@ func (d *Database) UpdateSettings(s AppSettings) error { upsert := `INSERT INTO app_settings (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value` - if s.PointsWeedTiers == nil { - s.PointsWeedTiers = []PointsTier{} - } - if s.PointsZipetteTiers == nil { - s.PointsZipetteTiers = []PointsTier{} - } - if s.PointsTotalTiers == nil { - s.PointsTotalTiers = []PointsTier{} - } - weedTiersJSON, err := json.Marshal(s.PointsWeedTiers) - if err != nil { - return fmt.Errorf("erreur sérialisation weed tiers: %w", err) - } - zipetteTiersJSON, err := json.Marshal(s.PointsZipetteTiers) - if err != nil { - return fmt.Errorf("erreur sérialisation zipette tiers: %w", err) - } - totalTiersJSON, err := json.Marshal(s.PointsTotalTiers) - if err != nil { - return fmt.Errorf("erreur sérialisation total tiers: %w", err) - } - pairs := [][2]string{ {"penalties_enabled", boolStr(s.PenaltiesEnabled)}, {"show_amende_score", boolStr(s.ShowAmendeScore)}, {"points_enabled", boolStr(s.PointsEnabled)}, - {"points_categories_weed", string(weedJSON)}, - {"points_categories_zipette", string(zipetteJSON)}, - {"points_categories_total", string(totalJSON)}, - {"points_separated", boolStr(s.PointsSeparated)}, - {"points_weed_tiers", string(weedTiersJSON)}, - {"points_zipette_tiers", string(zipetteTiersJSON)}, - {"points_total_tiers", string(totalTiersJSON)}, + {"points_pools", string(poolsJSON)}, {"referral_enabled", boolStr(s.ReferralEnabled)}, } diff --git a/backend/gestion/handlers/settings.go b/backend/gestion/handlers/settings.go index b3ef0533..3d35ab87 100644 --- a/backend/gestion/handlers/settings.go +++ b/backend/gestion/handlers/settings.go @@ -24,7 +24,7 @@ func GetPublicSettings(c *gin.Context) { "penalties_enabled": settings.PenaltiesEnabled, "show_amende_score": settings.ShowAmendeScore, "points_enabled": settings.PointsEnabled, - "points_separated": settings.PointsSeparated, + "points_separated": len(settings.PointsPools) > 1, "referral_enabled": settings.ReferralEnabled, "delivery_schedule": settings.DeliverySchedule, }) @@ -60,8 +60,7 @@ func UpdateSettings(c *gin.Context) { return } - log.Printf("✅ [SETTINGS] Mise à jour: penalties=%v, points_separated=%v, weed=%v, zipette=%v, total=%v", - req.PenaltiesEnabled, req.PointsSeparated, req.PointsCategoriesWeed, req.PointsCategoriesZipette, req.PointsCategoriesTotal) + log.Printf("✅ [SETTINGS] Mise à jour: penalties=%v, pools=%d", req.PenaltiesEnabled, len(req.PointsPools)) c.JSON(http.StatusOK, gin.H{"success": true, "settings": req}) } diff --git a/frontend-admin/src/api/api_admin.ts b/frontend-admin/src/api/api_admin.ts index 50d22b11..5baf55e2 100644 --- a/frontend-admin/src/api/api_admin.ts +++ b/frontend-admin/src/api/api_admin.ts @@ -704,6 +704,13 @@ export interface PointsTier { points: number; } +export interface PointsPool { + key: string; + name: string; + categories: string[]; + tiers: PointsTier[]; +} + export interface PostalZone { name: string; min_amount: number; @@ -744,13 +751,7 @@ export interface AppSettings { penalties_enabled: boolean; show_amende_score: boolean; points_enabled: boolean; - points_categories_weed: string[]; - points_categories_zipette: string[]; - points_categories_total: string[]; - points_separated: boolean; - points_weed_tiers: PointsTier[]; - points_zipette_tiers: PointsTier[]; - points_total_tiers: PointsTier[]; + points_pools: PointsPool[]; referral_enabled: boolean; delivery_schedule: DeliverySchedule; postal_zones: PostalZone[]; diff --git a/frontend-admin/src/screens/admin/OrdersScreen.tsx b/frontend-admin/src/screens/admin/OrdersScreen.tsx index 4ce9de8e..182edfa8 100644 --- a/frontend-admin/src/screens/admin/OrdersScreen.tsx +++ b/frontend-admin/src/screens/admin/OrdersScreen.tsx @@ -255,6 +255,26 @@ export default function OrdersScreen() { paddingHorizontal: spacing.l, paddingVertical: spacing.s, }, + refreshRow: { + paddingHorizontal: spacing.l, + paddingBottom: spacing.s, + alignItems: "flex-start", + }, + refreshBtn: { + flexDirection: "row", + alignItems: "center", + gap: spacing.xs, + paddingHorizontal: spacing.m, + paddingVertical: spacing.s, + backgroundColor: colors.bgCard, + borderRadius: borderRadius.sm, + borderWidth: 1, + borderColor: colors.border, + }, + refreshBtnText: { + color: colors.textSecondary, + fontSize: fontSize.sm, + }, filterBtn: { paddingHorizontal: spacing.l, paddingVertical: spacing.s, @@ -620,6 +640,20 @@ export default function OrdersScreen() { style={styles.filterList} showsHorizontalScrollIndicator={false} /> + + + + Actualiser + + item.id.toString()} @@ -637,6 +671,7 @@ export default function OrdersScreen() { } /> + {/* Modal items */} { - if ((settings.points_categories_weed ?? []).includes(name)) return "weed"; - if ((settings.points_categories_zipette ?? []).includes(name)) return "zipette"; - if ((settings.points_categories_total ?? []).includes(name)) return "total"; - return "none"; + // Retourne l'index du pool auquel la catégorie est assignée, ou -1 si aucun + const getPoolIndexFor = (catName: string): number => { + const pools = settings.points_pools ?? []; + for (let i = 0; i < pools.length; i++) { + if ((pools[i].categories ?? []).includes(catName)) return i; + } + return -1; }; - const setPool = (name: string, pool: PoolAssignment) => { + // Assigne une catégorie à un pool (ou la retire si poolIdx === -1) + const setCategoryPool = (catName: string, poolIdx: number) => { setSettings((prev) => { - const weed = (prev.points_categories_weed ?? []).filter((c) => c !== name); - const zipette = (prev.points_categories_zipette ?? []).filter((c) => c !== name); - const total = (prev.points_categories_total ?? []).filter((c) => c !== name); - if (pool === "weed") weed.push(name); - else if (pool === "zipette") zipette.push(name); - else if (pool === "total") total.push(name); - return { ...prev, points_categories_weed: weed, points_categories_zipette: zipette, points_categories_total: total }; + const pools = (prev.points_pools ?? []).map((pool, i) => ({ + ...pool, + categories: (pool.categories ?? []).filter((c) => c !== catName), + })); + if (poolIdx >= 0 && poolIdx < pools.length) { + pools[poolIdx] = { + ...pools[poolIdx], + categories: [...(pools[poolIdx].categories ?? []), catName], + }; + } + return { ...prev, points_pools: pools }; + }); + }; + + const addPool = () => { + setSettings((prev) => { + const pools = prev.points_pools ?? []; + const newKey = `pool_${Date.now()}`; + return { + ...prev, + points_pools: [...pools, { key: newKey, name: `Type ${pools.length + 1}`, categories: [], tiers: [] }], + }; + }); + }; + + const removePool = (poolIdx: number) => { + setSettings((prev) => { + const pools = (prev.points_pools ?? []).filter((_, i) => i !== poolIdx); + return { ...prev, points_pools: pools }; + }); + }; + + const renamePool = (poolIdx: number, name: string) => { + setSettings((prev) => { + const pools = (prev.points_pools ?? []).map((p, i) => + i === poolIdx ? { ...p, name } : p + ); + return { ...prev, points_pools: pools }; + }); + }; + + const updatePoolTiers = (poolIdx: number, tiers: PointsTier[]) => { + setSettings((prev) => { + const pools = (prev.points_pools ?? []).map((p, i) => + i === poolIdx ? { ...p, tiers } : p + ); + return { ...prev, points_pools: pools }; }); }; @@ -842,12 +879,7 @@ export default function SettingsScreen() { ); } - const WEED_COLOR = "#10b981"; - const ZIP_COLOR = "#9333ea"; - const TOTAL_COLOR = "#f97316"; - - // Tous les chips toujours disponibles — la config reste modifiable à tout moment - const allChips: PoolAssignment[] = ["weed", "zipette", "total", "none"]; + const pools = settings.points_pools ?? []; return ( @@ -918,7 +950,7 @@ export default function SettingsScreen() { Points activés - Les clients voient leurs scores de points et point_zipette.{"\n"} + Les clients voient leurs scores de points.{"\n"} La cabine peut réinitialiser les points. @@ -931,22 +963,42 @@ export default function SettingsScreen() { thumbColor="#fff" /> - - - Points séparés par pool - - Activé : barèmes Weed + Zipette utilisés séparément{"\n"} - Désactivé : barème Total utilisé (catégorie T) - - - - setSettings((prev) => ({ ...prev, points_separated: v })) - } - trackColor={{ false: colors.border, true: colors.accent }} - thumbColor="#fff" - /> + + {/* Gestion des types de points */} + + Types de points + + Créez vos propres types de points. Le 1er type → colonne principale, le 2e → colonne secondaire. + + {pools.map((pool, i) => { + const color = POOL_COLORS[i % POOL_COLORS.length]; + return ( + + + renamePool(i, v)} + placeholder={`Type ${i + 1}`} + placeholderTextColor={colors.textMuted} + /> + removePool(i)}> + + + + ); + })} + {pools.length < 2 && ( + + + + Ajouter un type + + + )} @@ -954,26 +1006,18 @@ export default function SettingsScreen() { Attribution des catégories aux points - {settings.points_separated - ? "Mode séparé actif : W et Z utilisés pour le calcul. T ignoré." - : "Mode non-séparé actif : T utilisé pour le calcul. W et Z ignorés."} - {"\n"}Vous pouvez modifier les attributions à tout moment. + Choisissez quel type de point est attribué pour chaque catégorie de produit.{"\n"} + Vous pouvez modifier les attributions à tout moment. {/* Légende */} - - - Weed - - - - Zipette - - - - Total - + {pools.map((pool, i) => ( + + + {pool.name} + + ))} Aucun @@ -981,7 +1025,7 @@ export default function SettingsScreen() { {categories.map((cat, index) => { - const pool = getPoolFor(cat.name); + const assignedPoolIdx = getPoolIndexFor(cat.name); return ( {cat.name} - {allChips.map((p) => { - const active = pool === p; - const chipColor = - p === "weed" - ? WEED_COLOR - : p === "zipette" - ? ZIP_COLOR - : p === "total" - ? TOTAL_COLOR - : colors.textMuted; - const label = - p === "weed" - ? "W" - : p === "zipette" - ? "Z" - : p === "total" - ? "T" - : "—"; - return ( - setPool(cat.name, p)} - > - - {label} - - - ); - })} + {pools.map((pool, pi) => { + const active = assignedPoolIdx === pi; + const chipColor = POOL_COLORS[pi % POOL_COLORS.length]; + const label = pool.name.slice(0, 2).toUpperCase(); + return ( + setCategoryPool(cat.name, active ? -1 : pi)} + > + + {label} + + + ); + })} + {/* Chip "Aucun" */} + setCategoryPool(cat.name, -1)} + > + + — + + ); @@ -1052,36 +1086,26 @@ export default function SettingsScreen() { Aucune catégorie disponible )} + {pools.length === 0 && categories.length > 0 && ( + + Ajoutez au moins un type de point pour assigner des catégories. + + )} - {/* Barèmes de points — toujours présents */} - setSettings((p) => ({ ...p, points_weed_tiers: tiers }))} - colors={colors} - s={s} - accentColor={WEED_COLOR} - active={settings.points_separated} - /> - setSettings((p) => ({ ...p, points_zipette_tiers: tiers }))} - colors={colors} - s={s} - accentColor={ZIP_COLOR} - active={settings.points_separated} - /> - setSettings((p) => ({ ...p, points_total_tiers: tiers }))} - colors={colors} - s={s} - accentColor={TOTAL_COLOR} - active={!settings.points_separated} - /> + {/* Barèmes de points — un par pool */} + {pools.map((pool, i) => ( + updatePoolTiers(i, tiers)} + colors={colors} + s={s} + accentColor={POOL_COLORS[i % POOL_COLORS.length]} + active={true} + /> + ))} {/* Horaires de livraison */} StyleSheet.create({ container: { flex: 1, backgroundColor: colors.bgPrimary }, + refreshRow: { + paddingHorizontal: spacing.l, + paddingTop: spacing.m, + paddingBottom: spacing.s, + alignItems: "flex-start", + }, + refreshBtn: { + flexDirection: "row", + alignItems: "center", + gap: spacing.xs, + paddingHorizontal: spacing.m, + paddingVertical: spacing.s, + backgroundColor: colors.bgCard, + borderRadius: borderRadius.sm, + borderWidth: 1, + borderColor: colors.border, + }, + refreshBtnText: { + color: colors.textSecondary, + fontSize: fontSize.sm, + }, row: { flexDirection: "row", justifyContent: "space-between", @@ -525,6 +546,20 @@ export default function OrdersScreen() { return ( + + + + Actualiser + + item.id.toString()} diff --git a/frontend-admin/src/screens/delivery/DashboardScreen.tsx b/frontend-admin/src/screens/delivery/DashboardScreen.tsx index b98c5099..c967483c 100644 --- a/frontend-admin/src/screens/delivery/DashboardScreen.tsx +++ b/frontend-admin/src/screens/delivery/DashboardScreen.tsx @@ -1671,12 +1671,6 @@ export default function DashboardScreen() { /> {" "}Client - - Pseudo - - {detailsDelivery?.clientUsername || "—"} - - Prénom diff --git a/mobile/src/api/api.ts b/mobile/src/api/api.ts index 22ebb687..bc940037 100644 --- a/mobile/src/api/api.ts +++ b/mobile/src/api/api.ts @@ -634,6 +634,7 @@ export const getCommandItemsWithDetails = async (commandId: number) => { export const formatOrderDate = (dateString: string): string => { try { const date = new Date(dateString); + if (isNaN(date.getTime())) return ""; return date.toLocaleDateString("fr-FR", { day: "2-digit", month: "long", @@ -642,7 +643,7 @@ export const formatOrderDate = (dateString: string): string => { minute: "2-digit", }); } catch { - return dateString; + return ""; } }; diff --git a/mobile/src/screens/client/OrderTrackingScreen.tsx b/mobile/src/screens/client/OrderTrackingScreen.tsx index 4a652f1d..32d5a3de 100644 --- a/mobile/src/screens/client/OrderTrackingScreen.tsx +++ b/mobile/src/screens/client/OrderTrackingScreen.tsx @@ -129,11 +129,12 @@ export default function OrderTrackingScreen() { try { const res = await confirmReception(orderId); if (res.success) { - const cat = res.category || ''; - let catLabel = ''; - if (cat === 'total') catLabel = ' (Total)'; - else if (cat.includes('zipette')) catLabel = ' (Zipette&Co)'; - else if (cat.includes('weed') || cat.includes('hash')) catLabel = ' (Weed&Hash)'; + const cat = res.category || ""; + let catLabel = ""; + if (cat === "total") catLabel = " (Total)"; + else if (cat.includes("zipette")) catLabel = " (Zipette&Co)"; + else if (cat.includes("weed") || cat.includes("hash")) + catLabel = " (Weed&Hash)"; showToast( `Livraison confirmee ! +${res.points_earned || 0} points${catLabel}`, "success", @@ -456,46 +457,71 @@ export default function OrderTrackingScreen() { color={colors.textMuted} /> - {order.address_proposal_status === "pending" && order.proposed_address && ( - - - 📍 Nouvelle adresse proposée - - - {order.proposed_address} - - -