@local-civics/hub-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,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import React__default, { useCallback, useState, useMemo } from 'react';
3
3
  import 'external-svg-loader';
4
- import { IconCaretDown, IconCaretUp, IconSearch, IconLayoutGrid, IconList, IconBrandInstagram, IconBrandLinkedin, IconBrandFacebook, IconCategory2, IconRoute, IconAlbum, IconSwitchHorizontal, IconLogout, IconUser, IconClipboard, IconBriefcase, IconMessageCircle, IconBell, IconExternalLink, IconDownload, IconCalendar, IconCalendarStats } from '@tabler/icons';
4
+ import { IconCaretDown, IconCaretUp, IconSearch, IconLayoutGrid, IconList, IconBrandInstagram, IconBrandLinkedin, IconBrandFacebook, IconCategory2, IconRoute, IconAlbum, IconSwitchHorizontal, IconLogout, IconUser, IconClipboard, IconBriefcase, IconMessageCircle, IconBell, IconDownload, IconCalendar, IconCalendarStats } from '@tabler/icons';
5
5
  import LinkifyIt from 'linkify-it';
6
6
  import reactStringReplace from 'react-string-replace';
7
7
  import { Link } from 'react-router-dom';
@@ -7011,42 +7011,32 @@ const FileList = (props) => {
7011
7011
  return null;
7012
7012
  }
7013
7013
  const columns = [
7014
- "minmax(0,2fr)",
7015
- !hideLesson && "minmax(0,1.3fr)",
7016
7014
  !hideBadge && "minmax(0,1.3fr)",
7015
+ !hideLesson && "minmax(0,1.3fr)",
7016
+ "minmax(0,2fr)",
7017
7017
  "auto"
7018
7018
  ].filter(Boolean).join(" ");
7019
7019
  const headerCell = "border-b border-slate-100 pb-2 text-[10.5px] font-extrabold uppercase tracking-wide text-slate-400";
7020
7020
  const dataCell = "py-3";
