ngx-thin-admin 0.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/fesm2022/ngx-thin-admin.mjs +961 -0
- package/fesm2022/ngx-thin-admin.mjs.map +1 -0
- package/package.json +35 -0
- package/types/ngx-thin-admin.d.ts +292 -0
|
@@ -0,0 +1,961 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, Component, EventEmitter, ChangeDetectorRef, Output, Input, Injectable, ViewChild } from '@angular/core';
|
|
3
|
+
import { lastValueFrom, Subject } from 'rxjs';
|
|
4
|
+
import { debounceTime } from 'rxjs/operators';
|
|
5
|
+
import * as i4 from '@angular/material/button';
|
|
6
|
+
import { MatButtonModule, MatIconButton, MatButton } from '@angular/material/button';
|
|
7
|
+
import { MatIcon } from '@angular/material/icon';
|
|
8
|
+
import * as i2 from '@angular/material/input';
|
|
9
|
+
import { MatInputModule, MatInput, MatPrefix, MatSuffix } from '@angular/material/input';
|
|
10
|
+
import { MatFormField } from '@angular/material/select';
|
|
11
|
+
import { MatTooltip } from '@angular/material/tooltip';
|
|
12
|
+
import * as i2$1 from '@ng-matero/extensions/grid';
|
|
13
|
+
import { MtxGridModule } from '@ng-matero/extensions/grid';
|
|
14
|
+
import * as i1$1 from '@angular/forms';
|
|
15
|
+
import { FormsModule, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
|
16
|
+
import { MatSelectionList, MatListOption } from '@angular/material/list';
|
|
17
|
+
import { MatMenu, MatMenuItem, MatMenuTrigger, MatMenuContent } from '@angular/material/menu';
|
|
18
|
+
import { MatSnackBar } from '@angular/material/snack-bar';
|
|
19
|
+
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose, MatDialog } from '@angular/material/dialog';
|
|
20
|
+
import * as i1 from '@angular/material/form-field';
|
|
21
|
+
import { MatFormFieldModule, MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
|
|
22
|
+
import { stringify } from 'csv-stringify/browser/esm/sync';
|
|
23
|
+
import { RouterLink } from '@angular/router';
|
|
24
|
+
import { MatCard, MatCardHeader, MatCardContent } from '@angular/material/card';
|
|
25
|
+
import { FieldWrapper, FormlyForm, provideFormlyCore } from '@ngx-formly/core';
|
|
26
|
+
import { withFormlyMaterial } from '@ngx-formly/material';
|
|
27
|
+
import { MatProgressBar } from '@angular/material/progress-bar';
|
|
28
|
+
|
|
29
|
+
class CategoryEditorDialog {
|
|
30
|
+
dialogRef = inject((MatDialogRef));
|
|
31
|
+
data = inject(MAT_DIALOG_DATA);
|
|
32
|
+
categoryLabel;
|
|
33
|
+
save() {
|
|
34
|
+
if (!this.categoryLabel) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
this.dialogRef.close({
|
|
38
|
+
label: this.categoryLabel,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
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 Edit {{ data.config.singularLabel ?? 'Category' }}: \"{{ data.editCategory.label }}\"\n } @else {\n Create {{ data.config.singularLabel ?? 'Category' }}\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 ?? 'Cancel' }}\n </button>\n <button type=\"submit\" matButton>{{ data.positiveButtonText ?? '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: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$1.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
|
+
}
|
|
44
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: CategoryEditorDialog, decorators: [{
|
|
45
|
+
type: Component,
|
|
46
|
+
args: [{ selector: 'lib-category-editor-dialog', imports: [
|
|
47
|
+
MatFormFieldModule,
|
|
48
|
+
MatInputModule,
|
|
49
|
+
FormsModule,
|
|
50
|
+
MatButtonModule,
|
|
51
|
+
MatDialogTitle,
|
|
52
|
+
MatDialogContent,
|
|
53
|
+
MatDialogActions,
|
|
54
|
+
MatDialogClose,
|
|
55
|
+
], template: "<h2 mat-dialog-title>\n @if (data.editCategory) {\n Edit {{ data.config.singularLabel ?? 'Category' }}: \"{{ data.editCategory.label }}\"\n } @else {\n Create {{ data.config.singularLabel ?? 'Category' }}\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 ?? 'Cancel' }}\n </button>\n <button type=\"submit\" matButton>{{ data.positiveButtonText ?? 'Save' }}</button>\n </mat-dialog-actions>\n</form>\n", styles: [".cancel-btn{--mat-button-text-label-text-color: #333}\n"] }]
|
|
56
|
+
}] });
|
|
57
|
+
|
|
58
|
+
class ConfirmDialog {
|
|
59
|
+
dialogRef = inject((MatDialogRef));
|
|
60
|
+
data = inject(MAT_DIALOG_DATA);
|
|
61
|
+
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 || 'Cancel' }}\n </button>\n <button matButton [mat-dialog-close]=\"true\" cdkFocusInitial>\n {{ data.positiveButtonText || '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
|
+
}
|
|
64
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ConfirmDialog, decorators: [{
|
|
65
|
+
type: Component,
|
|
66
|
+
args: [{ selector: 'lib-confirm-dialog', imports: [
|
|
67
|
+
FormsModule,
|
|
68
|
+
MatButtonModule,
|
|
69
|
+
MatDialogTitle,
|
|
70
|
+
MatDialogContent,
|
|
71
|
+
MatDialogActions,
|
|
72
|
+
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 || 'Cancel' }}\n </button>\n <button matButton [mat-dialog-close]=\"true\" cdkFocusInitial>\n {{ data.positiveButtonText || 'OK' }}\n </button>\n</mat-dialog-actions>\n", styles: [".cancel-btn{--mat-button-text-label-text-color: #333}\n"] }]
|
|
74
|
+
}] });
|
|
75
|
+
|
|
76
|
+
class ListCategorySelector {
|
|
77
|
+
/**
|
|
78
|
+
* Config for the category selector
|
|
79
|
+
*/
|
|
80
|
+
config;
|
|
81
|
+
/**
|
|
82
|
+
* Selected category. This will be updated when the user selects a category from the list.
|
|
83
|
+
*/
|
|
84
|
+
selectedCategory;
|
|
85
|
+
/**
|
|
86
|
+
* Event emitted when the selected category changes.
|
|
87
|
+
*/
|
|
88
|
+
selectedCategoryChange = new EventEmitter();
|
|
89
|
+
/**
|
|
90
|
+
* Categories to be displayed in the category selector.
|
|
91
|
+
*/
|
|
92
|
+
categories = [];
|
|
93
|
+
/**
|
|
94
|
+
* Services
|
|
95
|
+
*/
|
|
96
|
+
snackbar = inject(MatSnackBar);
|
|
97
|
+
dialog = inject(MatDialog);
|
|
98
|
+
cdr = inject(ChangeDetectorRef);
|
|
99
|
+
ngOnChanges(changes) {
|
|
100
|
+
if (changes.config &&
|
|
101
|
+
changes.config.currentValue?.fetcher !== changes.config.previousValue?.fetcher) {
|
|
102
|
+
this.fetchCategories();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async fetchCategories() {
|
|
106
|
+
if (!this.config?.fetcher) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
else if (!(this.config.fetcher instanceof Function)) {
|
|
110
|
+
throw new Error('categoryFetcher must be a function');
|
|
111
|
+
}
|
|
112
|
+
console.log('Fetching categories');
|
|
113
|
+
try {
|
|
114
|
+
const result = await Promise.resolve(this.config.fetcher());
|
|
115
|
+
console.log(result);
|
|
116
|
+
this.categories = result.categories;
|
|
117
|
+
this.cdr.markForCheck();
|
|
118
|
+
console.log('Fetched categories:', this.categories);
|
|
119
|
+
}
|
|
120
|
+
catch (e) {
|
|
121
|
+
console.error('Error fetching categories:', e);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async createCategory(categoryLabel) {
|
|
125
|
+
if (!this.config?.creator) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// Call the category creator function
|
|
129
|
+
try {
|
|
130
|
+
await this.config.creator(categoryLabel);
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
this.snackbar.open(`Error: ${e?.message ?? 'Could not create category'}`, undefined, {
|
|
134
|
+
duration: 3000,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
// Refresh category list
|
|
138
|
+
this.fetchCategories();
|
|
139
|
+
}
|
|
140
|
+
async updateCategory(category) {
|
|
141
|
+
if (!this.config?.updater) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
// Call the category updater function
|
|
145
|
+
try {
|
|
146
|
+
await this.config.updater(category);
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
this.snackbar.open(`Error: ${e?.message ?? `Failed to update category "${category.label}"`}`, undefined, {
|
|
150
|
+
duration: 3000,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
// Refresh category list
|
|
154
|
+
this.fetchCategories();
|
|
155
|
+
}
|
|
156
|
+
async deleteCategory(category) {
|
|
157
|
+
if (!this.config?.deleter) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
// Call the category deleter function
|
|
161
|
+
try {
|
|
162
|
+
await this.config.deleter(category);
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
this.snackbar.open(`Error: ${e?.message ?? `Failed to delete category: "${category.label}"`}`, undefined, {
|
|
166
|
+
duration: 3000,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
// Refresh category list
|
|
170
|
+
this.fetchCategories();
|
|
171
|
+
}
|
|
172
|
+
async openCategoryEditorDialog(editCategory) {
|
|
173
|
+
// Open a dialog for category creation / editing
|
|
174
|
+
const dialogRef = this.dialog.open(CategoryEditorDialog, {
|
|
175
|
+
width: '400px',
|
|
176
|
+
data: {
|
|
177
|
+
config: this.config,
|
|
178
|
+
editCategory: editCategory,
|
|
179
|
+
positiveButtonText: editCategory ? 'Save' : 'Create',
|
|
180
|
+
cancelButtonText: 'Cancel',
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
const result = await lastValueFrom(dialogRef.afterClosed());
|
|
184
|
+
if (!result || !result.label) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
// Call the category creator / updater function
|
|
188
|
+
try {
|
|
189
|
+
if (editCategory) {
|
|
190
|
+
await this.updateCategory({ ...editCategory, label: result.label });
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
await this.createCategory(result.label);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (e) {
|
|
197
|
+
this.snackbar.open(`Error: ${e?.message ?? 'Could not save category'}`, undefined, {
|
|
198
|
+
duration: 3000,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
// Refresh category list
|
|
202
|
+
this.fetchCategories();
|
|
203
|
+
}
|
|
204
|
+
async openCategoryDeletionDialog(category) {
|
|
205
|
+
// Open a dialog for category deletion
|
|
206
|
+
const dialogRef = this.dialog.open(ConfirmDialog, {
|
|
207
|
+
width: '400px',
|
|
208
|
+
data: {
|
|
209
|
+
title: 'Confirm Deletion',
|
|
210
|
+
message: `Are you sure you want to delete the ${this.config?.singularLabel ?? 'category'} "${category.label}"? This action cannot be undone.`,
|
|
211
|
+
positiveButtonText: 'Delete',
|
|
212
|
+
cancelButtonText: 'Cancel',
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
const result = await lastValueFrom(dialogRef.afterClosed());
|
|
216
|
+
if (!result) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
// Call the category deleter function
|
|
220
|
+
try {
|
|
221
|
+
await this.deleteCategory(category);
|
|
222
|
+
}
|
|
223
|
+
catch (e) {
|
|
224
|
+
this.snackbar.open(`Error: ${e?.message ?? `Failed to delete category "${category.label}"`}`, undefined, {
|
|
225
|
+
duration: 3000,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
// Refresh category list
|
|
229
|
+
this.fetchCategories();
|
|
230
|
+
}
|
|
231
|
+
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\"> {{ config?.title ?? 'Categories' }} </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 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)\">Edit</button>\n }\n @if (config?.deleter) {\n <button mat-menu-item (click)=\"openCategoryDeletionDialog(category)\">Delete</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: 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: "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
|
+
}
|
|
234
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ListCategorySelector, decorators: [{
|
|
235
|
+
type: Component,
|
|
236
|
+
args: [{ selector: 'lib-list-category-selector', imports: [
|
|
237
|
+
FormsModule,
|
|
238
|
+
MatIcon,
|
|
239
|
+
MatIconButton,
|
|
240
|
+
MatSelectionList,
|
|
241
|
+
MatMenu,
|
|
242
|
+
MatMenuItem,
|
|
243
|
+
MatMenuTrigger,
|
|
244
|
+
MatListOption,
|
|
245
|
+
MatMenuContent,
|
|
246
|
+
], template: "<header>\n <!-- Category selector title -->\n <span class=\"category-selector-title\"> {{ config?.title ?? 'Categories' }} </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 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)\">Edit</button>\n }\n @if (config?.deleter) {\n <button mat-menu-item (click)=\"openCategoryDeletionDialog(category)\">Delete</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
|
+
}], propDecorators: { config: [{
|
|
248
|
+
type: Input
|
|
249
|
+
}], selectedCategory: [{
|
|
250
|
+
type: Input
|
|
251
|
+
}], selectedCategoryChange: [{
|
|
252
|
+
type: Output
|
|
253
|
+
}] } });
|
|
254
|
+
|
|
255
|
+
class CsvExportService {
|
|
256
|
+
snackbar = inject(MatSnackBar);
|
|
257
|
+
encoders = [
|
|
258
|
+
{
|
|
259
|
+
key: 'utf8',
|
|
260
|
+
label: 'UTF-8',
|
|
261
|
+
mimeCharset: 'UTF-8',
|
|
262
|
+
encoder: undefined,
|
|
263
|
+
},
|
|
264
|
+
];
|
|
265
|
+
constructor() {
|
|
266
|
+
this.loadOptionalEncoders();
|
|
267
|
+
}
|
|
268
|
+
async loadOptionalEncoders() {
|
|
269
|
+
// Optional charset support for Shift_JIS (commonly used in Japan)
|
|
270
|
+
try {
|
|
271
|
+
// @ts-ignore
|
|
272
|
+
const Encoding = (await import('encoding-japanese'));
|
|
273
|
+
this.encoders.push({
|
|
274
|
+
key: 'sjis',
|
|
275
|
+
label: 'Shift_JIS',
|
|
276
|
+
mimeCharset: 'Shift_JIS',
|
|
277
|
+
encoder: (input) => {
|
|
278
|
+
const sjisArrayBuffer = Encoding.convert(input, {
|
|
279
|
+
from: 'UNICODE',
|
|
280
|
+
to: 'SJIS',
|
|
281
|
+
type: 'arraybuffer',
|
|
282
|
+
});
|
|
283
|
+
return new Uint8Array(sjisArrayBuffer).buffer;
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
console.error('Shift_JIS encoder is not available. If you need it, please install "encoding-japanese" package.', error);
|
|
289
|
+
// Do nothing
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
getAvailableEncoders() {
|
|
293
|
+
return this.encoders.map((encoder) => ({
|
|
294
|
+
key: encoder.key,
|
|
295
|
+
label: encoder.label,
|
|
296
|
+
}));
|
|
297
|
+
}
|
|
298
|
+
async exportListAsCsv(encoderKey, fileNamePrefix, fetcher, listQuery, columns) {
|
|
299
|
+
const CSV_EXPORT_MAX_PAGE_INDEX = 1000;
|
|
300
|
+
const CSV_EXPORT_PAGE_SIZE = 25;
|
|
301
|
+
console.log('Starting CSV export', listQuery, encoderKey);
|
|
302
|
+
// Get encoder
|
|
303
|
+
const encoderInfo = this.encoders.find((e) => e.key === encoderKey);
|
|
304
|
+
if (!encoderInfo) {
|
|
305
|
+
console.error(`exportListAsCsv - Encoder not found for key: ${encoderKey}`);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const mimeCharset = encoderInfo.mimeCharset;
|
|
309
|
+
const encoder = encoderInfo.encoder;
|
|
310
|
+
// Initialize
|
|
311
|
+
const items = [];
|
|
312
|
+
let errorMessage = undefined;
|
|
313
|
+
// Show processing message
|
|
314
|
+
const mes = this.snackbar.open('Exporting CSV...');
|
|
315
|
+
// Replace characters in the file name prefix that cannot be used in file names
|
|
316
|
+
fileNamePrefix = fileNamePrefix.replace(/[/¥\\*?:"<>|@\s;^.]/g, '_');
|
|
317
|
+
// Truncate the file name prefix if it is too long
|
|
318
|
+
const FILE_NAME_PREFIX_MAX_LENGTH = 100;
|
|
319
|
+
if (fileNamePrefix.length > FILE_NAME_PREFIX_MAX_LENGTH) {
|
|
320
|
+
fileNamePrefix = fileNamePrefix.substring(0, FILE_NAME_PREFIX_MAX_LENGTH);
|
|
321
|
+
}
|
|
322
|
+
// Generate file name
|
|
323
|
+
const now = new Date();
|
|
324
|
+
const year = now.getFullYear();
|
|
325
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
326
|
+
const date = String(now.getDate()).padStart(2, '0');
|
|
327
|
+
const hours = String(now.getHours()).padStart(2, '0');
|
|
328
|
+
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
329
|
+
const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
330
|
+
const datetimeString = `${year}${month}${date}_${hours}${minutes}${seconds}`;
|
|
331
|
+
const csvFileName = `${fileNamePrefix}_${datetimeString}.csv`;
|
|
332
|
+
// Fetch items page by page
|
|
333
|
+
let numOfExportedItems = 0;
|
|
334
|
+
for (let pageIndex = 0; pageIndex < CSV_EXPORT_MAX_PAGE_INDEX; pageIndex++) {
|
|
335
|
+
const query = {
|
|
336
|
+
...listQuery,
|
|
337
|
+
page: pageIndex,
|
|
338
|
+
perPage: CSV_EXPORT_PAGE_SIZE,
|
|
339
|
+
};
|
|
340
|
+
let result;
|
|
341
|
+
try {
|
|
342
|
+
result = await fetcher(query);
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
console.error(`exportListAsCsv - Error fetching data:`, error);
|
|
346
|
+
errorMessage = error.message || 'Unknown error';
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
// Wait for few seconds
|
|
350
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
351
|
+
if (result.items.length === 0) {
|
|
352
|
+
// No items, exit
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
// Add fetched items
|
|
356
|
+
items.push(...result.items);
|
|
357
|
+
numOfExportedItems += result.items.length;
|
|
358
|
+
}
|
|
359
|
+
// Determine CSV columns
|
|
360
|
+
const csvColumns = columns
|
|
361
|
+
.filter((column) => {
|
|
362
|
+
if (column.type === 'button') {
|
|
363
|
+
// Exclude columns that are buttons
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
return true;
|
|
367
|
+
})
|
|
368
|
+
.map((column) => {
|
|
369
|
+
// Get the column key (e.g., "name")
|
|
370
|
+
const columnKey = column.field;
|
|
371
|
+
// Get the column header string (e.g., "User Name")
|
|
372
|
+
let columnHeader;
|
|
373
|
+
if (typeof column.header === 'string') {
|
|
374
|
+
columnHeader = column.header;
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
columnHeader = columnKey;
|
|
378
|
+
}
|
|
379
|
+
// Return column information for csv-stringify
|
|
380
|
+
return {
|
|
381
|
+
key: column.field,
|
|
382
|
+
header: columnHeader,
|
|
383
|
+
};
|
|
384
|
+
})
|
|
385
|
+
.filter((column) => column !== null);
|
|
386
|
+
// Convert to CSV
|
|
387
|
+
const csv = stringify(items, {
|
|
388
|
+
header: true,
|
|
389
|
+
columns: csvColumns,
|
|
390
|
+
});
|
|
391
|
+
// Generate Blob
|
|
392
|
+
let blob = undefined;
|
|
393
|
+
if (encoder) {
|
|
394
|
+
const encodedData = encoder(csv);
|
|
395
|
+
if (encodedData) {
|
|
396
|
+
blob = new Blob([encodedData], { type: `text/csv; charset=${mimeCharset}` });
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
console.error(`exportListAsCsv - Encoding failed for encoder: ${encoderKey}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
blob = new Blob([csv], { type: `text/csv; charset=${mimeCharset}` });
|
|
404
|
+
}
|
|
405
|
+
if (!blob) {
|
|
406
|
+
console.error(`exportListAsCsv - Blob is not created`);
|
|
407
|
+
mes.dismiss();
|
|
408
|
+
this.snackbar.open(`Failed to generate CSV file.`, undefined, {
|
|
409
|
+
duration: 5000,
|
|
410
|
+
});
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
// Download as CSV
|
|
414
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
415
|
+
const a = document.createElement('a');
|
|
416
|
+
a.download = csvFileName;
|
|
417
|
+
a.href = objectUrl;
|
|
418
|
+
a.click();
|
|
419
|
+
// Revoke object URL after download
|
|
420
|
+
URL.revokeObjectURL(objectUrl);
|
|
421
|
+
// Dissmiss processing message
|
|
422
|
+
mes.dismiss();
|
|
423
|
+
// Show result message
|
|
424
|
+
if (errorMessage) {
|
|
425
|
+
this.snackbar.open(`CSV exported up to ${numOfExportedItems} items. but errors occurred: ${errorMessage}`, undefined, {
|
|
426
|
+
duration: 5000,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
this.snackbar.open(`CSV export completed. Exported ${numOfExportedItems} items.`, undefined, {
|
|
431
|
+
duration: 3000,
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: CsvExportService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
436
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: CsvExportService, providedIn: 'root' });
|
|
437
|
+
}
|
|
438
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: CsvExportService, decorators: [{
|
|
439
|
+
type: Injectable,
|
|
440
|
+
args: [{
|
|
441
|
+
providedIn: 'root',
|
|
442
|
+
}]
|
|
443
|
+
}], ctorParameters: () => [] });
|
|
444
|
+
|
|
445
|
+
class NgxThinAdminList {
|
|
446
|
+
/**
|
|
447
|
+
* Config for list
|
|
448
|
+
*/
|
|
449
|
+
listConfig;
|
|
450
|
+
/**
|
|
451
|
+
* Config for category selector. If provided, a category selector will be displayed in the UI.
|
|
452
|
+
*/
|
|
453
|
+
categorySelectorConfig;
|
|
454
|
+
/**
|
|
455
|
+
* Column schema (extended type of MtxGridColumn)
|
|
456
|
+
* @see https://ng-matero.github.io/extensions/components/grid/api
|
|
457
|
+
*/
|
|
458
|
+
listColumns;
|
|
459
|
+
/**
|
|
460
|
+
* Query (Optional)
|
|
461
|
+
*/
|
|
462
|
+
query = {
|
|
463
|
+
keyword: '',
|
|
464
|
+
page: 0,
|
|
465
|
+
perPage: 10,
|
|
466
|
+
categoryId: undefined,
|
|
467
|
+
};
|
|
468
|
+
/**
|
|
469
|
+
* Fetcher function to retrieve list data.
|
|
470
|
+
* This should be provided as part of listConfig.
|
|
471
|
+
*/
|
|
472
|
+
listFetcher;
|
|
473
|
+
/**
|
|
474
|
+
* Internal state for grid
|
|
475
|
+
*/
|
|
476
|
+
isLoading = false;
|
|
477
|
+
gridColumns = [];
|
|
478
|
+
gridData = [];
|
|
479
|
+
totalCount = 0;
|
|
480
|
+
/**
|
|
481
|
+
* Internal state for category list
|
|
482
|
+
*/
|
|
483
|
+
categories = [];
|
|
484
|
+
selectedCategory = [];
|
|
485
|
+
/**
|
|
486
|
+
* Internal subject to trigger keyword change with debounce
|
|
487
|
+
*/
|
|
488
|
+
keywordChanged$ = new Subject();
|
|
489
|
+
ngOnInit() {
|
|
490
|
+
this.keywordChanged$.pipe(debounceTime(500)).subscribe(() => {
|
|
491
|
+
this.fetchList();
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Template for cells
|
|
496
|
+
*/
|
|
497
|
+
cellTplWithLink;
|
|
498
|
+
/**
|
|
499
|
+
* Services
|
|
500
|
+
*/
|
|
501
|
+
cdr = inject(ChangeDetectorRef);
|
|
502
|
+
csvExportService = inject(CsvExportService);
|
|
503
|
+
ngOnChanges(changes) {
|
|
504
|
+
// Columns
|
|
505
|
+
if (changes.listColumns?.currentValue) {
|
|
506
|
+
this.gridColumns = changes.listColumns.currentValue.map((col) => {
|
|
507
|
+
if (col.link || col.routerLink) {
|
|
508
|
+
return { ...col, cellTemplate: this.cellTplWithLink };
|
|
509
|
+
}
|
|
510
|
+
return col;
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
// List - trigger strictly when listFetcher is newly provided or explicitly changed
|
|
514
|
+
if (changes.listConfig?.currentValue?.fetcher !== changes.listConfig?.previousValue?.fetcher) {
|
|
515
|
+
this.listFetcher = changes.listConfig?.currentValue?.fetcher;
|
|
516
|
+
this.fetchList();
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
async fetchList() {
|
|
520
|
+
if (!this.listFetcher) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
else if (!(this.listFetcher instanceof Function)) {
|
|
524
|
+
throw new Error('listFetcher must be a function');
|
|
525
|
+
}
|
|
526
|
+
console.log('Fetching data with query:', this.query);
|
|
527
|
+
this.isLoading = true;
|
|
528
|
+
try {
|
|
529
|
+
const result = await Promise.resolve(this.listFetcher(this.query));
|
|
530
|
+
this.gridData = result.items;
|
|
531
|
+
this.totalCount = result.totalCount;
|
|
532
|
+
}
|
|
533
|
+
catch (e) {
|
|
534
|
+
console.error('Error fetching list data:', e);
|
|
535
|
+
}
|
|
536
|
+
finally {
|
|
537
|
+
this.isLoading = false;
|
|
538
|
+
this.cdr.markForCheck();
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
clearFilters() {
|
|
542
|
+
this.query.keyword = '';
|
|
543
|
+
this.query.categoryId = undefined;
|
|
544
|
+
this.selectedCategory = [];
|
|
545
|
+
this.fetchList();
|
|
546
|
+
}
|
|
547
|
+
onKeywordInput() {
|
|
548
|
+
this.keywordChanged$.next();
|
|
549
|
+
}
|
|
550
|
+
async exportAsCsv(encoderKey) {
|
|
551
|
+
if (!this.listFetcher) {
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
// Determine file name prefix
|
|
555
|
+
let fileNamePrefix = this.listConfig?.title ? this.listConfig.title : 'exported';
|
|
556
|
+
if (this.selectedCategory && this.selectedCategory[0]?.id) {
|
|
557
|
+
// If a category is selected, include the category label in the file name prefix
|
|
558
|
+
fileNamePrefix += `_${this.selectedCategory[0]?.label}`;
|
|
559
|
+
}
|
|
560
|
+
if (this.query.keyword && this.query.keyword.length > 0) {
|
|
561
|
+
// If a search keyword is entered, include it in the file name prefix
|
|
562
|
+
fileNamePrefix += `_${this.query.keyword}`;
|
|
563
|
+
}
|
|
564
|
+
// Execute export
|
|
565
|
+
await this.csvExportService.exportListAsCsv(encoderKey, fileNamePrefix, this.listFetcher, {
|
|
566
|
+
categoryId: this.selectedCategory ? this.selectedCategory[0]?.id : undefined,
|
|
567
|
+
keyword: this.query.keyword,
|
|
568
|
+
}, this.listColumns);
|
|
569
|
+
}
|
|
570
|
+
onPageChange(event) {
|
|
571
|
+
this.query.page = event.pageIndex;
|
|
572
|
+
this.query.perPage = event.pageSize;
|
|
573
|
+
this.fetchList();
|
|
574
|
+
}
|
|
575
|
+
onSelectedCategoryChange($event) {
|
|
576
|
+
console.log('Selected category changed:', $event);
|
|
577
|
+
const categoryId = $event && $event.length >= 1 && $event[0] ? $event[0].id : undefined;
|
|
578
|
+
this.query.categoryId = categoryId;
|
|
579
|
+
this.fetchList();
|
|
580
|
+
}
|
|
581
|
+
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"] }] });
|
|
583
|
+
}
|
|
584
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: NgxThinAdminList, decorators: [{
|
|
585
|
+
type: Component,
|
|
586
|
+
args: [{ selector: 'ngx-thin-admin-list', imports: [
|
|
587
|
+
FormsModule,
|
|
588
|
+
MtxGridModule,
|
|
589
|
+
MatButton,
|
|
590
|
+
MatIcon,
|
|
591
|
+
MatIconButton,
|
|
592
|
+
MatTooltip,
|
|
593
|
+
MatFormField,
|
|
594
|
+
MatInput,
|
|
595
|
+
MatPrefix,
|
|
596
|
+
MatSuffix,
|
|
597
|
+
ListCategorySelector,
|
|
598
|
+
MatMenu,
|
|
599
|
+
MatMenuItem,
|
|
600
|
+
MatMenuTrigger,
|
|
601
|
+
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)=\"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"] }]
|
|
603
|
+
}], propDecorators: { listConfig: [{
|
|
604
|
+
type: Input
|
|
605
|
+
}], categorySelectorConfig: [{
|
|
606
|
+
type: Input
|
|
607
|
+
}], listColumns: [{
|
|
608
|
+
type: Input
|
|
609
|
+
}], query: [{
|
|
610
|
+
type: Input
|
|
611
|
+
}], cellTplWithLink: [{
|
|
612
|
+
type: ViewChild,
|
|
613
|
+
args: ['cellTplWithLink', { static: true }]
|
|
614
|
+
}] } });
|
|
615
|
+
|
|
616
|
+
class FormlyMatPrefixAddonWrapper extends FieldWrapper {
|
|
617
|
+
matPrefix;
|
|
618
|
+
matSuffix;
|
|
619
|
+
ngAfterViewInit() {
|
|
620
|
+
if (this.matPrefix) {
|
|
621
|
+
this.props['prefix'] = this.matPrefix;
|
|
622
|
+
}
|
|
623
|
+
if (this.matSuffix) {
|
|
624
|
+
this.props['suffix'] = this.matSuffix;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
addonRightClick($event) {
|
|
628
|
+
if (this.props['addonRight'].onClick) {
|
|
629
|
+
this.props['addonRight'].onClick(this.to, this, $event);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
addonLeftClick($event) {
|
|
633
|
+
if (this.props['addonLeft'].onClick) {
|
|
634
|
+
this.props['addonLeft'].onClick(this.to, this, $event);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: FormlyMatPrefixAddonWrapper, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
638
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: FormlyMatPrefixAddonWrapper, isStandalone: true, selector: "lib-formly-mat-prefix-addon-wrapper", viewQueries: [{ propertyName: "matPrefix", first: true, predicate: ["matPrefix"], descendants: true, static: true }, { propertyName: "matSuffix", first: true, predicate: ["matSuffix"], descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: `
|
|
639
|
+
<ng-container #fieldComponent></ng-container>
|
|
640
|
+
|
|
641
|
+
<ng-template #matSuffix>
|
|
642
|
+
@if (props['addonRight']) {
|
|
643
|
+
@if (props['addonRight']['icon']) {
|
|
644
|
+
<button
|
|
645
|
+
mat-icon-button
|
|
646
|
+
matSuffix
|
|
647
|
+
style="margin-right: 1rem;"
|
|
648
|
+
[matTooltip]="props['addonRight']['tooltip'] ?? ''"
|
|
649
|
+
(click)="props['addonRight'].onClick ? addonRightClick($event) : null"
|
|
650
|
+
type="button"
|
|
651
|
+
>
|
|
652
|
+
<mat-icon>{{ props['addonRight']['icon'] }}</mat-icon>
|
|
653
|
+
</button>
|
|
654
|
+
}
|
|
655
|
+
@if (props['addonRight']['text']) {
|
|
656
|
+
<span>{{ props['addonRight']['text'] }}</span>
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
</ng-template>
|
|
660
|
+
`, isInline: true, dependencies: [{ kind: "component", type: MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] });
|
|
661
|
+
}
|
|
662
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: FormlyMatPrefixAddonWrapper, decorators: [{
|
|
663
|
+
type: Component,
|
|
664
|
+
args: [{
|
|
665
|
+
selector: 'lib-formly-mat-prefix-addon-wrapper',
|
|
666
|
+
imports: [MatIconButton, MatIcon, MatSuffix, MatTooltip],
|
|
667
|
+
template: `
|
|
668
|
+
<ng-container #fieldComponent></ng-container>
|
|
669
|
+
|
|
670
|
+
<ng-template #matSuffix>
|
|
671
|
+
@if (props['addonRight']) {
|
|
672
|
+
@if (props['addonRight']['icon']) {
|
|
673
|
+
<button
|
|
674
|
+
mat-icon-button
|
|
675
|
+
matSuffix
|
|
676
|
+
style="margin-right: 1rem;"
|
|
677
|
+
[matTooltip]="props['addonRight']['tooltip'] ?? ''"
|
|
678
|
+
(click)="props['addonRight'].onClick ? addonRightClick($event) : null"
|
|
679
|
+
type="button"
|
|
680
|
+
>
|
|
681
|
+
<mat-icon>{{ props['addonRight']['icon'] }}</mat-icon>
|
|
682
|
+
</button>
|
|
683
|
+
}
|
|
684
|
+
@if (props['addonRight']['text']) {
|
|
685
|
+
<span>{{ props['addonRight']['text'] }}</span>
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
</ng-template>
|
|
689
|
+
`,
|
|
690
|
+
}]
|
|
691
|
+
}], propDecorators: { matPrefix: [{
|
|
692
|
+
type: ViewChild,
|
|
693
|
+
args: ['matPrefix', { static: true }]
|
|
694
|
+
}], matSuffix: [{
|
|
695
|
+
type: ViewChild,
|
|
696
|
+
args: ['matSuffix', { static: true }]
|
|
697
|
+
}] } });
|
|
698
|
+
function formlyAddonsExtension(field) {
|
|
699
|
+
if (!field.props || (field.wrappers && field.wrappers.indexOf('addons') !== -1)) {
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
if (field.props['addonLeft'] || field.props['addonRight']) {
|
|
703
|
+
field.wrappers = [...(field.wrappers || []), 'addons'];
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function getErrorMessage(error) {
|
|
708
|
+
if (typeof error === 'string') {
|
|
709
|
+
return error;
|
|
710
|
+
}
|
|
711
|
+
else if (error instanceof Error) {
|
|
712
|
+
return error.message;
|
|
713
|
+
}
|
|
714
|
+
else if (error?.error?.message) {
|
|
715
|
+
if (Array.isArray(error.error.message)) {
|
|
716
|
+
return error.error.message[0];
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
return error.error.message;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
return 'An unknown error occurred';
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
class NgxThinAdminEditor {
|
|
728
|
+
/**
|
|
729
|
+
* Config for editor
|
|
730
|
+
*/
|
|
731
|
+
editorConfig;
|
|
732
|
+
/**
|
|
733
|
+
* Fields for formly form
|
|
734
|
+
*/
|
|
735
|
+
editorFields;
|
|
736
|
+
/**
|
|
737
|
+
* Id of the item being edited.
|
|
738
|
+
*/
|
|
739
|
+
itemId;
|
|
740
|
+
/**
|
|
741
|
+
* Internal state for form
|
|
742
|
+
*/
|
|
743
|
+
isSaving = false;
|
|
744
|
+
isLoading = false;
|
|
745
|
+
form = new FormGroup({});
|
|
746
|
+
data = {};
|
|
747
|
+
formData = {};
|
|
748
|
+
/**
|
|
749
|
+
* Services
|
|
750
|
+
*/
|
|
751
|
+
snackbar = inject(MatSnackBar);
|
|
752
|
+
cdr = inject(ChangeDetectorRef);
|
|
753
|
+
ngOnChanges(changes) {
|
|
754
|
+
// Fetcher
|
|
755
|
+
if (changes.editorConfig &&
|
|
756
|
+
changes.editorConfig.currentValue?.fetcher !== changes.editorConfig.previousValue?.fetcher) {
|
|
757
|
+
this.fetchItem();
|
|
758
|
+
}
|
|
759
|
+
else if (changes.itemId && changes.itemId.currentValue !== changes.itemId.previousValue) {
|
|
760
|
+
this.fetchItem();
|
|
761
|
+
}
|
|
762
|
+
// Fields
|
|
763
|
+
if (changes.editorFields?.currentValue) {
|
|
764
|
+
this.editorFields = changes.editorFields.currentValue.map((col) => {
|
|
765
|
+
return this.overwriteFieldProps(col);
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
overwriteFieldProps(field) {
|
|
770
|
+
if (!field.props) {
|
|
771
|
+
field.props = {};
|
|
772
|
+
}
|
|
773
|
+
// requiredOnCreate
|
|
774
|
+
if (field.props.requiredOnCreate) {
|
|
775
|
+
if (this.itemId === undefined) {
|
|
776
|
+
field.props.required = true;
|
|
777
|
+
}
|
|
778
|
+
else {
|
|
779
|
+
field.props.required = false;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
// disabledOnUpdate
|
|
783
|
+
if (field.props.disabledOnUpdate) {
|
|
784
|
+
if (this.itemId !== undefined) {
|
|
785
|
+
//formControl.disable();
|
|
786
|
+
field.props.disabled = true;
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
//formControl.enable();
|
|
790
|
+
field.props.disabled = false;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return field;
|
|
794
|
+
}
|
|
795
|
+
async fetchItem() {
|
|
796
|
+
if (!this.editorConfig?.fetcher) {
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
else if (!this.itemId) {
|
|
800
|
+
this.data = {};
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
// Set loading state
|
|
804
|
+
this.isLoading = true;
|
|
805
|
+
this.cdr.markForCheck();
|
|
806
|
+
// Call the fetcher function to get the item data
|
|
807
|
+
try {
|
|
808
|
+
let res = await this.editorConfig.fetcher(this.itemId);
|
|
809
|
+
if (res instanceof Response) {
|
|
810
|
+
if (!res.ok) {
|
|
811
|
+
const data = await res.json();
|
|
812
|
+
throw new Error(data.error ?? data.message ?? `Failed to fetch data: ${res.status} ${res.statusText}`);
|
|
813
|
+
}
|
|
814
|
+
res = await res.json();
|
|
815
|
+
}
|
|
816
|
+
this.data = { ...res };
|
|
817
|
+
this.formData = { ...res };
|
|
818
|
+
}
|
|
819
|
+
catch (e) {
|
|
820
|
+
const errorMessage = getErrorMessage(e);
|
|
821
|
+
this.snackbar.open(`Error: ${errorMessage}`, undefined, {
|
|
822
|
+
duration: 3000,
|
|
823
|
+
});
|
|
824
|
+
// Reset loading state
|
|
825
|
+
this.isLoading = false;
|
|
826
|
+
this.cdr.markForCheck();
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
this.isLoading = false;
|
|
830
|
+
this.cdr.markForCheck();
|
|
831
|
+
}
|
|
832
|
+
async save() {
|
|
833
|
+
if (this.form.status === 'INVALID') {
|
|
834
|
+
this.snackbar.open('Error: Invalid input', undefined, {
|
|
835
|
+
duration: 3000,
|
|
836
|
+
});
|
|
837
|
+
// Mark field to show validation errors
|
|
838
|
+
Object.keys(this.form.controls).forEach((key) => {
|
|
839
|
+
const control = this.form.get(key);
|
|
840
|
+
control?.markAsTouched();
|
|
841
|
+
});
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
if (!this.editorConfig?.saver) {
|
|
845
|
+
this.snackbar.open('Error: No saver function provided', undefined, {
|
|
846
|
+
duration: 3000,
|
|
847
|
+
});
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
// Disable the form and save button to prevent multiple submissions
|
|
851
|
+
this.form.disable();
|
|
852
|
+
this.isSaving = true;
|
|
853
|
+
// Call the saver function
|
|
854
|
+
let result;
|
|
855
|
+
try {
|
|
856
|
+
let res = await this.editorConfig?.saver?.(this.formData, this.itemId);
|
|
857
|
+
if (res instanceof Response) {
|
|
858
|
+
if (!res.ok) {
|
|
859
|
+
const data = await res.json();
|
|
860
|
+
throw new Error(data.error ?? data.message ?? `Failed to save data: ${res.status} ${res.statusText}`);
|
|
861
|
+
}
|
|
862
|
+
res = await res.json();
|
|
863
|
+
}
|
|
864
|
+
result = res;
|
|
865
|
+
}
|
|
866
|
+
catch (e) {
|
|
867
|
+
const errorMessage = getErrorMessage(e);
|
|
868
|
+
this.snackbar.open(`Error: ${errorMessage}`, undefined, {
|
|
869
|
+
duration: 3000,
|
|
870
|
+
});
|
|
871
|
+
// Re-enable the form and save button
|
|
872
|
+
this.form.enable();
|
|
873
|
+
this.isSaving = false;
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
// Show success message
|
|
877
|
+
this.snackbar.open('Data saved successfully', undefined, {
|
|
878
|
+
duration: 3000,
|
|
879
|
+
});
|
|
880
|
+
// Update form data with the result from the saver (e.g., to get generated ID)
|
|
881
|
+
this.data = { ...result };
|
|
882
|
+
this.formData = { ...result };
|
|
883
|
+
// Re-enable the form and save button
|
|
884
|
+
this.form.enable();
|
|
885
|
+
for (const field of this.editorFields ?? []) {
|
|
886
|
+
this.overwriteFieldProps(field);
|
|
887
|
+
}
|
|
888
|
+
this.isSaving = false;
|
|
889
|
+
}
|
|
890
|
+
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: [
|
|
892
|
+
provideFormlyCore(withFormlyMaterial()),
|
|
893
|
+
provideFormlyCore({
|
|
894
|
+
wrappers: [
|
|
895
|
+
{
|
|
896
|
+
name: 'addons',
|
|
897
|
+
component: FormlyMatPrefixAddonWrapper,
|
|
898
|
+
},
|
|
899
|
+
],
|
|
900
|
+
extensions: [{ name: 'addons', extension: { onPopulate: formlyAddonsExtension } }],
|
|
901
|
+
}),
|
|
902
|
+
{
|
|
903
|
+
// Apply default appearance and other settings to all Material form fields in this component
|
|
904
|
+
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
|
|
905
|
+
useValue: {
|
|
906
|
+
appearance: 'outline',
|
|
907
|
+
floatLabel: 'always',
|
|
908
|
+
subscriptSizing: 'dynamic',
|
|
909
|
+
},
|
|
910
|
+
},
|
|
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 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 Edit <span style=\"color: #aaaaaa\">-</span>\n ID:\n <span class=\"editor-item-label\">{{ editItem.id }}</span>\n <!---->\n } @else {\n <!-- e.g., \"Edit Account (123)\" -->\n Edit\n @if (editorConfig?.singularLabel) {\n {{ editorConfig?.singularLabel }}\n }\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 Create\n @if (editorConfig?.singularLabel) {\n {{ editorConfig?.singularLabel }}\n }\n <!---->\n }\n </h3>\n </mat-card-header>\n\n <mat-card-content>\n @if (isLoading) {\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n } @else {\n <form [formGroup]=\"form\" (ngSubmit)=\"save()\">\n <formly-form\n [form]=\"form\"\n [fields]=\"$any(editorFields) ?? []\"\n [model]=\"formData\"\n ></formly-form>\n\n @if (editorConfig?.saver) {\n <!-- Submit button -->\n <button\n type=\"submit\"\n mat-flat-button\n style=\"color: white; transform: translateY(-0.3rem); float: right\"\n >\n @if (itemId !== undefined) {\n Save Changes\n } @else {\n Create\n }\n </button>\n <!---->\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}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.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: 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" }] });
|
|
912
|
+
}
|
|
913
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: NgxThinAdminEditor, decorators: [{
|
|
914
|
+
type: Component,
|
|
915
|
+
args: [{ selector: 'ngx-thin-admin-editor', imports: [
|
|
916
|
+
ReactiveFormsModule,
|
|
917
|
+
FormlyForm,
|
|
918
|
+
MatButton,
|
|
919
|
+
MatProgressBar,
|
|
920
|
+
MatCard,
|
|
921
|
+
MatCardHeader,
|
|
922
|
+
MatCardContent,
|
|
923
|
+
], providers: [
|
|
924
|
+
provideFormlyCore(withFormlyMaterial()),
|
|
925
|
+
provideFormlyCore({
|
|
926
|
+
wrappers: [
|
|
927
|
+
{
|
|
928
|
+
name: 'addons',
|
|
929
|
+
component: FormlyMatPrefixAddonWrapper,
|
|
930
|
+
},
|
|
931
|
+
],
|
|
932
|
+
extensions: [{ name: 'addons', extension: { onPopulate: formlyAddonsExtension } }],
|
|
933
|
+
}),
|
|
934
|
+
{
|
|
935
|
+
// Apply default appearance and other settings to all Material form fields in this component
|
|
936
|
+
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
|
|
937
|
+
useValue: {
|
|
938
|
+
appearance: 'outline',
|
|
939
|
+
floatLabel: 'always',
|
|
940
|
+
subscriptSizing: 'dynamic',
|
|
941
|
+
},
|
|
942
|
+
},
|
|
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 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 Edit <span style=\"color: #aaaaaa\">-</span>\n ID:\n <span class=\"editor-item-label\">{{ editItem.id }}</span>\n <!---->\n } @else {\n <!-- e.g., \"Edit Account (123)\" -->\n Edit\n @if (editorConfig?.singularLabel) {\n {{ editorConfig?.singularLabel }}\n }\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 Create\n @if (editorConfig?.singularLabel) {\n {{ editorConfig?.singularLabel }}\n }\n <!---->\n }\n </h3>\n </mat-card-header>\n\n <mat-card-content>\n @if (isLoading) {\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n } @else {\n <form [formGroup]=\"form\" (ngSubmit)=\"save()\">\n <formly-form\n [form]=\"form\"\n [fields]=\"$any(editorFields) ?? []\"\n [model]=\"formData\"\n ></formly-form>\n\n @if (editorConfig?.saver) {\n <!-- Submit button -->\n <button\n type=\"submit\"\n mat-flat-button\n style=\"color: white; transform: translateY(-0.3rem); float: right\"\n >\n @if (itemId !== undefined) {\n Save Changes\n } @else {\n Create\n }\n </button>\n <!---->\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}\n"] }]
|
|
944
|
+
}], propDecorators: { editorConfig: [{
|
|
945
|
+
type: Input
|
|
946
|
+
}], editorFields: [{
|
|
947
|
+
type: Input
|
|
948
|
+
}], itemId: [{
|
|
949
|
+
type: Input
|
|
950
|
+
}] } });
|
|
951
|
+
|
|
952
|
+
/*
|
|
953
|
+
* Public API Surface of ngx-thin-admin
|
|
954
|
+
*/
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* Generated bundle index. Do not edit.
|
|
958
|
+
*/
|
|
959
|
+
|
|
960
|
+
export { NgxThinAdminEditor, NgxThinAdminList };
|
|
961
|
+
//# sourceMappingURL=ngx-thin-admin.mjs.map
|