@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.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { useState, useMemo } from 'react';
3
- import { IconChevronRight, IconVideo, IconSwitchHorizontal, IconLogout, IconHome2, IconGauge, IconCategory2, IconRoute, IconAlbum, IconLambda, IconClipboard, IconBuilding, IconBooks, IconBackpack, IconClipboardCopy, IconTableExport, IconCaretDown, IconCaretUp, IconChevronDown, IconArrowLeft, IconSearch, IconCheck, IconTrash, IconPlus, IconLink, IconDownload, IconCloudUpload, IconInfoCircle, IconPlaylistAdd, IconPointer, IconX, IconScribble, IconSchool, IconPodium, IconBriefcase, IconPresentation, IconNews, IconTools, IconBrandInstagram, IconBrandLinkedin, IconBrandFacebook, IconExternalLink, IconChevronLeft } from '@tabler/icons';
3
+ import { IconChevronRight, IconVideo, IconSwitchHorizontal, IconLogout, IconHome2, IconGauge, IconCategory2, IconRoute, IconAlbum, IconLambda, IconClipboard, IconBuilding, IconBooks, IconBackpack, IconClipboardCopy, IconTableExport, IconCaretDown, IconCaretUp, IconChevronDown, IconArrowLeft, IconSearch, IconCheck, IconTrash, IconPlus, IconLink, IconDownload, IconCloudUpload, IconInfoCircle, IconPlaylistAdd, IconPointer, IconX, IconScribble, IconSchool, IconPodium, IconBriefcase, IconPresentation, IconNews, IconTools, IconBrandInstagram, IconBrandLinkedin, IconBrandFacebook, IconChevronLeft } from '@tabler/icons';
4
4
  import { Avatar, createStyles, Modal, Center, Loader, Container, Group, Button, Title, Text, Image, Drawer, Divider, TextInput, Tooltip, ActionIcon, SimpleGrid, Badge as Badge$1, Card, UnstyledButton, Overlay, Stack as Stack$5, ThemeIcon, Checkbox, Autocomplete, createEmotionCache, MantineProvider, AppShell } from '@mantine/core';
5
5
  import { Link } from 'react-router-dom';
6
6
  import 'external-svg-loader';
@@ -3335,31 +3335,21 @@ const SplitButton = (props) => {
3335
3335
  };
3336
3336
 
