11import { useState , useCallback , useMemo , useEffect } from "react"
22import { MapContainer } from "./components/Map/MapContainer"
33import { useMapData } from "./hooks/useMapData"
4+ import {
5+ FreeSpotsFilter ,
6+ ZoneSelector ,
7+ type FreeSpotFilterValue ,
8+ } from "./components/Filters"
49import type { MapState } from "./types"
510import type { Zone } from "./types/api"
611import "./App.css"
@@ -11,33 +16,54 @@ function App() {
1116 zoom : 12 ,
1217 } )
1318
19+ const [ freeSpotFilter , setFreeSpotFilter ] =
20+ useState < FreeSpotFilterValue > ( "all" )
21+ const [ selectedZoneId , setSelectedZoneId ] = useState < number | null > ( null )
22+
1423 const { zones, loading, error, total, refetch } = useMapData ( {
1524 autoFetch : true ,
1625 } )
1726
27+ const filteredZones = useMemo ( ( ) => {
28+ return zones . filter ( ( zone ) => {
29+ const freeSpots =
30+ zone . occupied !== undefined ? zone . capacity - zone . occupied : 0
31+
32+ switch ( freeSpotFilter ) {
33+ case "one" :
34+ return freeSpots === 1
35+ case "twoOrMore" :
36+ return freeSpots >= 2
37+ case "all" :
38+ default :
39+ return true
40+ }
41+ } )
42+ } , [ zones , freeSpotFilter ] )
43+
1844 const totalFreeSpots = useMemo (
1945 ( ) =>
20- zones . reduce ( ( acc , zone ) => {
46+ filteredZones . reduce ( ( acc , zone ) => {
2147 const occupied = zone . occupied
2248 const capacity = zone . capacity
2349 if ( occupied !== undefined ) {
2450 return acc + ( capacity - occupied )
2551 }
2652 return acc
2753 } , 0 ) ,
28- [ zones ]
54+ [ filteredZones ]
2955 )
3056
3157 const totalCapacity = useMemo (
3258 ( ) =>
33- zones . reduce ( ( acc , zone ) => {
59+ filteredZones . reduce ( ( acc , zone ) => {
3460 const capacity = zone . capacity
3561 return acc + capacity
3662 } , 0 ) ,
37- [ zones ]
63+ [ filteredZones ]
3864 )
3965
40- const handleZoneClick = useCallback ( ( zone : Zone ) => {
66+ const focusOnZone = useCallback ( ( zone : Zone ) => {
4167 const points = zone . points
4268 if ( points && points . length > 0 ) {
4369 const centerLat =
@@ -46,12 +72,31 @@ function App() {
4672 points . reduce ( ( sum , p ) => sum + p . longitude , 0 ) / points . length
4773
4874 setMapState ( ( prev ) => ( {
49- ...prev ,
5075 center : [ centerLat , centerLng ] ,
76+ zoom : Math . max ( prev . zoom , 18 ) ,
5177 } ) )
5278 }
5379 } , [ ] )
5480
81+ const handleZoneClick = useCallback (
82+ ( zone : Zone ) => {
83+ focusOnZone ( zone )
84+ } ,
85+ [ focusOnZone ]
86+ )
87+
88+ const handleZoneSelect = useCallback (
89+ ( zone : Zone | null ) => {
90+ if ( zone ) {
91+ setSelectedZoneId ( zone . zone_id )
92+ focusOnZone ( zone )
93+ } else {
94+ setSelectedZoneId ( null )
95+ }
96+ } ,
97+ [ focusOnZone ]
98+ )
99+
55100 const handleMapStateChange = useCallback ( ( newState : MapState ) => {
56101 setMapState ( newState )
57102 } , [ ] )
@@ -70,19 +115,40 @@ function App() {
70115 < main className = "mx-auto" >
71116 < div className = "relative h-[calc(100vh)] w-full" >
72117 < MapContainer
73- zones = { zones }
118+ zones = { filteredZones }
74119 mapState = { mapState }
75120 onMapStateChange = { handleMapStateChange }
76121 onZoneClick = { handleZoneClick }
77122 className = "w-full h-full"
78123 />
79124
125+ < div className = "absolute top-2 left-16 right-2 sm:top-4 sm:left-16 sm:right-auto sm:max-w-md z-[1000] bg-white/90 backdrop-blur-sm rounded-lg shadow-lg border border-gray-200" >
126+ < div className = "p-3 sm:p-4" >
127+ < div className = "flex flex-col gap-3" >
128+ < div >
129+ < h3 className = "text-xs sm:text-sm font-semibold text-gray-700 mb-2" >
130+ Фильтр по свободным местам
131+ </ h3 >
132+ < FreeSpotsFilter
133+ value = { freeSpotFilter }
134+ onChange = { setFreeSpotFilter }
135+ />
136+ </ div >
137+ < ZoneSelector
138+ zones = { zones }
139+ selectedZoneId = { selectedZoneId }
140+ onZoneSelect = { handleZoneSelect }
141+ />
142+ </ div >
143+ </ div >
144+ </ div >
145+
80146 < div className = "absolute bottom-2 left-2 right-2 sm:bottom-4 sm:left-4 sm:right-4 z-[1000] bg-white/70 backdrop-blur-sm rounded-lg shadow-lg border border-gray-200 map-overlay" >
81147 < div className = "flex items-center justify-between p-1 sm:p-2 min-w-0" >
82148 < div className = "flex items-center space-x-2 sm:space-x-3 min-w-0 flex-1 flex-wrap gap-1" >
83149 { total > 0 && (
84150 < span className = "text-xs sm:text-sm text-gray-700" >
85- Зон: { total }
151+ Зон: { filteredZones . length } / { total }
86152 </ span >
87153 ) }
88154 { totalCapacity > 0 && (
0 commit comments