@local-civics/mgmt-ui 0.1.225 → 0.1.226

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
@@ -136,10 +136,13 @@ type BadgeProps = {
136
136
  href: string;
137
137
  trial?: boolean;
138
138
  lessonsCompleted?: number;
139
+ pathwayId?: string;
140
+ pathwayTitle?: string;
139
141
  onBackClick: () => void;
140
142
  onClassChange: (classId: string) => void;
141
143
  onCopyLinkClick: () => void;
142
144
  onExportDataClick: () => void;
145
+ onPathwayClick?: () => void;
143
146
  };
144
147
  /**
145
148
  * Badge
package/dist/index.js CHANGED
@@ -598,7 +598,7 @@ const Badge = (props) => {
598
598
  const [tab, setTab] = React.useState("lessons");
599
599
  const numberOfStudents = props.students.length;
600
600
  const numberOfBadges = numberOfStudents > 0 ? props.students.filter((u) => u.isComplete).length : 0;
601
- return /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-col gap-5 px-4 py-8" }, /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-wrap items-start justify-between gap-4" }, /* @__PURE__ */ React__namespace.createElement("div", { className: "flex items-start gap-4" }, /* @__PURE__ */ React__namespace.createElement(Emblem, { imageURL: props.imageURL, alt: props.displayName, size: "xl", icon: icons.IconAlbum, accent: "mint" }), /* @__PURE__ */ React__namespace.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React__namespace.createElement("div", { onClick: props.onBackClick, className: "flex w-max cursor-pointer items-center gap-1 text-xs font-bold text-sky-blue-400" }, /* @__PURE__ */ React__namespace.createElement(icons.IconArrowLeft, { size: 13, stroke: 2.5 }), "Back"), /* @__PURE__ */ React__namespace.createElement("h1", { className: "text-2xl font-extrabold tracking-tight text-dark-blue-400" }, props.displayName || "Badge"), /* @__PURE__ */ React__namespace.createElement("p", { className: "max-w-xl text-sm text-slate-500" }, props.description || "No description"))), !props.trial && /* @__PURE__ */ React__namespace.createElement(
601
+ return /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-col gap-5 px-4 py-8" }, /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-wrap items-start justify-between gap-4" }, /* @__PURE__ */ React__namespace.createElement("div", { className: "flex items-start gap-4" }, /* @__PURE__ */ React__namespace.createElement(Emblem, { imageURL: props.imageURL, alt: props.displayName, size: "xl", icon: icons.IconAlbum, accent: "mint" }), /* @__PURE__ */ React__namespace.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React__namespace.createElement("div", { onClick: props.onBackClick, className: "flex w-max cursor-pointer items-center gap-1 text-xs font-bold text-sky-blue-400" }, /* @__PURE__ */ React__namespace.createElement(icons.IconArrowLeft, { size: 13, stroke: 2.5 }), "Back"), !!props.pathwayTitle && /* @__PURE__ */ React__namespace.createElement("div", { onClick: props.onPathwayClick, className: "flex w-max cursor-pointer items-center gap-1 text-xs font-bold text-sky-blue-400" }, /* @__PURE__ */ React__namespace.createElement(icons.IconArrowLeft, { size: 13, stroke: 2.5 }), "Go to Pathway"), /* @__PURE__ */ React__namespace.createElement("h1", { className: "text-2xl font-extrabold tracking-tight text-dark-blue-400" }, props.displayName || "Badge"), /* @__PURE__ */ React__namespace.createElement("p", { className: "max-w-xl text-sm text-slate-500" }, props.description || "No description"))), !props.trial && /* @__PURE__ */ React__namespace.createElement(
602
602
  SplitButton$4,
603
603
  {
604
604
  href: props.href,
@@ -3090,6 +3090,11 @@ const OTHER_FILTER = "__other__";
3090
3090
  function Table$1(props) {
3091
3091
  const { items: sortedBadges, requestSort, sortConfig } = useSortableData(props.badges);
3092
3092
  const [categoryFilter, setCategoryFilter] = React__namespace.useState("");
3093
+ React__namespace.useEffect(() => {
3094
+ if (props.activeCategoryId !== void 0) {
3095
+ setCategoryFilter(props.activeCategoryId);
3096
+ }
3097
+ }, [props.activeCategoryId]);
3093
3098
  if (props.badges.length === 0) {
3094
3099
  return /* @__PURE__ */ React__namespace.createElement(
3095
3100
  PlaceholderBanner,
@@ -3106,6 +3111,7 @@ function Table$1(props) {
3106
3111
  var _a;
3107
3112
  return !((_a = b.categories) == null ? void 0 : _a.some((id) => groupedIds.has(id)));
3108
3113
  });
3114
+ const extraCategory = categoryFilter && categoryFilter !== OTHER_FILTER && !groupedIds.has(categoryFilter) ? { categoryId: categoryFilter, name: props.activeCategoryLabel || categoryFilter } : void 0;
3109
3115
  const visibleBadges = !categoryFilter ? sortedBadges : categoryFilter === OTHER_FILTER ? other : sortedBadges.filter((b) => {
3110
3116
  var _a;
3111
3117
  return (_a = b.categories) == null ? void 0 : _a.includes(categoryFilter);
@@ -3118,7 +3124,7 @@ function Table$1(props) {
3118
3124
  active: categoryFilter === category.categoryId,
3119
3125
  onClick: () => setCategoryFilter(category.categoryId)
3120
3126
  }
3121
- )), other.length > 0 && /* @__PURE__ */ React__namespace.createElement(CategoryPill, { label: "Other", active: categoryFilter === OTHER_FILTER, onClick: () => setCategoryFilter(OTHER_FILTER) })), /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-col gap-2" }, /* @__PURE__ */ React__namespace.createElement("div", { className: "flex gap-4 px-4" }, /* @__PURE__ */ React__namespace.createElement(SortableHeader, { label: "Badge Name", sortKey: "badgeName", sortConfig, onSort: requestSort, className: "flex-1" }), /* @__PURE__ */ React__namespace.createElement(SortableHeader, { label: "Point Value", sortKey: "weight", sortConfig, onSort: requestSort, align: "right", className: "w-24 shrink-0" }), /* @__PURE__ */ React__namespace.createElement(SortableHeader, { label: "Badge Completion", sortKey: "percentageCompletion", sortConfig, onSort: requestSort, align: "right", className: "w-36 shrink-0" })), visibleBadges.length === 0 && /* @__PURE__ */ React__namespace.createElement("div", { className: "rounded-xl border border-dashed border-slate-200 px-4 py-3 text-xs text-slate-400" }, categoryFilter ? "No badges in this category yet." : "No badges yet."), visibleBadges.map((row) => /* @__PURE__ */ React__namespace.createElement(
3127
+ )), extraCategory && /* @__PURE__ */ React__namespace.createElement(CategoryPill, { label: extraCategory.name, active: true, onClick: () => setCategoryFilter(extraCategory.categoryId) }), other.length > 0 && /* @__PURE__ */ React__namespace.createElement(CategoryPill, { label: "Other", active: categoryFilter === OTHER_FILTER, onClick: () => setCategoryFilter(OTHER_FILTER) })), /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-col gap-2" }, /* @__PURE__ */ React__namespace.createElement("div", { className: "flex gap-4 px-4" }, /* @__PURE__ */ React__namespace.createElement(SortableHeader, { label: "Badge Name", sortKey: "badgeName", sortConfig, onSort: requestSort, className: "flex-1" }), /* @__PURE__ */ React__namespace.createElement(SortableHeader, { label: "Point Value", sortKey: "weight", sortConfig, onSort: requestSort, align: "right", className: "w-24 shrink-0" }), /* @__PURE__ */ React__namespace.createElement(SortableHeader, { label: "Badge Completion", sortKey: "percentageCompletion", sortConfig, onSort: requestSort, align: "right", className: "w-36 shrink-0" })), visibleBadges.length === 0 && /* @__PURE__ */ React__namespace.createElement("div", { className: "rounded-xl border border-dashed border-slate-200 px-4 py-3 text-xs text-slate-400" }, categoryFilter ? "No badges in this category yet." : "No badges yet."), visibleBadges.map((row) => /* @__PURE__ */ React__namespace.createElement(
3122
3128
  reactRouterDom.Link,
