@epistola.app/valtimo-plugin 0.4.5 → 0.5.1

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.
@@ -9,8 +9,8 @@ import * as i2$1 from '@valtimo/plugin';
9
9
  import { PluginTranslatePipeModule } from '@valtimo/plugin';
10
10
  import * as i3 from '@valtimo/components';
11
11
  import { FormModule, InputModule, ValuePathSelectorPrefix, ValuePathSelectorComponent, SelectModule, registerCustomFormioComponent } from '@valtimo/components';
12
- import { BehaviorSubject, combineLatest, take, Subject, merge, of } from 'rxjs';
13
- import { startWith, delay, takeUntil, filter, map, tap, switchMap, catchError, take as take$1, debounceTime } from 'rxjs/operators';
12
+ import { BehaviorSubject, combineLatest, take, Subject, of, merge } from 'rxjs';
13
+ import { startWith, delay, shareReplay, take as take$1, takeUntil, filter, map, distinctUntilChanged, tap, switchMap, catchError, debounceTime } from 'rxjs/operators';
14
14
  import * as i4 from '@angular/forms';
15
15
  import { FormsModule } from '@angular/forms';
16
16
  import * as i2$2 from '@valtimo/process-link';
@@ -46,22 +46,34 @@ class EpistolaPluginService {
46
46
  this.apiEndpoint = `${this.configService.config.valtimoApi.endpointUri}v1/plugin/epistola`;
47
47
  }
48
48
  /**
49
- * Get all available templates for a plugin configuration.
49
+ * Get all available catalogs for a plugin configuration.
50
50
  */
