Update award-detail-dialog.tsx
This commit is contained in:
@@ -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>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
Reference in New Issue
Block a user