408 lines
15 KiB
TypeScript
408 lines
15 KiB
TypeScript
"use client";
|
||
|
||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||
|
||
import Alert from "@mui/material/Alert";
|
||
import Box from "@mui/material/Box";
|
||
import Card from "@mui/material/Card";
|
||
import CardContent from "@mui/material/CardContent";
|
||
import Chip from "@mui/material/Chip";
|
||
import Divider from "@mui/material/Divider";
|
||
import IconButton from "@mui/material/IconButton";
|
||
import LinearProgress from "@mui/material/LinearProgress";
|
||
import Stack from "@mui/material/Stack";
|
||
import Table from "@mui/material/Table";
|
||
import TableBody from "@mui/material/TableBody";
|
||
import TableCell from "@mui/material/TableCell";
|
||
import TableContainer from "@mui/material/TableContainer";
|
||
import TableHead from "@mui/material/TableHead";
|
||
import TableRow from "@mui/material/TableRow";
|
||
import Tooltip from "@mui/material/Tooltip";
|
||
import Typography from "@mui/material/Typography";
|
||
|
||
import FertilizationPlanParserPage from "@views/dashboards/farm/fertilizationPlanParser/FertilizationPlanParserPage";
|
||
import RelatedPlanSelector, {
|
||
type RelatedPlanItem,
|
||
} from "@views/dashboards/farm/planSelector/RelatedPlanSelector";
|
||
import { useFarmHub } from "@/hooks/useFarmHub";
|
||
import {
|
||
fertilizationPlanService,
|
||
type FertilizationPlanDetail,
|
||
type FertilizationPlanListItem,
|
||
} from "@/libs/api/services/fertilizationPlanService";
|
||
import { irrigationPlanService } from "@/libs/api/services/irrigationPlanService";
|
||
|
||
import CustomTextField from "@core/components/mui/TextField";
|
||
|
||
type FertilizationPlanRow = {
|
||
id: string;
|
||
planName: string;
|
||
finalProduct: string;
|
||
harvestTime: string;
|
||
outputTon: number;
|
||
fertilizerType: string;
|
||
status: "active" | "draft";
|
||
sourceLabel: string;
|
||
growthStage: string;
|
||
};
|
||
|
||
const PAGE_SIZE = 10;
|
||
|
||
const formatDate = (value?: string | null) => {
|
||
if (!value) return "—";
|
||
|
||
try {
|
||
return new Intl.DateTimeFormat("fa-IR", {
|
||
year: "numeric",
|
||
month: "2-digit",
|
||
day: "2-digit",
|
||
}).format(new Date(value));
|
||
} catch {
|
||
return value;
|
||
}
|
||
};
|
||
|
||
const mapFertilizationPlanRow = (
|
||
plan: FertilizationPlanListItem,
|
||
detail?: FertilizationPlanDetail | null,
|
||
): FertilizationPlanRow => ({
|
||
id: plan.plan_uuid,
|
||
planName: plan.title,
|
||
finalProduct: plan.plant_name || plan.crop_id || "—",
|
||
harvestTime: formatDate(plan.created_at),
|
||
outputTon: 0,
|
||
fertilizerType: detail?.plan_payload?.items?.[0]?.name || plan.source_label || "—",
|
||
status: plan.is_active ? "active" : "draft",
|
||
sourceLabel: plan.source_label || "—",
|
||
growthStage: plan.growth_stage || "—",
|
||
});
|
||
|
||
const FertilizationPlanPage = () => {
|
||
const { getFarmUuid } = useFarmHub();
|
||
const [plans, setPlans] = useState<FertilizationPlanRow[]>([]);
|
||
const [minOutputTon, setMinOutputTon] = useState("0");
|
||
const [selectedPlan, setSelectedPlan] = useState<FertilizationPlanRow | null>(null);
|
||
const [detailsOpen, setDetailsOpen] = useState(false);
|
||
const [loading, setLoading] = useState(false);
|
||
const [detailsLoading, setDetailsLoading] = useState(false);
|
||
const [actionLoadingId, setActionLoadingId] = useState<string | null>(null);
|
||
const [error, setError] = useState<string | null>(null);
|
||
const [relatedPlans, setRelatedPlans] = useState<RelatedPlanItem[]>([]);
|
||
const [selectedRelatedPlanId, setSelectedRelatedPlanId] = useState<number | string | null>(
|
||
null,
|
||
);
|
||
|
||
const filteredPlans = useMemo(() => {
|
||
const minTon = Number(minOutputTon);
|
||
|
||
return plans
|
||
.filter((plan) => plan.outputTon >= minTon)
|
||
.sort((firstPlan, secondPlan) => secondPlan.outputTon - firstPlan.outputTon);
|
||
}, [minOutputTon, plans]);
|
||
|
||
const loadPlans = useCallback(async () => {
|
||
const farmUuid = getFarmUuid();
|
||
|
||
if (!farmUuid) {
|
||
setError("ابتدا مزرعه فعال را انتخاب کنید.");
|
||
setPlans([]);
|
||
setSelectedPlan(null);
|
||
return;
|
||
}
|
||
|
||
setLoading(true);
|
||
setError(null);
|
||
|
||
try {
|
||
const response = await fertilizationPlanService.getPlans(farmUuid, 1, PAGE_SIZE);
|
||
const mappedPlans = response.data.map((plan) => mapFertilizationPlanRow(plan));
|
||
|
||
setPlans(mappedPlans);
|
||
setSelectedPlan((current) =>
|
||
mappedPlans.find((plan) => plan.id === current?.id) ?? mappedPlans[0] ?? null,
|
||
);
|
||
} catch (loadError) {
|
||
setError(
|
||
loadError instanceof Error
|
||
? loadError.message
|
||
: "دریافت لیست برنامههای کوددهی انجام نشد.",
|
||
);
|
||
setPlans([]);
|
||
setSelectedPlan(null);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, [getFarmUuid]);
|
||
|
||
useEffect(() => {
|
||
loadPlans();
|
||
}, [loadPlans]);
|
||
|
||
const loadRelatedPlans = useCallback(async () => {
|
||
const farmUuid = getFarmUuid();
|
||
|
||
if (!farmUuid) {
|
||
setRelatedPlans([]);
|
||
setSelectedRelatedPlanId(null);
|
||
return;
|
||
}
|
||
|
||
const response = await irrigationPlanService.getPlans(farmUuid, 1, PAGE_SIZE);
|
||
const items: RelatedPlanItem[] = response.data.map((plan) => ({
|
||
id: plan.plan_uuid,
|
||
title: plan.title,
|
||
finalProduct: plan.plant_name || plan.crop_id || "—",
|
||
harvestTime: formatDate(plan.created_at),
|
||
outputTon: 0,
|
||
methodLabel: `منبع: ${plan.source_label || "—"}`,
|
||
status: plan.is_active ? "active" : "draft",
|
||
}));
|
||
|
||
setRelatedPlans(items);
|
||
setSelectedRelatedPlanId(items[0]?.id ?? null);
|
||
}, [getFarmUuid]);
|
||
|
||
const handleActivate = async (planId: string) => {
|
||
setActionLoadingId(planId);
|
||
|
||
try {
|
||
await fertilizationPlanService.updatePlanStatus(planId, true);
|
||
setPlans((currentPlans) =>
|
||
currentPlans.map((plan) => ({
|
||
...plan,
|
||
status: plan.id === planId ? "active" : "draft",
|
||
})),
|
||
);
|
||
setSelectedPlan((currentPlan) =>
|
||
currentPlan && currentPlan.id === planId
|
||
? { ...currentPlan, status: "active" }
|
||
: currentPlan,
|
||
);
|
||
} catch (updateError) {
|
||
setError(
|
||
updateError instanceof Error
|
||
? updateError.message
|
||
: "تغییر وضعیت برنامه کوددهی انجام نشد.",
|
||
);
|
||
} finally {
|
||
setActionLoadingId(null);
|
||
}
|
||
};
|
||
|
||
const handleDelete = async (planId: string) => {
|
||
setActionLoadingId(planId);
|
||
|
||
try {
|
||
await fertilizationPlanService.deletePlan(planId);
|
||
setPlans((currentPlans) => currentPlans.filter((plan) => plan.id !== planId));
|
||
if (selectedPlan?.id === planId) {
|
||
setSelectedPlan(null);
|
||
}
|
||
} catch (deleteError) {
|
||
setError(
|
||
deleteError instanceof Error
|
||
? deleteError.message
|
||
: "حذف برنامه کوددهی انجام نشد.",
|
||
);
|
||
} finally {
|
||
setActionLoadingId(null);
|
||
}
|
||
};
|
||
|
||
const handleShowDetails = async (plan: FertilizationPlanRow) => {
|
||
setSelectedPlan(plan);
|
||
setDetailsOpen(true);
|
||
setDetailsLoading(true);
|
||
|
||
try {
|
||
const detail = await fertilizationPlanService.getPlanDetail(plan.id);
|
||
setSelectedPlan(mapFertilizationPlanRow(detail, detail));
|
||
await loadRelatedPlans();
|
||
} catch (detailError) {
|
||
setError(
|
||
detailError instanceof Error
|
||
? detailError.message
|
||
: "دریافت جزئیات برنامه کوددهی انجام نشد.",
|
||
);
|
||
setDetailsOpen(false);
|
||
} finally {
|
||
setDetailsLoading(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<>
|
||
<Stack spacing={6}>
|
||
<FertilizationPlanParserPage />
|
||
|
||
<Card>
|
||
<CardContent>
|
||
<Stack spacing={4}>
|
||
{loading ? <LinearProgress sx={{ borderRadius: 999 }} /> : null}
|
||
{error ? <Alert severity="error">{error}</Alert> : null}
|
||
|
||
<Stack
|
||
direction={{ xs: "column", md: "row" }}
|
||
spacing={3}
|
||
alignItems={{ xs: "stretch", md: "center" }}
|
||
justifyContent="space-between"
|
||
>
|
||
<Box>
|
||
<Typography variant="h5">لیست برنامههای کوددهی</Typography>
|
||
<Typography color="text.secondary">
|
||
لیست برنامههای کوددهی ثبتشده برای مزرعه فعال
|
||
</Typography>
|
||
</Box>
|
||
|
||
<CustomTextField
|
||
select
|
||
value={minOutputTon}
|
||
onChange={(event) => setMinOutputTon(event.target.value)}
|
||
label="فیلتر تن خروجی محصول"
|
||
sx={{ minWidth: { xs: "100%", md: 260 } }}
|
||
SelectProps={{ native: true }}
|
||
>
|
||
<option value="0">همه برنامهها</option>
|
||
<option value="30">بیشتر از ۳۰ تن</option>
|
||
<option value="40">بیشتر از ۴۰ تن</option>
|
||
<option value="50">بیشتر از ۵۰ تن</option>
|
||
</CustomTextField>
|
||
</Stack>
|
||
|
||
<TableContainer>
|
||
<Table>
|
||
<TableHead>
|
||
<TableRow>
|
||
<TableCell>نام برنامه</TableCell>
|
||
<TableCell>محصول نهایی</TableCell>
|
||
<TableCell>تاریخ ایجاد</TableCell>
|
||
<TableCell>مرحله رشد</TableCell>
|
||
<TableCell>نوع کود / منبع</TableCell>
|
||
<TableCell>وضعیت</TableCell>
|
||
<TableCell align="center">عملیات</TableCell>
|
||
</TableRow>
|
||
</TableHead>
|
||
<TableBody>
|
||
{filteredPlans.map((plan) => (
|
||
<TableRow hover key={plan.id}>
|
||
<TableCell>{plan.planName}</TableCell>
|
||
<TableCell>{plan.finalProduct}</TableCell>
|
||
<TableCell>{plan.harvestTime}</TableCell>
|
||
<TableCell>{plan.growthStage}</TableCell>
|
||
<TableCell>{plan.fertilizerType}</TableCell>
|
||
<TableCell>
|
||
<Chip
|
||
size="small"
|
||
label={plan.status === "active" ? "فعال" : "پیشنویس"}
|
||
color={plan.status === "active" ? "success" : "default"}
|
||
variant={plan.status === "active" ? "filled" : "tonal"}
|
||
/>
|
||
</TableCell>
|
||
<TableCell align="center">
|
||
<Stack direction="row" spacing={1} justifyContent="center">
|
||
<Tooltip title="فعالسازی برنامه">
|
||
<span>
|
||
<IconButton
|
||
color="success"
|
||
onClick={() => handleActivate(plan.id)}
|
||
disabled={plan.status === "active" || actionLoadingId === plan.id}
|
||
>
|
||
<i className="tabler-player-play text-[20px]" />
|
||
</IconButton>
|
||
</span>
|
||
</Tooltip>
|
||
|
||
<Tooltip title="مشاهده جزئیات">
|
||
<IconButton
|
||
color="primary"
|
||
onClick={() => handleShowDetails(plan)}
|
||
>
|
||
<i className="tabler-eye text-[20px]" />
|
||
</IconButton>
|
||
</Tooltip>
|
||
|
||
<Tooltip title="حذف برنامه">
|
||
<IconButton
|
||
color="error"
|
||
onClick={() => handleDelete(plan.id)}
|
||
disabled={actionLoadingId === plan.id}
|
||
>
|
||
<i className="tabler-trash text-[20px]" />
|
||
</IconButton>
|
||
</Tooltip>
|
||
</Stack>
|
||
</TableCell>
|
||
</TableRow>
|
||
))}
|
||
|
||
{filteredPlans.length === 0 ? (
|
||
<TableRow>
|
||
<TableCell colSpan={7}>
|
||
<Alert severity="info">
|
||
برنامهای با این فیلتر پیدا نشد.
|
||
</Alert>
|
||
</TableCell>
|
||
</TableRow>
|
||
) : null}
|
||
</TableBody>
|
||
</Table>
|
||
</TableContainer>
|
||
|
||
<Divider />
|
||
|
||
<Stack spacing={2}>
|
||
<Typography variant="h6">جزئیات برنامه انتخابشده</Typography>
|
||
|
||
{selectedPlan ? (
|
||
<Stack
|
||
direction={{ xs: "column", md: "row" }}
|
||
spacing={2}
|
||
useFlexGap
|
||
flexWrap="wrap"
|
||
>
|
||
<Chip label={`محصول نهایی: ${selectedPlan.finalProduct}`} variant="tonal" />
|
||
<Chip label={`تاریخ ایجاد: ${selectedPlan.harvestTime}`} variant="tonal" />
|
||
<Chip label={`مرحله رشد: ${selectedPlan.growthStage}`} variant="tonal" />
|
||
<Chip label={`نوع کود: ${selectedPlan.fertilizerType}`} variant="tonal" />
|
||
</Stack>
|
||
) : (
|
||
<Alert severity="warning">
|
||
برای دیدن جزئیات، یکی از برنامههای کوددهی را انتخاب کنید.
|
||
</Alert>
|
||
)}
|
||
</Stack>
|
||
</Stack>
|
||
</CardContent>
|
||
</Card>
|
||
</Stack>
|
||
|
||
{detailsLoading ? <LinearProgress sx={{ borderRadius: 999, mt: 2 }} /> : null}
|
||
|
||
{selectedPlan ? (
|
||
<RelatedPlanSelector
|
||
open={detailsOpen}
|
||
onClose={() => setDetailsOpen(false)}
|
||
title="جزییات برنامه کوددهی"
|
||
description="برای تکمیل اجرای این برنامه کوددهی، یکی از برنامههای آبیاری مرتبط را انتخاب کنید."
|
||
currentPlanName={selectedPlan.planName}
|
||
currentPlanStatus={selectedPlan.status === "active" ? "برنامه فعال" : "آماده برای فعالسازی"}
|
||
currentPlanOutput={`منبع: ${selectedPlan.sourceLabel}`}
|
||
summaryItems={[
|
||
{ label: "محصول نهایی", value: selectedPlan.finalProduct },
|
||
{ label: "تاریخ ایجاد", value: selectedPlan.harvestTime },
|
||
{ label: "جزئیات", value: selectedPlan.fertilizerType },
|
||
]}
|
||
relatedTitle="برنامههای آبیاری پیشنهادی"
|
||
relatedDescription="از بین برنامههای زیر، مناسبترین برنامه آبیاری را برای این برنامه کوددهی انتخاب کنید."
|
||
relatedPlans={relatedPlans}
|
||
selectedRelatedPlanId={selectedRelatedPlanId}
|
||
onSelectRelatedPlan={(planId) => setSelectedRelatedPlanId(planId)}
|
||
onConfirm={() => setDetailsOpen(false)}
|
||
/>
|
||
) : null}
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default FertilizationPlanPage;
|