Remove deprecated dashboard pages and update vertical menu links for streamlined navigation
- Deleted unused pages for crop zoning, farm AI assistant, fertilization recommendation, irrigation recommendation, pest detection, plant simulator, soil data, and water data. - Updated the vertical menu to reflect the removal of these pages, ensuring a cleaner and more efficient user experience.
This commit is contained in:
@@ -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 (
|
||||
<Box position='relative'>
|
||||
<Grid container spacing={6}>
|
||||
<Grid size={12} container spacing={6} sx={{ display: 'flex', alignItems: 'flex-start' }}>
|
||||
{SOIL_CARD_IDS.map(cardId => {
|
||||
const size = CARD_GRID_SIZE[cardId]
|
||||
const Component = CARD_COMPONENTS[cardId]
|
||||
if (!Component) return null
|
||||
return (
|
||||
<Grid key={cardId} size={size} sx={cardRowSx}>
|
||||
<Component data={cardsData?.[cardId]} />
|
||||
</Grid>
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
{SOIL_ROWS.map((rowCards, rowIndex) => {
|
||||
const sizePerCard = 12 / rowCards.length
|
||||
return (
|
||||
<Grid
|
||||
key={rowIndex}
|
||||
size={12}
|
||||
container
|
||||
spacing={6}
|
||||
sx={{ display: 'flex', alignItems: 'stretch' }}
|
||||
>
|
||||
{rowCards.map(cardId => {
|
||||
const Component = CARD_COMPONENTS[cardId]
|
||||
if (!Component) return null
|
||||
return (
|
||||
<Grid key={cardId} size={sizePerCard} sx={cardRowSx}>
|
||||
<Component data={cardsData?.[cardId]} />
|
||||
</Grid>
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
</Box>
|
||||
)
|
||||
|
||||
@@ -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 (
|
||||
<Box position='relative'>
|
||||
<Grid container spacing={6}>
|
||||
<Grid size={12} container spacing={6} sx={{ display: 'flex', alignItems: 'flex-start' }}>
|
||||
{WATER_CARD_IDS.map(cardId => {
|
||||
const size = CARD_GRID_SIZE[cardId]
|
||||
const Component = CARD_COMPONENTS[cardId]
|
||||
if (!Component) return null
|
||||
return (
|
||||
<Grid key={cardId} size={size} sx={cardRowSx}>
|
||||
<Component data={cardsData?.[cardId]} />
|
||||
</Grid>
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
{WATER_ROWS.map((rowCards, rowIndex) => {
|
||||
const sizePerCard = 12 / rowCards.length
|
||||
return (
|
||||
<Grid
|
||||
key={rowIndex}
|
||||
size={12}
|
||||
container
|
||||
spacing={6}
|
||||
sx={{ display: 'flex', alignItems: 'stretch' }}
|
||||
>
|
||||
{rowCards.map(cardId => {
|
||||
const Component = CARD_COMPONENTS[cardId]
|
||||
if (!Component) return null
|
||||
return (
|
||||
<Grid key={cardId} size={sizePerCard} sx={cardRowSx}>
|
||||
<Component data={cardsData?.[cardId]} />
|
||||
</Grid>
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
</Box>
|
||||
)
|
||||
|
||||
@@ -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<HTMLDivElement>(null)
|
||||
const mapInstanceRef = useRef<L.Map | null>(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<Polygon>).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
|
||||
|
||||
@@ -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 (
|
||||
<Box className='pis-0 pie-0'>
|
||||
<Typography variant='h6' className='mbe-4 font-semibold'>
|
||||
{t('title')}
|
||||
</Typography>
|
||||
<Grid container spacing={3}>
|
||||
<Grid size={{ xs: 12, md: 6, lg: 4 }}>
|
||||
<FarmWeatherCard data={weatherData} />
|
||||
{/* Row 1: Weather + 3 KPI cards — equal width (3 each on md+) */}
|
||||
<Grid
|
||||
size={12}
|
||||
container
|
||||
spacing={3}
|
||||
sx={{ display: 'flex', alignItems: 'stretch' }}
|
||||
>
|
||||
<Grid size={{ xs: 12, md: 3 }} sx={cardRowSx}>
|
||||
<FarmWeatherCard data={weatherData} />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, sm: 4, md: 3 }} sx={cardRowSx}>
|
||||
<Card sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||
<CardContent className='flex flex-1 flex-col items-center justify-center gap-2'>
|
||||
<i className='tabler-temperature text-3xl text-error' />
|
||||
<Typography variant='body2' color='text.secondary'>
|
||||
{t('temperature')}
|
||||
</Typography>
|
||||
<Typography variant='h5'>
|
||||
{temp}
|
||||
{weatherData.unit ?? '°C'}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, sm: 4, md: 3 }} sx={cardRowSx}>
|
||||
<Card sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||
<CardContent className='flex flex-1 flex-col items-center justify-center gap-2'>
|
||||
<i className='tabler-droplet text-3xl text-info' />
|
||||
<Typography variant='body2' color='text.secondary'>
|
||||
{t('humidity')}
|
||||
</Typography>
|
||||
<Typography variant='h5'>{humidity}%</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, sm: 4, md: 3 }} sx={cardRowSx}>
|
||||
<Card sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||
<CardContent className='flex flex-1 flex-col items-center justify-center gap-2'>
|
||||
<i className='tabler-wind text-3xl text-success' />
|
||||
<Typography variant='body2' color='text.secondary'>
|
||||
{t('windSpeed')}
|
||||
</Typography>
|
||||
<Typography variant='h5'>
|
||||
{windSpeed} {(weatherData.windUnit as string) ?? 'km/h'}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 2 }}>
|
||||
<Card>
|
||||
<CardContent className='flex flex-col items-center gap-2'>
|
||||
<i className='tabler-temperature text-3xl text-error' />
|
||||
<Typography variant='body2' color='text.secondary'>
|
||||
{t('temperature')}
|
||||
</Typography>
|
||||
<Typography variant='h5'>
|
||||
{temp}
|
||||
{weatherData.unit ?? '°C'}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 2 }}>
|
||||
<Card>
|
||||
<CardContent className='flex flex-col items-center gap-2'>
|
||||
<i className='tabler-droplet text-3xl text-info' />
|
||||
<Typography variant='body2' color='text.secondary'>
|
||||
{t('humidity')}
|
||||
</Typography>
|
||||
<Typography variant='h5'>{humidity}%</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 2 }}>
|
||||
<Card>
|
||||
<CardContent className='flex flex-col items-center gap-2'>
|
||||
<i className='tabler-wind text-3xl text-success' />
|
||||
<Typography variant='body2' color='text.secondary'>
|
||||
{t('windSpeed')}
|
||||
</Typography>
|
||||
<Typography variant='h5'>
|
||||
{windSpeed} {(weatherData.windUnit as string) ?? 'km/h'}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<Card>
|
||||
{/* Row 2: Forecast chart — full width */}
|
||||
<Grid size={12} sx={cardRowSx}>
|
||||
<Card sx={{ height: '100%', display: 'flex', flexDirection: 'column', minHeight: 280 }}>
|
||||
<CardHeader
|
||||
title={t('forecastChart')}
|
||||
subheader={t('forecastSubheader')}
|
||||
/>
|
||||
<CardContent>
|
||||
<CardContent sx={{ flex: 1, minHeight: 0 }}>
|
||||
<AppReactApexCharts
|
||||
type='line'
|
||||
height={260}
|
||||
|
||||
@@ -11,6 +11,7 @@ import ZoneLegend from './ZoneLegend'
|
||||
import LayerControl from './LayerControl'
|
||||
import ZoneDetailPanel from './ZoneDetailPanel'
|
||||
import CropZoningWeatherSection from './CropZoningWeatherSection'
|
||||
import { MOCK_AREA_GEOJSON } from './cropZoningMockData'
|
||||
import type { LayerType } from './cropZoningTypes'
|
||||
import type { ZoneFeatureProperties } from './cropZoningTypes'
|
||||
import type { MapDrawGeoJSON } from './CropZoningMap'
|
||||
@@ -26,7 +27,7 @@ const MapComponent = dynamic(() => Promise.resolve(CropZoningMap), {
|
||||
|
||||
export default function CropZoningWrapper() {
|
||||
const t = useTranslations('cropZoning')
|
||||
const [areaGeoJson, setAreaGeoJson] = useState<MapDrawGeoJSON>(null)
|
||||
const [areaGeoJson, setAreaGeoJson] = useState<MapDrawGeoJSON>(MOCK_AREA_GEOJSON)
|
||||
const [activeLayer, setActiveLayer] = useState<LayerType>('crops')
|
||||
const [selectedZone, setSelectedZone] = useState<ZoneFeatureProperties | null>(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
|
||||
/>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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 <Line data={data} options={chartOptions} />
|
||||
return (
|
||||
<Box
|
||||
className='is-full'
|
||||
sx={{ minHeight: { xs: 380, sm: 340, md: 320 }, height: { xs: 380, sm: 340, md: 320 } }}
|
||||
>
|
||||
<Line data={data} options={chartOptions} />
|
||||
</Box>
|
||||
)
|
||||
})
|
||||
|
||||
// ─── Main Component ───────────────────────────────────────────────────────────
|
||||
@@ -715,12 +723,12 @@ export default function PlantSimulator() {
|
||||
]
|
||||
|
||||
return (
|
||||
<Box className='flex flex-col gap-6 is-full'>
|
||||
<Box className='flex flex-col gap-6 min-is-0 is-full'>
|
||||
<Typography variant='h4' className='text-center font-bold'>
|
||||
🌱 {t('title')}
|
||||
</Typography>
|
||||
|
||||
<Grid container spacing={6} className='max-w-6xl mx-auto'>
|
||||
<Grid container spacing={6} className='min-is-0 is-full'>
|
||||
{/* ── Left: Plant visualization ── */}
|
||||
<Grid size={{ xs: 12, lg: 4 }} className='flex flex-col items-center gap-4'>
|
||||
<Card className='is-full flex flex-col items-center p-6'>
|
||||
|
||||
Reference in New Issue
Block a user