first commit
This commit is contained in:
17
src/partials/main/MainInfractions.tsx
Normal file
17
src/partials/main/MainInfractions.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import Typography from "../../components/Typography/Typography";
|
||||
import { Grid } from "../../components/Grid/Grid";
|
||||
|
||||
export const MainInfractions = ({
|
||||
data,
|
||||
handleUpdate,
|
||||
}: {
|
||||
data: any;
|
||||
handleUpdate: () => void;
|
||||
}) => {
|
||||
console.log(data, handleUpdate);
|
||||
return (
|
||||
<Grid container column>
|
||||
<Typography variant="body1">این بخش در دست توسعه است</Typography>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
949
src/partials/main/MainPlaceInfo.tsx
Normal file
949
src/partials/main/MainPlaceInfo.tsx
Normal file
@@ -0,0 +1,949 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Typography from "../../components/Typography/Typography";
|
||||
import { Grid } from "../../components/Grid/Grid";
|
||||
import Button from "../../components/Button/Button";
|
||||
import { useApiMutation } from "../../utils/useApiRequest";
|
||||
import { useDrawerStore } from "../../context/zustand-store/appStore";
|
||||
import MainSubmitInspection from "./MainSubmitInspection";
|
||||
import { motion, AnimatePresence, Variants } from "framer-motion";
|
||||
import {
|
||||
UserIcon,
|
||||
IdentificationIcon,
|
||||
PhoneIcon,
|
||||
CalendarIcon,
|
||||
CreditCardIcon,
|
||||
InformationCircleIcon,
|
||||
MapPinIcon,
|
||||
BuildingStorefrontIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
|
||||
interface MainPlaceInfoProps {
|
||||
item: any;
|
||||
userlastseen?: string;
|
||||
isPoultry?: boolean;
|
||||
isLivestock?: boolean;
|
||||
}
|
||||
|
||||
interface PersonData {
|
||||
_id: string;
|
||||
nationalcode: string;
|
||||
name: string;
|
||||
family: string;
|
||||
father: string;
|
||||
birthdate: string;
|
||||
mobile: string[];
|
||||
info: string[];
|
||||
card: string[];
|
||||
}
|
||||
|
||||
interface ApiResponse {
|
||||
data: PersonData[];
|
||||
count: number;
|
||||
}
|
||||
|
||||
interface PersonRegistryData {
|
||||
registeryOfficePersonId: number;
|
||||
nationalCode: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
fatherName: string;
|
||||
supervisorNationalCode: string | null;
|
||||
gender: boolean;
|
||||
identityNo: string;
|
||||
identitySeries: string;
|
||||
identitySerial: string;
|
||||
birthDate: string;
|
||||
birthDateUnix: number;
|
||||
isLive: boolean;
|
||||
deathDate: string | null;
|
||||
city: string;
|
||||
vilage: string | null;
|
||||
town: string | null;
|
||||
errorCode: number;
|
||||
errorDescription: string | null;
|
||||
createdOn: string;
|
||||
errorCodeSpecified: boolean | null;
|
||||
birthDateDay: number;
|
||||
birthDateMonth: number;
|
||||
birthDateYear: number;
|
||||
}
|
||||
|
||||
interface PersonRegistryResponse {
|
||||
status: boolean;
|
||||
statusCode: number;
|
||||
data: PersonRegistryData;
|
||||
apiLogId: string;
|
||||
}
|
||||
|
||||
interface UnitRegistryData {
|
||||
counter: number;
|
||||
serviceType: number;
|
||||
title: string;
|
||||
isicname: string;
|
||||
fullname: string;
|
||||
state: string;
|
||||
city: string;
|
||||
address: string;
|
||||
licenseNumber: string;
|
||||
licenseExpireDate: string;
|
||||
licenseType: string;
|
||||
licenseStatus: string;
|
||||
layerTwo: {
|
||||
phonenumber: string;
|
||||
unionName: string;
|
||||
postalcode: string;
|
||||
nationalcode: string;
|
||||
mobilenumber: string;
|
||||
nationalId: string | null;
|
||||
corporationName: string | null;
|
||||
licenseIssueDate: string;
|
||||
jobType: string;
|
||||
hasPartner: string;
|
||||
hasSteward: string;
|
||||
isForeigner: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface UnitRegistryResponse {
|
||||
status?: boolean;
|
||||
statusCode?: number;
|
||||
data?: UnitRegistryData[];
|
||||
}
|
||||
|
||||
const MainPlaceInfo: React.FC<MainPlaceInfoProps> = ({
|
||||
item,
|
||||
userlastseen,
|
||||
}) => {
|
||||
const [personData, setPersonData] = useState<PersonData[]>([]);
|
||||
const [isLoadingPersonInfo, setIsLoadingPersonInfo] = useState(false);
|
||||
const [personRegistryData, setPersonRegistryData] =
|
||||
useState<PersonRegistryData | null>(null);
|
||||
const [unitRegistryData, setUnitRegistryData] = useState<UnitRegistryData[]>(
|
||||
[]
|
||||
);
|
||||
const [isLoadingRegistry, setIsLoadingRegistry] = useState(false);
|
||||
const { openDrawer } = useDrawerStore();
|
||||
|
||||
const personInfoMutation = useApiMutation<ApiResponse>({
|
||||
api: "people_info",
|
||||
method: "get",
|
||||
});
|
||||
|
||||
const personRegistryMutation = useApiMutation<PersonRegistryResponse>({
|
||||
api: "national-documents",
|
||||
method: "get",
|
||||
});
|
||||
|
||||
const unitRegistryMutation = useApiMutation<UnitRegistryResponse>({
|
||||
api: "national-documents",
|
||||
method: "get",
|
||||
});
|
||||
|
||||
// Auto-fetch person info and registry data if national_id has 10 digits
|
||||
useEffect(() => {
|
||||
const nationalId = item?.national_id;
|
||||
if (
|
||||
nationalId &&
|
||||
typeof nationalId === "string" &&
|
||||
nationalId.length === 10
|
||||
) {
|
||||
fetchPersonInfo(nationalId);
|
||||
fetchRegistryData(nationalId);
|
||||
}
|
||||
}, [item?.national_id]);
|
||||
|
||||
const fetchPersonInfo = async (nationalId: string) => {
|
||||
setIsLoadingPersonInfo(true);
|
||||
try {
|
||||
const result = await personInfoMutation.mutateAsync({
|
||||
searchfield: "nationalcode",
|
||||
value: nationalId,
|
||||
});
|
||||
|
||||
if (result?.data && result.data.length > 0) {
|
||||
setPersonData(result.data);
|
||||
} else {
|
||||
setPersonData([]);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("Person info error:", error);
|
||||
setPersonData([]);
|
||||
} finally {
|
||||
setIsLoadingPersonInfo(false);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchRegistryData = async (nationalCode: string) => {
|
||||
setIsLoadingRegistry(true);
|
||||
try {
|
||||
// Fetch person registry
|
||||
try {
|
||||
const personResult = await personRegistryMutation.mutateAsync({
|
||||
type: "person",
|
||||
info: nationalCode,
|
||||
});
|
||||
if (personResult?.status && personResult?.data) {
|
||||
setPersonRegistryData(personResult.data);
|
||||
} else {
|
||||
setPersonRegistryData(null);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("Person registry error:", error);
|
||||
setPersonRegistryData(null);
|
||||
}
|
||||
|
||||
// Fetch unit registry
|
||||
try {
|
||||
const unitResult = await unitRegistryMutation.mutateAsync({
|
||||
type: "guild",
|
||||
info: nationalCode,
|
||||
});
|
||||
const unitsCandidate =
|
||||
unitResult?.data ?? (Array.isArray(unitResult) ? unitResult : []);
|
||||
setUnitRegistryData(
|
||||
Array.isArray(unitsCandidate) ? unitsCandidate : []
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error("Unit registry error:", error);
|
||||
setUnitRegistryData([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Registry fetch error:", error);
|
||||
} finally {
|
||||
setIsLoadingRegistry(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenInspection = () => {
|
||||
openDrawer({
|
||||
title: "ثبت بازرسی",
|
||||
content: <MainSubmitInspection item={item} />,
|
||||
direction: "left",
|
||||
});
|
||||
};
|
||||
|
||||
const cardVariants: Variants = {
|
||||
hidden: { opacity: 0, y: 20, scale: 0.95 },
|
||||
visible: (i: number) => ({
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
scale: 1,
|
||||
transition: {
|
||||
delay: i * 0.1,
|
||||
duration: 0.3,
|
||||
ease: "easeOut",
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const getDisplayName = (person: PersonData) => {
|
||||
if (person.family && person.name.includes(person.family)) {
|
||||
return person.name;
|
||||
}
|
||||
return person.family ? `${person.name} ${person.family}` : person.name;
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid container column className="gap-4 p-2">
|
||||
{/* Place Basic Information */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4 }}
|
||||
className="w-full"
|
||||
>
|
||||
<div className="bg-white dark:bg-dark-800 rounded-xl shadow-lg border border-gray-200 dark:border-dark-600 p-6">
|
||||
<Grid container column className="gap-4">
|
||||
<div className="flex items-start justify-between pb-4 border-b border-gray-200 dark:border-dark-600">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-3 bg-primary-100 dark:bg-primary-900/30 rounded-lg">
|
||||
<BuildingStorefrontIcon className="w-6 h-6 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<div>
|
||||
<Typography
|
||||
variant="h6"
|
||||
className="text-gray-900 dark:text-gray-100 font-bold"
|
||||
>
|
||||
{item?.guilds_name || "نامشخص"}
|
||||
</Typography>
|
||||
{userlastseen && (
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-600 dark:text-gray-400 mt-1"
|
||||
>
|
||||
آخرین بازدید: {userlastseen}
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{item?.national_id && (
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-dark-700 rounded-lg">
|
||||
<CreditCardIcon className="w-5 h-5 text-gray-600 dark:text-gray-400 shrink-0" />
|
||||
<div>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-500 dark:text-gray-400 text-xs"
|
||||
>
|
||||
کد ملی
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className="text-gray-900 dark:text-gray-100 font-medium"
|
||||
>
|
||||
{item.national_id}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{item?.lat && item?.lng && (
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-dark-700 rounded-lg">
|
||||
<MapPinIcon className="w-5 h-5 text-gray-600 dark:text-gray-400 shrink-0" />
|
||||
<div>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-500 dark:text-gray-400 text-xs"
|
||||
>
|
||||
موقعیت
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className="text-gray-900 dark:text-gray-100 font-medium"
|
||||
>
|
||||
{item.lat.toFixed(6)}, {item.lng.toFixed(6)}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Submit Inspection Button */}
|
||||
<div className="pt-2">
|
||||
<Button
|
||||
onClick={handleOpenInspection}
|
||||
variant="submit"
|
||||
fullWidth
|
||||
className="mt-2"
|
||||
>
|
||||
ثبت بازرسی
|
||||
</Button>
|
||||
</div>
|
||||
</Grid>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Person Info Data */}
|
||||
<AnimatePresence mode="popLayout">
|
||||
{isLoadingPersonInfo && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="w-full"
|
||||
>
|
||||
<div className="bg-white dark:bg-dark-800 rounded-xl shadow-lg border border-gray-200 dark:border-dark-600 p-6">
|
||||
<Typography
|
||||
variant="body1"
|
||||
className="text-gray-700 dark:text-gray-300 text-center"
|
||||
>
|
||||
در حال بارگذاری اطلاعات شخص...
|
||||
</Typography>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{personData.length > 0 && !isLoadingPersonInfo && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="w-full"
|
||||
>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className="mb-4 text-gray-700 dark:text-gray-300 font-semibold"
|
||||
>
|
||||
اطلاعات شخص ({personData.length})
|
||||
</Typography>
|
||||
|
||||
<Grid container column className="gap-4">
|
||||
{personData.map((person, index) => (
|
||||
<motion.div
|
||||
key={person._id}
|
||||
custom={index}
|
||||
variants={cardVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
exit="exit"
|
||||
className="w-full"
|
||||
>
|
||||
<div className="bg-white dark:bg-dark-800 rounded-xl shadow-lg border border-gray-200 dark:border-dark-600 p-6 hover:shadow-xl transition-shadow">
|
||||
<Grid container column className="gap-4">
|
||||
<div className="flex items-start justify-between pb-4 border-b border-gray-200 dark:border-dark-600">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-3 bg-primary-100 dark:bg-primary-900/30 rounded-lg">
|
||||
<UserIcon className="w-6 h-6 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<div>
|
||||
<Typography
|
||||
variant="h6"
|
||||
className="text-gray-900 dark:text-gray-100 font-bold"
|
||||
>
|
||||
{getDisplayName(person)}
|
||||
</Typography>
|
||||
{person.father && (
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-600 dark:text-gray-400 mt-1"
|
||||
>
|
||||
فرزند {person.father}
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-dark-700 rounded-lg">
|
||||
<IdentificationIcon className="w-5 h-5 text-gray-600 dark:text-gray-400 shrink-0" />
|
||||
<div>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-500 dark:text-gray-400 text-xs"
|
||||
>
|
||||
کد ملی
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className="text-gray-900 dark:text-gray-100 font-medium"
|
||||
>
|
||||
{person.nationalcode}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{person.birthdate && (
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-dark-700 rounded-lg">
|
||||
<CalendarIcon className="w-5 h-5 text-gray-600 dark:text-gray-400 shrink-0" />
|
||||
<div>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-500 dark:text-gray-400 text-xs"
|
||||
>
|
||||
تاریخ تولد
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className="text-gray-900 dark:text-gray-100 font-medium"
|
||||
>
|
||||
{person.birthdate}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{person.mobile && person.mobile.length > 0 && (
|
||||
<div className="flex items-start gap-3 p-3 bg-gray-50 dark:bg-dark-700 rounded-lg">
|
||||
<PhoneIcon className="w-5 h-5 text-gray-600 dark:text-gray-400 shrink-0 mt-0.5" />
|
||||
<div className="flex-1">
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-500 dark:text-gray-400 text-xs mb-1"
|
||||
>
|
||||
شماره تلفن
|
||||
</Typography>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{person.mobile.map((mobile, idx) => (
|
||||
<span
|
||||
key={idx}
|
||||
className="px-2 py-1 bg-primary-50 dark:bg-primary-900/20 text-primary-700 dark:text-primary-300 rounded text-sm font-medium"
|
||||
>
|
||||
{mobile}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{person.card && person.card.length > 0 && (
|
||||
<div className="pt-2">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<CreditCardIcon className="w-5 h-5 text-gray-600 dark:text-gray-400" />
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-700 dark:text-gray-300 font-semibold"
|
||||
>
|
||||
کارت های بانکی
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{person.card.map((card, idx) => (
|
||||
<motion.span
|
||||
key={idx}
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ delay: idx * 0.05 }}
|
||||
className="px-3 py-2 bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300 rounded-lg text-sm font-mono border border-blue-200 dark:border-blue-800"
|
||||
>
|
||||
{card}
|
||||
</motion.span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{person.info && person.info.length > 0 && (
|
||||
<div className="pt-2">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<InformationCircleIcon className="w-5 h-5 text-gray-600 dark:text-gray-400" />
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-700 dark:text-gray-300 font-semibold"
|
||||
>
|
||||
اطلاعات جمع آوری شده توسط هوش مصنوعی
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{person.info.map((info, idx) => (
|
||||
<motion.div
|
||||
key={idx}
|
||||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: idx * 0.05 }}
|
||||
className="p-3 bg-gray-50 dark:bg-dark-700 rounded-lg border-r-4 border-primary-500"
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
{info}
|
||||
</Typography>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Grid>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</Grid>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Person Registry Data */}
|
||||
<AnimatePresence mode="popLayout">
|
||||
{isLoadingRegistry && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="w-full"
|
||||
>
|
||||
<div className="bg-white dark:bg-dark-800 rounded-xl shadow-lg border border-gray-200 dark:border-dark-600 p-6">
|
||||
<Typography
|
||||
variant="body1"
|
||||
className="text-gray-700 dark:text-gray-300 text-center"
|
||||
>
|
||||
در حال بارگذاری اطلاعات ثبت احوال...
|
||||
</Typography>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{personRegistryData && !isLoadingRegistry && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="w-full"
|
||||
>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className="mb-4 text-gray-700 dark:text-gray-300 font-semibold"
|
||||
>
|
||||
استعلام از ثبت احوال
|
||||
</Typography>
|
||||
<div className="bg-white dark:bg-dark-800 rounded-xl shadow-lg border border-gray-200 dark:border-dark-600 p-6">
|
||||
<Grid container column className="gap-4">
|
||||
<div className="flex items-start justify-between pb-4 border-b border-gray-200 dark:border-dark-600">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-3 bg-primary-100 dark:bg-primary-900/30 rounded-lg">
|
||||
<UserIcon className="w-6 h-6 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<div>
|
||||
<Typography
|
||||
variant="h6"
|
||||
className="text-gray-900 dark:text-gray-100 font-bold"
|
||||
>
|
||||
{personRegistryData.firstName}{" "}
|
||||
{personRegistryData.lastName}
|
||||
</Typography>
|
||||
{personRegistryData.fatherName && (
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-600 dark:text-gray-400 mt-1"
|
||||
>
|
||||
فرزند {personRegistryData.fatherName}
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-dark-700 rounded-lg">
|
||||
<IdentificationIcon className="w-5 h-5 text-gray-600 dark:text-gray-400 shrink-0" />
|
||||
<div>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-500 dark:text-gray-400 text-xs"
|
||||
>
|
||||
کد ملی
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className="text-gray-900 dark:text-gray-100 font-medium"
|
||||
>
|
||||
{personRegistryData.nationalCode}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{personRegistryData.birthDate && (
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-dark-700 rounded-lg">
|
||||
<CalendarIcon className="w-5 h-5 text-gray-600 dark:text-gray-400 shrink-0" />
|
||||
<div>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-500 dark:text-gray-400 text-xs"
|
||||
>
|
||||
تاریخ تولد
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className="text-gray-900 dark:text-gray-100 font-medium"
|
||||
>
|
||||
{personRegistryData.birthDate}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{personRegistryData.city && (
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-dark-700 rounded-lg">
|
||||
<InformationCircleIcon className="w-5 h-5 text-gray-600 dark:text-gray-400 shrink-0" />
|
||||
<div>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-500 dark:text-gray-400 text-xs"
|
||||
>
|
||||
شهر
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className="text-gray-900 dark:text-gray-100 font-medium"
|
||||
>
|
||||
{personRegistryData.city}
|
||||
{personRegistryData.vilage &&
|
||||
` - ${personRegistryData.vilage}`}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-dark-700 rounded-lg">
|
||||
<InformationCircleIcon className="w-5 h-5 text-gray-600 dark:text-gray-400 shrink-0" />
|
||||
<div>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-500 dark:text-gray-400 text-xs"
|
||||
>
|
||||
جنسیت
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className="text-gray-900 dark:text-gray-100 font-medium"
|
||||
>
|
||||
{personRegistryData.gender ? "مرد" : "زن"}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-dark-700 rounded-lg">
|
||||
<InformationCircleIcon className="w-5 h-5 text-gray-600 dark:text-gray-400 shrink-0" />
|
||||
<div>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-500 dark:text-gray-400 text-xs"
|
||||
>
|
||||
وضعیت حیات
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className="text-gray-900 dark:text-gray-100 font-medium"
|
||||
>
|
||||
{personRegistryData.isLive ? "زنده" : "فوت شده"}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Unit Registry Data */}
|
||||
<AnimatePresence mode="popLayout">
|
||||
{unitRegistryData.length > 0 && !isLoadingRegistry && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="w-full"
|
||||
>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className="mb-4 text-gray-700 dark:text-gray-300 font-semibold"
|
||||
>
|
||||
استعلام واحد صنفی ({unitRegistryData.length})
|
||||
</Typography>
|
||||
<Grid container column className="gap-4">
|
||||
{unitRegistryData.map((unit, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
custom={index}
|
||||
variants={cardVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
className="w-full"
|
||||
>
|
||||
<div className="bg-white dark:bg-dark-800 rounded-xl shadow-lg border border-gray-200 dark:border-dark-600 p-6 hover:shadow-xl transition-shadow">
|
||||
<Grid container column className="gap-4">
|
||||
<div className="flex items-start justify-between pb-4 border-b border-gray-200 dark:border-dark-600">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-3 bg-primary-100 dark:bg-primary-900/30 rounded-lg">
|
||||
<CreditCardIcon className="w-6 h-6 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<div>
|
||||
<Typography
|
||||
variant="h6"
|
||||
className="text-gray-900 dark:text-gray-100 font-bold"
|
||||
>
|
||||
{unit.title}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-600 dark:text-gray-400 mt-1"
|
||||
>
|
||||
{unit.fullname}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-dark-700 rounded-lg">
|
||||
<IdentificationIcon className="w-5 h-5 text-gray-600 dark:text-gray-400 shrink-0" />
|
||||
<div>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-500 dark:text-gray-400 text-xs"
|
||||
>
|
||||
کد ملی
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className="text-gray-900 dark:text-gray-100 font-medium"
|
||||
>
|
||||
{unit.layerTwo.nationalcode}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-dark-700 rounded-lg">
|
||||
<PhoneIcon className="w-5 h-5 text-gray-600 dark:text-gray-400 shrink-0" />
|
||||
<div>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-500 dark:text-gray-400 text-xs"
|
||||
>
|
||||
شماره موبایل
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className="text-gray-900 dark:text-gray-100 font-medium"
|
||||
>
|
||||
{unit.layerTwo.mobilenumber}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-dark-700 rounded-lg">
|
||||
<PhoneIcon className="w-5 h-5 text-gray-600 dark:text-gray-400 shrink-0" />
|
||||
<div>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-500 dark:text-gray-400 text-xs"
|
||||
>
|
||||
شماره تلفن
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className="text-gray-900 dark:text-gray-100 font-medium"
|
||||
>
|
||||
{unit.layerTwo.phonenumber}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-dark-700 rounded-lg">
|
||||
<CreditCardIcon className="w-5 h-5 text-gray-600 dark:text-gray-400 shrink-0" />
|
||||
<div>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-500 dark:text-gray-400 text-xs"
|
||||
>
|
||||
شماره پروانه
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className="text-gray-900 dark:text-gray-100 font-medium"
|
||||
>
|
||||
{unit.licenseNumber}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-dark-700 rounded-lg">
|
||||
<InformationCircleIcon className="w-5 h-5 text-gray-600 dark:text-gray-400 shrink-0" />
|
||||
<div>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-500 dark:text-gray-400 text-xs"
|
||||
>
|
||||
نوع پروانه
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className="text-gray-900 dark:text-gray-100 font-medium"
|
||||
>
|
||||
{unit.licenseType}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-dark-700 rounded-lg">
|
||||
<InformationCircleIcon className="w-5 h-5 text-gray-600 dark:text-gray-400 shrink-0" />
|
||||
<div>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-500 dark:text-gray-400 text-xs"
|
||||
>
|
||||
وضعیت پروانه
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className="text-gray-900 dark:text-gray-100 font-medium"
|
||||
>
|
||||
{unit.licenseStatus}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-dark-700 rounded-lg">
|
||||
<CalendarIcon className="w-5 h-5 text-gray-600 dark:text-gray-400 shrink-0" />
|
||||
<div>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-500 dark:text-gray-400 text-xs"
|
||||
>
|
||||
تاریخ انقضا
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className="text-gray-900 dark:text-gray-100 font-medium"
|
||||
>
|
||||
{unit.licenseExpireDate}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-dark-700 rounded-lg">
|
||||
<InformationCircleIcon className="w-5 h-5 text-gray-600 dark:text-gray-400 shrink-0" />
|
||||
<div>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-500 dark:text-gray-400 text-xs"
|
||||
>
|
||||
شهر / استان
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className="text-gray-900 dark:text-gray-100 font-medium"
|
||||
>
|
||||
{unit.city} - {unit.state}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{unit.address && (
|
||||
<div className="pt-2">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<InformationCircleIcon className="w-5 h-5 text-gray-600 dark:text-gray-400" />
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-700 dark:text-gray-300 font-semibold"
|
||||
>
|
||||
آدرس
|
||||
</Typography>
|
||||
</div>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-700 dark:text-gray-300 p-3 bg-gray-50 dark:bg-dark-700 rounded-lg"
|
||||
>
|
||||
{unit.address}
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{unit.layerTwo.unionName && (
|
||||
<div className="pt-2">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<InformationCircleIcon className="w-5 h-5 text-gray-600 dark:text-gray-400" />
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-700 dark:text-gray-300 font-semibold"
|
||||
>
|
||||
نام اتحادیه
|
||||
</Typography>
|
||||
</div>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-700 dark:text-gray-300 p-3 bg-gray-50 dark:bg-dark-700 rounded-lg"
|
||||
>
|
||||
{unit.layerTwo.unionName}
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
</Grid>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</Grid>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default MainPlaceInfo;
|
||||
513
src/partials/main/MainSubmitInspection.tsx
Normal file
513
src/partials/main/MainSubmitInspection.tsx
Normal file
@@ -0,0 +1,513 @@
|
||||
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 inspectorOptions = useMemo(() => {
|
||||
if (!usersData?.data) return [];
|
||||
return usersData.data
|
||||
.filter(
|
||||
(user: any) =>
|
||||
user.province === profile?.province &&
|
||||
!user?.permissions?.includes("admin")
|
||||
)
|
||||
.map((user: any) => ({
|
||||
key: `${user.fullname} / ${user.mobile}`,
|
||||
value: `${user.fullname} / ${user.mobile}`,
|
||||
}));
|
||||
}, [usersData, profile]);
|
||||
|
||||
const defaultInspectorKeys = useMemo(() => {
|
||||
if (!isEdit || !item?.inspectors) return [];
|
||||
return item.inspectors.map((insp: any) => {
|
||||
if (typeof insp === "string") return insp;
|
||||
const user = usersData?.data?.find(
|
||||
(u: any) => u.fullname === insp.fullname
|
||||
);
|
||||
return user ? `${insp.fullname} / ${user.mobile}` : insp.fullname;
|
||||
});
|
||||
}, [item, isEdit, usersData]);
|
||||
|
||||
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 && usersData?.data) {
|
||||
const inspectorStrings = item.inspectors.map((insp: any) => {
|
||||
if (typeof insp === "string") return insp;
|
||||
const user = usersData.data.find(
|
||||
(u: any) => u.fullname === insp.fullname
|
||||
);
|
||||
return user ? `${insp.fullname} / ${user.mobile}` : insp.fullname;
|
||||
});
|
||||
setValue("inspectors", inspectorStrings);
|
||||
}
|
||||
}, [item, isEdit, usersData, 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;
|
||||
Reference in New Issue
Block a user