Update award-detail-dialog.tsx

This commit is contained in:
2025-09-29 23:20:15 +08:00
parent bdbb7f18d5
commit d8da30b32a

View File

@@ -207,11 +207,47 @@ export function AwardDetailDialog({ open, onOpenChange, award }: AwardDetailDial
} }
const nextPhoto = () => { const nextPhoto = () => {
setCurrentPhotoIndex((prev) => (prev + 1) % competitionPhotos.length) setCurrentPhotoIndex((prev) => {
// 獲取當前照片數組
let photos = [];
if (award.photos) {
photos = award.photos;
} else if (award.photos) {
try {
photos = typeof award.photos === 'string'
? JSON.parse(award.photos)
: award.photos;
} catch (e) {
console.warn('解析 photos JSON 失敗:', e);
photos = [];
}
}
if (photos.length === 0) return 0;
return (prev + 1) % photos.length;
})
} }
const prevPhoto = () => { const prevPhoto = () => {
setCurrentPhotoIndex((prev) => (prev - 1 + competitionPhotos.length) % competitionPhotos.length) setCurrentPhotoIndex((prev) => {
// 獲取當前照片數組
let photos = [];
if (award.photos) {
photos = award.photos;
} else if (award.photos) {
try {
photos = typeof award.photos === 'string'
? JSON.parse(award.photos)
: award.photos;
} catch (e) {
console.warn('解析 photos JSON 失敗:', e);
photos = [];
}
}
if (photos.length === 0) return 0;
return (prev - 1 + photos.length) % photos.length;
})
} }
const handlePreview = (report: any) => { const handlePreview = (report: any) => {
@@ -707,23 +743,91 @@ export function AwardDetailDialog({ open, onOpenChange, award }: AwardDetailDial
) )
} }
const renderCompetitionPhotos = () => ( const renderCompetitionPhotos = () => {
<Card> // 解析 photos 資料
<CardHeader> let photos = [];
<CardTitle className="flex items-center space-x-2"> if (award.photos) {
<Camera className="w-5 h-5 text-blue-500" /> photos = award.photos;
<span></span> } else if (award.photos) {
</CardTitle> try {
<CardDescription></CardDescription> photos = typeof award.photos === 'string'
</CardHeader> ? JSON.parse(award.photos)
<CardContent> : award.photos;
<div className="text-center py-8 text-gray-500"> } catch (e) {
<ImageIcon className="w-12 h-12 mx-auto mb-4 text-gray-300" /> console.error('解析 photos 失敗:', e);
<p></p> }
</div> }
</CardContent>
</Card> console.log('🖼️ 競賽照片資料:', {
) hasPhotos: !!photos,
photosType: typeof photos,
photosLength: photos?.length,
photosData: photos
});
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<Camera className="w-5 h-5 text-blue-500" />
<span></span>
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
{!photos || !Array.isArray(photos) || photos.length === 0 ? (
<div className="text-center py-8 text-gray-500">
<ImageIcon className="w-12 h-12 mx-auto mb-4 text-gray-300" />
<p></p>
</div>
) : (
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{photos.map((photo: any, index: number) => {
console.log('📸 處理照片:', { index, photo });
return (
<div key={photo.id || photo.url || index} className="space-y-2">
<div className="aspect-video bg-gray-100 rounded-lg border overflow-hidden cursor-pointer hover:shadow-md transition-shadow"
onClick={() => {
setCurrentPhotoIndex(index);
setShowPhotoGallery(true);
}}>
{photo.url ? (
<img
src={photo.url}
alt={photo.caption || photo.name || "競賽照片"}
className="w-full h-full object-cover"
onError={(e) => {
console.log('❌ 圖片載入失敗:', photo.url);
e.currentTarget.style.display = 'none';
e.currentTarget.nextElementSibling?.classList.remove('hidden');
}}
onLoad={() => {
console.log('✅ 圖片載入成功:', photo.url);
}}
/>
) : (
<div className="w-full h-full bg-gray-200 flex items-center justify-center text-2xl">
🖼
</div>
)}
<div className="w-full h-full bg-gray-200 flex items-center justify-center text-2xl hidden">
🖼
</div>
</div>
{(photo.caption || photo.name) && (
<p className="text-xs text-gray-600 text-center line-clamp-2">
{photo.caption || photo.name}
</p>
)}
</div>
);
})}
</div>
)}
</CardContent>
</Card>
);
}
return ( return (
<> <>
@@ -766,6 +870,132 @@ export function AwardDetailDialog({ open, onOpenChange, award }: AwardDetailDial
</div> </div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
{/* Photo Gallery Modal */}
{showPhotoGallery && (() => {
// 解析 photos 資料
let photos = [];
if (award.photos) {
photos = award.photos;
} else if (award.photos) {
try {
photos = typeof award.photos === 'string'
? JSON.parse(award.photos)
: award.photos;
} catch (e) {
console.error('解析 photos 失敗:', e);
}
}
if (!photos || !Array.isArray(photos) || photos.length === 0) {
return null;
}
const currentPhoto = photos[isNaN(currentPhotoIndex) ? 0 : currentPhotoIndex];
return (
<Dialog open={showPhotoGallery} onOpenChange={setShowPhotoGallery}>
<DialogContent className="max-w-4xl max-h-[90vh] p-0">
<DialogHeader className="sr-only">
<DialogTitle></DialogTitle>
</DialogHeader>
<div className="relative">
{/* Close Button */}
<Button
variant="ghost"
size="sm"
className="absolute top-4 right-4 z-10 bg-black/50 text-white hover:bg-black/70"
onClick={() => setShowPhotoGallery(false)}
>
<X className="w-4 h-4" />
</Button>
{/* Main Photo */}
<div className="relative aspect-video bg-black">
{currentPhoto?.url ? (
<img
src={currentPhoto.url}
alt={currentPhoto.caption || currentPhoto.name || "競賽照片"}
className="w-full h-full object-contain"
/>
) : (
<div className="w-full h-full flex items-center justify-center text-white text-6xl">
🖼
</div>
)}
{/* Navigation Arrows */}
{photos.length > 1 && (
<>
<Button
variant="ghost"
size="sm"
className="absolute left-4 top-1/2 transform -translate-y-1/2 z-10 bg-black/50 text-white hover:bg-black/70"
onClick={prevPhoto}
>
<ChevronLeft className="w-4 h-4" />
</Button>
<Button
variant="ghost"
size="sm"
className="absolute right-4 top-1/2 transform -translate-y-1/2 z-10 bg-black/50 text-white hover:bg-black/70"
onClick={nextPhoto}
>
<ChevronRight className="w-4 h-4" />
</Button>
</>
)}
{/* Photo Counter */}
<div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 bg-black/50 text-white px-3 py-1 rounded-full text-sm">
{isNaN(currentPhotoIndex) ? 1 : currentPhotoIndex + 1} / {photos.length}
</div>
</div>
{/* Photo Caption */}
{(currentPhoto?.caption || currentPhoto?.name) && (
<div className="p-4 bg-white border-t">
<p className="text-center text-gray-700">
{currentPhoto.caption || currentPhoto.name}
</p>
</div>
)}
{/* Thumbnail Strip */}
{photos.length > 1 && (
<div className="p-4 bg-gray-50 border-t">
<div className="flex space-x-2 overflow-x-auto">
{photos.map((photo: any, index: number) => (
<button
key={photo.id || photo.url || index}
className={`flex-shrink-0 w-16 h-16 rounded-lg overflow-hidden border-2 ${
index === currentPhotoIndex
? 'border-blue-500'
: 'border-gray-200 hover:border-gray-300'
}`}
onClick={() => setCurrentPhotoIndex(index)}
>
{photo.url ? (
<img
src={photo.url}
alt={photo.caption || photo.name || "競賽照片"}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full bg-gray-200 flex items-center justify-center text-lg">
🖼
</div>
)}
</button>
))}
</div>
</div>
)}
</div>
</DialogContent>
</Dialog>
);
})()}
</> </>
) )
} }