diff --git a/sigap-website/app/(pages)/(admin)/dashboard/crime-management/crime-overview/_queries/queries.ts b/sigap-website/app/(pages)/(admin)/dashboard/crime-management/crime-overview/_queries/queries.ts
index c6231f1..ef29ab1 100644
--- a/sigap-website/app/(pages)/(admin)/dashboard/crime-management/crime-overview/_queries/queries.ts
+++ b/sigap-website/app/(pages)/(admin)/dashboard/crime-management/crime-overview/_queries/queries.ts
@@ -1,2 +1,30 @@
-// Ensure no usage of `supabase/server.ts` here
-// If needed, replace with `supabase/client.ts` for client-side functionality
+import { useQuery } from '@tanstack/react-query';
+import {
+ getAvailableYears,
+ getCrimeByYearAndMonth,
+ getCrimes,
+} from '../action';
+
+export const useGetAvailableYears = () => {
+ return useQuery({
+ queryKey: ['available-years'],
+ queryFn: () => getAvailableYears(),
+ });
+};
+
+export const useGetCrimeByYearAndMonth = (
+ year: number,
+ month: number | 'all'
+) => {
+ return useQuery({
+ queryKey: ['crimes', year, month],
+ queryFn: () => getCrimeByYearAndMonth(year, month),
+ });
+};
+
+export const useGetCrimes = () => {
+ return useQuery({
+ queryKey: ['crimes'],
+ queryFn: () => getCrimes(),
+ });
+};
diff --git a/sigap-website/app/_components/map/controls/example.tsx b/sigap-website/app/_components/map/controls/example.tsx
new file mode 100644
index 0000000..e8a268f
--- /dev/null
+++ b/sigap-website/app/_components/map/controls/example.tsx
@@ -0,0 +1,66 @@
+import { Map } from "mapbox-gl";
+
+/* Idea from Stack Overflow https://stackoverflow.com/a/51683226 */
+export class CustomControl {
+ private _className: string;
+ private _title: string;
+ private _eventHandler: (event: MouseEvent) => void;
+ private _btn!: HTMLButtonElement;
+ private _container!: HTMLDivElement;
+ private _map?: Map;
+ private _root: any; // React root for rendering our component
+
+ constructor({
+ className = "",
+ title = "",
+ eventHandler = () => { }
+ }: {
+ className?: string;
+ title?: string;
+ eventHandler?: (event: MouseEvent) => void;
+ }) {
+ this._className = className;
+ this._title = title;
+ this._eventHandler = eventHandler;
+ }
+
+ onAdd(map: Map) {
+ this._map = map;
+ this._btn = document.createElement("button");
+ this._btn.className = "mapboxgl-ctrl-icon" + " " + this._className;
+ this._btn.type = "button";
+ this._btn.title = this._title;
+ this._btn.onclick = this._eventHandler;
+
+ // Apply pointer-events: auto; style dynamically
+ this._btn.style.pointerEvents = "auto";
+
+ // Dynamically append the style to the auto-generated className
+ const styleSheet = document.styleSheets[0];
+ styleSheet.insertRule(
+ `.${this._className} { pointer-events: auto; }`,
+ styleSheet.cssRules.length
+ );
+
+ this._container = document.createElement("div");
+ this._container.className = "mapboxgl-ctrl-group mapboxgl-ctrl";
+ this._container.appendChild(this._btn);
+
+ return this._container;
+ }
+
+ onRemove() {
+ if (this._container && this._container.parentNode) {
+ this._container.parentNode.removeChild(this._container);
+ }
+
+ // Defer unmounting React component to prevent race conditions
+ if (this._root) {
+ setTimeout(() => {
+ this._root.unmount();
+ });
+ }
+
+ this._map = undefined;
+ }
+}
\ No newline at end of file
diff --git a/sigap-website/app/_components/map/controls/map-control.tsx b/sigap-website/app/_components/map/controls/map-control.tsx
index 45e7147..4ba706a 100644
--- a/sigap-website/app/_components/map/controls/map-control.tsx
+++ b/sigap-website/app/_components/map/controls/map-control.tsx
@@ -2,47 +2,38 @@
import { Button } from "@/app/_components/ui/button"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/app/_components/ui/tooltip"
import {
- Thermometer,
- Droplets,
- Wind,
- Cloud,
- Eye,
Clock,
AlertTriangle,
- MapIcon,
- BarChart3,
Users,
- Siren,
+ Building,
+ Skull,
} from "lucide-react"
import { Overlay } from "../overlay"
import { ControlPosition } from "mapbox-gl"
+import { IconBriefcaseOff, IconCategory, IconCategoryFilled } from "@tabler/icons-react"
-interface MapControlsProps {
+interface MapMenusProps {
onControlChange: (control: string) => void
activeControl: string
- position?: ControlPosition
+ position: ControlPosition
}
-export default function MapControls({ onControlChange, activeControl, position = "top-left" }: MapControlsProps) {
- const controls = [
- { id: "crime-rate", icon: , label: "Crime Rate" },
- { id: "theft", icon: , label: "Theft" },
- { id: "violence", icon: , label: "Violence" },
- { id: "vandalism", icon: , label: "Vandalism" },
- { id: "traffic", icon: , label: "Traffic" },
- { id: "time", icon: , label: "Time Analysis" },
+export default function MapMenus({ onControlChange, activeControl, position = "top-left" }: MapMenusProps) {
+ const menus = [
+ { id: "crime-rate", icon: , label: "Crime Rate" },
+ { id: "population", icon: , label: "Population" },
+ { id: "unemployment", icon: , label: "Unemployment" },
{ id: "alerts", icon: , label: "Alerts" },
- { id: "districts", icon: , label: "Districts" },
- { id: "statistics", icon: , label: "Statistics" },
- { id: "demographics", icon: , label: "Demographics" },
- { id: "emergency", icon: , label: "Emergency" },
+ { id: "time", icon: , label: "Time Analysis" },
+ { id: "unit", icon: , label: "Unit" },
+ { id: "category", icon: , label: "Category" },
]
return (
-
+
- {controls.map((control) => (
+ {menus.map((control) => (
-
+
)
}
diff --git a/sigap-website/app/_components/map/controls/map-sidebar.tsx b/sigap-website/app/_components/map/controls/map-sidebar.tsx
index 14c95e1..053e1da 100644
--- a/sigap-website/app/_components/map/controls/map-sidebar.tsx
+++ b/sigap-website/app/_components/map/controls/map-sidebar.tsx
@@ -1,10 +1,11 @@
"use client"
+import { ChevronLeft, ChevronRight, Cloud, Droplets, Wind } from "lucide-react"
+
import { Button } from "@/app/_components/ui/button"
-import { ChevronLeft, Filter, Map, BarChart3, Info } from "lucide-react"
+import { Card, CardContent, CardHeader, CardTitle } from "@/app/_components/ui/card"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/app/_components/ui/tabs"
-import { ScrollArea } from "@/app/_components/ui/scroll-area"
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/app/_components/ui/card"
-import { Separator } from "@/app/_components/ui/separator"
+import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/app/_components/ui/collapsible"
+import { cn } from "@/app/_lib/utils"
interface MapSidebarProps {
isOpen: boolean
@@ -12,297 +13,170 @@ interface MapSidebarProps {
crimes?: Array<{
id: string
district_name: string
- distrcit_id?: string
+ district_id?: string
number_of_crime?: number
level?: "low" | "medium" | "high" | "critical"
incidents: any[]
}>
selectedYear?: number | string
selectedMonth?: number | string
+ weatherData?: {
+ temperature: number
+ condition: string
+ humidity: number
+ windSpeed: number
+ forecast: Array<{
+ time: string
+ temperature: number
+ condition: string
+ }>
+ }
}
-export default function MapSidebar({ isOpen, onToggle, crimes = [], selectedYear, selectedMonth }: MapSidebarProps) {
- // Calculate some statistics for the sidebar
- const totalIncidents = crimes.reduce((total, district) => total + (district.number_of_crime || 0), 0)
- const highRiskDistricts = crimes.filter(
- (district) => district.level === "high" || district.level === "critical",
- ).length
- const districtCount = crimes.length
-
+export default function MapSidebar({
+ isOpen,
+ onToggle,
+ crimes = [],
+ selectedYear,
+ selectedMonth,
+ weatherData = {
+ temperature: 78,
+ condition: "Mostly cloudy",
+ humidity: 65,
+ windSpeed: 8,
+ forecast: [
+ { time: "Now", temperature: 78, condition: "Cloudy" },
+ { time: "9:00 PM", temperature: 75, condition: "Cloudy" },
+ { time: "10:00 PM", temperature: 73, condition: "Cloudy" },
+ { time: "11:00 PM", temperature: 72, condition: "Cloudy" },
+ { time: "12:00 AM", temperature: 70, condition: "Cloudy" },
+ ],
+ },
+}: MapSidebarProps) {
return (
-
-
-
Crime Map Explorer
-
-
+
+
Weather Information
+
+
-
-
-
-
- Overview
-
-
-
- Filters
-
-
-
- Stats
-
-
-
- Info
-
+
+
+
+ Current
+ Forecast
-
-
-
-
- Crime Summary
-
- {selectedYear}
- {selectedMonth !== "all" ? ` - Month ${selectedMonth}` : ""}
-
-
-
-
-
- Total Incidents
- {totalIncidents}
-
-
- High Risk Areas
- {highRiskDistricts}
-
-
- Districts
- {districtCount}
-
-
- Data Points
-
- {crimes.reduce((total, district) => total + district.incidents.length, 0)}
-
-
+
+
+
+
+ {weatherData.temperature}°F
+ {weatherData.condition}
+
+
+
+
+
+
+ Humidity: {weatherData.humidity}%
-
-
-
-
-
- District Overview
-
-
-
-
-
-
- District |
- Incidents |
- Level |
-
-
-
- {crimes
- .sort((a, b) => (b.number_of_crime || 0) - (a.number_of_crime || 0))
- .map((district) => (
-
- {district.district_name} |
- {district.number_of_crime || 0} |
-
-
- {district.level || "N/A"}
-
- |
-
- ))}
-
-
+
+
+ Wind: {weatherData.windSpeed} mph
-
-
-
+
+
+
-
-
-
- Filter Options
- Customize what you see on the map
-
-
-
-
Crime Types
-
-
-
-
-
+
+
Today's Recommendations
+
+
+
+
-
-
-
-
Severity Levels
-
+
+
+
+
-
+
+
+
+
+
+ {crimes.length > 0 ? (
+ crimes.map((crime) => (
+
+
+
+ {crime.district_name}
+
+ {crime.number_of_crime}
+
+
+
+
+ ))
+ ) : (
+ No crime data available
+ )}
+
+
+
-
-
Display Options
-
-
-
-
+
+
+ {weatherData.forecast.map((item, index) => (
+
+
+
+
+ {item.time}
-
-
-
-
-
-
-
-
- Crime Statistics
- Analysis of crime data
-
-
-
-
-
Crime by Type
-
- Chart Placeholder
-
+
+ {item.condition}
+ {item.temperature}°
-
-
-
-
-
Crime by Time of Day
-
- Chart Placeholder
-
-
-
-
-
-
-
Monthly Trend
-
- Chart Placeholder
-
-
-
-
-
-
-
-
-
-
- About This Map
-
-
-
- This interactive crime map visualizes crime data across different districts. Use the controls to
- explore different aspects of the data.
-
-
- Legend
-
-
-
-
-
-
-
Critical Crime Rate
-
-
-
- Data Sources
-
- Crime data is collected from official police reports and updated monthly. District boundaries are
- based on administrative regions.
-
-
- Help & Support
-
- For questions or support regarding this map, please contact the system administrator.
-
-
-
-
-
+
+
+ ))}
+
+
diff --git a/sigap-website/app/_components/map/controls/map-toggle.tsx b/sigap-website/app/_components/map/controls/map-toggle.tsx
index f9a7f8c..f1da82c 100644
--- a/sigap-website/app/_components/map/controls/map-toggle.tsx
+++ b/sigap-website/app/_components/map/controls/map-toggle.tsx
@@ -1,29 +1,39 @@
"use client"
-
-import { Button } from "@/app/_components/ui/button"
-import { Menu } from "lucide-react"
+import { ChevronLeft, ChevronRight } from "lucide-react"
+import { Button } from "../../ui/button"
+import { cn } from "@/app/_lib/utils"
import { Overlay } from "../overlay"
-import { ControlPosition } from "mapbox-gl"
interface SidebarToggleProps {
isOpen: boolean
onToggle: () => void
- position?: ControlPosition
+ position?: "left" | "right"
+ className?: string
}
-export default function SidebarToggle({ isOpen, onToggle, position = "left" }: SidebarToggleProps) {
- if (isOpen) return null
-
+export default function SidebarToggle({ isOpen, onToggle, position = "left", className }: SidebarToggleProps) {
return (
)
diff --git a/sigap-website/app/_components/map/controls/month-selector.tsx b/sigap-website/app/_components/map/controls/month-selector.tsx
new file mode 100644
index 0000000..cf5b125
--- /dev/null
+++ b/sigap-website/app/_components/map/controls/month-selector.tsx
@@ -0,0 +1,71 @@
+"use client"
+
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/app/_components/ui/select"
+import { useEffect, useRef, useState } from "react"
+
+// Month options
+const months = [
+ { value: "1", label: "January" },
+ { value: "2", label: "February" },
+ { value: "3", label: "March" },
+ { value: "4", label: "April" },
+ { value: "5", label: "May" },
+ { value: "6", label: "June" },
+ { value: "7", label: "July" },
+ { value: "8", label: "August" },
+ { value: "9", label: "September" },
+ { value: "10", label: "October" },
+ { value: "11", label: "November" },
+ { value: "12", label: "December" },
+]
+
+interface MonthSelectorProps {
+ selectedMonth: number | "all"
+ onMonthChange: (month: number | "all") => void
+ className?: string
+ includeAllOption?: boolean
+}
+
+export default function MonthSelector({
+ selectedMonth,
+ onMonthChange,
+ className = "w-[120px]",
+ includeAllOption = true
+}: MonthSelectorProps) {
+ const containerRef = useRef
(null)
+ const [isClient, setIsClient] = useState(false)
+
+ useEffect(() => {
+ // This will ensure that the document is only used in the client-side context
+ setIsClient(true)
+ })
+
+ const container = isClient ? document.getElementById("root") : null
+
+ return (
+
+
+
+ )
+}
+
+// Export months constant so it can be reused elsewhere
+export { months }
diff --git a/sigap-website/app/_components/map/controls/year-selector.tsx b/sigap-website/app/_components/map/controls/year-selector.tsx
index 0aa8115..dd62c74 100644
--- a/sigap-website/app/_components/map/controls/year-selector.tsx
+++ b/sigap-website/app/_components/map/controls/year-selector.tsx
@@ -1,29 +1,122 @@
"use client"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/app/_components/ui/select"
+import { createRoot } from "react-dom/client"
+import { useRef, useEffect, useState } from "react"
interface YearSelectorProps {
- years: string[]
- selectedYear: string
- onChange: (year: string) => void
+ availableYears?: (number | null)[]
+ selectedYear: number
+ onYearChange: (year: number) => void
+ isLoading?: boolean
+ className?: string
}
-export default function YearSelector({ years, selectedYear, onChange }: YearSelectorProps) {
- return (
-
- Year:
-
-
- )
+interface YearSelectorProps {
+ availableYears?: (number | null)[];
+ selectedYear: number;
+ onYearChange: (year: number) => void;
+ isLoading?: boolean;
+ className?: string;
}
+
+// React component for the year selector UI
+function YearSelectorUI({
+ availableYears = [],
+ selectedYear,
+ onYearChange,
+ isLoading = false,
+ className = "w-[120px]"
+}: YearSelectorProps) {
+ const containerRef = useRef(null);
+ const [isClient, setIsClient] = useState(false);
+
+ useEffect(() => {
+ // This will ensure that the document is only used in the client-side context
+ setIsClient(true);
+ }, []);
+
+ // Conditionally access the document only when running on the client
+ const container = isClient ? document.getElementById("root") : null;
+
+ return (
+
+ {isLoading ? (
+
+ ) : (
+
+ )}
+
+ );
+}
+
+// Mapbox GL control class implementation
+export class YearSelectorControl {
+ private _map: any;
+ private _container!: HTMLElement;
+ private _root: any;
+ private props: YearSelectorProps;
+
+ constructor(props: YearSelectorProps) {
+ this.props = props;
+ }
+
+ onAdd(map: any) {
+ this._map = map;
+ this._container = document.createElement('div');
+ this._container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group';
+ this._container.style.padding = '5px';
+
+ // Set position to relative to keep dropdown content in context
+ this._container.style.position = 'relative';
+ // Higher z-index to ensure dropdown appears above map elements
+ this._container.style.zIndex = '50';
+
+ // Create React root for rendering our component
+ this._root = createRoot(this._container);
+ this._root.render();
+
+ return this._container;
+ }
+
+ onRemove() {
+ if (this._container && this._container.parentNode) {
+ this._container.parentNode.removeChild(this._container);
+ }
+
+ // Unmount React component properly
+ if (this._root) {
+ this._root.unmount();
+ }
+
+ this._map = undefined;
+ }
+}
+
+// Export original React component as default for backward compatibility
+export default function YearSelector(props: YearSelectorProps) {
+ // This wrapper allows the component to be used both as a React component
+ // and to help create a MapboxGL control
+ return ;
+}
\ No newline at end of file
diff --git a/sigap-website/app/_components/map/crime-map.tsx b/sigap-website/app/_components/map/crime-map.tsx
index bc8c935..1bbadf6 100644
--- a/sigap-website/app/_components/map/crime-map.tsx
+++ b/sigap-website/app/_components/map/crime-map.tsx
@@ -4,31 +4,20 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/app/_components/ui/c
import { Skeleton } from "@/app/_components/ui/skeleton"
import DistrictLayer, { type DistrictFeature } from "./layers/district-layer"
import MapView from "./map"
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/app/_components/ui/select"
import { Button } from "@/app/_components/ui/button"
import { AlertCircle, FilterX } from "lucide-react"
import { getMonthName } from "@/app/_utils/common"
import { useCrimeMapHandler } from "@/app/(pages)/(admin)/dashboard/crime-management/crime-overview/_handlers/crime-map-handlers"
-import { useState } from "react"
+import { useRef, useState } from "react"
import { CrimePopup } from "./pop-up"
-import CrimeMarker, { type CrimeIncident } from "./markers/crime-marker"
-
-
-const months = [
- { value: "1", label: "January" },
- { value: "2", label: "February" },
- { value: "3", label: "March" },
- { value: "4", label: "April" },
- { value: "5", label: "May" },
- { value: "6", label: "June" },
- { value: "7", label: "July" },
- { value: "8", label: "August" },
- { value: "9", label: "September" },
- { value: "10", label: "October" },
- { value: "11", label: "November" },
- { value: "12", label: "December" },
-]
+import type { CrimeIncident } from "./markers/crime-marker"
+import YearSelector from "./controls/year-selector"
+import MonthSelector from "./controls/month-selector"
+import { useFullscreen } from "@/app/_hooks/use-fullscreen"
+import { Overlay } from "./overlay"
+import { useGetAvailableYears, useGetCrimeByYearAndMonth } from "@/app/(pages)/(admin)/dashboard/crime-management/crime-overview/_queries/queries"
+import MapLegend from "./controls/map-legend"
export default function CrimeMap() {
// Set default year to 2024 instead of "all"
@@ -38,8 +27,14 @@ export default function CrimeMap() {
const [selectedIncident, setSelectedIncident] = useState(null)
const [showLegend, setShowLegend] = useState(true)
- const { availableYears, yearsLoading, yearsError, crimes, crimesLoading, crimesError, refetchCrimes } =
- useCrimeMapHandler(selectedYear, selectedMonth)
+ const mapContainerRef = useRef(null)
+
+ // Use the custom fullscreen hook
+ const { isFullscreen } = useFullscreen(mapContainerRef)
+
+ const { data: availableYears, isLoading: isYearsLoading, error: yearsError } = useGetAvailableYears()
+
+ const { data: crimes, isLoading: isCrimesLoading, error: isCrimesError, refetch: refetchCrimes } = useGetCrimeByYearAndMonth(selectedYear, selectedMonth)
// Extract all incidents from all districts for marker display
const allIncidents =
@@ -90,82 +85,43 @@ export default function CrimeMap() {
return (
-
+
Crime Map {getMapTitle()}
- {/* Regular (non-fullscreen) controls */}
-
+ {/* Year selector component */}
+
-
+ {/* Month selector component */}
+
-
- {/* */}
- {crimesLoading ? (
+ {isCrimesLoading ? (
- ) : crimesError ? (
+ ) : isCrimesError ? (
Failed to load crime data. Please try again later.
) : (
-
+
-
{/* District Layer with crime data */}
)}
+
+ {isFullscreen && (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ )}
)}
diff --git a/sigap-website/app/_components/map/map.tsx b/sigap-website/app/_components/map/map.tsx
index 58c4d69..a3b3a44 100644
--- a/sigap-website/app/_components/map/map.tsx
+++ b/sigap-website/app/_components/map/map.tsx
@@ -1,28 +1,16 @@
"use client"
import type React from "react"
-
-import { useState, useCallback, useRef } from "react"
-import {
- type ViewState,
- NavigationControl,
- type MapRef,
- FullscreenControl,
- GeolocateControl,
- Map,
-} from "react-map-gl/mapbox"
+import { useState, useCallback, useRef, useEffect } from "react"
+import { type ViewState, Map, type MapRef, NavigationControl } from "react-map-gl/mapbox"
+import { FullscreenControl } from "react-map-gl/mapbox"
import { BASE_LATITUDE, BASE_LONGITUDE, BASE_ZOOM, MAPBOX_STYLES, type MapboxStyle } from "@/app/_utils/const/map"
import "mapbox-gl/dist/mapbox-gl.css"
-import MapSidebar from "./controls/map-sidebar"
-import SidebarToggle from "./controls/map-toggle"
-import TimeControls from "./controls/time-control"
-import SeverityIndicator from "./controls/severity-indicator"
-import MapFilterControl from "./controls/map-filter-control"
+import { createRoot } from "react-dom/client"
+import { YearSelectorControl } from "./controls/year-selector"
import { useFullscreen } from "@/app/_hooks/use-fullscreen"
-import MapControls from "./controls/map-control"
-import { Overlay } from "./overlay"
-import MapLegend from "./controls/map-legend"
-
+import { CustomControl } from "./controls/example"
+import { toast } from "sonner"
interface MapViewProps {
children?: React.ReactNode
@@ -34,22 +22,6 @@ interface MapViewProps {
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
- availableYears?: (number | null)[]
- yearsLoading?: boolean
- onYearChange?: (year: number) => void
- onMonthChange?: (month: number | "all") => void
- onApplyFilters?: () => void
- onResetFilters?: () => void
}
export default function MapView({
@@ -61,24 +33,9 @@ export default function MapView({
height = "100%",
mapboxApiAccessToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN,
onMoveEnd,
- customControls,
- crimes = [],
- selectedYear,
- selectedMonth,
- availableYears = [],
- yearsLoading = false,
- onYearChange = () => { },
- onMonthChange = () => { },
- onApplyFilters = () => { },
- onResetFilters = () => { },
}: MapViewProps) {
const [mapRef, setMapRef] = useState
(null)
- const [activeControl, setActiveControl] = useState("crime-rate")
- const [activeTime, setActiveTime] = useState("today")
- const [sidebarOpen, setSidebarOpen] = useState(false)
const mapContainerRef = useRef(null)
-
- // Use the custom fullscreen hook instead of manual event listeners
const { isFullscreen } = useFullscreen(mapContainerRef)
const defaultViewState: Partial = {
@@ -90,87 +47,32 @@ export default function MapView({
...initialViewState,
}
- const handleMapLoad = useCallback((event: any) => {
- setMapRef(event.target)
- }, [])
-
const handleMoveEnd = useCallback(
(event: any) => {
if (onMoveEnd) {
onMoveEnd(event.viewState)
}
},
- [onMoveEnd],
+ [onMoveEnd]
)
- const handleControlChange = (control: string) => {
- setActiveControl(control)
- }
-
- const handleTimeChange = (time: string) => {
- setActiveTime(time)
- }
-
- const toggleSidebar = () => {
- setSidebarOpen(!sidebarOpen)
- }
-
return (
- {/* Main content with left padding when sidebar is open */}
-
-
+
)
diff --git a/sigap-website/app/_components/map/overlay.tsx b/sigap-website/app/_components/map/overlay.tsx
index 44d880d..0a3e668 100644
--- a/sigap-website/app/_components/map/overlay.tsx
+++ b/sigap-website/app/_components/map/overlay.tsx
@@ -5,32 +5,72 @@ import { createPortal } from "react-dom";
import { useControl } from "react-map-gl/mapbox";
import { v4 as uuidv4 } from 'uuid';
+// Updated props type to include addControl in children props
type OverlayProps = {
position: ControlPosition;
- children: ReactElement<{ map?: Map }>;
+ children: ReactElement<{
+ map?: Map;
+ addControl?: (control: IControl, position?: ControlPosition) => void;
+ }>;
id?: string;
+ className?: string;
+ style?: React.CSSProperties;
};
-// Definisikan custom control untuk overlay
+// Custom control for overlay
class OverlayControl implements IControl {
_map: Map | null = null;
_container: HTMLElement | null = null;
_position: ControlPosition;
_id: string;
_redraw?: () => void;
+ _className?: string;
+ _style?: React.CSSProperties;
- constructor({ position, id, redraw }: { position: ControlPosition; id: string; redraw?: () => void }) {
+ constructor({
+ position,
+ id,
+ redraw,
+ className,
+ style,
+ }: {
+ position: ControlPosition;
+ id: string;
+ redraw?: () => void;
+ className?: string;
+ style?: React.CSSProperties;
+ }) {
this._position = position;
this._id = id;
this._redraw = redraw;
+ this._className = className;
+ this._style = style;
}
onAdd(map: Map) {
this._map = map;
this._container = document.createElement('div');
- this._container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group';
+
+ // Apply base classes but keep it minimal to avoid layout conflicts
+ this._container.className = `mapboxgl-ctrl ${this._className || ''}`;
this._container.id = this._id;
+ // Important: These styles make the overlay adapt to content
+ this._container.style.pointerEvents = 'auto';
+ this._container.style.display = 'inline-block'; // Allow container to size to content
+ this._container.style.maxWidth = 'none'; // Remove any max-width constraints
+ this._container.style.width = 'auto'; // Let width be determined by content
+ this._container.style.height = 'auto'; // Let height be determined by content
+ this._container.style.overflow = 'visible'; // Allow content to overflow if needed
+
+ // Apply any custom styles passed as props
+ if (this._style) {
+ Object.entries(this._style).forEach(([key, value]) => {
+ // @ts-ignore - dynamically setting style properties
+ this._container.style[key] = value;
+ });
+ }
+
if (this._redraw) {
map.on('move', this._redraw);
this._redraw();
@@ -61,20 +101,34 @@ class OverlayControl implements IControl {
getElement() {
return this._container;
}
+
+ // Method to add other controls to the map
+ addControl(control: IControl, position?: ControlPosition) {
+ if (this._map) {
+ this._map.addControl(control, position);
+ }
+ return this;
+ }
}
-// Komponen Overlay yang telah ditingkatkan
-function _Overlay({ position, children, id = `overlay-${uuidv4()}` }: OverlayProps) {
+// Enhanced Overlay component
+function _Overlay({ position, children, id = `overlay-${uuidv4()}`, className, style }: OverlayProps) {
const [container, setContainer] = useState
(null);
- const [map, setMap] = useState