Files

107 lines
3.1 KiB
TypeScript
Raw Permalink Normal View History

'use client';
import { useState, useEffect, useCallback } from 'react';
import {
listFiles,
isImageMime,
getThumbnailUrl,
type FileResponse,
} from '@/lib/files';
import Image from 'next/image';
import { FilePreview } from './FilePreview';
interface FileGalleryProps {
vehicleId: string;
}
export function FileGallery({ vehicleId }: FileGalleryProps) {
const [images, setImages] = useState<FileResponse[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [selectedImage, setSelectedImage] = useState<FileResponse | null>(null);
const fetchImages = useCallback(async () => {
setLoading(true);
setError(null);
try {
// Fetch all files and filter for images
const data = await listFiles(vehicleId, 1, 100);
const imageFiles = data.items.filter((f) => isImageMime(f.mime_type));
setImages(imageFiles);
} catch (err: unknown) {
const apiErr = err as { error?: { message?: string } };
setError(apiErr?.error?.message || 'Failed to load images');
} finally {
setLoading(false);
}
}, [vehicleId]);
useEffect(() => {
fetchImages();
}, [fetchImages]);
if (loading && images.length === 0) {
return (
<div className="text-center py-8 text-text-muted" data-testid="gallery-loading">
Loading gallery...
</div>
);
}
if (error) {
return (
<div className="text-center py-8 text-error" data-testid="gallery-error">
{error}
</div>
);
}
if (images.length === 0) {
return (
<div className="text-center py-8 text-text-muted" data-testid="gallery-empty">
No images available for this vehicle.
</div>
);
}
return (
<div data-testid="file-gallery">
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-3">
{images.map((image) => {
const thumbUrl = getThumbnailUrl(image);
return (
<div
key={image.id}
className="relative aspect-square rounded-lg overflow-hidden cursor-pointer group"
onClick={() => setSelectedImage(image)}
data-testid={`gallery-item-${image.id}`}
>
{thumbUrl && (
<Image
src={thumbUrl}
alt={image.original_filename}
fill
className="object-cover transition-transform group-hover:scale-105"
unoptimized
/>
)}
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/30 transition-colors flex items-end p-2">
<span className="text-white text-xs opacity-0 group-hover:opacity-100 transition-opacity truncate">
{image.original_filename}
</span>
</div>
</div>
);
})}
</div>
{/* Full preview modal */}
<FilePreview
file={selectedImage}
open={!!selectedImage}
onClose={() => setSelectedImage(null)}
/>
</div>
);
}