diff --git a/src/routes/(admin)/admin/characters/+page.server.ts b/src/routes/(admin)/admin/characters/+page.server.ts index 349dddb..039f68a 100644 --- a/src/routes/(admin)/admin/characters/+page.server.ts +++ b/src/routes/(admin)/admin/characters/+page.server.ts @@ -1,6 +1,6 @@ import { db } from '$lib/server/db'; import { character, devilFruit, arc, characterOverride } from '$lib/server/db/schema'; -import { eq } from 'drizzle-orm'; +import { eq, sql } from 'drizzle-orm'; import { fail } from '@sveltejs/kit'; import type { PageServerLoad, Actions } from './$types'; import { writeFile } from 'fs/promises'; @@ -9,7 +9,7 @@ import { existsSync, mkdirSync } from 'fs'; import { env } from '$env/dynamic/private'; export const load: PageServerLoad = async () => { - const [charactersData, devilFruits, arcs, overrides] = await Promise.all([ + const [charactersData, devilFruits, arcs, overrides, statusesData, gendersData] = await Promise.all([ db .select({ id: character.id, @@ -41,12 +41,36 @@ export const load: PageServerLoad = async () => { .orderBy(character.name), db.select().from(devilFruit).orderBy(devilFruit.name), db.select().from(arc).orderBy(arc.name), - db.select().from(characterOverride) + db.select().from(characterOverride), + db.selectDistinct({ status: character.status }) + .from(character) + .where(sql`${character.status} IS NOT NULL AND ${character.status} != ''`), + db.selectDistinct({ gender: character.gender }) + .from(character) + .where(sql`${character.gender} IS NOT NULL AND ${character.gender} != ''`) ]); // Create a map of overrides by characterId for easy lookup const overridesMap = new Map(overrides.map((o) => [o.characterId, o])); + // Create maps for arcs and devil fruits to lookup names by ID + const arcMap = new Map(arcs.map((a) => [a.id, a.name])); + const devilFruitMap = new Map(devilFruits.map((f) => [f.id, { name: f.name, type: f.type }])); + + // Helper function to normalize data (parse JSON arrays) + const normalizeArray = (value: any): any => { + if (!value) return value; + if (Array.isArray(value)) return value; + if (typeof value === 'string' && value.includes('[')) { + try { + return JSON.parse(value); + } catch { + return value; + } + } + return value; + }; + // Merge character data with overrides const charactersWithOverrides = charactersData.map((char) => { const override = overridesMap.get(char.id); @@ -59,8 +83,29 @@ export const load: PageServerLoad = async () => { displayValues[key as keyof typeof displayValues] = override[key as keyof typeof override]; } }); + + // Update arcName if arcId was overridden + if (override.arcId !== null && override.arcId !== undefined) { + displayValues.arcName = arcMap.get(override.arcId) || null; + } + + // Update devilFruitName and devilFruitType if devilFruitId was overridden + if (override.devilFruitId !== null && override.devilFruitId !== undefined) { + const fruit = devilFruitMap.get(override.devilFruitId); + displayValues.devilFruitName = fruit?.name || null; + displayValues.devilFruitType = fruit?.type || null; + } } + // Pre-normalize arrays (epithets, affiliations) for performance + displayValues.epithets = normalizeArray(displayValues.epithets); + displayValues.affiliations = normalizeArray(displayValues.affiliations); + + // Create search text for epithets + displayValues.epithetsSearchText = Array.isArray(displayValues.epithets) + ? displayValues.epithets.join(' ').toLowerCase() + : (displayValues.epithets || '').toLowerCase(); + return { ...char, override, @@ -71,7 +116,15 @@ export const load: PageServerLoad = async () => { return { characters: charactersWithOverrides, devilFruits, - arcs + arcs, + availableStatuses: statusesData + .map(s => s.status) + .filter((s): s is string => !!s) + .sort((a, b) => a.localeCompare(b)), + availableGenders: gendersData + .map(g => g.gender) + .filter((g): g is string => !!g) + .sort((a, b) => a.localeCompare(b)) }; }; @@ -137,11 +190,16 @@ export const actions: Actions = { if (hasUploadedPicture && key === 'pictureUrl') { return; } - // Handle integers (age, bounty, height, devilFruitId, arcId) - if (key === 'age' || key === 'bounty' || key === 'height' || key === 'devilFruitId' || key === 'arcId') { + // Handle integers (age, bounty, height) + if (key === 'age' || key === 'bounty' || key === 'height') { const strValue = value as string; updates[key] = strValue && strValue !== '' ? parseInt(strValue) : null; } + // Handle text IDs (devilFruitId, arcId) + else if (key === 'devilFruitId' || key === 'arcId') { + const strValue = value as string; + updates[key] = strValue && strValue !== '' ? strValue : null; + } // Handle checkboxes (haki fields) after parsing all form data else if (key === 'hakiObservation' || key === 'hakiArmament' || key === 'hakiConqueror') { return; diff --git a/src/routes/(admin)/admin/characters/+page.svelte b/src/routes/(admin)/admin/characters/+page.svelte index 7ad39ab..822325b 100644 --- a/src/routes/(admin)/admin/characters/+page.svelte +++ b/src/routes/(admin)/admin/characters/+page.svelte @@ -65,54 +65,14 @@ status: '' }); - const availableStatuses = $derived.by(() => { - const statuses = new Set(); - for (const char of data.characters) { - const status = char.displayValues.status; - if (status && String(status).trim() !== '') { - statuses.add(String(status)); - } - } - return Array.from(statuses).sort((a, b) => a.localeCompare(b)); - }); - - const availableGenders = $derived.by(() => { - const genders = new Set(); - for (const char of data.characters) { - const gender = char.displayValues.gender; - if (gender && String(gender).trim() !== '') { - genders.add(String(gender)); - } - } - return Array.from(genders).sort((a, b) => a.localeCompare(b)); - }); - const filteredCharacters = $derived.by(() => { return data.characters.filter((char) => { const normalizedQuery = searchQuery.toLowerCase().trim(); - let epithetsText = ''; - - if (char.displayValues.epithets) { - if (typeof char.displayValues.epithets === 'string') { - if (char.displayValues.epithets.includes('[')) { - try { - const parsed = JSON.parse(char.displayValues.epithets); - epithetsText = Array.isArray(parsed) ? parsed.join(' ') : String(parsed); - } catch { - epithetsText = char.displayValues.epithets; - } - } else { - epithetsText = char.displayValues.epithets; - } - } else if (Array.isArray(char.displayValues.epithets)) { - epithetsText = char.displayValues.epithets.join(' '); - } - } const matchesSearch = normalizedQuery === '' || char.displayValues.name.toLowerCase().includes(normalizedQuery) || - epithetsText.toLowerCase().includes(normalizedQuery); + char.displayValues.epithetsSearchText.includes(normalizedQuery); const matchesDaily = filterDaily === 'all' || (filterDaily === 'daily' && char.displayValues.isInDailyMode) || @@ -155,12 +115,12 @@ epithets: override.epithets ?? '', pictureUrl: override.pictureUrl ?? '', url: override.url ?? '', - devilFruitId: override.devilFruitId !== null && override.devilFruitId !== undefined ? override.devilFruitId : '', + devilFruitId: override.devilFruitId !== null && override.devilFruitId !== undefined ? override.devilFruitId : (char.devilFruitId || ''), hakiObservation: override.hakiObservation ?? char.hakiObservation, hakiArmament: override.hakiArmament ?? char.hakiArmament, hakiConqueror: override.hakiConqueror ?? char.hakiConqueror, firstAppearance: override.firstAppearance ?? '', - arcId: override.arcId !== null && override.arcId !== undefined ? override.arcId : '', + arcId: override.arcId !== null && override.arcId !== undefined ? override.arcId : (char.arcId || ''), status: override.status ?? '' }; showOriginalValue = {}; @@ -261,7 +221,7 @@ class="rounded-lg bg-slate-700 px-4 py-2 text-sm text-white outline-none transition focus:ring-2 focus:ring-amber-600" > - {#each availableStatuses as status} + {#each data.availableStatuses as status} {/each} @@ -270,7 +230,7 @@ class="rounded-lg bg-slate-700 px-4 py-2 text-sm text-white outline-none transition focus:ring-2 focus:ring-amber-600" > - {#each availableGenders as gender} + {#each data.availableGenders as gender} {/each} @@ -378,9 +338,9 @@ {/if} {#if char.displayValues.epithets} - {typeof char.displayValues.epithets === 'string' - ? (char.displayValues.epithets.includes('[') ? JSON.parse(char.displayValues.epithets).join(', ') : char.displayValues.epithets) - : char.displayValues.epithets.join(', ')} + {Array.isArray(char.displayValues.epithets) + ? char.displayValues.epithets.join(', ') + : char.displayValues.epithets} {/if} @@ -393,13 +353,10 @@ {#if char.displayValues.affiliations} - {@const parsedAffiliations = typeof char.displayValues.affiliations === 'string' - ? (char.displayValues.affiliations.includes('[') ? JSON.parse(char.displayValues.affiliations) : char.displayValues.affiliations.split(',').map((a: string) => a.trim())) - : char.displayValues.affiliations} - {#if Array.isArray(parsedAffiliations) && parsedAffiliations.length > 0} - {parsedAffiliations[0]} + {#if Array.isArray(char.displayValues.affiliations) && char.displayValues.affiliations.length > 0} + {char.displayValues.affiliations[0]} {:else} - {parsedAffiliations} + {char.displayValues.affiliations} {/if} {:else} -