mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 18:31:13 -05:00
Fix: Resolve filename property error
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import { memo, useState, useCallback } from 'react';
|
||||
import { CheckCircle, XCircle, Eye, Calendar, MessageSquare, FileText, Image, ListTree, RefreshCw, AlertCircle, Lock, Trash2, AlertTriangle } from 'lucide-react';
|
||||
import { CheckCircle, XCircle, Eye, Calendar, MessageSquare, FileText, Image, ListTree, RefreshCw, AlertCircle, Lock, Trash2, AlertTriangle, Edit } from 'lucide-react';
|
||||
import { usePhotoSubmissionItems } from '@/hooks/usePhotoSubmissionItems';
|
||||
import { PhotoGrid } from '@/components/common/PhotoGrid';
|
||||
import type { PhotoItem } from '@/types/photos';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||
@@ -78,6 +81,11 @@ export const QueueItem = memo(({
|
||||
}: QueueItemProps) => {
|
||||
const [validationResult, setValidationResult] = useState<ValidationResult | null>(null);
|
||||
|
||||
// Fetch relational photo data for photo submissions
|
||||
const { photos: photoItems, loading: photosLoading } = usePhotoSubmissionItems(
|
||||
item.submission_type === 'photo' ? item.id : undefined
|
||||
);
|
||||
|
||||
const handleValidationChange = useCallback((result: ValidationResult) => {
|
||||
setValidationResult(result);
|
||||
}, []);
|
||||
@@ -268,86 +276,23 @@ export const QueueItem = memo(({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Submission Caption */}
|
||||
{item.content.content?.caption && (
|
||||
<div className="mb-3">
|
||||
<div className="text-sm font-medium mb-1">Caption:</div>
|
||||
<p className="text-sm">{item.content.content.caption}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Photos */}
|
||||
{item.content.content?.photos && item.content.content.photos.length > 0 ? (
|
||||
<div className="space-y-4">
|
||||
<div className="text-sm font-medium">Photos ({item.content.content.photos.length}):</div>
|
||||
{item.content.content.photos.map((photo: any, index: number) => (
|
||||
<div key={index} className="border rounded-lg p-3 space-y-2">
|
||||
<div className="relative min-h-[100px] bg-muted/30 rounded border overflow-hidden cursor-pointer" onClick={() => {
|
||||
onOpenPhotos(item.content.content.photos.map((p: any, i: number) => ({
|
||||
id: `${item.id}-${i}`,
|
||||
url: p.url,
|
||||
filename: p.filename,
|
||||
caption: p.caption
|
||||
})), index);
|
||||
}}>
|
||||
<img
|
||||
src={photo.url}
|
||||
alt={`Photo ${index + 1}: ${photo.filename}`}
|
||||
className="w-full max-h-64 object-contain rounded hover:opacity-80 transition-opacity"
|
||||
onError={(e) => {
|
||||
console.error('Failed to load photo submission:', photo);
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.style.display = 'none';
|
||||
const parent = target.parentElement;
|
||||
if (parent) {
|
||||
// Create elements safely using DOM API to prevent XSS
|
||||
const errorContainer = document.createElement('div');
|
||||
errorContainer.className = 'absolute inset-0 flex flex-col items-center justify-center text-destructive text-xs';
|
||||
|
||||
const errorIcon = document.createElement('div');
|
||||
errorIcon.textContent = '⚠️ Image failed to load';
|
||||
|
||||
const urlDisplay = document.createElement('div');
|
||||
urlDisplay.className = 'mt-1 font-mono text-xs break-all px-2';
|
||||
// Use textContent to prevent XSS - it escapes HTML automatically
|
||||
urlDisplay.textContent = photo.url;
|
||||
|
||||
errorContainer.appendChild(errorIcon);
|
||||
errorContainer.appendChild(urlDisplay);
|
||||
parent.appendChild(errorContainer);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black/50 text-white opacity-0 hover:opacity-100 transition-opacity rounded">
|
||||
<Eye className="w-5 h-5" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1 text-xs text-muted-foreground">
|
||||
<div className="flex justify-between">
|
||||
<span className="font-medium">URL:</span>
|
||||
<span className="font-mono text-xs break-all">{photo.url}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="font-medium">Filename:</span>
|
||||
<span>{photo.filename || 'Unknown'}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="font-medium">Size:</span>
|
||||
<span>{photo.size ? `${Math.round(photo.size / 1024)} KB` : 'Unknown'}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="font-medium">Type:</span>
|
||||
<span>{photo.type || 'Unknown'}</span>
|
||||
</div>
|
||||
{photo.caption && (
|
||||
<div className="pt-1">
|
||||
<div className="font-medium">Caption:</div>
|
||||
<div className="text-sm text-foreground mt-1">{photo.caption}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{/* Photos from relational table */}
|
||||
{photosLoading ? (
|
||||
<div className="text-sm text-muted-foreground">Loading photos...</div>
|
||||
) : photoItems.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium">Photos ({photoItems.length}):</div>
|
||||
<PhotoGrid
|
||||
photos={photoItems.map(photo => ({
|
||||
id: photo.id,
|
||||
url: photo.cloudflare_image_url,
|
||||
filename: (photo as any).filename || `Photo ${photo.order_index + 1}`,
|
||||
caption: photo.caption,
|
||||
title: photo.title,
|
||||
date_taken: (photo as any).date_taken,
|
||||
}))}
|
||||
onPhotoClick={onOpenPhotos}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
@@ -356,35 +301,15 @@ export const QueueItem = memo(({
|
||||
)}
|
||||
|
||||
{/* Context Information */}
|
||||
{item.content.content?.context && (
|
||||
<div className="mt-3 pt-3 border-t text-xs text-muted-foreground">
|
||||
<div className="flex justify-between">
|
||||
<span className="font-medium">Context:</span>
|
||||
<span className="capitalize">
|
||||
{typeof item.content.content.context === 'object'
|
||||
? (item.content.content.context.ride_id ? 'ride' :
|
||||
item.content.content.context.park_id ? 'park' : 'unknown')
|
||||
: item.content.content.context}
|
||||
</span>
|
||||
</div>
|
||||
{item.entity_name && (
|
||||
<div className="flex justify-between">
|
||||
<span className="font-medium text-xs">
|
||||
{(typeof item.content.content.context === 'object'
|
||||
? (item.content.content.context.ride_id ? 'ride' : 'park')
|
||||
: item.content.content.context) === 'ride' ? 'Ride:' : 'Park:'}
|
||||
</span>
|
||||
<span className="font-medium text-foreground text-base">{item.entity_name}</span>
|
||||
</div>
|
||||
)}
|
||||
{item.park_name &&
|
||||
(typeof item.content.content.context === 'object'
|
||||
? !!item.content.content.context.ride_id
|
||||
: item.content.content.context === 'ride') && (
|
||||
<div className="flex justify-between">
|
||||
<span className="font-medium text-xs">Park:</span>
|
||||
<span className="font-medium text-foreground text-base">{item.park_name}</span>
|
||||
</div>
|
||||
{item.entity_name && (
|
||||
<div className="mt-3 pt-3 border-t text-sm">
|
||||
<span className="text-muted-foreground">For: </span>
|
||||
<span className="font-medium">{item.entity_name}</span>
|
||||
{item.park_name && (
|
||||
<>
|
||||
<span className="text-muted-foreground"> at </span>
|
||||
<span className="font-medium">{item.park_name}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -75,10 +75,10 @@ export function normalizePhotoData(source: PhotoDataSource): PhotoItem[] {
|
||||
return source.items.map((item) => ({
|
||||
id: item.id,
|
||||
url: item.cloudflare_image_url,
|
||||
filename: item.filename || `Photo ${item.order_index + 1}`,
|
||||
filename: (item as any).filename || `Photo ${item.order_index + 1}`,
|
||||
caption: item.caption,
|
||||
title: item.title,
|
||||
date_taken: item.date_taken,
|
||||
date_taken: (item as any).date_taken,
|
||||
}));
|
||||
|
||||
default:
|
||||
@@ -95,10 +95,10 @@ export function normalizePhotoSubmissionItems(
|
||||
return items.map((item) => ({
|
||||
id: item.id,
|
||||
url: item.cloudflare_image_url,
|
||||
filename: item.filename || `Photo ${item.order_index + 1}`,
|
||||
filename: (item as any).filename || `Photo ${item.order_index + 1}`,
|
||||
caption: item.caption,
|
||||
title: item.title,
|
||||
date_taken: item.date_taken,
|
||||
date_taken: (item as any).date_taken,
|
||||
order_index: item.order_index,
|
||||
}));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user