extend DatabaseGridWithProvider to support jobs

- add searchJobs method and job-specific filters to search adapter
- add jobs case to DatabaseProvider
- extend DatabaseGridWithProvider for jobs resource
- hide collection filters for jobs (no element/rarity/series)
- extend filterParams and listNavigation for job entity
This commit is contained in:
Justin Edmund 2026-01-04 02:50:59 -08:00
parent 23527c727e
commit ec4b4bc3ae
6 changed files with 114 additions and 26 deletions

View file

@ -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<SearchResponse> {
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<SearchResponse>('/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 * Clears all cached search results
* Useful when entity data has been updated * Useful when entity data has been updated

View file

@ -191,6 +191,23 @@ export interface SearchFilters {
/** Filter special characters */ /** Filter special characters */
special?: boolean | undefined 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 */ /** Custom filters for specific use cases */
[key: string]: any [key: string]: any
} }

View file

@ -28,7 +28,7 @@
import type { Snippet } from 'svelte' import type { Snippet } from 'svelte'
interface Props { interface Props {
resource: 'weapons' | 'characters' | 'summons' resource: 'weapons' | 'characters' | 'summons' | 'jobs'
columns: IColumn[] columns: IColumn[]
pageSize?: number pageSize?: number
leftActions?: Snippet leftActions?: Snippet
@ -45,9 +45,14 @@
// Derive entity type from resource // Derive entity type from resource
const entityType = $derived( 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) // Fetch weapon series list for URL slug mapping (only for weapons)
const weaponSeriesQuery = createQuery(() => const weaponSeriesQuery = createQuery(() =>
queryOptions({ queryOptions({
@ -403,29 +408,31 @@
{@render headerActions()} {@render headerActions()}
{/if} {/if}
<Button {#if supportsCollectionFilters}
variant="ghost" <Button
size="small" variant="ghost"
onclick={() => (showFilters = !showFilters)} size="small"
class="filter-toggle {hasActiveFilters ? 'has-active' : ''}" onclick={() => (showFilters = !showFilters)}
> class="filter-toggle {hasActiveFilters ? 'has-active' : ''}"
Filters >
{#if hasActiveFilters} Filters
<span class="filter-count {selectedElement ?? ''}"> {#if hasActiveFilters}
{elementFilters.length + <span class="filter-count {selectedElement ?? ''}">
rarityFilters.length + {elementFilters.length +
seriesFilters.length + rarityFilters.length +
proficiencyFilters.length + seriesFilters.length +
seasonFilters.length} proficiencyFilters.length +
</span> seasonFilters.length}
{/if} </span>
</Button> {/if}
</Button>
{/if}
<input type="text" placeholder="Search..." bind:value={searchTerm} /> <input type="text" placeholder="Search..." bind:value={searchTerm} />
</div> </div>
</div> </div>
{#if showFilters} {#if showFilters && supportsCollectionFilters}
<div class="filters-row"> <div class="filters-row">
<CollectionFilters <CollectionFilters
entityType={resource === 'characters' entityType={resource === 'characters'

View file

@ -4,7 +4,7 @@ import type { SearchParams } from '$lib/api/adapters/search.adapter'
import type { SearchFilters } from '$lib/api/adapters/types' import type { SearchFilters } from '$lib/api/adapters/types'
interface DatabaseProviderOptions { interface DatabaseProviderOptions {
resource: 'weapons' | 'characters' | 'summons' resource: 'weapons' | 'characters' | 'summons' | 'jobs'
pageSize?: number pageSize?: number
} }
@ -19,7 +19,7 @@ interface APIResponse {
} }
export class DatabaseProvider extends RestDataProvider<any> { export class DatabaseProvider extends RestDataProvider<any> {
private resource: 'weapons' | 'characters' | 'summons' private resource: 'weapons' | 'characters' | 'summons' | 'jobs'
private pageSize: number private pageSize: number
private currentPage: number = 1 private currentPage: number = 1
private totalCount: number = 0 private totalCount: number = 0
@ -72,6 +72,9 @@ export class DatabaseProvider extends RestDataProvider<any> {
case 'summons': case 'summons':
result = await searchAdapter.searchSummons(searchParams) result = await searchAdapter.searchSummons(searchParams)
break break
case 'jobs':
result = await searchAdapter.searchJobs(searchParams)
break
default: default:
throw new Error(`Unknown resource type: ${this.resource}`) throw new Error(`Unknown resource type: ${this.resource}`)
} }

View file

@ -181,7 +181,7 @@ function parseParamArray<T>(
*/ */
export function parseFiltersFromUrl( export function parseFiltersFromUrl(
searchParams: URLSearchParams, searchParams: URLSearchParams,
entityType: 'character' | 'weapon' | 'summon', entityType: 'character' | 'weapon' | 'summon' | 'job',
weaponSeriesList?: WeaponSeries[] weaponSeriesList?: WeaponSeries[]
): ParsedFilters { ): ParsedFilters {
const element = parseParamArray(searchParams, 'element', PARAM_TO_ELEMENT) const element = parseParamArray(searchParams, 'element', PARAM_TO_ELEMENT)
@ -252,7 +252,7 @@ export function buildUrlFromFilters(
filters: CollectionFilterState, filters: CollectionFilterState,
searchQuery: string, searchQuery: string,
page: number, page: number,
entityType: 'character' | 'weapon' | 'summon', entityType: 'character' | 'weapon' | 'summon' | 'job',
weaponSeriesList?: WeaponSeries[] weaponSeriesList?: WeaponSeries[]
): URLSearchParams { ): URLSearchParams {
const params = new URLSearchParams() const params = new URLSearchParams()

View file

@ -8,13 +8,13 @@ const STORAGE_KEY = 'database_list_url'
interface StoredListUrl { interface StoredListUrl {
url: string url: string
resource: 'characters' | 'weapons' | 'summons' resource: 'characters' | 'weapons' | 'summons' | 'jobs'
} }
/** /**
* Store the current list URL before navigating to a detail page * 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 { try {
const data: StoredListUrl = { url, resource } const data: StoredListUrl = { url, resource }
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(data)) sessionStorage.setItem(STORAGE_KEY, JSON.stringify(data))