7021
- return /* @__PURE__ */ React.createElement("div", { className: "grid items-center gap-x-3", style: { gridTemplateColumns: columns } }, /* @__PURE__ */ React.createElement("div", { className: headerCell }, "Question"), !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) => {
7021
+ return /* @__PURE__ */ React.createElement("div", { className: "grid items-center gap-x-3", style: { gridTemplateColumns: columns } }, !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) => {
7022
7022
  const rowBorder = i > 0 ? "border-t border-slate-100" : "";
7023
- 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), !hideLesson && (row.lessonId && onLessonClick ? /* @__PURE__ */ React.createElement(
7023
+ return /* @__PURE__ */ React.createElement(React.Fragment, { key: i }, !hideBadge && (row.badgeId && onBadgeClick ? /* @__PURE__ */ React.createElement(
7024
7024
  "button",
7025
7025
  {
7026
7026
  type: "button",
7027
- onClick: () => onLessonClick(row.lessonId),
7027
+ onClick: () => onBadgeClick(row.badgeId),
7028
7028
  className: `${dataCell} ${rowBorder} truncate text-left text-[11px] font-semibold text-sky-blue-400 hover:underline`
7029
7029
  },
7030
- row.lessonName
7031
- ) : /* @__PURE__ */ React.createElement("div", { className: `${dataCell} ${rowBorder} truncate text-[11px] text-slate-500` }, row.lessonName)), !hideBadge && (row.badgeId && onBadgeClick ? /* @__PURE__ */ React.createElement(
7030
+ row.badgeName
7031
+ ) : /* @__PURE__ */ React.createElement("div", { className: `${dataCell} ${rowBorder} truncate text-[11px] text-slate-500` }, row.badgeName)), !hideLesson && (row.lessonId && onLessonClick ? /* @__PURE__ */ React.createElement(
7032
7032
  "button",
7033
7033
  {
7034
7034
  type: "button",
7035
- onClick: () => onBadgeClick(row.badgeId),
7035
+ onClick: () => onLessonClick(row.lessonId),
7036
7036
  className: `${dataCell} ${rowBorder} truncate text-left text-[11px] font-semibold text-sky-blue-400 hover:underline`
7037
7037
  },
7038
- row.badgeName
7039
- ) : /* @__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(
7040
- "a",
7041
- {
7042
- href: row.link,
7043
- target: "_blank",
7044
- rel: "noopener noreferrer",
7045
- 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"
7046
- },
7047
- /* @__PURE__ */ React.createElement(IconExternalLink, { size: 12, stroke: 2 }),
7048
- "Preview"
7049
- ), /* @__PURE__ */ React.createElement(
7038
+ row.lessonName
7039
+ ) : /* @__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(
7050
7040
  "a",
7051
7041
  {
7052
7042
  href: row.link,
@@ -7076,9 +7066,18 @@ const TABS = [
7076
7066
  { value: "badges", label: "By badge" },
7077
7067
  { value: "lessons", label: "By lesson" }
7078
7068
  ];
7069
+ const SEARCH_PLACEHOLDER = "Search by pathway, badge, lesson, or question";
7079
7070
  const DAY_MS = 24 * 60 * 60 * 1e3;
7071
+ const matchesSearch = (item, term) => {
7072
+ var _a;
7073
+ if (!term)
7074
+ return true;
7075
+ return item.badgeName.toLowerCase().includes(term) || item.lessonName.toLowerCase().includes(term) || !!((_a = item.pathwayName) == null ? void 0 : _a.toLowerCase().includes(term)) || item.question.toLowerCase().includes(term);
7076
+ };
7080
7077
  const FileLocker = (props) => {
7081
7078
  const [tab, setTab] = React.useState("pathways");
7079
+ const [search, setSearch] = React.useState("");
7080
+ const searchLower = search.trim().toLowerCase();
7082
7081
  const filtered = useFilteredSubmissions(props.submissions);
7083
7082
  const files = props.submissions.length;
7084
7083
  const now = Date.now();
@@ -7086,9 +7085,24 @@ const FileLocker = (props) => {
7086
7085
  const startOfMonth = new Date(new Date().getFullYear(), new Date().getMonth(), 1).getTime();
7087
7086
  const thisWeek = props.submissions.filter((s) => s.updatedAt && new Date(s.updatedAt).getTime() >= weekAgo).length;
7088
7087
  const thisMonth = props.submissions.filter((s) => s.updatedAt && new Date(s.updatedAt).getTime() >= startOfMonth).length;
7089
- const pathwayGroups = props.pathways.map((p) => ({ pathway: p, items: filtered.byPathway(p.pathwayId, props.badges) })).filter((g) => g.items.length > 0);
7090
- const badgeGroups = props.badges.map((b) => ({ badge: b, items: filtered.byBadge(b.badgeId) })).filter((g) => g.items.length > 0);
7091
- const lessonGroups = props.lessons.map((l) => ({ lesson: l, items: filtered.byLesson(l.lessonId) })).filter((g) => g.items.length > 0);
7088
+ const pathwayGroups = props.pathways.map((p) => {
7089
+ const base = filtered.byPathway(p.pathwayId, props.badges);
7090
+ const ownNameMatches = !searchLower || p.title.toLowerCase().includes(searchLower);
7091
+ const items = ownNameMatches ? base : base.filter((it) => matchesSearch(it, searchLower));
7092
+ return { pathway: p, items };
7093
+ }).filter((g) => g.items.length > 0);
7094
+ const badgeGroups = props.badges.map((b) => {
7095
+ const base = filtered.byBadge(b.badgeId);
7096
+ const ownNameMatches = !searchLower || b.displayName.toLowerCase().includes(searchLower);
7097
+ const items = ownNameMatches ? base : base.filter((it) => matchesSearch(it, searchLower));
7098
+ return { badge: b, items };
7099
+ }).filter((g) => g.items.length > 0);
7100
+ const lessonGroups = props.lessons.map((l) => {
7101
+ const base = filtered.byLesson(l.lessonId);
7102
+ const ownNameMatches = !searchLower || l.lessonName.toLowerCase().includes(searchLower);
7103
+ const items = ownNameMatches ? base : base.filter((it) => matchesSearch(it, searchLower));
7104
+ return { lesson: l, items };
7105
+ }).filter((g) => g.items.length > 0);
7092
7106
  return /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-5" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h1", { className: "text-2xl font-extrabold tracking-tight text-dark-blue-400" }, props.displayName || "File Locker"), /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-500" }, props.description || "Files and links you've submitted through your lessons")), /* @__PURE__ */ React.createElement("div", { className: "flex gap-2.5" }, /* @__PURE__ */ React.createElement(StatCell, { icon: IconClipboard, value: files, label: "Files", accent: "cyan" }), /* @__PURE__ */ React.createElement(StatCell, { icon: IconCalendar, value: thisWeek, label: "This Week", accent: "mint" }), /* @__PURE__ */ React.createElement(StatCell, { icon: IconCalendarStats, value: thisMonth, label: "This Month", accent: "gold" })), /* @__PURE__ */ React.createElement("div", { className: "flex w-fit gap-1 rounded-xl border border-slate-200 bg-white p-1" }, TABS.map((t) => /* @__PURE__ */ React.createElement(
7093
7107
  "button",
7094
7108
  {
@@ -7097,7 +7111,16 @@ const FileLocker = (props) => {
7097
7111
  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"}`
7098
7112
  },
7099
7113
  t.label
7100
- ))), /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-3" }, props.loading && /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-400" }, "Loading..."), !props.loading && tab === "pathways" && pathwayGroups.map(({ pathway, items }) => /* @__PURE__ */ React.createElement(
7114
+ ))), /* @__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(
7115
+ "input",
7116
+ {
7117
+ type: "text",
7118
+ value: search,
7119
+ onChange: (e) => setSearch(e.target.value),
7120
+ placeholder: SEARCH_PLACEHOLDER,
7121
+ className: "flex-1 border-none bg-transparent text-sm text-dark-blue-400 outline-none placeholder:text-slate-400"
7122
+ }
7123
+ )), /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-3" }, props.loading && /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-400" }, "Loading..."), !props.loading && tab === "pathways" && pathwayGroups.map(({ pathway, items }) => /* @__PURE__ */ React.createElement(
7101
7124
  GroupCard,
7102
7125
  {
7103
7126
  key: pathway.pathwayId,
@@ -7106,7 +7129,25 @@ const FileLocker = (props) => {
7106
7129
  onClick: props.onPathwayClick ? () => props.onPathwayClick(pathway.pathwayId) : void 0
7107
7130
  },
7108
7131
  /* @__PURE__ */ React.createElement(FileList, { items, onBadgeClick: props.onBadgeClick, onLessonClick: props.onLessonClick })
7109
- )), !props.loading && tab === "badges" && badgeGroups.map(({ badge, items }) => /* @__PURE__ */ React.createElement(GroupCard, { key: badge.badgeId, title: badge.displayName, description: badgeSubtitle(items) }, /* @__PURE__ */ React.createElement(FileList, { items, hideBadge: true, onLessonClick: props.onLessonClick }))), !props.loading && tab === "lessons" && lessonGroups.map(({ lesson, items }) => /* @__PURE__ */ React.createElement(GroupCard, { key: lesson.lessonId, title: lesson.lessonName, description: lessonSubtitle(items) }, /* @__PURE__ */ React.createElement(FileList, { items, hideBadge: true, hideLesson: true }))), !props.loading && files === 0 && /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-400" }, "No files to display.")));
7132
+ )), !props.loading && tab === "badges" && badgeGroups.map(({ badge, items }) => /* @__PURE__ */ React.createElement(
7133
+ GroupCard,
7134
+ {
7135
+ key: badge.badgeId,
7136
+ title: badge.displayName,
7137
+ description: badgeSubtitle(items),
7138
+ onClick: props.onBadgeClick ? () => props.onBadgeClick(badge.badgeId) : void 0
7139
+ },
7140
+ /* @__PURE__ */ React.createElement(FileList, { items, hideBadge: true, onLessonClick: props.onLessonClick })
7141
+ )), !props.loading && tab === "lessons" && lessonGroups.map(({ lesson, items }) => /* @__PURE__ */ React.createElement(
7142
+ GroupCard,
7143
+ {
7144
+ key: lesson.lessonId,
7145
+ title: lesson.lessonName,
7146
+ description: lessonSubtitle(items),
7147
+ onClick: props.onLessonClick ? () => props.onLessonClick(lesson.lessonId) : void 0
7148
+ },
7149
+ /* @__PURE__ */ React.createElement(FileList, { items, hideBadge: true, hideLesson: true })
7150
+ )), !props.loading && files > 0 && searchLower && (tab === "pathways" && pathwayGroups.length === 0 || tab === "badges" && badgeGroups.length === 0 || tab === "lessons" && lessonGroups.length === 0) && /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-400" }, "No matching entries."), !props.loading && files === 0 && /* @__PURE__ */ React.createElement("p", { className: "text-sm text-slate-400" }, "No files to display.")));
7110
7151
  };
7111
7152
  const badgeSubtitle = (items) => {
7112
7153
  var _a;