@exxatdesignux/ui 0.0.6 → 0.0.7

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.
Files changed (264) hide show
  1. package/bin/init.mjs +29 -0
  2. package/package.json +7 -2
  3. package/template/.nvmrc +1 -0
  4. package/template/.prettierignore +7 -0
  5. package/template/.prettierrc +11 -0
  6. package/template/AGENTS.md +485 -0
  7. package/template/Logo/Exxat_Prism.svg +39 -0
  8. package/template/Logo/Exxat_one.svg +36 -0
  9. package/template/README.md +58 -0
  10. package/template/app/(app)/compliance/page.tsx +10 -0
  11. package/template/app/(app)/dashboard/loading.tsx +18 -0
  12. package/template/app/(app)/dashboard/page.tsx +36 -0
  13. package/template/app/(app)/data-list/[id]/page.tsx +28 -0
  14. package/template/app/(app)/data-list/new/page.tsx +31 -0
  15. package/template/app/(app)/data-list/page.tsx +10 -0
  16. package/template/app/(app)/error.tsx +43 -0
  17. package/template/app/(app)/help/page.tsx +34 -0
  18. package/template/app/(app)/layout.tsx +54 -0
  19. package/template/app/(app)/loading.tsx +18 -0
  20. package/template/app/(app)/question-bank/page.tsx +10 -0
  21. package/template/app/(app)/rotations/page.tsx +15 -0
  22. package/template/app/(app)/settings/page.tsx +17 -0
  23. package/template/app/(app)/sites/all/page.tsx +13 -0
  24. package/template/app/(app)/team/page.tsx +10 -0
  25. package/template/app/favicon.ico +0 -0
  26. package/template/app/globals.css +1811 -0
  27. package/template/app/layout.tsx +95 -0
  28. package/template/app/page.tsx +9 -0
  29. package/template/components/.gitkeep +0 -0
  30. package/template/components/app-sidebar-dynamic.tsx +15 -0
  31. package/template/components/app-sidebar.tsx +901 -0
  32. package/template/components/ask-leo-composer.tsx +216 -0
  33. package/template/components/ask-leo-sidebar.tsx +509 -0
  34. package/template/components/chart-area-interactive.tsx +293 -0
  35. package/template/components/charts-overview.tsx +2321 -0
  36. package/template/components/command-menu-01.tsx +133 -0
  37. package/template/components/command-menu-02.tsx +386 -0
  38. package/template/components/command-menu.tsx +182 -0
  39. package/template/components/compliance-board-view.tsx +134 -0
  40. package/template/components/compliance-client.tsx +92 -0
  41. package/template/components/compliance-list-view.tsx +59 -0
  42. package/template/components/compliance-page-header.tsx +89 -0
  43. package/template/components/compliance-table.tsx +525 -0
  44. package/template/components/dashboard-onboarding-gallery.tsx +13 -0
  45. package/template/components/dashboard-onboarding.tsx +21 -0
  46. package/template/components/dashboard-promo-banner.tsx +67 -0
  47. package/template/components/dashboard-quota-progress-card.tsx +369 -0
  48. package/template/components/dashboard-report-charts.tsx +69 -0
  49. package/template/components/dashboard-section-heading.tsx +68 -0
  50. package/template/components/dashboard-tabs.tsx +598 -0
  51. package/template/components/data-list-client.tsx +239 -0
  52. package/template/components/data-list-table-cells.test.tsx +22 -0
  53. package/template/components/data-list-table-cells.tsx +173 -0
  54. package/template/components/data-list-table.tsx +879 -0
  55. package/template/components/data-table/filter-date-calendar.tsx +38 -0
  56. package/template/components/data-table/filter-text-value-input.tsx +77 -0
  57. package/template/components/data-table/index.tsx +1612 -0
  58. package/template/components/data-table/pagination.tsx +256 -0
  59. package/template/components/data-table/types.ts +91 -0
  60. package/template/components/data-table/use-table-state.ts +566 -0
  61. package/template/components/data-view-dashboard-charts-compliance.tsx +960 -0
  62. package/template/components/data-view-dashboard-charts-team.tsx +968 -0
  63. package/template/components/data-view-dashboard-charts.tsx +1668 -0
  64. package/template/components/data-views/board-card-primitives.tsx +93 -0
  65. package/template/components/data-views/index.ts +41 -0
  66. package/template/components/data-views/list-page-board-card.tsx +192 -0
  67. package/template/components/data-views/list-page-board-template.tsx +122 -0
  68. package/template/components/data-views/placement-board-card.tsx +262 -0
  69. package/template/components/export-drawer.tsx +375 -0
  70. package/template/components/exxat-product-logo.tsx +453 -0
  71. package/template/components/form-layout-01.tsx +131 -0
  72. package/template/components/getting-started.tsx +625 -0
  73. package/template/components/key-metrics.tsx +920 -0
  74. package/template/components/leo-insight-indicator.tsx +364 -0
  75. package/template/components/leo-typing-dots.tsx +121 -0
  76. package/template/components/list-hub-status-badge.tsx +51 -0
  77. package/template/components/list-page-dashboard-charts.tsx +18 -0
  78. package/template/components/nav-documents.tsx +89 -0
  79. package/template/components/nav-main.tsx +58 -0
  80. package/template/components/nav-secondary.tsx +64 -0
  81. package/template/components/nav-user.tsx +190 -0
  82. package/template/components/new-placement-back-btn.tsx +28 -0
  83. package/template/components/new-placement-form.tsx +1066 -0
  84. package/template/components/onboarding/index.ts +4 -0
  85. package/template/components/onboarding/onboarding-01.tsx +7 -0
  86. package/template/components/onboarding/onboarding-02.tsx +7 -0
  87. package/template/components/onboarding/onboarding-03.tsx +7 -0
  88. package/template/components/onboarding/onboarding-04.tsx +7 -0
  89. package/template/components/page-header.tsx +57 -0
  90. package/template/components/placement-detail.tsx +438 -0
  91. package/template/components/placements-board-view.tsx +404 -0
  92. package/template/components/placements-list-view.tsx +285 -0
  93. package/template/components/placements-page-header.tsx +160 -0
  94. package/template/components/placements-table-columns.tsx +639 -0
  95. package/template/components/product-switcher.tsx +116 -0
  96. package/template/components/question-bank-board-view.tsx +205 -0
  97. package/template/components/question-bank-client.tsx +77 -0
  98. package/template/components/question-bank-list-view.tsx +59 -0
  99. package/template/components/question-bank-page-header.tsx +89 -0
  100. package/template/components/question-bank-table.tsx +586 -0
  101. package/template/components/rotations-empty-state.tsx +47 -0
  102. package/template/components/rotations-panel-activator.tsx +8 -0
  103. package/template/components/secondary-nav.tsx +394 -0
  104. package/template/components/secondary-panel.tsx +239 -0
  105. package/template/components/section-cards.tsx +106 -0
  106. package/template/components/settings-appearance-card.tsx +424 -0
  107. package/template/components/settings-client.tsx +537 -0
  108. package/template/components/settings-form-row.tsx +42 -0
  109. package/template/components/sidebar-auto-collapse.tsx +23 -0
  110. package/template/components/sidebar-auto-open.tsx +18 -0
  111. package/template/components/sidebar-shell.tsx +37 -0
  112. package/template/components/site-header.tsx +93 -0
  113. package/template/components/sites-all-client.tsx +154 -0
  114. package/template/components/sites-board-view.tsx +67 -0
  115. package/template/components/sites-list-view.tsx +47 -0
  116. package/template/components/sites-table.tsx +312 -0
  117. package/template/components/system-banner-slot.tsx +66 -0
  118. package/template/components/table-properties/column-row.tsx +90 -0
  119. package/template/components/table-properties/draggable-list.ts +49 -0
  120. package/template/components/table-properties/drawer-button.tsx +231 -0
  121. package/template/components/table-properties/drawer.tsx +1102 -0
  122. package/template/components/table-properties/filter-card.tsx +251 -0
  123. package/template/components/table-properties/index.ts +22 -0
  124. package/template/components/table-properties/sort-card.tsx +59 -0
  125. package/template/components/table-properties/types.ts +124 -0
  126. package/template/components/task-list-panel.tsx +98 -0
  127. package/template/components/task-priority-badge.tsx +28 -0
  128. package/template/components/team-board-view.tsx +114 -0
  129. package/template/components/team-client.tsx +93 -0
  130. package/template/components/team-list-view.tsx +62 -0
  131. package/template/components/team-page-header.tsx +92 -0
  132. package/template/components/team-table.tsx +525 -0
  133. package/template/components/templates/list-page.tsx +576 -0
  134. package/template/components/templates/primary-page-template.tsx +56 -0
  135. package/template/components/theme-color-sync.tsx +32 -0
  136. package/template/components/theme-provider.tsx +71 -0
  137. package/template/components/tinted-icon-disc.tsx +53 -0
  138. package/template/components/ui/ai-thinking-surface.tsx +121 -0
  139. package/template/components/ui/avatar.tsx +1 -0
  140. package/template/components/ui/badge.tsx +1 -0
  141. package/template/components/ui/banner.tsx +1 -0
  142. package/template/components/ui/breadcrumb.tsx +1 -0
  143. package/template/components/ui/button.tsx +1 -0
  144. package/template/components/ui/calendar.tsx +1 -0
  145. package/template/components/ui/card.tsx +1 -0
  146. package/template/components/ui/chart.tsx +1 -0
  147. package/template/components/ui/checkbox.tsx +1 -0
  148. package/template/components/ui/coach-mark.tsx +1 -0
  149. package/template/components/ui/collapsible.tsx +1 -0
  150. package/template/components/ui/command.tsx +1 -0
  151. package/template/components/ui/date-picker-field.tsx +1 -0
  152. package/template/components/ui/dialog.tsx +1 -0
  153. package/template/components/ui/dot-pattern.tsx +159 -0
  154. package/template/components/ui/drag-handle-grip.tsx +1 -0
  155. package/template/components/ui/drawer.tsx +1 -0
  156. package/template/components/ui/dropdown-menu.tsx +1 -0
  157. package/template/components/ui/field.tsx +1 -0
  158. package/template/components/ui/form.tsx +1 -0
  159. package/template/components/ui/input-group.tsx +1 -0
  160. package/template/components/ui/input-mask.tsx +1 -0
  161. package/template/components/ui/input.tsx +1 -0
  162. package/template/components/ui/kbd.tsx +1 -0
  163. package/template/components/ui/label.tsx +1 -0
  164. package/template/components/ui/leo-icon.tsx +726 -0
  165. package/template/components/ui/payment-card-fields.tsx +1 -0
  166. package/template/components/ui/popover.tsx +1 -0
  167. package/template/components/ui/radio-group.tsx +1 -0
  168. package/template/components/ui/select.tsx +1 -0
  169. package/template/components/ui/selection-tile-grid.tsx +1 -0
  170. package/template/components/ui/separator.tsx +1 -0
  171. package/template/components/ui/sheet.tsx +1 -0
  172. package/template/components/ui/sidebar.tsx +1 -0
  173. package/template/components/ui/skeleton.tsx +1 -0
  174. package/template/components/ui/sonner.tsx +1 -0
  175. package/template/components/ui/status-badge.tsx +1 -0
  176. package/template/components/ui/table.tsx +1 -0
  177. package/template/components/ui/tabs.tsx +1 -0
  178. package/template/components/ui/textarea.tsx +1 -0
  179. package/template/components/ui/tip.tsx +1 -0
  180. package/template/components/ui/toggle-group.tsx +1 -0
  181. package/template/components/ui/toggle-switch.tsx +1 -0
  182. package/template/components/ui/toggle.tsx +1 -0
  183. package/template/components/ui/tooltip.tsx +1 -0
  184. package/template/components/ui/view-segmented-control.tsx +1 -0
  185. package/template/components.json +27 -0
  186. package/template/contexts/chart-variant-context.tsx +35 -0
  187. package/template/contexts/command-menu-context.tsx +28 -0
  188. package/template/contexts/dashboard-view-context.tsx +35 -0
  189. package/template/contexts/product-context.tsx +38 -0
  190. package/template/contexts/system-banner-context.tsx +127 -0
  191. package/template/docs/command-menu-pattern.md +45 -0
  192. package/template/docs/data-views-pattern.md +160 -0
  193. package/template/ecosystem.config.cjs +20 -0
  194. package/template/eslint.config.mjs +18 -0
  195. package/template/fontawesome-subset.manifest.json +190 -0
  196. package/template/hooks/.gitkeep +0 -0
  197. package/template/hooks/use-app-theme.ts +1 -0
  198. package/template/hooks/use-coach-mark.ts +1 -0
  199. package/template/hooks/use-mobile.ts +1 -0
  200. package/template/hooks/use-mod-key-label.ts +1 -0
  201. package/template/lib/.gitkeep +0 -0
  202. package/template/lib/ask-leo-route-context.ts +133 -0
  203. package/template/lib/chart-keyboard-selection.test.ts +20 -0
  204. package/template/lib/chart-keyboard-selection.ts +17 -0
  205. package/template/lib/chart-line-dash.ts +16 -0
  206. package/template/lib/coach-mark-registry.ts +68 -0
  207. package/template/lib/command-menu-config.ts +127 -0
  208. package/template/lib/command-menu-search-data.ts +44 -0
  209. package/template/lib/conditional-rule-match.ts +32 -0
  210. package/template/lib/dashboard-customize-coach-mark.ts +18 -0
  211. package/template/lib/dashboard-layout-merge.ts +63 -0
  212. package/template/lib/data-list-display-options.ts +35 -0
  213. package/template/lib/data-list-persistence.ts +280 -0
  214. package/template/lib/data-list-view-surface.ts +58 -0
  215. package/template/lib/data-list-view.ts +29 -0
  216. package/template/lib/data-view-dashboard-storage.ts +101 -0
  217. package/template/lib/date-filter.ts +8 -0
  218. package/template/lib/dev-log.test.ts +28 -0
  219. package/template/lib/dev-log.ts +8 -0
  220. package/template/lib/editable-target.ts +10 -0
  221. package/template/lib/floating-sheet-panel.ts +72 -0
  222. package/template/lib/initials-from-name.ts +7 -0
  223. package/template/lib/list-page-table-properties.ts +52 -0
  224. package/template/lib/list-status-badges.ts +168 -0
  225. package/template/lib/logo-dev.ts +12 -0
  226. package/template/lib/mock/compliance-kpi.ts +61 -0
  227. package/template/lib/mock/compliance.ts +146 -0
  228. package/template/lib/mock/dashboard.ts +105 -0
  229. package/template/lib/mock/navigation.tsx +231 -0
  230. package/template/lib/mock/placements-kpi.ts +134 -0
  231. package/template/lib/mock/placements.ts +183 -0
  232. package/template/lib/mock/question-bank-kpi.ts +61 -0
  233. package/template/lib/mock/question-bank.ts +142 -0
  234. package/template/lib/mock/sites-directory.ts +16 -0
  235. package/template/lib/mock/sites-kpi.ts +25 -0
  236. package/template/lib/mock/team-kpi.ts +60 -0
  237. package/template/lib/mock/team.ts +118 -0
  238. package/template/lib/motion-ui.ts +17 -0
  239. package/template/lib/placement-board-card-layout.ts +79 -0
  240. package/template/lib/placement-lifecycle.ts +5 -0
  241. package/template/lib/row-height.ts +10 -0
  242. package/template/lib/stock-portrait.ts +11 -0
  243. package/template/lib/utils.test.ts +13 -0
  244. package/template/lib/utils.ts +1 -0
  245. package/template/next.config.mjs +15 -0
  246. package/template/package.json +83 -0
  247. package/template/postcss.config.mjs +8 -0
  248. package/template/public/.gitkeep +0 -0
  249. package/template/public/Illustration/Rotation.svg +74 -0
  250. package/template/public/avatars/user.svg +11 -0
  251. package/template/public/favicon/favicon.ico +0 -0
  252. package/template/public/favicon.ico +0 -0
  253. package/template/public/logos/exxat-one.svg +36 -0
  254. package/template/public/logos/exxat-prism.svg +39 -0
  255. package/template/public/mock-schools/emory.svg +4 -0
  256. package/template/public/mock-schools/rush.svg +4 -0
  257. package/template/scripts/fontawesome-subset-audit.mjs +190 -0
  258. package/template/scripts/pm2-startup-macos.sh +13 -0
  259. package/template/skills-lock.json +10 -0
  260. package/template/stores/app-store.ts +33 -0
  261. package/template/tests/setup.ts +1 -0
  262. package/template/tsconfig.json +35 -0
  263. package/template/types/react-payment-inputs.d.ts +19 -0
  264. package/template/vitest.config.ts +18 -0
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Inset + sizing for “floating” product sheets (Export, table properties):
3
+ * `Sheet` + `showOverlay={false}` — no dimming layer; panel is inset with rounded corners.
4
+ */
5
+
6
+ import type { CSSProperties } from "react"
7
+ import { cn } from "@/lib/utils"
8
+
9
+ export type FloatingSheetSide = "top" | "right" | "bottom" | "left"
10
+
11
+ const baseClassName =
12
+ "flex flex-col border border-border bg-background shadow-xl rounded-xl overflow-hidden p-0 gap-0"
13
+
14
+ /**
15
+ * Props to spread onto `SheetContent` together with `showOverlay={false}` and `showCloseButton={false}`.
16
+ */
17
+ export function getFloatingSheetInsetProps(side: FloatingSheetSide): {
18
+ side: FloatingSheetSide
19
+ className: string
20
+ style: CSSProperties
21
+ } {
22
+ switch (side) {
23
+ case "right":
24
+ return {
25
+ side: "right",
26
+ className: cn(baseClassName, "w-80 sm:max-w-80"),
27
+ style: {
28
+ top: "0.5rem",
29
+ bottom: "0.5rem",
30
+ right: "0.5rem",
31
+ left: "auto",
32
+ height: "calc(100vh - 1rem)",
33
+ },
34
+ }
35
+ case "left":
36
+ return {
37
+ side: "left",
38
+ className: cn(baseClassName, "w-80 sm:max-w-80"),
39
+ style: {
40
+ top: "0.5rem",
41
+ bottom: "0.5rem",
42
+ left: "0.5rem",
43
+ right: "auto",
44
+ height: "calc(100vh - 1rem)",
45
+ },
46
+ }
47
+ case "bottom":
48
+ return {
49
+ side: "bottom",
50
+ className: cn(baseClassName, "max-h-[min(80vh,32rem)]"),
51
+ style: {
52
+ left: "0.5rem",
53
+ right: "0.5rem",
54
+ bottom: "0.5rem",
55
+ top: "auto",
56
+ height: "auto",
57
+ },
58
+ }
59
+ case "top":
60
+ return {
61
+ side: "top",
62
+ className: cn(baseClassName, "max-h-[min(80vh,32rem)]"),
63
+ style: {
64
+ left: "0.5rem",
65
+ right: "0.5rem",
66
+ top: "0.5rem",
67
+ bottom: "auto",
68
+ height: "auto",
69
+ },
70
+ }
71
+ }
72
+ }
@@ -0,0 +1,7 @@
1
+ /** Two-letter initials for avatar chips when only a display name is available (e.g. compliance owner). */
2
+ export function initialsFromDisplayName(name: string): string {
3
+ const parts = name.trim().split(/\s+/).filter(Boolean)
4
+ if (parts.length === 0) return "?"
5
+ if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase()
6
+ return (parts[0][0]! + parts[parts.length - 1]![0]!).toUpperCase()
7
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Connects ListPageTemplate “View → Edit” to a surface that hosts TablePropertiesDrawer
3
+ * (DataListTable, TeamTable, ComplianceTable, …). Import from `@/components/table-properties`
4
+ * or use here — see `createListPageEditViewHandler`.
5
+ *
6
+ * View **labels** for tabs and Properties are centralized in `@/lib/data-list-view`
7
+ * (`DATA_LIST_VIEW_TILES`, `dataListViewLabel`, `dataListViewIcon`).
8
+ */
9
+
10
+ import * as React from "react"
11
+
12
+ import { dataListViewIcon, type DataListViewType } from "@/lib/data-list-view"
13
+
14
+ /** Minimal ref API any list/table surface exposes for the shared Properties drawer. */
15
+ export interface OpenTablePropertiesHandle {
16
+ openPropertiesDrawer: () => void
17
+ }
18
+
19
+ const SURFACE_VIEW_TYPES = new Set<DataListViewType>(["table", "list", "board", "dashboard"])
20
+
21
+ /** True when `viewType` is one of the data-list surfaces that support TablePropertiesDrawer. */
22
+ export function isDataListSurfaceViewType(viewType: string): viewType is DataListViewType {
23
+ return SURFACE_VIEW_TYPES.has(viewType as DataListViewType)
24
+ }
25
+
26
+ export interface CreateListPageEditViewHandlerOptions {
27
+ /** Delay before opening Properties after switching to table (ms). Default 160. */
28
+ switchDelayMs?: number
29
+ }
30
+
31
+ /**
32
+ * Returns `ListPageTemplate`’s `onEditView` handler: optionally coerces the tab to `table`
33
+ * when the view type is unknown, then calls `ref.current.openPropertiesDrawer()`.
34
+ */
35
+ export function createListPageEditViewHandler(
36
+ tableRef: React.RefObject<OpenTablePropertiesHandle | null>,
37
+ options?: CreateListPageEditViewHandlerOptions
38
+ ) {
39
+ const delay = options?.switchDelayMs ?? 160
40
+ return (
41
+ tab: { viewType: string },
42
+ { updateTab }: { updateTab: (patch: { viewType?: DataListViewType; icon?: string }) => void }
43
+ ) => {
44
+ const mustSwitchToTableSurface = !isDataListSurfaceViewType(tab.viewType)
45
+ if (mustSwitchToTableSurface) {
46
+ updateTab({ viewType: "table", icon: dataListViewIcon("table") })
47
+ }
48
+ window.setTimeout(() => {
49
+ tableRef.current?.openPropertiesDrawer()
50
+ }, mustSwitchToTableSurface ? delay : 0)
51
+ }
52
+ }
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Shared status chip labels, tint classes, and FA icon names for product list hubs
3
+ * (Placements, Team, Compliance, Question bank — table, list, board), plus related chips
4
+ * (dashboard **task priority**, placement **readiness** on the detail drawer).
5
+ *
6
+ * Labels use **sentence / title case** (e.g. "Due soon", "Under Review"). Do **not** add **`uppercase`**.
7
+ *
8
+ * **Rendering:** Use **`ListHubStatusBadge`** from `@/components/list-hub-status-badge`, or
9
+ * **`StatusBadge`** from **`components/data-list-table-cells.tsx`** for placement rows (wrapper
10
+ * around **`ListHubStatusBadge`** + **`PLACEMENT_STATUS_*`** below). Task priority → **`TaskPriorityBadge`**.
11
+ *
12
+ * **Semantic tints:** Map domain statuses onto **`LIST_HUB_STATUS_TINT_*`** before inventing new colors.
13
+ * **Icon-on-tinted-disc** (insights / activity): **`TintedIconDisc`** + **`--icon-disc-*`** in **`app/globals.css`**.
14
+ */
15
+
16
+ import type { ComplianceStatus } from "@/lib/mock/compliance"
17
+ import type { QuestionBankStatus } from "@/lib/mock/question-bank"
18
+ import type { Status as PlacementStatus } from "@/lib/mock/placements"
19
+ import type { TeamMember } from "@/lib/mock/team"
20
+
21
+ // ─── Semantic variants (reuse for new entities) ─────────────────────────────
22
+ //
23
+ // **Light washes** (same visual weight as before) + **darker ink** via `--chip-*` for WCAG 1.4.3.
24
+ // Backgrounds stay subtle; contrast is carried by label + icon color, not heavier fills.
25
+
26
+ export const LIST_HUB_STATUS_TINT_SUCCESS =
27
+ "bg-emerald-500/15 text-[var(--chip-2)] border-emerald-500/20 dark:bg-emerald-500/15 dark:text-emerald-200"
28
+
29
+ export const LIST_HUB_STATUS_TINT_WARNING =
30
+ "bg-amber-500/15 text-[var(--chip-4)] border-amber-500/20 dark:bg-amber-500/15 dark:text-amber-100"
31
+
32
+ export const LIST_HUB_STATUS_TINT_NEUTRAL =
33
+ "bg-slate-500/10 text-[var(--chip-3)] border-border dark:bg-slate-500/10 dark:text-slate-300"
34
+
35
+ export const LIST_HUB_STATUS_TINT_DANGER =
36
+ "bg-destructive/15 text-[var(--chip-destructive)] border-destructive/20 dark:bg-destructive/15 dark:text-red-200"
37
+
38
+ /** In-progress / review (distinct from warning where both appear — e.g. Placements “Under review”). */
39
+ export const LIST_HUB_STATUS_TINT_INFO =
40
+ "bg-sky-500/15 text-[var(--chip-1)] border-sky-500/20 dark:bg-sky-500/15 dark:text-sky-100"
41
+
42
+ // ─── Placement detail — readiness row (string labels from mock) ─────────────
43
+
44
+ const PLACEMENT_READINESS_BADGE_CLASS: Record<string, string> = {
45
+ Ready: LIST_HUB_STATUS_TINT_SUCCESS,
46
+ "At risk": LIST_HUB_STATUS_TINT_DANGER,
47
+ Blocked: LIST_HUB_STATUS_TINT_DANGER,
48
+ "In review": LIST_HUB_STATUS_TINT_INFO,
49
+ }
50
+
51
+ /** Badge `className` tail for placement readiness labels; unknown → neutral. */
52
+ export function placementReadinessBadgeClass(readiness: string): string {
53
+ return PLACEMENT_READINESS_BADGE_CLASS[readiness] ?? LIST_HUB_STATUS_TINT_NEUTRAL
54
+ }
55
+
56
+ // ─── Dashboard task priority (shared chip system) ──────────────────────────
57
+
58
+ export type TaskPriorityLevel = "high" | "medium" | "low"
59
+
60
+ export const TASK_PRIORITY_LABEL: Record<TaskPriorityLevel, string> = {
61
+ high: "High",
62
+ medium: "Medium",
63
+ low: "Low",
64
+ }
65
+
66
+ export const TASK_PRIORITY_BADGE_CLASS: Record<TaskPriorityLevel, string> = {
67
+ high: LIST_HUB_STATUS_TINT_DANGER,
68
+ medium: LIST_HUB_STATUS_TINT_WARNING,
69
+ low: LIST_HUB_STATUS_TINT_NEUTRAL,
70
+ }
71
+
72
+ export function normalizeTaskPriority(priority: string): TaskPriorityLevel | null {
73
+ const k = priority.toLowerCase()
74
+ if (k === "high" || k === "medium" || k === "low") return k
75
+ return null
76
+ }
77
+
78
+ // ─── Placements (lifecycle status) ───────────────────────────────────────
79
+
80
+ export const PLACEMENT_STATUS_LABEL: Record<PlacementStatus, string> = {
81
+ confirmed: "Confirmed",
82
+ pending: "Pending",
83
+ "under-review": "Under Review",
84
+ rejected: "Rejected",
85
+ completed: "Completed",
86
+ }
87
+
88
+ export const PLACEMENT_STATUS_BADGE_CLASS: Record<PlacementStatus, string> = {
89
+ confirmed: LIST_HUB_STATUS_TINT_SUCCESS,
90
+ pending: LIST_HUB_STATUS_TINT_WARNING,
91
+ "under-review": LIST_HUB_STATUS_TINT_INFO,
92
+ rejected: LIST_HUB_STATUS_TINT_DANGER,
93
+ completed: LIST_HUB_STATUS_TINT_NEUTRAL,
94
+ }
95
+
96
+ export const PLACEMENT_STATUS_ICON: Record<PlacementStatus, string> = {
97
+ confirmed: "fa-circle-check",
98
+ pending: "fa-hourglass-half",
99
+ "under-review": "fa-eye",
100
+ rejected: "fa-circle-xmark",
101
+ completed: "fa-clipboard-check",
102
+ }
103
+
104
+ // ─── Team ─────────────────────────────────────────────────────────────────
105
+
106
+ export type TeamMemberStatus = TeamMember["status"]
107
+
108
+ export const TEAM_MEMBER_STATUS_LABEL: Record<TeamMemberStatus, string> = {
109
+ active: "Active",
110
+ away: "Away",
111
+ invited: "Invited",
112
+ }
113
+
114
+ export const TEAM_MEMBER_STATUS_BADGE_CLASS: Record<TeamMemberStatus, string> = {
115
+ active: LIST_HUB_STATUS_TINT_SUCCESS,
116
+ away: LIST_HUB_STATUS_TINT_WARNING,
117
+ invited: LIST_HUB_STATUS_TINT_NEUTRAL,
118
+ }
119
+
120
+ /** Font Awesome icon per status — shape + label, not colour alone (WCAG 1.4.1). */
121
+ export const TEAM_MEMBER_STATUS_ICON: Record<TeamMemberStatus, string> = {
122
+ active: "fa-circle-check",
123
+ away: "fa-moon",
124
+ invited: "fa-envelope",
125
+ }
126
+
127
+ // ─── Compliance ───────────────────────────────────────────────────────────
128
+
129
+ export const COMPLIANCE_STATUS_LABEL: Record<ComplianceStatus, string> = {
130
+ compliant: "Compliant",
131
+ due_soon: "Due soon",
132
+ overdue: "Overdue",
133
+ pending: "Pending",
134
+ }
135
+
136
+ export const COMPLIANCE_STATUS_BADGE_CLASS: Record<ComplianceStatus, string> = {
137
+ compliant: LIST_HUB_STATUS_TINT_SUCCESS,
138
+ due_soon: LIST_HUB_STATUS_TINT_WARNING,
139
+ overdue: LIST_HUB_STATUS_TINT_DANGER,
140
+ pending: LIST_HUB_STATUS_TINT_NEUTRAL,
141
+ }
142
+
143
+ export const COMPLIANCE_STATUS_ICON: Record<ComplianceStatus, string> = {
144
+ compliant: "fa-shield-check",
145
+ due_soon: "fa-clock",
146
+ overdue: "fa-triangle-exclamation",
147
+ pending: "fa-hourglass-half",
148
+ }
149
+
150
+ // ─── Question bank ────────────────────────────────────────────────────────
151
+
152
+ export const QUESTION_BANK_STATUS_LABEL: Record<QuestionBankStatus, string> = {
153
+ published: "Published",
154
+ draft: "Draft",
155
+ in_review: "In review",
156
+ }
157
+
158
+ export const QUESTION_BANK_STATUS_BADGE_CLASS: Record<QuestionBankStatus, string> = {
159
+ published: LIST_HUB_STATUS_TINT_SUCCESS,
160
+ draft: LIST_HUB_STATUS_TINT_NEUTRAL,
161
+ in_review: LIST_HUB_STATUS_TINT_WARNING,
162
+ }
163
+
164
+ export const QUESTION_BANK_STATUS_ICON: Record<QuestionBankStatus, string> = {
165
+ published: "fa-circle-check",
166
+ draft: "fa-pen-field",
167
+ in_review: "fa-user-magnifying-glass",
168
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * logo.dev brand images — publishable key is safe in the browser (like the
3
+ * example: `https://img.logo.dev/stripe.com?token=pk_…`).
4
+ * Override locally with `NEXT_PUBLIC_LOGO_DEV_TOKEN` if needed.
5
+ */
6
+ export const LOGO_DEV_PUBLISHABLE_KEY =
7
+ process.env.NEXT_PUBLIC_LOGO_DEV_TOKEN ?? "pk_JltPpX0KR4WONn4HFjq3Aw"
8
+
9
+ export function logoDevUrl(domain: string): string {
10
+ const q = new URLSearchParams({ token: LOGO_DEV_PUBLISHABLE_KEY })
11
+ return `https://img.logo.dev/${domain}?${q.toString()}`
12
+ }
@@ -0,0 +1,61 @@
1
+ import type { MetricInsight, MetricItem } from "@/components/key-metrics"
2
+ import type { ComplianceItem } from "@/lib/mock/compliance"
3
+
4
+ export function complianceKpiMetrics(rows: ComplianceItem[]): MetricItem[] {
5
+ const compliant = rows.filter(r => r.status === "compliant").length
6
+ const dueSoon = rows.filter(r => r.status === "due_soon").length
7
+ const overdue = rows.filter(r => r.status === "overdue").length
8
+ const pending = rows.filter(r => r.status === "pending").length
9
+
10
+ return [
11
+ {
12
+ id: "total",
13
+ label: "Total items",
14
+ value: rows.length,
15
+ delta: "—",
16
+ trend: "neutral",
17
+ href: "#",
18
+ metricVariant: "hero",
19
+ },
20
+ {
21
+ id: "compliant",
22
+ label: "Compliant",
23
+ value: compliant,
24
+ delta: "—",
25
+ trend: "neutral",
26
+ href: "#",
27
+ },
28
+ {
29
+ id: "attention",
30
+ label: "Due soon",
31
+ value: dueSoon,
32
+ delta: dueSoon > 0 ? "!" : "—",
33
+ trend: dueSoon > 0 ? "up" : "neutral",
34
+ href: "#",
35
+ },
36
+ {
37
+ id: "risk",
38
+ label: "Overdue / pending",
39
+ value: overdue + pending,
40
+ delta: "—",
41
+ trend: overdue + pending > 0 ? "up" : "neutral",
42
+ href: "#",
43
+ },
44
+ ]
45
+ }
46
+
47
+ export function complianceKpiInsight(rows: ComplianceItem[]): MetricInsight {
48
+ const overdue = rows.filter(r => r.status === "overdue").length
49
+ const dueSoon = rows.filter(r => r.status === "due_soon").length
50
+ return {
51
+ title: "Compliance queue",
52
+ description:
53
+ overdue > 0
54
+ ? `${overdue} item(s) overdue. ${dueSoon} due within 30 days.`
55
+ : dueSoon > 0
56
+ ? `${dueSoon} item(s) due soon — review owners and dates.`
57
+ : "No overdue items in this view.",
58
+ severity: overdue > 0 ? "warning" : dueSoon > 0 ? "warning" : "info",
59
+ actionLabel: "Ask Leo",
60
+ }
61
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Mock compliance obligations — replace with API data in production.
3
+ */
4
+
5
+ export type ComplianceStatus = "compliant" | "due_soon" | "overdue" | "pending"
6
+
7
+ export interface ComplianceItem extends Record<string, unknown> {
8
+ id: string
9
+ title: string
10
+ category: string
11
+ status: ComplianceStatus
12
+ /** MM/DD/YYYY */
13
+ dueDate: string
14
+ owner: string
15
+ /** MM/DD/YYYY */
16
+ lastReviewed: string
17
+ }
18
+
19
+ export const COMPLIANCE_ITEMS: ComplianceItem[] = [
20
+ {
21
+ id: "1",
22
+ title: "Annual HIPAA training attestation",
23
+ category: "HIPAA",
24
+ status: "compliant",
25
+ dueDate: "12/31/2026",
26
+ owner: "Alex Rivera",
27
+ lastReviewed: "03/01/2026",
28
+ },
29
+ {
30
+ id: "2",
31
+ title: "OSHA bloodborne pathogen review",
32
+ category: "OSHA",
33
+ status: "due_soon",
34
+ dueDate: "04/10/2026",
35
+ owner: "Jordan Chen",
36
+ lastReviewed: "10/15/2025",
37
+ },
38
+ {
39
+ id: "3",
40
+ title: "Clinical site affiliation agreements",
41
+ category: "Clinical",
42
+ status: "pending",
43
+ dueDate: "05/01/2026",
44
+ owner: "Sam Patel",
45
+ lastReviewed: "—",
46
+ },
47
+ {
48
+ id: "4",
49
+ title: "FERPA student records policy",
50
+ category: "Privacy",
51
+ status: "compliant",
52
+ dueDate: "09/30/2026",
53
+ owner: "Taylor Brooks",
54
+ lastReviewed: "02/20/2026",
55
+ },
56
+ {
57
+ id: "5",
58
+ title: "Background check re-verification",
59
+ category: "Clinical",
60
+ status: "overdue",
61
+ dueDate: "03/01/2026",
62
+ owner: "Jordan Chen",
63
+ lastReviewed: "03/01/2025",
64
+ },
65
+ {
66
+ id: "6",
67
+ title: "Accreditation self-study draft",
68
+ category: "Accreditation",
69
+ status: "due_soon",
70
+ dueDate: "04/22/2026",
71
+ owner: "Alex Rivera",
72
+ lastReviewed: "01/10/2026",
73
+ },
74
+ {
75
+ id: "7",
76
+ title: "Incident response tabletop exercise",
77
+ category: "HIPAA",
78
+ status: "pending",
79
+ dueDate: "06/15/2026",
80
+ owner: "Sam Patel",
81
+ lastReviewed: "—",
82
+ },
83
+ {
84
+ id: "8",
85
+ title: "Emergency preparedness plan",
86
+ category: "OSHA",
87
+ status: "compliant",
88
+ dueDate: "11/01/2026",
89
+ owner: "Taylor Brooks",
90
+ lastReviewed: "02/28/2026",
91
+ },
92
+ {
93
+ id: "9",
94
+ title: "Medication safety competency checklist",
95
+ category: "Clinical",
96
+ status: "compliant",
97
+ dueDate: "08/01/2026",
98
+ owner: "Alex Rivera",
99
+ lastReviewed: "03/15/2026",
100
+ },
101
+ {
102
+ id: "10",
103
+ title: "Data breach notification procedure",
104
+ category: "Privacy",
105
+ status: "due_soon",
106
+ dueDate: "04/05/2026",
107
+ owner: "Jordan Chen",
108
+ lastReviewed: "10/01/2025",
109
+ },
110
+ {
111
+ id: "11",
112
+ title: "LCME / programmatic review evidence",
113
+ category: "Accreditation",
114
+ status: "pending",
115
+ dueDate: "07/30/2026",
116
+ owner: "Sam Patel",
117
+ lastReviewed: "—",
118
+ },
119
+ {
120
+ id: "12",
121
+ title: "Sharps injury log audit",
122
+ category: "OSHA",
123
+ status: "overdue",
124
+ dueDate: "02/20/2026",
125
+ owner: "Taylor Brooks",
126
+ lastReviewed: "02/20/2025",
127
+ },
128
+ {
129
+ id: "13",
130
+ title: "Business associate agreement inventory",
131
+ category: "HIPAA",
132
+ status: "compliant",
133
+ dueDate: "10/15/2026",
134
+ owner: "Alex Rivera",
135
+ lastReviewed: "03/01/2026",
136
+ },
137
+ {
138
+ id: "14",
139
+ title: "Student immunization tracking",
140
+ category: "Clinical",
141
+ status: "due_soon",
142
+ dueDate: "04/18/2026",
143
+ owner: "Jordan Chen",
144
+ lastReviewed: "09/01/2025",
145
+ },
146
+ ]
@@ -0,0 +1,105 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // Mock data — Dashboard
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+
5
+ import type { MetricItem, MetricInsight } from "@/components/key-metrics"
6
+
7
+ export const DASHBOARD_METRICS: MetricItem[] = [
8
+ { id: "pending-requests", label: "Pending requests", value: "23", delta: "+5", trend: "up", href: "/data-list#pending" },
9
+ { id: "confirmed-placements", label: "Confirmed placements", value: "89", delta: "+12", trend: "up", href: "/data-list#confirmed" },
10
+ { id: "pending-reviews", label: "Pending Reviews", value: "8", delta: "-3", trend: "down", href: "/data-list#reviews" },
11
+ { id: "available-slots", label: "Available Slots", value: "156", delta: "+24", trend: "up", href: "/data-list#slots" },
12
+ { id: "new-applications", label: "New Applications", value: "34", delta: "+7", trend: "up", href: "/data-list#applications"},
13
+ { id: "compliance-rate", label: "Compliance Rate", value: "98%", delta: "+2", trend: "up", href: "/data-list#compliance" },
14
+ ]
15
+
16
+ export const DASHBOARD_INSIGHT: MetricInsight = {
17
+ title: "Review Bottleneck",
18
+ description: "8 reviews pending with 23 new requests waiting. Clear reviews to maintain placement velocity.",
19
+ href: "/reviews",
20
+ severity: "warning",
21
+ actionLabel: "Ask Leo",
22
+ }
23
+
24
+ /** Integer scores as whole numbers; decimals to one place. */
25
+ export function formatBandScore(n: number) {
26
+ return Number.isInteger(n) ? String(n) : n.toFixed(1)
27
+ }
28
+
29
+ /** Clamp a score to the published min–max scale. */
30
+ export function clampScoreToBand(score: number, scaleMin: number, scaleMax: number) {
31
+ return Math.min(scaleMax, Math.max(scaleMin, score))
32
+ }
33
+
34
+ /**
35
+ * Map a score to 0–100 along the band for bar/radial geometry (Recharts domain).
36
+ * When score is outside the band it is clamped for display.
37
+ */
38
+ export function scoreToTrackPercent(score: number, scaleMin: number, scaleMax: number): number {
39
+ if (scaleMax <= scaleMin) return 0
40
+ const s = clampScoreToBand(score, scaleMin, scaleMax)
41
+ return Math.min(100, Math.max(0, ((s - scaleMin) / (scaleMax - scaleMin)) * 100))
42
+ }
43
+
44
+ /** One assessment row: student score vs class average on a fixed min–max scale. */
45
+ export interface StudentScoreMetric {
46
+ id: string
47
+ label: string
48
+ scaleMin: number
49
+ scaleMax: number
50
+ classAverage: number
51
+ studentScore: number
52
+ /** ChartCard description; defaults to suite `description` */
53
+ description?: string
54
+ /** Pill on the dashed marker (default "Class avg") */
55
+ averageMarkerLabel?: string
56
+ }
57
+
58
+ export interface StudentScoreRadial {
59
+ title: string
60
+ /** Shown under the large center score */
61
+ caption: string
62
+ scaleMin: number
63
+ scaleMax: number
64
+ classAverage: number
65
+ studentScore: number
66
+ }
67
+
68
+ export interface DashboardStudentScoresData {
69
+ title: string
70
+ description?: string
71
+ metrics: StudentScoreMetric[]
72
+ radial: StudentScoreRadial
73
+ }
74
+
75
+ /** Example: student 75 on scale 50–80, class average 60 (same band). */
76
+ export const DASHBOARD_STUDENT_SCORES: DashboardStudentScoresData = {
77
+ title: "Student scores",
78
+ description: "Your score vs class average on the published scale (reference).",
79
+ metrics: [
80
+ {
81
+ id: "midterm",
82
+ label: "Midterm exam",
83
+ scaleMin: 50,
84
+ scaleMax: 80,
85
+ classAverage: 60,
86
+ studentScore: 75,
87
+ },
88
+ {
89
+ id: "practical",
90
+ label: "Clinical practical",
91
+ scaleMin: 50,
92
+ scaleMax: 80,
93
+ classAverage: 62,
94
+ studentScore: 71,
95
+ },
96
+ ],
97
+ radial: {
98
+ title: "Term snapshot",
99
+ caption: "Midterm focus",
100
+ scaleMin: 50,
101
+ scaleMax: 80,
102
+ classAverage: 60,
103
+ studentScore: 75,
104
+ },
105
+ }