create bill water

This commit is contained in:
hamid zarghami
2026-02-17 09:21:57 +03:30
parent 6238e3e1a7
commit 8eabcf419e
12 changed files with 300 additions and 98 deletions
+192
View File
@@ -0,0 +1,192 @@
import { FC, useEffect, useRef } from 'react'
import { useGetAllCompanies } from '../company/hooks/useCompanyData'
import { useFormik } from 'formik'
import * as Yup from 'yup'
import { CreateBillType } from './types/Types'
import { useCreateBill } from './hooks/useBillData'
import Button from '../../components/Button'
import Input from '../../components/Input'
import { TickCircle } from 'iconsax-react'
import { toast } from 'react-toastify'
import { ErrorType } from '../../helpers/types'
import { Pages } from '../../config/Pages'
import { useNavigate } from 'react-router-dom'
import { CompanyItemType } from '../company/types/CompanyTypes'
import PageLoading from '../../components/PageLoading'
const CreateBill: FC = () => {
const navigate = useNavigate()
const getCompanies = useGetAllCompanies()
const createBill = useCreateBill()
const companies: CompanyItemType[] = getCompanies.data?.data?.companies ?? []
const formik = useFormik<CreateBillType>({
initialValues: {
waterRate: 0,
dueDays: 0,
values: [],
},
enableReinitialize: true,
validationSchema: Yup.object({
waterRate: Yup.number().min(0, 'نرخ آب باید عدد مثبت باشد').required('نرخ آب الزامی است'),
dueDays: Yup.number().min(0, 'تعداد روز مهلت باید عدد مثبت باشد').required('تعداد روز مهلت الزامی است'),
values: Yup.array()
.of(
Yup.object({
companyId: Yup.string().required(),
waterUsage: Yup.number().min(0, 'مصرف آب باید عدد مثبت باشد').required('مصرف آب الزامی است'),
})
)
.min(1, 'حداقل یک شرکت باید مصرف داشته باشد')
.required('اطلاعات مصرف شرکت‌ها الزامی است'),
}),
onSubmit: async (values) => {
const filteredValues = values.values.filter((v) => v.companyId && (v.waterUsage ?? 0) >= 0)
if (filteredValues.length === 0) {
toast.error('حداقل یک شرکت با مصرف معتبر وارد کنید')
return
}
await createBill.mutateAsync(
{ ...values, values: filteredValues },
{
onSuccess: () => {
toast.success('قبض آب با موفقیت ثبت شد')
navigate(Pages.company.list)
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error?.message?.[0] ?? 'خطا در ثبت قبض')
},
}
)
},
})
const initializedRef = useRef(false)
useEffect(() => {
if (companies.length > 0 && !initializedRef.current) {
initializedRef.current = true
formik.setFieldValue(
'values',
companies.map((c) => ({ companyId: c.id, waterUsage: 0 }))
)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [companies])
if (getCompanies.isLoading) {
return <PageLoading />
}
if (companies.length === 0 && !getCompanies.isLoading) {
return (
<div className="mt-4 p-6 bg-white rounded-3xl text-center text-gray-600">
شرکتی ثبت نشده است. ابتدا شرکت اضافه کنید.
</div>
)
}
const setWaterUsage = (companyId: string, waterUsage: number) => {
const next = formik.values.values.map((v) =>
v.companyId === companyId ? { ...v, waterUsage } : v
)
formik.setFieldValue('values', next)
}
return (
<div className="mt-4">
<div className="flex justify-between items-center">
<div className="text-lg font-medium">افزودن قبض آب</div>
<Button
className="w-fit px-4"
onClick={() => formik.handleSubmit()}
isLoading={createBill.isPending}
>
<div className="flex items-center gap-2">
<TickCircle size={20} color="white" />
<span>ثبت قبض</span>
</div>
</Button>
</div>
<div className="mt-10 bg-white py-8 xl:px-10 px-4 rounded-3xl">
<div className="text-base font-medium mb-6">اطلاعات قبض</div>
<div className="rowTwoInput mt-6">
<Input
label="نرخ آب (ریال)"
name="waterRate"
type="number"
min={0}
value={formik.values.waterRate}
onChange={formik.handleChange}
error_text={formik.touched.waterRate && formik.errors.waterRate ? String(formik.errors.waterRate) : ''}
/>
<Input
label="تعداد روز مهلت پرداخت"
name="dueDays"
type="number"
min={0}
value={formik.values.dueDays}
onChange={formik.handleChange}
error_text={formik.touched.dueDays && formik.errors.dueDays ? String(formik.errors.dueDays) : ''}
/>
</div>
<div className="mt-8">
<div className="mb-4">
<span className="text-sm font-medium">مصرف آب به تفکیک شرکت</span>
<p className="text-xs text-gray-500 mt-1">مصرف آب هر شرکت را در ردیف مربوطه وارد کنید.</p>
</div>
<div className="border border-border rounded-2xl overflow-hidden">
<table className="w-full text-sm">
<thead className="bg-gray-50">
<tr>
<th className="text-right py-3 px-4 font-medium border-b border-border">ردیف</th>
<th className="text-right py-3 px-4 font-medium border-b border-border">نام شرکت</th>
<th className="text-right py-3 px-4 font-medium border-b border-border w-48">مصرف آب</th>
</tr>
</thead>
<tbody>
{companies.map((company, index) => {
const value = formik.values.values.find((v) => v.companyId === company.id)
const usage = value?.waterUsage ?? 0
return (
<tr key={company.id} className="border-b border-border last:border-0 hover:bg-gray-50/50">
<td className="py-3 px-4 text-gray-600">{index + 1}</td>
<td className="py-3 px-4 font-medium">{company.name}</td>
<td className="py-2 px-4">
<Input
name={`usage-${company.id}`}
type="number"
min={0}
value={usage}
onChange={(e) => setWaterUsage(company.id, Number(e.target.value) || 0)}
error_text={
Array.isArray(formik.errors.values) &&
formik.errors.values[index] &&
(formik.errors.values[index] as { waterUsage?: string })?.waterUsage
? (formik.errors.values[index] as { waterUsage: string }).waterUsage
: ''
}
className="!mb-0"
/>
</td>
</tr>
)
})}
</tbody>
</table>
</div>
{formik.touched.values && typeof formik.errors.values === 'string' && (
<div className="text-xs text-red-600 mt-2 font-medium">{formik.errors.values}</div>
)}
</div>
</div>
</div>
)
}
export default CreateBill
+8
View File
@@ -0,0 +1,8 @@
import { useMutation } from "@tanstack/react-query";
import * as api from "../service/Service";
export const useCreateBill = () => {
return useMutation({
mutationFn: api.createBill,
});
};
+7
View File
@@ -0,0 +1,7 @@
import axios from "../../../config/axios";
import { CreateBillType } from "../types/Types";
export const createBill = async (params: CreateBillType) => {
const { data } = await axios.post("/bills/water", params);
return data;
};
+8
View File
@@ -0,0 +1,8 @@
export type CreateBillType = {
waterRate: number;
dueDays: number;
values: {
companyId: string;
waterUsage: number;
}[];
};