@exxatdesignux/ui 0.3.0 → 0.4.1
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/CHANGELOG.md +701 -6
- package/README.md +138 -0
- package/bin/init.mjs +134 -31
- package/consumer-extras/cursor-rules/exxat-board-cards.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-centralized-list-dataset.mdc +2 -2
- package/consumer-extras/cursor-rules/exxat-collaboration-access.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-data-tables.mdc +2 -0
- package/consumer-extras/cursor-rules/exxat-dedicated-search-surfaces.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-ds-agents.mdc +3 -3
- package/consumer-extras/cursor-rules/exxat-library-hub-header.mdc +28 -0
- package/consumer-extras/cursor-rules/exxat-mono-ids.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-person-identity-display.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-primary-nav-secondary-panel.mdc +6 -6
- package/consumer-extras/cursor-rules/exxat-reuse-before-custom.mdc +1 -1
- package/consumer-extras/cursor-skills/exxat-board-cards/SKILL.md +2 -2
- package/consumer-extras/cursor-skills/exxat-centralized-list-dataset/SKILL.md +1 -1
- package/consumer-extras/cursor-skills/exxat-collaboration-access/SKILL.md +3 -3
- package/consumer-extras/cursor-skills/exxat-dedicated-search-surfaces/SKILL.md +2 -2
- package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +7 -7
- package/consumer-extras/cursor-skills/exxat-kpi-flat-band/SKILL.md +1 -1
- package/consumer-extras/cursor-skills/exxat-list-page-view-shells/SKILL.md +1 -1
- package/consumer-extras/cursor-skills/exxat-mono-ids/SKILL.md +4 -4
- package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +8 -8
- package/consumer-extras/cursor-skills/exxat-token-economy/SKILL.md +277 -0
- package/consumer-extras/handbook/HANDBOOK.md +2 -0
- package/consumer-extras/handbook/glossary.md +2 -1
- package/consumer-extras/handbook/reference-implementations.md +31 -4
- package/consumer-extras/patterns/collaboration-access-pattern.md +7 -7
- package/consumer-extras/patterns/data-views-pattern.md +18 -16
- package/consumer-extras/patterns/kpi-flat-band-pattern.md +2 -2
- package/dist/components/data-table/index.js +2 -2
- package/dist/components/data-table/index.js.map +1 -1
- package/dist/components/data-table/pagination.js +3 -3
- package/dist/components/data-table/pagination.js.map +1 -1
- package/dist/components/data-table/use-table-state.d.ts +1 -1
- package/dist/components/data-table/use-table-state.js.map +1 -1
- package/dist/components/data-views/data-row-list.js.map +1 -1
- package/dist/components/data-views/finder-panel-view.d.ts +1 -1
- package/dist/components/data-views/finder-panel-view.js.map +1 -1
- package/dist/components/data-views/hub-table.d.ts +9 -3
- package/dist/components/data-views/hub-table.js +262 -40
- package/dist/components/data-views/hub-table.js.map +1 -1
- package/dist/components/data-views/index.js +262 -40
- package/dist/components/data-views/index.js.map +1 -1
- package/dist/components/data-views/list-page-split-hub-tokens.d.ts +2 -2
- package/dist/components/data-views/list-page-split-hub-tokens.js.map +1 -1
- package/dist/components/data-views/list-page-tree-column-header.d.ts +1 -1
- package/dist/components/data-views/list-page-tree-column-header.js.map +1 -1
- package/dist/components/data-views/list-page-tree-panel-shell.js.map +1 -1
- package/dist/components/data-views/os-folder-glyph.d.ts +1 -1
- package/dist/components/data-views/os-folder-glyph.js.map +1 -1
- package/dist/components/ui/avatar.d.ts +1 -1
- package/dist/components/ui/key-metrics.js.map +1 -1
- package/dist/index.js +136 -39
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/components/data-table/index.tsx +2 -2
- package/src/components/data-table/pagination.tsx +5 -1
- package/src/components/data-table/use-table-state.ts +1 -1
- package/src/components/data-views/data-row-list.tsx +1 -1
- package/src/components/data-views/finder-panel-view.tsx +2 -2
- package/src/components/data-views/hub-table.tsx +149 -41
- package/src/components/data-views/list-page-split-hub-tokens.ts +2 -2
- package/src/components/data-views/list-page-tree-column-header.tsx +1 -1
- package/src/components/data-views/os-folder-glyph.tsx +1 -1
- package/src/components/ui/key-metrics.tsx +1 -1
- package/template/.claude/skills/exxat-ds-skill/SKILL.md +8 -7
- package/template/.cursor/rules/exxat-accessibility.mdc +1 -1
- package/template/.cursor/rules/exxat-command-menu.mdc +1 -1
- package/template/.cursor/rules/exxat-dashboard-view-charts.mdc +6 -6
- package/template/.cursor/rules/exxat-data-tables.mdc +3 -3
- package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +5 -5
- package/template/.cursor/rules/exxat-mono-ids.mdc +1 -1
- package/template/.cursor/rules/exxat-page-vs-drawer.mdc +1 -1
- package/template/.cursor/rules/exxat-table-properties-drawer.mdc +1 -1
- package/template/AGENTS.md +43 -37
- package/template/app/(app)/columns/page.tsx +11 -0
- package/template/app/(app)/library/all/page.tsx +11 -0
- package/template/app/(app)/library/find/page.tsx +12 -0
- package/template/app/(app)/{question-bank → library}/layout.tsx +16 -16
- package/template/app/(app)/library/list/page.tsx +12 -0
- package/template/app/(app)/{question-bank → library}/new/page.tsx +10 -10
- package/template/app/(app)/library/page.tsx +11 -0
- package/template/app/(app)/tokens-themes/page.tsx +11 -0
- package/template/components/ask-leo-composer.tsx +2 -2
- package/template/components/columns-client.tsx +158 -0
- package/template/components/columns-showcase.tsx +541 -0
- package/template/components/data-views/index.ts +32 -6
- package/template/components/data-views/{question-bank-folder-tree-branch.tsx → library-folder-tree-branch.tsx} +19 -19
- package/template/components/data-views/table-cells.tsx +673 -0
- package/template/components/folder-details-shell.tsx +11 -11
- package/template/components/hub-tree-panel-view.tsx +24 -24
- package/template/components/{question-bank-board-view.tsx → library-board-view.tsx} +44 -44
- package/template/components/{question-bank-client.tsx → library-client.tsx} +82 -82
- package/template/components/{question-bank-dashboard-charts.tsx → library-dashboard-charts.tsx} +14 -14
- package/template/components/{question-bank-favorite-button.tsx → library-favorite-button.tsx} +7 -7
- package/template/components/{question-bank-hub-client.tsx → library-hub-client.tsx} +43 -43
- package/template/components/{question-bank-new-folder-sheet.tsx → library-new-folder-sheet.tsx} +14 -14
- package/template/components/{question-bank-os-folder-view.tsx → library-os-folder-view.tsx} +31 -31
- package/template/components/{question-bank-page-header.tsx → library-page-header.tsx} +6 -6
- package/template/components/library-panel-activator.tsx +8 -0
- package/template/components/{question-bank-secondary-nav.tsx → library-secondary-nav.tsx} +60 -60
- package/template/components/{question-bank-table.tsx → library-table.tsx} +97 -97
- package/template/components/list-hub-status-badge.tsx +2 -2
- package/template/components/{new-question-composer.tsx → new-library-item-form.tsx} +37 -37
- package/template/components/sidebar/app-sidebar.tsx +61 -5
- package/template/components/sidebar/secondary-panel.tsx +109 -56
- package/template/components/sidebar/sidebar-auto-collapse.tsx +2 -2
- package/template/components/sidebar/sidebar-auto-open.tsx +2 -1
- package/template/components/table-properties/types.ts +1 -1
- package/template/components/templates/discovery-hub-template.tsx +1 -1
- package/template/components/templates/new-focus-template.tsx +2 -2
- package/template/components/templates/secondary-panel-hub-template.tsx +1 -1
- package/template/components/tokens-secondary-nav.tsx +192 -0
- package/template/components/tokens-themes-client.tsx +476 -0
- package/template/components/tokens-themes-section.tsx +386 -0
- package/template/docs/HANDBOOK.md +187 -0
- package/template/docs/blueprints/README.md +1 -1
- package/template/docs/blueprints/board-card.md +1 -1
- package/template/docs/blueprints/data-table.md +2 -2
- package/template/docs/blueprints/list-page-template.md +3 -3
- package/template/docs/blueprints/page-header.md +4 -4
- package/template/docs/collaboration-access-pattern.md +7 -7
- package/template/docs/component-selection-guide.md +1 -1
- package/template/docs/data-views-pattern.md +18 -16
- package/template/docs/glossary.md +58 -0
- package/template/docs/kpi-flat-band-pattern.md +3 -3
- package/template/docs/kpi-trend-pattern.md +18 -3
- package/template/docs/large-dataset-strategy.md +155 -0
- package/template/docs/library-hub-header-pattern.md +25 -0
- package/template/docs/migrations/_template.md +1 -1
- package/template/docs/reference-implementations.md +151 -0
- package/template/docs/token-taxonomy.md +1 -1
- package/template/docs/voice-and-tone.md +262 -0
- package/template/eslint.config.mjs +9 -39
- package/template/hooks/use-secondary-panel-hub-nav.ts +10 -10
- package/template/lib/ask-leo-route-context.ts +6 -18
- package/template/lib/coach-mark-registry.ts +0 -16
- package/template/lib/command-menu-config.ts +5 -12
- package/template/lib/command-menu-search-data.ts +8 -39
- package/template/lib/{question-bank-authoring.ts → library-authoring.ts} +89 -88
- package/template/lib/library-dedicated-search.ts +19 -0
- package/template/lib/library-hub-search.ts +90 -0
- package/template/lib/library-nav.ts +477 -0
- package/template/lib/library-recent-searches.ts +22 -0
- package/template/lib/{placements-supported-views.ts → library-supported-views.ts} +2 -2
- package/template/lib/list-status-badges.ts +16 -104
- package/template/lib/mock/dashboard.ts +1 -1
- package/template/lib/mock/{question-bank-folders.ts → library-folders.ts} +30 -30
- package/template/lib/mock/library-header-collaborators.ts +54 -0
- package/template/lib/mock/{question-bank-inspector.ts → library-inspector.ts} +29 -29
- package/template/lib/mock/{question-bank-kpi.ts → library-kpi.ts} +20 -20
- package/template/lib/mock/library.ts +249 -0
- package/template/lib/mock/navigation.tsx +32 -26
- package/template/lib/table-state-lifecycle.ts +1 -1
- package/template/next.config.mjs +7 -4
- package/template/package.json +0 -1
- package/tokens/hooks-index.json +2874 -0
- package/consumer-extras/cursor-rules/exxat-question-bank-hub-header.mdc +0 -28
- package/template/app/(app)/examples/page.tsx +0 -41
- package/template/app/(app)/question-bank/find/page.tsx +0 -12
- package/template/app/(app)/question-bank/library/page.tsx +0 -11
- package/template/app/(app)/question-bank/list/page.tsx +0 -12
- package/template/app/(app)/question-bank/page.tsx +0 -11
- package/template/components/compliance-board-view.tsx +0 -142
- package/template/components/compliance-client.tsx +0 -92
- package/template/components/compliance-page-header.tsx +0 -89
- package/template/components/compliance-table.tsx +0 -468
- package/template/components/data-view-dashboard-charts-compliance.tsx +0 -963
- package/template/components/data-view-dashboard-charts-team.tsx +0 -971
- package/template/components/data-view-dashboard-charts.tsx +0 -1503
- package/template/components/new-placement-back-btn.tsx +0 -28
- package/template/components/new-placement-form.tsx +0 -942
- package/template/components/placement-board-card.tsx +0 -250
- package/template/components/placement-detail.tsx +0 -438
- package/template/components/placements-board-view.tsx +0 -397
- package/template/components/placements-client.tsx +0 -220
- package/template/components/placements-list-view.tsx +0 -124
- package/template/components/placements-page-header.tsx +0 -166
- package/template/components/placements-table-cells.test.tsx +0 -22
- package/template/components/placements-table-cells.tsx +0 -173
- package/template/components/placements-table-columns.tsx +0 -210
- package/template/components/placements-table.tsx +0 -934
- package/template/components/question-bank-panel-activator.tsx +0 -8
- package/template/components/rotations-empty-state.tsx +0 -50
- package/template/components/rotations-panel-activator.tsx +0 -8
- package/template/components/sites-board-view.tsx +0 -67
- package/template/components/sites-client.tsx +0 -154
- package/template/components/sites-table.tsx +0 -249
- package/template/components/team-board-view.tsx +0 -122
- package/template/components/team-client.tsx +0 -100
- package/template/components/team-page-header.tsx +0 -92
- package/template/components/team-table.tsx +0 -553
- package/template/docs/question-bank-hub-header-pattern.md +0 -25
- package/template/lib/compliance-supported-views.ts +0 -10
- package/template/lib/data-view-dashboard-placements-layout.ts +0 -215
- package/template/lib/mock/compliance-kpi.ts +0 -61
- package/template/lib/mock/compliance.ts +0 -146
- package/template/lib/mock/placements-kpi.ts +0 -134
- package/template/lib/mock/placements.ts +0 -176
- package/template/lib/mock/question-bank-header-collaborators.ts +0 -54
- package/template/lib/mock/question-bank.ts +0 -249
- package/template/lib/mock/sites-directory.ts +0 -16
- package/template/lib/mock/sites-kpi.ts +0 -25
- package/template/lib/mock/team-kpi.ts +0 -60
- package/template/lib/mock/team.ts +0 -118
- package/template/lib/placement-board-card-layout.ts +0 -79
- package/template/lib/question-bank-dedicated-search.ts +0 -19
- package/template/lib/question-bank-hub-search.ts +0 -90
- package/template/lib/question-bank-nav.ts +0 -477
- package/template/lib/question-bank-recent-searches.ts +0 -22
- package/template/lib/question-bank-supported-views.ts +0 -12
- package/template/lib/sites-supported-views.ts +0 -10
- package/template/lib/team-supported-views.ts +0 -10
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Question bank library — folder-scoped hub header More menu must expose Customize folder; sheet on hub client
|
|
3
|
-
globs: apps/web/components/question-bank-*.tsx, packages/ui/template/components/question-bank-*.tsx
|
|
4
|
-
alwaysApply: false
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Exxat DS — Question bank hub header (folder scope)
|
|
8
|
-
|
|
9
|
-
When the question bank library URL is **scoped to a folder** (`parseQuestionBankNav` → **`scope === "folder"`** and **`folderId`** set), users are effectively on a **“folder page”**: the hub title matches that folder, and **global** actions belong in the **`QuestionBankPageHeader`** **⋯ More** menu — not only on per-row or per-tile overflow menus inside a single view tab.
|
|
10
|
-
|
|
11
|
-
**Pattern doc:** **`apps/web/docs/question-bank-hub-header-pattern.md`**. **Handbook:** **`apps/web/AGENTS.md` §4.6** (folder-scoped hub chrome).
|
|
12
|
-
|
|
13
|
-
## MUST
|
|
14
|
-
|
|
15
|
-
1. **`QuestionBankPageHeader`** — When **`navState.scope === "folder"`** and **`navState.folderId`** resolves to a row in **`folders`**, pass **`onCustomizeFolder`** so **⋯ More** includes **Customize folder** ( **`fa-wand-magic-sparkles`** + label **Customize folder** ), placed after **Invite people** (collaboration variant) and before **Export**.
|
|
16
|
-
2. **Hub client** — Mount **`QuestionBankNewFolderSheet`** on the **hub client** (e.g. **`QuestionBankClient`**) next to **`ListPageTemplate`**, driven by local **`open` / `customizingFolder`** state opened from **`onCustomizeFolder`**. **MUST NOT** rely on **`QuestionBankTable`** alone to host the sheet when some view branches (**table**, **list**, **board**, **dashboard**) do not render that sheet — users would lose **Customize folder** on those tabs.
|
|
17
|
-
3. **`onCreated`** — On save, **`setFolders`** (or equivalent) **maps** the scoped folder **`id`** to updated **`name`**, **`icon`**, **`colorKey`** — same contract as **`QuestionBankTable`** panel/tree customize handlers.
|
|
18
|
-
|
|
19
|
-
## MUST NOT
|
|
20
|
-
|
|
21
|
-
- Omit **Customize folder** from the header **⋯** when the URL is folder-scoped, expecting users to find it only on secondary-nav tree rows or OS-folder tiles.
|
|
22
|
-
- Mount **only** one customize sheet inside **`QuestionBankTable`** without a **client-level** sheet when the hub uses **`ListPageTemplate`** view tabs that omit that table subtree.
|
|
23
|
-
|
|
24
|
-
## See also
|
|
25
|
-
|
|
26
|
-
- **`.cursor/rules/exxat-primary-nav-secondary-panel.mdc`** — URL scope + secondary panel.
|
|
27
|
-
- **`.cursor/rules/exxat-collaboration-access.mdc`** — **`variant="collaboration"`** header + **⋯** invite pattern.
|
|
28
|
-
- **`lib/question-bank-nav.ts`** — **`parseQuestionBankNav`**, **`QuestionBankNavState`**.
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import Link from "next/link"
|
|
2
|
-
import { PrimaryPageTemplate } from "@/components/templates/primary-page-template"
|
|
3
|
-
import { Button } from "@/components/ui/button"
|
|
4
|
-
|
|
5
|
-
const LINKS = [
|
|
6
|
-
{ href: "/dashboard", label: "Dashboard", description: "Metrics, charts, and layout patterns." },
|
|
7
|
-
{ href: "/question-bank", label: "Question bank", description: "Discovery hub for browsing folders, recents, and AI-assisted create/import flows." },
|
|
8
|
-
{ href: "/question-bank/library", label: "Question library", description: "Folders, OS folder view, panel, and tree demos on mock items." },
|
|
9
|
-
{ href: "/settings", label: "Settings", description: "Appearance, tours, and shell preferences." },
|
|
10
|
-
{ href: "/help", label: "Help", description: "Support and documentation entry points." },
|
|
11
|
-
] as const
|
|
12
|
-
|
|
13
|
-
export default function ExamplesPage() {
|
|
14
|
-
return (
|
|
15
|
-
<PrimaryPageTemplate
|
|
16
|
-
siteHeader={{ title: "Patterns" }}
|
|
17
|
-
maxWidthClassName="max-w-3xl"
|
|
18
|
-
contentClassName="px-4 lg:px-6 py-8"
|
|
19
|
-
bodyClassName="overflow-y-auto"
|
|
20
|
-
>
|
|
21
|
-
<p className="text-sm text-muted-foreground mb-6">
|
|
22
|
-
This workspace ships neutral chrome so you can reuse layouts, data views, and tokens as a design system.
|
|
23
|
-
</p>
|
|
24
|
-
<ul className="flex flex-col gap-3" role="list">
|
|
25
|
-
{LINKS.map((item) => (
|
|
26
|
-
<li key={item.href}>
|
|
27
|
-
<Button variant="outline" className="h-auto w-full justify-start gap-3 py-4 px-4" asChild>
|
|
28
|
-
<Link href={item.href}>
|
|
29
|
-
<span className="flex min-w-0 flex-1 flex-col items-start gap-0.5 text-left">
|
|
30
|
-
<span className="font-medium text-foreground">{item.label}</span>
|
|
31
|
-
<span className="text-xs font-normal text-muted-foreground">{item.description}</span>
|
|
32
|
-
</span>
|
|
33
|
-
<i className="fa-light fa-arrow-right shrink-0 text-muted-foreground" aria-hidden="true" />
|
|
34
|
-
</Link>
|
|
35
|
-
</Button>
|
|
36
|
-
</li>
|
|
37
|
-
))}
|
|
38
|
-
</ul>
|
|
39
|
-
</PrimaryPageTemplate>
|
|
40
|
-
)
|
|
41
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { Suspense } from "react"
|
|
2
|
-
|
|
3
|
-
import { QuestionBankClient } from "@/components/question-bank-client"
|
|
4
|
-
|
|
5
|
-
/** Discovery hub composer results — same hub chrome as the library, distinct from `/question-bank/list`. */
|
|
6
|
-
export default function QuestionBankHubFindPage() {
|
|
7
|
-
return (
|
|
8
|
-
<Suspense fallback={null}>
|
|
9
|
-
<QuestionBankClient />
|
|
10
|
-
</Suspense>
|
|
11
|
-
)
|
|
12
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { Suspense } from "react"
|
|
2
|
-
|
|
3
|
-
import { QuestionBankClient } from "@/components/question-bank-client"
|
|
4
|
-
|
|
5
|
-
/** Question bank list surface — same hub as `/question-bank/library`, optimized for `?q=` search landings. */
|
|
6
|
-
export default function QuestionBankListPage() {
|
|
7
|
-
return (
|
|
8
|
-
<Suspense fallback={null}>
|
|
9
|
-
<QuestionBankClient />
|
|
10
|
-
</Suspense>
|
|
11
|
-
)
|
|
12
|
-
}
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import { initialsFromDisplayName } from "@/lib/initials-from-name"
|
|
5
|
-
import {
|
|
6
|
-
COMPLIANCE_STATUS_BADGE_CLASS,
|
|
7
|
-
COMPLIANCE_STATUS_ICON,
|
|
8
|
-
COMPLIANCE_STATUS_LABEL,
|
|
9
|
-
} from "@/lib/list-status-badges"
|
|
10
|
-
import type { ComplianceItem } from "@/lib/mock/compliance"
|
|
11
|
-
import { BoardCardTwoLineBlock } from "@/components/data-views/board-card-primitives"
|
|
12
|
-
import { ListHubStatusBadge } from "@/components/list-hub-status-badge"
|
|
13
|
-
import {
|
|
14
|
-
ListPageBoardCard,
|
|
15
|
-
ListPageBoardCardAvatar,
|
|
16
|
-
ListPageBoardCardBadgeRow,
|
|
17
|
-
ListPageBoardCardBody,
|
|
18
|
-
ListPageBoardCardHeader,
|
|
19
|
-
ListPageBoardCardTitleRow,
|
|
20
|
-
} from "@/components/data-views/list-page-board-card"
|
|
21
|
-
import {
|
|
22
|
-
ListPageBoardTemplate,
|
|
23
|
-
type ListPageBoardColumnDef,
|
|
24
|
-
} from "@/components/data-views/list-page-board-template"
|
|
25
|
-
|
|
26
|
-
const NEUTRAL_COUNT_BADGE = "bg-muted/90 text-foreground"
|
|
27
|
-
|
|
28
|
-
const STATUS_BOARD_COLUMNS: ListPageBoardColumnDef<ComplianceItem>[] = [
|
|
29
|
-
{
|
|
30
|
-
id: "compliant",
|
|
31
|
-
label: "Compliant",
|
|
32
|
-
description: "On track",
|
|
33
|
-
filter: r => r.status === "compliant",
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
id: "due_soon",
|
|
37
|
-
label: "Due soon",
|
|
38
|
-
description: "Within window",
|
|
39
|
-
filter: r => r.status === "due_soon",
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
id: "overdue",
|
|
43
|
-
label: "Overdue",
|
|
44
|
-
description: "Action required",
|
|
45
|
-
filter: r => r.status === "overdue",
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
id: "pending",
|
|
49
|
-
label: "Pending",
|
|
50
|
-
description: "Not started",
|
|
51
|
-
filter: r => r.status === "pending",
|
|
52
|
-
},
|
|
53
|
-
]
|
|
54
|
-
|
|
55
|
-
function categoryBoardColumns(rows: ComplianceItem[]): {
|
|
56
|
-
columns: ListPageBoardColumnDef<ComplianceItem>[]
|
|
57
|
-
badgeMap: Record<string, string>
|
|
58
|
-
} {
|
|
59
|
-
const labels = [...new Set(rows.map(r => r.category))].sort((a, b) => a.localeCompare(b))
|
|
60
|
-
const columns: ListPageBoardColumnDef<ComplianceItem>[] = labels.map(label => ({
|
|
61
|
-
id: `category:${label}`,
|
|
62
|
-
label,
|
|
63
|
-
filter: (r: ComplianceItem) => r.category === label,
|
|
64
|
-
}))
|
|
65
|
-
const badgeMap = Object.fromEntries(labels.map(l => [`category:${l}`, NEUTRAL_COUNT_BADGE]))
|
|
66
|
-
return { columns, badgeMap }
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function useComplianceBoardModel(rows: ComplianceItem[], groupByColumnKey: string) {
|
|
70
|
-
return React.useMemo(() => {
|
|
71
|
-
if (groupByColumnKey === "category") {
|
|
72
|
-
const { columns, badgeMap } = categoryBoardColumns(rows)
|
|
73
|
-
return { columns, badgeMap }
|
|
74
|
-
}
|
|
75
|
-
return {
|
|
76
|
-
columns: STATUS_BOARD_COLUMNS,
|
|
77
|
-
badgeMap: COMPLIANCE_STATUS_BADGE_CLASS as Record<string, string>,
|
|
78
|
-
}
|
|
79
|
-
}, [rows, groupByColumnKey])
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function ComplianceBoardCard({
|
|
83
|
-
row,
|
|
84
|
-
onRowActivate,
|
|
85
|
-
}: {
|
|
86
|
-
row: ComplianceItem
|
|
87
|
-
onRowActivate?: (row: ComplianceItem) => void
|
|
88
|
-
}) {
|
|
89
|
-
const ownerInitials = initialsFromDisplayName(row.owner)
|
|
90
|
-
return (
|
|
91
|
-
<ListPageBoardCard className="w-full" onClick={onRowActivate ? () => onRowActivate(row) : undefined}>
|
|
92
|
-
<ListPageBoardCardHeader>
|
|
93
|
-
<ListPageBoardCardTitleRow
|
|
94
|
-
title={row.title}
|
|
95
|
-
titleClassName="line-clamp-2"
|
|
96
|
-
trailing={<ListPageBoardCardAvatar initials={ownerInitials} />}
|
|
97
|
-
/>
|
|
98
|
-
<ListPageBoardCardBadgeRow>
|
|
99
|
-
<ListHubStatusBadge
|
|
100
|
-
surface="board"
|
|
101
|
-
label={COMPLIANCE_STATUS_LABEL[row.status]}
|
|
102
|
-
tintClassName={COMPLIANCE_STATUS_BADGE_CLASS[row.status]}
|
|
103
|
-
icon={COMPLIANCE_STATUS_ICON[row.status]}
|
|
104
|
-
/>
|
|
105
|
-
</ListPageBoardCardBadgeRow>
|
|
106
|
-
<ListPageBoardCardBody>
|
|
107
|
-
<BoardCardTwoLineBlock iconClass="fa-tag" line1={row.category} line2={`Due ${row.dueDate}`} />
|
|
108
|
-
<BoardCardTwoLineBlock iconClass="fa-user" line1={row.owner} line2="Owner" />
|
|
109
|
-
</ListPageBoardCardBody>
|
|
110
|
-
</ListPageBoardCardHeader>
|
|
111
|
-
</ListPageBoardCard>
|
|
112
|
-
)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export const COMPLIANCE_BOARD_GROUP_OPTIONS = [
|
|
116
|
-
{ key: "status", label: "Status" },
|
|
117
|
-
{ key: "category", label: "Category" },
|
|
118
|
-
] as const
|
|
119
|
-
|
|
120
|
-
export function ComplianceBoardView({
|
|
121
|
-
rows,
|
|
122
|
-
groupByColumnKey,
|
|
123
|
-
onRowActivate,
|
|
124
|
-
}: {
|
|
125
|
-
rows: ComplianceItem[]
|
|
126
|
-
groupByColumnKey: string
|
|
127
|
-
onRowActivate?: (row: ComplianceItem) => void
|
|
128
|
-
}) {
|
|
129
|
-
const key = groupByColumnKey === "category" ? "category" : "status"
|
|
130
|
-
const { columns, badgeMap } = useComplianceBoardModel(rows, key)
|
|
131
|
-
|
|
132
|
-
return (
|
|
133
|
-
<ListPageBoardTemplate
|
|
134
|
-
columns={columns}
|
|
135
|
-
rows={rows}
|
|
136
|
-
getRowKey={r => r.id}
|
|
137
|
-
columnCountBadgeClassName={badgeMap}
|
|
138
|
-
emptyColumnLabel="No items"
|
|
139
|
-
renderCard={row => <ComplianceBoardCard row={row} onRowActivate={onRowActivate} />}
|
|
140
|
-
/>
|
|
141
|
-
)
|
|
142
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Compliance list page — `ListPageTemplate` + `ComplianceTable`; view types from `@/components/data-views`.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import * as React from "react"
|
|
8
|
-
import {
|
|
9
|
-
ListPageTemplate,
|
|
10
|
-
type ViewTab,
|
|
11
|
-
dataListViewIcon,
|
|
12
|
-
type DataListViewType,
|
|
13
|
-
} from "@/components/data-views"
|
|
14
|
-
import { CompliancePageHeader } from "@/components/compliance-page-header"
|
|
15
|
-
import { ComplianceTable, type ComplianceTableHandle } from "@/components/compliance-table"
|
|
16
|
-
import { KeyMetrics } from "@/components/key-metrics"
|
|
17
|
-
import { useAskLeoPageContext } from "@/components/ask-leo-sidebar"
|
|
18
|
-
import { COMPLIANCE_ITEMS } from "@/lib/mock/compliance"
|
|
19
|
-
import { complianceKpiInsight, complianceKpiMetrics } from "@/lib/mock/compliance-kpi"
|
|
20
|
-
|
|
21
|
-
const DEFAULT_TABS: ViewTab[] = [
|
|
22
|
-
{
|
|
23
|
-
id: "obligations",
|
|
24
|
-
label: "Obligations",
|
|
25
|
-
viewType: "table",
|
|
26
|
-
icon: "fa-table",
|
|
27
|
-
filterId: "all",
|
|
28
|
-
},
|
|
29
|
-
]
|
|
30
|
-
|
|
31
|
-
export function ComplianceClient() {
|
|
32
|
-
const [exportOpen, setExportOpen] = React.useState(false)
|
|
33
|
-
const [showMetrics, setShowMetrics] = React.useState(true)
|
|
34
|
-
const tableRef = React.useRef<ComplianceTableHandle>(null)
|
|
35
|
-
const count = COMPLIANCE_ITEMS.length
|
|
36
|
-
|
|
37
|
-
const metrics = React.useMemo(() => complianceKpiMetrics(COMPLIANCE_ITEMS), [])
|
|
38
|
-
const insight = React.useMemo(() => complianceKpiInsight(COMPLIANCE_ITEMS), [])
|
|
39
|
-
|
|
40
|
-
useAskLeoPageContext(
|
|
41
|
-
React.useMemo(
|
|
42
|
-
() => ({
|
|
43
|
-
title: "Compliance",
|
|
44
|
-
description: `${count} obligations tracked on this hub.`,
|
|
45
|
-
suggestions: [
|
|
46
|
-
"What’s due this week?",
|
|
47
|
-
"Summarize open items by student",
|
|
48
|
-
],
|
|
49
|
-
}),
|
|
50
|
-
[count],
|
|
51
|
-
),
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<ListPageTemplate
|
|
56
|
-
defaultTabs={DEFAULT_TABS}
|
|
57
|
-
getTabCount={() => count}
|
|
58
|
-
tablePropertiesRef={tableRef}
|
|
59
|
-
header={
|
|
60
|
-
<CompliancePageHeader
|
|
61
|
-
itemCount={count}
|
|
62
|
-
onAddReview={() => {}}
|
|
63
|
-
onExport={() => setExportOpen(true)}
|
|
64
|
-
showMetrics={showMetrics}
|
|
65
|
-
onToggleMetrics={() => setShowMetrics(v => !v)}
|
|
66
|
-
/>
|
|
67
|
-
}
|
|
68
|
-
metrics={
|
|
69
|
-
<KeyMetrics
|
|
70
|
-
variant="flat"
|
|
71
|
-
metrics={metrics}
|
|
72
|
-
insight={insight}
|
|
73
|
-
showHeader={false}
|
|
74
|
-
metricsSingleRow
|
|
75
|
-
/>
|
|
76
|
-
}
|
|
77
|
-
showMetrics={showMetrics}
|
|
78
|
-
exportOpen={exportOpen}
|
|
79
|
-
onExportOpenChange={setExportOpen}
|
|
80
|
-
exportTotalRows={count}
|
|
81
|
-
renderContent={(tab, updateTab) => (
|
|
82
|
-
<ComplianceTable
|
|
83
|
-
key={tab.id}
|
|
84
|
-
ref={tableRef}
|
|
85
|
-
items={COMPLIANCE_ITEMS}
|
|
86
|
-
view={tab.viewType}
|
|
87
|
-
onViewChange={(v: DataListViewType) => updateTab({ viewType: v, icon: dataListViewIcon(v) })}
|
|
88
|
-
/>
|
|
89
|
-
)}
|
|
90
|
-
/>
|
|
91
|
-
)
|
|
92
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import { Button } from "@/components/ui/button"
|
|
5
|
-
import { PageHeader } from "@/components/page-header"
|
|
6
|
-
import {
|
|
7
|
-
DropdownMenu,
|
|
8
|
-
DropdownMenuContent,
|
|
9
|
-
DropdownMenuItem,
|
|
10
|
-
DropdownMenuSeparator,
|
|
11
|
-
DropdownMenuTrigger,
|
|
12
|
-
} from "@/components/ui/dropdown-menu"
|
|
13
|
-
import { Tip } from "@/components/ui/tip"
|
|
14
|
-
|
|
15
|
-
export interface CompliancePageHeaderProps {
|
|
16
|
-
itemCount: number
|
|
17
|
-
onAddReview: () => void
|
|
18
|
-
onExport: () => void
|
|
19
|
-
showMetrics: boolean
|
|
20
|
-
onToggleMetrics: () => void
|
|
21
|
-
showTitleBlock?: boolean
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function CompliancePageHeader({
|
|
25
|
-
itemCount,
|
|
26
|
-
onAddReview,
|
|
27
|
-
onExport,
|
|
28
|
-
showMetrics,
|
|
29
|
-
onToggleMetrics,
|
|
30
|
-
showTitleBlock = true,
|
|
31
|
-
}: CompliancePageHeaderProps) {
|
|
32
|
-
const [moreOpen, setMoreOpen] = React.useState(false)
|
|
33
|
-
const countLine = `${itemCount} ${itemCount === 1 ? "item" : "items"} · Last updated now`
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<PageHeader
|
|
37
|
-
title="Compliance"
|
|
38
|
-
subtitle={countLine}
|
|
39
|
-
showTitleBlock={showTitleBlock}
|
|
40
|
-
actions={(
|
|
41
|
-
<div className="flex items-center gap-2" role="group" aria-label="Compliance actions">
|
|
42
|
-
<Tip side="bottom" label="Schedule a review (demo)">
|
|
43
|
-
<Button type="button" size="lg" onClick={onAddReview}>
|
|
44
|
-
<i className="fa-light fa-calendar-check" aria-hidden="true" />
|
|
45
|
-
New review
|
|
46
|
-
</Button>
|
|
47
|
-
</Tip>
|
|
48
|
-
<DropdownMenu open={moreOpen} onOpenChange={setMoreOpen}>
|
|
49
|
-
<Tip side="bottom" label="More actions">
|
|
50
|
-
<DropdownMenuTrigger asChild>
|
|
51
|
-
<Button
|
|
52
|
-
type="button"
|
|
53
|
-
size="lg"
|
|
54
|
-
variant="outline"
|
|
55
|
-
className="aspect-square px-0"
|
|
56
|
-
aria-label="More actions"
|
|
57
|
-
>
|
|
58
|
-
<i className="fa-light fa-ellipsis text-base" aria-hidden="true" />
|
|
59
|
-
</Button>
|
|
60
|
-
</DropdownMenuTrigger>
|
|
61
|
-
</Tip>
|
|
62
|
-
<DropdownMenuContent align="end">
|
|
63
|
-
<DropdownMenuItem
|
|
64
|
-
onSelect={() => {
|
|
65
|
-
window.setTimeout(() => onExport(), 0)
|
|
66
|
-
}}
|
|
67
|
-
>
|
|
68
|
-
<i className="fa-light fa-arrow-down-to-line" aria-hidden="true" />
|
|
69
|
-
Export
|
|
70
|
-
</DropdownMenuItem>
|
|
71
|
-
<DropdownMenuSeparator />
|
|
72
|
-
<DropdownMenuItem
|
|
73
|
-
onSelect={() => {
|
|
74
|
-
window.setTimeout(() => onToggleMetrics(), 0)
|
|
75
|
-
}}
|
|
76
|
-
>
|
|
77
|
-
<i
|
|
78
|
-
className={`fa-light ${showMetrics ? "fa-eye-slash" : "fa-eye"}`}
|
|
79
|
-
aria-hidden="true"
|
|
80
|
-
/>
|
|
81
|
-
{showMetrics ? "Hide metric section" : "Show metric section"}
|
|
82
|
-
</DropdownMenuItem>
|
|
83
|
-
</DropdownMenuContent>
|
|
84
|
-
</DropdownMenu>
|
|
85
|
-
</div>
|
|
86
|
-
)}
|
|
87
|
-
/>
|
|
88
|
-
)
|
|
89
|
-
}
|