added price filter, changed mobile filter ui

This commit is contained in:
@jcarymuhammedow
2026-02-25 20:31:49 +05:00
parent 53346b5a7b
commit 4e58062899
16 changed files with 530 additions and 536 deletions

View File

@@ -13,8 +13,8 @@ import {
useUpdateCartItemMutation,
useCleanCartMutation,
} from "../../app/api/cartApi";
import { useCart } from "../../app/api/useCart";
import { DecreaseIcon, IncreaseIcon } from "../../components/Icons";
import { debounce } from "lodash";
import Loader from "../../components/Loader/index";
const TruncatedDescription = ({ description, maxLength = 100 }) => {
@@ -35,8 +35,8 @@ const TruncatedDescription = ({ description, maxLength = 100 }) => {
__html: isExpanded
? description
: shouldTruncate
? description.substring(0, maxLength) + "..."
: description,
? description.substring(0, maxLength) + "..."
: description,
}}
/>
</div>
@@ -44,25 +44,8 @@ const TruncatedDescription = ({ description, maxLength = 100 }) => {
};
const CartPage = () => {
const {
data: response = {},
error,
isError,
isLoading,
} = useGetCartQuery(undefined, {
refetchOnMountOrArgChange: 30, // ✅ Sadece 30 saniye sonra mount'ta refetch
refetchOnFocus: false,
refetchOnReconnect: false,
});
const { cartData, cartItems, isLoading, isError, error } = useCart();
// Handle the new data structure - data is now an object grouped by store
const cartData = isError ? {} : (response.data || {});
// Convert object of arrays to flat array for backward compatibility
const cartItems = useMemo(() => {
return Object.values(cartData).flat();
}, [cartData]);
const { t, i18n } = useTranslation();
const [checkoutStores, setCheckoutStores] = useState({});
const [addToCart] = useAddToCartMutation();
@@ -89,20 +72,22 @@ const CartPage = () => {
// Convert grouped data to stores array
const stores = useMemo(() => {
return Object.entries(cartData).map(([storeSlug, items]) => {
if (!items || !items.length) return null;
// Get store info from first item
const storeInfo = items[0]?.product?.channel?.[0];
return {
id: storeInfo?.id || storeSlug,
name: storeInfo?.name || storeSlug,
slug: storeSlug,
shipping_price: storeInfo?.shipping_price,
items: items
};
}).filter(Boolean);
return Object.entries(cartData)
.map(([storeSlug, items]) => {
if (!items || !items.length) return null;
// Get store info from first item
const storeInfo = items[0]?.product?.channel?.[0];
return {
id: storeInfo?.id || storeSlug,
name: storeInfo?.name || storeSlug,
slug: storeSlug,
shipping_price: storeInfo?.shipping_price,
items: items,
};
})
.filter(Boolean);
}, [cartData]);
// ✅ Initialize local quantities from cart items
@@ -121,73 +106,56 @@ const CartPage = () => {
setPendingQuantities(newPendingQuantities);
}, [cartItems]);
// ✅ Debounced update - tek bir useEffect
// ✅ Debounced Cart Update - Her ürün için ayrı debounce
useEffect(() => {
const debouncedUpdates = {};
const timers = {};
const updateItem = async (productId) => {
const serverItem = cartItems.find((item) => item.product.id === productId);
const serverQuantity = serverItem ? parseInt(serverItem.product_quantity, 10) : 0;
Object.keys(pendingQuantities).forEach((productId) => {
const serverItem = cartItems.find(
(item) => String(item.product.id) === String(productId),
);
const serverQuantity = serverItem
? parseInt(serverItem.product_quantity, 10)
: 0;
const pendingQuantity = pendingQuantities[productId];
// ✅ Eğer değişiklik yoksa, güncelleme yapma
if (pendingQuantity === undefined || pendingQuantity === serverQuantity) {
// Değişiklik yoksa veya 0 ise (Delete modalı tetikler) bir şey yapma
if (
pendingQuantity === undefined ||
pendingQuantity === serverQuantity ||
pendingQuantity <= 0
) {
return;
}
try {
setLoadingItems((prev) => ({ ...prev, [productId]: true }));
if (pendingQuantity <= 0) {
await removeFromCart({ productId }).unwrap();
} else {
timers[productId] = setTimeout(async () => {
try {
setLoadingItems((prev) => ({ ...prev, [productId]: true }));
await updateCartItem({
productId,
quantity: pendingQuantity,
}).unwrap();
}
// ✅ RTK Query otomatik cache'i güncelleyecek, refetch'e gerek yok
} catch (error) {
console.error("Failed to update cart:", error);
// ✅ Hata durumunda geri al
const originalItem = cartItems.find(
(item) => item.product.id === productId
);
if (originalItem) {
const originalQty = parseInt(originalItem.product_quantity, 10) || 0;
} catch (error) {
console.error("Failed to update cart:", error);
// Hata durumunda rollback
setLocalQuantities((prev) => ({
...prev,
[productId]: originalQty,
[productId]: serverQuantity,
}));
setPendingQuantities((prev) => ({
...prev,
[productId]: originalQty,
[productId]: serverQuantity,
}));
} finally {
setLoadingItems((prev) => ({ ...prev, [productId]: false }));
}
} finally {
setLoadingItems((prev) => ({ ...prev, [productId]: false }));
}
};
// ✅ Her productId için debounced update oluştur
Object.keys(pendingQuantities).forEach((productId) => {
if (!debouncedUpdates[productId]) {
debouncedUpdates[productId] = debounce(
() => updateItem(productId),
500 // ✅ 500ms debounce (daha stabil)
);
}
debouncedUpdates[productId]();
}, 500);
});
return () => {
Object.values(debouncedUpdates).forEach((debouncedFn) =>
debouncedFn.cancel()
);
Object.values(timers).forEach((timer) => clearTimeout(timer));
};
}, [pendingQuantities, cartItems, updateCartItem, removeFromCart]);
}, [pendingQuantities, cartItems, updateCartItem]);
const handleQuantityIncrease = (productId) => (event) => {
event.preventDefault();
@@ -245,27 +213,25 @@ const CartPage = () => {
}, 0);
};
const getStoreShippingPrice = (store) => {
return store.shipping_price !== null && store.shipping_price !== undefined
? parseFloat(store.shipping_price)
return store.shipping_price !== null && store.shipping_price !== undefined
? parseFloat(store.shipping_price)
: 20;
};
const handleCheckout = (storeId) => {
setCheckoutStores(prev => ({ ...prev, [storeId]: true }));
setCheckoutStores((prev) => ({ ...prev, [storeId]: true }));
};
const handleBackToCart = (storeId) => {
setCheckoutStores(prev => ({ ...prev, [storeId]: false }));
setCheckoutStores((prev) => ({ ...prev, [storeId]: false }));
};
const handleOrderSubmit = async (storeId, storeItems) => {
if (checkoutStores[storeId] && checkoutRefs.current[storeId]) {
const success = await checkoutRefs.current[storeId]();
if (success) {
setCheckoutStores(prev => ({ ...prev, [storeId]: false }));
setCheckoutStores((prev) => ({ ...prev, [storeId]: false }));
}
} else {
handleCheckout(storeId);
@@ -292,7 +258,7 @@ const CartPage = () => {
if (itemToDelete) {
try {
await removeFromCart({ productId: itemToDelete }).unwrap();
setLocalQuantities((prev) => {
const newState = { ...prev };
delete newState[itemToDelete];
@@ -318,9 +284,7 @@ const CartPage = () => {
const handleEmptyCartConfirm = async () => {
try {
await cleanCart().unwrap();
setLocalQuantities({});
setPendingQuantities({});
setCheckoutStores({});
@@ -333,7 +297,7 @@ const CartPage = () => {
const getTotalItemCount = () => {
return cartItems.reduce(
(sum, item) => sum + parseInt(item.product_quantity, 10),
0
0,
);
};
@@ -397,14 +361,14 @@ const CartPage = () => {
<Checkout
cartItems={store.items}
shippingPrice={shippingPrice}
productIds={store.items.map(item => item.product.id)}
productIds={store.items.map((item) => item.product.id)}
onBackToCart={() => handleBackToCart(store.id)}
onPlaceOrder={(placeOrderFn) => {
checkoutRefs.current[store.id] = placeOrderFn;
}}
/>
) : (
<div style={{background:"white", width: "100%"}}>
<div style={{ background: "white", width: "100%" }}>
<div className={styles.storeHeader}>
<h3>{store.name}</h3>
</div>
@@ -427,23 +391,32 @@ const CartPage = () => {
</div>
<div className={styles.priceQuantity}>
<span className={styles.price}>
{(parseFloat(item.product.price_amount) || 0).toFixed(2)} m.
{(
parseFloat(item.product.price_amount) || 0
).toFixed(2)}{" "}
m.
</span>
<div className={styles.quantityControls}>
<button
onClick={handleQuantityDecrease(item.product.id)}
onClick={handleQuantityDecrease(
item.product.id,
)}
className={styles.quantityBtn}
disabled={loadingItems[item.product.id]}
>
<DecreaseIcon />
</button>
<span>
{localQuantities[item.product.id] !== undefined
{localQuantities[item.product.id] !==
undefined
? localQuantities[item.product.id]
: parseInt(item.product_quantity, 10) || 0}
: parseInt(item.product_quantity, 10) ||
0}
</span>
<button
onClick={handleQuantityIncrease(item.product.id)}
onClick={handleQuantityIncrease(
item.product.id,
)}
className={styles.quantityBtn}
disabled={loadingItems[item.product.id]}
>
@@ -454,7 +427,9 @@ const CartPage = () => {
<div className={styles.deleteBtnContainer}>
<button
className={styles.deleteBtn}
onClick={() => showDeleteConfirm(item.product.id)}
onClick={() =>
showDeleteConfirm(item.product.id)
}
>
<FaTrashAlt />
</button>
@@ -468,7 +443,9 @@ const CartPage = () => {
<div className={styles.storeSummary}>
<div className={styles.cartContent}>
<h3>{store.name} - {t("cart.basket")}:</h3>
<h3>
{store.name} - {t("cart.basket")}:
</h3>
<div className={styles.summaryRow}>
<span>{t("cart.price")}:</span>
<span>{storeTotal.toFixed(2)} m.</span>
@@ -486,7 +463,9 @@ const CartPage = () => {
onClick={() => handleOrderSubmit(store.id, store.items)}
className={styles.checkoutBtn}
>
{checkoutStores[store.id] ? t("cart.order") : t("cart.prepareOrders")}
{checkoutStores[store.id]
? t("cart.order")
: t("cart.prepareOrders")}
</button>
</div>
</div>
@@ -547,4 +526,4 @@ const CartPage = () => {
);
};
export default CartPage;
export default CartPage;