@tekkare/romulus 0.1.126 → 0.1.127
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/module.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
-->
|
|
15
15
|
<script setup lang="ts">
|
|
16
16
|
import { computed, ref } from 'vue'
|
|
17
|
-
import type { RmAlert, RmAlertMenuItem } from '../utils/alerts'
|
|
17
|
+
import type { RmAlert, RmAlertEvent, RmAlertMenuItem } from '../utils/alerts'
|
|
18
18
|
|
|
19
19
|
const props = withDefaults(
|
|
20
20
|
defineProps<{
|
|
@@ -27,8 +27,20 @@ const props = withDefaults(
|
|
|
27
27
|
* reconstruire sa requete Elasticsearch.
|
|
28
28
|
*/
|
|
29
29
|
onWatchedPage?: boolean
|
|
30
|
+
/** Le journal de l'alerte est deplie sous la carte. */
|
|
31
|
+
expanded?: boolean
|
|
32
|
+
/** Ce que l'alerte a rapporte, charge par l'appelant au depliement. */
|
|
33
|
+
events?: RmAlertEvent[]
|
|
34
|
+
eventsLoading?: boolean
|
|
30
35
|
}>(),
|
|
31
|
-
{
|
|
36
|
+
{
|
|
37
|
+
menu: () => [],
|
|
38
|
+
lang: 'fr',
|
|
39
|
+
onWatchedPage: false,
|
|
40
|
+
expanded: false,
|
|
41
|
+
events: () => [],
|
|
42
|
+
eventsLoading: false,
|
|
43
|
+
},
|
|
32
44
|
)
|
|
33
45
|
|
|
34
46
|
const emit = defineEmits<{
|
|
@@ -52,6 +64,9 @@ const FR = {
|
|
|
52
64
|
resume: 'Réactiver',
|
|
53
65
|
rearm: 'Réarmer depuis cette page',
|
|
54
66
|
shared: "Partagée avec l'espace",
|
|
67
|
+
journal: "Voir ce que l'alerte a rapporté",
|
|
68
|
+
journalClose: 'Replier le journal',
|
|
69
|
+
noEvents: 'Rien de signalé depuis la création.',
|
|
55
70
|
appeared: 'Nouvel élément',
|
|
56
71
|
changed: 'Changement de valeur',
|
|
57
72
|
disappeared: 'Sortie du périmètre',
|
|
@@ -113,6 +128,9 @@ const EN = {
|
|
|
113
128
|
resume: 'Resume',
|
|
114
129
|
rearm: 'Re-arm from this page',
|
|
115
130
|
shared: 'Shared with the workspace',
|
|
131
|
+
journal: 'See what this alert reported',
|
|
132
|
+
journalClose: 'Collapse the journal',
|
|
133
|
+
noEvents: 'Nothing reported since it was created.',
|
|
116
134
|
appeared: 'New item',
|
|
117
135
|
changed: 'Value change',
|
|
118
136
|
disappeared: 'Left the scope',
|
|
@@ -234,7 +252,21 @@ function pick(item: RmAlertMenuItem) {
|
|
|
234
252
|
<template>
|
|
235
253
|
<div class="rm-alert" :class="`rm-alert--${alert.status}`">
|
|
236
254
|
<div class="rm-alert__head">
|
|
237
|
-
|
|
255
|
+
<!-- Le titre est la commande du depliement : c'est le geste attendu
|
|
256
|
+
d'une carte qui cache quelque chose, et ca evite un bouton de plus
|
|
257
|
+
dans un en-tete qui en porte deja deux. -->
|
|
258
|
+
<button
|
|
259
|
+
type="button"
|
|
260
|
+
class="rm-alert__title"
|
|
261
|
+
:aria-expanded="expanded"
|
|
262
|
+
:title="expanded ? T.journalClose : T.journal"
|
|
263
|
+
@click="emit('open', alert.id)"
|
|
264
|
+
>
|
|
265
|
+
<RmIcon
|
|
266
|
+
:name="expanded ? 'CaretDown' : 'CaretRight'"
|
|
267
|
+
:size="14"
|
|
268
|
+
class="rm-alert__caret"
|
|
269
|
+
/>
|
|
238
270
|
{{ alert.title }}
|
|
239
271
|
<span v-if="alert.unreadCount" class="rm-alert__unread">
|
|
240
272
|
{{ alert.unreadCount }}
|
|
@@ -344,9 +376,15 @@ function pick(item: RmAlertMenuItem) {
|
|
|
344
376
|
<RmIcon name="Fire" :size="13" />{{ alert.lastEventLabel }}
|
|
345
377
|
</span>
|
|
346
378
|
</div>
|
|
379
|
+
|
|
380
|
+
<div v-if="expanded" class="rm-alert__journal">
|
|
381
|
+
<RmLoadingState v-if="eventsLoading" />
|
|
382
|
+
<RmAlertEvents v-else-if="events.length" :events="events" :lang="lang" />
|
|
383
|
+
<p v-else class="rm-alert__journal-empty">{{ T.noEvents }}</p>
|
|
384
|
+
</div>
|
|
347
385
|
</div>
|
|
348
386
|
</template>
|
|
349
387
|
|
|
350
388
|
<style scoped>
|
|
351
|
-
.rm-alert{background:var(--rm-surface,#fff);border:1px solid var(--rm-border,#e5e7eb);border-radius:10px;display:flex;flex-direction:column;gap:10px;padding:14px 16px}.rm-alert--paused,.rm-alert--seeding{background:var(--rm-surface-muted,#fafafa)}.rm-alert__head{align-items:flex-start;display:flex;gap:10px;justify-content:space-between}.rm-alert__title{all:unset;align-items:center;color:var(--rm-text,#111827);cursor:pointer;display:inline-flex;font-size:15px;font-weight:600;gap:8px;line-height:1.35}.rm-alert__title:hover{color:var(--rm-primary,#4f46e5)}.rm-alert__unread{align-items:center;background:var(--rm-primary,#4f46e5);border-radius:999px;color:#fff;display:inline-flex;font-size:11px;font-weight:700;height:18px;justify-content:center;min-width:18px;padding:0 5px}.rm-alert__head-actions{align-items:center;display:flex;flex-shrink:0;gap:6px}.rm-alert__status{align-items:center;background:var(--rm-surface-muted,#f3f4f6);border-radius:999px;color:var(--rm-text-soft,#6b7280);display:inline-flex;font-size:11px;font-weight:600;gap:5px;letter-spacing:.03em;padding:3px 8px;text-transform:uppercase;white-space:nowrap}.rm-alert__dot{background:currentColor;border-radius:999px;height:6px;width:6px}.rm-alert__status--active{background:var(--rm-success-soft,#ecfdf5);color:var(--rm-success,#047857)}.rm-alert__status--failing,.rm-alert__status--stale{background:var(--rm-warn-soft,#fffbeb);color:var(--rm-warn,#b45309)}.rm-alert__view{align-items:center;color:var(--rm-text-soft,#6b7280);display:flex;flex-wrap:wrap;font-size:12px;gap:5px;margin:0}.rm-alert__shared{border-radius:999px;font-size:11px;padding:1px 6px}.rm-alert__notice,.rm-alert__shared{background:var(--rm-surface-muted,#f3f4f6)}.rm-alert__notice{align-items:flex-start;border-radius:8px;color:var(--rm-text-soft,#4b5563);display:flex;font-size:12.5px;gap:8px;line-height:1.45;padding:9px 11px}.rm-alert__notice--warn{background:var(--rm-warn-soft,#fffbeb);color:var(--rm-warn,#92400e)}.rm-alert__notice-detail{display:block;font-size:11.5px;font-style:normal;margin-top:3px;opacity:.8}.rm-alert__spec{background:var(--rm-surface-muted,#f9fafb);border-radius:8px;display:flex;flex-direction:column;gap:6px;margin:0;padding:10px 12px}.rm-alert__row{align-items:baseline;display:grid;gap:10px;grid-template-columns:110px 1fr}.rm-alert__row dt{color:var(--rm-text-soft,#9ca3af);font-size:11px;font-weight:600;letter-spacing:.03em;text-transform:uppercase}.rm-alert__row dd{color:var(--rm-text,#111827);display:flex;flex-wrap:wrap;font-size:13px;gap:5px;margin:0}.rm-alert__chip{background:var(--rm-surface,#fff);border:1px solid var(--rm-border,#e5e7eb);border-radius:6px;font-size:12px;padding:1px 7px}.rm-alert__reseed{margin:0}.rm-alert__meta,.rm-alert__reseed{color:var(--rm-text-soft,#9ca3af);font-size:11.5px}.rm-alert__meta{border-top:1px solid var(--rm-border-soft,#f3f4f6);display:flex;flex-wrap:wrap;gap:12px;padding-top:9px}.rm-alert__meta span{align-items:center;display:inline-flex;gap:4px}.rm-alert__menu-wrap{position:relative}.rm-alert__more{all:unset;border-radius:6px;color:var(--rm-text-soft,#9ca3af);cursor:pointer;display:inline-flex;padding:3px}.rm-alert__more:hover{background:var(--rm-surface-muted,#f3f4f6);color:var(--rm-text,#111827)}.rm-alert__menu-shade{inset:0;position:fixed;z-index:20}.rm-alert__menu{background:var(--rm-surface,#fff);border:1px solid var(--rm-border,#e5e7eb);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.1);display:flex;flex-direction:column;min-width:190px;padding:4px;position:absolute;right:0;top:calc(100% + 4px);z-index:21}.rm-alert__menu-item{all:unset;align-items:center;border-radius:6px;color:var(--rm-text,#111827);cursor:pointer;display:flex;font-size:13px;gap:8px;padding:7px 9px}.rm-alert__menu-item:hover{background:var(--rm-surface-muted,#f3f4f6)}.rm-alert__menu-item--danger{color:var(--rm-danger,#b91c1c)}
|
|
389
|
+
.rm-alert{background:var(--rm-surface,#fff);border:1px solid var(--rm-border,#e5e7eb);border-radius:10px;display:flex;flex-direction:column;gap:10px;padding:14px 16px}.rm-alert--paused,.rm-alert--seeding{background:var(--rm-surface-muted,#fafafa)}.rm-alert__head{align-items:flex-start;display:flex;gap:10px;justify-content:space-between}.rm-alert__journal{border-top:1px solid var(--rm-border,#e5e7eb);padding-top:10px}.rm-alert__journal-empty{color:var(--rm-text-soft,#9ca3af);font-size:12.5px;margin:0}.rm-alert__caret{color:var(--rm-text-soft,#9ca3af);flex-shrink:0}.rm-alert__title{all:unset;align-items:center;color:var(--rm-text,#111827);cursor:pointer;display:inline-flex;font-size:15px;font-weight:600;gap:8px;line-height:1.35}.rm-alert__title:hover{color:var(--rm-primary,#4f46e5)}.rm-alert__unread{align-items:center;background:var(--rm-primary,#4f46e5);border-radius:999px;color:#fff;display:inline-flex;font-size:11px;font-weight:700;height:18px;justify-content:center;min-width:18px;padding:0 5px}.rm-alert__head-actions{align-items:center;display:flex;flex-shrink:0;gap:6px}.rm-alert__status{align-items:center;background:var(--rm-surface-muted,#f3f4f6);border-radius:999px;color:var(--rm-text-soft,#6b7280);display:inline-flex;font-size:11px;font-weight:600;gap:5px;letter-spacing:.03em;padding:3px 8px;text-transform:uppercase;white-space:nowrap}.rm-alert__dot{background:currentColor;border-radius:999px;height:6px;width:6px}.rm-alert__status--active{background:var(--rm-success-soft,#ecfdf5);color:var(--rm-success,#047857)}.rm-alert__status--failing,.rm-alert__status--stale{background:var(--rm-warn-soft,#fffbeb);color:var(--rm-warn,#b45309)}.rm-alert__view{align-items:center;color:var(--rm-text-soft,#6b7280);display:flex;flex-wrap:wrap;font-size:12px;gap:5px;margin:0}.rm-alert__shared{border-radius:999px;font-size:11px;padding:1px 6px}.rm-alert__notice,.rm-alert__shared{background:var(--rm-surface-muted,#f3f4f6)}.rm-alert__notice{align-items:flex-start;border-radius:8px;color:var(--rm-text-soft,#4b5563);display:flex;font-size:12.5px;gap:8px;line-height:1.45;padding:9px 11px}.rm-alert__notice--warn{background:var(--rm-warn-soft,#fffbeb);color:var(--rm-warn,#92400e)}.rm-alert__notice-detail{display:block;font-size:11.5px;font-style:normal;margin-top:3px;opacity:.8}.rm-alert__spec{background:var(--rm-surface-muted,#f9fafb);border-radius:8px;display:flex;flex-direction:column;gap:6px;margin:0;padding:10px 12px}.rm-alert__row{align-items:baseline;display:grid;gap:10px;grid-template-columns:110px 1fr}.rm-alert__row dt{color:var(--rm-text-soft,#9ca3af);font-size:11px;font-weight:600;letter-spacing:.03em;text-transform:uppercase}.rm-alert__row dd{color:var(--rm-text,#111827);display:flex;flex-wrap:wrap;font-size:13px;gap:5px;margin:0}.rm-alert__chip{background:var(--rm-surface,#fff);border:1px solid var(--rm-border,#e5e7eb);border-radius:6px;font-size:12px;padding:1px 7px}.rm-alert__reseed{margin:0}.rm-alert__meta,.rm-alert__reseed{color:var(--rm-text-soft,#9ca3af);font-size:11.5px}.rm-alert__meta{border-top:1px solid var(--rm-border-soft,#f3f4f6);display:flex;flex-wrap:wrap;gap:12px;padding-top:9px}.rm-alert__meta span{align-items:center;display:inline-flex;gap:4px}.rm-alert__menu-wrap{position:relative}.rm-alert__more{all:unset;border-radius:6px;color:var(--rm-text-soft,#9ca3af);cursor:pointer;display:inline-flex;padding:3px}.rm-alert__more:hover{background:var(--rm-surface-muted,#f3f4f6);color:var(--rm-text,#111827)}.rm-alert__menu-shade{inset:0;position:fixed;z-index:20}.rm-alert__menu{background:var(--rm-surface,#fff);border:1px solid var(--rm-border,#e5e7eb);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.1);display:flex;flex-direction:column;min-width:190px;padding:4px;position:absolute;right:0;top:calc(100% + 4px);z-index:21}.rm-alert__menu-item{all:unset;align-items:center;border-radius:6px;color:var(--rm-text,#111827);cursor:pointer;display:flex;font-size:13px;gap:8px;padding:7px 9px}.rm-alert__menu-item:hover{background:var(--rm-surface-muted,#f3f4f6)}.rm-alert__menu-item--danger{color:var(--rm-danger,#b91c1c)}
|
|
352
390
|
</style>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
RmAlertEvents - ce qu'une alerte a rapporte.
|
|
3
|
+
|
|
4
|
+
La meme liste sert a deux endroits : le journal du panneau, qui melange
|
|
5
|
+
toutes les alertes, et le depliement d'une carte, qui n'en montre qu'une.
|
|
6
|
+
D'ou `showSource` : rappeler « Veille Keytruda » sous chaque ligne d'une
|
|
7
|
+
carte qui porte deja ce titre n'apprend rien.
|
|
8
|
+
|
|
9
|
+
Le composant ne met rien en forme lui-meme : les dates arrivent deja
|
|
10
|
+
ecrites, comme partout ailleurs dans le paquet.
|
|
11
|
+
-->
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
import { computed } from 'vue'
|
|
14
|
+
import type { RmAlertEvent } from '../utils/alerts'
|
|
15
|
+
|
|
16
|
+
const props = withDefaults(
|
|
17
|
+
defineProps<{
|
|
18
|
+
events: RmAlertEvent[]
|
|
19
|
+
/** Nommer l'alerte d'ou vient chaque ligne. */
|
|
20
|
+
showSource?: boolean
|
|
21
|
+
lang?: 'fr' | 'en'
|
|
22
|
+
}>(),
|
|
23
|
+
{ showSource: false, lang: 'fr' },
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
const FR = {
|
|
27
|
+
appeared: 'Nouveau',
|
|
28
|
+
changed: 'Modifié',
|
|
29
|
+
disappeared: 'Sorti du périmètre',
|
|
30
|
+
becomes: 'devient',
|
|
31
|
+
empty: '(vide)',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const EN = {
|
|
35
|
+
appeared: 'New',
|
|
36
|
+
changed: 'Changed',
|
|
37
|
+
disappeared: 'Left the scope',
|
|
38
|
+
becomes: 'becomes',
|
|
39
|
+
empty: '(empty)',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const T = computed(() => (props.lang === 'en' ? EN : FR))
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<template>
|
|
46
|
+
<div class="rm-alert-events">
|
|
47
|
+
<article
|
|
48
|
+
v-for="event in events"
|
|
49
|
+
:key="event.id"
|
|
50
|
+
class="rm-alert-events__item"
|
|
51
|
+
:class="{ 'rm-alert-events__item--unread': !event.read }"
|
|
52
|
+
>
|
|
53
|
+
<header class="rm-alert-events__head">
|
|
54
|
+
<span class="rm-alert-events__kind" :class="`rm-alert-events__kind--${event.kind}`">
|
|
55
|
+
{{ T[event.kind] }}
|
|
56
|
+
</span>
|
|
57
|
+
<time class="rm-alert-events__date">{{ event.dateLabel }}</time>
|
|
58
|
+
</header>
|
|
59
|
+
<p class="rm-alert-events__title">{{ event.title }}</p>
|
|
60
|
+
<p v-if="event.subtitle" class="rm-alert-events__sub">{{ event.subtitle }}</p>
|
|
61
|
+
<p
|
|
62
|
+
v-for="(change, index) in event.changes ?? []"
|
|
63
|
+
:key="index"
|
|
64
|
+
class="rm-alert-events__change"
|
|
65
|
+
>
|
|
66
|
+
{{ change.label || change.field }} :
|
|
67
|
+
<s>{{ change.before || T.empty }}</s>
|
|
68
|
+
{{ T.becomes }}
|
|
69
|
+
<strong>{{ change.after || T.empty }}</strong>
|
|
70
|
+
</p>
|
|
71
|
+
<p v-if="showSource && event.alertTitle" class="rm-alert-events__from">
|
|
72
|
+
{{ event.alertTitle }}
|
|
73
|
+
</p>
|
|
74
|
+
</article>
|
|
75
|
+
</div>
|
|
76
|
+
</template>
|
|
77
|
+
|
|
78
|
+
<style scoped>
|
|
79
|
+
.rm-alert-events{display:flex;flex-direction:column;gap:10px}.rm-alert-events__item{border:1px solid var(--rm-border,#e5e7eb);border-radius:9px;display:flex;flex-direction:column;gap:3px;padding:10px 12px}.rm-alert-events__item--unread{border-left:3px solid var(--rm-primary,#4f46e5)}.rm-alert-events__head{align-items:center;display:flex;gap:8px;justify-content:space-between}.rm-alert-events__kind{color:var(--rm-text-soft,#6b7280);font-size:10.5px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.rm-alert-events__kind--appeared{color:var(--rm-success,#047857)}.rm-alert-events__kind--changed{color:var(--rm-info,#1d4ed8)}.rm-alert-events__kind--disappeared{color:var(--rm-warn,#b45309)}.rm-alert-events__date{color:var(--rm-text-soft,#9ca3af);font-size:11px}.rm-alert-events__title{color:var(--rm-text,#111827);font-size:13.5px;line-height:1.4;margin:0}.rm-alert-events__from,.rm-alert-events__sub{color:var(--rm-text-soft,#9ca3af);font-size:11.5px;margin:0}.rm-alert-events__change{color:var(--rm-text-soft,#4b5563);font-size:12.5px;margin:2px 0 0}.rm-alert-events__change s{opacity:.65}
|
|
80
|
+
</style>
|
|
@@ -30,6 +30,11 @@ const props = withDefaults(
|
|
|
30
30
|
createHint?: string
|
|
31
31
|
/** La page affichee est celle que les alertes de la liste surveillent. */
|
|
32
32
|
onWatchedPage?: boolean
|
|
33
|
+
/** Alerte dont le journal est deplie, s'il y en a une. */
|
|
34
|
+
expandedId?: string | null
|
|
35
|
+
/** Evenements de cette alerte, charges par l'appelant. */
|
|
36
|
+
expandedEvents?: RmAlertEvent[]
|
|
37
|
+
expandedLoading?: boolean
|
|
33
38
|
lang?: 'fr' | 'en'
|
|
34
39
|
}>(),
|
|
35
40
|
{
|
|
@@ -40,6 +45,9 @@ const props = withDefaults(
|
|
|
40
45
|
canCreate: false,
|
|
41
46
|
createHint: '',
|
|
42
47
|
onWatchedPage: false,
|
|
48
|
+
expandedId: null,
|
|
49
|
+
expandedEvents: () => [],
|
|
50
|
+
expandedLoading: false,
|
|
43
51
|
lang: 'fr',
|
|
44
52
|
},
|
|
45
53
|
)
|
|
@@ -68,11 +76,6 @@ const FR = {
|
|
|
68
76
|
emptyEvents: 'Rien de neuf',
|
|
69
77
|
emptyEventsText:
|
|
70
78
|
'Les changements détectés sur vos périmètres apparaîtront ici, du plus récent au plus ancien.',
|
|
71
|
-
appeared: 'Nouveau',
|
|
72
|
-
changed: 'Modifié',
|
|
73
|
-
disappeared: 'Sorti du périmètre',
|
|
74
|
-
becomes: 'devient',
|
|
75
|
-
empty: '(vide)',
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
const EN = {
|
|
@@ -89,11 +92,6 @@ const EN = {
|
|
|
89
92
|
emptyEvents: 'Nothing new',
|
|
90
93
|
emptyEventsText:
|
|
91
94
|
'Changes detected on your scopes will show up here, most recent first.',
|
|
92
|
-
appeared: 'New',
|
|
93
|
-
changed: 'Changed',
|
|
94
|
-
disappeared: 'Left the scope',
|
|
95
|
-
becomes: 'becomes',
|
|
96
|
-
empty: '(empty)',
|
|
97
95
|
}
|
|
98
96
|
|
|
99
97
|
const T = computed(() => (props.lang === 'en' ? EN : FR))
|
|
@@ -108,6 +106,29 @@ const paused = computed(() => props.alerts.filter((alert) => alert.status === 'p
|
|
|
108
106
|
*/
|
|
109
107
|
const active = computed(() => props.alerts.filter((alert) => alert.status !== 'paused'))
|
|
110
108
|
|
|
109
|
+
/**
|
|
110
|
+
* La liste visible, coupee en sections.
|
|
111
|
+
*
|
|
112
|
+
* Les sections suivent l'ordre d'apparition des alertes : c'est l'appelant
|
|
113
|
+
* qui trie, parce que lui seul sait ce qui vient en premier (chez lui, la
|
|
114
|
+
* page affichee). Sans `group`, tout tient dans une section sans titre et
|
|
115
|
+
* la liste se lit comme avant.
|
|
116
|
+
*/
|
|
117
|
+
const sections = computed(() => {
|
|
118
|
+
const list = props.tab === 'paused' ? paused.value : active.value
|
|
119
|
+
const order: string[] = []
|
|
120
|
+
const byLabel = new Map<string, RmAlert[]>()
|
|
121
|
+
for (const alert of list) {
|
|
122
|
+
const label = alert.group ?? ''
|
|
123
|
+
if (!byLabel.has(label)) {
|
|
124
|
+
byLabel.set(label, [])
|
|
125
|
+
order.push(label)
|
|
126
|
+
}
|
|
127
|
+
byLabel.get(label)!.push(alert)
|
|
128
|
+
}
|
|
129
|
+
return order.map((label) => ({ label, alerts: byLabel.get(label)! }))
|
|
130
|
+
})
|
|
131
|
+
|
|
111
132
|
const unreadTotal = computed(() =>
|
|
112
133
|
props.alerts.reduce((total, alert) => total + (alert.unreadCount ?? 0), 0),
|
|
113
134
|
)
|
|
@@ -162,50 +183,38 @@ function setTab(value: 'active' | 'paused' | 'events') {
|
|
|
162
183
|
{{ T.readAll }}
|
|
163
184
|
</button>
|
|
164
185
|
|
|
165
|
-
<
|
|
166
|
-
v-for="event in events"
|
|
167
|
-
:key="event.id"
|
|
168
|
-
class="rm-alerts__event"
|
|
169
|
-
:class="{ 'rm-alerts__event--unread': !event.read }"
|
|
170
|
-
>
|
|
171
|
-
<header class="rm-alerts__event-head">
|
|
172
|
-
<span class="rm-alerts__kind" :class="`rm-alerts__kind--${event.kind}`">
|
|
173
|
-
{{ T[event.kind] }}
|
|
174
|
-
</span>
|
|
175
|
-
<time class="rm-alerts__event-date">{{ event.dateLabel }}</time>
|
|
176
|
-
</header>
|
|
177
|
-
<p class="rm-alerts__event-title">{{ event.title }}</p>
|
|
178
|
-
<p v-if="event.subtitle" class="rm-alerts__event-sub">{{ event.subtitle }}</p>
|
|
179
|
-
<p
|
|
180
|
-
v-for="(change, index) in event.changes ?? []"
|
|
181
|
-
:key="index"
|
|
182
|
-
class="rm-alerts__change"
|
|
183
|
-
>
|
|
184
|
-
{{ change.label || change.field }} :
|
|
185
|
-
<s>{{ change.before || T.empty }}</s>
|
|
186
|
-
{{ T.becomes }}
|
|
187
|
-
<strong>{{ change.after || T.empty }}</strong>
|
|
188
|
-
</p>
|
|
189
|
-
<p v-if="event.alertTitle" class="rm-alerts__event-from">{{ event.alertTitle }}</p>
|
|
190
|
-
</article>
|
|
186
|
+
<RmAlertEvents :events="events" show-source :lang="lang" />
|
|
191
187
|
</div>
|
|
192
188
|
|
|
193
189
|
<RmEmptyState v-else :title="T.emptyEvents" :text="T.emptyEventsText" />
|
|
194
190
|
</template>
|
|
195
191
|
|
|
196
192
|
<template v-else>
|
|
197
|
-
<div v-if="
|
|
198
|
-
<
|
|
199
|
-
v-for="
|
|
200
|
-
:key="
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
193
|
+
<div v-if="sections.length" class="rm-alerts__list">
|
|
194
|
+
<section
|
|
195
|
+
v-for="section in sections"
|
|
196
|
+
:key="section.label"
|
|
197
|
+
class="rm-alerts__section"
|
|
198
|
+
>
|
|
199
|
+
<h3 v-if="section.label" class="rm-alerts__section-title">
|
|
200
|
+
{{ section.label }}
|
|
201
|
+
<span class="rm-alerts__section-count">{{ section.alerts.length }}</span>
|
|
202
|
+
</h3>
|
|
203
|
+
<RmAlertCard
|
|
204
|
+
v-for="alert in section.alerts"
|
|
205
|
+
:key="alert.id"
|
|
206
|
+
:alert="alert"
|
|
207
|
+
:menu="menu"
|
|
208
|
+
:lang="lang"
|
|
209
|
+
:on-watched-page="onWatchedPage"
|
|
210
|
+
:expanded="expandedId === alert.id"
|
|
211
|
+
:events="expandedId === alert.id ? expandedEvents : []"
|
|
212
|
+
:events-loading="expandedId === alert.id && expandedLoading"
|
|
213
|
+
@open="emit('open', $event)"
|
|
214
|
+
@rearm="emit('rearm', $event)"
|
|
215
|
+
@menu="emit('menu', $event)"
|
|
216
|
+
/>
|
|
217
|
+
</section>
|
|
209
218
|
</div>
|
|
210
219
|
|
|
211
220
|
<RmEmptyState
|
|
@@ -218,5 +227,5 @@ function setTab(value: 'active' | 'paused' | 'events') {
|
|
|
218
227
|
</template>
|
|
219
228
|
|
|
220
229
|
<style scoped>
|
|
221
|
-
.rm-alerts{display:flex;flex-direction:column;gap:14px}.rm-alerts__intro{color:var(--rm-text-soft,#6b7280);font-size:13px;line-height:1.5;margin:0}.rm-alerts__create{align-items:flex-start;display:flex;flex-direction:column;gap:6px}.rm-alerts__hint{color:var(--rm-text-soft,#9ca3af);font-size:12px;line-height:1.45;margin:0}.rm-alerts__tabs{border-bottom:1px solid var(--rm-border,#e5e7eb);display:flex;gap:4px}.rm-alerts__tab{all:unset;align-items:center;border-bottom:2px solid transparent;color:var(--rm-text-soft,#6b7280);cursor:pointer;display:inline-flex;font-size:13px;font-weight:500;gap:6px;padding:8px 10px}.rm-alerts__tab--on{border-bottom-color:var(--rm-primary,#4f46e5);color:var(--rm-primary,#4f46e5)}.rm-alerts__tab-count{background:var(--rm-surface-muted,#f3f4f6);border-radius:999px;font-size:11px;font-weight:700;padding:0 6px}.rm-alerts__tab--on .rm-alerts__tab-count{background:var(--rm-primary,#4f46e5);color:#fff}.rm-alerts__events,.rm-alerts__list
|
|
230
|
+
.rm-alerts{display:flex;flex-direction:column;gap:14px}.rm-alerts__intro{color:var(--rm-text-soft,#6b7280);font-size:13px;line-height:1.5;margin:0}.rm-alerts__create{align-items:flex-start;display:flex;flex-direction:column;gap:6px}.rm-alerts__hint{color:var(--rm-text-soft,#9ca3af);font-size:12px;line-height:1.45;margin:0}.rm-alerts__tabs{border-bottom:1px solid var(--rm-border,#e5e7eb);display:flex;gap:4px}.rm-alerts__tab{all:unset;align-items:center;border-bottom:2px solid transparent;color:var(--rm-text-soft,#6b7280);cursor:pointer;display:inline-flex;font-size:13px;font-weight:500;gap:6px;padding:8px 10px}.rm-alerts__tab--on{border-bottom-color:var(--rm-primary,#4f46e5);color:var(--rm-primary,#4f46e5)}.rm-alerts__tab-count{background:var(--rm-surface-muted,#f3f4f6);border-radius:999px;font-size:11px;font-weight:700;padding:0 6px}.rm-alerts__tab--on .rm-alerts__tab-count{background:var(--rm-primary,#4f46e5);color:#fff}.rm-alerts__events,.rm-alerts__list,.rm-alerts__section{display:flex;flex-direction:column;gap:10px}.rm-alerts__section-title{align-items:center;color:var(--rm-text-soft,#6b7280);display:flex;font-size:11.5px;font-weight:700;gap:6px;letter-spacing:.05em;margin:4px 0 0;text-transform:uppercase}.rm-alerts__section-count{background:var(--rm-surface-muted,#f3f4f6);border-radius:999px;font-size:11px;letter-spacing:0;padding:0 6px}.rm-alerts__read-all{all:unset;align-self:flex-end;color:var(--rm-primary,#4f46e5);cursor:pointer;font-size:12px}
|
|
222
231
|
</style>
|
|
@@ -65,6 +65,14 @@ export interface RmAlert {
|
|
|
65
65
|
createdLabel?: string | null;
|
|
66
66
|
lastEventLabel?: string | null;
|
|
67
67
|
eventsThisMonth?: number;
|
|
68
|
+
/**
|
|
69
|
+
* Section ou ranger l'alerte dans la liste, deja traduite.
|
|
70
|
+
*
|
|
71
|
+
* L'appelant seul sait ce qui separe ses alertes : ici, la page surveillee.
|
|
72
|
+
* Les alertes se rangent dans l'ordre ou leurs sections apparaissent, donc
|
|
73
|
+
* c'est a lui de trier la liste avant de la passer.
|
|
74
|
+
*/
|
|
75
|
+
group?: string;
|
|
68
76
|
/** Partagee avec l'espace de travail. */
|
|
69
77
|
shared?: boolean;
|
|
70
78
|
/** L'utilisateur courant en est proprietaire. */
|