Files
erika/app/(ui)/admin/upload/history/page.tsx
T
2026-08-27 14:11:20 +00:00

101 lines
2.8 KiB
TypeScript

import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { RealtimeRefresh } from "@/components/realtime-refresh";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { db } from "@/db";
import { videoUploads } from "@/db/schema";
import { desc } from "drizzle-orm";
import { retryVideoJob } from "../actions";
export const dynamic = "force-dynamic";
function statusVariant(status: string) {
return status === "completed"
? "default"
: status === "failed"
? "destructive"
: "secondary";
}
export default async function AdminUploadHistoryPage() {
const uploads = await db.query.videoUploads.findMany({
orderBy: [desc(videoUploads.createdAt)],
limit: 20,
});
const allJobs = uploads.length
? await db.query.videoUploadJobs.findMany({
where: (table, { inArray }) =>
inArray(
table.uploadId,
uploads.map((upload) => upload.id),
),
})
: [];
const jobsByUpload = new Map<string, typeof allJobs>();
for (const job of allJobs)
jobsByUpload.set(job.uploadId, [
...(jobsByUpload.get(job.uploadId) ?? []),
job,
]);
return (
<>
<RealtimeRefresh topic="uploads" />
<Card>
<CardHeader>
<CardTitle>Upload history</CardTitle>
<CardDescription>Scheduled and recent publishing jobs.</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-3">
{uploads.length === 0 ? (
<p className="text-sm text-muted-foreground">No uploads yet.</p>
) : (
uploads.map((upload) => (
<div
key={upload.id}
className="flex flex-col gap-3 border p-4 md:flex-row md:items-center md:justify-between"
>
<div className="min-w-0">
<p className="truncate font-medium">{upload.title}</p>
<p className="text-sm text-muted-foreground">
{upload.scheduledAt
? `Scheduled ${upload.scheduledAt.toLocaleString("en-GB", { timeZone: "Asia/Bangkok" })}`
: upload.createdAt.toLocaleString("en-GB", {
timeZone: "Asia/Bangkok",
})}
</p>
</div>
<div className="flex flex-wrap items-center gap-2">
<Badge variant={statusVariant(upload.status)}>
{upload.status}
</Badge>
{(jobsByUpload.get(upload.id) ?? []).map((job) => (
<span key={job.id} className="flex items-center gap-1">
<Badge variant={statusVariant(job.status)}>
{job.platform}: {job.status}
</Badge>
{job.status === "failed" && (
<form action={retryVideoJob.bind(null, job.id)}>
<Button type="submit" size="sm" variant="outline">
Retry
</Button>
</form>
)}
</span>
))}
</div>
</div>
))
)}
</CardContent>
</Card>
</>
);
}