chore: more stuff and minor features

main
parent 44f2076fba
commit 2c84fc74b4

@ -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))
}
/>
&nbsp;
<h2>Azioni</h2>
<SubmitAction roomId={room} onTeamQuestionIndex={(key, listener) => (listeners.current[key] = listener)} />
&nbsp;
<h2>Registro Azioni</h2>
<ActionRegistry roomId={room} />
</>
)
}

@ -4,10 +4,10 @@ type Props = {
questions: Question[] questions: Question[]
scoreboard: Scoreboard 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 ( return (
<div <div
class="leaderboard" class="leaderboard"
@ -32,7 +32,14 @@ export const Leaderboard = ({ questions, scoreboard, onQuestionClick }: Props) =
</div> </div>
<div class="answers"> <div class="answers">
{questions.map((question, j) => ( {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]} {questionScores[question.id]}
</div> </div>
))} ))}

@ -6,9 +6,10 @@ import { computeScoreboardState } from '@/ggwp'
type Props = { type Props = {
roomId: string 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) const [room, setRoom] = useState<RoomData | null>(null)
useEffect(() => { useEffect(() => {
requestJSON(`/api/room/${roomId}`).then(room => { requestJSON(`/api/room/${roomId}`).then(room => {
@ -30,5 +31,5 @@ export const LiveLeaderboard = ({ roomId }: Props) => {
room.actions room.actions
) )
return <Leaderboard questions={room.questions} scoreboard={scoreboard} /> return <Leaderboard questions={room.questions} scoreboard={scoreboard} selectTeamQuestion={selectTeamQuestion} />
} }

@ -6,32 +6,71 @@ import { useEffect, useState } from 'preact/hooks'
type Outcome = 'correct' | 'partial' | 'wrong' type Outcome = 'correct' | 'partial' | 'wrong'
const handleSendAnswer = (action: Action) => { const handleSendAnswer = (action: Action) => {
// TODO: send action to server
console.log(action) 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>({ const [answer, setAnswer] = useState<ActionAnswer>({
question: '', question: '',
team: '', team: '',
outcome: 'correct', outcome: 'correct',
}) })
onTeamQuestionIndex?.('SubmitActionAnswer', ({ team, question }) => {
console.log('onTeamQuestionIndex', team, question)
setAnswer(answer => ({ ...answer, team, question }))
})
return ( return (
<> <div class="card tint-red stack-v center">
<input <h3>Invia Risposta</h3>
type="text" <select
class="fill-w" class="fill-w"
placeholder="Domanda..." placeholder="Domanda..."
value={answer.question} value={answer.question}
onInput={e => setAnswer({ ...answer, question: e.currentTarget.value })} 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" type="text"
class="fill-w" class="fill-w"
placeholder="Squadra..." placeholder="Squadra..."
value={answer.team} value={answer.team}
onInput={e => setAnswer({ ...answer, team: e.currentTarget.value })} 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 <select
class="fill-w" class="fill-w"
value={answer.outcome} value={answer.outcome}
@ -51,32 +90,75 @@ export const SubmitActionAnswer = ({ room }: { room: RoomData }) => {
> >
Invia risposta Invia risposta
</button> </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>({ const [answer, setAnswer] = useState<ActionJolly>({
team: '', team: '',
groupId: '', groupId: '',
}) })
onTeamQuestionIndex?.('SubmitActionJolly', ({ team, question }) => {
console.log('onTeamQuestionIndex', team, question)
setAnswer(answer => ({ ...answer, team, groupId: questionToGroup[question] }))
})
return ( return (
<> <div class="card tint-red stack-v center">
<input <h3>Invia Jolly</h3>
type="text"
<select
class="fill-w" class="fill-w"
placeholder="Squadra..." placeholder="Squadra..."
value={answer.team} value={answer.team}
onInput={e => setAnswer({ ...answer, team: e.currentTarget.value })} onInput={e => setAnswer({ ...answer, team: e.currentTarget.value })}
/> >
<input <option value="" disabled>
type="text" Seleziona squadra...
</option>
{room.teams.map((team, i) => (
<option value={team} key={i}>
{team}
</option>
))}
</select>
<select
class="fill-w" class="fill-w"
placeholder="Gruppo..." placeholder="Gruppo..."
value={answer.groupId} value={answer.groupId}
onInput={e => setAnswer({ ...answer, groupId: e.currentTarget.value })} onInput={e => setAnswer({ ...answer, groupId: e.currentTarget.value })}
/> >
<option value="" disabled>
Seleziona gruppo...
</option>
{groups.map(group => (
<option value={group}>{group}</option>
))}
</select>
<button <button
onClick={() => onClick={() =>
handleSendAnswer({ handleSendAnswer({
@ -87,11 +169,17 @@ export const SubmitActionJolly = ({ room }: { room: RoomData }) => {
> >
Imposta jolly Imposta jolly
</button> </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) const [room, setRoom] = useState<RoomData | null>(null)
useEffect(() => { useEffect(() => {
requestJSON(`/api/room/${roomId}`).then(room => { requestJSON(`/api/room/${roomId}`).then(room => {
@ -104,12 +192,9 @@ export const SubmitAction = ({ roomId }: { roomId: string }) => {
} }
return ( return (
<div class="card tint-red stack-v center"> <>
<h3>Risposta</h3> <SubmitActionAnswer room={room} onTeamQuestionIndex={onTeamQuestionIndex} />
<SubmitActionAnswer room={room} /> <SubmitActionJolly room={room} onTeamQuestionIndex={onTeamQuestionIndex} />
</>
<h3>Jolly</h3>
<SubmitActionJolly room={room} />
</div>
) )
} }

@ -1,9 +1,9 @@
--- ---
import Base from '@/layouts/Base.astro' import Base from '@/layouts/Base.astro'
import { LiveLeaderboard } from '@/components/LiveLeaderboard'
import { getSession } from '@/db/sessions' import { getSession } from '@/db/sessions'
import { SubmitAction } from '@/components/SubmitAction'
import { AdminPage } from '@/components/AdminPage'
const { room } = Astro.params const { room } = Astro.params
if (!room) { if (!room) {
@ -17,6 +17,9 @@ if (Astro.cookies.has('sid')) {
} }
const sessionRoom = getSession(sid.value) const sessionRoom = getSession(sid.value)
if (!sessionRoom) {
return Astro.redirect('/error?msg=' + encodeURIComponent(`Sessione non valida`))
}
if (sessionRoom !== room) { if (sessionRoom !== room) {
return Astro.redirect('/error?msg=' + encodeURIComponent(`Sei solo l'admin di "${sessionRoom}"`)) return Astro.redirect('/error?msg=' + encodeURIComponent(`Sei solo l'admin di "${sessionRoom}"`))
@ -24,15 +27,13 @@ if (Astro.cookies.has('sid')) {
} else { } else {
return Astro.redirect('/error?msg=' + encodeURIComponent(`Devi essere loggato per accedere a questa pagina`)) return Astro.redirect('/error?msg=' + encodeURIComponent(`Devi essere loggato per accedere a questa pagina`))
} }
---
<Base> type Callback<T> = (value: T) => void
<h1>Admin</h1>
<h2>{room}</h2>
<LiveLeaderboard client:load roomId={room} />
&nbsp; const listeners: Callback<{ team: string; question: string }>[] = []
console.log('listeners', listeners)
---
<h2>Azioni</h2> <Base>
<SubmitAction client:load roomId={room} /> <AdminPage client:load room={room} />
</Base> </Base>

@ -225,7 +225,7 @@ select {
outline: none; outline: none;
&:focus { &:hover {
background: oklch(from var(--tint) calc(l + 1) 0.05 h); background: oklch(from var(--tint) calc(l + 1) 0.05 h);
} }
} }

Loading…
Cancel
Save