diff options
| author | it-fixcomart <it@fixcomart.co.id> | 2025-05-14 09:27:26 +0700 |
|---|---|---|
| committer | it-fixcomart <it@fixcomart.co.id> | 2025-05-14 09:27:26 +0700 |
| commit | 29ecab270822500ead372d8d3e05c9281a514715 (patch) | |
| tree | a32735f4b7fab774de6a2bbee9380f363c51bec2 | |
| parent | dd66804b05166ad6bb71bf54fe3374d9897fee86 (diff) | |
| parent | 746a11b810ae9e8a974a76d0548297cd0faff9b5 (diff) | |
Merge branch 'new-release' into CR/form-merchant
51 files changed, 905 insertions, 218 deletions
diff --git a/package.json b/package.json index 1e9dc696..89e3b47a 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,10 @@ "react-multi-select-component": "^4.3.4", "react-query": "^3.39.3", "react-select": "^5.8.0", + "react-slick": "^0.30.3", "react-web-share": "^2.0.2", "redis": "^4.7.0", + "slick-carousel": "^1.8.1", "snakecase-keys": "^5.5.0", "swiper": "^8.4.4", "tw-merge": "^0.0.1-alpha.3", diff --git a/public/images/socials/x-indoteknik.png b/public/images/socials/x-indoteknik.png Binary files differnew file mode 100644 index 00000000..c58056a0 --- /dev/null +++ b/public/images/socials/x-indoteknik.png diff --git a/src-migrate/modules/cart/components/Item.tsx b/src-migrate/modules/cart/components/Item.tsx index ab2e7ce1..86d1dc43 100644 --- a/src-migrate/modules/cart/components/Item.tsx +++ b/src-migrate/modules/cart/components/Item.tsx @@ -1,5 +1,6 @@ import style from '../styles/item.module.css' - +import odooApi from '~/libs/odooApi'; +import { useEffect, useState } from 'react'; import { Skeleton, SkeletonProps, Tooltip } from '@chakra-ui/react' import { InfoIcon } from 'lucide-react' import Image from 'next/image' @@ -22,6 +23,17 @@ type Props = { } const CartItem = ({ item, editable = true, selfPicking}: Props) => { + const [qtyPickUp, setQtyPickUp] = useState(0); + useEffect(() => { + const fetchData = async () => { + const qty_available = await odooApi( + 'GET', + `/api/v1/product_variant/${item.id}/qty_available` + ); + setQtyPickUp(qty_available?.qty); + }; + fetchData(); + }, [item]); return ( <div className={style.wrapper}> {item.cart_type === 'promotion' && ( @@ -54,11 +66,11 @@ const CartItem = ({ item, editable = true, selfPicking}: Props) => { <CartItem.Image item={item} /> <div className={style.details}> - {item?.available_quantity > 0 && ( + {qtyPickUp > 0 && ( <div className='text-[10px] text-red-500 italic'> - {item.quantity <= item?.available_quantity + {item.quantity <= qtyPickUp ? '*Barang ini bisa di pickup maksimal pukul 16.00' - : `*${item?.available_quantity} Barang ini bisa di pickup maksimal pukul 16.00`} + : `*${qtyPickUp} Barang ini bisa di pickup maksimal pukul 16.00`} </div> )} <CartItem.Name item={item} /> diff --git a/src-migrate/modules/popup-information/index.tsx b/src-migrate/modules/popup-information/index.tsx index d50711cc..cae50abf 100644 --- a/src-migrate/modules/popup-information/index.tsx +++ b/src-migrate/modules/popup-information/index.tsx @@ -15,7 +15,6 @@ const PagePopupInformation = () => { const [data, setData] = useState<any>(null); const [loading, setLoading] = useState(true); - useEffect(() => { const getData = async () => { const res = await fetch(`/api/hero-banner?type=popup-banner`); @@ -44,7 +43,10 @@ const PagePopupInformation = () => { className='w-[350px] md:w-[530px]' onClick={() => setActive(false)} > - <Link href={data[0].url === false ? '/' :data[0].url} aria-label='popup'> + <Link + href={data[0].url === false ? '/' : data[0].url} + aria-label='popup' + > <Image src={data[0]?.image} alt={data[0]?.name} diff --git a/src-migrate/modules/product-detail/components/AddToCart.tsx b/src-migrate/modules/product-detail/components/AddToCart.tsx index 280e4a7a..95bc1d88 100644 --- a/src-migrate/modules/product-detail/components/AddToCart.tsx +++ b/src-migrate/modules/product-detail/components/AddToCart.tsx @@ -15,7 +15,8 @@ import { useProductCartContext } from '@/contexts/ProductCartContext'; import { createSlug } from '~/libs/slug'; import formatCurrency from '~/libs/formatCurrency'; import { useProductDetail } from '../stores/useProductDetail'; - +import { gtagAddToCart } from '@/core/utils/googleTag'; +import axios from 'axios'; type Props = { variantId: number | null; quantity?: number; @@ -50,6 +51,38 @@ const AddToCart = ({ isLoading, setIsloading, } = useProductCartContext(); + const [activeVariant, setActiveVariant] = useState({ + id: 0, + code: '', + name: '', + price: '', + stock: '', + weight: '', + isFlashSale: false, + }); + + useEffect(() => { + const fetchData = async () => { + if (variantId) { + let response = await axios( + `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/variant-detail?id=${variantId}` + ); + let productVariant = response.data; + if (productVariant) { + setActiveVariant({ + id: productVariant[0].id, + code: productVariant[0].code, + name: productVariant[0].name, + price: productVariant[0].price.price, + stock: productVariant[0].stockTotal, + weight: productVariant[0].weight, + isFlashSale: productVariant[0].isFlashsale, + }); + } + } + }; + fetchData(); + }, [variantId]); const productSimilarQuery = [ product?.name, @@ -101,6 +134,8 @@ const AddToCart = ({ setRefreshCart(true); setAddCartAlert(true); + gtagAddToCart(activeVariant, quantity); + toast({ title: 'Tambah ke keranjang', description: 'Berhasil menambahkan barang ke keranjang belanja', diff --git a/src-migrate/modules/product-detail/components/PriceAction.tsx b/src-migrate/modules/product-detail/components/PriceAction.tsx index 9348bbfb..850c2d9d 100644 --- a/src-migrate/modules/product-detail/components/PriceAction.tsx +++ b/src-migrate/modules/product-detail/components/PriceAction.tsx @@ -163,7 +163,7 @@ const PriceAction = ({ product }: Props) => { </span> */} </div> <div> - {selectedVariant?.is_in_bu && ( + {qtyPickUp > 0 && ( <Link href='/panduan-pick-up-service' className='group'> <Image src='/images/PICKUP-NOW.png' diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx index b036cc2d..0660b9c0 100644 --- a/src-migrate/modules/product-detail/components/ProductDetail.tsx +++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx @@ -2,7 +2,7 @@ import style from '../styles/product-detail.module.css'; import Link from 'next/link'; import { useRouter } from 'next/router'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { Button } from '@chakra-ui/react'; import { MessageCircleIcon, Share2Icon } from 'lucide-react'; @@ -66,9 +66,23 @@ const ProductDetail = ({ product }: Props) => { if (typeof auth === 'object') { setIsApproval(auth?.feature?.soApproval); } - setSelectedVariant(product?.variants[0]) + const selectedVariant = + product?.variants?.find((variant) => variant.is_in_bu) || + product?.variants?.[0]; + setSelectedVariant(selectedVariant); + // setSelectedVariant(product?.variants[0]) }, []); + // Gabungkan semua gambar produk (utama + tambahan) + const allImages = product.image_carousel ? [...product.image_carousel] : []; + + if (product.image) { + allImages.unshift(product.image); // Tambahkan gambar utama di awal array + } + console.log(product); + + const [mainImage, setMainImage] = useState(allImages[0] || ''); + return ( <> <div className='md:flex md:flex-wrap'> @@ -78,7 +92,37 @@ const ProductDetail = ({ product }: Props) => { <div className='md:w-9/12 md:flex md:flex-col md:pr-4 md:pt-6'> <div className='md:flex md:flex-wrap'> <div className='md:w-4/12'> - <ProductImage product={product} /> + <ProductImage product={{ ...product, image: mainImage }} /> + + {/* Carousel horizontal */} + {allImages.length > 0 && ( + <div className='mt-4 overflow-x-auto'> + <div className='flex space-x-3 pb-3'> + {allImages.map((img, index) => ( + <div + key={index} + className={`flex-shrink-0 w-16 h-16 cursor-pointer border-2 rounded-md transition-colors ${ + mainImage === img + ? 'border-red-500 ring-2 ring-red-200' + : 'border-gray-200 hover:border-gray-300' + }`} + onClick={() => setMainImage(img)} + > + <img + src={img} + alt={`Thumbnail ${index + 1}`} + className='w-full h-full object-cover rounded-sm' + loading='lazy' + onError={(e) => { + (e.target as HTMLImageElement).src = + '/path/to/fallback-image.jpg'; + }} + /> + </div> + ))} + </div> + </div> + )} </div> <div className='md:w-8/12 px-4 md:pl-6'> @@ -102,7 +146,12 @@ const ProductDetail = ({ product }: Props) => { )} <div className='h-4 md:h-10' /> - {!!activeVariantId && !isApproval && <ProductPromoSection product={product} productId={activeVariantId} />} + {!!activeVariantId && !isApproval && ( + <ProductPromoSection + product={product} + productId={activeVariantId} + /> + )} {/* <div className={style['section-card']}> <h2 className={style['heading']}> @@ -117,15 +166,18 @@ const ProductDetail = ({ product }: Props) => { <div className={style['section-card']}> <h2 className={style['heading']}>Informasi Produk</h2> <div className='h-4' /> - <div - className={style['description']} - dangerouslySetInnerHTML={{ - __html: - !product.description || product.description == '<p><br></p>' - ? 'Belum ada deskripsi' - : product.description, - }} - /> + <div className='overflow-x-auto'> + <div + className={style['description']} + dangerouslySetInnerHTML={{ + __html: + !product.description || + product.description == '<p><br></p>' + ? 'Belum ada deskripsi' + : product.description, + }} + /> + </div> </div> </div> </div> diff --git a/src-migrate/modules/promo/components/Voucher.tsx b/src-migrate/modules/promo/components/Voucher.tsx index 034d13e9..0c225c74 100644 --- a/src-migrate/modules/promo/components/Voucher.tsx +++ b/src-migrate/modules/promo/components/Voucher.tsx @@ -18,6 +18,7 @@ interface Voucher { name: string; description: string; code: string; + voucher_category: []; } const VoucherComponent = () => { diff --git a/src-migrate/modules/register/components/RegistrasiBisnis.tsx b/src-migrate/modules/register/components/RegistrasiBisnis.tsx index 332c5358..5933b5f2 100644 --- a/src-migrate/modules/register/components/RegistrasiBisnis.tsx +++ b/src-migrate/modules/register/components/RegistrasiBisnis.tsx @@ -13,11 +13,11 @@ const RegistrasiBisnis: React.FC<FormProps> = ({ chekValid, buttonSubmitClick, }) => { - const [isPKP, setIsPKP] = useState(true); + const [isPKP, setIsPKP] = useState(false); const [isTerdaftar, setIsTerdaftar] = useState(false); const [isDropIndividu, setIsDropIndividu] = useState(true); const [isBisnisClicked, setisBisnisClicked] = useState(true); - const [selectedValue, setSelectedValue] = useState('PKP'); + const [selectedValue, setSelectedValue] = useState('Non-PKP'); const [selectedValueBisnis, setSelectedValueBisnis] = useState('false'); const { validate, updateForm } = useRegisterStore(); diff --git a/src-migrate/modules/register/index.tsx b/src-migrate/modules/register/index.tsx index 39f4771c..06f7cbea 100644 --- a/src-migrate/modules/register/index.tsx +++ b/src-migrate/modules/register/index.tsx @@ -101,6 +101,13 @@ const Register = () => { status: 'warning', }); break; + case 'BISNIS_NOT_FOUND': + toast({ + ...toastProps, + title: 'Bisnis tidak ditemukan', + status: 'warning', + }); + break; } }; diff --git a/src-migrate/modules/register/stores/usePengajuanTempoStore.ts b/src-migrate/modules/register/stores/usePengajuanTempoStore.ts index 1e086c06..79ab3612 100644 --- a/src-migrate/modules/register/stores/usePengajuanTempoStore.ts +++ b/src-migrate/modules/register/stores/usePengajuanTempoStore.ts @@ -171,7 +171,7 @@ type StatePengiriman = { }; type ActionPengiriman = { updateFormPengiriman: (name: string, value: string) => void; - + updateDokumenProsedur: (name: string, format: string, base64: string) => void; validatePengiriman: () => void; }; export const usePengajuanTempoStorePengiriman = create< @@ -186,6 +186,8 @@ export const usePengajuanTempoStorePengiriman = create< districtPengiriman: '', subDistrictPengiriman: '', zipPengiriman: '', + PICBarangMobile: '', + invoicePicMobile: '', invoicePicTittle: '', invoicePic: '', isSameAddrees: '', @@ -202,12 +204,21 @@ export const usePengajuanTempoStorePengiriman = create< dokumenPengirimanInput: '', dokumenKirimInput: '', dokumenPengirimanInvoice: '', + dokumenProsedur: { name: '', format: '', base64: '' }, }, updateFormPengiriman: (name, value) => set((state) => ({ formPengiriman: { ...state.formPengiriman, [name]: value }, })), + updateDokumenProsedur: (name, format, base64) => + set((state) => ({ + formPengiriman: { + ...state.formPengiriman, + dokumenProsedur: { name, format, base64 }, + }, + })), + errorsPengiriman: {}, validatePengiriman: () => { try { @@ -260,6 +271,7 @@ export const usePengajuanTempoStoreDokumen = create< dokumenLaporanKeuangan: { name: '', format: '', base64: '' }, dokumenFotoKantor: { name: '', format: '', base64: '' }, dokumenTempatBekerja: { name: '', format: '', base64: '' }, + dokumenProsedur: { name: '', format: '', base64: '' }, }, // Memperbarui dokumen dengan name, format, dan base64 diff --git a/src-migrate/modules/side-banner/index.tsx b/src-migrate/modules/side-banner/index.tsx index 878b8e70..7bc5f394 100644 --- a/src-migrate/modules/side-banner/index.tsx +++ b/src-migrate/modules/side-banner/index.tsx @@ -5,12 +5,15 @@ import Image from "~/components/ui/image";; import { getBanner } from "~/services/banner"; import { getRandomInt } from '@/utils/getRandomInt'; -const SideBanner = () => { +interface SideBannerProps { + query?: string; // Menentukan bahwa 'query' adalah string (bisa undefined) +} + +const SideBanner: React.FC<SideBannerProps> = ({ query }) => { const fetchSideBanner = useQuery({ - queryKey: 'sideBanner', - queryFn: () => getBanner({ type: 'side-banner-search' }) + queryKey: ["sideBanner", query], + queryFn: () => getBanner({ type: "side-banner-search", keyword: query }), }); - const length = useMemo(() => fetchSideBanner.data?.length, [fetchSideBanner.data]); const randomIndex = useMemo(() => getRandomInt(length), [length]); const banner = fetchSideBanner?.data?.[randomIndex] || false; diff --git a/src-migrate/pages/api/product-variant/[id].tsx b/src-migrate/pages/api/product-variant/[id].tsx index 2c46ac89..0f7524d0 100644 --- a/src-migrate/pages/api/product-variant/[id].tsx +++ b/src-migrate/pages/api/product-variant/[id].tsx @@ -6,7 +6,7 @@ const SOLR_HOST = process.env.SOLR_HOST as string export default async function handler(req: NextApiRequest, res: NextApiResponse) { const variantId = req.query.id as string - let price_tier = 'tier1' + let price_tier = 'tier1_v2' let auth = req.cookies.auth ? JSON.parse(req.cookies.auth) : null if (auth?.pricelist) price_tier = auth.pricelist @@ -29,7 +29,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const map = async (variant: any, price_tier: string) => { const data: any = {} - const price = variant[`price_${price_tier}_v2_f`] || 0 + const price = variant[`price_${price_tier}_f`] || 0 data.id = parseInt(variant.id) data.parent_id = variant.template_id_i diff --git a/src-migrate/services/banner.ts b/src-migrate/services/banner.ts index 1b46ba06..7fb922cf 100644 --- a/src-migrate/services/banner.ts +++ b/src-migrate/services/banner.ts @@ -3,9 +3,16 @@ import { IBanner } from '~/types/banner'; export const getBanner = async ({ type, + keyword, }: { type: string; + keyword?: string; // Tambahkan keyword sebagai parameter opsional }): Promise<IBanner[]> => { const searchParams = new URLSearchParams({ type }); - return await odooApi('GET', `/api/v1/banner?${searchParams.toString()}`); + + if (keyword) { + searchParams.append("keyword", keyword); + } + + return await odooApi("GET", `/api/v1/banner?${searchParams.toString()}`); }; diff --git a/src-migrate/types/auth.ts b/src-migrate/types/auth.ts index 1b400e95..f451ef75 100644 --- a/src-migrate/types/auth.ts +++ b/src-migrate/types/auth.ts @@ -30,7 +30,7 @@ export type RegisterProps = z.infer<typeof registerSchema>; export type RegisterResApiProps = { register: boolean; - reason: 'EMAIL_USED' | 'NOT_ACTIVE' | null; + reason: 'EMAIL_USED' | 'NOT_ACTIVE' | 'BISNIS_NOT_FOUND' | null; }; type ActivationResProps = { diff --git a/src-migrate/types/product.ts b/src-migrate/types/product.ts index 85ea702a..746cdd4a 100644 --- a/src-migrate/types/product.ts +++ b/src-migrate/types/product.ts @@ -4,6 +4,7 @@ export interface IProduct { id: number; image: string; image_mobile: string; + image_carousel: string[]; code: string; display_name: string; name: string; @@ -34,7 +35,7 @@ export interface IProduct { name: string; logo: string; }; - voucher_pasti_hemat : any; + voucher_pasti_hemat: any; } export interface IProductDetail extends IProduct { diff --git a/src-migrate/types/tempo.ts b/src-migrate/types/tempo.ts index d043e2d6..880a30bb 100644 --- a/src-migrate/types/tempo.ts +++ b/src-migrate/types/tempo.ts @@ -42,6 +42,8 @@ export type tempoPropsPengiriman = { cityPengiriman: string; streetInvoice: string; zip: string; + PICBarangMobile: string; + invoicePicMobile: string; invoicePic: string; isSameAddrees: string; stateInvoice: string; @@ -52,6 +54,7 @@ export type tempoPropsPengiriman = { dokumenPengirimanInput: string; dokumenPengirimanInvoice: string; dokumenPengirimanInvoiceInput: string; + dokumenProsedur: string; }; export type tempoPropsSupplier = { supplier: string; @@ -82,7 +85,7 @@ export type TempoPropsDokumen = z.infer<typeof TempoSchemaDokumen>; export type TempoResApiProps = { Tempo: boolean; - reason: 'EMAIL_USED' | 'NOT_ACTIVE' | null; + reason: 'EMAIL_USED' | 'NOT_ACTIVE' | 'BISNIS_NOT_FOUND' | null; }; type ActivationResProps = { diff --git a/src-migrate/types/voucher.ts b/src-migrate/types/voucher.ts index 3e90f449..d3140372 100644 --- a/src-migrate/types/voucher.ts +++ b/src-migrate/types/voucher.ts @@ -3,6 +3,7 @@ export interface IVoucher { image: string; name: string; code: string; + voucher_category: []; description: string | false; remaining_time: string; } diff --git a/src-migrate/validations/tempo.ts b/src-migrate/validations/tempo.ts index f019275c..46ac1ef1 100644 --- a/src-migrate/validations/tempo.ts +++ b/src-migrate/validations/tempo.ts @@ -8,7 +8,7 @@ export const TempoSchema = z.object({ state: z.string().min(1, { message: 'Provinsi harus dipilih' }), city: z.string().min(1, { message: 'Kota harus dipilih' }), district: z.string().min(1, { message: 'Kecamatan harus dipilih' }), - subDistrict: z.string().min(1, { message: 'Kelurahan harus dipilih' }), + subDistrict: z.string().optional(), mobile: z .string() .min(1, { message: 'Nomor telepon harus diisi' }) @@ -33,50 +33,81 @@ export const TempoSchema = z.object({ .min(1, { message: 'Category produk harus dipilih' }), }); -export const TempoSchemaKontakPerson = z.object({ - direkturName: z.string().min(1, { message: 'Nama harus diisi' }), - direkturTittle: z.string().min(1, { message: 'Tittle harus dipilih' }), - financeName: z.string().min(1, { message: 'Nama harus diisi' }), - direkturMobile: z.string().optional(), - financeMobile: z +export const TempoSchemaKontakPerson = z + .object({ + direkturName: z.string().min(1, { message: 'Nama harus diisi' }), + direkturTittle: z.string().min(1, { message: 'Tittle harus dipilih' }), + financeName: z.string().min(1, { message: 'Nama harus diisi' }), + direkturMobile: z.string().optional(), + financeMobile: z + .string() + .min(1, { message: 'Nomor telepon harus diisi' }) + .refine((val) => /^\d{10,12}$/.test(val), { + message: 'Format nomor telepon tidak valid, contoh: 081234567890', + }), + purchasingTittle: z.string().min(1, { message: 'Tittle harus dipilih' }), + financeTittle: z.string().min(1, { message: 'Tittle harus dipilih' }), + purchasingMobile: z + .string() + .min(1, { message: 'Nomor telepon harus diisi' }) + .refine((val) => /^\d{10,12}$/.test(val), { + message: 'Format nomor telepon tidak valid, contoh: 081234567890', + }), + direkturEmail: z + .string() + .min(1, { message: 'Email harus diisi' }) + .email({ message: 'Email harus menggunakan format example@mail.com' }), + purchasingEmail: z + .string() + .min(1, { message: 'Email harus diisi' }) + .email({ message: 'Email harus menggunakan format example@mail.com' }), + financeEmail: z + .string() + .min(1, { message: 'Email harus diisi' }) + .email({ message: 'Email harus menggunakan format example@mail.com' }), + purchasingName: z.string().min(1, { message: 'Nama harus diisi' }), + }) + .refine( + (data) => + !data.financeEmail || // Jika financeEmail kosong, tidak perlu validasi + data.financeEmail !== data.purchasingEmail, // Validasi jika financeEmail ada + { + message: 'Email Finance tidak boleh sama dengan Email Purchasing', + path: ['financeEmail'], + } + ) + .refine( + (data) => + !data.direkturEmail || + (data.direkturEmail !== data.financeEmail && + data.direkturEmail !== data.purchasingEmail), + { + message: + 'Email Direktur tidak boleh sama dengan Email Finance atau Purchasing', + path: ['direkturEmail'], + } + ); +export const TempoSchemaPengiriman = z.object({ + PICTittle: z.string().min(1, { message: 'Tittle harus dipilih' }), + PICName: z.string().min(1, { message: 'Nama harus diisi' }), + streetPengiriman: z.string().min(1, { message: 'Alamat harus diisi' }), + statePengiriman: z.string().min(1, { message: 'Provinsi harus dipilih' }), + cityPengiriman: z.string().min(1, { message: 'Kota harus dipilih' }), + districtPengiriman: z.string().min(1, { message: 'Kecamatan harus dipilih' }), + subDistrictPengiriman: z.string().optional(), + zipPengiriman: z.string().min(1, { message: 'Kode pos harus diisi' }), + PICBarangMobile: z .string() .min(1, { message: 'Nomor telepon harus diisi' }) .refine((val) => /^\d{10,12}$/.test(val), { message: 'Format nomor telepon tidak valid, contoh: 081234567890', }), - purchasingTittle: z.string().min(1, { message: 'Tittle harus dipilih' }), - financeTittle: z.string().min(1, { message: 'Tittle harus dipilih' }), - purchasingMobile: z + invoicePicMobile: z .string() .min(1, { message: 'Nomor telepon harus diisi' }) .refine((val) => /^\d{10,12}$/.test(val), { message: 'Format nomor telepon tidak valid, contoh: 081234567890', }), - direkturEmail: z - .string() - .min(1, { message: 'Email harus diisi' }) - .email({ message: 'Email harus menggunakan format example@mail.com' }), - purchasingEmail: z - .string() - .min(1, { message: 'Email harus diisi' }) - .email({ message: 'Email harus menggunakan format example@mail.com' }), - financeEmail: z - .string() - .min(1, { message: 'Email harus diisi' }) - .email({ message: 'Email harus menggunakan format example@mail.com' }), - purchasingName: z.string().min(1, { message: 'Nama harus diisi' }), -}); -export const TempoSchemaPengiriman = z.object({ - PICTittle: z.string().min(1, { message: 'Tittle harus dipilih' }), - PICName: z.string().min(1, { message: 'Nama harus diisi' }), - streetPengiriman: z.string().min(1, { message: 'Alamat harus diisi' }), - statePengiriman: z.string().min(1, { message: 'Provinsi harus dipilih' }), - cityPengiriman: z.string().min(1, { message: 'Kota harus dipilih' }), - districtPengiriman: z.string().min(1, { message: 'Kecamatan harus dipilih' }), - subDistrictPengiriman: z - .string() - .min(1, { message: 'Kelurahan harus dipilih' }), - zipPengiriman: z.string().min(1, { message: 'Kode pos harus diisi' }), invoicePicTittle: z.string().min(1, { message: 'Tittle harus dipilih' }), invoicePic: z.string().min(1, { message: 'Nama pic invoice harus diisi' }), streetInvoice: z.string().min(1, { message: 'Alamat invoice harus diisi' }), @@ -87,18 +118,25 @@ export const TempoSchemaPengiriman = z.object({ districtInvoice: z .string() .min(1, { message: 'Kecamatan invoice harus dipilih' }), - subDistrictInvoice: z - .string() - .min(1, { message: 'Kelurahan invoice harus dipilih' }), + subDistrictInvoice: z.string().optional(), zipInvoice: z.string().min(1, { message: 'Kode pos harus diisi' }), isSameAddrees: z.string(), isSameAddreesStreet: z.string(), - tukarInvoiceInput: z.string().optional(), - tukarInvoiceInputPembayaran: z.string().optional(), + tukarInvoiceInput: z + .string() + .min(1, { message: 'Jadwal Penukaran Invoice Harus Diisi' }), + tukarInvoiceInputPembayaran: z + .string() + .min(1, { message: 'Jadwal Pembayaran Harus Diisi' }), dokumenPengiriman: z.string().optional(), dokumenPengirimanInput: z.string().optional(), dokumenKirimInput: z.string().optional(), dokumenPengirimanInvoiceInput: z.string().optional(), + dokumenProsedur: z.object({ + name: z.string().optional(), + format: z.string().optional(), + base64: z.string().optional(), + }), }); export const TempoSchemaSupplier = z.object({ supplier: z.string().min(1, { message: 'Nama supplier harus diisi' }), diff --git a/src/core/components/elements/Navbar/NavbarDesktop.jsx b/src/core/components/elements/Navbar/NavbarDesktop.jsx index d6b1bdea..03d7fa0c 100644 --- a/src/core/components/elements/Navbar/NavbarDesktop.jsx +++ b/src/core/components/elements/Navbar/NavbarDesktop.jsx @@ -423,6 +423,20 @@ const SocialMedias = () => ( loading='eager' /> </a> + <a + target='_blank' + rel='noreferrer' + href='https://x.com/indoteknikcom' + aria-label='X - Indoteknik.com' + > + <NextImage + src='/images/socials/x-indoteknik.png' + alt='X' + width={24} + height={24} + loading='eager' + /> + </a> {/* <a target='_blank' rel='noreferrer' href={whatsappUrl(null)}> <NextImage src='/images/socials/Whatsapp.png' @@ -487,6 +501,7 @@ const SocialMedias = () => ( loading='eager' /> </a> + </div> </div> ); diff --git a/src/lib/address/components/EditAddress.jsx b/src/lib/address/components/EditAddress.jsx index 62858465..ba6bd25b 100644 --- a/src/lib/address/components/EditAddress.jsx +++ b/src/lib/address/components/EditAddress.jsx @@ -38,7 +38,7 @@ const EditAddress = ({ id, defaultValues }) => { useEffect(() => { const loadProfile = async () => { - const dataProfile = await addressApi({ id: auth.parentId }); + const dataProfile = await addressApi({ id: auth.partnerId }); setValue('industry', dataProfile.industryId); setValue('companyType', dataProfile.companyTypeId); setValue('taxName', dataProfile.taxName); @@ -161,7 +161,7 @@ const EditAddress = ({ id, defaultValues }) => { } // if (isUpdated?.id) { - if (address?.id && auth.company ? isUpdated?.id : true) { + if (address?.id && (auth.company && auth?.partnerId == id ? isUpdated?.id : true)) { toast.success('Berhasil mengubah alamat'); router.back(); } else { diff --git a/src/lib/auth/components/CompanyProfile.jsx b/src/lib/auth/components/CompanyProfile.jsx index 410d6a23..d404ebe7 100644 --- a/src/lib/auth/components/CompanyProfile.jsx +++ b/src/lib/auth/components/CompanyProfile.jsx @@ -59,7 +59,11 @@ const CompanyProfile = () => { useEffect(() => { const loadProfile = async () => { const dataProfile = await addressApi({ - id: auth.parentId ? auth.parentId : auth.parent_id, + id: auth?.company + ? auth.parentId + ? auth.parentId + : auth.partnerId + : auth.partnerId, }); setCompany_type(dataProfile?.companyType); setValue('name', dataProfile?.name); @@ -311,9 +315,19 @@ const CompanyProfile = () => { <input {...register('npwp')} type='text' + disabled className='form-input mt-3' maxLength={16} /> + <span className='text-xs opacity-60 text-red-500'> + *Untuk mengganti NPWP bisa menghubungi kami{' '} + <a + href='https://wa.me/6281717181922' target='_blank' rel='noreferrer' + style={{ textDecoration: 'underline' }} + > + disini. + </a> + </span> <div className='text-caption-2 text-danger-500 mt-1'> {errors.npwp?.message} </div> diff --git a/src/lib/auth/components/Menu.jsx b/src/lib/auth/components/Menu.jsx index 4682dbab..df33314c 100644 --- a/src/lib/auth/components/Menu.jsx +++ b/src/lib/auth/components/Menu.jsx @@ -197,6 +197,7 @@ const Menu = () => { <button type='button' className='text-gray_r-12/80 p-2 text-left hover:bg-gray_r-5 ' + onClick={() => logout()} > <div className='flex gap-x-3 items-center'> <ImageNext diff --git a/src/lib/category/components/Category.jsx b/src/lib/category/components/Category.jsx index 91553295..15bfabc9 100644 --- a/src/lib/category/components/Category.jsx +++ b/src/lib/category/components/Category.jsx @@ -14,7 +14,7 @@ const Category = () => { const [openCategories, setOpenCategory] = useState([]); const [banner, setBanner] = useState([]); - const promotionProgram = useQuery('banner-promo-category-card', bannerApi({ type: 'banner-promo-category-card' })); + // const promotionProgram = useQuery('banner-promo-category-card', bannerApi({ type: 'banner-promo-category-card' })); useEffect(() => { const loadCategories = async () => { diff --git a/src/lib/checkout/components/Checkout.jsx b/src/lib/checkout/components/Checkout.jsx index e3b586f1..5256a328 100644 --- a/src/lib/checkout/components/Checkout.jsx +++ b/src/lib/checkout/components/Checkout.jsx @@ -39,18 +39,20 @@ const { getProductsCheckout } = require('../api/checkoutApi'); function convertToInternational(number) { if (typeof number !== 'string') { - throw new Error("Input harus berupa string"); + throw new Error('Input harus berupa string'); } if (number.startsWith('08')) { - return '+62' + number.slice(2); + return '+62' + number.slice(2); } return number; } const Checkout = () => { - const PPN = process.env.NEXT_PUBLIC_PPN ? parseFloat(process.env.NEXT_PUBLIC_PPN) : 0; + const PPN = process.env.NEXT_PUBLIC_PPN + ? parseFloat(process.env.NEXT_PUBLIC_PPN) + : 0; const router = useRouter(); const query = router.query.source ?? null; const qVoucher = router.query.voucher ?? null; @@ -145,15 +147,31 @@ const Checkout = () => { if (!listVouchers) { try { setLoadingVoucher(true); + const productCategories = products + ?.reduce((categories, product) => { + if (product.categories && Array.isArray(product.categories)) { + product.categories.forEach((category) => { + if (category.id && !categories.includes(category.id)) { + categories.push(category.id); + } + }); + } + return categories; + }, []) + .join(','); + let dataVoucher = await getVoucher(auth?.id, { source: query, type: 'all,brand', + partner_id: auth?.partnerId, + voucher_category: productCategories, // Add the product categories }); SetListVoucher(dataVoucher); let dataVoucherShipping = await getVoucher(auth?.id, { source: query, type: 'shipping', + voucher_category: productCategories, // Add the product categories }); SetListVoucherShipping(dataVoucherShipping); } finally { @@ -163,10 +181,23 @@ const Checkout = () => { }; const VoucherCode = async (code) => { - // let dataVoucher = await findVoucher(code, auth.id, query); + const productCategories = products + ?.reduce((categories, product) => { + if (product.categories && Array.isArray(product.categories)) { + product.categories.forEach((category) => { + if (category.id && !categories.includes(category.id)) { + categories.push(category.id); + } + }); + } + return categories; + }, []) + .join(','); + let dataVoucher = await getVoucher(auth?.id, { source: query, code: code, + voucher_category: productCategories, // Add the product categories }); if (dataVoucher.length <= 0) { SetFindVoucher(1); @@ -482,13 +513,25 @@ const Checkout = () => { if (query) { data.source = 'buy'; } - if (poNumber.current.value) data.po_number = poNumber.current.value; + if (poNumber.current.value) { + if (typeof file == 'undefined') { + toast.error( + 'Nomor PO ' + + poNumber.current.value + + ' telah dimasukkan, Harap upload file PO yang dimaksud' + ); + setIsLoading(false); + return; + } + data.po_number = poNumber.current.value; + } if (typeof file !== 'undefined') data.po_file = await getFileBase64(file); const isCheckouted = await checkoutApi({ data }); if (!isCheckouted?.id) { toast.error('Gagal melakukan transaksi, terjadi kesalahan internal'); + setIsLoading(false); return; } else { gtagPurchase(products, biayaKirim, isCheckouted.name); @@ -504,10 +547,12 @@ const Checkout = () => { gtag('set', 'user_data', { email: auth.email, - phone_number: convertToInternational(auth.mobile) ?? convertToInternational(auth.phone), + phone_number: + convertToInternational(auth.mobile) ?? + convertToInternational(auth.phone), }); - gtag('config', 'AW-954540379', { ' allow_enhanced_conversions':true } ) ; + gtag('config', 'AW-954540379', { ' allow_enhanced_conversions': true }); for (const product of products) deleteItemCart({ productId: product.id }); if (grandTotal > 0) { @@ -1114,7 +1159,7 @@ const Checkout = () => { </Skeleton> )} <Divider /> - <SectionValidation address={selectedAddress.invoicing} /> + <SectionValidation address={selectedAddress.shipping} /> <SectionExpedisi address={selectedAddress.shipping} listExpedisi={listExpedisi} @@ -1210,7 +1255,9 @@ const Checkout = () => { <div>{currencyFormat(cartCheckout?.subtotal)}</div> </div> <div className='flex gap-x-2 justify-between'> - <div className='text-gray_r-11'>PPN {((PPN - 1) * 100).toFixed(0)}%</div> + <div className='text-gray_r-11'> + PPN {((PPN - 1) * 100).toFixed(0)}% + </div> <div>{currencyFormat(cartCheckout?.tax)}</div> </div> <div className='flex gap-x-2 justify-between'> @@ -1412,7 +1459,7 @@ const Checkout = () => { </Skeleton> )} <Divider /> - <SectionValidation address={selectedAddress.invoicing} /> + <SectionValidation address={selectedAddress.shipping} /> <SectionExpedisi address={selectedAddress.shipping} listExpedisi={listExpedisi} @@ -1514,7 +1561,9 @@ const Checkout = () => { <div>{currencyFormat(cartCheckout?.subtotal)}</div> </div> <div className='flex gap-x-2 justify-between'> - <div className='text-gray_r-11'>PPN {((PPN - 1) * 100).toFixed(0)}%</div> + <div className='text-gray_r-11'> + PPN {((PPN - 1) * 100).toFixed(0)}% + </div> <div>{currencyFormat(cartCheckout?.tax)}</div> </div> <div className='flex gap-x-2 justify-between'> @@ -1697,7 +1746,8 @@ const SectionAddress = ({ address, label, url }) => ( ); const SectionValidation = ({ address }) => - address?.stateId == 0 && ( + address?.stateId == 0 || + (address?.rajaongkirCityId == 0 && ( <BottomPopup active={true} title='Update Alamat'> <div className='leading-7 text-gray_r-12/80'> Mohon untuk memperbarui alamat Anda dengan mengklik tombol di bawah ini.{' '} @@ -1711,7 +1761,7 @@ const SectionValidation = ({ address }) => </Link> </div> </BottomPopup> - ); + )); const SectionExpedisi = ({ address, diff --git a/src/lib/checkout/components/CheckoutSection.jsx b/src/lib/checkout/components/CheckoutSection.jsx index 623152c6..c6be9056 100644 --- a/src/lib/checkout/components/CheckoutSection.jsx +++ b/src/lib/checkout/components/CheckoutSection.jsx @@ -33,7 +33,7 @@ export const SectionAddress = ({ address, label, url }) => { }; export const SectionValidation = ({ address }) => - address?.rajaongkirCityId == 0 && ( + address?.stateId == 0 || address?.rajaongkirCityId == 0 && ( <BottomPopup active={true} title='Update Alamat'> <div className='leading-7 text-gray_r-12/80'> Mohon untuk memperbarui alamat Anda dengan mengklik tombol di bawah ini.{' '} diff --git a/src/lib/flashSale/api/flashSaleApi.js b/src/lib/flashSale/api/flashSaleApi.js index 115b07dc..410b720c 100644 --- a/src/lib/flashSale/api/flashSaleApi.js +++ b/src/lib/flashSale/api/flashSaleApi.js @@ -1,8 +1,9 @@ import odooApi from '@/core/api/odooApi' -const flashSaleApi = async () => { - const flashSale = await odooApi('GET', '/api/v1/flashsale/header') +const flashSaleApi = async ({isShow = true}) => { + const flashSale = await odooApi('GET', '/api/v1/flashsale/header?is_show_program='+isShow) return flashSale } export default flashSaleApi +
\ No newline at end of file diff --git a/src/lib/flashSale/components/FlashSaleNonDisplay.jsx b/src/lib/flashSale/components/FlashSaleNonDisplay.jsx index 4b420fac..adcc7ba0 100644 --- a/src/lib/flashSale/components/FlashSaleNonDisplay.jsx +++ b/src/lib/flashSale/components/FlashSaleNonDisplay.jsx @@ -13,14 +13,14 @@ const FlashSaleNonDisplay = () => { const router = useRouter(); useEffect(() => { const loadFlashSales = async () => { - const dataFlashSales = await flashSaleApi(); + const dataFlashSales = await flashSaleApi({isShow: false}); setFlashSales(dataFlashSales); setIsLoading(false); }; loadFlashSales(); }, []); - const handleSubmit = () => { - router.push(`/shop/search?penawaran=${flashSales[0]?.pricelistId}`); + const handleSubmit = (flashSale) => { + router.push(`/shop/search?penawaran=${flashSale?.pricelistId}`); }; if (isLoading) { return <FlashSaleSkeleton />; @@ -33,10 +33,10 @@ const FlashSaleNonDisplay = () => { <div key={index}> <div className='flex items-center mb-4 justify-between '> <div className='font-medium sm:text-h-lg mt-1.5'> - Penawaran Terbatas + {flashSale.name} </div> <div - onClick={handleSubmit} + onClick={() => handleSubmit(flashSale)} className='!text-red-500 font-semibold cursor-pointer' > Lihat Semua @@ -56,7 +56,7 @@ const FlashSaleProduct = ({ flashSaleId }) => { useEffect(() => { const loadProducts = async () => { const dataProducts = await productSearchApi({ - query: `fq=-flashsale_id_i:${flashSaleId}&fq=flashsale_price_f:[1 TO *]&limit=25&orderBy=flashsale-discount-desc&source=similar`, + query: `fq=flashsale_id_i:${flashSaleId}&fq=flashsale_price_f:[1 TO *]&limit=25&orderBy=flashsale-discount-desc&source=similar`, operation: 'AND', }); setProducts(dataProducts.response); diff --git a/src/lib/home/components/ServiceList.jsx b/src/lib/home/components/ServiceList.jsx index e32e8747..8fa1cf22 100644 --- a/src/lib/home/components/ServiceList.jsx +++ b/src/lib/home/components/ServiceList.jsx @@ -59,7 +59,7 @@ const ServiceList = () => { </div> <div className='w-full '> <Link - href='/pembayaran-tempo-detail' + href='/pengajuan-tempo' className='border border-gray-200 p-2 flex items-center gap-x-2 rounded-lg' > <div className=''> diff --git a/src/lib/pengajuan-tempo/component/Dokumen.jsx b/src/lib/pengajuan-tempo/component/Dokumen.jsx index f986d47a..aedb9016 100644 --- a/src/lib/pengajuan-tempo/component/Dokumen.jsx +++ b/src/lib/pengajuan-tempo/component/Dokumen.jsx @@ -566,7 +566,7 @@ const Dokumen = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { > <div> <label className='form-label text-nowrap'> - Foto tempat kerja + Foto Ruangan Tempat Kerja </label> <span className='text-xs opacity-60 text-red-500'> Format: pdf, jpeg, jpg, png. max file size 2MB @@ -1144,7 +1144,7 @@ const Dokumen = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { ref={dokumenTempatBekerjaRef} > <label className='form-label text-nowrap'> - Foto tempat kerja + Foto Ruangan Tempat Kerja </label> <div className='flex flex-row gap-2'> <label diff --git a/src/lib/pengajuan-tempo/component/KonfirmasiDokumen.jsx b/src/lib/pengajuan-tempo/component/KonfirmasiDokumen.jsx index 66d33b43..7c10f80c 100644 --- a/src/lib/pengajuan-tempo/component/KonfirmasiDokumen.jsx +++ b/src/lib/pengajuan-tempo/component/KonfirmasiDokumen.jsx @@ -646,7 +646,7 @@ const KonfirmasiDokumen = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { <div className='w-full flex flex-row items-center '> <div className='w-2/5'> <label className='form-label text-nowrap'> - Foto tempat kerja + Foto Ruangan Tempat Kerja </label> </div> <div className='w-3/5'> @@ -1492,7 +1492,7 @@ const KonfirmasiDokumen = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { <div className='w-full flex flex-row items-center '> <div className='w-2/5'> <label className='form-label text-wrap'> - Foto tempat kerja + Foto Ruangan Tempat Kerja </label> </div> <div className='w-3/5'> diff --git a/src/lib/pengajuan-tempo/component/PengajuanTempo.jsx b/src/lib/pengajuan-tempo/component/PengajuanTempo.jsx index 5bef5134..7cf201b7 100644 --- a/src/lib/pengajuan-tempo/component/PengajuanTempo.jsx +++ b/src/lib/pengajuan-tempo/component/PengajuanTempo.jsx @@ -173,11 +173,11 @@ const PengajuanTempo = () => { } return data; // Jika bukan object atau array, kembalikan nilai aslinya }; - + const payment = auth?.parentId ? auth?.parentId : auth?.partnerId; try { const dataPaymentTerm = await odooApi( 'GET', - `/api/v1/partner/detail-tempo/${auth.parentId}` + `/api/v1/partner/detail-tempo/${payment}` ); const transformedData = transformKeysToCamelCase(dataPaymentTerm); setBigData(transformedData); @@ -191,7 +191,6 @@ const PengajuanTempo = () => { useEffect(() => { const cachedData = bigData; - const loadBigData = async () => { if (cachedData) { // Ambil kunci-kunci yang relevan berdasarkan currentStep dari stepDivsForm @@ -218,6 +217,10 @@ const PengajuanTempo = () => { : String(bigData[key]); // Untuk tipe primitif // Kirim data yang sudah diubah ke string ke stepDivsUpdateForm stepDivsUpdateForm[currentStep](key, stringData); + if (key == 'dokumenProsedur') { + const stringData = bigData[key]; + stepDivsUpdateForm[currentStep](key, stringData); + } } }); } @@ -293,11 +296,14 @@ const PengajuanTempo = () => { try { const address = await createPengajuanTempoApi({ id: idTempo, - user_id: auth.parentId, + user_id: auth.parentId ? auth.parentId : auth.partnerId, partner_id: auth.partnerId, section: label, tempo_request: tempoRequest, ...formData, + formDokumenProsedur: formData.dokumenProsedur + ? JSON.stringify(formData.dokumenProsedur) + : false, }); if (address.id) { setIdTempo(address.id); @@ -341,7 +347,7 @@ const PengajuanTempo = () => { try { const address = await createPengajuanTempoApi({ id: idTempo, - user_id: auth.parentId, + user_id: auth.parentId ? auth.parentId : auth.partnerId, partner_id: auth.partnerId, formDocs: JSON.stringify(formattedDokumen), }); @@ -402,7 +408,7 @@ const PengajuanTempo = () => { try { const address = await createPengajuanTempoApi({ id: idTempo, - user_id: auth.parentId, + user_id: auth.parentId ? auth.parentId : auth.partnerId, partner_id: auth.partnerId, formSupplier: JSON.stringify(productOrder), }); @@ -447,7 +453,7 @@ const PengajuanTempo = () => { const address = await createPengajuanTempoApi({ id: 0, partner_id: auth.partnerId, - user_id: auth.parentId, + user_id: auth.parentId ? auth.parentId : auth.partnerId, tempo_request: false, ...form, }); @@ -466,6 +472,9 @@ const PengajuanTempo = () => { user_id: address2.userId, tempo_request: false, ...formPengiriman, + formDokumenProsedur: formPengiriman.dokumenProsedur + ? JSON.stringify(formPengiriman.dokumenProsedur) + : false, }); if (address3.id && formattedDokumen.length > 0) { // Kirim dokumen yang sudah difilter @@ -518,7 +527,7 @@ const PengajuanTempo = () => { toast.dismiss(toastId); setIsLoading(false); - toast.error('Terjadi kesalahan dalam pengiriman formulir'); + toast.error('Terjadi kesalahan dalam pengiriman formulir hehehehe'); console.error(error); } }; @@ -647,11 +656,14 @@ const PengajuanTempo = () => { <TempoTermCondition onCheckChange={handleCheckChange} /> <Button colorScheme='red' - w={`${isMobile ? 'full' : '36'}`} - isDisabled={!isCheckedTNC} + w={`${isMobile ? 'full' : '48'}`} + isDisabled={!isCheckedTNC || isLoading} onClick={handleDaftarTempo} > - Daftar Tempo {<ChevronRightIcon className='w-5' />} + {isLoading + ? 'Loading...' + : 'Daftar Tempo'} + {!isLoading && <ChevronRightIcon className='w-5' />} </Button> </div> )} diff --git a/src/lib/pengajuan-tempo/component/Pengiriman.jsx b/src/lib/pengajuan-tempo/component/Pengiriman.jsx index 755cf45d..a8e7fd22 100644 --- a/src/lib/pengajuan-tempo/component/Pengiriman.jsx +++ b/src/lib/pengajuan-tempo/component/Pengiriman.jsx @@ -13,6 +13,7 @@ import { } from '../../../../src-migrate/modules/register/stores/usePengajuanTempoStore'; import { toast } from 'react-hot-toast'; import useDevice from '@/core/hooks/useDevice'; +import getFileBase64 from '@/core/utils/getFileBase64'; const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { const { control, watch, setValue } = useForm(); const { isDesktop, isMobile } = useDevice(); @@ -21,6 +22,7 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { errorsPengiriman, validatePengiriman, updateFormPengiriman, + updateDokumenProsedur, } = usePengajuanTempoStorePengiriman(); const { form } = usePengajuanTempoStore(); const [states, setState] = useState([]); @@ -34,6 +36,7 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { const [zipsInvoice, setZipsInvoice] = useState([]); const [sameAddress, setSameAddress] = useState(false); const [sameAddressStreet, setSameAddressStreet] = useState(false); + const [nameFile, setNameFile] = useState(); const [selectedIds, setSelectedIds] = useState( formPengiriman.dokumenPengiriman @@ -299,11 +302,66 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { validatePengiriman(); }; + const handleInputChangeFile = async (event) => { + let fileBase64 = ''; + const { name } = event.target; + const file = event.target.files?.[0]; + // Allowed file extensions + const allowedExtensions = ['pdf', 'png', 'jpg', 'jpeg']; + let fileExtension = ''; + if (file) { + fileExtension = file.name.split('.').pop()?.toLowerCase(); // Extract file extension + + // Check if the file extension is allowed + if (!fileExtension || !allowedExtensions.includes(fileExtension)) { + toast.error( + 'Format file yang diijinkan adalah .pdf, .png, .jpg, atau .jpeg', + { duration: 4000 } + ); + + event.target.value = ''; + return; + } + + // Check for file size + if (file.size > 2000000) { + // try { + // const toastId = toast.loading('mencoba mengompresi file...'); + // // Compress image file + // const options = { + // maxSizeMB: 0.5, // Target size in MB + // maxWidthOrHeight: 1920, // Adjust as needed + // useWebWorker: true, + // }; + // const compressedFile = await imageCompression(file, options); + // toast.success('berhasil mengompresi file', { duration: 4000 }); + // // Convert compressed file to Base64 + // fileBase64 = await getFileBase64(compressedFile); + // updateFormDokumen( + // name, + // compressedFile.name, + // fileExtension, + // fileBase64 + // ); + // } catch (error) { + // toast.error('Gagal mengompresi file', { duration: 4000 }); + // } + toast.error('Max File Upload 2MB', { duration: 4000 }); + } else { + // Convert file to Base64 + fileBase64 = await getFileBase64(file); + updateDokumenProsedur(file.name, fileExtension, fileBase64); + } + validatePengiriman(); + } + }; const isFormValid = useMemo( () => Object.keys(errorsPengiriman).length === 0, [errorsPengiriman] ); + const tukarInvoiceInputRef = useRef(null); + const tukarInvoiceInputPembayaranRef = useRef(null); const PICNameRef = useRef(null); const streetPengirimanRef = useRef(null); const statePengirimanRef = useRef(null); @@ -311,7 +369,9 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { const districtPengirimanRef = useRef(null); const subDistrictPengirimanRef = useRef(null); const zipRef = useRef(null); + const PICBarangMobileRef = useRef(null); const invoicePicRef = useRef(null); + const invoicePicMobileRef = useRef(null); const streetInvoiceRef = useRef(null); const stateInvoiceRef = useRef(null); const cityInvoiceRef = useRef(null); @@ -332,6 +392,14 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { behavior: 'smooth', block: 'center', }; + if (errorsPengiriman.tukarInvoiceInput && tukarInvoiceInputRef.current) { + tukarInvoiceInputRef.current.scrollIntoView(options); + return; + } + if (errorsPengiriman.tukarInvoiceInputPembayaran && tukarInvoiceInputPembayaranRef.current) { + tukarInvoiceInputPembayaranRef.current.scrollIntoView(options); + return; + } if (errorsPengiriman.PICName && PICNameRef.current) { PICNameRef.current.scrollIntoView(options); return; @@ -366,10 +434,18 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { zipRef.current.scrollIntoView(options); return; } + if (errorsPengiriman.PICBarangMobile && PICBarangMobileRef.current) { + PICBarangMobileRef.current.scrollIntoView(options); + return; + } if (errorsPengiriman.invoicePic && invoicePicRef.current) { invoicePicRef.current.scrollIntoView(options); return; } + if (errorsPengiriman.invoicePicMobile && invoicePicMobileRef.current) { + invoicePicMobileRef.current.scrollIntoView(options); + return; + } if (errorsPengiriman.streetInvoice && streetInvoiceRef.current) { streetInvoiceRef.current.scrollIntoView(options); return; @@ -803,6 +879,38 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { <div className='flex flex-row justify-between items-start'> <div className='w-2/5'> <label className='form-label text-nowrap'> + No. HP PIC Penerimaan Barang + </label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + isi nomor PIC penerimaan barang yang bertanggung jawab di + perusahaan anda + </span> + )} + </div> + <div className='w-3/5'> + <input + id='PICBarangMobile' + name='PICBarangMobile' + ref={PICBarangMobileRef} + placeholder='Masukkan nomor PIC penerimaan barang' + value={formPengiriman.PICBarangMobile} + type='tel' + className='form-input' + aria-invalid={errorsPengiriman.PICBarangMobile} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.PICBarangMobile} + </div> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> Alamat Pengiriman Barang </label> {!isKonfirmasi && ( @@ -954,9 +1062,7 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { <HookFormSelect {...props} options={zips} - disabled={ - !watchsubDistrict || sameAddressStreet - } + disabled={sameAddressStreet} placeholder='Zip' /> ) : ( @@ -967,9 +1073,7 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { ref={zipRef} placeholder='Kode Pos' type='number' - disabled={ - !watchsubDistrict || sameAddressStreet - } + disabled={sameAddressStreet} value={formPengiriman.zipPengiriman} className='form-input' onChange={handleInputChange} @@ -1035,6 +1139,38 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { <div className='flex flex-row justify-between items-start'> <div className='w-2/5'> + <label className='form-label text-nowrap'> + No. HP PIC Penerimaan Invoice + </label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + isi nomor PIC penerimaan invoice yang bertanggung jawab di + perusahaan anda + </span> + )} + </div> + <div className='w-3/5'> + <input + id='invoicePicMobile' + name='invoicePicMobile' + ref={invoicePicMobileRef} + placeholder='Masukkan nomor PIC Penerimaan Invoice' + value={formPengiriman.invoicePicMobile} + type='tel' + className='form-input' + aria-invalid={errorsPengiriman.invoicePicMobile} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.invoicePicMobile} + </div> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5'> <label className='form-label text-nowrap'> Alamat Pengiriman Invoice </label> @@ -1192,9 +1328,7 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { <HookFormSelect {...props} options={zipsInvoice} - disabled={ - !watchsubDistrictInvoice || sameAddress - } + disabled={sameAddress} placeholder='Zip' /> ) : ( @@ -1204,9 +1338,7 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { ref={zipInvoiceRef} placeholder='Kode Pos' type='number' - disabled={ - !watchsubDistrictInvoice || sameAddress - } + disabled={sameAddress} value={formPengiriman.zipInvoice} className='form-input' onChange={handleInputChange} @@ -1231,11 +1363,10 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { <div className='w-2/5'> <label className='form-label text-wrap'> Jadwal Penukaran Invoice{' '} - <span className=' opacity-60'>(Opsional)</span> </label> {!isKonfirmasi && ( <span className='text-xs opacity-60'> - isi jika perusahaan anda memiliki jadwal penukaran invoice + isi jadwal penukaran invoice </span> )} </div> @@ -1248,9 +1379,17 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { value={formPengiriman.tukarInvoiceInput} className='form-input' rows={4} + aria-invalid={errorsPengiriman.tukarInvoiceInput} + ref={tukarInvoiceInputRef} cols={40} onChange={handleInputChange} + required /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.tukarInvoiceInput} + </div> + )} </div> </div> @@ -1258,11 +1397,10 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { <div className='w-2/5'> <label className='form-label text-nowrap'> Jadwal Pembayaran{' '} - <span className=' opacity-60'>(Opsional)</span> </label> {!isKonfirmasi && ( <span className='text-xs opacity-60'> - isi jika perusahaan anda memiliki jadwal pembayaran + isi jadwal pembayaran </span> )} </div> @@ -1273,10 +1411,60 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { placeholder='Masukkan jadwal pembayaran' value={formPengiriman.tukarInvoiceInputPembayaran} className='form-input' + aria-invalid={errorsPengiriman.tukarInvoiceInputPembayaran} + ref={tukarInvoiceInputPembayaranRef} rows={4} cols={40} onChange={handleInputChange} + required /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.tukarInvoiceInputPembayaran} + </div> + )} + </div> + </div> + + <div className='w-full flex flex-row justify-between items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Prosedur Pengiriman{' '} + <span className=' opacity-60'>(Opsional)</span> + </label> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start gap-2'> + <label + htmlFor='dokumenProsedur' + className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' + > + {formPengiriman?.dokumenProsedur?.name + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + id='dokumenProsedur' + name='dokumenProsedur' + type='file' + title=' ' + className='hidden' + aria-invalid={formPengiriman.dokumenProsedur} + onChange={handleInputChangeFile} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='text-gray-600 line-clamp-2'> + {formPengiriman?.dokumenProsedur?.name} + </span> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.dokumenProsedur} + </div> + )} </div> </div> @@ -1483,6 +1671,30 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { </div> <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label text-nowrap'> + No. HP PIC Penerimaan Barang + </label> + <div className='flex items-center border border-gray-300 rounded-md w-full'> + <input + id='PICBarangMobile' + name='PICBarangMobile' + ref={PICBarangMobileRef} + value={formPengiriman.PICBarangMobile} + placeholder='Masukkan nomor PIC penerimaan barang' + type='tel' + className='form-input' + aria-invalid={errorsPengiriman.PICBarangMobile} + onChange={handleInputChange} + /> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.PICBarangMobile} + </div> + )} + </div> + + <div className='flex flex-col gap-2 justify-between items-start'> <label className='form-label text-nowrap'> Alamat Pengiriman Barang </label> @@ -1701,6 +1913,30 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { </div> <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label text-nowrap'> + No. HP PIC Penerimaan Invoice + </label> + <div className='flex items-center border border-gray-300 rounded-md w-full'> + <input + id='invoicePicMobile' + name='invoicePicMobile' + ref={invoicePicMobileRef} + value={formPengiriman.invoicePicMobile} + placeholder='Masukkan nomor PIC Penerimaan Invoice' + type='tel' + className='form-input' + aria-invalid={errorsPengiriman.invoicePicMobile} + onChange={handleInputChange} + /> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.PICBarangMobile} + </div> + )} + </div> + + <div className='flex flex-col gap-2 justify-between items-start'> <label className='form-label text-nowrap'> Alamat Pengiriman Invoice </label> @@ -1920,6 +2156,48 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { </div> </div> + <div className='w-full flex flex-col justify-between items-start gap-2'> + <div className=''> + <label className='form-label text-nowrap'> + Prosedur Pengiriman{' '} + <span className=' opacity-60'>(Opsional)</span> + </label> + </div> + <div className='flex flex-col gap-2'> + <div className='flex flex-row items-start gap-2'> + <label + htmlFor='dokumenProsedur' + className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' + > + {formPengiriman?.dokumenProsedur?.name + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + id='dokumenProsedur' + name='dokumenProsedur' + type='file' + title=' ' + className='hidden' + aria-invalid={errorsPengiriman.dokumenProsedur} + onChange={handleInputChangeFile} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2'> + {formPengiriman?.dokumenProsedur?.name} + </span> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.dokumenProsedur} + </div> + )} + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + </div> + </div> + <div className='flex flex-col gap-2 justify-between items-start'> <label className='form-label text-wrap'> Apakah ada dokumen tanda terima yang diberikan pada saat diff --git a/src/lib/pengajuan-tempo/component/informasiPerusahaan.jsx b/src/lib/pengajuan-tempo/component/informasiPerusahaan.jsx index 25a3a7ee..4898a3f8 100644 --- a/src/lib/pengajuan-tempo/component/informasiPerusahaan.jsx +++ b/src/lib/pengajuan-tempo/component/informasiPerusahaan.jsx @@ -470,7 +470,7 @@ const InformasiPerusahaan = ({ useEffect(() => { const loadProfile = async () => { try { - const dataProfile = await addressApi({ id: auth.parentId }); + const dataProfile = await addressApi({ id: auth.parentId ? auth.parentId : auth.partnerId }); if (dataProfile.name) { updateForm('name', dataProfile.name); } @@ -507,7 +507,7 @@ const InformasiPerusahaan = ({ } }; - if (auth?.parentId) { + if (auth?.parentId || auth?.partnerId) { loadProfile(); } }, [auth?.parentId]); @@ -782,7 +782,7 @@ const InformasiPerusahaan = ({ <HookFormSelect {...props} options={zips} - disabled={!watchsubDistrict} + // disabled={!watchsubDistrict} placeholder='Zip' /> ) : ( @@ -793,7 +793,7 @@ const InformasiPerusahaan = ({ ref={zipRef} placeholder='Kode Pos' type='number' - disabled={!watchsubDistrict} + // disabled={!watchsubDistrict} value={form.zip} className='form-input' onChange={handleInputChange} @@ -1333,7 +1333,7 @@ const InformasiPerusahaan = ({ <HookFormSelect {...props} options={zips} - disabled={!watchsubDistrict} + // disabled={!watchsubDistrict} placeholder='Zip' /> ) : ( @@ -1344,7 +1344,7 @@ const InformasiPerusahaan = ({ ref={zipRef} placeholder='Kode Pos' type='number' - disabled={!watchsubDistrict} + // disabled={!watchsubDistrict} value={form.zip} className='form-input' onChange={handleInputChange} diff --git a/src/lib/product/components/Product/ProductDesktopVariant.jsx b/src/lib/product/components/Product/ProductDesktopVariant.jsx index 5dfd452b..de88e5bb 100644 --- a/src/lib/product/components/Product/ProductDesktopVariant.jsx +++ b/src/lib/product/components/Product/ProductDesktopVariant.jsx @@ -26,7 +26,7 @@ import ProductCard from '../ProductCard'; import ProductSimilar from '../ProductSimilar'; import ProductPromoSection from '~/modules/product-promo/components/Section'; import SimilarBottom from '~/modules/product-detail/components/SimilarBottom'; - +import { gtagAddToCart } from '@/core/utils/googleTag'; const SELF_HOST = process.env.NEXT_PUBLIC_SELF_HOST; const ProductDesktopVariant = ({ @@ -41,14 +41,38 @@ const ProductDesktopVariant = ({ const { srsltid } = router.query; const [askAdminUrl, setAskAdminUrl, isApproval] = useState(); const [lowestPrice, setLowestPrice] = useState(null); - + const [qtyPickUp, setQtyPickUp] = useState(0); const [addCartAlert, setAddCartAlert] = useState(false); const [isLoadingSLA, setIsLoadingSLA] = useState(true); - + const [selectedVariant, setSelectedVariant] = useState(product.id); const { setRefreshCart } = useProductCartContext(); const [quantityInput, setQuantityInput] = useState(1); + const [activeVariant, setActiveVariant] = useState({ + id: null, + code: product.code, + name: product.name, + price: lowestPrice, + stock: product.stockTotal, + weight: product.weight, + isFlashSale: product.isFlashSale, + }); + + useEffect(() => { + if (selectedVariant) { + setActiveVariant({ + id: product.id, + code: product.code, + name: product.name, + price: product.price, + stock: product.stockTotal, + weight: product.weight, + isFlashSale: product.isFlashSale, + }); + } + }, [selectedVariant, product]); + const createdAskUrl = whatsappUrl({ template: 'product', payload: { @@ -95,6 +119,7 @@ const ProductDesktopVariant = ({ } const quantity = quantityInput; if (!validQuantity(quantity)) return; + gtagAddToCart(activeVariant, quantity); updateItemCart({ productId: product.id, quantity, @@ -232,6 +257,17 @@ const ProductDesktopVariant = ({ fetchData(); }, [product]); + useEffect(() => { + const fetchData = async () => { + const qty_available = await odooApi( + 'GET', + `/api/v1/product_variant/${product.id}/qty_available` + ); + setQtyPickUp(qty_available?.qty); + }; + fetchData(); + }, [product]); + return ( <DesktopView> <div className='container mx-auto pt-10'> @@ -451,7 +487,7 @@ const ProductDesktopVariant = ({ </Skeleton> </div> <div> - {product?.sla?.qty > 0 && ( + {qtyPickUp > 0 && ( <Link href='/panduan-pick-up-service' className='group'> <Image src='/images/PICKUP-NOW.png' @@ -462,6 +498,14 @@ const ProductDesktopVariant = ({ )} </div> </div> + {qtyPickUp > 0 && ( + <> + <div className='text-[12px] mt-1 text-red-500 italic'> + * {qtyPickUp} barang bisa di pickup + </div> + <div className='h-4' /> + </> + )} <div className='flex gap-x-3'> <Button onClick={() => handleAddToCart(product.id)} diff --git a/src/lib/product/components/Product/ProductMobile.jsx b/src/lib/product/components/Product/ProductMobile.jsx index 4cfd3755..16182271 100644 --- a/src/lib/product/components/Product/ProductMobile.jsx +++ b/src/lib/product/components/Product/ProductMobile.jsx @@ -95,7 +95,11 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => { useEffect(() => { if (!selectedVariant && variantOptions.length == 1) { - setSelectedVariant(variantOptions[0]); + const selectedVariant = + variantOptions?.find((variant) => variant.is_in_bu) || + variantOptions?.[0]; + setSelectedVariant(selectedVariant); + // setSelectedVariant(variantOptions[0]); } }, [selectedVariant, variantOptions]); diff --git a/src/lib/product/components/ProductSearch.jsx b/src/lib/product/components/ProductSearch.jsx index f7b044aa..eb86485d 100644 --- a/src/lib/product/components/ProductSearch.jsx +++ b/src/lib/product/components/ProductSearch.jsx @@ -84,10 +84,7 @@ const ProductSearch = ({ if (router.asPath.includes('penawaran')) { query = { ...query, - fq: [ - `-flashsale_id_i:${router.query.penawaran}`, - `flashsale_price_f:[1 TO *]`, - ], + fq:`flashsale_id_i:${router.query.penawaran} AND flashsale_price_f:[1 TO *]`, orderBy: 'flashsale-discount-desc', }; setFinalQuery(query); @@ -152,7 +149,7 @@ const ProductSearch = ({ }, [dataCategoriesProduct, dataLob]); useEffect(() => { - if (prefixUrl.includes('category') || prefixUrl.includes('lob')) { + if (prefixUrl.includes('category') || prefixUrl.includes('lob') || router.asPath.includes('penawaran')) { setQueryFinal({ ...finalQuery, q, limit, orderBy }); } else { setQueryFinal({ ...query, q, limit, orderBy }); @@ -549,7 +546,7 @@ const ProductSearch = ({ <div className='h-6' /> - <SideBanner /> + <SideBanner query={search} /> </div> <div className='w-9/12 pl-6'> diff --git a/src/lib/tempo/components/Tempo.jsx b/src/lib/tempo/components/Tempo.jsx index c246f3d8..19cd673b 100644 --- a/src/lib/tempo/components/Tempo.jsx +++ b/src/lib/tempo/components/Tempo.jsx @@ -153,14 +153,18 @@ const Tempo = () => { </Skeleton> </p> </div> - <div className='grid grid-flow-col gap-4'> + <div className='grid grid-flow-col gap-2'> <div className='border w-full p-4'> <p>Sisa Kredit Limit</p> <Skeleton isLoaded={!isLoading} - h='36px' + h='fit' w='full' - className='text-3xl font-semibold text-green-600' + className={`text-3xl font-semibold ${ + limitTempo - amountDue < 0 + ? 'text-red-500' + : 'text-green-600' + } `} > {limitTempo && amountDue ? currencyFormat( @@ -468,7 +472,11 @@ const Tempo = () => { isLoaded={!isLoading} // h='36px' // w={16} - className='font-semibold text-sm text-nowrap text-green-700 ' + className={`font-semibold text-sm text-nowrap ${ + limitTempo - amountDue < 0 + ? 'text-red-500' + : 'text-green-700' + }`} > {limitTempo && amountDue ? currencyFormat( diff --git a/src/lib/tracking-order/component/TrackingOrder.jsx b/src/lib/tracking-order/component/TrackingOrder.jsx index 8a7b2579..31ebd012 100644 --- a/src/lib/tracking-order/component/TrackingOrder.jsx +++ b/src/lib/tracking-order/component/TrackingOrder.jsx @@ -8,12 +8,17 @@ import { useQuery } from 'react-query'; import { Spinner } from '@chakra-ui/react'; import { Search } from 'lucide-react'; import Link from 'next/link'; +import odooApi from '~/libs/odooApi'; +import Image from '~/components/ui/image'; +import useDevice from '@/core/hooks/useDevice'; const TrackingOrder = () => { const [idAWB, setIdAWB] = useState(null); + const { isDesktop, isMobile } = useDevice(); const [inputQuery, setInputQuery] = useState(null); const [buttonClick, setButtonClick] = useState(false); const [apiError, setApiError] = useState(null); // State to store API error message + const [BannerTracking, setBannerTracking] = useState(); const closePopup = () => { setIdAWB(null); @@ -65,13 +70,35 @@ const TrackingOrder = () => { setButtonClick(true); }; + useEffect(() => { + const getBanner = async () => { + const get = await odooApi( + 'GET', + '/api/v1/banner?type=tracking-order-banner' + ); + setBannerTracking(get[0].image); + }; + getBanner(); + }, []); + return ( <div className='container mx-auto flex py-10 flex-col'> + <div className={`${isDesktop && 'min-h-96'} min-w-full`}> + {BannerTracking && ( + <Image + src={BannerTracking} + alt='Tracking Order' + width={500} + height={160} + className='w-full mt-6' + /> + )} + </div> <h1 className='text-h-sm md:text-title-sm font-semibold mb-6'> Tracking Order </h1> <div className='flex justify-start items-start'> - <p className='text-base w-full'> + <p className={`${isMobile ? 'text-sm' : 'text-base'} w-full text-pretty `}> {`Untuk melacak pesanan Anda, masukkan Nomor Transaksi di kotak bawah ini dan masukkan Email login anda lalu tekan tombol "Lacak". Nomor Transaksi ini dapat Anda lihat dalam menu `} <Link href='/my/transactions' className='text-red-500'> Daftar Transaksi @@ -93,7 +120,11 @@ const TrackingOrder = () => { onSubmit={handleSubmit(onSubmitHandler)} className='flex mt-4 flex-row w-full ' > - <div className='w-[90%] grid grid-cols-2 gap-4'> + <div + className={`w-[90%] grid ${ + isMobile ? 'grid-rows-3' : 'grid-cols-2' + } gap-4`} + > <div className='flex flex-col '> <label className='form-label mb-2'>ID Pesanan*</label> <input diff --git a/src/lib/transaction/components/Transaction.jsx b/src/lib/transaction/components/Transaction.jsx index b2fb2c17..2ca7d386 100644 --- a/src/lib/transaction/components/Transaction.jsx +++ b/src/lib/transaction/components/Transaction.jsx @@ -885,7 +885,7 @@ const Transaction = ({ id }) => { ? `| ${product?.attributes.join(', ')}` : ''} </div> - {product.soQty && product.reservedStockQty && ( + {product.soQty && ( <div className='text-[10px] text-red-500 italic mt-2'> {product.soQty !== product.reservedStockQty ? 'Barang sedang disiapkan' diff --git a/src/lib/variant/components/VariantCard.jsx b/src/lib/variant/components/VariantCard.jsx index 08b7a97e..79746995 100644 --- a/src/lib/variant/components/VariantCard.jsx +++ b/src/lib/variant/components/VariantCard.jsx @@ -7,12 +7,12 @@ import { createSlug } from '@/core/utils/slug' import currencyFormat from '@/core/utils/currencyFormat' import { updateItemCart } from '@/core/utils/cart' import whatsappUrl from '@/core/utils/whatsappUrl' -import {useState } from 'react'; +import { useEffect, useState } from 'react'; import rejectProductApi from '../../../lib/transaction/api/rejectProductApi' // import {useTransaction} from 'C:\Users\Indoteknik\next-indoteknik\src\lib\transaction\hooks\useTransaction.js' import useTransaction from '../../../lib/transaction/hooks/useTransaction'; import ImageNext from 'next/image'; - +import odooApi from '~/libs/odooApi'; const VariantCard = ({ product, openOnClick = true, buyMore = false }) => { const router = useRouter() const id = router.query.id @@ -21,7 +21,7 @@ const VariantCard = ({ product, openOnClick = true, buyMore = false }) => { const [isModalOpen, setIsModalOpen] = useState(false); const [selectedProduct, setSelectedProduct] = useState(null); const [reason, setReason] = useState(''); - + const [qtyPickUp, setQtyPickUp] = useState(0); const addItemToCart = () => { @@ -67,6 +67,17 @@ const VariantCard = ({ product, openOnClick = true, buyMore = false }) => { toast.error('Gagal reject produk. Silakan coba lagi.'); } }; + + useEffect(() => { + const fetchData = async () => { + const qty_available = await odooApi( + 'GET', + `/api/v1/product_variant/${product.id}/qty_available` + ); + setQtyPickUp(qty_available?.qty); + }; + fetchData(); + }, [product]); const Card = () => ( <div className='flex gap-x-3'> @@ -117,10 +128,18 @@ const VariantCard = ({ product, openOnClick = true, buyMore = false }) => { <p className='text-caption-2 text-gray_r-11 mt-1'> Berat Item : {product?.weight} Kg x {product?.quantity} Barang </p> - <p className='text-[10px] text-red-500 italic mt-2'> - {product.availableQuantity} barang ini bisa di pickup maksimal pukul + {product.soQty && ( + <div className='text-[10px] text-red-500 italic mt-2'> + {product.soQty !== product.reservedStockQty + ? 'Barang sedang disiapkan' + : `${product.reservedStockQty} barang bisa di + kirim/pickup`} + </div> + )} + {/* <p className='text-[10px] text-red-500 italic mt-2'> + {qtyPickUp} barang ini bisa di pickup maksimal pukul 16.00 - </p> + </p> */} <div className='flex flex-wrap gap-x-1 items-center mt-auto'> {product.hasFlashsale && ( <> diff --git a/src/pages/api/flashsale-header.js b/src/pages/api/flashsale-header.js index 578801ae..916a9cd2 100644 --- a/src/pages/api/flashsale-header.js +++ b/src/pages/api/flashsale-header.js @@ -35,7 +35,7 @@ export default async function handler(req, res) { } return res.status(200).json({ data }); } else { - const flashSale = await odooApi('GET', `/api/v1/flashsale/header`); + const flashSale = await odooApi('GET', `/api/v1/flashsale/header?is_show_program=true`); if (flashSale.length === 0) { return res.status(200).json({ data: [] }); } else { diff --git a/src/pages/api/shop/search.js b/src/pages/api/shop/search.js index 63ec7ca0..a1eecc52 100644 --- a/src/pages/api/shop/search.js +++ b/src/pages/api/shop/search.js @@ -19,6 +19,7 @@ export default async function handler(req, res) { source = '', } = req.query; + let { stock = '' } = req.query; let paramOrderBy = ''; switch (orderBy) { diff --git a/src/pages/api/webhook/biteship.js b/src/pages/api/webhook/biteship.js new file mode 100644 index 00000000..f1100d2d --- /dev/null +++ b/src/pages/api/webhook/biteship.js @@ -0,0 +1,13 @@ +import odooApi from '@/core/api/odooApi'; + + +export default async function handler(req, res) { + const body = req.body + + if(req.body){ + let result = odooApi('POST', '/api/v1/webhook/biteship', body) + } + + res.status(200).send("ok"); + +}
\ No newline at end of file diff --git a/src/pages/google_merchant/products/[page].js b/src/pages/google_merchant/products/[page].js index 8dd28a39..63d52d91 100644 --- a/src/pages/google_merchant/products/[page].js +++ b/src/pages/google_merchant/products/[page].js @@ -98,7 +98,8 @@ export async function getServerSideProps({ res, query }) { 'g:price': { '#text': `${Math.round(product.lowestPrice.price * PPN)} IDR`, }, - 'g:custom_label_3': { + // 'g:custom_label_3': { '#text': product.categories[0]?.name || 'Tidak Ada Kategori' }, + 'g:custom_label_4': { '#text': category_level_1?.name_s || 'Tidak Ada Kategori', }, }; diff --git a/src/pages/index.jsx b/src/pages/index.jsx index fce2202a..df5047a3 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -97,6 +97,20 @@ export default function Home({ categoryId }) { 'indoteknik, indoteknik.com, toko teknik, toko perkakas, jual genset, jual fogging, jual krisbow, harga krisbow, harga alat safety, harga pompa air', }, ]} + openGraph={ + { + title : 'Indoteknik.com: B2B Industrial Supply & Solution', + description : 'Temukan pilihan produk B2B Industri & Alat Teknik untuk Perusahaan, UMKM & Pemerintah dengan lengkap, mudah dan transparan.', + images: [ + { + url: 'https://indoteknik.com/icon.jpg', + width: 800, + height: 600, + alt: 'indoteknik.com', + }, + ], + } + } /> <PagePopupIformation /> diff --git a/src/pages/my/profile.jsx b/src/pages/my/profile.jsx index eaf3341c..859b6960 100644 --- a/src/pages/my/profile.jsx +++ b/src/pages/my/profile.jsx @@ -71,18 +71,19 @@ export default function Profile() { <IsAuth> <MobileView> <AppLayout title='Akun Saya'> - {!auth?.parentId && !ubahAkun && ( - <div className='text-sm p-4 flex items-center'> - <Checkbox - borderColor='gray.600' - colorScheme='red' - size='lg' - isChecked={isChecked} - onChange={handleChange} - /> - <p className='ml-2'>Ubah ke akun bisnis</p> - </div> - )} + {auth?.company || + (!ubahAkun && ( + <div className='text-sm p-4 flex items-center'> + <Checkbox + borderColor='gray.600' + colorScheme='red' + size='lg' + isChecked={isChecked} + onChange={handleChange} + /> + <p className='ml-2'>Ubah ke akun bisnis</p> + </div> + ))} {isChecked && ( <div> <SwitchAccount company_type='nonpkp' /> @@ -97,7 +98,7 @@ export default function Profile() { )} */} <PersonalProfile /> <Divider /> - {auth?.parentId && <CompanyProfile />} + {(auth?.parentId || auth?.company) && <CompanyProfile />} </AppLayout> </MobileView> @@ -108,18 +109,19 @@ export default function Profile() { <Menu /> </div> <div className='w-9/12 bg-white border border-gray_r-6 rounded'> - {!auth?.parentId && !ubahAkun && ( - <div className='text-sm p-4 flex items-center'> - <Checkbox - borderColor='gray.600' - colorScheme='red' - size='lg' - isChecked={isChecked} - onChange={handleChange} - /> - <p className='ml-2'>Ubah ke akun bisnis</p> - </div> - )} + {auth?.company || + (!ubahAkun && ( + <div className='text-sm p-4 flex items-center'> + <Checkbox + borderColor='gray.600' + colorScheme='red' + size='lg' + isChecked={isChecked} + onChange={handleChange} + /> + <p className='ml-2'>Ubah ke akun bisnis</p> + </div> + ))} {isChecked && ( <div> <SwitchAccount company_type='nonpkp' /> @@ -134,7 +136,7 @@ export default function Profile() { )} */} <PersonalProfile /> <Divider /> - {auth?.parentId && <CompanyProfile />} + {(auth?.parentId || auth?.company) && <CompanyProfile />} </div> </div> </BasicLayout> diff --git a/src/pages/my/tempo/index.jsx b/src/pages/my/tempo/index.jsx index 5fb9deba..6ca5c718 100644 --- a/src/pages/my/tempo/index.jsx +++ b/src/pages/my/tempo/index.jsx @@ -13,39 +13,41 @@ export default function MyTempo() { const router = useRouter(); const [isLoading, setIsLoading] = useState(true); useEffect(() => { + setIsLoading(true); if (!auth) { const nextUrl = encodeURIComponent(router.asPath); router.push(`/login?next=${nextUrl}`); - } - // else if ( - // (auth.tempoProgres === '' || auth.tempoProgres === 'rejected') && - // !auth.company - // ) { - // router.push('/pengajuan-tempo'); - // } - else { + } else { + if ( + !auth.partnerTempo && + (!auth.partnerTempo || auth.tempoProgres === 'review') + ) { + setIsLoading(true); + router.push('/pengajuan-tempo'); + } setIsLoading(false); } }, [auth]); - if (isLoading || !auth) { - return null; // Tidak render apa pun selama loading atau auth/tempo belum tersedia - } - return ( - <IsAuth> - <Seo title='Tempo - Indoteknik.com' /> + if (isLoading && !auth) { + return null; + } else { + return ( + <IsAuth> + <Seo title='Tempo - Indoteknik.com' /> - <MobileView> - <AppLayout title='Pembayaran Tempo'> - <InvoicesComponent /> - </AppLayout> - </MobileView> + <MobileView> + <AppLayout title='Pembayaran Tempo'> + <InvoicesComponent /> + </AppLayout> + </MobileView> - <DesktopView> - <BasicLayout> - <InvoicesComponent /> - </BasicLayout> - </DesktopView> - </IsAuth> - ); + <DesktopView> + <BasicLayout> + <InvoicesComponent /> + </BasicLayout> + </DesktopView> + </IsAuth> + ); + } } diff --git a/src/pages/pengajuan-tempo/index.jsx b/src/pages/pengajuan-tempo/index.jsx index 6987bd29..5ddd3e18 100644 --- a/src/pages/pengajuan-tempo/index.jsx +++ b/src/pages/pengajuan-tempo/index.jsx @@ -48,7 +48,7 @@ export default function TrackingOrder() { router.push(`/login?next=${nextUrl}`); } else if (auth.tempoProgres === 'approve' || auth?.partnerTempo) { router.push('/pengajuan-tempo/approve'); - } else if (!auth.parentId) { + } else if (!auth.parentId && !auth.company) { router.push('/pengajuan-tempo/switch-account'); } else if (auth.tempoProgres === 'review' && !tempo?.paymentTerm) { router.push('/pengajuan-tempo/review'); diff --git a/src/pages/sitemap/products/[page].js b/src/pages/sitemap/products/[page].js index e39755d6..3419489b 100644 --- a/src/pages/sitemap/products/[page].js +++ b/src/pages/sitemap/products/[page].js @@ -7,7 +7,10 @@ export async function getServerSideProps({ query, res }) { const baseUrl = process.env.SELF_HOST + '/shop/product/' const { page } = query const limit = 2500 - const queries = { limit, page: page.replace('.xml', '') } + const queries = { limit, page: page.replace('.xml', ''), + '-publish_b': false, + product_rating_f: '[8 TO *]', + price_tier1_v2_f: '[1 TO *]', } const products = await productSearchApi({ query: _.toQuery(queries) }) const sitemap = create('urlset', { encoding: 'utf-8' }).att( 'xmlns', diff --git a/src/utils/solrMapping.js b/src/utils/solrMapping.js index ecd62be2..33f0cbaf 100644 --- a/src/utils/solrMapping.js +++ b/src/utils/solrMapping.js @@ -43,6 +43,7 @@ export const productMappingSolr = (products, pricelist) => { let productMapped = { id: product.product_id_i || '', image: product.image_s || '', + imageCarousel: product.image_carousel_ss || '', imageMobile: product.image_mobile_s || '', code: product.default_code_s || '', description: product.description_t || '', |
