@dignite/vault-extract 0.2.0

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.
Files changed (39) hide show
  1. package/README.md +17 -0
  2. package/fesm2022/dignite-vault-extract-config.mjs +82 -0
  3. package/fesm2022/dignite-vault-extract-config.mjs.map +1 -0
  4. package/fesm2022/dignite-vault-extract-documents-cabinet-list.component-Ch0gpSCc.mjs +184 -0
  5. package/fesm2022/dignite-vault-extract-documents-cabinet-list.component-Ch0gpSCc.mjs.map +1 -0
  6. package/fesm2022/dignite-vault-extract-documents-content-type-DjCs-s4E.mjs +115 -0
  7. package/fesm2022/dignite-vault-extract-documents-content-type-DjCs-s4E.mjs.map +1 -0
  8. package/fesm2022/dignite-vault-extract-documents-document-detail.component-DHs42DWJ.mjs +1146 -0
  9. package/fesm2022/dignite-vault-extract-documents-document-detail.component-DHs42DWJ.mjs.map +1 -0
  10. package/fesm2022/dignite-vault-extract-documents-document-file-preview.component-CStXf8v9.mjs +72 -0
  11. package/fesm2022/dignite-vault-extract-documents-document-file-preview.component-CStXf8v9.mjs.map +1 -0
  12. package/fesm2022/dignite-vault-extract-documents-document-list.component-jThR5cct.mjs +642 -0
  13. package/fesm2022/dignite-vault-extract-documents-document-list.component-jThR5cct.mjs.map +1 -0
  14. package/fesm2022/dignite-vault-extract-documents-document-overview.component-BHUUUIVr.mjs +318 -0
  15. package/fesm2022/dignite-vault-extract-documents-document-overview.component-BHUUUIVr.mjs.map +1 -0
  16. package/fesm2022/dignite-vault-extract-documents-document-recycle-bin.component-dqeBrw22.mjs +178 -0
  17. package/fesm2022/dignite-vault-extract-documents-document-recycle-bin.component-dqeBrw22.mjs.map +1 -0
  18. package/fesm2022/dignite-vault-extract-documents-document-type-list.component-C8kXFJGb.mjs +464 -0
  19. package/fesm2022/dignite-vault-extract-documents-document-type-list.component-C8kXFJGb.mjs.map +1 -0
  20. package/fesm2022/dignite-vault-extract-documents-export-template-list.component-DlmZFFF1.mjs +361 -0
  21. package/fesm2022/dignite-vault-extract-documents-export-template-list.component-DlmZFFF1.mjs.map +1 -0
  22. package/fesm2022/dignite-vault-extract-documents-extensible-table-DkLXuoWo.mjs +53 -0
  23. package/fesm2022/dignite-vault-extract-documents-extensible-table-DkLXuoWo.mjs.map +1 -0
  24. package/fesm2022/dignite-vault-extract-documents-field-definition-list.component-ClmWkRun.mjs +530 -0
  25. package/fesm2022/dignite-vault-extract-documents-field-definition-list.component-ClmWkRun.mjs.map +1 -0
  26. package/fesm2022/dignite-vault-extract-documents-field-reextraction-modal.component-D7OOycv9.mjs +163 -0
  27. package/fesm2022/dignite-vault-extract-documents-field-reextraction-modal.component-D7OOycv9.mjs.map +1 -0
  28. package/fesm2022/dignite-vault-extract-documents-format-bytes-Cd3QwfQZ.mjs +19 -0
  29. package/fesm2022/dignite-vault-extract-documents-format-bytes-Cd3QwfQZ.mjs.map +1 -0
  30. package/fesm2022/dignite-vault-extract-documents-format-field-value-Xjb8lwzA.mjs +22 -0
  31. package/fesm2022/dignite-vault-extract-documents-format-field-value-Xjb8lwzA.mjs.map +1 -0
  32. package/fesm2022/dignite-vault-extract-documents.mjs +71 -0
  33. package/fesm2022/dignite-vault-extract-documents.mjs.map +1 -0
  34. package/fesm2022/dignite-vault-extract.mjs +522 -0
  35. package/fesm2022/dignite-vault-extract.mjs.map +1 -0
  36. package/package.json +38 -0
  37. package/types/dignite-vault-extract-config.d.ts +5 -0
  38. package/types/dignite-vault-extract-documents.d.ts +5 -0
  39. package/types/dignite-vault-extract.d.ts +521 -0
