diff options
| author | IT Fixcomart <it@fixcomart.co.id> | 2023-03-01 09:18:52 +0000 |
|---|---|---|
| committer | IT Fixcomart <it@fixcomart.co.id> | 2023-03-01 09:18:52 +0000 |
| commit | a7abbf4ddc70068620e9f44b74dc162ce2e16ee2 (patch) | |
| tree | 74f66253717515d364ce74bd8275015c1f829cbc /src2/pages/my | |
| parent | 90e1edab9b6a8ccc09a49fed3addbec2cbc4e4c3 (diff) | |
| parent | a1b9b647a6c4bda1f5db63879639d44543f9557e (diff) | |
Merged in refactor (pull request #1)
Refactor
Diffstat (limited to 'src2/pages/my')
| -rw-r--r-- | src2/pages/my/address/[id]/edit.js | 249 | ||||
| -rw-r--r-- | src2/pages/my/address/create.js | 234 | ||||
| -rw-r--r-- | src2/pages/my/address/index.js | 84 | ||||
| -rw-r--r-- | src2/pages/my/invoice/[id].js | 149 | ||||
| -rw-r--r-- | src2/pages/my/invoices.js | 180 | ||||
| -rw-r--r-- | src2/pages/my/menu.js | 82 | ||||
| -rw-r--r-- | src2/pages/my/profile.js | 134 | ||||
| -rw-r--r-- | src2/pages/my/transaction/[id].js | 265 | ||||
| -rw-r--r-- | src2/pages/my/transactions.js | 198 | ||||
| -rw-r--r-- | src2/pages/my/wishlist.js | 60 |
10 files changed, 1635 insertions, 0 deletions
diff --git a/src2/pages/my/address/[id]/edit.js b/src2/pages/my/address/[id]/edit.js new file mode 100644 index 00000000..838d39e7 --- /dev/null +++ b/src2/pages/my/address/[id]/edit.js @@ -0,0 +1,249 @@ +import { Controller, useForm } from "react-hook-form" +import WithAuth from "@/components/auth/WithAuth"; +import Layout from "@/components/layouts/Layout"; +import AppBar from "@/components/layouts/AppBar"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as Yup from "yup"; +import { Select } from "@/components/elements/Fields"; +import { useEffect, useState } from "react"; +import apiOdoo from "@/core/utils/apiOdoo"; +import { toast } from "react-hot-toast"; +import { useRouter } from "next/router"; + +const validationSchema = Yup.object().shape({ + type: Yup.string().required('Harus di-pilih'), + name: Yup.string().min(3, 'Minimal 3 karakter').required('Harus di-isi'), + email: Yup.string().email('Format harus seperti johndoe@example.com').required('Harus di-isi'), + mobile: Yup.string().required('Harus di-isi'), + street: Yup.string().required('Harus di-isi'), + zip: Yup.string().required('Harus di-isi'), + city: Yup.string().required('Harus di-pilih'), +}); + +const types = [ + { value: 'contact', label: 'Contact Address' }, + { value: 'invoice', label: 'Invoice Address' }, + { value: 'delivery', label: 'Delivery Address' }, + { value: 'other', label: 'Other Address' }, +]; + +export async function getServerSideProps( context ) { + const { id } = context.query; + const address = await apiOdoo('GET', `/api/v1/partner/${id}/address`); + let defaultValues = { + type: address.type, + name: address.name, + email: address.email, + mobile: address.mobile, + street: address.street, + zip: address.zip, + city: address.city?.id || '', + oldDistrict: address.district?.id || '', + district: '', + oldSubDistrict: address.sub_district?.id || '', + subDistrict: '', + }; + return { props: { id, defaultValues } }; +} + +export default function EditAddress({ id, defaultValues }) { + const router = useRouter(); + const { + register, + formState: { errors }, + handleSubmit, + watch, + setValue, + getValues, + control, + } = useForm({ + resolver: yupResolver(validationSchema), + defaultValues + }); + + const [ cities, setCities ] = useState([]); + const [ districts, setDistricts ] = useState([]); + const [ subDistricts, setSubDistricts ] = useState([]); + + useEffect(() => { + const loadCities = async () => { + let dataCities = await apiOdoo('GET', '/api/v1/city'); + dataCities = dataCities.map((city) => ({ value: city.id, label: city.name })); + setCities(dataCities); + }; + loadCities(); + }, []); + + const watchCity = watch('city'); + useEffect(() => { + setValue('district', ''); + if (watchCity) { + const loadDistricts = async () => { + let dataDistricts = await apiOdoo('GET', `/api/v1/district?city_id=${watchCity}`); + dataDistricts = dataDistricts.map((district) => ({ value: district.id, label: district.name })); + setDistricts(dataDistricts); + let oldDistrict = getValues('oldDistrict'); + if (oldDistrict) { + setValue('district', oldDistrict); + setValue('oldDistrict', ''); + } + }; + loadDistricts(); + } + }, [ watchCity, setValue, getValues ]); + + const watchDistrict = watch('district'); + useEffect(() => { + setValue('subDistrict', ''); + if (watchDistrict) { + const loadSubDistricts = async () => { + let dataSubDistricts = await apiOdoo('GET', `/api/v1/sub_district?district_id=${watchDistrict}`); + dataSubDistricts = dataSubDistricts.map((district) => ({ value: district.id, label: district.name })); + setSubDistricts(dataSubDistricts); + let oldSubDistrict = getValues('oldSubDistrict'); + if (oldSubDistrict) { + setValue('subDistrict', oldSubDistrict); + setValue('oldSubDistrict', ''); + } + }; + loadSubDistricts(); + } + }, [ watchDistrict, setValue, getValues ]) + + const onSubmitHandler = async (values) => { + const parameters = { + ...values, + city_id: values.city, + district_id: values.district, + sub_district_id: values.subDistrict + } + + const address = await apiOdoo('PUT', `/api/v1/partner/${id}/address`, parameters); + if (address?.id) { + toast.success('Berhasil mengubah alamat'); + router.back(); + } + }; + + return ( + <WithAuth> + <Layout> + <AppBar title="Ubah Alamat" /> + + <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 => <Select {...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 => <Select {...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 => ( + <Select + {...props} + options={districts} + disabled={!watchCity} + /> + )} + /> + </div> + + <div> + <label className="form-label mb-2">Kelurahan</label> + <Controller + name="subDistrict" + control={control} + render={props => ( + <Select + {...props} + options={subDistricts} + disabled={!watchDistrict} + /> + )} + /> + </div> + + <button + type="submit" + className="btn-yellow mt-2 w-full" + > + Simpan + </button> + </form> + </Layout> + </WithAuth> + ) +}
\ No newline at end of file diff --git a/src2/pages/my/address/create.js b/src2/pages/my/address/create.js new file mode 100644 index 00000000..42cd117c --- /dev/null +++ b/src2/pages/my/address/create.js @@ -0,0 +1,234 @@ +import { Controller, useForm } from "react-hook-form" +import WithAuth from "@/components/auth/WithAuth"; +import Layout from "@/components/layouts/Layout"; +import AppBar from "@/components/layouts/AppBar"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as Yup from "yup"; +import { Select } from "@/components/elements/Fields"; +import { useEffect, useState } from "react"; +import apiOdoo from "@/core/utils/apiOdoo"; +import { useAuth } from "@/core/utils/auth"; +import { toast } from "react-hot-toast"; +import { useRouter } from "next/router"; + +const validationSchema = Yup.object().shape({ + type: Yup.string().required('Harus di-pilih'), + name: Yup.string().min(3, 'Minimal 3 karakter').required('Harus di-isi'), + email: Yup.string().email('Format harus seperti johndoe@example.com').required('Harus di-isi'), + mobile: Yup.string().required('Harus di-isi'), + street: Yup.string().required('Harus di-isi'), + zip: Yup.string().required('Harus di-isi'), + city: Yup.string().required('Harus di-pilih'), +}); + +const defaultValues = { + type: '', + name: '', + email: '', + mobile: '', + street: '', + city: '', + district: '', + subDistrict: '', + zip: '', +}; + +const types = [ + { value: 'contact', label: 'Contact Address' }, + { value: 'invoice', label: 'Invoice Address' }, + { value: 'delivery', label: 'Delivery Address' }, + { value: 'other', label: 'Other Address' }, +]; + +export default function CreateAddress() { + const [ auth ] = useAuth(); + const router = useRouter(); + const { + register, + formState: { errors }, + handleSubmit, + watch, + setValue, + control, + } = useForm({ + resolver: yupResolver(validationSchema), + defaultValues + }); + + const [ cities, setCities ] = useState([]); + const [ districts, setDistricts ] = useState([]); + const [ subDistricts, setSubDistricts ] = useState([]); + + useEffect(() => { + const loadCities = async () => { + let dataCities = await apiOdoo('GET', '/api/v1/city'); + dataCities = dataCities.map((city) => ({ value: city.id, label: city.name })); + setCities(dataCities); + }; + loadCities(); + }, []); + + const watchCity = watch('city'); + useEffect(() => { + setValue('district', ''); + if (watchCity) { + const loadDistricts = async () => { + let dataDistricts = await apiOdoo('GET', `/api/v1/district?city_id=${watchCity}`); + dataDistricts = dataDistricts.map((district) => ({ value: district.id, label: district.name })); + setDistricts(dataDistricts); + }; + loadDistricts(); + } + }, [ watchCity, setValue ]); + + const watchDistrict = watch('district'); + useEffect(() => { + setValue('subDistrict', ''); + if (watchDistrict) { + const loadSubDistricts = async () => { + let dataSubDistricts = await apiOdoo('GET', `/api/v1/sub_district?district_id=${watchDistrict}`); + dataSubDistricts = dataSubDistricts.map((district) => ({ value: district.id, label: district.name })); + setSubDistricts(dataSubDistricts); + }; + loadSubDistricts(); + } + }, [ watchDistrict, setValue ]) + + const onSubmitHandler = async (values) => { + const parameters = { + ...values, + city_id: values.city, + district_id: values.district, + sub_district_id: values.subDistrict, + parent_id: auth.partner_id + }; + + const address = await apiOdoo('POST', '/api/v1/partner/address', parameters); + if (address?.id) { + toast.success('Berhasil menambahkan alamat'); + router.back(); + } + }; + + return ( + <WithAuth> + <Layout> + <AppBar title="Tambah Alamat" /> + + <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 => <Select {...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 => <Select {...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 => ( + <Select + {...props} + options={districts} + disabled={!watchCity} + /> + )} + /> + </div> + + <div> + <label className="form-label mb-2">Kelurahan</label> + <Controller + name="subDistrict" + control={control} + render={props => ( + <Select + {...props} + options={subDistricts} + disabled={!watchDistrict} + /> + )} + /> + </div> + + <button + type="submit" + className="btn-yellow mt-2 w-full" + > + Simpan + </button> + </form> + </Layout> + </WithAuth> + ) +}
\ No newline at end of file diff --git a/src2/pages/my/address/index.js b/src2/pages/my/address/index.js new file mode 100644 index 00000000..5cad4410 --- /dev/null +++ b/src2/pages/my/address/index.js @@ -0,0 +1,84 @@ +import { useEffect, useState } from "react"; +import { useRouter } from "next/router"; + +import AppBar from "@/components/layouts/AppBar"; +import Layout from "@/components/layouts/Layout"; +import Link from "@/components/elements/Link"; +import WithAuth from "@/components/auth/WithAuth"; + +import apiOdoo from "@/core/utils/apiOdoo"; +import { useAuth } from "@/core/utils/auth"; +import { createOrUpdateItemAddress, getItemAddress } from "@/core/utils/address"; +import { toast } from "react-hot-toast"; + +export default function Address() { + const router = useRouter(); + const { select } = router.query; + const [ auth ] = useAuth(); + const [ addresses, setAddresses ] = useState(null); + const [ selectedAdress, setSelectedAdress ] = useState(null); + + useEffect(() => { + const getAddress = async () => { + if (auth) { + const dataAddress = await apiOdoo('GET', `/api/v1/user/${auth.id}/address`); + setAddresses(dataAddress); + } + }; + getAddress(); + }, [auth]); + + useEffect(() => { + if (select) { + setSelectedAdress(getItemAddress(select)); + } + }, [select]); + + const changeSelectedAddress = (id) => { + if (select) { + createOrUpdateItemAddress(select, id); + router.back(); + } + }; + + return ( + <WithAuth> + <Layout> + <AppBar title="Daftar Alamat" /> + + <div className="text-right mt-4 px-4"> + <Link href="/my/address/create">Tambah Alamat</Link> + </div> + + <div className="grid gap-y-4 p-4"> + { auth && addresses && addresses.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 ? "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?.partner_id == 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 ${selectedAdress && selectedAdress == address.id ? "text-gray_r-12" : "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> + ); + }) } + </div> + </Layout> + </WithAuth> + ) +}
\ No newline at end of file diff --git a/src2/pages/my/invoice/[id].js b/src2/pages/my/invoice/[id].js new file mode 100644 index 00000000..820c9af8 --- /dev/null +++ b/src2/pages/my/invoice/[id].js @@ -0,0 +1,149 @@ +import AppBar from "@/components/layouts/AppBar"; +import Layout from "@/components/layouts/Layout"; +import LineDivider from "@/components/elements/LineDivider"; +import WithAuth from "@/components/auth/WithAuth"; +import { useEffect, useState } from "react"; +import apiOdoo from "@/core/utils/apiOdoo"; +import { useRouter } from "next/router"; +import { useAuth } from "@/core/utils/auth"; +import VariantCard from "@/components/variants/VariantCard"; +import currencyFormat from "@/core/utils/currencyFormat"; +import Disclosure from "@/components/elements/Disclosure"; +import DescriptionRow from "@/components/elements/DescriptionRow"; +import { SkeletonList } from "@/components/elements/Skeleton"; +import VariantGroupCard from "@/components/variants/VariantGroupCard"; + +export default function DetailInvoice() { + const router = useRouter(); + const { id } = router.query; + const [ auth ] = useAuth(); + const [ invoice, setInvoice ] = useState(null); + + useEffect(() => { + if (auth && id) { + const loadInvoice = async () => { + const dataInvoice = await apiOdoo('GET', `/api/v1/partner/${auth?.partner_id}/invoice/${id}`); + setInvoice(dataInvoice); + } + loadInvoice(); + } + }, [ auth, id ]); + + const Customer = () => { + const customer = invoice?.customer; + const fullAddress = []; + if (customer?.street) fullAddress.push(customer.street); + if (customer?.sub_district?.name) fullAddress.push(customer.sub_district.name); + if (customer?.district?.name) fullAddress.push(customer.district.name); + if (customer?.city?.name) fullAddress.push(customer.city.name); + + return ( + <div className="p-4 pt-0 flex flex-col gap-y-4"> + <DescriptionRow label="Nama">{ invoice?.customer?.name }</DescriptionRow> + <DescriptionRow label="Email">{ invoice?.customer?.email || '-' }</DescriptionRow> + <DescriptionRow label="No Telepon">{ invoice?.customer?.mobile || '-' }</DescriptionRow> + <DescriptionRow label="Alamat">{ fullAddress.join(', ') }</DescriptionRow> + </div> + ); + }; + + const downloadTaxInvoice = () => { + window.open(`${process.env.ODOO_HOST}/api/v1/download/tax-invoice/${invoice.id}/${invoice.token}`, 'Download') + } + + const downloadInvoice = () => { + window.open(`${process.env.ODOO_HOST}/api/v1/download/invoice/${invoice.id}/${invoice.token}`, 'Download') + } + + return ( + <WithAuth> + <Layout className="pb-4"> + <AppBar title="Detail Invoice" /> + + { invoice ? ( + <> + <div className="p-4 flex flex-col gap-y-4"> + <DescriptionRow label="No Invoice"> + { invoice?.name } + </DescriptionRow> + <DescriptionRow label="Status Transaksi"> + { invoice?.amount_residual > 0 ? ( + <span className="badge-solid-red">Belum Lunas</span> + ) : ( + <span className="badge-solid-green">Lunas</span> + ) } + </DescriptionRow> + <DescriptionRow label="Purchase Order"> + { invoice?.purchase_order_name || '-' } + </DescriptionRow> + <DescriptionRow label="Ketentuan Pembayaran"> + { invoice?.payment_term } + </DescriptionRow> + { invoice?.amount_residual > 0 && invoice.invoice_date != invoice.invoice_date_due && ( + <DescriptionRow label="Tanggal Jatuh Tempo"> + { invoice?.invoice_date_due } + </DescriptionRow> + ) } + <DescriptionRow label="Nama Sales"> + { invoice?.sales } + </DescriptionRow> + <DescriptionRow label="Tanggal Invoice"> + { invoice?.invoice_date } + </DescriptionRow> + <div className="flex items-center"> + <p className="text-gray_r-11 leading-none">Faktur Pembelian</p> + <button + type="button" + className="btn-light py-1.5 px-3 ml-auto" + onClick={downloadInvoice} + > + 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} + disabled={!invoice.efaktur} + > + Download + </button> + </div> + </div> + + <LineDivider /> + + <Disclosure + label="Detail Penagihan" + /> + + <Customer /> + + <LineDivider /> + + <Disclosure + label="Detail Produk" + /> + + <div className="mt-2 p-4 pt-0 flex flex-col gap-y-3"> + <VariantGroupCard + variants={invoice?.products} + buyMore + /> + <div className="flex justify-between mt-3 font-medium"> + <p className="text-gray_r-11">Total Belanja</p> + <p>{ currencyFormat(invoice?.amount_total || 0) }</p> + </div> + </div> + </> + ) : ( + <div className="p-4 py-6"> + <SkeletonList number={12} /> + </div> + ) } + </Layout> + </WithAuth> + ); +}
\ No newline at end of file diff --git a/src2/pages/my/invoices.js b/src2/pages/my/invoices.js new file mode 100644 index 00000000..9b2e77dc --- /dev/null +++ b/src2/pages/my/invoices.js @@ -0,0 +1,180 @@ +import WithAuth from "@/components/auth/WithAuth" +import Alert from "@/components/elements/Alert" +import Link from "@/components/elements/Link" +import Pagination from "@/components/elements/Pagination" +import AppBar from "@/components/layouts/AppBar" +import Layout from "@/components/layouts/Layout" +import apiOdoo from "@/core/utils/apiOdoo" +import { useAuth } from "@/core/utils/auth" +import currencyFormat from "@/core/utils/currencyFormat" +import useBottomPopup from "@/lib/elements/hooks/useBottomPopup" +import { CheckIcon, ClockIcon, EllipsisVerticalIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline" +import { useRouter } from "next/router" +import { useEffect, useRef, useState } from "react" + +export default function Invoices() { + const [ auth ] = useAuth() + const router = useRouter() + const { + q, + page = 1 + } = router.query + + const [ invoices, setInvoices ] = useState([]) + + const [ pageCount, setPageCount ] = useState(0) + const [ isLoading, setIsLoading ] = useState(true) + + const searchQueryRef = useRef() + + useEffect(() => { + const loadInvoices = async () => { + if (auth) { + const limit = 10 + let offset = (page - 1) * 10 + let queryParams = [`limit=${limit}`, `offset=${offset}`] + if (q) queryParams.push(`name=${q}`) + queryParams = queryParams.join('&') + queryParams = queryParams ? '?' + queryParams : '' + + const dataInvoices = await apiOdoo('GET', `/api/v1/partner/${auth.partner_id}/invoice${queryParams}`) + setInvoices(dataInvoices) + setPageCount(Math.ceil(dataInvoices.sale_order_total / limit)) + setIsLoading(false) + } + } + loadInvoices() + }, [ auth, q, page ]) + + const actionSearch = (e) => { + e.preventDefault() + let queryParams = [] + if (searchQueryRef.current.value) queryParams.push(`q=${searchQueryRef.current.value}`) + queryParams = queryParams.join('&') + queryParams = queryParams ? `?${queryParams}` : '' + router.push(`/my/invoices${queryParams}`) + } + + const downloadInvoice = (data) => { + const url = `${process.env.ODOO_HOST}/api/v1/download/invoice/${data.id}/${data.token}` + window.open(url, 'download') + closePopup() + } + + const downloadTaxInvoice = (data) => { + const url = `${process.env.ODOO_HOST}/api/v1/download/tax-invoice/${data.id}/${data.token}` + window.open(url, 'download') + closePopup() + } + + const childrenPopup = (data) => ( + <div className="flex flex-col gap-y-6"> + <button + className="text-left disabled:opacity-60" + onClick={() => downloadInvoice(data)} + > + Download Faktur Pembelian + </button> + <button + className="text-left disabled:opacity-60" + disabled={!data?.efaktur} + onClick={() => downloadTaxInvoice(data)} + > + Download Faktur Pajak + </button> + </div> + ) + + const { + closePopup, + openPopup, + BottomPopup + } = useBottomPopup({ + title: 'Lainnya', + children: childrenPopup + }) + + return ( + <WithAuth> + <Layout> + <AppBar title="Invoice" /> + + <form onSubmit={actionSearch} className="p-4 pb-0 flex gap-x-4"> + <input + type="text" + className="form-input" + placeholder="Cari Transaksi" + ref={searchQueryRef} + defaultValue={q} + /> + <button type="submit" className="border border-gray_r-7 rounded px-3"> + <MagnifyingGlassIcon className="w-5"/> + </button> + </form> + + <div className="p-4 flex flex-col gap-y-5"> + { invoices?.invoice_total === 0 && !isLoading && ( + <Alert type="info" className="text-center"> + Invoice tidak ditemukan + </Alert> + ) } + { invoices?.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.amount_residual > 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={() => openPopup(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.invoice_date } + </p> + <p className="text-right"> + { invoice.payment_term } + </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.purchase_order_name || '-' }</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.amount_total) }</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> + )) } + </div> + + <div className="pb-6 pt-2"> + <Pagination currentPage={page} pageCount={pageCount} url={`/my/invoices${q ? `?q=${q}` : ''}`} /> + </div> + { BottomPopup } + </Layout> + </WithAuth> + ) +}
\ No newline at end of file diff --git a/src2/pages/my/menu.js b/src2/pages/my/menu.js new file mode 100644 index 00000000..ae6c2af8 --- /dev/null +++ b/src2/pages/my/menu.js @@ -0,0 +1,82 @@ + +import AppBar from "@/components/layouts/AppBar"; +import Layout from "@/components/layouts/Layout"; +import Link from "@/components/elements/Link"; +import { useAuth } from "@/core/utils/auth"; +import { + ArrowRightOnRectangleIcon, + ChatBubbleLeftRightIcon, + ChevronRightIcon, + MapIcon, + PaperClipIcon, + PencilSquareIcon, + QuestionMarkCircleIcon, + ReceiptPercentIcon, + UserIcon, + HeartIcon +} from "@heroicons/react/24/outline"; +import WithAuth from "@/components/auth/WithAuth"; + +const Menu = ({ icon, name, url }) => { + return ( + <Link href={url} className="text-gray_r-11 font-normal flex gap-x-2 items-center py-4 border-b border-gray_r-6"> + <span className="flex gap-x-2"> + { icon } + { name } + </span> + <ChevronRightIcon className="w-5 ml-auto"/> + </Link> + ); +}; + +export default function MyMenu() { + const [auth] = useAuth(); + + return ( + <WithAuth> + <Layout> + <AppBar title="Menu Utama" /> + + <div className="p-4 flex gap-x-2 items-center"> + <div className="flex-1 flex gap-x-3 items-center"> + <div className="p-2 bg-gray_r-4 rounded-full h-fit"> + <UserIcon className="w-6" /> + </div> + <div> + <h2>{ auth?.name }</h2> + { auth?.company ? ( + <div className="badge-red font-normal text-xs">Akun Bisnis</div> + ) : ( + <div className="badge-gray font-normal text-xs">Akun Individu</div> + ) } + </div> + </div> + <Link href="/my/profile"> + <PencilSquareIcon className="w-6 text-yellow_r-12"/> + </Link> + </div> + + <div className="px-4 mt-4"> + <p className="font-medium mb-2">Aktivitas Pembelian</p> + <div className="flex flex-col mb-6"> + <Menu icon={<ReceiptPercentIcon className="w-5" />} name="Daftar Transaksi" url="/my/transactions" /> + <Menu icon={<PaperClipIcon className="w-5" />} name="Invoice & Faktur Pajak" url="/my/invoices" /> + <Menu icon={<HeartIcon className="w-5" />} name="Wishlist" url="/my/wishlist" /> + </div> + + <p className="font-medium mb-2">Pusat Bantuan</p> + <div className="flex flex-col mb-6"> + <Menu icon={<ChatBubbleLeftRightIcon className="w-5"/>} name="Layanan Pelanggan" url="/" /> + <Menu icon={<QuestionMarkCircleIcon className="w-5"/>} name="F.A.Q" url="/faqs" /> + </div> + + <p className="font-medium mb-2">Pengaturan Akun</p> + <div className="flex flex-col mb-6"> + <Menu icon={<MapIcon className="w-5" />} name="Daftar Alamat" url="/my/address" /> + <Menu icon={<ArrowRightOnRectangleIcon className="w-5" />} name="Keluar Akun" url="/logout" /> + </div> + </div> + </Layout> + </WithAuth> + ); +}
\ No newline at end of file diff --git a/src2/pages/my/profile.js b/src2/pages/my/profile.js new file mode 100644 index 00000000..97891259 --- /dev/null +++ b/src2/pages/my/profile.js @@ -0,0 +1,134 @@ +import { useState } from "react"; +import { toast } from "react-hot-toast"; +import AppBar from "@/components/layouts/AppBar"; +import Layout from "@/components/layouts/Layout"; +import WithAuth from "@/components/auth/WithAuth"; +import apiOdoo from "@/core/utils/apiOdoo"; +import { + useAuth, + setAuth as setAuthCookie, + getAuth +} from "@/core/utils/auth"; + +export default function MyProfile() { + const [auth, setAuth] = useAuth(); + const [editMode, setEditMode] = useState(false); + const [password, setPassword] = useState(''); + + const update = async (e) => { + e.preventDefault(); + let dataToUpdate = { + name: auth.name, + phone: auth.phone, + mobile: auth.mobile + }; + if (password) dataToUpdate.password = password; + let update = await apiOdoo('PUT', `/api/v1/user/${auth.id}`, dataToUpdate); + setAuthCookie(update.user); + cancelEdit(); + toast.success('Berhasil mengubah profil', { duration: 1500 }); + }; + + const handleInput = (e) => { + let authToUpdate = auth; + authToUpdate[e.target.name] = e.target.value; + setAuth({ ...authToUpdate }); + }; + + const cancelEdit = () => { + setEditMode(false); + setAuth(getAuth()); + setPassword(''); + } + + return ( + <WithAuth> + <Layout> + <AppBar title="Akun Saya" /> + + <form onSubmit={update} className="w-full px-4"> + { auth && ( + <> + <label className="form-label mt-4 mb-2">Email</label> + <input + type="text" + className="form-input" + placeholder="johndoe@gmail.com" + name="email" + value={auth.email} + onChange={handleInput} + disabled={true} + /> + + <label className="form-label mt-4 mb-2">Nama Lengkap</label> + <input + type="text" + className="form-input" + placeholder="John Doe" + name="name" + value={auth.name} + onChange={handleInput} + disabled={!editMode} + /> + + <label className="form-label mt-4 mb-2">No Telepon</label> + <input + type="tel" + className="form-input" + placeholder="08xxxxxxxx" + name="phone" + value={auth.phone} + onChange={handleInput} + disabled={!editMode} + /> + + <label className="form-label mt-4 mb-2">No Handphone</label> + <input + type="tel" + className="form-input" + placeholder="08xxxxxxxx" + name="mobile" + value={auth.mobile} + onChange={handleInput} + disabled={!editMode} + /> + + <label className="form-label mt-4 mb-2">Kata Sandi</label> + <input + type="password" + className="form-input" + placeholder="••••••••" + value={password} + onChange={(e) => setPassword(e.target.value)} + disabled={!editMode} + /> + </> + ) } + + { editMode && ( + <div className="flex gap-x-3 mt-6"> + <button + type="button" + className="btn-light flex-1 float-right" + onClick={cancelEdit} + > + Batal + </button> + <button type="submit" className="btn-yellow flex-1 float-right">Simpan</button> + </div> + ) } + + { !editMode && ( + <button + type="button" + className="btn-light float-right mt-6 w-full" + onClick={() => setEditMode(true)} + > + Ubah Profil + </button> + ) } + </form> + </Layout> + </WithAuth> + ); +}
\ No newline at end of file diff --git a/src2/pages/my/transaction/[id].js b/src2/pages/my/transaction/[id].js new file mode 100644 index 00000000..fb806aa4 --- /dev/null +++ b/src2/pages/my/transaction/[id].js @@ -0,0 +1,265 @@ +import AppBar from "@/components/layouts/AppBar"; +import Layout from "@/components/layouts/Layout"; +import LineDivider from "@/components/elements/LineDivider"; +import WithAuth from "@/components/auth/WithAuth"; +import { useCallback, useEffect, useRef, useState } from "react"; +import apiOdoo from "@/core/utils/apiOdoo"; +import { useRouter } from "next/router"; +import { useAuth } from "@/core/utils/auth"; +import currencyFormat from "@/core/utils/currencyFormat"; +import DescriptionRow from "@/components/elements/DescriptionRow"; +import { TransactionDetailAddress } from "@/components/transactions/TransactionDetail"; +import { SkeletonList } from "@/components/elements/Skeleton"; +import Link from "@/components/elements/Link"; +import { ChevronRightIcon } from "@heroicons/react/24/outline"; +import Alert from "@/components/elements/Alert"; +import TransactionStatusBadge from "@/components/transactions/TransactionStatusBadge"; +import useConfirmAlert from "@/lib/elements/hooks/useConfirmAlert"; +import { toast } from "react-hot-toast"; +import useBottomPopup from "@/lib/elements/hooks/useBottomPopup"; +import getFileBase64 from "@/core/utils/getFileBase64"; +import VariantGroupCard from "@/components/variants/VariantGroupCard"; + +export default function DetailTransaction() { + const router = useRouter(); + const { id } = router.query; + const [ auth ] = useAuth(); + const [ transaction, setTransaction ] = useState(null); + + const loadTransaction = useCallback(async () => { + if (auth && id) { + const dataTransaction = await apiOdoo('GET', `/api/v1/partner/${auth?.partner_id}/sale_order/${id}`); + setTransaction(dataTransaction); + } + }, [ auth, id ]); + + useEffect(() => { + loadTransaction(); + }, [ loadTransaction ]); + + const submitCancelTransaction = async (data) => { + const isCancelled = await apiOdoo('POST', `/api/v1/partner/${auth.partner_id}/sale_order/${data.id}/cancel`); + if (isCancelled) { + toast.success('Berhasil batalkan transaksi'); + loadTransaction(); + } + } + + const { + openConfirmAlert, + ConfirmAlert + } = useConfirmAlert({ + title: 'Batalkan Transaksi', + caption: 'Apakah anda yakin untuk membatalkan transaksi?', + closeText: 'Tidak', + submitText: 'Iya, batalkan', + onSubmit: submitCancelTransaction + }); + + const UploadPurchaseOrder = () => { + const nameRef = useRef(''); + const fileRef = useRef(''); + + const submitUploadPurchaseOrder = async (e) => { + e.preventDefault(); + const file = fileRef.current.files[0]; + const name = nameRef.current.value; + if (file.size > 5000000) { + toast.error('Maksimal ukuran file adalah 5MB', { + position: 'bottom-center' + }); + return; + } + const parameter = { + name, + file: await getFileBase64(file) + }; + const isUploaded = await apiOdoo('POST', `/api/v1/partner/${auth.partner_id}/sale_order/${transaction.id}/upload_po`, parameter); + if (isUploaded) { + toast.success('Berhasil upload PO'); + loadTransaction(); + closePopup(); + } + }; + + return ( + <form className="flex flex-col gap-y-4" onSubmit={submitUploadPurchaseOrder}> + <div> + <label className="form-label mb-2">Nama PO</label> + <input className="form-input" type="text" ref={nameRef} required /> + </div> + <div> + <label className="form-label mb-2">Dokumen PO</label> + <input className="form-input" type="file" ref={fileRef} required /> + </div> + <button type="submit" className="btn-yellow w-full mt-2">Upload</button> + </form> + ); + } + + const { + closePopup, + BottomPopup, + openPopup + } = useBottomPopup({ + title: 'Upload PO', + children: UploadPurchaseOrder + }); + + const downloadPurchaseOrder = () => { + const url = `${process.env.ODOO_HOST}/api/v1/partner/${auth.partner_id}/sale_order/${transaction.id}/download_po/${transaction.token}`; + window.open(url, 'download') + }; + + const downloadQuotation = () => { + const url = `${process.env.ODOO_HOST}/api/v1/partner/${auth.partner_id}/sale_order/${transaction.id}/download/${transaction.token}`; + window.open(url, 'download') + }; + + const checkout = async () => { + if (!transaction.purchase_order_file) { + toast.error('Mohon upload dokumen PO anda sebelum melanjutkan pesanan') + return + } + await apiOdoo('POST', `/api/v1/partner/${auth?.partner_id}/sale_order/${id}/checkout`) + toast.success('Berhasil melanjutkan pesanan') + loadTransaction() + } + + return ( + <WithAuth> + <Layout className="pb-4"> + <AppBar title="Detail Transaksi" /> + + { transaction ? ( + <> + <div className="p-4 flex flex-col gap-y-4"> + <DescriptionRow label="Status Transaksi"> + <div className="flex justify-end"> + <TransactionStatusBadge status={transaction?.status} /> + </div> + </DescriptionRow> + <DescriptionRow label="No Transaksi"> + { transaction?.name } + </DescriptionRow> + <DescriptionRow label="Ketentuan Pembayaran"> + { transaction?.payment_term } + </DescriptionRow> + <DescriptionRow label="Nama Sales"> + { transaction?.sales } + </DescriptionRow> + <DescriptionRow label="Waktu Transaksi"> + { transaction?.date_order } + </DescriptionRow> + </div> + + <LineDivider /> + + <div className="p-4 flex flex-col gap-y-4"> + <DescriptionRow label="Purchase Order"> + { transaction?.purchase_order_name || '-' } + </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?.purchase_order_file ? downloadPurchaseOrder : openPopup} + > + { transaction?.purchase_order_file ? 'Download' : 'Upload' } + </button> + </div> + </div> + + <LineDivider /> + + <p className="h2 p-4">Detail Produk</p> + + <div className="mt-2 p-4 pt-0 flex flex-col gap-y-3"> + <VariantGroupCard + variants={transaction?.products} + buyMore + /> + <div className="flex justify-between mt-3 font-medium"> + <p>Total Belanja</p> + <p>{ currencyFormat(transaction?.amount_total || 0) }</p> + </div> + </div> + + <LineDivider /> + + <TransactionDetailAddress transaction={transaction} /> + + <LineDivider /> + + <div className="p-4"> + <p className="h2">Invoice</p> + <div className="flex flex-col gap-y-3 mt-4"> + { transaction?.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.amount_residual > 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.amount_total) } + </p> + </div> + </div> + <ChevronRightIcon className="w-5 stroke-2" /> + </div> + </Link> + )) } + { transaction?.invoices?.length === 0 && ( + <Alert type='info' className='text-center'> + Belum ada Invoice + </Alert> + ) } + </div> + </div> + + <LineDivider /> + + <div className="px-4"> + { transaction?.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?.status != 'draft'} + onClick={downloadQuotation} + > + Download Quotation + </button> + { transaction?.status != 'draft' && ( + <button + className="btn-light w-full mt-4" + disabled={transaction?.status != 'waiting'} + onClick={() => openConfirmAlert(transaction)} + > + Batalkan Transaksi + </button> + ) } + </div> + </> + ) : ( + <div className="p-4 py-6"> + <SkeletonList number={12} /> + </div> + ) } + { ConfirmAlert } + { BottomPopup } + </Layout> + </WithAuth> + ); +}
\ No newline at end of file diff --git a/src2/pages/my/transactions.js b/src2/pages/my/transactions.js new file mode 100644 index 00000000..8be43af7 --- /dev/null +++ b/src2/pages/my/transactions.js @@ -0,0 +1,198 @@ +import { useRouter } from "next/router"; +import AppBar from "@/components/layouts/AppBar"; +import Layout from "@/components/layouts/Layout"; +import WithAuth from "@/components/auth/WithAuth"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { useAuth } from "@/core/utils/auth"; +import apiOdoo from "@/core/utils/apiOdoo"; +import currencyFormat from "@/core/utils/currencyFormat"; +import { EllipsisVerticalIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline"; +import Link from "@/components/elements/Link"; +import Pagination from "@/components/elements/Pagination"; +import Alert from "@/components/elements/Alert"; +import TransactionStatusBadge from "@/components/transactions/TransactionStatusBadge"; +import { toast } from "react-hot-toast"; +import useConfirmAlert from "@/lib/elements/hooks/useConfirmAlert"; +import useBottomPopup from "@/lib/elements/hooks/useBottomPopup"; + +export default function Transactions() { + const [ auth ] = useAuth(); + const router = useRouter(); + const { + q, + page = 1 + } = router.query; + + const [ transactions, setTransactions ] = useState([]); + + const [ pageCount, setPageCount ] = useState(0); + const [ isLoading, setIsLoading ] = useState(true); + + const searchQueryRef = useRef(); + const loadTransactions = useCallback(async () => { + if (auth) { + const limit = 10; + let offset = (page - 1) * 10; + let queryParams = [`limit=${limit}`, `offset=${offset}`]; + if (q) queryParams.push(`name=${q}`); + queryParams = queryParams.join('&'); + queryParams = queryParams ? '?' + queryParams : ''; + + const dataTransactions = await apiOdoo('GET', `/api/v1/partner/${auth.partner_id}/sale_order${queryParams}`); + setTransactions(dataTransactions); + setPageCount(Math.ceil(dataTransactions?.sale_order_total / limit)); + setIsLoading(false); + }; + }, [ auth, q, page ]); + + useEffect(() => { + loadTransactions(); + }, [ loadTransactions ]); + + const actionSearch = (e) => { + e.preventDefault(); + let queryParams = []; + if (searchQueryRef.current.value) queryParams.push(`q=${searchQueryRef.current.value}`); + queryParams = queryParams.join('&'); + queryParams = queryParams ? `?${queryParams}` : ''; + router.push(`/my/transactions${queryParams}`); + }; + + const downloadPurchaseOrder = (data) => { + const url = `${process.env.ODOO_HOST}/api/v1/partner/${auth.partner_id}/sale_order/${data.id}/download_po/${data.token}`; + window.open(url, 'download'); + closePopup(); + }; + + const downloadQuotation = (data) => { + const url = `${process.env.ODOO_HOST}/api/v1/partner/${auth.partner_id}/sale_order/${data.id}/download/${data.token}`; + window.open(url, 'download'); + closePopup(); + }; + + const childrenPopup = (data) => ( + <div className="flex flex-col gap-y-6"> + <button + className="text-left disabled:opacity-60" + disabled={!data?.purchase_order_file} + onClick={() => downloadPurchaseOrder(data)} + > + Download PO + </button> + <button + className="text-left disabled:opacity-60" + disabled={data?.status != 'draft'} + onClick={() => downloadQuotation(data)} + > + Download Quotation + </button> + <button + className="text-left disabled:opacity-60" + disabled={ data?.status != 'waiting' } + onClick={() => {openConfirmAlert(data); closePopup()}} + > + Batalkan Transaksi + </button> + </div> + ); + + const { + closePopup, + openPopup, + BottomPopup + } = useBottomPopup({ + title: 'Lainnya', + children: childrenPopup + }); + + const submitCancelTransaction = async (data) => { + const isCancelled = await apiOdoo('POST', `/api/v1/partner/${auth.partner_id}/sale_order/${data.id}/cancel`); + if (isCancelled) { + toast.success('Berhasil batalkan transaksi'); + loadTransactions(); + } + } + + const { + openConfirmAlert, + ConfirmAlert + } = useConfirmAlert({ + title: 'Batalkan Transaksi', + caption: 'Apakah anda yakin untuk membatalkan transaksi?', + closeText: 'Tidak', + submitText: 'Ya, Batalkan', + onSubmit: submitCancelTransaction + }); + + return ( + <WithAuth> + <Layout> + <AppBar title="Transaksi" /> + + <form onSubmit={actionSearch} className="p-4 pb-0 flex gap-x-4"> + <input + type="text" + className="form-input" + placeholder="Cari Transaksi" + ref={searchQueryRef} + defaultValue={q} + /> + <button type="submit" className="border border-gray_r-7 rounded px-3"> + <MagnifyingGlassIcon className="w-5"/> + </button> + </form> + + <div className="p-4 flex flex-col gap-y-5"> + { transactions?.sale_order_total === 0 && !isLoading && ( + <Alert type="info" className="text-center"> + Transaksi tidak ditemukan + </Alert> + ) } + { transactions?.sale_orders?.map((transaction, 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/${transaction.id}`}> + <span className="text-caption-2 text-gray_r-11">No. Transaksi</span> + <h2 className="text-red_r-11 mt-1">{ transaction.name }</h2> + </Link> + <div className="flex gap-x-1 justify-end"> + <TransactionStatusBadge status={transaction.status} /> + <EllipsisVerticalIcon className="w-5 h-5" onClick={() => openPopup(transaction)} /> + </div> + </div> + <Link href={`/my/transaction/${transaction.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">{ transaction.purchase_order_name || '-' }</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">{ transaction.invoice_count } 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">{ transaction.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(transaction.amount_total) }</p> + </div> + </div> + </Link> + </div> + )) } + </div> + + <div className="pb-6 pt-2"> + <Pagination currentPage={page} pageCount={pageCount} url={`/my/transactions${q ? `?q=${q}` : ''}`} /> + </div> + + { ConfirmAlert } + { BottomPopup } + </Layout> + </WithAuth> + ); +};
\ No newline at end of file diff --git a/src2/pages/my/wishlist.js b/src2/pages/my/wishlist.js new file mode 100644 index 00000000..3d479802 --- /dev/null +++ b/src2/pages/my/wishlist.js @@ -0,0 +1,60 @@ +import WithAuth from "@/components/auth/WithAuth"; +import Alert from "@/components/elements/Alert"; +import Pagination from "@/components/elements/Pagination"; +import Spinner from "@/components/elements/Spinner"; +import AppBar from "@/components/layouts/AppBar"; +import Layout from "@/components/layouts/Layout"; +import ProductCard from "@/components/products/ProductCard"; +import apiOdoo from "@/core/utils/apiOdoo"; +import { useAuth } from "@/core/utils/auth"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; + +export default function Wishlist() { + const [ auth ] = useAuth(); + const router = useRouter(); + const { page = 1 } = router.query; + const [ wishlists, setWishlists ] = useState(null); + const [ pageCount, setPageCount ] = useState(0); + + useEffect(() => { + const loadWishlist = async () => { + const limit = 10; + const offset = (page - 1) * limit; + if (auth) { + const dataWishlist = await apiOdoo('GET', `/api/v1/user/${auth.id}/wishlist?limit=${limit}&offset=${offset}`); + setWishlists(dataWishlist); + setPageCount(Math.ceil(dataWishlist.product_total / limit)); + } + } + loadWishlist(); + }, [ auth, page ]); + + return ( + <WithAuth> + <Layout> + <AppBar title='Wishlist' /> + + <div className="px-4 py-6"> + { !wishlists && ( + <Spinner className="w-6 h-6 text-gray-600 fill-gray-900 mx-auto" /> + ) } + { wishlists?.products?.length == 0 && ( + <Alert type='info' className='text-center'> + Wishlist anda masih kosong + </Alert> + ) } + <div className="grid grid-cols-2 gap-3"> + {wishlists?.products.map((product) => ( + <ProductCard key={product.id} data={product} /> + ))} + </div> + + <div className="mt-6"> + <Pagination currentPage={page} pageCount={pageCount} url={`/my/wishlist`} /> + </div> + </div> + </Layout> + </WithAuth> + ) +}
\ No newline at end of file |
