From c6d51deded4d6d47e93b0a57890bdfdf113a6d4f Mon Sep 17 00:00:00 2001 From: Domenico Gemoli Date: Thu, 14 Nov 2024 12:14:26 +0100 Subject: [PATCH] portalicious: registrations table component AB#31335 Moving the registrations table into a separate component so that it can easily be re-used by the "do payment" flow --- .../query-table/query-table.component.ts | 7 +- .../registrations-table.component.html | 18 ++ .../registrations-table.component.ts | 155 +++++++++++++ .../registration/registration.helper.ts | 17 ++ .../project-registrations.page.html | 35 ++- .../project-registrations.page.ts | 205 +++++------------- 6 files changed, 264 insertions(+), 173 deletions(-) create mode 100644 interfaces/Portalicious/src/app/components/registrations-table/registrations-table.component.html create mode 100644 interfaces/Portalicious/src/app/components/registrations-table/registrations-table.component.ts diff --git a/interfaces/Portalicious/src/app/components/query-table/query-table.component.ts b/interfaces/Portalicious/src/app/components/query-table/query-table.component.ts index 16e146e9dd..0786532681 100644 --- a/interfaces/Portalicious/src/app/components/query-table/query-table.component.ts +++ b/interfaces/Portalicious/src/app/components/query-table/query-table.component.ts @@ -121,7 +121,7 @@ export class QueryTableComponent { items = input.required(); isPending = input.required(); columns = input.required[]>(); - localStorageKey = input.required(); + localStorageKey = input(); contextMenuItems = input(); globalFilterFields = input<(keyof TData & string)[]>(); expandableRowTemplate = input>>(); @@ -244,7 +244,10 @@ export class QueryTableComponent { clearAllFilters() { this.table.clear(); - localStorage.removeItem(this.localStorageKey()); + const localStorageKey = this.localStorageKey(); + if (localStorageKey) { + localStorage.removeItem(localStorageKey); + } this.globalFilterVisible.set(false); this.tableFilters.set({}); this.selectAll.set(false); diff --git a/interfaces/Portalicious/src/app/components/registrations-table/registrations-table.component.html b/interfaces/Portalicious/src/app/components/registrations-table/registrations-table.component.html new file mode 100644 index 0000000000..16f6e4cc89 --- /dev/null +++ b/interfaces/Portalicious/src/app/components/registrations-table/registrations-table.component.html @@ -0,0 +1,18 @@ + +
+ +
+
diff --git a/interfaces/Portalicious/src/app/components/registrations-table/registrations-table.component.ts b/interfaces/Portalicious/src/app/components/registrations-table/registrations-table.component.ts new file mode 100644 index 0000000000..1295a08831 --- /dev/null +++ b/interfaces/Portalicious/src/app/components/registrations-table/registrations-table.component.ts @@ -0,0 +1,155 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + inject, + input, + signal, + ViewChild, +} from '@angular/core'; + +import { injectQuery } from '@tanstack/angular-query-experimental'; +import { MenuItem } from 'primeng/api'; + +import { RegistrationStatusEnum } from '@121-service/src/registration/enum/registration-status.enum'; +import { PermissionEnum } from '@121-service/src/user/enum/permission.enum'; + +import { getChipDataByRegistrationStatus } from '~/components/colored-chip/colored-chip.helper'; +import { + QueryTableColumn, + QueryTableColumnType, + QueryTableComponent, + QueryTableSelectionEvent, +} from '~/components/query-table/query-table.component'; +import { RegistrationApiService } from '~/domains/registration/registration.api.service'; +import { + REGISTRATION_STATUS_LABELS, + registrationLink, +} from '~/domains/registration/registration.helper'; +import { Registration } from '~/domains/registration/registration.model'; +import { + PaginateQuery, + PaginateQueryService, +} from '~/services/paginate-query.service'; +import { ToastService } from '~/services/toast.service'; + +@Component({ + selector: 'app-registrations-table', + standalone: true, + imports: [QueryTableComponent], + templateUrl: './registrations-table.component.html', + styles: ``, + providers: [ToastService], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class RegistrationsTableComponent { + projectId = input.required(); + contextMenuItems = input(); + localStorageKey = input(); + + private paginateQueryService = inject(PaginateQueryService); + private registrationApiService = inject(RegistrationApiService); + private toastService = inject(ToastService); + + PermissionEnum = PermissionEnum; + + @ViewChild('table') + private table: QueryTableComponent; + + protected RegistrationStatusEnum = RegistrationStatusEnum; + protected paginateQuery = signal(undefined); + protected tableSelection = signal>([]); + public contextMenuRegistration = signal(undefined); + + protected registrationsResponse = injectQuery( + this.registrationApiService.getManyByQuery( + this.projectId, + this.paginateQuery, + ), + ); + + protected registrations = computed( + () => this.registrationsResponse.data()?.data ?? [], + ); + protected totalRegistrations = computed( + () => this.registrationsResponse.data()?.meta.totalItems ?? 0, + ); + + protected columns = computed[]>(() => [ + { + field: 'personAffectedSequence', + fieldForSort: 'registrationProgramId', + header: $localize`PA #`, + getCellRouterLink: (registration) => + registrationLink({ + projectId: this.projectId(), + registrationId: registration.id, + }), + }, + { + field: 'fullName', + header: $localize`:@@registration-full-name:Full Name`, + getCellRouterLink: (registration) => + registrationLink({ + projectId: this.projectId(), + registrationId: registration.id, + }), + }, + { + field: 'registrationCreated', + fieldForFilter: 'registrationCreatedDate', + header: $localize`:@@registration-created:Registration created`, + type: QueryTableColumnType.DATE, + }, + { + field: 'status', + header: $localize`:@@registration-status:Status`, + type: QueryTableColumnType.MULTISELECT, + options: Object.entries(REGISTRATION_STATUS_LABELS).map( + ([value, label]) => ({ + label, + value, + }), + ), + getCellChipData: (registration) => + getChipDataByRegistrationStatus(registration.status), + }, + ]); + + public getActionData({ + triggeredFromContextMenu = false, + }: { + triggeredFromContextMenu?: boolean; + } = {}) { + let selection = this.tableSelection(); + + if (Array.isArray(selection) && selection.length === 0) { + if (triggeredFromContextMenu) { + const contextMenuRegistration = this.contextMenuRegistration(); + if (!contextMenuRegistration) { + this.toastService.showGenericError(); + return; + } + selection = [contextMenuRegistration]; + } else { + this.toastService.showToast({ + severity: 'error', + detail: $localize`:@@no-registrations-selected:Select one or more registrations and try again.`, + }); + return; + } + } + + return this.paginateQueryService.selectionEventToActionData({ + selection, + fieldForFilter: 'referenceId', + totalCount: this.totalRegistrations(), + currentPaginateQuery: this.paginateQuery(), + previewItemForSelectAll: this.registrations()[0], + }); + } + + public resetSelection() { + this.table.resetSelection(); + } +} diff --git a/interfaces/Portalicious/src/app/domains/registration/registration.helper.ts b/interfaces/Portalicious/src/app/domains/registration/registration.helper.ts index 01aa463252..ab6abfb023 100644 --- a/interfaces/Portalicious/src/app/domains/registration/registration.helper.ts +++ b/interfaces/Portalicious/src/app/domains/registration/registration.helper.ts @@ -1,6 +1,7 @@ import { ActivityTypeEnum } from '@121-service/src/activities/enum/activity-type.enum'; import { RegistrationStatusEnum } from '@121-service/src/registration/enum/registration-status.enum'; +import { AppRoutes } from '~/app.routes'; import { ChipVariant } from '~/components/colored-chip/colored-chip.component'; import { // TODO: AB#30525 should import this from 121-service @@ -83,3 +84,19 @@ export const ACTIVITY_LOG_ITEM_TYPE_ICONS: Record = { [ActivityTypeEnum.StatusChange]: 'pi pi-refresh', [ActivityTypeEnum.Transaction]: 'pi pi-money-bill', }; + +export function registrationLink({ + projectId, + registrationId, +}: { + projectId: number; + registrationId: number; +}) { + return [ + '/', + AppRoutes.project, + projectId, + AppRoutes.projectRegistrations, + registrationId, + ]; +} diff --git a/interfaces/Portalicious/src/app/pages/project-registrations/project-registrations.page.html b/interfaces/Portalicious/src/app/pages/project-registrations/project-registrations.page.html index 14b7251679..e856ebb323 100644 --- a/interfaces/Portalicious/src/app/pages/project-registrations/project-registrations.page.html +++ b/interfaces/Portalicious/src/app/pages/project-registrations/project-registrations.page.html @@ -13,31 +13,22 @@ @if (canExport()) { } -
- @if ( - canChangeStatus(RegistrationStatusEnum.validated) && - project.data()?.validation - ) { + @if (canChangeStatus()(RegistrationStatusEnum.validated)) { } - @if (canChangeStatus(RegistrationStatusEnum.included)) { + @if (canChangeStatus()(RegistrationStatusEnum.included)) { } - @if (canChangeStatus(RegistrationStatusEnum.declined)) { + @if (canChangeStatus()(RegistrationStatusEnum.declined)) { } - @if (canChangeStatus(RegistrationStatusEnum.paused)) { + @if (canChangeStatus()(RegistrationStatusEnum.paused)) { } - @if (canChangeStatus(RegistrationStatusEnum.deleted)) { + @if (canChangeStatus()(RegistrationStatusEnum.deleted)) {
-
+
diff --git a/interfaces/Portalicious/src/app/pages/project-registrations/project-registrations.page.ts b/interfaces/Portalicious/src/app/pages/project-registrations/project-registrations.page.ts index f16dc73307..fe85a39874 100644 --- a/interfaces/Portalicious/src/app/pages/project-registrations/project-registrations.page.ts +++ b/interfaces/Portalicious/src/app/pages/project-registrations/project-registrations.page.ts @@ -4,7 +4,6 @@ import { computed, inject, input, - signal, ViewChild, } from '@angular/core'; import { Router } from '@angular/router'; @@ -18,28 +17,15 @@ import { CardModule } from 'primeng/card'; import { RegistrationStatusEnum } from '@121-service/src/registration/enum/registration-status.enum'; import { PermissionEnum } from '@121-service/src/user/enum/permission.enum'; -import { AppRoutes } from '~/app.routes'; -import { getChipDataByRegistrationStatus } from '~/components/colored-chip/colored-chip.helper'; import { PageLayoutComponent } from '~/components/page-layout/page-layout.component'; -import { - QueryTableColumn, - QueryTableColumnType, - QueryTableComponent, - QueryTableSelectionEvent, -} from '~/components/query-table/query-table.component'; +import { RegistrationsTableComponent } from '~/components/registrations-table/registrations-table.component'; import { ProjectApiService } from '~/domains/project/project.api.service'; -import { RegistrationApiService } from '~/domains/registration/registration.api.service'; -import { REGISTRATION_STATUS_LABELS } from '~/domains/registration/registration.helper'; -import { Registration } from '~/domains/registration/registration.model'; +import { registrationLink } from '~/domains/registration/registration.helper'; import { ChangeStatusDialogComponent } from '~/pages/project-registrations/components/change-status-dialog/change-status-dialog.component'; import { ExportRegistrationsComponent } from '~/pages/project-registrations/components/export-registrations/export-registrations.component'; import { ImportRegistrationsComponent } from '~/pages/project-registrations/components/import-registrations/import-registrations.component'; import { SendMessageDialogComponent } from '~/pages/project-registrations/components/send-message-dialog/send-message-dialog.component'; import { AuthService } from '~/services/auth.service'; -import { - PaginateQuery, - PaginateQueryService, -} from '~/services/paginate-query.service'; import { ToastService } from '~/services/toast.service'; @Component({ @@ -48,13 +34,13 @@ import { ToastService } from '~/services/toast.service'; imports: [ PageLayoutComponent, CardModule, - QueryTableComponent, ButtonModule, ButtonGroupModule, SendMessageDialogComponent, ExportRegistrationsComponent, ChangeStatusDialogComponent, ImportRegistrationsComponent, + RegistrationsTableComponent, ], providers: [ToastService], templateUrl: './project-registrations.page.html', @@ -63,125 +49,30 @@ import { ToastService } from '~/services/toast.service'; }) export class ProjectRegistrationsPageComponent { // this is injected by the router - projectId = input.required(); + protected projectId = input.required(); - public authService = inject(AuthService); + private authService = inject(AuthService); private router = inject(Router); - private paginateQueryService = inject(PaginateQueryService); - private registrationApiService = inject(RegistrationApiService); private projectApiService = inject(ProjectApiService); private toastService = inject(ToastService); - PermissionEnum = PermissionEnum; - - @ViewChild('table') - private table: QueryTableComponent; + @ViewChild('registrationsTable') + private registrationsTable: RegistrationsTableComponent; @ViewChild('sendMessageDialog') private sendMessageDialog: SendMessageDialogComponent; @ViewChild('changeStatusDialog') private changeStatusDialog: ChangeStatusDialogComponent; RegistrationStatusEnum = RegistrationStatusEnum; - paginateQuery = signal(undefined); - tableSelection = signal>([]); - contextMenuRegistration = signal(undefined); - - registrationsResponse = injectQuery( - this.registrationApiService.getManyByQuery( - this.projectId, - this.paginateQuery, - ), - ); project = injectQuery(this.projectApiService.getProject(this.projectId)); - registrations = computed(() => this.registrationsResponse.data()?.data ?? []); - totalRegistrations = computed( - () => this.registrationsResponse.data()?.meta.totalItems ?? 0, - ); - - columns = computed[]>(() => [ - { - field: 'personAffectedSequence', - fieldForSort: 'registrationProgramId', - header: $localize`PA #`, - getCellRouterLink: (registration) => - this.registrationLink(registration.id), - }, - { - field: 'fullName', - header: $localize`:@@registration-full-name:Full Name`, - getCellRouterLink: (registration) => - this.registrationLink(registration.id), - }, - { - field: 'registrationCreated', - fieldForFilter: 'registrationCreatedDate', - header: $localize`:@@registration-created:Registration created`, - type: QueryTableColumnType.DATE, - }, - { - field: 'status', - header: $localize`:@@registration-status:Status`, - type: QueryTableColumnType.MULTISELECT, - options: Object.entries(REGISTRATION_STATUS_LABELS).map( - ([value, label]) => ({ - label, - value, - }), - ), - getCellChipData: (registration) => - getChipDataByRegistrationStatus(registration.status), - }, - ]); - - private registrationLink = (registrationId: number) => [ - '/', - AppRoutes.project, - this.projectId(), - AppRoutes.projectRegistrations, - registrationId, - ]; - - getActionData({ - triggeredFromContextMenu = false, - }: { - triggeredFromContextMenu?: boolean; - } = {}) { - let selection = this.tableSelection(); - - if (Array.isArray(selection) && selection.length === 0) { - if (triggeredFromContextMenu) { - const contextMenuRegistration = this.contextMenuRegistration(); - if (!contextMenuRegistration) { - this.toastService.showGenericError(); - return; - } - selection = [contextMenuRegistration]; - } else { - this.toastService.showToast({ - severity: 'error', - detail: $localize`:@@no-registrations-selected:Select one or more registrations and try again.`, - }); - return; - } - } - - return this.paginateQueryService.selectionEventToActionData({ - selection, - fieldForFilter: 'referenceId', - totalCount: this.totalRegistrations(), - currentPaginateQuery: this.paginateQuery(), - previewItemForSelectAll: this.registrations()[0], - }); - } - sendMessage({ triggeredFromContextMenu = false, }: { triggeredFromContextMenu?: boolean; } = {}) { - const actionData = this.getActionData({ + const actionData = this.registrationsTable.getActionData({ triggeredFromContextMenu, }); @@ -199,7 +90,7 @@ export class ProjectRegistrationsPageComponent { status: RegistrationStatusEnum; triggeredFromContextMenu?: boolean; }) { - const actionData = this.getActionData({ + const actionData = this.registrationsTable.getActionData({ triggeredFromContextMenu, }); @@ -211,33 +102,43 @@ export class ProjectRegistrationsPageComponent { } onActionComplete() { - this.table.resetSelection(); + this.registrationsTable.resetSelection(); } - canChangeStatus( - status: - | RegistrationStatusEnum.declined - | RegistrationStatusEnum.deleted - | RegistrationStatusEnum.included - | RegistrationStatusEnum.paused - | RegistrationStatusEnum.validated, - ) { - const statusToPermissionMap = { - [RegistrationStatusEnum.validated]: - PermissionEnum.RegistrationStatusMarkAsValidatedUPDATE, - [RegistrationStatusEnum.included]: - PermissionEnum.RegistrationStatusIncludedUPDATE, - [RegistrationStatusEnum.declined]: - PermissionEnum.RegistrationStatusMarkAsDeclinedUPDATE, - [RegistrationStatusEnum.deleted]: PermissionEnum.RegistrationDELETE, - [RegistrationStatusEnum.paused]: - PermissionEnum.RegistrationStatusPausedUPDATE, - }; - return this.authService.hasPermission({ - projectId: this.projectId(), - requiredPermission: statusToPermissionMap[status], - }); - } + canChangeStatus = computed( + () => + ( + status: + | RegistrationStatusEnum.declined + | RegistrationStatusEnum.deleted + | RegistrationStatusEnum.included + | RegistrationStatusEnum.paused + | RegistrationStatusEnum.validated, + ) => { + if ( + status === RegistrationStatusEnum.validated && + !this.project.data()?.validation + ) { + return false; + } + + const statusToPermissionMap = { + [RegistrationStatusEnum.validated]: + PermissionEnum.RegistrationStatusMarkAsValidatedUPDATE, + [RegistrationStatusEnum.included]: + PermissionEnum.RegistrationStatusIncludedUPDATE, + [RegistrationStatusEnum.declined]: + PermissionEnum.RegistrationStatusMarkAsDeclinedUPDATE, + [RegistrationStatusEnum.deleted]: PermissionEnum.RegistrationDELETE, + [RegistrationStatusEnum.paused]: + PermissionEnum.RegistrationStatusPausedUPDATE, + }; + return this.authService.hasPermission({ + projectId: this.projectId(), + requiredPermission: statusToPermissionMap[status], + }); + }, + ); canSendMessage = computed(() => this.authService.hasPermission({ @@ -269,18 +170,24 @@ export class ProjectRegistrationsPageComponent { label: $localize`Go to profile`, icon: 'pi pi-user', command: () => { - const registration = this.contextMenuRegistration(); + const registration = + this.registrationsTable.contextMenuRegistration(); if (!registration) { this.toastService.showGenericError(); return; } - void this.router.navigate(this.registrationLink(registration.id)); + void this.router.navigate( + registrationLink({ + projectId: this.projectId(), + registrationId: registration.id, + }), + ); }, }, { label: $localize`Validate`, icon: 'pi pi-check-circle', - visible: this.canChangeStatus(RegistrationStatusEnum.validated), + visible: this.canChangeStatus()(RegistrationStatusEnum.validated), command: () => { this.changeStatus({ status: RegistrationStatusEnum.validated, @@ -291,7 +198,7 @@ export class ProjectRegistrationsPageComponent { { label: $localize`Include`, icon: 'pi pi-check', - visible: this.canChangeStatus(RegistrationStatusEnum.included), + visible: this.canChangeStatus()(RegistrationStatusEnum.included), command: () => { this.changeStatus({ status: RegistrationStatusEnum.included, @@ -302,7 +209,7 @@ export class ProjectRegistrationsPageComponent { { label: $localize`Decline`, icon: 'pi pi-times', - visible: this.canChangeStatus(RegistrationStatusEnum.declined), + visible: this.canChangeStatus()(RegistrationStatusEnum.declined), command: () => { this.changeStatus({ status: RegistrationStatusEnum.declined, @@ -323,7 +230,7 @@ export class ProjectRegistrationsPageComponent { { label: $localize`Delete`, icon: 'pi pi-trash', - visible: this.canChangeStatus(RegistrationStatusEnum.deleted), + visible: this.canChangeStatus()(RegistrationStatusEnum.deleted), command: () => { this.changeStatus({ status: RegistrationStatusEnum.deleted,