From 7d4445bb9bad3d6c945503086a07bd882536e5f6 Mon Sep 17 00:00:00 2001
From: Miqdad <ahmadmiqdad27@gmail.com>
Date: Mon, 19 May 2025 11:02:19 +0700
Subject: <miqdad> fix unresponsive cart select

---
 src-migrate/pages/shop/cart/index.tsx | 395 +++++++++++++++++++++++++---------
 1 file changed, 289 insertions(+), 106 deletions(-)

(limited to 'src-migrate/pages/shop/cart/index.tsx')

diff --git a/src-migrate/pages/shop/cart/index.tsx b/src-migrate/pages/shop/cart/index.tsx
index 24baa933..475a4259 100644
--- a/src-migrate/pages/shop/cart/index.tsx
+++ b/src-migrate/pages/shop/cart/index.tsx
@@ -1,8 +1,16 @@
 import style from './cart.module.css';
 
-import React, { useEffect, useMemo, useRef, useState } from 'react';
+import React, { useEffect, useMemo, useState } from 'react';
 import Link from 'next/link';
-import { Button, Checkbox, Spinner, Tooltip } from '@chakra-ui/react';
+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';
@@ -14,26 +22,125 @@ 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 { CartItem } from '~/types/cart';
 import { deleteUserCart, upsertUserCart } from '~/services/cart';
 import { Trash2Icon } from 'lucide-react';
 import { useProductCartContext } from '@/contexts/ProductCartContext';
