summaryrefslogtreecommitdiff
path: root/src/lib/transaction/components
diff options
context:
space:
mode:
authorIT Fixcomart <it@fixcomart.co.id>2025-07-31 11:02:58 +0000
committerIT Fixcomart <it@fixcomart.co.id>2025-07-31 11:02:58 +0000
commit4cf94e241af5f95f567aac952dd990852026d23f (patch)
tree5c519ac632391b34e1cc2bb31d011be37bb2d779 /src/lib/transaction/components
parentdc31efb2fec4c7b79917324d922ae820c4b5bb50 (diff)
parent04961a55929017f77ee6801d2b7ada4c05689821 (diff)
Merged in cr/repeat-order (pull request #430)
Cr/repeat order
Diffstat (limited to 'src/lib/transaction/components')
-rw-r--r--src/lib/transaction/components/Transaction.jsx615
-rw-r--r--src/lib/transaction/components/TransactionStatusBadge.jsx4
-rw-r--r--src/lib/transaction/components/Transactions.jsx1045
3 files changed, 1521 insertions, 143 deletions
diff --git a/src/lib/transaction/components/Transaction.jsx b/src/lib/transaction/components/Transaction.jsx
index 8b3a8dd0..2c1a6208 100644
--- a/src/lib/transaction/components/Transaction.jsx
+++ b/src/lib/transaction/components/Transaction.jsx
@@ -17,6 +17,7 @@ import getFileBase64 from '@/core/utils/getFileBase64';
import currencyFormat from '@/core/utils/currencyFormat';
import VariantGroupCard from '@/lib/variant/components/VariantGroupCard';
import {
+ EllipsisVerticalIcon,
ChevronDownIcon,
ChevronRightIcon,
ChevronUpIcon,
@@ -40,6 +41,10 @@ import rejectProductApi from '../api/rejectProductApi';
import { useRouter } from 'next/router';
import { gtagPurchase } from '@/core/utils/googleTag';
import { deleteItemCart } from '@/core/utils/cart';
+import {
+ downloadInvoice,
+ downloadTaxInvoice,
+} from '@/lib/invoice/utils/invoices';
import axios from 'axios';
import InformationSection from '../../treckingAwb/component/InformationSection';
import { Button } from '@chakra-ui/react';
@@ -51,18 +56,39 @@ const Transaction = ({ id }) => {
const [reason, setReason] = useState('');
const auth = useAuth();
const { transaction } = useTransaction({ id });
+ console.log('transaction', transaction);
const statusApprovalWeb = transaction.data?.approvalStep;
const [isLoading, setIsLoading] = useState(false);
const { queryAirwayBill } = useAirwayBill({ orderId: id });
const [airwayBillPopup, setAirwayBillPopup] = useState(null);
-
+ const [isOpen, setIsOpen] = useState(false);
const poNumber = useRef(null);
const poFile = useRef(null);
const [uploadPo, setUploadPo] = useState(false);
const [idAWB, setIdAWB] = useState(null);
const openUploadPo = () => setUploadPo(true);
const closeUploadPo = () => setUploadPo(false);
-
+ const [copied, setCopied] = useState(false);
+ const [toOthers, setToOthers] = useState(null);
+ const [totalAmount, setTotalAmount] = useState(0);
+ const [totalDiscountAmount, setTotalDiscountAmount] = useState(0);
+
+ useEffect(() => {
+ if (transaction?.data?.products) {
+ let calculateTotalAmount = 0;
+ let calculateTotalDiscountAmount = 0;
+ transaction.data.products.forEach((product) => {
+ calculateTotalAmount += product.price.price * product.quantity;
+ calculateTotalDiscountAmount +=
+ (product.price.price - product.price.priceDiscount) *
+ product.quantity;
+ });
+ setTotalAmount(calculateTotalAmount);
+ setTotalDiscountAmount(calculateTotalDiscountAmount);
+ }
+ }, [transaction.data, transaction.isLoading]);
+ console.log('totalAmount', totalAmount);
+ console.log('totalDiscountAmount', totalDiscountAmount);
const submitUploadPo = async () => {
const file = poFile.current.files[0];
const name = poNumber.current.value;
@@ -283,6 +309,34 @@ const Transaction = ({ id }) => {
}
};
+ const handleCopyClick = (waybillNumber) => {
+ const textToCopy = waybillNumber;
+ navigator.clipboard.writeText(textToCopy);
+ setCopied(true);
+ toast.success('No Resi Berhasil di Copy');
+ setTimeout(() => setCopied(false), 2000); // Reset copied state after 2 seconds
+ };
+
+ const formatDate = (dateString) => {
+ const months = [
+ 'Januari',
+ 'Februari',
+ 'Maret',
+ 'April',
+ 'Mei',
+ 'Juni',
+ 'Juli',
+ 'Agustus',
+ 'September',
+ 'Oktober',
+ 'November',
+ 'Desember',
+ ];
+
+ const [day, month, year] = dateString.split('/');
+ return `${day} ${months[parseInt(month, 10) - 1]} ${year}`;
+ };
+
return (
transaction.data?.name && (
<>
@@ -392,18 +446,118 @@ const Transaction = ({ id }) => {
</button>
</div>
</BottomPopup>
+
+ <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);
+ }}
+ >
+ Download PO
+ </button>
+ <button
+ className='text-left disabled:opacity-60'
+ disabled={toOthers?.status != 'draft'}
+ onClick={() => {
+ downloadQuotation(toOthers);
+ setToOthers(null);
+ }}
+ >
+ Download Quotation
+ </button>
+ <button
+ className='text-left disabled:opacity-60'
+ disabled={toOthers?.status != 'waiting'}
+ onClick={() => {
+ setToCancel(toOthers);
+ setToOthers(null);
+ }}
+ >
+ Batalkan Transaksi
+ </button>
+ </div>
+ </BottomPopup>
+
<Manifest idAWB={idAWB} closePopup={closePopup}></Manifest>
<MobileView>
- <div className='p-4'>
- {auth?.feature?.soApproval && (
+ <div className='px-4'>
+ <div className='flex flex-row w-full justify-between items-center py-2 px-3 mb-4 text-sm border border-yellow-500 text-yellow-800 rounded-lg bg-yellow-50 gap-2'>
+ <div class='flex items-center w-full ' role='alert'>
+ <svg
+ class='flex-shrink-0 inline w-4 h-4 mr-2'
+ aria-hidden='true'
+ fill='currentColor'
+ viewBox='0 0 20 20'
+ >
+ <path d='M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z' />
+ </svg>
+ <div className='text-justify flex flex-col gap-1'>
+ <span className='text-black text-xs text-start'>
+ Pesanan anda mungkin mengalami keterlambatan tiba
+ </span>
+ </div>
+ </div>
+ <span
+ className='text-red-500 text-xs hover:cursor-pointer text-nowrap z-50'
+ onClick={() => setIdAWB(transaction?.data?.pickings[0]?.id)}
+ >
+ Lihat Detail
+ </span>
+ </div>
+ </div>
+ {auth?.feature?.soApproval && (
+ <div className='p-4'>
<StepApproval
layer={statusApprovalWeb}
status={transaction?.data?.status}
className='ml-auto'
/>
- )}
+ </div>
+ )}
+
+ <div className='flex flex-row justify-between items-center gap-2 px-4'>
+ <div className='flex flex-col justify-start items-start gap-2'>
+ <div className='font-medium'>Status Transaksi</div>
+ <TransactionStatusBadge status={transaction.data?.status} />
+ </div>
+ <div>
+ <EllipsisVerticalIcon
+ className='w-5 h-5'
+ onClick={() => setToOthers(transaction?.data)}
+ />
+ </div>
</div>
+
+ {transaction.data?.invoices?.length === 0 ? (
+ <h1 className=''></h1>
+ ) : (
+ transaction.data?.invoices?.map((invoice, index) => (
+ <div
+ className='flex flex-row justify-between items-center gap-2 p-4'
+ key={index}
+ >
+ <div className=''>{invoice?.name}</div>
+ <span
+ className='text-red-500'
+ onClick={() => downloadInvoice(invoice)}
+ >
+ Download
+ </span>
+ </div>
+ ))
+ )}
+
+ <Divider />
+
<div className='flex flex-col gap-y-4 p-4'>
<DescriptionRow label='Status Transaksi'>
<div className='flex justify-end'>
@@ -416,53 +570,123 @@ const Transaction = ({ id }) => {
</div>
</DescriptionRow>
<DescriptionRow label='No Transaksi'>
- {transaction.data?.name}
+ <p className='font-semibold'>{transaction.data?.name}</p>
+ </DescriptionRow>
+ <DescriptionRow label='Tanggal Transaksi'>
+ {transaction.data?.dateOrder
+ ? formatDate(transaction.data?.dateOrder)
+ : '-'}
+ </DescriptionRow>
+ <DescriptionRow label='Purchase Order'>
+ {transaction.data?.purchaseOrderName || '-'}
</DescriptionRow>
<DescriptionRow label='Ketentuan Pembayaran'>
- {transaction.data?.paymentTerm}
+ {transaction.data?.paymentTerm || '-'}
</DescriptionRow>
<DescriptionRow label='Nama Sales'>
{transaction.data?.sales}
</DescriptionRow>
- <DescriptionRow label='Waktu Transaksi'>
- {transaction.data?.dateOrder}
- </DescriptionRow>
</div>
<Divider />
<div className='p-4'>
- <div className='font-medium'>Pengiriman</div>
- <div className='flex flex-col gap-y-3 mt-4'>
- {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>
- <p className='text-sm text-gray_r-11'>{airway?.name}</p>
- <span className='mt-2 font-medium'>
- No Resi : {airway?.trackingNumber || '-'}{' '}
- </span>
- {/*biteship*/}
- {/*<p className='mt-1 font-medium'>{airway?.name}</p>*/}
+ <div className='flex flex-row justify-between items-center'>
+ <div className='font-medium'>Info Pengiriman</div>
+ <span
+ className='text-red-500'
+ onClick={() => setIdAWB(transaction?.data?.pickings[0]?.id)}
+ >
+ Lihat Detail
+ </span>
+ </div>
+ <hr className='mt-4 mb-4 border border-gray-100' />
+ <div className='flex flex-col gap-y-4'>
+ <DescriptionRow label='Dokumen Pengiriman'>
+ <p className='text-red-500 font-semibold text-start'>
+ {transaction.data?.pickings?.length == 0
+ ? 'Belum ada pengiriman'
+ : transaction?.data?.pickings[0].name}
+ </p>
+ </DescriptionRow>
+ <DescriptionRow label='Kurir'>
+ <p className='text-start'>
+ {transaction?.data?.pickings[0]?.carrierName ? (
+ <p className=' text-nowrap'>
+ {transaction?.data?.pickings[0]?.carrierName}
+ </p>
+ ) : (
+ '-'
+ )}
+ </p>
+ </DescriptionRow>
+ <DescriptionRow label='Jenis Service'>
+ <p className='text-start'>
+ {transaction?.data?.pickings[0]?.serviceType &&
+ transaction?.data?.pickings[0]?.carrierName
+ ? transaction?.data?.pickings[0]?.serviceType
+ : '-'}
+ </p>
+ </DescriptionRow>
+ <DescriptionRow label='Nomor Resi'>
+ <div className='flex flex-row gap-1 text-start'>
+ {transaction?.data?.pickings[0]?.trackingNumber || '-'}
+ {transaction?.data?.pickings[0]?.trackingNumber && (
+ <button
+ className={`${
+ copied ? 'text-gray-400' : 'text-red-600 '
+ }`}
+ onClick={() =>
+ handleCopyClick(
+ transaction?.data?.pickings[0]?.trackingNumber
+ )
+ }
+ >
+ <svg
+ aria-hidden='true'
+ fill='none'
+ stroke='currentColor'
+ stroke-width='1.5'
+ viewBox='0 0 24 24'
+ className='w-5 h-6'
+ >
+ <path
+ d='M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75'
+ stroke-linecap='round'
+ stroke-linejoin='round'
+ ></path>
+ </svg>
+ </button>
+ )}
+ </div>
+ </DescriptionRow>
+ <DescriptionRow label='Estimasi Tiba'>
+ <p className='text-start'>
+ {transaction?.data?.pickings[0]?.eta
+ ? transaction?.data?.pickings[0]?.eta
+ : '-'}
+ </p>
+ </DescriptionRow>
+ <DescriptionRow label='Alamat Pengiriman'>
+ <div className='flex flex-col justify-start items-start'>
+ <div className='text-start text-nowrap truncate w-full'>
+ {transaction?.data?.address?.customer?.name}
</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 className='text-start'>
+ {transaction?.data?.address?.customer?.phone
+ ? transaction?.data?.address?.customer?.phone
+ : '-'}
</div>
- </button>
- ))}
+ <div className='text-start'>
+ {transaction?.data?.address?.customer?.alamatBisnis}
+ </div>
+ </div>
+ </DescriptionRow>
</div>
- {transaction?.data?.pickings == 0 && (
- <div className='badge-red text-sm px-2'>Belum ada pengiriman</div>
- )}
+
</div>
- <Divider />
+ {/* <Divider />
<div className='p-4'>
<p className='font-medium'>Invoice</p>
@@ -491,11 +715,11 @@ const Transaction = ({ id }) => {
<div className='badge-red text-sm px-2'>Belum ada invoice</div>
)}
</div>
- </div>
+ </div> */}
<Divider />
- {!auth?.feature.soApproval && (
+ {/* {!auth?.feature.soApproval && (
<div className='p-4 flex flex-col gap-y-4'>
<DescriptionRow label='Purchase Order'>
{transaction.data?.purchaseOrderName || '-'}
@@ -523,11 +747,59 @@ const Transaction = ({ id }) => {
</div>
)}
- <Divider />
+ <Divider /> */}
<div className='font-medium p-4'>Detail Produk</div>
{transaction?.data?.products.length > 0 ? (
- <div>{memoizeVariantGroupCard}</div>
+ <div className='p-4 pt-0 flex flex-col gap-y-3'>
+ <VariantGroupCard variants={transaction.data?.products} buyMore />
+ <div className='font-medium'>Rincian Pembayaran</div>
+ <div className='flex justify-between mt-1'>
+ <p className='text-gray_r-12/70'>Metode Pembayaran</p>
+ <p>
+ {transaction.data?.paymentType
+ ? transaction.data?.paymentType
+ ?.replace(/_/g, ' ')
+ .replace(/\b\w/g, (char) => char.toUpperCase())
+ : '-'}
+ </p>
+ </div>
+ <div className='flex justify-between mt-1'>
+ <p className='text-gray_r-12/70'>Berat Barang</p>
+ <p>{transaction.data?.pickings[0]?.weightTotal + ' Kg'}</p>
+ </div>
+ <hr className='mt-1 border border-gray-100' />
+ <div className='flex justify-between mt-1'>
+ <p className='text-gray_r-12/70'>Total Belanja</p>
+ <p>{currencyFormat(totalAmount)}</p>
+ </div>
+ <div className='flex justify-between mt-1'>
+ <p className='text-gray_r-12/70'>Diskon Belanja</p>
+ <p>{'- ' + currencyFormat(totalDiscountAmount)}</p>
+ </div>
+ <div className='flex justify-between mt-1'>
+ <p className='text-gray_r-12/70'>Subtotal</p>
+ <p>{currencyFormat(transaction.data?.amountUntaxed)}</p>
+ </div>
+ <div className='flex justify-between mt-1'>
+ <p className='text-gray_r-12/70'>
+ PPN {((PPN - 1) * 100).toFixed(0)}%
+ </p>
+ <p>{currencyFormat(transaction.data?.amountTax)}</p>
+ </div>
+ <div className='flex justify-between mt-1'>
+ <p className='text-gray_r-12/70'>Biaya Pengiriman</p>
+ <p>{currencyFormat(transaction.data?.deliveryAmount)}</p>
+ </div>
+ <div className='flex justify-between mt-1 font-medium'>
+ <p className='text-gray_r-12/70'>Asuransi Pengiriman</p>
+ <p>-</p>
+ </div>
+ <div className='flex justify-between mt-1 font-medium'>
+ <p>Grand Total</p>
+ <p>{currencyFormat(transaction.data?.amountTotal)}</p>
+ </div>
+ </div>
) : (
<div className='badge-red text-sm px-2 ml-4'>
Semua produk telah di reject
@@ -541,13 +813,13 @@ const Transaction = ({ id }) => {
</div>
)}
- <Divider />
+ {/* <Divider /> */}
- <SectionAddress address={transaction.data?.address} />
+ {/* <SectionAddress address={transaction.data?.address} /> */}
- <Divider />
+ {/* <Divider /> */}
- <div className='p-4 pt-0'>
+ {/* <div className='p-4 pt-0'>
{transaction.data?.status == 'draft' &&
auth?.feature.soApproval && (
<div className='flex gap-x-2'>
@@ -604,7 +876,7 @@ const Transaction = ({ id }) => {
Batalkan Transaksi
</button>
)}
- </div>
+ </div> */}
</MobileView>
<DesktopView>
@@ -646,7 +918,7 @@ const Transaction = ({ id }) => {
</span>
<TransactionStatusBadge status={transaction?.data?.status} />
</div>
- <div className='flex gap-x-4'>
+ {/* <div className='flex gap-x-4'>
<button
type='button'
className='btn-solid-red px-3 py-2 mr-auto'
@@ -709,19 +981,15 @@ const Transaction = ({ id }) => {
Batalkan Transaksi
</button>
)}
- </div>
+ </div> */}
- <div className='grid grid-cols-2 gap-x-6 mt-6'>
- <div className='grid grid-cols-2 gap-y-4'>
+ <div className='grid grid-cols-2 gap-x-6 mt-4'>
+ <div className='grid grid-cols-[35%_65%] gap-y-4'>
<div>Nama Sales</div>
<div>: {transaction?.data?.sales}</div>
<div>Tanggal Transaksi</div>
<div>: {transaction?.data?.dateOrder}</div>
- </div>
- <div className='grid grid-cols-2 gap-y-4'>
- <div>Ketentuan Pembayaran</div>
- <div>: {transaction?.data?.paymentTerm}</div>
{!auth?.feature?.soApproval ? (
<>
@@ -754,6 +1022,193 @@ const Transaction = ({ id }) => {
</>
)}
</div>
+ <div className='grid grid-cols-[35%_65%] gap-y-4'>
+ <div>Payment Term</div>
+ <div>: {transaction?.data?.paymentTerm}</div>
+
+ <div>Dokumen Pengiriman</div>
+ <div>
+ :{' '}
+ {transaction.data?.pickings?.length === 0
+ ? 'Belum ada pengiriman'
+ : transaction?.data?.pickings[0].name}
+ </div>
+
+ <div>Invoice Pembelian</div>
+ <div>
+ :{' '}
+ {transaction.data?.invoices?.length === 0
+ ? 'Belum ada invoice'
+ : transaction.data?.invoices?.map((invoice, index) => (
+ <Link
+ href={`/my/invoices/${invoice.id}`}
+ className='contents'
+ key={index}
+ >
+ {invoice?.name}
+ {/* <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>
+ <hr className='mt-4 mb-4 border border-gray-100' />
+ {/* <div className='grid grid-cols-2 gap-x-6'> */}
+ <div className='flex flex-row justify-between items-start w-full h-fit '>
+ <div className='flex flex-col w-1/2 justify-start items-start'>
+ <span className='text-h-sm font-medium mb-2'>
+ Alamat Pengiriman
+ </span>
+ <div className='grid grid-cols-[34%_2%_64%] gap-y-4'>
+ <div>Nama Penerima</div>
+ <div>: </div>
+ <div>{transaction?.data?.address?.customer?.name}</div>
+
+ <div>No. Telepon</div>
+ <div>: </div>
+ <div>
+ {transaction?.data?.address?.customer?.phone
+ ? transaction?.data?.address?.customer?.phone
+ : '-'}
+ </div>
+
+ <div>Email</div>
+ <div>: </div>
+ <div>
+ {transaction?.data?.address?.customer?.email
+ ? transaction?.data?.address?.customer?.email
+ : '-'}
+ </div>
+
+ <div>Alamat Pengiriman</div>
+ <div>: </div>
+ <div className='text-indent-[2px]'>
+ {transaction?.data?.address?.customer?.alamatBisnis}
+ </div>
+ </div>
+ </div>
+ <div className='flex flex-col w-1/2 justify-start items-start'>
+ <span className='text-h-sm font-medium mb-2'>
+ Info Pengiriman
+ </span>
+ <div className='grid grid-cols-[34%_2%_64%] gap-y-4 w-full'>
+ <div>Nomor Resi</div>
+ <div>: </div>
+ <div className='flex flex-row gap-1 '>
+ {transaction?.data?.pickings[0]?.trackingNumber || '-'}
+ {transaction?.data?.pickings[0]?.trackingNumber && (
+ <button
+ className={`${
+ copied ? 'text-gray-400' : 'text-red-600 '
+ }`}
+ onClick={() =>
+ handleCopyClick(
+ transaction?.data?.pickings[0]?.trackingNumber
+ )
+ }
+ >
+ <svg
+ aria-hidden='true'
+ fill='none'
+ stroke='currentColor'
+ stroke-width='1.5'
+ viewBox='0 0 24 24'
+ className='w-5 h-6'
+ >
+ <path
+ d='M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75'
+ stroke-linecap='round'
+ stroke-linejoin='round'
+ ></path>
+ </svg>
+ </button>
+ )}
+ </div>
+
+ <div>Kurir</div>
+ <div>: </div>
+ {transaction?.data?.pickings[0]?.carrierName ? (
+ <div className='flex flex-row w-full gap-1 items-center justify-start '>
+ <p className=' text-nowrap'>
+ {transaction?.data?.pickings[0]?.carrierName}
+ </p>
+ <span
+ className='text-red-500 text-sm font-semibold hover:cursor-pointer'
+ onClick={() =>
+ setIdAWB(transaction?.data?.pickings[0]?.id)
+ }
+ >
+ Lacak Pengiriman
+ </span>
+ </div>
+ ) : (
+ '-'
+ )}
+
+ <div>Jenis Service</div>
+ <div>: </div>
+ <div>
+ {' '}
+ {transaction?.data?.pickings[0]?.serviceType &&
+ transaction?.data?.pickings[0]?.carrierName
+ ? transaction?.data?.pickings[0]?.serviceType
+ : '-'}
+ </div>
+
+ <div>Tanggal Kirim</div>
+ <div>: </div>
+ <div>
+ {transaction?.data?.pickings[0]?.date
+ ? formatDate(transaction?.data?.pickings[0]?.date)
+ : '-'}
+ </div>
+
+ <div>Estimasi Tiba</div>
+ <div>: </div>
+ <div className='text-red-500'>
+ {transaction?.data?.pickings[0]?.eta
+ ? transaction?.data?.pickings[0]?.eta
+ : '-'}
+ </div>
+ {transaction?.data?.pickings[0] && (
+ <div className='w-full bagian-informasi col-span-3'>
+ <div
+ class='flex items-center w-fit py-2 px-3 mb-4 text-sm border border-yellow-500 text-yellow-800 rounded-lg bg-yellow-50'
+ role='alert'
+ >
+ <svg
+ class='flex-shrink-0 inline w-4 h-4 mr-2'
+ aria-hidden='true'
+ fill='currentColor'
+ viewBox='0 0 20 20'
+ >
+ <path d='M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z' />
+ </svg>
+ <div className='text-justify flex flex-col gap-1'>
+ <span className='text-black text-xs'>
+ Pesanan anda mungkin mengalami keterlambatan tiba
+ </span>
+ </div>
+ </div>
+ </div>
+ )}
+ </div>
+ </div>
</div>
<div className='flex gap-x-3'>
@@ -1045,7 +1500,49 @@ const Transaction = ({ id }) => {
)}
{transaction?.data?.products?.length > 0 && (
- <div className='flex justify-end mt-4'>
+ // <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'>
+ // PPN {((PPN - 1) * 100).toFixed(0)}%
+ // </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 className='flex justify-end mt-4 flex-col items-end'>
+ <div className='w-1/4 grid grid-cols-2 gap-y-3 text-gray_r-12/80'>
+ <div className='text-right'>Total Belanja</div>
+ <div className='text-right font-medium'>
+ {currencyFormat(totalAmount)}
+ </div>
+
+ <div className='text-right'>Total Diskon</div>
+ <div className='text-right font-medium'>
+ {'- ' + currencyFormat(totalDiscountAmount)}
+ </div>
+ </div>
+
+ <hr className='w-full border border-gray-100 mt-4 mb-4 self-stretch' />
+
<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'>
@@ -1059,15 +1556,17 @@ const Transaction = ({ id }) => {
{currencyFormat(transaction.data?.amountTax)}
</div>
- <div className='text-right whitespace-nowrap'>
- Biaya Pengiriman
- </div>
+ <div className='text-right'>Biaya Pengiriman</div>
<div className='text-right font-medium'>
{currencyFormat(transaction.data?.deliveryAmount)}
</div>
+ </div>
+ <hr className='w-full border border-gray-100 mt-4 mb-4 self-stretch' />
+
+ <div className='w-1/4 grid grid-cols-2 gap-y-3 font-semibold'>
<div className='text-right'>Grand Total</div>
- <div className='text-right font-medium text-gray_r-12'>
+ <div className='text-right'>
{currencyFormat(transaction.data?.amountTotal)}
</div>
</div>
diff --git a/src/lib/transaction/components/TransactionStatusBadge.jsx b/src/lib/transaction/components/TransactionStatusBadge.jsx
index e061587c..cb8cbcd9 100644
--- a/src/lib/transaction/components/TransactionStatusBadge.jsx
+++ b/src/lib/transaction/components/TransactionStatusBadge.jsx
@@ -14,11 +14,11 @@ const TransactionStatusBadge = ({ status }) => {
break
case 'waiting':
badgeProps.className.push('badge-yellow')
- badgeProps.text = 'Pesanan Diterima'
+ badgeProps.text = 'Pesanan Diproses'
break
case 'sale':
badgeProps.className.push('badge-yellow')
- badgeProps.text = 'Pesanan Diproses'
+ badgeProps.text = 'Pesanan Dikemas'
break
case 'shipping':
badgeProps.className.push('badge-green')
diff --git a/src/lib/transaction/components/Transactions.jsx b/src/lib/transaction/components/Transactions.jsx
index 92bdd276..acb925da 100644
--- a/src/lib/transaction/components/Transactions.jsx
+++ b/src/lib/transaction/components/Transactions.jsx
@@ -1,12 +1,13 @@
import { useRouter } from 'next/router';
-import { useEffect, useState } from 'react';
+import { useEffect, useState, useRef } from 'react';
import { toast } from 'react-hot-toast';
import {
EllipsisVerticalIcon,
MagnifyingGlassIcon,
+ ChevronDownIcon,
+ ChevronUpIcon,
} from '@heroicons/react/24/outline';
import useAuth from '@/core/hooks/useAuth';
-
import {
downloadPurchaseOrder,
downloadQuotation,
@@ -28,32 +29,110 @@ import Menu from '@/lib/auth/components/Menu';
import * as XLSX from 'xlsx';
import getSite from '../api/listSiteApi';
import transactionsApi from '../api/transactionsApi';
-
+import { motion } from 'framer-motion';
+import Image from '@/core/components/elements/Image/Image';
+import { upsertUserCart } from '~/services/cart';
+import { useProductCartContext } from '@/contexts/ProductCartContext';
+import { Swiper, SwiperSlide } from 'swiper/react';
+import { Navigation } from 'swiper';
+import 'swiper/css';
+import 'swiper/css/navigation';
+import { Calendar } from 'lucide-react';
+import DatePicker from 'react-datepicker';
+import 'react-datepicker/dist/react-datepicker.css';
+import { DateRangePicker } from 'react-date-range';
+import { addDays } from 'date-fns';
+import 'react-date-range/dist/styles.css'; // main style file
+import 'react-date-range/dist/theme/default.css'; // theme css file
+import { Popover } from '@headlessui/react';
const Transactions = ({ context = '' }) => {
const auth = useAuth();
const router = useRouter();
- const { q = '', page = 1, site = null } = router.query;
-
- const limit = 15;
-
+ const swiperRef = useRef(null);
+ const {
+ q = '',
+ page = 1,
+ site = null,
+ limit = 15,
+ status = 'all',
+ sort = 'none',
+ startDate = null,
+ endDate = new Date(),
+ } = router.query;
+ const {
+ productCart,
+ setRefreshCart,
+ setProductCart,
+ refreshCart,
+ isLoading,
+ setIsloading,
+ } = useProductCartContext();
const [inputQuery, setInputQuery] = useState(q);
const [toOthers, setToOthers] = useState(null);
const [toCancel, setToCancel] = useState(null);
const [listSites, setListSites] = useState([]);
-
+ const [isOpen, setIsOpen] = useState(false);
const [siteFilter, setSiteFilter] = useState(site);
+ const [pageNew, setPageNew] = useState(page);
+ const [limitNew, setLimitNew] = useState(limit);
+ // const [status, setStatus] = useState('idle');
+ const [statusNew, setStatusNew] = useState(status);
+ const [sortNew, setSortNew] = useState(sort);
+ const [contextNew, setcontextNew] = useState(router.query.context || context);
+ const [dateRange, setDateRange] = useState([null, null]);
+ // const [startDate, endDate] = dateRange;
+ const [isOpenCalender, setIsOpenCalender] = useState(false);
+ const [cachedAllData, setCachedAllData] = useState(null); // Simpan data "All"
+ const [currentData, setCurrentData] = useState([]); // Data yang ditampilkan
+ const calendarRef = useRef(null);
+ const [isDateSelected, setIsDateSelected] = useState(false);
+
+ const parseDate = (date) => {
+ if (!date || date === 'null') return null;
+ if (date instanceof Date) return date;
+ const [day, month, year] = date.split('/').map(Number);
+ return new Date(year, month - 1, day);
+ };
+
+ const [state, setState] = useState([
+ {
+ startDate: startDate != null || 'null' ? parseDate(startDate) : null, // Gunakan `parseDate`
+ endDate: startDate == null ? endDate : parseDate(endDate),
+ key: 'selection',
+ },
+ ]);
const query = {
name: q,
- offset: (page - 1) * limit,
- context,
- limit,
+ offset: (pageNew - 1) * limitNew,
+ context: contextNew,
+ limit: limitNew,
+ status: statusNew,
+ sort: sortNew,
+ startDate: state[0].startDate
+ ? state[0].startDate.toLocaleDateString('id-ID')
+ : null,
+ endDate: state[0]?.endDate?.toLocaleDateString('id-ID'),
site:
siteFilter || (auth?.webRole === null && auth?.site ? auth.site : null),
};
+ const statuses = [
+ { id: 'all', label: 'Semua' },
+ { id: 'quotation', label: 'Pending Quotation' },
+ { id: 'diproses', label: 'Pesanan Diproses' },
+ { id: 'dikemas', label: 'Pesanan Dikemas' },
+ { id: 'partial', label: 'Dikirim Sebagian' },
+ { id: 'dikirim', label: 'Pesanan Dikirim' },
+ { id: 'selesai', label: 'Pesanan Selesai' },
+ { id: 'cancel', label: 'Pesanan Dibatalkan' },
+ ];
+ const sortes = [
+ { id: 'none', label: 'Urutkan' },
+ { id: 'asc', label: 'dari yang terkecil' },
+ { id: 'desc', label: 'dari yang terbesar' },
+ ];
const { transactions } = useTransactions({ query });
-
const fetchSite = async () => {
const site = await getSite();
setListSites(site.sites);
@@ -70,7 +149,7 @@ const Transactions = ({ context = '' }) => {
setToCancel(null);
};
- const pageCount = Math.ceil(transactions?.data?.saleOrderTotal / limit);
+ const pageCount = Math.ceil(transactions?.data?.saleOrderTotal / limitNew);
let pageQuery = _.omit(query, ['limit', 'offset', 'context']);
pageQuery = _.pickBy(
pageQuery,
@@ -137,14 +216,75 @@ const Transactions = ({ context = '' }) => {
XLSX.writeFile(workbook, 'transactions.xlsx');
};
+ const handleExportCSV = async () => {
+ const dataToExport = await getAllData();
+
+ exportToCSV(dataToExport?.saleOrders, siteFilter);
+ };
+
+ const exportToCSV = (data, siteFilter) => {
+ const fieldsToExport = [
+ 'No. Transaksi',
+ 'No. PO',
+ 'Tanggal',
+ 'Created By',
+ 'Salesperson',
+ 'Total',
+ 'Status',
+ ];
+
+ if (siteFilter) {
+ fieldsToExport.push('Site');
+ }
+
+ const rowsToExport = data.map((saleOrder) => {
+ const row = [
+ saleOrder.name,
+ saleOrder.purchaseOrderName || '-',
+ saleOrder.dateOrder || '-',
+ saleOrder.address.customer?.name || '-',
+ saleOrder.sales,
+ currencyFormat(saleOrder.amountTotal),
+ saleOrder.status,
+ ];
+
+ if (siteFilter) {
+ row.push(siteFilter);
+ }
+
+ return row.join(',');
+ });
+
+ const csvContent =
+ 'data:text/csv;charset=utf-8,' +
+ [fieldsToExport.join(','), ...rowsToExport].join('\n');
+
+ const encodedUri = encodeURI(csvContent);
+ const link = document.createElement('a');
+ link.setAttribute('href', encodedUri);
+ link.setAttribute('download', 'transactions.csv');
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ };
+
const getAllData = async () => {
const query = {
name: q,
- context,
+ offset: (pageNew - 1) * limitNew,
+ limit: limitNew,
+ context: contextMap[statusNew], // gunakan contextMap
+ sort: sortNew,
+ startDate: state[0]?.startDate
+ ? state[0].startDate.toLocaleDateString('id-ID')
+ : null,
+ endDate: state[0]?.endDate
+ ? state[0].endDate.toLocaleDateString('id-ID')
+ : null,
site:
siteFilter || (auth?.webRole === null && auth?.site ? auth.site : null),
};
- const queryString = toQuery(query)
+ const queryString = toQuery(query);
const data = await transactionsApi({ query: queryString });
return data;
};
@@ -155,25 +295,309 @@ const Transactions = ({ context = '' }) => {
exportToExcel(dataToExport?.saleOrders, siteFilter);
};
+ const handleDownload = (format) => {
+ handleExport(format);
+ setIsOpen(false);
+ };
+
+ const handleExport = (format) => {
+ if (format === 'csv') {
+ handleExportCSV();
+ } else if (format === 'xlsx') {
+ handleExportExcel();
+ }
+ };
+
+ useEffect(() => {
+ const handleClickOutside = (event) => {
+ if (
+ calendarRef.current &&
+ !calendarRef.current.contains(event.target)
+ ) {
+ setIsOpenCalender(false);
+ }
+ };
+
+ document.addEventListener("mousedown", handleClickOutside);
+ return () => {
+ document.removeEventListener("mousedown", handleClickOutside);
+ };
+ }, []);
+
+ const startItem = 1 + (pageNew - 1) * limitNew;
+ const endItem = Math.min(
+ limitNew * pageNew,
+ transactions?.data?.saleOrderTotal
+ );
+
useEffect(() => {
fetchSite();
}, []);
+
+ const handleBuyBack = async (products) => {
+ // if (status === 'success') return;
+
+ try {
+ // setStatus('loading');
+
+ const results = await Promise.all(
+ products.map((product) =>
+ upsertUserCart({
+ userId: auth.id,
+ type: 'product',
+ id: product.id,
+ qty: product.quantity,
+ selected: true,
+ source: 'buy', // Tetap gunakan 'buy' agar bisa masuk ke halaman pembelian
+ qtyAppend: false,
+ })
+ )
+ );
+
+ // ✅ Panggil setRefreshCart(true) setiap kali satu produk berhasil ditambahkan
+
+ setRefreshCart(true);
+
+ // setStatus('idle');
+ toast.success('Semua produk berhasil ditambahkan ke keranjang belanja');
+ // Tampilkan notifikasi
+ // toast({
+ // title: 'Tambah ke keranjang',
+ // description: 'Semua produk berhasil ditambahkan ke keranjang belanja',
+ // status: 'success',
+ // duration: 3000,
+ // isClosable: true,
+ // position: 'top',
+ // });
+
+ // Redirect ke halaman checkout
+ router.push('/shop/checkout?source=buy');
+ } catch (error) {
+ console.error('Gagal menambahkan produk ke keranjang:', error);
+ // setStatus('error');
+ }
+ };
+
+
+ const handleStatusChange = async (status) => {
+ setStatusNew(status);
+ setPageNew(1);
+
+ if (status === 'all' && cachedAllData) {
+ setCurrentData(cachedAllData);
+ return;
+ }
+ const data = await fetchSite(status, 1);
+
+ if (status === 'all') {
+ setCachedAllData(data);
+ }
+
+ setCurrentData(data);
+ };
+
+ useEffect(() => {
+ setCachedAllData([]);
+ }, []);
+
+
+ const handleReset = () => {
+ setState([
+ {
+ startDate: null,
+ endDate: new Date(),
+ key: 'selection',
+ },
+ ]);
+ setIsOpenCalender(false);
+ router.push(`${router.pathname}`);
+ };
+
+ const formatDate = (dateString) => {
+ const months = [
+ 'Januari',
+ 'Februari',
+ 'Maret',
+ 'April',
+ 'Mei',
+ 'Juni',
+ 'Juli',
+ 'Agustus',
+ 'September',
+ 'Oktober',
+ 'November',
+ 'Desember',
+ ];
+
+ const [day, month, year] = dateString.split('/');
+ return `${day} ${months[parseInt(month, 10) - 1]} ${year}`;
+ };
+
return (
<>
<MobileView>
<div className='p-4 flex flex-col gap-y-4'>
- <form className='flex gap-x-3' 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-[30%_30%_40%] justify-between items-center gap-2 w-full '>
+ <select
+ value={statusNew}
+ onChange={(e) => handleStatusChange(e.target.value)}
+ className='border border-gray-300 rounded-lg px-2 py-1 text-xs bg-white shadow-sm focus:outline-none focus:ring-2 focus:ring-red-500'
+ >
+ {statuses.map((status) => (
+ <option key={status.id} value={status.id}>
+ {status.label}
+ </option>
+ ))}
+ </select>
+ <select
+ value={sortNew}
+ onChange={(e) => setSortNew(e.target.value)}
+ className='border border-gray-300 rounded-lg px-2 py-1 text-xs bg-white shadow-sm focus:outline-none focus:ring-2 focus:ring-red-500'
+ >
+ {sortes.map((status) => (
+ <option key={status.id} value={status.id}>
+ {status.label}
+ </option>
+ ))}
+ </select>
+ <div ref={calendarRef} className="relative inline-block">
+ <button
+ type='button'
+ className='p-2 w-auto h-auto cursor-pointer hover:bg-gray-100 rounded transition duration-150 ease-in-out flex items-center justify-center'
+ onClick={() => setIsOpenCalender((prev) => !prev)}
+ >
+ <span className='text-nowrap px-1 truncate flex items-center gap-1'>
+ {state[0]?.startDate ? (
+ `${state[0].startDate.toLocaleDateString()} - ${state[0].endDate.toLocaleDateString()}`
+ ) : (
+ <Calendar size={16} className="text-gray-500" />
+ )}
+ </span>
+ </button>
+ {isOpenCalender && (
+ <div className='absolute right-1 mt-2 bg-white p-4 rounded shadow-lg z-50'>
+ {/* Tombol silang di sudut kanan atas */}
+ <button
+ onClick={() => setIsOpenCalender(false)}
+ className='absolute top-2 right-2 text-gray-600 hover:text-black text-xl font-bold'
+ >
+ &times;
+ </button>
+ <DateRangePicker
+ onChange={(item) => setState([item.selection])}
+ showSelectionPreview={false}
+ maxDate={new Date()}
+ moveRangeOnFirstSelection={false}
+ months={1}
+ ranges={state}
+ className='w-full'
+ />
+ <style>{`
+ /* Atur container agar menjadi column */
+ .rdrCalendarWrapper {
+ display: flex;
+ flex-direction: column;
+ }
+ .rdrDateRangePickerWrapper {
+ display: flex;
+ flex-direction: column;
+ }
+
+ /* Pindahkan rdrStaticRanges ke atas */
+ .rdrDefinedRangesWrapper {
+ order: -1;
+ width: fit-content;
+ }
+ .rdrStaticRanges {
+ flex-direction: row;
+ margin-right: 2px;
+ }
+
+ /* Sembunyikan bagian input manual */
+ .rdrInputRanges {
+ display: none !important;
+ }
+
+ .rdrStaticRangeLabel {
+ padding: 10px 10px;
+ }
+ .rdrMonth {
+ width: -moz-available;
+ }
+ `}</style>
+ <div className='flex flex-row justify-end gap-3 mt-2'>
+ <button
+ className='px-4 py-1 bg-red-500 text-white rounded'
+ onClick={handleReset}
+ >
+ Reset
+ </button>
+ </div>
+ </div>
+ )}
+ </div>
+ {/* <div className='border border-gray-300 rounded-lg px-1 py-1 bg-white shadow-sm focus:outline-none focus:ring-2 focus:ring-red-500 text-xs'>
+ <DatePicker
+ closeOnScroll={(e) => e.target === document}
+ selectsRange={true}
+ startDate={startDate}
+ endDate={endDate}
+ dateFormat='dd/MM'
+ className='w-full'
+ maxDate={new Date()}
+ placeholderText='Semua Tanggal'
+ onChange={(update) => {
+ setDateRange(update);
+ }}
+ withPortal
+ isClearable={true}
+ />
+ </div> */}
+ </div>
+ <div className='flex flex-row justify-between items-center gap-2'>
+ <form className='flex' onSubmit={handleSubmit}>
+ <button
+ className='btn-light border-r-0 rounded-r-none bg-transparent px-3'
+ type='submit'
+ >
+ <MagnifyingGlassIcon className='w-6' />
+ </button>
+ <input
+ type='text'
+ className='form-input border-l-0 rounded-l-none text-xs'
+ placeholder='Cari Transaksi...'
+ value={inputQuery}
+ onChange={(e) => setInputQuery(e.target.value)}
+ />
+ </form>
+ <div className='flex flex-row gap-2 items-center justify-center text-nowrap'>
+ <p className='text-xs'>
+ Menampilkan {startItem}-
+ {endItem
+ ? endItem
+ : transactions?.data?.saleOrderTotal
+ ? transactions?.data?.saleOrderTotal
+ : limitNew * pageNew}{' '}
+ dari{' '}
+ {transactions?.data?.saleOrderTotal
+ ? transactions?.data?.saleOrderTotal
+ : limitNew * pageNew}
+ </p>
+ <select
+ id='limitSelect'
+ value={limitNew}
+ onChange={(e) => {
+ setLimitNew(Number(e.target.value));
+ setPageNew(1);
+ }}
+ className='border p-2 text-xs'
+ >
+ <option value={10}>10</option>
+ <option value={15}>15</option>
+ <option value={20}>20</option>
+ </select>
+ </div>
+ </div>
{transactions.isLoading && (
<div className='flex justify-center my-4'>
@@ -190,15 +614,15 @@ const Transactions = ({ context = '' }) => {
{transactions.data?.saleOrders?.map((saleOrder, index) => (
<div
- className='p-4 shadow border border-gray_r-3 rounded-md'
+ className='p-4 shadow border border-gray_r-3 text-xs rounded-md flex flex-col gap-2'
key={index}
>
- <div className='grid grid-cols-2'>
+ <div className='flex flex-row justify-between items-start'>
<Link href={`${router.pathname}/${saleOrder.id}`}>
- <span className='text-caption-2 text-gray_r-11'>
- No. Transaksi
+ <h2 className='text-danger-500'>{saleOrder.name}</h2>
+ <span className='font-medium text-black opacity-75'>
+ {formatDate(saleOrder.dateOrder.split(' ')[0]) || '-'}
</span>
- <h2 className='text-danger-500 mt-1'>{saleOrder.name}</h2>
</Link>
<div className='flex gap-x-1 justify-end'>
<TransactionStatusBadge status={saleOrder.status} />
@@ -208,8 +632,94 @@ const Transactions = ({ context = '' }) => {
/>
</div>
</div>
- <Link href={`${router.pathname}/${saleOrder.id}`}>
- <div className='grid grid-cols-2 mt-3'>
+ <div className='col-span-2 h-[1px] w-full bg-gray-300'></div>
+ <Link
+ href={`${router.pathname}/${saleOrder.id}`}
+ className='flex flex-col gap-2'
+ >
+ <div className='flex flex-row gap-2'>
+ <div className='flex w-1/5'>
+ <Image
+ src={saleOrder.products[0]?.parent?.image}
+ alt={saleOrder.products[0]?.name}
+ className='object-contain object-center border border-gray_r-6 h-32 w-full rounded-md'
+ />
+ </div>
+ <div className='flex w-4/5 flex-col gap-2 justify-start'>
+ <p className='flex flex-row gap-2'>
+ <span className=' text-black'>Nomor PO:</span>
+ <span className=' text-red-500 font-semibold'>
+ {saleOrder.purchaseOrderName || '-'}
+ </span>
+ </p>
+ <p className='line-clamp-2 leading-6 tracking-wide opacity-90 !text-gray_r-12 font-semibold truncate text-sm'>
+ {saleOrder.products[0]?.parent?.name}
+ </p>
+ <p className='opacity-85 !text-gray_r-12'>
+ {saleOrder.products[0]?.quantity} x{' '}
+ {currencyFormat(
+ saleOrder.products[0]?.price?.priceDiscount
+ )}
+ </p>
+ <div className='flex flex-row justify-start items-center'>
+ {saleOrder.products?.length > 1 && (
+ <div className='flex flex-row gap-1 justify-start items-center'>
+ {saleOrder.products
+ .slice(1, 4)
+ .map((product, index) => (
+ <Image
+ key={index} // Tambahkan key untuk setiap elemen dalam map()
+ src={product?.parent?.image}
+ alt={product?.name}
+ className='object-contain object-center border border-gray_r-6 h-8 w-8 rounded-md'
+ />
+ ))}
+ {saleOrder.products.length > 4 ? (
+ <Link
+ href={`${router.pathname}/${saleOrder?.id}`}
+ className='text-red-500 text-nowrap'
+ >
+ +{saleOrder.products.length - 4} lihat semua produk
+ </Link>
+ ) : (
+ <Link
+ href={`${router.pathname}/${saleOrder?.id}`}
+ className='text-red-500 text-nowrap'
+ >
+ Lihat semua produk
+ </Link>
+ )}
+ </div>
+ )}
+ </div>
+ <div className='flex flex-row w-full text-nowrap gap-2 text-black'>
+ <span className=''>Pesanan dibuat oleh:</span>
+ <p className=' font-semibold truncate'>
+ {saleOrder.address.customer?.name || '-'}
+ </p>
+ </div>
+ </div>
+ </div>
+ <div className='col-span-2 h-[1px] w-full bg-gray-300'></div>
+ <div className='flex flex-row gap-3 justify-between items-center text-sm'>
+ <div className='flex flex-col text-black text-xs'>
+ <p className='font-extralight'>Total Harga</p>
+ <p className='font-semibold'>
+ {currencyFormat(saleOrder.amountTotal)}
+ </p>
+ </div>
+ <div>
+ <button
+ type='button'
+ onClick={() => handleBuyBack(saleOrder.products)}
+ className='flex-1 py-2 btn-solid-red text-nowrap'
+ >
+ Beli Lagi
+ </button>
+ </div>
+ </div>
+
+ {/* <div className='grid grid-cols-2 mt-3'>
<div>
<span className='text-caption-2 text-gray_r-11'>
No. Purchase Order
@@ -226,8 +736,8 @@ const Transactions = ({ context = '' }) => {
{saleOrder.invoiceCount} Invoice
</p>
</div>
- </div>
- <div className='grid grid-cols-2 mt-3'>
+ </div> */}
+ {/* <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'>
@@ -242,15 +752,16 @@ const Transactions = ({ context = '' }) => {
{currencyFormat(saleOrder.amountTotal)}
</p>
</div>
- </div>
+ </div> */}
</Link>
</div>
))}
<Pagination
pageCount={pageCount}
- currentPage={parseInt(page)}
- url={router.pathname + pageQuery}
+ currentPage={parseInt(pageNew)}
+ // url={router.pathname + pageQuery}
+ url={`${router.pathname}?${toQuery(_.omit(query, ['page']))}`}
className='mt-2 mb-2'
/>
@@ -328,63 +839,430 @@ const Transactions = ({ context = '' }) => {
<Menu />
</div>
<div className='w-9/12 p-4 bg-white border border-gray_r-6 rounded'>
- <div className='flex mb-6 items-center justify-between'>
+ <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?.saleOrderTotal})`
: ''}
</h1>
- <div className='grid grid-cols-2 gap-2'>
- {listSites?.length > 0 ? (
+ <div className='relative inline-block text-left'>
+ <button
+ onClick={() => setIsOpen(!isOpen)}
+ type='button'
+ className='btn-light bg-slate-50 mt-3 w-full gap-2 items-center flex flex-row !text-gray_r-11 px-4 py-3 mb-2'
+ >
+ <p>Export</p>
+ <motion.div
+ animate={{ rotate: isOpen ? 180 : 0 }}
+ transition={{ duration: 0.2, ease: 'easeInOut' }}
+ >
+ <ChevronDownIcon className='w-5' />
+ </motion.div>
+ </button>
+
+ {isOpen && (
+ <motion.div
+ initial={{ opacity: 0, y: -10 }}
+ animate={{ opacity: 1, y: 0 }}
+ exit={{ opacity: 0, y: -10 }}
+ transition={{ duration: 0.2, ease: 'easeInOut' }}
+ className='absolute mt-2 w-fit py-1 bg-white border border-gray-300 rounded-md shadow-lg'
+ >
+ <button
+ onClick={() => handleDownload('csv')}
+ className='block w-full px-4 py-2 text-left hover:bg-gray-200 text-nowrap'
+ >
+ Download CSV
+ </button>
+ <button
+ onClick={() => handleDownload('xlsx')}
+ className='block w-full px-4 py-2 text-left hover:bg-gray-200 text-nowrap'
+ >
+ Download XLSX
+ </button>
+ </motion.div>
+ )}
+ </div>
+ </div>
+ <div className=''>
+ <div
+ class='flex items-center p-4 mb-4 text-sm border border-yellow-500 text-yellow-800 rounded-lg bg-yellow-50'
+ role='alert'
+ >
+ <svg
+ class='flex-shrink-0 inline w-5 h-5 mr-2'
+ aria-hidden='true'
+ fill='currentColor'
+ viewBox='0 0 20 20'
+ >
+ <path d='M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z' />
+ </svg>
+ <span class='sr-only'>Info</span>
+ <div className='text-justify flex flex-col gap-1'>
+ <p className='font-bold text-black'>Info Transaksi</p>
+ <span className='text-black'>
+ Gunakan filter status untuk mempermudah pencarian transaksi anda di Daftar Transaksi
+ </span>
+ </div>
+ </div>
+ </div>
+ <div className='flex flex-col gap-y-2 border rounded-lg mb-2 w-full'>
+ <div className='p-2'>
+ <div className='flex items-center space-x-3'>
+ <span className='text-base font-semibold text-gray-600'>
+ Status
+ </span>
+ <div className="relative w-full overflow-hidden">
+ {/* Container flex: tombol prev - swiper - tombol next */}
+ <div className="flex items-center space-x-2">
+
+ {/* Prev */}
+ <button className="custom-prev w-8 h-8 flex-shrink-0 flex items-center justify-center bg-white border rounded-full shadow z-1">
+ <svg className="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
+ </svg>
+ </button>
+
+ {/* Swiper container scrollable */}
+ <div className="w-full overflow-hidden">
+ <Swiper
+ spaceBetween={10}
+ slidesPerView="auto"
+ className="status-swiper"
+ modules={[Navigation]}
+ navigation={{
+ nextEl: '.custom-next',
+ prevEl: '.custom-prev',
+ }}
+ >
+ {statuses.map((status) => (
+ <SwiperSlide key={status.id} className="!w-auto">
+ <button
+ className={`px-4 py-1 text-sm font-medium border rounded-lg transition whitespace-nowrap
+ ${statusNew === status.id
+ ? 'border-red-500 text-red-500 bg-white'
+ : 'border-gray-300 text-gray-400 bg-gray-100 hover:bg-gray-200'
+ }`}
+ onClick={() => handleStatusChange(status.id)}
+ >
+ {status.label}
+ </button>
+ </SwiperSlide>
+ ))}
+ </Swiper>
+ </div>
+
+ {/* Next */}
+ <button className="custom-next w-8 h-8 flex-shrink-0 flex items-center justify-center bg-white border rounded-full shadow z-10">
+ <svg className="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
+ </svg>
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div className='flex flex-row items-center justify-between mb-2 p-2'>
+ <div className='flex flex-col gap-2 pb-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 className='flex flex-row gap-4 items-center justify-center'>
+ <p>
+ Menampilkan {startItem}-
+ {endItem
+ ? endItem
+ : transactions?.data?.saleOrderTotal
+ ? transactions?.data?.saleOrderTotal
+ : limitNew * pageNew}{' '}
+ dari{' '}
+ {transactions?.data?.saleOrderTotal
+ ? transactions?.data?.saleOrderTotal
+ : limitNew * pageNew}
+ </p>
<select
- value={siteFilter}
- onChange={handleSiteFilterChange}
- className='form-input'
+ id='limitSelect'
+ value={limitNew}
+ onChange={(e) => {
+ setLimitNew(Number(e.target.value));
+ setPageNew(1);
+ }}
+ className='border p-2'
>
- <option value=''>Pilih Site</option>
- {listSites.map((site) => (
- <option value={site} key={site}>
- {site}
- </option>
- ))}
+ <option value={10}>10</option>
+ <option value={15}>15</option>
+ <option value={20}>20</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 ref={calendarRef} className="relative inline-block">
+ <button
+ type='button'
+ className='p-2 w-auto h-auto cursor-pointer border hover:bg-gray-100 rounded transition duration-150 ease-in-out flex items-center justify-center'
+ onClick={() => setIsOpenCalender((prev) => !prev)}
+ >
+ <span className='text-nowrap px-1 truncate flex items-center gap-1'>
+ {state[0]?.startDate ? (
+ `${state[0].startDate.toLocaleDateString()} - ${state[0].endDate.toLocaleDateString()}`
+ ) : (
+ <Calendar size={16} className="text-gray-500" />
+ )}
+ </span>
+ </button>
+ {isOpenCalender && (
+ <div className='absolute right-10 mt-2 bg-white p-4 rounded shadow-lg z-50'>
+ {/* Tombol silang di sudut kanan atas */}
+ <button
+ onClick={() => setIsOpenCalender(false)}
+ className='absolute top-2 right-2 text-gray-600 hover:text-black text-xl font-bold'
+ >
+ &times;
+ </button>
+ <DateRangePicker
+ onChange={(item) => setState([item.selection])}
+ showSelectionPreview={false}
+ maxDate={new Date()}
+ moveRangeOnFirstSelection={false}
+ months={1}
+ ranges={state}
+ className='w-full'
+ />
+ <style>{`
+ /* Atur container agar menjadi column */
+ .rdrCalendarWrapper {
+ display: flex;
+ flex-direction: column;
+ }
+ .rdrDateRangePickerWrapper {
+ display: flex;
+ flex-direction: column;
+ }
+
+ /* Pindahkan rdrStaticRanges ke atas */
+ .rdrDefinedRangesWrapper {
+ order: -1;
+ width: fit-content;
+ }
+ .rdrStaticRanges {
+ flex-direction: row;
+ margin-right: 2px;
+ }
+
+ /* Sembunyikan bagian input manual */
+ .rdrInputRanges {
+ display: none !important;
+ }
+
+ .rdrStaticRangeLabel {
+ padding: 10px 10px;
+ }
+ .rdrMonth {
+ width: -moz-available;
+ }
+ `}</style>
+ <div className='flex flex-row justify-end gap-3 mt-2'>
+ <button
+ className='px-4 py-1 bg-red-500 text-white rounded'
+ onClick={handleReset}
+ >
+ Reset
+ </button>
+ </div>
+ </div>
+ )}
+ </div>
+ </div>
</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'>
+ <div className='flex justify-center items-center'>
+ {!transactions.isLoading &&
+ transactions?.data?.saleOrders?.length == 0 && (
+ <div className='justify-center p-4'>
+ <p className='text-gray-500 text-center '>
+ Tidak Ada Transaksi
+ </p>
+ </div>
+ )}
+
+ {transactions.isLoading && (
+ <div className='flex justify-center items-center my-2'>
+ <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' />
+ </div>
+ )}
+
+ {!transactions.isLoading &&
+ transactions &&
+ transactions.data?.saleOrders?.length > 0 && (
+ <div className='flex flex-col gap-4 w-full'>
+ {transactions.data.saleOrders.map((saleOrder, index) => (
+ <div
+ key={index}
+ className='border py-2 px-4 hover:border-red-500 w-full rounded-lg data-item'
+ >
+ <Link
+ href={`${router.pathname}/${saleOrder?.id}`}
+ className='hover:border-red-500 block w-full'
+ >
+ <div className='flex flex-row justify-between items-center py-2'>
+ <div className='flex justify-center gap-3'>
+ <TransactionStatusBadge
+ status={saleOrder.status}
+ />
+ <p className='text-red-500'>{saleOrder.name}</p>
+ <p className='text-black'>
+ Salesperson:{' '}
+ {
+ <span className='font-semibold'>
+ {saleOrder.sales}
+ </span>
+ }
+ </p>
+ </div>
+ <div className='text-black'>
+ Tanggal Pesanan:{' '}
+ <span className='font-semibold'>
+ {formatDate(
+ saleOrder.dateOrder.split(' ')[0]
+ ) || '-'}
+ </span>
+ </div>
+ </div>
+ <hr className='mt-3 mb-3 border border-gray-100' />
+ <div className='flex flex-row gap-2 justify-between items-center '>
+ <div className='flex justify-start w-3/4 flex-col gap-2'>
+ <div className='flex gap-2'>
+ <div className='min-w-36 max-w-36'>
+ <Image
+ src={saleOrder.products[0]?.parent?.image}
+ alt={saleOrder.products[0]?.name}
+ className='object-contain object-center border border-gray_r-6 h-32 w-full rounded-md'
+ />
+ </div>
+ <div className='flex flex-col gap-3 justify-start truncate w-full'>
+ <p className='flex flex-row gap-2'>
+ <span className='text-sm text-black'>
+ Nomor PO:
+ </span>
+ <span className='text-sm text-red-500 font-semibold'>
+ {saleOrder.purchaseOrderName || '-'}
+ </span>
+ </p>
+ <p className='line-clamp-2 leading-6 tracking-wide opacity-90 !text-gray_r-12 font-semibold text-nowrap '>
+ {saleOrder.products[0]?.parent?.name}
+ </p>
+ <p className='opacity-85 !text-gray_r-12'>
+ {saleOrder.products[0]?.quantity} x{' '}
+ {currencyFormat(
+ saleOrder.products[0]?.price
+ ?.priceDiscount
+ )}
+ </p>
+ <div className='flex flex-row justify-start items-center'>
+ {saleOrder.products?.length > 1 && (
+ <div className='flex flex-row gap-1 justify-start items-center'>
+ {saleOrder.products
+ .slice(1, 4)
+ .map((product, index) => (
+ <Image
+ key={index} // Tambahkan key untuk setiap elemen dalam map()
+ src={product?.parent?.image}
+ alt={product?.name}
+ className='object-contain object-center border border-gray_r-6 h-16 w-16 rounded-md'
+ />
+ ))}
+ {saleOrder.products.length > 4 ? (
+ <Link
+ href={`${router.pathname}/${saleOrder?.id}`}
+ className='text-red-500 text-nowrap'
+ >
+ +{saleOrder.products.length - 4} lihat semua produk
+ </Link>
+ ) : (
+ <Link
+ href={`${router.pathname}/${saleOrder?.id}`}
+ className='text-red-500 text-nowrap'
+ >
+ Lihat semua produk
+ </Link>
+ )}
+ </div>
+ )}
+ </div>
+ </div>
+ </div>
+ <div className='flex flex-row w-full text-nowrap gap-2 text-black'>
+ <span className='text-sm'>
+ Pesanan dibuat oleh:
+ </span>
+ <p className='text-sm font-semibold'>
+ {saleOrder.address.customer?.name || '-'}
+ </p>
+ </div>
+ </div>
+ <div className='w-[1px] h-24 bg-gray-300'></div>
+ <div className='w-1/4 flex flex-row gap-3 justify-center items-center'>
+ <div className='flex flex-col text-black'>
+ <p>Total Harga</p>
+ <p className='font-bold'>
+ {currencyFormat(saleOrder.amountTotal)}
+ </p>
+ </div>
+ <div>
+ <button
+ type='button'
+ onClick={() =>
+ handleBuyBack(saleOrder.products)
+ }
+ className='flex-1 py-2 btn-solid-red text-nowrap'
+ >
+ Beli Lagi
+ </button>
+ </div>
+ </div>
+ </div>
+ </Link>
+ </div>
+ ))}
+ </div>
+ )}
+ </div>
+ {/* <table className='table-data'>
<thead>
<tr>
<th>No. Transaksi</th>
<th>No. PO</th>
<th>Tanggal</th>
<th>Created By</th>
- {auth?.feature?.soApproval && (
- <th>Site</th>
- )}
+ {auth?.feature?.soApproval && <th>Site</th>}
<th className='!text-left'>Salesperson</th>
<th className='!text-left'>Total</th>
<th>Status</th>
@@ -435,12 +1313,13 @@ const Transactions = ({ context = '' }) => {
</tr>
))}
</tbody>
- </table>
+ </table> */}
<Pagination
pageCount={pageCount}
- currentPage={parseInt(page)}
- url={router.pathname + (pageQuery ? `?${pageQuery}` : '')}
+ currentPage={parseInt(pageNew)}
+ // url={router.pathname + (pageQuery ? `?${pageQuery}` : '')}
+ url={`${router.pathname}?${toQuery(_.omit(query, ['page']))}`}
className='mt-2 mb-2'
/>
</div>