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