import {mdiFloppy} from '@mdi/js'; import RecordDialog, {RecordDialogAttributes} from '@osm/Components/Dialogs/RecordDialog'; import MdIcon from '@osm/Components/MdIcon'; import Model from '@osm/Models/Record'; import { VnodeCollection, VnodeCollectionItem } from '@osm/typings/jsx'; import { isFormValid, isVnode, showSnackbar } from '@osm/utils/misc'; import collect, {Collection} from 'collect.js'; import { Children, Vnode, VnodeDOM } from 'mithril'; import Stream from 'mithril/stream'; import {Form} from 'mithril-utilities'; import {Class} from 'type-fest'; import {JsonapiErrorDoc} from 'spraypaint'; import {ResponseError} from 'spraypaint/lib-esm/request'; export default abstract class AddEditRecordDialog extends RecordDialog { // eslint-disable-next-line unicorn/no-null protected formElement: HTMLFormElement | null = null; protected abstract formState: Map> | Record>; protected abstract modelType: Class; // Recommended: <= 3 protected numberOfColumns: number = 3; protected record!: M; protected formId?: string; oninit(vnode: Vnode, this>) { super.oninit(vnode); if (!this.record) { this.record = new this.modelType(); } this.fillForm(); } oncreate(vnode: VnodeDOM, this>) { super.oncreate(vnode); this.formElement = this.element.querySelector('form'); this.element.querySelector(`#saveBtn${this.formId!}`)?.setAttribute('form', this.formId!); } fillForm() { for (const [key, value] of this.formStateAsMap) { // @ts-ignore value(this.record[key] ?? value()); } } contents(): Children { return ( <> {this.form()} ); } headline() { return {this.record.isNew() ? __('Nuovo record') : __('Modifica record')}; } form(): Children { this.formId ??= `form-${Date.now()}`; return (
{this.formContents()}
); } formContents(): Children { return ( {...this.formFields().values().all()} ); } protected formFields(): Collection { return this.fields().map((field, key: string) => { // TODO: Remove this cast when new collection library is done if (isVnode<{name?: string, 'grid-span'?: number}>(field)) { field.attrs.name ??= key; field.attrs['grid-span'] ??= Math.floor(12 / this.numberOfColumns); field.key = key; } return field; }); } abstract fields(): Collection; onCancelButtonClicked(): void { void this.close('cancel'); } async onFormSubmit() { if (isFormValid(this.formElement!) && await this.save()) { void this.close(); } } actions(): VnodeCollection { return collect({ cancelButton: ( {__('Annulla')} ), saveButton: ( {__('Salva')} ) }); } async save(): Promise { this.record.assignAttributes(this.modelAttributesFromFormState); try { const result = await this.record.save(); this.afterSave(result); return result; } catch (error) { this.onSaveError(error as ResponseError); return false; } } afterSave(result: boolean): void { if (result) { void showSnackbar(__('Record salvato con successo')); } } async onSaveError(error: ResponseError) { const {message} = (await error.response!.json()) as JsonapiErrorDoc; void showSnackbar(message, false); } protected static createFormState>(entries: Record): Map { return new Map(Object.entries(entries) as [EK, EV][]); } get modelAttributesFromFormState(): Record { const state: Record = {}; for (const [key, value] of this.formStateAsMap) { state[key] = value(); } return state; } protected get formStateAsMap(): Map> { return this.formState instanceof Map ? this.formState : new Map(Object.entries(this.formState)); } }