You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
524 lines
15 KiB
524 lines
15 KiB
5 months ago
|
import { Component, EventEmitter, HostBinding, Input, Output, TemplateRef } from '@angular/core';
|
||
|
import { FormGroup, FormBuilder, FormControl, ValidatorFn } from '@angular/forms';
|
||
|
import { SelectionModel } from '@angular/cdk/collections';
|
||
|
import { DialogService } from '@app/core/services/dialog.service';
|
||
|
import { HttpService } from '@app/core/services/http.service';
|
||
|
import { I18nService } from '@app/core/services/i18n.service';
|
||
|
import { ToastService } from '@app/core/services/toast.service';
|
||
|
import { TableColumnDefinition } from '@app/shared/components/table-column-pattern/table-column-pattern.component';
|
||
|
import { CrudEditComponent } from './edit/edit.component';
|
||
|
|
||
|
@Component({
|
||
|
selector: 'crud-template',
|
||
|
templateUrl: './crud.component.html',
|
||
|
styleUrls: ['./crud.component.scss']
|
||
|
})
|
||
|
export class CrudComponent {
|
||
|
|
||
|
constructor(
|
||
|
private _formBuilder: FormBuilder,
|
||
|
private _dialogService: DialogService,
|
||
|
private _httpService: HttpService,
|
||
|
private _i18nService: I18nService,
|
||
|
private _toastService: ToastService
|
||
|
) { }
|
||
|
|
||
|
@HostBinding('class')
|
||
|
private _: string = 'crud-template';
|
||
|
private _columns: TableColumnDefinition<any>;
|
||
|
private _fixed: boolean = false;
|
||
|
private _pageable: boolean = true;
|
||
|
private _selectable: 'single' | 'multiple' | false = 'multiple';
|
||
|
private _editable: boolean = true;
|
||
|
private _addable: boolean = true;
|
||
|
private _deletable: boolean = true;
|
||
|
private _rowClass: (row: any) => string;
|
||
|
private _toolbarTemplate: TemplateRef<any>;
|
||
|
private _toolbarButtonsTemplate: TemplateRef<any>;
|
||
|
private _toolbarIconsTemplate: TemplateRef<any>;
|
||
|
private _toolbarSelectionTemplate: TemplateRef<any>;
|
||
|
private _rowOperationTemplate: TemplateRef<any>;
|
||
|
private _rowExpandingTemplate: TemplateRef<any>;
|
||
|
private _editor: { id: string, type: string, label: string, order?: number, template?: TemplateRef<any>, range?: any[], convertor?: (val: any) => any, validator: ValidatorFn[], required?: boolean, default?: any }[];
|
||
|
private _pk: string = 'id';
|
||
|
private _api: string;
|
||
|
private _query: string = '';
|
||
|
private _filter: { enabled?: boolean, query?: string, form: FormGroup, controls?: { id: string, type: string, label: string, order?: number, template?: TemplateRef<any>, range?: any[], convertor?: (val: any) => string, default?: any }[] } = { form: this._formBuilder.group({}) };
|
||
|
private _pageIndex: number = 0;
|
||
|
private _pageSize: number = 30;
|
||
|
private _sort: string = '';
|
||
|
private _selection = new SelectionModel<any>(true, []);
|
||
|
private _rows: any[] = [];
|
||
|
private _total: number = 0;
|
||
|
private _loading: boolean = true;
|
||
|
private _convertor: (e: any) => Promise<{ items: any[], total?: number }>;
|
||
|
private _error: (e: any) => boolean;
|
||
|
|
||
|
@Output('refresh')
|
||
|
public refreshEvent: EventEmitter<string> = new EventEmitter();
|
||
|
|
||
|
@Input()
|
||
|
public set defination(value: {
|
||
|
id: string,
|
||
|
header?: string,
|
||
|
template?: TemplateRef<any>,
|
||
|
style?: any,
|
||
|
sortable?: boolean,
|
||
|
sticky?: 'start' | 'end',
|
||
|
display?: boolean,
|
||
|
filter?: {
|
||
|
field: 'text' | 'number' | 'select' | 'multi-select' | 'date' | 'date-range',
|
||
|
order?: number,
|
||
|
template?: TemplateRef<any>,
|
||
|
range?: any[],
|
||
|
convertor?: (val: any) => string,
|
||
|
default?: any
|
||
|
},
|
||
|
editor?: {
|
||
|
field: 'text' | 'number' | 'select' | 'multi-select' | 'date',
|
||
|
order?: number,
|
||
|
template?: TemplateRef<any>,
|
||
|
range?: any[],
|
||
|
convertor?: (val: any) => any,
|
||
|
validator?: ValidatorFn[],
|
||
|
required?: boolean,
|
||
|
default?: any
|
||
|
}
|
||
|
}[]) {
|
||
|
const columns = value ? value.filter((item: any) => item.display !== null)
|
||
|
.map((item: any) => {
|
||
|
return {
|
||
|
id: item.id,
|
||
|
header: item.header,
|
||
|
template: item.template,
|
||
|
style: item.style,
|
||
|
sortable: item.sortable,
|
||
|
sticky: item.sticky,
|
||
|
display: item.display
|
||
|
};
|
||
|
}) : [];
|
||
|
if (this._selectable) {
|
||
|
columns.unshift({
|
||
|
id: '@selection',
|
||
|
header: null,
|
||
|
template: null,
|
||
|
style: null,
|
||
|
sortable: false,
|
||
|
sticky: 'start',
|
||
|
display: true
|
||
|
});
|
||
|
}
|
||
|
if (this._editable || this.rowOperationTemplate) {
|
||
|
columns.push({
|
||
|
id: '@operation',
|
||
|
header: null,
|
||
|
template: null,
|
||
|
style: null,
|
||
|
sortable: false,
|
||
|
sticky: 'end',
|
||
|
display: true
|
||
|
});
|
||
|
}
|
||
|
this._columns = new TableColumnDefinition<any>(columns);
|
||
|
this._editor = [];
|
||
|
this._filter.enabled = false;
|
||
|
this._filter.query = this._query;
|
||
|
this._filter.form.controls = {};
|
||
|
this._filter.controls = [];
|
||
|
value && value.forEach(item => {
|
||
|
if (item.editor) {
|
||
|
this._editor.push({
|
||
|
id: item.id,
|
||
|
type: item.editor.field,
|
||
|
label: item.header,
|
||
|
order: item.editor.order,
|
||
|
template: item.editor.template,
|
||
|
range: item.editor.range,
|
||
|
convertor: item.editor.convertor,
|
||
|
validator: item.editor.validator,
|
||
|
required: item.editor.required,
|
||
|
default: item.editor.default ?? null
|
||
|
});
|
||
|
}
|
||
|
if (item.filter) {
|
||
|
this._filter.enabled = true;
|
||
|
if (item.filter.field == 'date-range') {
|
||
|
const controlFrom = new FormControl();
|
||
|
const controlTo = new FormControl();
|
||
|
if (item.filter.default) {
|
||
|
controlFrom.setValue(item.filter.default[0]);
|
||
|
controlFrom.setValue(item.filter.default[1]);
|
||
|
}
|
||
|
this._filter.form.addControl(`${item.id}From`, controlFrom);
|
||
|
this._filter.form.addControl(`${item.id}To`, controlTo);
|
||
|
}
|
||
|
else {
|
||
|
const control = new FormControl();
|
||
|
if (item.filter.default) {
|
||
|
control.setValue(item.filter.default);
|
||
|
}
|
||
|
this._filter.form.addControl(item.id, control);
|
||
|
}
|
||
|
this._filter.controls.push({
|
||
|
id: item.id,
|
||
|
type: item.filter.field,
|
||
|
label: item.header,
|
||
|
order: item.filter.order,
|
||
|
template: item.filter.template,
|
||
|
range: item.filter.range,
|
||
|
convertor: item.filter.convertor,
|
||
|
default: item.filter.default ?? null
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
@Input()
|
||
|
public set fixed(value: boolean) {
|
||
|
this._fixed = value;
|
||
|
}
|
||
|
|
||
|
@Input()
|
||
|
public set pageable(value: boolean) {
|
||
|
this._pageable = value;
|
||
|
}
|
||
|
|
||
|
@Input()
|
||
|
public set selectable(value: 'single' | 'multiple' | false) {
|
||
|
this._selectable = value;
|
||
|
if (value) {
|
||
|
if (this._columns?.length && !this._columns.find(item => item.id == '@selection')) {
|
||
|
this._columns.unshift({
|
||
|
id: '@selection',
|
||
|
sticky: 'start'
|
||
|
});
|
||
|
}
|
||
|
this._selection['_multiple'] = value != 'single';
|
||
|
}
|
||
|
else {
|
||
|
if (this._columns?.length && this._columns[0].id == '@selection') {
|
||
|
this._columns.shift();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Input()
|
||
|
public set editable(value: boolean) {
|
||
|
this._editable = value;
|
||
|
if (value) {
|
||
|
if (this._columns?.length && !this._columns.find(item => item.id == '@operation')) {
|
||
|
this._columns.push({
|
||
|
id: '@operation',
|
||
|
sticky: 'end'
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
if (this._columns?.length && this._columns[this._columns.length - 1].id == '@operation') {
|
||
|
this._columns.pop();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Input()
|
||
|
public set addable(value: boolean) {
|
||
|
this._addable = value;
|
||
|
}
|
||
|
|
||
|
@Input()
|
||
|
public set deletable(value: boolean) {
|
||
|
this._deletable = value;
|
||
|
}
|
||
|
|
||
|
@Input('row-class')
|
||
|
public set rowClass(value: (row: any) => string) {
|
||
|
this._rowClass = value;
|
||
|
}
|
||
|
|
||
|
@Input('toolbar-template')
|
||
|
public set toolbarTemplate(value: TemplateRef<any>) {
|
||
|
this._toolbarTemplate = value;
|
||
|
}
|
||
|
|
||
|
@Input('toolbar-buttons-template')
|
||
|
public set toolbarButtonsTemplate(value: TemplateRef<any>) {
|
||
|
this._toolbarButtonsTemplate = value;
|
||
|
}
|
||
|
|
||
|
@Input('toolbar-icons-template')
|
||
|
public set toolbarIconsTemplate(value: TemplateRef<any>) {
|
||
|
this._toolbarIconsTemplate = value;
|
||
|
}
|
||
|
|
||
|
@Input('toolbar-selection-template')
|
||
|
public set toolbarSelectionTemplate(value: TemplateRef<any>) {
|
||
|
this._toolbarSelectionTemplate = value;
|
||
|
}
|
||
|
|
||
|
@Input('row-operation-template')
|
||
|
public set rowOperationTemplate(value: TemplateRef<any>) {
|
||
|
this._rowOperationTemplate = value;
|
||
|
}
|
||
|
|
||
|
@Input('row-expanding-template')
|
||
|
public set rowExpandingTemplate(value: TemplateRef<any>) {
|
||
|
this._rowExpandingTemplate = value;
|
||
|
}
|
||
|
|
||
|
@Input()
|
||
|
public set pk(value: string) {
|
||
|
this._pk = value;
|
||
|
}
|
||
|
|
||
|
@Input()
|
||
|
public set api(value: string) {
|
||
|
if (this._api = value) {
|
||
|
this.search('init');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Input()
|
||
|
public set query(value: string) {
|
||
|
this._filter.query = this._query = value ? `${value}&` : '';
|
||
|
}
|
||
|
|
||
|
@Input()
|
||
|
public set convertor(value: (e: any) => Promise<{ items: any[], total?: number }>) {
|
||
|
this._convertor = value;
|
||
|
}
|
||
|
|
||
|
@Input()
|
||
|
public set error(value: (e: any) => boolean) {
|
||
|
this._error = value;
|
||
|
}
|
||
|
|
||
|
public set loading(value: boolean) {
|
||
|
this._loading = value;
|
||
|
}
|
||
|
|
||
|
public get columns() {
|
||
|
return this._columns;
|
||
|
}
|
||
|
|
||
|
public get fixed() {
|
||
|
return this._fixed;
|
||
|
}
|
||
|
|
||
|
public get pageable() {
|
||
|
return this._pageable;
|
||
|
}
|
||
|
|
||
|
public get selectable() {
|
||
|
return this._selectable;
|
||
|
}
|
||
|
|
||
|
public get editable() {
|
||
|
return this._editable;
|
||
|
}
|
||
|
|
||
|
public get addable() {
|
||
|
return this._addable;
|
||
|
}
|
||
|
|
||
|
public get deletable() {
|
||
|
return this._deletable;
|
||
|
}
|
||
|
|
||
|
public get rowClass() {
|
||
|
return this._rowClass;
|
||
|
}
|
||
|
|
||
|
public get toolbarTemplate() {
|
||
|
return this._toolbarTemplate;
|
||
|
}
|
||
|
|
||
|
public get toolbarButtonsTemplate() {
|
||
|
return this._toolbarButtonsTemplate;
|
||
|
}
|
||
|
|
||
|
public get toolbarIconsTemplate() {
|
||
|
return this._toolbarIconsTemplate;
|
||
|
}
|
||
|
|
||
|
public get toolbarSelectionTemplate() {
|
||
|
return this._toolbarSelectionTemplate;
|
||
|
}
|
||
|
|
||
|
public get rowOperationTemplate() {
|
||
|
return this._rowOperationTemplate;
|
||
|
}
|
||
|
|
||
|
public get rowExpandingTemplate() {
|
||
|
return this._rowExpandingTemplate;
|
||
|
}
|
||
|
|
||
|
public get filter() {
|
||
|
return this._filter;
|
||
|
}
|
||
|
|
||
|
public get pageIndex() {
|
||
|
return this._pageIndex;
|
||
|
}
|
||
|
|
||
|
public get pageSize() {
|
||
|
return this._pageSize;
|
||
|
}
|
||
|
|
||
|
public get selection() {
|
||
|
return this._selection;
|
||
|
}
|
||
|
|
||
|
public get rows() {
|
||
|
return this._rows;
|
||
|
}
|
||
|
|
||
|
public get total() {
|
||
|
return this._total;
|
||
|
}
|
||
|
|
||
|
public get loading() {
|
||
|
return this._loading;
|
||
|
}
|
||
|
|
||
|
public async refresh(e?: any): Promise<void> {
|
||
|
let action: string = null;
|
||
|
if (e) {
|
||
|
if (e.pageIndex !== undefined) {
|
||
|
action = 'page';
|
||
|
this._pageIndex = e.pageIndex;
|
||
|
this._pageSize = e.pageSize;
|
||
|
}
|
||
|
else if (e.active !== undefined) {
|
||
|
action = 'sort';
|
||
|
this._sort = e.direction ?
|
||
|
e.direction == 'desc' ? `~${e.active}` : e.active :
|
||
|
'';
|
||
|
}
|
||
|
else {
|
||
|
action = e.action;
|
||
|
}
|
||
|
}
|
||
|
//king kun bug
|
||
|
let res: any = null;
|
||
|
if (this._pageable) {
|
||
|
let lastPage: number = -1;
|
||
|
while (this._pageIndex > lastPage) {
|
||
|
lastPage > -1 && (this._pageIndex = lastPage);
|
||
|
res = await this._httpService.get(`${this._api}?${this._filter.query}sort=${this._sort}&offset=${this._pageSize * this._pageIndex}&limit=${this._pageSize}`);
|
||
|
lastPage = res ? Math.max(0, Math.ceil(res.total / this._pageSize) - 1) : Number.MAX_VALUE;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
res = await this._httpService.get(`${this._api}?${this._filter.query}sort=${this._sort}`);
|
||
|
}
|
||
|
if (res) {
|
||
|
if (this._convertor) {
|
||
|
res = await this._convertor(res);
|
||
|
}
|
||
|
this._loading = false;
|
||
|
this._total = res.total;
|
||
|
this._rows = res.items;
|
||
|
this._selection.clear();
|
||
|
}
|
||
|
this.refreshEvent.emit(action);
|
||
|
}
|
||
|
|
||
|
public search(action?: string) {
|
||
|
let query: string = this._query;
|
||
|
this._filter.controls.forEach(item => {
|
||
|
if (item.convertor) {
|
||
|
query += item.convertor(this._filter.form.controls[item.id].value);
|
||
|
}
|
||
|
else {
|
||
|
switch (item.type) {
|
||
|
case 'multi-select':
|
||
|
if (this._filter.form.controls[item.id].value instanceof Array) {
|
||
|
query += this._filter.form.controls[item.id].value.map((id: any) => `${item.id}=${id}&`).join('') || `${item.id}=0&`;
|
||
|
}
|
||
|
break;
|
||
|
case 'date':
|
||
|
if (this._filter.form.controls[item.id].value?._isAMomentObject) {
|
||
|
query += `${item.id}=${this._filter.form.controls[item.id].value.format('yyyy-MM-DD')}&`;
|
||
|
}
|
||
|
break;
|
||
|
case 'date-range':
|
||
|
if (this._filter.form.controls[`${item.id}From`].value?._isAMomentObject) {
|
||
|
query += `${item.id}From=${this._filter.form.controls[`${item.id}From`].value.format('yyyy-MM-DD')}&`;
|
||
|
}
|
||
|
if (this._filter.form.controls[`${item.id}To`].value?._isAMomentObject) {
|
||
|
query += `${item.id}To=${this._filter.form.controls[`${item.id}To`].value.format('yyyy-MM-DD')}&`;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
if (this._filter.form.controls[item.id].value?.toString().length) {
|
||
|
query += `${item.id}=${this._filter.form.controls[item.id].value}&`;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
this._filter.query = query;
|
||
|
this._pageIndex = 0;
|
||
|
this.refresh({ action: action ?? 'search' });
|
||
|
}
|
||
|
|
||
|
public reset() {
|
||
|
this._filter.query = this._query;
|
||
|
this._filter.controls.forEach(item => {
|
||
|
switch (item.type) {
|
||
|
case 'date-range':
|
||
|
this._filter.form.controls[`${item.id}From`].reset(item.default && item.default[0]);
|
||
|
this._filter.form.controls[`${item.id}To`].reset(item.default && item.default[1]);
|
||
|
break;
|
||
|
default:
|
||
|
this._filter.form.controls[item.id].reset(item.default);
|
||
|
break;
|
||
|
}
|
||
|
});
|
||
|
this.search('reset');
|
||
|
}
|
||
|
|
||
|
public async edit(item?: any): Promise<void> {
|
||
|
if (await this._dialogService.open(CrudEditComponent, {
|
||
|
autoFocus: !item,
|
||
|
data: {
|
||
|
data: item,
|
||
|
fields: this._editor,
|
||
|
pk: this._pk,
|
||
|
api: this._api,
|
||
|
error: this._error
|
||
|
}
|
||
|
})) {
|
||
|
this.refresh({ action: item ? 'edit' : 'add' });
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public async delete(): Promise<void> {
|
||
|
if (await this._dialogService.confirm(this._i18nService.translate('shared.notification.confirm'))) {
|
||
|
const res: boolean = await this._httpService.post(`${this._api}/batch`, {
|
||
|
method: 'delete',
|
||
|
data: this._selection.selected.map(i => i[this._pk])
|
||
|
}).catch(e => {
|
||
|
switch(e.status)
|
||
|
{
|
||
|
case 409:
|
||
|
this._toastService.show(this._i18nService.translate(`routes.basic.role.error.conflict.${e.error}`));
|
||
|
break;
|
||
|
case 410:
|
||
|
this._toastService.show(this._i18nService.translate(`routes.basic.role.error.gone.${e.error}`));
|
||
|
break;
|
||
|
case 422:
|
||
|
this._toastService.show(this._i18nService.translate('shared.notification.fail'));
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}) !== undefined;
|
||
|
if (res) {
|
||
|
this._toastService.show(this._i18nService.translate('shared.notification.success'));
|
||
|
this.refresh({ action: 'delete' });
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|