diff --git a/src/lib/features/database/characters/schema.ts b/src/lib/features/database/characters/schema.ts index 2e49d23e..303f8c42 100644 --- a/src/lib/features/database/characters/schema.ts +++ b/src/lib/features/database/characters/schema.ts @@ -11,6 +11,9 @@ export const CharacterEditSchema = z.object({ gender: z.number().int().min(0), proficiency1: z.number().int().min(0), proficiency2: z.number().int().min(0), + season: z.number().int().min(1).nullable(), + series: z.array(z.number().int().min(1)), + gacha_available: z.boolean(), min_hp: z.number().int().min(0), max_hp: z.number().int().min(0), max_hp_flb: z.number().int().min(0), @@ -28,7 +31,7 @@ export type CharacterEdit = z.infer export function toEditData(model: any): CharacterEdit { return { name: model?.name ?? '', - granblue_id: model?.granblue_id ?? '', + granblue_id: model?.granblueId ?? model?.granblue_id ?? '', rarity: model?.rarity ?? 1, element: model?.element ?? 0, race1: model?.race?.[0] ?? null, @@ -36,12 +39,16 @@ export function toEditData(model: any): CharacterEdit { gender: model?.gender ?? 0, proficiency1: model?.proficiency?.[0] ?? 0, proficiency2: model?.proficiency?.[1] ?? 0, - min_hp: model?.hp?.min_hp ?? 0, - max_hp: model?.hp?.max_hp ?? 0, - max_hp_flb: model?.hp?.max_hp_flb ?? 0, - min_atk: model?.atk?.min_atk ?? 0, - max_atk: model?.atk?.max_atk ?? 0, - max_atk_flb: model?.atk?.max_atk_flb ?? 0, + season: model?.season ?? null, + series: model?.series ?? [], + // API returns camelCase (gachaAvailable) after transformation + gacha_available: model?.gachaAvailable ?? model?.gacha_available ?? true, + min_hp: model?.hp?.minHp ?? model?.hp?.min_hp ?? 0, + max_hp: model?.hp?.maxHp ?? model?.hp?.max_hp ?? 0, + max_hp_flb: model?.hp?.maxHpFlb ?? model?.hp?.max_hp_flb ?? 0, + min_atk: model?.atk?.minAtk ?? model?.atk?.min_atk ?? 0, + max_atk: model?.atk?.maxAtk ?? model?.atk?.max_atk ?? 0, + max_atk_flb: model?.atk?.maxAtkFlb ?? model?.atk?.max_atk_flb ?? 0, flb: model?.uncap?.flb ?? false, ulb: model?.uncap?.ulb ?? false, transcendence: model?.uncap?.transcendence ?? false, @@ -59,6 +66,9 @@ export function toPayload(edit: CharacterEdit) { race: [edit.race1, edit.race2].filter((r) => r !== null && r !== undefined), gender: edit.gender, proficiency: [edit.proficiency1, edit.proficiency2], + season: edit.season, + series: edit.series, + gacha_available: edit.gacha_available, hp: { min_hp: edit.min_hp, max_hp: edit.max_hp, diff --git a/src/lib/features/database/characters/sections/CharacterMetadataSection.svelte b/src/lib/features/database/characters/sections/CharacterMetadataSection.svelte index 57bc49eb..012a6601 100644 --- a/src/lib/features/database/characters/sections/CharacterMetadataSection.svelte +++ b/src/lib/features/database/characters/sections/CharacterMetadataSection.svelte @@ -7,6 +7,7 @@ import SuggestionDetailItem from '$lib/components/ui/SuggestionDetailItem.svelte' import CopyableText from '$lib/components/ui/CopyableText.svelte' import { getRarityLabel, getRarityOptions } from '$lib/utils/rarity' + import { getWeaponImage } from '$lib/utils/images' interface Props { character: any @@ -30,6 +31,11 @@ }: Props = $props() const rarityOptions = getRarityOptions() + + function formatPromotions(promotionNames: string[] | undefined): string { + if (!promotionNames || promotionNames.length === 0) return '—' + return promotionNames.join(', ') + } @@ -64,6 +70,58 @@ — {/if} + {#if character.recruitedBy} + + + {character.recruitedBy.name.en + {character.recruitedBy.name.en} + + + + {/if} {/if} + + diff --git a/src/lib/features/database/characters/sections/CharacterTaxonomySection.svelte b/src/lib/features/database/characters/sections/CharacterTaxonomySection.svelte index a5c55c83..6ef10958 100644 --- a/src/lib/features/database/characters/sections/CharacterTaxonomySection.svelte +++ b/src/lib/features/database/characters/sections/CharacterTaxonomySection.svelte @@ -7,10 +7,20 @@ import SuggestionDetailItem from '$lib/components/ui/SuggestionDetailItem.svelte' import ElementLabel from '$lib/components/labels/ElementLabel.svelte' import ProficiencyLabel from '$lib/components/labels/ProficiencyLabel.svelte' - import { getElementOptions } from '$lib/utils/element' + import { getElementLabel, getElementOptions } from '$lib/utils/element' import { getRaceLabel, getRaceOptions } from '$lib/utils/race' import { getGenderLabel, getGenderOptions } from '$lib/utils/gender' import { getProficiencyOptions } from '$lib/utils/proficiency' + import { + CharacterSeason, + CharacterSeries, + CHARACTER_SEASON_NAMES, + CHARACTER_SERIES_NAMES, + getSeasonName, + getSeriesNames + } from '$lib/types/enums' + + type ElementName = 'wind' | 'fire' | 'water' | 'earth' | 'dark' | 'light' interface Props { character: any @@ -37,6 +47,34 @@ const raceOptions = getRaceOptions() const genderOptions = getGenderOptions() const proficiencyOptions = getProficiencyOptions() + + // Season options (nullable, so include a "None" option) + const seasonOptions = [ + { value: 0, label: 'None' }, + ...Object.entries(CHARACTER_SEASON_NAMES).map(([value, label]) => ({ + value: Number(value), + label + })) + ] + + // Series options for multiselect + const seriesOptions = Object.entries(CHARACTER_SERIES_NAMES).map(([value, label]) => ({ + value: Number(value), + label + })) + + // Get element name for checkbox theming + const elementName = $derived.by((): ElementName | undefined => { + const el = editMode ? editData?.element : character?.element + const label = getElementLabel(el) + return label !== '—' && label !== 'Null' ? (label.toLowerCase() as ElementName) : undefined + }) + + // Format series for display + function formatSeriesDisplay(series: number[]): string { + if (!series || series.length === 0) return '—' + return getSeriesNames(series).join(', ') + } @@ -107,6 +145,29 @@ onAcceptSuggestion={() => onAcceptSuggestion?.('proficiency2', suggestions?.proficiency2)} onDismissSuggestion={() => onDismissSuggestion?.('proficiency2')} /> + + + {:else} @@ -120,6 +181,9 @@ + + + {/if}