2026-02-20 22:15:34 +03:30
|
|
|
'use client'
|
|
|
|
|
|
|
|
|
|
import { useState, useCallback } from 'react'
|
|
|
|
|
import dynamic from 'next/dynamic'
|
|
|
|
|
import { useTranslations } from 'next-intl'
|
|
|
|
|
import Box from '@mui/material/Box'
|
|
|
|
|
import CircularProgress from '@mui/material/CircularProgress'
|
|
|
|
|
import Button from '@mui/material/Button'
|
|
|
|
|
import CropZoningMap from './CropZoningMap'
|
|
|
|
|
import ZoneLegend from './ZoneLegend'
|
|
|
|
|
import LayerControl from './LayerControl'
|
|
|
|
|
import ZoneDetailPanel from './ZoneDetailPanel'
|
2026-02-20 23:08:44 +03:30
|
|
|
import CropZoningWeatherSection from './CropZoningWeatherSection'
|
2026-02-20 22:15:34 +03:30
|
|
|
import type { LayerType } from './cropZoningTypes'
|
|
|
|
|
import type { ZoneFeatureProperties } from './cropZoningTypes'
|
|
|
|
|
import type { MapDrawGeoJSON } from './CropZoningMap'
|
|
|
|
|
|
|
|
|
|
const MapComponent = dynamic(() => Promise.resolve(CropZoningMap), {
|
|
|
|
|
ssr: false,
|
|
|
|
|
loading: () => (
|
|
|
|
|
<Box className='flex items-center justify-center bs-full min-bs-[400px] rounded-xl bg-action-hover'>
|
|
|
|
|
<CircularProgress size={48} />
|
|
|
|
|
</Box>
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
export default function CropZoningWrapper() {
|
|
|
|
|
const t = useTranslations('cropZoning')
|
|
|
|
|
const [areaGeoJson, setAreaGeoJson] = useState<MapDrawGeoJSON>(null)
|
|
|
|
|
const [activeLayer, setActiveLayer] = useState<LayerType>('crops')
|
|
|
|
|
const [selectedZone, setSelectedZone] = useState<ZoneFeatureProperties | null>(null)
|
|
|
|
|
const [panelOpen, setPanelOpen] = useState(false)
|
|
|
|
|
const [optimizationKey, setOptimizationKey] = useState(0)
|
|
|
|
|
|
|
|
|
|
const handleAreaChange = useCallback((geojson: MapDrawGeoJSON) => {
|
|
|
|
|
setAreaGeoJson(geojson)
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
const handleZoneClick = useCallback((zone: ZoneFeatureProperties) => {
|
|
|
|
|
setSelectedZone(zone)
|
|
|
|
|
setPanelOpen(true)
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
const handleOptimize = useCallback(() => {
|
|
|
|
|
setOptimizationKey(k => k + 1)
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
return (
|
2026-02-20 23:08:44 +03:30
|
|
|
<Box className='flex flex-col gap-6 is-full'>
|
|
|
|
|
<Box className='relative min-bs-[400px] rounded-xl overflow-hidden' sx={{ height: 'min(60vh, 500px)' }}>
|
|
|
|
|
<Box className='absolute inset-0 z-0'>
|
|
|
|
|
<MapComponent
|
|
|
|
|
center={[35.6892, 51.389]}
|
|
|
|
|
zoom={13}
|
|
|
|
|
height='100%'
|
|
|
|
|
activeLayer={activeLayer}
|
|
|
|
|
onAreaChange={handleAreaChange}
|
|
|
|
|
onZoneClick={handleZoneClick}
|
|
|
|
|
optimizationKey={optimizationKey}
|
|
|
|
|
className='min-bs-[400px]'
|
|
|
|
|
/>
|
2026-02-20 22:15:34 +03:30
|
|
|
</Box>
|
|
|
|
|
|
|
|
|
|
<LayerControl activeLayer={activeLayer} onLayerChange={setActiveLayer} />
|
|
|
|
|
|
|
|
|
|
{areaGeoJson && (
|
|
|
|
|
<>
|
|
|
|
|
<ZoneLegend />
|
|
|
|
|
<Box className='absolute top-16 end-4 z-[1000]'>
|
|
|
|
|
<Button
|
|
|
|
|
variant='contained'
|
|
|
|
|
color='primary'
|
|
|
|
|
size='medium'
|
|
|
|
|
startIcon={<i className='tabler-refresh text-xl' />}
|
|
|
|
|
onClick={handleOptimize}
|
|
|
|
|
className='rounded-xl shadow-lg'
|
|
|
|
|
>
|
|
|
|
|
{t('optimizeAgain')}
|
|
|
|
|
</Button>
|
|
|
|
|
</Box>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<ZoneDetailPanel
|
|
|
|
|
open={panelOpen}
|
|
|
|
|
onClose={() => setPanelOpen(false)}
|
|
|
|
|
zone={selectedZone}
|
|
|
|
|
/>
|
2026-02-20 23:08:44 +03:30
|
|
|
</Box>
|
|
|
|
|
|
|
|
|
|
<CropZoningWeatherSection />
|
2026-02-20 22:15:34 +03:30
|
|
|
</Box>
|
|
|
|
|
)
|
|
|
|
|
}
|