3337
3337
  function Stack(props) {
3338
- const { items, hideBadge, hideLesson, hidePathway } = props;
3338
+ const { items, hideBadge, hideLesson, hidePathway, onBadgeClick, onLessonClick, onPathwayClick } = props;
3339
3339
  if (!items.length)
3340
3340
  return null;
3341
3341
  const columns = [
3342
- "minmax(0,2fr)",
3343
3342
  !hidePathway && "minmax(0,1.3fr)",
3344
- !hideLesson && "minmax(0,1.3fr)",
3345
3343
  !hideBadge && "minmax(0,1.3fr)",
3344
+ !hideLesson && "minmax(0,1.3fr)",
3345
+ "minmax(0,2fr)",
3346
3346
  "auto"
3347
3347
  ].filter(Boolean).join(" ");
3348
3348
  const headerCell = "border-b border-slate-100 pb-2 text-[10.5px] font-extrabold uppercase tracking-wide text-slate-400";
3349
3349
  const dataCell = "py-3";
3350
- return /* @__PURE__ */ React.createElement("div", { className: "grid items-center gap-x-3", style: { gridTemplateColumns: columns } }, /* @__PURE__ */ React.createElement("div", { className: headerCell }, "Question"), !hidePathway && /* @__PURE__ */ React.createElement("div", { className: headerCell }, "Pathway"), !hideLesson && /* @__PURE__ */ React.createElement("div", { className: headerCell }, "Lesson"), !hideBadge && /* @__PURE__ */ React.createElement("div", { className: headerCell }, "Badge"), /* @__PURE__ */ React.createElement("div", { className: headerCell }), items.map((row, i) => {
3350
+ return /* @__PURE__ */ React.createElement("div", { className: "grid items-center gap-x-3", style: { gridTemplateColumns: columns } }, !hidePathway && /* @__PURE__ */ React.createElement("div", { className: headerCell }, "Pathway"), !hideBadge && /* @__PURE__ */ React.createElement("div", { className: headerCell }, "Badge"), !hideLesson && /* @__PURE__ */ React.createElement("div", { className: headerCell }, "Lesson"), /* @__PURE__ */ React.createElement("div", { className: headerCell }, "Question"), /* @__PURE__ */ React.createElement("div", { className: headerCell }), items.map((row, i) => {
3351
3351
  const rowBorder = i > 0 ? "border-t border-slate-100" : "";
3352
- return /* @__PURE__ */ React.createElement(React.Fragment, { key: i }, /* @__PURE__ */ React.createElement("div", { className: `${dataCell} ${rowBorder} text-xs font-semibold text-dark-blue-400` }, row.question), !hidePathway && /* @__PURE__ */ React.createElement("div", { className: `${dataCell} ${rowBorder} truncate text-[11px] text-slate-500` }, row.pathwayName || "\u2014"), !hideLesson && /* @__PURE__ */ React.createElement("div", { className: `${dataCell} ${rowBorder} truncate text-[11px] text-slate-500` }, row.lessonName), !hideBadge && /* @__PURE__ */ React.createElement("div", { className: `${dataCell} ${rowBorder} truncate text-[11px] text-slate-500` }, row.badgeName), /* @__PURE__ */ React.createElement("div", { className: `${dataCell} ${rowBorder} flex shrink-0 gap-2` }, /* @__PURE__ */ React.createElement(
3353
- "a",
3354
- {
3355
- href: row.link,
3356
- target: "_blank",
3357
- rel: "noopener noreferrer",
3358
- 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"
3359
- },
3360
- /* @__PURE__ */ React.createElement(IconExternalLink, { size: 12, stroke: 2 }),
3361
- "Preview"
3362
- ), /* @__PURE__ */ React.createElement(
3352
+ return /* @__PURE__ */ React.createElement(React.Fragment, { key: i }, !hidePathway && (row.pathwayId && onPathwayClick ? /* @__PURE__ */ React.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.createElement("div", { className: `${dataCell} ${rowBorder} truncate text-[11px] text-slate-500` }, row.pathwayName || "\u2014")), !hideBadge && (row.badgeId && onBadgeClick ? /* @__PURE__ */ React.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.createElement("div", { className: `${dataCell} ${rowBorder} truncate text-[11px] text-slate-500` }, row.badgeName)), !hideLesson && (row.lessonId && onLessonClick ? /* @__PURE__ */ React.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.createElement("div", { className: `${dataCell} ${rowBorder} truncate text-[11px] text-slate-500` }, row.lessonName)), /* @__PURE__ */ React.createElement("div", { className: `${dataCell} ${rowBorder} text-xs font-semibold text-dark-blue-400` }, row.question), /* @__PURE__ */ React.createElement("div", { className: `${dataCell} ${rowBorder} flex shrink-0 gap-2` }, /* @__PURE__ */ React.createElement(
3363
3353
  "a",
3364
3354
  {
3365
3355
  href: row.link,
@@ -3438,14 +3428,33 @@ function Table(props) {
3438
3428
  "Review Submission"
3439
3429
  )),
3440
3430
  /* @__PURE__ */ React.createElement(IconChevronRight, { size: 16, stroke: 2, className: `w-4 shrink-0 text-slate-300 transition-transform ${isOpen ? "rotate-90" : ""}` })
3441
- ), isOpen && /* @__PURE__ */ React.createElement("div", { className: "border-t border-slate-100 px-4 py-3" }, /* @__PURE__ */ React.createElement(Stack, { items: row.submissions, hideBadge: props.hideBadge, hideLesson: props.hideLesson, hidePathway: props.hidePathway })));
3431
+ ), isOpen && /* @__PURE__ */ React.createElement("div", { className: "border-t border-slate-100 px-4 py-3" }, /* @__PURE__ */ React.createElement(
3432
+ Stack,
3433
+ {
3434
+ items: row.submissions,
3435
+ hideBadge: props.hideBadge,
3436
+ hideLesson: props.hideLesson,
3437
+ hidePathway: props.hidePathway,
3438
+ onBadgeClick: props.onBadgeClick,
3439
+ onLessonClick: props.onLessonClick,
3440
+ onPathwayClick: props.onPathwayClick
3441
+ }
3442
+ )));
3442
3443
  }));
3443
3444
  }
3444
3445
 
3445
3446
  const initials = (name) => name.split(" ").map((w) => w[0]).filter(Boolean).slice(0, 2).join("").toUpperCase();
3446
3447
  function SubmissionDetail(props) {
3447
3448
  const { student } = props;
3448
- return /* @__PURE__ */ React.createElement("div", { className: "flex w-full flex-col gap-4 px-4 py-8" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 text-xs text-slate-400" }, /* @__PURE__ */ React.createElement("button", { onClick: props.onBack, className: "font-bold text-sky-blue-400 hover:underline" }, "File Locker"), /* @__PURE__ */ React.createElement("span", null, "/"), /* @__PURE__ */ React.createElement("span", { className: "font-bold text-dark-blue-400" }, student.name)), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-4 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm" }, student.avatar ? /* @__PURE__ */ React.createElement("img", { src: student.avatar, className: "h-12 w-12 shrink-0 rounded-full object-cover", alt: "" }) : /* @__PURE__ */ React.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.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "text-lg font-extrabold text-dark-blue-400" }, student.name), /* @__PURE__ */ React.createElement("div", { className: "mt-0.5 text-xs text-slate-400" }, student.email, props.context ? ` \xB7 ${props.context}` : "")), /* @__PURE__ */ React.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.createElement("div", { className: "rounded-2xl border border-slate-200 bg-white p-5 shadow-sm" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-extrabold text-dark-blue-400" }, "Files"), /* @__PURE__ */ React.createElement("div", { className: "mt-2" }, /* @__PURE__ */ React.createElement(Stack, { items: student.submissions }))), /* @__PURE__ */ React.createElement("div", { className: "flex gap-3" }, /* @__PURE__ */ React.createElement(
3449
+ return /* @__PURE__ */ React.createElement("div", { className: "flex w-full flex-col gap-4 px-4 py-8" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 text-xs text-slate-400" }, /* @__PURE__ */ React.createElement("button", { onClick: props.onBack, className: "font-bold text-sky-blue-400 hover:underline" }, "File Locker"), /* @__PURE__ */ React.createElement("span", null, "/"), /* @__PURE__ */ React.createElement("span", { className: "font-bold text-dark-blue-400" }, student.name)), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-4 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm" }, student.avatar ? /* @__PURE__ */ React.createElement("img", { src: student.avatar, className: "h-12 w-12 shrink-0 rounded-full object-cover", alt: "" }) : /* @__PURE__ */ React.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.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "text-lg font-extrabold text-dark-blue-400" }, student.name), /* @__PURE__ */ React.createElement("div", { className: "mt-0.5 text-xs text-slate-400" }, student.email, props.context ? ` \xB7 ${props.context}` : "")), /* @__PURE__ */ React.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.createElement("div", { className: "rounded-2xl border border-slate-200 bg-white p-5 shadow-sm" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-extrabold text-dark-blue-400" }, "Files"), /* @__PURE__ */ React.createElement("div", { className: "mt-2" }, /* @__PURE__ */ React.createElement(
3450
+ Stack,
3451
+ {
3452
+ items: student.submissions,
3453
+ onBadgeClick: props.onBadgeClick,
3454
+ onLessonClick: props.onLessonClick,
3455
+ onPathwayClick: props.onPathwayClick
3456
+ }
3457
+ ))), /* @__PURE__ */ React.createElement("div", { className: "flex gap-3" }, /* @__PURE__ */ React.createElement(
3449
3458
  "button",
3450
3459
  {
3451
3460
  onClick: () => props.onNav(-1),
@@ -3536,42 +3545,143 @@ const TABS = [
3536
3545
  { value: "badges", label: "By badge" },
3537
3546
  { value: "lessons", label: "By lesson" }
3538
3547
  ];
3548
+ const SEARCH_PLACEHOLDER = "Search by student, pathway, badge, or lesson";
3539
3549
  const countFiles = (students) => students.reduce((acc, s) => {
3540
3550
  var _a;
3541
3551
  return acc + (((_a = s.submissions) == null ? void 0 : _a.length) || 0);
3542
3552
  }, 0);
3553
+ const matchesSearch = (student, term) => {
3554
+ if (!term)
3555
+ return true;
3556
+ if (student.name.toLowerCase().includes(term))
3557
+ return true;
3558
+ return (student.submissions || []).some(
3559
+ (sub) => {
3560
+ var _a, _b, _c;
3561
+ 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));
3562
+ }
3563
+ );
3564
+ };
3543
3565
  const GroupCard = (props) => {
3544
3566
  const [open, setOpen] = React.useState(false);
3545
- return /* @__PURE__ */ React.createElement("div", { className: "overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm" }, /* @__PURE__ */ React.createElement("div", { onClick: () => setOpen(!open), className: "flex cursor-pointer items-center gap-3.5 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0 flex-1" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-extrabold text-dark-blue-400" }, props.title), props.description && /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs leading-relaxed text-slate-500" }, props.description)), /* @__PURE__ */ React.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.createElement(IconChevronDown, { size: 15, stroke: 2.3, className: `shrink-0 text-slate-400 transition-transform ${open ? "rotate-180" : ""}` })), open && /* @__PURE__ */ React.createElement("div", { className: "border-t border-slate-100 p-4" }, props.children));
3567
+ return /* @__PURE__ */ React.createElement("div", { className: "overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm" }, /* @__PURE__ */ React.createElement("div", { onClick: () => setOpen(!open), className: "flex cursor-pointer items-center gap-3.5 p-4" }, /* @__PURE__ */ React.createElement("div", { className: "min-w-0 flex-1" }, props.onTitleClick ? /* @__PURE__ */ React.createElement(
3568
+ "button",
3569
+ {
3570
+ type: "button",
3571
+ onClick: (e) => {
3572
+ e.stopPropagation();
3573
+ props.onTitleClick();
3574
+ },
3575
+ className: "text-left text-sm font-extrabold text-sky-blue-400 hover:underline"
3576
+ },
3577
+ props.title
3578
+ ) : /* @__PURE__ */ React.createElement("div", { className: "text-sm font-extrabold text-dark-blue-400" }, props.title), props.description && /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-xs leading-relaxed text-slate-500" }, props.description)), /* @__PURE__ */ React.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.createElement(IconChevronDown, { size: 15, stroke: 2.3, className: `shrink-0 text-slate-400 transition-transform ${open ? "rotate-180" : ""}` })), open && /* @__PURE__ */ React.createElement("div", { className: "border-t border-slate-100 p-4" }, props.children));
3546
3579
  };
3547
3580
  const PathwayGroups = (props) => {
3548
3581
  const { byPathway } = useFilteredStudents(props.students);
3549
- return /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-3" }, props.pathways.map((p) => {
3550
- const filtered = byPathway(p.pathwayId, props.badges);
3551
- return /* @__PURE__ */ React.createElement(GroupCard, { key: p.pathwayId, title: p.title, description: p.description, count: countFiles(filtered) }, /* @__PURE__ */ React.createElement(Table, { loading: props.loading, items: filtered, hidePathway: true, onReview: (item) => props.onReview(item, filtered, p.title) }));
3552
- }));
3582
+ const term = props.search.trim().toLowerCase();
3583
+ const groups = props.pathways.map((p) => {
3584
+ const base = byPathway(p.pathwayId, props.badges);
3585
+ const ownNameMatches = !term || p.title.toLowerCase().includes(term);
3586
+ const filtered = ownNameMatches ? base : base.filter((s) => matchesSearch(s, term));
3587
+ return { pathway: p, filtered };
3588
+ }).filter((g) => !term || g.filtered.length > 0);
3589
+ if (term && groups.length === 0) {
3590
+ return /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-400" }, "No matching entries.");
3591
+ }
3592
+ return /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-3" }, groups.map(({ pathway: p, filtered }) => /* @__PURE__ */ React.createElement(
3593
+ GroupCard,
3594
+ {
3595
+ key: p.pathwayId,
3596
+ title: p.title,
3597
+ description: p.description,
3598
+ count: countFiles(filtered),
3599
+ onTitleClick: props.onPathwayClick ? () => props.onPathwayClick(p.pathwayId) : void 0
3600
+ },
3601
+ /* @__PURE__ */ React.createElement(
3602
+ Table,
3603
+ {
3604
+ loading: props.loading,
3605
+ items: filtered,
3606
+ hidePathway: true,
3607
+ onReview: (item) => props.onReview(item, filtered, p.title),
3608
+ onBadgeClick: props.onBadgeClick,
3609
+ onLessonClick: props.onLessonClick
3610
+ }
3611
+ )
3612
+ )));
3553
3613
  };
