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

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' });
}
}
}
}