@dashadmin/dash-components 1.3.73 → 1.3.74-srcfree.1
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 +24 -21
- package/src/components/AuditLog/AuditLog.tsx +0 -490
- package/src/components/AuditLog/index.ts +0 -2
- package/src/components/AutoCompleteArrayInput/AutoCompleteCheckBoxInput.tsx +0 -917
- package/src/components/AutoCompleteArrayInput/AutocompleteCheckBoxArrayInput.tsx +0 -39
- package/src/components/AutoCompleteArrayInputAutoSearch/AutoCompleteCheckBoxInput.tsx +0 -916
- package/src/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayAutoSearch.tsx.wip +0 -42
- package/src/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayInput.tsx +0 -44
- package/src/components/Json/Json.tsx +0 -312
- package/src/components/JsonColorSelector/JsonColorSelector.tsx +0 -1443
- package/src/components/JsonColorSelector/JsonColorSelector.tsx.bkup +0 -746
- package/src/components/JsonColorSelector/JsonColorSelectorEnhanced.tsx +0 -1135
- package/src/components/JsonColorSelector/JsonCssVarValues.tsx +0 -867
- package/src/components/JsonColorSelector/components/BasicColorSelector.tsx +0 -333
- package/src/components/JsonColorSelector/components/ColorEditDialog.tsx +0 -495
- package/src/components/JsonColorSelector/components/ColorPaletteItem.tsx +0 -80
- package/src/components/JsonColorSelector/components/ImageColorExtractor.tsx +0 -420
- package/src/components/JsonColorSelector/helpers/functions.tsx +0 -123
- package/src/components/JsonColorSelector/helpers/paletteDerivation.ts +0 -367
- package/src/components/JsonColorSelector/interfaces/interfaces.tsx +0 -22
- package/src/components/JsonColorSelector/useAsyncColorThief.tsx +0 -97
- package/src/components/ListActive/ListActive.tsx +0 -106
- package/src/components/ListActive/ListBoolean.tsx +0 -96
- package/src/components/NotificationPreferences/NotificationPreferences.tsx +0 -480
- package/src/components/NotificationPreferences/index.tsx +0 -2
- package/src/components/SearchableSelects/RASearchableSelect.tsx +0 -303
- package/src/components/SearchableSelects/RASearchableSelectCheckboxes.tsx +0 -617
- package/src/components/SearchableSelects/RASearchableSelectChips.tsx +0 -561
- package/src/components/SearchableSelects/RASearchableSelectChipsRecordContext.tsx +0 -366
- package/src/components/SearchableSelects/SearchableSelect.tsx +0 -323
- package/src/components/SearchableSelects/SearchableSelectCheckboxes.tsx +0 -358
- package/src/components/SortableDataGrid/README.md +0 -1
- package/src/components/SortableDataGrid/SortableDataGrid.tsx +0 -56
- package/src/components/SortableDataGrid/SortableDataGridBody.tsx +0 -175
- package/src/components/SortableDataGrid/SortableDataGridHeader.tsx +0 -171
- package/src/components/SortableDataGrid/SortableDataGridRow.tsx +0 -247
- package/src/components/Upload/SingleImageUploader.tsx +0 -55
- package/src/components/custom/PackageCopyMethod.tsx +0 -109
- package/src/components/custom/PackageRadioButtonGroup.tsx +0 -49
- package/src/components/dialog/Basic.tsx +0 -11
- package/src/components/dialog/Correct.tsx +0 -16
- package/src/components/dialog/Error.tsx +0 -15
- package/src/components/dialog/Info.tsx +0 -15
- package/src/components/dialog/InfoV2.tsx +0 -35
- package/src/components/dialog/NotFound.tsx +0 -18
- package/src/components/dialog/QuickSearch.tsx +0 -115
- package/src/components/notifications/NotificationsCenter.tsx +0 -743
- package/src/components/notifications/index.ts +0 -42
- package/src/components/tables/PaginationComponent.tsx +0 -17
- package/src/components/theme/AppLoadingFallback.tsx +0 -42
- package/src/components/theme/ErrorBoundary.tsx +0 -57
- package/src/components/theme/InitialLoader.tsx +0 -16
- package/src/components/theme/NotFound.tsx +0 -124
- package/src/components/theme/NotResults.tsx +0 -56
- package/src/hooks/useDraggable.ts +0 -11
- package/src/hooks/useNotifications.ts +0 -14
- package/src/hooks/useQuickSearch.tsx +0 -29
- package/src/index.tsx +0 -77
- package/src/styles/index.less +0 -0
- package/src/utils/setNativeValue.tsx +0 -23
|
@@ -1,743 +0,0 @@
|
|
|
1
|
-
import React, { useState, useCallback, useEffect } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
Button,
|
|
4
|
-
Badge,
|
|
5
|
-
Menu,
|
|
6
|
-
MenuItem,
|
|
7
|
-
Typography,
|
|
8
|
-
Divider,
|
|
9
|
-
Box,
|
|
10
|
-
IconButton,
|
|
11
|
-
Avatar,
|
|
12
|
-
Chip,
|
|
13
|
-
Snackbar,
|
|
14
|
-
Alert,
|
|
15
|
-
Stack,
|
|
16
|
-
Paper,
|
|
17
|
-
Portal
|
|
18
|
-
} from '@mui/material';
|
|
19
|
-
import {
|
|
20
|
-
NotificationsNone,
|
|
21
|
-
NotificationsActive,
|
|
22
|
-
Close as CloseIcon,
|
|
23
|
-
Clear as ClearIcon,
|
|
24
|
-
Person as PersonIcon,
|
|
25
|
-
Store as StoreIcon,
|
|
26
|
-
DragIndicator as DragIcon
|
|
27
|
-
} from '@mui/icons-material';
|
|
28
|
-
import {
|
|
29
|
-
useNotifications as useNotificationsState,
|
|
30
|
-
type ProcessedNotification,
|
|
31
|
-
type ToastNotification as DashUtilsToastNotification,
|
|
32
|
-
type DialogNotification as DashUtilsDialogNotification,
|
|
33
|
-
} from 'dash-utils';
|
|
34
|
-
|
|
35
|
-
// ============================================================================
|
|
36
|
-
// TYPES & INTERFACES
|
|
37
|
-
//
|
|
38
|
-
// The notification/toast/dialog shapes themselves live in dash-utils'
|
|
39
|
-
// useNotifications hook (the state management is delegated there below —
|
|
40
|
-
// see "HOOKS"). Re-exported here under this module's established names so
|
|
41
|
-
// existing imports of NotificationData/ToastNotification/DialogNotification
|
|
42
|
-
// from dash-components keep working unchanged.
|
|
43
|
-
// ============================================================================
|
|
44
|
-
|
|
45
|
-
export type NotificationData = ProcessedNotification;
|
|
46
|
-
export type ToastNotification = DashUtilsToastNotification;
|
|
47
|
-
export type DialogNotification = DashUtilsDialogNotification;
|
|
48
|
-
|
|
49
|
-
export interface NotificationsCenterConfig {
|
|
50
|
-
/** Maximum number of notifications to keep */
|
|
51
|
-
maxNotifications?: number;
|
|
52
|
-
/** Auto-hide duration for toast notifications (ms) */
|
|
53
|
-
toastAutoHideDuration?: number;
|
|
54
|
-
/** Base z-index for notifications */
|
|
55
|
-
baseZIndex?: number;
|
|
56
|
-
/** Menu max height */
|
|
57
|
-
menuMaxHeight?: number;
|
|
58
|
-
/** Menu width */
|
|
59
|
-
menuWidth?: number;
|
|
60
|
-
/** Custom labels for localization */
|
|
61
|
-
labels?: NotificationsCenterLabels;
|
|
62
|
-
/** Custom icons */
|
|
63
|
-
icons?: NotificationsCenterIcons;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export interface NotificationsCenterLabels {
|
|
67
|
-
title?: string;
|
|
68
|
-
markAll?: string;
|
|
69
|
-
noNotifications?: string;
|
|
70
|
-
close?: string;
|
|
71
|
-
acknowledge?: string;
|
|
72
|
-
customer?: string;
|
|
73
|
-
store?: string;
|
|
74
|
-
received?: string;
|
|
75
|
-
now?: string;
|
|
76
|
-
minutesAgo?: string;
|
|
77
|
-
hoursAgo?: string;
|
|
78
|
-
daysAgo?: string;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export interface NotificationsCenterIcons {
|
|
82
|
-
empty?: React.ReactNode;
|
|
83
|
-
active?: React.ReactNode;
|
|
84
|
-
customer?: React.ReactNode;
|
|
85
|
-
store?: React.ReactNode;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Default configuration
|
|
89
|
-
const defaultConfig: Required<NotificationsCenterConfig> = {
|
|
90
|
-
maxNotifications: 50,
|
|
91
|
-
toastAutoHideDuration: 8000,
|
|
92
|
-
baseZIndex: 100000,
|
|
93
|
-
menuMaxHeight: 480,
|
|
94
|
-
menuWidth: 360,
|
|
95
|
-
labels: {
|
|
96
|
-
title: 'Notifications',
|
|
97
|
-
markAll: 'Mark all',
|
|
98
|
-
noNotifications: 'No notifications',
|
|
99
|
-
close: 'Close',
|
|
100
|
-
acknowledge: 'Got it',
|
|
101
|
-
customer: 'Customer:',
|
|
102
|
-
store: 'Store:',
|
|
103
|
-
received: 'Received:',
|
|
104
|
-
now: 'Now',
|
|
105
|
-
minutesAgo: 'm',
|
|
106
|
-
hoursAgo: 'h',
|
|
107
|
-
daysAgo: 'd',
|
|
108
|
-
},
|
|
109
|
-
icons: {},
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
// ============================================================================
|
|
113
|
-
// UTILITIES
|
|
114
|
-
// ============================================================================
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Format timestamp to relative time (e.g., "2m", "1h", "3d")
|
|
118
|
-
*/
|
|
119
|
-
export const formatTimeAgo = (timestamp: string, labels?: NotificationsCenterLabels): string => {
|
|
120
|
-
const now = new Date();
|
|
121
|
-
const notificationTime = new Date(timestamp);
|
|
122
|
-
const diffInMinutes = Math.floor((now.getTime() - notificationTime.getTime()) / (1000 * 60));
|
|
123
|
-
|
|
124
|
-
const l = { ...defaultConfig.labels, ...labels };
|
|
125
|
-
|
|
126
|
-
if (diffInMinutes < 1) return l.now || 'Now';
|
|
127
|
-
if (diffInMinutes < 60) return `${diffInMinutes}${l.minutesAgo}`;
|
|
128
|
-
if (diffInMinutes < 1440) return `${Math.floor(diffInMinutes / 60)}${l.hoursAgo}`;
|
|
129
|
-
return `${Math.floor(diffInMinutes / 1440)}${l.daysAgo}`;
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Generate unique notification ID
|
|
134
|
-
*/
|
|
135
|
-
export const generateNotificationId = (): string => {
|
|
136
|
-
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
// ============================================================================
|
|
140
|
-
// HOOKS
|
|
141
|
-
// ============================================================================
|
|
142
|
-
|
|
143
|
-
export interface UseNotificationsCenterReturn {
|
|
144
|
-
notifications: NotificationData[];
|
|
145
|
-
toastNotifications: ToastNotification[];
|
|
146
|
-
dialogNotifications: DialogNotification[];
|
|
147
|
-
unreadCount: number;
|
|
148
|
-
addNotification: (notification: Omit<NotificationData, 'id' | 'timestamp' | 'isRead'>, options?: { showDialog?: boolean; showToast?: boolean; toastSeverity?: ToastNotification['severity'] }) => void;
|
|
149
|
-
markAsRead: (notificationId: string) => void;
|
|
150
|
-
markAllAsRead: () => void;
|
|
151
|
-
removeNotification: (notificationId: string) => void;
|
|
152
|
-
clearAllNotifications: () => void;
|
|
153
|
-
closeToast: (toastId: string) => void;
|
|
154
|
-
closeDialog: (notificationId: string) => void;
|
|
155
|
-
acknowledgeDialog: (notificationId: string) => void;
|
|
156
|
-
bringDialogToFront: (notificationId: string) => void;
|
|
157
|
-
updateDialogPosition: (notificationId: string, position: { x: number; y: number }) => void;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Hook for managing notifications state.
|
|
162
|
-
*
|
|
163
|
-
* State itself is delegated to dash-utils' useNotifications (the canonical
|
|
164
|
-
* implementation — this used to duplicate that logic locally). This hook is
|
|
165
|
-
* kept as a thin wrapper because its addNotification(notification, options)
|
|
166
|
-
* call — one call that optionally fans out to the base list, a dialog, and a
|
|
167
|
-
* toast — is more ergonomic for consumers than the three separate calls
|
|
168
|
-
* (addNotification/addDialogNotification/addToast) dash-utils exposes.
|
|
169
|
-
*/
|
|
170
|
-
export const useNotificationsCenter = (
|
|
171
|
-
config: NotificationsCenterConfig = {}
|
|
172
|
-
): UseNotificationsCenterReturn => {
|
|
173
|
-
const mergedConfig = { ...defaultConfig, ...config };
|
|
174
|
-
|
|
175
|
-
const base = useNotificationsState({
|
|
176
|
-
maxNotifications: mergedConfig.maxNotifications,
|
|
177
|
-
initialZIndex: mergedConfig.baseZIndex,
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
const addNotification = useCallback((
|
|
181
|
-
notification: Omit<NotificationData, 'id' | 'timestamp' | 'isRead'>,
|
|
182
|
-
options: { showDialog?: boolean; showToast?: boolean; toastSeverity?: ToastNotification['severity'] } = {}
|
|
183
|
-
) => {
|
|
184
|
-
const { showDialog = true, showToast = true, toastSeverity = 'warning' } = options;
|
|
185
|
-
|
|
186
|
-
const processedNotification = showDialog
|
|
187
|
-
? base.addDialogNotification(notification)
|
|
188
|
-
: base.addNotification(notification);
|
|
189
|
-
|
|
190
|
-
if (showToast) {
|
|
191
|
-
base.addToast({
|
|
192
|
-
title: processedNotification.title,
|
|
193
|
-
message: `${notification.customer || ''} ${notification.store ? `- ${notification.store}` : ''}`.trim(),
|
|
194
|
-
severity: toastSeverity,
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
}, [base]);
|
|
198
|
-
|
|
199
|
-
return {
|
|
200
|
-
notifications: base.notifications,
|
|
201
|
-
toastNotifications: base.toastNotifications,
|
|
202
|
-
dialogNotifications: base.dialogNotifications,
|
|
203
|
-
unreadCount: base.unreadCount,
|
|
204
|
-
addNotification,
|
|
205
|
-
markAsRead: base.markAsRead,
|
|
206
|
-
markAllAsRead: base.markAllAsRead,
|
|
207
|
-
removeNotification: base.removeNotification,
|
|
208
|
-
clearAllNotifications: base.clearAllNotifications,
|
|
209
|
-
closeToast: base.removeToast,
|
|
210
|
-
// closeDialog/acknowledgeDialog were already functionally identical
|
|
211
|
-
// (both marked read + removed the dialog) — dash-utils'
|
|
212
|
-
// removeDialogNotification does the same in one call.
|
|
213
|
-
closeDialog: base.removeDialogNotification,
|
|
214
|
-
acknowledgeDialog: base.removeDialogNotification,
|
|
215
|
-
bringDialogToFront: base.bringDialogToFront,
|
|
216
|
-
updateDialogPosition: base.updateDialogPosition,
|
|
217
|
-
};
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
// ============================================================================
|
|
221
|
-
// COMPONENTS
|
|
222
|
-
// ============================================================================
|
|
223
|
-
|
|
224
|
-
// Notification Button Component
|
|
225
|
-
export interface NotificationButtonProps {
|
|
226
|
-
unreadCount: number;
|
|
227
|
-
onClick: (event: React.MouseEvent<HTMLElement>) => void;
|
|
228
|
-
icons?: NotificationsCenterIcons;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export const NotificationButton: React.FC<NotificationButtonProps> = ({
|
|
232
|
-
unreadCount,
|
|
233
|
-
onClick,
|
|
234
|
-
icons,
|
|
235
|
-
}) => {
|
|
236
|
-
return (
|
|
237
|
-
<IconButton
|
|
238
|
-
onClick={onClick}
|
|
239
|
-
aria-label="notifications"
|
|
240
|
-
sx={{ padding: 0 }}
|
|
241
|
-
>
|
|
242
|
-
{/* Icon lives directly on the classed Avatar (not the IconButton/Badge) so it
|
|
243
|
-
picks up the design system's icon-tile colors (var(--text-color) on
|
|
244
|
-
var(--secondary-color), via the dash-icon-button-* classes) the same way
|
|
245
|
-
every other sidebar action icon does (avatar, locale switcher, dark mode
|
|
246
|
-
toggle). Classes/color on the IconButton itself (or color="inherit", the
|
|
247
|
-
original state here) don't reliably reach the icon through the extra
|
|
248
|
-
Badge wrapper layer, and left it uncolored/invisible in dark mode across
|
|
249
|
-
every app that had copy-pasted this component (fixed 2026-08-03). */}
|
|
250
|
-
<Badge badgeContent={unreadCount} color="error" max={99}>
|
|
251
|
-
<Avatar
|
|
252
|
-
sx={{ fontSize: '1rem' }}
|
|
253
|
-
style={{ width: '30px', height: '30px', minHeight: '30px' }}
|
|
254
|
-
className="dash-icon-button-color dash-icon-button-bg"
|
|
255
|
-
>
|
|
256
|
-
{unreadCount > 0
|
|
257
|
-
? (icons?.active || <NotificationsActive />)
|
|
258
|
-
: (icons?.empty || <NotificationsNone />)
|
|
259
|
-
}
|
|
260
|
-
</Avatar>
|
|
261
|
-
</Badge>
|
|
262
|
-
</IconButton>
|
|
263
|
-
);
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
// Notification Menu Item Component
|
|
267
|
-
export interface NotificationMenuItemProps {
|
|
268
|
-
notification: NotificationData;
|
|
269
|
-
onMarkAsRead: (id: string) => void;
|
|
270
|
-
onRemove: (id: string) => void;
|
|
271
|
-
formatTimeAgo: (timestamp: string) => string;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
export const NotificationMenuItem: React.FC<NotificationMenuItemProps> = ({
|
|
275
|
-
notification,
|
|
276
|
-
onMarkAsRead,
|
|
277
|
-
onRemove,
|
|
278
|
-
formatTimeAgo,
|
|
279
|
-
}) => {
|
|
280
|
-
return (
|
|
281
|
-
<MenuItem
|
|
282
|
-
onClick={() => onMarkAsRead(notification.id)}
|
|
283
|
-
sx={{
|
|
284
|
-
backgroundColor: notification.isRead ? 'transparent' : 'action.hover',
|
|
285
|
-
borderLeft: notification.isRead ? 'none' : '4px solid #ff9800',
|
|
286
|
-
whiteSpace: 'normal',
|
|
287
|
-
alignItems: 'flex-start',
|
|
288
|
-
py: 1.5,
|
|
289
|
-
}}
|
|
290
|
-
>
|
|
291
|
-
<Box sx={{ width: '100%' }}>
|
|
292
|
-
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 0.5 }}>
|
|
293
|
-
<Typography variant="subtitle2" sx={{ fontWeight: notification.isRead ? 'normal' : 'bold' }}>
|
|
294
|
-
{notification.title}
|
|
295
|
-
</Typography>
|
|
296
|
-
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
297
|
-
<Typography variant="caption" sx={{
|
|
298
|
-
color: "text.secondary"
|
|
299
|
-
}}>
|
|
300
|
-
{formatTimeAgo(notification.timestamp)}
|
|
301
|
-
</Typography>
|
|
302
|
-
<IconButton
|
|
303
|
-
size="small"
|
|
304
|
-
onClick={(e) => {
|
|
305
|
-
e.stopPropagation();
|
|
306
|
-
onRemove(notification.id);
|
|
307
|
-
}}
|
|
308
|
-
>
|
|
309
|
-
<CloseIcon fontSize="small" />
|
|
310
|
-
</IconButton>
|
|
311
|
-
</Box>
|
|
312
|
-
</Box>
|
|
313
|
-
<Typography
|
|
314
|
-
variant="body2"
|
|
315
|
-
sx={{
|
|
316
|
-
color: "text.secondary",
|
|
317
|
-
mb: 1
|
|
318
|
-
}}>
|
|
319
|
-
{notification.message}
|
|
320
|
-
</Typography>
|
|
321
|
-
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap' }}>
|
|
322
|
-
{notification.customer && (
|
|
323
|
-
<Chip
|
|
324
|
-
label={notification.customer}
|
|
325
|
-
size="small"
|
|
326
|
-
variant="outlined"
|
|
327
|
-
color="primary"
|
|
328
|
-
/>
|
|
329
|
-
)}
|
|
330
|
-
{notification.store && (
|
|
331
|
-
<Chip
|
|
332
|
-
label={notification.store}
|
|
333
|
-
size="small"
|
|
334
|
-
variant="outlined"
|
|
335
|
-
color="secondary"
|
|
336
|
-
/>
|
|
337
|
-
)}
|
|
338
|
-
</Box>
|
|
339
|
-
</Box>
|
|
340
|
-
</MenuItem>
|
|
341
|
-
);
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
// Notifications Menu Component
|
|
345
|
-
export interface NotificationsMenuProps {
|
|
346
|
-
anchorEl: HTMLElement | null;
|
|
347
|
-
notifications: NotificationData[];
|
|
348
|
-
onClose: () => void;
|
|
349
|
-
onMarkAsRead: (id: string) => void;
|
|
350
|
-
onMarkAllAsRead: () => void;
|
|
351
|
-
onRemove: (id: string) => void;
|
|
352
|
-
onClearAll: () => void;
|
|
353
|
-
config?: NotificationsCenterConfig;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
export const NotificationsMenu: React.FC<NotificationsMenuProps> = ({
|
|
357
|
-
anchorEl,
|
|
358
|
-
notifications,
|
|
359
|
-
onClose,
|
|
360
|
-
onMarkAsRead,
|
|
361
|
-
onMarkAllAsRead,
|
|
362
|
-
onRemove,
|
|
363
|
-
onClearAll,
|
|
364
|
-
config = {},
|
|
365
|
-
}) => {
|
|
366
|
-
const mergedConfig = { ...defaultConfig, ...config };
|
|
367
|
-
const labels = { ...defaultConfig.labels, ...config.labels };
|
|
368
|
-
const open = Boolean(anchorEl);
|
|
369
|
-
const unreadCount = notifications.filter(n => !n.isRead).length;
|
|
370
|
-
|
|
371
|
-
return (
|
|
372
|
-
<Menu
|
|
373
|
-
anchorEl={anchorEl}
|
|
374
|
-
open={open}
|
|
375
|
-
onClose={onClose}
|
|
376
|
-
PaperProps={{
|
|
377
|
-
style: {
|
|
378
|
-
maxHeight: mergedConfig.menuMaxHeight,
|
|
379
|
-
width: mergedConfig.menuWidth,
|
|
380
|
-
},
|
|
381
|
-
}}
|
|
382
|
-
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
|
|
383
|
-
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
|
|
384
|
-
>
|
|
385
|
-
<Box sx={{ p: 2, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
386
|
-
<Typography variant="h6">{labels.title}</Typography>
|
|
387
|
-
{notifications.length > 0 && (
|
|
388
|
-
<Box>
|
|
389
|
-
{unreadCount > 0 && (
|
|
390
|
-
<Button size="small" onClick={onMarkAllAsRead} sx={{ mr: 1 }}>
|
|
391
|
-
{labels.markAll}
|
|
392
|
-
</Button>
|
|
393
|
-
)}
|
|
394
|
-
<IconButton size="small" onClick={onClearAll}>
|
|
395
|
-
<ClearIcon fontSize="small" />
|
|
396
|
-
</IconButton>
|
|
397
|
-
</Box>
|
|
398
|
-
)}
|
|
399
|
-
</Box>
|
|
400
|
-
<Divider />
|
|
401
|
-
{notifications.length === 0 ? (
|
|
402
|
-
<MenuItem disabled>
|
|
403
|
-
<Typography sx={{
|
|
404
|
-
color: "text.secondary"
|
|
405
|
-
}}>{labels.noNotifications}</Typography>
|
|
406
|
-
</MenuItem>
|
|
407
|
-
) : (
|
|
408
|
-
notifications.map((notification) => (
|
|
409
|
-
<NotificationMenuItem
|
|
410
|
-
key={notification.id}
|
|
411
|
-
notification={notification}
|
|
412
|
-
onMarkAsRead={onMarkAsRead}
|
|
413
|
-
onRemove={onRemove}
|
|
414
|
-
formatTimeAgo={(ts) => formatTimeAgo(ts, labels)}
|
|
415
|
-
/>
|
|
416
|
-
))
|
|
417
|
-
)}
|
|
418
|
-
</Menu>
|
|
419
|
-
);
|
|
420
|
-
};
|
|
421
|
-
|
|
422
|
-
// Toast Notifications Stack Component
|
|
423
|
-
export interface ToastNotificationsStackProps {
|
|
424
|
-
toasts: ToastNotification[];
|
|
425
|
-
onClose: (toastId: string) => void;
|
|
426
|
-
autoHideDuration?: number;
|
|
427
|
-
baseZIndex?: number;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
export const ToastNotificationsStack: React.FC<ToastNotificationsStackProps> = ({
|
|
431
|
-
toasts,
|
|
432
|
-
onClose,
|
|
433
|
-
autoHideDuration = 8000,
|
|
434
|
-
baseZIndex = 100000,
|
|
435
|
-
}) => {
|
|
436
|
-
return (
|
|
437
|
-
<Stack
|
|
438
|
-
spacing={1}
|
|
439
|
-
sx={{
|
|
440
|
-
position: 'fixed',
|
|
441
|
-
top: 16,
|
|
442
|
-
right: 16,
|
|
443
|
-
zIndex: baseZIndex,
|
|
444
|
-
maxWidth: 400,
|
|
445
|
-
}}
|
|
446
|
-
>
|
|
447
|
-
{toasts.map((toast) => (
|
|
448
|
-
<Snackbar
|
|
449
|
-
key={toast.id}
|
|
450
|
-
open={true}
|
|
451
|
-
autoHideDuration={autoHideDuration}
|
|
452
|
-
onClose={() => onClose(toast.id)}
|
|
453
|
-
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
|
|
454
|
-
sx={{ position: 'relative', zIndex: baseZIndex }}
|
|
455
|
-
>
|
|
456
|
-
<Alert
|
|
457
|
-
onClose={() => onClose(toast.id)}
|
|
458
|
-
severity={toast.severity}
|
|
459
|
-
variant="filled"
|
|
460
|
-
sx={{ width: '100%' }}
|
|
461
|
-
>
|
|
462
|
-
<Typography variant="subtitle2">{toast.title}</Typography>
|
|
463
|
-
<Typography variant="body2">{toast.message}</Typography>
|
|
464
|
-
</Alert>
|
|
465
|
-
</Snackbar>
|
|
466
|
-
))}
|
|
467
|
-
</Stack>
|
|
468
|
-
);
|
|
469
|
-
};
|
|
470
|
-
|
|
471
|
-
// Draggable Notification Dialog Component
|
|
472
|
-
export interface DraggableNotificationDialogProps {
|
|
473
|
-
notification: DialogNotification;
|
|
474
|
-
onClose: () => void;
|
|
475
|
-
onAcknowledge: () => void;
|
|
476
|
-
onFocus: () => void;
|
|
477
|
-
onPositionChange: (position: { x: number; y: number }) => void;
|
|
478
|
-
config?: NotificationsCenterConfig;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
export const DraggableNotificationDialog: React.FC<DraggableNotificationDialogProps> = ({
|
|
482
|
-
notification,
|
|
483
|
-
onClose,
|
|
484
|
-
onAcknowledge,
|
|
485
|
-
onFocus,
|
|
486
|
-
onPositionChange,
|
|
487
|
-
config = {},
|
|
488
|
-
}) => {
|
|
489
|
-
const labels = { ...defaultConfig.labels, ...config.labels };
|
|
490
|
-
const icons = { ...defaultConfig.icons, ...config.icons };
|
|
491
|
-
|
|
492
|
-
const [isDragging, setIsDragging] = useState(false);
|
|
493
|
-
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
|
|
494
|
-
|
|
495
|
-
const handleMouseDown = useCallback((e: React.MouseEvent) => {
|
|
496
|
-
if ((e.target as HTMLElement).closest('.drag-handle')) {
|
|
497
|
-
e.preventDefault();
|
|
498
|
-
onFocus();
|
|
499
|
-
setIsDragging(true);
|
|
500
|
-
setDragOffset({
|
|
501
|
-
x: e.clientX - notification.position.x,
|
|
502
|
-
y: e.clientY - notification.position.y,
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
}, [onFocus, notification.position]);
|
|
506
|
-
|
|
507
|
-
const handleMouseMove = useCallback((e: MouseEvent) => {
|
|
508
|
-
if (isDragging) {
|
|
509
|
-
e.preventDefault();
|
|
510
|
-
const newX = Math.max(0, Math.min(window.innerWidth - 400, e.clientX - dragOffset.x));
|
|
511
|
-
const newY = Math.max(0, Math.min(window.innerHeight - 200, e.clientY - dragOffset.y));
|
|
512
|
-
onPositionChange({ x: newX, y: newY });
|
|
513
|
-
}
|
|
514
|
-
}, [isDragging, dragOffset, onPositionChange]);
|
|
515
|
-
|
|
516
|
-
const handleMouseUp = useCallback(() => {
|
|
517
|
-
if (isDragging) {
|
|
518
|
-
setIsDragging(false);
|
|
519
|
-
}
|
|
520
|
-
}, [isDragging]);
|
|
521
|
-
|
|
522
|
-
useEffect(() => {
|
|
523
|
-
if (isDragging) {
|
|
524
|
-
document.addEventListener('mousemove', handleMouseMove);
|
|
525
|
-
document.addEventListener('mouseup', handleMouseUp);
|
|
526
|
-
document.body.style.userSelect = 'none';
|
|
527
|
-
return () => {
|
|
528
|
-
document.removeEventListener('mousemove', handleMouseMove);
|
|
529
|
-
document.removeEventListener('mouseup', handleMouseUp);
|
|
530
|
-
document.body.style.userSelect = '';
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
}, [isDragging, handleMouseMove, handleMouseUp]);
|
|
534
|
-
|
|
535
|
-
return (
|
|
536
|
-
<Portal>
|
|
537
|
-
<div
|
|
538
|
-
style={{
|
|
539
|
-
position: 'fixed',
|
|
540
|
-
left: notification.position.x,
|
|
541
|
-
top: notification.position.y,
|
|
542
|
-
zIndex: notification.zIndex,
|
|
543
|
-
pointerEvents: 'auto',
|
|
544
|
-
}}
|
|
545
|
-
onMouseDown={handleMouseDown}
|
|
546
|
-
>
|
|
547
|
-
<Paper
|
|
548
|
-
elevation={8}
|
|
549
|
-
sx={{
|
|
550
|
-
width: 400,
|
|
551
|
-
borderRadius: 2,
|
|
552
|
-
overflow: 'hidden',
|
|
553
|
-
boxShadow: '0 8px 32px rgba(0,0,0,0.24)',
|
|
554
|
-
cursor: isDragging ? 'grabbing' : 'default',
|
|
555
|
-
'&:hover': {
|
|
556
|
-
boxShadow: '0 12px 40px rgba(0,0,0,0.3)',
|
|
557
|
-
},
|
|
558
|
-
}}
|
|
559
|
-
>
|
|
560
|
-
{/* Header */}
|
|
561
|
-
<Box
|
|
562
|
-
className="drag-handle"
|
|
563
|
-
sx={{
|
|
564
|
-
backgroundColor: 'warning.main',
|
|
565
|
-
color: 'warning.contrastText',
|
|
566
|
-
p: 2,
|
|
567
|
-
display: 'flex',
|
|
568
|
-
alignItems: 'center',
|
|
569
|
-
gap: 1,
|
|
570
|
-
cursor: isDragging ? 'grabbing' : 'grab',
|
|
571
|
-
userSelect: 'none',
|
|
572
|
-
}}
|
|
573
|
-
>
|
|
574
|
-
<DragIcon sx={{ mr: 1 }} />
|
|
575
|
-
<NotificationsActive />
|
|
576
|
-
<Typography variant="h6" sx={{ flexGrow: 1, fontSize: '1rem' }}>
|
|
577
|
-
{notification.title}
|
|
578
|
-
</Typography>
|
|
579
|
-
<IconButton
|
|
580
|
-
size="small"
|
|
581
|
-
onClick={(e) => {
|
|
582
|
-
e.stopPropagation();
|
|
583
|
-
onClose();
|
|
584
|
-
}}
|
|
585
|
-
sx={{ color: 'warning.contrastText' }}
|
|
586
|
-
>
|
|
587
|
-
<CloseIcon fontSize="small" />
|
|
588
|
-
</IconButton>
|
|
589
|
-
</Box>
|
|
590
|
-
|
|
591
|
-
{/* Content */}
|
|
592
|
-
<Box sx={{ p: 2 }}>
|
|
593
|
-
<Typography variant="body1" sx={{ mb: 2 }}>
|
|
594
|
-
{notification.message}
|
|
595
|
-
</Typography>
|
|
596
|
-
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
|
|
597
|
-
{notification.customer && (
|
|
598
|
-
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
599
|
-
{icons.customer || <PersonIcon color="primary" />}
|
|
600
|
-
<Typography variant="body2" sx={{
|
|
601
|
-
color: "text.secondary"
|
|
602
|
-
}}>
|
|
603
|
-
{labels.customer}
|
|
604
|
-
</Typography>
|
|
605
|
-
<Chip
|
|
606
|
-
label={notification.customer}
|
|
607
|
-
size="small"
|
|
608
|
-
color="primary"
|
|
609
|
-
variant="outlined"
|
|
610
|
-
/>
|
|
611
|
-
</Box>
|
|
612
|
-
)}
|
|
613
|
-
{notification.store && (
|
|
614
|
-
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
615
|
-
{icons.store || <StoreIcon color="secondary" />}
|
|
616
|
-
<Typography variant="body2" sx={{
|
|
617
|
-
color: "text.secondary"
|
|
618
|
-
}}>
|
|
619
|
-
{labels.store}
|
|
620
|
-
</Typography>
|
|
621
|
-
<Chip
|
|
622
|
-
label={notification.store}
|
|
623
|
-
size="small"
|
|
624
|
-
color="secondary"
|
|
625
|
-
variant="outlined"
|
|
626
|
-
/>
|
|
627
|
-
</Box>
|
|
628
|
-
)}
|
|
629
|
-
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mt: 1 }}>
|
|
630
|
-
<Typography variant="caption" sx={{
|
|
631
|
-
color: "text.secondary"
|
|
632
|
-
}}>
|
|
633
|
-
{labels.received} {formatTimeAgo(notification.timestamp, labels)}
|
|
634
|
-
</Typography>
|
|
635
|
-
</Box>
|
|
636
|
-
</Box>
|
|
637
|
-
</Box>
|
|
638
|
-
|
|
639
|
-
{/* Footer */}
|
|
640
|
-
<Box sx={{ p: 2, pt: 0, display: 'flex', gap: 1, justifyContent: 'flex-end' }}>
|
|
641
|
-
<Button
|
|
642
|
-
onClick={(e) => {
|
|
643
|
-
e.stopPropagation();
|
|
644
|
-
onClose();
|
|
645
|
-
}}
|
|
646
|
-
color="inherit"
|
|
647
|
-
variant="outlined"
|
|
648
|
-
size="small"
|
|
649
|
-
>
|
|
650
|
-
{labels.close}
|
|
651
|
-
</Button>
|
|
652
|
-
<Button
|
|
653
|
-
onClick={(e) => {
|
|
654
|
-
e.stopPropagation();
|
|
655
|
-
onAcknowledge();
|
|
656
|
-
}}
|
|
657
|
-
color="primary"
|
|
658
|
-
variant="contained"
|
|
659
|
-
size="small"
|
|
660
|
-
>
|
|
661
|
-
{labels.acknowledge}
|
|
662
|
-
</Button>
|
|
663
|
-
</Box>
|
|
664
|
-
</Paper>
|
|
665
|
-
</div>
|
|
666
|
-
</Portal>
|
|
667
|
-
);
|
|
668
|
-
};
|
|
669
|
-
|
|
670
|
-
// Main NotificationsCenter Component
|
|
671
|
-
export interface NotificationsCenterProps {
|
|
672
|
-
/** Configuration options */
|
|
673
|
-
config?: NotificationsCenterConfig;
|
|
674
|
-
/** External notifications hook (if managed externally) */
|
|
675
|
-
notificationsHook?: UseNotificationsCenterReturn;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
export const NotificationsCenter: React.FC<NotificationsCenterProps> = ({
|
|
679
|
-
config = {},
|
|
680
|
-
notificationsHook,
|
|
681
|
-
}) => {
|
|
682
|
-
const internalHook = useNotificationsCenter(config);
|
|
683
|
-
const hook = notificationsHook || internalHook;
|
|
684
|
-
|
|
685
|
-
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
|
686
|
-
|
|
687
|
-
const handleNotificationClick = (event: React.MouseEvent<HTMLElement>) => {
|
|
688
|
-
setAnchorEl(event.currentTarget);
|
|
689
|
-
};
|
|
690
|
-
|
|
691
|
-
const handleClose = () => {
|
|
692
|
-
setAnchorEl(null);
|
|
693
|
-
};
|
|
694
|
-
|
|
695
|
-
const handleClearAll = () => {
|
|
696
|
-
hook.clearAllNotifications();
|
|
697
|
-
handleClose();
|
|
698
|
-
};
|
|
699
|
-
|
|
700
|
-
return (
|
|
701
|
-
<>
|
|
702
|
-
<NotificationButton
|
|
703
|
-
unreadCount={hook.unreadCount}
|
|
704
|
-
onClick={handleNotificationClick}
|
|
705
|
-
icons={config.icons}
|
|
706
|
-
/>
|
|
707
|
-
|
|
708
|
-
<NotificationsMenu
|
|
709
|
-
anchorEl={anchorEl}
|
|
710
|
-
notifications={hook.notifications}
|
|
711
|
-
onClose={handleClose}
|
|
712
|
-
onMarkAsRead={hook.markAsRead}
|
|
713
|
-
onMarkAllAsRead={hook.markAllAsRead}
|
|
714
|
-
onRemove={hook.removeNotification}
|
|
715
|
-
onClearAll={handleClearAll}
|
|
716
|
-
config={config}
|
|
717
|
-
/>
|
|
718
|
-
|
|
719
|
-
{/* Stackable Draggable Notification Dialogs */}
|
|
720
|
-
{hook.dialogNotifications.map((dialogNotification) => (
|
|
721
|
-
<DraggableNotificationDialog
|
|
722
|
-
key={dialogNotification.id}
|
|
723
|
-
notification={dialogNotification}
|
|
724
|
-
onClose={() => hook.closeDialog(dialogNotification.id)}
|
|
725
|
-
onAcknowledge={() => hook.acknowledgeDialog(dialogNotification.id)}
|
|
726
|
-
onFocus={() => hook.bringDialogToFront(dialogNotification.id)}
|
|
727
|
-
onPositionChange={(newPosition) => hook.updateDialogPosition(dialogNotification.id, newPosition)}
|
|
728
|
-
config={config}
|
|
729
|
-
/>
|
|
730
|
-
))}
|
|
731
|
-
|
|
732
|
-
{/* Toast Notifications Stack */}
|
|
733
|
-
<ToastNotificationsStack
|
|
734
|
-
toasts={hook.toastNotifications}
|
|
735
|
-
onClose={hook.closeToast}
|
|
736
|
-
autoHideDuration={config.toastAutoHideDuration}
|
|
737
|
-
baseZIndex={config.baseZIndex}
|
|
738
|
-
/>
|
|
739
|
-
</>
|
|
740
|
-
);
|
|
741
|
-
};
|
|
742
|
-
|
|
743
|
-
export default NotificationsCenter;
|