goobs-frontend 0.9.24 → 0.9.25
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.
|
|
3
|
+
"version": "0.9.25",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A comprehensive React-based libary that extends the functionality of Material-UI",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
"otplib": "^12",
|
|
38
38
|
"react-datepicker": "^8",
|
|
39
39
|
"react-qr-code": "^2",
|
|
40
|
-
"slate": "^0.
|
|
41
|
-
"slate-dom": "^0.
|
|
42
|
-
"slate-history": "^0.
|
|
43
|
-
"slate-react": "^0.
|
|
40
|
+
"slate": "^0.114",
|
|
41
|
+
"slate-dom": "^0.114",
|
|
42
|
+
"slate-history": "^0.113",
|
|
43
|
+
"slate-react": "^0.114",
|
|
44
44
|
"storybook": "^8",
|
|
45
45
|
"zod": "^3",
|
|
46
46
|
"zod-formik-adapter": "^1"
|
|
@@ -38,8 +38,7 @@ const ColumnHeaderRow: React.FC<ColumnHeaderRowProps> = ({
|
|
|
38
38
|
// If we're mobile, just render a single dropdown + "select all" checkbox
|
|
39
39
|
if (isMobile) {
|
|
40
40
|
const mobileOptions = allColumns.map(col => ({
|
|
41
|
-
value: col.field,
|
|
42
|
-
label: col.headerName ?? col.field,
|
|
41
|
+
value: col.headerName ?? col.field,
|
|
43
42
|
}))
|
|
44
43
|
|
|
45
44
|
// Find the currently-selected column as an object
|
|
@@ -47,7 +46,19 @@ const ColumnHeaderRow: React.FC<ColumnHeaderRowProps> = ({
|
|
|
47
46
|
mobileOptions.find(opt => opt.value === selectedOverflowField) || null
|
|
48
47
|
|
|
49
48
|
const handleMobileChange = (value: { value: string } | null) => {
|
|
50
|
-
|
|
49
|
+
if (value && value.value) {
|
|
50
|
+
// Try to find a column with matching headerName first
|
|
51
|
+
const matchingColumn = allColumns.find(
|
|
52
|
+
col => col.headerName === value.value || col.field === value.value
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
// If found, use its field property, otherwise use the value directly
|
|
56
|
+
setSelectedOverflowField(
|
|
57
|
+
matchingColumn ? matchingColumn.field : value.value
|
|
58
|
+
)
|
|
59
|
+
} else {
|
|
60
|
+
setSelectedOverflowField('')
|
|
61
|
+
}
|
|
51
62
|
}
|
|
52
63
|
|
|
53
64
|
return (
|
|
@@ -107,7 +118,20 @@ const ColumnHeaderRow: React.FC<ColumnHeaderRowProps> = ({
|
|
|
107
118
|
// Desktop logic
|
|
108
119
|
// ---------------------------
|
|
109
120
|
const handleOverflowChange = (value: { value: string } | null) => {
|
|
110
|
-
|
|
121
|
+
// If using headerName for value in dropdown, we need to find the corresponding field
|
|
122
|
+
if (value && value.value) {
|
|
123
|
+
// Try to find a column with matching headerName first
|
|
124
|
+
const matchingColumn = overflowDesktopColumns.find(
|
|
125
|
+
col => col.headerName === value.value || col.field === value.value
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
// If found, use its field property, otherwise use the value directly
|
|
129
|
+
setSelectedOverflowField(
|
|
130
|
+
matchingColumn ? matchingColumn.field : value.value
|
|
131
|
+
)
|
|
132
|
+
} else {
|
|
133
|
+
setSelectedOverflowField('')
|
|
134
|
+
}
|
|
111
135
|
}
|
|
112
136
|
|
|
113
137
|
return (
|
|
@@ -150,8 +174,7 @@ const ColumnHeaderRow: React.FC<ColumnHeaderRowProps> = ({
|
|
|
150
174
|
<SearchableDropdown
|
|
151
175
|
label="More Columns"
|
|
152
176
|
options={overflowDesktopColumns.map(oc => ({
|
|
153
|
-
value: oc.field,
|
|
154
|
-
label: oc.headerName ?? oc.field,
|
|
177
|
+
value: oc.headerName ?? oc.field,
|
|
155
178
|
}))}
|
|
156
179
|
defaultValue={selectedOverflowField}
|
|
157
180
|
onChange={handleOverflowChange}
|
|
@@ -4,11 +4,38 @@ import DatePicker from 'react-datepicker'
|
|
|
4
4
|
import 'react-datepicker/dist/react-datepicker.css'
|
|
5
5
|
import CalendarTodayIcon from '@mui/icons-material/CalendarToday'
|
|
6
6
|
import TextField, { TextFieldProps } from '../../Field/Text'
|
|
7
|
+
import { Box } from '@mui/material'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* DateRange interface for range mode
|
|
11
|
+
*/
|
|
12
|
+
export interface DateRange {
|
|
13
|
+
start: Date | null
|
|
14
|
+
end: Date | null
|
|
15
|
+
}
|
|
7
16
|
|
|
8
17
|
export interface DateFieldProps
|
|
9
18
|
extends Omit<TextFieldProps, 'onChange' | 'value' | 'endAdornment'> {
|
|
10
|
-
|
|
11
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Callback when date changes
|
|
21
|
+
*/
|
|
22
|
+
onChange?: (date: Date | null | DateRange) => void
|
|
23
|
+
/**
|
|
24
|
+
* Current date value
|
|
25
|
+
*/
|
|
26
|
+
value?: Date | null | DateRange
|
|
27
|
+
/**
|
|
28
|
+
* Whether to show date range picker instead of single date
|
|
29
|
+
*/
|
|
30
|
+
isRange?: boolean
|
|
31
|
+
/**
|
|
32
|
+
* Start date label (for range mode)
|
|
33
|
+
*/
|
|
34
|
+
startLabel?: string
|
|
35
|
+
/**
|
|
36
|
+
* End date label (for range mode)
|
|
37
|
+
*/
|
|
38
|
+
endLabel?: string
|
|
12
39
|
}
|
|
13
40
|
|
|
14
41
|
interface CustomInputProps {
|
|
@@ -35,6 +62,9 @@ const DateField: React.FC<DateFieldProps> = ({
|
|
|
35
62
|
onChange,
|
|
36
63
|
label = 'Select Date',
|
|
37
64
|
value,
|
|
65
|
+
isRange = false,
|
|
66
|
+
startLabel = 'Start Date',
|
|
67
|
+
endLabel = 'End Date',
|
|
38
68
|
...rest
|
|
39
69
|
}) => {
|
|
40
70
|
const formatDate = (date: Date | null) => {
|
|
@@ -49,22 +79,45 @@ const DateField: React.FC<DateFieldProps> = ({
|
|
|
49
79
|
return ''
|
|
50
80
|
}
|
|
51
81
|
|
|
52
|
-
|
|
82
|
+
// Initialize state based on whether in range mode or single date mode
|
|
83
|
+
const [selectedDate, setSelectedDate] = useState<Date | null>(
|
|
84
|
+
isRange ? null : (value as Date | null) || new Date()
|
|
85
|
+
)
|
|
86
|
+
const [dateRange, setDateRange] = useState<DateRange>(
|
|
87
|
+
isRange
|
|
88
|
+
? (value as DateRange) || { start: new Date(), end: new Date() }
|
|
89
|
+
: { start: new Date(), end: new Date() }
|
|
90
|
+
)
|
|
53
91
|
const [isOpen, setIsOpen] = useState(false)
|
|
54
|
-
const [
|
|
92
|
+
const [isStartDateOpen, setIsStartDateOpen] = useState(false)
|
|
93
|
+
const [isEndDateOpen, setIsEndDateOpen] = useState(false)
|
|
94
|
+
const [inputValue, setInputValue] = useState(
|
|
95
|
+
isRange ? '' : formatDate(selectedDate)
|
|
96
|
+
)
|
|
97
|
+
const [startDateInputValue, setStartDateInputValue] = useState(
|
|
98
|
+
formatDate(dateRange.start)
|
|
99
|
+
)
|
|
100
|
+
const [endDateInputValue, setEndDateInputValue] = useState(
|
|
101
|
+
formatDate(dateRange.end)
|
|
102
|
+
)
|
|
55
103
|
|
|
104
|
+
// Single date mode handlers
|
|
56
105
|
const handleChange = (date: Date | null) => {
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
onChange
|
|
106
|
+
if (!isRange) {
|
|
107
|
+
if (date) {
|
|
108
|
+
setSelectedDate(date)
|
|
109
|
+
setInputValue(formatDate(date))
|
|
110
|
+
setIsOpen(false)
|
|
111
|
+
if (onChange) {
|
|
112
|
+
onChange(date)
|
|
113
|
+
}
|
|
63
114
|
}
|
|
64
115
|
}
|
|
65
116
|
}
|
|
66
117
|
|
|
67
118
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
119
|
+
if (isRange) return // Only for single date mode
|
|
120
|
+
|
|
68
121
|
const input = e.target
|
|
69
122
|
const newValue = e.target.value
|
|
70
123
|
const selectionStart = input.selectionStart || 0
|
|
@@ -103,6 +156,117 @@ const DateField: React.FC<DateFieldProps> = ({
|
|
|
103
156
|
}, 0)
|
|
104
157
|
}
|
|
105
158
|
|
|
159
|
+
// Range mode handlers
|
|
160
|
+
const handleStartDateChange = (date: Date | null) => {
|
|
161
|
+
if (isRange && date) {
|
|
162
|
+
const newRange = { ...dateRange, start: date }
|
|
163
|
+
setDateRange(newRange)
|
|
164
|
+
setStartDateInputValue(formatDate(date))
|
|
165
|
+
setIsStartDateOpen(false)
|
|
166
|
+
if (onChange) {
|
|
167
|
+
onChange(newRange)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const handleEndDateChange = (date: Date | null) => {
|
|
173
|
+
if (isRange && date) {
|
|
174
|
+
const newRange = { ...dateRange, end: date }
|
|
175
|
+
setDateRange(newRange)
|
|
176
|
+
setEndDateInputValue(formatDate(date))
|
|
177
|
+
setIsEndDateOpen(false)
|
|
178
|
+
if (onChange) {
|
|
179
|
+
onChange(newRange)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const handleStartDateInputChange = (
|
|
185
|
+
e: React.ChangeEvent<HTMLInputElement>
|
|
186
|
+
) => {
|
|
187
|
+
if (!isRange) return
|
|
188
|
+
|
|
189
|
+
const input = e.target
|
|
190
|
+
const newValue = e.target.value
|
|
191
|
+
const selectionStart = input.selectionStart || 0
|
|
192
|
+
|
|
193
|
+
setStartDateInputValue(newValue)
|
|
194
|
+
|
|
195
|
+
const parts = newValue.split('/')
|
|
196
|
+
if (parts.length === 3) {
|
|
197
|
+
const month = parseInt(parts[0], 10)
|
|
198
|
+
const day = parseInt(parts[1], 10)
|
|
199
|
+
const year = parseInt(parts[2], 10)
|
|
200
|
+
|
|
201
|
+
if (!isNaN(month) && !isNaN(day) && !isNaN(year)) {
|
|
202
|
+
const newDate = new Date(year, month - 1, day)
|
|
203
|
+
if (
|
|
204
|
+
newDate.getMonth() === month - 1 &&
|
|
205
|
+
newDate.getDate() === day &&
|
|
206
|
+
newDate.getFullYear() === year
|
|
207
|
+
) {
|
|
208
|
+
const newRange = { ...dateRange, start: newDate }
|
|
209
|
+
setDateRange(newRange)
|
|
210
|
+
if (onChange) {
|
|
211
|
+
onChange(newRange)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
setTimeout(() => {
|
|
218
|
+
if (selectionStart <= 2) {
|
|
219
|
+
input.setSelectionRange(selectionStart, selectionStart)
|
|
220
|
+
} else if (selectionStart <= 5) {
|
|
221
|
+
input.setSelectionRange(selectionStart, selectionStart)
|
|
222
|
+
} else {
|
|
223
|
+
input.setSelectionRange(selectionStart, selectionStart)
|
|
224
|
+
}
|
|
225
|
+
}, 0)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const handleEndDateInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
229
|
+
if (!isRange) return
|
|
230
|
+
|
|
231
|
+
const input = e.target
|
|
232
|
+
const newValue = e.target.value
|
|
233
|
+
const selectionStart = input.selectionStart || 0
|
|
234
|
+
|
|
235
|
+
setEndDateInputValue(newValue)
|
|
236
|
+
|
|
237
|
+
const parts = newValue.split('/')
|
|
238
|
+
if (parts.length === 3) {
|
|
239
|
+
const month = parseInt(parts[0], 10)
|
|
240
|
+
const day = parseInt(parts[1], 10)
|
|
241
|
+
const year = parseInt(parts[2], 10)
|
|
242
|
+
|
|
243
|
+
if (!isNaN(month) && !isNaN(day) && !isNaN(year)) {
|
|
244
|
+
const newDate = new Date(year, month - 1, day)
|
|
245
|
+
if (
|
|
246
|
+
newDate.getMonth() === month - 1 &&
|
|
247
|
+
newDate.getDate() === day &&
|
|
248
|
+
newDate.getFullYear() === year
|
|
249
|
+
) {
|
|
250
|
+
const newRange = { ...dateRange, end: newDate }
|
|
251
|
+
setDateRange(newRange)
|
|
252
|
+
if (onChange) {
|
|
253
|
+
onChange(newRange)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
setTimeout(() => {
|
|
260
|
+
if (selectionStart <= 2) {
|
|
261
|
+
input.setSelectionRange(selectionStart, selectionStart)
|
|
262
|
+
} else if (selectionStart <= 5) {
|
|
263
|
+
input.setSelectionRange(selectionStart, selectionStart)
|
|
264
|
+
} else {
|
|
265
|
+
input.setSelectionRange(selectionStart, selectionStart)
|
|
266
|
+
}
|
|
267
|
+
}, 0)
|
|
268
|
+
}
|
|
269
|
+
|
|
106
270
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
107
271
|
const input = e.currentTarget
|
|
108
272
|
const selectionStart = input.selectionStart || 0
|
|
@@ -118,7 +282,120 @@ const DateField: React.FC<DateFieldProps> = ({
|
|
|
118
282
|
|
|
119
283
|
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
|
120
284
|
e.preventDefault()
|
|
121
|
-
|
|
285
|
+
|
|
286
|
+
if (!isRange) {
|
|
287
|
+
// Single date mode
|
|
288
|
+
const newDate = new Date(selectedDate || new Date())
|
|
289
|
+
const increment = e.key === 'ArrowUp' ? 1 : -1
|
|
290
|
+
|
|
291
|
+
switch (selectedPart) {
|
|
292
|
+
case 'month':
|
|
293
|
+
newDate.setMonth(newDate.getMonth() + increment)
|
|
294
|
+
break
|
|
295
|
+
case 'day':
|
|
296
|
+
newDate.setDate(newDate.getDate() + increment)
|
|
297
|
+
break
|
|
298
|
+
case 'year':
|
|
299
|
+
newDate.setFullYear(newDate.getFullYear() + increment)
|
|
300
|
+
break
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
setSelectedDate(newDate)
|
|
304
|
+
setInputValue(formatDate(newDate))
|
|
305
|
+
if (onChange) {
|
|
306
|
+
onChange(newDate)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
setTimeout(() => {
|
|
311
|
+
switch (selectedPart) {
|
|
312
|
+
case 'month':
|
|
313
|
+
input.setSelectionRange(0, 2)
|
|
314
|
+
break
|
|
315
|
+
case 'day':
|
|
316
|
+
input.setSelectionRange(3, 5)
|
|
317
|
+
break
|
|
318
|
+
case 'year':
|
|
319
|
+
input.setSelectionRange(6, 10)
|
|
320
|
+
break
|
|
321
|
+
}
|
|
322
|
+
}, 0)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const handleStartDateKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
327
|
+
if (!isRange) return
|
|
328
|
+
|
|
329
|
+
const input = e.currentTarget
|
|
330
|
+
const selectionStart = input.selectionStart || 0
|
|
331
|
+
|
|
332
|
+
let selectedPart: 'month' | 'day' | 'year'
|
|
333
|
+
if (selectionStart <= 2) {
|
|
334
|
+
selectedPart = 'month'
|
|
335
|
+
} else if (selectionStart <= 5) {
|
|
336
|
+
selectedPart = 'day'
|
|
337
|
+
} else {
|
|
338
|
+
selectedPart = 'year'
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
|
342
|
+
e.preventDefault()
|
|
343
|
+
const newDate = new Date(dateRange.start || new Date())
|
|
344
|
+
const increment = e.key === 'ArrowUp' ? 1 : -1
|
|
345
|
+
|
|
346
|
+
switch (selectedPart) {
|
|
347
|
+
case 'month':
|
|
348
|
+
newDate.setMonth(newDate.getMonth() + increment)
|
|
349
|
+
break
|
|
350
|
+
case 'day':
|
|
351
|
+
newDate.setDate(newDate.getDate() + increment)
|
|
352
|
+
break
|
|
353
|
+
case 'year':
|
|
354
|
+
newDate.setFullYear(newDate.getFullYear() + increment)
|
|
355
|
+
break
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const newRange = { ...dateRange, start: newDate }
|
|
359
|
+
setDateRange(newRange)
|
|
360
|
+
setStartDateInputValue(formatDate(newDate))
|
|
361
|
+
if (onChange) {
|
|
362
|
+
onChange(newRange)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
setTimeout(() => {
|
|
366
|
+
switch (selectedPart) {
|
|
367
|
+
case 'month':
|
|
368
|
+
input.setSelectionRange(0, 2)
|
|
369
|
+
break
|
|
370
|
+
case 'day':
|
|
371
|
+
input.setSelectionRange(3, 5)
|
|
372
|
+
break
|
|
373
|
+
case 'year':
|
|
374
|
+
input.setSelectionRange(6, 10)
|
|
375
|
+
break
|
|
376
|
+
}
|
|
377
|
+
}, 0)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const handleEndDateKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
382
|
+
if (!isRange) return
|
|
383
|
+
|
|
384
|
+
const input = e.currentTarget
|
|
385
|
+
const selectionStart = input.selectionStart || 0
|
|
386
|
+
|
|
387
|
+
let selectedPart: 'month' | 'day' | 'year'
|
|
388
|
+
if (selectionStart <= 2) {
|
|
389
|
+
selectedPart = 'month'
|
|
390
|
+
} else if (selectionStart <= 5) {
|
|
391
|
+
selectedPart = 'day'
|
|
392
|
+
} else {
|
|
393
|
+
selectedPart = 'year'
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
|
397
|
+
e.preventDefault()
|
|
398
|
+
const newDate = new Date(dateRange.end || new Date())
|
|
122
399
|
const increment = e.key === 'ArrowUp' ? 1 : -1
|
|
123
400
|
|
|
124
401
|
switch (selectedPart) {
|
|
@@ -133,10 +410,11 @@ const DateField: React.FC<DateFieldProps> = ({
|
|
|
133
410
|
break
|
|
134
411
|
}
|
|
135
412
|
|
|
136
|
-
|
|
137
|
-
|
|
413
|
+
const newRange = { ...dateRange, end: newDate }
|
|
414
|
+
setDateRange(newRange)
|
|
415
|
+
setEndDateInputValue(formatDate(newDate))
|
|
138
416
|
if (onChange) {
|
|
139
|
-
onChange(
|
|
417
|
+
onChange(newRange)
|
|
140
418
|
}
|
|
141
419
|
|
|
142
420
|
setTimeout(() => {
|
|
@@ -170,7 +448,23 @@ const DateField: React.FC<DateFieldProps> = ({
|
|
|
170
448
|
|
|
171
449
|
const handleIconClick = (e: React.MouseEvent) => {
|
|
172
450
|
e.stopPropagation()
|
|
173
|
-
|
|
451
|
+
if (!isRange) {
|
|
452
|
+
setIsOpen(true)
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const handleStartIconClick = (e: React.MouseEvent) => {
|
|
457
|
+
e.stopPropagation()
|
|
458
|
+
if (isRange) {
|
|
459
|
+
setIsStartDateOpen(true)
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const handleEndIconClick = (e: React.MouseEvent) => {
|
|
464
|
+
e.stopPropagation()
|
|
465
|
+
if (isRange) {
|
|
466
|
+
setIsEndDateOpen(true)
|
|
467
|
+
}
|
|
174
468
|
}
|
|
175
469
|
|
|
176
470
|
const calendarIcon = (
|
|
@@ -187,6 +481,94 @@ const DateField: React.FC<DateFieldProps> = ({
|
|
|
187
481
|
/>
|
|
188
482
|
)
|
|
189
483
|
|
|
484
|
+
const startCalendarIcon = (
|
|
485
|
+
<CalendarTodayIcon
|
|
486
|
+
onClick={handleStartIconClick}
|
|
487
|
+
sx={{
|
|
488
|
+
cursor: 'pointer',
|
|
489
|
+
'&:hover': {
|
|
490
|
+
opacity: 0.8,
|
|
491
|
+
},
|
|
492
|
+
fontSize: '20px',
|
|
493
|
+
color: 'black',
|
|
494
|
+
}}
|
|
495
|
+
/>
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
const endCalendarIcon = (
|
|
499
|
+
<CalendarTodayIcon
|
|
500
|
+
onClick={handleEndIconClick}
|
|
501
|
+
sx={{
|
|
502
|
+
cursor: 'pointer',
|
|
503
|
+
'&:hover': {
|
|
504
|
+
opacity: 0.8,
|
|
505
|
+
},
|
|
506
|
+
fontSize: '20px',
|
|
507
|
+
color: 'black',
|
|
508
|
+
}}
|
|
509
|
+
/>
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
if (isRange) {
|
|
513
|
+
return (
|
|
514
|
+
<Box sx={{ display: 'flex', flexDirection: 'row', gap: 2 }}>
|
|
515
|
+
<Box sx={{ flex: 1 }}>
|
|
516
|
+
<TextField
|
|
517
|
+
label={startLabel}
|
|
518
|
+
value={startDateInputValue}
|
|
519
|
+
onChange={handleStartDateInputChange}
|
|
520
|
+
endAdornment={startCalendarIcon}
|
|
521
|
+
slotProps={{
|
|
522
|
+
input: {
|
|
523
|
+
readOnly: false,
|
|
524
|
+
style: { cursor: 'text', height: '40px' },
|
|
525
|
+
onKeyDown: handleStartDateKeyDown,
|
|
526
|
+
onClick: handleClick,
|
|
527
|
+
},
|
|
528
|
+
}}
|
|
529
|
+
{...rest}
|
|
530
|
+
/>
|
|
531
|
+
<DatePicker
|
|
532
|
+
selected={dateRange.start ?? undefined}
|
|
533
|
+
onChange={handleStartDateChange}
|
|
534
|
+
dateFormat="MM/dd/yyyy"
|
|
535
|
+
customInput={<CustomInput />}
|
|
536
|
+
open={isStartDateOpen}
|
|
537
|
+
onClickOutside={() => setIsStartDateOpen(false)}
|
|
538
|
+
shouldCloseOnSelect
|
|
539
|
+
/>
|
|
540
|
+
</Box>
|
|
541
|
+
<Box sx={{ flex: 1 }}>
|
|
542
|
+
<TextField
|
|
543
|
+
label={endLabel}
|
|
544
|
+
value={endDateInputValue}
|
|
545
|
+
onChange={handleEndDateInputChange}
|
|
546
|
+
endAdornment={endCalendarIcon}
|
|
547
|
+
slotProps={{
|
|
548
|
+
input: {
|
|
549
|
+
readOnly: false,
|
|
550
|
+
style: { cursor: 'text', height: '40px' },
|
|
551
|
+
onKeyDown: handleEndDateKeyDown,
|
|
552
|
+
onClick: handleClick,
|
|
553
|
+
},
|
|
554
|
+
}}
|
|
555
|
+
{...rest}
|
|
556
|
+
/>
|
|
557
|
+
<DatePicker
|
|
558
|
+
selected={dateRange.end ?? undefined}
|
|
559
|
+
onChange={handleEndDateChange}
|
|
560
|
+
dateFormat="MM/dd/yyyy"
|
|
561
|
+
customInput={<CustomInput />}
|
|
562
|
+
open={isEndDateOpen}
|
|
563
|
+
onClickOutside={() => setIsEndDateOpen(false)}
|
|
564
|
+
shouldCloseOnSelect
|
|
565
|
+
minDate={dateRange.start ?? undefined}
|
|
566
|
+
/>
|
|
567
|
+
</Box>
|
|
568
|
+
</Box>
|
|
569
|
+
)
|
|
570
|
+
}
|
|
571
|
+
|
|
190
572
|
return (
|
|
191
573
|
<>
|
|
192
574
|
<TextField
|
|
@@ -205,7 +587,7 @@ const DateField: React.FC<DateFieldProps> = ({
|
|
|
205
587
|
{...rest}
|
|
206
588
|
/>
|
|
207
589
|
<DatePicker
|
|
208
|
-
selected={selectedDate}
|
|
590
|
+
selected={selectedDate ?? undefined}
|
|
209
591
|
onChange={handleChange}
|
|
210
592
|
dateFormat="MM/dd/yyyy"
|
|
211
593
|
customInput={<CustomInput />}
|
|
@@ -16,6 +16,9 @@ export type TextFieldProps = (
|
|
|
16
16
|
| OutlinedTextFieldProps
|
|
17
17
|
| FilledTextFieldProps
|
|
18
18
|
) & {
|
|
19
|
+
/** element rendered on the left side of the input */
|
|
20
|
+
startAdornment?: React.ReactNode
|
|
21
|
+
/** element rendered on the right side of the input */
|
|
19
22
|
endAdornment?: React.ReactNode
|
|
20
23
|
value?: string | number | readonly string[] | undefined
|
|
21
24
|
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
|
@@ -89,7 +92,7 @@ const StyledMuiTextField = styled(MuiTextField, {
|
|
|
89
92
|
}) => ({
|
|
90
93
|
'& .MuiOutlinedInput-root': {
|
|
91
94
|
minHeight: '40px',
|
|
92
|
-
height: 'auto',
|
|
95
|
+
height: 'auto',
|
|
93
96
|
backgroundColor: backgroundcolor || 'inherit',
|
|
94
97
|
color: fontcolor || 'black',
|
|
95
98
|
'& .MuiSelect-icon': {
|
|
@@ -100,9 +103,7 @@ const StyledMuiTextField = styled(MuiTextField, {
|
|
|
100
103
|
outlinecolor ||
|
|
101
104
|
(hasvalue === 'true' ? 'black' : 'rgba(0, 0, 0, 0.23)'),
|
|
102
105
|
...(shrunklabelposition === 'aboveNotch' && {
|
|
103
|
-
legend: {
|
|
104
|
-
width: '0px !important',
|
|
105
|
-
},
|
|
106
|
+
legend: { width: '0px !important' },
|
|
106
107
|
}),
|
|
107
108
|
},
|
|
108
109
|
'&:hover fieldset': {
|
|
@@ -125,9 +126,7 @@ const StyledMuiTextField = styled(MuiTextField, {
|
|
|
125
126
|
},
|
|
126
127
|
'& .MuiInputLabel-root': {
|
|
127
128
|
color: unshrunkfontcolor || 'black',
|
|
128
|
-
'&.Mui-focused': {
|
|
129
|
-
color: shrunkfontcolor || 'black',
|
|
130
|
-
},
|
|
129
|
+
'&.Mui-focused': { color: shrunkfontcolor || 'black' },
|
|
131
130
|
'&.MuiInputLabel-shrink': {
|
|
132
131
|
color: shrunkfontcolor || 'black',
|
|
133
132
|
...(shrunklabelposition === 'aboveNotch' && {
|
|
@@ -157,6 +156,7 @@ const TextField = React.memo<TextFieldProps>(props => {
|
|
|
157
156
|
error,
|
|
158
157
|
disabled,
|
|
159
158
|
sx,
|
|
159
|
+
startAdornment,
|
|
160
160
|
endAdornment,
|
|
161
161
|
textAlign = 'left',
|
|
162
162
|
slotProps: customSlotProps = {},
|
|
@@ -183,29 +183,15 @@ const TextField = React.memo<TextFieldProps>(props => {
|
|
|
183
183
|
)
|
|
184
184
|
|
|
185
185
|
const handleChange = useCallback(
|
|
186
|
-
(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
187
|
-
if (onChange) {
|
|
188
|
-
onChange(e)
|
|
189
|
-
}
|
|
190
|
-
},
|
|
186
|
+
(e: React.ChangeEvent<HTMLInputElement>) => onChange?.(e),
|
|
191
187
|
[onChange]
|
|
192
188
|
)
|
|
193
|
-
|
|
194
189
|
const handleFocus = useCallback(
|
|
195
|
-
(e: React.FocusEvent<HTMLInputElement>) =>
|
|
196
|
-
if (onFocus) {
|
|
197
|
-
onFocus(e)
|
|
198
|
-
}
|
|
199
|
-
},
|
|
190
|
+
(e: React.FocusEvent<HTMLInputElement>) => onFocus?.(e),
|
|
200
191
|
[onFocus]
|
|
201
192
|
)
|
|
202
|
-
|
|
203
193
|
const handleBlur = useCallback(
|
|
204
|
-
(e: React.FocusEvent<HTMLInputElement>) =>
|
|
205
|
-
if (onBlur) {
|
|
206
|
-
onBlur(e)
|
|
207
|
-
}
|
|
208
|
-
},
|
|
194
|
+
(e: React.FocusEvent<HTMLInputElement>) => onBlur?.(e),
|
|
209
195
|
[onBlur]
|
|
210
196
|
)
|
|
211
197
|
|
|
@@ -214,31 +200,36 @@ const TextField = React.memo<TextFieldProps>(props => {
|
|
|
214
200
|
}, [])
|
|
215
201
|
|
|
216
202
|
const mergedSlotProps = useMemo(() => {
|
|
203
|
+
const adornmentSx = {
|
|
204
|
+
color: '#000000 !important',
|
|
205
|
+
'& svg': {
|
|
206
|
+
color: '#000000 !important',
|
|
207
|
+
fill: '#000000 !important',
|
|
208
|
+
stroke: '#000000 !important',
|
|
209
|
+
},
|
|
210
|
+
}
|
|
211
|
+
|
|
217
212
|
const defaultSlotProps = {
|
|
218
213
|
input: {
|
|
219
214
|
style: inputStyle,
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
position="
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
) : undefined,
|
|
215
|
+
...(startAdornment && {
|
|
216
|
+
startAdornment: (
|
|
217
|
+
<InputAdornment position="start" sx={adornmentSx}>
|
|
218
|
+
{startAdornment}
|
|
219
|
+
</InputAdornment>
|
|
220
|
+
),
|
|
221
|
+
}),
|
|
222
|
+
...(endAdornment && {
|
|
223
|
+
endAdornment: (
|
|
224
|
+
<InputAdornment position="end" sx={adornmentSx}>
|
|
225
|
+
{endAdornment}
|
|
226
|
+
</InputAdornment>
|
|
227
|
+
),
|
|
228
|
+
}),
|
|
235
229
|
},
|
|
236
230
|
inputLabel: {
|
|
237
231
|
sx: {
|
|
238
|
-
'&.MuiInputLabel-shrink': {
|
|
239
|
-
top: '0px',
|
|
240
|
-
left: '0px',
|
|
241
|
-
},
|
|
232
|
+
'&.MuiInputLabel-shrink': { top: '0px', left: '0px' },
|
|
242
233
|
'&:not(.MuiInputLabel-shrink)': {
|
|
243
234
|
transform: 'scale(1)',
|
|
244
235
|
transformOrigin: 'top left',
|
|
@@ -260,7 +251,7 @@ const TextField = React.memo<TextFieldProps>(props => {
|
|
|
260
251
|
...(customSlotProps.inputLabel || {}),
|
|
261
252
|
},
|
|
262
253
|
}
|
|
263
|
-
}, [inputStyle, endAdornment, customSlotProps])
|
|
254
|
+
}, [inputStyle, startAdornment, endAdornment, customSlotProps])
|
|
264
255
|
|
|
265
256
|
const hasValue = Boolean(value?.toString().length).toString()
|
|
266
257
|
|
|
@@ -269,11 +260,11 @@ const TextField = React.memo<TextFieldProps>(props => {
|
|
|
269
260
|
sx={{
|
|
270
261
|
display: 'flex',
|
|
271
262
|
flexDirection: 'column',
|
|
272
|
-
justifyContent: 'flex-start',
|
|
263
|
+
justifyContent: 'flex-start',
|
|
273
264
|
width: '100%',
|
|
274
265
|
marginTop: '15px',
|
|
275
|
-
height: 'auto',
|
|
276
|
-
overflow: 'visible',
|
|
266
|
+
height: 'auto',
|
|
267
|
+
overflow: 'visible',
|
|
277
268
|
...sx,
|
|
278
269
|
}}
|
|
279
270
|
onClick={handleClick}
|
|
@@ -308,5 +299,4 @@ const TextField = React.memo<TextFieldProps>(props => {
|
|
|
308
299
|
})
|
|
309
300
|
|
|
310
301
|
TextField.displayName = 'TextField'
|
|
311
|
-
|
|
312
302
|
export default TextField
|
|
@@ -191,7 +191,7 @@ const ShowTask: React.FC<ShowTaskProps> = ({
|
|
|
191
191
|
teamMemberAssigned,
|
|
192
192
|
nextActionDate,
|
|
193
193
|
})
|
|
194
|
-
// 5) For editing an individual comment
|
|
194
|
+
// 5) For editing an individual comment's text
|
|
195
195
|
const [editingCommentId, setEditingCommentId] = useState<string | null>(null)
|
|
196
196
|
const [editingCommentText, setEditingCommentText] = useState('')
|
|
197
197
|
// 6) For selecting revisions for each comment
|
|
@@ -248,7 +248,7 @@ const ShowTask: React.FC<ShowTaskProps> = ({
|
|
|
248
248
|
setNewComment('')
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
-
/** Begin editing a comment
|
|
251
|
+
/** Begin editing a comment's text */
|
|
252
252
|
const startEditingComment = (commentId: string, currentText: string) => {
|
|
253
253
|
setEditingCommentId(commentId)
|
|
254
254
|
setEditingCommentText(currentText)
|
|
@@ -1050,7 +1050,14 @@ const ShowTask: React.FC<ShowTaskProps> = ({
|
|
|
1050
1050
|
label="Next Action Date"
|
|
1051
1051
|
value={safeParseDate(formData.nextActionDate)}
|
|
1052
1052
|
onChange={date => {
|
|
1053
|
-
if
|
|
1053
|
+
// Check if date is a DateRange or a Date
|
|
1054
|
+
if (date && 'start' in date) {
|
|
1055
|
+
// It's a DateRange, but we're not using range mode
|
|
1056
|
+
return
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// Handle Date type
|
|
1060
|
+
if (date instanceof Date) {
|
|
1054
1061
|
const mm = String(date.getMonth() + 1).padStart(2, '0')
|
|
1055
1062
|
const dd = String(date.getDate()).padStart(2, '0')
|
|
1056
1063
|
const yyyy = date.getFullYear()
|
package/src/index.ts
CHANGED
|
@@ -71,6 +71,10 @@ import AccountNumber, {
|
|
|
71
71
|
import RoutingNumber, {
|
|
72
72
|
RoutingNumberProps,
|
|
73
73
|
} from './components/Field/Number/RoutingNumber'
|
|
74
|
+
import PercentageField, {
|
|
75
|
+
PercentageFieldProps,
|
|
76
|
+
} from './components/Field/Percentage'
|
|
77
|
+
import { DateRange } from './components/Field/Date'
|
|
74
78
|
|
|
75
79
|
// Add FormDataGrid import
|
|
76
80
|
import FormDataGrid from './components/Form/DataGrid'
|
|
@@ -207,11 +211,14 @@ export type { MACAddressFieldProps }
|
|
|
207
211
|
export type { VLANFieldProps }
|
|
208
212
|
// NEW: Export AddTask / ShowTask / ManageTask
|
|
209
213
|
export { ShowTask }
|
|
214
|
+
export { PercentageField }
|
|
215
|
+
|
|
210
216
|
export type { Task }
|
|
211
217
|
export type { RawCustomer }
|
|
212
218
|
export type { CheckboxProps }
|
|
213
219
|
export type { AccountNumberProps }
|
|
214
220
|
export type { RoutingNumberProps }
|
|
221
|
+
export type { PercentageFieldProps }
|
|
215
222
|
/* -------------------------------------------------------------------------- */
|
|
216
223
|
/* Named Type Exports */
|
|
217
224
|
/* -------------------------------------------------------------------------- */
|
|
@@ -223,6 +230,7 @@ export { RoutingNumber }
|
|
|
223
230
|
export type { FormDataGridProps }
|
|
224
231
|
export type { CustomDialogProps }
|
|
225
232
|
|
|
233
|
+
export type { DateRange }
|
|
226
234
|
// 2) All DataGrid Types
|
|
227
235
|
export type { DatagridProps }
|
|
228
236
|
export type { ColumnDef, RowData }
|