1import { r as reactExports, j as jsxRuntimeExports, aK as RefreshCw, L as LoaderCircle, _ as Star, ba as toast, aH as format, bb as MessageSquare, a as ChevronDown, aJ as ChevronRight, bc as ChartColumn, ak as Users, ah as Search, X, aB as BookOpen, bd as ShieldCheck, be as Copy, b9 as ExternalLink, E as useNavigate, $ as Navigate, Q as Helmet, V as motion, bf as PenLine, aI as ChevronLeft, W as Monitor, bg as Building2, bh as CheckCheck, b8 as Key, bi as Printer, ai as Calendar, aD as MapPin, C as CircleUser, aL as Phone, aM as Mail, J as FileText, av as Video } from "./vendor-Visk9v06.js"; 2import { B as Button, i as cn, I as Input, f as Badge, u as useSession, g as getUserWithRole, C as Card, a as CardHeader, b as CardTitle, c as CardDescription, S as Select, j as SelectTrigger, k as SelectValue, l as SelectContent, n as SelectItem, d as CardContent, D as Dialog, o as DialogContent, p as DialogHeader, q as DialogTitle, r as DialogDescription, h as formatSessionDate } from "./index-CXY7xJdM.js"; 3import { T as Tabs, a as TabsList, b as TabsTrigger, c as TabsContent } from "./tabs-Cr7Q0jtH.js"; 4import { S as Separator, C as CoursewareSection } from "./separator-D9zpTTcM.js"; 5import { P as ProfileMenu } from "./ProfileMenu-BH0_dJt6.js"; 6import "./preload-DNrUkv91.js"; 7const SURVEY_CATEGORIES = [{ 8 title: "Overall Experience", 9 questions: [{ 10 key: "q1", 11 label: "Overall Satisfaction" 12 }, { 13 key: "q2", 14 label: "Class Length Appropriate" 15 }, { 16 key: "q3", 17 label: "Setup Organized" 18 }, { 19 key: "q4", 20 label: "Will Apply Learning" 21 }] 22}, { 23 title: "Instructor", 24 questions: [{ 25 key: "q5", 26 label: "Knowledgeable" 27 }, { 28 key: "q6", 29 label: "Answered Questions Clearly" 30 }, { 31 key: "q7", 32 label: "Explained Concepts Clearly" 33 }, { 34 key: "q8", 35 label: "Real-World Examples" 36 }, { 37 key: "q9", 38 label: "Time Management" 39 }] 40}, { 41 title: "Course Materials", 42 questions: [{ 43 key: "q10", 44 label: "Free of Errors" 45 }, { 46 key: "q11", 47 label: "Exercises Easy to Follow" 48 }] 49}, { 50 title: "Technology", 51 questions: [{ 52 key: "q12", 53 label: "Platform Easy to Use" 54 }, { 55 key: "q13", 56 label: "No Connectivity Issues" 57 }, { 58 key: "q14", 59 label: "Audio/Video Quality" 60 }] 61}]; 62const ALL_Q_KEYS = ["q1", "q2", "q3", "q4", "q5", "q6", "q7", "q8", "q9", "q10", "q11", "q12", "q13", "q14"]; 63function avg(vals) { 64 const nums = vals.filter((v) => v !== null); 65 if (nums.length === 0) return null; 66 return nums.reduce((a, b) => a + b, 0) / nums.length; 67} 68function ScoreBar({ 69 value, 70 max = 5 71}) { 72 if (value === null) return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground text-xs", children: "â" }); 73 const pct = value / max * 100; 74 const color = value >= 4.5 ? "bg-green-500" : value >= 3.5 ? "bg-blue-500" : value >= 2.5 ? "bg-amber-500" : "bg-red-500"; 75 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 flex-1", children: [ 76 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 bg-muted rounded-full h-2 overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: cn("h-full rounded-full transition-all", color), style: { 77 width: `${pct}%` 78 } }) }), 79 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-semibold text-foreground w-8 text-right", children: value.toFixed(1) }) 80 ] }); 81} 82function StudentResponseRow({ 83 survey 84}
84) { 85 var _a, _b; 86 const [expanded, setExpanded] = reactExports.useState(false); 87 const studentAvg = avg(ALL_Q_KEYS.map((k) => survey[k])); 88 const submittedDate = survey.submittedAt ? format(new Date(survey.submittedAt), "MMM d") : null; 89 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-b border-border last:border-0", children: [ 90 /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", onClick: () => setExpanded((v) => !v), className: "w-full flex items-center gap-3 px-5 py-2.5 text-left hover:bg-muted/20 transition-colors", children: [ 91 expanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }), 92 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-sm text-muted-foreground flex-1", children: [ 93 "Response ", 94 submittedDate ? `· ${submittedDate}` : "" 95 ] }),
96 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [ 97 studentAvg !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: cn("inline-flex items-center gap-0.5 px-2 py-0.5 rounded-full text-xs font-semibold", studentAvg >= 4.5 ? "bg-green-100 text-green-800" : studentAvg >= 3.5 ? "bg-blue-100 text-blue-800" : studentAvg >= 2.5 ? "bg-amber-100 text-amber-800" : "bg-red-100 text-red-800"), children: [ 98 /* @__PURE__ */ jsxRuntimeExports.jsx(Star, { className: "h-3 w-3" }), 99 studentAvg.toFixed(1) 100 ] }), 101 ((_a = survey.comments) == null ? void 0 : _a.trim()) && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { title: "Has comment", children: /* @__PURE__ */ jsxRuntimeExports.jsx(MessageSquare, { className: "h-3.5 w-3.5 text-blue-500 shrink-0" }) }) 102 ] }) 103 ] }), 104 expanded && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-5 pb-3 space-y-3", children: [ 105 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-3", children: SURVEY_CATEGORIES.map((cat) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
106 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] font-semibold text-muted-foreground uppercase tracking-wide mb-1.5", children: cat.title }), 107 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-2 gap-1.5", children: cat.questions.map(({ 108 key, 109 label 110 }) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cn("flex items-center justify-between gap-1.5 rounded-md px-2.5 py-1.5 text-xs", (survey[key] ?? 0) >= 4 ? "bg-green-50 text-green-800" : (survey[key] ?? 0) >= 3 ? "bg-amber-50 text-amber-800" : survey[key] !== null ? "bg-red-50 text-red-800" : "bg-muted/40 text-muted-foreground"), children: [ 111 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: label }), 112 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold shrink-0", children: survey[key] ?? "â" }) 113 ] }, key)) }) 114 ] }, cat.title)) }), 115 ((_b = survey.comments) == null ? void 0 : _b.trim()) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2 bg-blue-50 border border-blue-200 rounded-lg px-3 py-2.5", children: [
116 /* @__PURE__ */ jsxRuntimeExports.jsx(MessageSquare, { className: "h-3.5 w-3.5 text-blue-500 mt-0.5 shrink-0" }), 117 /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-blue-900 italic leading-relaxed", children: [ 118 '"', 119 survey.comments, 120 '"' 121 ] }) 122 ] }) 123 ] }) 124 ] }); 125} 126function SessionCard({ 127 group 128}) { 129 const [expanded, setExpanded] = reactExports.useState(false); 130 const avgScores = Object.fromEntries(ALL_Q_KEYS.map((k) => [k, avg(group.surveys.map((s) => s[k]))])); 131 const overallAvg = avg(Object.values(avgScores)); 132 const sessionDate = group.sessionStartDate ? format(new Date(group.sessionStartDate), "MMM d, yyyy") : "â"; 133 const commentsWithText = group.surveys.filter((s) => { 134 var _a; 135 return (_a = s.comments) == null ? void 0 : _a.trim(); 136 }); 137 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-xl border border-border bg-card overflow-hidden", children: [ 138 /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", onClick: () => setExpanded((v) => !v), className: "w-full flex items-center gap-4 px-5 py-4 hover:bg-muted/30 transition-colors text-left", children: [
139 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [ 140 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [ 141 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-semibold text-foreground text-sm", children: group.courseTitle }), 142 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground text-xs", children: sessionDate }) 143 ] }), 144 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 mt-1", children: [ 145 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs text-muted-foreground", children: [ 146 group.surveys.length, 147 " response", 148 group.surveys.length !== 1 ? "s" : "" 149 ] }), 150 overallAvg !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1 text-xs font-semibold text-primary", children: [ 151 /* @__P
151URE__ */ jsxRuntimeExports.jsx(Star, { className: "h-3 w-3" }), 152 overallAvg.toFixed(1), 153 " / 5.0 overall" 154 ] }), 155 commentsWithText.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1 text-xs font-medium text-blue-700 bg-blue-50 border border-blue-200 rounded-full px-2 py-0.5", children: [ 156 /* @__PURE__ */ jsxRuntimeExports.jsx(MessageSquare, { className: "h-3 w-3" }), 157 commentsWithText.length, 158 " comment", 159 commentsWithText.length !== 1 ? "s" : "" 160 ] }) 161 ] }) 162 ] }), 163 expanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "h-4 w-4 text-muted-foreground shrink-0" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "h-4 w-4 text-muted-foreground shrink-0" }) 164 ] }), 165 expanded && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-border", children: [ 166 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-5 py-5 bg-muted/20 border-b border-border", children: [
167 /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs font-semibold text-muted-foreground uppercase tracking-wide mb-4 flex items-center gap-1.5", children: [ 168 /* @__PURE__ */ jsxRuntimeExports.jsx(ChartColumn, { className: "h-3.5 w-3.5" }), 169 "Score Breakdown" 170 ] }), 171 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-5", children: SURVEY_CATEGORIES.map((cat) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 172 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] font-semibold text-muted-foreground uppercase tracking-wide mb-2", children: cat.title }), 173 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-2", children: cat.questions.map(({ 174 key, 175 label 176 }) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [ 177 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm text-foreground w-48 shrink-0", children: label }),
178 /* @__PURE__ */ jsxRuntimeExports.jsx(ScoreBar, { value: avgScores[key] }) 179 ] }, key)) }) 180 ] }, cat.title)) }) 181 ] }), 182 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-border", children: [ 183 /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs font-semibold text-muted-foreground uppercase tracking-wide px-5 pt-3 pb-1 flex items-center gap-1.5", children: [ 184 /* @__PURE__ */ jsxRuntimeExports.jsx(Users, { className: "h-3.5 w-3.5" }), 185 "Individual Responses (", 186 group.surveys.length, 187 ")" 188 ] }), 189 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "divide-y divide-border", children: group.surveys.map((s) => /* @__PURE__ */ jsxRuntimeExports.jsx(StudentResponseRow, { survey: s }, s.id)) }) 190 ] }) 191 ] }) 192 ] }); 193} 194function InstructorSurveysTab() { 195 const [surveys, setSurveys] = reactExports.useState([]); 196 const [loading, setLoading] = reactExports.useState(true); 197 async function loadSurveys() { 198 setLoading(true); 199 try { 200 const res = await fetch("/api/instructor/surveys"); 201 const data = await res.json(); 202 setSurveys(Array.isArray(data) ? data : []); 203 } catch { 204 toast.error("Failed to load surveys"); 205 } finally { 206 setLoading(false); 207 } 208 } 209 reactExports.useEffect(() => { 210 loadSurveys(); 211 }, []); 212 const sessionGroups = reactExports.useMemo(() => { 213 const map = /* @__PURE__ */ new Map(); 214 for (const s of surveys) { 215 if (!map.has(s.sessionId)) { 216 map.set(s.sessionId, { 217 sessionId: s.sessionId, 218 courseTitle: s.courseTitle, 219 sessionStartDate: s.sessionStartDate, 220 surveys: [] 221 }); 222 } 223 map.get(s.sessionId).surveys.push(s); 224 } 225 return Array.from(map.values()).sort((a, b) => new Date(b.sessionStartDate).getTime() - new Date(a.sessionStartDate).getTime()); 226 }, [surveys]); 227 const overallAvg = avg(surveys.flatMap((s) => [s.q1, s.q2, s.q3, s.q4, s.q5, s.q6])); 228 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [ 229 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between flex-wrap gap-3", children: [
230 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 231 /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-xl font-bold text-foreground", children: "My Survey Results" }), 232 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-muted-foreground mt-0.5", children: "Anonymised feedback from students in your sessions" }) 233 ] }), 234 /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { variant: "outline", size: "sm", onClick: loadSurveys, disabled: loading, className: "gap-1.5", children: [ 235 /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: cn("h-3.5 w-3.5", loading && "animate-spin") }), 236 "Refresh" 237 ] }) 238 ] }), 239 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-3 gap-4", children: [ 240 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-xl border border-border bg-card p-4 text-center", children: [ 241 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-2xl font-bold text-foreground", children: sessionGroups.length }),
242 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: "Sessions with Feedback" }) 243 ] }), 244 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-xl border border-border bg-card p-4 text-center", children: [ 245 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-2xl font-bold text-foreground", children: surveys.length }), 246 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: "Total Responses" }) 247 ] }), 248 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-xl border border-border bg-card p-4 text-center", children: overallAvg !== null ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 249 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-2xl font-bold text-primary", children: overallAvg.toFixed(1) }), 250 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: "Overall Avg / 5.0" }) 251 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
252 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-2xl font-bold text-muted-foreground", children: "â" }), 253 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: "Overall Avg / 5.0" }) 254 ] }) }) 255 ] }), 256 loading ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "py-16 text-center", children: [ 257 /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "h-8 w-8 animate-spin mx-auto text-muted-foreground" }), 258 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-muted-foreground mt-3 text-sm", children: "Loading feedbackâ¦" }) 259 ] }) : sessionGroups.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "py-16 text-center rounded-xl border border-dashed border-border", children: [ 260 /* @__PURE__ */ jsxRuntimeExports.jsx(Star, { className: "h-10 w-10 text-muted-foreground mx-auto mb-3 opacity-40" }), 261 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-muted-foreground font-medium", children: "No feedback yet" }),
262 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Survey results will appear here after students complete their post-class feedback." }) 263 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-3", children: sessionGroups.map((group) => /* @__PURE__ */ jsxRuntimeExports.jsx(SessionCard, { group }, group.sessionId)) }) 264 ] }); 265} 266function InstructorManualsTab() { 267 const [manuals, setManuals] = reactExports.useState([]); 268 const [loading, setLoading] = reactExports.useState(true); 269 const [search, setSearch] = reactExports.useState(""); 270 reactExports.useEffect(() => { 271 fetch("/api/admin/manuals").then((r) => r.json()).then((data) => setManuals(Array.isArray(data) ? data : [])).catch(() => toast.error("Failed to load manuals")).finally(() => setLoading(false)); 272 }, []); 273 const filtered = manuals.filter((m) => !search.trim() || m.courseTitle.toLowerCase().includes(search.toLowerCase()) || m.label.toLowerCase().includes(search.toLowerCase()) || m.originalName.toLowerCase().includes(search.toLowerCase())); 274 const copyToClipboard = (text, label) => { 275 navigator.clipboard.writeText(text).then(() => toast.success(`${label} copied`)); 276 }; 277 const formatSize = (bytes) => { 278 if (!bytes) return "â"; 279 if (bytes >= 1048576) return `${(bytes / 1048576).toFixed(1)} MB`; 280 return `${(bytes / 1024).toFixed(0)} KB`; 281 }; 282 const fullUrl = (fileUrl) => `${window.location.origin}${fileUrl}`; 283 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [ 284 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", children: [
285 /* @__PURE__ */ jsxRuntimeExports.jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }), 286 /* @__PURE__ */ jsxRuntimeExports.jsx(Input, { className: "pl-9 pr-8", placeholder: "Search by course or manual nameâ¦", value: search, onChange: (e) => setSearch(e.target.value) }), 287 search && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground", onClick: () => setSearch(""), children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "h-4 w-4" }) }) 288 ] }), 289 loading ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "py-12 text-center text-muted-foreground text-sm", children: "Loading manualsâ¦" }) : filtered.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "py-12 text-center text-muted-foreground text-sm", children: search ? "No manuals match your search." : "No course manuals have been uploaded yet." }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-3", children: filtered.map((m) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-lg border bg-card p-4 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between", children: [
290 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-3 min-w-0", children: [ 291 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-0.5 shrink-0 rounded-md bg-primary/10 p-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(BookOpen, { className: "h-4 w-4 text-primary" }) }), 292 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [ 293 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [ 294 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium text-sm truncate", children: m.courseTitle }), 295 /* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { variant: "secondary", className: "text-xs shrink-0", children: [ 296 "v", 297 m.version 298 ] }) 299 ] }), 300 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground mt-0.5 truncate", children: m.label }), 301 /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-muted-foreground truncate", children: [ 302 m.originalName, 303 " · ", 304 formatSize(m.fileSize) 305 ] }),
306 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 flex items-center gap-1.5", children: [ 307 /* @__PURE__ */ jsxRuntimeExports.jsx(ShieldCheck, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }), 308 /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-xs font-mono bg-muted px-1.5 py-0.5 rounded select-all", children: m.accessCode }), 309 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "text-muted-foreground hover:text-foreground transition-colors", title: "Copy access code", onClick: () => copyToClipboard(m.accessCode, "Access code"), children: /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "h-3.5 w-3.5" }) }) 310 ] }) 311 ] }) 312 ] }), 313 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [ 314 /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "hidden lg:block text-xs font-mono text-muted-foreground bg-muted px-2 py-1 rounded max-w-[220px] truncate", children: m.fileUrl }),
315 /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { variant: "outline", size: "sm", className: "gap-1.5 text-xs", onClick: () => window.open(fullUrl(m.fileUrl), "_blank"), children: [ 316 /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "h-3.5 w-3.5" }), 317 "Open" 318 ] }), 319 /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { variant: "ghost", size: "sm", className: "gap-1.5 text-xs", onClick: () => copyToClipboard(fullUrl(m.fileUrl), "URL"), children: [ 320 /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "h-3.5 w-3.5" }), 321 "Copy URL" 322 ] }) 323 ] }) 324 ] }, m.id)) }) 325 ] }); 326} 327function parseLocalDate(dateStr) { 328 if (!dateStr) return /* @__PURE__ */ new Date(); 329 if (dateStr.endsWith("Z") || /[+-]\d{2}:\d{2}$/.test(dateStr)) return new Date(dateStr); 330 return new Date(dateStr.replace(" ", "T")); 331} 332function formatTime(t) { 333 if (!t) return "â"; 334 if (t.includes("AM") || t.includes("PM")) return t.trim(); 335 const [h, m] = t.split(":"); 336 const hour = parseInt(h); 337 const ampm = hour >= 12 ? "PM" : "AM"; 338 const display = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour; 339 return `${display}:${m} ${ampm}`; 340} 341function printSignInSheet(sessionData, students) { 342 const sorted = [...students].sort((a, b) => { 343 var _a, _b; 344 const nameA = (a.enrollment.studentName || ((_a = a.student) == null ? void 0 : _a.name) || "").toLowerCase(); 345 const nameB = (b.enrollment.studentName || ((_b = b.student) == null ? void 0 : _b.name) || "").toLowerCase(); 346 return nameA.localeCompare(nameB); 347 }); 348 students = sorted; 349 const dateStr = formatSessionDate(sessionData.session.startDate, { 350 weekday: "long", 351 year: "numeric", 352 month: "long", 353 day: "numeric" 354 }); 355 const timeStr = `${formatTime(sessionData.session.startTime)} â ${formatTime(sessionData.session.endTime)}${sessionData.session.timezone ? " " + sessionData.session.timezone : ""}`; 356 const isOnline = sessionData.session.deliveryMode === "online"; 357 const location = isOnline ? "Live Online (Virtual)" : sessionData.session.location || "Location TBD"; 358 const address = !isOnline && sessionData.session.address ? sessionData.session.address : ""; 359 const contactName = !isOnline && sessionData.session.contactName ? sessionData.session.contactName : ""; 360 const contactEmail = !isOnline && sessionData.session.contactEmail ? sessionData.session.contactEmail : ""; 361 const contactPhone = !isOnline && sessionData.session.contactPhone ? sessionData.session.contactPhone : ""; 362 const hasContact = contactName || contactEmail || contactPhone; 363 const rows = students.map((s, idx) => { 364 var _a, _b; 365 const name = s.enrollment.studentName || ((_a = s.student) == null ? void 0 : _a.name) || ""; 366 const company = s.enrollment.studentCompany || ""; 367 const email = s.enrollment.studentEmail || ((_b = s.student) == null ? void 0 : _b.email) || ""; 368 const keys = [s.enrollment.accessKey1, s.enrollment.accessKey2, s.enrollment.accessKey3, s.enrollment.accessKey4].filter(Boolean); 369 const keyHtml = keys.length > 0 ? keys.map((k, i) => `<div class="key-row"><span class="key-label">Key ${i + 1}</span><span class="key-val">${k}</span></div>`).join("") : '<span class="empty">â</span>'; 370 const rowBg = idx % 2 === 0 ? "#ffffff" : "#f8fafc"; 371 return ` 372 <tr style="background:${rowBg}"> 373 <td class="num">${idx + 1}</td> 374 <td class="cell-name"> 375 <div class="student-name">${name}</div> 376 <div class="student-email">${email}</div> 377 </td> 378 <td class="cell">${company}</td> 379 <td class="cell-keys">${keyHtml}</td> 380 <td class="cell-sig"></td> 381 </tr>`; 382 }).join(""); 383 const html = `<!DOCTYPE html> 384<html> 385<head> 386 <meta charset="utf-8" /> 387 <title>Sign-In Sheet â ${sessionData.course.title}</title> 388 <style> 389 @page { size: portrait; margin: 1cm 1.2cm; } 390 * { box-sizing: border-box; margin: 0; padding: 0; } 391 body { font-family: 'Helvetica Neue', Arial, sans-serif; color: #111; font-size: 9.5px; } 392 393 /* Header */ 394 .header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 10px; padding-bottom: 8px; border-bottom: 2.5px solid #FF7A00; } 395 .header-left {} 396 .brand { font-size: 9px; font-weight: 700; color: #FF7A00; letter-spacing: .08em; text-transform: uppercase; margin-bottom: 3px; } 397 .course-title { font-size: 15px; font-weight: 700; color: #0f172a; line-height: 1.2; margin-bottom: 2px; } 398 .course-sub { font-size: 9px; color: #64748b; } 399 .header-right { text-align: right; } 400 .sheet-label { font-size: 8.5px; font-weight: 600; color: #94a3b8; text-transform: uppercase; letter-spacing: .08em; margin-bottom: 4px; } 401 .meta-grid { display: flex; flex-direction: column; gap: 2px; } 402 .meta-row { display: flex; gap: 5px; justify-content: flex-end; font-size: 9px; color: #334155; } 403 .meta-label { font-weight: 600; color: #64748b; } 404 405 /* Table */ 406 table { width: 100%; border-collapse: collapse; } 407 thead tr { background: #0f172a; } 408 thead th { 409 color: #fff; font-size: 8px; font-weight: 600; 410 text-transform: uppercase; letter-spacing: .07em; 411 padding: 5px 7px; text-align: left; 412 } 413 tbody tr { border-bottom: 1px solid #e2e8f0; } 414 tbody tr:last-child { border-bottom: 2px solid #cbd5e1; } 415 416 td { padding: 5px 7px; vertical-align: top; } 417 .num { color: #94a3b8; font-size: 8.5px; width: 22px; text-align: center; padding-top: 6px; } 418 .cell-name { width: 24%; } 419 .student-name { font-weight: 600; font-size: 9.5px; color: #0f172a; margin-bottom: 1px; } 420 .student-email { font-size: 8px; color: #64748b; } 421 .cell { width: 18%; font-size: 9px; color: #334155; } 422 .cell-keys { width: 26%; } 423 .cell-sig { width: 24%; border-left: 1px dashed #cbd5e1; } 424 425 .key-row { display: flex; gap: 4px; align-items: baseline; margin-bottom: 1px; } 426 .key-label { font-size: 7.5px; font-weight: 600; color: #94a3b8; text-transform: uppercase; letter-spacing: .05em; white-space: nowrap; } 427 .key-val { font-family: 'Courier New', monospace; font-size: 8.5px; color: #0f172a; background: #f1f5f9; padding: 1px 4px; border-radius: 2px; } 428 .empty { color: #cbd5e1; } 429 430 /* Footer */ 431 .footer { margin-top: 8px; display: flex; justify-content: space-between; align-items: center; padding-top: 5px; border-top: 1px solid #e2e8f0; } 432 .footer-left { font-size: 8px; color: #94a3b8; } 433 .footer-right { font-size: 8px; color: #94a3b8; } 434 .count-badge { display: inline-block; background: #FF7A00; color: #fff; font-size: 7.5px; font-weight: 700; padding: 1px 6px; border-radius: 10px; margin-left: 5px; } 435 436 /* Signature line hint */ 437 .sig-hint { font-size: 7.5px; color: #cbd5e1; margin-top: 14px; border-top: 1px solid #e2e8f0; padding-top: 2px; } 438 </style> 439</head> 440<body> 441 <div class="header"> 442 <div class="header-left"> 443 <div class="brand">
443SkillForge Training</div> 444 <div class="course-title">${sessionData.course.title}</div> 445 <div class="course-sub">${sessionData.course.category}</div> 446 </div> 447 <div class="header-right"> 448 <div class="sheet-label">Class Sign-In Sheet</div> 449 <div class="meta-grid"> 450 <div class="meta-row"><span class="meta-label">Date:</span> ${dateStr}</div> 451 <div class="meta-row"><span class="meta-label">Time:</span> ${timeStr}</div> 452 <div class="meta-row"><span class="meta-label">Location:</span> ${location}</div> 453 ${address ? `<div class="meta-row"><span class="meta-label">Address:</span> ${address}</div>` : ""} 454 ${hasContact ? `<div class="meta-row"><span class="meta-label">Contact:</span> ${[contactName, contactPhone, contactEmail].filter(Boolean).join(" · ")}</div>` : ""} 455 </div> 456 </div> 457 </div> 458 459 <table> 460 <thead> 461 <tr> 462 <th style="width:28px">#</th> 463 <th style="width:22%">Student Name & Email</th> 464 <th style="width:18%">Company</th> 465 <th style="width:28%">Access Key(s)</th> 466 <th style="width:22%">Signature</th> 467 </tr> 468 </thead> 469 <tbody>${rows}</tbody> 470 </table> 471 472 <div class="footer"> 473 <div class="footer-left"> 474 Printed ${(/* @__PURE__ */ new Date()).toLocaleString("en-US", { 475 dateStyle: "medium", 476 timeStyle: "short" 477 })} 478 </div> 479 <div class="footer-right"> 480 Total Students <span class="count-badge">${students.length}</span> 481 </div> 482 </div> 483</body> 484</html>`; 485 const blob = new Blob([html], { 486 type: "text/html" 487 }); 488 const url = URL.createObjectURL(blob); 489 const win = window.open(url, "_blank"); 490 if (!win) return; 491 win.focus(); 492 win.onload = () => { 493 win.print(); 494 URL.revokeObjectURL(url); 495 }; 496 setTimeout(() => URL.revokeObjectURL(url), 15e3); 497} 498function InstructorSessionRecordings({ 499 session 500}) { 501 const [recs, setRecs] = reactExports.useState([{ 502 url: session.recording1Url || "", 503 key: session.recording1Key || "" 504 }, { 505 url: session.recording2Url || "", 506 key: session.recording2Key || "" 507 }, { 508 url: session.recording3Url || "", 509 key: session.recording3Key || "" 510 }]); 511 const [saving, setSaving] = reactExports.useState(false); 512 const [dirty, setDirty] = reactExports.useState(false); 513 const [visibleCount, setVisibleCount] = reactExports.useState(() => { 514 if (session.recording3Url || session.recording3Key) return 3; 515 if (session.recording2Url || session.recording2Key) return 2; 516 return 1; 517 }); 518 function updateRec(i, field, val) { 519 setRecs((prev) => { 520 const next = [...prev]; 521 next[i] = { 522 ...next[i], 523 [field]: val 524 }; 525 return next; 526 }); 527 setDirty(true); 528 } 529 async function save() { 530 setSaving(true); 531 try { 532 const res = await fetch(`/api/sessions/${session.id}/recordings`, { 533 method: "PATCH", 534 headers: { 535 "Content-Type": "application/json" 536 }, 537 body: JSON.stringify({ 538 recording1Url: recs[0].url, 539 recording1Key: recs[0].key, 540 recording2Url: recs[1].url, 541 recording2Key: recs[1].key, 542 recording3Url: recs[2].url, 543 recording3Key: recs[2].key 544 }) 545 }); 546 if (!res.ok) throw new Error(); 547 toast.success("Recordings saved"); 548 setDirty(false); 549 } catch { 550 toast.error("Failed to save recordings"); 551 } finally { 552 setSaving(false); 553 } 554 } 555 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border rounded-md p-3 space-y-2 bg-muted/30", children: [ 556 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs font-medium text-muted-foreground flex items-center gap-1.5", children: [
557 /* @__PURE__ */ jsxRuntimeExports.jsx(Video, { className: "h-3 w-3" }), 558 "Session Recordings" 559 ] }), 560 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-2", children: Array.from({ 561 length: visibleCount 562 }).map((_, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [ 563 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs text-muted-foreground pl-1", children: [ 564 "Recording ", 565 i + 1 566 ] }), 567 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5", children: [ 568 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground w-7 shrink-0 text-right text-xs", children: "URL" }), 569 /* @__PURE__ */ jsxRuntimeExports.jsx(Input, { className: "h-6 text-xs flex-1", placeholder: "https://â¦", value: recs[i].url, onChange: (e) => updateRec(i, "url", e.target.value) }) 570 ] }), 571 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5", children: [
572 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground w-14 shrink-0 text-right text-xs", children: "Passcode" }), 573 /* @__PURE__ */ jsxRuntimeExports.jsx(Input, { className: "h-6 text-xs font-mono flex-1", placeholder: "Passcodeâ¦", value: recs[i].key, onChange: (e) => updateRec(i, "key", e.target.value) }) 574 ] }) 575 ] }, i)) }), 576 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 pt-1", children: [ 577 visibleCount < 3 && /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", className: "text-xs text-primary hover:underline", onClick: () => setVisibleCount((v) => Math.min(v + 1, 3)), children: [ 578 "+ Add Recording ", 579 visibleCount + 1 580 ] }), 581 /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { size: "sm", variant: "outline", className: "h-6 px-2 text-xs ml-auto", disabled: saving || !dirty, onClick: save, children: saving ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "h-3 w-3 animate-spin" }) : "Save" }) 582 ] }) 583 ] }); 584} 585function InstructorDashboard() { 586 const { 587 session, 588 isPending 589 } = useSession(); 590 const navigate = useNavigate(); 591 const [assignedSessions, setAssignedSessions] = reactExports.useState([]); 592 const [loading, setLoading] = reactExports.useState(true); 593 const [canWriteBlog, setCanWriteBlog] = reactExports.useState(false); 594 const [calendarDate, setCalendarDate] = reactExports.useState(() => { 595 const d = /* @__PURE__ */ new Date(); 596 return { 597 year: d.getFullYear(), 598 month: d.getMonth() 599 }; 600 }); 601 const [rosterOpen, setRosterOpen] = reactExports.useState(false); 602 const [activeSession, setActiveSession] = reactExports.useState(null); 603 const [students, setStudents] = reactExports.useState([]); 604 const [loadingStudents, setLoadingStudents] = reactExports.useState(false); 605 const [bulkStatus, setBulkStatus] = reactExports.useState("attended"); 606 const [bulkApplying, setBulkApplying] = reactExports.useState(false); 607 const [sessionSort, setSessionSort] = reactExports.useState("newest"); 608 reactExports.useEffect(() => { 609 if (session == null ? void 0 : session.user) { 610 fetch(`/api/sessions?instructorId=${session.user.id}`).then((res) => res.json()).then((data) => { 611 setAssignedSessions(Array.isArray(data) ? data : []); 612 setLoading(false); 613 }).catch(() => setLoading(false)); 614 fetch("/api/users/me").then((r) => r.ok ? r.json() : null).then((u) => { 615 if ((u == null ? void 0 : u.canWriteBlog) || (u == null ? void 0 : u.role) === "admin") setCanWriteBlog(true); 616 }).catch(() => { 617 }); 618 } 619 }, [session]); 620 const openRoster = async (sessionData) => { 621 setActiveSession(sessionData); 622 setStudents([]); 623 setRosterOpen(true); 624 setLoadingStudents(true); 625 try { 626 const res = await fetch(`/api/enrollments/session/${sessionData.session.id}`); 627 const data = await res.json(); 628 setStudents(Array.isArray(data) ? data : []); 629 } catch { 630 toast.error("Failed to load students"); 631 } finally { 632 setLoadingStudents(false); 633 } 634 }; 635 const handleUpdateStatus = reactExports.useCallback(async (enrollmentId, status) => { 636 try { 637 const res = await fetch(`/api/enrollments/${enrollmentId}/status`, { 638 method: "PATCH", 639 headers: { 640 "Content-Type": "application/json" 641 }, 642 body: JSON.stringify({ 643 status 644 }) 645 }); 646 if (res.ok) { 647 setStudents((prev) => prev.map((s) => s.enrollment.id === enrollmentId ? { 648 ...s, 649 enrollment: { 650 ...s.enrollment, 651 status 652 } 653 } : s)); 654 } else { 655 toast.error("Failed to update status"); 656 } 657 } catch { 658 toast.error("Failed to update status"); 659 } 660 }, []); 661 const handleBulkUpdate = reactExports.useCallback(async () => { 662 if (!students.length) return; 663 setBulkApplying(true); 664 try { 665 await Promise.all(students.map((s) => fetch(`/api/enrollments/${s.enrollment.id}/status`, { 666 method: "PATCH", 667 headers: { 668 "Content-Type": "application/json" 669 }, 670 body: JSON.stringify({ 671 status: bulkStatus 672 }) 673 }))); 674 setStudents((prev) => prev.map((s) => ({ 675 ...s, 676 enrollment: { 677 ...s.enrollment, 678 status: bulkStatus 679 } 680 }))); 681 toast.success(`Marked all ${students.length} student${students.length !== 1 ? "s" : ""} as ${bulkStatus}`); 682 } catch { 683 toast.error("Some updates failed â please try again"); 684 } finally { 685 setBulkApplying(false); 686 } 687 }, [students, bulkStatus]); 688 if (isPending || loading) { 689 return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "min-h-screen bg-background flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-muted-foreground", children: "Loading dashboard..." }) }); 690 } 691 if (!(session == null ? void 0 : session.user)) return /* @__PURE__ */ jsxRuntimeExports.jsx(Navigate, { to: "/auth?mode=sign-in", replace: true }); 692 const user = getUserWithRole(session.user); 693 if (user.role !== "instructor") { 694 if (user.role === "admin") return /* @__PURE__ */ jsxRuntimeExports.jsx(Navigate, { to: "/admin", replace: true }); 695 if (user.role === "student") return /* @__PURE__ */ jsxRuntimeExports.jsx(Navigate, { to: "/student", replace: true }); 696 } 697 const now = /* @__PURE__ */ new Date(); 698 const upcomingSessions = assignedSessions.filter((s) => { 699 const end = parseLocalDate(s.session.endDate); 700 end.setDate(end.getDate() + 1); 701 return end > now; 702 }); 703 const pastSessions = assignedSessions.filter((s) => { 704 const end = parseLocalDate(s.session.endDate); 705 end.setDate(end.getDate() + 1); 706 return end <= now; 707 }); 708 const sortSessions = (list) => [...list].sort((a, b) => { 709 const aDate = new Date(a.session.startDate).getTime(); 710 const bDate = new Date(b.session.startDate).getTime(); 711 return sessionSort === "newest" ? bDate - aDate : aDate - bDate; 712 }); 713 const sortedUpcoming = sortSessions(upcomingSessions); 714 const sortedPast = sortSessions(pastSessions); 715 const SessionRow = ({ 716 sessionData, 717 past = false 718 }) => { 719 const isOnline = sessionData.session.deliveryMode === "online"; 720 const startTime = formatTime(sessionData.session.startTime); 721 const endTime = formatTime(sessionData.session.endTime); 722 const tz = sessionData.session.timezone || ""; 723 const hasContact = !isOnline && (sessionData.session.contactName || sessionData.session.contactEmail || sessionData.session.contactPhone); 724 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex items-start gap-4 px-4 py-3 rounded-lg border bg-card hover:bg-muted/40 transition-colors${past ? " opacity-60" : ""}`, children: [
725 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-shrink-0 w-14 text-center pt-0.5", children: [ 726 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs font-semibold text-muted-foreground uppercase tracking-wide", children: formatSessionDate(sessionData.session.startDate, { 727 month: "short" 728 }) }), 729 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-2xl font-bold text-foreground leading-none", children: new Date(sessionData.session.startDate).getUTCDate() }), 730 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-muted-foreground", children: formatSessionDate(sessionData.session.startDate, { 731 weekday: "short" 732 }) }) 733 ] }), 734 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px self-stretch bg-border" }), 735 /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: sessionData.course.imageUrl, alt: sessionData.course.title, className: `h-10 w-16 rounded object-cover flex-shrink-0 mt-0.5${past ? " grayscale" : ""}` }),
736 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [ 737 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mb-0.5", children: [ 738 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-semibold text-sm text-foreground truncate", children: sessionData.course.title }), 739 /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: past ? "outline" : "secondary", className: "text-xs shrink-0", children: past ? "Completed" : sessionData.course.category }) 740 ] }), 741 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 text-xs text-muted-foreground", children: [ 742 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1", children: [ 743 isOnline ? /* @__PURE__ */ jsxRuntimeExports.jsx(Monitor, { className: "h-3 w-3" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Building2, { className: "h-3 w-3" }), 744 isOnline ? "Live Online" : sessionData.session.location || "Location TBD" 745 ] }),
746 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1", children: [ 747 /* @__PURE__ */ jsxRuntimeExports.jsx(Users, { className: "h-3 w-3" }), 748 "Max ", 749 sessionData.session.maxStudents 750 ] }), 751 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1", children: [ 752 /* @__PURE__ */ jsxRuntimeExports.jsx(Calendar, { className: "h-3 w-3" }), 753 startTime, 754 " â ", 755 endTime, 756 tz ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground/60", children: [ 757 " ", 758 tz 759 ] }) : null 760 ] }) 761 ] }), 762 !isOnline && sessionData.session.address && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-muted-foreground mt-1 flex items-start gap-1", children: [ 763 /* @__PURE__ */ jsxRuntimeExports.jsx(MapPin, { className: "h-3 w-3 shrink-0 mt-0.5" }), 764 /* @__P
764URE__ */ jsxRuntimeExports.jsx("span", { children: sessionData.session.address }) 765 ] }), 766 hasContact && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-1.5 flex flex-wrap gap-x-4 gap-y-0.5 text-xs", children: [ 767 sessionData.session.contactName && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground", children: [ 768 /* @__PURE__ */ jsxRuntimeExports.jsx(CircleUser, { className: "h-3 w-3 shrink-0" }), 769 sessionData.session.contactName 770 ] }), 771 sessionData.session.contactPhone && /* @__PURE__ */ jsxRuntimeExports.jsxs("a", { href: `tel:${sessionData.session.contactPhone}`, className: "flex items-center gap-1 text-primary hover:underline", children: [ 772 /* @__PURE__ */ jsxRuntimeExports.jsx(Phone, { className: "h-3 w-3 shrink-0" }), 773 sessionData.session.contactPhone 774 ] }), 775 sessionData.session.contactEmail && /* @__PURE__ */ jsxRuntimeExports.jsxs("a", { href: `mailto:${sessionData.session.contactEmail}`, className: "flex items-center gap-1 text-primary hover:underline", children: [
776 /* @__PURE__ */ jsxRuntimeExports.jsx(Mail, { className: "h-3 w-3 shrink-0" }), 777 sessionData.session.contactEmail 778 ] }) 779 ] }), 780 sessionData.session.notes && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 rounded-md border border-amber-200 bg-amber-50 px-3 py-2 flex items-start gap-2", children: [ 781 /* @__PURE__ */ jsxRuntimeExports.jsx(FileText, { className: "h-3.5 w-3.5 shrink-0 mt-0.5 text-amber-600" }), 782 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 783 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs font-semibold text-amber-800 mb-0.5", children: "Session Notes" }), 784 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-amber-900 whitespace-pre-line", children: sessionData.session.notes }) 785 ] }) 786 ] }), 787 /* @__PURE__ */ jsxRuntimeExports.jsx(CoursewareSection, { courseId: sessionData.course.id }) 788 ] }), 789 /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { size: "sm", variant: "outline", className: "shrink-0 mt-0.5", onClick: () => openRoster(sessionData), children: [
790 /* @__PURE__ */ jsxRuntimeExports.jsx(Users, { className: "h-3.5 w-3.5 mr-1.5" }), 791 "Roster" 792 ] }) 793 ] }); 794 }; 795 const WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; 796 const calendarCells = (() => { 797 const { 798 year, 799 month 800 } = calendarDate; 801 const firstDay = new Date(year, month, 1).getDay(); 802 const daysInMonth = new Date(year, month + 1, 0).getDate(); 803 const cells = []; 804 for (let i = 0; i < firstDay; i++) cells.push(null); 805 for (let d = 1; d <= daysInMonth; d++) cells.push(d); 806 while (cells.length % 7 !== 0) cells.push(null); 807 return cells; 808 })(); 809 const sessionsByDay = assignedSessions.reduce((acc, s) => { 810 const d = new Date(s.session.startDate); 811 if (d.getUTCFullYear() === calendarDate.year && d.getUTCMonth() === calendarDate.month) { 812 const day = d.getUTCDate(); 813 if (!acc[day]) acc[day] = []; 814 acc[day].push(s); 815 } 816 return acc; 817 }, {}); 818 const today = /* @__PURE__ */ new Date(); 819 const isToday = (day) => today.getFullYear() === calendarDate.year && today.getMonth() === calendarDate.month && today.getDate() === day; 820 const monthLabel = new Date(calendarDate.year, calendarDate.month, 1).toLocaleDateString("en-US", { 821 month: "long", 822 year: "numeric" 823 }); 824 const prevMonth = () => setCalendarDate(({ 825 year, 826 month 827 }) => month === 0 ? { 828 year: year - 1, 829 month: 11 830 } : { 831 year, 832 month: month - 1 833 }); 834 const nextMonth = () => setCalendarDate(({ 835 year, 836 month 837 }) => month === 11 ? { 838 year: year + 1, 839 month: 0 840 } : { 841 year, 842 month: month + 1 843 }); 844 const goToday = () => setCalendarDate({ 845 year: today.getFullYear(), 846 month: today.getMonth() 847 }); 848 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-h-screen bg-background py-12", children: [ 849 /* @__PURE__ */ jsxRuntimeExports.jsxs(Helmet, { children: [
850 /* @__PURE__ */ jsxRuntimeExports.jsx("title", { children: "Instructor Portal - SkillForge" }), 851 /* @__PURE__ */ jsxRuntimeExports.jsx("meta", { name: "description", content: "SkillForge instructor portal." }), 852 /* @__PURE__ */ jsxRuntimeExports.jsx("link", { rel: "canonical", href: "https://skillforge.com/instructor" }), 853 /* @__PURE__ */ jsxRuntimeExports.jsx("meta", { name: "robots", content: "noindex, nofollow" }) 854 ] }), 855 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mx-auto max-w-7xl px-4 sm:px-6 lg:px-8", children: [ 856 /* @__PURE__ */ jsxRuntimeExports.jsxs(motion.div, { initial: { 857 opacity: 0, 858 y: 20 859 }, animate: { 860 opacity: 1, 861 y: 0 862 }, transition: { 863 duration: 0.5 864 }, className: "mb-8 flex items-center justify-between", children: [ 865 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 866 /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { className: "text-4xl font-bold text-foreground", children: "Instructor Portal" }),
867 /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "mt-2 text-lg text-muted-foreground", children: [ 868 "Welcome back, ", 869 session.user.name, 870 "!" 871 ] }) 872 ] }), 873 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [ 874 canWriteBlog && /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { variant: "outline", size: "sm", onClick: () => navigate("/instructor/blog"), children: [ 875 /* @__PURE__ */ jsxRuntimeExports.jsx(PenLine, { className: "h-4 w-4 mr-2" }), 876 " Blog Posts" 877 ] }), 878 /* @__PURE__ */ jsxRuntimeExports.jsx(ProfileMenu, {}) 879 ] }) 880 ] }), 881 /* @__PURE__ */ jsxRuntimeExports.jsxs(motion.div, { initial: { 882 opacity: 0, 883 y: 20 884 }, animate: { 885 opacity: 1, 886 y: 0 887 }, transition: { 888 duration: 0.5, 889 delay: 0.1 890 }, className: "grid grid-cols-1 md:grid-cols-3 gap-6 mb-8", children: [ 891 /* @__PURE__ */ jsxRuntimeExports.jsx(Card, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
892 /* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-2xl", children: upcomingSessions.length }), 893 /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: "Upcoming Sessions" }) 894 ] }) }), 895 /* @__PURE__ */ jsxRuntimeExports.jsx(Card, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [ 896 /* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-2xl", children: pastSessions.length }), 897 /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: "Completed Sessions" }) 898 ] }) }), 899 /* @__PURE__ */ jsxRuntimeExports.jsx(Card, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [ 900 /* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-2xl", children: assignedSessions.length }), 901 /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: "Total Assignments" }) 902 ] }) }) 903 ] }), 904 /* @__PURE__ */ jsxRuntimeExports.jsx(motion.div, { initial: { 905 opacity: 0, 906 y: 20 907 }, animate: { 908 opacity: 1, 909 y: 0 910 }, transition: { 911 duration: 0.5, 912 delay: 0.2 913 }, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tabs, { defaultValue: "upcoming", className: "w-full", children: [
914 /* @__PURE__ */ jsxRuntimeExports.jsxs(TabsList, { className: "grid w-full grid-cols-5 mb-8", children: [ 915 /* @__PURE__ */ jsxRuntimeExports.jsxs(TabsTrigger, { value: "upcoming", children: [ 916 "Upcoming (", 917 upcomingSessions.length, 918 ")" 919 ] }), 920 /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "calendar", children: "Calendar" }), 921 /* @__PURE__ */ jsxRuntimeExports.jsxs(TabsTrigger, { value: "past", children: [ 922 "Past (", 923 pastSessions.length, 924 ")" 925 ] }), 926 /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "surveys", children: "Surveys" }), 927 /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "manuals", children: "Manuals" }) 928 ] }), 929 /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "upcoming", children: upcomingSessions.length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
930 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end mb-1", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Select, { value: sessionSort, onValueChange: (v) => setSessionSort(v), children: [ 931 /* @__PURE__ */ jsxRuntimeExports.jsx(SelectTrigger, { className: "w-36 h-8 text-sm", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SelectValue, {}) }), 932 /* @__PURE__ */ jsxRuntimeExports.jsxs(SelectContent, { children: [ 933 /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "newest", children: "Newest first" }), 934 /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "oldest", children: "Oldest first" }) 935 ] }) 936 ] }) }), 937 sortedUpcoming.map((s) => /* @__PURE__ */ jsxRuntimeExports.jsx(SessionRow, { sessionData: s }, s.session.id)) 938 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Card, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "py-12 text-center", children: [ 939 /* @__P
939URE__ */ jsxRuntimeExports.jsx(BookOpen, { className: "h-12 w-12 text-muted-foreground mx-auto mb-4" }), 940 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-muted-foreground", children: "No upcoming sessions assigned" }) 941 ] }) }) }), 942 /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "calendar", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [ 943 /* @__PURE__ */ jsxRuntimeExports.jsx(CardHeader, { className: "pb-3", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [ 944 /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { variant: "ghost", size: "icon", onClick: prevMonth, children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronLeft, { className: "h-4 w-4" }) }), 945 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [ 946 /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-lg font-semibold", children: monthLabel }),
947 /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { variant: "outline", size: "sm", className: "text-xs h-7 px-2", onClick: goToday, children: "Today" }) 948 ] }), 949 /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { variant: "ghost", size: "icon", onClick: nextMonth, children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "h-4 w-4" }) }) 950 ] }) }), 951 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "p-0 pb-4", children: [ 952 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-7 border-b", children: WEEKDAYS.map((d) => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "py-2 text-center text-xs font-semibold text-muted-foreground uppercase tracking-wide", children: d }, d)) }), 953 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-7", children: calendarCells.map((day, idx) => { 954 const sessions = day ? sessionsByDay[day] ?? [] : []; 955 const today2 = isToday(day ?? -1); 956 return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `min-h-[90px] p-1.5 border-b border-r relative 957 ${idx % 7 === 0 ? "border-l" : ""} 958 ${!day ? "bg-muted/20" : "bg-background"}
959 `, children: day && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 960 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `inline-flex items-center justify-center w-6 h-6 text-xs font-medium rounded-full mb-1 961 ${today2 ? "bg-primary text-primary-foreground" : "text-foreground"}`, children: day }), 962 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col gap-0.5", children: sessions.map((s) => { 963 const isOnline = s.session.deliveryMode === "online"; 964 return /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: () => openRoster(s), className: `w-full text-left rounded px-1.5 py-0.5 text-[10px] leading-tight font-medium truncate transition-opacity hover:opacity-80 965 ${isOnline ? "bg-[#4A7A8A]/15 text-[#4A7A8A] border border-[#4A7A8A]/30" : "bg-[#FF7A00]/15 text-[#FF7A00] border border-[#FF7A00]/30"}`, title: `${s.course.title} â ${formatTime(s.session.startTime)} ${s.session.timezone}`, children: [
966 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block truncate", children: s.course.title }), 967 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "block text-[9px] opacity-70 font-normal", children: [ 968 isOnline ? /* @__PURE__ */ jsxRuntimeExports.jsx(Monitor, { className: "inline h-2 w-2 mr-0.5" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Building2, { className: "inline h-2 w-2 mr-0.5" }), 969 formatTime(s.session.startTime) 970 ] }) 971 ] }, s.session.id); 972 }) }) 973 ] }) }, idx); 974 }) }), 975 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4 px-4 pt-3 text-xs text-muted-foreground", children: [ 976 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1.5", children: [ 977 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "inline-block w-3 h-3 rounded-sm bg-[#4A7A8A]/20 border border-[#4A7A8A]/40" }), 978 "Live Online" 979 ] }),
980 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1.5", children: [ 981 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "inline-block w-3 h-3 rounded-sm bg-[#FF7A00]/20 border border-[#FF7A00]/40" }), 982 "Onsite" 983 ] }), 984 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex items-center gap-1.5 ml-auto", children: "Click any session to view roster" }) 985 ] }) 986 ] }) 987 ] }) }), 988 /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "past", children: pastSessions.length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [ 989 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end mb-1", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Select, { value: sessionSort, onValueChange: (v) => setSessionSort(v), children: [ 990 /* @__PURE__ */ jsxRuntimeExports.jsx(SelectTrigger, { className: "w-36 h-8 text-sm", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SelectValue, {}) }),
991 /* @__PURE__ */ jsxRuntimeExports.jsxs(SelectContent, { children: [ 992 /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "newest", children: "Newest first" }), 993 /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "oldest", children: "Oldest first" }) 994 ] }) 995 ] }) }), 996 sortedPast.map((s) => /* @__PURE__ */ jsxRuntimeExports.jsx(SessionRow, { sessionData: s, past: true }, s.session.id)) 997 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Card, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { className: "py-12 text-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-muted-foreground", children: "No completed sessions yet" }) }) }) }), 998 /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "surveys", children: /* @__PURE__ */ jsxRuntimeExports.jsx(InstructorSurveysTab, {}) }), 999 /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "manuals", children: /* @__PURE__ */ jsxRuntimeExports.jsx(InstructorManualsTab, {}) }) 1000 ] }) }) 1001 ] }), 1002 /* @__PURE__ */ jsxRuntimeExports.jsx(Dialog, { open: rosterOpen, onOpenChange: setRosterOpen, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(DialogContent, { className: "max-w-2xl max-h-[80vh] overflow-y-auto", children: [
1003 /* @__PURE__ */ jsxRuntimeExports.jsxs(DialogHeader, { children: [ 1004 /* @__PURE__ */ jsxRuntimeExports.jsx(DialogTitle, { children: "Student Roster" }), 1005 /* @__PURE__ */ jsxRuntimeExports.jsx(DialogDescription, { children: activeSession == null ? void 0 : activeSession.course.title }) 1006 ] }), 1007 loadingStudents ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "py-8 text-center", children: [ 1008 /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "h-6 w-6 animate-spin mx-auto text-muted-foreground" }), 1009 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-muted-foreground mt-2", children: "Loading students..." }) 1010 ] }) : students.length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 1011 activeSession && /* @__PURE__ */ jsxRuntimeExports.jsx(InstructorSessionRecordings, { session: activeSession.session }), 1012 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 p-3 bg-muted/50 rounded-lg border", children: [
1013 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-medium text-muted-foreground whitespace-nowrap", children: "Bulk update:" }), 1014 /* @__PURE__ */ jsxRuntimeExports.jsxs(Select, { value: bulkStatus, onValueChange: setBulkStatus, children: [ 1015 /* @__PURE__ */ jsxRuntimeExports.jsx(SelectTrigger, { className: "h-8 flex-1 text-sm", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SelectValue, {}) }), 1016 /* @__PURE__ */ jsxRuntimeExports.jsxs(SelectContent, { children: [ 1017 /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "enrolled", children: "Enrolled" }), 1018 /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "attended", children: "Attended" }), 1019 /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "no-show", children: "No Show" }), 1020 /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "cancelled", children: "Cancelled" }), 1021 /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "rescheduled", children: "Rescheduled" }) 1022 ] }) 1023 ] }),
1024 /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { size: "sm", className: "whitespace-nowrap", disabled: bulkApplying, onClick: handleBulkUpdate, children: [ 1025 bulkApplying ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "h-3.5 w-3.5 mr-1.5 animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(CheckCheck, { className: "h-3.5 w-3.5 mr-1.5" }), 1026 "Apply to All" 1027 ] }) 1028 ] }), 1029 /* @__PURE__ */ jsxRuntimeExports.jsx(Separator, {}), 1030 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-3", children: students.map((studentData) => { 1031 var _a, _b; 1032 const keys = [studentData.enrollment.accessKey1, studentData.enrollment.accessKey2, studentData.enrollment.accessKey3, studentData.enrollment.accessKey4].filter(Boolean); 1033 return /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [ 1034 /* @__PURE__ */ jsxRuntimeExports.jsx(CardHeader, { className: "pb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between gap-4", children: [
1035 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [ 1036 /* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-base", children: studentData.enrollment.studentName || ((_a = studentData.student) == null ? void 0 : _a.name) }), 1037 /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { className: "break-words text-xs", children: studentData.enrollment.studentEmail || ((_b = studentData.student) == null ? void 0 : _b.email) }), 1038 studentData.enrollment.studentCompany && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: studentData.enrollment.studentCompany }) 1039 ] }), 1040 /* @__PURE__ */ jsxRuntimeExports.jsxs(Select, { value: studentData.enrollment.status || "enrolled", onValueChange: (val) => handleUpdateStatus(studentData.enrollment.id, val), children: [ 1041 /* @__PURE__ */ jsxRuntimeExports.jsx(SelectTrigger, { className: "h-7 w-28 text-xs shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SelectValue, {}) }),
1042 /* @__PURE__ */ jsxRuntimeExports.jsxs(SelectContent, { children: [ 1043 /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "enrolled", children: "Enrolled" }), 1044 /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "attended", children: "Attended" }), 1045 /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "no-show", children: "No Show" }), 1046 /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "cancelled", children: "Cancelled" }), 1047 /* @__PURE__ */ jsxRuntimeExports.jsx(SelectItem, { value: "rescheduled", children: "Rescheduled" }) 1048 ] }) 1049 ] }) 1050 ] }) }), 1051 /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { className: "pt-0 space-y-2", children: keys.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 flex-wrap", children: [ 1052 /* @__PURE__ */ jsxRuntimeExports.jsx(Key, { className: "h-3 w-3 text-muted-foreground shrink-0" }), 1053 keys.map((k, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1 text-xs bg-muted rounded px-2 py-0.5 font-mono", children: [
1054 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground not-italic", children: [ 1055 "Key ", 1056 i + 1, 1057 ":" 1058 ] }), 1059 k 1060 ] }, i)) 1061 ] }) }) 1062 ] }, studentData.enrollment.id); 1063 }) }), 1064 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pt-2 border-t", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { variant: "outline", className: "w-full", onClick: () => activeSession && printSignInSheet(activeSession, students), children: [ 1065 /* @__PURE__ */ jsxRuntimeExports.jsx(Printer, { className: "h-4 w-4 mr-2" }), 1066 "Print Sign-In Sheet" 1067 ] }) }) 1068 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "py-8 text-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-muted-foreground", children: "No students enrolled yet" }) }) 1069 ] }) }) 1070 ] }); 1071} 1072export { 1073 InstructorDashboard as default 1074};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.