diff --git a/src/app/ew/fabric/Leaflet.ts b/src/app/ew/fabric/Leaflet.ts index 030ead24..5e9ce220 100644 --- a/src/app/ew/fabric/Leaflet.ts +++ b/src/app/ew/fabric/Leaflet.ts @@ -1,14 +1,13 @@ -import { Comparison, Model, model, required, type ModelArg } from '@decaf-ts/decorator-validation'; +import { Comparison, list, Model, model, required, type ModelArg } from '@decaf-ts/decorator-validation'; import { LeafletFile } from './LeafletFile'; // import { gtin } from "@pharmaledgerassoc/ptp-toolkit/shared"; // import { BatchPattern, TableNames } from "@pharmaledgerassoc/ptp-toolkit/shared"; -import { Cascade, column, index, oneToMany, OrderDirection, pk, table } from '@decaf-ts/core'; -import { composed, InternalError, OperationKeys, readonly } from '@decaf-ts/db-decorators'; +import { column, index, OrderDirection, pk, table } from '@decaf-ts/core'; +import { composed, InternalError, readonly } from '@decaf-ts/db-decorators'; import { description } from '@decaf-ts/decoration'; import { ComponentEventNames, DecafComponent, - DecafEventHandler, ElementPositions, ElementSizes, HTML5InputTypes, @@ -17,7 +16,7 @@ import { uilistmodel, uimodel, uionrender, - uitablecol, + uitablecol } from '@decaf-ts/ui-decorators'; import { getDocumentTypes, getLeafletLanguages, getMarkets } from 'src/app/ew/utils/helpers'; import { Batch } from './Batch'; @@ -43,6 +42,7 @@ export enum LeafletType { @uimodel('ngx-decaf-crud-form', { empty: { showButton: false } }) @uihandlers({ [ComponentEventNames.Submit]: LeafletHandler, + [ComponentEventNames.Render]: LeafletHandler, }) @model() export class Leaflet extends Cacheable { @@ -120,20 +120,21 @@ export class Leaflet extends Cacheable { }), options: () => Batch, }) - @uionrender( - () => - class _ extends DecafEventHandler { - override async render(): Promise { - const instance = this as unknown as { - headers: string[]; - operation: OperationKeys[]; - }; - if (instance.headers) - instance.headers = instance.headers.map((header) => (header === 'batchNumber' ? 'batchCol' : header)); - } - } - ) - @uitablecol(1, async (instance: DecafComponent & { type: string }, value: string) => { + @uionrender(() => LeafletHandler) + // @uionrender( + // () => + // class _ extends DecafEventHandler { + // override async render(): Promise { + // const instance = this as unknown as { + // headers: string[]; + // operation: OperationKeys[]; + // }; + // if (instance.headers) + // instance.headers = instance.headers.map((header) => (header === 'batchNumber' ? 'batchCol' : header)); + // } + // } + // ) + @uitablecol(1, async (instance: DecafComponent & { type: string }, prop: string, value: string) => { if (instance.operation && ['paginated', 'infinite'].includes(instance.type)) value = 'subinfo'; // fallback mapper to list item position return value; }) @@ -176,13 +177,16 @@ export class Leaflet extends Cacheable { startEmpty: false, translatable: false, }) - @uitablecol(3, async (instance: DecafComponent & { type: string }, v: string) => { - if (instance.operation && ['paginated', 'infinite'].includes(instance.type)) return 'subinfo'; - if (!instance.operation) { - return getLeafletLanguages().find(({ value }) => value === v)?.text; + @uitablecol( + 3, + async ( + instance: DecafComponent & { type: 'infinite' | 'paginated' }, + prop: keyof Leaflet, + value: string + ) => { + return LeafletHandler.getLanguage(instance, value); } - return v; - }) + ) lang!: string; // TODO -> rollback to language property //@cache() @@ -197,11 +201,8 @@ export class Leaflet extends Cacheable { type: HTML5InputTypes.SELECT, options: getMarkets(), }) - @uitablecol(4, async (instance: DecafComponent, v: string) => { - if (!instance.operation) { - return getMarkets().find(({ value }) => value === v)?.text; - } - return v; + @uitablecol(4, async (instance: DecafComponent, prop: keyof Leaflet, value: string) => { + return LeafletHandler.getMarket(instance, value); }) epiMarket!: string; // TODO -> Create validation decorator. CountryMarket is a CONDITIONAL property. can only exist for product only. no batch @@ -217,17 +218,27 @@ export class Leaflet extends Cacheable { size: ElementSizes.small, position: ElementPositions.left, required: true, + valueType: 'files', subType: 'text', maxFileSize: 10, // previewHandler: XmlPreviewHandler, accept: ['image/*', '.xml'], + // propsMapperFn: { + // parseValue: () => async (revert: boolean) => { + // const instance = this as unknown as FileUploadComponent; + // console.log(instance); + // console.log(revert); + // // override aqui + // }, + // }, }) xmlFileContent!: string | LeafletFile; //@cache() - @oneToMany(() => LeafletFile, { update: Cascade.CASCADE, delete: Cascade.CASCADE }, false) + // @oneToMany(() => LeafletFile, { update: Cascade.CASCADE, delete: Cascade.CASCADE }, false) + @list(LeafletFile) @description('List of additional files linked to the leaflet, such as PDFs or images.') - otherFilesContent!: string[] | LeafletFile[]; + otherFilesContent!: LeafletFile[]; constructor(model?: ModelArg) { super(model); diff --git a/src/app/ew/fabric/LeafletFile.ts b/src/app/ew/fabric/LeafletFile.ts index 903480f6..7228a0cc 100644 --- a/src/app/ew/fabric/LeafletFile.ts +++ b/src/app/ew/fabric/LeafletFile.ts @@ -1,41 +1,39 @@ -import type { ModelArg } from "@decaf-ts/decorator-validation"; -import { model, required } from "@decaf-ts/decorator-validation"; -import { column, index, OrderDirection, pk, table } from "@decaf-ts/core"; +import { column, index, OrderDirection, pk, table } from '@decaf-ts/core'; +import type { ModelArg } from '@decaf-ts/decorator-validation'; +import { model, required } from '@decaf-ts/decorator-validation'; // import { TableNames } from "@pharmaledgerassoc/ptp-toolkit/shared"; -import { description, uses } from "@decaf-ts/decoration"; - import { composed } from "@decaf-ts/db-decorators"; +import { composed } from '@decaf-ts/db-decorators'; +import { description } from '@decaf-ts/decoration'; // import { cache } from "@pharmaledgerassoc/ptp-toolkit/shared"; -import { Cacheable } from "./Cacheable"; -import { TableNames } from "./constants"; +import { Cacheable } from './Cacheable'; +import { TableNames } from './constants'; -@description( - "Represents an additional file associated with a leaflet, such as a PDF or image." -) +@description('Represents an additional file associated with a leaflet, such as a PDF or image.') //@uses(FabricFlavour) @table(TableNames.LeafletFile) @model() export class LeafletFile extends Cacheable { @pk() - @composed(["leafletId", "filename"], ":") - @description("Unique identifier of the leaflet file.") + @composed(['leafletId', 'fileName'], ':') + @description('Unique identifier of the leaflet file.') id!: string; @required() @index([OrderDirection.ASC, OrderDirection.DSC]) - @description("Identifier of the leaflet this file belongs to.") + @description('Identifier of the leaflet this file belongs to.') leafletId!: string; //@cache() @column() @required() @index([OrderDirection.ASC, OrderDirection.DSC]) - @description("Name of the file, including its extension.") - filename!: string; + @description('Name of the file, including its extension.') + fileName!: string; //@cache() @column() @required() - @description("Base64-encoded content of the file.") + @description('Base64-encoded content of the file.') fileContent!: string; // eslint-disable-next-line @typescript-eslint/no-useless-constructor diff --git a/src/app/ew/fabric/Product.ts b/src/app/ew/fabric/Product.ts index 4a5e950b..5f131f8d 100644 --- a/src/app/ew/fabric/Product.ts +++ b/src/app/ew/fabric/Product.ts @@ -1,44 +1,28 @@ -import type { Model, ModelArg } from '@decaf-ts/decorator-validation'; -import { maxlength, min, minlength, model, required, type } from '@decaf-ts/decorator-validation'; +import type { ModelArg } from '@decaf-ts/decorator-validation'; +import { minlength, model, required } from '@decaf-ts/decorator-validation'; // import { gtin, TableNames } from "@pharmaledgerassoc/ptp-toolkit/shared"; -import { propMetadata, Constructor } from '@decaf-ts/decoration'; -import { - Cascade, - column, - Condition, - index, - oneToMany, - oneToOne, - OrderDirection, - pk, - table, -} from '@decaf-ts/core'; +import { Cascade, column, index, oneToMany, oneToOne, OrderDirection, pk } from '@decaf-ts/core'; // import {BlockOperations, OperationKeys, readonly} from "@decaf-ts/db-decorators"; -import { description, uses } from '@decaf-ts/decoration'; -import { ProductStrength } from './ProductStrength'; +import { description } from '@decaf-ts/decoration'; import { ProductMarket } from './ProductMarket'; +import { ProductStrength } from './ProductStrength'; // import { assignProductOwner, audit } from "@pharmaledgerassoc/ptp-toolkit/shared"; import { ProductImage } from './ProductImage'; // import { cache } from "@pharmaledgerassoc/ptp-toolkit/shared"; -import { Cacheable } from './Cacheable'; import { + HTML5InputTypes, uielement, - uilistprop, - uilistmodel, uilayout, uilayoutprop, - HTML5InputTypes, + uilistmodel, + uilistprop, uionrender, - DecafComponent, } from '@decaf-ts/ui-decorators'; -import { getModelAndRepository, NgxComponentDirective, NgxEventHandler } from 'src/lib/engine'; -import { OperationKeys } from '@decaf-ts/db-decorators'; -import { audit } from './utils'; -import { FileUploadComponent } from 'src/lib/components'; -import { ProductImageHandler } from './handlers/ProductImageHandler'; -import { ProductHandler } from './handlers/ProductHandler'; +import { Cacheable } from './Cacheable'; import { gtin } from './gtin'; +import { ProductHandler } from './handlers/ProductHandler'; +import { audit } from './utils'; // @BlockOperations([OperationKeys.DELETE]) //@uses(FabricFlavour) @@ -126,14 +110,16 @@ export class Product extends Cacheable { update: Cascade.CASCADE, delete: Cascade.CASCADE, }, - false, + false ) @uielement('ngx-decaf-file-upload', { label: 'product.productImage.label', type: 'text', }) @uilayoutprop(1) - @uionrender(() => ProductImageHandler) + @uionrender(() => ProductHandler) + + // @uionrender(() => ProductImageHandler) imageData?: ProductImage; // diff --git a/src/app/ew/fabric/handlers/EpiHandler.ts b/src/app/ew/fabric/handlers/EpiHandler.ts index 025f57b2..b732af89 100644 --- a/src/app/ew/fabric/handlers/EpiHandler.ts +++ b/src/app/ew/fabric/handlers/EpiHandler.ts @@ -1,6 +1,7 @@ import { Condition, OrderDirection } from '@decaf-ts/core'; -import { Metadata, Constructor } from '@decaf-ts/decoration'; +import { OperationKeys } from '@decaf-ts/db-decorators'; import { Model, Primitives } from '@decaf-ts/decorator-validation'; +import { CrudFieldComponent } from 'src/lib/components'; import { DecafRepository, getModelAndRepository, @@ -9,10 +10,7 @@ import { NgxEventHandler, SelectOption, } from 'src/lib/engine'; -import { Batch, Leaflet, Product, ProductMarket, ProductStrength } from '..'; -import { CrudFieldComponent, FieldsetComponent, ListComponent } from 'src/lib/components'; -import { OperationKeys, readonly } from '@decaf-ts/db-decorators'; -import { DecafComponent } from '@decaf-ts/ui-decorators'; +import { Leaflet, ProductMarket } from '..'; export async function renderMakets(instance: C): Promise { return await new EpiHandler().renderMakets(instance); @@ -75,11 +73,7 @@ export class EpiHandler extends NgxEventHandler { async renderMakets(component: C): Promise { if (component?.modelId) { - const query = await this.query( - ProductMarket.name, - 'productCode', - component.modelId as Primitives, - ); + const query = await this.query(ProductMarket.name, 'productCode', component.modelId as Primitives); if (query?.length) { const filterMarkets = query.map((item) => item.marketId); component.options = (component.options as SelectOption[])?.filter((option) => { @@ -91,17 +85,13 @@ export class EpiHandler extends NgxEventHandler { } } - async query( - modelName: string, - relation: string, - modelId: Primitives, - ): Promise { + async query(modelName: string, relation: string, modelId: Primitives): Promise { const repo = getModelAndRepository(modelName); if (repo) { const { repository } = repo; return await (repository as DecafRepository).query( Condition.attribute(relation as keyof M).eq(modelId), - relation as keyof M, + relation as keyof M ); } return []; diff --git a/src/app/ew/fabric/handlers/LeafletHandler.ts b/src/app/ew/fabric/handlers/LeafletHandler.ts index 437f857e..187cd9ae 100644 --- a/src/app/ew/fabric/handlers/LeafletHandler.ts +++ b/src/app/ew/fabric/handlers/LeafletHandler.ts @@ -1,21 +1,49 @@ -import { - composedFromCreateUpdate, - ComposedFromMetadata, - IRepository, - OperationKeys, - PrimaryKeyType, -} from '@decaf-ts/db-decorators'; +import { Router } from '@angular/router'; +import { Condition } from '@decaf-ts/core'; +import { composedFromCreateUpdate, ComposedFromMetadata, OperationKeys, PrimaryKeyType } from '@decaf-ts/db-decorators'; import { Model } from '@decaf-ts/decorator-validation'; -import { NgxEventHandler } from 'src/lib/engine'; +import { DecafComponent } from '@decaf-ts/ui-decorators'; import { getNgxToastComponent } from 'src/app/utils/NgxToastComponent'; -import { Leaflet } from '..'; -import { Condition } from '@decaf-ts/core'; +import { DecafRepository, InputOption, NgxEventHandler } from 'src/lib/engine'; import { getModelAndRepository } from 'src/lib/engine/helpers'; -import { Router } from '@angular/router'; +import { Leaflet } from '../Leaflet'; +import { Product } from '../Product'; + +import { TableComponent } from 'src/lib/components'; +import { getLeafletLanguages, getMarkets } from '../../utils/helpers'; +import { LeafletFile } from '../LeafletFile'; export class LeafletHandler extends NgxEventHandler { override model!: Model; + private static product?: Product; + + private static languages: InputOption[] = getLeafletLanguages(); + + private static markets: InputOption[] = getMarkets(); + + override async render(instance: TableComponent, prop: string, value: string): Promise { + const { operation, type } = instance; + // if (!instance._query?.length) { + // instance._query = await ProductHandler.query(); + // } + if (prop === 'batchNumber' && value) { + if (instance?.headers) + instance.headers = instance.headers.map((header) => (header === 'batchNumber' ? 'batchCol' : header)); + if (!operation) { + return value; + } + // falback to list item position if operation is paginated or infinite and value is not mapped to avoid empty column + return 'info'; + + // if (!operation) { + // if (prop === 'lang' && value) { + // instance.value = getLeafletLanguages().find((item) => value === item.value)?.text; + // } + // } + } + } + override async handle(event: { data: Leaflet }): Promise { const repo = getModelAndRepository(this.model as Model); let operation = this.operation; @@ -26,14 +54,14 @@ export class LeafletHandler extends NgxEventHandler { if (repo) { try { const { repository, pk, pkType } = repo; - const model = Model.build(data as Partial, this.model.constructor.name) as Leaflet; + const model = Model.build(data as Model, this.model.constructor.name) as Leaflet; const composedMetadata = Model.composed(model as Model, pk as any); composedFromCreateUpdate.call( this as any, new Leaflet(), composedMetadata as ComposedFromMetadata, pk as keyof Model, - model as Model, + model as Model ); uid = model[pk as keyof Leaflet] as PrimaryKeyType; @@ -46,8 +74,10 @@ export class LeafletHandler extends NgxEventHandler { .execute(); if (query?.length) { operation = OperationKeys.UPDATE; - result = await repository.update(Object.assign(query[0], data) as Model); + result = await LeafletHandler.parseFileContent(Object.assign(query[0], data), repository); + result = await repository.update(Object.assign(query[0], data)); } else { + result = await LeafletHandler.parseFileContent(model as Model, repository); result = await repository.create(model as Model); } } @@ -81,4 +111,58 @@ export class LeafletHandler extends NgxEventHandler { const toast = getNgxToastComponent(options); await toast.show(options); } + + // static async beforeSave(data: M, repository: DecafRepository): Promise { + // return await LeafletHandler.parseFileContent(data, repository); + // } + + static async parseFileContent(data: Partial, repository: DecafRepository): Promise { + const { xmlFileContent } = data; + data.otherFilesContent = []; + const leafletId = data.id; + function buildFileContent(fileName: string, fileContent: string): LeafletFile { + const now = new Date(); + const file = Model.build( + { + leafletId, + fileName, + fileContent, + + id: `${leafletId}:${fileName}`, + createdAt: now, + updatedAt: now, + }, + LeafletFile.name + ) as LeafletFile; + + return file; + } + if (xmlFileContent) { + const arrayFiles = JSON.parse(xmlFileContent as string) as { name: string; source: string }[]; + for (const file of arrayFiles) { + const isXmlFile = file.name.toLowerCase().endsWith('.xml'); + const fileContent = buildFileContent(file.name, file.source); + if (isXmlFile) { + data.xmlFileContent = fileContent; + } else { + data.otherFilesContent.push(fileContent); + } + } + } + return data as Leaflet; + } + + static async getLanguage( + instance: DecafComponent & { type: 'infinite' | 'paginated' }, + lang: string + ): Promise { + if (instance.operation && ['paginated', 'infinite'].includes(instance.type)) { + return 'subinfo'; + } + return getLeafletLanguages().find((item) => lang === item.value)?.text || ''; + } + + static async getMarket(instance: DecafComponent, market: string): Promise { + return getMarkets().find((item) => market === item.value)?.text || ''; + } } diff --git a/src/app/ew/fabric/handlers/ProductHandler.ts b/src/app/ew/fabric/handlers/ProductHandler.ts index 56f05a8d..c8e5c027 100644 --- a/src/app/ew/fabric/handlers/ProductHandler.ts +++ b/src/app/ew/fabric/handlers/ProductHandler.ts @@ -1,26 +1,21 @@ -import { - CrudOperations, - IRepository, - OperationKeys, - PrimaryKeyType, -} from '@decaf-ts/db-decorators'; +import { Condition } from '@decaf-ts/core'; +import { CrudOperations, IRepository, OperationKeys, PrimaryKeyType } from '@decaf-ts/db-decorators'; import { Model } from '@decaf-ts/decorator-validation'; -import { ComponentEventNames } from '@decaf-ts/ui-decorators'; +import { getNgxToastComponent } from 'src/app/utils/NgxToastComponent'; +import { FileUploadComponent } from 'src/lib/components'; +import { getNgxModalComponent } from 'src/lib/components/modal/modal.component'; import { - NgxEventHandler, - ICrudFormEvent, - KeyValue, + ActionRoles, DecafRepository, FormParent, + getModelAndRepository, + ICrudFormEvent, IRepositoryModelProps, - ActionRoles, - NgxComponentDirective, + KeyValue, + NgxEventHandler, } from 'src/lib/engine'; -import { getNgxToastComponent } from 'src/app/utils/NgxToastComponent'; -import { getNgxModalComponent } from 'src/lib/components/modal/modal.component'; +import { Product } from '..'; import { ProductImage } from '../ProductImage'; -import { Batch, Product } from '..'; -import { CrudFieldComponent } from 'src/lib/components/crud-field/crud-field.component'; export class ProductHandler extends NgxEventHandler { formGroup!: FormParent; @@ -29,12 +24,10 @@ export class ProductHandler extends NgxEventHandler { // static deleteEvents: > = {}; - static data: - | Record; data?: Model[] }>> - | undefined; + static data: Record; data?: Model[] }>> | undefined; static readonly modelId: string; - override async render(instance: any, prop: string): Promise { + override async render(instance: any, prop: string, value: string): Promise { if (instance.pk) { ProductHandler.pk = instance.pk; } @@ -44,8 +37,13 @@ export class ProductHandler extends NgxEventHandler { // instance.hidden = true; // instance.component.nativeElement.style.display = 'none'; } + + if (prop === 'imageData') { + await ProductHandler.getProductImageData(instance as FileUploadComponent, value); + } } } + override async handle(event: ICrudFormEvent): Promise { const { name, role } = event; const allowSubmit = true; @@ -182,6 +180,16 @@ export class ProductHandler extends NgxEventHandler { } } + static async query() { + const context = getModelAndRepository(Product.name); + if (context) { + const { repository } = context; + const query = await repository.select().execute(); + return query || []; + } + return []; + } + static store(operation: CrudOperations, repository: DecafRepository, model: Model): void { const data = ProductHandler.data || {}; if (!(operation in data)) { @@ -197,8 +205,22 @@ export class ProductHandler extends NgxEventHandler { static getStored(operation: CrudOperations) { return ProductHandler.data?.[operation] || {}; } + static async getProductImageData(instance: FileUploadComponent, value: string) { + const repo = getModelAndRepository('ProductImage'); + if (repo && value) { + const { repository } = repo; + const data = (await repository + .select() + .where(Condition.attr('productCode' as keyof ProductImage).eq(instance.value)) + .execute()) as ProductImage[]; + if (data?.length) { + instance.setValue(data[0].content); + // instance.value = JSON.parse(data[0].content[0]) as string; + } + } + } - static getProductImage(data: M, repository: DecafRepository): M { + static buildImageDataModel(data: M, repository: DecafRepository): M { if (repository.class.name === Product.name) { const model = data as Product & M; if (!model.imageData) return data as M; @@ -207,7 +229,7 @@ export class ProductHandler extends NgxEventHandler { productCode: model['productCode'], content: model['imageData'], }, - ProductImage.name, + ProductImage.name ) as ProductImage; return model; } @@ -218,11 +240,8 @@ export class ProductHandler extends NgxEventHandler { ProductHandler.data = undefined; } - override async beforeCreate( - data: M, - repository: DecafRepository, - ): Promise { - return ProductHandler.getProductImage(data, repository); + override async beforeCreate(data: M, repository: DecafRepository): Promise { + return ProductHandler.buildImageDataModel(data, repository); } // static async beforeSave( @@ -264,11 +283,7 @@ export class ProductHandler extends NgxEventHandler { // ? filterDiffs // : undefined; // } - static async checkDiffs( - data: M, - repository: IRepository, - modelId: PrimaryKeyType, - ): Promise { + static async checkDiffs(data: M, repository: IRepository, modelId: PrimaryKeyType): Promise { const skip = [ Model.pk(repository.class) as keyof M, // 'strengths', @@ -310,15 +325,11 @@ export class ProductHandler extends NgxEventHandler { override async beforeUpdate( data: M, repository: DecafRepository, - modelId: PrimaryKeyType, + modelId: PrimaryKeyType ): Promise { - data = ProductHandler.getProductImage(data, repository); + data = ProductHandler.buildImageDataModel(data, repository); const modelName = repository.class.name; - const diffs = await ProductHandler.checkDiffs( - Model.build(data, modelName), - repository, - modelId, - ); + const diffs = await ProductHandler.checkDiffs(Model.build(data, modelName), repository, modelId); if (diffs) { const locale = modelName.toLowerCase(); const modal = await getNgxModalComponent({ diff --git a/src/app/pages/leaflet/leaflet.page.ts b/src/app/pages/leaflet/leaflet.page.ts index c267c303..a73b2712 100644 --- a/src/app/pages/leaflet/leaflet.page.ts +++ b/src/app/pages/leaflet/leaflet.page.ts @@ -1,15 +1,15 @@ import { Component, OnInit } from '@angular/core'; +import { OperationKeys } from '@decaf-ts/db-decorators'; import { IonContent } from '@ionic/angular/standalone'; import { TranslatePipe } from '@ngx-translate/core'; -import { HeaderComponent } from 'src/app/components/header/header.component'; +import { AppCardTitleComponent } from 'src/app/components/card-title/card-title.component'; import { ContainerComponent } from 'src/app/components/container/container.component'; -import { NgxModelPageDirective } from 'src/lib/engine'; -import { ModelRendererComponent } from 'src/lib/components/model-renderer/model-renderer.component'; -import { EmptyStateComponent } from 'src/lib/components/empty-state/empty-state.component'; +import { HeaderComponent } from 'src/app/components/header/header.component'; import { Leaflet } from 'src/app/ew/fabric/Leaflet'; import { TableComponent } from 'src/lib/components'; -import { OperationKeys } from '@decaf-ts/db-decorators'; -import { AppCardTitleComponent } from 'src/app/components/card-title/card-title.component'; +import { EmptyStateComponent } from 'src/lib/components/empty-state/empty-state.component'; +import { ModelRendererComponent } from 'src/lib/components/model-renderer/model-renderer.component'; +import { NgxModelPageDirective } from 'src/lib/engine'; @Component({ selector: 'app-leaflet', @@ -40,7 +40,9 @@ export class LeafletPage extends NgxModelPageDirective implements OnInit { await super.initialize(); } - // override async ionViewWillEnter(): Promise { - // await super.ionViewWillEnter(); - // } + override async ionViewWillEnter(): Promise { + await super.ionViewWillEnter(); + console.log(this._data); + console.log(this.model); + } } diff --git a/src/app/pages/products/products.page.ts b/src/app/pages/products/products.page.ts index f7422d99..16e4051b 100644 --- a/src/app/pages/products/products.page.ts +++ b/src/app/pages/products/products.page.ts @@ -1,27 +1,20 @@ import { Component, OnInit } from '@angular/core'; +import { OperationKeys } from '@decaf-ts/db-decorators'; import { IonContent } from '@ionic/angular/standalone'; -import { ModelRendererComponent } from 'src/lib/components/model-renderer/model-renderer.component'; -import { HeaderComponent } from 'src/app/components/header/header.component'; -import { ContainerComponent } from 'src/app/components/container/container.component'; -import { ListComponent } from 'src/lib/components/list/list.component'; -import { NgxModelPageDirective } from 'src/lib/engine/NgxModelPageDirective'; -import { EmptyStateComponent } from 'src/lib/components'; import { TranslatePipe } from '@ngx-translate/core'; -import { ICrudFormEvent, ITabItem } from 'src/lib/engine/interfaces'; -import { ProductLayout } from 'src/app/ew/layouts/ProductLayout'; -import { Product } from 'src/app/ew/fabric/Product'; -import { CardComponent } from 'src/lib/components/card/card.component'; -import { ProductHandler } from 'src/app/ew/fabric/handlers/ProductHandler'; +import { AppCardTitleComponent } from 'src/app/components/card-title/card-title.component'; +import { ContainerComponent } from 'src/app/components/container/container.component'; +import { HeaderComponent } from 'src/app/components/header/header.component'; import { AppModalDiffsComponent } from 'src/app/components/modal-diffs/modal-diffs.component'; -import { EpiTabs } from 'src/app/ew/utils/constants'; -import { OperationKeys } from '@decaf-ts/db-decorators'; -import { Model } from '@decaf-ts/decorator-validation'; import { AppProductItemComponent } from 'src/app/components/product-item/product-item.component'; -import { AppCardTitleComponent } from 'src/app/components/card-title/card-title.component'; -import { KeyValue } from 'src/lib/engine/types'; -import { ProductStrength } from 'src/app/ew/fabric'; -import { getModelAndRepository } from 'src/lib/engine'; -import { Condition } from '@decaf-ts/core'; +import { Product } from 'src/app/ew/fabric/Product'; +import { ProductLayout } from 'src/app/ew/layouts/ProductLayout'; +import { EpiTabs } from 'src/app/ew/utils/constants'; +import { EmptyStateComponent } from 'src/lib/components'; +import { ListComponent } from 'src/lib/components/list/list.component'; +import { ModelRendererComponent } from 'src/lib/components/model-renderer/model-renderer.component'; +import { ITabItem } from 'src/lib/engine/interfaces'; +import { NgxModelPageDirective } from 'src/lib/engine/NgxModelPageDirective'; /** * @description Angular component page for CRUD operations on dynamic model entities. @@ -146,6 +139,7 @@ export class ProductsPage extends NgxModelPageDirective implements OnInit { this.route = 'products'; await this.initialize(); + console.log(this.model); // function generateGtin(): string { // function pad(num: number, width: number, padding: string = '0') { // const n = num + ''; @@ -188,7 +182,7 @@ export class ProductsPage extends NgxModelPageDirective implements OnInit { } override async ionViewWillEnter(): Promise { - await this.refresh(this.modelId); + await super.ionViewWillEnter(); console.log(this.model); } } diff --git a/src/app/utils/FakerRepository.ts b/src/app/utils/FakerRepository.ts index 7e420c3f..4dafd2b5 100644 --- a/src/app/utils/FakerRepository.ts +++ b/src/app/utils/FakerRepository.ts @@ -8,7 +8,8 @@ import { DbAdapterFlavour } from '../app.config'; import { Audit } from '../ew/fabric/Audit'; import { Batch } from '../ew/fabric/Batch'; import { generateGtin } from '../ew/fabric/gtin'; -import { Leaflet } from '../ew/fabric/Leaflet'; +import { Leaflet, LeafletType } from '../ew/fabric/Leaflet'; +import { LeafletFile } from '../ew/fabric/LeafletFile'; import { Product } from '../ew/fabric/Product'; import { AIModel } from '../models/AIVendorModel'; import { AIFeatures } from './contants'; @@ -149,23 +150,42 @@ export class FakerRepository extends DecafFakerRepository { break; } case Leaflet.name: { - // const products = await getProducts(); - // this.limit = 2; - // this.propFnMapper = { - // productCode: () => this.pickRandomValue(products.map((p) => p.productCode)), - // lang: () => this.pickRandomValue(['en', 'pt-br']), - // epiMarket: () => this.pickRandomValue(['al', 'br']), - // }; - // let batches = await getBatchs(); - // data = (await this.generateData()).map((item) => { - // // item.epiMarket = this.pickRandomValue([... getMarkets().map(({value}) => value)]); - // item.leafletType = this.pickRandomValue(LeafletType) as LeafletType; - // item.otherFilesContent = []; - // // item.productCode = this.pickRandomValue(products); - // item.batchNumber = batches.find((b) => b.productCode === item.productCode)?.batchNumber; - // item.createdAt = item.updatedAt = faker.date.past({ years: 10 }); - // return item; - // }); + const products = await getQueryResults('Product'); + const batches = await getQueryResults('Batch'); + const now = new Date(); + function buildFileContent(leafletId: string, fileName: string, fileContent: string): LeafletFile { + const file = Model.build( + { + leafletId, + fileName, + fileContent, + + id: `${leafletId}:${fileName}`, + createdAt: now, + updatedAt: now, + }, + LeafletFile.name + ) as LeafletFile; + + return file; + } + this.limit = 2; + this.propFnMapper = { + productCode: () => this.pickRandomValue(products.map((p) => p.productCode)), + lang: () => this.pickRandomValue(['en', 'pt-br']), + epiMarket: () => this.pickRandomValue(['al', 'br']), + }; + data = (await this.generateData()).map((item) => { + // item.epiMarket = this.pickRandomValue([... getMarkets().map(({value}) => value)]); + item.leafletType = this.pickRandomValue(LeafletType) as LeafletType; + item.otherFilesContent = [buildFileContent(item.id, 'a.png', 'base64encodedstring')]; + item.xmlFileContent = buildFileContent(item.id, 'leaflet.xml', 'base64encodedstring'); + // item.productCode = this.pickRandomValue(products); + item.batchNumber = batches.find((b) => b.productCode === item.productCode)?.batchNumber; + item.createdAt = now; + item.updatedAt = now; + return item; + }); break; } // case ProductStrength.name: { diff --git a/src/lib/components/file-upload/file-upload.component.ts b/src/lib/components/file-upload/file-upload.component.ts index 49d6971c..c491251d 100644 --- a/src/lib/components/file-upload/file-upload.component.ts +++ b/src/lib/components/file-upload/file-upload.component.ts @@ -125,8 +125,16 @@ export class FileUploadComponent extends NgxFormFieldDirective implements OnInit @Input() override type: PossibleInputTypes = HTML5InputTypes.FILE; + /** + * @description Specifies the value type for the file upload. + * @summary Determines the format in which the uploaded file's value is returned. + * Options include 'base64' for base64-encoded strings or 'files' for File objects. + * + * @type {'base64' | 'files'} + * @default 'base64' + */ @Input() - subType: 'text' | 'file' = 'text'; + valueType: 'base64' | 'files' = 'base64'; /** * @description Label for the upload button. @@ -258,6 +266,17 @@ export class FileUploadComponent extends NgxFormFieldDirective implements OnInit */ private dragCounter: number = 0; + /** + * @description Specifies the subtype of the file upload. + * @summary Lock subtype to 'text' to ensure that the value is processed as a JSON string containing data URLs or Files. + * This property will be used to avoid validation errors and to determine how the value is processed and emitted. + * + * @type {string} + * @default 'text' + */ + @Input() + subType: string = 'text'; + constructor() { super('FileUploadComponent'); this.handleClear(); @@ -284,24 +303,7 @@ export class FileUploadComponent extends NgxFormFieldDirective implements OnInit override async initialize(): Promise { await super.initialize(); - - if (this.value && typeof this.value === Primitives.STRING) { - try { - const files = JSON.parse(this.value as string) as string[]; - this.files = files.map((file) => { - const mime = this.getFileMime(file)?.split('/') || []; - const type = mime?.[0] === 'text' ? mime?.[1] : `${mime?.[0]}/${mime?.[1]}`; - return { - name: mime?.[0] || 'file', - type: `${type}` || 'image/*', - source: file as string, - } as KeyValue; - }); - this.getPreview(); - } catch (error: unknown) { - this.log.for(this.initialize).error(`Error parsing file list: ${(error as Error).message || error}`); - } - } + await this.parseValue(true); } /** @@ -437,15 +439,52 @@ export class FileUploadComponent extends NgxFormFieldDirective implements OnInit this.files = [validFiles[0]]; } if (this.files.length) { - const dataValues = await this.getDataURLs(this.files as File[]); - this.setValue(this.subType === 'text' ? JSON.stringify(dataValues) : this.files); + this.setValue(await this.parseValue()); } - await this.getPreview(); this.changeEventEmit(); this.changeDetectorRef.detectChanges(); } + async parseValue(revert: boolean = false): Promise { + if (!revert) { + const files = this.files as File[]; + if (this.valueType === 'base64') { + return JSON.stringify(await this.getDataURLs(files)); + } else { + const data = []; + for (const file of files) { + const source = await this.getDataURLs(file); + data.push({ + name: file.name, + size: file.size, + type: file.type, + source, + }); + } + return JSON.stringify(data); + } + } + if (this.value && typeof this.value === Primitives.STRING) { + try { + const value = JSON.parse(this.value as string); + const files = Array.isArray(value) ? value : [value]; + this.files = files.map((file) => { + const mime = this.getFileMime(file)?.split('/') || []; + const type = mime?.[0] === 'text' ? mime?.[1] : `${mime?.[0]}/${mime?.[1]}`; + return { + name: mime?.[0] || 'file', + type: `${type}` || 'image/*', + source: file as string, + } as KeyValue; + }); + this.getPreview(); + } catch (error: unknown) { + this.log.for(this.initialize).error(`Error parsing file list: ${(error as Error).message || error}`); + } + } + } + /** * @description Validates a single file against the component's constraints. * @summary Checks the file type and size against the accepted values and limits. @@ -484,7 +523,9 @@ export class FileUploadComponent extends NgxFormFieldDirective implements OnInit let content: string | undefined; if (file instanceof File) { const dataUrl = (await this.getDataURLs(file)) as string[]; - if (dataUrl && dataUrl.length) file = dataUrl[0]; + if (dataUrl) { + file = Array.isArray(dataUrl) ? dataUrl[0] : dataUrl; + } } if (fileExtension.includes('image')) content = ''; @@ -532,6 +573,12 @@ export class FileUploadComponent extends NgxFormFieldDirective implements OnInit return file && file.type.startsWith('image/'); } + isBase64String(value: string): boolean { + const pure = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/; + const dataUrl = /^data:([a-zA-Z0-9.+-]+\/[a-zA-Z0-9.+-]+);base64,[A-Za-z0-9+/]+={0,2}$/; + return pure.test(value) || dataUrl.test(value); + } + getFileMime(base64: string): string { const match = base64.match(/^data:(.*?);base64,/); return match ? match[1] : ''; @@ -563,7 +610,9 @@ export class FileUploadComponent extends NgxFormFieldDirective implements OnInit const file = this.files && this.files.length ? this.files[0] : null; if (file instanceof File) { const dataUrl = (await this.getDataURLs(file as File)) as string[]; - if (dataUrl && dataUrl.length) this.previewFile = dataUrl[0]; + if (dataUrl) { + this.previewFile = Array.isArray(dataUrl) ? dataUrl[0] : dataUrl; + } } else { this.previewFile = (file as KeyValue)?.['source'] as string; } @@ -594,16 +643,19 @@ export class FileUploadComponent extends NgxFormFieldDirective implements OnInit * * @returns {Promise} - A promise that resolves to an array of data URLs, or undefined if an error occurs. */ - async getDataURLs(files?: File[] | File): Promise { - if (!files) files = this.files as File[]; - if (!Array.isArray(files)) files = [files]; - // files = files.filter(f => f.type && f.type.startsWith('image/')); + async getDataURLs(files?: File[] | File): Promise { + if (!files) { + files = this.files as File[]; + } + files = !Array.isArray(files) ? [files] : files; return this.readFile(files) .then((urls) => { // validate generated DataURLs const invalid = urls.some((u) => !this.isValidDataURL(u)); if (invalid) return undefined; - if (this.multiple || this.enableDirectoryMode) return urls; + if (this.multiple || this.enableDirectoryMode) { + return urls.length === 1 ? urls[0] : urls; + } return urls.length ? [urls[0]] : undefined; }) .catch(() => { diff --git a/src/lib/engine/NgxModelPageDirective.ts b/src/lib/engine/NgxModelPageDirective.ts index 9076bb52..a05e4cc7 100644 --- a/src/lib/engine/NgxModelPageDirective.ts +++ b/src/lib/engine/NgxModelPageDirective.ts @@ -1,4 +1,5 @@ import { AfterViewInit, Directive, Input } from '@angular/core'; +import { Repository } from '@decaf-ts/core'; import { CrudOperations, InternalError, @@ -7,14 +8,13 @@ import { OperationKeys, PrimaryKeyType, } from '@decaf-ts/db-decorators'; -import { Repository } from '@decaf-ts/core'; +import { Constructor, Metadata } from '@decaf-ts/decoration'; import { Model, Primitives } from '@decaf-ts/decorator-validation'; import { ComponentEventNames } from '@decaf-ts/ui-decorators'; import { NgxPageDirective } from './NgxPageDirective'; +import { getModelAndRepository } from './helpers'; import { ICrudFormEvent, ILayoutModelContext, IModelComponentSubmitEvent } from './interfaces'; import { CrudEvent, DecafRepository, KeyValue } from './types'; -import { Constructor, Metadata } from '@decaf-ts/decoration'; -import { getModelAndRepository } from './helpers'; @Directive() export abstract class NgxModelPageDirective extends NgxPageDirective implements AfterViewInit { @@ -30,11 +30,8 @@ export abstract class NgxModelPageDirective extends NgxPageDirective implements * @memberOf ModelPage */ @Input() - override operation: - | OperationKeys.CREATE - | OperationKeys.READ - | OperationKeys.UPDATE - | OperationKeys.DELETE = OperationKeys.READ; + override operation: OperationKeys.CREATE | OperationKeys.READ | OperationKeys.UPDATE | OperationKeys.DELETE = + OperationKeys.READ; /** * @description Error message from failed operations. @@ -67,16 +64,13 @@ export abstract class NgxModelPageDirective extends NgxPageDirective implements try { if (!this._repository && modelName) { const constructor = Model.get(modelName); - if (!constructor) - throw new InternalError('Cannot find model. was it registered with @model?'); + if (!constructor) throw new InternalError('Cannot find model. was it registered with @model?'); this._repository = Repository.forModel(constructor); if (!this.pk) this.pk = Model.pk(constructor) as string; this.model = new constructor() as Model; } } catch (error: unknown) { - this.log.warn( - `Error getting repository for model: ${modelName}. ${(error as Error).message}`, - ); + this.log.warn(`Error getting repository for model: ${modelName}. ${(error as Error).message}`); this._repository = undefined; // throw new InternalError((error as Error)?.message || (error as string)); } @@ -124,7 +118,7 @@ export abstract class NgxModelPageDirective extends NgxPageDirective implements case OperationKeys.UPDATE: case OperationKeys.DELETE: { - await this.handleRead(uid); + this.model = await this.handleRead(uid); } break; } @@ -168,10 +162,7 @@ export abstract class NgxModelPageDirective extends NgxPageDirective implements * * @param {IBaseCustomEvent} event - The event object containing event data and metadata */ - override async handleEvent( - event: CrudEvent, - repository?: DecafRepository, - ): Promise { + override async handleEvent(event: CrudEvent, repository?: DecafRepository): Promise { const { name, role, handler, data, modelId, handlers } = event; if (!this.modelId && modelId) { this.modelId = modelId; @@ -245,17 +236,13 @@ export abstract class NgxModelPageDirective extends NgxPageDirective implements async getTransactionRepository( event: CrudEvent, - repo: DecafRepository, + repo: DecafRepository ): Promise> { if (!repo) { repo = this._repository as DecafRepository; } if (!repo || repo?.class?.name !== this.model?.constructor?.name) { - const { context } = (await this.process( - event, - this.model as M, - false, - )) as ILayoutModelContext; + const { context } = (await this.process(event, this.model as M, false)) as ILayoutModelContext; if (context) { // parse data from main model to event event.data = context.data as M; @@ -295,7 +282,7 @@ export abstract class NgxModelPageDirective extends NgxPageDirective implements async handleRead( uid?: PrimaryKeyType, repository?: IRepository, - modelName?: string, + modelName?: string ): Promise { if (!uid) { this.log.info('No key passed to model page read operation, backing to last page'); @@ -304,13 +291,12 @@ export abstract class NgxModelPageDirective extends NgxPageDirective implements if (!modelName) { modelName = this.modelName; - if (!modelName && this.model?.constructor) - this.modelName = modelName = this.model.constructor.name; + if (!modelName && this.model?.constructor) this.modelName = modelName = this.model.constructor.name; } const getRepository = async ( modelName: string, acc: KeyValue = {}, - parent: string = '', + parent: string = '' ): Promise | void> => { if (this._repository) { return this._repository as DecafRepository; @@ -364,13 +350,9 @@ export abstract class NgxModelPageDirective extends NgxPageDirective implements } try { if (!this.pk) this.pk = Model.pk(repository.class) as string; - return await repository.read( - this.parsePkValue(uid as Primitives, this.getModelPkType(repository.class)), - ); + return await repository.read(this.parsePkValue(uid as Primitives, this.getModelPkType(repository.class))); } catch (error: unknown) { - this.log - .for(this.handleRead) - .info(`Error getting model instance with id ${uid}: ${(error as Error).message}`); + this.log.for(this.handleRead).info(`Error getting model instance with id ${uid}: ${(error as Error).message}`); return undefined; } } @@ -378,22 +360,17 @@ export abstract class NgxModelPageDirective extends NgxPageDirective implements async process( event: CrudEvent, model?: M, - submit: boolean = false, + submit: boolean = false ): Promise> { const result = { models: {} } as ILayoutModelContext; const eventData = event.data as KeyValue; - const iterate = async ( - evt: ICrudFormEvent & { data: KeyValue }, - model: string | M, - parent?: string, - ) => { + 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?.[prop] || (parent ? eventData?.[parent as string][prop] : eventData?.[prop]); + let data = 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); @@ -460,7 +437,7 @@ export abstract class NgxModelPageDirective extends NgxPageDirective implements event: CrudEvent, redirect: boolean = false, repo?: DecafRepository, - pk?: keyof M, + pk?: keyof M ): Promise> { let success = false; let message = ''; @@ -488,9 +465,7 @@ export abstract class NgxModelPageDirective extends NgxPageDirective implements switch (operation) { case OperationKeys.CREATE: - result = await (!Array.isArray(model) - ? repository.create(model as M) - : repository.createAll(model as M[])); + result = await (!Array.isArray(model) ? repository.create(model as M) : repository.createAll(model as M[])); break; case OperationKeys.UPDATE: { const models = (!Array.isArray(model) ? [model] : model) as M[]; @@ -525,9 +500,7 @@ export abstract class NgxModelPageDirective extends NgxPageDirective implements this.log .for(this.submit) .error( - `Error during ${this.operation} operation: ${ - error instanceof Error ? error.message : (error as string) - }`, + `Error during ${this.operation} operation: ${error instanceof Error ? error.message : (error as string)}` ); message = error instanceof Error ? error.message : (error as string); } diff --git a/src/lib/utils/DecafFakerRepository.ts b/src/lib/utils/DecafFakerRepository.ts index 494f67b5..c86f801d 100644 --- a/src/lib/utils/DecafFakerRepository.ts +++ b/src/lib/utils/DecafFakerRepository.ts @@ -1,13 +1,13 @@ -import { faker } from '@faker-js/faker'; -import { parseToNumber } from '@decaf-ts/ui-decorators'; -import { Model, Primitives } from '@decaf-ts/decorator-validation'; +import { Repository } from '@decaf-ts/core'; import { InternalError } from '@decaf-ts/db-decorators'; import { Metadata, uses } from '@decaf-ts/decoration'; -import { Repository } from '@decaf-ts/core'; +import { Model, Primitives } from '@decaf-ts/decorator-validation'; +import { LoggedClass } from '@decaf-ts/logging'; +import { parseToNumber } from '@decaf-ts/ui-decorators'; +import { faker } from '@faker-js/faker'; import { DB_ADAPTER_FLAVOUR_TOKEN } from '../engine/constants'; -import { DecafRepository, KeyValue, FunctionLike } from '../engine/types'; +import { DecafRepository, FunctionLike, KeyValue } from '../engine/types'; import { formatDate, getOnWindow } from './helpers'; -import { LoggedClass } from '@decaf-ts/logging'; export class DecafFakerRepository extends LoggedClass { protected propFnMapper?: KeyValue; @@ -22,20 +22,16 @@ export class DecafFakerRepository extends LoggedClass { constructor( protected model: string | Model, - protected limit: number = 36, + protected limit: number = 36 ) { super(); } protected get repository(): DecafRepository { if (!this._repository) { - const modelName = - typeof this.model === Primitives.STRING - ? this.model - : (this.model as Model).constructor.name; + const modelName = typeof this.model === Primitives.STRING ? this.model : (this.model as Model).constructor.name; const constructor = Model.get(String(modelName)); - if (!constructor) - throw new InternalError(`Cannot find model ${modelName}. was it registered with @model?`); + if (!constructor) throw new InternalError(`Cannot find model ${modelName}. was it registered with @model?`); try { this.model = new constructor(); const dbAdapterFlavour = getOnWindow(DB_ADAPTER_FLAVOUR_TOKEN) || undefined; @@ -54,11 +50,7 @@ export class DecafFakerRepository extends LoggedClass { if (!this._repository) this._repository = this.repository; } - async generateData( - values?: KeyValue, - key?: string, - keyType?: string, - ): Promise { + async generateData(values?: KeyValue, key?: string, keyType?: string): Promise { const limit = values ? Object.values(values || {}).length : this.limit; if (!key) key = Model.pk(this.repository.class) as string; @@ -78,7 +70,7 @@ export class DecafFakerRepository extends LoggedClass { const data = getFakerData( limit, dataFunctions, - typeof this.model === Primitives.STRING ? String(this.model) : this.model?.constructor.name, + typeof this.model === Primitives.STRING ? String(this.model) : this.model?.constructor.name ); if (!values) return data; @@ -124,8 +116,7 @@ export class DecafFakerRepository extends LoggedClass { protected getModelProperties(pk: string, pkType: string): string[] { return Object.keys(this.model as KeyValue).filter((k) => { - if (pkType === Primitives.STRING) - return !['updatedBy', 'createdAt', 'createdBy', 'updatedAt'].includes(k); + if (pkType === Primitives.STRING) return !['updatedBy', 'createdAt', 'createdBy', 'updatedAt'].includes(k); return ![pk, 'updatedBy', 'createdAt', 'createdBy', 'updatedAt'].includes(k); }); } @@ -156,22 +147,14 @@ export class DecafFakerRepository extends LoggedClass { } } -export function getFakerData( - limit = 100, - data: Record, - model?: string, -): T[] { +export function getFakerData(limit = 100, data: Record, model?: string): T[] { let index = 1; return Array.from({ length: limit }, () => { const item: Record = {}; for (const [key, value] of Object.entries(data)) { const val = value(); item[key] = - val?.constructor === Date - ? formatDate(val) - : typeof val === Primitives.STRING - ? String(val)?.trim() - : val; + val?.constructor === Date ? formatDate(val) : typeof val === Primitives.STRING ? String(val)?.trim() : val; } index = index + 1; return (!model ? item : Model.build(item, model)) as T;