510 lines
17 KiB
TypeScript
510 lines
17 KiB
TypeScript
import React, { useEffect, useMemo } from "react";
|
||
import { zodResolver } from "@hookform/resolvers/zod";
|
||
import { useForm, Controller, useFieldArray } from "react-hook-form";
|
||
import { z } from "zod";
|
||
import { Grid } from "../../components/Grid/Grid";
|
||
import Typography from "../../components/Typography/Typography";
|
||
import Button from "../../components/Button/Button";
|
||
import Textfield from "../../components/Textfeild/Textfeild";
|
||
import AutoComplete from "../../components/AutoComplete/AutoComplete";
|
||
import { useDrawerStore } from "../../context/zustand-store/appStore";
|
||
import { useUserProfileStore } from "../../context/zustand-store/userStore";
|
||
import { useApiRequest, useApiMutation } from "../../utils/useApiRequest";
|
||
import { useToast } from "../../hooks/useToast";
|
||
import { TrashIcon } from "@heroicons/react/24/outline";
|
||
import {
|
||
zValidateString,
|
||
zValidateStringOptional,
|
||
zValidateAutoCompleteOptional,
|
||
} from "../../data/getFormTypeErrors";
|
||
|
||
interface MainSubmitInspectionProps {
|
||
item?: any;
|
||
isEdit?: boolean;
|
||
inspectId?: string;
|
||
handleUpdate?: () => void;
|
||
}
|
||
|
||
const infractionSchema = z.object({
|
||
title: zValidateString("عنوان تخلف"),
|
||
description: zValidateString("توضیحات تخلف"),
|
||
});
|
||
|
||
const schema = z.object({
|
||
license_type: z.array(z.union([z.string(), z.number()])).min(1, {
|
||
message: "لطفاً نوع پروانه کسب را انتخاب کنید.",
|
||
}),
|
||
document_number: zValidateString("شماره پرونده یا مجوز"),
|
||
issuer: zValidateString("صادر کننده پروانه"),
|
||
economic_code: zValidateString("کد اقتصادی"),
|
||
registration_number: zValidateString("شماره ثبت"),
|
||
ownership_type: z.array(z.union([z.string(), z.number()])).min(1, {
|
||
message: "لطفاً نوع مالکیت را انتخاب کنید.",
|
||
}),
|
||
unit_type: z.array(z.union([z.string(), z.number()])).min(1, {
|
||
message: "لطفاً نوع واحد را انتخاب کنید.",
|
||
}),
|
||
description: zValidateStringOptional("توضیحات"),
|
||
inspectors: zValidateAutoCompleteOptional(),
|
||
infractions: z.array(infractionSchema).optional(),
|
||
violation_amount: zValidateStringOptional("جمع کل ارزش ریالی تخلف"),
|
||
plaintiff_damage: zValidateStringOptional("جمع کل خسارت وارده به شاکی"),
|
||
});
|
||
|
||
type FormValues = z.infer<typeof schema>;
|
||
|
||
const MainSubmitInspection: React.FC<MainSubmitInspectionProps> = ({
|
||
item,
|
||
isEdit = false,
|
||
inspectId,
|
||
handleUpdate,
|
||
}) => {
|
||
const { profile } = useUserProfileStore();
|
||
const { closeDrawer } = useDrawerStore();
|
||
const showToast = useToast();
|
||
|
||
const { data: usersData } = useApiRequest({
|
||
api: `users/${profile?.province}`,
|
||
enabled: !!profile?.province,
|
||
});
|
||
|
||
const licenseTypeOptions = [
|
||
{ key: "دائم", value: "دائم" },
|
||
{ key: "موقت", value: "موقت" },
|
||
];
|
||
|
||
const ownershipTypeOptions = [
|
||
{ key: "دولتی", value: "دولتی" },
|
||
{ key: "غیر دولتی", value: "غیر دولتی" },
|
||
{ key: "استیجاری", value: "استیجاری" },
|
||
{ key: "شخصی", value: "شخصی" },
|
||
{ key: "سایر موارد", value: "سایر موارد" },
|
||
];
|
||
|
||
const unitTypeOptions = [
|
||
{ key: "وارد کننده", value: "وارد کننده" },
|
||
{ key: "تولیدی صنعتی", value: "تولیدی صنعتی" },
|
||
{ key: "تولیدی صنفی", value: "تولیدی صنفی" },
|
||
{ key: "انبار", value: "انبار" },
|
||
{ key: "سردخانه", value: "سردخانه" },
|
||
{ key: "توزیعی", value: "توزیعی" },
|
||
{ key: "خدماتی", value: "خدماتی" },
|
||
{ key: "خرده فروش", value: "خرده فروش" },
|
||
{ key: "عمده فروش", value: "عمده فروش" },
|
||
];
|
||
|
||
const usersList = Array.isArray(usersData)
|
||
? usersData
|
||
: ((usersData as any)?.data ?? []);
|
||
|
||
const inspectorOptions = useMemo(() => {
|
||
if (!usersList?.length) return [];
|
||
return usersList
|
||
.filter((user: any) => user.province === profile?.province)
|
||
.map((user: any) => ({
|
||
key: `${user.fullname} / ${user.mobile}`,
|
||
value: `${user.fullname} / ${user.mobile}`,
|
||
}));
|
||
}, [usersList, profile]);
|
||
|
||
const defaultInspectorKeys = useMemo(() => {
|
||
if (!isEdit || !item?.inspectors) return [];
|
||
return item.inspectors.map((insp: any) => {
|
||
if (typeof insp === "string") return insp;
|
||
const user = usersList?.find((u: any) => u.fullname === insp.fullname);
|
||
return user ? `${insp.fullname} / ${user.mobile}` : insp.fullname;
|
||
});
|
||
}, [item, isEdit, usersList]);
|
||
|
||
const {
|
||
control,
|
||
handleSubmit,
|
||
setValue,
|
||
watch,
|
||
formState: { errors, isSubmitting },
|
||
} = useForm<FormValues>({
|
||
resolver: zodResolver(schema),
|
||
defaultValues: {
|
||
license_type: item?.license_type ? [item.license_type] : [],
|
||
document_number: item?.document_number || "",
|
||
issuer: item?.issuer || "",
|
||
economic_code: item?.economic_code || "",
|
||
registration_number: item?.registration_number || "",
|
||
ownership_type: item?.ownership_type ? [item.ownership_type] : [],
|
||
unit_type: item?.unit_type ? [item.unit_type] : [],
|
||
description: item?.description || "",
|
||
inspectors: defaultInspectorKeys,
|
||
infractions: item?.infractions || [],
|
||
violation_amount: item?.violation_amount || "",
|
||
plaintiff_damage: item?.plaintiff_damage || "",
|
||
},
|
||
});
|
||
|
||
const { fields, append, remove } = useFieldArray({
|
||
control,
|
||
name: "infractions",
|
||
});
|
||
|
||
const watchedInfractions = watch("infractions");
|
||
|
||
useEffect(() => {
|
||
if (isEdit && item?.inspectors && usersList?.length) {
|
||
const inspectorStrings = item.inspectors.map((insp: any) => {
|
||
if (typeof insp === "string") return insp;
|
||
const user = usersList.find((u: any) => u.fullname === insp.fullname);
|
||
return user ? `${insp.fullname} / ${user.mobile}` : insp.fullname;
|
||
});
|
||
setValue("inspectors", inspectorStrings);
|
||
}
|
||
}, [item, isEdit, usersList, setValue]);
|
||
|
||
const submitInspectionMutation = useApiMutation({
|
||
api: "inspections",
|
||
method: "post",
|
||
});
|
||
|
||
// Create update mutation with the correct URL including the ID
|
||
const updateInspectionMutation = useApiMutation({
|
||
api: inspectId ? `inspections/${inspectId}` : "inspections",
|
||
method: "put",
|
||
});
|
||
|
||
const onSubmit = async (data: FormValues) => {
|
||
try {
|
||
const payload = {
|
||
user_id: profile?._id || profile?.Id,
|
||
place_key: item?.key,
|
||
province: profile?.province,
|
||
license_type: String(data.license_type[0]),
|
||
document_number: data.document_number,
|
||
issuer: data.issuer,
|
||
economic_code: data.economic_code,
|
||
registration_number: data.registration_number,
|
||
ownership_type: String(data.ownership_type[0]),
|
||
unit_type: String(data.unit_type[0]),
|
||
description: data.description || "",
|
||
infractions: data.infractions || [],
|
||
inspectors: (data.inspectors || []).map((inspector) => {
|
||
const fullname = String(inspector).split(" / ")[0];
|
||
return { fullname };
|
||
}),
|
||
violation_amount: data.violation_amount || "0",
|
||
plaintiff_damage: data.plaintiff_damage || "0",
|
||
};
|
||
|
||
if (isEdit && inspectId) {
|
||
await updateInspectionMutation.mutateAsync(payload);
|
||
showToast("ویرایش بازرسی با موفقیت ثبت شد", "success");
|
||
handleUpdate?.();
|
||
} else {
|
||
await submitInspectionMutation.mutateAsync(payload);
|
||
showToast("بازرسی با موفقیت ثبت شد", "success");
|
||
handleUpdate?.();
|
||
}
|
||
|
||
closeDrawer();
|
||
} catch (error: any) {
|
||
console.error("Error submitting inspection:", error);
|
||
showToast(error?.response?.data?.message || "بازرسی ثبت نشد!", "error");
|
||
}
|
||
};
|
||
|
||
return (
|
||
<form onSubmit={handleSubmit(onSubmit)}>
|
||
<Grid container column className="gap-2">
|
||
<Controller
|
||
name="license_type"
|
||
control={control}
|
||
render={({ field }) => (
|
||
<AutoComplete
|
||
data={licenseTypeOptions}
|
||
selectedKeys={
|
||
(Array.isArray(field.value)
|
||
? field.value
|
||
: field.value
|
||
? [field.value]
|
||
: []) as (string | number)[]
|
||
}
|
||
onChange={(keys) => setValue("license_type", keys as any)}
|
||
title="نوع پروانه کسب"
|
||
error={!!errors.license_type}
|
||
helperText={errors.license_type?.message}
|
||
/>
|
||
)}
|
||
/>
|
||
|
||
<Controller
|
||
name="issuer"
|
||
control={control}
|
||
render={({ field }) => (
|
||
<Textfield
|
||
placeholder="صادر کننده پروانه"
|
||
value={field.value}
|
||
onChange={field.onChange}
|
||
error={!!errors.issuer}
|
||
helperText={errors.issuer?.message}
|
||
fullWidth
|
||
/>
|
||
)}
|
||
/>
|
||
|
||
<Controller
|
||
name="document_number"
|
||
control={control}
|
||
render={({ field }) => (
|
||
<Textfield
|
||
placeholder="شماره پروانه یا مجوز"
|
||
value={field.value}
|
||
onChange={field.onChange}
|
||
error={!!errors.document_number}
|
||
helperText={errors.document_number?.message}
|
||
fullWidth
|
||
/>
|
||
)}
|
||
/>
|
||
|
||
<Controller
|
||
name="registration_number"
|
||
control={control}
|
||
render={({ field }) => (
|
||
<Textfield
|
||
placeholder="شماره ثبت"
|
||
value={field.value}
|
||
onChange={field.onChange}
|
||
error={!!errors.registration_number}
|
||
helperText={errors.registration_number?.message}
|
||
fullWidth
|
||
/>
|
||
)}
|
||
/>
|
||
|
||
<Controller
|
||
name="economic_code"
|
||
control={control}
|
||
render={({ field }) => (
|
||
<Textfield
|
||
placeholder="کد اقتصادی"
|
||
value={field.value}
|
||
onChange={field.onChange}
|
||
error={!!errors.economic_code}
|
||
helperText={errors.economic_code?.message}
|
||
fullWidth
|
||
/>
|
||
)}
|
||
/>
|
||
|
||
<Controller
|
||
name="ownership_type"
|
||
control={control}
|
||
render={({ field }) => (
|
||
<AutoComplete
|
||
data={ownershipTypeOptions}
|
||
selectedKeys={
|
||
(Array.isArray(field.value)
|
||
? field.value
|
||
: field.value
|
||
? [field.value]
|
||
: []) as (string | number)[]
|
||
}
|
||
onChange={(keys) => setValue("ownership_type", keys as any)}
|
||
title="نوع مالکیت"
|
||
error={!!errors.ownership_type}
|
||
helperText={errors.ownership_type?.message}
|
||
/>
|
||
)}
|
||
/>
|
||
|
||
<Controller
|
||
name="unit_type"
|
||
control={control}
|
||
render={({ field }) => (
|
||
<AutoComplete
|
||
data={unitTypeOptions}
|
||
selectedKeys={
|
||
(Array.isArray(field.value)
|
||
? field.value
|
||
: field.value
|
||
? [field.value]
|
||
: []) as (string | number)[]
|
||
}
|
||
onChange={(keys) => setValue("unit_type", keys as any)}
|
||
title="نوع واحد"
|
||
error={!!errors.unit_type}
|
||
helperText={errors.unit_type?.message}
|
||
/>
|
||
)}
|
||
/>
|
||
|
||
<Controller
|
||
name="inspectors"
|
||
control={control}
|
||
render={({ field }) => (
|
||
<AutoComplete
|
||
data={inspectorOptions}
|
||
multiselect
|
||
selectedKeys={
|
||
(Array.isArray(field.value) ? field.value : []) as (
|
||
| string
|
||
| number
|
||
)[]
|
||
}
|
||
onChange={(keys) => setValue("inspectors", keys as any)}
|
||
title="بازرسان همراه"
|
||
error={!!errors.inspectors}
|
||
helperText={errors.inspectors?.message}
|
||
/>
|
||
)}
|
||
/>
|
||
|
||
<Grid container className="items-center justify-between gap-2">
|
||
<Typography variant="body1" className="font-semibold">
|
||
تخلفات
|
||
</Typography>
|
||
<Button
|
||
type="button"
|
||
size="small"
|
||
onClick={() => {
|
||
if (fields.length < 5) {
|
||
append({ title: "", description: "" });
|
||
} else {
|
||
showToast("حداکثر 5 تخلف میتوانید اضافه کنید", "error");
|
||
}
|
||
}}
|
||
className="flex items-center gap-2"
|
||
disabled={fields.length >= 5}
|
||
>
|
||
افزودن تخلف
|
||
</Button>
|
||
</Grid>
|
||
|
||
{fields.map((field, index) => (
|
||
<Grid
|
||
key={field.id}
|
||
container
|
||
column
|
||
className="gap-2 p-2 border border-gray-200 dark:border-dark-600 rounded-lg bg-gray-50 dark:bg-dark-700/50"
|
||
>
|
||
<Grid container className="items-center justify-between gap-2">
|
||
<Typography
|
||
variant="body2"
|
||
className="text-gray-700 dark:text-gray-300 font-medium"
|
||
>
|
||
تخلف {index + 1}
|
||
</Typography>
|
||
<button
|
||
type="button"
|
||
onClick={() => remove(index)}
|
||
className="p-1.5 text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-md transition-colors"
|
||
aria-label="حذف تخلف"
|
||
>
|
||
<TrashIcon className="w-5 h-5" />
|
||
</button>
|
||
</Grid>
|
||
<Controller
|
||
name={`infractions.${index}.title`}
|
||
control={control}
|
||
render={({ field }) => (
|
||
<Textfield
|
||
placeholder={`عنوان تخلف ${index + 1}`}
|
||
value={field.value}
|
||
onChange={field.onChange}
|
||
error={!!errors.infractions?.[index]?.title}
|
||
helperText={errors.infractions?.[index]?.title?.message}
|
||
fullWidth
|
||
/>
|
||
)}
|
||
/>
|
||
<Controller
|
||
name={`infractions.${index}.description`}
|
||
control={control}
|
||
render={({ field }) => (
|
||
<Grid container column className="gap-1">
|
||
<textarea
|
||
placeholder={`توضیحات تخلف ${index + 1}`}
|
||
value={field.value}
|
||
onChange={field.onChange}
|
||
className="w-full px-4 py-2 border border-gray-300 dark:border-dark-600 rounded-lg bg-white dark:bg-dark-800 text-gray-900 dark:text-gray-100 min-h-[80px] focus:outline-none focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400 transition-all"
|
||
rows={3}
|
||
/>
|
||
{errors.infractions?.[index]?.description && (
|
||
<p className="text-red-400 text-xs">
|
||
{errors.infractions[index]?.description?.message}
|
||
</p>
|
||
)}
|
||
</Grid>
|
||
)}
|
||
/>
|
||
</Grid>
|
||
))}
|
||
|
||
{watchedInfractions && watchedInfractions.length > 0 && (
|
||
<Grid container column className="gap-2">
|
||
<Controller
|
||
name="violation_amount"
|
||
control={control}
|
||
render={({ field }) => (
|
||
<Textfield
|
||
placeholder="جمع کل ارزش ریالی تخلف"
|
||
value={field.value}
|
||
onChange={field.onChange}
|
||
isNumber
|
||
fullWidth
|
||
error={!!errors.violation_amount}
|
||
helperText={errors.violation_amount?.message}
|
||
/>
|
||
)}
|
||
/>
|
||
|
||
<Controller
|
||
name="plaintiff_damage"
|
||
control={control}
|
||
render={({ field }) => (
|
||
<Textfield
|
||
placeholder="جمع کل خسارت وارده به شاکی"
|
||
value={field.value}
|
||
onChange={field.onChange}
|
||
isNumber
|
||
fullWidth
|
||
error={!!errors.plaintiff_damage}
|
||
helperText={errors.plaintiff_damage?.message}
|
||
/>
|
||
)}
|
||
/>
|
||
</Grid>
|
||
)}
|
||
|
||
<Controller
|
||
name="description"
|
||
control={control}
|
||
render={({ field }) => (
|
||
<Textfield
|
||
placeholder="توضیحات..."
|
||
value={field.value || ""}
|
||
onChange={field.onChange}
|
||
fullWidth
|
||
error={!!errors.description}
|
||
helperText={errors.description?.message}
|
||
/>
|
||
)}
|
||
/>
|
||
|
||
<Button
|
||
type="submit"
|
||
variant="submit"
|
||
fullWidth
|
||
disabled={
|
||
isSubmitting ||
|
||
submitInspectionMutation.isPending ||
|
||
updateInspectionMutation.isPending
|
||
}
|
||
className="mt-4"
|
||
>
|
||
{isEdit ? "ویرایش بازرسی" : "ثبت بازرسی"}
|
||
</Button>
|
||
</Grid>
|
||
</form>
|
||
);
|
||
};
|
||
|
||
export default MainSubmitInspection;
|