diff --git a/src/app/pages/model/model.page.ts b/src/app/pages/model/model.page.ts index 2a6385cb..0fc0aefc 100644 --- a/src/app/pages/model/model.page.ts +++ b/src/app/pages/model/model.page.ts @@ -11,6 +11,7 @@ import { CardComponent, EmptyStateComponent } from 'src/lib/components'; import { getNgxToastComponent } from 'src/app/utils/NgxToastComponent'; import { ICrudFormEvent, IModelComponentSubmitEvent } from 'src/lib/engine/interfaces'; import { Model } from '@decaf-ts/decorator-validation'; +import { CrudEvent } from 'src/lib/engine/types'; /** * @description Angular component page for CRUD operations on dynamic model entities. @@ -134,11 +135,11 @@ export class ModelPage extends NgxModelPageDirective implements OnInit { // await super.ionViewWillEnter(); // } - override async handleEvent(event: ICrudFormEvent): Promise { + override async handleEvent(event: ICrudFormEvent): Promise { const { name } = event; if (name === ComponentEventNames.Submit) { const { success, message } = (await super.submit( - event, + event as CrudEvent, true, )) as IModelComponentSubmitEvent; const toast = getNgxToastComponent(); diff --git a/src/lib/components/crud-form/crud-form.component.html b/src/lib/components/crud-form/crud-form.component.html index 824f51ee..6bad498a 100644 --- a/src/lib/components/crud-form/crud-form.component.html +++ b/src/lib/components/crud-form/crud-form.component.html @@ -33,7 +33,7 @@ @if (initialized) {
@if (!action) { -
+
@if (options.buttons.clear?.icon) { , + override async handleEvent( + event: CrudEvent, + repository?: DecafRepository, ): Promise { const { name, role, handler, data, modelId, handlers } = event; if (!this.modelId && modelId) { @@ -190,7 +185,7 @@ export abstract class NgxModelPageDirective extends NgxPageDirective implements await this.submit(event, true, repository); break; default: - this.listenEvent.emit(event as IBaseCustomEvent | ICrudFormEvent); + this.listenEvent.emit(event); } } @@ -249,7 +244,7 @@ export abstract class NgxModelPageDirective extends NgxPageDirective implements // } async getTransactionRepository( - event: ICrudFormEvent, + event: CrudEvent, repo: DecafRepository, ): Promise> { if (!repo) { @@ -263,7 +258,7 @@ export abstract class NgxModelPageDirective extends NgxPageDirective implements )) as ILayoutModelContext; if (context) { // parse data from main model to event - event.data = context.data; + event.data = context.data as M; return context.repository as DecafRepository; } } @@ -378,22 +373,24 @@ export abstract class NgxModelPageDirective extends NgxPageDirective implements } async process( - event: ICrudFormEvent, + event: CrudEvent, model?: M, submit: boolean = false, ): Promise> { const result = { models: {} } as ILayoutModelContext; - const iterate = async (evt: ICrudFormEvent, model: string | M, parent?: string) => { + const eventData = event.data as KeyValue; + const iterate = async ( + evt: ICrudFormEvent & { data: KeyValue }, + model: string | M, + parent?: string, + ) => { const constructor = this.getModelConstrutor(model); if (constructor) { const properties = Metadata.properties(constructor) as string[]; const promises = properties.map(async (prop) => { const type = Metadata.type(constructor as Constructor, prop).name; let data = - (evt.data as KeyValue)[prop] || - (parent - ? (event.data as KeyValue)[parent as string][prop] - : (event.data as KeyValue)[prop]); + evt.data?.[prop] || (parent ? eventData?.[parent as string][prop] : eventData?.[prop]); if (data) { if (parent || Array.isArray(data)) data = [...Object.values(data)]; const context = getModelAndRepository(type) || getModelAndRepository(prop); @@ -402,7 +399,9 @@ export abstract class NgxModelPageDirective extends NgxPageDirective implements await iterate(evt, type, prop); } else { const { repository, model, pk, pkType } = context; - if (!this.pk) this.pk = pk; + if (!this.pk) { + this.pk = pk; + } if (!result.context) { result.context = { repository, @@ -451,28 +450,30 @@ export abstract class NgxModelPageDirective extends NgxPageDirective implements * page, and displays success notifications. Comprehensive error handling ensures robust * operation with detailed logging. * - * @param {IBaseCustomEvent} event - The submit event containing form data + * @param {CrudEvent} event - The submit event containing form data * @return {Promise} Promise that resolves on success or throws on error */ override async submit( - event: ICrudFormEvent, + event: CrudEvent, redirect: boolean = false, repo?: DecafRepository, - pk?: string, + pk?: keyof M, ): Promise> { let success = false; let message = ''; let result = null; try { - const repository = await this.getTransactionRepository(event, repo as DecafRepository); + const repository = await this.getTransactionRepository(event, repo!); const { data, role } = event; const operation = (role || this.operation) as CrudOperations; if (data) { - if (!pk) pk = Model.pk(repository.class) as string; + if (!pk) { + pk = Model.pk(repository.class) as keyof M; + } if (!this.modelId) { - this.modelId = (data as M)[pk as keyof M] as PrimaryKeyType; + this.modelId = data?.[pk] as PrimaryKeyType; } - const model = await this.transactionBegin(data as M, repository, operation); + const model = await this.transactionBegin(data, repository, operation); if (!model) { return { success: false, @@ -491,7 +492,7 @@ export abstract class NgxModelPageDirective extends NgxPageDirective implements case OperationKeys.UPDATE: { const models = (!Array.isArray(model) ? [model] : model) as M[]; for (const m of models) { - const uid = m[pk as keyof M]; + const uid = m[pk as keyof M] as PrimaryKeyType; const check = uid ? await repository.read(uid as PrimaryKeyType) : false; result = await (!check ? repository.create(m as M) : repository.update(m as M)); } @@ -503,17 +504,19 @@ export abstract class NgxModelPageDirective extends NgxPageDirective implements : repository.deleteAll(model as [])); break; } - - const pkValue = (model as KeyValue)[pk] || (model as KeyValue)[this.pk] || model || ''; - message = await this.translate(`operations.${operation}.${result ? 'success' : 'error'}`, { - '0': repository.class.name || pk, - '1': pkValue, - }); success = result ? true : false; if (success) { - if ((result as KeyValue)?.[this.pk]) this.modelId = (result as KeyValue)[this.pk]; - if (redirect) this.location.back(); + if ((result as KeyValue)?.[this.pk]) { + this.modelId = (result as KeyValue)[this.pk]; + } + if (redirect) { + this.location.back(); + } } + message = await this.translate(`operations.${operation}.${result ? 'success' : 'error'}`, { + '0': repository.class.name || pk, + '1': this.modelId || '', + }); } } catch (error: unknown) { this.log diff --git a/src/lib/engine/NgxRenderableComponentDirective.ts b/src/lib/engine/NgxRenderableComponentDirective.ts index 68f9809d..fd1e71ee 100644 --- a/src/lib/engine/NgxRenderableComponentDirective.ts +++ b/src/lib/engine/NgxRenderableComponentDirective.ts @@ -17,11 +17,11 @@ import { IBaseCustomEvent, ICrudFormEvent, } from './interfaces'; -import { FormParent, KeyValue } from './types'; +import { CrudEvent, FormParent, KeyValue } from './types'; import { NgxRenderingEngine } from './NgxRenderingEngine'; import { shareReplay, takeUntil } from 'rxjs'; import { NgxModelPageDirective } from './NgxModelPageDirective'; -import { ModelKeys } from '@decaf-ts/decorator-validation'; +import { Model, ModelKeys } from '@decaf-ts/decorator-validation'; @Directive() export class NgxRenderableComponentDirective @@ -156,7 +156,7 @@ export class NgxRenderableComponentDirective * @return {void} * @memberOf NgxComponentDirective */ - protected async subscribeEvents(component?: Type): Promise { + protected async subscribeEvents(component?: Type): Promise { if (!component) component = this?.output?.component; if (!this.instance && component) this.instance = component; if (this.instance && component) { @@ -171,7 +171,7 @@ export class NgxRenderableComponentDirective component: component.name || '', name: key, ...event, - } as IBaseCustomEvent & ICrudFormEvent & CustomEvent); + } as CrudEvent); }); } } diff --git a/src/lib/engine/NgxRepositoryDirective.ts b/src/lib/engine/NgxRepositoryDirective.ts index e2d1a37f..b3608f95 100644 --- a/src/lib/engine/NgxRepositoryDirective.ts +++ b/src/lib/engine/NgxRepositoryDirective.ts @@ -14,6 +14,8 @@ import { IFilterQuery } from './interfaces'; import { Subject } from 'rxjs'; import { getModelAndRepository } from './helpers'; +type TransactionResponse = M | M[] | PrimaryKeyType | PrimaryKeyType[] | undefined; + @Directive() export class NgxRepositoryDirective extends DecafComponent { private _context?: DecafRepository; @@ -350,7 +352,7 @@ export class NgxRepositoryDirective extends DecafComponent { model: M, repository: DecafRepository, operation: CrudOperations, - ): Promise { + ): Promise> { try { const hook = `after${operation.charAt(0).toUpperCase() + operation.slice(1)}`; const handler = this.handlers?.[hook] || undefined; @@ -382,16 +384,16 @@ export class NgxRepositoryDirective extends DecafComponent { protected buildTransactionModel( data: KeyValue | KeyValue[], repository: DecafRepository, - operation?: CrudOperations, - ): M | M[] | PrimaryKeyType | PrimaryKeyType[] { + operation?: OperationKeys, + ): TransactionResponse { if (!operation) { - operation = this.operation as CrudOperations; + operation = this.operation as OperationKeys; } operation = ( [OperationKeys.READ, OperationKeys.DELETE].includes(operation) ? OperationKeys.DELETE : operation.toLowerCase() - ) as CrudOperations; + ) as OperationKeys; if (Array.isArray(data)) return data.map((item) => this.buildTransactionModel(item, repository, operation)) as M[]; diff --git a/src/lib/engine/types.ts b/src/lib/engine/types.ts index 8a89ad53..a6118558 100644 --- a/src/lib/engine/types.ts +++ b/src/lib/engine/types.ts @@ -9,9 +9,15 @@ import { IonCheckbox, IonInput, IonSelect, IonTextarea } from '@ionic/angular'; import { TextFieldTypes } from '@ionic/core'; import { FormArray, FormGroup } from '@angular/forms'; -import { FormServiceControl, I18nResourceConfig, InputOption } from './interfaces'; +import { + FormServiceControl, + I18nResourceConfig, + IBaseCustomEvent, + ICrudFormEvent, + InputOption, +} from './interfaces'; import { Adapter, Repository } from '@decaf-ts/core'; -import { Context, RepositoryFlags } from '@decaf-ts/db-decorators'; +import { Context, CrudOperations, RepositoryFlags } from '@decaf-ts/db-decorators'; import { ComparisonValidationKeys, Model } from '@decaf-ts/decorator-validation'; import { ActionRoles, ListItemPositions, WindowColorSchemes } from './constants'; import { @@ -289,3 +295,7 @@ export type PropsMapperFn = Record< >; export type DecafComponentConstructor = DecafComponent; + +export type CrudEvent = IBaseCustomEvent & + ICrudFormEvent & + CustomEvent & { data: M; role?: CrudOperations };