Ajout de Promotion et de Commande fonctionnel
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
|
||||
const STORAGE_KEY = 'librairie-orders'
|
||||
|
||||
function loadOrders() {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY)
|
||||
if (raw === null) return []
|
||||
const parsed = JSON.parse(raw)
|
||||
return Array.isArray(parsed) ? parsed : []
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function saveOrders(orders) {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(orders))
|
||||
}
|
||||
|
||||
function round2(n) {
|
||||
return Math.round((Number(n) + Number.EPSILON) * 100) / 100
|
||||
}
|
||||
|
||||
function calcSubtotal(items) {
|
||||
return round2(
|
||||
items.reduce((sum, it) => sum + Number(it.unitPrice) * Number(it.qty), 0),
|
||||
)
|
||||
}
|
||||
|
||||
function calcDiscount({ subtotal, promotion }) {
|
||||
if (!promotion) return 0
|
||||
if (promotion.type === 'percent') {
|
||||
const pct = Number(promotion.value)
|
||||
if (!Number.isFinite(pct) || pct <= 0) return 0
|
||||
return round2((subtotal * pct) / 100)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
const OrdersContext = createContext(null)
|
||||
|
||||
export function OrdersProvider({ children }) {
|
||||
const [orders, setOrders] = useState(loadOrders)
|
||||
|
||||
useEffect(() => {
|
||||
saveOrders(orders)
|
||||
}, [orders])
|
||||
|
||||
/**
|
||||
* Équivalent local d’un POST /api/orders.
|
||||
* orderDraft: { items: [{ bookId, title, unitPrice, qty }], promotion?: { code, type, value } }
|
||||
*/
|
||||
const createOrder = useCallback((orderDraft) => {
|
||||
const items = Array.isArray(orderDraft?.items) ? orderDraft.items : []
|
||||
const cleaned = items
|
||||
.map((it) => ({
|
||||
bookId: String(it.bookId),
|
||||
title: String(it.title || ''),
|
||||
unitPrice: Number(it.unitPrice),
|
||||
qty: Math.max(0, Math.trunc(Number(it.qty) || 0)),
|
||||
}))
|
||||
.filter((it) => it.bookId && it.qty > 0 && Number.isFinite(it.unitPrice))
|
||||
|
||||
if (cleaned.length === 0) {
|
||||
throw new Error('Aucun article dans la commande')
|
||||
}
|
||||
|
||||
const subtotal = calcSubtotal(cleaned)
|
||||
const promotion = orderDraft?.promotion
|
||||
? {
|
||||
code: String(orderDraft.promotion.code || ''),
|
||||
type: String(orderDraft.promotion.type || 'percent'),
|
||||
value: Number(orderDraft.promotion.value),
|
||||
}
|
||||
: null
|
||||
const discount = calcDiscount({ subtotal, promotion })
|
||||
const total = round2(Math.max(0, subtotal - discount))
|
||||
|
||||
const order = {
|
||||
id: crypto.randomUUID(),
|
||||
createdAt: new Date().toISOString(),
|
||||
items: cleaned,
|
||||
promotion: promotion && promotion.code ? promotion : null,
|
||||
subtotal,
|
||||
discount,
|
||||
total,
|
||||
}
|
||||
|
||||
setOrders((prev) => [order, ...prev])
|
||||
return order
|
||||
}, [])
|
||||
|
||||
const value = useMemo(() => ({ orders, createOrder }), [orders, createOrder])
|
||||
|
||||
return <OrdersContext.Provider value={value}>{children}</OrdersContext.Provider>
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export function useOrders() {
|
||||
const ctx = useContext(OrdersContext)
|
||||
if (!ctx) {
|
||||
throw new Error('useOrders doit être utilisé dans un OrdersProvider')
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user