diff options
Diffstat (limited to 'src/lib/transaction/components')
| -rw-r--r-- | src/lib/transaction/components/Transaction.jsx | 708 | ||||
| -rw-r--r-- | src/lib/transaction/components/Transactions.jsx | 328 |
2 files changed, 745 insertions, 291 deletions
diff --git a/src/lib/transaction/components/Transaction.jsx b/src/lib/transaction/components/Transaction.jsx index 8f4b2038..4d401037 100644 --- a/src/lib/transaction/components/Transaction.jsx +++ b/src/lib/transaction/components/Transaction.jsx @@ -1,8 +1,11 @@ import Spinner from '@/core/components/elements/Spinner/Spinner'; +import NextImage from 'next/image'; +import rejectImage from '../../../../public/images/reject.png'; import useTransaction from '../hooks/useTransaction'; import TransactionStatusBadge from './TransactionStatusBadge'; import Divider from '@/core/components/elements/Divider/Divider'; -import { useMemo, useRef, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; +import ImageNext from 'next/image'; import { downloadPurchaseOrder, downloadQuotation, @@ -33,12 +36,18 @@ import useAuth from '@/core/hooks/useAuth'; import StepApproval from './stepper'; import aprpoveApi from '../api/approveApi'; import rejectApi from '../api/rejectApi'; +import rejectProductApi from '../api/rejectProductApi'; +import { useRouter } from 'next/router'; const Transaction = ({ id }) => { + const router = useRouter(); + const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedProduct, setSelectedProduct] = useState(null); + const [reason, setReason] = useState(''); const auth = useAuth(); const { transaction } = useTransaction({ id }); - - const statusApprovalWeb = transaction.data?.approvalStep + + const statusApprovalWeb = transaction.data?.approvalStep; const { queryAirwayBill } = useAirwayBill({ orderId: id }); const [airwayBillPopup, setAirwayBillPopup] = useState(null); @@ -49,6 +58,7 @@ const Transaction = ({ id }) => { const [idAWB, setIdAWB] = useState(null); const openUploadPo = () => setUploadPo(true); const closeUploadPo = () => setUploadPo(false); + const submitUploadPo = async () => { const file = poFile.current.files[0]; const name = poNumber.current.value; @@ -78,7 +88,7 @@ const Transaction = ({ id }) => { const closeCancelTransaction = () => setCancelTransaction(false); const [rejectTransaction, setRejectTransaction] = useState(false); - + const openRejectTransaction = () => setRejectTransaction(true); const closeRejectTransaction = () => setRejectTransaction(false); const submitCancelTransaction = async () => { @@ -106,13 +116,13 @@ const Transaction = ({ id }) => { await aprpoveApi({ id }); toast.success('Berhasil melanjutkan approval'); transaction.refetch(); - } + }; const handleReject = async () => { - await rejectApi({ id }); - closeRejectTransaction() - transaction.refetch(); - } + await rejectApi({ id }); + closeRejectTransaction(); + transaction.refetch(); + }; const memoizeVariantGroupCard = useMemo( () => ( @@ -139,6 +149,18 @@ const Transaction = ({ id }) => { [transaction.data] ); + const memoizeVariantGroupCardReject = useMemo( + () => ( + <div className='p-4 pt-0 flex flex-col gap-y-3'> + <VariantGroupCard + variants={transaction.data?.productsRejectLine} + buyMore + /> + </div> + ), + [transaction.data] + ); + if (transaction.isLoading) { return ( <div className='flex justify-center my-6'> @@ -151,6 +173,37 @@ const Transaction = ({ id }) => { setIdAWB(null); }; + const openModal = (product) => { + setSelectedProduct(product); + setIsModalOpen(true); + }; + + const closeModal = () => { + setIsModalOpen(false); + setSelectedProduct(null); + setReason(''); + }; + + const handleRejectProduct = async () => { + try { + if (!reason.trim()) { + toast.error('Masukkan alasan terlebih dahulu'); + return; + } else { + let idSo = transaction?.data.id; + let idProduct = selectedProduct?.id; + await rejectProductApi({ idSo, idProduct, reason }); + closeModal(); + toast.success('Produk berhasil di reject'); + setTimeout(() => { + window.location.reload(); + }, 1500); + } + } catch (error) { + toast.error('Gagal reject produk. Silakan coba lagi.'); + } + }; + return ( transaction.data?.name && ( <> @@ -238,7 +291,13 @@ const Transaction = ({ id }) => { <MobileView> <div className='p-4'> - <StepApproval layer={2} status={'cancel'} className='ml-auto' /> + {auth?.feature?.soApproval && ( + <StepApproval + layer={statusApprovalWeb} + status={transaction?.data?.status} + className='ml-auto' + /> + )} </div> <div className='flex flex-col gap-y-4 p-4'> <DescriptionRow label='Status Transaksi'> @@ -293,40 +352,6 @@ const Transaction = ({ id }) => { <Divider /> - {!auth?.feature.soApproval && ( - <div className='p-4 flex flex-col gap-y-4'> - <DescriptionRow label='Purchase Order'> - {transaction.data?.purchaseOrderName || '-'} - </DescriptionRow> - <div className='flex items-center'> - <p className='text-gray_r-11 leading-none'>Dokumen PO</p> - <button - type='button' - className='btn-light py-1.5 px-3 ml-auto' - onClick={ - transaction.data?.purchaseOrderFile - ? () => downloadPurchaseOrder(transaction.data) - : openUploadPo - } - > - {transaction.data?.purchaseOrderFile ? 'Download' : 'Upload'} - </button> - </div> - </div> - )} - - <Divider /> - - <div className='font-medium p-4'>Detail Produk</div> - - {memoizeVariantGroupCard} - - <Divider /> - - <SectionAddress address={transaction.data?.address} /> - - <Divider /> - <div className='p-4'> <p className='font-medium'>Invoice</p> <div className='flex flex-col gap-y-3 mt-4'> @@ -358,34 +383,96 @@ const Transaction = ({ id }) => { <Divider /> - <div className='p-4 pt-0'> - {transaction.data?.status == 'draft' && auth?.feature.soApproval && ( - <div className='flex gap-x-2'> - <button - className='btn-yellow w-full' - onClick={checkout} - disabled={ - transaction.data?.status === 'cancel' ? true : false || auth?.webRole === statusApprovalWeb ? true : false - } - > - Approve - </button> + {!auth?.feature.soApproval && ( + <div className='p-4 flex flex-col gap-y-4'> + <DescriptionRow label='Purchase Order'> + {transaction.data?.purchaseOrderName || '-'} + </DescriptionRow> + <div className='flex items-center'> + <p className='text-gray_r-11 leading-none'>Dokumen PO</p> <button - className='btn-solid-red px-7 w-full' - onClick={checkout} - disabled={ - transaction.data?.status === 'cancel' ? true : false || auth?.webRole === statusApprovalWeb ? true : false + type='button' + className='inline-block text-danger-500' + onClick={ + transaction.data?.purchaseOrderFile + ? () => downloadPurchaseOrder(transaction.data) + : transaction?.data.invoices.length < 1 + ? openUploadPo + : '' } > - Reject + {transaction?.data?.purchaseOrderFile + ? 'Download' + : transaction?.data.invoices.length < 1 + ? 'Upload' + : '-'} </button> </div> - )} - {transaction.data?.status == 'draft' && !auth?.feature?.soApproval && ( - <button className='btn-yellow w-full mt-4' onClick={checkout}> - Lanjutkan Transaksi - </button> - )} + </div> + )} + + <Divider /> + + <div className='font-medium p-4'>Detail Produk</div> + {transaction?.data?.products.length > 0 ? ( + <div>{memoizeVariantGroupCard}</div> + ) : ( + <div className='badge-red text-sm px-2 ml-4'> + Semua produk telah di reject + </div> + )} + + {transaction?.data?.productsRejectLine.length > 0 && ( + <div> + <div className='font-medium p-4'>Detail Produk Reject</div> + {memoizeVariantGroupCardReject} + </div> + )} + + <Divider /> + + <SectionAddress address={transaction.data?.address} /> + + <Divider /> + + <div className='p-4 pt-0'> + {transaction.data?.status == 'draft' && + auth?.feature.soApproval && ( + <div className='flex gap-x-2'> + <button + className='btn-yellow w-full' + onClick={checkout} + disabled={ + transaction.data?.status === 'cancel' + ? true + : false || auth?.webRole === statusApprovalWeb + ? true + : false + } + > + Approve + </button> + <button + className='btn-solid-red px-7 w-full' + onClick={checkout} + disabled={ + transaction.data?.status === 'cancel' + ? true + : false || auth?.webRole === statusApprovalWeb + ? true + : false + } + > + Reject + </button> + </div> + )} + {transaction.data?.status == 'draft' && + !auth?.feature?.soApproval && ( + <button className='btn-yellow w-full mt-4' onClick={checkout}> + Lanjutkan Transaksi + </button> + )} <button className='btn-light w-full mt-4' disabled={transaction.data?.status != 'draft'} @@ -439,13 +526,20 @@ const Transaction = ({ id }) => { Download </button> {transaction.data?.status == 'draft' && - auth?.feature?.soApproval && auth?.webRole && ( + auth?.feature?.soApproval && + auth?.webRole && ( <div className='flex gap-x-2'> <button className='btn-yellow' onClick={handleApproval} disabled={ - transaction.data?.status === 'cancel' ? true : false || auth?.webRole === statusApprovalWeb ? true : false || statusApprovalWeb < 1 ? true : false + transaction.data?.status === 'cancel' + ? true + : false || auth?.webRole === statusApprovalWeb + ? true + : false || statusApprovalWeb < 1 + ? true + : false } > Approve @@ -454,7 +548,13 @@ const Transaction = ({ id }) => { className='btn-solid-red px-7' onClick={openRejectTransaction} disabled={ - transaction.data?.status === 'cancel' ? true : false || auth?.webRole === statusApprovalWeb ? true : false || statusApprovalWeb < 1 ? true : false + transaction.data?.status === 'cancel' + ? true + : false || auth?.webRole === statusApprovalWeb + ? true + : false || statusApprovalWeb < 1 + ? true + : false } > Reject @@ -467,15 +567,16 @@ const Transaction = ({ id }) => { Lanjutkan Transaksi </button> )} - {transaction.data?.status != 'draft' && !auth?.feature.soApproval && ( - <button - className='btn-light' - disabled={transaction.data?.status != 'waiting'} - onClick={openCancelTransaction} - > - Batalkan Transaksi - </button> - )} + {transaction.data?.status != 'draft' && + !auth?.feature.soApproval && ( + <button + className='btn-light' + disabled={transaction.data?.status != 'waiting'} + onClick={openCancelTransaction} + > + Batalkan Transaksi + </button> + )} </div> <div className='grid grid-cols-2 gap-x-6 mt-6'> @@ -490,7 +591,7 @@ const Transaction = ({ id }) => { <div>Ketentuan Pembayaran</div> <div>: {transaction?.data?.paymentTerm}</div> - {!auth?.feature?.soApproval && ( + {!auth?.feature?.soApproval ? ( <> <div>Purchase Order</div> <div> @@ -501,15 +602,24 @@ const Transaction = ({ id }) => { onClick={ transaction.data?.purchaseOrderFile ? () => downloadPurchaseOrder(transaction.data) - : openUploadPo + : transaction?.data.invoices.length < 1 + ? openUploadPo + : '' } > {transaction?.data?.purchaseOrderFile ? 'Download' - : 'Upload'} + : transaction?.data.invoices.length < 1 + ? 'Upload' + : '-'} </button> </div> </> + ) : ( + <> + <div>Site</div> + <div>: {transaction?.data?.sitePartner}</div> + </> )} </div> </div> @@ -525,157 +635,333 @@ const Transaction = ({ id }) => { /> </div> </div> - - <div className='text-h-sm font-semibold mt-10 mb-4'> - Pengiriman - </div> - <div className='grid grid-cols-3 gap-1'> - {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' - key={airway?.id} - onClick={() => setIdAWB(airway?.id)} - > - <div> - <span className='text-sm text-gray_r-11'> - No Resi : {airway?.trackingNumber || '-'}{' '} - </span> - <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'> - {airway?.delivered ? 'Pesanan Tiba' : 'Sedang Dikirim'} - </div> - <ChevronRightIcon className='w-5 stroke-2' /> + <div className='flex '> + <div className='w-1/2'> + <div className='text-h-sm font-semibold mt-10 mb-4'> + Pengiriman + </div> + {transaction?.data?.pickings.length == 0 && ( + <div className='badge-red text-sm'> + Belum ada pengiriman </div> - </button> - ))} + )} + <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> + <span className='text-sm text-gray_r-11'> + No Resi : {airway?.trackingNumber || '-'}{' '} + </span> + <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'> + {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 + </div> + {transaction.data?.invoices?.length === 0 && ( + <div className='badge-red text-sm'>Belum ada invoice</div> + )} + <div className='grid grid-cols-1 gap-1 w-2/3 '> + {transaction.data?.invoices?.map((invoice, index) => ( + <Link href={`/my/invoices/${invoice.id}`} key={index}> + <div className='shadow rounded-md p-4 text-gray_r-12 font-normal flex justify-between'> + <div> + <p className='mb-1'>{invoice?.name}</p> + <div className='flex items-center gap-x-1'> + {invoice.amountResidual > 0 ? ( + <div className='badge-red'>Belum Lunas</div> + ) : ( + <div className='badge-green'>Lunas</div> + )} + <p className='text-caption-2 text-gray_r-11'> + {currencyFormat(invoice.amountTotal)} + </p> + </div> + </div> + <ChevronRightIcon className='w-5 stroke-2' /> + </div> + </Link> + ))} + </div> + </div> </div> - {transaction?.data?.pickings.length == 0 && ( - <div className='badge-red text-sm'>Belum ada pengiriman</div> - )} - <div className='text-h-sm font-semibold mt-10 mb-4'> + <div className='text-h-sm font-semibold mt-4 mb-4'> Rincian Pembelian </div> - <table className='table-data'> - <thead> - <tr> - <th>Nama Produk</th> - <th>Diskon</th> - <th>Jumlah</th> - <th>Harga</th> - <th>Subtotal</th> - </tr> - </thead> - <tbody> - {transaction?.data?.products?.map((product) => ( - <tr key={product.id}> - <td className='flex'> - <Link - href={createSlug( - '/shop/product/', - product?.parent.name, - product?.parent.id - )} - className='w-[20%] flex-shrink-0' - > - <Image - src={product?.parent?.image} - alt={product?.name} - className='object-contain object-center border border-gray_r-6 h-32 w-full rounded-md' - /> - </Link> - <div className='px-2 text-left'> + {transaction?.data?.products?.length > 0 ? ( + <table className='table-data'> + <thead> + <tr> + <th>Nama Produk</th> + {/* <th>Diskon</th> */} + <th>Jumlah</th> + <th>Harga</th> + <th>Subtotal</th> + <th></th> + </tr> + </thead> + <tbody> + {transaction?.data?.products?.map((product) => ( + <tr key={product.id}> + <td className='flex'> <Link href={createSlug( '/shop/product/', product?.parent.name, product?.parent.id )} - className='line-clamp-2 leading-6 !text-gray_r-12 font-normal' + className='w-[20%] flex-shrink-0' > - {product?.parent?.name} + <div className='relative'> + <Image + src={product?.parent?.image} + alt={product?.name} + className='object-contain object-center border border-gray_r-6 h-32 w-full rounded-md' + /> + <div className='absolute top-0 right-4 flex mt-3'> + <div className='gambarB '> + {product.isSni && ( + <ImageNext + src='/images/sni-logo.png' + alt='SNI Logo' + className='w-2 h-4 object-contain object-top sm:h-4' + width={50} + height={50} + /> + )} + </div> + <div className='gambarC '> + {product.isTkdn && ( + <ImageNext + src='/images/TKDN.png' + alt='TKDN' + className='w-5 h-4 object-contain object-top ml-1 sm:h-4' + width={50} + height={50} + /> + )} + </div> + </div> + </div> </Link> - <div className='text-gray_r-11 mt-2'> - {product?.code}{' '} - {product?.attributes.length > 0 - ? `| ${product?.attributes.join(', ')}` - : ''} + <div className='px-2 text-left'> + <Link + href={createSlug( + '/shop/product/', + product?.parent.name, + product?.parent.id + )} + className='line-clamp-2 leading-6 !text-gray_r-12 font-normal' + > + {product?.parent?.name} + </Link> + <div className='text-gray_r-11 mt-2'> + {product?.code}{' '} + {product?.attributes.length > 0 + ? `| ${product?.attributes.join(', ')}` + : ''} + </div> </div> - </div> - </td> - <td> - {product.price.discountPercentage > 0 - ? `${product.price.discountPercentage}%` - : ''} - </td> - <td>{product.quantity}</td> - <td> - {product.price.discountPercentage > 0 && ( - <div className='line-through mb-1 text-caption-1 text-gray_r-12/70'> - {currencyFormat(product.price.price)} + </td> + {/* <td> + {product.price.discountPercentage > 0 + ? `${product.price.discountPercentage}%` + : ''} + </td> */} + <td>{product.quantity}</td> + <td> + {/* {product.price.discountPercentage > 0 && ( + <div className='line-through mb-1 text-caption-1 text-gray_r-12/70'> + {currencyFormat(product.price.price)} + </div> + )} */} + <div> + {currencyFormat(product.price.priceDiscount)} </div> - )} - <div>{currencyFormat(product.price.priceDiscount)}</div> - </td> - <td>{currencyFormat(product.price.subtotal)}</td> - </tr> - ))} - </tbody> - </table> - - <div className='flex justify-end mt-4'> - <div className='w-1/4 grid grid-cols-2 gap-y-3 text-gray_r-12/80'> - <div className='text-right'>Subtotal</div> - <div className='text-right font-medium'> - {currencyFormat(transaction.data?.amountUntaxed)} - </div> + </td> + <td>{currencyFormat(product.price.subtotal)}</td> + {/* {auth?.feature.soApproval && (auth.webRole == 2 || auth.webRole == 3) && (transaction.data.isReaject == false) && ( */} + {auth?.feature.soApproval && + (auth.webRole == 2 || auth.webRole == 3) && + router.asPath.includes('/my/quotations/') && + transaction.data?.status == 'draft' && ( + <td> + <button + className='bg-red-500 text-white py-1 px-3 rounded' + onClick={() => openModal(product)} + > + Reject + </button> + </td> + )} + </tr> + ))} + </tbody> + </table> + ) : ( + <div className='badge-red text-sm'> + Semua produk telah di reject + </div> + )} - <div className='text-right'>PPN 11%</div> - <div className='text-right font-medium'> - {currencyFormat(transaction.data?.amountTax)} + {isModalOpen && ( + <div className='fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center'> + <div + className='bg-white p-4 rounded w-96 + ease-in-out opacity-100 + transform transition-transform duration-300 scale-100' + > + <h2 className='text-lg mb-2'>Berikan Alasan</h2> + <textarea + value={reason} + onChange={(e) => setReason(e.target.value)} + className='w-full p-2 border rounded' + rows='4' + ></textarea> + <div className='mt-4 flex justify-end'> + <button + className='bg-gray-300 text-black py-1 px-3 rounded mr-2' + onClick={closeModal} + > + Batal + </button> + <button + className='bg-red-500 text-white py-1 px-3 rounded' + onClick={handleRejectProduct} + > + Reject + </button> + </div> </div> + </div> + )} - <div className='text-right whitespace-nowrap'> - Biaya Pengiriman - </div> - <div className='text-right font-medium'> - {currencyFormat(transaction.data?.deliveryAmount)} - </div> + {transaction?.data?.products?.length > 0 && ( + <div className='flex justify-end mt-4'> + <div className='w-1/4 grid grid-cols-2 gap-y-3 text-gray_r-12/80'> + <div className='text-right'>Subtotal</div> + <div className='text-right font-medium'> + {currencyFormat(transaction.data?.amountUntaxed)} + </div> - <div className='text-right'>Grand Total</div> - <div className='text-right font-medium text-gray_r-12'> - {currencyFormat(transaction.data?.amountTotal)} + <div className='text-right'>PPN 11%</div> + <div className='text-right font-medium'> + {currencyFormat(transaction.data?.amountTax)} + </div> + + <div className='text-right whitespace-nowrap'> + Biaya Pengiriman + </div> + <div className='text-right font-medium'> + {currencyFormat(transaction.data?.deliveryAmount)} + </div> + + <div className='text-right'>Grand Total</div> + <div className='text-right font-medium text-gray_r-12'> + {currencyFormat(transaction.data?.amountTotal)} + </div> </div> </div> - </div> + )} - <div className='text-h-sm font-semibold mt-10 mb-4'>Invoice</div> - <div className='grid grid-cols-3 gap-4'> - {transaction.data?.invoices?.map((invoice, index) => ( - <Link href={`/my/invoices/${invoice.id}`} key={index}> - <div className='shadow rounded-md p-4 text-gray_r-12 font-normal flex justify-between'> - <div> - <p className='mb-2'>{invoice?.name}</p> - <div className='flex items-center gap-x-1'> - {invoice.amountResidual > 0 ? ( - <div className='badge-red'>Belum Lunas</div> - ) : ( - <div className='badge-green'>Lunas</div> - )} - <p className='text-caption-2 text-gray_r-11'> - {currencyFormat(invoice.amountTotal)} - </p> - </div> - </div> - <ChevronRightIcon className='w-5 stroke-2' /> - </div> - </Link> - ))} - </div> - {transaction.data?.invoices?.length === 0 && ( - <div className='badge-red text-sm'>Belum ada invoice</div> + {transaction?.data?.productsRejectLine.length > 0 && ( + <div className='text-h-sm font-semibold mt-10 mb-4'> + Rincian Produk Reject + </div> + )} + {transaction?.data?.productsRejectLine.length > 0 && ( + <table className='table-data'> + <thead> + <tr> + <th>Nama Produk</th> + {/* <th>Diskon</th> */} + <th>Jumlah</th> + <th>Harga</th> + <th>Subtotal</th> + </tr> + </thead> + <tbody> + {transaction?.data?.productsRejectLine?.map((product) => ( + <tr key={product.id}> + <td className='flex'> + <Link + href={createSlug( + '/shop/product/', + product?.parent.name, + product?.parent.id + )} + className='w-[20%] flex-shrink-0' + > + <Image + src={product?.parent?.image} + alt={product?.name} + className='object-contain object-center border border-gray_r-6 h-32 w-full rounded-md' + /> + </Link> + <div className='px-2 text-left'> + <Link + href={createSlug( + '/shop/product/', + product?.parent.name, + product?.parent.id + )} + className='line-clamp-2 leading-6 !text-gray_r-12 font-normal' + > + {product?.parent?.name} + </Link> + <div className='text-gray_r-11 mt-2'> + {product?.code}{' '} + {product?.attributes.length > 0 + ? `| ${product?.attributes.join(', ')}` + : ''} + </div> + </div> + </td> + {/* <td> + {product.price.discountPercentage > 0 + ? `${product.price.discountPercentage}%` + : ''} + </td> */} + <td>{product.quantity}</td> + <td> + {/* {product.price.discountPercentage > 0 && ( + <div className='line-through mb-1 text-caption-1 text-gray_r-12/70'> + {currencyFormat(product.price.price)} + </div> + )} */} + <div> + {currencyFormat(product.price.priceDiscount)} + </div> + </td> + <td className='flex justify-center'> + <NextImage + src={rejectImage} + alt='Reject' + width={90} + height={30} + /> + </td> + </tr> + ))} + </tbody> + </table> )} </div> </div> diff --git a/src/lib/transaction/components/Transactions.jsx b/src/lib/transaction/components/Transactions.jsx index be63effd..92bdd276 100644 --- a/src/lib/transaction/components/Transactions.jsx +++ b/src/lib/transaction/components/Transactions.jsx @@ -1,63 +1,163 @@ -import { useRouter } from 'next/router' -import { useState } from 'react' -import { toast } from 'react-hot-toast' -import { EllipsisVerticalIcon, MagnifyingGlassIcon } from '@heroicons/react/24/outline' +import { useRouter } from 'next/router'; +import { useEffect, useState } from 'react'; +import { toast } from 'react-hot-toast'; +import { + EllipsisVerticalIcon, + MagnifyingGlassIcon, +} from '@heroicons/react/24/outline'; +import useAuth from '@/core/hooks/useAuth'; -import { downloadPurchaseOrder, downloadQuotation } from '../utils/transactions' -import useTransactions from '../hooks/useTransactions' -import currencyFormat from '@/core/utils/currencyFormat' -import cancelTransactionApi from '../api/cancelTransactionApi' -import TransactionStatusBadge from './TransactionStatusBadge' -import Spinner from '@/core/components/elements/Spinner/Spinner' -import Link from '@/core/components/elements/Link/Link' -import BottomPopup from '@/core/components/elements/Popup/BottomPopup' -import Pagination from '@/core/components/elements/Pagination/Pagination' -import { toQuery } from 'lodash-contrib' -import _ from 'lodash' -import Alert from '@/core/components/elements/Alert/Alert' -import MobileView from '@/core/components/views/MobileView' -import DesktopView from '@/core/components/views/DesktopView' -import Menu from '@/lib/auth/components/Menu' +import { + downloadPurchaseOrder, + downloadQuotation, +} from '../utils/transactions'; +import useTransactions from '../hooks/useTransactions'; +import currencyFormat from '@/core/utils/currencyFormat'; +import cancelTransactionApi from '../api/cancelTransactionApi'; +import TransactionStatusBadge from './TransactionStatusBadge'; +import Spinner from '@/core/components/elements/Spinner/Spinner'; +import Link from '@/core/components/elements/Link/Link'; +import BottomPopup from '@/core/components/elements/Popup/BottomPopup'; +import Pagination from '@/core/components/elements/Pagination/Pagination'; +import { toQuery } from 'lodash-contrib'; +import _ from 'lodash'; +import Alert from '@/core/components/elements/Alert/Alert'; +import MobileView from '@/core/components/views/MobileView'; +import DesktopView from '@/core/components/views/DesktopView'; +import Menu from '@/lib/auth/components/Menu'; +import * as XLSX from 'xlsx'; +import getSite from '../api/listSiteApi'; +import transactionsApi from '../api/transactionsApi'; const Transactions = ({ context = '' }) => { - const router = useRouter() - const { q = '', page = 1 } = router.query + const auth = useAuth(); + const router = useRouter(); + const { q = '', page = 1, site = null } = router.query; - const limit = 15 + const limit = 15; + + const [inputQuery, setInputQuery] = useState(q); + const [toOthers, setToOthers] = useState(null); + const [toCancel, setToCancel] = useState(null); + const [listSites, setListSites] = useState([]); + + const [siteFilter, setSiteFilter] = useState(site); const query = { name: q, offset: (page - 1) * limit, context, - limit - } - const { transactions } = useTransactions({ query }) + limit, + site: + siteFilter || (auth?.webRole === null && auth?.site ? auth.site : null), + }; + + const { transactions } = useTransactions({ query }); - const [inputQuery, setInputQuery] = useState(q) - const [toOthers, setToOthers] = useState(null) - const [toCancel, setToCancel] = useState(null) + const fetchSite = async () => { + const site = await getSite(); + setListSites(site.sites); + }; const submitCancelTransaction = async () => { const isCancelled = await cancelTransactionApi({ - transaction: toCancel - }) + transaction: toCancel, + }); if (isCancelled) { - toast.success('Berhasil batalkan transaksi') - transactions.refetch() + toast.success('Berhasil batalkan transaksi'); + transactions.refetch(); } - setToCancel(null) - } + setToCancel(null); + }; - const pageCount = Math.ceil(transactions?.data?.saleOrderTotal / limit) - let pageQuery = _.omit(query, ['limit', 'offset', 'context']) - pageQuery = _.pickBy(pageQuery, _.identity) - pageQuery = toQuery(pageQuery) + const pageCount = Math.ceil(transactions?.data?.saleOrderTotal / limit); + let pageQuery = _.omit(query, ['limit', 'offset', 'context']); + pageQuery = _.pickBy( + pageQuery, + (value, key) => value !== '' && !(key === 'page' && value === '1') + ); + pageQuery = toQuery(pageQuery); const handleSubmit = (e) => { - e.preventDefault() - router.push(`${router.pathname}?q=${inputQuery}`) - } + e.preventDefault(); + const queryParams = {}; + if (inputQuery) queryParams.q = inputQuery; + if (siteFilter) queryParams.site = siteFilter; + router.push({ + pathname: router.pathname, + query: queryParams, + }); + }; + + const handleSiteFilterChange = (e) => { + setSiteFilter(e.target.value); + const queryParams = {}; + if (inputQuery) queryParams.q = inputQuery; + if (e.target.value) queryParams.site = e.target.value; + router.push({ + pathname: router.pathname, + query: queryParams, + }); + }; + + const exportToExcel = (data, siteFilter) => { + const fieldsToExport = [ + 'No. Transaksi', + 'No. PO', + 'Tanggal', + 'Created By', + 'Salesperson', + 'Total', + 'Status', + ]; + const rowsToExport = []; + + data.forEach((saleOrder) => { + const row = { + 'No. Transaksi': saleOrder.name, + 'No. PO': saleOrder.purchaseOrderName || '-', + Tanggal: saleOrder.dateOrder || '-', + 'Created By': saleOrder.address.customer?.name || '-', + Salesperson: saleOrder.sales, + Total: currencyFormat(saleOrder.amountTotal), + Status: saleOrder.status, + }; + if (siteFilter) { + row['Site'] = siteFilter; + } + rowsToExport.push(row); + }); + const worksheet = XLSX.utils.json_to_sheet(rowsToExport, { + header: fieldsToExport, + }); + + const workbook = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1'); + XLSX.writeFile(workbook, 'transactions.xlsx'); + }; + + const getAllData = async () => { + const query = { + name: q, + context, + site: + siteFilter || (auth?.webRole === null && auth?.site ? auth.site : null), + }; + const queryString = toQuery(query) + const data = await transactionsApi({ query: queryString }); + return data; + }; + + const handleExportExcel = async () => { + const dataToExport = await getAllData(); + + exportToExcel(dataToExport?.saleOrders, siteFilter); + }; + + useEffect(() => { + fetchSite(); + }, []); return ( <> <MobileView> @@ -81,17 +181,23 @@ const Transactions = ({ context = '' }) => { </div> )} - {!transactions.isLoading && transactions.data?.saleOrders?.length === 0 && ( - <Alert type='info' className='text-center'> - Tidak ada transaksi - </Alert> - )} + {!transactions.isLoading && + transactions.data?.saleOrders?.length === 0 && ( + <Alert type='info' className='text-center'> + Tidak ada transaksi + </Alert> + )} {transactions.data?.saleOrders?.map((saleOrder, index) => ( - <div className='p-4 shadow border border-gray_r-3 rounded-md' key={index}> + <div + className='p-4 shadow border border-gray_r-3 rounded-md' + key={index} + > <div className='grid grid-cols-2'> <Link href={`${router.pathname}/${saleOrder.id}`}> - <span className='text-caption-2 text-gray_r-11'>No. Transaksi</span> + <span className='text-caption-2 text-gray_r-11'> + No. Transaksi + </span> <h2 className='text-danger-500 mt-1'>{saleOrder.name}</h2> </Link> <div className='flex gap-x-1 justify-end'> @@ -105,13 +211,17 @@ const Transactions = ({ context = '' }) => { <Link href={`${router.pathname}/${saleOrder.id}`}> <div className='grid grid-cols-2 mt-3'> <div> - <span className='text-caption-2 text-gray_r-11'>No. Purchase Order</span> + <span className='text-caption-2 text-gray_r-11'> + No. Purchase Order + </span> <p className='mt-1 font-medium text-gray_r-12'> {saleOrder.purchaseOrderName || '-'} </p> </div> <div className='text-right'> - <span className='text-caption-2 text-gray_r-11'>Total Invoice</span> + <span className='text-caption-2 text-gray_r-11'> + Total Invoice + </span> <p className='mt-1 font-medium text-gray_r-12'> {saleOrder.invoiceCount} Invoice </p> @@ -120,10 +230,14 @@ const Transactions = ({ context = '' }) => { <div className='grid grid-cols-2 mt-3'> <div> <span className='text-caption-2 text-gray_r-11'>Sales</span> - <p className='mt-1 font-medium text-gray_r-12'>{saleOrder.sales}</p> + <p className='mt-1 font-medium text-gray_r-12'> + {saleOrder.sales} + </p> </div> <div className='text-right'> - <span className='text-caption-2 text-gray_r-11'>Total Harga</span> + <span className='text-caption-2 text-gray_r-11'> + Total Harga + </span> <p className='mt-1 font-medium text-gray_r-12'> {currencyFormat(saleOrder.amountTotal)} </p> @@ -140,14 +254,18 @@ const Transactions = ({ context = '' }) => { className='mt-2 mb-2' /> - <BottomPopup title='Lainnya' active={toOthers} close={() => setToOthers(null)}> + <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' disabled={!toOthers?.purchaseOrderFile} onClick={() => { - downloadPurchaseOrder(toOthers) - setToOthers(null) + downloadPurchaseOrder(toOthers); + setToOthers(null); }} > Download PO @@ -156,8 +274,8 @@ const Transactions = ({ context = '' }) => { className='text-left disabled:opacity-60' disabled={toOthers?.status != 'draft'} onClick={() => { - downloadQuotation(toOthers) - setToOthers(null) + downloadQuotation(toOthers); + setToOthers(null); }} > Download Quotation @@ -166,8 +284,8 @@ const Transactions = ({ context = '' }) => { className='text-left disabled:opacity-60' disabled={toOthers?.status != 'waiting'} onClick={() => { - setToCancel(toOthers) - setToOthers(null) + setToCancel(toOthers); + setToOthers(null); }} > Batalkan Transaksi @@ -175,7 +293,11 @@ const Transactions = ({ context = '' }) => { </div> </BottomPopup> - <BottomPopup active={toCancel} close={() => setToCancel(null)} title='Batalkan Transaksi'> + <BottomPopup + active={toCancel} + close={() => setToCancel(null)} + title='Batalkan Transaksi' + > <div className='leading-7 text-gray_r-12/80'> Apakah anda yakin membatalkan transaksi{' '} <span className='underline'>{toCancel?.name}</span>? @@ -188,7 +310,11 @@ const Transactions = ({ context = '' }) => { > Ya, Batalkan </button> - <button className='btn-light flex-1' type='button' onClick={() => setToCancel(null)}> + <button + className='btn-light flex-1' + type='button' + onClick={() => setToCancel(null)} + > Batal </button> </div> @@ -205,21 +331,50 @@ const Transactions = ({ context = '' }) => { <div className='flex mb-6 items-center justify-between'> <h1 className='text-title-sm font-semibold'> Daftar Transaksi{' '} - {transactions?.data?.saleOrders ? `(${transactions?.data?.saleOrders.length})` : ''} + {transactions?.data?.saleOrders + ? `(${transactions?.data?.saleOrders.length})` + : ''} </h1> - <form className='flex gap-x-2' onSubmit={handleSubmit}> - <input - type='text' - className='form-input' - placeholder='Cari Transaksi...' - value={inputQuery} - onChange={(e) => setInputQuery(e.target.value)} - /> - <button className='btn-light bg-transparent px-3' type='submit'> - <MagnifyingGlassIcon className='w-6' /> - </button> - </form> + <div className='grid grid-cols-2 gap-2'> + {listSites?.length > 0 ? ( + <select + value={siteFilter} + onChange={handleSiteFilterChange} + className='form-input' + > + <option value=''>Pilih Site</option> + {listSites.map((site) => ( + <option value={site} key={site}> + {site} + </option> + ))} + </select> + ) : (<div></div>)} + + <form className='flex gap-x-1' onSubmit={handleSubmit}> + <input + type='text' + className='form-input' + placeholder='Cari Transaksi...' + value={inputQuery} + onChange={(e) => setInputQuery(e.target.value)} + /> + <button + className='btn-light bg-transparent px-3' + type='submit' + > + <MagnifyingGlassIcon className='w-6' /> + </button> + </form> + </div> </div> + <button + onClick={handleExportExcel} + type='button' + className='btn-solid-red px-3 py-2 mr-auto mb-2' + > + <span>Download</span> + </button> <table className='table-data'> <thead> <tr> @@ -227,6 +382,9 @@ const Transactions = ({ context = '' }) => { <th>No. PO</th> <th>Tanggal</th> <th>Created By</th> + {auth?.feature?.soApproval && ( + <th>Site</th> + )} <th className='!text-left'>Salesperson</th> <th className='!text-left'>Total</th> <th>Status</th> @@ -252,13 +410,23 @@ const Transactions = ({ context = '' }) => { {transactions.data?.saleOrders?.map((saleOrder) => ( <tr key={saleOrder.id}> <td> - <Link className='whitespace-nowrap' href={`${router.pathname}/${saleOrder.id}`}>{saleOrder.name}</Link> + <Link + className='whitespace-nowrap' + href={`${router.pathname}/${saleOrder.id}`} + > + {saleOrder.name} + </Link> </td> <td>{saleOrder.purchaseOrderName || '-'}</td> <td>{saleOrder.dateOrder || '-'}</td> <td>{saleOrder.address.customer?.name || '-'}</td> + {auth?.feature?.soApproval && ( + <td>{saleOrder.sitePartner || '-'}</td> + )} <td className='!text-left'>{saleOrder.sales}</td> - <td className='!text-left'>{currencyFormat(saleOrder.amountTotal)}</td> + <td className='!text-left'> + {currencyFormat(saleOrder.amountTotal)} + </td> <td> <div className='flex justify-center'> <TransactionStatusBadge status={saleOrder.status} /> @@ -272,14 +440,14 @@ const Transactions = ({ context = '' }) => { <Pagination pageCount={pageCount} currentPage={parseInt(page)} - url={router.pathname + pageQuery} + url={router.pathname + (pageQuery ? `?${pageQuery}` : '')} className='mt-2 mb-2' /> </div> </div> </DesktopView> </> - ) -} + ); +}; -export default Transactions +export default Transactions; |
