Fold NE selection and refresh status into the shared list pager.

Add an optional ListPager meta slot so Managed NE no longer needs a separate selection row above the table.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-10-13 00:41:37 +08:00
parent 312f6f2f96
commit 0ecff329cf
3 changed files with 38 additions and 12 deletions

View file

@ -1,5 +1,5 @@
import { Button, Input } from "@heroui/react"; import { Button, Input } from "@heroui/react";
import { useEffect, useState } from "react"; import { useEffect, useState, type ReactNode } from "react";
import { useI18n } from "../i18n"; import { useI18n } from "../i18n";
import { FieldSelect } from "./ui/FieldSelect"; import { FieldSelect } from "./ui/FieldSelect";
@ -13,6 +13,8 @@ export type ListPagerProps = {
onPageSizeChange?: (size: number) => void; onPageSizeChange?: (size: number) => void;
disabled?: boolean; disabled?: boolean;
className?: string; className?: string;
/** Extra status shown after the totals (e.g. selection count), so pages need no separate row. */
meta?: ReactNode;
}; };
/** Shared denser pager: meta + prev/next + jump + optional page size. */ /** Shared denser pager: meta + prev/next + jump + optional page size. */
@ -26,6 +28,7 @@ export function ListPager({
onPageSizeChange, onPageSizeChange,
disabled = false, disabled = false,
className = "", className = "",
meta,
}: ListPagerProps) { }: ListPagerProps) {
const { t } = useI18n(); const { t } = useI18n();
const safePages = Math.max(1, pages || 1); const safePages = Math.max(1, pages || 1);
@ -54,6 +57,7 @@ export function ListPager({
page: String(page), page: String(page),
pages: String(safePages), pages: String(safePages),
})} })}
{meta}
</div> </div>
<div className="pager__controls btn-row"> <div className="pager__controls btn-row">
<Button <Button

View file

@ -671,15 +671,6 @@ export function NePage() {
</div> </div>
<div className="pt-list"> <div className="pt-list">
{selected.length ? (
<div className="ne-selection-meta">
<span>{t("managedNe.selectedCount", { n: selected.length })}</span>
<Button size="sm" variant="ghost" onPress={() => setSelected([])}>
{t("managedNe.clearSelection")}
</Button>
</div>
) : null}
{listQuery.isFetching ? <p className="muted">{t("common.refreshing")}</p> : null}
{listQuery.isError ? ( {listQuery.isError ? (
<p className="error-text">{t("managedNe.loadFailed", { error: errText(listQuery.error) })}</p> <p className="error-text">{t("managedNe.loadFailed", { error: errText(listQuery.error) })}</p>
) : null} ) : null}
@ -832,6 +823,19 @@ export function NePage() {
setPage(1); setPage(1);
}} }}
disabled={listQuery.isLoading} disabled={listQuery.isLoading}
meta={
<>
{selected.length ? (
<span className="ne-selection-meta">
· {t("managedNe.selectedCount", { n: selected.length })}
<button type="button" className="ne-selection-meta__clear" onClick={() => setSelected([])}>
{t("managedNe.clearSelection")}
</button>
</span>
) : null}
{listQuery.isFetching ? <span className="ne-selection-meta"> · {t("common.refreshing")}</span> : null}
</>
}
/> />
</div> </div>
</section> </section>

View file

@ -115,9 +115,27 @@ button.ne-tag-card.is-active {
.ne-selection-meta { .ne-selection-meta {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 6px; gap: 8px;
margin-left: 6px;
color: var(--nx-text-muted); color: var(--nx-text-muted);
font-size: 12px; }
.ne-page .ne-selection-meta__clear {
padding: 0;
border: 0;
background: transparent;
color: var(--nx-accent);
cursor: pointer;
font: inherit;
}
.ne-page .ne-selection-meta__clear:hover {
text-decoration: underline;
}
.ne-page .ne-selection-meta__clear:focus-visible {
outline: 2px solid var(--nx-accent);
outline-offset: 2px;
} }
.ne-import-failures { .ne-import-failures {