From 897a130ff7348a09e36fbdee67ca453d5c8683f4 Mon Sep 17 00:00:00 2001 From: vergiLgood1 Date: Thu, 1 May 2025 21:15:31 +0700 Subject: [PATCH] feat: Enhance Overlay component with customizable styles and addControl method - Updated OverlayProps to include className and style for customization. - Modified OverlayControl to apply custom styles and handle control addition. - Improved rendering logic in _Overlay to pass addControl method to children. - Refactored SelectContent to accept a container prop for better positioning. - Cleaned up useFullscreen hook by removing unnecessary comments and improving readability. - Updated global CSS for better styling of map popups and controls. - Removed unused dependencies related to mapbox-gl-draw from package.json and package-lock.json. - Added CustomControl and MonthSelector components for enhanced map functionality. --- .../crime-overview/_queries/queries.ts | 32 +- .../app/_components/map/controls/example.tsx | 66 +++ .../_components/map/controls/map-control.tsx | 41 +- .../_components/map/controls/map-sidebar.tsx | 416 ++++++------------ .../_components/map/controls/map-toggle.tsx | 34 +- .../map/controls/month-selector.tsx | 71 +++ .../map/controls/year-selector.tsx | 135 +++++- .../app/_components/map/crime-map.tsx | 138 +++--- sigap-website/app/_components/map/map.tsx | 144 +----- sigap-website/app/_components/map/overlay.tsx | 91 +++- sigap-website/app/_components/ui/select.tsx | 40 +- sigap-website/app/_hooks/use-fullscreen.ts | 16 - sigap-website/app/_styles/globals.css | 25 +- sigap-website/package-lock.json | 71 --- sigap-website/package.json | 1 - 15 files changed, 673 insertions(+), 648 deletions(-) create mode 100644 sigap-website/app/_components/map/controls/example.tsx create mode 100644 sigap-website/app/_components/map/controls/month-selector.tsx 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 - - -
- - - - - - - - - - {crimes - .sort((a, b) => (b.number_of_crime || 0) - (a.number_of_crime || 0)) - .map((district) => ( - - - - - - ))} - -
DistrictIncidentsLevel
{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

+ +
+ + +
+
🌂
+
Umbrella
+
No need
-
+ + - - -
-

Severity Levels

-
- - - - + + +
+
🏞️
+
Outdoors
+
Very poor
-
+ + +
+
- + + + + + + {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

-
-
-
- Low Crime Rate -
-
-
- Medium Crime Rate -
-
-
- High Crime Rate -
-
-
- 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 */} -
- setMapRef(ref)} - mapStyle={mapStyle} - mapboxAccessToken={mapboxApiAccessToken} - initialViewState={defaultViewState} - onLoad={handleMapLoad} - onMoveEnd={handleMoveEnd} - interactiveLayerIds={["district-fill", "clusters", "unclustered-point"]} - attributionControl={false} - style={{ width: "100%", height: "100%" }} - > - {children} - - - - {/* Sidebar and other controls only in fullscreen */} - {isFullscreen && ( - <> - {/* */} - - - - {/* Map Legend - positioned at bottom-right */} - - - {/* Sidebar toggle - positioned on the left */} - - - {/* Map Control - positioned at top-left */} - - - {/* Time/year selector - positioned at bottom-left */} - - - - )} - +
+
+ + + + {children} + +
) 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(null) + const [map, setMap] = useState(null); - // Gunakan useControl dengan ID unik untuk menghindari konflik + // Use useControl with unique ID to avoid conflicts const ctrl = useControl( - () => new OverlayControl({ position, id }), - { position } // Hanya menggunakan position yang valid dalam ControlOptions + () => + new OverlayControl({ + position, + id, + className, + style, + }), + { position } ); - // Update container dan map instance ketika control siap + // Update container and map instance when control is ready useEffect(() => { if (ctrl) { setContainer(ctrl.getElement()); @@ -82,15 +136,20 @@ function _Overlay({ position, children, id = `overlay-${uuidv4()}` }: OverlayPro } }, [ctrl]); - // Hanya render jika container sudah siap + // Only render if container is ready if (!container || !map) return null; - // Gunakan createPortal untuk merender children ke container + // Use createPortal to render children to container and pass addControl method + // return createPortal( + // cloneElement(children, { map, addControl: ctrl.addControl.bind(ctrl) }), + // container + // ); + return createPortal( cloneElement(children, { map }), container - ); + ) } -// Export sebagai komponen memoized -export const Overlay = memo(_Overlay); \ No newline at end of file +// Export as memoized component +export const Overlay = memo(_Overlay); diff --git a/sigap-website/app/_components/ui/select.tsx b/sigap-website/app/_components/ui/select.tsx index 9b15b75..8c6a714 100644 --- a/sigap-website/app/_components/ui/select.tsx +++ b/sigap-website/app/_components/ui/select.tsx @@ -67,34 +67,38 @@ const SelectScrollDownButton = React.forwardRef< SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName +interface SelectContentProps extends React.ComponentPropsWithoutRef { + container?: HTMLElement +} + const SelectContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, position = "popper", ...props }, ref) => ( - + SelectContentProps +>(({ className, children, position = "popper", container, ...props }, ref) => ( + - - - {children} - - - + + + {children} + + + )) SelectContent.displayName = SelectPrimitive.Content.displayName diff --git a/sigap-website/app/_hooks/use-fullscreen.ts b/sigap-website/app/_hooks/use-fullscreen.ts index 9bea48d..7c839c1 100644 --- a/sigap-website/app/_hooks/use-fullscreen.ts +++ b/sigap-website/app/_hooks/use-fullscreen.ts @@ -2,11 +2,6 @@ import { useState, useEffect, RefObject } from 'react'; -/** - * Hook for detecting fullscreen state changes - * @param containerRef Reference to the container element - * @returns Object containing the fullscreen state and functions to control it - */ export function useFullscreen(containerRef: RefObject) { const [isFullscreen, setIsFullscreen] = useState(false); @@ -21,13 +16,11 @@ export function useFullscreen(containerRef: RefObject) { setIsFullscreen(!!fullscreenElement); }; - // Add event listeners for fullscreen changes across browsers document.addEventListener('fullscreenchange', handleFullscreenChange); document.addEventListener('webkitfullscreenchange', handleFullscreenChange); document.addEventListener('mozfullscreenchange', handleFullscreenChange); document.addEventListener('MSFullscreenChange', handleFullscreenChange); - // Cleanup function to remove event listeners return () => { document.removeEventListener('fullscreenchange', handleFullscreenChange); document.removeEventListener( @@ -45,9 +38,6 @@ export function useFullscreen(containerRef: RefObject) { }; }, []); - /** - * Requests fullscreen for the container element - */ const enterFullscreen = () => { if (!containerRef.current) return; @@ -64,9 +54,6 @@ export function useFullscreen(containerRef: RefObject) { } }; - /** - * Exits fullscreen mode - */ const exitFullscreen = () => { if (document.exitFullscreen) { document.exitFullscreen(); @@ -79,9 +66,6 @@ export function useFullscreen(containerRef: RefObject) { } }; - /** - * Toggles fullscreen mode - */ const toggleFullscreen = () => { if (isFullscreen) { exitFullscreen(); diff --git a/sigap-website/app/_styles/globals.css b/sigap-website/app/_styles/globals.css index febf645..0324c7b 100644 --- a/sigap-website/app/_styles/globals.css +++ b/sigap-website/app/_styles/globals.css @@ -161,7 +161,8 @@ .mapboxgl-popup-content { padding: 0 !important; border-radius: 0.5rem !important; - box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05) !important; overflow: hidden; } @@ -172,4 +173,26 @@ .map-popup .mapboxgl-popup-content { max-width: 300px; +} + +/* Mapbox copyright */ +.mapbox-logo { + /* display: none; */ +} +.mapboxgl-ctrl-logo { + /* display: none !important; */ +} + +/* .mapbox-improve-map { + display: none; +} +.mapboxgl-ctrl-compass { + display: none; +} */ + +.mapbox-gl-draw_point { + background-repeat: no-repeat; + background-position: center; + pointer-events: auto; + background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIgICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgICB4bWxuczpzb2RpcG9kaT0iaHR0cDovL3NvZGlwb2RpLnNvdXJjZWZvcmdlLm5ldC9EVEQvc29kaXBvZGktMC5kdGQiICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiICAgd2lkdGg9IjIwIiAgIGhlaWdodD0iMjAiICAgdmlld0JveD0iMCAwIDIwIDIwIiAgIGlkPSJzdmcxOTE2NyIgICB2ZXJzaW9uPSIxLjEiICAgaW5rc2NhcGU6dmVyc2lvbj0iMC45MStkZXZlbCtvc3htZW51IHIxMjkxMSIgICBzb2RpcG9kaTpkb2NuYW1lPSJtYXJrZXIuc3ZnIj4gIDxkZWZzICAgICBpZD0iZGVmczE5MTY5IiAvPiAgPHNvZGlwb2RpOm5hbWVkdmlldyAgICAgaWQ9ImJhc2UiICAgICBwYWdlY29sb3I9IiNmZmZmZmYiICAgICBib3JkZXJjb2xvcj0iIzY2NjY2NiIgICAgIGJvcmRlcm9wYWNpdHk9IjEuMCIgICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwLjAiICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIiAgICAgaW5rc2NhcGU6em9vbT0iMTYiICAgICBpbmtzY2FwZTpjeD0iMTQuMTY0MjUzIiAgICAgaW5rc2NhcGU6Y3k9IjguODg1NzIiICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0icHgiICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiICAgICBzaG93Z3JpZD0iZmFsc2UiICAgICB1bml0cz0icHgiICAgICBpbmtzY2FwZTp3aW5kb3ctd2lkdGg9IjEyODAiICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSI3NTEiICAgICBpbmtzY2FwZTp3aW5kb3cteD0iMjA4IiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjE5MCIgICAgIGlua3NjYXBlOndpbmRvdy1tYXhpbWl6ZWQ9IjAiICAgICBpbmtzY2FwZTpvYmplY3Qtbm9kZXM9InRydWUiPiAgICA8aW5rc2NhcGU6Z3JpZCAgICAgICB0eXBlPSJ4eWdyaWQiICAgICAgIGlkPSJncmlkMTk3MTUiIC8+ICA8L3NvZGlwb2RpOm5hbWVkdmlldz4gIDxtZXRhZGF0YSAgICAgaWQ9Im1ldGFkYXRhMTkxNzIiPiAgICA8cmRmOlJERj4gICAgICA8Y2M6V29yayAgICAgICAgIHJkZjphYm91dD0iIj4gICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PiAgICAgICAgPGRjOnR5cGUgICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiIC8+ICAgICAgICA8ZGM6dGl0bGUgLz4gICAgICA8L2NjOldvcms+ICAgIDwvcmRmOlJERj4gIDwvbWV0YWRhdGE+ICA8ZyAgICAgaW5rc2NhcGU6bGFiZWw9IkxheWVyIDEiICAgICBpbmtzY2FwZTpncm91cG1vZGU9ImxheWVyIiAgICAgaWQ9ImxheWVyMSIgICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsLTEwMzIuMzYyMikiPiAgICA8cGF0aCAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtjbGlwLXJ1bGU6bm9uemVybztkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO3Zpc2liaWxpdHk6dmlzaWJsZTtvcGFjaXR5OjE7aXNvbGF0aW9uOmF1dG87bWl4LWJsZW5kLW1vZGU6bm9ybWFsO2NvbG9yLWludGVycG9sYXRpb246c1JHQjtjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM6bGluZWFyUkdCO3NvbGlkLWNvbG9yOiMwMDAwMDA7c29saWQtb3BhY2l0eToxO2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MjtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MDtzdHJva2Utb3BhY2l0eToxO21hcmtlcjpub25lO2NvbG9yLXJlbmRlcmluZzphdXRvO2ltYWdlLXJlbmRlcmluZzphdXRvO3NoYXBlLXJlbmRlcmluZzphdXRvO3RleHQtcmVuZGVyaW5nOmF1dG87ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIgICAgICAgZD0ibSAzNiwxMDQwLjM2MjIgYyA2ZS02LDMuMzA5MyAtNS45ODg2MTIsMTAgLTUuOTg4NjEyLDEwIDAsMCAtNS45OTg3NzYsLTYuNjY4IC02LjAxMTM0NSwtOS45NzcyIC0wLjAxMjU3LC0zLjMwOTIgMi42NTY1NzYsLTYuMDAzOSA1Ljk2NTc5MiwtNi4wMjI3IDMuMzA5MTg5LC0wLjAxOSA2LjAwODg0LDIuNjQ1MiA2LjAzMzk5Miw1Ljk1NDMiICAgICAgIGlkPSJwYXRoMTI1NjEiICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2Nzc2MiIC8+ICAgIDxwYXRoICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2NsaXAtcnVsZTpub256ZXJvO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7dmlzaWJpbGl0eTp2aXNpYmxlO29wYWNpdHk6MTtpc29sYXRpb246YXV0bzttaXgtYmxlbmQtbW9kZTpub3JtYWw7Y29sb3ItaW50ZXJwb2xhdGlvbjpzUkdCO2NvbG9yLWludGVycG9sYXRpb24tZmlsdGVyczpsaW5lYXJSR0I7c29saWQtY29sb3I6IzAwMDAwMDtzb2xpZC1vcGFjaXR5OjE7ZmlsbDojZmZmZmZmO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZTpub25lO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO3N0cm9rZS1vcGFjaXR5OjE7bWFya2VyOm5vbmU7Y29sb3ItcmVuZGVyaW5nOmF1dG87aW1hZ2UtcmVuZGVyaW5nOmF1dG87c2hhcGUtcmVuZGVyaW5nOmF1dG87dGV4dC1yZW5kZXJpbmc6YXV0bztlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIiAgICAgICBkPSJtIDM0LjAwMDExNSwxMDQwLjM2MjIgYyAtNWUtNiwyLjIwNjIgLTMuOTkyNTIzLDcuMDAwMSAtMy45OTI1MjMsNy4wMDAxIDAsMCAtMy45OTkyOTEsLTQuNzc4NyAtNC4wMDc2NzksLTYuOTg0OSAtMC4wMDg0LC0yLjIwNjIgMS43NzEwODIsLTQuMDAyNyAzLjk3NzMxLC00LjAxNTMgMi4yMDYyMSwtMC4wMTMgNC4wMDYwMzcsMS43NjM1IDQuMDIyNzc3LDMuOTY5NyIgICAgICAgaWQ9InBhdGgxMjU2MyIgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIgICAgICAgc29kaXBvZGk6bm9kZXR5cGVzPSJjY2NzYyIgLz4gICAgPHBhdGggICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7Y2xpcC1ydWxlOm5vbnplcm87ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTt2aXNpYmlsaXR5OnZpc2libGU7b3BhY2l0eToxO2lzb2xhdGlvbjphdXRvO21peC1ibGVuZC1tb2RlOm5vcm1hbDtjb2xvci1pbnRlcnBvbGF0aW9uOnNSR0I7Y29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzOmxpbmVhclJHQjtzb2xpZC1jb2xvcjojMDAwMDAwO3NvbGlkLW9wYWNpdHk6MTtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7c3Ryb2tlLW9wYWNpdHk6MTttYXJrZXI6bm9uZTtjb2xvci1yZW5kZXJpbmc6YXV0bztpbWFnZS1yZW5kZXJpbmc6YXV0bztzaGFwZS1yZW5kZXJpbmc6YXV0bzt0ZXh0LXJlbmRlcmluZzphdXRvO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiICAgICAgIGQ9Ik0gOS45NjY3OTY5LDEwMTQuMzYyMiBDIDYuNjU3NTgwOSwxMDE0LjM4MSAzLjk4NzQzLDEwMTcuMDc2NCA0LDEwMjAuMzg1NiBjIDAuMDEyNTY5LDMuMzA5MiA2LjAxMTcxOSw4Ljk3NjYgNi4wMTE3MTksOC45NzY2IDAsMCA1Ljk4ODI4NywtNS42OTA3IDUuOTg4MjgxLC05IGwgMCwtMC4wNDUgYyAtMC4wMjUxNSwtMy4zMDkxIC0yLjcyNDAxNCwtNS45NzQxIC02LjAzMzIwMzEsLTUuOTU1MSB6IG0gMC4wMDk3NywyIGMgMi4yMDYyMDYxLC0wLjAxMyA0LjAwNjY5MzEsMS43NjI2IDQuMDIzNDMzMSwzLjk2ODggbCAwLDAuMDMxIGMgLTVlLTYsMi4yMDYyIC0zLjk5MjE4OCw2IC0zLjk5MjE4OCw2IDAsMCAtMy45OTk0MjQsLTMuNzc4MiAtNC4wMDc4MTIsLTUuOTg0NCAtMC4wMDg0LC0yLjIwNjIgMS43NzAzMzQ1LC00LjAwMyAzLjk3NjU2MjUsLTQuMDE1NiB6IiAgICAgICBpZD0icGF0aDEyNTY4IiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIiAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9ImNzY3NjY2Njc2NzYyIgLz4gICAgPHBhdGggICAgICAgc3R5bGU9Im9wYWNpdHk6MTtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46YmV2ZWw7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MDtzdHJva2Utb3BhY2l0eToxO21hcmtlcjpub25lIiAgICAgICBkPSJNIDEwIDIgQyA2LjY4NjI5MiAyIDQgNC42ODYzIDQgOCBDIDQgMTEuMzEzNyAxMCAxNyAxMCAxNyBDIDEwIDE3IDE2IDExLjMxMzcgMTYgOCBDIDE2IDQuNjg2MyAxMy4zMTM3MDggMiAxMCAyIHogTSAxMCA0IEMgMTIuMDcxMDY4IDQgMTMuNzUgNS42Nzg5IDEzLjc1IDcuNzUgQyAxMy43NSA5LjIwNTMyNzggMTEuOTMxMTEgMTEuNjQ0MzkzIDEwLjgzMDA3OCAxMyBMIDkuMTY5OTIxOSAxMyBDIDguMDY4ODkwMyAxMS42NDQzOTMgNi4yNSA5LjIwNTMyNzggNi4yNSA3Ljc1IEMgNi4yNSA1LjY3ODkgNy45Mjg5MzIgNCAxMCA0IHogIiAgICAgICB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLDEwMzIuMzYyMikiICAgICAgIGlkPSJwYXRoMTczMDUiIC8+ICA8L2c+PC9zdmc+); } \ No newline at end of file diff --git a/sigap-website/package-lock.json b/sigap-website/package-lock.json index 2a9e88c..9cef05e 100644 --- a/sigap-website/package-lock.json +++ b/sigap-website/package-lock.json @@ -7,7 +7,6 @@ "dependencies": { "@evyweb/ioctopus": "^1.2.0", "@hookform/resolvers": "^4.1.2", - "@mapbox/mapbox-gl-draw": "^1.5.0", "@prisma/client": "^6.4.1", "@prisma/instrumentation": "^6.5.0", "@radix-ui/react-alert-dialog": "^1.1.6", @@ -1645,24 +1644,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@mapbox/geojson-area": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@mapbox/geojson-area/-/geojson-area-0.2.2.tgz", - "integrity": "sha512-bBqqFn1kIbLBfn7Yq1PzzwVkPYQr9lVUeT8Dhd0NL5n76PBuXzOcuLV7GOSbEB1ia8qWxH4COCvFpziEu/yReA==", - "license": "BSD-2-Clause", - "dependencies": { - "wgs84": "0.0.0" - } - }, - "node_modules/@mapbox/geojson-normalize": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@mapbox/geojson-normalize/-/geojson-normalize-0.0.1.tgz", - "integrity": "sha512-82V7YHcle8lhgIGqEWwtXYN5cy0QM/OHq3ypGhQTbvHR57DF0vMHMjjVSQKFfVXBe/yWCBZTyOuzvK7DFFnx5Q==", - "license": "ISC", - "bin": { - "geojson-normalize": "geojson-normalize" - } - }, "node_modules/@mapbox/jsonlint-lines-primitives": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", @@ -1671,52 +1652,6 @@ "node": ">= 0.6" } }, - "node_modules/@mapbox/mapbox-gl-draw": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-draw/-/mapbox-gl-draw-1.5.0.tgz", - "integrity": "sha512-uchQbTa8wiv6GWWTbxW1g5b8H6VySz4t91SmduNH6jjWinPze7cjcmsPUEzhySXsYpYr2/50gRJLZz3bx7O88A==", - "license": "ISC", - "dependencies": { - "@mapbox/geojson-area": "^0.2.2", - "@mapbox/geojson-normalize": "^0.0.1", - "@mapbox/point-geometry": "^1.1.0", - "fast-deep-equal": "^3.1.3", - "nanoid": "^5.0.9" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, - "node_modules/@mapbox/mapbox-gl-draw/node_modules/@mapbox/point-geometry": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-1.1.0.tgz", - "integrity": "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==", - "license": "ISC" - }, - "node_modules/@mapbox/mapbox-gl-draw/node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/@mapbox/mapbox-gl-draw/node_modules/nanoid": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", - "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^18 || >=20" - } - }, "node_modules/@mapbox/mapbox-gl-supported": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-3.0.0.tgz", @@ -14080,12 +14015,6 @@ "node": ">=4.0" } }, - "node_modules/wgs84": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/wgs84/-/wgs84-0.0.0.tgz", - "integrity": "sha512-ANHlY4Rb5kHw40D0NJ6moaVfOCMrp9Gpd1R/AIQYg2ko4/jzcJ+TVXYYF6kXJqQwITvEZP4yEthjM7U6rYlljQ==", - "license": "BSD-2-Clause" - }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", diff --git a/sigap-website/package.json b/sigap-website/package.json index b988325..f4c8c89 100644 --- a/sigap-website/package.json +++ b/sigap-website/package.json @@ -13,7 +13,6 @@ "dependencies": { "@evyweb/ioctopus": "^1.2.0", "@hookform/resolvers": "^4.1.2", - "@mapbox/mapbox-gl-draw": "^1.5.0", "@prisma/client": "^6.4.1", "@prisma/instrumentation": "^6.5.0", "@radix-ui/react-alert-dialog": "^1.1.6",