From b03760ab01e21feb3578f90d065945d437d03452 Mon Sep 17 00:00:00 2001 From: paul Date: Tue, 16 Sep 2025 10:11:08 +0200 Subject: [PATCH] feat(gallery/filters): add Rated and Commented filters (UI + backend).\n\n- UI: add star (Rated) and message (Commented) buttons to feedback filter bars (desktop + mobile)\n- Backend: support filter=rated, commented, and combinations via aggregate counts/queries --- backend/src/routes/gallery.js | 35 ++++++++++++++--- .../src/components/gallery/GalleryFilter.tsx | 28 ++++++++++++-- .../src/components/gallery/PhotoFilterBar.tsx | 38 ++++++++++++++++++- 3 files changed, 90 insertions(+), 11 deletions(-) diff --git a/backend/src/routes/gallery.js b/backend/src/routes/gallery.js index caa6e7f..e64105d 100644 --- a/backend/src/routes/gallery.js +++ b/backend/src/routes/gallery.js @@ -110,12 +110,35 @@ router.get('/:slug/photos', verifyGalleryAccess, async (req, res) => { // Apply filtering if requested (global, based on aggregate counts) if (filter) { const f = String(filter).toLowerCase(); - if (f === 'liked') { - photos = photos.filter(p => (p.like_count || 0) > 0); - } else if (f === 'favorited') { - photos = photos.filter(p => (p.favorite_count || 0) > 0); - } else if (f === 'liked,favorited' || f === 'favorited,liked') { - photos = photos.filter(p => (p.like_count || 0) > 0 || (p.favorite_count || 0) > 0); + const parts = f.split(',').map(s => s.trim()); + const include = new Set(); + + // Helper to include IDs for a predicate + const includeBy = (predicate) => { + photos.forEach(p => { if (predicate(p)) include.add(p.id); }); + }; + + if (parts.includes('liked')) { + includeBy(p => (p.like_count || 0) > 0); + } + if (parts.includes('favorited')) { + includeBy(p => (p.favorite_count || 0) > 0); + } + if (parts.includes('rated')) { + includeBy(p => (p.average_rating || 0) > 0); + } + if (parts.includes('commented')) { + // Query commented photo IDs + const commented = await db('photo_feedback') + .where({ event_id: req.event.id, feedback_type: 'comment', is_approved: true, is_hidden: false }) + .groupBy('photo_id') + .select('photo_id'); + const commentedIds = new Set(commented.map(c => c.photo_id)); + includeBy(p => commentedIds.has(p.id)); + } + + if (include.size > 0) { + photos = photos.filter(p => include.has(p.id)); } } diff --git a/frontend/src/components/gallery/GalleryFilter.tsx b/frontend/src/components/gallery/GalleryFilter.tsx index 2b83034..68db24c 100644 --- a/frontend/src/components/gallery/GalleryFilter.tsx +++ b/frontend/src/components/gallery/GalleryFilter.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import { Heart, Star } from 'lucide-react'; +import { Heart, Star, Bookmark, MessageSquare } from 'lucide-react'; import { Button } from '../common'; import { useTranslation } from 'react-i18next'; -export type FilterType = 'all' | 'liked' | 'favorited'; +export type FilterType = 'all' | 'liked' | 'favorited' | 'rated' | 'commented'; interface GalleryFilterProps { currentFilter: FilterType; @@ -150,14 +150,34 @@ export const GalleryFilter: React.FC = ({ onClick={() => onFilterChange('favorited')} className="text-xs sm:text-sm flex items-center gap-1" > - - {t('gallery.favorited', 'Favorites')} + + {t('gallery.favorites', 'Favorites')} {favoriteCount > 0 && ( {favoriteCount} )} + + + + )} diff --git a/frontend/src/components/gallery/PhotoFilterBar.tsx b/frontend/src/components/gallery/PhotoFilterBar.tsx index 9ef1a6b..c09c0ce 100644 --- a/frontend/src/components/gallery/PhotoFilterBar.tsx +++ b/frontend/src/components/gallery/PhotoFilterBar.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Search, SortAsc, Grid, Heart, Star } from 'lucide-react'; +import { Search, SortAsc, Grid, Heart, Star, Bookmark, MessageSquare } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { Button, Input } from '../common'; import type { FilterType } from './GalleryFilter'; @@ -205,9 +205,27 @@ export const PhotoFilterBar: React.FC = ({ onClick={() => onFilterChange('favorited')} className="p-1 w-8 h-8 flex items-center justify-center" aria-label={t('feedback.favorites', 'Favorites')} + > + + + + )} @@ -249,9 +267,27 @@ export const PhotoFilterBar: React.FC = ({ onClick={() => onFilterChange('favorited')} className="p-1 w-8 h-8 flex items-center justify-center" aria-label={t('feedback.favorites', 'Favorites')} + > + + + + )}