3554
3614
  const BadgeGroups = (props) => {
3555
3615
  const { byBadge } = useFilteredStudents(props.students);
3556
- return /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-3" }, props.badges.map((b) => {
3557
- const filtered = byBadge(b.badgeId);
3616
+ const term = props.search.trim().toLowerCase();
3617
+ const groups = props.badges.map((b) => {
3618
+ const base = byBadge(b.badgeId);
3619
+ const ownNameMatches = !term || b.displayName.toLowerCase().includes(term);
3620
+ const filtered = ownNameMatches ? base : base.filter((s) => matchesSearch(s, term));
3621
+ return { badge: b, filtered };
3622
+ }).filter((g) => !term || g.filtered.length > 0);
3623
+ if (term && groups.length === 0) {
3624
+ return /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-400" }, "No matching entries.");
3625
+ }
3626
+ return /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-3" }, groups.map(({ badge: b, filtered }) => {
3558
3627
  const pathwayTitle = props.pathwayNameForBadge[b.badgeId];
3559
- return /* @__PURE__ */ React.createElement(GroupCard, { key: b.badgeId, title: b.displayName, description: pathwayTitle ? `${pathwayTitle} pathway` : void 0, count: countFiles(filtered) }, /* @__PURE__ */ React.createElement(Table, { loading: props.loading, items: filtered, hideBadge: true, hidePathway: true, onReview: (item) => props.onReview(item, filtered, b.displayName) }));
3628
+ return /* @__PURE__ */ React.createElement(
3629
+ GroupCard,
3630
+ {
3631
+ key: b.badgeId,
3632
+ title: b.displayName,
3633
+ description: pathwayTitle ? `${pathwayTitle} pathway` : void 0,
3634
+ count: countFiles(filtered),
3635
+ onTitleClick: props.onBadgeClick ? () => props.onBadgeClick(b.badgeId) : void 0
3636
+ },
3637
+ /* @__PURE__ */ React.createElement(
3638
+ Table,
3639
+ {
3640
+ loading: props.loading,
3641
+ items: filtered,
3642
+ hideBadge: true,
3643
+ hidePathway: true,
3644
+ onReview: (item) => props.onReview(item, filtered, b.displayName),
3645
+ onLessonClick: props.onLessonClick
3646
+ }
3647
+ )
3648
+ );
3560
3649
  }));
