diff options
| author | Miqdad <ahmadmiqdad27@gmail.com> | 2025-05-31 14:33:24 +0700 |
|---|---|---|
| committer | Miqdad <ahmadmiqdad27@gmail.com> | 2025-05-31 14:33:24 +0700 |
| commit | 3f992f62c54e09254c48d653c2cd138df1cbd8e2 (patch) | |
| tree | 3c2bc8de3f99b162aeb44c4ac138367cb1ea7332 /src-migrate | |
| parent | 2a1dea70b8f0062fe8eebeb7139a7b77a24e220b (diff) | |
<miqdad> fix error deployment
Diffstat (limited to 'src-migrate')
| -rw-r--r-- | src-migrate/modules/cart/components/ItemAction.tsx | 65 | ||||
| -rw-r--r-- | src-migrate/modules/cart/components/ItemSelect.tsx | 22 | ||||
| -rw-r--r-- | src-migrate/modules/cart/stores/useCartStore.ts | 98 | ||||
| -rw-r--r-- | src-migrate/pages/shop/cart/index.tsx | 121 |
4 files changed, 177 insertions, 129 deletions
diff --git a/src-migrate/modules/cart/components/ItemAction.tsx b/src-migrate/modules/cart/components/ItemAction.tsx index 4dcebd9e..b06e8e75 100644 --- a/src-migrate/modules/cart/components/ItemAction.tsx +++ b/src-migrate/modules/cart/components/ItemAction.tsx @@ -22,11 +22,11 @@ import { import { toast } from 'react-hot-toast'; -type Props = { +interface Props { item: CartItem; -}; +} -const CartItemAction = ({ item }: Props) => { +const CartItemAction: React.FC<Props> = ({ item }) => { const auth = getAuth(); const { setRefreshCart } = useProductCartContext(); const [isLoadDelete, setIsLoadDelete] = useState<boolean>(false); @@ -36,9 +36,9 @@ const CartItemAction = ({ item }: Props) => { const { loadCart, cart, updateCartItem } = useCartStore(); - const limitQty = item.limit_qty?.transaction || 0; + const limitQty: number = item.limit_qty?.transaction || 0; - const handleDelete = async () => { + const handleDelete = async (): Promise<void> => { if (typeof auth !== 'object') return; setIsLoadDelete(true); @@ -80,25 +80,29 @@ const CartItemAction = ({ item }: Props) => { } }; - const updateQuantityInCookie = (productId, cartId, newQuantity) => { + const updateQuantityInCookie = ( + productId: number, + cartId: string | number, + newQuantity: number + ): boolean => { try { - const cartData = getCartDataFromCookie(); + const cartData = getCartDataFromCookie() as Record<string, any>; let itemFound = false; + const cartIdString = String(cartId); // Find item by cart_id key or search within objects - if (cartData[cartId]) { - cartData[cartId].quantity = newQuantity; + if (cartData[cartIdString]) { + cartData[cartIdString].quantity = newQuantity; itemFound = true; } else { // Search by product id or cart_id within objects - for (const key in cartData) { + Object.keys(cartData).forEach((key) => { const item = cartData[key]; - if (item.id === productId || item.cart_id === cartId) { + if (item.id === productId || String(item.cart_id) === cartIdString) { item.quantity = newQuantity; itemFound = true; - break; } - } + }); } if (itemFound) { @@ -113,12 +117,12 @@ const CartItemAction = ({ item }: Props) => { } }; - const decreaseQty = () => { - setQuantity((quantity) => (quantity -= 1)); + const decreaseQty = (): void => { + setQuantity((prevQuantity) => prevQuantity - 1); }; - const increaseQty = () => { - setQuantity((quantity) => (quantity += 1)); + const increaseQty = (): void => { + setQuantity((prevQuantity) => prevQuantity + 1); }; const debounceQty = useDebounce(quantity, 1000); @@ -129,7 +133,7 @@ const CartItemAction = ({ item }: Props) => { }, [debounceQty, limitQty]); useEffect(() => { - const updateCart = async () => { + const updateCart = async (): Promise<void> => { if (typeof auth !== 'object' || isNaN(debounceQty)) return; if (debounceQty === item.quantity) return; @@ -167,20 +171,22 @@ const CartItemAction = ({ item }: Props) => { await loadCart(auth.id); // Re-update cookie if server reload overwrote it - const currentCookieData = getCartDataFromCookie(); + const currentCookieData = getCartDataFromCookie() as Record< + string, + any + >; let needsReUpdate = false; - for (const key in currentCookieData) { + Object.keys(currentCookieData).forEach((key) => { const cookieItem = currentCookieData[key]; if ( (cookieItem.id === item.id || - cookieItem.cart_id === item.cart_id) && + String(cookieItem.cart_id) === String(item.cart_id)) && cookieItem.quantity !== debounceQty ) { needsReUpdate = true; - break; } - } + }); if (needsReUpdate) { updateQuantityInCookie(item.id, item.cart_id, debounceQty); @@ -202,6 +208,13 @@ const CartItemAction = ({ item }: Props) => { //eslint-disable-next-line react-hooks/exhaustive-deps }, [debounceQty]); + const handleQuantityInputChange = ( + e: React.ChangeEvent<HTMLInputElement> + ): void => { + const value = parseInt(e.target.value); + setQuantity(isNaN(value) ? 1 : value); + }; + return ( <div className={style.actionSection}> <button @@ -230,8 +243,8 @@ const CartItemAction = ({ item }: Props) => { <input type='number' - className={style.quantity.toString()} - onChange={(e) => setQuantity(parseInt(e.target.value))} + className={style.quantity} + onChange={handleQuantityInputChange} value={quantity} /> @@ -249,4 +262,4 @@ const CartItemAction = ({ item }: Props) => { ); }; -export default CartItemAction;
\ No newline at end of file +export default CartItemAction; diff --git a/src-migrate/modules/cart/components/ItemSelect.tsx b/src-migrate/modules/cart/components/ItemSelect.tsx index 72ab49aa..f95ee36c 100644 --- a/src-migrate/modules/cart/components/ItemSelect.tsx +++ b/src-migrate/modules/cart/components/ItemSelect.tsx @@ -11,11 +11,11 @@ import { checkboxUpdateState, } from '~/utils/cart'; -type Props = { +interface Props { item: CartItem; -}; +} -const CartItemSelect = ({ item }: Props) => { +const CartItemSelect: React.FC<Props> = ({ item }) => { const auth = getAuth(); const { updateCartItem, cart, loadCart } = useCartStore(); const [isUpdating, setIsUpdating] = useState<boolean>(false); @@ -23,7 +23,7 @@ const CartItemSelect = ({ item }: Props) => { // Subscribe to global checkbox update state useEffect(() => { - const handleUpdateStateChange = (isUpdating) => { + const handleUpdateStateChange = (isUpdating: boolean): void => { // This component doesn't need to react to global state changes // Individual checkboxes are managed independently }; @@ -36,7 +36,10 @@ const CartItemSelect = ({ item }: Props) => { useEffect(() => { if (isUpdating) return; - const selectedItems = getSelectedItemsFromCookie(); + const selectedItems = getSelectedItemsFromCookie() as Record< + number, + boolean + >; const storedState = selectedItems[item.id]; if (storedState !== undefined) { @@ -62,7 +65,7 @@ const CartItemSelect = ({ item }: Props) => { }, [item.id, item.selected, localSelected, cart, updateCartItem, isUpdating]); const handleChange = useCallback( - async (e: React.ChangeEvent<HTMLInputElement>) => { + async (e: React.ChangeEvent<HTMLInputElement>): Promise<void> => { if (typeof auth !== 'object' || !cart || isUpdating) return; const newSelectedState = e.target.checked; @@ -70,7 +73,7 @@ const CartItemSelect = ({ item }: Props) => { // Update local state immediately setLocalSelected(newSelectedState); setIsUpdating(true); - checkboxUpdateState.startUpdate(item.id); + checkboxUpdateState.startUpdate(); try { // Update cookie immediately for responsive UI @@ -107,14 +110,13 @@ const CartItemSelect = ({ item }: Props) => { loadCart(auth.id); } finally { setIsUpdating(false); - checkboxUpdateState.endUpdate(item.id); + checkboxUpdateState.endUpdate(); } }, [auth, cart, item, isUpdating, updateCartItem, loadCart] ); - const isDisabled = - isUpdating || checkboxUpdateState.isCheckboxUpdating(item.id); + const isDisabled: boolean = isUpdating; return ( <div className='w-6 my-auto relative'> diff --git a/src-migrate/modules/cart/stores/useCartStore.ts b/src-migrate/modules/cart/stores/useCartStore.ts index dc47b011..69cf0384 100644 --- a/src-migrate/modules/cart/stores/useCartStore.ts +++ b/src-migrate/modules/cart/stores/useCartStore.ts @@ -8,24 +8,32 @@ import { forceResetAllSelectedItems, } from '~/utils/cart'; -type State = { +interface Summary { + subtotal: number; + discount: number; + total: number; + tax: number; + grandTotal: number; +} + +interface SyncResult { + cartData?: Record<string, any>; + selectedItems?: Record<number, boolean>; + needsUpdate: boolean; +} + +interface State { cart: CartProps | null; isLoadCart: boolean; - summary: { - subtotal: number; - discount: number; - total: number; - tax: number; - grandTotal: number; - }; -}; + summary: Summary; +} -type Action = { +interface Action { loadCart: (userId: number) => Promise<void>; updateCartItem: (updateCart: CartProps) => void; forceResetSelection: () => void; clearCart: () => void; -}; +} export const useCartStore = create<State & Action>((set, get) => ({ cart: null, @@ -38,7 +46,7 @@ export const useCartStore = create<State & Action>((set, get) => ({ grandTotal: 0, }, - loadCart: async (userId) => { + loadCart: async (userId: number): Promise<void> => { if (get().isLoadCart) return; set({ isLoadCart: true }); @@ -47,12 +55,15 @@ export const useCartStore = create<State & Action>((set, get) => ({ const cart: CartProps = (await getUserCart(userId)) as CartProps; // Sync with cookie data - const syncResult = syncCartWithCookie(cart); + const syncResult = syncCartWithCookie(cart) as SyncResult; if (syncResult?.needsUpdate && cart.products) { - const selectedItems = getSelectedItemsFromCookie(); + const selectedItems = getSelectedItemsFromCookie() as Record< + number, + boolean + >; - const updatedCart = { + const updatedCart: CartProps = { ...cart, products: cart.products.map((item) => ({ ...item, @@ -81,7 +92,7 @@ export const useCartStore = create<State & Action>((set, get) => ({ } }, - updateCartItem: (updatedCart) => { + updateCartItem: (updatedCart: CartProps): void => { set({ cart: updatedCart }); syncCartWithCookie(updatedCart); @@ -89,13 +100,13 @@ export const useCartStore = create<State & Action>((set, get) => ({ set({ summary }); }, - forceResetSelection: () => { + forceResetSelection: (): void => { const { cart } = get(); if (!cart) return; forceResetAllSelectedItems(); - const updatedCart = { + const updatedCart: CartProps = { ...cart, products: cart.products.map((item) => ({ ...item, selected: false })), }; @@ -106,7 +117,7 @@ export const useCartStore = create<State & Action>((set, get) => ({ set({ summary }); }, - clearCart: () => { + clearCart: (): void => { set({ cart: null, summary: { @@ -121,13 +132,15 @@ export const useCartStore = create<State & Action>((set, get) => ({ })); // Helper function for cookie fallback -const handleFallbackFromCookie = async () => { +const handleFallbackFromCookie = async (): Promise<void> => { try { - const cartData = getCartDataFromCookie(); + const cartData = getCartDataFromCookie() as Record<string, any>; if (Object.keys(cartData).length === 0) return; - const products = Object.values(cartData).map(transformCookieItemToProduct); + const products: CartItem[] = Object.values(cartData).map( + transformCookieItemToProduct + ); const fallbackCart: CartProps = { product_total: products.length, @@ -145,22 +158,43 @@ const handleFallbackFromCookie = async () => { // Helper function to transform cookie item to product format const transformCookieItemToProduct = (item: any): CartItem => ({ + image_program: item.image_program || '', cart_id: item.cart_id, - id: item.id, - cart_type: item.cart_type, - product_id: item.product?.id, - product_name: item.product?.name, - program_line_id: item.program_line?.id, - program_line_name: item.program_line?.name, quantity: item.quantity, selected: item.selected, - price: item.price, + can_buy: true, + cart_type: item.cart_type, + id: item.id, + name: item.product?.name || item.program_line?.name || '', + stock: 0, + is_in_bu: false, + on_hand_qty: 0, + available_quantity: 0, + weight: 0, + attributes: [], + parent: { + id: 0, + name: '', + image: '', + }, + price: item.price || { + price: 0, + discount_percentage: 0, + price_discount: 0, + }, + manufacture: { + id: 0, + name: '', + }, + has_flashsale: false, + subtotal: 0, + code: item.code, + image: item.image, package_price: item.package_price, - source: item.source, }); // Helper function to compute cart summary -const computeSummary = (cart: CartProps) => { +const computeSummary = (cart: CartProps): Summary => { if (!cart?.products) { return { subtotal: 0, discount: 0, total: 0, grandTotal: 0, tax: 0 }; } @@ -186,4 +220,4 @@ const computeSummary = (cart: CartProps) => { const tax = grandTotal - total; return { subtotal, discount, total, grandTotal, tax }; -};
\ No newline at end of file +}; diff --git a/src-migrate/pages/shop/cart/index.tsx b/src-migrate/pages/shop/cart/index.tsx index 03854d79..795dfa72 100644 --- a/src-migrate/pages/shop/cart/index.tsx +++ b/src-migrate/pages/shop/cart/index.tsx @@ -29,24 +29,26 @@ import { const SELECT_ALL_ID = 'select_all_checkbox'; -const CartPage = () => { +const CartPage: React.FC = () => { const router = useRouter(); const auth = getAuth(); - const [isStepApproval, setIsStepApproval] = useState(false); + const [isStepApproval, setIsStepApproval] = useState<boolean>(false); const [isLoadDelete, setIsLoadDelete] = useState<boolean>(false); const { loadCart, cart, summary, updateCartItem } = useCartStore(); const device = useDevice(); const { setRefreshCart } = useProductCartContext(); - const [isTop, setIsTop] = useState(true); - const [isUpdating, setIsUpdating] = useState(false); - const [isAnyCheckboxUpdating, setIsAnyCheckboxUpdating] = useState(false); - const [isAnyQuantityUpdating, setIsAnyQuantityUpdating] = useState(false); + const [isTop, setIsTop] = useState<boolean>(true); + const [isUpdating, setIsUpdating] = useState<boolean>(false); + const [isAnyCheckboxUpdating, setIsAnyCheckboxUpdating] = + useState<boolean>(false); + const [isAnyQuantityUpdating, setIsAnyQuantityUpdating] = + useState<boolean>(false); // Subscribe to update state changes useEffect(() => { - const handleCheckboxUpdate = (isUpdating) => + const handleCheckboxUpdate = (isUpdating: boolean): void => setIsAnyCheckboxUpdating(isUpdating); - const handleQuantityUpdate = (isUpdating) => + const handleQuantityUpdate = (isUpdating: boolean): void => setIsAnyQuantityUpdating(isUpdating); checkboxUpdateState.addListener(handleCheckboxUpdate); @@ -60,7 +62,7 @@ const CartPage = () => { // Handle scroll for sticky header styling useEffect(() => { - const handleScroll = () => setIsTop(window.scrollY < 200); + const handleScroll = (): void => setIsTop(window.scrollY < 200); window.addEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll); @@ -68,36 +70,40 @@ const CartPage = () => { // Initialize cart and sync with cookies useEffect(() => { - const initializeCart = async () => { + const initializeCart = async (): Promise<void> => { if (typeof auth === 'object' && !cart) { await loadCart(auth.id); setIsStepApproval(auth?.feature?.soApproval); - - if (cart?.products) { - const { items, needsUpdate } = syncSelectedItemsWithCookie( - cart.products - ); - - if (needsUpdate) { - const updatedCart = { - ...cart, - products: cart.products.map((item) => ({ - ...item, - selected: - items[item.id] !== undefined ? items[item.id] : item.selected, - })), - }; - updateCartItem(updatedCart); - } - } } }; initializeCart(); - }, [auth, cart, loadCart, updateCartItem]); + }, [auth, cart, loadCart]); + + // Separate effect to sync with cookies after cart is loaded + useEffect(() => { + if (cart?.products) { + const { items, needsUpdate } = syncSelectedItemsWithCookie(cart.products); + const typedItems = items as Record<number, boolean>; + + if (needsUpdate) { + const updatedCart = { + ...cart, + products: cart.products.map((item) => ({ + ...item, + selected: + typedItems[item.id] !== undefined + ? typedItems[item.id] + : item.selected, + })), + }; + updateCartItem(updatedCart); + } + } + }, [cart, updateCartItem]); // Computed values - const hasSelectedPromo = useMemo(() => { + const hasSelectedPromo = useMemo((): boolean => { return ( cart?.products?.some( (item) => item.cart_type === 'promotion' && item.selected @@ -105,11 +111,11 @@ const CartPage = () => { ); }, [cart]); - const hasSelected = useMemo(() => { + const hasSelected = useMemo((): boolean => { return cart?.products?.some((item) => item.selected) || false; }, [cart]); - const hasSelectNoPrice = useMemo(() => { + const hasSelectNoPrice = useMemo((): boolean => { return ( cart?.products?.some( (item) => item.selected && item.price.price_discount === 0 @@ -117,22 +123,22 @@ const CartPage = () => { ); }, [cart]); - const hasSelectedAll = useMemo(() => { + const hasSelectedAll = useMemo((): boolean => { if (!cart?.products?.length) return false; return cart.products.every((item) => item.selected); }, [cart]); // Button states - const areButtonsDisabled = + const areButtonsDisabled: boolean = isUpdating || isLoadDelete || isAnyCheckboxUpdating || isAnyQuantityUpdating; - const isSelectAllDisabled = - isUpdating || checkboxUpdateState.isCheckboxUpdating(SELECT_ALL_ID); + const isSelectAllDisabled: boolean = + isUpdating || checkboxUpdateState.isCheckboxUpdating(); // Handlers - const handleCheckout = () => { + const handleCheckout = (): void => { if (areButtonsDisabled) { toast.error('Harap tunggu pembaruan selesai'); return; @@ -140,7 +146,7 @@ const CartPage = () => { router.push('/shop/checkout'); }; - const handleQuotation = () => { + const handleQuotation = (): void => { if (areButtonsDisabled) { toast.error('Harap tunggu pembaruan selesai'); return; @@ -152,12 +158,14 @@ const CartPage = () => { } }; - const handleSelectAll = async (e: React.ChangeEvent<HTMLInputElement>) => { + const handleSelectAll = async ( + e: React.ChangeEvent<HTMLInputElement> + ): Promise<void> => { if (!cart || isUpdating || typeof auth !== 'object') return; const newSelectedState = !hasSelectedAll; setIsUpdating(true); - checkboxUpdateState.startUpdate(SELECT_ALL_ID); + checkboxUpdateState.startUpdate(); try { // Update UI immediately @@ -182,7 +190,6 @@ const CartPage = () => { id: item.id, qty: item.quantity, selected: newSelectedState, - purchase_tax_id: item.purchase_tax_id || null, }) ); @@ -208,15 +215,15 @@ const CartPage = () => { ); } finally { setIsUpdating(false); - checkboxUpdateState.endUpdate(SELECT_ALL_ID); + checkboxUpdateState.endUpdate(); } }; - const handleDelete = async () => { + const handleDelete = async (): Promise<void> => { if (typeof auth !== 'object' || !cart) return; setIsLoadDelete(true); - checkboxUpdateState.startUpdate('delete_operation'); + checkboxUpdateState.startUpdate(); try { const itemsToDelete = cart.products.filter((item) => item.selected); @@ -239,7 +246,7 @@ const CartPage = () => { // Clean up cookies removeSelectedItemsFromCookie(itemIdsToDelete); - removeCartItemsFromCookie(cartIdsToDelete); + removeCartItemsFromCookie(cartIdsToDelete.map(String)); // Reload from server loadCart(auth.id).catch((error) => @@ -254,12 +261,12 @@ const CartPage = () => { loadCart(auth.id); } finally { setIsLoadDelete(false); - checkboxUpdateState.endUpdate('delete_operation'); + checkboxUpdateState.endUpdate(); } }; // Tooltip messages - const getTooltipMessage = () => { + const getTooltipMessage = (): string => { if (isAnyQuantityUpdating) return 'Harap tunggu update quantity selesai'; if (isAnyCheckboxUpdating) return 'Harap tunggu pembaruan checkbox selesai'; if (isLoadDelete) return 'Harap tunggu penghapusan selesai'; @@ -267,7 +274,7 @@ const CartPage = () => { return ''; }; - const getQuotationTooltip = () => { + const getQuotationTooltip = (): string => { const baseMessage = getTooltipMessage(); if (baseMessage) return baseMessage; if (hasSelectedPromo) return 'Barang promo tidak dapat dibuat quotation'; @@ -275,7 +282,7 @@ const CartPage = () => { return ''; }; - const getCheckoutTooltip = () => { + const getCheckoutTooltip = (): string => { const baseMessage = getTooltipMessage(); if (baseMessage) return baseMessage; if (!hasSelected) return 'Tidak ada item yang dipilih'; @@ -283,7 +290,7 @@ const CartPage = () => { return ''; }; - const getDeleteTooltip = () => { + const getDeleteTooltip = (): string => { const baseMessage = getTooltipMessage(); if (baseMessage) return baseMessage; if (!hasSelected) return 'Tidak ada item yang dipilih'; @@ -395,17 +402,9 @@ const CartPage = () => { > <div className={style.summary}> {device.isMobile ? ( - <CartSummaryMobile - {...summary} - isLoaded={!!cart} - products={cart?.products} - /> + <CartSummaryMobile {...summary} isLoaded={!!cart} /> ) : ( - <CartSummary - {...summary} - isLoaded={!!cart} - products={cart?.products} - /> + <CartSummary {...summary} isLoaded={!!cart} /> )} <div @@ -452,4 +451,4 @@ const CartPage = () => { ); }; -export default CartPage;
\ No newline at end of file +export default CartPage; |
