chore: more stuff and minor features
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
import { requestJSON } from '@/client/utils'
|
||||
import type { RoomData } from '@/db/model'
|
||||
import type { ActionAnswer, ActionJolly } from '@/ggwp'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
|
||||
type Props = {
|
||||
roomId: string
|
||||
}
|
||||
|
||||
const ActionCardAnswer = ({ action }: { action: ActionAnswer }) => {
|
||||
return (
|
||||
<div class="card tint-blue stack-h center">
|
||||
<strong>{action.question}</strong>
|
||||
<div>{action.team}</div>
|
||||
<div>{action.outcome}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ActionCardJolly = ({ action }: { action: ActionJolly }) => {
|
||||
return (
|
||||
<div class="card tint-blue stack-h center">
|
||||
<strong>{action.team}</strong>
|
||||
<div>{action.groupId}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const ActionRegistry = ({ roomId }: Props) => {
|
||||
const [room, setRoom] = useState<RoomData | null>(null)
|
||||
useEffect(() => {
|
||||
requestJSON(`/api/room/${roomId}`).then(room => {
|
||||
setRoom(room)
|
||||
})
|
||||
}, [])
|
||||
|
||||
if (!room) {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
if (room.actions.length === 0) {
|
||||
return <p>Ancora nessuna azione</p>
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{room.actions.map(action =>
|
||||
action.type === 'answer' ? (
|
||||
<ActionCardAnswer action={action} />
|
||||
) : action.type === 'jolly' ? (
|
||||
<ActionCardJolly action={action} />
|
||||
) : (
|
||||
<div class="card tint-red">
|
||||
Unknown action type: <code>{JSON.stringify(action)}</code>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { LiveLeaderboard } from '@/components/LiveLeaderboard'
|
||||
import { SubmitAction } from '@/components/SubmitAction'
|
||||
import { ActionRegistry } from '@/components/ActionRegistry'
|
||||
import { useRef, useState } from 'preact/hooks'
|
||||
|
||||
export const AdminPage = ({ room }: { room: string }) => {
|
||||
const listeners = useRef<Record<string, (teamQuestion: { team: string; question: string }) => void>>({})
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Admin</h1>
|
||||
<h2>{room}</h2>
|
||||
<LiveLeaderboard
|
||||
roomId={room}
|
||||
selectTeamQuestion={teamQuestion =>
|
||||
Object.values(listeners.current).forEach(listener => listener(teamQuestion))
|
||||
}
|
||||
/>
|
||||
|
||||
<h2>Azioni</h2>
|
||||
<SubmitAction roomId={room} onTeamQuestionIndex={(key, listener) => (listeners.current[key] = listener)} />
|
||||
|
||||
<h2>Registro Azioni</h2>
|
||||
<ActionRegistry roomId={room} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -4,10 +4,10 @@ type Props = {
|
||||
questions: Question[]
|
||||
scoreboard: Scoreboard
|
||||
|
||||
onQuestionClick?: (team: string, questionId: string) => void
|
||||
selectTeamQuestion?: ({ team, question }: { team: string; question: string }) => void
|
||||
}
|
||||
|
||||
export const Leaderboard = ({ questions, scoreboard, onQuestionClick }: Props) => {
|
||||
export const Leaderboard = ({ questions, scoreboard, selectTeamQuestion }: Props) => {
|
||||
return (
|
||||
<div
|
||||
class="leaderboard"
|
||||
@@ -32,7 +32,14 @@ export const Leaderboard = ({ questions, scoreboard, onQuestionClick }: Props) =
|
||||
</div>
|
||||
<div class="answers">
|
||||
{questions.map((question, j) => (
|
||||
<div class="answer" key={j} onClick={() => onQuestionClick?.(team, question.id)}>
|
||||
<div
|
||||
class="answer"
|
||||
key={j}
|
||||
onClick={() => {
|
||||
console.log('emitting', { team, question: question.id })
|
||||
return selectTeamQuestion?.({ team, question: question.id })
|
||||
}}
|
||||
>
|
||||
{questionScores[question.id]}
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -6,9 +6,10 @@ import { computeScoreboardState } from '@/ggwp'
|
||||
|
||||
type Props = {
|
||||
roomId: string
|
||||
selectTeamQuestion?: ({ team, question }: { team: string; question: string }) => void
|
||||
}
|
||||
|
||||
export const LiveLeaderboard = ({ roomId }: Props) => {
|
||||
export const LiveLeaderboard = ({ roomId, selectTeamQuestion }: Props) => {
|
||||
const [room, setRoom] = useState<RoomData | null>(null)
|
||||
useEffect(() => {
|
||||
requestJSON(`/api/room/${roomId}`).then(room => {
|
||||
@@ -30,5 +31,5 @@ export const LiveLeaderboard = ({ roomId }: Props) => {
|
||||
room.actions
|
||||
)
|
||||
|
||||
return <Leaderboard questions={room.questions} scoreboard={scoreboard} />
|
||||
return <Leaderboard questions={room.questions} scoreboard={scoreboard} selectTeamQuestion={selectTeamQuestion} />
|
||||
}
|
||||
|
||||
+110
-25
@@ -6,32 +6,71 @@ import { useEffect, useState } from 'preact/hooks'
|
||||
type Outcome = 'correct' | 'partial' | 'wrong'
|
||||
|
||||
const handleSendAnswer = (action: Action) => {
|
||||
// TODO: send action to server
|
||||
console.log(action)
|
||||
}
|
||||
|
||||
export const SubmitActionAnswer = ({ room }: { room: RoomData }) => {
|
||||
export const SubmitActionAnswer = ({
|
||||
room,
|
||||
onTeamQuestionIndex,
|
||||
}: {
|
||||
room: RoomData
|
||||
onTeamQuestionIndex?: Receiver<{ team: string; question: string }>
|
||||
}) => {
|
||||
const [answer, setAnswer] = useState<ActionAnswer>({
|
||||
question: '',
|
||||
team: '',
|
||||
outcome: 'correct',
|
||||
})
|
||||
|
||||
onTeamQuestionIndex?.('SubmitActionAnswer', ({ team, question }) => {
|
||||
console.log('onTeamQuestionIndex', team, question)
|
||||
setAnswer(answer => ({ ...answer, team, question }))
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
type="text"
|
||||
<div class="card tint-red stack-v center">
|
||||
<h3>Invia Risposta</h3>
|
||||
<select
|
||||
class="fill-w"
|
||||
placeholder="Domanda..."
|
||||
value={answer.question}
|
||||
onInput={e => setAnswer({ ...answer, question: e.currentTarget.value })}
|
||||
/>
|
||||
<input
|
||||
>
|
||||
<option value="" disabled>
|
||||
Seleziona domanda...
|
||||
</option>
|
||||
{room.questions.map((question, i) => (
|
||||
<option value={question.id} key={i}>
|
||||
{question.id}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
{/* <input
|
||||
type="text"
|
||||
class="fill-w"
|
||||
placeholder="Squadra..."
|
||||
value={answer.team}
|
||||
onInput={e => setAnswer({ ...answer, team: e.currentTarget.value })}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
<select
|
||||
class="fill-w"
|
||||
placeholder="Squadra..."
|
||||
value={answer.team}
|
||||
onInput={e => setAnswer({ ...answer, team: e.currentTarget.value })}
|
||||
>
|
||||
<option value="" disabled>
|
||||
Seleziona squadra...
|
||||
</option>
|
||||
{room.teams.map((team, i) => (
|
||||
<option value={team} key={i}>
|
||||
{team}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<select
|
||||
class="fill-w"
|
||||
value={answer.outcome}
|
||||
@@ -51,32 +90,75 @@ export const SubmitActionAnswer = ({ room }: { room: RoomData }) => {
|
||||
>
|
||||
Invia risposta
|
||||
</button>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const SubmitActionJolly = ({ room }: { room: RoomData }) => {
|
||||
type Receiver<T> = (key: string, cb: (data: T) => void) => void
|
||||
|
||||
export const SubmitActionJolly = ({
|
||||
room,
|
||||
onTeamQuestionIndex,
|
||||
}: {
|
||||
room: RoomData
|
||||
onTeamQuestionIndex?: Receiver<{ team: string; question: string }>
|
||||
}) => {
|
||||
const groupsMap: Record<string, boolean> = {}
|
||||
room.questions.forEach(question => {
|
||||
groupsMap[question.group] = true
|
||||
})
|
||||
|
||||
const groups = Object.keys(groupsMap)
|
||||
|
||||
const questionToGroup: Record<string, string> = {}
|
||||
room.questions.forEach(question => {
|
||||
questionToGroup[question.id] = question.group
|
||||
})
|
||||
|
||||
const [answer, setAnswer] = useState<ActionJolly>({
|
||||
team: '',
|
||||
groupId: '',
|
||||
})
|
||||
|
||||
onTeamQuestionIndex?.('SubmitActionJolly', ({ team, question }) => {
|
||||
console.log('onTeamQuestionIndex', team, question)
|
||||
setAnswer(answer => ({ ...answer, team, groupId: questionToGroup[question] }))
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
type="text"
|
||||
<div class="card tint-red stack-v center">
|
||||
<h3>Invia Jolly</h3>
|
||||
|
||||
<select
|
||||
class="fill-w"
|
||||
placeholder="Squadra..."
|
||||
value={answer.team}
|
||||
onInput={e => setAnswer({ ...answer, team: e.currentTarget.value })}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
>
|
||||
<option value="" disabled>
|
||||
Seleziona squadra...
|
||||
</option>
|
||||
{room.teams.map((team, i) => (
|
||||
<option value={team} key={i}>
|
||||
{team}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<select
|
||||
class="fill-w"
|
||||
placeholder="Gruppo..."
|
||||
value={answer.groupId}
|
||||
onInput={e => setAnswer({ ...answer, groupId: e.currentTarget.value })}
|
||||
/>
|
||||
>
|
||||
<option value="" disabled>
|
||||
Seleziona gruppo...
|
||||
</option>
|
||||
{groups.map(group => (
|
||||
<option value={group}>{group}</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<button
|
||||
onClick={() =>
|
||||
handleSendAnswer({
|
||||
@@ -87,11 +169,17 @@ export const SubmitActionJolly = ({ room }: { room: RoomData }) => {
|
||||
>
|
||||
Imposta jolly
|
||||
</button>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const SubmitAction = ({ roomId }: { roomId: string }) => {
|
||||
export const SubmitAction = ({
|
||||
roomId,
|
||||
onTeamQuestionIndex,
|
||||
}: {
|
||||
roomId: string
|
||||
onTeamQuestionIndex?: Receiver<{ team: string; question: string }>
|
||||
}) => {
|
||||
const [room, setRoom] = useState<RoomData | null>(null)
|
||||
useEffect(() => {
|
||||
requestJSON(`/api/room/${roomId}`).then(room => {
|
||||
@@ -104,12 +192,9 @@ export const SubmitAction = ({ roomId }: { roomId: string }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="card tint-red stack-v center">
|
||||
<h3>Risposta</h3>
|
||||
<SubmitActionAnswer room={room} />
|
||||
|
||||
<h3>Jolly</h3>
|
||||
<SubmitActionJolly room={room} />
|
||||
</div>
|
||||
<>
|
||||
<SubmitActionAnswer room={room} onTeamQuestionIndex={onTeamQuestionIndex} />
|
||||
<SubmitActionJolly room={room} onTeamQuestionIndex={onTeamQuestionIndex} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
import Base from '@/layouts/Base.astro'
|
||||
import { LiveLeaderboard } from '@/components/LiveLeaderboard'
|
||||
|
||||
import { getSession } from '@/db/sessions'
|
||||
import { SubmitAction } from '@/components/SubmitAction'
|
||||
|
||||
import { AdminPage } from '@/components/AdminPage'
|
||||
|
||||
const { room } = Astro.params
|
||||
if (!room) {
|
||||
@@ -17,6 +17,9 @@ if (Astro.cookies.has('sid')) {
|
||||
}
|
||||
|
||||
const sessionRoom = getSession(sid.value)
|
||||
if (!sessionRoom) {
|
||||
return Astro.redirect('/error?msg=' + encodeURIComponent(`Sessione non valida`))
|
||||
}
|
||||
|
||||
if (sessionRoom !== room) {
|
||||
return Astro.redirect('/error?msg=' + encodeURIComponent(`Sei solo l'admin di "${sessionRoom}"`))
|
||||
@@ -24,15 +27,13 @@ if (Astro.cookies.has('sid')) {
|
||||
} else {
|
||||
return Astro.redirect('/error?msg=' + encodeURIComponent(`Devi essere loggato per accedere a questa pagina`))
|
||||
}
|
||||
|
||||
type Callback<T> = (value: T) => void
|
||||
|
||||
const listeners: Callback<{ team: string; question: string }>[] = []
|
||||
console.log('listeners', listeners)
|
||||
---
|
||||
|
||||
<Base>
|
||||
<h1>Admin</h1>
|
||||
<h2>{room}</h2>
|
||||
<LiveLeaderboard client:load roomId={room} />
|
||||
|
||||
|
||||
|
||||
<h2>Azioni</h2>
|
||||
<SubmitAction client:load roomId={room} />
|
||||
<AdminPage client:load room={room} />
|
||||
</Base>
|
||||
|
||||
+1
-1
@@ -225,7 +225,7 @@ select {
|
||||
|
||||
outline: none;
|
||||
|
||||
&:focus {
|
||||
&:hover {
|
||||
background: oklch(from var(--tint) calc(l + 1) 0.05 h);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user