fix build
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
hamid zarghami
2026-07-22 15:59:15 +03:30
parent 24171dbaf8
commit 76a68adc1c
4 changed files with 1139 additions and 1216 deletions
@@ -2,9 +2,7 @@ import axios from "../../../config/axios";
import { CreateCustomerType } from "../types/CustomerTypes";
export const getCustomers = async (page: number, limit: number, withoutPaginate?: boolean, search?: string) => {
const { data } = await axios.get(
`/users/customers?page=${page}&limit=${limit}&paginate=${withoutPaginate ? "0" : "1"}&q=${encodeURIComponent(search ?? "")}`,
);
const { data } = await axios.get(`/users/customers?page=${page}&limit=${limit}&paginate=${withoutPaginate ? "0" : "1"}&q=${encodeURIComponent(search ?? "")}`);
return data;
};
+248 -283
View File
@@ -1,62 +1,64 @@
import { ChangeEvent, FC, Fragment, useState } from 'react'
import { useTranslation } from 'react-i18next'
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 '../../components/Toast';
import { useGetCustomers } from '../customer/hooks/useCustomerData'
import { CustomerItemType } from '../customer/types/CustomerTypes'
import PageLoading from '../../components/PageLoading'
import { useCreateInvoice } from './hooks/useReceiptData'
import { useNavigate } from 'react-router-dom'
import { Pages } from '../../config/Pages'
import { ErrorType } from '../../helpers/types'
import { RecurringPeriodEnum } from './enum/ReceipEnum'
import SwitchComponent from '../../components/Switch'
import { useFormik } from "formik";
import { Add, TickCircle, TickSquare, Trash } from "iconsax-react";
import { ChangeEvent, FC, Fragment, useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import * as Yup from "yup";
import Button from "../../components/Button";
import Input from "../../components/Input";
import PageLoading from "../../components/PageLoading";
import Select from "../../components/Select";
import SwitchComponent from "../../components/Switch";
import { toast } from "../../components/Toast";
import { Pages } from "../../config/Pages";
import { ErrorType } from "../../helpers/types";
import { useGetCustomers } from "../customer/hooks/useCustomerData";
import { CustomerItemType } from "../customer/types/CustomerTypes";
import { RecurringPeriodEnum } from "./enum/ReceipEnum";
import { useCreateInvoice } from "./hooks/useReceiptData";
import { CreateReceiptType, ReceiptCreateItemsType } from "./types/ReceiptTypes";
const CreateReceipt: FC = () => {
const navigate = useNavigate()
const { t } = useTranslation('global')
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 createInvoice = useCreateInvoice()
const navigate = useNavigate();
const { t } = useTranslation("global");
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, 50, true);
const createInvoice = useCreateInvoice();
const formik = useFormik<ReceiptCreateItemsType>({
initialValues: {
name: '',
count: '',
unitPrice: '',
discount: ''
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')),
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, {
setItems((prev) => [
...prev,
{
count: +values.count,
discount: +values.discount,
name: values.name,
unitPrice: +values.unitPrice
}])
formik.resetForm()
}
})
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 value = e.target.value;
const customer = getCustomers.data?.data?.customers?.find((item: CustomerItemType) => item.id === value);
setCustomer(customer);
};
const handleSubmit = () => {
if (customer) {
@@ -65,406 +67,369 @@ const CreateReceipt: FC = () => {
items: items,
isRecurring: isRecurring,
recurringPeriod: isRecurring ? +type : undefined,
maxRecurringCycles: isRecurring ? maxRecurringCycles : undefined
}
maxRecurringCycles: isRecurring ? maxRecurringCycles : undefined,
};
createInvoice.mutate(params, {
onSuccess: () => {
toast(t('success'), 'success')
navigate(Pages.receipts.index)
toast(t("success"), "success");
navigate(Pages.receipts.index);
},
onError(error: ErrorType) {
toast(error.response?.data?.error.message[0], 'error')
toast(error.response?.data?.error.message[0], "error");
},
})
}
});
}
};
const handleRemove = (index: number) => {
setItems((prev) => prev.filter((_, i) => i !== index))
}
setItems((prev) => prev.filter((_, i) => i !== index));
};
return (
<div className='mt-4'>
{
getCustomers.isPending ?
<div className="mt-4">
{getCustomers.isPending ? (
<PageLoading />
:
) : (
<Fragment>
<div className='flex justify-between items-center'>
<div>
{t('receip.add_receip')}
</div>
<div className="flex justify-between items-center">
<div>{t("receip.add_receip")}</div>
<div>
<Button
className='px-5'
className="px-5"
onClick={handleSubmit}
isLoading={createInvoice.isPending}
>
<div className='flex gap-2 items-center'>
<TickCircle size={20} color='white' />
<div>
{t('receip.submit_receip')}
</div>
<div className="flex gap-2 items-center">
<TickCircle
size={20}
color="white"
/>
<div>{t("receip.submit_receip")}</div>
</div>
</Button>
</div>
</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.customer_information')}
</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.customer_information")}</div>
<div className='mt-8'>
<div className='w-full xl:w-1/2'>
<div className="mt-8">
<div className="w-full xl:w-1/2">
<Select
label={t('receip.customer')}
placeholder={t('select')}
label={t("receip.customer")}
placeholder={t("select")}
items={getCustomers.data?.data?.customers?.map((item: CustomerItemType) => {
return {
label: item.firstName + ' ' + item.lastName,
value: item.id
}
label: item.firstName + " " + item.lastName,
value: item.id,
};
})}
onChange={handleChangeCustomer}
/>
</div>
</div>
{
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>
{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 className='flex items-center gap-2'>
<div className='text-description'>{t('receip.representativeـname')}</div>
<div>{customer.firstName + ' ' + customer.lastName}</div>
<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 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 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 mt-7'>
<div className='flex items-center gap-2 flex-1'>
<div className='text-description'>{t('receip.tel_company')}</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 className='flex items-center gap-2 flex-1'>
<div className='text-description'>{t('receip.national_code')}</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 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 mt-7'>
<div className='flex items-center gap-2 flex-1'>
<div className='text-description'>{t('receip.state')}</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 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 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='mt-10'>
<div className="mt-10">
<SwitchComponent
label={t('receip.recurring')}
label={t("receip.recurring")}
active={isRecurring}
onChange={(value) => setIsRecurring(value)}
/>
</div>
{
isRecurring &&
<div className='mt-10'>
<div>
{t('receip.select_type')}
</div>
{isRecurring && (
<div className="mt-10">
<div>{t("receip.select_type")}</div>
<div className='mt-4 rowTwoInput'>
<div className="mt-4 rowTwoInput">
<Select
label={t('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() }
{ 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
label={t('receip.maxRecurringCycles')}
name='maxRecurringCycles'
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 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 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='flex flex-col gap-2 items-center'>
<div>
{t('receip.product_name')}
</div>
<div className="flex flex-col gap-2 items-center">
<div>{t("receip.product_name")}</div>
<Input
className='text-center'
name='name'
className="text-center"
name="name"
value={formik.values.name}
onChange={formik.handleChange}
/>
</div>
<div className='flex flex-col gap-2 items-center'>
<div>
{t('receip.count')}
</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'
type="number"
className="w-16 text-center"
name="count"
value={formik.values.count}
onChange={formik.handleChange}
/>
</div>
<div className='flex flex-col gap-2 items-center'>
<div>
{t('receip.unit_amount')}
</div>
<div className="flex flex-col gap-2 items-center">
<div>{t("receip.unit_amount")}</div>
<Input
className='text-center'
name='unitPrice'
className="text-center"
name="unitPrice"
value={formik.values.unitPrice}
onChange={(e) => formik.setFieldValue('unitPrice', e.target.value)}
onChange={(e) => formik.setFieldValue("unitPrice", e.target.value)}
seprator
/>
</div>
<div className='flex flex-col gap-2 items-center'>
<div>
{t('receip.discount')}
</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'
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>
<div className="flex flex-col gap-2 items-center">
<div>{t("receip.total_amount")}</div>
<Input
className='text-center bg-[#EBEDF5]'
className="text-center bg-[#EBEDF5]"
readOnly
/>
</div>
<div onClick={() => {
<div
onClick={() => {
if (formik.errors.name || formik.errors.count || formik.errors.unitPrice || formik.errors.discount) {
toast(t('receip.error_empty'), 'error')
toast(t("receip.error_empty"), "error");
} else {
formik.handleSubmit()
formik.handleSubmit();
}
}} className='size-10 border border-border rounded-xl flex justify-center items-center'>
<Add size={20} color='black' />
}}
className="size-10 border border-border rounded-xl flex justify-center items-center"
>
<Add
size={20}
color="black"
/>
</div>
</div>
</div>
{
items.map((item, index: number) => {
{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
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='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>
}
<div className="flex flex-col gap-2 items-center">
{index === 0 && <div>{t("receip.product_name")}</div>}
<Input
className='text-center'
name='name'
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>
}
<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'
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>
}
<div className="flex flex-col gap-2 items-center">
{index === 0 && <div>{t("receip.unit_amount")}</div>}
<Input
className='text-center'
name='unitPrice'
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>
}
<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'
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>
}
<div className="flex flex-col gap-2 items-center">
{index === 0 && <div>{t("receip.total_amount")}</div>}
<Input
className='text-center bg-[#EBEDF5]'
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
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'>
<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) * 1.10
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.1
).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.1
).toFixed(1)}
</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="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 className="mt-6">
<div className="flex items-start gap-2 border-b pb-5">
<div>
<TickSquare size={20} color='black' variant='Bold' />
<TickSquare
size={20}
color="black"
variant="Bold"
/>
</div>
<div className='text-description text-xs leading-5'>
سوالات - مشکلاتی که به یک موضوع مربوط میشوند را در یک درخواست پشتیبانی پیگیر باشید و چند درخواست برای یک موضوع باز نکنید.
<div className="text-description text-xs leading-5">سوالات - مشکلاتی که به یک موضوع مربوط میشوند را در یک درخواست پشتیبانی پیگیر باشید و چند درخواست برای یک موضوع باز نکنید.</div>
</div>
</div>
<div className='flex items-start gap-2 mt-6 border-b pb-5'>
<div className="flex items-start gap-2 mt-6 border-b pb-5">
<div>
<TickSquare size={20} color='black' variant='Bold' />
<TickSquare
size={20}
color="black"
variant="Bold"
/>
</div>
<div className='text-description text-xs leading-5'>
لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است.
<div className="text-description text-xs leading-5">لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است.</div>
</div>
</div>
<div className='flex items-start gap-2 mt-6'>
<div className="flex items-start gap-2 mt-6">
<div>
<TickSquare size={20} color='black' variant='Bold' />
<TickSquare
size={20}
color="black"
variant="Bold"
/>
</div>
<div className='text-description text-xs leading-5'>
<div className="text-description text-xs leading-5">
پاسخگویی 24 ساعته تلفنی را تنها از میهن وب هاست می توانید انتظار داشته باشید .بخش پشتیبانی در هر ساعتی حتی در روز های تعطیل آماده پیگیری سریع مشکلات کاربران است.
</div>
</div>
@@ -472,9 +437,9 @@ const CreateReceipt: FC = () => {
</div>
</div>
</Fragment>
}
)}
</div>
)
}
);
};
export default CreateReceipt
export default CreateReceipt;
+263 -296
View File
@@ -1,22 +1,22 @@
import { ChangeEvent, FC, Fragment, useState, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
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 '../../components/Toast';
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'
import { useFormik } from "formik";
import { Add, TickCircle, TickSquare, Trash } from "iconsax-react";
import { ChangeEvent, FC, Fragment, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "react-router-dom";
import * as Yup from "yup";
import Button from "../../components/Button";
import Input from "../../components/Input";
import PageLoading from "../../components/PageLoading";
import Select from "../../components/Select";
import SwitchComponent from "../../components/Switch";
import { toast } from "../../components/Toast";
import { Pages } from "../../config/Pages";
import { ErrorType } from "../../helpers/types";
import { useGetCustomers } from "../customer/hooks/useCustomerData";
import { CustomerItemType } from "../customer/types/CustomerTypes";
import { RecurringPeriodEnum } from "./enum/ReceipEnum";
import { useGetInvoiceById, useUpdateInvoice } from "./hooks/useReceiptData";
import { CreateReceiptType, ReceiptCreateItemsType } from "./types/ReceiptTypes";
// Add interface for invoice item from API
interface InvoiceItemType {
@@ -32,47 +32,49 @@ interface InvoiceItemType {
}
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 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, 50, true);
const formik = useFormik<ReceiptCreateItemsType>({
initialValues: {
name: '',
count: '',
unitPrice: '',
discount: ''
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')),
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, {
setItems((prev) => [
...prev,
{
count: +values.count,
discount: +values.discount,
name: values.name,
unitPrice: +values.unitPrice
}])
formik.resetForm()
}
})
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 value = e.target.value;
const customer = getCustomers.data?.data?.customers?.find((item: CustomerItemType) => item.id === value);
setCustomer(customer);
};
const handleSubmit = () => {
if (customer) {
@@ -81,28 +83,30 @@ const CreateReceipt: FC = () => {
items: items,
isRecurring: isRecurring,
recurringPeriod: isRecurring ? +type : undefined,
maxRecurringCycles: isRecurring ? maxRecurringCycles : undefined
}
maxRecurringCycles: isRecurring ? maxRecurringCycles : undefined,
};
updateInvoice.mutate({ id: id as string, params }, {
updateInvoice.mutate(
{ id: id as string, params },
{
onSuccess: () => {
toast(t('success'), 'success')
navigate(Pages.receipts.index)
toast(t("success"), "success");
navigate(Pages.receipts.index);
},
onError(error: ErrorType) {
toast(error.response?.data?.error.message[0], 'error')
toast(error.response?.data?.error.message[0], "error");
},
})
}
},
);
}
};
const handleRemove = (index: number) => {
setItems((prev) => prev.filter((_, i) => i !== index))
}
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;
@@ -111,9 +115,7 @@ const CreateReceipt: FC = () => {
// 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
);
const foundCustomer = getCustomers.data.data.customers.find((item: CustomerItemType) => item.id === invoiceData.user.id);
setCustomer(foundCustomer);
console.log("Found customer:", foundCustomer);
setCustomer(foundCustomer);
@@ -123,20 +125,21 @@ const CreateReceipt: FC = () => {
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) => ({
setItems(
invoiceData.items.map((item: InvoiceItemType) => ({
name: item.name,
count: item.count,
unitPrice: item.unitPrice,
discount: item.discount || 0
})));
discount: item.discount || 0,
})),
);
// 3. Set formik values from first item
}
// 4. Set recurring settings
setIsRecurring(Boolean(invoiceData.isRecurring));
if (typeof invoiceData.recurringPeriod === 'number') {
if (typeof invoiceData.recurringPeriod === "number") {
setType(invoiceData.recurringPeriod.toString());
}
setMaxRecurringCycles(invoiceData.maxRecurringCycles || 0);
@@ -149,8 +152,8 @@ const CreateReceipt: FC = () => {
// Trigger the onChange manually with a synthetic event
const event = {
target: {
value: invoice.user.id
}
value: invoice.user.id,
},
} as ChangeEvent<HTMLSelectElement>;
handleChangeCustomer(event);
@@ -166,387 +169,351 @@ const CreateReceipt: FC = () => {
}, [customer, invoice]);
return (
<div className='mt-4'>
{
getCustomers.isPending || isLoading ?
<div className="mt-4">
{getCustomers.isPending || isLoading ? (
<PageLoading />
:
) : (
<Fragment>
<div className='flex justify-between items-center'>
<div>
{t('receip.add_receip')}
</div>
<div className="flex justify-between items-center">
<div>{t("receip.add_receip")}</div>
<div>
<Button
className='px-5'
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 className="flex gap-2 items-center">
<TickCircle
size={20}
color="white"
/>
<div>{t("receip.submit_receip")}</div>
</div>
</Button>
</div>
</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.customer_information')}
</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.customer_information")}</div>
<div className='mt-8'>
<div className='w-full xl:w-1/2'>
<div className="mt-8">
<div className="w-full xl:w-1/2">
<Select
label={t('receip.customer')}
placeholder={t('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
}
label: item.firstName + " " + item.lastName,
value: item.id,
};
})}
onChange={handleChangeCustomer}
/>
</div>
</div>
{
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>
{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 className='flex items-center gap-2'>
<div className='text-description'>{t('receip.representativeـname')}</div>
<div>{customer.firstName + ' ' + customer.lastName}</div>
<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 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 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 mt-7'>
<div className='flex items-center gap-2 flex-1'>
<div className='text-description'>{t('receip.tel_company')}</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 className='flex items-center gap-2 flex-1'>
<div className='text-description'>{t('receip.national_code')}</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 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 mt-7'>
<div className='flex items-center gap-2 flex-1'>
<div className='text-description'>{t('receip.state')}</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 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 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='mt-10'>
<div className="mt-10">
<SwitchComponent
label={t('receip.recurring')}
label={t("receip.recurring")}
active={isRecurring}
onChange={(value) => setIsRecurring(value)}
/>
</div>
{
isRecurring &&
<div className='mt-10'>
<div>
{t('receip.select_type')}
</div>
{isRecurring && (
<div className="mt-10">
<div>{t("receip.select_type")}</div>
<div className='mt-4 rowTwoInput'>
<div className="mt-4 rowTwoInput">
<Select
label={t('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() }
{ 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'
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 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 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='flex flex-col gap-2 items-center'>
<div>
{t('receip.product_name')}
</div>
<div className="flex flex-col gap-2 items-center">
<div>{t("receip.product_name")}</div>
<Input
className='text-center'
name='name'
className="text-center"
name="name"
value={formik.values.name}
onChange={formik.handleChange}
/>
</div>
<div className='flex flex-col gap-2 items-center'>
<div>
{t('receip.count')}
</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'
type="number"
className="w-16 text-center"
name="count"
value={formik.values.count}
onChange={formik.handleChange}
/>
</div>
<div className='flex flex-col gap-2 items-center'>
<div>
{t('receip.unit_amount')}
</div>
<div className="flex flex-col gap-2 items-center">
<div>{t("receip.unit_amount")}</div>
<Input
className='text-center'
name='unitPrice'
className="text-center"
name="unitPrice"
value={formik.values.unitPrice}
onChange={(e) => formik.setFieldValue('unitPrice', e.target.value)}
onChange={(e) => formik.setFieldValue("unitPrice", e.target.value)}
seprator
/>
</div>
<div className='flex flex-col gap-2 items-center'>
<div>
{t('receip.discount')}
</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'
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>
<div className="flex flex-col gap-2 items-center">
<div>{t("receip.total_amount")}</div>
<Input
className='text-center bg-[#EBEDF5]'
className="text-center bg-[#EBEDF5]"
readOnly
/>
</div>
<div onClick={() => {
<div
onClick={() => {
if (formik.errors.name || formik.errors.count || formik.errors.unitPrice || formik.errors.discount) {
toast(t('receip.error_empty'), 'error')
toast(t("receip.error_empty"), "error");
} else {
formik.handleSubmit()
formik.handleSubmit();
}
}} className='size-10 border border-border rounded-xl flex justify-center items-center'>
<Add size={20} color='black' />
}}
className="size-10 border border-border rounded-xl flex justify-center items-center"
>
<Add
size={20}
color="black"
/>
</div>
</div>
</div>
{
items.map((item, index: number) => {
{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
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='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>
}
<div className="flex flex-col gap-2 items-center">
{index === 0 && <div>{t("receip.product_name")}</div>}
<Input
className='text-center'
name='name'
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>
}
<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'
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>
}
<div className="flex flex-col gap-2 items-center">
{index === 0 && <div>{t("receip.unit_amount")}</div>}
<Input
className='text-center'
name='unitPrice'
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>
}
<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'
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>
}
<div className="flex flex-col gap-2 items-center">
{index === 0 && <div>{t("receip.total_amount")}</div>}
<Input
className='text-center bg-[#EBEDF5]'
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
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'>
<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) * 1.10
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.1
).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.1
).toFixed(1)}
</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="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 className="mt-6">
<div className="flex items-start gap-2 border-b pb-5">
<div>
<TickSquare size={20} color='black' variant='Bold' />
<TickSquare
size={20}
color="black"
variant="Bold"
/>
</div>
<div className='text-description text-xs leading-5'>
سوالات - مشکلاتی که به یک موضوع مربوط میشوند را در یک درخواست پشتیبانی پیگیر باشید و چند درخواست برای یک موضوع باز نکنید.
<div className="text-description text-xs leading-5">سوالات - مشکلاتی که به یک موضوع مربوط میشوند را در یک درخواست پشتیبانی پیگیر باشید و چند درخواست برای یک موضوع باز نکنید.</div>
</div>
</div>
<div className='flex items-start gap-2 mt-6 border-b pb-5'>
<div className="flex items-start gap-2 mt-6 border-b pb-5">
<div>
<TickSquare size={20} color='black' variant='Bold' />
<TickSquare
size={20}
color="black"
variant="Bold"
/>
</div>
<div className='text-description text-xs leading-5'>
لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است.
<div className="text-description text-xs leading-5">لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است.</div>
</div>
</div>
<div className='flex items-start gap-2 mt-6'>
<div className="flex items-start gap-2 mt-6">
<div>
<TickSquare size={20} color='black' variant='Bold' />
<TickSquare
size={20}
color="black"
variant="Bold"
/>
</div>
<div className='text-description text-xs leading-5'>
<div className="text-description text-xs leading-5">
پاسخگویی 24 ساعته تلفنی را تنها از میهن وب هاست می توانید انتظار داشته باشید .بخش پشتیبانی در هر ساعتی حتی در روز های تعطیل آماده پیگیری سریع مشکلات کاربران است.
</div>
</div>
@@ -554,9 +521,9 @@ const CreateReceipt: FC = () => {
</div>
</div>
</Fragment>
}
)}
</div>
)
}
);
};
export default CreateReceipt
export default CreateReceipt;
+140 -147
View File
@@ -1,244 +1,237 @@
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Input from '../../components/Input'
import Select from '../../components/Select'
import Textarea from '../../components/Textarea'
import UploadBox from '../../components/UploadBox'
import Button from '../../components/Button'
import { TickCircle, TickSquare } from 'iconsax-react'
import { useCreateTicket, useGetCategories } from './hooks/useTicketData'
import { useGetProfile } from '../profile/hooks/useProfileData'
import PageLoading from '../../components/PageLoading'
import { useFormik } from 'formik'
import * as Yup from 'yup'
import { ErrorType } from '../../helpers/types'
import { useNavigate } from 'react-router-dom'
import { Pages } from '../../config/Pages'
import { CreateTicketAdminType } from './types/TicketTypes'
import { toast } from '../../components/Toast';
import { useMultiUpload } from '../service/hooks/useServiceData'
import { useGetCustomers } from '../customer/hooks/useCustomerData'
import { CustomerItemType } from '../customer/types/CustomerTypes'
import { useFormik } from "formik";
import { TickCircle, TickSquare } from "iconsax-react";
import { FC, useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import * as Yup from "yup";
import Button from "../../components/Button";
import Input from "../../components/Input";
import PageLoading from "../../components/PageLoading";
import Select from "../../components/Select";
import Textarea from "../../components/Textarea";
import { toast } from "../../components/Toast";
import UploadBox from "../../components/UploadBox";
import { Pages } from "../../config/Pages";
import { ErrorType } from "../../helpers/types";
import { useGetCustomers } from "../customer/hooks/useCustomerData";
import { CustomerItemType } from "../customer/types/CustomerTypes";
import { useGetProfile } from "../profile/hooks/useProfileData";
import { useMultiUpload } from "../service/hooks/useServiceData";
import { useCreateTicket, useGetCategories } from "./hooks/useTicketData";
import { CreateTicketAdminType } from "./types/TicketTypes";
const CreateTicket: FC = () => {
const { t } = useTranslation('global')
const navigate = useNavigate()
const [files, setFiles] = useState<File[]>([])
const getProfile = useGetProfile()
const getCategories = useGetCategories()
const multiUpload = useMultiUpload()
const createTicket = useCreateTicket()
const getCustomers = useGetCustomers(1, true)
const { t } = useTranslation("global");
const navigate = useNavigate();
const [files, setFiles] = useState<File[]>([]);
const getProfile = useGetProfile();
const getCategories = useGetCategories();
const multiUpload = useMultiUpload();
const createTicket = useCreateTicket();
const getCustomers = useGetCustomers(1, 50, true);
const formik = useFormik<CreateTicketAdminType>({
initialValues: {
subject: '',
message: '',
categoryId: '',
priority: 'LOW',
subject: "",
message: "",
categoryId: "",
priority: "LOW",
attachmentUrls: [],
userId: ''
userId: "",
},
validationSchema: Yup.object({
subject: Yup.string().required(t('errors.required')),
message: Yup.string().required(t('errors.required')),
categoryId: Yup.string().required(t('errors.required')),
priority: Yup.string().required(t('errors.required')),
subject: Yup.string().required(t("errors.required")),
message: Yup.string().required(t("errors.required")),
categoryId: Yup.string().required(t("errors.required")),
priority: Yup.string().required(t("errors.required")),
}),
onSubmit: async (values) => {
if (files.length > 0) {
const images = new FormData()
files.forEach(file => {
images.append('files', file)
})
const images = new FormData();
files.forEach((file) => {
images.append("files", file);
});
await multiUpload.mutateAsync(images, {
onSuccess: async (data) => {
values.attachmentUrls = await data.data.map((item: { url: string }) => item?.url)
values.attachmentUrls = await data.data.map((item: { url: string }) => item?.url);
},
onError: (error: ErrorType) => {
toast(error.response?.data?.error?.message[0], 'error')
}
})
toast(error.response?.data?.error?.message[0], "error");
},
});
} else {
delete values.attachmentUrls
delete values.attachmentUrls;
}
if (values.danakServiceId === '') {
delete values.danakServiceId
if (values.danakServiceId === "") {
delete values.danakServiceId;
}
createTicket.mutate(values, {
onSuccess: (data) => {
toast(t('success'), 'success')
formik.resetForm()
navigate(Pages.ticket.detail + data.data?.ticketId)
toast(t("success"), "success");
formik.resetForm();
navigate(Pages.ticket.detail + data.data?.ticketId);
},
onError: (error: ErrorType) => {
toast(error.response?.data?.error?.message[0], 'error')
}
})
}
})
toast(error.response?.data?.error?.message[0], "error");
},
});
},
});
return (
<div className='mt-4 pb-12'>
<div>
{t('ticket.new_ticket')}
</div>
<div className="mt-4 pb-12">
<div>{t("ticket.new_ticket")}</div>
{
getProfile.isPending || getCategories.isPending ?
{getProfile.isPending || getCategories.isPending ? (
<PageLoading />
:
<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('ticket.submit_your_message')}
</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("ticket.submit_your_message")}</div>
<div className='mt-5'>
<div className="mt-5">
<Select
label={t('receip.customer')}
placeholder={t('select')}
label={t("receip.customer")}
placeholder={t("select")}
items={getCustomers.data?.data?.customers?.map((item: CustomerItemType) => {
return {
label: item.firstName + ' ' + item.lastName,
value: item.id
}
label: item.firstName + " " + item.lastName,
value: item.id,
};
})}
{...formik.getFieldProps('userId')}
{...formik.getFieldProps("userId")}
/>
</div>
<div className='mt-6 rowTwoInput'>
<div className="mt-6 rowTwoInput">
<Input
label={t('ticket.subject')}
placeholder={t('ticket.enter_your_subject')}
name='subject'
label={t("ticket.subject")}
placeholder={t("ticket.enter_your_subject")}
name="subject"
value={formik.values.subject}
onChange={formik.handleChange}
error_text={formik.touched.subject && formik.errors.subject ? formik.errors.subject : ''}
error_text={formik.touched.subject && formik.errors.subject ? formik.errors.subject : ""}
/>
<Select
label={t('ticket.priority')}
label={t("ticket.priority")}
items={[
{
label: t('ticket.low'),
value: 'LOW'
label: t("ticket.low"),
value: "LOW",
},
{
label: t('ticket.medium'),
value: 'MEDIUM'
label: t("ticket.medium"),
value: "MEDIUM",
},
{
label: t('ticket.high'),
value: 'HIGH'
}
label: t("ticket.high"),
value: "HIGH",
},
]}
placeholder={t('select')}
className='border'
name='priority'
placeholder={t("select")}
className="border"
name="priority"
value={formik.values.priority}
onChange={formik.handleChange}
error_text={formik.touched.priority && formik.errors.priority ? formik.errors.priority : ''}
error_text={formik.touched.priority && formik.errors.priority ? formik.errors.priority : ""}
/>
</div>
<div className='mt-6 rowTwoInput'>
<div className="mt-6 rowTwoInput">
<Select
label={t('ticket.category')}
items={getCategories.data?.data?.ticketCategories?.map((item: { id: string, title: string }) => ({
label={t("ticket.category")}
items={getCategories.data?.data?.ticketCategories?.map((item: { id: string; title: string }) => ({
label: item.title,
value: item.id
})
)}
placeholder={t('select')}
className='border'
name='categoryId'
value: item.id,
}))}
placeholder={t("select")}
className="border"
name="categoryId"
value={formik.values.categoryId}
onChange={formik.handleChange}
error_text={formik.touched.categoryId && formik.errors.categoryId ? formik.errors.categoryId : ''}
error_text={formik.touched.categoryId && formik.errors.categoryId ? formik.errors.categoryId : ""}
/>
</div>
<div className='mt-6'>
<div className="mt-6">
<Textarea
label={t('description')}
placeholder={t('ticket.your_description')}
name='message'
label={t("description")}
placeholder={t("ticket.your_description")}
name="message"
value={formik.values.message}
onChange={formik.handleChange}
error_text={formik.touched.message && formik.errors.message ? formik.errors.message : ''}
error_text={formik.touched.message && formik.errors.message ? formik.errors.message : ""}
/>
</div>
<div className='mt-6'>
<div className="mt-6">
<UploadBox
label={t('ticket.attachment')}
label={t("ticket.attachment")}
isMultiple
onChange={setFiles}
/>
</div>
<div className='mt-8 flex justify-end'>
<div className="mt-8 flex justify-end">
<Button
className='xl:max-w-[100px]'
className="xl:max-w-[100px]"
onClick={() => formik.handleSubmit()}
isLoading={createTicket.isPending || multiUpload.isPending}
>
<div className='flex gap-2 items-center'>
<TickCircle size={20} color='white' />
<div>
{t('ticket.send')}
</div>
<div className="flex gap-2 items-center">
<TickCircle
size={20}
color="white"
/>
<div>{t("ticket.send")}</div>
</div>
</Button>
</div>
</div>
<div className='bg-white w-sidebar xl:block hidden py-10 px-5 h-fit rounded-3xl'>
<div className='text-sm'>
{t('ticket.title_hint')}
</div>
<div className="bg-white w-sidebar xl: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 className="mt-6">
<div className="flex items-start gap-2 border-b pb-5">
<div>
<TickSquare size={20} color='black' variant='Bold' />
<TickSquare
size={20}
color="black"
variant="Bold"
/>
</div>
<div className='text-description text-xs leading-5'>
سوالات - مشکلاتی که به یک موضوع مربوط میشوند را در یک درخواست پشتیبانی پیگیر باشید و چند درخواست برای یک موضوع باز نکنید.
<div className="text-description text-xs leading-5">سوالات - مشکلاتی که به یک موضوع مربوط میشوند را در یک درخواست پشتیبانی پیگیر باشید و چند درخواست برای یک موضوع باز نکنید.</div>
</div>
</div>
<div className='flex items-start gap-2 mt-6 border-b pb-5'>
<div className="flex items-start gap-2 mt-6 border-b pb-5">
<div>
<TickSquare size={20} color='black' variant='Bold' />
<TickSquare
size={20}
color="black"
variant="Bold"
/>
</div>
<div className='text-description text-xs leading-5'>
لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است.
<div className="text-description text-xs leading-5">لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است.</div>
</div>
</div>
<div className='flex items-start gap-2 mt-6'>
<div className="flex items-start gap-2 mt-6">
<div>
<TickSquare size={20} color='black' variant='Bold' />
<TickSquare
size={20}
color="black"
variant="Bold"
/>
</div>
<div className='text-description text-xs leading-5'>
<div className="text-description text-xs leading-5">
پاسخگویی 24 ساعته تلفنی را تنها از داناک می توانید انتظار داشته باشید .بخش پشتیبانی در هر ساعتی حتی در روز های تعطیل آماده پیگیری سریع مشکلات کاربران است.
</div>
</div>
</div>
</div>
</div>
}
)}
</div>
)
}
);
};
export default CreateTicket
export default CreateTicket;