Skip to content

Commit a32e2fd

Browse files
authored
Merge pull request #110 from armada-ths/feat/map
Feat/map
2 parents 6c783dd + 7ae6ba5 commit a32e2fd

26 files changed

+3966
-875
lines changed

src/app/student/map/_components/BoothMarker.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ export function BoothMarker({ booth, scale }: { booth: Booth; scale: number }) {
2222
height={300}></Image>
2323
</div>
2424
) : (
25-
<span className="text-black">{booth.exhibitor.name}</span>
25+
<div className="max-w-[100px] truncate text-neutral-200">
26+
{booth.exhibitor.name}
27+
</div>
2628
)}
2729
</div>
2830
</Marker>

src/app/student/map/_components/BoothPopup.tsx

-17
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"use client"
2+
3+
import { Filter, FilterItem } from "@/app/student/lib/filters"
4+
import { FilterSelectionItem } from "@/app/student/map/_components/FilterSelectionItem"
5+
import { useState } from "react"
6+
7+
export default function FilterSection({
8+
filter,
9+
onChange
10+
}: {
11+
filter: Filter
12+
onChange: (selected: FilterItem[]) => void
13+
}) {
14+
const { items, selected, label } = filter
15+
16+
const maxDisplayed = 7
17+
const [numDisplayed, setNumDisplayed] = useState(maxDisplayed)
18+
19+
function isSelected(item: FilterItem) {
20+
return selected.some(s => s.id === item.id)
21+
}
22+
23+
function onSelectionChange(item: FilterItem) {
24+
if (isSelected(item)) {
25+
onChange(selected.filter(s => s.id !== item.id)) // remove item
26+
} else {
27+
onChange([...selected, item]) // add item
28+
}
29+
}
30+
31+
return (
32+
<div className="flex flex-col justify-between">
33+
<div>
34+
<h2 className="m-2 text-left text-xl text-stone-200">{label}</h2>
35+
<div className="m-1 mt-3 flex flex-wrap gap-4">
36+
{items.slice(0, numDisplayed).map(item => (
37+
<div className="" key={item.id}>
38+
<FilterSelectionItem
39+
name={item.name}
40+
isSelected={filter.selected.includes(item)}
41+
onClick={() => onSelectionChange(item)}
42+
/>
43+
</div>
44+
))}
45+
{maxDisplayed < items.length && (
46+
<button
47+
onClick={() =>
48+
setNumDisplayed(
49+
numDisplayed < items.length ? items.length : maxDisplayed
50+
)
51+
}
52+
className="flex w-auto rounded-3xl border border-neutral-400 px-3 py-2 text-center text-xs text-neutral-400 xs:px-4 xs:py-2 xs:text-base">
53+
{numDisplayed < items.length ? (
54+
<span>Show all {items.length}</span>
55+
) : (
56+
<span>Show less</span>
57+
)}
58+
</button>
59+
)}
60+
</div>
61+
</div>
62+
</div>
63+
)
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export function FilterSelectionItem({
2+
name,
3+
isSelected,
4+
onClick
5+
}: {
6+
name: string
7+
isSelected: boolean
8+
onClick: () => void
9+
}) {
10+
return (
11+
<div
12+
className={`flex w-auto cursor-pointer text-ellipsis whitespace-nowrap rounded-3xl border px-3 py-2 text-center transition xs:px-4 xs:py-2 ${
13+
isSelected
14+
? "border-1 border-melon-700 text-melon-700 shadow-md shadow-melon-700/30"
15+
: "border-emerald-700 text-emerald-700"
16+
}`}
17+
onClick={onClick}>
18+
<span className="text-xs xs:text-base">{name}</span>
19+
</div>
20+
)
21+
}

src/app/student/map/_components/MainView.tsx

+17-44
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,12 @@
22

33
import LocationSelect from "@/app/student/map/_components/LocationSelect"
44
import { MapComponent } from "@/app/student/map/_components/MapComponent"
5-
import { QuestionnaireForm } from "@/app/student/map/_components/QuestionnaireForm"
65
import Sidebar from "@/app/student/map/_components/Sidebar"
76
import EditorMapComponent from "@/app/student/map/editor/EditorMapComponent"
87
import { Exhibitor } from "@/components/shared/hooks/api/useExhibitors"
9-
import { useScreenSize } from "@/components/shared/hooks/useScreenSize"
10-
import { useSurveyData } from "@/components/shared/hooks/useSurveyData"
11-
import Modal from "@/components/shared/Modal"
128
import { Button } from "@/components/ui/button"
13-
import { Filter } from "lucide-react"
14-
import { useRouter, useSearchParams } from "next/navigation"
15-
import { useEffect, useState } from "react"
9+
import { useSearchParams } from "next/navigation"
10+
import { useMemo, useState } from "react"
1611
import { Booth, BoothID, BoothMap } from "../lib/booths"
1712
import {
1813
defaultLocation,
@@ -35,16 +30,27 @@ export default function MainView({
3530
// if lat, lng or zoom is not provided, default to location center
3631

3732
const searchParams = useSearchParams()
38-
const { width } = useScreenSize()
39-
const router = useRouter()
40-
const { surveyData, isSurveyDataLoaded } = useSurveyData()
4133

4234
const floorUrlString = searchParams.get("floor") ?? "nymble/2"
4335
const [locationId, setLocationId] = useState<LocationId>(
4436
validLocationId(floorUrlString) ? floorUrlString : defaultLocation.id
4537
)
38+
const [preLocationId, setPreLocationId] = useState<LocationId>(locationId)
4639
const location = locations.find(loc => loc.id === locationId)!
47-
const currentLocationBoothsById = boothsByLocation.get(locationId)!
40+
const currentLocationBoothsById = useMemo(() => {
41+
const boothsById =
42+
locationId !== "library"
43+
? new Map([
44+
...Array.from(boothsByLocation.get("library")!.entries()),
45+
...Array.from(boothsByLocation.get(locationId)!.entries()) // Merge library booths with default location booths
46+
])
47+
: new Map([
48+
...Array.from(boothsByLocation.get(preLocationId)!.entries()),
49+
...Array.from(boothsByLocation.get(locationId)!.entries()) // Merge library booths with default location booths
50+
])
51+
setPreLocationId(location.id)
52+
return boothsById
53+
}, [location.id])
4854

4955
const latitude =
5056
parseFloat(searchParams.get("lat") ?? "") || location.center.latitude
@@ -60,27 +66,8 @@ export default function MainView({
6066
const [filteredBooths, setFilteredBooths] = useState<Booth[]>(
6167
Array.from(boothsById.values())
6268
)
63-
const [openSurvey, setOpenSurvey] = useState(false)
6469
const [editorMode, setEditorMode] = useState(false)
6570

66-
useEffect(() => {
67-
if (isSurveyDataLoaded) setOpenSurvey(!surveyData)
68-
}, [surveyData, isSurveyDataLoaded])
69-
70-
useEffect(() => {
71-
// A new survey page for filter when using mobile
72-
if (openSurvey && width && width < 768) {
73-
router.push("/student/map/survey")
74-
}
75-
}, [width])
76-
77-
const handleClickFilter = () => {
78-
setOpenSurvey(prev => !prev)
79-
if (width && width < 768) {
80-
router.push("/student/map/survey")
81-
}
82-
}
83-
8471
return (
8572
<div className="relative flex h-full w-full">
8673
{!editorMode ? (
@@ -123,20 +110,6 @@ export default function MainView({
123110
</Button>
124111
)}
125112

126-
{/* Questions Modal for filter when using PC*/}
127-
<Modal
128-
open={openSurvey}
129-
setOpen={setOpenSurvey}
130-
className="max-w-[780px] bg-gradient-to-br from-emerald-950 via-stone-900 to-stone-900 p-0">
131-
<QuestionnaireForm onClose={() => setOpenSurvey(false)} />
132-
</Modal>
133-
134-
<Button
135-
className="absolute top-2 ml-2 justify-self-center rounded-full sm:right-2 sm:top-20"
136-
onClick={handleClickFilter}>
137-
<Filter />
138-
</Button>
139-
140113
<LocationSelect
141114
locationId={locationId}
142115
setLocationId={setLocationId}

src/app/student/map/_components/MapComponent.tsx

+63-27
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import { BoothPopup } from "@/app/student/map/_components/BoothPopup"
21
import {
32
BoothID,
43
geoJsonBoothDataByLocation
54
} from "@/app/student/map/lib/booths"
6-
import { Location } from "@/app/student/map/lib/locations"
5+
import { Location, LocationId } from "@/app/student/map/lib/locations"
76
import { useFeatureState } from "@/components/shared/hooks/useFeatureState"
87
import { useGeoJsonPlanData } from "@/components/shared/hooks/useGeoJsonPlanData"
98
import "maplibre-gl/dist/maplibre-gl.css"
@@ -20,6 +19,7 @@ import {
2019
addMapIconAssets,
2120
backgroundLayerStyle,
2221
boothLayerStyle,
22+
boothOutlineStyle,
2323
buildingLayerStyle,
2424
lineLayerStyle,
2525
roomLayerStyle,
@@ -49,15 +49,21 @@ export function MapComponent({
4949
}) {
5050
const mapRef = useRef<MapRef>(null)
5151

52+
const [mapZoom, setMapZoom] = useState(initialView.zoom)
53+
5254
const [markerScale, setMarkerScale] = useState(1)
5355

56+
const [preLocationId, setPreLocationId] = useState<LocationId>(location.id)
5457
// Fly to location center on change
5558
useEffect(() => {
5659
const { longitude, latitude, zoom } = location.center
57-
mapRef.current?.flyTo({
58-
center: [longitude, latitude],
59-
zoom: zoom
60-
})
60+
const timeout = setTimeout(() => {
61+
mapRef.current?.flyTo({
62+
center: [longitude, latitude],
63+
zoom
64+
})
65+
}, 300)
66+
return () => clearTimeout(timeout)
6167
setMarkerScale(0.6)
6268
}, [location])
6369

@@ -79,7 +85,7 @@ export function MapComponent({
7985
if (!booth) return
8086
mapRef.current?.flyTo({
8187
center: booth.center as [number, number],
82-
zoom: 21,
88+
zoom: 20,
8389
speed: 0.8
8490
})
8591
}, [activeBoothId, boothsById])
@@ -88,10 +94,28 @@ export function MapComponent({
8894
useFeatureState(mapRef, hoveredBoothId ? [hoveredBoothId] : [], "hover")
8995
useFeatureState(mapRef, filteredBoothIds, "filtered")
9096

91-
const activeBooth =
92-
activeBoothId != null ? boothsById.get(activeBoothId) : null
93-
94-
const currentGeoJsonBoothData = geoJsonBoothDataByLocation.get(location.id)!
97+
const currentGeoJsonBoothData = useMemo(() => {
98+
const currentData = geoJsonBoothDataByLocation.get(
99+
location.id === "library" ? preLocationId : location.id
100+
) ?? {
101+
type: "FeatureCollection",
102+
features: []
103+
}
104+
const libraryFeatures = geoJsonBoothDataByLocation.get("library")!.features
105+
// Merge library features with the current location's features
106+
const mergedFeatures = [
107+
...libraryFeatures,
108+
...currentData.features.filter(
109+
feature =>
110+
!libraryFeatures.some(
111+
libraryFeature =>
112+
libraryFeature.properties.id === feature.properties.id
113+
)
114+
)
115+
]
116+
setPreLocationId(location.id)
117+
return { ...currentData, features: mergedFeatures }
118+
}, [location.id])
95119

96120
// Don't want to rerender markers on every map render
97121
const markers = useMemo(
@@ -132,8 +156,9 @@ export function MapComponent({
132156
function onZoomChange() {
133157
const zoom = mapRef.current?.getZoom()
134158
if (zoom === undefined) return
135-
const scale = Math.max(0.3, Math.min(2, 1 + (zoom - 20) * 0.5))
159+
const scale = Math.max(0.2, Math.min(1, 1 + (zoom - 20) * 0.5))
136160
setMarkerScale(scale)
161+
setMapZoom(zoom)
137162
}
138163

139164
return (
@@ -153,7 +178,7 @@ export function MapComponent({
153178
[18.063, 59.345],
154179
[18.079, 59.35]
155180
]}
156-
mapStyle="https://api.maptiler.com/maps/977e9770-60b4-4b8a-94e9-a9fa8db4c68d/style.json?key=57xj41WPFBbOEWiVSSwL">
181+
mapStyle="https://api.maptiler.com/maps/376fa556-c405-4a91-8e9e-15be82eb3a58/style.json?key=mgMcr2yF2fWUHzf27ygv">
157182
<Layer {...backgroundLayerStyle}></Layer>
158183

159184
{/** Order sensitive! */}
@@ -182,31 +207,42 @@ export function MapComponent({
182207
</Source>
183208

184209
<Source
185-
id="nymble-plan-style"
210+
id="booths-outline"
186211
type="geojson"
187212
promoteId={"id"}
188-
data={geoJsonPlanData}>
189-
<Layer {...lineLayerStyle}></Layer>
190-
</Source>
191-
192-
<Source
193-
id="nymble-plan-routes"
194-
type="geojson"
195-
promoteId={"id"}
196-
data={geoJsonPlanRoutesData}>
197-
<Layer {...routeLayerStyle}></Layer>
213+
data={currentGeoJsonBoothData}>
214+
<Layer {...boothOutlineStyle}></Layer>
198215
</Source>
199216

200217
<Source
201-
id="nymble-plan-points"
218+
id="nymble-plan-style"
202219
type="geojson"
203220
promoteId={"id"}
204221
data={geoJsonPlanData}>
205-
<Layer {...symbolLayerStyle}></Layer>
222+
<Layer {...lineLayerStyle}></Layer>
206223
</Source>
207224

225+
{mapZoom > 19 && (
226+
<Source
227+
id="nymble-plan-routes"
228+
type="geojson"
229+
promoteId={"id"}
230+
data={geoJsonPlanRoutesData}>
231+
<Layer {...routeLayerStyle}></Layer>
232+
</Source>
233+
)}
234+
235+
{mapZoom > 19 && (
236+
<Source
237+
id="nymble-plan-points"
238+
type="geojson"
239+
promoteId={"id"}
240+
data={geoJsonPlanData}>
241+
<Layer {...symbolLayerStyle}></Layer>
242+
</Source>
243+
)}
244+
208245
{markers}
209-
{activeBooth && <BoothPopup key={activeBooth.id} booth={activeBooth} />}
210246
</MapboxMap>
211247
</div>
212248
)

0 commit comments

Comments
 (0)