diff --git a/src/lib/features/database/characters/sections/CharacterStatsSection.svelte b/src/lib/features/database/characters/sections/CharacterStatsSection.svelte index b32fba01..0f1486b0 100644 --- a/src/lib/features/database/characters/sections/CharacterStatsSection.svelte +++ b/src/lib/features/database/characters/sections/CharacterStatsSection.svelte @@ -13,19 +13,28 @@ let { character, editMode = false, editData = $bindable() }: Props = $props() const flb = $derived(editMode ? Boolean(editData.flb) : Boolean(character?.uncap?.flb)) + const ulb = $derived(editMode ? Boolean(editData.ulb) : Boolean(character?.uncap?.ulb)) {#if editMode} - + {#if flb} + + {/if} + {#if ulb} + + {/if} {:else} {#if flb} {/if} + {#if ulb} + + {/if} {/if} @@ -33,13 +42,45 @@ {#if editMode} - + {#if flb} + + {/if} + {#if ulb} + + {/if} {:else} {#if flb} {/if} + {#if ulb} + + {/if} + {/if} + + + + {#if editMode} + + + + {#if flb} + + {/if} + {:else} + {#if character.baseDa} + + {/if} + {#if character.baseTa} + + {/if} + {#if character.ougiRatio} + + {/if} + {#if character.ougiRatioFlb} + + {/if} {/if} diff --git a/src/lib/features/database/characters/sections/CharacterUncapSection.svelte b/src/lib/features/database/characters/sections/CharacterUncapSection.svelte index b0453040..72eac45d 100644 --- a/src/lib/features/database/characters/sections/CharacterUncapSection.svelte +++ b/src/lib/features/database/characters/sections/CharacterUncapSection.svelte @@ -37,6 +37,42 @@ ? (label.toLowerCase() as ElementName) : undefined }) + + // Auto-check/uncheck uncap levels in hierarchy: Transcendence > ULB > FLB + function handleFlbChange(checked: boolean) { + if (!checked) { + // Unchecking FLB should also uncheck ULB and Transcendence + editData.ulb = false + editData.transcendence = false + } + } + + function handleUlbChange(checked: boolean) { + if (checked && !editData.flb) { + // Checking ULB should also check FLB + editData.flb = true + } else if (!checked) { + // Unchecking ULB should also uncheck Transcendence + editData.transcendence = false + } + } + + function handleTranscendenceChange(checked: boolean) { + if (checked) { + // Checking Transcendence should also check ULB and FLB + if (!editData.ulb) editData.ulb = true + if (!editData.flb) editData.flb = true + } + } + + function handleSpecialChange(checked: boolean) { + if (checked) { + // Special characters (Story SRs) don't have standard uncap levels + editData.flb = false + editData.ulb = false + editData.transcendence = false + } + } @@ -56,10 +92,31 @@ {/if} {#if editMode} - - - - + + + +
+ +

This is for Story SRs. Don't check this unless something really weird happens.

+
{/if}
+ + diff --git a/src/routes/(app)/database/characters/new/+page.svelte b/src/routes/(app)/database/characters/new/+page.svelte index a02c1173..d110cf6a 100644 --- a/src/routes/(app)/database/characters/new/+page.svelte +++ b/src/routes/(app)/database/characters/new/+page.svelte @@ -5,16 +5,16 @@ import { goto } from '$app/navigation' // Components - import DetailScaffold from '$lib/features/database/detail/DetailScaffold.svelte' - import CharacterMetadataSection from '$lib/features/database/characters/sections/CharacterMetadataSection.svelte' import CharacterUncapSection from '$lib/features/database/characters/sections/CharacterUncapSection.svelte' import CharacterTaxonomySection from '$lib/features/database/characters/sections/CharacterTaxonomySection.svelte' import CharacterStatsSection from '$lib/features/database/characters/sections/CharacterStatsSection.svelte' import DetailsContainer from '$lib/components/ui/DetailsContainer.svelte' import DetailItem from '$lib/components/ui/DetailItem.svelte' + import SidebarHeader from '$lib/components/ui/SidebarHeader.svelte' import Button from '$lib/components/ui/Button.svelte' - import { getCharacterImage } from '$lib/utils/images' + import ValidatedInput from '$lib/components/ui/ValidatedInput.svelte' import { entityAdapter } from '$lib/api/adapters/entity.adapter' + import { getRarityOptions } from '$lib/utils/rarity' // Types import type { PageData } from './$types' @@ -23,38 +23,20 @@ // Always in edit mode for new character const editMode = true - const canEdit = true let isSaving = $state(false) let saveError = $state(null) - let saveSuccess = $state(false) - // Validation state - let isValidating = $state(false) - let validationError = $state(null) - let validationResult = $state<{ - valid: boolean - existsInDb: boolean - imageUrls?: { main?: string; grid?: string; square?: string } - } | null>(null) - - // Download state - let isDownloading = $state(false) - let downloadStatus = $state<{ - status: string - progress: number - imagesDownloaded?: number - imagesTotal?: number - error?: string - } | null>(null) - let downloadPollingInterval = $state | null>(null) + // Validation state for canCreate check + let granblueIdValid = $state(false) + let granblueIdExistsInDb = $state(false) // Empty character for new creation const emptyCharacter = { id: '', name: { en: '', jp: '' }, granblueId: '', - characterId: null, + characterId: '', rarity: 3, element: 0, race: [], @@ -68,70 +50,97 @@ // Editable fields let editData = $state({ + // Basic Info name: '', + nameJp: '', granblueId: '', - characterId: null as number | null, + characterId: '', // Comma-separated for dual/trio units (e.g., "123, 456") rarity: 3, + + // Taxonomy element: 0, race1: null as number | null, race2: null as number | null, gender: 0, proficiency1: 0, proficiency2: 0, + + // Stats minHp: 0, maxHp: 0, maxHpFlb: 0, + maxHpUlb: 0, minAtk: 0, maxAtk: 0, maxAtkFlb: 0, + maxAtkUlb: 0, + baseDa: 0, + baseTa: 0, + ougiRatio: 0, + ougiRatioFlb: 0, + + // Uncap flb: false, ulb: false, - transcendence: false, - special: false + special: false, + + // Dates + releaseDate: '', + flbDate: '', + ulbDate: '', + + // Links + wikiEn: '', + wikiJa: '', + gamewith: '', + kamigame: '' }) + const rarityOptions = getRarityOptions() + // Validation is required before create const canCreate = $derived( - validationResult?.valid === true && - !validationResult?.existsInDb && + granblueIdValid && + !granblueIdExistsInDb && editData.name.trim() !== '' && editData.granblueId.trim() !== '' ) - // Get preview image from validation or placeholder - const previewImage = $derived( - validationResult?.imageUrls?.grid || getCharacterImage(editData.granblueId, 'grid', '01') - ) + async function validateGranblueId(value: string): Promise<{ valid: boolean; message: string }> { + console.log('[+page] validateGranblueId called with:', value) - async function validateGranblueId() { - if (!editData.granblueId || editData.granblueId.length !== 10) { - validationError = 'Granblue ID must be exactly 10 digits' - validationResult = null - return + if (!value || value.length !== 10) { + console.log('[+page] Invalid length, returning early') + granblueIdValid = false + granblueIdExistsInDb = false + return { valid: false, message: 'Granblue ID must be exactly 10 digits' } } - isValidating = true - validationError = null - validationResult = null - try { - const result = await entityAdapter.validateCharacterGranblueId(editData.granblueId) - validationResult = { - valid: result.valid, - existsInDb: result.existsInDb, - imageUrls: result.imageUrls - } + console.log('[+page] Calling entityAdapter.validateCharacterGranblueId...') + const result = await entityAdapter.validateCharacterGranblueId(value) + console.log('[+page] API result:', result) if (!result.valid) { - validationError = result.error || 'Invalid Granblue ID' - } else if (result.existsInDb) { - validationError = 'A character with this Granblue ID already exists' + granblueIdValid = false + granblueIdExistsInDb = false + return { valid: false, message: result.error || 'Invalid Granblue ID' } } + + if (result.existsInDb) { + granblueIdValid = true + granblueIdExistsInDb = true + return { valid: false, message: 'A character with this Granblue ID already exists' } + } + + granblueIdValid = true + granblueIdExistsInDb = false + return { valid: true, message: 'Valid Granblue ID - images found on server' } } catch (error) { - validationError = 'Failed to validate Granblue ID' - console.error('Validation error:', error) - } finally { - isValidating = false + granblueIdValid = false + granblueIdExistsInDb = false + console.error('[+page] Validation error:', error) + return { valid: false, message: 'Failed to validate Granblue ID' } } } @@ -140,37 +149,62 @@ isSaving = true saveError = null - saveSuccess = false try { - // Prepare the data for API const payload = { + // Basic Info granblue_id: editData.granblueId, name_en: editData.name, - name_jp: '', // Can be added later + name_jp: editData.nameJp, + character_id: + editData.characterId.trim() === '' + ? [] + : editData.characterId + .split(',') + .map((id) => Number(id.trim())) + .filter((id) => !isNaN(id)), rarity: editData.rarity, + + // Taxonomy element: editData.element, race1: editData.race1, race2: editData.race2, gender: editData.gender, proficiency1: editData.proficiency1, proficiency2: editData.proficiency2, + + // Stats min_hp: editData.minHp, max_hp: editData.maxHp, max_hp_flb: editData.maxHpFlb, + max_hp_ulb: editData.maxHpUlb, min_atk: editData.minAtk, max_atk: editData.maxAtk, max_atk_flb: editData.maxAtkFlb, + max_atk_ulb: editData.maxAtkUlb, + base_da: editData.baseDa, + base_ta: editData.baseTa, + ougi_ratio: editData.ougiRatio, + ougi_ratio_flb: editData.ougiRatioFlb, + + // Uncap flb: editData.flb, ulb: editData.ulb, - special: editData.special + special: editData.special, + + // Dates + release_date: editData.releaseDate || null, + flb_date: editData.flbDate || null, + ulb_date: editData.ulbDate || null, + + // Links + wiki_en: editData.wikiEn, + wiki_ja: editData.wikiJa, + gamewith: editData.gamewith, + kamigame: editData.kamigame } const newCharacter = await entityAdapter.createCharacter(payload) - - saveSuccess = true - - // Redirect to the new character's page await goto(`/database/characters/${newCharacter.id}`) } catch (error) { saveError = 'Failed to create character. Please try again.' @@ -180,155 +214,169 @@ } } - async function startImageDownload() { - if (!validationResult?.valid || validationResult.existsInDb) return - - // Note: This would need a character ID, so it would happen after creation - // For now, we'll skip this and implement it in the edit page - // The user can download images after creating the character - } - function handleCancel() { goto('/database/characters') } -
- -
- - -
-
- - -
+
+ + {#snippet leftAccessory()} + + {/snippet} + {#snippet rightAccessory()} + + {/snippet} + - {#if validationError} -
{validationError}
- {/if} + {#if saveError} +
{saveError}
+ {/if} - {#if validationResult?.valid && !validationResult.existsInDb} -
- Valid Granblue ID - images found on server -
- {/if} +
+ + + + + + + + + - {#if validationResult?.existsInDb} -
- A character with this ID already exists in the database -
- {/if} -
- + + + - - + + + {#if editData.flb} - + {/if} + {#if editData.ulb} + + {/if} + - - - - - - -
- - -
-
-
+ + + + + + +