Integrate next-intl for internationalization support across the application. Updated configuration to include next-intl plugin and modified components to utilize translation hooks for dynamic text rendering. Enhanced user experience by localizing navigation labels and form messages in various views, including login and dashboard components.
This commit is contained in:
@@ -0,0 +1,653 @@
|
||||
# مستندات 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
|
||||
```
|
||||
@@ -0,0 +1,216 @@
|
||||
{
|
||||
"common": {
|
||||
"cancel": "انصراف",
|
||||
"save": "ذخیره",
|
||||
"back": "بازگشت",
|
||||
"close": "بستن"
|
||||
},
|
||||
"login": {
|
||||
"welcome": "خوش آمدید به {templateName}! 👋🏻",
|
||||
"phoneStep": "شماره موبایل خود را برای دریافت کد OTP وارد کنید",
|
||||
"otpStep": "کد OTP ارسال شده به موبایل خود را وارد کنید",
|
||||
"phoneNumber": "شماره موبایل",
|
||||
"placeholderPhone": "شماره موبایل خود را وارد کنید",
|
||||
"sendOtp": "ارسال OTP",
|
||||
"verifyOtp": "تایید OTP",
|
||||
"backToPhone": "بازگشت به شماره موبایل",
|
||||
"newUser": "جدید هستید؟",
|
||||
"createAccount": "ثبت نام کنید",
|
||||
"otpSent": "کد OTP به {phone} ارسال شد",
|
||||
"validation": {
|
||||
"phoneRequired": "شماره موبایل الزامی است",
|
||||
"phoneMinLength": "شماره موبایل باید حداقل ۱۰ رقم باشد",
|
||||
"phoneMaxLength": "شماره موبایل باید حداکثر ۱۵ رقم باشد",
|
||||
"phoneDigitsOnly": "شماره موبایل باید فقط عدد باشد"
|
||||
},
|
||||
"errors": {
|
||||
"sendOtpFailed": "ارسال OTP ناموفق بود",
|
||||
"incompleteOtp": "لطفاً کد ۶ رقمی OTP را کامل وارد کنید",
|
||||
"otpVerificationFailed": "تایید OTP ناموفق بود"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"dashboards": "داشبوردها",
|
||||
"crm": "مدیریت ارتباط با مشتری",
|
||||
"analytics": "تحلیلها",
|
||||
"eCommerce": "فروشگاه",
|
||||
"academy": "آکادمی",
|
||||
"logistics": "لجستیک",
|
||||
"frontPages": "صفحات فرانت",
|
||||
"landing": "صفحه اصلی",
|
||||
"pricing": "قیمتگذاری",
|
||||
"payment": "پرداخت",
|
||||
"checkout": "تسویه حساب",
|
||||
"helpCenter": "مرکز راهنمایی",
|
||||
"appsPages": "اپلیکیشنها و صفحات",
|
||||
"apps": "اپلیکیشنها",
|
||||
"dashboard": "داشبورد",
|
||||
"products": "محصولات",
|
||||
"list": "فهرست",
|
||||
"add": "افزودن",
|
||||
"category": "دستهبندی",
|
||||
"orders": "سفارشات",
|
||||
"details": "جزئیات",
|
||||
"customers": "مشتریان",
|
||||
"manageReviews": "مدیریت نظرات",
|
||||
"referrals": "معرفی",
|
||||
"settings": "تنظیمات",
|
||||
"myCourses": "دورههای من",
|
||||
"courseDetails": "جزئیات دوره",
|
||||
"fleet": "ناوگان",
|
||||
"email": "ایمیل",
|
||||
"chat": "چت",
|
||||
"calendar": "تقویم",
|
||||
"kanban": "کانبان",
|
||||
"todo": "وظایف",
|
||||
"invoice": "فاکتور",
|
||||
"preview": "پیشنمایش",
|
||||
"edit": "ویرایش",
|
||||
"user": "کاربر",
|
||||
"view": "مشاهده",
|
||||
"rolesPermissions": "نقشها و دسترسیها",
|
||||
"roles": "نقشها",
|
||||
"permissions": "دسترسیها",
|
||||
"pages": "صفحات",
|
||||
"userProfile": "پروفایل کاربر",
|
||||
"accountSettings": "تنظیمات حساب",
|
||||
"faq": "سوالات متداول",
|
||||
"miscellaneous": "متفرقه",
|
||||
"comingSoon": "به زودی",
|
||||
"underMaintenance": "در حال تعمیر",
|
||||
"pageNotFound404": "صفحه یافت نشد - 404",
|
||||
"notAuthorized401": "غیرمجاز - 401",
|
||||
"authPages": "صفحات احراز هویت",
|
||||
"login": "ورود",
|
||||
"loginV1": "ورود نسخه 1",
|
||||
"loginV2": "ورود نسخه 2",
|
||||
"register": "ثبت نام",
|
||||
"registerV1": "ثبت نام نسخه 1",
|
||||
"registerV2": "ثبت نام نسخه 2",
|
||||
"registerMultiSteps": "ثبت نام چند مرحلهای",
|
||||
"verifyEmail": "تایید ایمیل",
|
||||
"verifyEmailV1": "تایید ایمیل نسخه 1",
|
||||
"verifyEmailV2": "تایید ایمیل نسخه 2",
|
||||
"forgotPassword": "فراموشی رمز عبور",
|
||||
"forgotPasswordV1": "فراموشی رمز عبور نسخه 1",
|
||||
"forgotPasswordV2": "فراموشی رمز عبور نسخه 2",
|
||||
"resetPassword": "بازنشانی رمز عبور",
|
||||
"resetPasswordV1": "بازنشانی رمز عبور نسخه 1",
|
||||
"resetPasswordV2": "بازنشانی رمز عبور نسخه 2",
|
||||
"twoSteps": "دو مرحلهای",
|
||||
"twoStepsV1": "دو مرحلهای نسخه 1",
|
||||
"twoStepsV2": "دو مرحلهای نسخه 2",
|
||||
"wizardExamples": "مثالهای ویزارد",
|
||||
"propertyListing": "فهرست املاک",
|
||||
"createDeal": "ایجاد معامله",
|
||||
"dialogExamples": "مثالهای دیالوگ",
|
||||
"widgetExamples": "مثالهای ویجت",
|
||||
"basic": "پایه",
|
||||
"advanced": "پیشرفته",
|
||||
"statistics": "آمار",
|
||||
"actions": "عملیات",
|
||||
"charts": "چارتها",
|
||||
"formsAndTables": "فرمها و جداول",
|
||||
"formLayouts": "چیدمان فرمها",
|
||||
"formValidation": "اعتبارسنجی فرم",
|
||||
"formWizard": "ویزارد فرم",
|
||||
"reactTable": "جدول React",
|
||||
"formELements": "عناصر فرم",
|
||||
"muiTables": "جداول MUI",
|
||||
"chartsMisc": "چارتها و متفرقه",
|
||||
"recharts": "Recharts",
|
||||
"apex": "Apex",
|
||||
"foundation": "پایه",
|
||||
"components": "کامپوننتها",
|
||||
"menuExamples": "مثالهای منو",
|
||||
"raiseSupport": "درخواست پشتیبانی",
|
||||
"documentation": "مستندات",
|
||||
"others": "سایر",
|
||||
"itemWithBadge": "آیتم با نشان",
|
||||
"externalLink": "لینک خارجی",
|
||||
"menuLevels": "سطحهای منو",
|
||||
"menuLevel2": "سطح منو 2",
|
||||
"menuLevel3": "سطح منو 3",
|
||||
"disabledMenu": "منوی غیرفعال"
|
||||
},
|
||||
"farmDashboard": {
|
||||
"settings": {
|
||||
"title": "تنظیمات داشبورد",
|
||||
"toggleCards": "کارتها را نمایش دهید یا مخفی کنید",
|
||||
"enableDragReorder": "فعالسازی کشیدن و مرتبسازی ردیفها",
|
||||
"ariaLabel": "تنظیمات داشبورد",
|
||||
"dragRow": "کشیدن {row}"
|
||||
},
|
||||
"cards": {
|
||||
"farmOverviewKpis": "خلاصه شاخصها",
|
||||
"farmWeatherCard": "آب و هوا",
|
||||
"farmAlertsTracker": "پیامهای هشدار",
|
||||
"sensorValuesList": "مقادیر سنسور",
|
||||
"sensorRadarChart": "نمودار راداری سنسور",
|
||||
"sensorComparisonChart": "مقایسه سنسور",
|
||||
"anomalyDetectionCard": "تشخیص ناهنجاری",
|
||||
"farmAlertsTimeline": "خط زمانی هشدارها",
|
||||
"waterNeedPrediction": "پیشبینی نیاز آبی",
|
||||
"harvestPredictionCard": "پیشبینی برداشت",
|
||||
"yieldPredictionChart": "پیشبینی عملکرد",
|
||||
"soilMoistureHeatmap": "نقشه حرارتی رطوبت خاک",
|
||||
"ndviHealthCard": "سلامت NDVI",
|
||||
"recommendationsList": "توصیهها",
|
||||
"economicOverview": "خلاصه اقتصادی"
|
||||
},
|
||||
"rows": {
|
||||
"overviewKpis": "خلاصه شاخصها",
|
||||
"weatherAlerts": "آب و هوا و هشدارها",
|
||||
"sensorMonitoring": "پایش سنسور",
|
||||
"sensorCharts": "نمودارهای سنسور",
|
||||
"alertsWater": "هشدارها و پیشبینی آب",
|
||||
"predictions": "پیشبینیها",
|
||||
"soilHeatmap": "نقشه حرارتی رطوبت خاک",
|
||||
"ndviRecommendations": "NDVI و توصیهها",
|
||||
"economic": "خلاصه اقتصادی"
|
||||
}
|
||||
},
|
||||
"sensorHub": {
|
||||
"title": "مرکز سنسور",
|
||||
"cancel": "انصراف",
|
||||
"selectSensor": "انتخاب سنسور",
|
||||
"selectSensorDescription": "سنسور مورد نظر را انتخاب کنید یا سنسور جدید اضافه کنید",
|
||||
"addSensor": "اضافه کردن سنسور",
|
||||
"sensorData": "داده سنسور",
|
||||
"back": "بازگشت",
|
||||
"sensorName": "نام سنسور",
|
||||
"sensorUuid": "شناسه سنسور (UUID)",
|
||||
"placeholderName": "نام سنسور را وارد کنید",
|
||||
"placeholderUuid": "شناسه سنسور را وارد کنید",
|
||||
"saveSensor": "ذخیره سنسور",
|
||||
"saving": "در حال ذخیره...",
|
||||
"errorSave": "خطا در ذخیره سنسور",
|
||||
"columns": {
|
||||
"name": "نام",
|
||||
"lastUpdate": "آخرین بروزرسانی",
|
||||
"uuid": "شناسه یکتا"
|
||||
},
|
||||
"ariaClose": "بستن",
|
||||
"errorLoad": "خطا در بارگذاری سنسورها"
|
||||
},
|
||||
"accountSettings": {
|
||||
"account": "حساب کاربری",
|
||||
"sensorHub": "مرکز سنسور",
|
||||
"firstName": "نام",
|
||||
"lastName": "نام خانوادگی",
|
||||
"email": "ایمیل",
|
||||
"phoneNumber": "شماره موبایل",
|
||||
"placeholderFirstName": "نام",
|
||||
"placeholderLastName": "نام خانوادگی",
|
||||
"placeholderEmail": "example@email.com",
|
||||
"placeholderPhone": "+۹۸ ۹۱۲ ۳۴۵ ۶۷۸۹",
|
||||
"errorSave": "خطا در ذخیره تغییرات",
|
||||
"deleteAccount": "حذف حساب",
|
||||
"confirmDeactivation": "تایید میکنم که میخواهم حساب من غیرفعال شود",
|
||||
"deactivateAccount": "غیرفعال کردن حساب",
|
||||
"confirmDelete": "لطفاً تایید کنید که میخواهید حساب را حذف کنید",
|
||||
"saveChanges": "ذخیره تغییرات",
|
||||
"saving": "در حال ذخیره...",
|
||||
"reset": "بازنشانی"
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -1,8 +1,11 @@
|
||||
import type { NextConfig } from 'next'
|
||||
import createNextIntlPlugin from 'next-intl/plugin'
|
||||
|
||||
const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts')
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
basePath: process.env.BASEPATH,
|
||||
output: 'standalone'
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
export default withNextIntl(nextConfig)
|
||||
|
||||
Generated
+111
@@ -54,9 +54,11 @@
|
||||
"mapbox-gl": "3.9.0",
|
||||
"negotiator": "1.0.0",
|
||||
"next": "15.1.2",
|
||||
"next-intl": "3.25.2",
|
||||
"react": "18.3.1",
|
||||
"react-apexcharts": "1.4.1",
|
||||
"react-colorful": "5.6.1",
|
||||
"react-date-object": "1.1.9",
|
||||
"react-datepicker": "7.3.0",
|
||||
"react-dom": "18.3.1",
|
||||
"react-dropzone": "14.3.5",
|
||||
@@ -1026,6 +1028,57 @@
|
||||
"version": "0.2.8",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@formatjs/ecma402-abstract": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://mirror-npm.runflare.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.6.tgz",
|
||||
"integrity": "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@formatjs/fast-memoize": "2.2.7",
|
||||
"@formatjs/intl-localematcher": "0.6.2",
|
||||
"decimal.js": "^10.4.3",
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/ecma402-abstract/node_modules/@formatjs/intl-localematcher": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://mirror-npm.runflare.com/@formatjs/intl-localematcher/-/intl-localematcher-0.6.2.tgz",
|
||||
"integrity": "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/fast-memoize": {
|
||||
"version": "2.2.7",
|
||||
"resolved": "https://mirror-npm.runflare.com/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz",
|
||||
"integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/icu-messageformat-parser": {
|
||||
"version": "2.11.4",
|
||||
"resolved": "https://mirror-npm.runflare.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.4.tgz",
|
||||
"integrity": "sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "2.3.6",
|
||||
"@formatjs/icu-skeleton-parser": "1.8.16",
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/icu-skeleton-parser": {
|
||||
"version": "1.8.16",
|
||||
"resolved": "https://mirror-npm.runflare.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.16.tgz",
|
||||
"integrity": "sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "2.3.6",
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/intl-localematcher": {
|
||||
"version": "0.5.9",
|
||||
"license": "MIT",
|
||||
@@ -4702,6 +4755,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/decimal.js": {
|
||||
"version": "10.6.0",
|
||||
"resolved": "https://mirror-npm.runflare.com/decimal.js/-/decimal.js-10.6.0.tgz",
|
||||
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/decimal.js-light": {
|
||||
"version": "2.5.1",
|
||||
"license": "MIT"
|
||||
@@ -6491,6 +6550,18 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/intl-messageformat": {
|
||||
"version": "10.7.18",
|
||||
"resolved": "https://mirror-npm.runflare.com/intl-messageformat/-/intl-messageformat-10.7.18.tgz",
|
||||
"integrity": "sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "2.3.6",
|
||||
"@formatjs/fast-memoize": "2.2.7",
|
||||
"@formatjs/icu-messageformat-parser": "2.11.4",
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-array-buffer": {
|
||||
"version": "3.0.5",
|
||||
"dev": true,
|
||||
@@ -7529,6 +7600,27 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/next-intl": {
|
||||
"version": "3.25.2",
|
||||
"resolved": "https://mirror-npm.runflare.com/next-intl/-/next-intl-3.25.2.tgz",
|
||||
"integrity": "sha512-C2BoRMX3h+KxCf5TC6BjlnZie2EOCK+QZz4C9A7xGf++1E/r1uD25wT8EZBaQAkO2uKKhBoZg78X8j8r2HPsag==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/amannn"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@formatjs/intl-localematcher": "^0.5.4",
|
||||
"negotiator": "^1.0.0",
|
||||
"use-intl": "^3.25.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"next": "^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/next/node_modules/postcss": {
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://mirror-npm.runflare.com/postcss/-/postcss-8.4.31.tgz",
|
||||
@@ -8486,6 +8578,12 @@
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-date-object": {
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://mirror-npm.runflare.com/react-date-object/-/react-date-object-1.1.9.tgz",
|
||||
"integrity": "sha512-NEbp/JSqwpF3nm4bXcez9XGwmlpOjSPJSYoRyPC1XOxzRHHaijp6xjMbYpyKB3O4yrluxsbwBHbO1WgeFKip3Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-datepicker": {
|
||||
"version": "7.3.0",
|
||||
"license": "MIT",
|
||||
@@ -10702,6 +10800,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/use-intl": {
|
||||
"version": "3.26.5",
|
||||
"resolved": "https://mirror-npm.runflare.com/use-intl/-/use-intl-3.26.5.tgz",
|
||||
"integrity": "sha512-OdsJnC/znPvHCHLQH/duvQNXnP1w0hPfS+tkSi3mAbfjYBGh4JnyfdwkQBfIVf7t8gs9eSX/CntxUMvtKdG2MQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@formatjs/fast-memoize": "^2.2.0",
|
||||
"intl-messageformat": "^10.5.14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-sidecar": {
|
||||
"version": "1.1.3",
|
||||
"license": "MIT",
|
||||
|
||||
+1
-1
@@ -59,13 +59,13 @@
|
||||
"mapbox-gl": "3.9.0",
|
||||
"negotiator": "1.0.0",
|
||||
"next": "15.1.2",
|
||||
"next-intl": "3.25.2",
|
||||
"react": "18.3.1",
|
||||
"react-apexcharts": "1.4.1",
|
||||
"react-colorful": "5.6.1",
|
||||
"react-date-object": "1.1.9",
|
||||
"react-datepicker": "7.3.0",
|
||||
"react-dom": "18.3.1",
|
||||
"react-multi-date-picker": "4.4.2",
|
||||
"react-dropzone": "14.3.5",
|
||||
"react-hook-form": "7.54.1",
|
||||
"react-map-gl": "7.1.8",
|
||||
|
||||
+7
-1
@@ -2,6 +2,8 @@
|
||||
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript'
|
||||
|
||||
// Third-party Imports
|
||||
import { NextIntlClientProvider } from 'next-intl'
|
||||
import { getLocale, getMessages } from 'next-intl/server'
|
||||
import 'react-perfect-scrollbar/dist/css/styles.css'
|
||||
|
||||
// Type Imports
|
||||
@@ -27,16 +29,20 @@ export const metadata = getMetadata()
|
||||
const RootLayout = async (props: ChildrenType) => {
|
||||
const { children } = props
|
||||
|
||||
const locale = await getLocale()
|
||||
const messages = await getMessages()
|
||||
|
||||
// Vars
|
||||
const systemMode = await getSystemMode()
|
||||
const direction = 'rtl' // Fixed RTL direction
|
||||
|
||||
return (
|
||||
<html id='__next' dir={direction} className={iran_sans.className} suppressHydrationWarning>
|
||||
<html id='__next' dir={direction} className={iran_sans.className} lang={locale} suppressHydrationWarning>
|
||||
<body className='flex is-full min-bs-full flex-auto flex-col'>
|
||||
<InitColorSchemeScript attribute='data' defaultMode={systemMode} />
|
||||
<NextIntlClientProvider locale={locale} messages={messages}>
|
||||
{children}
|
||||
</NextIntlClientProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// React Imports
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// MUI Imports
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
|
||||
@@ -9,11 +12,6 @@ import HorizontalNav, { Menu, SubMenu, MenuItem } from '@menu/horizontal-menu'
|
||||
import VerticalNavContent from './VerticalNavContent'
|
||||
import CustomChip from '@core/components/mui/Chip'
|
||||
|
||||
// Constants
|
||||
import { navigationLabels } from '@/constants/navigation'
|
||||
|
||||
// import { GenerateHorizontalMenu } from '@components/GenerateMenu'
|
||||
|
||||
// Hook Imports
|
||||
import useVerticalNav from '@menu/hooks/useVerticalNav'
|
||||
|
||||
@@ -53,7 +51,7 @@ const RenderVerticalExpandIcon = ({ open, transitionDuration }: RenderVerticalEx
|
||||
)
|
||||
|
||||
const HorizontalMenu = () => {
|
||||
// Hooks
|
||||
const t = useTranslations('navigation')
|
||||
const verticalNavOptions = useVerticalNav()
|
||||
const theme = useTheme()
|
||||
|
||||
@@ -87,67 +85,67 @@ const HorizontalMenu = () => {
|
||||
menuSectionStyles: verticalMenuSectionStyles(verticalNavOptions, theme)
|
||||
}}
|
||||
>
|
||||
<SubMenu label={navigationLabels.dashboards} icon={<i className='tabler-smart-home' />}>
|
||||
<SubMenu label={t('dashboards')} icon={<i className='tabler-smart-home' />}>
|
||||
|
||||
<MenuItem href={`/dashboards/analytics`} icon={<i className='tabler-trending-up' />}>
|
||||
{navigationLabels.analytics}
|
||||
{t('analytics')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/dashboards/ecommerce`} icon={<i className='tabler-shopping-cart' />}>
|
||||
{navigationLabels.eCommerce}
|
||||
{t('eCommerce')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/dashboards/academy`} icon={<i className='tabler-school' />}>
|
||||
{navigationLabels.academy}
|
||||
{t('academy')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/dashboards/logistics`} icon={<i className='tabler-truck' />}>
|
||||
{navigationLabels.logistics}
|
||||
{t('logistics')}
|
||||
</MenuItem>
|
||||
</SubMenu>
|
||||
<SubMenu label={navigationLabels.apps} icon={<i className='tabler-mail' />}>
|
||||
<SubMenu label={navigationLabels.eCommerce} icon={<i className='tabler-shopping-cart' />}>
|
||||
<MenuItem href={`/apps/ecommerce/dashboard`}>{navigationLabels.dashboard}</MenuItem>
|
||||
<SubMenu label={navigationLabels.products}>
|
||||
<MenuItem href={`/apps/ecommerce/products/list`}>{navigationLabels.list}</MenuItem>
|
||||
<MenuItem href={`/apps/ecommerce/products/add`}>{navigationLabels.add}</MenuItem>
|
||||
<SubMenu label={t('apps')} icon={<i className='tabler-mail' />}>
|
||||
<SubMenu label={t('eCommerce')} icon={<i className='tabler-shopping-cart' />}>
|
||||
<MenuItem href={`/apps/ecommerce/dashboard`}>{t('dashboard')}</MenuItem>
|
||||
<SubMenu label={t('products')}>
|
||||
<MenuItem href={`/apps/ecommerce/products/list`}>{t('list')}</MenuItem>
|
||||
<MenuItem href={`/apps/ecommerce/products/add`}>{t('add')}</MenuItem>
|
||||
<MenuItem href={`/apps/ecommerce/products/category`}>
|
||||
{navigationLabels.category}
|
||||
{t('category')}
|
||||
</MenuItem>
|
||||
</SubMenu>
|
||||
<SubMenu label={navigationLabels.orders}>
|
||||
<MenuItem href={`/apps/ecommerce/orders/list`}>{navigationLabels.list}</MenuItem>
|
||||
<SubMenu label={t('orders')}>
|
||||
<MenuItem href={`/apps/ecommerce/orders/list`}>{t('list')}</MenuItem>
|
||||
<MenuItem
|
||||
href={`/apps/ecommerce/orders/details/5434`}
|
||||
exactMatch={false}
|
||||
activeUrl='/apps/ecommerce/orders/details'
|
||||
>
|
||||
{navigationLabels.details}
|
||||
{t('details')}
|
||||
</MenuItem>
|
||||
</SubMenu>
|
||||
<SubMenu label={navigationLabels.customers}>
|
||||
<MenuItem href={`/apps/ecommerce/customers/list`}>{navigationLabels.list}</MenuItem>
|
||||
<SubMenu label={t('customers')}>
|
||||
<MenuItem href={`/apps/ecommerce/customers/list`}>{t('list')}</MenuItem>
|
||||
<MenuItem
|
||||
href={`/apps/ecommerce/customers/details/879861`}
|
||||
exactMatch={false}
|
||||
activeUrl='/apps/ecommerce/customers/details'
|
||||
>
|
||||
{navigationLabels.details}
|
||||
{t('details')}
|
||||
</MenuItem>
|
||||
</SubMenu>
|
||||
<MenuItem href={`/apps/ecommerce/manage-reviews`}>
|
||||
{navigationLabels.manageReviews}
|
||||
{t('manageReviews')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/apps/ecommerce/referrals`}>{navigationLabels.referrals}</MenuItem>
|
||||
<MenuItem href={`/apps/ecommerce/settings`}>{navigationLabels.settings}</MenuItem>
|
||||
<MenuItem href={`/apps/ecommerce/referrals`}>{t('referrals')}</MenuItem>
|
||||
<MenuItem href={`/apps/ecommerce/settings`}>{t('settings')}</MenuItem>
|
||||
</SubMenu>
|
||||
<SubMenu label={navigationLabels.academy} icon={<i className='tabler-school' />}>
|
||||
<MenuItem href={`/apps/academy/dashboard`}>{navigationLabels.dashboard}</MenuItem>
|
||||
<MenuItem href={`/apps/academy/my-courses`}>{navigationLabels.myCourses}</MenuItem>
|
||||
<SubMenu label={t('academy')} icon={<i className='tabler-school' />}>
|
||||
<MenuItem href={`/apps/academy/dashboard`}>{t('dashboard')}</MenuItem>
|
||||
<MenuItem href={`/apps/academy/my-courses`}>{t('myCourses')}</MenuItem>
|
||||
<MenuItem href={`/apps/academy/course-details`}>
|
||||
{navigationLabels.courseDetails}
|
||||
{t('courseDetails')}
|
||||
</MenuItem>
|
||||
</SubMenu>
|
||||
<SubMenu label={navigationLabels.logistics} icon={<i className='tabler-truck' />}>
|
||||
<MenuItem href={`/apps/logistics/dashboard`}>{navigationLabels.dashboard}</MenuItem>
|
||||
<MenuItem href={`/apps/logistics/fleet`}>{navigationLabels.fleet}</MenuItem>
|
||||
<SubMenu label={t('logistics')} icon={<i className='tabler-truck' />}>
|
||||
<MenuItem href={`/apps/logistics/dashboard`}>{t('dashboard')}</MenuItem>
|
||||
<MenuItem href={`/apps/logistics/fleet`}>{t('fleet')}</MenuItem>
|
||||
</SubMenu>
|
||||
<MenuItem
|
||||
href={`/apps/email`}
|
||||
@@ -155,162 +153,162 @@ const HorizontalMenu = () => {
|
||||
exactMatch={false}
|
||||
activeUrl='/apps/email'
|
||||
>
|
||||
{navigationLabels.email}
|
||||
{t('email')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/apps/chat`} icon={<i className='tabler-message-circle-2' />}>
|
||||
{navigationLabels.chat}
|
||||
{t('chat')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/apps/calendar`} icon={<i className='tabler-calendar' />}>
|
||||
{navigationLabels.calendar}
|
||||
{t('calendar')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/apps/kanban`} icon={<i className='tabler-copy' />}>
|
||||
{navigationLabels.kanban}
|
||||
{t('kanban')}
|
||||
</MenuItem>
|
||||
<SubMenu label={navigationLabels.invoice} icon={<i className='tabler-file-description' />}>
|
||||
<MenuItem href={`/apps/invoice/list`}>{navigationLabels.list}</MenuItem>
|
||||
<SubMenu label={t('invoice')} icon={<i className='tabler-file-description' />}>
|
||||
<MenuItem href={`/apps/invoice/list`}>{t('list')}</MenuItem>
|
||||
<MenuItem
|
||||
href={`/apps/invoice/preview/4987`}
|
||||
exactMatch={false}
|
||||
activeUrl='/apps/invoice/preview'
|
||||
>
|
||||
{navigationLabels.preview}
|
||||
{t('preview')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/apps/invoice/edit/4987`} exactMatch={false} activeUrl='/apps/invoice/edit'>
|
||||
{navigationLabels.edit}
|
||||
{t('edit')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/apps/invoice/add`}>{navigationLabels.add}</MenuItem>
|
||||
<MenuItem href={`/apps/invoice/add`}>{t('add')}</MenuItem>
|
||||
</SubMenu>
|
||||
<SubMenu label={navigationLabels.user} icon={<i className='tabler-user' />}>
|
||||
<MenuItem href={`/apps/user/list`}>{navigationLabels.list}</MenuItem>
|
||||
<MenuItem href={`/apps/user/view`}>{navigationLabels.view}</MenuItem>
|
||||
<SubMenu label={t('user')} icon={<i className='tabler-user' />}>
|
||||
<MenuItem href={`/apps/user/list`}>{t('list')}</MenuItem>
|
||||
<MenuItem href={`/apps/user/view`}>{t('view')}</MenuItem>
|
||||
</SubMenu>
|
||||
<SubMenu label={navigationLabels.rolesPermissions} icon={<i className='tabler-lock' />}>
|
||||
<MenuItem href={`/apps/roles`}>{navigationLabels.roles}</MenuItem>
|
||||
<MenuItem href={`/apps/permissions`}>{navigationLabels.permissions}</MenuItem>
|
||||
<SubMenu label={t('rolesPermissions')} icon={<i className='tabler-lock' />}>
|
||||
<MenuItem href={`/apps/roles`}>{t('roles')}</MenuItem>
|
||||
<MenuItem href={`/apps/permissions`}>{t('permissions')}</MenuItem>
|
||||
</SubMenu>
|
||||
</SubMenu>
|
||||
<SubMenu label={navigationLabels.pages} icon={<i className='tabler-file' />}>
|
||||
<SubMenu label={t('pages')} icon={<i className='tabler-file' />}>
|
||||
<MenuItem href={`/pages/user-profile`} icon={<i className='tabler-user-circle' />}>
|
||||
{navigationLabels.userProfile}
|
||||
{t('userProfile')}
|
||||
</MenuItem>
|
||||
<SubMenu label={navigationLabels.miscellaneous} icon={<i className='tabler-file-info' />}>
|
||||
<SubMenu label={t('miscellaneous')} icon={<i className='tabler-file-info' />}>
|
||||
<MenuItem href={`/pages/misc/coming-soon`} target='_blank'>
|
||||
{navigationLabels.comingSoon}
|
||||
{t('comingSoon')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/pages/misc/under-maintenance`} target='_blank'>
|
||||
{navigationLabels.underMaintenance}
|
||||
{t('underMaintenance')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/pages/misc/404-not-found`} target='_blank'>
|
||||
{navigationLabels.pageNotFound404}
|
||||
{t('pageNotFound404')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/pages/misc/401-not-authorized`} target='_blank'>
|
||||
{navigationLabels.notAuthorized401}
|
||||
{t('notAuthorized401')}
|
||||
</MenuItem>
|
||||
</SubMenu>
|
||||
<SubMenu label={navigationLabels.authPages} icon={<i className='tabler-shield-lock' />}>
|
||||
<SubMenu label={navigationLabels.login}>
|
||||
<SubMenu label={t('authPages')} icon={<i className='tabler-shield-lock' />}>
|
||||
<SubMenu label={t('login')}>
|
||||
<MenuItem href={`/pages/auth/login-v1`} target='_blank'>
|
||||
{navigationLabels.loginV1}
|
||||
{t('loginV1')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/pages/auth/login-v2`} target='_blank'>
|
||||
{navigationLabels.loginV2}
|
||||
{t('loginV2')}
|
||||
</MenuItem>
|
||||
</SubMenu>
|
||||
<SubMenu label={navigationLabels.register}>
|
||||
<SubMenu label={t('register')}>
|
||||
<MenuItem href={`/pages/auth/register-v1`} target='_blank'>
|
||||
{navigationLabels.registerV1}
|
||||
{t('registerV1')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/pages/auth/register-v2`} target='_blank'>
|
||||
{navigationLabels.registerV2}
|
||||
{t('registerV2')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/pages/auth/register-multi-steps`} target='_blank'>
|
||||
{navigationLabels.registerMultiSteps}
|
||||
{t('registerMultiSteps')}
|
||||
</MenuItem>
|
||||
</SubMenu>
|
||||
<SubMenu label={navigationLabels.verifyEmail}>
|
||||
<SubMenu label={t('verifyEmail')}>
|
||||
<MenuItem href={`/pages/auth/verify-email-v1`} target='_blank'>
|
||||
{navigationLabels.verifyEmailV1}
|
||||
{t('verifyEmailV1')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/pages/auth/verify-email-v2`} target='_blank'>
|
||||
{navigationLabels.verifyEmailV2}
|
||||
{t('verifyEmailV2')}
|
||||
</MenuItem>
|
||||
</SubMenu>
|
||||
<SubMenu label={navigationLabels.forgotPassword}>
|
||||
<SubMenu label={t('forgotPassword')}>
|
||||
<MenuItem href={`/pages/auth/forgot-password-v1`} target='_blank'>
|
||||
{navigationLabels.forgotPasswordV1}
|
||||
{t('forgotPasswordV1')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/pages/auth/forgot-password-v2`} target='_blank'>
|
||||
{navigationLabels.forgotPasswordV2}
|
||||
{t('forgotPasswordV2')}
|
||||
</MenuItem>
|
||||
</SubMenu>
|
||||
<SubMenu label={navigationLabels.resetPassword}>
|
||||
<SubMenu label={t('resetPassword')}>
|
||||
<MenuItem href={`/pages/auth/reset-password-v1`} target='_blank'>
|
||||
{navigationLabels.resetPasswordV1}
|
||||
{t('resetPasswordV1')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/pages/auth/reset-password-v2`} target='_blank'>
|
||||
{navigationLabels.resetPasswordV2}
|
||||
{t('resetPasswordV2')}
|
||||
</MenuItem>
|
||||
</SubMenu>
|
||||
<SubMenu label={navigationLabels.twoSteps}>
|
||||
<SubMenu label={t('twoSteps')}>
|
||||
<MenuItem href={`/pages/auth/two-steps-v1`} target='_blank'>
|
||||
{navigationLabels.twoStepsV1}
|
||||
{t('twoStepsV1')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/pages/auth/two-steps-v2`} target='_blank'>
|
||||
{navigationLabels.twoStepsV2}
|
||||
{t('twoStepsV2')}
|
||||
</MenuItem>
|
||||
</SubMenu>
|
||||
</SubMenu>
|
||||
<SubMenu label={navigationLabels.wizardExamples} icon={<i className='tabler-dots' />}>
|
||||
<MenuItem href={`/pages/wizard-examples/checkout`}>{navigationLabels.checkout}</MenuItem>
|
||||
<SubMenu label={t('wizardExamples')} icon={<i className='tabler-dots' />}>
|
||||
<MenuItem href={`/pages/wizard-examples/checkout`}>{t('checkout')}</MenuItem>
|
||||
<MenuItem href={`/pages/wizard-examples/property-listing`}>
|
||||
{navigationLabels.propertyListing}
|
||||
{t('propertyListing')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/pages/wizard-examples/create-deal`}>
|
||||
{navigationLabels.createDeal}
|
||||
{t('createDeal')}
|
||||
</MenuItem>
|
||||
</SubMenu>
|
||||
<MenuItem href={`/pages/dialog-examples`} icon={<i className='tabler-square' />}>
|
||||
{navigationLabels.dialogExamples}
|
||||
{t('dialogExamples')}
|
||||
</MenuItem>
|
||||
<SubMenu label={navigationLabels.widgetExamples} icon={<i className='tabler-chart-bar' />}>
|
||||
<MenuItem href={`/pages/widget-examples/basic`}>{navigationLabels.basic}</MenuItem>
|
||||
<MenuItem href={`/pages/widget-examples/advanced`}>{navigationLabels.advanced}</MenuItem>
|
||||
<SubMenu label={t('widgetExamples')} icon={<i className='tabler-chart-bar' />}>
|
||||
<MenuItem href={`/pages/widget-examples/basic`}>{t('basic')}</MenuItem>
|
||||
<MenuItem href={`/pages/widget-examples/advanced`}>{t('advanced')}</MenuItem>
|
||||
<MenuItem href={`/pages/widget-examples/statistics`}>
|
||||
{navigationLabels.statistics}
|
||||
{t('statistics')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/pages/widget-examples/charts`}>{navigationLabels.charts}</MenuItem>
|
||||
<MenuItem href={`/pages/widget-examples/actions`}>{navigationLabels.actions}</MenuItem>
|
||||
<MenuItem href={`/pages/widget-examples/charts`}>{t('charts')}</MenuItem>
|
||||
<MenuItem href={`/pages/widget-examples/actions`}>{t('actions')}</MenuItem>
|
||||
</SubMenu>
|
||||
<SubMenu label={navigationLabels.frontPages} icon={<i className='tabler-files' />}>
|
||||
<SubMenu label={t('frontPages')} icon={<i className='tabler-files' />}>
|
||||
<MenuItem href='/front-pages/landing-page' target='_blank'>
|
||||
{navigationLabels.landing}
|
||||
{t('landing')}
|
||||
</MenuItem>
|
||||
<MenuItem href='/front-pages/pricing' target='_blank'>
|
||||
{navigationLabels.pricing}
|
||||
{t('pricing')}
|
||||
</MenuItem>
|
||||
<MenuItem href='/front-pages/payment' target='_blank'>
|
||||
{navigationLabels.payment}
|
||||
{t('payment')}
|
||||
</MenuItem>
|
||||
<MenuItem href='/front-pages/checkout' target='_blank'>
|
||||
{navigationLabels.checkout}
|
||||
{t('checkout')}
|
||||
</MenuItem>
|
||||
<MenuItem href='/front-pages/help-center' target='_blank'>
|
||||
{navigationLabels.helpCenter}
|
||||
{t('helpCenter')}
|
||||
</MenuItem>
|
||||
</SubMenu>
|
||||
</SubMenu>
|
||||
<SubMenu label={navigationLabels.formsAndTables} icon={<i className='tabler-file-invoice' />}>
|
||||
<SubMenu label={t('formsAndTables')} icon={<i className='tabler-file-invoice' />}>
|
||||
<MenuItem href={`/forms/form-layouts`} icon={<i className='tabler-layout' />}>
|
||||
{navigationLabels.formLayouts}
|
||||
{t('formLayouts')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/forms/form-validation`} icon={<i className='tabler-checkup-list' />}>
|
||||
{navigationLabels.formValidation}
|
||||
{t('formValidation')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/forms/form-wizard`} icon={<i className='tabler-git-merge' />}>
|
||||
{navigationLabels.formWizard}
|
||||
{t('formWizard')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/react-table`} icon={<i className='tabler-table' />}>
|
||||
{navigationLabels.reactTable}
|
||||
{t('reactTable')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<i className='tabler-checkbox' />}
|
||||
@@ -318,7 +316,7 @@ const HorizontalMenu = () => {
|
||||
suffix={<i className='tabler-external-link text-xl' />}
|
||||
target='_blank'
|
||||
>
|
||||
{navigationLabels.formELements}
|
||||
{t('formELements')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<i className='tabler-layout-board-split' />}
|
||||
@@ -326,25 +324,25 @@ const HorizontalMenu = () => {
|
||||
suffix={<i className='tabler-external-link text-xl' />}
|
||||
target='_blank'
|
||||
>
|
||||
{navigationLabels.muiTables}
|
||||
{t('muiTables')}
|
||||
</MenuItem>
|
||||
</SubMenu>
|
||||
<SubMenu label={navigationLabels.charts} icon={<i className='tabler-chart-donut-2' />}>
|
||||
<SubMenu label={t('charts')} icon={<i className='tabler-chart-donut-2' />}>
|
||||
<MenuItem href={`/charts/apex-charts`} icon={<i className='tabler-chart-ppf' />}>
|
||||
{navigationLabels.apex}
|
||||
{t('apex')}
|
||||
</MenuItem>
|
||||
<MenuItem href={`/charts/recharts`} icon={<i className='tabler-chart-sankey' />}>
|
||||
{navigationLabels.recharts}
|
||||
{t('recharts')}
|
||||
</MenuItem>
|
||||
</SubMenu>
|
||||
<SubMenu label={navigationLabels.others} icon={<i className='tabler-dots' />}>
|
||||
<SubMenu label={t('others')} icon={<i className='tabler-dots' />}>
|
||||
<MenuItem
|
||||
icon={<i className='tabler-cards' />}
|
||||
href={`${process.env.NEXT_PUBLIC_DOCS_URL}/docs/user-interface/foundation`}
|
||||
suffix={<i className='tabler-external-link text-xl' />}
|
||||
target='_blank'
|
||||
>
|
||||
{navigationLabels.foundation}
|
||||
{t('foundation')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<i className='tabler-atom' />}
|
||||
@@ -352,7 +350,7 @@ const HorizontalMenu = () => {
|
||||
suffix={<i className='tabler-external-link text-xl' />}
|
||||
target='_blank'
|
||||
>
|
||||
{navigationLabels.components}
|
||||
{t('components')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<i className='tabler-list-search' />}
|
||||
@@ -360,7 +358,7 @@ const HorizontalMenu = () => {
|
||||
suffix={<i className='tabler-external-link text-xl' />}
|
||||
target='_blank'
|
||||
>
|
||||
{navigationLabels.menuExamples}
|
||||
{t('menuExamples')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
suffix={<i className='tabler-external-link text-xl' />}
|
||||
@@ -368,7 +366,7 @@ const HorizontalMenu = () => {
|
||||
href='https://pixinvent.ticksy.com'
|
||||
icon={<i className='tabler-lifebuoy' />}
|
||||
>
|
||||
{navigationLabels.raiseSupport}
|
||||
{t('raiseSupport')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
suffix={<i className='tabler-external-link text-xl' />}
|
||||
@@ -376,13 +374,13 @@ const HorizontalMenu = () => {
|
||||
icon={<i className='tabler-book-2' />}
|
||||
href={`${process.env.NEXT_PUBLIC_DOCS_URL}`}
|
||||
>
|
||||
{navigationLabels.documentation}
|
||||
{t('documentation')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
suffix={<CustomChip label='New' size='small' color='info' round='true' />}
|
||||
icon={<i className='tabler-notification' />}
|
||||
>
|
||||
{navigationLabels.itemWithBadge}
|
||||
{t('itemWithBadge')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<i className='tabler-link' />}
|
||||
@@ -390,16 +388,16 @@ const HorizontalMenu = () => {
|
||||
target='_blank'
|
||||
suffix={<i className='tabler-external-link text-xl' />}
|
||||
>
|
||||
{navigationLabels.externalLink}
|
||||
{t('externalLink')}
|
||||
</MenuItem>
|
||||
<SubMenu label={navigationLabels.menuLevels} icon={<i className='tabler-menu-2' />}>
|
||||
<MenuItem>{navigationLabels.menuLevel2}</MenuItem>
|
||||
<SubMenu label={navigationLabels.menuLevel2}>
|
||||
<MenuItem>{navigationLabels.menuLevel3}</MenuItem>
|
||||
<MenuItem>{navigationLabels.menuLevel3}</MenuItem>
|
||||
<SubMenu label={t('menuLevels')} icon={<i className='tabler-menu-2' />}>
|
||||
<MenuItem>{t('menuLevel2')}</MenuItem>
|
||||
<SubMenu label={t('menuLevel2')}>
|
||||
<MenuItem>{t('menuLevel3')}</MenuItem>
|
||||
<MenuItem>{t('menuLevel3')}</MenuItem>
|
||||
</SubMenu>
|
||||
</SubMenu>
|
||||
<MenuItem disabled>{navigationLabels.disabledMenu}</MenuItem>
|
||||
<MenuItem disabled>{t('disabledMenu')}</MenuItem>
|
||||
</SubMenu>
|
||||
</Menu>
|
||||
{/* <Menu
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// React Imports
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// MUI Imports
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
|
||||
@@ -11,11 +14,6 @@ import type { VerticalMenuContextProps } from "@menu/components/vertical-menu/Me
|
||||
import { Menu, SubMenu, MenuItem, MenuSection } from "@menu/vertical-menu";
|
||||
import CustomChip from "@core/components/mui/Chip";
|
||||
|
||||
// Constants
|
||||
import { navigationLabels } from "@/constants/navigation";
|
||||
|
||||
// import { GenerateVerticalMenu } from '@components/GenerateMenu'
|
||||
|
||||
// Hook Imports
|
||||
import useVerticalNav from "@menu/hooks/useVerticalNav";
|
||||
|
||||
@@ -51,7 +49,7 @@ const RenderExpandIcon = ({
|
||||
);
|
||||
|
||||
const VerticalMenu = ({ scrollMenu }: Props) => {
|
||||
// Hooks
|
||||
const t = useTranslations('navigation')
|
||||
const theme = useTheme();
|
||||
const verticalNavOptions = useVerticalNav();
|
||||
|
||||
@@ -94,7 +92,7 @@ const VerticalMenu = ({ scrollMenu }: Props) => {
|
||||
href={`/dashboard`}
|
||||
icon={<i className="tabler-smart-home" />}
|
||||
>
|
||||
{navigationLabels.dashboards}
|
||||
{t('dashboards')}
|
||||
</MenuItem>
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { getRequestConfig } from 'next-intl/server'
|
||||
import faMessages from '../../messages/fa.json'
|
||||
|
||||
export default getRequestConfig(async () => {
|
||||
const locale = 'fa'
|
||||
|
||||
return {
|
||||
locale,
|
||||
messages: faMessages as Record<string, unknown>
|
||||
}
|
||||
})
|
||||
+34
-32
@@ -1,11 +1,12 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useState } from 'react'
|
||||
import { useMemo, useState } from 'react'
|
||||
|
||||
// Next Imports
|
||||
import Link from 'next/link'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// MUI Imports
|
||||
import useMediaQuery from '@mui/material/useMediaQuery'
|
||||
@@ -20,7 +21,6 @@ import { Controller, useForm } from 'react-hook-form'
|
||||
import { valibotResolver } from '@hookform/resolvers/valibot'
|
||||
import { object, minLength, maxLength, string, pipe, nonEmpty, custom } from 'valibot'
|
||||
import type { SubmitHandler } from 'react-hook-form'
|
||||
import type { InferInput } from 'valibot'
|
||||
import classnames from 'classnames'
|
||||
import { OTPInput } from 'input-otp'
|
||||
|
||||
@@ -76,22 +76,26 @@ type ErrorType = {
|
||||
message: string
|
||||
}
|
||||
|
||||
type PhoneFormData = InferInput<typeof phoneSchema>
|
||||
|
||||
const phoneSchema = object({
|
||||
phone_number: pipe(
|
||||
string(),
|
||||
nonEmpty('Phone number is required'),
|
||||
minLength(10, 'Phone number must be at least 10 digits'),
|
||||
maxLength(15, 'Phone number must be at most 15 digits'),
|
||||
custom(
|
||||
(input) => /^[0-9]+$/.test(input),
|
||||
'Phone number must contain only digits'
|
||||
)
|
||||
)
|
||||
})
|
||||
type PhoneFormData = {
|
||||
phone_number: string
|
||||
}
|
||||
|
||||
const Login = ({ mode }: { mode: SystemMode }) => {
|
||||
const t = useTranslations('login')
|
||||
|
||||
const phoneSchema = useMemo(
|
||||
() =>
|
||||
object({
|
||||
phone_number: pipe(
|
||||
string(),
|
||||
nonEmpty(String(t('validation.phoneRequired'))),
|
||||
minLength(10, String(t('validation.phoneMinLength'))),
|
||||
maxLength(15, String(t('validation.phoneMaxLength'))),
|
||||
custom((input) => /^[0-9]+$/.test(String(input)), String(t('validation.phoneDigitsOnly')))
|
||||
)
|
||||
}),
|
||||
[t]
|
||||
)
|
||||
// States
|
||||
const [step, setStep] = useState<'phone' | 'otp'>('phone')
|
||||
const [otp, setOtp] = useState('')
|
||||
@@ -122,7 +126,7 @@ const Login = ({ mode }: { mode: SystemMode }) => {
|
||||
getValues,
|
||||
formState: { errors }
|
||||
} = useForm<PhoneFormData>({
|
||||
resolver: valibotResolver(phoneSchema),
|
||||
resolver: valibotResolver(phoneSchema) as any,
|
||||
defaultValues: {
|
||||
phone_number: ''
|
||||
}
|
||||
@@ -145,7 +149,7 @@ const Login = ({ mode }: { mode: SystemMode }) => {
|
||||
setTempToken(token)
|
||||
setStep('otp')
|
||||
} catch (error: any) {
|
||||
setErrorState({ message: error.message || 'Failed to send OTP' })
|
||||
setErrorState({ message: error.message || t('errors.sendOtpFailed') })
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
@@ -153,7 +157,7 @@ const Login = ({ mode }: { mode: SystemMode }) => {
|
||||
|
||||
const onOtpSubmit = async () => {
|
||||
if (otp.length !== 6) {
|
||||
setErrorState({ message: 'Please enter the complete 6-digit OTP code' })
|
||||
setErrorState({ message: t('errors.incompleteOtp') })
|
||||
return
|
||||
}
|
||||
|
||||
@@ -168,7 +172,7 @@ const Login = ({ mode }: { mode: SystemMode }) => {
|
||||
const redirectURL = searchParams.get('redirectTo') ?? '/'
|
||||
router.replace(redirectURL)
|
||||
} catch (error: any) {
|
||||
setErrorState({ message: error.message || 'OTP verification failed' })
|
||||
setErrorState({ message: error.message || t('errors.otpVerificationFailed') })
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
@@ -199,11 +203,9 @@ const Login = ({ mode }: { mode: SystemMode }) => {
|
||||
</div>
|
||||
<div className='flex flex-col gap-6 is-full sm:is-auto md:is-full sm:max-is-[400px] md:max-is-[unset] mbs-8 sm:mbs-11 md:mbs-0'>
|
||||
<div className='flex flex-col gap-1'>
|
||||
<Typography variant='h4'>{`Welcome to ${themeConfig.templateName}! 👋🏻`}</Typography>
|
||||
<Typography variant='h4'>{t('welcome', { templateName: themeConfig.templateName })}</Typography>
|
||||
<Typography>
|
||||
{step === 'phone'
|
||||
? 'Please enter your phone number to receive OTP'
|
||||
: 'Please enter the OTP code sent to your phone'}
|
||||
{step === 'phone' ? t('phoneStep') : t('otpStep')}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
@@ -231,8 +233,8 @@ const Login = ({ mode }: { mode: SystemMode }) => {
|
||||
autoFocus
|
||||
fullWidth
|
||||
type='tel'
|
||||
label='Phone Number'
|
||||
placeholder='Enter your phone number'
|
||||
label={t('phoneNumber')}
|
||||
placeholder={t('placeholderPhone')}
|
||||
onChange={e => {
|
||||
field.onChange(e.target.value.replace(/\D/g, '')) // Only allow digits
|
||||
errorState !== null && setErrorState(null)
|
||||
@@ -245,12 +247,12 @@ const Login = ({ mode }: { mode: SystemMode }) => {
|
||||
)}
|
||||
/>
|
||||
<Button fullWidth variant='contained' type='submit' disabled={isLoading}>
|
||||
{isLoading ? <CircularProgress size={24} /> : 'Send OTP'}
|
||||
{isLoading ? <CircularProgress size={24} /> : t('sendOtp')}
|
||||
</Button>
|
||||
<div className='flex justify-center items-center flex-wrap gap-2'>
|
||||
<Typography>New on our platform?</Typography>
|
||||
<Typography>{t('newUser')}</Typography>
|
||||
<Typography component={Link} href='/register' color='primary.main'>
|
||||
Create an account
|
||||
{t('createAccount')}
|
||||
</Typography>
|
||||
</div>
|
||||
</form>
|
||||
@@ -258,7 +260,7 @@ const Login = ({ mode }: { mode: SystemMode }) => {
|
||||
<div className='flex flex-col gap-6'>
|
||||
<div className='flex flex-col gap-4'>
|
||||
<Typography variant='body2' color='text.secondary'>
|
||||
OTP sent to {getValues('phone_number')}
|
||||
{t('otpSent', { phone: getValues('phone_number') })}
|
||||
</Typography>
|
||||
<OtpContainer>
|
||||
<OTPInput
|
||||
@@ -289,10 +291,10 @@ const Login = ({ mode }: { mode: SystemMode }) => {
|
||||
</OtpContainer>
|
||||
</div>
|
||||
<Button fullWidth variant='contained' onClick={onOtpSubmit} disabled={isLoading || otp.length !== 6}>
|
||||
{isLoading ? <CircularProgress size={24} /> : 'Verify OTP'}
|
||||
{isLoading ? <CircularProgress size={24} /> : t('verifyOtp')}
|
||||
</Button>
|
||||
<Button fullWidth variant='text' onClick={handleBackToPhone} disabled={isLoading}>
|
||||
Back to Phone Number
|
||||
{t('backToPhone')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
import CardHeader from '@mui/material/CardHeader'
|
||||
@@ -23,13 +26,14 @@ interface AnomalyDetectionCardProps {
|
||||
}
|
||||
|
||||
const AnomalyDetectionCard = ({ data }: AnomalyDetectionCardProps) => {
|
||||
const t = useTranslations('farmDashboard')
|
||||
const anomalies = (data?.anomalies as AnomalyItem[] | undefined) ?? []
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader
|
||||
avatar={<i className='tabler-alert-triangle text-xl' />}
|
||||
title='Anomaly Detection'
|
||||
title={t('cards.anomalyDetectionCard')}
|
||||
subheader='Out of range values'
|
||||
action={<OptionMenu options={['View All', 'Configure', 'Export']} />}
|
||||
sx={{ '& .MuiCardHeader-avatar': { mr: 3 } }}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
// Next Imports
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
@@ -35,6 +36,7 @@ interface EconomicOverviewProps {
|
||||
}
|
||||
|
||||
const EconomicOverview = ({ data }: EconomicOverviewProps) => {
|
||||
const t = useTranslations('farmDashboard')
|
||||
const economicData = (data?.economicData as EconomicItem[] | undefined) ?? []
|
||||
const chartSeries = (data?.chartSeries as Array<{ name: string; data: number[] }>) ?? []
|
||||
const chartCategories = (data?.chartCategories as string[]) ?? ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']
|
||||
@@ -74,7 +76,7 @@ const EconomicOverview = ({ data }: EconomicOverviewProps) => {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader
|
||||
title='Economic Overview'
|
||||
title={t('cards.economicOverview')}
|
||||
subheader='Costs & ROI'
|
||||
action={<OptionMenu options={['Export PDF', 'Export Excel', 'Details']} />}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
import CardHeader from '@mui/material/CardHeader'
|
||||
@@ -39,13 +42,14 @@ interface FarmAlertsTimelineProps {
|
||||
}
|
||||
|
||||
const FarmAlertsTimeline = ({ data }: FarmAlertsTimelineProps) => {
|
||||
const t = useTranslations('farmDashboard')
|
||||
const alerts = (data?.alerts as AlertItem[] | undefined) ?? []
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader
|
||||
avatar={<i className='tabler-bell-ring text-xl' />}
|
||||
title='AI Alerts'
|
||||
title={t('cards.farmAlertsTimeline')}
|
||||
titleTypographyProps={{ variant: 'h5' }}
|
||||
subheader='Explainable recommendations'
|
||||
action={<OptionMenu options={['View All', 'Configure', 'Export']} />}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
// Next Imports
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
@@ -36,6 +37,7 @@ interface FarmAlertsTrackerProps {
|
||||
}
|
||||
|
||||
const FarmAlertsTracker = ({ data }: FarmAlertsTrackerProps) => {
|
||||
const t = useTranslations('farmDashboard')
|
||||
const alertStats = (data?.alertStats as AlertStatType[] | undefined) ?? []
|
||||
const totalAlerts = (data?.totalAlerts as number | undefined) ?? 0
|
||||
const radialBarValue = (data?.radialBarValue as number | undefined) ?? 30
|
||||
@@ -94,7 +96,7 @@ const FarmAlertsTracker = ({ data }: FarmAlertsTrackerProps) => {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader
|
||||
title='Active Alerts'
|
||||
title={t('cards.farmAlertsTracker')}
|
||||
subheader='Requires Attention'
|
||||
action={<OptionMenu options={['View All', 'Dismiss', 'Settings']} />}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// MUI Imports
|
||||
import Drawer from '@mui/material/Drawer'
|
||||
import Typography from '@mui/material/Typography'
|
||||
@@ -33,6 +36,7 @@ type FarmDashboardSettingsDrawerProps = {
|
||||
}
|
||||
|
||||
const FarmDashboardSettingsDrawer = (props: FarmDashboardSettingsDrawerProps) => {
|
||||
const t = useTranslations('farmDashboard')
|
||||
const { open, onClose, disabledCardIds, onToggleCard, cardLabels, rowLabels, rowCards } = props
|
||||
const disabledSet = new Set(disabledCardIds)
|
||||
|
||||
@@ -55,9 +59,9 @@ const FarmDashboardSettingsDrawer = (props: FarmDashboardSettingsDrawerProps) =>
|
||||
>
|
||||
<Box className='flex flex-col is-full' sx={{ height: '100%' }}>
|
||||
<Box className='p-6'>
|
||||
<Typography variant='h5'>Dashboard Settings</Typography>
|
||||
<Typography variant='h5'>{t('settings.title')}</Typography>
|
||||
<Typography variant='body2' color='text.secondary' sx={{ mt: 0.5 }}>
|
||||
Toggle cards to show or hide on the dashboard
|
||||
{t('settings.toggleCards')}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Divider />
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
// React Imports
|
||||
import { useRef, useState } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// MUI Imports
|
||||
import IconButton from '@mui/material/IconButton'
|
||||
@@ -43,6 +44,7 @@ type FarmDashboardSettingsDropdownProps = {
|
||||
}
|
||||
|
||||
const FarmDashboardSettingsDropdown = (props: FarmDashboardSettingsDropdownProps) => {
|
||||
const t = useTranslations('farmDashboard')
|
||||
const { disabledCardIds, onToggleCard, enableDragReorder, onToggleDragReorder, cardLabels, rowLabels, rowCards, saving } = props
|
||||
const { settings } = useSettings()
|
||||
const [open, setOpen] = useState(false)
|
||||
@@ -59,7 +61,7 @@ const FarmDashboardSettingsDropdown = (props: FarmDashboardSettingsDropdownProps
|
||||
<IconButton
|
||||
ref={anchorRef}
|
||||
onClick={handleToggle}
|
||||
aria-label='Dashboard settings'
|
||||
aria-label={t('settings.ariaLabel')}
|
||||
className='text-textPrimary'
|
||||
>
|
||||
<i className='tabler-settings' />
|
||||
@@ -82,9 +84,9 @@ const FarmDashboardSettingsDropdown = (props: FarmDashboardSettingsDropdownProps
|
||||
<ClickAwayListener onClickAway={handleClose}>
|
||||
<Box className='flex flex-col max-bs-[70vh]'>
|
||||
<Box className='p-4'>
|
||||
<Typography variant='h6'>Dashboard Settings</Typography>
|
||||
<Typography variant='h6'>{t('settings.title')}</Typography>
|
||||
<Typography variant='body2' color='text.secondary' sx={{ mt: 0.5 }}>
|
||||
Toggle cards to show or hide
|
||||
{t('settings.toggleCards')}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box className='px-4 pb-2'>
|
||||
@@ -97,7 +99,7 @@ const FarmDashboardSettingsDropdown = (props: FarmDashboardSettingsDropdownProps
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Typography variant='body2'>Enable drag & reorder rows</Typography>
|
||||
<Typography variant='body2'>{t('settings.enableDragReorder')}</Typography>
|
||||
}
|
||||
sx={{ m: 0 }}
|
||||
/>
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
// React Imports
|
||||
import type { RefObject } from 'react'
|
||||
import { useEffect, useState, useCallback, useContext } from 'react'
|
||||
import { useEffect, useMemo, useState, useCallback, useContext } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// Context Imports
|
||||
import NavbarSlotContext from '@/contexts/navbarSlotContext'
|
||||
@@ -38,9 +39,7 @@ import EconomicOverview from '@views/dashboards/farm/EconomicOverview'
|
||||
import {
|
||||
ROW_IDS,
|
||||
ROW_CARDS,
|
||||
ROW_LABELS,
|
||||
CARD_GRID_SIZE,
|
||||
CARD_LABELS,
|
||||
DEFAULT_FARM_DASHBOARD_CONFIG,
|
||||
type RowId,
|
||||
type CardId,
|
||||
@@ -89,8 +88,55 @@ function mergeRowOrderAfterDrag(
|
||||
}
|
||||
|
||||
const FarmDashboardWrapper = () => {
|
||||
const t = useTranslations('farmDashboard')
|
||||
const { setSlotContent } = useContext(NavbarSlotContext)
|
||||
const [config, setConfig] = useState<FarmDashboardConfig>(DEFAULT_FARM_DASHBOARD_CONFIG)
|
||||
|
||||
const cardLabels = useMemo(
|
||||
() =>
|
||||
Object.fromEntries(
|
||||
(
|
||||
[
|
||||
'farmOverviewKpis',
|
||||
'farmWeatherCard',
|
||||
'farmAlertsTracker',
|
||||
'sensorValuesList',
|
||||
'sensorRadarChart',
|
||||
'sensorComparisonChart',
|
||||
'anomalyDetectionCard',
|
||||
'farmAlertsTimeline',
|
||||
'waterNeedPrediction',
|
||||
'harvestPredictionCard',
|
||||
'yieldPredictionChart',
|
||||
'soilMoistureHeatmap',
|
||||
'ndviHealthCard',
|
||||
'recommendationsList',
|
||||
'economicOverview'
|
||||
] as CardId[]
|
||||
).map((id) => [id, t(`cards.${id}`)])
|
||||
) as Record<CardId, string>,
|
||||
[t]
|
||||
)
|
||||
|
||||
const rowLabels = useMemo(
|
||||
() =>
|
||||
Object.fromEntries(
|
||||
(
|
||||
[
|
||||
'overviewKpis',
|
||||
'weatherAlerts',
|
||||
'sensorMonitoring',
|
||||
'sensorCharts',
|
||||
'alertsWater',
|
||||
'predictions',
|
||||
'soilHeatmap',
|
||||
'ndviRecommendations',
|
||||
'economic'
|
||||
] as RowId[]
|
||||
).map((id) => [id, t(`rows.${id}`)])
|
||||
) as Record<RowId, string>,
|
||||
[t]
|
||||
)
|
||||
const [cardsData, setCardsData] = useState<Partial<Record<CardId, Record<string, unknown>>>>({})
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [saving, setSaving] = useState(false)
|
||||
@@ -183,8 +229,8 @@ const FarmDashboardWrapper = () => {
|
||||
onToggleCard={handleToggleCard}
|
||||
enableDragReorder={config.enableDragReorder ?? true}
|
||||
onToggleDragReorder={handleToggleDragReorder}
|
||||
cardLabels={CARD_LABELS}
|
||||
rowLabels={ROW_LABELS}
|
||||
cardLabels={cardLabels}
|
||||
rowLabels={rowLabels}
|
||||
rowCards={ROW_CARDS}
|
||||
saving={saving}
|
||||
/>
|
||||
@@ -231,7 +277,7 @@ const FarmDashboardWrapper = () => {
|
||||
mt: 1,
|
||||
'&:active': { cursor: 'grabbing' }
|
||||
}}
|
||||
aria-label={`Drag ${ROW_LABELS[rowId as RowId]}`}
|
||||
aria-label={t('settings.dragRow', { row: rowLabels[rowId as RowId] })}
|
||||
>
|
||||
<i className='tabler-arrows-move text-textSecondary' />
|
||||
</IconButton>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
// Next Imports
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
@@ -24,6 +25,7 @@ interface FarmWeatherCardProps {
|
||||
}
|
||||
|
||||
const FarmWeatherCard = ({ data }: FarmWeatherCardProps) => {
|
||||
const t = useTranslations('farmDashboard')
|
||||
const temperature = data?.temperature ?? 24
|
||||
const condition = (data?.condition as string) ?? ''
|
||||
const humidity = data?.humidity ?? 45
|
||||
@@ -86,7 +88,7 @@ const FarmWeatherCard = ({ data }: FarmWeatherCardProps) => {
|
||||
return (
|
||||
<Card className='pbe-6'>
|
||||
<CardHeader
|
||||
title='Weather Today'
|
||||
title={t('cards.farmWeatherCard')}
|
||||
subheader={condition ? `${condition}, ${temperature}${unit}` : `${temperature}${unit}`}
|
||||
className='pbe-3'
|
||||
action={<OptionMenu options={['Refresh', '7-day forecast', 'Details']} />}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
import CardHeader from '@mui/material/CardHeader'
|
||||
@@ -16,6 +19,7 @@ interface HarvestPredictionCardProps {
|
||||
}
|
||||
|
||||
const HarvestPredictionCard = ({ data }: HarvestPredictionCardProps) => {
|
||||
const t = useTranslations('farmDashboard')
|
||||
const harvestDate = (data?.dateFormatted as string) ?? ''
|
||||
const daysUntil = (data?.daysUntil as number | undefined) ?? 0
|
||||
const daysLeftFormatted = daysUntil > 0 ? `${daysUntil} days` : ''
|
||||
@@ -29,7 +33,7 @@ const HarvestPredictionCard = ({ data }: HarvestPredictionCardProps) => {
|
||||
<i className='tabler-calendar-event text-2xl' />
|
||||
</CustomAvatar>
|
||||
}
|
||||
title='Harvest Prediction'
|
||||
title={t('cards.harvestPredictionCard')}
|
||||
subheader='AI Estimated Date'
|
||||
action={<OptionMenu options={['Details', 'Adjust', 'Export']} />}
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
// Next Imports
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
@@ -26,6 +27,7 @@ interface NDVIHealthCardProps {
|
||||
}
|
||||
|
||||
const NDVIHealthCard = ({ data }: NDVIHealthCardProps) => {
|
||||
const t = useTranslations('farmDashboard')
|
||||
const ndviIndex = (data?.ndviIndex as number | undefined) ?? 0
|
||||
const healthData =
|
||||
(data?.healthData as Array<{ title: string; value: string; color: string; icon: string }>) ?? []
|
||||
@@ -85,7 +87,7 @@ const NDVIHealthCard = ({ data }: NDVIHealthCardProps) => {
|
||||
<Card>
|
||||
<CardHeader
|
||||
avatar={<i className='tabler-chart-radar text-xl' />}
|
||||
title='NDVI Health'
|
||||
title={t('cards.ndviHealthCard')}
|
||||
subheader='Vegetation Index'
|
||||
sx={{ '& .MuiCardHeader-avatar': { mr: 3 } }}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
import CardHeader from '@mui/material/CardHeader'
|
||||
@@ -25,13 +28,14 @@ interface RecommendationsListProps {
|
||||
}
|
||||
|
||||
const RecommendationsList = ({ data }: RecommendationsListProps) => {
|
||||
const t = useTranslations('farmDashboard')
|
||||
const recommendations = (data?.recommendations as RecommendationType[] | undefined) ?? []
|
||||
if (recommendations.length === 0) return null
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader
|
||||
title='AI Recommendations'
|
||||
title={t('cards.recommendationsList')}
|
||||
subheader='Action Items'
|
||||
action={<OptionMenu options={['Export', 'Snooze', 'Mark Done']} />}
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
// Next Imports
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
@@ -21,6 +22,7 @@ interface SensorComparisonChartProps {
|
||||
}
|
||||
|
||||
const SensorComparisonChart = ({ data }: SensorComparisonChartProps) => {
|
||||
const t = useTranslations('farmDashboard')
|
||||
const series = (data?.series as Array<{ name: string; data: number[] }>) ?? []
|
||||
const categories = (data?.categories as string[]) ?? ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
||||
const currentValue = data?.currentValue ?? 48
|
||||
@@ -75,7 +77,7 @@ const SensorComparisonChart = ({ data }: SensorComparisonChartProps) => {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader
|
||||
title='Soil Moisture Trend'
|
||||
title={t('cards.sensorComparisonChart')}
|
||||
subheader='Today vs Last Week'
|
||||
/>
|
||||
<CardContent>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
// Next Imports
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
@@ -23,6 +24,7 @@ interface SensorRadarChartProps {
|
||||
}
|
||||
|
||||
const SensorRadarChart = ({ data }: SensorRadarChartProps) => {
|
||||
const t = useTranslations('farmDashboard')
|
||||
const series = (data?.series as Array<{ name: string; data: number[] }>) ?? []
|
||||
const labels = (data?.labels as string[]) ?? []
|
||||
const theme = useTheme()
|
||||
@@ -70,7 +72,7 @@ const SensorRadarChart = ({ data }: SensorRadarChartProps) => {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader
|
||||
title='Sensor Comparison'
|
||||
title={t('cards.sensorRadarChart')}
|
||||
subheader='Today vs Ideal Ranges'
|
||||
action={<OptionMenu options={['Today', 'This Week', 'This Month']} />}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
import CardHeader from '@mui/material/CardHeader'
|
||||
@@ -25,13 +28,14 @@ interface SensorValuesListProps {
|
||||
}
|
||||
|
||||
const SensorValuesList = ({ data }: SensorValuesListProps) => {
|
||||
const t = useTranslations('farmDashboard')
|
||||
const sensors = (data?.sensors as SensorDataType[] | undefined) ?? []
|
||||
if (sensors.length === 0) return null
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader
|
||||
title='Environmental Sensors'
|
||||
title={t('cards.sensorValuesList')}
|
||||
subheader='Real-time Data'
|
||||
action={<OptionMenu options={['Last Hour', 'Last 24h', 'Last 7 Days']} />}
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
// Next Imports
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
@@ -30,6 +31,7 @@ interface SoilMoistureHeatmapProps {
|
||||
}
|
||||
|
||||
const SoilMoistureHeatmap = ({ data }: SoilMoistureHeatmapProps) => {
|
||||
const t = useTranslations('farmDashboard')
|
||||
const series = (data?.series as HeatmapSeries[]) ?? []
|
||||
const theme = useTheme()
|
||||
if (series.length === 0) return null
|
||||
@@ -72,7 +74,7 @@ const SoilMoistureHeatmap = ({ data }: SoilMoistureHeatmapProps) => {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader
|
||||
title='Soil Moisture Heatmap'
|
||||
title={t('cards.soilMoistureHeatmap')}
|
||||
subheader='Field Zones by Time'
|
||||
/>
|
||||
<CardContent>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// Next Imports
|
||||
import dynamic from 'next/dynamic'
|
||||
|
||||
@@ -24,6 +27,7 @@ interface WaterNeedPredictionProps {
|
||||
}
|
||||
|
||||
const WaterNeedPrediction = ({ data }: WaterNeedPredictionProps) => {
|
||||
const t = useTranslations('farmDashboard')
|
||||
const series = (data?.series as Array<{ name: string; data: number[] }>) ?? []
|
||||
const categories = (data?.categories as string[]) ?? ['Day 1', 'Day 2', 'Day 3', 'Day 4', 'Day 5', 'Day 6', 'Day 7']
|
||||
const totalNext7Days = data?.totalNext7Days ?? 0
|
||||
@@ -74,7 +78,7 @@ const WaterNeedPrediction = ({ data }: WaterNeedPredictionProps) => {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader
|
||||
title='7-Day Water Need Prediction'
|
||||
title={t('cards.waterNeedPrediction')}
|
||||
subheader='AI Forecast'
|
||||
action={<OptionMenu options={['Export', 'Adjust', 'Details']} />}
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
// Next Imports
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
@@ -34,6 +35,7 @@ interface YieldPredictionChartProps {
|
||||
}
|
||||
|
||||
const YieldPredictionChart = ({ data }: YieldPredictionChartProps) => {
|
||||
const t = useTranslations('farmDashboard')
|
||||
const series = (data?.series as Array<{ name: string; data: number[] }>) ?? []
|
||||
const categories =
|
||||
(data?.categories as string[]) ??
|
||||
@@ -77,7 +79,7 @@ const YieldPredictionChart = ({ data }: YieldPredictionChartProps) => {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader
|
||||
title='Yield Prediction'
|
||||
title={t('cards.yieldPredictionChart')}
|
||||
subheader='This Year vs Last Year'
|
||||
action={<OptionMenu options={['Export', 'Compare', 'Details']} />}
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
// React Imports
|
||||
import { useState } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
@@ -20,7 +21,7 @@ import { useForm, Controller } from 'react-hook-form'
|
||||
import ConfirmationDialog from '@components/dialogs/confirmation-dialog'
|
||||
|
||||
const AccountDelete = () => {
|
||||
// States
|
||||
const t = useTranslations('accountSettings')
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
// Hooks
|
||||
@@ -40,7 +41,7 @@ const AccountDelete = () => {
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader title='Delete Account' />
|
||||
<CardHeader title={t('deleteAccount')} />
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<FormControl error={Boolean(errors.checkbox)} className='is-full mbe-6'>
|
||||
@@ -49,13 +50,13 @@ const AccountDelete = () => {
|
||||
control={control}
|
||||
rules={{ required: true }}
|
||||
render={({ field }) => (
|
||||
<FormControlLabel control={<Checkbox {...field} />} label='I confirm my account deactivation' />
|
||||
<FormControlLabel control={<Checkbox {...field} />} label={t('confirmDeactivation')} />
|
||||
)}
|
||||
/>
|
||||
{errors.checkbox && <FormHelperText error>Please confirm you want to delete account</FormHelperText>}
|
||||
{errors.checkbox && <FormHelperText error>{t('confirmDelete')}</FormHelperText>}
|
||||
</FormControl>
|
||||
<Button variant='contained' color='error' type='submit' disabled={!checkboxValue}>
|
||||
Deactivate Account
|
||||
{t('deactivateAccount')}
|
||||
</Button>
|
||||
<ConfirmationDialog open={open} setOpen={setOpen} type='delete-account' />
|
||||
</form>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
// React Imports
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import type { ChangeEvent } from 'react'
|
||||
|
||||
// MUI Imports
|
||||
@@ -36,7 +37,7 @@ const initialData: Data = {
|
||||
}
|
||||
|
||||
const AccountDetails = () => {
|
||||
// Auth
|
||||
const t = useTranslations('accountSettings')
|
||||
const { user: authUser } = useAuth()
|
||||
|
||||
// States
|
||||
@@ -98,7 +99,7 @@ const AccountDetails = () => {
|
||||
})
|
||||
// Refresh auth user data - caller would need to update context; for now form stays as-is
|
||||
} catch (err: any) {
|
||||
setError(err.message || 'خطا در ذخیره تغییرات')
|
||||
setError(err.message || t('errorSave'))
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
@@ -171,45 +172,45 @@ const AccountDetails = () => {
|
||||
<Grid size={{ xs: 12, sm: 6 }}>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
label='First Name'
|
||||
label={t('firstName')}
|
||||
value={formData.firstName}
|
||||
placeholder='John'
|
||||
placeholder={t('placeholderFirstName')}
|
||||
onChange={e => handleFormChange('firstName', e.target.value)}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, sm: 6 }}>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
label='Last Name'
|
||||
label={t('lastName')}
|
||||
value={formData.lastName}
|
||||
placeholder='Doe'
|
||||
placeholder={t('placeholderLastName')}
|
||||
onChange={e => handleFormChange('lastName', e.target.value)}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, sm: 6 }}>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
label='Email'
|
||||
label={t('email')}
|
||||
value={formData.email}
|
||||
placeholder='john.doe@gmail.com'
|
||||
placeholder={t('placeholderEmail')}
|
||||
onChange={e => handleFormChange('email', e.target.value)}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, sm: 6 }}>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
label='Phone Number'
|
||||
label={t('phoneNumber')}
|
||||
value={formData.phoneNumber}
|
||||
placeholder='+1 (234) 567-8901'
|
||||
placeholder={t('placeholderPhone')}
|
||||
onChange={e => handleFormChange('phoneNumber', e.target.value)}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }} className='flex gap-4 flex-wrap'>
|
||||
<Button variant='contained' type='submit' disabled={saving}>
|
||||
{saving ? 'در حال ذخیره...' : 'Save Changes'}
|
||||
{saving ? t('saving') : t('saveChanges')}
|
||||
</Button>
|
||||
<Button variant='tonal' type='button' color='secondary' onClick={handleReset}>
|
||||
Reset
|
||||
{t('reset')}
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
// React Imports
|
||||
import { useState } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import type { SyntheticEvent, ReactElement } from 'react'
|
||||
|
||||
// MUI Imports
|
||||
@@ -14,7 +15,7 @@ import TabPanel from '@mui/lab/TabPanel'
|
||||
import CustomTabList from '@core/components/mui/TabList'
|
||||
|
||||
const AccountSettings = ({ tabContentList }: { tabContentList: { [key: string]: ReactElement } }) => {
|
||||
// States
|
||||
const t = useTranslations('accountSettings')
|
||||
const [activeTab, setActiveTab] = useState('account')
|
||||
|
||||
const handleChange = (event: SyntheticEvent, value: string) => {
|
||||
@@ -26,8 +27,8 @@ const AccountSettings = ({ tabContentList }: { tabContentList: { [key: string]:
|
||||
<Grid container spacing={6}>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<CustomTabList onChange={handleChange} variant='scrollable' pill='true'>
|
||||
<Tab label='Account' icon={<i className='tabler-users' />} iconPosition='start' value='account' />
|
||||
<Tab label='SensorHub' icon={<i className='tabler-device-watch' />} iconPosition='start' value='sensor-hub' />
|
||||
<Tab label={t('account')} icon={<i className='tabler-users' />} iconPosition='start' value='account' />
|
||||
<Tab label={t('sensorHub')} icon={<i className='tabler-device-watch' />} iconPosition='start' value='sensor-hub' />
|
||||
{/* <Tab label='Security' icon={<i className='tabler-lock' />} iconPosition='start' value='security' />
|
||||
<Tab
|
||||
label='Billing & Plans'
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
// React Imports
|
||||
import { useState } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// MUI Imports
|
||||
import Grid from '@mui/material/Grid2'
|
||||
@@ -25,6 +26,7 @@ import FormSensorHub from '@views/sensorHub/FormSensorHub'
|
||||
const transitionTimeout = { enter: 300, exit: 200 }
|
||||
|
||||
const SensorHubTabContent = () => {
|
||||
const t = useTranslations('sensorHub')
|
||||
const [showAddForm, setShowAddForm] = useState(false)
|
||||
const { setSensorHub } = useSensorHub()
|
||||
|
||||
@@ -49,10 +51,10 @@ const SensorHubTabContent = () => {
|
||||
<div className='grid grid-cols-1 sm:grid-cols-[1fr_auto] items-center gap-4'>
|
||||
<div className='flex flex-col gap-0.5'>
|
||||
<Typography variant='h6' fontWeight={600}>
|
||||
انتخاب سنسور
|
||||
{t('selectSensor')}
|
||||
</Typography>
|
||||
<Typography variant='body2' color='text.secondary' sx={{ lineHeight: 1.5 }}>
|
||||
سنسور مورد نظر را انتخاب کنید یا سنسور جدید اضافه کنید
|
||||
{t('selectSensorDescription')}
|
||||
</Typography>
|
||||
</div>
|
||||
<Button
|
||||
@@ -61,7 +63,7 @@ const SensorHubTabContent = () => {
|
||||
startIcon={<i className='tabler-plus text-xl' />}
|
||||
onClick={() => setShowAddForm(true)}
|
||||
>
|
||||
اضافه کردن سنسور
|
||||
{t('addSensor')}
|
||||
</Button>
|
||||
</div>
|
||||
<OptionSensorHub onConfirm={handleConfirm} />
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
// React Imports
|
||||
import { useState } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// MUI Imports
|
||||
import Grid from '@mui/material/Grid2'
|
||||
@@ -20,6 +21,7 @@ type FormSensorHubProps = {
|
||||
}
|
||||
|
||||
const FormSensorHub = ({ onBack }: FormSensorHubProps) => {
|
||||
const t = useTranslations('sensorHub')
|
||||
const [name, setName] = useState('')
|
||||
const [uuidSensor, setUuidSensor] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
@@ -33,7 +35,7 @@ const FormSensorHub = ({ onBack }: FormSensorHubProps) => {
|
||||
await sensorHubService.addSensor({ name, uuid_sensor: uuidSensor })
|
||||
onBack()
|
||||
} catch (err: unknown) {
|
||||
const message = err && typeof err === 'object' && 'message' in err ? String((err as { message: string }).message) : 'خطا در ذخیره سنسور'
|
||||
const message = err && typeof err === 'object' && 'message' in err ? String((err as { message: string }).message) : t('errorSave')
|
||||
setError(message)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
@@ -50,7 +52,7 @@ const FormSensorHub = ({ onBack }: FormSensorHubProps) => {
|
||||
startIcon={<i className='tabler-arrow-right text-xl' />}
|
||||
onClick={onBack}
|
||||
>
|
||||
بازگشت
|
||||
{t('back')}
|
||||
</Button>
|
||||
{/* <Typography variant='h6'>افزودن سنسور جدید</Typography> */}
|
||||
</div>
|
||||
@@ -66,8 +68,8 @@ const FormSensorHub = ({ onBack }: FormSensorHubProps) => {
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
label='نام سنسور'
|
||||
placeholder='نام سنسور را وارد کنید'
|
||||
label={t('sensorName')}
|
||||
placeholder={t('placeholderName')}
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
/>
|
||||
@@ -75,15 +77,15 @@ const FormSensorHub = ({ onBack }: FormSensorHubProps) => {
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
label='شناسه سنسور (UUID)'
|
||||
placeholder='شناسه سنسور را وارد کنید'
|
||||
label={t('sensorUuid')}
|
||||
placeholder={t('placeholderUuid')}
|
||||
value={uuidSensor}
|
||||
onChange={e => setUuidSensor(e.target.value)}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }} className='flex gap-2'>
|
||||
<Button variant='tonal' color='secondary' onClick={onBack} disabled={loading}>
|
||||
انصراف
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
variant='contained'
|
||||
@@ -91,7 +93,7 @@ const FormSensorHub = ({ onBack }: FormSensorHubProps) => {
|
||||
disabled={loading}
|
||||
startIcon={loading ? <CircularProgress size={16} color='inherit' /> : <i className='tabler-plus' />}
|
||||
>
|
||||
{loading ? 'در حال ذخیره...' : 'ذخیره سنسور'}
|
||||
{loading ? t('saving') : t('saveSensor')}
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useMemo, useState, useEffect } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
@@ -33,22 +34,26 @@ const formatToShamsi = (dateStr: string | null | undefined): string => {
|
||||
// Column Definitions
|
||||
const columnHelper = createColumnHelper<Sensor>()
|
||||
|
||||
const columns = [
|
||||
const SensorHubTable = () => {
|
||||
const t = useTranslations('sensorHub')
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor('name', {
|
||||
cell: info => info.getValue(),
|
||||
header: 'Name'
|
||||
header: t('columns.name')
|
||||
}),
|
||||
columnHelper.accessor('last_updated', {
|
||||
cell: info => formatToShamsi(info.getValue()),
|
||||
header: 'Last Update'
|
||||
header: t('columns.lastUpdate')
|
||||
}),
|
||||
columnHelper.accessor('uuid_sensor', {
|
||||
cell: info => info.getValue(),
|
||||
header: 'UUID'
|
||||
header: t('columns.uuid')
|
||||
})
|
||||
]
|
||||
|
||||
const SensorHubTable = () => {
|
||||
],
|
||||
[t]
|
||||
)
|
||||
const [data, setData] = useState<Sensor[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
@@ -61,7 +66,7 @@ const SensorHubTable = () => {
|
||||
const sensors = await sensorHubService.listSensors()
|
||||
setData(sensors)
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to load sensors')
|
||||
setError(err instanceof Error ? err.message : t('errorLoad'))
|
||||
setData([])
|
||||
} finally {
|
||||
setLoading(false)
|
||||
@@ -83,7 +88,7 @@ const SensorHubTable = () => {
|
||||
if (loading) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader title='Sensor Hub' />
|
||||
<CardHeader title={t('title')} />
|
||||
<div className='flex items-center justify-center p-12'>
|
||||
<CircularProgress />
|
||||
</div>
|
||||
@@ -94,14 +99,14 @@ const SensorHubTable = () => {
|
||||
if (error) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader title='Sensor Hub' subheader={error} />
|
||||
<CardHeader title={t('title')} subheader={error} />
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader title='Sensor Hub' />
|
||||
<CardHeader title={t('title')} />
|
||||
<div className='overflow-x-auto'>
|
||||
<table className={styles.table}>
|
||||
<thead>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// React Imports
|
||||
import { useState } from 'react'
|
||||
import type { Theme } from '@mui/material/styles'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
// Hook Imports
|
||||
import { useSensorHub } from '@/hooks/useSensorHub'
|
||||
@@ -35,12 +36,18 @@ const DialogContentWithTransition = ({
|
||||
showAddForm,
|
||||
onShowAddForm,
|
||||
onBack,
|
||||
onConfirm
|
||||
onConfirm,
|
||||
selectSensor,
|
||||
selectSensorDescription,
|
||||
addSensor
|
||||
}: {
|
||||
showAddForm: boolean
|
||||
onShowAddForm: () => void
|
||||
onBack: () => void
|
||||
onConfirm: (sensor: Sensor) => void
|
||||
selectSensor: string
|
||||
selectSensorDescription: string
|
||||
addSensor: string
|
||||
}) => (
|
||||
<Fade key={showAddForm ? 'form' : 'options'} in timeout={transitionTimeout}>
|
||||
<div>
|
||||
@@ -51,10 +58,10 @@ const DialogContentWithTransition = ({
|
||||
<div className='grid grid-cols-[1fr_auto] items-center gap-4'>
|
||||
<div className='flex flex-col gap-0.5'>
|
||||
<Typography variant='h6' fontWeight={600}>
|
||||
انتخاب سنسور
|
||||
{selectSensor}
|
||||
</Typography>
|
||||
<Typography variant='body2' color='text.secondary' sx={{ lineHeight: 1.5 }}>
|
||||
سنسور مورد نظر را انتخاب کنید یا سنسور جدید اضافه کنید
|
||||
{selectSensorDescription}
|
||||
</Typography>
|
||||
</div>
|
||||
<Button
|
||||
@@ -63,7 +70,7 @@ const DialogContentWithTransition = ({
|
||||
startIcon={<i className='tabler-plus text-xl' />}
|
||||
onClick={onShowAddForm}
|
||||
>
|
||||
اضافه کردن سنسور
|
||||
{addSensor}
|
||||
</Button>
|
||||
</div>
|
||||
<OptionSensorHub onConfirm={onConfirm} />
|
||||
@@ -77,12 +84,18 @@ const DrawerContentWithTransition = ({
|
||||
showAddForm,
|
||||
onShowAddForm,
|
||||
onBack,
|
||||
onConfirm
|
||||
onConfirm,
|
||||
selectSensor,
|
||||
selectSensorDescription,
|
||||
addSensor
|
||||
}: {
|
||||
showAddForm: boolean
|
||||
onShowAddForm: () => void
|
||||
onBack: () => void
|
||||
onConfirm: (sensor: Sensor) => void
|
||||
selectSensor: string
|
||||
selectSensorDescription: string
|
||||
addSensor: string
|
||||
}) => (
|
||||
<Fade key={showAddForm ? 'form' : 'options'} in timeout={transitionTimeout}>
|
||||
<div>
|
||||
@@ -93,10 +106,10 @@ const DrawerContentWithTransition = ({
|
||||
<div className='grid grid-cols-[1fr_auto] items-center gap-4'>
|
||||
<div className='flex flex-col gap-0.5'>
|
||||
<Typography variant='h6' fontWeight={600}>
|
||||
انتخاب سنسور
|
||||
{selectSensor}
|
||||
</Typography>
|
||||
<Typography variant='body2' color='text.secondary' sx={{ lineHeight: 1.5 }}>
|
||||
سنسور مورد نظر را انتخاب کنید یا سنسور جدید اضافه کنید
|
||||
{selectSensorDescription}
|
||||
</Typography>
|
||||
</div>
|
||||
<Button
|
||||
@@ -105,7 +118,7 @@ const DrawerContentWithTransition = ({
|
||||
startIcon={<i className='tabler-plus text-xl' />}
|
||||
onClick={onShowAddForm}
|
||||
>
|
||||
اضافه کردن سنسور
|
||||
{addSensor}
|
||||
</Button>
|
||||
</div>
|
||||
<OptionSensorHub onConfirm={onConfirm} />
|
||||
@@ -116,10 +129,17 @@ const DrawerContentWithTransition = ({
|
||||
)
|
||||
|
||||
const TableModalSheet = ({ open, onClose }: TableModalSheetProps) => {
|
||||
const t = useTranslations('sensorHub')
|
||||
const [showAddForm, setShowAddForm] = useState(false)
|
||||
const isMobile = useMediaQuery((theme: Theme) => theme.breakpoints.down('sm'))
|
||||
const { setSensorHub } = useSensorHub()
|
||||
|
||||
const contentProps = {
|
||||
selectSensor: t('selectSensor'),
|
||||
selectSensorDescription: t('selectSensorDescription'),
|
||||
addSensor: t('addSensor')
|
||||
}
|
||||
|
||||
const handleBack = () => setShowAddForm(false)
|
||||
|
||||
const handleConfirm = (sensor: Sensor) => {
|
||||
@@ -149,8 +169,8 @@ const TableModalSheet = ({ open, onClose }: TableModalSheetProps) => {
|
||||
}}
|
||||
>
|
||||
<div className='flex items-center justify-between plb-4 pli-6 border-bs'>
|
||||
<Typography variant='h5'>Sensor Data</Typography>
|
||||
<IconButton size='small' onClick={onClose} aria-label='close'>
|
||||
<Typography variant='h5'>{t('sensorData')}</Typography>
|
||||
<IconButton size='small' onClick={onClose} aria-label={t('ariaClose')}>
|
||||
<i className='tabler-x text-2xl' />
|
||||
</IconButton>
|
||||
</div>
|
||||
@@ -160,6 +180,7 @@ const TableModalSheet = ({ open, onClose }: TableModalSheetProps) => {
|
||||
onShowAddForm={() => setShowAddForm(true)}
|
||||
onBack={handleBack}
|
||||
onConfirm={handleConfirm}
|
||||
{...contentProps}
|
||||
/>
|
||||
</div>
|
||||
</Drawer>
|
||||
@@ -185,6 +206,7 @@ const TableModalSheet = ({ open, onClose }: TableModalSheetProps) => {
|
||||
onShowAddForm={() => setShowAddForm(true)}
|
||||
onBack={handleBack}
|
||||
onConfirm={handleConfirm}
|
||||
{...contentProps}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
Reference in New Issue
Block a user