goobs-frontend 0.8.8 → 0.8.9

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.
@@ -1,57 +1,88 @@
1
1
  'use client'
2
- import React, { useState, useRef, useEffect } from 'react'
3
- import { Popover, Stack, Paper, Box, IconButton } from '@mui/material'
2
+ import React from 'react'
3
+ import { Paper, Stack, Box } from '@mui/material'
4
4
  import Typography from '../../Typography'
5
5
  import DuplicateIcon from '@mui/icons-material/FileCopy'
6
6
  import DeleteIcon from '@mui/icons-material/Delete'
7
7
  import ExportIcon from '@mui/icons-material/Download'
8
+ import EditIcon from '@mui/icons-material/Edit'
9
+ import VisibilityIcon from '@mui/icons-material/Visibility'
8
10
 
9
- type ModalType = 'duplicate' | 'delete' | 'export'
11
+ type ModalType = 'duplicate' | 'delete' | 'export' | 'manage' | 'show'
10
12
 
11
13
  interface ManageRowProps {
12
- open?: boolean
13
14
  handleClose?: () => void
14
15
  selectedRows?: string[]
15
16
  rows?: Array<{ [key: string]: unknown }>
16
17
  onDuplicate?: () => void
17
18
  onDelete?: () => void
19
+ onManage?: () => void
20
+ onShow?: () => void
21
+ onExport?: () => void
18
22
  }
19
23
 
