import '@maicol07/mwc-layout-grid'; import '@material/mwc-dialog'; import '@material/mwc-fab'; import '@material/mwc-snackbar'; import type {Dialog as MWCDialog} from '@material/mwc-dialog'; import type {Cash} from 'cash-dom'; import collect, {type Collection} from 'collect.js'; import type { Children, Vnode, VnodeDOM } from 'mithril'; import { IModel, Model } from '../../Models'; import type { FieldT, SelectT, TextAreaT, TextFieldT } from '../../types'; import {getFormData, isFormValid, showSnackbar} from '../../utils'; import DataTable from '../DataTable/DataTable'; import TableCell from '../DataTable/TableCell'; import TableColumn from '../DataTable/TableColumn'; import TableRow from '../DataTable/TableRow'; import LoadingButton from '../LoadingButton'; import Mdi from '../Mdi'; import Page from '../Page'; export type ColumnT = { id?: string title: string type?: 'checkbox' | 'numeric' valueModifier?: (instance: IModel, property: string) => any }; export type SectionT = { id?: string heading?: string columns?: number fields: | TextFieldT[] | TextAreaT | SelectT[] | Record }; export type ColumnsT = Record; export type RowsT = Collection; export type SectionsT = Record | SectionT[]; /** * @abstract */ export class RecordsPage extends Page { columns: ColumnsT; rows: RowsT = collect({}); sections: SectionsT; dialogs: Children[]; recordDialogMaxWidth: string | number = 'auto'; model: typeof Model; customSetter: ( model: IModel, fields: Collection ) => void; /** * What fields should take precedence when saving the record */ fieldsPrecedence: string[] = []; async oninit(vnode: Vnode) { super.oninit(vnode); // @ts-ignore const response = await this.model.with(this.model.relationships).get(); const data = response.getData() as Model[]; if (data.length > 0) { for (const record of data) { this.rows.put(record.getId(), record); } m.redraw(); } } onupdate(vnode: VnodeDOM) { const rows: Cash = $('.mdc-data-table__row[data-model-id]'); if (rows.length > 0) { rows.on('click', async (event: PointerEvent) => { const cell = event.target as HTMLElement; if (cell.tagName === 'MWC-CHECKBOX') { return; } await this.updateRecord($(cell).parent('tr').data('model-id') as number); }); } } tableColumns(): JSX.Element[] { return collect(this.columns) // @ts-ignore .map((column: ColumnT | string, id: string) => ( {typeof column === 'string' ? column : column.title} )) .toArray(); } tableRows(): Children { if (this.rows.isEmpty()) { return ( {__('Non sono presenti dati')} ); } return this.rows .map((instance: IModel, index: string) => ( {collect(this.columns) .map((column, index_: string) => ( {this.getModelValue(instance, (column as ColumnT).id ?? index_)} )) .toArray()} )) .toArray(); } async updateRecord(id: number) { // @ts-ignore const response = await this.model.with(this.model.relationships).find(id); const instance = response.getData() as IModel; const dialog = $('mwc-dialog#add-record-dialog'); dialog // eslint-disable-next-line sonarjs/no-duplicate-string .find('text-field, text-area, material-select') .each((index, field) => { (field as HTMLInputElement).value = this.getModelValue(instance, field.id) as string; }); dialog .find('mwc-button#delete-button') .show() .on('click', () => { const confirmDialog = $('mwc-dialog#confirm-delete-record-dialog'); const confirmButton = confirmDialog.find('mwc-button#confirm-button'); const loading: Cash = confirmButton.find('mwc-circular-progress'); confirmButton.on('click', async () => { loading.show(); await instance.delete(); // noinspection JSUnresolvedVariable this.rows.forget(instance.getId()); m.redraw(); await showSnackbar(__('Record eliminato!'), 4000); }); loading.hide(); (confirmDialog.get(0) as MWCDialog).show(); }); (dialog.get(0) as MWCDialog).show(); } recordDialog() { return (
); } deleteRecordDialog(): Children { return (

{__('Sei sicuro di voler eliminare questo record?')}

); } view(vnode: Vnode) { return ( <>

{this.title}

{this.tableColumns()} {this.tableRows()} {this.recordDialog()} {this.deleteRecordDialog()} {this.dialogs} ); } oncreate(vnode: VnodeDOM) { super.oncreate(vnode); const fab: Cash = $('mwc-fab#add-record'); const dialog: Cash = fab.next('mwc-dialog#add-record-dialog'); const form: Cash = dialog.find('form'); // Open "New record" dialog fab.on('click', () => { form .find('text-field, text-area, material-select') .each((index, field) => { (field as HTMLInputElement).value = $(field).data('default-value') as string; }); dialog.find('mwc-button[type="submit"] mwc-circular-progress').hide(); dialog.find('mwc-button#delete-button').hide(); const dialogElement: HTMLElement & Partial | undefined = dialog.get(0); if (dialogElement) { (dialogElement as MWCDialog).show(); } }); const button = dialog.find('mwc-button[type="submit"]'); button.on('click', () => { form.trigger('submit'); }); const loading: Cash = button.find('mwc-circular-progress'); form.on('submit', async (event: SubmitEvent) => { event.preventDefault(); loading.show(); if (isFormValid(form)) { const data = collect(getFormData(form)); // @ts-ignore // eslint-disable-next-line new-cap const instance = this.rows.get(data.get('id'), new this.model() as IModel) as IModel; if (this.customSetter) { // eslint-disable-next-line @typescript-eslint/await-thenable await this.customSetter(instance, data); } else { const filtered = data .filter((item: any, id: string) => this.fieldsPrecedence.includes(id)); // @ts-ignore (filtered.isEmpty() ? filtered : data).each((value: string, id: string) => { instance[id] = value; }); } const response = await instance.save(); const modelId = response.getModelId(); if (modelId) { // @ts-ignore const newResponse = await this.model.with(this.model.relationships).find(modelId); const model = newResponse.getData() as IModel; const dialogElement = dialog.get(0); if (dialogElement) { (dialogElement as MWCDialog).close(); } this.rows.put(model.getId(), model); m.redraw(); await showSnackbar(__('Record salvato'), 4000); } } else { loading.hide(); await showSnackbar(__('Campi non validi. Controlla i dati inseriti')); } }); } getModelValue(model: IModel, field: string, raw = false): any { const column = this.columns[field]; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment let value: any = model[field]; if (typeof column === 'object' && column.valueModifier) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment value = column.valueModifier(model, field); } return (value || raw) ? value : ''; } getElementFromType(type: string) { switch (type) { case 'text': return 'text-field'; case 'textarea': return 'text-area'; case 'select': return 'material-select'; /* Case 'checkbox': case 'radio': return Radio; */ default: return 'text-field'; } } getFieldBody(field: FieldT | TextFieldT | TextAreaT | SelectT) { const list = []; switch (field.type) { case 'select': // @ts-ignore for (const option of (field as SelectT).options) { list.push( {option.label} ); } break; case 'checkbox': return ''; case 'radio': return ''; default: } if (field.icon) { list.push(); } return list; } }