implemented payment computing for privacy office

This commit is contained in:
2025-11-19 15:42:56 +03:30
parent 0e4fc65d25
commit d9f27bda74
5 changed files with 176 additions and 60 deletions

View File

@@ -1,12 +1,27 @@
// javascript for production
{
"compilerOptions": {
"paths": {
"@/*": [
"./src/*"
],
"^/*": [
"./public/*"
]
"compilerOptions": {
"paths": {
"@/*": ["./src/*"],
"^/*": ["./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"]
// }

View File

@@ -1,18 +1,8 @@
const {
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";
const { IconButton, Tooltip, Box, Dialog, DialogTitle, Typography, DialogContent } = require("@mui/material");
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";
const ShowPrimaryArea = ({ primaryArea }) => {

View File

@@ -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 { 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 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: {
expert_description: "",
traffic: "",
road_type: "",
position: "",
final_area_space: "",
final_area_space: calculatedArea, // عرصه
final_plan_space: calculatedPlan, // عیان
payment_amount: "",
},
});
// Watch dependent fields for recalculation
const { traffic, road_type, position, final_area_space } = useWatch({
const { traffic, road_type, position } = useWatch({
control,
});
useEffect(() => {
const hasGeometry = calculatedArea > 0 || calculatedPlan > 0;
const t = parseFloat(traffic || "");
const r = parseFloat(road_type || "");
const p = parseFloat(position || "");
const area = parseFloat(final_area_space || "");
if (t && r && p && area) {
const result = t * r * p * area * price_fee;
setValue("payment_amount", result.toString());
const inputsValid = hasGeometry && !isNaN(t) && t > 0 && !isNaN(r) && r > 0 && !isNaN(p) && p > 0;
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 {
setValue("payment_amount", "");
}
}, [traffic, road_type, position, final_area_space, price_fee, setValue]);
}, [traffic, road_type, position, setValue, calculatedArea, calculatedPlan]);
const onSubmit = (data) => {
console.log(data);
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 (
@@ -50,14 +95,18 @@ export default function ComputePaymentForm({ mutate, onClose }) {
<Controller
name="traffic"
control={control}
rules={{ required: "ترافیک الزامی است" }}
render={({ field }) => (
<FormControl fullWidth>
<InputLabel>ترافیک</InputLabel>
<Select {...field} label="ترافیک">
<Select {...field} error={!!errors.traffic} label="ترافیک">
<MenuItem value={1}>سبک</MenuItem>
<MenuItem value={1.5}>متوسط</MenuItem>
<MenuItem value={2}>سنگین</MenuItem>
</Select>
{errors.traffic && (
<FormHelperText sx={{ color: "darkred" }}>{errors.traffic.message}</FormHelperText>
)}
</FormControl>
)}
/>
@@ -65,39 +114,66 @@ export default function ComputePaymentForm({ mutate, onClose }) {
<Controller
name="road_type"
control={control}
rules={{ required: "نوع راه الزامی است" }}
render={({ field }) => (
<FormControl fullWidth>
<InputLabel>نوع راه</InputLabel>
<Select {...field} label="نوع راه">
<Select {...field} error={!!errors.road_type} label="نوع راه">
<MenuItem value={1}>فرعی و راهآهن</MenuItem>
<MenuItem value={1.5}>اصلی و بزرگراه</MenuItem>
<MenuItem value={2}>آزادراه</MenuItem>
</Select>
{errors.road_type && (
<FormHelperText sx={{ color: "darkred" }}>{errors.road_type.message}</FormHelperText>
)}
</FormControl>
)}
/>
</Box>
<Box sx={{ display: "flex", gap: 1 }}>
<Controller
name="position"
control={control}
render={({ field }) => (
<FormControl fullWidth>
<InputLabel>موقعیت اقتصادی</InputLabel>
<Select {...field} label="موقعیت اقتصادی">
<MenuItem value={2}>در شعاع ۱۰ کیلومتری از مراکز استان</MenuItem>
<MenuItem value={1.5}>در شعاع ۵ کیلومتری از سایر شهرها</MenuItem>
</Select>
</FormControl>
)}
/>
<Controller
name="position"
control={control}
rules={{ required: "موقعیت اقتصادی الزامی است" }}
render={({ field }) => (
<FormControl fullWidth>
<InputLabel>موقعیت اقتصادی</InputLabel>
<Select {...field} error={!!errors.position} label="موقعیت اقتصادی">
<MenuItem value={2}>در شعاع ۱۰ کیلومتری از مراکز استان</MenuItem>
<MenuItem value={1.5}>در شعاع ۵ کیلومتری از سایر شهرها</MenuItem>
</Select>
{errors.position && (
<FormHelperText sx={{ color: "darkred" }}>{errors.position.message}</FormHelperText>
)}
</FormControl>
)}
/>
<Box sx={{ display: "flex", gap: 1 }}>
<Controller
name="final_area_space"
control={control}
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>
@@ -105,15 +181,35 @@ export default function ComputePaymentForm({ mutate, onClose }) {
<Controller
name="payment_amount"
control={control}
render={({ field }) => (
<TextField {...field} fullWidth label="مبلغ پرداختی" InputProps={{ readOnly: true }} />
)}
render={({ field }) => {
const { value } = field;
return (
<TextField
fullWidth
label="مبلغ پرداختی (ریال)"
value={formatNumber(value)}
InputProps={{ readOnly: true }}
/>
);
}}
/>
<Controller
name="expert_description"
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>

View File

@@ -22,7 +22,7 @@ export default function RenderActionForm({ type, rowData, mutate, onClose }) {
case "confirm_request_5":
return <ConfirmFileForm mutate={mutate} onClose={onClose} rowId={rowData.id} />;
case "confirm_request_13":
return <ComputePaymentForm mutate={mutate} onClose={onClose} />;
return <ComputePaymentForm mutate={mutate} onClose={onClose} rowData={rowData} />;
default:
return <></>;
}

View File

@@ -148,3 +148,18 @@ export const getClosedPolygonCoordinates = (polygon) => {
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;
}
}