@local-civics/mgmt-ui 0.1.229 → 0.1.231

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -872,9 +872,11 @@ interface SubmissionItem {
872
872
  badgeName: string;
873
873
  badgeId: string;
874
874
  lessonName: string;
875
+ lessonId?: string;
875
876
  question: string;
876
877
  updatedAt?: string;
877
878
  pathwayName?: string;
879
+ pathwayId?: string;
878
880
  }
879
881
 
880
882
  /**
@@ -921,6 +923,9 @@ type FileLockerProps = {
921
923
  onClassChange: (classId: string) => void;
922
924
  onCopyLinkClick: () => void;
923
925
  onExportDataClick: () => void;
926
+ onBadgeClick?: (badgeId: string) => void;
927
+ onLessonClick?: (lessonId: string) => void;
928
+ onPathwayClick?: (pathwayId: string) => void;
924
929
  };
925
930
  /**
926
931
  * FileLocker
package/dist/index.js CHANGED
@@ -3355,31 +3355,21 @@ const SplitButton = (props) => {
3355
3355
  };
3356
3356
 
3357
3357
  function Stack(props) {
3358
- const { items, hideBadge, hideLesson, hidePathway } = props;
3358
+ const { items, hideBadge, hideLesson, hidePathway, onBadgeClick, onLessonClick, onPathwayClick } = props;
3359
3359
  if (!items.length)
3360
3360
  return null;
3361
3361
  const columns = [
3362
- "minmax(0,2fr)",
3363
3362
  !hidePathway && "minmax(0,1.3fr)",
3364
- !hideLesson && "minmax(0,1.3fr)",
3365
3363
  !hideBadge && "minmax(0,1.3fr)",
3364
+ !hideLesson && "minmax(0,1.3fr)",
3365
+ "minmax(0,2fr)",
3366
3366
  "auto"
3367
3367
  ].filter(Boolean).join(" ");
3368
3368
  const headerCell = "border-b border-slate-100 pb-2 text-[10.5px] font-extrabold uppercase tracking-wide text-slate-400";
3369
3369
  const dataCell = "py-3";
3370
- return /* @__PURE__ */ React__namespace.createElement("div", { className: "grid items-center gap-x-3", style: { gridTemplateColumns: columns } }, /* @__PURE__ */ React__namespace.createElement("div", { className: headerCell }, "Question"), !hidePathway && /* @__PURE__ */ React__namespace.createElement("div", { className: headerCell }, "Pathway"), !hideLesson && /* @__PURE__ */ React__namespace.createElement("div", { className: headerCell }, "Lesson"), !hideBadge && /* @__PURE__ */ React__namespace.createElement("div", { className: headerCell }, "Badge"), /* @__PURE__ */ React__namespace.createElement("div", { className: headerCell }), items.map((row, i) => {
3370
+ return /* @__PURE__ */ React__namespace.createElement("div", { className: "grid items-center gap-x-3", style: { gridTemplateColumns: columns } }, !hidePathway && /* @__PURE__ */ React__namespace.createElement("div", { className: headerCell }, "Pathway"), !hideBadge && /* @__PURE__ */ React__namespace.createElement("div", { className: headerCell }, "Badge"), !hideLesson && /* @__PURE__ */ React__namespace.createElement("div", { className: headerCell }, "Lesson"), /* @__PURE__ */ React__namespace.createElement("div", { className: headerCell }, "Question"), /* @__PURE__ */ React__namespace.createElement("div", { className: headerCell }), items.map((row, i) => {
3371
3371
  const rowBorder = i > 0 ? "border-t border-slate-100" : "";
3372
- return /* @__PURE__ */ React__namespace.createElement(React__namespace.Fragment, { key: i }, /* @__PURE__ */ React__namespace.createElement("div", { className: `${dataCell} ${rowBorder} text-xs font-semibold text-dark-blue-400` }, row.question), !hidePathway && /* @__PURE__ */ React__namespace.createElement("div", { className: `${dataCell} ${rowBorder} truncate text-[11px] text-slate-500` }, row.pathwayName || "\u2014"), !hideLesson && /* @__PURE__ */ React__namespace.createElement("div", { className: `${dataCell} ${rowBorder} truncate text-[11px] text-slate-500` }, row.lessonName), !hideBadge && /* @__PURE__ */ React__namespace.createElement("div", { className: `${dataCell} ${rowBorder} truncate text-[11px] text-slate-500` }, row.badgeName), /* @__PURE__ */ React__namespace.createElement("div", { className: `${dataCell} ${rowBorder} flex shrink-0 gap-2` }, /* @__PURE__ */ React__namespace.createElement(
3373
- "a",
3374
- {
3375
- href: row.link,
3376
- target: "_blank",
3377
- rel: "noopener noreferrer",
3378
- className: "flex items-center gap-1 rounded-md border border-slate-200 px-2.5 py-1.5 text-[11px] font-bold text-slate-600 no-underline hover:bg-slate-50"
3379
- },
3380
- /* @__PURE__ */ React__namespace.createElement(icons.IconExternalLink, { size: 12, stroke: 2 }),
3381
- "Preview"
3382
- ), /* @__PURE__ */ React__namespace.createElement(
3372
+ return /* @__PURE__ */ React__namespace.createElement(React__namespace.Fragment, { key: i }, !hidePathway && (row.pathwayId && onPathwayClick ? /* @__PURE__ */ React__namespace.createElement("button", { type: "button", onClick: () => onPathwayClick(row.pathwayId), className: `${dataCell} ${rowBorder} truncate text-left text-[11px] font-semibold text-sky-blue-400 hover:underline` }, row.pathwayName || "\u2014") : /* @__PURE__ */ React__namespace.createElement("div", { className: `${dataCell} ${rowBorder} truncate text-[11px] text-slate-500` }, row.pathwayName || "\u2014")), !hideBadge && (row.badgeId && onBadgeClick ? /* @__PURE__ */ React__namespace.createElement("button", { type: "button", onClick: () => onBadgeClick(row.badgeId), className: `${dataCell} ${rowBorder} truncate text-left text-[11px] font-semibold text-sky-blue-400 hover:underline` }, row.badgeName) : /* @__PURE__ */ React__namespace.createElement("div", { className: `${dataCell} ${rowBorder} truncate text-[11px] text-slate-500` }, row.badgeName)), !hideLesson && (row.lessonId && onLessonClick ? /* @__PURE__ */ React__namespace.createElement("button", { type: "button", onClick: () => onLessonClick(row.lessonId), className: `${dataCell} ${rowBorder} truncate text-left text-[11px] font-semibold text-sky-blue-400 hover:underline` }, row.lessonName) : /* @__PURE__ */ React__namespace.createElement("div", { className: `${dataCell} ${rowBorder} truncate text-[11px] text-slate-500` }, row.lessonName)), /* @__PURE__ */ React__namespace.createElement("div", { className: `${dataCell} ${rowBorder} text-xs font-semibold text-dark-blue-400` }, row.question), /* @__PURE__ */ React__namespace.createElement("div", { className: `${dataCell} ${rowBorder} flex shrink-0 gap-2` }, /* @__PURE__ */ React__namespace.createElement(
3383
3373
  "a",
3384
3374
  {
3385
3375
  href: row.link,
@@ -3458,14 +3448,33 @@ function Table(props) {
3458
3448
  "Review Submission"
3459
3449
  )),
3460
3450
  /* @__PURE__ */ React__namespace.createElement(icons.IconChevronRight, { size: 16, stroke: 2, className: `w-4 shrink-0 text-slate-300 transition-transform ${isOpen ? "rotate-90" : ""}` })
3461
- ), isOpen && /* @__PURE__ */ React__namespace.createElement("div", { className: "border-t border-slate-100 px-4 py-3" }, /* @__PURE__ */ React__namespace.createElement(Stack, { items: row.submissions, hideBadge: props.hideBadge, hideLesson: props.hideLesson, hidePathway: props.hidePathway })));
3451
+ ), isOpen && /* @__PURE__ */ React__namespace.createElement("div", { className: "border-t border-slate-100 px-4 py-3" }, /* @__PURE__ */ React__namespace.createElement(
3452
+ Stack,
3453
+ {
3454
+ items: row.submissions,
3455
+ hideBadge: props.hideBadge,
3456
+ hideLesson: props.hideLesson,
3457
+ hidePathway: props.hidePathway,
3458
+ onBadgeClick: props.onBadgeClick,
3459
+ onLessonClick: props.onLessonClick,
3460
+ onPathwayClick: props.onPathwayClick
3461
+ }
3462
+ )));
3462
3463
  }));
3463
3464
  }
3464
3465
 
3465
3466
  const initials = (name) => name.split(" ").map((w) => w[0]).filter(Boolean).slice(0, 2).join("").toUpperCase();
3466
3467
  function SubmissionDetail(props) {
3467
3468
  const { student } = props;
3468
- return /* @__PURE__ */ React__namespace.createElement("div", { className: "flex w-full flex-col gap-4 px-4 py-8" }, /* @__PURE__ */ React__namespace.createElement("div", { className: "flex items-center gap-2 text-xs text-slate-400" }, /* @__PURE__ */ React__namespace.createElement("button", { onClick: props.onBack, className: "font-bold text-sky-blue-400 hover:underline" }, "File Locker"), /* @__PURE__ */ React__namespace.createElement("span", null, "/"), /* @__PURE__ */ React__namespace.createElement("span", { className: "font-bold text-dark-blue-400" }, student.name)), /* @__PURE__ */ React__namespace.createElement("div", { className: "flex items-center gap-4 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm" }, student.avatar ? /* @__PURE__ */ React__namespace.createElement("img", { src: student.avatar, className: "h-12 w-12 shrink-0 rounded-full object-cover", alt: "" }) : /* @__PURE__ */ React__namespace.createElement("div", { className: "flex h-12 w-12 shrink-0 items-center justify-center rounded-full bg-gradient-to-br from-mint-400 to-dark-blue-400 text-sm font-bold text-white" }, initials(student.name)), /* @__PURE__ */ React__namespace.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React__namespace.createElement("div", { className: "text-lg font-extrabold text-dark-blue-400" }, student.name), /* @__PURE__ */ React__namespace.createElement("div", { className: "mt-0.5 text-xs text-slate-400" }, student.email, props.context ? ` \xB7 ${props.context}` : "")), /* @__PURE__ */ React__namespace.createElement("div", { className: "shrink-0 rounded-full bg-slate-100 px-3 py-1 text-xs font-bold text-slate-500" }, student.submissions.length, " file", student.submissions.length === 1 ? "" : "s")), /* @__PURE__ */ React__namespace.createElement("div", { className: "rounded-2xl border border-slate-200 bg-white p-5 shadow-sm" }, /* @__PURE__ */ React__namespace.createElement("div", { className: "text-sm font-extrabold text-dark-blue-400" }, "Files"), /* @__PURE__ */ React__namespace.createElement("div", { className: "mt-2" }, /* @__PURE__ */ React__namespace.createElement(Stack, { items: student.submissions }))), /* @__PURE__ */ React__namespace.createElement("div", { className: "flex gap-3" }, /* @__PURE__ */ React__namespace.createElement(
3469
+ return /* @__PURE__ */ React__namespace.createElement("div", { className: "flex w-full flex-col gap-4 px-4 py-8" }, /* @__PURE__ */ React__namespace.createElement("div", { className: "flex items-center gap-2 text-xs text-slate-400" }, /* @__PURE__ */ React__namespace.createElement("button", { onClick: props.onBack, className: "font-bold text-sky-blue-400 hover:underline" }, "File Locker"), /* @__PURE__ */ React__namespace.createElement("span", null, "/"), /* @__PURE__ */ React__namespace.createElement("span", { className: "font-bold text-dark-blue-400" }, student.name)), /* @__PURE__ */ React__namespace.createElement("div", { className: "flex items-center gap-4 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm" }, student.avatar ? /* @__PURE__ */ React__namespace.createElement("img", { src: student.avatar, className: "h-12 w-12 shrink-0 rounded-full object-cover", alt: "" }) : /* @__PURE__ */ React__namespace.createElement("div", { className: "flex h-12 w-12 shrink-0 items-center justify-center rounded-full bg-gradient-to-br from-mint-400 to-dark-blue-400 text-sm font-bold text-white" }, initials(student.name)), /* @__PURE__ */ React__namespace.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React__namespace.createElement("div", { className: "text-lg font-extrabold text-dark-blue-400" }, student.name), /* @__PURE__ */ React__namespace.createElement("div", { className: "mt-0.5 text-xs text-slate-400" }, student.email, props.context ? ` \xB7 ${props.context}` : "")), /* @__PURE__ */ React__namespace.createElement("div", { className: "shrink-0 rounded-full bg-slate-100 px-3 py-1 text-xs font-bold text-slate-500" }, student.submissions.length, " file", student.submissions.length === 1 ? "" : "s")), /* @__PURE__ */ React__namespace.createElement("div", { className: "rounded-2xl border border-slate-200 bg-white p-5 shadow-sm" }, /* @__PURE__ */ React__namespace.createElement("div", { className: "text-sm font-extrabold text-dark-blue-400" }, "Files"), /* @__PURE__ */ React__namespace.createElement("div", { className: "mt-2" }, /* @__PURE__ */ React__namespace.createElement(
3470
+ Stack,
3471
+ {
3472
+ items: student.submissions,
3473
+ onBadgeClick: props.onBadgeClick,
3474
+ onLessonClick: props.onLessonClick,
3475
+ onPathwayClick: props.onPathwayClick
3476
+ }
3477
+ ))), /* @__PURE__ */ React__namespace.createElement("div", { className: "flex gap-3" }, /* @__PURE__ */ React__namespace.createElement(
3469
3478
  "button",
3470
3479
  {
3471
3480
  onClick: () => props.onNav(-1),
@@ -3556,42 +3565,143 @@ const TABS = [
3556
3565
  { value: "badges", label: "By badge" },
3557
3566
  { value: "lessons", label: "By lesson" }
3558
3567
  ];
3568
+ const SEARCH_PLACEHOLDER = "Search by student, pathway, badge, or lesson";
3559
3569
  const countFiles = (students) => students.reduce((acc, s) => {
3560
3570
  var _a;
3561
3571
  return acc + (((_a = s.submissions) == null ? void 0 : _a.length) || 0);
3562
3572
  }, 0);
3573
+ const matchesSearch = (student, term) => {
3574
+ if (!term)
3575
+ return true;
3576
+ if (student.name.toLowerCase().includes(term))
3577
+ return true;
3578
+ return (student.submissions || []).some(
3579
+ (sub) => {
3580
+ var _a, _b, _c;
3581
+ return ((_a = sub.badgeName) == null ? void 0 : _a.toLowerCase().includes(term)) || ((_b = sub.lessonName) == null ? void 0 : _b.toLowerCase().includes(term)) || ((_c = sub.pathwayName) == null ? void 0 : _c.toLowerCase().includes(term));
3582
+ }
3583
+ );
3584
+ };
3563
3585
  const GroupCard = (props) => {
3564
3586
  const [open, setOpen] = React__namespace.useState(false);
3565
- return /* @__PURE__ */ React__namespace.createElement("div", { className: "overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm" }, /* @__PURE__ */ React__namespace.createElement("div", { onClick: () => setOpen(!open), className: "flex cursor-pointer items-center gap-3.5 p-4" }, /* @__PURE__ */ React__namespace.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React__namespace.createElement("div", { className: "text-sm font-extrabold text-dark-blue-400" }, props.title), props.description && /* @__PURE__ */ React__namespace.createElement("div", { className: "mt-1 text-xs leading-relaxed text-slate-500" }, props.description)), /* @__PURE__ */ React__namespace.createElement("div", { className: "shrink-0 rounded-full bg-mint-100 px-2.5 py-1 text-xs font-bold text-dark-blue-400" }, props.count), /* @__PURE__ */ React__namespace.createElement(icons.IconChevronDown, { size: 15, stroke: 2.3, className: `shrink-0 text-slate-400 transition-transform ${open ? "rotate-180" : ""}` })), open && /* @__PURE__ */ React__namespace.createElement("div", { className: "border-t border-slate-100 p-4" }, props.children));
3587
+ return /* @__PURE__ */ React__namespace.createElement("div", { className: "overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm" }, /* @__PURE__ */ React__namespace.createElement("div", { onClick: () => setOpen(!open), className: "flex cursor-pointer items-center gap-3.5 p-4" }, /* @__PURE__ */ React__namespace.createElement("div", { className: "min-w-0 flex-1" }, props.onTitleClick ? /* @__PURE__ */ React__namespace.createElement(
3588
+ "button",
3589
+ {
3590
+ type: "button",
3591
+ onClick: (e) => {
3592
+ e.stopPropagation();
3593
+ props.onTitleClick();
3594
+ },
3595
+ className: "text-left text-sm font-extrabold text-sky-blue-400 hover:underline"
3596
+ },
3597
+ props.title
3598
+ ) : /* @__PURE__ */ React__namespace.createElement("div", { className: "text-sm font-extrabold text-dark-blue-400" }, props.title), props.description && /* @__PURE__ */ React__namespace.createElement("div", { className: "mt-1 text-xs leading-relaxed text-slate-500" }, props.description)), /* @__PURE__ */ React__namespace.createElement("div", { className: "shrink-0 rounded-full bg-mint-100 px-2.5 py-1 text-xs font-bold text-dark-blue-400" }, props.count), /* @__PURE__ */ React__namespace.createElement(icons.IconChevronDown, { size: 15, stroke: 2.3, className: `shrink-0 text-slate-400 transition-transform ${open ? "rotate-180" : ""}` })), open && /* @__PURE__ */ React__namespace.createElement("div", { className: "border-t border-slate-100 p-4" }, props.children));
3566
3599
  };
3567
3600
  const PathwayGroups = (props) => {
3568
3601
  const { byPathway } = useFilteredStudents(props.students);
3569
- return /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-col gap-3" }, props.pathways.map((p) => {
3570
- const filtered = byPathway(p.pathwayId, props.badges);
3571
- return /* @__PURE__ */ React__namespace.createElement(GroupCard, { key: p.pathwayId, title: p.title, description: p.description, count: countFiles(filtered) }, /* @__PURE__ */ React__namespace.createElement(Table, { loading: props.loading, items: filtered, hidePathway: true, onReview: (item) => props.onReview(item, filtered, p.title) }));
3572
- }));
3602
+ const term = props.search.trim().toLowerCase();
3603
+ const groups = props.pathways.map((p) => {
3604
+ const base = byPathway(p.pathwayId, props.badges);
3605
+ const ownNameMatches = !term || p.title.toLowerCase().includes(term);
3606
+ const filtered = ownNameMatches ? base : base.filter((s) => matchesSearch(s, term));
3607
+ return { pathway: p, filtered };
3608
+ }).filter((g) => !term || g.filtered.length > 0);
3609
+ if (term && groups.length === 0) {
3610
+ return /* @__PURE__ */ React__namespace.createElement("p", { className: "text-sm text-slate-400" }, "No matching entries.");
3611
+ }
3612
+ return /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-col gap-3" }, groups.map(({ pathway: p, filtered }) => /* @__PURE__ */ React__namespace.createElement(
3613
+ GroupCard,
3614
+ {
3615
+ key: p.pathwayId,
3616
+ title: p.title,
3617
+ description: p.description,
3618
+ count: countFiles(filtered),
3619
+ onTitleClick: props.onPathwayClick ? () => props.onPathwayClick(p.pathwayId) : void 0
3620
+ },
3621
+ /* @__PURE__ */ React__namespace.createElement(
3622
+ Table,
3623
+ {
3624
+ loading: props.loading,
3625
+ items: filtered,
3626
+ hidePathway: true,
3627
+ onReview: (item) => props.onReview(item, filtered, p.title),
3628
+ onBadgeClick: props.onBadgeClick,
3629
+ onLessonClick: props.onLessonClick
3630
+ }
3631
+ )
3632
+ )));
3573
3633
  };
3574
3634
  const BadgeGroups = (props) => {
3575
3635
  const { byBadge } = useFilteredStudents(props.students);
3576
- return /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-col gap-3" }, props.badges.map((b) => {
3577
- const filtered = byBadge(b.badgeId);
3636
+ const term = props.search.trim().toLowerCase();
3637
+ const groups = props.badges.map((b) => {
3638
+ const base = byBadge(b.badgeId);
3639
+ const ownNameMatches = !term || b.displayName.toLowerCase().includes(term);
3640
+ const filtered = ownNameMatches ? base : base.filter((s) => matchesSearch(s, term));
3641
+ return { badge: b, filtered };
3642
+ }).filter((g) => !term || g.filtered.length > 0);
3643
+ if (term && groups.length === 0) {
3644
+ return /* @__PURE__ */ React__namespace.createElement("p", { className: "text-sm text-slate-400" }, "No matching entries.");
3645
+ }
3646
+ return /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-col gap-3" }, groups.map(({ badge: b, filtered }) => {
3578
3647
  const pathwayTitle = props.pathwayNameForBadge[b.badgeId];
3579
- return /* @__PURE__ */ React__namespace.createElement(GroupCard, { key: b.badgeId, title: b.displayName, description: pathwayTitle ? `${pathwayTitle} pathway` : void 0, count: countFiles(filtered) }, /* @__PURE__ */ React__namespace.createElement(Table, { loading: props.loading, items: filtered, hideBadge: true, hidePathway: true, onReview: (item) => props.onReview(item, filtered, b.displayName) }));
3648
+ return /* @__PURE__ */ React__namespace.createElement(
3649
+ GroupCard,
3650
+ {
3651
+ key: b.badgeId,
3652
+ title: b.displayName,
3653
+ description: pathwayTitle ? `${pathwayTitle} pathway` : void 0,
3654
+ count: countFiles(filtered),
3655
+ onTitleClick: props.onBadgeClick ? () => props.onBadgeClick(b.badgeId) : void 0
3656
+ },
3657
+ /* @__PURE__ */ React__namespace.createElement(
3658
+ Table,
3659
+ {
3660
+ loading: props.loading,
3661
+ items: filtered,
3662
+ hideBadge: true,
3663
+ hidePathway: true,
3664
+ onReview: (item) => props.onReview(item, filtered, b.displayName),
3665
+ onLessonClick: props.onLessonClick
3666
+ }
3667
+ )
3668
+ );
3580
3669
  }));
3581
3670
  };