3561
3650
  };
3562
3651
  const LessonGroups = (props) => {
3563
3652
  const { byLesson } = useFilteredStudents(props.students);
3564
- return /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-3" }, props.lessons.map((l) => {
3565
- const filtered = byLesson(l.lessonName);
3653
+ const term = props.search.trim().toLowerCase();
3654
+ const groups = props.lessons.map((l) => {
3655
+ const base = byLesson(l.lessonName);
3656
+ const ownNameMatches = !term || l.lessonName.toLowerCase().includes(term);
3657
+ const filtered = ownNameMatches ? base : base.filter((s) => matchesSearch(s, term));
3658
+ return { lesson: l, filtered };
3659
+ }).filter((g) => !term || g.filtered.length > 0);
3660
+ if (term && groups.length === 0) {
3661
+ return /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-400" }, "No matching entries.");
3662
+ }
3663
+ return /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-3" }, groups.map(({ lesson: l, filtered }) => {
3566
3664
  const badge = props.badgeForLesson[l.lessonId];
3567
3665
  const pathwayTitle = badge ? props.pathwayNameForBadge[badge.badgeId] : void 0;
3568
3666
  const description = badge ? pathwayTitle ? `${badge.displayName} \xB7 ${pathwayTitle} pathway` : badge.displayName : void 0;
3569
- return /* @__PURE__ */ React.createElement(GroupCard, { key: l.lessonId, title: l.lessonName, description, count: countFiles(filtered) }, /* @__PURE__ */ React.createElement(Table, { loading: props.loading, items: filtered, hideBadge: true, hideLesson: true, hidePathway: true, onReview: (item) => props.onReview(item, filtered, l.lessonName) }));
3667
+ return /* @__PURE__ */ React.createElement(
3668
+ GroupCard,
3669
+ {
3670
+ key: l.lessonId,
3671
+ title: l.lessonName,
3672
+ description,
3673
+ count: countFiles(filtered),
3674
+ onTitleClick: props.onLessonClick ? () => props.onLessonClick(l.lessonId) : void 0
3675
+ },
3676
+ /* @__PURE__ */ React.createElement(Table, { loading: props.loading, items: filtered, hideBadge: true, hideLesson: true, hidePathway: true, onReview: (item) => props.onReview(item, filtered, l.lessonName) })
3677
+ );
3570
3678
  }));
