summaryrefslogtreecommitdiff
path: root/src-migrate/modules
diff options
context:
space:
mode:
authorFIN-IT_AndriFP <andrifebriyadiputra@gmail.com>2026-01-09 14:35:06 +0700
committerFIN-IT_AndriFP <andrifebriyadiputra@gmail.com>2026-01-09 14:35:06 +0700
commit13a6ccd99722464342942eeb7fbc73c2f0f27542 (patch)
tree4b4340021a0e6d936cb35fe4511a81b07ad4f076 /src-migrate/modules
parent983aae0a27ed93009964edf2cdf2c4d0f1ea072e (diff)
parent6c2de1aa1cc655b1da525015ac0280fd4c72731f (diff)
fix merge
Diffstat (limited to 'src-migrate/modules')
-rw-r--r--src-migrate/modules/product-detail/components/Information.tsx87
-rw-r--r--src-migrate/modules/product-detail/components/ProductDetail.tsx326
2 files changed, 280 insertions, 133 deletions
diff --git a/src-migrate/modules/product-detail/components/Information.tsx b/src-migrate/modules/product-detail/components/Information.tsx
index b7d3401e..c565682f 100644
--- a/src-migrate/modules/product-detail/components/Information.tsx
+++ b/src-migrate/modules/product-detail/components/Information.tsx
@@ -9,6 +9,7 @@ import style from '../styles/information.module.css';
import dynamic from 'next/dynamic';
import Link from 'next/link';
import { useEffect, useRef, useState } from 'react';
+import axios from 'axios'; // <--- 1. TAMBAHAN IMPORT AXIOS
import currencyFormat from '@/core/utils/currencyFormat';
import { InputGroup, InputRightElement, SimpleGrid, Flex, Text, Box } from '@chakra-ui/react';
@@ -44,6 +45,44 @@ const Information = ({ product }: Props) => {
const variantId = selectedVariant?.id;
const { slaVariant, isLoading } = useVariant({ variantId });
+ const [warranties, setWarranties] = useState<Record<string, string>>({});
+ const [loadingWarranty, setLoadingWarranty] = useState(false);
+
+ useEffect(() => {
+ const fetchWarrantyDirectly = async () => {
+ if (!product?.variants || product.variants.length === 0) return;
+
+ setLoadingWarranty(true);
+ try {
+ // Ambil semua SKU untuk dikirim ke API
+ const skus = product.variants.map((v) => v.id).join(',');
+ const mainSku = product.variants[0].id;
+ console.log("Fetching warranties for SKUs:", skus);
+ console.log("Main SKU:", mainSku);
+
+ // Panggil API magento-product
+ const res = await axios.get('/api/magento-product', {
+ params: { skus, main_sku: mainSku }
+ });
+
+ // Simpan hasil ke state lokal
+ if (res.data && res.data.warranties) {
+ setWarranties(res.data.warranties);
+ }
+ console.log("Warranties API Response:", res);
+ console.log("Warranties fetched:", res.data.warranties);
+ } catch (error) {
+ console.error("Gagal ambil garansi:", error);
+ } finally {
+ setLoadingWarranty(false);
+ }
+ };
+
+ fetchWarrantyDirectly();
+ }, [product]);
+ // ======================================================
+
+
useEffect(() => {
if (selectedVariant) {
setInputValue(
@@ -64,23 +103,20 @@ const Information = ({ product }: Props) => {
}
}, [slaVariant, isLoading, setSla]);
- const handleOnChange = (vals: any) => {
- setDisableFilter(true);
- let code = vals.replace(/\s-\s.*$/, '').trim();
- let variant = variantOptions.find((item) => item.code === code);
- setSelectedVariant(variant);
- setInputValue(
- variant?.code +
- (variant?.attributes[0] ? ' - ' + variant?.attributes[0] : '')
- );
+const handleOnChange = (vals: any) => {
+    setDisableFilter(true);
+    let code = vals.replace(/\s-\s.*$/, '').trim();
+    let variant = product?.variants.find((item) => item.code === code);
+   
if (variant) {
- const filteredOptions = product?.variants.filter(
- (item) => item !== variant
- );
- const newOptions = [variant, ...filteredOptions];
- setVariantOptions(newOptions);
- }
- };
+    setSelectedVariant(variant);
+    setInputValue(
+      variant?.code +
+        (variant?.attributes[0] ? ' - ' + variant?.attributes[0] : '')
+    );
+ setVariantOptions(product?.variants);
+    }
+  };
const handleOnKeyUp = (e: any) => {
setDisableFilter(false);
@@ -127,7 +163,11 @@ const Information = ({ product }: Props) => {
</InputGroup>
<AutoCompleteList>
- {variantOptions.map((option, cid) => (
+ {variantOptions
+ .sort((a: any, b: any) => {
+ return a.code.localeCompare(b.code, undefined, { numeric: true, sensitivity: 'base' });
+ })
+ .map((option, cid) => (
<AutoCompleteItem
key={`option-${cid}`}
value={
@@ -239,7 +279,6 @@ const Information = ({ product }: Props) => {
<div className="mt-6 border-t pt-4">
<h2 className="font-bold text-gray-800 text-sm mb-4">Detail Informasi Produk</h2>
- {/* Perubahan: Spacing diperbesar menjadi 10 agar estimasi bergeser ke kanan */}
<SimpleGrid columns={{ base: 1, md: 3 }} spacing={10}>
{/* 1. Distributor Resmi */}
<Flex align="center" className="gap-3">
@@ -282,7 +321,17 @@ const Information = ({ product }: Props) => {
/>
<Box>
<Text fontSize="11px" color="gray.500" lineHeight="short" mb="1px">Garansi Produk</Text>
- <Text fontSize="12px" fontWeight="bold" color="gray.800" lineHeight="short">24 Bulan</Text>
+
+ {/* Menggunakan Loading State & Data dari 'warranties' */}
+ {loadingWarranty ? (
+ <Skeleton height="12px" width="80px" mt="2px" />
+ ) : (
+ <Text fontSize="12px" fontWeight="bold" color="gray.800" lineHeight="short">
+ {selectedVariant && warranties[selectedVariant.id]
+ ? warranties[selectedVariant.id]
+ : '-'}
+ </Text>
+ )}
</Box>
</Flex>
</SimpleGrid>
diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx
index 58276f3c..dcd97ef1 100644
--- a/src-migrate/modules/product-detail/components/ProductDetail.tsx
+++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx
@@ -2,7 +2,8 @@ import style from '../styles/product-detail.module.css';
import Link from 'next/link';
import { useRouter } from 'next/router';
-import { useEffect, useRef, useState, UIEvent } from 'react';
+// Import useMemo
+import { useEffect, useRef, useState, UIEvent, useMemo } from 'react';
// Import komponen Chakra UI
import {
@@ -76,6 +77,7 @@ const ProductDetail = ({ product }: Props) => {
const [specsMatrix, setSpecsMatrix] = useState<any[]>([]);
const [upsellIds, setUpsellIds] = useState<number[]>([]);
const [relatedIds, setRelatedIds] = useState<number[]>([]);
+ const [descriptionMap, setDescriptionMap] = useState<Record<string, string>>({});
const [loadingSpecs, setLoadingSpecs] = useState(false);
@@ -176,12 +178,21 @@ const ProductDetail = ({ product }: Props) => {
// 1. Specs Matrix (Processed Grouping)
if (data.specsMatrix && Array.isArray(data.specsMatrix)) {
- const processed = processMatrixData(data.specsMatrix);
- setSpecsMatrix(processed);
+ const filteredMatrix = data.specsMatrix.filter((item: any) => {
+ const code = item.code || '';
+ return !code.includes('z_brand');
+ });
+
+ const processed = processMatrixData(filteredMatrix);
+ setSpecsMatrix(processed);
} else {
setSpecsMatrix([]);
}
+ if (data.descriptions){
+ setDescriptionMap(data.descriptions);
+ }
+
// 2. Upsell & Related
if (data.upsell_ids && Array.isArray(data.upsell_ids)) setUpsellIds(data.upsell_ids);
else setUpsellIds([]);
@@ -320,14 +331,48 @@ const ProductDetail = ({ product }: Props) => {
};
const scrollToIndex = (i: number) => {
- const el = sliderRef.current;
- if (!el) return;
- const elRef = sliderRef.current;
- elRef.scrollTo({ left: i * elRef.clientWidth, behavior: 'smooth' });
+ const el = sliderRef.current;
+ if (!el) return;
+ el.scrollTo({ left: i * el.clientWidth, behavior: 'smooth' });
setCurrentIdx(i);
setMainImage(allImages[i] || '');
};
+ const sortedVariants = useMemo(() => {
+ if (!product?.variants) return [];
+
+ return [...product.variants].sort((a, b) => {
+ const labelA = a.attributes && a.attributes.length > 0
+ ? a.attributes.join(' - ')
+ : a.code || '';
+
+ const labelB = b.attributes && b.attributes.length > 0
+ ? b.attributes.join(' - ')
+ : b.code || '';
+
+ const getNumber = (str: string) => {
+ const match = String(str).match(/(\d+(\.\d+)?)/);
+ return match ? parseFloat(match[0]) : null;
+ };
+
+ const numA = getNumber(labelA);
+ const numB = getNumber(labelB);
+
+ if (numA !== null && numB !== null && numA !== numB) {
+ return numA - numB;
+ }
+
+ return String(labelA).localeCompare(String(labelB), undefined, {
+ numeric: true,
+ sensitivity: 'base'
+ });
+ });
+ }, [product.variants]);
+
+ const activeMagentoDesc = selectedVariant?.id ? descriptionMap[String(selectedVariant.id)] : '';
+ const finalDescription = activeMagentoDesc || product.description || 'Deskripsi produk tidak tersedia.';
+ const cleanDescription = finalDescription === '<p><br></p>' ? 'Deskripsi produk tidak tersedia.' : finalDescription;
+
return (
<>
{/* 3. MODAL POPUP DIRENDER DISINI */}
@@ -501,117 +546,170 @@ const ProductDetail = ({ product }: Props) => {
{/* DESKRIPSI */}
<TabPanel px={0} py={6}>
<div className='overflow-x-auto text-sm text-gray-700'>
- <div
- className={style['description']}
- dangerouslySetInnerHTML={{
- __html:
- !product.description || product.description === '<p><br></p>'
- ? '<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>'
- : product.description,
- }}
- />
+ <div className={style['description']} dangerouslySetInnerHTML={{ __html: cleanDescription }} />
</div>
</TabPanel>
- {/* SPESIFIKASI (MATRIKS & SPLIT PIPA) */}
+ {/* SPESIFIKASI */}
<TabPanel px={0} py={2}>
- <Box border="1px solid" borderColor="gray.200" borderRadius="sm" overflowX="auto">
- {loadingSpecs ? (
- <Center py={6}><Spinner color='red.500' /></Center>
- ) : specsMatrix.length > 0 ? (
- // Cek Single vs Multi Variant
- (() => {
- const isSingleVariant = product.variants.length === 1;
- const globalAlign = isSingleVariant ? "left" : "center";
-
- return (
- <Table variant="simple" size="md">
- <Thead bg="red.600">
- <Tr>
- <Th
- width={isSingleVariant ? "30%" : "20%"}
- borderColor="whiteAlpha.300"
- color="white"
- fontSize="sm"
- textTransform="none"
- verticalAlign="middle"
- >
- Spesifikasi
- </Th>
-
- {product.variants.map(v => (
- <Th
- key={v.id}
- borderColor="whiteAlpha.300"
- color="white"
- textAlign={globalAlign}
- fontSize="sm"
- textTransform="none"
- verticalAlign="middle"
- >
- {isSingleVariant ? 'Detail' : (v.attributes && v.attributes.length > 0 ? v.attributes.join(' - ') : v.code)}
- </Th>
- ))}
- </Tr>
- </Thead>
- <Tbody>
- {specsMatrix.map((row, i) => {
-
- // CASE 1: GROUPING (Label punya ' : ')
- if (row.type === 'group') {
- return (
- <Tr key={i}>
- {/* Header Group Kiri */}
- <Td fontWeight="bold" borderColor="gray.200" fontSize="sm" verticalAlign="middle">{row.label}</Td>
-
- {/* Content Group Kanan */}
- {product.variants.map(v => (
- <Td key={v.id} borderColor="gray.200" textAlign={globalAlign} fontSize="sm" verticalAlign="middle">
- <div className={`inline-block text-left`}>
- <div className="flex flex-col gap-0">
- {row.children.map((child: any, idx: number) => {
- const rawVal = child.values[v.id];
- if (!rawVal || rawVal === '-') return null;
-
- return (
- <div key={idx} className="grid grid-cols-[auto_auto] gap-x-2">
- <span className="font-semibold text-gray-600 whitespace-nowrap">
- {child.label}:
- </span>
- <span>{renderSpecValue(rawVal)}</span>
- </div>
- );
- })}
- </div>
- </div>
- </Td>
- ))}
- </Tr>
- );
- }
-
- // CASE 2: SINGLE ITEM
- return (
- <Tr key={i}>
- <Td fontWeight="bold" borderColor="gray.200" fontSize="sm" verticalAlign="middle">{row.label}</Td>
- {product.variants.map(v => {
- const rawValue = row.values[v.id] || '-';
- return (
- <Td key={v.id} borderColor="gray.200" textAlign={globalAlign} fontSize="sm" verticalAlign="middle">
- {renderSpecValue(rawValue)}
- </Td>
- );
- })}
- </Tr>
- );
- })}
- </Tbody>
- </Table>
- );
- })()
- ) : (
- <Box p={4} color="gray.500" fontSize="sm"><Text>Spesifikasi teknis belum tersedia.</Text></Box>
- )}
+ <Box
+ border="1px solid"
+ borderColor="gray.200"
+ borderRadius="sm"
+ overflowX="auto"
+ overflowY="auto"
+ maxHeight="500px"
+ css={{
+ '&::-webkit-scrollbar': {
+ width: '12px',
+ height: '12px',
+ },
+ '&::-webkit-scrollbar-track': {
+ background: '#f1f1f1',
+ },
+ '&::-webkit-scrollbar-thumb': {
+ backgroundColor: '#a0aec0',
+ borderRadius: '8px',
+ border: '4px solid transparent',
+ backgroundClip: 'content-box',
+ },
+ '&::-webkit-scrollbar-thumb:hover': {
+ backgroundColor: '#718096',
+ },
+ }}
+ >
+ {loadingSpecs ? (
+ <Center py={6}><Spinner color='red.500' /></Center>
+ ) : specsMatrix.length > 0 ? (
+ (() => {
+ const topHeaders: any[] = [];
+ const subHeaders: any[] = [];
+ const flatSpecs: any[] = [];
+
+ specsMatrix.forEach(row => {
+ if (row.type === 'group') {
+ topHeaders.push({
+ label: row.label,
+ type: 'group',
+ colSpan: row.children.length,
+ rowSpan: 1
+ });
+ row.children.forEach((child: any) => {
+ subHeaders.push(child);
+ flatSpecs.push(child);
+ });
+ } else {
+ topHeaders.push({
+ label: row.label,
+ type: 'single',
+ colSpan: 1,
+ rowSpan: 2
+ });
+ flatSpecs.push(row);
+ }
+ });
+
+ return (
+ <Table variant="simple" size="md">
+ <Thead bg="red.600" position="sticky" top={0} zIndex={20}>
+ {/* Baris 1: Header Utama */}
+ <Tr>
+ {topHeaders.map((th, idx) => (
+ <Th
+ key={`top-${idx}`}
+ position={idx === 0 ? "sticky" : "static"}
+ left={idx === 0 ? 0 : undefined}
+ zIndex={idx === 0 ? 22 : 20}
+ boxShadow={idx === 0 ? "2px 0 5px -2px rgba(0,0,0,0.2)" : "none"}
+
+ bg="red.600"
+ colSpan={th.colSpan}
+ rowSpan={th.rowSpan}
+ color="white"
+ textAlign="center"
+ fontSize="sm"
+ textTransform="none"
+ fontWeight="800"
+ letterSpacing="wide"
+ verticalAlign="middle"
+ borderBottom="none"
+ >
+ {th.label}
+ </Th>
+ ))}
+ </Tr>
+
+ {/* Baris 2: Sub Header */}
+ <Tr>
+ {subHeaders.map((sub, idx) => {
+ const isFirstHeaderGroup = topHeaders[0]?.type === 'group';
+ const shouldSticky = idx === 0 && isFirstHeaderGroup;
+ return (
+ <Th
+ key={`sub-${idx}`}
+ position={shouldSticky ? "sticky" : "static"}
+ left={shouldSticky ? 0 : undefined}
+ zIndex={shouldSticky ? 21 : 1}
+ boxShadow={shouldSticky ? "2px 0 5px -2px rgba(0,0,0,0.2)" : "none"}
+
+ color="white"
+ textAlign="center"
+ fontSize="xs"
+ textTransform="none"
+ verticalAlign="middle"
+ whiteSpace="nowrap"
+ bg="red.600"
+ pt={1} pb={1}
+ >
+ {sub.label}
+ </Th>
+ );
+ })}
+ </Tr>
+ </Thead>
+
+ <Tbody>
+ {sortedVariants.map((v, vIdx) => (
+ <Tr key={v.id} bg={vIdx % 2 === 0 ? 'white' : 'gray.50'}>
+ {flatSpecs.map((spec, sIdx) => {
+ const rawValue = spec.values[v.id] || '-';
+ const isFirstCol = sIdx === 0;
+ return (
+ <Td
+ key={sIdx}
+ // === LOGIC STICKY DATA PERTAMA ===
+ position={isFirstCol ? "sticky" : "static"}
+ left={isFirstCol ? 0 : undefined}
+ zIndex={isFirstCol ? 10 : 1}
+ bg={vIdx % 2 === 0 ? 'white' : 'gray.50'}
+ boxShadow={isFirstCol ? "2px 0 5px -2px rgba(0,0,0,0.1)" : "none"}
+ // =================================
+
+ borderColor="gray.200"
+ textAlign="center"
+ fontSize="sm"
+ verticalAlign="middle"
+ px={2}
+ py={3}
+ minW="100px"
+ maxW="150px"
+ whiteSpace="normal"
+ overflowWrap="break-word"
+ fontWeight={isFirstCol ? "bold" : "normal"}
+ >
+ {renderSpecValue(rawValue)}
+ </Td>
+ );
+ })}
+ </Tr>
+ ))}
+ </Tbody>
+ </Table>
+ );
+ })()
+ ) : (
+ <Box p={4} color="gray.500" fontSize="sm"><Text>Spesifikasi teknis belum tersedia.</Text></Box>
+ )}
</Box>
</TabPanel>