feat: active & deactivate the price for product

This commit is contained in:
2026-05-14 16:20:59 +02:00
parent 0fe8a06bfa
commit e0c354d76c
4 changed files with 66 additions and 15 deletions
+4 -4
View File
@@ -69,8 +69,8 @@ func (d *Database) CreateProduct(product any) error {
p.SetUpdatedAt(result.UpdatedAt)
for i, price := range p.GetPrices() {
err := d.GDB.Exec(`INSERT INTO product_prices (product_id, quantity, price) VALUES (?, ?, ?)`,
result.ID, price.Quantity, price.Price).Error
err := d.GDB.Exec(`INSERT INTO product_prices (product_id, quantity, price, active_price) VALUES (?, ?, ?, ?)`,
result.ID, price.Quantity, price.Price, price.ActivePrice).Error
if err != nil {
log.Printf("❌ [DB CreateProduct] Erreur insertion prix[%d]: %v", i, err)
return fmt.Errorf("erreur insertion prix: %v", err)
@@ -190,8 +190,8 @@ func (d *Database) UpdateProduct(productID int, name, category, description, uni
d.GDB.Exec(`DELETE FROM product_prices WHERE product_id = ?`, productID)
for _, price := range prices {
if err := d.GDB.Exec(`INSERT INTO product_prices (product_id, quantity, price) VALUES (?, ?, ?)`,
productID, price.Quantity, price.Price).Error; err != nil {
if err := d.GDB.Exec(`INSERT INTO product_prices (product_id, quantity, price, active_price) VALUES (?, ?, ?, ?)`,
productID, price.Quantity, price.Price, price.ActivePrice).Error; err != nil {
log.Printf("❌ [UpdateProduct] Erreur prix: %v", err)
}
}
+14
View File
@@ -747,6 +747,20 @@ export const deleteProductMediaAdmin = async (
return { success: true, message: data.message };
};
export const activateProductPrice = async (priceId: number) => {
const { data } = await apiClient.post(
`${V2}/admin/protected/active/product/price/${priceId}`,
);
return { success: true, message: data.message };
};
export const deactivateProductPrice = async (priceId: number) => {
const { data } = await apiClient.post(
`${V2}/admin/protected/desactive/product/price/${priceId}`,
);
return { success: true, message: data.message };
};
// ============================================
// ADDRESSES
// ============================================
+1 -1
View File
@@ -102,7 +102,7 @@ export interface Product {
category: string;
stock: number;
unit: string;
prices?: Array<{ quantity: number; price: number }>;
prices?: Array<{ id?: number; quantity: number; price: number; active_price?: boolean }>;
media?: Array<{ url: string; type: string; id?: number; created_at?: string }>;
created_at?: string;
updated_at?: string;
@@ -26,6 +26,8 @@ import {
deleteProductAdmin,
uploadProductMediaAdmin,
deleteProductMediaAdmin,
activateProductPrice,
deactivateProductPrice,
getCategories,
} from "../../api/api_admin";
import type { Category } from "../../api/api_admin";
@@ -40,8 +42,10 @@ import { useAlert } from "../../hooks/useAlert";
// Types
// --------------------------------------------------
interface PriceRow {
id?: number;
quantity: string;
price: string;
active: boolean;
}
interface MediaItem {
id?: number;
@@ -82,7 +86,7 @@ const emptyForm = (firstCategory = ""): FormState => ({
description: "",
stock: "",
unit: "u",
prices: [{ quantity: "1", price: "" }],
prices: [{ quantity: "1", price: "", active: true }],
});
// ==================================================
@@ -162,10 +166,12 @@ export default function ProductsScreen() {
prices:
product.prices && product.prices.length > 0
? product.prices.map((p) => ({
id: p.id,
quantity: p.quantity.toString(),
price: p.price.toString(),
active: p.active_price ?? true,
}))
: [{ quantity: "1", price: "" }],
: [{ quantity: "1", price: "", active: true }],
});
setExistingMedia(
(product.media || []).map((m) => ({
@@ -191,8 +197,15 @@ export default function ProductsScreen() {
const addPriceRow = () =>
setForm((f) => ({
...f,
prices: [...f.prices, { quantity: "1", price: "" }],
prices: [...f.prices, { quantity: "1", price: "", active: true }],
}));
const togglePriceActive = (idx: number) =>
setForm((f) => {
const prices = [...f.prices];
prices[idx] = { ...prices[idx], active: !prices[idx].active };
return { ...f, prices };
});
const removePriceRow = (idx: number) =>
setForm((f) => ({
...f,
@@ -292,6 +305,7 @@ export default function ProductsScreen() {
const prices = form.prices.map((p) => ({
quantity: parseFloat(p.quantity),
price: parseFloat(p.price),
active_price: p.active,
}));
try {
@@ -329,11 +343,10 @@ export default function ProductsScreen() {
fd.append("stock", form.stock);
fd.append("unit", form.unit);
// ✅ FIX PRINCIPAL : Envoyer les prix au format que Go attend
// Backend attend : prices[0][quantity], prices[0][price], etc.
prices.forEach((p, i) => {
fd.append(`prices[${i}][quantity]`, String(p.quantity));
fd.append(`prices[${i}][price]`, String(p.price));
fd.append(`prices[${i}][active_price]`, p.active_price ? "true" : "false");
});
// Attacher les médias en attente
@@ -647,6 +660,7 @@ export default function ProductsScreen() {
paddingHorizontal: spacing.m,
},
removePriceBtn: { padding: spacing.s, marginBottom: 4 },
toggleActiveBtn: { padding: spacing.s, marginBottom: 4 },
// Media section
mediaSectionBox: { marginTop: spacing.m },
@@ -803,11 +817,24 @@ export default function ProductsScreen() {
)}
<Text style={styles.info}>Stock: {item.stock} {item.unit || "u"}</Text>
{item.prices && item.prices.length > 0 && (
<Text style={styles.info}>
{item.prices
.map((p) => `${p.quantity}${item.unit || "u"} = ${p.price}`)
.join(" | ")}
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 4, marginTop: 2 }}>
{item.prices.map((p, i) => (
<Text
key={i}
style={[
styles.info,
!p.active_price && {
textDecorationLine: "line-through",
color: colors.textMuted,
opacity: 0.5,
},
]}
>
{p.quantity}{item.unit || "u"} = {p.price}
{i < item.prices!.length - 1 ? " |" : ""}
</Text>
))}
</View>
)}
{item.description && (
<Text style={styles.desc} numberOfLines={2}>
@@ -1055,6 +1082,16 @@ export default function ProductsScreen() {
}
/>
</View>
<TouchableOpacity
style={styles.toggleActiveBtn}
onPress={() => togglePriceActive(idx)}
>
<Ionicons
name={p.active ? "checkmark-circle" : "close-circle"}
size={22}
color={p.active ? colors.success || "#22c55e" : colors.danger}
/>
</TouchableOpacity>
{form.prices.length > 1 && (
<TouchableOpacity
style={styles.removePriceBtn}