feat: implement feedback filter for liked/favorited photos (Issue #17)
Implemented Feature Request 1 from github.com/the-luap/picpeak/issues/17: - Added filter functionality to display only liked or favorited photos - Integrated feedback filter directly into PhotoFilterBar component - Implemented responsive design with proper mobile/tablet/desktop layouts - Filter only shows when feedback is enabled for the gallery - Added proper count display for liked and favorited photos Improvements: - Fixed responsive breakpoints (mobile <768px, tablet 768-1023px, desktop ≥1024px) - Feedback filter shows inline with categories on desktop with vertical divider - On mobile/tablet, filter appears below categories to prevent layout issues - Added horizontal scrolling for category buttons to prevent cut-off Code cleanup: - Removed all debug console.log statements from production code - Removed test route from backend gallery.js - Cleaned up unnecessary logging in frontend components 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -97,12 +97,42 @@ router.get('/:slug/info', async (req, res) => {
|
||||
// Get all photos
|
||||
router.get('/:slug/photos', verifyGalleryAccess, async (req, res) => {
|
||||
try {
|
||||
// Get filter parameters from query
|
||||
const { filter, guest_id } = req.query;
|
||||
const feedbackService = require('../services/feedbackService');
|
||||
|
||||
// First get all photos
|
||||
const photos = await db('photos')
|
||||
let photos = await db('photos')
|
||||
.where('photos.event_id', req.event.id)
|
||||
.select('photos.*')
|
||||
.orderBy('photos.uploaded_at', 'desc');
|
||||
|
||||
// Apply filtering if requested
|
||||
if (filter && guest_id) {
|
||||
let filters = {};
|
||||
|
||||
// Parse filter parameter
|
||||
if (filter === 'liked') {
|
||||
filters.liked = true;
|
||||
} else if (filter === 'favorited') {
|
||||
filters.favorited = true;
|
||||
} else if (filter === 'liked,favorited' || filter === 'favorited,liked') {
|
||||
filters.liked = true;
|
||||
filters.favorited = true;
|
||||
filters.operator = 'OR';
|
||||
}
|
||||
|
||||
// Get filtered photo IDs
|
||||
const filteredPhotoIds = await feedbackService.getFilteredPhotos(
|
||||
req.event.id,
|
||||
guest_id,
|
||||
filters
|
||||
);
|
||||
|
||||
// Filter photos to only include those with feedback
|
||||
photos = photos.filter(photo => filteredPhotoIds.includes(photo.id));
|
||||
}
|
||||
|
||||
// Then get comment counts separately
|
||||
const commentCounts = await db('photo_feedback')
|
||||
.whereIn('photo_id', photos.map(p => p.id))
|
||||
@@ -150,13 +180,6 @@ router.get('/:slug/photos', verifyGalleryAccess, async (req, res) => {
|
||||
overlay_protection: req.event.overlay_protection !== false
|
||||
};
|
||||
|
||||
console.log('[Gallery Photos] Event data:', {
|
||||
id: req.event.id,
|
||||
slug: req.params.slug,
|
||||
protection_level: req.event.protection_level,
|
||||
calculated_protection: protectionSettings.protection_level,
|
||||
is_basic_or_standard: (protectionSettings.protection_level === 'basic' || protectionSettings.protection_level === 'standard')
|
||||
});
|
||||
|
||||
res.json({
|
||||
event: {
|
||||
@@ -181,8 +204,6 @@ router.get('/:slug/photos', verifyGalleryAccess, async (req, res) => {
|
||||
`/api/gallery/${req.params.slug}/photo/${photo.id}` :
|
||||
`/api/secure-images/${req.params.slug}/secure/${photo.id}/{{token}}`;
|
||||
|
||||
console.log(`[Photo ${photo.id}] Protection: ${protectionSettings.protection_level}, Use JWT: ${useJwtUrl}, URL: ${photoUrl}`);
|
||||
|
||||
return {
|
||||
id: photo.id,
|
||||
filename: photo.filename,
|
||||
@@ -363,14 +384,6 @@ router.get('/:slug/download-all', verifyGalleryAccess, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Test route
|
||||
router.get('/:slug/photo-test/:photoId',
|
||||
verifyGalleryAccess,
|
||||
(req, res) => {
|
||||
console.log('TEST ROUTE EXECUTED!');
|
||||
res.json({ message: 'Test route works!', photoId: req.params.photoId });
|
||||
}
|
||||
);
|
||||
|
||||
// View single photo (with watermark if enabled)
|
||||
router.get('/:slug/photo/:photoId',
|
||||
|
||||
@@ -390,6 +390,76 @@ class FeedbackService {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filtered photos based on feedback criteria
|
||||
* @param {number} eventId - Event ID
|
||||
* @param {string} guestIdentifier - Guest identifier
|
||||
* @param {object} filters - Filter criteria
|
||||
* @param {boolean} filters.liked - Include liked photos
|
||||
* @param {boolean} filters.favorited - Include favorited photos
|
||||
* @param {string} filters.operator - 'AND' or 'OR' for multiple filters
|
||||
* @returns {Promise<number[]>} Array of photo IDs that match criteria
|
||||
*/
|
||||
async getFilteredPhotos(eventId, guestIdentifier, filters = {}) {
|
||||
try {
|
||||
const { liked, favorited, operator = 'OR' } = filters;
|
||||
|
||||
// If no filters specified, return all photos
|
||||
if (!liked && !favorited) {
|
||||
const allPhotos = await db('photos')
|
||||
.where('event_id', eventId)
|
||||
.select('id');
|
||||
return allPhotos.map(p => p.id);
|
||||
}
|
||||
|
||||
// Build query based on filters
|
||||
let query = db('photo_feedback')
|
||||
.where('event_id', eventId)
|
||||
.where('guest_identifier', guestIdentifier)
|
||||
.where('is_hidden', false);
|
||||
|
||||
// Apply filter logic
|
||||
if (operator === 'AND' && liked && favorited) {
|
||||
// For AND operation, we need photos that have both types of feedback
|
||||
const likedPhotos = await db('photo_feedback')
|
||||
.where('event_id', eventId)
|
||||
.where('guest_identifier', guestIdentifier)
|
||||
.where('feedback_type', 'like')
|
||||
.where('is_hidden', false)
|
||||
.select('photo_id');
|
||||
|
||||
const favoritedPhotos = await db('photo_feedback')
|
||||
.where('event_id', eventId)
|
||||
.where('guest_identifier', guestIdentifier)
|
||||
.where('feedback_type', 'favorite')
|
||||
.where('is_hidden', false)
|
||||
.select('photo_id');
|
||||
|
||||
const likedIds = new Set(likedPhotos.map(p => p.photo_id));
|
||||
const favoritedIds = new Set(favoritedPhotos.map(p => p.photo_id));
|
||||
|
||||
// Return intersection of both sets
|
||||
return Array.from(likedIds).filter(id => favoritedIds.has(id));
|
||||
} else {
|
||||
// OR operation or single filter
|
||||
const feedbackTypes = [];
|
||||
if (liked) feedbackTypes.push('like');
|
||||
if (favorited) feedbackTypes.push('favorite');
|
||||
|
||||
query.whereIn('feedback_type', feedbackTypes);
|
||||
}
|
||||
|
||||
const filteredPhotos = await query
|
||||
.distinct('photo_id')
|
||||
.select('photo_id');
|
||||
|
||||
return filteredPhotos.map(p => p.photo_id);
|
||||
} catch (error) {
|
||||
logger.error('Error getting filtered photos:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new FeedbackService();
|
||||
Reference in New Issue
Block a user