From 517dce1136351f57e68e08f632635ac0a1ab6bff Mon Sep 17 00:00:00 2001 From: Mqdd Date: Mon, 15 Dec 2025 13:10:42 +0700 Subject: fix bug select variant --- .../product-detail/components/Information.tsx | 145 ++++++++++----------- .../product-detail/components/ProductDetail.tsx | 12 ++ 2 files changed, 78 insertions(+), 79 deletions(-) diff --git a/src-migrate/modules/product-detail/components/Information.tsx b/src-migrate/modules/product-detail/components/Information.tsx index a7a58cbc..813b6bf5 100644 --- a/src-migrate/modules/product-detail/components/Information.tsx +++ b/src-migrate/modules/product-detail/components/Information.tsx @@ -11,12 +11,11 @@ import Link from 'next/link'; import { useEffect, useRef, useState } from 'react'; import currencyFormat from '@/core/utils/currencyFormat'; -import { InputGroup, InputRightElement, Spinner } from '@chakra-ui/react'; +import { InputGroup, InputRightElement } from '@chakra-ui/react'; import { ChevronDownIcon } from '@heroicons/react/24/outline'; import Image from 'next/image'; import { formatToShortText } from '~/libs/formatNumber'; import { createSlug } from '~/libs/slug'; -import { getVariantSLA } from '~/services/productVariant'; import { IProductDetail } from '~/types/product'; import { useProductDetail } from '../stores/useProductDetail'; import useVariant from '../hook/useVariant'; @@ -30,67 +29,57 @@ type Props = { }; const Information = ({ product }: Props) => { - const { selectedVariant, setSelectedVariant, setSla, setActive, sla } = + const { selectedVariant, setSelectedVariant, setSla, sla } = useProductDetail(); - const [inputValue, setInputValue] = useState( - selectedVariant?.code + ' - ' + selectedVariant?.attributes[0] - ); + const [inputValue, setInputValue] = useState(''); const [disableFilter, setDisableFilter] = useState(false); const inputRef = useRef(null); - const [variantOptions, setVariantOptions] = useState( - product?.variants - ); + // source of truth + const variantOptions = product.variants; + const variantId = selectedVariant?.id; const { slaVariant, isLoading } = useVariant({ variantId }); - // let variantOptions = product?.variants; - - // const querySLA = useQuery({ - // queryKey: ['variant-sla', selectedVariant?.id], - // queryFn: () => getVariantSLA(selectedVariant?.id), - // enabled: !!selectedVariant?.id, - // }); - // const sla = querySLA?.data; - + /* ====================== + * Sync input text + * ====================== */ useEffect(() => { - if (selectedVariant) { - setInputValue( - selectedVariant?.code + - (selectedVariant?.attributes[0] - ? ' - ' + selectedVariant?.attributes[0] - : '') - ); - } + if (!selectedVariant) return; + + setInputValue( + selectedVariant.code + + (selectedVariant.attributes?.[0] + ? ` - ${selectedVariant.attributes[0]}` + : '') + ); }, [selectedVariant]); + /* ====================== + * Sync SLA + * ====================== */ useEffect(() => { - if (isLoading){ + if (isLoading) { setSla(null); + return; } if (slaVariant) { setSla(slaVariant); } - }, [slaVariant, isLoading]); - + }, [slaVariant, isLoading, setSla]); - const handleOnChange = (vals: any) => { + /* ====================== + * Handlers + * ====================== */ + const handleOnChange = (value: string) => { setDisableFilter(true); - let code = vals.replace(/\s-\s.*$/, '').trim(); - let variant = variantOptions.find((item) => item.code === code); + + const variant = variantOptions.find((item) => String(item.id) === value); + + if (!variant) return; + setSelectedVariant(variant); - setInputValue( - variant?.code + - (variant?.attributes[0] ? ' - ' + variant?.attributes[0] : '') - ); - if (variant) { - const filteredOptions = product?.variants.filter( - (item) => item !== variant - ); - const newOptions = [variant, ...filteredOptions]; - setVariantOptions(newOptions); - } }; const handleOnKeyUp = (e: any) => { @@ -100,80 +89,72 @@ const Information = ({ product }: Props) => { return (
-
+ {/* ===== Variant Selector ===== */} +
+ handleOnChange(vals)} + onChange={handleOnChange} > handleOnKeyUp(e)} + value={inputValue} + onChange={handleOnKeyUp} onFocus={() => setDisableFilter(true)} /> inputRef?.current?.focus()} + className='h-6 w-6 text-gray-500 cursor-pointer' + onClick={() => inputRef.current?.focus()} /> - {variantOptions.map((option, cid) => ( + {variantOptions.map((option) => ( -
+
- {option.code + - (option?.attributes[0] - ? ' - ' + option?.attributes[0] - : '')} + {option.code} + {option.attributes?.[0] ? ` - ${option.attributes[0]}` : ''}
+
- {option?.price?.discount_percentage > 0 && ( + {option.price?.discount_percentage > 0 && ( <>
- {Math.floor(option?.price?.discount_percentage)}% + {Math.floor(option.price.discount_percentage)}%
- {currencyFormat(option?.price?.price)} + {currencyFormat(option.price.price)}
)}
- {currencyFormat(option?.price?.price_discount)} + {currencyFormat(option.price.price_discount)}
@@ -183,10 +164,12 @@ const Information = ({ product }: Props) => {
+ {/* ===== Info Rows ===== */}
Item Code
{selectedVariant?.code}
+
Manufacture
@@ -198,7 +181,7 @@ const Information = ({ product }: Props) => { product.manufacture.id.toString() )} > - {product?.manufacture.logo ? ( + {product.manufacture.logo ? ( { )}
+
Berat Barang
- {selectedVariant?.weight > 0 ? `${selectedVariant?.weight} Kg` : '-'} + {selectedVariant?.weight > 0 ? `${selectedVariant.weight} Kg` : '-'}
+
Terjual
{product.qty_sold > 0 ? formatToShortText(product.qty_sold) : '-'}
+
Persiapan Barang
- {isLoading && ( + {isLoading ? (
+ ) : ( +
{sla?.sla_date}
)} - {!isLoading &&
{sla?.sla_date}
}
); diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx index 085bbb1c..129ca8de 100644 --- a/src-migrate/modules/product-detail/components/ProductDetail.tsx +++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx @@ -83,6 +83,18 @@ const ProductDetail = ({ product }: Props) => { setAskAdminUrl(createdAskUrl); }, [router.asPath, product.manufacture.name, product.name, setAskAdminUrl]); + // useEffect(() => { + // if (!product?.variants?.length) return; + + // setIsApproval(auth?.feature?.soApproval); + + // setSelectedVariant((prev: any) => { + // if (prev) return prev; + + // return product.variants[0]; + // }); + // }, [product?.id]); + useEffect(() => { if (typeof auth === 'object') { setIsApproval(auth?.feature?.soApproval); -- cgit v1.2.3 From ec7ab4c654fc5b29b277d42ad84986f4c1220134 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Tue, 13 Jan 2026 17:56:33 +0700 Subject: add short desc category website --- src/lib/category/components/Breadcrumb.jsx | 66 +++++++++++++++++----- .../components/styles/breadcrumb.module.css | 3 + src/pages/shop/category/[slug].jsx | 30 ++++++++-- 3 files changed, 79 insertions(+), 20 deletions(-) create mode 100644 src/lib/category/components/styles/breadcrumb.module.css diff --git a/src/lib/category/components/Breadcrumb.jsx b/src/lib/category/components/Breadcrumb.jsx index 50557c3e..8579bb14 100644 --- a/src/lib/category/components/Breadcrumb.jsx +++ b/src/lib/category/components/Breadcrumb.jsx @@ -11,17 +11,21 @@ import React from 'react'; import { useQuery } from 'react-query'; import useDevice from '@/core/hooks/useDevice'; -const Breadcrumb = ({ categoryId }) => { +const Breadcrumb = ({ categoryId, shortDesc }) => { const breadcrumbs = useQuery( ['category-breadcrumbs', categoryId], async () => await odooApi('GET', `/api/v1/category/${categoryId}/category-breadcrumb`) ); + const { isDesktop, isMobile } = useDevice(); const items = breadcrumbs.data ?? []; const lastIdx = items.length - 1; + /* ========================= + DESKTOP + ========================= */ if (isDesktop) { return (
@@ -79,16 +83,34 @@ const Breadcrumb = ({ categoryId }) => { })} + {shortDesc && ( +
+ {shortDesc} +
+ )}
); } + /* ========================= + MOBILE + ========================= */ if (isMobile) { - const items = breadcrumbs.data ?? []; const n = items.length; - const lastCat = n >= 1 ? items[n - 1] : null; // terakhir (current) - const secondLast = n >= 2 ? items[n - 2] : null; // sebelum current - const beforeSecond = n >= 3 ? items[n - 3] : null; // sebelum secondLast + const lastCat = n >= 1 ? items[n - 1] : null; + const secondLast = n >= 2 ? items[n - 2] : null; + const beforeSecond = n >= 3 ? items[n - 3] : null; + const hiddenText = n >= 3 ? items @@ -101,15 +123,15 @@ const Breadcrumb = ({ categoryId }) => {
/} // lebih rapat + separator={/} spacing='4px' sx={{ '& ol': { display: 'flex', alignItems: 'center', - overflow: 'hidden', // untuk ellipsis - whiteSpace: 'nowrap', // untuk ellipsis - gap: '0', // no extra gap + overflow: 'hidden', + whiteSpace: 'nowrap', + gap: '0', }, '& li': { display: 'inline-flex', alignItems: 'center' }, '& li:not(:last-of-type)': { @@ -117,7 +139,7 @@ const Breadcrumb = ({ categoryId }) => { whiteSpace: 'nowrap', }, '& li:last-of-type': { - flex: '0 1 auto', // jangan ambil full space biar gak keliatan “space kosong” + flex: '0 1 auto', minWidth: 0, }, }} @@ -130,7 +152,6 @@ const Breadcrumb = ({ categoryId }) => { - {/* Jika ada kategori sebelum secondLast, tampilkan '..' (link ke beforeSecond) */} {beforeSecond && ( { beforeSecond.id )} title={hiddenText} - aria-label={`Kembali ke ${beforeSecond.name}`} className='!text-danger-500' > .. @@ -149,7 +169,6 @@ const Breadcrumb = ({ categoryId }) => { )} - {/* secondLast sebagai link (kalau ada) */} {secondLast && ( { )} - {/* lastCat (current) dengan truncate & lebar dibatasi */} {lastCat && ( {lastCat.name} @@ -180,9 +198,27 @@ const Breadcrumb = ({ categoryId }) => { )} + + {shortDesc && ( +
+ {shortDesc} +
+ )}
); } + + return null; }; export default Breadcrumb; diff --git a/src/lib/category/components/styles/breadcrumb.module.css b/src/lib/category/components/styles/breadcrumb.module.css new file mode 100644 index 00000000..dee4e1b4 --- /dev/null +++ b/src/lib/category/components/styles/breadcrumb.module.css @@ -0,0 +1,3 @@ +.category-short-desc { + flex: 0 0 100%; +} diff --git a/src/pages/shop/category/[slug].jsx b/src/pages/shop/category/[slug].jsx index 11840d47..e515e3f4 100644 --- a/src/pages/shop/category/[slug].jsx +++ b/src/pages/shop/category/[slug].jsx @@ -16,13 +16,14 @@ const ProductSearch = dynamic(() => ); const CategorySection = dynamic(() => import('@/lib/product/components/CategorySection') -) +); export default function CategoryDetail() { const router = useRouter(); const { slug = '', page = 1 } = router.query; - const [dataCategories, setDataCategories] = useState([]) + const [dataCategories, setDataCategories] = useState([]); + const [shortDesc, setShortDesc] = useState(''); const categoryName = getNameFromSlug(slug); const categoryId = getIdFromSlug(slug); const q = router?.query.q || null; @@ -33,6 +34,22 @@ export default function CategoryDetail() { if (q) { query.q = q; } + useEffect(() => { + if (!router.isReady) return; + if (!categoryId) return; + + const loadShortDesc = async () => { + const res = await odooApi( + 'GET', + `/api/v1/category/${categoryId}/short-desc` + ); + + const desc = res?.shortDesc || ''; + setShortDesc(desc); + }; + + loadShortDesc(); + }, [router.isReady, categoryId]); return ( @@ -47,11 +64,14 @@ export default function CategoryDetail() { ]} /> - - + {!_.isEmpty(router.query) && ( - + )} ); -- cgit v1.2.3