@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.
Files changed (57) hide show
  1. package/dist/components/QuickAccessCard/QuickAccessCard.d.ts +12 -0
  2. package/dist/components/ScoreBar/ScoreBar.d.ts +8 -0
  3. package/dist/components/index.d.ts +2 -0
  4. package/dist/examples/dashboard/StatusDonutChart/StatusDonutChart.tsx +41 -56
  5. package/dist/examples/data-visualization/CardCarouselPanel/CardCarouselPanel.tsx +171 -0
  6. package/dist/examples/data-visualization/CellRenderers/CellRenderers.tsx +175 -0
  7. package/dist/examples/data-visualization/ColumnConfigPopover/ColumnConfigPopover.tsx +124 -0
  8. package/dist/examples/data-visualization/DataCardView/DataCardView.tsx +223 -0
  9. package/dist/examples/data-visualization/DataExplorerPagination/DataExplorerPagination.tsx +143 -0
  10. package/dist/examples/data-visualization/DataExplorerToolbar/DataExplorerToolbar.tsx +88 -0
  11. package/dist/examples/data-visualization/DataListView/DataListView.tsx +246 -0
  12. package/dist/examples/data-visualization/DataTableView/DataTableView.tsx +449 -0
  13. package/dist/examples/data-visualization/DataVisualizationPage/DataVisualizationPage.tsx +140 -0
  14. package/dist/examples/data-visualization/FilterChipGroup/FilterChipGroup.tsx +125 -0
  15. package/dist/examples/data-visualization/MoreFiltersPopover/MoreFiltersPopover.tsx +132 -0
  16. package/dist/examples/data-visualization/constants.ts +587 -0
  17. package/dist/examples/data-visualization/hooks/use-column-config.ts +76 -0
  18. package/dist/examples/data-visualization/hooks/use-data-explorer-state.ts +313 -0
  19. package/dist/examples/data-visualization/index.ts +1 -0
  20. package/dist/examples/data-visualization/types.ts +99 -0
  21. package/dist/examples/quickaccess/QuickAccess/QuickAccess.tsx +97 -0
  22. package/dist/examples/quickaccess/index.ts +2 -0
  23. package/dist/examples/quickaccess/types.ts +11 -0
  24. package/dist/fastnd-components.js +5708 -5590
  25. package/package.json +1 -1
  26. package/dist/examples/data-explorer/CardCarouselPanel/CardCarouselPanel.tsx +0 -197
  27. package/dist/examples/data-explorer/CardView/CardView.tsx +0 -168
  28. package/dist/examples/data-explorer/ColumnConfigPopover/ColumnConfigPopover.tsx +0 -157
  29. package/dist/examples/data-explorer/DataExplorerEmpty/DataExplorerEmpty.tsx +0 -56
  30. package/dist/examples/data-explorer/DataExplorerPage/DataExplorerPage.tsx +0 -101
  31. package/dist/examples/data-explorer/DataExplorerPagination/DataExplorerPagination.tsx +0 -129
  32. package/dist/examples/data-explorer/DataExplorerToolbar/DataExplorerToolbar.tsx +0 -143
  33. package/dist/examples/data-explorer/DomainSwitcher/DomainSwitcher.tsx +0 -36
  34. package/dist/examples/data-explorer/ExpansionRows/ExpansionRows.tsx +0 -180
  35. package/dist/examples/data-explorer/FilterChip/FilterChip.tsx +0 -85
  36. package/dist/examples/data-explorer/FilterPopoverContent/FilterPopoverContent.tsx +0 -73
  37. package/dist/examples/data-explorer/ListView/ListView.tsx +0 -305
  38. package/dist/examples/data-explorer/MoreFiltersPopover/MoreFiltersPopover.tsx +0 -113
  39. package/dist/examples/data-explorer/TableView/TableView.tsx +0 -193
  40. package/dist/examples/data-explorer/cells/CellRenderer.tsx +0 -147
  41. package/dist/examples/data-explorer/cells/CurrencyCell.tsx +0 -31
  42. package/dist/examples/data-explorer/cells/DoubleTextCell.tsx +0 -27
  43. package/dist/examples/data-explorer/cells/ExpandButton.tsx +0 -67
  44. package/dist/examples/data-explorer/cells/InventoryBadgeCell.tsx +0 -52
  45. package/dist/examples/data-explorer/cells/LinkCell.tsx +0 -42
  46. package/dist/examples/data-explorer/cells/ScoreBar.tsx +0 -50
  47. package/dist/examples/data-explorer/cells/StatusBadgeCell.tsx +0 -39
  48. package/dist/examples/data-explorer/cells/TextCell.tsx +0 -35
  49. package/dist/examples/data-explorer/cells/index.ts +0 -26
  50. package/dist/examples/data-explorer/domains/applications.ts +0 -225
  51. package/dist/examples/data-explorer/domains/customers.ts +0 -267
  52. package/dist/examples/data-explorer/domains/index.ts +0 -26
  53. package/dist/examples/data-explorer/domains/products.ts +0 -1116
  54. package/dist/examples/data-explorer/domains/projects.ts +0 -205
  55. package/dist/examples/data-explorer/hooks/use-data-explorer-state.ts +0 -371
  56. package/dist/examples/data-explorer/index.ts +0 -3
  57. package/dist/examples/data-explorer/types.ts +0 -239
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fastnd/components",
3
- "version": "1.0.30",
3
+ "version": "1.0.32",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "main": "./dist/fastnd-components.js",
@@ -1,197 +0,0 @@
1
- import * as React from 'react'
2
- import { X, Plus } from 'lucide-react'
3
- import { Button } from '@/components/ui/button'
4
- import { Badge } from '@/components/ui/badge'
5
- import {
6
- Carousel,
7
- CarouselContent,
8
- CarouselItem,
9
- CarouselPrevious,
10
- CarouselNext,
11
- } from '@/components/ui/carousel'
12
- import { cn } from '@/lib/utils'
13
- import type { ColumnDef, CardLayout } from '../types'
14
- import { CellRenderer } from '../cells'
15
- import { ScoreBar } from '../cells/ScoreBar'
16
-
17
- interface CardCarouselPanelProps {
18
- row: Record<string, unknown>
19
- columnKey: string
20
- column: ColumnDef
21
- cardLayout: CardLayout
22
- columns: Record<string, ColumnDef>
23
- onClose: () => void
24
- className?: string
25
- }
26
-
27
- const CardCarouselPanel = ({
28
- row,
29
- columnKey,
30
- column,
31
- cardLayout,
32
- columns,
33
- onClose,
34
- className,
35
- }: CardCarouselPanelProps) => {
36
- const items = row[columnKey]
37
- if (!Array.isArray(items) || items.length === 0) return null
38
- if (!column.expandColumns) return null
39
-
40
- const title = column.expandTitleFn ? column.expandTitleFn(row) : column.expandLabel ?? columnKey
41
- const scoreEc = column.expandColumns.find((ec) => ec.type === 'score-bar')
42
-
43
- return (
44
- <div
45
- data-slot="card-carousel-panel"
46
- className={cn(
47
- 'col-span-full bg-accent border border-border rounded-lg p-0 overflow-hidden',
48
- className
49
- )}
50
- >
51
- {/* Header */}
52
- <div className="flex justify-between items-center px-4 py-3 border-b border-border">
53
- <div className="flex items-center gap-2">
54
- <span className="font-semibold text-sm">{title}</span>
55
- <Badge variant="secondary" className="text-xs">{items.length}</Badge>
56
- </div>
57
- <div className="flex items-center gap-2">
58
- <Button
59
- variant="ghost"
60
- size="icon-xs"
61
- aria-label="Schließen"
62
- onClick={onClose}
63
- >
64
- <X className="size-4" aria-hidden="true" />
65
- </Button>
66
- </div>
67
- </div>
68
-
69
- {/* Carousel */}
70
- <div className="px-4 py-3">
71
- <Carousel
72
- opts={{ align: 'start', dragFree: true }}
73
- className="w-full px-10"
74
- >
75
- <CarouselContent className="-ml-3">
76
- {items.map((item, index) => {
77
- // Build virtual row by mapping expandColumns → main column keys via mapTo
78
- const vRow: Record<string, unknown> = {}
79
- for (const ec of column.expandColumns!) {
80
- if (ec.mapTo) {
81
- vRow[ec.mapTo] = (item as Record<string, unknown>)[ec.key]
82
- }
83
- if (ec.secondaryKey && ec.mapTo) {
84
- const mainCol = columns[ec.mapTo]
85
- if (mainCol?.secondary) {
86
- vRow[mainCol.secondary] = (item as Record<string, unknown>)[ec.secondaryKey]
87
- }
88
- }
89
- }
90
-
91
- return (
92
- <CarouselItem
93
- key={index}
94
- className="pl-3 basis-[280px] shrink-0"
95
- >
96
- {/* Mini card */}
97
- <div className="flex flex-col gap-2 rounded-lg border border-border bg-card p-3 h-full text-[13px]">
98
- {/* Mini card header */}
99
- <div>
100
- <div className="font-semibold text-xs line-clamp-2">
101
- {String(vRow[cardLayout.titleField] ?? '')}
102
- </div>
103
- {cardLayout.subtitleField && vRow[cardLayout.subtitleField] != null && (
104
- <div className="text-xs text-muted-foreground line-clamp-1 mt-0.5">
105
- {String(vRow[cardLayout.subtitleField])}
106
- </div>
107
- )}
108
- </div>
109
-
110
- {/* Badges */}
111
- {cardLayout.badgeFields && cardLayout.badgeFields.length > 0 && (
112
- <div className="flex flex-wrap gap-1.5">
113
- {cardLayout.badgeFields.map((bf) => {
114
- const bc = columns[bf]
115
- if (!bc || vRow[bf] == null) return null
116
- return (
117
- <CellRenderer
118
- key={bf}
119
- column={bc}
120
- columnKey={bf}
121
- row={vRow}
122
- />
123
- )
124
- })}
125
- </div>
126
- )}
127
-
128
- {/* Key-value rows */}
129
- {cardLayout.rows && cardLayout.rows.length > 0 && (
130
- <div className="flex flex-col gap-1">
131
- {cardLayout.rows.map((r) => {
132
- if (vRow[r.field] == null) return null
133
- const rc = columns[r.field]
134
- return (
135
- <div key={r.field} className="flex justify-between">
136
- <span className="text-muted-foreground shrink-0">{r.label}</span>
137
- <span className="font-medium text-right">
138
- {r.rendererOverride && rc ? (
139
- <CellRenderer
140
- column={{ ...rc, type: r.rendererOverride as ColumnDef['type'] }}
141
- columnKey={r.field}
142
- row={vRow}
143
- />
144
- ) : rc ? (
145
- <CellRenderer column={rc} columnKey={r.field} row={vRow} />
146
- ) : (
147
- String(vRow[r.field] ?? '')
148
- )}
149
- </span>
150
- </div>
151
- )
152
- })}
153
- </div>
154
- )}
155
-
156
- {/* Score bar (expand-specific) */}
157
- {scoreEc && (
158
- <ScoreBar
159
- value={
160
- typeof (item as Record<string, unknown>)[scoreEc.key] === 'number'
161
- ? Math.round(
162
- ((item as Record<string, unknown>)[scoreEc.key] as number) * 100
163
- )
164
- : 0
165
- }
166
- />
167
- )}
168
-
169
- {/* Add button */}
170
- <div className="mt-auto pt-1">
171
- <Button
172
- variant="outline"
173
- size="sm"
174
- className="gap-1.5 text-xs h-7 font-normal w-full"
175
- onClick={(e) => e.preventDefault()}
176
- >
177
- <Plus className="size-3 shrink-0" aria-hidden="true" />
178
- Hinzufügen
179
- </Button>
180
- </div>
181
- </div>
182
- </CarouselItem>
183
- )
184
- })}
185
- </CarouselContent>
186
- <CarouselPrevious className="-left-10" />
187
- <CarouselNext className="-right-10" />
188
- </Carousel>
189
- </div>
190
- </div>
191
- )
192
- }
193
-
194
- CardCarouselPanel.displayName = 'CardCarouselPanel'
195
-
196
- export { CardCarouselPanel }
197
- export type { CardCarouselPanelProps }
@@ -1,168 +0,0 @@
1
- import * as React from 'react'
2
- import { Card } from '@/components/ui/card'
3
- import { FavoriteButton } from '@/components/FavoriteButton/FavoriteButton'
4
- import { cn } from '@/lib/utils'
5
- import type { ColumnDef, CardLayout } from '../types'
6
- import { CellRenderer } from '../cells'
7
- import { CardCarouselPanel } from '../CardCarouselPanel/CardCarouselPanel'
8
-
9
- interface CardViewProps {
10
- data: Record<string, unknown>[]
11
- columns: Record<string, ColumnDef>
12
- layout: CardLayout
13
- expandedRows: Set<string>
14
- favorites: Set<string>
15
- onToggleExpand: (rowId: string) => void
16
- onToggleFavorite: (rowId: string) => void
17
- className?: string
18
- }
19
-
20
- const CardView = ({
21
- data,
22
- columns,
23
- layout,
24
- expandedRows,
25
- favorites,
26
- onToggleExpand,
27
- onToggleFavorite,
28
- className,
29
- }: CardViewProps) => {
30
- const expandFields = layout.expandFields ?? (layout.expandField ? [layout.expandField] : [])
31
-
32
- return (
33
- <div
34
- data-slot="card-view"
35
- className={cn(
36
- 'grid grid-cols-[repeat(auto-fill,minmax(280px,1fr))] gap-4 px-6',
37
- className
38
- )}
39
- >
40
- {data.map((row) => {
41
- const rowId = row['id'] as string
42
- const isFavorite = favorites.has(rowId)
43
- const isExpanded = expandFields.some((f) => expandedRows.has(`${rowId}::${f}`))
44
-
45
- const activeExpandField =
46
- expandFields.find((f) => expandedRows.has(`${rowId}::${f}`)) ?? null
47
-
48
- return (
49
- <React.Fragment key={rowId}>
50
- <Card
51
- className={cn(
52
- 'rounded-lg p-0 gap-0 overflow-hidden hover:shadow-md transition-shadow',
53
- isExpanded && 'border-b-2 border-b-primary shadow-md'
54
- )}
55
- >
56
- {/* Inner content padding wrapper */}
57
- <div className="p-4 flex flex-col gap-3">
58
- {/* Header: title + favorite */}
59
- <div className="flex justify-between items-start gap-2">
60
- <div className="min-w-0">
61
- <div className="font-semibold text-sm min-h-[2.8em] line-clamp-2">
62
- {String(row[layout.titleField] ?? '')}
63
- </div>
64
- {layout.subtitleField && row[layout.subtitleField] != null && (
65
- <div className="text-xs text-muted-foreground mt-0.5">
66
- {String(row[layout.subtitleField])}
67
- </div>
68
- )}
69
- </div>
70
- <FavoriteButton
71
- pressed={isFavorite}
72
- itemName={rowId}
73
- onPressedChange={() => onToggleFavorite(rowId)}
74
- className="shrink-0 -mt-0.5 -mr-1"
75
- />
76
- </div>
77
-
78
- {/* Badge fields */}
79
- {layout.badgeFields && layout.badgeFields.length > 0 && (
80
- <div className="flex flex-wrap gap-1.5">
81
- {layout.badgeFields.map((bf) => {
82
- const bc = columns[bf]
83
- if (!bc) return null
84
- return (
85
- <CellRenderer
86
- key={bf}
87
- column={bc}
88
- columnKey={bf}
89
- row={row}
90
- />
91
- )
92
- })}
93
- </div>
94
- )}
95
-
96
- {/* Key-value rows */}
97
- {layout.rows && layout.rows.length > 0 && (
98
- <div className="flex flex-col gap-1.5">
99
- {layout.rows.map((r) => {
100
- const rc = columns[r.field]
101
- return (
102
- <div key={r.field} className="flex justify-between text-[13px]">
103
- <span className="text-muted-foreground">{r.label}</span>
104
- <span className="font-medium text-right">
105
- {r.rendererOverride && rc ? (
106
- <CellRenderer
107
- column={{ ...rc, type: r.rendererOverride as ColumnDef['type'] }}
108
- columnKey={r.field}
109
- row={row}
110
- />
111
- ) : rc ? (
112
- <CellRenderer column={rc} columnKey={r.field} row={row} />
113
- ) : (
114
- String(row[r.field] ?? '')
115
- )}
116
- </span>
117
- </div>
118
- )
119
- })}
120
- </div>
121
- )}
122
- </div>
123
-
124
- {/* Footer — outside padding div, with border-top */}
125
- {expandFields.length > 0 && (
126
- <div className="mt-auto border-t border-border px-4 py-3 flex gap-2">
127
- {expandFields.map((ef) => {
128
- const ec = columns[ef]
129
- if (!ec) return null
130
- const expandKey = `${rowId}::${ef}`
131
- return (
132
- <CellRenderer
133
- key={ef}
134
- column={ec}
135
- columnKey={ef}
136
- row={row}
137
- expanded={expandedRows.has(expandKey)}
138
- onToggleExpand={() => onToggleExpand(expandKey)}
139
- className="flex-1 justify-center"
140
- />
141
- )
142
- })}
143
- </div>
144
- )}
145
- </Card>
146
-
147
- {/* Expansion carousel panel — spans full grid width */}
148
- {activeExpandField && (
149
- <CardCarouselPanel
150
- row={row}
151
- columnKey={activeExpandField}
152
- column={columns[activeExpandField]}
153
- cardLayout={layout}
154
- columns={columns}
155
- onClose={() => onToggleExpand(`${rowId}::${activeExpandField}`)}
156
- />
157
- )}
158
- </React.Fragment>
159
- )
160
- })}
161
- </div>
162
- )
163
- }
164
-
165
- CardView.displayName = 'CardView'
166
-
167
- export { CardView }
168
- export type { CardViewProps }
@@ -1,157 +0,0 @@
1
- import * as React from 'react'
2
- import { GripVertical, SlidersHorizontal } from 'lucide-react'
3
- import {
4
- DndContext,
5
- closestCenter,
6
- KeyboardSensor,
7
- PointerSensor,
8
- useSensor,
9
- useSensors,
10
- type DragEndEvent,
11
- } from '@dnd-kit/core'
12
- import {
13
- SortableContext,
14
- sortableKeyboardCoordinates,
15
- useSortable,
16
- verticalListSortingStrategy,
17
- arrayMove,
18
- } from '@dnd-kit/sortable'
19
- import { CSS } from '@dnd-kit/utilities'
20
- import { Button } from '@/components/ui/button'
21
- import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
22
- import { Switch } from '@/components/ui/switch'
23
- import type { ColumnDef } from '../types'
24
- import { cn } from '@/lib/utils'
25
-
26
- // --- Sortable item ---
27
-
28
- interface SortableColumnItemProps {
29
- id: string
30
- label: string
31
- visible: boolean
32
- onToggleVisibility: () => void
33
- }
34
-
35
- function SortableColumnItem({ id, label, visible, onToggleVisibility }: SortableColumnItemProps) {
36
- const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id })
37
-
38
- const style: React.CSSProperties = {
39
- transform: CSS.Transform.toString(transform),
40
- transition,
41
- opacity: isDragging ? 0.5 : 1,
42
- }
43
-
44
- return (
45
- <div
46
- ref={setNodeRef}
47
- style={style}
48
- className="flex items-center gap-2 rounded px-2 py-1.5 text-sm hover:bg-accent"
49
- >
50
- <button
51
- {...attributes}
52
- {...listeners}
53
- className="cursor-grab touch-none text-muted-foreground hover:text-foreground focus-visible:outline-none active:cursor-grabbing"
54
- aria-label={`${label} verschieben`}
55
- tabIndex={0}
56
- >
57
- <GripVertical className="size-4" />
58
- </button>
59
- <span className="flex-1 leading-snug">{label}</span>
60
- <Switch
61
- checked={visible}
62
- onCheckedChange={onToggleVisibility}
63
- size="sm"
64
- aria-label={visible ? `${label} ausblenden` : `${label} einblenden`}
65
- />
66
- </div>
67
- )
68
- }
69
-
70
- // --- Main component ---
71
-
72
- export interface ColumnConfigPopoverProps {
73
- columns: Record<string, ColumnDef>
74
- columnOrder: string[]
75
- columnVisibility: Record<string, boolean>
76
- onToggleVisibility: (columnKey: string) => void
77
- onReorder: (newOrder: string[]) => void
78
- className?: string
79
- }
80
-
81
- export function ColumnConfigPopover({
82
- columns,
83
- columnOrder,
84
- columnVisibility,
85
- onToggleVisibility,
86
- onReorder,
87
- className,
88
- }: ColumnConfigPopoverProps) {
89
- const sensors = useSensors(
90
- useSensor(PointerSensor),
91
- useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
92
- )
93
-
94
- // Exclude 'favorite' columns from config list
95
- const configurableKeys = columnOrder.filter(
96
- (key) => columns[key] && columns[key].type !== 'favorite',
97
- )
98
-
99
- function handleDragEnd(event: DragEndEvent) {
100
- const { active, over } = event
101
- if (!over || active.id === over.id) return
102
-
103
- const oldIndex = columnOrder.indexOf(active.id as string)
104
- const newIndex = columnOrder.indexOf(over.id as string)
105
- if (oldIndex === -1 || newIndex === -1) return
106
-
107
- onReorder(arrayMove(columnOrder, oldIndex, newIndex))
108
- }
109
-
110
- return (
111
- <Popover>
112
- <PopoverTrigger asChild>
113
- <Button
114
- data-slot="column-config-popover"
115
- variant="outline"
116
- size="sm"
117
- aria-haspopup="dialog"
118
- aria-label="Spalten konfigurieren"
119
- className={cn('h-8 gap-1.5 text-sm', className)}
120
- >
121
- <SlidersHorizontal className="size-3.5" />
122
- <span>Konfigurieren</span>
123
- </Button>
124
- </PopoverTrigger>
125
-
126
- <PopoverContent
127
- align="end"
128
- sideOffset={4}
129
- role="dialog"
130
- aria-label="Spalten konfigurieren"
131
- className="w-72 p-0"
132
- >
133
- <div className="border-b px-3 py-2">
134
- <span className="text-sm font-medium">Spalten konfigurieren</span>
135
- </div>
136
-
137
- <div className="max-h-[400px] overflow-y-auto p-2">
138
- <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
139
- <SortableContext items={configurableKeys} strategy={verticalListSortingStrategy}>
140
- {configurableKeys.map((key) => (
141
- <SortableColumnItem
142
- key={key}
143
- id={key}
144
- label={columns[key].label}
145
- visible={columnVisibility[key] ?? true}
146
- onToggleVisibility={() => onToggleVisibility(key)}
147
- />
148
- ))}
149
- </SortableContext>
150
- </DndContext>
151
- </div>
152
- </PopoverContent>
153
- </Popover>
154
- )
155
- }
156
-
157
- ColumnConfigPopover.displayName = 'ColumnConfigPopover'
@@ -1,56 +0,0 @@
1
- import { Search } from 'lucide-react'
2
- import { Button } from '@/components/ui/button'
3
- import {
4
- Empty,
5
- EmptyHeader,
6
- EmptyMedia,
7
- EmptyTitle,
8
- EmptyDescription,
9
- } from '@/components/ui/empty'
10
- import { cn } from '@/lib/utils'
11
-
12
- export interface DataExplorerEmptyProps {
13
- searchTerm: string
14
- hasActiveFilters: boolean
15
- onResetAll: () => void
16
- className?: string
17
- }
18
-
19
- function buildDescription(searchTerm: string, hasActiveFilters: boolean): string {
20
- const hasTerm = searchTerm.length > 0
21
- if (hasTerm && hasActiveFilters) {
22
- return `Für '${searchTerm}' mit den aktiven Filtern wurden keine Ergebnisse gefunden.`
23
- }
24
- if (hasTerm) {
25
- return `Für '${searchTerm}' wurden keine Ergebnisse gefunden.`
26
- }
27
- return 'Die aktiven Filter liefern keine Ergebnisse.'
28
- }
29
-
30
- export function DataExplorerEmpty({
31
- searchTerm,
32
- hasActiveFilters,
33
- onResetAll,
34
- className,
35
- }: DataExplorerEmptyProps) {
36
- const showReset = hasActiveFilters || searchTerm.length > 0
37
-
38
- return (
39
- <Empty data-slot="data-explorer-empty" className={cn(className)}>
40
- <EmptyHeader>
41
- <EmptyMedia variant="icon">
42
- <Search />
43
- </EmptyMedia>
44
- <EmptyTitle>Keine Ergebnisse gefunden</EmptyTitle>
45
- <EmptyDescription>{buildDescription(searchTerm, hasActiveFilters)}</EmptyDescription>
46
- </EmptyHeader>
47
- {showReset && (
48
- <Button variant="outline" size="sm" onClick={onResetAll}>
49
- Filter zurücksetzen
50
- </Button>
51
- )}
52
- </Empty>
53
- )
54
- }
55
-
56
- DataExplorerEmpty.displayName = 'DataExplorerEmpty'
@@ -1,101 +0,0 @@
1
- import * as React from 'react'
2
- import { cn } from '@/lib/utils'
3
- import { useDataExplorerState } from '../hooks/use-data-explorer-state'
4
- import { DomainSwitcher } from '../DomainSwitcher/DomainSwitcher'
5
- import { DataExplorerToolbar } from '../DataExplorerToolbar/DataExplorerToolbar'
6
- import { TableView } from '../TableView/TableView'
7
- import { ListView } from '../ListView/ListView'
8
- import { CardView } from '../CardView/CardView'
9
- import { DataExplorerEmpty } from '../DataExplorerEmpty/DataExplorerEmpty'
10
- import { DataExplorerPagination } from '../DataExplorerPagination/DataExplorerPagination'
11
- import type { DomainKey } from '../types'
12
-
13
- export interface DataExplorerPageProps extends React.ComponentProps<'main'> {
14
- initialDomain?: DomainKey
15
- }
16
-
17
- export function DataExplorerPage({
18
- initialDomain,
19
- className,
20
- ...props
21
- }: DataExplorerPageProps) {
22
- const { state, derived, actions } = useDataExplorerState(initialDomain)
23
- const { domainConfig, paginatedData, visibleColumns, totalFiltered, totalPages } = derived
24
-
25
- const isEmpty = paginatedData.length === 0
26
- const showEmpty = isEmpty && (derived.hasActiveFilters || state.searchTerm.length > 0)
27
-
28
- return (
29
- <main
30
- data-slot="data-explorer-page"
31
- className={cn('flex flex-col gap-4', className)}
32
- {...props}
33
- >
34
- <DomainSwitcher
35
- activeDomain={state.activeDomain}
36
- onDomainChange={actions.switchDomain}
37
- />
38
-
39
- <DataExplorerToolbar
40
- state={state}
41
- derived={derived}
42
- actions={actions}
43
- />
44
-
45
- {showEmpty ? (
46
- <DataExplorerEmpty
47
- searchTerm={state.searchTerm}
48
- hasActiveFilters={derived.hasActiveFilters}
49
- onResetAll={actions.resetAllFilters}
50
- />
51
- ) : state.viewMode === 'table' ? (
52
- <TableView
53
- data={paginatedData}
54
- columns={domainConfig.columns}
55
- visibleColumns={visibleColumns}
56
- sortColumn={state.sortColumn}
57
- sortDirection={state.sortDirection}
58
- expandedRows={state.expandedRows}
59
- favorites={state.favorites}
60
- onToggleSort={actions.toggleSort}
61
- onToggleExpand={actions.toggleExpand}
62
- onToggleFavorite={actions.toggleFavorite}
63
- />
64
- ) : state.viewMode === 'list' ? (
65
- <ListView
66
- data={paginatedData}
67
- columns={domainConfig.columns}
68
- layout={domainConfig.layout.list}
69
- expandedRows={state.expandedRows}
70
- favorites={state.favorites}
71
- onToggleExpand={actions.toggleExpand}
72
- onToggleFavorite={actions.toggleFavorite}
73
- />
74
- ) : (
75
- <CardView
76
- data={paginatedData}
77
- columns={domainConfig.columns}
78
- layout={domainConfig.layout.card}
79
- expandedRows={state.expandedRows}
80
- favorites={state.favorites}
81
- onToggleExpand={actions.toggleExpand}
82
- onToggleFavorite={actions.toggleFavorite}
83
- />
84
- )}
85
-
86
- {totalFiltered > 0 && (
87
- <DataExplorerPagination
88
- currentPage={state.currentPage}
89
- totalPages={totalPages}
90
- pageSize={state.pageSize}
91
- totalFiltered={totalFiltered}
92
- totalData={domainConfig.data.length}
93
- onPageChange={actions.setCurrentPage}
94
- onPageSizeChange={actions.setPageSize}
95
- />
96
- )}
97
- </main>
98
- )
99
- }
100
-
101
- DataExplorerPage.displayName = 'DataExplorerPage'