@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.
- 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,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock question bank items — replace with API in production.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type QuestionBankStatus = "published" | "draft" | "in_review"
|
|
6
|
+
export type QuestionBankType = "multiple_choice" | "true_false" | "short_answer"
|
|
7
|
+
export type QuestionBankDifficulty = "easy" | "medium" | "hard"
|
|
8
|
+
|
|
9
|
+
export interface QuestionBankItem extends Record<string, unknown> {
|
|
10
|
+
id: string
|
|
11
|
+
/** Short preview / stem */
|
|
12
|
+
stem: string
|
|
13
|
+
topic: string
|
|
14
|
+
type: QuestionBankType
|
|
15
|
+
difficulty: QuestionBankDifficulty
|
|
16
|
+
status: QuestionBankStatus
|
|
17
|
+
author: string
|
|
18
|
+
updatedAt: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const QUESTION_BANK_ITEMS: QuestionBankItem[] = [
|
|
22
|
+
{
|
|
23
|
+
id: "q1",
|
|
24
|
+
stem: "Which nerve roots contribute to the brachial plexus?",
|
|
25
|
+
topic: "Anatomy",
|
|
26
|
+
type: "multiple_choice",
|
|
27
|
+
difficulty: "medium",
|
|
28
|
+
status: "published",
|
|
29
|
+
author: "Dr. Chen",
|
|
30
|
+
updatedAt: "2026-03-28",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: "q2",
|
|
34
|
+
stem: "Document baseline vitals before administering contrast.",
|
|
35
|
+
topic: "Clinical skills",
|
|
36
|
+
type: "true_false",
|
|
37
|
+
difficulty: "easy",
|
|
38
|
+
status: "published",
|
|
39
|
+
author: "Jordan Lee",
|
|
40
|
+
updatedAt: "2026-03-27",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "q3",
|
|
44
|
+
stem: "List three red flags for cauda equina syndrome.",
|
|
45
|
+
topic: "Neurology",
|
|
46
|
+
type: "short_answer",
|
|
47
|
+
difficulty: "hard",
|
|
48
|
+
status: "in_review",
|
|
49
|
+
author: "Alex Rivera",
|
|
50
|
+
updatedAt: "2026-03-26",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: "q4",
|
|
54
|
+
stem: "HIPAA permits disclosure to family without consent when…",
|
|
55
|
+
topic: "Ethics & law",
|
|
56
|
+
type: "multiple_choice",
|
|
57
|
+
difficulty: "medium",
|
|
58
|
+
status: "draft",
|
|
59
|
+
author: "Sam Patel",
|
|
60
|
+
updatedAt: "2026-03-25",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "q5",
|
|
64
|
+
stem: "Calculate BMI given height and weight (metric).",
|
|
65
|
+
topic: "Assessment",
|
|
66
|
+
type: "short_answer",
|
|
67
|
+
difficulty: "easy",
|
|
68
|
+
status: "published",
|
|
69
|
+
author: "Dr. Chen",
|
|
70
|
+
updatedAt: "2026-03-24",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: "q6",
|
|
74
|
+
stem: "Sterile field must be prepared before which step?",
|
|
75
|
+
topic: "Infection control",
|
|
76
|
+
type: "multiple_choice",
|
|
77
|
+
difficulty: "medium",
|
|
78
|
+
status: "published",
|
|
79
|
+
author: "Morgan Lee",
|
|
80
|
+
updatedAt: "2026-03-23",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "q7",
|
|
84
|
+
stem: "SOAP note: subjective section documents patient-reported data only.",
|
|
85
|
+
topic: "Documentation",
|
|
86
|
+
type: "true_false",
|
|
87
|
+
difficulty: "easy",
|
|
88
|
+
status: "draft",
|
|
89
|
+
author: "Casey Nguyen",
|
|
90
|
+
updatedAt: "2026-03-22",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "q8",
|
|
94
|
+
stem: "Contrast MRI safety screening includes renal function when…",
|
|
95
|
+
topic: "Radiology",
|
|
96
|
+
type: "multiple_choice",
|
|
97
|
+
difficulty: "hard",
|
|
98
|
+
status: "in_review",
|
|
99
|
+
author: "Riley Johnson",
|
|
100
|
+
updatedAt: "2026-03-21",
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: "q9",
|
|
104
|
+
stem: "Therapeutic communication: reflect feelings before offering solutions.",
|
|
105
|
+
topic: "Communication",
|
|
106
|
+
type: "true_false",
|
|
107
|
+
difficulty: "medium",
|
|
108
|
+
status: "published",
|
|
109
|
+
author: "Quinn Martinez",
|
|
110
|
+
updatedAt: "2026-03-20",
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: "q10",
|
|
114
|
+
stem: "Pediatric dose calculation uses body surface area when…",
|
|
115
|
+
topic: "Pharmacology",
|
|
116
|
+
type: "short_answer",
|
|
117
|
+
difficulty: "hard",
|
|
118
|
+
status: "published",
|
|
119
|
+
author: "Dr. Chen",
|
|
120
|
+
updatedAt: "2026-03-19",
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: "q11",
|
|
124
|
+
stem: "Fall risk assessment should be repeated after medication changes.",
|
|
125
|
+
topic: "Safety",
|
|
126
|
+
type: "true_false",
|
|
127
|
+
difficulty: "easy",
|
|
128
|
+
status: "draft",
|
|
129
|
+
author: "Taylor Brooks",
|
|
130
|
+
updatedAt: "2026-03-18",
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: "q12",
|
|
134
|
+
stem: "Describe hand hygiene moments (WHO five moments).",
|
|
135
|
+
topic: "Infection control",
|
|
136
|
+
type: "short_answer",
|
|
137
|
+
difficulty: "medium",
|
|
138
|
+
status: "in_review",
|
|
139
|
+
author: "Jordan Lee",
|
|
140
|
+
updatedAt: "2026-03-17",
|
|
141
|
+
},
|
|
142
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { NAV_PRIMARY } from "@/lib/mock/navigation"
|
|
2
|
+
|
|
3
|
+
/** Clinical site row — same source as legacy Sites nav children. */
|
|
4
|
+
export interface SiteDirectoryRow extends Record<string, unknown> {
|
|
5
|
+
id: string
|
|
6
|
+
name: string
|
|
7
|
+
url: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const SITES_DIRECTORY: SiteDirectoryRow[] = (NAV_PRIMARY.find(i => i.key === "sites")?.children ?? []).map(
|
|
11
|
+
c => ({
|
|
12
|
+
id: c.key,
|
|
13
|
+
name: c.title,
|
|
14
|
+
url: c.url,
|
|
15
|
+
}),
|
|
16
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { MetricInsight, MetricItem } from "@/components/key-metrics"
|
|
2
|
+
|
|
3
|
+
export function sitesKpiMetrics(count: number): MetricItem[] {
|
|
4
|
+
const active = Math.max(0, count - 3)
|
|
5
|
+
const pending = Math.min(count, 3)
|
|
6
|
+
return [
|
|
7
|
+
{
|
|
8
|
+
id: "total-sites",
|
|
9
|
+
label: "Total sites",
|
|
10
|
+
value: count,
|
|
11
|
+
delta: "+4",
|
|
12
|
+
trend: "up",
|
|
13
|
+
href: "#",
|
|
14
|
+
metricVariant: "hero",
|
|
15
|
+
},
|
|
16
|
+
{ id: "active", label: "Active", value: active, delta: "+3", trend: "up", href: "#" },
|
|
17
|
+
{ id: "pending", label: "Pending", value: pending, delta: "—", trend: "neutral", href: "#" },
|
|
18
|
+
{ id: "archived", label: "Archived", value: 0, delta: "—", trend: "neutral", href: "#" },
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const SITES_KPI_INSIGHT: MetricInsight = {
|
|
23
|
+
title: "4 new sites added this month",
|
|
24
|
+
description: "Most recent additions are affiliated with the Nursing program.",
|
|
25
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// Team page — KPI strip (mirrors placements-kpi pattern for primary list template)
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
import type { MetricInsight, MetricItem } from "@/components/key-metrics"
|
|
6
|
+
import type { TeamMember } from "@/lib/mock/team"
|
|
7
|
+
|
|
8
|
+
export function teamKpiMetrics(members: TeamMember[]): MetricItem[] {
|
|
9
|
+
const active = members.filter(m => m.status === "active").length
|
|
10
|
+
const away = members.filter(m => m.status === "away").length
|
|
11
|
+
const invited = members.filter(m => m.status === "invited").length
|
|
12
|
+
return [
|
|
13
|
+
{
|
|
14
|
+
id: "total-members",
|
|
15
|
+
label: "Total members",
|
|
16
|
+
value: members.length,
|
|
17
|
+
delta: "+2",
|
|
18
|
+
trend: "up",
|
|
19
|
+
href: "#",
|
|
20
|
+
metricVariant: "hero",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "active",
|
|
24
|
+
label: "Active",
|
|
25
|
+
value: active,
|
|
26
|
+
delta: "—",
|
|
27
|
+
trend: "neutral",
|
|
28
|
+
href: "#",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "away",
|
|
32
|
+
label: "Away",
|
|
33
|
+
value: away,
|
|
34
|
+
delta: "—",
|
|
35
|
+
trend: "neutral",
|
|
36
|
+
href: "#",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "invited",
|
|
40
|
+
label: "Invited",
|
|
41
|
+
value: invited,
|
|
42
|
+
delta: invited > 0 ? "+1" : "—",
|
|
43
|
+
trend: invited > 0 ? "up" : "neutral",
|
|
44
|
+
href: "#",
|
|
45
|
+
},
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function teamKpiInsight(members: TeamMember[]): MetricInsight {
|
|
50
|
+
const invited = members.filter(m => m.status === "invited").length
|
|
51
|
+
return {
|
|
52
|
+
title: "Pending invites",
|
|
53
|
+
description:
|
|
54
|
+
invited > 0
|
|
55
|
+
? `${invited} invitation(s) outstanding. Resend or revoke from the roster.`
|
|
56
|
+
: "No pending invitations.",
|
|
57
|
+
severity: invited > 0 ? "warning" : "info",
|
|
58
|
+
actionLabel: "Ask Leo",
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock team directory — replace with API data in production.
|
|
3
|
+
* Kept at 11+ rows so the Team page demonstrates search / sort / properties toolbar (see data-views-pattern.md).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface TeamMember extends Record<string, unknown> {
|
|
7
|
+
id: string
|
|
8
|
+
name: string
|
|
9
|
+
role: string
|
|
10
|
+
email: string
|
|
11
|
+
/** US 10-digit storage (filter uses masked entry; matching normalizes digits). */
|
|
12
|
+
phone: string
|
|
13
|
+
initials: string
|
|
14
|
+
/** Shown as a subtle status chip */
|
|
15
|
+
status: "active" | "away" | "invited"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const TEAM_MEMBERS: TeamMember[] = [
|
|
19
|
+
{
|
|
20
|
+
id: "1",
|
|
21
|
+
name: "Alex Rivera",
|
|
22
|
+
role: "Program Administrator",
|
|
23
|
+
email: "alex.rivera@school.edu",
|
|
24
|
+
phone: "5550100001",
|
|
25
|
+
initials: "AR",
|
|
26
|
+
status: "active",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: "2",
|
|
30
|
+
name: "Jordan Chen",
|
|
31
|
+
role: "Clinical Coordinator",
|
|
32
|
+
email: "jordan.chen@school.edu",
|
|
33
|
+
phone: "5550100002",
|
|
34
|
+
initials: "JC",
|
|
35
|
+
status: "active",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "3",
|
|
39
|
+
name: "Sam Patel",
|
|
40
|
+
role: "Field Education",
|
|
41
|
+
email: "sam.patel@school.edu",
|
|
42
|
+
phone: "5550100003",
|
|
43
|
+
initials: "SP",
|
|
44
|
+
status: "away",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "4",
|
|
48
|
+
name: "Taylor Brooks",
|
|
49
|
+
role: "Viewer",
|
|
50
|
+
email: "taylor.brooks@school.edu",
|
|
51
|
+
phone: "5550100004",
|
|
52
|
+
initials: "TB",
|
|
53
|
+
status: "invited",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "5",
|
|
57
|
+
name: "Morgan Lee",
|
|
58
|
+
role: "Program Administrator",
|
|
59
|
+
email: "morgan.lee@school.edu",
|
|
60
|
+
phone: "5550100005",
|
|
61
|
+
initials: "ML",
|
|
62
|
+
status: "active",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "6",
|
|
66
|
+
name: "Casey Nguyen",
|
|
67
|
+
role: "Clinical Coordinator",
|
|
68
|
+
email: "casey.nguyen@school.edu",
|
|
69
|
+
phone: "5550100006",
|
|
70
|
+
initials: "CN",
|
|
71
|
+
status: "active",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "7",
|
|
75
|
+
name: "Riley Johnson",
|
|
76
|
+
role: "Field Education",
|
|
77
|
+
email: "riley.johnson@school.edu",
|
|
78
|
+
phone: "5550100007",
|
|
79
|
+
initials: "RJ",
|
|
80
|
+
status: "away",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "8",
|
|
84
|
+
name: "Quinn Martinez",
|
|
85
|
+
role: "Viewer",
|
|
86
|
+
email: "quinn.martinez@school.edu",
|
|
87
|
+
phone: "5550100008",
|
|
88
|
+
initials: "QM",
|
|
89
|
+
status: "active",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: "9",
|
|
93
|
+
name: "Jamie Wilson",
|
|
94
|
+
role: "Program Administrator",
|
|
95
|
+
email: "jamie.wilson@school.edu",
|
|
96
|
+
phone: "5550100009",
|
|
97
|
+
initials: "JW",
|
|
98
|
+
status: "invited",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: "10",
|
|
102
|
+
name: "Drew Anderson",
|
|
103
|
+
role: "Clinical Coordinator",
|
|
104
|
+
email: "drew.anderson@school.edu",
|
|
105
|
+
phone: "5550100010",
|
|
106
|
+
initials: "DA",
|
|
107
|
+
status: "active",
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: "11",
|
|
111
|
+
name: "Skyler Kim",
|
|
112
|
+
role: "Field Education",
|
|
113
|
+
email: "skyler.kim@school.edu",
|
|
114
|
+
phone: "5550100011",
|
|
115
|
+
initials: "SK",
|
|
116
|
+
status: "active",
|
|
117
|
+
},
|
|
118
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Motion presets — same spirit as Animate UI (open component distribution:
|
|
3
|
+
* https://animate-ui.com/docs — copy/tweak in-repo, Motion + Tailwind).
|
|
4
|
+
*/
|
|
5
|
+
export const motionEaseOut = [0.22, 1, 0.36, 1] as const
|
|
6
|
+
|
|
7
|
+
/** App chrome (sidebar header stack, etc.) — `motion/react` */
|
|
8
|
+
export const motionHeaderEnter = {
|
|
9
|
+
duration: 0.22,
|
|
10
|
+
ease: motionEaseOut,
|
|
11
|
+
} as const
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Sheet / Vaul drawer timing — implemented in `@exxatdesignux/ui` `Sheet` + `Drawer`
|
|
15
|
+
* (`duration-300 ease-out`, shorter slide distance) so all panels animate consistently.
|
|
16
|
+
*/
|
|
17
|
+
export const motionSheetMs = 300
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Board placement card layout — which fields appear by default per lifecycle tab,
|
|
3
|
+
* which keys are grouped into header / site / schedule blocks, and helpers to
|
|
4
|
+
* resolve “active” fields when a column exists only on some tabs (e.g. status).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ColumnDef } from "@/components/data-table/types"
|
|
8
|
+
import type { Placement } from "@/lib/mock/placements"
|
|
9
|
+
|
|
10
|
+
/** Mirrors PlacementLifecycleTabId without importing data-list-table (avoids circular imports). */
|
|
11
|
+
export type BoardCardLifecycleTabId = "all" | "upcoming" | "ongoing" | "completed"
|
|
12
|
+
|
|
13
|
+
/** Default card fields per tab — intersected with visible table columns (except header-only rules below). */
|
|
14
|
+
export const DEFAULT_BOARD_CARD_KEYS: Record<BoardCardLifecycleTabId, readonly string[]> = {
|
|
15
|
+
all: ["student", "specialization", "site", "status", "internship", "start", "duration"],
|
|
16
|
+
upcoming: ["student", "specialization", "site", "status", "internship", "start", "daysUntilStart"],
|
|
17
|
+
ongoing: ["student", "specialization", "site", "status", "internship", "progressWeeksDone", "endDate"],
|
|
18
|
+
completed: ["student", "specialization", "site", "status", "internship", "completionDate", "finalStatus"],
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function scheduleKeysForTab(tab: BoardCardLifecycleTabId): readonly string[] {
|
|
22
|
+
switch (tab) {
|
|
23
|
+
case "all":
|
|
24
|
+
return ["start", "duration"]
|
|
25
|
+
case "upcoming":
|
|
26
|
+
return ["start", "daysUntilStart"]
|
|
27
|
+
case "ongoing":
|
|
28
|
+
return ["progressWeeksDone", "endDate"]
|
|
29
|
+
case "completed":
|
|
30
|
+
return ["completionDate", "finalStatus"]
|
|
31
|
+
default:
|
|
32
|
+
return []
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Keys rendered in the title/header cluster (not as icon rows). Status + New only — not internship/specialization. */
|
|
37
|
+
export function consumedKeysForCard(tab: BoardCardLifecycleTabId): Set<string> {
|
|
38
|
+
const schedule = scheduleKeysForTab(tab)
|
|
39
|
+
return new Set<string>(["student", "status", "site", ...schedule])
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isKeyInBoardWhitelist(tab: BoardCardLifecycleTabId, key: string): boolean {
|
|
43
|
+
return DEFAULT_BOARD_CARD_KEYS[tab].includes(key)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Field is allowed on the card when it is in the tab whitelist, not hidden, and either
|
|
48
|
+
* the column exists in this view and is visible, or the column does not exist (e.g. status on Upcoming)
|
|
49
|
+
* — then we still show from row data for header/summary fields.
|
|
50
|
+
*/
|
|
51
|
+
export function isBoardFieldActive<Row extends Placement>(
|
|
52
|
+
key: string,
|
|
53
|
+
tab: BoardCardLifecycleTabId,
|
|
54
|
+
hiddenColKeys: Set<string>,
|
|
55
|
+
boardColumns: ColumnDef<Row>[],
|
|
56
|
+
): boolean {
|
|
57
|
+
if (hiddenColKeys.has(key)) return false
|
|
58
|
+
if (!isKeyInBoardWhitelist(tab, key)) return false
|
|
59
|
+
const hasCol = boardColumns.some(c => c.key === key)
|
|
60
|
+
if (!hasCol) return true
|
|
61
|
+
return boardColumns.some(c => c.key === key && !hiddenColKeys.has(c.key))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Visible columns that pass whitelist (intersection). Order follows boardColumns. */
|
|
65
|
+
export function filterColumnsForBoardCard<Row extends Placement>(
|
|
66
|
+
tab: BoardCardLifecycleTabId,
|
|
67
|
+
visibleCols: ColumnDef<Row>[],
|
|
68
|
+
): ColumnDef<Row>[] {
|
|
69
|
+
return visibleCols.filter(c => isKeyInBoardWhitelist(tab, c.key))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Body rows only: columns not consumed by title, header badges, site block, or schedule block. */
|
|
73
|
+
export function remainingBodyColumns<Row extends Placement>(
|
|
74
|
+
tab: BoardCardLifecycleTabId,
|
|
75
|
+
cardCols: ColumnDef<Row>[],
|
|
76
|
+
): ColumnDef<Row>[] {
|
|
77
|
+
const consumed = consumedKeysForCard(tab)
|
|
78
|
+
return cardCols.filter(c => !consumed.has(c.key))
|
|
79
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Table row density — shared by Properties drawer tiles and useTableState.
|
|
3
|
+
*/
|
|
4
|
+
export type RowHeight = "compact" | "default" | "comfortable"
|
|
5
|
+
|
|
6
|
+
export const ROW_HEIGHT_TILES: readonly { value: RowHeight; label: string; icon: string }[] = [
|
|
7
|
+
{ value: "compact", label: "Compact", icon: "fa-down-to-line" },
|
|
8
|
+
{ value: "default", label: "Default", icon: "fa-arrows-up-down" },
|
|
9
|
+
{ value: "comfortable", label: "Comfortable", icon: "fa-up-to-line" },
|
|
10
|
+
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic stock portrait URLs via randomuser.me static assets (no API key).
|
|
3
|
+
* Same seed → same image across reloads.
|
|
4
|
+
*/
|
|
5
|
+
export function stockPortraitUrl(seed: string): string {
|
|
6
|
+
let h = 0
|
|
7
|
+
for (let i = 0; i < seed.length; i++) h = (h * 31 + seed.charCodeAt(i)) >>> 0
|
|
8
|
+
const n = h % 99
|
|
9
|
+
const gender = h % 2 === 0 ? "men" : "women"
|
|
10
|
+
return `https://randomuser.me/api/portraits/${gender}/${n}.jpg`
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { cn } from "./utils"
|
|
4
|
+
|
|
5
|
+
describe("cn", () => {
|
|
6
|
+
it("merges class names and drops falsy entries", () => {
|
|
7
|
+
expect(cn("a", false && "b", "c")).toBe("a c")
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it("merges tailwind conflicts toward last wins", () => {
|
|
11
|
+
expect(cn("p-2", "p-4")).toBe("p-4")
|
|
12
|
+
})
|
|
13
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { cn } from "../../../packages/ui/src/lib/utils"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import bundleAnalyzer from "@next/bundle-analyzer"
|
|
2
|
+
|
|
3
|
+
const withBundleAnalyzer = bundleAnalyzer({
|
|
4
|
+
enabled: process.env.ANALYZE === "true",
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
/** @type {import('next').NextConfig} */
|
|
8
|
+
const nextConfig = {
|
|
9
|
+
transpilePackages: ["@exxatdesignux/ui"],
|
|
10
|
+
experimental: {
|
|
11
|
+
optimizePackageImports: ["lucide-react", "recharts", "@exxatdesignux/ui"],
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default withBundleAnalyzer(nextConfig)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-exxat-app",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Starter app built on @exxatdesignux/ui design system.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": true,
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=20"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "next dev --turbopack",
|
|
12
|
+
"dev:3001": "next dev --turbopack -p 3001",
|
|
13
|
+
"dev:3005": "next dev --turbopack -p 3005",
|
|
14
|
+
"dev:daemon": "pm2 start ecosystem.config.cjs",
|
|
15
|
+
"dev:daemon:stop": "pm2 stop exxat-ds",
|
|
16
|
+
"dev:daemon:restart": "pm2 restart exxat-ds",
|
|
17
|
+
"dev:daemon:delete": "pm2 delete exxat-ds",
|
|
18
|
+
"dev:daemon:logs": "pm2 logs exxat-ds --lines 80",
|
|
19
|
+
"dev:daemon:status": "pm2 status",
|
|
20
|
+
"build": "next build",
|
|
21
|
+
"analyze": "ANALYZE=true next build",
|
|
22
|
+
"start": "next start",
|
|
23
|
+
"lint": "eslint",
|
|
24
|
+
"format": "prettier --write \"**/*.{ts,tsx}\"",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"test:watch": "vitest",
|
|
28
|
+
"fa:subset-audit": "node scripts/fontawesome-subset-audit.mjs"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@dnd-kit/core": "^6.3.1",
|
|
32
|
+
"@dnd-kit/modifiers": "^9.0.0",
|
|
33
|
+
"@dnd-kit/sortable": "^10.0.0",
|
|
34
|
+
"@dnd-kit/utilities": "^3.2.2",
|
|
35
|
+
"@exxatdesignux/ui": "latest",
|
|
36
|
+
"@hookform/resolvers": "^5.2.2",
|
|
37
|
+
"@tabler/icons-react": "^3.41.1",
|
|
38
|
+
"@tanstack/react-table": "^8.21.3",
|
|
39
|
+
"@tanstack/react-virtual": "^3.13.23",
|
|
40
|
+
"class-variance-authority": "^0.7.1",
|
|
41
|
+
"clsx": "^2.1.1",
|
|
42
|
+
"cmdk": "^1.1.1",
|
|
43
|
+
"lucide-react": "^0.577.0",
|
|
44
|
+
"motion": "^12.38.0",
|
|
45
|
+
"next": "16.1.7",
|
|
46
|
+
"next-themes": "^0.4.6",
|
|
47
|
+
"radix-ui": "^1.4.3",
|
|
48
|
+
"react": "^19.2.4",
|
|
49
|
+
"react-day-picker": "^9.14.0",
|
|
50
|
+
"react-dom": "^19.2.4",
|
|
51
|
+
"react-hook-form": "^7.72.0",
|
|
52
|
+
"recharts": "^2.15.4",
|
|
53
|
+
"shadcn": "^4.1.0",
|
|
54
|
+
"sonner": "^2.0.7",
|
|
55
|
+
"tailwind-merge": "^3.5.0",
|
|
56
|
+
"tw-animate-css": "^1.4.0",
|
|
57
|
+
"vaul": "^1.1.2",
|
|
58
|
+
"zod": "^4.3.6",
|
|
59
|
+
"zustand": "^5.0.12"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@eslint/eslintrc": "^3",
|
|
63
|
+
"@next/bundle-analyzer": "^16.2.1",
|
|
64
|
+
"@tailwindcss/postcss": "^4.2.1",
|
|
65
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
66
|
+
"@testing-library/react": "^16.3.0",
|
|
67
|
+
"@types/node": "^25.5.0",
|
|
68
|
+
"@types/react": "^19.2.14",
|
|
69
|
+
"@types/react-dom": "^19.2.3",
|
|
70
|
+
"@vitejs/plugin-react": "^4.7.0",
|
|
71
|
+
"eslint": "^9.39.4",
|
|
72
|
+
"eslint-config-next": "16.1.7",
|
|
73
|
+
"jsdom": "^26.1.0",
|
|
74
|
+
"pm2": "^6.0.14",
|
|
75
|
+
"postcss": "^8",
|
|
76
|
+
"prettier": "^3.8.1",
|
|
77
|
+
"prettier-plugin-tailwindcss": "^0.7.2",
|
|
78
|
+
"tailwindcss": "^4.2.1",
|
|
79
|
+
"typescript": "^5.9.3",
|
|
80
|
+
"vite": "^6.4.1",
|
|
81
|
+
"vitest": "^3.2.4"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
File without changes
|