diff --git a/src/main/java/net/javahippie/fitpub/controller/ActivityController.java b/src/main/java/net/javahippie/fitpub/controller/ActivityController.java index 1b66991..54b1bb7 100644 --- a/src/main/java/net/javahippie/fitpub/controller/ActivityController.java +++ b/src/main/java/net/javahippie/fitpub/controller/ActivityController.java @@ -352,9 +352,10 @@ public class ActivityController { @PathVariable String username, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size, + @RequestParam(required = false) Integer peakId, @AuthenticationPrincipal UserDetails userDetails ) { - log.debug("Retrieving public activities for user: {}", username); + log.debug("Retrieving public activities for user: {} (peakId: {})", username, peakId); // Get user by username User user = userRepository.findByUsername(username) @@ -366,6 +367,20 @@ public class ActivityController { // Get activity owner's privacy zones java.util.List privacyZones = privacyZoneService.getActivePrivacyZones(user.getId()); + // Filter by peak if requested + if (peakId != null) { + java.util.List activityIds = activityPeakRepository.findPublicActivityIdsByUserAndPeak(user.getId(), peakId); + java.util.List dtos = activityIds.stream() + .map(id -> fitFileService.getActivityById(id)) + .filter(java.util.Objects::nonNull) + .sorted((a, b) -> b.getStartedAt().compareTo(a.getStartedAt())) + .map(activity -> ActivityDTO.fromEntityWithFiltering(activity, requestingUserId, privacyZones, trackPrivacyFilter)) + .toList(); + org.springframework.data.domain.Pageable peakPageable = + org.springframework.data.domain.PageRequest.of(0, Math.max(dtos.size(), 1)); + return ResponseEntity.ok(new org.springframework.data.domain.PageImpl<>(dtos, peakPageable, dtos.size())); + } + // Get public activities only org.springframework.data.domain.Pageable pageable = org.springframework.data.domain.PageRequest.of(page, size, diff --git a/src/main/java/net/javahippie/fitpub/controller/UserController.java b/src/main/java/net/javahippie/fitpub/controller/UserController.java index 1c8658e..432c035 100644 --- a/src/main/java/net/javahippie/fitpub/controller/UserController.java +++ b/src/main/java/net/javahippie/fitpub/controller/UserController.java @@ -634,6 +634,7 @@ public class UserController { var result = projections.stream() .map(p -> { Map map = new java.util.LinkedHashMap<>(); + map.put("id", p.getPeakId()); map.put("name", p.getPeakName()); map.put("wikipedia", p.getWikipedia()); map.put("visitCount", p.getVisitCount()); diff --git a/src/main/java/net/javahippie/fitpub/repository/ActivityPeakRepository.java b/src/main/java/net/javahippie/fitpub/repository/ActivityPeakRepository.java index 67605df..f996576 100644 --- a/src/main/java/net/javahippie/fitpub/repository/ActivityPeakRepository.java +++ b/src/main/java/net/javahippie/fitpub/repository/ActivityPeakRepository.java @@ -16,6 +16,20 @@ public interface ActivityPeakRepository extends JpaRepository findPublicActivityIdsByUserAndPeak(UUID userId, Integer peakId); + /** * Find all unique peaks visited by a user with visit count and latest activity, * ordered by name. diff --git a/src/main/resources/templates/profile/public.html b/src/main/resources/templates/profile/public.html index 8604233..1d7b535 100644 --- a/src/main/resources/templates/profile/public.html +++ b/src/main/resources/templates/profile/public.html @@ -107,10 +107,13 @@
-
+
Public Activities
+
@@ -312,6 +315,14 @@ } let currentPage = 0; + let activePeakId = null; + + document.getElementById('clearPeakFilter').addEventListener('click', function() { + activePeakId = null; + currentPage = 0; + this.classList.add('d-none'); + loadPublicActivities(null, null); + }); async function loadPeaks() { try { @@ -325,16 +336,30 @@ const nameHtml = peak.wikipedia ? `${peak.name} ` : peak.name; - const visits = peak.visitCount > 1 ? `${peak.visitCount} visits` : '1 visit'; - const activityLink = peak.latestActivityId - ? ` · latest activity` - : ''; + const visitText = peak.visitCount > 1 ? `${peak.visitCount} visits` : '1 visit'; + const visitsLink = `${visitText}`; return `
  • ${nameHtml} - ${visits}${activityLink} + ${visitsLink}
  • `; }).join(''); document.getElementById('peaksSection').style.display = 'block'; + + // Wire up peak filter links + document.querySelectorAll('.peak-filter-link').forEach(link => { + link.addEventListener('click', function(e) { + e.preventDefault(); + const peakId = this.dataset.peakId; + const peakName = this.dataset.peakName; + currentPage = 0; + activePeakId = peakId; + const clearBtn = document.getElementById('clearPeakFilter'); + document.getElementById('clearPeakFilterLabel').textContent = `Clear filter: ${peakName}`; + clearBtn.classList.remove('d-none'); + loadPublicActivities(null, peakId); + document.getElementById('activitiesList').scrollIntoView({behavior: 'smooth'}); + }); + }); } } } catch (error) { @@ -342,9 +367,18 @@ } } - async function loadPublicActivities(userId) { + async function loadPublicActivities(userId, peakId) { try { - const response = await fetch(`/api/activities/user/${targetUsername}?page=${currentPage}&size=10`); + // Reset visibility + document.getElementById('activitiesLoading').classList.remove('d-none'); + document.getElementById('activitiesList').classList.add('d-none'); + document.getElementById('activitiesEmpty').classList.add('d-none'); + document.getElementById('pagination').classList.add('d-none'); + + let url = `/api/activities/user/${targetUsername}?page=${currentPage}&size=10`; + if (peakId) url += `&peakId=${peakId}`; + + const response = await fetch(url); if (response.ok) { const data = await response.json(); @@ -445,7 +479,7 @@ window.changePage = function(page) { currentPage = page; - loadPublicActivities(); + loadPublicActivities(null, activePeakId); window.scrollTo({ top: 0, behavior: 'smooth' }); }; diff --git a/src/main/resources/templates/profile/view.html b/src/main/resources/templates/profile/view.html index 890834b..337c4f8 100644 --- a/src/main/resources/templates/profile/view.html +++ b/src/main/resources/templates/profile/view.html @@ -233,12 +233,9 @@ ? `${peak.name} ` : peak.name; const visits = peak.visitCount > 1 ? `${peak.visitCount} visits` : '1 visit'; - const activityLink = peak.latestActivityId - ? ` · latest activity` - : ''; return `
  • ${nameHtml} - ${visits}${activityLink} + ${visits}
  • `; }).join(''); document.getElementById('peaksSection').style.display = 'block';