20
24
  function ManageRow({
21
- open = false,
22
25
  handleClose = () => {},
23
26
  selectedRows = [],
24
27
  rows = [],
25
28
  onDuplicate,
26
29
  onDelete,
30
+ onManage,
31
+ onShow,
32
+ onExport,
27
33
  }: ManageRowProps) {
28
- const [actionType, setActionType] = useState<ModalType | null>(null)
29
- const [position, setPosition] = useState({ x: 0, y: 0 })
30
- const popoverRef = useRef<HTMLDivElement>(null)
31
-
32
- const iconMap: { [key in ModalType]: React.ReactElement } = {
33
- duplicate: <DuplicateIcon />,
34
- delete: <DeleteIcon />,
35
- export: <ExportIcon />,
36
- }
34
+ console.log('ManageRow rendered with props:', {
35
+ selectedRowsCount: selectedRows.length,
36
+ selectedRows,
37
+ hasManageHandler: !!onManage,
38
+ })
37
39
 
38
40
  const handleActionSelection = (type: ModalType) => {
39
- setActionType(type)
41
+ console.log('Action selected:', type, {
42
+ selectedRows,
43
+ hasManageHandler: !!onManage,
44
+ timestamp: new Date().toISOString(),
45
+ })
46
+
40
47
  switch (type) {
41
48
  case 'duplicate':
42
49
  onDuplicate?.()
50
+ handleClose()
43
51
  break
44
52
  case 'delete':
45
53
  onDelete?.()
54
+ handleClose()
46
55
  break
47
56
  case 'export':
48
- handleExport()
57
+ if (onExport) {
58
+ onExport()
59
+ } else {
60
+ handleExport()
61
+ }
62
+ handleClose()
63
+ break
64
+ case 'manage':
65
+ if (selectedRows.length === 1 && onManage) {
66
+ console.log('Triggering manage with selection:', selectedRows[0])
67
+ onManage()
68
+ // Don't call handleClose for manage
69
+ return
70
+ } else {
71
+ console.warn('Invalid manage state:', {
72
+ selectedRows,
73
+ hasManageHandler: !!onManage,
74
+ })
75
+ }
76
+ break
77
+ case 'show':
78
+ onShow?.()
79
+ handleClose()
49
80
  break
50
81
  }
51
- handleClose()
52
82
  }
53
83
 
54
84
  const handleExport = () => {
85
+ console.log('Exporting data for rows:', selectedRows)
55
86
  const selectedData = rows.filter(row =>
56
87
  selectedRows.includes(row.id as string)
57
88
  )
@@ -73,109 +104,244 @@ function ManageRow({
73
104
  }
74
105
 
75
106
  const actionButtons = (
76
- <Stack component="div" spacing={0} direction="row" justifyContent="center">
77
- {(['duplicate', 'delete', 'export'] as ModalType[]).map(type => (
107
+ <Stack
108
+ component="div"
109
+ spacing={0}
110
+ direction="row"
111
+ justifyContent="center"
112
+ sx={{ '& > div:not(:last-child)': { marginRight: '2px' } }}
113
+ >
114
+ {/* First group: Manage, Show */}
115
+ {(onManage || onShow) && selectedRows.length === 1 && (
78
116
  <Box
79
- key={type}
80
117
  display="flex"
81
- flexDirection="column"
118
+ flexDirection="row"
82
119
  alignItems="center"
120
+ sx={{
121
+ borderRight: '1px solid #e0e0e0',
122
+ paddingRight: '8px',
123
+ marginRight: '8px',
124
+ }}
83
125
  >
84
- <IconButton
85
- size="small"
86
- onClick={() => handleActionSelection(type)}
87
- sx={{ color: 'black' }}
88
- >
89
- {iconMap[type]}
90
- </IconButton>
91
- <Typography
92
- fontvariant="merriparagraph"
93
- text={type.charAt(0).toUpperCase() + type.slice(1)}
94
- />
126
+ {onManage && (
127
+ <Box
128
+ onClick={e => {
129
+ e.stopPropagation()
130
+ handleActionSelection('manage')
131
+ }}
132
+ display="flex"
133
+ flexDirection="column"
134
+ alignItems="center"
135
+ sx={{
136
+ padding: '8px',
137
+ cursor: 'pointer',
138
+ '&:hover': {
139
+ backgroundColor: 'rgba(0, 0, 0, 0.04)',
140
+ },
141
+ borderRadius: '4px',
142
+ transition: 'background-color 0.2s',
143
+ userSelect: 'none',
144
+ }}
145
+ >
146
+ <Box
147
+ sx={{
148
+ display: 'flex',
149
+ flexDirection: 'column',
150
+ alignItems: 'center',
151
+ color: 'black',
152
+ }}
153
+ >
154
+ <EditIcon />
155
+ <Typography fontvariant="merriparagraph" text="Manage" />
156
+ </Box>
157
+ </Box>
158
+ )}
159
+ {onShow && (
160
+ <Box
161
+ onClick={e => {
162
+ e.stopPropagation()
163
+ handleActionSelection('show')
164
+ }}
165
+ display="flex"
166
+ flexDirection="column"
167
+ alignItems="center"
168
+ sx={{
169
+ padding: '8px',
170
+ cursor: 'pointer',
171
+ '&:hover': {
172
+ backgroundColor: 'rgba(0, 0, 0, 0.04)',
173
+ },
174
+ borderRadius: '4px',
175
+ transition: 'background-color 0.2s',
176
+ userSelect: 'none',
177
+ }}
178
+ >
179
+ <Box
180
+ sx={{
181
+ display: 'flex',
182
+ flexDirection: 'column',
183
+ alignItems: 'center',
184
+ color: 'black',
185
+ }}
186
+ >
187
+ <VisibilityIcon />
188
+ <Typography fontvariant="merriparagraph" text="Show" />
189
+ </Box>
190
+ </Box>
191
+ )}
192
+ </Box>
193
+ )}
194
+
195
+ {/* Second group: Duplicate, Delete, Export */}
196
+ {selectedRows.length > 0 && (
197
+ <Box display="flex" flexDirection="row" alignItems="center">
198
+ {onDuplicate && (
199
+ <Box
200
+ onClick={e => {
201
+ e.stopPropagation()
202
+ handleActionSelection('duplicate')
203
+ }}
204
+ display="flex"
205
+ flexDirection="column"
206
+ alignItems="center"
207
+ sx={{
208
+ padding: '8px',
209
+ cursor: 'pointer',
210
+ '&:hover': {
211
+ backgroundColor: 'rgba(0, 0, 0, 0.04)',
212
+ },
213
+ borderRadius: '4px',
214
+ transition: 'background-color 0.2s',
215
+ userSelect: 'none',
216
+ }}
217
+ >
218
+ <Box
219
+ sx={{
220
+ display: 'flex',
221
+ flexDirection: 'column',
222
+ alignItems: 'center',
223
+ color: 'black',
224
+ }}
225
+ >
226
+ <DuplicateIcon />
227
+ <Typography fontvariant="merriparagraph" text="Duplicate" />
228
+ </Box>
229
+ </Box>
230
+ )}
231
+ {onDelete && (
232
+ <Box
233
+ onClick={e => {
234
+ e.stopPropagation()
235
+ handleActionSelection('delete')
236
+ }}
237
+ display="flex"
238
+ flexDirection="column"
239
+ alignItems="center"
240
+ sx={{
241
+ padding: '8px',
242
+ cursor: 'pointer',
243
+ '&:hover': {
244
+ backgroundColor: 'rgba(0, 0, 0, 0.04)',
245
+ },
246
+ borderRadius: '4px',
247
+ transition: 'background-color 0.2s',
248
+ userSelect: 'none',
249
+ }}
250
+ >
251
+ <Box
252
+ sx={{
253
+ display: 'flex',
254
+ flexDirection: 'column',
255
+ alignItems: 'center',
256
+ color: 'black',
257
+ }}
258
+ >
259
+ <DeleteIcon />
260
+ <Typography fontvariant="merriparagraph" text="Delete" />
261
+ </Box>
262
+ </Box>
263
+ )}
264
+ {(onExport || rows.length > 0) && (
265
+ <Box
266
+ onClick={e => {
267
+ e.stopPropagation()
268
+ handleActionSelection('export')
269
+ }}
270
+ display="flex"
271
+ flexDirection="column"
272
+ alignItems="center"
273
+ sx={{
274
+ padding: '8px',
275
+ cursor: 'pointer',
276
+ '&:hover': {
277
+ backgroundColor: 'rgba(0, 0, 0, 0.04)',
278
+ },
279
+ borderRadius: '4px',
280
+ transition: 'background-color 0.2s',
281
+ userSelect: 'none',
282
+ }}
283
+ >
284
+ <Box
285
+ sx={{
286
+ display: 'flex',
287
+ flexDirection: 'column',
288
+ alignItems: 'center',
289
+ color: 'black',
290
+ }}
291
+ >
292
+ <ExportIcon />
293
+ <Typography fontvariant="merriparagraph" text="Export" />
294
+ </Box>
295
+ </Box>
296
+ )}
95
297
  </Box>
96
- ))}
298
+ )}
97
299
  </Stack>
98
300
  )
99
301
 
100
- useEffect(() => {
101
- const popover = popoverRef.current
102
- if (!popover) return
103
-
104
- let isDragging = false
105
- let startX: number, startY: number
106
-
107
- const handleMouseDown = (e: MouseEvent) => {
108
- isDragging = true
109
- startX = e.clientX - position.x
110
- startY = e.clientY - position.y
111
- document.addEventListener('mousemove', handleMouseMove)
112
- document.addEventListener('mouseup', handleMouseUp)
113
- }
114
-
115
- const handleMouseMove = (e: MouseEvent) => {
116
- if (!isDragging) return
117
- const newX = e.clientX - startX
118
- const newY = e.clientY - startY
119
- setPosition({ x: newX, y: newY })
120
- }
121
-
122
- const handleMouseUp = () => {
123
- isDragging = false
124
- document.removeEventListener('mousemove', handleMouseMove)
125
- document.removeEventListener('mouseup', handleMouseUp)
126
- }
127
-
128
- popover.addEventListener('mousedown', handleMouseDown)
129
-
130
- return () => {
131
- popover.removeEventListener('mousedown', handleMouseDown)
132
- document.removeEventListener('mousemove', handleMouseMove)
133
- document.removeEventListener('mouseup', handleMouseUp)
134
- }
135
- }, [position])
302
+ if (selectedRows.length === 0) return null
136
303
 
137
304
  return (
138
- <Popover
139
- open={open}
140
- onClose={handleClose}
141
- anchorReference="anchorPosition"
142
- anchorPosition={{ top: position.y, left: position.x }}
143
- transformOrigin={{
144
- vertical: 'top',
145
- horizontal: 'left',
146
- }}
147
- slotProps={{
148
- paper: {
149
- ref: popoverRef,
150
- component: Paper,
151
- style: {
152
- zIndex: 1300,
153
- overflow: 'hidden',
154
- display: 'flex',
155
- alignItems: 'center',
156
- justifyContent: 'space-between',
157
- height: '60px',
158
- minWidth: '560px',
159
- padding: '0 10px',
160
- cursor: 'move',
161
- },
162
- },
305
+ <Paper
306
+ elevation={3}
307
+ sx={{
308
+ zIndex: 1300,
309
+ overflow: 'hidden',
310
+ display: 'flex',
311
+ alignItems: 'center',
312
+ justifyContent: 'space-between',
313
+ height: '60px',
314
+ minWidth: '560px',
315
+ padding: '0 10px',
316
+ userSelect: 'none',
317
+ boxShadow: '0px 4px 10px rgba(0, 0, 0, 0.1)',
163
318
  }}
164
319
  >
165
320
  <Box
166
- flexGrow={1}
167
321
  display="flex"
168
322
  alignItems="center"
169
- paddingLeft="16px"
170
- paddingRight="16px"
323
+ gap={1}
324
+ sx={{
325
+ width: '100%',
326
+ }}
171
327
  >
172
- <Typography
173
- fontvariant="merriparagraph"
174
- text={`${selectedRows.length} items selected`}
175
- />
328
+ <Box
329
+ flexGrow={1}
330
+ display="flex"
331
+ alignItems="center"
332
+ paddingLeft="16px"
333
+ paddingRight="16px"
334
+ >
335
+ <Typography
336
+ fontvariant="merriparagraph"
337
+ text={`${selectedRows.length} ${
338
+ selectedRows.length === 1 ? 'item' : 'items'
339
+ } selected`}
340
+ />
341
+ </Box>
342
+ {actionButtons}
176
343
  </Box>
177
- {!actionType && actionButtons}
178
- </Popover>
344
+ </Paper>
179
345
  )
180
346
  }
181
347
 
@@ -4,9 +4,9 @@ import React from 'react'
4
4
  import { CircularProgress } from '@mui/material'
5
5
  import ContentSection from '../../Content'
6
6
  import type { ContentSectionProps } from '../../Content'
7
- import type { BorderProp } from '../../Grid'
8
7
  import { useAtomValue } from 'jotai'
9
8
  import { columnVisibilityAtom } from '../Jotai/atom'
9
+ import * as palette from '../../../styles/palette'
10
10
 
11
11
  export interface ColumnDef {
12
12
  field: string
@@ -34,7 +34,7 @@ export interface HeaderParams {
34
34
  }
35
35
 
36
36
  export interface RowData {
37
- id: string
37
+ _id: string
38
38
  [key: string]: unknown
39
39
  }
40
40
 
@@ -78,6 +78,8 @@ const Table = React.forwardRef<TableRef, TableProps>(
78
78
  rowHeight = DEFAULT_ROW_HEIGHT,
79
79
  onRowClick,
80
80
  selectedRows = [],
81
+ onSelectionChange,
82
+ checkboxSelection = false,
81
83
  },
82
84
  ref
83
85
  ) => {
@@ -90,7 +92,6 @@ const Table = React.forwardRef<TableRef, TableProps>(
90
92
  selectedRows,
91
93
  })
92
94
 
93
- // Filter visible columns
94
95
  const visibleColumns = columns.filter(column => {
95
96
  const isVisible = columnVisibility[column.field] !== false
96
97
  console.log(`Column ${column.field} visibility:`, isVisible)
@@ -102,13 +103,13 @@ const Table = React.forwardRef<TableRef, TableProps>(
102
103
  getSelectedRows: () =>
103
104
  new Map(
104
105
  rows
105
- .filter(row => selectedRows.includes(row.id))
106
- .map(row => [row.id, row])
106
+ .filter(row => selectedRows.includes(row._id))
107
+ .map(row => [row._id, row])
107
108
  ),
108
109
  forceUpdate: () => {},
109
110
  setPage: () => {},
110
111
  setPageSize: () => {},
111
- getRowIndex: (id: string) => rows.findIndex(row => row.id === id),
112
+ getRowIndex: (id: string) => rows.findIndex(row => row._id === id),
112
113
  isRowSelected: (id: string) => selectedRows.includes(id),
113
114
  }))
114
115
 
@@ -137,32 +138,30 @@ const Table = React.forwardRef<TableRef, TableProps>(
137
138
  )
138
139
  }
139
140
 
140
- if (rows.length === 0) {
141
- return (
142
- <ContentSection
143
- grids={[
144
- {
145
- grid: {
146
- gridconfig: {
147
- gridwidth: '100%',
148
- gridname: 'empty-table',
149
- alignment: 'left',
150
- },
151
- },
152
- typography: {
153
- columnconfig: {
154
- row: 1,
155
- column: 1,
156
- },
157
- text: 'No data available',
158
- },
159
- },
160
- ]}
161
- />
162
- )
141
+ const allRowsSelected =
142
+ rows.length > 0 && rows.every(row => selectedRows.includes(row._id))
143
+ const someRowsSelected =
144
+ rows.length > 0 && rows.some(row => selectedRows.includes(row._id))
145
+
146
+ const handleHeaderCheckboxChange = () => {
147
+ if (onSelectionChange) {
148
+ if (allRowsSelected) {
149
+ onSelectionChange([])
150
+ } else {
151
+ onSelectionChange(rows.map(row => row._id))
152
+ }
153
+ }
163
154
  }
164
155
 
165
- console.log('Table config - visibleColumns:', visibleColumns)
156
+ const handleRowCheckboxChange = (rowId: string) => {
157
+ if (onSelectionChange) {
158
+ if (selectedRows.includes(rowId)) {
159
+ onSelectionChange(selectedRows.filter(id => id !== rowId))
160
+ } else {
161
+ onSelectionChange([...selectedRows, rowId])
162
+ }
163
+ }
164
+ }
166
165
 
167
166
  const tableConfig: ContentSectionProps = {
168
167
  grids: [
@@ -174,45 +173,94 @@ const Table = React.forwardRef<TableRef, TableProps>(
174
173
  alignment: 'left',
175
174
  },
176
175
  },
176
+ checkbox: checkboxSelection
177
+ ? [
178
+ // Header checkbox
179
+ {
180
+ columnconfig: {
181
+ row: 1,
182
+ column: 1,
183
+ },
184
+ checked: allRowsSelected,
185
+ indeterminate: !allRowsSelected && someRowsSelected,
186
+ onChange: () => handleHeaderCheckboxChange(),
187
+ cellconfig: {
188
+ minHeight: `${rowHeight}px`,
189
+ width: '60px',
190
+ },
191
+ },
192
+ // Row checkboxes
193
+ ...rows.map((row, rowIndex) => ({
194
+ columnconfig: {
195
+ row: rowIndex + 2,
196
+ column: 1,
197
+ },
198
+ checked: selectedRows.includes(row._id),
199
+ onChange: () => handleRowCheckboxChange(row._id),
200
+ cellconfig: {
201
+ minHeight: `${rowHeight}px`,
202
+ width: '60px',
203
+ },
204
+ })),
205
+ ]
206
+ : [],
177
207
  typography: [
178
208
  // Header row
179
209
  ...visibleColumns.map((column, columnIndex) => ({
180
210
  columnconfig: {
181
211
  row: 1,
182
- column: columnIndex + 1,
212
+ column: checkboxSelection ? columnIndex + 2 : columnIndex + 1,
183
213
  },
184
214
  text: column.headerName,
185
215
  cellconfig: {
186
- border: 'solid' as BorderProp,
187
216
  minHeight: `${rowHeight}px`,
188
217
  width: column.width ? `${column.width}px` : '200px',
189
218
  mobilewidth: '100%',
190
219
  tabletwidth: '100%',
191
220
  computerwidth: '100%',
221
+ wrap: 'nowrap' as const,
192
222
  },
193
223
  })),
194
224
  // Data rows
195
- ...rows.flatMap((row, rowIndex) =>
196
- visibleColumns.map((column, columnIndex) => ({
197
- columnconfig: {
198
- row: rowIndex + 2,
199
- column: columnIndex + 1,
200
- },
201
- text: String(row[column.field] || ''),
202
- cellconfig: {
203
- border: 'solid' as BorderProp,
204
- minHeight: `${rowHeight}px`,
205
- width: column.width ? `${column.width}px` : '200px',
206
- mobilewidth: '100%',
207
- tabletwidth: '100%',
208
- computerwidth: '100%',
209
- onClick: onRowClick ? () => onRowClick(row) : undefined,
210
- backgroundColor: selectedRows.includes(row.id)
211
- ? 'rgba(0, 0, 0, 0.04)'
212
- : undefined,
213
- },
214
- }))
215
- ),
225
+ ...(rows.length === 0
226
+ ? visibleColumns.map((_, columnIndex) => ({
227
+ columnconfig: {
228
+ row: 2,
229
+ column: checkboxSelection
230
+ ? columnIndex + 2
231
+ : columnIndex + 1,
232
+ },
233
+ text: columnIndex === 0 ? 'No data available' : '',
234
+ cellconfig: {
235
+ minHeight: `${rowHeight}px`,
236
+ width: '200px',
237
+ mobilewidth: '100%',
238
+ tabletwidth: '100%',
239
+ computerwidth: '100%',
240
+ },
241
+ }))
242
+ : rows.flatMap((row, rowIndex) =>
243
+ visibleColumns.map((column, columnIndex) => ({
244
+ columnconfig: {
245
+ row: rowIndex + 2,
246
+ column: checkboxSelection
247
+ ? columnIndex + 2
248
+ : columnIndex + 1,
249
+ },
250
+ text: String(row[column.field] || ''),
251
+ cellconfig: {
252
+ minHeight: `${rowHeight}px`,
253
+ width: column.width ? `${column.width}px` : '200px',
254
+ mobilewidth: '100%',
255
+ tabletwidth: '100%',
256
+ computerwidth: '100%',
257
+ onClick: onRowClick ? () => onRowClick(row) : undefined,
258
+ backgroundColor: selectedRows.includes(row._id)
259
+ ? palette.marine.light
260
+ : undefined,
261
+ },
262
+ }))
263
+ )),
216
264
  ],
217
265
  },
218
266
  ],