migrate /teams/new to query-based architecture

This commit is contained in:
Justin Edmund 2025-11-29 17:50:11 -08:00
parent d0f7b3b89a
commit f66b31fea8

View file

@ -13,13 +13,22 @@
import { partyAdapter, gridAdapter } from '$lib/api/adapters' import { partyAdapter, gridAdapter } from '$lib/api/adapters'
import { getLocalId } from '$lib/utils/localId' import { getLocalId } from '$lib/utils/localId'
import { storeEditKey } from '$lib/utils/editKeys' import { storeEditKey } from '$lib/utils/editKeys'
import type { Party } from '$lib/types/api/party'
// TanStack Query
import { createQuery, useQueryClient } from '@tanstack/svelte-query'
import { partyQueries } from '$lib/api/queries/party.queries'
import { partyKeys } from '$lib/api/queries/party.queries'
// TanStack Query mutations // TanStack Query mutations
import { useCreateParty } from '$lib/api/mutations/party.mutations' import { useCreateParty } from '$lib/api/mutations/party.mutations'
import { import {
useCreateGridWeapon, useCreateGridWeapon,
useCreateGridCharacter, useCreateGridCharacter,
useCreateGridSummon useCreateGridSummon,
useDeleteGridWeapon,
useDeleteGridCharacter,
useDeleteGridSummon
} from '$lib/api/mutations/grid.mutations' } from '$lib/api/mutations/grid.mutations'
import { Dialog } from 'bits-ui' import { Dialog } from 'bits-ui'
import { replaceState } from '$app/navigation' import { replaceState } from '$app/navigation'
@ -73,19 +82,42 @@
return characters.length >= 5 return characters.length >= 5
} }
// Grid state
let weapons = $state<any[]>([])
let summons = $state<any[]>([])
let characters = $state<any[]>([])
let selectedSlot = $state<number | null>(null)
let isFirstItemForSlot = false // Track if this is the first item after clicking empty cell
// Party state // Party state
let partyId = $state<string | null>(null) let partyId = $state<string | null>(null)
let shortcode = $state<string | null>(null) let shortcode = $state<string | null>(null)
let editKey = $state<string | null>(null) let editKey = $state<string | null>(null)
let isCreatingParty = $state(false) let isCreatingParty = $state(false)
// Placeholder party for 'new' route
const placeholderParty: Party = {
id: 'new',
shortcode: 'new',
name: 'New Team',
description: '',
weapons: [],
summons: [],
characters: [],
element: 0,
visibility: 1
}
// Create query with placeholder data
const queryClient = useQueryClient()
const partyQuery = createQuery(() => ({
...partyQueries.byShortcode(shortcode || 'new'),
initialData: placeholderParty,
enabled: false // Disable automatic fetching for 'new' party
}))
// Derive state from query
const party = $derived(partyQuery.data ?? placeholderParty)
const weapons = $derived(party.weapons ?? [])
const summons = $derived(party.summons ?? [])
const characters = $derived(party.characters ?? [])
let selectedSlot = $state<number | null>(null)
let isFirstItemForSlot = false // Track if this is the first item after clicking empty cell
// Error dialog state // Error dialog state
let errorDialogOpen = $state(false) let errorDialogOpen = $state(false)
let errorMessage = $state('') let errorMessage = $state('')
@ -96,6 +128,22 @@
const createWeaponMutation = useCreateGridWeapon() const createWeaponMutation = useCreateGridWeapon()
const createCharacterMutation = useCreateGridCharacter() const createCharacterMutation = useCreateGridCharacter()
const createSummonMutation = useCreateGridSummon() const createSummonMutation = useCreateGridSummon()
const deleteWeapon = useDeleteGridWeapon()
const deleteCharacter = useDeleteGridCharacter()
const deleteSummon = useDeleteGridSummon()
// Helper to add item to cache
function addItemToCache(itemType: 'weapons' | 'summons' | 'characters', item: any) {
const cacheKey = partyKeys.detail(shortcode || 'new')
queryClient.setQueryData(cacheKey, (old: Party | undefined) => {
if (!old) return placeholderParty
return {
...old,
[itemType]: [...(old[itemType] ?? []), item]
}
})
}
// Calculate if grids are full // Calculate if grids are full
let isWeaponGridFull = $derived(weapons.length >= 10) // 1 mainhand + 9 grid slots let isWeaponGridFull = $derived(weapons.length >= 10) // 1 mainhand + 9 grid slots
@ -156,6 +204,12 @@
throw new Error('Party creation did not return ID or shortcode') throw new Error('Party creation did not return ID or shortcode')
} }
// Update the query cache with the created party
queryClient.setQueryData(
partyKeys.detail(createdParty.shortcode),
createdParty
)
// Step 2: Add the first item to the party // Step 2: Add the first item to the party
let position = selectedSlot !== null ? selectedSlot : -1 // Use selectedSlot if available let position = selectedSlot !== null ? selectedSlot : -1 // Use selectedSlot if available
let itemAdded = false let itemAdded = false
@ -174,8 +228,8 @@
console.log('Weapon added:', addResult) console.log('Weapon added:', addResult)
itemAdded = true itemAdded = true
// Update local state with the added weapon // Update cache with the added weapon
weapons = [...weapons, addResult] addItemToCache('weapons', addResult)
} else if (activeTab === GridType.Summon) { } else if (activeTab === GridType.Summon) {
// Use selectedSlot if available, otherwise default to main summon // Use selectedSlot if available, otherwise default to main summon
if (selectedSlot === null) position = -1 if (selectedSlot === null) position = -1
@ -189,8 +243,8 @@
console.log('Summon added:', addResult) console.log('Summon added:', addResult)
itemAdded = true itemAdded = true
// Update local state with the added summon // Update cache with the added summon
summons = [...summons, addResult] addItemToCache('summons', addResult)
} else if (activeTab === GridType.Character) { } else if (activeTab === GridType.Character) {
// Use selectedSlot if available, otherwise default to first slot // Use selectedSlot if available, otherwise default to first slot
if (selectedSlot === null) position = 0 if (selectedSlot === null) position = 0
@ -202,8 +256,8 @@
console.log('Character added:', addResult) console.log('Character added:', addResult)
itemAdded = true itemAdded = true
// Update local state with the added character // Update cache with the added character
characters = [...characters, addResult] addItemToCache('characters', addResult)
} }
selectedSlot = null // Reset after using selectedSlot = null // Reset after using
@ -290,8 +344,8 @@
mainhand: position === -1 mainhand: position === -1
}) })
// Add to local state // Add to cache
weapons = [...weapons, response] addItemToCache('weapons', response)
} else if (activeTab === GridType.Summon) { } else if (activeTab === GridType.Summon) {
// Use selectedSlot for first item if available // Use selectedSlot for first item if available
if (i === 0 && selectedSlot !== null && !summons.find(s => s.position === selectedSlot)) { if (i === 0 && selectedSlot !== null && !summons.find(s => s.position === selectedSlot)) {
@ -314,8 +368,8 @@
friend: position === 6 friend: position === 6
}) })
// Add to local state // Add to cache
summons = [...summons, response] addItemToCache('summons', response)
} else if (activeTab === GridType.Character) { } else if (activeTab === GridType.Character) {
// Use selectedSlot for first item if available // Use selectedSlot for first item if available
if (i === 0 && selectedSlot !== null && !characters.find(c => c.position === selectedSlot)) { if (i === 0 && selectedSlot !== null && !characters.find(c => c.position === selectedSlot)) {
@ -336,8 +390,8 @@
position position
}) })
// Add to local state // Add to cache
characters = [...characters, response] addItemToCache('characters', response)
} }
} }
} catch (error: any) { } catch (error: any) {
@ -348,143 +402,48 @@
} }
return return
} }
// Original local-only adding logic (before party creation)
if (activeTab === GridType.Weapon) {
// Add weapons to empty slots
const emptySlots = Array.from({ length: 10 }, (_, i) => i - 1) // -1 for mainhand, 0-8 for grid
.filter(i => !weapons.find(w => w.position === i))
items.forEach((item, index) => {
let position: number
// Use selectedSlot for first item if available
if (index === 0 && selectedSlot !== null && !weapons.find(w => w.position === selectedSlot)) {
position = selectedSlot
selectedSlot = null // Reset after using
} else {
// Find next empty slot
const availableSlots = emptySlots.filter(s => !weapons.find(w => w.position === s))
if (availableSlots.length === 0) return
position = availableSlots[0]!
} }
const newWeapon = {
id: `temp-${Date.now()}-${index}`,
position,
object: {
granblueId: item.granblueId,
name: item.name,
element: item.element
},
mainhand: position === -1
}
console.log('Adding weapon:', newWeapon)
weapons = [...weapons, newWeapon]
})
console.log('Updated weapons array:', weapons)
} else if (activeTab === GridType.Summon) {
// Add summons to empty slots
const emptySlots = [-1, 0, 1, 2, 3, 6] // main, 4 grid slots, friend
.filter(i => !summons.find(s => s.position === i))
items.forEach((item, index) => { // Provide party context using query data
let position: number
// Use selectedSlot for first item if available
if (index === 0 && selectedSlot !== null && !summons.find(s => s.position === selectedSlot)) {
position = selectedSlot
selectedSlot = null // Reset after using
} else {
// Find next empty slot
const availableSlots = emptySlots.filter(s => !summons.find(sum => sum.position === s))
if (availableSlots.length === 0) return
position = availableSlots[0]!
}
summons = [...summons, {
id: `temp-${Date.now()}-${index}`,
position,
object: {
granblueId: item.granblueId,
name: item.name,
element: item.element
},
main: position === -1,
friend: position === 6
}]
})
} else if (activeTab === GridType.Character) {
// Add characters to empty slots
const emptySlots = Array.from({ length: 5 }, (_, i) => i)
.filter(i => !characters.find(c => c.position === i))
items.forEach((item, index) => {
let position: number
// Use selectedSlot for first item if available
if (index === 0 && selectedSlot !== null && !characters.find(c => c.position === selectedSlot)) {
position = selectedSlot
selectedSlot = null // Reset after using
} else {
// Find next empty slot
const availableSlots = emptySlots.filter(s => !characters.find(c => c.position === s))
if (availableSlots.length === 0) return
position = availableSlots[0]!
}
characters = [...characters, {
id: `temp-${Date.now()}-${index}`,
position,
object: {
granblueId: item.granblueId,
name: item.name,
element: item.element
}
}]
})
}
}
// Remove functions
function removeWeapon(itemId: string) {
console.log('Removing weapon:', itemId)
weapons = weapons.filter(w => w.id !== itemId)
return Promise.resolve({ id: 'new', shortcode: 'new', weapons, summons, characters })
}
function removeSummon(itemId: string) {
console.log('Removing summon:', itemId)
summons = summons.filter(s => s.id !== itemId)
return Promise.resolve({ id: 'new', shortcode: 'new', weapons, summons, characters })
}
function removeCharacter(itemId: string) {
console.log('Removing character:', itemId)
characters = characters.filter(c => c.id !== itemId)
return Promise.resolve({ id: 'new', shortcode: 'new', weapons, summons, characters })
}
// Provide a minimal party context so Unit components can render safely.
setContext('party', { setContext('party', {
getParty: () => ({ id: 'new', shortcode: 'new', weapons, summons, characters }), getParty: () => party,
updateParty: (updatedParty: any) => { updateParty: (p: Party) => {
// Update the local state when party is updated // Update cache instead of local state
if (updatedParty.weapons) weapons = updatedParty.weapons queryClient.setQueryData(partyKeys.detail(shortcode || 'new'), p)
if (updatedParty.summons) summons = updatedParty.summons
if (updatedParty.characters) characters = updatedParty.characters
}, },
canEdit: () => true, canEdit: () => true,
getEditKey: () => editKey,
services: { services: {
gridService: { gridService: {
removeWeapon: (partyId: string, itemId: string) => removeWeapon(itemId), removeWeapon: async (partyId: string, itemId: string) => {
removeSummon: (partyId: string, itemId: string) => removeSummon(itemId), if (!partyId || partyId === 'new') return party
removeCharacter: (partyId: string, itemId: string) => removeCharacter(itemId), await deleteWeapon.mutateAsync({
addWeapon: () => Promise.resolve({ party: { id: 'new', shortcode: 'new', weapons, summons, characters } }), id: itemId,
addSummon: () => Promise.resolve({ party: { id: 'new', shortcode: 'new', weapons, summons, characters } }), partyId,
addCharacter: () => Promise.resolve({ party: { id: 'new', shortcode: 'new', weapons, summons, characters } }), partyShortcode: shortcode || 'new'
replaceWeapon: () => Promise.resolve({ party: { id: 'new', shortcode: 'new', weapons, summons, characters } }), })
replaceSummon: () => Promise.resolve({ party: { id: 'new', shortcode: 'new', weapons, summons, characters } }), return party
replaceCharacter: () => Promise.resolve({ party: { id: 'new', shortcode: 'new', weapons, summons, characters } })
}, },
partyService: { getEditKey: () => null } removeSummon: async (partyId: string, itemId: string) => {
if (!partyId || partyId === 'new') return party
await deleteSummon.mutateAsync({
id: itemId,
partyId,
partyShortcode: shortcode || 'new'
})
return party
},
removeCharacter: async (partyId: string, itemId: string) => {
if (!partyId || partyId === 'new') return party
await deleteCharacter.mutateAsync({
id: itemId,
partyId,
partyShortcode: shortcode || 'new'
})
return party
}
}
}, },
openPicker: (opts: { type: 'weapon' | 'summon' | 'character'; position: number; item?: any }) => { openPicker: (opts: { type: 'weapon' | 'summon' | 'character'; position: number; item?: any }) => {
selectedSlot = opts.position selectedSlot = opts.position