ngx-thin-admin 0.0.0-alpha.1 → 0.0.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/ngx-thin-admin.mjs +730 -76
- package/fesm2022/ngx-thin-admin.mjs.map +1 -1
- package/package.json +1 -1
- package/types/ngx-thin-admin.d.ts +215 -34
|
@@ -1,35 +1,190 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, Component, EventEmitter, ChangeDetectorRef, Output, Input, Injectable, ViewChild } from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, Component, EventEmitter, ChangeDetectorRef, Output, Input, Injectable, ViewChild } from '@angular/core';
|
|
3
3
|
import { lastValueFrom, Subject } from 'rxjs';
|
|
4
4
|
import { debounceTime } from 'rxjs/operators';
|
|
5
5
|
import * as i4 from '@angular/material/button';
|
|
6
6
|
import { MatButtonModule, MatIconButton, MatButton } from '@angular/material/button';
|
|
7
|
-
import {
|
|
8
|
-
import * as
|
|
7
|
+
import { MatSnackBar } from '@angular/material/snack-bar';
|
|
8
|
+
import * as i6 from '@angular/material/icon';
|
|
9
|
+
import { MatIcon, MatIconModule } from '@angular/material/icon';
|
|
10
|
+
import * as i2 from '@angular/material/input';
|
|
9
11
|
import { MatInputModule, MatInput, MatPrefix, MatSuffix } from '@angular/material/input';
|
|
10
|
-
import
|
|
12
|
+
import * as i2$1 from '@angular/material/select';
|
|
13
|
+
import { MatSelectModule, MatFormField } from '@angular/material/select';
|
|
11
14
|
import { MatTooltip } from '@angular/material/tooltip';
|
|
12
15
|
import * as i2$2 from '@ng-matero/extensions/grid';
|
|
13
16
|
import { MtxGridModule } from '@ng-matero/extensions/grid';
|
|
14
|
-
import * as
|
|
17
|
+
import * as i3 from '@angular/forms';
|
|
15
18
|
import { FormsModule, ReactiveFormsModule, FormGroup } from '@angular/forms';
|
|
16
19
|
import { MatSelectionList, MatListOption } from '@angular/material/list';
|
|
17
20
|
import { MatMenu, MatMenuItem, MatMenuTrigger, MatMenuContent } from '@angular/material/menu';
|
|
18
|
-
import { MatSnackBar } from '@angular/material/snack-bar';
|
|
19
21
|
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose, MatDialog } from '@angular/material/dialog';
|
|
20
|
-
import * as
|
|
22
|
+
import * as i1 from '@angular/material/form-field';
|
|
21
23
|
import { MatFormFieldModule, MatFormField as MatFormField$1, MatLabel, MatHint, MatSuffix as MatSuffix$1, MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
|
|
22
24
|
import { stringify } from 'csv-stringify/browser/esm/sync';
|
|
23
|
-
import { RouterLink } from '@angular/router';
|
|
25
|
+
import { RouterLink, Router } from '@angular/router';
|
|
26
|
+
import * as i5 from '@angular/material/progress-spinner';
|
|
27
|
+
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
|
24
28
|
import { MatCard, MatCardHeader, MatCardContent } from '@angular/material/card';
|
|
25
|
-
import { FieldWrapper, FieldType, FormlyAttributes, FormlyForm, provideFormlyCore } from '@ngx-formly/core';
|
|
29
|
+
import { FieldWrapper, FieldType, FormlyAttributes, FormlyForm, FORMLY_CONFIG, provideFormlyCore } from '@ngx-formly/core';
|
|
26
30
|
import { withFormlyMaterial } from '@ngx-formly/material';
|
|
27
31
|
import { MatProgressBar } from '@angular/material/progress-bar';
|
|
32
|
+
import * as i2$3 from '@angular/common';
|
|
33
|
+
import { CommonModule } from '@angular/common';
|
|
34
|
+
|
|
35
|
+
const messagesEn = {
|
|
36
|
+
'common.cancel': 'Cancel',
|
|
37
|
+
'common.ok': 'OK',
|
|
38
|
+
'common.save': 'Save',
|
|
39
|
+
'common.create': 'Create',
|
|
40
|
+
'common.edit': 'Edit',
|
|
41
|
+
'common.delete': 'Delete',
|
|
42
|
+
'common.back': 'Back',
|
|
43
|
+
'list.defaultTitle': 'List',
|
|
44
|
+
'list.clearFilters': 'Clear filters',
|
|
45
|
+
'list.searchDefaultPlaceholder': 'Search...',
|
|
46
|
+
'list.searchInCategoryPlaceholder': 'Search in {category}...',
|
|
47
|
+
'list.clearSearchKeyword': 'Clear search keyword',
|
|
48
|
+
'list.refresh': 'Refresh',
|
|
49
|
+
'list.exportAsCsv': 'Export as CSV',
|
|
50
|
+
'list.exportedFileName': 'exported',
|
|
51
|
+
'list.exportItems': 'Export {count} items',
|
|
52
|
+
'editor.titleForCreate': 'Create {itemType}',
|
|
53
|
+
'editor.titleForEdit': 'Edit {itemType}',
|
|
54
|
+
'editor.idLabel': 'ID',
|
|
55
|
+
'editor.saveForCreate': 'Save',
|
|
56
|
+
'editor.saveForEdit': 'Save Changes',
|
|
57
|
+
'editor.errorInvalidInput': 'Invalid input',
|
|
58
|
+
'editor.errorNoSaver': 'No saver function provided',
|
|
59
|
+
'editor.successSaved': 'Data saved successfully',
|
|
60
|
+
'editor.backToList': 'To List',
|
|
61
|
+
'category.defaultSingular': 'Category',
|
|
62
|
+
'category.defaultPlural': 'Categories',
|
|
63
|
+
'category.all': 'All',
|
|
64
|
+
'category.editorDialogTitleForCreate': 'Create {categoryType}',
|
|
65
|
+
'category.editorDialogTitleForEdit': 'Edit {categoryType}',
|
|
66
|
+
'category.deletionDialogTitle': 'Delete {categoryType}',
|
|
67
|
+
'category.deletionDialogMessage': 'Are you sure you want to delete the {categoryType} "{label}"? This action cannot be undone.',
|
|
68
|
+
'category.errorCouldNotCreate': 'Could not create {categoryType}',
|
|
69
|
+
'category.errorFailedUpdate': 'Failed to update {categoryType} "{label}"',
|
|
70
|
+
'category.errorFailedDelete': 'Failed to delete {categoryType} "{label}"',
|
|
71
|
+
'category.errorCouldNotSave': 'Could not save {categoryType}',
|
|
72
|
+
'category.successCreated': '{categoryType} "{label}" has been created successfully',
|
|
73
|
+
'category.successUpdated': '{categoryType} "{label}" has been updated successfully',
|
|
74
|
+
'category.successDeleted': '{categoryType} "{label}" has been deleted successfully',
|
|
75
|
+
'item.defaultSingular': 'Item',
|
|
76
|
+
'item.deletionDialogTitle': 'Delete {itemType}',
|
|
77
|
+
'item.deletionDialogMessage': 'Are you sure you want to delete "{label}" ({id})? This action cannot be undone.',
|
|
78
|
+
'item.errorFailedDelete': 'Failed to delete "{label}"',
|
|
79
|
+
'item.successDeleted': '"{label}" has been deleted successfully',
|
|
80
|
+
'item.changeCategory': 'Change {categoryType}',
|
|
81
|
+
'item.changeCategoryDialogTitle': 'Change {categoryType}',
|
|
82
|
+
'item.changeCategoryDialogMessage': 'Select a {categoryType} to move "{label}" to.',
|
|
83
|
+
'item.successCategoryChanged': '{categoryType} changed successfully',
|
|
84
|
+
'item.errorFailedCategoryChange': 'Failed to change {categoryType} for "{label}"',
|
|
85
|
+
'category.unselected': 'Uncategorized',
|
|
86
|
+
'csv.exporting': 'Exporting CSV...',
|
|
87
|
+
'csv.exportCompleted': 'CSV export completed. Exported {count} items.',
|
|
88
|
+
'csv.exportCompletedWithErrors': 'CSV exported up to {count} items, but errors occurred: {error}',
|
|
89
|
+
'csv.errorGenerateFailed': 'Failed to generate CSV file.',
|
|
90
|
+
};
|
|
91
|
+
const messagesJa = {
|
|
92
|
+
'common.cancel': 'キャンセル',
|
|
93
|
+
'common.ok': 'OK',
|
|
94
|
+
'common.save': '保存',
|
|
95
|
+
'common.create': '作成',
|
|
96
|
+
'common.edit': '編集',
|
|
97
|
+
'common.delete': '削除',
|
|
98
|
+
'common.back': '戻る',
|
|
99
|
+
'list.defaultTitle': '一覧',
|
|
100
|
+
'list.clearFilters': 'フィルターをクリア',
|
|
101
|
+
'list.searchDefaultPlaceholder': '検索...',
|
|
102
|
+
'list.searchInCategoryPlaceholder': '{category} 内を検索...',
|
|
103
|
+
'list.clearSearchKeyword': '検索キーワードをクリア',
|
|
104
|
+
'list.refresh': '再読み込み',
|
|
105
|
+
'list.exportAsCsv': 'CSV でエクスポート',
|
|
106
|
+
'list.exportedFileName': 'exported',
|
|
107
|
+
'list.exportItems': '{count} 件をエクスポート',
|
|
108
|
+
'editor.titleForCreate': '{itemType}の作成',
|
|
109
|
+
'editor.titleForEdit': '{itemType}の編集',
|
|
110
|
+
'editor.idLabel': 'ID',
|
|
111
|
+
'editor.saveForCreate': '作成',
|
|
112
|
+
'editor.saveForEdit': '変更を保存',
|
|
113
|
+
'editor.errorInvalidInput': '入力内容が不正です',
|
|
114
|
+
'editor.errorNoSaver': '保存処理が設定されていません',
|
|
115
|
+
'editor.successSaved': '保存しました',
|
|
116
|
+
'editor.backToList': '一覧へ',
|
|
117
|
+
'category.defaultSingular': 'カテゴリ',
|
|
118
|
+
'category.defaultPlural': 'カテゴリ',
|
|
119
|
+
'category.all': 'すべて',
|
|
120
|
+
'category.editorDialogTitleForCreate': '{categoryType}の作成',
|
|
121
|
+
'category.editorDialogTitleForEdit': '{categoryType}の編集',
|
|
122
|
+
'category.deletionDialogTitle': '{categoryType}の削除',
|
|
123
|
+
'category.deletionDialogMessage': '{categoryType} "{label}" を削除しますか?この操作は元に戻せません。',
|
|
124
|
+
'category.errorCouldNotCreate': '{categoryType} を作成できませんでした',
|
|
125
|
+
'category.errorFailedUpdate': '{categoryType} "{label}" の更新に失敗しました',
|
|
126
|
+
'category.errorFailedDelete': '{categoryType} "{label}" の削除に失敗しました',
|
|
127
|
+
'category.errorCouldNotSave': '{categoryType} を保存できませんでした',
|
|
128
|
+
'category.successCreated': '{categoryType} "{label}" を作成しました',
|
|
129
|
+
'category.successUpdated': '{categoryType} "{label}" を更新しました',
|
|
130
|
+
'category.successDeleted': '{categoryType} "{label}" を削除しました',
|
|
131
|
+
'item.defaultSingular': '項目',
|
|
132
|
+
'item.deletionDialogTitle': '{itemType}の削除',
|
|
133
|
+
'item.deletionDialogMessage': '"{label}" ({id}) を削除しますか?この操作は元に戻せません。',
|
|
134
|
+
'item.errorFailedDelete': '"{label}" の削除に失敗しました',
|
|
135
|
+
'item.successDeleted': '"{label}" を削除しました',
|
|
136
|
+
'item.changeCategory': '{categoryType}を変更',
|
|
137
|
+
'item.changeCategoryDialogTitle': '{categoryType}の変更',
|
|
138
|
+
'item.changeCategoryDialogMessage': '"{label}" の移動先を選択してください',
|
|
139
|
+
'item.successCategoryChanged': '{categoryType}を変更しました',
|
|
140
|
+
'item.errorFailedCategoryChange': '「{label}」の{categoryType}変更に失敗しました',
|
|
141
|
+
'category.unselected': '未分類',
|
|
142
|
+
'csv.exporting': 'CSV をエクスポート中...',
|
|
143
|
+
'csv.exportCompleted': 'CSV エクスポートが完了しました。{count} 件をエクスポートしました。',
|
|
144
|
+
'csv.exportCompletedWithErrors': '{count} 件までエクスポートしましたが、エラーが発生しました: {error}',
|
|
145
|
+
'csv.errorGenerateFailed': 'CSV ファイルの生成に失敗しました。',
|
|
146
|
+
};
|
|
147
|
+
function interpolate(message, params) {
|
|
148
|
+
if (!params) {
|
|
149
|
+
return message;
|
|
150
|
+
}
|
|
151
|
+
return message.replace(/\{(\w+)\}/g, (_, key) => {
|
|
152
|
+
const value = params[key];
|
|
153
|
+
return value === undefined ? '' : String(value);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function createTranslatorFromMessages(baseMessages, overrides) {
|
|
157
|
+
const merged = { ...baseMessages, ...overrides };
|
|
158
|
+
return (key, params) => {
|
|
159
|
+
return interpolate(merged[key] ?? messagesEn[key], params);
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const NGX_THIN_ADMIN_TRANSLATE = new InjectionToken('NGX_THIN_ADMIN_TRANSLATE', {
|
|
163
|
+
providedIn: 'root',
|
|
164
|
+
factory: () => createTranslatorFromMessages(messagesEn),
|
|
165
|
+
});
|
|
166
|
+
function provideNgxThinAdminI18n(config) {
|
|
167
|
+
return {
|
|
168
|
+
provide: NGX_THIN_ADMIN_TRANSLATE,
|
|
169
|
+
useFactory: () => {
|
|
170
|
+
if (config?.translate) {
|
|
171
|
+
return config.translate;
|
|
172
|
+
}
|
|
173
|
+
const locale = config?.locale ?? 'en';
|
|
174
|
+
const base = locale === 'ja' ? messagesJa : messagesEn;
|
|
175
|
+
return createTranslatorFromMessages(base, config?.messages);
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
28
179
|
|
|
29
180
|
class CategoryEditorDialog {
|
|
30
181
|
dialogRef = inject((MatDialogRef));
|
|
31
182
|
data = inject(MAT_DIALOG_DATA);
|
|
183
|
+
translate = inject(NGX_THIN_ADMIN_TRANSLATE);
|
|
32
184
|
categoryLabel;
|
|
185
|
+
t(key, params) {
|
|
186
|
+
return this.translate(key, params);
|
|
187
|
+
}
|
|
33
188
|
save() {
|
|
34
189
|
if (!this.categoryLabel) {
|
|
35
190
|
return;
|
|
@@ -39,7 +194,7 @@ class CategoryEditorDialog {
|
|
|
39
194
|
});
|
|
40
195
|
}
|
|
41
196
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: CategoryEditorDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
42
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: CategoryEditorDialog, isStandalone: true, selector: "lib-category-editor-dialog", ngImport: i0, template: "<h2 mat-dialog-title>\n @if (data.editCategory) {\n
|
|
197
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: CategoryEditorDialog, isStandalone: true, selector: "lib-category-editor-dialog", ngImport: i0, template: "<h2 mat-dialog-title>\n @if (data.editCategory) {\n {{\n t('category.editorDialogTitleForEdit', {\n categoryType: data.config.singularLabel ?? t('category.defaultSingular'),\n })\n }}\n } @else {\n {{\n t('category.editorDialogTitleForCreate', {\n categoryType: data.config.singularLabel ?? t('category.defaultSingular'),\n })\n }}\n }\n</h2>\n<form (ngSubmit)=\"save()\">\n <mat-dialog-content>\n <mat-form-field>\n <mat-label>{{ data.config.exampleLabel ?? data.config.singularLabel ?? '' }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"categoryLabel\"\n name=\"categoryLabel\"\n cdkFocusInitial\n [value]=\"data.editCategory ? data.editCategory.label : ''\"\n />\n </mat-form-field>\n </mat-dialog-content>\n <mat-dialog-actions>\n <button type=\"button\" class=\"cancel-btn\" matButton [mat-dialog-close]=\"undefined\">\n {{ data.cancelButtonText ?? t('common.cancel') }}\n </button>\n <button type=\"submit\" matButton>{{ data.positiveButtonText ?? t('common.save') }}</button>\n </mat-dialog-actions>\n</form>\n", styles: [".cancel-btn{--mat-button-text-label-text-color: #333}\n"], dependencies: [{ kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i1.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i2.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3.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: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i3.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }] });
|
|
43
198
|
}
|
|
44
199
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: CategoryEditorDialog, decorators: [{
|
|
45
200
|
type: Component,
|
|
@@ -52,14 +207,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
52
207
|
MatDialogContent,
|
|
53
208
|
MatDialogActions,
|
|
54
209
|
MatDialogClose,
|
|
55
|
-
], template: "<h2 mat-dialog-title>\n @if (data.editCategory) {\n
|
|
210
|
+
], template: "<h2 mat-dialog-title>\n @if (data.editCategory) {\n {{\n t('category.editorDialogTitleForEdit', {\n categoryType: data.config.singularLabel ?? t('category.defaultSingular'),\n })\n }}\n } @else {\n {{\n t('category.editorDialogTitleForCreate', {\n categoryType: data.config.singularLabel ?? t('category.defaultSingular'),\n })\n }}\n }\n</h2>\n<form (ngSubmit)=\"save()\">\n <mat-dialog-content>\n <mat-form-field>\n <mat-label>{{ data.config.exampleLabel ?? data.config.singularLabel ?? '' }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"categoryLabel\"\n name=\"categoryLabel\"\n cdkFocusInitial\n [value]=\"data.editCategory ? data.editCategory.label : ''\"\n />\n </mat-form-field>\n </mat-dialog-content>\n <mat-dialog-actions>\n <button type=\"button\" class=\"cancel-btn\" matButton [mat-dialog-close]=\"undefined\">\n {{ data.cancelButtonText ?? t('common.cancel') }}\n </button>\n <button type=\"submit\" matButton>{{ data.positiveButtonText ?? t('common.save') }}</button>\n </mat-dialog-actions>\n</form>\n", styles: [".cancel-btn{--mat-button-text-label-text-color: #333}\n"] }]
|
|
56
211
|
}] });
|
|
57
212
|
|
|
58
213
|
class ConfirmDialog {
|
|
59
214
|
dialogRef = inject((MatDialogRef));
|
|
60
215
|
data = inject(MAT_DIALOG_DATA);
|
|
216
|
+
translate = inject(NGX_THIN_ADMIN_TRANSLATE);
|
|
217
|
+
t(key, params) {
|
|
218
|
+
return this.translate(key, params);
|
|
219
|
+
}
|
|
61
220
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ConfirmDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
62
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.5", type: ConfirmDialog, isStandalone: true, selector: "lib-confirm-dialog", ngImport: i0, template: "<h2 mat-dialog-title>{{ data.title }}</h2>\n<mat-dialog-content>\n {{ data.message }}\n</mat-dialog-content>\n<mat-dialog-actions>\n <button class=\"cancel-btn\" matButton [mat-dialog-close]=\"undefined\">\n {{ data.cancelButtonText || '
|
|
221
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.5", type: ConfirmDialog, isStandalone: true, selector: "lib-confirm-dialog", ngImport: i0, template: "<h2 mat-dialog-title>{{ data.title }}</h2>\n<mat-dialog-content>\n {{ data.message }}\n</mat-dialog-content>\n<mat-dialog-actions>\n <button class=\"cancel-btn\" matButton [mat-dialog-close]=\"undefined\">\n {{ data.cancelButtonText || t('common.cancel') }}\n </button>\n <button matButton [mat-dialog-close]=\"true\" cdkFocusInitial>\n {{ data.positiveButtonText || t('common.ok') }}\n </button>\n</mat-dialog-actions>\n", styles: [".cancel-btn{--mat-button-text-label-text-color: #333}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }] });
|
|
63
222
|
}
|
|
64
223
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ConfirmDialog, decorators: [{
|
|
65
224
|
type: Component,
|
|
@@ -70,9 +229,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
70
229
|
MatDialogContent,
|
|
71
230
|
MatDialogActions,
|
|
72
231
|
MatDialogClose,
|
|
73
|
-
], template: "<h2 mat-dialog-title>{{ data.title }}</h2>\n<mat-dialog-content>\n {{ data.message }}\n</mat-dialog-content>\n<mat-dialog-actions>\n <button class=\"cancel-btn\" matButton [mat-dialog-close]=\"undefined\">\n {{ data.cancelButtonText || '
|
|
232
|
+
], template: "<h2 mat-dialog-title>{{ data.title }}</h2>\n<mat-dialog-content>\n {{ data.message }}\n</mat-dialog-content>\n<mat-dialog-actions>\n <button class=\"cancel-btn\" matButton [mat-dialog-close]=\"undefined\">\n {{ data.cancelButtonText || t('common.cancel') }}\n </button>\n <button matButton [mat-dialog-close]=\"true\" cdkFocusInitial>\n {{ data.positiveButtonText || t('common.ok') }}\n </button>\n</mat-dialog-actions>\n", styles: [".cancel-btn{--mat-button-text-label-text-color: #333}\n"] }]
|
|
74
233
|
}] });
|
|
75
234
|
|
|
235
|
+
function getErrorMessage(error) {
|
|
236
|
+
if (typeof error === 'string') {
|
|
237
|
+
return error;
|
|
238
|
+
}
|
|
239
|
+
else if (error instanceof Error) {
|
|
240
|
+
return error.message;
|
|
241
|
+
}
|
|
242
|
+
else if (error?.error?.message) {
|
|
243
|
+
if (Array.isArray(error.error.message)) {
|
|
244
|
+
return error.error.message[0];
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
return error.error.message;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
return 'An unknown error occurred';
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
76
255
|
class ListCategorySelector {
|
|
77
256
|
/**
|
|
78
257
|
* Config for the category selector
|
|
@@ -96,6 +275,10 @@ class ListCategorySelector {
|
|
|
96
275
|
snackbar = inject(MatSnackBar);
|
|
97
276
|
dialog = inject(MatDialog);
|
|
98
277
|
cdr = inject(ChangeDetectorRef);
|
|
278
|
+
translate = inject(NGX_THIN_ADMIN_TRANSLATE);
|
|
279
|
+
t(key, params) {
|
|
280
|
+
return this.translate(key, params);
|
|
281
|
+
}
|
|
99
282
|
ngOnChanges(changes) {
|
|
100
283
|
if (changes.config &&
|
|
101
284
|
changes.config.currentValue?.fetcher !== changes.config.previousValue?.fetcher) {
|
|
@@ -127,13 +310,25 @@ class ListCategorySelector {
|
|
|
127
310
|
}
|
|
128
311
|
// Call the category creator function
|
|
129
312
|
try {
|
|
130
|
-
await this.config.creator(categoryLabel);
|
|
313
|
+
const res = await this.config.creator(categoryLabel);
|
|
314
|
+
if (res && 'ok' in res && !res.ok) {
|
|
315
|
+
const errorMessage = getErrorMessage(res);
|
|
316
|
+
throw new Error(errorMessage);
|
|
317
|
+
}
|
|
131
318
|
}
|
|
132
319
|
catch (e) {
|
|
133
|
-
this.snackbar.open(`Error: ${e?.message ?? '
|
|
320
|
+
this.snackbar.open(`Error: ${e?.message ?? this.t('category.errorCouldNotCreate', { categoryType: this.config.singularLabel ?? this.t('category.defaultSingular') })}`, undefined, {
|
|
134
321
|
duration: 3000,
|
|
135
322
|
});
|
|
323
|
+
return;
|
|
136
324
|
}
|
|
325
|
+
// Show success message
|
|
326
|
+
this.snackbar.open(this.t('category.successCreated', {
|
|
327
|
+
categoryType: this.config.singularLabel ?? this.t('category.defaultSingular'),
|
|
328
|
+
label: categoryLabel,
|
|
329
|
+
}), undefined, {
|
|
330
|
+
duration: 3000,
|
|
331
|
+
});
|
|
137
332
|
// Refresh category list
|
|
138
333
|
this.fetchCategories();
|
|
139
334
|
}
|
|
@@ -143,13 +338,29 @@ class ListCategorySelector {
|
|
|
143
338
|
}
|
|
144
339
|
// Call the category updater function
|
|
145
340
|
try {
|
|
146
|
-
await this.config.updater(category);
|
|
341
|
+
const res = await this.config.updater(category);
|
|
342
|
+
if (res && 'ok' in res && !res.ok) {
|
|
343
|
+
const errorMessage = getErrorMessage(res);
|
|
344
|
+
throw new Error(errorMessage);
|
|
345
|
+
}
|
|
147
346
|
}
|
|
148
347
|
catch (e) {
|
|
149
|
-
this.snackbar.open(`Error: ${e?.message ??
|
|
348
|
+
this.snackbar.open(`Error: ${e?.message ??
|
|
349
|
+
this.t('category.errorFailedUpdate', {
|
|
350
|
+
categoryType: this.config.singularLabel ?? this.t('category.defaultSingular'),
|
|
351
|
+
label: category.label,
|
|
352
|
+
})}`, undefined, {
|
|
150
353
|
duration: 3000,
|
|
151
354
|
});
|
|
355
|
+
return;
|
|
152
356
|
}
|
|
357
|
+
// Show success message
|
|
358
|
+
this.snackbar.open(this.t('category.successUpdated', {
|
|
359
|
+
categoryType: this.config.singularLabel ?? this.t('category.defaultSingular'),
|
|
360
|
+
label: category.label,
|
|
361
|
+
}), undefined, {
|
|
362
|
+
duration: 3000,
|
|
363
|
+
});
|
|
153
364
|
// Refresh category list
|
|
154
365
|
this.fetchCategories();
|
|
155
366
|
}
|
|
@@ -159,13 +370,29 @@ class ListCategorySelector {
|
|
|
159
370
|
}
|
|
160
371
|
// Call the category deleter function
|
|
161
372
|
try {
|
|
162
|
-
await this.config.deleter(category);
|
|
373
|
+
const res = await this.config.deleter(category);
|
|
374
|
+
if (res && 'ok' in res && !res.ok) {
|
|
375
|
+
const errorMessage = getErrorMessage(res);
|
|
376
|
+
throw new Error(errorMessage);
|
|
377
|
+
}
|
|
163
378
|
}
|
|
164
379
|
catch (e) {
|
|
165
|
-
this.snackbar.open(`Error: ${e?.message ??
|
|
380
|
+
this.snackbar.open(`Error: ${e?.message ??
|
|
381
|
+
this.t('category.errorFailedDelete', {
|
|
382
|
+
categoryType: this.config.singularLabel ?? this.t('category.defaultSingular'),
|
|
383
|
+
label: category.label,
|
|
384
|
+
})}`, undefined, {
|
|
166
385
|
duration: 3000,
|
|
167
386
|
});
|
|
387
|
+
return;
|
|
168
388
|
}
|
|
389
|
+
// Show success message
|
|
390
|
+
this.snackbar.open(this.t('category.successDeleted', {
|
|
391
|
+
categoryType: this.config.singularLabel ?? this.t('category.defaultSingular'),
|
|
392
|
+
label: category.label,
|
|
393
|
+
}), undefined, {
|
|
394
|
+
duration: 3000,
|
|
395
|
+
});
|
|
169
396
|
// Refresh category list
|
|
170
397
|
this.fetchCategories();
|
|
171
398
|
}
|
|
@@ -176,8 +403,8 @@ class ListCategorySelector {
|
|
|
176
403
|
data: {
|
|
177
404
|
config: this.config,
|
|
178
405
|
editCategory: editCategory,
|
|
179
|
-
positiveButtonText: editCategory ? '
|
|
180
|
-
cancelButtonText: '
|
|
406
|
+
positiveButtonText: editCategory ? this.t('common.save') : this.t('common.create'),
|
|
407
|
+
cancelButtonText: this.t('common.cancel'),
|
|
181
408
|
},
|
|
182
409
|
});
|
|
183
410
|
const result = await lastValueFrom(dialogRef.afterClosed());
|
|
@@ -194,7 +421,10 @@ class ListCategorySelector {
|
|
|
194
421
|
}
|
|
195
422
|
}
|
|
196
423
|
catch (e) {
|
|
197
|
-
this.snackbar.open(`Error: ${e?.message ??
|
|
424
|
+
this.snackbar.open(`Error: ${e?.message ??
|
|
425
|
+
this.t('category.errorCouldNotSave', {
|
|
426
|
+
categoryType: this.config.singularLabel ?? this.t('category.defaultSingular'),
|
|
427
|
+
})}`, undefined, {
|
|
198
428
|
duration: 3000,
|
|
199
429
|
});
|
|
200
430
|
}
|
|
@@ -206,10 +436,15 @@ class ListCategorySelector {
|
|
|
206
436
|
const dialogRef = this.dialog.open(ConfirmDialog, {
|
|
207
437
|
width: '400px',
|
|
208
438
|
data: {
|
|
209
|
-
title: '
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
439
|
+
title: this.t('category.deletionDialogTitle', {
|
|
440
|
+
categoryType: this.config?.singularLabel ?? this.t('category.defaultSingular'),
|
|
441
|
+
}),
|
|
442
|
+
message: this.t('category.deletionDialogMessage', {
|
|
443
|
+
categoryType: this.config?.singularLabel ?? this.t('category.defaultSingular'),
|
|
444
|
+
label: category.label,
|
|
445
|
+
}),
|
|
446
|
+
positiveButtonText: this.t('common.delete'),
|
|
447
|
+
cancelButtonText: this.t('common.cancel'),
|
|
213
448
|
},
|
|
214
449
|
});
|
|
215
450
|
const result = await lastValueFrom(dialogRef.afterClosed());
|
|
@@ -221,7 +456,11 @@ class ListCategorySelector {
|
|
|
221
456
|
await this.deleteCategory(category);
|
|
222
457
|
}
|
|
223
458
|
catch (e) {
|
|
224
|
-
this.snackbar.open(`Error: ${e?.message ??
|
|
459
|
+
this.snackbar.open(`Error: ${e?.message ??
|
|
460
|
+
this.t('category.errorFailedDelete', {
|
|
461
|
+
categoryType: this.config?.singularLabel ?? this.t('category.defaultSingular'),
|
|
462
|
+
label: category.label,
|
|
463
|
+
})}`, undefined, {
|
|
225
464
|
duration: 3000,
|
|
226
465
|
});
|
|
227
466
|
}
|
|
@@ -229,7 +468,7 @@ class ListCategorySelector {
|
|
|
229
468
|
this.fetchCategories();
|
|
230
469
|
}
|
|
231
470
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ListCategorySelector, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
232
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: ListCategorySelector, isStandalone: true, selector: "lib-list-category-selector", inputs: { config: "config", selectedCategory: "selectedCategory" }, outputs: { selectedCategoryChange: "selectedCategoryChange" }, usesOnChanges: true, ngImport: i0, template: "<header>\n <!-- Category selector title -->\n <span class=\"category-selector-title\"
|
|
471
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: ListCategorySelector, isStandalone: true, selector: "lib-list-category-selector", inputs: { config: "config", selectedCategory: "selectedCategory" }, outputs: { selectedCategoryChange: "selectedCategoryChange" }, usesOnChanges: true, ngImport: i0, template: "<header>\n <!-- Category selector title -->\n <span class=\"category-selector-title\">\n {{ config?.title ?? t('category.defaultPlural') }}\n </span>\n <!---->\n\n <!-- Category Creation button -->\n @if (config?.creator) {\n <button\n mat-icon-button\n (click)=\"openCategoryEditorDialog(undefined)\"\n class=\"create-category-button\"\n >\n <mat-icon>add</mat-icon>\n </button>\n }\n <!---->\n</header>\n\n<mat-selection-list\n #categorySelector\n [multiple]=\"false\"\n [hideSingleSelectionIndicator]=\"true\"\n [(ngModel)]=\"selectedCategory\"\n (selectionChange)=\"this.selectedCategoryChange.emit(selectedCategory)\"\n>\n <!-- \"All\" Category -->\n <mat-list-option\n [value]=\"undefined\"\n [class.active]=\"selectedCategory && selectedCategory[0] === undefined\"\n >\n <div matListItemTitle>\n <!-- Category label -->\n {{ t('category.all') }} \n <!---->\n </div>\n </mat-list-option>\n <!---->\n\n <!-- Each category -->\n @for (category of categories; track category.id) {\n <mat-list-option\n [value]=\"category\"\n [class.active]=\"selectedCategory && selectedCategory[0]?.id === category.id\"\n >\n <div matListItemTitle>\n <!-- Category label -->\n {{ category.label }} \n <!---->\n\n <!-- Count of items in the category -->\n @if (category.itemCount !== undefined) {\n <small> ({{ category.itemCount }}) </small>\n }\n <!---->\n </div>\n\n <!-- Category menu button -->\n @if (\n selectedCategory &&\n selectedCategory[0] !== undefined &&\n selectedCategory[0].id === category.id &&\n (config?.updater || config?.deleter)\n ) {\n <button\n matIconButton\n [matMenuTriggerFor]=\"menu\"\n [matMenuTriggerData]=\"{ category: category }\"\n class=\"category-menu-button\"\n >\n <mat-icon>more_vert</mat-icon>\n </button>\n }\n <!---->\n </mat-list-option>\n }\n <!---->\n</mat-selection-list>\n\n<!-- Category Menu -->\n<mat-menu #menu=\"matMenu\">\n <ng-template matMenuContent let-category=\"category\">\n @if (config?.updater) {\n <button mat-menu-item (click)=\"openCategoryEditorDialog(category)\">\n {{ t('common.edit') }}\n </button>\n }\n @if (config?.deleter) {\n <button mat-menu-item (click)=\"openCategoryDeletionDialog(category)\">\n {{ t('common.delete') }}\n </button>\n }\n </ng-template>\n</mat-menu>\n<!---->\n", styles: [":host{display:block;border-radius:9px;border:1px solid rgba(198,198,201,.1254901961)}header{display:flex;align-items:center;border-bottom:1px solid #f5f6f8;justify-content:space-between;padding:0 .2rem 0 .1rem}header .category-selector-title{font-size:.9rem;padding:1rem .8rem}header button{transform:translateY(-1px)}header button mat-icon{color:#496487}mat-selection-list mat-list-option div[matListItemTitle]{font-size:.85rem;position:relative}mat-selection-list mat-list-option.active{background-color:#f0f0f0}mat-selection-list mat-list-option.active div[matListItemTitle]{color:#3d649c}mat-selection-list mat-list-option:hover{cursor:pointer;background-color:#f0f0f0}mat-selection-list mat-list-option .category-menu-button{position:absolute;right:0;top:0;height:100%;padding-top:.8rem;vertical-align:middle;z-index:1}mat-selection-list mat-list-option{--mat-icon-button-container-shape: 1px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: MatSelectionList, selector: "mat-selection-list", inputs: ["color", "compareWith", "multiple", "hideSingleSelectionIndicator", "disabled"], outputs: ["selectionChange"], exportAs: ["matSelectionList"] }, { kind: "component", type: MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: MatListOption, selector: "mat-list-option", inputs: ["togglePosition", "checkboxPosition", "color", "value", "selected"], outputs: ["selectedChange"], exportAs: ["matListOption"] }, { kind: "directive", type: MatMenuContent, selector: "ng-template[matMenuContent]" }] });
|
|
233
472
|
}
|
|
234
473
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ListCategorySelector, decorators: [{
|
|
235
474
|
type: Component,
|
|
@@ -243,7 +482,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
243
482
|
MatMenuTrigger,
|
|
244
483
|
MatListOption,
|
|
245
484
|
MatMenuContent,
|
|
246
|
-
], template: "<header>\n <!-- Category selector title -->\n <span class=\"category-selector-title\"
|
|
485
|
+
], template: "<header>\n <!-- Category selector title -->\n <span class=\"category-selector-title\">\n {{ config?.title ?? t('category.defaultPlural') }}\n </span>\n <!---->\n\n <!-- Category Creation button -->\n @if (config?.creator) {\n <button\n mat-icon-button\n (click)=\"openCategoryEditorDialog(undefined)\"\n class=\"create-category-button\"\n >\n <mat-icon>add</mat-icon>\n </button>\n }\n <!---->\n</header>\n\n<mat-selection-list\n #categorySelector\n [multiple]=\"false\"\n [hideSingleSelectionIndicator]=\"true\"\n [(ngModel)]=\"selectedCategory\"\n (selectionChange)=\"this.selectedCategoryChange.emit(selectedCategory)\"\n>\n <!-- \"All\" Category -->\n <mat-list-option\n [value]=\"undefined\"\n [class.active]=\"selectedCategory && selectedCategory[0] === undefined\"\n >\n <div matListItemTitle>\n <!-- Category label -->\n {{ t('category.all') }} \n <!---->\n </div>\n </mat-list-option>\n <!---->\n\n <!-- Each category -->\n @for (category of categories; track category.id) {\n <mat-list-option\n [value]=\"category\"\n [class.active]=\"selectedCategory && selectedCategory[0]?.id === category.id\"\n >\n <div matListItemTitle>\n <!-- Category label -->\n {{ category.label }} \n <!---->\n\n <!-- Count of items in the category -->\n @if (category.itemCount !== undefined) {\n <small> ({{ category.itemCount }}) </small>\n }\n <!---->\n </div>\n\n <!-- Category menu button -->\n @if (\n selectedCategory &&\n selectedCategory[0] !== undefined &&\n selectedCategory[0].id === category.id &&\n (config?.updater || config?.deleter)\n ) {\n <button\n matIconButton\n [matMenuTriggerFor]=\"menu\"\n [matMenuTriggerData]=\"{ category: category }\"\n class=\"category-menu-button\"\n >\n <mat-icon>more_vert</mat-icon>\n </button>\n }\n <!---->\n </mat-list-option>\n }\n <!---->\n</mat-selection-list>\n\n<!-- Category Menu -->\n<mat-menu #menu=\"matMenu\">\n <ng-template matMenuContent let-category=\"category\">\n @if (config?.updater) {\n <button mat-menu-item (click)=\"openCategoryEditorDialog(category)\">\n {{ t('common.edit') }}\n </button>\n }\n @if (config?.deleter) {\n <button mat-menu-item (click)=\"openCategoryDeletionDialog(category)\">\n {{ t('common.delete') }}\n </button>\n }\n </ng-template>\n</mat-menu>\n<!---->\n", styles: [":host{display:block;border-radius:9px;border:1px solid rgba(198,198,201,.1254901961)}header{display:flex;align-items:center;border-bottom:1px solid #f5f6f8;justify-content:space-between;padding:0 .2rem 0 .1rem}header .category-selector-title{font-size:.9rem;padding:1rem .8rem}header button{transform:translateY(-1px)}header button mat-icon{color:#496487}mat-selection-list mat-list-option div[matListItemTitle]{font-size:.85rem;position:relative}mat-selection-list mat-list-option.active{background-color:#f0f0f0}mat-selection-list mat-list-option.active div[matListItemTitle]{color:#3d649c}mat-selection-list mat-list-option:hover{cursor:pointer;background-color:#f0f0f0}mat-selection-list mat-list-option .category-menu-button{position:absolute;right:0;top:0;height:100%;padding-top:.8rem;vertical-align:middle;z-index:1}mat-selection-list mat-list-option{--mat-icon-button-container-shape: 1px}\n"] }]
|
|
247
486
|
}], propDecorators: { config: [{
|
|
248
487
|
type: Input
|
|
249
488
|
}], selectedCategory: [{
|
|
@@ -254,6 +493,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
254
493
|
|
|
255
494
|
class CsvExportService {
|
|
256
495
|
snackbar = inject(MatSnackBar);
|
|
496
|
+
translate = inject(NGX_THIN_ADMIN_TRANSLATE);
|
|
257
497
|
encoders = [
|
|
258
498
|
{
|
|
259
499
|
key: 'utf8',
|
|
@@ -310,8 +550,8 @@ class CsvExportService {
|
|
|
310
550
|
// Initialize
|
|
311
551
|
const items = [];
|
|
312
552
|
let errorMessage = undefined;
|
|
313
|
-
//
|
|
314
|
-
const mes = this.snackbar.open('
|
|
553
|
+
// 処理中メッセージを表示
|
|
554
|
+
const mes = this.snackbar.open(this.translate('csv.exporting'));
|
|
315
555
|
// Replace characters in the file name prefix that cannot be used in file names
|
|
316
556
|
fileNamePrefix = fileNamePrefix.replace(/[/¥\\*?:"<>|@\s;^.]/g, '_');
|
|
317
557
|
// Truncate the file name prefix if it is too long
|
|
@@ -405,7 +645,7 @@ class CsvExportService {
|
|
|
405
645
|
if (!blob) {
|
|
406
646
|
console.error(`exportListAsCsv - Blob is not created`);
|
|
407
647
|
mes.dismiss();
|
|
408
|
-
this.snackbar.open(
|
|
648
|
+
this.snackbar.open(this.translate('csv.errorGenerateFailed'), undefined, {
|
|
409
649
|
duration: 5000,
|
|
410
650
|
});
|
|
411
651
|
return;
|
|
@@ -420,14 +660,17 @@ class CsvExportService {
|
|
|
420
660
|
URL.revokeObjectURL(objectUrl);
|
|
421
661
|
// Dissmiss processing message
|
|
422
662
|
mes.dismiss();
|
|
423
|
-
//
|
|
663
|
+
// 結果メッセージを表示
|
|
424
664
|
if (errorMessage) {
|
|
425
|
-
this.snackbar.open(
|
|
665
|
+
this.snackbar.open(this.translate('csv.exportCompletedWithErrors', {
|
|
666
|
+
count: numOfExportedItems,
|
|
667
|
+
error: errorMessage,
|
|
668
|
+
}), undefined, {
|
|
426
669
|
duration: 5000,
|
|
427
670
|
});
|
|
428
671
|
}
|
|
429
672
|
else {
|
|
430
|
-
this.snackbar.open(
|
|
673
|
+
this.snackbar.open(this.translate('csv.exportCompleted', { count: numOfExportedItems }), undefined, {
|
|
431
674
|
duration: 3000,
|
|
432
675
|
});
|
|
433
676
|
}
|
|
@@ -442,6 +685,100 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
442
685
|
}]
|
|
443
686
|
}], ctorParameters: () => [] });
|
|
444
687
|
|
|
688
|
+
class CategoryChangeDialog {
|
|
689
|
+
dialogRef = inject((MatDialogRef));
|
|
690
|
+
data = inject(MAT_DIALOG_DATA);
|
|
691
|
+
translate = inject(NGX_THIN_ADMIN_TRANSLATE);
|
|
692
|
+
cdr = inject(ChangeDetectorRef);
|
|
693
|
+
dialog = inject(MatDialog);
|
|
694
|
+
snackbar = inject(MatSnackBar);
|
|
695
|
+
categories = [];
|
|
696
|
+
selectedCategoryId;
|
|
697
|
+
isLoading = false;
|
|
698
|
+
t(key, params) {
|
|
699
|
+
return this.translate(key, params);
|
|
700
|
+
}
|
|
701
|
+
async ngOnInit() {
|
|
702
|
+
this.selectedCategoryId = this.data.currentCategoryId;
|
|
703
|
+
this.isLoading = true;
|
|
704
|
+
this.cdr.markForCheck();
|
|
705
|
+
try {
|
|
706
|
+
const result = await Promise.resolve(this.data.categorySelectorConfig.fetcher());
|
|
707
|
+
this.categories = result.categories;
|
|
708
|
+
}
|
|
709
|
+
catch (e) {
|
|
710
|
+
console.error('Failed to fetch categories:', e);
|
|
711
|
+
}
|
|
712
|
+
finally {
|
|
713
|
+
this.isLoading = false;
|
|
714
|
+
this.cdr.markForCheck();
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
save() {
|
|
718
|
+
this.dialogRef.close({
|
|
719
|
+
categoryId: this.selectedCategoryId,
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
async createNewCategory() {
|
|
723
|
+
if (!this.data.categorySelectorConfig.creator)
|
|
724
|
+
return;
|
|
725
|
+
const dialogRef = this.dialog.open(CategoryEditorDialog, {
|
|
726
|
+
width: '400px',
|
|
727
|
+
data: {
|
|
728
|
+
config: this.data.categorySelectorConfig,
|
|
729
|
+
editCategory: undefined,
|
|
730
|
+
positiveButtonText: this.t('common.create'),
|
|
731
|
+
cancelButtonText: this.t('common.cancel'),
|
|
732
|
+
},
|
|
733
|
+
});
|
|
734
|
+
const result = await lastValueFrom(dialogRef.afterClosed());
|
|
735
|
+
if (!result || !result.label) {
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
this.isLoading = true;
|
|
739
|
+
this.cdr.markForCheck();
|
|
740
|
+
try {
|
|
741
|
+
const newCategory = await Promise.resolve(this.data.categorySelectorConfig.creator(result.label));
|
|
742
|
+
this.snackbar.open(this.t('category.successCreated', {
|
|
743
|
+
categoryType: this.data.categorySelectorConfig.singularLabel ?? this.t('category.defaultSingular'),
|
|
744
|
+
label: result.label,
|
|
745
|
+
}), undefined, { duration: 3000 });
|
|
746
|
+
// Re-fetch categories
|
|
747
|
+
const fetchResult = await Promise.resolve(this.data.categorySelectorConfig.fetcher());
|
|
748
|
+
this.categories = fetchResult.categories;
|
|
749
|
+
// Auto-select the newly created category
|
|
750
|
+
this.selectedCategoryId = newCategory.id;
|
|
751
|
+
}
|
|
752
|
+
catch (e) {
|
|
753
|
+
this.snackbar.open(`Error: ${e?.message ??
|
|
754
|
+
this.t('category.errorCouldNotSave', {
|
|
755
|
+
categoryType: this.data.categorySelectorConfig.singularLabel ?? this.t('category.defaultSingular'),
|
|
756
|
+
})}`, undefined, { duration: 3000 });
|
|
757
|
+
}
|
|
758
|
+
finally {
|
|
759
|
+
this.isLoading = false;
|
|
760
|
+
this.cdr.markForCheck();
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: CategoryChangeDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
764
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: CategoryChangeDialog, isStandalone: true, selector: "lib-category-change-dialog", ngImport: i0, template: "<h2 mat-dialog-title>\n {{\n t('item.changeCategoryDialogTitle', {\n categoryType: data.categorySelectorConfig.singularLabel || t('category.defaultSingular'),\n })\n }}\n</h2>\n\n<mat-dialog-content>\n @if (data.itemLabel) {\n <p style=\"margin-top: 5px; margin-bottom: 1rem\">\n {{\n t('item.changeCategoryDialogMessage', {\n label: data.itemLabel,\n categoryType: data.categorySelectorConfig.singularLabel || t('category.defaultSingular'),\n })\n }}\n </p>\n }\n\n <mat-form-field appearance=\"outline\" style=\"width: 100%; margin-top: 10px\">\n <mat-label>{{\n data.categorySelectorConfig.singularLabel || t('category.defaultSingular')\n }}</mat-label>\n <mat-select [(ngModel)]=\"selectedCategoryId\" [disabled]=\"isLoading\">\n <mat-option [value]=\"undefined\">{{ t('category.unselected') }}</mat-option>\n @for (category of categories; track category.id) {\n <mat-option [value]=\"category.id\">{{ category.label }}</mat-option>\n }\n </mat-select>\n @if (isLoading) {\n <mat-spinner matSuffix diameter=\"20\" style=\"margin-right: 12px\"></mat-spinner>\n }\n </mat-form-field>\n</mat-dialog-content>\n\n<mat-dialog-actions>\n @if (data.categorySelectorConfig.creator) {\n <button mat-button (click)=\"createNewCategory()\" [disabled]=\"isLoading\">\n <mat-icon style=\"margin-right: 4px; font-size: 1.2rem; width: 1.2rem; height: 1.2rem\"\n >add</mat-icon\n >\n <span>{{ t('common.create') }}</span>\n </button>\n }\n\n <span style=\"flex: 1\"></span>\n\n <button mat-button mat-dialog-close>\n {{ data.cancelButtonText || t('common.cancel') }}\n </button>\n <button mat-flat-button color=\"primary\" (click)=\"save()\" [disabled]=\"isLoading\">\n {{ data.positiveButtonText || t('common.save') }}\n </button>\n</mat-dialog-actions>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i2$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i2$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "ngmodule", type: MatProgressSpinnerModule }, { kind: "component", type: i5.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }] });
|
|
765
|
+
}
|
|
766
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: CategoryChangeDialog, decorators: [{
|
|
767
|
+
type: Component,
|
|
768
|
+
args: [{ selector: 'lib-category-change-dialog', imports: [
|
|
769
|
+
MatFormFieldModule,
|
|
770
|
+
MatSelectModule,
|
|
771
|
+
FormsModule,
|
|
772
|
+
MatButtonModule,
|
|
773
|
+
MatDialogTitle,
|
|
774
|
+
MatDialogContent,
|
|
775
|
+
MatDialogActions,
|
|
776
|
+
MatDialogClose,
|
|
777
|
+
MatProgressSpinnerModule,
|
|
778
|
+
MatIconModule,
|
|
779
|
+
], template: "<h2 mat-dialog-title>\n {{\n t('item.changeCategoryDialogTitle', {\n categoryType: data.categorySelectorConfig.singularLabel || t('category.defaultSingular'),\n })\n }}\n</h2>\n\n<mat-dialog-content>\n @if (data.itemLabel) {\n <p style=\"margin-top: 5px; margin-bottom: 1rem\">\n {{\n t('item.changeCategoryDialogMessage', {\n label: data.itemLabel,\n categoryType: data.categorySelectorConfig.singularLabel || t('category.defaultSingular'),\n })\n }}\n </p>\n }\n\n <mat-form-field appearance=\"outline\" style=\"width: 100%; margin-top: 10px\">\n <mat-label>{{\n data.categorySelectorConfig.singularLabel || t('category.defaultSingular')\n }}</mat-label>\n <mat-select [(ngModel)]=\"selectedCategoryId\" [disabled]=\"isLoading\">\n <mat-option [value]=\"undefined\">{{ t('category.unselected') }}</mat-option>\n @for (category of categories; track category.id) {\n <mat-option [value]=\"category.id\">{{ category.label }}</mat-option>\n }\n </mat-select>\n @if (isLoading) {\n <mat-spinner matSuffix diameter=\"20\" style=\"margin-right: 12px\"></mat-spinner>\n }\n </mat-form-field>\n</mat-dialog-content>\n\n<mat-dialog-actions>\n @if (data.categorySelectorConfig.creator) {\n <button mat-button (click)=\"createNewCategory()\" [disabled]=\"isLoading\">\n <mat-icon style=\"margin-right: 4px; font-size: 1.2rem; width: 1.2rem; height: 1.2rem\"\n >add</mat-icon\n >\n <span>{{ t('common.create') }}</span>\n </button>\n }\n\n <span style=\"flex: 1\"></span>\n\n <button mat-button mat-dialog-close>\n {{ data.cancelButtonText || t('common.cancel') }}\n </button>\n <button mat-flat-button color=\"primary\" (click)=\"save()\" [disabled]=\"isLoading\">\n {{ data.positiveButtonText || t('common.save') }}\n </button>\n</mat-dialog-actions>\n" }]
|
|
780
|
+
}] });
|
|
781
|
+
|
|
445
782
|
class NgxThinAdminList {
|
|
446
783
|
/**
|
|
447
784
|
* Config for list
|
|
@@ -451,6 +788,11 @@ class NgxThinAdminList {
|
|
|
451
788
|
* Config for category selector. If provided, a category selector will be displayed in the UI.
|
|
452
789
|
*/
|
|
453
790
|
categorySelectorConfig;
|
|
791
|
+
/**
|
|
792
|
+
* Config for item deleter (If provided, item deletion feature is enabled)
|
|
793
|
+
* It works by calling openItemDeletionDialog(row) from the click handler of the column definition.
|
|
794
|
+
*/
|
|
795
|
+
itemDeleterConfig;
|
|
454
796
|
/**
|
|
455
797
|
* Column schema (extended type of MtxGridColumn)
|
|
456
798
|
* @see https://ng-matero.github.io/extensions/components/grid/api
|
|
@@ -465,6 +807,10 @@ class NgxThinAdminList {
|
|
|
465
807
|
perPage: 10,
|
|
466
808
|
categoryId: undefined,
|
|
467
809
|
};
|
|
810
|
+
/**
|
|
811
|
+
* Output query
|
|
812
|
+
*/
|
|
813
|
+
queryChange = new EventEmitter();
|
|
468
814
|
/**
|
|
469
815
|
* Fetcher function to retrieve list data.
|
|
470
816
|
* This should be provided as part of listConfig.
|
|
@@ -488,6 +834,7 @@ class NgxThinAdminList {
|
|
|
488
834
|
keywordChanged$ = new Subject();
|
|
489
835
|
ngOnInit() {
|
|
490
836
|
this.keywordChanged$.pipe(debounceTime(500)).subscribe(() => {
|
|
837
|
+
this.queryChange.emit(this.query);
|
|
491
838
|
this.fetchList();
|
|
492
839
|
});
|
|
493
840
|
}
|
|
@@ -495,20 +842,30 @@ class NgxThinAdminList {
|
|
|
495
842
|
* Template for cells
|
|
496
843
|
*/
|
|
497
844
|
cellTplWithLink;
|
|
845
|
+
/**
|
|
846
|
+
* Category Selector
|
|
847
|
+
*/
|
|
848
|
+
categorySelector;
|
|
498
849
|
/**
|
|
499
850
|
* Services
|
|
500
851
|
*/
|
|
501
852
|
cdr = inject(ChangeDetectorRef);
|
|
853
|
+
translate = inject(NGX_THIN_ADMIN_TRANSLATE);
|
|
854
|
+
snackbar = inject(MatSnackBar);
|
|
502
855
|
csvExportService = inject(CsvExportService);
|
|
856
|
+
dialog = inject(MatDialog);
|
|
857
|
+
t(key, params) {
|
|
858
|
+
return this.translate(key, params);
|
|
859
|
+
}
|
|
503
860
|
ngOnChanges(changes) {
|
|
504
861
|
// Columns
|
|
505
|
-
if (changes.listColumns?.currentValue) {
|
|
506
|
-
this.
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
862
|
+
if (changes.listColumns?.currentValue || changes.listConfig?.currentValue) {
|
|
863
|
+
this.buildGridColumns();
|
|
864
|
+
}
|
|
865
|
+
// Default page size
|
|
866
|
+
if (changes.listConfig?.currentValue?.pageSize !== undefined &&
|
|
867
|
+
changes.listConfig.currentValue.pageSize !== changes.listConfig.previousValue?.pageSize) {
|
|
868
|
+
this.query.perPage = changes.listConfig.currentValue.pageSize;
|
|
512
869
|
}
|
|
513
870
|
// List - trigger strictly when listFetcher is newly provided or explicitly changed
|
|
514
871
|
if (changes.listConfig?.currentValue?.fetcher !== changes.listConfig?.previousValue?.fetcher) {
|
|
@@ -516,6 +873,48 @@ class NgxThinAdminList {
|
|
|
516
873
|
this.fetchList();
|
|
517
874
|
}
|
|
518
875
|
}
|
|
876
|
+
buildGridColumns() {
|
|
877
|
+
if (!this.listColumns) {
|
|
878
|
+
this.gridColumns = [];
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
let columns = this.listColumns.map((col) => {
|
|
882
|
+
if (col.link || col.routerLink) {
|
|
883
|
+
return { ...col, cellTemplate: this.cellTplWithLink };
|
|
884
|
+
}
|
|
885
|
+
return col;
|
|
886
|
+
});
|
|
887
|
+
if (this.listConfig?.categoryChanger && this.categorySelectorConfig) {
|
|
888
|
+
const actionsColIndex = columns.findIndex((col) => col.field === 'actions' && col.type === 'button');
|
|
889
|
+
const categoryType = this.categorySelectorConfig.singularLabel || this.t('category.defaultSingular');
|
|
890
|
+
const changeCategoryBtn = {
|
|
891
|
+
type: 'icon',
|
|
892
|
+
icon: 'folder_shared',
|
|
893
|
+
tooltip: this.t('item.changeCategory', { categoryType }),
|
|
894
|
+
click: (record) => this.openItemCategoryChangeDialog(record),
|
|
895
|
+
};
|
|
896
|
+
if (actionsColIndex >= 0) {
|
|
897
|
+
const col = { ...columns[actionsColIndex] };
|
|
898
|
+
const originalButtons = col.buttons;
|
|
899
|
+
if (typeof originalButtons === 'function') {
|
|
900
|
+
col.buttons = (record) => [...originalButtons(record), changeCategoryBtn];
|
|
901
|
+
}
|
|
902
|
+
else {
|
|
903
|
+
col.buttons = [...(originalButtons || []), changeCategoryBtn];
|
|
904
|
+
}
|
|
905
|
+
columns[actionsColIndex] = col;
|
|
906
|
+
}
|
|
907
|
+
else {
|
|
908
|
+
columns.push({
|
|
909
|
+
header: 'Actions',
|
|
910
|
+
field: 'actions',
|
|
911
|
+
type: 'button',
|
|
912
|
+
buttons: [changeCategoryBtn],
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
this.gridColumns = columns;
|
|
917
|
+
}
|
|
519
918
|
async fetchList() {
|
|
520
919
|
if (!this.listFetcher) {
|
|
521
920
|
return;
|
|
@@ -541,7 +940,11 @@ class NgxThinAdminList {
|
|
|
541
940
|
clearFilters() {
|
|
542
941
|
this.query.keyword = '';
|
|
543
942
|
this.query.categoryId = undefined;
|
|
943
|
+
this.query.filterColumn = undefined;
|
|
944
|
+
this.query.filterValue = undefined;
|
|
945
|
+
this.query.filterLabel = undefined;
|
|
544
946
|
this.selectedCategory = [];
|
|
947
|
+
this.queryChange.emit(this.query);
|
|
545
948
|
this.fetchList();
|
|
546
949
|
}
|
|
547
950
|
onKeywordInput() {
|
|
@@ -552,7 +955,9 @@ class NgxThinAdminList {
|
|
|
552
955
|
return;
|
|
553
956
|
}
|
|
554
957
|
// Determine file name prefix
|
|
555
|
-
let fileNamePrefix = this.listConfig?.title
|
|
958
|
+
let fileNamePrefix = this.listConfig?.title
|
|
959
|
+
? this.listConfig.title
|
|
960
|
+
: this.t('list.exportedFileName');
|
|
556
961
|
if (this.selectedCategory && this.selectedCategory[0]?.id) {
|
|
557
962
|
// If a category is selected, include the category label in the file name prefix
|
|
558
963
|
fileNamePrefix += `_${this.selectedCategory[0]?.label}`;
|
|
@@ -567,19 +972,144 @@ class NgxThinAdminList {
|
|
|
567
972
|
keyword: this.query.keyword,
|
|
568
973
|
}, this.listColumns);
|
|
569
974
|
}
|
|
975
|
+
getColumnLabel(columnName) {
|
|
976
|
+
const column = this.listColumns?.find((col) => col.field === columnName);
|
|
977
|
+
if (column?.header) {
|
|
978
|
+
return column.header;
|
|
979
|
+
}
|
|
980
|
+
return columnName;
|
|
981
|
+
}
|
|
982
|
+
getColumnValueLabel(columnName, value) {
|
|
983
|
+
const column = this.listColumns?.find((col) => col.field === columnName);
|
|
984
|
+
if (!column) {
|
|
985
|
+
return value;
|
|
986
|
+
}
|
|
987
|
+
if (this.gridData.length == 0) {
|
|
988
|
+
return value;
|
|
989
|
+
}
|
|
990
|
+
const item = this.gridData.find((item) => String(item[columnName]) === String(value));
|
|
991
|
+
if (item) {
|
|
992
|
+
if (column.formatter) {
|
|
993
|
+
return column.formatter(item, column);
|
|
994
|
+
}
|
|
995
|
+
return item[columnName];
|
|
996
|
+
}
|
|
997
|
+
return value;
|
|
998
|
+
}
|
|
570
999
|
onPageChange(event) {
|
|
571
1000
|
this.query.page = event.pageIndex;
|
|
572
1001
|
this.query.perPage = event.pageSize;
|
|
1002
|
+
this.queryChange.emit(this.query);
|
|
573
1003
|
this.fetchList();
|
|
574
1004
|
}
|
|
575
1005
|
onSelectedCategoryChange($event) {
|
|
576
1006
|
console.log('Selected category changed:', $event);
|
|
577
1007
|
const categoryId = $event && $event.length >= 1 && $event[0] ? $event[0].id : undefined;
|
|
578
1008
|
this.query.categoryId = categoryId;
|
|
1009
|
+
this.queryChange.emit(this.query);
|
|
1010
|
+
this.fetchList();
|
|
1011
|
+
}
|
|
1012
|
+
async openItemDeletionDialog(item) {
|
|
1013
|
+
if (!this.itemDeleterConfig?.deleter) {
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
// Get the label and ID to query in the confirmation dialog
|
|
1017
|
+
const idKey = this.listConfig?.idFieldKey;
|
|
1018
|
+
const labelKey = this.listConfig?.labelFieldKey;
|
|
1019
|
+
const id = idKey ? item[idKey] : (item.id ?? JSON.stringify(item));
|
|
1020
|
+
const label = labelKey ? item[labelKey] : id;
|
|
1021
|
+
const itemType = this.listConfig?.singularLabel || this.t('item.defaultSingular');
|
|
1022
|
+
// Open the confirmation dialog
|
|
1023
|
+
const dialogRef = this.dialog.open(ConfirmDialog, {
|
|
1024
|
+
width: '400px',
|
|
1025
|
+
data: {
|
|
1026
|
+
title: this.t('item.deletionDialogTitle', { itemType }),
|
|
1027
|
+
message: this.t('item.deletionDialogMessage', { label, id }),
|
|
1028
|
+
positiveButtonText: this.t('common.delete'),
|
|
1029
|
+
cancelButtonText: this.t('common.cancel'),
|
|
1030
|
+
},
|
|
1031
|
+
});
|
|
1032
|
+
const result = await lastValueFrom(dialogRef.afterClosed());
|
|
1033
|
+
if (!result) {
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
// Execute delete process
|
|
1037
|
+
await this.deleteItem(id, label);
|
|
1038
|
+
}
|
|
1039
|
+
async deleteItem(id, label) {
|
|
1040
|
+
if (!this.itemDeleterConfig?.deleter) {
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
try {
|
|
1044
|
+
await this.itemDeleterConfig.deleter(id);
|
|
1045
|
+
}
|
|
1046
|
+
catch (e) {
|
|
1047
|
+
const errorMessage = getErrorMessage(e);
|
|
1048
|
+
this.snackbar.open(`Error: ${errorMessage || this.t('item.errorFailedDelete', { label })}`, undefined, { duration: 3000 });
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
// Show success message
|
|
1052
|
+
this.snackbar.open(this.t('item.successDeleted', { label }), undefined, {
|
|
1053
|
+
duration: 3000,
|
|
1054
|
+
});
|
|
1055
|
+
// Refresh the list after deletion
|
|
1056
|
+
this.fetchList();
|
|
1057
|
+
}
|
|
1058
|
+
async openItemCategoryChangeDialog(item) {
|
|
1059
|
+
if (!this.listConfig?.categoryChanger || !this.categorySelectorConfig) {
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
const idKey = this.listConfig?.idFieldKey;
|
|
1063
|
+
const labelKey = this.listConfig?.labelFieldKey;
|
|
1064
|
+
const id = idKey ? item[idKey] : (item.id ?? JSON.stringify(item));
|
|
1065
|
+
const label = labelKey ? item[labelKey] : id;
|
|
1066
|
+
let currentCategoryId;
|
|
1067
|
+
if (typeof this.listConfig.categoryFieldKey === 'function') {
|
|
1068
|
+
currentCategoryId = this.listConfig.categoryFieldKey(item);
|
|
1069
|
+
}
|
|
1070
|
+
else if (typeof this.listConfig.categoryFieldKey === 'string') {
|
|
1071
|
+
currentCategoryId = item[this.listConfig.categoryFieldKey];
|
|
1072
|
+
}
|
|
1073
|
+
else {
|
|
1074
|
+
currentCategoryId = item.categoryId ?? item.category?.id;
|
|
1075
|
+
}
|
|
1076
|
+
const dialogRef = this.dialog.open(CategoryChangeDialog, {
|
|
1077
|
+
width: '400px',
|
|
1078
|
+
data: {
|
|
1079
|
+
itemLabel: label,
|
|
1080
|
+
categorySelectorConfig: this.categorySelectorConfig,
|
|
1081
|
+
currentCategoryId: currentCategoryId,
|
|
1082
|
+
positiveButtonText: this.t('common.save'),
|
|
1083
|
+
cancelButtonText: this.t('common.cancel'),
|
|
1084
|
+
},
|
|
1085
|
+
});
|
|
1086
|
+
const result = await lastValueFrom(dialogRef.afterClosed());
|
|
1087
|
+
if (!result) {
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
const newCategoryId = result.categoryId;
|
|
1091
|
+
const categoryType = this.categorySelectorConfig.singularLabel || this.t('category.defaultSingular');
|
|
1092
|
+
try {
|
|
1093
|
+
const res = await this.listConfig.categoryChanger(id, newCategoryId);
|
|
1094
|
+
if (res && 'ok' in res && !res.ok) {
|
|
1095
|
+
const errorMessage = getErrorMessage(res);
|
|
1096
|
+
throw new Error(errorMessage);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
catch (e) {
|
|
1100
|
+
const errorMessage = getErrorMessage(e);
|
|
1101
|
+
this.snackbar.open(`Error: ${errorMessage || this.t('item.errorFailedCategoryChange', { label, categoryType })}`, undefined, { duration: 3000 });
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
this.snackbar.open(this.t('item.successCategoryChanged', { label, categoryType }), undefined, {
|
|
1105
|
+
duration: 3000,
|
|
1106
|
+
});
|
|
1107
|
+
// Refresh the list and category selector
|
|
579
1108
|
this.fetchList();
|
|
1109
|
+
this.categorySelector?.fetchCategories();
|
|
580
1110
|
}
|
|
581
1111
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: NgxThinAdminList, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
582
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: NgxThinAdminList, isStandalone: true, selector: "ngx-thin-admin-list", inputs: { listConfig: "listConfig", categorySelectorConfig: "categorySelectorConfig", listColumns: "listColumns", query: "query" }, viewQueries: [{ propertyName: "cellTplWithLink", first: true, predicate: ["cellTplWithLink"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<!-- Category Selector -->\n@if (categorySelectorConfig) {\n <lib-list-category-selector\n [config]=\"categorySelectorConfig\"\n [(selectedCategory)]=\"selectedCategory\"\n (selectedCategoryChange)=\"onSelectedCategoryChange($event)\"\n ></lib-list-category-selector>\n}\n<!---->\n\n<!-- Toolbar Template -->\n<ng-template #toolbarTpl>\n <div class=\"custom-toolbar\">\n <!-- List Title -->\n <span class=\"list-title\">\n @if (listConfig?.title; as listTitle) {\n @if (query.keyword || query.categoryId) {\n <!-- Title (with link for reset filter) -->\n <a\n href=\"javascript:void(0)\"\n (click)=\"query.keyword = ''; query.categoryId = undefined; this.fetchList()\"\n matTooltip=\"Reset filters\"\n >{{ listTitle }}</a\n >\n <!---->\n } @else {\n <!-- Title -->\n {{ listTitle }}\n <!---->\n }\n } @else {\n <!-- Default Title -->\n List\n <!---->\n }\n\n @if (query.categoryId && selectedCategory?.[0]; as categoryLabel) {\n <span class=\"material-icons list-title-separator\"> keyboard_arrow_right </span>\n <!-- Category -->\n {{ categoryLabel.label }}\n <!---->\n }\n\n @if (query.keyword) {\n <span class=\"material-icons list-title-separator\"> keyboard_arrow_right </span>\n <!-- Keyword -->\n \"{{ query.keyword }}\"\n <!---->\n }\n </span>\n <!---->\n\n @if (query.keyword || query.categoryId) {\n <span style=\"width: 8px\"></span>\n <!-- Clear Filters Button -->\n <button\n mat-icon-button\n class=\"clear-filters-button\"\n matTooltip=\"Clear filters\"\n (click)=\"clearFilters()\"\n >\n <mat-icon>filter_alt_off</mat-icon>\n </button>\n <!---->\n }\n\n <span style=\"flex: 1\"></span>\n\n <!-- Create Button -->\n @if (listConfig?.createButton; as createBtn) {\n @if ($any(createBtn).routerLink; as routerLinkUrl) {\n <!-- Create Button with Router Link -->\n <a\n [routerLink]=\"routerLinkUrl\"\n mat-flat-button\n class=\"primary-button\"\n style=\"color: white; transform: translateY(-1px)\"\n >\n {{ createBtn.label }}\n <mat-icon style=\"margin-bottom: 1px\">add</mat-icon>\n </a>\n } @else if ($any(createBtn).link; as linkUrl) {\n <!-- Create Button with Link -->\n <a\n [href]=\"linkUrl\"\n mat-flat-button\n class=\"primary-button\"\n style=\"color: white; transform: translateY(-1px)\"\n >\n {{ createBtn.label }}\n <mat-icon style=\"margin-bottom: 1px\">add</mat-icon>\n </a>\n } @else if ($any(createBtn).click; as handler) {\n <!-- Create Button with Handler -->\n <button\n mat-flat-button\n class=\"primary-button\"\n style=\"color: white; transform: translateY(-1px)\"\n (click)=\"handler()\"\n >\n {{ createBtn?.label }}\n <mat-icon style=\"margin-bottom: 1px\">add</mat-icon>\n </button>\n }\n }\n <!---->\n\n <span style=\"width: 30px\"></span>\n\n <!-- Search Field -->\n <mat-form-field class=\"search-keyword xs-input\" appearance=\"outline\">\n <mat-icon matPrefix>search</mat-icon>\n <input\n matInput\n [placeholder]=\"\n selectedCategory?.[0]?.label\n ? 'Search in ' + selectedCategory?.[0]?.label + '...'\n : 'Search...'\n \"\n [(ngModel)]=\"query.keyword\"\n (keyup)=\"onKeywordInput()\"\n style=\"padding-top: 3px\"\n />\n\n <!-- Search clear button -->\n <button\n matSuffix\n mat-icon-button\n aria-label=\"Clear search keyword\"\n matTooltip=\"Clear search keyword\"\n (click)=\"query.keyword = ''; this.fetchList()\"\n [style.visibility]=\"query.keyword ? 'visible' : 'hidden'\"\n >\n <mat-icon>highlight_off</mat-icon>\n </button>\n <!---->\n </mat-form-field>\n <!---->\n\n <span style=\"width: 8px\"></span>\n\n <!-- Refresh Button -->\n <button mat-icon-button matTooltip=\"Refresh\" (click)=\"fetchList()\">\n <mat-icon>refresh</mat-icon>\n </button>\n <!---->\n\n <span style=\"width: 5px\"></span>\n\n <!-- CSV Export Button -->\n <button mat-icon-button [matMenuTriggerFor]=\"csvExportMenu\" matTooltip=\"Export as CSV\">\n <mat-icon>download</mat-icon>\n </button>\n <!---->\n\n <!-- CSV Export Menu -->\n <mat-menu #csvExportMenu=\"matMenu\">\n @for (encoder of csvExportService.getAvailableEncoders(); track encoder.key) {\n <!-- Export with specific charset -->\n <p style=\"color: #333; font-size: 0.8rem; margin: 1rem 0.75rem 1rem 0.7rem\">\n CSV ({{ encoder.label }})\n </p>\n <button mat-menu-item (click)=\"exportAsCsv(encoder.key)\">\n <mat-icon>download</mat-icon>\n <span>Export {{ totalCount }} items</span>\n </button>\n <!---->\n }\n </mat-menu>\n <!---->\n </div>\n</ng-template>\n<!---->\n\n<!-- List (Grid) -->\n<mtx-grid\n [data]=\"gridData\"\n [columns]=\"gridColumns\"\n [showToolbar]=\"true\"\n [toolbarTemplate]=\"toolbarTpl\"\n [length]=\"totalCount\"\n [loading]=\"isLoading\"\n [pageOnFront]=\"false\"\n [pageIndex]=\"query.page\"\n [pageSize]=\"query.perPage\"\n [pageSizeOptions]=\"listConfig?.pageSizes ?? [10, 25, 50, 100]\"\n columnMenuButtonType=\"icon\"\n columnMenuButtonClass=\"column-menu-button\"\n columnMenuButtonIcon=\"view_column\"\n (page)=\"onPageChange($event)\"\n></mtx-grid>\n<!---->\n\n<!-- Cell Template with Link -->\n<ng-template #cellTplWithLink let-row let-index=\"index\" let-col=\"colDef\">\n @if (col.link) {\n <a [href]=\"col.link(row)\">\n {{ row[col.field] }}\n </a>\n } @else if (col.routerLink) {\n <a [routerLink]=\"col.routerLink(row)\">\n {{ row[col.field] }}\n </a>\n } @else {\n {{ row[col.field] }}\n }\n</ng-template>\n<!---->\n", styles: [":host{display:flex;gap:1rem;padding:1rem;box-sizing:border-box}lib-list-category-selector{display:block;width:200px;background-color:#fcfcfc;border-radius:9px;padding:.5rem;box-sizing:border-box}lib-list-category-selector .category-list{max-height:300px;overflow-y:auto}::ng-deep .mtx-grid-toolbar{display:block;background-color:#fcfcfc;border-top-left-radius:9px;border-top-right-radius:9px;margin-bottom:1px}::ng-deep .mtx-grid-toolbar .custom-toolbar{display:flex;align-items:center;justify-content:space-between;height:37px}::ng-deep .mtx-grid-toolbar .custom-toolbar .list-title{font-size:1rem;line-height:37px}::ng-deep .mtx-grid-toolbar .custom-toolbar .list-title a{color:var(--mat-theme-primary)}::ng-deep .mtx-grid-toolbar .custom-toolbar .list-title a:hover{text-decoration:underline}::ng-deep .mtx-grid-toolbar .custom-toolbar .list-title .list-title-separator{color:#aaa;vertical-align:middle;margin:auto .2rem 4px}::ng-deep .mtx-grid-toolbar .custom-toolbar .clear-filters-button{color:var(--mat-sys-on-primary-container);min-width:32px;padding:0;vertical-align:middle}::ng-deep .mtx-grid-toolbar .custom-toolbar .clear-filters-button mat-icon{vertical-align:middle;margin-bottom:3px}::ng-deep .mtx-grid-toolbar .custom-toolbar .search-keyword{width:15rem;--mat-form-field-container-height: 18.5px;--mat-form-field-container-text-line-height: 18.5px;--mat-form-field-container-vertical-padding: 8.5px;--mat-form-field-outlined-container-shape: 28px;--mat-form-field-subscript-text-line-height: 0px}::ng-deep .mtx-grid-toolbar .custom-toolbar button{color:var(--mat-sys-on-primary-container)}::ng-deep .mtx-grid-toolbar ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}::ng-deep .mtx-grid-toolbar ::ng-deep mtx-grid-column-menu button[mat-icon-button]{color:var(--mat-sys-on-primary-container);margin-right:.5rem}::ng-deep .mtx-grid-main{background-color:#fcfcfc}table{width:100%}::ng-deep table thead,::ng-deep table tbody{background-color:#fcfcfc!important}::ng-deep .mtx-grid-footer,::ng-deep .mat-mdc-paginator{display:block;background-color:#fcfcfc!important;border-bottom-left-radius:9px!important;border-bottom-right-radius:9px!important}a,::ng-deep table a,::ng-deep table a:visited{color:var(--mat-sys-on-primary-container);text-decoration:none}a:hover,::ng-deep table a:hover,::ng-deep table a:visited:hover{text-decoration:underline}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { 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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MtxGridModule }, { kind: "component", type: i2$2.MtxGrid, selector: "mtx-grid", inputs: ["displayedColumns", "columns", "data", "length", "loading", "trackBy", "columnResizable", "emptyValuePlaceholder", "pageOnFront", "showPaginator", "pageDisabled", "showFirstLastButtons", "pageIndex", "pageSize", "pageSizeOptions", "hidePageSize", "paginationTemplate", "sortOnFront", "sortActive", "sortDirection", "sortDisableClear", "sortDisabled", "sortStart", "rowHover", "rowStriped", "expandable", "expansionTemplate", "multiSelectable", "multiSelectionWithClick", "rowSelectable", "hideRowSelectionCheckbox", "disableRowClickSelection", "rowSelectionFormatter", "rowClassFormatter", "rowSelected", "cellSelectable", "showToolbar", "toolbarTitle", "toolbarTemplate", "columnHideable", "columnHideableChecked", "columnSortable", "columnPinnable", "columnPinOptions", "showColumnMenuButton", "columnMenuButtonText", "columnMenuButtonType", "columnMenuButtonColor", "columnMenuButtonClass", "columnMenuButtonIcon", "columnMenuButtonFontIcon", "columnMenuButtonSvgIcon", "showColumnMenuHeader", "columnMenuHeaderText", "columnMenuHeaderTemplate", "showColumnMenuFooter", "columnMenuFooterText", "columnMenuFooterTemplate", "noResultText", "noResultTemplate", "headerTemplate", "headerExtraTemplate", "cellTemplate", "useContentRowTemplate", "useContentHeaderRowTemplate", "useContentFooterRowTemplate", "showSummary", "summaryTemplate", "showSidebar", "sidebarTemplate", "showStatusbar", "statusbarTemplate"], outputs: ["page", "sortChange", "rowClick", "rowContextMenu", "expansionChange", "rowSelectedChange", "cellSelectedChange", "columnChange"], exportAs: ["mtxGrid"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "directive", type: MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "directive", type: MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "component", type: ListCategorySelector, selector: "lib-list-category-selector", inputs: ["config", "selectedCategory"], outputs: ["selectedCategoryChange"] }, { kind: "component", type: MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] });
|
|
1112
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: NgxThinAdminList, isStandalone: true, selector: "ngx-thin-admin-list", inputs: { listConfig: "listConfig", categorySelectorConfig: "categorySelectorConfig", itemDeleterConfig: "itemDeleterConfig", listColumns: "listColumns", query: "query" }, outputs: { queryChange: "queryChange" }, viewQueries: [{ propertyName: "cellTplWithLink", first: true, predicate: ["cellTplWithLink"], descendants: true, static: true }, { propertyName: "categorySelector", first: true, predicate: ListCategorySelector, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<!-- Category Selector -->\n@if (categorySelectorConfig) {\n <lib-list-category-selector\n [config]=\"categorySelectorConfig\"\n [(selectedCategory)]=\"selectedCategory\"\n (selectedCategoryChange)=\"onSelectedCategoryChange($event)\"\n ></lib-list-category-selector>\n}\n<!---->\n\n<!-- Toolbar Template -->\n<ng-template #toolbarTpl>\n <div class=\"custom-toolbar\">\n <!-- List Title -->\n <span class=\"list-title\">\n @if (listConfig?.title; as listTitle) {\n @if (query.keyword || query.categoryId || (query.filterColumn && query.filterValue)) {\n <!-- Title (with link for reset filter) -->\n <a\n href=\"javascript:void(0)\"\n (click)=\"clearFilters()\"\n [matTooltip]=\"t('list.clearFilters')\"\n >{{ listTitle }}</a\n >\n <!---->\n } @else {\n <!-- Title -->\n {{ listTitle }}\n <!---->\n }\n } @else {\n <!-- Default Title -->\n {{ t('list.defaultTitle') }}\n <!---->\n }\n\n @if (query.categoryId && selectedCategory?.[0]; as categoryLabel) {\n <span class=\"material-icons list-title-separator\"> keyboard_arrow_right </span>\n <!-- Category -->\n {{ categoryLabel.label }}\n <!---->\n }\n\n @if (query.filterColumn && query.filterValue) {\n <span class=\"material-icons list-title-separator\"> keyboard_arrow_right </span>\n <!-- Custom Filter -->\n {{ query.filterLabel ?? getColumnLabel(query.filterColumn) }}:\n {{ getColumnValueLabel(query.filterColumn, query.filterValue) }}\n <!---->\n }\n\n @if (query.keyword) {\n <span class=\"material-icons list-title-separator\"> keyboard_arrow_right </span>\n <!-- Keyword -->\n \"{{ query.keyword }}\"\n <!---->\n }\n </span>\n <!---->\n\n @if (query.keyword || query.categoryId || (query.filterColumn && query.filterValue)) {\n <span style=\"width: 8px\"></span>\n <!-- Clear Filters Button -->\n <button\n mat-icon-button\n class=\"clear-filters-button\"\n [matTooltip]=\"t('list.clearFilters')\"\n (click)=\"clearFilters()\"\n >\n <mat-icon>filter_alt_off</mat-icon>\n </button>\n <!---->\n }\n\n <span style=\"flex: 1\"></span>\n\n <!-- Create Button -->\n @if (listConfig?.createButton; as createBtn) {\n @if ($any(createBtn).routerLink; as routerLinkUrl) {\n <!-- Create Button with Router Link -->\n <a\n [routerLink]=\"routerLinkUrl\"\n mat-flat-button\n class=\"create-item-button\"\n style=\"color: white; transform: translateY(-1px)\"\n >\n {{ createBtn.label ?? t('common.create') }}\n <mat-icon style=\"margin-bottom: 1px\">add</mat-icon>\n </a>\n } @else if ($any(createBtn).link; as linkUrl) {\n <!-- Create Button with Link -->\n <a\n [href]=\"linkUrl\"\n mat-flat-button\n class=\"create-item-button\"\n style=\"color: white; transform: translateY(-1px)\"\n >\n {{ createBtn.label ?? t('common.create') }}\n <mat-icon style=\"margin-bottom: 1px\">add</mat-icon>\n </a>\n } @else if ($any(createBtn).click; as handler) {\n <!-- Create Button with Handler -->\n <button\n mat-flat-button\n class=\"create-item-button\"\n style=\"color: white; transform: translateY(-1px)\"\n (click)=\"handler()\"\n >\n {{ createBtn?.label ?? t('common.create') }}\n <mat-icon style=\"margin-bottom: 1px\">add</mat-icon>\n </button>\n }\n }\n <!---->\n\n <span style=\"width: 30px\"></span>\n\n <!-- Search Field -->\n <mat-form-field class=\"search-keyword xs-input\" appearance=\"outline\">\n <mat-icon matPrefix>search</mat-icon>\n <input\n matInput\n [placeholder]=\"\n selectedCategory?.[0]?.label\n ? t('list.searchInCategoryPlaceholder', { category: selectedCategory?.[0]?.label })\n : t('list.searchDefaultPlaceholder')\n \"\n [(ngModel)]=\"query.keyword\"\n (keyup)=\"onKeywordInput()\"\n style=\"padding-top: 3px\"\n />\n\n <!-- Search clear button -->\n <button\n matSuffix\n mat-icon-button\n [attr.aria-label]=\"t('list.clearSearchKeyword')\"\n [matTooltip]=\"t('list.clearSearchKeyword')\"\n (click)=\"query.keyword = ''; this.fetchList()\"\n [style.visibility]=\"query.keyword ? 'visible' : 'hidden'\"\n >\n <mat-icon>highlight_off</mat-icon>\n </button>\n <!---->\n </mat-form-field>\n <!---->\n\n <span style=\"width: 8px\"></span>\n\n <!-- Refresh Button -->\n <button mat-icon-button [matTooltip]=\"t('list.refresh')\" (click)=\"fetchList()\">\n <mat-icon>refresh</mat-icon>\n </button>\n <!---->\n\n <span style=\"width: 5px\"></span>\n\n <!-- CSV Export Button -->\n <button\n mat-icon-button\n [matMenuTriggerFor]=\"csvExportMenu\"\n [matTooltip]=\"t('list.exportAsCsv')\"\n >\n <mat-icon>download</mat-icon>\n </button>\n <!---->\n\n <!-- CSV Export Menu -->\n <mat-menu #csvExportMenu=\"matMenu\">\n @for (encoder of csvExportService.getAvailableEncoders(); track encoder.key) {\n <!-- Export with specific charset -->\n <p style=\"color: #333; font-size: 0.8rem; margin: 1rem 0.75rem 1rem 0.7rem\">\n CSV ({{ encoder.label }})\n </p>\n <button mat-menu-item (click)=\"exportAsCsv(encoder.key)\">\n <mat-icon>download</mat-icon>\n <span>{{ t('list.exportItems', { count: totalCount }) }}</span>\n </button>\n <!---->\n }\n </mat-menu>\n <!---->\n </div>\n</ng-template>\n<!---->\n\n<!-- List (Grid) -->\n<mtx-grid\n [data]=\"gridData\"\n [columns]=\"gridColumns\"\n [showToolbar]=\"true\"\n [toolbarTemplate]=\"toolbarTpl\"\n [length]=\"totalCount\"\n [loading]=\"isLoading\"\n [pageOnFront]=\"false\"\n [pageIndex]=\"query.page\"\n [pageSize]=\"query.perPage\"\n [pageSizeOptions]=\"listConfig?.pageSizes ?? [10, 25, 50, 100]\"\n columnMenuButtonType=\"icon\"\n columnMenuButtonClass=\"column-menu-button\"\n columnMenuButtonIcon=\"view_column\"\n (page)=\"onPageChange($event)\"\n></mtx-grid>\n<!---->\n\n<!-- Cell Template with Link -->\n<ng-template #cellTplWithLink let-row let-index=\"index\" let-col=\"colDef\">\n @if (col.link) {\n <a [href]=\"col.link(row)\">\n @if (col.formatter) {\n {{ col.formatter(row) }}\n } @else {\n {{ row[col.field] }}\n }\n </a>\n } @else if (col.routerLink) {\n @let link = col.routerLink(row);\n @let fragment = link.fragment ? link.fragment : link;\n <a [routerLink]=\"fragment\">\n @if (col.formatter) {\n {{ col.formatter(row) }}\n } @else {\n {{ row[col.field] }}\n }\n </a>\n } @else {\n @if (col.formatter) {\n {{ col.formatter(row) }}\n } @else {\n {{ row[col.field] }}\n }\n }\n</ng-template>\n<!---->\n", styles: [":host{display:flex;gap:1rem;padding:1rem;box-sizing:border-box}lib-list-category-selector{display:block;width:200px;background-color:#fcfcfc;border-radius:9px;padding:.5rem;box-sizing:border-box}lib-list-category-selector .category-list{max-height:300px;overflow-y:auto}::ng-deep .mtx-grid-toolbar{display:block;background-color:#fcfcfc;border-top-left-radius:9px;border-top-right-radius:9px;margin-bottom:1px}::ng-deep .mtx-grid-toolbar .custom-toolbar{display:flex;align-items:center;justify-content:space-between;height:37px}::ng-deep .mtx-grid-toolbar .custom-toolbar .list-title{font-size:1rem;line-height:37px}::ng-deep .mtx-grid-toolbar .custom-toolbar .list-title a{color:var(--mat-theme-primary)}::ng-deep .mtx-grid-toolbar .custom-toolbar .list-title a:hover{text-decoration:underline}::ng-deep .mtx-grid-toolbar .custom-toolbar .list-title .list-title-separator{color:#aaa;vertical-align:middle;margin:auto .2rem 4px}::ng-deep .mtx-grid-toolbar .custom-toolbar .clear-filters-button{color:var(--mat-sys-on-primary-container);min-width:32px;padding:0;vertical-align:middle}::ng-deep .mtx-grid-toolbar .custom-toolbar .clear-filters-button mat-icon{vertical-align:middle;margin-bottom:3px}::ng-deep .mtx-grid-toolbar .custom-toolbar .create-item-button{text-decoration:none!important}::ng-deep .mtx-grid-toolbar .custom-toolbar .search-keyword{width:15rem;--mat-form-field-container-height: 18.5px;--mat-form-field-container-text-line-height: 18.5px;--mat-form-field-container-vertical-padding: 8.5px;--mat-form-field-outlined-container-shape: 28px;--mat-form-field-subscript-text-line-height: 0px}::ng-deep .mtx-grid-toolbar .custom-toolbar button{color:var(--mat-sys-on-primary-container)}::ng-deep .mtx-grid-toolbar ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}::ng-deep .mtx-grid-toolbar ::ng-deep mtx-grid-column-menu button[mat-icon-button]{color:var(--mat-sys-on-primary-container);margin-right:.5rem}::ng-deep .mtx-grid-main{background-color:#fcfcfc}table{width:100%}::ng-deep table thead,::ng-deep table tbody{background-color:#fcfcfc!important}::ng-deep .mtx-grid-footer,::ng-deep .mat-mdc-paginator{display:block;background-color:#fcfcfc!important;border-bottom-left-radius:9px!important;border-bottom-right-radius:9px!important}a,::ng-deep table a,::ng-deep table a:visited{color:var(--mat-sys-on-primary-container);text-decoration:none}a:hover,::ng-deep table a:hover,::ng-deep table a:visited:hover{text-decoration:underline}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.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: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MtxGridModule }, { kind: "component", type: i2$2.MtxGrid, selector: "mtx-grid", inputs: ["displayedColumns", "columns", "data", "length", "loading", "trackBy", "columnResizable", "emptyValuePlaceholder", "pageOnFront", "showPaginator", "pageDisabled", "showFirstLastButtons", "pageIndex", "pageSize", "pageSizeOptions", "hidePageSize", "paginationTemplate", "sortOnFront", "sortActive", "sortDirection", "sortDisableClear", "sortDisabled", "sortStart", "rowHover", "rowStriped", "expandable", "expansionTemplate", "multiSelectable", "multiSelectionWithClick", "rowSelectable", "hideRowSelectionCheckbox", "disableRowClickSelection", "rowSelectionFormatter", "rowClassFormatter", "rowSelected", "cellSelectable", "showToolbar", "toolbarTitle", "toolbarTemplate", "columnHideable", "columnHideableChecked", "columnSortable", "columnPinnable", "columnPinOptions", "showColumnMenuButton", "columnMenuButtonText", "columnMenuButtonType", "columnMenuButtonColor", "columnMenuButtonClass", "columnMenuButtonIcon", "columnMenuButtonFontIcon", "columnMenuButtonSvgIcon", "showColumnMenuHeader", "columnMenuHeaderText", "columnMenuHeaderTemplate", "showColumnMenuFooter", "columnMenuFooterText", "columnMenuFooterTemplate", "noResultText", "noResultTemplate", "headerTemplate", "headerExtraTemplate", "cellTemplate", "useContentRowTemplate", "useContentHeaderRowTemplate", "useContentFooterRowTemplate", "showSummary", "summaryTemplate", "showSidebar", "sidebarTemplate", "showStatusbar", "statusbarTemplate"], outputs: ["page", "sortChange", "rowClick", "rowContextMenu", "expansionChange", "rowSelectedChange", "cellSelectedChange", "columnChange"], exportAs: ["mtxGrid"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "directive", type: MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "directive", type: MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "component", type: ListCategorySelector, selector: "lib-list-category-selector", inputs: ["config", "selectedCategory"], outputs: ["selectedCategoryChange"] }, { kind: "component", type: MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] });
|
|
583
1113
|
}
|
|
584
1114
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: NgxThinAdminList, decorators: [{
|
|
585
1115
|
type: Component,
|
|
@@ -599,20 +1129,48 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
599
1129
|
MatMenuItem,
|
|
600
1130
|
MatMenuTrigger,
|
|
601
1131
|
RouterLink,
|
|
602
|
-
], template: "<!-- Category Selector -->\n@if (categorySelectorConfig) {\n <lib-list-category-selector\n [config]=\"categorySelectorConfig\"\n [(selectedCategory)]=\"selectedCategory\"\n (selectedCategoryChange)=\"onSelectedCategoryChange($event)\"\n ></lib-list-category-selector>\n}\n<!---->\n\n<!-- Toolbar Template -->\n<ng-template #toolbarTpl>\n <div class=\"custom-toolbar\">\n <!-- List Title -->\n <span class=\"list-title\">\n @if (listConfig?.title; as listTitle) {\n @if (query.keyword || query.categoryId) {\n <!-- Title (with link for reset filter) -->\n <a\n href=\"javascript:void(0)\"\n (click)=\"
|
|
1132
|
+
], template: "<!-- Category Selector -->\n@if (categorySelectorConfig) {\n <lib-list-category-selector\n [config]=\"categorySelectorConfig\"\n [(selectedCategory)]=\"selectedCategory\"\n (selectedCategoryChange)=\"onSelectedCategoryChange($event)\"\n ></lib-list-category-selector>\n}\n<!---->\n\n<!-- Toolbar Template -->\n<ng-template #toolbarTpl>\n <div class=\"custom-toolbar\">\n <!-- List Title -->\n <span class=\"list-title\">\n @if (listConfig?.title; as listTitle) {\n @if (query.keyword || query.categoryId || (query.filterColumn && query.filterValue)) {\n <!-- Title (with link for reset filter) -->\n <a\n href=\"javascript:void(0)\"\n (click)=\"clearFilters()\"\n [matTooltip]=\"t('list.clearFilters')\"\n >{{ listTitle }}</a\n >\n <!---->\n } @else {\n <!-- Title -->\n {{ listTitle }}\n <!---->\n }\n } @else {\n <!-- Default Title -->\n {{ t('list.defaultTitle') }}\n <!---->\n }\n\n @if (query.categoryId && selectedCategory?.[0]; as categoryLabel) {\n <span class=\"material-icons list-title-separator\"> keyboard_arrow_right </span>\n <!-- Category -->\n {{ categoryLabel.label }}\n <!---->\n }\n\n @if (query.filterColumn && query.filterValue) {\n <span class=\"material-icons list-title-separator\"> keyboard_arrow_right </span>\n <!-- Custom Filter -->\n {{ query.filterLabel ?? getColumnLabel(query.filterColumn) }}:\n {{ getColumnValueLabel(query.filterColumn, query.filterValue) }}\n <!---->\n }\n\n @if (query.keyword) {\n <span class=\"material-icons list-title-separator\"> keyboard_arrow_right </span>\n <!-- Keyword -->\n \"{{ query.keyword }}\"\n <!---->\n }\n </span>\n <!---->\n\n @if (query.keyword || query.categoryId || (query.filterColumn && query.filterValue)) {\n <span style=\"width: 8px\"></span>\n <!-- Clear Filters Button -->\n <button\n mat-icon-button\n class=\"clear-filters-button\"\n [matTooltip]=\"t('list.clearFilters')\"\n (click)=\"clearFilters()\"\n >\n <mat-icon>filter_alt_off</mat-icon>\n </button>\n <!---->\n }\n\n <span style=\"flex: 1\"></span>\n\n <!-- Create Button -->\n @if (listConfig?.createButton; as createBtn) {\n @if ($any(createBtn).routerLink; as routerLinkUrl) {\n <!-- Create Button with Router Link -->\n <a\n [routerLink]=\"routerLinkUrl\"\n mat-flat-button\n class=\"create-item-button\"\n style=\"color: white; transform: translateY(-1px)\"\n >\n {{ createBtn.label ?? t('common.create') }}\n <mat-icon style=\"margin-bottom: 1px\">add</mat-icon>\n </a>\n } @else if ($any(createBtn).link; as linkUrl) {\n <!-- Create Button with Link -->\n <a\n [href]=\"linkUrl\"\n mat-flat-button\n class=\"create-item-button\"\n style=\"color: white; transform: translateY(-1px)\"\n >\n {{ createBtn.label ?? t('common.create') }}\n <mat-icon style=\"margin-bottom: 1px\">add</mat-icon>\n </a>\n } @else if ($any(createBtn).click; as handler) {\n <!-- Create Button with Handler -->\n <button\n mat-flat-button\n class=\"create-item-button\"\n style=\"color: white; transform: translateY(-1px)\"\n (click)=\"handler()\"\n >\n {{ createBtn?.label ?? t('common.create') }}\n <mat-icon style=\"margin-bottom: 1px\">add</mat-icon>\n </button>\n }\n }\n <!---->\n\n <span style=\"width: 30px\"></span>\n\n <!-- Search Field -->\n <mat-form-field class=\"search-keyword xs-input\" appearance=\"outline\">\n <mat-icon matPrefix>search</mat-icon>\n <input\n matInput\n [placeholder]=\"\n selectedCategory?.[0]?.label\n ? t('list.searchInCategoryPlaceholder', { category: selectedCategory?.[0]?.label })\n : t('list.searchDefaultPlaceholder')\n \"\n [(ngModel)]=\"query.keyword\"\n (keyup)=\"onKeywordInput()\"\n style=\"padding-top: 3px\"\n />\n\n <!-- Search clear button -->\n <button\n matSuffix\n mat-icon-button\n [attr.aria-label]=\"t('list.clearSearchKeyword')\"\n [matTooltip]=\"t('list.clearSearchKeyword')\"\n (click)=\"query.keyword = ''; this.fetchList()\"\n [style.visibility]=\"query.keyword ? 'visible' : 'hidden'\"\n >\n <mat-icon>highlight_off</mat-icon>\n </button>\n <!---->\n </mat-form-field>\n <!---->\n\n <span style=\"width: 8px\"></span>\n\n <!-- Refresh Button -->\n <button mat-icon-button [matTooltip]=\"t('list.refresh')\" (click)=\"fetchList()\">\n <mat-icon>refresh</mat-icon>\n </button>\n <!---->\n\n <span style=\"width: 5px\"></span>\n\n <!-- CSV Export Button -->\n <button\n mat-icon-button\n [matMenuTriggerFor]=\"csvExportMenu\"\n [matTooltip]=\"t('list.exportAsCsv')\"\n >\n <mat-icon>download</mat-icon>\n </button>\n <!---->\n\n <!-- CSV Export Menu -->\n <mat-menu #csvExportMenu=\"matMenu\">\n @for (encoder of csvExportService.getAvailableEncoders(); track encoder.key) {\n <!-- Export with specific charset -->\n <p style=\"color: #333; font-size: 0.8rem; margin: 1rem 0.75rem 1rem 0.7rem\">\n CSV ({{ encoder.label }})\n </p>\n <button mat-menu-item (click)=\"exportAsCsv(encoder.key)\">\n <mat-icon>download</mat-icon>\n <span>{{ t('list.exportItems', { count: totalCount }) }}</span>\n </button>\n <!---->\n }\n </mat-menu>\n <!---->\n </div>\n</ng-template>\n<!---->\n\n<!-- List (Grid) -->\n<mtx-grid\n [data]=\"gridData\"\n [columns]=\"gridColumns\"\n [showToolbar]=\"true\"\n [toolbarTemplate]=\"toolbarTpl\"\n [length]=\"totalCount\"\n [loading]=\"isLoading\"\n [pageOnFront]=\"false\"\n [pageIndex]=\"query.page\"\n [pageSize]=\"query.perPage\"\n [pageSizeOptions]=\"listConfig?.pageSizes ?? [10, 25, 50, 100]\"\n columnMenuButtonType=\"icon\"\n columnMenuButtonClass=\"column-menu-button\"\n columnMenuButtonIcon=\"view_column\"\n (page)=\"onPageChange($event)\"\n></mtx-grid>\n<!---->\n\n<!-- Cell Template with Link -->\n<ng-template #cellTplWithLink let-row let-index=\"index\" let-col=\"colDef\">\n @if (col.link) {\n <a [href]=\"col.link(row)\">\n @if (col.formatter) {\n {{ col.formatter(row) }}\n } @else {\n {{ row[col.field] }}\n }\n </a>\n } @else if (col.routerLink) {\n @let link = col.routerLink(row);\n @let fragment = link.fragment ? link.fragment : link;\n <a [routerLink]=\"fragment\">\n @if (col.formatter) {\n {{ col.formatter(row) }}\n } @else {\n {{ row[col.field] }}\n }\n </a>\n } @else {\n @if (col.formatter) {\n {{ col.formatter(row) }}\n } @else {\n {{ row[col.field] }}\n }\n }\n</ng-template>\n<!---->\n", styles: [":host{display:flex;gap:1rem;padding:1rem;box-sizing:border-box}lib-list-category-selector{display:block;width:200px;background-color:#fcfcfc;border-radius:9px;padding:.5rem;box-sizing:border-box}lib-list-category-selector .category-list{max-height:300px;overflow-y:auto}::ng-deep .mtx-grid-toolbar{display:block;background-color:#fcfcfc;border-top-left-radius:9px;border-top-right-radius:9px;margin-bottom:1px}::ng-deep .mtx-grid-toolbar .custom-toolbar{display:flex;align-items:center;justify-content:space-between;height:37px}::ng-deep .mtx-grid-toolbar .custom-toolbar .list-title{font-size:1rem;line-height:37px}::ng-deep .mtx-grid-toolbar .custom-toolbar .list-title a{color:var(--mat-theme-primary)}::ng-deep .mtx-grid-toolbar .custom-toolbar .list-title a:hover{text-decoration:underline}::ng-deep .mtx-grid-toolbar .custom-toolbar .list-title .list-title-separator{color:#aaa;vertical-align:middle;margin:auto .2rem 4px}::ng-deep .mtx-grid-toolbar .custom-toolbar .clear-filters-button{color:var(--mat-sys-on-primary-container);min-width:32px;padding:0;vertical-align:middle}::ng-deep .mtx-grid-toolbar .custom-toolbar .clear-filters-button mat-icon{vertical-align:middle;margin-bottom:3px}::ng-deep .mtx-grid-toolbar .custom-toolbar .create-item-button{text-decoration:none!important}::ng-deep .mtx-grid-toolbar .custom-toolbar .search-keyword{width:15rem;--mat-form-field-container-height: 18.5px;--mat-form-field-container-text-line-height: 18.5px;--mat-form-field-container-vertical-padding: 8.5px;--mat-form-field-outlined-container-shape: 28px;--mat-form-field-subscript-text-line-height: 0px}::ng-deep .mtx-grid-toolbar .custom-toolbar button{color:var(--mat-sys-on-primary-container)}::ng-deep .mtx-grid-toolbar ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}::ng-deep .mtx-grid-toolbar ::ng-deep mtx-grid-column-menu button[mat-icon-button]{color:var(--mat-sys-on-primary-container);margin-right:.5rem}::ng-deep .mtx-grid-main{background-color:#fcfcfc}table{width:100%}::ng-deep table thead,::ng-deep table tbody{background-color:#fcfcfc!important}::ng-deep .mtx-grid-footer,::ng-deep .mat-mdc-paginator{display:block;background-color:#fcfcfc!important;border-bottom-left-radius:9px!important;border-bottom-right-radius:9px!important}a,::ng-deep table a,::ng-deep table a:visited{color:var(--mat-sys-on-primary-container);text-decoration:none}a:hover,::ng-deep table a:hover,::ng-deep table a:visited:hover{text-decoration:underline}\n"] }]
|
|
603
1133
|
}], propDecorators: { listConfig: [{
|
|
604
1134
|
type: Input
|
|
605
1135
|
}], categorySelectorConfig: [{
|
|
606
1136
|
type: Input
|
|
1137
|
+
}], itemDeleterConfig: [{
|
|
1138
|
+
type: Input
|
|
607
1139
|
}], listColumns: [{
|
|
608
1140
|
type: Input
|
|
609
1141
|
}], query: [{
|
|
610
1142
|
type: Input
|
|
1143
|
+
}], queryChange: [{
|
|
1144
|
+
type: Output
|
|
611
1145
|
}], cellTplWithLink: [{
|
|
612
1146
|
type: ViewChild,
|
|
613
1147
|
args: ['cellTplWithLink', { static: true }]
|
|
1148
|
+
}], categorySelector: [{
|
|
1149
|
+
type: ViewChild,
|
|
1150
|
+
args: [ListCategorySelector]
|
|
614
1151
|
}] } });
|
|
615
1152
|
|
|
1153
|
+
/**
|
|
1154
|
+
* Injection token for providing extra formly config to NgxThinAdminEditor.
|
|
1155
|
+
* Use provideNgxThinAdminFormlyConfig() to register from the consumer app.
|
|
1156
|
+
*/
|
|
1157
|
+
const NGX_THIN_ADMIN_EXTRA_FORMLY_CONFIG = new InjectionToken('NGX_THIN_ADMIN_EXTRA_FORMLY_CONFIG');
|
|
1158
|
+
/**
|
|
1159
|
+
* Returns a provider that adds custom formly types, wrappers, etc. to NgxThinAdminEditor.
|
|
1160
|
+
*
|
|
1161
|
+
* @example
|
|
1162
|
+
* provideNgxThinAdminFormlyConfig({
|
|
1163
|
+
* types: [{ name: 'qrcode', component: QrcodeFieldComponent }],
|
|
1164
|
+
* })
|
|
1165
|
+
*/
|
|
1166
|
+
function provideNgxThinAdminFormlyConfig(config) {
|
|
1167
|
+
return {
|
|
1168
|
+
provide: NGX_THIN_ADMIN_EXTRA_FORMLY_CONFIG,
|
|
1169
|
+
useValue: config,
|
|
1170
|
+
multi: true,
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
|
|
616
1174
|
class FormlyMatPrefixAddonWrapper extends FieldWrapper {
|
|
617
1175
|
matPrefix;
|
|
618
1176
|
matSuffix;
|
|
@@ -704,26 +1262,6 @@ function formlyAddonsExtension(field) {
|
|
|
704
1262
|
}
|
|
705
1263
|
}
|
|
706
1264
|
|
|
707
|
-
function getErrorMessage(error) {
|
|
708
|
-
if (typeof error === 'string') {
|
|
709
|
-
return error;
|
|
710
|
-
}
|
|
711
|
-
else if (error instanceof Error) {
|
|
712
|
-
return error.message;
|
|
713
|
-
}
|
|
714
|
-
else if (error?.error?.message) {
|
|
715
|
-
if (Array.isArray(error.error.message)) {
|
|
716
|
-
return error.error.message[0];
|
|
717
|
-
}
|
|
718
|
-
else {
|
|
719
|
-
return error.error.message;
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
else {
|
|
723
|
-
return 'An unknown error occurred';
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
|
|
727
1265
|
class FormlyFieldFile extends FieldType {
|
|
728
1266
|
/** 表示用のファイル名 */
|
|
729
1267
|
fileName = '';
|
|
@@ -767,7 +1305,7 @@ class FormlyFieldFile extends FieldType {
|
|
|
767
1305
|
<mat-hint>{{ props.description }}</mat-hint>
|
|
768
1306
|
}
|
|
769
1307
|
</mat-form-field>
|
|
770
|
-
`, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: FormlyAttributes, selector: "[formlyAttributes]", inputs: ["formlyAttributes", "id"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i2
|
|
1308
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: FormlyAttributes, selector: "[formlyAttributes]", inputs: ["formlyAttributes", "id"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i2.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i1.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }] });
|
|
771
1309
|
}
|
|
772
1310
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: FormlyFieldFile, decorators: [{
|
|
773
1311
|
type: Component,
|
|
@@ -831,6 +1369,10 @@ class NgxThinAdminEditor {
|
|
|
831
1369
|
* Id of the item being edited.
|
|
832
1370
|
*/
|
|
833
1371
|
itemId;
|
|
1372
|
+
/**
|
|
1373
|
+
* Config for item deleter
|
|
1374
|
+
*/
|
|
1375
|
+
itemDeleterConfig;
|
|
834
1376
|
/**
|
|
835
1377
|
* Internal state for form
|
|
836
1378
|
*/
|
|
@@ -839,11 +1381,24 @@ class NgxThinAdminEditor {
|
|
|
839
1381
|
form = new FormGroup({});
|
|
840
1382
|
data = {};
|
|
841
1383
|
formData = {};
|
|
1384
|
+
/**
|
|
1385
|
+
* For history back
|
|
1386
|
+
*/
|
|
1387
|
+
windowHistory = window.history;
|
|
842
1388
|
/**
|
|
843
1389
|
* Services
|
|
844
1390
|
*/
|
|
845
1391
|
snackbar = inject(MatSnackBar);
|
|
846
1392
|
cdr = inject(ChangeDetectorRef);
|
|
1393
|
+
translate = inject(NGX_THIN_ADMIN_TRANSLATE);
|
|
1394
|
+
dialog = inject(MatDialog);
|
|
1395
|
+
router = inject(Router);
|
|
1396
|
+
t(key, params) {
|
|
1397
|
+
return this.translate(key, params);
|
|
1398
|
+
}
|
|
1399
|
+
get editorItemType() {
|
|
1400
|
+
return this.editorConfig?.singularLabel || this.t('item.defaultSingular');
|
|
1401
|
+
}
|
|
847
1402
|
ngOnChanges(changes) {
|
|
848
1403
|
// Fetcher
|
|
849
1404
|
if (changes.editorConfig &&
|
|
@@ -925,7 +1480,7 @@ class NgxThinAdminEditor {
|
|
|
925
1480
|
}
|
|
926
1481
|
async save() {
|
|
927
1482
|
if (this.form.status === 'INVALID') {
|
|
928
|
-
this.snackbar.open(
|
|
1483
|
+
this.snackbar.open(`Error: ${this.t('editor.errorInvalidInput')}`, undefined, {
|
|
929
1484
|
duration: 3000,
|
|
930
1485
|
});
|
|
931
1486
|
// Mark field to show validation errors
|
|
@@ -936,7 +1491,7 @@ class NgxThinAdminEditor {
|
|
|
936
1491
|
return;
|
|
937
1492
|
}
|
|
938
1493
|
if (!this.editorConfig?.saver) {
|
|
939
|
-
this.snackbar.open(
|
|
1494
|
+
this.snackbar.open(`Error: ${this.t('editor.errorNoSaver')}`, undefined, {
|
|
940
1495
|
duration: 3000,
|
|
941
1496
|
});
|
|
942
1497
|
return;
|
|
@@ -946,8 +1501,9 @@ class NgxThinAdminEditor {
|
|
|
946
1501
|
this.isSaving = true;
|
|
947
1502
|
// Call the saver function
|
|
948
1503
|
let result;
|
|
1504
|
+
let showSuccessMessage = true;
|
|
949
1505
|
try {
|
|
950
|
-
let res = await this.editorConfig
|
|
1506
|
+
let res = await this.editorConfig.saver(this.formData, this.itemId);
|
|
951
1507
|
if (res instanceof Response) {
|
|
952
1508
|
if (!res.ok) {
|
|
953
1509
|
const data = await res.json();
|
|
@@ -955,6 +1511,9 @@ class NgxThinAdminEditor {
|
|
|
955
1511
|
}
|
|
956
1512
|
res = await res.json();
|
|
957
1513
|
}
|
|
1514
|
+
else if (typeof res === 'undefined') {
|
|
1515
|
+
showSuccessMessage = false;
|
|
1516
|
+
}
|
|
958
1517
|
result = res;
|
|
959
1518
|
}
|
|
960
1519
|
catch (e) {
|
|
@@ -968,9 +1527,11 @@ class NgxThinAdminEditor {
|
|
|
968
1527
|
return;
|
|
969
1528
|
}
|
|
970
1529
|
// Show success message
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1530
|
+
if (showSuccessMessage) {
|
|
1531
|
+
this.snackbar.open(this.t('editor.successSaved'), undefined, {
|
|
1532
|
+
duration: 3000,
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
974
1535
|
// Update form data with the result from the saver (e.g., to get generated ID)
|
|
975
1536
|
this.data = { ...result };
|
|
976
1537
|
this.formData = { ...result };
|
|
@@ -981,8 +1542,60 @@ class NgxThinAdminEditor {
|
|
|
981
1542
|
}
|
|
982
1543
|
this.isSaving = false;
|
|
983
1544
|
}
|
|
1545
|
+
async openDeletionDialog() {
|
|
1546
|
+
if (!this.itemDeleterConfig?.deleter || this.itemId === undefined) {
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
// Get the label and ID to query in the confirmation dialog
|
|
1550
|
+
const id = this.itemId;
|
|
1551
|
+
const label = this.editorConfig?.labelFieldKey
|
|
1552
|
+
? this.data[this.editorConfig.labelFieldKey]
|
|
1553
|
+
: id;
|
|
1554
|
+
const itemType = this.editorItemType;
|
|
1555
|
+
// Open the confirmation dialog
|
|
1556
|
+
const dialogRef = this.dialog.open(ConfirmDialog, {
|
|
1557
|
+
width: '400px',
|
|
1558
|
+
data: {
|
|
1559
|
+
title: this.t('item.deletionDialogTitle', { itemType }),
|
|
1560
|
+
message: this.t('item.deletionDialogMessage', { label, id }),
|
|
1561
|
+
positiveButtonText: this.t('common.delete'),
|
|
1562
|
+
cancelButtonText: this.t('common.cancel'),
|
|
1563
|
+
},
|
|
1564
|
+
});
|
|
1565
|
+
const result = await lastValueFrom(dialogRef.afterClosed());
|
|
1566
|
+
if (!result) {
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
// Execute delete process
|
|
1570
|
+
await this.deleteItem(id, label);
|
|
1571
|
+
}
|
|
1572
|
+
async deleteItem(id, label) {
|
|
1573
|
+
if (!this.itemDeleterConfig?.deleter) {
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
try {
|
|
1577
|
+
const res = await this.itemDeleterConfig.deleter(id);
|
|
1578
|
+
if (res && 'ok' in res && !res.ok) {
|
|
1579
|
+
const errorMessage = getErrorMessage(res);
|
|
1580
|
+
throw new Error(errorMessage);
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
catch (e) {
|
|
1584
|
+
const errorMessage = getErrorMessage(e);
|
|
1585
|
+
this.snackbar.open(`Error: ${errorMessage || this.t('item.errorFailedDelete', { label })}`, undefined, { duration: 3000 });
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
// Show success message
|
|
1589
|
+
this.snackbar.open(this.t('item.successDeleted', { label }), undefined, {
|
|
1590
|
+
duration: 3000,
|
|
1591
|
+
});
|
|
1592
|
+
// Subsequence routing
|
|
1593
|
+
if (this.itemDeleterConfig.afterDeleteNavigateTo) {
|
|
1594
|
+
this.router.navigate([this.itemDeleterConfig.afterDeleteNavigateTo]);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
984
1597
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: NgxThinAdminEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
985
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: NgxThinAdminEditor, isStandalone: true, selector: "ngx-thin-admin-editor", inputs: { editorConfig: "editorConfig", editorFields: "editorFields", itemId: "itemId" }, providers: [
|
|
1598
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: NgxThinAdminEditor, isStandalone: true, selector: "ngx-thin-admin-editor", inputs: { editorConfig: "editorConfig", editorFields: "editorFields", itemId: "itemId", itemDeleterConfig: "itemDeleterConfig" }, providers: [
|
|
986
1599
|
provideFormlyCore(withFormlyMaterial()),
|
|
987
1600
|
provideFormlyCore({
|
|
988
1601
|
wrappers: [
|
|
@@ -999,6 +1612,24 @@ class NgxThinAdminEditor {
|
|
|
999
1612
|
},
|
|
1000
1613
|
],
|
|
1001
1614
|
}),
|
|
1615
|
+
{
|
|
1616
|
+
provide: FORMLY_CONFIG,
|
|
1617
|
+
multi: true,
|
|
1618
|
+
useFactory: () => {
|
|
1619
|
+
const configs = inject(NGX_THIN_ADMIN_EXTRA_FORMLY_CONFIG, {
|
|
1620
|
+
optional: true,
|
|
1621
|
+
skipSelf: true,
|
|
1622
|
+
});
|
|
1623
|
+
if (!configs || configs.length === 0)
|
|
1624
|
+
return {};
|
|
1625
|
+
return configs.reduce((acc, c) => ({
|
|
1626
|
+
types: [...(acc.types ?? []), ...(c.types ?? [])],
|
|
1627
|
+
wrappers: [...(acc.wrappers ?? []), ...(c.wrappers ?? [])],
|
|
1628
|
+
validators: [...(acc.validators ?? []), ...(c.validators ?? [])],
|
|
1629
|
+
extensions: [...(acc.extensions ?? []), ...(c.extensions ?? [])],
|
|
1630
|
+
}), {});
|
|
1631
|
+
},
|
|
1632
|
+
},
|
|
1002
1633
|
{
|
|
1003
1634
|
// Apply default appearance and other settings to all Material form fields in this component
|
|
1004
1635
|
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
|
|
@@ -1008,7 +1639,7 @@ class NgxThinAdminEditor {
|
|
|
1008
1639
|
subscriptSizing: 'dynamic',
|
|
1009
1640
|
},
|
|
1010
1641
|
},
|
|
1011
|
-
], usesOnChanges: true, ngImport: i0, template: "<mat-card>\n <mat-card-header>\n <h3>\n @if (itemId !== undefined && $any(data); as editItem) {\n @if (editorConfig?.labelFieldKey && editItem[editorConfig!.labelFieldKey!]) {\n <!-- e.g., \"Edit - Taro (taro) \" -->\n
|
|
1642
|
+
], usesOnChanges: true, ngImport: i0, template: "<mat-card>\n <mat-card-header style=\"position: relative\">\n <!-- Back button -->\n @if (editorConfig?.backButton) {\n @if ($any(editorConfig?.backButton).historyBack && windowHistory.length > 1) {\n <button mat-button (click)=\"windowHistory.back()\" class=\"back-link\">\n <mat-icon>arrow_back_ios_new</mat-icon>\n {{ editorConfig?.backButton?.label ?? t('editor.backToList') }}\n </button>\n } @else if ($any(editorConfig?.backButton).routerLink; as backButtonLink) {\n <a [routerLink]=\"backButtonLink\" mat-button class=\"back-link\">\n <mat-icon>arrow_back_ios_new</mat-icon>\n {{ editorConfig?.backButton?.label ?? t('editor.backToList') }}\n </a>\n } @else if ($any(editorConfig?.backButton).link; as backButtonLink) {\n <a [href]=\"backButtonLink\" mat-button class=\"back-link\">\n <mat-icon>arrow_back_ios_new</mat-icon>\n {{ editorConfig?.backButton?.label ?? t('editor.backToList') }}\n </a>\n } @else if ($any(editorConfig?.backButton).click; as handler) {\n <button mat-button (click)=\"handler()\" class=\"back-link\">\n <mat-icon>arrow_back_ios_new</mat-icon>\n {{ editorConfig?.backButton?.label ?? t('editor.backToList') }}\n </button>\n }\n }\n <!---->\n\n <!-- Editor title -->\n <h3 style=\"margin: 0\" [style.margin-top]=\"editorConfig?.backButton ? '1rem' : '0'\">\n @if (itemId !== undefined && $any(data); as editItem) {\n @if (editorConfig?.labelFieldKey && editItem[editorConfig!.labelFieldKey!]) {\n <!-- e.g., \"Edit - Taro (taro) \" -->\n {{ t('common.edit') }} <span style=\"color: #aaaaaa\">-</span>\n <span class=\"editor-item-label\">{{ editItem[editorConfig!.labelFieldKey!] }}</span>\n @if (editorConfig?.idFieldKey && editItem[editorConfig!.idFieldKey!]) {\n <small class=\"editor-header-sub\">{{ editItem[editorConfig!.idFieldKey!] }}</small>\n }\n <!---->\n } @else if (editItem.id) {\n <!-- e.g., \"Edit - ID: 123\" -->\n {{ t('common.edit') }} <span style=\"color: #aaaaaa\">-</span>\n {{ t('editor.idLabel') }}:\n <span class=\"editor-item-label\">{{ editItem.id }}</span>\n <!---->\n } @else {\n <!-- e.g., \"Edit Account (123)\" -->\n {{ t('editor.titleForEdit', { itemType: editorItemType }) }}\n @if (editorConfig?.idFieldKey && editItem[editorConfig!.idFieldKey!]) {\n <small class=\"editor-header-sub\">{{ editItem[editorConfig!.idFieldKey!] }}</small>\n }\n <!---->\n }\n } @else {\n <!-- Create Account -->\n {{ t('editor.titleForCreate', { itemType: editorItemType }) }}\n <!---->\n }\n </h3>\n <!---->\n </mat-card-header>\n\n <mat-card-content>\n @if (isLoading || isSaving) {\n <mat-progress-bar mode=\"indeterminate\" style=\"margin-top: 1rem\"></mat-progress-bar>\n }\n\n @if (!isLoading) {\n <form [formGroup]=\"form\" (ngSubmit)=\"save()\">\n <formly-form\n [form]=\"form\"\n [fields]=\"$any(editorFields) ?? []\"\n [model]=\"formData\"\n ></formly-form>\n\n <!-- Button Area -->\n <div\n class=\"footer-buttons\"\n style=\"\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-top: 0.5rem;\n gap: 1rem;\n \"\n >\n <!-- Delete button (visible only in edit mode and if itemDeleterConfig is provided) -->\n @if (itemDeleterConfig?.deleter && itemId !== undefined) {\n <button\n type=\"button\"\n class=\"delete-button\"\n matButton=\"filled\"\n color=\"warn\"\n (click)=\"openDeletionDialog()\"\n [disabled]=\"isLoading || isSaving\"\n >\n {{ t('common.delete') }}\n </button>\n } @else {\n <span></span>\n }\n <!---->\n\n <!-- Custom footer buttons -->\n @if (editorConfig?.footerButtons) {\n @for (buttonConfig of editorConfig?.footerButtons; track buttonConfig.label) {\n @if (buttonConfig.show ? buttonConfig.show($any(formData), itemId) : true) {\n <button\n type=\"button\"\n mat-stroked-button\n (click)=\"buttonConfig.click($any(formData), itemId)\"\n [disabled]=\"isLoading || isSaving\"\n [ngClass]=\"buttonConfig.className\"\n >\n {{ buttonConfig.label }}\n </button>\n }\n }\n }\n <!---->\n\n <span style=\"flex: 1\"></span>\n\n <!-- Submit button (visible only if saver is provided) -->\n @if (editorConfig?.saver) {\n <button\n type=\"submit\"\n class=\"save-button\"\n matButton=\"filled\"\n style=\"color: white; transform: translateY(-0.3rem)\"\n >\n @if (itemId !== undefined) {\n {{ t('editor.saveForEdit') }}\n } @else {\n {{ t('editor.saveForCreate') }}\n }\n </button>\n }\n <!---->\n </div>\n <!---->\n </form>\n }\n </mat-card-content>\n</mat-card>\n", styles: [":host{max-width:600px;display:block;margin:0 auto}mat-card{padding:2rem}mat-card-header h3{font-size:1.2rem;font-weight:400}mat-card-header .editor-item-label{margin-left:1rem}mat-card-header .editor-header-sub{color:#555;margin-left:.85rem;font-size:.9rem;font-style:italic}form{margin-top:2rem}form ::ng-deep .mat-mdc-form-field-subscript-wrapper{margin-bottom:14px!important}.back-link{display:flex;position:absolute;top:-1.5rem;left:-1rem;align-items:center;gap:.3rem;font-size:.8rem}.back-link mat-icon{height:.8rem;width:.8rem;font-size:.8rem}.footer-buttons .delete-button{--mat-button-filled-container-color: var(--mat-sys-error);--mat-button-filled-label-text-color: var(--mat-sys-on-error)}.footer-buttons .save-button{--mat-button-filled-container-color: var(--mat-sys-primary);--mat-button-filled-label-text-color: var(--mat-sys-on-primary)}a,::ng-deep table a,::ng-deep table a:visited{color:var(--mat-sys-on-primary-container);text-decoration:none}a:hover,::ng-deep table a:hover,::ng-deep table a:visited:hover{text-decoration:underline}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: FormlyForm, selector: "formly-form", inputs: ["form", "model", "fields", "options"], outputs: ["modelChange"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "component", type: MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: MatCardContent, selector: "mat-card-content" }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
|
|
1012
1643
|
}
|
|
1013
1644
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: NgxThinAdminEditor, decorators: [{
|
|
1014
1645
|
type: Component,
|
|
@@ -1016,10 +1647,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1016
1647
|
ReactiveFormsModule,
|
|
1017
1648
|
FormlyForm,
|
|
1018
1649
|
MatButton,
|
|
1650
|
+
MatIcon,
|
|
1651
|
+
RouterLink,
|
|
1019
1652
|
MatProgressBar,
|
|
1020
1653
|
MatCard,
|
|
1021
1654
|
MatCardHeader,
|
|
1022
1655
|
MatCardContent,
|
|
1656
|
+
CommonModule,
|
|
1023
1657
|
], providers: [
|
|
1024
1658
|
provideFormlyCore(withFormlyMaterial()),
|
|
1025
1659
|
provideFormlyCore({
|
|
@@ -1037,6 +1671,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1037
1671
|
},
|
|
1038
1672
|
],
|
|
1039
1673
|
}),
|
|
1674
|
+
{
|
|
1675
|
+
provide: FORMLY_CONFIG,
|
|
1676
|
+
multi: true,
|
|
1677
|
+
useFactory: () => {
|
|
1678
|
+
const configs = inject(NGX_THIN_ADMIN_EXTRA_FORMLY_CONFIG, {
|
|
1679
|
+
optional: true,
|
|
1680
|
+
skipSelf: true,
|
|
1681
|
+
});
|
|
1682
|
+
if (!configs || configs.length === 0)
|
|
1683
|
+
return {};
|
|
1684
|
+
return configs.reduce((acc, c) => ({
|
|
1685
|
+
types: [...(acc.types ?? []), ...(c.types ?? [])],
|
|
1686
|
+
wrappers: [...(acc.wrappers ?? []), ...(c.wrappers ?? [])],
|
|
1687
|
+
validators: [...(acc.validators ?? []), ...(c.validators ?? [])],
|
|
1688
|
+
extensions: [...(acc.extensions ?? []), ...(c.extensions ?? [])],
|
|
1689
|
+
}), {});
|
|
1690
|
+
},
|
|
1691
|
+
},
|
|
1040
1692
|
{
|
|
1041
1693
|
// Apply default appearance and other settings to all Material form fields in this component
|
|
1042
1694
|
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
|
|
@@ -1046,13 +1698,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1046
1698
|
subscriptSizing: 'dynamic',
|
|
1047
1699
|
},
|
|
1048
1700
|
},
|
|
1049
|
-
], template: "<mat-card>\n <mat-card-header>\n <h3>\n @if (itemId !== undefined && $any(data); as editItem) {\n @if (editorConfig?.labelFieldKey && editItem[editorConfig!.labelFieldKey!]) {\n <!-- e.g., \"Edit - Taro (taro) \" -->\n
|
|
1701
|
+
], template: "<mat-card>\n <mat-card-header style=\"position: relative\">\n <!-- Back button -->\n @if (editorConfig?.backButton) {\n @if ($any(editorConfig?.backButton).historyBack && windowHistory.length > 1) {\n <button mat-button (click)=\"windowHistory.back()\" class=\"back-link\">\n <mat-icon>arrow_back_ios_new</mat-icon>\n {{ editorConfig?.backButton?.label ?? t('editor.backToList') }}\n </button>\n } @else if ($any(editorConfig?.backButton).routerLink; as backButtonLink) {\n <a [routerLink]=\"backButtonLink\" mat-button class=\"back-link\">\n <mat-icon>arrow_back_ios_new</mat-icon>\n {{ editorConfig?.backButton?.label ?? t('editor.backToList') }}\n </a>\n } @else if ($any(editorConfig?.backButton).link; as backButtonLink) {\n <a [href]=\"backButtonLink\" mat-button class=\"back-link\">\n <mat-icon>arrow_back_ios_new</mat-icon>\n {{ editorConfig?.backButton?.label ?? t('editor.backToList') }}\n </a>\n } @else if ($any(editorConfig?.backButton).click; as handler) {\n <button mat-button (click)=\"handler()\" class=\"back-link\">\n <mat-icon>arrow_back_ios_new</mat-icon>\n {{ editorConfig?.backButton?.label ?? t('editor.backToList') }}\n </button>\n }\n }\n <!---->\n\n <!-- Editor title -->\n <h3 style=\"margin: 0\" [style.margin-top]=\"editorConfig?.backButton ? '1rem' : '0'\">\n @if (itemId !== undefined && $any(data); as editItem) {\n @if (editorConfig?.labelFieldKey && editItem[editorConfig!.labelFieldKey!]) {\n <!-- e.g., \"Edit - Taro (taro) \" -->\n {{ t('common.edit') }} <span style=\"color: #aaaaaa\">-</span>\n <span class=\"editor-item-label\">{{ editItem[editorConfig!.labelFieldKey!] }}</span>\n @if (editorConfig?.idFieldKey && editItem[editorConfig!.idFieldKey!]) {\n <small class=\"editor-header-sub\">{{ editItem[editorConfig!.idFieldKey!] }}</small>\n }\n <!---->\n } @else if (editItem.id) {\n <!-- e.g., \"Edit - ID: 123\" -->\n {{ t('common.edit') }} <span style=\"color: #aaaaaa\">-</span>\n {{ t('editor.idLabel') }}:\n <span class=\"editor-item-label\">{{ editItem.id }}</span>\n <!---->\n } @else {\n <!-- e.g., \"Edit Account (123)\" -->\n {{ t('editor.titleForEdit', { itemType: editorItemType }) }}\n @if (editorConfig?.idFieldKey && editItem[editorConfig!.idFieldKey!]) {\n <small class=\"editor-header-sub\">{{ editItem[editorConfig!.idFieldKey!] }}</small>\n }\n <!---->\n }\n } @else {\n <!-- Create Account -->\n {{ t('editor.titleForCreate', { itemType: editorItemType }) }}\n <!---->\n }\n </h3>\n <!---->\n </mat-card-header>\n\n <mat-card-content>\n @if (isLoading || isSaving) {\n <mat-progress-bar mode=\"indeterminate\" style=\"margin-top: 1rem\"></mat-progress-bar>\n }\n\n @if (!isLoading) {\n <form [formGroup]=\"form\" (ngSubmit)=\"save()\">\n <formly-form\n [form]=\"form\"\n [fields]=\"$any(editorFields) ?? []\"\n [model]=\"formData\"\n ></formly-form>\n\n <!-- Button Area -->\n <div\n class=\"footer-buttons\"\n style=\"\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-top: 0.5rem;\n gap: 1rem;\n \"\n >\n <!-- Delete button (visible only in edit mode and if itemDeleterConfig is provided) -->\n @if (itemDeleterConfig?.deleter && itemId !== undefined) {\n <button\n type=\"button\"\n class=\"delete-button\"\n matButton=\"filled\"\n color=\"warn\"\n (click)=\"openDeletionDialog()\"\n [disabled]=\"isLoading || isSaving\"\n >\n {{ t('common.delete') }}\n </button>\n } @else {\n <span></span>\n }\n <!---->\n\n <!-- Custom footer buttons -->\n @if (editorConfig?.footerButtons) {\n @for (buttonConfig of editorConfig?.footerButtons; track buttonConfig.label) {\n @if (buttonConfig.show ? buttonConfig.show($any(formData), itemId) : true) {\n <button\n type=\"button\"\n mat-stroked-button\n (click)=\"buttonConfig.click($any(formData), itemId)\"\n [disabled]=\"isLoading || isSaving\"\n [ngClass]=\"buttonConfig.className\"\n >\n {{ buttonConfig.label }}\n </button>\n }\n }\n }\n <!---->\n\n <span style=\"flex: 1\"></span>\n\n <!-- Submit button (visible only if saver is provided) -->\n @if (editorConfig?.saver) {\n <button\n type=\"submit\"\n class=\"save-button\"\n matButton=\"filled\"\n style=\"color: white; transform: translateY(-0.3rem)\"\n >\n @if (itemId !== undefined) {\n {{ t('editor.saveForEdit') }}\n } @else {\n {{ t('editor.saveForCreate') }}\n }\n </button>\n }\n <!---->\n </div>\n <!---->\n </form>\n }\n </mat-card-content>\n</mat-card>\n", styles: [":host{max-width:600px;display:block;margin:0 auto}mat-card{padding:2rem}mat-card-header h3{font-size:1.2rem;font-weight:400}mat-card-header .editor-item-label{margin-left:1rem}mat-card-header .editor-header-sub{color:#555;margin-left:.85rem;font-size:.9rem;font-style:italic}form{margin-top:2rem}form ::ng-deep .mat-mdc-form-field-subscript-wrapper{margin-bottom:14px!important}.back-link{display:flex;position:absolute;top:-1.5rem;left:-1rem;align-items:center;gap:.3rem;font-size:.8rem}.back-link mat-icon{height:.8rem;width:.8rem;font-size:.8rem}.footer-buttons .delete-button{--mat-button-filled-container-color: var(--mat-sys-error);--mat-button-filled-label-text-color: var(--mat-sys-on-error)}.footer-buttons .save-button{--mat-button-filled-container-color: var(--mat-sys-primary);--mat-button-filled-label-text-color: var(--mat-sys-on-primary)}a,::ng-deep table a,::ng-deep table a:visited{color:var(--mat-sys-on-primary-container);text-decoration:none}a:hover,::ng-deep table a:hover,::ng-deep table a:visited:hover{text-decoration:underline}\n"] }]
|
|
1050
1702
|
}], propDecorators: { editorConfig: [{
|
|
1051
1703
|
type: Input
|
|
1052
1704
|
}], editorFields: [{
|
|
1053
1705
|
type: Input
|
|
1054
1706
|
}], itemId: [{
|
|
1055
1707
|
type: Input
|
|
1708
|
+
}], itemDeleterConfig: [{
|
|
1709
|
+
type: Input
|
|
1056
1710
|
}] } });
|
|
1057
1711
|
|
|
1058
1712
|
/*
|
|
@@ -1063,5 +1717,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1063
1717
|
* Generated bundle index. Do not edit.
|
|
1064
1718
|
*/
|
|
1065
1719
|
|
|
1066
|
-
export { NgxThinAdminEditor, NgxThinAdminList };
|
|
1720
|
+
export { NGX_THIN_ADMIN_EXTRA_FORMLY_CONFIG, NGX_THIN_ADMIN_TRANSLATE, NgxThinAdminEditor, NgxThinAdminList, provideNgxThinAdminFormlyConfig, provideNgxThinAdminI18n };
|
|
1067
1721
|
//# sourceMappingURL=ngx-thin-admin.mjs.map
|