diff options
| author | Miqdad <ahmadmiqdad27@gmail.com> | 2025-05-31 21:39:53 +0700 |
|---|---|---|
| committer | Miqdad <ahmadmiqdad27@gmail.com> | 2025-05-31 21:39:53 +0700 |
| commit | 77f976d3bd09d9e00d4d55bbd40579b439405d96 (patch) | |
| tree | a8959bba3d8a51570c439789f92653409a0065ae | |
| parent | ca05a70e98e9066882de6394ffbd89db7af2cb9d (diff) | |
| parent | 2a1dea70b8f0062fe8eebeb7139a7b77a24e220b (diff) | |
Merge branch 'new-release' of https://bitbucket.org/altafixco/next-indoteknik into biteship-merge
# Conflicts:
# src/lib/checkout/components/Checkout.jsx
# src/lib/transaction/components/Transaction.jsx
41 files changed, 6652 insertions, 430 deletions
diff --git a/public/file/Surat Pernyataan Nomor Rekening.docx b/public/file/Surat Pernyataan Nomor Rekening.docx Binary files differnew file mode 100644 index 00000000..b431de24 --- /dev/null +++ b/public/file/Surat Pernyataan Nomor Rekening.docx diff --git a/public/images/ICON_TEMPO.png b/public/images/ICON_TEMPO.png Binary files differnew file mode 100644 index 00000000..dee76189 --- /dev/null +++ b/public/images/ICON_TEMPO.png diff --git a/src-migrate/modules/cart/components/ItemAction.tsx b/src-migrate/modules/cart/components/ItemAction.tsx index 7220e362..4dcebd9e 100644 --- a/src-migrate/modules/cart/components/ItemAction.tsx +++ b/src-migrate/modules/cart/components/ItemAction.tsx @@ -1,74 +1,214 @@ -import style from '../styles/item-action.module.css' +import style from '../styles/item-action.module.css'; -import React, { useEffect, useState } from 'react' +import React, { useEffect, useState } from 'react'; -import { Spinner, Tooltip } from '@chakra-ui/react' -import { MinusIcon, PlusIcon, Trash2Icon } from 'lucide-react' +import { Spinner, Tooltip } from '@chakra-ui/react'; +import { MinusIcon, PlusIcon, Trash2Icon } from 'lucide-react'; -import { CartItem } from '~/types/cart' -import { getAuth } from '~/libs/auth' -import { deleteUserCart, upsertUserCart } from '~/services/cart' +import { CartItem } from '~/types/cart'; +import { getAuth } from '~/libs/auth'; +import { deleteUserCart, upsertUserCart } from '~/services/cart'; -import { useDebounce } from 'usehooks-ts' -import { useCartStore } from '../stores/useCartStore' -import { useProductCartContext } from '@/contexts/ProductCartContext' +import { useDebounce } from 'usehooks-ts'; +import { useCartStore } from '../stores/useCartStore'; +import { useProductCartContext } from '@/contexts/ProductCartContext'; +import { + removeSelectedItemsFromCookie, + removeCartItemsFromCookie, + quantityUpdateState, + getCartDataFromCookie, + setCartDataToCookie, +} from '~/utils/cart'; + +import { toast } from 'react-hot-toast'; type Props = { - item: CartItem -} + item: CartItem; +}; const CartItemAction = ({ item }: Props) => { - const auth = getAuth() - const { setRefreshCart } = useProductCartContext() - const [isLoadDelete, setIsLoadDelete] = useState<boolean>(false) - const [isLoadQuantity, setIsLoadQuantity] = useState<boolean>(false) + const auth = getAuth(); + const { setRefreshCart } = useProductCartContext(); + const [isLoadDelete, setIsLoadDelete] = useState<boolean>(false); + const [isLoadQuantity, setIsLoadQuantity] = useState<boolean>(false); - const [quantity, setQuantity] = useState<number>(item.quantity) + const [quantity, setQuantity] = useState<number>(item.quantity); - const { loadCart } = useCartStore() + const { loadCart, cart, updateCartItem } = useCartStore(); - const limitQty = item.limit_qty?.transaction || 0 + const limitQty = item.limit_qty?.transaction || 0; const handleDelete = async () => { - if (typeof auth !== 'object') return - - setIsLoadDelete(true) - await deleteUserCart(auth.id, [item.cart_id]) - await loadCart(auth.id) - setIsLoadDelete(false) - setRefreshCart(true) - } - - const decreaseQty = () => { setQuantity((quantity) => quantity -= 1) } - const increaseQty = () => { setQuantity((quantity) => quantity += 1) } - const debounceQty = useDebounce(quantity, 1000) + if (typeof auth !== 'object') return; + + setIsLoadDelete(true); + + try { + // Delete from server + await deleteUserCart(auth.id, [item.cart_id]); + + // Clean up cookies immediately + removeSelectedItemsFromCookie([item.id]); + removeCartItemsFromCookie([item.cart_id]); + + // Update local cart state optimistically + if (cart) { + const updatedProducts = cart.products.filter( + (product) => product.id !== item.id + ); + const updatedCart = { + ...cart, + products: updatedProducts, + product_total: updatedProducts.length, + }; + updateCartItem(updatedCart); + } + + // Reload from server and refresh context + await loadCart(auth.id); + setRefreshCart(true); + + toast.success('Item berhasil dihapus'); + } catch (error) { + console.error('Failed to delete cart item:', error); + toast.error('Gagal menghapus item'); + + // Reload on error + await loadCart(auth.id); + } finally { + setIsLoadDelete(false); + } + }; + + const updateQuantityInCookie = (productId, cartId, newQuantity) => { + try { + const cartData = getCartDataFromCookie(); + let itemFound = false; + + // Find item by cart_id key or search within objects + if (cartData[cartId]) { + cartData[cartId].quantity = newQuantity; + itemFound = true; + } else { + // Search by product id or cart_id within objects + for (const key in cartData) { + const item = cartData[key]; + if (item.id === productId || item.cart_id === cartId) { + item.quantity = newQuantity; + itemFound = true; + break; + } + } + } + + if (itemFound) { + setCartDataToCookie(cartData); + return true; + } + + return false; + } catch (error) { + console.error('Error updating quantity in cookie:', error); + return false; + } + }; + + const decreaseQty = () => { + setQuantity((quantity) => (quantity -= 1)); + }; + + const increaseQty = () => { + setQuantity((quantity) => (quantity += 1)); + }; + + const debounceQty = useDebounce(quantity, 1000); + useEffect(() => { - if (isNaN(debounceQty)) setQuantity(1) - if (limitQty > 0 && debounceQty > limitQty) setQuantity(limitQty) - }, [debounceQty, limitQty]) + if (isNaN(debounceQty)) setQuantity(1); + if (limitQty > 0 && debounceQty > limitQty) setQuantity(limitQty); + }, [debounceQty, limitQty]); useEffect(() => { const updateCart = async () => { - if (typeof auth !== 'object' || isNaN(debounceQty)) return - - setIsLoadQuantity(true) - await upsertUserCart({ - userId: auth.id, - type: item.cart_type, - id: item.id, - qty: debounceQty, - selected: item.selected, - }) - await loadCart(auth.id) - setIsLoadQuantity(false) - } - updateCart() + if (typeof auth !== 'object' || isNaN(debounceQty)) return; + if (debounceQty === item.quantity) return; + + quantityUpdateState.startUpdate(item.id); + setIsLoadQuantity(true); + + try { + // Update cookie immediately for responsive UI + updateQuantityInCookie(item.id, item.cart_id, debounceQty); + + // Update local cart state optimistically + if (cart) { + const updatedProducts = cart.products.map((product) => + product.id === item.id + ? { ...product, quantity: debounceQty } + : product + ); + const updatedCart = { + ...cart, + products: updatedProducts, + }; + updateCartItem(updatedCart); + } + + // Send update to server + await upsertUserCart({ + userId: auth.id, + type: item.cart_type, + id: item.id, + qty: debounceQty, + selected: item.selected, + }); + + // Reload from server to ensure consistency + await loadCart(auth.id); + + // Re-update cookie if server reload overwrote it + const currentCookieData = getCartDataFromCookie(); + let needsReUpdate = false; + + for (const key in currentCookieData) { + const cookieItem = currentCookieData[key]; + if ( + (cookieItem.id === item.id || + cookieItem.cart_id === item.cart_id) && + cookieItem.quantity !== debounceQty + ) { + needsReUpdate = true; + break; + } + } + + if (needsReUpdate) { + updateQuantityInCookie(item.id, item.cart_id, debounceQty); + } + } catch (error) { + console.error('Error updating quantity:', error); + toast.error('Gagal mengupdate quantity'); + + // Revert changes on error + updateQuantityInCookie(item.id, item.cart_id, item.quantity); + loadCart(auth.id); + } finally { + setIsLoadQuantity(false); + quantityUpdateState.endUpdate(item.id); + } + }; + + updateCart(); //eslint-disable-next-line react-hooks/exhaustive-deps - }, [debounceQty]) + }, [debounceQty]); return ( <div className={style.actionSection}> - <button className={style.deleteButton} onClick={handleDelete} disabled={isLoadDelete}> + <button + className={style.deleteButton} + onClick={handleDelete} + disabled={isLoadDelete} + > {isLoadDelete && <Spinner size='xs' />} {!isLoadDelete && <Trash2Icon size={16} />} </button> @@ -106,7 +246,7 @@ const CartItemAction = ({ item }: Props) => { </Tooltip> </div> </div> - ) -} + ); +}; -export default CartItemAction
\ No newline at end of file +export default CartItemAction;
\ No newline at end of file diff --git a/src-migrate/modules/cart/components/ItemSelect.tsx b/src-migrate/modules/cart/components/ItemSelect.tsx index d4a1b537..72ab49aa 100644 --- a/src-migrate/modules/cart/components/ItemSelect.tsx +++ b/src-migrate/modules/cart/components/ItemSelect.tsx @@ -1,56 +1,140 @@ -import { Checkbox, Spinner } from '@chakra-ui/react' -import React, { useState } from 'react' - -import { getAuth } from '~/libs/auth' -import { CartItem } from '~/types/cart' -import { upsertUserCart } from '~/services/cart' - -import { useCartStore } from '../stores/useCartStore' +import { Checkbox } from '@chakra-ui/react'; +import React, { useState, useCallback, useEffect } from 'react'; +import { getAuth } from '~/libs/auth'; +import { CartItem } from '~/types/cart'; +import { upsertUserCart } from '~/services/cart'; +import { useCartStore } from '../stores/useCartStore'; +import { toast } from 'react-hot-toast'; +import { + getSelectedItemsFromCookie, + updateSelectedItemInCookie, + checkboxUpdateState, +} from '~/utils/cart'; type Props = { - item: CartItem -} + item: CartItem; +}; const CartItemSelect = ({ item }: Props) => { - const auth = getAuth() - const { updateCartItem, cart } = useCartStore() + const auth = getAuth(); + const { updateCartItem, cart, loadCart } = useCartStore(); + const [isUpdating, setIsUpdating] = useState<boolean>(false); + const [localSelected, setLocalSelected] = useState<boolean>(item.selected); + + // Subscribe to global checkbox update state + useEffect(() => { + const handleUpdateStateChange = (isUpdating) => { + // This component doesn't need to react to global state changes + // Individual checkboxes are managed independently + }; + + checkboxUpdateState.addListener(handleUpdateStateChange); + return () => checkboxUpdateState.removeListener(handleUpdateStateChange); + }, []); + + // Sync local state with cookie and server data + useEffect(() => { + if (isUpdating) return; + + const selectedItems = getSelectedItemsFromCookie(); + const storedState = selectedItems[item.id]; + + if (storedState !== undefined) { + // Update local state if cookie differs + if (localSelected !== storedState) { + setLocalSelected(storedState); + } + + // Sync cart state with cookie if needed + if (storedState !== item.selected && cart) { + const updatedCartItems = cart.products.map((cartItem) => + cartItem.id === item.id + ? { ...cartItem, selected: storedState } + : cartItem + ); + updateCartItem({ ...cart, products: updatedCartItems }); + } + } else { + // Initialize cookie with server state + setLocalSelected(item.selected); + updateSelectedItemInCookie(item.id, item.selected, false); + } + }, [item.id, item.selected, localSelected, cart, updateCartItem, isUpdating]); + + const handleChange = useCallback( + async (e: React.ChangeEvent<HTMLInputElement>) => { + if (typeof auth !== 'object' || !cart || isUpdating) return; + + const newSelectedState = e.target.checked; + + // Update local state immediately + setLocalSelected(newSelectedState); + setIsUpdating(true); + checkboxUpdateState.startUpdate(item.id); + + try { + // Update cookie immediately for responsive UI + updateSelectedItemInCookie(item.id, newSelectedState, false); + + // Update cart state optimistically + const updatedCartItems = cart.products.map((cartItem) => + cartItem.id === item.id + ? { ...cartItem, selected: newSelectedState } + : cartItem + ); + updateCartItem({ ...cart, products: updatedCartItems }); - const [isLoad, setIsLoad] = useState<boolean>(false) + // Send update to server + await upsertUserCart({ + userId: auth.id, + type: item.cart_type, + id: item.id, + qty: item.quantity, + selected: newSelectedState, + // purchase_tax_id: item.purchase_tax_id, + // vendor_id: item.vendor_id + }); - const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => { - if (typeof auth !== 'object' || !cart) return - - setIsLoad(true); - const updatedCartItems = cart.products.map(cartItem => - cartItem.id === item.id - ? { ...cartItem, selected: e.target.checked } - : cartItem - ); + // Reload cart for consistency + await loadCart(auth.id); + } catch (error) { + console.error('Failed to update item selection:', error); + toast.error('Gagal memperbarui pilihan barang'); - // Update the entire cart - const updatedCart = { ...cart, products: updatedCartItems }; - updateCartItem(updatedCart); + // Revert changes on error + setLocalSelected(!newSelectedState); + updateSelectedItemInCookie(item.id, !newSelectedState, false); + loadCart(auth.id); + } finally { + setIsUpdating(false); + checkboxUpdateState.endUpdate(item.id); + } + }, + [auth, cart, item, isUpdating, updateCartItem, loadCart] + ); - setIsLoad(false); - } + const isDisabled = + isUpdating || checkboxUpdateState.isCheckboxUpdating(item.id); return ( - <div className='w-6 my-auto'> - {isLoad && ( - <Spinner className='my-auto' size='sm' /> - )} - - {!isLoad && ( - <Checkbox - borderColor='gray.600' - colorScheme='red' - size='lg' - isChecked={item.selected} - onChange={handleChange} - /> - )} + <div className='w-6 my-auto relative'> + <Checkbox + borderColor='gray.600' + colorScheme='red' + size='lg' + isChecked={localSelected} + onChange={handleChange} + isDisabled={isDisabled} + opacity={isDisabled ? 0.5 : 1} + cursor={isDisabled ? 'not-allowed' : 'pointer'} + _disabled={{ + opacity: 0.5, + cursor: 'not-allowed', + backgroundColor: 'gray.100', + }} + /> </div> - ) -} + ); +}; -export default CartItemSelect
\ No newline at end of file +export default CartItemSelect; diff --git a/src-migrate/modules/cart/components/Summary.tsx b/src-migrate/modules/cart/components/Summary.tsx index 0af5ab18..68db6323 100644 --- a/src-migrate/modules/cart/components/Summary.tsx +++ b/src-migrate/modules/cart/components/Summary.tsx @@ -1,20 +1,19 @@ -import style from '../styles/summary.module.css' - -import React from 'react' -import formatCurrency from '~/libs/formatCurrency' -import clsxm from '~/libs/clsxm' -import { Skeleton } from '@chakra-ui/react' -import _ from 'lodash' +import style from '../styles/summary.module.css'; +import React, { useEffect, useState, useMemo } from 'react'; +import formatCurrency from '~/libs/formatCurrency'; +import clsxm from '~/libs/clsxm'; +import { Skeleton, Box, useColorModeValue, Text } from '@chakra-ui/react'; type Props = { - total?: number - discount?: number - subtotal?: number - tax?: number - shipping?: number - grandTotal?: number - isLoaded: boolean -} + total?: number; + discount?: number; + subtotal?: number; + tax?: number; + shipping?: number; + grandTotal?: number; + isLoaded: boolean; + products?: any[]; // Added to detect changes in selected products +}; const CartSummary = ({ total, @@ -24,53 +23,191 @@ const CartSummary = ({ shipping, grandTotal, isLoaded = false, + products = [], }: Props) => { - const PPN : number = process.env.NEXT_PUBLIC_PPN ? parseFloat(process.env.NEXT_PUBLIC_PPN) : 0; - return ( - <> - <div className='text-h-sm font-medium'>Ringkasan Pesanan</div> + const PPN: number = process.env.NEXT_PUBLIC_PPN + ? parseFloat(process.env.NEXT_PUBLIC_PPN) + : 0; + const [isMounted, setIsMounted] = useState(false); + + // Local state to store calculated values + const [summaryValues, setSummaryValues] = useState({ + subtotal: 0, + discount: 0, + total: 0, + tax: 0, + shipping: 0, + grandTotal: 0, + }); + + // This fixes hydration issues by ensuring the component only renders fully after mounting + useEffect(() => { + setIsMounted(true); + }, []); + + // Calculate summary based on products whenever products change + useMemo(() => { + if (!products || products.length === 0) return; + + // Only count selected products + const selectedProducts = products.filter((product) => product.selected); + + // Calculate values based on selected products + let calculatedSubtotal = 0; + let calculatedDiscount = 0; + + selectedProducts.forEach((product) => { + // Get raw price and discount from product + const productBasePrice = product.price?.price || 0; + const productQty = product.quantity || 1; + const productDiscountedPrice = + product.price?.price_discount || productBasePrice; + const productDiscount = productBasePrice - productDiscountedPrice; + + calculatedSubtotal += productBasePrice * productQty; + calculatedDiscount += productDiscount * productQty; + }); + + const calculatedTotal = calculatedSubtotal - calculatedDiscount; + const calculatedTax = calculatedTotal * (PPN - 1); + const calculatedShipping = shipping || 0; + const calculatedGrandTotal = + calculatedTotal + calculatedTax + calculatedShipping; + + // If calculated values are different from props, use calculated ones + const shouldUpdateValues = + Math.abs((subtotal || 0) - calculatedSubtotal) > 0.01 || + Math.abs((discount || 0) - calculatedDiscount) > 0.01 || + Math.abs((total || 0) - calculatedTotal) > 0.01 || + Math.abs((tax || 0) - calculatedTax) > 0.01 || + Math.abs((grandTotal || 0) - calculatedGrandTotal) > 0.01; - <div className="h-6" /> + if (shouldUpdateValues && isLoaded) { + setSummaryValues({ + subtotal: calculatedSubtotal, + discount: calculatedDiscount, + total: calculatedTotal, + tax: calculatedTax, + shipping: calculatedShipping, + grandTotal: calculatedGrandTotal, + }); + } else if (isLoaded) { + // Use values from props when available + setSummaryValues({ + subtotal: subtotal || 0, + discount: discount || 0, + total: total || 0, + tax: tax || 0, + shipping: shipping || 0, + grandTotal: grandTotal || 0, + }); + } + }, [ + products, + isLoaded, + subtotal, + discount, + total, + tax, + shipping, + grandTotal, + PPN, + ]); + + // Update local values whenever props change + useEffect(() => { + if (isLoaded) { + setSummaryValues({ + subtotal: subtotal || 0, + discount: discount || 0, + total: total || 0, + tax: tax || 0, + shipping: shipping || 0, + grandTotal: grandTotal || 0, + }); + } + }, [isLoaded, subtotal, discount, total, tax, shipping, grandTotal]); + + if (!isMounted) { + return ( + <Box p={4} borderWidth='1px' borderRadius='lg' boxShadow='sm'> + <Text fontSize='lg' fontWeight='medium' mb={4}> + Ringkasan Pesanan + </Text> + {Array(6) + .fill(0) + .map((_, index) => ( + <Skeleton key={index} height='24px' my={2} /> + ))} + </Box> + ); + } + + // Use local state for rendering to ensure responsiveness + const { + subtotal: displaySubtotal, + discount: displayDiscount, + total: displayTotal, + tax: displayTax, + shipping: displayShipping, + grandTotal: displayGrandTotal, + } = summaryValues; + + return ( + <div className={style.summaryContainer}> + <Text fontSize='lg' fontWeight='medium' mb={4}> + Ringkasan Pesanan + </Text> <div className='flex flex-col gap-y-3'> <Skeleton isLoaded={isLoaded} className={style.line}> <span className={style.label}>Total Belanja</span> - <span className={style.value}>Rp {formatCurrency(subtotal || 0)}</span> + <span className={style.value}> + Rp {formatCurrency(displaySubtotal)} + </span> </Skeleton> <Skeleton isLoaded={isLoaded} className={style.line}> <span className={style.label}>Total Diskon</span> - <span className={clsxm(style.value, style.discount)}>- Rp {formatCurrency(discount || 0)}</span> + <span className={clsxm(style.value, style.discount)}> + - Rp {formatCurrency(displayDiscount)} + </span> </Skeleton> <div className={style.divider} /> <Skeleton isLoaded={isLoaded} className={style.line}> <span className={style.label}>Subtotal</span> - <span className={style.value}>Rp {formatCurrency(total || 0)}</span> + <span className={style.value}>Rp {formatCurrency(displayTotal)}</span> </Skeleton> <Skeleton isLoaded={isLoaded} className={style.line}> - <span className={style.label}>Tax {((PPN - 1) * 100).toFixed(0)}%</span> - <span className={style.value}>Rp {formatCurrency(tax || 0)}</span> + <span className={style.label}> + Tax {((PPN - 1) * 100).toFixed(0)}% + </span> + <span className={style.value}>Rp {formatCurrency(displayTax)}</span> </Skeleton> <Skeleton isLoaded={isLoaded} className={style.line}> <span className={style.label}>Biaya Kirim</span> - <span className={style.value}>Rp {formatCurrency(shipping || 0)}</span> + <span className={style.value}> + Rp {formatCurrency(displayShipping)} + </span> </Skeleton> <div className={style.divider} /> - <Skeleton isLoaded={isLoaded} className={style.line}> + <Skeleton isLoaded={isLoaded}> <span className={clsxm(style.label, style.grandTotal)}> Grand Total </span> - <span className={style.value}>Rp {formatCurrency(grandTotal || 0)}</span> + <span className={clsxm(style.value, style.grandTotalValue)}> + Rp {formatCurrency(displayGrandTotal)} + </span> </Skeleton> </div> - </> - ) -} + </div> + ); +}; -export default CartSummary
\ No newline at end of file +export default CartSummary; diff --git a/src-migrate/modules/cart/stores/useCartStore.ts b/src-migrate/modules/cart/stores/useCartStore.ts index e7d2cdd3..dc47b011 100644 --- a/src-migrate/modules/cart/stores/useCartStore.ts +++ b/src-migrate/modules/cart/stores/useCartStore.ts @@ -1,6 +1,12 @@ import { create } from 'zustand'; import { CartItem, CartProps } from '~/types/cart'; import { getUserCart } from '~/services/cart'; +import { + syncCartWithCookie, + getCartDataFromCookie, + getSelectedItemsFromCookie, + forceResetAllSelectedItems, +} from '~/utils/cart'; type State = { cart: CartProps | null; @@ -17,6 +23,8 @@ type State = { type Action = { loadCart: (userId: number) => Promise<void>; updateCartItem: (updateCart: CartProps) => void; + forceResetSelection: () => void; + clearCart: () => void; }; export const useCartStore = create<State & Action>((set, get) => ({ @@ -29,50 +37,153 @@ export const useCartStore = create<State & Action>((set, get) => ({ tax: 0, grandTotal: 0, }, + loadCart: async (userId) => { - if (get().isLoadCart === true) return; + if (get().isLoadCart) return; set({ isLoadCart: true }); - const cart: CartProps = (await getUserCart(userId)) as CartProps; - set({ cart }); - set({ isLoadCart: false }); - const summary = computeSummary(cart); - set({ summary }); + try { + const cart: CartProps = (await getUserCart(userId)) as CartProps; + + // Sync with cookie data + const syncResult = syncCartWithCookie(cart); + + if (syncResult?.needsUpdate && cart.products) { + const selectedItems = getSelectedItemsFromCookie(); + + const updatedCart = { + ...cart, + products: cart.products.map((item) => ({ + ...item, + selected: + selectedItems[item.id] !== undefined + ? selectedItems[item.id] + : item.selected, + })), + }; + + set({ cart: updatedCart }); + } else { + set({ cart }); + } + + // Update summary + const summary = computeSummary(get().cart!); + set({ summary }); + } catch (error) { + console.error('Failed to load cart:', error); + + // Fallback to cookie data + await handleFallbackFromCookie(); + } finally { + set({ isLoadCart: false }); + } }, + updateCartItem: (updatedCart) => { - const cart = get().cart; + set({ cart: updatedCart }); + syncCartWithCookie(updatedCart); + + const summary = computeSummary(updatedCart); + set({ summary }); + }, + + forceResetSelection: () => { + const { cart } = get(); if (!cart) return; + forceResetAllSelectedItems(); + + const updatedCart = { + ...cart, + products: cart.products.map((item) => ({ ...item, selected: false })), + }; + set({ cart: updatedCart }); + const summary = computeSummary(updatedCart); set({ summary }); }, + clearCart: () => { + set({ + cart: null, + summary: { + subtotal: 0, + discount: 0, + total: 0, + tax: 0, + grandTotal: 0, + }, + }); + }, })); +// Helper function for cookie fallback +const handleFallbackFromCookie = async () => { + try { + const cartData = getCartDataFromCookie(); + + if (Object.keys(cartData).length === 0) return; + + const products = Object.values(cartData).map(transformCookieItemToProduct); + + const fallbackCart: CartProps = { + product_total: products.length, + products, + }; + + useCartStore.setState({ cart: fallbackCart }); + + const summary = computeSummary(fallbackCart); + useCartStore.setState({ summary }); + } catch (error) { + console.error('Cookie fallback failed:', error); + } +}; + +// Helper function to transform cookie item to product format +const transformCookieItemToProduct = (item: any): CartItem => ({ + cart_id: item.cart_id, + id: item.id, + cart_type: item.cart_type, + product_id: item.product?.id, + product_name: item.product?.name, + program_line_id: item.program_line?.id, + program_line_name: item.program_line?.name, + quantity: item.quantity, + selected: item.selected, + price: item.price, + package_price: item.package_price, + source: item.source, +}); + +// Helper function to compute cart summary const computeSummary = (cart: CartProps) => { + if (!cart?.products) { + return { subtotal: 0, discount: 0, total: 0, grandTotal: 0, tax: 0 }; + } + + const PPN = parseFloat(process.env.NEXT_PUBLIC_PPN || '0'); let subtotal = 0; let discount = 0; - const PPN: number = process.env.NEXT_PUBLIC_PPN ? parseFloat(process.env.NEXT_PUBLIC_PPN) : 0; - - for (const item of cart?.products) { + for (const item of cart.products) { if (!item.selected) continue; - let price = 0; - if (item.cart_type === 'promotion') - price = (item?.package_price || 0) * item.quantity; - else if (item.cart_type === 'product') - price = item.price.price * item.quantity; + const price = + item.cart_type === 'promotion' + ? (item?.package_price || 0) * item.quantity + : item.price.price * item.quantity; subtotal += price; discount += price - item.price.price_discount * item.quantity; } - let total = subtotal - discount; - let grandTotal = total * PPN; - let tax = grandTotal - total; - // let grandTotal = total + tax; - return { subtotal, discount, total, grandTotal, tax }; + const total = subtotal - discount; + const grandTotal = total * (1 + PPN); + const tax = grandTotal - total; + + return { subtotal, discount, total, grandTotal, tax }; };
\ No newline at end of file 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/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx index 1581f33d..192e1dc3 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'; @@ -74,6 +74,16 @@ const ProductDetail = ({ product }: Props) => { // 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'> @@ -83,7 +93,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'> 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/pages/shop/cart/index.tsx b/src-migrate/pages/shop/cart/index.tsx index 24baa933..03854d79 100644 --- a/src-migrate/pages/shop/cart/index.tsx +++ b/src-migrate/pages/shop/cart/index.tsx @@ -1,6 +1,6 @@ import style from './cart.module.css'; -import React, { useEffect, useMemo, useRef, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import Link from 'next/link'; import { Button, Checkbox, Spinner, Tooltip } from '@chakra-ui/react'; import { toast } from 'react-hot-toast'; @@ -14,127 +14,137 @@ import clsxm from '~/libs/clsxm'; import useDevice from '@/core/hooks/useDevice'; import CartSummaryMobile from '~/modules/cart/components/CartSummaryMobile'; import Image from '~/components/ui/image'; -import { CartItem } from '~/types/cart'; import { deleteUserCart, upsertUserCart } from '~/services/cart'; import { Trash2Icon } from 'lucide-react'; import { useProductCartContext } from '@/contexts/ProductCartContext'; +import { + getSelectedItemsFromCookie, + syncSelectedItemsWithCookie, + setAllSelectedInCookie, + removeSelectedItemsFromCookie, + removeCartItemsFromCookie, + checkboxUpdateState, + quantityUpdateState, +} from '~/utils/cart'; + +const SELECT_ALL_ID = 'select_all_checkbox'; const CartPage = () => { const router = useRouter(); const auth = getAuth(); const [isStepApproval, setIsStepApproval] = useState(false); - const [isSelectedAll, setIsSelectedAll] = useState(false); - const [isButtonChek, setIsButtonChek] = useState(false); - const [buttonSelectNow, setButtonSelectNow] = useState(true); - const [isLoad, setIsLoad] = useState<boolean>(false); const [isLoadDelete, setIsLoadDelete] = useState<boolean>(false); const { loadCart, cart, summary, updateCartItem } = useCartStore(); - const useDivvice = useDevice(); + const device = useDevice(); const { setRefreshCart } = useProductCartContext(); const [isTop, setIsTop] = useState(true); - const [hasChanged, setHasChanged] = useState(false); - const prevCartRef = useRef<CartItem[] | null>(null); + const [isUpdating, setIsUpdating] = useState(false); + const [isAnyCheckboxUpdating, setIsAnyCheckboxUpdating] = useState(false); + const [isAnyQuantityUpdating, setIsAnyQuantityUpdating] = useState(false); + // Subscribe to update state changes useEffect(() => { - const handleScroll = () => { - setIsTop(window.scrollY < 200); - }; + const handleCheckboxUpdate = (isUpdating) => + setIsAnyCheckboxUpdating(isUpdating); + const handleQuantityUpdate = (isUpdating) => + setIsAnyQuantityUpdating(isUpdating); + + checkboxUpdateState.addListener(handleCheckboxUpdate); + quantityUpdateState.addListener(handleQuantityUpdate); - window.addEventListener('scroll', handleScroll); return () => { - window.removeEventListener('scroll', handleScroll); + checkboxUpdateState.removeListener(handleCheckboxUpdate); + quantityUpdateState.removeListener(handleQuantityUpdate); }; }, []); + // Handle scroll for sticky header styling useEffect(() => { - if (typeof auth === 'object' && !cart) { - loadCart(auth.id); - setIsStepApproval(auth?.feature?.soApproval); - } - }, [auth, loadCart, cart, isButtonChek]); + const handleScroll = () => setIsTop(window.scrollY < 200); - useEffect(() => { - if (typeof auth === 'object' && !cart) { - loadCart(auth.id); - setIsStepApproval(auth?.feature?.soApproval); - } - }, [auth, loadCart, cart, isButtonChek]); + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + // Initialize cart and sync with cookies useEffect(() => { - const hasSelectedChanged = () => { - if (prevCartRef.current && cart) { - const prevCart = prevCartRef.current; - return cart.products.some( - (item, index) => - prevCart[index] && prevCart[index].selected !== item.selected - ); + const initializeCart = async () => { + if (typeof auth === 'object' && !cart) { + await loadCart(auth.id); + setIsStepApproval(auth?.feature?.soApproval); + + if (cart?.products) { + const { items, needsUpdate } = syncSelectedItemsWithCookie( + cart.products + ); + + if (needsUpdate) { + const updatedCart = { + ...cart, + products: cart.products.map((item) => ({ + ...item, + selected: + items[item.id] !== undefined ? items[item.id] : item.selected, + })), + }; + updateCartItem(updatedCart); + } + } } - return false; }; - if (hasSelectedChanged()) { - setHasChanged(true); - // Perform necessary actions here if selection has changed - } else { - setHasChanged(false); - } - - prevCartRef.current = cart ? [...cart.products] : null; - }, [cart]); + initializeCart(); + }, [auth, cart, loadCart, updateCartItem]); + // Computed values const hasSelectedPromo = useMemo(() => { - if (!cart) return false; - return cart?.products?.some( - (item) => item.cart_type === 'promotion' && item.selected + return ( + cart?.products?.some( + (item) => item.cart_type === 'promotion' && item.selected + ) || false ); }, [cart]); const hasSelected = useMemo(() => { - if (!cart) return false; - return cart?.products?.some((item) => item.selected); + return cart?.products?.some((item) => item.selected) || false; }, [cart]); const hasSelectNoPrice = useMemo(() => { - if (!cart) return false; - return cart?.products?.some( - (item) => item.selected && item.price.price_discount === 0 + return ( + cart?.products?.some( + (item) => item.selected && item.price.price_discount === 0 + ) || false ); }, [cart]); const hasSelectedAll = useMemo(() => { - if (!cart || !Array.isArray(cart.products)) return false; + if (!cart?.products?.length) return false; return cart.products.every((item) => item.selected); }, [cart]); - useEffect(() => { - const updateCartItems = async () => { - if (typeof auth === 'object' && cart) { - const upsertPromises = cart.products.map((item) => - upsertUserCart({ - userId: auth.id, - type: item.cart_type, - id: item.id, - qty: item.quantity, - selected: item.selected, - }) - ); - try { - await Promise.all(upsertPromises); - await loadCart(auth.id); - } catch (error) { - console.error('Failed to update cart items:', error); - } - } - }; - - updateCartItems(); - }, [hasChanged]); + // Button states + const areButtonsDisabled = + isUpdating || + isLoadDelete || + isAnyCheckboxUpdating || + isAnyQuantityUpdating; + const isSelectAllDisabled = + isUpdating || checkboxUpdateState.isCheckboxUpdating(SELECT_ALL_ID); + // Handlers const handleCheckout = () => { + if (areButtonsDisabled) { + toast.error('Harap tunggu pembaruan selesai'); + return; + } router.push('/shop/checkout'); }; const handleQuotation = () => { + if (areButtonsDisabled) { + toast.error('Harap tunggu pembaruan selesai'); + return; + } if (hasSelectedPromo || !hasSelected) { toast.error('Maaf, Barang promo tidak dapat dibuat quotation'); } else { @@ -142,22 +152,63 @@ const CartPage = () => { } }; - const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => { - if (cart) { + const handleSelectAll = async (e: React.ChangeEvent<HTMLInputElement>) => { + if (!cart || isUpdating || typeof auth !== 'object') return; + + const newSelectedState = !hasSelectedAll; + setIsUpdating(true); + checkboxUpdateState.startUpdate(SELECT_ALL_ID); + + try { + // Update UI immediately const updatedCart = { ...cart, products: cart.products.map((item) => ({ ...item, - selected: !hasSelectedAll, + selected: newSelectedState, })), }; - updateCartItem(updatedCart); - if (hasSelectedAll) { - setIsSelectedAll(false); - } else { - setIsSelectedAll(true); - } + + // Update cookies + const productIds = cart.products.map((item) => item.id); + setAllSelectedInCookie(productIds, newSelectedState, false); + + // Update server + const updatePromises = cart.products.map((item) => + upsertUserCart({ + userId: auth.id, + type: item.cart_type, + id: item.id, + qty: item.quantity, + selected: newSelectedState, + purchase_tax_id: item.purchase_tax_id || null, + }) + ); + + await Promise.all(updatePromises); + await loadCart(auth.id); + } catch (error) { + console.error('Error updating select all:', error); + toast.error('Gagal memperbarui pilihan'); + + // Revert on error + const revertedCart = { + ...cart, + products: cart.products.map((item) => ({ + ...item, + selected: !newSelectedState, + })), + }; + updateCartItem(revertedCart); + setAllSelectedInCookie( + cart.products.map((item) => item.id), + !newSelectedState, + false + ); + } finally { + setIsUpdating(false); + checkboxUpdateState.endUpdate(SELECT_ALL_ID); } }; @@ -165,53 +216,124 @@ const CartPage = () => { if (typeof auth !== 'object' || !cart) return; setIsLoadDelete(true); - for (const item of cart.products) { - if (item.selected === true) { + checkboxUpdateState.startUpdate('delete_operation'); + + try { + const itemsToDelete = cart.products.filter((item) => item.selected); + const itemIdsToDelete = itemsToDelete.map((item) => item.id); + const cartIdsToDelete = itemsToDelete.map((item) => item.cart_id); + + // Delete from server + for (const item of itemsToDelete) { await deleteUserCart(auth.id, [item.cart_id]); - await loadCart(auth.id); } + + // Update local state optimistically + const updatedProducts = cart.products.filter((item) => !item.selected); + const updatedCart = { + ...cart, + products: updatedProducts, + product_total: updatedProducts.length, + }; + updateCartItem(updatedCart); + + // Clean up cookies + removeSelectedItemsFromCookie(itemIdsToDelete); + removeCartItemsFromCookie(cartIdsToDelete); + + // Reload from server + loadCart(auth.id).catch((error) => + console.error('Error reloading cart:', error) + ); + + setRefreshCart(true); + toast.success('Item berhasil dihapus'); + } catch (error) { + console.error('Failed to delete cart items:', error); + toast.error('Gagal menghapus item'); + loadCart(auth.id); + } finally { + setIsLoadDelete(false); + checkboxUpdateState.endUpdate('delete_operation'); } - setIsLoadDelete(false); - setRefreshCart(true); + }; + + // Tooltip messages + const getTooltipMessage = () => { + if (isAnyQuantityUpdating) return 'Harap tunggu update quantity selesai'; + if (isAnyCheckboxUpdating) return 'Harap tunggu pembaruan checkbox selesai'; + if (isLoadDelete) return 'Harap tunggu penghapusan selesai'; + if (isUpdating) return 'Harap tunggu pembaruan selesai'; + return ''; + }; + + const getQuotationTooltip = () => { + const baseMessage = getTooltipMessage(); + if (baseMessage) return baseMessage; + if (hasSelectedPromo) return 'Barang promo tidak dapat dibuat quotation'; + if (!hasSelected) return 'Tidak ada item yang dipilih'; + return ''; + }; + + const getCheckoutTooltip = () => { + const baseMessage = getTooltipMessage(); + if (baseMessage) return baseMessage; + if (!hasSelected) return 'Tidak ada item yang dipilih'; + if (hasSelectNoPrice) return 'Terdapat item yang tidak ada harga'; + return ''; + }; + + const getDeleteTooltip = () => { + const baseMessage = getTooltipMessage(); + if (baseMessage) return baseMessage; + if (!hasSelected) return 'Tidak ada item yang dipilih'; + return ''; }; return ( <> + {/* Sticky Header */} <div className={`${ isTop ? 'border-b-[0px]' : 'border-b-[1px]' - } sticky md:top-[157px] flex-col bg-white py-4 border-gray-300 z-50 sm:w-full md:w-3/4`} + } sticky md:top-[157px] flex-col bg-white py-4 border-gray-300 z-50 sm:w-full md:w-3/4`} > - <h1 className={`${style['title']}`}>Keranjang Belanja</h1> + <div className='flex items-center justify-between mb-2'> + <h1 className={style.title}>Keranjang Belanja</h1> + </div> + <div className='h-2' /> - <div className={`flex items-center object-center justify-between `}> + <div className='flex items-center object-center justify-between flex-wrap gap-2'> <div className='flex items-center object-center'> - {isLoad && <Spinner className='my-auto' size='sm' />} - {!isLoad && ( - <Checkbox - borderColor='gray.600' - colorScheme='red' - size='lg' - isChecked={hasSelectedAll} - onChange={handleChange} - /> - )} + <Checkbox + borderColor='gray.600' + colorScheme='red' + size='lg' + isChecked={hasSelectedAll} + onChange={handleSelectAll} + isDisabled={isSelectAllDisabled} + opacity={isSelectAllDisabled ? 0.5 : 1} + cursor={isSelectAllDisabled ? 'not-allowed' : 'pointer'} + _disabled={{ + opacity: 0.5, + cursor: 'not-allowed', + backgroundColor: 'gray.100', + }} + /> <p className='p-2 text-caption-2'> {hasSelectedAll ? 'Uncheck all' : 'Select all'} </p> </div> - <div className='delate all flex items-center object-center'> - <Tooltip - label={clsxm({ - 'Tidak ada item yang dipilih': !hasSelected, - })} - > + + <div className='flex items-center object-center'> + <Tooltip label={getDeleteTooltip()}> <Button bg='#fadede' variant='outline' colorScheme='red' - w='full' - isDisabled={!hasSelected} + w='auto' + size={device.isMobile ? 'sm' : 'md'} + isDisabled={!hasSelected || areButtonsDisabled} onClick={handleDelete} > {isLoadDelete && <Spinner size='xs' />} @@ -223,19 +345,20 @@ const CartPage = () => { </div> </div> - <div className={style['content']}> + {/* Main Content */} + <div className={style.content}> <div className={style['item-wrapper']}> <div className={style['item-skeleton']}> {!cart && <CartItemModule.Skeleton count={5} height='120px' />} </div> - <div className={style['items']}> + <div className={style.items}> {cart?.products?.map((item) => ( <CartItemModule key={item.id} item={item} /> ))} {cart?.products?.length === 0 && ( - <div className='flex flex-col items-center'> + <div className='flex flex-col items-center p-4'> <Image src='/images/empty_cart.svg' alt='Empty Cart' @@ -261,17 +384,28 @@ const CartPage = () => { )} </div> </div> + + {/* Cart Summary */} <div className={`${style['summary-wrapper']} ${ - useDivvice.isMobile && cart?.product_total === 0 ? 'hidden' : '' + device.isMobile && (!cart || cart?.product_total === 0) + ? 'hidden' + : '' }`} > - <div className={style['summary']}> - {useDivvice.isMobile && ( - <CartSummaryMobile {...summary} isLoaded={!!cart} /> - )} - {!useDivvice.isMobile && ( - <CartSummary {...summary} isLoaded={!!cart} /> + <div className={style.summary}> + {device.isMobile ? ( + <CartSummaryMobile + {...summary} + isLoaded={!!cart} + products={cart?.products} + /> + ) : ( + <CartSummary + {...summary} + isLoaded={!!cart} + products={cart?.products} + /> )} <div @@ -281,34 +415,31 @@ const CartPage = () => { : style['summary-buttons'] } > - <Tooltip - label={ - hasSelectedPromo && - 'Barang promo tidak dapat dibuat quotation' - } - > + <Tooltip label={getQuotationTooltip()}> <Button colorScheme='yellow' w='full' - isDisabled={hasSelectedPromo || !hasSelected} + isDisabled={ + hasSelectedPromo || !hasSelected || areButtonsDisabled + } onClick={handleQuotation} > + {areButtonsDisabled && <Spinner size='sm' mr={2} />} Quotation </Button> </Tooltip> + {!isStepApproval && ( - <Tooltip - label={clsxm({ - 'Tidak ada item yang dipilih': !hasSelected, - 'Terdapat item yang tidak ada harga': hasSelectNoPrice, - })} - > + <Tooltip label={getCheckoutTooltip()}> <Button colorScheme='red' w='full' - isDisabled={!hasSelected || hasSelectNoPrice} + isDisabled={ + !hasSelected || hasSelectNoPrice || areButtonsDisabled + } onClick={handleCheckout} > + {areButtonsDisabled && <Spinner size='sm' mr={2} />} Checkout </Button> </Tooltip> @@ -321,4 +452,4 @@ const CartPage = () => { ); }; -export default CartPage; +export default CartPage;
\ No newline at end of file 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/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/utils/cart.js b/src-migrate/utils/cart.js new file mode 100644 index 00000000..4bdee49a --- /dev/null +++ b/src-migrate/utils/cart.js @@ -0,0 +1,455 @@ +// cart-cookie-utils.js +import Cookies from 'js-cookie'; +import checkboxUpdateState from './checkBoxState'; + +// Constants +const CART_ITEMS_COOKIE = 'cart_data'; +const SELECTED_ITEMS_COOKIE = 'cart_selected_items'; +const COOKIE_EXPIRY_DAYS = 7; // Cookie akan berlaku selama 7 hari + +/** + * Mengambil data cart lengkap dari cookie + * @returns {Object} Object dengan key cart_id dan value cart item data lengkap + */ +export const getCartDataFromCookie = () => { + try { + const storedData = Cookies.get(CART_ITEMS_COOKIE); + return storedData ? JSON.parse(storedData) : {}; + } catch (error) { + console.error('Error reading cart data from cookie:', error); + return {}; + } +}; + +/** + * Menyimpan data cart lengkap ke cookie + * @param {Object} cartData Object dengan key cart_id dan value cart item data lengkap + */ +export const setCartDataToCookie = (cartData) => { + try { + Cookies.set(CART_ITEMS_COOKIE, JSON.stringify(cartData), { + expires: COOKIE_EXPIRY_DAYS, + path: '/', + sameSite: 'strict', + }); + } catch (error) { + console.error('Error saving cart data to cookie:', error); + } +}; + +/** + * Mengambil state selected items dari cookie + * @returns {Object} Object dengan key product id dan value boolean selected status + */ +export const getSelectedItemsFromCookie = () => { + try { + const storedItems = Cookies.get(SELECTED_ITEMS_COOKIE); + return storedItems ? JSON.parse(storedItems) : {}; + } catch (error) { + console.error('Error reading selected items from cookie:', error); + return {}; + } +}; + +/** + * Menyimpan state selected items ke cookie + * @param {Object} items Object dengan key product id dan value boolean selected status + */ +export const setSelectedItemsToCookie = (items) => { + try { + Cookies.set(SELECTED_ITEMS_COOKIE, JSON.stringify(items), { + expires: COOKIE_EXPIRY_DAYS, + path: '/', + sameSite: 'strict', + }); + } catch (error) { + console.error('Error saving selected items to cookie:', error); + } +}; + +/** + * Transform cart items dari format API ke format yang lebih simpel untuk disimpan di cookie + * @param {Array} cartItems Array cart items dari API + * @returns {Object} Object dengan key cart_id dan value cart item data + */ +export const transformCartItemsForCookie = (cartItems) => { + if (!cartItems || !Array.isArray(cartItems)) return {}; + + const cartData = {}; + + cartItems.forEach((item) => { + // Skip items yang tidak memiliki cart_id + if (!item.cart_id) return; + + cartData[item.cart_id] = { + id: item.id, + cart_id: item.cart_id, + cart_type: item.cart_type, + product: item.product_id + ? { + id: item.product_id, + name: item.product_name || '', + } + : null, + program_line: item.program_line_id + ? { + id: item.program_line_id, + name: item.program_line_name || '', + } + : null, + quantity: item.quantity, + selected: item.selected, + price: item.price, + package_price: item.package_price, + source: item.source || 'add_to_cart', + }; + }); + + return cartData; +}; + +/** + * Sinkronisasi cart data dan selected items dari server dengan cookie + * @param {Object} cart Cart object dari API + * @returns {Object} Object yang berisi updated cartData dan selectedItems + */ +export const syncCartWithCookie = (cart) => { + try { + if (!cart || !cart.products) return { needsUpdate: false }; + + // Transform data API ke cookie + const serverCartData = transformCartItemsForCookie(cart.products); + + // Ambil data lama dari cookie + const existingCartData = getCartDataFromCookie(); + + // Ambil selected status dari cookie + const selectedItems = getSelectedItemsFromCookie(); + + // Gabungkan data cart, (prioritize data server) + const mergedCartData = { ...existingCartData, ...serverCartData }; + + // Periksa apakah ada perbedaan status selected + let needsUpdate = false; + + // Update selected status berdasarkan cookie jika ada + for (const cartId in mergedCartData) { + const item = mergedCartData[cartId]; + if (item.id && selectedItems[item.id] !== undefined) { + // Jika status di cookie berbeda dengan di cart + if (item.selected !== selectedItems[item.id]) { + needsUpdate = true; + item.selected = selectedItems[item.id]; + } + } else if (item.id) { + selectedItems[item.id] = item.selected; + } + } + + // Simpan ke cookie + setCartDataToCookie(mergedCartData); + setSelectedItemsToCookie(selectedItems); + + return { + cartData: mergedCartData, + selectedItems, + needsUpdate, + }; + } catch (error) { + console.error('Error syncing cart with cookie:', error); + return { needsUpdate: false }; + } +}; + +/** + * Update selected status item di cookie + * @param {number} productId ID produk + * @param {boolean} isSelected Status selected baru + * @param {boolean} notifyUpdate Whether to notify checkbox update state (default: true) + */ +export const updateSelectedItemInCookie = ( + productId, + isSelected, + notifyUpdate = true +) => { + try { + // Notify checkbox update state if requested + if (notifyUpdate) { + checkboxUpdateState.startUpdate(); + } + + const selectedItems = getSelectedItemsFromCookie(); + selectedItems[productId] = isSelected; + setSelectedItemsToCookie(selectedItems); + + // Update juga di cart data + const cartData = getCartDataFromCookie(); + + for (const cartId in cartData) { + const item = cartData[cartId]; + if (item.id === productId) { + item.selected = isSelected; + } + } + + setCartDataToCookie(cartData); + + return { selectedItems, cartData }; + } catch (error) { + console.error('Error updating selected item in cookie:', error); + return {}; + } finally { + // End update notification if requested + if (notifyUpdate) { + checkboxUpdateState.endUpdate(); + } + } +}; + +/** + * Set semua item menjadi selected atau unselected di cookie + * @param {Array} productIds Array product IDs + * @param {boolean} isSelected Status selected baru + * @param {boolean} notifyUpdate Whether to notify checkbox update state (default: true) + */ +export const setAllSelectedInCookie = ( + productIds, + isSelected, + notifyUpdate = true +) => { + try { + // Notify checkbox update state if requested + if (notifyUpdate) { + checkboxUpdateState.startUpdate(); + } + + const selectedItems = getSelectedItemsFromCookie(); + + productIds.forEach((id) => { + if (id) selectedItems[id] = isSelected; + }); + + setSelectedItemsToCookie(selectedItems); + + // Update juga di cart data + const cartData = getCartDataFromCookie(); + + for (const cartId in cartData) { + if (productIds.includes(cartData[cartId].id)) { + cartData[cartId].selected = isSelected; + } + } + + setCartDataToCookie(cartData); + + return { selectedItems, cartData }; + } catch (error) { + console.error('Error setting all selected in cookie:', error); + return {}; + } finally { + // End update notification if requested + if (notifyUpdate) { + checkboxUpdateState.endUpdate(); + } + } +}; + +/** + * Hapus item dari cookie + * @param {Array} cartIds Array cart IDs untuk dihapus + */ +export const removeCartItemsFromCookie = (cartIds) => { + try { + const cartData = getCartDataFromCookie(); + const selectedItems = getSelectedItemsFromCookie(); + const productIdsToRemove = []; + + // Hapus item dari cartData dan catat product IDs + cartIds.forEach((cartId) => { + if (cartData[cartId]) { + if (cartData[cartId].id) { + productIdsToRemove.push(cartData[cartId].id); + } + delete cartData[cartId]; + } + }); + + // Hapus dari selectedItems + productIdsToRemove.forEach((productId) => { + if (selectedItems[productId] !== undefined) { + delete selectedItems[productId]; + } + }); + + // Simpan kembali ke cookie + setCartDataToCookie(cartData); + setSelectedItemsToCookie(selectedItems); + + return { cartData, selectedItems }; + } catch (error) { + console.error('Error removing cart items from cookie:', error); + return {}; + } +}; + +/** + * Hapus item selected dari cookie berdasarkan product IDs + * @param {Array} productIds Array product IDs untuk dihapus + */ +/** + * Hapus item selected dari cookie berdasarkan product IDs dan juga hapus dari cart data + * @param {Array} productIds Array product IDs untuk dihapus + */ + +/** + * Force reset semua selected items ke unselected state + */ +export const forceResetAllSelectedItems = () => { + try { + checkboxUpdateState.startUpdate(); + + const cartData = getCartDataFromCookie(); + const selectedItems = {}; + + // Reset semua selected status di cartData + for (const cartId in cartData) { + cartData[cartId].selected = false; + if (cartData[cartId].id) { + selectedItems[cartData[cartId].id] = false; + } + } + + // Simpan kembali ke cookie + setCartDataToCookie(cartData); + setSelectedItemsToCookie(selectedItems); + + return { cartData, selectedItems }; + } catch (error) { + console.error('Error resetting all selected items:', error); + return {}; + } finally { + checkboxUpdateState.endUpdate(); + } +}; + +/** + * Sync selected items between cookie and cart data + * @param {Array} cartProducts Products array from cart + */ +export const syncSelectedItemsWithCookie = (cartProducts) => { + try { + if (!cartProducts || !Array.isArray(cartProducts)) { + return { items: {}, needsUpdate: false }; + } + + const selectedItems = getSelectedItemsFromCookie(); + let needsUpdate = false; + + // Check if we need to update any items based on cookie values + cartProducts.forEach((product) => { + if (product.id && selectedItems[product.id] !== undefined) { + if (product.selected !== selectedItems[product.id]) { + needsUpdate = true; + } + } else if (product.id) { + // If not in cookie, add with current value + selectedItems[product.id] = product.selected; + } + }); + + // Update the cookie with the latest values + setSelectedItemsToCookie(selectedItems); + + return { items: selectedItems, needsUpdate }; + } catch (error) { + console.error('Error syncing selected items with cookie:', error); + return { items: {}, needsUpdate: false }; + } +}; + +// Export the checkbox update state for use in components +export { checkboxUpdateState }; + +/** + * Hapus item selected dari cookie berdasarkan product IDs dan juga hapus dari cart data + * @param {Array} productIds Array product IDs untuk dihapus + */ +/** + * Hapus item selected dari cookie berdasarkan product IDs dan juga hapus dari cart data + * @param {Array} productIds Array product IDs untuk dihapus + */ +export const removeSelectedItemsFromCookie = (productIds) => { + try { + const selectedItems = getSelectedItemsFromCookie(); + const cartData = getCartDataFromCookie(); + const cartIdsToRemove = []; + + // Find cart IDs that match the product IDs + for (const cartId in cartData) { + if (productIds.includes(cartData[cartId].id)) { + cartIdsToRemove.push(cartId); + } + } + + // Remove from selectedItems + productIds.forEach((productId) => { + if (selectedItems[productId] !== undefined) { + delete selectedItems[productId]; + } + }); + + // Remove from cartData + cartIdsToRemove.forEach((cartId) => { + delete cartData[cartId]; + }); + + // Save both cookies + setSelectedItemsToCookie(selectedItems); + setCartDataToCookie(cartData); + + return { selectedItems, cartData }; + } catch (error) { + console.error('Error removing selected items from cookie:', error); + return {}; + } +}; + +class QuantityUpdateState { + constructor() { + this.updateItems = new Set(); + this.listeners = new Set(); + } + + startUpdate(itemId) { + this.updateItems.add(itemId); + this.notifyListeners(); + } + + endUpdate(itemId) { + this.updateItems.delete(itemId); + this.notifyListeners(); + } + + isAnyQuantityUpdating() { + return this.updateItems.size > 0; + } + + isItemUpdating(itemId) { + return this.updateItems.has(itemId); + } + + addListener(callback) { + this.listeners.add(callback); + } + + removeListener(callback) { + this.listeners.delete(callback); + } + + notifyListeners() { + const isUpdating = this.isAnyQuantityUpdating(); + this.listeners.forEach(callback => callback(isUpdating)); + } +} + +export const quantityUpdateState = new QuantityUpdateState();
\ No newline at end of file diff --git a/src-migrate/utils/checkBoxState.js b/src-migrate/utils/checkBoxState.js new file mode 100644 index 00000000..9568c321 --- /dev/null +++ b/src-migrate/utils/checkBoxState.js @@ -0,0 +1,90 @@ +/** + * State manager for checkbox updates + * Tracks global and individual checkbox update states + */ +class CheckboxUpdateState { + constructor() { + this.updateCount = 0; + this.listeners = new Set(); + this.updatingCheckboxIds = new Set(); + } + + // Global update state (for buttons quotation and checkout) + isUpdating() { + return this.updateCount > 0; + } + + // Individual checkbox state + isCheckboxUpdating(itemId) { + return this.updatingCheckboxIds.has(String(itemId)); + } + + // Start update + startUpdate(itemId = null) { + this.updateCount++; + + if (itemId !== null) { + this.updatingCheckboxIds.add(String(itemId)); + } + + this.notifyListeners(); + return this.updateCount; + } + + // End update + endUpdate(itemId = null) { + this.updateCount = Math.max(0, this.updateCount - 1); + + if (itemId !== null) { + this.updatingCheckboxIds.delete(String(itemId)); + } + + this.notifyListeners(); + return this.updateCount; + } + + // Reset all states + reset() { + this.updateCount = 0; + this.updatingCheckboxIds.clear(); + this.notifyListeners(); + } + + // Listener management + addListener(callback) { + if (typeof callback === 'function') { + this.listeners.add(callback); + // Immediate callback with current state + callback(this.isUpdating()); + } + } + + removeListener(callback) { + this.listeners.delete(callback); + } + + // Debug helpers + getUpdateCount() { + return this.updateCount; + } + + getUpdatingCheckboxIds() { + return [...this.updatingCheckboxIds]; + } + + // Private method to notify listeners + notifyListeners() { + const isUpdating = this.isUpdating(); + + this.listeners.forEach((listener) => { + try { + listener(isUpdating); + } catch (error) { + console.error('Checkbox update state listener error:', error); + } + }); + } +} + +const checkboxUpdateState = new CheckboxUpdateState(); +export default checkboxUpdateState; diff --git a/src-migrate/validations/tempo.ts b/src-migrate/validations/tempo.ts index cf5914b5..46ac1ef1 100644 --- a/src-migrate/validations/tempo.ts +++ b/src-migrate/validations/tempo.ts @@ -122,8 +122,12 @@ export const TempoSchemaPengiriman = z.object({ 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(), diff --git a/src/api/productApi.js b/src/api/productApi.js index 4a29b59d..dc96a77e 100644 --- a/src/api/productApi.js +++ b/src/api/productApi.js @@ -1,13 +1,15 @@ -import axios from 'axios' +import axios from 'axios'; export const popularProductApi = () => { return async () => { const today = new Date(); - const dayOfYear = Math.floor((today - new Date(today.getFullYear(), 0, 0)) / 86400000); + const dayOfYear = Math.floor( + (today - new Date(today.getFullYear(), 0, 0)) / 86400000 + ); const page = (dayOfYear % 24) + 1; const dataPopularProducts = await axios( `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/search?q=*&page=${page}&orderBy=stock&priceFrom=1` - ) - return dataPopularProducts.data.response - } -} + ); + return dataPopularProducts.data.response; + }; +}; diff --git a/src/lib/auth/components/CompanyProfile.jsx b/src/lib/auth/components/CompanyProfile.jsx index 6d4da1d1..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?.company ? (auth.parentId ? auth.parentId : auth.partnerId) : auth.partnerId, + 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/checkout/components/Checkout.jsx b/src/lib/checkout/components/Checkout.jsx index 637706a2..0b386c30 100644 --- a/src/lib/checkout/components/Checkout.jsx +++ b/src/lib/checkout/components/Checkout.jsx @@ -31,12 +31,12 @@ import addressesApi from '@/lib/address/api/addressesApi'; import { MapPinIcon } from 'lucide-react'; import CartItem from '~/modules/cart/components/Item.tsx'; import ExpedisiList from '../api/ExpedisiList'; -import { getVoucher } from '../api/getVoucher'; +import { findVoucher, getVoucher, getVoucherNew } from '../api/getVoucher'; + import { useAddress } from '../stores/useAdress'; import SectionExpedition from './SectionExpedition'; import { useCheckout } from '../stores/stateCheckout'; import { formatShipmentRange, getToDate } from '../utils/functionCheckouit'; - const SELF_PICKUP_ID = 32; const { checkoutApi } = require('../api/checkoutApi'); @@ -55,7 +55,9 @@ function convertToInternational(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; @@ -72,11 +74,18 @@ const Checkout = () => { voucher: activeVoucher, voucher_shipping: activeVoucherShipping, }), + //biteship { keepPreviousData: true, // Menjaga data sebelumnya sampai data baru tersedia } ); + // const [selectedAddress, setSelectedAddress] = useState({ + // shipping: null, + // invoicing: null, + // }); + // const [addresses, setAddresses] = useState(null); + const { selectedAddress, setSelectedAddress, @@ -131,12 +140,17 @@ const Checkout = () => { } }, [addresses]); + // const [products, setProducts] = useState(null); const [totalWeight, setTotalWeight] = useState(0); const [priceCheck, setPriceCheck] = useState(false); + // const [listExpedisi, setExpedisi] = useState([]); const [listserviceExpedisi, setListServiceExpedisi] = useState([]); const [selectedExpedisi, setSelectedExpedisi] = useState(0); const [selectedCarrierId, setselectedCarrierId] = useState(0); const [selectedCarrier, setselectedCarrier] = useState(0); + //new release + // const [biayaKirim, setBiayaKirim] = useState(0); + // const [checkWeigth, setCheckWeight] = useState(false); const [selectedServiceType, setSelectedServiceType] = useState(null); const [selectedExpedisiService, setselectedExpedisiService] = useState(null); // const [etd, setEtd] = useState(null); @@ -151,9 +165,11 @@ const Checkout = () => { const [findCodeVoucher, SetFindVoucher] = useState(null); const [selisihHargaCode, SetSelisihHargaCode] = useState(null); const [buttonTerapkan, SetButtonTerapkan] = useState(false); + // const [checkoutValidation, setCheckoutValidation] = useState(false); const [loadingVoucher, setLoadingVoucher] = useState(true); const [loadingRajaOngkir, setLoadingRajaOngkir] = useState(false); const [grandTotal, setGrandTotal] = useState(0); + const [hasFlashSale, setHasFlashSale] = useState(false); const { checkWeigth, @@ -174,23 +190,37 @@ const Checkout = () => { setExpedisi, productSla } = useCheckout(); - const expedisiValidation = useRef(null); const voucher = async () => { 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, + 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 { @@ -200,16 +230,28 @@ 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); return; } - dataVoucher.forEach((addNewLine) => { if (addNewLine.applyType !== 'shipping') { @@ -320,6 +362,58 @@ const Checkout = () => { }; }, []); + const hitungDiscountVoucher = (code, source) => { + let countDiscount = 0; + if (source === 'voucher') { + let dataVoucherIndex = listVouchers.findIndex( + (voucher) => voucher.code == code + ); + let dataActiveVoucher = listVouchers[dataVoucherIndex]; + + countDiscount = dataActiveVoucher.discountVoucher; + } else { + let dataVoucherIndex = listVoucherShippings.findIndex( + (voucher) => voucher.code == code + ); + let dataActiveVoucher = listVoucherShippings[dataVoucherIndex]; + + countDiscount = dataActiveVoucher.discountVoucher; + } + + /*if (dataActiveVoucher.discountType === 'percentage') { + countDiscount = cartCheckout?.subtotal * (dataActiveVoucher.discountAmount / 100) + if ( + dataActiveVoucher.maxDiscountAmount > 0 && + countDiscount > dataActiveVoucher.maxDiscountAmount + ) { + countDiscount = dataActiveVoucher.maxDiscountAmount + } + } else { + countDiscount = dataActiveVoucher.discountAmount + }*/ + + return countDiscount; + }; + + // useEffect(() => { + // if (!listVouchers) return; + // if (!activeVoucher) return; + + // console.log('voucher') + // const countDiscount = hitungDiscountVoucher(activeVoucher, 'voucher'); + + // SetDiscountVoucher(countDiscount); + // }, [activeVoucher, listVouchers]); + + // useEffect(() => { + // if (!listVoucherShippings) return; + // if (!activeVoucherShipping) return; + + // const countDiscount = hitungDiscountVoucher(activeVoucherShipping, 'voucher_shipping'); + + // SetDiscountVoucherOngkir(countDiscount); + // }, [activeVoucherShipping, listVoucherShippings]); + useEffect(() => { if (qVoucher === 'PASTIHEMAT' && listVouchers) { let code = qVoucher; @@ -337,6 +431,72 @@ const Checkout = () => { setHasFlashSale(hasFlashSale); }, [cartCheckout]); + // useEffect(() => { + // setCheckoutValidation(false); + // const loadServiceRajaOngkir = async () => { + // setLoadingRajaOngkir(true); + // const body = { + // origin: 2127, + // destination: selectedAddress.shipping.rajaongkirCityId, + // weight: totalWeight, + // courier: selectedCarrier, + // originType: 'subdistrict', + // destinationType: 'subdistrict', + // }; + // setBiayaKirim(0); + // const dataService = await axios( + // '/api/rajaongkir-service?body=' + JSON.stringify(body) + // ); + // setLoadingRajaOngkir(false); + // setListServiceExpedisi(dataService.data[0].costs); + // if (dataService.data[0].costs[0]) { + // setBiayaKirim(dataService.data[0].costs[0]?.cost[0].value); + // setselectedExpedisiService( + // dataService.data[0].costs[0]?.description + + // '-' + + // dataService.data[0].costs[0]?.service + // ); + // setEtd(dataService.data[0].costs[0]?.cost[0].etd); + // toast.success('Harap pilih tipe layanan pengiriman'); + // } else { + // toast.error('Maaf, layanan tidak tersedia. Mohon pilih expedisi lain.'); + // } + // }; + // if (selectedCarrier != 0 && selectedCarrier != 1 && totalWeight > 0) { + // loadServiceRajaOngkir(); + // } else { + // setListServiceExpedisi(); + // setBiayaKirim(0); + // setselectedExpedisiService(); + // setEtd(); + // } + // }, [selectedCarrier, selectedAddress, totalWeight]); + // + // useEffect(() => { + // if (selectedServiceType) { + // let serviceType = selectedServiceType.split(','); + // setBiayaKirim(serviceType[0]); + // setselectedExpedisiService(serviceType[1]); + // setEtd(serviceType[2]); + // } + // }, [selectedServiceType]); + // + // useEffect(() => { + // if (etd) setEtdFix(calculateEstimatedArrival(etd)); + // }, [etd]); + // + // useEffect(() => { + // if (selectedExpedisi) { + // let serviceType = selectedExpedisi.split(','); + // if (serviceType[0] === 0) return; + // + // setselectedCarrier(serviceType[0]); + // setselectedCarrierId(serviceType[1]); + // setListServiceExpedisi([]); + // } + // }, [selectedExpedisi]); + // + const poNumber = useRef(null); const poFile = useRef(null); @@ -374,10 +534,15 @@ const Checkout = () => { } return; } - if (!selectedService) { - toast.error('Harap pilih tipe layanan pengiriman'); - return; - } + //new release + // if (selectedCarrier != 1 && biayaKirim == 0) { + // toast.error('Maaf, layanan tidak tersedia. Mohon pilih expedisi lain.'); + // return; + // } + // if (!selectedService) { + // toast.error('Harap pilih tipe layanan pengiriman'); + // return; + // } if (selectedCourier != 1 && biayaKirim == 0) { toast.error('Maaf, layanan tidak tersedia. Mohon pilih expedisi lain.'); return; @@ -404,11 +569,16 @@ const Checkout = () => { estimated_arrival_days_start : parseInt(eta_courier_start) + parseInt(productSla), estimated_arrival_days: parseInt(eta_courier) + parseInt(productSla), delivery_service_type: selectedService?.service_type, + // New release + // carrier_id: selectedCarrierId, + // estimated_arrival_days: splitDuration(etd), + // delivery_service_type: selectedExpedisiService, flash_sale: hasFlashSale, // dibuat negasi untuk ngetest kebalikan nilai false voucher: activeVoucher, voucher_shipping: activeVoucherShipping, type: 'sale_order', }; + if (query) { data.source = 'buy'; } @@ -469,6 +639,24 @@ const Checkout = () => { )}`; } } + + /* const midtrans = async () => { + for (const product of products) deleteItemCart({ productId: product.id }); + if (grandTotal > 0) { + const payment = await axios.post( + `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/midtrans-payment?transactionId=${isCheckouted.id}` + ); + setIsLoading(false); + window.location.href = payment.data.redirectUrl; + } else { + window.location.href = `${ + process.env.NEXT_PUBLIC_SELF_HOST + }/shop/checkout/success?order_id=${isCheckouted.name.replace( + /\//g, + '-' + )}`; + } + };*/ }; const handlingActivateCode = async () => { @@ -642,6 +830,19 @@ const Checkout = () => { )} <hr className='mt-8 mb-4 border-gray_r-8' /> + {/* {!loadingVoucher && + listVouchers?.length === 1 && + listVoucherShippings?.length === 1} + { + <div className='flex items-center justify-center mt-4 mb-4'> + <div className='text-center'> + <h1 className='font-bold mb-4'>Tidak ada voucher tersedia</h1> + <p className='text-gray-500'> + Maaf, saat ini tidak ada voucher yang tersedia. + </p> + </div> + </div> + } */} {listVoucherShippings && listVoucherShippings?.length > 0 && ( <div> @@ -1027,21 +1228,22 @@ const Checkout = () => { </Skeleton> )} <Divider /> - <SectionValidation address={selectedAddress.invoicing} /> - {/* <SectionExpedisi - address={selectedAddress.shipping} - listExpedisi={listExpedisi} - setSelectedExpedisi={setSelectedExpedisi} - checkWeigth={checkWeigth} - checkoutValidation={checkoutValidation} - expedisiValidation={expedisiValidation} - loadingRajaOngkir={loadingRajaOngkir} - /> - <Divider /> - <SectionListService - listserviceExpedisi={listserviceExpedisi} - setSelectedServiceType={setSelectedServiceType} - /> */} + <SectionValidation address={selectedAddress.shipping} /> + {/*new-relase*/} + {/*<SectionExpedisi*/} + {/* address={selectedAddress.shipping}*/} + {/* listExpedisi={listExpedisi}*/} + {/* setSelectedExpedisi={setSelectedExpedisi}*/} + {/* checkWeigth={checkWeigth}*/} + {/* checkoutValidation={checkoutValidation}*/} + {/* expedisiValidation={expedisiValidation}*/} + {/* loadingRajaOngkir={loadingRajaOngkir}*/} + {/* />*/} + {/* <Divider />*/} + {/* <SectionListService*/} + {/* listserviceExpedisi={listserviceExpedisi}*/} + {/* setSelectedServiceType={setSelectedServiceType}*/} + {/* />*/} <div className='p-4 flex flex-col gap-y-4'> {!!products && @@ -1123,7 +1325,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'> @@ -1255,6 +1459,8 @@ const Checkout = () => { className='flex-1 btn-yellow' onClick={checkout} disabled={ + //new release + // isLoading || !products || products?.length == 0 || priceCheck || @@ -1328,7 +1534,7 @@ const Checkout = () => { )} {products && <SectionExpedition products={products} />} <Divider /> - <SectionValidation address={selectedAddress.invoicing} /> + <SectionValidation address={selectedAddress.shipping} /> {/* <SectionExpedisi address={selectedAddress.shipping} listExpedisi={listExpedisi} @@ -1430,7 +1636,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'> @@ -1560,6 +1768,8 @@ const Checkout = () => { className='w-full btn-yellow mt-4' onClick={checkout} disabled={ + // new-relase + // isLoading || !products || products?.length == 0 || priceCheck || @@ -1630,6 +1840,9 @@ const SectionAddress = ({ address, label, url }) => ( ); const SectionValidation = ({ address }) => + //new release + // address?.stateId == 0 || + // (address?.rajaongkirCityId == 0 && ( address?.stateId == 0 && ( <BottomPopup active={true} title='Update Alamat'> <div className='leading-7 text-gray_r-12/80'> @@ -1644,7 +1857,9 @@ 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 c82e15c7..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?.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.{' '} diff --git a/src/lib/checkout/components/FinishCheckout.jsx b/src/lib/checkout/components/FinishCheckout.jsx index 4a67b252..d533325e 100644 --- a/src/lib/checkout/components/FinishCheckout.jsx +++ b/src/lib/checkout/components/FinishCheckout.jsx @@ -53,13 +53,12 @@ const FinishCheckout = ({ query }) => { height={isMobile ? 300 : 450} /> <div className='text-title-sm md:text-title-lg text-center font-semibold'> - Terima Kasih atas Pembelian Kamu + Terima Kasih atas Pembelian di Indoteknik.com </div> + <p className='text-title-sm md:text-title-lg font-semibold my-2'>No. Transaksi: <span className='text-red-500'>{query?.order_id?.replaceAll('-', '/')}</span></p> <div className='flex flex-col justify-center items-center text-body-2 md:text-body-1 text-center mt-3 px-24 md:px-36 py-4 border-2 gap-y-2 rounded'> - <p className='font-bold'>No. Transaksi</p> - <p className='mb-2 font-medium text-red-500 text-xl'> - {query?.order_id?.replaceAll('-', '/')} - </p> + <p className="text-title-sm md:text-title-xl text-gray-500 mt-1">Estimasi Barang Siap pada Tanggal</p> + <p className="text-title-sm md:text-title-xl text-red-500 font-semibold my-2">{data?.expectedReadyToShip}</p> <Link href={`/my/quotations/${data?.id}`} className='btn-solid-red rounded-md text-base' 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/invoice/components/Invoice.jsx b/src/lib/invoice/components/Invoice.jsx index 15bfa746..a26b231f 100644 --- a/src/lib/invoice/components/Invoice.jsx +++ b/src/lib/invoice/components/Invoice.jsx @@ -1,60 +1,66 @@ -import Spinner from '@/core/components/elements/Spinner/Spinner' -import useInvoice from '../hooks/useInvoice' -import { downloadInvoice, downloadTaxInvoice } from '../utils/invoices' -import Divider from '@/core/components/elements/Divider/Divider' -import VariantGroupCard from '@/lib/variant/components/VariantGroupCard' -import currencyFormat from '@/core/utils/currencyFormat' -import MobileView from '@/core/components/views/MobileView' -import DesktopView from '@/core/components/views/DesktopView' -import Menu from '@/lib/auth/components/Menu' -import Link from '@/core/components/elements/Link/Link' -import Image from '@/core/components/elements/Image/Image' -import { createSlug } from '@/core/utils/slug' -import { useEffect, useState } from 'react' +import Spinner from '@/core/components/elements/Spinner/Spinner'; +import useInvoice from '../hooks/useInvoice'; +import { downloadInvoice, downloadTaxInvoice } from '../utils/invoices'; +import Divider from '@/core/components/elements/Divider/Divider'; +import VariantGroupCard from '@/lib/variant/components/VariantGroupCard'; +import currencyFormat from '@/core/utils/currencyFormat'; +import MobileView from '@/core/components/views/MobileView'; +import DesktopView from '@/core/components/views/DesktopView'; +import Menu from '@/lib/auth/components/Menu'; +import Link from '@/core/components/elements/Link/Link'; +import Image from '@/core/components/elements/Image/Image'; +import { createSlug } from '@/core/utils/slug'; +import { useEffect, useState } from 'react'; const Invoice = ({ id }) => { - const PPN = process.env.NEXT_PUBLIC_PPN - const { invoice } = useInvoice({ id }) + const PPN = process.env.NEXT_PUBLIC_PPN; + const { invoice } = useInvoice({ id }); - const [totalAmount, setTotalAmount] = useState(0) - const [totalDiscountAmount, setTotalDiscountAmount] = useState(0) + const [totalAmount, setTotalAmount] = useState(0); + const [totalDiscountAmount, setTotalDiscountAmount] = useState(0); + + const amountBeforePPN = invoice.data?.amountTotal / PPN; + const taxAmount = invoice.data?.amountTotal - amountBeforePPN; useEffect(() => { if (invoice?.data?.products) { - let calculateTotalAmount = 0 - let calculateTotalDiscountAmount = 0 + let calculateTotalAmount = 0; + let calculateTotalDiscountAmount = 0; invoice.data.products.forEach((product) => { - calculateTotalAmount += product.price.price * product.quantity + calculateTotalAmount += product.price.price * product.quantity; calculateTotalDiscountAmount += - (product.price.price - product.price.priceDiscount) * product.quantity - }) - setTotalAmount(calculateTotalAmount) - setTotalDiscountAmount(calculateTotalDiscountAmount) + (product.price.price - product.price.priceDiscount) * + product.quantity; + }); + setTotalAmount(calculateTotalAmount); + setTotalDiscountAmount(calculateTotalDiscountAmount); } - }, [invoice]) + }, [invoice]); if (invoice.isLoading) { return ( <div className='flex justify-center my-6'> <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' /> </div> - ) + ); } - const address = invoice.data?.customer - let fullAddress = [] - if (address?.street) fullAddress.push(address.street) - if (address?.subDistrict?.name) fullAddress.push(address.subDistrict.name) - if (address?.district?.name) fullAddress.push(address.district.name) - if (address?.city?.name) fullAddress.push(address.city.name) - fullAddress = fullAddress.join(', ') + const address = invoice.data?.customer; + let fullAddress = []; + if (address?.street) fullAddress.push(address.street); + if (address?.subDistrict?.name) fullAddress.push(address.subDistrict.name); + if (address?.district?.name) fullAddress.push(address.district.name); + if (address?.city?.name) fullAddress.push(address.city.name); + fullAddress = fullAddress.join(', '); return ( invoice.data?.name && ( <> <MobileView> <div className='flex flex-col gap-y-4 p-4'> - <DescriptionRow label='No Invoice'>{invoice.data?.name}</DescriptionRow> + <DescriptionRow label='No Invoice'> + {invoice.data?.name} + </DescriptionRow> <DescriptionRow label='Status Transaksi'> {invoice.data?.amountResidual > 0 ? ( <span className='badge-solid-red'>Belum Lunas</span> @@ -68,13 +74,18 @@ const Invoice = ({ id }) => { <DescriptionRow label='Ketentuan Pembayaran'> {invoice.data?.paymentTerm} </DescriptionRow> - {invoice.data?.amountResidual > 0 && invoice.invoiceDate != invoice.invoiceDateDue && ( - <DescriptionRow label='Tanggal Jatuh Tempo'> - {invoice.data?.invoiceDateDue} - </DescriptionRow> - )} - <DescriptionRow label='Nama Sales'>{invoice.data?.sales}</DescriptionRow> - <DescriptionRow label='Tanggal Invoice'>{invoice.data?.invoiceDate}</DescriptionRow> + {invoice.data?.amountResidual > 0 && + invoice.invoiceDate != invoice.invoiceDateDue && ( + <DescriptionRow label='Tanggal Jatuh Tempo'> + {invoice.data?.invoiceDateDue} + </DescriptionRow> + )} + <DescriptionRow label='Nama Sales'> + {invoice.data?.sales} + </DescriptionRow> + <DescriptionRow label='Tanggal Invoice'> + {invoice.data?.invoiceDate} + </DescriptionRow> <div className='flex items-center'> <p className='text-gray_r-11 leading-none'>Invoice</p> <button @@ -104,8 +115,12 @@ const Invoice = ({ id }) => { <div className='flex flex-col gap-y-4 p-4 border-t border-gray_r-6'> <DescriptionRow label='Nama'>{address?.name}</DescriptionRow> - <DescriptionRow label='Email'>{address?.email || '-'}</DescriptionRow> - <DescriptionRow label='No Telepon'>{address?.mobile || '-'}</DescriptionRow> + <DescriptionRow label='Email'> + {address?.email || '-'} + </DescriptionRow> + <DescriptionRow label='No Telepon'> + {address?.mobile || '-'} + </DescriptionRow> <DescriptionRow label='Alamat'>{fullAddress}</DescriptionRow> </div> @@ -128,10 +143,14 @@ const Invoice = ({ id }) => { <Menu /> </div> <div className='w-9/12 p-4 bg-white border border-gray_r-6 rounded'> - <h1 className='text-title-sm font-semibold mb-6'>Invoice & Faktur Pajak</h1> + <h1 className='text-title-sm font-semibold mb-6'> + Invoice & Faktur Pajak + </h1> <div className='flex items-center gap-x-2 mb-3'> - <span className='text-h-sm font-medium'>{invoice?.data?.name}</span> + <span className='text-h-sm font-medium'> + {invoice?.data?.name} + </span> {invoice?.data?.amountResidual > 0 ? ( <div className='badge-solid-red h-fit'>Belum Lunas</div> ) : ( @@ -180,14 +199,16 @@ const Invoice = ({ id }) => { </div> </div> - <div className='text-h-sm font-semibold mt-6 mb-4'>Rincian Pembelian</div> + <div className='text-h-sm font-semibold mt-6 mb-4'> + Rincian Pembelian + </div> <table className='table-data'> <thead> <tr> <th>Nama Produk</th> <th>Jumlah</th> <th>Harga</th> - <th>Diskon</th> + {/* <th>Diskon</th> */} <th>Subtotal</th> </tr> </thead> @@ -229,13 +250,22 @@ const Invoice = ({ id }) => { </div> </td> <td>{product.quantity}</td> - <td>{currencyFormat(product.price.price)}</td> <td> + {currencyFormat( + product.price.priceDiscount - taxAmount + )} + </td> + {/* <td> {product.price.discountPercentage > 0 ? `${product.price.discountPercentage}%` : ''} + </td> */} + <td> + {currencyFormat( + product.price.priceDiscount * product.quantity - + taxAmount + )} </td> - <td>{currencyFormat(product.price.priceDiscount * product.quantity)}</td> </tr> ))} </tbody> @@ -244,20 +274,30 @@ const Invoice = ({ id }) => { <div className='flex justify-end mt-4'> <div className='w-1/4 grid grid-cols-2 gap-y-2 text-gray_r-12/80'> <div className='text-right'>Subtotal</div> - <div className='text-right font-medium'>{currencyFormat(totalAmount)}</div> + <div className='text-right font-medium'> + {currencyFormat( + totalAmount - totalDiscountAmount - taxAmount + )} + </div> - <div className='text-right'>Total Diskon</div> + {/* <div className='text-right'>Total Diskon</div> <div className='text-right font-medium'> - {currencyFormat(-totalDiscountAmount)} + {currencyFormat(totalDiscountAmount)} + </div> */} + <div className='text-right'> + PPN {((PPN - 1) * 100).toFixed(0)}% + </div> + <div className='text-right font-medium'> + {currencyFormat( + invoice.data?.amountTotal - + invoice.data?.amountTotal / PPN + )} </div> <div className='text-right'>Grand Total</div> <div className='text-right font-medium text-gray_r-12'> {currencyFormat(invoice.data?.amountTotal)} </div> - - <div className='text-right'>PPN {((PPN - 1) * 100).toFixed(0)}% (Incl.)</div> - <div className='text-right font-medium'>{currencyFormat(invoice.data?.amountTotal - totalAmount)}</div> </div> </div> </div> @@ -265,14 +305,14 @@ const Invoice = ({ id }) => { </DesktopView> </> ) - ) -} + ); +}; const DescriptionRow = ({ children, label }) => ( <div className='grid grid-cols-2'> <span className='text-gray_r-11'>{label}</span> <span className='text-right'>{children}</span> </div> -) +); -export default Invoice +export default Invoice; diff --git a/src/lib/merchant/api/createMerchantApi.js b/src/lib/merchant/api/createMerchantApi.js new file mode 100644 index 00000000..878ab7c0 --- /dev/null +++ b/src/lib/merchant/api/createMerchantApi.js @@ -0,0 +1,14 @@ +// import odooApi from '@/core/api/odooApi'; +// import { getAuth } from '@/core/utils/auth'; + +// const createMerchantApi = async ({ data }) => { +// const auth = getAuth(); +// const lead = await odooApi( +// 'POST', +// `/api/v1/merchant/${auth.partnerId}`, +// data +// ); +// return lead; +// }; + +// export default createMerchantApi; diff --git a/src/lib/merchant/api/getMerchantApi.js b/src/lib/merchant/api/getMerchantApi.js new file mode 100644 index 00000000..be27e947 --- /dev/null +++ b/src/lib/merchant/api/getMerchantApi.js @@ -0,0 +1,13 @@ +// import odooApi from '@/core/api/odooApi'; +// import { getAuth } from '@/core/utils/auth'; + +// const createMerchantApi = async () => { +// const auth = getAuth(); +// const lead = await odooApi( +// 'GET', +// `/api/v1/detail-merchant/${auth.partnerId}` +// ); +// return lead; +// }; + +// export default createMerchantApi; diff --git a/src/lib/merchant/api/getMerchantProgresApi.js b/src/lib/merchant/api/getMerchantProgresApi.js new file mode 100644 index 00000000..3bec15fe --- /dev/null +++ b/src/lib/merchant/api/getMerchantProgresApi.js @@ -0,0 +1,10 @@ +// import odooApi from '@/core/api/odooApi'; +// import { getAuth } from '@/core/utils/auth'; + +// const createMerchantApi = async () => { +// const auth = getAuth(); +// const lead = await odooApi('GET', `/api/v1/check-merchant/${auth.partnerId}`); +// return lead; +// }; + +// export default createMerchantApi; diff --git a/src/lib/merchant/components/AccountSwitch.jsx b/src/lib/merchant/components/AccountSwitch.jsx new file mode 100644 index 00000000..abbdcc92 --- /dev/null +++ b/src/lib/merchant/components/AccountSwitch.jsx @@ -0,0 +1,60 @@ +// import Link from 'next/link'; +// import Image from '~/components/ui/image'; +// import whatsappUrl from '@/core/utils/whatsappUrl'; +// import { useEffect, useState } from 'react'; +// import odooApi from '@/core/api/odooApi'; +// import useDevice from '@/core/hooks/useDevice'; +// import useAuth from '@/core/hooks/useAuth'; +// import axios from 'axios'; +// import { toast } from 'react-hot-toast'; +// import { ChevronRightIcon, ChevronLeftIcon } from '@heroicons/react/24/outline'; + +// const FinishTempo = ({ query }) => { +// const [data, setData] = useState(); +// const [transactionData, setTransactionData] = useState(); +// const { isDesktop, isMobile } = useDevice(); +// const auth = useAuth(); + +// return ( +// <div className='container flex flex-col items-center gap-4'> +// <div +// className={`flex ${ +// isMobile ? 'w-full' : 'w-2/3' +// } justify-center items-center`} +// > +// <h1 +// className={`text-red-500 text-center py-4 font-semibold ${ +// isMobile ? 'text-lg' : 'text-3xl' +// }`} +// > +// Form Merchant Kamu Gagal Dilakukan +// </h1> +// </div> +// <Image +// src='/images/ICON_TEMPO.png' +// alt='Registrasi Tempo' +// width={isMobile ? 300 : 550} +// height={isMobile ? 300 : 550} +// /> + +// <div +// className={`mt-2 text-center opacity-75 leading-6 p-4 md:p-0 ${ +// isMobile ? 'w-full text-sm' : 'w-4/5 text-base' +// }`} +// > +// Terima kasih atas minat anda untuk mendaftar merchant, namun sayangnya +// akun anda bukan merupakan akun bisnis. Segera ubah akun anda menjadi +// Bisnis untuk menggunakan fitur ini +// </div> +// <Link +// href={'/my/profile'} +// className='btn-solid-red rounded-md text-base flex flex-row items-center justify-center' +// > +// Ubah Akun +// <ChevronRightIcon className='w-5' /> +// </Link> +// </div> +// ); +// }; + +// export default FinishTempo; diff --git a/src/lib/merchant/components/Dokumen.jsx b/src/lib/merchant/components/Dokumen.jsx new file mode 100644 index 00000000..42a9426b --- /dev/null +++ b/src/lib/merchant/components/Dokumen.jsx @@ -0,0 +1,1252 @@ +// import HookFormSelect from '@/core/components/elements/Select/HookFormSelect'; +// import cityApi from '@/lib/address/api/cityApi'; +// import stateApi from '@/lib/address/api/stateApi.js'; +// import districtApi from '@/lib/address/api/districtApi'; +// import subDistrictApi from '@/lib/address/api/subDistrictApi'; +// import { yupResolver } from '@hookform/resolvers/yup'; +// import React, { +// useEffect, +// useRef, +// useState, +// forwardRef, +// useImperativeHandle, +// } from 'react'; +// import ReCAPTCHA from 'react-google-recaptcha'; +// import { Controller, useForm } from 'react-hook-form'; +// import { toast } from 'react-hot-toast'; +// import * as Yup from 'yup'; +// import createMerchantApi from '../api/createMerchantApi'; +// import getMerchantApi from '../api/getMerchantApi'; +// import addressApi from '@/lib/address/api/addressApi'; +// import PageContent from '@/lib/content/components/PageContent'; +// import { useRouter } from 'next/router'; +// import useAuth from '@/core/hooks/useAuth'; +// import { Radio, RadioGroup, Stack, Divider, Button } from '@chakra-ui/react'; +// import { EyeIcon } from '@heroicons/react/24/outline'; +// import BottomPopup from '@/core/components/elements/Popup/BottomPopup'; +// import Image from 'next/image'; +// import ImageBanner from '~/components/ui/image'; +// import { ChevronRightIcon } from '@heroicons/react/24/outline'; +// import MobileView from '@/core/components/views/MobileView'; +// import DesktopView from '@/core/components/views/DesktopView'; +// import getFileBase64 from '@/core/utils/getFileBase64'; +// import odooApi from '~/libs/odooApi'; +// const Dokumen = forwardRef( +// ({ handleIsError, isKonfirmasi, buttonSubmitClick }, ref) => { +// const isError = (value) => { +// // Logika menentukan error +// const result = value ? true : false; +// handleIsError(result); // Panggil handleIsError dari Merchant +// return result; +// }; +// const { +// register, +// handleSubmit, +// formState: { errors }, +// control, +// reset, +// watch, +// setValue, +// getValues, +// } = useForm({ +// resolver: yupResolver(validationSchema), +// defaultValues, +// }); + +// const list_unit = [ +// { +// value: 'Manufacturing', +// label: 'Manufacturing', +// }, +// { +// value: 'Hospitality', +// label: 'Hospitality', +// }, +// { +// value: 'Automotive', +// label: 'Automotive', +// }, +// { +// value: 'Retail', +// label: 'Retail', +// }, +// { +// value: 'Maining', +// label: 'Maining', +// }, +// { +// value: 'Lain - Lain', +// label: 'Lain - Lain', +// }, +// ]; +// const [fileNames, setFileNames] = useState({}); +// const [DeatailFile, setDetailFile] = useState({}); +// const [isExample, setIsExample] = useState(false); +// const [isPkp, setIsPkp] = useState(false); + +// const npwpRef = useRef(null); +// const sppkpRef = useRef(null); +// const ktpDirutRef = useRef(null); +// const kartuNamaRef = useRef(null); +// const suratPernyataanRef = useRef(null); +// const fotoKantorRef = useRef(null); +// const dataProdukRef = useRef(null); +// const pricelistRef = useRef(null); +// const router = useRouter(); + +// const auth = useAuth(); +// if (auth == false) { +// router.push(`/login?next=${encodeURIComponent('/daftar-merchant')}`); +// } +// useEffect(() => { +// const loadData = async () => { +// try { +// const data = await getMerchantApi(); +// if (data) { +// setFileNames((prev) => ({ +// ...prev, +// ['npwp']: data.fileNpwp ? data.fileNpwp.name : '', +// ['sppkp']: data.fileSppkp ? data.fileSppkp.name : '', +// ['dokumenKtpDirut']: data.fileDokumenKtpDirut +// ? data.fileDokumenKtpDirut.name +// : '', +// ['kartuNama']: data.fileKartuNama ? data.fileKartuNama.name : '', +// ['suratPernyataan']: data.fileSuratPernyataan +// ? data.fileSuratPernyataan.name +// : '', +// ['fotoKantor']: data.fileFotoKantor +// ? data.fileFotoKantor.name +// : '', +// ['dataProduk']: data.fileDataProduk +// ? data.fileDataProduk.name +// : '', +// ['pricelist']: data.filePricelist ? data.filePricelist.name : '', +// })); +// } +// } catch (error) { +// console.error('Error loading profile:', error); +// handleIsError(true); // Jika ada error, panggil fungsi error handler +// } +// }; + +// loadData(); +// }, [reset, handleIsError]); + +// useEffect(() => { +// if (!isKonfirmasi) { +// window.scrollTo({ +// top: 0, +// behavior: 'smooth', +// }); +// } +// }, []); +// useImperativeHandle(ref, () => () => { +// handleSubmit(onSubmitHandler)(); +// }); +// useEffect(() => { +// const loadProfile = async () => { +// try { +// const dataProfile = await addressApi({ +// id: auth.parentId ? auth.parentId : auth.partnerId, +// }); +// if (dataProfile.companyType == 'pkp') { +// setIsPkp(true); +// } +// setValue('company', dataProfile?.name); +// setValue('address', dataProfile?.alamatBisnis); +// setValue('state', parseInt(dataProfile.stateId.id)); +// setValue('city', parseInt(dataProfile.city.id)); +// setValue('district', parseInt(dataProfile.district.id)); +// setValue('subDistrict', parseInt(dataProfile.subDistrict.id)); +// setValue('zip', parseInt(dataProfile.zip)); +// } catch (error) { +// console.error('Error loading profile:', error); +// } +// }; + +// loadProfile(); +// }, [auth?.parentId]); + +// const onSubmitHandler = async (values) => { +// const options = { +// behavior: 'smooth', +// block: 'center', +// }; +// const npwp = { name: fileNames.npwp, format: DeatailFile.npwp }; +// const sppkp = { name: fileNames.sppkp, format: DeatailFile.sppkp }; +// const dokumenKtpDirut = { +// name: fileNames.dokumenKtpDirut, +// format: DeatailFile.dokumenKtpDirut, +// }; +// const kartuNama = { +// name: fileNames.kartuNama, +// format: DeatailFile.kartuNama, +// }; +// const suratPernyataan = { +// name: fileNames.suratPernyataan, +// format: DeatailFile.suratPernyataan, +// }; +// const fotoKantor = { +// name: fileNames.fotoKantor, +// format: DeatailFile.fotoKantor, +// }; +// const dataProduk = { +// name: fileNames.dataProduk, +// format: DeatailFile.dataProduk, +// }; +// const pricelist = { +// name: fileNames.pricelist, +// format: DeatailFile.pricelist, +// }; + +// if (!npwp.name && isPkp) { +// if (npwpRef?.current) { +// npwpRef.current.scrollIntoView(options); +// } +// toast.error('NPWP wajib di tambahkan'); +// return; +// } +// if (!sppkp.name && isPkp) { +// toast.error('SPPKP wajib di tambahkan'); +// if (sppkpRef?.current) { +// sppkpRef.current.scrollIntoView(options); +// } +// return; +// } +// if (!dokumenKtpDirut.name && !isPkp) { +// toast.error('KTP Dirut/Direktur wajib di tambahkan'); +// if (ktpDirutRef?.current) { +// ktpDirutRef.current.scrollIntoView(options); +// } +// return; +// } +// if (!fotoKantor.name) { +// toast.error('Foto Gudang / Kantor Bagian Depan wajib di tambahkan'); +// if (fotoKantorRef?.current) { +// fotoKantorRef.current.scrollIntoView(options); +// } +// return; +// } +// if (!pricelist.name) { +// toast.error('Pricelist wajib di tambahkan'); +// if (pricelistRef?.current) { +// pricelistRef.current.scrollIntoView(options); +// } +// return; +// } +// const toastId = toast.loading('Mengirimkan formulir merchant...'); +// const dokumen = { +// file_npwp: { details: npwp.format ? npwp : '' }, +// file_sppkp: { details: sppkp.format ? sppkp : '' }, +// file_dokumenKtpDirut: { +// details: dokumenKtpDirut.format ? dokumenKtpDirut : '', +// }, +// file_kartuNama: { details: kartuNama.format ? kartuNama : '' }, +// file_suratPernyataan: { +// details: suratPernyataan.format ? suratPernyataan : '', +// }, +// file_fotoKantor: { details: fotoKantor.format ? fotoKantor : '' }, +// file_dataProduk: { details: dataProduk.format ? dataProduk : '' }, +// file_pricelist: { details: pricelist.format ? pricelist : '' }, +// }; +// let data = { +// file_dokumen: JSON.stringify(dokumen), +// }; +// const create_leads = await createMerchantApi({ data }); +// if (create_leads) { +// toast.dismiss(toastId); +// toast.success('Berhasil menambahkan data'); +// isError(false); +// reset(); +// } else { +// toast.dismiss(toastId); +// toast.error('Gagal menambahkan data'); +// } +// }; + +// if (!auth) { +// return; +// } + +// const handleFileChange = async (event) => { +// let fileBase64 = ''; +// const file = event.target.files[0]; + +// 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); +// // } catch (error) { +// // toast.error('Gagal mengompresi file', { duration: 4000 }); +// // } +// toast.error('Maks file size 2MB', { duration: 4000 }); +// } else { +// // Convert file to Base64 +// fileBase64 = await getFileBase64(file); +// } +// const fieldName = event.target.name; // Nama input file +// setDetailFile((prev) => ({ +// ...prev, +// [fieldName]: file ? fileBase64 : '', // Tambahkan atau perbarui file di state +// })); +// setFileNames((prev) => ({ +// ...prev, +// [fieldName]: file ? file.name : '', // Tambahkan atau perbarui file di state +// })); +// }; +// return ( +// <> +// <BottomPopup +// className='' +// title='Contoh SPPKP' +// active={isExample} +// close={() => setIsExample(false)} +// > +// <div className='flex p-2'> +// <Image +// src='/images/NO-SPPKP-FORMAT-TEMPLATE.jpg' +// alt='Contoh SPPKP' +// className='w-full h-full ' +// width={800} +// height={800} +// quality={85} +// /> +// </div> +// </BottomPopup> +// <DesktopView> +// <div className='container flex flex-col items-star py-4 '> +// <h2 className='text-xs md:text-title-sm font-semibold mb-6'> +// Dokumen +// </h2> + +// <div className='w-full mt-4'> +// <form +// onSubmit={handleSubmit(onSubmitHandler)} +// className='flex flex-col gap-4' +// > +// <div className='w-full flex flex-row'> +// <div className='w-2/5'> +// <label className='form-label text-nowrap'> +// NPWP{' '} +// {!isPkp && ( +// <span className=' opacity-60'>(Opsional)</span> +// )} +// </label> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// Pastikan dokumen yang anda upload sudah benar +// </span> +// )} +// </div> +// <div +// className={`w-3/5 flex flex-col justify-between ${ +// isKonfirmasi ? 'items-end' : 'items-start' +// }`} +// ref={npwpRef} +// > +// <div +// className={`flex flex-row items-start gap-2 w-full ${ +// isKonfirmasi ? 'justify-end' : 'justify-start' +// }`} +// > +// {isKonfirmasi && ( +// <span className='mt-2 text-gray-600 line-clamp-2'> +// {fileNames.npwp} +// </span> +// )} +// <label +// htmlFor='npwp' +// className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' +// > +// {fileNames.npwp ? 'Ubah Dokumen' : 'Upload Dokumen'} +// </label> +// <input +// {...register('npwp')} +// type='file' +// className='form-input hidden' +// accept='.pdf,.png,.jpg,.jpeg' +// id='npwp' +// onChange={(e) => { +// handleFileChange(e); // Untuk update UI (opsional) +// }} +// aria-invalid={errors.npwp?.message} +// /> +// {!isKonfirmasi && ( +// <span className='mt-2 text-gray-600 line-clamp-2'> +// {fileNames.npwp} +// </span> +// )} +// </div> +// {!isKonfirmasi && ( +// <span className='text-xs opacity-60 text-red-500'> +// Format: pdf, jpeg, jpg, png. max file size 2MB +// </span> +// )} + +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.npwp?.message} +// </div> +// </div> +// </div> +// <div +// className={`w-full flex flex-row items-start ${ +// isKonfirmasi && 'gap-2' +// } `} +// > +// <div className='w-2/5 flex flex-row justify-between items-center '> +// <div> +// <label className='form-label text-nowrap' ref={sppkpRef}> +// SPPKP{' '} +// {!isPkp && ( +// <span className=' opacity-60'>(Opsional)</span> +// )} +// </label> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// Pastikan dokumen yang anda upload sudah benar +// </span> +// )} +// </div> +// <div +// onClick={() => setIsExample(!isExample)} +// className={`h-fit ${ +// !isKonfirmasi && 'mr-8' +// } rounded text-white p-2 flex flex-row items-center bg-red-500 hover:cursor-pointer hover:bg-red-400`} +// > +// <EyeIcon +// className={`${isKonfirmasi ? 'w-4' : 'w-4 mr-2'} `} +// /> +// {!isKonfirmasi && ( +// <p className='font-light text-xs '>Lihat Contoh</p> +// )} +// </div> +// </div> +// <div +// className={`w-3/5 flex flex-col justify-between ${ +// isKonfirmasi ? 'items-end' : 'items-start' +// }`} +// > +// <div className='flex flex-row items-start justify-start gap-2'> +// {isKonfirmasi && ( +// <span className='mt-2 text-gray-600 line-clamp-2 '> +// {fileNames.sppkp} +// </span> +// )} +// <label +// htmlFor='sppkp' +// className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' +// > +// {fileNames.sppkp ? 'Ubah Dokumen' : 'Upload Dokumen'} +// </label> +// <input +// {...register('sppkp')} +// type='file' +// className='form-input hidden' +// accept='.pdf,.png,.jpg,.jpeg' +// id='sppkp' +// onChange={handleFileChange} +// aria-invalid={errors.sppkp?.message} +// /> +// </div> +// {!isKonfirmasi && ( +// <span className='text-xs opacity-60 text-red-500'> +// Format: pdf, jpeg, jpg, png. max file size 2MB +// </span> +// )} + +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.sppkp?.message} +// </div> +// </div> +// </div> + +// <div className='w-full flex flex-row'> +// <div className='w-2/5'> +// <label className='form-label text-nowrap'> +// KTP Dirut/Direktur{' '} +// {isPkp && <span className=' opacity-60'>(Opsional)</span>} +// </label> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// Pastikan dokumen yang anda upload sudah benar +// </span> +// )} +// </div> +// <div +// className={`w-3/5 flex flex-col justify-between ${ +// isKonfirmasi ? 'items-end' : 'items-start' +// }`} +// ref={ktpDirutRef} +// > +// <div +// className={`flex flex-row items-start ${ +// isKonfirmasi ? 'justify-end' : 'justify-start' +// } gap-2 w-full`} +// > +// {isKonfirmasi && ( +// <span className='mt-2 text-gray-600 line-clamp-2'> +// {fileNames.dokumenKtpDirut} +// </span> +// )} +// <label +// htmlFor='dokumenKtpDirut' +// className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' +// > +// {fileNames.dokumenKtpDirut +// ? 'Ubah Dokumen' +// : 'Upload Dokumen'} +// </label> +// <input +// {...register('dokumenKtpDirut')} +// type='file' +// className='form-input hidden' +// accept='.pdf,.png,.jpg,.jpeg' +// id='dokumenKtpDirut' +// onChange={handleFileChange} +// aria-invalid={errors.dokumenKtpDirut?.message} +// /> +// {!isKonfirmasi && ( +// <span className='mt-2 text-gray-600 line-clamp-2'> +// {fileNames.dokumenKtpDirut} +// </span> +// )} +// </div> +// {!isKonfirmasi && ( +// <span className='text-xs opacity-60 text-red-500'> +// Format: pdf, jpeg, jpg, png. max file size 2MB +// </span> +// )} + +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.dokumenKtpDirut?.message} +// </div> +// </div> +// </div> + +// <div className='w-full flex flex-row'> +// <div className='w-2/5'> +// <label +// className='form-label text-nowrap' +// ref={kartuNamaRef} +// > +// Kartu Nama <span className=' opacity-60'>(Opsional)</span>{' '} +// </label> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// Pastikan dokumen yang anda upload sudah benar +// </span> +// )} +// </div> +// <div +// className={`w-3/5 flex flex-col justify-between ${ +// isKonfirmasi ? 'items-end' : 'items-start' +// }`} +// > +// <div className='flex flex-row items-start justify-start gap-2'> +// {isKonfirmasi && ( +// <span className='mt-2 text-gray-600 line-clamp-2'> +// {fileNames.kartuNama} +// </span> +// )} +// <label +// htmlFor='kartuNama' +// className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' +// > +// {fileNames.kartuNama +// ? 'Ubah Dokumen' +// : 'Upload Dokumen'} +// </label> +// <input +// {...register('kartuNama')} +// type='file' +// className='form-input hidden' +// accept='.pdf,.png,.jpg,.jpeg' +// id='kartuNama' +// onChange={handleFileChange} +// aria-invalid={errors.kartuNama?.message} +// /> +// {!isKonfirmasi && ( +// <span className='mt-2 text-gray-600 line-clamp-2'> +// {fileNames.kartuNama} +// </span> +// )} +// </div> +// {!isKonfirmasi && ( +// <span className='text-xs opacity-60 text-red-500'> +// Format: pdf, jpeg, jpg, png. max file size 2MB +// </span> +// )} + +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.kartuNama?.message} +// </div> +// </div> +// </div> + +// <div className='w-full flex flex-row items-start '> +// <div className='w-2/5 flex flex-row justify-between items-center '> +// <div> +// <label +// className={`form-label ${ +// isKonfirmasi ? 'text-wrap' : ' text-nowrap' +// }`} +// ref={suratPernyataanRef} +// > +// Surat Pernyataan Nomor Rekening{' '} +// <span className=' opacity-60'>(Opsional)</span> +// </label> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// Wajib diisi jika nomor rekening berbeda dengan nama +// perusahaan +// </span> +// )} +// </div> +// <a +// href='/file/Surat Pernyataan Nomor Rekening.docx' +// download='Surat Pernyataan Nomor Rekening.docx' +// className='h-fit mr-8 rounded text-white p-2 flex flex-row items-center bg-red-500 hover:cursor-pointer hover:bg-red-400' +// > +// <p className='font-light text-xs'>Download Template</p> +// </a> +// </div> +// <div +// className={`w-3/5 flex flex-col justify-between ${ +// isKonfirmasi ? 'items-end' : 'items-start' +// }`} +// > +// <div className='flex flex-row items-start justify-start gap-2'> +// {isKonfirmasi && ( +// <span className='mt-2 text-gray-600 line-clamp-2'> +// {fileNames.suratPernyataan} +// </span> +// )} +// <label +// htmlFor='suratPernyataan' +// className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' +// > +// {fileNames.suratPernyataan +// ? 'Ubah Dokumen' +// : 'Upload Dokumen'} +// </label> +// <input +// {...register('suratPernyataan')} +// type='file' +// className='form-input hidden' +// accept='.pdf,.png,.jpg,.jpeg' +// id='suratPernyataan' +// onChange={handleFileChange} +// aria-invalid={errors.suratPernyataan?.message} +// /> +// {!isKonfirmasi && ( +// <span className='mt-2 text-gray-600 line-clamp-2'> +// {fileNames.suratPernyataan} +// </span> +// )} +// </div> +// {!isKonfirmasi && ( +// <span className='text-xs opacity-60 text-red-500'> +// Format: pdf, jpeg, jpg, png. max file size 2MB +// </span> +// )} + +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.suratPernyataan?.message} +// </div> +// </div> +// </div> +// <div className='w-full flex flex-row items-start '> +// <div className='w-2/5 flex flex-col justify-start items-start '> +// <label +// className={`form-label ${ +// isKonfirmasi ? 'text-wrap' : 'text-nowrap' +// }`} +// ref={fotoKantorRef} +// > +// Foto Gudang / Kantor Bagian Depan +// </label> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// Pastikan dokumen yang anda upload sudah benar +// </span> +// )} +// </div> +// <div +// className={`w-3/5 flex flex-col justify-between ${ +// isKonfirmasi ? 'items-end' : 'items-start' +// }`} +// > +// <div className='flex flex-row items-start justify-start gap-2'> +// {isKonfirmasi && ( +// <span className='mt-2 text-gray-600 line-clamp-2'> +// {fileNames.fotoKantor} +// </span> +// )} +// <label +// htmlFor='fotoKantor' +// className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' +// > +// {fileNames.fotoKantor +// ? 'Ubah Dokumen' +// : 'Upload Dokumen'} +// </label> +// <input +// {...register('fotoKantor')} +// type='file' +// className='form-input hidden' +// accept='.pdf,.png,.jpg,.jpeg' +// id='fotoKantor' +// onChange={handleFileChange} +// aria-invalid={errors.fotoKantor?.message} +// /> +// {!isKonfirmasi && ( +// <span className='mt-2 text-gray-600 line-clamp-2'> +// {fileNames.fotoKantor} +// </span> +// )} +// </div> +// {!isKonfirmasi && ( +// <span className='text-xs opacity-60 text-red-500'> +// Format: pdf, jpeg, jpg, png. max file size 2MB +// </span> +// )} +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.fotoKantor?.message} +// </div> +// </div> +// </div> +// <div className='w-full flex flex-row items-start '> +// <div className='w-2/5 flex flex-col justify-start items-start '> +// <label +// className={`form-label ${ +// isKonfirmasi ? 'text-wrap' : 'text-nowrap' +// }`} +// ref={dataProdukRef} +// > +// Data Produk (Item Name, Gambar, Deskripsi){' '} +// <span className=' opacity-60'>(Opsional)</span>{' '} +// </label> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// Pastikan dokumen yang anda upload sudah benar +// </span> +// )} +// </div> +// <div +// className={`w-3/5 flex flex-col justify-between ${ +// isKonfirmasi ? 'items-end' : 'items-start' +// }`} +// > +// <div className='flex flex-row items-start justify-start gap-2'> +// {isKonfirmasi && ( +// <span className='mt-2 text-gray-600 line-clamp-2'> +// {fileNames.dataProduk} +// </span> +// )} +// <label +// htmlFor='dataProduk' +// className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' +// > +// {fileNames.dataProduk +// ? 'Ubah Dokumen' +// : 'Upload Dokumen'} +// </label> +// <input +// {...register('dataProduk')} +// type='file' +// className='form-input hidden' +// accept='.pdf,.png,.jpg,.jpeg' +// id='dataProduk' +// onChange={handleFileChange} +// aria-invalid={errors.dataProduk?.message} +// /> +// {!isKonfirmasi && ( +// <span className='mt-2 text-gray-600 line-clamp-2'> +// {fileNames.dataProduk} +// </span> +// )} +// </div> +// {!isKonfirmasi && ( +// <span className='text-xs opacity-60 text-red-500'> +// Format: pdf, jpeg, jpg, png. max file size 2MB +// </span> +// )} + +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.dataProduk?.message} +// </div> +// </div> +// </div> +// <div className='w-full flex flex-row items-start '> +// <div className='w-2/5 flex flex-col justify-start items-start '> +// <label +// className='form-label text-nowrap' +// ref={pricelistRef} +// > +// Pricelist +// </label> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// Pastikan dokumen yang anda upload sudah benar +// </span> +// )} +// </div> +// <div +// className={`w-3/5 flex flex-col justify-between ${ +// isKonfirmasi ? 'items-end' : 'items-start' +// }`} +// > +// <div className='flex flex-row items-start justify-start gap-2'> +// {isKonfirmasi && ( +// <span className='mt-2 text-gray-600 line-clamp-2'> +// {fileNames.pricelist} +// </span> +// )} +// <label +// htmlFor='pricelist' +// className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' +// > +// {fileNames.pricelist +// ? 'Ubah Dokumen' +// : 'Upload Dokumen'} +// </label> +// <input +// {...register('pricelist')} +// type='file' +// className='form-input hidden' +// accept='.pdf,.png,.jpg,.jpeg' +// id='pricelist' +// onChange={handleFileChange} +// aria-invalid={errors.pricelist?.message} +// /> +// {!isKonfirmasi && ( +// <span className='mt-2 text-gray-600 line-clamp-2'> +// {fileNames.pricelist} +// </span> +// )} +// </div> +// {!isKonfirmasi && ( +// <span className='text-xs opacity-60 text-red-500'> +// Format: pdf, jpeg, jpg, png. max file size 2MB +// </span> +// )} +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.pricelist?.message} +// </div> +// </div> +// </div> +// <div className=''> +// {/* <div> +// <ReCAPTCHA +// ref={recaptchaRef} +// sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_GOOGLE} +// /> +// </div> */} +// </div> +// <div className='flex justify-end'> +// {/* <Button +// colorScheme='red' +// className='w-full md:w-fit' +// type='submit' +// > +// Daftar Merchant{' '} +// {<ChevronRightIcon className='w-5' color='white' />} +// </Button> */} +// {!isKonfirmasi && ( +// <div> +// <span className='text-xs opacity-60'> +// *Pastikan data yang anda masukan sudah benar dan sesuai +// </span> +// <button +// type='submit' +// className='btn-light bg-red-500 rounded-lg text-white w-fit py-2 px-4 md:w-fit mt-2 ml-0 md:ml-auto flex justify-between hover:bg-red-400' +// > +// <span className={` `}>Langkah Selanjutnya</span> +// {<ChevronRightIcon className='w-5' />} +// </button> +// </div> +// )} +// </div> +// </form> +// <PageContent path='/daftar-merchant' /> +// </div> +// </div> +// </DesktopView> +// <MobileView> +// <div className='container flex flex-col items-star py-4'> +// {!isKonfirmasi && ( +// <h2 className='font-semibold mb-6 text-xl'>Dokumen</h2> +// )} + +// <div className='w-full mt-4'> +// <form +// onSubmit={handleSubmit(onSubmitHandler)} +// className='flex flex-col gap-4' +// > +// <div className='w-full flex flex-col gap-2'> +// <label className='form-label text-nowrap'> +// NPWP{' '} +// {!isPkp && <span className=' opacity-60'>(Opsional)</span>} +// </label> +// <div className='flex flex-row items-center justify-start gap-2'> +// <label +// htmlFor='npwp' +// className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' +// > +// {fileNames.npwp ? 'Ubah Dokumen' : 'Upload Dokumen'} +// </label> +// <input +// {...register('npwp')} +// type='file' +// className='form-input hidden' +// accept='.pdf,.png,.jpg,.jpeg' +// id='npwp' +// onChange={(e) => { +// handleFileChange(e); // Untuk update UI (opsional) +// }} +// aria-invalid={errors.npwp?.message} +// /> +// <span className=' text-gray-600 line-clamp-2'> +// {fileNames.npwp} +// </span> +// </div> +// <span className='text-xs opacity-60 text-red-500'> +// Format: pdf, jpeg, jpg, png. max file size 2MB +// </span> + +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.npwp?.message} +// </div> +// </div> +// <div className='w-full flex flex-col gap-2 items-start '> +// <div className='flex flex-row w-full justify-between items-center '> +// <label className='form-label text-nowrap'> +// SPPKP{' '} +// {!isPkp && ( +// <span className=' opacity-60'>(Opsional)</span> +// )} +// </label> +// <div +// onClick={() => setIsExample(!isExample)} +// className='h-fit rounded text-white p-2 flex flex-row items-center bg-red-500 hover:cursor-pointer hover:bg-red-400' +// > +// <EyeIcon className={`w-4 mr-2 `} /> + +// <p className='font-light text-xs '>Lihat Contoh</p> +// </div> +// </div> +// <div className='flex flex-row items-center justify-start gap-2'> +// <label +// htmlFor='sppkp' +// className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' +// > +// {fileNames.sppkp ? 'Ubah Dokumen' : 'Upload Dokumen'} +// </label> +// <input +// {...register('sppkp')} +// type='file' +// className='form-input hidden' +// accept='.pdf,.png,.jpg,.jpeg' +// id='sppkp' +// onChange={handleFileChange} +// aria-invalid={errors.sppkp?.message} +// /> +// <span className=' text-gray-600 line-clamp-2'> +// {fileNames.sppkp} +// </span> +// </div> +// <span className='text-xs opacity-60 text-red-500'> +// Format: pdf, jpeg, jpg, png. max file size 2MB +// </span> + +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.sppkp?.message} +// </div> +// </div> + +// <div className='w-full flex flex-col gap-2'> +// <label className='form-label text-nowrap'> +// KTP Dirut/Direktur{' '} +// {isPkp && <span className=' opacity-60'>(Opsional)</span>} +// </label> +// <div className='flex flex-row items-center justify-start gap-2 '> +// <label +// htmlFor='dokumenKtpDirut' +// className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' +// > +// {fileNames.dokumenKtpDirut +// ? 'Ubah Dokumen' +// : 'Upload Dokumen'} +// </label> +// <input +// {...register('dokumenKtpDirut')} +// type='file' +// className='form-input hidden' +// accept='.pdf,.png,.jpg,.jpeg' +// id='dokumenKtpDirut' +// onChange={handleFileChange} +// aria-invalid={errors.dokumenKtpDirut?.message} +// /> +// <span className=' text-gray-600 line-clamp-2'> +// {fileNames.dokumenKtpDirut} +// </span> +// </div> +// <span className='text-xs opacity-60 text-red-500'> +// Format: pdf, jpeg, jpg, png. max file size 2MB +// </span> + +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.dokumenKtpDirut?.message} +// </div> +// </div> + +// <div className='w-full flex flex-col gap-2'> +// <label className='form-label text-nowrap'> +// Kartu Nama <span className=' opacity-60'>(Opsional)</span>{' '} +// </label> +// <div className='flex flex-row items-center justify-start gap-2'> +// <label +// htmlFor='kartuNama' +// className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' +// > +// {fileNames.kartuNama ? 'Ubah Dokumen' : 'Upload Dokumen'} +// </label> +// <input +// {...register('kartuNama')} +// type='file' +// className='form-input hidden' +// accept='.pdf,.png,.jpg,.jpeg' +// id='kartuNama' +// onChange={handleFileChange} +// aria-invalid={errors.kartuNama?.message} +// /> +// <span className=' text-gray-600 line-clamp-2'> +// {fileNames.kartuNama} +// </span> +// </div> +// <span className='text-xs opacity-60 text-red-500'> +// Format: pdf, jpeg, jpg, png. max file size 2MB +// </span> + +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.kartuNama?.message} +// </div> +// </div> + +// <div className='w-full flex flex-col gap-2 items-start '> +// <div className='flex flex-row w-full justify-between items-center'> +// <label className='form-label text-wrap'> +// Surat Pernyataan Nomor Rekening{' '} +// <span className=' opacity-60'>(Opsional)</span> +// </label> +// <a +// href='/file/Surat Pernyataan Nomor Rekening.docx' +// download='Surat Pernyataan Nomor Rekening.docx' +// className='h-fit rounded text-white p-2 flex flex-row items-center bg-red-500 hover:cursor-pointer hover:bg-red-400' +// > +// <p className='font-light text-xs text-nowrap'> +// Download Template +// </p> +// </a> +// </div> +// <div className='flex flex-row items-center justify-start gap-2'> +// <label +// htmlFor='suratPernyataan' +// className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' +// > +// {fileNames.suratPernyataan +// ? 'Ubah Dokumen' +// : 'Upload Dokumen'} +// </label> +// <input +// {...register('suratPernyataan')} +// type='file' +// className='form-input hidden' +// accept='.pdf,.png,.jpg,.jpeg' +// id='suratPernyataan' +// onChange={handleFileChange} +// aria-invalid={errors.suratPernyataan?.message} +// /> +// <span className=' text-gray-600 line-clamp-2'> +// {fileNames.suratPernyataan} +// </span> +// </div> +// <span className='text-xs opacity-60 text-red-500'> +// Format: pdf, jpeg, jpg, png. max file size 2MB +// </span> + +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.suratPernyataan?.message} +// </div> +// </div> +// <div className='w-full flex flex-col gap-2 items-start '> +// <label className='form-label text-nowrap'> +// Foto Gudang / Kantor Bagian Depan +// </label> +// <div className='flex flex-row items-center justify-start gap-2 '> +// <label +// htmlFor='fotoKantor' +// className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' +// > +// {fileNames.fotoKantor ? 'Ubah Dokumen' : 'Upload Dokumen'} +// </label> +// <input +// {...register('fotoKantor')} +// type='file' +// className='form-input hidden' +// accept='.pdf,.png,.jpg,.jpeg' +// id='fotoKantor' +// onChange={handleFileChange} +// aria-invalid={errors.fotoKantor?.message} +// /> +// <span className=' text-gray-600 line-clamp-2'> +// {fileNames.fotoKantor} +// </span> +// </div> +// <span className='text-xs opacity-60 text-red-500'> +// Format: pdf, jpeg, jpg, png. max file size 2MB +// </span> + +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.fotoKantor?.message} +// </div> +// </div> +// <div className='w-full flex flex-col gap-2 items-start '> +// <label className='form-label text-wrap'> +// Data Produk (Item Name, Gambar, Deskripsi){' '} +// <span className=' opacity-60'>(Opsional)</span>{' '} +// </label> +// <div className='flex flex-row items-center justify-start gap-2'> +// <label +// htmlFor='dataProduk' +// className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' +// > +// {fileNames.dataProduk ? 'Ubah Dokumen' : 'Upload Dokumen'} +// </label> +// <input +// {...register('dataProduk')} +// type='file' +// className='form-input hidden' +// accept='.pdf,.png,.jpg,.jpeg' +// id='dataProduk' +// onChange={handleFileChange} +// aria-invalid={errors.dataProduk?.message} +// /> +// <span className=' text-gray-600 line-clamp-2'> +// {fileNames.dataProduk} +// </span> +// </div> +// <span className='text-xs opacity-60 text-red-500'> +// Format: pdf, jpeg, jpg, png. max file size 2MB +// </span> + +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.fotoKantor?.message} +// </div> +// </div> +// <div className='w-full flex flex-col gap-2 items-start '> +// <label className='form-label text-nowrap'>Pricelist</label> +// <div className='flex flex-row items-center justify-start gap-2'> +// <label +// htmlFor='pricelist' +// className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' +// > +// {fileNames.pricelist ? 'Ubah Dokumen' : 'Upload Dokumen'} +// </label> +// <input +// {...register('pricelist')} +// type='file' +// className='form-input hidden' +// accept='.pdf,.png,.jpg,.jpeg' +// id='pricelist' +// onChange={handleFileChange} +// aria-invalid={errors.pricelist?.message} +// /> +// <span className=' text-gray-600 line-clamp-2'> +// {fileNames.pricelist} +// </span> +// </div> +// <span className='text-xs opacity-60 text-red-500'> +// Format: pdf, jpeg, jpg, png. max file size 2MB +// </span> + +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.pricelist?.message} +// </div> +// </div> +// <div className=''> +// {/* <div> +// <ReCAPTCHA +// ref={recaptchaRef} +// sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_GOOGLE} +// /> +// </div> */} +// </div> +// <div className='flex justify-center w-full '> +// {/* <Button +// colorScheme='red' +// className='w-full md:w-fit' +// type='submit' +// > +// Daftar Merchant{' '} +// {<ChevronRightIcon className='w-5' color='white' />} +// </Button> */} +// {!isKonfirmasi && ( +// <div className='w-full'> +// <span className='text-xs opacity-60'> +// *Pastikan data yang anda masukan sudah benar dan sesuai +// </span> +// <button +// type='submit' +// className='btn-light bg-red-500 rounded-lg text-white w-full py-2 px-4 md:w-fit mt-2 ml-0 md:ml-auto flex justify-center hover:bg-red-400' +// > +// <span className={` `}>Langkah Selanjutnya</span> +// {<ChevronRightIcon className='w-5' />} +// </button> +// </div> +// )} +// </div> +// </form> +// <PageContent path='/daftar-merchant' /> +// </div> +// </div> +// </MobileView> +// </> +// ); +// } +// ); +// const validationSchema = Yup.object().shape({ +// npwp: Yup.mixed().required('File is required'), +// pricelist: Yup.mixed().required('File is required'), +// }); +// const defaultValues = { +// company: '', +// pejabatName: '', +// PICName: '', +// PICPosition: '', +// email: '', +// emailSales: '', +// emailFinance: '', +// phone: '', +// state: '', +// city: '', +// district: '', +// subDistrict: '', +// zip: '', +// bank: '', +// rekening: '', +// accountNumber: '', +// address: '', +// mobile: '', +// }; + +// export default Dokumen; diff --git a/src/lib/merchant/components/InformasiPerusahaan.jsx b/src/lib/merchant/components/InformasiPerusahaan.jsx new file mode 100644 index 00000000..e9486214 --- /dev/null +++ b/src/lib/merchant/components/InformasiPerusahaan.jsx @@ -0,0 +1,1399 @@ +// import HookFormSelect from '@/core/components/elements/Select/HookFormSelect'; +// import cityApi from '@/lib/address/api/cityApi'; +// import stateApi from '@/lib/address/api/stateApi.js'; +// import districtApi from '@/lib/address/api/districtApi'; +// import subDistrictApi from '@/lib/address/api/subDistrictApi'; +// import { yupResolver } from '@hookform/resolvers/yup'; +// import React, { +// useEffect, +// useRef, +// useState, +// forwardRef, +// useImperativeHandle, +// } from 'react'; +// import ReCAPTCHA from 'react-google-recaptcha'; +// import { Controller, useForm } from 'react-hook-form'; +// import { toast } from 'react-hot-toast'; +// import * as Yup from 'yup'; +// import createMerchantApi from '../api/createMerchantApi'; +// import getMerchantApi from '../api/getMerchantApi'; +// import addressApi from '@/lib/address/api/addressApi'; +// import PageContent from '@/lib/content/components/PageContent'; +// import { useRouter } from 'next/router'; +// import useAuth from '@/core/hooks/useAuth'; +// import { Radio, RadioGroup, Stack, Divider, Button } from '@chakra-ui/react'; +// import { EyeIcon } from '@heroicons/react/24/outline'; +// import BottomPopup from '@/core/components/elements/Popup/BottomPopup'; +// import Image from 'next/image'; +// import ImageBanner from '~/components/ui/image'; +// import { ChevronRightIcon, ChevronLeftIcon } from '@heroicons/react/24/outline'; +// import MobileView from '@/core/components/views/MobileView'; +// import DesktopView from '@/core/components/views/DesktopView'; +// import getFileBase64 from '@/core/utils/getFileBase64'; +// import odooApi from '~/libs/odooApi'; + +// const CreateMerchant = forwardRef( +// ({ handleIsError, isKonfirmasi, buttonSubmitClick }, ref) => { +// const isError = (value) => { +// // Logika menentukan error +// const result = value ? true : false; +// handleIsError(result); // Panggil handleIsError dari Merchant +// return result; +// }; +// // React.useEffect(() => { +// // handleIsError(isError()); +// // }, [handleIsError]); +// const { +// register, +// handleSubmit, +// formState: { errors }, +// control, +// reset, +// watch, +// setValue, +// getValues, +// } = useForm({ +// resolver: yupResolver(validationSchema), +// defaultValues, +// }); +// const [state, setState] = useState([]); +// const [cities, setCities] = useState([]); +// const [districts, setDistricts] = useState([]); +// const [subDistricts, setSubDistricts] = useState([]); +// const [zips, setZips] = useState([]); +// const [isExample, setIsExample] = useState(false); +// const [isPkp, setIsPkp] = useState(false); + +// useEffect(() => { +// window.scrollTo({ +// top: 0, +// behavior: 'smooth', +// }); +// }, []); + + +// useImperativeHandle(ref, () => () => { +// handleSubmit(onSubmitHandler)(); +// }); + +// const recaptchaRef = useRef(null); +// const router = useRouter(); + +// const auth = useAuth(); +// if (auth == false) { +// router.push(`/login?next=${encodeURIComponent('/daftar-merchant')}`); +// } +// const dataBisnisType = [ +// { value: 1, label: 'PT' }, +// { value: 2, label: 'CV' }, +// { value: 3, label: 'Perorangan' }, +// ]; +// const dataCategoryPerusahaan = [ +// { value: 1, label: 'Principal (Pemegang merk/Produsen)' }, +// { value: 2, label: 'Sole Distributor (Distributor Tunggal)' }, +// { value: 3, label: 'Authorized Distributor (Distributor Resmi)' }, +// { value: 4, label: 'Importer (Pengimpor Barang)' }, +// { value: 5, label: 'Wholesaler (Pedagang Besar)' }, +// ]; + +// useEffect(() => { +// const loadData = async () => { +// try { +// const data = await getMerchantApi(); +// if (data) { +// reset({ +// pejabatName: data.pejabatName ? data.pejabatName : '', +// PICName: data.picMerchant || '', +// PICPosition: data.picPosition || '', +// address: data.address ? data.address : '', +// state: data.state ? data.state : '', +// city: data.city || '', +// district: data.district || '', +// subDistrict: data.subDistrict || '', +// zip: parseInt(data.zip) || '', +// email: data.emailCompany || '', +// emailSales: data.emailSales || '', +// emailFinance: data.emailFinance || '', +// bank: data.bankName || '', +// rekening: data.rekeningName || '', +// accountNumber: data.accountNumber || '', +// phone: data.phone || '', +// mobile: data.mobile || '', +// bisnisType: data.bisnisType ? parseInt(data.bisnisType) : null, +// categoryPerusahaan: data.categoryPerusahaan +// ? parseInt(data.categoryPerusahaan) +// : null, +// website: data.website || '', +// }); +// } +// } catch (error) { +// console.error('Error loading profile:', error); +// handleIsError(true); // Jika ada error, panggil fungsi error handler +// } +// }; + +// loadData(); +// }, [reset, handleIsError]); + +// useEffect(() => { +// const loadProfile = async () => { +// try { +// const dataProfile = await addressApi({ +// id: auth.parentId ? auth.parentId : auth.partnerId, +// }); +// if (dataProfile.companyType == 'pkp') { +// setIsPkp(true); +// } +// setValue('company', dataProfile?.name); +// setValue('address', dataProfile?.alamatBisnis); +// setValue('state', parseInt(dataProfile.stateId.id)); +// setValue('city', parseInt(dataProfile.city.id)); +// setValue('district', parseInt(dataProfile.district.id)); +// setValue('subDistrict', parseInt(dataProfile.subDistrict.id)); +// setValue('zip', parseInt(dataProfile.zip)); +// } catch (error) { +// console.error('Error loading profile:', error); +// } +// }; + +// loadProfile(); +// }, [auth?.parentId]); + +// useEffect(() => { +// const loadState = async () => { +// let dataState = await stateApi({ tempo: false }); +// dataState = dataState.map((state) => ({ +// value: state.id, +// label: state.name, +// })); +// setState(dataState); +// }; +// loadState(); +// }, []); + +// const watchState = watch('state'); +// useEffect(() => { +// if (auth == false) { +// return; +// } +// if (watchState) { +// // setValue('city', ''); +// const loadCities = async () => { +// let dataCities = await cityApi({ stateId: watchState }); +// dataCities = dataCities?.map((city) => ({ +// value: city.id, +// label: city.name, +// })); +// setCities(dataCities); +// }; +// loadCities(); +// } +// }, [auth, watchState]); + +// const watchCity = watch('city'); +// useEffect(() => { +// if (watchCity) { +// // setValue('district', ''); +// const loadDistricts = async () => { +// let dataDistricts = await districtApi({ cityId: watchCity }); +// dataDistricts = dataDistricts.map((district) => ({ +// value: district.id, +// label: district.name, +// })); +// setDistricts(dataDistricts); +// }; +// loadDistricts(); +// } +// }, [watchCity]); + +// const watchDistrict = watch('district'); +// useEffect(() => { +// if (watchDistrict) { +// // setValue('subDistrict', ''); +// const loadSubDistricts = async () => { +// let dataSubDistricts = await subDistrictApi({ +// districtId: watchDistrict, +// }); +// dataSubDistricts = dataSubDistricts.map((district) => ({ +// value: district.id, +// label: district.name, +// })); +// setSubDistricts(dataSubDistricts); +// }; +// loadSubDistricts(); +// } +// }, [watchDistrict]); + +// const watchsubDistrict = watch('subDistrict'); + +// useEffect(() => { +// let kelurahan = ''; +// let kecamatan = ''; + +// if (watchDistrict) { +// // setValue('zip', ''); +// for (const data in districts) { +// if (districts[data].value == watchDistrict) { +// kecamatan = districts[data].label.toLowerCase(); +// } +// } +// } + +// if (watchsubDistrict) { +// for (const data in subDistricts) { +// if (subDistricts[data].value == watchsubDistrict) { +// kelurahan = subDistricts[data].label.toLowerCase(); +// } +// } +// const loadZip = async () => { +// const response = await fetch( +// `https://alamat.thecloudalert.com/api/cari/index/?keyword=${kelurahan}` +// ); + +// let dataMasuk = []; // Array untuk menyimpan kode pos yang sudah diproses + +// const result = await response.json(); + +// // Filter dan map data +// const dataZips = result.result +// .filter((zip) => zip.kecamatan.toLowerCase() === kecamatan) // Filter berdasarkan kecamatan +// .filter((zip) => { +// // Pastikan zip.kodepos belum ada di dataMasuk +// if (dataMasuk.includes(zip.kodepos)) { +// return false; // Jika sudah ada, maka skip (tidak akan ditambahkan) +// } else { +// dataMasuk.push(zip.kodepos); // Tambahkan ke dataMasuk +// return true; // Tambahkan zip ke hasil +// } +// }) +// .map((zip) => ({ +// value: parseInt(zip.kodepos), +// label: zip.kodepos, +// })); + +// setZips(dataZips); // Set hasil ke state +// }; + +// loadZip(); +// } +// }, [watchsubDistrict, subDistricts]); +// const onSubmitHandler = async (values) => { +// const toastId = toast.loading('Mengirimkan formulir merchant...'); +// const data = { +// name_merchant: 'Form Merchant - ' + values.company, +// pejabat_name: values.pejabatName, +// pic_merchant: values.PICName, +// pic_position: values.PICPosition, +// address: values.address, +// state: parseInt(values.state), +// city: parseInt(values.city), +// district: parseInt(values.district), +// subDistrict: parseInt(values.subDistrict), +// zip: values.zip, +// bank_name: values.bank, +// rekening_name: values.rekening, +// account_number: values.accountNumber, +// email_company: values.email, +// email_sales: values.emailSales, +// email_finance: values.emailFinance, +// phone: values.phone, +// mobile: values.mobile, +// bisnis_type: values.bisnisType, +// category_perusahaan: values.categoryPerusahaan, +// website: values.website, +// description: +// 'Nama Perusahaan : ' + +// values.company + +// ' \r\n Alamat : ' + +// values.address + +// ' \r\n Kota : ' + +// values.city + +// ' \r\n Telepon: ' + +// values.phone + +// ' \r\n Email : ' + +// values.email + +// ' \r\n No Hp : ' + +// values.mobile, +// }; +// const create_leads = await createMerchantApi({ data }); +// if (create_leads) { +// toast.dismiss(toastId); +// toast.success('Berhasil menambahkan data'); +// isError(false); +// reset(); +// // router.push('/'); +// } else { +// toast.dismiss(toastId); +// toast.error('Gagal menambahkan data'); +// } +// }; + +// // const DownLoadSurat = () => { +// // download surat dari /public/file/Surat Pernyataan Nomor Rekening.docx +// // }; + +// if (!auth) { +// return; +// } +// // Tetap di bagian atas, tidak boleh ada kondisi sebelum hook + + + +// return ( +// <> +// <BottomPopup +// className='' +// title='Contoh SPPKP' +// active={isExample} +// close={() => setIsExample(false)} +// > +// <div className='flex p-2'> +// <Image +// src='/images/NO-SPPKP-FORMAT-TEMPLATE.jpg' +// alt='Contoh SPPKP' +// className='w-full h-full ' +// width={800} +// height={800} +// quality={85} +// /> +// </div> +// </BottomPopup> +// <DesktopView> +// <div className='container flex flex-col items-star py-4 '> +// <h2 className='text-xs md:text-title-sm font-semibold mb-6'> +// Informasi Perusahaan +// </h2> + +// <div className='w-full mt-4'> +// <form +// onSubmit={handleSubmit(onSubmitHandler)} +// className='flex flex-col gap-4' +// > +// <div className='w-full flex flex-row'> +// <div className='w-2/5'> +// <label className='form-label text-nowrap'> +// Nama Perusahaan +// </label> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// Isi detail perusahaan sesuai dengan nama yang terdaftar{' '} +// </span> +// )} +// </div> +// <div className='w-3/5'> +// <input +// {...register('company')} +// placeholder='Masukkan nama perusahaan' +// type='text' +// className='form-input' +// /> +// <span className='opacity-65 text-xs'> +// Format: PT. INDOTEKNIK DOTCOM GEMILANG +// </span> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.company?.message} +// </div> +// </div> +// </div> +// <div className='w-full flex flex-row'> +// <div className='w-2/5'> +// <label className='form-label text-nowrap'> +// Pejabat Berwenang +// </label> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// isi dengan nama pejabat yang berwewenang di perusahaan +// anda +// </span> +// )} +// </div> +// <div className='w-3/5'> +// <input +// {...register('pejabatName')} +// placeholder='Masukkan nama pejabat berwenang' +// type='text' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.pejabatName?.message} +// </div> +// </div> +// </div> +// <div className='w-full flex flex-row'> +// <div className='w-2/5'> +// <label className='form-label text-nowrap'>Nama PIC</label> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// isi dengan nama sales / penanggung jawab +// </span> +// )} +// </div> +// <div className='w-3/5'> +// <input +// {...register('PICName')} +// placeholder='Masukkan nama PIC' +// type='text' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.PICName?.message} +// </div> +// </div> +// </div> +// <div className='w-full flex flex-row'> +// <div className='w-2/5'> +// <label className='form-label text-nowrap'> +// Jabatan PIC +// </label> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// isi dengan jabatan sales / penanggung jawab +// </span> +// )} +// </div> +// <div className='w-3/5'> +// <input +// {...register('PICPosition')} +// placeholder='Masukkan jabatan PIC' +// type='text' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.PICPosition?.message} +// </div> +// </div> +// </div> +// <div className='w-full flex flex-row'> +// <div className='w-2/5'> +// <label className='form-label text-nowrap'> +// Alamat Perusahaan +// </label> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// Alamat sesuai dengan alamat perusahaan +// </span> +// )} +// </div> +// <div className='w-3/5 flex flex-col'> +// <div> +// <input +// {...register('address')} +// placeholder='Masukkan alamat lengkap perusahaan' +// type='text' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.address?.message} +// </div> +// </div> +// <div +// className={` sub-alamat flex ${ +// isKonfirmasi ? 'flex-col' : 'flex-row' +// } w-full gap-3`} +// > +// <div +// className={`flex ${ +// isKonfirmasi +// ? ' flex-row gap-3 w-full' +// : 'flex-row gap-3 w-2/5' +// }`} +// > +// <div className='provinsi w-full'> +// <Controller +// name='state' +// control={control} +// render={(props) => ( +// <HookFormSelect +// {...props} +// options={state} +// placeholder='Provinsi' +// /> +// )} +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.state?.message} +// </div> +// </div> +// <div className='kab w-full'> +// <Controller +// name='city' +// control={control} +// render={(props) => ( +// <HookFormSelect +// {...props} +// options={cities} +// disabled={!watchState} +// placeholder='Kab/Kota' +// /> +// )} +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.city?.message} +// </div> +// </div> +// </div> +// <div +// className={`flex-row flex gap-2 justify-between ${ +// isKonfirmasi ? 'w-full' : 'w-3/5' +// }`} +// > +// <div className='kec w-full'> +// <Controller +// name='district' +// control={control} +// render={(props) => ( +// <HookFormSelect +// {...props} +// options={districts} +// disabled={!watchState || !watchCity} +// placeholder='Kecamatan' +// /> +// )} +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.district?.message} +// </div> +// </div> +// <div className='kel w-full'> +// <Controller +// name='subDistrict' +// control={control} +// render={(props) => ( +// <HookFormSelect +// {...props} +// options={subDistricts} +// disabled={!watchDistrict} +// placeholder='Kelurahan' +// /> +// )} +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.subDistrict?.message} +// </div> +// </div> +// <div className='zip w-full'> +// <Controller +// name='zip' +// control={control} +// render={(props) => ( +// <> +// {/* Jika zips tidak kosong, tampilkan dropdown */} +// {zips.length < 0 ? ( +// // Jika zips kosong, tampilkan input manual +// <input +// {...register('zip')} +// placeholder='Kode Pos' +// type='number' +// className='form-input' +// disabled={!watchsubDistrict} +// /> +// ) : ( +// <HookFormSelect +// {...props} +// options={zips} +// disabled={!watchsubDistrict} +// placeholder='Zip' +// /> +// )} +// </> +// )} +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.zip?.message} +// </div> +// </div> +// </div> +// </div> +// </div> +// </div> +// <div className='w-full flex flex-row'> +// <div className='w-2/5'> +// <label className='form-label text-nowrap'>Data Bank</label> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// Isi detail data bank perusahaan anda +// </span> +// )} +// </div> +// <div className='w-3/5 flex flex-row gap-2'> +// <div> +// <input +// {...register('bank')} +// placeholder='Nama bank' +// type='text' +// className='form-input' +// /> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// Format: BCA, Mandiri, CIMB, BNI dll +// </span> +// )} +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.bank?.message} +// </div> +// </div> +// <div> +// <input +// {...register('rekening')} +// placeholder='Nama Rekening' +// type='text' +// className='form-input' +// /> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// Format: John Doe +// </span> +// )} +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.rekening?.message} +// </div> +// </div> +// <div> +// <input +// {...register('accountNumber')} +// placeholder='Nomor Rekening Bank' +// type='number' +// className='form-input' +// /> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// Format: 01234567896 +// </span> +// )} +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.accountNumber?.message} +// </div> +// </div> +// </div> +// </div> +// <div className='w-full flex flex-row'> +// <div className='w-2/5'> +// <label className='form-label text-nowrap'> +// Email Perusahaan +// </label> +// </div> +// <div className='w-3/5'> +// <input +// {...register('email')} +// placeholder='contoh@email.com' +// type='email' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.email?.message} +// </div> +// </div> +// </div> +// <div className='w-full flex flex-row'> +// <div className='w-2/5'> +// <label className='form-label text-nowrap'> +// Email Sales +// </label> +// </div> +// <div className='w-3/5'> +// <input +// {...register('emailSales')} +// placeholder='contoh@email.com' +// type='email' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.emailSales?.message} +// </div> +// </div> +// </div> +// <div className='w-full flex flex-row'> +// <div className='w-2/5'> +// <label className='form-label text-nowrap'> +// Email Finance +// </label> +// </div> +// <div className='w-3/5'> +// <input +// {...register('emailFinance')} +// placeholder='contoh@email.com' +// type='email' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.emailFinance?.message} +// </div> +// </div> +// </div> +// <div className='w-full flex flex-row'> +// <div className='w-2/5'> +// <label className='form-label text-nowrap'> +// No. Telepon Perusahaan +// </label> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// Isi no telepon perusahaan yang sesuai +// </span> +// )} +// </div> +// <div className='w-3/5'> +// <input +// {...register('phone', { +// required: 'Nomor telepon wajib diisi.', +// pattern: { +// value: /^\+?[0-9]{10,15}$/, +// message: 'Nomor telepon tidak valid.', +// }, +// })} +// placeholder='Masukkan nomor telepon perusahaan' +// type='tel' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.phone?.message} +// </div> +// </div> +// </div> +// <div className='w-full flex flex-row'> +// <div className='w-2/5'> +// <label className='form-label text-nowrap'> +// No. Handphone +// </label> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// Isi no handphone perusahaan yang sesuai +// </span> +// )} +// </div> +// <div className='w-3/5'> +// <input +// {...register('mobile')} +// placeholder='Masukkan nomor handphone' +// type='tel' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.mobile?.message} +// </div> +// </div> +// </div> + +// <div className='flex flex-row justify-between items-center'> +// <div className='w-2/5'> +// <label className='form-label text-nowrap'> +// Tipe Bisnis +// </label> +// {!isKonfirmasi && ( +// <span className='text-xs opacity-60'> +// Pilih tipe bisnis yang sesuai +// </span> +// )} +// </div> +// <div className='w-3/5 flex flex-col '> +// <div className='flex flex-row items-center gap-3'> +// <div +// // className={`${isKonfirmasi ? 'w-[75%]' : 'w-[25%]'}`} +// // ref={tempoDurationRef} +// > +// <Controller +// name='bisnisType' +// control={control} +// render={(props) => ( +// <HookFormSelect +// {...props} +// options={dataBisnisType} +// placeholder={'Pilih tipe bisnis'} +// /> +// )} +// /> + +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.bisnisType?.message} +// </div> +// </div> +// </div> +// </div> +// </div> +// <div className='flex flex-row justify-between items-center'> +// <div className='w-2/5'> +// <label className='form-label text-nowrap'> +// Kategori Perusahaan +// </label> +// {!isKonfirmasi && ( +// <span className='text-xs opacity-60'> +// Pilih kategori perusahaan yang sesuai +// </span> +// )} +// </div> +// <div className='w-3/5 flex flex-col '> +// <div className='flex flex-row items-center gap-3'> +// <div +// // className={`${isKonfirmasi ? 'w-[75%]' : 'w-[25%]'}`} +// className='w-[55%]' +// // ref={tempoDurationRef} +// > +// <Controller +// name='categoryPerusahaan' +// control={control} +// render={(props) => ( +// <HookFormSelect +// {...props} +// options={dataCategoryPerusahaan} +// placeholder={'Pilih category perusahaan'} +// /> +// )} +// /> + +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.categoryPerusahaan?.message} +// </div> +// </div> +// </div> +// </div> +// </div> + +// <div className='w-full flex flex-row'> +// <div className='w-2/5'> +// <label className='form-label text-nowrap'>Website</label> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// isi dengan website perusahaan anda +// </span> +// )} +// </div> +// <div className='w-3/5'> +// <input +// {...register('website')} +// placeholder='Masukkan website' +// type='text' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.website?.message} +// </div> +// </div> +// </div> + +// <div className=''> +// {/* <div> +// <ReCAPTCHA +// ref={recaptchaRef} +// sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_GOOGLE} +// /> +// </div> */} +// </div> +// <div className='flex justify-end'> +// {/* <Button +// colorScheme='red' +// className='w-full md:w-fit' +// type='submit' +// > +// Daftar Merchant{' '} +// {<ChevronRightIcon className='w-5' color='white' />} +// </Button> */} +// {!isKonfirmasi && ( +// <div> +// <span className='text-xs opacity-60'> +// *Pastikan data yang anda masukan sudah benar dan sesuai +// </span> +// <button +// type='submit' +// className='btn-light bg-red-500 rounded-lg text-white w-fit py-2 px-4 md:w-fit mt-2 ml-0 md:ml-auto flex justify-between hover:bg-red-400' +// > +// <span className={` `}>Langkah Selanjutnya</span> +// {<ChevronRightIcon className='w-5' />} +// </button> +// </div> +// )} +// </div> +// </form> +// <PageContent path='/daftar-merchant' /> +// </div> +// </div> +// </DesktopView> +// <MobileView> +// <div className='container flex flex-col items-star py-4'> +// {!isKonfirmasi && ( +// <h2 className='font-semibold mb-6 text-xl'> +// Informasi Perusahaan +// </h2> +// )} + +// <div className='w-full mt-4'> +// <form +// onSubmit={handleSubmit(onSubmitHandler)} +// className='flex flex-col gap-4' +// > +// <div className='w-full flex flex-col'> +// <div className='w-full'> +// <label className='form-label text-nowrap'> +// Nama Perusahaan +// </label> +// <input +// {...register('company')} +// placeholder='Format: PT. INDOTEKNIK DOTCOM GEMILANG' +// type='text' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.company?.message} +// </div> +// <span className='opacity-65 text-xs'> +// Isi detail perusahaan sesuai dengan nama yang terdaftar{' '} +// </span> +// </div> +// </div> +// <div className='w-full flex flex-col'> +// <div className='w-full'> +// <label className='form-label text-nowrap'> +// Pejabat Berwenang +// </label> +// <input +// {...register('pejabatName')} +// placeholder='Masukkan nama pejabat berwenang' +// type='text' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.pejabatName?.message} +// </div> +// <span className='opacity-65 text-xs'> +// isi dengan nama pejabat yang berwewenang di perusahaan +// anda{' '} +// </span> +// </div> +// </div> +// <div className='w-full flex flex-col'> +// <div className='w-full'> +// <label className='form-label text-nowrap'>Nama PIC</label> +// <input +// {...register('PICName')} +// placeholder='Masukkan Nama PIC ' +// type='text' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.PICName?.message} +// </div> +// <span className='opacity-65 text-xs'> +// Isi dengan nama sales / penanggung jawab +// </span> +// </div> +// </div> +// <div className='w-full flex flex-col'> +// <div className='w-full'> +// <label className='form-label text-nowrap'> +// Jabatan PIC +// </label> +// <input +// {...register('PICPosition')} +// placeholder='Masukkan jabatan PIC ' +// type='text' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.PICPosition?.message} +// </div> +// <span className='opacity-65 text-xs'> +// isi dengan jabatan sales / penanggung jawab +// </span> +// </div> +// </div> +// <div className='w-full flex flex-col'> +// <div className='w-full'> +// <label className='form-label text-nowrap'> +// Alamat Perusahaan +// </label> +// <input +// {...register('address')} +// placeholder='Masukkan alamat lengkap perusahaan' +// type='text' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.address?.message} +// </div> +// <div className='flex flex-row w-full justify-between gap-2'> +// <div className='provinsi w-full'> +// <Controller +// name='state' +// control={control} +// render={(props) => ( +// <HookFormSelect +// {...props} +// options={state} +// placeholder='Provinsi' +// /> +// )} +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.state?.message} +// </div> +// </div> +// <div className='kab w-full'> +// <Controller +// name='city' +// control={control} +// render={(props) => ( +// <HookFormSelect +// {...props} +// options={cities} +// disabled={!watchState} +// placeholder='Kab/Kota' +// /> +// )} +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.city?.message} +// </div> +// </div> +// </div> +// <div className='flex flex-row w-full justify-between gap-2'> +// <div className='kec w-full'> +// <Controller +// name='district' +// control={control} +// render={(props) => ( +// <HookFormSelect +// {...props} +// options={districts} +// disabled={!watchState || !watchCity} +// placeholder='Kecamatan' +// /> +// )} +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.district?.message} +// </div> +// </div> +// <div className='kel w-full'> +// <Controller +// name='subDistrict' +// control={control} +// render={(props) => ( +// <HookFormSelect +// {...props} +// options={subDistricts} +// disabled={!watchDistrict} +// placeholder='Kelurahan' +// /> +// )} +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.subDistrict?.message} +// </div> +// </div> +// <div className='zip w-full'> +// <Controller +// name='zip' +// control={control} +// render={(props) => ( +// <> +// {zips.length > 0 ? ( +// <HookFormSelect +// {...props} +// options={zips} +// disabled={!watchsubDistrict} +// placeholder='Zip' +// /> +// ) : ( +// <input +// {...register('zip')} +// placeholder='Kode Pos' +// type='number' +// className='form-input' +// disabled={!watchsubDistrict} +// /> +// )} +// </> +// )} +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.zip?.message} +// </div> +// </div> +// </div> +// </div> +// <span className='opacity-65 text-xs'> +// Alamat sesuai dengan alamat perusahaan +// </span> +// </div> +// <div className='w-full flex flex-col'> +// <label className='form-label text-nowrap'>Data Bank</label> +// <div className='w-full flex flex-row gap-2'> +// <div> +// <input +// {...register('bank')} +// placeholder='Nama bank' +// type='text' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.bank?.message} +// </div> +// </div> +// <div> +// <input +// {...register('rekening')} +// placeholder='Nama Rekening' +// type='text' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.rekening?.message} +// </div> +// </div> +// <div> +// <input +// {...register('accountNumber')} +// placeholder='Nomor Rekening Bank' +// type='number' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.accountNumber?.message} +// </div> +// </div> +// </div> +// <span className='opacity-65 text-xs'> +// Isi detail data bank perusahaan anda +// </span> +// </div> +// <div className='w-full flex flex-col'> +// <label className='form-label text-nowrap'> +// Email Perusahaan +// </label> +// <input +// {...register('email')} +// placeholder='contoh@email.com' +// type='email' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.email?.message} +// </div> +// <span className='opacity-65 text-xs'> +// Isi detail perusahaan sesuai dengan data yang terdaftar +// </span> +// </div> +// <div className='w-full flex flex-col'> +// <label className='form-label text-nowrap'>Email Sales</label> +// <input +// {...register('emailSales')} +// placeholder='contoh@email.com' +// type='email' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.emailSales?.message} +// </div> +// <span className='opacity-65 text-xs'> +// Isi detail perusahaan sesuai dengan data yang terdaftar +// </span> +// </div> +// <div className='w-full flex flex-col'> +// <label className='form-label text-nowrap'> +// Email Finance +// </label> +// <input +// {...register('emailFinance')} +// placeholder='contoh@email.com' +// type='email' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.emailFinance?.message} +// </div> +// <span className='opacity-65 text-xs'> +// Isi detail perusahaan sesuai dengan data yang terdaftar +// </span> +// </div> +// <div className='w-full flex flex-col'> +// <label className='form-label text-nowrap'> +// No. Telepon Perusahaan +// </label> +// <input +// {...register('phone')} +// placeholder='Format: 08123456789 / (021) 123 4567' +// type='tel' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.phone?.message} +// </div> +// <span className='opacity-65 text-xs'> +// Isi detail perusahaan sesuai dengan data yang terdaftar +// </span> +// </div> +// <div className='w-full flex flex-col'> +// <label className='form-label text-nowrap'> +// No. Handphone +// </label> +// <input +// {...register('mobile')} +// placeholder='Masukkan nomor handphone' +// type='tel' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.mobile?.message} +// </div> +// <span className='opacity-65 text-xs'> +// Isi detail perusahaan sesuai dengan data yang terdaftar +// </span> +// </div> +// <div className='flex flex-col'> +// <label className='form-label text-nowrap'>Tipe Bisnis</label> +// <div className='flex flex-col '> +// <Controller +// name='bisnisType' +// control={control} +// render={(props) => ( +// <HookFormSelect +// {...props} +// options={dataBisnisType} +// placeholder={'Pilih tipe bisnis'} +// /> +// )} +// /> +// {!isKonfirmasi && ( +// <span className='text-xs opacity-60'> +// Pilih tipe bisnis yang sesuai +// </span> +// )} +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.bisnisType?.message} +// </div> +// </div> +// </div> +// <div className='flex flex-col'> +// <label className='form-label text-nowrap'> +// Kategori Perusahaan +// </label> +// <div className='flex flex-col '> +// <Controller +// name='categoryPerusahaan' +// control={control} +// render={(props) => ( +// <HookFormSelect +// {...props} +// options={dataCategoryPerusahaan} +// placeholder={'Pilih category perusahaan'} +// /> +// )} +// /> +// {!isKonfirmasi && ( +// <span className='text-xs opacity-60'> +// Pilih kategori perusahaan yang sesuai +// </span> +// )} +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.categoryPerusahaan?.message} +// </div> +// </div> +// </div> +// <div className='w-full flex flex-col'> +// <label className='form-label text-nowrap'>Website</label> +// <input +// {...register('website')} +// placeholder='Masukkan website' +// type='text' +// className='form-input' +// /> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// isi dengan website perusahaan anda +// </span> +// )} +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.website?.message} +// </div> +// </div> + +// <div className=''> +// {/* <div> +// <ReCAPTCHA +// ref={recaptchaRef} +// sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_GOOGLE} +// /> +// </div> */} +// </div> +// <div className='flex justify-center w-full '> +// {/* <Button +// colorScheme='red' +// className='w-full md:w-fit' +// type='submit' +// > +// Daftar Merchant{' '} +// {<ChevronRightIcon className='w-5' color='white' />} +// </Button> */} +// {!isKonfirmasi && ( +// <div className='w-full'> +// <span className='text-xs opacity-60'> +// *Pastikan data yang anda masukan sudah benar dan sesuai +// </span> +// <button +// type='submit' +// className='btn-light bg-red-500 rounded-lg text-white w-full py-2 px-4 md:w-fit mt-2 ml-0 md:ml-auto flex justify-center hover:bg-red-400' +// > +// <span className={` `}>Langkah Selanjutnya</span> +// {<ChevronRightIcon className='w-5' />} +// </button> +// </div> +// )} +// </div> +// </form> +// <PageContent path='/daftar-merchant' /> +// </div> +// </div> +// </MobileView> +// </> +// ); +// } +// ); + +// const validationSchema = Yup.object().shape({ +// company: Yup.string().required('Harus di-isi'), +// pejabatName: Yup.string().required('Harus di-isi'), +// PICName: Yup.string().required('Harus di-isi'), +// PICPosition: Yup.string().required('Harus di-isi'), +// address: Yup.string().required('Harus di-isi'), +// state: Yup.string().required('Harus dipilih'), +// city: Yup.string().required('Harus dipilih'), +// district: Yup.string().required('Harus dipilih'), +// subDistrict: Yup.string().required('Harus dipilih'), +// zip: Yup.string().required('Harus di-isi'), +// bank: Yup.string().required('Harus di-isi'), +// rekening: Yup.string().required('Harus di-isi'), +// accountNumber: Yup.string().required('Harus di-isi'), +// email: Yup.string() +// .email('Format harus seperti contoh@email.com') +// .required('Harus di-isi'), +// emailSales: Yup.string() +// .email('Format harus seperti contoh@email.com') +// .required('Harus di-isi'), +// emailFinance: Yup.string() +// .email('Format harus seperti contoh@email.com') +// .required('Harus di-isi'), +// phone: Yup.string().required('Harus di-isi'), +// mobile: Yup.string().required('Harus di-isi'), +// bisnisType: Yup.string().required('Harus dipilih'), +// categoryPerusahaan: Yup.string().required('Harus dipilih'), +// }); +// const defaultValues = { +// company: '', +// pejabatName: '', +// PICName: '', +// PICPosition: '', +// address: '', +// state: '', +// city: '', +// district: '', +// subDistrict: '', +// zip: '', +// email: '', +// emailSales: '', +// emailFinance: '', +// bank: '', +// rekening: '', +// accountNumber: '', +// phone: '', +// mobile: '', +// }; + +// export default CreateMerchant; diff --git a/src/lib/merchant/components/InformasiVendor.jsx b/src/lib/merchant/components/InformasiVendor.jsx new file mode 100644 index 00000000..90763029 --- /dev/null +++ b/src/lib/merchant/components/InformasiVendor.jsx @@ -0,0 +1,748 @@ +// import HookFormSelect from '@/core/components/elements/Select/HookFormSelect'; +// import cityApi from '@/lib/address/api/cityApi'; +// import stateApi from '@/lib/address/api/stateApi.js'; +// import districtApi from '@/lib/address/api/districtApi'; +// import subDistrictApi from '@/lib/address/api/subDistrictApi'; +// import { yupResolver } from '@hookform/resolvers/yup'; +// import React, { +// useEffect, +// useRef, +// useState, +// forwardRef, +// useImperativeHandle, +// } from 'react'; +// import ReCAPTCHA from 'react-google-recaptcha'; +// import { Controller, useForm } from 'react-hook-form'; +// import { toast } from 'react-hot-toast'; +// import * as Yup from 'yup'; +// import createMerchantApi from '../api/createMerchantApi'; +// import getMerchantApi from '../api/getMerchantApi'; +// import addressApi from '@/lib/address/api/addressApi'; +// import PageContent from '@/lib/content/components/PageContent'; +// import { useRouter } from 'next/router'; +// import useAuth from '@/core/hooks/useAuth'; +// import { Radio, RadioGroup, Stack, Checkbox, Button } from '@chakra-ui/react'; +// import { EyeIcon } from '@heroicons/react/24/outline'; +// import BottomPopup from '@/core/components/elements/Popup/BottomPopup'; +// import Image from 'next/image'; +// import ImageBanner from '~/components/ui/image'; +// import { ChevronRightIcon } from '@heroicons/react/24/outline'; +// import MobileView from '@/core/components/views/MobileView'; +// import DesktopView from '@/core/components/views/DesktopView'; +// import getFileBase64 from '@/core/utils/getFileBase64'; +// import odooApi from '~/libs/odooApi'; +// import { formatValue } from 'react-currency-input-field'; +// const InformasiVendor = forwardRef(({ handleIsError, isKonfirmasi }, ref) => { +// const isError = (value) => { +// // Logika menentukan error +// const result = value ? true : false; +// handleIsError(result); // Panggil handleIsError dari Merchant +// return result; +// }; +// const { +// register, +// handleSubmit, +// formState: { errors }, +// control, +// reset, +// watch, +// setValue, +// getValues, +// } = useForm({ +// resolver: yupResolver(validationSchema), +// defaultValues, +// }); +// const [categoryProduk, setCategoryProduk] = useState([]); +// const [isExample, setIsExample] = useState(false); + +// const router = useRouter(); + +// const auth = useAuth(); +// if (auth == false) { +// router.push(`/login?next=${encodeURIComponent('/daftar-merchant')}`); +// } +// const dataTerhitungSejak = [ +// { value: 1, label: 'Terima PO' }, +// { value: 2, label: 'Barang Dikirimkan' }, +// { value: 3, label: 'Tukar Faktur' }, +// ]; + +// const dataTempo = [ +// { value: 24, label: 'Tempo 14 Hari' }, +// { value: 25, label: 'Tempo 30 Hari' }, +// { value: 28, label: 'Tempo 60 Hari' }, +// { value: 31, label: 'Tempo 90 Hari' }, +// ]; + +// const midIndex = Math.ceil(categoryProduk.length / 2); +// const firstColumn = categoryProduk.slice(0, midIndex); +// const secondColumn = categoryProduk.slice(midIndex); +// const [kreditLimitFormat, setKreditLimitFormat] = useState(); + +// useEffect(() => { +// const loadData = async () => { +// const data = await getMerchantApi(); +// if (data) { +// reset({ +// hargaTayang: data.hargaTayang || '', +// categoryProduk: data.categoryProduk || '', +// merkDagang: data.merkDagang || '', +// isPengajuanTempo: data.isPengajuanTempo || '', +// tempoDuration: parseInt(data.tempoDuration) || '', +// // kreditLimit: parseInt(data.kreditLimit) || '', +// waktuPengiriman: data.waktuPengiriman || '', +// terhitungSejak: parseInt(data.terhitungSejak) || '', +// }); +// handleKreditLimitChange(data.kreditLimit || ''); +// setSelectedIds(watch('categoryProduk').split(',').map(Number)); +// } +// }; + +// loadData(); +// }, []); + +// useImperativeHandle(ref, () => () => { +// handleSubmit(onSubmitHandler)(); +// }); + +// const handleKreditLimitChange = (e) => { +// let value = e.target?.value ? e.target.value : e; + +// // Hapus semua karakter non-numeric +// value = value.replace(/[^\d]/g, ''); + +// // Format angka sebagai Rupiah tanpa mengubah nilai sebenarnya +// const formattedValue1 = formatValue({ +// value: value, +// groupSeparator: '.', +// decimalSeparator: ',', +// prefix: 'Rp ', +// }); + +// setKreditLimitFormat(formattedValue1); +// setValue('kreditLimit', formattedValue1); +// }; + +// const [selectedIds, setSelectedIds] = useState( +// watch('categoryProduk') +// ? watch('categoryProduk').split(',').map(Number) +// : [] +// // form.categoryProduk ? form.categoryProduk.split(',').map(Number) : [] // Parse string menjadi array angka +// // [] // Parse string menjadi array angka +// ); +// const handleCheckboxChange = (id) => { +// const updatedSelected = selectedIds.includes(id) +// ? selectedIds.filter((selectedId) => selectedId !== id) +// : [...selectedIds, id]; + +// setSelectedIds(updatedSelected); + +// // Mengubah array kembali menjadi string yang dipisahkan oleh koma +// setValue('categoryProduk', updatedSelected.join(',')); +// }; + +// const isChecked = (id) => selectedIds.includes(id); + +// const handleCheckboxPortalChange = (value) => { +// setValue('isPengajuanTempo', `${value}`); +// }; + +// useEffect(() => { +// if (!isKonfirmasi) { +// window.scrollTo({ +// top: 0, +// behavior: 'smooth', +// }); +// } +// }, []); + +// useEffect(() => { +// const loadCategories = async () => { +// let dataCategories = await odooApi('GET', '/api/v1/category/tree'); +// const formattedCategories = dataCategories.map((category) => ({ +// id: category.id, +// name: category.name, +// })); +// // Simpan hasil ke state +// setCategoryProduk(formattedCategories); +// }; +// loadCategories(); +// }, []); + +// const onSubmitHandler = async (values) => { +// const toastId = toast.loading('Mengirimkan formulir merchant...'); +// const data = { +// harga_tayang: values.hargaTayang, +// categoryProduk: values.categoryProduk, +// merk_dagang: values.merkDagang, +// is_pengajuan_tempo: values.isPengajuanTempo, +// tempo_duration: values.tempoDuration, +// kredit_limit: values.kreditLimit, +// waktu_pengiriman: values.waktuPengiriman, +// terhitung_sejak: values.terhitungSejak, +// }; +// const create_leads = await createMerchantApi({ data }); +// if (create_leads) { +// toast.dismiss(toastId); +// isError(false); +// toast.success('Berhasil menambahkan data'); +// reset(); +// // router.push('/+'); +// } else { +// toast.dismiss(toastId); +// toast.error('Gagal menambahkan data'); +// } +// }; + +// // const DownLoadSurat = () => { +// // download surat dari /public/file/Surat Pernyataan Nomor Rekening.docx +// // }; + +// if (!auth) { +// return; +// } + +// return ( +// <> +// <BottomPopup +// className='' +// title='Contoh SPPKP' +// active={isExample} +// close={() => setIsExample(false)} +// > +// <div className='flex p-2'> +// <Image +// src='/images/NO-SPPKP-FORMAT-TEMPLATE.jpg' +// alt='Contoh SPPKP' +// className='w-full h-full ' +// width={800} +// height={800} +// quality={85} +// /> +// </div> +// </BottomPopup> +// <DesktopView> +// <div className='container flex flex-col items-star py-4 '> +// <h2 className='text-xs md:text-title-sm font-semibold mb-6'> +// Informasi Vendor +// </h2> + +// <div className='w-full mt-4'> +// <form +// onSubmit={handleSubmit(onSubmitHandler)} +// className='flex flex-col gap-4' +// > +// <div className='w-full flex flex-row'> +// <div className='w-2/5'> +// <label className='form-label text-nowrap'> +// Harga Tayang (HET){' '} +// <span className=' opacity-60'>(Opsional)</span> +// </label> +// </div> +// <div className='w-3/5'> +// <textarea +// {...register('hargaTayang')} +// placeholder='Jelaskan detail HET yang anda miliki' +// type='textarea' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.hargaTayang?.message} +// </div> +// </div> +// </div> + +// <div className={`flex flex-row justify-between items-start`}> +// <div className='w-2/5 text-nowrap'> +// <label +// className={`form-label ${isKonfirmasi && 'text-wrap'}`} +// > +// Tipe Kategori Produk yang Digunakan +// </label> +// {!isKonfirmasi && ( +// <span className='text-xs opacity-60'> +// Pilih kategori produk bisa lebih dari 1 +// </span> +// )} +// </div> +// <div className='w-3/5 flex flex-col'> +// <div className='flex flex-row justify-between gap-2'> +// <div +// className='flex flex-col gap-2' +// // ref={categoryProdukRef} +// > +// {firstColumn.map((item) => ( +// <Checkbox +// colorScheme='red' +// key={item.id} +// onChange={() => handleCheckboxChange(item.id)} +// isChecked={isChecked(item.id)} +// > +// {item.name} +// </Checkbox> +// ))} +// </div> +// <div className='flex flex-col gap-2 '> +// {secondColumn.map((item) => ( +// <Checkbox +// colorScheme='red' +// key={item.id} +// isChecked={isChecked(item.id)} +// onChange={() => handleCheckboxChange(item.id)} +// > +// {item.name} +// </Checkbox> +// ))} +// </div> +// </div> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.categoryProduk?.message} +// </div> +// </div> +// </div> + +// <div className='w-full flex flex-row'> +// <div className='w-2/5'> +// <label className='form-label text-nowrap'>Merk Dagang</label> +// </div> +// <div className='w-3/5'> +// <input +// {...register('merkDagang')} +// placeholder='Merk 1, Merk 2, Merk 3' +// type='text' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.merkDagang?.message} +// </div> +// </div> +// </div> + +// <div className='flex flex-row justify-between items-start'> +// <div className='w-2/5'> +// <label className='form-label text-wrap '> +// Apakah anda memiliki Form Pengajuan Tempo? +// </label> +// </div> +// <div className='w-3/5 flex flex-col justify-start'> +// <div className='flex gap-x-4'> +// <RadioGroup +// onChange={handleCheckboxPortalChange} +// value={watch('isPengajuanTempo')} +// > +// <Stack direction='row'> +// <Radio colorScheme='red' value='ada'> +// Ya, ada +// </Radio> +// <Radio colorScheme='red' value='tidak'> +// Tidak ada +// </Radio> +// </Stack> +// </RadioGroup> +// </div> + +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.isPengajuanTempo?.message} +// </div> +// </div> +// </div> + +// <div className='flex flex-row justify-between items-center'> +// <div className='w-2/5'> +// <label className='form-label text-nowrap'>Durasi Tempo</label> +// {!isKonfirmasi && ( +// <span className='text-xs opacity-60'> +// Pilih durasi tempo yang anda inginkan +// </span> +// )} +// </div> +// <div className='w-3/5 flex flex-col '> +// <div className='flex flex-row items-center gap-3'> +// <div className={`${!isKonfirmasi && 'w-[25%]'}`}> +// <Controller +// name='tempoDuration' +// control={control} +// render={(props) => ( +// <HookFormSelect +// {...props} +// options={dataTempo} +// placeholder={'Pilih Durasi Tempo'} +// /> +// )} +// /> + +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.tempoDuration?.message} +// </div> +// </div> +// </div> +// </div> +// </div> + +// <div className='w-full flex flex-row'> +// <div className='w-2/5'> +// <label className='form-label text-nowrap'> +// Jumlah Kredit Limit +// </label> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// isi dengan kredit limit perusahaan anda +// </span> +// )} +// </div> +// <div className='w-3/5'> +// <input +// value={kreditLimitFormat} +// onChange={handleKreditLimitChange} +// placeholder='Masukkan jumlah kredit limit' +// type='text' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.kreditLimit?.message} +// </div> +// </div> +// </div> + +// <div className='w-full flex flex-row'> +// <div className='w-2/5'> +// <label className='form-label text-nowrap'> +// Waktu Pengiriman +// </label> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// isi dengan waktu pengiriman anda +// </span> +// )} +// </div> +// <div className='w-3/5 flex flex-row gap-2'> +// <div className='w-1/3'> +// <input +// {...register('waktuPengiriman')} +// placeholder='Masukkan waktu pengiriman' +// type='text' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.waktuPengiriman?.message} +// </div> +// </div> +// <div className='w-2/3 '> +// <div className='flex flex-row items-center gap-2'> +// <label className=' text-nowrap text-sm opacity-70 italic'> +// terhitung sejak +// </label> + +// <Controller +// name='terhitungSejak' +// control={control} +// render={(props) => ( +// <HookFormSelect +// {...props} +// options={dataTerhitungSejak} +// placeholder={'waktu pengiriman'} +// /> +// )} +// /> +// </div> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.terhitungSejak?.message} +// </div> +// </div> +// </div> +// </div> + +// <div className=''> +// {/* <div> +// <ReCAPTCHA +// ref={recaptchaRef} +// sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_GOOGLE} +// /> +// </div> */} +// </div> +// <div className='flex justify-end'> +// {/* <Button +// colorScheme='red' +// className='w-full md:w-fit' +// type='submit' +// > +// Daftar Merchant{' '} +// {<ChevronRightIcon className='w-5' color='white' />} +// </Button> */} +// {!isKonfirmasi && ( +// <div> +// <span className='text-xs opacity-60'> +// *Pastikan data yang anda masukan sudah benar dan sesuai +// </span> +// <button +// type='submit' +// className='btn-light bg-red-500 rounded-lg text-white w-fit py-2 px-4 md:w-fit mt-2 ml-0 md:ml-auto flex justify-between hover:bg-red-400' +// > +// <span className={` `}>Langkah Selanjutnya</span> +// {<ChevronRightIcon className='w-5' />} +// </button> +// </div> +// )} +// </div> +// </form> +// <PageContent path='/daftar-merchant' /> +// </div> +// </div> +// </DesktopView> +// <MobileView> +// <div className='container flex flex-col items-star py-4'> +// {!isKonfirmasi && ( +// <h2 className='font-semibold mb-6 text-xl'>Informasi Vendor</h2> +// )} + +// <div className='w-full mt-4'> +// <form +// onSubmit={handleSubmit(onSubmitHandler)} +// className='flex flex-col gap-4' +// > +// <div className='w-full flex flex-col'> +// <div className='w-full'> +// <label className='form-label text-nowrap'> +// Harga Tayang (HET){' '} +// <span className=' opacity-60'>(Opsional)</span> +// </label> +// <input +// {...register('hargaTayang')} +// placeholder='Jelaskan detail HET yang anda miliki' +// type='textarea' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.hargaTayang?.message} +// </div> +// </div> +// </div> +// <div +// className={`flex flex-col gap-2 justify-between ${ +// isKonfirmasi ? 'items-start' : 'items-start' +// }`} +// > +// <label className='form-label '> +// Tipe Kategori Produk yang Digunakan +// </label> +// <div className='flex flex-col justify-between gap-2 '> +// <div className='flex flex-col gap-2'> +// {firstColumn.map((item) => ( +// <Checkbox +// size='sm' +// colorScheme='red' +// key={item.id} +// onChange={() => handleCheckboxChange(item.id)} +// isChecked={isChecked(item.id)} +// > +// {item.name} +// </Checkbox> +// ))} +// </div> +// <div className='flex flex-col gap-2'> +// {secondColumn.map((item) => ( +// <Checkbox +// size='sm' +// colorScheme='red' +// key={item.id} +// isChecked={isChecked(item.id)} +// onChange={() => handleCheckboxChange(item.id)} +// > +// {item.name} +// </Checkbox> +// ))} +// </div> +// </div> + +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.categoryProduk?.message} +// </div> +// </div> +// <div className='w-full flex flex-col'> +// <div className='w-full'> +// <label className='form-label text-nowrap'>Merk Dagang</label> +// <input +// {...register('merkDagang')} +// placeholder='Merk 1, Merk 2, Merk 3' +// type='text' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.merkDagang?.message} +// </div> +// </div> +// </div> +// <div className='w-full flex flex-col'> +// <label className='form-label text-nowrap'> +// Apakah anda memiliki Form Pengajuan Tempo? +// </label> +// <div className='flex gap-x-4'> +// <RadioGroup +// onChange={handleCheckboxPortalChange} +// value={watch('isPengajuanTempo')} +// > +// <Stack direction='row'> +// <Radio colorScheme='red' value='ada'> +// Ya, ada +// </Radio> +// <Radio colorScheme='red' value='tidak'> +// Tidak ada +// </Radio> +// </Stack> +// </RadioGroup> +// </div> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.isPengajuanTempo?.message} +// </div> +// </div> + +// <div className='w-full flex flex-col'> +// <label className='form-label text-nowrap'>Durasi Tempo</label> +// <Controller +// name='tempoDuration' +// control={control} +// render={(props) => ( +// <HookFormSelect +// {...props} +// options={dataTempo} +// placeholder={'Pilih Durasi Tempo'} +// /> +// )} +// /> + +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.tempoDuration?.message} +// </div> +// {!isKonfirmasi && ( +// <span className='text-xs opacity-60'> +// Pilih durasi tempo yang anda inginkan +// </span> +// )} +// </div> +// <div className='w-full flex flex-col'> +// <label className='form-label text-nowrap'> +// Jumlah Kredit Limit +// </label> +// <input +// value={kreditLimitFormat} +// onChange={handleKreditLimitChange} +// placeholder='Masukkan jumlah kredit limit' +// type='text' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.kreditLimit?.message} +// </div> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// isi dengan kredit limit perusahaan anda +// </span> +// )} +// </div> +// <div className='w-full flex flex-col'> +// <label className='form-label text-nowrap'> +// {' '} +// Waktu Pengiriman +// </label> +// <div className='w-full flex flex-row gap-2'> +// <div className='w-1/3'> +// <input +// {...register('waktuPengiriman')} +// placeholder='Masukkan waktu pengiriman' +// type='text' +// className='form-input' +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.waktuPengiriman?.message} +// </div> +// </div> +// <div className='w-2/3 '> +// <div className='flex flex-row items-center gap-2'> +// <label className=' text-nowrap text-sm opacity-70 italic'> +// terhitung sejak +// </label> + +// <Controller +// name='terhitungSejak' +// control={control} +// render={(props) => ( +// <HookFormSelect +// {...props} +// options={dataTerhitungSejak} +// placeholder={'waktu pengiriman'} +// /> +// )} +// /> +// </div> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.terhitungSejak?.message} +// </div> +// </div> +// </div> +// {!isKonfirmasi && ( +// <span className='opacity-65 text-xs'> +// isi dengan waktu pengiriman anda +// </span> +// )} +// </div> + +// <div className=''> +// {/* <div> +// <ReCAPTCHA +// ref={recaptchaRef} +// sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_GOOGLE} +// /> +// </div> */} +// </div> +// <div className='flex justify-center w-full '> +// {/* <Button +// colorScheme='red' +// className='w-full md:w-fit' +// type='submit' +// > +// Daftar Merchant{' '} +// {<ChevronRightIcon className='w-5' color='white' />} +// </Button> */} +// {!isKonfirmasi && ( +// <div className='w-full'> +// <span className='text-xs opacity-60'> +// *Pastikan data yang anda masukan sudah benar dan sesuai +// </span> +// <button +// type='submit' +// className='btn-light bg-red-500 rounded-lg text-white w-full py-2 px-4 md:w-fit mt-2 ml-0 md:ml-auto flex justify-center hover:bg-red-400' +// > +// <span className={` `}>Langkah Selanjutnya</span> +// {<ChevronRightIcon className='w-5' />} +// </button> +// </div> +// )} +// </div> +// </form> +// <PageContent path='/daftar-merchant' /> +// </div> +// </div> +// </MobileView> +// </> +// ); +// }); +// const validationSchema = Yup.object().shape({ +// categoryProduk: Yup.string().required('Harus di-pilih'), +// merkDagang: Yup.string().required('Harus di-isi'), +// isPengajuanTempo: Yup.string().required('Harus di-pilih'), +// tempoDuration: Yup.string().required('Harus di-pilih'), +// kreditLimit: Yup.string().required('Harus di-isi'), +// waktuPengiriman: Yup.string().required('Harus di-isi'), +// terhitungSejak: Yup.string().required('Harus di-pilih'), +// }); +// const defaultValues = { +// categoryProduk: '', +// merkDagang: '', +// isPengajuanTempo: '', +// tempoDuration: '', +// kreditLimit: '', +// waktuPengiriman: '', +// terhitungSejak: '', +// }; + +// export default InformasiVendor; diff --git a/src/lib/merchant/components/Konfirmasi.jsx b/src/lib/merchant/components/Konfirmasi.jsx new file mode 100644 index 00000000..f7d552ac --- /dev/null +++ b/src/lib/merchant/components/Konfirmasi.jsx @@ -0,0 +1,224 @@ +// import React, { useState, useEffect, useMemo, useRef } from 'react'; +// import { Controller, set, useForm } from 'react-hook-form'; +// import HookFormSelect from '@/core/components/elements/Select/HookFormSelect'; +// // import ProgressBar from '@ramonak/react-progress-bar'; +// import { +// Button, +// Checkbox, +// Spinner, +// Tooltip, +// UseToastOptions, +// } from '@chakra-ui/react'; +// import odooApi from '~/libs/odooApi'; +// import { toast } from 'react-hot-toast'; +// import getFileBase64 from '@/core/utils/getFileBase64'; +// import { CheckCircleIcon } from '@heroicons/react/24/outline'; +// import InformasiPerusahaan from './InformasiPerusahaan'; +// import InformasiVendor from './InformasiVendor'; +// import SyaratDagang from './SyaratDagang'; +// import Dokumen from './Dokumen'; +// import createMerchantApi from '../api/createMerchantApi'; +// import useDevice from '@/core/hooks/useDevice'; +// import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline'; +// import { useRouter } from 'next/router'; +// const Konfirmasi = ({ chekValid, buttonSubmitClick }) => { +// const { control, watch, setValue, getValues, reset } = useForm(); +// const { isDesktop, isMobile } = useDevice(); +// const [isOpenInformasi, setIsOpenInformasi] = useState(true); +// const [isOpenKontak, setIsOpenKontak] = useState(false); +// const [isOpenPengiriman, setIsOpenPengiriman] = useState(false); +// const [isOpenKonfirmasi, setIsOpenKonfirmasi] = useState(false); +// const formRef = useRef(null); +// const router = useRouter(); +// const handleDaftarMerchant = () => { +// if (formRef.current) { +// formRef.current(); // Memicu submit form di InformasiPerusahaan +// } +// }; +// const handleIsError = async (value) => { +// if (!value) { +// // goToNextStep(); +// const toastId = toast.loading('Mengirimkan formulir merchant...'); +// const data = { +// merchant_request: true, +// }; +// const create_leads = await createMerchantApi({ data }); +// if (create_leads) { +// toast.dismiss(toastId); +// toast.success('Berhasil medaftarkan merchant'); +// reset(); +// // router.push('/+'); +// } else { +// toast.dismiss(toastId); +// toast.error('Gagal menambahkan data'); +// } +// } +// reset(); +// router.push('/'); +// }; + +// return ( +// <> +// {isDesktop && ( +// <> +// <form className='flex mt-4 flex-col w-full '> +// <div className='w-full grid grid-cols-[1fr_auto_1fr] gap-5'> +// <div className='w-full flex flex-col gap-5 '> +// <div className=''> +// <InformasiPerusahaan +// isKonfirmasi={true} +// ref={formRef} +// handleIsError={handleIsError} +// /> +// </div> +// <div className='h-px bg-gray-300'></div> +// <div className=''> +// <SyaratDagang +// isKonfirmasi={true} +// ref={formRef} +// handleIsError={handleIsError} +// /> +// </div> +// </div> + +// <div className='w-px bg-gray-300'></div> +// <div className='w-full grid grid-rows-[1fc_auto_1fc] gap-5'> +// <div className=''> +// <InformasiVendor +// isKonfirmasi={true} +// ref={formRef} +// handleIsError={handleIsError} +// /> +// </div> +// <div className='h-px bg-gray-300'></div> +// <div> +// <Dokumen +// isKonfirmasi={true} +// ref={formRef} +// handleIsError={handleIsError} +// /> +// </div> +// </div> +// </div> +// </form> + +// <div className='flex flex-col items-end justify-end gap-2'> +// <span className='text-xs opacity-60'> +// *Pastikan data yang anda masukan sudah benar dan sesuai +// </span> +// <Button +// colorScheme='red' +// w='36' +// onClick={handleDaftarMerchant} // Memicu form submit +// > +// Daftar Merchant +// </Button> +// </div> +// </> +// )} +// {isMobile && ( +// <form className='flex mt-8 py-4 flex-col w-full gap-4'> +// <div className='flex flex-col gap-4'> +// <div className='flex flex-row justify-between items-center'> +// <div className='flex flex-col justify-center items-start'> +// <p className='font-semibold text-lg'>Informasi Perusahaan</p> +// {/* <span className='text-xs opacity-70'> +// Pastikan informasi usaha yang anda masukkan sudah sesuai +// dengan data perusahaan anda +// </span> */} +// </div> +// <div className='p-2 bg-gray-200'> +// {isOpenInformasi ? ( +// <ChevronUpIcon +// className='w-4' +// onClick={() => setIsOpenInformasi(!isOpenInformasi)} +// /> +// ) : ( +// <ChevronDownIcon +// className='w-4' +// onClick={() => setIsOpenInformasi(!isOpenInformasi)} +// /> +// )} +// </div> +// </div> +// {isOpenInformasi && <InformasiPerusahaan isKonfirmasi={true} />} +// <div className='h-[2px] bg-gray-300 w-[120%] inset-0 mt-4 mb-4 relative transform -translate-x-5'></div> +// </div> +// <div className='flex flex-col gap-4'> +// <div className='flex flex-row justify-between'> +// <p className='font-semibold text-lg'>Informasi Vendor</p> +// <div className='p-2 bg-gray-200'> +// {isOpenKontak ? ( +// <ChevronUpIcon +// className='w-4' +// onClick={() => setIsOpenKontak(!isOpenKontak)} +// /> +// ) : ( +// <ChevronDownIcon +// className='w-4' +// onClick={() => setIsOpenKontak(!isOpenKontak)} +// /> +// )} +// </div> +// </div> +// {isOpenKontak && <InformasiVendor isKonfirmasi={true} />} +// <div className='h-[2px] bg-gray-300 w-[120%] inset-0 mt-4 mb-4 relative transform -translate-x-5'></div> +// </div> +// <div className='flex flex-col gap-4'> +// <div className='flex flex-row justify-between'> +// <p className='font-semibold text-lg'>Syarat Perdagangan</p> +// <div className='p-2 bg-gray-200'> +// {isOpenPengiriman ? ( +// <ChevronUpIcon +// className='w-4' +// onClick={() => setIsOpenPengiriman(!isOpenPengiriman)} +// /> +// ) : ( +// <ChevronDownIcon +// className='w-4' +// onClick={() => setIsOpenPengiriman(!isOpenPengiriman)} +// /> +// )} +// </div> +// </div> +// {isOpenPengiriman && <SyaratDagang isKonfirmasi={true} />} +// <div className='h-[2px] bg-gray-300 w-[120%] inset-0 mt-4 mb-4 relative transform -translate-x-5'></div> +// </div> +// <div className='flex flex-col gap-4'> +// <div className='flex flex-row justify-between'> +// <p className='font-semibold text-lg'>Dokumen</p> +// <div className='p-2 bg-gray-200'> +// {isOpenKonfirmasi ? ( +// <ChevronUpIcon +// className='w-4' +// onClick={() => setIsOpenKonfirmasi(!isOpenKonfirmasi)} +// /> +// ) : ( +// <ChevronDownIcon +// className='w-4' +// onClick={() => setIsOpenKonfirmasi(!isOpenKonfirmasi)} +// /> +// )} +// </div> +// </div> +// {isOpenKonfirmasi && <Dokumen isKonfirmasi={true} />} +// </div> +// <div className='flex flex-col items-end justify-end gap-2'> +// <span className='text-xs opacity-60'> +// *Pastikan data yang anda masukan sudah benar dan sesuai +// </span> +// <Button +// colorScheme='red' +// w='full' +// onClick={handleDaftarMerchant} // Memicu form submit +// > +// Daftar Merchant +// </Button> +// </div> +// </form> +// )} +// </> +// ); +// }; + +// export default Konfirmasi; diff --git a/src/lib/merchant/components/Merchant.jsx b/src/lib/merchant/components/Merchant.jsx new file mode 100644 index 00000000..382db064 --- /dev/null +++ b/src/lib/merchant/components/Merchant.jsx @@ -0,0 +1,147 @@ +// import React from 'react'; +// import { useMemo, useState, useEffect, useRef } from 'react'; +// import Image from '~/components/ui/image'; +// import InformasiPerusahaan from './InformasiPerusahaan'; +// import InformasiVendor from './InformasiVendor'; +// import SyaratDagang from './SyaratDagang'; +// import Dokumen from './Dokumen'; +// import Konfirmasi from './Konfirmasi'; +// import { getAuth } from '~/libs/auth'; +// import { setAuth } from '@/core/utils/auth'; +// import useAuth from '@/core/hooks/useAuth'; +// import { useRouter } from 'next/router'; +// import { Controller, useForm } from 'react-hook-form'; +// import { ChevronRightIcon, ChevronLeftIcon } from '@heroicons/react/24/outline'; +// import { Button, Checkbox, Spinner, Tooltip } from '@chakra-ui/react'; +// import clsxm from '~/libs/clsxm'; +// import { toast } from 'react-hot-toast'; +// import useDevice from '@/core/hooks/useDevice'; +// import odooApi from '~/libs/odooApi'; +// import BottomPopup from '@/core/components/elements/Popup/BottomPopup'; +// import PageContent from '@/lib/content/components/PageContent'; +// const Merchant = () => { +// const { isDesktop, isMobile } = useDevice(); +// const [currentStep, setCurrentStep] = React.useState(0); +// const NUMBER_OF_STEPS = 5; +// const [isLoading, setIsLoading] = useState(false); +// const [bigData, setBigData] = useState(); +// const [idTempo, setIdTempo] = useState(0); +// const { control, watch, setValue } = useForm(); +// const auth = useAuth(); +// const router = useRouter(); +// const [BannerTempo, setBannerTempo] = useState(); +// const [notValid, setNotValid] = useState(false); +// const [buttonSubmitClick, setButtonSubmitClick] = useState(false); + +// const [error, setError] = useState(false); + +// const handleIsError = (value) => { +// if (!value) { +// goToNextStep(); +// } +// setError(value); // Memperbarui state berdasarkan isError +// }; +// const stepDivs = [ +// <InformasiPerusahaan +// handleIsError={handleIsError} +// buttonSubmitClick={buttonSubmitClick} +// />, +// <InformasiVendor +// handleIsError={handleIsError} +// buttonSubmitClick={buttonSubmitClick} +// />, +// <SyaratDagang +// handleIsError={handleIsError} +// buttonSubmitClick={buttonSubmitClick} +// />, +// <Dokumen +// handleIsError={handleIsError} +// buttonSubmitClick={buttonSubmitClick} +// />, +// <Konfirmasi handleIsError={handleIsError} />, +// ]; + +// const stepLabels = [ +// 'informasi_perusahaan', +// 'kontak_person', +// 'Pengiriman', +// 'Referensi', +// 'Dokumen', +// 'Konfirmasi', +// ]; + +// useEffect(() => { +// window.scrollTo({ +// top: 0, +// behavior: 'smooth', +// }); +// }, [currentStep]); + +// useEffect(() => { +// <InformasiPerusahaan buttonSubmitClick={buttonSubmitClick} />; +// }, [buttonSubmitClick]); + +// const goToNextStep = () => { +// setCurrentStep((prev) => (prev === NUMBER_OF_STEPS - 1 ? prev : prev + 1)); +// }; + +// const goPrevStep = () => { +// setCurrentStep((prev) => (prev === NUMBER_OF_STEPS - 1 ? prev : prev - 1)); +// }; + +// useEffect(() => { +// const getBanner = async () => { +// const get = await odooApi('GET', '/api/v1/banner?type=banner-form-tempo'); +// // setBannerTempo(get[0].image); +// setBannerTempo( +// 'https://erp.indoteknik.com/api/image/x_banner.banner/x_banner_image/431' +// ); +// }; +// getBanner(); +// }, []); +// return ( +// <> +// <div className='container flex flex-col items-center '> +// {BannerTempo && ( +// <Image +// src={BannerTempo} +// alt='FORM Tempo' +// width={500} +// height={160} +// className='w-full mt-6' +// /> +// )} +// <h1 className=' font-semibold text-center mb-6'>Form Merchant</h1> +// <p +// className={`text-center ${ +// isMobile ? 'w-full text-sm' : 'w-3/4 mb-4' +// }`} +// > +// Pembayaran tempo adalah layanan pembayaran berjangka yang difasilitasi +// indoteknik.com untuk konsumen akun bisnis yang terdaftar dengan waktu +// pembayaran mulai dari 7, 14, 21 hingga 30 Hari. +// </p> +// </div> +// <div +// className={`h-[2px] w-full ${isMobile ? 'mt-4' : 'mb-4'} bg-gray_r-3`} +// /> + +// <div className={`container ${isMobile ? 'mt-4' : ''} flex flex-col`}> +// <div>{stepDivs[currentStep]}</div> +// {isDesktop && <section className='flex gap-10 mt-10'></section>} +// {isMobile && ( +// <div className='h-[2px] bg-gray-300 w-[120%] inset-0 mt-4 mb-4 relative transform -translate-x-5'></div> +// )} +// <div +// className={`flex ${ +// isMobile +// ? 'flex-col justify-start items-start' +// : 'flex-col justify-end items-end' +// } mb-8 gap-2`} +// ></div> +// </div> +// </> +// ); +// }; + +// export default Merchant; diff --git a/src/lib/merchant/components/SyaratDagang.jsx b/src/lib/merchant/components/SyaratDagang.jsx new file mode 100644 index 00000000..a3060e23 --- /dev/null +++ b/src/lib/merchant/components/SyaratDagang.jsx @@ -0,0 +1,822 @@ +// import HookFormSelect from '@/core/components/elements/Select/HookFormSelect'; +// import cityApi from '@/lib/address/api/cityApi'; +// import stateApi from '@/lib/address/api/stateApi.js'; +// import districtApi from '@/lib/address/api/districtApi'; +// import subDistrictApi from '@/lib/address/api/subDistrictApi'; +// import { yupResolver } from '@hookform/resolvers/yup'; +// import React, { +// useEffect, +// useRef, +// useState, +// forwardRef, +// useImperativeHandle, +// } from 'react'; +// import ReCAPTCHA from 'react-google-recaptcha'; +// import { Controller, useForm } from 'react-hook-form'; +// import { toast } from 'react-hot-toast'; +// import * as Yup from 'yup'; +// import createMerchantApi from '../api/createMerchantApi'; +// import getMerchantApi from '../api/getMerchantApi'; +// import addressApi from '@/lib/address/api/addressApi'; +// import PageContent from '@/lib/content/components/PageContent'; +// import { useRouter } from 'next/router'; +// import useAuth from '@/core/hooks/useAuth'; +// import { Radio, RadioGroup, Stack, Checkbox, Button } from '@chakra-ui/react'; +// import { EyeIcon } from '@heroicons/react/24/outline'; +// import BottomPopup from '@/core/components/elements/Popup/BottomPopup'; +// import Image from 'next/image'; +// import ImageBanner from '~/components/ui/image'; +// import { ChevronRightIcon } from '@heroicons/react/24/outline'; +// import MobileView from '@/core/components/views/MobileView'; +// import DesktopView from '@/core/components/views/DesktopView'; +// import getFileBase64 from '@/core/utils/getFileBase64'; +// import odooApi from '~/libs/odooApi'; +// const SyaratDagang = forwardRef(({ handleIsError, isKonfirmasi }, ref) => { +// const isError = (value) => { +// // Logika menentukan error +// const result = value ? true : false; +// handleIsError(result); // Panggil handleIsError dari Merchant +// return result; +// }; +// const { +// register, +// handleSubmit, +// formState: { errors }, +// control, +// reset, +// watch, +// setValue, +// getValues, +// } = useForm({ +// resolver: yupResolver(validationSchema), +// defaultValues, +// }); +// const [bigData, setbigData] = useState([]); +// const [isExample, setIsExample] = useState(false); + +// const recaptchaRef = useRef(null); +// const router = useRouter(); + +// useEffect(() => { +// const loadData = async () => { +// const data = await getMerchantApi(); +// setbigData(data); +// if (data) { +// reset({ +// isKembaliBarang: data.isKembaliBarang || '', +// textReturn: data.textReturn || '', +// tenggatWaktu: (() => { +// const waktu = data.tenggatWaktu +// ? data.tenggatWaktu.split(' ')[0] +// : ''; +// if (waktu === '14') return '14'; +// if (waktu === '30') return '30'; +// return 'custom'; +// })(), +// customTenggatWaktu: (() => { +// if (watch('tenggatWaktu') === 'custom') +// return data.tenggatWaktu ? data.tenggatWaktu : ''; +// return ''; +// })(), +// sertifikatProduk: data.sertifikatProduk || '', +// customSertifikatProduk: data.customSertifikatProduk || '', +// tempoGaransi: parseInt(data.tempoGaransi) || '', +// explainGaransi: data.explainGaransi || '', +// minimumPembelian: +// data.isOrderQuantity == 'tidak' ? '' : data.minimumPembelian || '', +// isOrderQuantity: data.isOrderQuantity || '', +// }); +// // handleKreditLimitChange(data.kreditLimit); +// if (watch('sertifikatProduk') != false) { +// setSelectedIds(watch('sertifikatProduk').split(',').map(Number)); +// } +// if (watch('customSertifikatProduk')) { +// // setSelectedIds([...selectedIds, 4]); +// } +// } +// }; + +// loadData(); +// }, []); +// useEffect(() => { +// if (!isKonfirmasi) { +// window.scrollTo({ +// top: 0, +// behavior: 'smooth', +// }); +// } +// }, []); +// useImperativeHandle(ref, () => () => { +// handleSubmit(onSubmitHandler)(); +// }); +// const auth = useAuth(); +// if (auth == false) { +// router.push(`/login?next=${encodeURIComponent('/daftar-merchant')}`); +// } + +// const dataGaransi = [ +// { value: 1, label: '6 Bulan Garansi' }, +// { value: 2, label: '1 Tahun Garansi' }, +// { value: 3, label: '2 Tahun Garansi' }, +// ]; +// const dataMinimumOrderQuantity = [ +// { value: 'dus', label: 'Dus' }, +// { value: 'lusin', label: 'Lusin' }, +// { value: 'minimum pembelian', label: 'Minimum pembelian' }, +// ]; + +// const category_produk = [ +// { id: 0, name: 'TKDN' }, +// { id: 1, name: 'SNI' }, +// { id: 2, name: 'K3L' }, +// { id: 3, name: '' }, +// ]; + +// const [selectedIds, setSelectedIds] = useState( +// watch('sertifikatProduk') +// ? watch('sertifikatProduk').split(',').map(Number) +// : [] +// // form.sertifikatProduk ? form.sertifikatProduk.split(',').map(Number) : [] // Parse string menjadi array angka +// // [] // Parse string menjadi array angka +// ); +// const handleCheckboxChange = (id) => { +// const updatedSelected = selectedIds.includes(id) +// ? selectedIds.filter((selectedId) => selectedId !== id) +// : [...selectedIds, id]; + +// setSelectedIds(updatedSelected); + +// // Mengubah array kembali menjadi string yang dipisahkan oleh koma +// setValue('sertifikatProduk', updatedSelected.join(',')); +// }; +// const custom_sertifikat_produk_handle = () => { +// const updatedSelected = [...selectedIds, 3]; + +// setSelectedIds(updatedSelected); + +// // Mengubah array kembali menjadi string yang dipisahkan oleh koma +// setValue('sertifikatProduk', updatedSelected.join(',')); +// }; + +// const isChecked = (id) => selectedIds.includes(id); + +// const handleCheckboxReturChange = (value) => { +// setValue('isKembaliBarang', `${value}`); +// }; +// const handleCheckboxOrderQuantityChange = (value) => { +// setValue('isOrderQuantity', `${value}`); +// }; + +// const handleCheckboxTenggatWaktuChange = (value) => { +// setValue('tenggatWaktu', `${value}`); +// }; + +// const onSubmitHandler = async (values) => { +// const toastId = toast.loading('Mengirimkan formulir merchant...'); +// const data = { +// is_kembali_barang: values.isKembaliBarang, +// text_return: values.textReturn, +// tenggat_waktu: values.tenggatWaktu, +// custom_tenggat_waktu: values.customTenggatWaktu, +// sertifikat_produk: values.sertifikatProduk, +// custom_sertifikat_produk: +// values.customSertifikatProduk == '' +// ? false +// : values.customSertifikatProduk, +// tempo_garansi: values.tempoGaransi, +// explain_garansi: values.explainGaransi, +// is_order_quantity: values.isOrderQuantity, +// minimum_pembelian: values.minimumPembelian, +// }; +// const create_leads = await createMerchantApi({ data }); +// if (create_leads) { +// toast.dismiss(toastId); +// toast.success('Berhasil menambahkan data'); +// isError(false); +// reset(); +// // router.push('/'); +// } else { +// toast.dismiss(toastId); +// toast.error('Gagal menambahkan data'); +// } +// }; + +// // const DownLoadSurat = () => { +// // download surat dari /public/file/Surat Pernyataan Nomor Rekening.docx +// // }; + +// if (!auth) { +// return; +// } +// // Tetap di bagian atas, tidak boleh ada kondisi sebelum hook + +// return ( +// <> +// <BottomPopup +// className='' +// title='Contoh SPPKP' +// active={isExample} +// close={() => setIsExample(false)} +// > +// <div className='flex p-2'> +// <Image +// src='/images/NO-SPPKP-FORMAT-TEMPLATE.jpg' +// alt='Contoh SPPKP' +// className='w-full h-full ' +// width={800} +// height={800} +// quality={85} +// /> +// </div> +// </BottomPopup> +// <DesktopView> +// <div +// className={`container flex flex-col h-fit items-start ${ +// !isKonfirmasi && 'py-4' +// }`} +// > +// <h2 className='text-xs md:text-title-sm font-semibold mb-6'> +// Syarat Perdagangan +// </h2> + +// <div className='w-full mt-4 '> +// <form +// onSubmit={handleSubmit(onSubmitHandler)} +// className='flex flex-col gap-4' +// > +// <div className='flex flex-row justify-between items-start'> +// <div className='w-2/5'> +// <label className='form-label text-wrap '> +// Syarat Pengembalian Barang +// </label> +// </div> +// <div className='w-3/5 flex flex-row justify-start'> +// <div className='flex gap-x-4 flex-col w-full'> +// <RadioGroup +// onChange={handleCheckboxReturChange} +// value={watch('isKembaliBarang')} +// > +// <Stack direction='column'> +// <div className='flex flex-row text-nowrap gap-2'> +// <Radio colorScheme='red' value='ya'> +// Ya, dapat diretur +// </Radio> +// {watch('isKembaliBarang') == 'ya' && ( +// <textarea +// {...register('textReturn')} +// placeholder='jelaskan syarat pengembalian' +// type='textarea' +// className='form-input w-full' +// /> +// )} +// </div> +// <Radio colorScheme='red' value='tidak'> +// Tidak dapat diretur +// </Radio> +// </Stack> +// </RadioGroup> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.isKembaliBarang?.message} +// </div> +// </div> +// </div> +// </div> + +// <div className='flex flex-row justify-between items-start'> +// <div className='w-2/5'> +// <label className='form-label text-wrap '> +// Tenggat Waktu Perubahan Harga +// </label> +// </div> +// <div className='w-3/5 flex flex-row justify-start'> +// <div className='flex gap-x-4 flex-col w-full'> +// <RadioGroup +// onChange={handleCheckboxTenggatWaktuChange} +// value={watch('tenggatWaktu')} +// > +// <Stack direction='column'> +// <Radio +// colorScheme='red' +// value='14' +// onChange={() => setValue('customTenggatWaktu', ' ')} +// > +// 14 hari sejak data dikirimkan +// </Radio> +// <Radio +// colorScheme='red' +// value='30' +// onChange={() => setValue('customTenggatWaktu', ' ')} +// > +// 30 hari sejak data dikirimkan +// </Radio> +// <div className='flex flex-row gap-2'> +// <Radio colorScheme='red' value='custom'></Radio> +// <input +// {...register('customTenggatWaktu')} +// placeholder='Masukkan jumlah hari untuk tenggat waktu' +// type='text' +// onFocus={() => setValue('tenggatWaktu', 'custom')} +// className='form-input mt-2' +// /> +// </div> +// </Stack> +// </RadioGroup> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.tenggatWaktu?.message} +// </div> +// </div> +// </div> +// </div> + +// <div className={`flex flex-row justify-between items-start`}> +// <div className='w-2/5 text-nowrap'> +// <label +// className={`form-label ${isKonfirmasi && 'text-wrap'}`} +// > +// Dokumen/Sertifikat yang Dimiliki Oleh Brand +// </label> +// {!isKonfirmasi && ( +// <span className='text-xs opacity-60'> +// Pilih dokumen/sertifikat bisa lebih dari 1 +// </span> +// )} +// </div> +// <div className='w-3/5 flex flex-col'> +// <div className='flex flex-row justify-between w-full'> +// <div +// className='flex flex-col gap-2 w-full' +// // ref={categoryProdukRef} +// > +// <Checkbox +// colorScheme='red' +// key={0} +// onChange={() => handleCheckboxChange(0)} +// isChecked={isChecked(0)} +// > +// TKDN +// </Checkbox> +// <Checkbox +// colorScheme='red' +// key={1} +// onChange={() => handleCheckboxChange(1)} +// isChecked={isChecked(1)} +// > +// SNI +// </Checkbox> +// <Checkbox +// colorScheme='red' +// key={2} +// onChange={() => handleCheckboxChange(2)} +// isChecked={isChecked(2)} +// > +// K3L +// </Checkbox> +// <div className='flex flex-row gap-2 w-full'> +// <Checkbox +// colorScheme='red' +// key={3} +// onChange={() => handleCheckboxChange(3)} +// isChecked={isChecked(3)} +// ></Checkbox> +// <input +// {...register('customSertifikatProduk')} +// placeholder='Masukkan Dokumen/Sertifikat yang dimiliki oleh brand' +// type='text' +// onFocus={() => { +// custom_sertifikat_produk_handle(); +// }} +// // onFocus={() => handleCheckboxChange(4)} +// className='form-input mt-2' +// /> +// </div> +// </div> +// </div> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.sertifikatProduk?.message} +// </div> +// </div> +// </div> + +// <div className='flex flex-row justify-between items-center'> +// <div className='w-2/5'> +// <label className='form-label text-nowrap'>Garansi</label> +// {!isKonfirmasi && ( +// <span className='text-xs opacity-60'> +// Pilih waktu garansi yang diberikan +// </span> +// )} +// </div> +// <div className='w-3/5 flex flex-col '> +// <div className='flex flex-row items-center gap-3'> +// <div className={`${!isKonfirmasi && 'w-[25%]'}`}> +// <Controller +// name='tempoGaransi' +// control={control} +// render={(props) => ( +// <HookFormSelect +// {...props} +// options={dataGaransi} +// placeholder={'Pilih durasi garansi'} +// /> +// )} +// /> + +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.tempoGaransi?.message} +// </div> +// </div> +// </div> +// </div> +// </div> + +// <div className='w-full flex flex-row'> +// <div className='w-2/5'> +// <label className='form-label text-nowrap'> +// Jelaskan Garansi yang dimaksud! +// </label> +// </div> +// <div className='w-3/5'> +// <textarea +// {...register('explainGaransi')} +// placeholder='Jelaskan bagian apa yang termasuk garansi' +// type='textarea' +// className='form-input' +// rows={4} +// cols={40} +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.explainGaransi?.message} +// </div> +// </div> +// </div> + +// <div className='flex flex-row justify-between items-start'> +// <div className='w-2/5'> +// <label className='form-label text-wrap '> +// Apakah Memiliki Minimum Order Quantity (MOQ) +// </label> +// </div> +// <div className='w-3/5 flex flex-row justify-start'> +// <div className='flex gap-x-4 flex-col w-full'> +// <RadioGroup +// onChange={handleCheckboxOrderQuantityChange} +// value={ +// watch('minimumPembelian') +// ? 'ya' +// : watch('isOrderQuantity') +// } +// > +// <Stack direction='column'> +// <div className='flex flex-row text-nowrap gap-2'> +// <Radio +// colorScheme='red' +// value='ya' +// onChange={() => setValue('isOrderQuantity', 'ya')} +// > +// Ya +// </Radio> + +// <Controller +// name='minimumPembelian' +// control={control} +// render={(props) => ( +// <HookFormSelect +// {...props} +// options={dataMinimumOrderQuantity} +// placeholder={ +// 'Pilih jenis minimum order quantity' +// } +// onFocus={() => +// setValue('isOrderQuantity', 'ya') +// } +// /> +// )} +// /> +// </div> +// <Radio +// colorScheme='red' +// value='tidak' +// onChange={() => setValue('minimumPembelian', '')} +// > +// Tidak Ada +// </Radio> +// </Stack> +// </RadioGroup> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.isOrderQuantity?.message} +// </div> +// </div> +// </div> +// </div> + +// {!isKonfirmasi && ( +// <div className='flex justify-end'> +// <div> +// <span className='text-xs opacity-60'> +// *Pastikan data yang anda masukan sudah benar dan sesuai +// </span> +// <button +// type='submit' +// className='btn-light bg-red-500 rounded-lg text-white w-fit py-2 px-4 md:w-fit mt-2 ml-0 md:ml-auto flex justify-between hover:bg-red-400' +// > +// <span className={` `}>Langkah Selanjutnya</span> +// {<ChevronRightIcon className='w-5' />} +// </button> +// </div> +// </div> +// )} +// </form> +// </div> +// </div> +// </DesktopView> +// <MobileView> +// <div className='container flex flex-col items-star py-4'> +// {!isKonfirmasi && ( +// <h2 className='font-semibold mb-6 text-xl'>Syarat Perdagangan</h2> +// )} + +// <div className='w-full mt-4'> +// <form +// onSubmit={handleSubmit(onSubmitHandler)} +// className='flex flex-col gap-4' +// > +// <div className='w-full flex flex-col'> +// <div className='w-full'> +// <label className='form-label text-nowrap'> +// Syarat Pengembalian Barang +// </label> +// <div className='flex gap-x-4 flex-col w-full'> +// <RadioGroup +// onChange={handleCheckboxReturChange} +// value={watch('isKembaliBarang')} +// > +// <Stack direction='column'> +// <div className='flex flex-row text-nowrap gap-2'> +// <Radio colorScheme='red' value='ya'> +// Ya, dapat diretur +// </Radio> +// {watch('isKembaliBarang') == 'ya' && ( +// <textarea +// {...register('textReturn')} +// placeholder='jelaskan syarat pengembalian' +// type='textarea' +// className='form-input w-full' +// /> +// )} +// </div> +// <Radio colorScheme='red' value='tidak'> +// Tidak dapat diretur +// </Radio> +// </Stack> +// </RadioGroup> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.isKembaliBarang?.message} +// </div> +// </div> +// </div> +// </div> +// <div className='w-full flex flex-col'> +// <div className='w-full'> +// <label className='form-label text-nowrap'> +// Tenggat Waktu Perubahan Harga +// </label> +// <div className='flex gap-x-4 flex-col w-full'> +// <RadioGroup +// onChange={handleCheckboxTenggatWaktuChange} +// value={watch('tenggatWaktu')} +// > +// <Stack direction='column'> +// <Radio +// colorScheme='red' +// value='14' +// onChange={() => setValue('customTenggatWaktu', ' ')} +// > +// 14 hari sejak data dikirimkan +// </Radio> +// <Radio +// colorScheme='red' +// value='30' +// onChange={() => setValue('customTenggatWaktu', ' ')} +// > +// 30 hari sejak data dikirimkan +// </Radio> +// <div className='flex flex-row gap-2'> +// <Radio colorScheme='red' value='custom'></Radio> +// <input +// {...register('customTenggatWaktu')} +// placeholder='Masukkan jumlah hari untuk tenggat waktu' +// type='text' +// onFocus={() => setValue('tenggatWaktu', 'custom')} +// className='form-input mt-2' +// /> +// </div> +// </Stack> +// </RadioGroup> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.tenggatWaktu?.message} +// </div> +// </div> +// </div> +// </div> +// <div className='w-full flex flex-col'> +// <div className='w-full'> +// <label className='form-label text-nowrap'> +// Dokumen/Sertifikat yang Dimiliki Oleh Brand +// </label> +// <div +// className='flex flex-col gap-2 w-full' +// // ref={categoryProdukRef} +// > +// <Checkbox +// colorScheme='red' +// key={0} +// onChange={() => handleCheckboxChange(0)} +// isChecked={isChecked(0)} +// > +// TKDN +// </Checkbox> +// <Checkbox +// colorScheme='red' +// key={1} +// onChange={() => handleCheckboxChange(1)} +// isChecked={isChecked(1)} +// > +// SNI +// </Checkbox> +// <Checkbox +// colorScheme='red' +// key={2} +// onChange={() => handleCheckboxChange(2)} +// isChecked={isChecked(2)} +// > +// K3L +// </Checkbox> +// <div className='flex flex-row gap-2 w-full'> +// <Checkbox +// colorScheme='red' +// key={3} +// onChange={() => handleCheckboxChange(3)} +// isChecked={isChecked(3)} +// ></Checkbox> +// <input +// {...register('customSertifikatProduk')} +// placeholder='Masukkan Dokumen/Sertifikat yang dimiliki oleh brand' +// type='text' +// onFocus={() => { +// custom_sertifikat_produk_handle(); +// }} +// // onFocus={() => handleCheckboxChange(4)} +// className='form-input mt-2' +// /> +// </div> +// </div> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.sertifikatProduk?.message} +// </div> +// </div> +// </div> + +// <div className='w-full flex flex-col'> +// <label className='form-label text-nowrap'>Garansi</label> +// <div className='w-full flex flex-row gap-2'> +// <Controller +// name='tempoGaransi' +// control={control} +// render={(props) => ( +// <HookFormSelect +// {...props} +// options={dataGaransi} +// placeholder={'Pilih durasi garansi'} +// /> +// )} +// /> + +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.tempoGaransi?.message} +// </div> +// </div> +// </div> +// <div className='w-full flex flex-col'> +// <label className='form-label text-nowrap'> +// Jelaskan Garansi yang dimaksud! +// </label> +// <textarea +// {...register('explainGaransi')} +// placeholder='Jelaskan bagian apa yang termasuk garansi' +// type='textarea' +// className='form-input' +// rows={4} +// cols={40} +// /> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.explainGaransi?.message} +// </div> +// </div> +// <div className='w-full flex flex-col'> +// <label className='form-label text-nowrap'> +// Apakah Memiliki Minimum Order Quantity (MOQ) +// </label> +// <div className='flex gap-x-4 flex-col w-full'> +// <RadioGroup +// onChange={handleCheckboxOrderQuantityChange} +// value={ +// watch('minimumPembelian') +// ? 'ya' +// : watch('isOrderQuantity') +// } +// > +// <Stack direction='column'> +// <div className='flex flex-row text-nowrap gap-2'> +// <Radio +// colorScheme='red' +// value='ya' +// onChange={() => setValue('isOrderQuantity', 'ya')} +// > +// Ya +// </Radio> + +// <Controller +// name='minimumPembelian' +// control={control} +// render={(props) => ( +// <HookFormSelect +// {...props} +// options={dataMinimumOrderQuantity} +// placeholder={'Pilih jenis minimum order quantity'} +// onFocus={() => setValue('isOrderQuantity', 'ya')} +// /> +// )} +// /> +// </div> +// <Radio +// colorScheme='red' +// value='tidak' +// onChange={() => setValue('minimumPembelian', '')} +// > +// Tidak Ada +// </Radio> +// </Stack> +// </RadioGroup> +// <div className='text-caption-2 text-danger-500 mt-1'> +// {errors.isOrderQuantity?.message} +// </div> +// </div> +// </div> + +// <div className=''> +// {/* <div> +// <ReCAPTCHA +// ref={recaptchaRef} +// sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_GOOGLE} +// /> +// </div> */} +// </div> +// <div className='flex justify-center w-full '> +// {/* <Button +// colorScheme='red' +// className='w-full md:w-fit' +// type='submit' +// > +// Daftar Merchant{' '} +// {<ChevronRightIcon className='w-5' color='white' />} +// </Button> */} +// {!isKonfirmasi && ( +// <div className='w-full'> +// <span className='text-xs opacity-60'> +// *Pastikan data yang anda masukan sudah benar dan sesuai +// </span> +// <button +// type='submit' +// className='btn-light bg-red-500 rounded-lg text-white w-full py-2 px-4 md:w-fit mt-2 ml-0 md:ml-auto flex justify-center hover:bg-red-400' +// > +// <span className={` `}>Langkah Selanjutnya</span> +// {<ChevronRightIcon className='w-5' />} +// </button> +// </div> +// )} +// </div> +// </form> +// <PageContent path='/daftar-merchant' /> +// </div> +// </div> +// </MobileView> +// </> +// ); +// }); +// const validationSchema = Yup.object().shape({ +// isKembaliBarang: Yup.string().required('Harus di-pilih'), +// tenggatWaktu: Yup.string().required('Harus di-pilih'), +// sertifikatProduk: Yup.string().required('Harus di-pilih'), +// tempoGaransi: Yup.string().required('Harus di-pilih'), +// explainGaransi: Yup.string().required('Harus di-isi'), +// isOrderQuantity: Yup.string().required('Harus di-isi'), +// }); +// const defaultValues = { +// isKembaliBarang: '', +// tenggatWaktu: '', +// sertifikatProduk: '', +// explainGaransi: '', +// isOrderQuantity: '', +// }; + +// export default SyaratDagang; diff --git a/src/lib/pengajuan-tempo/component/Pengiriman.jsx b/src/lib/pengajuan-tempo/component/Pengiriman.jsx index fcfa7e1e..a8e7fd22 100644 --- a/src/lib/pengajuan-tempo/component/Pengiriman.jsx +++ b/src/lib/pengajuan-tempo/component/Pengiriman.jsx @@ -360,6 +360,8 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { [errorsPengiriman] ); + const tukarInvoiceInputRef = useRef(null); + const tukarInvoiceInputPembayaranRef = useRef(null); const PICNameRef = useRef(null); const streetPengirimanRef = useRef(null); const statePengirimanRef = useRef(null); @@ -390,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; @@ -1052,9 +1062,7 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { <HookFormSelect {...props} options={zips} - disabled={ - sameAddressStreet - } + disabled={sameAddressStreet} placeholder='Zip' /> ) : ( @@ -1065,9 +1073,7 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { ref={zipRef} placeholder='Kode Pos' type='number' - disabled={ - sameAddressStreet - } + disabled={sameAddressStreet} value={formPengiriman.zipPengiriman} className='form-input' onChange={handleInputChange} @@ -1322,9 +1328,7 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { <HookFormSelect {...props} options={zipsInvoice} - disabled={ - sameAddress - } + disabled={sameAddress} placeholder='Zip' /> ) : ( @@ -1334,9 +1338,7 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { ref={zipInvoiceRef} placeholder='Kode Pos' type='number' - disabled={ - sameAddress - } + disabled={sameAddress} value={formPengiriman.zipInvoice} className='form-input' onChange={handleInputChange} @@ -1361,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> @@ -1378,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> @@ -1388,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> @@ -1403,10 +1411,18 @@ 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> @@ -1601,7 +1617,6 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { )} </div> </div> - </div> </form> </div> @@ -2330,8 +2345,6 @@ const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { </div> <div className='w-2/5'></div> </div> - - </div> </form> </div> diff --git a/src/lib/product/components/ProductSearch.jsx b/src/lib/product/components/ProductSearch.jsx index 486d1a4b..2fb3138a 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 }); diff --git a/src/lib/shipment/components/Shipments.jsx b/src/lib/shipment/components/Shipments.jsx index 20dbb013..aaf778c3 100644 --- a/src/lib/shipment/components/Shipments.jsx +++ b/src/lib/shipment/components/Shipments.jsx @@ -251,7 +251,7 @@ const Shipments = () => { <tr> <th>Tanggal</th> <th>No. Resi</th> - <th>No. Pengiriman</th> + <th>No. Dokumen</th> <th>Sales Order</th> <th>Purchase Order</th> <th>Expedisi</th> diff --git a/src/lib/transaction/components/Transaction.jsx b/src/lib/transaction/components/Transaction.jsx index 867de577..8b3a8dd0 100644 --- a/src/lib/transaction/components/Transaction.jsx +++ b/src/lib/transaction/components/Transaction.jsx @@ -410,6 +410,11 @@ const Transaction = ({ id }) => { <TransactionStatusBadge status={transaction.data?.status} /> </div> </DescriptionRow> + <DescriptionRow label='Status Transaksi'> + <div className='flex justify-end font-semibold text-red-500'> + {transaction.data?.expectedReadyToShip} + </div> + </DescriptionRow> <DescriptionRow label='No Transaksi'> {transaction.data?.name} </DescriptionRow> @@ -436,13 +441,15 @@ const Transaction = ({ id }) => { onClick={() => setIdAWB(airway?.id)} > <div> - <span className='text-sm text-gray_r-11'> + <p className='text-sm text-gray_r-11'>{airway?.name}</p> + <span className='mt-2 font-medium'> No Resi : {airway?.trackingNumber || '-'}{' '} </span> - <p className='mt-1 font-medium'>{airway?.name}</p> + {/*biteship*/} + {/*<p className='mt-1 font-medium'>{airway?.name}</p>*/} </div> <div className='flex gap-x-2'> - <div className='text-sm text-gray-600 badge-green leading-[1.5] mt-1'> + <div className='text-sm text-gray-600 badge-green leading-[1.5] mt-1 text-center'> {airway?.delivered ? 'Pesanan Tiba' : 'Sedang Dikirim'} </div> <ChevronRightIcon className='w-5 stroke-2' /> @@ -619,6 +626,20 @@ const Transaction = ({ id }) => { )} </div> + {/*new-release*/} + {/*<div className='flex items-center justify-between mb-3'>*/} + {/* <div className='flex items-center gap-x-2'>*/} + {/* <span className='text-h-sm font-medium'>*/} + {/* {transaction?.data?.name}*/} + {/* </span>*/} + {/* <TransactionStatusBadge status={transaction?.data?.status} />*/} + {/* </div>*/} + {/* <div className='text-h-sm'>*/} + {/* Estimasi Barang Siap:{' '}*/} + {/* <span className='text-red-500 font-semibold'>*/} + {/* {transaction?.data?.expectedReadyToShip}*/} + {/* </span>*/} + {/* </div>*/} <div className='flex items-center gap-x-2 mb-3'> <span className='text-h-sm font-medium'> {transaction?.data?.name} @@ -804,6 +825,34 @@ const Transaction = ({ id }) => { </div> <div className='flex '> + {/*New release*/} + {/* <div className='grid grid-cols-1 gap-1 w-2/3'>*/} + {/* {transaction?.data?.pickings?.map((airway) => (*/} + {/* <button*/} + {/* className='shadow rounded-md p-3 text-gray_r-12 font-normal flex justify-between items-center text-left h-20'*/} + {/* key={airway?.id}*/} + {/* onClick={() => setIdAWB(airway?.id)}*/} + {/* >*/} + {/* <div>*/} + {/* <p className='text-sm text-gray_r-11'>*/} + {/* {airway?.name}*/} + {/* </p>*/} + {/* <span className='text-md text-bold mt-1'>*/} + {/* No Resi : {airway?.trackingNumber || '-'}{' '}*/} + {/* </span>*/} + {/* </div>*/} + {/* <div className='flex gap-x-2'>*/} + {/* <div className='text-sm text-gray-600 badge-green leading-[1.5] mt-1 text-center'>*/} + {/* {airway?.delivered*/} + {/* ? 'Pesanan Tiba'*/} + {/* : 'Sedang Dikirim'}*/} + {/* </div>*/} + {/* <ChevronRightIcon className='w-5 stroke-2' />*/} + {/* </div>*/} + {/* </button>*/} + {/* ))}*/} + {/* </div>*/} + {/*</div>*/} <div className='invoice w-1/2 '> <div className='text-h-sm font-semibold mt-10 mb-4 '> Invoice 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..e14b0ca2 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) { @@ -88,6 +89,10 @@ export default async function handler(req, res) { 'price_tier1_v2_f:[1 TO *]', ]; + if (orderBy === 'stock') { + filterQueries.push('stock_total_f:{0 TO *}'); + } + if (fq && source != 'similar' && typeof fq != 'string') { // filterQueries.push(fq); fq.push(...filterQueries); diff --git a/src/pages/daftar-merchant.jsx b/src/pages/daftar-merchant.jsx index e1fa9bcb..945a0060 100644 --- a/src/pages/daftar-merchant.jsx +++ b/src/pages/daftar-merchant.jsx @@ -12,4 +12,4 @@ export default function DaftarMerchant() { </BasicLayout> </> ) -} +}
\ No newline at end of file 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 || '', |
