implemented payment computing for privacy office
This commit is contained in:
@@ -1,12 +1,27 @@
|
|||||||
|
// javascript for production
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": [
|
"@/*": ["./src/*"],
|
||||||
"./src/*"
|
"^/*": ["./public/*"]
|
||||||
],
|
}
|
||||||
"^/*": [
|
|
||||||
"./public/*"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// typescript for local development
|
||||||
|
// {
|
||||||
|
// "compilerOptions": {
|
||||||
|
// "jsx": "react-jsx",
|
||||||
|
// "noUnusedLocals": true,
|
||||||
|
// "noImplicitAny": false,
|
||||||
|
// "checkJs": true,
|
||||||
|
// "allowJs": true,
|
||||||
|
// "strict": true,
|
||||||
|
// "baseUrl": ".",
|
||||||
|
// "paths": {
|
||||||
|
// "@/*": ["./src/*"],
|
||||||
|
// "^/*": ["./public/*"]
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// "exclude": ["node_modules"]
|
||||||
|
// }
|
||||||
|
|||||||
@@ -1,18 +1,8 @@
|
|||||||
const {
|
const { IconButton, Tooltip, Box, Dialog, DialogTitle, Typography, DialogContent } = require("@mui/material");
|
||||||
Button,
|
|
||||||
IconButton,
|
|
||||||
Tooltip,
|
|
||||||
Box,
|
|
||||||
Dialog,
|
|
||||||
DialogTitle,
|
|
||||||
Typography,
|
|
||||||
DialogContent,
|
|
||||||
DialogActions,
|
|
||||||
} = require("@mui/material");
|
|
||||||
import LayersIcon from "@mui/icons-material/Layers";
|
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
|
||||||
import { useMemo, useState } from "react";
|
|
||||||
import MapLayer from "@/core/components/MapLayer";
|
import MapLayer from "@/core/components/MapLayer";
|
||||||
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
|
import LayersIcon from "@mui/icons-material/Layers";
|
||||||
|
import { useMemo, useState } from "react";
|
||||||
import ShowBound from "./ShowBound";
|
import ShowBound from "./ShowBound";
|
||||||
|
|
||||||
const ShowPrimaryArea = ({ primaryArea }) => {
|
const ShowPrimaryArea = ({ primaryArea }) => {
|
||||||
|
|||||||
@@ -1,47 +1,92 @@
|
|||||||
import { Box, Button, DialogActions, FormControl, InputLabel, MenuItem, Select, TextField } from "@mui/material";
|
import { calculatePolygonMetrics } from "@/core/utils/geoCalculations";
|
||||||
|
import { PRIVACY_ADMIN_ACTION_API } from "@/core/utils/routes";
|
||||||
|
import { safeParsePolygon } from "@/core/utils/utils";
|
||||||
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
DialogActions,
|
||||||
|
FormControl,
|
||||||
|
FormHelperText,
|
||||||
|
InputLabel,
|
||||||
|
MenuItem,
|
||||||
|
Select,
|
||||||
|
TextField,
|
||||||
|
} from "@mui/material";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Controller, useForm, useWatch } from "react-hook-form";
|
import { Controller, useForm, useWatch } from "react-hook-form";
|
||||||
|
|
||||||
export default function ComputePaymentForm({ mutate, onClose }) {
|
const formatNumber = (num) => {
|
||||||
|
if (!num) return "";
|
||||||
|
return Number(num).toLocaleString("en-US");
|
||||||
|
};
|
||||||
|
|
||||||
|
const price_fee = 1; // TODO: Replace with real price_fee
|
||||||
|
export default function ComputePaymentForm({ mutate, onClose, rowData }) {
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
const request = useRequest({ notificationSuccess: true, notificationFailed: true });
|
||||||
|
|
||||||
const price_fee = 1; // TODO: Replace with value from .env
|
const coordsArea = safeParsePolygon(rowData.final_area);
|
||||||
|
const coordsPlan = safeParsePolygon(rowData.final_plan);
|
||||||
|
|
||||||
const { control, handleSubmit, setValue } = useForm({
|
const areaMetrics = coordsArea ? calculatePolygonMetrics(coordsArea) : { area: 0 };
|
||||||
|
const planMetrics = coordsPlan ? calculatePolygonMetrics(coordsPlan) : { area: 0 };
|
||||||
|
|
||||||
|
const calculatedArea = Number(areaMetrics.area.toFixed(2) || 0);
|
||||||
|
const calculatedPlan = Number(planMetrics.area.toFixed(2) || 0);
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
setValue,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
expert_description: "",
|
expert_description: "",
|
||||||
traffic: "",
|
traffic: "",
|
||||||
road_type: "",
|
road_type: "",
|
||||||
position: "",
|
position: "",
|
||||||
final_area_space: "",
|
final_area_space: calculatedArea, // عرصه
|
||||||
|
final_plan_space: calculatedPlan, // عیان
|
||||||
payment_amount: "",
|
payment_amount: "",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Watch dependent fields for recalculation
|
const { traffic, road_type, position } = useWatch({
|
||||||
const { traffic, road_type, position, final_area_space } = useWatch({
|
|
||||||
control,
|
control,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const hasGeometry = calculatedArea > 0 || calculatedPlan > 0;
|
||||||
|
|
||||||
const t = parseFloat(traffic || "");
|
const t = parseFloat(traffic || "");
|
||||||
const r = parseFloat(road_type || "");
|
const r = parseFloat(road_type || "");
|
||||||
const p = parseFloat(position || "");
|
const p = parseFloat(position || "");
|
||||||
const area = parseFloat(final_area_space || "");
|
|
||||||
|
|
||||||
if (t && r && p && area) {
|
const inputsValid = hasGeometry && !isNaN(t) && t > 0 && !isNaN(r) && r > 0 && !isNaN(p) && p > 0;
|
||||||
const result = t * r * p * area * price_fee;
|
|
||||||
setValue("payment_amount", result.toString());
|
if (inputsValid) {
|
||||||
|
const resultArea = t * r * p * calculatedArea * price_fee;
|
||||||
|
const resultPlan = t * r * p * calculatedPlan * price_fee;
|
||||||
|
|
||||||
|
setValue("payment_amount", Math.floor(resultArea + resultPlan).toString());
|
||||||
} else {
|
} else {
|
||||||
setValue("payment_amount", "");
|
setValue("payment_amount", "");
|
||||||
}
|
}
|
||||||
}, [traffic, road_type, position, final_area_space, price_fee, setValue]);
|
}, [traffic, road_type, position, setValue, calculatedArea, calculatedPlan]);
|
||||||
|
|
||||||
const onSubmit = (data) => {
|
const onSubmit = (data) => {
|
||||||
console.log(data);
|
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
mutate();
|
|
||||||
onClose();
|
try {
|
||||||
|
request(`${PRIVACY_ADMIN_ACTION_API}/computing_payment/${rowData.id}`, "post", {
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error submitting payment:", error);
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -50,14 +95,18 @@ export default function ComputePaymentForm({ mutate, onClose }) {
|
|||||||
<Controller
|
<Controller
|
||||||
name="traffic"
|
name="traffic"
|
||||||
control={control}
|
control={control}
|
||||||
|
rules={{ required: "ترافیک الزامی است" }}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>ترافیک</InputLabel>
|
<InputLabel>ترافیک</InputLabel>
|
||||||
<Select {...field} label="ترافیک">
|
<Select {...field} error={!!errors.traffic} label="ترافیک">
|
||||||
<MenuItem value={1}>سبک</MenuItem>
|
<MenuItem value={1}>سبک</MenuItem>
|
||||||
<MenuItem value={1.5}>متوسط</MenuItem>
|
<MenuItem value={1.5}>متوسط</MenuItem>
|
||||||
<MenuItem value={2}>سنگین</MenuItem>
|
<MenuItem value={2}>سنگین</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
|
{errors.traffic && (
|
||||||
|
<FormHelperText sx={{ color: "darkred" }}>{errors.traffic.message}</FormHelperText>
|
||||||
|
)}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -65,39 +114,66 @@ export default function ComputePaymentForm({ mutate, onClose }) {
|
|||||||
<Controller
|
<Controller
|
||||||
name="road_type"
|
name="road_type"
|
||||||
control={control}
|
control={control}
|
||||||
|
rules={{ required: "نوع راه الزامی است" }}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>نوع راه</InputLabel>
|
<InputLabel>نوع راه</InputLabel>
|
||||||
<Select {...field} label="نوع راه">
|
<Select {...field} error={!!errors.road_type} label="نوع راه">
|
||||||
<MenuItem value={1}>فرعی و راهآهن</MenuItem>
|
<MenuItem value={1}>فرعی و راهآهن</MenuItem>
|
||||||
<MenuItem value={1.5}>اصلی و بزرگراه</MenuItem>
|
<MenuItem value={1.5}>اصلی و بزرگراه</MenuItem>
|
||||||
<MenuItem value={2}>آزادراه</MenuItem>
|
<MenuItem value={2}>آزادراه</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
|
{errors.road_type && (
|
||||||
|
<FormHelperText sx={{ color: "darkred" }}>{errors.road_type.message}</FormHelperText>
|
||||||
|
)}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ display: "flex", gap: 1 }}>
|
<Controller
|
||||||
<Controller
|
name="position"
|
||||||
name="position"
|
control={control}
|
||||||
control={control}
|
rules={{ required: "موقعیت اقتصادی الزامی است" }}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>موقعیت اقتصادی</InputLabel>
|
<InputLabel>موقعیت اقتصادی</InputLabel>
|
||||||
<Select {...field} label="موقعیت اقتصادی">
|
<Select {...field} error={!!errors.position} label="موقعیت اقتصادی">
|
||||||
<MenuItem value={2}>در شعاع ۱۰ کیلومتری از مراکز استان</MenuItem>
|
<MenuItem value={2}>در شعاع ۱۰ کیلومتری از مراکز استان</MenuItem>
|
||||||
<MenuItem value={1.5}>در شعاع ۵ کیلومتری از سایر شهرها</MenuItem>
|
<MenuItem value={1.5}>در شعاع ۵ کیلومتری از سایر شهرها</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
{errors.position && (
|
||||||
)}
|
<FormHelperText sx={{ color: "darkred" }}>{errors.position.message}</FormHelperText>
|
||||||
/>
|
)}
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box sx={{ display: "flex", gap: 1 }}>
|
||||||
<Controller
|
<Controller
|
||||||
name="final_area_space"
|
name="final_area_space"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<TextField {...field} type="number" fullWidth label="مساحت نهایی (متر مربع)" />
|
<TextField
|
||||||
|
{...field}
|
||||||
|
type="number"
|
||||||
|
InputProps={{ readOnly: true }}
|
||||||
|
fullWidth
|
||||||
|
label="مساحت عرصه (متر مربع)"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="final_plan_space"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<TextField
|
||||||
|
{...field}
|
||||||
|
type="number"
|
||||||
|
InputProps={{ readOnly: true }}
|
||||||
|
fullWidth
|
||||||
|
label="مساحت عیان (متر مربع)"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -105,15 +181,35 @@ export default function ComputePaymentForm({ mutate, onClose }) {
|
|||||||
<Controller
|
<Controller
|
||||||
name="payment_amount"
|
name="payment_amount"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => {
|
||||||
<TextField {...field} fullWidth label="مبلغ پرداختی" InputProps={{ readOnly: true }} />
|
const { value } = field;
|
||||||
)}
|
|
||||||
|
return (
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="مبلغ پرداختی (ریال)"
|
||||||
|
value={formatNumber(value)}
|
||||||
|
InputProps={{ readOnly: true }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
name="expert_description"
|
name="expert_description"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => <TextField {...field} fullWidth multiline minRows={3} label="توضیحات" />}
|
rules={{ required: "توضیحات ناظر اجباری است" }}
|
||||||
|
render={({ field }) => (
|
||||||
|
<TextField
|
||||||
|
{...field}
|
||||||
|
fullWidth
|
||||||
|
error={!!errors.expert_description}
|
||||||
|
multiline
|
||||||
|
minRows={3}
|
||||||
|
label="توضیحات"
|
||||||
|
helperText={errors.expert_description?.message}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export default function RenderActionForm({ type, rowData, mutate, onClose }) {
|
|||||||
case "confirm_request_5":
|
case "confirm_request_5":
|
||||||
return <ConfirmFileForm mutate={mutate} onClose={onClose} rowId={rowData.id} />;
|
return <ConfirmFileForm mutate={mutate} onClose={onClose} rowId={rowData.id} />;
|
||||||
case "confirm_request_13":
|
case "confirm_request_13":
|
||||||
return <ComputePaymentForm mutate={mutate} onClose={onClose} />;
|
return <ComputePaymentForm mutate={mutate} onClose={onClose} rowData={rowData} />;
|
||||||
default:
|
default:
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,3 +148,18 @@ export const getClosedPolygonCoordinates = (polygon) => {
|
|||||||
|
|
||||||
return coordinates;
|
return coordinates;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function safeParsePolygon(polygonJson) {
|
||||||
|
if (!polygonJson) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(polygonJson);
|
||||||
|
if (parsed && parsed.coordinates) {
|
||||||
|
return parsed.coordinates;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Invalid polygon JSON:", err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user