chore: build
Frontend Admin - EAS Build / build (push) Canceled after 1h38m52s

This commit is contained in:
Xor290
2026-08-25 18:47:33 +02:00
parent de075a8fe3
commit f469e52c32
2 changed files with 61 additions and 39 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
"expo": { "expo": {
"name": "Admin Panel", "name": "Admin Panel",
"slug": "frontend-admin", "slug": "frontend-admin",
"version": "1.0.0", "version": "1.0.1",
"orientation": "portrait", "orientation": "portrait",
"icon": "./assets/icon.png", "icon": "./assets/icon.png",
"userInterfaceStyle": "dark", "userInterfaceStyle": "dark",
@@ -819,29 +819,44 @@ function CentralRewardSection({
const update = (patch: Partial<PointsReward>) => const update = (patch: Partial<PointsReward>) =>
onChange({ ...r, ...patch }); onChange({ ...r, ...patch });
const getCatConfig = (catName: string): RewardCategoryConfig => // Une catégorie peut avoir jusqu'à deux configs actives en parallèle
r.category_configs.find((c) => c.category === catName) ?? // (une "free_product" et une "half_price_product"), chacune avec sa
{ category: catName, type: "free_product", all_products: true, product_ids: [] }; // propre sélection de produits — d'où la recherche par (catégorie, type).
const getCatConfig = (catName: string, type: RewardCategoryConfig["type"]): RewardCategoryConfig =>
r.category_configs.find((c) => c.category === catName && c.type === type) ??
{ category: catName, type, all_products: true, product_ids: [] };
const isTypeEnabled = (catName: string, type: RewardCategoryConfig["type"]) =>
r.category_configs.some((c) => c.category === catName && c.type === type);
const isCatSelected = (catName: string) => const isCatSelected = (catName: string) =>
r.category_configs.some((c) => c.category === catName); r.category_configs.some((c) => c.category === catName);
const toggleCategory = (catName: string) => { const toggleCategoryType = (catName: string, type: RewardCategoryConfig["type"]) => {
if (isCatSelected(catName)) { if (isTypeEnabled(catName, type)) {
update({ category_configs: r.category_configs.filter((c) => c.category !== catName) }); update({ category_configs: r.category_configs.filter((c) => !(c.category === catName && c.type === type)) });
} else { } else {
update({ category_configs: [...r.category_configs, { category: catName, type: "free_product", all_products: true, product_ids: [] }] }); update({ category_configs: [...r.category_configs, { category: catName, type, all_products: true, product_ids: [] }] });
} }
}; };
const updateCatConfig = (cfg: RewardCategoryConfig) => { const updateCatConfig = (cfg: RewardCategoryConfig) => {
update({ update({
category_configs: r.category_configs.map((c) => category_configs: r.category_configs.map((c) =>
c.category === cfg.category ? cfg : c c.category === cfg.category && c.type === cfg.type ? cfg : c
), ),
}); });
}; };
const [expandedCats, setExpandedCats] = useState<Set<string>>(new Set());
const toggleExpanded = (catName: string) => {
setExpandedCats((prev) => {
const next = new Set(prev);
if (next.has(catName)) next.delete(catName); else next.add(catName);
return next;
});
};
const rewardBadge = ( const rewardBadge = (
<View style={{ <View style={{
paddingHorizontal: spacing.s, paddingVertical: 2, borderRadius: 10, paddingHorizontal: spacing.s, paddingVertical: 2, borderRadius: 10,
@@ -1040,7 +1055,7 @@ function CentralRewardSection({
<View> <View>
<Text style={[s.rowLabel, { marginBottom: spacing.xs }]}>Catégories éligibles</Text> <Text style={[s.rowLabel, { marginBottom: spacing.xs }]}>Catégories éligibles</Text>
<Text style={[s.rowDesc, { marginBottom: spacing.m }]}> <Text style={[s.rowDesc, { marginBottom: spacing.m }]}>
Sélectionnez les catégories, choisissez le type de récompense pour chacune, puis tous les produits ou une sélection. Sélectionnez une catégorie pour activer, indépendamment, un lot de produits offerts et/ou un lot de produits à -50%, chacun avec sa propre sélection de produits.
</Text> </Text>
{allCategories.length === 0 ? ( {allCategories.length === 0 ? (
<Text style={[s.hint, { paddingHorizontal: 0 }]}>Aucune catégorie disponible</Text> <Text style={[s.hint, { paddingHorizontal: 0 }]}>Aucune catégorie disponible</Text>
@@ -1048,12 +1063,13 @@ function CentralRewardSection({
<View style={{ gap: spacing.m }}> <View style={{ gap: spacing.m }}>
{allCategories.map((cat) => { {allCategories.map((cat) => {
const selected = isCatSelected(cat.name); const selected = isCatSelected(cat.name);
const expanded = expandedCats.has(cat.name);
const catColor = cat.color || REWARD_ACCENT; const catColor = cat.color || REWARD_ACCENT;
return ( return (
<View key={cat.name}> <View key={cat.name}>
{/* Chip catégorie */} {/* Chip catégorie (couleur = au moins un type actif, clic = déplier/replier) */}
<TouchableOpacity <TouchableOpacity
onPress={() => toggleCategory(cat.name)} onPress={() => toggleExpanded(cat.name)}
style={{ style={{
flexDirection: "row", alignItems: "center", gap: spacing.xs, flexDirection: "row", alignItems: "center", gap: spacing.xs,
alignSelf: "flex-start", alignSelf: "flex-start",
@@ -1068,46 +1084,52 @@ function CentralRewardSection({
{cat.name} {cat.name}
</Text> </Text>
<Ionicons <Ionicons
name={selected ? "chevron-down" : "chevron-forward"} name={expanded ? "chevron-down" : "chevron-forward"}
size={12} size={12}
color={selected ? catColor : colors.textMuted} color={selected ? catColor : colors.textMuted}
/> />
</TouchableOpacity> </TouchableOpacity>
{/* Type + sélecteur produits (visible si catégorie sélectionnée) */} {/* Les deux types de récompense, activables indépendamment */}
{selected && ( {expanded && (
<View style={{ marginTop: spacing.s, paddingLeft: spacing.m, gap: spacing.s }}> <View style={{ marginTop: spacing.s, paddingLeft: spacing.m, gap: spacing.m }}>
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: spacing.xs }}> {REWARD_TYPES.map((rt) => {
{REWARD_TYPES.map((rt) => { const typeEnabled = isTypeEnabled(cat.name, rt.value);
const cfg = getCatConfig(cat.name); return (
const sel = (cfg.type || "free_product") === rt.value; <View key={rt.value} style={{ gap: spacing.s }}>
return (
<TouchableOpacity <TouchableOpacity
key={rt.value} onPress={() => toggleCategoryType(cat.name, rt.value)}
onPress={() => updateCatConfig({ ...cfg, type: rt.value })}
style={{ style={{
flexDirection: "row", alignItems: "center", gap: 4, flexDirection: "row", alignItems: "center", gap: 4,
alignSelf: "flex-start",
paddingHorizontal: spacing.s, paddingVertical: 4, paddingHorizontal: spacing.s, paddingVertical: 4,
borderRadius: borderRadius.sm, borderWidth: 1.5, borderRadius: borderRadius.sm, borderWidth: 1.5,
borderColor: sel ? REWARD_ACCENT : colors.border, borderColor: typeEnabled ? REWARD_ACCENT : colors.border,
backgroundColor: sel ? REWARD_ACCENT + "22" : "transparent", backgroundColor: typeEnabled ? REWARD_ACCENT + "22" : "transparent",
}} }}
> >
<Ionicons name={rt.icon as any} size={12} color={sel ? REWARD_ACCENT : colors.textMuted} /> <Ionicons
<Text style={{ fontSize: 12, fontWeight: sel ? "700" : "400", color: sel ? REWARD_ACCENT : colors.textMuted }}> name={typeEnabled ? "checkbox" : "square-outline"}
size={14}
color={typeEnabled ? REWARD_ACCENT : colors.textMuted}
/>
<Ionicons name={rt.icon as any} size={12} color={typeEnabled ? REWARD_ACCENT : colors.textMuted} />
<Text style={{ fontSize: 12, fontWeight: typeEnabled ? "700" : "400", color: typeEnabled ? REWARD_ACCENT : colors.textMuted }}>
{rt.label} {rt.label}
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
); {typeEnabled && (
})} <CategoryProductPicker
</View> catConfig={getCatConfig(cat.name, rt.value)}
<CategoryProductPicker products={productsByCategory[cat.name] ?? []}
catConfig={getCatConfig(cat.name)} onChange={updateCatConfig}
products={productsByCategory[cat.name] ?? []} colors={colors}
onChange={updateCatConfig} s={s}
colors={colors} />
s={s} )}
/> </View>
);
})}
</View> </View>
)} )}
</View> </View>
@@ -1127,8 +1149,8 @@ function CentralRewardSection({
{r.description !== "" && ( {r.description !== "" && (
<Text style={{ fontSize: 12, color: colors.textMuted, fontStyle: "italic" }}>"{r.description}"</Text> <Text style={{ fontSize: 12, color: colors.textMuted, fontStyle: "italic" }}>"{r.description}"</Text>
)} )}
{r.category_configs.map((cfg) => ( {r.category_configs.map((cfg, idx) => (
<Text key={cfg.category} style={{ fontSize: 12, color: colors.textSecondary }}> <Text key={`${cfg.category}-${cfg.type}-${idx}`} style={{ fontSize: 12, color: colors.textSecondary }}>
{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)`}
</Text> </Text>
))} ))}