feat(classes): per-session signups roster on /classes/manage (staff)
Reuses the existing SessionAttendance component (listSessionSignups, classes.manage/instruct-gated) behind a per-session 'Signups' toggle on the manage page, so managers can see who signed up + status + mark attendance + complete without leaving the manage screen. Members still see only spots-left (privacy posture unchanged).
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
|||||||
deleteSession,
|
deleteSession,
|
||||||
} from '@/lib/actions'
|
} from '@/lib/actions'
|
||||||
import { SESSION_STATUS_LABEL } from '@/lib/classes-logic'
|
import { SESSION_STATUS_LABEL } from '@/lib/classes-logic'
|
||||||
|
import { SessionAttendance } from '@/components/classes/session-attendance'
|
||||||
|
|
||||||
type Cls = {
|
type Cls = {
|
||||||
id: string
|
id: string
|
||||||
@@ -77,6 +78,7 @@ export function ClassesManageClient({
|
|||||||
const [editDraft, setEditDraft] = useState(emptyClassDraft())
|
const [editDraft, setEditDraft] = useState(emptyClassDraft())
|
||||||
const [sessForClass, setSessForClass] = useState<string | null>(null)
|
const [sessForClass, setSessForClass] = useState<string | null>(null)
|
||||||
const [sessDraft, setSessDraft] = useState(emptySessionDraft())
|
const [sessDraft, setSessDraft] = useState(emptySessionDraft())
|
||||||
|
const [rosterFor, setRosterFor] = useState<string | null>(null)
|
||||||
const [busy, setBusy] = useState(false)
|
const [busy, setBusy] = useState(false)
|
||||||
|
|
||||||
function certName(id: string | null) {
|
function certName(id: string | null) {
|
||||||
@@ -395,7 +397,8 @@ export function ClassesManageClient({
|
|||||||
) : (
|
) : (
|
||||||
<ul className="divide-y divide-border">
|
<ul className="divide-y divide-border">
|
||||||
{cs.map(s => (
|
{cs.map(s => (
|
||||||
<li key={s.id} className="py-2 flex items-center justify-between gap-3 flex-wrap">
|
<li key={s.id} className="py-2">
|
||||||
|
<div className="flex items-center justify-between gap-3 flex-wrap">
|
||||||
<div className="font-mono text-[11px] text-muted-foreground">
|
<div className="font-mono text-[11px] text-muted-foreground">
|
||||||
{new Date(s.starts_at).toLocaleString()}
|
{new Date(s.starts_at).toLocaleString()}
|
||||||
{s.location ? ` · ${s.location}` : ''}
|
{s.location ? ` · ${s.location}` : ''}
|
||||||
@@ -406,11 +409,26 @@ export function ClassesManageClient({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
|
<Button size="sm" variant="outline"
|
||||||
|
onClick={() => setRosterFor(rosterFor === s.id ? null : s.id)}>
|
||||||
|
{rosterFor === s.id ? 'Hide signups' : 'Signups'}
|
||||||
|
</Button>
|
||||||
{s.status === 'scheduled' && (
|
{s.status === 'scheduled' && (
|
||||||
<Button size="sm" variant="outline" onClick={() => onCancelSession(s)}>Cancel</Button>
|
<Button size="sm" variant="outline" onClick={() => onCancelSession(s)}>Cancel</Button>
|
||||||
)}
|
)}
|
||||||
<Button size="sm" variant="outline" onClick={() => onDeleteSession(s)}>Delete</Button>
|
<Button size="sm" variant="outline" onClick={() => onDeleteSession(s)}>Delete</Button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{rosterFor === s.id && (
|
||||||
|
<SessionAttendance
|
||||||
|
sessionId={s.id}
|
||||||
|
sessionStatus={s.status}
|
||||||
|
onCompleted={() => {
|
||||||
|
setSessions(prev => prev.map(x => (x.id === s.id ? { ...x, status: 'completed' } : x)))
|
||||||
|
setRosterFor(null)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
Reference in New Issue
Block a user