3123
3129
  {
3124
3130
  key: row.badgeId,
@@ -3193,13 +3199,26 @@ function buildCategoryTree(categories) {
3193
3199
 
3194
3200
  function CategoriesModal(props) {
3195
3201
  const tree = React__namespace.useMemo(() => buildCategoryTree(props.categories || []), [props.categories]);
3196
- return /* @__PURE__ */ React__namespace.createElement(core.Modal, { opened: props.opened, onClose: props.onClose, title: "Category structure", size: "lg", centered: true }, /* @__PURE__ */ React__namespace.createElement("div", { className: "flex max-h-[70vh] flex-col gap-2 overflow-y-auto pr-1" }, tree.length === 0 && /* @__PURE__ */ React__namespace.createElement("p", { className: "text-sm text-slate-400" }, "No categories to display."), tree.map((node) => /* @__PURE__ */ React__namespace.createElement(CategoryNode, { key: node.categoryId, node, criteria: props.criteria, depth: 0 }))));
3202
+ return /* @__PURE__ */ React__namespace.createElement(core.Modal, { opened: props.opened, onClose: props.onClose, title: "Category structure", size: "lg", centered: true }, /* @__PURE__ */ React__namespace.createElement("div", { className: "flex max-h-[70vh] flex-col gap-2 overflow-y-auto pr-1" }, tree.length === 0 && /* @__PURE__ */ React__namespace.createElement("p", { className: "text-sm text-slate-400" }, "No categories to display."), tree.map((node) => /* @__PURE__ */ React__namespace.createElement(CategoryNode, { key: node.categoryId, node, criteria: props.criteria, depth: 0, onCategoryClick: props.onCategoryClick }))));
3197
3203
  }
3198
3204
  const CategoryNode = (props) => {
3199
3205
  var _a;
3200
3206
  const hasChildren = props.node.children.length > 0;
3201
3207
  const required = props.criteria[props.node.categoryId];
3202
- return /* @__PURE__ */ React__namespace.createElement("div", { className: hasChildren ? "rounded-lg border border-slate-100 bg-slate-50/60 p-3" : "rounded-lg px-3 py-2 hover:bg-slate-50" }, /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-wrap items-center justify-between gap-2" }, /* @__PURE__ */ React__namespace.createElement("span", { className: props.depth === 0 ? "text-sm font-extrabold text-dark-blue-400" : "text-xs font-bold text-dark-blue-400" }, props.node.name), /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-wrap items-center gap-1.5" }, required !== void 0 && /* @__PURE__ */ React__namespace.createElement("span", { className: "rounded-full bg-sky-blue-400/15 px-2 py-0.5 text-[10px] font-bold text-dark-blue-400" }, "min. ", required, " pts"), !!props.node.maxPoints && /* @__PURE__ */ React__namespace.createElement("span", { className: "rounded-full bg-gold-400/15 px-2 py-0.5 text-[10px] font-bold text-dark-blue-400" }, "cap ", props.node.maxPoints, " pts"))), !!((_a = props.node.description) == null ? void 0 : _a.trim()) && /* @__PURE__ */ React__namespace.createElement("p", { className: "mt-0.5 text-xs text-slate-500" }, props.node.description), hasChildren && /* @__PURE__ */ React__namespace.createElement("div", { className: "mt-3 flex flex-col gap-2 border-l-2 border-slate-200 pl-4" }, props.node.children.map((child) => /* @__PURE__ */ React__namespace.createElement(CategoryNode, { key: child.categoryId, node: child, criteria: props.criteria, depth: props.depth + 1 }))));
3208
+ const clickable = !!props.onCategoryClick;
3209
+ return /* @__PURE__ */ React__namespace.createElement(
3210
+ "div",
3211
+ {
3212
+ onClick: clickable ? (e) => {
3213
+ e.stopPropagation();
3214
+ props.onCategoryClick(props.node.categoryId);
3215
+ } : void 0,
3216
+ className: `${hasChildren ? "rounded-lg border border-slate-100 bg-slate-50/60 p-3" : "rounded-lg px-3 py-2 hover:bg-slate-50"} ${clickable ? "cursor-pointer" : ""}`
3217
+ },
3218
+ /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-wrap items-center justify-between gap-2" }, /* @__PURE__ */ React__namespace.createElement("span", { className: props.depth === 0 ? "text-sm font-extrabold text-dark-blue-400" : "text-xs font-bold text-dark-blue-400" }, props.node.name), /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-wrap items-center gap-1.5" }, required !== void 0 && /* @__PURE__ */ React__namespace.createElement("span", { className: "rounded-full bg-sky-blue-400/15 px-2 py-0.5 text-[10px] font-bold text-dark-blue-400" }, "min. ", required, " pts"), !!props.node.maxPoints && /* @__PURE__ */ React__namespace.createElement("span", { className: "rounded-full bg-gold-400/15 px-2 py-0.5 text-[10px] font-bold text-dark-blue-400" }, "cap ", props.node.maxPoints, " pts"))),
3219
+ !!((_a = props.node.description) == null ? void 0 : _a.trim()) && /* @__PURE__ */ React__namespace.createElement("p", { className: "mt-0.5 text-xs text-slate-500" }, props.node.description),
3220
+ hasChildren && /* @__PURE__ */ React__namespace.createElement("div", { className: "mt-3 flex flex-col gap-2 border-l-2 border-slate-200 pl-4" }, props.node.children.map((child) => /* @__PURE__ */ React__namespace.createElement(CategoryNode, { key: child.categoryId, node: child, criteria: props.criteria, depth: props.depth + 1, onCategoryClick: props.onCategoryClick })))
3221
+ );
3203
3222
  };
