Use react-bootstrap throughout, improve entry detail
Switch sidebar components (FilterSection, FilterSidebar, Pagination) and both explorer pages to use react-bootstrap: Card, Table, Badge, Button, Alert, Form.Control, Form.Select, InputGroup, Collapse, Spinner. Use react-bootstrap-icons for Search, ChevronDown, Download, BoxArrowUpRight, etc. Entry detail page: remove MD5 columns from Downloads and Files tables. Hide empty sections entirely instead of showing placeholder cards. Human-readable file sizes (KB/MB). Web links shown as compact list with external-link icons. Notes rendered as badge+text instead of table rows. Scores and web links moved to sidebar. No-results alert now shows active machine filter names and offers to search all machines via Alert.Link. Update CLAUDE.md with react-bootstrap design conventions and remove stale ZxdbExplorer.tsx references. claude-opus-4-6@McFiver
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import ZxdbBreadcrumbs from "@/app/zxdb/components/ZxdbBreadcrumbs";
|
||||
import Pagination from "@/components/explorer/Pagination";
|
||||
|
||||
type Label = { id: number; name: string; labeltypeId: string | null };
|
||||
type Paged<T> = { items: T[]; page: number; pageSize: number; total: number };
|
||||
@@ -14,12 +15,10 @@ export default function LabelsSearch({ initial, initialQ }: { initial?: Paged<La
|
||||
const [data, setData] = useState<Paged<Label> | null>(initial ?? null);
|
||||
const totalPages = useMemo(() => (data ? Math.max(1, Math.ceil(data.total / data.pageSize)) : 1), [data]);
|
||||
|
||||
// Sync incoming SSR payload on navigation (e.g., when clicking Prev/Next Links)
|
||||
useEffect(() => {
|
||||
if (initial) setData(initial);
|
||||
}, [initial]);
|
||||
|
||||
// Keep input in sync with URL q on navigation
|
||||
useEffect(() => {
|
||||
setQ(initialQ ?? "");
|
||||
}, [initialQ]);
|
||||
@@ -32,6 +31,13 @@ export default function LabelsSearch({ initial, initialQ }: { initial?: Paged<La
|
||||
router.push(`/zxdb/labels?${params.toString()}`);
|
||||
}
|
||||
|
||||
const buildHref = useCallback((p: number) => {
|
||||
const params = new URLSearchParams();
|
||||
if (q) params.set("q", q);
|
||||
params.set("page", String(p));
|
||||
return `/zxdb/labels?${params.toString()}`;
|
||||
}, [q]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ZxdbBreadcrumbs
|
||||
@@ -55,7 +61,7 @@ export default function LabelsSearch({ initial, initialQ }: { initial?: Paged<La
|
||||
<form className="d-flex flex-column gap-2" onSubmit={submit}>
|
||||
<div>
|
||||
<label className="form-label small text-secondary">Search</label>
|
||||
<input className="form-control" placeholder="Search labels…" value={q} onChange={(e) => setQ(e.target.value)} />
|
||||
<input className="form-control" placeholder="Search labels..." value={q} onChange={(e) => setQ(e.target.value)} />
|
||||
</div>
|
||||
<div className="d-grid">
|
||||
<button className="btn btn-primary">Search</button>
|
||||
@@ -96,25 +102,12 @@ export default function LabelsSearch({ initial, initialQ }: { initial?: Paged<La
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="d-flex align-items-center gap-2 mt-2">
|
||||
<span>Page {data?.page ?? 1} / {totalPages}</span>
|
||||
<div className="ms-auto d-flex gap-2">
|
||||
<Link
|
||||
className={`btn btn-outline-secondary ${!data || (data.page <= 1) ? "disabled" : ""}`}
|
||||
aria-disabled={!data || data.page <= 1}
|
||||
href={`/zxdb/labels?${(() => { const p = new URLSearchParams(); if (q) p.set("q", q); p.set("page", String(Math.max(1, (data?.page ?? 1) - 1))); return p.toString(); })()}`}
|
||||
>
|
||||
Prev
|
||||
</Link>
|
||||
<Link
|
||||
className={`btn btn-outline-secondary ${!data || (data.page >= totalPages) ? "disabled" : ""}`}
|
||||
aria-disabled={!data || data.page >= totalPages}
|
||||
href={`/zxdb/labels?${(() => { const p = new URLSearchParams(); if (q) p.set("q", q); p.set("page", String(Math.min(totalPages, (data?.page ?? 1) + 1))); return p.toString(); })()}`}
|
||||
>
|
||||
Next
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<Pagination
|
||||
page={data?.page ?? 1}
|
||||
totalPages={totalPages}
|
||||
buildHref={buildHref}
|
||||
onPageChange={(p) => router.push(buildHref(p))}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user