3582
3671
  const LessonGroups = (props) => {
3583
3672
  const { byLesson } = useFilteredStudents(props.students);
3584
- return /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-col gap-3" }, props.lessons.map((l) => {
3585
- const filtered = byLesson(l.lessonName);
3673
+ const term = props.search.trim().toLowerCase();
3674
+ const groups = props.lessons.map((l) => {
3675
+ const base = byLesson(l.lessonName);
3676
+ const ownNameMatches = !term || l.lessonName.toLowerCase().includes(term);
3677
+ const filtered = ownNameMatches ? base : base.filter((s) => matchesSearch(s, term));
3678
+ return { lesson: l, filtered };
3679
+ }).filter((g) => !term || g.filtered.length > 0);
3680
+ if (term && groups.length === 0) {
3681
+ return /* @__PURE__ */ React__namespace.createElement("p", { className: "text-sm text-slate-400" }, "No matching entries.");
3682
+ }
3683
+ return /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-col gap-3" }, groups.map(({ lesson: l, filtered }) => {
3586
3684
  const badge = props.badgeForLesson[l.lessonId];
3587
3685
  const pathwayTitle = badge ? props.pathwayNameForBadge[badge.badgeId] : void 0;
3588
3686
  const description = badge ? pathwayTitle ? `${badge.displayName} \xB7 ${pathwayTitle} pathway` : badge.displayName : void 0;
3589
- return /* @__PURE__ */ React__namespace.createElement(GroupCard, { key: l.lessonId, title: l.lessonName, description, count: countFiles(filtered) }, /* @__PURE__ */ React__namespace.createElement(Table, { loading: props.loading, items: filtered, hideBadge: true, hideLesson: true, hidePathway: true, onReview: (item) => props.onReview(item, filtered, l.lessonName) }));
3687
+ return /* @__PURE__ */ React__namespace.createElement(
3688
+ GroupCard,
3689
+ {
3690
+ key: l.lessonId,
3691
+ title: l.lessonName,
3692
+ description,
3693
+ count: countFiles(filtered),
3694
+ onTitleClick: props.onLessonClick ? () => props.onLessonClick(l.lessonId) : void 0
3695
+ },
3696
+ /* @__PURE__ */ React__namespace.createElement(Table, { loading: props.loading, items: filtered, hideBadge: true, hideLesson: true, hidePathway: true, onReview: (item) => props.onReview(item, filtered, l.lessonName) })
3697
+ );
3590
3698
  }));
