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} />