+import {
+  getSelectedItemsFromCookie,
+  setSelectedItemsToCookie,
+  syncSelectedItemsWithCookie,
+  setAllSelectedInCookie,
+  removeSelectedItemsFromCookie,
+  forceResetAllSelectedItems,
+} from '~/utils/cart';
 
 const CartPage = () => {
   const router = useRouter();
   const auth = getAuth();
   const [isStepApproval, setIsStepApproval] = useState(false);
-  const [isSelectedAll, setIsSelectedAll] = useState(false);
-  const [isButtonChek, setIsButtonChek] = useState(false);
-  const [buttonSelectNow, setButtonSelectNow] = useState(true);
   const [isLoad, setIsLoad] = useState<boolean>(false);
   const [isLoadDelete, setIsLoadDelete] = useState<boolean>(false);
   const { loadCart, cart, summary, updateCartItem } = useCartStore();
-  const useDivvice = useDevice();
+  const device = useDevice();
   const { setRefreshCart } = useProductCartContext();
   const [isTop, setIsTop] = useState(true);
-  const [hasChanged, setHasChanged] = useState(false);
-  const prevCartRef = useRef<CartItem[] | null>(null);
+  const [isUpdating, setIsUpdating] = useState(false);
+  const [isStateMismatch, setIsStateMismatch] = useState(false);
+
+  // 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,
+          })
+        );
+
+        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 = () => {
@@ -47,40 +154,35 @@ const CartPage = () => {
   }, []);
 
   useEffect(() => {
-    if (typeof auth === 'object' && !cart) {
-      loadCart(auth.id);
-      setIsStepApproval(auth?.feature?.soApproval);
-    }
-  }, [auth, loadCart, cart, isButtonChek]);
+    const loadCartWithStorage = async () => {
+      if (typeof auth === 'object' && !cart) {
+        await loadCart(auth.id);
+        setIsStepApproval(auth?.feature?.soApproval);
 
-  useEffect(() => {
-    if (typeof auth === 'object' && !cart) {
-      loadCart(auth.id);
-      setIsStepApproval(auth?.feature?.soApproval);
-    }
-  }, [auth, loadCart, cart, isButtonChek]);
+        // Sync selected items with server data using cookies
+        if (cart?.products) {
+          const { items, needsUpdate } = syncSelectedItemsWithCookie(
+            cart.products
+          );
 
-  useEffect(() => {
-    const hasSelectedChanged = () => {
-      if (prevCartRef.current && cart) {
-        const prevCart = prevCartRef.current;
-        return cart.products.some(
-          (item, index) =>
-            prevCart[index] && prevCart[index].selected !== item.selected
-        );
+          // 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);
+          }
+        }
       }
-      return false;
     };
 
-    if (hasSelectedChanged()) {
-      setHasChanged(true);
-      // Perform necessary actions here if selection has changed
-    } else {
-      setHasChanged(false);
-    }
-
-    prevCartRef.current = cart ? [...cart.products] : null;
-  }, [cart]);
+    loadCartWithStorage();
+  }, [auth, cart]);
 
   const hasSelectedPromo = useMemo(() => {
     if (!cart) return false;
@@ -103,38 +205,24 @@ const CartPage = () => {
 
   const hasSelectedAll = useMemo(() => {
     if (!cart || !Array.isArray(cart.products)) return false;
-    return cart.products.every((item) => item.selected);
+    return (
+      cart.products.length > 0 && cart.products.every((item) => item.selected)
+    );
   }, [cart]);
 
-  useEffect(() => {
-    const updateCartItems = async () => {
-      if (typeof auth === 'object' && cart) {
-        const upsertPromises = cart.products.map((item) =>
-          upsertUserCart({
-            userId: auth.id,
-            type: item.cart_type,
-            id: item.id,
-            qty: item.quantity,
-            selected: item.selected,
-          })
-        );
-        try {
-          await Promise.all(upsertPromises);
-          await loadCart(auth.id);
-        } catch (error) {
-          console.error('Failed to update cart items:', error);
-        }
-      }
-    };
-
-    updateCartItems();
-  }, [hasChanged]);
-
   const handleCheckout = () => {
+    if (isUpdating || isLoadDelete) {
+      toast.error('Harap tunggu pembaruan selesai');
+      return;
+    }
     router.push('/shop/checkout');
   };
 
   const handleQuotation = () => {
+    if (isUpdating || isLoadDelete) {
+      toast.error('Harap tunggu pembaruan selesai');
+      return;
+    }
     if (hasSelectedPromo || !hasSelected) {
       toast.error('Maaf, Barang promo tidak dapat dibuat quotation');
     } else {
@@ -143,20 +231,62 @@ const CartPage = () => {
   };
 
   const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
-    if (cart) {
+    if (cart && !isUpdating && typeof auth === 'object') {
+      const newSelectedState = !hasSelectedAll;
+
+      // Update UI immediately
       const updatedCart = {
         ...cart,
         products: cart.products.map((item) => ({
           ...item,
-          selected: !hasSelectedAll,
+          selected: newSelectedState,
         })),
       };
 
       updateCartItem(updatedCart);
-      if (hasSelectedAll) {
-        setIsSelectedAll(false);
-      } else {
-        setIsSelectedAll(true);
+
+      // Get all product IDs in cart
+      const productIds = cart.products.map((item) => item.id);
+
+      // Update cookies immediately for responsive UI
+      setAllSelectedInCookie(productIds, newSelectedState);
+
+      setIsUpdating(true);
+
+      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,
+          })
+        );
+
+        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);
+
+        toast.error('Gagal memperbarui pilihan');
+      } finally {
+        setIsUpdating(false);
       }
     }
   };
@@ -165,14 +295,25 @@ const CartPage = () => {
     if (typeof auth !== 'object' || !cart) return;
 
     setIsLoadDelete(true);
-    for (const item of cart.products) {
-      if (item.selected === true) {
+    try {
+      const itemsToDelete = cart.products.filter((item) => item.selected);
+      const itemIdsToDelete = itemsToDelete.map((item) => item.id);
+
+      for (const item of itemsToDelete) {
         await deleteUserCart(auth.id, [item.cart_id]);
-        await loadCart(auth.id);
       }
+
+      // Remove deleted items from cookie
+      removeSelectedItemsFromCookie(itemIdsToDelete);
+
+      await loadCart(auth.id);
+      setRefreshCart(true);
+    } catch (error) {
+      console.error('Failed to delete cart items:', error);
+      toast.error('Gagal menghapus item');
+    } finally {
+      setIsLoadDelete(false);
     }
-    setIsLoadDelete(false);
-    setRefreshCart(true);
   };
 
   return (
@@ -180,27 +321,45 @@ const CartPage = () => {
       <div
         className={`${
           isTop ? 'border-b-[0px]' : 'border-b-[1px]'
-        } sticky  md:top-[157px] flex-col  bg-white py-4 border-gray-300 z-50 sm:w-full md:w-3/4`}
+        } sticky md:top-[157px] flex-col bg-white py-4 border-gray-300 z-50 sm:w-full md:w-3/4`}
       >
-        <h1 className={`${style['title']}`}>Keranjang Belanja</h1>
+        <div className='flex items-center justify-between mb-2'>
+          <h1 className={style.title}>Keranjang Belanja</h1>
+          {isStateMismatch && (
+            <Button
+              colorScheme='red'
+              size='sm'
+              isLoading={isUpdating}
+              onClick={handleResetSelections}
+            >
+              Reset Pilihan
+            </Button>
+          )}
+        </div>
+
         <div className='h-2' />
-        <div className={`flex items-center object-center justify-between `}>
+        <div className='flex items-center object-center justify-between flex-wrap gap-2'>
           <div className='flex items-center object-center'>
-            {isLoad && <Spinner className='my-auto' size='sm' />}
-            {!isLoad && (
-              <Checkbox
-                borderColor='gray.600'
-                colorScheme='red'
-                size='lg'
-                isChecked={hasSelectedAll}
-                onChange={handleChange}
-              />
-            )}
+            <Checkbox
+              borderColor='gray.600'
+              colorScheme='red'
+              size='lg'
+              isChecked={hasSelectedAll}
+              onChange={handleChange}
+              isDisabled={isUpdating || isLoadDelete}
+              opacity={isUpdating ? 0.5 : 1}
+              cursor={isUpdating ? 'not-allowed' : 'pointer'}
+              _disabled={{
+                opacity: 0.5,
+                cursor: 'not-allowed',
+                backgroundColor: 'gray.100',
+              }}
+            />
             <p className='p-2 text-caption-2'>
               {hasSelectedAll ? 'Uncheck all' : 'Select all'}
             </p>
           </div>
-          <div className='delate all flex items-center object-center'>
+          <div className='flex items-center object-center'>
             <Tooltip
               label={clsxm({
                 'Tidak ada item yang dipilih': !hasSelected,
@@ -210,8 +369,9 @@ const CartPage = () => {
                 bg='#fadede'
                 variant='outline'
                 colorScheme='red'
-                w='full'
-                isDisabled={!hasSelected}
+                w='auto'
+                size={device.isMobile ? 'sm' : 'md'}
+                isDisabled={!hasSelected || isUpdating}
                 onClick={handleDelete}
               >
                 {isLoadDelete && <Spinner size='xs' />}
@@ -223,19 +383,19 @@ const CartPage = () => {
         </div>
       </div>
 
-      <div className={style['content']}>
+      <div className={style.content}>
         <div className={style['item-wrapper']}>
           <div className={style['item-skeleton']}>
             {!cart && <CartItemModule.Skeleton count={5} height='120px' />}
           </div>
 
-          <div className={style['items']}>
+          <div className={style.items}>
             {cart?.products?.map((item) => (
               <CartItemModule key={item.id} item={item} />
             ))}
 
             {cart?.products?.length === 0 && (
-              <div className='flex flex-col items-center'>
+              <div className='flex flex-col items-center p-4'>
                 <Image
                   src='/images/empty_cart.svg'
                   alt='Empty Cart'
@@ -263,15 +423,24 @@ const CartPage = () => {
         </div>
         <div
           className={`${style['summary-wrapper']} ${
-            useDivvice.isMobile && cart?.product_total === 0 ? 'hidden' : ''
+            device.isMobile && (!cart || cart?.product_total === 0)
+              ? 'hidden'
+              : ''
           }`}
         >
-          <div className={style['summary']}>
-            {useDivvice.isMobile && (
-              <CartSummaryMobile {...summary} isLoaded={!!cart} />
-            )}
-            {!useDivvice.isMobile && (
-              <CartSummary {...summary} isLoaded={!!cart} />
+          <div className={style.summary}>
+            {device.isMobile ? (
+              <CartSummaryMobile
+                {...summary}
+                isLoaded={!!cart}
+                products={cart?.products}
+              />
+            ) : (
+              <CartSummary
+                {...summary}
+                isLoaded={!!cart}
+                products={cart?.products}
+              />
             )}
 
             <div
@@ -282,17 +451,24 @@ const CartPage = () => {
               }
             >
               <Tooltip
-                label={
-                  hasSelectedPromo &&
-                  'Barang promo tidak dapat dibuat quotation'
-                }
+                label={clsxm({
+                  'Barang promo tidak dapat dibuat quotation': hasSelectedPromo,
+                  'Harap tunggu pembaruan selesai': isUpdating,
+                  'Tidak ada item yang dipilih': !hasSelected,
+                })}
               >
                 <Button
                   colorScheme='yellow'
                   w='full'
-                  isDisabled={hasSelectedPromo || !hasSelected}
+                  isDisabled={
+                    hasSelectedPromo ||
+                    !hasSelected ||
+                    isUpdating ||
+                    isLoadDelete
+                  }
                   onClick={handleQuotation}
                 >
+                  {isUpdating ? <Spinner size='sm' mr={2} /> : null}
                   Quotation
                 </Button>
               </Tooltip>
@@ -301,14 +477,21 @@ const CartPage = () => {
                   label={clsxm({
                     'Tidak ada item yang dipilih': !hasSelected,
                     'Terdapat item yang tidak ada harga': hasSelectNoPrice,
+                    'Harap tunggu pembaruan selesai': isUpdating,
                   })}
                 >
                   <Button
                     colorScheme='red'
                     w='full'
-                    isDisabled={!hasSelected || hasSelectNoPrice}
+                    isDisabled={
+                      !hasSelected ||
+                      hasSelectNoPrice ||
+                      isUpdating ||
+                      isLoadDelete
+                    }
                     onClick={handleCheckout}
                   >
+                    {isUpdating ? <Spinner size='sm' mr={2} /> : null}
                     Checkout
                   </Button>
                 </Tooltip>
-- 
cgit v1.2.3


From 09cebc9020c4f1995a73305187bc1576e339d183 Mon Sep 17 00:00:00 2001
From: Miqdad <ahmadmiqdad27@gmail.com>
Date: Thu, 22 May 2025 10:05:09 +0700
Subject: <miqdad> disable button when updating checkboxes and change summary
 design

---
 src-migrate/pages/shop/cart/index.tsx | 98 +++++++++++++++++++++++------------
 1 file changed, 64 insertions(+), 34 deletions(-)

(limited to 'src-migrate/pages/shop/cart/index.tsx')

diff --git a/src-migrate/pages/shop/cart/index.tsx b/src-migrate/pages/shop/cart/index.tsx
index 475a4259..eefe8d09 100644
--- a/src-migrate/pages/shop/cart/index.tsx
+++ b/src-migrate/pages/shop/cart/index.tsx
@@ -32,8 +32,12 @@ import {
   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();
@@ -46,6 +50,22 @@ const CartPage = () => {
   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 = () => {
@@ -91,11 +111,14 @@ const CartPage = () => {
       console.error('Error checking cart state mismatch:', error);
       return false;
     }
-  }; // Function to reset all selected items when state is inconsistent
+  };
+
+  // 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();
@@ -115,6 +138,7 @@ const CartPage = () => {
             id: item.id,
             qty: item.quantity,
             selected: false,
+            purchase_tax_id: item.purchase_tax_id || null, // Fix integer field issue
           })
         );
 
@@ -123,7 +147,9 @@ const CartPage = () => {
           .catch((error) => {
             console.error('Error updating selections to server:', error);
           })
-          .finally(() => setIsUpdating(false));
+          .finally(() => {
+            setIsUpdating(false);
+          });
       } else {
         setIsUpdating(false);
       }
@@ -211,7 +237,7 @@ const CartPage = () => {
   }, [cart]);
 
   const handleCheckout = () => {
-    if (isUpdating || isLoadDelete) {
+    if (isUpdating || isLoadDelete || isAnyCheckboxUpdating) {
       toast.error('Harap tunggu pembaruan selesai');
       return;
     }
@@ -219,7 +245,7 @@ const CartPage = () => {
   };
 
   const handleQuotation = () => {
-    if (isUpdating || isLoadDelete) {
+    if (isUpdating || isLoadDelete || isAnyCheckboxUpdating) {
       toast.error('Harap tunggu pembaruan selesai');
       return;
     }
@@ -234,6 +260,12 @@ const CartPage = () => {
     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,
@@ -249,9 +281,7 @@ const CartPage = () => {
       const productIds = cart.products.map((item) => item.id);
 
       // Update cookies immediately for responsive UI
-      setAllSelectedInCookie(productIds, newSelectedState);
-
-      setIsUpdating(true);
+      setAllSelectedInCookie(productIds, newSelectedState, false); // We're already notifying
 
       try {
         // Update all items on server in background
@@ -262,6 +292,7 @@ const CartPage = () => {
             id: item.id,
             qty: item.quantity,
             selected: newSelectedState,
+            purchase_tax_id: item.purchase_tax_id || null, // Fix integer field issue
           })
         );
 
@@ -282,11 +313,14 @@ const CartPage = () => {
         updateCartItem(revertedCart);
 
         // Revert cookies
-        setAllSelectedInCookie(productIds, !newSelectedState);
+        setAllSelectedInCookie(productIds, !newSelectedState, false);
 
         toast.error('Gagal memperbarui pilihan');
       } finally {
         setIsUpdating(false);
+
+        // End update notification
+        checkboxUpdateState.endUpdate(SELECT_ALL_ID);
       }
     }
   };
@@ -295,6 +329,8 @@ const CartPage = () => {
     if (typeof auth !== 'object' || !cart) return;
 
     setIsLoadDelete(true);
+    checkboxUpdateState.startUpdate('delete_operation'); // Use special ID for delete
+
     try {
       const itemsToDelete = cart.products.filter((item) => item.selected);
       const itemIdsToDelete = itemsToDelete.map((item) => item.id);
@@ -313,9 +349,18 @@ const CartPage = () => {
       toast.error('Gagal menghapus item');
     } 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 (
     <>
       <div
@@ -325,16 +370,6 @@ const CartPage = () => {
       >
         <div className='flex items-center justify-between mb-2'>
           <h1 className={style.title}>Keranjang Belanja</h1>
-          {isStateMismatch && (
-            <Button
-              colorScheme='red'
-              size='sm'
-              isLoading={isUpdating}
-              onClick={handleResetSelections}
-            >
-              Reset Pilihan
-            </Button>
-          )}
         </div>
 
         <div className='h-2' />
@@ -346,9 +381,9 @@ const CartPage = () => {
               size='lg'
               isChecked={hasSelectedAll}
               onChange={handleChange}
-              isDisabled={isUpdating || isLoadDelete}
-              opacity={isUpdating ? 0.5 : 1}
-              cursor={isUpdating ? 'not-allowed' : 'pointer'}
+              isDisabled={isSelectAllDisabled}
+              opacity={isSelectAllDisabled ? 0.5 : 1}
+              cursor={isSelectAllDisabled ? 'not-allowed' : 'pointer'}
               _disabled={{
                 opacity: 0.5,
                 cursor: 'not-allowed',
@@ -363,6 +398,7 @@ const CartPage = () => {
             <Tooltip
               label={clsxm({
                 'Tidak ada item yang dipilih': !hasSelected,
+                'Harap tunggu pembaruan selesai': areButtonsDisabled,
               })}
             >
               <Button
@@ -371,7 +407,7 @@ const CartPage = () => {
                 colorScheme='red'
                 w='auto'
                 size={device.isMobile ? 'sm' : 'md'}
-                isDisabled={!hasSelected || isUpdating}
+                isDisabled={!hasSelected || areButtonsDisabled}
                 onClick={handleDelete}
               >
                 {isLoadDelete && <Spinner size='xs' />}
@@ -453,7 +489,7 @@ const CartPage = () => {
               <Tooltip
                 label={clsxm({
                   'Barang promo tidak dapat dibuat quotation': hasSelectedPromo,
-                  'Harap tunggu pembaruan selesai': isUpdating,
+                  'Harap tunggu pembaruan selesai': areButtonsDisabled,
                   'Tidak ada item yang dipilih': !hasSelected,
                 })}
               >
@@ -461,14 +497,11 @@ const CartPage = () => {
                   colorScheme='yellow'
                   w='full'
                   isDisabled={
-                    hasSelectedPromo ||
-                    !hasSelected ||
-                    isUpdating ||
-                    isLoadDelete
+                    hasSelectedPromo || !hasSelected || areButtonsDisabled
                   }
                   onClick={handleQuotation}
                 >
-                  {isUpdating ? <Spinner size='sm' mr={2} /> : null}
+                  {areButtonsDisabled ? <Spinner size='sm' mr={2} /> : null}
                   Quotation
                 </Button>
               </Tooltip>
@@ -477,21 +510,18 @@ const CartPage = () => {
                   label={clsxm({
                     'Tidak ada item yang dipilih': !hasSelected,
                     'Terdapat item yang tidak ada harga': hasSelectNoPrice,
-                    'Harap tunggu pembaruan selesai': isUpdating,
+                    'Harap tunggu pembaruan selesai': areButtonsDisabled,
                   })}
                 >
                   <Button
                     colorScheme='red'
                     w='full'
                     isDisabled={
-                      !hasSelected ||
-                      hasSelectNoPrice ||
-                      isUpdating ||
-                      isLoadDelete
+                      !hasSelected || hasSelectNoPrice || areButtonsDisabled
                     }
                     onClick={handleCheckout}
                   >
-                    {isUpdating ? <Spinner size='sm' mr={2} /> : null}
+                    {areButtonsDisabled ? <Spinner size='sm' mr={2} /> : null}
                     Checkout
                   </Button>
                 </Tooltip>
-- 
cgit v1.2.3


From 4904573845478e7e9648735d008153728870a123 Mon Sep 17 00:00:00 2001
From: Miqdad <ahmadmiqdad27@gmail.com>
Date: Fri, 23 May 2025 09:37:46 +0700
Subject: <miqdad> fix cookie not updating when delete an item

---
 src-migrate/pages/shop/cart/index.tsx | 36 +++++++++++++++++++++++++++++++----
 1 file changed, 32 insertions(+), 4 deletions(-)

(limited to 'src-migrate/pages/shop/cart/index.tsx')

diff --git a/src-migrate/pages/shop/cart/index.tsx b/src-migrate/pages/shop/cart/index.tsx
index eefe8d09..798ad318 100644
--- a/src-migrate/pages/shop/cart/index.tsx
+++ b/src-migrate/pages/shop/cart/index.tsx
@@ -329,24 +329,52 @@ const CartPage = () => {
     if (typeof auth !== 'object' || !cart) return;
 
     setIsLoadDelete(true);
-    checkboxUpdateState.startUpdate('delete_operation'); // Use special ID for delete
+    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]);
       }
 
-      // Remove deleted items from cookie
-      removeSelectedItemsFromCookie(itemIdsToDelete);
+      // 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);
 
-      await loadCart(auth.id);
+      // 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');
-- 
cgit v1.2.3


From 3feaad9127ff429b27f0eb69fa6ea539de2f2e8c Mon Sep 17 00:00:00 2001
From: Miqdad <ahmadmiqdad27@gmail.com>
Date: Mon, 26 May 2025 20:00:17 +0700
Subject: <miqdad> Cleaning code

---
 src-migrate/pages/shop/cart/index.tsx | 380 ++++++++++++----------------------
 1 file changed, 135 insertions(+), 245 deletions(-)

(limited to 'src-migrate/pages/shop/cart/index.tsx')

diff --git a/src-migrate/pages/shop/cart/index.tsx b/src-migrate/pages/shop/cart/index.tsx
index 798ad318..03854d79 100644
--- a/src-migrate/pages/shop/cart/index.tsx
+++ b/src-migrate/pages/shop/cart/index.tsx
@@ -2,15 +2,7 @@ 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 { Button, Checkbox, Spinner, Tooltip } from '@chakra-ui/react';
 import { toast } from 'react-hot-toast';
 import { useRouter } from 'next/router';
 import { getAuth } from '~/libs/auth';
@@ -27,171 +19,65 @@ import { Trash2Icon } from 'lucide-react';
 import { useProductCartContext } from '@/contexts/ProductCartContext';
 import {
   getSelectedItemsFromCookie,
-  setSelectedItemsToCookie,
   syncSelectedItemsWithCookie,
   setAllSelectedInCookie,
   removeSelectedItemsFromCookie,
-  forceResetAllSelectedItems,
+  removeCartItemsFromCookie,
   checkboxUpdateState,
+  quantityUpdateState,
 } 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<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 [isStateMismatch, setIsStateMismatch] = useState(false);
   const [isAnyCheckboxUpdating, setIsAnyCheckboxUpdating] = useState(false);
+  const [isAnyQuantityUpdating, setIsAnyQuantityUpdating] = useState(false);
 
-  // Subscribe to checkbox update state changes
+  // Subscribe to update state changes
   useEffect(() => {
-    const handleUpdateStateChange = (isUpdating) => {
+    const handleCheckboxUpdate = (isUpdating) =>
       setIsAnyCheckboxUpdating(isUpdating);
-    };
+    const handleQuantityUpdate = (isUpdating) =>
+      setIsAnyQuantityUpdating(isUpdating);
 
-    // Add listener for checkbox update state changes
-    checkboxUpdateState.addListener(handleUpdateStateChange);
+    checkboxUpdateState.addListener(handleCheckboxUpdate);
+    quantityUpdateState.addListener(handleQuantityUpdate);
 
-    // Cleanup listener on component unmount
     return () => {
-      checkboxUpdateState.removeListener(handleUpdateStateChange);
+      checkboxUpdateState.removeListener(handleCheckboxUpdate);
+      quantityUpdateState.removeListener(handleQuantityUpdate);
     };
   }, []);
 
-  // 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]);
-
+  // Handle scroll for sticky header styling
   useEffect(() => {
-    const handleScroll = () => {
-      setIsTop(window.scrollY < 200);
-    };
+    const handleScroll = () => setIsTop(window.scrollY < 200);
 
     window.addEventListener('scroll', handleScroll);
-    return () => {
-      window.removeEventListener('scroll', handleScroll);
-    };
+    return () => window.removeEventListener('scroll', handleScroll);
   }, []);
 
+  // Initialize cart and sync with cookies
   useEffect(() => {
-    const loadCartWithStorage = async () => {
+    const initializeCart = 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,
@@ -207,37 +93,47 @@ const CartPage = () => {
       }
     };
 
-    loadCartWithStorage();
-  }, [auth, cart]);
+    initializeCart();
+  }, [auth, cart, loadCart, updateCartItem]);
 
+  // Computed values
   const hasSelectedPromo = useMemo(() => {
-    if (!cart) return false;
-    return cart?.products?.some(
-      (item) => item.cart_type === 'promotion' && item.selected
+    return (
+      cart?.products?.some(
+        (item) => item.cart_type === 'promotion' && item.selected
+      ) || false
     );
   }, [cart]);
 
   const hasSelected = useMemo(() => {
-    if (!cart) return false;
-    return cart?.products?.some((item) => item.selected);
+    return cart?.products?.some((item) => item.selected) || false;
   }, [cart]);
 
   const hasSelectNoPrice = useMemo(() => {
-    if (!cart) return false;
-    return cart?.products?.some(
-      (item) => item.selected && item.price.price_discount === 0
+    return (
+      cart?.products?.some(
+        (item) => item.selected && item.price.price_discount === 0
+      ) || false
     );
   }, [cart]);
 
   const hasSelectedAll = useMemo(() => {
-    if (!cart || !Array.isArray(cart.products)) return false;
-    return (
-      cart.products.length > 0 && cart.products.every((item) => item.selected)
-    );
+    if (!cart?.products?.length) return false;
+    return cart.products.every((item) => item.selected);
   }, [cart]);
 
+  // Button states
+  const areButtonsDisabled =
+    isUpdating ||
+    isLoadDelete ||
+    isAnyCheckboxUpdating ||
+    isAnyQuantityUpdating;
+  const isSelectAllDisabled =
+    isUpdating || checkboxUpdateState.isCheckboxUpdating(SELECT_ALL_ID);
+
+  // Handlers
   const handleCheckout = () => {
-    if (isUpdating || isLoadDelete || isAnyCheckboxUpdating) {
+    if (areButtonsDisabled) {
       toast.error('Harap tunggu pembaruan selesai');
       return;
     }
@@ -245,7 +141,7 @@ const CartPage = () => {
   };
 
   const handleQuotation = () => {
-    if (isUpdating || isLoadDelete || isAnyCheckboxUpdating) {
+    if (areButtonsDisabled) {
       toast.error('Harap tunggu pembaruan selesai');
       return;
     }
@@ -256,16 +152,14 @@ const CartPage = () => {
     }
   };
 
-  const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
-    if (cart && !isUpdating && typeof auth === 'object') {
-      const newSelectedState = !hasSelectedAll;
+  const handleSelectAll = async (e: React.ChangeEvent<HTMLInputElement>) => {
+    if (!cart || isUpdating || typeof auth !== 'object') return;
 
-      // Set updating flag
-      setIsUpdating(true);
-
-      // Notify checkbox update state system with the special select all ID
-      checkboxUpdateState.startUpdate(SELECT_ALL_ID);
+    const newSelectedState = !hasSelectedAll;
+    setIsUpdating(true);
+    checkboxUpdateState.startUpdate(SELECT_ALL_ID);
 
+    try {
       // Update UI immediately
       const updatedCart = {
         ...cart,
@@ -274,54 +168,47 @@ const CartPage = () => {
           selected: newSelectedState,
         })),
       };
-
       updateCartItem(updatedCart);
 
-      // Get all product IDs in cart
+      // Update cookies
       const productIds = cart.products.map((item) => item.id);
+      setAllSelectedInCookie(productIds, newSelectedState, false);
+
+      // Update server
+      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,
+        })
+      );
 
-      // 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);
+      await Promise.all(updatePromises);
+      await loadCart(auth.id);
+    } catch (error) {
+      console.error('Error updating select all:', error);
+      toast.error('Gagal memperbarui pilihan');
 
-        // End update notification
-        checkboxUpdateState.endUpdate(SELECT_ALL_ID);
-      }
+      // Revert on error
+      const revertedCart = {
+        ...cart,
+        products: cart.products.map((item) => ({
+          ...item,
+          selected: !newSelectedState,
+        })),
+      };
+      updateCartItem(revertedCart);
+      setAllSelectedInCookie(
+        cart.products.map((item) => item.id),
+        !newSelectedState,
+        false
+      );
+    } finally {
+      setIsUpdating(false);
+      checkboxUpdateState.endUpdate(SELECT_ALL_ID);
     }
   };
 
@@ -336,12 +223,12 @@ const CartPage = () => {
       const itemIdsToDelete = itemsToDelete.map((item) => item.id);
       const cartIdsToDelete = itemsToDelete.map((item) => item.cart_id);
 
-      // Step 1: Delete from server first
+      // Delete from server
       for (const item of itemsToDelete) {
         await deleteUserCart(auth.id, [item.cart_id]);
       }
 
-      // Step 2: Update local cart state immediately (optimistic update)
+      // Update local state optimistically
       const updatedProducts = cart.products.filter((item) => !item.selected);
       const updatedCart = {
         ...cart,
@@ -350,30 +237,20 @@ const CartPage = () => {
       };
       updateCartItem(updatedCart);
 
-      // Step 3: Clean up cookies AFTER state update
+      // Clean up cookies
       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
-        });
+      // Reload from server
+      loadCart(auth.id).catch((error) =>
+        console.error('Error reloading cart:', error)
+      );
 
-      // 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);
@@ -381,16 +258,41 @@ const CartPage = () => {
     }
   };
 
-  // Check if buttons should be disabled
-  const areButtonsDisabled =
-    isUpdating || isLoadDelete || isAnyCheckboxUpdating;
+  // Tooltip messages
+  const getTooltipMessage = () => {
+    if (isAnyQuantityUpdating) return 'Harap tunggu update quantity selesai';
+    if (isAnyCheckboxUpdating) return 'Harap tunggu pembaruan checkbox selesai';
+    if (isLoadDelete) return 'Harap tunggu penghapusan selesai';
+    if (isUpdating) return 'Harap tunggu pembaruan selesai';
+    return '';
+  };
 
-  // Only disable the select all checkbox if it specifically is updating
-  const isSelectAllDisabled =
-    isUpdating || checkboxUpdateState.isCheckboxUpdating(SELECT_ALL_ID);
+  const getQuotationTooltip = () => {
+    const baseMessage = getTooltipMessage();
+    if (baseMessage) return baseMessage;
+    if (hasSelectedPromo) return 'Barang promo tidak dapat dibuat quotation';
+    if (!hasSelected) return 'Tidak ada item yang dipilih';
+    return '';
+  };
+
+  const getCheckoutTooltip = () => {
+    const baseMessage = getTooltipMessage();
+    if (baseMessage) return baseMessage;
+    if (!hasSelected) return 'Tidak ada item yang dipilih';
+    if (hasSelectNoPrice) return 'Terdapat item yang tidak ada harga';
+    return '';
+  };
+
+  const getDeleteTooltip = () => {
+    const baseMessage = getTooltipMessage();
+    if (baseMessage) return baseMessage;
+    if (!hasSelected) return 'Tidak ada item yang dipilih';
+    return '';
+  };
 
   return (
     <>
+      {/* Sticky Header */}
       <div
         className={`${
           isTop ? 'border-b-[0px]' : 'border-b-[1px]'
@@ -408,7 +310,7 @@ const CartPage = () => {
               colorScheme='red'
               size='lg'
               isChecked={hasSelectedAll}
-              onChange={handleChange}
+              onChange={handleSelectAll}
               isDisabled={isSelectAllDisabled}
               opacity={isSelectAllDisabled ? 0.5 : 1}
               cursor={isSelectAllDisabled ? 'not-allowed' : 'pointer'}
@@ -422,13 +324,9 @@ const CartPage = () => {
               {hasSelectedAll ? 'Uncheck all' : 'Select all'}
             </p>
           </div>
+
           <div className='flex items-center object-center'>
-            <Tooltip
-              label={clsxm({
-                'Tidak ada item yang dipilih': !hasSelected,
-                'Harap tunggu pembaruan selesai': areButtonsDisabled,
-              })}
-            >
+            <Tooltip label={getDeleteTooltip()}>
               <Button
                 bg='#fadede'
                 variant='outline'
@@ -447,6 +345,7 @@ const CartPage = () => {
         </div>
       </div>
 
+      {/* Main Content */}
       <div className={style.content}>
         <div className={style['item-wrapper']}>
           <div className={style['item-skeleton']}>
@@ -485,6 +384,8 @@ const CartPage = () => {
             )}
           </div>
         </div>
+
+        {/* Cart Summary */}
         <div
           className={`${style['summary-wrapper']} ${
             device.isMobile && (!cart || cart?.product_total === 0)
@@ -514,13 +415,7 @@ const CartPage = () => {
                   : style['summary-buttons']
               }
             >
-              <Tooltip
-                label={clsxm({
-                  'Barang promo tidak dapat dibuat quotation': hasSelectedPromo,
-                  'Harap tunggu pembaruan selesai': areButtonsDisabled,
-                  'Tidak ada item yang dipilih': !hasSelected,
-                })}
-              >
+              <Tooltip label={getQuotationTooltip()}>
                 <Button
                   colorScheme='yellow'
                   w='full'
@@ -529,18 +424,13 @@ const CartPage = () => {
                   }
                   onClick={handleQuotation}
                 >
-                  {areButtonsDisabled ? <Spinner size='sm' mr={2} /> : null}
+                  {areButtonsDisabled && <Spinner size='sm' mr={2} />}
                   Quotation
                 </Button>
               </Tooltip>
+
               {!isStepApproval && (
-                <Tooltip
-                  label={clsxm({
-                    'Tidak ada item yang dipilih': !hasSelected,
-                    'Terdapat item yang tidak ada harga': hasSelectNoPrice,
-                    'Harap tunggu pembaruan selesai': areButtonsDisabled,
-                  })}
-                >
+                <Tooltip label={getCheckoutTooltip()}>
                   <Button
                     colorScheme='red'
                     w='full'
@@ -549,7 +439,7 @@ const CartPage = () => {
                     }
                     onClick={handleCheckout}
                   >
-                    {areButtonsDisabled ? <Spinner size='sm' mr={2} /> : null}
+                    {areButtonsDisabled && <Spinner size='sm' mr={2} />}
                     Checkout
                   </Button>
                 </Tooltip>
@@ -562,4 +452,4 @@ const CartPage = () => {
   );
 };
 
-export default CartPage;
+export default CartPage;
\ No newline at end of file
-- 
cgit v1.2.3

