diff options
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/transaction/api/rejectProductApi.js | 9 | ||||
| -rw-r--r-- | src/lib/transaction/components/Transaction.jsx | 380 | ||||
| -rw-r--r-- | src/lib/variant/components/VariantCard.jsx | 98 |
3 files changed, 379 insertions, 108 deletions
diff --git a/src/lib/transaction/api/rejectProductApi.js b/src/lib/transaction/api/rejectProductApi.js new file mode 100644 index 00000000..e03c7975 --- /dev/null +++ b/src/lib/transaction/api/rejectProductApi.js @@ -0,0 +1,9 @@ +import odooApi from '@/core/api/odooApi' + +const rejectProductApi = async ({ idSo, idProduct, reason }) => { + const dataCheckout = await odooApi('POST', `/api/v1/sale_order/${idSo}/reject/${idProduct}`, { + reason_reject: reason + }); + return dataCheckout +} +export default rejectProductApi
\ No newline at end of file diff --git a/src/lib/transaction/components/Transaction.jsx b/src/lib/transaction/components/Transaction.jsx index c6152ca9..cd3613c0 100644 --- a/src/lib/transaction/components/Transaction.jsx +++ b/src/lib/transaction/components/Transaction.jsx @@ -34,8 +34,14 @@ 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 }); @@ -141,6 +147,15 @@ 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'> @@ -153,6 +168,38 @@ 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 && ( <> @@ -301,6 +348,37 @@ const Transaction = ({ id }) => { <Divider /> + <div className='p-4'> + <p className='font-medium'>Invoice</p> + <div className='flex flex-col gap-y-3 mt-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> + ))} + {transaction.data?.invoices?.length === 0 && ( + <div className='badge-red text-sm px-2'>Belum ada invoice</div> + )} + </div> + </div> + + <Divider /> + {!auth?.feature.soApproval && ( <div className='p-4 flex flex-col gap-y-4'> <DescriptionRow label='Purchase Order'> @@ -326,8 +404,20 @@ const Transaction = ({ id }) => { <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> + )} - {memoizeVariantGroupCard} + {transaction?.data?.productsRejectLine.length > 0 && ( + <div> + <div className='font-medium p-4'>Detail Produk Reject</div> + {memoizeVariantGroupCardReject} + </div> + )} <Divider /> @@ -335,37 +425,6 @@ const Transaction = ({ id }) => { <Divider /> - <div className='p-4'> - <p className='font-medium'>Invoice</p> - <div className='flex flex-col gap-y-3 mt-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> - ))} - {transaction.data?.invoices?.length === 0 && ( - <div className='badge-red text-sm px-2'>Belum ada invoice</div> - )} - </div> - </div> - - <Divider /> - <div className='p-4 pt-0'> {transaction.data?.status == 'draft' && auth?.feature.soApproval && ( @@ -562,50 +621,83 @@ 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> + )} + <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> - </button> - ))} + </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?.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'> @@ -683,11 +775,56 @@ const Transaction = ({ id }) => { <div>{currencyFormat(product.price.priceDiscount)}</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/")) && ( + <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> + )} + + {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> + )} + {transaction?.data?.products?.map((product) => ( <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> @@ -713,33 +850,84 @@ const Transaction = ({ id }) => { </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>{currencyFormat(product.quantity * product.price.priceDiscount)}</td> + </tr> + ))} + </tbody> + </table> )} + </div> </div> </DesktopView> diff --git a/src/lib/variant/components/VariantCard.jsx b/src/lib/variant/components/VariantCard.jsx index 9f65fc3c..dd87ba1b 100644 --- a/src/lib/variant/components/VariantCard.jsx +++ b/src/lib/variant/components/VariantCard.jsx @@ -1,18 +1,27 @@ import { useRouter } from 'next/router' import { toast } from 'react-hot-toast' - +import useAuth from '@/core/hooks/useAuth'; import Image from '@/core/components/elements/Image/Image' import Link from '@/core/components/elements/Link/Link' import { createSlug } from '@/core/utils/slug' import currencyFormat from '@/core/utils/currencyFormat' import { updateItemCart } from '@/core/utils/cart' import whatsappUrl from '@/core/utils/whatsappUrl' +import {useState } from 'react'; +import rejectProductApi from '../../../lib/transaction/api/rejectProductApi' +// import {useTransaction} from 'C:\Users\Indoteknik\next-indoteknik\src\lib\transaction\hooks\useTransaction.js' +import useTransaction from '../../../lib/transaction/hooks/useTransaction'; import ImageNext from 'next/image'; import { useMemo, useEffect, useState } from 'react'; const VariantCard = ({ product, openOnClick = true, buyMore = false }) => { const router = useRouter() - + const id = router.query.id + const auth = useAuth(); + const { transaction } = useTransaction({id}); + const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedProduct, setSelectedProduct] = useState(null); + const [reason, setReason] = useState(''); @@ -24,11 +33,42 @@ const VariantCard = ({ product, openOnClick = true, buyMore = false }) => { }) return } - + const checkoutItem = () => { router.push(`/shop/checkout?product_id=${product.id}&qty=${product.quantity}`) } - + + 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.'); + } + }; + const Card = () => ( <div className='flex gap-x-3'> <div className='w-4/12 flex items-center gap-x-2'> @@ -115,7 +155,7 @@ const VariantCard = ({ product, openOnClick = true, buyMore = false }) => { <Link href={createSlug('/shop/product/', product.parent.name, product.parent.id)}> <Card /> </Link> - {buyMore && ( + {buyMore && (!transaction?.data?.productsRejectLine.some(pr => pr.id === product.id)) && ( <div className='flex justify-end gap-x-2 mb-2'> <button type='button' @@ -124,14 +164,48 @@ const VariantCard = ({ product, openOnClick = true, buyMore = false }) => { > Tambah Keranjang </button> - <button - type='button' - onClick={checkoutItem} - className='btn-solid-red py-2 px-3 text-caption-1' - > - Beli Lagi - </button> + {/* {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?.productsRejectLine.some(pr => pr.id === product.id) && ( + <button + className="bg-red-500 text-white py-1 px-3 rounded" + onClick={() => openModal(product)} + > + Reject + </button> + ) + )} + {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> + )} </> ) |
