ngx-thin-admin 0.0.0-alpha.0 → 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 +833 -73
- package/fesm2022/ngx-thin-admin.mjs.map +1 -1
- package/package.json +9 -7
- 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 {
|
|
7
|
+
import { MatSnackBar } from '@angular/material/snack-bar';
|
|
8
|
+
import * as i6 from '@angular/material/icon';
|
|
9
|
+
import { MatIcon, MatIconModule } from '@angular/material/icon';
|
|
8
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
|
-
import * as i2$
|
|
15
|
+
import * as i2$2 from '@ng-matero/extensions/grid';
|
|
13
16
|
import { MtxGridModule } from '@ng-matero/extensions/grid';
|
|
14
|
-
import * as
|
|
15
|
-
import { FormsModule,
|
|
17
|
+
import * as i3 from '@angular/forms';
|
|
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
22
|
import * as i1 from '@angular/material/form-field';
|
|
21
|
-
import { MatFormFieldModule, MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
|
|
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, 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$1.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$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MtxGridModule }, { kind: "component", type: i2$1.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,25 +1262,99 @@ function formlyAddonsExtension(field) {
|
|
|
704
1262
|
}
|
|
705
1263
|
}
|
|
706
1264
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
return error.error.message;
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
else {
|
|
723
|
-
return 'An unknown error occurred';
|
|
1265
|
+
class FormlyFieldFile extends FieldType {
|
|
1266
|
+
/** 表示用のファイル名 */
|
|
1267
|
+
fileName = '';
|
|
1268
|
+
/** ファイルが選択されたときの処理 */
|
|
1269
|
+
onFileChange(event) {
|
|
1270
|
+
const input = event.target;
|
|
1271
|
+
const file = input.files?.[0] ?? null;
|
|
1272
|
+
// formControl に File オブジェクトをセット
|
|
1273
|
+
this.formControl.setValue(file);
|
|
1274
|
+
this.formControl.markAsDirty();
|
|
1275
|
+
// 表示用のファイル名を更新
|
|
1276
|
+
this.fileName = file?.name ?? '';
|
|
724
1277
|
}
|
|
1278
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: FormlyFieldFile, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
1279
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: FormlyFieldFile, isStandalone: true, selector: "formly-field-file", usesInheritance: true, ngImport: i0, template: `
|
|
1280
|
+
<mat-form-field appearance="outline" floatLabel="always" subscriptSizing="dynamic" style="width:100%">
|
|
1281
|
+
<!-- Field label -->
|
|
1282
|
+
@if (props.label) {
|
|
1283
|
+
<mat-label>{{ props.label }}{{ props.required ? ' *' : '' }}</mat-label>
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
<!-- Choosed file name -->
|
|
1287
|
+
<input matInput type="text" readonly [value]="fileName" [formlyAttributes]="field" (click)="fileInput.click()" />
|
|
1288
|
+
|
|
1289
|
+
<!-- File choose button -->
|
|
1290
|
+
<button mat-icon-button matSuffix style="margin-right: 1rem;" type="button" (click)="fileInput.click()">
|
|
1291
|
+
<mat-icon>attach_file</mat-icon>
|
|
1292
|
+
</button>
|
|
1293
|
+
|
|
1294
|
+
<!-- Actual file input (hidden) -->
|
|
1295
|
+
<input
|
|
1296
|
+
type="file"
|
|
1297
|
+
#fileInput
|
|
1298
|
+
style="display: none"
|
|
1299
|
+
[attr.accept]="props['accept'] ?? null"
|
|
1300
|
+
(change)="onFileChange($event)"
|
|
1301
|
+
/>
|
|
1302
|
+
|
|
1303
|
+
<!-- Field description -->
|
|
1304
|
+
@if (props.description) {
|
|
1305
|
+
<mat-hint>{{ props.description }}</mat-hint>
|
|
1306
|
+
}
|
|
1307
|
+
</mat-form-field>
|
|
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"] }] });
|
|
725
1309
|
}
|
|
1310
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: FormlyFieldFile, decorators: [{
|
|
1311
|
+
type: Component,
|
|
1312
|
+
args: [{
|
|
1313
|
+
selector: 'formly-field-file',
|
|
1314
|
+
template: `
|
|
1315
|
+
<mat-form-field appearance="outline" floatLabel="always" subscriptSizing="dynamic" style="width:100%">
|
|
1316
|
+
<!-- Field label -->
|
|
1317
|
+
@if (props.label) {
|
|
1318
|
+
<mat-label>{{ props.label }}{{ props.required ? ' *' : '' }}</mat-label>
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
<!-- Choosed file name -->
|
|
1322
|
+
<input matInput type="text" readonly [value]="fileName" [formlyAttributes]="field" (click)="fileInput.click()" />
|
|
1323
|
+
|
|
1324
|
+
<!-- File choose button -->
|
|
1325
|
+
<button mat-icon-button matSuffix style="margin-right: 1rem;" type="button" (click)="fileInput.click()">
|
|
1326
|
+
<mat-icon>attach_file</mat-icon>
|
|
1327
|
+
</button>
|
|
1328
|
+
|
|
1329
|
+
<!-- Actual file input (hidden) -->
|
|
1330
|
+
<input
|
|
1331
|
+
type="file"
|
|
1332
|
+
#fileInput
|
|
1333
|
+
style="display: none"
|
|
1334
|
+
[attr.accept]="props['accept'] ?? null"
|
|
1335
|
+
(change)="onFileChange($event)"
|
|
1336
|
+
/>
|
|
1337
|
+
|
|
1338
|
+
<!-- Field description -->
|
|
1339
|
+
@if (props.description) {
|
|
1340
|
+
<mat-hint>{{ props.description }}</mat-hint>
|
|
1341
|
+
}
|
|
1342
|
+
</mat-form-field>
|
|
1343
|
+
`,
|
|
1344
|
+
standalone: true,
|
|
1345
|
+
imports: [
|
|
1346
|
+
ReactiveFormsModule,
|
|
1347
|
+
FormlyAttributes,
|
|
1348
|
+
MatInputModule,
|
|
1349
|
+
MatIcon,
|
|
1350
|
+
MatIconButton,
|
|
1351
|
+
MatFormField$1,
|
|
1352
|
+
MatLabel,
|
|
1353
|
+
MatHint,
|
|
1354
|
+
MatSuffix$1,
|
|
1355
|
+
],
|
|
1356
|
+
}]
|
|
1357
|
+
}] });
|
|
726
1358
|
|
|
727
1359
|
class NgxThinAdminEditor {
|
|
728
1360
|
/**
|
|
@@ -737,6 +1369,10 @@ class NgxThinAdminEditor {
|
|
|
737
1369
|
* Id of the item being edited.
|
|
738
1370
|
*/
|
|
739
1371
|
itemId;
|
|
1372
|
+
/**
|
|
1373
|
+
* Config for item deleter
|
|
1374
|
+
*/
|
|
1375
|
+
itemDeleterConfig;
|
|
740
1376
|
/**
|
|
741
1377
|
* Internal state for form
|
|
742
1378
|
*/
|
|
@@ -745,11 +1381,24 @@ class NgxThinAdminEditor {
|
|
|
745
1381
|
form = new FormGroup({});
|
|
746
1382
|
data = {};
|
|
747
1383
|
formData = {};
|
|
1384
|
+
/**
|
|
1385
|
+
* For history back
|
|
1386
|
+
*/
|
|
1387
|
+
windowHistory = window.history;
|
|
748
1388
|
/**
|
|
749
1389
|
* Services
|
|
750
1390
|
*/
|
|
751
1391
|
snackbar = inject(MatSnackBar);
|
|
752
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
|
+
}
|
|
753
1402
|
ngOnChanges(changes) {
|
|
754
1403
|
// Fetcher
|
|
755
1404
|
if (changes.editorConfig &&
|
|
@@ -831,7 +1480,7 @@ class NgxThinAdminEditor {
|
|
|
831
1480
|
}
|
|
832
1481
|
async save() {
|
|
833
1482
|
if (this.form.status === 'INVALID') {
|
|
834
|
-
this.snackbar.open(
|
|
1483
|
+
this.snackbar.open(`Error: ${this.t('editor.errorInvalidInput')}`, undefined, {
|
|
835
1484
|
duration: 3000,
|
|
836
1485
|
});
|
|
837
1486
|
// Mark field to show validation errors
|
|
@@ -842,7 +1491,7 @@ class NgxThinAdminEditor {
|
|
|
842
1491
|
return;
|
|
843
1492
|
}
|
|
844
1493
|
if (!this.editorConfig?.saver) {
|
|
845
|
-
this.snackbar.open(
|
|
1494
|
+
this.snackbar.open(`Error: ${this.t('editor.errorNoSaver')}`, undefined, {
|
|
846
1495
|
duration: 3000,
|
|
847
1496
|
});
|
|
848
1497
|
return;
|
|
@@ -852,8 +1501,9 @@ class NgxThinAdminEditor {
|
|
|
852
1501
|
this.isSaving = true;
|
|
853
1502
|
// Call the saver function
|
|
854
1503
|
let result;
|
|
1504
|
+
let showSuccessMessage = true;
|
|
855
1505
|
try {
|
|
856
|
-
let res = await this.editorConfig
|
|
1506
|
+
let res = await this.editorConfig.saver(this.formData, this.itemId);
|
|
857
1507
|
if (res instanceof Response) {
|
|
858
1508
|
if (!res.ok) {
|
|
859
1509
|
const data = await res.json();
|
|
@@ -861,6 +1511,9 @@ class NgxThinAdminEditor {
|
|
|
861
1511
|
}
|
|
862
1512
|
res = await res.json();
|
|
863
1513
|
}
|
|
1514
|
+
else if (typeof res === 'undefined') {
|
|
1515
|
+
showSuccessMessage = false;
|
|
1516
|
+
}
|
|
864
1517
|
result = res;
|
|
865
1518
|
}
|
|
866
1519
|
catch (e) {
|
|
@@ -874,9 +1527,11 @@ class NgxThinAdminEditor {
|
|
|
874
1527
|
return;
|
|
875
1528
|
}
|
|
876
1529
|
// Show success message
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
1530
|
+
if (showSuccessMessage) {
|
|
1531
|
+
this.snackbar.open(this.t('editor.successSaved'), undefined, {
|
|
1532
|
+
duration: 3000,
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
880
1535
|
// Update form data with the result from the saver (e.g., to get generated ID)
|
|
881
1536
|
this.data = { ...result };
|
|
882
1537
|
this.formData = { ...result };
|
|
@@ -887,8 +1542,60 @@ class NgxThinAdminEditor {
|
|
|
887
1542
|
}
|
|
888
1543
|
this.isSaving = false;
|
|
889
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
|
+
}
|
|
890
1597
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: NgxThinAdminEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
891
|
-
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: [
|
|
892
1599
|
provideFormlyCore(withFormlyMaterial()),
|
|
893
1600
|
provideFormlyCore({
|
|
894
1601
|
wrappers: [
|
|
@@ -898,7 +1605,31 @@ class NgxThinAdminEditor {
|
|
|
898
1605
|
},
|
|
899
1606
|
],
|
|
900
1607
|
extensions: [{ name: 'addons', extension: { onPopulate: formlyAddonsExtension } }],
|
|
1608
|
+
types: [
|
|
1609
|
+
{
|
|
1610
|
+
name: 'file',
|
|
1611
|
+
component: FormlyFieldFile,
|
|
1612
|
+
},
|
|
1613
|
+
],
|
|
901
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
|
+
},
|
|
902
1633
|
{
|
|
903
1634
|
// Apply default appearance and other settings to all Material form fields in this component
|
|
904
1635
|
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
|
|
@@ -908,7 +1639,7 @@ class NgxThinAdminEditor {
|
|
|
908
1639
|
subscriptSizing: 'dynamic',
|
|
909
1640
|
},
|
|
910
1641
|
},
|
|
911
|
-
], 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"] }] });
|
|
912
1643
|
}
|
|
913
1644
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: NgxThinAdminEditor, decorators: [{
|
|
914
1645
|
type: Component,
|
|
@@ -916,10 +1647,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
916
1647
|
ReactiveFormsModule,
|
|
917
1648
|
FormlyForm,
|
|
918
1649
|
MatButton,
|
|
1650
|
+
MatIcon,
|
|
1651
|
+
RouterLink,
|
|
919
1652
|
MatProgressBar,
|
|
920
1653
|
MatCard,
|
|
921
1654
|
MatCardHeader,
|
|
922
1655
|
MatCardContent,
|
|
1656
|
+
CommonModule,
|
|
923
1657
|
], providers: [
|
|
924
1658
|
provideFormlyCore(withFormlyMaterial()),
|
|
925
1659
|
provideFormlyCore({
|
|
@@ -930,7 +1664,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
930
1664
|
},
|
|
931
1665
|
],
|
|
932
1666
|
extensions: [{ name: 'addons', extension: { onPopulate: formlyAddonsExtension } }],
|
|
1667
|
+
types: [
|
|
1668
|
+
{
|
|
1669
|
+
name: 'file',
|
|
1670
|
+
component: FormlyFieldFile,
|
|
1671
|
+
},
|
|
1672
|
+
],
|
|
933
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
|
+
},
|
|
934
1692
|
{
|
|
935
1693
|
// Apply default appearance and other settings to all Material form fields in this component
|
|
936
1694
|
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
|
|
@@ -940,13 +1698,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
940
1698
|
subscriptSizing: 'dynamic',
|
|
941
1699
|
},
|
|
942
1700
|
},
|
|
943
|
-
], 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"] }]
|
|
944
1702
|
}], propDecorators: { editorConfig: [{
|
|
945
1703
|
type: Input
|
|
946
1704
|
}], editorFields: [{
|
|
947
1705
|
type: Input
|
|
948
1706
|
}], itemId: [{
|
|
949
1707
|
type: Input
|
|
1708
|
+
}], itemDeleterConfig: [{
|
|
1709
|
+
type: Input
|
|
950
1710
|
}] } });
|
|
951
1711
|
|
|
952
1712
|
/*
|
|
@@ -957,5 +1717,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
957
1717
|
* Generated bundle index. Do not edit.
|
|
958
1718
|
*/
|
|
959
1719
|
|
|
960
|
-
export { NgxThinAdminEditor, NgxThinAdminList };
|
|
1720
|
+
export { NGX_THIN_ADMIN_EXTRA_FORMLY_CONFIG, NGX_THIN_ADMIN_TRANSLATE, NgxThinAdminEditor, NgxThinAdminList, provideNgxThinAdminFormlyConfig, provideNgxThinAdminI18n };
|
|
961
1721
|
//# sourceMappingURL=ngx-thin-admin.mjs.map
|