Files
boxofficefantasy/frontend/src/apps/draft/admin/DraftAdmin.jsx
Anthony Correa cd4d974fce Add timed bidding support with countdown displays and debug view
- Added `bidding_duration` field to `DraftSessionSettings` model and migration.
- Updated `DraftStateManager` to manage bidding start/end times using session settings.
- Extended WebSocket payloads to include bidding timer data.
- Added `DraftCountdownClock` React component and integrated into admin and participant UIs.
- Created new `DraftDebug` view, template, and front-end component for real-time state debugging.
- Updated utility functions to handle new timer fields in draft state.
- Changed script tags in templates to load with `defer` for non-blocking execution.
2025-08-10 18:19:54 -05:00

169 lines
5.6 KiB
JavaScript

import React, { useEffect, useState } from "react";
import { useWebSocket } from "../WebSocketContext.jsx";
import { WebSocketStatus } from "../common/WebSocketStatus.jsx";
import { ParticipantList } from "../common/ParticipantList.jsx";
import { DraftMessage, DraftPhase, DraftPhaseLabel, DraftPhasesOrdered } from '../constants.js';
import { fetchDraftDetails, isEmptyObject, handleDraftStatusMessages, handleUserIdentifyMessages } from "../common/utils.js"
import { DraftMoviePool } from "../common/DraftMoviePool.jsx"
import { DraftCountdownClock } from "../common/DraftCountdownClock.jsx"
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>
{
DraftPhasesOrdered.map((p) => (
<li key={p} className={p === draftPhase ? "current-phase" : ""}>
<span>{DraftPhaseLabel[p]}</span>
</li>
))
}
</ol>
<div className="change-phase"><button onClick={nextPhaseHandler}><i className="bi bi-chevron-right"></i></button></div>
</div>
</div>
)
}
export const DraftAdmin = ({ draftSessionId }) => {
const socket = useWebSocket();
const [draftDetails, setDraftDetails] = useState();
const [draftState, setDraftState] = useState({})
const [currentUser, setCurrentUser] = useState(null);
useEffect(() => {
fetchDraftDetails(draftSessionId)
.then((data) => {
console.log("Fetched draft data", data)
setDraftDetails(data)
})
}, [])
useEffect(()=>{
if (!socket) return;
const openHandler = (event)=>{
console.log('Websocket Opened')
}
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])
useEffect(() => {
if (!socket) return;
const draftStatusMessageHandler = (event) => handleDraftStatusMessages(event, setDraftState)
const userIdentifyMessageHandler = (event) => handleUserIdentifyMessages(event, setCurrentUser)
const handleNominationRequest = (event)=> {
const message = JSON.parse(event.data)
const { type, payload } = message;
if (type == DraftMessage.NOMINATION_SUBMIT_REQUEST) {
socket.send(JSON.stringify(
{
type: DraftMessage.NOMINATION_SUBMIT_REQUEST,
payload
}
))
}
}
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]);
const handlePhaseChange = (target) => {
let destination
const origin = draftState.phase
const originPhaseIndex = DraftPhasesOrdered.findIndex(i => i == origin)
console.log('origin phase index', originPhaseIndex)
if (target == "next" && originPhaseIndex < DraftPhasesOrdered.length) {
destination = DraftPhasesOrdered[originPhaseIndex + 1]
}
else if (target == "previous" && originPhaseIndex > 0) {
destination = DraftPhasesOrdered[originPhaseIndex - 1]
}
console.log(destination)
socket.send(
JSON.stringify(
{ type: DraftMessage.PHASE_CHANGE_REQUEST, origin, destination }
)
)
}
const handleStartDraft = () => {
}
const handleAdvanceDraft = () => {
socket.send(
JSON.stringify(
{ type: DraftMessage.DRAFT_INDEX_ADVANCE_REQUEST }
)
)
}
const handleRequestDraftSummary = () => {
socket.send(
JSON.stringify(
{ type: DraftMessage.STATUS_SYNC_REQUEST }
)
)
}
const handleStartBidding = () => {
socket.send(
JSON.stringify(
{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>
</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>
</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>
);
};