@tekkare/romulus 0.1.183 → 0.1.185

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.182",
7
+ "version": "0.1.184",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -14,7 +14,11 @@
14
14
  voudrait dire autant de requetes et autant de references sur le meme jeu
15
15
  de documents, et plusieurs messages le meme matin pour le meme produit ;
16
16
  - la cadence appartient au mail. La pastille de l'app encaisse au fil de
17
- l'eau, elle n'a pas de digest.
17
+ l'eau, elle n'a pas de digest ;
18
+ - une alerte de produit ne choisit ni champs ni declencheurs : ils sont deja
19
+ forces a « tout » (cf. `watchesProduct`), le formulaire ne fait que les
20
+ cacher. Choisir « seulement l'apparition » sur une marque suivie partout
21
+ ferait manquer un dereboursement.
18
22
  -->
19
23
  <script setup lang="ts">
20
24
  import { computed, ref, watch } from 'vue'
@@ -103,6 +107,13 @@ const emit = defineEmits<{
103
107
  'select-view': [id: string]
104
108
  /** Saisie du selecteur de marques, a porter jusqu'a l'index. */
105
109
  'search-brands': [value: string]
110
+ /**
111
+ * Le formulaire s'ecarte de sa reference de depart. L'appelant n'a pas
112
+ * acces aux refs internes (titre, marques, declencheurs...) : c'est le
113
+ * seul moyen pour lui de savoir s'il y a quelque chose a perdre avant de
114
+ * fermer.
115
+ */
116
+ 'update:dirty': [value: boolean]
106
117
  }>()
107
118
 
