Add multi-select machine filters

Replace machine dropdowns with multi-select chips and pass machine lists in queries.

Signed-off-by: codex@lucy.xalior.com
This commit is contained in:
2026-01-11 13:04:41 +00:00
parent 2f93ed1774
commit 1e8925e631
7 changed files with 193 additions and 65 deletions

View File

@@ -63,7 +63,7 @@ export interface SearchParams {
// Optional simple filters (ANDed together)
genreId?: number;
languageId?: string;
machinetypeId?: number;
machinetypeId?: number | number[];
// Sorting
sort?: "title" | "id_desc";
// Search scope (defaults to titles only)
@@ -177,7 +177,10 @@ export async function searchEntries(params: SearchParams): Promise<PagedResult<S
const offset = (page - 1) * pageSize;
const sort = params.sort ?? (q ? "title" : "id_desc");
const scope: EntrySearchScope = params.scope ?? "title";
const preferMachineOrder = typeof params.machinetypeId === "number"
const hasMachineFilter = Array.isArray(params.machinetypeId)
? params.machinetypeId.length > 0
: typeof params.machinetypeId === "number";
const preferMachineOrder = hasMachineFilter
? null
: sql`case
when ${entries.machinetypeId} = 27 then 0
@@ -190,7 +193,7 @@ export async function searchEntries(params: SearchParams): Promise<PagedResult<S
if (q.length === 0) {
// Default listing: return first page by id desc (no guaranteed ordering field; using id)
// Apply optional filters even without q
const whereClauses: Array<ReturnType<typeof eq>> = [];
const whereClauses: Array<ReturnType<typeof sql>> = [];
if (typeof params.genreId === "number") {
whereClauses.push(eq(entries.genretypeId, params.genreId));
}
@@ -199,6 +202,9 @@ export async function searchEntries(params: SearchParams): Promise<PagedResult<S
}
if (typeof params.machinetypeId === "number") {
whereClauses.push(eq(entries.machinetypeId, params.machinetypeId));
} else if (Array.isArray(params.machinetypeId) && params.machinetypeId.length > 0) {
const ids = params.machinetypeId.map((id) => sql`${id}`);
whereClauses.push(sql`${entries.machinetypeId} in (${sql.join(ids, sql`, `)})`);
}
const whereExpr = whereClauses.length ? and(...whereClauses) : undefined;
@@ -1655,7 +1661,12 @@ export async function getEntryFacets(params: SearchParams): Promise<EntryFacets>
}
if (params.genreId) whereParts.push(sql`e.genretype_id = ${params.genreId}`);
if (params.languageId) whereParts.push(sql`e.language_id = ${params.languageId}`);
if (params.machinetypeId) whereParts.push(sql`e.machinetype_id = ${params.machinetypeId}`);
if (typeof params.machinetypeId === "number") {
whereParts.push(sql`e.machinetype_id = ${params.machinetypeId}`);
} else if (Array.isArray(params.machinetypeId) && params.machinetypeId.length > 0) {
const ids = params.machinetypeId.map((id) => sql`${id}`);
whereParts.push(sql`e.machinetype_id in (${sql.join(ids, sql`, `)})`);
}
const whereSql = whereParts.length ? sql.join([sql`where `, sql.join(whereParts, sql` and `)], sql``) : sql``;
@@ -1733,7 +1744,7 @@ export interface ReleaseSearchParams {
sort?: "year_desc" | "year_asc" | "title" | "entry_id_desc";
// Optional download-based filters (matched via EXISTS on downloads)
dLanguageId?: string; // downloads.language_id
dMachinetypeId?: number; // downloads.machinetype_id
dMachinetypeId?: number | number[]; // downloads.machinetype_id
filetypeId?: number; // downloads.filetype_id
schemetypeId?: string; // downloads.schemetype_id
sourcetypeId?: string; // downloads.sourcetype_id
@@ -1754,7 +1765,10 @@ export async function searchReleases(params: ReleaseSearchParams): Promise<Paged
const pageSize = Math.max(1, Math.min(params.pageSize ?? 20, 100));
const page = Math.max(1, params.page ?? 1);
const offset = (page - 1) * pageSize;
const preferMachineOrder = params.dMachinetypeId != null
const hasMachineFilter = Array.isArray(params.dMachinetypeId)
? params.dMachinetypeId.length > 0
: params.dMachinetypeId != null;
const preferMachineOrder = hasMachineFilter
? null
: sql`case
when ${entries.machinetypeId} = 27 then 0
@@ -1780,7 +1794,12 @@ export async function searchReleases(params: ReleaseSearchParams): Promise<Paged
// would produce "from `d`" which MySQL interprets as a literal table.
const dlConds: Array<ReturnType<typeof sql>> = [];
if (params.dLanguageId) dlConds.push(sql`d.language_id = ${params.dLanguageId}`);
if (params.dMachinetypeId != null) dlConds.push(sql`d.machinetype_id = ${params.dMachinetypeId}`);
if (typeof params.dMachinetypeId === "number") {
dlConds.push(sql`d.machinetype_id = ${params.dMachinetypeId}`);
} else if (Array.isArray(params.dMachinetypeId) && params.dMachinetypeId.length > 0) {
const ids = params.dMachinetypeId.map((id) => sql`${id}`);
dlConds.push(sql`d.machinetype_id in (${sql.join(ids, sql`, `)})`);
}
if (params.filetypeId != null) dlConds.push(sql`d.filetype_id = ${params.filetypeId}`);
if (params.schemetypeId) dlConds.push(sql`d.schemetype_id = ${params.schemetypeId}`);
if (params.sourcetypeId) dlConds.push(sql`d.sourcetype_id = ${params.sourcetypeId}`);