diff --git a/.env b/.env index 97cbbeb..05c5ab8 100644 --- a/.env +++ b/.env @@ -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' \ No newline at end of file +# VITE_BASE_URL = 'http://192.168.82.158:4000' \ No newline at end of file diff --git a/src/langs/fa.json b/src/langs/fa.json index 896d87a..937e9b9 100644 --- a/src/langs/fa.json +++ b/src/langs/fa.json @@ -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": { diff --git a/src/pages/receipts/Create.tsx b/src/pages/receipts/Create.tsx index ce04c37..aa3b629 100644 --- a/src/pages/receipts/Create.tsx +++ b/src/pages/receipts/Create.tsx @@ -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([]) const [customer, setCustomer] = useState() const [isRecurring, setIsRecurring] = useState(false) - const [type, setType] = useState(ReceiptTypeEnum.DAILY) + const [type, setType] = useState(RecurringPeriodEnum.WEEKLY) const [maxRecurringCycles, setMaxRecurringCycles] = useState(0) const getCustomers = useGetCustomers(1, true) const createInvoice = useCreateInvoice() @@ -204,13 +204,14 @@ const CreateReceipt: 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([]) + const [customer, setCustomer] = useState() + const [isRecurring, setIsRecurring] = useState(false) + const [type, setType] = useState(RecurringPeriodEnum.WEEKLY) + const [maxRecurringCycles, setMaxRecurringCycles] = useState(0) + const getCustomers = useGetCustomers(1, true) + + const formik = useFormik({ + 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) => { + 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; + + 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 (
-
- {t('receip.detail_account')} - ۱۲۳۱۲ -
- -
-
-
+ { + getCustomers.isPending || isLoading ? + + : +
- {t('receip.personal_information')} -
-
- -
- {t('edit')} -
-
-
- -
-
-
- {t('receip.type_person')} -
-
- {t('receip.company')} -
-
- -
-
- {t('receip.representativeـname')} -
-
- مهرداد مظفری -
-
- -
-
- {t('receip.company_name')} -
-
- داناک -
-
-
-
- {t('receip.registration_number')} -
-
- ۱۲۳۴ -
-
-
- -
-
-
- {t('receip.tel_company')} -
-
- ۰۰۱۲۳۴۵۶۷۸ -
-
- -
-
- {t('receip.national_code')} -
-
- ۰۰۱۲۳۴۵۶۷۸ -
-
- -
-
- {t('receip.postal_code')} -
-
- ۳۸۲۱۳۵۸۸۹۲ -
-
- -
- -
-
-
- {t('receip.state')} -
-
- مرکزی -
-
-
-
- {t('receip.state')} -
-
- مرکزی -
-
-
-
- {t('receip.city')} -
-
- اراک -
-
-
-
- {t('receip.address')} -
-
- اراک -خیابان شریعتی-خیابان جنت -
-
-
- -
- -
-
-
- {t('receip.detail_receip')} -
-
- -
- {t('receip.not_paid')} -
-
-
- -
-
-
- {t('receip.description')} -
-
- {t('receip.price')} -
-
-
-
- اکانت یکساله با دسترسی -
-
- ۱۰,۰۰۰,۰۰۰ ریال -
-
-
-
- ۱۰٪ ارزش افزوده -
-
- ۱۰,۰۰۰,۰۰۰ ریال -
-
-
-
- جمع کل -
-
- ۱۰,۰۰۰,۰۰۰ ریال -
-
-
-
-
- -
-
-
- {t('receip.receip_information')} -
-
- -
پرداخت نشده
-
-
- -
-
-
- {t('receip.date_factor')} + {t('receip.add_receip')}
- ۱۴۰۲/۱۰/۰۴ +
-
-
- {t('receip.due_date')} -
-
- ۱۴۰۲/۱۰/۰۴ -
-
-
-
- {t('receip.factor_number')} -
-
- ۱۲۳۱ -
-
-
-
- {t('receip.status_factor')} -
-
- تایید شده -
-
-
-
-
- saman -
- بانک سامان -
-
-
- saman -
- بانک سامان -
-
-
-
-
+
+
+
+ {t('receip.customer_information')} +
+
+
+ setType(e.target.value as unknown as RecurringPeriodEnum)} + /> + + setMaxRecurringCycles(+e.target.value)} + /> +
+
+ } + +
+
{t('receip.receipt_information')}
+
+ +
+
+
+ {t('receip.number')} +
+
+ {items.length + 1} +
+
+ +
+
+ {t('receip.product_name')} +
+ +
+ +
+
+ {t('receip.count')} +
+ +
+ +
+
+ {t('receip.unit_amount')} +
+ formik.setFieldValue('unitPrice', e.target.value)} + seprator + /> +
+ +
+
+ {t('receip.discount')} +
+ +
+ +
+
+ {t('receip.total_amount')} +
+ +
+ +
{ + 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'> + +
+ + +
+ + { + items.map((item, index: number) => { + return ( +
+
+ { + index === 0 && +
+ {t('receip.number')} +
+ } +
+ {index + 1} +
+
+
+ { + index === 0 && +
+ {t('receip.product_name')} +
+ } + +
+ +
+ { + index === 0 && +
+ {t('receip.count')} +
+ } + +
+ +
+ { + index === 0 && +
+ {t('receip.unit_amount')} +
+ } + +
+ +
+ { + index === 0 && +
+ {t('receip.discount')} +
+ } + +
+ +
+ { + index === 0 && +
+ {t('receip.total_amount')} +
+ } + +
+ +
handleRemove(index)} className='size-10 border border-border rounded-xl flex justify-center items-center'> + +
+
+ ) + }) + } + + + +
+
+
+ {t('receip.tax')} +
+
+ {(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)} +
+
+
+
+ {t('receip.total')} +
+
+ {( + 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)} +
+
+
+ +
+ +
+
+ {t('ticket.title_hint')} +
+ +
+
+
+ +
+
+ سوالات - مشکلاتی که به یک موضوع مربوط میشوند را در یک درخواست پشتیبانی پیگیر باشید و چند درخواست برای یک موضوع باز نکنید. +
+
+
+
+ +
+
+ لطفاً برای بررسی و رفع مشکلات احتمالی صبور باشید بررسی و رفع مشکلات در برخی موارد زمان گیر است. +
+
+
+
+ +
+
+ پاسخگویی 24 ساعته تلفنی را تنها از میهن وب هاست می توانید انتظار داشته باشید .بخش پشتیبانی در هر ساعتی حتی در روز های تعطیل آماده پیگیری سریع مشکلات کاربران است. +
+
+
+
+
+ + }
) } -export default ReceiptsDetail \ No newline at end of file +export default CreateReceipt diff --git a/src/pages/receipts/List.tsx b/src/pages/receipts/List.tsx index 1804a7f..ab446a0 100644 --- a/src/pages/receipts/List.tsx +++ b/src/pages/receipts/List.tsx @@ -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 = () => { - {item.isRecurring ? t('receip.recurringPeriod') : '_'} + {item.isRecurring ? t(`receip.${item.recurringPeriod}`) : '_'} {item.maxRecurringCycles} - + + + + + ) }) diff --git a/src/pages/receipts/enum/ReceipEnum.tsx b/src/pages/receipts/enum/ReceipEnum.tsx index 022b77b..50245db 100644 --- a/src/pages/receipts/enum/ReceipEnum.tsx +++ b/src/pages/receipts/enum/ReceipEnum.tsx @@ -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, // سالانه +} \ No newline at end of file diff --git a/src/pages/receipts/hooks/useReceiptData.ts b/src/pages/receipts/hooks/useReceiptData.ts index 86bd2fc..ed5da18 100644 --- a/src/pages/receipts/hooks/useReceiptData.ts +++ b/src/pages/receipts/hooks/useReceiptData.ts @@ -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), + }); +}; diff --git a/src/pages/receipts/service/ReceiptService.ts b/src/pages/receipts/service/ReceiptService.ts index 925e7b7..f2aff6a 100644 --- a/src/pages/receipts/service/ReceiptService.ts +++ b/src/pages/receipts/service/ReceiptService.ts @@ -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; +};