fix(admin): make filters reactive in Svelte 5

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Justin Edmund 2025-10-07 07:30:01 -07:00
parent 42be8ebcfc
commit c67dbeaf38
3 changed files with 139 additions and 81 deletions

View file

@ -14,6 +14,10 @@
variant?: 'default' | 'minimal' variant?: 'default' | 'minimal'
fullWidth?: boolean fullWidth?: boolean
pill?: boolean pill?: boolean
onchange?: (event: Event) => void
oninput?: (event: Event) => void
onfocus?: (event: FocusEvent) => void
onblur?: (event: FocusEvent) => void
} }
let { let {
@ -23,6 +27,10 @@
variant = 'default', variant = 'default',
fullWidth = false, fullWidth = false,
pill = true, pill = true,
onchange,
oninput,
onfocus,
onblur,
class: className = '', class: className = '',
...restProps ...restProps
}: Props = $props() }: Props = $props()
@ -34,6 +42,10 @@
class="select select-{size} select-{variant} {className}" class="select select-{size} select-{variant} {className}"
class:select-full-width={fullWidth} class:select-full-width={fullWidth}
class:select-pill={pill} class:select-pill={pill}
onchange={(e) => onchange?.(e)}
oninput={(e) => oninput?.(e)}
onfocus={(e) => onfocus?.(e)}
onblur={(e) => onblur?.(e)}
{...restProps} {...restProps}
> >
{#each options as option} {#each options as option}

View file

@ -12,18 +12,19 @@
import type { PageData } from './$types' import type { PageData } from './$types'
import type { AdminPost } from '$lib/types/admin' import type { AdminPost } from '$lib/types/admin'
const { data, form } = $props<{ data: PageData; form?: { message?: string } }>() const { data, form } = $props<{ data: PageData; form?: { message?: string } }>()
let showInlineComposer = $state(true) let showInlineComposer = true
let showDeleteConfirmation = $state(false) let showDeleteConfirmation = false
let postToDelete = $state<AdminPost | null>(null) let postToDelete: AdminPost | null = null
let selectedTypeFilter = $state<string>('all') let selectedTypeFilter = 'all'
let selectedStatusFilter = $state<string>('all') let selectedStatusFilter = 'all'
let sortBy = $state<string>('newest') let sortBy = 'newest'
const actionError = $derived(form?.message ?? '') const actionError = form?.message ?? ''
const posts = $derived(data.items ?? []) const posts = data.items ?? []
let filteredPosts = $state<AdminPost[]>([...posts])
let toggleForm: HTMLFormElement | null = null let toggleForm: HTMLFormElement | null = null
let toggleIdField: HTMLInputElement | null = null let toggleIdField: HTMLInputElement | null = null
@ -33,17 +34,17 @@
let deleteForm: HTMLFormElement | null = null let deleteForm: HTMLFormElement | null = null
let deleteIdField: HTMLInputElement | null = null let deleteIdField: HTMLInputElement | null = null
const typeFilterOptions = $derived([ const typeFilterOptions = [
{ value: 'all', label: 'All posts' }, { value: 'all', label: 'All posts' },
{ value: 'post', label: 'Posts' }, { value: 'post', label: 'Posts' },
{ value: 'essay', label: 'Essays' } { value: 'essay', label: 'Essays' }
]) ]
const statusFilterOptions = $derived([ const statusFilterOptions = [
{ value: 'all', label: 'All statuses' }, { value: 'all', label: 'All statuses' },
{ value: 'published', label: 'Published' }, { value: 'published', label: 'Published' },
{ value: 'draft', label: 'Draft' } { value: 'draft', label: 'Draft' }
]) ]
const sortOptions = [ const sortOptions = [
{ value: 'newest', label: 'Newest first' }, { value: 'newest', label: 'Newest first' },
@ -54,7 +55,7 @@
{ value: 'status-draft', label: 'Draft first' } { value: 'status-draft', label: 'Draft first' }
] ]
const filteredPosts = $derived(() => { function applyFilterAndSort() {
let next = [...posts] let next = [...posts]
if (selectedTypeFilter !== 'all') { if (selectedTypeFilter !== 'all') {
@ -93,8 +94,22 @@
break break
} }
return next filteredPosts = next
}) }
applyFilterAndSort()
function handleTypeFilterChange() {
applyFilterAndSort()
}
function handleStatusFilterChange() {
applyFilterAndSort()
}
function handleSortChange() {
applyFilterAndSort()
}
onMount(() => { onMount(() => {
document.addEventListener('click', handleOutsideClick) document.addEventListener('click', handleOutsideClick)
@ -184,16 +199,24 @@
options={typeFilterOptions} options={typeFilterOptions}
size="small" size="small"
variant="minimal" variant="minimal"
onchange={handleTypeFilterChange}
/> />
<Select <Select
bind:value={selectedStatusFilter} bind:value={selectedStatusFilter}
options={statusFilterOptions} options={statusFilterOptions}
size="small" size="small"
variant="minimal" variant="minimal"
onchange={handleStatusFilterChange}
/> />
{/snippet} {/snippet}
{#snippet right()} {#snippet right()}
<Select bind:value={sortBy} options={sortOptions} size="small" variant="minimal" /> <Select
bind:value={sortBy}
options={sortOptions}
size="small"
variant="minimal"
onchange={handleSortChange}
/>
{/snippet} {/snippet}
</AdminFilters> </AdminFilters>

View file

@ -13,15 +13,16 @@
const { data, form } = $props<{ data: PageData; form?: { message?: string } }>() const { data, form } = $props<{ data: PageData; form?: { message?: string } }>()
let showDeleteModal = $state(false) let showDeleteModal = false
let projectToDelete = $state<AdminProject | null>(null) let projectToDelete: AdminProject | null = null
let selectedTypeFilter = $state<string>('all') let selectedTypeFilter: string = 'all'
let selectedStatusFilter = $state<string>('all') let selectedStatusFilter: string = 'all'
let sortBy = $state<string>('newest') let sortBy: string = 'newest'
const actionError = $derived(form?.message ?? '') const actionError = form?.message ?? ''
const projects = $derived(data.items ?? []) const projects = data.items ?? []
let filteredProjects = $state<AdminProject[]>([...projects])
let toggleForm: HTMLFormElement | null = null let toggleForm: HTMLFormElement | null = null
let toggleIdField: HTMLInputElement | null = null let toggleIdField: HTMLInputElement | null = null
@ -31,17 +32,17 @@
let deleteForm: HTMLFormElement | null = null let deleteForm: HTMLFormElement | null = null
let deleteIdField: HTMLInputElement | null = null let deleteIdField: HTMLInputElement | null = null
const typeFilterOptions = $derived([ const typeFilterOptions = [
{ value: 'all', label: 'All projects' }, { value: 'all', label: 'All projects' },
{ value: 'work', label: 'Work' }, { value: 'work', label: 'Work' },
{ value: 'labs', label: 'Labs' } { value: 'labs', label: 'Labs' }
]) ]
const statusFilterOptions = $derived([ const statusFilterOptions = [
{ value: 'all', label: 'All statuses' }, { value: 'all', label: 'All statuses' },
{ value: 'published', label: 'Published' }, { value: 'published', label: 'Published' },
{ value: 'draft', label: 'Draft' } { value: 'draft', label: 'Draft' }
]) ]
const sortOptions = [ const sortOptions = [
{ value: 'newest', label: 'Newest first' }, { value: 'newest', label: 'Newest first' },
@ -54,7 +55,7 @@
{ value: 'status-draft', label: 'Draft first' } { value: 'status-draft', label: 'Draft first' }
] ]
const filteredProjects = $derived(() => { function applyFilterAndSort() {
let next = [...projects] let next = [...projects]
if (selectedStatusFilter !== 'all') { if (selectedStatusFilter !== 'all') {
@ -99,8 +100,22 @@
break break
} }
return next filteredProjects = next
}) }
applyFilterAndSort()
function handleTypeFilterChange() {
applyFilterAndSort()
}
function handleStatusFilterChange() {
applyFilterAndSort()
}
function handleSortChange() {
applyFilterAndSort()
}
onMount(() => { onMount(() => {
document.addEventListener('click', handleOutsideClick) document.addEventListener('click', handleOutsideClick)
@ -174,16 +189,24 @@
options={typeFilterOptions} options={typeFilterOptions}
size="small" size="small"
variant="minimal" variant="minimal"
onchange={handleTypeFilterChange}
/> />
<Select <Select
bind:value={selectedStatusFilter} bind:value={selectedStatusFilter}
options={statusFilterOptions} options={statusFilterOptions}
size="small" size="small"
variant="minimal" variant="minimal"
onchange={handleStatusFilterChange}
/> />
{/snippet} {/snippet}
{#snippet right()} {#snippet right()}
<Select bind:value={sortBy} options={sortOptions} size="small" variant="minimal" /> <Select
bind:value={sortBy}
options={sortOptions}
size="small"
variant="minimal"
onchange={handleSortChange}
/>
{/snippet} {/snippet}
</AdminFilters> </AdminFilters>