import style from './cart.module.css'; import React, { useEffect, useMemo, useState } from 'react'; import Link from 'next/link'; import { Button, Checkbox, Spinner, Tooltip, Text, Box, Flex, } from '@chakra-ui/react'; import { toast } from 'react-hot-toast'; import { useRouter } from 'next/router'; import { getAuth } from '~/libs/auth'; import { useCartStore } from '~/modules/cart/stores/useCartStore'; import CartItemModule from '~/modules/cart/components/Item'; import CartSummary from '~/modules/cart/components/Summary'; import clsxm from '~/libs/clsxm'; import useDevice from '@/core/hooks/useDevice'; import CartSummaryMobile from '~/modules/cart/components/CartSummaryMobile'; import Image from '~/components/ui/image'; import { deleteUserCart, upsertUserCart } from '~/services/cart'; import { Trash2Icon } from 'lucide-react'; import { useProductCartContext } from '@/contexts/ProductCartContext'; import { getSelectedItemsFromCookie, setSelectedItemsToCookie, syncSelectedItemsWithCookie, setAllSelectedInCookie, removeSelectedItemsFromCookie, forceResetAllSelectedItems, checkboxUpdateState, } from '~/utils/cart'; // Special ID for the "select all" checkbox const SELECT_ALL_ID = 'select_all_checkbox'; const CartPage = () => { const router = useRouter(); const auth = getAuth(); const [isStepApproval, setIsStepApproval] = useState(false); const [isLoad, setIsLoad] = useState(false); const [isLoadDelete, setIsLoadDelete] = useState(false); const { loadCart, cart, summary, updateCartItem } = useCartStore(); const device = useDevice(); const { setRefreshCart } = useProductCartContext(); const [isTop, setIsTop] = useState(true); const [isUpdating, setIsUpdating] = useState(false); const [isStateMismatch, setIsStateMismatch] = useState(false); const [isAnyCheckboxUpdating, setIsAnyCheckboxUpdating] = useState(false); // Subscribe to checkbox update state changes useEffect(() => { const handleUpdateStateChange = (isUpdating) => { setIsAnyCheckboxUpdating(isUpdating); }; // Add listener for checkbox update state changes checkboxUpdateState.addListener(handleUpdateStateChange); // Cleanup listener on component unmount return () => { checkboxUpdateState.removeListener(handleUpdateStateChange); }; }, []); // Function to check if cart state is inconsistent const checkCartStateMismatch = () => { if (!cart || !cart.products || isUpdating) return false; try { // Ambil status selected dari cookie const selectedItems = getSelectedItemsFromCookie(); // Periksa ketidaksesuaian antara UI dan cookie // 1. Periksa item yang selected di UI tapi tidak di cookie for (const product of cart.products) { const cookieState = selectedItems[product.id]; // Jika ada di cookie tapi tidak sama dengan UI if (cookieState !== undefined && cookieState !== product.selected) { return true; } // Jika tidak ada di cookie tapi selected di UI if (cookieState === undefined && product.selected) { return true; } } // 2. Periksa item yang selected di cookie tapi tidak ada di cart for (const productId in selectedItems) { const isSelected = selectedItems[productId]; if (isSelected) { // Cek apakah product id ini ada di cart const productExists = cart.products.some( (p) => p.id.toString() === productId.toString() ); if (!productExists) { // Ada item selected di cookie yang tidak ada di cart return true; } } } return false; } catch (error) { console.error('Error checking cart state mismatch:', error); return false; } }; // Function to reset all selected items when state is inconsistent const handleResetSelections = () => { if (!cart) return; setIsUpdating(true); try { // Use the forceResetSelection function from the store useCartStore.getState().forceResetSelection(); // Set state back to normal setIsStateMismatch(false); // Give visual feedback toast.success('Semua pilihan telah direset'); // Optional: Sync with server if needed if (typeof auth === 'object') { const updatePromises = cart.products.map((item) => upsertUserCart({ userId: auth.id, type: item.cart_type, id: item.id, qty: item.quantity, selected: false, purchase_tax_id: item.purchase_tax_id || null, // Fix integer field issue }) ); Promise.all(updatePromises) .then(() => loadCart(auth.id)) .catch((error) => { console.error('Error updating selections to server:', error); }) .finally(() => { setIsUpdating(false); }); } else { setIsUpdating(false); } } catch (error) { console.error('Error resetting selections:', error); setIsUpdating(false); toast.error('Gagal mereset pilihan'); } }; // Check for state inconsistency useEffect(() => { if (!cart || !cart.products || isUpdating) return; const hasMismatch = checkCartStateMismatch(); setIsStateMismatch(hasMismatch); }, [cart, isUpdating]); useEffect(() => { const handleScroll = () => { setIsTop(window.scrollY < 200); }; window.addEventListener('scroll', handleScroll); return () => { window.removeEventListener('scroll', handleScroll); }; }, []); useEffect(() => { const loadCartWithStorage = async () => { if (typeof auth === 'object' && !cart) { await loadCart(auth.id); setIsStepApproval(auth?.feature?.soApproval); // Sync selected items with server data using cookies if (cart?.products) { const { items, needsUpdate } = syncSelectedItemsWithCookie( cart.products ); // If there's a mismatch between cookie and server data, update the UI if (needsUpdate) { const updatedCart = { ...cart, products: cart.products.map((item) => ({ ...item, selected: items[item.id] !== undefined ? items[item.id] : item.selected, })), }; updateCartItem(updatedCart); } } } }; loadCartWithStorage(); }, [auth, cart]); const hasSelectedPromo = useMemo(() => { if (!cart) return false; return cart?.products?.some( (item) => item.cart_type === 'promotion' && item.selected ); }, [cart]); const hasSelected = useMemo(() => { if (!cart) return false; return cart?.products?.some((item) => item.selected); }, [cart]); const hasSelectNoPrice = useMemo(() => { if (!cart) return false; return cart?.products?.some( (item) => item.selected && item.price.price_discount === 0 ); }, [cart]); const hasSelectedAll = useMemo(() => { if (!cart || !Array.isArray(cart.products)) return false; return ( cart.products.length > 0 && cart.products.every((item) => item.selected) ); }, [cart]); const handleCheckout = () => { if (isUpdating || isLoadDelete || isAnyCheckboxUpdating) { toast.error('Harap tunggu pembaruan selesai'); return; } router.push('/shop/checkout'); }; const handleQuotation = () => { if (isUpdating || isLoadDelete || isAnyCheckboxUpdating) { toast.error('Harap tunggu pembaruan selesai'); return; } if (hasSelectedPromo || !hasSelected) { toast.error('Maaf, Barang promo tidak dapat dibuat quotation'); } else { router.push('/shop/quotation'); } }; const handleChange = async (e: React.ChangeEvent) => { if (cart && !isUpdating && typeof auth === 'object') { const newSelectedState = !hasSelectedAll; // Set updating flag setIsUpdating(true); // Notify checkbox update state system with the special select all ID checkboxUpdateState.startUpdate(SELECT_ALL_ID); // Update UI immediately const updatedCart = { ...cart, products: cart.products.map((item) => ({ ...item, selected: newSelectedState, })), }; updateCartItem(updatedCart); // Get all product IDs in cart const productIds = cart.products.map((item) => item.id); // Update cookies immediately for responsive UI setAllSelectedInCookie(productIds, newSelectedState, false); // We're already notifying try { // Update all items on server in background const updatePromises = cart.products.map((item) => upsertUserCart({ userId: auth.id, type: item.cart_type, id: item.id, qty: item.quantity, selected: newSelectedState, purchase_tax_id: item.purchase_tax_id || null, // Fix integer field issue }) ); await Promise.all(updatePromises); await loadCart(auth.id); } catch (error) { console.error('Error updating select all:', error); // Revert changes on error const revertedCart = { ...cart, products: cart.products.map((item) => ({ ...item, selected: !newSelectedState, })), }; updateCartItem(revertedCart); // Revert cookies setAllSelectedInCookie(productIds, !newSelectedState, false); toast.error('Gagal memperbarui pilihan'); } finally { setIsUpdating(false); // End update notification checkboxUpdateState.endUpdate(SELECT_ALL_ID); } } }; const handleDelete = async () => { if (typeof auth !== 'object' || !cart) return; setIsLoadDelete(true); checkboxUpdateState.startUpdate('delete_operation'); try { const itemsToDelete = cart.products.filter((item) => item.selected); const itemIdsToDelete = itemsToDelete.map((item) => item.id); const cartIdsToDelete = itemsToDelete.map((item) => item.cart_id); // Step 1: Delete from server first for (const item of itemsToDelete) { await deleteUserCart(auth.id, [item.cart_id]); } // Step 2: Update local cart state immediately (optimistic update) const updatedProducts = cart.products.filter((item) => !item.selected); const updatedCart = { ...cart, products: updatedProducts, product_total: updatedProducts.length, }; updateCartItem(updatedCart); // Step 3: Clean up cookies AFTER state update removeSelectedItemsFromCookie(itemIdsToDelete); removeCartItemsFromCookie(cartIdsToDelete); // Step 4: Reload from server to ensure consistency (but don't wait for it to complete UI update) loadCart(auth.id) .then(() => { console.log('Cart reloaded from server'); }) .catch((error) => { console.error('Error reloading cart:', error); // If reload fails, at least we have the optimistic update }); // Step 5: Trigger context refresh setRefreshCart(true); // Success feedback toast.success('Item berhasil dihapus'); } catch (error) { console.error('Failed to delete cart items:', error); toast.error('Gagal menghapus item'); // If deletion failed, reload cart to restore proper state loadCart(auth.id); } finally { setIsLoadDelete(false); checkboxUpdateState.endUpdate('delete_operation'); } }; // Check if buttons should be disabled const areButtonsDisabled = isUpdating || isLoadDelete || isAnyCheckboxUpdating; // Only disable the select all checkbox if it specifically is updating const isSelectAllDisabled = isUpdating || checkboxUpdateState.isCheckboxUpdating(SELECT_ALL_ID); return ( <>

Keranjang Belanja

{hasSelectedAll ? 'Uncheck all' : 'Select all'}

{!cart && }
{cart?.products?.map((item) => ( ))} {cart?.products?.length === 0 && (
Empty Cart
Keranjangnya masih kosong nih
Yuk, tambahin barang-barang yang kamu mau ke keranjang sekarang!
Ada banyak potongan belanjanya pakai kode voucher
Mulai Belanja
)}
{device.isMobile ? ( ) : ( )}
{!isStepApproval && ( )}
); }; export default CartPage;