summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorHATEC\SPVDEV001 <tri.susilo@altama.co.id>2024-03-05 14:37:44 +0700
committerHATEC\SPVDEV001 <tri.susilo@altama.co.id>2024-03-05 14:37:44 +0700
commit39b5e05a5fcc7ca26342f37e85c6585d1dacb3a5 (patch)
treecc3a02014e31ff6a15c149cf96345c91e753c78f /src/lib
parent07138ddc724233f688de9c16de59c1b61b885520 (diff)
add address & expedisi di page quotation - template stepper approval
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/checkout/components/CheckoutSection.jsx174
-rw-r--r--src/lib/quotation/components/Quotation.jsx116
-rw-r--r--src/lib/transaction/components/Transaction.jsx396
-rw-r--r--src/lib/transaction/components/stepper.jsx64
4 files changed, 543 insertions, 207 deletions
diff --git a/src/lib/checkout/components/CheckoutSection.jsx b/src/lib/checkout/components/CheckoutSection.jsx
index 34fe19f7..7f9ea08a 100644
--- a/src/lib/checkout/components/CheckoutSection.jsx
+++ b/src/lib/checkout/components/CheckoutSection.jsx
@@ -1,35 +1,35 @@
-import Link from "next/link";
+import Link from 'next/link';
import BottomPopup from '@/core/components/elements/Popup/BottomPopup';
-import { AnimatePresence, motion } from "framer-motion";
-import { Divider, Spinner } from "@chakra-ui/react";
+import { AnimatePresence, motion } from 'framer-motion';
+import { Divider, Spinner } from '@chakra-ui/react';
export const SectionAddress = ({ address, label, url }) => {
- return (
- <div className='p-4'>
- <div className='flex justify-between items-center'>
- <div className='font-medium'>{label}</div>
- <Link className='text-caption-1' href={url}>
- Pilih Alamat Lain
- </Link>
- </div>
-
- {address && (
- <div className='mt-4 text-caption-1'>
- <div className='badge-red mb-2'>
- {address.type.charAt(0).toUpperCase() +
- address.type.slice(1) +
- ' Address'}
- </div>
- <p className='font-medium'>{address.name}</p>
- <p className='mt-2 text-gray_r-11'>{address.mobile}</p>
- <p className='mt-1 text-gray_r-11'>
- {address.street}, {address?.city?.name}
- </p>
- </div>
- )}
+ return (
+ <div className='p-4'>
+ <div className='flex justify-between items-center'>
+ <div className='font-medium'>{label}</div>
+ <Link className='text-caption-1' href={url}>
+ Pilih Alamat Lain
+ </Link>
</div>
- )
-}
+
+ {address && (
+ <div className='mt-4 text-caption-1'>
+ <div className='badge-red mb-2'>
+ {address.type.charAt(0).toUpperCase() +
+ address.type.slice(1) +
+ ' Address'}
+ </div>
+ <p className='font-medium'>{address.name}</p>
+ <p className='mt-2 text-gray_r-11'>{address.mobile}</p>
+ <p className='mt-1 text-gray_r-11'>
+ {address.street}, {address?.city?.name}
+ </p>
+ </div>
+ )}
+ </div>
+ );
+};
export const SectionValidation = ({ address }) =>
address?.rajaongkirCityId == 0 && (
@@ -129,7 +129,10 @@ export const SectionExpedisi = ({
</div>
);
-export const SectionListService = ({ listserviceExpedisi, setSelectedServiceType }) =>
+export const SectionListService = ({
+ listserviceExpedisi,
+ setSelectedServiceType,
+}) =>
listserviceExpedisi?.length > 0 && (
<>
<div className='p-4'>
@@ -169,39 +172,86 @@ export const SectionListService = ({ listserviceExpedisi, setSelectedServiceType
</>
);
- export const PickupAddress = ({ label }) => (
- <div className='p-4'>
- <div className='flex justify-between items-center'>
- <div className='font-medium'>{label}</div>
- </div>
- <div className='mt-4 text-caption-1'>
- <p className='font-medium'>Indoteknik</p>
- <p className='mt-2 mb-2 text-gray_r-11 leading-6'>
- Jl. Bandengan Utara Raya No.85, RT.3/RW.16, Penjaringan, Kec.
- Penjaringan, Kota Jkt Utara, Daerah Khusus Ibukota Jakarta, Indonesia
- Kodepos : 14440
- </p>
- <p className='mt-1 text-gray_r-11'>Telp : 021-2933 8828/29</p>
- <p className='mt-1 text-gray_r-11'>Mobile : 0813 9000 7430</p>
- </div>
+export const PickupAddress = ({ label }) => (
+ <div className='p-4'>
+ <div className='flex justify-between items-center'>
+ <div className='font-medium'>{label}</div>
</div>
- );
+ <div className='mt-4 text-caption-1'>
+ <p className='font-medium'>Indoteknik</p>
+ <p className='mt-2 mb-2 text-gray_r-11 leading-6'>
+ Jl. Bandengan Utara Raya No.85, RT.3/RW.16, Penjaringan, Kec.
+ Penjaringan, Kota Jkt Utara, Daerah Khusus Ibukota Jakarta, Indonesia
+ Kodepos : 14440
+ </p>
+ <p className='mt-1 text-gray_r-11'>Telp : 021-2933 8828/29</p>
+ <p className='mt-1 text-gray_r-11'>Mobile : 0813 9000 7430</p>
+ </div>
+ </div>
+);
+
+const extractDuration = (text) => {
+ const matches = text.match(/\d+(?:-\d+)?/g);
- const extractDuration = (text) => {
- const matches = text.match(/\d+(?:-\d+)?/g);
-
- if (matches && matches.length === 1) {
- const parts = matches[0].split('-');
- const min = parseInt(parts[0]);
- const max = parseInt(parts[1]);
-
- if (min === max) {
- return min.toString();
- }
-
- return matches[0];
+ if (matches && matches.length === 1) {
+ const parts = matches[0].split('-');
+ const min = parseInt(parts[0]);
+ const max = parseInt(parts[1]);
+
+ if (min === max) {
+ return min.toString();
}
-
- return '';
- };
- \ No newline at end of file
+
+ return matches[0];
+ }
+
+ return '';
+};
+
+export function calculateEstimatedArrival(duration) {
+ if (duration) {
+ let estimationDate = duration.split('-');
+ estimationDate[0] = parseInt(estimationDate[0]);
+ estimationDate[1] = parseInt(estimationDate[1]);
+ const from = addDays(new Date(), estimationDate[0] + 3);
+ const to = addDays(new Date(), estimationDate[1] + 3);
+
+ let etdText = `*Estimasi tiba ${formatDate(from)}`;
+
+ if (estimationDate[1] > estimationDate[0]) {
+ etdText += ` - ${formatDate(to)}`;
+ }
+
+ return etdText;
+ }
+
+ return '';
+}
+
+function addDays(date, days) {
+ const result = new Date(date);
+ result.setDate(result.getDate() + days);
+ return result;
+}
+
+function formatDate(date) {
+ const day = date.getDate();
+ const month = date.toLocaleString('default', { month: 'short' });
+ return `${day} ${month}`;
+}
+
+export function splitDuration(duration) {
+ if (duration) {
+ let estimationDate = null;
+ if (duration.includes('-')) {
+ estimationDate = duration.split('-');
+ estimationDate = parseInt(estimationDate[1]);
+ } else {
+ estimationDate = parseInt(duration);
+ }
+
+ return estimationDate;
+ }
+
+ return '';
+} \ No newline at end of file
diff --git a/src/lib/quotation/components/Quotation.jsx b/src/lib/quotation/components/Quotation.jsx
index baf1492c..fbb0627c 100644
--- a/src/lib/quotation/components/Quotation.jsx
+++ b/src/lib/quotation/components/Quotation.jsx
@@ -24,6 +24,8 @@ import {
SectionExpedisi,
SectionListService,
SectionValidation,
+ calculateEstimatedArrival,
+ splitDuration,
} from '../../checkout/components/CheckoutSection';
import addressesApi from '@/lib/address/api/addressesApi';
import { getItemAddress } from '@/core/utils/address';
@@ -64,6 +66,7 @@ const Quotation = () => {
const [biayaKirim, setBiayaKirim] = useState(0);
const [selectedExpedisiService, setselectedExpedisiService] = useState(null);
const [etd, setEtd] = useState(null);
+ const [etdFix, setEtdFix] = useState(null);
const expedisiValidation = useRef(null);
@@ -170,6 +173,19 @@ const Quotation = () => {
}
}, [selectedExpedisi]);
+ useEffect(() => {
+ if (selectedServiceType) {
+ let serviceType = selectedServiceType.split(',');
+ setBiayaKirim(serviceType[0]);
+ setselectedExpedisiService(serviceType[1]);
+ setEtd(serviceType[2]);
+ }
+ }, [selectedServiceType]);
+
+ useEffect(() => {
+ if (etd) setEtdFix(calculateEstimatedArrival(etd));
+ }, [etd]);
+
// end set up address and carrier
useEffect(() => {
@@ -248,6 +264,10 @@ const Quotation = () => {
partner_invoice_id: auth.partnerId,
user_id: auth.id,
order_line: JSON.stringify(productOrder),
+ delivery_amount: biayaKirim,
+ carrier_id: selectedCarrierId,
+ estimated_arrival_days: splitDuration(etd),
+ delivery_service_type: selectedExpedisiService,
};
const isSuccess = await checkoutApi({ data });
setIsLoading(false);
@@ -278,6 +298,68 @@ const Quotation = () => {
<Divider />
+ {selectedCarrierId == SELF_PICKUP_ID && (
+ <div className='p-4'>
+ <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-4 h-4 mr-3'
+ 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'>
+ Fitur Self Pickup, hanya berlaku untuk customer di area jakarta.
+ Apa bila memilih fitur ini, anda akan dihubungi setelah barang
+ siap diambil.
+ </div>
+ </div>
+ </div>
+ )}
+
+ {selectedCarrierId == SELF_PICKUP_ID && (
+ <PickupAddress label='Alamat Pickup' />
+ )}
+ {selectedCarrierId != SELF_PICKUP_ID && (
+ <Skeleton
+ isLoaded={!!selectedAddress.invoicing && !!selectedAddress.shipping}
+ minHeight={320}
+ >
+ <SectionAddress
+ address={selectedAddress.shipping}
+ label='Alamat Pengiriman'
+ url='/my/address?select=shipping'
+ />
+ <Divider />
+ <SectionAddress
+ address={selectedAddress.invoicing}
+ label='Alamat Penagihan'
+ url='/my/address?select=invoice'
+ />
+ </Skeleton>
+ )}
+ <Divider />
+ <SectionValidation address={selectedAddress.invoicing} />
+ <SectionExpedisi
+ address={selectedAddress.shipping}
+ listExpedisi={listExpedisi}
+ setSelectedExpedisi={setSelectedExpedisi}
+ checkWeigth={checkWeigth}
+ checkoutValidation={checkoutValidation}
+ expedisiValidation={expedisiValidation}
+ loadingRajaOngkir={loadingRajaOngkir}
+ />
+ <Divider />
+ <SectionListService
+ listserviceExpedisi={listserviceExpedisi}
+ setSelectedServiceType={setSelectedServiceType}
+ />
+
<div className='p-4 flex flex-col gap-y-4'>
{products && (
<VariantGroupCard openOnClick={false} variants={products} />
@@ -313,12 +395,25 @@ const Quotation = () => {
<div className='text-gray_r-11'>PPN 11%</div>
<div>{currencyFormat(cartCheckout?.tax)}</div>
</div>
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>
+ Biaya Kirim <p className='text-xs mt-3'>{etdFix}</p>
+ </div>
+ <div>
+ {currencyFormat(
+ Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
+ )}
+ </div>
+ </div>
</div>
<hr className='my-4 border-gray_r-6' />
<div className='flex gap-x-2 justify-between mb-4'>
<div>Grand Total</div>
<div className='font-semibold text-gray_r-12'>
- {currencyFormat(cartCheckout?.grandTotal)}
+ {currencyFormat(
+ cartCheckout?.grandTotal +
+ Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
+ )}
</div>
</div>
<p className='text-caption-2 text-gray_r-10 mb-2'>
@@ -428,6 +523,16 @@ const Quotation = () => {
<div className='text-gray_r-11'>PPN 11%</div>
<div>{currencyFormat(cartCheckout?.tax)}</div>
</div>
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>
+ Biaya Kirim <p className='text-xs mt-3'>{etdFix}</p>
+ </div>
+ <div>
+ {currencyFormat(
+ Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
+ )}
+ </div>
+ </div>
</div>
<hr className='my-4 border-gray_r-6' />
@@ -435,12 +540,15 @@ const Quotation = () => {
<div className='flex gap-x-2 justify-between mb-4'>
<div>Grand Total</div>
<div className='font-semibold text-gray_r-12'>
- {currencyFormat(cartCheckout?.grandTotal)}
+ {currencyFormat(
+ cartCheckout?.grandTotal +
+ Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
+ )}
</div>
</div>
- <p className='text-caption-2 text-gray_r-11 mb-2'>
+ {/* <p className='text-caption-2 text-gray_r-11 mb-2'>
*) Belum termasuk biaya pengiriman
- </p>
+ </p> */}
<p className='text-caption-2 text-gray_r-11 leading-5'>
Dengan melakukan pembelian melalui website Indoteknik, saya
menyetujui{' '}
diff --git a/src/lib/transaction/components/Transaction.jsx b/src/lib/transaction/components/Transaction.jsx
index 82eb1775..d4b0f92c 100644
--- a/src/lib/transaction/components/Transaction.jsx
+++ b/src/lib/transaction/components/Transaction.jsx
@@ -1,83 +1,97 @@
-import Spinner from '@/core/components/elements/Spinner/Spinner'
-import useTransaction from '../hooks/useTransaction'
-import TransactionStatusBadge from './TransactionStatusBadge'
-import Divider from '@/core/components/elements/Divider/Divider'
-import { useMemo, useRef, useState } from 'react'
-import { downloadPurchaseOrder, downloadQuotation } from '../utils/transactions'
-import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
-import uploadPoApi from '../api/uploadPoApi'
-import { toast } from 'react-hot-toast'
-import getFileBase64 from '@/core/utils/getFileBase64'
-import currencyFormat from '@/core/utils/currencyFormat'
-import VariantGroupCard from '@/lib/variant/components/VariantGroupCard'
-import { ChevronDownIcon, ChevronRightIcon, ChevronUpIcon } from '@heroicons/react/24/outline'
-import Link from '@/core/components/elements/Link/Link'
-import checkoutPoApi from '../api/checkoutPoApi'
-import cancelTransactionApi from '../api/cancelTransactionApi'
-import MobileView from '@/core/components/views/MobileView'
-import DesktopView from '@/core/components/views/DesktopView'
-import Menu from '@/lib/auth/components/Menu'
-import Image from '@/core/components/elements/Image/Image'
-import { createSlug } from '@/core/utils/slug'
-import toTitleCase from '@/core/utils/toTitleCase'
-import useAirwayBill from '../hooks/useAirwayBill'
-import Manifest from '@/lib/treckingAwb/component/Manifest'
+import Spinner from '@/core/components/elements/Spinner/Spinner';
+import useTransaction from '../hooks/useTransaction';
+import TransactionStatusBadge from './TransactionStatusBadge';
+import Divider from '@/core/components/elements/Divider/Divider';
+import { useMemo, useRef, useState } from 'react';
+import {
+ downloadPurchaseOrder,
+ downloadQuotation,
+} from '../utils/transactions';
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup';
+import uploadPoApi from '../api/uploadPoApi';
+import { toast } from 'react-hot-toast';
+import getFileBase64 from '@/core/utils/getFileBase64';
+import currencyFormat from '@/core/utils/currencyFormat';
+import VariantGroupCard from '@/lib/variant/components/VariantGroupCard';
+import {
+ ChevronDownIcon,
+ ChevronRightIcon,
+ ChevronUpIcon,
+} from '@heroicons/react/24/outline';
+import Link from '@/core/components/elements/Link/Link';
+import checkoutPoApi from '../api/checkoutPoApi';
+import cancelTransactionApi from '../api/cancelTransactionApi';
+import MobileView from '@/core/components/views/MobileView';
+import DesktopView from '@/core/components/views/DesktopView';
+import Menu from '@/lib/auth/components/Menu';
+import Image from '@/core/components/elements/Image/Image';
+import { createSlug } from '@/core/utils/slug';
+import toTitleCase from '@/core/utils/toTitleCase';
+import useAirwayBill from '../hooks/useAirwayBill';
+import Manifest from '@/lib/treckingAwb/component/Manifest';
+import useAuth from '@/core/hooks/useAuth';
+import StepApproval from './stepper';
const Transaction = ({ id }) => {
- const { transaction } = useTransaction({ id })
- const { queryAirwayBill } = useAirwayBill({ orderId: id })
-
- const [airwayBillPopup, setAirwayBillPopup] = useState(null)
-
- 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 auth = { ...useAuth(), isApprovalState: true };
+ const { transaction } = useTransaction({ id });
+ const { queryAirwayBill } = useAirwayBill({ orderId: id });
+
+ const [airwayBillPopup, setAirwayBillPopup] = useState(null);
+
+ 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 submitUploadPo = async () => {
- const file = poFile.current.files[0]
- const name = poNumber.current.value
+ const file = poFile.current.files[0];
+ const name = poNumber.current.value;
if (typeof file === 'undefined' || !name) {
- toast.error('Nomor dan Dokumen PO harus diisi')
- return
+ toast.error('Nomor dan Dokumen PO harus diisi');
+ return;
}
if (file.size > 5000000) {
- toast.error('Maksimal ukuran file adalah 5MB')
- return
+ toast.error('Maksimal ukuran file adalah 5MB');
+ return;
}
- const data = { name, file: await getFileBase64(file) }
- const isUploaded = await uploadPoApi({ id, data })
+ const data = { name, file: await getFileBase64(file) };
+ const isUploaded = await uploadPoApi({ id, data });
if (isUploaded) {
- toast.success('Berhasil upload PO')
- transaction.refetch()
- closeUploadPo()
- return
+ toast.success('Berhasil upload PO');
+ transaction.refetch();
+ closeUploadPo();
+ return;
}
- toast.error('Terjadi kesalahan internal, coba lagi nanti atau hubungi kami')
- }
-
- const [cancelTransaction, setCancelTransaction] = useState(false)
- const openCancelTransaction = () => setCancelTransaction(true)
- const closeCancelTransaction = () => setCancelTransaction(false)
+ toast.error(
+ 'Terjadi kesalahan internal, coba lagi nanti atau hubungi kami'
+ );
+ };
+
+ const [cancelTransaction, setCancelTransaction] = useState(false);
+ const openCancelTransaction = () => setCancelTransaction(true);
+ const closeCancelTransaction = () => setCancelTransaction(false);
const submitCancelTransaction = async () => {
- const isCancelled = await cancelTransactionApi({ transaction: transaction.data })
+ const isCancelled = await cancelTransactionApi({
+ transaction: transaction.data,
+ });
if (isCancelled) {
- toast.success('Berhasil batalkan transaksi')
- transaction.refetch()
+ toast.success('Berhasil batalkan transaksi');
+ transaction.refetch();
}
- closeCancelTransaction()
- }
+ closeCancelTransaction();
+ };
const checkout = async () => {
if (!transaction.data?.purchaseOrderFile) {
- toast.error('Mohon upload dokumen PO anda sebelum melanjutkan pesanan')
- return
+ toast.error('Mohon upload dokumen PO anda sebelum melanjutkan pesanan');
+ return;
}
- await checkoutPoApi({ id })
- toast.success('Berhasil melanjutkan pesanan')
- transaction.refetch()
- }
+ await checkoutPoApi({ id });
+ toast.success('Berhasil melanjutkan pesanan');
+ transaction.refetch();
+ };
const memoizeVariantGroupCard = useMemo(
() => (
@@ -102,19 +116,19 @@ const Transaction = ({ id }) => {
</div>
),
[transaction.data]
- )
+ );
if (transaction.isLoading) {
return (
<div className='flex justify-center my-6'>
<Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' />
</div>
- )
+ );
}
const closePopup = () => {
- setIdAWB(null)
- }
+ setIdAWB(null);
+ };
return (
transaction.data?.name && (
@@ -156,10 +170,18 @@ const Transaction = ({ id }) => {
<input type='file' className='form-input mt-3 py-2' ref={poFile} />
</div>
<div className='grid grid-cols-2 gap-x-3 mt-6'>
- <button type='button' className='btn-light w-full' onClick={closeUploadPo}>
+ <button
+ type='button'
+ className='btn-light w-full'
+ onClick={closeUploadPo}
+ >
Batal
</button>
- <button type='button' className='btn-solid-red w-full' onClick={submitUploadPo}>
+ <button
+ type='button'
+ className='btn-solid-red w-full'
+ onClick={submitUploadPo}
+ >
Upload
</button>
</div>
@@ -167,18 +189,27 @@ const Transaction = ({ id }) => {
<Manifest idAWB={idAWB} closePopup={closePopup}></Manifest>
<MobileView>
+ <div className='p-4'>
+ <StepApproval layer={2} status={'cancel'} className='ml-auto' />
+ </div>
<div className='flex flex-col gap-y-4 p-4'>
<DescriptionRow label='Status Transaksi'>
<div className='flex justify-end'>
<TransactionStatusBadge status={transaction.data?.status} />
</div>
</DescriptionRow>
- <DescriptionRow label='No Transaksi'>{transaction.data?.name}</DescriptionRow>
+ <DescriptionRow label='No Transaksi'>
+ {transaction.data?.name}
+ </DescriptionRow>
<DescriptionRow label='Ketentuan Pembayaran'>
{transaction.data?.paymentTerm}
</DescriptionRow>
- <DescriptionRow label='Nama Sales'>{transaction.data?.sales}</DescriptionRow>
- <DescriptionRow label='Waktu Transaksi'>{transaction.data?.dateOrder}</DescriptionRow>
+ <DescriptionRow label='Nama Sales'>
+ {transaction.data?.sales}
+ </DescriptionRow>
+ <DescriptionRow label='Waktu Transaksi'>
+ {transaction.data?.dateOrder}
+ </DescriptionRow>
</div>
<Divider />
@@ -214,25 +245,27 @@ const Transaction = ({ id }) => {
<Divider />
- <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>
+ {!auth.isApprovalState && (
+ <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>
- </div>
+ )}
<Divider />
@@ -278,7 +311,29 @@ const Transaction = ({ id }) => {
<Divider />
<div className='p-4 pt-0'>
- {transaction.data?.status == 'draft' && (
+ {transaction.data?.status == 'draft' && auth.isApprovalState && (
+ <div className='flex gap-x-2'>
+ <button
+ className='btn-yellow w-full'
+ onClick={checkout}
+ disabled={
+ transaction.data?.status === 'cancel' ? true : false
+ }
+ >
+ Approve
+ </button>
+ <button
+ className='btn-solid-red px-7 w-full'
+ onClick={checkout}
+ disabled={
+ transaction.data?.status === 'cancel' ? true : false
+ }
+ >
+ Reject
+ </button>
+ </div>
+ )}
+ {transaction.data?.status == 'draft' && !auth.isApprovalState && (
<button className='btn-yellow w-full mt-4' onClick={checkout}>
Lanjutkan Transaksi
</button>
@@ -308,10 +363,17 @@ const Transaction = ({ id }) => {
<Menu />
</div>
<div className='w-9/12 p-4 py-6 bg-white border border-gray_r-6 rounded'>
- <h1 className='text-title-sm font-semibold mb-6'>Detail Transaksi</h1>
+ <div className='flex justify-between'>
+ <h1 className='text-title-sm font-semibold mb-6'>
+ Detail Transaksi
+ </h1>
+ <StepApproval layer={2} status={'cancel'} className='ml-auto' />
+ </div>
<div className='flex items-center gap-x-2 mb-3'>
- <span className='text-h-sm font-medium'>{transaction?.data?.name}</span>
+ <span className='text-h-sm font-medium'>
+ {transaction?.data?.name}
+ </span>
<TransactionStatusBadge status={transaction?.data?.status} />
</div>
<div className='flex gap-x-4'>
@@ -322,11 +384,35 @@ const Transaction = ({ id }) => {
>
Download
</button>
- {transaction.data?.status == 'draft' && (
- <button className='btn-yellow' onClick={checkout}>
- Lanjutkan Transaksi
- </button>
- )}
+ {transaction.data?.status == 'draft' &&
+ auth.isApprovalState && (
+ <div className='flex gap-x-2'>
+ <button
+ className='btn-yellow'
+ onClick={checkout}
+ disabled={
+ transaction.data?.status === 'cancel' ? true : false
+ }
+ >
+ Approve
+ </button>
+ <button
+ className='btn-solid-red px-7'
+ onClick={checkout}
+ disabled={
+ transaction.data?.status === 'cancel' ? true : false
+ }
+ >
+ Reject
+ </button>
+ </div>
+ )}
+ {transaction.data?.status == 'draft' &&
+ !auth.isApprovalState && (
+ <button className='btn-yellow' onClick={checkout}>
+ Lanjutkan Transaksi
+ </button>
+ )}
{transaction.data?.status != 'draft' && (
<button
className='btn-light'
@@ -350,33 +436,46 @@ const Transaction = ({ id }) => {
<div>Ketentuan Pembayaran</div>
<div>: {transaction?.data?.paymentTerm}</div>
- <div>Purchase Order</div>
- <div>
- : {transaction?.data?.purchaseOrderName}{' '}
- <button
- type='button'
- className='inline-block text-danger-500'
- onClick={
- transaction.data?.purchaseOrderFile
- ? () => downloadPurchaseOrder(transaction.data)
- : openUploadPo
- }
- >
- {transaction?.data?.purchaseOrderFile ? 'Download' : 'Upload'}
- </button>
- </div>
+ {!auth.isApprovalState && (
+ <>
+ <div>Purchase Order</div>
+ <div>
+ : {transaction?.data?.purchaseOrderName}{' '}
+ <button
+ type='button'
+ className='inline-block text-danger-500'
+ onClick={
+ transaction.data?.purchaseOrderFile
+ ? () => downloadPurchaseOrder(transaction.data)
+ : openUploadPo
+ }
+ >
+ {transaction?.data?.purchaseOrderFile
+ ? 'Download'
+ : 'Upload'}
+ </button>
+ </div>
+ )
+ </>
+ )}
</div>
</div>
- <div className='text-h-sm font-semibold mt-10 mb-4'>Informasi Pelanggan</div>
+ <div className='text-h-sm font-semibold mt-10 mb-4'>
+ Informasi Pelanggan
+ </div>
<div className='grid grid-cols-2 gap-x-4'>
<div className='border border-gray_r-6 rounded p-3'>
<div className='font-medium mb-4'>Detail Pelanggan</div>
- <SectionContent address={transaction?.data?.address?.customer} />
+ <SectionContent
+ address={transaction?.data?.address?.customer}
+ />
</div>
</div>
- <div className='text-h-sm font-semibold mt-10 mb-4'>Pengiriman</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
@@ -403,7 +502,9 @@ const Transaction = ({ id }) => {
<div className='badge-red text-sm'>Belum ada pengiriman</div>
)}
- <div className='text-h-sm font-semibold mt-10 mb-4'>Rincian Pembelian</div>
+ <div className='text-h-sm font-semibold mt-10 mb-4'>
+ Rincian Pembelian
+ </div>
<table className='table-data'>
<thead>
<tr>
@@ -483,7 +584,9 @@ const Transaction = ({ id }) => {
{currencyFormat(transaction.data?.amountTax)}
</div>
- <div className='text-right whitespace-nowrap'>Biaya Pengiriman</div>
+ <div className='text-right whitespace-nowrap'>
+ Biaya Pengiriman
+ </div>
<div className='text-right font-medium'>
{currencyFormat(transaction.data?.deliveryAmount)}
</div>
@@ -578,18 +681,18 @@ const Transaction = ({ id }) => {
))} */}
</>
)
- )
-}
+ );
+};
const SectionAddress = ({ address }) => {
const [section, setSection] = useState({
customer: false,
invoice: false,
- shipping: false
- })
+ shipping: false,
+ });
const toggleSection = (name) => {
- setSection({ ...section, [name]: !section[name] })
- }
+ setSection({ ...section, [name]: !section[name] });
+ };
return (
<>
@@ -620,39 +723,50 @@ const SectionAddress = ({ address }) => {
/>
{section.invoice && <SectionContent address={address?.invoice} />} */}
</>
- )
-}
+ );
+};
const SectionButton = ({ label, active, toggle }) => (
- <button className='p-4 font-medium flex justify-between w-full' onClick={toggle}>
+ <button
+ className='p-4 font-medium flex justify-between w-full'
+ onClick={toggle}
+ >
<span>{label}</span>
- {active ? <ChevronUpIcon className='w-5' /> : <ChevronDownIcon className='w-5' />}
+ {active ? (
+ <ChevronUpIcon className='w-5' />
+ ) : (
+ <ChevronDownIcon className='w-5' />
+ )}
</button>
-)
+);
const SectionContent = ({ address }) => {
- let fullAddress = []
- if (address?.street) fullAddress.push(address.street)
- if (address?.subDistrict?.name) fullAddress.push(toTitleCase(address.subDistrict.name))
- if (address?.district?.name) fullAddress.push(toTitleCase(address.district.name))
- if (address?.city?.name) fullAddress.push(toTitleCase(address.city.name))
- fullAddress = fullAddress.join(', ')
+ let fullAddress = [];
+ if (address?.street) fullAddress.push(address.street);
+ if (address?.subDistrict?.name)
+ fullAddress.push(toTitleCase(address.subDistrict.name));
+ if (address?.district?.name)
+ fullAddress.push(toTitleCase(address.district.name));
+ if (address?.city?.name) fullAddress.push(toTitleCase(address.city.name));
+ fullAddress = fullAddress.join(', ');
return (
<div className='flex flex-col gap-y-4 p-4 md:p-0 border-t border-gray_r-6 md:border-0'>
<DescriptionRow label='Nama'>{address.name}</DescriptionRow>
<DescriptionRow label='Email'>{address.email || '-'}</DescriptionRow>
- <DescriptionRow label='No Telepon'>{address.mobile || '-'}</DescriptionRow>
+ <DescriptionRow label='No Telepon'>
+ {address.mobile || '-'}
+ </DescriptionRow>
<DescriptionRow label='Alamat'>{fullAddress}</DescriptionRow>
</div>
- )
-}
+ );
+};
const DescriptionRow = ({ children, label }) => (
<div className='grid grid-cols-2'>
<span className='text-gray_r-11'>{label}</span>
<span className='text-right leading-6'>{children}</span>
</div>
-)
+);
-export default Transaction
+export default Transaction;
diff --git a/src/lib/transaction/components/stepper.jsx b/src/lib/transaction/components/stepper.jsx
new file mode 100644
index 00000000..54243946
--- /dev/null
+++ b/src/lib/transaction/components/stepper.jsx
@@ -0,0 +1,64 @@
+import {
+ Box,
+ Step,
+ StepDescription,
+ StepIcon,
+ StepIndicator,
+ StepNumber,
+ StepSeparator,
+ StepStatus,
+ StepTitle,
+ Stepper,
+ useSteps,
+} from '@chakra-ui/react';
+import Image from 'next/image';
+
+const StepApproval = ({ layer, status }) => {
+ const steps = [
+ { title: 'Indoteknik', description: 'Contact Info', layer_approval: 1 },
+ { title: 'Manager', description: 'Date & Time', layer_approval: 2 },
+ { title: 'Director', description: 'Select Rooms', layer_approval: 3 },
+ ];
+ const { activeStep } = useSteps({
+ index: layer,
+ count: steps.length,
+ });
+ return (
+ <Stepper size='md' index={layer} colorScheme='green'>
+ {steps.map((step, index) => (
+ <Step key={index}>
+ <StepIndicator>
+ { layer === step.layer_approval && status === 'cancel' ? (
+ <StepStatus
+ complete={
+ <Image
+ src='/images/remove.png'
+ width={20}
+ height={20}
+ alt=''
+ className='w-full'
+ />
+ }
+ incomplete={<StepNumber />}
+ active={<StepNumber />}
+ />
+ ) : (
+ <StepStatus
+ complete={<StepIcon />}
+ incomplete={<StepNumber />}
+ active={<StepNumber />}
+ />
+ )}
+ </StepIndicator>
+
+ <Box flexShrink='0'>
+ <StepTitle className='md:text-xs'>{step.title}</StepTitle>
+ <StepDescription className='md:text-[8px]'>{step.description}</StepDescription>
+ </Box>
+ </Step>
+ ))}
+ </Stepper>
+ );
+};
+
+export default StepApproval;