51
- getTemplates(pluginConfigurationId) {
52
- return this.http.get(`${this.apiEndpoint}/configurations/${pluginConfigurationId}/templates`);
51
+ getCatalogs(pluginConfigurationId) {
52
+ return this.http.get(`${this.apiEndpoint}/configurations/${pluginConfigurationId}/catalogs`);
53
+ }
54
+ /**
55
+ * Get all available templates for a plugin configuration and catalog.
56
+ */
57
+ getTemplates(pluginConfigurationId, catalogId) {
58
+ const params = {};
59
+ if (catalogId)
60
+ params['catalogId'] = catalogId;
61
+ return this.http.get(`${this.apiEndpoint}/configurations/${pluginConfigurationId}/templates`, { params });
53
62
  }
54
63
  /**
55
64
  * Get template details including its fields.
56
65
  */
57
- getTemplateDetails(pluginConfigurationId, templateId) {
58
- return this.http.get(`${this.apiEndpoint}/configurations/${pluginConfigurationId}/templates/${templateId}`);
66
+ getTemplateDetails(pluginConfigurationId, templateId, catalogId) {
67
+ return this.http.get(`${this.apiEndpoint}/configurations/${pluginConfigurationId}/templates/${templateId}`, { params: { catalogId } });
59
68
  }
60
69
  /**
61
- * Get all attribute definitions for a plugin configuration's tenant.
70
+ * Get all attribute definitions for a plugin configuration's tenant and catalog.
62
71
  */
63
- getAttributes(pluginConfigurationId) {
64
- return this.http.get(`${this.apiEndpoint}/configurations/${pluginConfigurationId}/attributes`);
72
+ getAttributes(pluginConfigurationId, catalogId) {
73
+ const params = {};
74
+ if (catalogId)
75
+ params['catalogId'] = catalogId;
76
+ return this.http.get(`${this.apiEndpoint}/configurations/${pluginConfigurationId}/attributes`, { params });
65
77
  }
66
78
  /**
67
79
  * Get all available environments for a plugin configuration.
@@ -72,8 +84,11 @@ class EpistolaPluginService {
72
84
  /**
73
85
  * Get all variants for a specific template.
74
86
  */
75
- getVariants(pluginConfigurationId, templateId) {
76
- return this.http.get(`${this.apiEndpoint}/configurations/${pluginConfigurationId}/templates/${templateId}/variants`);
87
+ getVariants(pluginConfigurationId, templateId, catalogId) {
88
+ const params = {};
89
+ if (catalogId)
90
+ params['catalogId'] = catalogId;
91
+ return this.http.get(`${this.apiEndpoint}/configurations/${pluginConfigurationId}/templates/${templateId}/variants`, { params });
77
92
  }
78
93
  /**
79
94
  * Discover process variable names for a given process definition.
@@ -666,6 +681,7 @@ class GenerateDocumentConfigurationComponent {
666
681
  context$;
667
682
  valid = new EventEmitter();
668
683
  configuration = new EventEmitter();
684
+ catalogs$ = new BehaviorSubject(initialResource([]));
669
685
  templates$ = new BehaviorSubject(initialResource([]));
670
686
  variants$ = new BehaviorSubject(initialResource([]));
671
687
  environments$ = new BehaviorSubject(initialResource([]));
@@ -675,6 +691,8 @@ class GenerateDocumentConfigurationComponent {
675
691
  { id: 'PDF', text: 'PDF' },
676
692
  { id: 'HTML', text: 'HTML' }
677
693
  ];
694
+ selectedCatalogId$ = new BehaviorSubject('');
695
+ /** Composite ID: "catalogId/templateId" */
678
696
  selectedTemplateId$ = new BehaviorSubject('');
679
697
  selectedVariantId$ = new BehaviorSubject('');
680
698
  variantSelectionMode = 'explicit';
@@ -689,20 +707,18 @@ class GenerateDocumentConfigurationComponent {
689
707
  formValue$ = new BehaviorSubject(null);
690
708
  valid$ = new BehaviorSubject(false);
691
709
  pluginConfigurationId$ = new BehaviorSubject('');
710
+ /** Resolves once with the prefill config (or empty config if none). */
711
+ prefill$;
692
712
  constructor(epistolaPluginService, processLinkStateService, cdr) {
693
713
  this.epistolaPluginService = epistolaPluginService;
694
714
  this.processLinkStateService = processLinkStateService;
695
715
  this.cdr = cdr;
696
716
  }
697
717
  ngOnInit() {
718
+ this.prefill$ = this.resolvePrefill$();
698
719
  this.initContext();
699
- this.initPrefill();
700
720
  this.initPluginConfiguration();
701
- this.initTemplatesLoading();
702
- this.initEnvironmentsLoading();
703
- this.initAttributesLoading();
704
- this.initVariantsLoading();
705
- this.initTemplateFieldsLoading();
721
+ this.initCascade();
706
722
  this.openSaveSubscription();
707
723
  }
708
724
  ngOnDestroy() {
@@ -713,6 +729,13 @@ class GenerateDocumentConfigurationComponent {
713
729
  formValueChange(formOutput) {
714
730
  const formValue = formOutput;
715
731
  this.formValue$.next(formValue);
732
+ // When catalog changes, reset template and variant selection
733
+ if (formValue.catalogId && formValue.catalogId !== this.selectedCatalogId$.getValue()) {
734
+ this.selectedCatalogId$.next(formValue.catalogId);
735
+ this.selectedTemplateId$.next('');
736
+ this.selectedVariantId$.next('');
737
+ }
738
+ // templateId from v-select is the template ID within the selected catalog
716
739
  if (formValue.templateId && formValue.templateId !== this.selectedTemplateId$.getValue()) {
717
740
  this.selectedTemplateId$.next(formValue.templateId);
718
741
  this.selectedVariantId$.next('');
@@ -781,6 +804,17 @@ class GenerateDocumentConfigurationComponent {
781
804
  return '';
782
805
  return ` (${entries.map(([k, v]) => `${k}=${v}`).join(', ')})`;
783
806
  }
807
+ /**
808
+ * Creates a shared observable that resolves once with the prefill config
809
+ * (or null if no prefill is provided). This is used to seed the cascade
810
+ * with initial selection values before any loading starts.
811
+ */
812
+ resolvePrefill$() {
813
+ if (!this.prefillConfiguration$) {
814
+ return of(null).pipe(shareReplay(1));
815
+ }
816
+ return this.prefillConfiguration$.pipe(take$1(1), shareReplay(1));
817
+ }
784
818
  initContext() {
785
819
  if (this.context$) {
786
820
  this.context$.pipe(takeUntil(this.destroy$), filter(([context]) => context === 'case')).subscribe(([, params]) => {
@@ -789,35 +823,6 @@ class GenerateDocumentConfigurationComponent {
789
823
  });
790
824
  }
791
825
  }
792
- initPrefill() {
793
- if (this.prefillConfiguration$) {
794
- this.prefillConfiguration$.pipe(takeUntil(this.destroy$), filter(config => !!config?.templateId)).subscribe(config => {
795
- this.selectedTemplateId$.next(config.templateId);
796
- if (config.variantAttributes && (Array.isArray(config.variantAttributes) ? config.variantAttributes.length > 0 : Object.keys(config.variantAttributes).length > 0)) {
797
- this.variantSelectionMode = 'attributes';
798
- if (Array.isArray(config.variantAttributes)) {
799
- // New format: VariantAttributeEntry[]
800
- this.variantAttributeEntries = config.variantAttributes
801
- .map(e => ({ key: e.key, value: e.value, required: e.required !== false }));
802
- }
803
- else {
804
- // Old format: Record<string, string> — treat all as required
805
- this.variantAttributeEntries = Object.entries(config.variantAttributes)
806
- .map(([key, value]) => ({ key, value: String(value), required: true }));
807
- }
808
- }
809
- else if (config.variantId) {
810
- this.variantSelectionMode = 'explicit';
811
- this.selectedVariantId$.next(config.variantId);
812
- }
813
- if (config.dataMapping) {
814
- this.prefillDataMapping = config.dataMapping;
815
- this.dataMapping$.next(config.dataMapping);
816
- }
817
- this.cdr.markForCheck();
818
- });
819
- }
820
- }
821
826
  initPluginConfiguration() {
822
827
  const sources = [];
823
828
  if (this.selectedPluginConfigurationData$) {
@@ -828,51 +833,88 @@ class GenerateDocumentConfigurationComponent {
828
833
  this.pluginConfigurationId$.next(configurationId);
829
834
  });
830
835
  }
831
- initTemplatesLoading() {
832
- this.pluginConfigurationId$.pipe(takeUntil(this.destroy$), filter(id => !!id), tap(() => this.templates$.next(loadingResource(this.templates$.getValue().data))), switchMap(configurationId => this.epistolaPluginService.getTemplates(configurationId).pipe(catchError(() => {
833
- this.templates$.next(errorResource([], 'Failed to load templates'));
834
- return of(null);
835
- }))), filter(result => result !== null)).subscribe(templates => {
836
- this.templates$.next(successResource(templates.map(t => ({ id: t.id, text: t.name }))));
837
- });
838
- }
839
- initEnvironmentsLoading() {
840
- this.pluginConfigurationId$.pipe(takeUntil(this.destroy$), filter(id => !!id), tap(() => this.environments$.next(loadingResource(this.environments$.getValue().data))), switchMap(configurationId => this.epistolaPluginService.getEnvironments(configurationId).pipe(catchError(() => {
841
- this.environments$.next(errorResource([], 'Failed to load environments'));
842
- return of(null);
843
- }))), filter(result => result !== null)).subscribe(environments => {
844
- this.environments$.next(successResource(environments.map(e => ({ id: e.id, text: e.name }))));
836
+ /**
837
+ * Sets up the entire reactive cascade:
838
+ *
839
+ * pluginConfigurationId$ → catalogs (+ environments independently)
840
+ * prefill + catalogs loaded seed selectedCatalogId$
841
+ * selectedCatalogId$ → templates (+ attributes)
842
+ * prefill + templates loaded → seed selectedTemplateId$
843
+ * selectedTemplateId$ → variants + templateFields
844
+ * prefill + templateFields loaded → seed dataMapping
845
+ */
846
+ initCascade() {
847
+ const configId$ = this.pluginConfigurationId$.pipe(filter(id => !!id), distinctUntilChanged());
848
+ // ── Catalogs: load when pluginConfigurationId changes ──
849
+ configId$.pipe(takeUntil(this.destroy$), tap(() => this.catalogs$.next(loadingResource(this.catalogs$.getValue().data))), switchMap(configurationId => this.epistolaPluginService.getCatalogs(configurationId).pipe(map(catalogs => successResource(catalogs.map(c => ({ id: c.id, text: c.name })))), catchError(() => of(errorResource([], 'Failed to load catalogs')))))).subscribe(resource => this.catalogs$.next(resource));
850
+ // ── Environments: load when pluginConfigurationId changes (independent) ──
851
+ configId$.pipe(takeUntil(this.destroy$), tap(() => this.environments$.next(loadingResource(this.environments$.getValue().data))), switchMap(configurationId => this.epistolaPluginService.getEnvironments(configurationId).pipe(map(envs => successResource(envs.map(e => ({ id: e.id, text: e.name })))), catchError(() => of(errorResource([], 'Failed to load environments')))))).subscribe(resource => this.environments$.next(resource));
852
+ // ── Seed selectedCatalogId$ from prefill once catalogs are loaded ──
853
+ combineLatest([
854
+ this.prefill$.pipe(filter(config => !!config?.catalogId)),
855
+ this.catalogs$.pipe(filter(c => !c.loading && c.data.length > 0))
856
+ ]).pipe(takeUntil(this.destroy$), take$1(1)).subscribe(([config]) => {
857
+ this.selectedCatalogId$.next(config.catalogId);
845
858
  });
846
- }
847
- initAttributesLoading() {
848
- this.pluginConfigurationId$.pipe(takeUntil(this.destroy$), filter(id => !!id), switchMap(configurationId => this.epistolaPluginService.getAttributes(configurationId).pipe(catchError(() => of([]))))).subscribe(attributes => {
859
+ // ── Templates: load when catalogId changes ──
860
+ const catalogId$ = this.selectedCatalogId$.pipe(filter(id => !!id), distinctUntilChanged());
861
+ combineLatest([configId$, catalogId$]).pipe(takeUntil(this.destroy$), tap(() => this.templates$.next(loadingResource(this.templates$.getValue().data))), switchMap(([configurationId, catalogId]) => this.epistolaPluginService.getTemplates(configurationId, catalogId).pipe(map(templates => successResource(templates.map(t => ({ id: t.id, text: t.name })))), catchError(() => of(errorResource([], 'Failed to load templates')))))).subscribe(resource => this.templates$.next(resource));
862
+ // ── Attributes: load when catalogId changes ──
863
+ combineLatest([configId$, catalogId$]).pipe(takeUntil(this.destroy$), switchMap(([configurationId, catalogId]) => this.epistolaPluginService.getAttributes(configurationId, catalogId).pipe(catchError(() => of([]))))).subscribe(attributes => {
849
864
  this.availableAttributeKeys = attributes.map(a => a.key).sort();
850
865
  this.cdr.markForCheck();
851
866
  });
852
- }
853
- initVariantsLoading() {
867
+ // ── Seed selectedTemplateId$ from prefill once templates are loaded ──
854
868
  combineLatest([
855
- this.pluginConfigurationId$,
856
- this.selectedTemplateId$
857
- ]).pipe(takeUntil(this.destroy$), filter(([configId, templateId]) => !!configId && !!templateId), tap(() => this.variants$.next(loadingResource(this.variants$.getValue().data))), switchMap(([configurationId, templateId]) => this.epistolaPluginService.getVariants(configurationId, templateId).pipe(catchError(() => {
858
- this.variants$.next(errorResource([], 'Failed to load variants'));
859
- return of(null);
860
- }))), filter(result => result !== null)).subscribe(variants => {
861
- this.variants$.next(successResource(variants.map(v => ({ id: v.id, text: v.name + this.formatAttributes(v.attributes) }))));
869
+ this.prefill$.pipe(filter(config => !!config?.templateId)),
870
+ this.templates$.pipe(filter(t => !t.loading && t.data.length > 0))
871
+ ]).pipe(takeUntil(this.destroy$), take$1(1)).subscribe(([config]) => {
872
+ this.selectedTemplateId$.next(config.templateId);
862
873
  });
863
- }
864
- initTemplateFieldsLoading() {
865
- combineLatest([
866
- this.pluginConfigurationId$,
867
- this.selectedTemplateId$
868
- ]).pipe(takeUntil(this.destroy$), filter(([configId, templateId]) => !!configId && !!templateId), tap(() => {
874
+ // ── Variants: load when templateId changes ──
875
+ const templateId$ = this.selectedTemplateId$.pipe(filter(id => !!id), distinctUntilChanged());
876
+ combineLatest([configId$, catalogId$, templateId$]).pipe(takeUntil(this.destroy$), tap(() => this.variants$.next(loadingResource(this.variants$.getValue().data))), switchMap(([configurationId, catalogId, templateId]) => this.epistolaPluginService.getVariants(configurationId, templateId, catalogId).pipe(map(variants => successResource(variants.map(v => ({ id: v.id, text: v.name + this.formatAttributes(v.attributes) })))), catchError(() => of(errorResource([], 'Failed to load variants')))))).subscribe(resource => this.variants$.next(resource));
877
+ // ── Template fields: load when templateId changes ──
878
+ combineLatest([configId$, catalogId$, templateId$]).pipe(takeUntil(this.destroy$), tap(() => {
869
879
  this.templateFields$.next(loadingResource(this.templateFields$.getValue().data));
870
880
  this.loadProcessVariables();
871
- }), switchMap(([configurationId, templateId]) => this.epistolaPluginService.getTemplateDetails(configurationId, templateId).pipe(catchError(() => {
872
- this.templateFields$.next(errorResource([], 'Failed to load template fields'));
873
- return of(null);
874
- }))), filter(result => result !== null)).subscribe(details => {
875
- this.templateFields$.next(successResource(details.fields || []));
881
+ }), switchMap(([configurationId, catalogId, templateId]) => this.epistolaPluginService.getTemplateDetails(configurationId, templateId, catalogId).pipe(map(details => successResource(details.fields || [])), catchError(() => of(errorResource([], 'Failed to load template fields')))))).subscribe(resource => this.templateFields$.next(resource));
882
+ // ── Seed variant + dataMapping from prefill once templateFields are loaded ──
883
+ combineLatest([
884
+ this.prefill$.pipe(filter(config => !!config?.templateId)),
885
+ this.templateFields$.pipe(filter(tf => !tf.loading && tf.data.length > 0))
886
+ ]).pipe(takeUntil(this.destroy$), take$1(1)).subscribe(([config]) => {
887
+ if (!config)
888
+ return;
889
+ // Apply variant prefill
890
+ if (config.variantAttributes && (Array.isArray(config.variantAttributes) ? config.variantAttributes.length > 0 : Object.keys(config.variantAttributes).length > 0)) {
891
+ this.variantSelectionMode = 'attributes';
892
+ if (Array.isArray(config.variantAttributes)) {
893
+ this.variantAttributeEntries = config.variantAttributes
894
+ .map(e => ({ key: e.key, value: e.value, required: e.required !== false }));
895
+ }
896
+ else {
897
+ this.variantAttributeEntries = Object.entries(config.variantAttributes)
898
+ .map(([key, value]) => ({ key, value: String(value), required: true }));
899
+ }
900
+ }
901
+ else if (config.variantId) {
902
+ this.variantSelectionMode = 'explicit';
903
+ this.selectedVariantId$.next(config.variantId);
904
+ }
905
+ // Apply dataMapping prefill — templateFields are guaranteed loaded at this point.
906
+ // Use setTimeout to ensure the tree component exists in the DOM (after *ngIf resolves)
907
+ // before setting the prefill, so ngOnChanges fires correctly on the child.
908
+ if (config.dataMapping) {
909
+ this.dataMapping$.next(config.dataMapping);
910
+ setTimeout(() => {
911
+ this.prefillDataMapping = { ...config.dataMapping };
912
+ this.cdr.detectChanges();
913
+ });
914
+ }
915
+ else {
916
+ this.cdr.detectChanges();
917
+ }
876
918
  });
877
919
  }
878
920
  loadProcessVariables() {
@@ -884,7 +926,8 @@ class GenerateDocumentConfigurationComponent {
884
926
  }
885
927
  }
886
928
  handleValid(formValue) {
887
- const baseComplete = !!(formValue?.templateId &&
929
+ const baseComplete = !!(this.selectedCatalogId$.getValue() &&
930
+ formValue?.templateId &&
888
931
  formValue?.outputFormat &&
889
932
  formValue?.filename &&
890
933
  formValue?.resultProcessVariable);
@@ -904,8 +947,11 @@ class GenerateDocumentConfigurationComponent {
904
947
  .pipe(take$1(1))
905
948
  .subscribe(([formValue, valid, dataMapping]) => {
906
949
  if (valid && formValue) {
950
+ const catalogId = this.selectedCatalogId$.getValue();
951
+ const templateId = formValue.templateId;
907
952
  const config = {
908
- templateId: formValue.templateId,
953
+ catalogId,
954
+ templateId,
909
955
  environmentId: formValue.environmentId || undefined,
910
956
  dataMapping: dataMapping,
911
957
  outputFormat: formValue.outputFormat,
@@ -927,7 +973,7 @@ class GenerateDocumentConfigurationComponent {
927
973
  });
928
974
  }
929
975
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GenerateDocumentConfigurationComponent, deps: [{ token: EpistolaPluginService }, { token: i2$2.ProcessLinkStateService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
930
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: GenerateDocumentConfigurationComponent, isStandalone: true, selector: "epistola-generate-document-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$", selectedPluginConfigurationData$: "selectedPluginConfigurationData$", context$: "context$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: disabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null,\n templates: templates$ | async,\n variants: variants$ | async,\n environments: environments$ | async,\n templateFields: templateFields$ | async,\n selectedTemplateId: selectedTemplateId$ | async\n } as obs\"\n>\n <v-select\n name=\"templateId\"\n [title]=\"'templateId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'templateIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.templates.data\"\n [defaultSelectionId]=\"obs.prefill?.templateId\"\n [disabled]=\"obs.disabled || obs.templates.loading\"\n [required]=\"true\"\n [loading]=\"obs.templates.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.templates.error\" class=\"loading-error\">{{ obs.templates.error }}</div>\n\n <!-- Variant selection mode toggle -->\n <div class=\"variant-mode-toggle\" *ngIf=\"obs.selectedTemplateId\">\n <label class=\"variant-mode-label\">{{ 'variantSelectionMode' | pluginTranslate: pluginId | async }}</label>\n <div class=\"variant-mode-buttons\">\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'explicit'\"\n (click)=\"onVariantSelectionModeChange('explicit')\"\n [disabled]=\"obs.disabled\"\n >{{ 'selectByVariant' | pluginTranslate: pluginId | async }}</button>\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'attributes'\"\n (click)=\"onVariantSelectionModeChange('attributes')\"\n [disabled]=\"obs.disabled\"\n >{{ 'selectByAttributes' | pluginTranslate: pluginId | async }}</button>\n </div>\n </div>\n\n <!-- Explicit variant selection (dropdown) -->\n <v-select\n *ngIf=\"variantSelectionMode === 'explicit'\"\n name=\"variantId\"\n [title]=\"'variantId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'variantIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.variants.data\"\n [defaultSelectionId]=\"obs.prefill?.variantId\"\n [disabled]=\"obs.disabled || obs.variants.loading || !obs.selectedTemplateId\"\n [required]=\"false\"\n [loading]=\"obs.variants.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.variants.error\" class=\"loading-error\">{{ obs.variants.error }}</div>\n\n <!-- Attribute-based variant selection -->\n <div *ngIf=\"variantSelectionMode === 'attributes' && obs.selectedTemplateId\" class=\"variant-attributes-section\">\n <label class=\"variant-attributes-label\">{{ 'variantAttributes' | pluginTranslate: pluginId | async }}</label>\n <div class=\"variant-attributes-list\">\n <div *ngFor=\"let entry of variantAttributeEntries; let i = index\" class=\"variant-attribute-row\">\n <select\n *ngIf=\"!entry._customKey\"\n class=\"variant-attribute-input\"\n [ngModel]=\"entry.key\"\n (ngModelChange)=\"onKeySelected(entry, $event)\"\n [disabled]=\"obs.disabled\"\n >\n <option value=\"\" disabled>{{ 'attributeKey' | pluginTranslate: pluginId | async }}</option>\n <option *ngFor=\"let key of availableAttributeKeys\" [value]=\"key\">{{ key }}</option>\n <option value=\"__custom__\">{{ 'attributeKeyCustom' | pluginTranslate: pluginId | async }}</option>\n </select>\n <div *ngIf=\"entry._customKey\" class=\"custom-key-input\">\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeKey' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.key\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <button type=\"button\" class=\"custom-key-cancel\" (click)=\"cancelCustomKey(entry)\" [disabled]=\"obs.disabled\">&times;</button>\n </div>\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeValue' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.value\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <label class=\"variant-attribute-required-toggle\">\n <input\n type=\"checkbox\"\n [(ngModel)]=\"entry.required\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <span class=\"required-label\">{{ (entry.required ? 'attributeRequired' : 'attributePreferred') | pluginTranslate: pluginId | async }}</span>\n </label>\n <button\n type=\"button\"\n class=\"variant-attribute-remove-btn\"\n (click)=\"removeAttributeEntry(i)\"\n [disabled]=\"obs.disabled\"\n title=\"{{ 'removeAttribute' | pluginTranslate: pluginId | async }}\"\n >&times;</button>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"variant-attribute-add-btn\"\n (click)=\"addAttributeEntry()\"\n [disabled]=\"obs.disabled\"\n >+ {{ 'addAttribute' | pluginTranslate: pluginId | async }}</button>\n </div>\n\n <v-select\n name=\"environmentId\"\n [title]=\"'environmentId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'environmentIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.environments.data\"\n [defaultSelectionId]=\"obs.prefill?.environmentId\"\n [disabled]=\"obs.disabled || obs.environments.loading\"\n [required]=\"false\"\n [loading]=\"obs.environments.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.environments.error\" class=\"loading-error\">{{ obs.environments.error }}</div>\n\n <v-select\n name=\"outputFormat\"\n [title]=\"'outputFormat' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'outputFormatTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"outputFormatOptions\"\n [defaultSelectionId]=\"obs.prefill?.outputFormat || 'PDF'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-select>\n\n <v-input\n name=\"filename\"\n [title]=\"'filename' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'filenameTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.filename\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"correlationId\"\n [title]=\"'correlationId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'correlationIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.correlationId\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"resultProcessVariable\"\n [title]=\"'resultProcessVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'resultProcessVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.resultProcessVariable\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n\n<div *ngIf=\"(templateFields$ | async)?.error as templateFieldsError\" class=\"loading-error\">{{ templateFieldsError }}</div>\n\n<epistola-data-mapping-tree\n *ngIf=\"(selectedTemplateId$ | async)\"\n [pluginId]=\"pluginId\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n [disabled]=\"!!(disabled$ | async)\"\n [prefillMapping]=\"prefillDataMapping\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n (mappingChange)=\"onDataMappingChange($event)\"\n (requiredFieldsStatus)=\"onRequiredFieldsStatusChange($event)\"\n></epistola-data-mapping-tree>\n\n<div class=\"validation-summary\" *ngIf=\"(selectedTemplateId$ | async) && requiredFieldsStatus.total > 0\">\n <span *ngIf=\"requiredFieldsStatus.mapped === requiredFieldsStatus.total\" class=\"validation-complete\">\n {{ 'requiredFieldsComplete' | pluginTranslate: pluginId | async }}\n </span>\n <span *ngIf=\"requiredFieldsStatus.mapped < requiredFieldsStatus.total\" class=\"validation-incomplete\">\n {{ requiredFieldsStatus.mapped }} / {{ requiredFieldsStatus.total }}\n {{ 'validationSummary' | pluginTranslate: pluginId | async }}\n </span>\n</div>\n", styles: [".loading-error{padding:.25rem .75rem;font-size:.8125rem;color:#dc3545}.validation-summary{margin-top:.5rem;padding:.5rem .75rem;border-radius:4px;font-size:.875rem}.validation-summary .validation-complete{color:#198754}.validation-summary .validation-incomplete{color:#dc3545;font-weight:500}.variant-mode-toggle{margin-bottom:1rem;padding:0 .75rem}.variant-mode-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-mode-buttons{display:flex;gap:0;border:1px solid #d1d5db;border-radius:4px;overflow:hidden;width:fit-content}.variant-mode-btn{padding:.375rem .75rem;font-size:.8125rem;background:#fff;border:none;border-right:1px solid #d1d5db;cursor:pointer;color:#374151;transition:background-color .15s,color .15s}.variant-mode-btn:last-child{border-right:none}.variant-mode-btn:hover:not([disabled]){background:#f3f4f6}.variant-mode-btn.active{background:#2563eb;color:#fff}.variant-mode-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attributes-section{margin-bottom:1rem;padding:0 .75rem}.variant-attributes-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-attributes-list{display:flex;flex-direction:column;gap:.375rem}.variant-attribute-row{display:flex;gap:.375rem;align-items:center}.variant-attribute-input{flex:1;padding:.375rem .5rem;font-size:.8125rem;border:1px solid #d1d5db;border-radius:4px;outline:none}.variant-attribute-input:focus{border-color:#2563eb;box-shadow:0 0 0 1px #2563eb}.variant-attribute-input[disabled]{opacity:.5;background:#f9fafb}.custom-key-input{display:flex;flex:1;gap:.25rem}.custom-key-input .variant-attribute-input{flex:1}.custom-key-cancel{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.custom-key-cancel:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.custom-key-cancel[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-required-toggle{display:flex;align-items:center;gap:.25rem;font-size:.75rem;color:#374151;white-space:nowrap;cursor:pointer}.variant-attribute-required-toggle input[type=checkbox]{margin:0;cursor:pointer}.variant-attribute-required-toggle .required-label{-webkit-user-select:none;user-select:none}.variant-attribute-remove-btn{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-remove-btn:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.variant-attribute-remove-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-add-btn{margin-top:.375rem;padding:.25rem .5rem;font-size:.8125rem;background:none;border:1px dashed #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-add-btn:hover:not([disabled]){color:#2563eb;border-color:#2563eb}.variant-attribute-add-btn[disabled]{opacity:.5;cursor:not-allowed}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i4.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: FormModule }, { kind: "component", type: i3.FormComponent, selector: "v-form", inputs: ["className"], outputs: ["valueChange"] }, { kind: "ngmodule", type: InputModule }, { kind: "component", type: i3.InputComponent, selector: "v-input", inputs: ["name", "type", "title", "titleTranslationKey", "defaultValue", "widthPx", "fullWidth", "margin", "smallMargin", "disabled", "step", "min", "maxLength", "tooltip", "required", "hideNumberSpinBox", "smallLabel", "rows", "clear$", "carbonTheme", "placeholder", "dataTestId", "trim", "presetsTitle", "presetOptions"], outputs: ["valueChange"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i3.SelectComponent, selector: "v-select", inputs: ["items", "defaultSelection", "defaultSelectionId", "defaultSelectionIds", "disabled", "dropUp", "invalid", "multiple", "margin", "widthInPx", "notFoundText", "clearAllText", "clearText", "clearable", "name", "title", "titleTranslationKey", "clearSelectionSubject$", "tooltip", "required", "loading", "loadingText", "placeholder", "smallMargin", "carbonTheme", "appendInline", "warn", "warnText", "dataTestId"], outputs: ["selectedChange"] }, { kind: "component", type: DataMappingTreeComponent, selector: "epistola-data-mapping-tree", inputs: ["pluginId", "templateFields", "prefillMapping", "disabled", "caseDefinitionKey", "processVariables"], outputs: ["mappingChange", "requiredFieldsStatus"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
976
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: GenerateDocumentConfigurationComponent, isStandalone: true, selector: "epistola-generate-document-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$", selectedPluginConfigurationData$: "selectedPluginConfigurationData$", context$: "context$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: disabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null,\n catalogs: catalogs$ | async,\n templates: templates$ | async,\n variants: variants$ | async,\n environments: environments$ | async,\n templateFields: templateFields$ | async,\n selectedCatalogId: selectedCatalogId$ | async,\n selectedTemplateId: selectedTemplateId$ | async\n } as obs\"\n>\n <v-select\n name=\"catalogId\"\n [title]=\"'catalogId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'catalogIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.catalogs.data\"\n [defaultSelectionId]=\"obs.prefill?.catalogId\"\n [disabled]=\"obs.disabled || obs.catalogs.loading\"\n [required]=\"true\"\n [loading]=\"obs.catalogs.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.catalogs.error\" class=\"loading-error\">{{ obs.catalogs.error }}</div>\n\n <v-select\n name=\"templateId\"\n [title]=\"'templateId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'templateIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.templates.data\"\n [defaultSelectionId]=\"obs.prefill?.templateId\"\n [disabled]=\"obs.disabled || obs.templates.loading || !obs.selectedCatalogId\"\n [required]=\"true\"\n [loading]=\"obs.templates.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.templates.error\" class=\"loading-error\">{{ obs.templates.error }}</div>\n\n <!-- Variant selection mode toggle -->\n <div class=\"variant-mode-toggle\" *ngIf=\"obs.selectedTemplateId\">\n <label class=\"variant-mode-label\">{{ 'variantSelectionMode' | pluginTranslate: pluginId | async }}</label>\n <div class=\"variant-mode-buttons\">\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'explicit'\"\n (click)=\"onVariantSelectionModeChange('explicit')\"\n [disabled]=\"obs.disabled\"\n >{{ 'selectByVariant' | pluginTranslate: pluginId | async }}</button>\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'attributes'\"\n (click)=\"onVariantSelectionModeChange('attributes')\"\n [disabled]=\"obs.disabled\"\n >{{ 'selectByAttributes' | pluginTranslate: pluginId | async }}</button>\n </div>\n </div>\n\n <!-- Explicit variant selection (dropdown) -->\n <v-select\n *ngIf=\"variantSelectionMode === 'explicit'\"\n name=\"variantId\"\n [title]=\"'variantId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'variantIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.variants.data\"\n [defaultSelectionId]=\"obs.prefill?.variantId\"\n [disabled]=\"obs.disabled || obs.variants.loading || !obs.selectedTemplateId\"\n [required]=\"false\"\n [loading]=\"obs.variants.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.variants.error\" class=\"loading-error\">{{ obs.variants.error }}</div>\n\n <!-- Attribute-based variant selection -->\n <div *ngIf=\"variantSelectionMode === 'attributes' && obs.selectedTemplateId\" class=\"variant-attributes-section\">\n <label class=\"variant-attributes-label\">{{ 'variantAttributes' | pluginTranslate: pluginId | async }}</label>\n <div class=\"variant-attributes-list\">\n <div *ngFor=\"let entry of variantAttributeEntries; let i = index\" class=\"variant-attribute-row\">\n <select\n *ngIf=\"!entry._customKey\"\n class=\"variant-attribute-input\"\n [ngModel]=\"entry.key\"\n (ngModelChange)=\"onKeySelected(entry, $event)\"\n [disabled]=\"obs.disabled\"\n >\n <option value=\"\" disabled>{{ 'attributeKey' | pluginTranslate: pluginId | async }}</option>\n <option *ngFor=\"let key of availableAttributeKeys\" [value]=\"key\">{{ key }}</option>\n <option value=\"__custom__\">{{ 'attributeKeyCustom' | pluginTranslate: pluginId | async }}</option>\n </select>\n <div *ngIf=\"entry._customKey\" class=\"custom-key-input\">\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeKey' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.key\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <button type=\"button\" class=\"custom-key-cancel\" (click)=\"cancelCustomKey(entry)\" [disabled]=\"obs.disabled\">&times;</button>\n </div>\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeValue' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.value\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <label class=\"variant-attribute-required-toggle\">\n <input\n type=\"checkbox\"\n [(ngModel)]=\"entry.required\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <span class=\"required-label\">{{ (entry.required ? 'attributeRequired' : 'attributePreferred') | pluginTranslate: pluginId | async }}</span>\n </label>\n <button\n type=\"button\"\n class=\"variant-attribute-remove-btn\"\n (click)=\"removeAttributeEntry(i)\"\n [disabled]=\"obs.disabled\"\n title=\"{{ 'removeAttribute' | pluginTranslate: pluginId | async }}\"\n >&times;</button>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"variant-attribute-add-btn\"\n (click)=\"addAttributeEntry()\"\n [disabled]=\"obs.disabled\"\n >+ {{ 'addAttribute' | pluginTranslate: pluginId | async }}</button>\n </div>\n\n <v-select\n name=\"environmentId\"\n [title]=\"'environmentId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'environmentIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.environments.data\"\n [defaultSelectionId]=\"obs.prefill?.environmentId\"\n [disabled]=\"obs.disabled || obs.environments.loading\"\n [required]=\"false\"\n [loading]=\"obs.environments.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.environments.error\" class=\"loading-error\">{{ obs.environments.error }}</div>\n\n <v-select\n name=\"outputFormat\"\n [title]=\"'outputFormat' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'outputFormatTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"outputFormatOptions\"\n [defaultSelectionId]=\"obs.prefill?.outputFormat || 'PDF'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-select>\n\n <v-input\n name=\"filename\"\n [title]=\"'filename' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'filenameTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.filename\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"correlationId\"\n [title]=\"'correlationId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'correlationIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.correlationId\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"resultProcessVariable\"\n [title]=\"'resultProcessVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'resultProcessVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.resultProcessVariable\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n\n<div *ngIf=\"(templateFields$ | async)?.error as templateFieldsError\" class=\"loading-error\">{{ templateFieldsError }}</div>\n\n<epistola-data-mapping-tree\n *ngIf=\"(selectedTemplateId$ | async)\"\n [pluginId]=\"pluginId\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n [disabled]=\"!!(disabled$ | async)\"\n [prefillMapping]=\"prefillDataMapping\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n (mappingChange)=\"onDataMappingChange($event)\"\n (requiredFieldsStatus)=\"onRequiredFieldsStatusChange($event)\"\n></epistola-data-mapping-tree>\n\n<div class=\"validation-summary\" *ngIf=\"(selectedTemplateId$ | async) && requiredFieldsStatus.total > 0\">\n <span *ngIf=\"requiredFieldsStatus.mapped === requiredFieldsStatus.total\" class=\"validation-complete\">\n {{ 'requiredFieldsComplete' | pluginTranslate: pluginId | async }}\n </span>\n <span *ngIf=\"requiredFieldsStatus.mapped < requiredFieldsStatus.total\" class=\"validation-incomplete\">\n {{ requiredFieldsStatus.mapped }} / {{ requiredFieldsStatus.total }}\n {{ 'validationSummary' | pluginTranslate: pluginId | async }}\n </span>\n</div>\n", styles: [".loading-error{padding:.25rem .75rem;font-size:.8125rem;color:#dc3545}.validation-summary{margin-top:.5rem;padding:.5rem .75rem;border-radius:4px;font-size:.875rem}.validation-summary .validation-complete{color:#198754}.validation-summary .validation-incomplete{color:#dc3545;font-weight:500}.variant-mode-toggle{margin-bottom:1rem;padding:0 .75rem}.variant-mode-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-mode-buttons{display:flex;gap:0;border:1px solid #d1d5db;border-radius:4px;overflow:hidden;width:fit-content}.variant-mode-btn{padding:.375rem .75rem;font-size:.8125rem;background:#fff;border:none;border-right:1px solid #d1d5db;cursor:pointer;color:#374151;transition:background-color .15s,color .15s}.variant-mode-btn:last-child{border-right:none}.variant-mode-btn:hover:not([disabled]){background:#f3f4f6}.variant-mode-btn.active{background:#2563eb;color:#fff}.variant-mode-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attributes-section{margin-bottom:1rem;padding:0 .75rem}.variant-attributes-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-attributes-list{display:flex;flex-direction:column;gap:.375rem}.variant-attribute-row{display:flex;gap:.375rem;align-items:center}.variant-attribute-input{flex:1;padding:.375rem .5rem;font-size:.8125rem;border:1px solid #d1d5db;border-radius:4px;outline:none}.variant-attribute-input:focus{border-color:#2563eb;box-shadow:0 0 0 1px #2563eb}.variant-attribute-input[disabled]{opacity:.5;background:#f9fafb}.custom-key-input{display:flex;flex:1;gap:.25rem}.custom-key-input .variant-attribute-input{flex:1}.custom-key-cancel{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.custom-key-cancel:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.custom-key-cancel[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-required-toggle{display:flex;align-items:center;gap:.25rem;font-size:.75rem;color:#374151;white-space:nowrap;cursor:pointer}.variant-attribute-required-toggle input[type=checkbox]{margin:0;cursor:pointer}.variant-attribute-required-toggle .required-label{-webkit-user-select:none;user-select:none}.variant-attribute-remove-btn{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-remove-btn:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.variant-attribute-remove-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-add-btn{margin-top:.375rem;padding:.25rem .5rem;font-size:.8125rem;background:none;border:1px dashed #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-add-btn:hover:not([disabled]){color:#2563eb;border-color:#2563eb}.variant-attribute-add-btn[disabled]{opacity:.5;cursor:not-allowed}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i4.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: FormModule }, { kind: "component", type: i3.FormComponent, selector: "v-form", inputs: ["className"], outputs: ["valueChange"] }, { kind: "ngmodule", type: InputModule }, { kind: "component", type: i3.InputComponent, selector: "v-input", inputs: ["name", "type", "title", "titleTranslationKey", "defaultValue", "widthPx", "fullWidth", "margin", "smallMargin", "disabled", "step", "min", "maxLength", "tooltip", "required", "hideNumberSpinBox", "smallLabel", "rows", "clear$", "carbonTheme", "placeholder", "dataTestId", "trim", "presetsTitle", "presetOptions"], outputs: ["valueChange"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i3.SelectComponent, selector: "v-select", inputs: ["items", "defaultSelection", "defaultSelectionId", "defaultSelectionIds", "disabled", "dropUp", "invalid", "multiple", "margin", "widthInPx", "notFoundText", "clearAllText", "clearText", "clearable", "name", "title", "titleTranslationKey", "clearSelectionSubject$", "tooltip", "required", "loading", "loadingText", "placeholder", "smallMargin", "carbonTheme", "appendInline", "warn", "warnText", "dataTestId"], outputs: ["selectedChange"] }, { kind: "component", type: DataMappingTreeComponent, selector: "epistola-data-mapping-tree", inputs: ["pluginId", "templateFields", "prefillMapping", "disabled", "caseDefinitionKey", "processVariables"], outputs: ["mappingChange", "requiredFieldsStatus"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
931
977
  }
932
978
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GenerateDocumentConfigurationComponent, decorators: [{
933
979
  type: Component,
@@ -939,7 +985,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
939
985
  InputModule,
940
986
  SelectModule,
941
987
  DataMappingTreeComponent
942
- ], template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: disabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null,\n templates: templates$ | async,\n variants: variants$ | async,\n environments: environments$ | async,\n templateFields: templateFields$ | async,\n selectedTemplateId: selectedTemplateId$ | async\n } as obs\"\n>\n <v-select\n name=\"templateId\"\n [title]=\"'templateId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'templateIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.templates.data\"\n [defaultSelectionId]=\"obs.prefill?.templateId\"\n [disabled]=\"obs.disabled || obs.templates.loading\"\n [required]=\"true\"\n [loading]=\"obs.templates.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.templates.error\" class=\"loading-error\">{{ obs.templates.error }}</div>\n\n <!-- Variant selection mode toggle -->\n <div class=\"variant-mode-toggle\" *ngIf=\"obs.selectedTemplateId\">\n <label class=\"variant-mode-label\">{{ 'variantSelectionMode' | pluginTranslate: pluginId | async }}</label>\n <div class=\"variant-mode-buttons\">\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'explicit'\"\n (click)=\"onVariantSelectionModeChange('explicit')\"\n [disabled]=\"obs.disabled\"\n >{{ 'selectByVariant' | pluginTranslate: pluginId | async }}</button>\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'attributes'\"\n (click)=\"onVariantSelectionModeChange('attributes')\"\n [disabled]=\"obs.disabled\"\n >{{ 'selectByAttributes' | pluginTranslate: pluginId | async }}</button>\n </div>\n </div>\n\n <!-- Explicit variant selection (dropdown) -->\n <v-select\n *ngIf=\"variantSelectionMode === 'explicit'\"\n name=\"variantId\"\n [title]=\"'variantId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'variantIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.variants.data\"\n [defaultSelectionId]=\"obs.prefill?.variantId\"\n [disabled]=\"obs.disabled || obs.variants.loading || !obs.selectedTemplateId\"\n [required]=\"false\"\n [loading]=\"obs.variants.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.variants.error\" class=\"loading-error\">{{ obs.variants.error }}</div>\n\n <!-- Attribute-based variant selection -->\n <div *ngIf=\"variantSelectionMode === 'attributes' && obs.selectedTemplateId\" class=\"variant-attributes-section\">\n <label class=\"variant-attributes-label\">{{ 'variantAttributes' | pluginTranslate: pluginId | async }}</label>\n <div class=\"variant-attributes-list\">\n <div *ngFor=\"let entry of variantAttributeEntries; let i = index\" class=\"variant-attribute-row\">\n <select\n *ngIf=\"!entry._customKey\"\n class=\"variant-attribute-input\"\n [ngModel]=\"entry.key\"\n (ngModelChange)=\"onKeySelected(entry, $event)\"\n [disabled]=\"obs.disabled\"\n >\n <option value=\"\" disabled>{{ 'attributeKey' | pluginTranslate: pluginId | async }}</option>\n <option *ngFor=\"let key of availableAttributeKeys\" [value]=\"key\">{{ key }}</option>\n <option value=\"__custom__\">{{ 'attributeKeyCustom' | pluginTranslate: pluginId | async }}</option>\n </select>\n <div *ngIf=\"entry._customKey\" class=\"custom-key-input\">\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeKey' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.key\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <button type=\"button\" class=\"custom-key-cancel\" (click)=\"cancelCustomKey(entry)\" [disabled]=\"obs.disabled\">&times;</button>\n </div>\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeValue' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.value\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <label class=\"variant-attribute-required-toggle\">\n <input\n type=\"checkbox\"\n [(ngModel)]=\"entry.required\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <span class=\"required-label\">{{ (entry.required ? 'attributeRequired' : 'attributePreferred') | pluginTranslate: pluginId | async }}</span>\n </label>\n <button\n type=\"button\"\n class=\"variant-attribute-remove-btn\"\n (click)=\"removeAttributeEntry(i)\"\n [disabled]=\"obs.disabled\"\n title=\"{{ 'removeAttribute' | pluginTranslate: pluginId | async }}\"\n >&times;</button>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"variant-attribute-add-btn\"\n (click)=\"addAttributeEntry()\"\n [disabled]=\"obs.disabled\"\n >+ {{ 'addAttribute' | pluginTranslate: pluginId | async }}</button>\n </div>\n\n <v-select\n name=\"environmentId\"\n [title]=\"'environmentId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'environmentIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.environments.data\"\n [defaultSelectionId]=\"obs.prefill?.environmentId\"\n [disabled]=\"obs.disabled || obs.environments.loading\"\n [required]=\"false\"\n [loading]=\"obs.environments.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.environments.error\" class=\"loading-error\">{{ obs.environments.error }}</div>\n\n <v-select\n name=\"outputFormat\"\n [title]=\"'outputFormat' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'outputFormatTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"outputFormatOptions\"\n [defaultSelectionId]=\"obs.prefill?.outputFormat || 'PDF'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-select>\n\n <v-input\n name=\"filename\"\n [title]=\"'filename' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'filenameTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.filename\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"correlationId\"\n [title]=\"'correlationId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'correlationIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.correlationId\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"resultProcessVariable\"\n [title]=\"'resultProcessVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'resultProcessVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.resultProcessVariable\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n\n<div *ngIf=\"(templateFields$ | async)?.error as templateFieldsError\" class=\"loading-error\">{{ templateFieldsError }}</div>\n\n<epistola-data-mapping-tree\n *ngIf=\"(selectedTemplateId$ | async)\"\n [pluginId]=\"pluginId\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n [disabled]=\"!!(disabled$ | async)\"\n [prefillMapping]=\"prefillDataMapping\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n (mappingChange)=\"onDataMappingChange($event)\"\n (requiredFieldsStatus)=\"onRequiredFieldsStatusChange($event)\"\n></epistola-data-mapping-tree>\n\n<div class=\"validation-summary\" *ngIf=\"(selectedTemplateId$ | async) && requiredFieldsStatus.total > 0\">\n <span *ngIf=\"requiredFieldsStatus.mapped === requiredFieldsStatus.total\" class=\"validation-complete\">\n {{ 'requiredFieldsComplete' | pluginTranslate: pluginId | async }}\n </span>\n <span *ngIf=\"requiredFieldsStatus.mapped < requiredFieldsStatus.total\" class=\"validation-incomplete\">\n {{ requiredFieldsStatus.mapped }} / {{ requiredFieldsStatus.total }}\n {{ 'validationSummary' | pluginTranslate: pluginId | async }}\n </span>\n</div>\n", styles: [".loading-error{padding:.25rem .75rem;font-size:.8125rem;color:#dc3545}.validation-summary{margin-top:.5rem;padding:.5rem .75rem;border-radius:4px;font-size:.875rem}.validation-summary .validation-complete{color:#198754}.validation-summary .validation-incomplete{color:#dc3545;font-weight:500}.variant-mode-toggle{margin-bottom:1rem;padding:0 .75rem}.variant-mode-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-mode-buttons{display:flex;gap:0;border:1px solid #d1d5db;border-radius:4px;overflow:hidden;width:fit-content}.variant-mode-btn{padding:.375rem .75rem;font-size:.8125rem;background:#fff;border:none;border-right:1px solid #d1d5db;cursor:pointer;color:#374151;transition:background-color .15s,color .15s}.variant-mode-btn:last-child{border-right:none}.variant-mode-btn:hover:not([disabled]){background:#f3f4f6}.variant-mode-btn.active{background:#2563eb;color:#fff}.variant-mode-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attributes-section{margin-bottom:1rem;padding:0 .75rem}.variant-attributes-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-attributes-list{display:flex;flex-direction:column;gap:.375rem}.variant-attribute-row{display:flex;gap:.375rem;align-items:center}.variant-attribute-input{flex:1;padding:.375rem .5rem;font-size:.8125rem;border:1px solid #d1d5db;border-radius:4px;outline:none}.variant-attribute-input:focus{border-color:#2563eb;box-shadow:0 0 0 1px #2563eb}.variant-attribute-input[disabled]{opacity:.5;background:#f9fafb}.custom-key-input{display:flex;flex:1;gap:.25rem}.custom-key-input .variant-attribute-input{flex:1}.custom-key-cancel{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.custom-key-cancel:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.custom-key-cancel[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-required-toggle{display:flex;align-items:center;gap:.25rem;font-size:.75rem;color:#374151;white-space:nowrap;cursor:pointer}.variant-attribute-required-toggle input[type=checkbox]{margin:0;cursor:pointer}.variant-attribute-required-toggle .required-label{-webkit-user-select:none;user-select:none}.variant-attribute-remove-btn{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-remove-btn:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.variant-attribute-remove-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-add-btn{margin-top:.375rem;padding:.25rem .5rem;font-size:.8125rem;background:none;border:1px dashed #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-add-btn:hover:not([disabled]){color:#2563eb;border-color:#2563eb}.variant-attribute-add-btn[disabled]{opacity:.5;cursor:not-allowed}\n"] }]
988
+ ], template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: disabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null,\n catalogs: catalogs$ | async,\n templates: templates$ | async,\n variants: variants$ | async,\n environments: environments$ | async,\n templateFields: templateFields$ | async,\n selectedCatalogId: selectedCatalogId$ | async,\n selectedTemplateId: selectedTemplateId$ | async\n } as obs\"\n>\n <v-select\n name=\"catalogId\"\n [title]=\"'catalogId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'catalogIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.catalogs.data\"\n [defaultSelectionId]=\"obs.prefill?.catalogId\"\n [disabled]=\"obs.disabled || obs.catalogs.loading\"\n [required]=\"true\"\n [loading]=\"obs.catalogs.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.catalogs.error\" class=\"loading-error\">{{ obs.catalogs.error }}</div>\n\n <v-select\n name=\"templateId\"\n [title]=\"'templateId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'templateIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.templates.data\"\n [defaultSelectionId]=\"obs.prefill?.templateId\"\n [disabled]=\"obs.disabled || obs.templates.loading || !obs.selectedCatalogId\"\n [required]=\"true\"\n [loading]=\"obs.templates.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.templates.error\" class=\"loading-error\">{{ obs.templates.error }}</div>\n\n <!-- Variant selection mode toggle -->\n <div class=\"variant-mode-toggle\" *ngIf=\"obs.selectedTemplateId\">\n <label class=\"variant-mode-label\">{{ 'variantSelectionMode' | pluginTranslate: pluginId | async }}</label>\n <div class=\"variant-mode-buttons\">\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'explicit'\"\n (click)=\"onVariantSelectionModeChange('explicit')\"\n [disabled]=\"obs.disabled\"\n >{{ 'selectByVariant' | pluginTranslate: pluginId | async }}</button>\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'attributes'\"\n (click)=\"onVariantSelectionModeChange('attributes')\"\n [disabled]=\"obs.disabled\"\n >{{ 'selectByAttributes' | pluginTranslate: pluginId | async }}</button>\n </div>\n </div>\n\n <!-- Explicit variant selection (dropdown) -->\n <v-select\n *ngIf=\"variantSelectionMode === 'explicit'\"\n name=\"variantId\"\n [title]=\"'variantId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'variantIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.variants.data\"\n [defaultSelectionId]=\"obs.prefill?.variantId\"\n [disabled]=\"obs.disabled || obs.variants.loading || !obs.selectedTemplateId\"\n [required]=\"false\"\n [loading]=\"obs.variants.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.variants.error\" class=\"loading-error\">{{ obs.variants.error }}</div>\n\n <!-- Attribute-based variant selection -->\n <div *ngIf=\"variantSelectionMode === 'attributes' && obs.selectedTemplateId\" class=\"variant-attributes-section\">\n <label class=\"variant-attributes-label\">{{ 'variantAttributes' | pluginTranslate: pluginId | async }}</label>\n <div class=\"variant-attributes-list\">\n <div *ngFor=\"let entry of variantAttributeEntries; let i = index\" class=\"variant-attribute-row\">\n <select\n *ngIf=\"!entry._customKey\"\n class=\"variant-attribute-input\"\n [ngModel]=\"entry.key\"\n (ngModelChange)=\"onKeySelected(entry, $event)\"\n [disabled]=\"obs.disabled\"\n >\n <option value=\"\" disabled>{{ 'attributeKey' | pluginTranslate: pluginId | async }}</option>\n <option *ngFor=\"let key of availableAttributeKeys\" [value]=\"key\">{{ key }}</option>\n <option value=\"__custom__\">{{ 'attributeKeyCustom' | pluginTranslate: pluginId | async }}</option>\n </select>\n <div *ngIf=\"entry._customKey\" class=\"custom-key-input\">\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeKey' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.key\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <button type=\"button\" class=\"custom-key-cancel\" (click)=\"cancelCustomKey(entry)\" [disabled]=\"obs.disabled\">&times;</button>\n </div>\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeValue' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.value\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <label class=\"variant-attribute-required-toggle\">\n <input\n type=\"checkbox\"\n [(ngModel)]=\"entry.required\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <span class=\"required-label\">{{ (entry.required ? 'attributeRequired' : 'attributePreferred') | pluginTranslate: pluginId | async }}</span>\n </label>\n <button\n type=\"button\"\n class=\"variant-attribute-remove-btn\"\n (click)=\"removeAttributeEntry(i)\"\n [disabled]=\"obs.disabled\"\n title=\"{{ 'removeAttribute' | pluginTranslate: pluginId | async }}\"\n >&times;</button>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"variant-attribute-add-btn\"\n (click)=\"addAttributeEntry()\"\n [disabled]=\"obs.disabled\"\n >+ {{ 'addAttribute' | pluginTranslate: pluginId | async }}</button>\n </div>\n\n <v-select\n name=\"environmentId\"\n [title]=\"'environmentId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'environmentIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.environments.data\"\n [defaultSelectionId]=\"obs.prefill?.environmentId\"\n [disabled]=\"obs.disabled || obs.environments.loading\"\n [required]=\"false\"\n [loading]=\"obs.environments.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.environments.error\" class=\"loading-error\">{{ obs.environments.error }}</div>\n\n <v-select\n name=\"outputFormat\"\n [title]=\"'outputFormat' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'outputFormatTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"outputFormatOptions\"\n [defaultSelectionId]=\"obs.prefill?.outputFormat || 'PDF'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-select>\n\n <v-input\n name=\"filename\"\n [title]=\"'filename' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'filenameTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.filename\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"correlationId\"\n [title]=\"'correlationId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'correlationIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.correlationId\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"resultProcessVariable\"\n [title]=\"'resultProcessVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'resultProcessVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.resultProcessVariable\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n\n<div *ngIf=\"(templateFields$ | async)?.error as templateFieldsError\" class=\"loading-error\">{{ templateFieldsError }}</div>\n\n<epistola-data-mapping-tree\n *ngIf=\"(selectedTemplateId$ | async)\"\n [pluginId]=\"pluginId\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n [disabled]=\"!!(disabled$ | async)\"\n [prefillMapping]=\"prefillDataMapping\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n (mappingChange)=\"onDataMappingChange($event)\"\n (requiredFieldsStatus)=\"onRequiredFieldsStatusChange($event)\"\n></epistola-data-mapping-tree>\n\n<div class=\"validation-summary\" *ngIf=\"(selectedTemplateId$ | async) && requiredFieldsStatus.total > 0\">\n <span *ngIf=\"requiredFieldsStatus.mapped === requiredFieldsStatus.total\" class=\"validation-complete\">\n {{ 'requiredFieldsComplete' | pluginTranslate: pluginId | async }}\n </span>\n <span *ngIf=\"requiredFieldsStatus.mapped < requiredFieldsStatus.total\" class=\"validation-incomplete\">\n {{ requiredFieldsStatus.mapped }} / {{ requiredFieldsStatus.total }}\n {{ 'validationSummary' | pluginTranslate: pluginId | async }}\n </span>\n</div>\n", styles: [".loading-error{padding:.25rem .75rem;font-size:.8125rem;color:#dc3545}.validation-summary{margin-top:.5rem;padding:.5rem .75rem;border-radius:4px;font-size:.875rem}.validation-summary .validation-complete{color:#198754}.validation-summary .validation-incomplete{color:#dc3545;font-weight:500}.variant-mode-toggle{margin-bottom:1rem;padding:0 .75rem}.variant-mode-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-mode-buttons{display:flex;gap:0;border:1px solid #d1d5db;border-radius:4px;overflow:hidden;width:fit-content}.variant-mode-btn{padding:.375rem .75rem;font-size:.8125rem;background:#fff;border:none;border-right:1px solid #d1d5db;cursor:pointer;color:#374151;transition:background-color .15s,color .15s}.variant-mode-btn:last-child{border-right:none}.variant-mode-btn:hover:not([disabled]){background:#f3f4f6}.variant-mode-btn.active{background:#2563eb;color:#fff}.variant-mode-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attributes-section{margin-bottom:1rem;padding:0 .75rem}.variant-attributes-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-attributes-list{display:flex;flex-direction:column;gap:.375rem}.variant-attribute-row{display:flex;gap:.375rem;align-items:center}.variant-attribute-input{flex:1;padding:.375rem .5rem;font-size:.8125rem;border:1px solid #d1d5db;border-radius:4px;outline:none}.variant-attribute-input:focus{border-color:#2563eb;box-shadow:0 0 0 1px #2563eb}.variant-attribute-input[disabled]{opacity:.5;background:#f9fafb}.custom-key-input{display:flex;flex:1;gap:.25rem}.custom-key-input .variant-attribute-input{flex:1}.custom-key-cancel{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.custom-key-cancel:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.custom-key-cancel[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-required-toggle{display:flex;align-items:center;gap:.25rem;font-size:.75rem;color:#374151;white-space:nowrap;cursor:pointer}.variant-attribute-required-toggle input[type=checkbox]{margin:0;cursor:pointer}.variant-attribute-required-toggle .required-label{-webkit-user-select:none;user-select:none}.variant-attribute-remove-btn{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-remove-btn:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.variant-attribute-remove-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-add-btn{margin-top:.375rem;padding:.25rem .5rem;font-size:.8125rem;background:none;border:1px dashed #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-add-btn:hover:not([disabled]){color:#2563eb;border-color:#2563eb}.variant-attribute-add-btn[disabled]{opacity:.5;cursor:not-allowed}\n"] }]
943
989
  }], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$2.ProcessLinkStateService }, { type: i0.ChangeDetectorRef }], propDecorators: { save$: [{
944
990
  type: Input
945
991
  }], disabled$: [{
@@ -1991,6 +2037,8 @@ const epistolaPluginSpecification = {
1991
2037
  templateSyncEnabled: 'Template synchronisatie',
1992
2038
  templateSyncEnabledTooltip: 'Synchroniseer template definities automatisch van het classpath naar Epistola bij het opstarten',
1993
2039
  'generate-document': 'Genereer Document',
2040
+ catalogId: 'Catalogus',
2041
+ catalogIdTooltip: 'Selecteer de catalogus waaruit een template gekozen wordt',
1994
2042
  templateId: 'Template',
1995
2043
  templateIdTooltip: 'Selecteer het template dat gebruikt wordt voor documentgeneratie',
1996
2044
  variantId: 'Variant',
@@ -2080,6 +2128,8 @@ const epistolaPluginSpecification = {
2080
2128
  templateSyncEnabled: 'Template sync',
2081
2129
  templateSyncEnabledTooltip: 'Automatically synchronize template definitions from classpath to Epistola on startup',
2082
2130
  'generate-document': 'Generate Document',
2131
+ catalogId: 'Catalog',
2132
+ catalogIdTooltip: 'Select the catalog to choose a template from',
2083
2133
  templateId: 'Template',
2084
2134
  templateIdTooltip: 'Select the template to use for document generation',
2085
2135
  variantId: 'Variant',