goobs-frontend 0.9.17 → 0.9.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goobs-frontend",
3
- "version": "0.9.17",
3
+ "version": "0.9.19",
4
4
  "type": "module",
5
5
  "description": "A comprehensive React-based libary that extends the functionality of Material-UI",
6
6
  "license": "MIT",
@@ -25,8 +25,8 @@
25
25
  "@emotion/cache": "^11",
26
26
  "@emotion/react": "^11",
27
27
  "@emotion/styled": "^11",
28
- "@mui/icons-material": "^6",
29
- "@mui/material": "^6",
28
+ "@mui/icons-material": "^7",
29
+ "@mui/material": "^7",
30
30
  "@storybook/addon-links": "^8",
31
31
  "@types/lodash": "^4",
32
32
  "formik": "^2",
@@ -36,7 +36,6 @@
36
36
  "next": "15",
37
37
  "otplib": "^12",
38
38
  "react-datepicker": "^8",
39
- "react-native": "^0.78.0",
40
39
  "react-qr-code": "^2",
41
40
  "slate": "^0.112",
42
41
  "slate-dom": "^0.112",
@@ -65,7 +64,7 @@
65
64
  "eslint-config-next": "^15",
66
65
  "eslint-config-prettier": "^10",
67
66
  "eslint-plugin-prettier": "^5",
68
- "eslint-plugin-storybook": "^0.11",
67
+ "eslint-plugin-storybook": "^0.12",
69
68
  "prettier": "^3",
70
69
  "react": "^19",
71
70
  "react-dom": "^19",
