diff --git a/src/app/(dashboard)/(private)/dashboard/crop-zoning/page.tsx b/src/app/(dashboard)/(private)/crop-zoning/page.tsx
similarity index 100%
rename from src/app/(dashboard)/(private)/dashboard/crop-zoning/page.tsx
rename to src/app/(dashboard)/(private)/crop-zoning/page.tsx
diff --git a/src/app/(dashboard)/(private)/dashboard/farm-ai-assistant/page.tsx b/src/app/(dashboard)/(private)/farm-ai-assistant/page.tsx
similarity index 100%
rename from src/app/(dashboard)/(private)/dashboard/farm-ai-assistant/page.tsx
rename to src/app/(dashboard)/(private)/farm-ai-assistant/page.tsx
diff --git a/src/app/(dashboard)/(private)/dashboard/fertilization-recommendation/page.tsx b/src/app/(dashboard)/(private)/fertilization-recommendation/page.tsx
similarity index 100%
rename from src/app/(dashboard)/(private)/dashboard/fertilization-recommendation/page.tsx
rename to src/app/(dashboard)/(private)/fertilization-recommendation/page.tsx
diff --git a/src/app/(dashboard)/(private)/dashboard/irrigation-recommendation/page.tsx b/src/app/(dashboard)/(private)/irrigation-recommendation/page.tsx
similarity index 100%
rename from src/app/(dashboard)/(private)/dashboard/irrigation-recommendation/page.tsx
rename to src/app/(dashboard)/(private)/irrigation-recommendation/page.tsx
diff --git a/src/app/(dashboard)/(private)/dashboard/pest-detection/page.tsx b/src/app/(dashboard)/(private)/pest-detection/page.tsx
similarity index 100%
rename from src/app/(dashboard)/(private)/dashboard/pest-detection/page.tsx
rename to src/app/(dashboard)/(private)/pest-detection/page.tsx
diff --git a/src/app/(dashboard)/(private)/dashboard/plant-simulator/page.tsx b/src/app/(dashboard)/(private)/plant-simulator/page.tsx
similarity index 100%
rename from src/app/(dashboard)/(private)/dashboard/plant-simulator/page.tsx
rename to src/app/(dashboard)/(private)/plant-simulator/page.tsx
diff --git a/src/app/(dashboard)/(private)/dashboard/soil-data/page.tsx b/src/app/(dashboard)/(private)/soil-data/page.tsx
similarity index 100%
rename from src/app/(dashboard)/(private)/dashboard/soil-data/page.tsx
rename to src/app/(dashboard)/(private)/soil-data/page.tsx
diff --git a/src/app/(dashboard)/(private)/dashboard/water-data/page.tsx b/src/app/(dashboard)/(private)/water-data/page.tsx
similarity index 100%
rename from src/app/(dashboard)/(private)/dashboard/water-data/page.tsx
rename to src/app/(dashboard)/(private)/water-data/page.tsx
diff --git a/src/components/layout/vertical/VerticalMenu.tsx b/src/components/layout/vertical/VerticalMenu.tsx
index 306a705..9d690bc 100644
--- a/src/components/layout/vertical/VerticalMenu.tsx
+++ b/src/components/layout/vertical/VerticalMenu.tsx
@@ -95,36 +95,34 @@ const VerticalMenu = ({ scrollMenu }: Props) => {
{t('dashboards')}
-
- }>
+ }>
{t('waterData')}
- }>
+ }>
{t('soilData')}
- }>
+ }>
{t('cropZoning')}
-
-
- }>
+
+
+ }>
{t('plantSimulator')}
-
-
-
- }>
+
+
+ }>
{t('irrigationRecommendation')}
- }>
+ }>
{t('fertilizationRecommendation')}
-
-
- }>
+
+
+ }>
{t('farmAiAssistant')}
- }>
+ }>
{t('pestDetection')}
diff --git a/src/views/dashboards/farm/SoilDataDashboardWrapper.tsx b/src/views/dashboards/farm/SoilDataDashboardWrapper.tsx
index d3bb7a2..7f54d4c 100644
--- a/src/views/dashboards/farm/SoilDataDashboardWrapper.tsx
+++ b/src/views/dashboards/farm/SoilDataDashboardWrapper.tsx
@@ -18,19 +18,18 @@ import AnomalyDetectionCard from '@views/dashboards/farm/AnomalyDetectionCard'
// Service
import { farmDashboardService } from '@/libs/api/services/farmDashboardService'
import type { CardId } from '@views/dashboards/farm/farmDashboardConfig'
-import { CARD_GRID_SIZE } from '@views/dashboards/farm/farmDashboardConfig'
-const SOIL_CARD_IDS: CardId[] = [
- 'soilMoistureHeatmap',
- 'sensorValuesList',
- 'sensorRadarChart',
- 'sensorComparisonChart',
- 'anomalyDetectionCard'
+/** هر ردیف: آرایهٔ کارتها؛ در هر ردیف فضا مساوی بین گریدها تقسیم میشود (جمع = ۱۲) */
+const SOIL_ROWS: CardId[][] = [
+ ['soilMoistureHeatmap'],
+ ['sensorValuesList', 'sensorRadarChart'],
+ ['sensorComparisonChart', 'anomalyDetectionCard']
]
const cardRowSx = {
display: 'flex',
flexDirection: 'column',
+ minHeight: 380,
'& > *': { flex: 1, minHeight: 0 }
}
@@ -65,18 +64,28 @@ const SoilDataDashboardWrapper = () => {
return (
-
- {SOIL_CARD_IDS.map(cardId => {
- const size = CARD_GRID_SIZE[cardId]
- const Component = CARD_COMPONENTS[cardId]
- if (!Component) return null
- return (
-
-
-
- )
- })}
-
+ {SOIL_ROWS.map((rowCards, rowIndex) => {
+ const sizePerCard = 12 / rowCards.length
+ return (
+
+ {rowCards.map(cardId => {
+ const Component = CARD_COMPONENTS[cardId]
+ if (!Component) return null
+ return (
+
+
+
+ )
+ })}
+
+ )
+ })}
)
diff --git a/src/views/dashboards/farm/WaterDataDashboardWrapper.tsx b/src/views/dashboards/farm/WaterDataDashboardWrapper.tsx
index f953973..1c49aa9 100644
--- a/src/views/dashboards/farm/WaterDataDashboardWrapper.tsx
+++ b/src/views/dashboards/farm/WaterDataDashboardWrapper.tsx
@@ -17,18 +17,17 @@ import SensorValuesList from '@views/dashboards/farm/SensorValuesList'
// Service
import { farmDashboardService } from '@/libs/api/services/farmDashboardService'
import type { CardId } from '@views/dashboards/farm/farmDashboardConfig'
-import { CARD_GRID_SIZE } from '@views/dashboards/farm/farmDashboardConfig'
-const WATER_CARD_IDS: CardId[] = [
- 'farmWeatherCard',
- 'farmAlertsTimeline',
- 'waterNeedPrediction',
- 'sensorValuesList'
+/** هر ردیف: آرایهٔ کارتها؛ در هر ردیف فضا مساوی بین گریدها تقسیم میشود (جمع = ۱۲) */
+const WATER_ROWS: CardId[][] = [
+ ['farmWeatherCard', 'farmAlertsTimeline', 'waterNeedPrediction'],
+ ['sensorValuesList']
]
const cardRowSx = {
display: 'flex',
flexDirection: 'column',
+ minHeight: 380,
'& > *': { flex: 1, minHeight: 0 }
}
@@ -62,18 +61,28 @@ const WaterDataDashboardWrapper = () => {
return (
-
- {WATER_CARD_IDS.map(cardId => {
- const size = CARD_GRID_SIZE[cardId]
- const Component = CARD_COMPONENTS[cardId]
- if (!Component) return null
- return (
-
-
-
- )
- })}
-
+ {WATER_ROWS.map((rowCards, rowIndex) => {
+ const sizePerCard = 12 / rowCards.length
+ return (
+
+ {rowCards.map(cardId => {
+ const Component = CARD_COMPONENTS[cardId]
+ if (!Component) return null
+ return (
+
+
+
+ )
+ })}
+
+ )
+ })}
)
diff --git a/src/views/dashboards/farm/cropZoning/CropZoningMap.tsx b/src/views/dashboards/farm/cropZoning/CropZoningMap.tsx
index 313fb12..2969106 100644
--- a/src/views/dashboards/farm/cropZoning/CropZoningMap.tsx
+++ b/src/views/dashboards/farm/cropZoning/CropZoningMap.tsx
@@ -19,6 +19,10 @@ type CropZoningMapProps = {
onZoneClick?: (zone: ZoneFeatureProperties) => void
optimizationKey?: number
className?: string
+ /** منطقهٔ اولیه از دیتای ماک؛ وقتی مقدار دارد نقشه فقط نمایشی است و کاربر نمیتواند منطقه را تغییر دهد */
+ initialAreaGeoJson?: MapDrawGeoJSON | null
+ /** غیرفعال کردن رسم و ویرایش منطقه توسط کاربر */
+ readOnly?: boolean
}
export default function CropZoningMap({
@@ -29,7 +33,9 @@ export default function CropZoningMap({
onAreaChange,
onZoneClick,
optimizationKey = 0,
- className = ''
+ className = '',
+ initialAreaGeoJson = null,
+ readOnly = false
}: CropZoningMapProps) {
const mapRef = useRef(null)
const mapInstanceRef = useRef(null)
@@ -124,21 +130,22 @@ export default function CropZoningMap({
const drawnItems = L.featureGroup().addTo(map)
drawnItemsRef.current = drawnItems
- const drawControl = new L.Control.Draw({
- position: 'topright',
- draw: {
- polygon: { shapeOptions: { color: '#3388ff' } },
- rectangle: { shapeOptions: { color: '#3388ff' } },
- circle: false,
- circlemarker: false,
- marker: false,
- polyline: false
- },
- edit: { featureGroup: drawnItems, remove: true }
- })
-
- map.addControl(drawControl)
- drawControlRef.current = drawControl
+ if (!readOnly) {
+ const drawControl = new L.Control.Draw({
+ position: 'topright',
+ draw: {
+ polygon: { shapeOptions: { color: '#3388ff' } },
+ rectangle: { shapeOptions: { color: '#3388ff' } },
+ circle: false,
+ circlemarker: false,
+ marker: false,
+ polyline: false
+ },
+ edit: { featureGroup: drawnItems, remove: true }
+ })
+ map.addControl(drawControl)
+ drawControlRef.current = drawControl
+ }
const getGeoJsonFromDrawn = (): MapDrawGeoJSON => {
const geojson = drawnItems.toGeoJSON()
@@ -159,6 +166,12 @@ export default function CropZoningMap({
}
}
+ if (readOnly && initialAreaGeoJson && initialAreaGeoJson.geometry && (initialAreaGeoJson.geometry as { type: string }).type === 'Polygon') {
+ drawnItems.clearLayers()
+ L.geoJSON(initialAreaGeoJson as Feature).eachLayer((layer) => drawnItems.addLayer(layer))
+ emitAndRender()
+ }
+
const onCreated = (e: L.LeafletEvent) => {
const event = e as L.DrawEvents.Created
drawnItems.clearLayers()
@@ -175,17 +188,23 @@ export default function CropZoningMap({
}
}
- map.on(L.Draw.Event.CREATED, onCreated)
- map.on(L.Draw.Event.EDITED, onEdited)
- map.on(L.Draw.Event.DELETED, onDeleted)
+ if (!readOnly) {
+ map.on(L.Draw.Event.CREATED, onCreated)
+ map.on(L.Draw.Event.EDITED, onEdited)
+ map.on(L.Draw.Event.DELETED, onDeleted)
+ }
mapInstanceRef.current = map
cleanupFn = () => {
- map.off(L.Draw.Event.CREATED, onCreated)
- map.off(L.Draw.Event.EDITED, onEdited)
- map.off(L.Draw.Event.DELETED, onDeleted)
- map.removeControl(drawControl)
+ if (!readOnly) {
+ map.off(L.Draw.Event.CREATED, onCreated)
+ map.off(L.Draw.Event.EDITED, onEdited)
+ map.off(L.Draw.Event.DELETED, onDeleted)
+ if (drawControlRef.current) {
+ map.removeControl(drawControlRef.current)
+ }
+ }
if (zonesLayerRef.current) map.removeLayer(zonesLayerRef.current)
map.remove()
mapInstanceRef.current = null
diff --git a/src/views/dashboards/farm/cropZoning/CropZoningWeatherSection.tsx b/src/views/dashboards/farm/cropZoning/CropZoningWeatherSection.tsx
index 445496f..90d6d49 100644
--- a/src/views/dashboards/farm/cropZoning/CropZoningWeatherSection.tsx
+++ b/src/views/dashboards/farm/cropZoning/CropZoningWeatherSection.tsx
@@ -96,64 +96,77 @@ export default function CropZoningWeatherSection() {
const humidity = (weatherData.humidity as number) ?? 45
const windSpeed = (weatherData.windSpeed as number) ?? 12
+ const cardRowSx = {
+ display: 'flex',
+ flexDirection: 'column' as const,
+ minHeight: 200,
+ '& > *': { flex: 1, minHeight: 0 }
+ }
+
return (
{t('title')}
-
-
+ {/* Row 1: Weather + 3 KPI cards — equal width (3 each on md+) */}
+
+
+
+
+
+
+
+
+
+ {t('temperature')}
+
+
+ {temp}
+ {weatherData.unit ?? '°C'}
+
+
+
+
+
+
+
+
+
+ {t('humidity')}
+
+ {humidity}%
+
+
+
+
+
+
+
+
+ {t('windSpeed')}
+
+
+ {windSpeed} {(weatherData.windUnit as string) ?? 'km/h'}
+
+
+
+
-
-
-
-
-
- {t('temperature')}
-
-
- {temp}
- {weatherData.unit ?? '°C'}
-
-
-
-
-
-
-
-
-
-
- {t('humidity')}
-
- {humidity}%
-
-
-
-
-
-
-
-
-
- {t('windSpeed')}
-
-
- {windSpeed} {(weatherData.windUnit as string) ?? 'km/h'}
-
-
-
-
-
-
-
+ {/* Row 2: Forecast chart — full width */}
+
+
-
+
Promise.resolve(CropZoningMap), {
export default function CropZoningWrapper() {
const t = useTranslations('cropZoning')
- const [areaGeoJson, setAreaGeoJson] = useState(null)
+ const [areaGeoJson, setAreaGeoJson] = useState(MOCK_AREA_GEOJSON)
const [activeLayer, setActiveLayer] = useState('crops')
const [selectedZone, setSelectedZone] = useState(null)
const [panelOpen, setPanelOpen] = useState(false)
@@ -58,6 +59,8 @@ export default function CropZoningWrapper() {
onZoneClick={handleZoneClick}
optimizationKey={optimizationKey}
className='min-bs-[400px]'
+ initialAreaGeoJson={MOCK_AREA_GEOJSON}
+ readOnly
/>
diff --git a/src/views/dashboards/farm/cropZoning/cropZoningMockData.ts b/src/views/dashboards/farm/cropZoning/cropZoningMockData.ts
new file mode 100644
index 0000000..b0e8d43
--- /dev/null
+++ b/src/views/dashboards/farm/cropZoning/cropZoningMockData.ts
@@ -0,0 +1,23 @@
+import type { MapDrawGeoJSON } from './CropZoningMap'
+
+/**
+ * منطقهٔ ثابت برای نمایش روی نقشه (دیتای ماک).
+ * کاربر امکان تغییر یا رسم منطقهٔ جدید را ندارد.
+ * مختصات: یک چندضلعی حول تهران [35.6892, 51.389] — در GeoJSON به صورت [lng, lat].
+ */
+export const MOCK_AREA_GEOJSON: MapDrawGeoJSON = {
+ type: 'Feature',
+ properties: {},
+ geometry: {
+ type: 'Polygon',
+ coordinates: [
+ [
+ [51.38, 35.68],
+ [51.40, 35.68],
+ [51.40, 35.70],
+ [51.38, 35.70],
+ [51.38, 35.68]
+ ]
+ ]
+ }
+} as MapDrawGeoJSON
diff --git a/src/views/dashboards/farm/plantSimulator/PlantSimulator.tsx b/src/views/dashboards/farm/plantSimulator/PlantSimulator.tsx
index bdd9b0c..e2aee09 100644
--- a/src/views/dashboards/farm/plantSimulator/PlantSimulator.tsx
+++ b/src/views/dashboards/farm/plantSimulator/PlantSimulator.tsx
@@ -415,6 +415,7 @@ const GrowthChart = memo(function GrowthChart({
const chartOptions = {
responsive: true,
+ maintainAspectRatio: false,
animation: { duration: 0 },
plugins: {
legend: { labels: { font: { size: 11 } } },
@@ -510,7 +511,14 @@ const GrowthChart = memo(function GrowthChart({
]
}
- return
+ return (
+
+
+
+ )
})
// ─── Main Component ───────────────────────────────────────────────────────────
@@ -715,12 +723,12 @@ export default function PlantSimulator() {
]
return (
-
+
🌱 {t('title')}
-
+
{/* ── Left: Plant visualization ── */}