qdadm 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/layout/AppLayout.vue +3 -1
- package/src/composables/useEntityItemPage.ts +2 -0
- package/src/composables/useEntityItemShowPage.ts +13 -2
- package/src/composables/useSidebarState.ts +24 -0
- package/src/index.ts +1 -0
- package/src/notifications/NotificationPanel.vue +84 -84
- package/src/notifications/styles.scss +9 -4
package/package.json
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { ref, watch, onMounted, onUnmounted, computed, inject, provide, useSlots } from 'vue'
|
|
16
|
+
import { SIDEBAR_COLLAPSED_KEY } from '../../composables/useSidebarState'
|
|
16
17
|
import { RouterLink, RouterView, useRouter, useRoute } from 'vue-router'
|
|
17
18
|
import { useNavigation, type NavSection } from '../../composables/useNavigation'
|
|
18
19
|
import { useApp } from '../../composables/useApp'
|
|
@@ -69,8 +70,9 @@ const collapsedSections = ref<Record<string, boolean>>({})
|
|
|
69
70
|
const sidebarOpen = ref<boolean>(false)
|
|
70
71
|
const MOBILE_BREAKPOINT = 768
|
|
71
72
|
|
|
72
|
-
// Desktop sidebar collapsed state
|
|
73
|
+
// Desktop sidebar collapsed state (provided for child components via useSidebarState)
|
|
73
74
|
const sidebarCollapsed = ref<boolean>(false)
|
|
75
|
+
provide(SIDEBAR_COLLAPSED_KEY, sidebarCollapsed)
|
|
74
76
|
const COLLAPSED_STORAGE_KEY = computed<string>(() => `${app.shortName.toLowerCase()}_sidebar_collapsed`)
|
|
75
77
|
|
|
76
78
|
function toggleSidebar(): void {
|
|
@@ -78,6 +78,8 @@ export interface EntityManager {
|
|
|
78
78
|
canCreate: () => boolean
|
|
79
79
|
canUpdate: (data?: unknown) => boolean
|
|
80
80
|
canDelete: (data?: unknown) => boolean
|
|
81
|
+
hasSeverityMap?: (field: string) => boolean
|
|
82
|
+
getSeverity?: (field: string, value: string | number, defaultSeverity?: string) => string
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
/**
|
|
@@ -384,10 +384,11 @@ export function useEntityItemShowPage(
|
|
|
384
384
|
schemaType = type,
|
|
385
385
|
label = '',
|
|
386
386
|
reference = '',
|
|
387
|
+
severity,
|
|
387
388
|
...rest
|
|
388
389
|
} = fieldConfig
|
|
389
390
|
|
|
390
|
-
|
|
391
|
+
let resolvedDisplayType = TYPE_MAPPINGS[type] || TYPE_MAPPINGS[schemaType] || 'text'
|
|
391
392
|
const resolvedLabel = label || snakeCaseToTitle(name)
|
|
392
393
|
|
|
393
394
|
// Auto-set reference route if reference entity is specified
|
|
@@ -402,13 +403,23 @@ export function useEntityItemShowPage(
|
|
|
402
403
|
}
|
|
403
404
|
}
|
|
404
405
|
|
|
406
|
+
// Auto-inject severity from manager's severity maps
|
|
407
|
+
let resolvedSeverity = severity
|
|
408
|
+
if (!resolvedSeverity && manager.hasSeverityMap?.(name)) {
|
|
409
|
+
resolvedSeverity = (value: unknown) => manager.getSeverity!(name, value as string | number, 'secondary')
|
|
410
|
+
if (resolvedDisplayType === 'text') {
|
|
411
|
+
resolvedDisplayType = 'badge'
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
405
415
|
return {
|
|
406
416
|
name,
|
|
407
|
-
type:
|
|
417
|
+
type: resolvedDisplayType,
|
|
408
418
|
schemaType,
|
|
409
419
|
label: resolvedLabel,
|
|
410
420
|
reference,
|
|
411
421
|
referenceRoute,
|
|
422
|
+
severity: resolvedSeverity,
|
|
412
423
|
...rest,
|
|
413
424
|
}
|
|
414
425
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useSidebarState - Access sidebar collapsed state from any component
|
|
3
|
+
*
|
|
4
|
+
* The sidebar collapsed ref is provided by AppLayout via Vue's provide/inject.
|
|
5
|
+
* This composable gives blocks rendered inside the sidebar (including zone blocks)
|
|
6
|
+
* programmatic access to the collapsed state.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { useSidebarState } from 'qdadm'
|
|
11
|
+
*
|
|
12
|
+
* const { collapsed } = useSidebarState()
|
|
13
|
+
* // collapsed.value === true when sidebar is in icon-only mode
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { inject, ref, type Ref } from 'vue'
|
|
18
|
+
|
|
19
|
+
export const SIDEBAR_COLLAPSED_KEY = Symbol('qdadm-sidebar-collapsed') as symbol & { __type: Ref<boolean> }
|
|
20
|
+
|
|
21
|
+
export function useSidebarState(): { collapsed: Ref<boolean> } {
|
|
22
|
+
const collapsed = inject<Ref<boolean>>(SIDEBAR_COLLAPSED_KEY, ref(false))
|
|
23
|
+
return { collapsed }
|
|
24
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -291,6 +291,7 @@ export {
|
|
|
291
291
|
} from './composables/useUserImpersonator'
|
|
292
292
|
export { useCurrentEntity, type UseCurrentEntityReturn } from './composables/useCurrentEntity'
|
|
293
293
|
export { useChildPage, type UseChildPageReturn } from './composables/useChildPage'
|
|
294
|
+
export { useSidebarState, SIDEBAR_COLLAPSED_KEY } from './composables/useSidebarState'
|
|
294
295
|
|
|
295
296
|
// ════════════════════════════════════════════════════════════════════════════
|
|
296
297
|
// COMPONENTS
|
|
@@ -8,11 +8,10 @@
|
|
|
8
8
|
* - Mobile: Full width overlay at bottom of screen
|
|
9
9
|
*
|
|
10
10
|
* Features:
|
|
11
|
-
* -
|
|
11
|
+
* - Inline action bar (mark read, clear, close) at top right
|
|
12
12
|
* - Status items section (custom module items)
|
|
13
13
|
* - Notification list (most recent first)
|
|
14
14
|
* - Empty state
|
|
15
|
-
* - Closable
|
|
16
15
|
*/
|
|
17
16
|
import { RouterLink } from 'vue-router'
|
|
18
17
|
import { useNotifications } from './NotificationStore'
|
|
@@ -55,28 +54,26 @@ function formatTime(timestamp: number): string {
|
|
|
55
54
|
v-if="store.isOpen.value"
|
|
56
55
|
class="notification-panel"
|
|
57
56
|
>
|
|
58
|
-
<!--
|
|
59
|
-
<div class="notification-
|
|
60
|
-
<div class="notification-panel-actions">
|
|
61
|
-
<button
|
|
62
|
-
v-if="store.unreadCount.value > 0"
|
|
63
|
-
class="notification-panel-btn"
|
|
64
|
-
title="Mark all read"
|
|
65
|
-
@click="store.markAllRead()"
|
|
66
|
-
>
|
|
67
|
-
<i class="pi pi-check-circle" />
|
|
68
|
-
</button>
|
|
69
|
-
<button
|
|
70
|
-
v-if="store.notifications.value.length > 0"
|
|
71
|
-
class="notification-panel-btn"
|
|
72
|
-
title="Clear all"
|
|
73
|
-
@click="store.clearNotifications()"
|
|
74
|
-
>
|
|
75
|
-
<i class="pi pi-trash" />
|
|
76
|
-
</button>
|
|
77
|
-
</div>
|
|
57
|
+
<!-- Inline toolbar -->
|
|
58
|
+
<div class="notification-toolbar">
|
|
78
59
|
<button
|
|
79
|
-
|
|
60
|
+
v-if="store.unreadCount.value > 0"
|
|
61
|
+
class="notification-toolbar-btn"
|
|
62
|
+
title="Mark all read"
|
|
63
|
+
@click="store.markAllRead()"
|
|
64
|
+
>
|
|
65
|
+
<i class="pi pi-check-circle" />
|
|
66
|
+
</button>
|
|
67
|
+
<button
|
|
68
|
+
v-if="store.notifications.value.length > 0"
|
|
69
|
+
class="notification-toolbar-btn"
|
|
70
|
+
title="Clear all"
|
|
71
|
+
@click="store.clearNotifications()"
|
|
72
|
+
>
|
|
73
|
+
<i class="pi pi-trash" />
|
|
74
|
+
</button>
|
|
75
|
+
<button
|
|
76
|
+
class="notification-toolbar-btn notification-toolbar-close"
|
|
80
77
|
title="Close"
|
|
81
78
|
@click="store.close()"
|
|
82
79
|
>
|
|
@@ -159,11 +156,12 @@ function formatTime(timestamp: number): string {
|
|
|
159
156
|
left: calc(var(--fad-sidebar-width, 15rem) + 0.375rem);
|
|
160
157
|
bottom: 0.5rem;
|
|
161
158
|
width: 22rem;
|
|
159
|
+
min-height: 6.6rem;
|
|
162
160
|
max-height: 50vh;
|
|
163
161
|
background: var(--p-surface-0, #ffffff);
|
|
164
162
|
border: 1px solid var(--p-surface-200, #e2e8f0);
|
|
165
|
-
border-radius:
|
|
166
|
-
box-shadow: 0
|
|
163
|
+
border-radius: 2px;
|
|
164
|
+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
|
167
165
|
display: flex;
|
|
168
166
|
flex-direction: column;
|
|
169
167
|
z-index: 200;
|
|
@@ -171,51 +169,51 @@ function formatTime(timestamp: number): string {
|
|
|
171
169
|
}
|
|
172
170
|
|
|
173
171
|
/* Collapsed sidebar */
|
|
174
|
-
.sidebar--collapsed ~ .main-area .notification-panel,
|
|
175
172
|
:root:has(.sidebar--collapsed) .notification-panel {
|
|
176
173
|
left: calc(var(--fad-sidebar-width-collapsed, 2.5rem) + 0.375rem);
|
|
177
174
|
}
|
|
178
175
|
|
|
179
|
-
/*
|
|
180
|
-
.notification-
|
|
176
|
+
/* Inline toolbar - floats top-right inside the panel */
|
|
177
|
+
.notification-toolbar {
|
|
178
|
+
position: absolute;
|
|
179
|
+
top: 0;
|
|
180
|
+
right: 0;
|
|
181
181
|
display: flex;
|
|
182
182
|
align-items: center;
|
|
183
|
-
|
|
184
|
-
padding: 0.
|
|
185
|
-
|
|
186
|
-
background: var(--p-surface-50, #f8fafc);
|
|
187
|
-
flex-shrink: 0;
|
|
183
|
+
gap: 1px;
|
|
184
|
+
padding: 0.25rem;
|
|
185
|
+
z-index: 1;
|
|
188
186
|
}
|
|
189
187
|
|
|
190
|
-
.notification-
|
|
191
|
-
display: flex;
|
|
192
|
-
align-items: center;
|
|
193
|
-
gap: 0.125rem;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
.notification-panel-btn {
|
|
188
|
+
.notification-toolbar-btn {
|
|
197
189
|
display: inline-flex;
|
|
198
190
|
align-items: center;
|
|
199
191
|
justify-content: center;
|
|
200
|
-
width: 1.
|
|
201
|
-
height: 1.
|
|
192
|
+
width: 1.375rem;
|
|
193
|
+
height: 1.375rem;
|
|
202
194
|
border: none;
|
|
203
195
|
background: none;
|
|
204
|
-
border-radius:
|
|
205
|
-
color: var(--p-surface-
|
|
196
|
+
border-radius: 2px;
|
|
197
|
+
color: var(--p-surface-400, #94a3b8);
|
|
206
198
|
cursor: pointer;
|
|
207
|
-
font-size: 0.
|
|
199
|
+
font-size: 0.6875rem;
|
|
200
|
+
transition: color 0.1s, background 0.1s;
|
|
208
201
|
}
|
|
209
202
|
|
|
210
|
-
.notification-
|
|
211
|
-
background: var(--p-surface-
|
|
203
|
+
.notification-toolbar-btn:hover {
|
|
204
|
+
background: var(--p-surface-100, #f1f5f9);
|
|
205
|
+
color: var(--p-surface-600, #475569);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.notification-toolbar-close:hover {
|
|
212
209
|
color: var(--p-surface-700, #334155);
|
|
213
210
|
}
|
|
214
211
|
|
|
215
212
|
/* Status items */
|
|
216
213
|
.notification-panel-status {
|
|
217
|
-
padding: 0.5rem 0.
|
|
218
|
-
|
|
214
|
+
padding: 0.5rem 0.625rem;
|
|
215
|
+
padding-right: 2.5rem;
|
|
216
|
+
border-bottom: 1px solid var(--p-surface-100, #f1f5f9);
|
|
219
217
|
flex-shrink: 0;
|
|
220
218
|
}
|
|
221
219
|
|
|
@@ -223,13 +221,13 @@ function formatTime(timestamp: number): string {
|
|
|
223
221
|
display: flex;
|
|
224
222
|
align-items: center;
|
|
225
223
|
gap: 0.375rem;
|
|
226
|
-
padding: 0.
|
|
224
|
+
padding: 0.1875rem 0;
|
|
227
225
|
font-size: 0.75rem;
|
|
228
226
|
color: var(--p-surface-600, #475569);
|
|
229
227
|
}
|
|
230
228
|
|
|
231
229
|
.notification-status-item i {
|
|
232
|
-
font-size: 0.
|
|
230
|
+
font-size: 0.6875rem;
|
|
233
231
|
}
|
|
234
232
|
|
|
235
233
|
.notification-status-item--nominal i {
|
|
@@ -250,22 +248,24 @@ function formatTime(timestamp: number): string {
|
|
|
250
248
|
|
|
251
249
|
.notification-status-count {
|
|
252
250
|
font-weight: 600;
|
|
251
|
+
font-size: 0.6875rem;
|
|
253
252
|
}
|
|
254
253
|
|
|
255
254
|
.notification-status-item--link {
|
|
256
255
|
text-decoration: none;
|
|
257
256
|
cursor: pointer;
|
|
258
|
-
border-radius:
|
|
259
|
-
padding: 0.
|
|
257
|
+
border-radius: 2px;
|
|
258
|
+
padding: 0.1875rem 0.375rem;
|
|
260
259
|
margin: 0 -0.375rem;
|
|
260
|
+
transition: background 0.1s;
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
.notification-status-item--link:hover {
|
|
264
|
-
background: var(--p-surface-
|
|
264
|
+
background: var(--p-surface-50, #f8fafc);
|
|
265
265
|
}
|
|
266
266
|
|
|
267
267
|
.notification-status-arrow {
|
|
268
|
-
font-size: 0.
|
|
268
|
+
font-size: 0.5625rem;
|
|
269
269
|
opacity: 0;
|
|
270
270
|
transition: opacity 0.1s;
|
|
271
271
|
color: var(--p-surface-400, #94a3b8);
|
|
@@ -287,12 +287,16 @@ function formatTime(timestamp: number): string {
|
|
|
287
287
|
display: flex;
|
|
288
288
|
align-items: flex-start;
|
|
289
289
|
gap: 0.5rem;
|
|
290
|
-
padding: 0.5rem 0.
|
|
291
|
-
border-bottom: 1px solid var(--p-surface-
|
|
290
|
+
padding: 0.5rem 0.625rem;
|
|
291
|
+
border-bottom: 1px solid var(--p-surface-50, #f8fafc);
|
|
292
292
|
cursor: pointer;
|
|
293
293
|
transition: background 0.1s;
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
+
.notification-item:first-child {
|
|
297
|
+
padding-right: 2.5rem;
|
|
298
|
+
}
|
|
299
|
+
|
|
296
300
|
.notification-item:hover {
|
|
297
301
|
background: var(--p-surface-50, #f8fafc);
|
|
298
302
|
}
|
|
@@ -311,16 +315,16 @@ function formatTime(timestamp: number): string {
|
|
|
311
315
|
|
|
312
316
|
.notification-item-icon {
|
|
313
317
|
flex-shrink: 0;
|
|
314
|
-
width: 1.
|
|
315
|
-
height: 1.
|
|
318
|
+
width: 1.125rem;
|
|
319
|
+
height: 1.125rem;
|
|
316
320
|
display: flex;
|
|
317
321
|
align-items: center;
|
|
318
322
|
justify-content: center;
|
|
319
|
-
margin-top: 0.
|
|
323
|
+
margin-top: 0.0625rem;
|
|
320
324
|
}
|
|
321
325
|
|
|
322
326
|
.notification-item-icon i {
|
|
323
|
-
font-size: 0.
|
|
327
|
+
font-size: 0.75rem;
|
|
324
328
|
}
|
|
325
329
|
|
|
326
330
|
.notification-item--success .notification-item-icon i {
|
|
@@ -345,16 +349,16 @@ function formatTime(timestamp: number): string {
|
|
|
345
349
|
}
|
|
346
350
|
|
|
347
351
|
.notification-item-summary {
|
|
348
|
-
font-size: 0.
|
|
352
|
+
font-size: 0.75rem;
|
|
349
353
|
font-weight: 500;
|
|
350
354
|
color: var(--p-surface-700, #334155);
|
|
351
355
|
line-height: 1.3;
|
|
352
356
|
}
|
|
353
357
|
|
|
354
358
|
.notification-item-detail {
|
|
355
|
-
font-size: 0.
|
|
359
|
+
font-size: 0.6875rem;
|
|
356
360
|
color: var(--p-surface-500, #64748b);
|
|
357
|
-
margin-top: 0.
|
|
361
|
+
margin-top: 0.0625rem;
|
|
358
362
|
line-height: 1.3;
|
|
359
363
|
}
|
|
360
364
|
|
|
@@ -362,8 +366,8 @@ function formatTime(timestamp: number): string {
|
|
|
362
366
|
display: flex;
|
|
363
367
|
align-items: center;
|
|
364
368
|
gap: 0.375rem;
|
|
365
|
-
margin-top: 0.
|
|
366
|
-
font-size: 0.
|
|
369
|
+
margin-top: 0.125rem;
|
|
370
|
+
font-size: 0.625rem;
|
|
367
371
|
color: var(--p-surface-400, #94a3b8);
|
|
368
372
|
}
|
|
369
373
|
|
|
@@ -377,14 +381,14 @@ function formatTime(timestamp: number): string {
|
|
|
377
381
|
display: inline-flex;
|
|
378
382
|
align-items: center;
|
|
379
383
|
justify-content: center;
|
|
380
|
-
width: 1.
|
|
381
|
-
height: 1.
|
|
384
|
+
width: 1.125rem;
|
|
385
|
+
height: 1.125rem;
|
|
382
386
|
border: none;
|
|
383
387
|
background: none;
|
|
384
|
-
border-radius:
|
|
388
|
+
border-radius: 2px;
|
|
385
389
|
color: var(--p-surface-400, #94a3b8);
|
|
386
390
|
cursor: pointer;
|
|
387
|
-
font-size: 0.
|
|
391
|
+
font-size: 0.5625rem;
|
|
388
392
|
opacity: 0;
|
|
389
393
|
transition: opacity 0.1s;
|
|
390
394
|
}
|
|
@@ -404,17 +408,17 @@ function formatTime(timestamp: number): string {
|
|
|
404
408
|
flex-direction: column;
|
|
405
409
|
align-items: center;
|
|
406
410
|
justify-content: center;
|
|
407
|
-
padding:
|
|
408
|
-
color: var(--p-surface-
|
|
409
|
-
gap: 0.
|
|
411
|
+
padding: 1.5rem 1rem;
|
|
412
|
+
color: var(--p-surface-300, #cbd5e1);
|
|
413
|
+
gap: 0.375rem;
|
|
410
414
|
}
|
|
411
415
|
|
|
412
416
|
.notification-panel-empty i {
|
|
413
|
-
font-size: 1.
|
|
417
|
+
font-size: 1.25rem;
|
|
414
418
|
}
|
|
415
419
|
|
|
416
420
|
.notification-panel-empty span {
|
|
417
|
-
font-size: 0.
|
|
421
|
+
font-size: 0.75rem;
|
|
418
422
|
}
|
|
419
423
|
|
|
420
424
|
/* Transition */
|
|
@@ -435,12 +439,12 @@ function formatTime(timestamp: number): string {
|
|
|
435
439
|
/* Mobile: full width bottom overlay */
|
|
436
440
|
@media (max-width: 767px) {
|
|
437
441
|
.notification-panel {
|
|
438
|
-
left: 0;
|
|
442
|
+
left: 0 !important;
|
|
439
443
|
right: 0;
|
|
440
444
|
bottom: 0;
|
|
441
445
|
width: auto;
|
|
442
446
|
max-height: 60vh;
|
|
443
|
-
border-radius:
|
|
447
|
+
border-radius: 2px 2px 0 0;
|
|
444
448
|
border-bottom: none;
|
|
445
449
|
}
|
|
446
450
|
}
|
|
@@ -451,13 +455,9 @@ function formatTime(timestamp: number): string {
|
|
|
451
455
|
border-color: var(--p-surface-700, #334155);
|
|
452
456
|
}
|
|
453
457
|
|
|
454
|
-
.dark-mode .notification-
|
|
455
|
-
background: var(--p-surface-
|
|
456
|
-
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
.dark-mode .notification-panel-title {
|
|
460
|
-
color: var(--p-surface-200, #e2e8f0);
|
|
458
|
+
.dark-mode .notification-toolbar-btn:hover {
|
|
459
|
+
background: var(--p-surface-700, #334155);
|
|
460
|
+
color: var(--p-surface-300, #cbd5e1);
|
|
461
461
|
}
|
|
462
462
|
|
|
463
463
|
.dark-mode .notification-item--unread {
|
|
@@ -18,10 +18,15 @@ $_content-duration: 0.1s;
|
|
|
18
18
|
transition: opacity $_content-duration ease $_content-delay;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
/* Status label: delayed fade-in on expand, instant hide on collapse.
|
|
22
|
+
Matches SidebarBox content behavior (.sidebar-box-content). */
|
|
23
|
+
.sidebar-notification-status .status-label {
|
|
24
|
+
opacity: 1;
|
|
25
|
+
transition: opacity $_content-duration ease $_content-delay;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.sidebar--collapsed .sidebar-notification-status .status-label {
|
|
22
29
|
opacity: 0;
|
|
23
30
|
visibility: hidden;
|
|
24
|
-
|
|
25
|
-
overflow: hidden;
|
|
26
|
-
transition: opacity 0s, visibility 0s, height 0s;
|
|
31
|
+
transition: opacity 0s, visibility 0s;
|
|
27
32
|
}
|