feat: improve draft admin UI, draft state sync, and styling

Major refactor of Draft admin and participant Websocket state sync
Use consistent state dict serialization in DraftStateManager (to_dict, dict-like access, etc.)
Always include up-to-date participants and draft status in sync payloads
Draft phase/order summary now sent as objects instead of calling .get_summary()
UI/UX updates:
Updated DraftAdmin.jsx:
Connects DraftParticipant panel for real-time participant state
Centralizes phase advance, bidding, and sync controls
Moves phase selector into a dedicated panel
Refine markup/extends in room_admin.dj.html (use block body, fix root data attribute)
Minor fixes to DraftCountdownClock.jsx to robustly handle NaN time
CSS/layout:
Refactor .draft-participant styling to .wrapper within #draft-participant-root and #draft-admin-root for better responsive layout and code clarity
Server code:
Simplify draft consumer/manager state interaction, drop unused cache keys, update order determination and phase management, and ensure DRY status object responses
Small code style and consistency cleanups
Misc:
Add debugpy launch task in code-workspace and clean workspace JSON (style/consistency)
Minor formatting and error handling improvements
This commit is contained in:
2025-08-15 11:06:27 -05:00
parent 71f0f01abc
commit 9ddc8663a9
9 changed files with 193 additions and 167 deletions

View File