@@ -0,0 +1,464 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, DestroyRef, EventEmitter, signal, Output, Input, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
+ import * as i2 from '@angular/common';
5
+ import { CommonModule } from '@angular/common';
6
+ import { Router, RouterModule } from '@angular/router';
7
+ import * as i1 from '@angular/forms';
8
+ import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
9
+ import { LocalizationPipe, PermissionService, ListService, escapeHtmlChars } from '@abp/ng.core';
10
+ import { ExtensionsService, EntityProp, ExtensibleTableComponent, EXTENSIONS_IDENTIFIER } from '@abp/ng.components/extensible';
11
+ import { ToasterService, ConfirmationService, Confirmation } from '@abp/ng.theme.shared';
12
+ import * as i2$1 from '@ng-bootstrap/ng-bootstrap';
13
+ import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
14
+ import { Subject, switchMap, catchError, EMPTY, of, map } from 'rxjs';
15
+ import { DocumentReprocessingService, ReclassificationScope, DocumentTypeService, SlugSuggestionService, EXTRACT_PERMISSIONS } from '@dignite/vault-extract';
16
+ import { c as configureEntityTable, E as EXTRACT_TABLES, p as pageClientItems } from './dignite-vault-extract-documents-extensible-table-DkLXuoWo.mjs';
17
+ import { w as wireSlugSuggestion, F as FieldReextractionModalComponent } from './dignite-vault-extract-documents-field-reextraction-modal.component-D7OOycv9.mjs';
18
+
19
+ /**
20
+ * Bulk reclassification preview and trigger modal (#289 scenario 1): cascading, destructive, and
21
+ * strongly warned.
22
+ * Humans choose the scope by intent (only this type / all documents across types / pending review
23
+ * queue); the system does not prescribe a default. Manual confirmations are protected by default, and
24
+ * overwriting manual results requires an explicit opt-in. Scope and toggle changes refresh the affected
25
+ * document count preview immediately.
26
+ *
27
+ * When opened from a document type menu, [documentTypeId] is passed and the default scope is only that
28
+ * type; users can switch to all documents or the pending review queue.
29
+ * When opened without type context, such as a global entry, only-this-type is unavailable and the
30
+ * default is all documents across types.
31
+ */
32
+ class ReclassificationModalComponent {
33
+ constructor() {
34
+ this.service = inject(DocumentReprocessingService);
35
+ this.toaster = inject(ToasterService);
36
+ this.fb = inject(FormBuilder);
37
+ this.destroyRef = inject(DestroyRef);
38
+ this.documentTypeDisplayName = '';
39
+ this.closed = new EventEmitter();
40
+ this.Scope = ReclassificationScope;
41
+ this.documentCount = signal(null, ...(ngDevMode ? [{ debugName: "documentCount" }] : /* istanbul ignore next */ []));
42
+ this.isLoadingPreview = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingPreview" }] : /* istanbul ignore next */ []));
43
+ this.previewFailed = signal(false, ...(ngDevMode ? [{ debugName: "previewFailed" }] : /* istanbul ignore next */ []));
44
+ this.isSubmitting = signal(false, ...(ngDevMode ? [{ debugName: "isSubmitting" }] : /* istanbul ignore next */ []));
45
+ this.form = this.fb.nonNullable.group({
46
+ scope: ReclassificationScope.OnlyCurrentType,
47
+ includeManuallyConfirmed: false,
48
+ });
49
+ this.previewTrigger = new Subject();
50
+ this.backdropMouseDownOnSelf = false;
51
+ }
52
+ get hasType() {
53
+ return !!this.documentTypeId;
54
+ }
55
+ // The protect-manual-confirmations toggle is meaningless for the pending review queue, because pending
56
+ // review documents are not confirmed yet.
57
+ get includeToggleApplies() {
58
+ return this.form.controls.scope.value !== ReclassificationScope.PendingReviewQueue;
59
+ }
60
+ ngOnInit() {
61
+ // No type context: default to all documents across types; only-this-type is unavailable.
62
+ this.form.controls.scope.setValue(this.hasType ? ReclassificationScope.OnlyCurrentType : ReclassificationScope.AllDocuments, { emitEvent: false });
63
+ this.applyIncludeTogglePolicy();
64
+ // switchMap cancels in-flight preview requests when scope or toggles change quickly, preventing
65
+ // out-of-order responses from showing counts for stale scopes (review round 1).
66
+ this.previewTrigger
67
+ .pipe(switchMap(() => {
68
+ this.isLoadingPreview.set(true);
69
+ this.previewFailed.set(false);
70
+ return this.service.previewReclassification(this.buildInput()).pipe(catchError(() => {
71
+ this.documentCount.set(null);
72
+ this.previewFailed.set(true);
73
+ this.isLoadingPreview.set(false);
74
+ return EMPTY;
75
+ }));
76
+ }), takeUntilDestroyed(this.destroyRef))
77
+ .subscribe(dto => {
78
+ this.documentCount.set(dto.documentCount ?? null);
79
+ this.isLoadingPreview.set(false);
80
+ });
81
+ this.form.valueChanges
82
+ .pipe(takeUntilDestroyed(this.destroyRef))
83
+ .subscribe(() => {
84
+ this.applyIncludeTogglePolicy();
85
+ this.previewTrigger.next();
86
+ });
87
+ this.previewTrigger.next();
88
+ }
89
+ // The protect-manual-confirmations toggle is meaningless for the pending review queue. Drive this with
90
+ // reactive-forms disable/enable instead of template [disabled], avoiding Angular dev warnings on
91
+ // formControlName [disabled] and keeping FormControl.disabled truly synchronized.
92
+ applyIncludeTogglePolicy() {
93
+ const control = this.form.controls.includeManuallyConfirmed;
94
+ if (this.includeToggleApplies) {
95
+ if (control.disabled)
96
+ control.enable({ emitEvent: false });
97
+ }
98
+ else if (control.enabled) {
99
+ control.disable({ emitEvent: false });
100
+ }
101
+ }
102
+ // Explicit retry after preview failure, aligned with the field re-extraction modal UX; switchMap
103
+ // cancels any in-flight request.
104
+ refreshPreview() {
105
+ this.previewTrigger.next();
106
+ }
107
+ buildInput() {
108
+ const raw = this.form.getRawValue();
109
+ return {
110
+ scope: Number(raw.scope),
111
+ documentTypeId: Number(raw.scope) === ReclassificationScope.OnlyCurrentType ? this.documentTypeId : undefined,
112
+ includeManuallyConfirmed: raw.includeManuallyConfirmed,
113
+ };
114
+ }
115
+ confirm() {
116
+ if (this.isSubmitting())
117
+ return;
118
+ this.isSubmitting.set(true);
119
+ this.service
120
+ .startReclassification(this.buildInput())
121
+ .pipe(takeUntilDestroyed(this.destroyRef))
122
+ .subscribe({
123
+ next: () => {
124
+ this.isSubmitting.set(false);
125
+ this.toaster.success('::Document:Reprocess:Reclassification:Queued', '::Success');
126
+ this.close();
127
+ },
128
+ error: () => {
129
+ this.isSubmitting.set(false);
130
+ this.toaster.error('::Document:Reprocess:Failed', '::Error');
131
+ },
132
+ });
133
+ }
134
+ close() {
135
+ this.closed.emit();
136
+ }
137
+ onBackdropMouseDown(event) {
138
+ this.backdropMouseDownOnSelf = event.target === event.currentTarget;
139
+ }
140
+ onBackdropClick(event) {
141
+ if (this.backdropMouseDownOnSelf && event.target === event.currentTarget && !this.isSubmitting()) {
142
+ this.close();
143
+ }
144
+ this.backdropMouseDownOnSelf = false;
145
+ }
146
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ReclassificationModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
147
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: ReclassificationModalComponent, isStandalone: true, selector: "lib-reclassification-modal", inputs: { documentTypeId: "documentTypeId", documentTypeDisplayName: "documentTypeDisplayName" }, outputs: { closed: "closed" }, ngImport: i0, template: "<div\n class=\"modal d-block\"\n tabindex=\"-1\"\n style=\"background:rgba(0,0,0,.4);\"\n (mousedown)=\"onBackdropMouseDown($event)\"\n (click)=\"onBackdropClick($event)\"\n>\n <div class=\"modal-dialog modal-dialog-centered\">\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <h5 class=\"modal-title\">\n <i class=\"fas fa-shuffle me-2\"></i>\n {{ '::Document:Reprocess:Reclassification:Title' | abpLocalization }}\n </h5>\n <button type=\"button\" class=\"btn-close\" (click)=\"close()\" [disabled]=\"isSubmitting()\"></button>\n </div>\n\n <div class=\"modal-body\" [formGroup]=\"form\">\n <!-- Scope selection: humans choose by intent; the system does not prescribe a default. -->\n <div class=\"mb-3\">\n <label class=\"form-label fw-semibold\">{{ '::Document:Reprocess:Reclassification:Scope' | abpLocalization }}</label>\n\n @if (hasType) {\n <div class=\"form-check\">\n <input class=\"form-check-input\" type=\"radio\" formControlName=\"scope\" id=\"scope-type\"\n [value]=\"Scope.OnlyCurrentType\" />\n <label class=\"form-check-label\" for=\"scope-type\">\n {{ '::Document:Reprocess:Reclassification:Scope:OnlyCurrentType' | abpLocalization: documentTypeDisplayName }}\n <span class=\"d-block small text-muted\">{{ '::Document:Reprocess:Reclassification:Scope:OnlyCurrentType:Hint' | abpLocalization }}</span>\n </label>\n </div>\n }\n\n <div class=\"form-check\">\n <input class=\"form-check-input\" type=\"radio\" formControlName=\"scope\" id=\"scope-all\"\n [value]=\"Scope.AllDocuments\" />\n <label class=\"form-check-label\" for=\"scope-all\">\n {{ '::Document:Reprocess:Reclassification:Scope:AllDocuments' | abpLocalization }}\n <span class=\"d-block small text-muted\">{{ '::Document:Reprocess:Reclassification:Scope:AllDocuments:Hint' | abpLocalization }}</span>\n </label>\n </div>\n\n <div class=\"form-check\">\n <input class=\"form-check-input\" type=\"radio\" formControlName=\"scope\" id=\"scope-pending\"\n [value]=\"Scope.PendingReviewQueue\" />\n <label class=\"form-check-label\" for=\"scope-pending\">\n {{ '::Document:Reprocess:Reclassification:Scope:PendingReviewQueue' | abpLocalization }}\n <span class=\"d-block small text-muted\">{{ '::Document:Reprocess:Reclassification:Scope:PendingReviewQueue:Hint' | abpLocalization }}</span>\n </label>\n </div>\n </div>\n\n <!-- Protect manual confirmations by default; checked means manually confirmed documents are reclassified too. -->\n <div class=\"form-check mb-3\" [class.opacity-50]=\"!includeToggleApplies\">\n <input class=\"form-check-input\" type=\"checkbox\" formControlName=\"includeManuallyConfirmed\"\n id=\"include-confirmed\" />\n <label class=\"form-check-label\" for=\"include-confirmed\">\n {{ '::Document:Reprocess:Reclassification:IncludeConfirmed' | abpLocalization }}\n <span class=\"d-block small text-muted\">{{ '::Document:Reprocess:Reclassification:IncludeConfirmed:Hint' | abpLocalization }}</span>\n </label>\n </div>\n\n <!-- Affected document count preview. -->\n <div class=\"alert alert-secondary py-2\">\n @if (isLoadingPreview()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n {{ '::Document:Reprocess:Counting' | abpLocalization }}\n } @else if (previewFailed()) {\n <span class=\"d-flex justify-content-between align-items-center\">\n <span class=\"text-danger\">{{ '::Document:Reprocess:PreviewFailed' | abpLocalization }}</span>\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger\" (click)=\"refreshPreview()\">\n {{ '::Refresh' | abpLocalization }}\n </button>\n </span>\n } @else {\n <span class=\"fw-semibold\">{{ '::Document:Reprocess:AffectedCount' | abpLocalization: (((documentCount() ?? 0) | number) ?? '') }}</span>\n }\n </div>\n\n <!-- Strong warning: cascading and destructive. -->\n <div class=\"alert alert-danger mb-0 small\">\n <i class=\"fas fa-triangle-exclamation me-1\"></i>\n {{ '::Document:Reprocess:Reclassification:Warning' | abpLocalization }}\n </div>\n </div>\n\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"close()\" [disabled]=\"isSubmitting()\">\n {{ '::Cancel' | abpLocalization }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-danger\"\n (click)=\"confirm()\"\n [disabled]=\"isLoadingPreview() || previewFailed() || isSubmitting() || (documentCount() ?? 0) === 0\"\n >\n @if (isSubmitting()) {\n <span class=\"spinner-border spinner-border-sm me-1\"></span>\n }\n {{ '::Document:Reprocess:Reclassification:Start' | abpLocalization }}\n </button>\n </div>\n </div>\n </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.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: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "pipe", type: i2.DecimalPipe, name: "number" }, { kind: "pipe", type: LocalizationPipe, name: "abpLocalization" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
148
+ }
149
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ReclassificationModalComponent, decorators: [{
150
+ type: Component,
151
+ args: [{ selector: 'lib-reclassification-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule, LocalizationPipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n class=\"modal d-block\"\n tabindex=\"-1\"\n style=\"background:rgba(0,0,0,.4);\"\n (mousedown)=\"onBackdropMouseDown($event)\"\n (click)=\"onBackdropClick($event)\"\n>\n <div class=\"modal-dialog modal-dialog-centered\">\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <h5 class=\"modal-title\">\n <i class=\"fas fa-shuffle me-2\"></i>\n {{ '::Document:Reprocess:Reclassification:Title' | abpLocalization }}\n </h5>\n <button type=\"button\" class=\"btn-close\" (click)=\"close()\" [disabled]=\"isSubmitting()\"></button>\n </div>\n\n <div class=\"modal-body\" [formGroup]=\"form\">\n <!-- Scope selection: humans choose by intent; the system does not prescribe a default. -->\n <div class=\"mb-3\">\n <label class=\"form-label fw-semibold\">{{ '::Document:Reprocess:Reclassification:Scope' | abpLocalization }}</label>\n\n @if (hasType) {\n <div class=\"form-check\">\n <input class=\"form-check-input\" type=\"radio\" formControlName=\"scope\" id=\"scope-type\"\n [value]=\"Scope.OnlyCurrentType\" />\n <label class=\"form-check-label\" for=\"scope-type\">\n {{ '::Document:Reprocess:Reclassification:Scope:OnlyCurrentType' | abpLocalization: documentTypeDisplayName }}\n <span class=\"d-block small text-muted\">{{ '::Document:Reprocess:Reclassification:Scope:OnlyCurrentType:Hint' | abpLocalization }}</span>\n </label>\n </div>\n }\n\n <div class=\"form-check\">\n <input class=\"form-check-input\" type=\"radio\" formControlName=\"scope\" id=\"scope-all\"\n [value]=\"Scope.AllDocuments\" />\n <label class=\"form-check-label\" for=\"scope-all\">\n {{ '::Document:Reprocess:Reclassification:Scope:AllDocuments' | abpLocalization }}\n <span class=\"d-block small text-muted\">{{ '::Document:Reprocess:Reclassification:Scope:AllDocuments:Hint' | abpLocalization }}</span>\n </label>\n </div>\n\n <div class=\"form-check\">\n <input class=\"form-check-input\" type=\"radio\" formControlName=\"scope\" id=\"scope-pending\"\n [value]=\"Scope.PendingReviewQueue\" />\n <label class=\"form-check-label\" for=\"scope-pending\">\n {{ '::Document:Reprocess:Reclassification:Scope:PendingReviewQueue' | abpLocalization }}\n <span class=\"d-block small text-muted\">{{ '::Document:Reprocess:Reclassification:Scope:PendingReviewQueue:Hint' | abpLocalization }}</span>\n </label>\n </div>\n </div>\n\n <!-- Protect manual confirmations by default; checked means manually confirmed documents are reclassified too. -->\n <div class=\"form-check mb-3\" [class.opacity-50]=\"!includeToggleApplies\">\n <input class=\"form-check-input\" type=\"checkbox\" formControlName=\"includeManuallyConfirmed\"\n id=\"include-confirmed\" />\n <label class=\"form-check-label\" for=\"include-confirmed\">\n {{ '::Document:Reprocess:Reclassification:IncludeConfirmed' | abpLocalization }}\n <span class=\"d-block small text-muted\">{{ '::Document:Reprocess:Reclassification:IncludeConfirmed:Hint' | abpLocalization }}</span>\n </label>\n </div>\n\n <!-- Affected document count preview. -->\n <div class=\"alert alert-secondary py-2\">\n @if (isLoadingPreview()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n {{ '::Document:Reprocess:Counting' | abpLocalization }}\n } @else if (previewFailed()) {\n <span class=\"d-flex justify-content-between align-items-center\">\n <span class=\"text-danger\">{{ '::Document:Reprocess:PreviewFailed' | abpLocalization }}</span>\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger\" (click)=\"refreshPreview()\">\n {{ '::Refresh' | abpLocalization }}\n </button>\n </span>\n } @else {\n <span class=\"fw-semibold\">{{ '::Document:Reprocess:AffectedCount' | abpLocalization: (((documentCount() ?? 0) | number) ?? '') }}</span>\n }\n </div>\n\n <!-- Strong warning: cascading and destructive. -->\n <div class=\"alert alert-danger mb-0 small\">\n <i class=\"fas fa-triangle-exclamation me-1\"></i>\n {{ '::Document:Reprocess:Reclassification:Warning' | abpLocalization }}\n </div>\n </div>\n\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"close()\" [disabled]=\"isSubmitting()\">\n {{ '::Cancel' | abpLocalization }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-danger\"\n (click)=\"confirm()\"\n [disabled]=\"isLoadingPreview() || previewFailed() || isSubmitting() || (documentCount() ?? 0) === 0\"\n >\n @if (isSubmitting()) {\n <span class=\"spinner-border spinner-border-sm me-1\"></span>\n }\n {{ '::Document:Reprocess:Reclassification:Start' | abpLocalization }}\n </button>\n </div>\n </div>\n </div>\n</div>\n" }]
152
+ }], propDecorators: { documentTypeId: [{
153
+ type: Input
154
+ }], documentTypeDisplayName: [{
155
+ type: Input
156
+ }], closed: [{
157
+ type: Output
158
+ }] } });
159
+
160
+ // Mirrors DocumentTypeConsts (Domain.Shared): TypeCode whitelist + length cap.
161
+ const TYPE_CODE_PATTERN = /^[A-Za-z0-9_\-]+(\.[A-Za-z0-9_\-]+)*$/;
162
+ const MAX_TYPE_CODE_LENGTH = 128;
163
+ const MAX_DISPLAY_NAME_LENGTH = 128;
164
+ const MAX_DESCRIPTION_LENGTH = 512;
165
+ const DOCUMENT_TYPE_SORTS = {
166
+ typeCode: type => type.typeCode,
167
+ displayName: type => type.displayName,
168
+ confidenceThreshold: type => type.confidenceThreshold,
169
+ priority: type => type.priority,
170
+ };
171
+ class DocumentTypeListComponent {
172
+ constructor() {
173
+ this.service = inject(DocumentTypeService);
174
+ this.slugService = inject(SlugSuggestionService);
175
+ this.router = inject(Router);
176
+ this.fb = inject(FormBuilder);
177
+ this.confirmation = inject(ConfirmationService);
178
+ this.toaster = inject(ToasterService);
179
+ this.permissionService = inject(PermissionService);
180
+ this.destroyRef = inject(DestroyRef);
181
+ this.extensions = inject(ExtensionsService);
182
+ this.list = inject(ListService);
183
+ // Create/edit/delete buttons require any DocumentTypes write grant (#217); the route's
184
+ // DocumentTypes.Default only lists. ABP evaluates the `||` policy expression.
185
+ this.canManage = this.permissionService.getGrantedPolicy(`${EXTRACT_PERMISSIONS.DocumentTypes.Create} || ${EXTRACT_PERMISSIONS.DocumentTypes.Update} || ${EXTRACT_PERMISSIONS.DocumentTypes.Delete}`);
186
+ // Bulk reprocessing entry points (#289): admin-level and independent from type CRUD permissions.
187
+ this.canReextractFields = this.permissionService.getGrantedPolicy(EXTRACT_PERMISSIONS.Documents.Reprocessing.FieldExtraction);
188
+ this.canReclassify = this.permissionService.getGrantedPolicy(EXTRACT_PERMISSIONS.Documents.Reprocessing.Reclassification);
189
+ // Target for the open reprocessing modal; null means closed.
190
+ this.reextractTarget = signal(null, ...(ngDevMode ? [{ debugName: "reextractTarget" }] : /* istanbul ignore next */ []));
191
+ this.reclassifyTarget = signal(null, ...(ngDevMode ? [{ debugName: "reclassifyTarget" }] : /* istanbul ignore next */ []));
192
+ this.allTypes = signal([], ...(ngDevMode ? [{ debugName: "allTypes" }] : /* istanbul ignore next */ []));
193
+ this.types = signal({ totalCount: 0, items: [] }, ...(ngDevMode ? [{ debugName: "types" }] : /* istanbul ignore next */ []));
194
+ this.isLoading = signal(true, ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
195
+ this.showDeleted = signal(false, ...(ngDevMode ? [{ debugName: "showDeleted" }] : /* istanbul ignore next */ []));
196
+ // null = closed; 'create' / DocumentTypeDto = open in the matching mode.
197
+ this.editing = signal(null, ...(ngDevMode ? [{ debugName: "editing" }] : /* istanbul ignore next */ []));
198
+ this.isSubmitting = signal(false, ...(ngDevMode ? [{ debugName: "isSubmitting" }] : /* istanbul ignore next */ []));
199
+ this.isSuggesting = signal(false, ...(ngDevMode ? [{ debugName: "isSuggesting" }] : /* istanbul ignore next */ []));
200
+ this.tableQuery = {};
201
+ this.form = this.fb.nonNullable.group({
202
+ typeCode: [
203
+ '',
204
+ [
205
+ Validators.required,
206
+ Validators.maxLength(MAX_TYPE_CODE_LENGTH),
207
+ Validators.pattern(TYPE_CODE_PATTERN),
208
+ ],
209
+ ],
210
+ displayName: ['', [Validators.required, Validators.maxLength(MAX_DISPLAY_NAME_LENGTH)]],
211
+ // Optional classification helper description (#262): only helps AI identify the type and does not
212
+ // participate in document content processing.
213
+ description: ['', [Validators.maxLength(MAX_DESCRIPTION_LENGTH)]],
214
+ confidenceThreshold: [0.7, [Validators.required, Validators.min(0), Validators.max(1)]],
215
+ priority: [0, [Validators.required]],
216
+ });
217
+ // Backdrop close guard: close only when both mousedown and click occur on the backdrop itself, not
218
+ // inside the dialog.
219
+ // Otherwise, dragging selected text inside an input and releasing over the backdrop can make the
220
+ // browser fire click on the backdrop, the nearest common ancestor of mousedown/mouseup, closing the
221
+ // modal and losing entered content. Recording the mousedown origin is the only reliable way to know
222
+ // whether this click truly started from the backdrop.
223
+ this.backdropMouseDownOnSelf = false;
224
+ configureEntityTable(this.extensions, EXTRACT_TABLES.DocumentTypes, [
225
+ EntityProp.create({
226
+ type: "string" /* ePropType.String */,
227
+ name: 'typeCode',
228
+ displayName: '::DocumentType:TypeCode',
229
+ sortable: true,
230
+ columnWidth: 240,
231
+ valueResolver: data => of(`<span class="badge bg-info text-dark">${escapeHtmlChars(data.record.typeCode)}</span>`),
232
+ }),
233
+ EntityProp.create({
234
+ type: "string" /* ePropType.String */,
235
+ name: 'displayName',
236
+ displayName: '::DocumentType:DisplayName',
237
+ sortable: true,
238
+ }),
239
+ EntityProp.create({
240
+ type: "number" /* ePropType.Number */,
241
+ name: 'confidenceThreshold',
242
+ displayName: '::DocumentType:ConfidenceThreshold',
243
+ sortable: true,
244
+ columnWidth: 190,
245
+ valueResolver: data => of(`${((data.record.confidenceThreshold ?? 0) * 100).toFixed(0)}%`),
246
+ }),
247
+ EntityProp.create({
248
+ type: "number" /* ePropType.Number */,
249
+ name: 'priority',
250
+ displayName: '::DocumentType:Priority',
251
+ sortable: true,
252
+ columnWidth: 140,
253
+ }),
254
+ ]);
255
+ }
256
+ ngOnInit() {
257
+ this.hookTableQuery();
258
+ this.slugHandle = wireSlugSuggestion({
259
+ displayName: this.form.controls.displayName,
260
+ target: this.form.controls.typeCode,
261
+ suggest: text => this.slugService.suggest({ label: text }, undefined).pipe(map(r => r.slug ?? '')),
262
+ fallback: () => this.nextTypeCode(),
263
+ destroyRef: this.destroyRef,
264
+ onPending: pending => this.isSuggesting.set(pending),
265
+ });
266
+ this.load();
267
+ }
268
+ // Local fallback when the LLM is unavailable or does not translate: choose the smallest type_{n} that
269
+ // does not conflict with existing type codes.
270
+ nextTypeCode() {
271
+ const existing = new Set(this.allTypes().map(t => t.typeCode));
272
+ let i = 1;
273
+ while (existing.has(`type_${i}`))
274
+ i++;
275
+ return `type_${i}`;
276
+ }
277
+ refresh() {
278
+ this.load();
279
+ }
280
+ toggleDeleted() {
281
+ this.showDeleted.update(v => !v);
282
+ this.load();
283
+ }
284
+ load() {
285
+ this.isLoading.set(true);
286
+ const source$ = this.showDeleted() ? this.service.getDeleted() : this.service.getVisible();
287
+ source$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
288
+ next: list => {
289
+ this.allTypes.set(list);
290
+ this.list.totalCount = list.length;
291
+ this.applyTableQuery();
292
+ this.isLoading.set(false);
293
+ },
294
+ error: () => {
295
+ this.allTypes.set([]);
296
+ this.types.set({ totalCount: 0, items: [] });
297
+ this.list.totalCount = 0;
298
+ this.isLoading.set(false);
299
+ },
300
+ });
301
+ }
302
+ hookTableQuery() {
303
+ this.list.query$
304
+ .pipe(takeUntilDestroyed(this.destroyRef))
305
+ .subscribe(query => this.applyTableQuery(query));
306
+ }
307
+ applyTableQuery(query = this.tableQuery) {
308
+ this.tableQuery = query;
309
+ this.types.set(pageClientItems(this.allTypes(), query, DOCUMENT_TYPE_SORTS));
310
+ }
311
+ openCreate() {
312
+ this.form.reset({ typeCode: '', displayName: '', description: '', confidenceThreshold: 0.7, priority: 0 });
313
+ this.form.controls.typeCode.enable();
314
+ // Must be called after form.reset()/enable(): both trigger valueChanges that can be misread as
315
+ // "manual edit". reset() clears that marker and resets suggestion state, including the spinner.
316
+ this.slugHandle?.reset();
317
+ this.editing.set('create');
318
+ }
319
+ openEdit(type) {
320
+ // Disable before reset so slug auto-suggestion sees edit-mode reset as not automatically managed and
321
+ // does not clear the existing typeCode as a stale key. See wireSlugSuggestion comments.
322
+ this.form.controls.typeCode.disable();
323
+ this.form.reset({
324
+ typeCode: type.typeCode,
325
+ displayName: type.displayName,
326
+ description: type.description ?? '',
327
+ confidenceThreshold: type.confidenceThreshold,
328
+ priority: type.priority,
329
+ });
330
+ this.form.controls.typeCode.enable();
331
+ this.slugHandle?.markManual();
332
+ this.editing.set(type);
333
+ }
334
+ // Display-name blur triggers slug auto-suggestion. Measured feedback changed this from pause debounce
335
+ // to blur trigger.
336
+ onDisplayNameBlur() {
337
+ this.slugHandle?.notifyDisplayNameBlur();
338
+ }
339
+ onBackdropMouseDown(event) {
340
+ this.backdropMouseDownOnSelf = event.target === event.currentTarget;
341
+ }
342
+ onBackdropClick(event) {
343
+ if (this.backdropMouseDownOnSelf && event.target === event.currentTarget) {
344
+ this.closeModal();
345
+ }
346
+ this.backdropMouseDownOnSelf = false;
347
+ }
348
+ closeModal() {
349
+ this.editing.set(null);
350
+ }
351
+ submit() {
352
+ if (this.form.invalid) {
353
+ this.form.markAllAsTouched();
354
+ return;
355
+ }
356
+ const mode = this.editing();
357
+ if (mode === null)
358
+ return;
359
+ this.isSubmitting.set(true);
360
+ const raw = this.form.getRawValue();
361
+ if (mode === 'create') {
362
+ const input = {
363
+ typeCode: raw.typeCode,
364
+ displayName: raw.displayName,
365
+ description: raw.description.trim() || undefined,
366
+ confidenceThreshold: raw.confidenceThreshold,
367
+ priority: raw.priority,
368
+ };
369
+ this.service.create(input)
370
+ .pipe(takeUntilDestroyed(this.destroyRef))
371
+ .subscribe({
372
+ next: () => this.onSaved('::DocumentType:CreatedSuccessfully'),
373
+ error: () => this.isSubmitting.set(false),
374
+ });
375
+ }
376
+ else {
377
+ this.service.update(mode.id, {
378
+ typeCode: raw.typeCode,
379
+ displayName: raw.displayName,
380
+ description: raw.description.trim() || undefined,
381
+ confidenceThreshold: raw.confidenceThreshold,
382
+ priority: raw.priority,
383
+ })
384
+ .pipe(takeUntilDestroyed(this.destroyRef))
385
+ .subscribe({
386
+ next: () => this.onSaved('::DocumentType:UpdatedSuccessfully'),
387
+ error: () => this.isSubmitting.set(false),
388
+ });
389
+ }
390
+ }
391
+ onSaved(messageKey) {
392
+ this.isSubmitting.set(false);
393
+ this.closeModal();
394
+ this.toaster.success(messageKey, '::Success');
395
+ this.load();
396
+ }
397
+ delete(type) {
398
+ this.confirmation
399
+ .warn('::DocumentType:AreYouSureToDelete', '::AreYouSure')
400
+ .pipe(takeUntilDestroyed(this.destroyRef))
401
+ .subscribe(status => {
402
+ if (status !== Confirmation.Status.confirm)
403
+ return;
404
+ this.service.delete(type.id)
405
+ .pipe(takeUntilDestroyed(this.destroyRef))
406
+ .subscribe({
407
+ next: () => {
408
+ this.toaster.success('::DocumentType:DeletedSuccessfully', '::Success');
409
+ this.load();
410
+ },
411
+ error: () => this.toaster.error('::DocumentType:DeleteFailed', '::Error'),
412
+ });
413
+ });
414
+ }
415
+ restore(type) {
416
+ this.service.restore(type.id)
417
+ .pipe(takeUntilDestroyed(this.destroyRef))
418
+ .subscribe({
419
+ next: () => {
420
+ this.toaster.success('::DocumentType:RestoredSuccessfully', '::Success');
421
+ this.load();
422
+ },
423
+ });
424
+ }
425
+ manageFields(type) {
426
+ this.router.navigate(['/documents/types', type.id, 'fields']);
427
+ }
428
+ openReextractFields(type) {
429
+ this.reextractTarget.set(type);
430
+ }
431
+ openReclassify(type) {
432
+ this.reclassifyTarget.set(type);
433
+ }
434
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DocumentTypeListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
435
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: DocumentTypeListComponent, isStandalone: true, selector: "lib-document-type-list", providers: [
436
+ ListService,
437
+ {
438
+ provide: EXTENSIONS_IDENTIFIER,
439
+ useValue: EXTRACT_TABLES.DocumentTypes,
440
+ },
441
+ ], ngImport: i0, template: "<div class=\"container-fluid py-4\">\n <!-- Header -->\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <h4 class=\"mb-0\">\n <i class=\"fas fa-tags me-2\"></i>\n {{ '::DocumentType:Title' | abpLocalization }}\n </h4>\n <div class=\"d-flex gap-2\">\n <button\n class=\"btn btn-outline-secondary\"\n (click)=\"refresh()\"\n [disabled]=\"isLoading()\"\n title=\"{{ '::Refresh' | abpLocalization }}\"\n >\n <i class=\"fas fa-sync-alt\" [class.fa-spin]=\"isLoading()\"></i>\n </button>\n <button\n class=\"btn\"\n [class.btn-secondary]=\"showDeleted()\"\n [class.btn-outline-secondary]=\"!showDeleted()\"\n (click)=\"toggleDeleted()\"\n >\n <i class=\"fas fa-trash-can me-1\"></i>\n {{ '::DocumentType:ShowDeleted' | abpLocalization }}\n </button>\n @if (canManage && !showDeleted()) {\n <button class=\"btn btn-primary\" (click)=\"openCreate()\">\n <i class=\"fas fa-plus me-1\"></i>\n {{ '::DocumentType:New' | abpLocalization }}\n </button>\n }\n </div>\n </div>\n\n <!-- Loading spinner -->\n @if (isLoading()) {\n <div class=\"text-center py-5\">\n <div class=\"spinner-border text-primary\" role=\"status\"></div>\n </div>\n }\n\n <!-- Empty state -->\n @if (!isLoading() && types().totalCount === 0) {\n <div class=\"card shadow-sm\">\n <div class=\"card-body text-center py-5\">\n <i class=\"fas fa-tags fa-3x text-muted mb-3 d-block\"></i>\n <p class=\"text-muted mb-0\">\n {{ (showDeleted() ? '::DocumentType:RecycleBinEmpty' : '::DocumentType:Empty') | abpLocalization }}\n </p>\n </div>\n </div>\n }\n\n <!-- Types table -->\n @if (!isLoading() && types().totalCount > 0) {\n <div class=\"card shadow-sm\">\n <div class=\"card-body p-0\">\n <ng-template #actionsTemplate let-type>\n @if (showDeleted()) {\n @if (canManage) {\n <div ngbDropdown container=\"body\" class=\"d-inline-block\">\n <button type=\"button\" class=\"btn btn-sm btn-primary\" ngbDropdownToggle>\n {{ 'AbpUi::Actions' | abpLocalization }}\n </button>\n <div ngbDropdownMenu>\n <button type=\"button\" ngbDropdownItem (click)=\"restore(type)\">\n <i class=\"fas fa-rotate-left me-2\"></i>\n {{ '::DocumentType:Restore' | abpLocalization }}\n </button>\n </div>\n </div>\n }\n } @else {\n <div ngbDropdown container=\"body\" class=\"d-inline-block\">\n <button type=\"button\" class=\"btn btn-sm btn-primary\" ngbDropdownToggle>\n {{ 'AbpUi::Actions' | abpLocalization }}\n </button>\n <div ngbDropdownMenu>\n <button type=\"button\" ngbDropdownItem (click)=\"manageFields(type)\">\n <i class=\"fas fa-list-ul me-2\"></i>\n {{ '::DocumentType:ManageFields' | abpLocalization }}\n </button>\n @if (canManage) {\n <button type=\"button\" ngbDropdownItem (click)=\"openEdit(type)\">\n <i class=\"fas fa-pen me-2\"></i>\n {{ '::Edit' | abpLocalization }}\n </button>\n <button type=\"button\" ngbDropdownItem (click)=\"delete(type)\">\n <i class=\"fas fa-trash me-2\"></i>\n {{ '::Delete' | abpLocalization }}\n </button>\n }\n @if (canReextractFields || canReclassify) {\n <div class=\"dropdown-divider\"></div>\n }\n @if (canReextractFields) {\n <button type=\"button\" ngbDropdownItem (click)=\"openReextractFields(type)\">\n <i class=\"fas fa-wand-magic-sparkles me-2\"></i>\n {{ '::Document:Reprocess:FieldExtraction:Action' | abpLocalization }}\n </button>\n }\n @if (canReclassify) {\n <button type=\"button\" ngbDropdownItem class=\"text-danger\" (click)=\"openReclassify(type)\">\n <i class=\"fas fa-shuffle me-2\"></i>\n {{ '::Document:Reprocess:Reclassification:Action' | abpLocalization }}\n </button>\n }\n </div>\n </div>\n }\n </ng-template>\n\n <abp-extensible-table\n [data]=\"types().items\"\n [recordsTotal]=\"types().totalCount\"\n [list]=\"list\"\n [actionsTemplate]=\"actionsTemplate\"\n actionsText=\"AbpUi::Actions\"\n [actionsColumnWidth]=\"150\"\n />\n </div>\n </div>\n }\n</div>\n\n<!-- Create / Edit modal -->\n@if (editing(); as mode) {\n <div\n class=\"modal d-block\"\n tabindex=\"-1\"\n style=\"background:rgba(0,0,0,.4);\"\n (mousedown)=\"onBackdropMouseDown($event)\"\n (click)=\"onBackdropClick($event)\"\n >\n <div class=\"modal-dialog modal-dialog-centered\">\n <div class=\"modal-content\">\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\">\n <div class=\"modal-header\">\n <h5 class=\"modal-title\">\n <i class=\"fas fa-tags me-2\"></i>\n {{ (mode === 'create' ? '::DocumentType:New' : '::DocumentType:Edit') | abpLocalization }}\n </h5>\n <button type=\"button\" class=\"btn-close\" (click)=\"closeModal()\"></button>\n </div>\n <div class=\"modal-body\">\n <div class=\"mb-3\">\n <label class=\"form-label\">{{ '::DocumentType:DisplayName' | abpLocalization }}</label>\n <input\n type=\"text\"\n class=\"form-control\"\n formControlName=\"displayName\"\n (blur)=\"onDisplayNameBlur()\"\n [class.is-invalid]=\"form.controls.displayName.touched && form.controls.displayName.invalid\"\n />\n </div>\n <div class=\"mb-3\">\n <label class=\"form-label d-flex align-items-center gap-2\">\n <span>{{ '::DocumentType:TypeCode' | abpLocalization }}</span>\n @if (isSuggesting()) {\n <span class=\"text-muted small fw-normal\">\n <span class=\"spinner-border spinner-border-sm me-1\" style=\"width:.75rem;height:.75rem;\"></span>\n {{ '::DocumentType:TypeCodeSuggesting' | abpLocalization }}\n </span>\n }\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n formControlName=\"typeCode\"\n [class.is-invalid]=\"form.controls.typeCode.touched && form.controls.typeCode.invalid\"\n placeholder=\"host.contract\"\n />\n <div class=\"form-text\">{{ '::DocumentType:TypeCodeHint' | abpLocalization }}</div>\n @if (form.controls.typeCode.touched && form.controls.typeCode.invalid) {\n <div class=\"invalid-feedback d-block\">{{ '::DocumentType:TypeCodeInvalid' | abpLocalization }}</div>\n }\n </div>\n <div class=\"mb-3\">\n <label class=\"form-label\">{{ '::DocumentType:Description' | abpLocalization }}</label>\n <textarea\n class=\"form-control\"\n formControlName=\"description\"\n rows=\"3\"\n [class.is-invalid]=\"form.controls.description.touched && form.controls.description.invalid\"\n ></textarea>\n <div class=\"form-text\">{{ '::DocumentType:DescriptionHint' | abpLocalization }}</div>\n </div>\n <div class=\"row g-3\">\n <div class=\"col-6\">\n <label class=\"form-label\">{{ '::DocumentType:ConfidenceThreshold' | abpLocalization }}</label>\n <input\n type=\"number\"\n class=\"form-control\"\n formControlName=\"confidenceThreshold\"\n min=\"0\"\n max=\"1\"\n step=\"0.05\"\n [class.is-invalid]=\"form.controls.confidenceThreshold.touched && form.controls.confidenceThreshold.invalid\"\n />\n <div class=\"form-text\">{{ '::DocumentType:ConfidenceThresholdHint' | abpLocalization }}</div>\n </div>\n <div class=\"col-6\">\n <label class=\"form-label\">{{ '::DocumentType:Priority' | abpLocalization }}</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"priority\" step=\"1\" />\n </div>\n </div>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"closeModal()\">\n {{ '::Cancel' | abpLocalization }}\n </button>\n <button type=\"submit\" class=\"btn btn-primary\" [disabled]=\"form.invalid || isSubmitting()\">\n @if (isSubmitting()) {\n <span class=\"spinner-border spinner-border-sm me-1\"></span>\n }\n {{ '::Save' | abpLocalization }}\n </button>\n </div>\n </form>\n </div>\n </div>\n </div>\n}\n\n<!-- Bulk field re-extraction modal (#289) -->\n@if (reextractTarget(); as t) {\n <lib-field-reextraction-modal\n [documentTypeId]=\"t.id!\"\n [documentTypeDisplayName]=\"t.displayName ?? ''\"\n (closed)=\"reextractTarget.set(null)\"\n />\n}\n\n<!-- Bulk reclassification modal (#289) -->\n@if (reclassifyTarget(); as t) {\n <lib-reclassification-modal\n [documentTypeId]=\"t.id!\"\n [documentTypeDisplayName]=\"t.displayName ?? ''\"\n (closed)=\"reclassifyTarget.set(null)\"\n />\n}\n", styles: ["tbody tr:hover{background-color:#00000006}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: RouterModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.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: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: ExtensibleTableComponent, selector: "abp-extensible-table", inputs: ["actionsText", "data", "list", "recordsTotal", "actionsColumnWidth", "actionsTemplate", "selectable", "selectionType", "selected", "infiniteScroll", "isLoading", "scrollThreshold", "tableHeight", "rowDetailTemplate", "rowDetailHeight"], outputs: ["tableActivate", "selectionChange", "loadMore", "rowDetailToggle"], exportAs: ["abpExtensibleTable"] }, { kind: "ngmodule", type: NgbDropdownModule }, { kind: "directive", type: i2$1.NgbDropdown, selector: "[ngbDropdown]", inputs: ["autoClose", "dropdownClass", "open", "placement", "popperOptions", "container", "display"], outputs: ["openChange"], exportAs: ["ngbDropdown"] }, { kind: "directive", type: i2$1.NgbDropdownToggle, selector: "[ngbDropdownToggle]" }, { kind: "directive", type: i2$1.NgbDropdownMenu, selector: "[ngbDropdownMenu]" }, { kind: "directive", type: i2$1.NgbDropdownItem, selector: "[ngbDropdownItem]", inputs: ["tabindex", "disabled"] }, { kind: "directive", type: i2$1.NgbDropdownButtonItem, selector: "button[ngbDropdownItem]" }, { kind: "component", type: FieldReextractionModalComponent, selector: "lib-field-reextraction-modal", inputs: ["documentTypeId", "documentTypeDisplayName"], outputs: ["closed"] }, { kind: "component", type: ReclassificationModalComponent, selector: "lib-reclassification-modal", inputs: ["documentTypeId", "documentTypeDisplayName"], outputs: ["closed"] }, { kind: "pipe", type: LocalizationPipe, name: "abpLocalization" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
442
+ }
443
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DocumentTypeListComponent, decorators: [{
444
+ type: Component,
445
+ args: [{ selector: 'lib-document-type-list', imports: [
446
+ CommonModule,
447
+ RouterModule,
448
+ ReactiveFormsModule,
449
+ LocalizationPipe,
450
+ ExtensibleTableComponent,
451
+ NgbDropdownModule,
452
+ FieldReextractionModalComponent,
453
+ ReclassificationModalComponent,
454
+ ], providers: [
455
+ ListService,
456
+ {
457
+ provide: EXTENSIONS_IDENTIFIER,
458
+ useValue: EXTRACT_TABLES.DocumentTypes,
459
+ },
460
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"container-fluid py-4\">\n <!-- Header -->\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <h4 class=\"mb-0\">\n <i class=\"fas fa-tags me-2\"></i>\n {{ '::DocumentType:Title' | abpLocalization }}\n </h4>\n <div class=\"d-flex gap-2\">\n <button\n class=\"btn btn-outline-secondary\"\n (click)=\"refresh()\"\n [disabled]=\"isLoading()\"\n title=\"{{ '::Refresh' | abpLocalization }}\"\n >\n <i class=\"fas fa-sync-alt\" [class.fa-spin]=\"isLoading()\"></i>\n </button>\n <button\n class=\"btn\"\n [class.btn-secondary]=\"showDeleted()\"\n [class.btn-outline-secondary]=\"!showDeleted()\"\n (click)=\"toggleDeleted()\"\n >\n <i class=\"fas fa-trash-can me-1\"></i>\n {{ '::DocumentType:ShowDeleted' | abpLocalization }}\n </button>\n @if (canManage && !showDeleted()) {\n <button class=\"btn btn-primary\" (click)=\"openCreate()\">\n <i class=\"fas fa-plus me-1\"></i>\n {{ '::DocumentType:New' | abpLocalization }}\n </button>\n }\n </div>\n </div>\n\n <!-- Loading spinner -->\n @if (isLoading()) {\n <div class=\"text-center py-5\">\n <div class=\"spinner-border text-primary\" role=\"status\"></div>\n </div>\n }\n\n <!-- Empty state -->\n @if (!isLoading() && types().totalCount === 0) {\n <div class=\"card shadow-sm\">\n <div class=\"card-body text-center py-5\">\n <i class=\"fas fa-tags fa-3x text-muted mb-3 d-block\"></i>\n <p class=\"text-muted mb-0\">\n {{ (showDeleted() ? '::DocumentType:RecycleBinEmpty' : '::DocumentType:Empty') | abpLocalization }}\n </p>\n </div>\n </div>\n }\n\n <!-- Types table -->\n @if (!isLoading() && types().totalCount > 0) {\n <div class=\"card shadow-sm\">\n <div class=\"card-body p-0\">\n <ng-template #actionsTemplate let-type>\n @if (showDeleted()) {\n @if (canManage) {\n <div ngbDropdown container=\"body\" class=\"d-inline-block\">\n <button type=\"button\" class=\"btn btn-sm btn-primary\" ngbDropdownToggle>\n {{ 'AbpUi::Actions' | abpLocalization }}\n </button>\n <div ngbDropdownMenu>\n <button type=\"button\" ngbDropdownItem (click)=\"restore(type)\">\n <i class=\"fas fa-rotate-left me-2\"></i>\n {{ '::DocumentType:Restore' | abpLocalization }}\n </button>\n </div>\n </div>\n }\n } @else {\n <div ngbDropdown container=\"body\" class=\"d-inline-block\">\n <button type=\"button\" class=\"btn btn-sm btn-primary\" ngbDropdownToggle>\n {{ 'AbpUi::Actions' | abpLocalization }}\n </button>\n <div ngbDropdownMenu>\n <button type=\"button\" ngbDropdownItem (click)=\"manageFields(type)\">\n <i class=\"fas fa-list-ul me-2\"></i>\n {{ '::DocumentType:ManageFields' | abpLocalization }}\n </button>\n @if (canManage) {\n <button type=\"button\" ngbDropdownItem (click)=\"openEdit(type)\">\n <i class=\"fas fa-pen me-2\"></i>\n {{ '::Edit' | abpLocalization }}\n </button>\n <button type=\"button\" ngbDropdownItem (click)=\"delete(type)\">\n <i class=\"fas fa-trash me-2\"></i>\n {{ '::Delete' | abpLocalization }}\n </button>\n }\n @if (canReextractFields || canReclassify) {\n <div class=\"dropdown-divider\"></div>\n }\n @if (canReextractFields) {\n <button type=\"button\" ngbDropdownItem (click)=\"openReextractFields(type)\">\n <i class=\"fas fa-wand-magic-sparkles me-2\"></i>\n {{ '::Document:Reprocess:FieldExtraction:Action' | abpLocalization }}\n </button>\n }\n @if (canReclassify) {\n <button type=\"button\" ngbDropdownItem class=\"text-danger\" (click)=\"openReclassify(type)\">\n <i class=\"fas fa-shuffle me-2\"></i>\n {{ '::Document:Reprocess:Reclassification:Action' | abpLocalization }}\n </button>\n }\n </div>\n </div>\n }\n </ng-template>\n\n <abp-extensible-table\n [data]=\"types().items\"\n [recordsTotal]=\"types().totalCount\"\n [list]=\"list\"\n [actionsTemplate]=\"actionsTemplate\"\n actionsText=\"AbpUi::Actions\"\n [actionsColumnWidth]=\"150\"\n />\n </div>\n </div>\n }\n</div>\n\n<!-- Create / Edit modal -->\n@if (editing(); as mode) {\n <div\n class=\"modal d-block\"\n tabindex=\"-1\"\n style=\"background:rgba(0,0,0,.4);\"\n (mousedown)=\"onBackdropMouseDown($event)\"\n (click)=\"onBackdropClick($event)\"\n >\n <div class=\"modal-dialog modal-dialog-centered\">\n <div class=\"modal-content\">\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\">\n <div class=\"modal-header\">\n <h5 class=\"modal-title\">\n <i class=\"fas fa-tags me-2\"></i>\n {{ (mode === 'create' ? '::DocumentType:New' : '::DocumentType:Edit') | abpLocalization }}\n </h5>\n <button type=\"button\" class=\"btn-close\" (click)=\"closeModal()\"></button>\n </div>\n <div class=\"modal-body\">\n <div class=\"mb-3\">\n <label class=\"form-label\">{{ '::DocumentType:DisplayName' | abpLocalization }}</label>\n <input\n type=\"text\"\n class=\"form-control\"\n formControlName=\"displayName\"\n (blur)=\"onDisplayNameBlur()\"\n [class.is-invalid]=\"form.controls.displayName.touched && form.controls.displayName.invalid\"\n />\n </div>\n <div class=\"mb-3\">\n <label class=\"form-label d-flex align-items-center gap-2\">\n <span>{{ '::DocumentType:TypeCode' | abpLocalization }}</span>\n @if (isSuggesting()) {\n <span class=\"text-muted small fw-normal\">\n <span class=\"spinner-border spinner-border-sm me-1\" style=\"width:.75rem;height:.75rem;\"></span>\n {{ '::DocumentType:TypeCodeSuggesting' | abpLocalization }}\n </span>\n }\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n formControlName=\"typeCode\"\n [class.is-invalid]=\"form.controls.typeCode.touched && form.controls.typeCode.invalid\"\n placeholder=\"host.contract\"\n />\n <div class=\"form-text\">{{ '::DocumentType:TypeCodeHint' | abpLocalization }}</div>\n @if (form.controls.typeCode.touched && form.controls.typeCode.invalid) {\n <div class=\"invalid-feedback d-block\">{{ '::DocumentType:TypeCodeInvalid' | abpLocalization }}</div>\n }\n </div>\n <div class=\"mb-3\">\n <label class=\"form-label\">{{ '::DocumentType:Description' | abpLocalization }}</label>\n <textarea\n class=\"form-control\"\n formControlName=\"description\"\n rows=\"3\"\n [class.is-invalid]=\"form.controls.description.touched && form.controls.description.invalid\"\n ></textarea>\n <div class=\"form-text\">{{ '::DocumentType:DescriptionHint' | abpLocalization }}</div>\n </div>\n <div class=\"row g-3\">\n <div class=\"col-6\">\n <label class=\"form-label\">{{ '::DocumentType:ConfidenceThreshold' | abpLocalization }}</label>\n <input\n type=\"number\"\n class=\"form-control\"\n formControlName=\"confidenceThreshold\"\n min=\"0\"\n max=\"1\"\n step=\"0.05\"\n [class.is-invalid]=\"form.controls.confidenceThreshold.touched && form.controls.confidenceThreshold.invalid\"\n />\n <div class=\"form-text\">{{ '::DocumentType:ConfidenceThresholdHint' | abpLocalization }}</div>\n </div>\n <div class=\"col-6\">\n <label class=\"form-label\">{{ '::DocumentType:Priority' | abpLocalization }}</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"priority\" step=\"1\" />\n </div>\n </div>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"closeModal()\">\n {{ '::Cancel' | abpLocalization }}\n </button>\n <button type=\"submit\" class=\"btn btn-primary\" [disabled]=\"form.invalid || isSubmitting()\">\n @if (isSubmitting()) {\n <span class=\"spinner-border spinner-border-sm me-1\"></span>\n }\n {{ '::Save' | abpLocalization }}\n </button>\n </div>\n </form>\n </div>\n </div>\n </div>\n}\n\n<!-- Bulk field re-extraction modal (#289) -->\n@if (reextractTarget(); as t) {\n <lib-field-reextraction-modal\n [documentTypeId]=\"t.id!\"\n [documentTypeDisplayName]=\"t.displayName ?? ''\"\n (closed)=\"reextractTarget.set(null)\"\n />\n}\n\n<!-- Bulk reclassification modal (#289) -->\n@if (reclassifyTarget(); as t) {\n <lib-reclassification-modal\n [documentTypeId]=\"t.id!\"\n [documentTypeDisplayName]=\"t.displayName ?? ''\"\n (closed)=\"reclassifyTarget.set(null)\"\n />\n}\n", styles: ["tbody tr:hover{background-color:#00000006}\n"] }]
461
+ }], ctorParameters: () => [] });
462
+
463
+ export { DocumentTypeListComponent };
464
+ //# sourceMappingURL=dignite-vault-extract-documents-document-type-list.component-C8kXFJGb.mjs.map