@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.d.ts +10 -9
- package/dist/index.js +66 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +67 -26
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1498,15 +1498,16 @@ type FileListProps = {
|
|
|
1498
1498
|
onLessonClick?: (lessonId: string) => void;
|
|
1499
1499
|
};
|
|
1500
1500
|
/**
|
|
1501
|
-
* The per-file list shown inside each of FileLocker's grouped sections. Which columns render
|
|
1502
|
-
*
|
|
1503
|
-
*
|
|
1504
|
-
*
|
|
1505
|
-
*
|
|
1506
|
-
*
|
|
1507
|
-
*
|
|
1508
|
-
* row
|
|
1509
|
-
*
|
|
1501
|
+
* The per-file list shown inside each of FileLocker's grouped sections. Which columns render, and
|
|
1502
|
+
* in what order, follows the real Badge -> Lesson -> Question hierarchy left to right (pathway,
|
|
1503
|
+
* the one level above Badge, always surfaces at the group's own header instead - every call site
|
|
1504
|
+
* groups by pathway/badge/lesson and none renders an ungrouped flat list that would need a Pathway
|
|
1505
|
+
* column of its own). Whichever level the calling group has already fixed simply drops out of the
|
|
1506
|
+
* sequence, it never reorders what's left - so Question, the leaf, always sits immediately before
|
|
1507
|
+
* the action column, not first. Header and every row are direct children of one shared grid (not
|
|
1508
|
+
* one grid per row) so column tracks are computed once across all content - a per-row grid would
|
|
1509
|
+
* let each row's auto-sized action column resolve to a different width than the empty header cell
|
|
1510
|
+
* above it, drifting the columns out of alignment. Preview/Download both point at the same existing
|
|
1510
1511
|
* `link` - there's no separate filename/size data available (the link is an opaque storage key,
|
|
1511
1512
|
* not a real filename), so `question` doubles as the file's label.
|
|
1512
1513
|
* @param props
|
package/dist/index.js
CHANGED
|
@@ -7031,42 +7031,32 @@ const FileList = (props) => {
|
|
|
7031
7031
|
return null;
|
|
7032
7032
|
}
|
|
7033
7033
|
const columns = [
|
|
7034
|
-
"minmax(0,2fr)",
|
|
7035
|
-
!hideLesson && "minmax(0,1.3fr)",
|
|
7036
7034
|
!hideBadge && "minmax(0,1.3fr)",
|
|
7035
|
+
!hideLesson && "minmax(0,1.3fr)",
|
|
7036
|
+
"minmax(0,2fr)",
|
|
7037
7037
|
"auto"
|
|
7038
7038
|
].filter(Boolean).join(" ");
|
|
7039
7039
|
const headerCell = "border-b border-slate-100 pb-2 text-[10.5px] font-extrabold uppercase tracking-wide text-slate-400";
|
|
7040
7040
|
const dataCell = "py-3";
|
|
7041
|
-
return /* @__PURE__ */ React__namespace.createElement("div", { className: "grid items-center gap-x-3", style: { gridTemplateColumns: columns } }, /* @__PURE__ */ React__namespace.createElement("div", { className: headerCell }, "
|
|
7041
|
+
return /* @__PURE__ */ React__namespace.createElement("div", { className: "grid items-center gap-x-3", style: { gridTemplateColumns: columns } }, !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) => {
|
|
7042
7042
|
const rowBorder = i > 0 ? "border-t border-slate-100" : "";
|
|
7043
|
-
return /* @__PURE__ */ React__namespace.createElement(React__namespace.Fragment, { key: i },
|
|
7043
|
+
return /* @__PURE__ */ React__namespace.createElement(React__namespace.Fragment, { key: i }, !hideBadge && (row.badgeId && onBadgeClick ? /* @__PURE__ */ React__namespace.createElement(
|
|
7044
7044
|
"button",
|
|
7045
7045
|
{
|
|
7046
7046
|
type: "button",
|
|
7047
|
-
onClick: () =>
|
|
7047
|
+
onClick: () => onBadgeClick(row.badgeId),
|
|
7048
7048
|
className: `${dataCell} ${rowBorder} truncate text-left text-[11px] font-semibold text-sky-blue-400 hover:underline`
|
|
7049
7049
|
},
|
|
7050
|
-
row.
|
|
7051
|
-
) : /* @__PURE__ */ React__namespace.createElement("div", { className: `${dataCell} ${rowBorder} truncate text-[11px] text-slate-500` }, row.
|
|
7050
|
+
row.badgeName
|
|
7051
|
+
) : /* @__PURE__ */ React__namespace.createElement("div", { className: `${dataCell} ${rowBorder} truncate text-[11px] text-slate-500` }, row.badgeName)), !hideLesson && (row.lessonId && onLessonClick ? /* @__PURE__ */ React__namespace.createElement(
|
|
7052
7052
|
"button",
|
|
7053
7053
|
{
|
|
7054
7054
|
type: "button",
|
|
7055
|
-
onClick: () =>
|
|
7055
|
+
onClick: () => onLessonClick(row.lessonId),
|
|
7056
7056
|
className: `${dataCell} ${rowBorder} truncate text-left text-[11px] font-semibold text-sky-blue-400 hover:underline`
|
|
7057
7057
|
},
|
|
7058
|
-
row.
|
|
7059
|
-
) : /* @__PURE__ */ React__namespace.createElement("div", { className: `${dataCell} ${rowBorder} truncate text-[11px] text-slate-500` }, row.
|
|
7060
|
-
"a",
|
|
7061
|
-
{
|
|
7062
|
-
href: row.link,
|
|
7063
|
-
target: "_blank",
|
|
7064
|
-
rel: "noopener noreferrer",
|
|
7065
|
-
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"
|
|
7066
|
-
},
|
|
7067
|
-
/* @__PURE__ */ React__namespace.createElement(icons$1.IconExternalLink, { size: 12, stroke: 2 }),
|
|
7068
|
-
"Preview"
|
|
7069
|
-
), /* @__PURE__ */ React__namespace.createElement(
|
|
7058
|
+
row.lessonName
|
|
7059
|
+
) : /* @__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(
|
|
7070
7060
|
"a",
|
|
7071
7061
|
{
|
|
7072
7062
|
href: row.link,
|
|
@@ -7096,9 +7086,18 @@ const TABS = [
|
|
|
7096
7086
|
{ value: "badges", label: "By badge" },
|
|
7097
7087
|
{ value: "lessons", label: "By lesson" }
|
|
7098
7088
|
];
|
|
7089
|
+
const SEARCH_PLACEHOLDER = "Search by pathway, badge, lesson, or question";
|
|
7099
7090
|
const DAY_MS = 24 * 60 * 60 * 1e3;
|
|
7091
|
+
const matchesSearch = (item, term) => {
|
|
7092
|
+
var _a;
|
|
7093
|
+
if (!term)
|
|
7094
|
+
return true;
|
|
7095
|
+
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);
|
|
7096
|
+
};
|
|
7100
7097
|
const FileLocker = (props) => {
|
|
7101
7098
|
const [tab, setTab] = React__namespace.useState("pathways");
|
|
7099
|
+
const [search, setSearch] = React__namespace.useState("");
|
|
7100
|
+
const searchLower = search.trim().toLowerCase();
|
|
7102
7101
|
const filtered = useFilteredSubmissions(props.submissions);
|
|
7103
7102
|
const files = props.submissions.length;
|
|
7104
7103
|
const now = Date.now();
|
|
@@ -7106,9 +7105,24 @@ const FileLocker = (props) => {
|
|
|
7106
7105
|
const startOfMonth = new Date(new Date().getFullYear(), new Date().getMonth(), 1).getTime();
|
|
7107
7106
|
const thisWeek = props.submissions.filter((s) => s.updatedAt && new Date(s.updatedAt).getTime() >= weekAgo).length;
|
|
7108
7107
|
const thisMonth = props.submissions.filter((s) => s.updatedAt && new Date(s.updatedAt).getTime() >= startOfMonth).length;
|
|
7109
|
-
const pathwayGroups = props.pathways.map((p) =>
|
|
7110
|
-
|
|
7111
|
-
|
|
7108
|
+
const pathwayGroups = props.pathways.map((p) => {
|
|
7109
|
+
const base = filtered.byPathway(p.pathwayId, props.badges);
|
|
7110
|
+
const ownNameMatches = !searchLower || p.title.toLowerCase().includes(searchLower);
|
|
7111
|
+
const items = ownNameMatches ? base : base.filter((it) => matchesSearch(it, searchLower));
|
|
7112
|
+
return { pathway: p, items };
|
|
7113
|
+
}).filter((g) => g.items.length > 0);
|
|
7114
|
+
const badgeGroups = props.badges.map((b) => {
|
|
7115
|
+
const base = filtered.byBadge(b.badgeId);
|
|
7116
|
+
const ownNameMatches = !searchLower || b.displayName.toLowerCase().includes(searchLower);
|
|
7117
|
+
const items = ownNameMatches ? base : base.filter((it) => matchesSearch(it, searchLower));
|
|
7118
|
+
return { badge: b, items };
|
|
7119
|
+
}).filter((g) => g.items.length > 0);
|
|
7120
|
+
const lessonGroups = props.lessons.map((l) => {
|
|
7121
|
+
const base = filtered.byLesson(l.lessonId);
|
|
7122
|
+
const ownNameMatches = !searchLower || l.lessonName.toLowerCase().includes(searchLower);
|
|
7123
|
+
const items = ownNameMatches ? base : base.filter((it) => matchesSearch(it, searchLower));
|
|
7124
|
+
return { lesson: l, items };
|
|
7125
|
+
}).filter((g) => g.items.length > 0);
|
|
7112
7126
|
return /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-col gap-5" }, /* @__PURE__ */ React__namespace.createElement("div", null, /* @__PURE__ */ React__namespace.createElement("h1", { className: "text-2xl font-extrabold tracking-tight text-dark-blue-400" }, props.displayName || "File Locker"), /* @__PURE__ */ React__namespace.createElement("p", { className: "text-sm text-slate-500" }, props.description || "Files and links you've submitted through your lessons")), /* @__PURE__ */ React__namespace.createElement("div", { className: "flex gap-2.5" }, /* @__PURE__ */ React__namespace.createElement(StatCell, { icon: icons$1.IconClipboard, value: files, label: "Files", accent: "cyan" }), /* @__PURE__ */ React__namespace.createElement(StatCell, { icon: icons$1.IconCalendar, value: thisWeek, label: "This Week", accent: "mint" }), /* @__PURE__ */ React__namespace.createElement(StatCell, { icon: icons$1.IconCalendarStats, value: thisMonth, label: "This Month", accent: "gold" })), /* @__PURE__ */ React__namespace.createElement("div", { className: "flex w-fit gap-1 rounded-xl border border-slate-200 bg-white p-1" }, TABS.map((t) => /* @__PURE__ */ React__namespace.createElement(
|
|
7113
7127
|
"button",
|
|
7114
7128
|
{
|
|
@@ -7117,7 +7131,16 @@ const FileLocker = (props) => {
|
|
|
7117
7131
|
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"}`
|
|
7118
7132
|
},
|
|
7119
7133
|
t.label
|
|
7120
|
-
))), /* @__PURE__ */ React__namespace.createElement("div", { className: "flex
|
|
7134
|
+
))), /* @__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$1.IconSearch, { size: 15, stroke: 1.75, className: "text-slate-400" }), /* @__PURE__ */ React__namespace.createElement(
|
|
7135
|
+
"input",
|
|
7136
|
+
{
|
|
7137
|
+
type: "text",
|
|
7138
|
+
value: search,
|
|
7139
|
+
onChange: (e) => setSearch(e.target.value),
|
|
7140
|
+
placeholder: SEARCH_PLACEHOLDER,
|
|
7141
|
+
className: "flex-1 border-none bg-transparent text-sm text-dark-blue-400 outline-none placeholder:text-slate-400"
|
|
7142
|
+
}
|
|
7143
|
+
)), /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-col gap-3" }, props.loading && /* @__PURE__ */ React__namespace.createElement("p", { className: "text-sm text-slate-400" }, "Loading..."), !props.loading && tab === "pathways" && pathwayGroups.map(({ pathway, items }) => /* @__PURE__ */ React__namespace.createElement(
|
|
7121
7144
|
GroupCard,
|
|
7122
7145
|
{
|
|
7123
7146
|
key: pathway.pathwayId,
|
|
@@ -7126,7 +7149,25 @@ const FileLocker = (props) => {
|
|
|
7126
7149
|
onClick: props.onPathwayClick ? () => props.onPathwayClick(pathway.pathwayId) : void 0
|
|
7127
7150
|
},
|
|
7128
7151
|
/* @__PURE__ */ React__namespace.createElement(FileList, { items, onBadgeClick: props.onBadgeClick, onLessonClick: props.onLessonClick })
|
|
7129
|
-
)), !props.loading && tab === "badges" && badgeGroups.map(({ badge, items }) => /* @__PURE__ */ React__namespace.createElement(
|
|
7152
|
+
)), !props.loading && tab === "badges" && badgeGroups.map(({ badge, items }) => /* @__PURE__ */ React__namespace.createElement(
|
|
7153
|
+
GroupCard,
|
|
7154
|
+
{
|
|
7155
|
+
key: badge.badgeId,
|
|
7156
|
+
title: badge.displayName,
|
|
7157
|
+
description: badgeSubtitle(items),
|
|
7158
|
+
onClick: props.onBadgeClick ? () => props.onBadgeClick(badge.badgeId) : void 0
|
|
7159
|
+
},
|
|
7160
|
+
/* @__PURE__ */ React__namespace.createElement(FileList, { items, hideBadge: true, onLessonClick: props.onLessonClick })
|
|
7161
|
+
)), !props.loading && tab === "lessons" && lessonGroups.map(({ lesson, items }) => /* @__PURE__ */ React__namespace.createElement(
|
|
7162
|
+
GroupCard,
|
|
7163
|
+
{
|
|
7164
|
+
key: lesson.lessonId,
|
|
7165
|
+
title: lesson.lessonName,
|
|
7166
|
+
description: lessonSubtitle(items),
|
|
7167
|
+
onClick: props.onLessonClick ? () => props.onLessonClick(lesson.lessonId) : void 0
|
|
7168
|
+
},
|
|
7169
|
+
/* @__PURE__ */ React__namespace.createElement(FileList, { items, hideBadge: true, hideLesson: true })
|
|
7170
|
+
)), !props.loading && files > 0 && searchLower && (tab === "pathways" && pathwayGroups.length === 0 || tab === "badges" && badgeGroups.length === 0 || tab === "lessons" && lessonGroups.length === 0) && /* @__PURE__ */ React__namespace.createElement("p", { className: "text-sm text-slate-400" }, "No matching entries."), !props.loading && files === 0 && /* @__PURE__ */ React__namespace.createElement("p", { className: "text-sm text-slate-400" }, "No files to display.")));
|
|
7130
7171
|
};
|
|
7131
7172
|
const badgeSubtitle = (items) => {
|
|
7132
7173
|
var _a;
|