feat: postal inquiry
This commit is contained in:
290
src/Pages/PostcodeInquiry.tsx
Normal file
290
src/Pages/PostcodeInquiry.tsx
Normal file
@@ -0,0 +1,290 @@
|
||||
import React, { useState } from "react";
|
||||
import { Grid } from "../components/Grid/Grid";
|
||||
import Textfield from "../components/Textfeild/Textfeild";
|
||||
import { useApiMutation } from "../utils/useApiRequest";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import Typography from "../components/Typography/Typography";
|
||||
import { useToast } from "../hooks/useToast";
|
||||
import {
|
||||
MapPinIcon,
|
||||
BuildingOfficeIcon,
|
||||
IdentificationIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import Button from "../components/Button/Button";
|
||||
|
||||
interface PostcodeInquiryData {
|
||||
avenue: string;
|
||||
buildingName: string;
|
||||
description: string;
|
||||
floorNo: number | null;
|
||||
houseNo: string;
|
||||
houseNoSpecified: boolean;
|
||||
location: string;
|
||||
locationCode: string;
|
||||
locationType: string;
|
||||
parish: string | null;
|
||||
postCode: string;
|
||||
preAvenue: string;
|
||||
sideFloor: number | null;
|
||||
state: string;
|
||||
townShip: string;
|
||||
village: string;
|
||||
zone: string;
|
||||
address: string;
|
||||
hasError: boolean;
|
||||
errorMessage: string | null;
|
||||
errorCode: number;
|
||||
}
|
||||
|
||||
interface PostcodeInquiryResponse {
|
||||
status: boolean;
|
||||
statusCode: number;
|
||||
data: PostcodeInquiryData;
|
||||
apiLogId: string;
|
||||
}
|
||||
|
||||
const PostcodeInquiry: React.FC = () => {
|
||||
const [postcode, setPostcode] = useState("");
|
||||
const [inquiryData, setInquiryData] = useState<PostcodeInquiryData | null>(
|
||||
null,
|
||||
);
|
||||
const showToast = useToast();
|
||||
|
||||
const inquiryMutation = useApiMutation<PostcodeInquiryResponse>({
|
||||
api: "postcode-inquiry",
|
||||
method: "get",
|
||||
});
|
||||
|
||||
const handleSearch = async () => {
|
||||
const trimmed = postcode.trim();
|
||||
|
||||
if (!trimmed) {
|
||||
showToast("لطفا کد پستی را وارد کنید", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
if (trimmed.length !== 10) {
|
||||
showToast("کد پستی باید 10 رقم باشد", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await inquiryMutation.mutateAsync({ postcode: trimmed });
|
||||
|
||||
if (result?.status && result?.data) {
|
||||
if (result.data.hasError) {
|
||||
setInquiryData(null);
|
||||
showToast(result.data.errorMessage || "نتیجهای یافت نشد", "info");
|
||||
} else {
|
||||
setInquiryData(result.data);
|
||||
}
|
||||
} else {
|
||||
setInquiryData(null);
|
||||
showToast("نتیجهای یافت نشد", "info");
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("Postcode inquiry error:", error);
|
||||
showToast(
|
||||
error?.response?.data?.message || "خطا در استعلام کد پستی",
|
||||
"error",
|
||||
);
|
||||
setInquiryData(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePostcodeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
const numericValue = value.replace(/\D/g, "").slice(0, 10);
|
||||
setPostcode(numericValue);
|
||||
};
|
||||
|
||||
const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter") {
|
||||
handleSearch();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid container column className="gap-4 p-4 max-w-6xl mx-auto">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4, delay: 0.1 }}
|
||||
className="w-full"
|
||||
>
|
||||
<Grid
|
||||
container
|
||||
column
|
||||
className="gap-4 p-6 bg-white dark:bg-dark-800 rounded-xl shadow-lg border border-gray-200 dark:border-dark-600"
|
||||
>
|
||||
<Typography
|
||||
variant="h5"
|
||||
className="text-gray-900 dark:text-gray-100 font-bold mb-2"
|
||||
>
|
||||
استعلام کد پستی
|
||||
</Typography>
|
||||
|
||||
<Grid container className="gap-4 items-end">
|
||||
<Grid container className="flex-1" column>
|
||||
<Textfield
|
||||
placeholder="کد پستی (10 رقم)"
|
||||
value={postcode}
|
||||
onChange={handlePostcodeChange}
|
||||
onKeyPress={handleKeyPress}
|
||||
fullWidth
|
||||
/>
|
||||
</Grid>
|
||||
<Button
|
||||
onClick={handleSearch}
|
||||
disabled={inquiryMutation.isPending}
|
||||
className="px-6 py-2.5"
|
||||
>
|
||||
{inquiryMutation.isPending ? "در حال جستجو..." : "جستجو"}
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</motion.div>
|
||||
|
||||
<AnimatePresence mode="popLayout">
|
||||
{inquiryData && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
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">
|
||||
<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">
|
||||
<MapPinIcon 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"
|
||||
>
|
||||
نتیجه استعلام کد پستی
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="text-gray-600 dark:text-gray-400 mt-1"
|
||||
>
|
||||
کد پستی: {inquiryData.postCode}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{inquiryData.address && (
|
||||
<div className="flex items-start gap-3 p-3 bg-gray-50 dark:bg-dark-700 rounded-lg md:col-span-2">
|
||||
<MapPinIcon 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>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className="text-gray-900 dark:text-gray-100 font-medium"
|
||||
>
|
||||
{inquiryData.address}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{inquiryData.buildingName && (
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-dark-700 rounded-lg">
|
||||
<BuildingOfficeIcon 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"
|
||||
>
|
||||
{inquiryData.buildingName}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{inquiryData.state && (
|
||||
<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"
|
||||
>
|
||||
{inquiryData.state}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{inquiryData.townShip && (
|
||||
<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"
|
||||
>
|
||||
{inquiryData.townShip}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{inquiryData.locationCode && (
|
||||
<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"
|
||||
>
|
||||
{inquiryData.locationCode}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Grid>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default PostcodeInquiry;
|
||||
@@ -33,6 +33,11 @@ export const getInspectionMenuItems = (
|
||||
path: "/veterinarytransfer",
|
||||
component: () => import("../Pages/VeterinaryTransfer"),
|
||||
},
|
||||
{
|
||||
name: "postcodeinquiry",
|
||||
path: "/postcode-inquiry",
|
||||
component: () => import("../Pages/PostcodeInquiry"),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,3 +14,4 @@ export const AUTO_LOGIN_WITH_PARAMS = "/autologin/:key/:province";
|
||||
export const NATIONAL_INFO = "/nationalinfo";
|
||||
export const LADING_INFO = "/ladinginfo";
|
||||
export const VETERINARY_TRANSFER = "/veterinarytransfer";
|
||||
export const POSTCODE_INQUIRY = "/postcode-inquiry";
|
||||
|
||||
@@ -11,6 +11,7 @@ import AutoLogin from "../Pages/AutoLogin";
|
||||
import NationalInfo from "../Pages/NationalInfo";
|
||||
import LadingInfo from "../Pages/LadingInfo";
|
||||
import VeterinaryTransfer from "../Pages/VeterinaryTransfer";
|
||||
import PostcodeInquiry from "../Pages/PostcodeInquiry";
|
||||
|
||||
interface Route {
|
||||
path: string;
|
||||
@@ -30,6 +31,7 @@ export const getRoutes = (auth: string | null): Route[] => {
|
||||
{ path: R.NATIONAL_INFO, component: NationalInfo },
|
||||
{ path: R.LADING_INFO, component: LadingInfo },
|
||||
{ path: R.VETERINARY_TRANSFER, component: VeterinaryTransfer },
|
||||
{ path: R.POSTCODE_INQUIRY, component: PostcodeInquiry },
|
||||
];
|
||||
|
||||
if (checkIsMobile()) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const checkMenuPermission = (
|
||||
menuItemName: string,
|
||||
userPermissions: string[] = []
|
||||
userPermissions: string[] = [],
|
||||
): boolean => {
|
||||
if (userPermissions.includes("admin")) {
|
||||
return true;
|
||||
@@ -24,7 +24,7 @@ export const checkMenuPermission = (
|
||||
|
||||
export const checkRoutePermission = (
|
||||
routePath: string,
|
||||
userPermissions: string[] = []
|
||||
userPermissions: string[] = [],
|
||||
): boolean => {
|
||||
if (userPermissions.includes("admin")) {
|
||||
return true;
|
||||
@@ -35,6 +35,7 @@ export const checkRoutePermission = (
|
||||
"/inspections": ["admin", "submit"],
|
||||
"/statics": [],
|
||||
"/nationalinfo": ["admin"],
|
||||
"/postcode-inquiry": ["admin"],
|
||||
};
|
||||
|
||||
const requiredPermissions = routePermissionMap[routePath] || [];
|
||||
|
||||
@@ -22,6 +22,9 @@ export function getFaPermissions(permission: string) {
|
||||
case "veterinarytransfer":
|
||||
faPermission = "اطلاعات گواهی حمل";
|
||||
break;
|
||||
case "postcodeinquiry":
|
||||
faPermission = "استعلام کد پستی";
|
||||
break;
|
||||
case "main":
|
||||
faPermission = "صفحه اصلی";
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user