Peak filters for users activity
This commit is contained in:
parent
db025e42c3
commit
aa7a7bc9fc
5 changed files with 75 additions and 14 deletions
|
|
@ -352,9 +352,10 @@ public class ActivityController {
|
||||||
@PathVariable String username,
|
@PathVariable String username,
|
||||||
@RequestParam(defaultValue = "0") int page,
|
@RequestParam(defaultValue = "0") int page,
|
||||||
@RequestParam(defaultValue = "10") int size,
|
@RequestParam(defaultValue = "10") int size,
|
||||||
|
@RequestParam(required = false) Integer peakId,
|
||||||
@AuthenticationPrincipal UserDetails userDetails
|
@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
|
// Get user by username
|
||||||
User user = userRepository.findByUsername(username)
|
User user = userRepository.findByUsername(username)
|
||||||
|
|
@ -366,6 +367,20 @@ public class ActivityController {
|
||||||
// Get activity owner's privacy zones
|
// Get activity owner's privacy zones
|
||||||
java.util.List<PrivacyZone> privacyZones = privacyZoneService.getActivePrivacyZones(user.getId());
|
java.util.List<PrivacyZone> privacyZones = privacyZoneService.getActivePrivacyZones(user.getId());
|
||||||
|
|
||||||
|
// Filter by peak if requested
|
||||||
|
if (peakId != null) {
|
||||||
|
java.util.List<UUID> activityIds = activityPeakRepository.findPublicActivityIdsByUserAndPeak(user.getId(), peakId);
|
||||||
|
java.util.List<ActivityDTO> 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
|
// Get public activities only
|
||||||
org.springframework.data.domain.Pageable pageable =
|
org.springframework.data.domain.Pageable pageable =
|
||||||
org.springframework.data.domain.PageRequest.of(page, size,
|
org.springframework.data.domain.PageRequest.of(page, size,
|
||||||
|
|
|
||||||
|
|
@ -634,6 +634,7 @@ public class UserController {
|
||||||
var result = projections.stream()
|
var result = projections.stream()
|
||||||
.map(p -> {
|
.map(p -> {
|
||||||
Map<String, Object> map = new java.util.LinkedHashMap<>();
|
Map<String, Object> map = new java.util.LinkedHashMap<>();
|
||||||
|
map.put("id", p.getPeakId());
|
||||||
map.put("name", p.getPeakName());
|
map.put("name", p.getPeakName());
|
||||||
map.put("wikipedia", p.getWikipedia());
|
map.put("wikipedia", p.getWikipedia());
|
||||||
map.put("visitCount", p.getVisitCount());
|
map.put("visitCount", p.getVisitCount());
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,20 @@ public interface ActivityPeakRepository extends JpaRepository<ActivityPeak, Inte
|
||||||
|
|
||||||
boolean existsByActivityIdAndPeakId(UUID activityId, Integer peakId);
|
boolean existsByActivityIdAndPeakId(UUID activityId, Integer peakId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all public activity IDs for a user that include a specific peak,
|
||||||
|
* ordered by start time descending.
|
||||||
|
*/
|
||||||
|
@Query("""
|
||||||
|
SELECT ap.activityId FROM ActivityPeak ap
|
||||||
|
WHERE ap.peak.id = :peakId
|
||||||
|
AND ap.activityId IN (
|
||||||
|
SELECT a.id FROM Activity a
|
||||||
|
WHERE a.userId = :userId AND a.visibility = 'PUBLIC'
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
List<UUID> findPublicActivityIdsByUserAndPeak(UUID userId, Integer peakId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all unique peaks visited by a user with visit count and latest activity,
|
* Find all unique peaks visited by a user with visit count and latest activity,
|
||||||
* ordered by name.
|
* ordered by name.
|
||||||
|
|
|
||||||
|
|
@ -107,10 +107,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<h5 class="mb-0">
|
<h5 class="mb-0">
|
||||||
<i class="bi bi-list-task"></i> Public Activities
|
<i class="bi bi-list-task"></i> Public Activities
|
||||||
</h5>
|
</h5>
|
||||||
|
<button type="button" id="clearPeakFilter" class="btn btn-sm btn-outline-secondary d-none">
|
||||||
|
<i class="bi bi-x-lg"></i> <span id="clearPeakFilterLabel">Clear filter</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<!-- Loading Indicator for Activities -->
|
<!-- Loading Indicator for Activities -->
|
||||||
|
|
@ -312,6 +315,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentPage = 0;
|
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() {
|
async function loadPeaks() {
|
||||||
try {
|
try {
|
||||||
|
|
@ -325,16 +336,30 @@
|
||||||
const nameHtml = peak.wikipedia
|
const nameHtml = peak.wikipedia
|
||||||
? `<a href="${peak.wikipedia}" target="_blank" rel="noopener">${peak.name} <i class="bi bi-box-arrow-up-right small"></i></a>`
|
? `<a href="${peak.wikipedia}" target="_blank" rel="noopener">${peak.name} <i class="bi bi-box-arrow-up-right small"></i></a>`
|
||||||
: peak.name;
|
: peak.name;
|
||||||
const visits = peak.visitCount > 1 ? `${peak.visitCount} visits` : '1 visit';
|
const visitText = peak.visitCount > 1 ? `${peak.visitCount} visits` : '1 visit';
|
||||||
const activityLink = peak.latestActivityId
|
const visitsLink = `<a href="#" class="peak-filter-link" data-peak-id="${peak.id}" data-peak-name="${peak.name.replace(/"/g, '"')}">${visitText}</a>`;
|
||||||
? ` · <a href="/activities/${peak.latestActivityId}">latest activity</a>`
|
|
||||||
: '';
|
|
||||||
return `<li class="list-group-item d-flex justify-content-between align-items-center">
|
return `<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
<span>${nameHtml}</span>
|
<span>${nameHtml}</span>
|
||||||
<span class="text-muted small">${visits}${activityLink}</span>
|
<span class="text-muted small">${visitsLink}</span>
|
||||||
</li>`;
|
</li>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
document.getElementById('peaksSection').style.display = 'block';
|
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) {
|
} catch (error) {
|
||||||
|
|
@ -342,9 +367,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadPublicActivities(userId) {
|
async function loadPublicActivities(userId, peakId) {
|
||||||
try {
|
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) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
@ -445,7 +479,7 @@
|
||||||
|
|
||||||
window.changePage = function(page) {
|
window.changePage = function(page) {
|
||||||
currentPage = page;
|
currentPage = page;
|
||||||
loadPublicActivities();
|
loadPublicActivities(null, activePeakId);
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -233,12 +233,9 @@
|
||||||
? `<a href="${peak.wikipedia}" target="_blank" rel="noopener">${peak.name} <i class="bi bi-box-arrow-up-right small"></i></a>`
|
? `<a href="${peak.wikipedia}" target="_blank" rel="noopener">${peak.name} <i class="bi bi-box-arrow-up-right small"></i></a>`
|
||||||
: peak.name;
|
: peak.name;
|
||||||
const visits = peak.visitCount > 1 ? `${peak.visitCount} visits` : '1 visit';
|
const visits = peak.visitCount > 1 ? `${peak.visitCount} visits` : '1 visit';
|
||||||
const activityLink = peak.latestActivityId
|
|
||||||
? ` · <a href="/activities/${peak.latestActivityId}">latest activity</a>`
|
|
||||||
: '';
|
|
||||||
return `<li class="list-group-item d-flex justify-content-between align-items-center">
|
return `<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
<span>${nameHtml}</span>
|
<span>${nameHtml}</span>
|
||||||
<span class="text-muted small">${visits}${activityLink}</span>
|
<span class="text-muted small">${visits}</span>
|
||||||
</li>`;
|
</li>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
document.getElementById('peaksSection').style.display = 'block';
|
document.getElementById('peaksSection').style.display = 'block';
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue