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:
parent
42be8ebcfc
commit
c67dbeaf38
3 changed files with 139 additions and 81 deletions
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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,47 +55,61 @@
|
||||||
{ 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') {
|
||||||
next = next.filter((post) => post.postType === selectedTypeFilter)
|
next = next.filter((post) => post.postType === selectedTypeFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedStatusFilter !== 'all') {
|
if (selectedStatusFilter !== 'all') {
|
||||||
next = next.filter((post) => post.status === selectedStatusFilter)
|
next = next.filter((post) => post.status === selectedStatusFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (sortBy) {
|
switch (sortBy) {
|
||||||
case 'oldest':
|
case 'oldest':
|
||||||
next.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime())
|
next.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime())
|
||||||
break
|
break
|
||||||
case 'title-asc':
|
case 'title-asc':
|
||||||
next.sort((a, b) => (a.title || '').localeCompare(b.title || ''))
|
next.sort((a, b) => (a.title || '').localeCompare(b.title || ''))
|
||||||
break
|
break
|
||||||
case 'title-desc':
|
case 'title-desc':
|
||||||
next.sort((a, b) => (b.title || '').localeCompare(a.title || ''))
|
next.sort((a, b) => (b.title || '').localeCompare(a.title || ''))
|
||||||
break
|
break
|
||||||
case 'status-published':
|
case 'status-published':
|
||||||
next.sort((a, b) => {
|
next.sort((a, b) => {
|
||||||
if (a.status === b.status) return 0
|
if (a.status === b.status) return 0
|
||||||
return a.status === 'published' ? -1 : 1
|
return a.status === 'published' ? -1 : 1
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
case 'status-draft':
|
case 'status-draft':
|
||||||
next.sort((a, b) => {
|
next.sort((a, b) => {
|
||||||
if (a.status === b.status) return 0
|
if (a.status === b.status) return 0
|
||||||
return a.status === 'draft' ? -1 : 1
|
return a.status === 'draft' ? -1 : 1
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
case 'newest':
|
case 'newest':
|
||||||
default:
|
default:
|
||||||
next.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
|
next.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
|
||||||
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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -169,21 +184,29 @@
|
||||||
|
|
||||||
<AdminFilters>
|
<AdminFilters>
|
||||||
{#snippet left()}
|
{#snippet left()}
|
||||||
<Select
|
<Select
|
||||||
bind:value={selectedTypeFilter}
|
bind:value={selectedTypeFilter}
|
||||||
options={typeFilterOptions}
|
options={typeFilterOptions}
|
||||||
size="small"
|
size="small"
|
||||||
variant="minimal"
|
variant="minimal"
|
||||||
/>
|
onchange={handleTypeFilterChange}
|
||||||
<Select
|
/>
|
||||||
bind:value={selectedStatusFilter}
|
<Select
|
||||||
options={statusFilterOptions}
|
bind:value={selectedStatusFilter}
|
||||||
size="small"
|
options={statusFilterOptions}
|
||||||
variant="minimal"
|
size="small"
|
||||||
/>
|
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>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue