feat(catalog): improve visual manager search and offer discovery
CI / check (push) Successful in 1m14s
CI / deploy (push) Successful in 1m29s

This commit is contained in:
Simo committed 2026-09-06 20:58:53 +02:00
1 parent 08321df2aa
commit 93a7e9a7c2
9 files changed
+505 -72

No files matched your search

@@ -102,6 +102,7 @@ export interface PageUpdateData {
}
interface InlineEditorProps {
initialOfferId?: number | null;
pageId: number | null;
onSaved: (updated: PageUpdateData) => void;
}
@@ -115,7 +116,11 @@ export function InlineEditor(props: InlineEditorProps) {
);
}
function InlineEditorSession({ pageId, onSaved }: InlineEditorProps) {
function InlineEditorSession({
pageId,
onSaved,
initialOfferId,
}: InlineEditorProps) {
const pageRequests = useLatestRequest();
const itemRequests = useLatestRequest();
const saveRequests = useLatestRequest();
@@ -135,7 +140,9 @@ function InlineEditorSession({ pageId, onSaved }: InlineEditorProps) {
const [form, setForm] = useState<FormState>({});
const [original, setOriginal] = useState<FormState>({});
const [showTexts, setShowTexts] = useState(false);
const [activeTab, setActiveTab] = useState("settings");
const [activeTab, setActiveTab] = useState(
initialOfferId ? "items" : "settings",
);
const [showPreview, setShowPreview] = useState(false);
// Items/translate data (lazy loaded)
@@ -247,7 +254,7 @@ function InlineEditorSession({ pageId, onSaved }: InlineEditorProps) {
loadPage(pageId);
setItemsData(null);
setItemsLoaded(false);
setActiveTab("settings");
setActiveTab(initialOfferId ? "items" : "settings");
setShowPreview(false);
} else {
setPage(null);
@@ -256,7 +263,7 @@ function InlineEditorSession({ pageId, onSaved }: InlineEditorProps) {
setItemsData(null);
setItemsLoaded(false);
}
}, [pageId, loadPage]);
}, [pageId, loadPage, initialOfferId]);
// Lazy-load items (shop catalog only — BC items live on the detail page)
useEffect(() => {
@@ -360,14 +367,14 @@ function InlineEditorSession({ pageId, onSaved }: InlineEditorProps) {
return (
<div className="flex-1 flex flex-col min-h-0">
{/* Header */}
<div className="flex items-center justify-between px-4 py-2 border-b shrink-0">
<div className="flex flex-wrap items-center justify-between gap-2 px-4 py-2 border-b shrink-0">
<div className="flex items-center gap-2">
<CatalogIcon
iconImage={(form.iconImage as number) ?? page.iconImage}
size={24}
/>
<div>
<div className="flex items-center gap-2">
<div className="flex flex-wrap items-center gap-2">
<h3 className="text-sm font-semibold">
{(form.caption as string) || page.caption}
</h3>
@@ -379,13 +386,13 @@ function InlineEditorSession({ pageId, onSaved }: InlineEditorProps) {
>
{form.pageLayout as string}
</Badge>
{isDirty && (
{(isDirty || offersDirty) && (
<Badge
variant="outline"
className="h-4 px-1.5 text-[9px] text-[var(--admin-warning)] border-[var(--admin-warning-border)] bg-[var(--admin-warning-subtle)]"
>
<AlertCircle className="h-2.5 w-2.5 mr-0.5" />
unsaved
{t("unsaved")}
</Badge>
)}
</div>
@@ -397,9 +404,13 @@ function InlineEditorSession({ pageId, onSaved }: InlineEditorProps) {
</div>
</div>
<Link href={pageHref} target="_blank">
<Button variant="ghost" size="sm" className="h-7 text-xs gap-1">
<Button
variant="ghost"
size="sm"
className="h-7 text-xs gap-1 text-[var(--admin-text)]"
>
<ExternalLink className="h-3 w-3" />
Full editor
{t("fullEditor")}
</Button>
</Link>
</div>
@@ -408,13 +419,7 @@ function InlineEditorSession({ pageId, onSaved }: InlineEditorProps) {
{isDirty && (
<div className="flex items-center gap-2 px-4 py-1.5 bg-[var(--admin-warning-subtle)] border-b border-[var(--admin-warning-border)] text-xs text-[var(--admin-warning)] shrink-0">
<AlertCircle className="h-3.5 w-3.5 shrink-0" />
<span className="flex-1">
Unsaved changes — press{" "}
<kbd className="rounded border bg-background px-1 font-mono text-[10px]">
Ctrl+S
</kbd>{" "}
to save
</span>
<span className="flex-1">{t("saveHint")}</span>
<Button
size="sm"
variant="outline"
@@ -768,6 +773,9 @@ function InlineEditorSession({ pageId, onSaved }: InlineEditorProps) {
{/* Items table */}
<CatalogItemsTable
initialSearch={
initialOfferId ? `#${initialOfferId}` : undefined
}
onDirtyChange={setOffersDirty}
onRefresh={reloadItems}
items={itemsData.items}