Skip to content

Commit 668fad9

Browse files
authored
Merge pull request #1928 from tekdi/release-1.11.0
Release 1.11.0 to admin prod
2 parents 4143045 + 352e97a commit 668fad9

File tree

36 files changed

+2419
-1338
lines changed

36 files changed

+2419
-1338
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,6 @@ apps/admin-app-repo/build/
5858

5959
#add below for generic editor
6060
mfes/workspace/public/content-plugins/
61-
mfes/workspace/public/generic-editor/
61+
mfes/workspace/public/generic-editor/
62+
.cursor/rules/nx-rules.mdc
63+
.github/instructions/nx.instructions.md

apps/admin-app-repo/src/components/DynamicForm/DynamicForm.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Box } from '@mui/material';
1212
import { TextField, Container, Typography } from '@mui/material';
1313
import { useTranslation } from 'next-i18next';
1414
import _ from 'lodash'; // Lodash for deep comparison
15+
import AutoCompleteMultiSelectWidget from './RJSFWidget/AutoCompleteMultiSelectWidget';
1516
import CustomMultiSelectWidget from './RJSFWidget/CustomMultiSelectWidget';
1617
import CustomCheckboxWidget from './RJSFWidget/CustomCheckboxWidget';
1718
import CustomDateWidget from './RJSFWidget/CustomDateWidget';
@@ -105,6 +106,7 @@ const DynamicForm = forwardRef(({
105106

106107
const widgets = {
107108
CustomMultiSelectWidget,
109+
AutoCompleteMultiSelectWidget,
108110
CustomCheckboxWidget,
109111
CustomDateWidget,
110112
SearchTextFieldWidget,
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
// @ts-nocheck
2+
import React, { useEffect, useState, useMemo } from 'react';
3+
import { WidgetProps } from '@rjsf/utils';
4+
import {
5+
Autocomplete,
6+
TextField,
7+
Chip,
8+
Checkbox,
9+
FormControl,
10+
FormHelperText,
11+
InputLabel,
12+
} from '@mui/material';
13+
import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
14+
import CheckBoxIcon from '@mui/icons-material/CheckBox';
15+
import { useTranslation } from 'libs/shared-lib-v2/src/lib/context/LanguageContext';
16+
17+
const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
18+
const checkedIcon = <CheckBoxIcon fontSize="small" />;
19+
20+
const AutoCompleteMultiSelectWidget = ({
21+
id,
22+
options,
23+
value = [],
24+
required,
25+
label,
26+
onChange,
27+
schema,
28+
uiSchema,
29+
rawErrors = [],
30+
}: WidgetProps) => {
31+
const { enumOptions = [] } = options;
32+
const maxSelections = schema.maxSelection || enumOptions.length;
33+
const { t } = useTranslation();
34+
35+
const selectedValues = Array.isArray(value) ? value : [];
36+
const isDisabled = uiSchema?.['ui:disabled'] === true;
37+
38+
// Convert enumOptions to the format expected by Autocomplete
39+
const optionsList = useMemo(() => {
40+
return enumOptions
41+
.filter((option) => option.value !== 'Select')
42+
.map((option) => ({
43+
value: option.value,
44+
label: t(`FORM.${option.label}`, { defaultValue: option.label }),
45+
}));
46+
}, [enumOptions, t]);
47+
48+
// Get selected options based on current values
49+
const selectedOptions = useMemo(() => {
50+
return optionsList.filter((option) =>
51+
selectedValues.includes(option.value)
52+
);
53+
}, [optionsList, selectedValues]);
54+
55+
// Filter options based on search input
56+
const [inputValue, setInputValue] = useState('');
57+
const [open, setOpen] = useState(false);
58+
59+
const filteredOptions = useMemo(() => {
60+
if (!inputValue) return optionsList;
61+
62+
return optionsList.filter((option) =>
63+
option.label.toLowerCase().includes(inputValue.toLowerCase())
64+
);
65+
}, [optionsList, inputValue]);
66+
67+
const handleChange = (event: any, newValue: any) => {
68+
if (Array.isArray(newValue)) {
69+
// Limit selections based on maxSelections
70+
const limitedValue = newValue.slice(0, maxSelections);
71+
const values = limitedValue.map((option) => option.value);
72+
onChange(values.length > 0 ? values : []);
73+
74+
// Close dropdown only if max selections reached
75+
if (values.length >= maxSelections) {
76+
setTimeout(() => setOpen(false), 100);
77+
}
78+
} else {
79+
onChange([]);
80+
}
81+
};
82+
83+
const handleInputChange = (event: any, newInputValue: string) => {
84+
setInputValue(newInputValue);
85+
};
86+
87+
// Select All functionality
88+
const handleSelectAll = () => {
89+
if (selectedValues.length === optionsList.length) {
90+
onChange([]);
91+
// Keep dropdown open when deselecting all
92+
} else {
93+
const allValues = optionsList.map((option) => option.value);
94+
onChange(allValues.slice(0, maxSelections));
95+
// Close dropdown after selecting all (when max selections reached)
96+
if (allValues.length >= maxSelections) {
97+
setTimeout(() => setOpen(false), 100);
98+
}
99+
}
100+
};
101+
102+
const isAllSelected = selectedValues.length === optionsList.length;
103+
104+
// Limit the number of visible chips to prevent size increase
105+
const maxVisibleChips = 2;
106+
const visibleChips = selectedOptions.slice(0, maxVisibleChips);
107+
const remainingCount = selectedOptions.length - maxVisibleChips;
108+
109+
return (
110+
<FormControl
111+
fullWidth
112+
error={rawErrors.length > 0}
113+
required={required}
114+
disabled={isDisabled}
115+
>
116+
<Autocomplete
117+
id={id}
118+
multiple
119+
options={filteredOptions}
120+
value={selectedOptions}
121+
onChange={handleChange}
122+
onInputChange={handleInputChange}
123+
inputValue={inputValue}
124+
open={open}
125+
onOpen={() => setOpen(true)}
126+
onClose={() => setOpen(false)}
127+
getOptionLabel={(option) => option.label}
128+
isOptionEqualToValue={(option, value) => option.value === value.value}
129+
disabled={isDisabled}
130+
disableCloseOnSelect={true}
131+
renderTags={(value, getTagProps) => (
132+
<>
133+
{visibleChips.map((option, index) => (
134+
<Chip
135+
variant="outlined"
136+
label={option.label}
137+
{...getTagProps({ index })}
138+
key={option.value}
139+
/>
140+
))}
141+
{remainingCount > 0 && (
142+
<Chip
143+
variant="outlined"
144+
label={`+${remainingCount} more`}
145+
style={{
146+
backgroundColor: '#f5f5f5',
147+
color: '#666',
148+
cursor: 'default',
149+
}}
150+
onDelete={undefined}
151+
key="remaining-count"
152+
/>
153+
)}
154+
</>
155+
)}
156+
renderOption={(props, option, { selected }) => (
157+
<li {...props} key={option.value}>
158+
<Checkbox
159+
icon={icon}
160+
checkedIcon={checkedIcon}
161+
style={{ marginRight: 8 }}
162+
checked={selected}
163+
/>
164+
{option.label}
165+
</li>
166+
)}
167+
renderInput={(params) => (
168+
<TextField
169+
{...params}
170+
label={`${t(`FORM.${label}`, { defaultValue: label })} ${
171+
required && selectedOptions.length !== 0 ? '*' : ''
172+
}`}
173+
placeholder={
174+
selectedOptions.length === 0
175+
? `Type to search ${label?.toLowerCase() || 'options'}...`
176+
: ''
177+
}
178+
error={rawErrors.length > 0}
179+
// helperText={
180+
// rawErrors.length > 0
181+
// ? rawErrors[0]
182+
// : maxSelections < enumOptions.length
183+
// ? `Select up to ${maxSelections} options`
184+
// : undefined
185+
// }
186+
required={required && selectedOptions.length === 0}
187+
/>
188+
)}
189+
ListboxProps={{
190+
style: {
191+
maxHeight: '300px',
192+
},
193+
}}
194+
// Add Select All option at the top
195+
ListboxComponent={(props) => (
196+
<div {...props}>
197+
{optionsList.length > 1 && maxSelections >= optionsList.length && (
198+
<div
199+
style={{
200+
padding: '8px 16px',
201+
borderBottom: '1px solid #e0e0e0',
202+
cursor: 'pointer',
203+
backgroundColor: '#f5f5f5',
204+
display: 'flex',
205+
alignItems: 'center',
206+
}}
207+
onClick={handleSelectAll}
208+
>
209+
<Checkbox
210+
icon={icon}
211+
checkedIcon={checkedIcon}
212+
style={{ marginRight: 8 }}
213+
checked={isAllSelected}
214+
/>
215+
{isAllSelected ? 'Deselect All' : 'Select All'}
216+
</div>
217+
)}
218+
{props.children}
219+
</div>
220+
)}
221+
// Disable options when max selections reached
222+
getOptionDisabled={(option) =>
223+
selectedValues.length >= maxSelections &&
224+
!selectedValues.includes(option.value)
225+
}
226+
/>
227+
</FormControl>
228+
);
229+
};
230+
231+
export default AutoCompleteMultiSelectWidget;

apps/admin-app-repo/src/components/DynamicForm/RJSFWidget/CustomDateWidget.tsx

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@ const CustomDateWidget = ({
1313
required,
1414
label,
1515
rawErrors = [],
16+
uiSchema = {}, // <-- include uiSchema
1617
}: any) => {
1718
const { minValue, maxValue } = options;
1819
const { t } = useTranslation();
1920

21+
const isDisabled = uiSchema?.['ui:disabled'] === true;
22+
2023
const initialValue =
2124
typeof value === 'string' && value.match(/^\d{4}-\d{2}-\d{2}$/)
2225
? value
@@ -48,11 +51,28 @@ const CustomDateWidget = ({
4851
const errorText = rawErrors?.length > 0 ? rawErrors[0] : '';
4952

5053
return (
51-
<LocalizationProvider dateAdapter={AdapterDayjs}>
54+
<>
55+
<input
56+
value={value ?? ''}
57+
required={required}
58+
onChange={() => {}}
59+
tabIndex={-1}
60+
style={{
61+
height: 1,
62+
padding: 0,
63+
border: 0,
64+
...(value && { visibility: 'hidden' }),
65+
}}
66+
aria-hidden="true"
67+
marginTop="50px"
68+
69+
/>
70+
<LocalizationProvider dateAdapter={AdapterDayjs} required={required}>
5271
<DatePicker
72+
disabled={isDisabled}
5373
label={`${t(`FORM.${label}`, {
5474
defaultValue: label,
55-
})} ${required ? '*' : ''}`}
75+
})}`}
5676
value={selectedDate || null}
5777
onChange={handleDateChange}
5878
minDate={minValue ? dayjs(minValue, 'YYYY-MM-DD') : undefined}
@@ -62,12 +82,30 @@ const CustomDateWidget = ({
6282
textField: {
6383
fullWidth: true,
6484
variant: 'outlined',
65-
error: rawErrors.length > 0,
85+
// error: rawErrors.length > 0,
6686
// helperText: errorText,
87+
required,
88+
inputProps: {
89+
readOnly: true,
90+
},
91+
onClick: (event: any) => {
92+
event.preventDefault();
93+
},
94+
onKeyDown: (event: any) => {
95+
event.preventDefault();
96+
},
97+
onInput: (event: any) => {
98+
event.preventDefault();
99+
}
67100
},
68101
}}
102+
required={required}
69103
/>
104+
70105
</LocalizationProvider>
106+
{/* Hidden text input to force native validation */}
107+
108+
</>
71109
);
72110
};
73111

0 commit comments

Comments
 (0)