3571
3679
  };
3572
3680
  const FileLocker = (props) => {
3573
3681
  const [tab, setTab] = React.useState("students");
3574
3682
  const [reviewing, setReviewing] = React.useState(null);
3683
+ const [search, setSearch] = React.useState("");
3684
+ const searchLower = search.trim().toLowerCase();
3575
3685
  const numberOfFiles = countFiles(props.students);
3576
3686
  const { thisWeek, thisMonth } = React.useMemo(() => {
3577
3687
  const now = new Date();
@@ -3602,6 +3712,17 @@ const FileLocker = (props) => {
3602
3712
  });
3603
3713
  return map;
3604
3714
  }, [props.badges, props.pathways]);
3715
+ const pathwayIdForBadge = React.useMemo(() => {
3716
+ const map = {};
3717
+ (props.badges || []).forEach((b) => {
3718
+ if (!Array.isArray(b.categories))
3719
+ return;
3720
+ const pathway = (props.pathways || []).find((p) => b.categories.some((c) => c.startsWith(p.pathwayId)));
3721
+ if (pathway)
3722
+ map[b.badgeId] = pathway.pathwayId;
3723
+ });
3724
+ return map;
3725
+ }, [props.badges, props.pathways]);
3605
3726
  const badgeForLesson = React.useMemo(() => {
3606
3727
  const map = {};
3607
3728
  (props.badges || []).forEach((b) => {
@@ -3616,9 +3737,12 @@ const FileLocker = (props) => {
3616
3737
  }, [props.badges]);
3617
3738
  const enrichedStudents = React.useMemo(() => {
3618
3739
  return props.students.map((s) => __spreadProps(__spreadValues({}, s), {
3619
- submissions: (s.submissions || []).map((sub) => __spreadProps(__spreadValues({}, sub), { pathwayName: pathwayNameForBadge[sub.badgeId] || "" }))
3740
+ submissions: (s.submissions || []).map((sub) => __spreadProps(__spreadValues({}, sub), {
3741
+ pathwayName: pathwayNameForBadge[sub.badgeId] || "",
3742
+ pathwayId: pathwayIdForBadge[sub.badgeId]
3743
+ }))
3620
3744
  }));
3621
- }, [props.students, pathwayNameForBadge]);
3745
+ }, [props.students, pathwayNameForBadge, pathwayIdForBadge]);
3622
3746
  const { byPathway } = useFilteredStudents(enrichedStudents);
3623
3747
  const mostActivePathway = React.useMemo(() => {
3624
3748
  return (props.pathways || []).reduce((best, p) => {
@@ -3626,6 +3750,10 @@ const FileLocker = (props) => {
3626
3750
  return count > 0 && (!best || count > best.count) ? { title: p.title, count } : best;
3627
3751
  }, null);
3628
3752
  }, [props.pathways, props.badges, byPathway]);
3753
+ const searchedStudents = React.useMemo(
3754
+ () => searchLower ? enrichedStudents.filter((s) => matchesSearch(s, searchLower)) : enrichedStudents,
3755
+ [enrichedStudents, searchLower]
3756
+ );
3629
3757
  const onReview = (item, list, context) => setReviewing({ student: item, list, context });
3630
3758
  const onNav = (dir) => {
3631
3759
  if (!reviewing)
@@ -3643,7 +3771,10 @@ const FileLocker = (props) => {
3643
3771
  student: reviewing.student,
3644
3772
  context: reviewing.context,
3645
3773
  onBack: () => setReviewing(null),
3646
- onNav
3774
+ onNav,
3775
+ onBadgeClick: props.onBadgeClick,
3776
+ onLessonClick: props.onLessonClick,
3777
+ onPathwayClick: props.onPathwayClick
3647
3778
  }
3648
3779
  );
3649
3780
  }
@@ -3670,7 +3801,66 @@ const FileLocker = (props) => {
3670
3801
  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"}`
3671
3802
  },
3672
3803
  t.label
3673
- ))), !props.trial && tab === "students" && /* @__PURE__ */ React.createElement(Table, { loading: props.loading, items: enrichedStudents, onReview: (item) => onReview(item, enrichedStudents) }), !props.trial && tab === "pathways" && /* @__PURE__ */ React.createElement(PathwayGroups, { students: enrichedStudents, loading: props.loading, pathways: props.pathways || [], badges: props.badges || [], onReview }), !props.trial && tab === "badges" && /* @__PURE__ */ React.createElement(BadgeGroups, { students: enrichedStudents, loading: props.loading, badges: props.badges || [], pathwayNameForBadge, onReview }), !props.trial && tab === "lessons" && /* @__PURE__ */ React.createElement(LessonGroups, { students: enrichedStudents, loading: props.loading, lessons: props.lessons, badgeForLesson, pathwayNameForBadge, onReview }));
3804
+ ))), !props.trial && /* @__PURE__ */ React.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.createElement(IconSearch, { size: 15, stroke: 1.75, className: "text-slate-400" }), /* @__PURE__ */ React.createElement(
3805
+ "input",
3806
+ {
3807
+ type: "text",
3808
+ value: search,
3809
+ onChange: (e) => setSearch(e.target.value),
3810
+ placeholder: SEARCH_PLACEHOLDER,
3811
+ className: "flex-1 border-none bg-transparent text-sm text-dark-blue-400 outline-none placeholder:text-slate-400"
3812
+ }
3813
+ )), !props.trial && tab === "students" && /* @__PURE__ */ React.createElement(
3814
+ Table,
3815
+ {
3816
+ loading: props.loading,
3817
+ items: searchedStudents,
3818
+ onReview: (item) => onReview(item, searchedStudents),
3819
+ onBadgeClick: props.onBadgeClick,
3820
+ onLessonClick: props.onLessonClick,
3821
+ onPathwayClick: props.onPathwayClick
3822
+ }
3823
+ ), !props.trial && tab === "pathways" && /* @__PURE__ */ React.createElement(
3824
+ PathwayGroups,
3825
+ {
3826
+ students: enrichedStudents,
3827
+ loading: props.loading,
3828
+ search,
3829
+ pathways: props.pathways || [],
3830
+ badges: props.badges || [],
3831
+ onReview,
3832
+ onBadgeClick: props.onBadgeClick,
3833
+ onLessonClick: props.onLessonClick,
3834
+ onPathwayClick: props.onPathwayClick
3835
+ }
3836
+ ), !props.trial && tab === "badges" && /* @__PURE__ */ React.createElement(
3837
+ BadgeGroups,
3838
+ {
3839
+ students: enrichedStudents,
3840
+ loading: props.loading,
3841
+ search,
3842
+ badges: props.badges || [],
3843
+ pathwayNameForBadge,
3844
+ onReview,
3845
+ onBadgeClick: props.onBadgeClick,
3846
+ onLessonClick: props.onLessonClick,
3847
+ onPathwayClick: props.onPathwayClick
3848
+ }
3849
+ ), !props.trial && tab === "lessons" && /* @__PURE__ */ React.createElement(
3850
+ LessonGroups,
3851
+ {
3852
+ students: enrichedStudents,
3853
+ loading: props.loading,
3854
+ search,
3855
+ lessons: props.lessons,
3856
+ badgeForLesson,
3857
+ pathwayNameForBadge,
3858
+ onReview,
3859
+ onBadgeClick: props.onBadgeClick,
3860
+ onLessonClick: props.onLessonClick,
3861
+ onPathwayClick: props.onPathwayClick
3862
+ }
3863
+ ));
3674
3864
  };
3675
3865
 
3676
3866
  export { AdminProvider, App, Badge, Badges, Class, Classes, Dashboard, FileLocker, GettingStarted, Home, Lesson, Lessons, Navbar, Organization, Pathway, Pathways, People, StartAnonymousLesson, Student, SwitchAccount, TrialHome, TrialRegistration };