From 0844100613023ca5e047c6c095c209d3bf58017b Mon Sep 17 00:00:00 2001 From: Mohammad Sajad Pourajam Date: Thu, 19 Feb 2026 17:21:43 +0330 Subject: [PATCH] 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. --- DASHBOARD_API_DOCUMENTATION.md | 653 ++++++++++++++++++ messages/fa.json | 216 ++++++ next.config.ts | 5 +- package-lock.json | 111 +++ package.json | 2 +- src/app/layout.tsx | 12 +- .../layout/horizontal/HorizontalMenu.tsx | 232 +++---- .../layout/vertical/VerticalMenu.tsx | 12 +- src/i18n/request.ts | 11 + src/views/Login.tsx | 66 +- .../dashboards/farm/AnomalyDetectionCard.tsx | 6 +- .../dashboards/farm/EconomicOverview.tsx | 4 +- .../dashboards/farm/FarmAlertsTimeline.tsx | 6 +- .../dashboards/farm/FarmAlertsTracker.tsx | 4 +- .../farm/FarmDashboardSettingsDrawer.tsx | 8 +- .../farm/FarmDashboardSettingsDropdown.tsx | 10 +- .../dashboards/farm/FarmDashboardWrapper.tsx | 58 +- src/views/dashboards/farm/FarmWeatherCard.tsx | 4 +- .../dashboards/farm/HarvestPredictionCard.tsx | 6 +- src/views/dashboards/farm/NDVIHealthCard.tsx | 4 +- .../dashboards/farm/RecommendationsList.tsx | 6 +- .../dashboards/farm/SensorComparisonChart.tsx | 4 +- .../dashboards/farm/SensorRadarChart.tsx | 4 +- .../dashboards/farm/SensorValuesList.tsx | 6 +- .../dashboards/farm/SoilMoistureHeatmap.tsx | 4 +- .../dashboards/farm/WaterNeedPrediction.tsx | 6 +- .../dashboards/farm/YieldPredictionChart.tsx | 4 +- .../account/AccountDelete.tsx | 11 +- .../account/AccountDetails.tsx | 25 +- src/views/pages/account-settings/index.tsx | 7 +- .../sensor-hub/SensorHubTabContent.tsx | 8 +- src/views/sensorHub/FormSensorHub.tsx | 18 +- src/views/sensorHub/SensorHubTable.tsx | 45 +- src/views/sensorHub/TableModalSheet.tsx | 42 +- 34 files changed, 1372 insertions(+), 248 deletions(-) create mode 100644 DASHBOARD_API_DOCUMENTATION.md create mode 100644 messages/fa.json create mode 100644 src/i18n/request.ts diff --git a/DASHBOARD_API_DOCUMENTATION.md b/DASHBOARD_API_DOCUMENTATION.md new file mode 100644 index 0000000..af063ad --- /dev/null +++ b/DASHBOARD_API_DOCUMENTATION.md @@ -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 +``` diff --git a/messages/fa.json b/messages/fa.json new file mode 100644 index 0000000..715e69c --- /dev/null +++ b/messages/fa.json @@ -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": "بازنشانی" + } +} diff --git a/next.config.ts b/next.config.ts index cce0450..1d536bd 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,8 +1,11 @@ import type { NextConfig } from 'next' +import createNextIntlPlugin from 'next-intl/plugin' + +const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts') const nextConfig: NextConfig = { basePath: process.env.BASEPATH, output: 'standalone' } -export default nextConfig +export default withNextIntl(nextConfig) diff --git a/package-lock.json b/package-lock.json index d79b1df..4d4b4e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,9 +54,11 @@ "mapbox-gl": "3.9.0", "negotiator": "1.0.0", "next": "15.1.2", + "next-intl": "3.25.2", "react": "18.3.1", "react-apexcharts": "1.4.1", "react-colorful": "5.6.1", + "react-date-object": "1.1.9", "react-datepicker": "7.3.0", "react-dom": "18.3.1", "react-dropzone": "14.3.5", @@ -1026,6 +1028,57 @@ "version": "0.2.8", "license": "MIT" }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "2.3.6", + "resolved": "https://mirror-npm.runflare.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.6.tgz", + "integrity": "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "2.2.7", + "@formatjs/intl-localematcher": "0.6.2", + "decimal.js": "^10.4.3", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/ecma402-abstract/node_modules/@formatjs/intl-localematcher": { + "version": "0.6.2", + "resolved": "https://mirror-npm.runflare.com/@formatjs/intl-localematcher/-/intl-localematcher-0.6.2.tgz", + "integrity": "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "2.2.7", + "resolved": "https://mirror-npm.runflare.com/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz", + "integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.11.4", + "resolved": "https://mirror-npm.runflare.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.4.tgz", + "integrity": "sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.6", + "@formatjs/icu-skeleton-parser": "1.8.16", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.16", + "resolved": "https://mirror-npm.runflare.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.16.tgz", + "integrity": "sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.6", + "tslib": "^2.8.0" + } + }, "node_modules/@formatjs/intl-localematcher": { "version": "0.5.9", "license": "MIT", @@ -4702,6 +4755,12 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://mirror-npm.runflare.com/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, "node_modules/decimal.js-light": { "version": "2.5.1", "license": "MIT" @@ -6491,6 +6550,18 @@ "node": ">=12" } }, + "node_modules/intl-messageformat": { + "version": "10.7.18", + "resolved": "https://mirror-npm.runflare.com/intl-messageformat/-/intl-messageformat-10.7.18.tgz", + "integrity": "sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==", + "license": "BSD-3-Clause", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.6", + "@formatjs/fast-memoize": "2.2.7", + "@formatjs/icu-messageformat-parser": "2.11.4", + "tslib": "^2.8.0" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "dev": true, @@ -7529,6 +7600,27 @@ } } }, + "node_modules/next-intl": { + "version": "3.25.2", + "resolved": "https://mirror-npm.runflare.com/next-intl/-/next-intl-3.25.2.tgz", + "integrity": "sha512-C2BoRMX3h+KxCf5TC6BjlnZie2EOCK+QZz4C9A7xGf++1E/r1uD25wT8EZBaQAkO2uKKhBoZg78X8j8r2HPsag==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "license": "MIT", + "dependencies": { + "@formatjs/intl-localematcher": "^0.5.4", + "negotiator": "^1.0.0", + "use-intl": "^3.25.2" + }, + "peerDependencies": { + "next": "^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://mirror-npm.runflare.com/postcss/-/postcss-8.4.31.tgz", @@ -8486,6 +8578,12 @@ "react-dom": ">=16.8.0" } }, + "node_modules/react-date-object": { + "version": "1.1.9", + "resolved": "https://mirror-npm.runflare.com/react-date-object/-/react-date-object-1.1.9.tgz", + "integrity": "sha512-NEbp/JSqwpF3nm4bXcez9XGwmlpOjSPJSYoRyPC1XOxzRHHaijp6xjMbYpyKB3O4yrluxsbwBHbO1WgeFKip3Q==", + "license": "MIT" + }, "node_modules/react-datepicker": { "version": "7.3.0", "license": "MIT", @@ -10702,6 +10800,19 @@ } } }, + "node_modules/use-intl": { + "version": "3.26.5", + "resolved": "https://mirror-npm.runflare.com/use-intl/-/use-intl-3.26.5.tgz", + "integrity": "sha512-OdsJnC/znPvHCHLQH/duvQNXnP1w0hPfS+tkSi3mAbfjYBGh4JnyfdwkQBfIVf7t8gs9eSX/CntxUMvtKdG2MQ==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "^2.2.0", + "intl-messageformat": "^10.5.14" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0" + } + }, "node_modules/use-sidecar": { "version": "1.1.3", "license": "MIT", diff --git a/package.json b/package.json index 76bf1b0..0b0e53e 100644 --- a/package.json +++ b/package.json @@ -59,13 +59,13 @@ "mapbox-gl": "3.9.0", "negotiator": "1.0.0", "next": "15.1.2", + "next-intl": "3.25.2", "react": "18.3.1", "react-apexcharts": "1.4.1", "react-colorful": "5.6.1", "react-date-object": "1.1.9", "react-datepicker": "7.3.0", "react-dom": "18.3.1", - "react-multi-date-picker": "4.4.2", "react-dropzone": "14.3.5", "react-hook-form": "7.54.1", "react-map-gl": "7.1.8", diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 1f9f344..d8dd802 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,6 +2,8 @@ import InitColorSchemeScript from '@mui/material/InitColorSchemeScript' // Third-party Imports +import { NextIntlClientProvider } from 'next-intl' +import { getLocale, getMessages } from 'next-intl/server' import 'react-perfect-scrollbar/dist/css/styles.css' // Type Imports @@ -26,17 +28,21 @@ export const metadata = getMetadata() const RootLayout = async (props: ChildrenType) => { const { children } = props - + + const locale = await getLocale() + const messages = await getMessages() // Vars const systemMode = await getSystemMode() const direction = 'rtl' // Fixed RTL direction return ( - + - {children} + + {children} + ) diff --git a/src/components/layout/horizontal/HorizontalMenu.tsx b/src/components/layout/horizontal/HorizontalMenu.tsx index a9d4f8f..9ff6e4f 100644 --- a/src/components/layout/horizontal/HorizontalMenu.tsx +++ b/src/components/layout/horizontal/HorizontalMenu.tsx @@ -1,3 +1,6 @@ +// React Imports +import { useTranslations } from 'next-intl' + // MUI Imports import { useTheme } from '@mui/material/styles' @@ -9,11 +12,6 @@ import HorizontalNav, { Menu, SubMenu, MenuItem } from '@menu/horizontal-menu' import VerticalNavContent from './VerticalNavContent' import CustomChip from '@core/components/mui/Chip' -// Constants -import { navigationLabels } from '@/constants/navigation' - -// import { GenerateHorizontalMenu } from '@components/GenerateMenu' - // Hook Imports import useVerticalNav from '@menu/hooks/useVerticalNav' @@ -53,7 +51,7 @@ const RenderVerticalExpandIcon = ({ open, transitionDuration }: RenderVerticalEx ) const HorizontalMenu = () => { - // Hooks + const t = useTranslations('navigation') const verticalNavOptions = useVerticalNav() const theme = useTheme() @@ -87,67 +85,67 @@ const HorizontalMenu = () => { menuSectionStyles: verticalMenuSectionStyles(verticalNavOptions, theme) }} > - }> + }> }> - {navigationLabels.analytics} + {t('analytics')} }> - {navigationLabels.eCommerce} + {t('eCommerce')} }> - {navigationLabels.academy} + {t('academy')} }> - {navigationLabels.logistics} + {t('logistics')} - }> - }> - {navigationLabels.dashboard} - - {navigationLabels.list} - {navigationLabels.add} + }> + }> + {t('dashboard')} + + {t('list')} + {t('add')} - {navigationLabels.category} + {t('category')} - - {navigationLabels.list} + + {t('list')} - {navigationLabels.details} + {t('details')} - - {navigationLabels.list} + + {t('list')} - {navigationLabels.details} + {t('details')} - {navigationLabels.manageReviews} + {t('manageReviews')} - {navigationLabels.referrals} - {navigationLabels.settings} + {t('referrals')} + {t('settings')} - }> - {navigationLabels.dashboard} - {navigationLabels.myCourses} + }> + {t('dashboard')} + {t('myCourses')} - {navigationLabels.courseDetails} + {t('courseDetails')} - }> - {navigationLabels.dashboard} - {navigationLabels.fleet} + }> + {t('dashboard')} + {t('fleet')} { exactMatch={false} activeUrl='/apps/email' > - {navigationLabels.email} + {t('email')} }> - {navigationLabels.chat} + {t('chat')} }> - {navigationLabels.calendar} + {t('calendar')} }> - {navigationLabels.kanban} + {t('kanban')} - }> - {navigationLabels.list} + }> + {t('list')} - {navigationLabels.preview} + {t('preview')} - {navigationLabels.edit} + {t('edit')} - {navigationLabels.add} + {t('add')} - }> - {navigationLabels.list} - {navigationLabels.view} + }> + {t('list')} + {t('view')} - }> - {navigationLabels.roles} - {navigationLabels.permissions} + }> + {t('roles')} + {t('permissions')} - }> + }> }> - {navigationLabels.userProfile} + {t('userProfile')} - }> + }> - {navigationLabels.comingSoon} + {t('comingSoon')} - {navigationLabels.underMaintenance} + {t('underMaintenance')} - {navigationLabels.pageNotFound404} + {t('pageNotFound404')} - {navigationLabels.notAuthorized401} + {t('notAuthorized401')} - }> - + }> + - {navigationLabels.loginV1} + {t('loginV1')} - {navigationLabels.loginV2} + {t('loginV2')} - + - {navigationLabels.registerV1} + {t('registerV1')} - {navigationLabels.registerV2} + {t('registerV2')} - {navigationLabels.registerMultiSteps} + {t('registerMultiSteps')} - + - {navigationLabels.verifyEmailV1} + {t('verifyEmailV1')} - {navigationLabels.verifyEmailV2} + {t('verifyEmailV2')} - + - {navigationLabels.forgotPasswordV1} + {t('forgotPasswordV1')} - {navigationLabels.forgotPasswordV2} + {t('forgotPasswordV2')} - + - {navigationLabels.resetPasswordV1} + {t('resetPasswordV1')} - {navigationLabels.resetPasswordV2} + {t('resetPasswordV2')} - + - {navigationLabels.twoStepsV1} + {t('twoStepsV1')} - {navigationLabels.twoStepsV2} + {t('twoStepsV2')} - }> - {navigationLabels.checkout} + }> + {t('checkout')} - {navigationLabels.propertyListing} + {t('propertyListing')} - {navigationLabels.createDeal} + {t('createDeal')} }> - {navigationLabels.dialogExamples} + {t('dialogExamples')} - }> - {navigationLabels.basic} - {navigationLabels.advanced} + }> + {t('basic')} + {t('advanced')} - {navigationLabels.statistics} + {t('statistics')} - {navigationLabels.charts} - {navigationLabels.actions} + {t('charts')} + {t('actions')} - }> + }> - {navigationLabels.landing} + {t('landing')} - {navigationLabels.pricing} + {t('pricing')} - {navigationLabels.payment} + {t('payment')} - {navigationLabels.checkout} + {t('checkout')} - {navigationLabels.helpCenter} + {t('helpCenter')} - }> + }> }> - {navigationLabels.formLayouts} + {t('formLayouts')} }> - {navigationLabels.formValidation} + {t('formValidation')} }> - {navigationLabels.formWizard} + {t('formWizard')} }> - {navigationLabels.reactTable} + {t('reactTable')} } @@ -318,7 +316,7 @@ const HorizontalMenu = () => { suffix={} target='_blank' > - {navigationLabels.formELements} + {t('formELements')} } @@ -326,25 +324,25 @@ const HorizontalMenu = () => { suffix={} target='_blank' > - {navigationLabels.muiTables} + {t('muiTables')} - }> + }> }> - {navigationLabels.apex} + {t('apex')} }> - {navigationLabels.recharts} + {t('recharts')} - }> + }> } href={`${process.env.NEXT_PUBLIC_DOCS_URL}/docs/user-interface/foundation`} suffix={} target='_blank' > - {navigationLabels.foundation} + {t('foundation')} } @@ -352,7 +350,7 @@ const HorizontalMenu = () => { suffix={} target='_blank' > - {navigationLabels.components} + {t('components')} } @@ -360,7 +358,7 @@ const HorizontalMenu = () => { suffix={} target='_blank' > - {navigationLabels.menuExamples} + {t('menuExamples')} } @@ -368,7 +366,7 @@ const HorizontalMenu = () => { href='https://pixinvent.ticksy.com' icon={} > - {navigationLabels.raiseSupport} + {t('raiseSupport')} } @@ -376,13 +374,13 @@ const HorizontalMenu = () => { icon={} href={`${process.env.NEXT_PUBLIC_DOCS_URL}`} > - {navigationLabels.documentation} + {t('documentation')} } icon={} > - {navigationLabels.itemWithBadge} + {t('itemWithBadge')} } @@ -390,16 +388,16 @@ const HorizontalMenu = () => { target='_blank' suffix={} > - {navigationLabels.externalLink} + {t('externalLink')} - }> - {navigationLabels.menuLevel2} - - {navigationLabels.menuLevel3} - {navigationLabels.menuLevel3} + }> + {t('menuLevel2')} + + {t('menuLevel3')} + {t('menuLevel3')} - {navigationLabels.disabledMenu} + {t('disabledMenu')} {/* { - // Hooks + const t = useTranslations('navigation') const theme = useTheme(); const verticalNavOptions = useVerticalNav(); @@ -94,7 +92,7 @@ const VerticalMenu = ({ scrollMenu }: Props) => { href={`/dashboard`} icon={} > - {navigationLabels.dashboards} + {t('dashboards')} diff --git a/src/i18n/request.ts b/src/i18n/request.ts new file mode 100644 index 0000000..e17339a --- /dev/null +++ b/src/i18n/request.ts @@ -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 + } +}) diff --git a/src/views/Login.tsx b/src/views/Login.tsx index 39bc946..c11ffae 100644 --- a/src/views/Login.tsx +++ b/src/views/Login.tsx @@ -1,11 +1,12 @@ 'use client' // React Imports -import { useState } from 'react' +import { useMemo, useState } from 'react' // Next Imports import Link from 'next/link' import { useRouter, useSearchParams } from 'next/navigation' +import { useTranslations } from 'next-intl' // MUI Imports import useMediaQuery from '@mui/material/useMediaQuery' @@ -20,7 +21,6 @@ import { Controller, useForm } from 'react-hook-form' import { valibotResolver } from '@hookform/resolvers/valibot' import { object, minLength, maxLength, string, pipe, nonEmpty, custom } from 'valibot' import type { SubmitHandler } from 'react-hook-form' -import type { InferInput } from 'valibot' import classnames from 'classnames' import { OTPInput } from 'input-otp' @@ -76,22 +76,26 @@ type ErrorType = { message: string } -type PhoneFormData = InferInput - -const phoneSchema = object({ - phone_number: pipe( - string(), - nonEmpty('Phone number is required'), - minLength(10, 'Phone number must be at least 10 digits'), - maxLength(15, 'Phone number must be at most 15 digits'), - custom( - (input) => /^[0-9]+$/.test(input), - 'Phone number must contain only digits' - ) - ) -}) +type PhoneFormData = { + phone_number: string +} const Login = ({ mode }: { mode: SystemMode }) => { + const t = useTranslations('login') + + const phoneSchema = useMemo( + () => + object({ + phone_number: pipe( + string(), + nonEmpty(String(t('validation.phoneRequired'))), + minLength(10, String(t('validation.phoneMinLength'))), + maxLength(15, String(t('validation.phoneMaxLength'))), + custom((input) => /^[0-9]+$/.test(String(input)), String(t('validation.phoneDigitsOnly'))) + ) + }), + [t] + ) // States const [step, setStep] = useState<'phone' | 'otp'>('phone') const [otp, setOtp] = useState('') @@ -122,7 +126,7 @@ const Login = ({ mode }: { mode: SystemMode }) => { getValues, formState: { errors } } = useForm({ - resolver: valibotResolver(phoneSchema), + resolver: valibotResolver(phoneSchema) as any, defaultValues: { phone_number: '' } @@ -145,7 +149,7 @@ const Login = ({ mode }: { mode: SystemMode }) => { setTempToken(token) setStep('otp') } catch (error: any) { - setErrorState({ message: error.message || 'Failed to send OTP' }) + setErrorState({ message: error.message || t('errors.sendOtpFailed') }) } finally { setIsLoading(false) } @@ -153,7 +157,7 @@ const Login = ({ mode }: { mode: SystemMode }) => { const onOtpSubmit = async () => { if (otp.length !== 6) { - setErrorState({ message: 'Please enter the complete 6-digit OTP code' }) + setErrorState({ message: t('errors.incompleteOtp') }) return } @@ -168,7 +172,7 @@ const Login = ({ mode }: { mode: SystemMode }) => { const redirectURL = searchParams.get('redirectTo') ?? '/' router.replace(redirectURL) } catch (error: any) { - setErrorState({ message: error.message || 'OTP verification failed' }) + setErrorState({ message: error.message || t('errors.otpVerificationFailed') }) } finally { setIsLoading(false) } @@ -199,11 +203,9 @@ const Login = ({ mode }: { mode: SystemMode }) => {
- {`Welcome to ${themeConfig.templateName}! 👋🏻`} + {t('welcome', { templateName: themeConfig.templateName })} - {step === 'phone' - ? 'Please enter your phone number to receive OTP' - : 'Please enter the OTP code sent to your phone'} + {step === 'phone' ? t('phoneStep') : t('otpStep')}
@@ -231,8 +233,8 @@ const Login = ({ mode }: { mode: SystemMode }) => { autoFocus fullWidth type='tel' - label='Phone Number' - placeholder='Enter your phone number' + label={t('phoneNumber')} + placeholder={t('placeholderPhone')} onChange={e => { field.onChange(e.target.value.replace(/\D/g, '')) // Only allow digits errorState !== null && setErrorState(null) @@ -245,12 +247,12 @@ const Login = ({ mode }: { mode: SystemMode }) => { )} />
- New on our platform? + {t('newUser')} - Create an account + {t('createAccount')}
@@ -258,7 +260,7 @@ const Login = ({ mode }: { mode: SystemMode }) => {
- OTP sent to {getValues('phone_number')} + {t('otpSent', { phone: getValues('phone_number') })} {
)} diff --git a/src/views/dashboards/farm/AnomalyDetectionCard.tsx b/src/views/dashboards/farm/AnomalyDetectionCard.tsx index 396526b..f1ebe10 100644 --- a/src/views/dashboards/farm/AnomalyDetectionCard.tsx +++ b/src/views/dashboards/farm/AnomalyDetectionCard.tsx @@ -1,5 +1,8 @@ 'use client' +// React Imports +import { useTranslations } from 'next-intl' + // MUI Imports import Card from '@mui/material/Card' import CardHeader from '@mui/material/CardHeader' @@ -23,13 +26,14 @@ interface AnomalyDetectionCardProps { } const AnomalyDetectionCard = ({ data }: AnomalyDetectionCardProps) => { + const t = useTranslations('farmDashboard') const anomalies = (data?.anomalies as AnomalyItem[] | undefined) ?? [] return ( } - title='Anomaly Detection' + title={t('cards.anomalyDetectionCard')} subheader='Out of range values' action={} sx={{ '& .MuiCardHeader-avatar': { mr: 3 } }} diff --git a/src/views/dashboards/farm/EconomicOverview.tsx b/src/views/dashboards/farm/EconomicOverview.tsx index 9e19a20..a4233f1 100644 --- a/src/views/dashboards/farm/EconomicOverview.tsx +++ b/src/views/dashboards/farm/EconomicOverview.tsx @@ -2,6 +2,7 @@ // Next Imports import dynamic from 'next/dynamic' +import { useTranslations } from 'next-intl' // MUI Imports import Card from '@mui/material/Card' @@ -35,6 +36,7 @@ interface EconomicOverviewProps { } const EconomicOverview = ({ data }: EconomicOverviewProps) => { + const t = useTranslations('farmDashboard') const economicData = (data?.economicData as EconomicItem[] | undefined) ?? [] const chartSeries = (data?.chartSeries as Array<{ name: string; data: number[] }>) ?? [] const chartCategories = (data?.chartCategories as string[]) ?? ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'] @@ -74,7 +76,7 @@ const EconomicOverview = ({ data }: EconomicOverviewProps) => { return ( } /> diff --git a/src/views/dashboards/farm/FarmAlertsTimeline.tsx b/src/views/dashboards/farm/FarmAlertsTimeline.tsx index 3c4ef45..abdf67b 100644 --- a/src/views/dashboards/farm/FarmAlertsTimeline.tsx +++ b/src/views/dashboards/farm/FarmAlertsTimeline.tsx @@ -1,5 +1,8 @@ 'use client' +// React Imports +import { useTranslations } from 'next-intl' + // MUI Imports import Card from '@mui/material/Card' import CardHeader from '@mui/material/CardHeader' @@ -39,13 +42,14 @@ interface FarmAlertsTimelineProps { } const FarmAlertsTimeline = ({ data }: FarmAlertsTimelineProps) => { + const t = useTranslations('farmDashboard') const alerts = (data?.alerts as AlertItem[] | undefined) ?? [] return ( } - title='AI Alerts' + title={t('cards.farmAlertsTimeline')} titleTypographyProps={{ variant: 'h5' }} subheader='Explainable recommendations' action={} diff --git a/src/views/dashboards/farm/FarmAlertsTracker.tsx b/src/views/dashboards/farm/FarmAlertsTracker.tsx index bf7139e..5267647 100644 --- a/src/views/dashboards/farm/FarmAlertsTracker.tsx +++ b/src/views/dashboards/farm/FarmAlertsTracker.tsx @@ -2,6 +2,7 @@ // Next Imports import dynamic from 'next/dynamic' +import { useTranslations } from 'next-intl' // MUI Imports import Card from '@mui/material/Card' @@ -36,6 +37,7 @@ interface FarmAlertsTrackerProps { } const FarmAlertsTracker = ({ data }: FarmAlertsTrackerProps) => { + const t = useTranslations('farmDashboard') const alertStats = (data?.alertStats as AlertStatType[] | undefined) ?? [] const totalAlerts = (data?.totalAlerts as number | undefined) ?? 0 const radialBarValue = (data?.radialBarValue as number | undefined) ?? 30 @@ -94,7 +96,7 @@ const FarmAlertsTracker = ({ data }: FarmAlertsTrackerProps) => { return ( } /> diff --git a/src/views/dashboards/farm/FarmDashboardSettingsDrawer.tsx b/src/views/dashboards/farm/FarmDashboardSettingsDrawer.tsx index 77b4931..ef033ce 100644 --- a/src/views/dashboards/farm/FarmDashboardSettingsDrawer.tsx +++ b/src/views/dashboards/farm/FarmDashboardSettingsDrawer.tsx @@ -1,5 +1,8 @@ 'use client' +// React Imports +import { useTranslations } from 'next-intl' + // MUI Imports import Drawer from '@mui/material/Drawer' import Typography from '@mui/material/Typography' @@ -33,6 +36,7 @@ type FarmDashboardSettingsDrawerProps = { } const FarmDashboardSettingsDrawer = (props: FarmDashboardSettingsDrawerProps) => { + const t = useTranslations('farmDashboard') const { open, onClose, disabledCardIds, onToggleCard, cardLabels, rowLabels, rowCards } = props const disabledSet = new Set(disabledCardIds) @@ -55,9 +59,9 @@ const FarmDashboardSettingsDrawer = (props: FarmDashboardSettingsDrawerProps) => > - Dashboard Settings + {t('settings.title')} - Toggle cards to show or hide on the dashboard + {t('settings.toggleCards')} diff --git a/src/views/dashboards/farm/FarmDashboardSettingsDropdown.tsx b/src/views/dashboards/farm/FarmDashboardSettingsDropdown.tsx index e912ad2..ab34272 100644 --- a/src/views/dashboards/farm/FarmDashboardSettingsDropdown.tsx +++ b/src/views/dashboards/farm/FarmDashboardSettingsDropdown.tsx @@ -2,6 +2,7 @@ // React Imports import { useRef, useState } from 'react' +import { useTranslations } from 'next-intl' // MUI Imports import IconButton from '@mui/material/IconButton' @@ -43,6 +44,7 @@ type FarmDashboardSettingsDropdownProps = { } const FarmDashboardSettingsDropdown = (props: FarmDashboardSettingsDropdownProps) => { + const t = useTranslations('farmDashboard') const { disabledCardIds, onToggleCard, enableDragReorder, onToggleDragReorder, cardLabels, rowLabels, rowCards, saving } = props const { settings } = useSettings() const [open, setOpen] = useState(false) @@ -59,7 +61,7 @@ const FarmDashboardSettingsDropdown = (props: FarmDashboardSettingsDropdownProps @@ -82,9 +84,9 @@ const FarmDashboardSettingsDropdown = (props: FarmDashboardSettingsDropdownProps - Dashboard Settings + {t('settings.title')} - Toggle cards to show or hide + {t('settings.toggleCards')} @@ -97,7 +99,7 @@ const FarmDashboardSettingsDropdown = (props: FarmDashboardSettingsDropdownProps /> } label={ - Enable drag & reorder rows + {t('settings.enableDragReorder')} } sx={{ m: 0 }} /> diff --git a/src/views/dashboards/farm/FarmDashboardWrapper.tsx b/src/views/dashboards/farm/FarmDashboardWrapper.tsx index d99edca..9952893 100644 --- a/src/views/dashboards/farm/FarmDashboardWrapper.tsx +++ b/src/views/dashboards/farm/FarmDashboardWrapper.tsx @@ -2,7 +2,8 @@ // React Imports import type { RefObject } from 'react' -import { useEffect, useState, useCallback, useContext } from 'react' +import { useEffect, useMemo, useState, useCallback, useContext } from 'react' +import { useTranslations } from 'next-intl' // Context Imports import NavbarSlotContext from '@/contexts/navbarSlotContext' @@ -38,9 +39,7 @@ import EconomicOverview from '@views/dashboards/farm/EconomicOverview' import { ROW_IDS, ROW_CARDS, - ROW_LABELS, CARD_GRID_SIZE, - CARD_LABELS, DEFAULT_FARM_DASHBOARD_CONFIG, type RowId, type CardId, @@ -89,8 +88,55 @@ function mergeRowOrderAfterDrag( } const FarmDashboardWrapper = () => { + const t = useTranslations('farmDashboard') const { setSlotContent } = useContext(NavbarSlotContext) const [config, setConfig] = useState(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, + [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, + [t] + ) const [cardsData, setCardsData] = useState>>>({}) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) @@ -183,8 +229,8 @@ const FarmDashboardWrapper = () => { onToggleCard={handleToggleCard} enableDragReorder={config.enableDragReorder ?? true} onToggleDragReorder={handleToggleDragReorder} - cardLabels={CARD_LABELS} - rowLabels={ROW_LABELS} + cardLabels={cardLabels} + rowLabels={rowLabels} rowCards={ROW_CARDS} saving={saving} /> @@ -231,7 +277,7 @@ const FarmDashboardWrapper = () => { mt: 1, '&:active': { cursor: 'grabbing' } }} - aria-label={`Drag ${ROW_LABELS[rowId as RowId]}`} + aria-label={t('settings.dragRow', { row: rowLabels[rowId as RowId] })} > diff --git a/src/views/dashboards/farm/FarmWeatherCard.tsx b/src/views/dashboards/farm/FarmWeatherCard.tsx index 9cde823..651a268 100644 --- a/src/views/dashboards/farm/FarmWeatherCard.tsx +++ b/src/views/dashboards/farm/FarmWeatherCard.tsx @@ -2,6 +2,7 @@ // Next Imports import dynamic from 'next/dynamic' +import { useTranslations } from 'next-intl' // MUI Imports import Card from '@mui/material/Card' @@ -24,6 +25,7 @@ interface FarmWeatherCardProps { } const FarmWeatherCard = ({ data }: FarmWeatherCardProps) => { + const t = useTranslations('farmDashboard') const temperature = data?.temperature ?? 24 const condition = (data?.condition as string) ?? '' const humidity = data?.humidity ?? 45 @@ -86,7 +88,7 @@ const FarmWeatherCard = ({ data }: FarmWeatherCardProps) => { return ( } diff --git a/src/views/dashboards/farm/HarvestPredictionCard.tsx b/src/views/dashboards/farm/HarvestPredictionCard.tsx index a0326c3..ab61acb 100644 --- a/src/views/dashboards/farm/HarvestPredictionCard.tsx +++ b/src/views/dashboards/farm/HarvestPredictionCard.tsx @@ -1,5 +1,8 @@ 'use client' +// React Imports +import { useTranslations } from 'next-intl' + // MUI Imports import Card from '@mui/material/Card' import CardHeader from '@mui/material/CardHeader' @@ -16,6 +19,7 @@ interface HarvestPredictionCardProps { } const HarvestPredictionCard = ({ data }: HarvestPredictionCardProps) => { + const t = useTranslations('farmDashboard') const harvestDate = (data?.dateFormatted as string) ?? '' const daysUntil = (data?.daysUntil as number | undefined) ?? 0 const daysLeftFormatted = daysUntil > 0 ? `${daysUntil} days` : '' @@ -29,7 +33,7 @@ const HarvestPredictionCard = ({ data }: HarvestPredictionCardProps) => { } - title='Harvest Prediction' + title={t('cards.harvestPredictionCard')} subheader='AI Estimated Date' action={} /> diff --git a/src/views/dashboards/farm/NDVIHealthCard.tsx b/src/views/dashboards/farm/NDVIHealthCard.tsx index 70d3c84..ae30e2a 100644 --- a/src/views/dashboards/farm/NDVIHealthCard.tsx +++ b/src/views/dashboards/farm/NDVIHealthCard.tsx @@ -2,6 +2,7 @@ // Next Imports import dynamic from 'next/dynamic' +import { useTranslations } from 'next-intl' // MUI Imports import Card from '@mui/material/Card' @@ -26,6 +27,7 @@ interface NDVIHealthCardProps { } const NDVIHealthCard = ({ data }: NDVIHealthCardProps) => { + const t = useTranslations('farmDashboard') const ndviIndex = (data?.ndviIndex as number | undefined) ?? 0 const healthData = (data?.healthData as Array<{ title: string; value: string; color: string; icon: string }>) ?? [] @@ -85,7 +87,7 @@ const NDVIHealthCard = ({ data }: NDVIHealthCardProps) => { } - title='NDVI Health' + title={t('cards.ndviHealthCard')} subheader='Vegetation Index' sx={{ '& .MuiCardHeader-avatar': { mr: 3 } }} /> diff --git a/src/views/dashboards/farm/RecommendationsList.tsx b/src/views/dashboards/farm/RecommendationsList.tsx index 70dd431..fa7d509 100644 --- a/src/views/dashboards/farm/RecommendationsList.tsx +++ b/src/views/dashboards/farm/RecommendationsList.tsx @@ -1,5 +1,8 @@ 'use client' +// React Imports +import { useTranslations } from 'next-intl' + // MUI Imports import Card from '@mui/material/Card' import CardHeader from '@mui/material/CardHeader' @@ -25,13 +28,14 @@ interface RecommendationsListProps { } const RecommendationsList = ({ data }: RecommendationsListProps) => { + const t = useTranslations('farmDashboard') const recommendations = (data?.recommendations as RecommendationType[] | undefined) ?? [] if (recommendations.length === 0) return null return ( } /> diff --git a/src/views/dashboards/farm/SensorComparisonChart.tsx b/src/views/dashboards/farm/SensorComparisonChart.tsx index 1e73ad7..42a0027 100644 --- a/src/views/dashboards/farm/SensorComparisonChart.tsx +++ b/src/views/dashboards/farm/SensorComparisonChart.tsx @@ -2,6 +2,7 @@ // Next Imports import dynamic from 'next/dynamic' +import { useTranslations } from 'next-intl' // MUI Imports import Card from '@mui/material/Card' @@ -21,6 +22,7 @@ interface SensorComparisonChartProps { } const SensorComparisonChart = ({ data }: SensorComparisonChartProps) => { + const t = useTranslations('farmDashboard') const series = (data?.series as Array<{ name: string; data: number[] }>) ?? [] const categories = (data?.categories as string[]) ?? ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] const currentValue = data?.currentValue ?? 48 @@ -75,7 +77,7 @@ const SensorComparisonChart = ({ data }: SensorComparisonChartProps) => { return ( diff --git a/src/views/dashboards/farm/SensorRadarChart.tsx b/src/views/dashboards/farm/SensorRadarChart.tsx index 35bb222..4826a1c 100644 --- a/src/views/dashboards/farm/SensorRadarChart.tsx +++ b/src/views/dashboards/farm/SensorRadarChart.tsx @@ -2,6 +2,7 @@ // Next Imports import dynamic from 'next/dynamic' +import { useTranslations } from 'next-intl' // MUI Imports import Card from '@mui/material/Card' @@ -23,6 +24,7 @@ interface SensorRadarChartProps { } const SensorRadarChart = ({ data }: SensorRadarChartProps) => { + const t = useTranslations('farmDashboard') const series = (data?.series as Array<{ name: string; data: number[] }>) ?? [] const labels = (data?.labels as string[]) ?? [] const theme = useTheme() @@ -70,7 +72,7 @@ const SensorRadarChart = ({ data }: SensorRadarChartProps) => { return ( } /> diff --git a/src/views/dashboards/farm/SensorValuesList.tsx b/src/views/dashboards/farm/SensorValuesList.tsx index f7b9e5f..36d0609 100644 --- a/src/views/dashboards/farm/SensorValuesList.tsx +++ b/src/views/dashboards/farm/SensorValuesList.tsx @@ -1,5 +1,8 @@ 'use client' +// React Imports +import { useTranslations } from 'next-intl' + // MUI Imports import Card from '@mui/material/Card' import CardHeader from '@mui/material/CardHeader' @@ -25,13 +28,14 @@ interface SensorValuesListProps { } const SensorValuesList = ({ data }: SensorValuesListProps) => { + const t = useTranslations('farmDashboard') const sensors = (data?.sensors as SensorDataType[] | undefined) ?? [] if (sensors.length === 0) return null return ( } /> diff --git a/src/views/dashboards/farm/SoilMoistureHeatmap.tsx b/src/views/dashboards/farm/SoilMoistureHeatmap.tsx index bde22af..80790d5 100644 --- a/src/views/dashboards/farm/SoilMoistureHeatmap.tsx +++ b/src/views/dashboards/farm/SoilMoistureHeatmap.tsx @@ -2,6 +2,7 @@ // Next Imports import dynamic from 'next/dynamic' +import { useTranslations } from 'next-intl' // MUI Imports import Card from '@mui/material/Card' @@ -30,6 +31,7 @@ interface SoilMoistureHeatmapProps { } const SoilMoistureHeatmap = ({ data }: SoilMoistureHeatmapProps) => { + const t = useTranslations('farmDashboard') const series = (data?.series as HeatmapSeries[]) ?? [] const theme = useTheme() if (series.length === 0) return null @@ -72,7 +74,7 @@ const SoilMoistureHeatmap = ({ data }: SoilMoistureHeatmapProps) => { return ( diff --git a/src/views/dashboards/farm/WaterNeedPrediction.tsx b/src/views/dashboards/farm/WaterNeedPrediction.tsx index 42ba317..3367c50 100644 --- a/src/views/dashboards/farm/WaterNeedPrediction.tsx +++ b/src/views/dashboards/farm/WaterNeedPrediction.tsx @@ -1,5 +1,8 @@ 'use client' +// React Imports +import { useTranslations } from 'next-intl' + // Next Imports import dynamic from 'next/dynamic' @@ -24,6 +27,7 @@ interface WaterNeedPredictionProps { } const WaterNeedPrediction = ({ data }: WaterNeedPredictionProps) => { + const t = useTranslations('farmDashboard') const series = (data?.series as Array<{ name: string; data: number[] }>) ?? [] const categories = (data?.categories as string[]) ?? ['Day 1', 'Day 2', 'Day 3', 'Day 4', 'Day 5', 'Day 6', 'Day 7'] const totalNext7Days = data?.totalNext7Days ?? 0 @@ -74,7 +78,7 @@ const WaterNeedPrediction = ({ data }: WaterNeedPredictionProps) => { return ( } /> diff --git a/src/views/dashboards/farm/YieldPredictionChart.tsx b/src/views/dashboards/farm/YieldPredictionChart.tsx index 6c23b59..7358b31 100644 --- a/src/views/dashboards/farm/YieldPredictionChart.tsx +++ b/src/views/dashboards/farm/YieldPredictionChart.tsx @@ -2,6 +2,7 @@ // Next Imports import dynamic from 'next/dynamic' +import { useTranslations } from 'next-intl' // MUI Imports import Card from '@mui/material/Card' @@ -34,6 +35,7 @@ interface YieldPredictionChartProps { } const YieldPredictionChart = ({ data }: YieldPredictionChartProps) => { + const t = useTranslations('farmDashboard') const series = (data?.series as Array<{ name: string; data: number[] }>) ?? [] const categories = (data?.categories as string[]) ?? @@ -77,7 +79,7 @@ const YieldPredictionChart = ({ data }: YieldPredictionChartProps) => { return ( } /> diff --git a/src/views/pages/account-settings/account/AccountDelete.tsx b/src/views/pages/account-settings/account/AccountDelete.tsx index 6233e3d..c9d7bc7 100644 --- a/src/views/pages/account-settings/account/AccountDelete.tsx +++ b/src/views/pages/account-settings/account/AccountDelete.tsx @@ -2,6 +2,7 @@ // React Imports import { useState } from 'react' +import { useTranslations } from 'next-intl' // MUI Imports import Card from '@mui/material/Card' @@ -20,7 +21,7 @@ import { useForm, Controller } from 'react-hook-form' import ConfirmationDialog from '@components/dialogs/confirmation-dialog' const AccountDelete = () => { - // States + const t = useTranslations('accountSettings') const [open, setOpen] = useState(false) // Hooks @@ -40,7 +41,7 @@ const AccountDelete = () => { return ( - +
@@ -49,13 +50,13 @@ const AccountDelete = () => { control={control} rules={{ required: true }} render={({ field }) => ( - } label='I confirm my account deactivation' /> + } label={t('confirmDeactivation')} /> )} /> - {errors.checkbox && Please confirm you want to delete account} + {errors.checkbox && {t('confirmDelete')}} diff --git a/src/views/pages/account-settings/account/AccountDetails.tsx b/src/views/pages/account-settings/account/AccountDetails.tsx index b9c5b0d..f520d83 100644 --- a/src/views/pages/account-settings/account/AccountDetails.tsx +++ b/src/views/pages/account-settings/account/AccountDetails.tsx @@ -2,6 +2,7 @@ // React Imports import { useState, useEffect } from 'react' +import { useTranslations } from 'next-intl' import type { ChangeEvent } from 'react' // MUI Imports @@ -36,7 +37,7 @@ const initialData: Data = { } const AccountDetails = () => { - // Auth + const t = useTranslations('accountSettings') const { user: authUser } = useAuth() // States @@ -98,7 +99,7 @@ const AccountDetails = () => { }) // Refresh auth user data - caller would need to update context; for now form stays as-is } catch (err: any) { - setError(err.message || 'خطا در ذخیره تغییرات') + setError(err.message || t('errorSave')) } finally { setSaving(false) } @@ -171,45 +172,45 @@ const AccountDetails = () => { handleFormChange('firstName', e.target.value)} /> handleFormChange('lastName', e.target.value)} /> handleFormChange('email', e.target.value)} /> handleFormChange('phoneNumber', e.target.value)} /> diff --git a/src/views/pages/account-settings/index.tsx b/src/views/pages/account-settings/index.tsx index 0735009..c959565 100644 --- a/src/views/pages/account-settings/index.tsx +++ b/src/views/pages/account-settings/index.tsx @@ -2,6 +2,7 @@ // React Imports import { useState } from 'react' +import { useTranslations } from 'next-intl' import type { SyntheticEvent, ReactElement } from 'react' // MUI Imports @@ -14,7 +15,7 @@ import TabPanel from '@mui/lab/TabPanel' import CustomTabList from '@core/components/mui/TabList' const AccountSettings = ({ tabContentList }: { tabContentList: { [key: string]: ReactElement } }) => { - // States + const t = useTranslations('accountSettings') const [activeTab, setActiveTab] = useState('account') const handleChange = (event: SyntheticEvent, value: string) => { @@ -26,8 +27,8 @@ const AccountSettings = ({ tabContentList }: { tabContentList: { [key: string]: - } iconPosition='start' value='account' /> - } iconPosition='start' value='sensor-hub' /> + } iconPosition='start' value='account' /> + } iconPosition='start' value='sensor-hub' /> {/* } iconPosition='start' value='security' /> { + const t = useTranslations('sensorHub') const [showAddForm, setShowAddForm] = useState(false) const { setSensorHub } = useSensorHub() @@ -49,10 +51,10 @@ const SensorHubTabContent = () => {
- انتخاب سنسور + {t('selectSensor')} - سنسور مورد نظر را انتخاب کنید یا سنسور جدید اضافه کنید + {t('selectSensorDescription')}
diff --git a/src/views/sensorHub/FormSensorHub.tsx b/src/views/sensorHub/FormSensorHub.tsx index b4d2e84..4a5797d 100644 --- a/src/views/sensorHub/FormSensorHub.tsx +++ b/src/views/sensorHub/FormSensorHub.tsx @@ -2,6 +2,7 @@ // React Imports import { useState } from 'react' +import { useTranslations } from 'next-intl' // MUI Imports import Grid from '@mui/material/Grid2' @@ -20,6 +21,7 @@ type FormSensorHubProps = { } const FormSensorHub = ({ onBack }: FormSensorHubProps) => { + const t = useTranslations('sensorHub') const [name, setName] = useState('') const [uuidSensor, setUuidSensor] = useState('') const [loading, setLoading] = useState(false) @@ -33,7 +35,7 @@ const FormSensorHub = ({ onBack }: FormSensorHubProps) => { await sensorHubService.addSensor({ name, uuid_sensor: uuidSensor }) onBack() } catch (err: unknown) { - const message = err && typeof err === 'object' && 'message' in err ? String((err as { message: string }).message) : 'خطا در ذخیره سنسور' + const message = err && typeof err === 'object' && 'message' in err ? String((err as { message: string }).message) : t('errorSave') setError(message) } finally { setLoading(false) @@ -50,7 +52,7 @@ const FormSensorHub = ({ onBack }: FormSensorHubProps) => { startIcon={} onClick={onBack} > - بازگشت + {t('back')} {/* افزودن سنسور جدید */}
@@ -66,8 +68,8 @@ const FormSensorHub = ({ onBack }: FormSensorHubProps) => { setName(e.target.value)} /> @@ -75,15 +77,15 @@ const FormSensorHub = ({ onBack }: FormSensorHubProps) => { setUuidSensor(e.target.value)} /> diff --git a/src/views/sensorHub/SensorHubTable.tsx b/src/views/sensorHub/SensorHubTable.tsx index 41adbe3..9bee843 100644 --- a/src/views/sensorHub/SensorHubTable.tsx +++ b/src/views/sensorHub/SensorHubTable.tsx @@ -1,7 +1,8 @@ 'use client' // React Imports -import { useState, useEffect } from 'react' +import { useMemo, useState, useEffect } from 'react' +import { useTranslations } from 'next-intl' // MUI Imports import Card from '@mui/material/Card' @@ -33,22 +34,26 @@ const formatToShamsi = (dateStr: string | null | undefined): string => { // Column Definitions const columnHelper = createColumnHelper() -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 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([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) @@ -61,7 +66,7 @@ const SensorHubTable = () => { const sensors = await sensorHubService.listSensors() setData(sensors) } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to load sensors') + setError(err instanceof Error ? err.message : t('errorLoad')) setData([]) } finally { setLoading(false) @@ -83,7 +88,7 @@ const SensorHubTable = () => { if (loading) { return ( - +
@@ -94,14 +99,14 @@ const SensorHubTable = () => { if (error) { return ( - + ) } return ( - +
diff --git a/src/views/sensorHub/TableModalSheet.tsx b/src/views/sensorHub/TableModalSheet.tsx index a45c75d..0169bcc 100644 --- a/src/views/sensorHub/TableModalSheet.tsx +++ b/src/views/sensorHub/TableModalSheet.tsx @@ -3,6 +3,7 @@ // React Imports import { useState } from 'react' import type { Theme } from '@mui/material/styles' +import { useTranslations } from 'next-intl' // Hook Imports import { useSensorHub } from '@/hooks/useSensorHub' @@ -35,12 +36,18 @@ const DialogContentWithTransition = ({ showAddForm, onShowAddForm, onBack, - onConfirm + onConfirm, + selectSensor, + selectSensorDescription, + addSensor }: { showAddForm: boolean onShowAddForm: () => void onBack: () => void onConfirm: (sensor: Sensor) => void + selectSensor: string + selectSensorDescription: string + addSensor: string }) => (
@@ -51,10 +58,10 @@ const DialogContentWithTransition = ({
- انتخاب سنسور + {selectSensor} - سنسور مورد نظر را انتخاب کنید یا سنسور جدید اضافه کنید + {selectSensorDescription}
@@ -77,12 +84,18 @@ const DrawerContentWithTransition = ({ showAddForm, onShowAddForm, onBack, - onConfirm + onConfirm, + selectSensor, + selectSensorDescription, + addSensor }: { showAddForm: boolean onShowAddForm: () => void onBack: () => void onConfirm: (sensor: Sensor) => void + selectSensor: string + selectSensorDescription: string + addSensor: string }) => (
@@ -93,10 +106,10 @@ const DrawerContentWithTransition = ({
- انتخاب سنسور + {selectSensor} - سنسور مورد نظر را انتخاب کنید یا سنسور جدید اضافه کنید + {selectSensorDescription}
@@ -116,10 +129,17 @@ const DrawerContentWithTransition = ({ ) const TableModalSheet = ({ open, onClose }: TableModalSheetProps) => { + const t = useTranslations('sensorHub') const [showAddForm, setShowAddForm] = useState(false) const isMobile = useMediaQuery((theme: Theme) => theme.breakpoints.down('sm')) const { setSensorHub } = useSensorHub() + const contentProps = { + selectSensor: t('selectSensor'), + selectSensorDescription: t('selectSensorDescription'), + addSensor: t('addSensor') + } + const handleBack = () => setShowAddForm(false) const handleConfirm = (sensor: Sensor) => { @@ -149,8 +169,8 @@ const TableModalSheet = ({ open, onClose }: TableModalSheetProps) => { }} >
- Sensor Data - + {t('sensorData')} +
@@ -160,6 +180,7 @@ const TableModalSheet = ({ open, onClose }: TableModalSheetProps) => { onShowAddForm={() => setShowAddForm(true)} onBack={handleBack} onConfirm={handleConfirm} + {...contentProps} />
@@ -185,6 +206,7 @@ const TableModalSheet = ({ open, onClose }: TableModalSheetProps) => { onShowAddForm={() => setShowAddForm(true)} onBack={handleBack} onConfirm={handleConfirm} + {...contentProps} />