summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src-migrate/modules/product-detail/components/ProductComparisonModal.tsx133
1 files changed, 50 insertions, 83 deletions
diff --git a/src-migrate/modules/product-detail/components/ProductComparisonModal.tsx b/src-migrate/modules/product-detail/components/ProductComparisonModal.tsx
index a58ad5b2..3ee61c9d 100644
--- a/src-migrate/modules/product-detail/components/ProductComparisonModal.tsx
+++ b/src-migrate/modules/product-detail/components/ProductComparisonModal.tsx
@@ -80,6 +80,7 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant
}
const targetId = activeItem.id;
+ // Ambil code dari properti yang tersedia
const displayCode = activeItem.default_code || activeItem.code || activeItem.sku || mainProduct.default_code || mainProduct.code;
const variantOptions = mainProduct.variants?.map((v: any) => ({
@@ -102,8 +103,8 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant
const productSlot1 = {
id: targetId,
- sku: targetId, // ID untuk API
- realCode: displayCode,
+ sku: targetId, // ID untuk API & Key
+ realCode: displayCode, // String untuk Tampilan
name: mainProduct.name,
price: activeItem.price?.price || activeItem.price || mainProduct.lowest_price?.price || 0,
image: activeItem.image || mainProduct.image,
@@ -130,6 +131,7 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant
const fetchSpecs = async () => {
setIsLoadingMatrix(true);
try {
+ // Kirim ID ke API
const allSkus = validProducts.map(p => p.sku).join(',');
const mainSku = validProducts[0]?.sku;
@@ -151,7 +153,7 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant
}, [products, isOpen]);
// ===========================================================================
- // 3. SEARCH LOGIC
+ // 3. SEARCH LOGIC (MATCHED WITH API JSON)
// ===========================================================================
useEffect(() => {
const delayDebounceFn = setTimeout(async () => {
@@ -163,16 +165,21 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant
setIsSearching(true);
try {
const attrSetId = selectedVariant?.attribute_set_id || mainProduct?.attribute_set_id;
+
const params = new URLSearchParams({
source: 'compare',
- q: searchQuery,
+ q: '*',
limit: '5',
+ // Filter kategori aktif agar Apple-to-Apple
fq: attrSetId ? `attribute_set_id_i:${attrSetId}` : ''
});
- const res = await fetch(`/api/search?${params.toString()}`);
+ // Gunakan endpoint yang benar: /api/shop/search
+ const res = await fetch(`/api/shop/search?${params.toString()}`);
+
if (res.ok) {
const data = await res.json();
+ // Data sudah CamelCase dari Backend
setSearchResults(data.response?.products || []);
} else {
setSearchResults([]);
@@ -210,12 +217,19 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant
}
};
- const handleAddProduct = (solrProduct: any, slotIndex: number) => {
+ const handleAddProduct = (searchItem: any, slotIndex: number) => {
const newProducts = [...products];
- const idToAdd = solrProduct.product_id_i || solrProduct.id;
- const codeToAdd = solrProduct.default_code_s || solrProduct.sku;
+ // [FIX] MAPPING DARI JSON API ANDA (CAMELCASE)
+ // JSON: { id: 88019, code: "RX-SP0006", displayName: "...", lowestPrice: { price: ... } }
+
+ const idToAdd = searchItem.id;
+ const codeToAdd = searchItem.code; // Langsung 'code', bukan 'default_code_s'
+ const nameToAdd = searchItem.displayName || searchItem.name;
+ const imageToAdd = searchItem.image;
+ const priceToAdd = searchItem.lowestPrice?.price || 0;
+ // Cek Duplikat
if (newProducts.find(p => p && String(p.id) === String(idToAdd))) {
toast({ title: "Produk sudah ada", status: "warning", position: "top" });
return;
@@ -223,17 +237,17 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant
newProducts[slotIndex] = {
id: idToAdd,
- sku: idToAdd,
- realCode: codeToAdd,
- name: solrProduct.display_name_s || solrProduct.name_s,
- price: solrProduct.price_tier1_v2_f || 0,
- image: solrProduct.image_s,
+ sku: idToAdd, // ID untuk API
+ realCode: codeToAdd, // Code String untuk Tampilan
+ name: nameToAdd,
+ price: priceToAdd,
+ image: imageToAdd,
variants: [{
id: idToAdd,
code: codeToAdd,
- name: solrProduct.name_s,
- price: solrProduct.price_tier1_v2_f,
- image: solrProduct.image_s
+ name: nameToAdd,
+ price: priceToAdd,
+ image: imageToAdd
}]
};
@@ -269,17 +283,11 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant
<ModalBody p={6} bg="white">
<Grid templateColumns="200px repeat(4, 1fr)" gap={4}>
-
- {/* Cell 1: Kosong */}
<GridItem />
-
- {/* Loop Slot Produk */}
{products.map((product, index) => (
<GridItem key={index} position="relative" minW="0">
{product ? (
<VStack align="stretch" spacing={3} h="100%">
-
- {/* Tombol Hapus */}
{index !== 0 && (
<IconButton
aria-label="Hapus" icon={<Trash2 size={16}/>}
@@ -287,46 +295,33 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant
colorScheme="red" onClick={() => handleRemoveProduct(index)} zIndex={2}
/>
)}
-
- {/* Gambar */}
<Box h="160px" display="flex" alignItems="center" justifyContent="center" bg="gray.50" borderRadius="md" p={2}>
<Image
src={product.image || '/images/noimage.jpeg'}
- alt={product.name}
- maxH="100%" objectFit="contain"
- onError={(e) => { (e.target as HTMLImageElement).src = '/images/noimage.jpeg'; }}
+ alt={product.name} maxH="100%" objectFit="contain"
+ onError={(e) => { (e.target as HTMLImageElement).src = '/images/no-image-compare.svg'; }}
/>
</Box>
-
- {/* Info Harga & Nama */}
<Box>
<Text color="red.600" fontWeight="bold" fontSize="md">
{product.price > 0 ? formatPrice(product.price) : 'Hubungi Admin'}
</Text>
- {/* Margin Bottom agar tidak tertutup dropdown */}
<Text fontSize="xs" fontWeight="bold" noOfLines={2} h="35px" title={product.name} mb={2}>
{product.name}
</Text>
</Box>
-
- {/* Dropdown Varian */}
+
<Select
- size="sm"
- borderRadius="md"
- fontSize="xs"
+ size="sm" borderRadius="md" fontSize="xs"
value={product.id}
onChange={(e) => handleVariantChange(index, e.target.value)}
- isDisabled={false}
- bg="white"
+ isDisabled={false} bg="white"
>
{product.variants && product.variants.map((v: any) => (
- <option key={v.id} value={v.id}>
- {v.code}
- </option>
+ <option key={v.id} value={v.id}>{v.code}</option>
))}
</Select>
- {/* Tombol */}
<HStack spacing={2}>
<IconButton aria-label="Cart" icon={<Icon as={ShoppingCart} />} variant="outline" colorScheme="red" size="sm" />
<Button as="a" href={`/product/${product.id}`} target="_blank" colorScheme="red" size="sm" flex={1} fontSize="xs">
@@ -335,7 +330,6 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant
</HStack>
</VStack>
) : (
- // SLOT KOSONG
<VStack align="stretch" spacing={3} h="100%" position="relative">
<InputGroup size="sm">
<InputLeftElement pointerEvents="none"><Icon as={Search} color="gray.300" /></InputLeftElement>
@@ -347,6 +341,7 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant
/>
</InputGroup>
+ {/* --- HASIL SEARCH (MAPPING FIX) --- */}
{activeSearchSlot === index && searchQuery.length > 0 && (
<Box position="absolute" top="35px" left={0} right={0} bg="white" boxShadow="lg" zIndex={10} borderRadius="md" border="1px solid" borderColor="gray.200" maxH="250px" overflowY="auto">
{isSearching ? (
@@ -355,16 +350,20 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant
<List spacing={0}>
{searchResults.map((res) => (
<ListItem
- key={res.product_id_i || res.id}
+ key={res.id}
p={2} borderBottom="1px solid #f0f0f0"
_hover={{ bg: 'red.50', cursor: 'pointer' }}
onClick={() => handleAddProduct(res, index)}
>
<Flex align="center" gap={2}>
- <Image src={res.image_s || '/images/noimage.jpeg'} boxSize="30px" objectFit="contain" onError={(e) => { (e.target as HTMLImageElement).src = '/images/noimage.jpeg'; }} />
+ <Image src={res.image || '/images/noimage.jpeg'} boxSize="30px" objectFit="contain" onError={(e) => { (e.target as HTMLImageElement).src = '/images/noimage.jpeg'; }} />
<Box>
- <Text fontSize="xs" fontWeight="bold" noOfLines={1}>{res.display_name_s || res.name_s}</Text>
- <Text fontSize="xs" color="red.500">{formatPrice(res.price_tier1_v2_f || 0)}</Text>
+ {/* [FIX] Gunakan displayName atau name */}
+ <Text fontSize="xs" fontWeight="bold" noOfLines={1}>{res.displayName || res.name}</Text>
+ {/* [FIX] Gunakan lowestPrice.price */}
+ <Text fontSize="xs" color="red.500">
+ {formatPrice(res.lowestPrice?.price || 0)}
+ </Text>
</Box>
</Flex>
</ListItem>
@@ -385,51 +384,22 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant
</GridItem>
))}
- {/* --- BAGIAN SPESIFIKASI --- */}
<GridItem colSpan={5} py={6}>
- <Box borderBottom="2px solid" borderColor="gray.100" pb={2}>
- <Text fontSize="lg" fontWeight="bold">Spesifikasi Teknis</Text>
- </Box>
+ <Box borderBottom="2px solid" borderColor="gray.100" pb={2}><Text fontSize="lg" fontWeight="bold">Spesifikasi Teknis</Text></Box>
</GridItem>
{isLoadingMatrix ? (
- <GridItem colSpan={5} textAlign="center" py={10}>
- <Spinner color="red.500" thickness="4px" size="xl" />
- <Text mt={2} color="gray.500">Memuat data...</Text>
- </GridItem>
+ <GridItem colSpan={5} textAlign="center" py={10}><Spinner color="red.500" thickness="4px" size="xl" /><Text mt={2} color="gray.500">Memuat data...</Text></GridItem>
) : specsMatrix.length > 0 ? (
specsMatrix.map((row, rowIndex) => (
<React.Fragment key={row.code || rowIndex}>
- {/* Label (Kiri) */}
- <GridItem
- py={3}
- px={2}
- borderBottom="1px solid"
- borderColor="gray.100"
- bg={rowIndex % 2 !== 0 ? "white" : "gray.50"}
- display="flex"
- alignItems="center"
- >
+ <GridItem py={3} px={2} borderBottom="1px solid" borderColor="gray.100" bg={rowIndex % 2 !== 0 ? "white" : "gray.50"} display="flex" alignItems="center">
<Text fontWeight="bold" fontSize="sm" color="gray.700">{row.label}</Text>
</GridItem>
-
- {/* Value (Rata Tengah) */}
{products.map((product, colIndex) => {
const val = product ? (row.values[String(product.sku)] || '-') : '';
-
return (
- <GridItem
- key={`${row.code}-${colIndex}`}
- py={3}
- px={2}
- borderBottom="1px solid"
- borderColor="gray.100"
- bg={rowIndex % 2 !== 0 ? "white" : "gray.50"}
- display="flex" // [FIX] Flex untuk centering
- alignItems="center" // [FIX] Vertical Center
- justifyContent="center" // [FIX] Horizontal Center
- textAlign="center" // [FIX] Text Align Center
- >
+ <GridItem key={`${row.code}-${colIndex}`} py={3} px={2} borderBottom="1px solid" borderColor="gray.100" bg={rowIndex % 2 !== 0 ? "white" : "gray.50"} display="flex" alignItems="center" justifyContent="center" textAlign="center">
<Text fontSize="sm" color="gray.600">{renderSpecValue(val)}</Text>
</GridItem>
);
@@ -437,11 +407,8 @@ const ProductComparisonModal = ({ isOpen, onClose, mainProduct, selectedVariant
</React.Fragment>
))
) : (
- <GridItem colSpan={5} py={10} textAlign="center" color="gray.500" bg="gray.50">
- <Text>Data spesifikasi belum tersedia untuk produk ini.</Text>
- </GridItem>
+ <GridItem colSpan={5} py={10} textAlign="center" color="gray.500" bg="gray.50"><Text>Data spesifikasi belum tersedia untuk produk ini.</Text></GridItem>
)}
-
</Grid>
</ModalBody>
</ModalContent>