From 7d4445bb9bad3d6c945503086a07bd882536e5f6 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 19 May 2025 11:02:19 +0700 Subject: fix unresponsive cart select --- src-migrate/modules/cart/components/ItemSelect.tsx | 170 +++++++++++++++------ 1 file changed, 126 insertions(+), 44 deletions(-) (limited to 'src-migrate/modules/cart/components/ItemSelect.tsx') diff --git a/src-migrate/modules/cart/components/ItemSelect.tsx b/src-migrate/modules/cart/components/ItemSelect.tsx index d4a1b537..733ee64d 100644 --- a/src-migrate/modules/cart/components/ItemSelect.tsx +++ b/src-migrate/modules/cart/components/ItemSelect.tsx @@ -1,56 +1,138 @@ -import { Checkbox, Spinner } from '@chakra-ui/react' -import React, { useState } from 'react' - -import { getAuth } from '~/libs/auth' -import { CartItem } from '~/types/cart' -import { upsertUserCart } from '~/services/cart' - -import { useCartStore } from '../stores/useCartStore' +import { Checkbox } from '@chakra-ui/react'; +import React, { useState, useCallback, useEffect } from 'react'; +import { getAuth } from '~/libs/auth'; +import { CartItem } from '~/types/cart'; +import { upsertUserCart } from '~/services/cart'; +import { useCartStore } from '../stores/useCartStore'; +import { toast } from 'react-hot-toast'; +import { + getSelectedItemsFromCookie, + updateSelectedItemInCookie, +} from '~/utils/cart'; type Props = { - item: CartItem -} + item: CartItem; +}; const CartItemSelect = ({ item }: Props) => { - const auth = getAuth() - const { updateCartItem, cart } = useCartStore() + const auth = getAuth(); + const { updateCartItem, cart, loadCart } = useCartStore(); + const [isUpdating, setIsUpdating] = useState(false); + const [localSelected, setLocalSelected] = useState(item.selected); + + // Initialize local state from cookie or server + useEffect(() => { + if (isUpdating) return; // Skip if we're currently updating + + // Check cookie first + const selectedItems = getSelectedItemsFromCookie(); + const storedState = selectedItems[item.id]; + + if (storedState !== undefined) { + // Only update local state if it differs from current state + if (localSelected !== storedState) { + setLocalSelected(storedState); + } + + // If cookie state differs from server state and we're not in the middle of an update, + // synchronize the item state with cookie + if (storedState !== item.selected) { + // Update cart item silently to match cookie + if (cart) { + const updatedCartItems = cart.products.map((cartItem) => + cartItem.id === item.id + ? { ...cartItem, selected: storedState } + : cartItem + ); + + const updatedCart = { ...cart, products: updatedCartItems }; + updateCartItem(updatedCart); + } + } + } else { + // Fall back to server state if no cookie exists + setLocalSelected(item.selected); + + // Save this state to cookie for future use + updateSelectedItemInCookie(item.id, item.selected); + } + }, [item.id, item.selected, localSelected, cart, updateCartItem, isUpdating]); + + const handleChange = useCallback( + async (e: React.ChangeEvent) => { + if (typeof auth !== 'object' || !cart || isUpdating) { + return; + } + + const newSelectedState = e.target.checked; + + // Update local state immediately for responsiveness + setLocalSelected(newSelectedState); + setIsUpdating(true); + + try { + // Update cookie immediately + updateSelectedItemInCookie(item.id, newSelectedState); + + // Update cart state immediately for UI responsiveness + const updatedCartItems = cart.products.map((cartItem) => + cartItem.id === item.id + ? { ...cartItem, selected: newSelectedState } + : cartItem + ); + + const updatedCart = { ...cart, products: updatedCartItems }; + updateCartItem(updatedCart); + + // Save to server + await upsertUserCart({ + userId: auth.id, + type: item.cart_type, + id: item.id, + qty: item.quantity, + selected: newSelectedState, + }); - const [isLoad, setIsLoad] = useState(false) + // Reload cart to ensure consistency + await loadCart(auth.id); + } catch (error) { + console.error('Failed to update item selection:', error); + toast.error('Gagal memperbarui pilihan barang'); - const handleChange = async (e: React.ChangeEvent) => { - if (typeof auth !== 'object' || !cart) return - - setIsLoad(true); - const updatedCartItems = cart.products.map(cartItem => - cartItem.id === item.id - ? { ...cartItem, selected: e.target.checked } - : cartItem - ); + // Revert local state on error + setLocalSelected(!newSelectedState); - // Update the entire cart - const updatedCart = { ...cart, products: updatedCartItems }; - updateCartItem(updatedCart); + // Update cookie back + updateSelectedItemInCookie(item.id, !newSelectedState); - setIsLoad(false); - } + // Reload cart to get server state + loadCart(auth.id); + } finally { + setIsUpdating(false); + } + }, + [auth, cart, item, isUpdating, updateCartItem, loadCart] + ); return ( -
- {isLoad && ( - - )} - - {!isLoad && ( - - )} +
+
- ) -} + ); +}; -export default CartItemSelect \ No newline at end of file +export default CartItemSelect; -- cgit v1.2.3 From 09cebc9020c4f1995a73305187bc1576e339d183 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 22 May 2025 10:05:09 +0700 Subject: disable button when updating checkboxes and change summary design --- src-migrate/modules/cart/components/ItemSelect.tsx | 43 ++++++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) (limited to 'src-migrate/modules/cart/components/ItemSelect.tsx') diff --git a/src-migrate/modules/cart/components/ItemSelect.tsx b/src-migrate/modules/cart/components/ItemSelect.tsx index 733ee64d..70b656ec 100644 --- a/src-migrate/modules/cart/components/ItemSelect.tsx +++ b/src-migrate/modules/cart/components/ItemSelect.tsx @@ -8,6 +8,7 @@ import { toast } from 'react-hot-toast'; import { getSelectedItemsFromCookie, updateSelectedItemInCookie, + checkboxUpdateState, } from '~/utils/cart'; type Props = { @@ -19,6 +20,20 @@ const CartItemSelect = ({ item }: Props) => { const { updateCartItem, cart, loadCart } = useCartStore(); const [isUpdating, setIsUpdating] = useState(false); const [localSelected, setLocalSelected] = useState(item.selected); + const [isGlobalUpdating, setIsGlobalUpdating] = useState(false); + + // Subscribe to global checkbox update state + useEffect(() => { + const handleUpdateStateChange = (isUpdating) => { + setIsGlobalUpdating(isUpdating); + }; + + checkboxUpdateState.addListener(handleUpdateStateChange); + + return () => { + checkboxUpdateState.removeListener(handleUpdateStateChange); + }; + }, []); // Initialize local state from cookie or server useEffect(() => { @@ -54,7 +69,7 @@ const CartItemSelect = ({ item }: Props) => { setLocalSelected(item.selected); // Save this state to cookie for future use - updateSelectedItemInCookie(item.id, item.selected); + updateSelectedItemInCookie(item.id, item.selected, false); // don't notify for initial sync } }, [item.id, item.selected, localSelected, cart, updateCartItem, isUpdating]); @@ -70,9 +85,12 @@ const CartItemSelect = ({ item }: Props) => { setLocalSelected(newSelectedState); setIsUpdating(true); + // Start the update - notify global state with this checkbox's ID + checkboxUpdateState.startUpdate(item.id); + try { - // Update cookie immediately - updateSelectedItemInCookie(item.id, newSelectedState); + // The cookie update is now handled inside the function with notification + updateSelectedItemInCookie(item.id, newSelectedState, false); // We already started above // Update cart state immediately for UI responsiveness const updatedCartItems = cart.products.map((cartItem) => @@ -91,6 +109,7 @@ const CartItemSelect = ({ item }: Props) => { id: item.id, qty: item.quantity, selected: newSelectedState, + purchase_tax_id: item.purchase_tax_id || null, // Ensure null for numeric fields }); // Reload cart to ensure consistency @@ -102,18 +121,26 @@ const CartItemSelect = ({ item }: Props) => { // Revert local state on error setLocalSelected(!newSelectedState); - // Update cookie back - updateSelectedItemInCookie(item.id, !newSelectedState); + // Revert cookie change + updateSelectedItemInCookie(item.id, !newSelectedState, false); // Reload cart to get server state loadCart(auth.id); } finally { setIsUpdating(false); + + // End the update - notify global state with this checkbox's ID + checkboxUpdateState.endUpdate(item.id); } }, [auth, cart, item, isUpdating, updateCartItem, loadCart] ); + // Determine if THIS specific checkbox should be disabled - only disable + // if this specific checkbox is updating + const isDisabled = + isUpdating || checkboxUpdateState.isCheckboxUpdating(item.id); + return (
{ size='lg' isChecked={localSelected} onChange={handleChange} - isDisabled={isUpdating} - opacity={isUpdating ? 0.5 : 1} - cursor={isUpdating ? 'not-allowed' : 'pointer'} + isDisabled={isDisabled} + opacity={isDisabled ? 0.5 : 1} + cursor={isDisabled ? 'not-allowed' : 'pointer'} _disabled={{ opacity: 0.5, cursor: 'not-allowed', -- cgit v1.2.3 From 4904573845478e7e9648735d008153728870a123 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 23 May 2025 09:37:46 +0700 Subject: fix cookie not updating when delete an item --- src-migrate/modules/cart/components/ItemSelect.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src-migrate/modules/cart/components/ItemSelect.tsx') diff --git a/src-migrate/modules/cart/components/ItemSelect.tsx b/src-migrate/modules/cart/components/ItemSelect.tsx index 70b656ec..f580f81d 100644 --- a/src-migrate/modules/cart/components/ItemSelect.tsx +++ b/src-migrate/modules/cart/components/ItemSelect.tsx @@ -109,7 +109,7 @@ const CartItemSelect = ({ item }: Props) => { id: item.id, qty: item.quantity, selected: newSelectedState, - purchase_tax_id: item.purchase_tax_id || null, // Ensure null for numeric fields + // purchase_tax_id: item.purchase_tax_id || null, // Ensure null for numeric fields }); // Reload cart to ensure consistency -- cgit v1.2.3 From 1a247903bf7bb87e0a43b4e5e338ea67ec90e6de Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 26 May 2025 14:39:39 +0700 Subject: cleaning code --- src-migrate/modules/cart/components/ItemSelect.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src-migrate/modules/cart/components/ItemSelect.tsx') diff --git a/src-migrate/modules/cart/components/ItemSelect.tsx b/src-migrate/modules/cart/components/ItemSelect.tsx index f580f81d..00c7be43 100644 --- a/src-migrate/modules/cart/components/ItemSelect.tsx +++ b/src-migrate/modules/cart/components/ItemSelect.tsx @@ -68,8 +68,8 @@ const CartItemSelect = ({ item }: Props) => { // Fall back to server state if no cookie exists setLocalSelected(item.selected); - // Save this state to cookie for future use - updateSelectedItemInCookie(item.id, item.selected, false); // don't notify for initial sync + // Save state to cookie for future + updateSelectedItemInCookie(item.id, item.selected, false); } }, [item.id, item.selected, localSelected, cart, updateCartItem, isUpdating]); -- cgit v1.2.3 From 3feaad9127ff429b27f0eb69fa6ea539de2f2e8c Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 26 May 2025 20:00:17 +0700 Subject: Cleaning code --- src-migrate/modules/cart/components/ItemSelect.tsx | 76 +++++++--------------- 1 file changed, 25 insertions(+), 51 deletions(-) (limited to 'src-migrate/modules/cart/components/ItemSelect.tsx') diff --git a/src-migrate/modules/cart/components/ItemSelect.tsx b/src-migrate/modules/cart/components/ItemSelect.tsx index 00c7be43..8dbfe2bc 100644 --- a/src-migrate/modules/cart/components/ItemSelect.tsx +++ b/src-migrate/modules/cart/components/ItemSelect.tsx @@ -20,124 +20,98 @@ const CartItemSelect = ({ item }: Props) => { const { updateCartItem, cart, loadCart } = useCartStore(); const [isUpdating, setIsUpdating] = useState(false); const [localSelected, setLocalSelected] = useState(item.selected); - const [isGlobalUpdating, setIsGlobalUpdating] = useState(false); // Subscribe to global checkbox update state useEffect(() => { const handleUpdateStateChange = (isUpdating) => { - setIsGlobalUpdating(isUpdating); + // This component doesn't need to react to global state changes + // Individual checkboxes are managed independently }; checkboxUpdateState.addListener(handleUpdateStateChange); - - return () => { - checkboxUpdateState.removeListener(handleUpdateStateChange); - }; + return () => checkboxUpdateState.removeListener(handleUpdateStateChange); }, []); - // Initialize local state from cookie or server + // Sync local state with cookie and server data useEffect(() => { - if (isUpdating) return; // Skip if we're currently updating + if (isUpdating) return; - // Check cookie first const selectedItems = getSelectedItemsFromCookie(); const storedState = selectedItems[item.id]; if (storedState !== undefined) { - // Only update local state if it differs from current state + // Update local state if cookie differs if (localSelected !== storedState) { setLocalSelected(storedState); } - // If cookie state differs from server state and we're not in the middle of an update, - // synchronize the item state with cookie - if (storedState !== item.selected) { - // Update cart item silently to match cookie - if (cart) { - const updatedCartItems = cart.products.map((cartItem) => - cartItem.id === item.id - ? { ...cartItem, selected: storedState } - : cartItem - ); - - const updatedCart = { ...cart, products: updatedCartItems }; - updateCartItem(updatedCart); - } + // Sync cart state with cookie if needed + if (storedState !== item.selected && cart) { + const updatedCartItems = cart.products.map((cartItem) => + cartItem.id === item.id + ? { ...cartItem, selected: storedState } + : cartItem + ); + updateCartItem({ ...cart, products: updatedCartItems }); } } else { - // Fall back to server state if no cookie exists + // Initialize cookie with server state setLocalSelected(item.selected); - - // Save state to cookie for future updateSelectedItemInCookie(item.id, item.selected, false); } }, [item.id, item.selected, localSelected, cart, updateCartItem, isUpdating]); const handleChange = useCallback( async (e: React.ChangeEvent) => { - if (typeof auth !== 'object' || !cart || isUpdating) { - return; - } + if (typeof auth !== 'object' || !cart || isUpdating) return; const newSelectedState = e.target.checked; - // Update local state immediately for responsiveness + // Update local state immediately setLocalSelected(newSelectedState); setIsUpdating(true); - - // Start the update - notify global state with this checkbox's ID checkboxUpdateState.startUpdate(item.id); try { - // The cookie update is now handled inside the function with notification - updateSelectedItemInCookie(item.id, newSelectedState, false); // We already started above + // Update cookie immediately for responsive UI + updateSelectedItemInCookie(item.id, newSelectedState, false); - // Update cart state immediately for UI responsiveness + // Update cart state optimistically const updatedCartItems = cart.products.map((cartItem) => cartItem.id === item.id ? { ...cartItem, selected: newSelectedState } : cartItem ); + updateCartItem({ ...cart, products: updatedCartItems }); - const updatedCart = { ...cart, products: updatedCartItems }; - updateCartItem(updatedCart); - - // Save to server + // Send update to server await upsertUserCart({ userId: auth.id, type: item.cart_type, id: item.id, qty: item.quantity, selected: newSelectedState, - // purchase_tax_id: item.purchase_tax_id || null, // Ensure null for numeric fields + purchase_tax_id: item.purchase_tax_id || null, }); - // Reload cart to ensure consistency + // Reload cart for consistency await loadCart(auth.id); } catch (error) { console.error('Failed to update item selection:', error); toast.error('Gagal memperbarui pilihan barang'); - // Revert local state on error + // Revert changes on error setLocalSelected(!newSelectedState); - - // Revert cookie change updateSelectedItemInCookie(item.id, !newSelectedState, false); - - // Reload cart to get server state loadCart(auth.id); } finally { setIsUpdating(false); - - // End the update - notify global state with this checkbox's ID checkboxUpdateState.endUpdate(item.id); } }, [auth, cart, item, isUpdating, updateCartItem, loadCart] ); - // Determine if THIS specific checkbox should be disabled - only disable - // if this specific checkbox is updating const isDisabled = isUpdating || checkboxUpdateState.isCheckboxUpdating(item.id); -- cgit v1.2.3 From 2732c04b36f98a25895826b28003b1e2c56ad952 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 27 May 2025 09:05:10 +0700 Subject: remove purchase_tax_id and vendor_id --- src-migrate/modules/cart/components/ItemSelect.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src-migrate/modules/cart/components/ItemSelect.tsx') diff --git a/src-migrate/modules/cart/components/ItemSelect.tsx b/src-migrate/modules/cart/components/ItemSelect.tsx index 8dbfe2bc..72ab49aa 100644 --- a/src-migrate/modules/cart/components/ItemSelect.tsx +++ b/src-migrate/modules/cart/components/ItemSelect.tsx @@ -91,7 +91,8 @@ const CartItemSelect = ({ item }: Props) => { id: item.id, qty: item.quantity, selected: newSelectedState, - purchase_tax_id: item.purchase_tax_id || null, + // purchase_tax_id: item.purchase_tax_id, + // vendor_id: item.vendor_id }); // Reload cart for consistency -- cgit v1.2.3