108
119
  const FR = {
@@ -116,7 +127,7 @@ const FR = {
116
127
  brandsHint: 'Cherchez par marque, molécule, classe ATC ou laboratoire. L’alerte couvre tout le hub pour chaque marque retenue.',
117
128
  brandsRequired: 'Choisissez au moins une marque.',
118
129
  brandsTooMany: 'Une alerte suit {max} marques au maximum.',
119
- productFields: 'Tous les champs sont suivis : une alerte de marque ne se restreint pas, elle rapporte tout ce qui bouge.',
130
+ productFields: 'Rien à choisir : une alerte de marque suit tout, tous champs et tous déclencheurs confondus (nouvel élément, changement de valeur, sortie du périmètre).',
120
131
  screenOption: 'Écran actuel (une vue sera créée)',
121
132
  viewSelect: 'Vue à surveiller',
122
133
  viewApplied: 'La vue choisie est appliquée à l’écran : c’est lui qui fournit la requête surveillée.',
@@ -169,7 +180,7 @@ const EN = {
169
180
  brandsHint: 'Search by brand, molecule, ATC class or company. The alert covers the whole hub for each brand kept.',
170
181
  brandsRequired: 'Pick at least one brand.',
171
182
  brandsTooMany: 'An alert watches at most {max} brands.',
172
- productFields: 'Every field is watched: a brand alert does not narrow down, it reports whatever moves.',
183
+ productFields: 'Nothing to choose: a brand alert watches everything, every field and every trigger (new item, value change, left the scope).',
173
184
  screenOption: 'Current screen (a view will be created)',
174
185
  viewSelect: 'View to watch',
175
186
  viewApplied: 'The chosen view is applied to the screen: the screen is what provides the watched query.',
@@ -249,7 +260,19 @@ watch(brandSearch, (value) => emit('search-brands', value))
249
260
  const watchesProduct = computed(() => kind.value === 'product')
250
261
 
251
262
  const title = ref(props.initial?.title ?? props.viewTitle)
252
- const triggers = ref<RmAlertTrigger[]>(props.initial?.triggers ?? ['appeared'])
263
+ /**
264
+ * Une alerte de produit suit tout, declencheurs compris : le choix ne se pose
265
+ * pas plus que celui des champs, et cocher que « nouvel element » laisserait
266
+ * croire qu'un changement de prix sur une marque deja suivie ne compte pas.
267
+ */
268
+ const ALL_TRIGGERS: RmAlertTrigger[] = ['appeared', 'changed', 'disappeared']
269
+ const triggers = ref<RmAlertTrigger[]>(
270
+ props.initial?.triggers ??
271
+ (kind.value === 'product' ? ALL_TRIGGERS : ['appeared']),
272
+ )
273
+ watch(kind, (value) => {
274
+ if (value === 'product') triggers.value = ALL_TRIGGERS
275
+ })
253
276
  const watchedFields = ref<string[]>(
254
277
  (props.initial?.watchedFields ?? []).filter((field) => field !== RM_ALERT_ANY_FIELD),
255
278
  )
@@ -264,6 +287,37 @@ const initialFields = JSON.stringify([...(props.initial?.watchedFields ?? [])].s
264
287
  /** Titre libre des que l'utilisateur y touche : on ne l'ecrase plus. */
265
288
  const titleTouched = ref(props.mode === 'edit')
266
289
 
290
+ /**
291
+ * Etat de depart, capture une seule fois : ce a quoi `dirty` compare le
292
+ * formulaire. Le titre n'y figure pas, il se suit par `titleTouched` (il
293
+ * bouge tout seul tant que l'utilisateur n'y a pas touche, cf. plus bas).
294
+ */
295
+ const baseline = {
296
+ kind: kind.value,
297
+ brands: JSON.stringify([...brands.value].sort()),
298
+ triggers: JSON.stringify([...triggers.value].sort()),
299
+ watchedFields: JSON.stringify([...watchedFields.value].sort()),
300
+ anyField: anyField.value,
301
+ emailOn: emailOn.value,
302
+ cadence: cadence.value,
303
+ selectedViewId: selectedViewId.value,
304
+ }
305
+
306
+ const dirty = computed(
307
+ () =>
308
+ titleTouched.value ||
309
+ kind.value !== baseline.kind ||
310
+ JSON.stringify([...brands.value].sort()) !== baseline.brands ||
311
+ JSON.stringify([...triggers.value].sort()) !== baseline.triggers ||
312
+ JSON.stringify([...watchedFields.value].sort()) !== baseline.watchedFields ||
313
+ anyField.value !== baseline.anyField ||
314
+ emailOn.value !== baseline.emailOn ||
315
+ cadence.value !== baseline.cadence ||
316
+ selectedViewId.value !== baseline.selectedViewId,
317
+ )
318
+
319
+ watch(dirty, (value) => emit('update:dirty', value), { immediate: true })
320
+
267
321
  watch(
268
322
  () => props.viewTitle,
269
323
  (value) => {
@@ -495,7 +549,7 @@ function submit() {
495
549
  {{ watchesProduct ? `${T.brands} : ${brands.join(', ')}` : `${T.view} : ${viewTitle}` }}
496
550
  </p>
497
551
 
498
- <fieldset class="rm-alert-form__group" data-field="triggers">
552
+ <fieldset v-if="!watchesProduct" class="rm-alert-form__group" data-field="triggers">
499
553
  <legend class="rm-alert-form__label">{{ T.triggers }}</legend>
500
554
  <p class="rm-alert-form__hint">{{ T.triggersHint }}</p>
501
555
  <RmCheckbox
@@ -512,7 +566,7 @@ function submit() {
512
566
  />
513
567
  </fieldset>
514
568
 
515
- <p v-if="watchesProduct && watchesChanges" class="rm-alert-form__hint">
569
+ <p v-if="watchesProduct" class="rm-alert-form__hint">
516
570
  {{ T.productFields }}
517
571
  </p>
518
572
 
@@ -163,9 +163,9 @@ export declare function rmLegend(theme: RmChartTheme, count: number, position?:
163
163
  * echelle de comparaison entre deux courbes.
164
164
  */
165
165
  /**
166
- * `formatter` s'applique AUSSI a la graduation, pas seulement a l'infobulle :
167
- * un axe qui affiche 400 000 000 la ou l'infobulle dit 400 M donne deux
168
- * lectures du meme nombre sur le meme graphe.
166
+ * `formatter` s'applique a la graduation. L'infobulle, elle, garde toujours
167
+ * le nombre entier (`rmNumber`) meme quand l'axe est abrege : au survol, le
168
+ * lecteur veut la valeur exacte, la ou l'axe n'a que la place pour "18,8 M".
169
169
  */
170
170
  /**
171
171
  * Couleur d'une serie a l'indice `index`.
@@ -1,3 +1,4 @@
1
+ import { rmNumber } from "./format.js";
1
2
  export const RM_PALETTES = {
2
3
  default: {
3
4
  cat: ["#2100AD", "#00D4A8", "#F59E0B", "#4164EE", "#EC4899", "#6B3FFF"],
@@ -227,6 +228,7 @@ export function rmLineOption(categories, series, theme, palette, options = {}) {
227
228
  const dual = series.some((s) => s.axis === 1);
228
229
  const bottomLegend = legendPosition === "bottom";
229
230
  const format = (value) => valueFormatter ? valueFormatter(value) : String(value);
231
+ const tooltipFormat = (value) => valueFormatter ? rmNumber(value) : String(value);
230
232
  const values = series.flatMap((s) => s.data).filter((v) => typeof v === "number");
231
233
  const floorAtZero = startAtZero && values.length > 0 && Math.min(...values) >= 0;
232
234
  const base = rmBaseOption(theme, decal);
@@ -239,7 +241,7 @@ export function rmLineOption(categories, series, theme, palette, options = {}) {
239
241
  tooltip: {
240
242
  ...base.tooltip,
241
243
  trigger: rmTooltipTrigger(series.length),
242
- ...valueFormatter ? { valueFormatter: (value) => typeof value === "number" ? format(value) : "" } : {}
244
+ ...valueFormatter ? { valueFormatter: (value) => typeof value === "number" ? tooltipFormat(value) : "" } : {}
243
245
  },
244
246
  // Legende obligatoire en double axe : sans elle, rien ne dit quelle courbe
245
247
  // se lit sur quel axe.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekkare/romulus",
3
- "version": "0.1.183",
3
+ "version": "0.1.185",
4
4
  "description": "Romulus Design System - Nuxt module by Tekkare",
5
5
  "license": "MIT",
6
6
  "type": "module",