Add DRF API app and real-time draft management UI
- Created new `api` Django app with serializers, viewsets, and routers to expose draft sessions, participants, and movie data. - Registered `api` app in settings and updated root URL configuration. - Extended WebSocket consumers with `inform.draft_status` / `request.draft_status` to allow fetching current draft state. - Updated `DraftSession` and related models to support reverse lookups for draft picks. - Enhanced draft state manager to include `draft_order` in summaries. - Added React WebSocket context provider, connection status component, and new admin/participant panels with phase and participant tracking. - Updated SCSS for participant lists, phase indicators, and status badges. - Modified Django templates to mount new React roots for admin and participant views. - Updated Webpack dev server config to proxy WebSocket connections.
This commit is contained in:
198
frontend/src/apps/draft/admin/DraftAdmin.jsx
Normal file
198
frontend/src/apps/draft/admin/DraftAdmin.jsx
Normal file
@@ -0,0 +1,198 @@
|
||||
// DraftAdmin.jsx
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { useWebSocket } from "../WebSocketContext.jsx";
|
||||
import { WebSocketStatus } from "../common/WebSocketStatus.jsx";
|
||||
import { DraftMessage, DraftPhases, DraftPhase } from '../constants.js';
|
||||
|
||||
const ParticipantList = ({ socket, participants, draftOrder }) => {
|
||||
const [connectedParticipants, setConnectedParticipants] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
const handleMessage = async ({ data }) => {
|
||||
const message = JSON.parse(data)
|
||||
const { type, payload } = message
|
||||
console.log('socket changed', message)
|
||||
if (payload?.connected_participants) {
|
||||
const { connected_participants } = payload
|
||||
setConnectedParticipants(connected_participants)
|
||||
}
|
||||
}
|
||||
socket.addEventListener("message", handleMessage)
|
||||
return () => {
|
||||
socket.removeEventListener("message", handleMessage)
|
||||
}
|
||||
}, [socket])
|
||||
|
||||
const ListTag = draftOrder.length > 0 ? "ol" : "ul"
|
||||
console.log
|
||||
const listItems = draftOrder.length > 0 ? draftOrder.map(d => participants.find(p => p.username == d)) : participants
|
||||
|
||||
|
||||
return (
|
||||
<div className="participant-list-container">
|
||||
<label>Particpants</label>
|
||||
<ListTag className="participant-list">
|
||||
{listItems.map((p, i) => (
|
||||
<li key={i}>
|
||||
<span>{p?.full_name}</span>
|
||||
<div
|
||||
className={
|
||||
`ms-2 stop-light ${connectedParticipants.includes(p?.username) ? "success" : "danger"}`
|
||||
}
|
||||
></div>
|
||||
</li>
|
||||
))}
|
||||
</ListTag>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const DraftPhaseDisplay = ({ draftPhase }) => {
|
||||
return (
|
||||
<div className="draft-phase-container">
|
||||
<label>Phase</label>
|
||||
<ol>
|
||||
{
|
||||
DraftPhases.map((p) => (
|
||||
<li key={p} className={p === draftPhase ? "current-phase" : ""}>
|
||||
<span>{p}</span>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ol>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const DraftOrder = ({ socket, draftOrder }) => {
|
||||
console.log("in component", draftOrder)
|
||||
return (
|
||||
<div>
|
||||
<label>Draft Order</label>
|
||||
<ol>
|
||||
{
|
||||
draftOrder.map((p) => (
|
||||
<li key={p}>
|
||||
{p}
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ol>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const DraftAdmin = ({ draftSessionId }) => {
|
||||
const socket = useWebSocket();
|
||||
const [connectedParticipants, setConnectedParticipants] = useState([]);
|
||||
const [draftDetails, setDraftDetails] = useState();
|
||||
const [participants, setParticipants] = React.useState([]);
|
||||
const [draftPhase, setDraftPhase] = useState();
|
||||
const [draftOrder, setDraftOrder] = useState([]);
|
||||
console.log(socket)
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchDraftDetails(draftSessionId) {
|
||||
fetch(`/api/draft/${draftSessionId}/`)
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
return response.json()
|
||||
}
|
||||
else {
|
||||
throw new Error()
|
||||
}
|
||||
})
|
||||
.then((data) => {
|
||||
console.log(data)
|
||||
setParticipants(data.participants)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error fetching draft details", err)
|
||||
})
|
||||
}
|
||||
fetchDraftDetails(draftSessionId)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
if (!socket) return;
|
||||
else {
|
||||
console.warn("socket doesn't exist")
|
||||
}
|
||||
console.log('socket created', socket)
|
||||
|
||||
const handleMessage = (event) => {
|
||||
const message = JSON.parse(event.data)
|
||||
const { type, payload } = message;
|
||||
console.log(type, event)
|
||||
if (!payload) return
|
||||
if (type == DraftMessage.REQUEST.JOIN_PARTICIPANT) {
|
||||
console.log('join request', data)
|
||||
}
|
||||
if (payload.phase) {
|
||||
console.log('phase_change')
|
||||
setDraftPhase(payload.phase)
|
||||
}
|
||||
if (payload.draft_order) {
|
||||
console.log('draft_order', payload.draft_order)
|
||||
setDraftOrder(payload.draft_order)
|
||||
}
|
||||
}
|
||||
|
||||
socket.addEventListener('message', handleMessage);
|
||||
|
||||
socket.onclose = (event) => {
|
||||
console.log('Websocket Closed')
|
||||
socket = null;
|
||||
}
|
||||
|
||||
return () => {
|
||||
socket.removeEventListener('message', handleMessage)
|
||||
socket.close();
|
||||
};
|
||||
}, [socket]);
|
||||
|
||||
const handlePhaseChange = (destinationPhase) => {
|
||||
socket.send(
|
||||
JSON.stringify(
|
||||
{ type: DraftMessage.REQUEST.PHASE_CHANGE, "origin": draftPhase, "destination": destinationPhase }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const handleRequestDraftSummary = () => {
|
||||
socket.send(
|
||||
JSON.stringify(
|
||||
{ type: DraftMessage.REQUEST.DRAFT_STATUS }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="container draft-panel admin">
|
||||
<h3>Draft Admin Panel</h3>
|
||||
<WebSocketStatus socket={socket} />
|
||||
{/* <MessageLogger socket={socketRef.current} /> */}
|
||||
|
||||
<ParticipantList
|
||||
socket={socket}
|
||||
participants={participants}
|
||||
draftOrder={draftOrder}
|
||||
/>
|
||||
<DraftPhaseDisplay draftPhase={draftPhase}></DraftPhaseDisplay>
|
||||
<button onClick={() => handlePhaseChange(DraftPhase.DETERMINE_ORDER)} className="btn btn-primary mt-2 me-2">
|
||||
Determine Draft Order
|
||||
</button>
|
||||
<button onClick={() => handleRequestDraftSummary()} className="btn btn-primary mt-2">
|
||||
Request status
|
||||
</button>
|
||||
<button onClick={() => handlePhaseChange(DraftPhase.NOMINATION)} className="btn btn-primary mt-2 me-2">
|
||||
Go to Nominate
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user