From dffe057772c922ab6a213e25f171157e0c2badf8 Mon Sep 17 00:00:00 2001 From: Paul Nothaft Date: Sun, 12 Apr 2026 10:05:24 +0200 Subject: [PATCH] fix: apply sort direction in gallery view and respect show_feedback_to_guests (#302, #303) - Gallery now respects the configured sort direction (asc/desc) from default_photo_sort setting instead of using hard-coded directions - Photos endpoint zeroes out feedback fields (like_count, favorite_count, average_rating, comment_count, has_feedback) when show_feedback_to_guests is disabled, while still showing data to admin/client users --- backend/src/routes/gallery.js | 17 ++++++---- .../src/components/gallery/GalleryView.tsx | 31 +++++++++++-------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/backend/src/routes/gallery.js b/backend/src/routes/gallery.js index 716b085a..8c464591 100644 --- a/backend/src/routes/gallery.js +++ b/backend/src/routes/gallery.js @@ -311,6 +311,11 @@ router.get('/:slug/photos', verifyGalleryAccess, async (req, res) => { } } + // Check if feedback should be visible to guests + const feedbackService = require('../services/feedbackService'); + const feedbackSettings = await feedbackService.getEventFeedbackSettings(req.event.id); + const showFeedbackToGuests = isClient || feedbackSettings.show_feedback_to_guests !== false; + // Then get comment counts separately const commentCounts = await db('photo_feedback') .whereIn('photo_id', photos.map(p => p.id)) @@ -437,12 +442,12 @@ router.get('/:slug/photos', verifyGalleryAccess, async (req, res) => { media_type: photo.media_type || null, mime_type: photo.mime_type || null, duration: photo.duration || null, - // Feedback data - has_feedback: (commentMap[photo.id] > 0 || photo.average_rating > 0 || photo.like_count > 0), - average_rating: photo.average_rating || 0, - comment_count: commentMap[photo.id] || 0, - like_count: photo.like_count || 0, - favorite_count: photo.favorite_count || 0, + // Feedback data (hidden when show_feedback_to_guests is disabled) + has_feedback: showFeedbackToGuests ? (commentMap[photo.id] > 0 || photo.average_rating > 0 || photo.like_count > 0) : false, + average_rating: showFeedbackToGuests ? (photo.average_rating || 0) : 0, + comment_count: showFeedbackToGuests ? (commentMap[photo.id] || 0) : 0, + like_count: showFeedbackToGuests ? (photo.like_count || 0) : 0, + favorite_count: showFeedbackToGuests ? (photo.favorite_count || 0) : 0, // Visibility (only included for clients) ...(isClient ? { visibility: photo.visibility || 'visible' } : {}) }; diff --git a/frontend/src/components/gallery/GalleryView.tsx b/frontend/src/components/gallery/GalleryView.tsx index 3eb8764c..e790c3df 100644 --- a/frontend/src/components/gallery/GalleryView.tsx +++ b/frontend/src/components/gallery/GalleryView.tsx @@ -72,6 +72,7 @@ export const GalleryView: React.FC = ({ slug, event }) => { const [selectedCategoryId, setSelectedCategoryId] = useState(null); const [searchTerm, setSearchTerm] = useState(''); const [sortBy, setSortBy] = useState<'date' | 'name' | 'size' | 'rating' | 'capture_date'>('date'); + const [sortDesc, setSortDesc] = useState(true); const [defaultSortApplied, setDefaultSortApplied] = useState(false); const [brandingSettings, setBrandingSettings] = useState(null); const [showUploadModal, setShowUploadModal] = useState(false); @@ -129,8 +130,9 @@ export const GalleryView: React.FC = ({ slug, event }) => { // Apply default photo sort from event settings useEffect(() => { if (!defaultSortApplied && data?.event?.default_photo_sort) { - const { sortBy: defaultSortBy } = parseDefaultPhotoSort(data.event.default_photo_sort); + const { sortBy: defaultSortBy, sortDesc: defaultSortDesc } = parseDefaultPhotoSort(data.event.default_photo_sort); setSortBy(defaultSortBy); + setSortDesc(defaultSortDesc); setDefaultSortApplied(true); } }, [data?.event?.default_photo_sort, defaultSortApplied]); @@ -447,29 +449,32 @@ export const GalleryView: React.FC = ({ slug, event }) => { } // Apply sorting + // Each comparator defaults to its natural order (desc for dates/size/rating, asc for name). + // The flip multiplier reverses that when sortDesc differs from the natural order. + const flip = sortDesc ? 1 : -1; photos.sort((a, b) => { switch (sortBy) { case 'name': - return a.filename.localeCompare(b.filename); + // Natural order is ascending (A-Z); flip when sortDesc=true + return (sortDesc ? -1 : 1) * a.filename.localeCompare(b.filename); case 'size': - return b.size - a.size; - case 'rating': - // Sort by rating (highest first), then by comment count + return flip * (b.size - a.size); + case 'rating': { const ratingA = a.average_rating || 0; const ratingB = b.average_rating || 0; if (ratingA !== ratingB) { - return ratingB - ratingA; + return flip * (ratingB - ratingA); } - // If ratings are equal, sort by comment count - return (b.comment_count || 0) - (a.comment_count || 0); - case 'capture_date': - // Sort by capture date (from EXIF), fall back to upload date + return flip * ((b.comment_count || 0) - (a.comment_count || 0)); + } + case 'capture_date': { const captureDateA = a.captured_at || a.uploaded_at; const captureDateB = b.captured_at || b.uploaded_at; - return new Date(captureDateB).getTime() - new Date(captureDateA).getTime(); + return flip * (new Date(captureDateB).getTime() - new Date(captureDateA).getTime()); + } case 'date': default: - return new Date(b.uploaded_at).getTime() - new Date(a.uploaded_at).getTime(); + return flip * (new Date(b.uploaded_at).getTime() - new Date(a.uploaded_at).getTime()); } }); @@ -483,7 +488,7 @@ export const GalleryView: React.FC = ({ slug, event }) => { } return photos; - }, [data?.photos, selectedCategoryId, searchTerm, sortBy, watermarkEnabled, slug, filterType, mediaFilter]); + }, [data?.photos, selectedCategoryId, searchTerm, sortBy, sortDesc, watermarkEnabled, slug, filterType, mediaFilter]); const likeCount = useMemo( () => data?.photos?.filter(p => (p.like_count ?? 0) > 0).length || 0,