Refactor roster action handling in WorkflowRosterEditor to improve clarity and maintainability. Update player movement and roster manipulation logic to use structured action parameters. Enhance drag-and-drop functionality for roster slots and ensure proper state management in the UI.
This commit is contained in:
@@ -524,11 +524,21 @@ function WorkflowRosterEditor({ eventId, roster, players, locked, onChanged }: {
|
||||
| { kind: 'place'; teamId: string; reservePlayerId: string; role: 'Tank' | 'Damage' | 'Support' }
|
||||
| { kind: 'remove'; teamId: string; playerId: string }
|
||||
| { kind: 'captain'; teamId: string; playerId: string }) => {
|
||||
if (action.kind === 'swap') return api.swapRoster(eventId, { ...action, expectedVersion: roster.version })
|
||||
if (action.kind === 'substitute') return api.substituteRoster(eventId, { ...action, expectedVersion: roster.version })
|
||||
if (action.kind === 'move') return api.moveRosterPlayer(eventId, { ...action, expectedVersion: roster.version })
|
||||
if (action.kind === 'place') return api.placeReservePlayer(eventId, { ...action, expectedVersion: roster.version })
|
||||
if (action.kind === 'remove') return api.removeRosterPlayer(eventId, { ...action, expectedVersion: roster.version })
|
||||
if (action.kind === 'swap') return api.swapRoster(eventId, {
|
||||
teamAId: action.teamAId, playerAId: action.playerAId, teamBId: action.teamBId, playerBId: action.playerBId, expectedVersion: roster.version,
|
||||
})
|
||||
if (action.kind === 'substitute') return api.substituteRoster(eventId, {
|
||||
teamId: action.teamId, outgoingPlayerId: action.outgoingPlayerId, reservePlayerId: action.reservePlayerId, expectedVersion: roster.version,
|
||||
})
|
||||
if (action.kind === 'move') return api.moveRosterPlayer(eventId, {
|
||||
fromTeamId: action.fromTeamId, playerId: action.playerId, toTeamId: action.toTeamId, role: action.role, expectedVersion: roster.version,
|
||||
})
|
||||
if (action.kind === 'place') return api.placeReservePlayer(eventId, {
|
||||
teamId: action.teamId, reservePlayerId: action.reservePlayerId, role: action.role, expectedVersion: roster.version,
|
||||
})
|
||||
if (action.kind === 'remove') return api.removeRosterPlayer(eventId, {
|
||||
teamId: action.teamId, playerId: action.playerId, expectedVersion: roster.version,
|
||||
})
|
||||
return api.setRosterCaptain(eventId, action.teamId, action.playerId, roster.version)
|
||||
},
|
||||
onSuccess: (updated) => {
|
||||
@@ -564,7 +574,7 @@ function WorkflowRosterEditor({ eventId, roster, players, locked, onChanged }: {
|
||||
}
|
||||
return <section className="card roster-editor">
|
||||
<div className="section-title"><div><h2>Roster editor</h2><p>Version {roster.version} · drag players into any role slot or through reserve.</p></div>{roster.confirmed && <Badge tone="success"><Check />Locked</Badge>}</div>
|
||||
<div className="roster-editor-body"><div className="teams-grid">{roster.teams.map((team) => <article className="workflow-team" key={team.id}><header><h3>{team.name}</h3><label>Captain<select disabled={locked || mutate.isPending} value={team.captainPlayerId} onChange={(event) => mutate.mutate({ kind: 'captain', teamId: team.id, playerId: event.target.value })}><option value="">Choose captain</option>{team.slots.filter((slot) => slot.playerId).map((slot) => <option value={slot.playerId} key={slot.playerId}>{names.get(slot.playerId) ?? slot.playerId}</option>)}</select></label></header>{team.slots.map((slot, slotIndex) => <div className={`roster-slot ${slot.playerId ? '' : 'empty'}`} key={`${slot.role}-${slotIndex}`} onDragOver={(event) => { if (!locked) event.preventDefault() }} onDrop={(event) => dropOnSlot(event, team.id, slot)}><span className={`role-chip ${slot.role.toLowerCase()}`}>{slot.role[0]}</span>{slot.playerId ? <><strong draggable={!locked} onDragStart={(event) => startDrag(event, { kind: 'team', teamId: team.id, playerId: slot.playerId, role: slot.role })}>{names.get(slot.playerId) ?? slot.playerId}</strong><small>{rankLabel(slot.rating, language)}</small>{!locked && <button className="icon-button slot-remove" type="button" aria-label={`Move ${names.get(slot.playerId) ?? slot.playerId} to reserve`} onClick={() => mutate.mutate({ kind: 'remove', teamId: team.id, playerId: slot.playerId })}><X /></button>}</> : <><strong>Empty</strong><small>{slot.role}</small></>}</div>)}</article>)}</div>
|
||||
<div className="roster-editor-body"><div className="teams-grid">{roster.teams.map((team) => <article className="workflow-team" key={team.id}><header><h3>{team.name}</h3><label>Captain<select disabled={locked || mutate.isPending} value={team.captainPlayerId} onChange={(event) => mutate.mutate({ kind: 'captain', teamId: team.id, playerId: event.target.value })}><option value="">Choose captain</option>{team.slots.filter((slot) => slot.playerId).map((slot) => <option value={slot.playerId} key={slot.playerId}>{names.get(slot.playerId) ?? slot.playerId}</option>)}</select></label></header>{team.slots.map((slot, slotIndex) => <div className={`roster-slot ${slot.playerId ? '' : 'empty'}`} key={`${slot.role}-${slotIndex}`} draggable={!locked && Boolean(slot.playerId)} onDragStart={(event) => { if (slot.playerId) startDrag(event, { kind: 'team', teamId: team.id, playerId: slot.playerId, role: slot.role }) }} onDragOver={(event) => { if (!locked) event.preventDefault() }} onDrop={(event) => dropOnSlot(event, team.id, slot)}><span className={`role-chip ${slot.role.toLowerCase()}`}>{slot.role[0]}</span>{slot.playerId ? <><strong>{names.get(slot.playerId) ?? slot.playerId}</strong><small>{rankLabel(slot.rating, language)}</small>{!locked && <button className="icon-button slot-remove" type="button" aria-label={`Move ${names.get(slot.playerId) ?? slot.playerId} to reserve`} onClick={() => mutate.mutate({ kind: 'remove', teamId: team.id, playerId: slot.playerId })}><X /></button>}</> : <><strong>Empty</strong><small>{slot.role}</small></>}</div>)}</article>)}</div>
|
||||
<aside className="roster-reserve" onDragOver={(event) => { if (!locked) event.preventDefault() }} onDrop={(event) => { event.preventDefault(); const source = readDrag(event); if (!locked && source?.kind === 'team') mutate.mutate({ kind: 'remove', teamId: source.teamId, playerId: source.playerId }) }}><div><UsersRound /><span><strong>Reserve</strong><small>Drop a player here to remove them from a team</small></span></div><div className="reserve-list">{roster.reserve.length === 0 ? <p>Reserve is empty</p> : roster.reserve.map((id) => <button type="button" draggable={!locked} onDragStart={(event) => startDrag(event, { kind: 'reserve', playerId: id })} key={id}><span className="mini-avatar">{(names.get(id) ?? id).slice(0, 2).toUpperCase()}</span><strong>{names.get(id) ?? id}</strong></button>)}</div></aside>
|
||||
</div>
|
||||
{mutate.isError && <p className="error-note">{mutate.error.message}</p>}
|
||||
|
||||
@@ -244,7 +244,7 @@ textarea { min-height: 85px; resize: vertical; }
|
||||
.workflow-team header { padding: 14px; display: flex; align-items: end; justify-content: space-between; gap: 12px; border-bottom: 1px solid var(--border); }
|
||||
.workflow-team header label { display: flex; align-items: center; gap: 8px; color: var(--muted); font-size: 9px; }.workflow-team header select { padding: 7px; }
|
||||
.roster-slot { min-height: 54px; padding: 8px 12px; display: grid; grid-template-columns: 24px minmax(100px,1fr) auto 30px; align-items: center; gap: 8px; border-bottom: 1px solid #22272e; font-size: 10px; transition: background .15s,border-color .15s; }
|
||||
.roster-slot > small { color: var(--muted); }.roster-slot > strong[draggable="true"] { cursor: grab; }.roster-slot > strong[draggable="true"]:active { cursor: grabbing; }
|
||||
.roster-slot > small { color: var(--muted); }.roster-slot[draggable="true"] { cursor: grab; }.roster-slot[draggable="true"]:active { cursor: grabbing; }
|
||||
.roster-slot.empty { border: 1px dashed #424a55; background: #15181d; }.roster-slot.empty > strong { color: #737b86; font-style: italic; }.slot-remove { width: 28px; height: 28px; }.slot-remove svg { width: 13px; }
|
||||
.roster-reserve { min-height: 100%; padding: 14px; border: 1px dashed #3b424c; border-radius: 9px; background: #0d1015; }
|
||||
.roster-reserve > div:first-child { display: flex; gap: 9px; align-items: center; margin-bottom: 13px; }.roster-reserve > div:first-child svg { width: 17px; color: var(--orange); }.roster-reserve span { display: grid; }.roster-reserve small,.reserve-list p { color: var(--muted); font-size: 9px; }
|
||||
|
||||
Reference in New Issue
Block a user