fix(practice): use Next.js Link for student tiles + fix session observer z-index

- Replace <button> with <Link> in StudentCard for proper routing
  - Enables Cmd/Ctrl+Click to open in new tab
  - Shows URL on hover in browser status bar
  - Enables Next.js prefetching
- Close NotesModal before opening SessionObserverModal
  - Fixes z-index stacking issue where observer appeared behind quicklook

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock 2025-12-28 11:18:25 -06:00
parent 07484fdfac
commit 6def610877
2 changed files with 29 additions and 15 deletions

View File

@ -120,7 +120,13 @@ function buildStudentActionData(student: StudentProp): StudentActionData {
* - Overflow menu: All student actions (uses shared useStudentActions hook)
* - Zoom animation from source tile
*/
export function NotesModal({ isOpen, student, sourceBounds, onClose, onObserveSession }: NotesModalProps) {
export function NotesModal({
isOpen,
student,
sourceBounds,
onClose,
onObserveSession,
}: NotesModalProps) {
const router = useRouter()
const { resolvedTheme } = useTheme()
const isDark = resolvedTheme === 'dark'
@ -140,9 +146,13 @@ export function NotesModal({ isOpen, student, sourceBounds, onClose, onObserveSe
const studentActionData = buildStudentActionData(student)
const { actions, handlers, modals } = useStudentActions(studentActionData, {
// Session observer is rendered at parent level to avoid z-index issues
// Close this modal first so the observer modal appears on top
onObserveSession:
onObserveSession && student.activity?.sessionId
? () => onObserveSession(student.activity!.sessionId!)
? () => {
onClose()
onObserveSession(student.activity!.sessionId!)
}
: undefined,
})
@ -207,10 +217,12 @@ export function NotesModal({ isOpen, student, sourceBounds, onClose, onObserveSe
const handleBannerWatchSession = useCallback(() => {
// Session observer is rendered at parent level to avoid z-index issues
// Close this modal first so the observer modal appears on top
if (onObserveSession && student.activity?.sessionId) {
onClose()
onObserveSession(student.activity.sessionId)
}
}, [onObserveSession, student.activity?.sessionId])
}, [onObserveSession, student.activity?.sessionId, onClose])
// ========== Effects ==========

View File

@ -2,6 +2,7 @@
import * as Checkbox from '@radix-ui/react-checkbox'
import * as HoverCard from '@radix-ui/react-hover-card'
import Link from 'next/link'
import { useCallback, useEffect, useRef, useState } from 'react'
import { useTheme } from '@/contexts/ThemeContext'
import { Z_INDEX } from '@/constants/zIndex'
@ -159,7 +160,8 @@ export interface StudentWithProgress extends Player {
interface StudentCardProps {
student: StudentWithProgress
onSelect: (student: StudentWithProgress) => void
/** Optional callback when student is selected (Link handles navigation) */
onSelect?: (student: StudentWithProgress) => void
onToggleSelection: (student: StudentWithProgress) => void
onOpenQuickLook: (student: StudentWithProgress, bounds: DOMRect) => void
isSelected?: boolean
@ -323,11 +325,7 @@ function StudentCard({
})}
aria-label="View relationship details"
>
<RelationshipBadge
relationship={relationship}
size="sm"
showTooltip={false}
/>
<RelationshipBadge relationship={relationship} size="sm" showTooltip={false} />
</button>
</HoverCard.Trigger>
<HoverCard.Portal>
@ -406,11 +404,12 @@ function StudentCard({
</button>
)}
{/* Main clickable area */}
<button
type="button"
{/* Main clickable area - uses Next.js Link for proper routing */}
<Link
href={`/practice/${student.id}/dashboard`}
scroll={false}
data-action="select-student"
onClick={() => onSelect(student)}
onClick={() => onSelect?.(student)}
className={css({
display: 'flex',
flexDirection: 'column',
@ -422,6 +421,8 @@ function StudentCard({
padding: '0.5rem',
paddingTop: '1.5rem', // Extra space for the notes/checkbox
width: '100%',
textDecoration: 'none',
color: 'inherit',
_hover: {
'& > div:first-child': {
transform: 'scale(1.05)',
@ -618,7 +619,7 @@ function StudentCard({
<span>{student.intervention.message}</span>
</div>
)}
</button>
</Link>
{/* Enrollment action buttons (for enrollment requests) */}
{enrollmentActions && student.enrollmentRequestId && (
@ -753,7 +754,8 @@ function AddStudentButton({ onClick }: AddStudentButtonProps) {
interface StudentSelectorProps {
students: StudentWithProgress[]
onSelectStudent: (student: StudentWithProgress) => void
/** Optional callback when student is selected (Link handles navigation) */
onSelectStudent?: (student: StudentWithProgress) => void
onToggleSelection: (student: StudentWithProgress) => void
onAddStudent?: () => void
title?: string