diff options
| author | Rafi Zadanly <zadanlyr@gmail.com> | 2023-02-21 12:04:20 +0700 |
|---|---|---|
| committer | Rafi Zadanly <zadanlyr@gmail.com> | 2023-02-21 12:04:20 +0700 |
| commit | fdfb47c3a825258b871ac5921605642e5e05fdd8 (patch) | |
| tree | 9d8ee50a5a28bd67fad7a5699aa88c415b6fc60c /src | |
| parent | fa45f8bc91adef3bacd3460ef4e3b1151ee13e71 (diff) | |
fix
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/components/elements/Appbar/Appbar.jsx | 2 | ||||
| -rw-r--r-- | src/core/utils/getFileBase64.js | 11 | ||||
| -rw-r--r-- | src/lib/transaction/api/transactionApi.js | 10 | ||||
| -rw-r--r-- | src/lib/transaction/api/uploadPoApi.js | 10 | ||||
| -rw-r--r-- | src/lib/transaction/components/Transaction.jsx | 149 | ||||
| -rw-r--r-- | src/lib/transaction/components/Transactions.jsx | 32 | ||||
| -rw-r--r-- | src/lib/transaction/hooks/useTransaction.js | 13 | ||||
| -rw-r--r-- | src/lib/transaction/utils/transactions.js | 12 | ||||
| -rw-r--r-- | src/lib/variant/components/VariantCard.jsx | 97 | ||||
| -rw-r--r-- | src/lib/variant/components/VariantGroupCard.jsx | 33 | ||||
| -rw-r--r-- | src/pages/my/transaction/[id].jsx | 13 |
11 files changed, 364 insertions, 18 deletions
diff --git a/src/core/components/elements/Appbar/Appbar.jsx b/src/core/components/elements/Appbar/Appbar.jsx index 8d060c44..8fa5a2a7 100644 --- a/src/core/components/elements/Appbar/Appbar.jsx +++ b/src/core/components/elements/Appbar/Appbar.jsx @@ -22,7 +22,7 @@ const AppBar = ({ title }) => { <Link href="/" className="py-4 px-2"> <HomeIcon className="w-6 text-gray_r-12" /> </Link> - <Link href="/shop/cart" className="py-4 px-2"> + <Link href="/my/menu" className="py-4 px-2"> <Bars3Icon className="w-6 text-gray_r-12" /> </Link> </div> diff --git a/src/core/utils/getFileBase64.js b/src/core/utils/getFileBase64.js new file mode 100644 index 00000000..78013e43 --- /dev/null +++ b/src/core/utils/getFileBase64.js @@ -0,0 +1,11 @@ +const getFileBase64 = file => new Promise((resolve, reject) => { + let reader = new FileReader(); + reader.readAsBinaryString(file); + reader.onload = () => { + let result = reader.result; + resolve(btoa(result)); + }; + reader.onerror = error => reject(error); +}); + +export default getFileBase64;
\ No newline at end of file diff --git a/src/lib/transaction/api/transactionApi.js b/src/lib/transaction/api/transactionApi.js new file mode 100644 index 00000000..7186f847 --- /dev/null +++ b/src/lib/transaction/api/transactionApi.js @@ -0,0 +1,10 @@ +import odooApi from "@/core/api/odooApi" +import { getAuth } from "@/core/utils/auth" + +const transactionApi = async ({ id }) => { + const auth = getAuth() + const dataTransaction = await odooApi('GET', `/api/v1/partner/${auth.partnerId}/sale_order/${id}`) + return dataTransaction +} + +export default transactionApi
\ No newline at end of file diff --git a/src/lib/transaction/api/uploadPoApi.js b/src/lib/transaction/api/uploadPoApi.js new file mode 100644 index 00000000..00ad1d8d --- /dev/null +++ b/src/lib/transaction/api/uploadPoApi.js @@ -0,0 +1,10 @@ +import odooApi from "@/core/api/odooApi" +import { getAuth } from "@/core/utils/auth" + +const uploadPoApi = async ({ id, data }) => { + const auth = getAuth() + const dataUploadPo = await odooApi('POST', `/api/v1/partner/${auth.partnerId}/sale_order/${id}/upload_po`, data) + return dataUploadPo +} + +export default uploadPoApi
\ No newline at end of file diff --git a/src/lib/transaction/components/Transaction.jsx b/src/lib/transaction/components/Transaction.jsx new file mode 100644 index 00000000..c9bdf715 --- /dev/null +++ b/src/lib/transaction/components/Transaction.jsx @@ -0,0 +1,149 @@ +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 { useRef, useState } from "react" +import { downloadPurchaseOrder } 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" + +const Transaction = ({ id }) => { + const { transaction } = useTransaction({ id }) + + const poNumber = useRef('') + const poFile = useRef('') + const [ uploadPo, setUploadPo ] = useState(false) + const openUploadPo = () => setUploadPo(true) + const closeUploadPo = () => setUploadPo(false) + const submitUploadPo = async () => { + const file = poFile.current.files[0] + const name = poNumber.current.value + if (typeof file === 'undefined' || !name) { + toast.error('Nomor dan Dokumen PO harus diisi', { position: 'bottom-center' }) + return + } + if (file.size > 5000000) { + toast.error('Maksimal ukuran file adalah 5MB', { position: 'bottom-center' }) + return + } + 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.error('Terjadi kesalahan internal, coba lagi nanti atau hubungi kami') + } + + return ( + <> + { transaction.isLoading && ( + <div className="flex justify-center my-4"> + <Spinner className="w-6 text-gray_r-12/50 fill-gray_r-12" /> + </div> + ) } + + { transaction.data?.name && ( + <> + <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="Ketentuan Pembayaran"> + { 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 flex flex-col gap-y-4"> + <DescriptionRow label="Purchase Order"> + { transaction.data?.purchaseOrderName || '-' } + </DescriptionRow> + <div className="flex items-center"> + <p className="text-gray_r-11 leading-none">Dokumen PO</p> + <button + type="button" + className="btn-light py-1.5 px-3 ml-auto" + onClick={transaction.data?.purchaseOrderFile ? () => downloadPurchaseOrder(transaction.data) : openUploadPo} + > + { transaction.data?.purchaseOrderFile ? 'Download' : 'Upload' } + </button> + </div> + </div> + + <Divider /> + + <div className="font-medium p-4">Detail Produk</div> + + <div className="p-4 pt-0 flex flex-col gap-y-3"> + <VariantGroupCard + variants={transaction.data?.products} + buyMore + /> + <div className="flex justify-between mt-3 font-medium"> + <p>Total Belanja</p> + <p>{ currencyFormat(transaction.data?.amountTotal) }</p> + </div> + </div> + + <Divider /> + + <BottomPopup + title="Upload PO" + close={closeUploadPo} + active={uploadPo} + > + <div> + <label>Nomor PO</label> + <input type="text" className="form-input mt-3" ref={poNumber} /> + </div> + <div className="mt-4"> + <label>Dokumen PO</label> + <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} + >Batal</button> + <button + type="button" + className="btn-solid-red w-full" + onClick={submitUploadPo} + >Upload</button> + </div> + </BottomPopup> + </> + )} + + </> + ) +} + +const DescriptionRow = ({ children, label }) => ( + <div className="grid grid-cols-2"> + <span className="text-gray_r-11">{ label }</span> + <span className="text-right">{ children }</span> + </div> +) + +export default Transaction
\ No newline at end of file diff --git a/src/lib/transaction/components/Transactions.jsx b/src/lib/transaction/components/Transactions.jsx index 5eb1d947..246a4a2c 100644 --- a/src/lib/transaction/components/Transactions.jsx +++ b/src/lib/transaction/components/Transactions.jsx @@ -14,6 +14,7 @@ import BottomPopup from "@/core/components/elements/Popup/BottomPopup" import Pagination from "@/core/components/elements/Pagination/Pagination" import { toQuery } from "lodash-contrib" import _ from "lodash" +import Alert from "@/core/components/elements/Alert/Alert" const Transactions = () => { const router = useRouter() @@ -29,24 +30,22 @@ const Transactions = () => { offset: (page - 1) * limit, limit } - - const [ inputQuery, setInputQuery ] = useState(q) - const { transactions } = useTransactions({ query }) + const [ inputQuery, setInputQuery ] = useState(q) const [ toOthers, setToOthers ] = useState(null) - const [ toDelete, setToDelete ] = useState(null) + const [ toCancel, setToCancel ] = useState(null) const submitCancelTransaction = async () => { const isCancelled = await cancelTransactionApi({ partnerId: auth.partnerId, - transaction: toDelete + transaction: toCancel }) if (isCancelled) { toast.success('Berhasil batalkan transaksi') transactions.refetch() } - setToDelete(null) + setToCancel(null) } const pageCount = Math.ceil(transactions?.data?.saleOrderTotal / limit) @@ -82,6 +81,13 @@ const Transactions = () => { <Spinner className="w-6 text-gray_r-12/50 fill-gray_r-12" /> </div> ) } + + { !transactions.isLoading && transactions.data?.saleOrders?.length === 0 && ( + <Alert type="info" className="text-center"> + Tidak ada data transaksi + </Alert> + ) } + { transactions.data?.saleOrders?.map((saleOrder, index) => ( <div className="p-4 shadow border border-gray_r-3 rounded-md" key={index}> <div className="grid grid-cols-2"> @@ -132,21 +138,21 @@ const Transactions = () => { <button className="text-left disabled:opacity-60" disabled={!toOthers?.purchaseOrderFile} - onClick={() => { downloadPurchaseOrder(auth.partnerId, toOthers); setToOthers(null) }} + onClick={() => { downloadPurchaseOrder(toOthers); setToOthers(null) }} > Download PO </button> <button className="text-left disabled:opacity-60" disabled={toOthers?.status != 'draft'} - onClick={() => { downloadQuotation(auth.partnerId, toOthers); setToOthers(null) }} + onClick={() => { downloadQuotation(toOthers); setToOthers(null) }} > Download Quotation </button> <button className="text-left disabled:opacity-60" disabled={ toOthers?.status != 'waiting' } - onClick={() => { setToDelete(toOthers); setToOthers(null) }} + onClick={() => { setToCancel(toOthers); setToOthers(null) }} > Batalkan Transaksi </button> @@ -154,12 +160,12 @@ const Transactions = () => { </BottomPopup> <BottomPopup - active={toDelete} - close={() => setToDelete(null)} + active={toCancel} + close={() => setToCancel(null)} title="Batalkan Transaksi" > <div className="leading-7 text-gray_r-12/80"> - Apakah anda yakin membatalkan transaksi <span className="underline">{toDelete?.name}</span>? + Apakah anda yakin membatalkan transaksi <span className="underline">{toCancel?.name}</span>? </div> <div className="flex mt-6 gap-x-4"> <button @@ -172,7 +178,7 @@ const Transactions = () => { <button className="btn-light flex-1" type="button" - onClick={() => setToDelete(null)} + onClick={() => setToCancel(null)} > Batal </button> diff --git a/src/lib/transaction/hooks/useTransaction.js b/src/lib/transaction/hooks/useTransaction.js new file mode 100644 index 00000000..f2b493ee --- /dev/null +++ b/src/lib/transaction/hooks/useTransaction.js @@ -0,0 +1,13 @@ +import { useQuery } from "react-query" +import transactionApi from "../api/transactionApi" + +const useTransaction = ({ id }) => { + const fetchTransaction = async () => await transactionApi({ id }) + const { data, isLoading, refetch } = useQuery(`transaction-${id}`, fetchTransaction) + + return { + transaction: { data, isLoading, refetch } + } +} + +export default useTransaction
\ No newline at end of file diff --git a/src/lib/transaction/utils/transactions.js b/src/lib/transaction/utils/transactions.js index 166e8a7e..03d4dbd4 100644 --- a/src/lib/transaction/utils/transactions.js +++ b/src/lib/transaction/utils/transactions.js @@ -1,10 +1,14 @@ -const downloadPurchaseOrder = (partnerId, transaction) => { - const url = `${process.env.ODOO_HOST}/api/v1/partner/${partnerId}/sale_order/${transaction.id}/download_po/${transaction.token}` +import { getAuth } from "@/core/utils/auth" + +const downloadPurchaseOrder = (transaction) => { + const auth = getAuth() + const url = `${process.env.ODOO_HOST}/api/v1/partner/${auth.partnerId}/sale_order/${transaction.id}/download_po/${transaction.token}` window.open(url, 'download') } -const downloadQuotation = (partnerId, transaction) => { - const url = `${process.env.ODOO_HOST}/api/v1/partner/${partnerId}/sale_order/${transaction.id}/download/${transaction.token}` +const downloadQuotation = (transaction) => { + const auth = getAuth() + const url = `${process.env.ODOO_HOST}/api/v1/partner/${auth.partnerId}/sale_order/${transaction.id}/download/${transaction.token}` window.open(url, 'download') } diff --git a/src/lib/variant/components/VariantCard.jsx b/src/lib/variant/components/VariantCard.jsx new file mode 100644 index 00000000..6c7ab22f --- /dev/null +++ b/src/lib/variant/components/VariantCard.jsx @@ -0,0 +1,97 @@ +import { useRouter } from "next/router" +import { toast } from "react-hot-toast" + +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" + +const VariantCard = ({ + product, + openOnClick = true, + buyMore = false +}) => { + const router = useRouter() + + const addItemToCart = () => { + toast.success('Berhasil menambahkan ke keranjang', { duration: 1500 }) + updateItemCart({ + productId: product.id, + quantity: 1 + }) + return + } + + const checkoutItem = () => { + router.push(`/shop/checkout?product_id=${product.id}&qty=${product.quantity}`) + } + + const Card = () => ( + <div className="flex gap-x-3"> + <div className="w-4/12 flex items-center gap-x-2"> + <Image + src={product.parent.image} + alt={product.parent.name} + className="object-contain object-center border border-gray_r-6 h-32 w-full rounded-md" + /> + </div> + <div className="w-8/12 flex flex-col"> + <p className="product-card__title wrap-line-ellipsis-2"> + {product.parent.name} + </p> + <p className="text-caption-2 text-gray_r-11 mt-1"> + {product.code || '-'} + {product.attributes.length > 0 ? ` ・ ${product.attributes.join(', ')}` : ''} + </p> + <div className="flex flex-wrap gap-x-1 items-center mt-auto"> + {product.price.discountPercentage > 0 && ( + <> + <p className="text-caption-2 text-gray_r-11 line-through">{currencyFormat(product.price.price)}</p> + <span className="badge-red">{product.price.discountPercentage}%</span> + </> + )} + <p className="text-caption-2 text-gray_r-12">{currencyFormat(product.price.priceDiscount)}</p> + </div> + <p className="text-caption-2 text-gray_r-11 mt-1"> + {currencyFormat(product.price.priceDiscount)} × {product.quantity} Barang + </p> + <p className="text-caption-2 text-gray_r-12 font-bold mt-2"> + {currencyFormat(product.quantity * product.price.priceDiscount)} + </p> + </div> + </div> + ) + + if (openOnClick) { + return ( + <> + <Link href={createSlug('/shop/product/', product.parent.name, product.parent.id)}> + <Card /> + </Link> + { buyMore && ( + <div className="flex justify-end gap-x-2 mb-2"> + <button + type="button" + onClick={addItemToCart} + className="btn-yellow text-gray_r-12 py-2 px-3 text-caption-1" + > + Tambah Keranjang + </button> + <button + type="button" + onClick={checkoutItem} + className="btn-solid-red py-2 px-3 text-caption-1" + > + Beli Lagi + </button> + </div> + ) } + </> + ) + } + + return <Card/> +} + +export default VariantCard
\ No newline at end of file diff --git a/src/lib/variant/components/VariantGroupCard.jsx b/src/lib/variant/components/VariantGroupCard.jsx new file mode 100644 index 00000000..fd4f9b4d --- /dev/null +++ b/src/lib/variant/components/VariantGroupCard.jsx @@ -0,0 +1,33 @@ +import { useState } from "react" +import VariantCard from "./VariantCard" + +const VariantGroupCard = ({ + variants, + ...props +}) => { + const [ showAll, setShowAll ] = useState(false) + const variantsToShow = showAll ? variants : variants.slice(0, 2) + + return ( + <> + { variantsToShow?.map((variant, index) => ( + <VariantCard + key={index} + product={variant} + {...props} + /> + )) } + { variants.length > 2 && ( + <button + type="button" + className="btn-light py-2 w-full" + onClick={() => setShowAll(!showAll)} + > + { !showAll ? `Lihat Semua +${variants.length - variantsToShow.length}` : 'Tutup' } + </button> + ) } + </> + ) +} + +export default VariantGroupCard
\ No newline at end of file diff --git a/src/pages/my/transaction/[id].jsx b/src/pages/my/transaction/[id].jsx new file mode 100644 index 00000000..4b81b2a3 --- /dev/null +++ b/src/pages/my/transaction/[id].jsx @@ -0,0 +1,13 @@ +import AppLayout from "@/core/components/layouts/AppLayout" +import TransactionComponent from "@/lib/transaction/components/Transaction" +import { useRouter } from "next/router" + +export default function Transaction() { + const router = useRouter() + + return ( + <AppLayout title="Transaksi"> + <TransactionComponent id={router.query.id} /> + </AppLayout> + ) +}
\ No newline at end of file |
