From 5403aebe48ff59efa65efb2fbbd33cc67cf9b581 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Fri, 28 Nov 2025 11:04:16 -0800 Subject: [PATCH] fix job skill types and slot styling - update JobSkill type with emp/base boolean flags - use new skill fields in jobUtils and components - update job adapter with locale and filter params - restyle empty skill slots with cleaner placeholder - simplify ML badge to not show level number --- src/lib/api/adapters/job.adapter.ts | 15 ++-- src/lib/components/job/JobSection.svelte | 4 +- src/lib/components/job/JobSkillItem.svelte | 5 +- src/lib/components/job/JobSkillSlot.svelte | 49 ++++++++--- .../sidebar/JobSkillSelectionSidebar.svelte | 86 ++++++++++++++++--- src/lib/types/api/entities.ts | 16 ++-- src/lib/utils/jobUtils.ts | 15 ++-- 7 files changed, 138 insertions(+), 52 deletions(-) diff --git a/src/lib/api/adapters/job.adapter.ts b/src/lib/api/adapters/job.adapter.ts index 4d6643d3..96c8d963 100644 --- a/src/lib/api/adapters/job.adapter.ts +++ b/src/lib/api/adapters/job.adapter.ts @@ -21,6 +21,8 @@ export interface SearchJobSkillsParams { jobId: string // Required for API page?: number per?: number + locale?: string + filters?: { group?: number } } /** @@ -114,12 +116,13 @@ export class JobAdapter extends BaseAdapter { body: { search: { query: params.query || '', - job: params.jobId - }, - page: params.page || 1, - per: params.per || 50 - }, - cacheTTL: 60000 // Cache for 1 minute + job: params.jobId, + page: params.page || 1, + locale: params.locale || 'en', + filters: params.filters || {} + } + } + // Note: No caching - filters change frequently and cache key bug causes stale data }) // Transform the response to match the expected format diff --git a/src/lib/components/job/JobSection.svelte b/src/lib/components/job/JobSection.svelte index 5945fc96..bd2ea448 100644 --- a/src/lib/components/job/JobSection.svelte +++ b/src/lib/components/job/JobSection.svelte @@ -93,7 +93,7 @@ {#if job.masterLevel || job.ultimateMastery}
{#if job.masterLevel} - ML{job.masterLevel} + ML {/if} {#if job.ultimateMastery} UM @@ -110,7 +110,7 @@ {#if job.masterLevel || job.ultimateMastery}
{#if job.masterLevel} - ML{job.masterLevel} + ML {/if} {#if job.ultimateMastery} UM diff --git a/src/lib/components/job/JobSkillItem.svelte b/src/lib/components/job/JobSkillItem.svelte index cc635499..19171f66 100644 --- a/src/lib/components/job/JobSkillItem.svelte +++ b/src/lib/components/job/JobSkillItem.svelte @@ -23,9 +23,8 @@ function getSkillColorClass(skill: JobSkill): string { if (skill.main) return 'skill-main' if (skill.sub) return 'skill-sub' - // Use category for additional classification - if (skill.category === 2) return 'skill-emp' - if (skill.category === 1) return 'skill-base' + if (skill.emp) return 'skill-emp' + if (skill.base) return 'skill-base' return '' } diff --git a/src/lib/components/job/JobSkillSlot.svelte b/src/lib/components/job/JobSkillSlot.svelte index 342fa33a..d2576995 100644 --- a/src/lib/components/job/JobSkillSlot.svelte +++ b/src/lib/components/job/JobSkillSlot.svelte @@ -112,8 +112,10 @@ {#snippet EmptyState({ slot })}
- - Slot {slot + 1} +
+ +
+ Select a skill
{/snippet} @@ -154,12 +156,19 @@ } &.empty { - border-style: dashed; - background: var(--placeholder-bg); + background: transparent; &.editable:hover { - background: var(--button-contained-bg-hover); - border-style: solid; + background: var(--button-bg-hover); + + .placeholder-icon { + border-color: var(--border-medium); + box-shadow: var(--hover-shadow); + } + + .placeholder-text { + color: var(--text-tertiary-hover); + } } } @@ -222,16 +231,30 @@ .empty-content { display: flex; - flex-direction: column; + flex-direction: row; align-items: center; - justify-content: center; - gap: 4px; + gap: $unit; padding: $unit; - color: var(--text-tertiary); - height: 60px; + height: 100%; - span { - font-size: 12px; + .placeholder-icon { + width: 32px; + height: 32px; + background: var(--card-bg); + border: 1px solid transparent; + border-radius: $unit-half; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + color: var(--icon-secondary); + transition: all 0.15s ease; + } + + .placeholder-text { + font-size: 14px; + color: var(--text-tertiary); + transition: color 0.15s ease; } } diff --git a/src/lib/components/sidebar/JobSkillSelectionSidebar.svelte b/src/lib/components/sidebar/JobSkillSelectionSidebar.svelte index 43a33a57..6394f3e6 100644 --- a/src/lib/components/sidebar/JobSkillSelectionSidebar.svelte +++ b/src/lib/components/sidebar/JobSkillSelectionSidebar.svelte @@ -8,6 +8,7 @@ import JobSkillItem from '../job/JobSkillItem.svelte' import Button from '../ui/Button.svelte' import Input from '../ui/Input.svelte' + import Select from '../ui/Select.svelte' import Icon from '../Icon.svelte' import * as m from '$lib/paraglide/messages' @@ -27,13 +28,28 @@ onRemoveSkill }: Props = $props() + // Skill category filter options + // Values match Rails search_controller expectations: + // -1: All, 0-3: Color categories, 4: EMP, 5: Base + const skillCategoryOptions = [ + { value: -1, label: 'All Skills' }, + { value: 2, label: 'Damaging' }, + { value: 0, label: 'Buffing' }, + { value: 1, label: 'Debuffing' }, + { value: 3, label: 'Healing' }, + { value: 4, label: 'EMP' }, + { value: 5, label: 'Base' } + ] + // State let searchQuery = $state('') + let skillCategory = $state(-1) // -1 = All let error = $state() let sentinelEl = $state() let skillsResource = $state> | null>(null) let lastSearchQuery = '' let lastJobId: string | undefined + let lastCategory = -1 // Check if slot is locked const slotLocked = $derived(targetSlot === 0) @@ -44,9 +60,10 @@ // Manage resource creation and search updates let debounceTimer: ReturnType | undefined - function updateSearch() { + function updateSearch(forceRebuild = false) { const jobId = job?.id const locked = slotLocked + const category = skillCategory // Clean up if no job or locked if (!jobId || locked) { @@ -56,21 +73,29 @@ } lastJobId = undefined lastSearchQuery = '' + lastCategory = -1 return } - // Create new resource if job changed - if (jobId !== lastJobId) { + // Rebuild resource if job or category changed + const needsRebuild = forceRebuild || jobId !== lastJobId || category !== lastCategory + + if (needsRebuild) { if (skillsResource) { skillsResource.destroy() } + // Capture current values for the closure + const currentQuery = searchQuery + const currentCategory = category + const resource = createInfiniteScrollResource({ fetcher: async (page) => { const response = await jobAdapter.searchSkills({ - query: lastSearchQuery, + query: currentQuery, jobId, - page + page, + ...(currentCategory >= 0 ? { filters: { group: currentCategory } } : {}) }) return response }, @@ -81,19 +106,21 @@ skillsResource = resource lastJobId = jobId lastSearchQuery = searchQuery + lastCategory = category resource.load() } - // Reload if search query changed + // Reload if only search query changed else if (searchQuery !== lastSearchQuery && skillsResource) { lastSearchQuery = searchQuery - skillsResource.reset() - skillsResource.load() + // Need to rebuild to capture new query in closure + updateSearch(true) } } - // Watch for job changes + // Watch for job and category changes $effect(() => { job // Track job + skillCategory // Track category updateSearch() }) @@ -198,6 +225,16 @@ disabled={!canSearch} fullWidth={true} /> +
+