MIF_E31221222/sigap-website/app/_components/map/map.tsx

175 lines
5.4 KiB
TypeScript

"use client"
import type React from "react"
import { useState, useCallback, useEffect, useRef } from "react"
import ReactMapGL, {
type ViewState,
NavigationControl,
ScaleControl,
type MapRef,
FullscreenControl,
GeolocateControl,
} from "react-map-gl/mapbox"
import { BASE_LATITUDE, BASE_LONGITUDE, BASE_ZOOM, MAP_STYLE } from "@/app/_utils/const/map"
import { Search } from "lucide-react"
import "mapbox-gl/dist/mapbox-gl.css"
import MapSidebar from "./controls/map-sidebar"
import SidebarToggle from "./controls/map-toggle"
import MapControls from "./controls/map-control"
import TimeControls from "./controls/time-control"
import SeverityIndicator from "./controls/severity-indicator"
import { useFullscreen } from "@/app/_hooks/use-fullscreen"
import MapFilterControl from "./controls/map-filter-control"
interface MapViewProps {
children?: React.ReactNode
initialViewState?: Partial<ViewState>
mapStyle?: string
className?: string
width?: string | number
height?: string | number
mapboxApiAccessToken?: string
onMoveEnd?: (viewState: ViewState) => void
customControls?: React.ReactNode
crimes?: Array<{
id: string
district_name: string
district_id?: string
number_of_crime?: number
level?: "low" | "medium" | "high" | "critical"
incidents: any[]
}>
selectedYear?: number | string
selectedMonth?: number | string
}
export default function MapView({
children,
initialViewState,
mapStyle = MAP_STYLE,
className = "w-full h-96",
width = "100%",
height = "100%",
mapboxApiAccessToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN,
onMoveEnd,
customControls,
crimes = [],
selectedYear,
selectedMonth,
}: MapViewProps) {
const [mapRef, setMapRef] = useState<MapRef | null>(null)
const [activeControl, setActiveControl] = useState<string>("crime-rate")
const [activeTime, setActiveTime] = useState<string>("today")
const [sidebarOpen, setSidebarOpen] = useState<boolean>(false)
const mapContainerRef = useRef<HTMLDivElement>(null)
const { isFullscreen } = useFullscreen(mapContainerRef)
const defaultViewState: Partial<ViewState> = {
longitude: BASE_LONGITUDE,
latitude: BASE_LATITUDE,
zoom: BASE_ZOOM,
bearing: 0,
pitch: 0,
...initialViewState,
}
const handleMapLoad = useCallback((event: any) => {
setMapRef(event.target)
}, [])
const handleMoveEnd = useCallback(
(event: any) => {
if (onMoveEnd) {
onMoveEnd(event.viewState)
}
},
[onMoveEnd],
)
const handleControlChange = (control: string) => {
setActiveControl(control)
// Here you would implement logic to change the map display based on the selected control
}
const handleTimeChange = (time: string) => {
setActiveTime(time)
// Here you would implement logic to change the time period of data shown
}
const toggleSidebar = () => {
setSidebarOpen(!sidebarOpen)
}
return (
<div ref={mapContainerRef} className={`relative ${className}`}>
{/* Custom controls - only show when in fullscreen mode */}
{isFullscreen && (
<>
{/* Sidebar */}
<MapSidebar
isOpen={sidebarOpen}
onToggle={toggleSidebar}
crimes={crimes}
selectedYear={selectedYear}
selectedMonth={selectedMonth}
/>
<SidebarToggle isOpen={sidebarOpen} onToggle={toggleSidebar} />
{/* Additional controls that should only appear in fullscreen */}
<div className="absolute top-2 right-2 z-10 flex items-center bg-white rounded-md shadow-md">
<input
type="text"
placeholder="Search location..."
className="px-3 py-2 rounded-l-md border-0 focus:outline-none w-64"
/>
<button className="bg-gray-100 p-2 rounded-r-md">
<Search size={20} />
</button>
</div>
<MapControls onControlChange={handleControlChange} activeControl={activeControl} />
<TimeControls onTimeChange={handleTimeChange} activeTime={activeTime} />
<SeverityIndicator />
{/* Make sure customControls is displayed in fullscreen mode */}
{customControls}
</>
)}
{/* Main content with left padding when sidebar is open */}
<div className={`relative h-full transition-all duration-300 ${isFullscreen && sidebarOpen ? "ml-80" : "ml-0"}`}>
<ReactMapGL
ref={(ref) => setMapRef(ref)}
mapStyle={mapStyle}
mapboxAccessToken={mapboxApiAccessToken}
initialViewState={defaultViewState}
onLoad={handleMapLoad}
onMoveEnd={handleMoveEnd}
interactiveLayerIds={["district-fill", "clusters", "unclustered-point"]}
attributionControl={false}
style={{ width, height }}
>
{children}
<NavigationControl position="right" />
<FullscreenControl
position="right"
containerId={mapContainerRef.current?.id}
/>
<ScaleControl position="bottom-left" />
{/* GeolocateControl only shown in fullscreen mode */}
{isFullscreen && (
<GeolocateControl position="right" />
)}
</ReactMapGL>
</div>
{/* Debug indicator - remove in production */}
<div className="absolute bottom-4 left-4 bg-black bg-opacity-70 text-white text-xs p-1 rounded z-50">
Fullscreen: {isFullscreen ? "Yes" : "No"}
</div>
</div>
)
}