@tekkare/romulus 0.1.112 → 0.1.114
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
|
@@ -40,6 +40,7 @@ const emit = defineEmits<{
|
|
|
40
40
|
|
|
41
41
|
const FR = {
|
|
42
42
|
view: 'Vue',
|
|
43
|
+
brands: 'Marques',
|
|
43
44
|
trigger: 'Déclencheur',
|
|
44
45
|
watched: 'Champs suivis',
|
|
45
46
|
channel: 'Canaux',
|
|
@@ -100,6 +101,7 @@ const FR = {
|
|
|
100
101
|
|
|
101
102
|
const EN = {
|
|
102
103
|
view: 'View',
|
|
104
|
+
brands: 'Brands',
|
|
103
105
|
trigger: 'Trigger',
|
|
104
106
|
watched: 'Watched fields',
|
|
105
107
|
channel: 'Channels',
|
|
@@ -278,8 +280,10 @@ function pick(item: RmAlertMenuItem) {
|
|
|
278
280
|
</div>
|
|
279
281
|
|
|
280
282
|
<p v-if="alert.viewTitle" class="rm-alert__view">
|
|
281
|
-
|
|
282
|
-
|
|
283
|
+
<!-- Une alerte de produit ne surveille pas une vue : l'annoncer comme
|
|
284
|
+
telle ferait chercher une vue qui n'existe pas. -->
|
|
285
|
+
<RmIcon :name="alert.kind === 'product' ? 'Tag' : 'BookmarkSimple'" :size="13" />
|
|
286
|
+
{{ alert.kind === 'product' ? T.brands : T.view }} : {{ alert.viewTitle }}
|
|
283
287
|
<span v-if="alert.shared" class="rm-alert__shared">{{ T.shared }}</span>
|
|
284
288
|
</p>
|
|
285
289
|
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import { computed, ref, watch } from 'vue'
|
|
21
21
|
import type {
|
|
22
22
|
RmAlertCadence,
|
|
23
|
+
RmAlertKind,
|
|
23
24
|
RmAlertDraft,
|
|
24
25
|
RmAlertField,
|
|
25
26
|
RmAlertTrigger,
|
|
@@ -43,6 +44,19 @@ const props = withDefaults(
|
|
|
43
44
|
canSaveScreen?: boolean
|
|
44
45
|
/** Champs surveillables de la page. Vide : seule l'apparition compte. */
|
|
45
46
|
fields?: RmAlertField[]
|
|
47
|
+
/**
|
|
48
|
+
* Marques proposees au choix, pour une alerte de produit. Elles arrivent
|
|
49
|
+
* du serveur a chaque saisie : le formulaire ne filtre pas lui-meme.
|
|
50
|
+
*/
|
|
51
|
+
brandOptions?: string[]
|
|
52
|
+
brandsLoading?: boolean
|
|
53
|
+
/** Plafond de marques, tel que l'API l'impose. */
|
|
54
|
+
maxBrands?: number
|
|
55
|
+
/**
|
|
56
|
+
* L'ecran permet de partir d'une vue. Faux depuis un endroit qui n'en a
|
|
57
|
+
* pas : le choix se reduit alors a l'alerte de produit.
|
|
58
|
+
*/
|
|
59
|
+
canWatchView?: boolean
|
|
46
60
|
/** Nombre d'elements que la vue tient a l'ecran, s'il est connu. */
|
|
47
61
|
scopeCount?: number | null
|
|
48
62
|
submitting?: boolean
|
|
@@ -64,6 +78,10 @@ const props = withDefaults(
|
|
|
64
78
|
{
|
|
65
79
|
views: () => [],
|
|
66
80
|
canSaveScreen: false,
|
|
81
|
+
brandOptions: () => [],
|
|
82
|
+
brandsLoading: false,
|
|
83
|
+
maxBrands: 20,
|
|
84
|
+
canWatchView: true,
|
|
67
85
|
fields: () => [],
|
|
68
86
|
scopeCount: null,
|
|
69
87
|
submitting: false,
|
|
@@ -83,9 +101,21 @@ const emit = defineEmits<{
|
|
|
83
101
|
* la vue surveillee doivent dire la meme chose au moment de la capture.
|
|
84
102
|
*/
|
|
85
103
|
'select-view': [id: string]
|
|
104
|
+
/** Saisie du selecteur de marques, a porter jusqu'a l'index. */
|
|
105
|
+
'search-brands': [value: string]
|
|
86
106
|
}>()
|
|
87
107
|
|
|
88
108
|
const FR = {
|
|
109
|
+
kind: 'Que surveiller',
|
|
110
|
+
kindView: 'Une vue',
|
|
111
|
+
kindViewHint: 'Un écran filtré, aussi fin que vous voulez, mais limité à cette page.',
|
|
112
|
+
kindProduct: 'Un produit',
|
|
113
|
+
kindProductHint: 'Une à {max} marques, suivies partout : prix, avis HAS, indications, ANSM, PUT, MITM, Journal officiel.',
|
|
114
|
+
brands: 'Marques surveillées',
|
|
115
|
+
brandsHint: 'Cherchez une marque et ajoutez-la. L’alerte couvre tout le hub pour chacune.',
|
|
116
|
+
brandsRequired: 'Choisissez au moins une marque.',
|
|
117
|
+
brandsTooMany: 'Une alerte suit {max} marques au maximum.',
|
|
118
|
+
productFields: 'Tous les champs sont suivis : une alerte de produit ne se restreint pas, elle rapporte tout ce qui bouge sur ces marques.',
|
|
89
119
|
screenOption: 'Écran actuel (une vue sera créée)',
|
|
90
120
|
viewSelect: 'Vue à surveiller',
|
|
91
121
|
viewApplied: 'La vue choisie est appliquée à l’écran : c’est lui qui fournit la requête surveillée.',
|
|
@@ -128,6 +158,16 @@ const FR = {
|
|
|
128
158
|
}
|
|
129
159
|
|
|
130
160
|
const EN = {
|
|
161
|
+
kind: 'What to watch',
|
|
162
|
+
kindView: 'A view',
|
|
163
|
+
kindViewHint: 'A filtered screen, as precise as you like, but tied to that page.',
|
|
164
|
+
kindProduct: 'A product',
|
|
165
|
+
kindProductHint: 'One to {max} brands, watched everywhere: prices, HAS opinions, indications, ANSM, PUT, MITM, official journal.',
|
|
166
|
+
brands: 'Watched brands',
|
|
167
|
+
brandsHint: 'Search a brand and add it. The alert covers the whole hub for each one.',
|
|
168
|
+
brandsRequired: 'Pick at least one brand.',
|
|
169
|
+
brandsTooMany: 'An alert watches at most {max} brands.',
|
|
170
|
+
productFields: 'Every field is watched: a product alert does not narrow down, it reports whatever moves on those brands.',
|
|
131
171
|
screenOption: 'Current screen (a view will be created)',
|
|
132
172
|
viewSelect: 'View to watch',
|
|
133
173
|
viewApplied: 'The chosen view is applied to the screen: the screen is what provides the watched query.',
|
|
@@ -189,6 +229,16 @@ const selectedView = computed(
|
|
|
189
229
|
|
|
190
230
|
const editing = computed(() => props.mode === 'edit')
|
|
191
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Ce que l'alerte surveille. Le choix ne se pose qu'a la creation : changer
|
|
234
|
+
* une alerte de vue en alerte de produit, ce n'est plus la meme alerte.
|
|
235
|
+
*/
|
|
236
|
+
const kind = ref<RmAlertKind>(
|
|
237
|
+
(props.initial?.kind as RmAlertKind) ?? (props.canWatchView ? 'view' : 'product')
|
|
238
|
+
)
|
|
239
|
+
const brands = ref<string[]>(props.initial?.brands ?? [])
|
|
240
|
+
const watchesProduct = computed(() => kind.value === 'product')
|
|
241
|
+
|
|
192
242
|
const title = ref(props.initial?.title ?? props.viewTitle)
|
|
193
243
|
const triggers = ref<RmAlertTrigger[]>(props.initial?.triggers ?? ['appeared'])
|
|
194
244
|
const watchedFields = ref<string[]>(
|
|
@@ -212,6 +262,22 @@ watch(
|
|
|
212
262
|
},
|
|
213
263
|
)
|
|
214
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Le titre suit ce qu'on surveille, tant que l'utilisateur n'y a pas touche.
|
|
267
|
+
*
|
|
268
|
+
* En passant sur « un produit », garder le nom d'une vue donnerait une alerte
|
|
269
|
+
* « Suivi prix anticoagulants » qui suit en realite Opdivo.
|
|
270
|
+
*/
|
|
271
|
+
watch([kind, brands], () => {
|
|
272
|
+
if (titleTouched.value) return
|
|
273
|
+
if (watchesProduct.value) {
|
|
274
|
+
title.value = brands.value.length ? `Veille ${brands.value.join(', ')}` : ''
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
const view = props.views.find((entry) => entry.id === selectedViewId.value)
|
|
278
|
+
title.value = view?.title ?? props.viewTitle
|
|
279
|
+
})
|
|
280
|
+
|
|
215
281
|
watch(selectedViewId, (id) => {
|
|
216
282
|
const view = props.views.find((entry) => entry.id === id)
|
|
217
283
|
if (view && !titleTouched.value) title.value = view.title
|
|
@@ -263,8 +329,19 @@ const cadenceLabel = computed(
|
|
|
263
329
|
() => cadenceOptions.value.find((option) => option.value === cadence.value)?.label ?? '',
|
|
264
330
|
)
|
|
265
331
|
|
|
332
|
+
const brandsInvalid = computed(
|
|
333
|
+
() =>
|
|
334
|
+
watchesProduct.value &&
|
|
335
|
+
(brands.value.length === 0 || brands.value.length > props.maxBrands),
|
|
336
|
+
)
|
|
337
|
+
|
|
266
338
|
const valid = computed(
|
|
267
|
-
() =>
|
|
339
|
+
() =>
|
|
340
|
+
title.value.trim().length > 0 &&
|
|
341
|
+
triggers.value.length > 0 &&
|
|
342
|
+
!brandsInvalid.value &&
|
|
343
|
+
// Une alerte de produit suit tout : la question des champs ne se pose pas.
|
|
344
|
+
(watchesProduct.value || !missingFields.value),
|
|
268
345
|
)
|
|
269
346
|
|
|
270
347
|
/**
|
|
@@ -286,15 +363,21 @@ function submit() {
|
|
|
286
363
|
if (!valid.value) return
|
|
287
364
|
emit('submit', {
|
|
288
365
|
title: title.value.trim(),
|
|
289
|
-
|
|
366
|
+
kind: kind.value,
|
|
367
|
+
brands: watchesProduct.value ? brands.value : [],
|
|
368
|
+
viewId: watchesProduct.value ? null : selectedViewId.value,
|
|
290
369
|
triggers: triggers.value,
|
|
291
370
|
// Les champs ne servent qu'au declencheur « changement » : les garder
|
|
292
371
|
// sans lui ferait porter a l'alerte une promesse qu'elle ne tient pas.
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
:
|
|
372
|
+
// Une alerte de produit traverse sept index : une liste de champs nommes
|
|
373
|
+
// n'y voudrait rien dire, ils different d'un index a l'autre.
|
|
374
|
+
watchedFields: watchesProduct.value
|
|
375
|
+
? [RM_ALERT_ANY_FIELD]
|
|
376
|
+
: watchesChanges.value
|
|
377
|
+
? anyField.value
|
|
378
|
+
? [RM_ALERT_ANY_FIELD]
|
|
379
|
+
: watchedFields.value
|
|
380
|
+
: [],
|
|
298
381
|
channels: emailOn.value ? ['in_app', 'email'] : ['in_app'],
|
|
299
382
|
cadence: cadence.value,
|
|
300
383
|
// Le partage viendra plus tard : le schema le porte deja, l'ecran non.
|
|
@@ -315,7 +398,49 @@ function submit() {
|
|
|
315
398
|
"
|
|
316
399
|
/>
|
|
317
400
|
|
|
318
|
-
<
|
|
401
|
+
<fieldset v-if="!editing && canWatchView" class="rm-alert-form__group">
|
|
402
|
+
<legend class="rm-alert-form__label">{{ T.kind }}</legend>
|
|
403
|
+
<p class="rm-alert-form__hint">
|
|
404
|
+
{{
|
|
405
|
+
watchesProduct
|
|
406
|
+
? T.kindProductHint.replace('{max}', String(maxBrands))
|
|
407
|
+
: T.kindViewHint
|
|
408
|
+
}}
|
|
409
|
+
</p>
|
|
410
|
+
<RmSegmented
|
|
411
|
+
v-model="kind"
|
|
412
|
+
block
|
|
413
|
+
variant="soft"
|
|
414
|
+
:options="[
|
|
415
|
+
{ value: 'view', label: T.kindView },
|
|
416
|
+
{ value: 'product', label: T.kindProduct },
|
|
417
|
+
]"
|
|
418
|
+
/>
|
|
419
|
+
</fieldset>
|
|
420
|
+
|
|
421
|
+
<fieldset v-if="watchesProduct" class="rm-alert-form__group">
|
|
422
|
+
<legend class="rm-alert-form__label">{{ T.brands }}</legend>
|
|
423
|
+
<p class="rm-alert-form__hint">{{ T.brandsHint }}</p>
|
|
424
|
+
<RmMultipleSelect
|
|
425
|
+
v-model="brands"
|
|
426
|
+
label=""
|
|
427
|
+
search
|
|
428
|
+
:filter-search="false"
|
|
429
|
+
:options="brandOptions"
|
|
430
|
+
:loading="brandsLoading"
|
|
431
|
+
:show-amount="true"
|
|
432
|
+
:lang="lang"
|
|
433
|
+
@search="emit('search-brands', $event)"
|
|
434
|
+
/>
|
|
435
|
+
<p v-if="brands.length > maxBrands" class="rm-alert-form__error">
|
|
436
|
+
{{ T.brandsTooMany.replace('{max}', String(maxBrands)) }}
|
|
437
|
+
</p>
|
|
438
|
+
<p v-else-if="brands.length === 0" class="rm-alert-form__hint">
|
|
439
|
+
{{ T.brandsRequired }}
|
|
440
|
+
</p>
|
|
441
|
+
</fieldset>
|
|
442
|
+
|
|
443
|
+
<div v-else-if="!editing" class="rm-alert-form__view">
|
|
319
444
|
<p class="rm-alert-form__label">{{ T.view }}</p>
|
|
320
445
|
<p class="rm-alert-form__hint">
|
|
321
446
|
<!-- L'avertissement sur l'application ne vaut que pour une vue deja
|
|
@@ -344,9 +469,9 @@ function submit() {
|
|
|
344
469
|
<p v-if="!selectedViewId" class="rm-alert-form__hint">{{ T.screenNote }}</p>
|
|
345
470
|
</div>
|
|
346
471
|
|
|
347
|
-
<p v-else class="rm-alert-form__view-name">
|
|
348
|
-
<RmIcon name="BookmarkSimple" :size="14" />
|
|
349
|
-
{{ T.
|
|
472
|
+
<p v-else-if="editing" class="rm-alert-form__view-name">
|
|
473
|
+
<RmIcon :name="watchesProduct ? 'Tag' : 'BookmarkSimple'" :size="14" />
|
|
474
|
+
{{ watchesProduct ? `${T.brands} : ${brands.join(', ')}` : `${T.view} : ${viewTitle}` }}
|
|
350
475
|
</p>
|
|
351
476
|
|
|
352
477
|
<fieldset class="rm-alert-form__group">
|
|
@@ -366,7 +491,11 @@ function submit() {
|
|
|
366
491
|
/>
|
|
367
492
|
</fieldset>
|
|
368
493
|
|
|
369
|
-
<
|
|
494
|
+
<p v-if="watchesProduct && watchesChanges" class="rm-alert-form__hint">
|
|
495
|
+
{{ T.productFields }}
|
|
496
|
+
</p>
|
|
497
|
+
|
|
498
|
+
<fieldset v-if="watchesChanges && !watchesProduct" class="rm-alert-form__group">
|
|
370
499
|
<legend class="rm-alert-form__label">{{ T.fields }}</legend>
|
|
371
500
|
<p class="rm-alert-form__hint">{{ anyField ? T.anyFieldHint : T.fieldsHint }}</p>
|
|
372
501
|
<template v-if="fields.length">
|
|
@@ -30,7 +30,9 @@ export interface RmAlertMenuItem {
|
|
|
30
30
|
export interface RmAlert {
|
|
31
31
|
id: string;
|
|
32
32
|
title: string;
|
|
33
|
-
/**
|
|
33
|
+
/** Ce que l'alerte surveille : une vue, ou des marques. */
|
|
34
|
+
kind?: RmAlertKind;
|
|
35
|
+
/** Nom de la vue surveillee, ou les marques d'une alerte de produit. */
|
|
34
36
|
viewTitle?: string | null;
|
|
35
37
|
status: RmAlertStatus;
|
|
36
38
|
triggers: RmAlertTrigger[];
|
|
@@ -96,9 +98,19 @@ export interface RmAlertViewOption {
|
|
|
96
98
|
id: string;
|
|
97
99
|
title: string;
|
|
98
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Ce qu'une alerte surveille.
|
|
103
|
+
*
|
|
104
|
+
* `view` fige le perimetre d'un ecran : fin, mais lie a une page. `product`
|
|
105
|
+
* nomme des marques et vaut pour tout le hub, sans dependre d'aucune page.
|
|
106
|
+
*/
|
|
107
|
+
export type RmAlertKind = 'view' | 'product';
|
|
99
108
|
/** Ce que le formulaire de creation rend a l'appelant. */
|
|
100
109
|
export interface RmAlertDraft {
|
|
101
110
|
title: string;
|
|
111
|
+
kind: RmAlertKind;
|
|
112
|
+
/** Marques surveillees, pour une alerte de produit. */
|
|
113
|
+
brands: string[];
|
|
102
114
|
/**
|
|
103
115
|
* Vue a surveiller. `null` : l'ecran actuel, que l'appelant enregistrera
|
|
104
116
|
* comme vue avant de creer l'alerte.
|