diff options
| author | it-fixcomart <it@fixcomart.co.id> | 2025-01-15 16:29:48 +0700 |
|---|---|---|
| committer | it-fixcomart <it@fixcomart.co.id> | 2025-01-15 16:29:48 +0700 |
| commit | 98236a47c3558c4b701009a275c7ae917ee8bf67 (patch) | |
| tree | 21e0300680a724c8a24ed815ea4e9a32ab13a895 /src/lib/tempo/components | |
| parent | 1fa1a7873aa67cdd9ca211c239276a148cd4cdda (diff) | |
| parent | 7a14ed5ccdde86d0400d6aa02ac866317d4add63 (diff) | |
Merge branch 'new-release' into Feature/switch-account
# Conflicts:
# src/lib/auth/components/CompanyProfile.jsx
# src/lib/auth/components/Menu.jsx
Diffstat (limited to 'src/lib/tempo/components')
| -rw-r--r-- | src/lib/tempo/components/Tempo.jsx | 754 |
1 files changed, 754 insertions, 0 deletions
diff --git a/src/lib/tempo/components/Tempo.jsx b/src/lib/tempo/components/Tempo.jsx new file mode 100644 index 00000000..c246f3d8 --- /dev/null +++ b/src/lib/tempo/components/Tempo.jsx @@ -0,0 +1,754 @@ +import { + CheckIcon, + ClockIcon, + EllipsisVerticalIcon, + MagnifyingGlassIcon, +} from '@heroicons/react/24/outline'; +import { Skeleton } from '@chakra-ui/react'; +import { div, toQuery } from 'lodash-contrib'; +import _ from 'lodash'; +import { useRouter } from 'next/router'; +import { useState, useEffect } from 'react'; +import useInvoices from '../../invoice/hooks/useInvoices'; +import Spinner from '@/core/components/elements/Spinner/Spinner'; +import Alert from '@/core/components/elements/Alert/Alert'; +import Pagination from '@/core/components/elements/Pagination/Pagination'; +import Link from '@/core/components/elements/Link/Link'; +import currencyFormat from '@/core/utils/currencyFormat'; +import BottomPopup from '@/core/components/elements/Popup/BottomPopup'; +import { + downloadInvoice, + downloadTaxInvoice, +} from '../../invoice/utils/invoices'; +import MobileView from '@/core/components/views/MobileView'; +import DesktopView from '@/core/components/views/DesktopView'; +import Menu from '@/lib/auth/components/Menu'; +import odooApi from '@/core/api/odooApi'; +import { getAuth } from '@/core/utils/auth'; +import FooterBanner from '~/modules/footer-banner'; +import Image from '~/components/ui/image'; +import useDevice from '@/core/hooks/useDevice'; +import { Tooltip } from '@chakra-ui/react'; +import { InfoIcon } from 'lucide-react'; +const Tempo = () => { + const auth = getAuth(); + const router = useRouter(); + const { q = '', page = 1, limit = 15, status = '' } = router.query; + const { isDesktop, isMobile } = useDevice(); + const [pageNew, setPageNew] = useState(page); + const [limitNew, setLimitNew] = useState(limit); + const [statusNew, setStatusNew] = useState(status); + + const query = { + name: q, + offset: (pageNew - 1) * limitNew, + limit: limitNew, + status: statusNew, + }; + const { invoices } = useInvoices({ query }); + const [isLoading, setIsLoading] = useState(true); + const [tempo, setTempo] = useState([]); + const [inputQuery, setInputQuery] = useState(q); + const [toOthers, setToOthers] = useState(null); + + const pageCount = Math.ceil(invoices?.data?.invoiceTotal / limitNew); + let pageQuery = _.omit(query, ['limitNew', 'offset']); + pageQuery = _.pickBy(pageQuery, _.identity); + pageQuery = toQuery(pageQuery); + const handleSubmit = (e) => { + e.preventDefault(); + router.push(`/my/tempo?q=${inputQuery}`); + }; + useEffect(() => { + const loadTempo = async () => { + setIsLoading(true); + const dataTempo = await odooApi( + 'GET', + `/api/v1/check/${auth.partnerId}/tempo` + ); + setTempo(dataTempo); + setIsLoading(false); + }; + loadTempo(); + }, []); + const limitTempo = Math.round( + parseInt(tempo.amountDue + tempo.remainingLimit) + ); + const amountDue = Math.round(parseInt(tempo.amountDue + tempo.amountDueSale)); + + const startItem = 1 + (pageNew - 1) * limitNew; + const endItem = Math.min( + limitNew * pageNew, + invoices?.data?.invoiceTotal + + (statusNew === 0 || statusNew === 2 || statusNew === 4 + ? invoices?.data?.saleOrderTotal + : 0) + ); + const getDueDate = (invoice) => { + const [day, month, year] = invoice.split('/'); + const dueDate = new Date(year, month - 1, day); // Konversi ke objek Date + return dueDate; + }; + + const formatTanggal = (tanggalInput) => { + const [day, month, year] = tanggalInput.split('/'); + const date = new Date(year, month - 1, day); + const formattedDate = new Intl.DateTimeFormat('id-ID', { + day: '2-digit', + month: 'short', + year: 'numeric', + }).format(date); + + return formattedDate; + }; + + const handleClick = () => { + setStatusNew((prevStatus) => (prevStatus === 4 ? 0 : 4)); // Toggle antara 4 dan 0 + }; + const handleClickInvoice = () => { + setStatusNew((prevStatus) => (prevStatus === 1 ? 0 : 1)); // Toggle antara 4 dan 0 + }; + return ( + <> + <DesktopView> + <div className='container mx-auto flex py-10'> + <div className='w-3/12 pr-4'> + <Menu /> + </div> + <div className='w-9/12 p-4 bg-white border border-gray_r-6 rounded flex flex-col gap-y-4'> + <div className='flex flex-col mb-6 items-start justify-start'> + <h1 className='text-title-sm font-semibold'>Pembayaran Tempo</h1> + <div className='flex items-start mb-4'> + <p className='flex flex-row gap-2 w-full items-center'> + Kredit Limit Anda + <Skeleton + isLoaded={!isLoading} + h='fit' + w='fit' + className='text-3xl font-semibold text-green-600 min-w-56' + > + {limitTempo ? currencyFormat(limitTempo) : '-'} + </Skeleton> + </p> + <Tooltip + placement='top-start' + label='Jumlah kredit limit dapat berubah sesuai dengan ketentuan yang Indoteknik miliki.' + > + <div className='text-gray-600'> + <InfoIcon size={15} /> + </div> + </Tooltip> + </div> + <p className='flex flex-row gap-2 w-full'> + Status Detail Tempo Pembayaran Anda adalah{' '} + <Skeleton + isLoaded={!isLoading} + h='fit' + w={32} + className={`${ + tempo.paymentTerm ? 'badge-solid-green' : 'badge-yellow' + } px-1 text-sm flex items-center justify-center font-thin`} + > + {tempo.paymentTerm ? tempo.paymentTerm : 'Review'} + </Skeleton> + </p> + </div> + <div className='grid grid-flow-col gap-4'> + <div className='border w-full p-4'> + <p>Sisa Kredit Limit</p> + <Skeleton + isLoaded={!isLoading} + h='36px' + w='full' + className='text-3xl font-semibold text-green-600' + > + {limitTempo && amountDue + ? currencyFormat( + Math.round(parseInt(limitTempo - amountDue)) + ) + : '-'} + </Skeleton> + </div> + <div + className={`border w-full p-4 flex gap-4 items-center hover:cursor-pointer hover:shadow-lg ${ + statusNew == 4 ? 'bg-red-100' : '' + }`} + onClick={handleClick} + > + <div className=''> + <p>Kredit Limit Terpakai</p> + <Skeleton + isLoaded={!isLoading} + h='36px' + w='full' + className='text-3xl font-semibold text-red-600' + > + {amountDue ? currencyFormat(amountDue) : '-'} + </Skeleton> + </div> + <div className='w-fit h-full flex items-center text-red-600 text-lg text-nowrap font-medium '> + {tempo.amountDueTotal + ? tempo.amountDueTotal + + tempo.amountDueSaleTotal + + ` Transaksi` + : ''} + </div> + </div> + <div + className={`border w-full p-4 flex gap-4 items-center hover:cursor-pointer hover:shadow-lg ${ + statusNew == 1 ? 'bg-red-100' : '' + }`} + onClick={handleClickInvoice} + > + <div className=''> + <p>Jatuh Tempo</p> + <Skeleton + isLoaded={!isLoading} + h='36px' + w='full' + className='text-3xl font-semibold text-red-600' + > + {tempo.amountJatuhTempo + ? currencyFormat(tempo.amountJatuhTempo) + : '-'} + </Skeleton> + </div> + <div className='w-fit h-full flex items-center text-red-600 text-lg text-nowrap font-medium '> + {tempo.amountJatuhTempoTotal + ? tempo.amountJatuhTempoTotal + ` Invoice` + : ''} + </div> + </div> + {/* <div className='border w-full p-4'> + <p>Jatuh Tempo</p> + <Skeleton + isLoaded={!isLoading} + h='36px' + // w={16} + className='text-3xl font-semibold text-red-600' + > + {tempo.amountJatuhTempo + ? currencyFormat(tempo.amountJatuhTempo) + : '-'} + </Skeleton> + </div> */} + </div> + + {auth && auth?.tempoProgres == 'review' && !tempo.paymentTerm ? ( + <div className='flex justify-center'> + <Image + src='/images/ICON-DOKUMEN-VERIFIKASI.png' + alt='Registrasi Tempo' + width={isMobile ? 300 : 600} + height={isMobile ? 300 : 550} + /> + </div> + ) : ( + <div className='border p-4 rounded'> + <div className='flex mb-6 items-center justify-between mt-4'> + <form className='flex' onSubmit={handleSubmit}> + <button className='bg-gray_r-2 px-3' type='submit'> + <MagnifyingGlassIcon className='w-6' /> + </button> + <input + type='text' + className='form-input bg-gray_r-2 border-none' + placeholder='Cari Invoice...' + value={inputQuery} + onChange={(e) => setInputQuery(e.target.value)} + /> + </form> + {!invoices.isLoading && ( + <div className='flex flex-row gap-4 items-center justify-center'> + <p> + Menampilkan {startItem}-{endItem} dari{' '} + {invoices?.data?.invoiceTotal + + (statusNew === 0 || statusNew === 2 || statusNew === 4 + ? invoices?.data?.saleOrderTotal + : 0)} + </p> + <select + id='limitSelect' + value={limitNew} + onChange={(e) => { + setLimitNew(Number(e.target.value)); + setPageNew(1); + }} + className='border p-2' + > + <option value={10}>10</option> + <option value={15}>15</option> + <option value={20}>20</option> + </select> + <select + id='statusSelect' + value={statusNew ?? ''} // Gunakan nullish coalescing untuk default value + onChange={(e) => { + setStatusNew(Number(e.target.value)); + if (e.target.value == 0) { + if (inputQuery) { + router.push('/my/tempo/'); + } + } + }} + className='border p-2' + > + <option value='' disabled hidden> + Status + </option> + <option value={0}>All</option> + <option value={1}>Jatuh Tempo</option> + <option value={2}>Belum Jatuh Tempo</option> + <option value={3}>Lunas</option> + </select> + </div> + )} + </div> + + <table className='table-data'> + <thead> + <tr> + <th>No. Invoice</th> + <th>No. Transaksi</th> + <th className='!text-left'>Salesperson</th> + <th>Tanggal</th> + <th>Jatuh Tempo</th> + <th>Status</th> + <th className='!text-left'>Total</th> + </tr> + </thead> + <tbody> + {/* {invoices.isLoading && ( + <tr> + <td colSpan={6}> + <div className='flex justify-center my-2'> + <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' /> + </div> + </td> + </tr> + )} */} + {/* {!invoices.isLoading && + (!invoices?.data?.saleOrders || + invoices?.data?.saleOrders?.length == 0) && ( + <tr> + <td colSpan={6}>Tidak ada orders</td> + </tr> + )} */} + {(statusNew == 0 || statusNew == 2 || statusNew == 4) && + invoices.data?.saleOrders?.map((orders) => ( + <tr key={orders.id}> + <td>{orders.salesOrder || '-'}</td> + <td> + <Link href={`/my/transactions/${orders.id}`}> + {orders.name} + </Link> + </td> + <td className='!text-left'>{orders.sales}</td> + <td>{orders.dateOrder.split(' ')[0]}</td> + <td>-</td> + <td> + { + <div className=' h-fit mx-auto inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-red-600/20'> + Belum Jatuh Tempo + </div> + } + </td> + <td className='!text-left'> + {currencyFormat(orders.amountTotal)} + </td> + </tr> + ))} + </tbody> + <tbody> + {invoices.isLoading && ( + <tr> + <td colSpan={6}> + <div className='flex justify-center my-2'> + <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' /> + </div> + </td> + </tr> + )} + {!invoices.isLoading && + (!invoices?.data?.invoices || + invoices?.data?.invoices?.length == 0) && ( + <tr> + <td colSpan={6}>Tidak ada Invoice</td> + </tr> + )} + {invoices.data?.invoices?.map((invoice) => ( + <tr key={invoice.id}> + <td> + <Link href={`/my/invoices/${invoice.id}`}> + {invoice.name} + </Link> + </td> + <td>{invoice.salesOrder || '-'}</td> + <td className='!text-left'>{invoice.sales}</td> + <td>{invoice.invoiceDate}</td> + <td>{invoice.invoiceDateDue}</td> + <td> + {invoice.amountResidual > 0 ? ( + new Date() > getDueDate(invoice.invoiceDateDue) ? ( + <div className='badge-solid-red h-fit mx-auto'> + Jatuh Tempo + </div> + ) : ( + <div className=' h-fit mx-auto inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-red-600/20'> + Belum Jatuh Tempo + </div> + ) + ) : ( + <div className='badge-solid-green h-fit mx-auto'> + Lunas + </div> + )} + </td> + <td className='!text-left'> + {currencyFormat(invoice.amountTotal)} + </td> + </tr> + ))} + </tbody> + </table> + + <Pagination + pageCount={pageCount} + currentPage={parseInt(pageNew)} + // url={`/my/tempo${pageQuery}`} + url={`/my/tempo?${toQuery(_.omit(query, ['page']))}`} + className='mt-2 mb-2' + /> + </div> + )} + + <div className=''> + <FooterBanner /> + </div> + </div> + </div> + </DesktopView> + <MobileView> + <div className=' bg-[#FEF8E2] w-[110%] inset-0 relative transform -translate-x-5 px-8 py-4'> + <div className='flex flex-col gap-y-4 '> + <p className='flex flex-row gap-2 w-full text-sm items-center'> + <span className='opacity-70 text-nowrap'> + Kredit Limit Anda :{' '} + </span> + <Skeleton + isLoaded={!isLoading} + h='fit' + w='fit' + className='text-xl font-semibold text-green-700 min-w-56' + > + {limitTempo ? currencyFormat(limitTempo) : '-'} + </Skeleton> + </p> + <div className='flex flex-col items-center justify-center'> + <p className='flex flex-row w-full text-sm gap-2 items-center justify-start'> + <span className='opacity-70'> + Status Detail Tempo Pembayaran Anda adalah : + </span> + <Skeleton + isLoaded={!isLoading} + h='fit' + w={24} + className={`${ + tempo.paymentTerm ? 'badge-solid-green' : 'badge-yellow' + } px-1 text-xs flex items-center justify-center font-extralight text-nowrap`} + > + {tempo.paymentTerm ? tempo.paymentTerm : 'Review'} + </Skeleton> + </p> + </div> + <div className='flex items-start justify-around text-sm gap-3'> + <div className='w-[30%] flex flex-col gap-2'> + <p className='opacity-70 text-nowrap'>Sisa Kredit Limit</p> + <Skeleton + isLoaded={!isLoading} + // h='36px' + // w={16} + className='font-semibold text-sm text-nowrap text-green-700 ' + > + {limitTempo && amountDue + ? currencyFormat( + Math.round(parseInt(limitTempo - amountDue)) + ) + : '-'} + </Skeleton> + </div>{' '} + <div className='w-[40%] flex flex-col gap-2'> + <p className='opacity-70 text-nowrap'>Kredit Limit Terpakai</p> + <Skeleton + isLoaded={!isLoading} + // h='36px' + // w={16} + className='font-semibold text-sm text-nowrap text-green-700' + > + {amountDue ? currencyFormat(amountDue) : '-'} + </Skeleton> + </div> + <div className=' w-[30%] flex flex-col gap-2'> + <p className='opacity-70 text-nowrap'>Jatuh Tempo</p> + <Skeleton + isLoaded={!isLoading} + // h='36px' + // w={16} + className=' font-semibold text-sm text-nowrap text-red-600' + > + {tempo.amountJatuhTempo + ? currencyFormat(tempo.amountJatuhTempo) + : '-'} + </Skeleton> + </div> + </div> + </div> + </div> + <div className='p-4 flex flex-col gap-y-4 w-full'> + {auth && (!auth?.tempoProgres == 'review' || tempo.paymentTerm) && ( + <form + className='w-full flex gap-x-2 flex-row justify-between' + onSubmit={handleSubmit} + > + <div className='flex w-3/5'> + <input + type='text' + className='form-input' + placeholder='Cari Invoice...' + value={inputQuery} + onChange={(e) => setInputQuery(e.target.value)} + /> + </div> + <button className='btn-light bg-transparent px-3' type='submit'> + <MagnifyingGlassIcon className='w-6' /> + </button> + <div className='flex w-1/5 '> + <div className='flex flex-row gap-4 items-center justify-center'> + <select + id='statusSelect' + value={statusNew} + onChange={(e) => { + setStatusNew(Number(e.target.value)); + if (e.target.value == 0) { + setInputQuery(' '); + } + }} + className='border p-2 w-full h-full rounded-md' + > + <option value={0}>All</option> + <option value={1}>Jatuh Tempo</option> + <option value={2}>Belum Jatuh Tempo</option> + <option value={3}>Lunas</option> + </select> + </div> + </div> + </form> + )} + + {invoices.isLoading && ( + <div className='flex justify-center my-4'> + <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' /> + </div> + )} + + {auth && auth?.tempoProgres == 'review' && !tempo.paymentTerm ? ( + // <Alert type='info' className='text-center'> + // Tidak ada invoice + // </Alert> + <div className='flex items-center justify-center'> + <Image + src='/images/ICON-DOKUMEN-VERIFIKASI.png' + alt='Registrasi Tempo' + width={isMobile ? 300 : 600} + height={isMobile ? 300 : 550} + /> + </div> + ) : ( + <div> + {(statusNew == 0 || statusNew == 2 || statusNew == 4) && + invoices.data?.saleOrders?.map((invoice, index) => ( + <div + className='p-4 shadow border border-gray_r-3 rounded-md' + key={index} + > + <div className='grid grid-cols-2'> + <Link href={`/my/quotations/${invoice.id}`}> + <span className='text-caption-2 text-gray_r-11'> + No. Transaksi + </span> + <h2 className='font-semibold text-black mt-1'> + {invoice.name} + </h2> + </Link> + <div className='flex gap-x-1 justify-end items-end '> + <div className='badge-solid-red h-fit ml-auto'> + Belum Jatuh Tempo + </div> + {/* <EllipsisVerticalIcon + className='w-5 h-5' + onClick={() => setToOthers(invoice)} + /> */} + </div> + </div> + <Link href={`/my/quotations/${invoice.id}`}> + <div className='grid grid-cols-2 text-caption-2 text-gray_r-11 mt-2 font-normal'> + <p className='opacity-70'> + {invoice.dateOrder.split(' ')[0]} + </p> + <p className='text-right text-xs text-red-500'> + Jatuh Tempo: - + </p> + </div> + <hr className='my-3' /> + <div className='grid grid-cols-2'> + <div> + <span className='text-caption-2 text-gray_r-11'> + No. Invoice + </span> + <p className='mt-1 font-semibold text-gray_r-12'>-</p> + </div> + <div className='text-right'> + <span className='opacity-65 text-caption-2 text-gray_r-11'> + Total Belanja + </span> + <p className='mt-1 font-semibold text-red-500 '> + {currencyFormat(invoice.amountTotal)} + </p> + </div> + </div> + </Link> + {/* {invoice.efaktur ? ( + <div className='badge-green h-fit mt-3 ml-auto flex items-center gap-x-0.5'> + <CheckIcon className='w-4 stroke-2' /> + Faktur Pajak + </div> + ) : ( + <div className='badge-red h-fit mt-3 ml-auto flex items-center gap-x-0.5'> + <ClockIcon className='w-4 stroke-2' /> + Faktur Pajak + </div> + )} */} + </div> + ))} + {invoices.data?.invoices?.map((invoice, index) => ( + <div + className='p-4 shadow border border-gray_r-3 rounded-md' + key={index} + > + <div className='grid grid-cols-2'> + <Link href={`/my/quotations/${invoice.salesOrderId}`}> + <span className='text-caption-2 text-gray_r-11'> + No. Transaksi + </span> + <h2 className='font-semibold text-black mt-1'> + {invoice.salesOrder} + </h2> + </Link> + <div className='flex gap-x-1 justify-end items-end '> + {invoice.amountResidual > 0 ? ( + new Date() > getDueDate(invoice.invoiceDateDue) ? ( + <div className='inline-flex items-end rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-red-600/20'> + Jatuh Tempo + </div> + ) : ( + <div className='badge-solid-red h-fit ml-auto'> + Belum Jatuh Tempo + </div> + ) + ) : ( + <div className='badge-solid-green h-fit ml-auto'> + Lunas + </div> + )} + {/* <EllipsisVerticalIcon + className='w-5 h-5' + onClick={() => setToOthers(invoice)} + /> */} + </div> + </div> + <Link href={`/my/invoices/${invoice.id}`}> + <div className='grid grid-cols-2 text-caption-2 text-gray_r-11 mt-2 font-normal'> + <p className='opacity-70'> + {formatTanggal(invoice.invoiceDate)} + </p> + <p className='text-right text-xs text-red-500'> + Jatuh Tempo: {formatTanggal(invoice.invoiceDateDue)} + </p> + </div> + <hr className='my-3' /> + <div className='grid grid-cols-2'> + <div> + <span className='text-caption-2 text-gray_r-11'> + No. Invoice + </span> + <p className='mt-1 font-semibold text-gray_r-12'> + {invoice.name || '-'} + </p> + </div> + <div className='text-right'> + <span className='opacity-65 text-caption-2 text-gray_r-11'> + Total Belanja + </span> + <p className='mt-1 font-semibold text-red-500 '> + {currencyFormat(invoice.amountTotal)} + </p> + </div> + </div> + </Link> + {/* {invoice.efaktur ? ( + <div className='badge-green h-fit mt-3 ml-auto flex items-center gap-x-0.5'> + <CheckIcon className='w-4 stroke-2' /> + Faktur Pajak + </div> + ) : ( + <div className='badge-red h-fit mt-3 ml-auto flex items-center gap-x-0.5'> + <ClockIcon className='w-4 stroke-2' /> + Faktur Pajak + </div> + )} */} + </div> + ))} + </div> + )} + + <Pagination + pageCount={pageCount} + currentPage={parseInt(pageNew)} + url={`/my/tempo?${toQuery(_.omit(query, ['page']))}`} + className='mt-2 mb-2' + /> + + <BottomPopup + title='Lainnya' + active={toOthers} + close={() => setToOthers(null)} + > + <div className='flex flex-col gap-y-4 mt-2'> + <button + className='text-left disabled:opacity-60' + onClick={() => { + downloadInvoice(toOthers); + setToOthers(null); + }} + > + Download Invoice + </button> + <button + className='text-left disabled:opacity-60' + disabled={!toOthers?.efaktur} + onClick={() => { + downloadTaxInvoice(toOthers); + setToOthers(null); + }} + > + Download Faktur Pajak + </button> + </div> + </BottomPopup> + </div> + </MobileView> + </> + ); +}; + +export default Tempo; |
