From 12074203f12177694deca42f86411b8d48891221 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Sun, 21 Dec 2025 12:29:49 -0800 Subject: [PATCH] add raid parties pane click raid tile to see other parties for same raid filters for element and battle settings --- src/lib/api/adapters/party.adapter.ts | 52 +++ src/lib/api/queries/party.queries.ts | 52 ++- .../party/info/PartyInfoGrid.svelte | 28 +- src/lib/components/party/info/RaidTile.svelte | 8 +- .../components/sidebar/RaidPartiesPane.svelte | 346 ++++++++++++++++++ 5 files changed, 477 insertions(+), 9 deletions(-) create mode 100644 src/lib/components/sidebar/RaidPartiesPane.svelte diff --git a/src/lib/api/adapters/party.adapter.ts b/src/lib/api/adapters/party.adapter.ts index cc2f786a..974f66d9 100644 --- a/src/lib/api/adapters/party.adapter.ts +++ b/src/lib/api/adapters/party.adapter.ts @@ -64,6 +64,19 @@ export interface ListUserPartiesParams { summonId?: string } +/** + * Parameters for listing parties by raid + */ +export interface ListRaidPartiesParams { + raidId: string + page?: number + per?: number + element?: number + fullAuto?: boolean + autoGuard?: boolean + chargeAttack?: boolean +} + /** * Grid operation for batch updates */ @@ -207,6 +220,45 @@ export class PartyAdapter extends BaseAdapter { }) } + /** + * Lists public parties for a specific raid + */ + async listRaidParties(params: ListRaidPartiesParams): Promise> { + const { raidId, element, fullAuto, autoGuard, chargeAttack, ...rest } = params + + // Build query with raid filter and convert booleans to API format + const query: Record = { + ...rest, + raid: raidId + } + + if (element !== undefined && element >= 0) query.element = element + if (fullAuto !== undefined) query.full_auto = fullAuto ? 1 : 0 + if (autoGuard !== undefined) query.auto_guard = autoGuard ? 1 : 0 + if (chargeAttack !== undefined) query.charge_attack = chargeAttack ? 1 : 0 + + const response = await this.request<{ + results: Party[] + meta?: { + count?: number + totalPages?: number + perPage?: number + } + }>('/parties', { + method: 'GET', + query, + cacheTTL: 30000 + }) + + return { + results: response.results, + page: params.page || 1, + total: response.meta?.count || 0, + totalPages: response.meta?.totalPages || 1, + perPage: response.meta?.perPage || 20 + } + } + /** * Performs atomic batch grid updates * Supports move, swap, and remove operations on grid items diff --git a/src/lib/api/queries/party.queries.ts b/src/lib/api/queries/party.queries.ts index d1460ae1..1e891432 100644 --- a/src/lib/api/queries/party.queries.ts +++ b/src/lib/api/queries/party.queries.ts @@ -10,7 +10,8 @@ import { queryOptions, infiniteQueryOptions } from '@tanstack/svelte-query' import { partyAdapter, - type ListUserPartiesParams + type ListUserPartiesParams, + type ListRaidPartiesParams } from '$lib/api/adapters/party.adapter' import type { Party } from '$lib/types/api/party' @@ -33,6 +34,16 @@ export interface ListPartiesParams { per?: number } +/** + * Filter options for raid parties query + */ +export interface RaidPartiesFilters { + element?: number + fullAuto?: boolean + autoGuard?: boolean + chargeAttack?: boolean +} + /** * Party query options factory * @@ -139,6 +150,42 @@ export const partyQueries = { gcTime: 1000 * 60 * 15 // 15 minutes }), + /** + * Parties by raid infinite query options + * + * @param raidId - Raid ID to filter parties by + * @param filters - Optional filter parameters (element, battle settings) + * @returns Infinite query options for listing parties by raid + */ + raidParties: (raidId: string, filters?: RaidPartiesFilters) => + infiniteQueryOptions({ + queryKey: ['parties', 'raid', raidId, filters] as const, + queryFn: async ({ pageParam }): Promise => { + const response = await partyAdapter.listRaidParties({ + raidId, + ...filters, + page: pageParam + }) + return { + results: response.results, + page: response.page, + totalPages: response.totalPages, + total: response.total, + perPage: response.perPage + } + }, + initialPageParam: 1, + getNextPageParam: (lastPage) => { + if (lastPage.page < lastPage.totalPages) { + return lastPage.page + 1 + } + return undefined + }, + enabled: !!raidId, + staleTime: 1000 * 60 * 2, // 2 minutes + gcTime: 1000 * 60 * 15 // 15 minutes + }), + /** * Party preview status query options * @@ -179,6 +226,9 @@ export const partyKeys = { userLists: () => [...partyKeys.all, 'user'] as const, userList: (username: string, params?: Omit) => [...partyKeys.userLists(), username, params] as const, + raidLists: () => [...partyKeys.all, 'raid'] as const, + raidList: (raidId: string, filters?: RaidPartiesFilters) => + [...partyKeys.raidLists(), raidId, filters] as const, details: () => ['party'] as const, detail: (shortcode: string) => [...partyKeys.details(), shortcode] as const, preview: (shortcode: string) => [...partyKeys.detail(shortcode), 'preview'] as const diff --git a/src/lib/components/party/info/PartyInfoGrid.svelte b/src/lib/components/party/info/PartyInfoGrid.svelte index 5ff5d4a3..23c1c762 100644 --- a/src/lib/components/party/info/PartyInfoGrid.svelte +++ b/src/lib/components/party/info/PartyInfoGrid.svelte @@ -4,6 +4,9 @@ import RaidTile from './RaidTile.svelte' import BattleTile from './BattleTile.svelte' import VideoTile from './VideoTile.svelte' + import { sidebar } from '$lib/stores/sidebar.svelte' + import RaidPartiesPane from '$lib/components/sidebar/RaidPartiesPane.svelte' + import { getRaidImage } from '$lib/utils/images' interface Props { party: Party @@ -25,11 +28,25 @@ // Video tile only shown when there's a video (no empty placeholder) const showVideo = $derived(hasVideo) // Battle tile always shown - settings have default values + + function handleRaidClick() { + if (!party.raid) return + + const raidName = + typeof party.raid.name === 'string' + ? party.raid.name + : party.raid.name?.en || 'Raid Parties' + + sidebar.openWithComponent(raidName, RaidPartiesPane, { raid: party.raid }, { + scrollable: true, + image: getRaidImage(party.raid.slug) + }) + }
-
+
{#if showDescription} {/if} @@ -40,7 +57,7 @@
-
+
{#if showRaid} - + {/if}
@@ -77,8 +94,7 @@ .row-1 { grid-template-columns: 2fr 1fr; - // If only one item, let it take full width - &:has(> :only-child) { + &.single { grid-template-columns: 1fr; } } @@ -86,7 +102,7 @@ .row-2 { grid-template-columns: repeat(2, 1fr); - &:has(> :only-child) { + &.single { grid-template-columns: 1fr; } } diff --git a/src/lib/components/party/info/RaidTile.svelte b/src/lib/components/party/info/RaidTile.svelte index 4ec0f662..dea9eab6 100644 --- a/src/lib/components/party/info/RaidTile.svelte +++ b/src/lib/components/party/info/RaidTile.svelte @@ -6,9 +6,13 @@ interface Props { raid?: Raid + onclick?: () => void } - let { raid }: Props = $props() + let { raid, onclick }: Props = $props() + + // Only clickable if raid exists and onclick is provided + const clickable = $derived(!!raid && !!onclick) const raidName = $derived(() => { if (!raid) return null @@ -19,7 +23,7 @@ const elementLabel = $derived(raid ? getElementLabel(raid.element) : null) - + {#if raid}
diff --git a/src/lib/components/sidebar/RaidPartiesPane.svelte b/src/lib/components/sidebar/RaidPartiesPane.svelte new file mode 100644 index 00000000..fce26793 --- /dev/null +++ b/src/lib/components/sidebar/RaidPartiesPane.svelte @@ -0,0 +1,346 @@ + + +
+ +
+ +
+ Element +
+ {#each elementOptions as option (option.label)} + + {/each} +
+
+ + +
+ Battle +
+ {#each battleSettings as setting (setting.key)} + + {/each} +
+
+
+ + +
+ {#if partiesQuery.isLoading && parties.length === 0} +
+ + Loading parties... +
+ {:else if partiesQuery.isError} +
+ +

Failed to load parties

+ +
+ {:else if isEmpty} +
+

No parties found for this raid

+
+ {:else} +
+ {#each parties as party (party.id)} + + {/each} +
+ +
+ + {#if partiesQuery.isFetchingNextPage} +
+ + Loading more... +
+ {/if} + {/if} +
+
+ +