All files / web/src/components/practice studentActions.ts

100% Statements 139/139
96% Branches 24/25
100% Functions 1/1
100% Lines 139/139

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 1401x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 14x 14x 14x 14x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x  
/**
 * Shared logic for student action menus
 *
 * Used by both StudentActionMenu (on tiles) and NotesModal (in quicklook)
 * to ensure consistent action visibility across the app.
 */
 
export interface StudentActionContext {
  /** Whether the viewer is a teacher */
  isTeacher: boolean
  /** Classroom ID if teacher has one */
  classroomId?: string
}
 
export interface StudentActionData {
  id: string
  name: string
  isArchived?: boolean
  relationship?: {
    isMyChild: boolean
    isEnrolled: boolean
    isPresent: boolean
    enrollmentStatus: string | null
  }
  activity?: {
    status: string
    sessionId?: string
  }
}
 
export interface AvailableActions {
  // Primary actions
  startPractice: boolean
  watchSession: boolean
  enterClassroom: boolean
  leaveClassroom: boolean
  removeFromClassroom: boolean
  promptToEnter: boolean
 
  // Enrollment actions
  addToMyClassroom: boolean
  enrollInClassroom: boolean
  unenrollStudent: boolean
 
  // Management actions
  shareAccess: boolean
  archive: boolean
  unarchive: boolean
}
 
/**
 * Compute which actions are available for a student
 */
export function getAvailableActions(
  student: StudentActionData,
  context: StudentActionContext,
  options: {
    hasEnrolledClassrooms?: boolean
    isEnrolledInMyClassroom?: boolean
  } = {}
): AvailableActions {
  const { relationship, activity, isArchived } = student
  const { isTeacher } = context
  const { hasEnrolledClassrooms = relationship?.isEnrolled, isEnrolledInMyClassroom } = options
 
  const isPracticing = activity?.status === 'practicing'
  const hasSessionId = !!activity?.sessionId
  const isPresent = !!relationship?.isPresent
  const isEnrolled = !!relationship?.isEnrolled
  const isMyChild = !!relationship?.isMyChild
 
  // Check if this is a pending enrollment request - disable most actions
  const isPendingEnrollment = relationship?.enrollmentStatus?.startsWith('pending') ?? false
 
  // For pending enrollment requests, only allow viewing (no actions)
  if (isPendingEnrollment) {
    return {
      startPractice: false,
      watchSession: false,
      enterClassroom: false,
      leaveClassroom: false,
      removeFromClassroom: false,
      promptToEnter: false,
      addToMyClassroom: false,
      enrollInClassroom: false,
      unenrollStudent: false,
      shareAccess: false,
      archive: false,
      unarchive: false,
    }
  }
 
  return {
    // Primary actions
    startPractice: true, // Always available for enrolled/owned students
    watchSession: isPracticing && hasSessionId,
    // Parents can enter/leave their own children (even if they're also teachers)
    enterClassroom: isMyChild && !!hasEnrolledClassrooms && !isPresent,
    leaveClassroom: isMyChild && isPresent,
    // Teachers can remove students from their classroom
    removeFromClassroom: isTeacher && isPresent,
    // Teachers can prompt parents to enter their enrolled students
    promptToEnter: isTeacher && isEnrolled && !isPresent,
 
    // Enrollment actions
    // Parent-teachers can directly add their own children to their classroom
    addToMyClassroom: isMyChild && isTeacher && !isEnrolledInMyClassroom,
    // Parents can enroll their children (even if they're also teachers)
    enrollInClassroom: isMyChild && !isEnrolled,
    unenrollStudent: isTeacher && isEnrolled,
 
    // Management actions
    shareAccess: isMyChild,
    archive: !isArchived && isMyChild,
    unarchive: !!isArchived && isMyChild,
  }
}
 
/**
 * Action definitions for consistent rendering
 */
export const ACTION_DEFINITIONS = {
  startPractice: { icon: '▶️', label: 'Start Practice' },
  watchSession: { icon: '👁', label: 'Watch Session' },
  enterClassroom: { icon: '🏫', label: 'Enter Classroom' },
  leaveClassroom: { icon: '🚪', label: 'Leave Classroom' },
  removeFromClassroom: { icon: '🚪', label: 'Remove from Classroom' },
  promptToEnter: { icon: '📣', label: 'Prompt to Enter' },
  addToMyClassroom: { icon: '🏫', label: 'Add to My Class' },
  enrollInClassroom: { icon: '➕', label: 'Enroll in Classroom' },
  unenrollStudent: {
    icon: '📋',
    label: 'Unenroll Student',
    variant: 'danger' as const,
  },
  shareAccess: { icon: '🔗', label: 'Share Access' },
  archive: { icon: '📦', label: 'Archive' },
  unarchive: { icon: '📤', label: 'Unarchive' },
} as const