feat: add My Uploads listing with card and table views
This commit is contained in:
107
src/app/(modules)/upload/components/UploadCardGrid.tsx
Normal file
107
src/app/(modules)/upload/components/UploadCardGrid.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
'use client';
|
||||
|
||||
import { FileVideo } from 'lucide-react';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import type { UploadVideo } from '@/types';
|
||||
|
||||
import { CardPagination } from './CardPagination';
|
||||
import { UploadActionsMenu } from './UploadActionsMenu';
|
||||
import { UploadThumbnail } from './UploadThumbnail';
|
||||
import { formatUploadedAt } from './uploadUtils';
|
||||
|
||||
interface UploadCardGridProps {
|
||||
uploads: UploadVideo[];
|
||||
isLoading: boolean;
|
||||
skip: number;
|
||||
limit: number;
|
||||
total: number;
|
||||
onPageChange: (skip: number) => void;
|
||||
onLimitChange: (limit: number) => void;
|
||||
onUploadNew: () => void;
|
||||
onOpenUpload: (upload: UploadVideo) => void;
|
||||
onViewDetails: (upload: UploadVideo) => void;
|
||||
onViewProcessedVideo: (upload: UploadVideo) => void;
|
||||
}
|
||||
|
||||
export function UploadCardGrid({
|
||||
uploads,
|
||||
isLoading,
|
||||
skip,
|
||||
limit,
|
||||
total,
|
||||
onPageChange,
|
||||
onLimitChange,
|
||||
onUploadNew,
|
||||
onOpenUpload,
|
||||
onViewDetails,
|
||||
onViewProcessedVideo,
|
||||
}: UploadCardGridProps) {
|
||||
const skeletonItems = Array.from({ length: Math.min(limit, 20) });
|
||||
|
||||
if (!isLoading && uploads.length === 0) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="flex min-h-80 flex-col items-center justify-center gap-3 text-center">
|
||||
<div className="flex size-12 items-center justify-center rounded-md border bg-muted/40">
|
||||
<FileVideo className="size-6 text-muted-foreground" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-base">No uploads found</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Upload your first road inspection video.
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={onUploadNew}>Upload New</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="space-y-4">
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5">
|
||||
{isLoading
|
||||
? skeletonItems.map((_, index) => (
|
||||
<Card key={index} className="h-62 animate-pulse bg-muted/30" />
|
||||
))
|
||||
: uploads.map((upload) => (
|
||||
<Card
|
||||
key={upload.video_id}
|
||||
className="cursor-pointer overflow-hidden transition-colors hover:bg-muted/30"
|
||||
onClick={() => onOpenUpload(upload)}
|
||||
>
|
||||
<CardContent className="space-y-3 p-3">
|
||||
<UploadThumbnail upload={upload} />
|
||||
<div className="flex items-start gap-2">
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-medium">
|
||||
{upload.filename}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Uploaded {formatUploadedAt(upload.uploaded_at)}
|
||||
</p>
|
||||
</div>
|
||||
<UploadActionsMenu
|
||||
upload={upload}
|
||||
onViewDetails={onViewDetails}
|
||||
onViewProcessedVideo={onViewProcessedVideo}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<CardPagination
|
||||
skip={skip}
|
||||
limit={limit}
|
||||
total={total}
|
||||
count={uploads.length}
|
||||
onPageChange={onPageChange}
|
||||
onLimitChange={onLimitChange}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user