@exxatdesignux/ui 0.2.8 → 0.2.10
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/consumer-extras/cursor-skills/exxat-card-vs-list-rows/SKILL.md +20 -0
- package/consumer-extras/cursor-skills/exxat-collaboration-access/SKILL.md +33 -0
- package/consumer-extras/cursor-skills/exxat-dedicated-search-surfaces/SKILL.md +45 -0
- package/consumer-extras/cursor-skills/exxat-drawer-vs-dialog/SKILL.md +20 -0
- package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +31 -5
- package/consumer-extras/cursor-skills/exxat-kpi-max-four/SKILL.md +19 -0
- package/consumer-extras/cursor-skills/exxat-kpi-trends/SKILL.md +27 -0
- package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +1 -0
- package/consumer-extras/patterns/collaboration-access-pattern.md +114 -0
- package/consumer-extras/patterns/data-views-pattern.md +12 -4
- package/package.json +17 -4
- package/src/components/ui/banner.tsx +20 -7
- package/src/components/ui/date-picker-field.tsx +3 -3
- package/src/components/ui/dropdown-menu.tsx +17 -6
- package/src/components/ui/input-group.tsx +1 -1
- package/src/components/ui/input.tsx +1 -1
- package/src/components/ui/select.tsx +1 -1
- package/src/components/ui/separator.tsx +2 -2
- package/src/components/ui/sidebar.tsx +31 -3
- package/src/components/ui/textarea.tsx +1 -1
- package/src/globals.css +0 -1
- package/src/index.ts +1 -0
- package/src/lib/date-filter.ts +13 -4
- package/src/lib/dropdown-menu-surface.ts +13 -0
- package/template/.claude/skills/exxat-ds-skill/SKILL.md +27 -9
- package/template/.cursor/rules/exxat-data-tables.mdc +1 -0
- package/template/AGENTS.md +82 -27
- package/template/app/(app)/examples/page.tsx +2 -1
- package/template/app/(app)/help/page.tsx +6 -0
- package/template/app/(app)/layout.tsx +7 -4
- package/template/app/(app)/question-bank/find/page.tsx +12 -0
- package/template/app/(app)/question-bank/layout.tsx +46 -0
- package/template/app/(app)/question-bank/library/page.tsx +11 -0
- package/template/app/(app)/question-bank/list/page.tsx +12 -0
- package/template/app/(app)/question-bank/page.tsx +4 -3
- package/template/app/globals.css +1 -2
- package/template/components/app-sidebar.tsx +51 -13
- package/template/components/ask-leo-composer.tsx +173 -45
- package/template/components/ask-leo-sidebar.tsx +9 -1
- package/template/components/chart-area-interactive.tsx +3 -13
- package/template/components/charts-overview.tsx +33 -6
- package/template/components/collaboration-access-flow.tsx +144 -0
- package/template/components/compliance-page-header.tsx +1 -1
- package/template/components/compliance-table.tsx +2 -2
- package/template/components/dashboard-tabs.tsx +4 -3
- package/template/components/data-list-table-cells.tsx +1 -1
- package/template/components/data-list-table.tsx +1 -1
- package/template/components/data-table/index.tsx +5 -5
- package/template/components/data-table/use-table-state.ts +18 -2
- package/template/components/data-view-dashboard-charts-compliance.tsx +8 -5
- package/template/components/data-view-dashboard-charts-team.tsx +8 -5
- package/template/components/data-view-dashboard-charts.tsx +62 -227
- package/template/components/dedicated-search-recents.tsx +96 -0
- package/template/components/dedicated-search-url-composer.tsx +112 -0
- package/template/components/getting-started.tsx +1 -1
- package/template/components/hub-tree-panel-view.tsx +10 -26
- package/template/components/invite-collaborators-drawer.tsx +453 -0
- package/template/components/key-metrics.tsx +54 -8
- package/template/components/nav-documents.tsx +1 -1
- package/template/components/new-placement-form.tsx +3 -3
- package/template/components/page-header.tsx +76 -59
- package/template/components/placements-board-view.tsx +3 -3
- package/template/components/placements-page-header.tsx +1 -1
- package/template/components/placements-table-columns.tsx +3 -2
- package/template/components/product-switcher.tsx +0 -1
- package/template/components/question-bank-board-view.tsx +35 -47
- package/template/components/question-bank-client.tsx +293 -81
- package/template/components/question-bank-dashboard-charts.tsx +174 -0
- package/template/components/question-bank-favorite-button.tsx +46 -0
- package/template/components/question-bank-hub-client.tsx +436 -0
- package/template/components/question-bank-list-view.tsx +26 -19
- package/template/components/question-bank-new-folder-sheet.tsx +56 -42
- package/template/components/question-bank-os-folder-view.tsx +3 -14
- package/template/components/question-bank-page-header.tsx +85 -53
- package/template/components/question-bank-panel-activator.tsx +3 -4
- package/template/components/question-bank-secondary-nav.tsx +523 -65
- package/template/components/question-bank-table.tsx +125 -343
- package/template/components/secondary-panel.tsx +130 -63
- package/template/components/settings-client.tsx +3 -1
- package/template/components/sidebar-shell.tsx +2 -0
- package/template/components/sites-all-client.tsx +1 -1
- package/template/components/sites-table.tsx +1 -1
- package/template/components/system-banner-slot.tsx +2 -1
- package/template/components/table-properties/drawer.tsx +3 -3
- package/template/components/table-properties/sort-card.tsx +1 -1
- package/template/components/team-page-header.tsx +1 -1
- package/template/components/team-table.tsx +8 -4
- package/template/components/templates/dedicated-search-landing-template.tsx +58 -0
- package/template/components/templates/dedicated-search-results-template.tsx +19 -0
- package/template/components/templates/discovery-hub-template.tsx +273 -0
- package/template/components/templates/list-page.tsx +11 -4
- package/template/components/templates/nested-secondary-panel-shell.tsx +57 -0
- package/template/components/templates/secondary-panel-hub-template.tsx +54 -0
- package/template/docs/card-vs-rows-pattern.md +36 -0
- package/template/docs/collaboration-access-pattern.md +114 -0
- package/template/docs/data-views-pattern.md +12 -4
- package/template/docs/drawer-vs-dialog-pattern.md +50 -0
- package/template/docs/kpi-strip-max-four-pattern.md +29 -0
- package/template/docs/kpi-trend-pattern.md +43 -0
- package/template/fontawesome-subset.manifest.json +2 -2
- package/template/hooks/use-location-hash.ts +14 -8
- package/template/hooks/use-secondary-panel-hub-nav.ts +98 -0
- package/template/lib/ask-leo-route-context.ts +24 -0
- package/template/lib/collaborator-access.ts +92 -0
- package/template/lib/command-menu-config.ts +8 -1
- package/template/lib/command-menu-search-data.ts +11 -8
- package/template/lib/data-list-display-options.ts +1 -1
- package/template/lib/data-view-dashboard-placements-layout.ts +215 -0
- package/template/lib/date-filter.ts +1 -0
- package/template/lib/dedicated-search-recents.ts +76 -0
- package/template/lib/dedicated-search-url.ts +23 -0
- package/template/lib/discovery-hub.ts +15 -0
- package/template/lib/list-status-badges.ts +1 -21
- package/template/lib/mock/navigation.tsx +4 -2
- package/template/lib/mock/placements.ts +9 -9
- package/template/lib/mock/question-bank-folders.ts +7 -0
- package/template/lib/mock/question-bank-header-collaborators.ts +45 -5
- package/template/lib/mock/question-bank-inspector.ts +1 -2
- package/template/lib/mock/question-bank-kpi.ts +38 -26
- package/template/lib/mock/question-bank.ts +43 -16
- package/template/lib/question-bank-dedicated-search.ts +19 -0
- package/template/lib/question-bank-hub-search.ts +90 -0
- package/template/lib/question-bank-nav.ts +322 -6
- package/template/lib/question-bank-recent-searches.ts +22 -0
- package/template/package.json +0 -1
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import * as React from "react"
|
|
8
|
-
import
|
|
9
|
-
import { ChartCard, ChartDataTable, ChartFigure } from "@/components/charts-overview"
|
|
8
|
+
import dynamic from "next/dynamic"
|
|
10
9
|
import { DataTable, DataTableToolbar } from "@/components/data-table"
|
|
11
10
|
import type { DataListViewType } from "@/lib/data-list-view"
|
|
12
11
|
import type { OpenTablePropertiesHandle } from "@/lib/list-page-table-properties"
|
|
@@ -14,15 +13,7 @@ import type { ColumnDef } from "@/components/data-table/types"
|
|
|
14
13
|
import { useTableState } from "@/components/data-table/use-table-state"
|
|
15
14
|
import { TablePropertiesDrawerButton } from "@/components/table-properties"
|
|
16
15
|
import type { ConditionalRule, FilterFieldDef, FilterOperator } from "@/components/table-properties/types"
|
|
17
|
-
import { ListHubStatusBadge } from "@/components/list-hub-status-badge"
|
|
18
16
|
import { Button } from "@/components/ui/button"
|
|
19
|
-
import {
|
|
20
|
-
ChartContainer,
|
|
21
|
-
ChartTooltip,
|
|
22
|
-
chartTooltipKeyboardSyncProps,
|
|
23
|
-
ChartTooltipContent,
|
|
24
|
-
type ChartConfig,
|
|
25
|
-
} from "@/components/ui/chart"
|
|
26
17
|
import {
|
|
27
18
|
DropdownMenu,
|
|
28
19
|
DropdownMenuContent,
|
|
@@ -30,7 +21,7 @@ import {
|
|
|
30
21
|
DropdownMenuTrigger,
|
|
31
22
|
} from "@/components/ui/dropdown-menu"
|
|
32
23
|
import { Tip } from "@/components/ui/tip"
|
|
33
|
-
import {
|
|
24
|
+
import { Skeleton } from "@/components/ui/skeleton"
|
|
34
25
|
import {
|
|
35
26
|
ResizableHandle,
|
|
36
27
|
ResizablePanel,
|
|
@@ -50,32 +41,66 @@ import {
|
|
|
50
41
|
import { ListPageTreeColumnHeader } from "@/components/data-views/list-page-tree-column-header"
|
|
51
42
|
import { QuestionBankBoardView, QUESTION_BANK_BOARD_GROUP_OPTIONS } from "@/components/question-bank-board-view"
|
|
52
43
|
import { QuestionBankListView } from "@/components/question-bank-list-view"
|
|
44
|
+
import {
|
|
45
|
+
QuestionBankFavoriteButton,
|
|
46
|
+
QUESTION_BANK_FAVORITE_HOVER_GROUP,
|
|
47
|
+
} from "@/components/question-bank-favorite-button"
|
|
53
48
|
import { QuestionBankOsFolderView } from "@/components/question-bank-os-folder-view"
|
|
54
49
|
import { QuestionBankNewFolderSheet } from "@/components/question-bank-new-folder-sheet"
|
|
55
50
|
import { FolderDetailsShell } from "@/components/folder-details-shell"
|
|
56
51
|
import { HubTreePanelView } from "@/components/hub-tree-panel-view"
|
|
57
|
-
import {
|
|
58
|
-
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
|
|
59
|
-
import { initialsFromDisplayName } from "@/lib/initials-from-name"
|
|
52
|
+
import { AvatarInitials } from "@/components/ui/avatar"
|
|
60
53
|
import { cn } from "@/lib/utils"
|
|
61
|
-
import {
|
|
62
|
-
import
|
|
63
|
-
import { newFolderId, type QuestionBankFolder, QUESTION_BANK_FOLDER_COLOR_STYLES, QUESTION_BANK_FOLDER_ICON_COLORS } from "@/lib/mock/question-bank-folders"
|
|
64
|
-
import { questionBankKpiInsight, questionBankKpiMetrics } from "@/lib/mock/question-bank-kpi"
|
|
54
|
+
import { formatDateUS } from "@/lib/date-filter"
|
|
55
|
+
import { initialsFromDisplayName } from "@/lib/initials-from-name"
|
|
65
56
|
import {
|
|
66
|
-
|
|
57
|
+
newQuestionBankQuestionId,
|
|
58
|
+
type QuestionBankDifficulty,
|
|
59
|
+
type QuestionBankItem,
|
|
60
|
+
type QuestionBankType,
|
|
61
|
+
} from "@/lib/mock/question-bank"
|
|
62
|
+
import { type QuestionBankFolder, QUESTION_BANK_FOLDER_COLOR_STYLES, QUESTION_BANK_FOLDER_ICON_COLORS } from "@/lib/mock/question-bank-folders"
|
|
63
|
+
import {
|
|
64
|
+
toggleQuestionBankItemFavorite,
|
|
65
|
+
applyQuestionBankHubDisplayFilters,
|
|
66
|
+
type QuestionBankLandingFilterState,
|
|
67
67
|
type QuestionBankNavState,
|
|
68
68
|
} from "@/lib/question-bank-nav"
|
|
69
|
-
import {
|
|
70
|
-
QUESTION_BANK_STATUS_BADGE_CLASS,
|
|
71
|
-
QUESTION_BANK_STATUS_ICON,
|
|
72
|
-
QUESTION_BANK_STATUS_LABEL,
|
|
73
|
-
} from "@/lib/list-status-badges"
|
|
74
69
|
import {
|
|
75
70
|
DEFAULT_DATA_LIST_DISPLAY_OPTIONS,
|
|
76
71
|
type DataListDisplayOptions,
|
|
77
72
|
} from "@/lib/data-list-display-options"
|
|
78
73
|
|
|
74
|
+
const QuestionBankDashboardChartsSection = dynamic(
|
|
75
|
+
() =>
|
|
76
|
+
import("@/components/question-bank-dashboard-charts").then(mod => ({
|
|
77
|
+
default: mod.QuestionBankDashboardChartsSection,
|
|
78
|
+
})),
|
|
79
|
+
{
|
|
80
|
+
ssr: false,
|
|
81
|
+
loading: () => (
|
|
82
|
+
<div className="flex min-h-0 flex-1 flex-col gap-4 pb-6">
|
|
83
|
+
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
|
84
|
+
<Skeleton className="h-20 w-full rounded-lg" />
|
|
85
|
+
<Skeleton className="h-20 w-full rounded-lg" />
|
|
86
|
+
<Skeleton className="h-20 w-full rounded-lg" />
|
|
87
|
+
<Skeleton className="h-20 w-full rounded-lg" />
|
|
88
|
+
</div>
|
|
89
|
+
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
|
90
|
+
<div className="flex flex-col gap-3 rounded-xl border border-border p-4">
|
|
91
|
+
<Skeleton className="h-5 w-32" />
|
|
92
|
+
<Skeleton className="min-h-[220px] w-full rounded-lg" />
|
|
93
|
+
</div>
|
|
94
|
+
<div className="flex flex-col gap-3 rounded-xl border border-border p-4">
|
|
95
|
+
<Skeleton className="h-5 w-28" />
|
|
96
|
+
<Skeleton className="min-h-[220px] w-full rounded-lg" />
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
),
|
|
101
|
+
},
|
|
102
|
+
)
|
|
103
|
+
|
|
79
104
|
const TYPE_LABEL: Record<QuestionBankType, string> = {
|
|
80
105
|
multiple_choice: "Multiple choice",
|
|
81
106
|
true_false: "True / false",
|
|
@@ -88,10 +113,6 @@ const DIFFICULTY_LABEL: Record<QuestionBankDifficulty, string> = {
|
|
|
88
113
|
hard: "Hard",
|
|
89
114
|
}
|
|
90
115
|
|
|
91
|
-
const BAR_CFG: ChartConfig = {
|
|
92
|
-
count: { label: "Questions", color: "var(--color-chart-2)" },
|
|
93
|
-
}
|
|
94
|
-
|
|
95
116
|
function newQuestionBankItemId() {
|
|
96
117
|
return `q-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`
|
|
97
118
|
}
|
|
@@ -107,12 +128,6 @@ function uniqueTopics(items: QuestionBankItem[]) {
|
|
|
107
128
|
return [...new Set(items.map(i => i.topic))].sort().map(t => ({ value: t, label: t }))
|
|
108
129
|
}
|
|
109
130
|
|
|
110
|
-
const STATUS_FILTER_OPTS = [
|
|
111
|
-
{ value: "published", label: QUESTION_BANK_STATUS_LABEL.published },
|
|
112
|
-
{ value: "draft", label: QUESTION_BANK_STATUS_LABEL.draft },
|
|
113
|
-
{ value: "in_review", label: QUESTION_BANK_STATUS_LABEL.in_review },
|
|
114
|
-
]
|
|
115
|
-
|
|
116
131
|
const TYPE_FILTER_OPTS = (Object.keys(TYPE_LABEL) as QuestionBankType[]).map(k => ({
|
|
117
132
|
value: k,
|
|
118
133
|
label: TYPE_LABEL[k],
|
|
@@ -145,8 +160,12 @@ function columnsToFilterFields(cols: ColumnDef<QuestionBankItem>[]) {
|
|
|
145
160
|
return cols.map(columnToFilterFieldDef).filter((x): x is FilterFieldDef => x !== null)
|
|
146
161
|
}
|
|
147
162
|
|
|
148
|
-
function buildQuestionBankColumns(
|
|
163
|
+
function buildQuestionBankColumns(
|
|
164
|
+
items: QuestionBankItem[],
|
|
165
|
+
opts: { onToggleFavorite: (row: QuestionBankItem) => void },
|
|
166
|
+
): ColumnDef<QuestionBankItem>[] {
|
|
149
167
|
const topicOpts = uniqueTopics(items)
|
|
168
|
+
const { onToggleFavorite } = opts
|
|
150
169
|
|
|
151
170
|
const COLUMN_SELECT: ColumnDef<QuestionBankItem> = {
|
|
152
171
|
key: "select",
|
|
@@ -157,8 +176,8 @@ function buildQuestionBankColumns(items: QuestionBankItem[]): ColumnDef<Question
|
|
|
157
176
|
lockPin: true,
|
|
158
177
|
}
|
|
159
178
|
|
|
160
|
-
const cols: ColumnDef<QuestionBankItem>[] = [
|
|
161
|
-
|
|
179
|
+
const cols: ColumnDef<QuestionBankItem>[] = [COLUMN_SELECT]
|
|
180
|
+
cols.push(
|
|
162
181
|
{
|
|
163
182
|
key: "stem",
|
|
164
183
|
label: "Question",
|
|
@@ -173,7 +192,13 @@ function buildQuestionBankColumns(items: QuestionBankItem[]): ColumnDef<Question
|
|
|
173
192
|
operators: ["contains", "not_contains"],
|
|
174
193
|
},
|
|
175
194
|
cell: row => (
|
|
176
|
-
<
|
|
195
|
+
<div className={cn(QUESTION_BANK_FAVORITE_HOVER_GROUP, "flex min-w-0 items-start gap-2")}>
|
|
196
|
+
<div className="flex min-w-0 flex-1 flex-col gap-0.5 pr-1">
|
|
197
|
+
<span className="line-clamp-2 text-sm font-medium text-foreground">{row.stem}</span>
|
|
198
|
+
<span className="font-mono text-xs text-muted-foreground">{row.questionId}</span>
|
|
199
|
+
</div>
|
|
200
|
+
<QuestionBankFavoriteButton row={row} onToggleFavorite={onToggleFavorite} />
|
|
201
|
+
</div>
|
|
177
202
|
),
|
|
178
203
|
},
|
|
179
204
|
{
|
|
@@ -223,27 +248,6 @@ function buildQuestionBankColumns(items: QuestionBankItem[]): ColumnDef<Question
|
|
|
223
248
|
<span className="text-sm text-foreground/90">{DIFFICULTY_LABEL[row.difficulty]}</span>
|
|
224
249
|
),
|
|
225
250
|
},
|
|
226
|
-
{
|
|
227
|
-
key: "status",
|
|
228
|
-
label: "Status",
|
|
229
|
-
width: 120,
|
|
230
|
-
minWidth: 100,
|
|
231
|
-
sortable: true,
|
|
232
|
-
sortKey: "status",
|
|
233
|
-
filter: {
|
|
234
|
-
type: "select",
|
|
235
|
-
icon: "fa-circle-dot",
|
|
236
|
-
operators: ["is", "is_not"],
|
|
237
|
-
options: STATUS_FILTER_OPTS,
|
|
238
|
-
},
|
|
239
|
-
cell: row => (
|
|
240
|
-
<ListHubStatusBadge
|
|
241
|
-
label={QUESTION_BANK_STATUS_LABEL[row.status]}
|
|
242
|
-
tintClassName={QUESTION_BANK_STATUS_BADGE_CLASS[row.status]}
|
|
243
|
-
icon={QUESTION_BANK_STATUS_ICON[row.status]}
|
|
244
|
-
/>
|
|
245
|
-
),
|
|
246
|
-
},
|
|
247
251
|
{
|
|
248
252
|
key: "updatedAt",
|
|
249
253
|
label: "Updated",
|
|
@@ -253,14 +257,14 @@ function buildQuestionBankColumns(items: QuestionBankItem[]): ColumnDef<Question
|
|
|
253
257
|
sortKey: "updatedAt",
|
|
254
258
|
filter: { type: "date", icon: "fa-calendar-days", operators: ["is", "is_not"] },
|
|
255
259
|
cell: row => (
|
|
256
|
-
<span className="text-sm tabular-nums text-foreground/90 whitespace-nowrap">{row.updatedAt}</span>
|
|
260
|
+
<span className="text-sm tabular-nums text-foreground/90 whitespace-nowrap">{formatDateUS(row.updatedAt)}</span>
|
|
257
261
|
),
|
|
258
262
|
},
|
|
259
263
|
{
|
|
260
264
|
key: "author",
|
|
261
265
|
label: "Author",
|
|
262
|
-
width:
|
|
263
|
-
minWidth:
|
|
266
|
+
width: 260,
|
|
267
|
+
minWidth: 200,
|
|
264
268
|
sortable: true,
|
|
265
269
|
sortKey: "author",
|
|
266
270
|
filter: {
|
|
@@ -268,7 +272,26 @@ function buildQuestionBankColumns(items: QuestionBankItem[]): ColumnDef<Question
|
|
|
268
272
|
icon: "fa-user",
|
|
269
273
|
operators: ["contains", "not_contains"],
|
|
270
274
|
},
|
|
271
|
-
cell: row =>
|
|
275
|
+
cell: row => {
|
|
276
|
+
const initials = initialsFromDisplayName(row.author)
|
|
277
|
+
return (
|
|
278
|
+
<div className="flex min-w-0 items-center gap-2.5">
|
|
279
|
+
<AvatarInitials initials={initials} className="size-8 shrink-0 text-xs" />
|
|
280
|
+
<div className="flex min-w-0 flex-col gap-0.5">
|
|
281
|
+
<span className="truncate text-sm font-medium text-foreground">{row.author}</span>
|
|
282
|
+
{row.authorEmail ? (
|
|
283
|
+
<a
|
|
284
|
+
href={`mailto:${row.authorEmail}`}
|
|
285
|
+
className="truncate text-xs text-muted-foreground transition-colors hover:text-foreground hover:underline"
|
|
286
|
+
onClick={e => e.stopPropagation()}
|
|
287
|
+
>
|
|
288
|
+
{row.authorEmail}
|
|
289
|
+
</a>
|
|
290
|
+
) : null}
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
)
|
|
294
|
+
},
|
|
272
295
|
},
|
|
273
296
|
{
|
|
274
297
|
key: "actions",
|
|
@@ -285,7 +308,7 @@ function buildQuestionBankColumns(items: QuestionBankItem[]): ColumnDef<Question
|
|
|
285
308
|
<i className="fa-light fa-ellipsis text-sm" aria-hidden="true" />
|
|
286
309
|
</Button>
|
|
287
310
|
</DropdownMenuTrigger>
|
|
288
|
-
<DropdownMenuContent align="end"
|
|
311
|
+
<DropdownMenuContent align="end">
|
|
289
312
|
<DropdownMenuItem disabled>
|
|
290
313
|
<i className="fa-light fa-eye" aria-hidden="true" />
|
|
291
314
|
Preview
|
|
@@ -299,257 +322,9 @@ function buildQuestionBankColumns(items: QuestionBankItem[]): ColumnDef<Question
|
|
|
299
322
|
</div>
|
|
300
323
|
),
|
|
301
324
|
},
|
|
302
|
-
]
|
|
303
|
-
|
|
304
|
-
return cols
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
function aggregateByStatus(rows: QuestionBankItem[]) {
|
|
309
|
-
const c = { published: 0, draft: 0, in_review: 0 }
|
|
310
|
-
for (const r of rows) c[r.status]++
|
|
311
|
-
return [
|
|
312
|
-
{ name: QUESTION_BANK_STATUS_LABEL.published, value: c.published, key: "published" },
|
|
313
|
-
{ name: QUESTION_BANK_STATUS_LABEL.draft, value: c.draft, key: "draft" },
|
|
314
|
-
{ name: QUESTION_BANK_STATUS_LABEL.in_review, value: c.in_review, key: "in_review" },
|
|
315
|
-
]
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function aggregateByTopic(rows: QuestionBankItem[]) {
|
|
319
|
-
const map = new Map<string, number>()
|
|
320
|
-
for (const r of rows) map.set(r.topic, (map.get(r.topic) ?? 0) + 1)
|
|
321
|
-
return [...map.entries()]
|
|
322
|
-
.map(([name, value]) => ({ name: name.length > 20 ? `${name.slice(0, 18)}…` : name, value }))
|
|
323
|
-
.sort((a, b) => b.value - a.value)
|
|
324
|
-
.slice(0, 8)
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
function QuestionsByStatusChart({ rows }: { rows: QuestionBankItem[] }) {
|
|
328
|
-
const data = React.useMemo(() => aggregateByStatus(rows), [rows])
|
|
329
|
-
if (rows.length === 0) {
|
|
330
|
-
return (
|
|
331
|
-
<div className="flex h-[200px] items-center justify-center text-sm text-muted-foreground" role="status">
|
|
332
|
-
No questions in this view.
|
|
333
|
-
</div>
|
|
334
|
-
)
|
|
335
|
-
}
|
|
336
|
-
const summary = `Status breakdown: ${data.map(d => `${d.name} ${d.value}`).join(", ")}. Total ${rows.length}.`
|
|
337
|
-
return (
|
|
338
|
-
<ChartFigure label="Questions by status" summary={summary} dataLength={data.length}>
|
|
339
|
-
{(activeIndex) => (
|
|
340
|
-
<>
|
|
341
|
-
<ChartContainer config={BAR_CFG} className="h-[220px] w-full">
|
|
342
|
-
<BarChart data={data} margin={{ top: 8, right: 8, left: 0, bottom: 0 }}>
|
|
343
|
-
<CartesianGrid vertical={false} strokeDasharray="3 3" className="stroke-border" />
|
|
344
|
-
<XAxis dataKey="name" tick={{ fontSize: 11 }} tickLine={false} axisLine={false} />
|
|
345
|
-
<YAxis allowDecimals={false} width={32} tick={{ fontSize: 12 }} tickLine={false} axisLine={false} />
|
|
346
|
-
<ChartTooltip key={chartTooltipKeyboardSyncProps(activeIndex).key} {...chartTooltipKeyboardSyncProps(activeIndex).props} content={<ChartTooltipContent />} />
|
|
347
|
-
<Bar
|
|
348
|
-
dataKey="value"
|
|
349
|
-
fill="var(--color-chart-2)"
|
|
350
|
-
radius={[4, 4, 0, 0]}
|
|
351
|
-
maxBarSize={40}
|
|
352
|
-
activeBar={CHART_KBD_ACTIVE_BAR}
|
|
353
|
-
activeIndex={activeIndex ?? undefined}
|
|
354
|
-
>
|
|
355
|
-
{data.map((_, i) => (
|
|
356
|
-
<Cell key={i} fill="var(--color-chart-2)" />
|
|
357
|
-
))}
|
|
358
|
-
</Bar>
|
|
359
|
-
</BarChart>
|
|
360
|
-
</ChartContainer>
|
|
361
|
-
<ChartDataTable
|
|
362
|
-
caption="Questions by status"
|
|
363
|
-
headers={["Status", "Count"]}
|
|
364
|
-
rows={data.map(d => [d.name, d.value])}
|
|
365
|
-
/>
|
|
366
|
-
</>
|
|
367
|
-
)}
|
|
368
|
-
</ChartFigure>
|
|
369
|
-
)
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
function QuestionBankFinderListRow({
|
|
373
|
-
row,
|
|
374
|
-
isSelected,
|
|
375
|
-
compact,
|
|
376
|
-
}: {
|
|
377
|
-
row: QuestionBankItem
|
|
378
|
-
isSelected: boolean
|
|
379
|
-
compact?: boolean
|
|
380
|
-
}) {
|
|
381
|
-
const initials = initialsFromDisplayName(row.author)
|
|
382
|
-
return (
|
|
383
|
-
<div
|
|
384
|
-
className={cn(
|
|
385
|
-
"flex w-full items-center px-3 py-2",
|
|
386
|
-
compact ? "gap-2 py-1.5 pl-2 pr-2.5" : "gap-3",
|
|
387
|
-
)}
|
|
388
|
-
>
|
|
389
|
-
<Avatar className={cn("shrink-0", compact ? "size-6" : "size-8")}>
|
|
390
|
-
<AvatarFallback
|
|
391
|
-
className={cn(
|
|
392
|
-
"font-semibold",
|
|
393
|
-
compact ? "text-[10px]" : "text-[11px]",
|
|
394
|
-
isSelected ? "bg-background/25 text-accent-foreground" : "bg-brand/15 text-brand",
|
|
395
|
-
)}
|
|
396
|
-
>
|
|
397
|
-
{initials}
|
|
398
|
-
</AvatarFallback>
|
|
399
|
-
</Avatar>
|
|
400
|
-
<div className="min-w-0 flex-1">
|
|
401
|
-
<p className={cn("line-clamp-2 font-medium leading-tight", compact ? "text-[11px]" : "text-xs")}>
|
|
402
|
-
{row.stem}
|
|
403
|
-
</p>
|
|
404
|
-
<p
|
|
405
|
-
className={cn(
|
|
406
|
-
"mt-0.5 truncate text-[11px] leading-tight",
|
|
407
|
-
isSelected ? "text-accent-foreground/85" : "text-muted-foreground",
|
|
408
|
-
)}
|
|
409
|
-
>
|
|
410
|
-
{row.topic} · {row.author}
|
|
411
|
-
</p>
|
|
412
|
-
</div>
|
|
413
|
-
{!isSelected && (
|
|
414
|
-
<ListHubStatusBadge
|
|
415
|
-
surface="board"
|
|
416
|
-
label={QUESTION_BANK_STATUS_LABEL[row.status]}
|
|
417
|
-
tintClassName={QUESTION_BANK_STATUS_BADGE_CLASS[row.status]}
|
|
418
|
-
icon={QUESTION_BANK_STATUS_ICON[row.status]}
|
|
419
|
-
/>
|
|
420
|
-
)}
|
|
421
|
-
</div>
|
|
422
|
-
)
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
function QuestionBankFinderDetail({ row }: { row: QuestionBankItem }) {
|
|
426
|
-
return (
|
|
427
|
-
<div className="flex h-full min-h-0 flex-col">
|
|
428
|
-
<div className="flex shrink-0 flex-col gap-2 border-b border-border px-5 py-4">
|
|
429
|
-
<h2 className="line-clamp-4 text-base font-semibold leading-tight text-foreground">{row.stem}</h2>
|
|
430
|
-
<ListHubStatusBadge
|
|
431
|
-
surface="board"
|
|
432
|
-
label={QUESTION_BANK_STATUS_LABEL[row.status]}
|
|
433
|
-
tintClassName={QUESTION_BANK_STATUS_BADGE_CLASS[row.status]}
|
|
434
|
-
icon={QUESTION_BANK_STATUS_ICON[row.status]}
|
|
435
|
-
/>
|
|
436
|
-
</div>
|
|
437
|
-
<div className="min-h-0 flex-1 overflow-y-auto px-5 py-4">
|
|
438
|
-
<dl className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
439
|
-
<div className="flex flex-col gap-0.5">
|
|
440
|
-
<dt className="flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
|
|
441
|
-
<i className="fa-light fa-layer-group text-xs" aria-hidden="true" />
|
|
442
|
-
Topic
|
|
443
|
-
</dt>
|
|
444
|
-
<dd className="text-xs text-foreground">{row.topic}</dd>
|
|
445
|
-
</div>
|
|
446
|
-
<div className="flex flex-col gap-0.5">
|
|
447
|
-
<dt className="flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
|
|
448
|
-
<i className="fa-light fa-list-check text-xs" aria-hidden="true" />
|
|
449
|
-
Type
|
|
450
|
-
</dt>
|
|
451
|
-
<dd className="text-xs text-foreground">{TYPE_LABEL[row.type]}</dd>
|
|
452
|
-
</div>
|
|
453
|
-
<div className="flex flex-col gap-0.5">
|
|
454
|
-
<dt className="flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
|
|
455
|
-
<i className="fa-light fa-signal text-xs" aria-hidden="true" />
|
|
456
|
-
Difficulty
|
|
457
|
-
</dt>
|
|
458
|
-
<dd className="text-xs text-foreground">{DIFFICULTY_LABEL[row.difficulty]}</dd>
|
|
459
|
-
</div>
|
|
460
|
-
<div className="flex flex-col gap-0.5">
|
|
461
|
-
<dt className="flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
|
|
462
|
-
<i className="fa-light fa-user text-xs" aria-hidden="true" />
|
|
463
|
-
Author
|
|
464
|
-
</dt>
|
|
465
|
-
<dd className="text-xs text-foreground">{row.author}</dd>
|
|
466
|
-
</div>
|
|
467
|
-
<div className="flex flex-col gap-0.5 sm:col-span-2">
|
|
468
|
-
<dt className="flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
|
|
469
|
-
<i className="fa-light fa-calendar-days text-xs" aria-hidden="true" />
|
|
470
|
-
Updated
|
|
471
|
-
</dt>
|
|
472
|
-
<dd className="text-xs tabular-nums text-foreground">{row.updatedAt}</dd>
|
|
473
|
-
</div>
|
|
474
|
-
</dl>
|
|
475
|
-
</div>
|
|
476
|
-
</div>
|
|
477
|
-
)
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
function QuestionsByTopicChart({ rows }: { rows: QuestionBankItem[] }) {
|
|
481
|
-
const data = React.useMemo(() => aggregateByTopic(rows), [rows])
|
|
482
|
-
if (rows.length === 0) {
|
|
483
|
-
return (
|
|
484
|
-
<div className="flex h-[200px] items-center justify-center text-sm text-muted-foreground" role="status">
|
|
485
|
-
No questions in this view.
|
|
486
|
-
</div>
|
|
487
|
-
)
|
|
488
|
-
}
|
|
489
|
-
const summary = `${data.length} topics shown. Total ${rows.length} questions.`
|
|
490
|
-
return (
|
|
491
|
-
<ChartFigure label="Questions by topic" summary={summary} dataLength={data.length}>
|
|
492
|
-
{(activeIndex) => (
|
|
493
|
-
<>
|
|
494
|
-
<ChartContainer config={BAR_CFG} className="h-[220px] w-full">
|
|
495
|
-
<BarChart data={data} layout="vertical" margin={{ top: 8, right: 8, left: 4, bottom: 0 }}>
|
|
496
|
-
<CartesianGrid horizontal={false} strokeDasharray="3 3" className="stroke-border" />
|
|
497
|
-
<XAxis type="number" allowDecimals={false} tick={{ fontSize: 12 }} tickLine={false} axisLine={false} />
|
|
498
|
-
<YAxis type="category" dataKey="name" width={100} tick={{ fontSize: 11 }} tickLine={false} axisLine={false} />
|
|
499
|
-
<ChartTooltip key={chartTooltipKeyboardSyncProps(activeIndex).key} {...chartTooltipKeyboardSyncProps(activeIndex).props} content={<ChartTooltipContent />} />
|
|
500
|
-
<Bar
|
|
501
|
-
dataKey="value"
|
|
502
|
-
fill="var(--color-chart-4)"
|
|
503
|
-
radius={[0, 4, 4, 0]}
|
|
504
|
-
maxBarSize={22}
|
|
505
|
-
activeBar={CHART_KBD_ACTIVE_BAR}
|
|
506
|
-
activeIndex={activeIndex ?? undefined}
|
|
507
|
-
>
|
|
508
|
-
{data.map((_, i) => (
|
|
509
|
-
<Cell key={i} fill="var(--color-chart-4)" />
|
|
510
|
-
))}
|
|
511
|
-
</Bar>
|
|
512
|
-
</BarChart>
|
|
513
|
-
</ChartContainer>
|
|
514
|
-
<ChartDataTable
|
|
515
|
-
caption="Questions by topic"
|
|
516
|
-
headers={["Topic", "Count"]}
|
|
517
|
-
rows={data.map(d => [d.name, d.value])}
|
|
518
|
-
/>
|
|
519
|
-
</>
|
|
520
|
-
)}
|
|
521
|
-
</ChartFigure>
|
|
522
|
-
)
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
function QuestionBankDashboardSimple({ rows }: { rows: QuestionBankItem[] }) {
|
|
526
|
-
const kpi = React.useMemo(
|
|
527
|
-
() => ({
|
|
528
|
-
metrics: questionBankKpiMetrics(rows),
|
|
529
|
-
insight: questionBankKpiInsight(rows),
|
|
530
|
-
}),
|
|
531
|
-
[rows],
|
|
532
325
|
)
|
|
533
326
|
|
|
534
|
-
return
|
|
535
|
-
<div className="flex min-h-0 flex-1 flex-col gap-4 pb-6">
|
|
536
|
-
<KeyMetrics
|
|
537
|
-
variant="flat"
|
|
538
|
-
metrics={kpi.metrics}
|
|
539
|
-
insight={kpi.insight}
|
|
540
|
-
showHeader={false}
|
|
541
|
-
metricsSingleRow
|
|
542
|
-
/>
|
|
543
|
-
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
|
544
|
-
<ChartCard variant="normal" title="By status" description="Filtered question set">
|
|
545
|
-
<QuestionsByStatusChart rows={rows} />
|
|
546
|
-
</ChartCard>
|
|
547
|
-
<ChartCard variant="normal" title="By topic" description="Up to eight topics">
|
|
548
|
-
<QuestionsByTopicChart rows={rows} />
|
|
549
|
-
</ChartCard>
|
|
550
|
-
</div>
|
|
551
|
-
</div>
|
|
552
|
-
)
|
|
327
|
+
return cols
|
|
553
328
|
}
|
|
554
329
|
|
|
555
330
|
interface HubFolderColumnsPanelProps {
|
|
@@ -813,7 +588,7 @@ function HubFolderColumnsPanel({
|
|
|
813
588
|
<i className="fa-light fa-ellipsis text-xs" aria-hidden="true" />
|
|
814
589
|
</Button>
|
|
815
590
|
</DropdownMenuTrigger>
|
|
816
|
-
<DropdownMenuContent align="end"
|
|
591
|
+
<DropdownMenuContent align="end">
|
|
817
592
|
<DropdownMenuItem onSelect={() => onCustomizeFolder?.(folder)}>
|
|
818
593
|
<i className="fa-light fa-wand-magic-sparkles text-xs" aria-hidden="true" />
|
|
819
594
|
Customize
|
|
@@ -865,6 +640,12 @@ export const QuestionBankTable = React.forwardRef<
|
|
|
865
640
|
items: QuestionBankItem[]
|
|
866
641
|
/** When set, table / board / tree rows are limited to this nav scope (secondary sidebar). */
|
|
867
642
|
navState?: QuestionBankNavState
|
|
643
|
+
/** URL toolbar search binding (`?q=`) — omit on search landing so hub `q` does not pre-fill the grid search. */
|
|
644
|
+
urlListSearch?: string
|
|
645
|
+
/** When true, dedicated search shell: hub landing row filters; table toolbar search stays independent of URL `q`. */
|
|
646
|
+
searchLanding?: boolean
|
|
647
|
+
/** Applied with nav filters before `useTableState` when {@link searchLanding} is true. */
|
|
648
|
+
landingFilters?: QuestionBankLandingFilterState | null
|
|
868
649
|
view?: DataListViewType
|
|
869
650
|
onViewChange?: (v: DataListViewType) => void
|
|
870
651
|
folders: QuestionBankFolder[]
|
|
@@ -872,20 +653,31 @@ export const QuestionBankTable = React.forwardRef<
|
|
|
872
653
|
onItemsChange: React.Dispatch<React.SetStateAction<QuestionBankItem[]>>
|
|
873
654
|
}
|
|
874
655
|
>(function QuestionBankTable(
|
|
875
|
-
{ items, navState, view = "table", onViewChange, folders, onFoldersChange, onItemsChange },
|
|
656
|
+
{ items, navState, urlListSearch, searchLanding, landingFilters, view = "table", onViewChange, folders, onFoldersChange, onItemsChange },
|
|
876
657
|
ref,
|
|
877
658
|
) {
|
|
878
659
|
const tableSourceItems = React.useMemo(() => {
|
|
879
660
|
const nav = navState ?? { scope: "all" as const, folderId: null }
|
|
880
|
-
|
|
881
|
-
|
|
661
|
+
const landing = searchLanding ? (landingFilters ?? null) : null
|
|
662
|
+
return applyQuestionBankHubDisplayFilters(items, folders, nav, landing)
|
|
663
|
+
}, [items, folders, navState, searchLanding, landingFilters])
|
|
882
664
|
|
|
883
|
-
const
|
|
665
|
+
const toggleFavorite = React.useCallback(
|
|
666
|
+
(row: QuestionBankItem) => {
|
|
667
|
+
onItemsChange(prev => prev.map(r => (r.id === row.id ? toggleQuestionBankItemFavorite(r) : r)))
|
|
668
|
+
},
|
|
669
|
+
[onItemsChange],
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
const columns = React.useMemo(
|
|
673
|
+
() => buildQuestionBankColumns(tableSourceItems, { onToggleFavorite: toggleFavorite }),
|
|
674
|
+
[tableSourceItems, toggleFavorite],
|
|
675
|
+
)
|
|
884
676
|
const filterFields = React.useMemo(() => columnsToFilterFields(columns), [columns])
|
|
885
677
|
const fieldDefinitionsForDrawer = React.useMemo(
|
|
886
678
|
() =>
|
|
887
679
|
columns
|
|
888
|
-
.filter(c => c.key !== "select" && c.key !== "actions")
|
|
680
|
+
.filter(c => c.key !== "select" && c.key !== "favorite" && c.key !== "actions")
|
|
889
681
|
.map(c => ({ key: c.key, label: c.label, sortable: !!(c.sortable && (c.sortKey ?? c.key)) })),
|
|
890
682
|
[columns],
|
|
891
683
|
)
|
|
@@ -915,7 +707,13 @@ export const QuestionBankTable = React.forwardRef<
|
|
|
915
707
|
setConditionalRules(prev => prev.map(r => r.id === id ? { ...r, ...patch } : r))
|
|
916
708
|
}, [])
|
|
917
709
|
|
|
918
|
-
const tableState = useTableState(
|
|
710
|
+
const tableState = useTableState(
|
|
711
|
+
tableSourceItems,
|
|
712
|
+
columns,
|
|
713
|
+
{ key: "updatedAt", dir: "desc" },
|
|
714
|
+
undefined,
|
|
715
|
+
searchLanding ? undefined : urlListSearch,
|
|
716
|
+
)
|
|
919
717
|
|
|
920
718
|
const openNewFolderForColumn = React.useCallback((parentId: string | null) => {
|
|
921
719
|
setNewFolderParentId(parentId)
|
|
@@ -940,12 +738,13 @@ export const QuestionBankTable = React.forwardRef<
|
|
|
940
738
|
...prev,
|
|
941
739
|
{
|
|
942
740
|
id: newQuestionBankItemId(),
|
|
741
|
+
questionId: newQuestionBankQuestionId(),
|
|
943
742
|
stem: "New question",
|
|
944
743
|
topic: "General",
|
|
945
744
|
type: "short_answer",
|
|
946
745
|
difficulty: "medium",
|
|
947
|
-
status: "draft",
|
|
948
746
|
author: "Demo user",
|
|
747
|
+
authorEmail: "demo.user@demo.exxat.io",
|
|
949
748
|
updatedAt: `${y}-${m}-${d}`,
|
|
950
749
|
folderId,
|
|
951
750
|
},
|
|
@@ -956,19 +755,6 @@ export const QuestionBankTable = React.forwardRef<
|
|
|
956
755
|
|
|
957
756
|
const renderFilterOptionValue = React.useCallback(
|
|
958
757
|
(fieldKey: string, value: string): React.ReactNode => {
|
|
959
|
-
if (fieldKey === "status") {
|
|
960
|
-
if (value in QUESTION_BANK_STATUS_LABEL) {
|
|
961
|
-
const status = value as keyof typeof QUESTION_BANK_STATUS_LABEL
|
|
962
|
-
return (
|
|
963
|
-
<ListHubStatusBadge
|
|
964
|
-
label={QUESTION_BANK_STATUS_LABEL[status]}
|
|
965
|
-
tintClassName={QUESTION_BANK_STATUS_BADGE_CLASS[status]}
|
|
966
|
-
icon={QUESTION_BANK_STATUS_ICON[status]}
|
|
967
|
-
/>
|
|
968
|
-
)
|
|
969
|
-
}
|
|
970
|
-
return <span className="text-foreground">{value}</span>
|
|
971
|
-
}
|
|
972
758
|
const col = columns.find(c => c.key === fieldKey)
|
|
973
759
|
const opt = col?.filter?.options?.find(o => o.value === value)
|
|
974
760
|
return <span className="text-foreground">{opt?.label ?? value}</span>
|
|
@@ -986,13 +772,14 @@ export const QuestionBankTable = React.forwardRef<
|
|
|
986
772
|
o => o.key === displayOptions.boardGroupByColumnKey,
|
|
987
773
|
)
|
|
988
774
|
? displayOptions.boardGroupByColumnKey
|
|
989
|
-
: "
|
|
775
|
+
: "topic"
|
|
990
776
|
|
|
991
777
|
const panelRenderDetail = (row: QuestionBankItem) => (
|
|
992
778
|
<div className="flex min-w-0 flex-col gap-4">
|
|
993
779
|
<div>
|
|
994
780
|
<h3 className="text-sm font-semibold text-foreground mb-2">Question</h3>
|
|
995
781
|
<p className="text-sm text-foreground">{row.stem}</p>
|
|
782
|
+
<p className="mt-1 font-mono text-xs text-muted-foreground">{row.questionId}</p>
|
|
996
783
|
</div>
|
|
997
784
|
<div className="flex gap-3 flex-wrap">
|
|
998
785
|
<div className="flex flex-col gap-1">
|
|
@@ -1003,12 +790,6 @@ export const QuestionBankTable = React.forwardRef<
|
|
|
1003
790
|
<span className="text-xs font-medium text-muted-foreground">Difficulty</span>
|
|
1004
791
|
<span className="text-sm text-foreground">{DIFFICULTY_LABEL[row.difficulty]}</span>
|
|
1005
792
|
</div>
|
|
1006
|
-
<div className="flex flex-col gap-1">
|
|
1007
|
-
<span className="text-xs font-medium text-muted-foreground">Status</span>
|
|
1008
|
-
<span className="text-sm text-foreground">
|
|
1009
|
-
{QUESTION_BANK_STATUS_LABEL[row.status]}
|
|
1010
|
-
</span>
|
|
1011
|
-
</div>
|
|
1012
793
|
</div>
|
|
1013
794
|
{row.topic && (
|
|
1014
795
|
<div>
|
|
@@ -1117,7 +898,7 @@ export const QuestionBankTable = React.forwardRef<
|
|
|
1117
898
|
return (
|
|
1118
899
|
<div className="flex min-h-0 flex-1 flex-col">
|
|
1119
900
|
{sharedToolbar}
|
|
1120
|
-
<QuestionBankListView rows={tableState.rows as QuestionBankItem[]} />
|
|
901
|
+
<QuestionBankListView rows={tableState.rows as QuestionBankItem[]} onToggleFavorite={toggleFavorite} />
|
|
1121
902
|
</div>
|
|
1122
903
|
)
|
|
1123
904
|
}
|
|
@@ -1129,6 +910,7 @@ export const QuestionBankTable = React.forwardRef<
|
|
|
1129
910
|
<QuestionBankBoardView
|
|
1130
911
|
rows={tableState.rows as QuestionBankItem[]}
|
|
1131
912
|
groupByColumnKey={questionBankBoardGroupKey}
|
|
913
|
+
onToggleFavorite={toggleFavorite}
|
|
1132
914
|
/>
|
|
1133
915
|
</div>
|
|
1134
916
|
)
|
|
@@ -1263,7 +1045,7 @@ export const QuestionBankTable = React.forwardRef<
|
|
|
1263
1045
|
return (
|
|
1264
1046
|
<div className="flex min-h-0 flex-1 flex-col">
|
|
1265
1047
|
{sharedToolbar}
|
|
1266
|
-
<
|
|
1048
|
+
<QuestionBankDashboardChartsSection rows={tableState.rows as QuestionBankItem[]} />
|
|
1267
1049
|
</div>
|
|
1268
1050
|
)
|
|
1269
1051
|
})
|