3204
3223
 
3205
3224
  const TABS$1 = [
@@ -3207,9 +3226,10 @@ const TABS$1 = [
3207
3226
  { label: "By student", value: "students" }
3208
3227
  ];
3209
3228
  const Pathway = (props) => {
3210
- var _a, _b, _c;
3229
+ var _a, _b, _c, _d;
3211
3230
  const [tab, setTab] = React.useState("badges");
3212
3231
  const [categoriesOpen, setCategoriesOpen] = React.useState(false);
3232
+ const [selectedCategoryId, setSelectedCategoryId] = React.useState(void 0);
3213
3233
  const numberOfStudents = props.students.length;
3214
3234
  const numberOfBadgesEarned = numberOfStudents > 0 ? props.students.filter((u) => u.isComplete).length : 0;
3215
3235
  const criteria = props.criteria || {};
@@ -3218,7 +3238,8 @@ const Pathway = (props) => {
3218
3238
  (props.allCategories || []).filter((c) => !c.parentCategoryId).map((c) => c.categoryId)
3219
3239
  );
3220
3240
  const badgeGroupCategories = criteriaCategories.filter((c) => !rootCategoryIds.has(c.categoryId));
3221
- return /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-col gap-5 px-4 py-8" }, /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-wrap items-start justify-between gap-4" }, /* @__PURE__ */ React__namespace.createElement("div", { className: "flex items-start gap-4" }, /* @__PURE__ */ React__namespace.createElement(Emblem, { imageURL: props.imageURL, alt: props.title, size: "xl", icon: icons.IconRoute, accent: "cyan" }), /* @__PURE__ */ React__namespace.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React__namespace.createElement("div", { onClick: props.onBackClick, className: "flex w-max cursor-pointer items-center gap-1 text-xs font-bold text-sky-blue-400" }, /* @__PURE__ */ React__namespace.createElement(icons.IconArrowLeft, { size: 13, stroke: 2.5 }), "Back"), /* @__PURE__ */ React__namespace.createElement("h1", { className: "text-2xl font-extrabold tracking-tight text-dark-blue-400" }, props.title || "Pathway"), !!((_a = props.displayTags) == null ? void 0 : _a.length) && /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-wrap gap-1.5" }, props.displayTags.map((tag) => /* @__PURE__ */ React__namespace.createElement("span", { key: tag, className: "rounded-full bg-slate-100 px-2.5 py-1 text-[10px] font-bold text-slate-500" }, tag))), /* @__PURE__ */ React__namespace.createElement("p", { className: "max-w-xl text-sm text-slate-500" }, props.description || "No description"), (criteriaCategories.length > 0 || !!((_b = props.allCategories) == null ? void 0 : _b.length)) && /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-wrap items-center gap-1.5 pt-0.5" }, criteriaCategories.length > 0 && /* @__PURE__ */ React__namespace.createElement(React__namespace.Fragment, null, /* @__PURE__ */ React__namespace.createElement("span", { className: "text-[10.5px] font-extrabold uppercase tracking-wide text-slate-400" }, "Criteria"), criteriaCategories.map((c) => /* @__PURE__ */ React__namespace.createElement("span", { key: c.categoryId, className: "rounded-full bg-sky-blue-400/15 px-2.5 py-1 text-[10px] font-bold text-dark-blue-400" }, c.name, ": ", criteria[c.categoryId], "+", c.maxPoints ? ` of ${c.maxPoints}` : "", " pts"))), !!((_c = props.allCategories) == null ? void 0 : _c.length) && /* @__PURE__ */ React__namespace.createElement(
3241
+ const selectedCategoryName = selectedCategoryId ? (_a = (props.allCategories || []).find((c) => c.categoryId === selectedCategoryId)) == null ? void 0 : _a.name : void 0;
3242
+ return /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-col gap-5 px-4 py-8" }, /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-wrap items-start justify-between gap-4" }, /* @__PURE__ */ React__namespace.createElement("div", { className: "flex items-start gap-4" }, /* @__PURE__ */ React__namespace.createElement(Emblem, { imageURL: props.imageURL, alt: props.title, size: "xl", icon: icons.IconRoute, accent: "cyan" }), /* @__PURE__ */ React__namespace.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React__namespace.createElement("div", { onClick: props.onBackClick, className: "flex w-max cursor-pointer items-center gap-1 text-xs font-bold text-sky-blue-400" }, /* @__PURE__ */ React__namespace.createElement(icons.IconArrowLeft, { size: 13, stroke: 2.5 }), "Back"), /* @__PURE__ */ React__namespace.createElement("h1", { className: "text-2xl font-extrabold tracking-tight text-dark-blue-400" }, props.title || "Pathway"), !!((_b = props.displayTags) == null ? void 0 : _b.length) && /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-wrap gap-1.5" }, props.displayTags.map((tag) => /* @__PURE__ */ React__namespace.createElement("span", { key: tag, className: "rounded-full bg-slate-100 px-2.5 py-1 text-[10px] font-bold text-slate-500" }, tag))), /* @__PURE__ */ React__namespace.createElement("p", { className: "max-w-xl text-sm text-slate-500" }, props.description || "No description"), (criteriaCategories.length > 0 || !!((_c = props.allCategories) == null ? void 0 : _c.length)) && /* @__PURE__ */ React__namespace.createElement("div", { className: "flex flex-wrap items-center gap-1.5 pt-0.5" }, criteriaCategories.length > 0 && /* @__PURE__ */ React__namespace.createElement(React__namespace.Fragment, null, /* @__PURE__ */ React__namespace.createElement("span", { className: "text-[10.5px] font-extrabold uppercase tracking-wide text-slate-400" }, "Criteria"), criteriaCategories.map((c) => /* @__PURE__ */ React__namespace.createElement("span", { key: c.categoryId, className: "rounded-full bg-sky-blue-400/15 px-2.5 py-1 text-[10px] font-bold text-dark-blue-400" }, c.name, ": ", criteria[c.categoryId], "+", c.maxPoints ? ` of ${c.maxPoints}` : "", " pts"))), !!((_d = props.allCategories) == null ? void 0 : _d.length) && /* @__PURE__ */ React__namespace.createElement(
3222
3243
  "button",
3223
3244
  {
3224
3245
  onClick: () => setCategoriesOpen(true),
@@ -3255,13 +3276,18 @@ const Pathway = (props) => {
3255
3276
  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"}`
3256
3277
  },
3257
3278
  t.label
3258
- ))), (!!props.trial || tab === "badges") && /* @__PURE__ */ React__namespace.createElement(Table$1, { loading: props.loading, badges: props.badges, categories: badgeGroupCategories }), !props.trial && tab === "students" && /* @__PURE__ */ React__namespace.createElement(Table$2, { loading: props.loading, items: props.students, categories: props.categories })), /* @__PURE__ */ React__namespace.createElement(
3279
+ ))), (!!props.trial || tab === "badges") && /* @__PURE__ */ React__namespace.createElement(Table$1, { loading: props.loading, badges: props.badges, categories: badgeGroupCategories, activeCategoryId: selectedCategoryId, activeCategoryLabel: selectedCategoryName }), !props.trial && tab === "students" && /* @__PURE__ */ React__namespace.createElement(Table$2, { loading: props.loading, items: props.students, categories: props.categories })), /* @__PURE__ */ React__namespace.createElement(
3259
3280
  CategoriesModal,
3260
3281
  {
3261
3282
  opened: categoriesOpen,
3262
3283
  onClose: () => setCategoriesOpen(false),
3263
3284
  categories: props.allCategories || [],
3264
- criteria
3285
+ criteria,
3286
+ onCategoryClick: (id) => {
3287
+ setSelectedCategoryId(id);
3288
+ setTab("badges");
3289
+ setCategoriesOpen(false);
3290
+ }
3265
3291
  }
3266
3292
  ));
3267
3293
  };