goobs-frontend 0.8.6 → 0.8.8
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 +18 -18
- package/src/components/ConfirmationCodeInput/index.tsx +120 -46
- package/src/components/Content/Structure/phoneNumber/usePhoneNumber.tsx +3 -4
- package/src/components/Content/Structure/textfield/useTextField.tsx +5 -29
- package/src/components/DataGrid/Checkbox/index.tsx +79 -0
- package/src/components/DataGrid/Footer/index.tsx +169 -0
- package/src/components/DataGrid/Jotai/atom.ts +91 -0
- package/src/components/DataGrid/ManageColumn/index.tsx +211 -0
- package/src/components/DataGrid/ManageRow/index.tsx +182 -0
- package/src/components/DataGrid/Table/index.tsx +227 -0
- package/src/components/DataGrid/VerticalDivider/index.tsx +6 -0
- package/src/components/DataGrid/index.tsx +267 -0
- package/src/components/DataGrid/utils/useManageColumn.tsx +138 -0
- package/src/components/DataGrid/utils/useSearchbar.tsx +122 -0
- package/src/components/Form/DataGrid/index.tsx +63 -0
- package/src/components/PhoneNumberField/index.tsx +91 -67
- package/src/components/Searchbar/index.tsx +98 -42
- package/src/components/TextField/index.tsx +21 -33
- package/src/components/Toolbar/index.tsx +16 -18
- package/src/index.ts +16 -3
- package/src/components/ConfirmationCodeInput/utils/useCodeConfirmation.tsx +0 -150
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "goobs-frontend",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A comprehensive React-based UI library built on Material-UI, offering a wide range of customizable components including grids, typography, buttons, cards, forms, navigation, pricing tables, steppers, tooltips, accordions, and more. Designed for building responsive and consistent user interfaces with advanced features like form validation, theming, and code syntax highlighting.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,35 +19,35 @@
|
|
|
19
19
|
"lint": "next lint"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@emotion/cache": "^11.13.
|
|
23
|
-
"@emotion/react": "^11.13.
|
|
24
|
-
"@emotion/styled": "^11.13.
|
|
25
|
-
"@mui/icons-material": "^6.1.
|
|
26
|
-
"@mui/material": "^6.1.
|
|
27
|
-
"@types/lodash": "^4.17.
|
|
22
|
+
"@emotion/cache": "^11.13.5",
|
|
23
|
+
"@emotion/react": "^11.13.5",
|
|
24
|
+
"@emotion/styled": "^11.13.5",
|
|
25
|
+
"@mui/icons-material": "^6.1.8",
|
|
26
|
+
"@mui/material": "^6.1.8",
|
|
27
|
+
"@types/lodash": "^4.17.13",
|
|
28
28
|
"highlight.js": "^11.10.0",
|
|
29
|
-
"jotai": "^2.10.
|
|
29
|
+
"jotai": "^2.10.3",
|
|
30
30
|
"lodash": "^4.17.21",
|
|
31
|
-
"next": "15.0.
|
|
31
|
+
"next": "15.0.3",
|
|
32
32
|
"otplib": "^12.0.1",
|
|
33
33
|
"react-datepicker": "^7.5.0",
|
|
34
34
|
"react-qr-code": "^2.0.15"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@next/eslint-plugin-next": "^15.0.
|
|
38
|
-
"@types/node": "^22.
|
|
39
|
-
"@types/react": "18.3.
|
|
37
|
+
"@next/eslint-plugin-next": "^15.0.3",
|
|
38
|
+
"@types/node": "^22.10.0",
|
|
39
|
+
"@types/react": "18.3.12",
|
|
40
40
|
"@types/react-dom": "^18.3.1",
|
|
41
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
42
|
-
"@typescript-eslint/parser": "^8.
|
|
43
|
-
"eslint": "^9.
|
|
44
|
-
"eslint-config-next": "^15.0.
|
|
41
|
+
"@typescript-eslint/eslint-plugin": "^8.16.0",
|
|
42
|
+
"@typescript-eslint/parser": "^8.16.0",
|
|
43
|
+
"eslint": "^9.15.0",
|
|
44
|
+
"eslint-config-next": "^15.0.3",
|
|
45
45
|
"eslint-config-prettier": "^9.1.0",
|
|
46
46
|
"eslint-plugin-prettier": "^5.2.1",
|
|
47
|
-
"prettier": "^3.
|
|
47
|
+
"prettier": "^3.4.1",
|
|
48
48
|
"react": "^18.3.1",
|
|
49
49
|
"react-dom": "^18.3.1",
|
|
50
|
-
"typescript": "^5.
|
|
50
|
+
"typescript": "^5.7.2"
|
|
51
51
|
},
|
|
52
52
|
"files": [
|
|
53
53
|
"src"
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
|
-
import React, { ChangeEvent, KeyboardEvent, useState } from 'react'
|
|
2
|
+
import React, { ChangeEvent, KeyboardEvent, useState, useCallback } from 'react'
|
|
3
3
|
import { Input, Box } from '@mui/material'
|
|
4
|
-
import {
|
|
5
|
-
import { columnconfig } from '../../components/Grid'
|
|
4
|
+
import { columnconfig } from '../Grid'
|
|
6
5
|
import { red, green } from '../../styles/palette'
|
|
7
6
|
|
|
8
7
|
export interface ConfirmationCodeInputsProps {
|
|
@@ -13,59 +12,138 @@ export interface ConfirmationCodeInputsProps {
|
|
|
13
12
|
'aria-label'?: string
|
|
14
13
|
'aria-required'?: boolean
|
|
15
14
|
'aria-invalid'?: boolean
|
|
15
|
+
onChange?: (value: string) => void
|
|
16
|
+
value?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface UseCodeConfirmationProps {
|
|
20
|
+
codeLength: number
|
|
21
|
+
onChange?: (value: string) => void
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const useCodeConfirmation = ({
|
|
25
|
+
codeLength,
|
|
26
|
+
onChange,
|
|
27
|
+
}: UseCodeConfirmationProps) => {
|
|
28
|
+
const [code, setCode] = useState<Record<string, string>>(
|
|
29
|
+
Object.fromEntries(
|
|
30
|
+
Array.from({ length: codeLength }, (_, i) => [`code${i + 1}`, ''])
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
const handleCodeChange = useCallback(
|
|
35
|
+
(event: React.ChangeEvent<HTMLInputElement>, index: number) => {
|
|
36
|
+
const value = event.target.value.replace(/\D/g, '') // Only keep digits
|
|
37
|
+
if (value.length <= 1) {
|
|
38
|
+
// Only process if it's a single digit or empty
|
|
39
|
+
setCode(prevCode => {
|
|
40
|
+
const newCode = {
|
|
41
|
+
...prevCode,
|
|
42
|
+
[`code${index + 1}`]: value,
|
|
43
|
+
}
|
|
44
|
+
const combinedValue = Object.values(newCode).join('')
|
|
45
|
+
onChange?.(combinedValue)
|
|
46
|
+
return newCode
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
[onChange]
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
const handleKeyDown = useCallback(
|
|
54
|
+
(event: React.KeyboardEvent<HTMLInputElement>, index: number) => {
|
|
55
|
+
// Allow only numeric keys, navigation keys, and backspace
|
|
56
|
+
const allowedKeys = [
|
|
57
|
+
'Backspace',
|
|
58
|
+
'ArrowLeft',
|
|
59
|
+
'ArrowRight',
|
|
60
|
+
'Tab',
|
|
61
|
+
'Delete',
|
|
62
|
+
'Home',
|
|
63
|
+
'End',
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
if (!allowedKeys.includes(event.key) && !/^\d$/.test(event.key)) {
|
|
67
|
+
event.preventDefault()
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (event.key === 'Backspace' && !code[`code${index + 1}`] && index > 0) {
|
|
72
|
+
setCode(prevCode => {
|
|
73
|
+
const newCode = {
|
|
74
|
+
...prevCode,
|
|
75
|
+
[`code${index}`]: '',
|
|
76
|
+
}
|
|
77
|
+
const combinedValue = Object.values(newCode).join('')
|
|
78
|
+
onChange?.(combinedValue)
|
|
79
|
+
return newCode
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const prevInput = document.querySelector(
|
|
83
|
+
`input[name=code${index}]`
|
|
84
|
+
) as HTMLInputElement | null
|
|
85
|
+
if (prevInput) {
|
|
86
|
+
prevInput.focus()
|
|
87
|
+
}
|
|
88
|
+
} else if (event.key === 'ArrowLeft' && index > 0) {
|
|
89
|
+
const prevInput = document.querySelector(
|
|
90
|
+
`input[name=code${index}]`
|
|
91
|
+
) as HTMLInputElement | null
|
|
92
|
+
if (prevInput) {
|
|
93
|
+
prevInput.focus()
|
|
94
|
+
}
|
|
95
|
+
} else if (event.key === 'ArrowRight' && index < codeLength - 1) {
|
|
96
|
+
const nextInput = document.querySelector(
|
|
97
|
+
`input[name=code${index + 2}]`
|
|
98
|
+
) as HTMLInputElement | null
|
|
99
|
+
if (nextInput) {
|
|
100
|
+
nextInput.focus()
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
[code, codeLength, onChange]
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
handleCodeChange,
|
|
109
|
+
handleKeyDown,
|
|
110
|
+
}
|
|
16
111
|
}
|
|
17
112
|
|
|
18
|
-
/**
|
|
19
|
-
* ConfirmationCodeInputs component renders a set of input fields for entering a confirmation code.
|
|
20
|
-
* It uses the useCodeConfirmation hook to handle code changes and key events.
|
|
21
|
-
* @param props The props for the ConfirmationCodeInputs component.
|
|
22
|
-
* @returns The rendered ConfirmationCodeInputs component.
|
|
23
|
-
*/
|
|
24
113
|
const ConfirmationCodeInputs: React.FC<ConfirmationCodeInputsProps> = ({
|
|
25
114
|
codeLength = 6,
|
|
26
115
|
isValid,
|
|
116
|
+
onChange,
|
|
117
|
+
value,
|
|
27
118
|
'aria-label': ariaLabel,
|
|
28
119
|
'aria-required': ariaRequired,
|
|
29
120
|
'aria-invalid': ariaInvalid,
|
|
30
121
|
...props
|
|
31
122
|
}) => {
|
|
32
|
-
const
|
|
123
|
+
const { handleCodeChange, handleKeyDown } = useCodeConfirmation({
|
|
124
|
+
codeLength,
|
|
125
|
+
onChange,
|
|
126
|
+
})
|
|
33
127
|
|
|
34
|
-
const { handleCodeChange, handleKeyDown, combinedCode } = useCodeConfirmation(
|
|
35
|
-
{
|
|
36
|
-
codeLength,
|
|
37
|
-
isValid,
|
|
38
|
-
}
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* handleChange function is called when the value of an input field changes.
|
|
43
|
-
* It updates the code state using the handleCodeChange function from the useCodeConfirmation hook.
|
|
44
|
-
* If the input field has a value, it focuses on the next input field.
|
|
45
|
-
* @param event The change event triggered by the input field.
|
|
46
|
-
* @param index The index of the input field.
|
|
47
|
-
*/
|
|
48
128
|
const handleChange = (
|
|
49
129
|
event: ChangeEvent<HTMLInputElement>,
|
|
50
130
|
index: number
|
|
51
131
|
) => {
|
|
52
|
-
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
|
|
132
|
+
const inputValue = event.target.value.replace(/\D/g, '') // Only keep digits
|
|
133
|
+
if (inputValue.length <= 1) {
|
|
134
|
+
// Only process if it's a single digit or empty
|
|
135
|
+
handleCodeChange(event, index)
|
|
136
|
+
if (inputValue) {
|
|
137
|
+
const nextInput = document.querySelector(
|
|
138
|
+
`input[name=code${index + 2}]`
|
|
139
|
+
) as HTMLInputElement | null
|
|
140
|
+
if (nextInput) {
|
|
141
|
+
nextInput.focus()
|
|
142
|
+
}
|
|
59
143
|
}
|
|
60
144
|
}
|
|
61
145
|
}
|
|
62
146
|
|
|
63
|
-
/**
|
|
64
|
-
* handleKeyDownWrapper function is a wrapper for the handleKeyDown function from the useCodeConfirmation hook.
|
|
65
|
-
* It is called when a key is pressed while an input field is focused.
|
|
66
|
-
* @param event The keyboard event triggered by the input field.
|
|
67
|
-
* @param index The index of the input field.
|
|
68
|
-
*/
|
|
69
147
|
const handleKeyDownWrapper = (
|
|
70
148
|
event: KeyboardEvent<HTMLInputElement>,
|
|
71
149
|
index: number
|
|
@@ -73,15 +151,8 @@ const ConfirmationCodeInputs: React.FC<ConfirmationCodeInputsProps> = ({
|
|
|
73
151
|
handleKeyDown(event, index)
|
|
74
152
|
}
|
|
75
153
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
* It sets the code whenever the combinedCode changes and the code is valid.
|
|
79
|
-
*/
|
|
80
|
-
React.useEffect(() => {
|
|
81
|
-
if (isValid) {
|
|
82
|
-
setVerificationCode(combinedCode)
|
|
83
|
-
}
|
|
84
|
-
}, [combinedCode, isValid])
|
|
154
|
+
// Split the value into individual digits
|
|
155
|
+
const digits = value?.split('') || Array(codeLength).fill('')
|
|
85
156
|
|
|
86
157
|
return (
|
|
87
158
|
<Box
|
|
@@ -96,8 +167,11 @@ const ConfirmationCodeInputs: React.FC<ConfirmationCodeInputsProps> = ({
|
|
|
96
167
|
<Input
|
|
97
168
|
key={index}
|
|
98
169
|
name={`code${index + 1}`}
|
|
170
|
+
value={digits[index] || ''}
|
|
99
171
|
inputProps={{
|
|
100
172
|
maxLength: 1,
|
|
173
|
+
pattern: '[0-9]*',
|
|
174
|
+
inputMode: 'numeric',
|
|
101
175
|
'aria-label': `Code Digit ${index + 1}`,
|
|
102
176
|
'aria-required': ariaRequired,
|
|
103
177
|
'aria-invalid': ariaInvalid,
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
import React from 'react'
|
|
3
|
-
import PhoneNumberField
|
|
4
|
-
PhoneNumberFieldProps,
|
|
5
|
-
} from './../../../PhoneNumberField'
|
|
3
|
+
import PhoneNumberField from '../../../PhoneNumberField'
|
|
6
4
|
import { columnconfig, cellconfig } from '../../../Grid'
|
|
5
|
+
import { TextFieldProps } from '@mui/material'
|
|
7
6
|
|
|
8
|
-
export
|
|
7
|
+
export type ExtendedPhoneNumberFieldProps = TextFieldProps & {
|
|
9
8
|
columnconfig?: Partial<columnconfig>
|
|
10
9
|
cellconfig?: cellconfig
|
|
11
10
|
}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
+
'use client'
|
|
1
2
|
import React from 'react'
|
|
2
|
-
import TextField
|
|
3
|
-
CustomTextFieldProps,
|
|
4
|
-
} from '../../../../components/TextField'
|
|
3
|
+
import TextField from '../../../../components/TextField'
|
|
5
4
|
import { columnconfig, cellconfig } from '../../../Grid'
|
|
5
|
+
import { TextFieldProps } from '@mui/material'
|
|
6
6
|
|
|
7
7
|
type ExtendedColumnConfig = Omit<columnconfig, 'component'> & {
|
|
8
8
|
component?: columnconfig['component']
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export
|
|
12
|
-
extends Omit<CustomTextFieldProps, 'columnconfig'> {
|
|
11
|
+
export type ExtendedTextFieldProps = TextFieldProps & {
|
|
13
12
|
columnconfig?: ExtendedColumnConfig
|
|
14
13
|
cellconfig?: cellconfig
|
|
15
14
|
}
|
|
@@ -24,15 +23,6 @@ const useTextField = (grid: {
|
|
|
24
23
|
index: number
|
|
25
24
|
): columnconfig => {
|
|
26
25
|
const {
|
|
27
|
-
name,
|
|
28
|
-
label,
|
|
29
|
-
placeholder,
|
|
30
|
-
value,
|
|
31
|
-
onChange,
|
|
32
|
-
onFocus,
|
|
33
|
-
onBlur,
|
|
34
|
-
error,
|
|
35
|
-
InputProps,
|
|
36
26
|
columnconfig: itemColumnConfig,
|
|
37
27
|
cellconfig,
|
|
38
28
|
...restProps
|
|
@@ -54,21 +44,7 @@ const useTextField = (grid: {
|
|
|
54
44
|
cellconfig: {
|
|
55
45
|
...cellconfig,
|
|
56
46
|
},
|
|
57
|
-
component:
|
|
58
|
-
<TextField
|
|
59
|
-
key={`textfield-${index}`}
|
|
60
|
-
name={name}
|
|
61
|
-
label={label}
|
|
62
|
-
placeholder={placeholder}
|
|
63
|
-
value={value}
|
|
64
|
-
onChange={onChange}
|
|
65
|
-
onFocus={onFocus}
|
|
66
|
-
onBlur={onBlur}
|
|
67
|
-
error={error}
|
|
68
|
-
InputProps={InputProps}
|
|
69
|
-
{...restProps}
|
|
70
|
-
/>
|
|
71
|
-
),
|
|
47
|
+
component: <TextField key={`textfield-${index}`} {...restProps} />,
|
|
72
48
|
}
|
|
73
49
|
|
|
74
50
|
return mergedConfig
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Checkbox } from '@mui/material'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
import * as palette from '../../../styles/palette'
|
|
6
|
+
|
|
7
|
+
interface DataGridCheckboxProps {
|
|
8
|
+
onClick?: (event: React.MouseEvent) => void
|
|
9
|
+
checked?: boolean
|
|
10
|
+
indeterminate?: boolean
|
|
11
|
+
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
|
12
|
+
disabled?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function DataGridCheckbox({
|
|
16
|
+
onClick,
|
|
17
|
+
checked,
|
|
18
|
+
indeterminate,
|
|
19
|
+
onChange,
|
|
20
|
+
disabled,
|
|
21
|
+
...props
|
|
22
|
+
}: DataGridCheckboxProps) {
|
|
23
|
+
console.log('DataGridCheckbox render:', { checked, indeterminate, disabled })
|
|
24
|
+
|
|
25
|
+
const handleClick = (event: React.MouseEvent) => {
|
|
26
|
+
console.log('Checkbox clicked:', {
|
|
27
|
+
checked,
|
|
28
|
+
indeterminate,
|
|
29
|
+
eventTarget: event.target,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
if (onClick) {
|
|
33
|
+
onClick(event)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
38
|
+
event.stopPropagation()
|
|
39
|
+
|
|
40
|
+
console.log('Checkbox changed:', {
|
|
41
|
+
newChecked: event.target.checked,
|
|
42
|
+
previousChecked: checked,
|
|
43
|
+
indeterminate,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
if (onChange) {
|
|
47
|
+
onChange(event)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<Checkbox
|
|
53
|
+
sx={{
|
|
54
|
+
color: palette.marine.main,
|
|
55
|
+
'&.Mui-checked': {
|
|
56
|
+
color: palette.marine.main,
|
|
57
|
+
},
|
|
58
|
+
'&.Mui-indeterminate': {
|
|
59
|
+
color: palette.marine.main,
|
|
60
|
+
},
|
|
61
|
+
'&.Mui-disabled': {
|
|
62
|
+
color: palette.greyborder.main,
|
|
63
|
+
},
|
|
64
|
+
'&:hover': {
|
|
65
|
+
backgroundColor: palette.marine.light,
|
|
66
|
+
opacity: 0.1,
|
|
67
|
+
},
|
|
68
|
+
}}
|
|
69
|
+
checked={checked}
|
|
70
|
+
indeterminate={indeterminate}
|
|
71
|
+
onClick={handleClick}
|
|
72
|
+
onChange={handleChange}
|
|
73
|
+
disabled={disabled}
|
|
74
|
+
{...props}
|
|
75
|
+
/>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export default DataGridCheckbox
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { useState } from 'react'
|
|
4
|
+
import { Box } from '@mui/material'
|
|
5
|
+
import { useAtomValue } from 'jotai'
|
|
6
|
+
import { columnVisibilityAtom } from '../Jotai/atom'
|
|
7
|
+
import { VerticalDivider } from '../VerticalDivider'
|
|
8
|
+
import TablePagination from '@mui/material/TablePagination'
|
|
9
|
+
import ManageColumn from '../ManageColumn'
|
|
10
|
+
import { ColumnDef } from '../Table'
|
|
11
|
+
import CustomButton from '../../Button'
|
|
12
|
+
import ShowHideEyeIcon from '@/components/Icons/ShowHideEye'
|
|
13
|
+
|
|
14
|
+
export interface CustomFooterProps {
|
|
15
|
+
page: number
|
|
16
|
+
pageSize: number
|
|
17
|
+
rowCount: number
|
|
18
|
+
onPageChange: (newPage: number) => void
|
|
19
|
+
onPageSizeChange: (newPageSize: number) => void
|
|
20
|
+
columns: ColumnDef[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function CustomFooter({
|
|
24
|
+
page,
|
|
25
|
+
pageSize,
|
|
26
|
+
rowCount,
|
|
27
|
+
onPageChange,
|
|
28
|
+
onPageSizeChange,
|
|
29
|
+
columns,
|
|
30
|
+
}: CustomFooterProps) {
|
|
31
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
32
|
+
const [checkboxWidth] = useState(45)
|
|
33
|
+
const columnVisibility = useAtomValue(columnVisibilityAtom)
|
|
34
|
+
|
|
35
|
+
const handleOpen = () => {
|
|
36
|
+
console.log('Footer handleOpen')
|
|
37
|
+
setIsOpen(true)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const handleClose = () => {
|
|
41
|
+
console.log('Footer handleClose')
|
|
42
|
+
setIsOpen(false)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Calculate total width based on visible columns only
|
|
46
|
+
const totalWidth = columns.reduce((sum, col) => {
|
|
47
|
+
if (columnVisibility[col.field] !== false) {
|
|
48
|
+
return sum + (col.width || 150)
|
|
49
|
+
}
|
|
50
|
+
return sum
|
|
51
|
+
}, 0)
|
|
52
|
+
|
|
53
|
+
const totalPages = Math.ceil(rowCount / pageSize)
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Box
|
|
57
|
+
className="custom-footer-container"
|
|
58
|
+
sx={{
|
|
59
|
+
width: `${totalWidth}px`,
|
|
60
|
+
minWidth: '100%',
|
|
61
|
+
height: '56px',
|
|
62
|
+
position: 'sticky',
|
|
63
|
+
left: 0,
|
|
64
|
+
marginLeft: `${checkboxWidth}px`,
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
<Box
|
|
68
|
+
sx={{
|
|
69
|
+
display: 'flex',
|
|
70
|
+
justifyContent: 'space-between',
|
|
71
|
+
alignItems: 'center',
|
|
72
|
+
flexWrap: 'nowrap',
|
|
73
|
+
width: '100%',
|
|
74
|
+
height: '100%',
|
|
75
|
+
px: 2,
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
<Box
|
|
79
|
+
sx={{
|
|
80
|
+
display: 'flex',
|
|
81
|
+
alignItems: 'center',
|
|
82
|
+
}}
|
|
83
|
+
className="left-box"
|
|
84
|
+
>
|
|
85
|
+
<Box sx={{ display: 'flex', alignItems: 'center', mr: '10px' }}>
|
|
86
|
+
<VerticalDivider />
|
|
87
|
+
</Box>
|
|
88
|
+
<Box
|
|
89
|
+
sx={{
|
|
90
|
+
display: 'flex',
|
|
91
|
+
alignItems: 'center',
|
|
92
|
+
gap: '8px',
|
|
93
|
+
pr: '8px',
|
|
94
|
+
}}
|
|
95
|
+
>
|
|
96
|
+
<CustomButton
|
|
97
|
+
onClick={handleOpen}
|
|
98
|
+
text="Manage Columns"
|
|
99
|
+
fontvariant="merriparagraph"
|
|
100
|
+
fontcolor="black"
|
|
101
|
+
icon={<ShowHideEyeIcon visible={true} />}
|
|
102
|
+
iconcolor="black"
|
|
103
|
+
iconlocation="left"
|
|
104
|
+
disableButton="false"
|
|
105
|
+
sx={{
|
|
106
|
+
minWidth: 'unset',
|
|
107
|
+
padding: '8px',
|
|
108
|
+
'& .MuiTypography-root': {
|
|
109
|
+
marginLeft: '16px',
|
|
110
|
+
},
|
|
111
|
+
}}
|
|
112
|
+
/>
|
|
113
|
+
</Box>
|
|
114
|
+
<Box
|
|
115
|
+
sx={{
|
|
116
|
+
display: 'flex',
|
|
117
|
+
alignItems: 'center',
|
|
118
|
+
ml: '10px',
|
|
119
|
+
mr: '10px',
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
<VerticalDivider />
|
|
123
|
+
</Box>
|
|
124
|
+
</Box>
|
|
125
|
+
<Box
|
|
126
|
+
sx={{
|
|
127
|
+
display: 'flex',
|
|
128
|
+
alignItems: 'center',
|
|
129
|
+
ml: 'auto',
|
|
130
|
+
}}
|
|
131
|
+
className="right-box"
|
|
132
|
+
>
|
|
133
|
+
<TablePagination
|
|
134
|
+
component="div"
|
|
135
|
+
count={rowCount}
|
|
136
|
+
page={page}
|
|
137
|
+
onPageChange={(_, newPage) => {
|
|
138
|
+
onPageChange(newPage)
|
|
139
|
+
}}
|
|
140
|
+
rowsPerPage={pageSize}
|
|
141
|
+
onRowsPerPageChange={event => {
|
|
142
|
+
const newPageSize = parseInt(event.target.value, 10)
|
|
143
|
+
onPageSizeChange(newPageSize)
|
|
144
|
+
}}
|
|
145
|
+
rowsPerPageOptions={[10, 25, 50, 100]}
|
|
146
|
+
slotProps={{
|
|
147
|
+
actions: {
|
|
148
|
+
previousButton: {
|
|
149
|
+
disabled: page === 0,
|
|
150
|
+
},
|
|
151
|
+
nextButton: {
|
|
152
|
+
disabled: page >= totalPages - 1,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
}}
|
|
156
|
+
showFirstButton
|
|
157
|
+
showLastButton
|
|
158
|
+
labelDisplayedRows={({ from, to, count }) =>
|
|
159
|
+
`${from}-${to} of ${count !== -1 ? count : `more than ${to}`}`
|
|
160
|
+
}
|
|
161
|
+
/>
|
|
162
|
+
</Box>
|
|
163
|
+
</Box>
|
|
164
|
+
<ManageColumn open={isOpen} handleClose={handleClose} columns={columns} />
|
|
165
|
+
</Box>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export default CustomFooter
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { atom } from 'jotai'
|
|
2
|
+
import { atomWithStorage } from 'jotai/utils'
|
|
3
|
+
|
|
4
|
+
interface ColumnVisibility {
|
|
5
|
+
[key: string]: boolean
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Create a persistent atom that saves to localStorage
|
|
9
|
+
export const columnVisibilityAtom = atomWithStorage<ColumnVisibility>(
|
|
10
|
+
'columnVisibility',
|
|
11
|
+
{}
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
// Atom for managing initial column setup
|
|
15
|
+
export const columnsAtom = atom<string[]>([])
|
|
16
|
+
|
|
17
|
+
// Actions atom for updating visibility
|
|
18
|
+
export const columnVisibilityActions = atom(
|
|
19
|
+
null,
|
|
20
|
+
(
|
|
21
|
+
get,
|
|
22
|
+
set,
|
|
23
|
+
update: {
|
|
24
|
+
type: 'toggle' | 'setAll' | 'reset' | 'save'
|
|
25
|
+
field?: string
|
|
26
|
+
value?: boolean
|
|
27
|
+
newState?: ColumnVisibility
|
|
28
|
+
}
|
|
29
|
+
) => {
|
|
30
|
+
const currentVisibility = get(columnVisibilityAtom)
|
|
31
|
+
const columns = get(columnsAtom)
|
|
32
|
+
let newVisibility: ColumnVisibility = {}
|
|
33
|
+
|
|
34
|
+
console.log('columnVisibilityActions - before:', {
|
|
35
|
+
type: update.type,
|
|
36
|
+
currentVisibility,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
switch (update.type) {
|
|
40
|
+
case 'toggle': {
|
|
41
|
+
if (update.field) {
|
|
42
|
+
newVisibility = {
|
|
43
|
+
...currentVisibility,
|
|
44
|
+
[update.field]: !currentVisibility[update.field],
|
|
45
|
+
}
|
|
46
|
+
console.log('columnVisibilityActions - toggle:', {
|
|
47
|
+
field: update.field,
|
|
48
|
+
before: currentVisibility[update.field],
|
|
49
|
+
after: newVisibility[update.field],
|
|
50
|
+
})
|
|
51
|
+
set(columnVisibilityAtom, newVisibility)
|
|
52
|
+
}
|
|
53
|
+
break
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
case 'setAll': {
|
|
57
|
+
columns.forEach(column => {
|
|
58
|
+
newVisibility[column] = !!update.value
|
|
59
|
+
})
|
|
60
|
+
console.log('columnVisibilityActions - setAll:', {
|
|
61
|
+
value: update.value,
|
|
62
|
+
newState: newVisibility,
|
|
63
|
+
})
|
|
64
|
+
set(columnVisibilityAtom, newVisibility)
|
|
65
|
+
break
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
case 'save': {
|
|
69
|
+
if (update.newState) {
|
|
70
|
+
console.log('columnVisibilityActions - save:', {
|
|
71
|
+
before: currentVisibility,
|
|
72
|
+
after: update.newState,
|
|
73
|
+
})
|
|
74
|
+
set(columnVisibilityAtom, update.newState)
|
|
75
|
+
}
|
|
76
|
+
break
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
case 'reset': {
|
|
80
|
+
columns.forEach(column => {
|
|
81
|
+
newVisibility[column] = true
|
|
82
|
+
})
|
|
83
|
+
console.log('columnVisibilityActions - reset:', newVisibility)
|
|
84
|
+
set(columnVisibilityAtom, newVisibility)
|
|
85
|
+
break
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.log('columnVisibilityActions - after:', get(columnVisibilityAtom))
|
|
90
|
+
}
|
|
91
|
+
)
|