- 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
This commit is contained in:
@@ -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
|
// Then get comment counts separately
|
||||||
const commentCounts = await db('photo_feedback')
|
const commentCounts = await db('photo_feedback')
|
||||||
.whereIn('photo_id', photos.map(p => p.id))
|
.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,
|
media_type: photo.media_type || null,
|
||||||
mime_type: photo.mime_type || null,
|
mime_type: photo.mime_type || null,
|
||||||
duration: photo.duration || null,
|
duration: photo.duration || null,
|
||||||
// Feedback data
|
// Feedback data (hidden when show_feedback_to_guests is disabled)
|
||||||
has_feedback: (commentMap[photo.id] > 0 || photo.average_rating > 0 || photo.like_count > 0),
|
has_feedback: showFeedbackToGuests ? (commentMap[photo.id] > 0 || photo.average_rating > 0 || photo.like_count > 0) : false,
|
||||||
average_rating: photo.average_rating || 0,
|
average_rating: showFeedbackToGuests ? (photo.average_rating || 0) : 0,
|
||||||
comment_count: commentMap[photo.id] || 0,
|
comment_count: showFeedbackToGuests ? (commentMap[photo.id] || 0) : 0,
|
||||||
like_count: photo.like_count || 0,
|
like_count: showFeedbackToGuests ? (photo.like_count || 0) : 0,
|
||||||
favorite_count: photo.favorite_count || 0,
|
favorite_count: showFeedbackToGuests ? (photo.favorite_count || 0) : 0,
|
||||||
// Visibility (only included for clients)
|
// Visibility (only included for clients)
|
||||||
...(isClient ? { visibility: photo.visibility || 'visible' } : {})
|
...(isClient ? { visibility: photo.visibility || 'visible' } : {})
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ export const GalleryView: React.FC<GalleryViewProps> = ({ slug, event }) => {
|
|||||||
const [selectedCategoryId, setSelectedCategoryId] = useState<number | string | null>(null);
|
const [selectedCategoryId, setSelectedCategoryId] = useState<number | string | null>(null);
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [sortBy, setSortBy] = useState<'date' | 'name' | 'size' | 'rating' | 'capture_date'>('date');
|
const [sortBy, setSortBy] = useState<'date' | 'name' | 'size' | 'rating' | 'capture_date'>('date');
|
||||||
|
const [sortDesc, setSortDesc] = useState(true);
|
||||||
const [defaultSortApplied, setDefaultSortApplied] = useState(false);
|
const [defaultSortApplied, setDefaultSortApplied] = useState(false);
|
||||||
const [brandingSettings, setBrandingSettings] = useState<any>(null);
|
const [brandingSettings, setBrandingSettings] = useState<any>(null);
|
||||||
const [showUploadModal, setShowUploadModal] = useState(false);
|
const [showUploadModal, setShowUploadModal] = useState(false);
|
||||||
@@ -129,8 +130,9 @@ export const GalleryView: React.FC<GalleryViewProps> = ({ slug, event }) => {
|
|||||||
// Apply default photo sort from event settings
|
// Apply default photo sort from event settings
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!defaultSortApplied && data?.event?.default_photo_sort) {
|
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);
|
setSortBy(defaultSortBy);
|
||||||
|
setSortDesc(defaultSortDesc);
|
||||||
setDefaultSortApplied(true);
|
setDefaultSortApplied(true);
|
||||||
}
|
}
|
||||||
}, [data?.event?.default_photo_sort, defaultSortApplied]);
|
}, [data?.event?.default_photo_sort, defaultSortApplied]);
|
||||||
@@ -447,29 +449,32 @@ export const GalleryView: React.FC<GalleryViewProps> = ({ slug, event }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply sorting
|
// 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) => {
|
photos.sort((a, b) => {
|
||||||
switch (sortBy) {
|
switch (sortBy) {
|
||||||
case 'name':
|
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':
|
case 'size':
|
||||||
return b.size - a.size;
|
return flip * (b.size - a.size);
|
||||||
case 'rating':
|
case 'rating': {
|
||||||
// Sort by rating (highest first), then by comment count
|
|
||||||
const ratingA = a.average_rating || 0;
|
const ratingA = a.average_rating || 0;
|
||||||
const ratingB = b.average_rating || 0;
|
const ratingB = b.average_rating || 0;
|
||||||
if (ratingA !== ratingB) {
|
if (ratingA !== ratingB) {
|
||||||
return ratingB - ratingA;
|
return flip * (ratingB - ratingA);
|
||||||
}
|
}
|
||||||
// If ratings are equal, sort by comment count
|
return flip * ((b.comment_count || 0) - (a.comment_count || 0));
|
||||||
return (b.comment_count || 0) - (a.comment_count || 0);
|
}
|
||||||
case 'capture_date':
|
case 'capture_date': {
|
||||||
// Sort by capture date (from EXIF), fall back to upload date
|
|
||||||
const captureDateA = a.captured_at || a.uploaded_at;
|
const captureDateA = a.captured_at || a.uploaded_at;
|
||||||
const captureDateB = b.captured_at || b.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':
|
case 'date':
|
||||||
default:
|
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<GalleryViewProps> = ({ slug, event }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return photos;
|
return photos;
|
||||||
}, [data?.photos, selectedCategoryId, searchTerm, sortBy, watermarkEnabled, slug, filterType, mediaFilter]);
|
}, [data?.photos, selectedCategoryId, searchTerm, sortBy, sortDesc, watermarkEnabled, slug, filterType, mediaFilter]);
|
||||||
|
|
||||||
const likeCount = useMemo(
|
const likeCount = useMemo(
|
||||||
() => data?.photos?.filter(p => (p.like_count ?? 0) > 0).length || 0,
|
() => data?.photos?.filter(p => (p.like_count ?? 0) > 0).length || 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user