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) => (