@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
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.16.0"
6
6
  },
7
- "version": "0.1.125",
7
+ "version": "0.1.126",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -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
- { menu: () => [], lang: 'fr', onWatchedPage: false },
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
- <button type="button" class="rm-alert__title" @click="emit('open', alert.id)">
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
- <article
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="(tab === 'paused' ? paused : active).length" class="rm-alerts__list">
198
- <RmAlertCard
199
- v-for="alert in tab === 'paused' ? paused : active"
200
- :key="alert.id"
201
- :alert="alert"
202
- :menu="menu"
203
- :lang="lang"
204
- :on-watched-page="onWatchedPage"
205
- @open="emit('open', $event)"
206
- @rearm="emit('rearm', $event)"
207
- @menu="emit('menu', $event)"
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{display:flex;flex-direction:column;gap:10px}.rm-alerts__read-all{all:unset;align-self:flex-end;color:var(--rm-primary,#4f46e5);cursor:pointer;font-size:12px}.rm-alerts__event{border:1px solid var(--rm-border,#e5e7eb);border-radius:9px;display:flex;flex-direction:column;gap:3px;padding:10px 12px}.rm-alerts__event--unread{border-left:3px solid var(--rm-primary,#4f46e5)}.rm-alerts__event-head{align-items:center;display:flex;gap:8px;justify-content:space-between}.rm-alerts__kind{color:var(--rm-text-soft,#6b7280);font-size:10.5px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.rm-alerts__kind--appeared{color:var(--rm-success,#047857)}.rm-alerts__kind--changed{color:var(--rm-info,#1d4ed8)}.rm-alerts__kind--disappeared{color:var(--rm-warn,#b45309)}.rm-alerts__event-date{color:var(--rm-text-soft,#9ca3af);font-size:11px}.rm-alerts__event-title{color:var(--rm-text,#111827);font-size:13.5px;line-height:1.4;margin:0}.rm-alerts__event-from,.rm-alerts__event-sub{color:var(--rm-text-soft,#9ca3af);font-size:11.5px;margin:0}.rm-alerts__change{color:var(--rm-text-soft,#4b5563);font-size:12.5px;margin:2px 0 0}.rm-alerts__change s{opacity:.65}
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. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekkare/romulus",
3
- "version": "0.1.126",
3
+ "version": "0.1.127",
4
4
  "description": "Romulus Design System - Nuxt module by Tekkare",
5
5
  "license": "MIT",
6
6
  "type": "module",