@fastnd/components 1.0.32 → 1.0.34

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.
@@ -25,7 +25,7 @@ const DashboardPage = React.forwardRef<HTMLElement, DashboardPageProps>(
25
25
  ref={ref}
26
26
  data-slot="dashboard-page"
27
27
  className={cn(
28
- 'grid grid-cols-1 lg:grid-cols-[350px_1fr] gap-6 max-w-[1400px] mx-auto p-8',
28
+ 'grid grid-cols-1 lg:grid-cols-[280px_1fr] gap-6 max-w-[1400px] mx-auto p-8',
29
29
  className,
30
30
  )}
31
31
  {...props}
@@ -1,22 +1,25 @@
1
1
  import * as React from 'react'
2
- import { Star } from 'lucide-react'
3
2
  import { Card, CardContent } from '@/components/ui/card'
4
3
  import { Input } from '@/components/ui/input'
5
- import { Table, TableHeader, TableHead, TableBody, TableRow, TableCell } from '@/components/ui/table'
6
4
  import { Badge } from '@/components/ui/badge'
7
- import { FavoriteButton } from '@/components/FavoriteButton/FavoriteButton'
5
+ import { DataTableView } from '@/features/data-visualization/DataTableView/DataTableView'
6
+ import { formatRelativeDate } from '@/utils/date'
8
7
  import { cn } from '@/lib/utils'
8
+ import type { ColumnDef, SortState } from '@/features/data-visualization/types'
9
9
  import type { Project, ProjectStatus } from '../types'
10
10
  import { STATUS_CONFIG } from '../constants'
11
11
 
12
12
  const STATUS_BADGE_CLASSES: Record<ProjectStatus, string> = {
13
- 'neu': 'bg-primary/10 text-primary border-transparent',
14
- 'offen': 'bg-muted-foreground/10 text-muted-foreground border-transparent',
15
- 'in-prufung': 'bg-[#ebbe0d]/15 text-[#8a7100] border-transparent',
16
- 'validierung': 'bg-[#e8a026]/15 text-[#8a5a00] border-transparent',
17
- 'abgeschlossen': 'bg-[#1ec489]/15 text-[#0a6e44] border-transparent',
13
+ 'neu': 'border-primary',
14
+ 'offen': 'border-muted-foreground',
15
+ 'in-prufung': 'border-[#ebbe0d]',
16
+ 'validierung': 'border-[#e8a026]',
17
+ 'abgeschlossen': 'border-[#1ec489]',
18
18
  }
19
19
 
20
+ const STATIC_SORT: SortState = { column: null, direction: 'asc' }
21
+ const EMPTY_ROWS = new Set<string>()
22
+
20
23
  interface ProjectListProps extends React.ComponentProps<'section'> {
21
24
  projects: Project[]
22
25
  filterLabel: string
@@ -38,6 +41,75 @@ const ProjectList = React.forwardRef<HTMLElement, ProjectListProps>(
38
41
  },
39
42
  ref,
40
43
  ) => {
44
+ const columns: Record<string, ColumnDef> = React.useMemo(() => ({
45
+ isFavorite: {
46
+ label: 'Favorit',
47
+ type: 'favorite',
48
+ sortable: false,
49
+ filterable: false,
50
+ visible: true,
51
+ headerIcon: 'star',
52
+ headerTooltip: 'Favorit',
53
+ },
54
+ name: {
55
+ label: 'Projekt / Applikation',
56
+ type: 'double-text',
57
+ secondary: 'application',
58
+ sortable: false,
59
+ filterable: false,
60
+ visible: true,
61
+ },
62
+ client: {
63
+ label: 'Kunde',
64
+ type: 'text',
65
+ sortable: false,
66
+ filterable: false,
67
+ visible: true,
68
+ },
69
+ status: {
70
+ label: 'Status',
71
+ type: 'text',
72
+ sortable: false,
73
+ filterable: false,
74
+ visible: true,
75
+ render: (val) => {
76
+ const s = val as ProjectStatus
77
+ return (
78
+ <Badge variant="outline" className={STATUS_BADGE_CLASSES[s]}>
79
+ {STATUS_CONFIG[s].label}
80
+ </Badge>
81
+ )
82
+ },
83
+ },
84
+ updatedAt: {
85
+ label: 'Aktivität',
86
+ type: 'text',
87
+ sortable: false,
88
+ filterable: false,
89
+ visible: true,
90
+ render: (val, row) => (
91
+ <div className="flex flex-col">
92
+ <span className="font-medium text-[13px]">{formatRelativeDate(String(val))}</span>
93
+ <span className="text-muted-foreground text-xs">
94
+ Erstellt: {new Date(String(row.createdAt)).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })}
95
+ </span>
96
+ </div>
97
+ ),
98
+ },
99
+ }), [])
100
+
101
+ const visibleColumns = ['isFavorite', 'name', 'client', 'status', 'updatedAt']
102
+
103
+ const rowData = React.useMemo(
104
+ () => projects.map(p => ({ ...p } as Record<string, unknown>)),
105
+ [projects],
106
+ )
107
+
108
+ const favorites = React.useMemo(
109
+ () => new Set(projects.filter(p => p.isFavorite).map(p => p.id)),
110
+ [projects],
111
+ )
112
+
41
113
  return (
42
114
  <section
43
115
  ref={ref}
@@ -63,50 +135,18 @@ const ProjectList = React.forwardRef<HTMLElement, ProjectListProps>(
63
135
  onChange={e => onSearchChange(e.target.value)}
64
136
  />
65
137
  </div>
66
- <CardContent>
67
- <Table>
68
- <TableHeader>
69
- <TableRow>
70
- <TableHead scope="col">Projektname</TableHead>
71
- <TableHead scope="col">Kunde</TableHead>
72
- <TableHead scope="col">Applikation</TableHead>
73
- <TableHead scope="col">Status</TableHead>
74
- <TableHead scope="col" aria-label="Favorit">
75
- <Star className="size-3.5" />
76
- </TableHead>
77
- </TableRow>
78
- </TableHeader>
79
- <TableBody>
80
- {projects.map(project => (
81
- <TableRow key={project.id}>
82
- <TableCell>
83
- <span className="font-medium">{project.name}</span>
84
- </TableCell>
85
- <TableCell>
86
- <span className="text-muted-foreground text-[0.8125rem]">
87
- {project.client}
88
- </span>
89
- </TableCell>
90
- <TableCell>{project.application}</TableCell>
91
- <TableCell>
92
- <Badge
93
- variant="secondary"
94
- className={cn(STATUS_BADGE_CLASSES[project.status])}
95
- >
96
- {STATUS_CONFIG[project.status].label}
97
- </Badge>
98
- </TableCell>
99
- <TableCell>
100
- <FavoriteButton
101
- pressed={project.isFavorite}
102
- projectName={project.name}
103
- onPressedChange={() => onToggleFavorite(project.id)}
104
- />
105
- </TableCell>
106
- </TableRow>
107
- ))}
108
- </TableBody>
109
- </Table>
138
+ <CardContent className="p-0">
139
+ <DataTableView
140
+ data={rowData}
141
+ columns={columns}
142
+ visibleColumns={visibleColumns}
143
+ sort={STATIC_SORT}
144
+ onToggleSort={() => {}}
145
+ expandedRows={EMPTY_ROWS}
146
+ onToggleExpansion={() => {}}
147
+ favorites={favorites}
148
+ onToggleFavorite={onToggleFavorite}
149
+ />
110
150
  </CardContent>
111
151
  </Card>
112
152
  </section>
@@ -58,7 +58,7 @@ const StatusDonutChart = React.forwardRef<HTMLDivElement, StatusDonutChartProps>
58
58
  {...props}
59
59
  >
60
60
  {/* Relative wrapper so the center label overlay can be positioned absolutely */}
61
- <div className="relative mx-auto aspect-square max-h-[200px]">
61
+ <div className="relative mx-auto aspect-square max-h-[160px]">
62
62
  <ChartContainer config={chartConfig} className="size-full">
63
63
  <PieChart>
64
64
  <ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
@@ -66,8 +66,8 @@ const StatusDonutChart = React.forwardRef<HTMLDivElement, StatusDonutChartProps>
66
66
  data={chartData}
67
67
  dataKey="count"
68
68
  nameKey="status"
69
- innerRadius={60}
70
- outerRadius={85}
69
+ innerRadius={48}
70
+ outerRadius={68}
71
71
  strokeWidth={5}
72
72
  className="cursor-pointer"
73
73
  onClick={handlePieClick}
@@ -88,7 +88,7 @@ const StatusDonutChart = React.forwardRef<HTMLDivElement, StatusDonutChartProps>
88
88
  {/* Center label — rendered as HTML so it is always in the DOM (recharts SVG labels are invisible in jsdom) */}
89
89
  <div className="pointer-events-none absolute inset-0 flex flex-col items-center justify-center gap-0.5">
90
90
  <span
91
- className="text-3xl font-bold text-foreground"
91
+ className="text-2xl font-bold text-foreground"
92
92
  style={{ fontFamily: 'var(--font-clash)' }}
93
93
  >
94
94
  {total.toLocaleString()}
@@ -43,7 +43,7 @@ const StatusFilterLegend = React.forwardRef<HTMLDivElement, StatusFilterLegendPr
43
43
  data-filter={filter}
44
44
  onClick={() => onFilterChange(filter)}
45
45
  className={cn(
46
- 'flex items-center justify-between w-full px-3 py-2 rounded-md border border-transparent transition-colors cursor-pointer text-sm outline-none focus-visible:bg-muted',
46
+ 'flex items-center justify-between w-full px-3 py-1.5 rounded-md border border-transparent transition-colors cursor-pointer text-sm outline-none focus-visible:bg-muted',
47
47
  isActive ? 'bg-muted border-border' : 'hover:bg-muted',
48
48
  )}
49
49
  >
@@ -22,7 +22,7 @@ const StatusOverview = React.forwardRef<HTMLElement, StatusOverviewProps>(
22
22
  className={cn(className)}
23
23
  {...props}
24
24
  >
25
- <Card>
25
+ <Card className="h-full py-4 gap-3">
26
26
  <CardHeader>
27
27
  <CardTitle
28
28
  className="text-2xl font-medium"
@@ -8,11 +8,25 @@ export const STATUS_CONFIG: Record<ProjectStatus, { label: string; color: string
8
8
  'abgeschlossen': { label: 'Abgeschlossen', color: '#1ec489' },
9
9
  }
10
10
 
11
+ const d = (daysAgo: number) => new Date(Date.now() - daysAgo * 86_400_000).toISOString()
12
+
11
13
  export const MOCK_PROJECTS: Project[] = [
12
- { id: '1', name: 'E-Commerce Relaunch', client: 'Müller GmbH', application: 'Shopify CMS', status: 'neu', isFavorite: false },
13
- { id: '2', name: 'CRM Integration MVP', client: 'TechCorp Inc.', application: 'Salesforce API', status: 'offen', isFavorite: true },
14
- { id: '3', name: 'Data Analytics Dashboard', client: 'DataViz AG', application: 'React / Supabase', status: 'in-prufung', isFavorite: false },
15
- { id: '4', name: 'Mobile App Onboarding', client: 'FitHealth', application: 'Flutter', status: 'neu', isFavorite: false },
16
- { id: '5', name: 'Payment Gateway Update', client: 'FinTech Solutions', application: 'Stripe API', status: 'validierung', isFavorite: false },
17
- { id: '6', name: 'Legacy System Migration', client: 'OldBank Corp.', application: 'Node.js / PostgreSQL', status: 'abgeschlossen', isFavorite: true },
14
+ { id: '1', name: 'E-Commerce Relaunch', client: 'Müller GmbH', application: 'Shopify CMS', status: 'neu', isFavorite: false, createdAt: '2025-03-15', updatedAt: d(0) },
15
+ { id: '2', name: 'CRM Integration MVP', client: 'TechCorp Inc.', application: 'Salesforce API', status: 'offen', isFavorite: true, createdAt: '2025-02-10', updatedAt: d(1) },
16
+ { id: '3', name: 'Data Analytics Dashboard', client: 'DataViz AG', application: 'React / Supabase', status: 'in-prufung', isFavorite: false, createdAt: '2025-01-20', updatedAt: d(3) },
17
+ { id: '4', name: 'Mobile App Onboarding', client: 'FitHealth', application: 'Flutter', status: 'neu', isFavorite: false, createdAt: '2025-03-01', updatedAt: d(7) },
18
+ { id: '5', name: 'Payment Gateway Update', client: 'FinTech Solutions', application: 'Stripe API', status: 'validierung', isFavorite: false, createdAt: '2024-11-05', updatedAt: d(14) },
19
+ { id: '6', name: 'Legacy System Migration', client: 'OldBank Corp.', application: 'Node.js / PostgreSQL', status: 'abgeschlossen', isFavorite: true, createdAt: '2024-08-20', updatedAt: d(60) },
20
+ ]
21
+
22
+ export const MOCK_PROJECTS_EXTENDED: Project[] = [
23
+ ...MOCK_PROJECTS,
24
+ { id: '7', name: 'Supply Chain Visibility & Tracking Platform', client: 'Bayerische LogiTrans Handelsgesellschaft mbH', application: 'Next.js 14 / tRPC / PostgreSQL', status: 'in-prufung', isFavorite: true, createdAt: '2024-12-03', updatedAt: d(2) },
25
+ { id: '8', name: 'HR Self-Service & Workforce Management Portal', client: 'Personalwerk Süddeutschland AG', application: 'Angular 17 / .NET Core 8 / Azure AD', status: 'offen', isFavorite: false, createdAt: '2025-01-08', updatedAt: d(5) },
26
+ { id: '9', name: 'Echtzeit IoT Monitoring & Alerting Dashboard', client: 'SmartFactory Automatisierungstechnik SE', application: 'Vue.js 3 / InfluxDB / Grafana', status: 'validierung', isFavorite: false, createdAt: '2024-10-14', updatedAt: d(9) },
27
+ { id: '10', name: 'Digitales Dokumenten- & Archivierungssystem', client: 'Kanzlei Bauer, Hoffmann & Partner GbR', application: 'SharePoint Online / Power Automate', status: 'abgeschlossen', isFavorite: false, createdAt: '2024-06-01', updatedAt: d(30) },
28
+ { id: '11', name: 'Multi-Channel Customer Loyalty & Rewards App', client: 'Retail Chain Deutschland GmbH & Co. KG', application: 'React Native / Expo / Firebase', status: 'neu', isFavorite: false, createdAt: '2025-03-20', updatedAt: d(0) },
29
+ { id: '12', name: 'KI-gestützte Predictive Maintenance Engine', client: 'IndustrieWerk Maschinenbau KG', application: 'Python 3.12 / FastAPI / PyTorch / MLflow',status: 'in-prufung', isFavorite: true, createdAt: '2024-09-17', updatedAt: d(4) },
30
+ { id: '13', name: 'Automatisierte Compliance & Audit Reporting Suite', client: 'RegulaCorp Financial Services AG', application: 'Power BI Embedded / Azure Synapse', status: 'offen', isFavorite: false, createdAt: '2025-02-25', updatedAt: d(11) },
31
+ { id: '14', name: 'Mobile Field Service & Wartungsmanagement', client: 'TechService Außendienst GmbH', application: 'Salesforce Field Service / MuleSoft', status: 'validierung', isFavorite: true, createdAt: '2024-07-30', updatedAt: d(6) },
18
32
  ]
@@ -9,6 +9,8 @@ export interface Project {
9
9
  application: string
10
10
  status: ProjectStatus
11
11
  isFavorite: boolean
12
+ createdAt: string
13
+ updatedAt: string
12
14
  }
13
15
 
14
16
  export interface StatusCount {
@@ -1,25 +1,19 @@
1
1
  import React from 'react'
2
- import { ChevronDown, ArrowLeftRight, Sparkles } from 'lucide-react'
2
+ import { ChevronDown, ArrowLeftRight, Sparkles, FolderPlus, BookmarkPlus, Trash2 } from 'lucide-react'
3
3
  import { Badge } from '@/components/ui/badge'
4
4
  import { Button } from '@/components/ui/button'
5
5
  import { ScoreBar } from '@/components/ScoreBar/ScoreBar'
6
6
  import { FavoriteButton } from '@/components/FavoriteButton/FavoriteButton'
7
7
  import { cn } from '@/lib/utils'
8
- import type { ColumnDef } from '../types'
9
-
10
- export interface RenderCellOptions {
11
- mode?: 'default' | 'compact' | 'inventory-label'
12
- isExpanded?: boolean
13
- isFavorite?: boolean
14
- onToggleExpand?: () => void
15
- onToggleFavorite?: () => void
16
- }
8
+ import type { ColumnDef, RenderCellOptions } from '../types'
9
+
10
+ export type { RenderCellOptions }
17
11
 
18
12
  const STATUS_COLORS: Record<string, string> = {
19
- active: 'bg-[var(--lifecycle-active-bg)] text-[var(--lifecycle-active)] border-transparent',
20
- nrnd: 'bg-[var(--lifecycle-nrnd-bg)] text-[var(--lifecycle-nrnd)] border-transparent',
21
- eol: 'bg-[var(--lifecycle-eol-bg)] text-[var(--lifecycle-eol)] border-transparent',
22
- production: 'bg-[var(--lifecycle-production-bg)] text-[var(--lifecycle-production)] border-transparent',
13
+ active: 'border-[var(--lifecycle-active)]',
14
+ nrnd: 'border-[var(--lifecycle-nrnd)]',
15
+ eol: 'border-[var(--lifecycle-eol)]',
16
+ production: 'border-[var(--lifecycle-production)]',
23
17
  }
24
18
 
25
19
  const INVENTORY_COLORS: Record<string, string> = {
@@ -39,9 +33,11 @@ export function renderCell(
39
33
  row: Record<string, unknown>,
40
34
  options: RenderCellOptions = {},
41
35
  ): React.ReactNode {
42
- const { mode = 'default', isExpanded = false, isFavorite = false, onToggleExpand, onToggleFavorite } = options
36
+ const { mode = 'default', isExpanded = false, isFavorite = false, onToggleExpand, onToggleFavorite, onAddToProject, onAddToCollection, onDelete } = options
43
37
  const val = row[colKey]
44
38
 
39
+ if (col.render) return col.render(val, row, options)
40
+
45
41
  switch (col.type) {
46
42
  case 'text': {
47
43
  // col.rowLines drives the clamp; default 2 so columns can shrink without growing rows
@@ -83,7 +79,7 @@ export function renderCell(
83
79
  if (val == null || !col.statusMap) return <span className="text-[13px]">{val != null ? String(val) : ''}</span>
84
80
  const status = col.statusMap[String(val)] ?? 'active'
85
81
  return (
86
- <Badge className={STATUS_COLORS[status]}>
82
+ <Badge variant="outline" className={STATUS_COLORS[status]}>
87
83
  {String(val)}
88
84
  </Badge>
89
85
  )
@@ -168,6 +164,40 @@ export function renderCell(
168
164
  )
169
165
  }
170
166
 
167
+ case 'actions': {
168
+ return (
169
+ <div className="flex items-center justify-end gap-0.5">
170
+ <Button
171
+ variant="ghost"
172
+ size="icon"
173
+ className="size-7 text-muted-foreground hover:text-foreground"
174
+ aria-label="Zu Projekt hinzufügen"
175
+ onClick={(e) => { e.stopPropagation(); onAddToProject?.() }}
176
+ >
177
+ <FolderPlus className="size-3.5" aria-hidden="true" />
178
+ </Button>
179
+ <Button
180
+ variant="ghost"
181
+ size="icon"
182
+ className="size-7 text-muted-foreground hover:text-foreground"
183
+ aria-label="Zu Sammlung hinzufügen"
184
+ onClick={(e) => { e.stopPropagation(); onAddToCollection?.() }}
185
+ >
186
+ <BookmarkPlus className="size-3.5" aria-hidden="true" />
187
+ </Button>
188
+ <Button
189
+ variant="ghost"
190
+ size="icon"
191
+ className="size-7 text-muted-foreground hover:text-destructive"
192
+ aria-label="Produkt löschen"
193
+ onClick={(e) => { e.stopPropagation(); onDelete?.() }}
194
+ >
195
+ <Trash2 className="size-3.5" aria-hidden="true" />
196
+ </Button>
197
+ </div>
198
+ )
199
+ }
200
+
171
201
  default: {
172
202
  return <span className="text-[13px]">{val != null ? String(val) : ''}</span>
173
203
  }
@@ -106,7 +106,7 @@ export function ColumnConfigPopover({
106
106
  >
107
107
  <SortableContext items={columnOrder} strategy={verticalListSortingStrategy}>
108
108
  <div className="p-1">
109
- {columnOrder.map((key) => (
109
+ {columnOrder.filter((key) => columns[key]?.configurable !== false).map((key) => (
110
110
  <SortableColumnItem
111
111
  key={key}
112
112
  colKey={key}
@@ -86,6 +86,7 @@ function DataCardView({
86
86
  }: DataCardViewProps) {
87
87
  const gridRef = useRef<HTMLDivElement>(null)
88
88
  const expandFields = getExpandFields(layout)
89
+ const actionsColKey = Object.keys(columns).find((k) => columns[k]?.type === 'actions')
89
90
 
90
91
  useLayoutEffect(() => {
91
92
  if (gridRef.current) {
@@ -97,7 +98,7 @@ function DataCardView({
97
98
  <div
98
99
  ref={gridRef}
99
100
  data-slot="data-card-view"
100
- className={cn('grid grid-cols-[repeat(auto-fill,minmax(280px,1fr))] gap-3', className)}
101
+ className={cn('grid grid-cols-[repeat(auto-fill,minmax(280px,1fr))] gap-4', className)}
101
102
  >
102
103
  {data.map((row) => {
103
104
  const rowId = String(row.id ?? '')
@@ -111,7 +112,7 @@ function DataCardView({
111
112
  <div className="flex flex-col">
112
113
  <div
113
114
  className={cn(
114
- 'flex flex-col border border-border rounded-lg overflow-hidden hover:shadow-md transition-shadow bg-card text-card-foreground',
115
+ 'flex flex-col flex-1 border border-border rounded-lg overflow-hidden hover:shadow-md transition-shadow bg-card text-card-foreground',
115
116
  isAnyExpanded && 'border-b-2 border-b-primary',
116
117
  )}
117
118
  >
@@ -136,7 +137,7 @@ function DataCardView({
136
137
 
137
138
  {/* Badges */}
138
139
  {layout.badgeFields.length > 0 && (
139
- <div className="px-4 pt-2 flex gap-2 flex-wrap">
140
+ <div className="px-4 pt-2 flex gap-1.5 flex-wrap">
140
141
  {layout.badgeFields.map((bf) => {
141
142
  const col = columns[bf]
142
143
  if (!col || row[bf] == null) return null
@@ -163,31 +164,38 @@ function DataCardView({
163
164
  )
164
165
  : String(row[r.field] ?? '')
165
166
  return (
166
- <div key={r.field} className="flex justify-between items-center">
167
- <span className="text-xs text-muted-foreground">{r.label}</span>
168
- <span className="text-[13px] font-medium">{valueNode}</span>
167
+ <div key={r.field} className="flex justify-between items-start gap-4">
168
+ <span className="text-xs text-muted-foreground shrink-0">{r.label}</span>
169
+ <span className="text-[13px] font-medium min-w-0 text-right">{valueNode}</span>
169
170
  </div>
170
171
  )
171
172
  })}
172
173
  </div>
173
174
  )}
174
175
 
175
- {/* Footer — expand buttons */}
176
- {expandFields.length > 0 && (
177
- <div className="p-4 pt-3">
178
- <div className="flex gap-2">
179
- {expandFields.map((ef) => {
180
- const col = columns[ef]
181
- if (!col) return null
182
- const expansionKey = `${rowId}::${ef}`
183
- return (
184
- <React.Fragment key={ef}>
185
- {renderCell(ef, col, row, {
186
- isExpanded: expandedRows.has(expansionKey),
187
- onToggleExpand: () => onToggleExpansion(rowId, ef),
188
- })}
189
- </React.Fragment>
190
- )
176
+ {/* Footer — expand buttons + actions */}
177
+ {(expandFields.length > 0 || actionsColKey) && (
178
+ <div className="mt-auto p-4 pt-3 border-t border-border">
179
+ <div className="flex items-center justify-between gap-2">
180
+ <div className="flex gap-2">
181
+ {expandFields.map((ef) => {
182
+ const col = columns[ef]
183
+ if (!col) return null
184
+ const expansionKey = `${rowId}::${ef}`
185
+ return (
186
+ <React.Fragment key={ef}>
187
+ {renderCell(ef, col, row, {
188
+ isExpanded: expandedRows.has(expansionKey),
189
+ onToggleExpand: () => onToggleExpansion(rowId, ef),
190
+ })}
191
+ </React.Fragment>
192
+ )
193
+ })}
194
+ </div>
195
+ {actionsColKey && renderCell(actionsColKey, columns[actionsColKey], row, {
196
+ onAddToProject: () => {},
197
+ onAddToCollection: () => {},
198
+ onDelete: () => {},
191
199
  })}
192
200
  </div>
193
201
  </div>