@dashadmin/dash-components 1.3.25 → 1.3.26
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/dist/components/AuditLog/AuditLog.js +345 -1
- package/dist/components/AuditLog/index.js +4 -1
- package/dist/components/AutoCompleteArrayInput/AutoCompleteCheckBoxInput.js +630 -3
- package/dist/components/AutoCompleteArrayInput/AutocompleteCheckBoxArrayInput.js +53 -1
- package/dist/components/AutoCompleteArrayInputAutoSearch/AutoCompleteCheckBoxInput.js +647 -3
- package/dist/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayInput.js +53 -1
- package/dist/components/Json/Json.js +235 -1
- package/dist/components/JsonColorSelector/JsonColorSelector.js +1172 -1
- package/dist/components/JsonColorSelector/JsonColorSelectorEnhanced.js +863 -2
- package/dist/components/JsonColorSelector/JsonCssVarValues.js +738 -1
- package/dist/components/JsonColorSelector/components/ColorEditDialog.js +426 -1
- package/dist/components/JsonColorSelector/components/ColorPaletteItem.js +75 -1
- package/dist/components/JsonColorSelector/components/ImageColorExtractor.js +283 -1
- package/dist/components/JsonColorSelector/helpers/functions.js +101 -1
- package/dist/components/JsonColorSelector/useAsyncColorThief.js +105 -1
- package/dist/components/ListActive/ListActive.js +126 -1
- package/dist/components/ListActive/ListBoolean.js +124 -1
- package/dist/components/NotificationPreferences/NotificationPreferences.js +317 -1
- package/dist/components/NotificationPreferences/index.js +7 -1
- package/dist/components/SearchableSelects/RASearchableSelect.js +236 -1
- package/dist/components/SearchableSelects/RASearchableSelectCheckboxes.js +479 -1
- package/dist/components/SearchableSelects/RASearchableSelectChips.js +465 -1
- package/dist/components/SearchableSelects/RASearchableSelectChipsRecordContext.js +295 -1
- package/dist/components/SearchableSelects/SearchableSelect.js +202 -1
- package/dist/components/SearchableSelects/SearchableSelectCheckboxes.js +290 -1
- package/dist/components/SortableDataGrid/SortableDataGrid.js +72 -1
- package/dist/components/SortableDataGrid/SortableDataGridBody.js +167 -1
- package/dist/components/SortableDataGrid/SortableDataGridHeader.js +109 -1
- package/dist/components/SortableDataGrid/SortableDataGridRow.js +283 -1
- package/dist/components/Upload/SingleImageUploader.js +40 -1
- package/dist/components/custom/PackageCopyMethod.js +78 -1
- package/dist/components/custom/PackageRadioButtonGroup.js +45 -1
- package/dist/components/dialog/Basic.js +8 -1
- package/dist/components/dialog/Correct.js +14 -1
- package/dist/components/dialog/Error.js +14 -1
- package/dist/components/dialog/Info.js +14 -1
- package/dist/components/dialog/InfoV2.js +21 -1
- package/dist/components/dialog/NotFound.js +14 -1
- package/dist/components/dialog/QuickSearch.js +117 -1
- package/dist/components/notifications/NotificationsCenter.js +597 -1
- package/dist/components/notifications/index.js +24 -1
- package/dist/components/tables/PaginationComponent.js +36 -1
- package/dist/components/theme/AppLoadingFallback.js +38 -2
- package/dist/components/theme/ErrorBoundary.js +52 -1
- package/dist/components/theme/InitialLoader.js +13 -1
- package/dist/components/theme/NotFound.js +136 -1
- package/dist/components/theme/NotResults.js +38 -1
- package/dist/hooks/useDraggable.js +6 -1
- package/dist/hooks/useNotifications.js +8 -1
- package/dist/hooks/useQuickSearch.js +26 -1
- package/dist/index.js +66 -5567
- package/dist/utils/setNativeValue.js +17 -1
- package/package.json +149 -132
- package/src/components/AuditLog/AuditLog.tsx +476 -0
- package/src/components/AuditLog/index.ts +2 -0
- package/src/components/AutoCompleteArrayInput/AutoCompleteCheckBoxInput.tsx +917 -0
- package/src/components/AutoCompleteArrayInput/AutocompleteCheckBoxArrayInput.tsx +39 -0
- package/src/components/AutoCompleteArrayInputAutoSearch/AutoCompleteCheckBoxInput.tsx +916 -0
- package/src/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayInput.tsx +44 -0
- package/src/components/Json/Json.tsx +312 -0
- package/src/components/JsonColorSelector/JsonColorSelector.tsx +1435 -0
- package/src/components/JsonColorSelector/JsonColorSelectorEnhanced.tsx +1029 -0
- package/src/components/JsonColorSelector/JsonCssVarValues.tsx +863 -0
- package/src/components/JsonColorSelector/components/ColorEditDialog.tsx +491 -0
- package/src/components/JsonColorSelector/components/ColorPaletteItem.tsx +80 -0
- package/src/components/JsonColorSelector/components/ImageColorExtractor.tsx +328 -0
- package/src/components/JsonColorSelector/helpers/functions.tsx +123 -0
- package/src/components/JsonColorSelector/interfaces/interfaces.tsx +22 -0
- package/src/components/JsonColorSelector/useAsyncColorThief.tsx +97 -0
- package/src/components/ListActive/ListActive.tsx +106 -0
- package/src/components/ListActive/ListBoolean.tsx +96 -0
- package/src/components/NotificationPreferences/NotificationPreferences.tsx +463 -0
- package/src/components/NotificationPreferences/index.tsx +2 -0
- package/src/components/SearchableSelects/RASearchableSelect.tsx +300 -0
- package/src/components/SearchableSelects/RASearchableSelectCheckboxes.tsx +604 -0
- package/src/components/SearchableSelects/RASearchableSelectChips.tsx +558 -0
- package/src/components/SearchableSelects/RASearchableSelectChipsRecordContext.tsx +362 -0
- package/src/components/SearchableSelects/SearchableSelect.tsx +321 -0
- package/src/components/SearchableSelects/SearchableSelectCheckboxes.tsx +355 -0
- package/src/components/SortableDataGrid/SortableDataGrid.tsx +56 -0
- package/src/components/SortableDataGrid/SortableDataGridBody.tsx +175 -0
- package/src/components/SortableDataGrid/SortableDataGridHeader.tsx +171 -0
- package/src/components/SortableDataGrid/SortableDataGridRow.tsx +247 -0
- package/src/components/Upload/SingleImageUploader.tsx +55 -0
- package/src/components/custom/PackageCopyMethod.tsx +109 -0
- package/src/components/custom/PackageRadioButtonGroup.tsx +47 -0
- package/src/components/dialog/Basic.tsx +11 -0
- package/src/components/dialog/Correct.tsx +16 -0
- package/src/components/dialog/Error.tsx +15 -0
- package/src/components/dialog/Info.tsx +15 -0
- package/src/components/dialog/InfoV2.tsx +35 -0
- package/src/components/dialog/NotFound.tsx +18 -0
- package/src/components/dialog/QuickSearch.tsx +115 -0
- package/src/components/notifications/NotificationsCenter.tsx +792 -0
- package/src/components/notifications/index.ts +42 -0
- package/src/components/tables/PaginationComponent.tsx +17 -0
- package/src/components/theme/AppLoadingFallback.tsx +42 -0
- package/src/components/theme/ErrorBoundary.tsx +57 -0
- package/src/components/theme/InitialLoader.tsx +16 -0
- package/src/components/theme/NotFound.tsx +124 -0
- package/src/components/theme/NotResults.tsx +54 -0
- package/src/hooks/useDraggable.ts +11 -0
- package/src/hooks/useNotifications.ts +14 -0
- package/src/hooks/useQuickSearch.tsx +29 -0
- package/src/index.tsx +77 -0
- package/src/utils/setNativeValue.tsx +23 -0
- package/dist/color-thief-CTwGFhOB.js +0 -272
- /package/{dist → src}/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayAutoSearch.tsx.wip +0 -0
- /package/{dist → src}/components/JsonColorSelector/JsonColorSelector.tsx.bkup +0 -0
- /package/{dist → src}/components/SortableDataGrid/README.md +0 -0
- /package/{dist → src}/styles/index.less +0 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import React, { act, useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { IDashAutoAdminCustomFieldComponent } from 'dash-auto-admin';
|
|
3
|
+
/*import { ICommunes } from '@app/interfaces/ICommunes'; */
|
|
4
|
+
import { useRecordContext, useUpdate } from 'react-admin';
|
|
5
|
+
import { Switch, Box } from '@mui/material';
|
|
6
|
+
import { FormControlLabel } from '@mui/material';
|
|
7
|
+
import { AttributeToField } from 'dash-auto-admin/src/mui/AttributeToField';
|
|
8
|
+
import AttributeToInput from 'dash-auto-admin/src/mui/AttributeToInput';
|
|
9
|
+
|
|
10
|
+
export interface IListActive extends IDashAutoAdminCustomFieldComponent {
|
|
11
|
+
activeLabel?: string;
|
|
12
|
+
inactiveLabel?: string;
|
|
13
|
+
enableLabel?: string;
|
|
14
|
+
disableLabel?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const ListActive = ({
|
|
18
|
+
method,
|
|
19
|
+
attribute,
|
|
20
|
+
resourceConfig,
|
|
21
|
+
activeLabel = 'Activo',
|
|
22
|
+
inactiveLabel = 'Inactivo',
|
|
23
|
+
enableLabel = 'Activar',
|
|
24
|
+
disableLabel = 'Desactivar',
|
|
25
|
+
}: IListActive) => {
|
|
26
|
+
const [ ,setLoading] = useState<boolean>(true);
|
|
27
|
+
const [update, { isLoading: updateLoading }] =
|
|
28
|
+
useUpdate();
|
|
29
|
+
const record: any = useRecordContext();
|
|
30
|
+
const onError = (error: any) => {
|
|
31
|
+
console.error(error);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const onChange = useCallback(
|
|
35
|
+
async (value: boolean) => {
|
|
36
|
+
window.dispatchEvent(
|
|
37
|
+
new MessageEvent('dash-global-loader', { data: true })
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
await update(
|
|
42
|
+
resourceConfig.model + '/change-status/' + record.id,
|
|
43
|
+
{
|
|
44
|
+
id: record.id,
|
|
45
|
+
data: { is_active: value },
|
|
46
|
+
previousData: record,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
onSuccess: () => {
|
|
50
|
+
// Force a refresh of the record context
|
|
51
|
+
record.is_active = value;
|
|
52
|
+
},
|
|
53
|
+
onSettled: (data, error) => {
|
|
54
|
+
if (error) onError(error);
|
|
55
|
+
window.dispatchEvent(
|
|
56
|
+
new MessageEvent('dash-global-loader', { data: false })
|
|
57
|
+
);
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
onError(error);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
[update, record]
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
setLoading(false);
|
|
70
|
+
}, [record]);
|
|
71
|
+
const is_active = record?.is_active ? true : false;
|
|
72
|
+
const textSwitch = record?.is_active ? disableLabel : enableLabel;
|
|
73
|
+
switch (method) {
|
|
74
|
+
case 'create':
|
|
75
|
+
case 'edit':
|
|
76
|
+
return AttributeToInput(method, resourceConfig, {...attribute,custom:false,type:Boolean})
|
|
77
|
+
case 'view':
|
|
78
|
+
return AttributeToField(method, resourceConfig, {...attribute,custom:false,type:Boolean})
|
|
79
|
+
case 'list':
|
|
80
|
+
return (
|
|
81
|
+
<div>
|
|
82
|
+
<Box>
|
|
83
|
+
<Switch
|
|
84
|
+
|
|
85
|
+
onChange={(e, value) => onChange(value)}
|
|
86
|
+
{...(is_active === true && { defaultChecked: true })}
|
|
87
|
+
/>{is_active ? activeLabel : inactiveLabel}
|
|
88
|
+
</Box>
|
|
89
|
+
</div>
|
|
90
|
+
)
|
|
91
|
+
default:
|
|
92
|
+
return <>{attribute.label}: {is_active ? activeLabel : inactiveLabel}</>;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export default ListActive;
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Box,
|
|
4
|
+
Card,
|
|
5
|
+
CardContent,
|
|
6
|
+
Typography,
|
|
7
|
+
Switch,
|
|
8
|
+
FormControlLabel,
|
|
9
|
+
Alert,
|
|
10
|
+
TextField,
|
|
11
|
+
InputAdornment,
|
|
12
|
+
IconButton,
|
|
13
|
+
Chip,
|
|
14
|
+
} from '@mui/material';
|
|
15
|
+
import { Search as SearchIcon, Clear as ClearIcon, Email as EmailIcon, Notifications as PushIcon } from '@mui/icons-material';
|
|
16
|
+
import { useRecordContext } from 'react-admin';
|
|
17
|
+
import { useFormContext } from 'react-hook-form';
|
|
18
|
+
import { AuthPersistenceService } from 'dash-auth';
|
|
19
|
+
import { IDashAutoAdminCustomFieldComponent } from 'dash-auto-admin';
|
|
20
|
+
|
|
21
|
+
interface NotificationConfig {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
className: string;
|
|
25
|
+
hasEmail: boolean;
|
|
26
|
+
hasPush: boolean;
|
|
27
|
+
hasSocket: boolean;
|
|
28
|
+
hasDatabase: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface NotificationPreference {
|
|
32
|
+
id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
email: boolean;
|
|
35
|
+
push: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Helper functions - moved outside component
|
|
39
|
+
const getNestedValue = (obj: any, path: string): any => {
|
|
40
|
+
if (!path || !obj) return [];
|
|
41
|
+
const keys = path.split('.');
|
|
42
|
+
return keys.reduce((o, key) => (o && o[key] !== undefined ? o[key] : []), obj);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const setNestedValue = (obj: any, path: string, value: any): any => {
|
|
46
|
+
if (!path) return value;
|
|
47
|
+
const keys = path.split('.');
|
|
48
|
+
const lastKey = keys.pop();
|
|
49
|
+
const result = { ...obj };
|
|
50
|
+
let current = result;
|
|
51
|
+
|
|
52
|
+
for (const key of keys) {
|
|
53
|
+
if (!current[key]) current[key] = {};
|
|
54
|
+
current = current[key];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (lastKey) {
|
|
58
|
+
current[lastKey] = value;
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* NotificationPreferencesEdit - Edit mode component
|
|
65
|
+
* All hooks are called unconditionally at the top level
|
|
66
|
+
*/
|
|
67
|
+
export const NotificationPreferencesEdit: React.FC<IDashAutoAdminCustomFieldComponent> = (props) => {
|
|
68
|
+
const { attribute } = props;
|
|
69
|
+
|
|
70
|
+
// ALL HOOKS CALLED UNCONDITIONALLY AT TOP LEVEL
|
|
71
|
+
const record = useRecordContext();
|
|
72
|
+
const formContext = useFormContext();
|
|
73
|
+
const [searchTerm, setSearchTerm] = React.useState<string>('');
|
|
74
|
+
const [localPreferences, setLocalPreferences] = React.useState<NotificationPreference[] | null>(null);
|
|
75
|
+
|
|
76
|
+
// Get form methods safely
|
|
77
|
+
const setValue = formContext?.setValue;
|
|
78
|
+
const getValues = formContext?.getValues;
|
|
79
|
+
|
|
80
|
+
// Calculate attribute paths
|
|
81
|
+
const attributePath = attribute?.attribute || '';
|
|
82
|
+
const isNestedSetting = attributePath.startsWith('preferences.');
|
|
83
|
+
const preferencesPath = isNestedSetting ? attributePath.split('.').slice(1).join('.') : attributePath;
|
|
84
|
+
|
|
85
|
+
// Get available notifications from systemValues (memoized)
|
|
86
|
+
const availableNotifications = useMemo<NotificationConfig[]>(() => {
|
|
87
|
+
const systemValues = AuthPersistenceService.getSystemValues();
|
|
88
|
+
console.log('🔔 NotificationPreferencesEdit - systemValues:', systemValues);
|
|
89
|
+
const notifications = systemValues?.user_notifications || [];
|
|
90
|
+
console.log('🔔 NotificationPreferencesEdit - user_notifications:', notifications);
|
|
91
|
+
return notifications;
|
|
92
|
+
}, []);
|
|
93
|
+
|
|
94
|
+
// Get current preferences from record/form (memoized)
|
|
95
|
+
const initialPreferences = useMemo<NotificationPreference[]>(() => {
|
|
96
|
+
let currentPrefs: NotificationPreference[] = [];
|
|
97
|
+
|
|
98
|
+
if (record) {
|
|
99
|
+
const recordValue = isNestedSetting
|
|
100
|
+
? getNestedValue(record, preferencesPath)
|
|
101
|
+
: record[attributePath];
|
|
102
|
+
currentPrefs = Array.isArray(recordValue) ? recordValue : [];
|
|
103
|
+
console.log('🔔 NotificationPreferencesEdit - preferences from record:', currentPrefs);
|
|
104
|
+
} else if (getValues) {
|
|
105
|
+
const formValues = getValues();
|
|
106
|
+
const formValue = isNestedSetting
|
|
107
|
+
? getNestedValue(formValues, preferencesPath)
|
|
108
|
+
: formValues[attributePath];
|
|
109
|
+
currentPrefs = Array.isArray(formValue) ? formValue : [];
|
|
110
|
+
console.log('🔔 NotificationPreferencesEdit - preferences from form:', currentPrefs);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Map available notifications to preferences with defaults
|
|
114
|
+
return availableNotifications.map(notification => {
|
|
115
|
+
const existing = currentPrefs.find(p => p.id === notification.className);
|
|
116
|
+
return existing || {
|
|
117
|
+
id: notification.className,
|
|
118
|
+
name: notification.name,
|
|
119
|
+
email: notification.hasEmail,
|
|
120
|
+
push: notification.hasPush,
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
}, [availableNotifications, record, attributePath, isNestedSetting, preferencesPath, getValues]);
|
|
124
|
+
|
|
125
|
+
// Use local state if modified, otherwise use initial
|
|
126
|
+
const preferences = localPreferences ?? initialPreferences;
|
|
127
|
+
|
|
128
|
+
// Filter preferences based on search term
|
|
129
|
+
const filteredPreferences = useMemo(() => {
|
|
130
|
+
if (!searchTerm.trim()) return preferences;
|
|
131
|
+
return preferences.filter(pref =>
|
|
132
|
+
pref.name.toLowerCase().includes(searchTerm.toLowerCase())
|
|
133
|
+
);
|
|
134
|
+
}, [preferences, searchTerm]);
|
|
135
|
+
|
|
136
|
+
// Update form value when preferences change
|
|
137
|
+
const updateFormValue = (newPreferences: NotificationPreference[]) => {
|
|
138
|
+
if (!setValue) return;
|
|
139
|
+
|
|
140
|
+
if (isNestedSetting) {
|
|
141
|
+
const formValues = getValues?.() || {};
|
|
142
|
+
const updatedValues = setNestedValue({ ...formValues }, preferencesPath, newPreferences);
|
|
143
|
+
setValue(attributePath, getNestedValue(updatedValues, preferencesPath), { shouldDirty: true });
|
|
144
|
+
} else {
|
|
145
|
+
setValue(attributePath, newPreferences, { shouldDirty: true });
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Handle toggle
|
|
150
|
+
const handleToggle = (notificationId: string, channel: 'email' | 'push') => {
|
|
151
|
+
const updatedPreferences = preferences.map(pref =>
|
|
152
|
+
pref.id === notificationId
|
|
153
|
+
? { ...pref, [channel]: !pref[channel] }
|
|
154
|
+
: pref
|
|
155
|
+
);
|
|
156
|
+
setLocalPreferences(updatedPreferences);
|
|
157
|
+
updateFormValue(updatedPreferences);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Early return if no form context
|
|
161
|
+
if (!formContext) {
|
|
162
|
+
return (
|
|
163
|
+
<Alert severity="warning">
|
|
164
|
+
Form context not available. This component must be used within a form.
|
|
165
|
+
</Alert>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Early return if no notifications
|
|
170
|
+
if (availableNotifications.length === 0) {
|
|
171
|
+
return (
|
|
172
|
+
<Alert severity="info">
|
|
173
|
+
No notifications available to configure.
|
|
174
|
+
<Typography variant="caption" display="block" sx={{ mt: 1 }}>
|
|
175
|
+
Debug: No user_notifications found in systemValues.
|
|
176
|
+
Try logging out and logging back in to refresh auth data.
|
|
177
|
+
</Typography>
|
|
178
|
+
</Alert>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<Box sx={{ mt: 1, mb: 2 }}>
|
|
184
|
+
<Typography variant="h6" gutterBottom>
|
|
185
|
+
Notification Preferences
|
|
186
|
+
</Typography>
|
|
187
|
+
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
|
188
|
+
Configure which notifications you want to receive via email and push notifications
|
|
189
|
+
</Typography>
|
|
190
|
+
|
|
191
|
+
{/* Search Box */}
|
|
192
|
+
{preferences.length > 5 && (
|
|
193
|
+
<TextField
|
|
194
|
+
fullWidth
|
|
195
|
+
variant="outlined"
|
|
196
|
+
placeholder="Search notifications..."
|
|
197
|
+
value={searchTerm}
|
|
198
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
199
|
+
sx={{ mb: 2 }}
|
|
200
|
+
InputProps={{
|
|
201
|
+
startAdornment: (
|
|
202
|
+
<InputAdornment position="start">
|
|
203
|
+
<SearchIcon />
|
|
204
|
+
</InputAdornment>
|
|
205
|
+
),
|
|
206
|
+
endAdornment: searchTerm && (
|
|
207
|
+
<InputAdornment position="end">
|
|
208
|
+
<IconButton onClick={() => setSearchTerm('')} edge="end" size="small">
|
|
209
|
+
<ClearIcon />
|
|
210
|
+
</IconButton>
|
|
211
|
+
</InputAdornment>
|
|
212
|
+
),
|
|
213
|
+
}}
|
|
214
|
+
/>
|
|
215
|
+
)}
|
|
216
|
+
|
|
217
|
+
{/* No results message */}
|
|
218
|
+
{searchTerm && filteredPreferences.length === 0 && (
|
|
219
|
+
<Alert severity="info" sx={{ mb: 2 }}>
|
|
220
|
+
No notifications found matching "{searchTerm}"
|
|
221
|
+
</Alert>
|
|
222
|
+
)}
|
|
223
|
+
|
|
224
|
+
{/* Notification list */}
|
|
225
|
+
{filteredPreferences.map((preference) => {
|
|
226
|
+
const notification = availableNotifications.find(n => n.className === preference.id);
|
|
227
|
+
if (!notification) return null;
|
|
228
|
+
|
|
229
|
+
return (
|
|
230
|
+
<Card
|
|
231
|
+
key={preference.id}
|
|
232
|
+
variant="outlined"
|
|
233
|
+
sx={{
|
|
234
|
+
mb: 1,
|
|
235
|
+
'&:hover': {
|
|
236
|
+
boxShadow: 2,
|
|
237
|
+
},
|
|
238
|
+
}}
|
|
239
|
+
>
|
|
240
|
+
<CardContent sx={{ p: 2, '&:last-child': { pb: 2 } }}>
|
|
241
|
+
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
242
|
+
<Box sx={{ flex: 1 }}>
|
|
243
|
+
<Typography variant="subtitle1" fontWeight="bold">
|
|
244
|
+
{preference.name}
|
|
245
|
+
</Typography>
|
|
246
|
+
<Box sx={{ display: 'flex', gap: 1, mt: 0.5 }}>
|
|
247
|
+
{notification.hasSocket && (
|
|
248
|
+
<Chip label="Real-time" size="small" color="info" />
|
|
249
|
+
)}
|
|
250
|
+
{notification.hasDatabase && (
|
|
251
|
+
<Chip label="Stored" size="small" color="default" />
|
|
252
|
+
)}
|
|
253
|
+
</Box>
|
|
254
|
+
</Box>
|
|
255
|
+
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
|
|
256
|
+
{notification.hasEmail && (
|
|
257
|
+
<FormControlLabel
|
|
258
|
+
control={
|
|
259
|
+
<Switch
|
|
260
|
+
checked={preference.email}
|
|
261
|
+
onChange={() => handleToggle(preference.id, 'email')}
|
|
262
|
+
color="primary"
|
|
263
|
+
/>
|
|
264
|
+
}
|
|
265
|
+
label={
|
|
266
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
267
|
+
<EmailIcon fontSize="small" />
|
|
268
|
+
<Typography variant="body2">Email</Typography>
|
|
269
|
+
</Box>
|
|
270
|
+
}
|
|
271
|
+
/>
|
|
272
|
+
)}
|
|
273
|
+
{notification.hasPush && (
|
|
274
|
+
<FormControlLabel
|
|
275
|
+
control={
|
|
276
|
+
<Switch
|
|
277
|
+
checked={preference.push}
|
|
278
|
+
onChange={() => handleToggle(preference.id, 'push')}
|
|
279
|
+
color="secondary"
|
|
280
|
+
/>
|
|
281
|
+
}
|
|
282
|
+
label={
|
|
283
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
284
|
+
<PushIcon fontSize="small" />
|
|
285
|
+
<Typography variant="body2">Push</Typography>
|
|
286
|
+
</Box>
|
|
287
|
+
}
|
|
288
|
+
/>
|
|
289
|
+
)}
|
|
290
|
+
</Box>
|
|
291
|
+
</Box>
|
|
292
|
+
</CardContent>
|
|
293
|
+
</Card>
|
|
294
|
+
);
|
|
295
|
+
})}
|
|
296
|
+
</Box>
|
|
297
|
+
);
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* NotificationPreferencesView - View mode component
|
|
302
|
+
* All hooks are called unconditionally at the top level
|
|
303
|
+
*/
|
|
304
|
+
export const NotificationPreferencesView: React.FC<IDashAutoAdminCustomFieldComponent> = (props) => {
|
|
305
|
+
const { attribute } = props;
|
|
306
|
+
|
|
307
|
+
// ALL HOOKS CALLED UNCONDITIONALLY AT TOP LEVEL
|
|
308
|
+
const record = useRecordContext();
|
|
309
|
+
const [searchTerm, setSearchTerm] = React.useState<string>('');
|
|
310
|
+
|
|
311
|
+
// Calculate attribute paths
|
|
312
|
+
const attributePath = attribute?.attribute || '';
|
|
313
|
+
const isNestedSetting = attributePath.startsWith('preferences.');
|
|
314
|
+
const preferencesPath = isNestedSetting ? attributePath.split('.').slice(1).join('.') : attributePath;
|
|
315
|
+
|
|
316
|
+
// Get available notifications from systemValues (memoized)
|
|
317
|
+
const availableNotifications = useMemo<NotificationConfig[]>(() => {
|
|
318
|
+
const systemValues = AuthPersistenceService.getSystemValues();
|
|
319
|
+
return systemValues?.user_notifications || [];
|
|
320
|
+
}, []);
|
|
321
|
+
|
|
322
|
+
// Get current preferences from record (memoized)
|
|
323
|
+
const preferences = useMemo<NotificationPreference[]>(() => {
|
|
324
|
+
if (!record) return [];
|
|
325
|
+
const recordValue = isNestedSetting
|
|
326
|
+
? getNestedValue(record, preferencesPath)
|
|
327
|
+
: record[attributePath];
|
|
328
|
+
return Array.isArray(recordValue) ? recordValue : [];
|
|
329
|
+
}, [record, attributePath, isNestedSetting, preferencesPath]);
|
|
330
|
+
|
|
331
|
+
// Filter preferences based on search term
|
|
332
|
+
const filteredPreferences = useMemo(() => {
|
|
333
|
+
if (!searchTerm.trim()) return preferences;
|
|
334
|
+
return preferences.filter(pref =>
|
|
335
|
+
pref.name.toLowerCase().includes(searchTerm.toLowerCase())
|
|
336
|
+
);
|
|
337
|
+
}, [preferences, searchTerm]);
|
|
338
|
+
|
|
339
|
+
// Early return if no notifications
|
|
340
|
+
if (availableNotifications.length === 0) {
|
|
341
|
+
return (
|
|
342
|
+
<Alert severity="info">
|
|
343
|
+
No notification preferences configured.
|
|
344
|
+
</Alert>
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return (
|
|
349
|
+
<Box sx={{ mt: 1, mb: 2 }}>
|
|
350
|
+
<Typography variant="h6" gutterBottom>
|
|
351
|
+
Notification Preferences
|
|
352
|
+
</Typography>
|
|
353
|
+
|
|
354
|
+
{/* Search Box */}
|
|
355
|
+
{preferences.length > 5 && (
|
|
356
|
+
<TextField
|
|
357
|
+
fullWidth
|
|
358
|
+
variant="outlined"
|
|
359
|
+
placeholder="Search notifications..."
|
|
360
|
+
value={searchTerm}
|
|
361
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
362
|
+
sx={{ mb: 2 }}
|
|
363
|
+
InputProps={{
|
|
364
|
+
startAdornment: (
|
|
365
|
+
<InputAdornment position="start">
|
|
366
|
+
<SearchIcon />
|
|
367
|
+
</InputAdornment>
|
|
368
|
+
),
|
|
369
|
+
endAdornment: searchTerm && (
|
|
370
|
+
<InputAdornment position="end">
|
|
371
|
+
<IconButton onClick={() => setSearchTerm('')} edge="end" size="small">
|
|
372
|
+
<ClearIcon />
|
|
373
|
+
</IconButton>
|
|
374
|
+
</InputAdornment>
|
|
375
|
+
),
|
|
376
|
+
}}
|
|
377
|
+
/>
|
|
378
|
+
)}
|
|
379
|
+
|
|
380
|
+
{/* Notification list */}
|
|
381
|
+
{filteredPreferences.map((preference) => {
|
|
382
|
+
const notification = availableNotifications.find(n => n.className === preference.id);
|
|
383
|
+
if (!notification) return null;
|
|
384
|
+
|
|
385
|
+
return (
|
|
386
|
+
<Card key={preference.id} variant="outlined" sx={{ mb: 1 }}>
|
|
387
|
+
<CardContent sx={{ p: 2, '&:last-child': { pb: 2 } }}>
|
|
388
|
+
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
389
|
+
<Box sx={{ flex: 1 }}>
|
|
390
|
+
<Typography variant="subtitle1" fontWeight="bold">
|
|
391
|
+
{preference.name}
|
|
392
|
+
</Typography>
|
|
393
|
+
<Box sx={{ display: 'flex', gap: 1, mt: 0.5 }}>
|
|
394
|
+
{notification.hasSocket && (
|
|
395
|
+
<Chip label="Real-time" size="small" color="info" />
|
|
396
|
+
)}
|
|
397
|
+
{notification.hasDatabase && (
|
|
398
|
+
<Chip label="Stored" size="small" color="default" />
|
|
399
|
+
)}
|
|
400
|
+
</Box>
|
|
401
|
+
</Box>
|
|
402
|
+
<Box sx={{ display: 'flex', gap: 2 }}>
|
|
403
|
+
{notification.hasEmail && (
|
|
404
|
+
<Chip
|
|
405
|
+
icon={<EmailIcon />}
|
|
406
|
+
label="Email"
|
|
407
|
+
color={preference.email ? 'primary' : 'default'}
|
|
408
|
+
variant={preference.email ? 'filled' : 'outlined'}
|
|
409
|
+
/>
|
|
410
|
+
)}
|
|
411
|
+
{notification.hasPush && (
|
|
412
|
+
<Chip
|
|
413
|
+
icon={<PushIcon />}
|
|
414
|
+
label="Push"
|
|
415
|
+
color={preference.push ? 'secondary' : 'default'}
|
|
416
|
+
variant={preference.push ? 'filled' : 'outlined'}
|
|
417
|
+
/>
|
|
418
|
+
)}
|
|
419
|
+
</Box>
|
|
420
|
+
</Box>
|
|
421
|
+
</CardContent>
|
|
422
|
+
</Card>
|
|
423
|
+
);
|
|
424
|
+
})}
|
|
425
|
+
|
|
426
|
+
{preferences.length === 0 && (
|
|
427
|
+
<Alert severity="info">
|
|
428
|
+
No notification preferences configured
|
|
429
|
+
</Alert>
|
|
430
|
+
)}
|
|
431
|
+
</Box>
|
|
432
|
+
);
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* NotificationPreferences - Main component
|
|
437
|
+
* NO CONDITIONAL HOOKS - All hooks called at top level
|
|
438
|
+
* Renders appropriate view based on method prop
|
|
439
|
+
*/
|
|
440
|
+
const NotificationPreferences: React.FC<IDashAutoAdminCustomFieldComponent> = (props) => {
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
const { method="edit", attribute=null, resourceConfig=null } = props;
|
|
445
|
+
|
|
446
|
+
console.log('🔔 NotificationPreferences - method:', method);
|
|
447
|
+
console.log('🔔 NotificationPreferences - attribute:', attribute);
|
|
448
|
+
|
|
449
|
+
// Determine which component to render based on method
|
|
450
|
+
// No hooks, no conditional logic, just a simple switch
|
|
451
|
+
switch (method) {
|
|
452
|
+
case 'edit':
|
|
453
|
+
case 'create':
|
|
454
|
+
return <><NotificationPreferencesEdit method={method} attribute={attribute} resourceConfig={resourceConfig} /></>;
|
|
455
|
+
case 'view':
|
|
456
|
+
return <><NotificationPreferencesView method={method} attribute={attribute} resourceConfig={resourceConfig} /></>;
|
|
457
|
+
default:
|
|
458
|
+
console.log('🔔 NotificationPreferences - unsupported method:', method);
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
export default NotificationPreferences;
|