@exxatdesignux/ui 0.0.5 → 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.
- package/bin/init.mjs +29 -0
- package/package.json +7 -2
- package/template/.nvmrc +1 -0
- package/template/.prettierignore +7 -0
- package/template/.prettierrc +11 -0
- package/template/AGENTS.md +485 -0
- package/template/Logo/Exxat_Prism.svg +39 -0
- package/template/Logo/Exxat_one.svg +36 -0
- package/template/README.md +58 -0
- package/template/app/(app)/compliance/page.tsx +10 -0
- package/template/app/(app)/dashboard/loading.tsx +18 -0
- package/template/app/(app)/dashboard/page.tsx +36 -0
- package/template/app/(app)/data-list/[id]/page.tsx +28 -0
- package/template/app/(app)/data-list/new/page.tsx +31 -0
- package/template/app/(app)/data-list/page.tsx +10 -0
- package/template/app/(app)/error.tsx +43 -0
- package/template/app/(app)/help/page.tsx +34 -0
- package/template/app/(app)/layout.tsx +54 -0
- package/template/app/(app)/loading.tsx +18 -0
- package/template/app/(app)/question-bank/page.tsx +10 -0
- package/template/app/(app)/rotations/page.tsx +15 -0
- package/template/app/(app)/settings/page.tsx +17 -0
- package/template/app/(app)/sites/all/page.tsx +13 -0
- package/template/app/(app)/team/page.tsx +10 -0
- package/template/app/favicon.ico +0 -0
- package/template/app/globals.css +1811 -0
- package/template/app/layout.tsx +95 -0
- package/template/app/page.tsx +9 -0
- package/template/components/.gitkeep +0 -0
- package/template/components/app-sidebar-dynamic.tsx +15 -0
- package/template/components/app-sidebar.tsx +901 -0
- package/template/components/ask-leo-composer.tsx +216 -0
- package/template/components/ask-leo-sidebar.tsx +509 -0
- package/template/components/chart-area-interactive.tsx +293 -0
- package/template/components/charts-overview.tsx +2321 -0
- package/template/components/command-menu-01.tsx +133 -0
- package/template/components/command-menu-02.tsx +386 -0
- package/template/components/command-menu.tsx +182 -0
- package/template/components/compliance-board-view.tsx +134 -0
- package/template/components/compliance-client.tsx +92 -0
- package/template/components/compliance-list-view.tsx +59 -0
- package/template/components/compliance-page-header.tsx +89 -0
- package/template/components/compliance-table.tsx +525 -0
- package/template/components/dashboard-onboarding-gallery.tsx +13 -0
- package/template/components/dashboard-onboarding.tsx +21 -0
- package/template/components/dashboard-promo-banner.tsx +67 -0
- package/template/components/dashboard-quota-progress-card.tsx +369 -0
- package/template/components/dashboard-report-charts.tsx +69 -0
- package/template/components/dashboard-section-heading.tsx +68 -0
- package/template/components/dashboard-tabs.tsx +598 -0
- package/template/components/data-list-client.tsx +239 -0
- package/template/components/data-list-table-cells.test.tsx +22 -0
- package/template/components/data-list-table-cells.tsx +173 -0
- package/template/components/data-list-table.tsx +879 -0
- package/template/components/data-table/filter-date-calendar.tsx +38 -0
- package/template/components/data-table/filter-text-value-input.tsx +77 -0
- package/template/components/data-table/index.tsx +1612 -0
- package/template/components/data-table/pagination.tsx +256 -0
- package/template/components/data-table/types.ts +91 -0
- package/template/components/data-table/use-table-state.ts +566 -0
- package/template/components/data-view-dashboard-charts-compliance.tsx +960 -0
- package/template/components/data-view-dashboard-charts-team.tsx +968 -0
- package/template/components/data-view-dashboard-charts.tsx +1668 -0
- package/template/components/data-views/board-card-primitives.tsx +93 -0
- package/template/components/data-views/index.ts +41 -0
- package/template/components/data-views/list-page-board-card.tsx +192 -0
- package/template/components/data-views/list-page-board-template.tsx +122 -0
- package/template/components/data-views/placement-board-card.tsx +262 -0
- package/template/components/export-drawer.tsx +375 -0
- package/template/components/exxat-product-logo.tsx +453 -0
- package/template/components/form-layout-01.tsx +131 -0
- package/template/components/getting-started.tsx +625 -0
- package/template/components/key-metrics.tsx +920 -0
- package/template/components/leo-insight-indicator.tsx +364 -0
- package/template/components/leo-typing-dots.tsx +121 -0
- package/template/components/list-hub-status-badge.tsx +51 -0
- package/template/components/list-page-dashboard-charts.tsx +18 -0
- package/template/components/nav-documents.tsx +89 -0
- package/template/components/nav-main.tsx +58 -0
- package/template/components/nav-secondary.tsx +64 -0
- package/template/components/nav-user.tsx +190 -0
- package/template/components/new-placement-back-btn.tsx +28 -0
- package/template/components/new-placement-form.tsx +1066 -0
- package/template/components/onboarding/index.ts +4 -0
- package/template/components/onboarding/onboarding-01.tsx +7 -0
- package/template/components/onboarding/onboarding-02.tsx +7 -0
- package/template/components/onboarding/onboarding-03.tsx +7 -0
- package/template/components/onboarding/onboarding-04.tsx +7 -0
- package/template/components/page-header.tsx +57 -0
- package/template/components/placement-detail.tsx +438 -0
- package/template/components/placements-board-view.tsx +404 -0
- package/template/components/placements-list-view.tsx +285 -0
- package/template/components/placements-page-header.tsx +160 -0
- package/template/components/placements-table-columns.tsx +639 -0
- package/template/components/product-switcher.tsx +116 -0
- package/template/components/question-bank-board-view.tsx +205 -0
- package/template/components/question-bank-client.tsx +77 -0
- package/template/components/question-bank-list-view.tsx +59 -0
- package/template/components/question-bank-page-header.tsx +89 -0
- package/template/components/question-bank-table.tsx +586 -0
- package/template/components/rotations-empty-state.tsx +47 -0
- package/template/components/rotations-panel-activator.tsx +8 -0
- package/template/components/secondary-nav.tsx +394 -0
- package/template/components/secondary-panel.tsx +239 -0
- package/template/components/section-cards.tsx +106 -0
- package/template/components/settings-appearance-card.tsx +424 -0
- package/template/components/settings-client.tsx +537 -0
- package/template/components/settings-form-row.tsx +42 -0
- package/template/components/sidebar-auto-collapse.tsx +23 -0
- package/template/components/sidebar-auto-open.tsx +18 -0
- package/template/components/sidebar-shell.tsx +37 -0
- package/template/components/site-header.tsx +93 -0
- package/template/components/sites-all-client.tsx +154 -0
- package/template/components/sites-board-view.tsx +67 -0
- package/template/components/sites-list-view.tsx +47 -0
- package/template/components/sites-table.tsx +312 -0
- package/template/components/system-banner-slot.tsx +66 -0
- package/template/components/table-properties/column-row.tsx +90 -0
- package/template/components/table-properties/draggable-list.ts +49 -0
- package/template/components/table-properties/drawer-button.tsx +231 -0
- package/template/components/table-properties/drawer.tsx +1102 -0
- package/template/components/table-properties/filter-card.tsx +251 -0
- package/template/components/table-properties/index.ts +22 -0
- package/template/components/table-properties/sort-card.tsx +59 -0
- package/template/components/table-properties/types.ts +124 -0
- package/template/components/task-list-panel.tsx +98 -0
- package/template/components/task-priority-badge.tsx +28 -0
- package/template/components/team-board-view.tsx +114 -0
- package/template/components/team-client.tsx +93 -0
- package/template/components/team-list-view.tsx +62 -0
- package/template/components/team-page-header.tsx +92 -0
- package/template/components/team-table.tsx +525 -0
- package/template/components/templates/list-page.tsx +576 -0
- package/template/components/templates/primary-page-template.tsx +56 -0
- package/template/components/theme-color-sync.tsx +32 -0
- package/template/components/theme-provider.tsx +71 -0
- package/template/components/tinted-icon-disc.tsx +53 -0
- package/template/components/ui/ai-thinking-surface.tsx +121 -0
- package/template/components/ui/avatar.tsx +1 -0
- package/template/components/ui/badge.tsx +1 -0
- package/template/components/ui/banner.tsx +1 -0
- package/template/components/ui/breadcrumb.tsx +1 -0
- package/template/components/ui/button.tsx +1 -0
- package/template/components/ui/calendar.tsx +1 -0
- package/template/components/ui/card.tsx +1 -0
- package/template/components/ui/chart.tsx +1 -0
- package/template/components/ui/checkbox.tsx +1 -0
- package/template/components/ui/coach-mark.tsx +1 -0
- package/template/components/ui/collapsible.tsx +1 -0
- package/template/components/ui/command.tsx +1 -0
- package/template/components/ui/date-picker-field.tsx +1 -0
- package/template/components/ui/dialog.tsx +1 -0
- package/template/components/ui/dot-pattern.tsx +159 -0
- package/template/components/ui/drag-handle-grip.tsx +1 -0
- package/template/components/ui/drawer.tsx +1 -0
- package/template/components/ui/dropdown-menu.tsx +1 -0
- package/template/components/ui/field.tsx +1 -0
- package/template/components/ui/form.tsx +1 -0
- package/template/components/ui/input-group.tsx +1 -0
- package/template/components/ui/input-mask.tsx +1 -0
- package/template/components/ui/input.tsx +1 -0
- package/template/components/ui/kbd.tsx +1 -0
- package/template/components/ui/label.tsx +1 -0
- package/template/components/ui/leo-icon.tsx +726 -0
- package/template/components/ui/payment-card-fields.tsx +1 -0
- package/template/components/ui/popover.tsx +1 -0
- package/template/components/ui/radio-group.tsx +1 -0
- package/template/components/ui/select.tsx +1 -0
- package/template/components/ui/selection-tile-grid.tsx +1 -0
- package/template/components/ui/separator.tsx +1 -0
- package/template/components/ui/sheet.tsx +1 -0
- package/template/components/ui/sidebar.tsx +1 -0
- package/template/components/ui/skeleton.tsx +1 -0
- package/template/components/ui/sonner.tsx +1 -0
- package/template/components/ui/status-badge.tsx +1 -0
- package/template/components/ui/table.tsx +1 -0
- package/template/components/ui/tabs.tsx +1 -0
- package/template/components/ui/textarea.tsx +1 -0
- package/template/components/ui/tip.tsx +1 -0
- package/template/components/ui/toggle-group.tsx +1 -0
- package/template/components/ui/toggle-switch.tsx +1 -0
- package/template/components/ui/toggle.tsx +1 -0
- package/template/components/ui/tooltip.tsx +1 -0
- package/template/components/ui/view-segmented-control.tsx +1 -0
- package/template/components.json +27 -0
- package/template/contexts/chart-variant-context.tsx +35 -0
- package/template/contexts/command-menu-context.tsx +28 -0
- package/template/contexts/dashboard-view-context.tsx +35 -0
- package/template/contexts/product-context.tsx +38 -0
- package/template/contexts/system-banner-context.tsx +127 -0
- package/template/docs/command-menu-pattern.md +45 -0
- package/template/docs/data-views-pattern.md +160 -0
- package/template/ecosystem.config.cjs +20 -0
- package/template/eslint.config.mjs +18 -0
- package/template/fontawesome-subset.manifest.json +190 -0
- package/template/hooks/.gitkeep +0 -0
- package/template/hooks/use-app-theme.ts +1 -0
- package/template/hooks/use-coach-mark.ts +1 -0
- package/template/hooks/use-mobile.ts +1 -0
- package/template/hooks/use-mod-key-label.ts +1 -0
- package/template/lib/.gitkeep +0 -0
- package/template/lib/ask-leo-route-context.ts +133 -0
- package/template/lib/chart-keyboard-selection.test.ts +20 -0
- package/template/lib/chart-keyboard-selection.ts +17 -0
- package/template/lib/chart-line-dash.ts +16 -0
- package/template/lib/coach-mark-registry.ts +68 -0
- package/template/lib/command-menu-config.ts +127 -0
- package/template/lib/command-menu-search-data.ts +44 -0
- package/template/lib/conditional-rule-match.ts +32 -0
- package/template/lib/dashboard-customize-coach-mark.ts +18 -0
- package/template/lib/dashboard-layout-merge.ts +63 -0
- package/template/lib/data-list-display-options.ts +35 -0
- package/template/lib/data-list-persistence.ts +280 -0
- package/template/lib/data-list-view-surface.ts +58 -0
- package/template/lib/data-list-view.ts +29 -0
- package/template/lib/data-view-dashboard-storage.ts +101 -0
- package/template/lib/date-filter.ts +8 -0
- package/template/lib/dev-log.test.ts +28 -0
- package/template/lib/dev-log.ts +8 -0
- package/template/lib/editable-target.ts +10 -0
- package/template/lib/floating-sheet-panel.ts +72 -0
- package/template/lib/initials-from-name.ts +7 -0
- package/template/lib/list-page-table-properties.ts +52 -0
- package/template/lib/list-status-badges.ts +168 -0
- package/template/lib/logo-dev.ts +12 -0
- package/template/lib/mock/compliance-kpi.ts +61 -0
- package/template/lib/mock/compliance.ts +146 -0
- package/template/lib/mock/dashboard.ts +105 -0
- package/template/lib/mock/navigation.tsx +231 -0
- package/template/lib/mock/placements-kpi.ts +134 -0
- package/template/lib/mock/placements.ts +183 -0
- package/template/lib/mock/question-bank-kpi.ts +61 -0
- package/template/lib/mock/question-bank.ts +142 -0
- package/template/lib/mock/sites-directory.ts +16 -0
- package/template/lib/mock/sites-kpi.ts +25 -0
- package/template/lib/mock/team-kpi.ts +60 -0
- package/template/lib/mock/team.ts +118 -0
- package/template/lib/motion-ui.ts +17 -0
- package/template/lib/placement-board-card-layout.ts +79 -0
- package/template/lib/placement-lifecycle.ts +5 -0
- package/template/lib/row-height.ts +10 -0
- package/template/lib/stock-portrait.ts +11 -0
- package/template/lib/utils.test.ts +13 -0
- package/template/lib/utils.ts +1 -0
- package/template/next.config.mjs +15 -0
- package/template/package.json +83 -0
- package/template/postcss.config.mjs +8 -0
- package/template/public/.gitkeep +0 -0
- package/template/public/Illustration/Rotation.svg +74 -0
- package/template/public/avatars/user.svg +11 -0
- package/template/public/favicon/favicon.ico +0 -0
- package/template/public/favicon.ico +0 -0
- package/template/public/logos/exxat-one.svg +36 -0
- package/template/public/logos/exxat-prism.svg +39 -0
- package/template/public/mock-schools/emory.svg +4 -0
- package/template/public/mock-schools/rush.svg +4 -0
- package/template/scripts/fontawesome-subset-audit.mjs +190 -0
- package/template/scripts/pm2-startup-macos.sh +13 -0
- package/template/skills-lock.json +10 -0
- package/template/stores/app-store.ts +33 -0
- package/template/tests/setup.ts +1 -0
- package/template/tsconfig.json +35 -0
- package/template/types/react-payment-inputs.d.ts +19 -0
- package/template/vitest.config.ts +18 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// Mock data — Navigation
|
|
3
|
+
// Uses .tsx because icons are JSX (<i> elements).
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
import type * as React from "react"
|
|
7
|
+
|
|
8
|
+
import { logoDevUrl } from "@/lib/logo-dev"
|
|
9
|
+
import { stockPortraitUrl } from "@/lib/stock-portrait"
|
|
10
|
+
|
|
11
|
+
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
/** Flat sidebar link (screenshot: primary + Documents + utilities) */
|
|
14
|
+
export interface NavLinkItem {
|
|
15
|
+
key: string
|
|
16
|
+
title: string
|
|
17
|
+
url: string
|
|
18
|
+
icon: React.ReactNode
|
|
19
|
+
iconActive?: React.ReactNode
|
|
20
|
+
/** Optional child links — renders as a collapsible sub-menu */
|
|
21
|
+
children?: NavLinkItem[]
|
|
22
|
+
/** Optional inline badge — count (number) or label text (string).
|
|
23
|
+
* Special string values get distinct colors:
|
|
24
|
+
* "New" → green, "Beta" → amber, other strings → brand/primary */
|
|
25
|
+
badge?: number | string
|
|
26
|
+
/**
|
|
27
|
+
* When this collapsible section uses `SecondaryPanel`, set the panel id so
|
|
28
|
+
* child links can reopen the panel on click while already on the same route
|
|
29
|
+
* (Next.js Link does not navigate on same href).
|
|
30
|
+
*/
|
|
31
|
+
secondaryPanel?: string
|
|
32
|
+
/**
|
|
33
|
+
* When several children share the same `url` (hub route), only this child’s
|
|
34
|
+
* key receives `data-active` for that pathname — otherwise every sub-row
|
|
35
|
+
* looks selected (same issue as duplicate `href` in any nav).
|
|
36
|
+
*/
|
|
37
|
+
primaryHubChildKey?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── Team / school switcher mock data ─────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
export interface NavProgram { id: string; name: string }
|
|
43
|
+
export interface NavSchool {
|
|
44
|
+
id: string
|
|
45
|
+
name: string
|
|
46
|
+
/** URL of school logo — used as the avatar image */
|
|
47
|
+
logo: string
|
|
48
|
+
/** Two-letter abbreviation shown as AvatarFallback */
|
|
49
|
+
initials: string
|
|
50
|
+
programs: NavProgram[]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const NAV_SCHOOLS: NavSchool[] = [
|
|
54
|
+
{
|
|
55
|
+
id: "jhu",
|
|
56
|
+
name: "Johns Hopkins University",
|
|
57
|
+
logo: logoDevUrl("jhu.edu"),
|
|
58
|
+
initials: "JH",
|
|
59
|
+
programs: [
|
|
60
|
+
{ id: "som", name: "School of Medicine" },
|
|
61
|
+
{ id: "son", name: "School of Nursing" },
|
|
62
|
+
{ id: "sph", name: "Bloomberg School of Public Health" },
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: "mayo",
|
|
67
|
+
name: "Mayo Clinic Alix School of Medicine",
|
|
68
|
+
logo: logoDevUrl("mayoclinic.org"),
|
|
69
|
+
initials: "MC",
|
|
70
|
+
programs: [
|
|
71
|
+
{ id: "md", name: "Doctor of Medicine" },
|
|
72
|
+
{ id: "bms", name: "Biomedical Sciences" },
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
export const NAV_SCHOOL_DEFAULT = NAV_SCHOOLS[0]
|
|
78
|
+
export const NAV_PROGRAM_DEFAULT = NAV_SCHOOLS[0].programs[0]
|
|
79
|
+
|
|
80
|
+
// ── Primary navigation ────────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
export const NAV_PRIMARY: NavLinkItem[] = [
|
|
83
|
+
{
|
|
84
|
+
key: "dashboard",
|
|
85
|
+
title: "Dashboard",
|
|
86
|
+
url: "/dashboard",
|
|
87
|
+
icon: <i className="fa-light fa-grid-2" aria-hidden="true" />,
|
|
88
|
+
iconActive: <i className="fa-solid fa-grid-2" aria-hidden="true" />,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
key: "question-bank",
|
|
92
|
+
title: "Question bank",
|
|
93
|
+
url: "/question-bank",
|
|
94
|
+
icon: <i className="fa-light fa-books" aria-hidden="true" />,
|
|
95
|
+
iconActive: <i className="fa-solid fa-books" aria-hidden="true" />,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
key: "data-list",
|
|
99
|
+
title: "Placements",
|
|
100
|
+
url: "/data-list",
|
|
101
|
+
icon: <i className="fa-light fa-user-graduate" aria-hidden="true" />,
|
|
102
|
+
iconActive: <i className="fa-solid fa-user-graduate" aria-hidden="true" />,
|
|
103
|
+
badge: 24,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
key: "rotations",
|
|
107
|
+
title: "Rotations",
|
|
108
|
+
url: "/rotations",
|
|
109
|
+
icon: <i className="fa-light fa-arrows-rotate" aria-hidden="true" />,
|
|
110
|
+
iconActive: <i className="fa-solid fa-arrows-rotate" aria-hidden="true" />,
|
|
111
|
+
secondaryPanel: "rotations",
|
|
112
|
+
primaryHubChildKey: "view-all-rotations",
|
|
113
|
+
children: [
|
|
114
|
+
{
|
|
115
|
+
key: "rotation-1",
|
|
116
|
+
title: "Clinical Nursing — Fall 2026",
|
|
117
|
+
url: "/rotations",
|
|
118
|
+
icon: <i className="fa-light fa-folder" aria-hidden="true" />,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
key: "rotation-2",
|
|
122
|
+
title: "PT Fieldwork — Spring 2026",
|
|
123
|
+
url: "/rotations",
|
|
124
|
+
icon: <i className="fa-light fa-folder" aria-hidden="true" />,
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
key: "rotation-3",
|
|
128
|
+
title: "OT Level II — Summer 2026",
|
|
129
|
+
url: "/rotations",
|
|
130
|
+
icon: <i className="fa-light fa-folder" aria-hidden="true" />,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
key: "view-all-rotations",
|
|
134
|
+
title: "View all",
|
|
135
|
+
url: "/rotations",
|
|
136
|
+
icon: <i className="fa-light fa-arrow-right" aria-hidden="true" />,
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
key: "sites",
|
|
142
|
+
title: "Sites",
|
|
143
|
+
url: "/sites/all",
|
|
144
|
+
icon: <i className="fa-light fa-hospital" aria-hidden="true" />,
|
|
145
|
+
iconActive: <i className="fa-solid fa-hospital" aria-hidden="true" />,
|
|
146
|
+
children: Array.from({ length: 45 }, (_, i) => ({
|
|
147
|
+
key: `site-${i + 1}`,
|
|
148
|
+
title: `Site ${String(i + 1).padStart(2, "0")}`,
|
|
149
|
+
url: `/sites/all#site-${i + 1}`,
|
|
150
|
+
icon: <i className="fa-light fa-hospital" aria-hidden="true" />,
|
|
151
|
+
})),
|
|
152
|
+
},
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
// ── Documents section ───────────────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
export const NAV_DOCUMENTS_LABEL = "Documents"
|
|
158
|
+
|
|
159
|
+
export const NAV_DOCUMENTS: NavLinkItem[] = [
|
|
160
|
+
{
|
|
161
|
+
key: "word-assistant",
|
|
162
|
+
title: "Word Assistant",
|
|
163
|
+
url: "#",
|
|
164
|
+
icon: <i className="fa-light fa-file-pen" aria-hidden="true" />,
|
|
165
|
+
iconActive: <i className="fa-solid fa-file-pen" aria-hidden="true" />,
|
|
166
|
+
badge: "Beta",
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
key: "more",
|
|
170
|
+
title: "More",
|
|
171
|
+
url: "#",
|
|
172
|
+
icon: <i className="fa-light fa-ellipsis" aria-hidden="true" />,
|
|
173
|
+
iconActive: <i className="fa-solid fa-ellipsis" aria-hidden="true" />,
|
|
174
|
+
},
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
// ── Quick actions (above primary nav) + bottom utilities ───────────────────────
|
|
178
|
+
|
|
179
|
+
/** `opensCommandMenu` wires ⌘K instead of `url`. */
|
|
180
|
+
export interface NavSecondaryItem {
|
|
181
|
+
key: string
|
|
182
|
+
title: string
|
|
183
|
+
url: string
|
|
184
|
+
icon: React.ReactNode
|
|
185
|
+
/** Solid / filled variant when the route is active — matches primary `NavLinkItem` rows. */
|
|
186
|
+
iconActive?: React.ReactNode
|
|
187
|
+
opensCommandMenu?: boolean
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Search + Notifications — rendered above Dashboard in the sidebar. */
|
|
191
|
+
export const NAV_QUICK_ACTIONS: NavSecondaryItem[] = [
|
|
192
|
+
{
|
|
193
|
+
key: "command-menu",
|
|
194
|
+
title: "Search or ask Leo",
|
|
195
|
+
url: "#",
|
|
196
|
+
icon: <i className="fa-light fa-magnifying-glass" aria-hidden="true" />,
|
|
197
|
+
opensCommandMenu: true,
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
key: "notifications",
|
|
201
|
+
title: "Notifications",
|
|
202
|
+
url: "#",
|
|
203
|
+
icon: <i className="fa-light fa-bell" aria-hidden="true" />,
|
|
204
|
+
},
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
export const NAV_SECONDARY: NavSecondaryItem[] = [
|
|
208
|
+
{
|
|
209
|
+
key: "settings",
|
|
210
|
+
title: "Settings",
|
|
211
|
+
url: "/settings",
|
|
212
|
+
icon: <i className="fa-light fa-gear" aria-hidden="true" />,
|
|
213
|
+
iconActive: <i className="fa-solid fa-gear" aria-hidden="true" />,
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
key: "help",
|
|
217
|
+
title: "Get Help",
|
|
218
|
+
url: "/help",
|
|
219
|
+
icon: <i className="fa-light fa-circle-question" aria-hidden="true" />,
|
|
220
|
+
iconActive: <i className="fa-solid fa-circle-question" aria-hidden="true" />,
|
|
221
|
+
},
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
// ── User ──────────────────────────────────────────────────────────────────────
|
|
225
|
+
|
|
226
|
+
export const NAV_USER = {
|
|
227
|
+
name: "Jordan Rivera",
|
|
228
|
+
email: "jordan.rivera@jhmi.edu",
|
|
229
|
+
/** Stock portrait (randomuser.me); stable for this seed */
|
|
230
|
+
avatar: stockPortraitUrl("exxat-nav-user-jordan-rivera"),
|
|
231
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// Placements page — KPI strip + insight (data-list; dashboard view uses row-driven helpers)
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
import type { MetricInsight, MetricItem } from "@/components/key-metrics"
|
|
6
|
+
import { ALL_PLACEMENTS, type Placement, type Status } from "./placements"
|
|
7
|
+
|
|
8
|
+
function statusCount(status: Status): number {
|
|
9
|
+
return ALL_PLACEMENTS.filter((p) => p.status === status).length
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* KPIs from the current filtered placement set (table/list/board/dashboard shared state).
|
|
14
|
+
* Use for the dashboard view tab; optional for the template metrics strip when you want parity.
|
|
15
|
+
*/
|
|
16
|
+
export function placementKpiMetricsFromRows(rows: Placement[]): MetricItem[] {
|
|
17
|
+
const total = rows.length
|
|
18
|
+
const startingWeek = rows.filter(p => p.daysUntilStart >= 0 && p.daysUntilStart <= 7).length
|
|
19
|
+
const alerts = rows.filter(p => {
|
|
20
|
+
const r = p.readiness.toLowerCase()
|
|
21
|
+
return r.includes("risk") || r.includes("blocked")
|
|
22
|
+
}).length
|
|
23
|
+
const complete = rows.filter(p => p.compliance === "Complete").length
|
|
24
|
+
const avgPct = total > 0 ? Math.round((complete / total) * 100) : 0
|
|
25
|
+
|
|
26
|
+
return [
|
|
27
|
+
{
|
|
28
|
+
id: "total-placements",
|
|
29
|
+
label: "Total Placements",
|
|
30
|
+
value: total,
|
|
31
|
+
delta: "—",
|
|
32
|
+
trend: "neutral",
|
|
33
|
+
href: "#",
|
|
34
|
+
metricVariant: "hero",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: "starting-week",
|
|
38
|
+
label: "Starting This Week",
|
|
39
|
+
value: startingWeek,
|
|
40
|
+
delta: "—",
|
|
41
|
+
trend: startingWeek > 0 ? "up" : "neutral",
|
|
42
|
+
href: "#",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "compliance-alerts",
|
|
46
|
+
label: "Readiness alerts",
|
|
47
|
+
value: alerts,
|
|
48
|
+
delta: "—",
|
|
49
|
+
trend: alerts > 0 ? "up" : "neutral",
|
|
50
|
+
href: "#",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: "avg-compliance",
|
|
54
|
+
label: "Compliance complete",
|
|
55
|
+
value: `${avgPct}%`,
|
|
56
|
+
delta: "—",
|
|
57
|
+
trend: "neutral",
|
|
58
|
+
href: "#",
|
|
59
|
+
},
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function placementKpiInsightFromRows(rows: Placement[]): MetricInsight {
|
|
64
|
+
const pending = rows.filter(p => p.status === "pending").length
|
|
65
|
+
const inReview = rows.filter(p => p.status === "under-review").length
|
|
66
|
+
const n = rows.length
|
|
67
|
+
return {
|
|
68
|
+
title: "Pending Reviews",
|
|
69
|
+
description:
|
|
70
|
+
n > 0
|
|
71
|
+
? `${pending} pending, ${inReview} in review in this view. Clear the queue to keep placements moving.`
|
|
72
|
+
: "No placements match the current filters.",
|
|
73
|
+
href: "/placements/reviews",
|
|
74
|
+
severity: pending + inReview > 0 ? "warning" : "info",
|
|
75
|
+
actionLabel: "Ask Leo",
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Placements KPI row — matches design reference (totals + operational metrics).
|
|
81
|
+
*/
|
|
82
|
+
export const PLACEMENT_KPI_METRICS: MetricItem[] = [
|
|
83
|
+
{
|
|
84
|
+
id: "total-placements",
|
|
85
|
+
label: "Total Placements",
|
|
86
|
+
value: 50,
|
|
87
|
+
delta: "+12",
|
|
88
|
+
trend: "up",
|
|
89
|
+
href: "#all",
|
|
90
|
+
metricVariant: "hero",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "starting-week",
|
|
94
|
+
label: "Starting This Week",
|
|
95
|
+
value: 0,
|
|
96
|
+
delta: "-5",
|
|
97
|
+
trend: "down",
|
|
98
|
+
href: "#starting",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: "compliance-alerts",
|
|
102
|
+
label: "Compliance Alerts",
|
|
103
|
+
value: 23,
|
|
104
|
+
delta: "+13",
|
|
105
|
+
trend: "up",
|
|
106
|
+
href: "#alerts",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: "avg-compliance",
|
|
110
|
+
label: "Avg Compliance",
|
|
111
|
+
value: "87%",
|
|
112
|
+
delta: "+3",
|
|
113
|
+
trend: "up",
|
|
114
|
+
href: "#compliance",
|
|
115
|
+
},
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Insight copy still derived from live placement rows for the queue narrative.
|
|
120
|
+
*/
|
|
121
|
+
export function getPlacementInsight(): MetricInsight {
|
|
122
|
+
const pending = statusCount("pending")
|
|
123
|
+
const inReview = statusCount("under-review")
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
title: "Pending Reviews",
|
|
127
|
+
description: `${pending} pending, ${inReview} in review. Clear the queue to keep placements moving.`,
|
|
128
|
+
href: "/placements/reviews",
|
|
129
|
+
severity: "warning",
|
|
130
|
+
actionLabel: "Ask Leo",
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export const PLACEMENT_KPI_INSIGHT = getPlacementInsight()
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// Mock data — Placements
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
export type Status = "confirmed" | "pending" | "under-review" | "rejected" | "completed"
|
|
6
|
+
|
|
7
|
+
/** Which lifecycle tab owns this row (All shows every row). */
|
|
8
|
+
export type PlacementPhase = "upcoming" | "ongoing" | "completed"
|
|
9
|
+
|
|
10
|
+
export interface Placement extends Record<string, unknown> {
|
|
11
|
+
id: number
|
|
12
|
+
student: string
|
|
13
|
+
email: string
|
|
14
|
+
initials: string
|
|
15
|
+
program: string
|
|
16
|
+
site: string
|
|
17
|
+
siteAddress: string
|
|
18
|
+
status: Status
|
|
19
|
+
start: string
|
|
20
|
+
duration: string
|
|
21
|
+
supervisor: string
|
|
22
|
+
placementPhase: PlacementPhase
|
|
23
|
+
/** Display title for internship column */
|
|
24
|
+
internship: string
|
|
25
|
+
specialization: string
|
|
26
|
+
/** Upcoming */
|
|
27
|
+
compliance: string
|
|
28
|
+
daysUntilStart: number
|
|
29
|
+
readiness: string
|
|
30
|
+
/** Ongoing */
|
|
31
|
+
progressWeeksDone: number
|
|
32
|
+
progressWeeksTotal: number
|
|
33
|
+
endDate: string
|
|
34
|
+
lastCheckin: string
|
|
35
|
+
/** Completed */
|
|
36
|
+
completionDate: string
|
|
37
|
+
finalStatus: string
|
|
38
|
+
rating: number
|
|
39
|
+
suggestedToHire: string
|
|
40
|
+
/** Visual indicator — row has new activity or was recently updated */
|
|
41
|
+
isNew?: boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const ALL_PLACEMENTS: Placement[] = [
|
|
45
|
+
{
|
|
46
|
+
id: 1, student: "Sarah Johnson", email: "s.johnson@college.edu", initials: "SJ", program: "Nursing", site: "City Medical Center",
|
|
47
|
+
siteAddress: "1400 N Lake Shore Dr, Chicago, IL", status: "confirmed", start: "03/15/2026", duration: "12 wks", supervisor: "Dr. Patel",
|
|
48
|
+
placementPhase: "ongoing", internship: "Med-Surg Clinical I", specialization: "Adult Health", compliance: "Complete", daysUntilStart: 0,
|
|
49
|
+
readiness: "Ready", progressWeeksDone: 5, progressWeeksTotal: 12, endDate: "06/07/2026", lastCheckin: "Mar 22, 2026",
|
|
50
|
+
completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—", isNew: true,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 2, student: "Mike Chen", email: "m.chen@university.edu", initials: "MC", program: "Physical Therapy", site: "Metro Rehab",
|
|
54
|
+
siteAddress: "250 E Superior St, Chicago, IL", status: "pending", start: "04/01/2026", duration: "8 wks", supervisor: "Dr. Kim",
|
|
55
|
+
placementPhase: "upcoming", internship: "Outpatient PT Rotation", specialization: "Orthopedics", compliance: "In progress", daysUntilStart: 8,
|
|
56
|
+
readiness: "Ready", progressWeeksDone: 0, progressWeeksTotal: 8, endDate: "05/27/2026", lastCheckin: "—",
|
|
57
|
+
completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 3, student: "Amy Park", email: "a.park@school.edu", initials: "AP", program: "Occupational Therapy", site: "Bay Area Health",
|
|
61
|
+
siteAddress: "3100 Telegraph Ave, Oakland, CA", status: "under-review", start: "03/28/2026", duration: "10 wks", supervisor: "Unassigned",
|
|
62
|
+
placementPhase: "upcoming", internship: "Acute Care OT", specialization: "Hand Therapy", compliance: "Pending documents", daysUntilStart: 4,
|
|
63
|
+
readiness: "At risk", progressWeeksDone: 0, progressWeeksTotal: 10, endDate: "06/06/2026", lastCheckin: "—",
|
|
64
|
+
completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—", isNew: true,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 4, student: "James Williams", email: "j.williams@college.edu", initials: "JW", program: "Nursing", site: "Sunrise Hospital",
|
|
68
|
+
siteAddress: "5775 Wayzata Blvd, Minneapolis, MN", status: "confirmed", start: "04/07/2026", duration: "12 wks", supervisor: "Dr. Torres",
|
|
69
|
+
placementPhase: "ongoing", internship: "ICU Practicum", specialization: "Critical Care", compliance: "Complete", daysUntilStart: 0,
|
|
70
|
+
readiness: "Ready", progressWeeksDone: 2, progressWeeksTotal: 12, endDate: "06/30/2026", lastCheckin: "Mar 21, 2026",
|
|
71
|
+
completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 5, student: "Emily Davis", email: "e.davis@university.edu", initials: "ED", program: "Social Work", site: "Community Care",
|
|
75
|
+
siteAddress: "220 S State St, Chicago, IL", status: "rejected", start: "—", duration: "—", supervisor: "—",
|
|
76
|
+
placementPhase: "upcoming", internship: "Community SW Field", specialization: "Behavioral Health", compliance: "Incomplete", daysUntilStart: 0,
|
|
77
|
+
readiness: "Blocked", progressWeeksDone: 0, progressWeeksTotal: 10, endDate: "—", lastCheckin: "—",
|
|
78
|
+
completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 6, student: "Carlos Rivera", email: "c.rivera@school.edu", initials: "CR", program: "Physical Therapy", site: "Summit Sports Med",
|
|
82
|
+
siteAddress: "900 N Michigan Ave, Chicago, IL", status: "pending", start: "04/14/2026", duration: "8 wks", supervisor: "Dr. Lee",
|
|
83
|
+
placementPhase: "upcoming", internship: "Sports Medicine PT", specialization: "Sports Rehab", compliance: "Complete", daysUntilStart: 21,
|
|
84
|
+
readiness: "Ready", progressWeeksDone: 0, progressWeeksTotal: 8, endDate: "06/09/2026", lastCheckin: "—",
|
|
85
|
+
completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 7, student: "Priya Sharma", email: "p.sharma@college.edu", initials: "PS", program: "Nursing", site: "Harbor Medical",
|
|
89
|
+
siteAddress: "1200 W Harrison St, Chicago, IL", status: "confirmed", start: "03/22/2026", duration: "12 wks", supervisor: "Dr. Patel",
|
|
90
|
+
placementPhase: "ongoing", internship: "Pediatric Nursing", specialization: "Pediatrics", compliance: "Complete", daysUntilStart: 0,
|
|
91
|
+
readiness: "Ready", progressWeeksDone: 7, progressWeeksTotal: 12, endDate: "06/14/2026", lastCheckin: "Mar 23, 2026",
|
|
92
|
+
completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—", isNew: true,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: 8, student: "Tom Bradley", email: "t.bradley@university.edu", initials: "TB", program: "Occupational Therapy", site: "Valley Health",
|
|
96
|
+
siteAddress: "4646 N Marine Dr, Chicago, IL", status: "under-review", start: "04/03/2026", duration: "10 wks", supervisor: "Dr. Wong",
|
|
97
|
+
placementPhase: "upcoming", internship: "Rehabilitation OT", specialization: "Neuro", compliance: "Review", daysUntilStart: 10,
|
|
98
|
+
readiness: "In review", progressWeeksDone: 0, progressWeeksTotal: 10, endDate: "06/12/2026", lastCheckin: "—",
|
|
99
|
+
completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 9, student: "Lena Fischer", email: "l.fischer@school.edu", initials: "LF", program: "Nursing", site: "Westside Clinic",
|
|
103
|
+
siteAddress: "2525 S Michigan Ave, Chicago, IL", status: "confirmed", start: "03/18/2026", duration: "12 wks", supervisor: "Dr. Santos",
|
|
104
|
+
placementPhase: "ongoing", internship: "Primary Care RN", specialization: "Family Practice", compliance: "Complete", daysUntilStart: 0,
|
|
105
|
+
readiness: "Ready", progressWeeksDone: 8, progressWeeksTotal: 12, endDate: "06/10/2026", lastCheckin: "Mar 24, 2026",
|
|
106
|
+
completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: 10, student: "Omar Hassan", email: "o.hassan@college.edu", initials: "OH", program: "Physical Therapy", site: "Metro Rehab",
|
|
110
|
+
siteAddress: "250 E Superior St, Chicago, IL", status: "pending", start: "04/21/2026", duration: "8 wks", supervisor: "Dr. Kim",
|
|
111
|
+
placementPhase: "upcoming", internship: "Neurologic PT", specialization: "Neuro Rehab", compliance: "Pending", daysUntilStart: 28,
|
|
112
|
+
readiness: "Ready", progressWeeksDone: 0, progressWeeksTotal: 8, endDate: "06/16/2026", lastCheckin: "—",
|
|
113
|
+
completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 11, student: "Nina Patel", email: "n.patel@university.edu", initials: "NP", program: "Social Work", site: "Hope Community Ctr",
|
|
117
|
+
siteAddress: "3517 W Arthington St, Chicago, IL", status: "confirmed", start: "03/25/2026", duration: "10 wks", supervisor: "Ms. Torres",
|
|
118
|
+
placementPhase: "ongoing", internship: "School Social Work", specialization: "Youth Services", compliance: "Complete", daysUntilStart: 0,
|
|
119
|
+
readiness: "Ready", progressWeeksDone: 4, progressWeeksTotal: 10, endDate: "06/03/2026", lastCheckin: "Mar 19, 2026",
|
|
120
|
+
completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—",
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: 12, student: "David Okonkwo", email: "d.okonkwo@school.edu", initials: "DO", program: "Nursing", site: "Lakeside Hospital",
|
|
124
|
+
siteAddress: "4440 N Clark St, Chicago, IL", status: "under-review", start: "04/10/2026", duration: "12 wks", supervisor: "Unassigned",
|
|
125
|
+
placementPhase: "upcoming", internship: "Emergency Dept RN", specialization: "Emergency", compliance: "Pending", daysUntilStart: 17,
|
|
126
|
+
readiness: "At risk", progressWeeksDone: 0, progressWeeksTotal: 12, endDate: "07/03/2026", lastCheckin: "—",
|
|
127
|
+
completionDate: "—", finalStatus: "—", rating: 0, suggestedToHire: "—",
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 13, student: "Alex Morgan", email: "a.morgan@college.edu", initials: "AM", program: "Nursing", site: "City Medical Center",
|
|
131
|
+
siteAddress: "1400 N Lake Shore Dr, Chicago, IL", status: "completed", start: "01/06/2026", duration: "12 wks", supervisor: "Dr. Patel",
|
|
132
|
+
placementPhase: "completed", internship: "Med-Surg Clinical II", specialization: "Adult Health", compliance: "Complete", daysUntilStart: 0,
|
|
133
|
+
readiness: "Ready", progressWeeksDone: 12, progressWeeksTotal: 12, endDate: "03/30/2026", lastCheckin: "Mar 28, 2026",
|
|
134
|
+
completionDate: "03/30/2026", finalStatus: "Passed", rating: 4.8, suggestedToHire: "Yes",
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: 14, student: "Jordan Lee", email: "j.lee@university.edu", initials: "JL", program: "Physical Therapy", site: "Metro Rehab",
|
|
138
|
+
siteAddress: "250 E Superior St, Chicago, IL", status: "completed", start: "11/04/2025", duration: "8 wks", supervisor: "Dr. Kim",
|
|
139
|
+
placementPhase: "completed", internship: "Inpatient PT", specialization: "Acute Care", compliance: "Complete", daysUntilStart: 0,
|
|
140
|
+
readiness: "Ready", progressWeeksDone: 8, progressWeeksTotal: 8, endDate: "12/30/2025", lastCheckin: "Dec 28, 2025",
|
|
141
|
+
completionDate: "12/30/2025", finalStatus: "Passed", rating: 4.2, suggestedToHire: "Yes",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: 15, student: "Sam Rivera", email: "s.rivera@school.edu", initials: "SR", program: "Occupational Therapy", site: "Bay Area Health",
|
|
145
|
+
siteAddress: "3100 Telegraph Ave, Oakland, CA", status: "completed", start: "10/01/2025", duration: "10 wks", supervisor: "Dr. Nguyen",
|
|
146
|
+
placementPhase: "completed", internship: "Hand Therapy OT", specialization: "Hand Therapy", compliance: "Complete", daysUntilStart: 0,
|
|
147
|
+
readiness: "Ready", progressWeeksDone: 10, progressWeeksTotal: 10, endDate: "12/10/2025", lastCheckin: "Dec 08, 2025",
|
|
148
|
+
completionDate: "12/10/2025", finalStatus: "Incomplete", rating: 3.5, suggestedToHire: "No",
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: 16, student: "Taylor Brooks", email: "t.brooks@college.edu", initials: "TB", program: "Nursing", site: "Sunrise Hospital",
|
|
152
|
+
siteAddress: "5775 Wayzata Blvd, Minneapolis, MN", status: "completed", start: "09/02/2025", duration: "12 wks", supervisor: "Dr. Torres",
|
|
153
|
+
placementPhase: "completed", internship: "Labor & Delivery", specialization: "Women's Health", compliance: "Complete", daysUntilStart: 0,
|
|
154
|
+
readiness: "Ready", progressWeeksDone: 12, progressWeeksTotal: 12, endDate: "11/25/2025", lastCheckin: "Nov 22, 2025",
|
|
155
|
+
completionDate: "11/25/2025", finalStatus: "Passed", rating: 5, suggestedToHire: "Yes",
|
|
156
|
+
},
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
/** Distinct sorted values for filter dropdowns (list UI) on text-like columns. */
|
|
160
|
+
export function uniquePlacementFieldOptions(
|
|
161
|
+
key: keyof Placement,
|
|
162
|
+
): { value: string; label: string }[] {
|
|
163
|
+
const set = new Set<string>()
|
|
164
|
+
for (const r of ALL_PLACEMENTS) {
|
|
165
|
+
const raw = r[key]
|
|
166
|
+
if (raw === undefined || raw === null) continue
|
|
167
|
+
const v = typeof raw === "number" ? String(raw) : String(raw).trim()
|
|
168
|
+
if (!v || v === "—") continue
|
|
169
|
+
set.add(v)
|
|
170
|
+
}
|
|
171
|
+
return [...set].sort((a, b) => a.localeCompare(b)).map(v => ({ value: v, label: v }))
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function placementsForPhase(
|
|
175
|
+
phase: "all" | PlacementPhase,
|
|
176
|
+
): Placement[] {
|
|
177
|
+
if (phase === "all") return ALL_PLACEMENTS
|
|
178
|
+
return ALL_PLACEMENTS.filter((p) => p.placementPhase === phase)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function getPlacementById(id: number): Placement | undefined {
|
|
182
|
+
return ALL_PLACEMENTS.find(p => p.id === id)
|
|
183
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { MetricInsight, MetricItem } from "@/components/key-metrics"
|
|
2
|
+
import type { QuestionBankItem } from "@/lib/mock/question-bank"
|
|
3
|
+
|
|
4
|
+
export function questionBankKpiMetrics(rows: QuestionBankItem[]): MetricItem[] {
|
|
5
|
+
const published = rows.filter(r => r.status === "published").length
|
|
6
|
+
const draft = rows.filter(r => r.status === "draft").length
|
|
7
|
+
const review = rows.filter(r => r.status === "in_review").length
|
|
8
|
+
|
|
9
|
+
return [
|
|
10
|
+
{
|
|
11
|
+
id: "total",
|
|
12
|
+
label: "Total questions",
|
|
13
|
+
value: rows.length,
|
|
14
|
+
delta: "—",
|
|
15
|
+
trend: "neutral",
|
|
16
|
+
href: "#",
|
|
17
|
+
metricVariant: "hero",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "published",
|
|
21
|
+
label: "Published",
|
|
22
|
+
value: published,
|
|
23
|
+
delta: "—",
|
|
24
|
+
trend: "neutral",
|
|
25
|
+
href: "#",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "review",
|
|
29
|
+
label: "In review",
|
|
30
|
+
value: review,
|
|
31
|
+
delta: review > 0 ? "!" : "—",
|
|
32
|
+
trend: review > 0 ? "up" : "neutral",
|
|
33
|
+
href: "#",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "draft",
|
|
37
|
+
label: "Drafts",
|
|
38
|
+
value: draft,
|
|
39
|
+
delta: "—",
|
|
40
|
+
trend: "neutral",
|
|
41
|
+
href: "#",
|
|
42
|
+
},
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function questionBankKpiInsight(rows: QuestionBankItem[]): MetricInsight {
|
|
47
|
+
const review = rows.filter(r => r.status === "in_review").length
|
|
48
|
+
const draft = rows.filter(r => r.status === "draft").length
|
|
49
|
+
return {
|
|
50
|
+
title: "Question bank",
|
|
51
|
+
description:
|
|
52
|
+
review > 0
|
|
53
|
+
? `${review} item(s) in review. ${draft} draft(s) not yet published.`
|
|
54
|
+
: draft > 0
|
|
55
|
+
? `${draft} draft(s) ready to finalize or send for review.`
|
|
56
|
+
: "All items are published or in review with no backlog.",
|
|
57
|
+
href: "/question-bank",
|
|
58
|
+
severity: review > 2 ? "warning" : "info",
|
|
59
|
+
actionLabel: "Ask Leo",
|
|
60
|
+
}
|
|
61
|
+
}
|