3591
3699
  };
3592
3700
  const FileLocker = (props) => {
3593
3701
  const [tab, setTab] = React__namespace.useState("students");
3594
3702
  const [reviewing, setReviewing] = React__namespace.useState(null);
3703
+ const [search, setSearch] = React__namespace.useState("");
3704
+ const searchLower = search.trim().toLowerCase();
3595
3705
  const numberOfFiles = countFiles(props.students);
3596
3706
  const { thisWeek, thisMonth } = React__namespace.useMemo(() => {
3597
3707
  const now = new Date();
@@ -3622,6 +3732,17 @@ const FileLocker = (props) => {
3622
3732
  });
3623
3733
  return map;
3624
3734
  }, [props.badges, props.pathways]);
3735
+ const pathwayIdForBadge = React__namespace.useMemo(() => {
3736
+ const map = {};
3737
+ (props.badges || []).forEach((b) => {
3738
+ if (!Array.isArray(b.categories))
3739
+ return;
3740
+ const pathway = (props.pathways || []).find((p) => b.categories.some((c) => c.startsWith(p.pathwayId)));
3741
+ if (pathway)
3742
+ map[b.badgeId] = pathway.pathwayId;
3743
+ });
3744
+ return map;
3745
+ }, [props.badges, props.pathways]);
3625
3746
  const badgeForLesson = React__namespace.useMemo(() => {
3626
3747
  const map = {};
3627
3748
  (props.badges || []).forEach((b) => {
@@ -3636,9 +3757,12 @@ const FileLocker = (props) => {
3636
3757
  }, [props.badges]);
3637
3758
  const enrichedStudents = React__namespace.useMemo(() => {
3638
3759
  return props.students.map((s) => __spreadProps(__spreadValues({}, s), {
3639
- submissions: (s.submissions || []).map((sub) => __spreadProps(__spreadValues({}, sub), { pathwayName: pathwayNameForBadge[sub.badgeId] || "" }))
3760
+ submissions: (s.submissions || []).map((sub) => __spreadProps(__spreadValues({}, sub), {
3761
+ pathwayName: pathwayNameForBadge[sub.badgeId] || "",
3762
+ pathwayId: pathwayIdForBadge[sub.badgeId]
3763
+ }))
3640
3764
  }));
3641
- }, [props.students, pathwayNameForBadge]);
3765
+ }, [props.students, pathwayNameForBadge, pathwayIdForBadge]);
3642
3766
  const { byPathway } = useFilteredStudents(enrichedStudents);
3643
3767
  const mostActivePathway = React__namespace.useMemo(() => {
3644
3768
  return (props.pathways || []).reduce((best, p) => {
@@ -3646,6 +3770,10 @@ const FileLocker = (props) => {
3646
3770
  return count > 0 && (!best || count > best.count) ? { title: p.title, count } : best;
3647
3771
  }, null);
3648
3772
  }, [props.pathways, props.badges, byPathway]);
3773
+ const searchedStudents = React__namespace.useMemo(
3774
+ () => searchLower ? enrichedStudents.filter((s) => matchesSearch(s, searchLower)) : enrichedStudents,
3775
+ [enrichedStudents, searchLower]
3776
+ );
3649
3777
  const onReview = (item, list, context) => setReviewing({ student: item, list, context });
3650
3778
  const onNav = (dir) => {
3651
3779
  if (!reviewing)
@@ -3663,7 +3791,10 @@ const FileLocker = (props) => {
3663
3791
  student: reviewing.student,
3664
3792
  context: reviewing.context,
3665
3793
  onBack: () => setReviewing(null),
3666
- onNav
3794
+ onNav,
3795
+ onBadgeClick: props.onBadgeClick,
3796
+ onLessonClick: props.onLessonClick,
3797
+ onPathwayClick: props.onPathwayClick
3667
3798
  }
3668
3799
  );
3669
3800
  }
@@ -3690,7 +3821,66 @@ const FileLocker = (props) => {
3690
3821
  className: `rounded-lg px-4 py-2 text-xs font-bold ${tab === t.value ? "bg-sky-blue-400/20 text-dark-blue-400" : "text-slate-400 hover:text-slate-600"}`
3691
3822
  },
3692
3823
  t.label
3693
- ))), !props.trial && tab === "students" && /* @__PURE__ */ React__namespace.createElement(Table, { loading: props.loading, items: enrichedStudents, onReview: (item) => onReview(item, enrichedStudents) }), !props.trial && tab === "pathways" && /* @__PURE__ */ React__namespace.createElement(PathwayGroups, { students: enrichedStudents, loading: props.loading, pathways: props.pathways || [], badges: props.badges || [], onReview }), !props.trial && tab === "badges" && /* @__PURE__ */ React__namespace.createElement(BadgeGroups, { students: enrichedStudents, loading: props.loading, badges: props.badges || [], pathwayNameForBadge, onReview }), !props.trial && tab === "lessons" && /* @__PURE__ */ React__namespace.createElement(LessonGroups, { students: enrichedStudents, loading: props.loading, lessons: props.lessons, badgeForLesson, pathwayNameForBadge, onReview }));
3824
+ ))), !props.trial && /* @__PURE__ */ React__namespace.createElement("div", { className: "flex items-center gap-2.5 rounded-xl border border-slate-200 bg-white px-4 py-3 shadow-sm" }, /* @__PURE__ */ React__namespace.createElement(icons.IconSearch, { size: 15, stroke: 1.75, className: "text-slate-400" }), /* @__PURE__ */ React__namespace.createElement(
3825
+ "input",
3826
+ {
3827
+ type: "text",
3828
+ value: search,
3829
+ onChange: (e) => setSearch(e.target.value),
3830
+ placeholder: SEARCH_PLACEHOLDER,
3831
+ className: "flex-1 border-none bg-transparent text-sm text-dark-blue-400 outline-none placeholder:text-slate-400"
3832
+ }
3833
+ )), !props.trial && tab === "students" && /* @__PURE__ */ React__namespace.createElement(
3834
+ Table,
3835
+ {
3836
+ loading: props.loading,
3837
+ items: searchedStudents,
3838
+ onReview: (item) => onReview(item, searchedStudents),
3839
+ onBadgeClick: props.onBadgeClick,
3840
+ onLessonClick: props.onLessonClick,
3841
+ onPathwayClick: props.onPathwayClick
3842
+ }
3843
+ ), !props.trial && tab === "pathways" && /* @__PURE__ */ React__namespace.createElement(
3844
+ PathwayGroups,
3845
+ {
3846
+ students: enrichedStudents,
3847
+ loading: props.loading,
3848
+ search,
3849
+ pathways: props.pathways || [],
3850
+ badges: props.badges || [],
3851
+ onReview,
3852
+ onBadgeClick: props.onBadgeClick,
3853
+ onLessonClick: props.onLessonClick,
3854
+ onPathwayClick: props.onPathwayClick
3855
+ }
3856
+ ), !props.trial && tab === "badges" && /* @__PURE__ */ React__namespace.createElement(
3857
+ BadgeGroups,
3858
+ {
3859
+ students: enrichedStudents,
3860
+ loading: props.loading,
3861
+ search,
3862
+ badges: props.badges || [],
3863
+ pathwayNameForBadge,
3864
+ onReview,
3865
+ onBadgeClick: props.onBadgeClick,
3866
+ onLessonClick: props.onLessonClick,
3867
+ onPathwayClick: props.onPathwayClick
3868
+ }
3869
+ ), !props.trial && tab === "lessons" && /* @__PURE__ */ React__namespace.createElement(
3870
+ LessonGroups,
3871
+ {
3872
+ students: enrichedStudents,
3873
+ loading: props.loading,
3874
+ search,
3875
+ lessons: props.lessons,
3876
+ badgeForLesson,
3877
+ pathwayNameForBadge,
3878
+ onReview,
3879
+ onBadgeClick: props.onBadgeClick,
3880
+ onLessonClick: props.onLessonClick,
3881
+ onPathwayClick: props.onPathwayClick
3882
+ }
3883
+ ));
3694
3884
  };
3695
3885
 
3696
3886
  Object.defineProperty(exports, 'hideNotification', {