feat: enhance database pages with uncap columns

This commit is contained in:
Justin Edmund 2025-09-17 10:45:44 -07:00
parent 6f428f2aa3
commit 666109ef7d
6 changed files with 100 additions and 41 deletions

View file

@ -8,7 +8,7 @@
import { Grid } from 'wx-svelte-grid'
import type { IColumn, IRow } from 'wx-svelte-grid'
import { DatabaseProvider } from '$lib/providers/DatabaseProvider'
import { onMount } from 'svelte'
import { onMount, onDestroy } from 'svelte'
import { goto } from '$app/navigation'
interface Props {
@ -31,6 +31,7 @@
let total = $state(0)
let searchTerm = $state('')
let pageSize = $state(initialPageSize)
let searchTimeout: ReturnType<typeof setTimeout> | undefined
// Create provider
const provider = new DatabaseProvider({ resource, pageSize: initialPageSize })
@ -101,31 +102,31 @@
const handlePageSizeChange = async (event: Event) => {
const target = event.target as HTMLSelectElement
const newPageSize = Number(target.value)
pageSize = newPageSize // Update local state immediately
await provider.setPageSize(newPageSize)
loadData(1)
}
// Filter data based on search
const filteredData = $derived.by(() => {
if (!searchTerm) return data
// Handle search with debounce
const handleSearch = (term: string) => {
// Clear existing timeout
if (searchTimeout) {
clearTimeout(searchTimeout)
}
const term = searchTerm.toLowerCase()
return data.filter(item => {
// Search in name
if (item.name) {
const name = typeof item.name === 'string'
? item.name
: item.name.en || item.name.ja || ''
if (name.toLowerCase().includes(term)) return true
}
// Set new timeout for debounced search
searchTimeout = setTimeout(() => {
provider.setSearchQuery(term)
loadData(1) // Reset to first page when searching
}, 300) // 300ms debounce
}
// Search in other string fields
return Object.values(item).some(value =>
typeof value === 'string' && value.toLowerCase().includes(term)
)
})
// Watch for search term changes
$effect(() => {
handleSearch(searchTerm)
})
// Computed values
const startItem = $derived((currentPage - 1) * pageSize + 1)
const endItem = $derived(Math.min(currentPage * pageSize, total))
@ -134,6 +135,13 @@
onMount(() => {
loadData()
})
// Clean up timeout on destroy
onDestroy(() => {
if (searchTimeout) {
clearTimeout(searchTimeout)
}
})
</script>
<div class="database-grid">
@ -166,7 +174,7 @@
{/if}
<Grid
data={filteredData}
data={data}
{columns}
{init}
sizes={{ rowHeight: 80 }}

View file

@ -1,10 +1,26 @@
<script lang="ts">
import { localizeHref } from '$lib/paraglide/runtime'
import { onMount } from 'svelte'
const baseHref = localizeHref('/database')
const summonsHref = localizeHref('/database/summons')
const charactersHref = localizeHref('/database/characters')
const weaponsHref = localizeHref('/database/weapons')
// Apply wider layout to the main element for database pages
onMount(() => {
const main = document.querySelector('main')
if (main) {
main.classList.add('database-layout')
}
return () => {
const main = document.querySelector('main')
if (main) {
main.classList.remove('database-layout')
}
}
})
</script>
<section class="db-nav">

View file

@ -5,6 +5,8 @@
import type { IColumn } from 'wx-svelte-grid'
import CharacterImageCell from '$lib/components/database/cells/CharacterImageCell.svelte'
import ElementCell from '$lib/components/database/cells/ElementCell.svelte'
import CharacterUncapCell from '$lib/components/database/cells/CharacterUncapCell.svelte'
import LastUpdatedCell from '$lib/components/database/cells/LastUpdatedCell.svelte'
import { getRarityLabel } from '$lib/utils/rarity'
// Column configuration for characters
@ -43,10 +45,17 @@
cell: ElementCell
},
{
id: 'max_level',
header: 'Max Level',
width: 80,
sort: true
id: 'uncap',
header: 'Uncap',
width: 160,
cell: CharacterUncapCell
},
{
id: 'last_updated',
header: 'Last Updated',
width: 120,
sort: true,
cell: LastUpdatedCell
}
]
</script>

View file

@ -5,6 +5,8 @@
import type { IColumn } from 'wx-svelte-grid'
import SummonImageCell from '$lib/components/database/cells/SummonImageCell.svelte'
import ElementCell from '$lib/components/database/cells/ElementCell.svelte'
import SummonUncapCell from '$lib/components/database/cells/SummonUncapCell.svelte'
import LastUpdatedCell from '$lib/components/database/cells/LastUpdatedCell.svelte'
import { getRarityLabel } from '$lib/utils/rarity'
// Column configuration for summons
@ -43,10 +45,17 @@
cell: ElementCell
},
{
id: 'max_level',
header: 'Max Level',
width: 80,
sort: true
id: 'uncap',
header: 'Uncap',
width: 160,
cell: SummonUncapCell
},
{
id: 'last_updated',
header: 'Last Updated',
width: 120,
sort: true,
cell: LastUpdatedCell
}
]
</script>

