implemented payment computing for privacy office
This commit is contained in:
@@ -1,12 +1,27 @@
|
||||
// javascript for production
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
],
|
||||
"^/*": [
|
||||
"./public/*"
|
||||
]
|
||||
"@/*": ["./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"]
|
||||
// }
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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}
|
||||
rules={{ required: "موقعیت اقتصادی الزامی است" }}
|
||||
render={({ field }) => (
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>موقعیت اقتصادی</InputLabel>
|
||||
<Select {...field} label="موقعیت اقتصادی">
|
||||
<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>
|
||||
|
||||
@@ -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 <></>;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user