'use client' import { useState, useCallback, useEffect, useMemo } 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' import CropZoningWeatherSection from './CropZoningWeatherSection' import { createGridFromPolygon } from './cropZoningUtils' import { cropZoningService, type Product, type ZoneInitialData, type ZoneDetailData, type ZoneMapData, type ZoneWaterNeedData, type ZoneSoilQualityData, type ZoneCultivationRiskData } from '@/libs/api/services/cropZoningService' import { CROP_COLORS, type CropType } from './cropZoningTypes' import type { LayerType } from './cropZoningTypes' import type { MapDrawGeoJSON } from './CropZoningMap' const MapComponent = dynamic(() => Promise.resolve(CropZoningMap), { ssr: false, loading: () => ( ) }) function isPolygon(geojson: MapDrawGeoJSON): geojson is MapDrawGeoJSON & { geometry: { type: 'Polygon'; coordinates: unknown[] } } { return !!geojson?.geometry && (geojson.geometry as { type: string }).type === 'Polygon' } export default function CropZoningWrapper() { const t = useTranslations('cropZoning') const [areaGeoJson, setAreaGeoJson] = useState(null) const [areaLoading, setAreaLoading] = useState(true) const [zonesData, setZonesData] = useState(null) const [zonesWaterNeed, setZonesWaterNeed] = useState(null) const [zonesSoilQuality, setZonesSoilQuality] = useState(null) const [zonesCultivationRisk, setZonesCultivationRisk] = useState(null) const [products, setProducts] = useState([]) const [productsLoading, setProductsLoading] = useState(true) const [zonesLoading, setZonesLoading] = useState(false) const [layerDataLoading, setLayerDataLoading] = useState(false) const [activeLayer, setActiveLayer] = useState('crops') const [selectedZone, setSelectedZone] = useState(null) const [panelOpen, setPanelOpen] = useState(false) const [zoneDetailLoading, setZoneDetailLoading] = useState(false) const [optimizationKey, setOptimizationKey] = useState(0) const productLabels = Object.fromEntries(products.map(p => [p.id, p.label])) useEffect(() => { cropZoningService .getProducts() .then(res => setProducts(res.products)) .catch(() => setProducts([])) .finally(() => setProductsLoading(false)) }, []) useEffect(() => { setAreaLoading(true) cropZoningService .getArea() .then(res => setAreaGeoJson(res.area as unknown as MapDrawGeoJSON)) .catch(() => setAreaGeoJson(null)) .finally(() => setAreaLoading(false)) }, []) const fetchZones = useCallback((geojson: MapDrawGeoJSON) => { if (!isPolygon(geojson)) { setZonesData(null) setZonesWaterNeed(null) setZonesSoilQuality(null) setZonesCultivationRisk(null) return } setZonesWaterNeed(null) setZonesSoilQuality(null) setZonesCultivationRisk(null) setZonesLoading(true) const grid = createGridFromPolygon(geojson as unknown as import('geojson').Feature) cropZoningService .getZonesInitial({ zones: grid }) .then(res => setZonesData(res.zones)) .catch(() => setZonesData(null)) .finally(() => setZonesLoading(false)) }, []) useEffect(() => { if (isPolygon(areaGeoJson)) { fetchZones(areaGeoJson) } else { setZonesData(null) setZonesWaterNeed(null) setZonesSoilQuality(null) setZonesCultivationRisk(null) } }, [areaGeoJson, optimizationKey, fetchZones]) const gridForLayers = isPolygon(areaGeoJson) ? createGridFromPolygon(areaGeoJson as unknown as import('geojson').Feature) : null useEffect(() => { if (!gridForLayers || zonesLoading) return if (activeLayer === 'waterNeed' && zonesWaterNeed === null) { setLayerDataLoading(true) cropZoningService .getZonesWaterNeed({ zones: gridForLayers }) .then(res => setZonesWaterNeed(res.zones)) .catch(() => setZonesWaterNeed(null)) .finally(() => setLayerDataLoading(false)) } else if (activeLayer === 'soilQuality' && zonesSoilQuality === null) { setLayerDataLoading(true) cropZoningService .getZonesSoilQuality({ zones: gridForLayers }) .then(res => setZonesSoilQuality(res.zones)) .catch(() => setZonesSoilQuality(null)) .finally(() => setLayerDataLoading(false)) } else if (activeLayer === 'cultivationRisk' && zonesCultivationRisk === null) { setLayerDataLoading(true) cropZoningService .getZonesCultivationRisk({ zones: gridForLayers }) .then(res => setZonesCultivationRisk(res.zones)) .catch(() => setZonesCultivationRisk(null)) .finally(() => setLayerDataLoading(false)) } }, [activeLayer, gridForLayers, zonesLoading, zonesWaterNeed, zonesSoilQuality, zonesCultivationRisk]) const mapZonesData = useMemo((): ZoneMapData[] | null => { const labels = Object.fromEntries(products.map(p => [p.id, p.label])) if (activeLayer === 'crops' && zonesData) { const isCultivable = (crop: string | null | undefined) => !!crop && crop !== 'uncultivable' && crop.toLowerCase() !== 'uncultivable' return zonesData.map(z => { const cultivable = isCultivable(z.crop) const cropLabel = cultivable ? (labels[z.crop!] ?? z.crop) : 'غیر قابل کشت' const tooltipContent = cultivable ? `
${cropLabel}
درصد تطابق: ${z.matchPercent ?? '-'}%
نیاز آب: ${z.waterNeed ?? '-'}
سود تخمینی: ${z.estimatedProfit ?? '-'}
` : `
غیر قابل کشت
این بخش برای کشت مناسب تشخیص داده نشده است.
` const color = cultivable ? (CROP_COLORS[z.crop as CropType] ?? '#94a3b8') : '#94a3b8' return { zoneId: z.zoneId, geometry: z.geometry, color, tooltipContent, cultivable, zoneInitialData: z } }) } if (activeLayer === 'waterNeed' && zonesWaterNeed) { const levelLabels = { low: 'کم', medium: 'متوسط', high: 'زیاد' } return zonesWaterNeed.map(z => ({ zoneId: z.zoneId, geometry: z.geometry, color: z.color, tooltipContent: `
نیاز آبی: ${levelLabels[z.level]}
${z.value ?? '-'}
`, cultivable: false })) } if (activeLayer === 'soilQuality' && zonesSoilQuality) { const levelLabels = { low: 'کم', medium: 'متوسط', high: 'زیاد' } return zonesSoilQuality.map(z => ({ zoneId: z.zoneId, geometry: z.geometry, color: z.color, tooltipContent: `
کیفیت خاک: ${levelLabels[z.level]}
امتیاز: ${z.score ?? '-'}
`, cultivable: false })) } if (activeLayer === 'cultivationRisk' && zonesCultivationRisk) { const levelLabels = { low: 'کم', medium: 'متوسط', high: 'زیاد' } return zonesCultivationRisk.map(z => ({ zoneId: z.zoneId, geometry: z.geometry, color: z.color, tooltipContent: `
ریسک کشت: ${levelLabels[z.level]}
`, cultivable: false })) } return null }, [activeLayer, zonesData, zonesWaterNeed, zonesSoilQuality, zonesCultivationRisk, products]) const handleAreaChange = useCallback((geojson: MapDrawGeoJSON) => { setAreaGeoJson(geojson) }, []) const handleZoneClick = useCallback((zone: ZoneInitialData) => { setZoneDetailLoading(true) setPanelOpen(true) setSelectedZone(null) cropZoningService .getZoneDetails(zone.zoneId) .then(details => setSelectedZone(details)) .catch(() => setSelectedZone(null)) .finally(() => setZoneDetailLoading(false)) }, []) const handleOptimize = useCallback(() => { setOptimizationKey(k => k + 1) }, []) return ( {areaGeoJson ? ( ) : ( )} {(areaLoading || zonesLoading || (activeLayer !== 'crops' && layerDataLoading)) && ( )} {areaGeoJson && ( )} setPanelOpen(false)} zone={selectedZone} products={products} loading={zoneDetailLoading} /> ) }