@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.
Files changed (60) hide show
  1. package/package.json +24 -21
  2. package/src/components/AuditLog/AuditLog.tsx +0 -490
  3. package/src/components/AuditLog/index.ts +0 -2
  4. package/src/components/AutoCompleteArrayInput/AutoCompleteCheckBoxInput.tsx +0 -917
  5. package/src/components/AutoCompleteArrayInput/AutocompleteCheckBoxArrayInput.tsx +0 -39
  6. package/src/components/AutoCompleteArrayInputAutoSearch/AutoCompleteCheckBoxInput.tsx +0 -916
  7. package/src/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayAutoSearch.tsx.wip +0 -42
  8. package/src/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayInput.tsx +0 -44
  9. package/src/components/Json/Json.tsx +0 -312
  10. package/src/components/JsonColorSelector/JsonColorSelector.tsx +0 -1443
  11. package/src/components/JsonColorSelector/JsonColorSelector.tsx.bkup +0 -746
  12. package/src/components/JsonColorSelector/JsonColorSelectorEnhanced.tsx +0 -1135
  13. package/src/components/JsonColorSelector/JsonCssVarValues.tsx +0 -867
  14. package/src/components/JsonColorSelector/components/BasicColorSelector.tsx +0 -333
  15. package/src/components/JsonColorSelector/components/ColorEditDialog.tsx +0 -495
  16. package/src/components/JsonColorSelector/components/ColorPaletteItem.tsx +0 -80
  17. package/src/components/JsonColorSelector/components/ImageColorExtractor.tsx +0 -420
  18. package/src/components/JsonColorSelector/helpers/functions.tsx +0 -123
  19. package/src/components/JsonColorSelector/helpers/paletteDerivation.ts +0 -367
  20. package/src/components/JsonColorSelector/interfaces/interfaces.tsx +0 -22
  21. package/src/components/JsonColorSelector/useAsyncColorThief.tsx +0 -97
  22. package/src/components/ListActive/ListActive.tsx +0 -106
  23. package/src/components/ListActive/ListBoolean.tsx +0 -96
  24. package/src/components/NotificationPreferences/NotificationPreferences.tsx +0 -480
  25. package/src/components/NotificationPreferences/index.tsx +0 -2
  26. package/src/components/SearchableSelects/RASearchableSelect.tsx +0 -303
  27. package/src/components/SearchableSelects/RASearchableSelectCheckboxes.tsx +0 -617
  28. package/src/components/SearchableSelects/RASearchableSelectChips.tsx +0 -561
  29. package/src/components/SearchableSelects/RASearchableSelectChipsRecordContext.tsx +0 -366
  30. package/src/components/SearchableSelects/SearchableSelect.tsx +0 -323
  31. package/src/components/SearchableSelects/SearchableSelectCheckboxes.tsx +0 -358
  32. package/src/components/SortableDataGrid/README.md +0 -1
  33. package/src/components/SortableDataGrid/SortableDataGrid.tsx +0 -56
  34. package/src/components/SortableDataGrid/SortableDataGridBody.tsx +0 -175
  35. package/src/components/SortableDataGrid/SortableDataGridHeader.tsx +0 -171
  36. package/src/components/SortableDataGrid/SortableDataGridRow.tsx +0 -247
  37. package/src/components/Upload/SingleImageUploader.tsx +0 -55
  38. package/src/components/custom/PackageCopyMethod.tsx +0 -109
  39. package/src/components/custom/PackageRadioButtonGroup.tsx +0 -49
  40. package/src/components/dialog/Basic.tsx +0 -11
  41. package/src/components/dialog/Correct.tsx +0 -16
  42. package/src/components/dialog/Error.tsx +0 -15
  43. package/src/components/dialog/Info.tsx +0 -15
  44. package/src/components/dialog/InfoV2.tsx +0 -35
  45. package/src/components/dialog/NotFound.tsx +0 -18
  46. package/src/components/dialog/QuickSearch.tsx +0 -115
  47. package/src/components/notifications/NotificationsCenter.tsx +0 -743
  48. package/src/components/notifications/index.ts +0 -42
  49. package/src/components/tables/PaginationComponent.tsx +0 -17
  50. package/src/components/theme/AppLoadingFallback.tsx +0 -42
  51. package/src/components/theme/ErrorBoundary.tsx +0 -57
  52. package/src/components/theme/InitialLoader.tsx +0 -16
  53. package/src/components/theme/NotFound.tsx +0 -124
  54. package/src/components/theme/NotResults.tsx +0 -56
  55. package/src/hooks/useDraggable.ts +0 -11
  56. package/src/hooks/useNotifications.ts +0 -14
  57. package/src/hooks/useQuickSearch.tsx +0 -29
  58. package/src/index.tsx +0 -77
  59. package/src/styles/index.less +0 -0
  60. 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;