diff --git a/src/lib/api/adapters/search.adapter.ts b/src/lib/api/adapters/search.adapter.ts index a24a0628..d7a4e557 100644 --- a/src/lib/api/adapters/search.adapter.ts +++ b/src/lib/api/adapters/search.adapter.ts @@ -377,6 +377,67 @@ export class SearchAdapter extends BaseAdapter { }) } + /** + * Searches for jobs with specific filters + * + * @param params - Search parameters with job-specific filters + * @returns Promise resolving to job search results + */ + async searchJobs(params: SearchParams = {}): Promise { + const body: any = { + locale: params.locale || 'en', + page: params.page || 1 + } + + if (params.per) { + body.per = params.per + } + + if (params.query) { + body.query = params.query + } + + if (params.sortBy) { + body.sort = params.sortBy + } + if (params.sortOrder) { + body.order = params.sortOrder + } + + // Build job-specific filters + if (params.filters) { + const filters: any = {} + + if (params.filters.row?.length) { + filters.row = params.filters.row + } + if (params.filters.proficiency?.length) { + filters.proficiency = params.filters.proficiency + } + if (params.filters.masterLevel !== undefined) { + filters.masterLevel = params.filters.masterLevel + } + if (params.filters.ultimateMastery !== undefined) { + filters.ultimateMastery = params.filters.ultimateMastery + } + if (params.filters.accessory !== undefined) { + filters.accessory = params.filters.accessory + } + + if (Object.keys(filters).length > 0) { + body.filters = filters + } + } + + return this.request('/search/jobs', { + method: 'POST', + body: { search: body }, + credentials: 'omit', + cacheTTL: params.query ? 300000 : 0, + headers: params.per ? { 'X-Per-Page': String(params.per) } : undefined + }) + } + /** * Clears all cached search results * Useful when entity data has been updated diff --git a/src/lib/api/adapters/types.ts b/src/lib/api/adapters/types.ts index 2dc76909..3caf9f08 100644 --- a/src/lib/api/adapters/types.ts +++ b/src/lib/api/adapters/types.ts @@ -191,6 +191,23 @@ export interface SearchFilters { /** Filter special characters */ special?: boolean | undefined + // Job-specific filters + + /** Filter by job row (1-5, ex, ex2, o1) */ + row?: string[] | undefined + + /** Filter by proficiency (for jobs - matches either proficiency1 or proficiency2) */ + proficiency?: number[] | undefined + + /** Filter jobs with master level */ + masterLevel?: boolean | undefined + + /** Filter jobs with ultimate mastery */ + ultimateMastery?: boolean | undefined + + /** Filter jobs that support accessories */ + accessory?: boolean | undefined + /** Custom filters for specific use cases */ [key: string]: any } diff --git a/src/lib/components/database/DatabaseGridWithProvider.svelte b/src/lib/components/database/DatabaseGridWithProvider.svelte index 2f7a3a13..22e54886 100644 --- a/src/lib/components/database/DatabaseGridWithProvider.svelte +++ b/src/lib/components/database/DatabaseGridWithProvider.svelte @@ -28,7 +28,7 @@ import type { Snippet } from 'svelte' interface Props { - resource: 'weapons' | 'characters' | 'summons' + resource: 'weapons' | 'characters' | 'summons' | 'jobs' columns: IColumn[] pageSize?: number leftActions?: Snippet @@ -45,9 +45,14 @@ // Derive entity type from resource const entityType = $derived( - resource === 'characters' ? 'character' : resource === 'summons' ? 'summon' : 'weapon' + resource === 'characters' ? 'character' : + resource === 'summons' ? 'summon' : + resource === 'jobs' ? 'job' : 'weapon' ) + // Jobs don't use the standard CollectionFilters component + const supportsCollectionFilters = $derived(resource !== 'jobs') + // Fetch weapon series list for URL slug mapping (only for weapons) const weaponSeriesQuery = createQuery(() => queryOptions({ @@ -403,29 +408,31 @@ {@render headerActions()} {/if} - + {#if supportsCollectionFilters} + + {/if} - {#if showFilters} + {#if showFilters && supportsCollectionFilters}
{ - private resource: 'weapons' | 'characters' | 'summons' + private resource: 'weapons' | 'characters' | 'summons' | 'jobs' private pageSize: number private currentPage: number = 1 private totalCount: number = 0 @@ -72,6 +72,9 @@ export class DatabaseProvider extends RestDataProvider { case 'summons': result = await searchAdapter.searchSummons(searchParams) break + case 'jobs': + result = await searchAdapter.searchJobs(searchParams) + break default: throw new Error(`Unknown resource type: ${this.resource}`) } diff --git a/src/lib/utils/filterParams.ts b/src/lib/utils/filterParams.ts index ea3730f3..ea4a5e3c 100644 --- a/src/lib/utils/filterParams.ts +++ b/src/lib/utils/filterParams.ts @@ -181,7 +181,7 @@ function parseParamArray( */ export function parseFiltersFromUrl( searchParams: URLSearchParams, - entityType: 'character' | 'weapon' | 'summon', + entityType: 'character' | 'weapon' | 'summon' | 'job', weaponSeriesList?: WeaponSeries[] ): ParsedFilters { const element = parseParamArray(searchParams, 'element', PARAM_TO_ELEMENT) @@ -252,7 +252,7 @@ export function buildUrlFromFilters( filters: CollectionFilterState, searchQuery: string, page: number, - entityType: 'character' | 'weapon' | 'summon', + entityType: 'character' | 'weapon' | 'summon' | 'job', weaponSeriesList?: WeaponSeries[] ): URLSearchParams { const params = new URLSearchParams() diff --git a/src/lib/utils/listNavigation.ts b/src/lib/utils/listNavigation.ts index 582aac0e..a72219b7 100644 --- a/src/lib/utils/listNavigation.ts +++ b/src/lib/utils/listNavigation.ts @@ -8,13 +8,13 @@ const STORAGE_KEY = 'database_list_url' interface StoredListUrl { url: string - resource: 'characters' | 'weapons' | 'summons' + resource: 'characters' | 'weapons' | 'summons' | 'jobs' } /** * Store the current list URL before navigating to a detail page */ -export function storeListUrl(url: string, resource: 'characters' | 'weapons' | 'summons'): void { +export function storeListUrl(url: string, resource: 'characters' | 'weapons' | 'summons' | 'jobs'): void { try { const data: StoredListUrl = { url, resource } sessionStorage.setItem(STORAGE_KEY, JSON.stringify(data))