@@ -7,6 +7,7 @@ import { DraftMessage, DraftPhase, DraftPhaseLabel, DraftPhasesOrdered } from '.
import { fetchDraftDetails, isEmptyObject, handleDraftStatusMessages, handleUserIdentifyMessages } from "../common/utils.js"
import { DraftMoviePool } from "../common/DraftMoviePool.jsx"
import { DraftCountdownClock } from "../common/DraftCountdownClock.jsx"
import { DraftParticipant } from "../participant/DraftParticipant.jsx";
import { jsxs } from "react/jsx-runtime";
@@ -14,7 +15,6 @@ import { jsxs } from "react/jsx-runtime";
const DraftPhaseDisplay = ({ draftPhase, nextPhaseHandler, prevPhaseHandler }) => {
return (
<div className="draft-phase-container">
<label>Phase</label>
<div className="d-flex">
<div className="change-phase"><button onClick={prevPhaseHandler}><i className="bi bi-chevron-left"></i></button></div>
<ol>
@@ -46,19 +46,19 @@ export const DraftAdmin = ({ draftSessionId }) => {
})
}, [])
useEffect(()=>{
useEffect(() => {
if (!socket) return;
const openHandler = (event)=>{
const openHandler = (event) => {
console.log('Websocket Opened')
}
const closeHandler = (event)=>{
const closeHandler = (event) => {
console.log('Websocket Closed')
}
socket.addEventListener('open', openHandler );
socket.addEventListener('close', closeHandler );
return ()=>{
socket.removeEventListener('open', openHandler );
socket.removeEventListener('close', closeHandler );
socket.addEventListener('open', openHandler);
socket.addEventListener('close', closeHandler);
return () => {
socket.removeEventListener('open', openHandler);
socket.removeEventListener('close', closeHandler);
}
}, [socket])
@@ -67,7 +67,7 @@ export const DraftAdmin = ({ draftSessionId }) => {
const draftStatusMessageHandler = (event) => handleDraftStatusMessages(event, setDraftState)
const userIdentifyMessageHandler = (event) => handleUserIdentifyMessages(event, setCurrentUser)
const handleNominationRequest = (event)=> {
const handleNominationRequest = (event) => {
const message = JSON.parse(event.data)
const { type, payload } = message;
if (type == DraftMessage.NOMINATION_SUBMIT_REQUEST) {
@@ -79,15 +79,15 @@ export const DraftAdmin = ({ draftSessionId }) => {
))
}
}
socket.addEventListener('message', draftStatusMessageHandler );
socket.addEventListener('message', userIdentifyMessageHandler );
socket.addEventListener('message', handleNominationRequest );
socket.addEventListener('message', draftStatusMessageHandler);
socket.addEventListener('message', userIdentifyMessageHandler);
socket.addEventListener('message', handleNominationRequest);
return () => {
socket.removeEventListener('message', draftStatusMessageHandler)
socket.removeEventListener('message', userIdentifyMessageHandler );
socket.removeEventListener('message', handleNominationRequest );
socket.removeEventListener('message', userIdentifyMessageHandler);
socket.removeEventListener('message', handleNominationRequest);
};
}, [socket]);
@@ -134,36 +134,31 @@ export const DraftAdmin = ({ draftSessionId }) => {
const handleStartBidding = () => {
socket.send(
JSON.stringify(
{type: DraftMessage.BID_START_REQUEST}
{ type: DraftMessage.BID_START_REQUEST }
)
)
}
return (
<div className="container draft-panel admin">
<div className="d-flex justify-content-between border-bottom mb-2 p-1">
<h3>Draft Panel</h3>
<div className="d-flex gap-1">
<WebSocketStatus socket={socket} />
<button onClick={() => handleRequestDraftSummary()} className="btn btn-small btn-light">
<i className="bi bi-arrow-clockwise"></i>
</button>
<div className="">
<div className="">
<DraftParticipant draftSessionId={draftSessionId}></DraftParticipant>
<div className="d-flex justify-content-between border-bottom mb-2 p-1">
</div>
</div>
<ParticipantList
currentUser = {currentUser}
draftState={draftState}
draftDetails={draftDetails}
isAdmin={true}
/>
<div className="d-flex gap-1 m-1">
<button onClick={handleAdvanceDraft} className="btn btn-primary">Advance Draft</button>
<button onClick={handleStartBidding} className="btn btn-primary">Start Bidding</button>
<section className="d-flex justify-content-center mt-3">
<button onClick={() => handleRequestDraftSummary()} className="btn btn-small btn-light mx-1">
<i className="bi bi-arrow-clockwise"></i>
</button>
<button onClick={handleAdvanceDraft} className="btn btn-primary mx-1">Advance Index</button>
<button onClick={handleStartBidding} className="btn btn-primary mx-1">Start Bidding</button>
</section>
<div class="d-flex justify-content-center mt-3">
<DraftPhaseDisplay draftPhase={draftState.phase} nextPhaseHandler={() => { handlePhaseChange('next') }} prevPhaseHandler={() => { handlePhaseChange('previous') }}></DraftPhaseDisplay>
</div>
<DraftMoviePool draftDetails={draftDetails} draftState={draftState}></DraftMoviePool>
<DraftPhaseDisplay draftPhase={draftState.phase} nextPhaseHandler={() => { handlePhaseChange('next') }} prevPhaseHandler={() => { handlePhaseChange('previous') }}></DraftPhaseDisplay>
<DraftCountdownClock endTime={draftState.bidding_timer_end}></DraftCountdownClock>
</div>
);
};

View File

@@ -27,7 +27,7 @@ export function DraftCountdownClock({ endTime, onFinish }) {
return (
<div className="countdown-clock">
<span>
{minutes}:{pad(secs)}
{!isNaN(minutes) && !isNaN(secs) ? `${minutes}:${pad(secs)}` : "0:00"}
</span>
</div>
);

View File

@@ -85,7 +85,7 @@ export const DraftParticipant = ({ draftSessionId }) => {
}, [socket]);
return (
<div className="draft-participant">
<div className="wrapper">
<section class="panel draft-live">
<header class="panel-header d-flex justify-content-between align-items-center">
<h2 class="panel-title">Draft Live</h2>

View File

@@ -124,30 +124,34 @@
}
}
.draft-participant {
display: flex;
flex-wrap: wrap; /* allow panels to wrap */
gap: 1rem; /* space between panels */
justify-content: center; /* center the panels horizontally */
#draft-participant-root,
#draft-admin-root {
@extend .flex-grow-1;
.wrapper:first-child {
display: flex;
flex-wrap: wrap; /* allow panels to wrap */
gap: 1rem; /* space between panels */
justify-content: center; /* center the panels horizontally */
.panel {
flex: 1 1 350px; /* grow/shrink, base width */
max-width: 450px; /* never go beyond this */
min-width: 300px; /* keeps them from getting too small */
}
.panel.draft-live {
.draft-live-state-container {
@extend .d-flex;
.countdown-clock {
@extend .fs-1;
@extend .fw-bold;
@extend .col;
@extend .align-content-center;
@extend .text-center;
}
.pick-description{
@extend .col;
.panel {
flex: 1 1 350px; /* grow/shrink, base width */
max-width: 450px; /* never go beyond this */
min-width: 300px; /* keeps them from getting too small */
}
.panel.draft-live {
.draft-live-state-container {
@extend .d-flex;
.countdown-clock {
@extend .fs-1;
@extend .fw-bold;
@extend .col;
@extend .align-content-center;
@extend .text-center;
}
.pick-description {
@extend .col;
}
}
}
}
}
}