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