diff --git a/frontend-admin/src/api/api_admin.ts b/frontend-admin/src/api/api_admin.ts index c442501c..89e8e2da 100644 --- a/frontend-admin/src/api/api_admin.ts +++ b/frontend-admin/src/api/api_admin.ts @@ -1112,19 +1112,13 @@ export interface RewardCategoryConfig { type: "free_product" | "half_price_product"; all_products: boolean; product_ids: number[]; -} - -export interface RewardItem { - product_id: number; - quantity: number; - price: number; + quantity: number; // quantité offerte / à -50% (palier de prix catalogue, ex: 1g) } export interface PointsReward { threshold: number; description: string; category_configs: RewardCategoryConfig[]; - reward_items: RewardItem[]; } export interface PointsPool { diff --git a/frontend-admin/src/screens/admin/SettingsScreen.tsx b/frontend-admin/src/screens/admin/SettingsScreen.tsx index 4a35eb79..431d144b 100644 --- a/frontend-admin/src/screens/admin/SettingsScreen.tsx +++ b/frontend-admin/src/screens/admin/SettingsScreen.tsx @@ -17,7 +17,7 @@ import { Ionicons } from "@expo/vector-icons"; import { spacing, fontSize, borderRadius } from "../../theme"; import { useTheme } from "../../context/ThemeContext"; import { getSettings, updateSettings, getCategories, getAvailableDeliveryPersons, getAllProductsAdmin, DEFAULT_DELIVERY_SCHEDULE, DEFAULT_POSTAL_ZONES } from "../../api/api_admin"; -import type { AppSettings, Category, CategoryRoute, DeliveryModeConfig, PointsTier, PointsPool, PointsReward, RewardCategoryConfig, RewardItem, DaySchedule, DeliverySchedule, PostalZone } from "../../api/api_admin"; +import type { AppSettings, Category, CategoryRoute, DeliveryModeConfig, PointsTier, PointsPool, PointsReward, RewardCategoryConfig, DaySchedule, DeliverySchedule, PostalZone } from "../../api/api_admin"; import type { Product } from "../../api/types"; import AlertModal from "../../components/ui/AlertModal"; import { useAlert } from "../../hooks/useAlert"; @@ -695,7 +695,6 @@ const EMPTY_REWARD: PointsReward = { threshold: 20, description: "", category_configs: [], - reward_items: [], }; // ────────────────────────────────────────────────────────────── @@ -725,6 +724,27 @@ function CategoryProductPicker({ return ( + {/* Quantité offerte / à -50% — correspond à un palier de prix du + catalogue (ex: si le produit a un palier quantity=1 à 30€, + indiquer 1 ici donne "30€ offert" ou "15€ à -50%"). */} + + Quantité : + 0 ? String(catConfig.quantity) : ""} + onChangeText={(v) => { + const n = parseFloat(v); + onChange({ ...catConfig, quantity: isNaN(n) ? 0 : n }); + }} + placeholder="1" + placeholderTextColor={colors.textMuted} + /> + + doit correspondre à un palier de prix existant + + + {/* Toggle tous / sélection */} r.category_configs.find((c) => c.category === catName && c.type === type) ?? - { category: catName, type, all_products: true, product_ids: [] }; + { category: catName, type, all_products: true, product_ids: [], quantity: 1 }; const isTypeEnabled = (catName: string, type: RewardCategoryConfig["type"]) => r.category_configs.some((c) => c.category === catName && c.type === type); @@ -836,7 +856,7 @@ function CentralRewardSection({ if (isTypeEnabled(catName, type)) { update({ category_configs: r.category_configs.filter((c) => !(c.category === catName && c.type === type)) }); } else { - update({ category_configs: [...r.category_configs, { category: catName, type, all_products: true, product_ids: [] }] }); + update({ category_configs: [...r.category_configs, { category: catName, type, all_products: true, product_ids: [], quantity: 1 }] }); } }; @@ -921,134 +941,6 @@ function CentralRewardSection({ /> - {/* Produits récompense — proposés au client selon le type choisi - pour la catégorie de chaque produit (voir "Catégories éligibles" ci-dessous) */} - - Produits ajoutés au panier - - Quand le client réclame sa récompense, ces produits sont automatiquement ajoutés à son panier — gratuits ou à -50% selon le type configuré pour la catégorie du produit. Il doit commander au moins un produit normal. - - - {r.reward_items.map((item, idx) => { - const allProds = Object.values(productsByCategory).flat(); - const prod = allProds.find((p) => p.id === item.product_id); - return ( - - {/* Sélecteur produit */} - - {allProds.map((p) => { - const sel = item.product_id === p.id; - return ( - { - const updated = r.reward_items.map((it, i) => - i === idx ? { ...it, product_id: p.id } : it - ); - update({ reward_items: updated }); - }} - style={{ - flexDirection: "row", alignItems: "center", gap: 4, - paddingHorizontal: spacing.s, paddingVertical: 4, - borderRadius: borderRadius.sm, borderWidth: 1.5, - borderColor: sel ? REWARD_ACCENT : colors.border, - backgroundColor: sel ? REWARD_ACCENT + "22" : "transparent", - }} - > - {sel && } - - {p.name} ({p.category}) - - - ); - })} - - - {/* Quantité + Prix + Supprimer */} - - - Qté : - 0 ? String(item.quantity) : ""} - onChangeText={(v) => { - const n = parseFloat(v); - const updated = r.reward_items.map((it, i) => - i === idx ? { ...it, quantity: isNaN(n) ? 0 : n } : it - ); - update({ reward_items: updated }); - }} - placeholder="1" - placeholderTextColor={colors.textMuted} - /> - - - Prix : - 0 ? String(item.price) : ""} - onChangeText={(v) => { - const n = parseFloat(v); - const updated = r.reward_items.map((it, i) => - i === idx ? { ...it, price: isNaN(n) ? 0 : n } : it - ); - update({ reward_items: updated }); - }} - placeholder="0" - placeholderTextColor={colors.textMuted} - /> - - - update({ reward_items: r.reward_items.filter((_, i) => i !== idx) })} - style={{ padding: spacing.xs }} - > - - - - - {prod && ( - - {prod.name}{item.quantity > 0 ? ` · x${item.quantity}` : ""}{item.price > 0 ? ` · ${item.price}€` : ""} - - )} - {!prod && ( - - Sélectionnez un produit ci-dessus - - )} - - ); - })} - - {Object.values(productsByCategory).flat().length === 0 ? ( - Aucun produit disponible - ) : ( - update({ reward_items: [...r.reward_items, { product_id: 0, quantity: 1, price: 0 }] })} - style={{ - flexDirection: "row", alignItems: "center", justifyContent: "center", gap: spacing.xs, - paddingHorizontal: spacing.m, paddingVertical: spacing.s, - borderRadius: borderRadius.sm, borderWidth: 1.5, - borderColor: REWARD_ACCENT + "66", - }} - > - - Ajouter un produit récompense - - )} - - - {/* Catégories éligibles — chaque catégorie choisit son propre type (produit offert ou -50%), qui s'applique aux produits récompense de cette catégorie configurés ci-dessus */} @@ -1140,7 +1032,7 @@ function CentralRewardSection({ {/* Récapitulatif */} - {(r.category_configs.length > 0 || r.reward_items.filter((it) => it.product_id > 0).length > 0) && ( + {r.category_configs.length > 0 && ( Récapitulatif @@ -1151,20 +1043,9 @@ function CentralRewardSection({ )} {r.category_configs.map((cfg, idx) => ( - • {REWARD_TYPES.find((x) => x.value === (cfg.type || "free_product"))?.label} : {cfg.category} — {cfg.all_products ? "tous les produits" : `${cfg.product_ids.length} produit(s)`} + • {REWARD_TYPES.find((x) => x.value === (cfg.type || "free_product"))?.label} : {cfg.category} — {cfg.all_products ? "tous les produits" : `${cfg.product_ids.length} produit(s)`} · qté {cfg.quantity > 0 ? cfg.quantity : 1} ))} - {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}€` : ""} - - - ); - })} )} @@ -1299,7 +1180,7 @@ export default function SettingsScreen() { category_routes: s.delivery_mode?.category_routes ?? [], }, points_reward: s.points_reward - ? { ...s.points_reward, category_configs: s.points_reward.category_configs ?? [], reward_items: s.points_reward.reward_items ?? [] } + ? { ...s.points_reward, category_configs: s.points_reward.category_configs ?? [] } : null, }); } diff --git a/mobile/src/api/api.ts b/mobile/src/api/api.ts index 58afaa19..6380eac1 100644 --- a/mobile/src/api/api.ts +++ b/mobile/src/api/api.ts @@ -967,7 +967,7 @@ export type RewardCategoryConfig = { all_products: boolean; product_ids: number[]; product_names: string[]; - amount: number; + quantity: number; }; export type RewardItemConfig = {