@cityway/bo-ui 1.1.0-beta010 → 1.1.0-beta011

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.
Binary file
@@ -109,7 +109,7 @@ interface ImportWizardConfig {
109
109
  modes: readonly [ImportModeOption, ...ImportModeOption[]]; // jamais vide ; 1 élément = imposé
110
110
  interruption: ImportInterruption; // requis : ce qui se passe vraiment à l'arrêt
111
111
  showErrorDetails?: boolean; // [Option]
112
- scope?: ImportScopeEnum; // défaut list
112
+ scope: ImportScope; // requis — voir ci-dessous
113
113
  }
114
114
  ```
115
115
 
@@ -139,6 +139,26 @@ Aucune de ces situations n'est rattrapée à l'exécution : elles ne compilent p
139
139
 
140
140
  ---
141
141
 
142
+ ### `scope` — et le nom de l'objet importé
143
+
144
+ `scope` est un type discriminé : le libellé de l'objet **voyage avec la portée qui l'affiche**, comme `keyFieldHint` voyage avec son mode. Indéclarable en `singleConfig` (aucun compteur n'y est rendu), inoubliable en `list`.
145
+
146
+ ```ts
147
+ scope: { kind: ImportScopeEnum.list, objectLabel: i18nKey('DOMAIN.USER') }
148
+ // ou
149
+ scope: { kind: ImportScopeEnum.singleConfig }
150
+ ```
151
+
152
+ `objectLabel` est une clé **racine**, suffixée par le composant de la catégorie de pluriel de la langue active — exactement comme les libellés de compteur. Le bundle de l'application doit donc déclarer les trois catégories :
153
+
154
+ ```jsonc
155
+ "DOMAIN": { "USER": { "one": "utilisateur", "many": "utilisateurs", "other": "utilisateurs" } }
156
+ ```
157
+
158
+ Rendu : « **2 431** utilisateurs à ajouter ».
159
+
160
+ > ⚠️ **Accord.** Les libellés du *rapport* sont des participes passés accordés au masculin pluriel (« … ajoutés », « … supprimés »). Un nom féminin (« lignes ») donnerait « lignes ajoutés ». Le cas échéant, surcharger `_COMMON._IMPORT._COUNTS.REPORT_*` dans le bundle de l'application — le deepmerge du loader donne le dernier mot à l'application.
161
+
142
162
  ## 4. Exemples
143
163
 
144
164
  ### Import d'une liste, ouvert depuis le bouton *Importer* de l'en-tête
@@ -270,7 +290,7 @@ readonly configImport: ImportWizardConfig = {
270
290
  accept: '.json',
271
291
  acceptLabel: 'JSON',
272
292
  modes: [{ mode: ImportModeEnum.replaceAll }], // un seul mode => imposé, lecture seule
273
- scope: ImportScopeEnum.singleConfig, // pas de compteurs, message de conformité
293
+ scope: { kind: ImportScopeEnum.singleConfig }, // pas de compteurs, message de conformité
274
294
  interruption: { kind: ImportInterruptionEnum.atomic }, // back-end transactionnel
275
295
  source: { analyze: …, execute: … },
276
296
  };
@@ -1229,22 +1229,32 @@ var ImportInterruptionEnum;
1229
1229
  //
1230
1230
  // Pas dans basic-ui : ce paquet est derriere une frontiere de publication, et rien ne
1231
1231
  // presse. A remonter le jour ou basic-ui affichera lui aussi des decomptes.
1232
- /** Instances `Intl` mises en cache : une par langue, pas une par decompte affiche. */
1232
+ /**
1233
+ * Instances `Intl` mises en cache : une par langue, pas une par decompte affiche.
1234
+ *
1235
+ * `lang` peut etre `undefined` : c'est le cas ou le service de traduction n'a resolu NI
1236
+ * langue courante NI langue par defaut. `Intl` interprete `undefined` comme « prends la
1237
+ * locale de l'environnement », ce qui est la seule reponse honnete — un litteral y
1238
+ * imposerait la locale de l'auteur de la lib (cf. l'avertissement de `formatCount`).
1239
+ * La cle de cache `''` represente ce cas.
1240
+ */
1233
1241
  const PLURAL_RULES = new Map();
1234
1242
  const NUMBER_FORMATS = new Map();
1235
1243
  function pluralRules(lang) {
1236
- let rules = PLURAL_RULES.get(lang);
1244
+ const key = lang ?? '';
1245
+ let rules = PLURAL_RULES.get(key);
1237
1246
  if (!rules) {
1238
1247
  rules = new Intl.PluralRules(lang);
1239
- PLURAL_RULES.set(lang, rules);
1248
+ PLURAL_RULES.set(key, rules);
1240
1249
  }
1241
1250
  return rules;
1242
1251
  }
1243
1252
  function numberFormat(lang) {
1244
- let format = NUMBER_FORMATS.get(lang);
1253
+ const key = lang ?? '';
1254
+ let format = NUMBER_FORMATS.get(key);
1245
1255
  if (!format) {
1246
1256
  format = new Intl.NumberFormat(lang);
1247
- NUMBER_FORMATS.set(lang, format);
1257
+ NUMBER_FORMATS.set(key, format);
1248
1258
  }
1249
1259
  return format;
1250
1260
  }
@@ -1310,12 +1320,20 @@ class ImportWizardComponent {
1310
1320
  * Langue active, en signal : la categorie de pluriel et le format des nombres doivent
1311
1321
  * suivre un changement de langue a chaud.
1312
1322
  */
1313
- this.lang = toSignal(this.translate.onLangChange.pipe(map((event) => event.lang)),
1323
+ this.lang = toSignal(
1324
+ // Annotation explicite : `toSignal` impose que `initialValue` etende le type de la
1325
+ // source. Sans elle, la source est `string` et n'accepte pas `undefined`.
1326
+ this.translate.onLangChange.pipe(map((event) => event.lang)),
1314
1327
  // `currentLang` et `getDefaultLang()` sont types `string` en @ngx-translate/core 16,
1315
- // mais nullables dans des majeures plus recentes : le repli garde le contrat local a
1316
- // `string` quelle que soit la version installee chez le consommateur, et evite un
1317
- // `Intl.PluralRules('')` qui leverait. Meme forme que form-field (lang intl-tel-input).
1318
- { initialValue: this.translate.currentLang || this.translate.getDefaultLang() || 'fr' });
1328
+ // nullables a partir de la 17 (`Language | null`) : le `|| undefined` normalise, et le
1329
+ // contrat local devient `string | undefined`.
1330
+ //
1331
+ // Surtout, PAS de repli litteral (un `|| 'fr'` par exemple) : `lang` alimente
1332
+ // `Intl.PluralRules` / `Intl.NumberFormat`, et figer une locale imposerait les regles
1333
+ // de pluriel et les separateurs francais a une application qui n'a rien demande —
1334
+ // exactement ce que l'avertissement de `formatCount` proscrit. `undefined` laisse
1335
+ // `Intl` prendre la locale de l'environnement.
1336
+ { initialValue: this.translate.currentLang || this.translate.getDefaultLang() || undefined });
1319
1337
  /** Pont signal du control : un `computed` lisant `mode.value` resterait fige. */
1320
1338
  this.modeValue = toSignal(this.mode.valueChanges, { initialValue: null });
1321
1339
  /** Idem pour le fichier : meme piege de `computed` fige. */
@@ -1372,8 +1390,13 @@ class ImportWizardComponent {
1372
1390
  this.spriteIcon = SpriteIconEnum;
1373
1391
  this.loaderSize = LoaderSizeEnum;
1374
1392
  this.progressType = ProgressBarTypeEnum;
1375
- this.scope = computed(() => this.config().scope ?? ImportScopeEnum.list, ...(ngDevMode ? [{ debugName: "scope" }] : []));
1376
- this.isList = computed(() => this.scope() === ImportScopeEnum.list, ...(ngDevMode ? [{ debugName: "isList" }] : []));
1393
+ this.scope = computed(() => this.config().scope, ...(ngDevMode ? [{ debugName: "scope" }] : []));
1394
+ this.isList = computed(() => this.scope().kind === ImportScopeEnum.list, ...(ngDevMode ? [{ debugName: "isList" }] : []));
1395
+ /** Cle racine du nom de l'objet importe. `undefined` hors scope `list`, ou rien n'est compte. */
1396
+ this.objectLabelKey = computed(() => {
1397
+ const scope = this.scope();
1398
+ return scope.kind === ImportScopeEnum.list ? scope.objectLabel : undefined;
1399
+ }, ...(ngDevMode ? [{ debugName: "objectLabelKey" }] : []));
1377
1400
  this.interruption = computed(() => this.config().interruption, ...(ngDevMode ? [{ debugName: "interruption" }] : []));
1378
1401
  /** Le bouton d'arret n'existe que si l'interruption est reellement possible. */
1379
1402
  this.canInterrupt = computed(() => this.interruption().kind !== ImportInterruptionEnum.notInterruptible, ...(ngDevMode ? [{ debugName: "canInterrupt" }] : []));
@@ -1395,8 +1418,6 @@ class ImportWizardComponent {
1395
1418
  this.modes = computed(() => this.config().modes, ...(ngDevMode ? [{ debugName: "modes" }] : []));
1396
1419
  this.isModeImposed = computed(() => this.modes().length === 1, ...(ngDevMode ? [{ debugName: "isModeImposed" }] : []));
1397
1420
  this.modeOptions = computed(() => this.modes().map(({ mode }) => ({ value: mode, label: ImportWizardComponent.MODE_LABEL[mode] })), ...(ngDevMode ? [{ debugName: "modeOptions" }] : []));
1398
- /** Libelle du mode quand il est impose (affiche en lecture seule, sans groupe radio). */
1399
- this.imposedModeLabel = computed(() => ImportWizardComponent.MODE_LABEL[this.modes()[0].mode], ...(ngDevMode ? [{ debugName: "imposedModeLabel" }] : []));
1400
1421
  /**
1401
1422
  * Mode retenu : le seul propose s'il est impose, sinon la selection de l'utilisateur.
1402
1423
  * `null` en choix libre tant qu'il n'a pas choisi — il n'y a AUCUNE preselection, sous
@@ -1426,11 +1447,15 @@ class ImportWizardComponent {
1426
1447
  }, ...(ngDevMode ? [{ debugName: "selectedKeyFieldHint" }] : []));
1427
1448
  /** Avertissement sur la portee du mode retenu : `replaceAll` est destructif, les autres non. */
1428
1449
  this.modeNoticeType = computed(() => this.selectedMode() === ImportModeEnum.replaceAll ? AlertTypeEnum.warning : AlertTypeEnum.info, ...(ngDevMode ? [{ debugName: "modeNoticeType" }] : []));
1429
- /** `undefined` tant qu'aucun mode n'est retenu : pas d'alerte decrivant un mode non choisi. */
1430
- this.modeNoticeKey = computed(() => {
1450
+ /**
1451
+ * Contenu de l'alerte de portee : une phrase d'introduction, et pour les modes de fusion
1452
+ * la liste a puces de la maquette. `null` tant qu'aucun mode n'est retenu — l'alerte ne
1453
+ * decrit jamais un mode que l'utilisateur n'a pas choisi.
1454
+ */
1455
+ this.modeNotice = computed(() => {
1431
1456
  const selected = this.selectedMode();
1432
- return selected ? ImportWizardComponent.MODE_NOTICE[selected] : undefined;
1433
- }, ...(ngDevMode ? [{ debugName: "modeNoticeKey" }] : []));
1457
+ return selected ? ImportWizardComponent.MODE_NOTICE[selected] : null;
1458
+ }, ...(ngDevMode ? [{ debugName: "modeNotice" }] : []));
1434
1459
  /**
1435
1460
  * `cw-file-to-download` attend des `Attachment`, dont la taille est en Ko : conversion
1436
1461
  * a la frontiere. Le contrat public, lui, n'a qu'une seule unite — l'octet.
@@ -1457,6 +1482,18 @@ class ImportWizardComponent {
1457
1482
  * `computed` lisant la langue : categorie de pluriel et separateur de milliers suivent
1458
1483
  * un changement de langue a chaud.
1459
1484
  */
1485
+ /** « N erreurs » : meme rendu que les compteurs (nombre colore + libelle accorde). */
1486
+ this.errorCountRow = computed(() => {
1487
+ const total = this.errors().length;
1488
+ if (!total)
1489
+ return null;
1490
+ const lang = this.lang();
1491
+ return {
1492
+ key: pluralizedCountKey('_COMMON._IMPORT._COUNTS.ERRORS', total, lang),
1493
+ count: formatCount(total, lang),
1494
+ tone: 'danger',
1495
+ };
1496
+ }, ...(ngDevMode ? [{ debugName: "errorCountRow" }] : []));
1460
1497
  this.previewRows = computed(() => this.countRows(this.analysis()?.counts, 'PREVIEW'), ...(ngDevMode ? [{ debugName: "previewRows" }] : []));
1461
1498
  this.reportRows = computed(() => this.countRows(this.report()?.counts, 'REPORT'), ...(ngDevMode ? [{ debugName: "reportRows" }] : []));
1462
1499
  // `cw-card.collapsed` n'est qu'un etat INITIAL (lu au ngOnInit) : le rebinder ne replie
@@ -1497,9 +1534,20 @@ class ImportWizardComponent {
1497
1534
  [ImportModeEnum.replaceAll]: '_COMMON._IMPORT._MODE.REPLACE_ALL',
1498
1535
  }; }
1499
1536
  static { this.MODE_NOTICE = {
1500
- [ImportModeEnum.addOnly]: '_COMMON._IMPORT._MODE.ADD_ONLY_NOTICE',
1501
- [ImportModeEnum.upsert]: '_COMMON._IMPORT._MODE.UPSERT_NOTICE',
1502
- [ImportModeEnum.replaceAll]: '_COMMON._IMPORT._MODE.REPLACE_ALL_NOTICE',
1537
+ [ImportModeEnum.addOnly]: {
1538
+ lead: '_COMMON._IMPORT._MODE.MERGE_LEAD',
1539
+ items: ['_COMMON._IMPORT._MODE.MERGE_ADDED', '_COMMON._IMPORT._MODE.MERGE_UNCHANGED'],
1540
+ },
1541
+ [ImportModeEnum.upsert]: {
1542
+ lead: '_COMMON._IMPORT._MODE.MERGE_LEAD',
1543
+ items: [
1544
+ '_COMMON._IMPORT._MODE.MERGE_ADDED',
1545
+ '_COMMON._IMPORT._MODE.MERGE_UPDATED',
1546
+ '_COMMON._IMPORT._MODE.MERGE_UNCHANGED',
1547
+ ],
1548
+ },
1549
+ // Ecrasement : une seule phrase, sans puces (cf. maquette).
1550
+ [ImportModeEnum.replaceAll]: { lead: '_COMMON._IMPORT._MODE.REPLACE_ALL_NOTICE', items: [] },
1503
1551
  }; }
1504
1552
  static { this.INTERRUPTION_MESSAGE = {
1505
1553
  [ImportInterruptionStateEnum.unchanged]: '_COMMON._IMPORT._INTERRUPTED.UNCHANGED',
@@ -1695,6 +1743,9 @@ class ImportWizardComponent {
1695
1743
  if (!counts)
1696
1744
  return [];
1697
1745
  const lang = this.lang();
1746
+ // Le nom de l'objet est lui-meme accorde au nombre : sa cle racine porte les memes
1747
+ // categories de pluriel que les libelles de compteur (cf. `ImportScope.objectLabel`).
1748
+ const labelKey = this.objectLabelKey();
1698
1749
  const rows = [
1699
1750
  ['CREATED', counts.created, 'success'],
1700
1751
  ['UPDATED', counts.updated, 'primary'],
@@ -1705,12 +1756,15 @@ class ImportWizardComponent {
1705
1756
  .filter((row) => row[1] !== undefined)
1706
1757
  .map(([name, value, tone]) => ({
1707
1758
  key: pluralizedCountKey(`_COMMON._IMPORT._COUNTS.${prefix}_${name}`, value, lang),
1759
+ params: labelKey
1760
+ ? { label: this.translate.instant(pluralizedCountKey(labelKey, value, lang)) }
1761
+ : undefined,
1708
1762
  count: formatCount(value, lang),
1709
1763
  tone,
1710
1764
  }));
1711
1765
  }
1712
1766
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.20", ngImport: i0, type: ImportWizardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1713
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.20", type: ImportWizardComponent, isStandalone: true, selector: "cw-import-wizard", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { completed: "completed" }, host: { classAttribute: "cw-import-wizard" }, viewQueries: [{ propertyName: "panels", predicate: ["stepPanel"], descendants: true, read: CardComponent, isSignal: true }, { propertyName: "selectionTarget", first: true, predicate: ["selectionTarget"], descendants: true, isSignal: true }, { propertyName: "analysisStatus", first: true, predicate: ["analysisStatus"], descendants: true, isSignal: true }, { propertyName: "analysisOutcome", first: true, predicate: ["analysisOutcome"], descendants: true, isSignal: true }, { propertyName: "executionStatus", first: true, predicate: ["executionStatus"], descendants: true, isSignal: true }, { propertyName: "cancellingStatus", first: true, predicate: ["cancellingStatus"], descendants: true, isSignal: true }, { propertyName: "reportOutcome", first: true, predicate: ["reportOutcome"], descendants: true, isSignal: true }, { propertyName: "interruptedOutcome", first: true, predicate: ["interruptedOutcome"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Pastille d'etape : icone ronde verte (terminee) / rouge (echec), sinon le numero.\n Rendue dans [header-start] pour etre AVANT le titre, comme sur les maquettes.\n Le numero seul n'etant pas parlant a l'oreille, le statut est double en visually-hidden --\n et c'est LUI qui distingue \u00AB en cours \u00BB de \u00AB a venir \u00BB pour qui ne voit pas la couleur. -->\n<ng-template #badge let-stepKey=\"stepKey\" let-number=\"number\">\n @if (stateOf(stepKey) === stepState.done) {\n <cw-icon [icon]=\"spriteIcon.check\" [type]=\"iconType.circular\" [iconSize]=\"iconSize['sm']\"\n [iconUiType]=\"iconUiType.success\" [label]=\"stepStatusLabel(stepKey) | translate\"></cw-icon>\n }\n @else if (stateOf(stepKey) === stepState.error) {\n <cw-icon [icon]=\"spriteIcon.close\" [type]=\"iconType.circular\" [iconSize]=\"iconSize['sm']\"\n [iconUiType]=\"iconUiType.danger\" [label]=\"stepStatusLabel(stepKey) | translate\"></cw-icon>\n }\n @else {\n <div class=\"icon-circular icon-circular-sm\" [class]=\"'icon-circular-' + badgeVariant(stepKey)\">\n <span aria-hidden=\"true\">{{ number }}</span>\n <span class=\"visually-hidden\">{{ stepStatusLabel(stepKey) | translate }}</span>\n </div>\n }\n</ng-template>\n\n<ng-template #counts let-rows=\"rows\">\n <ul class=\"list-unstyled d-flex-h gap-md\">\n @for (row of rows; track row.key) {\n <li>\n <span class=\"fs-lg fw-bold\" [class]=\"'text-' + row.tone\">{{ row.count }}</span> {{ row.key | translate }}\n </li>\n }\n </ul>\n</ng-template>\n\n<cw-accordion [closeOthers]=\"true\" [destroyOnHide]=\"false\">\n\n <!-- Etape 1 : fichier + type d'import -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.SELECT'\"\n [collapseId]=\"'import-step-select'\"\n [collapsed]=\"!isPanelOpen(step.select)\"\n [collapseLocked]=\"stateOf(step.select) === stepState.done\"\n [disabled]=\"stateOf(step.select) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.select, number: 1 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n <div class=\"row\">\n <div class=\"col-lg-6\">\n <!-- Retour depuis une analyse non conforme : cible de focus. Le bouton qui declenche\n ce retour disparait avec le repli du panneau 2, le focus retomberait sur <body>. -->\n @if (selectionReopened()) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #selectionTarget>\n {{ '_COMMON._IMPORT._A11Y.SELECTION' | translate }}\n </h3>\n }\n\n <cw-file-drop\n [formControl]=\"file\"\n [accept]=\"config().accept\"\n [acceptLabel]=\"config().acceptLabel\"\n [maxSizeMb]=\"maxSizeMb()\"\n [label]=\"'_COMMON._IMPORT.FILE_TO_IMPORT' | translate\">\n </cw-file-drop>\n </div>\n\n @if (config().templateFile) {\n <div class=\"col-lg-6\">\n <cw-card [type]=\"cardType.light\">\n <strong>{{ '_COMMON._IMPORT._TEMPLATE.TITLE' | translate }}</strong>\n <span>{{ '_COMMON._IMPORT._TEMPLATE.TEXT' | translate }}</span>\n <cw-file-to-download [attachments]=\"templateAttachments()\"></cw-file-to-download>\n </cw-card>\n </div>\n }\n </div>\n\n <!-- Choix impose : on informe sans champ de saisie. Choix libre : groupe radio,\n dont la selection est REQUISE (pas de preselection, cf. `selectedMode`). -->\n @if (isModeImposed()) {\n <div class=\"d-flex-v gap-2xs\">\n <p class=\"mb-none\">\n <strong>{{ '_COMMON._IMPORT._MODE.LABEL' | translate }}</strong>\n {{ imposedModeLabel() | translate }}\n </p>\n <!-- Champ cle affiche ici AUSSI : c'est le cas le plus courant, et le seul ou\n l'utilisateur ne voyait pas sur quoi portait le rapprochement. -->\n @if (selectedKeyFieldHint(); as keyFieldHint) {\n <span class=\"fs fs-xs text-light\">{{ keyFieldHint | translate }}</span>\n }\n </div>\n }\n @else {\n <cw-form-field\n [type]=\"fieldType.radio\"\n [control]=\"mode\"\n [name]=\"'importMode'\"\n [label]=\"'_COMMON._IMPORT._MODE.LABEL'\"\n [options]=\"modeOptions()\"\n [isRequired]=\"true\"\n [help]=\"selectedKeyFieldHint()\">\n </cw-form-field>\n }\n\n <!-- Portee du mode retenu (destructif ou non) : exigence SFD \u00AB l'utilisateur est\n informe du type d'import selectionne ou impose \u00BB. Rien tant qu'aucun mode n'est\n retenu : l'alerte ne decrit jamais un mode que l'utilisateur n'a pas choisi. -->\n @if (modeNoticeKey(); as noticeKey) {\n <cw-alert [type]=\"modeNoticeType()\">\n {{ noticeKey | translate }}\n </cw-alert>\n }\n\n <!-- Un bouton desactive muet est un cul-de-sac : ce texte dit ce qui manque, et il est\n place AVANT le bouton, seul chemin possible puisqu'un bouton desactive n'est pas\n focusable (donc pas d'aria-describedby exploitable). Pas de classe de couleur\n atenuee : c'est une information necessaire, elle garde le contraste du corps. -->\n <div class=\"d-flex-h justify-end gap-md\">\n @if (missingRequirementKey(); as missing) {\n <p class=\"mb-none fs-xs\">{{ missing | translate }}</p>\n }\n\n <cw-button\n [type]=\"buttonType.primary\"\n [label]=\"'_COMMON._IMPORT.CONTINUE'\"\n [dataTest]=\"'btn-import-continue'\"\n [isDisabled]=\"!canContinue()\"\n (btnAction)=\"continueToAnalysis()\">\n </cw-button>\n </div>\n </div>\n </cw-card>\n\n <!-- Etape 2 : analyse a blanc -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.ANALYSIS'\"\n [collapseId]=\"'import-step-analysis'\"\n [collapsed]=\"!isPanelOpen(step.analysis)\"\n [collapseLocked]=\"stateOf(step.analysis) === stepState.done\"\n [disabled]=\"stateOf(step.analysis) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.analysis, number: 2 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n @if (analyzing()) {\n <!-- Cible de focus : ce texte EST le message d'etat, il est donc lu une fois et une\n seule (aucune region live en doublon). -->\n <span tabindex=\"-1\" #analysisStatus>{{ '_COMMON._IMPORT.ANALYSIS_IN_PROGRESS' | translate }}</span>\n }\n @else if (analysis(); as result) {\n <!-- Cible de focus a l'arrivee du resultat : libelle de section, pas une reprise du\n message, pour que la lecture enchaine sur l'alerte puis les compteurs. -->\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #analysisOutcome>\n {{ '_COMMON._IMPORT._A11Y.ANALYSIS_RESULT' | translate }}\n </h3>\n\n @if (result.valid) {\n <cw-alert [type]=\"alertType.success\">\n {{ '_COMMON._IMPORT.ANALYSIS_OK' | translate }}\n </cw-alert>\n\n <!-- scope=list : previsualisation chiffree. scope=singleConfig : le message ci-dessus suffit. -->\n @if (isList() && previewRows().length) {\n <ng-container *ngTemplateOutlet=\"counts; context: { rows: previewRows() }\"></ng-container>\n }\n\n <div class=\"list-unstyled d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.primary\"\n [label]=\"'_COMMON._IMPORT.START'\"\n [dataTest]=\"'btn-import-start'\"\n (btnAction)=\"startImport()\">\n </cw-button>\n </div>\n }\n @else {\n <cw-alert [type]=\"alertType.danger\">\n {{ '_COMMON._IMPORT.ANALYSIS_KO' | translate }}\n </cw-alert>\n\n @if (errors().length) {\n <ul class=\"mb-none\">\n @for (issue of errors(); track $index) {\n <li>\n @if (issue.line !== undefined) {\n <strong>{{ '_COMMON._IMPORT.ERROR_LINE' | translate: { line: issue.line } }}</strong>\n }\n {{ issue.messageKey ? (issue.messageKey | translate: issue.messageParams) : issue.message }}\n </li>\n }\n </ul>\n }\n\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.secondary\"\n [label]=\"'_COMMON._IMPORT.BACK_TO_SELECTION'\"\n [dataTest]=\"'btn-import-back'\"\n (btnAction)=\"backToSelection()\">\n </cw-button>\n </div>\n }\n }\n </div>\n </cw-card>\n\n <!-- Etape 3 : import -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.EXECUTION'\"\n [collapseId]=\"'import-step-execution'\"\n [collapsed]=\"!isPanelOpen(step.execution)\"\n [disabled]=\"stateOf(step.execution) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.execution, number: 3 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n @if (isImporting) {\n <div class=\"d-flex-v gap-2xs\">\n <!-- Cible de focus. Le bouton \u00AB Arreter l'import \u00BB est le focusable suivant. -->\n <span tabindex=\"-1\" #executionStatus>{{ '_COMMON._IMPORT.IN_PROGRESS' | translate }}</span>\n @if (progress()?.remainingLabel; as remaining) {\n <span>{{ '_COMMON._IMPORT.REMAINING' | translate: { value: remaining } }}</span>\n }\n </div>\n\n <!-- Avancement connu => barre deterministe. Aucune progression recue => spinner\n indetermine, plutot qu'une barre figee a 0 %. La bascule se fait a la premiere\n emission `ImportProgress` : une source qui n'en emet jamais reste sur le spinner.\n `displayText` a false : le libelle \u00AB Import en cours \u00BB est deja au-dessus. -->\n @if (progress(); as current) {\n <cw-progress-bar\n [value]=\"current.percent\"\n [type]=\"progressType.information\"\n [label]=\"'_COMMON._IMPORT.IN_PROGRESS' | translate\">\n </cw-progress-bar>\n }\n @else {\n <cw-loader [size]=\"loaderSize.sm\" [displayText]=\"false\"></cw-loader>\n }\n\n <!-- Pas de bouton d'arret quand l'objet declare l'import non interruptible : un\n bouton qui ne peut rien arreter serait pire que son absence. -->\n @if (canInterrupt()) {\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.dangerOutline\"\n [label]=\"'_COMMON._IMPORT.STOP'\"\n [dataTest]=\"'btn-import-stop'\"\n (btnAction)=\"stopImport()\">\n </cw-button>\n </div>\n }\n }\n @else if (cancelling()) {\n <!-- Annulation demandee au serveur, reponse en attente. Cible de focus. -->\n <span tabindex=\"-1\" #cancellingStatus>{{ '_COMMON._IMPORT.CANCELLING' | translate }}</span>\n\n <cw-loader [size]=\"loaderSize.sm\" [displayText]=\"false\"></cw-loader>\n\n <!-- Sortie de secours : aucun delai n'etant defendable depuis la lib, c'est\n l'utilisateur qui decide d'arreter d'attendre. Conclut en \u00AB etat incertain \u00BB. -->\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.secondary\"\n [label]=\"'_COMMON._IMPORT.STOP_WAITING'\"\n [dataTest]=\"'btn-import-stop-waiting'\"\n (btnAction)=\"stopWaiting()\">\n </cw-button>\n </div>\n }\n @else if (interruptionMessageKey(); as interruptionMessage) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #interruptedOutcome>\n {{ '_COMMON._IMPORT._A11Y.IMPORT_RESULT' | translate }}\n </h3>\n\n <cw-alert [type]=\"interruptionAlertType()\">\n {{ interruptionMessage | translate }}\n </cw-alert>\n }\n @else if (report(); as result) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #reportOutcome>\n {{ '_COMMON._IMPORT._A11Y.IMPORT_RESULT' | translate }}\n </h3>\n\n @if (result.success) {\n <cw-alert [type]=\"alertType.success\">\n {{ '_COMMON._IMPORT.SUCCESS' | translate }}\n </cw-alert>\n\n @if (isList() && reportRows().length) {\n <ng-container *ngTemplateOutlet=\"counts; context: { rows: reportRows() }\"></ng-container>\n }\n }\n @else {\n <!-- Le message generique ne promet l'absence d'ecriture que si l'atomicite est\n declaree (cf. `failureMessageKey`). -->\n <cw-alert [type]=\"alertType.danger\">\n {{ result.errorMessage ?? (failureMessageKey() | translate) }}\n </cw-alert>\n }\n }\n </div>\n </cw-card>\n\n</cw-accordion>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: AccordionComponent, selector: "cw-accordion", inputs: ["closeOthers", "allowMultiple", "destroyOnHide"], outputs: ["panelOpened", "panelClosed"] }, { kind: "component", type: AlertComponent, selector: "cw-alert", inputs: ["type", "title", "isDismissible", "cssClass", "dataTest"], outputs: ["close"] }, { kind: "component", type: ButtonComponent, selector: "cw-button", inputs: ["type", "label", "id", "link", "isDropdown", "hideCaret", "withIcon", "withOnlyIcon", "iconSize", "iconPosition", "dropdownPosition", "isMinWidth", "isDisabled", "isLoading", "loadingAnnounce", "cssClass", "dataTest", "skeletonMode", "withTooltip", "tooltipText", "tooltipTitle", "tooltipTrigger", "tooltipPosition", "tooltipType"], outputs: ["btnAction", "dropDownClicked"] }, { kind: "component", type: CardComponent, selector: "cw-card", inputs: ["title", "subtitle", "type", "titleRank", "cssClass", "collapsible", "collapsed", "disabled", "collapseLocked", "collapseStretched", "collapseId", "destroyOnHide"], outputs: ["collapsibleChange", "destroyOnHideChange", "collapseClicked"] }, { kind: "component", type: FileDropComponent, selector: "cw-file-drop", inputs: ["mode", "multiple", "maxFiles", "accept", "acceptLabel", "maxSizeMb", "recommendedDimensions", "label", "isDisabled", "errorMessage", "dataTest"], outputs: ["filesChange", "rejected", "retry"] }, { kind: "component", type: FileToDownloadComponent, selector: "cw-file-to-download", inputs: ["attachments"] }, { kind: "component", type: FormFieldComponent, selector: "cw-form-field", inputs: ["control", "formGroup", "type", "label", "displayLabel", "name", "placeholder", "autocomplete", "options", "rangeOptions", "validationOptions", "autocompleteOptions", "richTextOptions", "errorMessages", "isRequired", "help", "tooltip", "addonLeft", "addonRight", "isInAddon", "isReadonly", "isDisabled", "sliderValue", "sliderOptions", "cssClass", "skeletonMode", "clearable", "appendTo", "initialCountry", "indeterminate"], outputs: ["refreshEvent", "textChanged", "valueChange", "phoneRegionChange"] }, { kind: "component", type: IconComponent, selector: "cw-icon", inputs: ["icon", "label", "type", "iconSize", "iconCssClass", "iconUiType", "iconColorCustom", "spritePath"] }, { kind: "component", type: LoaderComponent, selector: "cw-loader", inputs: ["size", "displayText"] }, { kind: "component", type: ProgressBarComponent, selector: "cw-progress-bar", inputs: ["value", "max", "size", "type", "showValue", "label", "dataTest"] }, { kind: "pipe", type: i1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1767
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.20", type: ImportWizardComponent, isStandalone: true, selector: "cw-import-wizard", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { completed: "completed" }, host: { classAttribute: "cw-import-wizard" }, viewQueries: [{ propertyName: "panels", predicate: ["stepPanel"], descendants: true, read: CardComponent, isSignal: true }, { propertyName: "selectionTarget", first: true, predicate: ["selectionTarget"], descendants: true, isSignal: true }, { propertyName: "analysisStatus", first: true, predicate: ["analysisStatus"], descendants: true, isSignal: true }, { propertyName: "analysisOutcome", first: true, predicate: ["analysisOutcome"], descendants: true, isSignal: true }, { propertyName: "executionStatus", first: true, predicate: ["executionStatus"], descendants: true, isSignal: true }, { propertyName: "cancellingStatus", first: true, predicate: ["cancellingStatus"], descendants: true, isSignal: true }, { propertyName: "reportOutcome", first: true, predicate: ["reportOutcome"], descendants: true, isSignal: true }, { propertyName: "interruptedOutcome", first: true, predicate: ["interruptedOutcome"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Pastille d'etape : icone ronde verte (terminee) / rouge (echec), sinon le numero.\n Rendue dans [header-start] pour etre AVANT le titre, comme sur les maquettes.\n Le numero seul n'etant pas parlant a l'oreille, le statut est double en visually-hidden --\n et c'est LUI qui distingue \u00AB en cours \u00BB de \u00AB a venir \u00BB pour qui ne voit pas la couleur. -->\n<ng-template #badge let-stepKey=\"stepKey\" let-number=\"number\">\n @if (stateOf(stepKey) === stepState.done) {\n <cw-icon [icon]=\"spriteIcon.check\" [type]=\"iconType.circular\" [iconSize]=\"iconSize['sm']\"\n [iconUiType]=\"iconUiType.success\" [label]=\"stepStatusLabel(stepKey) | translate\"></cw-icon>\n }\n @else if (stateOf(stepKey) === stepState.error) {\n <cw-icon [icon]=\"spriteIcon.close\" [type]=\"iconType.circular\" [iconSize]=\"iconSize['sm']\"\n [iconUiType]=\"iconUiType.danger\" [label]=\"stepStatusLabel(stepKey) | translate\"></cw-icon>\n }\n @else {\n <div class=\"icon-circular icon-circular-sm\" [class]=\"'icon-circular-' + badgeVariant(stepKey)\">\n <span aria-hidden=\"true\">{{ number }}</span>\n <span class=\"visually-hidden\">{{ stepStatusLabel(stepKey) | translate }}</span>\n </div>\n }\n</ng-template>\n\n<ng-template #counts let-rows=\"rows\">\n <ul class=\"list-unstyled d-flex-h gap-md\">\n @for (row of rows; track row.key) {\n <li>\n <span class=\"fs-lg fw-bold\" [class]=\"'text-' + row.tone\">{{ row.count }}</span> {{ row.key | translate: row.params }}\n </li>\n }\n </ul>\n</ng-template>\n\n<cw-accordion [closeOthers]=\"true\" [destroyOnHide]=\"false\">\n\n <!-- Etape 1 : fichier + type d'import -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.SELECT'\"\n [collapseId]=\"'import-step-select'\"\n [collapsed]=\"!isPanelOpen(step.select)\"\n [collapseLocked]=\"stateOf(step.select) === stepState.done\"\n [disabled]=\"stateOf(step.select) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.select, number: 1 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n <div class=\"row\">\n <div class=\"col-lg-6\">\n <!-- Retour depuis une analyse non conforme : cible de focus. Le bouton qui declenche\n ce retour disparait avec le repli du panneau 2, le focus retomberait sur <body>. -->\n @if (selectionReopened()) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #selectionTarget>\n {{ '_COMMON._IMPORT._A11Y.SELECTION' | translate }}\n </h3>\n }\n\n <cw-file-drop\n [formControl]=\"file\"\n [accept]=\"config().accept\"\n [acceptLabel]=\"config().acceptLabel\"\n [maxSizeMb]=\"maxSizeMb()\"\n [label]=\"'_COMMON._IMPORT.FILE_TO_IMPORT' | translate\">\n </cw-file-drop>\n </div>\n\n @if (config().templateFile) {\n <div class=\"col-lg-6\">\n <cw-card [type]=\"cardType.light\">\n <strong>{{ '_COMMON._IMPORT._TEMPLATE.TITLE' | translate }}</strong>\n <span>{{ '_COMMON._IMPORT._TEMPLATE.TEXT' | translate }}</span>\n <cw-file-to-download [attachments]=\"templateAttachments()\"></cw-file-to-download>\n </cw-card>\n </div>\n }\n </div>\n\n <!-- Mode IMPOSE : aucun champ, et pas davantage de ligne \u00AB Type d'import : ... \u00BB \u2014 la\n maquette ne rend que l'alerte de portee ci-dessous, qui informe deja du mode retenu.\n Le champ cle de rapprochement y est replie (voir l'alerte).\n Choix LIBRE : groupe radio, dont la selection est REQUISE (pas de preselection). -->\n @if (!isModeImposed()) {\n <cw-form-field\n [type]=\"fieldType.radio\"\n [control]=\"mode\"\n [name]=\"'importMode'\"\n [label]=\"'_COMMON._IMPORT._MODE.LABEL'\"\n [options]=\"modeOptions()\"\n [isRequired]=\"true\"\n [help]=\"selectedKeyFieldHint()\">\n </cw-form-field>\n }\n\n <!-- Portee du mode retenu (destructif ou non) : exigence SFD \u00AB l'utilisateur est\n informe du type d'import selectionne ou impose \u00BB. Rien tant qu'aucun mode n'est\n retenu : l'alerte ne decrit jamais un mode que l'utilisateur n'a pas choisi. -->\n @if (modeNotice(); as notice) {\n <cw-alert [type]=\"modeNoticeType()\">\n {{ notice.lead | translate }}\n @if (notice.items.length) {\n <ul class=\"mb-none\">\n @for (item of notice.items; track $index) {\n <li>{{ item | translate }}</li>\n }\n </ul>\n }\n <!-- Mode impose : l'alerte est le SEUL endroit qui reste pour dire sur quoi porte\n le rapprochement. En choix libre, c'est l'aide du groupe radio qui le porte. -->\n @if (isModeImposed() && selectedKeyFieldHint(); as keyFieldHint) {\n <p class=\"mb-none\">{{ keyFieldHint | translate }}</p>\n }\n </cw-alert>\n }\n\n <!-- Un bouton desactive muet est un cul-de-sac : ce texte dit ce qui manque, et il est\n place AVANT le bouton, seul chemin possible puisqu'un bouton desactive n'est pas\n focusable (donc pas d'aria-describedby exploitable). Pas de classe de couleur\n atenuee : c'est une information necessaire, elle garde le contraste du corps. -->\n <div class=\"d-flex-h justify-end gap-md\">\n @if (missingRequirementKey(); as missing) {\n <p class=\"mb-none fs-xs\">{{ missing | translate }}</p>\n }\n\n <cw-button\n [type]=\"buttonType.primary\"\n [label]=\"'_COMMON._IMPORT.CONTINUE'\"\n [dataTest]=\"'btn-import-continue'\"\n [isDisabled]=\"!canContinue()\"\n (btnAction)=\"continueToAnalysis()\">\n </cw-button>\n </div>\n </div>\n </cw-card>\n\n <!-- Etape 2 : analyse a blanc -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.ANALYSIS'\"\n [collapseId]=\"'import-step-analysis'\"\n [collapsed]=\"!isPanelOpen(step.analysis)\"\n [collapseLocked]=\"stateOf(step.analysis) === stepState.done\"\n [disabled]=\"stateOf(step.analysis) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.analysis, number: 2 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n @if (analyzing()) {\n <!-- Cible de focus : ce texte EST le message d'etat, il est donc lu une fois et une\n seule (aucune region live en doublon). -->\n <span tabindex=\"-1\" #analysisStatus>{{ '_COMMON._IMPORT.ANALYSIS_IN_PROGRESS' | translate }}</span>\n }\n @else if (analysis(); as result) {\n <!-- Cible de focus a l'arrivee du resultat : libelle de section, pas une reprise du\n message, pour que la lecture enchaine sur l'alerte puis les compteurs. -->\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #analysisOutcome>\n {{ '_COMMON._IMPORT._A11Y.ANALYSIS_RESULT' | translate }}\n </h3>\n\n @if (result.valid) {\n <cw-alert [type]=\"alertType.success\">\n {{ '_COMMON._IMPORT.ANALYSIS_OK' | translate }}\n </cw-alert>\n\n <!-- scope=list : previsualisation chiffree. scope=singleConfig : le message ci-dessus suffit. -->\n @if (isList() && previewRows().length) {\n <ng-container *ngTemplateOutlet=\"counts; context: { rows: previewRows() }\"></ng-container>\n }\n\n <div class=\"list-unstyled d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.primary\"\n [label]=\"'_COMMON._IMPORT.START'\"\n [dataTest]=\"'btn-import-start'\"\n (btnAction)=\"startImport()\">\n </cw-button>\n </div>\n }\n @else {\n <cw-alert [type]=\"alertType.danger\">\n {{ '_COMMON._IMPORT.ANALYSIS_KO' | translate }}\n </cw-alert>\n\n @if (errorCountRow(); as errorCount) {\n <p class=\"mb-none\">\n <span class=\"fs-lg fw-bold\" [class]=\"'text-' + errorCount.tone\">{{ errorCount.count }}</span>\n {{ errorCount.key | translate }}\n </p>\n\n <!-- Une alerte par anomalie, et non une liste a puces : c'est le rendu de la\n maquette, et chaque message reste lisible isolement. -->\n @for (issue of errors(); track $index) {\n <cw-alert [type]=\"alertType.danger\">\n @if (issue.line !== undefined) {\n <strong>{{ '_COMMON._IMPORT.ERROR_LINE' | translate: { line: issue.line } }}</strong>\n }\n {{ issue.messageKey ? (issue.messageKey | translate: issue.messageParams) : issue.message }}\n </cw-alert>\n }\n }\n\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.secondary\"\n [label]=\"'_COMMON._IMPORT.BACK_TO_SELECTION'\"\n [dataTest]=\"'btn-import-back'\"\n (btnAction)=\"backToSelection()\">\n </cw-button>\n </div>\n }\n }\n </div>\n </cw-card>\n\n <!-- Etape 3 : import -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.EXECUTION'\"\n [collapseId]=\"'import-step-execution'\"\n [collapsed]=\"!isPanelOpen(step.execution)\"\n [disabled]=\"stateOf(step.execution) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.execution, number: 3 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n @if (isImporting) {\n <div class=\"d-flex-v gap-2xs\">\n <!-- Cible de focus. Le bouton \u00AB Arreter l'import \u00BB est le focusable suivant. -->\n <span tabindex=\"-1\" #executionStatus>{{ '_COMMON._IMPORT.IN_PROGRESS' | translate }}</span>\n @if (progress()?.remainingLabel; as remaining) {\n <span>{{ '_COMMON._IMPORT.REMAINING' | translate: { value: remaining } }}</span>\n }\n </div>\n\n <!-- Avancement connu => barre deterministe. Aucune progression recue => spinner\n indetermine, plutot qu'une barre figee a 0 %. La bascule se fait a la premiere\n emission `ImportProgress` : une source qui n'en emet jamais reste sur le spinner.\n `displayText` a false : le libelle \u00AB Import en cours \u00BB est deja au-dessus. -->\n @if (progress(); as current) {\n <cw-progress-bar\n [value]=\"current.percent\"\n [type]=\"progressType.information\"\n [label]=\"'_COMMON._IMPORT.IN_PROGRESS' | translate\">\n </cw-progress-bar>\n }\n @else {\n <cw-loader [size]=\"loaderSize.sm\" [displayText]=\"false\"></cw-loader>\n }\n\n <!-- Pas de bouton d'arret quand l'objet declare l'import non interruptible : un\n bouton qui ne peut rien arreter serait pire que son absence. -->\n @if (canInterrupt()) {\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.dangerOutline\"\n [label]=\"'_COMMON._IMPORT.STOP'\"\n [dataTest]=\"'btn-import-stop'\"\n (btnAction)=\"stopImport()\">\n </cw-button>\n </div>\n }\n }\n @else if (cancelling()) {\n <!-- Annulation demandee au serveur, reponse en attente. Cible de focus. -->\n <span tabindex=\"-1\" #cancellingStatus>{{ '_COMMON._IMPORT.CANCELLING' | translate }}</span>\n\n <cw-loader [size]=\"loaderSize.sm\" [displayText]=\"false\"></cw-loader>\n\n <!-- Sortie de secours : aucun delai n'etant defendable depuis la lib, c'est\n l'utilisateur qui decide d'arreter d'attendre. Conclut en \u00AB etat incertain \u00BB. -->\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.secondary\"\n [label]=\"'_COMMON._IMPORT.STOP_WAITING'\"\n [dataTest]=\"'btn-import-stop-waiting'\"\n (btnAction)=\"stopWaiting()\">\n </cw-button>\n </div>\n }\n @else if (interruptionMessageKey(); as interruptionMessage) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #interruptedOutcome>\n {{ '_COMMON._IMPORT._A11Y.IMPORT_RESULT' | translate }}\n </h3>\n\n <cw-alert [type]=\"interruptionAlertType()\">\n {{ interruptionMessage | translate }}\n </cw-alert>\n }\n @else if (report(); as result) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #reportOutcome>\n {{ '_COMMON._IMPORT._A11Y.IMPORT_RESULT' | translate }}\n </h3>\n\n @if (result.success) {\n <cw-alert [type]=\"alertType.success\">\n {{ '_COMMON._IMPORT.SUCCESS' | translate }}\n </cw-alert>\n\n @if (isList() && reportRows().length) {\n <ng-container *ngTemplateOutlet=\"counts; context: { rows: reportRows() }\"></ng-container>\n }\n }\n @else {\n <!-- Le message generique ne promet l'absence d'ecriture que si l'atomicite est\n declaree (cf. `failureMessageKey`). -->\n <cw-alert [type]=\"alertType.danger\">\n {{ result.errorMessage ?? (failureMessageKey() | translate) }}\n </cw-alert>\n }\n }\n </div>\n </cw-card>\n\n</cw-accordion>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: AccordionComponent, selector: "cw-accordion", inputs: ["closeOthers", "allowMultiple", "destroyOnHide"], outputs: ["panelOpened", "panelClosed"] }, { kind: "component", type: AlertComponent, selector: "cw-alert", inputs: ["type", "title", "isDismissible", "cssClass", "dataTest"], outputs: ["close"] }, { kind: "component", type: ButtonComponent, selector: "cw-button", inputs: ["type", "label", "id", "link", "isDropdown", "hideCaret", "withIcon", "withOnlyIcon", "iconSize", "iconPosition", "dropdownPosition", "isMinWidth", "isDisabled", "isLoading", "loadingAnnounce", "cssClass", "dataTest", "skeletonMode", "withTooltip", "tooltipText", "tooltipTitle", "tooltipTrigger", "tooltipPosition", "tooltipType"], outputs: ["btnAction", "dropDownClicked"] }, { kind: "component", type: CardComponent, selector: "cw-card", inputs: ["title", "subtitle", "type", "titleRank", "cssClass", "collapsible", "collapsed", "disabled", "collapseLocked", "collapseStretched", "collapseId", "destroyOnHide"], outputs: ["collapsibleChange", "destroyOnHideChange", "collapseClicked"] }, { kind: "component", type: FileDropComponent, selector: "cw-file-drop", inputs: ["mode", "multiple", "maxFiles", "accept", "acceptLabel", "maxSizeMb", "recommendedDimensions", "label", "isDisabled", "errorMessage", "dataTest"], outputs: ["filesChange", "rejected", "retry"] }, { kind: "component", type: FileToDownloadComponent, selector: "cw-file-to-download", inputs: ["attachments"] }, { kind: "component", type: FormFieldComponent, selector: "cw-form-field", inputs: ["control", "formGroup", "type", "label", "displayLabel", "name", "placeholder", "autocomplete", "options", "rangeOptions", "validationOptions", "autocompleteOptions", "richTextOptions", "errorMessages", "isRequired", "help", "tooltip", "addonLeft", "addonRight", "isInAddon", "isReadonly", "isDisabled", "sliderValue", "sliderOptions", "cssClass", "skeletonMode", "clearable", "appendTo", "initialCountry", "indeterminate"], outputs: ["refreshEvent", "textChanged", "valueChange", "phoneRegionChange"] }, { kind: "component", type: IconComponent, selector: "cw-icon", inputs: ["icon", "label", "type", "iconSize", "iconCssClass", "iconUiType", "iconColorCustom", "spritePath"] }, { kind: "component", type: LoaderComponent, selector: "cw-loader", inputs: ["size", "displayText"] }, { kind: "component", type: ProgressBarComponent, selector: "cw-progress-bar", inputs: ["value", "max", "size", "type", "showValue", "label", "dataTest"] }, { kind: "pipe", type: i1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1714
1768
  }
1715
1769
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.20", ngImport: i0, type: ImportWizardComponent, decorators: [{
1716
1770
  type: Component,
@@ -1728,7 +1782,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.20", ngImpo
1728
1782
  IconComponent,
1729
1783
  LoaderComponent,
1730
1784
  ProgressBarComponent
1731
- ], template: "<!-- Pastille d'etape : icone ronde verte (terminee) / rouge (echec), sinon le numero.\n Rendue dans [header-start] pour etre AVANT le titre, comme sur les maquettes.\n Le numero seul n'etant pas parlant a l'oreille, le statut est double en visually-hidden --\n et c'est LUI qui distingue \u00AB en cours \u00BB de \u00AB a venir \u00BB pour qui ne voit pas la couleur. -->\n<ng-template #badge let-stepKey=\"stepKey\" let-number=\"number\">\n @if (stateOf(stepKey) === stepState.done) {\n <cw-icon [icon]=\"spriteIcon.check\" [type]=\"iconType.circular\" [iconSize]=\"iconSize['sm']\"\n [iconUiType]=\"iconUiType.success\" [label]=\"stepStatusLabel(stepKey) | translate\"></cw-icon>\n }\n @else if (stateOf(stepKey) === stepState.error) {\n <cw-icon [icon]=\"spriteIcon.close\" [type]=\"iconType.circular\" [iconSize]=\"iconSize['sm']\"\n [iconUiType]=\"iconUiType.danger\" [label]=\"stepStatusLabel(stepKey) | translate\"></cw-icon>\n }\n @else {\n <div class=\"icon-circular icon-circular-sm\" [class]=\"'icon-circular-' + badgeVariant(stepKey)\">\n <span aria-hidden=\"true\">{{ number }}</span>\n <span class=\"visually-hidden\">{{ stepStatusLabel(stepKey) | translate }}</span>\n </div>\n }\n</ng-template>\n\n<ng-template #counts let-rows=\"rows\">\n <ul class=\"list-unstyled d-flex-h gap-md\">\n @for (row of rows; track row.key) {\n <li>\n <span class=\"fs-lg fw-bold\" [class]=\"'text-' + row.tone\">{{ row.count }}</span> {{ row.key | translate }}\n </li>\n }\n </ul>\n</ng-template>\n\n<cw-accordion [closeOthers]=\"true\" [destroyOnHide]=\"false\">\n\n <!-- Etape 1 : fichier + type d'import -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.SELECT'\"\n [collapseId]=\"'import-step-select'\"\n [collapsed]=\"!isPanelOpen(step.select)\"\n [collapseLocked]=\"stateOf(step.select) === stepState.done\"\n [disabled]=\"stateOf(step.select) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.select, number: 1 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n <div class=\"row\">\n <div class=\"col-lg-6\">\n <!-- Retour depuis une analyse non conforme : cible de focus. Le bouton qui declenche\n ce retour disparait avec le repli du panneau 2, le focus retomberait sur <body>. -->\n @if (selectionReopened()) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #selectionTarget>\n {{ '_COMMON._IMPORT._A11Y.SELECTION' | translate }}\n </h3>\n }\n\n <cw-file-drop\n [formControl]=\"file\"\n [accept]=\"config().accept\"\n [acceptLabel]=\"config().acceptLabel\"\n [maxSizeMb]=\"maxSizeMb()\"\n [label]=\"'_COMMON._IMPORT.FILE_TO_IMPORT' | translate\">\n </cw-file-drop>\n </div>\n\n @if (config().templateFile) {\n <div class=\"col-lg-6\">\n <cw-card [type]=\"cardType.light\">\n <strong>{{ '_COMMON._IMPORT._TEMPLATE.TITLE' | translate }}</strong>\n <span>{{ '_COMMON._IMPORT._TEMPLATE.TEXT' | translate }}</span>\n <cw-file-to-download [attachments]=\"templateAttachments()\"></cw-file-to-download>\n </cw-card>\n </div>\n }\n </div>\n\n <!-- Choix impose : on informe sans champ de saisie. Choix libre : groupe radio,\n dont la selection est REQUISE (pas de preselection, cf. `selectedMode`). -->\n @if (isModeImposed()) {\n <div class=\"d-flex-v gap-2xs\">\n <p class=\"mb-none\">\n <strong>{{ '_COMMON._IMPORT._MODE.LABEL' | translate }}</strong>\n {{ imposedModeLabel() | translate }}\n </p>\n <!-- Champ cle affiche ici AUSSI : c'est le cas le plus courant, et le seul ou\n l'utilisateur ne voyait pas sur quoi portait le rapprochement. -->\n @if (selectedKeyFieldHint(); as keyFieldHint) {\n <span class=\"fs fs-xs text-light\">{{ keyFieldHint | translate }}</span>\n }\n </div>\n }\n @else {\n <cw-form-field\n [type]=\"fieldType.radio\"\n [control]=\"mode\"\n [name]=\"'importMode'\"\n [label]=\"'_COMMON._IMPORT._MODE.LABEL'\"\n [options]=\"modeOptions()\"\n [isRequired]=\"true\"\n [help]=\"selectedKeyFieldHint()\">\n </cw-form-field>\n }\n\n <!-- Portee du mode retenu (destructif ou non) : exigence SFD \u00AB l'utilisateur est\n informe du type d'import selectionne ou impose \u00BB. Rien tant qu'aucun mode n'est\n retenu : l'alerte ne decrit jamais un mode que l'utilisateur n'a pas choisi. -->\n @if (modeNoticeKey(); as noticeKey) {\n <cw-alert [type]=\"modeNoticeType()\">\n {{ noticeKey | translate }}\n </cw-alert>\n }\n\n <!-- Un bouton desactive muet est un cul-de-sac : ce texte dit ce qui manque, et il est\n place AVANT le bouton, seul chemin possible puisqu'un bouton desactive n'est pas\n focusable (donc pas d'aria-describedby exploitable). Pas de classe de couleur\n atenuee : c'est une information necessaire, elle garde le contraste du corps. -->\n <div class=\"d-flex-h justify-end gap-md\">\n @if (missingRequirementKey(); as missing) {\n <p class=\"mb-none fs-xs\">{{ missing | translate }}</p>\n }\n\n <cw-button\n [type]=\"buttonType.primary\"\n [label]=\"'_COMMON._IMPORT.CONTINUE'\"\n [dataTest]=\"'btn-import-continue'\"\n [isDisabled]=\"!canContinue()\"\n (btnAction)=\"continueToAnalysis()\">\n </cw-button>\n </div>\n </div>\n </cw-card>\n\n <!-- Etape 2 : analyse a blanc -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.ANALYSIS'\"\n [collapseId]=\"'import-step-analysis'\"\n [collapsed]=\"!isPanelOpen(step.analysis)\"\n [collapseLocked]=\"stateOf(step.analysis) === stepState.done\"\n [disabled]=\"stateOf(step.analysis) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.analysis, number: 2 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n @if (analyzing()) {\n <!-- Cible de focus : ce texte EST le message d'etat, il est donc lu une fois et une\n seule (aucune region live en doublon). -->\n <span tabindex=\"-1\" #analysisStatus>{{ '_COMMON._IMPORT.ANALYSIS_IN_PROGRESS' | translate }}</span>\n }\n @else if (analysis(); as result) {\n <!-- Cible de focus a l'arrivee du resultat : libelle de section, pas une reprise du\n message, pour que la lecture enchaine sur l'alerte puis les compteurs. -->\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #analysisOutcome>\n {{ '_COMMON._IMPORT._A11Y.ANALYSIS_RESULT' | translate }}\n </h3>\n\n @if (result.valid) {\n <cw-alert [type]=\"alertType.success\">\n {{ '_COMMON._IMPORT.ANALYSIS_OK' | translate }}\n </cw-alert>\n\n <!-- scope=list : previsualisation chiffree. scope=singleConfig : le message ci-dessus suffit. -->\n @if (isList() && previewRows().length) {\n <ng-container *ngTemplateOutlet=\"counts; context: { rows: previewRows() }\"></ng-container>\n }\n\n <div class=\"list-unstyled d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.primary\"\n [label]=\"'_COMMON._IMPORT.START'\"\n [dataTest]=\"'btn-import-start'\"\n (btnAction)=\"startImport()\">\n </cw-button>\n </div>\n }\n @else {\n <cw-alert [type]=\"alertType.danger\">\n {{ '_COMMON._IMPORT.ANALYSIS_KO' | translate }}\n </cw-alert>\n\n @if (errors().length) {\n <ul class=\"mb-none\">\n @for (issue of errors(); track $index) {\n <li>\n @if (issue.line !== undefined) {\n <strong>{{ '_COMMON._IMPORT.ERROR_LINE' | translate: { line: issue.line } }}</strong>\n }\n {{ issue.messageKey ? (issue.messageKey | translate: issue.messageParams) : issue.message }}\n </li>\n }\n </ul>\n }\n\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.secondary\"\n [label]=\"'_COMMON._IMPORT.BACK_TO_SELECTION'\"\n [dataTest]=\"'btn-import-back'\"\n (btnAction)=\"backToSelection()\">\n </cw-button>\n </div>\n }\n }\n </div>\n </cw-card>\n\n <!-- Etape 3 : import -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.EXECUTION'\"\n [collapseId]=\"'import-step-execution'\"\n [collapsed]=\"!isPanelOpen(step.execution)\"\n [disabled]=\"stateOf(step.execution) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.execution, number: 3 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n @if (isImporting) {\n <div class=\"d-flex-v gap-2xs\">\n <!-- Cible de focus. Le bouton \u00AB Arreter l'import \u00BB est le focusable suivant. -->\n <span tabindex=\"-1\" #executionStatus>{{ '_COMMON._IMPORT.IN_PROGRESS' | translate }}</span>\n @if (progress()?.remainingLabel; as remaining) {\n <span>{{ '_COMMON._IMPORT.REMAINING' | translate: { value: remaining } }}</span>\n }\n </div>\n\n <!-- Avancement connu => barre deterministe. Aucune progression recue => spinner\n indetermine, plutot qu'une barre figee a 0 %. La bascule se fait a la premiere\n emission `ImportProgress` : une source qui n'en emet jamais reste sur le spinner.\n `displayText` a false : le libelle \u00AB Import en cours \u00BB est deja au-dessus. -->\n @if (progress(); as current) {\n <cw-progress-bar\n [value]=\"current.percent\"\n [type]=\"progressType.information\"\n [label]=\"'_COMMON._IMPORT.IN_PROGRESS' | translate\">\n </cw-progress-bar>\n }\n @else {\n <cw-loader [size]=\"loaderSize.sm\" [displayText]=\"false\"></cw-loader>\n }\n\n <!-- Pas de bouton d'arret quand l'objet declare l'import non interruptible : un\n bouton qui ne peut rien arreter serait pire que son absence. -->\n @if (canInterrupt()) {\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.dangerOutline\"\n [label]=\"'_COMMON._IMPORT.STOP'\"\n [dataTest]=\"'btn-import-stop'\"\n (btnAction)=\"stopImport()\">\n </cw-button>\n </div>\n }\n }\n @else if (cancelling()) {\n <!-- Annulation demandee au serveur, reponse en attente. Cible de focus. -->\n <span tabindex=\"-1\" #cancellingStatus>{{ '_COMMON._IMPORT.CANCELLING' | translate }}</span>\n\n <cw-loader [size]=\"loaderSize.sm\" [displayText]=\"false\"></cw-loader>\n\n <!-- Sortie de secours : aucun delai n'etant defendable depuis la lib, c'est\n l'utilisateur qui decide d'arreter d'attendre. Conclut en \u00AB etat incertain \u00BB. -->\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.secondary\"\n [label]=\"'_COMMON._IMPORT.STOP_WAITING'\"\n [dataTest]=\"'btn-import-stop-waiting'\"\n (btnAction)=\"stopWaiting()\">\n </cw-button>\n </div>\n }\n @else if (interruptionMessageKey(); as interruptionMessage) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #interruptedOutcome>\n {{ '_COMMON._IMPORT._A11Y.IMPORT_RESULT' | translate }}\n </h3>\n\n <cw-alert [type]=\"interruptionAlertType()\">\n {{ interruptionMessage | translate }}\n </cw-alert>\n }\n @else if (report(); as result) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #reportOutcome>\n {{ '_COMMON._IMPORT._A11Y.IMPORT_RESULT' | translate }}\n </h3>\n\n @if (result.success) {\n <cw-alert [type]=\"alertType.success\">\n {{ '_COMMON._IMPORT.SUCCESS' | translate }}\n </cw-alert>\n\n @if (isList() && reportRows().length) {\n <ng-container *ngTemplateOutlet=\"counts; context: { rows: reportRows() }\"></ng-container>\n }\n }\n @else {\n <!-- Le message generique ne promet l'absence d'ecriture que si l'atomicite est\n declaree (cf. `failureMessageKey`). -->\n <cw-alert [type]=\"alertType.danger\">\n {{ result.errorMessage ?? (failureMessageKey() | translate) }}\n </cw-alert>\n }\n }\n </div>\n </cw-card>\n\n</cw-accordion>\n" }]
1785
+ ], template: "<!-- Pastille d'etape : icone ronde verte (terminee) / rouge (echec), sinon le numero.\n Rendue dans [header-start] pour etre AVANT le titre, comme sur les maquettes.\n Le numero seul n'etant pas parlant a l'oreille, le statut est double en visually-hidden --\n et c'est LUI qui distingue \u00AB en cours \u00BB de \u00AB a venir \u00BB pour qui ne voit pas la couleur. -->\n<ng-template #badge let-stepKey=\"stepKey\" let-number=\"number\">\n @if (stateOf(stepKey) === stepState.done) {\n <cw-icon [icon]=\"spriteIcon.check\" [type]=\"iconType.circular\" [iconSize]=\"iconSize['sm']\"\n [iconUiType]=\"iconUiType.success\" [label]=\"stepStatusLabel(stepKey) | translate\"></cw-icon>\n }\n @else if (stateOf(stepKey) === stepState.error) {\n <cw-icon [icon]=\"spriteIcon.close\" [type]=\"iconType.circular\" [iconSize]=\"iconSize['sm']\"\n [iconUiType]=\"iconUiType.danger\" [label]=\"stepStatusLabel(stepKey) | translate\"></cw-icon>\n }\n @else {\n <div class=\"icon-circular icon-circular-sm\" [class]=\"'icon-circular-' + badgeVariant(stepKey)\">\n <span aria-hidden=\"true\">{{ number }}</span>\n <span class=\"visually-hidden\">{{ stepStatusLabel(stepKey) | translate }}</span>\n </div>\n }\n</ng-template>\n\n<ng-template #counts let-rows=\"rows\">\n <ul class=\"list-unstyled d-flex-h gap-md\">\n @for (row of rows; track row.key) {\n <li>\n <span class=\"fs-lg fw-bold\" [class]=\"'text-' + row.tone\">{{ row.count }}</span> {{ row.key | translate: row.params }}\n </li>\n }\n </ul>\n</ng-template>\n\n<cw-accordion [closeOthers]=\"true\" [destroyOnHide]=\"false\">\n\n <!-- Etape 1 : fichier + type d'import -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.SELECT'\"\n [collapseId]=\"'import-step-select'\"\n [collapsed]=\"!isPanelOpen(step.select)\"\n [collapseLocked]=\"stateOf(step.select) === stepState.done\"\n [disabled]=\"stateOf(step.select) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.select, number: 1 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n <div class=\"row\">\n <div class=\"col-lg-6\">\n <!-- Retour depuis une analyse non conforme : cible de focus. Le bouton qui declenche\n ce retour disparait avec le repli du panneau 2, le focus retomberait sur <body>. -->\n @if (selectionReopened()) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #selectionTarget>\n {{ '_COMMON._IMPORT._A11Y.SELECTION' | translate }}\n </h3>\n }\n\n <cw-file-drop\n [formControl]=\"file\"\n [accept]=\"config().accept\"\n [acceptLabel]=\"config().acceptLabel\"\n [maxSizeMb]=\"maxSizeMb()\"\n [label]=\"'_COMMON._IMPORT.FILE_TO_IMPORT' | translate\">\n </cw-file-drop>\n </div>\n\n @if (config().templateFile) {\n <div class=\"col-lg-6\">\n <cw-card [type]=\"cardType.light\">\n <strong>{{ '_COMMON._IMPORT._TEMPLATE.TITLE' | translate }}</strong>\n <span>{{ '_COMMON._IMPORT._TEMPLATE.TEXT' | translate }}</span>\n <cw-file-to-download [attachments]=\"templateAttachments()\"></cw-file-to-download>\n </cw-card>\n </div>\n }\n </div>\n\n <!-- Mode IMPOSE : aucun champ, et pas davantage de ligne \u00AB Type d'import : ... \u00BB \u2014 la\n maquette ne rend que l'alerte de portee ci-dessous, qui informe deja du mode retenu.\n Le champ cle de rapprochement y est replie (voir l'alerte).\n Choix LIBRE : groupe radio, dont la selection est REQUISE (pas de preselection). -->\n @if (!isModeImposed()) {\n <cw-form-field\n [type]=\"fieldType.radio\"\n [control]=\"mode\"\n [name]=\"'importMode'\"\n [label]=\"'_COMMON._IMPORT._MODE.LABEL'\"\n [options]=\"modeOptions()\"\n [isRequired]=\"true\"\n [help]=\"selectedKeyFieldHint()\">\n </cw-form-field>\n }\n\n <!-- Portee du mode retenu (destructif ou non) : exigence SFD \u00AB l'utilisateur est\n informe du type d'import selectionne ou impose \u00BB. Rien tant qu'aucun mode n'est\n retenu : l'alerte ne decrit jamais un mode que l'utilisateur n'a pas choisi. -->\n @if (modeNotice(); as notice) {\n <cw-alert [type]=\"modeNoticeType()\">\n {{ notice.lead | translate }}\n @if (notice.items.length) {\n <ul class=\"mb-none\">\n @for (item of notice.items; track $index) {\n <li>{{ item | translate }}</li>\n }\n </ul>\n }\n <!-- Mode impose : l'alerte est le SEUL endroit qui reste pour dire sur quoi porte\n le rapprochement. En choix libre, c'est l'aide du groupe radio qui le porte. -->\n @if (isModeImposed() && selectedKeyFieldHint(); as keyFieldHint) {\n <p class=\"mb-none\">{{ keyFieldHint | translate }}</p>\n }\n </cw-alert>\n }\n\n <!-- Un bouton desactive muet est un cul-de-sac : ce texte dit ce qui manque, et il est\n place AVANT le bouton, seul chemin possible puisqu'un bouton desactive n'est pas\n focusable (donc pas d'aria-describedby exploitable). Pas de classe de couleur\n atenuee : c'est une information necessaire, elle garde le contraste du corps. -->\n <div class=\"d-flex-h justify-end gap-md\">\n @if (missingRequirementKey(); as missing) {\n <p class=\"mb-none fs-xs\">{{ missing | translate }}</p>\n }\n\n <cw-button\n [type]=\"buttonType.primary\"\n [label]=\"'_COMMON._IMPORT.CONTINUE'\"\n [dataTest]=\"'btn-import-continue'\"\n [isDisabled]=\"!canContinue()\"\n (btnAction)=\"continueToAnalysis()\">\n </cw-button>\n </div>\n </div>\n </cw-card>\n\n <!-- Etape 2 : analyse a blanc -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.ANALYSIS'\"\n [collapseId]=\"'import-step-analysis'\"\n [collapsed]=\"!isPanelOpen(step.analysis)\"\n [collapseLocked]=\"stateOf(step.analysis) === stepState.done\"\n [disabled]=\"stateOf(step.analysis) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.analysis, number: 2 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n @if (analyzing()) {\n <!-- Cible de focus : ce texte EST le message d'etat, il est donc lu une fois et une\n seule (aucune region live en doublon). -->\n <span tabindex=\"-1\" #analysisStatus>{{ '_COMMON._IMPORT.ANALYSIS_IN_PROGRESS' | translate }}</span>\n }\n @else if (analysis(); as result) {\n <!-- Cible de focus a l'arrivee du resultat : libelle de section, pas une reprise du\n message, pour que la lecture enchaine sur l'alerte puis les compteurs. -->\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #analysisOutcome>\n {{ '_COMMON._IMPORT._A11Y.ANALYSIS_RESULT' | translate }}\n </h3>\n\n @if (result.valid) {\n <cw-alert [type]=\"alertType.success\">\n {{ '_COMMON._IMPORT.ANALYSIS_OK' | translate }}\n </cw-alert>\n\n <!-- scope=list : previsualisation chiffree. scope=singleConfig : le message ci-dessus suffit. -->\n @if (isList() && previewRows().length) {\n <ng-container *ngTemplateOutlet=\"counts; context: { rows: previewRows() }\"></ng-container>\n }\n\n <div class=\"list-unstyled d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.primary\"\n [label]=\"'_COMMON._IMPORT.START'\"\n [dataTest]=\"'btn-import-start'\"\n (btnAction)=\"startImport()\">\n </cw-button>\n </div>\n }\n @else {\n <cw-alert [type]=\"alertType.danger\">\n {{ '_COMMON._IMPORT.ANALYSIS_KO' | translate }}\n </cw-alert>\n\n @if (errorCountRow(); as errorCount) {\n <p class=\"mb-none\">\n <span class=\"fs-lg fw-bold\" [class]=\"'text-' + errorCount.tone\">{{ errorCount.count }}</span>\n {{ errorCount.key | translate }}\n </p>\n\n <!-- Une alerte par anomalie, et non une liste a puces : c'est le rendu de la\n maquette, et chaque message reste lisible isolement. -->\n @for (issue of errors(); track $index) {\n <cw-alert [type]=\"alertType.danger\">\n @if (issue.line !== undefined) {\n <strong>{{ '_COMMON._IMPORT.ERROR_LINE' | translate: { line: issue.line } }}</strong>\n }\n {{ issue.messageKey ? (issue.messageKey | translate: issue.messageParams) : issue.message }}\n </cw-alert>\n }\n }\n\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.secondary\"\n [label]=\"'_COMMON._IMPORT.BACK_TO_SELECTION'\"\n [dataTest]=\"'btn-import-back'\"\n (btnAction)=\"backToSelection()\">\n </cw-button>\n </div>\n }\n }\n </div>\n </cw-card>\n\n <!-- Etape 3 : import -->\n <cw-card\n #stepPanel\n [title]=\"'_COMMON._IMPORT._STEP.EXECUTION'\"\n [collapseId]=\"'import-step-execution'\"\n [collapsed]=\"!isPanelOpen(step.execution)\"\n [disabled]=\"stateOf(step.execution) === stepState.todo\"\n [collapseStretched]=\"false\">\n\n <ng-container ngProjectAs=\"[header-start]\">\n <ng-container *ngTemplateOutlet=\"badge; context: { stepKey: step.execution, number: 3 }\"></ng-container>\n </ng-container>\n\n <div class=\"d-flex-v gap-md\">\n @if (isImporting) {\n <div class=\"d-flex-v gap-2xs\">\n <!-- Cible de focus. Le bouton \u00AB Arreter l'import \u00BB est le focusable suivant. -->\n <span tabindex=\"-1\" #executionStatus>{{ '_COMMON._IMPORT.IN_PROGRESS' | translate }}</span>\n @if (progress()?.remainingLabel; as remaining) {\n <span>{{ '_COMMON._IMPORT.REMAINING' | translate: { value: remaining } }}</span>\n }\n </div>\n\n <!-- Avancement connu => barre deterministe. Aucune progression recue => spinner\n indetermine, plutot qu'une barre figee a 0 %. La bascule se fait a la premiere\n emission `ImportProgress` : une source qui n'en emet jamais reste sur le spinner.\n `displayText` a false : le libelle \u00AB Import en cours \u00BB est deja au-dessus. -->\n @if (progress(); as current) {\n <cw-progress-bar\n [value]=\"current.percent\"\n [type]=\"progressType.information\"\n [label]=\"'_COMMON._IMPORT.IN_PROGRESS' | translate\">\n </cw-progress-bar>\n }\n @else {\n <cw-loader [size]=\"loaderSize.sm\" [displayText]=\"false\"></cw-loader>\n }\n\n <!-- Pas de bouton d'arret quand l'objet declare l'import non interruptible : un\n bouton qui ne peut rien arreter serait pire que son absence. -->\n @if (canInterrupt()) {\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.dangerOutline\"\n [label]=\"'_COMMON._IMPORT.STOP'\"\n [dataTest]=\"'btn-import-stop'\"\n (btnAction)=\"stopImport()\">\n </cw-button>\n </div>\n }\n }\n @else if (cancelling()) {\n <!-- Annulation demandee au serveur, reponse en attente. Cible de focus. -->\n <span tabindex=\"-1\" #cancellingStatus>{{ '_COMMON._IMPORT.CANCELLING' | translate }}</span>\n\n <cw-loader [size]=\"loaderSize.sm\" [displayText]=\"false\"></cw-loader>\n\n <!-- Sortie de secours : aucun delai n'etant defendable depuis la lib, c'est\n l'utilisateur qui decide d'arreter d'attendre. Conclut en \u00AB etat incertain \u00BB. -->\n <div class=\"d-flex-h justify-end\">\n <cw-button\n [type]=\"buttonType.secondary\"\n [label]=\"'_COMMON._IMPORT.STOP_WAITING'\"\n [dataTest]=\"'btn-import-stop-waiting'\"\n (btnAction)=\"stopWaiting()\">\n </cw-button>\n </div>\n }\n @else if (interruptionMessageKey(); as interruptionMessage) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #interruptedOutcome>\n {{ '_COMMON._IMPORT._A11Y.IMPORT_RESULT' | translate }}\n </h3>\n\n <cw-alert [type]=\"interruptionAlertType()\">\n {{ interruptionMessage | translate }}\n </cw-alert>\n }\n @else if (report(); as result) {\n <h3 class=\"visually-hidden\" tabindex=\"-1\" #reportOutcome>\n {{ '_COMMON._IMPORT._A11Y.IMPORT_RESULT' | translate }}\n </h3>\n\n @if (result.success) {\n <cw-alert [type]=\"alertType.success\">\n {{ '_COMMON._IMPORT.SUCCESS' | translate }}\n </cw-alert>\n\n @if (isList() && reportRows().length) {\n <ng-container *ngTemplateOutlet=\"counts; context: { rows: reportRows() }\"></ng-container>\n }\n }\n @else {\n <!-- Le message generique ne promet l'absence d'ecriture que si l'atomicite est\n declaree (cf. `failureMessageKey`). -->\n <cw-alert [type]=\"alertType.danger\">\n {{ result.errorMessage ?? (failureMessageKey() | translate) }}\n </cw-alert>\n }\n }\n </div>\n </cw-card>\n\n</cw-accordion>\n" }]
1732
1786
  }], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], completed: [{ type: i0.Output, args: ["completed"] }], panels: [{ type: i0.ViewChildren, args: ['stepPanel', { ...{ read: CardComponent }, isSignal: true }] }], selectionTarget: [{ type: i0.ViewChild, args: ['selectionTarget', { isSignal: true }] }], analysisStatus: [{ type: i0.ViewChild, args: ['analysisStatus', { isSignal: true }] }], analysisOutcome: [{ type: i0.ViewChild, args: ['analysisOutcome', { isSignal: true }] }], executionStatus: [{ type: i0.ViewChild, args: ['executionStatus', { isSignal: true }] }], cancellingStatus: [{ type: i0.ViewChild, args: ['cancellingStatus', { isSignal: true }] }], reportOutcome: [{ type: i0.ViewChild, args: ['reportOutcome', { isSignal: true }] }], interruptedOutcome: [{ type: i0.ViewChild, args: ['interruptedOutcome', { isSignal: true }] }] } });
1733
1787
 
1734
1788
  /**