add membership history editing for boomerang players
This commit is contained in:
parent
90eb4b6659
commit
74df78a949
2 changed files with 188 additions and 23 deletions
|
|
@ -123,6 +123,17 @@ export class CrewAdapter extends BaseAdapter {
|
||||||
this.clearCache('/crew/members')
|
this.clearCache('/crew/members')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all membership periods for a user in a crew (for boomerang players)
|
||||||
|
*/
|
||||||
|
async getMembershipHistory(crewId: string, userId: string, options?: RequestOptions): Promise<CrewMembership[]> {
|
||||||
|
const response = await this.request<{ memberships: CrewMembership[] }>(
|
||||||
|
`/crews/${crewId}/memberships/by_user/${userId}`,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
return response.memberships
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== Invitation Operations ====================
|
// ==================== Invitation Operations ====================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,16 @@
|
||||||
let editRetiredAt = $state('')
|
let editRetiredAt = $state('')
|
||||||
let editGranblueId = $state('')
|
let editGranblueId = $state('')
|
||||||
|
|
||||||
|
// Membership history for boomerang players
|
||||||
|
interface EditableMembershipPeriod {
|
||||||
|
id: string
|
||||||
|
joinedAt: string
|
||||||
|
retiredAt: string
|
||||||
|
retired: boolean
|
||||||
|
}
|
||||||
|
let membershipHistory = $state<EditableMembershipPeriod[]>([])
|
||||||
|
let loadingHistory = $state(false)
|
||||||
|
|
||||||
// Dialog state for scout modal
|
// Dialog state for scout modal
|
||||||
let scoutModalOpen = $state(false)
|
let scoutModalOpen = $state(false)
|
||||||
|
|
||||||
|
|
@ -202,14 +212,35 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Member/phantom editing
|
// Member/phantom editing
|
||||||
function openEditMemberDialog(member: CrewMembership) {
|
async function openEditMemberDialog(member: CrewMembership) {
|
||||||
editingMember = member
|
editingMember = member
|
||||||
editingPhantom = null
|
editingPhantom = null
|
||||||
// Format date for input
|
// Format date for input
|
||||||
editJoinDate = member.joinedAt ? (member.joinedAt.split('T')[0] ?? '') : ''
|
editJoinDate = member.joinedAt ? (member.joinedAt.split('T')[0] ?? '') : ''
|
||||||
editRetired = member.retired
|
editRetired = member.retired
|
||||||
editRetiredAt = member.retiredAt ? (member.retiredAt.split('T')[0] ?? '') : ''
|
editRetiredAt = member.retiredAt ? (member.retiredAt.split('T')[0] ?? '') : ''
|
||||||
|
membershipHistory = []
|
||||||
editDialogOpen = true
|
editDialogOpen = true
|
||||||
|
|
||||||
|
// Fetch membership history for boomerang players
|
||||||
|
if (crewStore.crew && member.user?.id) {
|
||||||
|
loadingHistory = true
|
||||||
|
try {
|
||||||
|
const history = await crewAdapter.getMembershipHistory(crewStore.crew.id, member.user.id)
|
||||||
|
// Only show multi-period UI if there are multiple memberships
|
||||||
|
if (history.length > 1) {
|
||||||
|
membershipHistory = history.map((m) => ({
|
||||||
|
id: m.id,
|
||||||
|
joinedAt: m.joinedAt ? (m.joinedAt.split('T')[0] ?? '') : '',
|
||||||
|
retiredAt: m.retiredAt ? (m.retiredAt.split('T')[0] ?? '') : '',
|
||||||
|
retired: m.retired
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch membership history:', error)
|
||||||
|
}
|
||||||
|
loadingHistory = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openEditPhantomDialog(phantom: PhantomPlayer) {
|
function openEditPhantomDialog(phantom: PhantomPlayer) {
|
||||||
|
|
@ -227,15 +258,32 @@
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (editingMember) {
|
if (editingMember) {
|
||||||
await updateMembershipMutation.mutateAsync({
|
// Check if we have multiple membership periods (boomerang player)
|
||||||
crewId: crewStore.crew.id,
|
if (membershipHistory.length > 1) {
|
||||||
membershipId: editingMember.id,
|
// Update each membership period
|
||||||
input: {
|
for (const period of membershipHistory) {
|
||||||
joinedAt: editJoinDate,
|
await updateMembershipMutation.mutateAsync({
|
||||||
retired: editRetired,
|
crewId: crewStore.crew.id,
|
||||||
retiredAt: editRetired ? editRetiredAt || undefined : undefined
|
membershipId: period.id,
|
||||||
|
input: {
|
||||||
|
joinedAt: period.joinedAt,
|
||||||
|
retired: period.retired,
|
||||||
|
retiredAt: period.retired ? period.retiredAt || undefined : undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
} else {
|
||||||
|
// Single membership period (normal case)
|
||||||
|
await updateMembershipMutation.mutateAsync({
|
||||||
|
crewId: crewStore.crew.id,
|
||||||
|
membershipId: editingMember.id,
|
||||||
|
input: {
|
||||||
|
joinedAt: editJoinDate,
|
||||||
|
retired: editRetired,
|
||||||
|
retiredAt: editRetired ? editRetiredAt || undefined : undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
} else if (editingPhantom) {
|
} else if (editingPhantom) {
|
||||||
// Call the phantom update directly through the adapter
|
// Call the phantom update directly through the adapter
|
||||||
await crewAdapter.updatePhantom(crewStore.crew.id, editingPhantom.id, {
|
await crewAdapter.updatePhantom(crewStore.crew.id, editingPhantom.id, {
|
||||||
|
|
@ -260,6 +308,7 @@
|
||||||
editRetired = false
|
editRetired = false
|
||||||
editRetiredAt = ''
|
editRetiredAt = ''
|
||||||
editGranblueId = ''
|
editGranblueId = ''
|
||||||
|
membershipHistory = []
|
||||||
}
|
}
|
||||||
|
|
||||||
function openDeletePhantomDialog(phantom: PhantomPlayer) {
|
function openDeletePhantomDialog(phantom: PhantomPlayer) {
|
||||||
|
|
@ -559,21 +608,81 @@
|
||||||
variant="contained"
|
variant="contained"
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<DatePicker label="Join date" bind:value={editJoinDate} contained />
|
|
||||||
<p class="help-text">
|
{#if loadingHistory}
|
||||||
This date is used to determine which events a member was active for when adding
|
<p class="loading-text">Loading membership history...</p>
|
||||||
historical GW scores.
|
{:else if membershipHistory.length > 1}
|
||||||
</p>
|
<!-- Multiple membership periods (boomerang player) -->
|
||||||
<SettingsRow title="Retired" subtitle="This player is no longer a part of the crew">
|
<div class="membership-periods">
|
||||||
{#snippet control()}
|
<h4 class="periods-title">Membership Periods</h4>
|
||||||
<Switch bind:checked={editRetired} name="retired" />
|
<p class="help-text">
|
||||||
{/snippet}
|
This player has joined and left the crew multiple times. Edit each period below.
|
||||||
</SettingsRow>
|
</p>
|
||||||
{#if editRetired}
|
{#each membershipHistory as period, i}
|
||||||
<DatePicker label="Retired date" bind:value={editRetiredAt} contained />
|
<div class="period-row">
|
||||||
|
<span class="period-label">
|
||||||
|
{#if i === 0}
|
||||||
|
Current
|
||||||
|
{:else}
|
||||||
|
Period {membershipHistory.length - i}
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
<div class="period-fields">
|
||||||
|
<DatePicker
|
||||||
|
label="Joined"
|
||||||
|
bind:value={period.joinedAt}
|
||||||
|
contained
|
||||||
|
/>
|
||||||
|
{#if period.retired || i > 0}
|
||||||
|
<DatePicker
|
||||||
|
label="Left"
|
||||||
|
bind:value={period.retiredAt}
|
||||||
|
contained
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Retired toggle only affects current membership -->
|
||||||
|
{#if !membershipHistory[0]?.retired}
|
||||||
|
<SettingsRow title="Retired" subtitle="Mark current membership as ended">
|
||||||
|
{#snippet control()}
|
||||||
|
<Switch
|
||||||
|
checked={editRetired}
|
||||||
|
name="retired"
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
editRetired = checked
|
||||||
|
if (membershipHistory[0]) {
|
||||||
|
membershipHistory[0].retired = checked
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/snippet}
|
||||||
|
</SettingsRow>
|
||||||
|
{#if editRetired && membershipHistory[0]}
|
||||||
|
<DatePicker label="Retired date" bind:value={membershipHistory[0].retiredAt} contained />
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<!-- Single membership period (normal case) -->
|
||||||
|
<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 retired player was active for.
|
This date is used to determine which events a member was active for when adding
|
||||||
|
historical GW scores.
|
||||||
</p>
|
</p>
|
||||||
|
<SettingsRow title="Retired" subtitle="This player is no longer a part of the crew">
|
||||||
|
{#snippet control()}
|
||||||
|
<Switch bind:checked={editRetired} name="retired" />
|
||||||
|
{/snippet}
|
||||||
|
</SettingsRow>
|
||||||
|
{#if editRetired}
|
||||||
|
<DatePicker label="Retired date" bind:value={editRetiredAt} contained />
|
||||||
|
<p class="help-text">
|
||||||
|
This date is used to determine which events a retired player was active for.
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -584,7 +693,7 @@
|
||||||
primaryAction={{
|
primaryAction={{
|
||||||
label: 'Save',
|
label: 'Save',
|
||||||
onclick: handleSaveEdit,
|
onclick: handleSaveEdit,
|
||||||
disabled: !editJoinDate
|
disabled: membershipHistory.length > 1 ? !membershipHistory[0]?.joinedAt : !editJoinDate
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
@ -781,6 +890,51 @@
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
font-size: typography.$font-small;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin: 0;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.membership-periods {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: spacing.$unit-2x;
|
||||||
|
}
|
||||||
|
|
||||||
|
.periods-title {
|
||||||
|
font-size: typography.$font-regular;
|
||||||
|
font-weight: typography.$medium;
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.period-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: spacing.$unit;
|
||||||
|
padding: spacing.$unit-2x;
|
||||||
|
background: rgba(0, 0, 0, 0.02);
|
||||||
|
border-radius: layout.$item-corner;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.period-label {
|
||||||
|
font-size: typography.$font-small;
|
||||||
|
font-weight: typography.$medium;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.period-fields {
|
||||||
|
display: flex;
|
||||||
|
gap: spacing.$unit-2x;
|
||||||
|
|
||||||
|
:global(.date-picker) {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Invitation row styles
|
// Invitation row styles
|
||||||
.invitation-row {
|
.invitation-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue