From 1a1d62a1c1a1f4cb828c2c0695caecdc36839c7d Mon Sep 17 00:00:00 2001 From: Xor290 Date: Sun, 14 Jun 2026 16:34:34 +0200 Subject: [PATCH] chore:build --- backend/gestion/handlers/points.go | 35 +++++++++--- backend/gestion/models/settings.go | 7 ++- frontend-admin/src/api/api_admin.ts | 1 - .../src/screens/admin/SettingsScreen.tsx | 53 +++++++------------ frontend-prep/src/api/api.ts | 8 +++ .../src/pages/User/ConsultationHistorique.tsx | 18 +++++-- mobile/src/api/api.ts | 8 +++ .../src/screens/client/OrderHistoryScreen.tsx | 18 +++++-- 8 files changed, 96 insertions(+), 52 deletions(-) diff --git a/backend/gestion/handlers/points.go b/backend/gestion/handlers/points.go index 9cd8ec16..526cbb8a 100644 --- a/backend/gestion/handlers/points.go +++ b/backend/gestion/handlers/points.go @@ -45,7 +45,6 @@ func GetMyPointsRewards(c *gin.Context) { AllProducts bool `json:"all_products"` ProductIDs []int `json:"product_ids"` ProductNames []string `json:"product_names"` - Amount float64 `json:"amount"` } type PoolInfo struct { @@ -66,6 +65,11 @@ func GetMyPointsRewards(c *gin.Context) { allProductIDs = append(allProductIDs, cfg.ProductIDs...) } } + for _, item := range reward.RewardItems { + if item.ProductID > 0 { + allProductIDs = append(allProductIDs, item.ProductID) + } + } } productNames, _ := database.GetProductNamesByIDs(allProductIDs) @@ -105,7 +109,6 @@ func GetMyPointsRewards(c *gin.Context) { AllProducts: cfg.AllProducts, ProductIDs: cfg.ProductIDs, ProductNames: names, - Amount: cfg.Amount, }) } } @@ -121,13 +124,33 @@ func GetMyPointsRewards(c *gin.Context) { }) } - // Retourner la récompense sans category_configs (les configs sont par pool) + // Construire la liste des produits récompense avec leurs noms + type RewardItemResponse struct { + ProductID int `json:"product_id"` + ProductName string `json:"product_name"` + Quantity float64 `json:"quantity"` + Price float64 `json:"price"` + } var rewardMeta gin.H if reward != nil { + rewardItems := make([]RewardItemResponse, 0, len(reward.RewardItems)) + for _, item := range reward.RewardItems { + if item.ProductID <= 0 { + continue + } + name := productNames[item.ProductID] + rewardItems = append(rewardItems, RewardItemResponse{ + ProductID: item.ProductID, + ProductName: name, + Quantity: item.Quantity, + Price: item.Price, + }) + } rewardMeta = gin.H{ - "threshold": reward.Threshold, - "type": reward.Type, - "description": reward.Description, + "threshold": reward.Threshold, + "type": reward.Type, + "description": reward.Description, + "reward_items": rewardItems, } } diff --git a/backend/gestion/models/settings.go b/backend/gestion/models/settings.go index 1c1a6c70..90529d54 100644 --- a/backend/gestion/models/settings.go +++ b/backend/gestion/models/settings.go @@ -16,10 +16,9 @@ type PointsTier struct { // RewardCategoryConfig définit les produits éligibles dans une catégorie pour une récompense type RewardCategoryConfig struct { - Category string `json:"category"` // nom de la catégorie - AllProducts bool `json:"all_products"` // true = tous les produits de la catégorie - ProductIDs []int `json:"product_ids"` // IDs des produits éligibles si AllProducts = false - Amount float64 `json:"amount"` // valeur monétaire de la récompense pour cette catégorie (ex: 30.0) + Category string `json:"category"` // nom de la catégorie + AllProducts bool `json:"all_products"` // true = tous les produits de la catégorie + ProductIDs []int `json:"product_ids"` // IDs des produits éligibles si AllProducts = false } // RewardItem représente un produit offert lors d'une récompense, avec sa quantité et son prix associé diff --git a/frontend-admin/src/api/api_admin.ts b/frontend-admin/src/api/api_admin.ts index a1fc9490..e28093f4 100644 --- a/frontend-admin/src/api/api_admin.ts +++ b/frontend-admin/src/api/api_admin.ts @@ -921,7 +921,6 @@ export interface RewardCategoryConfig { category: string; all_products: boolean; product_ids: number[]; - amount: number; } export interface RewardItem { diff --git a/frontend-admin/src/screens/admin/SettingsScreen.tsx b/frontend-admin/src/screens/admin/SettingsScreen.tsx index 6d817555..b60e098c 100644 --- a/frontend-admin/src/screens/admin/SettingsScreen.tsx +++ b/frontend-admin/src/screens/admin/SettingsScreen.tsx @@ -688,7 +688,6 @@ const REWARD_ACCENT = "#f59e0b"; const REWARD_TYPES: { value: PointsReward["type"]; label: string; icon: string }[] = [ { value: "free_product", label: "Produit offert", icon: "gift-outline" }, { value: "half_price_product", label: "Produit à -50%", icon: "pricetag-outline" }, - { value: "custom", label: "Personnalisé", icon: "star-outline" }, ]; const EMPTY_REWARD: PointsReward = { @@ -726,27 +725,6 @@ function CategoryProductPicker({ return ( - {/* Montant pour cette catégorie */} - - - Valeur du produit offert - - - 0 ? String(catConfig.amount) : ""} - onChangeText={(v) => { - const n = parseFloat(v); - onChange({ ...catConfig, amount: isNaN(n) ? 0 : n }); - }} - placeholder="0" - placeholderTextColor={colors.textMuted} - /> - - - - {/* Toggle tous / sélection */} r.category_configs.find((c) => c.category === catName) ?? - { category: catName, all_products: true, product_ids: [], amount: 0 }; + { category: catName, all_products: true, product_ids: [] }; const isCatSelected = (catName: string) => r.category_configs.some((c) => c.category === catName); @@ -852,7 +830,7 @@ function CentralRewardSection({ if (isCatSelected(catName)) { update({ category_configs: r.category_configs.filter((c) => c.category !== catName) }); } else { - update({ category_configs: [...r.category_configs, { category: catName, all_products: true, product_ids: [], amount: 0 }] }); + update({ category_configs: [...r.category_configs, { category: catName, all_products: true, product_ids: [] }] }); } }; @@ -956,7 +934,8 @@ function CentralRewardSection({ /> - {/* Produits récompense */} + {/* Produits récompense — uniquement pour le type "Produit offert" */} + {r.type === "free_product" && ( Produits ajoutés au panier @@ -1082,10 +1061,12 @@ function CentralRewardSection({ )} + )} - {/* Catégories éligibles */} + {/* Catégories éligibles — uniquement pour le type "Produit à -50%" */} + {r.type === "half_price_product" && ( - Catégories éligibles + Catégories éligibles à -50% Sélectionnez les catégories, puis pour chacune choisissez tous les produits ou une sélection. @@ -1137,9 +1118,10 @@ function CentralRewardSection({ )} + )} {/* Récapitulatif */} - {(r.category_configs.length > 0 || r.reward_items.length > 0) && ( + {(r.category_configs.length > 0 || r.reward_items.filter((it) => it.product_id > 0).length > 0) && ( Récapitulatif @@ -1149,17 +1131,20 @@ function CentralRewardSection({ {r.description !== "" && ( "{r.description}" )} - {r.category_configs.map((cfg) => ( + {r.type === "half_price_product" && r.category_configs.map((cfg) => ( - • Éligible : {cfg.category}{cfg.amount > 0 ? ` (${cfg.amount}€)` : ""} — {cfg.all_products ? "tous les produits" : `${cfg.product_ids.length} produit(s)`} + • Éligible à -50% : {cfg.category} — {cfg.all_products ? "tous les produits" : `${cfg.product_ids.length} produit(s)`} ))} - {r.reward_items.filter((it) => it.product_id > 0).map((it, idx) => { + {r.type === "free_product" && r.reward_items.filter((it) => it.product_id > 0).map((it, idx) => { const prod = Object.values(productsByCategory).flat().find((p) => p.id === it.product_id); return ( - - 🎁 {prod?.name ?? `Produit #${it.product_id}`}{it.quantity > 0 ? ` · x${it.quantity}` : ""}{it.price > 0 ? ` · ${it.price}€` : ""} - + + + + {prod?.name ?? `Produit #${it.product_id}`}{it.quantity > 0 ? ` · x${it.quantity}` : ""}{it.price > 0 ? ` · ${it.price}€` : ""} + + ); })} diff --git a/frontend-prep/src/api/api.ts b/frontend-prep/src/api/api.ts index 62a48de1..63d18acc 100644 --- a/frontend-prep/src/api/api.ts +++ b/frontend-prep/src/api/api.ts @@ -2162,10 +2162,18 @@ export type PointsPoolInfo = { eligible_configs: RewardCategoryConfig[]; }; +export type RewardItemConfig = { + product_id: number; + product_name: string; + quantity: number; + price: number; +}; + export type PointsRewardConfig = { threshold: number; type: string; description: string; + reward_items: RewardItemConfig[]; }; export const getMyPointsRewards = async (): Promise<{ diff --git a/frontend-prep/src/pages/User/ConsultationHistorique.tsx b/frontend-prep/src/pages/User/ConsultationHistorique.tsx index ca1f30cf..f30ba7c7 100644 --- a/frontend-prep/src/pages/User/ConsultationHistorique.tsx +++ b/frontend-prep/src/pages/User/ConsultationHistorique.tsx @@ -13,7 +13,7 @@ import { getMyPointsRewards, claimMyReward, } from "../../api/api"; -import type { PublicSettings, PointsPoolInfo, PointsRewardConfig } from "../../api/api"; +import type { PublicSettings, PointsPoolInfo, PointsRewardConfig, RewardItemConfig } from "../../api/api"; import type { CompletedOrder, ClientStats, @@ -304,7 +304,19 @@ function ConsultationHistorique() { Récompenses