@@ -46,6 +46,7 @@ function CustomButton({
46
46
  iconlocation = 'left',
47
47
  fontlocation = 'center',
48
48
  disabled,
49
+ style = {},
49
50
  ...restProps
50
51
  }: CustomButtonProps) {
51
52
  // Merge MUI's "disabled" with our "disableButton"
@@ -119,6 +120,7 @@ function CustomButton({
119
120
  }
120
121
 
121
122
  // Inline styles for the top-level container (Box)
123
+ // Merge the passed style prop with our containerStyle
122
124
  const containerStyle: React.CSSProperties = {
123
125
  display: 'flex',
124
126
  flexDirection: 'column',
@@ -127,6 +129,7 @@ function CustomButton({
127
129
  height: height || (isIconOnly ? '36px' : isIconAbove ? 'auto' : '40px'),
128
130
  minHeight: isIconOnly ? '36px' : isIconAbove ? minHeight : 'auto',
129
131
  minWidth: isIconOnly ? '36px' : 'fit-content',
132
+ ...style, // Apply any custom styles passed through the style prop
130
133
  }
131
134
 
132
135
  // Style for the inner content box
@@ -43,8 +43,15 @@ function ManageRow({
43
43
  handleClose()
44
44
  break
45
45
  case 'delete':
46
- onDelete?.() // same here
47
- handleClose()
46
+ if (onDelete) {
47
+ // Execute the delete operation
48
+ onDelete()
49
+ // Clear the selection by setting it to empty array
50
+ if (selectedRows.length > 0) {
51
+ // This will properly close the ManageRow component
52
+ handleClose()
53
+ }
54
+ }
48
55
  break
49
56
  case 'export':
50
57
  if (onExport) {
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import React, { useState } from 'react'
3
+ import React, { useState, useMemo } from 'react'
4
4
  import { Box, Alert } from '@mui/material'
5
5
  import CustomToolbar from '../Toolbar'
6
6
  import Table from './Table'
@@ -24,7 +24,16 @@ function DataGrid({
24
24
  onManage,
25
25
  onShow,
26
26
  onSelectionChange,
27
+ showIdColumns = false,
27
28
  }: DatagridProps) {
29
+ // Filter columns to hide ID columns based on showIdColumns prop
30
+ const filteredColumns = useMemo(() => {
31
+ if (showIdColumns) {
32
+ return columns
33
+ }
34
+ return columns.filter(col => col.field !== 'id' && col.field !== '_id')
35
+ }, [columns, showIdColumns])
36
+
28
37
  // Local state
29
38
  const [rows, setRows] = useState<RowData[]>(providedRows || [])
30
39
  const [selectedRows, setSelectedRows] = useState<string[]>([])
@@ -32,7 +41,7 @@ function DataGrid({
32
41
  const [pageSize, setPageSize] = useState(10)
33
42
 
34
43
  // Initialize columns/rows if needed
35
- useInitializeGrid({ columns, providedRows, setRows })
44
+ useInitializeGrid({ columns: filteredColumns, providedRows, setRows })
36
45
 
37
46
  // 1) When row selection changes
38
47
  const handleSelectionChange = (newSelectedIds: string[]) => {
@@ -60,7 +69,7 @@ function DataGrid({
60
69
 
61
70
  // 2) Search logic
62
71
  const { filteredRows, updatedSearchbarProps } = useSearchbar({
63
- columns,
72
+ columns: filteredColumns,
64
73
  rows,
65
74
  searchbarProps,
66
75
  })
@@ -116,7 +125,14 @@ function DataGrid({
116
125
  onDuplicate: onDuplicate
117
126
  ? () => onDuplicate(selectedRows)
118
127
  : undefined,
119
- onDelete: onDelete ? () => onDelete(selectedRows) : undefined,
128
+ onDelete: onDelete
129
+ ? () => {
130
+ // Call the onDelete handler and clear selection after it completes
131
+ onDelete(selectedRows)
132
+ // Clear the selection after delete operation
133
+ handleSelectionChange([])
134
+ }
135
+ : undefined,
120
136
  onManage: handleManage,
121
137
  onShow: onShow,
122
138
  handleClose: handleManageRowClose,
@@ -138,7 +154,7 @@ function DataGrid({
138
154
  >
139
155
  {/* Table component */}
140
156
  <Table
141
- columns={columns}
157
+ columns={filteredColumns}
142
158
  rows={visibleRows}
143
159
  selectedRowIds={selectedRows}
144
160
  onRowClick={handleRowClick}
@@ -154,7 +170,7 @@ function DataGrid({
154
170
  rowCount={filteredRows.length}
155
171
  onPageChange={setPage}
156
172
  onPageSizeChange={setPageSize}
157
- columns={columns}
173
+ columns={filteredColumns}
158
174
  />
159
175
  </Box>
160
176
  </Box>
@@ -48,6 +48,9 @@ export interface DatagridProps {
48
48
  searchbarProps?: SearchbarProps
49
49
  error?: Error | null
50
50
 
51
+ // Controls whether ID columns (id/_id) are visible
52
+ showIdColumns?: boolean
53
+
51
54
  // Single or multi selection callbacks:
52
55
  onManage?: () => void
53
56
  onShow?: () => void
@@ -53,6 +53,8 @@ export interface DropdownProps extends Omit<FormControlProps, 'onChange'> {
53
53
  value?: string
54
54
  width?: string
55
55
  disabled?: boolean
56
+ // Controls whether ID columns (containing 'id' or '_id') are visible by default
57
+ showIdColumns?: boolean
56
58
  }
57
59
 
58
60
  const StyledFormControl = styled(FormControl)<{ width?: string }>(
@@ -173,10 +175,28 @@ const Dropdown: React.FC<DropdownProps> = ({
173
175
  value: externalValue,
174
176
  width,
175
177
  disabled = false,
178
+ showIdColumns = false, // Default to hiding ID columns for security
176
179
  }) => {
177
180
  const [selectedValue, setSelectedValue] = useState<string>('')
178
181
  const [focused, setFocused] = useState(false)
179
182
 
183
+ // Filter out options with id values if showIdColumns is false
184
+ const filteredOptions = React.useMemo(() => {
185
+ if (showIdColumns) {
186
+ return options
187
+ }
188
+ // Hide options where the value is exactly 'id' or '_id', or looks like a database ID
189
+ return options.filter(opt => {
190
+ const value = opt.value.toLowerCase()
191
+ // Check if value is an ID field or looks like an ObjectId
192
+ return !(
193
+ value === 'id' ||
194
+ value === '_id' ||
195
+ /^[0-9a-f]{24}$/.test(value) // MongoDB ObjectId format
196
+ )
197
+ })
198
+ }, [options, showIdColumns])
199
+
180
200
  useEffect(() => {
181
201
  if (externalValue !== undefined) {
182
202
  setSelectedValue(externalValue)
@@ -351,7 +371,7 @@ const Dropdown: React.FC<DropdownProps> = ({
351
371
  fontcolor={fontcolor}
352
372
  disabled={disabled}
353
373
  >
354
- {options.map(renderMenuItem)}
374
+ {filteredOptions.map(renderMenuItem)}
355
375
  </StyledSelect>
356
376
  {helperText && (
357
377
  <FormHelperText error={error}>{helperText}</FormHelperText>
@@ -66,6 +66,8 @@ export interface SearchableDropdownProps {
66
66
  searchHistory?: HistoryItem[] | string[]
67
67
  onSearch?: (searchTerm: string, timestamp?: Date) => void
68
68
  maxHistoryItems?: number
69
+ // Controls whether ID columns (containing 'id' or '_id') are visible by default
70
+ showIdColumns?: boolean
69
71
  }
70
72
 
71
73
  const StyledFormControl = styled(FormControl)<{ width?: string }>(
@@ -252,6 +254,28 @@ const StyledFormHelperText = styled(FormHelperText)({
252
254
  marginLeft: '14px',
253
255
  })
254
256
 
257
+ // Create a utility function to check if a field is an ID field
258
+ const isIdField = (fieldName: string): boolean => {
259
+ if (!fieldName) return false
260
+
261
+ // Check if it's a common ID field name
262
+ if (fieldName.toLowerCase() === 'id' || fieldName.toLowerCase() === '_id') {
263
+ return true
264
+ }
265
+
266
+ // Check if it contains "id" or "_id" as a standalone word or suffix
267
+ if (/(\b|_)id$/i.test(fieldName)) {
268
+ return true
269
+ }
270
+
271
+ // Check if it looks like a MongoDB ObjectId
272
+ if (/^[0-9a-f]{24}$/.test(fieldName)) {
273
+ return true
274
+ }
275
+
276
+ return false
277
+ }
278
+
255
279
  const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
256
280
  label,
257
281
  options,
@@ -277,6 +301,7 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
277
301
  searchHistory = [], // Default to empty array
278
302
  onSearch,
279
303
  maxHistoryItems = 5, // Default to showing 5 history items
304
+ showIdColumns = false, // Default to hiding ID columns for security
280
305
  }) => {
281
306
  const [value, setValue] = useState<DropdownOption | string | null>(null)
282
307
  const [inputValue, setInputValue] = useState('')
@@ -561,6 +586,23 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
561
586
  return localHistory
562
587
  }, [searchHistory, localHistory])
563
588
 
589
+ // Filter out options with id values if showIdColumns is false
590
+ const filteredBaseOptions = React.useMemo(() => {
591
+ if (showIdColumns) {
592
+ return options
593
+ }
594
+ // Hide options where the value is exactly 'id' or '_id', or looks like a database ID
595
+ return options.filter(opt => {
596
+ const value = opt.value.toLowerCase()
597
+ // Check if value is an ID field or looks like an ObjectId (MongoDB ID format)
598
+ return !(
599
+ value === 'id' ||
600
+ value === '_id' ||
601
+ /^[0-9a-f]{24}$/.test(value)
602
+ )
603
+ })
604
+ }, [options, showIdColumns])
605
+
564
606
  // Create a combined options array based on active tab and input value
565
607
  const getFilteredOptions = React.useCallback(() => {
566
608
  const currentInputVal = inputValue.trim()
@@ -581,7 +623,7 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
581
623
  // Map history items to dropdown options
582
624
  return combinedHistory.map(item => {
583
625
  // Check if this history item matches any of the original options
584
- const matchingOption = options.find(
626
+ const matchingOption = filteredBaseOptions.find(
585
627
  opt =>
586
628
  opt.value.toLowerCase() === item.text.toLowerCase() ||
587
629
  (
@@ -617,11 +659,11 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
617
659
  // ALL OPTIONS TAB (activeTab === 0)
618
660
  // If input is empty, return all options
619
661
  if (!currentInputVal) {
620
- return options
662
+ return filteredBaseOptions
621
663
  }
622
664
 
623
665
  // Filter options based on current input
624
- const filteredOpts = options.filter(opt =>
666
+ const filteredOpts = filteredBaseOptions.filter(opt =>
625
667
  opt.value.toLowerCase().includes(currentInputVal.toLowerCase())
626
668
  )
627
669
 
@@ -654,7 +696,7 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
654
696
  }
655
697
 
656
698
  return filteredOpts
657
- }, [inputValue, combinedHistory, options, activeTab, variant])
699
+ }, [inputValue, combinedHistory, filteredBaseOptions, activeTab, variant])
658
700
 
659
701
  // Create the footer component for the dropdown with tabs
660
702
  const ListboxFooter = React.forwardRef<HTMLDivElement>((_, ref) => (
@@ -962,124 +1004,165 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
962
1004
  />
963
1005
  )}
964
1006
 
965
- {/* For simple variant - show attribute1 and attribute2 on one line */}
1007
+ {/* For simple variant - show attribute1 and attribute2 on one line (excluding ID fields) */}
966
1008
  {variant === 'simple' &&
967
1009
  !isHistoryItem &&
968
1010
  !isCurrentInput &&
969
1011
  !isNoHistoryPlaceholder &&
970
- (option.attribute1 || option.attribute2) && (
971
- <Typography
972
- fontvariant="merriparagraph"
973
- text={[option.attribute1, option.attribute2]
974
- .filter(Boolean)
975
- .join(' | ')}
976
- fontcolor="rgba(0, 0, 0, 0.6)"
977
- sx={{
978
- fontSize: '12px',
979
- lineHeight: '16px',
980
- width: '100%',
981
- textAlign: 'left',
982
- }}
983
- />
984
- )}
1012
+ (() => {
1013
+ // Filter out ID attributes if showIdColumns is false
1014
+ const filteredAttributes = [
1015
+ option.attribute1,
1016
+ option.attribute2,
1017
+ ].filter(attr => showIdColumns || (attr && !isIdField(attr)))
1018
+
1019
+ return filteredAttributes.length > 0 ? (
1020
+ <Typography
1021
+ fontvariant="merriparagraph"
1022
+ text={filteredAttributes.join(' | ')}
1023
+ fontcolor="rgba(0, 0, 0, 0.6)"
1024
+ sx={{
1025
+ fontSize: '12px',
1026
+ lineHeight: '16px',
1027
+ width: '100%',
1028
+ textAlign: 'left',
1029
+ }}
1030
+ />
1031
+ ) : null
1032
+ })()}
985
1033
 
986
- {/* For complex variant - show attributes on separate lines */}
1034
+ {/* For complex variant - show attributes on separate lines (excluding ID fields) */}
987
1035
  {variant === 'complex' &&
988
1036
  !isHistoryItem &&
989
1037
  !isCurrentInput &&
990
1038
  !isNoHistoryPlaceholder && (
991
1039
  <>
992
1040
  {/* First line of attributes */}
993
- {(option.attribute1 || option.attribute2) && (
994
- <Typography
995
- fontvariant="merriparagraph"
996
- text={[option.attribute1, option.attribute2]
997
- .filter(Boolean)
998
- .join(' | ')}
999
- fontcolor="rgba(0, 0, 0, 0.6)"
1000
- sx={{
1001
- fontSize: '12px',
1002
- lineHeight: '16px',
1003
- width: '100%',
1004
- textAlign: 'left',
1005
- }}
1006
- />
1007
- )}
1041
+ {(() => {
1042
+ const filteredAttributes = [
1043
+ option.attribute1,
1044
+ option.attribute2,
1045
+ ].filter(
1046
+ attr => showIdColumns || (attr && !isIdField(attr))
1047
+ )
1048
+
1049
+ return filteredAttributes.length > 0 ? (
1050
+ <Typography
1051
+ fontvariant="merriparagraph"
1052
+ text={filteredAttributes.join(' | ')}
1053
+ fontcolor="rgba(0, 0, 0, 0.6)"
1054
+ sx={{
1055
+ fontSize: '12px',
1056
+ lineHeight: '16px',
1057
+ width: '100%',
1058
+ textAlign: 'left',
1059
+ }}
1060
+ />
1061
+ ) : null
1062
+ })()}
1008
1063
 
1009
1064
  {/* Second line of attributes */}
1010
- {(option.attribute3 || option.attribute4) && (
1065
+ {(() => {
1066
+ const filteredAttributes = [
1067
+ option.attribute3,
1068
+ option.attribute4,
1069
+ ].filter(
1070
+ attr => showIdColumns || (attr && !isIdField(attr))
1071
+ )
1072
+
1073
+ return filteredAttributes.length > 0 ? (
1074
+ <Typography
1075
+ fontvariant="merriparagraph"
1076
+ text={filteredAttributes.join(' | ')}
1077
+ fontcolor="rgba(0, 0, 0, 0.6)"
1078
+ sx={{
1079
+ fontSize: '12px',
1080
+ lineHeight: '16px',
1081
+ width: '100%',
1082
+ textAlign: 'left',
1083
+ }}
1084
+ />
1085
+ ) : null
1086
+ })()}
1087
+
1088
+ {/* Third line of attributes */}
1089
+ {(() => {
1090
+ const filteredAttributes = [
1091
+ option.attribute5,
1092
+ option.attribute6,
1093
+ ].filter(
1094
+ attr => showIdColumns || (attr && !isIdField(attr))
1095
+ )
1096
+
1097
+ return filteredAttributes.length > 0 ? (
1098
+ <Typography
1099
+ fontvariant="merriparagraph"
1100
+ text={filteredAttributes.join(' | ')}
1101
+ fontcolor="rgba(0, 0, 0, 0.6)"
1102
+ sx={{
1103
+ fontSize: '12px',
1104
+ lineHeight: '16px',
1105
+ width: '100%',
1106
+ textAlign: 'left',
1107
+ }}
1108
+ />
1109
+ ) : null
1110
+ })()}
1111
+ </>
1112
+ )}
1113
+
1114
+ {/* For history items, show additional attributes from original options (excluding ID fields) */}
1115
+ {isHistoryItem && variant === 'complex' && (
1116
+ <>
1117
+ {/* Show attribute3/4 as first additional line for history */}
1118
+ {(() => {
1119
+ const filteredAttributes = [
1120
+ option.attribute3,
1121
+ option.attribute4,
1122
+ ].filter(
1123
+ attr => showIdColumns || (attr && !isIdField(attr))
1124
+ )
1125
+
1126
+ return filteredAttributes.length > 0 ? (
1011
1127
  <Typography
1012
1128
  fontvariant="merriparagraph"
1013
- text={[option.attribute3, option.attribute4]
1014
- .filter(Boolean)
1015
- .join(' | ')}
1129
+ text={filteredAttributes.join(' | ')}
1016
1130
  fontcolor="rgba(0, 0, 0, 0.6)"
1017
1131
  sx={{
1018
1132
  fontSize: '12px',
1019
1133
  lineHeight: '16px',
1020
1134
  width: '100%',
1021
1135
  textAlign: 'left',
1136
+ fontStyle: 'italic',
1022
1137
  }}
1023
1138
  />
1024
- )}
1139
+ ) : null
1140
+ })()}
1025
1141
 
1026
- {/* Third line of attributes */}
1027
- {(option.attribute5 || option.attribute6) && (
1142
+ {/* Show attribute5/6 as second additional line for history */}
1143
+ {(() => {
1144
+ const filteredAttributes = [
1145
+ option.attribute5,
1146
+ option.attribute6,
1147
+ ].filter(
1148
+ attr => showIdColumns || (attr && !isIdField(attr))
1149
+ )
1150
+
1151
+ return filteredAttributes.length > 0 ? (
1028
1152
  <Typography
1029
1153
  fontvariant="merriparagraph"
1030
- text={[option.attribute5, option.attribute6]
1031
- .filter(Boolean)
1032
- .join(' | ')}
1154
+ text={filteredAttributes.join(' | ')}
1033
1155
  fontcolor="rgba(0, 0, 0, 0.6)"
1034
1156
  sx={{
1035
1157
  fontSize: '12px',
1036
1158
  lineHeight: '16px',
1037
1159
  width: '100%',
1038
1160
  textAlign: 'left',
1161
+ fontStyle: 'italic',
1039
1162
  }}
1040
1163
  />
1041
- )}
1042
- </>
1043
- )}
1044
-
1045
- {/* For history items, show additional attributes from original options */}
1046
- {isHistoryItem && variant === 'complex' && (
1047
- <>
1048
- {/* Show attribute3/4 as first additional line for history */}
1049
- {(option.attribute3 || option.attribute4) && (
1050
- <Typography
1051
- fontvariant="merriparagraph"
1052
- text={[option.attribute3, option.attribute4]
1053
- .filter(Boolean)
1054
- .join(' | ')}
1055
- fontcolor="rgba(0, 0, 0, 0.6)"
1056
- sx={{
1057
- fontSize: '12px',
1058
- lineHeight: '16px',
1059
- width: '100%',
1060
- textAlign: 'left',
1061
- fontStyle: 'italic',
1062
- }}
1063
- />
1064
- )}
1065
-
1066
- {/* Show attribute5/6 as second additional line for history */}
1067
- {(option.attribute5 || option.attribute6) && (
1068
- <Typography
1069
- fontvariant="merriparagraph"
1070
- text={[option.attribute5, option.attribute6]
1071
- .filter(Boolean)
1072
- .join(' | ')}
1073
- fontcolor="rgba(0, 0, 0, 0.6)"
1074
- sx={{
1075
- fontSize: '12px',
1076
- lineHeight: '16px',
1077
- width: '100%',
1078
- textAlign: 'left',
1079
- fontStyle: 'italic',
1080
- }}
1081
- />
1082
- )}
1164
+ ) : null
1165
+ })()}
1083
1166
  </>
1084
1167
  )}
1085
1168