This commit is contained in:
2026-05-02 16:31:23 +03:30
parent f8d1f84ed6
commit baf3f01dbc
9 changed files with 1386 additions and 1626 deletions
@@ -1,16 +1,15 @@
"use client"; "use client";
import { useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { useRouter } from "next/navigation";
import Alert from "@mui/material/Alert"; import Alert from "@mui/material/Alert";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Card from "@mui/material/Card"; import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent"; import CardContent from "@mui/material/CardContent";
import Chip from "@mui/material/Chip"; import Chip from "@mui/material/Chip";
import Divider from "@mui/material/Divider"; import Divider from "@mui/material/Divider";
import IconButton from "@mui/material/IconButton"; import IconButton from "@mui/material/IconButton";
import LinearProgress from "@mui/material/LinearProgress";
import Stack from "@mui/material/Stack"; import Stack from "@mui/material/Stack";
import Table from "@mui/material/Table"; import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody"; import TableBody from "@mui/material/TableBody";
@@ -25,98 +24,72 @@ import FertilizationPlanParserPage from "@views/dashboards/farm/fertilizationPla
import RelatedPlanSelector, { import RelatedPlanSelector, {
type RelatedPlanItem, type RelatedPlanItem,
} from "@views/dashboards/farm/planSelector/RelatedPlanSelector"; } 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"; import CustomTextField from "@core/components/mui/TextField";
type FertilizationPlanRow = { type FertilizationPlanRow = {
id: number; id: string;
planName: string; planName: string;
finalProduct: string; finalProduct: string;
harvestTime: string; harvestTime: string;
outputTon: number; outputTon: number;
fertilizerType: string; fertilizerType: string;
status: "active" | "draft"; status: "active" | "draft";
sourceLabel: string;
growthStage: string;
}; };
const mockFertilizationPlans: FertilizationPlanRow[] = [ const PAGE_SIZE = 10;
{
id: 1,
planName: "برنامه کوددهی گوجه فرنگی گلخانه‌ای",
finalProduct: "گوجه فرنگی ممتاز",
harvestTime: "۱۴۰۴/۰۶/۱۲",
outputTon: 52,
fertilizerType: "NPK 20-20-20",
status: "active",
},
{
id: 2,
planName: "برنامه کوددهی فلفل دلمه‌ای",
finalProduct: "فلفل دلمه‌ای رنگی",
harvestTime: "۱۴۰۴/۰۵/۲۰",
outputTon: 34,
fertilizerType: "نیترات کلسیم",
status: "draft",
},
{
id: 3,
planName: "برنامه کوددهی خیار گلخانه‌ای",
finalProduct: "خیار ممتاز صادراتی",
harvestTime: "۱۴۰۴/۰۴/۲۸",
outputTon: 41,
fertilizerType: "سولفات پتاسیم",
status: "draft",
},
{
id: 4,
planName: "برنامه کوددهی هندوانه",
finalProduct: "هندوانه شیرین بازارپسند",
harvestTime: "۱۴۰۴/۰۵/۲۹",
outputTon: 57,
fertilizerType: "اوره + هیومیک اسید",
status: "draft",
},
];
const relatedIrrigationPlans: RelatedPlanItem[] = [ const formatDate = (value?: string | null) => {
{ if (!value) return "—";
id: 301,
title: "آبیاری قطره‌ای صبحگاهی", try {
finalProduct: "گوجه فرنگی ممتاز", return new Intl.DateTimeFormat("fa-IR", {
harvestTime: "۱۴۰۴/۰۶/۰۸", year: "numeric",
outputTon: 45, month: "2-digit",
methodLabel: "روش آبیاری: قطره‌ای", day: "2-digit",
status: "active", }).format(new Date(value));
}, } catch {
{ return value;
id: 302, }
title: "آبیاری تنظیم‌شده مرحله گلدهی", };
finalProduct: "گوجه فرنگی ممتاز",
harvestTime: "۱۴۰۴/۰۶/۱۰", const mapFertilizationPlanRow = (
outputTon: 49, plan: FertilizationPlanListItem,
methodLabel: "روش آبیاری: تیپ", detail?: FertilizationPlanDetail | null,
status: "draft", ): FertilizationPlanRow => ({
}, id: plan.plan_uuid,
{ planName: plan.title,
id: 303, finalProduct: plan.plant_name || plan.crop_id || "—",
title: "آبیاری تقویتی پیش از برداشت", harvestTime: formatDate(plan.created_at),
finalProduct: "گوجه فرنگی ممتاز", outputTon: 0,
harvestTime: "۱۴۰۴/۰۶/۱۴", fertilizerType: detail?.plan_payload?.items?.[0]?.name || plan.source_label || "—",
outputTon: 53, status: plan.is_active ? "active" : "draft",
methodLabel: "روش آبیاری: بارانی سبک", sourceLabel: plan.source_label || "—",
status: "draft", growthStage: plan.growth_stage || "—",
}, });
];
const FertilizationPlanPage = () => { const FertilizationPlanPage = () => {
const router = useRouter(); const { getFarmUuid } = useFarmHub();
const [plans, setPlans] = useState(mockFertilizationPlans); const [plans, setPlans] = useState<FertilizationPlanRow[]>([]);
const [minOutputTon, setMinOutputTon] = useState("0"); const [minOutputTon, setMinOutputTon] = useState("0");
const [selectedPlan, setSelectedPlan] = useState<FertilizationPlanRow | null>( const [selectedPlan, setSelectedPlan] = useState<FertilizationPlanRow | null>(null);
mockFertilizationPlans[0],
);
const [detailsOpen, setDetailsOpen] = useState(false); const [detailsOpen, setDetailsOpen] = useState(false);
const [selectedRelatedPlanId, setSelectedRelatedPlanId] = useState<number | null>( const [loading, setLoading] = useState(false);
relatedIrrigationPlans[0]?.id ?? null, 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 filteredPlans = useMemo(() => {
@@ -127,51 +100,134 @@ const FertilizationPlanPage = () => {
.sort((firstPlan, secondPlan) => secondPlan.outputTon - firstPlan.outputTon); .sort((firstPlan, secondPlan) => secondPlan.outputTon - firstPlan.outputTon);
}, [minOutputTon, plans]); }, [minOutputTon, plans]);
const handleActivate = (planId: number) => { const loadPlans = useCallback(async () => {
setPlans((currentPlans) => const farmUuid = getFarmUuid();
currentPlans.map((plan) => ({
...plan,
status: plan.id === planId ? "active" : "draft",
})),
);
const activePlan = plans.find((plan) => plan.id === planId); if (!farmUuid) {
setError("ابتدا مزرعه فعال را انتخاب کنید.");
if (activePlan) { setPlans([]);
setSelectedPlan({ ...activePlan, status: "active" });
}
};
const handleDelete = (planId: number) => {
setPlans((currentPlans) => currentPlans.filter((plan) => plan.id !== planId));
if (selectedPlan?.id === planId) {
setSelectedPlan(null); setSelectedPlan(null);
setDetailsOpen(false); 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 handleShowDetails = (plan: FertilizationPlanRow) => { const handleDelete = async (planId: string) => {
setSelectedPlan(plan); setActionLoadingId(planId);
setSelectedRelatedPlanId(relatedIrrigationPlans[0]?.id ?? null);
setDetailsOpen(true); 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 handleConfirmRelatedPlan = (relatedPlan: RelatedPlanItem) => { const handleShowDetails = async (plan: FertilizationPlanRow) => {
setDetailsOpen(false); setSelectedPlan(plan);
if (!selectedPlan) return; setDetailsOpen(true);
setDetailsLoading(true);
const query = new URLSearchParams({ try {
from: "fertilization-plan", const detail = await fertilizationPlanService.getPlanDetail(plan.id);
planType: "fertilization", setSelectedPlan(mapFertilizationPlanRow(detail, detail));
planId: String(selectedPlan.id), await loadRelatedPlans();
planName: selectedPlan.planName, } catch (detailError) {
relatedType: "irrigation", setError(
relatedPlanId: String(relatedPlan.id), detailError instanceof Error
relatedPlanName: relatedPlan.title, ? detailError.message
}); : "دریافت جزئیات برنامه کوددهی انجام نشد.",
);
router.push(`/yield-harvest?${query.toString()}`); setDetailsOpen(false);
} finally {
setDetailsLoading(false);
}
}; };
return ( return (
@@ -182,6 +238,9 @@ const FertilizationPlanPage = () => {
<Card> <Card>
<CardContent> <CardContent>
<Stack spacing={4}> <Stack spacing={4}>
{loading ? <LinearProgress sx={{ borderRadius: 999 }} /> : null}
{error ? <Alert severity="error">{error}</Alert> : null}
<Stack <Stack
direction={{ xs: "column", md: "row" }} direction={{ xs: "column", md: "row" }}
spacing={3} spacing={3}
@@ -191,7 +250,7 @@ const FertilizationPlanPage = () => {
<Box> <Box>
<Typography variant="h5">لیست برنامههای کوددهی</Typography> <Typography variant="h5">لیست برنامههای کوددهی</Typography>
<Typography color="text.secondary"> <Typography color="text.secondary">
نمایش همه برنامهها با داده ماک و امکان فیلتر بر اساس بیشترین تن خروجی محصول لیست برنامههای کوددهی ثبتشده برای مزرعه فعال
</Typography> </Typography>
</Box> </Box>
@@ -216,9 +275,9 @@ const FertilizationPlanPage = () => {
<TableRow> <TableRow>
<TableCell>نام برنامه</TableCell> <TableCell>نام برنامه</TableCell>
<TableCell>محصول نهایی</TableCell> <TableCell>محصول نهایی</TableCell>
<TableCell>زمان برداشت</TableCell> <TableCell>تاریخ ایجاد</TableCell>
<TableCell>تن خروجی</TableCell> <TableCell>مرحله رشد</TableCell>
<TableCell>نوع کود</TableCell> <TableCell>نوع کود / منبع</TableCell>
<TableCell>وضعیت</TableCell> <TableCell>وضعیت</TableCell>
<TableCell align="center">عملیات</TableCell> <TableCell align="center">عملیات</TableCell>
</TableRow> </TableRow>
@@ -229,7 +288,7 @@ const FertilizationPlanPage = () => {
<TableCell>{plan.planName}</TableCell> <TableCell>{plan.planName}</TableCell>
<TableCell>{plan.finalProduct}</TableCell> <TableCell>{plan.finalProduct}</TableCell>
<TableCell>{plan.harvestTime}</TableCell> <TableCell>{plan.harvestTime}</TableCell>
<TableCell>{plan.outputTon} تن</TableCell> <TableCell>{plan.growthStage}</TableCell>
<TableCell>{plan.fertilizerType}</TableCell> <TableCell>{plan.fertilizerType}</TableCell>
<TableCell> <TableCell>
<Chip <Chip
@@ -246,7 +305,7 @@ const FertilizationPlanPage = () => {
<IconButton <IconButton
color="success" color="success"
onClick={() => handleActivate(plan.id)} onClick={() => handleActivate(plan.id)}
disabled={plan.status === "active"} disabled={plan.status === "active" || actionLoadingId === plan.id}
> >
<i className="tabler-player-play text-[20px]" /> <i className="tabler-player-play text-[20px]" />
</IconButton> </IconButton>
@@ -266,6 +325,7 @@ const FertilizationPlanPage = () => {
<IconButton <IconButton
color="error" color="error"
onClick={() => handleDelete(plan.id)} onClick={() => handleDelete(plan.id)}
disabled={actionLoadingId === plan.id}
> >
<i className="tabler-trash text-[20px]" /> <i className="tabler-trash text-[20px]" />
</IconButton> </IconButton>
@@ -301,25 +361,9 @@ const FertilizationPlanPage = () => {
flexWrap="wrap" flexWrap="wrap"
> >
<Chip label={`محصول نهایی: ${selectedPlan.finalProduct}`} variant="tonal" /> <Chip label={`محصول نهایی: ${selectedPlan.finalProduct}`} variant="tonal" />
<Chip label={`زمان برداشت: ${selectedPlan.harvestTime}`} variant="tonal" /> <Chip label={`تاریخ ایجاد: ${selectedPlan.harvestTime}`} variant="tonal" />
<Chip label={`تن خروجی: ${selectedPlan.outputTon} تن`} variant="tonal" /> <Chip label={`مرحله رشد: ${selectedPlan.growthStage}`} variant="tonal" />
<Chip label={`نوع کود: ${selectedPlan.fertilizerType}`} variant="tonal" /> <Chip label={`نوع کود: ${selectedPlan.fertilizerType}`} variant="tonal" />
<Button
variant="outlined"
color="primary"
startIcon={<i className="tabler-list-details" />}
onClick={() => handleShowDetails(selectedPlan)}
>
مشاهده و انتخاب برنامه آبیاری
</Button>
<Button
variant="contained"
color="success"
startIcon={<i className="tabler-check" />}
onClick={() => handleActivate(selectedPlan.id)}
>
فعال کردن این برنامه
</Button>
</Stack> </Stack>
) : ( ) : (
<Alert severity="warning"> <Alert severity="warning">
@@ -332,6 +376,8 @@ const FertilizationPlanPage = () => {
</Card> </Card>
</Stack> </Stack>
{detailsLoading ? <LinearProgress sx={{ borderRadius: 999, mt: 2 }} /> : null}
{selectedPlan ? ( {selectedPlan ? (
<RelatedPlanSelector <RelatedPlanSelector
open={detailsOpen} open={detailsOpen}
@@ -340,18 +386,18 @@ const FertilizationPlanPage = () => {
description="برای تکمیل اجرای این برنامه کوددهی، یکی از برنامه‌های آبیاری مرتبط را انتخاب کنید." description="برای تکمیل اجرای این برنامه کوددهی، یکی از برنامه‌های آبیاری مرتبط را انتخاب کنید."
currentPlanName={selectedPlan.planName} currentPlanName={selectedPlan.planName}
currentPlanStatus={selectedPlan.status === "active" ? "برنامه فعال" : "آماده برای فعال‌سازی"} currentPlanStatus={selectedPlan.status === "active" ? "برنامه فعال" : "آماده برای فعال‌سازی"}
currentPlanOutput={`خروجی هدف: ${selectedPlan.outputTon} تن محصول نهایی`} currentPlanOutput={`منبع: ${selectedPlan.sourceLabel}`}
summaryItems={[ summaryItems={[
{ label: "محصول نهایی", value: selectedPlan.finalProduct }, { label: "محصول نهایی", value: selectedPlan.finalProduct },
{ label: "زمان برداشت", value: selectedPlan.harvestTime }, { label: "تاریخ ایجاد", value: selectedPlan.harvestTime },
{ label: "نوع کود", value: selectedPlan.fertilizerType }, { label: "جزئیات", value: selectedPlan.fertilizerType },
]} ]}
relatedTitle="برنامه‌های آبیاری پیشنهادی" relatedTitle="برنامه‌های آبیاری پیشنهادی"
relatedDescription="از بین برنامه‌های زیر، مناسب‌ترین برنامه آبیاری را برای این برنامه کوددهی انتخاب کنید." relatedDescription="از بین برنامه‌های زیر، مناسب‌ترین برنامه آبیاری را برای این برنامه کوددهی انتخاب کنید."
relatedPlans={relatedIrrigationPlans} relatedPlans={relatedPlans}
selectedRelatedPlanId={selectedRelatedPlanId} selectedRelatedPlanId={selectedRelatedPlanId}
onSelectRelatedPlan={setSelectedRelatedPlanId} onSelectRelatedPlan={(planId) => setSelectedRelatedPlanId(planId)}
onConfirm={handleConfirmRelatedPlan} onConfirm={() => setDetailsOpen(false)}
/> />
) : null} ) : null}
</> </>
@@ -1,6 +1,6 @@
"use client"; "use client";
import { useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import Alert from "@mui/material/Alert"; import Alert from "@mui/material/Alert";
@@ -11,6 +11,7 @@ import CardContent from "@mui/material/CardContent";
import Chip from "@mui/material/Chip"; import Chip from "@mui/material/Chip";
import Divider from "@mui/material/Divider"; import Divider from "@mui/material/Divider";
import IconButton from "@mui/material/IconButton"; import IconButton from "@mui/material/IconButton";
import LinearProgress from "@mui/material/LinearProgress";
import Stack from "@mui/material/Stack"; import Stack from "@mui/material/Stack";
import Table from "@mui/material/Table"; import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody"; import TableBody from "@mui/material/TableBody";
@@ -25,99 +26,77 @@ import FertilizationPlanParserPage from "@views/dashboards/farm/fertilizationPla
import RelatedPlanSelector, { import RelatedPlanSelector, {
type RelatedPlanItem, type RelatedPlanItem,
} from "@views/dashboards/farm/planSelector/RelatedPlanSelector"; } from "@views/dashboards/farm/planSelector/RelatedPlanSelector";
import { useFarmHub } from "@/hooks/useFarmHub";
import {
irrigationPlanService,
type IrrigationPlanDetail,
type IrrigationPlanListItem,
} from "@/libs/api/services/irrigationPlanService";
import { fertilizationPlanService } from "@/libs/api/services/fertilizationPlanService";
import CustomTextField from "@core/components/mui/TextField"; import CustomTextField from "@core/components/mui/TextField";
type IrrigationPlanRow = { type IrrigationPlanRow = {
id: number; id: string;
planName: string; planName: string;
finalProduct: string; finalProduct: string;
harvestTime: string; harvestTime: string;
outputTon: number; outputTon: number;
irrigationMethod: string; irrigationMethod: string;
status: "active" | "draft"; status: "active" | "draft";
sourceLabel: string;
growthStage: string;
createdAt: string;
}; };
const mockIrrigationPlans: IrrigationPlanRow[] = [ const PAGE_SIZE = 10;
{
id: 1,
planName: "برنامه آبیاری گوجه فرنگی گلخانه‌ای",
finalProduct: "گوجه فرنگی ممتاز",
harvestTime: "۱۴۰۴/۰۶/۱۰",
outputTon: 48,
irrigationMethod: "قطره‌ای",
status: "active",
},
{
id: 2,
planName: "برنامه آبیاری ذرت علوفه‌ای",
finalProduct: "ذرت علوفه‌ای درجه یک",
harvestTime: "۱۴۰۴/۰۵/۲۲",
outputTon: 36,
irrigationMethod: "بارانی",
status: "draft",
},
{
id: 3,
planName: "برنامه آبیاری خیار فضای باز",
finalProduct: "خیار صادراتی",
harvestTime: "۱۴۰۴/۰۴/۱۸",
outputTon: 28,
irrigationMethod: "تیپ",
status: "draft",
},
{
id: 4,
planName: "برنامه آبیاری هندوانه",
finalProduct: "هندوانه شیرین بازارپسند",
harvestTime: "۱۴۰۴/۰۵/۳۰",
outputTon: 55,
irrigationMethod: "قطره‌ای",
status: "draft",
},
];
const relatedFertilizationPlans: RelatedPlanItem[] = [ const formatDate = (value?: string | null) => {
{ if (!value) return "—";
id: 201,
title: "کوددهی آغاز رشد رویشی", try {
finalProduct: "گوجه فرنگی ممتاز", return new Intl.DateTimeFormat("fa-IR", {
harvestTime: "۱۴۰۴/۰۵/۲۰", year: "numeric",
outputTon: 42, month: "2-digit",
methodLabel: "نوع کود: NPK 20-20-20", day: "2-digit",
status: "active", }).format(new Date(value));
}, } catch {
{ return value;
id: 202, }
title: "کوددهی تقویت گلدهی", };
finalProduct: "گوجه فرنگی ممتاز",
harvestTime: "۱۴۰۴/۰۶/۰۵", const mapIrrigationPlanRow = (
outputTon: 47, plan: IrrigationPlanListItem,
methodLabel: "نوع کود: کلسیم بور", detail?: IrrigationPlanDetail | null,
status: "draft", ): IrrigationPlanRow => ({
}, id: plan.plan_uuid,
{ planName: plan.title,
id: 203, finalProduct: plan.plant_name || plan.crop_id || "—",
title: "کوددهی پتاس بالا برای رنگ‌گیری", harvestTime: formatDate(plan.created_at),
finalProduct: "گوجه فرنگی ممتاز", outputTon: 0,
harvestTime: "۱۴۰۴/۰۶/۱۲", irrigationMethod:
outputTon: 51, detail?.plan_payload?.plan?.durationMinutes !== undefined
methodLabel: "نوع کود: سولفات پتاسیم", ? `${detail.plan_payload.plan.durationMinutes} دقیقه`
status: "draft", : plan.source_label || "—",
}, status: plan.is_active ? "active" : "draft",
]; sourceLabel: plan.source_label || "—",
growthStage: plan.growth_stage || "—",
createdAt: plan.created_at,
});
const IrrigationPlanPage = () => { const IrrigationPlanPage = () => {
const router = useRouter(); const router = useRouter();
const [plans, setPlans] = useState(mockIrrigationPlans); const { getFarmUuid } = useFarmHub();
const [plans, setPlans] = useState<IrrigationPlanRow[]>([]);
const [minOutputTon, setMinOutputTon] = useState("0"); const [minOutputTon, setMinOutputTon] = useState("0");
const [selectedPlan, setSelectedPlan] = useState<IrrigationPlanRow | null>( const [selectedPlan, setSelectedPlan] = useState<IrrigationPlanRow | null>(null);
mockIrrigationPlans[0],
);
const [detailsOpen, setDetailsOpen] = useState(false); const [detailsOpen, setDetailsOpen] = useState(false);
const [selectedRelatedPlanId, setSelectedRelatedPlanId] = useState<number | null>( const [selectedRelatedPlanId, setSelectedRelatedPlanId] = useState<number | string | null>(null);
relatedFertilizationPlans[0]?.id ?? null, const [relatedPlans, setRelatedPlans] = useState<RelatedPlanItem[]>([]);
); 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 filteredPlans = useMemo(() => { const filteredPlans = useMemo(() => {
const minTon = Number(minOutputTon); const minTon = Number(minOutputTon);
@@ -127,34 +106,134 @@ const IrrigationPlanPage = () => {
.sort((firstPlan, secondPlan) => secondPlan.outputTon - firstPlan.outputTon); .sort((firstPlan, secondPlan) => secondPlan.outputTon - firstPlan.outputTon);
}, [minOutputTon, plans]); }, [minOutputTon, plans]);
const handleActivate = (planId: number) => { const loadPlans = useCallback(async () => {
setPlans((currentPlans) => const farmUuid = getFarmUuid();
currentPlans.map((plan) => ({
...plan,
status: plan.id === planId ? "active" : "draft",
})),
);
const activePlan = plans.find((plan) => plan.id === planId); if (!farmUuid) {
setError("ابتدا مزرعه فعال را انتخاب کنید.");
if (activePlan) { setPlans([]);
setSelectedPlan({ ...activePlan, status: "active" });
}
};
const handleDelete = (planId: number) => {
setPlans((currentPlans) => currentPlans.filter((plan) => plan.id !== planId));
if (selectedPlan?.id === planId) {
setSelectedPlan(null); setSelectedPlan(null);
setDetailsOpen(false); return;
}
setLoading(true);
setError(null);
try {
const response = await irrigationPlanService.getPlans(farmUuid, 1, PAGE_SIZE);
const mappedPlans = response.data.map((plan) => mapIrrigationPlanRow(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 fertilizationPlanService.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 irrigationPlanService.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 handleShowDetails = (plan: IrrigationPlanRow) => { const handleDelete = async (planId: string) => {
setActionLoadingId(planId);
try {
await irrigationPlanService.deletePlan(planId);
setPlans((currentPlans) => currentPlans.filter((plan) => plan.id !== planId));
if (selectedPlan?.id === planId) {
setSelectedPlan(null);
setDetailsOpen(false);
}
} catch (deleteError) {
setError(
deleteError instanceof Error
? deleteError.message
: "حذف برنامه آبیاری انجام نشد.",
);
} finally {
setActionLoadingId(null);
}
};
const handleShowDetails = async (plan: IrrigationPlanRow) => {
setSelectedPlan(plan); setSelectedPlan(plan);
setSelectedRelatedPlanId(relatedFertilizationPlans[0]?.id ?? null);
setDetailsOpen(true); setDetailsOpen(true);
setDetailsLoading(true);
try {
const detail = await irrigationPlanService.getPlanDetail(plan.id);
setSelectedPlan(mapIrrigationPlanRow(detail, detail));
await loadRelatedPlans();
} catch (detailError) {
setError(
detailError instanceof Error
? detailError.message
: "دریافت جزئیات برنامه آبیاری انجام نشد.",
);
} finally {
setDetailsLoading(false);
}
}; };
const handleConfirmRelatedPlan = (relatedPlan: RelatedPlanItem) => { const handleConfirmRelatedPlan = (relatedPlan: RelatedPlanItem) => {
@@ -185,6 +264,9 @@ const IrrigationPlanPage = () => {
<Card> <Card>
<CardContent> <CardContent>
<Stack spacing={4}> <Stack spacing={4}>
{loading ? <LinearProgress sx={{ borderRadius: 999 }} /> : null}
{error ? <Alert severity="error">{error}</Alert> : null}
<Stack <Stack
direction={{ xs: "column", md: "row" }} direction={{ xs: "column", md: "row" }}
spacing={3} spacing={3}
@@ -194,7 +276,7 @@ const IrrigationPlanPage = () => {
<Box> <Box>
<Typography variant="h5">لیست برنامههای آبیاری</Typography> <Typography variant="h5">لیست برنامههای آبیاری</Typography>
<Typography color="text.secondary"> <Typography color="text.secondary">
نمایش همه برنامهها با داده ماک و امکان فیلتر بر اساس بیشترین تن خروجی محصول لیست برنامههای آبیاری ثبتشده برای مزرعه فعال
</Typography> </Typography>
</Box> </Box>
@@ -219,9 +301,9 @@ const IrrigationPlanPage = () => {
<TableRow> <TableRow>
<TableCell>نام برنامه</TableCell> <TableCell>نام برنامه</TableCell>
<TableCell>محصول نهایی</TableCell> <TableCell>محصول نهایی</TableCell>
<TableCell>زمان برداشت</TableCell> <TableCell>تاریخ ایجاد</TableCell>
<TableCell>تن خروجی</TableCell> <TableCell>مرحله رشد</TableCell>
<TableCell>روش آبیاری</TableCell> <TableCell>منبع / جزئیات</TableCell>
<TableCell>وضعیت</TableCell> <TableCell>وضعیت</TableCell>
<TableCell align="center">عملیات</TableCell> <TableCell align="center">عملیات</TableCell>
</TableRow> </TableRow>
@@ -232,7 +314,7 @@ const IrrigationPlanPage = () => {
<TableCell>{plan.planName}</TableCell> <TableCell>{plan.planName}</TableCell>
<TableCell>{plan.finalProduct}</TableCell> <TableCell>{plan.finalProduct}</TableCell>
<TableCell>{plan.harvestTime}</TableCell> <TableCell>{plan.harvestTime}</TableCell>
<TableCell>{plan.outputTon} تن</TableCell> <TableCell>{plan.growthStage}</TableCell>
<TableCell>{plan.irrigationMethod}</TableCell> <TableCell>{plan.irrigationMethod}</TableCell>
<TableCell> <TableCell>
<Chip <Chip
@@ -249,7 +331,7 @@ const IrrigationPlanPage = () => {
<IconButton <IconButton
color="success" color="success"
onClick={() => handleActivate(plan.id)} onClick={() => handleActivate(plan.id)}
disabled={plan.status === "active"} disabled={plan.status === "active" || actionLoadingId === plan.id}
> >
<i className="tabler-player-play text-[20px]" /> <i className="tabler-player-play text-[20px]" />
</IconButton> </IconButton>
@@ -269,6 +351,7 @@ const IrrigationPlanPage = () => {
<IconButton <IconButton
color="error" color="error"
onClick={() => handleDelete(plan.id)} onClick={() => handleDelete(plan.id)}
disabled={actionLoadingId === plan.id}
> >
<i className="tabler-trash text-[20px]" /> <i className="tabler-trash text-[20px]" />
</IconButton> </IconButton>
@@ -304,9 +387,9 @@ const IrrigationPlanPage = () => {
flexWrap="wrap" flexWrap="wrap"
> >
<Chip label={`محصول نهایی: ${selectedPlan.finalProduct}`} variant="tonal" /> <Chip label={`محصول نهایی: ${selectedPlan.finalProduct}`} variant="tonal" />
<Chip label={`زمان برداشت: ${selectedPlan.harvestTime}`} variant="tonal" /> <Chip label={`تاریخ ایجاد: ${selectedPlan.harvestTime}`} variant="tonal" />
<Chip label={`تن خروجی: ${selectedPlan.outputTon} تن`} variant="tonal" /> <Chip label={`مرحله رشد: ${selectedPlan.growthStage}`} variant="tonal" />
<Chip label={`روش آبیاری: ${selectedPlan.irrigationMethod}`} variant="tonal" /> <Chip label={`جزئیات: ${selectedPlan.irrigationMethod}`} variant="tonal" />
<Button <Button
variant="outlined" variant="outlined"
color="primary" color="primary"
@@ -320,6 +403,7 @@ const IrrigationPlanPage = () => {
color="success" color="success"
startIcon={<i className="tabler-check" />} startIcon={<i className="tabler-check" />}
onClick={() => handleActivate(selectedPlan.id)} onClick={() => handleActivate(selectedPlan.id)}
disabled={actionLoadingId === selectedPlan.id}
> >
فعال کردن این برنامه فعال کردن این برنامه
</Button> </Button>
@@ -343,17 +427,17 @@ const IrrigationPlanPage = () => {
description="برای تکمیل اجرای این برنامه آبیاری، یکی از برنامه‌های کوددهی مرتبط را انتخاب کنید." description="برای تکمیل اجرای این برنامه آبیاری، یکی از برنامه‌های کوددهی مرتبط را انتخاب کنید."
currentPlanName={selectedPlan.planName} currentPlanName={selectedPlan.planName}
currentPlanStatus={selectedPlan.status === "active" ? "برنامه فعال" : "آماده برای فعال‌سازی"} currentPlanStatus={selectedPlan.status === "active" ? "برنامه فعال" : "آماده برای فعال‌سازی"}
currentPlanOutput={`خروجی هدف: ${selectedPlan.outputTon} تن محصول نهایی`} currentPlanOutput={`منبع: ${selectedPlan.sourceLabel}`}
summaryItems={[ summaryItems={[
{ label: "محصول نهایی", value: selectedPlan.finalProduct }, { label: "محصول نهایی", value: selectedPlan.finalProduct },
{ label: "زمان برداشت", value: selectedPlan.harvestTime }, { label: "تاریخ ایجاد", value: selectedPlan.harvestTime },
{ label: "روش آبیاری", value: selectedPlan.irrigationMethod }, { label: "جزئیات", value: selectedPlan.irrigationMethod },
]} ]}
relatedTitle="برنامه‌های کوددهی پیشنهادی" relatedTitle="برنامه‌های کوددهی پیشنهادی"
relatedDescription="از بین برنامه‌های زیر، مناسب‌ترین برنامه کوددهی را برای این برنامه آبیاری انتخاب کنید." relatedDescription="از بین برنامه‌های زیر، مناسب‌ترین برنامه کوددهی را برای این برنامه آبیاری انتخاب کنید."
relatedPlans={relatedFertilizationPlans} relatedPlans={relatedPlans}
selectedRelatedPlanId={selectedRelatedPlanId} selectedRelatedPlanId={selectedRelatedPlanId}
onSelectRelatedPlan={setSelectedRelatedPlanId} onSelectRelatedPlan={(planId) => setSelectedRelatedPlanId(planId)}
onConfirm={handleConfirmRelatedPlan} onConfirm={handleConfirmRelatedPlan}
/> />
) : null} ) : null}
@@ -20,23 +20,50 @@ const YieldHarvestPage = () => {
const selectionSummary = useMemo(() => { const selectionSummary = useMemo(() => {
const from = searchParams.get("from"); const from = searchParams.get("from");
const planType = searchParams.get("planType"); const planType = searchParams.get("planType");
const planId = searchParams.get("planId");
const planName = searchParams.get("planName"); const planName = searchParams.get("planName");
const relatedType = searchParams.get("relatedType"); const relatedType = searchParams.get("relatedType");
const relatedPlanId = searchParams.get("relatedPlanId");
const relatedPlanName = searchParams.get("relatedPlanName"); const relatedPlanName = searchParams.get("relatedPlanName");
if (!from || !planType || !planName || !relatedType || !relatedPlanName) { if (!from || !planType || !planId || !planName || !relatedType || !relatedPlanId || !relatedPlanName) {
return null; return null;
} }
return { return {
from, from,
planType, planType,
planId,
planName, planName,
relatedType, relatedType,
relatedPlanId,
relatedPlanName, relatedPlanName,
}; };
}, [searchParams]); }, [searchParams]);
const planContext = useMemo(() => {
if (!selectionSummary) return undefined;
const context: {
irrigationPlanId?: string;
fertilizationPlanId?: string;
} = {};
if (selectionSummary.planType === "irrigation") {
context.irrigationPlanId = selectionSummary.planId;
} else {
context.fertilizationPlanId = selectionSummary.planId;
}
if (selectionSummary.relatedType === "irrigation") {
context.irrigationPlanId = selectionSummary.relatedPlanId;
} else {
context.fertilizationPlanId = selectionSummary.relatedPlanId;
}
return context;
}, [selectionSummary]);
const handleBack = () => { const handleBack = () => {
const from = selectionSummary?.from; const from = selectionSummary?.from;
@@ -97,7 +124,7 @@ const YieldHarvestPage = () => {
</Card> </Card>
) : null} ) : null}
<PlantProductionPage /> <PlantProductionPage planContext={planContext} />
</Stack> </Stack>
); );
}; };
@@ -0,0 +1,97 @@
import { apiClient } from "../client";
const PREFIX = "/api/fertilization";
interface ApiResponse<T> {
code: number;
msg: string;
data: T;
}
export interface PlanPagination {
page: number;
page_size: number;
total_pages: number;
total_items: number;
has_next: boolean;
has_previous: boolean;
next: string | null;
previous: string | null;
}
interface PaginatedApiResponse<T> extends ApiResponse<T> {
pagination: PlanPagination;
}
export interface FertilizationPlanListItem {
plan_uuid: string;
source: string;
source_label: string;
title: string;
crop_id: string | null;
plant_name: string | null;
growth_stage: string | null;
is_active: boolean;
created_at: string;
}
export interface FertilizationPlanDetail extends FertilizationPlanListItem {
updated_at: string;
plan_payload: {
title?: string;
items?: Array<{
name?: string;
[key: string]: unknown;
}>;
[key: string]: unknown;
} | null;
}
async function unwrap<T>(promise: Promise<ApiResponse<T>>): Promise<T> {
const response = await promise;
return response.data;
}
export const fertilizationPlanService = {
async getPlans(farmUuid: string, page = 1, pageSize = 10) {
const response = await apiClient.get<
PaginatedApiResponse<FertilizationPlanListItem[]>
>(
`${PREFIX}/plans/?farm_uuid=${encodeURIComponent(farmUuid)}&page=${page}&page_size=${pageSize}`,
);
return {
data: response.data,
pagination: response.pagination,
};
},
getPlanDetail(planUuid: string): Promise<FertilizationPlanDetail> {
return unwrap(
apiClient.get<ApiResponse<FertilizationPlanDetail>>(
`${PREFIX}/plans/${encodeURIComponent(planUuid)}/`,
),
);
},
deletePlan(planUuid: string): Promise<{ plan_uuid: string; is_deleted: boolean }> {
return unwrap(
apiClient.delete<ApiResponse<{ plan_uuid: string; is_deleted: boolean }>>(
`${PREFIX}/plans/${encodeURIComponent(planUuid)}/`,
),
);
},
updatePlanStatus(
planUuid: string,
isActive: boolean,
): Promise<{ plan_uuid: string; is_active: boolean }> {
return unwrap(
apiClient.patch<ApiResponse<{ plan_uuid: string; is_active: boolean }>>(
`${PREFIX}/plans/${encodeURIComponent(planUuid)}/status/`,
{ is_active: isActive },
),
);
},
};
@@ -0,0 +1,96 @@
import { apiClient } from "../client";
const PREFIX = "/api/irrigation";
interface ApiResponse<T> {
code: number;
msg: string;
data: T;
}
export interface PlanPagination {
page: number;
page_size: number;
total_pages: number;
total_items: number;
has_next: boolean;
has_previous: boolean;
next: string | null;
previous: string | null;
}
interface PaginatedApiResponse<T> extends ApiResponse<T> {
pagination: PlanPagination;
}
export interface IrrigationPlanListItem {
plan_uuid: string;
source: string;
source_label: string;
title: string;
crop_id: string | null;
plant_name: string | null;
growth_stage: string | null;
is_active: boolean;
created_at: string;
}
export interface IrrigationPlanDetail extends IrrigationPlanListItem {
updated_at: string;
plan_payload: {
plan?: {
durationMinutes?: number;
[key: string]: unknown;
};
[key: string]: unknown;
} | null;
}
async function unwrap<T>(promise: Promise<ApiResponse<T>>): Promise<T> {
const response = await promise;
return response.data;
}
export const irrigationPlanService = {
async getPlans(farmUuid: string, page = 1, pageSize = 10) {
const response = await apiClient.get<
PaginatedApiResponse<IrrigationPlanListItem[]>
>(
`${PREFIX}/plans/?farm_uuid=${encodeURIComponent(farmUuid)}&page=${page}&page_size=${pageSize}`,
);
return {
data: response.data,
pagination: response.pagination,
};
},
getPlanDetail(planUuid: string): Promise<IrrigationPlanDetail> {
return unwrap(
apiClient.get<ApiResponse<IrrigationPlanDetail>>(
`${PREFIX}/plans/${encodeURIComponent(planUuid)}/`,
),
);
},
deletePlan(planUuid: string): Promise<{ plan_uuid: string; is_deleted: boolean }> {
return unwrap(
apiClient.delete<ApiResponse<{ plan_uuid: string; is_deleted: boolean }>>(
`${PREFIX}/plans/${encodeURIComponent(planUuid)}/`,
),
);
},
updatePlanStatus(
planUuid: string,
isActive: boolean,
): Promise<{ plan_uuid: string; is_active: boolean }> {
return unwrap(
apiClient.patch<ApiResponse<{ plan_uuid: string; is_active: boolean }>>(
`${PREFIX}/plans/${encodeURIComponent(planUuid)}/status/`,
{ is_active: isActive },
),
);
},
};
+44 -10
View File
@@ -113,6 +113,11 @@ interface ApiResponse<T> {
result?: T result?: T
} }
export interface YieldHarvestPlanContext {
irrigationPlanId?: string | number | null
fertilizationPlanId?: string | number | null
}
function extract<T>(res: ApiResponse<T> | T): T { function extract<T>(res: ApiResponse<T> | T): T {
if (res && typeof res === 'object') { if (res && typeof res === 'object') {
if ('data' in res) return (res as ApiResponse<T>).data if ('data' in res) return (res as ApiResponse<T>).data
@@ -122,6 +127,16 @@ function extract<T>(res: ApiResponse<T> | T): T {
return res as T return res as T
} }
function normalizePlanId(value: string | number | null | undefined): string | number | undefined {
if (value === null || value === undefined || value === '') return undefined
if (typeof value === 'number') return value
const parsed = Number(value)
return Number.isFinite(parsed) ? parsed : value
}
function asRecord(value: unknown): GenericRecord | undefined { function asRecord(value: unknown): GenericRecord | undefined {
return value && typeof value === 'object' && !Array.isArray(value) return value && typeof value === 'object' && !Array.isArray(value)
? (value as GenericRecord) ? (value as GenericRecord)
@@ -142,8 +157,19 @@ function toNumber(value: unknown): number | null {
return null return null
} }
function buildFarmPayload(farmUuid: string) { function buildFarmPayload(
return { farm_uuid: farmUuid } farmUuid: string,
planContext?: YieldHarvestPlanContext,
): Record<string, unknown> {
return {
farm_uuid: farmUuid,
...(normalizePlanId(planContext?.irrigationPlanId) !== undefined
? { irrigation_plan_id: normalizePlanId(planContext?.irrigationPlanId) }
: {}),
...(normalizePlanId(planContext?.fertilizationPlanId) !== undefined
? { fertilization_plan_id: normalizePlanId(planContext?.fertilizationPlanId) }
: {}),
}
} }
function buildSummaryQuery(farmUuid: string, options?: SummaryOptions) { function buildSummaryQuery(farmUuid: string, options?: SummaryOptions) {
@@ -864,10 +890,13 @@ function enrichSummaryData(
} }
export const yieldHarvestService = { export const yieldHarvestService = {
async getCurrentFarmChart(farmUuid: string): Promise<CurrentFarmChartResponse> { async getCurrentFarmChart(
farmUuid: string,
planContext?: YieldHarvestPlanContext,
): Promise<CurrentFarmChartResponse> {
const response = await apiClient.post< const response = await apiClient.post<
ApiResponse<CurrentFarmChartResponse> | CurrentFarmChartResponse ApiResponse<CurrentFarmChartResponse> | CurrentFarmChartResponse
>(`${PREFIX}/current-farm-chart/`, buildFarmPayload(farmUuid)) >(`${PREFIX}/current-farm-chart/`, buildFarmPayload(farmUuid, planContext))
return extract(response) return extract(response)
}, },
@@ -905,18 +934,22 @@ export const yieldHarvestService = {
async getHarvestPrediction( async getHarvestPrediction(
farmUuid: string, farmUuid: string,
planContext?: YieldHarvestPlanContext,
): Promise<HarvestPredictionResponse> { ): Promise<HarvestPredictionResponse> {
const response = await apiClient.post< const response = await apiClient.post<
ApiResponse<HarvestPredictionResponse> | HarvestPredictionResponse ApiResponse<HarvestPredictionResponse> | HarvestPredictionResponse
>(`${PREFIX}/harvest-prediction/`, buildFarmPayload(farmUuid)) >(`${PREFIX}/harvest-prediction/`, buildFarmPayload(farmUuid, planContext))
return extract(response) return extract(response)
}, },
async getYieldPrediction(farmUuid: string): Promise<YieldPredictionResponse> { async getYieldPrediction(
farmUuid: string,
planContext?: YieldHarvestPlanContext,
): Promise<YieldPredictionResponse> {
const response = await apiClient.post< const response = await apiClient.post<
ApiResponse<YieldPredictionResponse> | YieldPredictionResponse ApiResponse<YieldPredictionResponse> | YieldPredictionResponse
>(`${PREFIX}/yield-prediction/`, buildFarmPayload(farmUuid)) >(`${PREFIX}/yield-prediction/`, buildFarmPayload(farmUuid, planContext))
return extract(response) return extract(response)
}, },
@@ -935,14 +968,15 @@ export const yieldHarvestService = {
async getDashboardData( async getDashboardData(
farmUuid: string, farmUuid: string,
planContext?: YieldHarvestPlanContext,
options?: SummaryOptions, options?: SummaryOptions,
): Promise<YieldHarvestDashboardData> { ): Promise<YieldHarvestDashboardData> {
const [summaryResult, harvestResult, yieldResult, currentChartResult] = const [summaryResult, harvestResult, yieldResult, currentChartResult] =
await Promise.allSettled([ await Promise.allSettled([
this.getSummary(farmUuid, options), this.getSummary(farmUuid, options),
this.getHarvestPrediction(farmUuid), this.getHarvestPrediction(farmUuid, planContext),
this.getYieldPrediction(farmUuid), this.getYieldPrediction(farmUuid, planContext),
this.getCurrentFarmChart(farmUuid), this.getCurrentFarmChart(farmUuid, planContext),
]) ])
const summary = const summary =
@@ -5,6 +5,7 @@ import { useFarmHub } from "@/hooks/useFarmHub";
import { yieldHarvestService } from "@/libs/api/services/yieldHarvestService"; import { yieldHarvestService } from "@/libs/api/services/yieldHarvestService";
import type { import type {
CurrentFarmChartResponse, CurrentFarmChartResponse,
YieldHarvestPlanContext,
YieldHarvestSummary, YieldHarvestSummary,
} from "@/libs/api/services/yieldHarvestService"; } from "@/libs/api/services/yieldHarvestService";
@@ -17,7 +18,11 @@ import YieldSeasonHighlightsCard from "@views/dashboards/farm/YieldSeasonHighlig
import PlantSimulator from "@views/dashboards/farm/plantSimulator/PlantSimulator"; import PlantSimulator from "@views/dashboards/farm/plantSimulator/PlantSimulator";
import { mockYieldHarvestSummary } from "@views/dashboards/farm/yieldHarvestMockData"; import { mockYieldHarvestSummary } from "@views/dashboards/farm/yieldHarvestMockData";
const PlantProductionPage = () => { type PlantProductionPageProps = {
planContext?: YieldHarvestPlanContext;
};
const PlantProductionPage = ({ planContext }: PlantProductionPageProps) => {
const { farmHub } = useFarmHub(); const { farmHub } = useFarmHub();
const farmUuid = farmHub?.farm_uuid; const farmUuid = farmHub?.farm_uuid;
const [summary, setSummary] = const [summary, setSummary] =
@@ -36,7 +41,7 @@ const PlantProductionPage = () => {
setLoading(true); setLoading(true);
return yieldHarvestService return yieldHarvestService
.getDashboardData(farmUuid) .getDashboardData(farmUuid, planContext)
.then((data) => { .then((data) => {
setSummary(data.summary); setSummary(data.summary);
setCurrentFarmChart(data.currentFarmChart ?? null); setCurrentFarmChart(data.currentFarmChart ?? null);
@@ -46,7 +51,7 @@ const PlantProductionPage = () => {
setCurrentFarmChart(null); setCurrentFarmChart(null);
}) })
.finally(() => setLoading(false)); .finally(() => setLoading(false));
}, [farmUuid]); }, [farmUuid, planContext]);
useEffect(() => { useEffect(() => {
loadDashboard(); loadDashboard();
File diff suppressed because it is too large Load Diff
@@ -20,7 +20,7 @@ import Typography from "@mui/material/Typography";
import useMediaQuery from "@mui/material/useMediaQuery"; import useMediaQuery from "@mui/material/useMediaQuery";
export type RelatedPlanItem = { export type RelatedPlanItem = {
id: number; id: number | string;
title: string; title: string;
finalProduct: string; finalProduct: string;
harvestTime: string; harvestTime: string;
@@ -46,8 +46,8 @@ type RelatedPlanSelectorProps = {
relatedTitle: string; relatedTitle: string;
relatedDescription: string; relatedDescription: string;
relatedPlans: RelatedPlanItem[]; relatedPlans: RelatedPlanItem[];
selectedRelatedPlanId: number | null; selectedRelatedPlanId: number | string | null;
onSelectRelatedPlan: (planId: number) => void; onSelectRelatedPlan: (planId: number | string) => void;
onConfirm: (selectedPlan: RelatedPlanItem) => void; onConfirm: (selectedPlan: RelatedPlanItem) => void;
}; };