diff --git a/DASHBOARD_API_DOCUMENTATION.md b/DASHBOARD_API_DOCUMENTATION.md deleted file mode 100644 index af063ad..0000000 --- a/DASHBOARD_API_DOCUMENTATION.md +++ /dev/null @@ -1,653 +0,0 @@ -# مستندات API داشبورد Farm (Farm Dashboard) - -این سند شامل توضیحات کل داشبورد، APIهای تنظیمات (disable/enable/move کارت‌ها) و ساختار پیشنهادی ریسپانس برای محتوای کارت‌ها است. - ---- - -## ۱. نمای کلی داشبورد - -داشبورد Farm از کامپوننت `FarmDashboardWrapper` استفاده می‌کند و شامل ردیف‌ها (rows) و کارت‌های (cards) زیر است: - -| Row ID | Row Label | کارت‌ها | -|--------|-----------|---------| -| `overviewKpis` | Overview KPIs | `farmOverviewKpis` | -| `weatherAlerts` | Weather & Alerts | `farmWeatherCard`, `farmAlertsTracker` | -| `sensorMonitoring` | Sensor Monitoring | `sensorValuesList`, `sensorRadarChart` | -| `sensorCharts` | Sensor Charts | `sensorComparisonChart`, `anomalyDetectionCard` | -| `alertsWater` | Alerts & Water Prediction | `farmAlertsTimeline`, `waterNeedPrediction` | -| `predictions` | Predictions | `harvestPredictionCard`, `yieldPredictionChart` | -| `soilHeatmap` | Soil Moisture Heatmap | `soilMoistureHeatmap` | -| `ndviRecommendations` | NDVI & Recommendations | `ndviHealthCard`, `recommendationsList` | -| `economic` | Economic Overview | `economicOverview` | - ---- - -## ۲. APIهای تنظیمات داشبورد - -### ۲.۱ دریافت تنظیمات داشبورد (Get Config) - -``` -GET /api/farm-dashboard-config -``` - -**توضیح:** تنظیمات شخصی‌سازی داشبورد کاربر لاگین‌شده را برمی‌گرداند. - -**Response:** -```json -{ - "code": 200, - "msg": "OK", - "data": { - "disabled_card_ids": ["farmWeatherCard", "sensorRadarChart"], - "row_order": [ - "overviewKpis", - "weatherAlerts", - "sensorMonitoring", - "sensorCharts", - "alertsWater", - "predictions", - "soilHeatmap", - "ndviRecommendations", - "economic" - ], - "enable_drag_reorder": true - } -} -``` - -**فیلدها:** -| فیلد | نوع | توضیح | -|------|-----|-------| -| `disabled_card_ids` | `string[]` | لیست شناسه کارت‌های غیرفعال (hidden) | -| `row_order` | `string[]` | ترتیب نمایش ردیف‌ها | -| `enable_drag_reorder` | `boolean` | امکان جابجایی ردیف‌ها با drag | - ---- - -### ۲.۲ غیرفعال کردن کارت (Disable Card) - -``` -PATCH /api/farm-dashboard-config -``` - -**Request Body:** -```json -{ - "disabled_card_ids": ["farmWeatherCard", "sensorRadarChart"] -} -``` - -کارت با شناسه `cardId` به لیست `disabled_card_ids` اضافه می‌شود و در داشبورد نمایش داده نمی‌شود. - ---- - -### ۲.۳ فعال کردن کارت (Enable Card) - -``` -PATCH /api/farm-dashboard-config -``` - -**Request Body:** -```json -{ - "disabled_card_ids": ["farmWeatherCard"] -} -``` - -شناسه کارت از لیست `disabled_card_ids` حذف می‌شود و کارت دوباره نمایش داده می‌شود. - -**نکته:** کل لیست `disabled_card_ids` جدید ارسال می‌شود؛ برای enable باید آرایه بدون آن کارت فرستاده شود. - ---- - -### ۲.۴ جابجایی ردیف‌ها (Move Rows) - -``` -PATCH /api/farm-dashboard-config -``` - -**Request Body:** -```json -{ - "row_order": [ - "overviewKpis", - "weatherAlerts", - "sensorMonitoring", - "predictions", - "sensorCharts", - "alertsWater", - "soilHeatmap", - "ndviRecommendations", - "economic" - ] -} -``` - -ترتیب ردیف‌ها طبق آرایه `row_order` ذخیره می‌شود. - ---- - -### ۲.۵ تغییر وضعیت Drag Reorder - -``` -PATCH /api/farm-dashboard-config -``` - -**Request Body:** -```json -{ - "enable_drag_reorder": false -} -``` - ---- - -## ۳. API دریافت همه دیتای کارت‌ها - -### Endpoint پیشنهادی - -``` -GET /api/farm-dashboard -``` - -یا به تفکیک کارت: -``` -GET /api/farm-dashboard/cards -``` - ---- - -## ۴. لیست کامل ریسپانس هر کارت - -ساختار پیشنهادی response برای محتوای هر کارت (بر اساس داده‌های mock فعلی در فرانت): - -### ۴.۱ farmOverviewKpis - -```json -{ - "kpis": [ - { - "id": "farm_health_score", - "title": "Farm Health Score", - "subtitle": "AI Analysis", - "stats": "87%", - "avatarColor": "success", - "avatarIcon": "tabler-heartbeat", - "chipText": "Good", - "chipColor": "success" - }, - { - "id": "water_stress_index", - "title": "Water Stress Index", - "subtitle": "Current", - "stats": "12%", - "avatarColor": "info", - "avatarIcon": "tabler-droplet", - "chipText": "Low", - "chipColor": "success" - }, - { - "id": "disease_risk", - "title": "Disease Risk", - "subtitle": "Last 7 Days", - "stats": "Low", - "avatarColor": "success", - "avatarIcon": "tabler-bug", - "chipText": "5%", - "chipColor": "success" - }, - { - "id": "avg_soil_moisture", - "title": "Avg Soil Moisture", - "subtitle": "Field-wide", - "stats": "65%", - "avatarColor": "primary", - "avatarIcon": "tabler-plant-2", - "chipText": "Optimal", - "chipColor": "success" - }, - { - "id": "yield_prediction", - "title": "Yield Prediction", - "subtitle": "This Season", - "stats": "42 ton", - "avatarColor": "secondary", - "avatarIcon": "tabler-chart-bar", - "chipText": "+8%", - "chipColor": "success" - }, - { - "id": "pest_risk", - "title": "Pest Risk", - "subtitle": "AI Forecast", - "stats": "15%", - "avatarColor": "warning", - "avatarIcon": "tabler-bug-off", - "chipText": "Monitor", - "chipColor": "warning" - } - ] -} -``` - ---- - -### ۴.۲ farmWeatherCard - -```json -{ - "condition": "Clear", - "temperature": 24, - "unit": "°C", - "humidity": 45, - "windSpeed": 12, - "windUnit": "km/h", - "chartData": { - "labels": ["6am", "9am", "12pm", "3pm", "6pm", "9pm", "12am"], - "series": [[18, 22, 26, 28, 25, 20, 18]] - } -} -``` - ---- - -### ۴.۳ farmAlertsTracker - -```json -{ - "totalAlerts": 3, - "radialBarValue": 30, - "alertStats": [ - { - "title": "Water Shortage", - "count": "2", - "avatarColor": "error", - "avatarIcon": "tabler-droplet-half-2" - }, - { - "title": "Fungal Risk", - "count": "1", - "avatarColor": "warning", - "avatarIcon": "tabler-mushroom" - }, - { - "title": "Frost Alert", - "count": "0", - "avatarColor": "info", - "avatarIcon": "tabler-snowflake" - } - ] -} -``` - ---- - -### ۴.۴ sensorValuesList - -```json -{ - "sensors": [ - { - "title": "28°C", - "subtitle": "Air Temperature", - "trendNumber": 2.1, - "trend": "positive", - "unit": "°C" - }, - { - "title": "24°C", - "subtitle": "Soil Temperature", - "trendNumber": -0.5, - "trend": "negative", - "unit": "°C" - }, - { - "title": "65%", - "subtitle": "Air Humidity", - "trendNumber": 3.2, - "trend": "positive", - "unit": "%" - }, - { - "title": "42%", - "subtitle": "Soil Moisture (10cm)", - "trendNumber": -1.8, - "trend": "negative", - "unit": "%" - }, - { - "title": "6.8", - "subtitle": "Soil pH", - "trendNumber": 0.2, - "trend": "positive", - "unit": "pH" - }, - { - "title": "1.2", - "subtitle": "EC (dS/m)", - "trendNumber": 0.1, - "trend": "positive", - "unit": "dS/m" - }, - { - "title": "850", - "subtitle": "Light Intensity (lux)", - "trendNumber": 15.3, - "trend": "positive", - "unit": "lux" - }, - { - "title": "12", - "subtitle": "Wind Speed (km/h)", - "trendNumber": -2.4, - "trend": "negative", - "unit": "km/h" - } - ] -} -``` - ---- - -### ۴.۵ sensorRadarChart - -```json -{ - "labels": ["Temp", "Humidity", "pH", "EC", "Light", "Wind"], - "series": [ - { "name": "Today", "data": [75, 65, 80, 70, 85, 60] }, - { "name": "Ideal", "data": [80, 70, 75, 75, 90, 50] } - ] -} -``` - ---- - -### ۴.۶ sensorComparisonChart - -```json -{ - "currentValue": 48, - "vsLastWeek": "+5%", - "vsLastWeekValue": 5, - "categories": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], - "series": [ - { "name": "Today", "data": [42, 45, 48, 52, 50, 48, 46] }, - { "name": "Last Week", "data": [38, 40, 42, 45, 43, 40, 38] } - ] -} -``` - ---- - -### ۴.۷ anomalyDetectionCard - -```json -{ - "anomalies": [ - { - "sensor": "Soil Moisture Z3", - "value": "38%", - "expected": "45-65%", - "deviation": "-12%", - "severity": "warning" - }, - { - "sensor": "pH Sector 2", - "value": "5.2", - "expected": "6.0-7.0", - "deviation": "-0.8", - "severity": "error" - } - ] -} -``` - ---- - -### ۴.۸ farmAlertsTimeline - -```json -{ - "alerts": [ - { - "title": "Water Shortage Risk", - "description": "Soil moisture at 10cm depth (42%) is below optimal. AI predicts stress in 2-3 days if no irrigation. Recommended: irrigate within 24h.", - "time": "15 min ago", - "color": "warning" - }, - { - "title": "Fungal Disease Risk", - "description": "High humidity (65%) + temp 24°C creates favorable conditions for fungal growth. Consider preventive fungicide or reduce irrigation.", - "time": "1 hour ago", - "color": "error" - }, - { - "title": "Irrigation Suggestion", - "description": "Optimal watering window: 6:00-8:00 AM. Suggested amount: 450 m³ for Zone A. Expected efficiency gain: 12%.", - "time": "2 hours ago", - "color": "info" - }, - { - "title": "Soil Salinity Check", - "description": "EC reading 1.2 dS/m is within range. No action needed. Next check recommended in 5 days.", - "time": "4 hours ago", - "color": "success" - } - ] -} -``` - ---- - -### ۴.۹ waterNeedPrediction - -```json -{ - "totalNext7Days": 3290, - "unit": "m³", - "categories": ["Day 1", "Day 2", "Day 3", "Day 4", "Day 5", "Day 6", "Day 7"], - "series": [{ "name": "Water Need", "data": [420, 450, 480, 460, 490, 510, 480] }] -} -``` - ---- - -### ۴.۱۰ harvestPredictionCard - -```json -{ - "date": "2025-10-15", - "dateFormatted": "Oct 15, 2025", - "daysUntil": 58, - "description": "Based on current GDD accumulation and weather forecast. Optimal harvest window: Oct 12-18.", - "optimalWindowStart": "2025-10-12", - "optimalWindowEnd": "2025-10-18" -} -``` - ---- - -### ۴.۱۱ yieldPredictionChart - -```json -{ - "categories": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], - "series": [ - { "name": "This Year", "data": [35, 38, 40, 42, 45, 48, 50, 48, 46, 44, 42, 42] }, - { "name": "Last Year", "data": [32, 34, 36, 38, 40, 42, 44, 42, 40, 38, 36, 38] } - ], - "summary": [ - { "title": "Predicted Yield", "subtitle": "This Season", "amount": "42 ton", "avatarColor": "primary", "avatarIcon": "tabler-chart-bar" }, - { "title": "Harvest Date", "subtitle": "Est. Oct 15", "amount": "+8%", "avatarColor": "success", "avatarIcon": "tabler-calendar" } - ] -} -``` - ---- - -### ۴.۱۲ soilMoistureHeatmap - -```json -{ - "zones": ["Z1", "Z2", "Z3", "Z4", "Z5", "Z6", "Z7"], - "hours": ["6h", "8h", "10h", "12h", "14h", "16h", "18h"], - "series": [ - { "name": "Z1", "data": [{"x": "6h", "y": 52}, {"x": "8h", "y": 48}, {"x": "10h", "y": 55}, {"x": "12h", "y": 60}, {"x": "14h", "y": 58}, {"x": "16h", "y": 54}, {"x": "18h", "y": 50}] }, - { "name": "Z2", "data": [{"x": "6h", "y": 45}, {"x": "8h", "y": 42}, {"x": "10h", "y": 48}, {"x": "12h", "y": 52}, {"x": "14h", "y": 50}, {"x": "16h", "y": 47}, {"x": "18h", "y": 44}] } - ] -} -``` - ---- - -### ۴.۱۳ ndviHealthCard - -```json -{ - "ndviIndex": 0.78, - "healthData": [ - { "title": "Nitrogen Stress", "value": "Low", "color": "success", "icon": "tabler-leaf" }, - { "title": "Crop Health", "value": "Good", "color": "success", "icon": "tabler-plant" } - ] -} -``` - ---- - -### ۴.۱۴ recommendationsList - -```json -{ - "recommendations": [ - { - "title": "Irrigation: 6:00-8:00 AM", - "subtitle": "450 m³ for Zone A. Without irrigation, yield may drop ~8%.", - "avatarIcon": "tabler-droplet", - "avatarColor": "primary" - }, - { - "title": "Fertilizer: NPK 20-20-20", - "subtitle": "Apply 25 kg/ha in 7 days. Current N deficiency in sector 2.", - "avatarIcon": "tabler-leaf", - "avatarColor": "success" - }, - { - "title": "Fungicide: Preventive", - "subtitle": "Humidity + temp favor fungi. Consider copper-based spray.", - "avatarIcon": "tabler-mushroom", - "avatarColor": "warning" - }, - { - "title": "Harvest Window: Oct 12-18", - "subtitle": "Peak ripeness expected Oct 15. Plan labor accordingly.", - "avatarIcon": "tabler-calendar-event", - "avatarColor": "info" - } - ] -} -``` - ---- - -### ۴.۱۵ economicOverview - -```json -{ - "economicData": [ - { "title": "Water Cost", "value": "€720", "subtitle": "This month", "avatarIcon": "tabler-droplet", "avatarColor": "primary" }, - { "title": "AI Water Savings", "value": "€156", "subtitle": "18% saved", "avatarIcon": "tabler-bulb", "avatarColor": "success" }, - { "title": "Platform ROI", "value": "127%", "subtitle": "vs last year", "avatarIcon": "tabler-chart-line", "avatarColor": "info" }, - { "title": "Income Forecast", "value": "€42k", "subtitle": "This season", "avatarIcon": "tabler-currency-euro", "avatarColor": "success" } - ], - "chartSeries": [ - { "name": "Water Cost", "data": [120, 115, 110, 125, 118, 122] }, - { "name": "Fertilizer", "data": [80, 85, 90, 75, 82, 78] } - ], - "chartCategories": ["Jan", "Feb", "Mar", "Apr", "May", "Jun"] -} -``` - ---- - -## ۵. Response یکپارچه همه کارت‌ها - -اگر یک endpoint برای کل دیتای داشبورد داشته باشید: - -``` -GET /api/farm-dashboard -``` - -**Response پیشنهادی:** -```json -{ - "code": 200, - "msg": "OK", - "data": { - "farmOverviewKpis": { ... }, - "farmWeatherCard": { ... }, - "farmAlertsTracker": { ... }, - "sensorValuesList": { ... }, - "sensorRadarChart": { ... }, - "sensorComparisonChart": { ... }, - "anomalyDetectionCard": { ... }, - "farmAlertsTimeline": { ... }, - "waterNeedPrediction": { ... }, - "harvestPredictionCard": { ... }, - "yieldPredictionChart": { ... }, - "soilMoistureHeatmap": { ... }, - "ndviHealthCard": { ... }, - "recommendationsList": { ... }, - "economicOverview": { ... } - } -} -``` - ---- - -## ۶. خلاصه Endpoints - -| عملیات | Method | Endpoint | Body | -|--------|--------|----------|------| -| دریافت تنظیمات | GET | `/api/farm-dashboard-config` | - | -| غیرفعال کردن کارت | PATCH | `/api/farm-dashboard-config` | `{ "disabled_card_ids": [...] }` | -| فعال کردن کارت | PATCH | `/api/farm-dashboard-config` | `{ "disabled_card_ids": [...] }` | -| جابجایی ردیف‌ها | PATCH | `/api/farm-dashboard-config` | `{ "row_order": [...] }` | -| Enable/Disable Drag | PATCH | `/api/farm-dashboard-config` | `{ "enable_drag_reorder": boolean }` | -| دیتای همه کارت‌ها | GET | `/api/farm-dashboard` یا `/api/farm-dashboard/cards` | - | - ---- - -## ۷. Card IDs معتبر - -``` -farmOverviewKpis -farmWeatherCard -farmAlertsTracker -sensorValuesList -sensorRadarChart -sensorComparisonChart -anomalyDetectionCard -farmAlertsTimeline -waterNeedPrediction -harvestPredictionCard -yieldPredictionChart -soilMoistureHeatmap -ndviHealthCard -recommendationsList -economicOverview -``` - -## ۸. Row IDs معتبر - -``` -overviewKpis -weatherAlerts -sensorMonitoring -sensorCharts -alertsWater -predictions -soilHeatmap -ndviRecommendations -economic -``` diff --git a/messages/fa.json b/messages/fa.json index 86cdd45..4808070 100644 --- a/messages/fa.json +++ b/messages/fa.json @@ -142,7 +142,18 @@ "menuLevels": "سطح‌های منو", "menuLevel2": "سطح منو 2", "menuLevel3": "سطح منو 3", - "disabledMenu": "منوی غیرفعال" + "disabledMenu": "منوی غیرفعال", + "farmDomain": "مدیریت مزرعه", + "farmDashboard": "داشبورد مزرعه", + "cropHealth": "سلامت محصول", + "waterWeather": "آب و هوا", + "soilAnalytics": "تحلیل خاک", + "yieldHarvest": "عملکرد و برداشت", + "farmAlerts": "هشدارهای مزرعه", + "pestDiseaseRisk": "ریسک آفات و بیماری", + "economicOverview": "نمای اقتصادی", + "sensorSection": "سنسورها", + "sensor7In1": "سنسور خاک 7 در 1" }, "farmDashboard": { "settings": { diff --git a/src/app/(dashboard)/(private)/crop-health/page.tsx b/src/app/(dashboard)/(private)/crop-health/page.tsx new file mode 100644 index 0000000..72e4fae --- /dev/null +++ b/src/app/(dashboard)/(private)/crop-health/page.tsx @@ -0,0 +1,7 @@ +import CropHealthPageWrapper from '@views/dashboards/farm/CropHealthPageWrapper' + +const CropHealthPage = async () => { + return +} + +export default CropHealthPage diff --git a/src/app/(dashboard)/(private)/economic-overview/page.tsx b/src/app/(dashboard)/(private)/economic-overview/page.tsx new file mode 100644 index 0000000..226d183 --- /dev/null +++ b/src/app/(dashboard)/(private)/economic-overview/page.tsx @@ -0,0 +1,7 @@ +import EconomicOverviewPageWrapper from '@views/dashboards/farm/EconomicOverviewPageWrapper' + +const EconomicOverviewPage = async () => { + return +} + +export default EconomicOverviewPage diff --git a/src/app/(dashboard)/(private)/farm-alerts/page.tsx b/src/app/(dashboard)/(private)/farm-alerts/page.tsx new file mode 100644 index 0000000..274eefc --- /dev/null +++ b/src/app/(dashboard)/(private)/farm-alerts/page.tsx @@ -0,0 +1,7 @@ +import AlertsPageWrapper from '@views/dashboards/farm/AlertsPageWrapper' + +const FarmAlertsPage = async () => { + return +} + +export default FarmAlertsPage diff --git a/src/app/(dashboard)/(private)/pest-risk/page.tsx b/src/app/(dashboard)/(private)/pest-risk/page.tsx new file mode 100644 index 0000000..61db928 --- /dev/null +++ b/src/app/(dashboard)/(private)/pest-risk/page.tsx @@ -0,0 +1,7 @@ +import PestRiskPageWrapper from '@views/dashboards/farm/PestRiskPageWrapper' + +const PestRiskPage = async () => { + return +} + +export default PestRiskPage diff --git a/src/app/(dashboard)/(private)/yield-harvest/page.tsx b/src/app/(dashboard)/(private)/yield-harvest/page.tsx new file mode 100644 index 0000000..5aac408 --- /dev/null +++ b/src/app/(dashboard)/(private)/yield-harvest/page.tsx @@ -0,0 +1,7 @@ +import YieldHarvestPageWrapper from '@views/dashboards/farm/YieldHarvestPageWrapper' + +const YieldHarvestPage = async () => { + return +} + +export default YieldHarvestPage diff --git a/src/components/layout/vertical/VerticalMenu.tsx b/src/components/layout/vertical/VerticalMenu.tsx index 75e7dfd..3762ec5 100644 --- a/src/components/layout/vertical/VerticalMenu.tsx +++ b/src/components/layout/vertical/VerticalMenu.tsx @@ -94,6 +94,23 @@ const VerticalMenu = ({ scrollMenu }: Props) => { > {t('dashboards')} + + }> + {t('cropHealth')} + + }> + {t('yieldHarvest')} + + }> + {t('farmAlerts')} + + }> + {t('pestDiseaseRisk')} + + }> + {t('economicOverview')} + + }> {t('waterData')} @@ -105,9 +122,9 @@ const VerticalMenu = ({ scrollMenu }: Props) => { {t('cropZoning')} - + }> - Sensor 7 + {t('sensor7In1')} diff --git a/src/constants/navigation.ts b/src/constants/navigation.ts index 4290a74..7a49129 100644 --- a/src/constants/navigation.ts +++ b/src/constants/navigation.ts @@ -4,6 +4,8 @@ export const navigationLabels = { farm: 'داشبورد مزرعه', waterData: 'دیتاهای آب', soilData: 'اطلاعات خاک', + sensorSection: 'سنسورها', + sensor7In1: 'سنسور خاک 7 در 1', dataSection: 'بخش داده‌ها', crm: 'مدیریت ارتباط با مشتری', analytics: 'تحلیل‌ها', diff --git a/src/data/dictionaries/ar.json b/src/data/dictionaries/ar.json index 6b18001..1f2c788 100644 --- a/src/data/dictionaries/ar.json +++ b/src/data/dictionaries/ar.json @@ -100,6 +100,17 @@ "menuLevels": "مستويات القائمة", "menuLevel2": "مستوى القائمة 2", "menuLevel3": "مستوى القائمة 3", - "disabledMenu": "قائمة المعوقين" + "disabledMenu": "قائمة المعوقين", + "farmDomain": "إدارة المزرعة", + "farmDashboard": "لوحة المزرعة", + "cropHealth": "صحة المحصول", + "waterWeather": "المياه والطقس", + "soilAnalytics": "تحليل التربة", + "yieldHarvest": "الإنتاج والحصاد", + "farmAlerts": "تنبيهات المزرعة", + "pestDiseaseRisk": "مخاطر الآفات والأمراض", + "economicOverview": "النظرة الاقتصادية", + "sensorSection": "المستشعرات", + "sensor7In1": "مستشعر التربة 7 في 1" } } diff --git a/src/data/dictionaries/en.json b/src/data/dictionaries/en.json index d32e680..e76778d 100644 --- a/src/data/dictionaries/en.json +++ b/src/data/dictionaries/en.json @@ -100,6 +100,17 @@ "menuLevels": "Menu Levels", "menuLevel2": "Menu Level 2", "menuLevel3": "Menu Level 3", - "disabledMenu": "Disabled Menu" + "disabledMenu": "Disabled Menu", + "farmDomain": "Farm Management", + "farmDashboard": "Farm Dashboard", + "cropHealth": "Crop Health", + "waterWeather": "Water & Weather", + "soilAnalytics": "Soil Analytics", + "yieldHarvest": "Yield & Harvest", + "farmAlerts": "Farm Alerts", + "pestDiseaseRisk": "Pest & Disease Risk", + "economicOverview": "Economic Overview", + "sensorSection": "Sensors", + "sensor7In1": "Soil Sensor 7-in-1" } } diff --git a/src/data/dictionaries/fa.json b/src/data/dictionaries/fa.json index e962eed..bfdb9de 100644 --- a/src/data/dictionaries/fa.json +++ b/src/data/dictionaries/fa.json @@ -100,8 +100,17 @@ "menuLevels": "سطح‌های منو", "menuLevel2": "سطح منو 2", "menuLevel3": "سطح منو 3", - "disabledMenu": "منوی غیرفعال" + "disabledMenu": "منوی غیرفعال", + "farmDomain": "مدیریت مزرعه", + "farmDashboard": "داشبورد مزرعه", + "cropHealth": "سلامت محصول", + "waterWeather": "آب و هوا", + "soilAnalytics": "تحلیل خاک", + "yieldHarvest": "عملکرد و برداشت", + "farmAlerts": "هشدارهای مزرعه", + "pestDiseaseRisk": "ریسک آفات و بیماری", + "economicOverview": "نمای اقتصادی", + "sensorSection": "سنسورها", + "sensor7In1": "سنسور خاک 7 در 1" } } - - diff --git a/src/data/dictionaries/fr.json b/src/data/dictionaries/fr.json index 17bef06..e584f0b 100644 --- a/src/data/dictionaries/fr.json +++ b/src/data/dictionaries/fr.json @@ -100,6 +100,17 @@ "menuLevels": "Niveaux de menus", "menuLevel2": "Niveau menu 2", "menuLevel3": "Niveau menu 3", - "disabledMenu": "Menu désactivé" + "disabledMenu": "Menu désactivé", + "farmDomain": "Gestion de la ferme", + "farmDashboard": "Tableau de bord ferme", + "cropHealth": "Santé des cultures", + "waterWeather": "Eau et météo", + "soilAnalytics": "Analyse du sol", + "yieldHarvest": "Rendement et récolte", + "farmAlerts": "Alertes ferme", + "pestDiseaseRisk": "Risque ravageurs et maladies", + "economicOverview": "Aperçu économique", + "sensorSection": "Capteurs", + "sensor7In1": "Capteur de sol 7-en-1" } } diff --git a/src/data/navigation/horizontalMenuData.tsx b/src/data/navigation/horizontalMenuData.tsx index c50a8ab..5c28d9c 100644 --- a/src/data/navigation/horizontalMenuData.tsx +++ b/src/data/navigation/horizontalMenuData.tsx @@ -36,6 +36,128 @@ const horizontalMenuData = (): HorizontalMenuDataType[] => [ } ] }, + { + label: 'farmDomain', + icon: 'tabler-plant-2', + children: [ + { + label: 'farmDashboard', + icon: 'tabler-dashboard', + href: '/dashboard' + }, + { + label: 'cropHealth', + icon: 'tabler-plant', + href: '/crop-health' + }, + { + label: 'waterWeather', + icon: 'tabler-droplet', + href: '/water-data' + }, + { + label: 'soilAnalytics', + icon: 'tabler-grain', + href: '/soil-data' + }, + { + label: 'yieldHarvest', + icon: 'tabler-chart-line', + href: '/yield-harvest' + }, + { + label: 'plantSimulator', + icon: 'tabler-seeding', + href: '/plant-simulator' + }, + { + label: 'farmAlerts', + icon: 'tabler-alert-triangle', + href: '/farm-alerts' + }, + { + label: 'pestDiseaseRisk', + icon: 'tabler-bug', + href: '/pest-risk' + }, + { + label: 'economicOverview', + icon: 'tabler-currency-dollar', + href: '/economic-overview' + }, + { + label: 'cropZoning', + icon: 'tabler-map-pin', + href: '/crop-zoning' + }, + { + label: 'farmAiAssistant', + icon: 'tabler-robot', + href: '/farm-ai-assistant' + } + ] + }, + { + label: 'farmDomain', + icon: 'tabler-plant-2', + children: [ + { + label: 'farmDashboard', + icon: 'tabler-dashboard', + href: '/dashboard' + }, + { + label: 'cropHealth', + icon: 'tabler-plant', + href: '/crop-health' + }, + { + label: 'waterWeather', + icon: 'tabler-droplet', + href: '/water-data' + }, + { + label: 'soilAnalytics', + icon: 'tabler-grain', + href: '/soil-data' + }, + { + label: 'yieldHarvest', + icon: 'tabler-chart-line', + href: '/yield-harvest' + }, + { + label: 'plantSimulator', + icon: 'tabler-seeding', + href: '/plant-simulator' + }, + { + label: 'farmAlerts', + icon: 'tabler-alert-triangle', + href: '/farm-alerts' + }, + { + label: 'pestDiseaseRisk', + icon: 'tabler-bug', + href: '/pest-risk' + }, + { + label: 'economicOverview', + icon: 'tabler-currency-dollar', + href: '/economic-overview' + }, + { + label: 'cropZoning', + icon: 'tabler-map-pin', + href: '/crop-zoning' + }, + { + label: 'farmAiAssistant', + icon: 'tabler-robot', + href: '/farm-ai-assistant' + } + ] + }, { label: 'apps', icon: 'tabler-mail', diff --git a/src/data/navigation/verticalMenuData.tsx b/src/data/navigation/verticalMenuData.tsx index 64981ff..1ba196b 100644 --- a/src/data/navigation/verticalMenuData.tsx +++ b/src/data/navigation/verticalMenuData.tsx @@ -40,6 +40,67 @@ const verticalMenuData = (): VerticalMenuDataType[] => [ } ] }, + { + label: 'farmDomain', + isSection: true, + children: [ + { + label: 'farmDashboard', + icon: 'tabler-dashboard', + href: '/dashboard' + }, + { + label: 'cropHealth', + icon: 'tabler-plant', + href: '/crop-health' + }, + { + label: 'waterWeather', + icon: 'tabler-droplet', + href: '/water-data' + }, + { + label: 'soilAnalytics', + icon: 'tabler-grain', + href: '/soil-data' + }, + { + label: 'yieldHarvest', + icon: 'tabler-chart-line', + href: '/yield-harvest' + }, + { + label: 'plantSimulator', + icon: 'tabler-seeding', + href: '/plant-simulator' + }, + { + label: 'farmAlerts', + icon: 'tabler-alert-triangle', + href: '/farm-alerts' + }, + { + label: 'pestDiseaseRisk', + icon: 'tabler-bug', + href: '/pest-risk' + }, + { + label: 'economicOverview', + icon: 'tabler-currency-dollar', + href: '/economic-overview' + }, + { + label: 'cropZoning', + icon: 'tabler-map-pin', + href: '/crop-zoning' + }, + { + label: 'farmAiAssistant', + icon: 'tabler-robot', + href: '/farm-ai-assistant' + } + ] + }, { label: 'frontPages', icon: 'tabler-files', diff --git a/src/hooks/useFarmHub.ts b/src/hooks/useFarmHub.ts index c93e872..df0e936 100644 --- a/src/hooks/useFarmHub.ts +++ b/src/hooks/useFarmHub.ts @@ -4,6 +4,7 @@ import { useCallback, useEffect, useState } from "react"; export const FARM_HUB_STORAGE_KEY = "farm_hub"; const LEGACY_SENSOR_HUB_STORAGE_KEY = "sensor_hub"; +const FARM_HUB_UPDATED_EVENT = "farm-hub-updated"; export interface FarmHubInfo { farm_uuid: string; @@ -70,7 +71,28 @@ export const useFarmHub = (): UseFarmHubReturn => { const [farmHub, setFarmHubState] = useState(null); useEffect(() => { - setFarmHubState(getStoredFarmHub()); + const syncFarmHub = () => { + setFarmHubState(getStoredFarmHub()); + }; + + const handleStorage = (event: StorageEvent) => { + if ( + event.key === null || + event.key === FARM_HUB_STORAGE_KEY || + event.key === LEGACY_SENSOR_HUB_STORAGE_KEY + ) { + syncFarmHub(); + } + }; + + syncFarmHub(); + window.addEventListener("storage", handleStorage); + window.addEventListener(FARM_HUB_UPDATED_EVENT, syncFarmHub); + + return () => { + window.removeEventListener("storage", handleStorage); + window.removeEventListener(FARM_HUB_UPDATED_EVENT, syncFarmHub); + }; }, []); const setFarmHub = useCallback((data: FarmHubInfo | null) => { @@ -79,6 +101,7 @@ export const useFarmHub = (): UseFarmHubReturn => { if (data === null) { localStorage.removeItem(FARM_HUB_STORAGE_KEY); setFarmHubState(null); + window.dispatchEvent(new Event(FARM_HUB_UPDATED_EVENT)); return; } @@ -90,6 +113,7 @@ export const useFarmHub = (): UseFarmHubReturn => { localStorage.setItem(FARM_HUB_STORAGE_KEY, JSON.stringify(normalized)); setFarmHubState(normalized); + window.dispatchEvent(new Event(FARM_HUB_UPDATED_EVENT)); }, []); const getFarmUuid = useCallback(() => { diff --git a/src/libs/api/services/cropHealthService.ts b/src/libs/api/services/cropHealthService.ts new file mode 100644 index 0000000..e698393 --- /dev/null +++ b/src/libs/api/services/cropHealthService.ts @@ -0,0 +1,27 @@ +import { apiClient } from '../client' + +const PREFIX = '/api/crop-health' + +export interface CropHealthSummary { + farm_health_score?: Record + ndviHealthCard?: Record +} + +interface ApiResponse { + code: number + msg: string + data: T +} + +function extract(res: ApiResponse | T): T { + return res && typeof res === 'object' && 'data' in res ? (res as ApiResponse).data : (res as T) +} + +export const cropHealthService = { + async getSummary(farmUuid: string): Promise { + const res = await apiClient.get | CropHealthSummary>( + `${PREFIX}/summary/?farm_uuid=${encodeURIComponent(farmUuid)}` + ) + return extract(res) + }, +} diff --git a/src/libs/api/services/economicOverviewService.ts b/src/libs/api/services/economicOverviewService.ts new file mode 100644 index 0000000..4b04b47 --- /dev/null +++ b/src/libs/api/services/economicOverviewService.ts @@ -0,0 +1,34 @@ +import { apiClient } from '../client' + +const PREFIX = '/api/economic-overview' + +export interface EconomicOverviewSummary { + economicOverview?: Record +} + +interface ApiResponse { + code: number + msg: string + data: T + result?: T +} + +function extract(res: ApiResponse | T): T { + if (res && typeof res === 'object') { + if ('data' in res) return (res as ApiResponse).data + if ('result' in res) return (res as ApiResponse).result as T + } + + return res as T +} + +export const economicOverviewService = { + async getSummary(farmUuid: string): Promise { + const res = await apiClient.get | EconomicOverviewSummary>( + `${PREFIX}/summary/?farm_uuid=${encodeURIComponent(farmUuid)}` + ) + const data = extract(res) as Record + + return 'economicOverview' in data ? (data as EconomicOverviewSummary) : { economicOverview: data } + }, +} diff --git a/src/libs/api/services/farmAlertsService.ts b/src/libs/api/services/farmAlertsService.ts new file mode 100644 index 0000000..0fb6b3e --- /dev/null +++ b/src/libs/api/services/farmAlertsService.ts @@ -0,0 +1,48 @@ +import { apiClient } from '../client' + +const PREFIX = '/api/farm-alerts' + +export interface FarmAlertsSummary { + tracker?: Record + timeline?: Record + recommendations?: Record +} + +interface ApiResponse { + code: number + msg: string + data: T + result?: T +} + +function extract(res: ApiResponse | T): T { + if (res && typeof res === 'object') { + if ('data' in res) return (res as ApiResponse).data + if ('result' in res) return (res as ApiResponse).result as T + } + + return res as T +} + +export const farmAlertsService = { + async getTracker(farmUuid: string): Promise> { + const res = await apiClient.get> | Record>( + `${PREFIX}/tracker/?farm_uuid=${encodeURIComponent(farmUuid)}` + ) + return extract(res) + }, + + async getTimeline(farmUuid: string): Promise> { + const res = await apiClient.get> | Record>( + `${PREFIX}/timeline/?farm_uuid=${encodeURIComponent(farmUuid)}` + ) + return extract(res) + }, + + async getRecommendations(farmUuid: string): Promise> { + const res = await apiClient.get> | Record>( + `${PREFIX}/recommendations/?farm_uuid=${encodeURIComponent(farmUuid)}` + ) + return extract(res) + }, +} diff --git a/src/libs/api/services/pestDetectionDomainService.ts b/src/libs/api/services/pestDetectionDomainService.ts new file mode 100644 index 0000000..e2318ee --- /dev/null +++ b/src/libs/api/services/pestDetectionDomainService.ts @@ -0,0 +1,44 @@ +import { apiClient } from '../client' + +const PREFIX = '/api/pest-detection' + +export interface PestRiskSummary { + disease_risk?: Record + pest_risk?: Record +} + +interface ApiResponse { + code: number + msg: string + data: T + result?: T +} + +function extract(res: ApiResponse | T): T { + if (res && typeof res === 'object') { + if ('data' in res) return (res as ApiResponse).data + if ('result' in res) return (res as ApiResponse).result as T + } + + return res as T +} + +function toKpiCard(card?: Record): Record { + if (!card || typeof card !== 'object') return {} + + return { kpis: [card] } +} + +export const pestDetectionDomainService = { + async getRiskSummary(farmUuid: string): Promise { + const res = await apiClient.get | PestRiskSummary>( + `${PREFIX}/risk-summary/?farm_uuid=${encodeURIComponent(farmUuid)}` + ) + const data = extract(res) + + return { + disease_risk: toKpiCard(data?.disease_risk), + pest_risk: toKpiCard(data?.pest_risk) + } + }, +} diff --git a/src/libs/api/services/soilService.ts b/src/libs/api/services/soilService.ts new file mode 100644 index 0000000..134e052 --- /dev/null +++ b/src/libs/api/services/soilService.ts @@ -0,0 +1,65 @@ +import { apiClient } from '../client' + +const PREFIX = '/api/soil' + +export interface SoilSummary { + avg_soil_moisture?: Record + sensorRadarChart?: Record + sensorComparisonChart?: Record + anomalyDetectionCard?: Record + soilMoistureHeatmap?: Record +} + +interface ApiResponse { + code: number + msg: string + data: T +} + +function extract(res: ApiResponse | T): T { + return res && typeof res === 'object' && 'data' in res ? (res as ApiResponse).data : (res as T) +} + +export const soilService = { + async getSummary(farmUuid: string): Promise { + const res = await apiClient.get | SoilSummary>( + `${PREFIX}/summary/?farm_uuid=${encodeURIComponent(farmUuid)}` + ) + return extract(res) + }, + + async getAvgMoisture(farmUuid: string): Promise> { + const res = await apiClient.get> | Record>( + `${PREFIX}/avg-moisture/?farm_uuid=${encodeURIComponent(farmUuid)}` + ) + return extract(res) + }, + + async getSensorRadarChart(farmUuid: string): Promise> { + const res = await apiClient.get> | Record>( + `${PREFIX}/sensor-radar-chart/?farm_uuid=${encodeURIComponent(farmUuid)}` + ) + return extract(res) + }, + + async getSensorComparisonChart(farmUuid: string): Promise> { + const res = await apiClient.get> | Record>( + `${PREFIX}/sensor-comparison-chart/?farm_uuid=${encodeURIComponent(farmUuid)}` + ) + return extract(res) + }, + + async getAnomalies(farmUuid: string): Promise> { + const res = await apiClient.get> | Record>( + `${PREFIX}/anomalies/?farm_uuid=${encodeURIComponent(farmUuid)}` + ) + return extract(res) + }, + + async getMoistureHeatmap(farmUuid: string): Promise> { + const res = await apiClient.get> | Record>( + `${PREFIX}/moisture-heatmap/?farm_uuid=${encodeURIComponent(farmUuid)}` + ) + return extract(res) + }, +} diff --git a/src/libs/api/services/waterService.ts b/src/libs/api/services/waterService.ts new file mode 100644 index 0000000..64610dc --- /dev/null +++ b/src/libs/api/services/waterService.ts @@ -0,0 +1,49 @@ +import { apiClient } from '../client' + +const PREFIX = '/api/water' + +export interface WaterSummary { + farmWeatherCard?: Record + waterNeedPrediction?: Record + water_stress_index?: Record +} + +interface ApiResponse { + code: number + msg: string + data: T +} + +function extract(res: ApiResponse | T): T { + return res && typeof res === 'object' && 'data' in res ? (res as ApiResponse).data : (res as T) +} + +export const waterService = { + async getSummary(farmUuid: string): Promise { + const res = await apiClient.get | WaterSummary>( + `${PREFIX}/summary/?farm_uuid=${encodeURIComponent(farmUuid)}` + ) + return extract(res) + }, + + async getCard(farmUuid: string): Promise> { + const res = await apiClient.get> | Record>( + `${PREFIX}/card/?farm_uuid=${encodeURIComponent(farmUuid)}` + ) + return extract(res) + }, + + async getNeedPrediction(farmUuid: string): Promise> { + const res = await apiClient.get> | Record>( + `${PREFIX}/need-prediction/?farm_uuid=${encodeURIComponent(farmUuid)}` + ) + return extract(res) + }, + + async getStressIndex(farmUuid: string): Promise> { + const res = await apiClient.get> | Record>( + `${PREFIX}/stress-index/?farm_uuid=${encodeURIComponent(farmUuid)}` + ) + return extract(res) + }, +} diff --git a/src/libs/api/services/yieldHarvestService.ts b/src/libs/api/services/yieldHarvestService.ts new file mode 100644 index 0000000..4ac8f42 --- /dev/null +++ b/src/libs/api/services/yieldHarvestService.ts @@ -0,0 +1,46 @@ +import { apiClient } from '../client' + +const PREFIX = '/api/yield-harvest' + +export interface YieldHarvestSummary { + yield_prediction?: Record + yieldPredictionChart?: Record + harvestPredictionCard?: Record +} + +interface ApiResponse { + code: number + msg: string + data: T + result?: T +} + +function extract(res: ApiResponse | T): T { + if (res && typeof res === 'object') { + if ('data' in res) return (res as ApiResponse).data + if ('result' in res) return (res as ApiResponse).result as T + } + + return res as T +} + +function toKpiCard(card?: Record): Record { + if (!card || typeof card !== 'object') return {} + + return { kpis: [card] } +} + +export const yieldHarvestService = { + async getSummary(farmUuid: string): Promise { + const res = await apiClient.get> | Record>( + `${PREFIX}/summary/?farm_uuid=${encodeURIComponent(farmUuid)}` + ) + const data = extract(res) + + return { + yield_prediction: toKpiCard(data?.yield_prediction ?? data?.yield_prediction_card), + yieldPredictionChart: (data?.yieldPredictionChart ?? data?.yield_prediction_chart ?? {}) as Record, + harvestPredictionCard: (data?.harvestPredictionCard ?? data?.harvest_prediction_card ?? {}) as Record + } + }, +} diff --git a/src/views/dashboards/farm/AlertsPageWrapper.tsx b/src/views/dashboards/farm/AlertsPageWrapper.tsx new file mode 100644 index 0000000..b4360a6 --- /dev/null +++ b/src/views/dashboards/farm/AlertsPageWrapper.tsx @@ -0,0 +1,81 @@ +'use client' + +import { useEffect, useState } from 'react' +import { useFarmHub } from '@/hooks/useFarmHub' + +import Grid from '@mui/material/Grid2' +import Box from '@mui/material/Box' +import CircularProgress from '@mui/material/CircularProgress' + +import FarmAlertsTracker from '@views/dashboards/farm/FarmAlertsTracker' +import FarmAlertsTimeline from '@views/dashboards/farm/FarmAlertsTimeline' +import RecommendationsList from '@views/dashboards/farm/RecommendationsList' + +import { farmAlertsService } from '@/libs/api/services/farmAlertsService' + +const cardRowSx = { + display: 'flex', + flexDirection: 'column', + minHeight: 380, + '& > *': { flex: 1, minHeight: 0 } +} + +const AlertsPageWrapper = () => { + const { farmHub } = useFarmHub() + const farmUuid = farmHub?.farm_uuid + const [tracker, setTracker] = useState>({}) + const [timeline, setTimeline] = useState>({}) + const [recommendations, setRecommendations] = useState>({}) + const [loading, setLoading] = useState(true) + + useEffect(() => { + if (!farmUuid) { + setTracker({}) + setTimeline({}) + setRecommendations({}) + setLoading(false) + return + } + + setLoading(true) + Promise.all([ + farmAlertsService.getTracker(farmUuid).catch(() => ({})), + farmAlertsService.getTimeline(farmUuid).catch(() => ({})), + farmAlertsService.getRecommendations(farmUuid).catch(() => ({})) + ]) + .then(([t, tl, r]) => { + setTracker(t) + setTimeline(tl) + setRecommendations(r) + }) + .finally(() => setLoading(false)) + }, [farmUuid]) + + if (loading) { + return ( + + + + ) + } + + return ( + + + + + + + + + + + + + + + + ) +} + +export default AlertsPageWrapper diff --git a/src/views/dashboards/farm/CropHealthPageWrapper.tsx b/src/views/dashboards/farm/CropHealthPageWrapper.tsx new file mode 100644 index 0000000..73fe6f6 --- /dev/null +++ b/src/views/dashboards/farm/CropHealthPageWrapper.tsx @@ -0,0 +1,65 @@ +'use client' + +import { useEffect, useState } from 'react' +import { useFarmHub } from '@/hooks/useFarmHub' + +import Grid from '@mui/material/Grid2' +import Box from '@mui/material/Box' +import CircularProgress from '@mui/material/CircularProgress' + +import FarmOverviewKPIs from '@views/dashboards/farm/FarmOverviewKPIs' +import NDVIHealthCard from '@views/dashboards/farm/NDVIHealthCard' + +import { cropHealthService } from '@/libs/api/services/cropHealthService' + +const cardRowSx = { + display: 'flex', + flexDirection: 'column', + minHeight: 380, + '& > *': { flex: 1, minHeight: 0 } +} + +const CropHealthPageWrapper = () => { + const { farmHub } = useFarmHub() + const farmUuid = farmHub?.farm_uuid + const [data, setData] = useState>({}) + const [loading, setLoading] = useState(true) + + useEffect(() => { + if (!farmUuid) { + setData({}) + setLoading(false) + return + } + + setLoading(true) + cropHealthService + .getSummary(farmUuid) + .then(summary => setData((summary as Record) ?? {})) + .catch(() => setData({})) + .finally(() => setLoading(false)) + }, [farmUuid]) + + if (loading) { + return ( + + + + ) + } + + return ( + + + + } /> + + + } /> + + + + ) +} + +export default CropHealthPageWrapper diff --git a/src/views/dashboards/farm/EconomicOverviewPageWrapper.tsx b/src/views/dashboards/farm/EconomicOverviewPageWrapper.tsx new file mode 100644 index 0000000..6c0d233 --- /dev/null +++ b/src/views/dashboards/farm/EconomicOverviewPageWrapper.tsx @@ -0,0 +1,62 @@ +'use client' + +import { useEffect, useState } from 'react' +import { useFarmHub } from '@/hooks/useFarmHub' + +import Grid from '@mui/material/Grid2' +import Box from '@mui/material/Box' +import CircularProgress from '@mui/material/CircularProgress' + +import EconomicOverview from '@views/dashboards/farm/EconomicOverview' + +import { economicOverviewService } from '@/libs/api/services/economicOverviewService' +import type { EconomicOverviewSummary } from '@/libs/api/services/economicOverviewService' + +const cardRowSx = { + display: 'flex', + flexDirection: 'column', + minHeight: 380, + '& > *': { flex: 1, minHeight: 0 } +} + +const EconomicOverviewPageWrapper = () => { + const { farmHub } = useFarmHub() + const farmUuid = farmHub?.farm_uuid + const [data, setData] = useState({}) + const [loading, setLoading] = useState(true) + + useEffect(() => { + if (!farmUuid) { + setData({}) + setLoading(false) + return + } + + setLoading(true) + economicOverviewService + .getSummary(farmUuid) + .then(summary => setData(summary ?? {})) + .catch(() => setData({})) + .finally(() => setLoading(false)) + }, [farmUuid]) + + if (loading) { + return ( + + + + ) + } + + return ( + + + + } /> + + + + ) +} + +export default EconomicOverviewPageWrapper diff --git a/src/views/dashboards/farm/PestRiskPageWrapper.tsx b/src/views/dashboards/farm/PestRiskPageWrapper.tsx new file mode 100644 index 0000000..3d41210 --- /dev/null +++ b/src/views/dashboards/farm/PestRiskPageWrapper.tsx @@ -0,0 +1,62 @@ +'use client' + +import { useEffect, useState } from 'react' +import { useFarmHub } from '@/hooks/useFarmHub' + +import Grid from '@mui/material/Grid2' +import Box from '@mui/material/Box' +import CircularProgress from '@mui/material/CircularProgress' + +import FarmOverviewKPIs from '@views/dashboards/farm/FarmOverviewKPIs' + +import { pestDetectionDomainService } from '@/libs/api/services/pestDetectionDomainService' +import type { PestRiskSummary } from '@/libs/api/services/pestDetectionDomainService' + +const PestRiskPageWrapper = () => { + const { farmHub } = useFarmHub() + const farmUuid = farmHub?.farm_uuid + const [data, setData] = useState({}) + const [loading, setLoading] = useState(true) + + useEffect(() => { + if (!farmUuid) { + setData({}) + setLoading(false) + return + } + + setLoading(true) + pestDetectionDomainService + .getRiskSummary(farmUuid) + .then(summary => setData(summary ?? {})) + .catch(() => setData({})) + .finally(() => setLoading(false)) + }, [farmUuid]) + + if (loading) { + return ( + + + + ) + } + + return ( + + + {data.disease_risk && ( + + } /> + + )} + {data.pest_risk && ( + + } /> + + )} + + + ) +} + +export default PestRiskPageWrapper diff --git a/src/views/dashboards/farm/SoilDataDashboardWrapper.tsx b/src/views/dashboards/farm/SoilDataDashboardWrapper.tsx index 3a1d21a..b8d7315 100644 --- a/src/views/dashboards/farm/SoilDataDashboardWrapper.tsx +++ b/src/views/dashboards/farm/SoilDataDashboardWrapper.tsx @@ -1,31 +1,19 @@ 'use client' -// React Imports import { useEffect, useState } from 'react' import { useFarmHub } from '@/hooks/useFarmHub' -// MUI Imports import Grid from '@mui/material/Grid2' import Box from '@mui/material/Box' import CircularProgress from '@mui/material/CircularProgress' -// Component Imports import SoilMoistureHeatmap from '@views/dashboards/farm/SoilMoistureHeatmap' -import SensorValuesList from '@views/dashboards/farm/SensorValuesList' import SensorComparisonChart from '@views/dashboards/farm/SensorComparisonChart' import SensorRadarChart from '@views/dashboards/farm/SensorRadarChart' import AnomalyDetectionCard from '@views/dashboards/farm/AnomalyDetectionCard' -// Service -import { farmDashboardService } from '@/libs/api/services/farmDashboardService' -import type { CardId } from '@views/dashboards/farm/farmDashboardConfig' - -/** هر ردیف: آرایهٔ کارت‌ها؛ در هر ردیف فضا مساوی بین گریدها تقسیم می‌شود (جمع = ۱۲) */ -const SOIL_ROWS: CardId[][] = [ - ['soilMoistureHeatmap'], - ['sensorValuesList', 'sensorRadarChart'], - ['sensorComparisonChart', 'anomalyDetectionCard'] -] +import { soilService } from '@/libs/api/services/soilService' +import type { SoilSummary } from '@/libs/api/services/soilService' const cardRowSx = { display: 'flex', @@ -34,32 +22,24 @@ const cardRowSx = { '& > *': { flex: 1, minHeight: 0 } } -const CARD_COMPONENTS: Partial }>>> = { - soilMoistureHeatmap: SoilMoistureHeatmap, - sensorValuesList: SensorValuesList, - sensorComparisonChart: SensorComparisonChart, - sensorRadarChart: SensorRadarChart, - anomalyDetectionCard: AnomalyDetectionCard -} - const SoilDataDashboardWrapper = () => { const { farmHub } = useFarmHub() const farmUuid = farmHub?.farm_uuid - const [cardsData, setCardsData] = useState>>>({}) + const [data, setData] = useState({}) const [loading, setLoading] = useState(true) useEffect(() => { if (!farmUuid) { - setCardsData({}) + setData({}) setLoading(false) return } setLoading(true) - farmDashboardService - .getAllCards(farmUuid) - .then(cards => setCardsData(cards ?? {})) - .catch(() => setCardsData({})) + soilService + .getSummary(farmUuid) + .then(summary => setData(summary ?? {})) + .catch(() => setData({})) .finally(() => setLoading(false)) }, [farmUuid]) @@ -74,28 +54,20 @@ const SoilDataDashboardWrapper = () => { return ( - {SOIL_ROWS.map((rowCards, rowIndex) => { - const sizePerCard = 12 / rowCards.length - return ( - - {rowCards.map(cardId => { - const Component = CARD_COMPONENTS[cardId] - if (!Component) return null - return ( - - - - ) - })} - - ) - })} + + } /> + + + + } /> + + + } /> + + + + } /> + ) diff --git a/src/views/dashboards/farm/WaterDataDashboardWrapper.tsx b/src/views/dashboards/farm/WaterDataDashboardWrapper.tsx index 2d328b9..443e2b2 100644 --- a/src/views/dashboards/farm/WaterDataDashboardWrapper.tsx +++ b/src/views/dashboards/farm/WaterDataDashboardWrapper.tsx @@ -11,19 +11,11 @@ import CircularProgress from '@mui/material/CircularProgress' // Component Imports import FarmWeatherCard from '@views/dashboards/farm/FarmWeatherCard' -import FarmAlertsTimeline from '@views/dashboards/farm/FarmAlertsTimeline' import WaterNeedPrediction from '@views/dashboards/farm/WaterNeedPrediction' -import SensorValuesList from '@views/dashboards/farm/SensorValuesList' +import FarmOverviewKPIs from '@views/dashboards/farm/FarmOverviewKPIs' // Service -import { farmDashboardService } from '@/libs/api/services/farmDashboardService' -import type { CardId } from '@views/dashboards/farm/farmDashboardConfig' - -/** هر ردیف: آرایهٔ کارت‌ها؛ در هر ردیف فضا مساوی بین گریدها تقسیم می‌شود (جمع = ۱۲) */ -const WATER_ROWS: CardId[][] = [ - ['farmWeatherCard', 'farmAlertsTimeline', 'waterNeedPrediction'], - ['sensorValuesList'] -] +import { waterService } from '@/libs/api/services/waterService' const cardRowSx = { display: 'flex', @@ -32,31 +24,24 @@ const cardRowSx = { '& > *': { flex: 1, minHeight: 0 } } -const CARD_COMPONENTS: Partial }>>> = { - farmWeatherCard: FarmWeatherCard, - farmAlertsTimeline: FarmAlertsTimeline, - waterNeedPrediction: WaterNeedPrediction, - sensorValuesList: SensorValuesList -} - const WaterDataDashboardWrapper = () => { const { farmHub } = useFarmHub() const farmUuid = farmHub?.farm_uuid - const [cardsData, setCardsData] = useState>>>({}) + const [data, setData] = useState>({}) const [loading, setLoading] = useState(true) useEffect(() => { if (!farmUuid) { - setCardsData({}) + setData({}) setLoading(false) return } setLoading(true) - farmDashboardService - .getAllCards(farmUuid) - .then(cards => setCardsData(cards ?? {})) - .catch(() => setCardsData({})) + waterService + .getSummary(farmUuid) + .then(summary => setData((summary as Record) ?? {})) + .catch(() => setData({})) .finally(() => setLoading(false)) }, [farmUuid]) @@ -71,28 +56,19 @@ const WaterDataDashboardWrapper = () => { return ( - {WATER_ROWS.map((rowCards, rowIndex) => { - const sizePerCard = 12 / rowCards.length - return ( - - {rowCards.map(cardId => { - const Component = CARD_COMPONENTS[cardId] - if (!Component) return null - return ( - - - - ) - })} - - ) - })} + {data.water_stress_index != null && ( + + } /> + + )} + + + } /> + + + } /> + + ) diff --git a/src/views/dashboards/farm/YieldHarvestPageWrapper.tsx b/src/views/dashboards/farm/YieldHarvestPageWrapper.tsx new file mode 100644 index 0000000..2ba74cb --- /dev/null +++ b/src/views/dashboards/farm/YieldHarvestPageWrapper.tsx @@ -0,0 +1,74 @@ +'use client' + +import { useEffect, useState } from 'react' +import { useFarmHub } from '@/hooks/useFarmHub' + +import Grid from '@mui/material/Grid2' +import Box from '@mui/material/Box' +import CircularProgress from '@mui/material/CircularProgress' + +import HarvestPredictionCard from '@views/dashboards/farm/HarvestPredictionCard' +import YieldPredictionChart from '@views/dashboards/farm/YieldPredictionChart' +import FarmOverviewKPIs from '@views/dashboards/farm/FarmOverviewKPIs' + +import { yieldHarvestService } from '@/libs/api/services/yieldHarvestService' +import type { YieldHarvestSummary } from '@/libs/api/services/yieldHarvestService' + +const cardRowSx = { + display: 'flex', + flexDirection: 'column', + minHeight: 380, + '& > *': { flex: 1, minHeight: 0 } +} + +const YieldHarvestPageWrapper = () => { + const { farmHub } = useFarmHub() + const farmUuid = farmHub?.farm_uuid + const [data, setData] = useState({}) + const [loading, setLoading] = useState(true) + + useEffect(() => { + if (!farmUuid) { + setData({}) + setLoading(false) + return + } + + setLoading(true) + yieldHarvestService + .getSummary(farmUuid) + .then(summary => setData(summary ?? {})) + .catch(() => setData({})) + .finally(() => setLoading(false)) + }, [farmUuid]) + + if (loading) { + return ( + + + + ) + } + + return ( + + + {data.yield_prediction && ( + + } /> + + )} + + + } /> + + + } /> + + + + + ) +} + +export default YieldHarvestPageWrapper