@fastnd/components 1.0.30 → 1.0.32
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/dist/components/QuickAccessCard/QuickAccessCard.d.ts +12 -0
- package/dist/components/ScoreBar/ScoreBar.d.ts +8 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/examples/dashboard/StatusDonutChart/StatusDonutChart.tsx +41 -56
- package/dist/examples/data-visualization/CardCarouselPanel/CardCarouselPanel.tsx +171 -0
- package/dist/examples/data-visualization/CellRenderers/CellRenderers.tsx +175 -0
- package/dist/examples/data-visualization/ColumnConfigPopover/ColumnConfigPopover.tsx +124 -0
- package/dist/examples/data-visualization/DataCardView/DataCardView.tsx +223 -0
- package/dist/examples/data-visualization/DataExplorerPagination/DataExplorerPagination.tsx +143 -0
- package/dist/examples/data-visualization/DataExplorerToolbar/DataExplorerToolbar.tsx +88 -0
- package/dist/examples/data-visualization/DataListView/DataListView.tsx +246 -0
- package/dist/examples/data-visualization/DataTableView/DataTableView.tsx +449 -0
- package/dist/examples/data-visualization/DataVisualizationPage/DataVisualizationPage.tsx +140 -0
- package/dist/examples/data-visualization/FilterChipGroup/FilterChipGroup.tsx +125 -0
- package/dist/examples/data-visualization/MoreFiltersPopover/MoreFiltersPopover.tsx +132 -0
- package/dist/examples/data-visualization/constants.ts +587 -0
- package/dist/examples/data-visualization/hooks/use-column-config.ts +76 -0
- package/dist/examples/data-visualization/hooks/use-data-explorer-state.ts +313 -0
- package/dist/examples/data-visualization/index.ts +1 -0
- package/dist/examples/data-visualization/types.ts +99 -0
- package/dist/examples/quickaccess/QuickAccess/QuickAccess.tsx +97 -0
- package/dist/examples/quickaccess/index.ts +2 -0
- package/dist/examples/quickaccess/types.ts +11 -0
- package/dist/fastnd-components.js +5708 -5590
- package/package.json +1 -1
- package/dist/examples/data-explorer/CardCarouselPanel/CardCarouselPanel.tsx +0 -197
- package/dist/examples/data-explorer/CardView/CardView.tsx +0 -168
- package/dist/examples/data-explorer/ColumnConfigPopover/ColumnConfigPopover.tsx +0 -157
- package/dist/examples/data-explorer/DataExplorerEmpty/DataExplorerEmpty.tsx +0 -56
- package/dist/examples/data-explorer/DataExplorerPage/DataExplorerPage.tsx +0 -101
- package/dist/examples/data-explorer/DataExplorerPagination/DataExplorerPagination.tsx +0 -129
- package/dist/examples/data-explorer/DataExplorerToolbar/DataExplorerToolbar.tsx +0 -143
- package/dist/examples/data-explorer/DomainSwitcher/DomainSwitcher.tsx +0 -36
- package/dist/examples/data-explorer/ExpansionRows/ExpansionRows.tsx +0 -180
- package/dist/examples/data-explorer/FilterChip/FilterChip.tsx +0 -85
- package/dist/examples/data-explorer/FilterPopoverContent/FilterPopoverContent.tsx +0 -73
- package/dist/examples/data-explorer/ListView/ListView.tsx +0 -305
- package/dist/examples/data-explorer/MoreFiltersPopover/MoreFiltersPopover.tsx +0 -113
- package/dist/examples/data-explorer/TableView/TableView.tsx +0 -193
- package/dist/examples/data-explorer/cells/CellRenderer.tsx +0 -147
- package/dist/examples/data-explorer/cells/CurrencyCell.tsx +0 -31
- package/dist/examples/data-explorer/cells/DoubleTextCell.tsx +0 -27
- package/dist/examples/data-explorer/cells/ExpandButton.tsx +0 -67
- package/dist/examples/data-explorer/cells/InventoryBadgeCell.tsx +0 -52
- package/dist/examples/data-explorer/cells/LinkCell.tsx +0 -42
- package/dist/examples/data-explorer/cells/ScoreBar.tsx +0 -50
- package/dist/examples/data-explorer/cells/StatusBadgeCell.tsx +0 -39
- package/dist/examples/data-explorer/cells/TextCell.tsx +0 -35
- package/dist/examples/data-explorer/cells/index.ts +0 -26
- package/dist/examples/data-explorer/domains/applications.ts +0 -225
- package/dist/examples/data-explorer/domains/customers.ts +0 -267
- package/dist/examples/data-explorer/domains/index.ts +0 -26
- package/dist/examples/data-explorer/domains/products.ts +0 -1116
- package/dist/examples/data-explorer/domains/projects.ts +0 -205
- package/dist/examples/data-explorer/hooks/use-data-explorer-state.ts +0 -371
- package/dist/examples/data-explorer/index.ts +0 -3
- package/dist/examples/data-explorer/types.ts +0 -239
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
import * as React from 'react'
|
|
2
|
-
import {
|
|
3
|
-
Table,
|
|
4
|
-
TableBody,
|
|
5
|
-
TableCell,
|
|
6
|
-
TableHead,
|
|
7
|
-
TableHeader,
|
|
8
|
-
TableRow,
|
|
9
|
-
} from '@/components/ui/table'
|
|
10
|
-
import { FavoriteButton } from '@/components/FavoriteButton/FavoriteButton'
|
|
11
|
-
import { cn } from '@/lib/utils'
|
|
12
|
-
import type { ColumnDef, ListLayout } from '../types'
|
|
13
|
-
import { CellRenderer } from '../cells'
|
|
14
|
-
import { ScoreBar } from '../cells'
|
|
15
|
-
|
|
16
|
-
interface ListViewProps {
|
|
17
|
-
data: Record<string, unknown>[]
|
|
18
|
-
columns: Record<string, ColumnDef>
|
|
19
|
-
layout: ListLayout
|
|
20
|
-
expandedRows: Set<string>
|
|
21
|
-
favorites: Set<string>
|
|
22
|
-
onToggleExpand: (rowId: string) => void
|
|
23
|
-
onToggleFavorite: (rowId: string) => void
|
|
24
|
-
className?: string
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
// Expansion table rendered inline below a list item when a field is expanded
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
|
|
31
|
-
interface ExpansionTableProps {
|
|
32
|
-
row: Record<string, unknown>
|
|
33
|
-
columnKey: string
|
|
34
|
-
column: ColumnDef
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const ExpansionTable = ({ row, columnKey, column }: ExpansionTableProps) => {
|
|
38
|
-
if (!column.expandColumns || column.expandColumns.length === 0) return null
|
|
39
|
-
|
|
40
|
-
const items = row[columnKey]
|
|
41
|
-
if (!Array.isArray(items) || items.length === 0) return null
|
|
42
|
-
|
|
43
|
-
const title = column.expandTitleFn
|
|
44
|
-
? column.expandTitleFn(row)
|
|
45
|
-
: column.expandLabel ?? columnKey
|
|
46
|
-
|
|
47
|
-
return (
|
|
48
|
-
<div data-slot="expansion-table" className="border rounded-lg overflow-hidden bg-muted/30">
|
|
49
|
-
<div className="px-3 py-2 border-b bg-muted/50">
|
|
50
|
-
<span className="text-xs font-medium text-foreground">{title}</span>
|
|
51
|
-
<span className="ml-2 text-xs text-muted-foreground">({items.length})</span>
|
|
52
|
-
</div>
|
|
53
|
-
<Table>
|
|
54
|
-
<TableHeader>
|
|
55
|
-
<TableRow>
|
|
56
|
-
{column.expandColumns.map((ec) => (
|
|
57
|
-
<TableHead key={ec.key} className="text-xs h-8 px-3">
|
|
58
|
-
{ec.label}
|
|
59
|
-
</TableHead>
|
|
60
|
-
))}
|
|
61
|
-
</TableRow>
|
|
62
|
-
</TableHeader>
|
|
63
|
-
<TableBody>
|
|
64
|
-
{(items as Record<string, unknown>[]).map((item, idx) => (
|
|
65
|
-
<TableRow key={idx}>
|
|
66
|
-
{column.expandColumns!.map((ec) => {
|
|
67
|
-
const value = item[ec.key]
|
|
68
|
-
|
|
69
|
-
if (ec.type === 'score-bar') {
|
|
70
|
-
const numValue = typeof value === 'number' ? value : 0
|
|
71
|
-
return (
|
|
72
|
-
<TableCell key={ec.key} className="px-3 py-1.5">
|
|
73
|
-
<ScoreBar value={numValue} />
|
|
74
|
-
</TableCell>
|
|
75
|
-
)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
let display: React.ReactNode
|
|
79
|
-
|
|
80
|
-
if (ec.bold) {
|
|
81
|
-
display = (
|
|
82
|
-
<span className="font-medium text-xs text-foreground">
|
|
83
|
-
{value != null ? String(value) : '—'}
|
|
84
|
-
</span>
|
|
85
|
-
)
|
|
86
|
-
} else if (ec.muted) {
|
|
87
|
-
display = (
|
|
88
|
-
<span className="text-xs text-muted-foreground">
|
|
89
|
-
{value != null ? String(value) : '—'}
|
|
90
|
-
</span>
|
|
91
|
-
)
|
|
92
|
-
} else {
|
|
93
|
-
display = (
|
|
94
|
-
<span className="text-xs">
|
|
95
|
-
{typeof value === 'boolean'
|
|
96
|
-
? value
|
|
97
|
-
? 'Ja'
|
|
98
|
-
: 'Nein'
|
|
99
|
-
: value != null
|
|
100
|
-
? String(value)
|
|
101
|
-
: '—'}
|
|
102
|
-
</span>
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return (
|
|
107
|
-
<TableCell key={ec.key} className="px-3 py-1.5">
|
|
108
|
-
{display}
|
|
109
|
-
</TableCell>
|
|
110
|
-
)
|
|
111
|
-
})}
|
|
112
|
-
</TableRow>
|
|
113
|
-
))}
|
|
114
|
-
</TableBody>
|
|
115
|
-
</Table>
|
|
116
|
-
</div>
|
|
117
|
-
)
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
ExpansionTable.displayName = 'ExpansionTable'
|
|
121
|
-
|
|
122
|
-
// ---------------------------------------------------------------------------
|
|
123
|
-
// Single list item — CSS Grid row, no Card
|
|
124
|
-
// ---------------------------------------------------------------------------
|
|
125
|
-
|
|
126
|
-
interface ListItemProps {
|
|
127
|
-
row: Record<string, unknown>
|
|
128
|
-
columns: Record<string, ColumnDef>
|
|
129
|
-
layout: ListLayout
|
|
130
|
-
expandedRows: Set<string>
|
|
131
|
-
isFavorite: boolean
|
|
132
|
-
isLast: boolean
|
|
133
|
-
onToggleExpand: (rowId: string) => void
|
|
134
|
-
onToggleFavorite: (rowId: string) => void
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const ListItem = ({
|
|
138
|
-
row,
|
|
139
|
-
columns,
|
|
140
|
-
layout,
|
|
141
|
-
expandedRows,
|
|
142
|
-
isFavorite,
|
|
143
|
-
isLast,
|
|
144
|
-
onToggleExpand,
|
|
145
|
-
onToggleFavorite,
|
|
146
|
-
}: ListItemProps) => {
|
|
147
|
-
const rowId = row['id'] as string
|
|
148
|
-
const expandFields = layout.expandFields ?? (layout.expandField ? [layout.expandField] : [])
|
|
149
|
-
|
|
150
|
-
const activeExpandField = expandFields.find((f) => expandedRows.has(`${rowId}::${f}`))
|
|
151
|
-
|
|
152
|
-
const titleCol = columns[layout.titleField]
|
|
153
|
-
const subtitle =
|
|
154
|
-
titleCol?.type === 'double-text' && titleCol.secondary
|
|
155
|
-
? (row[titleCol.secondary] as string | null | undefined)
|
|
156
|
-
: undefined
|
|
157
|
-
|
|
158
|
-
const borderClass = isLast && !activeExpandField ? '' : 'border-b border-border'
|
|
159
|
-
|
|
160
|
-
return (
|
|
161
|
-
<>
|
|
162
|
-
{/* Main grid row */}
|
|
163
|
-
<div
|
|
164
|
-
data-slot="list-item"
|
|
165
|
-
className={cn(
|
|
166
|
-
'grid grid-cols-[36px_1fr_auto_auto] gap-x-3 items-center py-3 px-4 hover:bg-accent transition-colors',
|
|
167
|
-
borderClass,
|
|
168
|
-
)}
|
|
169
|
-
>
|
|
170
|
-
{/* Col 1 (row-span-2): Favorite */}
|
|
171
|
-
<div className="row-span-2 self-center">
|
|
172
|
-
<FavoriteButton
|
|
173
|
-
pressed={isFavorite}
|
|
174
|
-
itemName={String(row[layout.titleField] ?? rowId)}
|
|
175
|
-
onPressedChange={() => onToggleFavorite(rowId)}
|
|
176
|
-
/>
|
|
177
|
-
</div>
|
|
178
|
-
|
|
179
|
-
{/* Col 2 Row 1: Title */}
|
|
180
|
-
<div className="font-semibold text-sm max-w-[280px] truncate">
|
|
181
|
-
{row[layout.titleField] != null ? String(row[layout.titleField]) : '—'}
|
|
182
|
-
</div>
|
|
183
|
-
|
|
184
|
-
{/* Col 3 (row-span-2): Badges */}
|
|
185
|
-
<div className="row-span-2 flex gap-1.5 items-center self-center">
|
|
186
|
-
{layout.badgeFields?.map((field) => {
|
|
187
|
-
const col = columns[field]
|
|
188
|
-
if (!col) return null
|
|
189
|
-
return (
|
|
190
|
-
<CellRenderer
|
|
191
|
-
key={field}
|
|
192
|
-
column={col}
|
|
193
|
-
columnKey={field}
|
|
194
|
-
row={row}
|
|
195
|
-
/>
|
|
196
|
-
)
|
|
197
|
-
})}
|
|
198
|
-
</div>
|
|
199
|
-
|
|
200
|
-
{/* Col 4 (row-span-2): Expand actions */}
|
|
201
|
-
<div className="row-span-2 flex gap-1 items-center self-center">
|
|
202
|
-
{expandFields.map((field) => {
|
|
203
|
-
const col = columns[field]
|
|
204
|
-
if (!col) return null
|
|
205
|
-
const expansionKey = `${rowId}::${field}`
|
|
206
|
-
return (
|
|
207
|
-
<CellRenderer
|
|
208
|
-
key={field}
|
|
209
|
-
column={col}
|
|
210
|
-
columnKey={field}
|
|
211
|
-
row={row}
|
|
212
|
-
expanded={expandedRows.has(expansionKey)}
|
|
213
|
-
onToggleExpand={() => onToggleExpand(expansionKey)}
|
|
214
|
-
/>
|
|
215
|
-
)
|
|
216
|
-
})}
|
|
217
|
-
</div>
|
|
218
|
-
|
|
219
|
-
{/* Col 2 Row 2: Subtitle + meta chips */}
|
|
220
|
-
<div className="flex gap-1.5 items-center mt-0.5 min-w-0">
|
|
221
|
-
{subtitle && (
|
|
222
|
-
<span className="text-xs text-muted-foreground max-w-[320px] truncate">
|
|
223
|
-
{subtitle}
|
|
224
|
-
</span>
|
|
225
|
-
)}
|
|
226
|
-
{layout.metaFields?.map((field) => {
|
|
227
|
-
const value = row[field]
|
|
228
|
-
if (value == null || value === '') return null
|
|
229
|
-
return (
|
|
230
|
-
<span
|
|
231
|
-
key={field}
|
|
232
|
-
className="bg-secondary rounded-sm px-2 py-0.5 text-xs text-muted-foreground shrink-0"
|
|
233
|
-
>
|
|
234
|
-
{String(value)}
|
|
235
|
-
</span>
|
|
236
|
-
)
|
|
237
|
-
})}
|
|
238
|
-
</div>
|
|
239
|
-
</div>
|
|
240
|
-
|
|
241
|
-
{/* Expansion content */}
|
|
242
|
-
{activeExpandField && columns[activeExpandField] && (
|
|
243
|
-
<div
|
|
244
|
-
data-slot="expansion-row"
|
|
245
|
-
className={cn(
|
|
246
|
-
'py-3 pl-[52px] pr-4',
|
|
247
|
-
isLast ? '' : 'border-b border-border',
|
|
248
|
-
)}
|
|
249
|
-
>
|
|
250
|
-
<ExpansionTable
|
|
251
|
-
row={row}
|
|
252
|
-
columnKey={activeExpandField}
|
|
253
|
-
column={columns[activeExpandField]}
|
|
254
|
-
/>
|
|
255
|
-
</div>
|
|
256
|
-
)}
|
|
257
|
-
</>
|
|
258
|
-
)
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
ListItem.displayName = 'ListItem'
|
|
262
|
-
|
|
263
|
-
// ---------------------------------------------------------------------------
|
|
264
|
-
// ListView — single bordered container, items separated by border-bottom
|
|
265
|
-
// ---------------------------------------------------------------------------
|
|
266
|
-
|
|
267
|
-
const ListView = ({
|
|
268
|
-
data,
|
|
269
|
-
columns,
|
|
270
|
-
layout,
|
|
271
|
-
expandedRows,
|
|
272
|
-
favorites,
|
|
273
|
-
onToggleExpand,
|
|
274
|
-
onToggleFavorite,
|
|
275
|
-
className,
|
|
276
|
-
}: ListViewProps) => {
|
|
277
|
-
return (
|
|
278
|
-
<div
|
|
279
|
-
data-slot="list-view"
|
|
280
|
-
className={cn('border border-border rounded-lg mx-6 overflow-hidden', className)}
|
|
281
|
-
>
|
|
282
|
-
{data.map((row, index) => {
|
|
283
|
-
const rowId = row['id'] as string
|
|
284
|
-
return (
|
|
285
|
-
<ListItem
|
|
286
|
-
key={rowId}
|
|
287
|
-
row={row}
|
|
288
|
-
columns={columns}
|
|
289
|
-
layout={layout}
|
|
290
|
-
expandedRows={expandedRows}
|
|
291
|
-
isFavorite={favorites.has(rowId)}
|
|
292
|
-
isLast={index === data.length - 1}
|
|
293
|
-
onToggleExpand={onToggleExpand}
|
|
294
|
-
onToggleFavorite={onToggleFavorite}
|
|
295
|
-
/>
|
|
296
|
-
)
|
|
297
|
-
})}
|
|
298
|
-
</div>
|
|
299
|
-
)
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
ListView.displayName = 'ListView'
|
|
303
|
-
|
|
304
|
-
export { ListView }
|
|
305
|
-
export type { ListViewProps }
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { ChevronDown, ListFilter } from 'lucide-react'
|
|
2
|
-
import { Badge } from '@/components/ui/badge'
|
|
3
|
-
import { Button } from '@/components/ui/button'
|
|
4
|
-
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
|
|
5
|
-
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
|
6
|
-
import { FilterPopoverContent } from '../FilterPopoverContent/FilterPopoverContent'
|
|
7
|
-
import type { ColumnDef } from '../types'
|
|
8
|
-
import { cn } from '@/lib/utils'
|
|
9
|
-
|
|
10
|
-
export interface MoreFiltersPopoverProps {
|
|
11
|
-
columns: [string, ColumnDef][]
|
|
12
|
-
filters: Record<string, string[]>
|
|
13
|
-
getFilterOptions: (columnKey: string) => string[]
|
|
14
|
-
onToggleOption: (columnKey: string, value: string) => void
|
|
15
|
-
onResetSecondary: () => void
|
|
16
|
-
totalSecondaryCount: number
|
|
17
|
-
className?: string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function MoreFiltersPopover({
|
|
21
|
-
columns,
|
|
22
|
-
filters,
|
|
23
|
-
getFilterOptions,
|
|
24
|
-
onToggleOption,
|
|
25
|
-
onResetSecondary,
|
|
26
|
-
totalSecondaryCount,
|
|
27
|
-
className,
|
|
28
|
-
}: MoreFiltersPopoverProps) {
|
|
29
|
-
if (columns.length === 0) return null
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<Popover>
|
|
33
|
-
<PopoverTrigger asChild>
|
|
34
|
-
<Button
|
|
35
|
-
data-slot="more-filters-popover"
|
|
36
|
-
variant="outline"
|
|
37
|
-
size="sm"
|
|
38
|
-
aria-haspopup="dialog"
|
|
39
|
-
aria-label="Weitere Filter"
|
|
40
|
-
className={cn(
|
|
41
|
-
'h-8 gap-1.5 text-sm',
|
|
42
|
-
totalSecondaryCount > 0 && 'border-primary/50 bg-primary/5 text-primary hover:bg-primary/10',
|
|
43
|
-
className,
|
|
44
|
-
)}
|
|
45
|
-
>
|
|
46
|
-
<ListFilter className="size-3.5" />
|
|
47
|
-
<span>Weitere Filter</span>
|
|
48
|
-
{totalSecondaryCount > 0 && (
|
|
49
|
-
<Badge variant="default" className="h-4.5 min-w-4 px-1.5 py-0 text-[10px]">
|
|
50
|
-
{totalSecondaryCount}
|
|
51
|
-
</Badge>
|
|
52
|
-
)}
|
|
53
|
-
</Button>
|
|
54
|
-
</PopoverTrigger>
|
|
55
|
-
|
|
56
|
-
<PopoverContent
|
|
57
|
-
align="start"
|
|
58
|
-
sideOffset={4}
|
|
59
|
-
role="dialog"
|
|
60
|
-
aria-label="Weitere Filter"
|
|
61
|
-
className="w-80 p-0"
|
|
62
|
-
>
|
|
63
|
-
<div className="flex items-center justify-between border-b px-3 py-2">
|
|
64
|
-
<span className="text-sm font-medium">Weitere Filter</span>
|
|
65
|
-
{totalSecondaryCount > 0 && (
|
|
66
|
-
<Button
|
|
67
|
-
variant="ghost"
|
|
68
|
-
size="sm"
|
|
69
|
-
onClick={onResetSecondary}
|
|
70
|
-
className="h-7 px-2 text-xs text-muted-foreground hover:text-destructive"
|
|
71
|
-
>
|
|
72
|
-
Alle zurücksetzen
|
|
73
|
-
</Button>
|
|
74
|
-
)}
|
|
75
|
-
</div>
|
|
76
|
-
|
|
77
|
-
<div className="max-h-[420px] overflow-y-auto">
|
|
78
|
-
{columns.map(([colKey, col]) => {
|
|
79
|
-
const selected = filters[colKey] ?? []
|
|
80
|
-
const options = getFilterOptions(colKey)
|
|
81
|
-
return (
|
|
82
|
-
<Collapsible key={colKey} defaultOpen>
|
|
83
|
-
<CollapsibleTrigger className="group flex w-full items-center justify-between px-3 py-2 text-left text-sm font-medium hover:bg-accent">
|
|
84
|
-
<span className="flex items-center gap-2">
|
|
85
|
-
{col.label}
|
|
86
|
-
{selected.length > 0 && (
|
|
87
|
-
<Badge variant="default" className="h-4 min-w-4 px-1.5 py-0 text-[10px]">
|
|
88
|
-
{selected.length}
|
|
89
|
-
</Badge>
|
|
90
|
-
)}
|
|
91
|
-
</span>
|
|
92
|
-
<ChevronDown className="size-3.5 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" />
|
|
93
|
-
</CollapsibleTrigger>
|
|
94
|
-
<CollapsibleContent>
|
|
95
|
-
<div className="px-3 pb-3 pt-1">
|
|
96
|
-
<FilterPopoverContent
|
|
97
|
-
options={options}
|
|
98
|
-
selectedValues={selected}
|
|
99
|
-
onToggleOption={(value) => onToggleOption(colKey, value)}
|
|
100
|
-
labelMap={col.labelMap}
|
|
101
|
-
/>
|
|
102
|
-
</div>
|
|
103
|
-
</CollapsibleContent>
|
|
104
|
-
</Collapsible>
|
|
105
|
-
)
|
|
106
|
-
})}
|
|
107
|
-
</div>
|
|
108
|
-
</PopoverContent>
|
|
109
|
-
</Popover>
|
|
110
|
-
)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
MoreFiltersPopover.displayName = 'MoreFiltersPopover'
|
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import * as React from 'react'
|
|
2
|
-
import { ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react'
|
|
3
|
-
import { cn } from '@/lib/utils'
|
|
4
|
-
import {
|
|
5
|
-
Table,
|
|
6
|
-
TableBody,
|
|
7
|
-
TableHead,
|
|
8
|
-
TableHeader,
|
|
9
|
-
TableRow,
|
|
10
|
-
TableCell,
|
|
11
|
-
} from '@/components/ui/table'
|
|
12
|
-
import { CellRenderer } from '../cells'
|
|
13
|
-
import { ExpansionRows } from '../ExpansionRows/ExpansionRows'
|
|
14
|
-
import type { ColumnDef, SortDirection } from '../types'
|
|
15
|
-
|
|
16
|
-
interface TableViewProps {
|
|
17
|
-
data: Record<string, unknown>[]
|
|
18
|
-
columns: Record<string, ColumnDef>
|
|
19
|
-
visibleColumns: string[]
|
|
20
|
-
sortColumn: string | null
|
|
21
|
-
sortDirection: SortDirection
|
|
22
|
-
expandedRows: Set<string>
|
|
23
|
-
favorites: Set<string>
|
|
24
|
-
onToggleSort: (column: string) => void
|
|
25
|
-
onToggleExpand: (rowId: string) => void
|
|
26
|
-
onToggleFavorite: (rowId: string) => void
|
|
27
|
-
className?: string
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const SortIcon = ({
|
|
31
|
-
columnKey,
|
|
32
|
-
sortColumn,
|
|
33
|
-
sortDirection,
|
|
34
|
-
}: {
|
|
35
|
-
columnKey: string
|
|
36
|
-
sortColumn: string | null
|
|
37
|
-
sortDirection: SortDirection
|
|
38
|
-
}) => {
|
|
39
|
-
if (sortColumn !== columnKey) {
|
|
40
|
-
return <ChevronsUpDown className="size-3.5 text-muted-foreground/60" aria-hidden />
|
|
41
|
-
}
|
|
42
|
-
return sortDirection === 'asc' ? (
|
|
43
|
-
<ChevronUp className="size-3.5" aria-hidden />
|
|
44
|
-
) : (
|
|
45
|
-
<ChevronDown className="size-3.5" aria-hidden />
|
|
46
|
-
)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const TableView = ({
|
|
50
|
-
data,
|
|
51
|
-
columns,
|
|
52
|
-
visibleColumns,
|
|
53
|
-
sortColumn,
|
|
54
|
-
sortDirection,
|
|
55
|
-
expandedRows,
|
|
56
|
-
favorites,
|
|
57
|
-
onToggleSort,
|
|
58
|
-
onToggleExpand,
|
|
59
|
-
onToggleFavorite,
|
|
60
|
-
className,
|
|
61
|
-
}: TableViewProps) => {
|
|
62
|
-
return (
|
|
63
|
-
<div
|
|
64
|
-
data-slot="table-view"
|
|
65
|
-
className={cn('border border-border rounded-lg mx-6 overflow-hidden', className)}
|
|
66
|
-
>
|
|
67
|
-
<div
|
|
68
|
-
role="region"
|
|
69
|
-
aria-label="Datentabelle"
|
|
70
|
-
tabIndex={0}
|
|
71
|
-
className="overflow-auto focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
|
|
72
|
-
>
|
|
73
|
-
<Table>
|
|
74
|
-
<TableHeader className="bg-accent">
|
|
75
|
-
<TableRow className="hover:bg-transparent">
|
|
76
|
-
{visibleColumns.map((columnKey) => {
|
|
77
|
-
const column = columns[columnKey]
|
|
78
|
-
if (!column) return null
|
|
79
|
-
|
|
80
|
-
const isSorted = sortColumn === columnKey
|
|
81
|
-
const ariaSortValue: React.AriaAttributes['aria-sort'] = isSorted
|
|
82
|
-
? sortDirection === 'asc'
|
|
83
|
-
? 'ascending'
|
|
84
|
-
: 'descending'
|
|
85
|
-
: undefined
|
|
86
|
-
|
|
87
|
-
return (
|
|
88
|
-
<TableHead
|
|
89
|
-
key={columnKey}
|
|
90
|
-
scope="col"
|
|
91
|
-
aria-sort={ariaSortValue}
|
|
92
|
-
onClick={column.sortable ? () => onToggleSort(columnKey) : undefined}
|
|
93
|
-
className={cn(
|
|
94
|
-
'text-xs font-semibold uppercase tracking-wide text-muted-foreground px-3 py-2.5 h-auto',
|
|
95
|
-
column.sortable && 'cursor-pointer select-none',
|
|
96
|
-
column.hideMobile && 'hidden sm:table-cell',
|
|
97
|
-
column.hideTablet && 'hidden lg:table-cell',
|
|
98
|
-
)}
|
|
99
|
-
>
|
|
100
|
-
<span className="inline-flex items-center gap-1">
|
|
101
|
-
{column.label}
|
|
102
|
-
{column.sortable && (
|
|
103
|
-
<SortIcon
|
|
104
|
-
columnKey={columnKey}
|
|
105
|
-
sortColumn={sortColumn}
|
|
106
|
-
sortDirection={sortDirection}
|
|
107
|
-
/>
|
|
108
|
-
)}
|
|
109
|
-
</span>
|
|
110
|
-
</TableHead>
|
|
111
|
-
)
|
|
112
|
-
})}
|
|
113
|
-
</TableRow>
|
|
114
|
-
</TableHeader>
|
|
115
|
-
|
|
116
|
-
<TableBody>
|
|
117
|
-
{data.map((row) => {
|
|
118
|
-
const rowId = row['id'] as string
|
|
119
|
-
|
|
120
|
-
// Collect which expand columns are currently expanded for this row
|
|
121
|
-
const expandedColumnKeys = visibleColumns.filter((columnKey) => {
|
|
122
|
-
const column = columns[columnKey]
|
|
123
|
-
return column?.type === 'expand' && expandedRows.has(`${rowId}::${columnKey}`)
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
const isExpanded = expandedColumnKeys.length > 0
|
|
127
|
-
|
|
128
|
-
return (
|
|
129
|
-
<React.Fragment key={rowId}>
|
|
130
|
-
<TableRow className={cn(isExpanded && 'bg-primary/[0.03]')}>
|
|
131
|
-
{visibleColumns.map((columnKey) => {
|
|
132
|
-
const column = columns[columnKey]
|
|
133
|
-
if (!column) return null
|
|
134
|
-
|
|
135
|
-
const expandKey = `${rowId}::${columnKey}`
|
|
136
|
-
const isExpandedCell = expandedRows.has(expandKey)
|
|
137
|
-
|
|
138
|
-
return (
|
|
139
|
-
<TableCell
|
|
140
|
-
key={columnKey}
|
|
141
|
-
className={cn(
|
|
142
|
-
'px-3 py-2.5 text-[13px]',
|
|
143
|
-
column.hideMobile && 'hidden sm:table-cell',
|
|
144
|
-
column.hideTablet && 'hidden lg:table-cell',
|
|
145
|
-
)}
|
|
146
|
-
>
|
|
147
|
-
<CellRenderer
|
|
148
|
-
column={column}
|
|
149
|
-
columnKey={columnKey}
|
|
150
|
-
row={row}
|
|
151
|
-
expanded={isExpandedCell}
|
|
152
|
-
onToggleExpand={
|
|
153
|
-
column.type === 'expand'
|
|
154
|
-
? () => onToggleExpand(expandKey)
|
|
155
|
-
: undefined
|
|
156
|
-
}
|
|
157
|
-
onToggleFavorite={
|
|
158
|
-
column.type === 'favorite'
|
|
159
|
-
? () => onToggleFavorite(rowId)
|
|
160
|
-
: undefined
|
|
161
|
-
}
|
|
162
|
-
isFavorite={favorites.has(rowId)}
|
|
163
|
-
/>
|
|
164
|
-
</TableCell>
|
|
165
|
-
)
|
|
166
|
-
})}
|
|
167
|
-
</TableRow>
|
|
168
|
-
|
|
169
|
-
{expandedColumnKeys.map((columnKey) => (
|
|
170
|
-
<ExpansionRows
|
|
171
|
-
key={`${rowId}::${columnKey}::expansion`}
|
|
172
|
-
row={row}
|
|
173
|
-
columnKey={columnKey}
|
|
174
|
-
column={columns[columnKey]}
|
|
175
|
-
visibleColumns={visibleColumns}
|
|
176
|
-
columns={columns}
|
|
177
|
-
colSpan={visibleColumns.length}
|
|
178
|
-
/>
|
|
179
|
-
))}
|
|
180
|
-
</React.Fragment>
|
|
181
|
-
)
|
|
182
|
-
})}
|
|
183
|
-
</TableBody>
|
|
184
|
-
</Table>
|
|
185
|
-
</div>
|
|
186
|
-
</div>
|
|
187
|
-
)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
TableView.displayName = 'TableView'
|
|
191
|
-
|
|
192
|
-
export { TableView }
|
|
193
|
-
export type { TableViewProps }
|