@tekkare/romulus 0.1.103 → 0.1.105
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 +1 -1
- package/dist/runtime/components/AlertForm.vue +147 -35
- package/dist/runtime/components/ExportPanel.vue +31 -6
- package/dist/runtime/components/Modal.vue +1 -1
- package/dist/runtime/utils/alerts.d.ts +19 -1
- package/dist/runtime/utils/alerts.js +1 -0
- package/dist/runtime/utils/export.d.ts +10 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -18,12 +18,29 @@
|
|
|
18
18
|
-->
|
|
19
19
|
<script setup lang="ts">
|
|
20
20
|
import { computed, ref, watch } from 'vue'
|
|
21
|
-
import type {
|
|
21
|
+
import type {
|
|
22
|
+
RmAlertCadence,
|
|
23
|
+
RmAlertDraft,
|
|
24
|
+
RmAlertField,
|
|
25
|
+
RmAlertTrigger,
|
|
26
|
+
RmAlertViewOption,
|
|
27
|
+
} from '../utils/alerts'
|
|
28
|
+
import { RM_ALERT_ANY_FIELD } from '../utils/alerts'
|
|
22
29
|
|
|
23
30
|
const props = withDefaults(
|
|
24
31
|
defineProps<{
|
|
25
32
|
/** Nom de la vue surveillee, affiche et repris comme titre par defaut. */
|
|
26
33
|
viewTitle: string
|
|
34
|
+
/**
|
|
35
|
+
* Vues enregistrees de la page. Avec plusieurs, le formulaire propose un
|
|
36
|
+
* selecteur : une alerte peut se poser sans avoir la vue a l'ecran.
|
|
37
|
+
*/
|
|
38
|
+
views?: RmAlertViewOption[]
|
|
39
|
+
/**
|
|
40
|
+
* L'ecran porte des filtres qui pourraient devenir une vue. Ajoute
|
|
41
|
+
* l'entree « ecran actuel » au selecteur.
|
|
42
|
+
*/
|
|
43
|
+
canSaveScreen?: boolean
|
|
27
44
|
/** Champs surveillables de la page. Vide : seule l'apparition compte. */
|
|
28
45
|
fields?: RmAlertField[]
|
|
29
46
|
/** Nombre d'elements que la vue tient a l'ecran, s'il est connu. */
|
|
@@ -31,15 +48,32 @@ const props = withDefaults(
|
|
|
31
48
|
submitting?: boolean
|
|
32
49
|
lang?: 'fr' | 'en'
|
|
33
50
|
}>(),
|
|
34
|
-
{
|
|
51
|
+
{
|
|
52
|
+
views: () => [],
|
|
53
|
+
canSaveScreen: false,
|
|
54
|
+
fields: () => [],
|
|
55
|
+
scopeCount: null,
|
|
56
|
+
submitting: false,
|
|
57
|
+
lang: 'fr',
|
|
58
|
+
},
|
|
35
59
|
)
|
|
36
60
|
|
|
37
61
|
const emit = defineEmits<{
|
|
38
62
|
submit: [draft: RmAlertDraft]
|
|
39
63
|
cancel: []
|
|
64
|
+
/**
|
|
65
|
+
* Une autre vue vient d'etre choisie. L'appelant doit l'appliquer a la
|
|
66
|
+
* page : c'est elle qui construit la requete Elasticsearch, donc l'ecran et
|
|
67
|
+
* la vue surveillee doivent dire la meme chose au moment de la capture.
|
|
68
|
+
*/
|
|
69
|
+
'select-view': [id: string]
|
|
40
70
|
}>()
|
|
41
71
|
|
|
42
72
|
const FR = {
|
|
73
|
+
screenOption: 'Écran actuel (une vue sera créée)',
|
|
74
|
+
viewSelect: 'Vue à surveiller',
|
|
75
|
+
viewApplied: 'La vue choisie est appliquée à l’écran : c’est lui qui fournit la requête surveillée.',
|
|
76
|
+
screenNote: 'Vos filtres actuels seront enregistrés comme une vue portant le nom de l’alerte.',
|
|
43
77
|
name: 'Nom de l’alerte',
|
|
44
78
|
view: 'Vue surveillée',
|
|
45
79
|
viewHint: 'L’alerte se déclenche sur le périmètre de cette vue, tel qu’il est à l’écran.',
|
|
@@ -55,17 +89,18 @@ const FR = {
|
|
|
55
89
|
fieldsHint: 'Un seul message par élément, quel que soit le nombre de champs cochés.',
|
|
56
90
|
fieldsRequired: 'Cochez au moins un champ pour surveiller les changements.',
|
|
57
91
|
noFields: 'Cette page ne propose aucun champ surveillable pour le moment.',
|
|
92
|
+
anyField: 'N’importe quel changement',
|
|
93
|
+
anyFieldHint:
|
|
94
|
+
'Tout le document est comparé. Plus large, mais plus bavard : une correction de libellé compte autant qu’un mouvement de prix.',
|
|
58
95
|
channels: 'Canaux',
|
|
59
96
|
inApp: 'Dans l’app (pastille sur la tuile Alertes)',
|
|
60
97
|
email: 'Email',
|
|
61
98
|
cadence: 'Fréquence d’envoi du mail',
|
|
62
99
|
daily: 'Quotidienne (un envoi par jour au plus)',
|
|
63
100
|
weekly: 'Hebdomadaire (un envoi par semaine au plus)',
|
|
101
|
+
monthly: 'Mensuelle (un envoi par mois au plus)',
|
|
64
102
|
cadenceHint:
|
|
65
103
|
'Un plafond, pas un rythme : rien ne part quand rien n’a bougé, et la détection suit celle des données.',
|
|
66
|
-
visibility: 'Partage',
|
|
67
|
-
private: 'Pour moi seul',
|
|
68
|
-
workspace: 'Visible par mon espace de travail',
|
|
69
104
|
submit: 'Créer l’alerte',
|
|
70
105
|
cancel: 'Annuler',
|
|
71
106
|
baseline:
|
|
@@ -74,6 +109,10 @@ const FR = {
|
|
|
74
109
|
}
|
|
75
110
|
|
|
76
111
|
const EN = {
|
|
112
|
+
screenOption: 'Current screen (a view will be created)',
|
|
113
|
+
viewSelect: 'View to watch',
|
|
114
|
+
viewApplied: 'The chosen view is applied to the screen: the screen is what provides the watched query.',
|
|
115
|
+
screenNote: 'Your current filters will be saved as a view named after the alert.',
|
|
77
116
|
name: 'Alert name',
|
|
78
117
|
view: 'Watched view',
|
|
79
118
|
viewHint: 'The alert fires on this view’s scope, as it stands on screen.',
|
|
@@ -89,17 +128,18 @@ const EN = {
|
|
|
89
128
|
fieldsHint: 'One message per item, however many fields are ticked.',
|
|
90
129
|
fieldsRequired: 'Tick at least one field to watch for changes.',
|
|
91
130
|
noFields: 'This page offers no watchable field yet.',
|
|
131
|
+
anyField: 'Any change',
|
|
132
|
+
anyFieldHint:
|
|
133
|
+
'The whole document is compared. Wider, but noisier: a label fix counts as much as a price move.',
|
|
92
134
|
channels: 'Channels',
|
|
93
135
|
inApp: 'In app (badge on the Alerts tile)',
|
|
94
136
|
email: 'Email',
|
|
95
137
|
cadence: 'Mail frequency',
|
|
96
138
|
daily: 'Daily (at most one a day)',
|
|
97
139
|
weekly: 'Weekly (at most one a week)',
|
|
140
|
+
monthly: 'Monthly (at most one a month)',
|
|
98
141
|
cadenceHint:
|
|
99
142
|
'A cap, not a rhythm: nothing goes out when nothing moved, and detection follows the data.',
|
|
100
|
-
visibility: 'Sharing',
|
|
101
|
-
private: 'Just me',
|
|
102
|
-
workspace: 'Visible to my workspace',
|
|
103
143
|
submit: 'Create the alert',
|
|
104
144
|
cancel: 'Cancel',
|
|
105
145
|
baseline:
|
|
@@ -109,20 +149,47 @@ const EN = {
|
|
|
109
149
|
|
|
110
150
|
const T = computed(() => (props.lang === 'en' ? EN : FR))
|
|
111
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Vue retenue. `null` designe l'ecran actuel, qui deviendra une vue au moment
|
|
154
|
+
* de la creation.
|
|
155
|
+
*/
|
|
156
|
+
const selectedViewId = ref<string | null>(props.views[0]?.id ?? null)
|
|
157
|
+
|
|
158
|
+
/** Entrees du selecteur, l'ecran actuel en tete quand il a des filtres. */
|
|
159
|
+
const viewOptions = computed(() => [
|
|
160
|
+
...(props.canSaveScreen ? [{ id: null as string | null, title: T.value.screenOption }] : []),
|
|
161
|
+
...props.views.map((view) => ({ id: view.id as string | null, title: view.title })),
|
|
162
|
+
])
|
|
163
|
+
|
|
164
|
+
const selectedView = computed(
|
|
165
|
+
() => viewOptions.value.find((option) => option.id === selectedViewId.value) ?? viewOptions.value[0],
|
|
166
|
+
)
|
|
167
|
+
|
|
112
168
|
const title = ref(props.viewTitle)
|
|
113
169
|
const triggers = ref<RmAlertTrigger[]>(['appeared'])
|
|
114
170
|
const watchedFields = ref<string[]>([])
|
|
171
|
+
/** Surveiller le document entier plutot qu'une liste de champs. */
|
|
172
|
+
const anyField = ref(false)
|
|
115
173
|
const emailOn = ref(false)
|
|
116
|
-
const cadence = ref<
|
|
117
|
-
|
|
174
|
+
const cadence = ref<RmAlertCadence>('weekly')
|
|
175
|
+
|
|
176
|
+
/** Titre libre des que l'utilisateur y touche : on ne l'ecrase plus. */
|
|
177
|
+
const titleTouched = ref(false)
|
|
118
178
|
|
|
119
179
|
watch(
|
|
120
180
|
() => props.viewTitle,
|
|
121
181
|
(value) => {
|
|
122
|
-
title.value = value
|
|
182
|
+
if (!titleTouched.value) title.value = value
|
|
123
183
|
},
|
|
124
184
|
)
|
|
125
185
|
|
|
186
|
+
watch(selectedViewId, (id) => {
|
|
187
|
+
const view = props.views.find((entry) => entry.id === id)
|
|
188
|
+
if (view && !titleTouched.value) title.value = view.title
|
|
189
|
+
// Une vue choisie doit etre posee a l'ecran avant la capture.
|
|
190
|
+
if (id) emit('select-view', id)
|
|
191
|
+
})
|
|
192
|
+
|
|
126
193
|
/**
|
|
127
194
|
* Deux bascules plutot qu'une generique : dans le template, un `ref` est
|
|
128
195
|
* deballe, donc lui passer la reference elle-meme ne marche pas.
|
|
@@ -146,7 +213,22 @@ const watchesChanges = computed(() => triggers.value.includes('changed'))
|
|
|
146
213
|
* moteur comparerait un hachage vide et ne trouverait jamais rien.
|
|
147
214
|
*/
|
|
148
215
|
const missingFields = computed(
|
|
149
|
-
() =>
|
|
216
|
+
() =>
|
|
217
|
+
watchesChanges.value &&
|
|
218
|
+
!anyField.value &&
|
|
219
|
+
watchedFields.value.length === 0 &&
|
|
220
|
+
props.fields.length > 0,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
/** Options de cadence, dans l'ordre du plus espace au plus frequent. */
|
|
224
|
+
const cadenceOptions = computed(() => [
|
|
225
|
+
{ value: 'monthly' as const, label: T.value.monthly },
|
|
226
|
+
{ value: 'weekly' as const, label: T.value.weekly },
|
|
227
|
+
{ value: 'daily' as const, label: T.value.daily },
|
|
228
|
+
])
|
|
229
|
+
|
|
230
|
+
const cadenceLabel = computed(
|
|
231
|
+
() => cadenceOptions.value.find((option) => option.value === cadence.value)?.label ?? '',
|
|
150
232
|
)
|
|
151
233
|
|
|
152
234
|
const valid = computed(
|
|
@@ -157,35 +239,65 @@ function submit() {
|
|
|
157
239
|
if (!valid.value) return
|
|
158
240
|
emit('submit', {
|
|
159
241
|
title: title.value.trim(),
|
|
242
|
+
viewId: selectedViewId.value,
|
|
160
243
|
triggers: triggers.value,
|
|
161
244
|
// Les champs ne servent qu'au declencheur « changement » : les garder
|
|
162
245
|
// sans lui ferait porter a l'alerte une promesse qu'elle ne tient pas.
|
|
163
|
-
watchedFields: watchesChanges.value
|
|
246
|
+
watchedFields: watchesChanges.value
|
|
247
|
+
? anyField.value
|
|
248
|
+
? [RM_ALERT_ANY_FIELD]
|
|
249
|
+
: watchedFields.value
|
|
250
|
+
: [],
|
|
164
251
|
channels: emailOn.value ? ['in_app', 'email'] : ['in_app'],
|
|
165
252
|
cadence: cadence.value,
|
|
166
|
-
|
|
253
|
+
// Le partage viendra plus tard : le schema le porte deja, l'ecran non.
|
|
254
|
+
visibility: 'private',
|
|
167
255
|
})
|
|
168
256
|
}
|
|
169
257
|
</script>
|
|
170
258
|
|
|
171
259
|
<template>
|
|
172
260
|
<form class="rm-alert-form" @submit.prevent="submit">
|
|
173
|
-
<RmInput
|
|
261
|
+
<RmInput
|
|
262
|
+
:model-value="title"
|
|
263
|
+
:label="T.name"
|
|
264
|
+
required
|
|
265
|
+
@update:model-value="
|
|
266
|
+
titleTouched = true;
|
|
267
|
+
title = $event;
|
|
268
|
+
"
|
|
269
|
+
/>
|
|
174
270
|
|
|
175
271
|
<div class="rm-alert-form__view">
|
|
176
272
|
<p class="rm-alert-form__label">{{ T.view }}</p>
|
|
177
|
-
<p class="rm-alert-
|
|
273
|
+
<p class="rm-alert-form__hint">
|
|
274
|
+
{{ viewOptions.length > 1 ? T.viewApplied : T.viewHint }}
|
|
275
|
+
</p>
|
|
276
|
+
|
|
277
|
+
<RmDropdownSelect
|
|
278
|
+
v-if="viewOptions.length > 1"
|
|
279
|
+
label=""
|
|
280
|
+
:options="viewOptions.map((option) => option.title)"
|
|
281
|
+
:model-value="selectedView?.title ?? ''"
|
|
282
|
+
@update:model-value="
|
|
283
|
+
selectedViewId =
|
|
284
|
+
viewOptions.find((option) => option.title === $event)?.id ?? null
|
|
285
|
+
"
|
|
286
|
+
/>
|
|
287
|
+
<p v-else class="rm-alert-form__view-name">
|
|
178
288
|
<RmIcon name="BookmarkSimple" :size="14" />
|
|
179
|
-
{{ viewTitle }}
|
|
180
|
-
<span v-if="scopeCount" class="rm-alert-form__count">
|
|
181
|
-
{{ scopeCount }} {{ T.scope }}
|
|
182
|
-
</span>
|
|
289
|
+
{{ selectedView?.title ?? viewTitle }}
|
|
183
290
|
</p>
|
|
184
|
-
|
|
291
|
+
|
|
292
|
+
<p v-if="scopeCount && selectedViewId" class="rm-alert-form__count">
|
|
293
|
+
{{ scopeCount }} {{ T.scope }}
|
|
294
|
+
</p>
|
|
295
|
+
<p v-if="!selectedViewId" class="rm-alert-form__hint">{{ T.screenNote }}</p>
|
|
185
296
|
</div>
|
|
186
297
|
|
|
187
298
|
<fieldset class="rm-alert-form__group">
|
|
188
299
|
<legend class="rm-alert-form__label">{{ T.triggers }}</legend>
|
|
300
|
+
<p class="rm-alert-form__hint">{{ T.triggersHint }}</p>
|
|
189
301
|
<RmCheckbox
|
|
190
302
|
v-for="entry in [
|
|
191
303
|
{ key: 'appeared', label: T.appeared, hint: T.appearedHint },
|
|
@@ -198,20 +310,21 @@ function submit() {
|
|
|
198
310
|
:tooltip="entry.hint"
|
|
199
311
|
@update:model-value="toggleTrigger(entry.key as RmAlertTrigger)"
|
|
200
312
|
/>
|
|
201
|
-
<p class="rm-alert-form__hint">{{ T.triggersHint }}</p>
|
|
202
313
|
</fieldset>
|
|
203
314
|
|
|
204
315
|
<fieldset v-if="watchesChanges" class="rm-alert-form__group">
|
|
205
316
|
<legend class="rm-alert-form__label">{{ T.fields }}</legend>
|
|
317
|
+
<p class="rm-alert-form__hint">{{ anyField ? T.anyFieldHint : T.fieldsHint }}</p>
|
|
206
318
|
<template v-if="fields.length">
|
|
319
|
+
<RmCheckbox v-model="anyField" :label="T.anyField" />
|
|
207
320
|
<RmCheckbox
|
|
208
321
|
v-for="field in fields"
|
|
209
322
|
:key="field.value"
|
|
210
|
-
:model-value="watchedFields.includes(field.value)"
|
|
323
|
+
:model-value="anyField || watchedFields.includes(field.value)"
|
|
211
324
|
:label="field.label"
|
|
325
|
+
:disabled="anyField"
|
|
212
326
|
@update:model-value="toggleField(field.value)"
|
|
213
327
|
/>
|
|
214
|
-
<p class="rm-alert-form__hint">{{ T.fieldsHint }}</p>
|
|
215
328
|
<p v-if="missingFields" class="rm-alert-form__error">{{ T.fieldsRequired }}</p>
|
|
216
329
|
</template>
|
|
217
330
|
<p v-else class="rm-alert-form__hint">{{ T.noFields }}</p>
|
|
@@ -223,21 +336,20 @@ function submit() {
|
|
|
223
336
|
<RmCheckbox v-model="emailOn" :label="T.email" />
|
|
224
337
|
|
|
225
338
|
<div v-if="emailOn" class="rm-alert-form__nested">
|
|
339
|
+
<p class="rm-alert-form__label">{{ T.cadence }}</p>
|
|
340
|
+
<p class="rm-alert-form__hint">{{ T.cadenceHint }}</p>
|
|
226
341
|
<RmDropdownSelect
|
|
227
|
-
|
|
228
|
-
:options="
|
|
229
|
-
:model-value="
|
|
230
|
-
@update:model-value="
|
|
342
|
+
label=""
|
|
343
|
+
:options="cadenceOptions.map((option) => option.label)"
|
|
344
|
+
:model-value="cadenceLabel"
|
|
345
|
+
@update:model-value="
|
|
346
|
+
cadence =
|
|
347
|
+
cadenceOptions.find((option) => option.label === $event)?.value ?? 'weekly'
|
|
348
|
+
"
|
|
231
349
|
/>
|
|
232
|
-
<p class="rm-alert-form__hint">{{ T.cadenceHint }}</p>
|
|
233
350
|
</div>
|
|
234
351
|
</fieldset>
|
|
235
352
|
|
|
236
|
-
<fieldset class="rm-alert-form__group">
|
|
237
|
-
<legend class="rm-alert-form__label">{{ T.visibility }}</legend>
|
|
238
|
-
<RmCheckbox v-model="shared" :label="T.workspace" />
|
|
239
|
-
</fieldset>
|
|
240
|
-
|
|
241
353
|
<p class="rm-alert-form__baseline">
|
|
242
354
|
<RmIcon name="Info" :size="14" />
|
|
243
355
|
{{ T.baseline }}
|
|
@@ -258,5 +370,5 @@ function submit() {
|
|
|
258
370
|
</template>
|
|
259
371
|
|
|
260
372
|
<style scoped>
|
|
261
|
-
.rm-alert-form{display:flex;flex-direction:column;gap:18px}.rm-alert-form__label{color:var(--rm-text,#111827);font-size:13px;font-weight:600;margin:0 0
|
|
373
|
+
.rm-alert-form{display:flex;flex-direction:column;gap:18px}.rm-alert-form__label{color:var(--rm-text,#111827);font-size:13px;font-weight:600;margin:0 0 2px;padding:0}.rm-alert-form__group{border:0;display:flex;flex-direction:column;gap:8px;margin:0;padding:0}.rm-alert-form__view-name{align-items:center;color:var(--rm-text,#111827);display:flex;flex-wrap:wrap;font-size:14px;gap:6px;margin:0}.rm-alert-form__count{color:var(--rm-text-soft,#9ca3af);font-size:11.5px}.rm-alert-form__hint{color:var(--rm-text-soft,#9ca3af);font-size:12px;line-height:1.45;margin:0 0 4px}.rm-alert-form__error{color:var(--rm-danger,#b91c1c);font-size:12px;margin:0}.rm-alert-form__nested{display:flex;flex-direction:column;gap:6px;padding-left:26px}.rm-alert-form__baseline{align-items:flex-start;background:var(--rm-surface-muted,#f9fafb);border-radius:8px;color:var(--rm-text-soft,#4b5563);display:flex;font-size:12.5px;gap:8px;line-height:1.45;margin:0;padding:10px 12px}.rm-alert-form__actions{display:flex;gap:8px;justify-content:flex-end}
|
|
262
374
|
</style>
|
|
@@ -86,6 +86,15 @@ const props = withDefaults(
|
|
|
86
86
|
|
|
87
87
|
const emit = defineEmits<{
|
|
88
88
|
export: [config: RmExportConfig]
|
|
89
|
+
/**
|
|
90
|
+
* Plage retenue a l'etape « Donnees ».
|
|
91
|
+
*
|
|
92
|
+
* Emis au choix, et pas seulement a l'export : une plage peut changer les
|
|
93
|
+
* colonnes disponibles (l'historique des prix ajoute la variation). Sans ce
|
|
94
|
+
* signal, l'etape « Colonnes » listerait celles de l'autre plage, et
|
|
95
|
+
* l'export perdrait silencieusement la colonne propre a celle-ci.
|
|
96
|
+
*/
|
|
97
|
+
'range-change': [key: string]
|
|
89
98
|
/** Relancer un export conserve, par son identifiant cote app. */
|
|
90
99
|
rerun: [id: string]
|
|
91
100
|
/** Ouvrir l'ecran d'origine d'un export tire d'ailleurs. */
|
|
@@ -242,14 +251,28 @@ watch(
|
|
|
242
251
|
},
|
|
243
252
|
)
|
|
244
253
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
254
|
+
/*
|
|
255
|
+
Les colonnes ne sont connues qu'une fois la page chargee : la selection
|
|
256
|
+
suit, sinon un export lance tot sort avec toutes les colonnes alors que le
|
|
257
|
+
panneau en montre cinq.
|
|
258
|
+
|
|
259
|
+
Une colonne qui APPARAIT est cochee d'office. Le cas vient du changement de
|
|
260
|
+
plage : passer aux historiques ajoute la variation de prix, et la garder
|
|
261
|
+
decochee ferait sortir un fichier sans la seule colonne propre a la plage
|
|
262
|
+
qu'on vient de choisir. Decocher reste un geste, jamais un defaut.
|
|
263
|
+
*/
|
|
248
264
|
watch(
|
|
249
265
|
() => props.columns,
|
|
250
|
-
(next) => {
|
|
251
|
-
|
|
266
|
+
(next, previous) => {
|
|
267
|
+
const added = next.filter(column => !(previous ?? []).includes(column))
|
|
268
|
+
config.columns = [
|
|
269
|
+
...config.columns.filter(column => next.includes(column)),
|
|
270
|
+
...added,
|
|
271
|
+
]
|
|
252
272
|
if (!config.columns.length) config.columns = [...next]
|
|
273
|
+
// L'ordre de la page fait foi : concatener les nouvelles a la fin
|
|
274
|
+
// sortirait la variation apres la derniere colonne au lieu de sa place.
|
|
275
|
+
config.columns = next.filter(column => config.columns.includes(column))
|
|
253
276
|
},
|
|
254
277
|
)
|
|
255
278
|
|
|
@@ -296,6 +319,8 @@ const range = computed(
|
|
|
296
319
|
|
|
297
320
|
const rows = computed(() => (showRange.value ? range.value?.rows ?? 0 : 0))
|
|
298
321
|
|
|
322
|
+
watch(() => config.range, key => emit('range-change', key))
|
|
323
|
+
|
|
299
324
|
const sizeKb = computed(() =>
|
|
300
325
|
rmExportSizeKb({
|
|
301
326
|
format: config.format,
|
|
@@ -723,7 +748,7 @@ const recentDate = (at: string | Date) =>
|
|
|
723
748
|
<div>
|
|
724
749
|
<div class="rm-export__recap-k">{{ t.recapData }}</div>
|
|
725
750
|
<div class="rm-export__recap-v">
|
|
726
|
-
{{ rows.toLocaleString(lang === 'en' ? 'en-US' : 'fr-FR') }} {{ t.rows }}, {{ range?.label }}
|
|
751
|
+
{{ rows.toLocaleString(lang === 'en' ? 'en-US' : 'fr-FR') }} {{ range?.unit ?? t.rows }}, {{ range?.label }}
|
|
727
752
|
</div>
|
|
728
753
|
</div>
|
|
729
754
|
</div>
|
|
@@ -101,5 +101,5 @@ const modalStyle = computed(() => {
|
|
|
101
101
|
</template>
|
|
102
102
|
|
|
103
103
|
<style scoped>
|
|
104
|
-
.rm-modal-overlay{align-items:center;backdrop-filter:blur(2px);background:rgba(0,0,0,.4);display:flex;inset:0;justify-content:center;position:fixed;z-index:100}.rm-modal{background:var(--bg-surface);border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,.15);display:flex;flex-direction:column;max-height:90vh;max-width:95vw}.rm-modal__header{align-items:flex-start;display:flex;gap:var(--space-16);justify-content:space-between;padding:var(--space-20) var(--space-24) 0}.rm-modal__header-content{flex:1;min-width:0}.rm-modal__close{align-items:center;background:none;border:none;border-radius:var(--r-radius-md);color:var(--text-muted);cursor:pointer;display:flex;flex-shrink:0;height:32px;justify-content:center;transition:all var(--r-transition-fast);width:32px}.rm-modal__close:hover{background:var(--bg-surface-3);color:var(--text-primary)}.rm-modal__tabs{padding:0}.rm-modal__body{flex:1;overflow-y:auto;padding:var(--space-24)}.rm-modal__footer{border-top:1px solid var(--border-soft);padding:var(--space-16) var(--space-24)}.rm-modal-enter-active,.rm-modal-leave-active{transition:opacity .2s ease}.rm-modal-enter-active .rm-modal,.rm-modal-leave-active .rm-modal{transition:transform .2s ease}.rm-modal-enter-from,.rm-modal-leave-to{opacity:0}.rm-modal-enter-from .rm-modal,.rm-modal-leave-to .rm-modal{transform:scale(.95)}.rm-modal-leave-active,.rm-modal-leave-to{pointer-events:none}
|
|
104
|
+
.rm-modal-overlay{align-items:center;backdrop-filter:blur(2px);background:rgba(0,0,0,.4);display:flex;font-family:var(--r-font-family);inset:0;justify-content:center;position:fixed;z-index:100}.rm-modal{background:var(--bg-surface);border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,.15);display:flex;flex-direction:column;max-height:90vh;max-width:95vw}.rm-modal__header{align-items:flex-start;display:flex;gap:var(--space-16);justify-content:space-between;padding:var(--space-20) var(--space-24) 0}.rm-modal__header-content{flex:1;min-width:0}.rm-modal__close{align-items:center;background:none;border:none;border-radius:var(--r-radius-md);color:var(--text-muted);cursor:pointer;display:flex;flex-shrink:0;height:32px;justify-content:center;transition:all var(--r-transition-fast);width:32px}.rm-modal__close:hover{background:var(--bg-surface-3);color:var(--text-primary)}.rm-modal__tabs{padding:0}.rm-modal__body{flex:1;overflow-y:auto;padding:var(--space-24)}.rm-modal__footer{border-top:1px solid var(--border-soft);padding:var(--space-16) var(--space-24)}.rm-modal-enter-active,.rm-modal-leave-active{transition:opacity .2s ease}.rm-modal-enter-active .rm-modal,.rm-modal-leave-active .rm-modal{transition:transform .2s ease}.rm-modal-enter-from,.rm-modal-leave-to{opacity:0}.rm-modal-enter-from .rm-modal,.rm-modal-leave-to .rm-modal{transform:scale(.95)}.rm-modal-leave-active,.rm-modal-leave-to{pointer-events:none}
|
|
105
105
|
</style>
|
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
* importer un `.vue`. Meme raison que pour `utils/export.ts`.
|
|
7
7
|
*/
|
|
8
8
|
export type RmAlertStatus = 'seeding' | 'active' | 'paused' | 'stale' | 'failing';
|
|
9
|
+
/** Plafond d'envoi du mail, jamais une promesse de frequence. */
|
|
10
|
+
export type RmAlertCadence = 'daily' | 'weekly' | 'monthly';
|
|
11
|
+
/**
|
|
12
|
+
* Surveiller le document entier plutot qu'une liste de champs. Les champs
|
|
13
|
+
* d'ingestion (horodatage, date de collecte) restent exclus cote moteur,
|
|
14
|
+
* sinon chaque reindexation ferait bouger chaque document.
|
|
15
|
+
*/
|
|
16
|
+
export declare const RM_ALERT_ANY_FIELD = "*";
|
|
9
17
|
/** Ce qu'une alerte sait detecter. Les trois sortent du meme passage de diff. */
|
|
10
18
|
export type RmAlertTrigger = 'appeared' | 'changed' | 'disappeared';
|
|
11
19
|
export interface RmAlertMenuItem {
|
|
@@ -66,12 +74,22 @@ export interface RmAlertField {
|
|
|
66
74
|
value: string;
|
|
67
75
|
label: string;
|
|
68
76
|
}
|
|
77
|
+
/** Une vue proposee dans le selecteur du formulaire. */
|
|
78
|
+
export interface RmAlertViewOption {
|
|
79
|
+
id: string;
|
|
80
|
+
title: string;
|
|
81
|
+
}
|
|
69
82
|
/** Ce que le formulaire de creation rend a l'appelant. */
|
|
70
83
|
export interface RmAlertDraft {
|
|
71
84
|
title: string;
|
|
85
|
+
/**
|
|
86
|
+
* Vue a surveiller. `null` : l'ecran actuel, que l'appelant enregistrera
|
|
87
|
+
* comme vue avant de creer l'alerte.
|
|
88
|
+
*/
|
|
89
|
+
viewId: string | null;
|
|
72
90
|
triggers: RmAlertTrigger[];
|
|
73
91
|
watchedFields: string[];
|
|
74
92
|
channels: string[];
|
|
75
|
-
cadence:
|
|
93
|
+
cadence: RmAlertCadence;
|
|
76
94
|
visibility: 'private' | 'workspace';
|
|
77
95
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const RM_ALERT_ANY_FIELD = "*";
|
|
@@ -119,7 +119,17 @@ export interface RmExportSection {
|
|
|
119
119
|
export interface RmExportRange {
|
|
120
120
|
key: string;
|
|
121
121
|
label: string;
|
|
122
|
+
/**
|
|
123
|
+
* Ce que la plage represente, en nombre. Pas toujours des lignes : un export
|
|
124
|
+
* qui deplie une entite en plusieurs lignes se plafonne par entite (500
|
|
125
|
+
* marques, pas 500 lignes), sans quoi il coupe la derniere en deux.
|
|
126
|
+
*/
|
|
122
127
|
rows: number;
|
|
128
|
+
/**
|
|
129
|
+
* Nom de ce que `rows` compte, au pluriel (« marques », « essais »). Absent,
|
|
130
|
+
* le recapitulatif dit « lignes ».
|
|
131
|
+
*/
|
|
132
|
+
unit?: string;
|
|
123
133
|
}
|
|
124
134
|
/** Option de l'etape « Personnaliser » (logo, sources, ZIP...). */
|
|
125
135
|
export interface RmExportOption {
|