-

{pointsRewards.reward.description}

+ {pointsRewards.reward.description && ( +

{pointsRewards.reward.description}

+ )} + {(pointsRewards.reward.reward_items ?? []).filter((it: RewardItemConfig) => it.product_name).length > 0 && ( +
+ {(pointsRewards.reward.reward_items ?? []).map((it: RewardItemConfig, idx: number) => ( + + + {it.product_name}{it.quantity > 0 && it.quantity !== 1 ? ` ×${it.quantity}` : ""}{it.price > 0 ? ` — ${it.price}€` : ""} + + ))} +
+ )}
{pointsRewards.pools.map((pool) => { const threshold = pointsRewards.reward!.threshold; @@ -323,7 +335,7 @@ function ConsultationHistorique() { {pool.eligible_configs.flatMap((cfg) => cfg.all_products ? [ - {cfg.category}{cfg.amount > 0 ? ` — ${cfg.amount}€` : ""} + {cfg.category} ] : (cfg.product_names ?? []).map((name) => ( diff --git a/mobile/src/api/api.ts b/mobile/src/api/api.ts index 67921c6b..941313d7 100644 --- a/mobile/src/api/api.ts +++ b/mobile/src/api/api.ts @@ -936,10 +936,18 @@ export type PointsPoolInfo = { eligible_configs: RewardCategoryConfig[]; }; +export type RewardItemConfig = { + product_id: number; + product_name: string; + quantity: number; + price: number; +}; + export type PointsRewardConfig = { threshold: number; type: string; description: string; + reward_items: RewardItemConfig[]; }; export const getMyPointsRewards = async (): Promise<{ diff --git a/mobile/src/screens/client/OrderHistoryScreen.tsx b/mobile/src/screens/client/OrderHistoryScreen.tsx index 493c07e6..41daaf5d 100644 --- a/mobile/src/screens/client/OrderHistoryScreen.tsx +++ b/mobile/src/screens/client/OrderHistoryScreen.tsx @@ -20,7 +20,7 @@ import { formatOrderDate, formatPrice, } from "../../api/api"; -import type { PublicSettings, PointsPoolInfo, PointsRewardConfig } from "../../api/api"; +import type { PublicSettings, PointsPoolInfo, PointsRewardConfig, RewardItemConfig } from "../../api/api"; import type { CompletedOrder, ClientStats, @@ -482,6 +482,18 @@ export default function OrderHistoryScreen() { {pointsRewards.reward.description !== "" && ( {pointsRewards.reward.description} )} + {(pointsRewards.reward.reward_items ?? []).filter((it) => it.price > 0 || it.product_name).length > 0 && ( + + {(pointsRewards.reward.reward_items ?? []).map((it: RewardItemConfig, idx: number) => ( + + + + {it.product_name}{it.quantity > 0 && it.quantity !== 1 ? ` ×${it.quantity}` : ""}{it.price > 0 ? ` — ${it.price}€` : ""} + + + ))} + + )} {pointsRewards.pools.map((pool) => { const threshold = pointsRewards.reward!.threshold; const progress = Math.min(1, (pool.points % threshold) / threshold); @@ -499,9 +511,7 @@ export default function OrderHistoryScreen() { {pool.eligible_configs.flatMap((cfg) => cfg.all_products ? [ - - {cfg.category}{cfg.amount > 0 ? ` — ${cfg.amount}€` : ""} - + {cfg.category} ] : (cfg.product_names ?? []).map((name) => (