import React, { useState } from 'react'; import { X, ChevronLeft, ChevronRight, Download, Trash2, Tag, Calendar, HardDrive, Eye, MousePointer, MessageSquare, Star, Heart, CheckCircle, XCircle, AlertCircle } from 'lucide-react'; import { format } from 'date-fns'; import { toast } from 'react-toastify'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { AdminPhoto } from '../../services/photos.service'; import { photosService } from '../../services/photos.service'; import { feedbackService, type PhotoFeedback, type FeedbackSummary } from '../../services/feedback.service'; import { Button } from '../common'; import { AdminAuthenticatedImage } from './AdminAuthenticatedImage'; type AdminFeedbackResponse = { feedback: PhotoFeedback[]; summary?: FeedbackSummary; }; interface AdminPhotoViewerProps { photos: AdminPhoto[]; initialIndex: number; eventId: number; onClose: () => void; onPhotoDeleted: () => void; categories: Array<{ id: number; name: string; slug: string }>; } export const AdminPhotoViewer: React.FC = ({ photos, initialIndex, eventId, onClose, onPhotoDeleted, categories }) => { const [currentIndex, setCurrentIndex] = useState(initialIndex); const [isDeleting, setIsDeleting] = useState(false); const [showCategoryMenu, setShowCategoryMenu] = useState(false); const [expandedComments, setExpandedComments] = useState(false); const queryClient = useQueryClient(); const currentPhoto = photos[currentIndex]; const averageRating = currentPhoto?.average_rating ?? 0; const likeCount = currentPhoto?.like_count ?? 0; const favoriteCount = currentPhoto?.favorite_count ?? 0; if (!currentPhoto) { return null; } // Fetch feedback for current photo const { data: feedbackData } = useQuery({ queryKey: ['admin-photo-feedback', eventId, currentPhoto?.id], queryFn: () => feedbackService.getEventFeedback(eventId.toString(), { photoId: currentPhoto?.id.toString(), status: 'all' // Get all comments including unapproved }), enabled: !!currentPhoto }); const comments = (feedbackData?.feedback ?? []).filter((item): item is PhotoFeedback => item.feedback_type === 'comment'); const goToPrevious = () => { setCurrentIndex((prev) => (prev > 0 ? prev - 1 : photos.length - 1)); }; const goToNext = () => { setCurrentIndex((prev) => (prev < photos.length - 1 ? prev + 1 : 0)); }; const handleDelete = async () => { if (!confirm(`Are you sure you want to delete "${currentPhoto.filename}"?`)) { return; } setIsDeleting(true); try { await photosService.deletePhoto(eventId, currentPhoto.id); toast.success('Photo deleted successfully'); // Close viewer if this was the last photo if (photos.length === 1) { onClose(); } else { // Move to next photo if available, otherwise previous if (currentIndex === photos.length - 1) { setCurrentIndex(currentIndex - 1); } } onPhotoDeleted(); } catch (error) { toast.error('Failed to delete photo'); } finally { setIsDeleting(false); } }; const handleDownload = async () => { try { await photosService.downloadPhoto(eventId, currentPhoto.id, currentPhoto.filename); toast.success('Download started'); } catch (error) { toast.error('Failed to download photo'); } }; const handleCategoryChange = async (categoryId: number | null) => { try { await photosService.updatePhotoCategory(eventId, currentPhoto.id, categoryId); toast.success('Category updated'); setShowCategoryMenu(false); // Trigger refresh to update the photo data onPhotoDeleted(); // This will refresh the photos list } catch (error) { toast.error('Failed to update category'); } }; // Mutations for feedback moderation const moderateFeedbackMutation = useMutation({ mutationFn: ({ feedbackId, action }: { feedbackId: string; action: 'approve' | 'hide' | 'reject' }) => feedbackService.moderateFeedback(feedbackId, action), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-photo-feedback', eventId, currentPhoto?.id] }); toast.success('Feedback moderated successfully'); }, onError: () => { toast.error('Failed to moderate feedback'); } }); const deleteFeedbackMutation = useMutation({ mutationFn: (feedbackId: string) => feedbackService.deleteFeedback(feedbackId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-photo-feedback', eventId, currentPhoto?.id] }); toast.success('Feedback deleted successfully'); }, onError: () => { toast.error('Failed to delete feedback'); } }); React.useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { switch (e.key) { case 'Escape': onClose(); break; case 'ArrowLeft': goToPrevious(); break; case 'ArrowRight': goToNext(); break; } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [currentIndex]); return (
{/* Close button */} {/* Navigation */} {/* Main content */}
{/* Image */}

Failed to load image

} />
{/* Sidebar */}

{currentPhoto.filename}

{/* Actions */}
{/* Category */}
Category

{currentPhoto.category_name || 'Uncategorized'}

{showCategoryMenu && (
{categories.map(cat => ( ))}
)}
{/* Metadata */}
File Size

{photosService.formatBytes(currentPhoto.size)}

Uploaded

{format(new Date(currentPhoto.uploaded_at), 'MMM d, yyyy h:mm a')}

{currentPhoto.view_count !== undefined && (
Views

{currentPhoto.view_count}

)} {currentPhoto.download_count !== undefined && (
Downloads

{currentPhoto.download_count}

)}
{/* Feedback Section */} {feedbackData && (

Feedback & Comments

{/* Feedback Stats */}
{averageRating > 0 && (
{Number(averageRating).toFixed(1)}

Avg Rating

)} {likeCount > 0 && (
{likeCount}

Likes

)} {favoriteCount > 0 && (
{favoriteCount}

Favorites

)} {comments.length > 0 && (
{comments.length}

Comments

)}
{/* Comments List */} {comments.length > 0 && (
{expandedComments && (
{comments.map((comment) => (

{comment.guest_name || 'Anonymous'}

{format(new Date(comment.created_at), 'MMM d, yyyy h:mm a')}

{/* Comment Status Badge */}
{!comment.is_approved && !comment.is_hidden && ( Pending )} {comment.is_approved && !comment.is_hidden && ( Approved )} {comment.is_hidden && ( Hidden )}

{comment.comment_text}

{/* Moderation Actions */}
{!comment.is_approved && ( )} {!comment.is_hidden && ( )} {comment.is_hidden && ( )}
))}
)}
)} {/* No feedback message */} {comments.length === 0 && (

No feedback for this photo yet.

)}
)} {/* Navigation info */}

{currentIndex + 1} of {photos.length}

); };