feat(admin): reorganize housekeeping and strengthen shared workflows
This commit is contained in:
1 parent
35f66d879f
commit
a070004e7b
35 files changed
+1667
-462
No files matched your search
@@ -1,26 +1,29 @@
|
||||
"use client";
|
||||
import { useFormatter, useTranslations } from "next-intl";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { retryableJobItems } from "@/lib/furni/import-job-retry";
|
||||
import type { useFurnitureJobs } from "./use-furniture-jobs";
|
||||
export function FurnitureJobHistory({
|
||||
state,
|
||||
}: {
|
||||
state: ReturnType<typeof useFurnitureJobs>;
|
||||
}) {
|
||||
const t = useTranslations("pages.admin.importHistory");
|
||||
const format = useFormatter();
|
||||
return (
|
||||
<details className="shrink-0 border-b border-[var(--admin-border)] bg-[var(--admin-surface)] px-4 py-2 text-sm">
|
||||
<summary className="cursor-pointer">
|
||||
Import history ·{" "}
|
||||
{
|
||||
state.jobs.filter(
|
||||
(j) => j.state === "queued" || j.state === "running",
|
||||
).length
|
||||
}{" "}
|
||||
active
|
||||
{t("summary", {
|
||||
count: state.jobs.filter(
|
||||
(job) => job.state === "queued" || job.state === "running",
|
||||
).length,
|
||||
})}
|
||||
</summary>
|
||||
<div className="max-h-72 space-y-2 overflow-auto py-2">
|
||||
{state.error && <p role="alert">{state.error}</p>}
|
||||
{!state.jobs.length && <p>No background imports yet.</p>}
|
||||
{!state.jobs.length && <p>{t("empty")}</p>}
|
||||
{state.jobs.map((job) => {
|
||||
const retryItems = retryableJobItems(job);
|
||||
const done = job.items.filter((i) => i.state === "done"),
|
||||
failed = job.items.filter((i) => i.state === "failed");
|
||||
return (
|
||||
@@ -29,14 +32,27 @@ export function FurnitureJobHistory({
|
||||
className="rounded border border-[var(--admin-border)] p-2"
|
||||
>
|
||||
<summary className="cursor-pointer">
|
||||
{new Date(job.createdAt).toLocaleString()} · {job.state} ·{" "}
|
||||
{done.length}/{job.items.length} imported · {failed.length}{" "}
|
||||
failed
|
||||
{format.dateTime(new Date(job.createdAt), {
|
||||
dateStyle: "short",
|
||||
timeStyle: "short",
|
||||
})}{" "}
|
||||
· {t(job.state)} ·{" "}
|
||||
{t("result", {
|
||||
done: done.length,
|
||||
total: job.items.length,
|
||||
failed: failed.length,
|
||||
})}
|
||||
</summary>
|
||||
<progress
|
||||
className="mt-2 h-2 w-full"
|
||||
value={done.length + failed.length}
|
||||
max={Math.max(1, job.items.length)}
|
||||
aria-label={t("progress")}
|
||||
/>
|
||||
<ul className="space-y-1 py-2">
|
||||
{job.items.map((item) => (
|
||||
<li key={item.classname}>
|
||||
<strong>{item.classname}</strong> · {item.state}
|
||||
<strong>{item.classname}</strong> · {t(item.state)}
|
||||
{item.itemId ? ` · ID ${item.itemId}` : ""}
|
||||
{item.error && (
|
||||
<p className="text-[var(--admin-warning)]">
|
||||
@@ -54,12 +70,12 @@ export function FurnitureJobHistory({
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{failed.length > 0 && job.state !== "running" && (
|
||||
{retryItems.length > 0 && (
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={state.busy}
|
||||
onClick={() =>
|
||||
void state.submit(failed, {
|
||||
void state.submit(retryItems, {
|
||||
sourceId: job.sourceId,
|
||||
mode: job.mode,
|
||||
translate: job.translate,
|
||||
@@ -67,7 +83,11 @@ export function FurnitureJobHistory({
|
||||
})
|
||||
}
|
||||
>
|
||||
Retry failed items only
|
||||
{t(
|
||||
job.state === "interrupted"
|
||||
? "retryUnfinished"
|
||||
: "retryFailed",
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</details>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { useVisiblePolling } from "@/hooks/use-visible-polling";
|
||||
import { adminFetch } from "@/lib/admin-fetch";
|
||||
import type { ImportJob, ImportJobItem } from "@/lib/furni/import-job";
|
||||
export function useFurnitureJobs(onComplete: () => void) {
|
||||
@@ -12,7 +13,10 @@ export function useFurnitureJobs(onComplete: () => void) {
|
||||
callback.current = onComplete;
|
||||
const request = useRef<{ payload: string; id: string } | null>(null);
|
||||
const submitting = useRef(false);
|
||||
const refreshing = useRef(false);
|
||||
const refresh = useCallback(async () => {
|
||||
if (refreshing.current) return;
|
||||
refreshing.current = true;
|
||||
try {
|
||||
const response = await adminFetch("/api/admin/studio/import-jobs");
|
||||
const data = await response.json();
|
||||
@@ -36,13 +40,11 @@ export function useFurnitureJobs(onComplete: () => void) {
|
||||
setError(
|
||||
error instanceof Error ? error.message : "Cannot load import history",
|
||||
);
|
||||
} finally {
|
||||
refreshing.current = false;
|
||||
}
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
void refresh();
|
||||
const timer = setInterval(() => void refresh(), 5000);
|
||||
return () => clearInterval(timer);
|
||||
}, [refresh]);
|
||||
useVisiblePolling(refresh, 5000);
|
||||
const submit = async (
|
||||
items: ImportJobItem[],
|
||||
options: {
|
||||
|
||||
Reference in new issue
Block a user