Files
Frontend/src/app/(dashboard)/(private)/fertilization-plan/page.tsx
T
2026-05-02 16:31:23 +03:30

408 lines
15 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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;