97 lines
2.7 KiB
TypeScript
97 lines
2.7 KiB
TypeScript
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
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 (
|
|
<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 rounded-lg 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>
|
|
);
|
|
}
|