refactor members page filters, add granblue id to phantom edit
This commit is contained in:
parent
b718dcc335
commit
3a236032bd
2 changed files with 71 additions and 101 deletions
|
|
@ -43,9 +43,6 @@
|
||||||
<div class="phantom-info">
|
<div class="phantom-info">
|
||||||
<div class="phantom-details">
|
<div class="phantom-details">
|
||||||
<span class="name">{phantom.name}</span>
|
<span class="name">{phantom.name}</span>
|
||||||
{#if phantom.granblueId}
|
|
||||||
<span class="granblue-id">ID: {phantom.granblueId}</span>
|
|
||||||
{/if}
|
|
||||||
{#if phantom.joinedAt}
|
{#if phantom.joinedAt}
|
||||||
<span class="joined-date">Joined {formatDate(phantom.joinedAt)}</span>
|
<span class="joined-date">Joined {formatDate(phantom.joinedAt)}</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -144,11 +141,6 @@
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.granblue-id {
|
|
||||||
font-size: typography.$font-small;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.joined-date {
|
.joined-date {
|
||||||
font-size: typography.$font-small;
|
font-size: typography.$font-small;
|
||||||
color: var(--text-tertiary);
|
color: var(--text-tertiary);
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@
|
||||||
import Button from '$lib/components/ui/Button.svelte'
|
import Button from '$lib/components/ui/Button.svelte'
|
||||||
import Dialog from '$lib/components/ui/Dialog.svelte'
|
import Dialog from '$lib/components/ui/Dialog.svelte'
|
||||||
import DropdownMenu from '$lib/components/ui/DropdownMenu.svelte'
|
import DropdownMenu from '$lib/components/ui/DropdownMenu.svelte'
|
||||||
|
import Input from '$lib/components/ui/Input.svelte'
|
||||||
|
import DatePicker from '$lib/components/ui/DatePicker.svelte'
|
||||||
import ModalHeader from '$lib/components/ui/ModalHeader.svelte'
|
import ModalHeader from '$lib/components/ui/ModalHeader.svelte'
|
||||||
import ModalBody from '$lib/components/ui/ModalBody.svelte'
|
import ModalBody from '$lib/components/ui/ModalBody.svelte'
|
||||||
import ModalFooter from '$lib/components/ui/ModalFooter.svelte'
|
import ModalFooter from '$lib/components/ui/ModalFooter.svelte'
|
||||||
|
|
@ -66,10 +68,10 @@
|
||||||
enabled: crewStore.isOfficer && !!crewStore.crew?.id
|
enabled: crewStore.isOfficer && !!crewStore.crew?.id
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Query for phantoms (needed for pending claims badge when not viewing phantom/all filter)
|
// Query for phantoms (needed for pending claims badge when viewing pending filter)
|
||||||
const phantomsQuery = createQuery(() => ({
|
const phantomsQuery = createQuery(() => ({
|
||||||
...crewQueries.members('phantom'),
|
...crewQueries.members('phantom'),
|
||||||
enabled: filter !== 'phantom' && filter !== 'all' && crewStore.isOfficer
|
enabled: filter === 'pending' && crewStore.isOfficer
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Calculate total active roster size (members + phantoms)
|
// Calculate total active roster size (members + phantoms)
|
||||||
|
|
@ -97,8 +99,7 @@
|
||||||
const filterOptions = $derived.by(() => {
|
const filterOptions = $derived.by(() => {
|
||||||
const options: { value: MemberFilter; label: string }[] = [
|
const options: { value: MemberFilter; label: string }[] = [
|
||||||
{ value: 'all', label: 'All' },
|
{ value: 'all', label: 'All' },
|
||||||
{ value: 'active', label: 'Active' },
|
{ value: 'active', label: 'Active' }
|
||||||
{ value: 'phantom', label: 'Phantoms' }
|
|
||||||
]
|
]
|
||||||
if (crewStore.isOfficer) {
|
if (crewStore.isOfficer) {
|
||||||
options.push({ value: 'pending', label: 'Pending' })
|
options.push({ value: 'pending', label: 'Pending' })
|
||||||
|
|
@ -130,6 +131,7 @@
|
||||||
let editJoinDate = $state('')
|
let editJoinDate = $state('')
|
||||||
let editRetired = $state(false)
|
let editRetired = $state(false)
|
||||||
let editRetiredAt = $state('')
|
let editRetiredAt = $state('')
|
||||||
|
let editGranblueId = $state('')
|
||||||
|
|
||||||
// Dialog state for scout modal
|
// Dialog state for scout modal
|
||||||
let scoutModalOpen = $state(false)
|
let scoutModalOpen = $state(false)
|
||||||
|
|
@ -216,6 +218,7 @@
|
||||||
editJoinDate = phantom.joinedAt ? (phantom.joinedAt.split('T')[0] ?? '') : ''
|
editJoinDate = phantom.joinedAt ? (phantom.joinedAt.split('T')[0] ?? '') : ''
|
||||||
editRetired = phantom.retired
|
editRetired = phantom.retired
|
||||||
editRetiredAt = phantom.retiredAt ? (phantom.retiredAt.split('T')[0] ?? '') : ''
|
editRetiredAt = phantom.retiredAt ? (phantom.retiredAt.split('T')[0] ?? '') : ''
|
||||||
|
editGranblueId = phantom.granblueId ?? ''
|
||||||
editDialogOpen = true
|
editDialogOpen = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -238,7 +241,8 @@
|
||||||
await crewAdapter.updatePhantom(crewStore.crew.id, editingPhantom.id, {
|
await crewAdapter.updatePhantom(crewStore.crew.id, editingPhantom.id, {
|
||||||
joinedAt: editJoinDate,
|
joinedAt: editJoinDate,
|
||||||
retired: editRetired,
|
retired: editRetired,
|
||||||
retiredAt: editRetired ? editRetiredAt || undefined : undefined
|
retiredAt: editRetired ? editRetiredAt || undefined : undefined,
|
||||||
|
granblueId: editGranblueId || undefined
|
||||||
})
|
})
|
||||||
// Invalidate members query
|
// Invalidate members query
|
||||||
membersQuery.refetch()
|
membersQuery.refetch()
|
||||||
|
|
@ -255,6 +259,7 @@
|
||||||
editJoinDate = ''
|
editJoinDate = ''
|
||||||
editRetired = false
|
editRetired = false
|
||||||
editRetiredAt = ''
|
editRetiredAt = ''
|
||||||
|
editGranblueId = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
function openDeletePhantomDialog(phantom: PhantomPlayer) {
|
function openDeletePhantomDialog(phantom: PhantomPlayer) {
|
||||||
|
|
@ -315,12 +320,11 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
// Get phantoms with pending claims (assigned but not confirmed)
|
// Get phantoms with pending claims (assigned but not confirmed)
|
||||||
// Use phantom query when not viewing phantom/all filter to ensure badge always has data
|
// Use phantom query when viewing pending filter since it doesn't include phantoms
|
||||||
const pendingClaimPhantoms = $derived.by(() => {
|
const pendingClaimPhantoms = $derived.by(() => {
|
||||||
let phantoms = membersQuery.data?.phantoms
|
const phantoms = filter === 'pending'
|
||||||
if (filter !== 'phantom' && filter !== 'all') {
|
? phantomsQuery.data?.phantoms
|
||||||
phantoms = phantomsQuery.data?.phantoms
|
: membersQuery.data?.phantoms
|
||||||
}
|
|
||||||
return phantoms?.filter((p) => p.claimedBy && !p.claimConfirmed) ?? []
|
return phantoms?.filter((p) => p.claimedBy && !p.claimConfirmed) ?? []
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -444,52 +448,56 @@
|
||||||
<p>Failed to load members</p>
|
<p>Failed to load members</p>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<!-- Regular members -->
|
{@const hasMembers = membersQuery.data?.members && membersQuery.data.members.length > 0}
|
||||||
{#if membersQuery.data?.members && membersQuery.data.members.length > 0}
|
{@const hasPhantoms = membersQuery.data?.phantoms && membersQuery.data.phantoms.length > 0}
|
||||||
<ul class="member-list">
|
|
||||||
{#each membersQuery.data.members as member}
|
|
||||||
<MemberRow
|
|
||||||
{member}
|
|
||||||
onEdit={() => openEditMemberDialog(member)}
|
|
||||||
onPromote={() => openPromoteDialog(member)}
|
|
||||||
onDemote={() => openDemoteDialog(member)}
|
|
||||||
onRemove={() => openRemoveDialog(member)}
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
{:else if filter !== 'phantom'}
|
|
||||||
<p class="empty-state">No members found</p>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<!-- Phantom players -->
|
<!-- Empty state for active/retired when no members or phantoms -->
|
||||||
{#if membersQuery.data?.phantoms && membersQuery.data.phantoms.length > 0}
|
{#if (filter === 'active' || filter === 'retired') && !hasMembers && !hasPhantoms}
|
||||||
{#if filter === 'all' && membersQuery.data.members.length > 0}
|
|
||||||
<div class="section-divider">
|
|
||||||
<span>Phantom Players</span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<ul class="member-list">
|
|
||||||
{#each membersQuery.data.phantoms as phantom}
|
|
||||||
<PhantomRow
|
|
||||||
{phantom}
|
|
||||||
currentUserId={crewStore.membership?.user?.id}
|
|
||||||
onEdit={() => openEditPhantomDialog(phantom)}
|
|
||||||
onDelete={() => openDeletePhantomDialog(phantom)}
|
|
||||||
onAssign={() => openAssignPhantomDialog(phantom)}
|
|
||||||
onAccept={() => openConfirmClaimDialog(phantom)}
|
|
||||||
onDecline={() => handleDeclineClaim(phantom)}
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
{:else if filter === 'phantom'}
|
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<p>No phantom players.</p>
|
<p>No {filter} players found.</p>
|
||||||
{#if crewStore.isOfficer}
|
|
||||||
<Button variant="secondary" size="small" onclick={() => (bulkPhantomDialogOpen = true)}>
|
|
||||||
Add phantoms...
|
|
||||||
</Button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
{:else}
|
||||||
|
<!-- Regular members -->
|
||||||
|
{#if hasMembers}
|
||||||
|
{#if (filter === 'active' || filter === 'retired') && hasPhantoms}
|
||||||
|
<div class="section-divider">
|
||||||
|
<span>Members ({membersQuery.data?.members.length})</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<ul class="member-list">
|
||||||
|
{#each membersQuery.data?.members ?? [] as member}
|
||||||
|
<MemberRow
|
||||||
|
{member}
|
||||||
|
onEdit={() => openEditMemberDialog(member)}
|
||||||
|
onPromote={() => openPromoteDialog(member)}
|
||||||
|
onDemote={() => openDemoteDialog(member)}
|
||||||
|
onRemove={() => openRemoveDialog(member)}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- Phantom players -->
|
||||||
|
{#if hasPhantoms}
|
||||||
|
{#if filter === 'all' || filter === 'active' || filter === 'retired'}
|
||||||
|
<div class="section-divider">
|
||||||
|
<span>Phantom Players ({membersQuery.data?.phantoms.length})</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<ul class="member-list">
|
||||||
|
{#each membersQuery.data?.phantoms ?? [] as phantom}
|
||||||
|
<PhantomRow
|
||||||
|
{phantom}
|
||||||
|
currentUserId={crewStore.membership?.user?.id}
|
||||||
|
onEdit={() => openEditPhantomDialog(phantom)}
|
||||||
|
onDelete={() => openDeletePhantomDialog(phantom)}
|
||||||
|
onAssign={() => openAssignPhantomDialog(phantom)}
|
||||||
|
onAccept={() => openConfirmClaimDialog(phantom)}
|
||||||
|
onDecline={() => handleDeclineClaim(phantom)}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -543,10 +551,15 @@
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<div class="modal-form">
|
<div class="modal-form">
|
||||||
<div class="form-fields">
|
<div class="form-fields">
|
||||||
<div class="form-field">
|
{#if editingPhantom}
|
||||||
<label for="joinDate">Join date</label>
|
<Input
|
||||||
<input id="joinDate" type="date" bind:value={editJoinDate} class="date-input" />
|
label="Granblue ID"
|
||||||
</div>
|
bind:value={editGranblueId}
|
||||||
|
maxLength={20}
|
||||||
|
variant="contained"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
<DatePicker label="Join date" bind:value={editJoinDate} contained />
|
||||||
<p class="help-text">
|
<p class="help-text">
|
||||||
This date is used to determine which events a member was active for when adding
|
This date is used to determine which events a member was active for when adding
|
||||||
historical GW scores.
|
historical GW scores.
|
||||||
|
|
@ -557,10 +570,7 @@
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
{#if editRetired}
|
{#if editRetired}
|
||||||
<div class="form-field">
|
<DatePicker label="Retired date" bind:value={editRetiredAt} contained />
|
||||||
<label for="retiredAt">Retired date</label>
|
|
||||||
<input id="retiredAt" type="date" bind:value={editRetiredAt} class="date-input" />
|
|
||||||
</div>
|
|
||||||
<p class="help-text">
|
<p class="help-text">
|
||||||
This date is used to determine which events a retired player was active for.
|
This date is used to determine which events a retired player was active for.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -758,44 +768,12 @@
|
||||||
gap: spacing.$unit-3x;
|
gap: spacing.$unit-3x;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-field {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: spacing.$unit-half;
|
|
||||||
|
|
||||||
label {
|
|
||||||
font-size: typography.$font-small;
|
|
||||||
font-weight: typography.$medium;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(fieldset) {
|
:global(fieldset) {
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.date-input {
|
|
||||||
padding: spacing.$unit spacing.$unit-2x;
|
|
||||||
border: none;
|
|
||||||
border-radius: layout.$input-corner;
|
|
||||||
font-size: typography.$font-regular;
|
|
||||||
font-family: inherit;
|
|
||||||
background: var(--input-bound-bg);
|
|
||||||
color: var(--text-primary);
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--input-bound-bg-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
background: var(--input-bound-bg-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.help-text {
|
.help-text {
|
||||||
font-size: typography.$font-small;
|
font-size: typography.$font-small;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue