diff --git a/package-lock.json b/package-lock.json index ec6d064d6..af678aed2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -147,7 +147,7 @@ "date-fns": "^2.17.0", "deep-object-diff": "^1.1.9", "depd": "^2.0.0", - "dexie": "^4.0.7", + "dexie": "^4.0.11", "dompurify": "^3.0.6", "dotenv": "^10.0.0", "duration": "^0.2.2", @@ -15180,9 +15180,9 @@ "integrity": "sha512-pmMDBKiRVjh0uKK6CT1WqZmM3hBVSgD+N2MrgyV1uNizAZMw4tx6i/RTc+/uCsKSCmg0xXx7arCP/OFcIwTsiQ==" }, "node_modules/dexie": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.0.7.tgz", - "integrity": "sha512-M+Lo6rk4pekIfrc2T0o2tvVJwL6EAAM/B78DNfb8aaxFVoI1f8/rz5KTxuAnApkwqTSuxx7T5t0RKH7qprapGg==" + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.0.11.tgz", + "integrity": "sha512-SOKO002EqlvBYYKQSew3iymBoN2EQ4BDw/3yprjh7kAfFzjBYkaMNa/pZvcA7HSWlcKSQb9XhPe3wKyQ0x4A8A==" }, "node_modules/di": { "version": "0.0.1", @@ -55940,9 +55940,9 @@ "integrity": "sha512-pmMDBKiRVjh0uKK6CT1WqZmM3hBVSgD+N2MrgyV1uNizAZMw4tx6i/RTc+/uCsKSCmg0xXx7arCP/OFcIwTsiQ==" }, "dexie": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.0.7.tgz", - "integrity": "sha512-M+Lo6rk4pekIfrc2T0o2tvVJwL6EAAM/B78DNfb8aaxFVoI1f8/rz5KTxuAnApkwqTSuxx7T5t0RKH7qprapGg==" + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.0.11.tgz", + "integrity": "sha512-SOKO002EqlvBYYKQSew3iymBoN2EQ4BDw/3yprjh7kAfFzjBYkaMNa/pZvcA7HSWlcKSQb9XhPe3wKyQ0x4A8A==" }, "di": { "version": "0.0.1", diff --git a/package.json b/package.json index 4f361e186..4189640be 100644 --- a/package.json +++ b/package.json @@ -166,7 +166,7 @@ "date-fns": "^2.17.0", "deep-object-diff": "^1.1.9", "depd": "^2.0.0", - "dexie": "^4.0.7", + "dexie": "^4.0.11", "dompurify": "^3.0.6", "dotenv": "^10.0.0", "duration": "^0.2.2", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 6d89de9da..cf9dbd396 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -104,6 +104,7 @@ import { registerLocaleData } from '@angular/common'; import localePt from '@angular/common/locales/pt'; import { LogsDatabase } from './infra/database/dexie/instance/logs/service'; import { UserModule } from './module/user/user.module'; +import { GabineteModule } from './module/gabinete/gabinete.module'; // Register the locale data registerLocaleData(localePt, 'pt'); @@ -210,7 +211,8 @@ registerLocaleData(localePt, 'pt'); MatIconModule, // module ChatModule, - UserModule + UserModule, + GabineteModule ], entryComponents: [ DiplomaOptionsPage, diff --git a/src/app/core/chat/entity/message.ts b/src/app/core/chat/entity/message.ts index 235839a51..3b46604d4 100644 --- a/src/app/core/chat/entity/message.ts +++ b/src/app/core/chat/entity/message.ts @@ -72,7 +72,7 @@ export const MessageEntitySchema = z.object({ export type IMessage = z.infer; -export class MessageEntity { +export class MessageEntity implements IMessage { $id?: string id?: string diff --git a/src/app/core/gabinete/dto/task-list-output-dto.ts b/src/app/core/gabinete/dto/task-list-output-dto.ts new file mode 100644 index 000000000..dd97cb690 --- /dev/null +++ b/src/app/core/gabinete/dto/task-list-output-dto.ts @@ -0,0 +1,50 @@ +import { z } from "zod"; + +export const TaskListOutputItemDTO = z.object({ + id: z.number(), + instanceID: z.number(), + folderId: z.number(), + folio: z.string(), + elementID: z.number(), + elementName: z.string(), + workflowId: z.number(), + workflowName: z.string(), + dispatchNumber: z.string(), + sender: z.string().optional(), + status: z.number(), + createdOn: z.coerce.date(), + createdByName: z.string(), + lastUpdate: z.coerce.date(), + assignedToUserID: z.number(), + assignedOpened: z.boolean(), + assignedUser: z.string(), + delegatedUserId: z.number().optional(), + delegatedOpened: z.boolean(), + delegatedUser: z.string().optional(), + delegatedOn: z.coerce.date().optional(), + openedBy: z.number().optional(), + openedUser: z.string().optional(), + openedDate: z.coerce.date().optional(), + completedBy: z.number().optional(), + completedUser: z.string().optional(), + completedDate: z.coerce.date().optional(), + dueDate: z.coerce.date().optional(), + sequencial: z.boolean(), + order: z.number(), + acknowledge: z.boolean(), + instruction: z.string().optional(), + note: z.string().optional(), + processCreatedBy: z.number(), + processCreatedByName: z.string(), + activityDataFields: z.any().optional(), + assigneeStandBy: z.boolean(), + delegateStandBy: z.boolean(), + pending: z.boolean().default(false), +}); + +export const TaskListOutputDTOSchema = z.object({ + total: z.number(), + result: z.array(TaskListOutputItemDTO), +}); + +export type ITaskListOutputDTO = z.infer; diff --git a/src/app/core/gabinete/entity/task.entity.ts b/src/app/core/gabinete/entity/task.entity.ts new file mode 100644 index 000000000..080386fd8 --- /dev/null +++ b/src/app/core/gabinete/entity/task.entity.ts @@ -0,0 +1,58 @@ +import { z } from 'zod'; +import { TaskListOutputItemDTO } from '../dto/task-list-output-dto'; + +export const TaskEntitySchema = z.object({ + +}).merge(TaskListOutputItemDTO); + +export type ITaskEntity = z.infer; + +export class TaskEntity implements ITaskEntity { + id: typeof TaskEntitySchema._type.id; + instanceID: typeof TaskEntitySchema._type.instanceID; + folderId: typeof TaskEntitySchema._type.folderId; + folio: typeof TaskEntitySchema._type.folio; + elementID: typeof TaskEntitySchema._type.elementID; + elementName: typeof TaskEntitySchema._type.elementName; + workflowId: typeof TaskEntitySchema._type.workflowId; + workflowName: typeof TaskEntitySchema._type.workflowName; + dispatchNumber: typeof TaskEntitySchema._type.dispatchNumber; + sender?: typeof TaskEntitySchema._type.sender; + status: typeof TaskEntitySchema._type.status; + createdOn: typeof TaskEntitySchema._type.createdOn; + createdByName: typeof TaskEntitySchema._type.createdByName; + lastUpdate: typeof TaskEntitySchema._type.lastUpdate; + assignedToUserID: typeof TaskEntitySchema._type.assignedToUserID; + assignedOpened: typeof TaskEntitySchema._type.assignedOpened; + assignedUser: typeof TaskEntitySchema._type.assignedUser; + delegatedUserId?: typeof TaskEntitySchema._type.delegatedUserId; + delegatedOpened: typeof TaskEntitySchema._type.delegatedOpened; + delegatedUser?: typeof TaskEntitySchema._type.delegatedUser; + delegatedOn?: typeof TaskEntitySchema._type.delegatedOn; + openedBy?: typeof TaskEntitySchema._type.openedBy; + openedUser?: typeof TaskEntitySchema._type.openedUser; + openedDate?: typeof TaskEntitySchema._type.openedDate; + completedBy?: typeof TaskEntitySchema._type.completedBy; + completedUser?: typeof TaskEntitySchema._type.completedUser; + completedDate?: typeof TaskEntitySchema._type.completedDate; + dueDate?: typeof TaskEntitySchema._type.dueDate; + sequencial: typeof TaskEntitySchema._type.sequencial; + order: typeof TaskEntitySchema._type.order; + acknowledge: typeof TaskEntitySchema._type.acknowledge; + instruction?: typeof TaskEntitySchema._type.instruction; + note?: typeof TaskEntitySchema._type.note; + processCreatedBy: typeof TaskEntitySchema._type.processCreatedBy; + processCreatedByName: typeof TaskEntitySchema._type.processCreatedByName; + activityDataFields?: typeof TaskEntitySchema._type.activityDataFields; + assigneeStandBy: typeof TaskEntitySchema._type.assigneeStandBy; + delegateStandBy: typeof TaskEntitySchema._type.delegateStandBy; + pending: typeof TaskEntitySchema._type.pending; + + constructor(data: ITaskEntity) { + Object.assign(this, data); + } + + setData(data: Partial): void { + Object.assign(this, data); + } +} diff --git a/src/app/core/gabinete/use-case/task-get-all-by-user-id.ts b/src/app/core/gabinete/use-case/task-get-all-by-user-id.ts new file mode 100644 index 000000000..e6e7630da --- /dev/null +++ b/src/app/core/gabinete/use-case/task-get-all-by-user-id.ts @@ -0,0 +1,61 @@ +import { TracingType } from "src/app/services/monitoring/opentelemetry/tracer"; +import { TaskEntity } from "../entity/task.entity"; +import { TaskRepository } from "src/app/module/gabinete/data/repository/task.repository"; +import { Injectable } from "@angular/core"; +import { TaskLocalRepository } from "src/app/module/gabinete/data/repository/task-local.repository"; + +@Injectable({ + providedIn: 'root' +}) +export class TaskGetAllByUserIdUseCase { + + constructor( + private taskRepository: TaskRepository, + private taskLocalRepository: TaskLocalRepository + ) {} + + async execute(tracing?: TracingType): Promise { + const finalList: Record = {}; + + await Promise.all([ + (async () => { + const response = await this.taskRepository.getAllTasksPaginated({ + pendingtasks: true, + tracing + }); + + if (response.isOk()) { + const list = response.value + for (const task of list) { + task.pending = true; + } + for (const task of list) { + finalList[task.id.toString()] = task; + } + } + })(), + (async () => { + const response = await this.taskRepository.getAllTasksPaginated({ + pendingtasks: false, + tracing + }); + + if (response.isOk()) { + const list = response.value; + for (const task of list) { + finalList[task.id.toString()] = task; + } + } + })() + ]); + tracing?.finish(); + + //await this._localRepo.clear(); + //await this._localRepo.updateMany(Object.values(finalList)); + + //return this._localRepo.getAll(); + + return []; + } + +} diff --git a/src/app/infra/database/dexie/instance/gabinete/schema/task.ts b/src/app/infra/database/dexie/instance/gabinete/schema/task.ts new file mode 100644 index 000000000..da36bb9e7 --- /dev/null +++ b/src/app/infra/database/dexie/instance/gabinete/schema/task.ts @@ -0,0 +1,11 @@ +import { EntityTable } from 'Dexie'; +import { TaskEntitySchema } from 'src/app/core/gabinete/entity/task.entity'; +import { z } from 'zod'; + +export const taskTableSchema = z.object({ + //$id: z.string().optional(), +}).merge(TaskEntitySchema); + +export type TaskTable = z.infer +export type DexieTaskTable = EntityTable; +export const taskTableColumn = 'id, instanceID, folderId, folio, elementID, elementName, workflowId, workflowName, dispatchNumber, sender, status, createdOn, createdByName, lastUpdate, assignedToUserID, assignedOpened, assignedUser, delegatedUserId, delegatedOpened, delegatedUser, delegatedOn, openedBy, openedUser, openedDate, completedBy, completedUser, completedDate, dueDate, sequencial, order, acknowledge, instruction, note, processCreatedBy, processCreatedByName, activityDataFields, assigneeStandBy, delegateStandBy, pending'; diff --git a/src/app/infra/database/dexie/instance/gabinete/service.ts b/src/app/infra/database/dexie/instance/gabinete/service.ts new file mode 100644 index 000000000..0b63f9cfa --- /dev/null +++ b/src/app/infra/database/dexie/instance/gabinete/service.ts @@ -0,0 +1,16 @@ + +import { Dexie } from 'Dexie'; +import { DexieTaskTable, taskTableColumn } from './schema/task'; + +// Database declaration (move this to its own module also) +export const gabineteDatabase = new Dexie('gabinete-database-v1',{ + //indexedDB: new FDBFactory, + //IDBKeyRange: FDBKeyRange, // Mocking IDBKeyRange +}) as Dexie & { + task: DexieTaskTable, + +}; + +gabineteDatabase.version(1).stores({ + message: taskTableColumn, +}); diff --git a/src/app/module/gabinete/data/repository/task-local.repository.ts b/src/app/module/gabinete/data/repository/task-local.repository.ts new file mode 100644 index 000000000..4c0993bcd --- /dev/null +++ b/src/app/module/gabinete/data/repository/task-local.repository.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import { DexieRepository } from 'src/app/infra/repository/dexie/dexie-repository.service'; +import { chatDatabase } from 'src/app/infra/database/dexie/instance/chat/service'; +import { TaskEntity } from 'src/app/core/gabinete/entity/task.entity'; +import { TaskTable, DexieTaskTable, taskTableSchema } from 'src/app/infra/database/dexie/instance/gabinete/schema/task'; +import { gabineteDatabase } from 'src/app/infra/database/dexie/instance/gabinete/service'; + +@Injectable({ + providedIn: 'root' +}) +export class TaskLocalRepository extends DexieRepository { + + constructor() { + super(gabineteDatabase.task, taskTableSchema, gabineteDatabase) + } + +} + diff --git a/src/app/module/gabinete/data/repository/task.repository.ts b/src/app/module/gabinete/data/repository/task.repository.ts new file mode 100644 index 000000000..0ee6db65a --- /dev/null +++ b/src/app/module/gabinete/data/repository/task.repository.ts @@ -0,0 +1,78 @@ +import { Injectable } from '@angular/core'; +import { APINODReturn } from 'src/app/services/decorator/api-validate-schema.decorator'; +import { TracingType } from 'src/app/services/monitoring/opentelemetry/tracer'; +import { HttpService, isHttpError } from 'src/app/services/http.service'; +import { err, ok, Result } from 'neverthrow'; +import { HttpErrorResponse } from '@angular/common/http'; +import { ITaskEntity, TaskEntity } from 'src/app/core/gabinete/entity/task.entity'; +import { SessionStore } from 'src/app/store/session.service'; +import { z } from 'zod'; +import { ITaskListOutputDTO, TaskListOutputDTOSchema } from 'src/app/core/gabinete/dto/task-list-output-dto'; + +@Injectable({ + providedIn: 'root' +}) +export class TaskRepository { + + private baseUrl = 'https://dev-api.doneit.co.ao/api'; // Your base URL + + constructor( + private httpService: HttpService + ) { } + + + + async getAllTasksPaginated(options?: { + pendingtasks?: boolean; + tracing?: TracingType; + }): Promise> { + let currentPage = 1; + let totalTasks = 0; + const allTasks: TaskEntity[] = []; + + const tracing = options?.tracing; + + do { + const queryParams: Record = { + PageNumber: currentPage.toString(), + PageSize: '500' + }; + + if (options?.pendingtasks !== undefined) { + queryParams['pendingtasks'] = options.pendingtasks.toString(); + } + + const result = await this.httpService.get( + `${this.baseUrl}/Processes/${SessionStore.user.UserId}`, + queryParams, + tracing + ); + + if (result.isErr()) { + return err(result.error); + } + + try { + const dto = result.value; + const rawList = dto.result; + totalTasks = dto.total; + allTasks.push(...rawList.map(item => new TaskEntity(item))); + } catch (e) { + tracing?.setAttribute('outcome', 'mapping_failed'); + tracing?.log('Failed to map task entities', { + context: (e as Error).message + }); + + return err({ + name: 'MappingError', + message: 'Failed to map response to TaskEntity', + } as unknown as HttpErrorResponse); + } + + currentPage++; + } while (allTasks.length < totalTasks); + + return ok(allTasks); + } + +} diff --git a/src/app/module/gabinete/domain/gabinete.service.ts b/src/app/module/gabinete/domain/gabinete.service.ts new file mode 100644 index 000000000..de756b963 --- /dev/null +++ b/src/app/module/gabinete/domain/gabinete.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from "@angular/core"; +import { TaskGetAllByUserIdUseCase } from "src/app/core/gabinete/use-case/task-get-all-by-user-id"; + + +@Injectable({ + providedIn: 'root' +}) +export class GabineteService { + constructor( + private taskGetAllByUserIdUseCase: TaskGetAllByUserIdUseCase + ) {} + + + taskGetAllByUserId() { + this.taskGetAllByUserIdUseCase.execute(); + } +} diff --git a/src/app/module/gabinete/gabinete.module.ts b/src/app/module/gabinete/gabinete.module.ts new file mode 100644 index 000000000..154e564cf --- /dev/null +++ b/src/app/module/gabinete/gabinete.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from '@angular/core'; +import { SignalRService } from 'src/app/infra/socket/signalR/signal-r.service'; +import { HttpModule } from 'src/app/infra/http/http.module'; +import { TaskGetAllByUserIdUseCase } from 'src/app/core/gabinete/use-case/task-get-all-by-user-id'; +import { ISignalRService } from 'src/app/infra/socket/adapter'; +import { TaskLocalRepository } from './data/repository/task-local.repository'; + +@NgModule({ + imports: [HttpModule], + providers: [ + { + provide: ISignalRService, + useClass: SignalRService, // or MockDataService + }, + { + provide: TaskGetAllByUserIdUseCase, + useClass: TaskGetAllByUserIdUseCase, // or MockDataService + }, + { + provide: TaskLocalRepository, + useClass: TaskLocalRepository + } + ], + declarations: [], + schemas: [], + entryComponents: [] +}) +export class GabineteModule { + + constructor() {} + + async listenToTyping() {} + async syncMessage() {} +} diff --git a/src/app/pages/gabinete-digital/gabinete-digital.page.ts b/src/app/pages/gabinete-digital/gabinete-digital.page.ts index a8f53d4a6..1cfbeabb1 100644 --- a/src/app/pages/gabinete-digital/gabinete-digital.page.ts +++ b/src/app/pages/gabinete-digital/gabinete-digital.page.ts @@ -16,6 +16,7 @@ import { SessionStore } from 'src/app/store/session.service'; import { NotificationsService } from 'src/app/services/notifications.service' import { environment } from 'src/environments/environment'; import { TaskService } from 'src/app/services/task.service' +import { GabineteService } from 'src/app/module/gabinete/domain/gabinete.service' @Component({ selector: 'app-gabinete-digital', @@ -103,9 +104,12 @@ export class GabineteDigitalPage implements OnInit { public ThemeService: ThemeService, public p: PermissionService, public NotificationsService: NotificationsService, - public TaskService: TaskService + public TaskService: TaskService, + private gabineteService: GabineteService, ) { + this.gabineteService.taskGetAllByUserId(); + window.onresize = (event) => { if (window.innerWidth < 701) { this.hideRefreshBtn = false; diff --git a/version/git-version.ts b/version/git-version.ts index eafe497b1..1e030f1ce 100644 --- a/version/git-version.ts +++ b/version/git-version.ts @@ -1,11 +1,11 @@ export let versionData = { - "shortSHA": "470b1e7f0", - "SHA": "470b1e7f0e0e7a4ae7155d7b88c322e770e21f26", - "branch": "feature/login-v2", - "lastCommitAuthor": "'peter.maquiran'", - "lastCommitTime": "'Tue Jun 3 09:42:08 2025 +0100'", - "lastCommitMessage": "fix", - "lastCommitNumber": "6141", - "changeStatus": "On branch feature/login-v2\nYour branch is ahead of 'origin/feature/login-v2' by 6 commits.\n (use \"git push\" to publish your local commits)\n\nChanges to be committed:\n (use \"git restore --staged ...\" to unstage)\n\tmodified: version/git-version.ts", + "shortSHA": "cfc7330e7", + "SHA": "cfc7330e729bb815925e687ffa74c20fe4431eb3", + "branch": "api-doneit", + "lastCommitAuthor": "'Peter Maquiran'", + "lastCommitTime": "'Tue Jun 3 08:46:54 2025 +0000'", + "lastCommitMessage": "Merged in feature/login-v2 (pull request #36)\n\nFeature/login v2", + "lastCommitNumber": "6143", + "changeStatus": "On branch api-doneit\nYour branch is up to date with 'origin/api-doneit'.\n\nChanges to be committed:\n (use \"git restore --staged ...\" to unstage)\n\tmodified: package-lock.json\n\tmodified: package.json\n\tmodified: src/app/app.module.ts\n\tmodified: src/app/core/chat/entity/message.ts\n\tnew file: src/app/core/gabinete/dto/task-list-output-dto.ts\n\tnew file: src/app/core/gabinete/entity/task.entity.ts\n\tnew file: src/app/core/gabinete/use-case/task-get-all-by-user-id.ts\n\tnew file: src/app/infra/database/dexie/instance/gabinete/schema/task.ts\n\tnew file: src/app/infra/database/dexie/instance/gabinete/service.ts\n\tnew file: src/app/module/gabinete/data/repository/task-local.repository.ts\n\tnew file: src/app/module/gabinete/data/repository/task.repository.ts\n\tnew file: src/app/module/gabinete/domain/gabinete.service.ts\n\tnew file: src/app/module/gabinete/gabinete.module.ts\n\tmodified: src/app/pages/gabinete-digital/gabinete-digital.page.ts", "changeAuthor": "peter.maquiran" } \ No newline at end of file