sozsoft-platform/ui/src/utils/cartUtils.ts

189 lines
5.8 KiB
TypeScript
Raw Normal View History

2026-02-24 20:44:16 +00:00
import { BillingCycle, BasketItem, ProductDto } from "@/proxy/order/models";
export interface CartState {
items: BasketItem[];
2026-07-07 13:41:07 +00:00
subtotal: number;
vatTotal: number;
2026-02-24 20:44:16 +00:00
total: number;
globalBillingCycle: BillingCycle;
globalPeriod: number;
}
export const initialCartState: CartState = {
items: [],
2026-07-07 13:41:07 +00:00
subtotal: 0,
vatTotal: 0,
2026-02-24 20:44:16 +00:00
total: 0,
globalBillingCycle: 'yearly',
globalPeriod: 1
};
// Key for localStorage
const CART_STORAGE_KEY = 'cartState';
export const calculateItemPrice = (
product: ProductDto,
quantity: number,
billingCycle: BillingCycle,
period: number = 1
): number => {
const price = billingCycle === 'monthly' ? product.monthlyPrice! : product.yearlyPrice!;
2026-07-07 13:41:07 +00:00
const vatRate = product.vatRate ?? 0.2;
return roundMoney(price * quantity * period * (1 + vatRate));
};
const roundMoney = (value: number): number => Math.round(value * 100) / 100;
const calculateItemVatAmount = (
totalPrice: number,
vatRate: number = 0.2,
): number => (vatRate > 0 ? roundMoney(totalPrice - totalPrice / (1 + vatRate)) : 0);
const withItemVat = (
product: ProductDto,
quantity: number,
billingCycle: BillingCycle,
period: number,
): BasketItem => {
const vatRate = product.vatRate ?? 0.2;
const totalPrice = calculateItemPrice(product, quantity, billingCycle, period);
return {
product,
quantity,
billingCycle,
vatRate,
vatAmount: calculateItemVatAmount(totalPrice, vatRate),
totalPrice,
};
};
const calculateCartTotals = (items: BasketItem[]) => {
const total = roundMoney(items.reduce((sum, item) => sum + item.totalPrice, 0));
const vatTotal = roundMoney(items.reduce((sum, item) => sum + (item.vatAmount ?? 0), 0));
return {
subtotal: roundMoney(total - vatTotal),
vatTotal,
total,
};
2026-02-24 20:44:16 +00:00
};
export const addItemToCart = (
cartState: CartState,
product: ProductDto,
quantity: number,
billingCycle: BillingCycle
): CartState => {
const isAlreadyInCart = isItemInCartState(cartState.items, product.id, billingCycle);
if (!product.isQuantityBased && isAlreadyInCart) {
return cartState;
}
const existingItemIndex = cartState.items.findIndex(
item => item.product.id === product.id && item.billingCycle === billingCycle
);
let newItems;
if (existingItemIndex > -1) {
newItems = [...cartState.items];
newItems[existingItemIndex].quantity += quantity;
2026-07-07 13:41:07 +00:00
newItems[existingItemIndex] = withItemVat(
2026-02-24 20:44:16 +00:00
product,
newItems[existingItemIndex].quantity,
billingCycle,
2026-07-07 13:41:07 +00:00
cartState.globalPeriod,
2026-02-24 20:44:16 +00:00
);
} else {
const actualQuantity = !product.isQuantityBased ? 1 : quantity;
2026-07-07 13:41:07 +00:00
newItems = [
...cartState.items,
withItemVat(product, actualQuantity, billingCycle, cartState.globalPeriod),
];
2026-02-24 20:44:16 +00:00
}
2026-07-07 13:41:07 +00:00
const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) };
2026-02-24 20:44:16 +00:00
saveCartToLocalStorage(updatedState);
return updatedState;
};
export const removeItemFromCart = (cartState: CartState, id: string): CartState => {
const newItems = cartState.items.filter(item => item.product.id !== id);
2026-07-07 13:41:07 +00:00
const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) };
2026-02-24 20:44:16 +00:00
saveCartToLocalStorage(updatedState);
return updatedState;
};
export const updateCartItemQuantity = (cartState: CartState, id: string, quantity: number): CartState => {
const newItems = cartState.items.map(item => {
if (item.product.id === id) {
if (quantity <= 0) return null;
if (!item.product.isQuantityBased) return item;
2026-07-07 13:41:07 +00:00
return withItemVat(item.product, quantity, item.billingCycle, cartState.globalPeriod);
2026-02-24 20:44:16 +00:00
}
return item;
}).filter(Boolean) as BasketItem[];
2026-07-07 13:41:07 +00:00
const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) };
2026-02-24 20:44:16 +00:00
saveCartToLocalStorage(updatedState);
return updatedState;
};
export const clearCart = (): CartState => {
localStorage.removeItem(CART_STORAGE_KEY);
return initialCartState;
};
export const setCartGlobalBillingCycle = (cartState: CartState, cycle: BillingCycle): CartState => {
2026-07-07 13:41:07 +00:00
const newItems = cartState.items.map(item =>
withItemVat(item.product, item.quantity, cycle, cartState.globalPeriod)
);
const updatedState = { ...cartState, globalBillingCycle: cycle, items: newItems, ...calculateCartTotals(newItems) };
2026-02-24 20:44:16 +00:00
saveCartToLocalStorage(updatedState);
return updatedState;
};
export const setCartGlobalPeriod = (cartState: CartState, period: number): CartState => {
2026-07-07 13:41:07 +00:00
const newItems = cartState.items.map(item =>
withItemVat(item.product, item.quantity, item.billingCycle, period)
);
const updatedState = { ...cartState, globalPeriod: period, items: newItems, ...calculateCartTotals(newItems) };
2026-02-24 20:44:16 +00:00
saveCartToLocalStorage(updatedState);
return updatedState;
};
export const isItemInCart = (cartState: CartState, productId: string, billingCycle: BillingCycle): boolean => {
return cartState.items.some(
item => item.product.id === productId && item.billingCycle === billingCycle
);
};
export const isItemInCartState = (items: BasketItem[], productId: string, billingCycle: BillingCycle): boolean => {
return items.some(
item => item.product.id === productId && item.billingCycle === billingCycle
);
};
// Utility: Save to localStorage
const saveCartToLocalStorage = (cartState: CartState) => {
localStorage.setItem(CART_STORAGE_KEY, JSON.stringify(cartState));
};
// Utility: Load from localStorage
export const loadCartFromLocalStorage = (): CartState => {
const saved = localStorage.getItem(CART_STORAGE_KEY);
2026-07-07 13:41:07 +00:00
if (!saved) return initialCartState;
const parsed = JSON.parse(saved) as CartState;
const period = parsed.globalPeriod || 1;
const items = (parsed.items || []).map(item =>
withItemVat(item.product, item.quantity, item.billingCycle, period)
);
return {
...initialCartState,
...parsed,
items,
...calculateCartTotals(items),
};
2026-02-24 20:44:16 +00:00
};