Files
Rasadyar_Inspection_System/src/partials/main/MainSubmitInspection.tsx

510 lines
17 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;