Expand ZXDB entry data and search

Add entry release/license sections, label permissions/licenses,
expanded search scope (titles+aliases+origins), and home search.
Also include ZXDB submodule and update gitignore.

Signed-off-by: codex@lucy.xalior.com
This commit is contained in:
2026-01-10 18:04:04 +00:00
parent 3e13da5552
commit e2f6aac856
8 changed files with 422 additions and 9 deletions

View File

@@ -16,6 +16,8 @@ type Item = {
languageName?: string | null;
};
type SearchScope = "title" | "title_aliases" | "title_aliases_origins";
type Paged<T> = {
items: T[];
page: number;
@@ -41,6 +43,7 @@ export default function EntriesExplorer({
languageId: string | "";
machinetypeId: string | number | "";
sort: "title" | "id_desc";
scope?: SearchScope;
};
}) {
const router = useRouter();
@@ -61,6 +64,7 @@ export default function EntriesExplorer({
initialUrlState?.machinetypeId === "" ? "" : initialUrlState?.machinetypeId ? Number(initialUrlState.machinetypeId) : ""
);
const [sort, setSort] = useState<"title" | "id_desc">(initialUrlState?.sort ?? "id_desc");
const [scope, setScope] = useState<SearchScope>(initialUrlState?.scope ?? "title");
const pageSize = 20;
const totalPages = useMemo(() => (data ? Math.max(1, Math.ceil(data.total / data.pageSize)) : 1), [data]);
@@ -73,6 +77,7 @@ export default function EntriesExplorer({
if (languageId !== "") params.set("languageId", String(languageId));
if (machinetypeId !== "") params.set("machinetypeId", String(machinetypeId));
if (sort) params.set("sort", sort);
if (scope !== "title") params.set("scope", scope);
const qs = params.toString();
router.replace(qs ? `${pathname}?${qs}` : pathname);
}
@@ -88,6 +93,7 @@ export default function EntriesExplorer({
if (languageId !== "") params.set("languageId", String(languageId));
if (machinetypeId !== "") params.set("machinetypeId", String(machinetypeId));
if (sort) params.set("sort", sort);
if (scope !== "title") params.set("scope", scope);
const res = await fetch(`/api/zxdb/search?${params.toString()}`);
if (!res.ok) throw new Error(`Failed: ${res.status}`);
const json: Paged<Item> = await res.json();
@@ -120,7 +126,8 @@ export default function EntriesExplorer({
(initialUrlState?.languageId ?? "") === (languageId ?? "") &&
(initialUrlState?.machinetypeId === "" ? "" : Number(initialUrlState?.machinetypeId ?? "")) ===
(machinetypeId === "" ? "" : Number(machinetypeId)) &&
sort === (initialUrlState?.sort ?? "id_desc")
sort === (initialUrlState?.sort ?? "id_desc") &&
(initialUrlState?.scope ?? "title") === scope
) {
updateUrl(page);
return;
@@ -128,7 +135,7 @@ export default function EntriesExplorer({
updateUrl(page);
fetchData(q, page);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [page, genreId, languageId, machinetypeId, sort]);
}, [page, genreId, languageId, machinetypeId, sort, scope]);
// Load filter lists on mount only if not provided by server
useEffect(() => {
@@ -163,8 +170,9 @@ export default function EntriesExplorer({
if (languageId !== "") params.set("languageId", String(languageId));
if (machinetypeId !== "") params.set("machinetypeId", String(machinetypeId));
if (sort) params.set("sort", sort);
if (scope !== "title") params.set("scope", scope);
return `/zxdb/entries?${params.toString()}`;
}, [q, data?.page, genreId, languageId, machinetypeId, sort]);
}, [q, data?.page, genreId, languageId, machinetypeId, sort, scope]);
const nextHref = useMemo(() => {
const params = new URLSearchParams();
@@ -174,8 +182,9 @@ export default function EntriesExplorer({
if (languageId !== "") params.set("languageId", String(languageId));
if (machinetypeId !== "") params.set("machinetypeId", String(machinetypeId));
if (sort) params.set("sort", sort);
if (scope !== "title") params.set("scope", scope);
return `/zxdb/entries?${params.toString()}`;
}, [q, data?.page, genreId, languageId, machinetypeId, sort]);
}, [q, data?.page, genreId, languageId, machinetypeId, sort, scope]);
return (
<div>
@@ -230,6 +239,13 @@ export default function EntriesExplorer({
<option value="id_desc">Sort: Newest</option>
</select>
</div>
<div className="col-auto">
<select className="form-select" value={scope} onChange={(e) => { setScope(e.target.value as SearchScope); setPage(1); }}>
<option value="title">Search: Titles</option>
<option value="title_aliases">Search: Titles + Aliases</option>
<option value="title_aliases_origins">Search: Titles + Aliases + Origins</option>
</select>
</div>
{loading && (
<div className="col-auto text-secondary">Loading...</div>
)}