@judo/components 0.1.1-alpha.0
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/LICENSE +277 -0
- package/README.md +1 -0
- package/dist/cjs/components.cjs +2 -0
- package/dist/cjs/components.cjs.map +1 -0
- package/dist/components.d.cts +111 -0
- package/dist/components.d.mts +111 -0
- package/dist/esm/components.mjs +2 -0
- package/dist/esm/components.mjs.map +1 -0
- package/package.json +63 -0
- package/src/AggregationInput.tsx +156 -0
- package/src/CustomBreadcrumb.tsx +144 -0
- package/src/CustomLink.tsx +35 -0
- package/src/CustomTablePagination.tsx +41 -0
- package/src/DropdownButton.tsx +124 -0
- package/src/PageHeader.tsx +35 -0
- package/src/TrinaryLogicCombobox.tsx +64 -0
- package/src/dialog/ConfirmationDialog.tsx +50 -0
- package/src/dialog/DialogContext.tsx +206 -0
- package/src/dialog/FilterDialog.tsx +424 -0
- package/src/dialog/PageDialog.tsx +40 -0
- package/src/dialog/RangeDialog.tsx +324 -0
- package/src/dialog/index.tsx +5 -0
- package/src/index.tsx +9 -0
- package/src/table/index.tsx +1 -0
- package/src/table/table-row-actions.tsx +86 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { createContext, useContext, useState } from 'react';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
import { ConfirmationDialog } from './ConfirmationDialog';
|
|
4
|
+
import { FilterDialog } from './FilterDialog';
|
|
5
|
+
import { PageDialog } from './PageDialog';
|
|
6
|
+
import { RangeDialog } from './RangeDialog';
|
|
7
|
+
import type {
|
|
8
|
+
ConfirmDialogProviderContext,
|
|
9
|
+
DialogProviderProps,
|
|
10
|
+
FilterDialogProviderContext,
|
|
11
|
+
OpenRangeDialogProps,
|
|
12
|
+
PageDialogProviderContext,
|
|
13
|
+
RangeDialogProviderContext,
|
|
14
|
+
} from '@judo/components-api';
|
|
15
|
+
import type { Filter, FilterOption } from '@judo/components-api';
|
|
16
|
+
import type { JudoStored, QueryCustomizer } from '@judo/data-api-common';
|
|
17
|
+
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
const PageDialogContextState = createContext<PageDialogProviderContext>();
|
|
20
|
+
|
|
21
|
+
// @ts-ignore
|
|
22
|
+
const ConfirmDialogContextState = createContext<ConfirmDialogProviderContext>();
|
|
23
|
+
|
|
24
|
+
// @ts-ignore
|
|
25
|
+
const RangeDialogContextState = createContext<RangeDialogProviderContext>();
|
|
26
|
+
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
const FilterDialogContextState = createContext<FilterDialogProviderContext>();
|
|
29
|
+
|
|
30
|
+
const usePageDialog = () => {
|
|
31
|
+
const context = useContext(PageDialogContextState);
|
|
32
|
+
|
|
33
|
+
if (context === undefined) {
|
|
34
|
+
throw new Error('useConfirmDialog was used outside of its Provider');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return context;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const useConfirmDialog = () => {
|
|
41
|
+
const context = useContext(ConfirmDialogContextState);
|
|
42
|
+
|
|
43
|
+
if (context === undefined) {
|
|
44
|
+
throw new Error('useConfirmDialog was used outside of its Provider');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return context;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const useRangeDialog = () => {
|
|
51
|
+
const context = useContext(RangeDialogContextState);
|
|
52
|
+
|
|
53
|
+
if (context === undefined) {
|
|
54
|
+
throw new Error('useRangeDialog was used outside of its Provider');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return context;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const useFilterDialog = () => {
|
|
61
|
+
const context = useContext(FilterDialogContextState);
|
|
62
|
+
|
|
63
|
+
if (context === undefined) {
|
|
64
|
+
throw new Error('useFilterDialog was used outside of its Provider');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return context;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const DialogProvider = ({ children }: DialogProviderProps) => {
|
|
71
|
+
// Page Dialog
|
|
72
|
+
const [isOpenPageDialog, setIsOpenPageDialog] = useState(false);
|
|
73
|
+
const [pageDialog, setPageDialog] = useState<ReactNode>();
|
|
74
|
+
|
|
75
|
+
const handleClosePageDialog = () => {
|
|
76
|
+
setIsOpenPageDialog(false);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const handleOpenPageDialog = async (page: ReactNode) => {
|
|
80
|
+
setIsOpenPageDialog(true);
|
|
81
|
+
return new Promise<void>((resolve) => {
|
|
82
|
+
setPageDialog(<PageDialog page={page} handleClose={handleClosePageDialog} open={true} resolve={resolve} />);
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const pageDialogContext: PageDialogProviderContext = {
|
|
87
|
+
openPageDialog: handleOpenPageDialog,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Range Dialog
|
|
91
|
+
const [isOpenRangeDialog, setIsOpenRangeDialog] = useState(false);
|
|
92
|
+
const [rangeDialog, setRangeDialog] = useState<ReactNode>();
|
|
93
|
+
|
|
94
|
+
const handleCloseRangeDialog = () => {
|
|
95
|
+
setIsOpenRangeDialog(false);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const handleOpenRangeDialog = async <T extends JudoStored<T>, U extends QueryCustomizer<T>>({
|
|
99
|
+
columns,
|
|
100
|
+
defaultSortField,
|
|
101
|
+
rangeCall,
|
|
102
|
+
single = false,
|
|
103
|
+
alreadySelectedItems,
|
|
104
|
+
filterOptions,
|
|
105
|
+
initialQueryCustomizer,
|
|
106
|
+
}: OpenRangeDialogProps<T, U>) => {
|
|
107
|
+
setIsOpenRangeDialog(true);
|
|
108
|
+
|
|
109
|
+
return new Promise<T[] | T>((resolve) => {
|
|
110
|
+
setRangeDialog(
|
|
111
|
+
<RangeDialog<T, U>
|
|
112
|
+
handleClose={handleCloseRangeDialog}
|
|
113
|
+
open={true}
|
|
114
|
+
resolve={resolve}
|
|
115
|
+
columns={columns}
|
|
116
|
+
defaultSortField={defaultSortField}
|
|
117
|
+
rangeCall={rangeCall}
|
|
118
|
+
single={single}
|
|
119
|
+
alreadySelectedItems={alreadySelectedItems}
|
|
120
|
+
filterOptions={filterOptions}
|
|
121
|
+
initalQueryCustomizer={initialQueryCustomizer}
|
|
122
|
+
/>,
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const customDialogContext: RangeDialogProviderContext = {
|
|
128
|
+
openRangeDialog: handleOpenRangeDialog,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Confirmation Dialog
|
|
132
|
+
const [isOpenConfirmDialog, setIsOpenConfirmDialog] = useState(false);
|
|
133
|
+
const [confirmDialog, setConfirmDialog] = useState<ReactNode>();
|
|
134
|
+
|
|
135
|
+
const handleCloseConfirmDialog = () => {
|
|
136
|
+
setIsOpenConfirmDialog(false);
|
|
137
|
+
return false;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const handleOpenConfirmDialog = async (confirmationMessage: string | ReactNode, title?: string | ReactNode) => {
|
|
141
|
+
setIsOpenConfirmDialog(true);
|
|
142
|
+
|
|
143
|
+
return new Promise<boolean>((resolve) => {
|
|
144
|
+
setConfirmDialog(
|
|
145
|
+
<ConfirmationDialog
|
|
146
|
+
confirmationMessage={confirmationMessage}
|
|
147
|
+
title={title}
|
|
148
|
+
handleClose={handleCloseConfirmDialog}
|
|
149
|
+
open={true}
|
|
150
|
+
resolve={resolve}
|
|
151
|
+
/>,
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const confirmDialogContext: ConfirmDialogProviderContext = {
|
|
157
|
+
openConfirmDialog: handleOpenConfirmDialog,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Filter dialog
|
|
161
|
+
const [isOpenFilterDialog, setIsOpenFilterDialog] = useState(false);
|
|
162
|
+
const [filterDialog, setFilterDialog] = useState<ReactNode>();
|
|
163
|
+
|
|
164
|
+
const handleCloseFilterDialog = () => {
|
|
165
|
+
setIsOpenFilterDialog(false);
|
|
166
|
+
return false;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const handleOpenFilterDialog = async (filterOptions: FilterOption[], filters?: Filter[]) => {
|
|
170
|
+
setIsOpenFilterDialog(true);
|
|
171
|
+
|
|
172
|
+
return new Promise<Filter[]>((resolve) => {
|
|
173
|
+
setFilterDialog(
|
|
174
|
+
<FilterDialog
|
|
175
|
+
filters={filters}
|
|
176
|
+
filterOptions={filterOptions}
|
|
177
|
+
handleClose={handleCloseFilterDialog}
|
|
178
|
+
open={true}
|
|
179
|
+
resolve={resolve}
|
|
180
|
+
/>,
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const filterDialogContext: FilterDialogProviderContext = {
|
|
186
|
+
openFilterDialog: handleOpenFilterDialog,
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<PageDialogContextState.Provider value={pageDialogContext}>
|
|
191
|
+
<ConfirmDialogContextState.Provider value={confirmDialogContext}>
|
|
192
|
+
<RangeDialogContextState.Provider value={customDialogContext}>
|
|
193
|
+
<FilterDialogContextState.Provider value={filterDialogContext}>
|
|
194
|
+
{children}
|
|
195
|
+
{isOpenPageDialog && pageDialog}
|
|
196
|
+
{isOpenConfirmDialog && confirmDialog}
|
|
197
|
+
{isOpenRangeDialog && rangeDialog}
|
|
198
|
+
{isOpenFilterDialog && filterDialog}
|
|
199
|
+
</FilterDialogContextState.Provider>
|
|
200
|
+
</RangeDialogContextState.Provider>
|
|
201
|
+
</ConfirmDialogContextState.Provider>
|
|
202
|
+
</PageDialogContextState.Provider>
|
|
203
|
+
);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export { DialogProvider, useConfirmDialog, useRangeDialog, useFilterDialog, usePageDialog };
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import { mdiCalendarClock, mdiCalendarMonth, mdiFormatTextVariant, mdiNumeric } from '@mdi/js';
|
|
2
|
+
import Icon from '@mdi/react';
|
|
3
|
+
import { Close } from '@mui/icons-material';
|
|
4
|
+
import {
|
|
5
|
+
Dialog,
|
|
6
|
+
DialogTitle,
|
|
7
|
+
DialogContent,
|
|
8
|
+
DialogContentText,
|
|
9
|
+
DialogActions,
|
|
10
|
+
Button,
|
|
11
|
+
Slide,
|
|
12
|
+
Box,
|
|
13
|
+
Container,
|
|
14
|
+
Grid,
|
|
15
|
+
TextField,
|
|
16
|
+
MenuItem,
|
|
17
|
+
Checkbox,
|
|
18
|
+
FormControlLabel,
|
|
19
|
+
InputAdornment,
|
|
20
|
+
IconButton,
|
|
21
|
+
Typography,
|
|
22
|
+
} from '@mui/material';
|
|
23
|
+
import type { TransitionProps } from '@mui/material/transitions';
|
|
24
|
+
import { DatePicker, DateTimePicker } from '@mui/x-date-pickers';
|
|
25
|
+
import { forwardRef, useEffect, useRef, useState } from 'react';
|
|
26
|
+
import type { ChangeEvent, ReactElement, Ref } from 'react';
|
|
27
|
+
import type {
|
|
28
|
+
Filter,
|
|
29
|
+
FilterDialogProps,
|
|
30
|
+
FilterInputProps,
|
|
31
|
+
FilterOperatorProps,
|
|
32
|
+
FilterProps,
|
|
33
|
+
Operation,
|
|
34
|
+
} from '@judo/components-api';
|
|
35
|
+
import { FilterType } from '@judo/components-api';
|
|
36
|
+
import { dateToJudoDateString, exists } from '@judo/utilities';
|
|
37
|
+
import { mainContainerPadding } from '@judo/theme';
|
|
38
|
+
import { _BooleanOperation, _EnumerationOperation, _NumericOperation, _StringOperation } from '@judo/data-api-common';
|
|
39
|
+
import { DropdownButton } from '../DropdownButton';
|
|
40
|
+
import TrinaryLogicCombobox from '../TrinaryLogicCombobox';
|
|
41
|
+
|
|
42
|
+
const getDefaultOperator = (filterType: FilterType) => {
|
|
43
|
+
switch (filterType) {
|
|
44
|
+
case FilterType.boolean:
|
|
45
|
+
return _BooleanOperation['equals'];
|
|
46
|
+
case FilterType.date:
|
|
47
|
+
return _NumericOperation['equal'];
|
|
48
|
+
case FilterType.dateTime:
|
|
49
|
+
return _NumericOperation['equal'];
|
|
50
|
+
// case FilterType.time:
|
|
51
|
+
// return _NumericOperation['equal'];
|
|
52
|
+
case FilterType.enumeration:
|
|
53
|
+
return _EnumerationOperation['equals'];
|
|
54
|
+
case FilterType.numeric:
|
|
55
|
+
return _NumericOperation['equal'];
|
|
56
|
+
case FilterType.string:
|
|
57
|
+
return _StringOperation['equal'];
|
|
58
|
+
case FilterType.trinaryLogic:
|
|
59
|
+
return _BooleanOperation['equals'];
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const getOperationEnumValue = (filter: Filter, operator: string) => {
|
|
64
|
+
switch (filter.filterOption.filterType) {
|
|
65
|
+
case FilterType.boolean:
|
|
66
|
+
return _BooleanOperation[operator as keyof typeof _BooleanOperation];
|
|
67
|
+
case FilterType.date:
|
|
68
|
+
return _NumericOperation[operator as keyof typeof _NumericOperation];
|
|
69
|
+
case FilterType.dateTime:
|
|
70
|
+
return _NumericOperation[operator as keyof typeof _NumericOperation];
|
|
71
|
+
// case FilterType.time:
|
|
72
|
+
// return _NumericOperation[operator as keyof typeof _NumericOperation];
|
|
73
|
+
case FilterType.enumeration:
|
|
74
|
+
return _EnumerationOperation[operator as keyof typeof _BooleanOperation];
|
|
75
|
+
case FilterType.numeric:
|
|
76
|
+
return _NumericOperation[operator as keyof typeof _NumericOperation];
|
|
77
|
+
case FilterType.string:
|
|
78
|
+
return _StringOperation[operator as keyof typeof _StringOperation];
|
|
79
|
+
case FilterType.trinaryLogic:
|
|
80
|
+
return _BooleanOperation[operator as keyof typeof _BooleanOperation];
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const getOperatorsByFilter = (filter: Filter): string[] => {
|
|
85
|
+
switch (filter.filterOption.filterType) {
|
|
86
|
+
case FilterType.boolean:
|
|
87
|
+
return Object.values(_BooleanOperation);
|
|
88
|
+
case FilterType.date:
|
|
89
|
+
return Object.values(_NumericOperation);
|
|
90
|
+
case FilterType.dateTime:
|
|
91
|
+
return Object.values(_NumericOperation);
|
|
92
|
+
// case FilterType.time:
|
|
93
|
+
// return Object.values(_NumericOperation);
|
|
94
|
+
case FilterType.enumeration:
|
|
95
|
+
return Object.values(_EnumerationOperation);
|
|
96
|
+
case FilterType.numeric:
|
|
97
|
+
return Object.values(_NumericOperation);
|
|
98
|
+
case FilterType.string:
|
|
99
|
+
return Object.values(_StringOperation);
|
|
100
|
+
case FilterType.trinaryLogic:
|
|
101
|
+
return Object.values(_BooleanOperation);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const FilterOperator = ({ filter, setFilterOperator }: FilterOperatorProps) => {
|
|
106
|
+
const onChangeHandler = (event: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
|
|
107
|
+
setFilterOperator(filter, getOperationEnumValue(filter, event.target.value));
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<TextField
|
|
112
|
+
name={'operation'}
|
|
113
|
+
id={'operation'}
|
|
114
|
+
label={'Operation'}
|
|
115
|
+
select
|
|
116
|
+
value={filter.filterBy.operator}
|
|
117
|
+
onChange={onChangeHandler}
|
|
118
|
+
>
|
|
119
|
+
{getOperatorsByFilter(filter).map((item) => (
|
|
120
|
+
<MenuItem key={item} value={item}>
|
|
121
|
+
{/* TODO: do not forget localization here*/}
|
|
122
|
+
{item}
|
|
123
|
+
</MenuItem>
|
|
124
|
+
))}
|
|
125
|
+
</TextField>
|
|
126
|
+
);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const FilterInput = ({ filter, setFilterValue }: FilterInputProps) => {
|
|
130
|
+
if (filter.filterOption.filterType === FilterType.enumeration && !exists(filter.filterOption.enumValues)) {
|
|
131
|
+
throw new Error(`Missing enumValues from FilterOptions of ${filter.filterOption.attributeName}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<>
|
|
136
|
+
{(() => {
|
|
137
|
+
switch (filter.filterOption.filterType) {
|
|
138
|
+
case FilterType.boolean:
|
|
139
|
+
return (
|
|
140
|
+
<FormControlLabel
|
|
141
|
+
control={
|
|
142
|
+
<Checkbox
|
|
143
|
+
checked={!!filter.filterBy.value}
|
|
144
|
+
onChange={(event) => setFilterValue(filter, !!event.target.value)}
|
|
145
|
+
/>
|
|
146
|
+
}
|
|
147
|
+
label={filter.filterOption.attributeName}
|
|
148
|
+
/>
|
|
149
|
+
);
|
|
150
|
+
case FilterType.date:
|
|
151
|
+
return (
|
|
152
|
+
<DatePicker
|
|
153
|
+
renderInput={(props) => <TextField {...props} />}
|
|
154
|
+
label={filter.filterOption.attributeName}
|
|
155
|
+
value={filter.filterBy.value ?? null}
|
|
156
|
+
onChange={(newValue) => setFilterValue(filter, dateToJudoDateString(newValue))}
|
|
157
|
+
InputProps={{
|
|
158
|
+
startAdornment: (
|
|
159
|
+
<InputAdornment position="start">
|
|
160
|
+
<Icon path={mdiCalendarMonth} size={1} />
|
|
161
|
+
</InputAdornment>
|
|
162
|
+
),
|
|
163
|
+
}}
|
|
164
|
+
/>
|
|
165
|
+
);
|
|
166
|
+
case FilterType.dateTime:
|
|
167
|
+
return (
|
|
168
|
+
<DateTimePicker
|
|
169
|
+
renderInput={(props) => <TextField {...props} />}
|
|
170
|
+
label={filter.filterOption.attributeName}
|
|
171
|
+
value={filter.filterBy.value ?? null}
|
|
172
|
+
onChange={(newValue) => setFilterValue(filter, newValue)}
|
|
173
|
+
InputProps={{
|
|
174
|
+
startAdornment: (
|
|
175
|
+
<InputAdornment position="start">
|
|
176
|
+
<Icon path={mdiCalendarClock} size={1} />
|
|
177
|
+
</InputAdornment>
|
|
178
|
+
),
|
|
179
|
+
}}
|
|
180
|
+
/>
|
|
181
|
+
);
|
|
182
|
+
// case FilterType.time:
|
|
183
|
+
// return (
|
|
184
|
+
// <TextField
|
|
185
|
+
// label={filter.filterOption.attributeName}
|
|
186
|
+
// value={filter.filterBy.value}
|
|
187
|
+
// onChange={(event) => setFilterValue(filter, event.target.value)}
|
|
188
|
+
// InputProps={{
|
|
189
|
+
// startAdornment: (
|
|
190
|
+
// <InputAdornment position="start">
|
|
191
|
+
// <Icon path={mdiClockOutline} size={1} />
|
|
192
|
+
// </InputAdornment>
|
|
193
|
+
// ),
|
|
194
|
+
// }}
|
|
195
|
+
// />
|
|
196
|
+
// );
|
|
197
|
+
case FilterType.enumeration:
|
|
198
|
+
return (
|
|
199
|
+
<TextField
|
|
200
|
+
label={filter.filterOption.attributeName}
|
|
201
|
+
value={filter.filterBy.value}
|
|
202
|
+
select
|
|
203
|
+
onChange={(event) => setFilterValue(filter, event.target.value)}
|
|
204
|
+
>
|
|
205
|
+
{filter.filterOption.enumValues?.map((item) => (
|
|
206
|
+
<MenuItem key={item} value={item}>
|
|
207
|
+
{item}
|
|
208
|
+
</MenuItem>
|
|
209
|
+
))}
|
|
210
|
+
</TextField>
|
|
211
|
+
);
|
|
212
|
+
case FilterType.numeric:
|
|
213
|
+
return (
|
|
214
|
+
<TextField
|
|
215
|
+
label={filter.filterOption.attributeName}
|
|
216
|
+
type="number"
|
|
217
|
+
value={filter.filterBy.value}
|
|
218
|
+
onChange={(event) => setFilterValue(filter, Number(event.target.value))}
|
|
219
|
+
InputProps={{
|
|
220
|
+
startAdornment: (
|
|
221
|
+
<InputAdornment position="start">
|
|
222
|
+
<Icon path={mdiNumeric} size={1} />
|
|
223
|
+
</InputAdornment>
|
|
224
|
+
),
|
|
225
|
+
}}
|
|
226
|
+
/>
|
|
227
|
+
);
|
|
228
|
+
case FilterType.string:
|
|
229
|
+
return (
|
|
230
|
+
<TextField
|
|
231
|
+
label={filter.filterOption.attributeName}
|
|
232
|
+
value={filter.filterBy.value}
|
|
233
|
+
onChange={(event) => setFilterValue(filter, event.target.value)}
|
|
234
|
+
InputProps={{
|
|
235
|
+
startAdornment: (
|
|
236
|
+
<InputAdornment position="start">
|
|
237
|
+
<Icon path={mdiFormatTextVariant} size={1} />
|
|
238
|
+
</InputAdornment>
|
|
239
|
+
),
|
|
240
|
+
}}
|
|
241
|
+
/>
|
|
242
|
+
);
|
|
243
|
+
case FilterType.trinaryLogic:
|
|
244
|
+
return (
|
|
245
|
+
<TrinaryLogicCombobox
|
|
246
|
+
label={filter.filterOption.attributeName}
|
|
247
|
+
value={filter.filterBy.value}
|
|
248
|
+
onChange={(value) => setFilterValue(filter, value)}
|
|
249
|
+
/>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
})()}
|
|
253
|
+
</>
|
|
254
|
+
);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const FilterRow = ({ filter, closeHandler, setFilterOperator, setFilterValue }: FilterProps) => {
|
|
258
|
+
return (
|
|
259
|
+
<Grid item container spacing={2} alignItems={'center'}>
|
|
260
|
+
<Grid item xs={4}>
|
|
261
|
+
{filter && <FilterOperator filter={filter} setFilterOperator={setFilterOperator} />}
|
|
262
|
+
</Grid>
|
|
263
|
+
<Grid item xs={7}>
|
|
264
|
+
{filter && <FilterInput filter={filter} setFilterValue={setFilterValue} />}
|
|
265
|
+
</Grid>
|
|
266
|
+
<Grid item xs={1}>
|
|
267
|
+
<IconButton onClick={() => closeHandler(filter)}>
|
|
268
|
+
<Close />
|
|
269
|
+
</IconButton>
|
|
270
|
+
</Grid>
|
|
271
|
+
</Grid>
|
|
272
|
+
);
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const Transition = forwardRef(function Transition(
|
|
276
|
+
props: TransitionProps & {
|
|
277
|
+
children: ReactElement<any, any>;
|
|
278
|
+
},
|
|
279
|
+
ref: Ref<unknown>,
|
|
280
|
+
) {
|
|
281
|
+
return <Slide direction="left" ref={ref} {...props} />;
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
export const FilterDialog = ({ filters, filterOptions, resolve, open, handleClose }: FilterDialogProps) => {
|
|
285
|
+
const descriptionElementRef = useRef<HTMLElement>(null);
|
|
286
|
+
const [tempFilters, setTempFilters] = useState<Filter[]>(filters ?? []);
|
|
287
|
+
|
|
288
|
+
useEffect(() => {
|
|
289
|
+
if (open) {
|
|
290
|
+
const { current: descriptionElement } = descriptionElementRef;
|
|
291
|
+
if (descriptionElement !== null) {
|
|
292
|
+
descriptionElement.focus();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}, [open]);
|
|
296
|
+
|
|
297
|
+
const updateFilterValue = (filter: Filter, value: any) => {
|
|
298
|
+
setTempFilters((prevTempFilters) => {
|
|
299
|
+
return prevTempFilters.map((tempFilter) => {
|
|
300
|
+
if (filter.id === tempFilter.id) {
|
|
301
|
+
return {
|
|
302
|
+
...tempFilter,
|
|
303
|
+
filterBy: { value: value, operator: tempFilter.filterBy.operator },
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return tempFilter;
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const updateFilterOperator = (filter: Filter, operator: Operation) => {
|
|
313
|
+
setTempFilters((prevTempFilters) => {
|
|
314
|
+
return prevTempFilters.map((tempFilter) => {
|
|
315
|
+
if (filter.id === tempFilter.id) {
|
|
316
|
+
return {
|
|
317
|
+
...tempFilter,
|
|
318
|
+
filterBy: { value: tempFilter.filterBy.value, operator: operator },
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return tempFilter;
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const filterCloseHandler = (filter: Filter) => {
|
|
328
|
+
setTempFilters((prevTempFilters) => [...prevTempFilters.filter((tempFilter) => tempFilter.id !== filter.id)]);
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const cancel = () => {
|
|
332
|
+
handleClose();
|
|
333
|
+
resolve(undefined);
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const ok = () => {
|
|
337
|
+
handleClose();
|
|
338
|
+
resolve(tempFilters);
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
return (
|
|
342
|
+
<Dialog
|
|
343
|
+
open={open}
|
|
344
|
+
onClose={cancel}
|
|
345
|
+
scroll="paper"
|
|
346
|
+
TransitionComponent={Transition}
|
|
347
|
+
disableEnforceFocus
|
|
348
|
+
fullWidth
|
|
349
|
+
maxWidth="sm"
|
|
350
|
+
sx={{
|
|
351
|
+
'& .MuiDialog-container': {
|
|
352
|
+
justifyContent: 'flex-end',
|
|
353
|
+
},
|
|
354
|
+
}}
|
|
355
|
+
PaperProps={{
|
|
356
|
+
sx: {
|
|
357
|
+
m: 0,
|
|
358
|
+
height: '100%',
|
|
359
|
+
},
|
|
360
|
+
}}
|
|
361
|
+
>
|
|
362
|
+
<DialogTitle id="scroll-dialog-title">
|
|
363
|
+
<Typography component="span" color="text.primary" variant="h5">
|
|
364
|
+
Filters
|
|
365
|
+
</Typography>
|
|
366
|
+
</DialogTitle>
|
|
367
|
+
<DialogContent dividers={true}>
|
|
368
|
+
<DialogContentText id="scroll-dialog-description" ref={descriptionElementRef} tabIndex={-1}>
|
|
369
|
+
<Container component="main" maxWidth="xs">
|
|
370
|
+
<Box sx={mainContainerPadding}>
|
|
371
|
+
<Grid container spacing={2}>
|
|
372
|
+
{tempFilters.map((filter) => (
|
|
373
|
+
<FilterRow
|
|
374
|
+
key={filter.id}
|
|
375
|
+
filter={filter}
|
|
376
|
+
closeHandler={filterCloseHandler}
|
|
377
|
+
setFilterOperator={updateFilterOperator}
|
|
378
|
+
setFilterValue={updateFilterValue}
|
|
379
|
+
/>
|
|
380
|
+
))}
|
|
381
|
+
<Grid item container>
|
|
382
|
+
<DropdownButton
|
|
383
|
+
fullWidth={true}
|
|
384
|
+
showDropdownIcon={false}
|
|
385
|
+
menuItems={filterOptions.map((filterOption) => {
|
|
386
|
+
return {
|
|
387
|
+
label: filterOption.label ?? filterOption.attributeName,
|
|
388
|
+
onClick: () =>
|
|
389
|
+
setTempFilters((prevTempFilters) => [
|
|
390
|
+
...prevTempFilters,
|
|
391
|
+
{
|
|
392
|
+
id: prevTempFilters.length,
|
|
393
|
+
filterOption: {
|
|
394
|
+
attributeName: filterOption.attributeName,
|
|
395
|
+
label: filterOption.label,
|
|
396
|
+
filterType: filterOption.filterType,
|
|
397
|
+
},
|
|
398
|
+
filterBy: {
|
|
399
|
+
operator: getDefaultOperator(filterOption.filterType),
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
]),
|
|
403
|
+
};
|
|
404
|
+
})}
|
|
405
|
+
>
|
|
406
|
+
Add new filter
|
|
407
|
+
</DropdownButton>
|
|
408
|
+
</Grid>
|
|
409
|
+
</Grid>
|
|
410
|
+
</Box>
|
|
411
|
+
</Container>
|
|
412
|
+
</DialogContentText>
|
|
413
|
+
</DialogContent>
|
|
414
|
+
<DialogActions>
|
|
415
|
+
<Button fullWidth variant="outlined" onClick={cancel}>
|
|
416
|
+
Cancel
|
|
417
|
+
</Button>
|
|
418
|
+
<Button fullWidth onClick={ok}>
|
|
419
|
+
Apply {'(' + tempFilters.length + ')'}
|
|
420
|
+
</Button>
|
|
421
|
+
</DialogActions>
|
|
422
|
+
</Dialog>
|
|
423
|
+
);
|
|
424
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Dialog, DialogContent, DialogContentText, DialogActions, Button } from '@mui/material';
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
interface PageDialogProps {
|
|
6
|
+
page: ReactNode;
|
|
7
|
+
open: boolean;
|
|
8
|
+
handleClose: () => void;
|
|
9
|
+
resolve: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const PageDialog = ({ page, open, handleClose, resolve }: PageDialogProps) => {
|
|
13
|
+
const descriptionElementRef = useRef<HTMLElement>(null);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (open) {
|
|
16
|
+
const { current: descriptionElement } = descriptionElementRef;
|
|
17
|
+
if (descriptionElement !== null) {
|
|
18
|
+
descriptionElement.focus();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}, [open]);
|
|
22
|
+
|
|
23
|
+
const ok = () => {
|
|
24
|
+
resolve();
|
|
25
|
+
handleClose();
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Dialog open={open} onClose={ok} scroll="paper">
|
|
30
|
+
<DialogContent dividers={true}>
|
|
31
|
+
<DialogContentText ref={descriptionElementRef} tabIndex={-1}>
|
|
32
|
+
{page}
|
|
33
|
+
</DialogContentText>
|
|
34
|
+
</DialogContent>
|
|
35
|
+
<DialogActions>
|
|
36
|
+
<Button onClick={ok}>Ok</Button>
|
|
37
|
+
</DialogActions>
|
|
38
|
+
</Dialog>
|
|
39
|
+
);
|
|
40
|
+
};
|