chore: build
This commit is contained in:
@@ -1161,13 +1161,22 @@ function PromotionProductPicker({
|
||||
});
|
||||
};
|
||||
|
||||
const updateProductQuantity = (id: number, quantity: number) => {
|
||||
onChange({
|
||||
...catConfig,
|
||||
products: catConfig.products.map((pq) =>
|
||||
pq.product_id === id ? { ...pq, quantity } : pq
|
||||
),
|
||||
});
|
||||
// Chaque produit peut être en promo sur plusieurs paliers de quantité en
|
||||
// même temps (ex: 1g ET 3g) — on ajoute/retire l'entrée {product_id,
|
||||
// quantity} correspondante plutôt que de remplacer une quantité unique.
|
||||
const toggleProductQuantity = (id: number, quantity: number) => {
|
||||
const exists = catConfig.products.some((pq) => pq.product_id === id && pq.quantity === quantity);
|
||||
if (exists) {
|
||||
onChange({
|
||||
...catConfig,
|
||||
products: catConfig.products.filter((pq) => !(pq.product_id === id && pq.quantity === quantity)),
|
||||
});
|
||||
} else {
|
||||
onChange({
|
||||
...catConfig,
|
||||
products: [...catConfig.products, { product_id: id, quantity }],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -1227,8 +1236,9 @@ function PromotionProductPicker({
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Mode "Sélection" : chaque produit choisi a sa propre quantité,
|
||||
via les paliers de prix réels du produit (pas de saisie libre) */}
|
||||
{/* Mode "Sélection" : chaque produit choisi peut être en promo sur
|
||||
plusieurs paliers de quantité à la fois, via les paliers de
|
||||
prix réels du produit (pas de saisie libre) */}
|
||||
{!catConfig.all_products && (
|
||||
<View style={{ gap: spacing.xs }}>
|
||||
{catProducts.length === 0 ? (
|
||||
@@ -1263,13 +1273,16 @@ function PromotionProductPicker({
|
||||
|
||||
{catConfig.products.length > 0 && (
|
||||
<View style={{ gap: spacing.s, marginTop: spacing.xs }}>
|
||||
{catConfig.products.map((pq) => {
|
||||
const prod = catProducts.find((p) => p.id === pq.product_id);
|
||||
{Array.from(new Set(catConfig.products.map((pq) => pq.product_id))).map((productId) => {
|
||||
const prod = catProducts.find((p) => p.id === productId);
|
||||
const tiers = (prod?.prices ?? []).filter((pr) => pr.active_price !== false);
|
||||
const selectedQuantities = catConfig.products
|
||||
.filter((pq) => pq.product_id === productId)
|
||||
.map((pq) => pq.quantity);
|
||||
return (
|
||||
<View key={pq.product_id} style={{ gap: 4 }}>
|
||||
<View key={productId} style={{ gap: 4 }}>
|
||||
<Text style={{ fontSize: 12, color: colors.textMuted }} numberOfLines={1}>
|
||||
{prod?.name ?? `Produit #${pq.product_id}`}
|
||||
{prod?.name ?? `Produit #${productId}`}
|
||||
</Text>
|
||||
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 4 }}>
|
||||
{tiers.length === 0 ? (
|
||||
@@ -1277,11 +1290,11 @@ function PromotionProductPicker({
|
||||
Aucun palier de prix actif pour ce produit
|
||||
</Text>
|
||||
) : tiers.map((tier) => {
|
||||
const isSel = pq.quantity === tier.quantity;
|
||||
const isSel = selectedQuantities.includes(tier.quantity);
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={tier.quantity}
|
||||
onPress={() => updateProductQuantity(pq.product_id, tier.quantity)}
|
||||
onPress={() => toggleProductQuantity(productId, tier.quantity)}
|
||||
style={{
|
||||
paddingHorizontal: spacing.s, paddingVertical: 3,
|
||||
borderRadius: borderRadius.sm, borderWidth: 1.5,
|
||||
|
||||
@@ -122,6 +122,13 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.product-price-strike {
|
||||
color: var(--text-muted);
|
||||
text-decoration: line-through;
|
||||
font-size: 0.75em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.product-stock {
|
||||
color: var(--text-muted);
|
||||
font-size: clamp(0.85rem, 2.5vw, 1rem);
|
||||
|
||||
@@ -28,7 +28,13 @@ interface ProductCardProps {
|
||||
image: string;
|
||||
stock: number;
|
||||
category: string;
|
||||
prices?: Array<{ quantity: number; price: number; active_price?: boolean }>;
|
||||
prices?: Array<{
|
||||
quantity: number;
|
||||
price: number;
|
||||
active_price?: boolean;
|
||||
promo_price?: number | null;
|
||||
promo_percent?: number;
|
||||
}>;
|
||||
hasVideo?: boolean;
|
||||
videoUrl?: string; // ✨ Nouveau prop pour l'URL de la vidéo
|
||||
categoryColor?: string;
|
||||
@@ -63,6 +69,11 @@ function ProductCard({
|
||||
const isOutOfStock = stock === 0;
|
||||
const isComingSoon = coming_soon === true;
|
||||
const normalizedCategory = (category || "autre").toLowerCase().trim();
|
||||
const firstPromoPrice =
|
||||
prices?.[0]?.promo_price != null &&
|
||||
prices[0].promo_price < prices[0].price
|
||||
? prices[0].promo_price
|
||||
: null;
|
||||
|
||||
const handleDetailsClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
@@ -171,9 +182,20 @@ function ProductCard({
|
||||
<div className="product-info">
|
||||
<h3 className="product-name">{name}</h3>
|
||||
<p className="product-price">
|
||||
{price > 0
|
||||
? `${price.toFixed(2)} €`
|
||||
: "Prix non disponible"}
|
||||
{price > 0 ? (
|
||||
firstPromoPrice !== null ? (
|
||||
<>
|
||||
<span className="product-price-strike">
|
||||
{price.toFixed(2)} €
|
||||
</span>{" "}
|
||||
{firstPromoPrice.toFixed(2)} €
|
||||
</>
|
||||
) : (
|
||||
`${price.toFixed(2)} €`
|
||||
)
|
||||
) : (
|
||||
"Prix non disponible"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -224,9 +246,11 @@ function ProductCard({
|
||||
key={priceOption.quantity}
|
||||
value={priceOption.quantity}
|
||||
>
|
||||
{priceOption.quantity}
|
||||
{unit} - {priceOption.price.toFixed(2)}{" "}
|
||||
€
|
||||
{priceOption.promo_price != null &&
|
||||
priceOption.promo_price <
|
||||
priceOption.price
|
||||
? `${priceOption.quantity}${unit} - ${priceOption.promo_price.toFixed(2)} € (au lieu de ${priceOption.price.toFixed(2)} €, -${priceOption.promo_percent}%)`
|
||||
: `${priceOption.quantity}${unit} - ${priceOption.price.toFixed(2)} €`}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
@@ -47,6 +47,8 @@ interface ProductCardProps {
|
||||
quantity: number;
|
||||
price: number;
|
||||
active_price?: boolean;
|
||||
promo_price?: number | null;
|
||||
promo_percent?: number;
|
||||
}>;
|
||||
media?: Array<{ url: string; type: string }>;
|
||||
};
|
||||
@@ -67,6 +69,11 @@ export default function ProductCard({
|
||||
const activePrices =
|
||||
product.prices?.filter((p) => p.active_price !== false) ?? [];
|
||||
const firstPrice = activePrices[0]?.price ?? null;
|
||||
const firstPromoPrice =
|
||||
activePrices[0]?.promo_price != null &&
|
||||
activePrices[0].promo_price < activePrices[0].price
|
||||
? activePrices[0].promo_price
|
||||
: null;
|
||||
|
||||
const imageMedia = product.media?.find((m) => m.type === "image");
|
||||
const videoMedia = product.media?.find((m) => m.type === "video");
|
||||
@@ -198,11 +205,33 @@ export default function ProductCard({
|
||||
>
|
||||
{product.name}
|
||||
</Text>
|
||||
<Text style={[styles.price, { color: colors.success }]}>
|
||||
{firstPrice !== null
|
||||
? `${firstPrice.toFixed(2)} €`
|
||||
: "Prix non disponible"}
|
||||
</Text>
|
||||
{firstPrice !== null ? (
|
||||
firstPromoPrice !== null ? (
|
||||
<View style={styles.priceRow}>
|
||||
<Text
|
||||
style={[
|
||||
styles.priceStrike,
|
||||
{ color: colors.textMuted },
|
||||
]}
|
||||
>
|
||||
{firstPrice.toFixed(2)} €
|
||||
</Text>
|
||||
<Text
|
||||
style={[styles.price, { color: colors.success }]}
|
||||
>
|
||||
{firstPromoPrice.toFixed(2)} €
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<Text style={[styles.price, { color: colors.success }]}>
|
||||
{firstPrice.toFixed(2)} €
|
||||
</Text>
|
||||
)
|
||||
) : (
|
||||
<Text style={[styles.price, { color: colors.success }]}>
|
||||
Prix non disponible
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View
|
||||
@@ -317,14 +346,39 @@ export default function ProductCard({
|
||||
{p.quantity}
|
||||
{product.unit || "g"}
|
||||
</Text>
|
||||
<Text
|
||||
style={[
|
||||
styles.pickerOptionPrice,
|
||||
{ color: catColor },
|
||||
]}
|
||||
>
|
||||
{p.price.toFixed(2)} €
|
||||
</Text>
|
||||
{p.promo_price != null &&
|
||||
p.promo_price < p.price ? (
|
||||
<View style={styles.pickerPriceRow}>
|
||||
<Text
|
||||
style={[
|
||||
styles.priceStrike,
|
||||
{ color: colors.textMuted },
|
||||
]}
|
||||
>
|
||||
{p.price.toFixed(2)} €
|
||||
</Text>
|
||||
<Text
|
||||
style={[
|
||||
styles.pickerOptionPrice,
|
||||
{ color: catColor },
|
||||
]}
|
||||
>
|
||||
{p.promo_price.toFixed(2)} €
|
||||
{p.promo_percent
|
||||
? ` (-${p.promo_percent}%)`
|
||||
: ""}
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<Text
|
||||
style={[
|
||||
styles.pickerOptionPrice,
|
||||
{ color: catColor },
|
||||
]}
|
||||
>
|
||||
{p.price.toFixed(2)} €
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
<Ionicons
|
||||
name="add-circle"
|
||||
@@ -515,6 +569,21 @@ const styles = StyleSheet.create({
|
||||
fontWeight: fontWeight.bold,
|
||||
textAlign: "center",
|
||||
},
|
||||
priceRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: spacing.xs,
|
||||
},
|
||||
pickerPriceRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: spacing.xs,
|
||||
},
|
||||
priceStrike: {
|
||||
fontSize: fontSize.md,
|
||||
textDecorationLine: "line-through",
|
||||
},
|
||||
quickAddSection: { padding: spacing.m, borderTopWidth: 1 },
|
||||
quickAddBtn: {
|
||||
width: "100%",
|
||||
|
||||
Reference in New Issue
Block a user