add bulk phantom creation, delete confirmation, default to all filter
- bulk create phantoms with individual join dates - confirm before deleting phantoms - reorder filters with All first and as default
This commit is contained in:
parent
7dcb100412
commit
4745baca1c
4 changed files with 350 additions and 110 deletions
|
|
@ -205,6 +205,26 @@ export class CrewAdapter extends BaseAdapter {
|
||||||
return response.phantom_player
|
return response.phantom_player
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create multiple phantom players at once (officers only)
|
||||||
|
*/
|
||||||
|
async bulkCreatePhantoms(
|
||||||
|
crewId: string,
|
||||||
|
phantoms: CreatePhantomPlayerInput[],
|
||||||
|
options?: RequestOptions
|
||||||
|
): Promise<PhantomPlayer[]> {
|
||||||
|
const response = await this.request<{ phantom_players: PhantomPlayer[] }>(
|
||||||
|
`/crews/${crewId}/phantom_players/bulk_create`,
|
||||||
|
{
|
||||||
|
...options,
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ phantom_players: phantoms })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
this.clearCache('/crew/members')
|
||||||
|
return response.phantom_players
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update a phantom player
|
* Update a phantom player
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -183,6 +183,21 @@ export function useCreatePhantom() {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bulk create phantom players mutation
|
||||||
|
*/
|
||||||
|
export function useBulkCreatePhantoms() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return createMutation(() => ({
|
||||||
|
mutationFn: ({ crewId, phantoms }: { crewId: string; phantoms: CreatePhantomPlayerInput[] }) =>
|
||||||
|
crewAdapter.bulkCreatePhantoms(crewId, phantoms),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: crewKeys.membersAll() })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update phantom player mutation
|
* Update phantom player mutation
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
263
src/lib/components/crew/BulkPhantomModal.svelte
Normal file
263
src/lib/components/crew/BulkPhantomModal.svelte
Normal file
|
|
@ -0,0 +1,263 @@
|
||||||
|
<svelte:options runes={true} />
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { untrack } from 'svelte'
|
||||||
|
import { useBulkCreatePhantoms } from '$lib/api/mutations/crew.mutations'
|
||||||
|
import Dialog from '$lib/components/ui/Dialog.svelte'
|
||||||
|
import ModalHeader from '$lib/components/ui/ModalHeader.svelte'
|
||||||
|
import ModalBody from '$lib/components/ui/ModalBody.svelte'
|
||||||
|
import ModalFooter from '$lib/components/ui/ModalFooter.svelte'
|
||||||
|
import Button from '$lib/components/ui/Button.svelte'
|
||||||
|
import Icon from '$lib/components/Icon.svelte'
|
||||||
|
import type { CreatePhantomPlayerInput } from '$lib/types/api/crew'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open: boolean
|
||||||
|
crewId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
let { open = $bindable(false), crewId }: Props = $props()
|
||||||
|
|
||||||
|
const bulkCreateMutation = useBulkCreatePhantoms()
|
||||||
|
|
||||||
|
// State for phantom rows
|
||||||
|
interface PhantomRow {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
granblueId: string
|
||||||
|
joinedAt: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a regular variable for ID counter (doesn't need to be reactive)
|
||||||
|
let nextId = 1
|
||||||
|
let rows = $state<PhantomRow[]>([createEmptyRow()])
|
||||||
|
|
||||||
|
function createEmptyRow(): PhantomRow {
|
||||||
|
return {
|
||||||
|
id: nextId++,
|
||||||
|
name: '',
|
||||||
|
granblueId: '',
|
||||||
|
joinedAt: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addRow() {
|
||||||
|
rows = [...rows, createEmptyRow()]
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeRow(id: number) {
|
||||||
|
if (rows.length <= 1) return
|
||||||
|
rows = rows.filter((row) => row.id !== id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetState() {
|
||||||
|
nextId = 1
|
||||||
|
rows = [createEmptyRow()]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have at least one valid phantom (name is required)
|
||||||
|
const validPhantoms = $derived(rows.filter((row) => row.name.trim()))
|
||||||
|
const canSubmit = $derived(validPhantoms.length > 0 && !bulkCreateMutation.isPending)
|
||||||
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
if (!canSubmit) return
|
||||||
|
|
||||||
|
const phantoms: CreatePhantomPlayerInput[] = validPhantoms.map((row) => ({
|
||||||
|
name: row.name.trim(),
|
||||||
|
granblueId: row.granblueId.trim() || undefined,
|
||||||
|
joinedAt: row.joinedAt || undefined
|
||||||
|
}))
|
||||||
|
|
||||||
|
try {
|
||||||
|
await bulkCreateMutation.mutateAsync({ crewId, phantoms })
|
||||||
|
resetState()
|
||||||
|
open = false
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to create phantoms:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
resetState()
|
||||||
|
open = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset state when modal opens
|
||||||
|
$effect(() => {
|
||||||
|
if (open) {
|
||||||
|
untrack(() => resetState())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dialog bind:open size="medium">
|
||||||
|
{#snippet children()}
|
||||||
|
<ModalHeader
|
||||||
|
title="Add Phantom Players"
|
||||||
|
description="Phantom players allow you to track the scores of members without accounts"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ModalBody>
|
||||||
|
<div class="phantom-rows">
|
||||||
|
<div class="row-header">
|
||||||
|
<span class="col-name">Name</span>
|
||||||
|
<span class="col-id">Granblue ID</span>
|
||||||
|
<span class="col-date">Join Date</span>
|
||||||
|
<span class="col-action"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#each rows as row (row.id)}
|
||||||
|
<div class="phantom-row">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="input name-input"
|
||||||
|
placeholder="Player name"
|
||||||
|
bind:value={row.name}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="input id-input"
|
||||||
|
placeholder="Optional"
|
||||||
|
bind:value={row.granblueId}
|
||||||
|
/>
|
||||||
|
<input type="date" class="input date-input" bind:value={row.joinedAt} />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="remove-btn"
|
||||||
|
onclick={() => removeRow(row.id)}
|
||||||
|
disabled={rows.length <= 1}
|
||||||
|
aria-label="Remove row"
|
||||||
|
>
|
||||||
|
<Icon name="close" size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button variant="ghost" size="small" leftIcon="plus" onclick={addRow}>
|
||||||
|
Add Another
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{#if bulkCreateMutation.isError}
|
||||||
|
<p class="error-message">
|
||||||
|
Failed to create phantom players. Please check the inputs and try again.
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter
|
||||||
|
onCancel={handleCancel}
|
||||||
|
cancelDisabled={bulkCreateMutation.isPending}
|
||||||
|
primaryAction={{
|
||||||
|
label: bulkCreateMutation.isPending
|
||||||
|
? 'Creating...'
|
||||||
|
: `Create ${validPhantoms.length} Phantom${validPhantoms.length !== 1 ? 's' : ''}`,
|
||||||
|
onclick: handleSubmit,
|
||||||
|
disabled: !canSubmit
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/snippet}
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@use '$src/themes/colors' as colors;
|
||||||
|
@use '$src/themes/spacing' as spacing;
|
||||||
|
@use '$src/themes/typography' as typography;
|
||||||
|
@use '$src/themes/layout' as layout;
|
||||||
|
|
||||||
|
.phantom-rows {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: spacing.$unit;
|
||||||
|
margin-bottom: spacing.$unit-2x;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 140px 180px 32px;
|
||||||
|
gap: spacing.$unit;
|
||||||
|
padding: 0 spacing.$unit-half;
|
||||||
|
font-size: typography.$font-small;
|
||||||
|
font-weight: typography.$medium;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.phantom-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 140px 180px 32px;
|
||||||
|
gap: spacing.$unit;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-input {
|
||||||
|
padding: spacing.$unit-half spacing.$unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
border-radius: layout.$item-corner-small;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background: rgba(0, 0, 0, 0.06);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: colors.$error;
|
||||||
|
font-size: typography.$font-small;
|
||||||
|
margin: spacing.$unit 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-name {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-id,
|
||||||
|
.col-date {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-action {
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
import {
|
import {
|
||||||
useRemoveMember,
|
useRemoveMember,
|
||||||
useUpdateMembership,
|
useUpdateMembership,
|
||||||
useCreatePhantom,
|
|
||||||
useDeletePhantom
|
useDeletePhantom
|
||||||
} from '$lib/api/mutations/crew.mutations'
|
} from '$lib/api/mutations/crew.mutations'
|
||||||
import { crewAdapter } from '$lib/api/adapters/crew.adapter'
|
import { crewAdapter } from '$lib/api/adapters/crew.adapter'
|
||||||
|
|
@ -19,9 +18,9 @@
|
||||||
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'
|
||||||
import Input from '$lib/components/ui/Input.svelte'
|
|
||||||
import CrewHeader from '$lib/components/crew/CrewHeader.svelte'
|
import CrewHeader from '$lib/components/crew/CrewHeader.svelte'
|
||||||
import ScoutUserModal from '$lib/components/crew/ScoutUserModal.svelte'
|
import ScoutUserModal from '$lib/components/crew/ScoutUserModal.svelte'
|
||||||
|
import BulkPhantomModal from '$lib/components/crew/BulkPhantomModal.svelte'
|
||||||
import { DropdownMenu as DropdownMenuBase } from 'bits-ui'
|
import { DropdownMenu as DropdownMenuBase } from 'bits-ui'
|
||||||
import type { MemberFilter, CrewMembership, PhantomPlayer, CrewInvitation } from '$lib/types/api/crew'
|
import type { MemberFilter, CrewMembership, PhantomPlayer, CrewInvitation } from '$lib/types/api/crew'
|
||||||
import type { PageData } from './$types'
|
import type { PageData } from './$types'
|
||||||
|
|
@ -36,7 +35,7 @@
|
||||||
|
|
||||||
// Get filter from URL
|
// Get filter from URL
|
||||||
const filter = $derived<MemberFilter>(
|
const filter = $derived<MemberFilter>(
|
||||||
($page.url.searchParams.get('filter') as MemberFilter) || 'active'
|
($page.url.searchParams.get('filter') as MemberFilter) || 'all'
|
||||||
)
|
)
|
||||||
|
|
||||||
// Query for members based on filter
|
// Query for members based on filter
|
||||||
|
|
@ -76,21 +75,20 @@
|
||||||
// Mutations
|
// Mutations
|
||||||
const removeMemberMutation = useRemoveMember()
|
const removeMemberMutation = useRemoveMember()
|
||||||
const updateMembershipMutation = useUpdateMembership()
|
const updateMembershipMutation = useUpdateMembership()
|
||||||
const createPhantomMutation = useCreatePhantom()
|
|
||||||
const deletePhantomMutation = useDeletePhantom()
|
const deletePhantomMutation = useDeletePhantom()
|
||||||
|
|
||||||
// Filter options
|
// Filter options
|
||||||
const filterOptions: { value: MemberFilter; label: string }[] = [
|
const filterOptions: { value: MemberFilter; label: string }[] = [
|
||||||
|
{ value: 'all', label: 'All' },
|
||||||
{ value: 'active', label: 'Active' },
|
{ value: 'active', label: 'Active' },
|
||||||
{ value: 'phantom', label: 'Phantoms' },
|
{ value: 'phantom', label: 'Phantoms' },
|
||||||
{ value: 'retired', label: 'Retired' },
|
{ value: 'retired', label: 'Retired' }
|
||||||
{ value: 'all', label: 'All' }
|
|
||||||
]
|
]
|
||||||
|
|
||||||
// Change filter
|
// Change filter
|
||||||
function handleFilterChange(newFilter: MemberFilter) {
|
function handleFilterChange(newFilter: MemberFilter) {
|
||||||
const url = new URL($page.url)
|
const url = new URL($page.url)
|
||||||
if (newFilter === 'active') {
|
if (newFilter === 'all') {
|
||||||
url.searchParams.delete('filter')
|
url.searchParams.delete('filter')
|
||||||
} else {
|
} else {
|
||||||
url.searchParams.set('filter', newFilter)
|
url.searchParams.set('filter', newFilter)
|
||||||
|
|
@ -116,11 +114,11 @@
|
||||||
let invitationsSectionOpen = $state(true)
|
let invitationsSectionOpen = $state(true)
|
||||||
|
|
||||||
// Dialog state for phantom creation
|
// Dialog state for phantom creation
|
||||||
let phantomDialogOpen = $state(false)
|
let bulkPhantomDialogOpen = $state(false)
|
||||||
let phantomName = $state('')
|
|
||||||
let phantomGranblueId = $state('')
|
// Dialog state for phantom deletion confirmation
|
||||||
let phantomNotes = $state('')
|
let deletePhantomDialogOpen = $state(false)
|
||||||
let phantomJoinedAt = $state('')
|
let phantomToDelete = $state<PhantomPlayer | null>(null)
|
||||||
|
|
||||||
// Role display helpers
|
// Role display helpers
|
||||||
function getRoleLabel(role: string): string {
|
function getRoleLabel(role: string): string {
|
||||||
|
|
@ -241,45 +239,25 @@
|
||||||
editJoinDate = ''
|
editJoinDate = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phantom actions
|
function openDeletePhantomDialog(phantom: PhantomPlayer) {
|
||||||
function openPhantomDialog() {
|
phantomToDelete = phantom
|
||||||
phantomName = ''
|
deletePhantomDialogOpen = true
|
||||||
phantomGranblueId = ''
|
|
||||||
phantomNotes = ''
|
|
||||||
phantomJoinedAt = ''
|
|
||||||
phantomDialogOpen = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCreatePhantom() {
|
async function handleConfirmDeletePhantom() {
|
||||||
if (!phantomName.trim() || !crewStore.crew) return
|
if (!crewStore.crew || !phantomToDelete) return
|
||||||
|
|
||||||
try {
|
|
||||||
await createPhantomMutation.mutateAsync({
|
|
||||||
crewId: crewStore.crew.id,
|
|
||||||
input: {
|
|
||||||
name: phantomName.trim(),
|
|
||||||
granblueId: phantomGranblueId.trim() || undefined,
|
|
||||||
notes: phantomNotes.trim() || undefined,
|
|
||||||
joinedAt: phantomJoinedAt || undefined
|
|
||||||
}
|
|
||||||
})
|
|
||||||
phantomDialogOpen = false
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to create phantom:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleDeletePhantom(phantom: PhantomPlayer) {
|
|
||||||
if (!crewStore.crew) return
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await deletePhantomMutation.mutateAsync({
|
await deletePhantomMutation.mutateAsync({
|
||||||
crewId: crewStore.crew.id,
|
crewId: crewStore.crew.id,
|
||||||
phantomId: phantom.id
|
phantomId: phantomToDelete.id
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to delete phantom:', error)
|
console.error('Failed to delete phantom:', error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deletePhantomDialogOpen = false
|
||||||
|
phantomToDelete = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format date
|
// Format date
|
||||||
|
|
@ -337,8 +315,11 @@
|
||||||
<Button variant="secondary" size="small" iconOnly icon="ellipsis" {...props} />
|
<Button variant="secondary" size="small" iconOnly icon="ellipsis" {...props} />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet menu()}
|
{#snippet menu()}
|
||||||
<DropdownMenuBase.Item class="dropdown-menu-item" onclick={openPhantomDialog}>
|
<DropdownMenuBase.Item
|
||||||
Add Phantom
|
class="dropdown-menu-item"
|
||||||
|
onclick={() => (bulkPhantomDialogOpen = true)}
|
||||||
|
>
|
||||||
|
Add phantoms...
|
||||||
</DropdownMenuBase.Item>
|
</DropdownMenuBase.Item>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
@ -504,7 +485,7 @@
|
||||||
</DropdownMenuBase.Item>
|
</DropdownMenuBase.Item>
|
||||||
<DropdownMenuBase.Item
|
<DropdownMenuBase.Item
|
||||||
class="dropdown-menu-item danger"
|
class="dropdown-menu-item danger"
|
||||||
onclick={() => handleDeletePhantom(phantom)}
|
onclick={() => openDeletePhantomDialog(phantom)}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</DropdownMenuBase.Item>
|
</DropdownMenuBase.Item>
|
||||||
|
|
@ -518,8 +499,8 @@
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<p>No phantom players.</p>
|
<p>No phantom players.</p>
|
||||||
{#if crewStore.isOfficer}
|
{#if crewStore.isOfficer}
|
||||||
<Button variant="secondary" size="small" onclick={openPhantomDialog}>
|
<Button variant="secondary" size="small" onclick={() => (bulkPhantomDialogOpen = true)}>
|
||||||
Add Phantom
|
Add phantoms...
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -563,69 +544,6 @@
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<!-- Create Phantom Dialog -->
|
|
||||||
<Dialog bind:open={phantomDialogOpen}>
|
|
||||||
{#snippet children()}
|
|
||||||
<ModalHeader
|
|
||||||
title="Add Phantom Player"
|
|
||||||
description="Phantom players allow you to track the scores of members without accounts"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ModalBody>
|
|
||||||
<div class="modal-form">
|
|
||||||
<div class="form-fields">
|
|
||||||
<Input
|
|
||||||
label="Name"
|
|
||||||
bind:value={phantomName}
|
|
||||||
placeholder="Player's name or nickname"
|
|
||||||
fullWidth
|
|
||||||
contained
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Input
|
|
||||||
label="Granblue ID"
|
|
||||||
bind:value={phantomGranblueId}
|
|
||||||
placeholder="In-game player ID (optional)"
|
|
||||||
fullWidth
|
|
||||||
contained
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="form-field">
|
|
||||||
<label for="phantomJoinedAt">Join Date <span class="optional">(optional)</span></label>
|
|
||||||
<input
|
|
||||||
id="phantomJoinedAt"
|
|
||||||
type="date"
|
|
||||||
bind:value={phantomJoinedAt}
|
|
||||||
class="date-input"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-field">
|
|
||||||
<label for="phantomNotes">Notes <span class="optional">(optional)</span></label>
|
|
||||||
<textarea
|
|
||||||
id="phantomNotes"
|
|
||||||
bind:value={phantomNotes}
|
|
||||||
placeholder="Optional notes about this player"
|
|
||||||
rows="2"
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ModalBody>
|
|
||||||
|
|
||||||
<ModalFooter
|
|
||||||
onCancel={() => (phantomDialogOpen = false)}
|
|
||||||
cancelDisabled={createPhantomMutation.isPending}
|
|
||||||
primaryAction={{
|
|
||||||
label: createPhantomMutation.isPending ? 'Creating...' : 'Create',
|
|
||||||
onclick: handleCreatePhantom,
|
|
||||||
disabled: !phantomName.trim() || createPhantomMutation.isPending
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/snippet}
|
|
||||||
</Dialog>
|
|
||||||
|
|
||||||
<!-- Edit Join Date Dialog -->
|
<!-- Edit Join Date Dialog -->
|
||||||
<Dialog bind:open={editJoinDateDialogOpen}>
|
<Dialog bind:open={editJoinDateDialogOpen}>
|
||||||
{#snippet children()}
|
{#snippet children()}
|
||||||
|
|
@ -657,9 +575,33 @@
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
<!-- Delete Phantom Confirmation Dialog -->
|
||||||
|
<Dialog bind:open={deletePhantomDialogOpen}>
|
||||||
|
{#snippet children()}
|
||||||
|
<ModalHeader title="Delete Phantom Player" />
|
||||||
|
|
||||||
|
<ModalBody>
|
||||||
|
<p class="confirm-message">
|
||||||
|
Are you sure you want to delete {phantomToDelete?.name ?? 'this phantom player'}? This
|
||||||
|
action cannot be undone.
|
||||||
|
</p>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter
|
||||||
|
onCancel={() => (deletePhantomDialogOpen = false)}
|
||||||
|
primaryAction={{
|
||||||
|
label: 'Delete',
|
||||||
|
onclick: handleConfirmDeletePhantom,
|
||||||
|
destructive: true
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/snippet}
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
<!-- Scout User Modal -->
|
<!-- Scout User Modal -->
|
||||||
{#if crewStore.crew?.id}
|
{#if crewStore.crew?.id}
|
||||||
<ScoutUserModal bind:open={scoutModalOpen} crewId={crewStore.crew.id} />
|
<ScoutUserModal bind:open={scoutModalOpen} crewId={crewStore.crew.id} />
|
||||||
|
<BulkPhantomModal bind:open={bulkPhantomDialogOpen} crewId={crewStore.crew.id} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
@ -868,7 +810,7 @@
|
||||||
|
|
||||||
// Confirm dialog styles
|
// Confirm dialog styles
|
||||||
.confirm-message {
|
.confirm-message {
|
||||||
color: var(--text-secondary);
|
color: var(--text-primary);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue