feat(studio): localize catalog studio UI and polish responsive layout
Route all Catalog Studio strings through the admin.studio namespace (workspace, filters, review, inspector, quality, sync, scale32, organize imports and page titles/subtitles) with English fallback, and add shared StudioPage chrome plus a loading skeleton. Tighten mobile padding on the header and sub-bars.
This commit is contained in:
1 parent
d7d6382b60
commit
56676effc3
28 files changed
+1293
-634
No files matched your search
@@ -9,19 +9,20 @@ import {
|
||||
RefreshCw,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { memo, useEffect, useMemo, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { BatchItemStatus, VerificationSummary } from "./studio-types";
|
||||
|
||||
const statusLabels: Record<BatchItemStatus["status"], string> = {
|
||||
pending: "Waiting",
|
||||
started: "Starting",
|
||||
downloading: "Downloading assets",
|
||||
converting: "Preparing assets",
|
||||
writing_db: "Saving",
|
||||
done: "Imported",
|
||||
failed: "Failed",
|
||||
const STATUS_KEYS: Record<BatchItemStatus["status"], string> = {
|
||||
pending: "status.pending",
|
||||
started: "status.started",
|
||||
downloading: "status.downloading",
|
||||
converting: "status.converting",
|
||||
writing_db: "status.writingDb",
|
||||
done: "status.done",
|
||||
failed: "status.failed",
|
||||
};
|
||||
|
||||
function formatDuration(ms: number): string {
|
||||
@@ -65,6 +66,7 @@ export const BatchProgress = memo(function BatchProgress({
|
||||
onDismiss?: () => void;
|
||||
onRetry?: (classnames: string[]) => void;
|
||||
}) {
|
||||
const t = useTranslations("admin.studio.batch");
|
||||
const [now, setNow] = useState(() => Date.now());
|
||||
|
||||
useEffect(() => {
|
||||
@@ -99,20 +101,20 @@ export const BatchProgress = memo(function BatchProgress({
|
||||
const rate = formatRate(completed, elapsed);
|
||||
const heading = !done
|
||||
? cancelling
|
||||
? "Stopping import…"
|
||||
: "Importing furniture…"
|
||||
? t("headingStopping")
|
||||
: t("headingImporting")
|
||||
: unfinished
|
||||
? "Import stopped"
|
||||
? t("headingStopped")
|
||||
: errors
|
||||
? "Import completed with errors"
|
||||
: "Import completed";
|
||||
? t("headingCompletedErrors")
|
||||
: t("headingCompleted");
|
||||
const issues = verification
|
||||
? verification.furniDataMissing + verification.furniDataConflicts
|
||||
: 0;
|
||||
|
||||
return (
|
||||
<section
|
||||
aria-label="Import progress"
|
||||
aria-label={t("ariaProgress")}
|
||||
className="max-h-[45%] shrink-0 overflow-auto border-t border-[var(--admin-border)] bg-[var(--admin-surface)] p-4"
|
||||
>
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
@@ -128,7 +130,7 @@ export const BatchProgress = memo(function BatchProgress({
|
||||
<span className="font-semibold text-[var(--admin-success)]">
|
||||
{imported}
|
||||
</span>{" "}
|
||||
imported
|
||||
{t("imported")}
|
||||
</span>
|
||||
<span className="text-[var(--admin-border)]">·</span>
|
||||
<span>
|
||||
@@ -140,13 +142,13 @@ export const BatchProgress = memo(function BatchProgress({
|
||||
>
|
||||
{errors}
|
||||
</span>{" "}
|
||||
failed
|
||||
{t("failed")}
|
||||
</span>
|
||||
{unfinished > 0 && (
|
||||
<>
|
||||
<span className="text-[var(--admin-border)]">·</span>
|
||||
<span>
|
||||
{unfinished} {done ? "not completed" : "remaining"}
|
||||
{unfinished} {done ? t("notCompleted") : t("remaining")}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
@@ -173,12 +175,12 @@ export const BatchProgress = memo(function BatchProgress({
|
||||
onClick={() => onRetry(failedClassnames)}
|
||||
>
|
||||
<RefreshCw size={14} />
|
||||
Retry failed ({failedClassnames.length})
|
||||
{t("retryFailed", { count: failedClassnames.length })}
|
||||
</Button>
|
||||
)}
|
||||
<Button size="sm" variant="secondary" onClick={onDismiss}>
|
||||
<Check size={14} />
|
||||
Close summary
|
||||
{t("closeSummary")}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
@@ -193,14 +195,14 @@ export const BatchProgress = memo(function BatchProgress({
|
||||
) : (
|
||||
<Ban size={14} />
|
||||
)}
|
||||
Stop import
|
||||
{t("stopImport")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
role="progressbar"
|
||||
aria-label="Furniture processed"
|
||||
aria-label={t("ariaProcessed")}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={total || 1}
|
||||
aria-valuenow={completed}
|
||||
@@ -215,39 +217,42 @@ export const BatchProgress = memo(function BatchProgress({
|
||||
<div className="mb-3 flex flex-wrap items-center gap-1.5">
|
||||
<p className="mr-1 text-sm text-[var(--admin-text-muted)]">
|
||||
{issues
|
||||
? `${issues} catalog data issue(s) still need attention. Open Catalog audit to review them.`
|
||||
: "Catalog data verification completed."}
|
||||
? t("verificationIssues", { count: issues })
|
||||
: t("verificationDone")}
|
||||
</p>
|
||||
<NodeChip
|
||||
count={verification.furniDataFixedIds}
|
||||
label="furnidata ids fixed"
|
||||
label={t("chipFurnidataIds")}
|
||||
/>
|
||||
<NodeChip
|
||||
count={verification.offerIdsFixed}
|
||||
label="offer ids reconciled"
|
||||
label={t("chipOfferIds")}
|
||||
/>
|
||||
<NodeChip
|
||||
count={verification.ownershipFixed.length}
|
||||
label="ownerships fixed"
|
||||
label={t("chipOwnerships")}
|
||||
/>
|
||||
<NodeChip
|
||||
count={verification.nitrosSynced.length}
|
||||
label="nitros synced"
|
||||
label={t("chipNitros")}
|
||||
/>
|
||||
<NodeChip
|
||||
count={verification.iconsSynced.length}
|
||||
label="icons synced"
|
||||
label={t("chipIcons")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<details>
|
||||
<summary className="cursor-pointer text-xs font-medium">
|
||||
View item details ({total})
|
||||
{t("viewItemDetails", { count: total })}
|
||||
</summary>
|
||||
<div className="mt-2 max-h-44 space-y-2 overflow-auto">
|
||||
{entries.length > VISIBLE_ITEMS && (
|
||||
<p className="px-1 text-[0.65rem] text-[var(--admin-text-muted)]">
|
||||
Showing {VISIBLE_ITEMS} of {entries.length} items
|
||||
{t("showingItems", {
|
||||
shown: VISIBLE_ITEMS,
|
||||
total: entries.length,
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
{entries.slice(0, VISIBLE_ITEMS).map((item) => (
|
||||
@@ -280,8 +285,8 @@ export const BatchProgress = memo(function BatchProgress({
|
||||
</span>
|
||||
<span className="shrink-0 text-[var(--admin-text-muted)]">
|
||||
{done && item.status !== "done" && item.status !== "failed"
|
||||
? "Not completed"
|
||||
: statusLabels[item.status]}
|
||||
? t("statusNotCompleted")
|
||||
: t(STATUS_KEYS[item.status])}
|
||||
</span>
|
||||
</span>
|
||||
{item.message && (
|
||||
|
||||
Reference in new issue
Block a user