View file

@ -6,6 +6,8 @@
import WeaponImageCell from '$lib/components/database/cells/WeaponImageCell.svelte'
import ElementCell from '$lib/components/database/cells/ElementCell.svelte'
import ProficiencyCell from '$lib/components/database/cells/ProficiencyCell.svelte'
import WeaponUncapCell from '$lib/components/database/cells/WeaponUncapCell.svelte'
import LastUpdatedCell from '$lib/components/database/cells/LastUpdatedCell.svelte'
import { getRarityLabel } from '$lib/utils/rarity'
// Column configuration for weapons
@ -51,10 +53,17 @@
cell: ProficiencyCell
},
{
id: 'max_level',
header: 'Max Level',
width: 80,
sort: true
id: 'uncap',
header: 'Uncap',
width: 160,
cell: WeaponUncapCell
},
{
id: 'last_updated',
header: 'Last Updated',
width: 120,
sort: true,
cell: LastUpdatedCell
}
]
</script>

View file

@ -46,7 +46,9 @@
<img
src={getWeaponImage(weapon)}
alt={getWeaponName(weapon.name)}
onerror={(e) => { e.currentTarget.src = '/images/placeholders/placeholder-weapon-main.png' }}
onerror={(e) => {
e.currentTarget.src = '/images/placeholders/placeholder-weapon-main.png'
}}
/>
</div>
<div class="weapon-info">
@ -188,7 +190,9 @@
}
}
.loading, .error, .not-found {
.loading,
.error,
.not-found {
text-align: center;
padding: spacing.$unit * 4;
@ -257,7 +261,7 @@
gap: spacing.$unit * 0.5;
.label {
font-weight: typography.$semibold;
font-weight: typography.$medium;
color: #666;
min-width: 100px;
}
@ -266,12 +270,14 @@
color: #333;
}
.element-display, .proficiency-display {
.element-display,
.proficiency-display {
display: flex;
align-items: center;
gap: spacing.$unit * 0.25;
.element-icon, .proficiency-icon {
.element-icon,
.proficiency-icon {
width: 25px;
height: auto;
}
@ -281,7 +287,8 @@
}
}
.weapon-details, .weapon-skills {
.weapon-details,
.weapon-skills {
padding: spacing.$unit * 2;
border-bottom: 1px solid #e5e5e5;
@ -309,7 +316,7 @@
border-radius: 4px;
.label {
font-weight: typography.$semibold;
font-weight: typography.$medium;
color: #666;
}
@ -331,7 +338,7 @@
.skill-name {
font-size: typography.$font-medium;
font-weight: typography.$semibold;
font-weight: typography.$medium;
margin: 0 0 spacing.$unit * 0.5 0;
color: #333;
}
@ -362,8 +369,9 @@
}
}
.details-grid, .skills-grid {
.details-grid,
.skills-grid {
grid-template-columns: 1fr;
}
}
</style>
</style>