From 13796a6b611838506aadd15a4952e451335865c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lipt=C3=A1k=20P=C3=A9ter?= Date: Thu, 4 Jun 2026 15:46:38 +0200 Subject: [PATCH 1/4] feat: enhance map functionality with fullscreen toggle and user location display --- .../src/common-components/map/MapContent.tsx | 44 ++++++++++++------- frontend/src/pages/map/map.page.tsx | 38 +++++++++++++--- 2 files changed, 60 insertions(+), 22 deletions(-) diff --git a/frontend/src/common-components/map/MapContent.tsx b/frontend/src/common-components/map/MapContent.tsx index 24227994..3f30191f 100644 --- a/frontend/src/common-components/map/MapContent.tsx +++ b/frontend/src/common-components/map/MapContent.tsx @@ -2,16 +2,18 @@ import { useToast } from '@/hooks/use-toast' import { l } from '@/util/language' import { type MapDataItemView, MapMarkerShape } from '@/util/views/map.view' import { Map, Marker, ZoomControl } from 'pigeon-maps' -import { useEffect } from 'react' +import { useEffect, useMemo } from 'react' import { useGeolocated } from 'react-geolocated' import { MapMarker } from './MapMarker' interface MapContentProps { showUserLocation: boolean mapData: MapDataItemView[] + className?: string + height?: number } -export function MapContent({ showUserLocation, mapData }: MapContentProps) { +export function MapContent({ showUserLocation, mapData, className, height = 400 }: MapContentProps) { const { toast } = useToast() const { coords, getPosition, isGeolocationAvailable, isGeolocationEnabled } = useGeolocated({ @@ -22,7 +24,15 @@ export function MapContent({ showUserLocation, mapData }: MapContentProps) { suppressLocationOnMount: true, watchPosition: showUserLocation }) - const center: [number, number] = coords ? [coords.latitude, coords.longitude] : [47.47303, 19.0531] + + const markersCenter = useMemo(() => { + if (!mapData || mapData.length === 0) return null + const latSum = mapData.reduce((acc, curr) => acc + curr.latitude, 0) + const lonSum = mapData.reduce((acc, curr) => acc + curr.longitude, 0) + return [latSum / mapData.length, lonSum / mapData.length] as [number, number] + }, [mapData]) + + const center: [number, number] = (showUserLocation && coords) ? [coords.latitude, coords.longitude] : (markersCenter ?? [47.47303, 19.0531]) useEffect(() => { if (showUserLocation) getPosition() @@ -35,19 +45,21 @@ export function MapContent({ showUserLocation, mapData }: MapContentProps) { }, [showUserLocation, isGeolocationAvailable, isGeolocationEnabled, toast]) return ( - - - {mapData.map((mapDataItem) => ( - - - - ))} - {coords && ( - - - - )} - +
+ + + {mapData.map((mapDataItem) => ( + + + + ))} + {showUserLocation && coords && ( + + + + )} + +
) } diff --git a/frontend/src/pages/map/map.page.tsx b/frontend/src/pages/map/map.page.tsx index a5349963..4ce547f7 100644 --- a/frontend/src/pages/map/map.page.tsx +++ b/frontend/src/pages/map/map.page.tsx @@ -6,10 +6,12 @@ import Markdown from '@/common-components/Markdown' import { Checkbox } from '@/components/ui/checkbox' import { Label } from '@/components/ui/label' import { l } from '@/util/language' +import { X } from 'lucide-react' import { useState } from 'react' export default function MapPage() { const [showUserLocation, setShowUserLocation] = useState(false) + const [fullScreen, setFullScreen] = useState(false) const locationQuery = useLocationQuery() const component = useConfigContext()?.components?.location @@ -21,13 +23,37 @@ export default function MapPage() { )} -
- setShowUserLocation(!!checked)} /> - +
+
+ setShowUserLocation(!!checked)} /> + +
+
+ setFullScreen(!!checked)} /> + +
+
+
+ {fullScreen && ( + + )} +
-

{l('location-description')}

{l('location-privacy')}

{component?.bottomMessage && ( From 0abdc601e592f0275cf0ed331eee87e7ea84ae68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lipt=C3=A1k=20P=C3=A9ter?= Date: Thu, 4 Jun 2026 16:01:36 +0200 Subject: [PATCH 2/4] feat: refactor MapContent center calculation and enhance fullscreen layout with logo --- .../src/common-components/map/MapContent.tsx | 3 +- frontend/src/pages/map/map.page.tsx | 38 +++++++++++++------ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/frontend/src/common-components/map/MapContent.tsx b/frontend/src/common-components/map/MapContent.tsx index 3f30191f..d23d36ad 100644 --- a/frontend/src/common-components/map/MapContent.tsx +++ b/frontend/src/common-components/map/MapContent.tsx @@ -32,7 +32,8 @@ export function MapContent({ showUserLocation, mapData, className, height = 400 return [latSum / mapData.length, lonSum / mapData.length] as [number, number] }, [mapData]) - const center: [number, number] = (showUserLocation && coords) ? [coords.latitude, coords.longitude] : (markersCenter ?? [47.47303, 19.0531]) + const fallbackCenter: [number, number] = markersCenter ?? [47.47303, 19.0531] + const center: [number, number] = showUserLocation && coords ? [coords.latitude, coords.longitude] : fallbackCenter useEffect(() => { if (showUserLocation) getPosition() diff --git a/frontend/src/pages/map/map.page.tsx b/frontend/src/pages/map/map.page.tsx index 4ce547f7..a8bad1ca 100644 --- a/frontend/src/pages/map/map.page.tsx +++ b/frontend/src/pages/map/map.page.tsx @@ -1,10 +1,12 @@ import { useConfigContext } from '@/api/contexts/config/ConfigContext' import { useLocationQuery } from '@/api/hooks/location/useLocationQuery' +import { KirDevLogo } from '@/assets/kir-dev-logo' import { CmschPage } from '@/common-components/layout/CmschPage' import { MapContent } from '@/common-components/map/MapContent' import Markdown from '@/common-components/Markdown' import { Checkbox } from '@/components/ui/checkbox' import { Label } from '@/components/ui/label' +import { cn } from '@/lib/utils' import { l } from '@/util/language' import { X } from 'lucide-react' import { useState } from 'react' @@ -13,7 +15,9 @@ export default function MapPage() { const [showUserLocation, setShowUserLocation] = useState(false) const [fullScreen, setFullScreen] = useState(false) const locationQuery = useLocationQuery() - const component = useConfigContext()?.components?.location + const config = useConfigContext() + const component = config?.components?.location + const devWebsiteUrl = config?.components?.footer?.devWebsiteUrl || 'https://kir-dev.hu/project/cmsch' return ( @@ -37,21 +41,33 @@ export default function MapPage() {
-
+
{fullScreen && ( - + <> + +
+ + + +
+ )}

{l('location-description')}

From db2137d2afc8ad21ae8e3abc89a447e8f0db20be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lipt=C3=A1k=20P=C3=A9ter?= Date: Thu, 4 Jun 2026 16:28:19 +0200 Subject: [PATCH 3/4] feat: update location handling to associate user ID with location entities and filter by group ID --- .../component/location/LocationService.kt | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/backend/src/main/kotlin/hu/bme/sch/cmsch/component/location/LocationService.kt b/backend/src/main/kotlin/hu/bme/sch/cmsch/component/location/LocationService.kt index db0b5645..d767c574 100644 --- a/backend/src/main/kotlin/hu/bme/sch/cmsch/component/location/LocationService.kt +++ b/backend/src/main/kotlin/hu/bme/sch/cmsch/component/location/LocationService.kt @@ -37,7 +37,7 @@ class LocationService( if (userEntity.role.value >= RoleType.STAFF.value) { tokenToLocationMapping[locationDto.token] = LocationEntity( - id = 0, + id = userEntity.id, userId = userEntity.id, userName = userEntity.fullName, alias = userEntity.alias, @@ -113,16 +113,23 @@ class LocationService( .forEach { token -> tokenToLocationMapping[token]?.let { val user = userRepository.findByCmschId(startupPropertyConfig.profileQrPrefix + token) - it.userId = user.get().id - it.userName = user.get().fullName - it.alias = user.get().alias - it.groupName = user.get().groupName + if (user.isPresent) { + val u = user.get() + it.id = u.id + it.userId = u.id + it.userName = u.fullName + it.alias = u.alias + it.groupName = u.groupName + } } } } fun findLocationsOfGroup(groupId: Int): List { - return tokenToLocationMapping.values.filter { it.id == groupId } + return tokenToLocationMapping.values.filter { + val user = userRepository.findById(it.userId) + user.isPresent && user.get().group?.id == groupId + } } fun findLocationsOfGroupName(group: String): List { From ca47dca89837437f2c31a00a0dddfcbd4dba9a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lipt=C3=A1k=20P=C3=A9ter?= Date: Thu, 4 Jun 2026 16:52:39 +0200 Subject: [PATCH 4/4] feat: improve location filtering by group ID and enhance fullscreen height handling in map page --- .../cmsch/component/location/LocationService.kt | 14 +++++++++++--- frontend/src/common-components/map/MapContent.tsx | 8 +++++++- frontend/src/pages/map/map.page.tsx | 14 ++++++++++++-- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/backend/src/main/kotlin/hu/bme/sch/cmsch/component/location/LocationService.kt b/backend/src/main/kotlin/hu/bme/sch/cmsch/component/location/LocationService.kt index d767c574..27e22ab7 100644 --- a/backend/src/main/kotlin/hu/bme/sch/cmsch/component/location/LocationService.kt +++ b/backend/src/main/kotlin/hu/bme/sch/cmsch/component/location/LocationService.kt @@ -126,10 +126,18 @@ class LocationService( } fun findLocationsOfGroup(groupId: Int): List { - return tokenToLocationMapping.values.filter { - val user = userRepository.findById(it.userId) - user.isPresent && user.get().group?.id == groupId + val locations = tokenToLocationMapping.values.toList() + if (locations.isEmpty()) return emptyList() + + val userIds = locations.map { it.userId } + val userIdsInGroup = transactionManager.transaction(readOnly = true) { + userRepository.findAllById(userIds) + .filter { it.group?.id == groupId } + .map { it.id } + .toSet() } + + return locations.filter { it.userId in userIdsInGroup } } fun findLocationsOfGroupName(group: String): List { diff --git a/frontend/src/common-components/map/MapContent.tsx b/frontend/src/common-components/map/MapContent.tsx index d23d36ad..040210a3 100644 --- a/frontend/src/common-components/map/MapContent.tsx +++ b/frontend/src/common-components/map/MapContent.tsx @@ -50,7 +50,13 @@ export function MapContent({ showUserLocation, mapData, className, height = 400 {mapData.map((mapDataItem) => ( - + ))} diff --git a/frontend/src/pages/map/map.page.tsx b/frontend/src/pages/map/map.page.tsx index a8bad1ca..28af9c01 100644 --- a/frontend/src/pages/map/map.page.tsx +++ b/frontend/src/pages/map/map.page.tsx @@ -9,16 +9,26 @@ import { Label } from '@/components/ui/label' import { cn } from '@/lib/utils' import { l } from '@/util/language' import { X } from 'lucide-react' -import { useState } from 'react' +import { useEffect, useState } from 'react' export default function MapPage() { const [showUserLocation, setShowUserLocation] = useState(false) const [fullScreen, setFullScreen] = useState(false) + const [fullscreenHeight, setFullscreenHeight] = useState(() => (typeof window !== 'undefined' ? window.innerHeight : 800)) const locationQuery = useLocationQuery() const config = useConfigContext() const component = config?.components?.location const devWebsiteUrl = config?.components?.footer?.devWebsiteUrl || 'https://kir-dev.hu/project/cmsch' + useEffect(() => { + if (typeof window === 'undefined') return + const handleResize = () => { + setFullscreenHeight(window.innerHeight) + } + window.addEventListener('resize', handleResize) + return () => window.removeEventListener('resize', handleResize) + }, []) + return (

Térkép

@@ -67,7 +77,7 @@ export default function MapPage() { mapData={locationQuery.data ?? []} showUserLocation={showUserLocation} className={fullScreen ? 'h-screen w-screen' : ''} - height={fullScreen ? (typeof window !== 'undefined' ? window.innerHeight : 800) : 400} + height={fullScreen ? fullscreenHeight : 400} />

{l('location-description')}