UPDATE
This commit is contained in:
@@ -38,6 +38,7 @@
|
||||
"recommendation": "توصیهها",
|
||||
"irrigationRecommendation": "توصیه آبیاری",
|
||||
"fertilizationRecommendation": "توصیه کوددهی",
|
||||
"fertilizationPlanParser": "برنامه آبیاری و کودهی",
|
||||
"aiAssistant": "دستیار هوشمند",
|
||||
"farmAiAssistant": "دستیار هوشمند مزرعه",
|
||||
"pestDetection": "تشخیص آفات گیاهی",
|
||||
@@ -151,7 +152,9 @@
|
||||
"yieldHarvest": "عملکرد و برداشت",
|
||||
"farmAlerts": "هشدارهای مزرعه",
|
||||
"pestDiseaseRisk": "ریسک آفات و بیماری",
|
||||
"farmerTodos": "کارهای روزانه",
|
||||
"economicOverview": "نمای اقتصادی",
|
||||
"farmWallet": "کیف پول مزرعه",
|
||||
"farmCalendar": "تقویم کشاورز",
|
||||
"sensorSection": "سنسورها",
|
||||
"sensor7In1": "سنسور خاک 7 در 1"
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import EconomicOverviewPageWrapper from "@views/dashboards/farm/EconomicOverviewPageWrapper";
|
||||
|
||||
const EconomyPage = () => {
|
||||
return <EconomicOverviewPageWrapper />;
|
||||
};
|
||||
|
||||
export default EconomyPage;
|
||||
@@ -0,0 +1,7 @@
|
||||
import FarmerTodoPage from "@views/dashboards/farm/todos/FarmerTodoPage";
|
||||
|
||||
const FarmerTodos = () => {
|
||||
return <FarmerTodoPage />;
|
||||
};
|
||||
|
||||
export default FarmerTodos;
|
||||
@@ -0,0 +1,7 @@
|
||||
import FertilizationPlanParserPage from "@views/dashboards/farm/fertilizationPlanParser/FertilizationPlanParserPage";
|
||||
|
||||
const FertilizationPlanPage = () => {
|
||||
return <FertilizationPlanParserPage />;
|
||||
};
|
||||
|
||||
export default FertilizationPlanPage;
|
||||
@@ -0,0 +1,7 @@
|
||||
import FarmWalletPage from "@views/dashboards/farm/wallet/FarmWalletPage";
|
||||
|
||||
const WalletPage = () => {
|
||||
return <FarmWalletPage />;
|
||||
};
|
||||
|
||||
export default WalletPage;
|
||||
@@ -1,5 +1,5 @@
|
||||
// React Imports
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
// MUI Imports
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
@@ -49,7 +49,7 @@ const RenderExpandIcon = ({
|
||||
);
|
||||
|
||||
const VerticalMenu = ({ scrollMenu }: Props) => {
|
||||
const t = useTranslations('navigation')
|
||||
const t = useTranslations("navigation");
|
||||
const theme = useTheme();
|
||||
const verticalNavOptions = useVerticalNav();
|
||||
|
||||
@@ -64,13 +64,13 @@ const VerticalMenu = ({ scrollMenu }: Props) => {
|
||||
<ScrollWrapper
|
||||
{...(isBreakpointReached
|
||||
? {
|
||||
className: "bs-full overflow-y-auto overflow-x-hidden",
|
||||
onScroll: (container) => scrollMenu(container, false),
|
||||
}
|
||||
className: "bs-full overflow-y-auto overflow-x-hidden",
|
||||
onScroll: (container) => scrollMenu(container, false),
|
||||
}
|
||||
: {
|
||||
options: { wheelPropagation: false, suppressScrollX: true },
|
||||
onScrollY: (container) => scrollMenu(container, true),
|
||||
})}
|
||||
options: { wheelPropagation: false, suppressScrollX: true },
|
||||
onScrollY: (container) => scrollMenu(container, true),
|
||||
})}
|
||||
>
|
||||
{/* Incase you also want to scroll NavHeader to scroll with Vertical Menu, remove NavHeader from above and paste it below this comment */}
|
||||
{/* Vertical Menu */}
|
||||
@@ -92,53 +92,91 @@ const VerticalMenu = ({ scrollMenu }: Props) => {
|
||||
href={`/dashboard`}
|
||||
icon={<i className="tabler-smart-home" />}
|
||||
>
|
||||
{t('dashboards')}
|
||||
{t("dashboards")}
|
||||
</MenuItem>
|
||||
<MenuSection label={t('farmDomain')}>
|
||||
<MenuItem href="/yield-harvest" icon={<i className="tabler-chart-line" />}>
|
||||
{t('yieldHarvest')}
|
||||
<MenuSection label={t("farmDomain")}>
|
||||
<MenuItem
|
||||
href="/yield-harvest"
|
||||
icon={<i className="tabler-chart-line" />}
|
||||
>
|
||||
{t("yieldHarvest")}
|
||||
</MenuItem>
|
||||
<MenuItem href="/farm-alerts" icon={<i className="tabler-alert-triangle" />}>
|
||||
{t('farmAlerts')}
|
||||
<MenuItem
|
||||
href="/farm-alerts"
|
||||
icon={<i className="tabler-alert-triangle" />}
|
||||
>
|
||||
{t("farmAlerts")}
|
||||
</MenuItem>
|
||||
<MenuItem href="/pest-risk" icon={<i className="tabler-bug" />}>
|
||||
{t('pestDiseaseRisk')}
|
||||
{t("pestDiseaseRisk")}
|
||||
</MenuItem>
|
||||
<MenuItem href="/farmer-calendar" icon={<i className="tabler-calendar-event" />}>
|
||||
{t('farmCalendar')}
|
||||
<MenuItem
|
||||
href="/farmer-todos"
|
||||
icon={<i className="tabler-checklist" />}
|
||||
>
|
||||
{t("farmerTodos")}
|
||||
</MenuItem>
|
||||
<MenuItem href="/wallet" icon={<i className="tabler-wallet" />}>
|
||||
{t("farmWallet")}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
href="/farmer-calendar"
|
||||
icon={<i className="tabler-calendar-event" />}
|
||||
>
|
||||
{t("farmCalendar")}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
href="/economy"
|
||||
icon={<i className="tabler-cash-banknote" />}
|
||||
>
|
||||
{t("economicOverview")}
|
||||
</MenuItem>
|
||||
</MenuSection>
|
||||
<MenuSection label={t('dataSection')}>
|
||||
<MenuSection label={t("dataSection")}>
|
||||
<MenuItem href="/water-data" icon={<i className="tabler-droplet" />}>
|
||||
{t('waterData')}
|
||||
{t("waterData")}
|
||||
</MenuItem>
|
||||
<MenuItem href="/soil-data" icon={<i className="tabler-seedling" />}>
|
||||
{t('soilData')}
|
||||
{t("soilData")}
|
||||
</MenuItem>
|
||||
<MenuItem href="/crop-zoning" icon={<i className="tabler-map-2" />}>
|
||||
{t('cropZoning')}
|
||||
{t("cropZoning")}
|
||||
</MenuItem>
|
||||
</MenuSection>
|
||||
<MenuSection label={t('sensorSection')}>
|
||||
<MenuSection label={t("sensorSection")}>
|
||||
<MenuItem href="/solid-sensor" icon={<i className="tabler-sensor" />}>
|
||||
{t('sensor7In1')}
|
||||
{t("sensor7In1")}
|
||||
</MenuItem>
|
||||
</MenuSection>
|
||||
|
||||
<MenuSection label={t('recommendation')}>
|
||||
<MenuItem href="/irrigation-recommendation" icon={<i className="tabler-droplet-half-2" />}>
|
||||
{t('irrigationRecommendation')}
|
||||
<MenuSection label={t("recommendation")}>
|
||||
<MenuItem
|
||||
href="/irrigation-recommendation"
|
||||
icon={<i className="tabler-droplet-half-2" />}
|
||||
>
|
||||
{t("irrigationRecommendation")}
|
||||
</MenuItem>
|
||||
<MenuItem href="/fertilization-recommendation" icon={<i className="tabler-atom-2" />}>
|
||||
{t('fertilizationRecommendation')}
|
||||
<MenuItem
|
||||
href="/fertilization-recommendation"
|
||||
icon={<i className="tabler-atom-2" />}
|
||||
>
|
||||
{t("fertilizationRecommendation")}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
href="/fertilization-plan"
|
||||
icon={<i className="tabler-sparkles" />}
|
||||
>
|
||||
{t("fertilizationPlanParser")}
|
||||
</MenuItem>
|
||||
</MenuSection>
|
||||
<MenuSection label={t('aiAssistant')}>
|
||||
<MenuItem href="/farm-ai-assistant" icon={<i className="tabler-robot" />}>
|
||||
{t('farmAiAssistant')}
|
||||
<MenuSection label={t("aiAssistant")}>
|
||||
<MenuItem
|
||||
href="/farm-ai-assistant"
|
||||
icon={<i className="tabler-robot" />}
|
||||
>
|
||||
{t("farmAiAssistant")}
|
||||
</MenuItem>
|
||||
</MenuSection>
|
||||
|
||||
</Menu>
|
||||
{/* <Menu
|
||||
popoutMenuOffset={{ mainAxis: 23 }}
|
||||
|
||||
@@ -109,7 +109,9 @@
|
||||
"yieldHarvest": "الإنتاج والحصاد",
|
||||
"farmAlerts": "تنبيهات المزرعة",
|
||||
"pestDiseaseRisk": "مخاطر الآفات والأمراض",
|
||||
"farmerTodos": "مهام المزارع",
|
||||
"economicOverview": "النظرة الاقتصادية",
|
||||
"farmWallet": "محفظة المزرعة",
|
||||
"farmCalendar": "تقويم المزارع",
|
||||
"dataSection": "قسم البيانات",
|
||||
"waterData": "بيانات المياه",
|
||||
|
||||
@@ -109,7 +109,9 @@
|
||||
"yieldHarvest": "Yield & Harvest",
|
||||
"farmAlerts": "Farm Alerts",
|
||||
"pestDiseaseRisk": "Pest & Disease Risk",
|
||||
"farmerTodos": "Farmer Todos",
|
||||
"economicOverview": "Economic Overview",
|
||||
"farmWallet": "Farm Wallet",
|
||||
"farmCalendar": "Farmer Calendar",
|
||||
"dataSection": "Data Section",
|
||||
"waterData": "Water Data",
|
||||
|
||||
@@ -109,7 +109,9 @@
|
||||
"yieldHarvest": "عملکرد و برداشت",
|
||||
"farmAlerts": "هشدارهای مزرعه",
|
||||
"pestDiseaseRisk": "ریسک آفات و بیماری",
|
||||
"farmerTodos": "کارهای روزانه",
|
||||
"economicOverview": "نمای اقتصادی",
|
||||
"farmWallet": "کیف پول مزرعه",
|
||||
"farmCalendar": "تقویم کشاورز",
|
||||
"dataSection": "بخش دادهها",
|
||||
"waterData": "دیتاهای آب",
|
||||
|
||||
@@ -109,7 +109,9 @@
|
||||
"yieldHarvest": "Rendement et récolte",
|
||||
"farmAlerts": "Alertes ferme",
|
||||
"pestDiseaseRisk": "Risque ravageurs et maladies",
|
||||
"farmerTodos": "Taches de l'agriculteur",
|
||||
"economicOverview": "Aperçu économique",
|
||||
"farmWallet": "Portefeuille agricole",
|
||||
"farmCalendar": "Calendrier de l'agriculteur",
|
||||
"dataSection": "Section des données",
|
||||
"waterData": "Données sur l'eau",
|
||||
|
||||
@@ -70,6 +70,16 @@ const horizontalMenuData = (): HorizontalMenuDataType[] => [
|
||||
icon: 'tabler-bug',
|
||||
href: '/pest-risk'
|
||||
},
|
||||
{
|
||||
label: 'farmerTodos',
|
||||
icon: 'tabler-checklist',
|
||||
href: '/farmer-todos'
|
||||
},
|
||||
{
|
||||
label: 'farmWallet',
|
||||
icon: 'tabler-wallet',
|
||||
href: '/wallet'
|
||||
},
|
||||
{
|
||||
label: 'economicOverview',
|
||||
icon: 'tabler-cash-banknote',
|
||||
@@ -126,6 +136,16 @@ const horizontalMenuData = (): HorizontalMenuDataType[] => [
|
||||
icon: 'tabler-bug',
|
||||
href: '/pest-risk'
|
||||
},
|
||||
{
|
||||
label: 'farmerTodos',
|
||||
icon: 'tabler-checklist',
|
||||
href: '/farmer-todos'
|
||||
},
|
||||
{
|
||||
label: 'farmWallet',
|
||||
icon: 'tabler-wallet',
|
||||
href: '/wallet'
|
||||
},
|
||||
{
|
||||
label: 'economicOverview',
|
||||
icon: 'tabler-cash-banknote',
|
||||
|
||||
@@ -74,6 +74,16 @@ const verticalMenuData = (): VerticalMenuDataType[] => [
|
||||
icon: 'tabler-bug',
|
||||
href: '/pest-risk'
|
||||
},
|
||||
{
|
||||
label: 'farmerTodos',
|
||||
icon: 'tabler-checklist',
|
||||
href: '/farmer-todos'
|
||||
},
|
||||
{
|
||||
label: 'farmWallet',
|
||||
icon: 'tabler-wallet',
|
||||
href: '/wallet'
|
||||
},
|
||||
{
|
||||
label: 'economicOverview',
|
||||
icon: 'tabler-cash-banknote',
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { apiClient } from "../client";
|
||||
|
||||
const PREFIX = "/api/fertilization";
|
||||
|
||||
interface ApiResponse<T> {
|
||||
code: number;
|
||||
msg: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
export type FertilizationPlanParserStatus = "completed" | "needs_clarification";
|
||||
|
||||
export interface FertilizationPlanQuestion {
|
||||
id: string;
|
||||
field: string;
|
||||
question: string;
|
||||
rationale: string;
|
||||
}
|
||||
|
||||
export interface FertilizationPlanApplication {
|
||||
fertilizer_name: string | null;
|
||||
formula: string | null;
|
||||
amount: string | null;
|
||||
application_method: string | null;
|
||||
timing: string | null;
|
||||
interval_days: number | null;
|
||||
purpose: string | null;
|
||||
}
|
||||
|
||||
export interface FertilizationPlanData {
|
||||
crop_name: string | null;
|
||||
growth_stage: string | null;
|
||||
objective: string | null;
|
||||
applications: FertilizationPlanApplication[];
|
||||
notes: string[];
|
||||
}
|
||||
|
||||
export interface FertilizationPlanParserResult {
|
||||
status: FertilizationPlanParserStatus;
|
||||
status_fa: string;
|
||||
summary: string;
|
||||
missing_fields: string[];
|
||||
questions: FertilizationPlanQuestion[];
|
||||
collected_data: FertilizationPlanData;
|
||||
final_plan: FertilizationPlanData | null;
|
||||
}
|
||||
|
||||
export type FertilizationPlanAnswerValue = string | number | boolean | null;
|
||||
|
||||
export interface FertilizationPlanParserPayload {
|
||||
message?: string;
|
||||
answers?: Record<string, FertilizationPlanAnswerValue>;
|
||||
partial_plan?: FertilizationPlanData;
|
||||
farm_uuid?: string;
|
||||
}
|
||||
|
||||
async function unwrap<T>(promise: Promise<ApiResponse<T>>): Promise<T> {
|
||||
const response = await promise;
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const fertilizationPlanParserService = {
|
||||
parseFromText(
|
||||
payload: FertilizationPlanParserPayload,
|
||||
): Promise<FertilizationPlanParserResult> {
|
||||
return unwrap(
|
||||
apiClient.post<ApiResponse<FertilizationPlanParserResult>>(
|
||||
`${PREFIX}/plan-from-text/`,
|
||||
payload,
|
||||
),
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import { apiClient } from "../client";
|
||||
|
||||
const PREFIX = "/api/irrigation";
|
||||
|
||||
interface ApiResponse<T> {
|
||||
code: number;
|
||||
msg: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
export type IrrigationPlanParserStatus = "completed" | "needs_clarification";
|
||||
|
||||
export interface IrrigationPlanQuestion {
|
||||
id: string;
|
||||
field: string;
|
||||
question: string;
|
||||
rationale: string;
|
||||
}
|
||||
|
||||
export interface IrrigationPlanData {
|
||||
crop_name: string | null;
|
||||
growth_stage: string | null;
|
||||
irrigation_method: string | null;
|
||||
water_amount_per_event: string | null;
|
||||
duration_minutes: number | null;
|
||||
frequency_text: string | null;
|
||||
interval_days: number | null;
|
||||
preferred_time_of_day: string | null;
|
||||
start_date: string | null;
|
||||
target_area: string | null;
|
||||
trigger_conditions: string[];
|
||||
notes: string[];
|
||||
}
|
||||
|
||||
export interface IrrigationPlanParserResult {
|
||||
status: IrrigationPlanParserStatus;
|
||||
status_fa: string;
|
||||
summary: string;
|
||||
missing_fields: string[];
|
||||
questions: IrrigationPlanQuestion[];
|
||||
collected_data: IrrigationPlanData;
|
||||
final_plan: IrrigationPlanData | null;
|
||||
}
|
||||
|
||||
export type IrrigationPlanAnswerValue = string | number | boolean | null;
|
||||
|
||||
export interface IrrigationPlanParserPayload {
|
||||
message?: string;
|
||||
answers?: Record<string, IrrigationPlanAnswerValue>;
|
||||
partial_plan?: IrrigationPlanData;
|
||||
farm_uuid?: string;
|
||||
}
|
||||
|
||||
async function unwrap<T>(promise: Promise<ApiResponse<T>>): Promise<T> {
|
||||
const response = await promise;
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const irrigationPlanParserService = {
|
||||
parseFromText(
|
||||
payload: IrrigationPlanParserPayload,
|
||||
): Promise<IrrigationPlanParserResult> {
|
||||
return unwrap(
|
||||
apiClient.post<ApiResponse<IrrigationPlanParserResult>>(
|
||||
`${PREFIX}/plan-from-text/`,
|
||||
payload,
|
||||
),
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -1,60 +1,722 @@
|
||||
'use client'
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import Box from '@mui/material/Box'
|
||||
import CircularProgress from '@mui/material/CircularProgress'
|
||||
import Grid from '@mui/material/Grid2'
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import Card from "@mui/material/Card";
|
||||
import CardContent from "@mui/material/CardContent";
|
||||
import CardHeader from "@mui/material/CardHeader";
|
||||
import Chip from "@mui/material/Chip";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import Grid from "@mui/material/Grid2";
|
||||
import LinearProgress from "@mui/material/LinearProgress";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { alpha, useTheme } from "@mui/material/styles";
|
||||
|
||||
import { useFarmHub } from '@/hooks/useFarmHub'
|
||||
import { economicOverviewService } from '@/libs/api/services/economicOverviewService'
|
||||
import EconomicOverview from '@views/dashboards/farm/EconomicOverview'
|
||||
import type { ThemeColor } from "@core/types";
|
||||
|
||||
import { useFarmHub } from "@/hooks/useFarmHub";
|
||||
import { economicOverviewService } from "@/libs/api/services/economicOverviewService";
|
||||
import OptionMenu from "@core/components/option-menu";
|
||||
import CustomAvatar from "@core/components/mui/Avatar";
|
||||
import HorizontalWithAvatar from "@components/card-statistics/HorizontalWithAvatar";
|
||||
import Link from "@components/Link";
|
||||
import EconomicOverview from "@views/dashboards/farm/EconomicOverview";
|
||||
|
||||
const cardRowSx = {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
minHeight: 380,
|
||||
'& > *': { flex: 1, minHeight: 0 }
|
||||
}
|
||||
"& > *": { flex: 1, minHeight: 0 },
|
||||
};
|
||||
|
||||
type EconomicItem = {
|
||||
title: string;
|
||||
value: string;
|
||||
subtitle: string;
|
||||
avatarIcon: string;
|
||||
avatarColor: ThemeColor;
|
||||
};
|
||||
|
||||
type ChartSeriesItem = {
|
||||
name: string;
|
||||
data: number[];
|
||||
};
|
||||
|
||||
type FertilizerProductItem = {
|
||||
fertilizerCode: string;
|
||||
fertilizerName: string;
|
||||
productName: string;
|
||||
storeName: string;
|
||||
price: string;
|
||||
packageSize: string;
|
||||
storeUrl: string;
|
||||
color: ThemeColor;
|
||||
};
|
||||
|
||||
const fertilizerProductsMock: FertilizerProductItem[] = [
|
||||
{
|
||||
fertilizerCode: "NPK-20-20-20",
|
||||
fertilizerName: "کود کامل 20-20-20",
|
||||
productName: "NPK یونیورسال گرین پلاس",
|
||||
storeName: "فروشگاه سبزینه",
|
||||
price: "۱,۹۸۰,۰۰۰ تومان",
|
||||
packageSize: "کیسه 25 کیلوگرم",
|
||||
storeUrl: "/apps/ecommerce/products/list",
|
||||
color: "primary",
|
||||
},
|
||||
{
|
||||
fertilizerCode: "UREA-46",
|
||||
fertilizerName: "اوره 46 درصد",
|
||||
productName: "اوره دانه ای کشاورزی مهر",
|
||||
storeName: "مارکت نهاده یار",
|
||||
price: "۱,۴۲۰,۰۰۰ تومان",
|
||||
packageSize: "کیسه 50 کیلوگرم",
|
||||
storeUrl: "/apps/ecommerce/products/list",
|
||||
color: "success",
|
||||
},
|
||||
{
|
||||
fertilizerCode: "MAP-12-61",
|
||||
fertilizerName: "مونوآمونیوم فسفات",
|
||||
productName: "MAP خالص برای شروع رشد",
|
||||
storeName: "فروشگاه آگرومال",
|
||||
price: "۲,۳۵۰,۰۰۰ تومان",
|
||||
packageSize: "کیسه 25 کیلوگرم",
|
||||
storeUrl: "/apps/ecommerce/products/list",
|
||||
color: "warning",
|
||||
},
|
||||
];
|
||||
|
||||
const EconomicOverviewPageWrapper = () => {
|
||||
const { farmHub } = useFarmHub()
|
||||
const farmUuid = farmHub?.farm_uuid
|
||||
const [data, setData] = useState<Record<string, unknown>>({})
|
||||
const [loading, setLoading] = useState(true)
|
||||
const t = useTranslations("farmDashboard");
|
||||
const theme = useTheme();
|
||||
const { farmHub } = useFarmHub();
|
||||
const farmUuid = farmHub?.farm_uuid;
|
||||
const [data, setData] = useState<Record<string, unknown>>({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const economicData = (data?.economicData as EconomicItem[] | undefined) ?? [];
|
||||
const chartSeries =
|
||||
(data?.chartSeries as ChartSeriesItem[] | undefined) ?? [];
|
||||
|
||||
const headlineCards = useMemo(
|
||||
() =>
|
||||
economicData.slice(0, 3).map((item) => ({
|
||||
stats: item.value,
|
||||
title: item.title,
|
||||
avatarIcon: item.avatarIcon,
|
||||
avatarColor: item.avatarColor,
|
||||
})),
|
||||
[economicData],
|
||||
);
|
||||
|
||||
const portfolioMix = useMemo(() => {
|
||||
const fallbackColors: ThemeColor[] = [
|
||||
"primary",
|
||||
"info",
|
||||
"success",
|
||||
"warning",
|
||||
];
|
||||
|
||||
return chartSeries.map((series, index) => {
|
||||
const total = series.data.reduce((sum, current) => sum + current, 0);
|
||||
const max = chartSeries.reduce(
|
||||
(best, item) =>
|
||||
Math.max(
|
||||
best,
|
||||
item.data.reduce((sum, current) => sum + current, 0),
|
||||
),
|
||||
0,
|
||||
);
|
||||
|
||||
return {
|
||||
name: series.name,
|
||||
total,
|
||||
share: max > 0 ? Math.round((total / max) * 100) : 0,
|
||||
color: fallbackColors[index % fallbackColors.length],
|
||||
};
|
||||
});
|
||||
}, [chartSeries]);
|
||||
|
||||
const completionValue = useMemo(() => {
|
||||
const completed = economicData.filter(
|
||||
(item) => item.value && item.value !== "-",
|
||||
).length;
|
||||
|
||||
return economicData.length > 0
|
||||
? Math.round((completed / economicData.length) * 100)
|
||||
: 72;
|
||||
}, [economicData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!farmUuid) {
|
||||
setData({})
|
||||
setLoading(false)
|
||||
return
|
||||
setData({});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
setLoading(true);
|
||||
economicOverviewService
|
||||
.getSummary(farmUuid)
|
||||
.then(summary => setData((summary.economicOverview as Record<string, unknown>) ?? {}))
|
||||
.then((summary) =>
|
||||
setData((summary.economicOverview as Record<string, unknown>) ?? {}),
|
||||
)
|
||||
.catch(() => setData({}))
|
||||
.finally(() => setLoading(false))
|
||||
}, [farmUuid])
|
||||
.finally(() => setLoading(false));
|
||||
}, [farmUuid]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Box display='flex' justifyContent='center' alignItems='center' minHeight={200}>
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
minHeight={200}
|
||||
>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box position='relative'>
|
||||
<Box position="relative">
|
||||
<Grid container spacing={6}>
|
||||
<Grid size={12} sx={cardRowSx}>
|
||||
<Grid size={12}>
|
||||
<Card sx={{ overflow: "hidden" }}>
|
||||
<CardContent
|
||||
sx={{
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
p: { xs: 4, md: 6 },
|
||||
color: "common.white",
|
||||
background: `linear-gradient(135deg, ${theme.palette.success.main} 0%, ${theme.palette.primary.main} 45%, ${theme.palette.info.main} 100%)`,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
insetInlineEnd: -48,
|
||||
insetBlockStart: -56,
|
||||
inlineSize: 190,
|
||||
blockSize: 190,
|
||||
borderRadius: "50%",
|
||||
backgroundColor: alpha(theme.palette.common.white, 0.12),
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
insetInlineStart: "-10%",
|
||||
insetBlockEnd: "-62%",
|
||||
inlineSize: "42%",
|
||||
blockSize: 260,
|
||||
borderRadius: "50%",
|
||||
backgroundColor: alpha(theme.palette.common.white, 0.08),
|
||||
}}
|
||||
/>
|
||||
<Grid
|
||||
container
|
||||
spacing={5}
|
||||
sx={{ position: "relative", zIndex: 1, alignItems: "center" }}
|
||||
>
|
||||
<Grid size={{ xs: 12, lg: 7 }}>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Chip
|
||||
label="Economy Snapshot"
|
||||
size="small"
|
||||
sx={{
|
||||
width: "fit-content",
|
||||
color: "common.white",
|
||||
border: `1px solid ${alpha(theme.palette.common.white, 0.22)}`,
|
||||
backgroundColor: alpha(
|
||||
theme.palette.common.white,
|
||||
0.14,
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{ color: "common.white", fontWeight: 800, mb: 1.5 }}
|
||||
>
|
||||
صفحه اقتصاد مزرعه با همان کامپوننت های مالی موجود
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
color: alpha(theme.palette.common.white, 0.82),
|
||||
maxWidth: 760,
|
||||
}}
|
||||
>
|
||||
خلاصه عملکرد اقتصادی، روند درآمد و هزینه، و چند نمای
|
||||
سریع برای تصمیم گیری روزانه را در یک صفحه یکپارچه ببین.
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Box
|
||||
sx={{
|
||||
px: 2.5,
|
||||
py: 1.75,
|
||||
borderRadius: 4,
|
||||
border: `1px solid ${alpha(theme.palette.common.white, 0.2)}`,
|
||||
backgroundColor: alpha(
|
||||
theme.palette.common.white,
|
||||
0.12,
|
||||
),
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
color: alpha(theme.palette.common.white, 0.78),
|
||||
}}
|
||||
>
|
||||
وضعیت تصمیم گیری
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{ color: "common.white", fontWeight: 700 }}
|
||||
>
|
||||
{headlineCards[0]?.stats ??
|
||||
"در حال بارگذاری داده مالی"}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
px: 2.5,
|
||||
py: 1.75,
|
||||
borderRadius: 4,
|
||||
border: `1px solid ${alpha(theme.palette.common.white, 0.2)}`,
|
||||
backgroundColor: alpha(
|
||||
theme.palette.common.white,
|
||||
0.12,
|
||||
),
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
color: alpha(theme.palette.common.white, 0.78),
|
||||
}}
|
||||
>
|
||||
تمرکز این صفحه
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{ color: "common.white", fontWeight: 700 }}
|
||||
>
|
||||
هزینه، بازگشت سرمایه و سهم درآمد
|
||||
</Typography>
|
||||
</Box>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, lg: 5 }}>
|
||||
<Box
|
||||
sx={{
|
||||
p: 4,
|
||||
borderRadius: 5,
|
||||
border: `1px solid ${alpha(theme.palette.common.white, 0.18)}`,
|
||||
backgroundColor: alpha(theme.palette.common.white, 0.14),
|
||||
backdropFilter: "blur(14px)",
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-4 mbe-3">
|
||||
<div>
|
||||
<Typography
|
||||
sx={{
|
||||
color: alpha(theme.palette.common.white, 0.76),
|
||||
mb: 1,
|
||||
}}
|
||||
>
|
||||
آمادگی داده های اقتصادی
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{ color: "common.white", fontWeight: 800 }}
|
||||
>
|
||||
{completionValue}% تکمیل
|
||||
</Typography>
|
||||
</div>
|
||||
<CustomAvatar
|
||||
skin="light-static"
|
||||
color="success"
|
||||
size={42}
|
||||
>
|
||||
<i className="tabler-chart-histogram text-xl" />
|
||||
</CustomAvatar>
|
||||
</div>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={completionValue}
|
||||
sx={{
|
||||
mb: 3,
|
||||
blockSize: 8,
|
||||
borderRadius: 999,
|
||||
backgroundColor: alpha(
|
||||
theme.palette.common.white,
|
||||
0.18,
|
||||
),
|
||||
"& .MuiLinearProgress-bar": {
|
||||
borderRadius: 999,
|
||||
backgroundColor: theme.palette.common.white,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<div className="flex flex-col gap-3">
|
||||
{economicData.slice(0, 2).map((item) => (
|
||||
<div
|
||||
key={item.title}
|
||||
className="flex items-center justify-between gap-3"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<CustomAvatar
|
||||
skin="light-static"
|
||||
color={item.avatarColor}
|
||||
size={34}
|
||||
>
|
||||
<i className={item.avatarIcon} />
|
||||
</CustomAvatar>
|
||||
<div>
|
||||
<Typography
|
||||
sx={{ color: "common.white", fontWeight: 700 }}
|
||||
>
|
||||
{item.title}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
color: alpha(
|
||||
theme.palette.common.white,
|
||||
0.72,
|
||||
),
|
||||
}}
|
||||
>
|
||||
{item.subtitle}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<Typography
|
||||
sx={{ color: "common.white", fontWeight: 700 }}
|
||||
>
|
||||
{item.value}
|
||||
</Typography>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
{headlineCards.map((item) => (
|
||||
<Grid key={item.title} size={{ xs: 12, md: 4 }}>
|
||||
<HorizontalWithAvatar
|
||||
stats={item.stats}
|
||||
title={item.title}
|
||||
avatarIcon={item.avatarIcon}
|
||||
avatarColor={item.avatarColor}
|
||||
avatarSkin="light"
|
||||
avatarVariant="rounded"
|
||||
avatarSize={46}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
|
||||
<Grid size={{ xs: 12, xl: 8 }} sx={cardRowSx}>
|
||||
<EconomicOverview data={data} />
|
||||
</Grid>
|
||||
|
||||
<Grid size={{ xs: 12, xl: 4 }} sx={cardRowSx}>
|
||||
<Card className="bs-full">
|
||||
<CardHeader
|
||||
title="ترکیب جریان مالی"
|
||||
subheader={t("subheaders.costsAndRoi")}
|
||||
action={
|
||||
<OptionMenu
|
||||
options={[
|
||||
t("optionMenu.details"),
|
||||
t("optionMenu.exportExcel"),
|
||||
]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
{portfolioMix.length > 0 ? (
|
||||
portfolioMix.map((item) => (
|
||||
<Box key={item.name}>
|
||||
<div className="flex items-center justify-between gap-3 mbe-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<CustomAvatar skin="light" color={item.color} size={34}>
|
||||
<i className="tabler-coins text-lg" />
|
||||
</CustomAvatar>
|
||||
<Typography
|
||||
color="text.primary"
|
||||
className="font-medium"
|
||||
>
|
||||
{item.name}
|
||||
</Typography>
|
||||
</div>
|
||||
<Typography
|
||||
color="text.primary"
|
||||
className="font-semibold"
|
||||
>
|
||||
{item.total.toLocaleString("fa-IR")}
|
||||
</Typography>
|
||||
</div>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={Math.min(item.share, 100)}
|
||||
color={item.color}
|
||||
sx={{
|
||||
blockSize: 8,
|
||||
borderRadius: 999,
|
||||
backgroundColor: alpha(
|
||||
theme.palette[item.color].main,
|
||||
0.14,
|
||||
),
|
||||
"& .MuiLinearProgress-bar": { borderRadius: 999 },
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
))
|
||||
) : (
|
||||
<Typography variant="body2">
|
||||
هنوز داده سری های اقتصادی برای نمایش سهم درآمد و هزینه در
|
||||
دسترس نیست.
|
||||
</Typography>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
<Grid size={{ xs: 12, lg: 6 }}>
|
||||
<Card className="bs-full">
|
||||
<CardHeader
|
||||
title="شاخص های مالی کلیدی"
|
||||
subheader="خلاصه سریع برای مرور قبل از تصمیم گیری"
|
||||
/>
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
{economicData.length > 0 ? (
|
||||
economicData.map((item) => (
|
||||
<Box
|
||||
key={item.title}
|
||||
sx={{
|
||||
p: 3,
|
||||
borderRadius: 4,
|
||||
border: `1px solid ${alpha(theme.palette.divider, 0.9)}`,
|
||||
backgroundColor: alpha(
|
||||
theme.palette[item.avatarColor].main,
|
||||
0.05,
|
||||
),
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<CustomAvatar
|
||||
skin="light"
|
||||
color={item.avatarColor}
|
||||
size={38}
|
||||
>
|
||||
<i className={item.avatarIcon} />
|
||||
</CustomAvatar>
|
||||
<div>
|
||||
<Typography
|
||||
color="text.primary"
|
||||
className="font-semibold"
|
||||
>
|
||||
{item.title}
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
{item.subtitle}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<Typography
|
||||
color="text.primary"
|
||||
className="font-semibold"
|
||||
>
|
||||
{item.value}
|
||||
</Typography>
|
||||
</div>
|
||||
</Box>
|
||||
))
|
||||
) : (
|
||||
<Typography variant="body2">
|
||||
هنوز شاخص مالی برای این مزرعه دریافت نشده است.
|
||||
</Typography>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
<Grid size={{ xs: 12, lg: 6 }}>
|
||||
<Card className="bs-full">
|
||||
<CardHeader
|
||||
title="خرید محصول بر اساس کد کود"
|
||||
subheader="بر اساس کود مصرفی، نزدیک ترین پیشنهاد خرید و قیمت نمایش داده می شود."
|
||||
action={
|
||||
<OptionMenu
|
||||
options={[
|
||||
"مرتب سازی بر اساس قیمت",
|
||||
"فقط محصولات پیشنهادی",
|
||||
"مشاهده همه فروشگاه ها",
|
||||
]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
{fertilizerProductsMock.map((item) => (
|
||||
<Box
|
||||
key={item.fertilizerCode}
|
||||
sx={{
|
||||
p: 3,
|
||||
borderRadius: 4,
|
||||
border: `1px solid ${alpha(theme.palette[item.color].main, 0.18)}`,
|
||||
background: `linear-gradient(135deg, ${alpha(theme.palette[item.color].main, 0.08)} 0%, ${alpha(theme.palette.background.paper, 0.96)} 100%)`,
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div className="flex items-start gap-3">
|
||||
<CustomAvatar skin="light" color={item.color} size={42}>
|
||||
<i className="tabler-shopping-bag text-xl" />
|
||||
</CustomAvatar>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Typography
|
||||
color="text.primary"
|
||||
className="font-semibold"
|
||||
>
|
||||
{item.productName}
|
||||
</Typography>
|
||||
<Chip
|
||||
label={item.fertilizerCode}
|
||||
size="small"
|
||||
color={item.color}
|
||||
variant="tonal"
|
||||
/>
|
||||
</div>
|
||||
<Typography variant="body2">
|
||||
{item.fertilizerName}
|
||||
</Typography>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Chip
|
||||
icon={
|
||||
<i className="tabler-building-store text-sm" />
|
||||
}
|
||||
label={item.storeName}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
/>
|
||||
<Chip
|
||||
icon={<i className="tabler-package text-sm" />}
|
||||
label={item.packageSize}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
minInlineSize: { xs: "100%", lg: 220 },
|
||||
p: 2.5,
|
||||
borderRadius: 4,
|
||||
backgroundColor: alpha(
|
||||
theme.palette[item.color].main,
|
||||
0.08,
|
||||
),
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" className="mbe-1">
|
||||
قیمت امروز
|
||||
</Typography>
|
||||
<Typography
|
||||
color="text.primary"
|
||||
variant="h6"
|
||||
className="font-semibold mbe-3"
|
||||
>
|
||||
{item.price}
|
||||
</Typography>
|
||||
<Button
|
||||
fullWidth
|
||||
component={Link}
|
||||
href={item.storeUrl}
|
||||
variant="contained"
|
||||
color={item.color}
|
||||
endIcon={<i className="tabler-external-link text-lg" />}
|
||||
>
|
||||
لینک فروشگاه
|
||||
</Button>
|
||||
</Box>
|
||||
</div>
|
||||
</Box>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
<Grid size={{ xs: 12, lg: 6 }}>
|
||||
<Card className="bs-full">
|
||||
<CardHeader
|
||||
title="یادداشت تحلیلی"
|
||||
subheader="خروجی سبک برای مرور سریع مدیر مزرعه"
|
||||
/>
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
<Box
|
||||
sx={{
|
||||
p: 3,
|
||||
borderRadius: 4,
|
||||
border: `1px dashed ${alpha(theme.palette.primary.main, 0.24)}`,
|
||||
backgroundColor: alpha(theme.palette.primary.main, 0.05),
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<CustomAvatar skin="light" color="primary" size={38}>
|
||||
<i className="tabler-sparkles text-lg" />
|
||||
</CustomAvatar>
|
||||
<div>
|
||||
<Typography
|
||||
color="text.primary"
|
||||
className="font-semibold mbe-1"
|
||||
>
|
||||
جمع بندی سریع
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
این صفحه با همان کامپوننت اقتصادی موجود ساخته شده و نمای
|
||||
خلاصه، کارت های سریع و وضعیت ترکیب جریان مالی را در کنار
|
||||
چارت اصلی نشان می دهد.
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
p: 3,
|
||||
borderRadius: 4,
|
||||
border: `1px solid ${alpha(theme.palette.success.main, 0.18)}`,
|
||||
backgroundColor: alpha(theme.palette.success.main, 0.05),
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
color="text.primary"
|
||||
className="font-semibold mbe-1"
|
||||
>
|
||||
پیشنهاد استفاده
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
اگر خواستی مرحله بعدی می شود این صفحه را به داده های ریزتر مثل
|
||||
هزینه آبیاری، حمل، کوددهی و درآمد محصول به تفکیک فصل وصل کرد.
|
||||
</Typography>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default EconomicOverviewPageWrapper
|
||||
export default EconomicOverviewPageWrapper;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,665 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import Card from "@mui/material/Card";
|
||||
import CardContent from "@mui/material/CardContent";
|
||||
import CardHeader from "@mui/material/CardHeader";
|
||||
import Checkbox from "@mui/material/Checkbox";
|
||||
import Chip from "@mui/material/Chip";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import Grid from "@mui/material/Grid2";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { alpha, useTheme } from "@mui/material/styles";
|
||||
|
||||
import classnames from "classnames";
|
||||
|
||||
import type { ThemeColor } from "@core/types";
|
||||
|
||||
import OptionMenu from "@core/components/option-menu";
|
||||
import CustomAvatar from "@core/components/mui/Avatar";
|
||||
import CustomTextField from "@core/components/mui/TextField";
|
||||
import HorizontalWithAvatar from "@components/card-statistics/HorizontalWithAvatar";
|
||||
|
||||
type TaskPriority = "زیاد" | "متوسط" | "کم";
|
||||
type TaskStatus = "open" | "done";
|
||||
type TaskSegment = "همه" | "امروز" | "فوری" | "انجام شده";
|
||||
|
||||
type FarmerTask = {
|
||||
id: number;
|
||||
title: string;
|
||||
zone: string;
|
||||
time: string;
|
||||
priority: TaskPriority;
|
||||
note: string;
|
||||
tags: string[];
|
||||
status: TaskStatus;
|
||||
};
|
||||
|
||||
const initialTasks: FarmerTask[] = [
|
||||
{
|
||||
id: 1,
|
||||
title: "بررسی رطوبت ردیف شمالی و تنظیم آبیاری قطره ای",
|
||||
zone: "قطعه گندم - شمال مزرعه",
|
||||
time: "06:30",
|
||||
priority: "زیاد",
|
||||
note: "اگر رطوبت کمتر از 28٪ بود، دور دوم آبیاری را 20 دقیقه جلو بینداز.",
|
||||
tags: ["آبیاری", "صبح زود"],
|
||||
status: "open",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "نمونه برداری خاک برای بخش سبزیجات",
|
||||
zone: "گلخانه شماره 2",
|
||||
time: "09:15",
|
||||
priority: "متوسط",
|
||||
note: "سه نقطه از بستر برداشت شود و برای آزمایش فسفر ثبت گردد.",
|
||||
tags: ["خاک", "آزمایش"],
|
||||
status: "open",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "هماهنگی با راننده برای ارسال بار جو",
|
||||
zone: "انبار مرکزی",
|
||||
time: "12:00",
|
||||
priority: "کم",
|
||||
note: "وزن نهایی بارنامه قبل از خروج با باسکول تطبیق داده شود.",
|
||||
tags: ["لجستیک", "فروش"],
|
||||
status: "done",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "بازدید سریع برای نشانه های آفت روی برگ های تازه",
|
||||
zone: "باغچه آزمایشی غربی",
|
||||
time: "17:40",
|
||||
priority: "زیاد",
|
||||
note: "فقط لکه های جدید را علامت بزن و برای تیم سمپاشی عکس بگیر.",
|
||||
tags: ["آفت", "بازدید عصر"],
|
||||
status: "open",
|
||||
},
|
||||
];
|
||||
|
||||
const priorityMeta: Record<TaskPriority, { color: ThemeColor; icon: string }> =
|
||||
{
|
||||
زیاد: { color: "error", icon: "tabler-alert-triangle" },
|
||||
متوسط: { color: "warning", icon: "tabler-sun-high" },
|
||||
کم: { color: "success", icon: "tabler-leaf" },
|
||||
};
|
||||
|
||||
const segments: TaskSegment[] = ["همه", "امروز", "فوری", "انجام شده"];
|
||||
|
||||
const FarmerTodoPage = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [tasks, setTasks] = useState(initialTasks);
|
||||
const [segment, setSegment] = useState<TaskSegment>("همه");
|
||||
const [draftTitle, setDraftTitle] = useState("");
|
||||
const [draftZone, setDraftZone] = useState("قطعه گندم - شمال مزرعه");
|
||||
const [draftTime, setDraftTime] = useState("07:00");
|
||||
const [draftPriority, setDraftPriority] = useState<TaskPriority>("متوسط");
|
||||
|
||||
const completedCount = useMemo(
|
||||
() => tasks.filter((task) => task.status === "done").length,
|
||||
[tasks],
|
||||
);
|
||||
const openCount = tasks.length - completedCount;
|
||||
const urgentCount = useMemo(
|
||||
() =>
|
||||
tasks.filter((task) => task.priority === "زیاد" && task.status === "open")
|
||||
.length,
|
||||
[tasks],
|
||||
);
|
||||
const progressValue =
|
||||
tasks.length === 0 ? 0 : Math.round((completedCount / tasks.length) * 100);
|
||||
|
||||
const filteredTasks = useMemo(() => {
|
||||
switch (segment) {
|
||||
case "فوری":
|
||||
return tasks.filter(
|
||||
(task) => task.priority === "زیاد" && task.status === "open",
|
||||
);
|
||||
case "انجام شده":
|
||||
return tasks.filter((task) => task.status === "done");
|
||||
case "امروز":
|
||||
return tasks;
|
||||
default:
|
||||
return tasks;
|
||||
}
|
||||
}, [segment, tasks]);
|
||||
|
||||
const stats = [
|
||||
{
|
||||
stats: `${openCount} کار`,
|
||||
title: "کارهای باز امروز",
|
||||
avatarIcon: "tabler-plant-2",
|
||||
avatarColor: "primary" as const,
|
||||
},
|
||||
{
|
||||
stats: `${completedCount} مورد`,
|
||||
title: "انجام شده تا الان",
|
||||
avatarIcon: "tabler-circle-check",
|
||||
avatarColor: "success" as const,
|
||||
},
|
||||
{
|
||||
stats: `${urgentCount} تسک`,
|
||||
title: "اولویت خیلی بالا",
|
||||
avatarIcon: "tabler-bolt",
|
||||
avatarColor: "error" as const,
|
||||
},
|
||||
];
|
||||
|
||||
const toggleTask = (taskId: number) => {
|
||||
setTasks((currentTasks) =>
|
||||
currentTasks.map((task) =>
|
||||
task.id === taskId
|
||||
? { ...task, status: task.status === "done" ? "open" : "done" }
|
||||
: task,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const addTask = () => {
|
||||
if (!draftTitle.trim()) return;
|
||||
|
||||
setTasks((currentTasks) => [
|
||||
{
|
||||
id: Date.now(),
|
||||
title: draftTitle.trim(),
|
||||
zone: draftZone,
|
||||
time: draftTime,
|
||||
priority: draftPriority,
|
||||
note: "بعد از ثبت انجام، اگر موردی غیرعادی بود برای شیفت بعدی یادداشت بگذار.",
|
||||
tags: [draftPriority === "زیاد" ? "فوری" : "روزانه", "ثبت دستی"],
|
||||
status: "open",
|
||||
},
|
||||
...currentTasks,
|
||||
]);
|
||||
|
||||
setDraftTitle("");
|
||||
setDraftTime("07:00");
|
||||
setDraftPriority("متوسط");
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid container spacing={6}>
|
||||
<Grid size={12}>
|
||||
<Card sx={{ overflow: "hidden" }}>
|
||||
<CardContent
|
||||
sx={{
|
||||
position: "relative",
|
||||
p: { xs: 4, md: 6 },
|
||||
overflow: "hidden",
|
||||
background: `linear-gradient(135deg, ${theme.palette.success.main} 0%, ${theme.palette.primary.main} 52%, ${theme.palette.info.main} 100%)`,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
insetInlineEnd: -50,
|
||||
insetBlockStart: -60,
|
||||
inlineSize: 200,
|
||||
blockSize: 200,
|
||||
borderRadius: "50%",
|
||||
backgroundColor: alpha(theme.palette.common.white, 0.12),
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
insetInlineStart: "-10%",
|
||||
insetBlockEnd: "-58%",
|
||||
inlineSize: "45%",
|
||||
blockSize: 240,
|
||||
borderRadius: "50%",
|
||||
backgroundColor: alpha(theme.palette.common.white, 0.08),
|
||||
}}
|
||||
/>
|
||||
<Grid
|
||||
container
|
||||
spacing={5}
|
||||
sx={{ position: "relative", zIndex: 1, alignItems: "center" }}
|
||||
>
|
||||
<Grid size={{ xs: 12, lg: 8 }}>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Chip
|
||||
label="برنامه روز کشاورز"
|
||||
size="small"
|
||||
sx={{
|
||||
width: "fit-content",
|
||||
color: "common.white",
|
||||
border: `1px solid ${alpha(theme.palette.common.white, 0.2)}`,
|
||||
backgroundColor: alpha(theme.palette.common.white, 0.12),
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{ color: "common.white", fontWeight: 800, mb: 1.5 }}
|
||||
>
|
||||
تودولیست ساده، تمیز و کاربردی برای مدیریت کارهای مزرعه
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
color: alpha(theme.palette.common.white, 0.84),
|
||||
maxWidth: 720,
|
||||
}}
|
||||
>
|
||||
کارهای مهم روز، بازدیدهای میدانی و کارهای پیگیری را یکجا
|
||||
نگه دار تا بین آبیاری، خاک، برداشت و هماهنگی تیم چیزی از
|
||||
قلم نیفتد.
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Box
|
||||
sx={{
|
||||
px: 2.5,
|
||||
py: 1.75,
|
||||
borderRadius: 4,
|
||||
border: `1px solid ${alpha(theme.palette.common.white, 0.2)}`,
|
||||
backgroundColor: alpha(
|
||||
theme.palette.common.white,
|
||||
0.12,
|
||||
),
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ color: alpha(theme.palette.common.white, 0.78) }}
|
||||
>
|
||||
شروع روز
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{ color: "common.white", fontWeight: 700 }}
|
||||
>
|
||||
بازدید 06:30 از ردیف شمالی
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
px: 2.5,
|
||||
py: 1.75,
|
||||
borderRadius: 4,
|
||||
border: `1px solid ${alpha(theme.palette.common.white, 0.2)}`,
|
||||
backgroundColor: alpha(
|
||||
theme.palette.common.white,
|
||||
0.12,
|
||||
),
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ color: alpha(theme.palette.common.white, 0.78) }}
|
||||
>
|
||||
تمرکز امروز
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{ color: "common.white", fontWeight: 700 }}
|
||||
>
|
||||
آبیاری، آفت و هماهنگی بار خروجی
|
||||
</Typography>
|
||||
</Box>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, lg: 4 }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 2.5,
|
||||
p: 4,
|
||||
borderRadius: 5,
|
||||
border: `1px solid ${alpha(theme.palette.common.white, 0.18)}`,
|
||||
backgroundColor: alpha(theme.palette.common.white, 0.14),
|
||||
backdropFilter: "blur(14px)",
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<Typography
|
||||
sx={{
|
||||
color: alpha(theme.palette.common.white, 0.78),
|
||||
mb: 1,
|
||||
}}
|
||||
>
|
||||
پیشرفت امروز
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{ color: "common.white", fontWeight: 800 }}
|
||||
>
|
||||
{progressValue}% تکمیل شده
|
||||
</Typography>
|
||||
</div>
|
||||
<Box sx={{ position: "relative", display: "inline-flex" }}>
|
||||
<CircularProgress
|
||||
variant="determinate"
|
||||
value={progressValue}
|
||||
size={74}
|
||||
thickness={5}
|
||||
sx={{ color: "common.white" }}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
inset: 0,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{ color: "common.white", fontWeight: 700 }}
|
||||
>
|
||||
{progressValue}%
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</div>
|
||||
<Divider
|
||||
sx={{
|
||||
borderColor: alpha(theme.palette.common.white, 0.14),
|
||||
}}
|
||||
/>
|
||||
<div className="flex items-center gap-3">
|
||||
<CustomAvatar skin="light-static" color="success" size={42}>
|
||||
<i className="tabler-sunrise text-xl" />
|
||||
</CustomAvatar>
|
||||
<div>
|
||||
<Typography
|
||||
sx={{ color: "common.white", fontWeight: 700 }}
|
||||
>
|
||||
پنجره طلایی صبح
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ color: alpha(theme.palette.common.white, 0.78) }}
|
||||
>
|
||||
بهترین زمان برای آبیاری و بازدید سریع تا قبل از اوج
|
||||
گرما.
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
{stats.map((item) => (
|
||||
<Grid key={item.title} size={{ xs: 12, md: 4 }}>
|
||||
<HorizontalWithAvatar
|
||||
stats={item.stats}
|
||||
title={item.title}
|
||||
avatarIcon={item.avatarIcon}
|
||||
avatarColor={item.avatarColor}
|
||||
avatarSkin="light"
|
||||
avatarVariant="rounded"
|
||||
avatarSize={46}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
|
||||
<Grid size={{ xs: 12, xl: 8 }}>
|
||||
<Card>
|
||||
<CardHeader
|
||||
title="لیست کارهای امروز"
|
||||
subheader="کارت ها را تیک بزن تا تمرکز تیم روی کارهای باقی مانده بماند."
|
||||
action={
|
||||
<OptionMenu
|
||||
options={[
|
||||
"نمای روز",
|
||||
"مرتب سازی بر اساس ساعت",
|
||||
"فقط موارد باز",
|
||||
]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{segments.map((item) => (
|
||||
<Chip
|
||||
key={item}
|
||||
clickable
|
||||
label={item}
|
||||
color={segment === item ? "primary" : "default"}
|
||||
variant={segment === item ? "filled" : "tonal"}
|
||||
onClick={() => setSegment(item)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
{filteredTasks.map((task) => {
|
||||
const meta = priorityMeta[task.priority];
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={task.id}
|
||||
sx={{
|
||||
p: 3,
|
||||
borderRadius: 5,
|
||||
border: `1px solid ${alpha(theme.palette.divider, 0.9)}`,
|
||||
backgroundColor:
|
||||
task.status === "done"
|
||||
? alpha(theme.palette.success.main, 0.06)
|
||||
: alpha(theme.palette.background.default, 0.7),
|
||||
transition: "all .2s ease",
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
||||
<div className="flex items-start gap-3">
|
||||
<Checkbox
|
||||
checked={task.status === "done"}
|
||||
onChange={() => toggleTask(task.id)}
|
||||
sx={{ mt: -0.5 }}
|
||||
/>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Typography
|
||||
color="text.primary"
|
||||
className={classnames("font-semibold", {
|
||||
"line-through opacity-60":
|
||||
task.status === "done",
|
||||
})}
|
||||
>
|
||||
{task.title}
|
||||
</Typography>
|
||||
<Chip
|
||||
label={task.priority}
|
||||
size="small"
|
||||
color={meta.color}
|
||||
variant="tonal"
|
||||
/>
|
||||
</div>
|
||||
<Typography variant="body2">{task.note}</Typography>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{task.tags.map((tag) => (
|
||||
<Chip
|
||||
key={tag}
|
||||
label={tag}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Box
|
||||
sx={{
|
||||
minInlineSize: { xs: "100%", lg: 220 },
|
||||
p: 2.5,
|
||||
borderRadius: 4,
|
||||
backgroundColor: alpha(
|
||||
theme.palette[meta.color].main,
|
||||
0.08,
|
||||
),
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2 mbe-2">
|
||||
<CustomAvatar
|
||||
skin="light"
|
||||
color={meta.color}
|
||||
size={34}
|
||||
>
|
||||
<i className={classnames(meta.icon, "text-lg")} />
|
||||
</CustomAvatar>
|
||||
<div>
|
||||
<Typography
|
||||
color="text.primary"
|
||||
className="font-semibold"
|
||||
>
|
||||
{task.time}
|
||||
</Typography>
|
||||
<Typography variant="body2">{task.zone}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<Typography variant="body2">
|
||||
{task.status === "done"
|
||||
? "انجام شده و ثبت شده"
|
||||
: "منتظر اقدام تیم مزرعه"}
|
||||
</Typography>
|
||||
</Box>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
<Grid size={{ xs: 12, xl: 4 }}>
|
||||
<div className="flex flex-col gap-6">
|
||||
<Card>
|
||||
<CardHeader
|
||||
title="افزودن کار جدید"
|
||||
subheader="خیلی سبک و سریع برای ثبت تسک های روزانه"
|
||||
/>
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
label="عنوان کار"
|
||||
placeholder="مثلا بازدید پمپ جنوب"
|
||||
value={draftTitle}
|
||||
onChange={(event) => setDraftTitle(event.target.value)}
|
||||
/>
|
||||
<CustomTextField
|
||||
select
|
||||
fullWidth
|
||||
label="محل اجرا"
|
||||
value={draftZone}
|
||||
onChange={(event) => setDraftZone(event.target.value)}
|
||||
>
|
||||
<MenuItem value="قطعه گندم - شمال مزرعه">
|
||||
قطعه گندم - شمال مزرعه
|
||||
</MenuItem>
|
||||
<MenuItem value="گلخانه شماره 2">گلخانه شماره 2</MenuItem>
|
||||
<MenuItem value="انبار مرکزی">انبار مرکزی</MenuItem>
|
||||
<MenuItem value="باغچه آزمایشی غربی">
|
||||
باغچه آزمایشی غربی
|
||||
</MenuItem>
|
||||
</CustomTextField>
|
||||
<Grid container spacing={3}>
|
||||
<Grid size={6}>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
label="ساعت"
|
||||
value={draftTime}
|
||||
onChange={(event) => setDraftTime(event.target.value)}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={6}>
|
||||
<CustomTextField
|
||||
select
|
||||
fullWidth
|
||||
label="اولویت"
|
||||
value={draftPriority}
|
||||
onChange={(event) =>
|
||||
setDraftPriority(event.target.value as TaskPriority)
|
||||
}
|
||||
>
|
||||
<MenuItem value="زیاد">زیاد</MenuItem>
|
||||
<MenuItem value="متوسط">متوسط</MenuItem>
|
||||
<MenuItem value="کم">کم</MenuItem>
|
||||
</CustomTextField>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="large"
|
||||
startIcon={<i className="tabler-plus text-lg" />}
|
||||
onClick={addTask}
|
||||
>
|
||||
ثبت در لیست امروز
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader
|
||||
title="تابلوی تمرکز"
|
||||
subheader="سه نکته کوتاه برای نظم بهتر روز مزرعه"
|
||||
/>
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
{[
|
||||
{
|
||||
title: "اول بازدیدها، بعد تماس ها",
|
||||
text: "کارهای میدانی صبح را قبل از تماس ها و هماهنگی های اداری جمع کن.",
|
||||
color: "primary" as const,
|
||||
icon: "tabler-tractor",
|
||||
},
|
||||
{
|
||||
title: "یادداشت های کوتاه ولی دقیق",
|
||||
text: "برای هر کار فقط یک یادداشت عملی ثبت کن تا شیفت بعدی سریع متوجه شود.",
|
||||
color: "info" as const,
|
||||
icon: "tabler-notes",
|
||||
},
|
||||
{
|
||||
title: "فوری ها را جدا نگه دار",
|
||||
text: "اگر کاری روی کیفیت محصول یا آب تاثیر مستقیم دارد، آن را در دسته فوری نگه دار.",
|
||||
color: "error" as const,
|
||||
icon: "tabler-bolt",
|
||||
},
|
||||
].map((item) => (
|
||||
<Box
|
||||
key={item.title}
|
||||
sx={{
|
||||
p: 3,
|
||||
borderRadius: 4,
|
||||
border: `1px solid ${alpha(theme.palette[item.color].main, 0.18)}`,
|
||||
backgroundColor: alpha(
|
||||
theme.palette[item.color].main,
|
||||
0.05,
|
||||
),
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<CustomAvatar skin="light" color={item.color} size={38}>
|
||||
<i className={classnames(item.icon, "text-lg")} />
|
||||
</CustomAvatar>
|
||||
<div>
|
||||
<Typography
|
||||
color="text.primary"
|
||||
className="font-semibold mbe-1"
|
||||
>
|
||||
{item.title}
|
||||
</Typography>
|
||||
<Typography variant="body2">{item.text}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default FarmerTodoPage;
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user