summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/address/components/Addresses.jsx125
-rw-r--r--src/lib/address/components/CreateAddress.jsx237
-rw-r--r--src/lib/address/components/EditAddress.jsx237
-rw-r--r--src/lib/auth/components/Menu.jsx47
-rw-r--r--src/lib/cart/components/Cart.jsx15
-rw-r--r--src/lib/invoice/components/Invoice.jsx292
-rw-r--r--src/lib/invoice/components/Invoices.jsx313
-rw-r--r--src/lib/product/api/productPriceApi.js8
-rw-r--r--src/lib/product/components/Product/ProductDesktop.jsx79
-rw-r--r--src/lib/product/components/Product/ProductMobile.jsx54
-rw-r--r--src/lib/product/components/ProductCard.jsx99
-rw-r--r--src/lib/product/hooks/useProductPrice.js13
-rw-r--r--src/lib/transaction/components/Transaction.jsx478
-rw-r--r--src/lib/transaction/components/TransactionStatusBadge.jsx2
-rw-r--r--src/lib/transaction/components/Transactions.jsx362
-rw-r--r--src/lib/variant/api/variantPriceApi.js8
-rw-r--r--src/lib/variant/hooks/useVariantPrice.js13
-rw-r--r--src/lib/wishlist/components/Wishlists.jsx68
18 files changed, 1574 insertions, 876 deletions
diff --git a/src/lib/address/components/Addresses.jsx b/src/lib/address/components/Addresses.jsx
index 3ac06b6c..a2adecb1 100644
--- a/src/lib/address/components/Addresses.jsx
+++ b/src/lib/address/components/Addresses.jsx
@@ -4,13 +4,15 @@ import useAuth from '@/core/hooks/useAuth'
import { getItemAddress, updateItemAddress } from '@/core/utils/address'
import { useRouter } from 'next/router'
import useAddresses from '../hooks/useAddresses'
+import MobileView from '@/core/components/views/MobileView'
+import DesktopView from '@/core/components/views/DesktopView'
+import Menu from '@/lib/auth/components/Menu'
const Addresses = () => {
const router = useRouter()
const { select = null } = router.query
- const auth = useAuth()
const { addresses } = useAddresses()
- const selectedAdress = getItemAddress(select || '')
+ const selectedAddress = getItemAddress(select || '')
const changeSelectedAddress = (id) => {
if (!select) return
updateItemAddress(select, id)
@@ -26,43 +28,96 @@ const Addresses = () => {
}
return (
- <div className='p-4'>
- <div className='text-right'>
- <Link href='/my/address/create'>Tambah Alamat</Link>
- </div>
+ <>
+ <MobileView>
+ <div className='p-4'>
+ <div className='text-right'>
+ <Link href='/my/address/create'>Tambah Alamat</Link>
+ </div>
+
+ <div className='grid gap-y-4 mt-4'>
+ {addresses.data?.map((address, index) => {
+ const type = address.type.charAt(0).toUpperCase() + address.type.slice(1) + ' Address'
+ return (
+ <AddressCard
+ key={index}
+ address={address}
+ type={type}
+ changeSelectedAddress={changeSelectedAddress}
+ selectedAddress={selectedAddress}
+ select={select}
+ />
+ )
+ })}
+ </div>
+ </div>
+ </MobileView>
- <div className='grid gap-y-4 mt-4'>
- {addresses.data?.map((address, index) => {
- let type = address.type.charAt(0).toUpperCase() + address.type.slice(1) + ' Address'
- return (
- <div
- key={index}
- className={
- 'p-4 rounded-md border ' +
- (selectedAdress && selectedAdress == address.id
- ? 'border-gray_r-7 bg-gray_r-4'
- : 'border-gray_r-7')
- }
- >
- <div onClick={() => changeSelectedAddress(address.id)}>
- <div className='flex gap-x-2'>
- <div className='badge-red'>{type}</div>
- {auth?.partnerId == address.id && <div className='badge-green'>Utama</div>}
- </div>
- <p className='font-medium mt-2'>{address.name}</p>
- {address.mobile && <p className='mt-2 text-gray_r-11'>{address.mobile}</p>}
- <p className='mt-1 leading-6 text-gray_r-11'>{address.street}</p>
- </div>
- <Link
- href={`/my/address/${address.id}/edit`}
- className='btn-light bg-white mt-3 w-full !text-gray_r-11'
- >
- Ubah Alamat
+ <DesktopView>
+ <div className='container mx-auto flex py-10'>
+ <div className='w-3/12 pr-4'>
+ <Menu />
+ </div>
+ <div className='w-9/12 p-4 bg-white border border-gray_r-6 rounded'>
+ <div className='flex items-center mb-6'>
+ <h1 className='text-title-sm font-semibold'>Daftar Alamat</h1>
+ <Link href='/my/address/create' className='btn-solid-red py-2 px-3 text-gray_r-1 h-fit ml-auto'>
+ Tambah Alamat
</Link>
</div>
- )
- })}
+
+ <div className='grid grid-cols-2 gap-4'>
+ {addresses.data?.map((address, index) => {
+ const type =
+ address.type.charAt(0).toUpperCase() + address.type.slice(1) + ' Address'
+ return (
+ <AddressCard
+ key={index}
+ address={address}
+ type={type}
+ changeSelectedAddress={changeSelectedAddress}
+ selectedAddress={selectedAddress}
+ select={select}
+ />
+ )
+ })}
+ </div>
+ </div>
+ </div>
+ </DesktopView>
+ </>
+ )
+}
+
+const AddressCard = ({ address, selectedAddress, changeSelectedAddress, type, select }) => {
+ const auth = useAuth()
+
+ return (
+ <div
+ className={`p-4 rounded-md border
+ ${
+ (selectedAddress && selectedAddress == address.id
+ ? 'border-gray_r-7 bg-gray_r-4'
+ : 'border-gray_r-7') +
+ ' ' +
+ (select && 'cursor-pointer hover:bg-gray_r-4 transition')
+ }`}
+ >
+ <div onClick={() => changeSelectedAddress(address.id)} className={select && 'cursor-pointer'}>
+ <div className='flex gap-x-2'>
+ <div className='badge-red'>{type}</div>
+ {auth?.partnerId == address.id && <div className='badge-green'>Utama</div>}
+ </div>
+ <p className='font-medium mt-2'>{address.name}</p>
+ {address.mobile && <p className='mt-2 text-gray_r-11'>{address.mobile}</p>}
+ <p className='mt-1 leading-6 text-gray_r-11'>{address.street}</p>
</div>
+ <Link
+ href={`/my/address/${address.id}/edit`}
+ className='btn-light bg-white mt-3 w-full !text-gray_r-11'
+ >
+ Ubah Alamat
+ </Link>
</div>
)
}
diff --git a/src/lib/address/components/CreateAddress.jsx b/src/lib/address/components/CreateAddress.jsx
index 849b4c01..475d8548 100644
--- a/src/lib/address/components/CreateAddress.jsx
+++ b/src/lib/address/components/CreateAddress.jsx
@@ -10,6 +10,7 @@ import { useEffect, useState } from 'react'
import createAddressApi from '../api/createAddressApi'
import { toast } from 'react-hot-toast'
import { yupResolver } from '@hookform/resolvers/yup'
+import Menu from '@/lib/auth/components/Menu'
const CreateAddress = () => {
const auth = useAuth()
@@ -88,133 +89,119 @@ const CreateAddress = () => {
}
return (
- <form
- className='p-4 flex flex-col gap-y-4'
- onSubmit={handleSubmit(onSubmitHandler)}
- >
- <div>
- <label className='form-label mb-2'>Label Alamat</label>
- <Controller
- name='type'
- control={control}
- render={(props) => (
- <HookFormSelect
- {...props}
- isSearchable={false}
- options={types}
- />
- )}
- />
- <div className='text-caption-2 text-red_r-11 mt-1'>{errors.type?.message}</div>
+ <div className='max-w-none md:container mx-auto flex p-0 md:py-10'>
+ <div className='hidden md:block w-3/12 pr-4'>
+ <Menu />
</div>
-
- <div>
- <label className='form-label mb-2'>Nama</label>
- <input
- {...register('name')}
- placeholder='John Doe'
- type='text'
- className='form-input'
- />
- <div className='text-caption-2 text-red_r-11 mt-1'>{errors.name?.message}</div>
- </div>
-
- <div>
- <label className='form-label mb-2'>Email</label>
- <input
- {...register('email')}
- placeholder='contoh@email.com'
- type='email'
- className='form-input'
- />
- <div className='text-caption-2 text-red_r-11 mt-1'>{errors.email?.message}</div>
- </div>
-
- <div>
- <label className='form-label mb-2'>Mobile</label>
- <input
- {...register('mobile')}
- placeholder='08xxxxxxxx'
- type='tel'
- className='form-input'
- />
- <div className='text-caption-2 text-red_r-11 mt-1'>{errors.mobile?.message}</div>
- </div>
-
- <div>
- <label className='form-label mb-2'>Alamat</label>
- <input
- {...register('street')}
- placeholder='Jl. Bandengan Utara 85A'
- type='text'
- className='form-input'
- />
- <div className='text-caption-2 text-red_r-11 mt-1'>{errors.street?.message}</div>
- </div>
-
- <div>
- <label className='form-label mb-2'>Kode Pos</label>
- <input
- {...register('zip')}
- placeholder='10100'
- type='number'
- className='form-input'
- />
- <div className='text-caption-2 text-red_r-11 mt-1'>{errors.zip?.message}</div>
- </div>
-
- <div>
- <label className='form-label mb-2'>Kota</label>
- <Controller
- name='city'
- control={control}
- render={(props) => (
- <HookFormSelect
- {...props}
- options={cities}
- />
- )}
- />
- <div className='text-caption-2 text-red_r-11 mt-1'>{errors.city?.message}</div>
- </div>
-
- <div>
- <label className='form-label mb-2'>Kecamatan</label>
- <Controller
- name='district'
- control={control}
- render={(props) => (
- <HookFormSelect
- {...props}
- options={districts}
- disabled={!watchCity}
- />
- )}
- />
- </div>
-
- <div>
- <label className='form-label mb-2'>Kelurahan</label>
- <Controller
- name='subDistrict'
- control={control}
- render={(props) => (
- <HookFormSelect
- {...props}
- options={subDistricts}
- disabled={!watchDistrict}
- />
- )}
- />
+ <div className='w-full md:w-9/12 p-4 bg-white border border-gray_r-6 rounded'>
+ <form onSubmit={handleSubmit(onSubmitHandler)}>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4'>
+ <div>
+ <label className='form-label mb-2'>Label Alamat</label>
+ <Controller
+ name='type'
+ control={control}
+ render={(props) => (
+ <HookFormSelect {...props} isSearchable={false} options={types} />
+ )}
+ />
+ <div className='text-caption-2 text-red_r-11 mt-1'>{errors.type?.message}</div>
+ </div>
+
+ <div>
+ <label className='form-label mb-2'>Nama</label>
+ <input
+ {...register('name')}
+ placeholder='John Doe'
+ type='text'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-red_r-11 mt-1'>{errors.name?.message}</div>
+ </div>
+
+ <div>
+ <label className='form-label mb-2'>Email</label>
+ <input
+ {...register('email')}
+ placeholder='contoh@email.com'
+ type='email'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-red_r-11 mt-1'>{errors.email?.message}</div>
+ </div>
+
+ <div>
+ <label className='form-label mb-2'>Mobile</label>
+ <input
+ {...register('mobile')}
+ placeholder='08xxxxxxxx'
+ type='tel'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-red_r-11 mt-1'>{errors.mobile?.message}</div>
+ </div>
+
+ <div>
+ <label className='form-label mb-2'>Alamat</label>
+ <input
+ {...register('street')}
+ placeholder='Jl. Bandengan Utara 85A'
+ type='text'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-red_r-11 mt-1'>{errors.street?.message}</div>
+ </div>
+
+ <div>
+ <label className='form-label mb-2'>Kode Pos</label>
+ <input
+ {...register('zip')}
+ placeholder='10100'
+ type='number'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-red_r-11 mt-1'>{errors.zip?.message}</div>
+ </div>
+
+ <div>
+ <label className='form-label mb-2'>Kota</label>
+ <Controller
+ name='city'
+ control={control}
+ render={(props) => <HookFormSelect {...props} options={cities} />}
+ />
+ <div className='text-caption-2 text-red_r-11 mt-1'>{errors.city?.message}</div>
+ </div>
+
+ <div>
+ <label className='form-label mb-2'>Kecamatan</label>
+ <Controller
+ name='district'
+ control={control}
+ render={(props) => (
+ <HookFormSelect {...props} options={districts} disabled={!watchCity} />
+ )}
+ />
+ </div>
+
+ <div>
+ <label className='form-label mb-2'>Kelurahan</label>
+ <Controller
+ name='subDistrict'
+ control={control}
+ render={(props) => (
+ <HookFormSelect {...props} options={subDistricts} disabled={!watchDistrict} />
+ )}
+ />
+ </div>
+ </div>
+
+ <button type='submit' className='btn-yellow w-full md:w-fit mt-6 ml-0 md:ml-auto'>
+ Simpan
+ </button>
+ </form>
</div>
-
- <button
- type='submit'
- className='btn-yellow mt-2 w-full'
- >
- Simpan
- </button>
- </form>
+ </div>
)
}
diff --git a/src/lib/address/components/EditAddress.jsx b/src/lib/address/components/EditAddress.jsx
index a832edbc..d754cbd9 100644
--- a/src/lib/address/components/EditAddress.jsx
+++ b/src/lib/address/components/EditAddress.jsx
@@ -9,6 +9,7 @@ import subDistrictApi from '../api/subDistrictApi'
import editAddressApi from '../api/editAddressApi'
import HookFormSelect from '@/core/components/elements/Select/HookFormSelect'
import { toast } from 'react-hot-toast'
+import Menu from '@/lib/auth/components/Menu'
const EditAddress = ({ id, defaultValues }) => {
const router = useRouter()
@@ -102,133 +103,119 @@ const EditAddress = ({ id, defaultValues }) => {
}
return (
- <form
- className='p-4 flex flex-col gap-y-4'
- onSubmit={handleSubmit(onSubmitHandler)}
- >
- <div>
- <label className='form-label mb-2'>Label Alamat</label>
- <Controller
- name='type'
- control={control}
- render={(props) => (
- <HookFormSelect
- {...props}
- isSearchable={false}
- options={types}
- />
- )}
- />
- <div className='text-caption-2 text-red_r-11 mt-1'>{errors.type?.message}</div>
+ <div className='max-w-none md:container mx-auto flex p-0 md:py-10'>
+ <div className='hidden md:block w-3/12 pr-4'>
+ <Menu />
</div>
-
- <div>
- <label className='form-label mb-2'>Nama</label>
- <input
- {...register('name')}
- placeholder='John Doe'
- type='text'
- className='form-input'
- />
- <div className='text-caption-2 text-red_r-11 mt-1'>{errors.name?.message}</div>
- </div>
-
- <div>
- <label className='form-label mb-2'>Email</label>
- <input
- {...register('email')}
- placeholder='johndoe@example.com'
- type='email'
- className='form-input'
- />
- <div className='text-caption-2 text-red_r-11 mt-1'>{errors.email?.message}</div>
- </div>
-
- <div>
- <label className='form-label mb-2'>Mobile</label>
- <input
- {...register('mobile')}
- placeholder='08xxxxxxxx'
- type='tel'
- className='form-input'
- />
- <div className='text-caption-2 text-red_r-11 mt-1'>{errors.mobile?.message}</div>
- </div>
-
- <div>
- <label className='form-label mb-2'>Alamat</label>
- <input
- {...register('street')}
- placeholder='Jl. Bandengan Utara 85A'
- type='text'
- className='form-input'
- />
- <div className='text-caption-2 text-red_r-11 mt-1'>{errors.street?.message}</div>
- </div>
-
- <div>
- <label className='form-label mb-2'>Kode Pos</label>
- <input
- {...register('zip')}
- placeholder='10100'
- type='number'
- className='form-input'
- />
- <div className='text-caption-2 text-red_r-11 mt-1'>{errors.zip?.message}</div>
- </div>
-
- <div>
- <label className='form-label mb-2'>Kota</label>
- <Controller
- name='city'
- control={control}
- render={(props) => (
- <HookFormSelect
- {...props}
- options={cities}
- />
- )}
- />
- <div className='text-caption-2 text-red_r-11 mt-1'>{errors.city?.message}</div>
- </div>
-
- <div>
- <label className='form-label mb-2'>Kecamatan</label>
- <Controller
- name='district'
- control={control}
- render={(props) => (
- <HookFormSelect
- {...props}
- options={districts}
- disabled={!watchCity}
- />
- )}
- />
- </div>
-
- <div>
- <label className='form-label mb-2'>Kelurahan</label>
- <Controller
- name='subDistrict'
- control={control}
- render={(props) => (
- <HookFormSelect
- {...props}
- options={subDistricts}
- disabled={!watchDistrict}
- />
- )}
- />
+ <div className='w-full md:w-9/12 p-4 bg-white border border-gray_r-6 rounded'>
+ <h1 className='text-title-sm font-semibold mb-6 hidden md:block'>Ubah Alamat</h1>
+ <form onSubmit={handleSubmit(onSubmitHandler)}>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4'>
+ <div>
+ <label className='form-label mb-2'>Label Alamat</label>
+ <Controller
+ name='type'
+ control={control}
+ render={(props) => (
+ <HookFormSelect {...props} isSearchable={false} options={types} />
+ )}
+ />
+ <div className='text-caption-2 text-red_r-11 mt-1'>{errors.type?.message}</div>
+ </div>
+
+ <div>
+ <label className='form-label mb-2'>Nama</label>
+ <input
+ {...register('name')}
+ placeholder='John Doe'
+ type='text'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-red_r-11 mt-1'>{errors.name?.message}</div>
+ </div>
+
+ <div>
+ <label className='form-label mb-2'>Email</label>
+ <input
+ {...register('email')}
+ placeholder='johndoe@example.com'
+ type='email'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-red_r-11 mt-1'>{errors.email?.message}</div>
+ </div>
+
+ <div>
+ <label className='form-label mb-2'>Mobile</label>
+ <input
+ {...register('mobile')}
+ placeholder='08xxxxxxxx'
+ type='tel'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-red_r-11 mt-1'>{errors.mobile?.message}</div>
+ </div>
+
+ <div>
+ <label className='form-label mb-2'>Alamat</label>
+ <input
+ {...register('street')}
+ placeholder='Jl. Bandengan Utara 85A'
+ type='text'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-red_r-11 mt-1'>{errors.street?.message}</div>
+ </div>
+
+ <div>
+ <label className='form-label mb-2'>Kode Pos</label>
+ <input
+ {...register('zip')}
+ placeholder='10100'
+ type='number'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-red_r-11 mt-1'>{errors.zip?.message}</div>
+ </div>
+
+ <div>
+ <label className='form-label mb-2'>Kota</label>
+ <Controller
+ name='city'
+ control={control}
+ render={(props) => <HookFormSelect {...props} options={cities} />}
+ />
+ <div className='text-caption-2 text-red_r-11 mt-1'>{errors.city?.message}</div>
+ </div>
+
+ <div>
+ <label className='form-label mb-2'>Kecamatan</label>
+ <Controller
+ name='district'
+ control={control}
+ render={(props) => (
+ <HookFormSelect {...props} options={districts} disabled={!watchCity} />
+ )}
+ />
+ </div>
+
+ <div>
+ <label className='form-label mb-2'>Kelurahan</label>
+ <Controller
+ name='subDistrict'
+ control={control}
+ render={(props) => (
+ <HookFormSelect {...props} options={subDistricts} disabled={!watchDistrict} />
+ )}
+ />
+ </div>
+ </div>
+ <button type='submit' className='btn-yellow w-full md:w-fit mt-6 ml-0 md:ml-auto'>
+ Simpan
+ </button>
+ </form>
</div>
-
- <button
- type='submit'
- className='btn-yellow mt-2 w-full'
- >
- Simpan
- </button>
- </form>
+ </div>
)
}
diff --git a/src/lib/auth/components/Menu.jsx b/src/lib/auth/components/Menu.jsx
new file mode 100644
index 00000000..9a73609d
--- /dev/null
+++ b/src/lib/auth/components/Menu.jsx
@@ -0,0 +1,47 @@
+import Link from '@/core/components/elements/Link/Link'
+import { useRouter } from 'next/router'
+
+const Menu = () => {
+ const router = useRouter()
+
+ const routeStartWith = (route) => router.pathname.startsWith(route)
+
+ return (
+ <div className='grid grid-cols-1 bg-white border border-gray_r-6 rounded py-2 px-4'>
+ <div className='mt-4 mb-1 font-medium'>Menu</div>
+ <LinkItem href='/my/transactions' active={routeStartWith('/my/transaction')}>
+ Daftar Transaksi
+ </LinkItem>
+ <LinkItem href='/my/invoices' active={routeStartWith('/my/invoice')}>
+ Invoice & Faktur Pajak
+ </LinkItem>
+ <LinkItem href='/my/wishlist' active={routeStartWith('/my/wishlist')}>
+ Wishlist
+ </LinkItem>
+
+ <div className='mt-4 mb-1 font-medium'>Pusat Bantuan</div>
+ <LinkItem href='/'>Layanan Pelanggan</LinkItem>
+
+ <div className='mt-4 mb-1 font-medium'>Pengaturan Akun</div>
+ <LinkItem href='/my/address' active={routeStartWith('/my/address')}>
+ Daftar Alamat
+ </LinkItem>
+ <button type='button' className='text-gray_r-12/80 p-2 text-left'>
+ Keluar Akun
+ </button>
+ </div>
+ )
+}
+
+const LinkItem = ({ children, ...props }) => (
+ <Link
+ {...props}
+ className={`!text-gray_r-12/80 !font-normal p-2 rounded ${
+ props.active == true ? 'bg-gray_r-3' : ''
+ }`}
+ >
+ {children}
+ </Link>
+)
+
+export default Menu
diff --git a/src/lib/cart/components/Cart.jsx b/src/lib/cart/components/Cart.jsx
index 8bd9e362..8cd6df96 100644
--- a/src/lib/cart/components/Cart.jsx
+++ b/src/lib/cart/components/Cart.jsx
@@ -128,16 +128,16 @@ const Cart = () => {
Apakah anda yakin menghapus barang{' '}
<span className='underline'>{deleteConfirmation?.name}</span> dari keranjang?
</div>
- <div className='flex mt-6 gap-x-4'>
+ <div className='flex mt-6 gap-x-4 md:justify-end'>
<button
- className='btn-solid-red flex-1'
+ className='btn-solid-red flex-1 md:flex-none'
type='button'
onClick={() => deleteProduct(deleteConfirmation?.id)}
>
Ya, Hapus
</button>
<button
- className='btn-light flex-1'
+ className='btn-light flex-1 md:flex-none'
type='button'
onClick={() => setDeleteConfirmation(null)}
>
@@ -302,6 +302,15 @@ const Cart = () => {
</tr>
</thead>
<tbody>
+ {cart.isLoading && (
+ <tr>
+ <td colSpan={6}>
+ <div className='flex justify-center my-2'>
+ <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' />
+ </div>
+ </td>
+ </tr>
+ )}
{!cart.isLoading && (!products || products?.length == 0) && (
<tr>
<td colSpan={6}>Keranjang belanja anda masih kosong</td>
diff --git a/src/lib/invoice/components/Invoice.jsx b/src/lib/invoice/components/Invoice.jsx
index e34ad8c2..355f36e5 100644
--- a/src/lib/invoice/components/Invoice.jsx
+++ b/src/lib/invoice/components/Invoice.jsx
@@ -4,10 +4,34 @@ import { downloadInvoice, downloadTaxInvoice } from '../utils/invoices'
import Divider from '@/core/components/elements/Divider/Divider'
import VariantGroupCard from '@/lib/variant/components/VariantGroupCard'
import currencyFormat from '@/core/utils/currencyFormat'
+import MobileView from '@/core/components/views/MobileView'
+import DesktopView from '@/core/components/views/DesktopView'
+import Menu from '@/lib/auth/components/Menu'
+import Link from '@/core/components/elements/Link/Link'
+import Image from '@/core/components/elements/Image/Image'
+import { createSlug } from '@/core/utils/slug'
+import { useEffect, useState } from 'react'
const Invoice = ({ id }) => {
const { invoice } = useInvoice({ id })
+ const [totalAmount, setTotalAmount] = useState(0)
+ const [totalDiscountAmount, setTotalDiscountAmount] = useState(0)
+
+ useEffect(() => {
+ if (invoice?.data?.products) {
+ let calculateTotalAmount = 0
+ let calculateTotalDiscountAmount = 0
+ invoice.data.products.forEach((product) => {
+ calculateTotalAmount += product.price.price * product.quantity
+ calculateTotalDiscountAmount +=
+ (product.price.price - product.price.priceDiscount) * product.quantity
+ })
+ setTotalAmount(calculateTotalAmount)
+ setTotalDiscountAmount(calculateTotalDiscountAmount)
+ }
+ }, [invoice])
+
if (invoice.isLoading) {
return (
<div className='flex justify-center my-6'>
@@ -27,74 +51,226 @@ const Invoice = ({ id }) => {
return (
invoice.data?.name && (
<>
- <div className='flex flex-col gap-y-4 p-4'>
- <DescriptionRow label='No Invoice'>{invoice.data?.name}</DescriptionRow>
- <DescriptionRow label='Status Transaksi'>
- {invoice.data?.amountResidual > 0 ? (
- <span className='badge-solid-red'>Belum Lunas</span>
- ) : (
- <span className='badge-solid-green'>Lunas</span>
- )}
- </DescriptionRow>
- <DescriptionRow label='Purchase Order'>
- {invoice.data?.purchaseOrderName || '-'}
- </DescriptionRow>
- <DescriptionRow label='Ketentuan Pembayaran'>{invoice.data?.paymentTerm}</DescriptionRow>
- {invoice.data?.amountResidual > 0 && invoice.invoiceDate != invoice.invoiceDateDue && (
- <DescriptionRow label='Tanggal Jatuh Tempo'>
- {invoice.data?.invoiceDateDue}
+ <MobileView>
+ <div className='flex flex-col gap-y-4 p-4'>
+ <DescriptionRow label='No Invoice'>{invoice.data?.name}</DescriptionRow>
+ <DescriptionRow label='Status Transaksi'>
+ {invoice.data?.amountResidual > 0 ? (
+ <span className='badge-solid-red'>Belum Lunas</span>
+ ) : (
+ <span className='badge-solid-green'>Lunas</span>
+ )}
+ </DescriptionRow>
+ <DescriptionRow label='Purchase Order'>
+ {invoice.data?.purchaseOrderName || '-'}
+ </DescriptionRow>
+ <DescriptionRow label='Ketentuan Pembayaran'>
+ {invoice.data?.paymentTerm}
</DescriptionRow>
- )}
- <DescriptionRow label='Nama Sales'>{invoice.data?.sales}</DescriptionRow>
- <DescriptionRow label='Tanggal Invoice'>{invoice.data?.invoiceDate}</DescriptionRow>
- <div className='flex items-center'>
- <p className='text-gray_r-11 leading-none'>Invoice</p>
- <button
- type='button'
- className='btn-light py-1.5 px-3 ml-auto'
- onClick={() => downloadInvoice(invoice.data)}
- >
- Download
- </button>
+ {invoice.data?.amountResidual > 0 && invoice.invoiceDate != invoice.invoiceDateDue && (
+ <DescriptionRow label='Tanggal Jatuh Tempo'>
+ {invoice.data?.invoiceDateDue}
+ </DescriptionRow>
+ )}
+ <DescriptionRow label='Nama Sales'>{invoice.data?.sales}</DescriptionRow>
+ <DescriptionRow label='Tanggal Invoice'>{invoice.data?.invoiceDate}</DescriptionRow>
+ <div className='flex items-center'>
+ <p className='text-gray_r-11 leading-none'>Invoice</p>
+ <button
+ type='button'
+ className='btn-light py-1.5 px-3 ml-auto'
+ onClick={() => downloadInvoice(invoice.data)}
+ >
+ Download
+ </button>
+ </div>
+ <div className='flex items-center'>
+ <p className='text-gray_r-11 leading-none'>Faktur Pajak</p>
+ <button
+ type='button'
+ className='btn-light py-1.5 px-3 ml-auto'
+ onClick={() => downloadTaxInvoice(invoice.data)}
+ disabled={!invoice.data?.efaktur}
+ >
+ Download
+ </button>
+ </div>
</div>
- <div className='flex items-center'>
- <p className='text-gray_r-11 leading-none'>Faktur Pajak</p>
- <button
- type='button'
- className='btn-light py-1.5 px-3 ml-auto'
- onClick={() => downloadTaxInvoice(invoice.data)}
- disabled={!invoice.data?.efaktur}
- >
- Download
- </button>
+
+ <Divider />
+
+ <div className='p-4 font-medium'>Detail Penagihan</div>
+
+ <div className='flex flex-col gap-y-4 p-4 border-t border-gray_r-6'>
+ <DescriptionRow label='Nama'>{address?.name}</DescriptionRow>
+ <DescriptionRow label='Email'>{address?.email || '-'}</DescriptionRow>
+ <DescriptionRow label='No Telepon'>{address?.mobile || '-'}</DescriptionRow>
+ <DescriptionRow label='Alamat'>{fullAddress}</DescriptionRow>
</div>
- </div>
- <Divider />
+ <Divider />
+
+ <div className='font-medium p-4'>Detail Produk</div>
+
+ <div className='p-4 pt-0 flex flex-col gap-y-3'>
+ <VariantGroupCard variants={invoice.data?.products} buyMore />
+ <div className='flex justify-between mt-3 font-medium'>
+ <p>Total Belanja</p>
+ <p>{currencyFormat(invoice.data?.amountTotal)}</p>
+ </div>
+ </div>
+ </MobileView>
+
+ <DesktopView>
+ <div className='container mx-auto flex py-10'>
+ <div className='w-3/12 pr-4'>
+ <Menu />
+ </div>
+ <div className='w-9/12 p-4 bg-white border border-gray_r-6 rounded'>
+ <h1 className='text-title-sm font-semibold mb-6'>Invoice & Faktur Pajak</h1>
+
+ <div className='flex items-center gap-x-2 mb-3'>
+ <span className='text-h-sm font-medium'>{invoice?.data?.name}</span>
+ {invoice?.data?.amountResidual > 0 ? (
+ <div className='badge-solid-red h-fit'>Belum Lunas</div>
+ ) : (
+ <div className='badge-solid-green h-fit'>Lunas</div>
+ )}
+ </div>
+
+ <button
+ type='button'
+ className='btn-solid-red px-3 py-2'
+ onClick={() => downloadInvoice(invoice.data)}
+ >
+ Download
+ </button>
+
+ <div className='grid grid-cols-2 gap-x-6 mt-6'>
+ <div className='h-fit grid grid-cols-2 gap-y-4'>
+ <div>Tanggal Invoice</div>
+ <div>: {invoice?.data?.invoiceDate}</div>
+
+ <div>Purchase Order</div>
+ <div>
+ :{' '}
+ <button
+ type='button'
+ className='inline-block text-red_r-11'
+ onClick={() => downloadInvoice(invoice.data)}
+ >
+ Download
+ </button>
+ </div>
+
+ <div>Ketentuan Pembayaran</div>
+ <div>: {invoice?.data?.paymentTerm}</div>
+ </div>
+ <div className='h-fit grid grid-cols-2 gap-y-4'>
+ <div>Nama Sales</div>
+ <div>: {invoice?.data?.sales}</div>
+
+ <div>Faktur Pajak</div>
+ <div>
+ :{' '}
+ {invoice.data?.efaktur ? (
+ <button
+ type='button'
+ className='inline-block text-red_r-11'
+ onClick={() => downloadTaxInvoice(invoice?.data)}
+ >
+ Download
+ </button>
+ ) : (
+ '-'
+ )}
+ </div>
+ </div>
+ </div>
- <div className='p-4 font-medium'>Detail Penagihan</div>
+ <div className='text-h-sm font-semibold mt-6 mb-4'>Rincian Pembelian</div>
+ <table className='table-data'>
+ <thead>
+ <tr>
+ <th>Nama Produk</th>
+ <th>Jumlah</th>
+ <th>Harga</th>
+ <th>Diskon</th>
+ <th>Subtotal</th>
+ </tr>
+ </thead>
+ <tbody>
+ {invoice?.data?.products?.map((product) => (
+ <tr key={product.id}>
+ <td className='flex'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ className='w-[30%] flex-shrink-0'
+ >
+ <Image
+ src={product?.parent?.image}
+ alt={product?.name}
+ className='object-contain object-center border border-gray_r-6 h-40 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.quantity}</td>
+ <td>{currencyFormat(product.price.price)}</td>
+ <td>
+ {product.price.discountPercentage > 0
+ ? `${product.price.discountPercentage}%`
+ : ''}
+ </td>
+ <td>{currencyFormat(product.price.priceDiscount * product.quantity)}</td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
- <div className='flex flex-col gap-y-4 p-4 border-t border-gray_r-6'>
- <DescriptionRow label='Nama'>{address?.name}</DescriptionRow>
- <DescriptionRow label='Email'>{address?.email || '-'}</DescriptionRow>
- <DescriptionRow label='No Telepon'>{address?.mobile || '-'}</DescriptionRow>
- <DescriptionRow label='Alamat'>{fullAddress}</DescriptionRow>
- </div>
+ <div className='flex justify-end mt-4'>
+ <div className='w-1/4 grid grid-cols-2 gap-y-2 text-gray_r-12/80'>
+ <div className='text-right'>Subtotal</div>
+ <div className='text-right font-medium'>{currencyFormat(totalAmount)}</div>
- <Divider />
+ <div className='text-right'>Total Diskon</div>
+ <div className='text-right font-medium'>
+ {currencyFormat(-totalDiscountAmount)}
+ </div>
- <div className='font-medium p-4'>Detail Produk</div>
+ <div className='text-right'>PPN 11% (Incl.)</div>
+ <div className='text-right font-medium'>{currencyFormat(totalAmount * 0.11)}</div>
- <div className='p-4 pt-0 flex flex-col gap-y-3'>
- <VariantGroupCard
- variants={invoice.data?.products}
- buyMore
- />
- <div className='flex justify-between mt-3 font-medium'>
- <p>Total Belanja</p>
- <p>{currencyFormat(invoice.data?.amountTotal)}</p>
+ <div className='text-right'>Grand Total</div>
+ <div className='text-right font-medium text-gray_r-12'>
+ {currencyFormat(invoice.data?.amountTotal)}
+ </div>
+ </div>
+ </div>
+ </div>
</div>
- </div>
+ </DesktopView>
</>
)
)
diff --git a/src/lib/invoice/components/Invoices.jsx b/src/lib/invoice/components/Invoices.jsx
index ab318a3c..51041316 100644
--- a/src/lib/invoice/components/Invoices.jsx
+++ b/src/lib/invoice/components/Invoices.jsx
@@ -16,6 +16,9 @@ import Link from '@/core/components/elements/Link/Link'
import currencyFormat from '@/core/utils/currencyFormat'
import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
import { downloadInvoice, downloadTaxInvoice } from '../utils/invoices'
+import MobileView from '@/core/components/views/MobileView'
+import DesktopView from '@/core/components/views/DesktopView'
+import Menu from '@/lib/auth/components/Menu'
const Invoices = () => {
const router = useRouter()
@@ -44,133 +47,201 @@ const Invoices = () => {
}
return (
- <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 Invoice...'
- value={inputQuery}
- onChange={(e) => setInputQuery(e.target.value)}
- />
- <button
- className='btn-light bg-transparent px-3'
- type='submit'
- >
- <MagnifyingGlassIcon className='w-6' />
- </button>
- </form>
-
- {invoices.isLoading && (
- <div className='flex justify-center my-4'>
- <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' />
- </div>
- )}
-
- {!invoices.isLoading && invoices.data?.invoices?.length === 0 && (
- <Alert
- type='info'
- className='text-center'
- >
- Tidak ada data invoice
- </Alert>
- )}
-
- {invoices.data?.invoices?.map((invoice, index) => (
- <div
- className='p-4 shadow border border-gray_r-3 rounded-md'
- key={index}
- >
- <div className='grid grid-cols-2'>
- <Link href={`/my/invoice/${invoice.id}`}>
- <span className='text-caption-2 text-gray_r-11'>No. Invoice</span>
- <h2 className='text-red_r-11 mt-1'>{invoice.name}</h2>
- </Link>
- <div className='flex gap-x-1 justify-end'>
- {invoice.amountResidual > 0 ? (
- <div className='badge-solid-red h-fit ml-auto'>Belum Lunas</div>
- ) : (
- <div className='badge-solid-green h-fit ml-auto'>Lunas</div>
- )}
- <EllipsisVerticalIcon
- className='w-5 h-5'
- onClick={() => setToOthers(invoice)}
- />
- </div>
- </div>
- <Link href={`/my/invoice/${invoice.id}`}>
- <div className='grid grid-cols-2 text-caption-2 text-gray_r-11 mt-2 font-normal'>
- <p>{invoice.invoiceDate}</p>
- <p className='text-right'>{invoice.paymentTerm}</p>
+ <>
+ <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 Invoice...'
+ value={inputQuery}
+ onChange={(e) => setInputQuery(e.target.value)}
+ />
+ <button className='btn-light bg-transparent px-3' type='submit'>
+ <MagnifyingGlassIcon className='w-6' />
+ </button>
+ </form>
+
+ {invoices.isLoading && (
+ <div className='flex justify-center my-4'>
+ <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' />
</div>
- <hr className='my-3' />
- <div className='grid grid-cols-2'>
- <div>
- <span className='text-caption-2 text-gray_r-11'>No. Purchase Order</span>
- <p className='mt-1 font-medium text-gray_r-12'>
- {invoice.purchaseOrderName || '-'}
- </p>
- </div>
- <div className='text-right'>
- <span className='text-caption-2 text-gray_r-11'>Total Invoice</span>
- <p className='mt-1 font-medium text-gray_r-12'>
- {currencyFormat(invoice.amountTotal)}
- </p>
+ )}
+
+ {!invoices.isLoading && invoices.data?.invoices?.length === 0 && (
+ <Alert type='info' className='text-center'>
+ Tidak ada data invoice
+ </Alert>
+ )}
+
+ {invoices.data?.invoices?.map((invoice, index) => (
+ <div className='p-4 shadow border border-gray_r-3 rounded-md' key={index}>
+ <div className='grid grid-cols-2'>
+ <Link href={`/my/invoice/${invoice.id}`}>
+ <span className='text-caption-2 text-gray_r-11'>No. Invoice</span>
+ <h2 className='text-red_r-11 mt-1'>{invoice.name}</h2>
+ </Link>
+ <div className='flex gap-x-1 justify-end'>
+ {invoice.amountResidual > 0 ? (
+ <div className='badge-solid-red h-fit ml-auto'>Belum Lunas</div>
+ ) : (
+ <div className='badge-solid-green h-fit ml-auto'>Lunas</div>
+ )}
+ <EllipsisVerticalIcon className='w-5 h-5' onClick={() => setToOthers(invoice)} />
+ </div>
</div>
+ <Link href={`/my/invoice/${invoice.id}`}>
+ <div className='grid grid-cols-2 text-caption-2 text-gray_r-11 mt-2 font-normal'>
+ <p>{invoice.invoiceDate}</p>
+ <p className='text-right'>{invoice.paymentTerm}</p>
+ </div>
+ <hr className='my-3' />
+ <div className='grid grid-cols-2'>
+ <div>
+ <span className='text-caption-2 text-gray_r-11'>No. Purchase Order</span>
+ <p className='mt-1 font-medium text-gray_r-12'>
+ {invoice.purchaseOrderName || '-'}
+ </p>
+ </div>
+ <div className='text-right'>
+ <span className='text-caption-2 text-gray_r-11'>Total Invoice</span>
+ <p className='mt-1 font-medium text-gray_r-12'>
+ {currencyFormat(invoice.amountTotal)}
+ </p>
+ </div>
+ </div>
+ </Link>
+ {invoice.efaktur ? (
+ <div className='badge-green h-fit mt-3 ml-auto flex items-center gap-x-0.5'>
+ <CheckIcon className='w-4 stroke-2' />
+ Faktur Pajak
+ </div>
+ ) : (
+ <div className='badge-red h-fit mt-3 ml-auto flex items-center gap-x-0.5'>
+ <ClockIcon className='w-4 stroke-2' />
+ Faktur Pajak
+ </div>
+ )}
</div>
- </Link>
- {invoice.efaktur ? (
- <div className='badge-green h-fit mt-3 ml-auto flex items-center gap-x-0.5'>
- <CheckIcon className='w-4 stroke-2' />
- Faktur Pajak
- </div>
- ) : (
- <div className='badge-red h-fit mt-3 ml-auto flex items-center gap-x-0.5'>
- <ClockIcon className='w-4 stroke-2' />
- Faktur Pajak
+ ))}
+
+ <Pagination
+ pageCount={pageCount}
+ currentPage={parseInt(page)}
+ url={`/my/invoices${pageQuery}`}
+ className='mt-2 mb-2'
+ />
+
+ <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'
+ onClick={() => {
+ downloadInvoice(toOthers)
+ setToOthers(null)
+ }}
+ >
+ Download Invoice
+ </button>
+ <button
+ className='text-left disabled:opacity-60'
+ disabled={!toOthers?.efaktur}
+ onClick={() => {
+ downloadTaxInvoice(toOthers)
+ setToOthers(null)
+ }}
+ >
+ Download Faktur Pajak
+ </button>
</div>
- )}
+ </BottomPopup>
</div>
- ))}
-
- <Pagination
- pageCount={pageCount}
- currentPage={parseInt(page)}
- url={`/my/invoices${pageQuery}`}
- className='mt-2 mb-2'
- />
-
- <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'
- onClick={() => {
- downloadInvoice(toOthers)
- setToOthers(null)
- }}
- >
- Download Invoice
- </button>
- <button
- className='text-left disabled:opacity-60'
- disabled={!toOthers?.efaktur}
- onClick={() => {
- downloadTaxInvoice(toOthers)
- setToOthers(null)
- }}
- >
- Download Faktur Pajak
- </button>
+ </MobileView>
+
+ <DesktopView>
+ <div className='container mx-auto flex py-10'>
+ <div className='w-3/12 pr-4'>
+ <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'>
+ <h1 className='text-title-sm font-semibold'>
+ Invoice & Faktur Pajak{' '}
+ {invoices?.data?.invoices ? `(${invoices?.data?.invoices.length})` : ''}
+ </h1>
+ <form className='flex gap-x-2' onSubmit={handleSubmit}>
+ <input
+ type='text'
+ className='form-input'
+ placeholder='Cari Invoice...'
+ 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>
+
+ <table className='table-data'>
+ <thead>
+ <tr>
+ <th>No. Invoice</th>
+ <th>No. PO</th>
+ <th>Tanggal</th>
+ <th className='!text-left'>Salesperson</th>
+ <th>Status</th>
+ <th className='!text-left'>Total</th>
+ </tr>
+ </thead>
+ <tbody>
+ {invoices.isLoading && (
+ <tr>
+ <td colSpan={5}>
+ <div className='flex justify-center my-2'>
+ <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' />
+ </div>
+ </td>
+ </tr>
+ )}
+ {!invoices.isLoading &&
+ (!invoices?.data?.invoices || invoices?.data?.invoices?.length == 0) && (
+ <tr>
+ <td colSpan={6}>Tidak ada data invoice</td>
+ </tr>
+ )}
+ {invoices.data?.invoices?.map((invoice) => (
+ <tr key={invoice.id}>
+ <td>
+ <Link href={`/my/invoice/${invoice.id}`}>{invoice.name}</Link>
+ </td>
+ <td>{invoice.purchaseOrderName || '-'}</td>
+ <td>{invoice.invoiceDate}</td>
+ <td className='!text-left'>{invoice.sales}</td>
+ <td>
+ {invoice.amountResidual > 0 ? (
+ <div className='badge-solid-red h-fit mx-auto'>Belum Lunas</div>
+ ) : (
+ <div className='badge-solid-green h-fit mx-auto'>Lunas</div>
+ )}
+ </td>
+ <td className='!text-left'>{currencyFormat(invoice.amountTotal)}</td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+
+ <Pagination
+ pageCount={pageCount}
+ currentPage={parseInt(page)}
+ url={`/my/transactions${pageQuery}`}
+ className='mt-2 mb-2'
+ />
+ </div>
</div>
- </BottomPopup>
- </div>
+ </DesktopView>
+ </>
)
}
diff --git a/src/lib/product/api/productPriceApi.js b/src/lib/product/api/productPriceApi.js
new file mode 100644
index 00000000..94a68216
--- /dev/null
+++ b/src/lib/product/api/productPriceApi.js
@@ -0,0 +1,8 @@
+import odooApi from '@/core/api/odooApi'
+
+const productPriceApi = async ({ id }) => {
+ const dataProductPrice = await odooApi('GET', `/api/v1/product/template/price/${id}`)
+ return dataProductPrice
+}
+
+export default productPriceApi
diff --git a/src/lib/product/components/Product/ProductDesktop.jsx b/src/lib/product/components/Product/ProductDesktop.jsx
index 98b40400..663d5a74 100644
--- a/src/lib/product/components/Product/ProductDesktop.jsx
+++ b/src/lib/product/components/Product/ProductDesktop.jsx
@@ -8,8 +8,12 @@ import LazyLoad from 'react-lazy-load'
import ProductSimilar from '../ProductSimilar'
import { toast } from 'react-hot-toast'
import { updateItemCart } from '@/core/utils/cart'
+import useVariantPrice from '@/lib/variant/hooks/useVariantPrice'
+import useProductPrice from '../../hooks/useProductPrice'
+import PriceSkeleton from '@/core/components/elements/Skeleton/PriceSkeleton'
const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
+ const { productPrice } = useProductPrice({ id: product.id })
const [informationTab, setInformationTab] = useState(informationTabOptions[0].value)
const variantQuantityRefs = useRef([])
@@ -104,28 +108,33 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
{product.variants.length > 1 && product.lowestPrice.priceDiscount > 0 && (
<div className='text-gray_r-12/80'>Harga mulai dari: </div>
)}
- {product?.lowestPrice.discountPercentage > 0 && (
- <div className='flex gap-x-1 items-center mt-2'>
- <div className='badge-solid-red text-caption-1'>
- {product?.lowestPrice.discountPercentage}%
- </div>
- <div className='text-gray_r-11 line-through text-caption-1'>
- {currencyFormat(product?.lowestPrice.price)}
- </div>
- </div>
+ {productPrice.isLoading && <PriceSkeleton />}
+ {productPrice.isFetched && (
+ <>
+ {productPrice?.data?.discount > 0 && (
+ <div className='flex gap-x-1 items-center mt-2'>
+ <div className='badge-solid-red text-caption-1'>
+ {productPrice?.data?.discount}%
+ </div>
+ <div className='text-gray_r-11 line-through text-caption-1'>
+ {currencyFormat(productPrice?.data?.priceExclude)}
+ </div>
+ </div>
+ )}
+ <h3 className='text-red_r-11 font-semibold mt-1 text-title-md'>
+ {productPrice?.data?.priceExcludeAfterDiscount > 0 ? (
+ currencyFormat(productPrice?.data?.priceExcludeAfterDiscount)
+ ) : (
+ <span className='text-gray_r-11 leading-6 font-normal'>
+ Hubungi kami untuk dapatkan harga terbaik,&nbsp;
+ <a href='https://wa.me/' className='text-red_r-11 underline'>
+ klik disini
+ </a>
+ </span>
+ )}
+ </h3>
+ </>
)}
- <h3 className='text-red_r-11 font-semibold mt-1 text-title-md'>
- {product?.lowestPrice.priceDiscount > 0 ? (
- currencyFormat(product?.lowestPrice.priceDiscount)
- ) : (
- <span className='text-gray_r-11 leading-6 font-normal'>
- Hubungi kami untuk dapatkan harga terbaik,&nbsp;
- <a href='https://wa.me/' className='text-red_r-11 underline'>
- klik disini
- </a>
- </span>
- )}
- </h3>
<button
type='button'
onClick={goToVariantSection}
@@ -166,14 +175,7 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
<td>{variant.code}</td>
<td>{variant.attributes.join(', ')}</td>
<td>
- {variant.price.discountPercentage > 0 && (
- <>
- <span className='line-through text-caption-1 text-gray_r-11'>
- {currencyFormat(variant.price.price)}
- </span>{' '}
- </>
- )}
- {currencyFormat(variant.price.priceDiscount)}
+ <VariantPrice id={variant.id} />
</td>
<td>
<input
@@ -245,6 +247,25 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
)
}
+const VariantPrice = ({ id }) => {
+ const { variantPrice } = useVariantPrice({ id })
+
+ if (variantPrice.isLoading) return <PriceSkeleton />
+
+ return (
+ <>
+ {variantPrice?.data?.discount > 0 && (
+ <>
+ <span className='line-through text-caption-1 text-gray_r-11'>
+ {currencyFormat(variantPrice?.data?.priceExclude)}
+ </span>{' '}
+ </>
+ )}
+ {currencyFormat(variantPrice?.data?.priceExcludeAfterDiscount)}
+ </>
+ )
+}
+
const informationTabOptions = [
{ value: 'description', label: 'Deskripsi' },
{ value: 'information', label: 'Info Penting' }
diff --git a/src/lib/product/components/Product/ProductMobile.jsx b/src/lib/product/components/Product/ProductMobile.jsx
index c572a58e..f6ed3d40 100644
--- a/src/lib/product/components/Product/ProductMobile.jsx
+++ b/src/lib/product/components/Product/ProductMobile.jsx
@@ -11,6 +11,8 @@ import { HeartIcon } from '@heroicons/react/24/outline'
import { useRouter } from 'next/router'
import MobileView from '@/core/components/views/MobileView'
import { toast } from 'react-hot-toast'
+import useVariantPrice from '@/lib/variant/hooks/useVariantPrice'
+import PriceSkeleton from '@/core/components/elements/Skeleton/PriceSkeleton'
const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
const router = useRouter()
@@ -115,26 +117,7 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
</button>
</div>
<h1 className='leading-6 font-medium'>{activeVariant?.name}</h1>
- {activeVariant?.price?.discountPercentage > 0 && (
- <div className='flex gap-x-1 items-center mt-2'>
- <div className='text-gray_r-11 line-through text-caption-1'>
- {currencyFormat(activeVariant?.price?.price)}
- </div>
- <div className='badge-solid-red'>{activeVariant?.price?.discountPercentage}%</div>
- </div>
- )}
- <h3 className='text-red_r-11 font-semibold mt-1'>
- {activeVariant?.price?.priceDiscount > 0 ? (
- currencyFormat(activeVariant?.price?.priceDiscount)
- ) : (
- <span className='text-gray_r-11 leading-6 font-normal'>
- Hubungi kami untuk dapatkan harga terbaik,&nbsp;
- <a href='https://wa.me/' className='text-red_r-11 underline'>
- klik disini
- </a>
- </span>
- )}
- </h3>
+ <VariantPrice id={activeVariant.id} />
</div>
<Divider />
@@ -249,6 +232,37 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
)
}
+const VariantPrice = ({ id }) => {
+ const { variantPrice } = useVariantPrice({ id })
+
+ if (variantPrice.isLoading) return <PriceSkeleton />
+
+ return (
+ <>
+ {variantPrice?.data?.discount > 0 && (
+ <div className='flex gap-x-1 items-center mt-2'>
+ <div className='text-gray_r-11 line-through text-caption-1'>
+ {currencyFormat(variantPrice?.data?.priceExclude)}
+ </div>
+ <div className='badge-solid-red'>{variantPrice?.data?.discount}%</div>
+ </div>
+ )}
+ <h3 className='text-red_r-11 font-semibold mt-1'>
+ {variantPrice?.data?.priceExcludeAfterDiscount > 0 ? (
+ currencyFormat(variantPrice?.data?.priceExcludeAfterDiscount)
+ ) : (
+ <span className='text-gray_r-11 leading-6 font-normal'>
+ Hubungi kami untuk dapatkan harga terbaik,&nbsp;
+ <a href='https://wa.me/' className='text-red_r-11 underline'>
+ klik disini
+ </a>
+ </span>
+ )}
+ </h3>
+ </>
+ )
+}
+
const informationTabOptions = [
{ value: 'specification', label: 'Spesifikasi' },
{ value: 'description', label: 'Deskripsi' },
diff --git a/src/lib/product/components/ProductCard.jsx b/src/lib/product/components/ProductCard.jsx
index 3454d4fd..e48ab88a 100644
--- a/src/lib/product/components/ProductCard.jsx
+++ b/src/lib/product/components/ProductCard.jsx
@@ -2,6 +2,9 @@ import Image from '@/core/components/elements/Image/Image'
import Link from '@/core/components/elements/Link/Link'
import currencyFormat from '@/core/utils/currencyFormat'
import { createSlug } from '@/core/utils/slug'
+import useProductPrice from '../hooks/useProductPrice'
+import { LazyLoadComponent } from 'react-lazy-load-image-component'
+import PriceSkeleton from '@/core/components/elements/Skeleton/PriceSkeleton'
const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
if (variant == 'vertical') {
@@ -45,22 +48,9 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
>
{product?.name}
</Link>
- {product?.lowestPrice?.discountPercentage > 0 && (
- <div className='flex gap-x-1 mb-1 items-center'>
- <div className='text-gray_r-11 line-through text-[11px] sm:text-caption-2'>
- {currencyFormat(product?.lowestPrice?.price)}
- </div>
- <div className='badge-solid-red'>{product?.lowestPrice?.discountPercentage}%</div>
- </div>
- )}
-
- <div className='text-red_r-11 font-semibold mb-2'>
- {product?.lowestPrice?.priceDiscount > 0 ? (
- currencyFormat(product?.lowestPrice?.priceDiscount)
- ) : (
- <a href='https://wa.me/'>Call for price</a>
- )}
- </div>
+ <LazyLoadComponent>
+ <ProductCardPrice variant='vertical' id={product.id} />
+ </LazyLoadComponent>
{product?.stockTotal > 0 && (
<div className='flex gap-x-1'>
<div className='badge-solid-red'>Ready Stock</div>
@@ -116,30 +106,83 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
{product?.name}
</Link>
- {product?.lowestPrice?.discountPercentage > 0 && (
+ <LazyLoadComponent>
+ <ProductCardPrice variant='horizontal' id={product.id} />
+ </LazyLoadComponent>
+ {product?.stockTotal > 0 && (
+ <div className='flex gap-x-1'>
+ <div className='badge-solid-red'>Ready Stock</div>
+ <div className='badge-gray'>{product?.stockTotal > 5 ? '> 5' : '< 5'}</div>
+ </div>
+ )}
+ </div>
+ </div>
+ )
+ }
+}
+
+const ProductCardPrice = ({ variant, id }) => {
+ const { productPrice } = useProductPrice({ id })
+
+ if (productPrice.isLoading) return <PriceSkeleton />
+
+ if (variant == 'vertical') {
+ return (
+ productPrice.isFetched && (
+ <>
+ {productPrice?.data?.discount > 0 && (
<div className='flex gap-x-1 mb-1 items-center'>
- <div className='badge-solid-red'>{product?.lowestPrice?.discountPercentage}%</div>
- <div className='text-gray_r-11 line-through text-caption-2'>
- {currencyFormat(product?.lowestPrice?.price)}
+ <div className='text-gray_r-11 line-through text-[11px] sm:text-caption-2'>
+ {currencyFormat(
+ productPrice?.data?.priceStartFrom || productPrice?.data?.priceExclude
+ )}
</div>
+ <div className='badge-solid-red'>{productPrice?.data?.discount}%</div>
</div>
)}
<div className='text-red_r-11 font-semibold mb-2'>
- {product?.lowestPrice?.priceDiscount > 0 ? (
- currencyFormat(product?.lowestPrice?.priceDiscount)
+ {productPrice?.data?.priceExcludeAfterDiscount > 0 ? (
+ currencyFormat(
+ productPrice?.data?.priceDiscStartFrom ||
+ productPrice?.data?.priceExcludeAfterDiscount
+ )
) : (
<a href='https://wa.me/'>Call for price</a>
)}
</div>
- {product?.stockTotal > 0 && (
- <div className='flex gap-x-1'>
- <div className='badge-solid-red'>Ready Stock</div>
- <div className='badge-gray'>{product?.stockTotal > 5 ? '> 5' : '< 5'}</div>
+ </>
+ )
+ )
+ }
+
+ if (variant == 'horizontal') {
+ return (
+ productPrice.isFetched && (
+ <>
+ {productPrice?.data?.discount > 0 && (
+ <div className='flex gap-x-1 mb-1 items-center'>
+ <div className='badge-solid-red'>{productPrice?.data?.discount}%</div>
+ <div className='text-gray_r-11 line-through text-caption-2'>
+ {currencyFormat(
+ productPrice?.data?.priceStartFrom || productPrice?.data?.priceExclude
+ )}
+ </div>
</div>
)}
- </div>
- </div>
+
+ <div className='text-red_r-11 font-semibold mb-2'>
+ {productPrice?.data?.priceExcludeAfterDiscount > 0 ? (
+ currencyFormat(
+ productPrice?.data?.priceDiscStartFrom ||
+ productPrice?.data?.priceExcludeAfterDiscount
+ )
+ ) : (
+ <a href='https://wa.me/'>Call for price</a>
+ )}
+ </div>
+ </>
+ )
)
}
}
diff --git a/src/lib/product/hooks/useProductPrice.js b/src/lib/product/hooks/useProductPrice.js
new file mode 100644
index 00000000..f8ef62b8
--- /dev/null
+++ b/src/lib/product/hooks/useProductPrice.js
@@ -0,0 +1,13 @@
+import { useQuery } from 'react-query'
+import productPriceApi from '../api/productPriceApi'
+
+const useProductPrice = ({ id }) => {
+ const fetchProductPrice = async () => await productPriceApi({ id })
+ const productPrice = useQuery(`productPrice-${id}`, fetchProductPrice, {
+ refetchOnWindowFocus: false
+ })
+
+ return { productPrice }
+}
+
+export default useProductPrice
diff --git a/src/lib/transaction/components/Transaction.jsx b/src/lib/transaction/components/Transaction.jsx
index dc9338fa..104a7f2d 100644
--- a/src/lib/transaction/components/Transaction.jsx
+++ b/src/lib/transaction/components/Transaction.jsx
@@ -15,6 +15,12 @@ import Link from '@/core/components/elements/Link/Link'
import Alert from '@/core/components/elements/Alert/Alert'
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'
const Transaction = ({ id }) => {
const { transaction } = useTransaction({ id })
@@ -88,10 +94,7 @@ const Transaction = ({ id }) => {
const memoizeVariantGroupCard = useMemo(
() => (
<div className='p-4 pt-0 flex flex-col gap-y-3'>
- <VariantGroupCard
- variants={transaction.data?.products}
- buyMore
- />
+ <VariantGroupCard variants={transaction.data?.products} buyMore />
<div className='flex justify-between mt-1'>
<p className='text-gray_r-12/70'>Subtotal</p>
<p>{currencyFormat(totalAmount)}</p>
@@ -124,120 +127,6 @@ const Transaction = ({ id }) => {
return (
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>
-
- {memoizeVariantGroupCard}
-
- <Divider />
-
- <SectionAddress address={transaction.data?.address} />
-
- <Divider />
-
- <div className='p-4'>
- <p className='font-medium'>Invoice</p>
- <div className='flex flex-col gap-y-3 mt-4'>
- {transaction.data?.invoices?.map((invoice, index) => (
- <Link
- href={`/my/invoice/${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 && (
- <Alert
- type='info'
- className='text-center'
- >
- Belum ada Invoice
- </Alert>
- )}
- </div>
- </div>
-
- <Divider />
-
- <div className='p-4 pt-0'>
- {transaction.data?.status == 'draft' && (
- <button
- className='btn-yellow w-full mt-4'
- onClick={checkout}
- >
- Lanjutkan Transaksi
- </button>
- )}
- <button
- className='btn-light w-full mt-4'
- disabled={transaction.data?.status != 'draft'}
- onClick={downloadQuotation}
- >
- Download Quotation
- </button>
- {transaction.data?.status != 'draft' && (
- <button
- className='btn-light w-full mt-4'
- disabled={transaction.data?.status != 'waiting'}
- onClick={openCancelTransaction}
- >
- Batalkan Transaksi
- </button>
- )}
- </div>
-
<BottomPopup
active={cancelTransaction}
close={closeCancelTransaction}
@@ -255,54 +144,334 @@ const Transaction = ({ id }) => {
>
Ya, Batalkan
</button>
- <button
- className='btn-light flex-1'
- type='button'
- onClick={closeCancelTransaction}
- >
+ <button className='btn-light flex-1' type='button' onClick={closeCancelTransaction}>
Batal
</button>
</div>
</BottomPopup>
- <BottomPopup
- title='Upload PO'
- close={closeUploadPo}
- active={uploadPo}
- >
+ <BottomPopup title='Upload PO' close={closeUploadPo} active={uploadPo}>
<div>
<label>Nomor PO</label>
- <input
- type='text'
- className='form-input mt-3'
- ref={poNumber}
- />
+ <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}
- />
+ <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>
</BottomPopup>
+
+ <MobileView>
+ <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>
+
+ {memoizeVariantGroupCard}
+
+ <Divider />
+
+ <SectionAddress address={transaction.data?.address} />
+
+ <Divider />
+
+ <div className='p-4'>
+ <p className='font-medium'>Invoice</p>
+ <div className='flex flex-col gap-y-3 mt-4'>
+ {transaction.data?.invoices?.map((invoice, index) => (
+ <Link href={`/my/invoice/${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 && (
+ <Alert type='info' className='text-center'>
+ Belum ada Invoice
+ </Alert>
+ )}
+ </div>
+ </div>
+
+ <Divider />
+
+ <div className='p-4 pt-0'>
+ {transaction.data?.status == 'draft' && (
+ <button className='btn-yellow w-full mt-4' onClick={checkout}>
+ Lanjutkan Transaksi
+ </button>
+ )}
+ <button
+ className='btn-light w-full mt-4'
+ disabled={transaction.data?.status != 'draft'}
+ onClick={downloadQuotation}
+ >
+ Download Quotation
+ </button>
+ {transaction.data?.status != 'draft' && (
+ <button
+ className='btn-light w-full mt-4'
+ disabled={transaction.data?.status != 'waiting'}
+ onClick={openCancelTransaction}
+ >
+ Batalkan Transaksi
+ </button>
+ )}
+ </div>
+ </MobileView>
+
+ <DesktopView>
+ <div className='container mx-auto flex py-10'>
+ <div className='w-3/12 pr-4'>
+ <Menu />
+ </div>
+ <div className='w-9/12 p-4 bg-white border border-gray_r-6 rounded'>
+ <h1 className='text-title-sm font-semibold mb-6'>Detail Transaksi</h1>
+
+ <div className='flex items-center gap-x-2 mb-3'>
+ <span className='text-h-sm font-medium'>{transaction?.data?.name}</span>
+ <TransactionStatusBadge status={transaction?.data?.status} />
+ </div>
+ <button
+ type='button'
+ className='btn-solid-red px-3 py-2'
+ disabled={transaction.data?.status != 'draft'}
+ onClick={downloadQuotation}
+ >
+ Download
+ </button>
+
+ <div className='grid grid-cols-2 gap-x-6 mt-6'>
+ <div className='grid grid-cols-2 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>
+
+ <div>Purchase Order</div>
+ <div>
+ : {transaction?.data?.purchaseOrderName}{' '}
+ <button
+ type='button'
+ className='inline-block text-red_r-11'
+ 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-6 mb-4'>Info Pengiriman</div>
+ <div className='grid grid-cols-3 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} />
+ </div>
+ <div className='border border-gray_r-6 rounded p-3'>
+ <div className='font-medium mb-4'>Detail Pengiriman</div>
+ <SectionContent address={transaction?.data?.address?.shipping} />
+ </div>
+ <div className='border border-gray_r-6 rounded p-3'>
+ <div className='font-medium mb-4'>Detail Penagihan</div>
+ <SectionContent address={transaction?.data?.address?.invoice} />
+ </div>
+ </div>
+
+ <div className='text-h-sm font-semibold mt-6 mb-4'>Rincian Pembelian</div>
+ <table className='table-data'>
+ <thead>
+ <tr>
+ <th>Nama Produk</th>
+ <th>Jumlah</th>
+ <th>Harga</th>
+ <th>Diskon</th>
+ <th>Subtotal</th>
+ </tr>
+ </thead>
+ <tbody>
+ {transaction?.data?.products?.map((product) => (
+ <tr key={product.id}>
+ <td className='flex'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ className='w-[30%] flex-shrink-0'
+ >
+ <Image
+ src={product?.parent?.image}
+ alt={product?.name}
+ className='object-contain object-center border border-gray_r-6 h-40 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.quantity}</td>
+ <td>{currencyFormat(product.price.price)}</td>
+ <td>
+ {product.price.discountPercentage > 0
+ ? `${product.price.discountPercentage}%`
+ : ''}
+ </td>
+ <td>{currencyFormat(product.price.priceDiscount * product.quantity)}</td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+
+ <div className='flex justify-end mt-4'>
+ <div className='w-1/4 grid grid-cols-2 gap-y-2 text-gray_r-12/80'>
+ <div className='text-right'>Subtotal</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 className='text-right'>PPN 11% (Incl.)</div>
+ <div className='text-right font-medium'>{currencyFormat(totalAmount * 0.11)}</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='text-h-sm font-semibold mt-6 mb-4'>Invoice</div>
+ <div className='grid grid-cols-4 gap-4'>
+ {transaction.data?.invoices?.map((invoice, index) => (
+ <Link href={`/my/invoice/${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 && (
+ <Alert type='info' className='text-center'>
+ Belum ada Invoice
+ </Alert>
+ )}
+
+ <div className='mt-6'>
+ {transaction.data?.status == 'draft' && (
+ <button className='btn-yellow' onClick={checkout}>
+ Lanjutkan Transaksi
+ </button>
+ )}
+ {transaction.data?.status != 'draft' && (
+ <button
+ className='btn-light'
+ disabled={transaction.data?.status != 'waiting'}
+ onClick={openCancelTransaction}
+ >
+ Batalkan Transaksi
+ </button>
+ )}
+ </div>
+ </div>
+ </div>
+ </DesktopView>
</>
)
)
@@ -351,10 +520,7 @@ const SectionAddress = ({ address }) => {
}
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' />}
</button>
@@ -363,13 +529,13 @@ const SectionButton = ({ label, active, toggle }) => (
const SectionContent = ({ address }) => {
let fullAddress = []
if (address?.street) fullAddress.push(address.street)
- if (address?.subDistrict?.name) fullAddress.push(address.subDistrict.name)
- if (address?.district?.name) fullAddress.push(address.district.name)
- if (address?.city?.name) fullAddress.push(address.city.name)
+ 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 border-t border-gray_r-6'>
+ <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>
diff --git a/src/lib/transaction/components/TransactionStatusBadge.jsx b/src/lib/transaction/components/TransactionStatusBadge.jsx
index 7372e4da..88467c2b 100644
--- a/src/lib/transaction/components/TransactionStatusBadge.jsx
+++ b/src/lib/transaction/components/TransactionStatusBadge.jsx
@@ -1,6 +1,6 @@
const TransactionStatusBadge = ({ status }) => {
let badgeProps = {
- className: ['h-fit'],
+ className: ['h-fit md:text-caption-2'],
text: ''
}
switch (status) {
diff --git a/src/lib/transaction/components/Transactions.jsx b/src/lib/transaction/components/Transactions.jsx
index ccbdede2..30c670ae 100644
--- a/src/lib/transaction/components/Transactions.jsx
+++ b/src/lib/transaction/components/Transactions.jsx
@@ -15,6 +15,9 @@ import Pagination from '@/core/components/elements/Pagination/Pagination'
import { toQuery } from 'lodash-contrib'
import _ from 'lodash'
import Alert from '@/core/components/elements/Alert/Alert'
+import MobileView from '@/core/components/views/MobileView'
+import DesktopView from '@/core/components/views/DesktopView'
+import Menu from '@/lib/auth/components/Menu'
const Transactions = () => {
const router = useRouter()
@@ -55,161 +58,222 @@ const Transactions = () => {
}
return (
- <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>
-
- {transactions.isLoading && (
- <div className='flex justify-center my-4'>
- <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'>
- <Link href={`/my/transaction/${saleOrder.id}`}>
- <span className='text-caption-2 text-gray_r-11'>No. Transaksi</span>
- <h2 className='text-red_r-11 mt-1'>{saleOrder.name}</h2>
- </Link>
- <div className='flex gap-x-1 justify-end'>
- <TransactionStatusBadge status={saleOrder.status} />
- <EllipsisVerticalIcon
- className='w-5 h-5'
- onClick={() => setToOthers(saleOrder)}
- />
+ <>
+ <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>
+
+ {transactions.isLoading && (
+ <div className='flex justify-center my-4'>
+ <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' />
</div>
- </div>
- <Link href={`/my/transaction/${saleOrder.id}`}>
- <div className='grid grid-cols-2 mt-3'>
- <div>
- <span className='text-caption-2 text-gray_r-11'>No. Purchase Order</span>
- <p className='mt-1 font-medium text-gray_r-12'>
- {saleOrder.purchaseOrderName || '-'}
- </p>
- </div>
- <div className='text-right'>
- <span className='text-caption-2 text-gray_r-11'>Total Invoice</span>
- <p className='mt-1 font-medium text-gray_r-12'>{saleOrder.invoiceCount} Invoice</p>
+ )}
+
+ {!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'>
+ <Link href={`/my/transaction/${saleOrder.id}`}>
+ <span className='text-caption-2 text-gray_r-11'>No. Transaksi</span>
+ <h2 className='text-red_r-11 mt-1'>{saleOrder.name}</h2>
+ </Link>
+ <div className='flex gap-x-1 justify-end'>
+ <TransactionStatusBadge status={saleOrder.status} />
+ <EllipsisVerticalIcon
+ className='w-5 h-5'
+ onClick={() => setToOthers(saleOrder)}
+ />
+ </div>
</div>
+ <Link href={`/my/transaction/${saleOrder.id}`}>
+ <div className='grid grid-cols-2 mt-3'>
+ <div>
+ <span className='text-caption-2 text-gray_r-11'>No. Purchase Order</span>
+ <p className='mt-1 font-medium text-gray_r-12'>
+ {saleOrder.purchaseOrderName || '-'}
+ </p>
+ </div>
+ <div className='text-right'>
+ <span className='text-caption-2 text-gray_r-11'>Total Invoice</span>
+ <p className='mt-1 font-medium text-gray_r-12'>
+ {saleOrder.invoiceCount} Invoice
+ </p>
+ </div>
+ </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'>{saleOrder.sales}</p>
+ </div>
+ <div className='text-right'>
+ <span className='text-caption-2 text-gray_r-11'>Total Harga</span>
+ <p className='mt-1 font-medium text-gray_r-12'>
+ {currencyFormat(saleOrder.amountTotal)}
+ </p>
+ </div>
+ </div>
+ </Link>
</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'>{saleOrder.sales}</p>
- </div>
- <div className='text-right'>
- <span className='text-caption-2 text-gray_r-11'>Total Harga</span>
- <p className='mt-1 font-medium text-gray_r-12'>
- {currencyFormat(saleOrder.amountTotal)}
- </p>
- </div>
+ ))}
+
+ <Pagination
+ pageCount={pageCount}
+ currentPage={parseInt(page)}
+ url={`/my/transactions${pageQuery}`}
+ className='mt-2 mb-2'
+ />
+
+ <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>
- </Link>
- </div>
- ))}
-
- <Pagination
- pageCount={pageCount}
- currentPage={parseInt(page)}
- url={`/my/transactions${pageQuery}`}
- className='mt-2 mb-2'
- />
-
- <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>
-
- <BottomPopup
- active={toCancel}
- close={() => setToCancel(null)}
- title='Batalkan Transaksi'
- >
- <div className='leading-7 text-gray_r-12/80'>
- Apakah anda yakin membatalkan transaksi{' '}
- <span className='underline'>{toCancel?.name}</span>?
+ </BottomPopup>
+
+ <BottomPopup active={toCancel} close={() => setToCancel(null)} title='Batalkan Transaksi'>
+ <div className='leading-7 text-gray_r-12/80'>
+ Apakah anda yakin membatalkan transaksi{' '}
+ <span className='underline'>{toCancel?.name}</span>?
+ </div>
+ <div className='flex mt-6 gap-x-4'>
+ <button
+ className='btn-solid-red flex-1'
+ type='button'
+ onClick={submitCancelTransaction}
+ >
+ Ya, Batalkan
+ </button>
+ <button className='btn-light flex-1' type='button' onClick={() => setToCancel(null)}>
+ Batal
+ </button>
+ </div>
+ </BottomPopup>
</div>
- <div className='flex mt-6 gap-x-4'>
- <button
- className='btn-solid-red flex-1'
- type='button'
- onClick={submitCancelTransaction}
- >
- Ya, Batalkan
- </button>
- <button
- className='btn-light flex-1'
- type='button'
- onClick={() => setToCancel(null)}
- >
- Batal
- </button>
+ </MobileView>
+
+ <DesktopView>
+ <div className='container mx-auto flex py-10'>
+ <div className='w-3/12 pr-4'>
+ <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'>
+ <h1 className='text-title-sm font-semibold'>
+ Daftar Transaksi{' '}
+ {transactions?.data?.saleOrders ? `(${transactions?.data?.saleOrders.length})` : ''}
+ </h1>
+ <form className='flex gap-x-2' onSubmit={handleSubmit}>
+ <input
+ type='text'
+ className='form-input'
+ placeholder='Cari Transaksi...'
+ value={inputQuery}
+ onChange={(e) => setInputQuery(e.target.value)}
+ />
+ <button className='btn-light bg-transparent px-3' type='submit'>
+ <MagnifyingGlassIcon className='w-6' />
+ </button>
+ </form>
+ </div>
+ <table className='table-data'>
+ <thead>
+ <tr>
+ <th>No. Transaksi</th>
+ <th>Tanggal</th>
+ <th className='!text-left'>Salesperson</th>
+ <th className='!text-left'>Total</th>
+ <th>Status</th>
+ </tr>
+ </thead>
+ <tbody>
+ {transactions.isLoading && (
+ <tr>
+ <td colSpan={5}>
+ <div className='flex justify-center my-2'>
+ <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' />
+ </div>
+ </td>
+ </tr>
+ )}
+ {!transactions.isLoading &&
+ (!transactions?.data?.saleOrders ||
+ transactions?.data?.saleOrders?.length == 0) && (
+ <tr>
+ <td colSpan={5}>Tidak ada data transaksi</td>
+ </tr>
+ )}
+ {transactions.data?.saleOrders?.map((saleOrder) => (
+ <tr key={saleOrder.id}>
+ <td>
+ <Link href={`/my/transaction/${saleOrder.id}`}>{saleOrder.name}</Link>
+ </td>
+ <td>-</td>
+ <td className='!text-left'>{saleOrder.sales}</td>
+ <td className='!text-left'>{currencyFormat(saleOrder.amountTotal)}</td>
+ <td>
+ <div className='flex justify-center'>
+ <TransactionStatusBadge status={saleOrder.status} />
+ </div>
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+
+ <Pagination
+ pageCount={pageCount}
+ currentPage={parseInt(page)}
+ url={`/my/transactions${pageQuery}`}
+ className='mt-2 mb-2'
+ />
+ </div>
</div>
- </BottomPopup>
- </div>
+ </DesktopView>
+ </>
)
}
diff --git a/src/lib/variant/api/variantPriceApi.js b/src/lib/variant/api/variantPriceApi.js
new file mode 100644
index 00000000..8621ca78
--- /dev/null
+++ b/src/lib/variant/api/variantPriceApi.js
@@ -0,0 +1,8 @@
+import odooApi from '@/core/api/odooApi'
+
+const variantPriceApi = async ({ id }) => {
+ const dataVariantPrice = await odooApi('GET', `/api/v1/product/product/price/${id}`)
+ return dataVariantPrice
+}
+
+export default variantPriceApi
diff --git a/src/lib/variant/hooks/useVariantPrice.js b/src/lib/variant/hooks/useVariantPrice.js
new file mode 100644
index 00000000..d00eb810
--- /dev/null
+++ b/src/lib/variant/hooks/useVariantPrice.js
@@ -0,0 +1,13 @@
+import { useQuery } from 'react-query'
+import variantPriceApi from '../api/variantPriceApi'
+
+const useVariantPrice = ({ id }) => {
+ const fetchVariantPrice = async () => await variantPriceApi({ id })
+ const variantPrice = useQuery(`variantPrice-${id}`, fetchVariantPrice, {
+ refetchOnWindowFocus: false
+ })
+
+ return { variantPrice }
+}
+
+export default useVariantPrice
diff --git a/src/lib/wishlist/components/Wishlists.jsx b/src/lib/wishlist/components/Wishlists.jsx
index e61efcc3..e13c61e5 100644
--- a/src/lib/wishlist/components/Wishlists.jsx
+++ b/src/lib/wishlist/components/Wishlists.jsx
@@ -4,6 +4,9 @@ import Spinner from '@/core/components/elements/Spinner/Spinner'
import ProductCard from '@/lib/product/components/ProductCard'
import { useRouter } from 'next/router'
import useWishlists from '../hooks/useWishlists'
+import MobileView from '@/core/components/views/MobileView'
+import DesktopView from '@/core/components/views/DesktopView'
+import Menu from '@/lib/auth/components/Menu'
const Wishlists = () => {
const router = useRouter()
@@ -22,33 +25,46 @@ const Wishlists = () => {
}
return (
- <div className='px-4 py-6'>
- {wishlists.data?.products?.length == 0 && (
- <Alert
- type='info'
- className='text-center'
- >
- Wishlist anda masih kosong
- </Alert>
- )}
-
- <div className='grid grid-cols-2 gap-3'>
- {wishlists.data?.products.map((product) => (
- <ProductCard
- key={product.id}
- product={product}
- />
- ))}
- </div>
+ <>
+ <MobileView>
+ <div className='px-4 py-6'>
+ {wishlists.data?.products?.length == 0 && (
+ <Alert type='info' className='text-center'>
+ Wishlist anda masih kosong
+ </Alert>
+ )}
- <div className='mt-6'>
- <Pagination
- currentPage={page}
- pageCount={pageCount}
- url={`/my/wishlist`}
- />
- </div>
- </div>
+ <div className='grid grid-cols-2 gap-3'>
+ {wishlists.data?.products.map((product) => (
+ <ProductCard key={product.id} product={product} />
+ ))}
+ </div>
+
+ <div className='mt-6'>
+ <Pagination currentPage={page} pageCount={pageCount} url={`/my/wishlist`} />
+ </div>
+ </div>
+ </MobileView>
+
+ <DesktopView>
+ <div className='container mx-auto flex py-10'>
+ <div className='w-3/12 pr-4'>
+ <Menu />
+ </div>
+ <div className='w-9/12'>
+ <div className='grid grid-cols-5 gap-3'>
+ {wishlists.data?.products.map((product) => (
+ <ProductCard key={product.id} product={product} />
+ ))}
+ </div>
+
+ <div className='mt-6'>
+ <Pagination currentPage={page} pageCount={pageCount} url={`/my/wishlist`} />
+ </div>
+ </div>
+ </div>
+ </DesktopView>
+ </>
)
}