update invoice
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
VITE_TOKEN_NAME = 'admin_token'
|
||||
VITE_REFRESH_TOKEN_NAME = 'admin_refresh_token'
|
||||
VITE_BASE_URL = 'https://api.danakcorp.com'
|
||||
# VITE_BASE_URL = 'http://192.168.1.113:4000'
|
||||
# VITE_BASE_URL = 'http://192.168.82.158:4000'
|
||||
+11
-5
@@ -484,16 +484,22 @@
|
||||
"to": "تا تاریخ",
|
||||
"recurring": "دوره ای",
|
||||
"select_type": "انتخاب نوع دوره",
|
||||
"1": "روزانه",
|
||||
"2": "هفتگی",
|
||||
"3": "ماهانه",
|
||||
"4": "چهارماهه",
|
||||
"1": "هفتگی",
|
||||
"2": "ماهانه",
|
||||
"3": "سهماهه",
|
||||
"4": "ششماهه",
|
||||
"5": "سالانه",
|
||||
"maxRecurringCycles": "حداکثر تعداد تکرار دوره",
|
||||
"WAIT_PAYMENT": "در انتظار پرداخت",
|
||||
"overdue": "سررسید شده",
|
||||
"OVERDUE": "سررسید شده",
|
||||
"lateFee": "مبلغ جریمه"
|
||||
"lateFee": "مبلغ جریمه",
|
||||
"recurringPeriod": "دوره تکرار",
|
||||
"WEEKLY": "هفتگی",
|
||||
"MONTHLY": "ماهانه",
|
||||
"QUARTERLY": "سهماهه",
|
||||
"SEMIANNUALLY": "ششماهه",
|
||||
"ANNUALLY": "سالانه"
|
||||
},
|
||||
"edit": "ویرایش",
|
||||
"transaction": {
|
||||
|
||||
@@ -15,7 +15,7 @@ import { useCreateInvoice } from './hooks/useReceiptData'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Pages } from '../../config/Pages'
|
||||
import { ErrorType } from '../../helpers/types'
|
||||
import { ReceiptTypeEnum } from './enum/ReceipEnum'
|
||||
import { RecurringPeriodEnum } from './enum/ReceipEnum'
|
||||
import SwitchComponent from '../../components/Switch'
|
||||
const CreateReceipt: FC = () => {
|
||||
|
||||
@@ -24,7 +24,7 @@ const CreateReceipt: FC = () => {
|
||||
const [items, setItems] = useState<ReceiptCreateItemsType[]>([])
|
||||
const [customer, setCustomer] = useState<CustomerItemType>()
|
||||
const [isRecurring, setIsRecurring] = useState<boolean>(false)
|
||||
const [type, setType] = useState<ReceiptTypeEnum>(ReceiptTypeEnum.DAILY)
|
||||
const [type, setType] = useState<RecurringPeriodEnum>(RecurringPeriodEnum.WEEKLY)
|
||||
const [maxRecurringCycles, setMaxRecurringCycles] = useState<number>(0)
|
||||
const getCustomers = useGetCustomers(1, true)
|
||||
const createInvoice = useCreateInvoice()
|
||||
@@ -204,13 +204,14 @@ const CreateReceipt: FC = () => {
|
||||
<div className='mt-4 rowTwoInput'>
|
||||
<Select
|
||||
label={t('select')}
|
||||
items={Object.values(ReceiptTypeEnum).map((item) => {
|
||||
return {
|
||||
label: t(`receip.${item}`),
|
||||
value: item
|
||||
}
|
||||
})}
|
||||
onChange={(e) => setType(e.target.value as ReceiptTypeEnum)}
|
||||
items={[
|
||||
{ label: t('receip.WEEKLY'), value: RecurringPeriodEnum.WEEKLY.toString() },
|
||||
{ label: t('receip.MONTHLY'), value: RecurringPeriodEnum.MONTHLY.toString() },
|
||||
{ label: t('receip.QUARTERLY'), value: RecurringPeriodEnum.QUARTERLY.toString() },
|
||||
{ label: t('receip.SEMIANNUALLY'), value: RecurringPeriodEnum.SEMIANNUALLY.toString() },
|
||||
{ label: t('receip.ANNUALLY'), value: RecurringPeriodEnum.ANNUALLY.toString() }
|
||||
]}
|
||||
onChange={(e) => setType(e.target.value as unknown as RecurringPeriodEnum)}
|
||||
/>
|
||||
|
||||
<Input
|
||||
|
||||
+504
-200
@@ -1,258 +1,562 @@
|
||||
import { FC } from 'react'
|
||||
import { ChangeEvent, FC, Fragment, useState, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Edit } from 'iconsax-react'
|
||||
import StatusCircle from '../../components/StatusCircle'
|
||||
import SamanImage from '../../assets/images/saman.png'
|
||||
import Button from '../../components/Button'
|
||||
import { Add, TickCircle, TickSquare, Trash } from 'iconsax-react'
|
||||
import Select from '../../components/Select'
|
||||
import Input from '../../components/Input'
|
||||
import { CreateReceiptType, ReceiptCreateItemsType } from './types/ReceiptTypes'
|
||||
import { useFormik } from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import { toast } from 'react-toastify'
|
||||
import { useGetCustomers } from '../customer/hooks/useCustomerData'
|
||||
import { CustomerItemType } from '../customer/types/CustomerTypes'
|
||||
import PageLoading from '../../components/PageLoading'
|
||||
import { useGetInvoiceById, useUpdateInvoice } from './hooks/useReceiptData'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { Pages } from '../../config/Pages'
|
||||
import { ErrorType } from '../../helpers/types'
|
||||
import { RecurringPeriodEnum } from './enum/ReceipEnum'
|
||||
import SwitchComponent from '../../components/Switch'
|
||||
|
||||
const ReceiptsDetail: FC = () => {
|
||||
// Add interface for invoice item from API
|
||||
interface InvoiceItemType {
|
||||
id: string;
|
||||
name: string;
|
||||
count: number;
|
||||
unitPrice: number;
|
||||
discount?: number;
|
||||
totalPrice: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
subscriptionPlan: null | string;
|
||||
}
|
||||
|
||||
const CreateReceipt: FC = () => {
|
||||
|
||||
const navigate = useNavigate()
|
||||
const { id } = useParams()
|
||||
const { t } = useTranslation('global')
|
||||
const { data: invoice, isPending: isLoading } = useGetInvoiceById(id as string)
|
||||
const updateInvoice = useUpdateInvoice()
|
||||
const [items, setItems] = useState<ReceiptCreateItemsType[]>([])
|
||||
const [customer, setCustomer] = useState<CustomerItemType>()
|
||||
const [isRecurring, setIsRecurring] = useState<boolean>(false)
|
||||
const [type, setType] = useState<RecurringPeriodEnum>(RecurringPeriodEnum.WEEKLY)
|
||||
const [maxRecurringCycles, setMaxRecurringCycles] = useState<number>(0)
|
||||
const getCustomers = useGetCustomers(1, true)
|
||||
|
||||
const formik = useFormik<ReceiptCreateItemsType>({
|
||||
initialValues: {
|
||||
name: '',
|
||||
count: '',
|
||||
unitPrice: '',
|
||||
discount: ''
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
name: Yup.string().required(t('errors.required')),
|
||||
count: Yup.string().required(t('errors.required')),
|
||||
unitPrice: Yup.string().required(t('errors.required')),
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
setItems((prev) => [...prev, {
|
||||
count: +values.count,
|
||||
discount: +values.discount,
|
||||
name: values.name,
|
||||
unitPrice: +values.unitPrice
|
||||
}])
|
||||
formik.resetForm()
|
||||
}
|
||||
})
|
||||
|
||||
const handleChangeCustomer = (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
const value = e.target.value
|
||||
const customer = getCustomers.data?.data?.customers?.find((item: CustomerItemType) => item.id === value)
|
||||
setCustomer(customer)
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (customer) {
|
||||
const params: CreateReceiptType = {
|
||||
userId: customer?.id,
|
||||
items: items,
|
||||
isRecurring: isRecurring,
|
||||
recurringPeriod: isRecurring ? +type : undefined,
|
||||
maxRecurringCycles: isRecurring ? maxRecurringCycles : undefined
|
||||
}
|
||||
|
||||
updateInvoice.mutate({ id: id as string, params }, {
|
||||
onSuccess: () => {
|
||||
toast.success(t('success'))
|
||||
navigate(Pages.receipts.index)
|
||||
},
|
||||
onError(error: ErrorType) {
|
||||
toast.error(error.response?.data?.error.message[0])
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleRemove = (index: number) => {
|
||||
setItems((prev) => prev.filter((_, i) => i !== index))
|
||||
}
|
||||
|
||||
// Prefill form when editing an invoice
|
||||
useEffect(() => {
|
||||
|
||||
// Check if we need to access invoice.data or directly invoice
|
||||
const invoiceData = invoice?.data?.invoice || invoice;
|
||||
|
||||
if (id && invoiceData) {
|
||||
console.log("Invoice data to use:", invoiceData);
|
||||
|
||||
// 1. If there's a user, find matching customer
|
||||
if (invoiceData.user && getCustomers.data?.data?.customers) {
|
||||
const foundCustomer = getCustomers.data.data.customers.find(
|
||||
(item: CustomerItemType) => item.id === invoiceData.user.id
|
||||
);
|
||||
setCustomer(foundCustomer);
|
||||
console.log("Found customer:", foundCustomer);
|
||||
setCustomer(foundCustomer);
|
||||
}
|
||||
|
||||
// 2. Set items from invoice - directly use the structure from API response
|
||||
if (invoiceData.items && invoiceData.items.length > 0) {
|
||||
console.log("Setting items:", invoiceData.items);
|
||||
// Map API items to the format expected by our component
|
||||
setItems(invoiceData.items.map((item: InvoiceItemType) => ({
|
||||
name: item.name,
|
||||
count: item.count,
|
||||
unitPrice: item.unitPrice,
|
||||
discount: item.discount || 0
|
||||
})));
|
||||
|
||||
// 3. Set formik values from first item
|
||||
|
||||
}
|
||||
|
||||
// 4. Set recurring settings
|
||||
setIsRecurring(Boolean(invoiceData.isRecurring));
|
||||
if (typeof invoiceData.recurringPeriod === 'number') {
|
||||
setType(invoiceData.recurringPeriod.toString());
|
||||
}
|
||||
setMaxRecurringCycles(invoiceData.maxRecurringCycles || 0);
|
||||
}
|
||||
}, [id, invoice, getCustomers.data]);
|
||||
|
||||
// Add this function to programmatically select the customer in the dropdown
|
||||
const selectCustomerInDropdown = () => {
|
||||
if (customer && invoice?.user?.id) {
|
||||
// Trigger the onChange manually with a synthetic event
|
||||
const event = {
|
||||
target: {
|
||||
value: invoice.user.id
|
||||
}
|
||||
} as ChangeEvent<HTMLSelectElement>;
|
||||
|
||||
handleChangeCustomer(event);
|
||||
}
|
||||
};
|
||||
|
||||
// Call this when customer or invoice changes
|
||||
useEffect(() => {
|
||||
if (customer && invoice?.user?.id) {
|
||||
selectCustomerInDropdown();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [customer, invoice]);
|
||||
|
||||
return (
|
||||
<div className='mt-4'>
|
||||
<div>
|
||||
{t('receip.detail_account')}
|
||||
۱۲۳۱۲
|
||||
</div>
|
||||
|
||||
<div className='flex xl:flex-row flex-col mt-8 gap-6'>
|
||||
<div className='flex-1'>
|
||||
<div className='w-full bg-white rounded-3xl p-8'>
|
||||
{
|
||||
getCustomers.isPending || isLoading ?
|
||||
<PageLoading />
|
||||
:
|
||||
<Fragment>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div>
|
||||
{t('receip.personal_information')}
|
||||
{t('receip.add_receip')}
|
||||
</div>
|
||||
<div className='flex gap-1 items-center text-xs text-[#0047FF]'>
|
||||
<Edit size={16} color='#0047FF' />
|
||||
<div>
|
||||
{t('edit')}
|
||||
<Button
|
||||
className='px-5'
|
||||
onClick={handleSubmit}
|
||||
isLoading={updateInvoice.isPending}
|
||||
>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<TickCircle size={20} color='white' />
|
||||
<div>
|
||||
{t('receip.submit_receip')}
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-8 flex xl:flex-row flex-col xl:gap-16 gap-4 xl:items-center text-xs'>
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.type_person')}
|
||||
</div>
|
||||
<div className='flex gap-6 xl:mt-8 mt-4'>
|
||||
<div className='flex-1 bg-white py-8 xl:px-10 px-4 rounded-3xl'>
|
||||
<div>
|
||||
{t('receip.company')}
|
||||
</div>
|
||||
{t('receip.customer_information')}
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.representativeـname')}
|
||||
</div>
|
||||
<div>
|
||||
مهرداد مظفری
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.company_name')}
|
||||
</div>
|
||||
<div>
|
||||
داناک
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.registration_number')}
|
||||
</div>
|
||||
<div>
|
||||
۱۲۳۴
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-8 border-t pt-7 flex xl:flex-row flex-col xl:gap-16 gap-4 xl:items-center text-xs'>
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.tel_company')}
|
||||
</div>
|
||||
<div>
|
||||
۰۰۱۲۳۴۵۶۷۸
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.national_code')}
|
||||
</div>
|
||||
<div>
|
||||
۰۰۱۲۳۴۵۶۷۸
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.postal_code')}
|
||||
</div>
|
||||
<div>
|
||||
۳۸۲۱۳۵۸۸۹۲
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className='mt-6 flex xl:flex-row flex-col xl:gap-16 gap-4 xl:items-center text-xs'>
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.state')}
|
||||
</div>
|
||||
<div>
|
||||
مرکزی
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.state')}
|
||||
</div>
|
||||
<div>
|
||||
مرکزی
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.city')}
|
||||
</div>
|
||||
<div>
|
||||
اراک
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-2'>
|
||||
<div className='text-description'>
|
||||
{t('receip.address')}
|
||||
</div>
|
||||
<div>
|
||||
اراک -خیابان شریعتی-خیابان جنت
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className='w-full bg-white rounded-3xl mt-8 p-8'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div>
|
||||
{t('receip.detail_receip')}
|
||||
</div>
|
||||
<div className='flex gap-2 text-xs items-center'>
|
||||
<StatusCircle
|
||||
color='#FF7B00'
|
||||
<div className='mt-8'>
|
||||
<div className='w-full xl:w-1/2'>
|
||||
<Select
|
||||
label={t('receip.customer')}
|
||||
placeholder={t('select')}
|
||||
value={(invoice?.data?.invoice || invoice)?.user?.id}
|
||||
items={getCustomers.data?.data?.customers?.map((item: CustomerItemType) => {
|
||||
return {
|
||||
label: item.firstName + ' ' + item.lastName,
|
||||
value: item.id
|
||||
}
|
||||
})}
|
||||
onChange={handleChangeCustomer}
|
||||
/>
|
||||
<div>
|
||||
{t('receip.not_paid')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-7 text-sm'>
|
||||
<div className='flex justify-between items-center text-description h-14'>
|
||||
<div>
|
||||
{t('receip.description')}
|
||||
{
|
||||
customer &&
|
||||
<div className='mt-10 bg-[#F6F7FB] p-8 rounded-3xl text-sm'>
|
||||
<div className='flex justify-between items-center border-b border-border border-dashed pb-7'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='text-description'>{t('receip.type_person')}</div>
|
||||
<div>{customer.legalUser ? 'حقوقی' : 'حقیقی'}</div>
|
||||
</div>
|
||||
<div>
|
||||
{t('receip.price')}
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='text-description'>{t('receip.representativeـname')}</div>
|
||||
<div>{customer.firstName + ' ' + customer.lastName}</div>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='text-description'>{t('receip.company_name')}</div>
|
||||
<div>{customer?.legalUser?.registrationName}</div>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='text-description'>{t('receip.registration_number')}</div>
|
||||
<div>{customer?.legalUser?.registrationCode}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex justify-between items-center text-xs border-t border-[#EAEDF5] h-14'>
|
||||
<div>
|
||||
اکانت یکساله با دسترسی
|
||||
<div className='flex justify-between items-center mt-7'>
|
||||
<div className='flex items-center gap-2 flex-1'>
|
||||
<div className='text-description'>{t('receip.tel_company')}</div>
|
||||
<div>{customer?.realUser?.phone || customer?.legalUser?.phone}</div>
|
||||
</div>
|
||||
<div>
|
||||
۱۰,۰۰۰,۰۰۰ ریال
|
||||
<div className='flex items-center gap-2 flex-1'>
|
||||
<div className='text-description'>{t('receip.national_code')}</div>
|
||||
<div>{customer.nationalCode}</div>
|
||||
</div>
|
||||
<div className='flex items-center gap-2 flex-1'>
|
||||
<div className='text-description'>{t('receip.postal_code')}</div>
|
||||
<div>{customer?.realUser?.address?.postalCode || customer?.legalUser?.address?.postalCode}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex justify-between items-center text-xs border-t border-[#EAEDF5] h-14'>
|
||||
<div>
|
||||
۱۰٪ ارزش افزوده
|
||||
</div>
|
||||
<div>
|
||||
۱۰,۰۰۰,۰۰۰ ریال
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex justify-between items-center text-xs border-t border-[#EAEDF5] h-14'>
|
||||
<div>
|
||||
جمع کل
|
||||
</div>
|
||||
<div>
|
||||
۱۰,۰۰۰,۰۰۰ ریال
|
||||
<div className='flex justify-between items-center mt-7'>
|
||||
<div className='flex items-center gap-2 flex-1'>
|
||||
<div className='text-description'>{t('receip.state')}</div>
|
||||
<div>{customer?.realUser?.address?.city?.province?.name || customer?.legalUser?.address?.city?.province?.name}</div>
|
||||
</div>
|
||||
<div className='flex items-center gap-2 flex-1'>
|
||||
<div className='text-description'>{t('receip.city')}</div>
|
||||
<div>{customer?.realUser?.address?.city?.name || customer?.legalUser?.address?.city?.name}</div>
|
||||
</div>
|
||||
<div className='flex items-center gap-2 flex-1'>
|
||||
<div className='text-description'>{t('receip.address')}</div>
|
||||
<div>{customer?.realUser?.address?.fullAddress || customer?.legalUser?.address?.fullAddress}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className='bg-white xl:w-sidebar text-sm h-fit rounded-3xl p-8'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div>
|
||||
{t('receip.receip_information')}
|
||||
</div>
|
||||
<div className='flex gap-2 text-xs items-center'>
|
||||
<StatusCircle
|
||||
color='#FF7B00'
|
||||
<div className='mt-10'>
|
||||
<SwitchComponent
|
||||
label={t('receip.recurring')}
|
||||
active={isRecurring}
|
||||
onChange={(value) => setIsRecurring(value)}
|
||||
/>
|
||||
<div>پرداخت نشده</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
isRecurring &&
|
||||
<div className='mt-10'>
|
||||
<div>
|
||||
{t('receip.select_type')}
|
||||
</div>
|
||||
|
||||
<div className='mt-4 rowTwoInput'>
|
||||
<Select
|
||||
label={t('select')}
|
||||
items={[
|
||||
{ label: t('receip.WEEKLY'), value: RecurringPeriodEnum.WEEKLY.toString() },
|
||||
{ label: t('receip.MONTHLY'), value: RecurringPeriodEnum.MONTHLY.toString() },
|
||||
{ label: t('receip.QUARTERLY'), value: RecurringPeriodEnum.QUARTERLY.toString() },
|
||||
{ label: t('receip.SEMIANNUALLY'), value: RecurringPeriodEnum.SEMIANNUALLY.toString() },
|
||||
{ label: t('receip.ANNUALLY'), value: RecurringPeriodEnum.ANNUALLY.toString() }
|
||||
]}
|
||||
value={type}
|
||||
onChange={(e) => setType(e.target.value as unknown as RecurringPeriodEnum)}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label={t('receip.maxRecurringCycles')}
|
||||
name='maxRecurringCycles'
|
||||
value={maxRecurringCycles}
|
||||
onChange={(e) => setMaxRecurringCycles(+e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className='mt-10'>
|
||||
<div>{t('receip.receipt_information')}</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-8 border-b border-dashed border-border pb-7 flex items-end justify-between text-xs text-description'>
|
||||
<div className='flex flex-col gap-2 items-center'>
|
||||
<div>
|
||||
{t('receip.number')}
|
||||
</div>
|
||||
<div className='size-10 bg-[#EBEDF5] rounded-xl flex justify-center items-center'>
|
||||
{items.length + 1}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-6 text-xs font-extralight'>
|
||||
<div className='flex justify-between h-12 border-b items-center'>
|
||||
<div className='text-description'>
|
||||
{t('receip.date_factor')}
|
||||
</div>
|
||||
<div className='flex flex-col gap-2 items-center'>
|
||||
<div>
|
||||
۱۴۰۲/۱۰/۰۴
|
||||
{t('receip.product_name')}
|
||||
</div>
|
||||
<Input
|
||||
className='text-center'
|
||||
name='name'
|
||||
value={formik.values.name}
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex justify-between h-12 border-b items-center'>
|
||||
<div className='text-description'>
|
||||
{t('receip.due_date')}
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col gap-2 items-center'>
|
||||
<div>
|
||||
۱۴۰۲/۱۰/۰۴
|
||||
{t('receip.count')}
|
||||
</div>
|
||||
<Input
|
||||
type='number'
|
||||
className='w-16 text-center'
|
||||
name='count'
|
||||
value={formik.values.count}
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex justify-between h-12 border-b items-center'>
|
||||
<div className='text-description'>
|
||||
{t('receip.factor_number')}
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col gap-2 items-center'>
|
||||
<div>
|
||||
۱۲۳۱
|
||||
{t('receip.unit_amount')}
|
||||
</div>
|
||||
<Input
|
||||
className='text-center'
|
||||
name='unitPrice'
|
||||
value={formik.values.unitPrice}
|
||||
onChange={(e) => formik.setFieldValue('unitPrice', e.target.value)}
|
||||
seprator
|
||||
/>
|
||||
</div>
|
||||
<div className='flex justify-between h-12 border-b items-center'>
|
||||
<div className='text-description'>
|
||||
{t('receip.status_factor')}
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col gap-2 items-center'>
|
||||
<div>
|
||||
تایید شده
|
||||
{t('receip.discount')}
|
||||
</div>
|
||||
<Input
|
||||
type='number'
|
||||
className='w-16 text-center'
|
||||
name='discount'
|
||||
value={formik.values.discount}
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col gap-2 items-center'>
|
||||
<div>
|
||||
{t('receip.total_amount')}
|
||||
</div>
|
||||
<Input
|
||||
className='text-center bg-[#EBEDF5]'
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div onClick={() => {
|
||||
if (formik.errors.name || formik.errors.count || formik.errors.unitPrice || formik.errors.discount) {
|
||||
toast.error(t('receip.error_empty'))
|
||||
} else {
|
||||
formik.handleSubmit()
|
||||
}
|
||||
}} className='size-10 border border-border rounded-xl flex justify-center items-center'>
|
||||
<Add size={20} color='black' />
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
{
|
||||
items.map((item, index: number) => {
|
||||
return (
|
||||
<div key={item.name} className='mt-6 flex items-end justify-between text-xs text-description'>
|
||||
<div className='flex flex-col gap-2 items-center'>
|
||||
{
|
||||
index === 0 &&
|
||||
<div>
|
||||
{t('receip.number')}
|
||||
</div>
|
||||
}
|
||||
<div className='size-10 bg-[#EBEDF5] rounded-xl flex justify-center items-center'>
|
||||
{index + 1}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col gap-2 items-center'>
|
||||
{
|
||||
index === 0 &&
|
||||
<div>
|
||||
{t('receip.product_name')}
|
||||
</div>
|
||||
}
|
||||
<Input
|
||||
className='text-center'
|
||||
name='name'
|
||||
readOnly
|
||||
value={item.name}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col gap-2 items-center'>
|
||||
{
|
||||
index === 0 &&
|
||||
<div>
|
||||
{t('receip.count')}
|
||||
</div>
|
||||
}
|
||||
<Input
|
||||
type='number'
|
||||
className='w-16 text-center'
|
||||
name='count'
|
||||
readOnly
|
||||
value={item.count}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col gap-2 items-center'>
|
||||
{
|
||||
index === 0 &&
|
||||
<div>
|
||||
{t('receip.unit_amount')}
|
||||
</div>
|
||||
}
|
||||
<Input
|
||||
className='text-center'
|
||||
name='unitPrice'
|
||||
readOnly
|
||||
value={item.unitPrice}
|
||||
seprator
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col gap-2 items-center'>
|
||||
{
|
||||
index === 0 &&
|
||||
<div>
|
||||
{t('receip.discount')}
|
||||
</div>
|
||||
}
|
||||
<Input
|
||||
type='number'
|
||||
className='w-16 text-center'
|
||||
name='discount'
|
||||
readOnly
|
||||
value={item.discount}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col gap-2 items-center'>
|
||||
{
|
||||
index === 0 &&
|
||||
<div>
|
||||
{t('receip.total_amount')}
|
||||
</div>
|
||||
}
|
||||
<Input
|
||||
className='text-center bg-[#EBEDF5]'
|
||||
readOnly
|
||||
value={((Number(item.unitPrice) || 0) * (Number(item.count) || 0) - (Number(item.unitPrice) || 0) * (Number(item.count) || 0) * (Number(item.discount) / 100 || 0)).toFixed(1)}
|
||||
seprator
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div onClick={() => handleRemove(index)} className='size-10 border border-border rounded-xl flex justify-center items-center'>
|
||||
<Trash size={20} color='black' />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
<div className='mt-8 flex flex-col gap-4 items-end'>
|
||||
<div className='xl:w-1/2 h-12 w-full rounded-xl flex px-4 bg-[#EBEDF5] text-sm text-description justify-between items-center'>
|
||||
<div>
|
||||
{t('receip.tax')}
|
||||
</div>
|
||||
<div className='text-black'>
|
||||
{(items.reduce((acc, item) => acc + (Number(item.unitPrice) || 0) * (Number(item.count) || 0) - (Number(item.unitPrice) || 0) * (Number(item.count) || 0) * (Number(item.discount) / 100 || 0), 0) * 0.10).toFixed(1)}
|
||||
</div>
|
||||
</div>
|
||||
<div className='xl:w-1/2 h-12 w-full rounded-xl flex px-4 bg-[#EBEDF5] text-sm text-description justify-between items-center'>
|
||||
<div>
|
||||
{t('receip.total')}
|
||||
</div>
|
||||
<div className='text-black'>
|
||||
{(
|
||||
items.reduce((acc, item) => acc + (Number(item.unitPrice) || 0) * (Number(item.count) || 0) - (Number(item.unitPrice) || 0) * (Number(item.count) || 0) * (Number(item.discount) / 100 || 0), 0) * 1.10
|
||||
).toFixed(1)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-5 flex justify-center gap-2'>
|
||||
<div className='border size-[72px] flex-col text-[10px] justify-center items-center rounded-xl border-border flex'>
|
||||
<img src={SamanImage} alt='saman' className='w-8 h-8' />
|
||||
<div className='mt-2'>
|
||||
بانک سامان
|
||||
</div>
|
||||
</div>
|
||||
<div className='border size-[72px] flex-col text-[10px] justify-center items-center rounded-xl border-border flex'>
|
||||
<img src={SamanImage} alt='saman' className='w-8 h-8' />
|
||||
<div className='mt-2'>
|
||||
بانک سامان
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='bg-white w-sidebar 3xl:block hidden py-10 px-5 h-fit rounded-3xl'>
|
||||
<div className='text-sm'>
|
||||
{t('ticket.title_hint')}
|
||||
</div>
|
||||
|
||||
<div className='mt-6'>
|
||||
<div className='flex items-start gap-2 border-b pb-5'>
|
||||
<div>
|
||||
<TickSquare size={20} color='black' variant='Bold' />
|
||||
</div>
|
||||
<div className='text-description text-xs leading-5'>
|
||||
سوالات - مشکلاتی که به یک موضوع مربوط میشوند را در یک درخواست پشتیبانی پیگیر باشید و چند درخواست برای یک موضوع باز نکنید.
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-start gap-2 mt-6 border-b pb-5'>
|
||||
<div>
|
||||
<TickSquare size={20} color='black' variant='Bold' />
|
||||
</div>
|
||||
<div className='text-description text-xs leading-5'>
|
||||
لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است.
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-start gap-2 mt-6'>
|
||||
<div>
|
||||
<TickSquare size={20} color='black' variant='Bold' />
|
||||
</div>
|
||||
<div className='text-description text-xs leading-5'>
|
||||
پاسخگویی 24 ساعته تلفنی را تنها از میهن وب هاست می توانید انتظار داشته باشید .بخش پشتیبانی در هر ساعتی حتی در روز های تعطیل آماده پیگیری سریع مشکلات کاربران است.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ReceiptsDetail
|
||||
export default CreateReceipt
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tabs from '../../components/Tabs'
|
||||
import { FolderOpen, WalletCheck, WalletMinus, WalletRemove, WalletSearch } from 'iconsax-react'
|
||||
import { Edit, FolderOpen, WalletCheck, WalletMinus, WalletRemove, WalletSearch } from 'iconsax-react'
|
||||
import Td from '../../components/Td'
|
||||
import { useGetInvoices } from './hooks/useReceiptData'
|
||||
import PageLoading from '../../components/PageLoading'
|
||||
@@ -10,9 +10,10 @@ import { NumberFormat } from '../../config/func'
|
||||
import Select from '../../components/Select'
|
||||
import Input from '../../components/Input'
|
||||
import DatePickerComponent from '../../components/DatePicker'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
import { Link, useSearchParams } from 'react-router-dom'
|
||||
import moment from 'moment-jalaali'
|
||||
import Pagination from '../../components/Pagination'
|
||||
import { Pages } from '../../config/Pages'
|
||||
|
||||
const ReceiptsList: FC = () => {
|
||||
|
||||
@@ -154,12 +155,16 @@ const ReceiptsList: FC = () => {
|
||||
<Td text={item.status === 'APPROVED' || item.status === 'PAID' ? t('receip.approved') : t('receip.pending')} />
|
||||
<Td text={t(`receip.${item.status}`)} />
|
||||
<Td text=''>
|
||||
{item.isRecurring ? t('receip.recurringPeriod') : '_'}
|
||||
{item.isRecurring ? t(`receip.${item.recurringPeriod}`) : '_'}
|
||||
</Td>
|
||||
<Td text=''>
|
||||
{item.maxRecurringCycles}
|
||||
</Td>
|
||||
<Td text={''} />
|
||||
<Td text={''}>
|
||||
<Link to={Pages.receipts.detail + item.id}>
|
||||
<Edit size={20} color='#888' />
|
||||
</Link>
|
||||
</Td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export enum ReceiptTypeEnum {
|
||||
DAILY = '1',
|
||||
WEEKLY = '2',
|
||||
MONTHLY = '3',
|
||||
QUARTERLY = '4',
|
||||
YEARLY = '5',
|
||||
export enum RecurringPeriodEnum {
|
||||
WEEKLY = 1, // هفتگی
|
||||
MONTHLY = 2, // ماهانه
|
||||
QUARTERLY = 3, // سهماهه
|
||||
SEMIANNUALLY = 4, // ششماهه
|
||||
ANNUALLY = 5, // سالانه
|
||||
}
|
||||
@@ -22,3 +22,17 @@ export const useCreateInvoice = () => {
|
||||
mutationFn: (variables: CreateReceiptType) => api.createInvoice(variables),
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetInvoiceById = (id: string) => {
|
||||
return useQuery({
|
||||
queryKey: ["invoice", id],
|
||||
queryFn: () => api.getInvoiceById(id),
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateInvoice = () => {
|
||||
return useMutation({
|
||||
mutationFn: ({ id, params }: { id: string; params: CreateReceiptType }) =>
|
||||
api.updateInvoice(id, params),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -25,3 +25,13 @@ export const createInvoice = async (params: CreateReceiptType) => {
|
||||
const { data } = await axios.post(`/invoices`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getInvoiceById = async (id: string) => {
|
||||
const { data } = await axios.get(`/invoices/${id}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const updateInvoice = async (id: string, params: CreateReceiptType) => {
|
||||
const { data } = await axios.patch(`/invoices/${id}`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user