From ccd46f613725c36183a424d4563895830f01b65b Mon Sep 17 00:00:00 2001 From: Shaed Parkar Date: Thu, 14 Mar 2024 16:08:14 +0000 Subject: [PATCH 01/44] GA-203 track the `mo-new-cases` feature toggle key to hide or show the new cases menu item --- src/app/app-initializer.ts | 1 + src/app/app.constants.ts | 12 +++++- src/app/store/reducers/app.reducer.spec.ts | 9 +++++ src/app/store/selectors/app.selectors.spec.ts | 8 ++++ src/app/store/selectors/app.selectors.ts | 39 +++++++++++++++---- .../store/reducers/organisation.reducer.ts | 7 ++-- 6 files changed, 64 insertions(+), 12 deletions(-) diff --git a/src/app/app-initializer.ts b/src/app/app-initializer.ts index 8d82e6660..168d246fd 100644 --- a/src/app/app-initializer.ts +++ b/src/app/app-initializer.ts @@ -10,6 +10,7 @@ export function initApplication(store: Store): VoidFunction { store.dispatch(new fromApp.LoadFeatureToggleConfig([AppConstants.FEATURE_NAMES.feeAccount, AppConstants.FEATURE_NAMES.editUserPermissions, AppConstants.FEATURE_NAMES.caaMenuItems, + AppConstants.FEATURE_NAMES.newCasesItems, AppConstants.FEATURE_NAMES.newRegisterOrg])); store.pipe( diff --git a/src/app/app.constants.ts b/src/app/app.constants.ts index 38f79edaa..5910b3f22 100644 --- a/src/app/app.constants.ts +++ b/src/app/app.constants.ts @@ -8,6 +8,7 @@ const featureNames = { editUserPermissions: 'edit-permissions', removeUserFromCase: 'remove-user-from-case-mo', caaMenuItems: 'mo-caa-menu-items', + newCasesItems: 'mo-new-cases', newRegisterOrg: 'mo-new-register-org' }; @@ -50,6 +51,15 @@ const navItemsArray: NavItemModel[] = [ featureToggle: { featureName: featureNames.caaMenuItems } + }, + { + text: 'Cases', + href: '/cases', + orderId: 6, + active: false, + featureToggle: { + featureName: featureNames.newCasesItems + } } ]; @@ -57,7 +67,7 @@ const roleBasedNav = { 'pui-organisation-manager': navItemsArray[0], 'pui-user-manager': navItemsArray[1], 'pui-finance-manager': navItemsArray[2], - 'pui-caa': [navItemsArray[3], navItemsArray[4]] + 'pui-caa': [navItemsArray[3], navItemsArray[4], navItemsArray[5]] }; const userNav: UserNavModel = { diff --git a/src/app/store/reducers/app.reducer.spec.ts b/src/app/store/reducers/app.reducer.spec.ts index 93d40c902..50bd874f4 100644 --- a/src/app/store/reducers/app.reducer.spec.ts +++ b/src/app/store/reducers/app.reducer.spec.ts @@ -99,6 +99,15 @@ describe('AppReducer', () => { featureToggle: { featureName: AppConstants.FEATURE_NAMES.caaMenuItems } + }, + { + text: 'Cases', + href: '/cases', + orderId: 6, + active: false, + featureToggle: { + featureName: AppConstants.FEATURE_NAMES.newCasesItems + } } ]; const action = new fromAppActions.SetUserRoles(payload); diff --git a/src/app/store/selectors/app.selectors.spec.ts b/src/app/store/selectors/app.selectors.spec.ts index fe2dbb068..80853368b 100644 --- a/src/app/store/selectors/app.selectors.spec.ts +++ b/src/app/store/selectors/app.selectors.spec.ts @@ -112,6 +112,14 @@ describe('App Selectors', () => { expect(result).toBeUndefined(); }); + it('should get CAA new cases feature is enabled', () => { + const featureFlag: AppFeatureFlag = { featureName: 'mo-new-cases', isEnabled: true }; + let result; + store.dispatch(new fromActions.LoadFeatureToggleConfig(featureFlag)); + store.pipe(select(fromSelectors.getCaaNewCasesMenuItemsFeatureIsEnabled)).subscribe((value) => (result = value)); + expect(result).toBeUndefined(); + }); + it('should get edit user feature', () => { const featureFlags: AppFeatureFlag[] = [ { featureName: 'edit-permissions', isEnabled: true }, diff --git a/src/app/store/selectors/app.selectors.ts b/src/app/store/selectors/app.selectors.ts index 3f7a853b4..b3a288d2d 100644 --- a/src/app/store/selectors/app.selectors.ts +++ b/src/app/store/selectors/app.selectors.ts @@ -28,10 +28,7 @@ export const getHeaderTitle = createSelector( } ); -export const getNav = createSelector( - getAppState, - fromAppFeature.getNavItems -); +export const getNav = createSelector(getAppState, fromAppFeature.getNavItems); export const getFeatureFlag = createSelector( getAppState, @@ -40,7 +37,11 @@ export const getFeatureFlag = createSelector( export const getFeeAndPayFeature = createSelector( getFeatureFlag, - (featureFlags) => featureFlags && featureFlags.find((flag) => flag.featureName === AppConstants.FEATURE_NAMES.feeAccount) + (featureFlags) => + featureFlags && + featureFlags.find( + (flag) => flag.featureName === AppConstants.FEATURE_NAMES.feeAccount + ) ); export const getFeeAndPayFeatureIsEnabled = createSelector( @@ -50,7 +51,11 @@ export const getFeeAndPayFeatureIsEnabled = createSelector( export const getCaaMenuItemsFeature = createSelector( getFeatureFlag, - (featureFlags) => featureFlags && featureFlags.find((flag) => flag.featureName === AppConstants.FEATURE_NAMES.caaMenuItems) + (featureFlags) => + featureFlags && + featureFlags.find( + (flag) => flag.featureName === AppConstants.FEATURE_NAMES.caaMenuItems + ) ); export const getCaaMenuItemsFeatureIsEnabled = createSelector( @@ -58,9 +63,28 @@ export const getCaaMenuItemsFeatureIsEnabled = createSelector( (featureFlag) => featureFlag && featureFlag.isEnabled ); +export const getCaaNewCasesMenuItemsFeature = createSelector( + getFeatureFlag, + (featureFlags) => + featureFlags && + featureFlags.find( + (flag) => flag.featureName === AppConstants.FEATURE_NAMES.newCasesItems + ) +); + +export const getCaaNewCasesMenuItemsFeatureIsEnabled = createSelector( + getCaaNewCasesMenuItemsFeature, + (featureFlag) => featureFlag && featureFlag.isEnabled +); + export const getEditUserFeature = createSelector( getFeatureFlag, - (featureFlags) => featureFlags && featureFlags.find((flag) => flag.featureName === AppConstants.FEATURE_NAMES.editUserPermissions) + (featureFlags) => + featureFlags && + featureFlags.find( + (flag) => + flag.featureName === AppConstants.FEATURE_NAMES.editUserPermissions + ) ); export const getEditUserFeatureIsEnabled = createSelector( @@ -104,7 +128,6 @@ export const getUserNav = createSelector( (state, routes) => { return AppUtils.setSetUserNavItems(state, routes); } - ); export const getTermsAndConditions = createSelector( diff --git a/src/organisation/store/reducers/organisation.reducer.ts b/src/organisation/store/reducers/organisation.reducer.ts index 1eaceb0ae..4adef935a 100644 --- a/src/organisation/store/reducers/organisation.reducer.ts +++ b/src/organisation/store/reducers/organisation.reducer.ts @@ -30,8 +30,9 @@ export function reducer( case fromOrganisation.LOAD_ORGANISATION_SUCCESS: { const paymentAccount: PBANumberModel[] = []; // if the users are loaded before organisation, the profile ids will be added since this is not provided by the GET operation - action.payload = { ...action.payload, organisationProfileIds: state.organisationDetails?.organisationProfileIds }; - action.payload.paymentAccount.forEach((pba) => { + const newPayload = { ...action.payload, organisationProfileIds: state.organisationDetails?.organisationProfileIds }; + const newAction = { ...action, payload: newPayload }; + newAction.payload.paymentAccount.forEach((pba) => { let pbaNumberModel: PBANumberModel; if (typeof pba === 'string') { pbaNumberModel = { @@ -41,7 +42,7 @@ export function reducer( paymentAccount.push(pbaNumberModel); }); const loadedOrgDetails = { - ...action.payload, + ...newAction.payload, paymentAccount, pendingAddPaymentAccount: [], pendingRemovePaymentAccount: [] From 2a5b7a7ca598038f138cb2104ce5a0d02308d2eb Mon Sep 17 00:00:00 2001 From: Shaed Parkar Date: Thu, 14 Mar 2024 16:35:30 +0000 Subject: [PATCH 02/44] Create a cases page for the new cases flow --- src/app/app.constants.ts | 2 +- src/app/app.routes.ts | 5 ++++ src/caa-cases/caa-cases.module.ts | 3 ++- src/caa-cases/caa-cases.routing.ts | 12 +++++++++- .../containers/cases/cases.component.html | 1 + .../containers/cases/cases.component.scss | 0 .../containers/cases/cases.component.spec.ts | 23 +++++++++++++++++++ .../containers/cases/cases.component.ts | 10 ++++++++ src/caa-cases/containers/index.ts | 5 +++- .../guards/new-cases-feature-toggle.guard.ts | 14 +++++++++++ 10 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 src/caa-cases/containers/cases/cases.component.html create mode 100644 src/caa-cases/containers/cases/cases.component.scss create mode 100644 src/caa-cases/containers/cases/cases.component.spec.ts create mode 100644 src/caa-cases/containers/cases/cases.component.ts create mode 100644 src/caa-cases/guards/new-cases-feature-toggle.guard.ts diff --git a/src/app/app.constants.ts b/src/app/app.constants.ts index 5910b3f22..3b35654a5 100644 --- a/src/app/app.constants.ts +++ b/src/app/app.constants.ts @@ -54,7 +54,7 @@ const navItemsArray: NavItemModel[] = [ }, { text: 'Cases', - href: '/cases', + href: '/cases/all', orderId: 6, active: false, featureToggle: { diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 7c299e969..97f1cd206 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -25,6 +25,11 @@ export const ROUTES: Routes = [ canActivate: [AuthGuard, HealthCheckGuard], loadChildren: () => import('../fee-accounts/fee-accounts.module').then((m) => m.FeeAccountsModule) }, + { + path: 'cases', + canActivate: [AuthGuard, HealthCheckGuard], + loadChildren: () => import('../caa-cases/caa-cases.module').then((m) => m.CaaCasesModule) + }, { path: 'unassigned-cases', canActivate: [AuthGuard, HealthCheckGuard], diff --git a/src/caa-cases/caa-cases.module.ts b/src/caa-cases/caa-cases.module.ts index 390720a07..076eccaa3 100644 --- a/src/caa-cases/caa-cases.module.ts +++ b/src/caa-cases/caa-cases.module.ts @@ -19,6 +19,7 @@ import { FeatureToggleAccountGuard } from './guards/feature-toggle.guard'; import { RoleGuard } from './guards/user-role.guard'; import * as fromServices from './services'; import { effects, reducers } from './store'; +import { NewCaseFeatureToggleGuard } from './guards/new-cases-feature-toggle.guard'; @NgModule({ imports: [ @@ -39,7 +40,7 @@ import { effects, reducers } from './store'; ], exports: [...fromContainers.containers, ...fromComponents.components], declarations: [...fromContainers.containers, ...fromComponents.components], - providers: [...fromServices.services, OrganisationService, PBAService, UsersService, InviteUserService, FeatureToggleAccountGuard, RoleGuard], + providers: [...fromServices.services, OrganisationService, PBAService, UsersService, InviteUserService, FeatureToggleAccountGuard, NewCaseFeatureToggleGuard, RoleGuard], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) diff --git a/src/caa-cases/caa-cases.routing.ts b/src/caa-cases/caa-cases.routing.ts index 0823604b6..a1fc6474e 100644 --- a/src/caa-cases/caa-cases.routing.ts +++ b/src/caa-cases/caa-cases.routing.ts @@ -2,11 +2,21 @@ import { ModuleWithProviders } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { AuthGuard } from '../user-profile/guards/auth.guard'; import { CaaCasesModule } from './caa-cases.module'; -import { CaaCasesComponent, CaseShareCompleteComponent, CaseShareComponent, CaseShareConfirmComponent } from './containers'; +import { CaaCasesComponent, CaseShareCompleteComponent, CaseShareComponent, CaseShareConfirmComponent, CasesComponent } from './containers'; import { FeatureToggleAccountGuard } from './guards/feature-toggle.guard'; import { RoleGuard } from './guards/user-role.guard'; +import { NewCaseFeatureToggleGuard } from './guards/new-cases-feature-toggle.guard'; export const ROUTES: Routes = [ + { + path: 'all', + component: CasesComponent, + canActivate: [ + AuthGuard, + NewCaseFeatureToggleGuard, + RoleGuard + ] + }, { path: '', component: CaaCasesComponent, diff --git a/src/caa-cases/containers/cases/cases.component.html b/src/caa-cases/containers/cases/cases.component.html new file mode 100644 index 000000000..f98f109c8 --- /dev/null +++ b/src/caa-cases/containers/cases/cases.component.html @@ -0,0 +1 @@ +

cases works!

diff --git a/src/caa-cases/containers/cases/cases.component.scss b/src/caa-cases/containers/cases/cases.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/caa-cases/containers/cases/cases.component.spec.ts b/src/caa-cases/containers/cases/cases.component.spec.ts new file mode 100644 index 000000000..e71f6289f --- /dev/null +++ b/src/caa-cases/containers/cases/cases.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CasesComponent } from './cases.component'; + +describe('CasesComponent', () => { + let component: CasesComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CasesComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CasesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/caa-cases/containers/cases/cases.component.ts b/src/caa-cases/containers/cases/cases.component.ts new file mode 100644 index 000000000..00c47bc6d --- /dev/null +++ b/src/caa-cases/containers/cases/cases.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-cases', + templateUrl: './cases.component.html', + styleUrls: ['./cases.component.scss'] +}) +export class CasesComponent { + +} diff --git a/src/caa-cases/containers/index.ts b/src/caa-cases/containers/index.ts index cadbf3923..0e9dfffc5 100644 --- a/src/caa-cases/containers/index.ts +++ b/src/caa-cases/containers/index.ts @@ -2,16 +2,19 @@ import { CaaCasesComponent } from './caa-cases/caa-cases.component'; import { CaseShareCompleteComponent } from './case-share-complete/case-share-complete.component'; import { CaseShareConfirmComponent } from './case-share-confirm/case-share-confirm.component'; import { CaseShareComponent } from './case-share/case-share.component'; +import { CasesComponent } from './cases/cases.component'; export const containers: any[] = [ CaaCasesComponent, CaseShareComponent, CaseShareConfirmComponent, - CaseShareCompleteComponent + CaseShareCompleteComponent, + CasesComponent ]; export * from './caa-cases/caa-cases.component'; export * from './case-share-complete/case-share-complete.component'; export * from './case-share-confirm/case-share-confirm.component'; export * from './case-share/case-share.component'; +export * from './cases/cases.component'; diff --git a/src/caa-cases/guards/new-cases-feature-toggle.guard.ts b/src/caa-cases/guards/new-cases-feature-toggle.guard.ts new file mode 100644 index 000000000..c722981c4 --- /dev/null +++ b/src/caa-cases/guards/new-cases-feature-toggle.guard.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@angular/core'; +import { CanActivate } from '@angular/router'; +import { select, Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import * as fromRoot from '../../app/store'; + +@Injectable() +export class NewCaseFeatureToggleGuard implements CanActivate { + constructor(private readonly appStore: Store) {} + + public canActivate(): Observable { + return this.appStore.pipe(select(fromRoot.getCaaNewCasesMenuItemsFeatureIsEnabled)); + } +} From 206906041992b7f0a220f5009b506a87c7b2ad52 Mon Sep 17 00:00:00 2001 From: Shaed Parkar Date: Thu, 14 Mar 2024 17:06:35 +0000 Subject: [PATCH 03/44] WIP cases container component --- angular.json | 10 +++- .../containers/cases/cases.component.html | 51 ++++++++++++++++++- .../containers/cases/cases.component.ts | 39 +++++++++++++- 3 files changed, 95 insertions(+), 5 deletions(-) diff --git a/angular.json b/angular.json index dbd52a88e..30f72a28d 100644 --- a/angular.json +++ b/angular.json @@ -37,7 +37,12 @@ "src/styles.scss" ], "scripts": [], - "sourceMap": true + "sourceMap": true, + "vendorChunk": true, + "extractLicenses": false, + "buildOptimizer": false, + "optimization": false, + "namedChunks": true }, "configurations": { "production": { @@ -74,7 +79,8 @@ "production": { "browserTarget": "rpx-xui-manage-organisations:build:production" } - } + }, + "defaultConfiguration": "" }, "serveTest": { "builder": "@angular-devkit/build-angular:dev-server", diff --git a/src/caa-cases/containers/cases/cases.component.html b/src/caa-cases/containers/cases/cases.component.html index f98f109c8..f4a68679d 100644 --- a/src/caa-cases/containers/cases/cases.component.html +++ b/src/caa-cases/containers/cases/cases.component.html @@ -1 +1,50 @@ -

cases works!

+
+ {{ + (selectedOrganisation$ | async)?.name + }} +

{{ pageTitle }}

+ +
+ +
+ + + + + +
+
+
diff --git a/src/caa-cases/containers/cases/cases.component.ts b/src/caa-cases/containers/cases/cases.component.ts index 00c47bc6d..81dccd793 100644 --- a/src/caa-cases/containers/cases/cases.component.ts +++ b/src/caa-cases/containers/cases/cases.component.ts @@ -1,10 +1,45 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; +import { CaaCasesService } from 'src/caa-cases/services'; + +import * as organisationStore from '../../../organisation/store'; +import * as userStore from '../../../users/store'; +import * as caaCasesStore from '../../store'; +import { Store, select } from '@ngrx/store'; +import { OrganisationDetails } from 'src/models/organisation.model'; +import { Observable } from 'rxjs'; +import { Router } from '@angular/router'; +import { ErrorMessage } from 'src/shared/models/error-message.model'; @Component({ selector: 'app-cases', templateUrl: './cases.component.html', styleUrls: ['./cases.component.scss'] }) -export class CasesComponent { +export class CasesComponent implements OnInit { + public selectedOrganisation$: Observable; + + public pageTitle = 'Cases'; + public showFilterSection = false; + public errorMessages: ErrorMessage[]; + + constructor(private readonly caaCasesStore: Store, + private readonly organisationStore: Store, + private readonly userStore: Store, + private readonly router: Router, + private readonly service: CaaCasesService) { + } + + public ngOnInit(): void { + // Load selected organisation details from store + this.organisationStore.dispatch(new organisationStore.LoadOrganisation()); + this.selectedOrganisation$ = this.organisationStore.pipe(select(organisationStore.getOrganisationSel)); + } + + public toggleFilterSection(): void { + this.showFilterSection = !this.showFilterSection; + } + public isAnyError(): boolean { + return Array.isArray(this.errorMessages) && this.errorMessages.length > 0; + } } From f84125514698747268f722a8e77ba07e974159a9 Mon Sep 17 00:00:00 2001 From: Shaed Parkar Date: Fri, 15 Mar 2024 22:18:29 +0000 Subject: [PATCH 04/44] GA-206 Add cases filter component --- api/caaCases/enums/index.ts | 14 +- .../cases-filter/cases-filter.component.html | 256 ++++++++++++++++++ .../cases-filter/cases-filter.component.scss | 44 +++ .../cases-filter.component.spec.ts | 23 ++ .../cases-filter/cases-filter.component.ts | 173 ++++++++++++ src/caa-cases/components/index.ts | 5 +- .../containers/cases/cases.component.html | 7 +- .../containers/cases/cases.component.ts | 20 +- src/caa-cases/models/caa-cases.enum.ts | 4 +- .../models/selected-case-filter.model.ts | 6 + src/caa-cases/util/caa-cases.util.ts | 12 + 11 files changed, 554 insertions(+), 10 deletions(-) create mode 100644 src/caa-cases/components/cases-filter/cases-filter.component.html create mode 100644 src/caa-cases/components/cases-filter/cases-filter.component.scss create mode 100644 src/caa-cases/components/cases-filter/cases-filter.component.spec.ts create mode 100644 src/caa-cases/components/cases-filter/cases-filter.component.ts create mode 100644 src/caa-cases/models/selected-case-filter.model.ts diff --git a/api/caaCases/enums/index.ts b/api/caaCases/enums/index.ts index c63cedd96..63c22a790 100644 --- a/api/caaCases/enums/index.ts +++ b/api/caaCases/enums/index.ts @@ -1,11 +1,13 @@ export enum CaaCasesPageType { - AssignedCases = 'assigned-cases', - UnassignedCases = 'unassigned-cases', + AssignedCases = 'assigned-cases', + UnassignedCases = 'unassigned-cases', } export enum CaaCasesFilterType { - AllAssignees = 'all-assignees', - AssigneeName = 'assignee-name', - CaseReferenceNumber = 'case-reference-number', - None = 'none', + AllAssignees = 'all-assignees', + AssigneeName = 'assignee-name', + CaseReferenceNumber = 'case-reference-number', + NewCasesToAccept = 'new-cases-to-accept', + UnassignedCases = 'unassigned-cases', + None = 'none', } diff --git a/src/caa-cases/components/cases-filter/cases-filter.component.html b/src/caa-cases/components/cases-filter/cases-filter.component.html new file mode 100644 index 000000000..2aa0d3988 --- /dev/null +++ b/src/caa-cases/components/cases-filter/cases-filter.component.html @@ -0,0 +1,256 @@ +
+
+
+
+
+
+
+
+ +

Filter cases

+
+
+ Filter your organisation's assigned cases by either; +
+ +
+ +
+ + +
+ + +
+ + +
+
+
+ +

+ Error: + {{ assigneeNameErrorMessage }} +

+ + + + {{ + filteredAndGroupedUser.key + }} + + {{ user.fullName + " - " + user.email }} + + No matches + + +
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ +
+
+ +

+ Error:{{ caseReferenceNumberErrorMessage }} +

+ +
+
+
+
+
+ +
+ + +
+
+
+
+
+
diff --git a/src/caa-cases/components/cases-filter/cases-filter.component.scss b/src/caa-cases/components/cases-filter/cases-filter.component.scss new file mode 100644 index 000000000..0c0512166 --- /dev/null +++ b/src/caa-cases/components/cases-filter/cases-filter.component.scss @@ -0,0 +1,44 @@ +@import "govuk-frontend/govuk/base"; + +.govuk-grid-row { + .govuk-grid-column-one-half { + background-color: govuk-colour("light-grey"); + padding-top: 20px; + margin-left: 15px; + } + + #filterContainer { + margin-bottom: 30px; + } + + #assignee-person { + width: 90%; + } + + #case-reference-number { + width: 70%; + } + + .hide-autocomplete { + display: none; + } +} + +.mat-optgroup { + .mat-group-header { + font-size: 16px !important; + font-weight: bold !important; + } + + .mat-option { + &:hover { + background: $govuk-focus-colour; + } + &.select-option { + &:hover { + background: govuk-colour("blue"); + color: govuk-colour("white"); + } + } + } +} diff --git a/src/caa-cases/components/cases-filter/cases-filter.component.spec.ts b/src/caa-cases/components/cases-filter/cases-filter.component.spec.ts new file mode 100644 index 000000000..d1c00fb6f --- /dev/null +++ b/src/caa-cases/components/cases-filter/cases-filter.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CasesFilterComponent } from './cases-filter.component'; + +describe('CasesFilterComponent', () => { + let component: CasesFilterComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CasesFilterComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CasesFilterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/caa-cases/components/cases-filter/cases-filter.component.ts b/src/caa-cases/components/cases-filter/cases-filter.component.ts new file mode 100644 index 000000000..c2b7887af --- /dev/null +++ b/src/caa-cases/components/cases-filter/cases-filter.component.ts @@ -0,0 +1,173 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; +import { User } from '@hmcts/rpx-xui-common-lib'; +import { Observable, catchError, debounceTime, of, switchMap, tap } from 'rxjs'; +import { CaaCasesFilterErrorMessage, CaaCasesFilterType } from 'src/caa-cases/models/caa-cases.enum'; +import { SelectedCaseFilter } from 'src/caa-cases/models/selected-case-filter.model'; +import { CaaCasesUtil } from 'src/caa-cases/util/caa-cases.util'; +import { ErrorMessage } from 'src/shared/models/error-message.model'; + +@Component({ + selector: 'app-cases-filter', + templateUrl: './cases-filter.component.html', + styleUrls: ['./cases-filter.component.scss'] +}) +export class CasesFilterComponent implements OnInit{ + @Input() public selectedOrganisationUsers: User[]; + + @Output() public selectedFilter = new EventEmitter(); + @Output() public emitErrorMessages = new EventEmitter(); + + public readonly ACTIVE_USER_GROUP_HEADING = 'Active users:'; + public readonly INACTIVE_USER_GROUP_HEADING = 'Inactive users:'; + public readonly ACTIVE_USER_STATUS = 'active'; + + public readonly assigneeNameErrorMessage = 'Enter a valid assignee name'; + public readonly caseReferenceNumberErrorMessage = 'Enter a valid HMCTS case reference number'; + + public filteredAndGroupedUsers = new Map(); + + public caaCasesFilterType = CaaCasesFilterType; + public selectedFilterType: CaaCasesFilterType; + + public errorMessages: ErrorMessage[]; + + public form: FormGroup; + public showAutocomplete: boolean = false; + + public constructor(private formBuilder: FormBuilder) {} + + ngOnInit(): void { + this.createForm(); + } + + createForm() { + this.form = this.formBuilder.group({ + filterOption: this.formBuilder.control(CaaCasesFilterType.None), + assigneePerson: this.formBuilder.control(''), + caseReferenceNumber: this.formBuilder.control('') + }); + + this.form.controls.filterOption.valueChanges.subscribe((value: CaaCasesFilterType) => { + this.selectFilterOption(value); + this.handleOnFilterOptionChange(value); + }); + + this.form.controls.assigneePerson.valueChanges.pipe( + tap(() => { + this.showAutocomplete = false; + this.filteredAndGroupedUsers = null; + }, + debounceTime(300)), + switchMap((searchTerm: any) => this.filterSelectedOrganisationUsers(searchTerm).pipe( + tap(() => this.showAutocomplete = true), + catchError(() => this.filteredAndGroupedUsers = null) + )) + ).subscribe((filteredAndGroupedUsers: Map) => { + this.filteredAndGroupedUsers = filteredAndGroupedUsers; + }); + } + + public filterSelectedOrganisationUsers(searchTerm?: string | User): Observable> { + const filteredUsers = searchTerm && searchTerm.length > 0 + ? typeof(searchTerm) === 'string' + ? this.selectedOrganisationUsers.filter((user) => this.getDisplayName(user).toLowerCase().includes(searchTerm.toLowerCase())) + : this.selectedOrganisationUsers.filter((user) => this.getDisplayName(user).toLowerCase().includes(this.getDisplayName(searchTerm).toLowerCase())) + : this.selectedOrganisationUsers; + const activeUsers = filteredUsers.filter((user) => user.status.toLowerCase() === this.ACTIVE_USER_STATUS); + const inactiveUsers = filteredUsers.filter((user) => user.status.toLowerCase() !== this.ACTIVE_USER_STATUS); + const groupedUsers = new Map(); + groupedUsers.set(this.ACTIVE_USER_GROUP_HEADING, activeUsers); + groupedUsers.set(this.INACTIVE_USER_GROUP_HEADING, inactiveUsers); + return of(groupedUsers); + } + + public getDisplayName(selectedUser: User): string { + return `${selectedUser.fullName} - ${selectedUser.email}`; + } + + public onSearch(): void { + if (this.validateForm()) { + let filterValue = null; + if (this.form.controls.filterOption.value === CaaCasesFilterType.CaseReferenceNumber) { + filterValue = this.form.controls.caseReferenceNumber.value; + } + + if (this.form.controls.filterOption.value === CaaCasesFilterType.AssigneeName) { + const selectedUser = this.form.controls.assigneePerson.value; + const fullName = selectedUser.split(' - ')[0]; + const email = selectedUser.split(' - ')[1]; + filterValue = this.selectedOrganisationUsers && this.selectedOrganisationUsers.find( + (user) => user.fullName === fullName && user.email === email).userIdentifier; + } + const selectedFilter: SelectedCaseFilter = { + filterType: this.selectedFilterType, + filterValue: filterValue + }; + this.selectedFilter.emit(selectedFilter); + } + } + + public onReset(): void { + this.form.reset(); + this.selectedFilter.emit({ filterType: this.selectedFilterType, filterValue: '' }); + } + + public onSelectionChange(selectedUser: User) { + this.form.controls.assigneePerson.clearValidators(); + this.form.controls.assigneePerson.updateValueAndValidity(); + this.form.controls.assigneePerson.setValue(this.getDisplayName(selectedUser), { emitEvent: false, onlySelf: true }); + } + + public selectFilterOption(caaCasesFilterType: CaaCasesFilterType): void { + this.selectedFilterType = caaCasesFilterType; + } + + private validateForm(): boolean { + let isValid = true; + this.errorMessages = []; + if (this.form.controls.filterOption.value === CaaCasesFilterType.AssigneeName) { + this.form.controls.assigneePerson.updateValueAndValidity({ emitEvent: false, onlySelf: true }); // ensure validation is run even if the field is empty + if (this.form.controls.assigneePerson.invalid) { + this.errorMessages.push({ title: '', description: CaaCasesFilterErrorMessage.InvalidAssigneeName, fieldId: 'assigneePerson' }); + isValid = false; + } + } + if (this.form.controls.filterOption.value === CaaCasesFilterType.CaseReferenceNumber) { + this.form.controls.caseReferenceNumber.updateValueAndValidity({ emitEvent: false, onlySelf: true }); // ensure validation is run even if the field is empty + if (this.form.controls.caseReferenceNumber.invalid) { + this.errorMessages.push({ title: '', description: CaaCasesFilterErrorMessage.InvalidCaseReference, fieldId: 'caseReferenceNumber' }); + isValid = false; + } + } + this.emitErrorMessages.emit(this.errorMessages); + return isValid; + } + + private handleOnFilterOptionChange(value: CaaCasesFilterType): void { + // clear validators for all forms + this.form.controls.assigneePerson.clearValidators(); + this.form.controls.caseReferenceNumber.clearValidators(); + this.form.controls.assigneePerson.reset(); + this.form.controls.caseReferenceNumber.reset(); + switch (value) { + case CaaCasesFilterType.AssigneeName: + this.form.controls.assigneePerson.setValidators([Validators.required, CaaCasesUtil.assigneeNameValidator2()]); + break; + case CaaCasesFilterType.CaseReferenceNumber: + this.form.controls.caseReferenceNumber.setValidators([Validators.required, CaaCasesUtil.caseReferenceValidator()]); + break; + case CaaCasesFilterType.AllAssignees: + case CaaCasesFilterType.NewCasesToAccept: + case CaaCasesFilterType.UnassignedCases: + break; + } + this.form.updateValueAndValidity(); + } +} + +interface CasesFilterForm { + filterOption: FormControl; + assigneePerson: FormControl; + caseReferenceNumber: FormControl; +} diff --git a/src/caa-cases/components/index.ts b/src/caa-cases/components/index.ts index cb680e597..df597db64 100644 --- a/src/caa-cases/components/index.ts +++ b/src/caa-cases/components/index.ts @@ -1,7 +1,10 @@ import { CaaFilterComponent } from './caa-filter/caa-filter.component'; +import { CasesFilterComponent } from './cases-filter/cases-filter.component'; export const components: any[] = [ - CaaFilterComponent + CaaFilterComponent, + CasesFilterComponent ]; export * from './caa-filter/caa-filter.component'; +export * from './cases-filter/cases-filter.component'; diff --git a/src/caa-cases/containers/cases/cases.component.html b/src/caa-cases/containers/cases/cases.component.html index f4a68679d..5bef7829e 100644 --- a/src/caa-cases/containers/cases/cases.component.html +++ b/src/caa-cases/containers/cases/cases.component.html @@ -13,7 +13,7 @@

{{ pageTitle }}

{{ showFilterSection ? "Hide cases filter" : "Show cases filter" }}
- +
+ ; + public selectedOrganisationUsers$: Observable; public pageTitle = 'Cases'; public showFilterSection = false; - public errorMessages: ErrorMessage[]; + public errorMessages: ErrorMessage[] = []; constructor(private readonly caaCasesStore: Store, private readonly organisationStore: Store, @@ -33,6 +36,21 @@ export class CasesComponent implements OnInit { // Load selected organisation details from store this.organisationStore.dispatch(new organisationStore.LoadOrganisation()); this.selectedOrganisation$ = this.organisationStore.pipe(select(organisationStore.getOrganisationSel)); + + // Load users of selected organisation from store + this.userStore.dispatch(new userStore.LoadAllUsersNoRoleData()); + this.selectedOrganisationUsers$ = this.userStore.pipe(select(userStore.getGetUserList)); + } + + public onSelectedFilter(selectedFilter: SelectedCaseFilter): void { + console.log('Selected filter:', selectedFilter); + // todo: update session state (i.e. remove or store) + + // load cases types based on fileter and value + } + + public onErrorMessages(errorMessages: ErrorMessage[]): void { + this.errorMessages = errorMessages; } public toggleFilterSection(): void { diff --git a/src/caa-cases/models/caa-cases.enum.ts b/src/caa-cases/models/caa-cases.enum.ts index e50f4c00c..bdc718742 100644 --- a/src/caa-cases/models/caa-cases.enum.ts +++ b/src/caa-cases/models/caa-cases.enum.ts @@ -14,7 +14,9 @@ export enum CaaCasesFilterType { AllAssignees = 'all-assignees', AssigneeName = 'assignee-name', CaseReferenceNumber = 'case-reference-number', - None = 'none' + NewCasesToAccept = 'new-cases-to-accept', + UnassignedCases = 'unassigned-cases', + None = 'none', } export enum CaaCasesPageTitle { diff --git a/src/caa-cases/models/selected-case-filter.model.ts b/src/caa-cases/models/selected-case-filter.model.ts new file mode 100644 index 000000000..77a3e7548 --- /dev/null +++ b/src/caa-cases/models/selected-case-filter.model.ts @@ -0,0 +1,6 @@ +import { CaaCasesFilterType } from './caa-cases.enum'; + +export interface SelectedCaseFilter { + filterType: CaaCasesFilterType; + filterValue: string; +} diff --git a/src/caa-cases/util/caa-cases.util.ts b/src/caa-cases/util/caa-cases.util.ts index bc178e193..c8ef2a00e 100644 --- a/src/caa-cases/util/caa-cases.util.ts +++ b/src/caa-cases/util/caa-cases.util.ts @@ -47,4 +47,16 @@ export class CaaCasesUtil { return null; }; } + + public static assigneeNameValidator2(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + if (!control.value) { + return { assigneeName: true }; + } + if (typeof control.value !== 'string' || control.value.includes('@') || control.value.includes('-')) { + return { assigneeName: true }; + } + return null; + }; + } } From 09ba297c11e396d4866875d8d6a7b307d353e4e6 Mon Sep 17 00:00:00 2001 From: Shaed Parkar Date: Fri, 15 Mar 2024 23:21:32 +0000 Subject: [PATCH 05/44] GA-206 track and populate filter selection from session storage --- .../cases-filter/cases-filter.component.html | 2 +- .../cases-filter/cases-filter.component.ts | 47 ++++++++++++++++--- .../containers/cases/cases.component.html | 11 +---- .../containers/cases/cases.component.ts | 37 +++++++++++++++ 4 files changed, 80 insertions(+), 17 deletions(-) diff --git a/src/caa-cases/components/cases-filter/cases-filter.component.html b/src/caa-cases/components/cases-filter/cases-filter.component.html index 2aa0d3988..271c46716 100644 --- a/src/caa-cases/components/cases-filter/cases-filter.component.html +++ b/src/caa-cases/components/cases-filter/cases-filter.component.html @@ -94,7 +94,7 @@

Filter cases

autoActiveFirstOption #auto="matAutocomplete" (optionSelected)=" - onSelectionChange($event.option.value) + onUserSelectionChange($event.option.value) " > (); @Output() public emitErrorMessages = new EventEmitter(); @@ -27,8 +29,8 @@ export class CasesFilterComponent implements OnInit{ public filteredAndGroupedUsers = new Map(); - public caaCasesFilterType = CaaCasesFilterType; - public selectedFilterType: CaaCasesFilterType; + public caaCasesFilterType = CaaCasesFilterType; // used in the template + public selectedFilterType = CaaCasesFilterType.None; public errorMessages: ErrorMessage[]; @@ -41,6 +43,17 @@ export class CasesFilterComponent implements OnInit{ this.createForm(); } + ngOnChanges(changes: SimpleChanges): void { + if (changes.selectedOrganisationUsers && + changes.selectedOrganisationUsers.currentValue && + changes.selectedOrganisationUsers.currentValue.length > 0) { + this.filterSelectedOrganisationUsers().subscribe((filteredAndGroupedUsers) => { + this.filteredAndGroupedUsers = filteredAndGroupedUsers; + }); + this.populateFormFromSessionState(); + } + } + createForm() { this.form = this.formBuilder.group({ filterOption: this.formBuilder.control(CaaCasesFilterType.None), @@ -68,6 +81,28 @@ export class CasesFilterComponent implements OnInit{ }); } + public populateFormFromSessionState(): void { + if (this.sessionStateValue) { + const filterOptionValue = this.sessionStateValue.filterType as CaaCasesFilterType; + this.form.controls.filterOption.setValue(filterOptionValue, { emitEvent: false, onlySelf: true }); + + this.selectFilterOption(filterOptionValue); + if (filterOptionValue === CaaCasesFilterType.AssigneeName) { + const assigneePersonValue = this.sessionStateValue.assigneeName; + const user = this.selectedOrganisationUsers.find((user) => user.userIdentifier === assigneePersonValue); + + this.form.controls.assigneePerson.setValue(this.getDisplayName(user), { emitEvent: false, onlySelf: true }); + } + + if (filterOptionValue === CaaCasesFilterType.CaseReferenceNumber) { + const caseReferenceNumberValue = this.sessionStateValue.caseReferenceNumber; + this.form.controls.caseReferenceNumber.setValue(caseReferenceNumberValue, { emitEvent: false, onlySelf: true }); + } + + this.form.markAsDirty(); + } + } + public filterSelectedOrganisationUsers(searchTerm?: string | User): Observable> { const filteredUsers = searchTerm && searchTerm.length > 0 ? typeof(searchTerm) === 'string' @@ -109,11 +144,11 @@ export class CasesFilterComponent implements OnInit{ } public onReset(): void { - this.form.reset(); + this.form.reset({ filterOption: CaaCasesFilterType.None, assigneePerson: '', caseReferenceNumber: '' }); this.selectedFilter.emit({ filterType: this.selectedFilterType, filterValue: '' }); } - public onSelectionChange(selectedUser: User) { + public onUserSelectionChange(selectedUser: User) { this.form.controls.assigneePerson.clearValidators(); this.form.controls.assigneePerson.updateValueAndValidity(); this.form.controls.assigneePerson.setValue(this.getDisplayName(selectedUser), { emitEvent: false, onlySelf: true }); diff --git a/src/caa-cases/containers/cases/cases.component.html b/src/caa-cases/containers/cases/cases.component.html index 5bef7829e..4c7a4b6fe 100644 --- a/src/caa-cases/containers/cases/cases.component.html +++ b/src/caa-cases/containers/cases/cases.component.html @@ -37,19 +37,10 @@

- - diff --git a/src/caa-cases/containers/cases/cases.component.ts b/src/caa-cases/containers/cases/cases.component.ts index 6c9e9978e..a3fd55704 100644 --- a/src/caa-cases/containers/cases/cases.component.ts +++ b/src/caa-cases/containers/cases/cases.component.ts @@ -11,6 +11,8 @@ import { Router } from '@angular/router'; import { ErrorMessage } from 'src/shared/models/error-message.model'; import { User } from '@hmcts/rpx-xui-common-lib'; import { SelectedCaseFilter } from 'src/caa-cases/models/selected-case-filter.model'; +import { CaaCasesSessionState, CaaCasesSessionStateValue } from 'src/caa-cases/models/caa-cases.model'; +import { CaaCasesFilterType } from 'src/caa-cases/models/caa-cases.enum'; @Component({ selector: 'app-cases', @@ -18,12 +20,15 @@ import { SelectedCaseFilter } from 'src/caa-cases/models/selected-case-filter.mo styleUrls: ['./cases.component.scss'] }) export class CasesComponent implements OnInit { + private readonly caaCasesPageType = 'all-cases-filter'; + public selectedOrganisation$: Observable; public selectedOrganisationUsers$: Observable; public pageTitle = 'Cases'; public showFilterSection = false; public errorMessages: ErrorMessage[] = []; + public sessionStateValue: CaaCasesSessionStateValue; constructor(private readonly caaCasesStore: Store, private readonly organisationStore: Store, @@ -40,11 +45,19 @@ export class CasesComponent implements OnInit { // Load users of selected organisation from store this.userStore.dispatch(new userStore.LoadAllUsersNoRoleData()); this.selectedOrganisationUsers$ = this.userStore.pipe(select(userStore.getGetUserList)); + + // Retrieve session state to check and pre-populate the previous state if any + this.retrieveSessionState(); } public onSelectedFilter(selectedFilter: SelectedCaseFilter): void { console.log('Selected filter:', selectedFilter); // todo: update session state (i.e. remove or store) + if (selectedFilter.filterType === CaaCasesFilterType.None) { + this.removeSessionState(this.caaCasesPageType); + } else { + this.storeSessionState(selectedFilter); + } // load cases types based on fileter and value } @@ -60,4 +73,28 @@ export class CasesComponent implements OnInit { public isAnyError(): boolean { return Array.isArray(this.errorMessages) && this.errorMessages.length > 0; } + + public removeSessionState(key: string): void { + this.service.removeSessionState(key); + } + + public retrieveSessionState(): void { + this.sessionStateValue = this.service.retrieveSessionState(this.caaCasesPageType); + if (this.sessionStateValue) { + this.toggleFilterSection(); + } + } + + public storeSessionState(selectedFilter: SelectedCaseFilter): void { + const sessionStateToUpdate: CaaCasesSessionState = { + key: this.caaCasesPageType, + value: { + filterType: selectedFilter.filterType, + caseReferenceNumber: selectedFilter.filterType === CaaCasesFilterType.CaseReferenceNumber ? selectedFilter.filterValue : null, + assigneeName: selectedFilter.filterType === CaaCasesFilterType.AssigneeName ? selectedFilter.filterValue : null + } + }; + this.sessionStateValue = sessionStateToUpdate.value; + this.service.storeSessionState(sessionStateToUpdate); + } } From 9e1ff0834a14437ddfe01e18c9be3df6fdcd446b Mon Sep 17 00:00:00 2001 From: Shaed Parkar Date: Mon, 18 Mar 2024 23:27:38 +0000 Subject: [PATCH 06/44] WIP: create a results table component to display case data --- .../cases-results-table.component.html | 76 +++++++++ .../cases-results-table.component.scss | 0 .../cases-results-table.component.spec.ts | 23 +++ .../cases-results-table.component.ts | 149 ++++++++++++++++++ src/caa-cases/components/index.ts | 5 +- .../caa-cases/caa-cases.component.ts | 2 +- .../containers/cases/cases.component.html | 10 ++ .../containers/cases/cases.component.ts | 103 +++++++++++- 8 files changed, 362 insertions(+), 6 deletions(-) create mode 100644 src/caa-cases/components/cases-results-table/cases-results-table.component.html create mode 100644 src/caa-cases/components/cases-results-table/cases-results-table.component.scss create mode 100644 src/caa-cases/components/cases-results-table/cases-results-table.component.spec.ts create mode 100644 src/caa-cases/components/cases-results-table/cases-results-table.component.ts diff --git a/src/caa-cases/components/cases-results-table/cases-results-table.component.html b/src/caa-cases/components/cases-results-table/cases-results-table.component.html new file mode 100644 index 000000000..c4efd9392 --- /dev/null +++ b/src/caa-cases/components/cases-results-table/cases-results-table.component.html @@ -0,0 +1,76 @@ + + +
+ This view has not been configured for the case type. +
+
+
+
+

+ Showing {{ getFirstResult() }} to + {{ getLastResult() }} of + {{ getTotalResults() }} {{ currentCaseType }} cases +

+
+

+ Select any {{ currentCaseType }} cases you want to + manage case sharing for. +

+ + + + + + +
+ + +
+
+
diff --git a/src/caa-cases/components/cases-results-table/cases-results-table.component.scss b/src/caa-cases/components/cases-results-table/cases-results-table.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/caa-cases/components/cases-results-table/cases-results-table.component.spec.ts b/src/caa-cases/components/cases-results-table/cases-results-table.component.spec.ts new file mode 100644 index 000000000..f4447ed40 --- /dev/null +++ b/src/caa-cases/components/cases-results-table/cases-results-table.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CasesResultsTableComponent } from './cases-results-table.component'; + +describe('CasesResultsTableComponent', () => { + let component: CasesResultsTableComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CasesResultsTableComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CasesResultsTableComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/caa-cases/components/cases-results-table/cases-results-table.component.ts b/src/caa-cases/components/cases-results-table/cases-results-table.component.ts new file mode 100644 index 000000000..2c3dff734 --- /dev/null +++ b/src/caa-cases/components/cases-results-table/cases-results-table.component.ts @@ -0,0 +1,149 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core'; +import { MatTabGroup } from '@angular/material/tabs'; +import { TableConfig } from '@hmcts/ccd-case-ui-toolkit'; +import { SharedCase, SubNavigation } from '@hmcts/rpx-xui-common-lib'; +import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; + +import * as fromStore from '../../store'; +import { CaaCases } from 'api/caaCases/interfaces'; + +@Component({ + selector: 'app-cases-results-table', + templateUrl: './cases-results-table.component.html', + styleUrls: ['./cases-results-table.component.scss'] +}) +export class CasesResultsTableComponent { + private _allCaseTypes: SubNavigation[]; + private _cases: any; // can we type this? + + @Input() set allCaseTypes(value: SubNavigation[]) { + this._allCaseTypes = value; + this.fixCurrentTab(this._allCaseTypes); + } + + @Input() set casesConfig(value: CaaCases) { + if (value){ + this.tableConfig = value; + this.setTableConfig(this.tableConfig); + } + } + + get cases(): SubNavigation[] { + return this._cases; + } + + @Input() set cases(value: any) { + if (value){ + this._cases = value; + } + } + + @Input() shareAssignedCases$: Observable; + @Input() shareUnassignedCases$: Observable; + @Input() paginationPageSize: number = 25; + + @Output() public caseSelected = new EventEmitter(); + @Output() public pageChanged = new EventEmitter(); + + // Needed for the tab group + public navItems: any[]; + public currentPageNo: number; + public totalCases: number = 0; + public tableConfig: TableConfig; + public currentCaseType: string; + + public casesError$: Observable; + public noCasesFoundMessage = ''; + + public selectedCases: any[] = []; + public selectedAssignedCases: any[] = []; + public selectedUnassignedCases: any[] = []; + + @ViewChild('tabGroup') public tabGroup: MatTabGroup; + + /** + * + */ + constructor(private readonly store: Store,) { + + } + + private fixCurrentTab(items: any): void { + this.navItems = items; + if (items && items.length > 0) { + this.totalCases = items[0].total ? items[0].total : 0; + this.setTabItems(items[0].text); + } else { + this.totalCases = 0; + // this.noCasesFoundMessage = this.getNoCasesFoundMessage(); + } + } + + private setTabItems(tabName: string, fromTabChangedEvent?: boolean): void { + this.resetPaginationParameters(); + // if (this.caaCasesPageType === CaaCasesPageType.UnassignedCases) { + // this.store.pipe(select(fromStore.getAllUnassignedCases)); + // } else { + // this.store.pipe(select(fromStore.getAllAssignedCases)); + // } + // this.shareAssignedCases$ = this.store.pipe(select(fromStore.getShareAssignedCaseListState)); + // this.shareUnassignedCases$ = this.store.pipe(select(fromStore.getShareUnassignedCaseListState)); + this.currentCaseType = tabName; + if (!fromTabChangedEvent && this.tabGroup) { + this.tabGroup.selectedIndex = 0; + } + // this.loadDataFromStore(); + } + + public resetPaginationParameters(): void { + this.currentPageNo = 1; + } + + public hasResults(): any { + return this.totalCases; + } + + public getFirstResult(): number { + return ((this.currentPageNo - 1) * this.paginationPageSize) + 1; + } + + public getLastResult(): number { + const count = ((this.currentPageNo) * this.paginationPageSize); + return count >= this.totalCases ? this.totalCases : count < this.totalCases ? count : 1; + } + + public getTotalResults(): number { + return this.totalCases; + } + + public setTableConfig(config: TableConfig): void { + if (config !== null) { + this.tableConfig = { + idField: config.idField, + columnConfigs: config.columnConfigs + }; + } + } + + public onCaseSelection(selectedCases: any[]): void { + this.caseSelected.emit(selectedCases); + } + + public onPaginationHandler(pageNo: number): void { + this.currentPageNo = pageNo; + this.pageChanged.emit(pageNo); + } + + public shareAssignedCaseSubmit(): void { + // TODO: emit this action + } + + public shareUnassignedCaseSubmit(): void { + // this.store.dispatch(new fromStore.AddShareUnassignedCases({ + // sharedCases: converters.toShareCaseConverter(this.selectedUnassignedCases, this.currentCaseType) + // })); + // TODO: emit this action + } +} diff --git a/src/caa-cases/components/index.ts b/src/caa-cases/components/index.ts index df597db64..0931b4ec4 100644 --- a/src/caa-cases/components/index.ts +++ b/src/caa-cases/components/index.ts @@ -1,10 +1,13 @@ import { CaaFilterComponent } from './caa-filter/caa-filter.component'; import { CasesFilterComponent } from './cases-filter/cases-filter.component'; +import { CasesResultsTableComponent } from './cases-results-table/cases-results-table.component'; export const components: any[] = [ CaaFilterComponent, - CasesFilterComponent + CasesFilterComponent, + CasesResultsTableComponent ]; export * from './caa-filter/caa-filter.component'; export * from './cases-filter/cases-filter.component'; +export * from './cases-results-table/cases-results-table.component'; diff --git a/src/caa-cases/containers/caa-cases/caa-cases.component.ts b/src/caa-cases/containers/caa-cases/caa-cases.component.ts index f0e65c2b2..7465a0d10 100644 --- a/src/caa-cases/containers/caa-cases/caa-cases.component.ts +++ b/src/caa-cases/containers/caa-cases/caa-cases.component.ts @@ -45,7 +45,7 @@ export class CaaCasesComponent implements OnInit { public navItems: any[]; public currentPageNo: number; - public paginationPageSize: number = 25; + public paginationPageSize: number = 1; public totalCases: number = 0; public pageTitle: string; public caaCasesPageType: string; diff --git a/src/caa-cases/containers/cases/cases.component.html b/src/caa-cases/containers/cases/cases.component.html index 4c7a4b6fe..2b7235615 100644 --- a/src/caa-cases/containers/cases/cases.component.html +++ b/src/caa-cases/containers/cases/cases.component.html @@ -42,5 +42,15 @@

(emitErrorMessages)="onErrorMessages($event)" > + diff --git a/src/caa-cases/containers/cases/cases.component.ts b/src/caa-cases/containers/cases/cases.component.ts index a3fd55704..1e52be747 100644 --- a/src/caa-cases/containers/cases/cases.component.ts +++ b/src/caa-cases/containers/cases/cases.component.ts @@ -9,10 +9,12 @@ import { OrganisationDetails } from 'src/models/organisation.model'; import { Observable } from 'rxjs'; import { Router } from '@angular/router'; import { ErrorMessage } from 'src/shared/models/error-message.model'; -import { User } from '@hmcts/rpx-xui-common-lib'; +import { SharedCase, SubNavigation, User } from '@hmcts/rpx-xui-common-lib'; import { SelectedCaseFilter } from 'src/caa-cases/models/selected-case-filter.model'; -import { CaaCasesSessionState, CaaCasesSessionStateValue } from 'src/caa-cases/models/caa-cases.model'; -import { CaaCasesFilterType } from 'src/caa-cases/models/caa-cases.enum'; +import { CaaCases, CaaCasesSessionState, CaaCasesSessionStateValue } from 'src/caa-cases/models/caa-cases.model'; +import { CaaCasesFilterType, CaaCasesPageType } from 'src/caa-cases/models/caa-cases.enum'; +import { HttpErrorResponse } from '@angular/common/http'; +import * as converters from '../../converters/case-converter'; @Component({ selector: 'app-cases', @@ -30,6 +32,16 @@ export class CasesComponent implements OnInit { public errorMessages: ErrorMessage[] = []; public sessionStateValue: CaaCasesSessionStateValue; + // for the results table + public allCaseTypes: SubNavigation[]; + public currentPageNo: number; + public paginationPageSize: number = 25; + public casesConfig: CaaCases; + public cases: any; // can we type this? + public casesError$: Observable; + public shareAssignedCases$: Observable; + public shareUnassignedCases$: Observable; + constructor(private readonly caaCasesStore: Store, private readonly organisationStore: Store, private readonly userStore: Store, @@ -38,6 +50,9 @@ export class CasesComponent implements OnInit { } public ngOnInit(): void { + // this.loadCaseTypes(selectedFilterType, selectedFilterValue); + this.loadCaseTypes(); + // Load selected organisation details from store this.organisationStore.dispatch(new organisationStore.LoadOrganisation()); this.selectedOrganisation$ = this.organisationStore.pipe(select(organisationStore.getOrganisationSel)); @@ -48,6 +63,61 @@ export class CasesComponent implements OnInit { // Retrieve session state to check and pre-populate the previous state if any this.retrieveSessionState(); + + // TODO: clean this up to get all cases + this.caaCasesStore.pipe(select(caaCasesStore.getAllUnassignedCases)).subscribe((config: CaaCases) => { + if (config){ + this.casesConfig = config; + } + }); + this.caaCasesStore.pipe(select(caaCasesStore.getAllUnassignedCaseData)).subscribe((items) => { + if (items){ + this.cases = items; + } + }); + this.casesError$ = this.caaCasesStore.pipe(select(caaCasesStore.getAllUnassignedCasesError)); + + this.shareAssignedCases$ = this.caaCasesStore.pipe(select(caaCasesStore.getShareAssignedCaseListState)); + this.shareUnassignedCases$ = this.caaCasesStore.pipe(select(caaCasesStore.getShareUnassignedCaseListState)); + } + + /** + * This will load all case types based on the selected filter type and value + */ + public loadCaseTypes() { + // TODO: get selected filter type and selected filter value from somewhere + const selectedFilterType = CaaCasesPageType.UnassignedCases; // todo: this will be all cases since it's a merge of both assigned and unassigned cases + const pageType = CaaCasesPageType.UnassignedCases; // todo: this will be all cases since it's a merge of both assigned and unassigned cases + const selectedFilterValue = null; + // Load case types based on current page type according to filtered value + this.caaCasesStore.dispatch(new caaCasesStore.LoadCaseTypes({ + caaCasesPageType: pageType, + caaCasesFilterType: selectedFilterType, + caaCasesFilterValue: selectedFilterValue }) + ); + this.caaCasesStore.pipe(select(caaCasesStore.getAllCaseTypes)).subscribe((items) => { + this.allCaseTypes = items; + this.loadCases(); + }); + } + + /** + * This will load cases (i.e. unassigned or assigned) from the API with the selected filter type and value + */ + public loadCases(){ + if (this.allCaseTypes && this.allCaseTypes.length > 0) { + const selectedFilterType = CaaCasesFilterType.None; // todo: this will be all cases since it's a merge of both assigned and unassigned cases + const selectedFilterValue = null; + const currentCaseType = this.allCaseTypes[0].text; + + this.caaCasesStore.dispatch(new caaCasesStore.LoadUnassignedCases({ + caseType: currentCaseType, + pageNo: this.currentPageNo, + pageSize: this.paginationPageSize, + caaCasesFilterType: selectedFilterType, + caaCasesFilterValue: selectedFilterValue + })); + } } public onSelectedFilter(selectedFilter: SelectedCaseFilter): void { @@ -59,7 +129,7 @@ export class CasesComponent implements OnInit { this.storeSessionState(selectedFilter); } - // load cases types based on fileter and value + // load cases types based on filter and value } public onErrorMessages(errorMessages: ErrorMessage[]): void { @@ -97,4 +167,29 @@ export class CasesComponent implements OnInit { this.sessionStateValue = sessionStateToUpdate.value; this.service.storeSessionState(sessionStateToUpdate); } + + public onCaseSelected(selectedCases: any[]): void { + // if (this.caaCasesPageType === CaaCasesPageType.UnassignedCases) { + // this.selectedUnassignedCases = selectedCases; + // this.store.dispatch(new fromStore.SynchronizeStateToStoreUnassignedCases( + // converters.toShareCaseConverter(selectedCases, this.currentCaseType) + // )); + // } else { + // this.selectedAssignedCases = selectedCases; + // this.store.dispatch(new fromStore.SynchronizeStateToStoreAssignedCases( + // converters.toShareCaseConverter(selectedCases, this.currentCaseType) + // )); + // } + + // do i need the line below? + // this.selectedUnassignedCases = selectedCases; + this.caaCasesStore.dispatch(new caaCasesStore.SynchronizeStateToStoreUnassignedCases( + converters.toShareCaseConverter(selectedCases, CaaCasesPageType.UnassignedCases) + )); + } + + public onPageChanged(pageNo: number): void { + this.currentPageNo = pageNo; + this.loadCases(); + } } From 3802e24c85e93c2207f0218ddc1000e715c93636 Mon Sep 17 00:00:00 2001 From: Shaed Parkar Date: Wed, 20 Mar 2024 22:14:05 +0000 Subject: [PATCH 07/44] WIP Add new route for case sharing and update filter type enum --- src/caa-cases/caa-cases.routing.ts | 9 + .../caa-filter/caa-filter.component.html | 14 +- .../caa-filter/caa-filter.component.spec.ts | 8 +- .../caa-filter/caa-filter.component.ts | 4 +- .../cases-filter/cases-filter.component.html | 25 ++- .../cases-filter/cases-filter.component.ts | 16 +- .../cases-results-table.component.html | 21 +- .../cases-results-table.component.ts | 72 +++---- .../caa-cases/caa-cases.component.spec.ts | 14 +- .../caa-cases/caa-cases.component.ts | 8 +- .../containers/cases/cases.component.html | 5 +- .../containers/cases/cases.component.ts | 184 +++++++++++++----- src/caa-cases/models/caa-cases.enum.ts | 8 +- .../services/caa-cases.service.spec.ts | 2 +- 14 files changed, 244 insertions(+), 146 deletions(-) diff --git a/src/caa-cases/caa-cases.routing.ts b/src/caa-cases/caa-cases.routing.ts index a1fc6474e..e7f7b8862 100644 --- a/src/caa-cases/caa-cases.routing.ts +++ b/src/caa-cases/caa-cases.routing.ts @@ -35,6 +35,15 @@ export const ROUTES: Routes = [ RoleGuard ] }, + { + path: 'all/case-share', + component: CaseShareComponent, + canActivate: [ + AuthGuard, + FeatureToggleAccountGuard, + RoleGuard + ] + }, { path: 'case-share-confirm/:pageType', component: CaseShareConfirmComponent, diff --git a/src/caa-cases/components/caa-filter/caa-filter.component.html b/src/caa-cases/components/caa-filter/caa-filter.component.html index 416b25f15..a5abdd5dd 100644 --- a/src/caa-cases/components/caa-filter/caa-filter.component.html +++ b/src/caa-cases/components/caa-filter/caa-filter.component.html @@ -18,26 +18,26 @@

+ (click)="selectFilterOption(caaCasesFilterType.AllAssignedCases)">
+ (click)="selectFilterOption(caaCasesFilterType.CasesAssignedToAUser)">
-

class="govuk-radios__conditional" [ngClass]="{ 'govuk-radios__conditional--hidden': - selectedFilterType !== caaCasesFilterType.AssigneeName + selectedFilterType !== caaCasesFilterType.CasesAssignedToAUser }" id="conditional-assignee-person" > @@ -70,7 +70,7 @@

Filter cases

Type the name and select an available match option

@@ -154,7 +154,7 @@

Filter cases

name="filterOption" type="radio" formControlName="filterOption" - [value]="caaCasesFilterType.AllAssignees" + [value]="caaCasesFilterType.AllAssignedCases" />

Reset + +
+ +
+ + + Warning + The tabs below list all of your organisation's cases which are not assigned to any users. You can assign + cases to users by selecting 'Manage cases'. + + You do not need to assigned cases to users if they have 'Access all cases in the organisation' enabled for + that case type. + +
+
+
diff --git a/src/caa-cases/components/cases-filter/cases-filter.component.ts b/src/caa-cases/components/cases-filter/cases-filter.component.ts index 7c2cff65a..5e5fa34a5 100644 --- a/src/caa-cases/components/cases-filter/cases-filter.component.ts +++ b/src/caa-cases/components/cases-filter/cases-filter.component.ts @@ -36,6 +36,7 @@ export class CasesFilterComponent implements OnInit, OnChanges{ public form: FormGroup; public showAutocomplete: boolean = false; + public filterApplied: boolean = false; public constructor(private formBuilder: FormBuilder) {} @@ -87,7 +88,7 @@ export class CasesFilterComponent implements OnInit, OnChanges{ this.form.controls.filterOption.setValue(filterOptionValue, { emitEvent: false, onlySelf: true }); this.selectFilterOption(filterOptionValue); - if (filterOptionValue === CaaCasesFilterType.AssigneeName) { + if (filterOptionValue === CaaCasesFilterType.CasesAssignedToAUser) { const assigneePersonValue = this.sessionStateValue.assigneeName; const user = this.selectedOrganisationUsers.find((user) => user.userIdentifier === assigneePersonValue); @@ -98,8 +99,9 @@ export class CasesFilterComponent implements OnInit, OnChanges{ const caseReferenceNumberValue = this.sessionStateValue.caseReferenceNumber; this.form.controls.caseReferenceNumber.setValue(caseReferenceNumberValue, { emitEvent: false, onlySelf: true }); } - + this.filterApplied = true; this.form.markAsDirty(); + this.onSearch(); } } @@ -128,7 +130,7 @@ export class CasesFilterComponent implements OnInit, OnChanges{ filterValue = this.form.controls.caseReferenceNumber.value; } - if (this.form.controls.filterOption.value === CaaCasesFilterType.AssigneeName) { + if (this.form.controls.filterOption.value === CaaCasesFilterType.CasesAssignedToAUser) { const selectedUser = this.form.controls.assigneePerson.value; const fullName = selectedUser.split(' - ')[0]; const email = selectedUser.split(' - ')[1]; @@ -139,6 +141,7 @@ export class CasesFilterComponent implements OnInit, OnChanges{ filterType: this.selectedFilterType, filterValue: filterValue }; + this.filterApplied = true; this.selectedFilter.emit(selectedFilter); } } @@ -146,6 +149,7 @@ export class CasesFilterComponent implements OnInit, OnChanges{ public onReset(): void { this.form.reset({ filterOption: CaaCasesFilterType.None, assigneePerson: '', caseReferenceNumber: '' }); this.selectedFilter.emit({ filterType: this.selectedFilterType, filterValue: '' }); + this.filterApplied = false; } public onUserSelectionChange(selectedUser: User) { @@ -161,7 +165,7 @@ export class CasesFilterComponent implements OnInit, OnChanges{ private validateForm(): boolean { let isValid = true; this.errorMessages = []; - if (this.form.controls.filterOption.value === CaaCasesFilterType.AssigneeName) { + if (this.form.controls.filterOption.value === CaaCasesFilterType.CasesAssignedToAUser) { this.form.controls.assigneePerson.updateValueAndValidity({ emitEvent: false, onlySelf: true }); // ensure validation is run even if the field is empty if (this.form.controls.assigneePerson.invalid) { this.errorMessages.push({ title: '', description: CaaCasesFilterErrorMessage.InvalidAssigneeName, fieldId: 'assigneePerson' }); @@ -186,13 +190,13 @@ export class CasesFilterComponent implements OnInit, OnChanges{ this.form.controls.assigneePerson.reset(); this.form.controls.caseReferenceNumber.reset(); switch (value) { - case CaaCasesFilterType.AssigneeName: + case CaaCasesFilterType.CasesAssignedToAUser: this.form.controls.assigneePerson.setValidators([Validators.required, CaaCasesUtil.assigneeNameValidator2()]); break; case CaaCasesFilterType.CaseReferenceNumber: this.form.controls.caseReferenceNumber.setValidators([Validators.required, CaaCasesUtil.caseReferenceValidator()]); break; - case CaaCasesFilterType.AllAssignees: + case CaaCasesFilterType.AllAssignedCases: case CaaCasesFilterType.NewCasesToAccept: case CaaCasesFilterType.UnassignedCases: break; diff --git a/src/caa-cases/components/cases-results-table/cases-results-table.component.html b/src/caa-cases/components/cases-results-table/cases-results-table.component.html index c4efd9392..8342c913d 100644 --- a/src/caa-cases/components/cases-results-table/cases-results-table.component.html +++ b/src/caa-cases/components/cases-results-table/cases-results-table.component.html @@ -30,32 +30,17 @@ manage case sharing for.

- - - - ; - @Input() shareUnassignedCases$: Observable; @Input() paginationPageSize: number = 25; + @Input() shareButtonText = 'Share case'; @Output() public caseSelected = new EventEmitter(); @Output() public pageChanged = new EventEmitter(); + @Output() public shareButtonClicked = new EventEmitter(); // Needed for the tab group public navItems: any[]; @@ -56,6 +56,7 @@ export class CasesResultsTableComponent { public casesError$: Observable; public noCasesFoundMessage = ''; + public enableShareButton = false; public selectedCases: any[] = []; public selectedAssignedCases: any[] = []; @@ -70,31 +71,11 @@ export class CasesResultsTableComponent { } - private fixCurrentTab(items: any): void { - this.navItems = items; - if (items && items.length > 0) { - this.totalCases = items[0].total ? items[0].total : 0; - this.setTabItems(items[0].text); - } else { - this.totalCases = 0; - // this.noCasesFoundMessage = this.getNoCasesFoundMessage(); - } - } - - private setTabItems(tabName: string, fromTabChangedEvent?: boolean): void { - this.resetPaginationParameters(); - // if (this.caaCasesPageType === CaaCasesPageType.UnassignedCases) { - // this.store.pipe(select(fromStore.getAllUnassignedCases)); - // } else { - // this.store.pipe(select(fromStore.getAllAssignedCases)); - // } - // this.shareAssignedCases$ = this.store.pipe(select(fromStore.getShareAssignedCaseListState)); - // this.shareUnassignedCases$ = this.store.pipe(select(fromStore.getShareUnassignedCaseListState)); - this.currentCaseType = tabName; - if (!fromTabChangedEvent && this.tabGroup) { - this.tabGroup.selectedIndex = 0; - } - // this.loadDataFromStore(); + public tabChanged(event: { tab: { textLabel: string }}): void { + this.totalCases = this.navItems.find((data) => data.text === event.tab.textLabel) + ? this.navItems.find((data) => data.text === event.tab.textLabel).total + : 0; + this.setTabItems(event.tab.textLabel, true); } public resetPaginationParameters(): void { @@ -129,6 +110,7 @@ export class CasesResultsTableComponent { public onCaseSelection(selectedCases: any[]): void { this.caseSelected.emit(selectedCases); + this.enableShareButton = selectedCases.length > 0; } public onPaginationHandler(pageNo: number): void { @@ -136,14 +118,38 @@ export class CasesResultsTableComponent { this.pageChanged.emit(pageNo); } - public shareAssignedCaseSubmit(): void { - // TODO: emit this action - } - - public shareUnassignedCaseSubmit(): void { + public onShareButtonClicked(): void { // this.store.dispatch(new fromStore.AddShareUnassignedCases({ // sharedCases: converters.toShareCaseConverter(this.selectedUnassignedCases, this.currentCaseType) // })); // TODO: emit this action + this.shareButtonClicked.emit(); + } + + private fixCurrentTab(items: any): void { + this.navItems = items; + if (items && items.length > 0) { + this.totalCases = items[0].total ? items[0].total : 0; + this.setTabItems(items[0].text); + } else { + this.totalCases = 0; + // this.noCasesFoundMessage = this.getNoCasesFoundMessage(); + } + } + + private setTabItems(tabName: string, fromTabChangedEvent?: boolean): void { + this.resetPaginationParameters(); + // if (this.caaCasesPageType === CaaCasesPageType.UnassignedCases) { + // this.store.pipe(select(fromStore.getAllUnassignedCases)); + // } else { + // this.store.pipe(select(fromStore.getAllAssignedCases)); + // } + // this.shareAssignedCases$ = this.store.pipe(select(fromStore.getShareAssignedCaseListState)); + // this.shareUnassignedCases$ = this.store.pipe(select(fromStore.getShareUnassignedCaseListState)); + this.currentCaseType = tabName; + if (!fromTabChangedEvent && this.tabGroup) { + this.tabGroup.selectedIndex = 0; + } + // this.loadDataFromStore(); } } diff --git a/src/caa-cases/containers/caa-cases/caa-cases.component.spec.ts b/src/caa-cases/containers/caa-cases/caa-cases.component.spec.ts index 075c950df..ab64ae592 100644 --- a/src/caa-cases/containers/caa-cases/caa-cases.component.spec.ts +++ b/src/caa-cases/containers/caa-cases/caa-cases.component.spec.ts @@ -30,7 +30,7 @@ describe('CaaCasesComponent', () => { let router: Router; let caaCasesService: jasmine.SpyObj; const sessionStateValue: CaaCasesSessionStateValue = { - filterType: CaaCasesFilterType.AssigneeName, + filterType: CaaCasesFilterType.CasesAssignedToAUser, caseReferenceNumber: null, assigneeName: 'assignee123' }; @@ -114,7 +114,7 @@ describe('CaaCasesComponent', () => { expect(component.selectedFilterType).toEqual(CaaCasesFilterType.None); component.caaCasesPageType = CaaCasesPageType.AssignedCases; component.setSelectedFilterTypeAndValue(); - expect(component.selectedFilterType).toEqual(CaaCasesFilterType.AllAssignees); + expect(component.selectedFilterType).toEqual(CaaCasesFilterType.AllAssignedCases); expect(component.selectedFilterValue).toBeNull(); }); @@ -196,12 +196,12 @@ describe('CaaCasesComponent', () => { component.selectedFilterType = CaaCasesFilterType.CaseReferenceNumber; expect(component.getNoCasesFoundMessage()).toEqual(CaaCasesNoDataMessage.UnassignedCasesFilterMessage); component.caaCasesPageType = CaaCasesPageType.AssignedCases; - component.selectedFilterType = CaaCasesFilterType.AllAssignees; + component.selectedFilterType = CaaCasesFilterType.AllAssignedCases; expect(component.getNoCasesFoundMessage()).toEqual(CaaCasesNoDataMessage.NoAssignedCases); component.caaCasesPageType = CaaCasesPageType.AssignedCases; component.selectedFilterType = CaaCasesFilterType.CaseReferenceNumber; expect(component.getNoCasesFoundMessage()).toEqual(CaaCasesNoDataMessage.AssignedCasesFilterMessage); - component.selectedFilterType = CaaCasesFilterType.AssigneeName; + component.selectedFilterType = CaaCasesFilterType.CasesAssignedToAUser; expect(component.getNoCasesFoundMessage()).toEqual(CaaCasesNoDataMessage.AssignedCasesFilterMessage); component.totalCases = 1; expect(component.getNoCasesFoundMessage()).toEqual(''); @@ -230,8 +230,8 @@ describe('CaaCasesComponent', () => { }); it('should remove session state', () => { - component.removeSessionState(CaaCasesFilterType.AssigneeName); - expect(caaCasesService.removeSessionState).toHaveBeenCalledWith(CaaCasesFilterType.AssigneeName); + component.removeSessionState(CaaCasesFilterType.CasesAssignedToAUser); + expect(caaCasesService.removeSessionState).toHaveBeenCalledWith(CaaCasesFilterType.CasesAssignedToAUser); }); it('should retrieve session state', () => { @@ -240,7 +240,7 @@ describe('CaaCasesComponent', () => { component.caaCasesPageType = CaaCasesPageType.AssignedCases; component.retrieveSessionState(); expect(component.sessionStateValue).toEqual(sessionStateValue); - expect(component.selectedFilterType).toEqual(CaaCasesFilterType.AssigneeName); + expect(component.selectedFilterType).toEqual(CaaCasesFilterType.CasesAssignedToAUser); expect(component.selectedFilterValue).toEqual('assignee123'); expect(component.toggleFilterSection).toHaveBeenCalled(); }); diff --git a/src/caa-cases/containers/caa-cases/caa-cases.component.ts b/src/caa-cases/containers/caa-cases/caa-cases.component.ts index 7465a0d10..d456af579 100644 --- a/src/caa-cases/containers/caa-cases/caa-cases.component.ts +++ b/src/caa-cases/containers/caa-cases/caa-cases.component.ts @@ -175,7 +175,7 @@ export class CaaCasesComponent implements OnInit { public setSelectedFilterTypeAndValue(): void { this.selectedFilterType = this.caaCasesPageType === CaaCasesPageType.UnassignedCases ? CaaCasesFilterType.None - : CaaCasesFilterType.AllAssignees; + : CaaCasesFilterType.AllAssignedCases; this.selectedFilterValue = null; } @@ -315,7 +315,7 @@ export class CaaCasesComponent implements OnInit { if (this.caaCasesPageType === CaaCasesPageType.UnassignedCases && caseReferenceNumber) { this.selectedFilterValue = caseReferenceNumber; } else if (this.caaCasesPageType === CaaCasesPageType.AssignedCases) { - if (this.selectedFilterType === CaaCasesFilterType.AssigneeName && assigneeName) { + if (this.selectedFilterType === CaaCasesFilterType.CasesAssignedToAUser && assigneeName) { this.selectedFilterValue = assigneeName; } else if (this.selectedFilterType === CaaCasesFilterType.CaseReferenceNumber && caseReferenceNumber) { this.selectedFilterValue = caseReferenceNumber; @@ -335,7 +335,7 @@ export class CaaCasesComponent implements OnInit { value: { filterType: this.selectedFilterType, caseReferenceNumber: this.selectedFilterType === CaaCasesFilterType.CaseReferenceNumber ? this.selectedFilterValue : caseReferenceNumber, - assigneeName: this.selectedFilterType === CaaCasesFilterType.AssigneeName ? this.selectedFilterValue : assigneeName + assigneeName: this.selectedFilterType === CaaCasesFilterType.CasesAssignedToAUser ? this.selectedFilterValue : assigneeName } }; this.service.storeSessionState(sessionStateToUpdate); @@ -356,7 +356,7 @@ export class CaaCasesComponent implements OnInit { } // Return no cases found messages related to assigned cases if (this.caaCasesPageType === CaaCasesPageType.AssignedCases) { - if (this.selectedFilterType === CaaCasesFilterType.AssigneeName || this.selectedFilterType === CaaCasesFilterType.CaseReferenceNumber) { + if (this.selectedFilterType === CaaCasesFilterType.CasesAssignedToAUser || this.selectedFilterType === CaaCasesFilterType.CaseReferenceNumber) { return CaaCasesNoDataMessage.AssignedCasesFilterMessage; } return CaaCasesNoDataMessage.NoAssignedCases; diff --git a/src/caa-cases/containers/cases/cases.component.html b/src/caa-cases/containers/cases/cases.component.html index 2b7235615..8712d3cc6 100644 --- a/src/caa-cases/containers/cases/cases.component.html +++ b/src/caa-cases/containers/cases/cases.component.html @@ -43,14 +43,15 @@

> diff --git a/src/caa-cases/containers/cases/cases.component.ts b/src/caa-cases/containers/cases/cases.component.ts index 1e52be747..3002bfc97 100644 --- a/src/caa-cases/containers/cases/cases.component.ts +++ b/src/caa-cases/containers/cases/cases.component.ts @@ -9,7 +9,7 @@ import { OrganisationDetails } from 'src/models/organisation.model'; import { Observable } from 'rxjs'; import { Router } from '@angular/router'; import { ErrorMessage } from 'src/shared/models/error-message.model'; -import { SharedCase, SubNavigation, User } from '@hmcts/rpx-xui-common-lib'; +import { SubNavigation, User } from '@hmcts/rpx-xui-common-lib'; import { SelectedCaseFilter } from 'src/caa-cases/models/selected-case-filter.model'; import { CaaCases, CaaCasesSessionState, CaaCasesSessionStateValue } from 'src/caa-cases/models/caa-cases.model'; import { CaaCasesFilterType, CaaCasesPageType } from 'src/caa-cases/models/caa-cases.enum'; @@ -22,7 +22,8 @@ import * as converters from '../../converters/case-converter'; styleUrls: ['./cases.component.scss'] }) export class CasesComponent implements OnInit { - private readonly caaCasesPageType = 'all-cases-filter'; + // private caaCasesPageType = 'all-cases-filter'; // todo: this will be all cases since it's a merge of both assigned and unassigned cases + private caaCasesPageType = CaaCasesPageType.UnassignedCases; // todo: this will be all cases since it's a merge of both assigned and unassigned cases public selectedOrganisation$: Observable; public selectedOrganisationUsers$: Observable; @@ -32,15 +33,19 @@ export class CasesComponent implements OnInit { public errorMessages: ErrorMessage[] = []; public sessionStateValue: CaaCasesSessionStateValue; + public selectedFilterType: CaaCasesFilterType = CaaCasesFilterType.None; + public selectedFilterValue: string = null; + public selectedCaseType: string; + // for the results table - public allCaseTypes: SubNavigation[]; - public currentPageNo: number; + public allCaseTypes: SubNavigation[] = []; + public currentPageNo: number = 1; public paginationPageSize: number = 25; public casesConfig: CaaCases; public cases: any; // can we type this? public casesError$: Observable; - public shareAssignedCases$: Observable; - public shareUnassignedCases$: Observable; + public caseResultsTableShareButtonText: string = 'Share cases'; + private selectedCases: any[] = []; constructor(private readonly caaCasesStore: Store, private readonly organisationStore: Store, @@ -50,8 +55,12 @@ export class CasesComponent implements OnInit { } public ngOnInit(): void { - // this.loadCaseTypes(selectedFilterType, selectedFilterValue); - this.loadCaseTypes(); + // Retrieve session state to check and pre-populate the previous state if any + this.retrieveSessionState(); + // if session state is found, then filter component will emit filter values to avoid double query + if (!this.sessionStateValue) { + this.loadCaseTypes(); + } // Load selected organisation details from store this.organisationStore.dispatch(new organisationStore.LoadOrganisation()); @@ -61,9 +70,6 @@ export class CasesComponent implements OnInit { this.userStore.dispatch(new userStore.LoadAllUsersNoRoleData()); this.selectedOrganisationUsers$ = this.userStore.pipe(select(userStore.getGetUserList)); - // Retrieve session state to check and pre-populate the previous state if any - this.retrieveSessionState(); - // TODO: clean this up to get all cases this.caaCasesStore.pipe(select(caaCasesStore.getAllUnassignedCases)).subscribe((config: CaaCases) => { if (config){ @@ -75,54 +81,70 @@ export class CasesComponent implements OnInit { this.cases = items; } }); - this.casesError$ = this.caaCasesStore.pipe(select(caaCasesStore.getAllUnassignedCasesError)); + this.caaCasesStore.pipe(select(caaCasesStore.getAllAssignedCases)).subscribe((config: CaaCases) => { + if (config){ + this.casesConfig = config; + } + }); + this.caaCasesStore.pipe(select(caaCasesStore.getAllAssignedCaseData)).subscribe((items) => { + if (items){ + this.cases = items; + } + }); - this.shareAssignedCases$ = this.caaCasesStore.pipe(select(caaCasesStore.getShareAssignedCaseListState)); - this.shareUnassignedCases$ = this.caaCasesStore.pipe(select(caaCasesStore.getShareUnassignedCaseListState)); + this.casesError$ = this.caaCasesStore.pipe(select(caaCasesStore.getAllUnassignedCasesError)); + this.casesError$ = this.caaCasesStore.pipe(select(caaCasesStore.getAllAssignedCasesError)); } /** * This will load all case types based on the selected filter type and value */ public loadCaseTypes() { - // TODO: get selected filter type and selected filter value from somewhere - const selectedFilterType = CaaCasesPageType.UnassignedCases; // todo: this will be all cases since it's a merge of both assigned and unassigned cases - const pageType = CaaCasesPageType.UnassignedCases; // todo: this will be all cases since it's a merge of both assigned and unassigned cases - const selectedFilterValue = null; - // Load case types based on current page type according to filtered value this.caaCasesStore.dispatch(new caaCasesStore.LoadCaseTypes({ - caaCasesPageType: pageType, - caaCasesFilterType: selectedFilterType, - caaCasesFilterValue: selectedFilterValue }) + caaCasesPageType: this.caaCasesPageType, + caaCasesFilterType: this.selectedFilterType, + caaCasesFilterValue: this.selectedFilterValue }) ); this.caaCasesStore.pipe(select(caaCasesStore.getAllCaseTypes)).subscribe((items) => { this.allCaseTypes = items; - this.loadCases(); + if (this.allCaseTypes && this.allCaseTypes.length > 0) { + this.selectedCaseType = this.allCaseTypes[0].text; + this.loadCaseData(); + } }); } /** * This will load cases (i.e. unassigned or assigned) from the API with the selected filter type and value */ - public loadCases(){ + public loadCaseData(){ if (this.allCaseTypes && this.allCaseTypes.length > 0) { - const selectedFilterType = CaaCasesFilterType.None; // todo: this will be all cases since it's a merge of both assigned and unassigned cases - const selectedFilterValue = null; - const currentCaseType = this.allCaseTypes[0].text; - - this.caaCasesStore.dispatch(new caaCasesStore.LoadUnassignedCases({ - caseType: currentCaseType, - pageNo: this.currentPageNo, - pageSize: this.paginationPageSize, - caaCasesFilterType: selectedFilterType, - caaCasesFilterValue: selectedFilterValue - })); + if (this.caaCasesPageType === CaaCasesPageType.AssignedCases) { + this.caaCasesStore.dispatch(new caaCasesStore.LoadAssignedCases({ + caseType: this.selectedCaseType, + pageNo: this.currentPageNo, + pageSize: this.paginationPageSize, + caaCasesFilterType: this.selectedFilterType, + caaCasesFilterValue: this.selectedFilterValue + })); + } + if (this.caaCasesPageType === CaaCasesPageType.UnassignedCases){ + this.caaCasesStore.dispatch(new caaCasesStore.LoadUnassignedCases({ + caseType: this.selectedCaseType, + pageNo: this.currentPageNo, + pageSize: this.paginationPageSize, + caaCasesFilterType: this.selectedFilterType, + caaCasesFilterValue: this.selectedFilterValue + })); + } } } public onSelectedFilter(selectedFilter: SelectedCaseFilter): void { console.log('Selected filter:', selectedFilter); - // todo: update session state (i.e. remove or store) + this.selectedFilterType = selectedFilter.filterType; + this.selectedFilterValue = selectedFilter.filterValue; + if (selectedFilter.filterType === CaaCasesFilterType.None) { this.removeSessionState(this.caaCasesPageType); } else { @@ -130,6 +152,28 @@ export class CasesComponent implements OnInit { } // load cases types based on filter and value + if (selectedFilter.filterType === CaaCasesFilterType.CaseReferenceNumber) { + // dispatch action to load case by ref number + this.caseResultsTableShareButtonText = 'Accept and assign cases'; + } + if (selectedFilter.filterType === CaaCasesFilterType.CasesAssignedToAUser) { + // dispatch action to load case by assignee name + this.caaCasesPageType = CaaCasesPageType.AssignedCases; + this.caseResultsTableShareButtonText = 'Manage cases'; + } + if (selectedFilter.filterType === CaaCasesFilterType.AllAssignedCases) { + // dispatch action to load all cases + this.caseResultsTableShareButtonText = 'Manage cases'; + } + if (selectedFilter.filterType === CaaCasesFilterType.NewCasesToAccept) { + // dispatch action to load new cases to accept + this.caseResultsTableShareButtonText = 'Accept cases'; + } + if (selectedFilter.filterType === CaaCasesFilterType.UnassignedCases) { + // dispatch action to load unassigned cases + this.caseResultsTableShareButtonText = 'Manage cases'; + } + this.loadCaseTypes(); } public onErrorMessages(errorMessages: ErrorMessage[]): void { @@ -161,7 +205,7 @@ export class CasesComponent implements OnInit { value: { filterType: selectedFilter.filterType, caseReferenceNumber: selectedFilter.filterType === CaaCasesFilterType.CaseReferenceNumber ? selectedFilter.filterValue : null, - assigneeName: selectedFilter.filterType === CaaCasesFilterType.AssigneeName ? selectedFilter.filterValue : null + assigneeName: selectedFilter.filterType === CaaCasesFilterType.CasesAssignedToAUser ? selectedFilter.filterValue : null } }; this.sessionStateValue = sessionStateToUpdate.value; @@ -169,27 +213,59 @@ export class CasesComponent implements OnInit { } public onCaseSelected(selectedCases: any[]): void { - // if (this.caaCasesPageType === CaaCasesPageType.UnassignedCases) { - // this.selectedUnassignedCases = selectedCases; - // this.store.dispatch(new fromStore.SynchronizeStateToStoreUnassignedCases( - // converters.toShareCaseConverter(selectedCases, this.currentCaseType) - // )); - // } else { - // this.selectedAssignedCases = selectedCases; - // this.store.dispatch(new fromStore.SynchronizeStateToStoreAssignedCases( - // converters.toShareCaseConverter(selectedCases, this.currentCaseType) - // )); - // } - - // do i need the line below? + // do i need the line below? and remove the selector in ngOnInit // this.selectedUnassignedCases = selectedCases; - this.caaCasesStore.dispatch(new caaCasesStore.SynchronizeStateToStoreUnassignedCases( - converters.toShareCaseConverter(selectedCases, CaaCasesPageType.UnassignedCases) - )); + if (this.caaCasesPageType === CaaCasesPageType.AssignedCases){ + this.caaCasesStore.dispatch(new caaCasesStore.SynchronizeStateToStoreAssignedCases( + converters.toShareCaseConverter(selectedCases, CaaCasesPageType.AssignedCases) + )); + } + + if (this.caaCasesPageType === CaaCasesPageType.UnassignedCases){ + this.caaCasesStore.dispatch(new caaCasesStore.SynchronizeStateToStoreUnassignedCases( + converters.toShareCaseConverter(selectedCases, CaaCasesPageType.UnassignedCases) + )); + } + this.selectedCases = selectedCases; } public onPageChanged(pageNo: number): void { this.currentPageNo = pageNo; - this.loadCases(); + this.loadCaseData(); + } + + onShareButtonClicked($event: void) { + // load cases types based on filter and value + if (this.selectedFilterType === CaaCasesFilterType.CaseReferenceNumber) { + // TODO: need to handle the `new_case` flag + // if returning new case then go to add recipient page + // else if returning non-new case then go to manage case assignments + this.caaCasesStore.dispatch(new caaCasesStore.AddShareAssignedCases({ + sharedCases: converters.toShareCaseConverter(this.selectedCases, this.selectedCaseType) + })); + } + if (this.selectedFilterType === CaaCasesFilterType.CasesAssignedToAUser) { + // todo: go to manage case assignments + this.caaCasesStore.dispatch(new caaCasesStore.AddShareAssignedCases({ + sharedCases: converters.toShareCaseConverter(this.selectedCases, this.selectedCaseType) + } + )); + } + if (this.selectedFilterType === CaaCasesFilterType.AllAssignedCases) { + this.caaCasesStore.dispatch(new caaCasesStore.AddShareAssignedCases({ + sharedCases: converters.toShareCaseConverter(this.selectedCases, this.selectedCaseType) + } + )); + } + if (this.selectedFilterType === CaaCasesFilterType.NewCasesToAccept) { + // dispatch action to load new cases to accept + // if group_access is enabled then go to accept cases page + // else go to add recipient + } + if (this.selectedFilterType === CaaCasesFilterType.UnassignedCases) { + this.caaCasesStore.dispatch(new caaCasesStore.AddShareUnassignedCases({ + sharedCases: converters.toShareCaseConverter(this.selectedCases, this.selectedCaseType) + })); + } } } diff --git a/src/caa-cases/models/caa-cases.enum.ts b/src/caa-cases/models/caa-cases.enum.ts index bdc718742..edd89f7ad 100644 --- a/src/caa-cases/models/caa-cases.enum.ts +++ b/src/caa-cases/models/caa-cases.enum.ts @@ -11,11 +11,11 @@ export enum CaaCasesShowHideFilterButtonText { } export enum CaaCasesFilterType { - AllAssignees = 'all-assignees', - AssigneeName = 'assignee-name', + AllAssignedCases = 'all-assignees', + CasesAssignedToAUser = 'assignee-name', CaseReferenceNumber = 'case-reference-number', - NewCasesToAccept = 'new-cases-to-accept', - UnassignedCases = 'unassigned-cases', + NewCasesToAccept = 'new-cases-to-accept', // new enum + UnassignedCases = 'unassigned-cases', // new enum None = 'none', } diff --git a/src/caa-cases/services/caa-cases.service.spec.ts b/src/caa-cases/services/caa-cases.service.spec.ts index 52992412f..89a0f1ab7 100644 --- a/src/caa-cases/services/caa-cases.service.spec.ts +++ b/src/caa-cases/services/caa-cases.service.spec.ts @@ -10,7 +10,7 @@ describe('CaaCasesService', () => { let mockSessionStorage: any; let mockHttp: any; const sessionStateValue: CaaCasesSessionStateValue = { - filterType: CaaCasesFilterType.AssigneeName, + filterType: CaaCasesFilterType.CasesAssignedToAUser, caseReferenceNumber: null, assigneeName: 'assignee123' }; From dd0adb9876babe96922f5073666c0f150b7820b9 Mon Sep 17 00:00:00 2001 From: Shaed Parkar Date: Mon, 25 Mar 2024 10:06:57 +0000 Subject: [PATCH 08/44] Refactor routes and constants --- src/app/app.constants.ts | 2 +- src/app/app.routes.ts | 2 +- src/caa-cases/caa-cases.routing.ts | 21 +- src/cases/cases.module.ts | 47 +++ src/cases/cases.routing.ts | 48 +++ .../cases-filter/cases-filter.component.html | 273 ++++++++++++++++++ .../cases-filter/cases-filter.component.scss | 44 +++ .../cases-filter.component.spec.ts | 23 ++ .../cases-filter/cases-filter.component.ts | 212 ++++++++++++++ .../cases-results-table.component.html | 61 ++++ .../cases-results-table.component.scss | 0 .../cases-results-table.component.spec.ts | 23 ++ .../cases-results-table.component.ts | 155 ++++++++++ src/cases/components/index.ts | 10 + .../case-share-complete.component.html | 68 +++++ .../case-share-complete.component.scss | 11 + .../case-share-complete.component.spec.ts | 213 ++++++++++++++ .../case-share-complete.component.ts | 90 ++++++ .../case-share-confirm.component.html | 10 + .../case-share-confirm.component.scss | 0 .../case-share-confirm.component.spec.ts | 63 ++++ .../case-share-confirm.component.ts | 51 ++++ .../case-share/case-share.component.html | 15 + .../case-share/case-share.component.scss | 0 .../case-share/case-share.component.spec.ts | 69 +++++ .../case-share/case-share.component.ts | 103 +++++++ src/cases/containers/case-share/index.ts | 45 +++ src/cases/containers/case-share/test.spec.ts | 120 ++++++++ .../containers/cases/cases.component.html | 57 ++++ .../containers/cases/cases.component.scss | 0 .../containers/cases/cases.component.spec.ts | 23 ++ src/cases/containers/cases/cases.component.ts | 271 +++++++++++++++++ src/cases/containers/index.ts | 17 ++ src/cases/converters/case-converter.ts | 75 +++++ src/cases/converters/case-converters.spec.ts | 104 +++++++ src/cases/guards/feature-toggle.guard.ts | 14 + src/cases/guards/user-role.guard.ts | 15 + src/cases/models/caa-cases.enum.ts | 42 +++ src/cases/models/caa-cases.model.ts | 37 +++ .../models/selected-case-filter.model.ts | 6 + src/cases/services/caa-cases.service.spec.ts | 101 +++++++ src/cases/services/caa-cases.service.ts | 54 ++++ src/cases/services/index.ts | 10 + src/cases/services/share-case.service.spec.ts | 54 ++++ src/cases/services/share-case.service.ts | 28 ++ .../store/actions/caa-cases.actions.spec.ts | 99 +++++++ src/cases/store/actions/caa-cases.actions.ts | 76 +++++ src/cases/store/actions/index.ts | 20 ++ .../store/actions/share-case.action.spec.ts | 195 +++++++++++++ src/cases/store/actions/share-case.action.ts | 208 +++++++++++++ .../store/effects/caa-cases.effects.spec.ts | 160 ++++++++++ src/cases/store/effects/caa-cases.effects.ts | 74 +++++ src/cases/store/effects/index.ts | 11 + .../store/effects/share-case.effects.spec.ts | 265 +++++++++++++++++ src/cases/store/effects/share-case.effects.ts | 148 ++++++++++ src/cases/store/index.ts | 4 + .../store/reducers/caa-cases.reducer.spec.ts | 61 ++++ src/cases/store/reducers/caa-cases.reducer.ts | 49 ++++ src/cases/store/reducers/index.ts | 21 ++ .../store/reducers/share-case.reducer.spec.ts | 273 ++++++++++++++++++ .../store/reducers/share-case.reducer.ts | 250 ++++++++++++++++ .../selectors/caa-cases.selector.spec.ts | 75 +++++ .../store/selectors/caa-cases.selector.ts | 58 ++++ src/cases/store/selectors/index.ts | 2 + .../store/selectors/share-case.selector.ts | 23 ++ .../selectors/share-case.selectors.spec.ts | 68 +++++ src/cases/util/caa-cases.util.spec.ts | 115 ++++++++ src/cases/util/caa-cases.util.ts | 62 ++++ 68 files changed, 4982 insertions(+), 22 deletions(-) create mode 100644 src/cases/cases.module.ts create mode 100644 src/cases/cases.routing.ts create mode 100644 src/cases/components/cases-filter/cases-filter.component.html create mode 100644 src/cases/components/cases-filter/cases-filter.component.scss create mode 100644 src/cases/components/cases-filter/cases-filter.component.spec.ts create mode 100644 src/cases/components/cases-filter/cases-filter.component.ts create mode 100644 src/cases/components/cases-results-table/cases-results-table.component.html create mode 100644 src/cases/components/cases-results-table/cases-results-table.component.scss create mode 100644 src/cases/components/cases-results-table/cases-results-table.component.spec.ts create mode 100644 src/cases/components/cases-results-table/cases-results-table.component.ts create mode 100644 src/cases/components/index.ts create mode 100644 src/cases/containers/case-share-complete/case-share-complete.component.html create mode 100644 src/cases/containers/case-share-complete/case-share-complete.component.scss create mode 100644 src/cases/containers/case-share-complete/case-share-complete.component.spec.ts create mode 100644 src/cases/containers/case-share-complete/case-share-complete.component.ts create mode 100644 src/cases/containers/case-share-confirm/case-share-confirm.component.html create mode 100644 src/cases/containers/case-share-confirm/case-share-confirm.component.scss create mode 100644 src/cases/containers/case-share-confirm/case-share-confirm.component.spec.ts create mode 100644 src/cases/containers/case-share-confirm/case-share-confirm.component.ts create mode 100644 src/cases/containers/case-share/case-share.component.html create mode 100644 src/cases/containers/case-share/case-share.component.scss create mode 100644 src/cases/containers/case-share/case-share.component.spec.ts create mode 100644 src/cases/containers/case-share/case-share.component.ts create mode 100644 src/cases/containers/case-share/index.ts create mode 100644 src/cases/containers/case-share/test.spec.ts create mode 100644 src/cases/containers/cases/cases.component.html create mode 100644 src/cases/containers/cases/cases.component.scss create mode 100644 src/cases/containers/cases/cases.component.spec.ts create mode 100644 src/cases/containers/cases/cases.component.ts create mode 100644 src/cases/containers/index.ts create mode 100644 src/cases/converters/case-converter.ts create mode 100644 src/cases/converters/case-converters.spec.ts create mode 100644 src/cases/guards/feature-toggle.guard.ts create mode 100644 src/cases/guards/user-role.guard.ts create mode 100644 src/cases/models/caa-cases.enum.ts create mode 100644 src/cases/models/caa-cases.model.ts create mode 100644 src/cases/models/selected-case-filter.model.ts create mode 100644 src/cases/services/caa-cases.service.spec.ts create mode 100644 src/cases/services/caa-cases.service.ts create mode 100644 src/cases/services/index.ts create mode 100644 src/cases/services/share-case.service.spec.ts create mode 100644 src/cases/services/share-case.service.ts create mode 100644 src/cases/store/actions/caa-cases.actions.spec.ts create mode 100644 src/cases/store/actions/caa-cases.actions.ts create mode 100644 src/cases/store/actions/index.ts create mode 100644 src/cases/store/actions/share-case.action.spec.ts create mode 100644 src/cases/store/actions/share-case.action.ts create mode 100644 src/cases/store/effects/caa-cases.effects.spec.ts create mode 100644 src/cases/store/effects/caa-cases.effects.ts create mode 100644 src/cases/store/effects/index.ts create mode 100644 src/cases/store/effects/share-case.effects.spec.ts create mode 100644 src/cases/store/effects/share-case.effects.ts create mode 100644 src/cases/store/index.ts create mode 100644 src/cases/store/reducers/caa-cases.reducer.spec.ts create mode 100644 src/cases/store/reducers/caa-cases.reducer.ts create mode 100644 src/cases/store/reducers/index.ts create mode 100644 src/cases/store/reducers/share-case.reducer.spec.ts create mode 100644 src/cases/store/reducers/share-case.reducer.ts create mode 100644 src/cases/store/selectors/caa-cases.selector.spec.ts create mode 100644 src/cases/store/selectors/caa-cases.selector.ts create mode 100644 src/cases/store/selectors/index.ts create mode 100644 src/cases/store/selectors/share-case.selector.ts create mode 100644 src/cases/store/selectors/share-case.selectors.spec.ts create mode 100644 src/cases/util/caa-cases.util.spec.ts create mode 100644 src/cases/util/caa-cases.util.ts diff --git a/src/app/app.constants.ts b/src/app/app.constants.ts index 3b35654a5..5910b3f22 100644 --- a/src/app/app.constants.ts +++ b/src/app/app.constants.ts @@ -54,7 +54,7 @@ const navItemsArray: NavItemModel[] = [ }, { text: 'Cases', - href: '/cases/all', + href: '/cases', orderId: 6, active: false, featureToggle: { diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 97f1cd206..09f667583 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -28,7 +28,7 @@ export const ROUTES: Routes = [ { path: 'cases', canActivate: [AuthGuard, HealthCheckGuard], - loadChildren: () => import('../caa-cases/caa-cases.module').then((m) => m.CaaCasesModule) + loadChildren: () => import('../cases/cases.module').then((m) => m.CaaCasesModule) }, { path: 'unassigned-cases', diff --git a/src/caa-cases/caa-cases.routing.ts b/src/caa-cases/caa-cases.routing.ts index e7f7b8862..0823604b6 100644 --- a/src/caa-cases/caa-cases.routing.ts +++ b/src/caa-cases/caa-cases.routing.ts @@ -2,21 +2,11 @@ import { ModuleWithProviders } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { AuthGuard } from '../user-profile/guards/auth.guard'; import { CaaCasesModule } from './caa-cases.module'; -import { CaaCasesComponent, CaseShareCompleteComponent, CaseShareComponent, CaseShareConfirmComponent, CasesComponent } from './containers'; +import { CaaCasesComponent, CaseShareCompleteComponent, CaseShareComponent, CaseShareConfirmComponent } from './containers'; import { FeatureToggleAccountGuard } from './guards/feature-toggle.guard'; import { RoleGuard } from './guards/user-role.guard'; -import { NewCaseFeatureToggleGuard } from './guards/new-cases-feature-toggle.guard'; export const ROUTES: Routes = [ - { - path: 'all', - component: CasesComponent, - canActivate: [ - AuthGuard, - NewCaseFeatureToggleGuard, - RoleGuard - ] - }, { path: '', component: CaaCasesComponent, @@ -35,15 +25,6 @@ export const ROUTES: Routes = [ RoleGuard ] }, - { - path: 'all/case-share', - component: CaseShareComponent, - canActivate: [ - AuthGuard, - FeatureToggleAccountGuard, - RoleGuard - ] - }, { path: 'case-share-confirm/:pageType', component: CaseShareConfirmComponent, diff --git a/src/cases/cases.module.ts b/src/cases/cases.module.ts new file mode 100644 index 000000000..83699a373 --- /dev/null +++ b/src/cases/cases.module.ts @@ -0,0 +1,47 @@ +import { CommonModule } from '@angular/common'; +import { HttpClientModule } from '@angular/common/http'; +import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; +import { MatLegacyAutocompleteModule as MatAutocompleteModule } from '@angular/material/legacy-autocomplete'; +import { MatLegacyTabsModule as MatTabsModule } from '@angular/material/legacy-tabs'; +import { CaseListModule } from '@hmcts/ccd-case-ui-toolkit'; +import { ExuiCommonLibModule } from '@hmcts/rpx-xui-common-lib'; +import { EffectsModule } from '@ngrx/effects'; +import { StoreModule } from '@ngrx/store'; +import { OrganisationService, PBAService } from '../organisation/services'; +import { effects as orgEffects, reducers as orgReducers } from '../organisation/store'; +import { SharedModule } from '../shared/shared.module'; +import { InviteUserService, UsersService } from '../users/services'; +import { effects as userEffects, reducers as userReducers } from '../users/store'; +import { casesRouting } from './cases.routing'; +import * as fromComponents from './components'; +import * as fromContainers from './containers'; +import { FeatureToggleAccountGuard } from './guards/feature-toggle.guard'; +import { RoleGuard } from './guards/user-role.guard'; +import * as fromServices from './services'; +import { effects, reducers } from './store'; + +@NgModule({ + imports: [ + CommonModule, + ExuiCommonLibModule, + HttpClientModule, + SharedModule, + casesRouting, + StoreModule.forFeature('org', orgReducers), + EffectsModule.forFeature(orgEffects), + StoreModule.forFeature('users', userReducers), + EffectsModule.forFeature(userEffects), + StoreModule.forFeature('caaCases', reducers), + EffectsModule.forFeature(effects), + CaseListModule, + MatTabsModule, + MatAutocompleteModule + ], + exports: [...fromContainers.containers, ...fromComponents.components], + declarations: [...fromContainers.containers, ...fromComponents.components], + providers: [...fromServices.services, OrganisationService, PBAService, UsersService, InviteUserService, FeatureToggleAccountGuard, RoleGuard], + schemas: [CUSTOM_ELEMENTS_SCHEMA] +}) + +export class CaaCasesModule { +} diff --git a/src/cases/cases.routing.ts b/src/cases/cases.routing.ts new file mode 100644 index 000000000..a42866983 --- /dev/null +++ b/src/cases/cases.routing.ts @@ -0,0 +1,48 @@ +import { ModuleWithProviders } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { AuthGuard } from '../user-profile/guards/auth.guard'; +import { CaaCasesModule } from './cases.module'; +import { CaseShareCompleteComponent, CaseShareComponent, CaseShareConfirmComponent, CasesComponent } from './containers'; +import { FeatureToggleAccountGuard } from './guards/feature-toggle.guard'; +import { RoleGuard } from './guards/user-role.guard'; + +export const ROUTES: Routes = [ + { + path: '', + component: CasesComponent, + canActivate: [ + AuthGuard, + FeatureToggleAccountGuard, + RoleGuard + ] + }, + { + path: 'case-share', + component: CaseShareComponent, + canActivate: [ + AuthGuard, + FeatureToggleAccountGuard, + RoleGuard + ] + }, + { + path: 'case-share-confirm/:pageType', + component: CaseShareConfirmComponent, + canActivate: [ + AuthGuard, + FeatureToggleAccountGuard, + RoleGuard + ] + }, + { + path: 'case-share-complete/:pageType', + component: CaseShareCompleteComponent, + canActivate: [ + AuthGuard, + FeatureToggleAccountGuard, + RoleGuard + ] + } +]; + +export const casesRouting: ModuleWithProviders = RouterModule.forChild(ROUTES); diff --git a/src/cases/components/cases-filter/cases-filter.component.html b/src/cases/components/cases-filter/cases-filter.component.html new file mode 100644 index 000000000..c203d4047 --- /dev/null +++ b/src/cases/components/cases-filter/cases-filter.component.html @@ -0,0 +1,273 @@ +
+
+
+
+
+
+
+
+ +

Filter cases

+
+
+ Filter your organisation's assigned cases by either; +
+ +
+ +
+ + +
+ + +
+ + +
+
+
+ +

+ Error: + {{ assigneeNameErrorMessage }} +

+ + + + {{ + filteredAndGroupedUser.key + }} + + {{ user.fullName + " - " + user.email }} + + No matches + + +
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ +
+
+ +

+ Error:{{ caseReferenceNumberErrorMessage }} +

+ +
+
+
+
+
+ +
+ + +
+ +
+
+
+ +
+ + + Warning + The tabs below list all of your organisation's cases which are not assigned to any users. You can assign + cases to users by selecting 'Manage cases'. + + You do not need to assigned cases to users if they have 'Access all cases in the organisation' enabled for + that case type. + +
+
+
+
+
+
diff --git a/src/cases/components/cases-filter/cases-filter.component.scss b/src/cases/components/cases-filter/cases-filter.component.scss new file mode 100644 index 000000000..0c0512166 --- /dev/null +++ b/src/cases/components/cases-filter/cases-filter.component.scss @@ -0,0 +1,44 @@ +@import "govuk-frontend/govuk/base"; + +.govuk-grid-row { + .govuk-grid-column-one-half { + background-color: govuk-colour("light-grey"); + padding-top: 20px; + margin-left: 15px; + } + + #filterContainer { + margin-bottom: 30px; + } + + #assignee-person { + width: 90%; + } + + #case-reference-number { + width: 70%; + } + + .hide-autocomplete { + display: none; + } +} + +.mat-optgroup { + .mat-group-header { + font-size: 16px !important; + font-weight: bold !important; + } + + .mat-option { + &:hover { + background: $govuk-focus-colour; + } + &.select-option { + &:hover { + background: govuk-colour("blue"); + color: govuk-colour("white"); + } + } + } +} diff --git a/src/cases/components/cases-filter/cases-filter.component.spec.ts b/src/cases/components/cases-filter/cases-filter.component.spec.ts new file mode 100644 index 000000000..d1c00fb6f --- /dev/null +++ b/src/cases/components/cases-filter/cases-filter.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CasesFilterComponent } from './cases-filter.component'; + +describe('CasesFilterComponent', () => { + let component: CasesFilterComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CasesFilterComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CasesFilterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/cases/components/cases-filter/cases-filter.component.ts b/src/cases/components/cases-filter/cases-filter.component.ts new file mode 100644 index 000000000..0531f8ade --- /dev/null +++ b/src/cases/components/cases-filter/cases-filter.component.ts @@ -0,0 +1,212 @@ +import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; +import { User } from '@hmcts/rpx-xui-common-lib'; +import { Observable, catchError, debounceTime, of, switchMap, tap } from 'rxjs'; +import { CaaCasesFilterErrorMessage, CaaCasesFilterType } from 'src/cases/models/caa-cases.enum'; +import { CaaCasesSessionStateValue } from 'src/cases/models/caa-cases.model'; +import { SelectedCaseFilter } from 'src/cases/models/selected-case-filter.model'; +import { CaaCasesUtil } from 'src/cases/util/caa-cases.util'; +import { ErrorMessage } from 'src/shared/models/error-message.model'; + +@Component({ + selector: 'app-cases-filter', + templateUrl: './cases-filter.component.html', + styleUrls: ['./cases-filter.component.scss'] +}) +export class CasesFilterComponent implements OnInit, OnChanges{ + @Input() public selectedOrganisationUsers: User[]; + @Input() public sessionStateValue: CaaCasesSessionStateValue; + + @Output() public selectedFilter = new EventEmitter(); + @Output() public emitErrorMessages = new EventEmitter(); + + public readonly ACTIVE_USER_GROUP_HEADING = 'Active users:'; + public readonly INACTIVE_USER_GROUP_HEADING = 'Inactive users:'; + public readonly ACTIVE_USER_STATUS = 'active'; + + public readonly assigneeNameErrorMessage = 'Enter a valid assignee name'; + public readonly caseReferenceNumberErrorMessage = 'Enter a valid HMCTS case reference number'; + + public filteredAndGroupedUsers = new Map(); + + public caaCasesFilterType = CaaCasesFilterType; // used in the template + public selectedFilterType = CaaCasesFilterType.None; + + public errorMessages: ErrorMessage[]; + + public form: FormGroup; + public showAutocomplete: boolean = false; + public filterApplied: boolean = false; + + public constructor(private formBuilder: FormBuilder) {} + + ngOnInit(): void { + this.createForm(); + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes.selectedOrganisationUsers && + changes.selectedOrganisationUsers.currentValue && + changes.selectedOrganisationUsers.currentValue.length > 0) { + this.filterSelectedOrganisationUsers().subscribe((filteredAndGroupedUsers) => { + this.filteredAndGroupedUsers = filteredAndGroupedUsers; + }); + this.populateFormFromSessionState(); + } + } + + createForm() { + this.form = this.formBuilder.group({ + filterOption: this.formBuilder.control(CaaCasesFilterType.None), + assigneePerson: this.formBuilder.control(''), + caseReferenceNumber: this.formBuilder.control('') + }); + + this.form.controls.filterOption.valueChanges.subscribe((value: CaaCasesFilterType) => { + this.selectFilterOption(value); + this.handleOnFilterOptionChange(value); + }); + + this.form.controls.assigneePerson.valueChanges.pipe( + tap(() => { + this.showAutocomplete = false; + this.filteredAndGroupedUsers = null; + }, + debounceTime(300)), + switchMap((searchTerm: any) => this.filterSelectedOrganisationUsers(searchTerm).pipe( + tap(() => this.showAutocomplete = true), + catchError(() => this.filteredAndGroupedUsers = null) + )) + ).subscribe((filteredAndGroupedUsers: Map) => { + this.filteredAndGroupedUsers = filteredAndGroupedUsers; + }); + } + + public populateFormFromSessionState(): void { + if (this.sessionStateValue) { + const filterOptionValue = this.sessionStateValue.filterType as CaaCasesFilterType; + this.form.controls.filterOption.setValue(filterOptionValue, { emitEvent: false, onlySelf: true }); + + this.selectFilterOption(filterOptionValue); + if (filterOptionValue === CaaCasesFilterType.CasesAssignedToAUser) { + const assigneePersonValue = this.sessionStateValue.assigneeName; + const user = this.selectedOrganisationUsers.find((user) => user.userIdentifier === assigneePersonValue); + + this.form.controls.assigneePerson.setValue(this.getDisplayName(user), { emitEvent: false, onlySelf: true }); + } + + if (filterOptionValue === CaaCasesFilterType.CaseReferenceNumber) { + const caseReferenceNumberValue = this.sessionStateValue.caseReferenceNumber; + this.form.controls.caseReferenceNumber.setValue(caseReferenceNumberValue, { emitEvent: false, onlySelf: true }); + } + this.filterApplied = true; + this.form.markAsDirty(); + this.onSearch(); + } + } + + public filterSelectedOrganisationUsers(searchTerm?: string | User): Observable> { + const filteredUsers = searchTerm && searchTerm.length > 0 + ? typeof(searchTerm) === 'string' + ? this.selectedOrganisationUsers.filter((user) => this.getDisplayName(user).toLowerCase().includes(searchTerm.toLowerCase())) + : this.selectedOrganisationUsers.filter((user) => this.getDisplayName(user).toLowerCase().includes(this.getDisplayName(searchTerm).toLowerCase())) + : this.selectedOrganisationUsers; + const activeUsers = filteredUsers.filter((user) => user.status.toLowerCase() === this.ACTIVE_USER_STATUS); + const inactiveUsers = filteredUsers.filter((user) => user.status.toLowerCase() !== this.ACTIVE_USER_STATUS); + const groupedUsers = new Map(); + groupedUsers.set(this.ACTIVE_USER_GROUP_HEADING, activeUsers); + groupedUsers.set(this.INACTIVE_USER_GROUP_HEADING, inactiveUsers); + return of(groupedUsers); + } + + public getDisplayName(selectedUser: User): string { + return `${selectedUser.fullName} - ${selectedUser.email}`; + } + + public onSearch(): void { + if (this.validateForm()) { + let filterValue = null; + if (this.form.controls.filterOption.value === CaaCasesFilterType.CaseReferenceNumber) { + filterValue = this.form.controls.caseReferenceNumber.value; + } + + if (this.form.controls.filterOption.value === CaaCasesFilterType.CasesAssignedToAUser) { + const selectedUser = this.form.controls.assigneePerson.value; + const fullName = selectedUser.split(' - ')[0]; + const email = selectedUser.split(' - ')[1]; + filterValue = this.selectedOrganisationUsers && this.selectedOrganisationUsers.find( + (user) => user.fullName === fullName && user.email === email).userIdentifier; + } + const selectedFilter: SelectedCaseFilter = { + filterType: this.selectedFilterType, + filterValue: filterValue + }; + this.filterApplied = true; + this.selectedFilter.emit(selectedFilter); + } + } + + public onReset(): void { + this.form.reset({ filterOption: CaaCasesFilterType.None, assigneePerson: '', caseReferenceNumber: '' }); + this.selectedFilter.emit({ filterType: this.selectedFilterType, filterValue: '' }); + this.filterApplied = false; + } + + public onUserSelectionChange(selectedUser: User) { + this.form.controls.assigneePerson.clearValidators(); + this.form.controls.assigneePerson.updateValueAndValidity(); + this.form.controls.assigneePerson.setValue(this.getDisplayName(selectedUser), { emitEvent: false, onlySelf: true }); + } + + public selectFilterOption(caaCasesFilterType: CaaCasesFilterType): void { + this.selectedFilterType = caaCasesFilterType; + } + + private validateForm(): boolean { + let isValid = true; + this.errorMessages = []; + if (this.form.controls.filterOption.value === CaaCasesFilterType.CasesAssignedToAUser) { + this.form.controls.assigneePerson.updateValueAndValidity({ emitEvent: false, onlySelf: true }); // ensure validation is run even if the field is empty + if (this.form.controls.assigneePerson.invalid) { + this.errorMessages.push({ title: '', description: CaaCasesFilterErrorMessage.InvalidAssigneeName, fieldId: 'assigneePerson' }); + isValid = false; + } + } + if (this.form.controls.filterOption.value === CaaCasesFilterType.CaseReferenceNumber) { + this.form.controls.caseReferenceNumber.updateValueAndValidity({ emitEvent: false, onlySelf: true }); // ensure validation is run even if the field is empty + if (this.form.controls.caseReferenceNumber.invalid) { + this.errorMessages.push({ title: '', description: CaaCasesFilterErrorMessage.InvalidCaseReference, fieldId: 'caseReferenceNumber' }); + isValid = false; + } + } + this.emitErrorMessages.emit(this.errorMessages); + return isValid; + } + + private handleOnFilterOptionChange(value: CaaCasesFilterType): void { + // clear validators for all forms + this.form.controls.assigneePerson.clearValidators(); + this.form.controls.caseReferenceNumber.clearValidators(); + this.form.controls.assigneePerson.reset(); + this.form.controls.caseReferenceNumber.reset(); + switch (value) { + case CaaCasesFilterType.CasesAssignedToAUser: + this.form.controls.assigneePerson.setValidators([Validators.required, CaaCasesUtil.assigneeNameValidator2()]); + break; + case CaaCasesFilterType.CaseReferenceNumber: + this.form.controls.caseReferenceNumber.setValidators([Validators.required, CaaCasesUtil.caseReferenceValidator()]); + break; + case CaaCasesFilterType.AllAssignedCases: + case CaaCasesFilterType.NewCasesToAccept: + case CaaCasesFilterType.UnassignedCases: + break; + } + this.form.updateValueAndValidity(); + } +} + +interface CasesFilterForm { + filterOption: FormControl; + assigneePerson: FormControl; + caseReferenceNumber: FormControl; +} diff --git a/src/cases/components/cases-results-table/cases-results-table.component.html b/src/cases/components/cases-results-table/cases-results-table.component.html new file mode 100644 index 000000000..8342c913d --- /dev/null +++ b/src/cases/components/cases-results-table/cases-results-table.component.html @@ -0,0 +1,61 @@ + + +
+ This view has not been configured for the case type. +
+
+
+
+

+ Showing {{ getFirstResult() }} to + {{ getLastResult() }} of + {{ getTotalResults() }} {{ currentCaseType }} cases +

+
+

+ Select any {{ currentCaseType }} cases you want to + manage case sharing for. +

+ + +
+ + +
+
+
diff --git a/src/cases/components/cases-results-table/cases-results-table.component.scss b/src/cases/components/cases-results-table/cases-results-table.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/cases/components/cases-results-table/cases-results-table.component.spec.ts b/src/cases/components/cases-results-table/cases-results-table.component.spec.ts new file mode 100644 index 000000000..f4447ed40 --- /dev/null +++ b/src/cases/components/cases-results-table/cases-results-table.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CasesResultsTableComponent } from './cases-results-table.component'; + +describe('CasesResultsTableComponent', () => { + let component: CasesResultsTableComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CasesResultsTableComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CasesResultsTableComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/cases/components/cases-results-table/cases-results-table.component.ts b/src/cases/components/cases-results-table/cases-results-table.component.ts new file mode 100644 index 000000000..5c857f2d9 --- /dev/null +++ b/src/cases/components/cases-results-table/cases-results-table.component.ts @@ -0,0 +1,155 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core'; +import { MatTabGroup } from '@angular/material/tabs'; +import { TableConfig } from '@hmcts/ccd-case-ui-toolkit'; +import { SubNavigation } from '@hmcts/rpx-xui-common-lib'; +import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; + +import * as fromStore from '../../store'; +import { CaaCases } from 'api/caaCases/interfaces'; + +@Component({ + selector: 'app-cases-results-table', + templateUrl: './cases-results-table.component.html', + styleUrls: ['./cases-results-table.component.scss'] +}) +export class CasesResultsTableComponent { + private _allCaseTypes: SubNavigation[]; + private _cases: any; // can we type this? + + @Input() set allCaseTypes(value: SubNavigation[]) { + this._allCaseTypes = value; + this.fixCurrentTab(this._allCaseTypes); + } + + @Input() set casesConfig(value: CaaCases) { + if (value){ + this.tableConfig = value; + this.setTableConfig(this.tableConfig); + } + } + + get cases(): SubNavigation[] { + return this._cases; + } + + @Input() set cases(value: any) { + if (value){ + this._cases = value; + } + } + + @Input() paginationPageSize: number = 25; + @Input() shareButtonText = 'Share case'; + + @Output() public caseSelected = new EventEmitter(); + @Output() public pageChanged = new EventEmitter(); + @Output() public shareButtonClicked = new EventEmitter(); + + // Needed for the tab group + public navItems: any[]; + public currentPageNo: number; + public totalCases: number = 0; + public tableConfig: TableConfig; + public currentCaseType: string; + + public casesError$: Observable; + public noCasesFoundMessage = ''; + public enableShareButton = false; + + public selectedCases: any[] = []; + public selectedAssignedCases: any[] = []; + public selectedUnassignedCases: any[] = []; + + @ViewChild('tabGroup') public tabGroup: MatTabGroup; + + /** + * + */ + constructor(private readonly store: Store,) { + + } + + public tabChanged(event: { tab: { textLabel: string }}): void { + this.totalCases = this.navItems.find((data) => data.text === event.tab.textLabel) + ? this.navItems.find((data) => data.text === event.tab.textLabel).total + : 0; + this.setTabItems(event.tab.textLabel, true); + } + + public resetPaginationParameters(): void { + this.currentPageNo = 1; + } + + public hasResults(): any { + return this.totalCases; + } + + public getFirstResult(): number { + return ((this.currentPageNo - 1) * this.paginationPageSize) + 1; + } + + public getLastResult(): number { + const count = ((this.currentPageNo) * this.paginationPageSize); + return count >= this.totalCases ? this.totalCases : count < this.totalCases ? count : 1; + } + + public getTotalResults(): number { + return this.totalCases; + } + + public setTableConfig(config: TableConfig): void { + if (config !== null) { + this.tableConfig = { + idField: config.idField, + columnConfigs: config.columnConfigs + }; + } + } + + public onCaseSelection(selectedCases: any[]): void { + this.caseSelected.emit(selectedCases); + this.enableShareButton = selectedCases.length > 0; + } + + public onPaginationHandler(pageNo: number): void { + this.currentPageNo = pageNo; + this.pageChanged.emit(pageNo); + } + + public onShareButtonClicked(): void { + // this.store.dispatch(new fromStore.AddShareUnassignedCases({ + // sharedCases: converters.toShareCaseConverter(this.selectedUnassignedCases, this.currentCaseType) + // })); + // TODO: emit this action + this.shareButtonClicked.emit(); + } + + private fixCurrentTab(items: any): void { + this.navItems = items; + if (items && items.length > 0) { + this.totalCases = items[0].total ? items[0].total : 0; + this.setTabItems(items[0].text); + } else { + this.totalCases = 0; + // this.noCasesFoundMessage = this.getNoCasesFoundMessage(); + } + } + + private setTabItems(tabName: string, fromTabChangedEvent?: boolean): void { + this.resetPaginationParameters(); + // if (this.caaCasesPageType === CaaCasesPageType.UnassignedCases) { + // this.store.pipe(select(fromStore.getAllUnassignedCases)); + // } else { + // this.store.pipe(select(fromStore.getAllAssignedCases)); + // } + // this.shareAssignedCases$ = this.store.pipe(select(fromStore.getShareAssignedCaseListState)); + // this.shareUnassignedCases$ = this.store.pipe(select(fromStore.getShareUnassignedCaseListState)); + this.currentCaseType = tabName; + if (!fromTabChangedEvent && this.tabGroup) { + this.tabGroup.selectedIndex = 0; + } + // this.loadDataFromStore(); + } +} diff --git a/src/cases/components/index.ts b/src/cases/components/index.ts new file mode 100644 index 000000000..e55c65f6c --- /dev/null +++ b/src/cases/components/index.ts @@ -0,0 +1,10 @@ +import { CasesFilterComponent } from './cases-filter/cases-filter.component'; +import { CasesResultsTableComponent } from './cases-results-table/cases-results-table.component'; + +export const components: any[] = [ + CasesFilterComponent, + CasesResultsTableComponent +]; + +export * from './cases-filter/cases-filter.component'; +export * from './cases-results-table/cases-results-table.component'; diff --git a/src/cases/containers/case-share-complete/case-share-complete.component.html b/src/cases/containers/case-share-complete/case-share-complete.component.html new file mode 100644 index 000000000..e31e795c2 --- /dev/null +++ b/src/cases/containers/case-share-complete/case-share-complete.component.html @@ -0,0 +1,68 @@ +
+
+
+
+
+
+

+ Your selected cases have been updated +

+
+

What happens next

+

The people you added can now access and update the selected cases from their case list.

+

If you removed someone from a case, they cannot access the case anymore.

+

If you've shared one or more cases, your colleagues will now be able to access them from their case list.

+
+

To continue managing case sharing for other users or case types:

+ Go to assigned cases list
+ Go to unassigned cases list +
+
+
+
+
+
+
+
+

Sorry, there is a problem


+

We couldn't share or remove access to the following cases:

+

We couldn't share access to the following cases:

+
+
+

{{ acase.caseTitle }}

+
+
+ {{ acase.caseId }} +
+
+ + + + + + + + + + + + + + + + + + + + +
NameEmail addressActions
{{ user.firstName + ' ' + user.lastName }}{{ user.email }} + To be added +
{{ user.firstName + ' ' + user.lastName }}{{ user.email }} + To be removed +
+
+
+

Access to all your other selected cases was shared or removed.

+

Access to all your other selected cases was shared.

+

Try again or get help if you're still having problems

+
diff --git a/src/cases/containers/case-share-complete/case-share-complete.component.scss b/src/cases/containers/case-share-complete/case-share-complete.component.scss new file mode 100644 index 000000000..6b852b331 --- /dev/null +++ b/src/cases/containers/case-share-complete/case-share-complete.component.scss @@ -0,0 +1,11 @@ +.complete_banner { + text-align: center; + padding: 30px 0 30px 0; + margin-top: 75px; +} +.govuk-table-column-header { + width: 45%; +} +.govuk-table-column-actions { + width: 10%; +} diff --git a/src/cases/containers/case-share-complete/case-share-complete.component.spec.ts b/src/cases/containers/case-share-complete/case-share-complete.component.spec.ts new file mode 100644 index 000000000..51c760c11 --- /dev/null +++ b/src/cases/containers/case-share-complete/case-share-complete.component.spec.ts @@ -0,0 +1,213 @@ +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute, Router } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { FeatureToggleService } from '@hmcts/rpx-xui-common-lib'; +import { Store } from '@ngrx/store'; +import { provideMockStore } from '@ngrx/store/testing'; +import { of } from 'rxjs'; +import { CaaCasesPageType } from '../../models/caa-cases.enum'; +import { CaaCasesState } from '../../store/reducers'; +import { CaseShareCompleteComponent } from './case-share-complete.component'; + +describe('CaseShareCompleteComponent', () => { + let component: CaseShareCompleteComponent; + let fixture: ComponentFixture; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let store: Store; + const mockFeatureToggleService = jasmine.createSpyObj('FeatureToggleService', ['getValue']); + let router: Router; + const mockRoute = { + snapshot: { + params: { + pageType: CaaCasesPageType.AssignedCases + } + } + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + schemas: [CUSTOM_ELEMENTS_SCHEMA], + declarations: [CaseShareCompleteComponent], + imports: [RouterTestingModule], + providers: [ + provideMockStore(), + { provide: FeatureToggleService, useValue: mockFeatureToggleService }, + { provide: ActivatedRoute, useValue: mockRoute } + ] + }).compileComponents(); + store = TestBed.inject(Store); + router = TestBed.inject(Router); + fixture = TestBed.createComponent(CaseShareCompleteComponent); + component = fixture.componentInstance; + mockFeatureToggleService.getValue.and.returnValue(of(true)); + component.shareCases$ = of([{ + caseId: '9417373995765133', + caseTitle: 'Sam Green Vs Williams Lee', + pendingShares: [ + { + idamId: 'u666666', + firstName: 'Kate', + lastName: 'Grant', + email: 'kate.grant@lambbrooks.com' + }] + }]); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should check if pending', () => { + const sharedCases = [{ + caseId: '9417373995765133', + caseTitle: 'Sam Green Vs Williams Lee', + sharedWith: [ + { + idamId: 'u666666', + firstName: 'Kate', + lastName: 'Grant', + email: 'kate.grant@lambbrooks.com' + }], + pendingUnshares: [ + { + idamId: 'u777777', + firstName: 'Nick', + lastName: 'Rodrigues', + email: 'nick.rodrigues@lambbrooks.com' + }] + }]; + component.isLoading = true; + const returnValue = component.checkIfIncomplete(sharedCases); + expect(returnValue).toEqual('PENDING'); + }); + + it('should check if complete', () => { + const sharedCases = [{ + caseId: '9417373995765133', + caseTitle: 'Sam Green Vs Williams Lee', + sharedWith: [ + { + idamId: 'u666666', + firstName: 'Kate', + lastName: 'Grant', + email: 'kate.grant@lambbrooks.com' + }] + }]; + component.isLoading = true; + const returnValue = component.checkIfIncomplete(sharedCases); + expect(returnValue).toEqual('COMPLETE'); + }); + + it('should user access block', () => { + const case1 = { + caseId: '9417373995765133', + caseTitle: 'Sam Green Vs Williams Lee', + sharedWith: [ + { + idamId: 'u666666', + firstName: 'Kate', + lastName: 'Grant', + email: 'kate.grant@lambbrooks.com' + }] + }; + const case2 = { + caseId: '9417373995765133', + caseTitle: 'Sam Green Vs Williams Lee', + pendingShares: [ + { + idamId: 'u666666', + firstName: 'Kate', + lastName: 'Grant', + email: 'kate.grant@lambbrooks.com' + }] + }; + const case3 = { + caseId: '9417373995765133', + caseTitle: 'Sam Green Vs Williams Lee', + pendingUnshares: [ + { + idamId: 'u666666', + firstName: 'Kate', + lastName: 'Grant', + email: 'kate.grant@lambbrooks.com' + }] + }; + expect(component.showUserAccessBlock(case1)).toBeFalsy(); + expect(component.showUserAccessBlock(case2)).toBeTruthy(); + expect(component.showUserAccessBlock(case3)).toBeTruthy(); + }); + + xit('should tidy up shared case if complete', () => { + component.completeScreenMode = 'COMPLETE'; + component.ngOnDestroy(); + expect(component.shareCases.length).toEqual(0); + }); + + it('should see add user info only from case if remove user feature is toggled off', () => { + component.completeScreenMode = 'PENDING'; + component.removeUserFromCaseToggleOn$ = of(false); + fixture.detectChanges(); + const removeUserError = fixture.debugElement.nativeElement.querySelector('#add-user-error'); + expect(removeUserError).toBeTruthy(); + const addAndRemoveUserError = fixture.debugElement.nativeElement.querySelector('#add-and-remove-user-error'); + expect(addAndRemoveUserError).toBeFalsy(); + const removeUserInfo = fixture.debugElement.nativeElement.querySelector('#add-user-info'); + expect(removeUserInfo).toBeTruthy(); + const addAndRemoveUserInfo = fixture.debugElement.nativeElement.querySelector('#add-and-remove-user-info'); + expect(addAndRemoveUserInfo).toBeFalsy(); + }); + + it('should see add and remove user info from case if remove user feature is toggled on', () => { + component.completeScreenMode = 'PENDING'; + component.removeUserFromCaseToggleOn$ = of(true); + fixture.detectChanges(); + const removeUserError = fixture.debugElement.nativeElement.querySelector('#add-user-error'); + expect(removeUserError).toBeFalsy(); + const addAndRemoveUserError = fixture.debugElement.nativeElement.querySelector('#add-and-remove-user-error'); + expect(addAndRemoveUserError).toBeTruthy(); + const removeUserInfo = fixture.debugElement.nativeElement.querySelector('#add-user-info'); + expect(removeUserInfo).toBeFalsy(); + const addAndRemoveUserInfo = fixture.debugElement.nativeElement.querySelector('#add-and-remove-user-info'); + expect(addAndRemoveUserInfo).toBeTruthy(); + }); + + it('should display the correct success text for Assigned Cases', () => { + spyOn(store, 'pipe').and.returnValue(of({})); + spyOnProperty(router, 'url', 'get').and.returnValue('/assigned-cases/case-share-complete'); + component.ngOnInit(); + component.completeScreenMode = 'COMPLETE'; + fixture.detectChanges(); + expect(component.isFromAssignedCasesRoute).toBe(true); + const successTextAssignedCases1 = fixture.debugElement.nativeElement.querySelector('#what-happens-next-added'); + expect(successTextAssignedCases1).toBeTruthy(); + expect(successTextAssignedCases1.textContent).toContain('The people you added'); + const successTextAssignedCases2 = fixture.debugElement.nativeElement.querySelector('#what-happens-next-removed'); + expect(successTextAssignedCases2).toBeTruthy(); + expect(successTextAssignedCases2.textContent).toContain('If you removed someone'); + const successTextUnassignedCases = fixture.debugElement.nativeElement.querySelector('#what-happens-next-shared'); + expect(successTextUnassignedCases).toBeNull(); + }); + + it('should display the correct success text for Unassigned Cases', () => { + spyOn(store, 'pipe').and.returnValue(of({})); + spyOnProperty(router, 'url', 'get').and.returnValue('/unassigned-cases/case-share-complete'); + component.ngOnInit(); + component.completeScreenMode = 'COMPLETE'; + fixture.detectChanges(); + expect(component.isFromAssignedCasesRoute).toBe(false); + const successTextAssignedCases1 = fixture.debugElement.nativeElement.querySelector('#what-happens-next-added'); + expect(successTextAssignedCases1).toBeNull(); + const successTextAssignedCases2 = fixture.debugElement.nativeElement.querySelector('#what-happens-next-removed'); + expect(successTextAssignedCases2).toBeNull(); + const successTextUnassignedCases = fixture.debugElement.nativeElement.querySelector('#what-happens-next-shared'); + expect(successTextUnassignedCases).toBeTruthy(); + expect(successTextUnassignedCases.textContent).toContain('If you\'ve shared'); + }); + + afterEach(() => { + fixture.destroy(); + }); +}); diff --git a/src/cases/containers/case-share-complete/case-share-complete.component.ts b/src/cases/containers/case-share-complete/case-share-complete.component.ts new file mode 100644 index 000000000..d70eca271 --- /dev/null +++ b/src/cases/containers/case-share-complete/case-share-complete.component.ts @@ -0,0 +1,90 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FeatureToggleService } from '@hmcts/rpx-xui-common-lib'; +import { SharedCase } from '@hmcts/rpx-xui-common-lib/lib/models/case-share.model'; +import { select, Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import { CaaCasesPageType } from '../../models/caa-cases.enum'; +import * as fromCasesFeature from '../../store'; +import * as fromCaseList from '../../store/reducers'; + +@Component({ + selector: 'app-exui-case-share-complete', + templateUrl: './case-share-complete.component.html', + styleUrls: ['case-share-complete.component.scss'] +}) +export class CaseShareCompleteComponent implements OnInit, OnDestroy { + public shareCases$: Observable; + public shareCases: SharedCase[]; + public newShareCases$: Observable; + public newShareCases: SharedCase[]; + public shareCaseState$: Observable; + public pageType: string; + public isLoading: boolean; + public completeScreenMode: string; + public removeUserFromCaseToggleOn$: Observable; + public isFromAssignedCasesRoute: boolean = false; + + constructor( + private readonly store: Store, + private readonly featureToggleService: FeatureToggleService, + private readonly route: ActivatedRoute, + private readonly router: Router + ) { + this.pageType = this.route.snapshot.params.pageType; + } + + public ngOnInit(): void { + this.shareCaseState$ = this.store.pipe(select(fromCasesFeature.getCaseShareState)); + this.shareCaseState$.subscribe((state) => this.isLoading = state.loading); + + this.shareCases$ = this.pageType === CaaCasesPageType.UnassignedCases + ? this.store.pipe(select(fromCasesFeature.getShareUnassignedCaseListState)) + : this.store.pipe(select(fromCasesFeature.getShareAssignedCaseListState)); + this.shareCases$.subscribe((shareCases) => this.shareCases = shareCases); + + if (this.pageType === CaaCasesPageType.UnassignedCases) { + this.store.dispatch(new fromCasesFeature.AssignUsersToUnassignedCase(this.shareCases)); + this.newShareCases$ = this.store.pipe(select(fromCasesFeature.getShareUnassignedCaseListState)); + } else { + this.store.dispatch(new fromCasesFeature.AssignUsersToAssignedCase(this.shareCases)); + this.newShareCases$ = this.store.pipe(select(fromCasesFeature.getShareAssignedCaseListState)); + } + + this.newShareCases$.subscribe((shareCases) => { + this.completeScreenMode = this.checkIfIncomplete(shareCases); + this.newShareCases = shareCases; + }); + + this.removeUserFromCaseToggleOn$ = this.featureToggleService.getValue('remove-user-from-case-mo', false); + this.isFromAssignedCasesRoute = this.router.url.startsWith('/assigned-cases'); + } + + public ngOnDestroy(): void { + if (this.completeScreenMode === 'COMPLETE') { + if (this.pageType === CaaCasesPageType.UnassignedCases) { + this.store.dispatch(new fromCasesFeature.ResetUnassignedCaseSelection()); + } else { + this.store.dispatch(new fromCasesFeature.ResetAssignedCaseSelection()); + } + } + } + + public checkIfIncomplete(shareCases: SharedCase[]): string { + if (this.isLoading) { + if (shareCases.some((aCase) => aCase.pendingShares && aCase.pendingShares.length > 0) + || shareCases.some((aCase) => aCase.pendingUnshares && aCase.pendingUnshares.length > 0)) { + return 'PENDING'; + } + return 'COMPLETE'; + } + } + + public showUserAccessBlock(aCase: SharedCase): boolean { + if ((aCase.pendingShares && aCase.pendingShares.length > 0) + || (aCase.pendingUnshares && aCase.pendingUnshares.length > 0)) { + return true; + } + return false; + } +} diff --git a/src/cases/containers/case-share-confirm/case-share-confirm.component.html b/src/cases/containers/case-share-confirm/case-share-confirm.component.html new file mode 100644 index 000000000..16f0afca5 --- /dev/null +++ b/src/cases/containers/case-share-confirm/case-share-confirm.component.html @@ -0,0 +1,10 @@ + + + + diff --git a/src/cases/containers/case-share-confirm/case-share-confirm.component.scss b/src/cases/containers/case-share-confirm/case-share-confirm.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/cases/containers/case-share-confirm/case-share-confirm.component.spec.ts b/src/cases/containers/case-share-confirm/case-share-confirm.component.spec.ts new file mode 100644 index 000000000..0eb8c6eb8 --- /dev/null +++ b/src/cases/containers/case-share-confirm/case-share-confirm.component.spec.ts @@ -0,0 +1,63 @@ +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { ActivatedRoute, Router } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { Store } from '@ngrx/store'; +import { provideMockStore } from '@ngrx/store/testing'; +import { CaaCasesPageType } from '../../models/caa-cases.enum'; +import { CaaCasesState } from '../../store/reducers'; +import { CaseShareConfirmComponent } from './case-share-confirm.component'; + +describe('CaseShareConfirmComponent', () => { + let component: CaseShareConfirmComponent; + let fixture: ComponentFixture; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let store: Store; + const mockRoute = { + snapshot: { + params: { + pageType: CaaCasesPageType.AssignedCases + } + } + }; + const mockRouter = { + url: '/assigned-cases' + }; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [RouterTestingModule], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + declarations: [CaseShareConfirmComponent], + providers: [ + provideMockStore(), + { provide: Router, useValue: mockRouter }, + { provide: ActivatedRoute, useValue: mockRoute } + ] + }).compileComponents(); + })); + + beforeEach(() => { + store = TestBed.inject(Store); + fixture = TestBed.createComponent(CaseShareConfirmComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + afterEach(() => { + fixture.destroy(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should set correct fnTitle, backLink, changeLink and completeLink for assigned cases', () => { + fixture.detectChanges(); + expect(component.fnTitle).toEqual('Manage case sharing'); + expect(component.backLink).toEqual('/assigned-cases/case-share'); + expect(component.changeLink).toEqual('/assigned-cases/case-share'); + expect(component.completeLink).toEqual('/assigned-cases/case-share-complete/assigned-cases'); + }); +}); diff --git a/src/cases/containers/case-share-confirm/case-share-confirm.component.ts b/src/cases/containers/case-share-confirm/case-share-confirm.component.ts new file mode 100644 index 000000000..85aed976f --- /dev/null +++ b/src/cases/containers/case-share-confirm/case-share-confirm.component.ts @@ -0,0 +1,51 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { SharedCase } from '@hmcts/rpx-xui-common-lib/lib/models/case-share.model'; +import { select, Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import { CaaCasesPageType } from '../../models/caa-cases.enum'; +import * as fromCasesFeature from '../../store'; +import * as fromCaseList from '../../store/reducers'; + +@Component({ + selector: 'app-exui-case-share-confirm', + templateUrl: './case-share-confirm.component.html', + styleUrls: ['./case-share-confirm.component.scss'] +}) +export class CaseShareConfirmComponent implements OnInit { + public shareCases$: Observable; + public shareCases: SharedCase[]; + public url: string; + public pageType: string; + public fnTitle: string; + public backLink: string; + public changeLink: string; + public completeLink: string; + + constructor(private readonly store: Store, + private readonly route: ActivatedRoute, + private readonly router: Router) { + this.url = this.router?.url; + this.pageType = this.route.snapshot.params.pageType; + } + + public ngOnInit(): void { + // Set fnTitle, backLink, changeLink (these two links are the same as each other) and confirmLink depending on + // whether navigation is via the Unassigned Cases or Assigned Cases page + if (this.url.startsWith('/unassigned-cases')) { + this.fnTitle = 'Share a case'; + this.backLink = '/unassigned-cases/case-share'; + this.changeLink = '/unassigned-cases/case-share'; + this.completeLink = `/unassigned-cases/case-share-complete/${this.pageType}`; + } else { + this.fnTitle = 'Manage case sharing'; + this.backLink = '/assigned-cases/case-share'; + this.changeLink = '/assigned-cases/case-share'; + this.completeLink = `/assigned-cases/case-share-complete/${this.pageType}`; + } + this.shareCases$ = this.pageType === CaaCasesPageType.UnassignedCases + ? this.store.pipe(select(fromCasesFeature.getShareUnassignedCaseListState)) + : this.store.pipe(select(fromCasesFeature.getShareAssignedCaseListState)); + this.shareCases$.subscribe((shareCases) => this.shareCases = shareCases); + } +} diff --git a/src/cases/containers/case-share/case-share.component.html b/src/cases/containers/case-share/case-share.component.html new file mode 100644 index 000000000..215e07533 --- /dev/null +++ b/src/cases/containers/case-share/case-share.component.html @@ -0,0 +1,15 @@ +Back + + diff --git a/src/cases/containers/case-share/case-share.component.scss b/src/cases/containers/case-share/case-share.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/cases/containers/case-share/case-share.component.spec.ts b/src/cases/containers/case-share/case-share.component.spec.ts new file mode 100644 index 000000000..efceb84e8 --- /dev/null +++ b/src/cases/containers/case-share/case-share.component.spec.ts @@ -0,0 +1,69 @@ +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { FeatureToggleService } from '@hmcts/rpx-xui-common-lib'; +import { Store } from '@ngrx/store'; +import { provideMockStore } from '@ngrx/store/testing'; +import { of } from 'rxjs'; +import { CaaCasesState } from '../../store/reducers'; +import { CaseShareComponent } from './case-share.component'; + +describe('CaseShareComponent', () => { + let component: CaseShareComponent; + let fixture: ComponentFixture; + + let mockStore: Store; + let dispatchSpy: jasmine.Spy; + const mockFeatureToggleService = jasmine.createSpyObj('FeatureToggleService', ['getValue']); + + const sharedCases = [{ + caseId: '9417373995765133', + caseTitle: 'Sam Green Vs Williams Lee', + sharedWith: [ + { + idamId: 'u666666', + firstName: 'Kate', + lastName: 'Grant', + email: 'kate.grant@lambbrooks.com' + }] + }]; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [RouterTestingModule], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + declarations: [CaseShareComponent], + providers: [ + provideMockStore(), + { + provide: FeatureToggleService, + useValue: mockFeatureToggleService + } + ] + }).compileComponents(); + mockStore = TestBed.inject(Store); + mockFeatureToggleService.getValue.and.returnValue(of(true)); + dispatchSpy = spyOn(mockStore, 'dispatch'); + fixture = TestBed.createComponent(CaseShareComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should deselect case', () => { + component.deselect(sharedCases); + expect(dispatchSpy).toHaveBeenCalled(); + }); + + it('should synchronize to store', () => { + component.synchronizeStore(sharedCases); + expect(dispatchSpy).toHaveBeenCalled(); + }); + + afterEach(() => { + fixture.destroy(); + }); +}); diff --git a/src/cases/containers/case-share/case-share.component.ts b/src/cases/containers/case-share/case-share.component.ts new file mode 100644 index 000000000..2217c1d83 --- /dev/null +++ b/src/cases/containers/case-share/case-share.component.ts @@ -0,0 +1,103 @@ +import { Component, OnInit } from '@angular/core'; +import { FeatureToggleService } from '@hmcts/rpx-xui-common-lib'; +import { SharedCase } from '@hmcts/rpx-xui-common-lib/lib/models/case-share.model'; +import { UserDetails } from '@hmcts/rpx-xui-common-lib/lib/models/user-details.model'; +import { RouterReducerState } from '@ngrx/router-store'; +import { select, Store } from '@ngrx/store'; +import { initAll } from 'govuk-frontend'; +import { Observable } from 'rxjs'; +import { getRouterState, RouterStateUrl } from '../../../app/store/reducers'; +import { CaaCasesPageType } from '../../models/caa-cases.enum'; +import * as fromCasesFeature from '../../store'; +import { LoadShareAssignedCases, LoadShareUnassignedCases, LoadUserFromOrgForCase } from '../../store/actions'; +import * as fromCaseList from '../../store/reducers'; + +@Component({ + selector: 'app-exui-case-share', + templateUrl: './case-share.component.html', + styleUrls: ['./case-share.component.scss'] +}) +export class CaseShareComponent implements OnInit { + public routerState$: Observable>; + public init: boolean; + public pageType: string; + public shareCases$: Observable; + public shareCases: SharedCase[]; + public orgUsers$: Observable; + public removeUserFromCaseToggleOn$: Observable; + public backLink: string; + public fnTitle: string; + public title: string; + public confirmLink: string; + public addUserLabel: string; + public showRemoveUsers: boolean = false; + + constructor( + public store: Store, + public featureToggleService: FeatureToggleService + ) {} + + public ngOnInit(): void { + this.routerState$ = this.store.pipe(select(getRouterState)); + this.routerState$.subscribe((router) => { + this.init = router.state.queryParams.init; + this.pageType = router.state.queryParams.pageType; + // Set backLink, fnTitle, title, confirmLink, addUserLabel, and showRemoveUsers depending on whether navigation + // is via the Unassigned Cases or Assigned Cases page + const url = router.state.url.substring(0, router.state.url.indexOf('/', 1)); + // Set backLink and confirmLink only if the URL is either "/unassigned-cases" or "/assigned-cases" + if (url === '/unassigned-cases' || url === '/assigned-cases') { + this.backLink = `${url}`; + this.confirmLink = `${url}/case-share-confirm/${this.pageType}`; + } + if (url === '/unassigned-cases') { + this.fnTitle = 'Share a case'; + this.title = 'Add recipient'; + this.addUserLabel = 'Enter email address'; + this.showRemoveUsers = false; + } else if (url === '/assigned-cases') { + this.fnTitle = 'Manage case sharing'; + this.title = 'Manage shared access to a case'; + this.addUserLabel = 'Add people to share access to the selected cases'; + this.showRemoveUsers = true; + } + + this.shareCases$ = this.pageType === CaaCasesPageType.UnassignedCases + ? this.store.pipe(select(fromCasesFeature.getShareUnassignedCaseListState)) + : this.store.pipe(select(fromCasesFeature.getShareAssignedCaseListState)); + this.shareCases$.subscribe((shareCases) => this.shareCases = shareCases); + }); + + this.orgUsers$ = this.store.pipe(select(fromCasesFeature.getOrganisationUsersState)); + if (this.init) { + // call api to retrieve case share users + if (this.pageType === CaaCasesPageType.UnassignedCases) { + this.store.dispatch(new LoadShareUnassignedCases(this.shareCases)); + } else { + this.store.dispatch(new LoadShareAssignedCases(this.shareCases)); + } + // call api to retrieve users in the same organisation + this.store.dispatch(new LoadUserFromOrgForCase()); + } + this.removeUserFromCaseToggleOn$ = this.featureToggleService.getValue('remove-user-from-case-mo', false); + + // initialize javascript for accordion component to enable open/close button + setTimeout(() => initAll(), 1000); + } + + public deselect($event): void { + if (this.pageType === CaaCasesPageType.UnassignedCases) { + this.store.dispatch(new fromCasesFeature.DeleteAShareUnassignedCase($event)); + } else { + this.store.dispatch(new fromCasesFeature.DeleteAShareAssignedCase($event)); + } + } + + public synchronizeStore($event): void { + if (this.pageType === CaaCasesPageType.UnassignedCases) { + this.store.dispatch(new fromCasesFeature.SynchronizeStateToStoreUnassignedCases($event)); + } else { + this.store.dispatch(new fromCasesFeature.SynchronizeStateToStoreAssignedCases($event)); + } + } +} diff --git a/src/cases/containers/case-share/index.ts b/src/cases/containers/case-share/index.ts new file mode 100644 index 000000000..80856c0f6 --- /dev/null +++ b/src/cases/containers/case-share/index.ts @@ -0,0 +1,45 @@ +import { Params } from '@angular/router'; +import * as fromNgrxRouter from '@ngrx/router-store'; +import { + ActionReducerMap, + createFeatureSelector, + createSelector +} from '@ngrx/store'; + +// import * as fromAuth from './auth.reducer'; + +export interface RouterStateUrl { + url: string; + queryParams: Params; + params: Params; +} + +export interface State { + routerX: fromNgrxRouter.RouterReducerState; + // auth: fromAuth.AuthState; +} + +export const reducers: ActionReducerMap = { + routerX: fromNgrxRouter.routerReducer + // auth: fromAuth.reducer, +}; + +export const getRouterState = + createFeatureSelector>( + 'routerX' + ); + +export const getRouterStateUrl = createSelector( + getRouterState, + (routerState: fromNgrxRouter.RouterReducerState) => + routerState.state +); + +export const isSomeIdParamValid = createSelector(getRouterState, (routerS) => { + return ( + routerS && + routerS.state && + routerS.state.params && + routerS.state.params.someId + ); +}); diff --git a/src/cases/containers/case-share/test.spec.ts b/src/cases/containers/case-share/test.spec.ts new file mode 100644 index 000000000..1336d3815 --- /dev/null +++ b/src/cases/containers/case-share/test.spec.ts @@ -0,0 +1,120 @@ +import { Component } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { Router, RouterStateSnapshot } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { RouterReducerState, RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store'; +import { Store, StoreModule, combineReducers } from '@ngrx/store'; +import { skip } from 'rxjs/operators'; +import { RouterStateUrl, State, getRouterState, reducers } from './'; + +class CustomRouterStateSerializer implements RouterStateSerializer { + public serialize(routerState: RouterStateSnapshot): RouterStateUrl { + let route = routerState.root; + + while (route.firstChild) { + route = route.firstChild; + } + + const { + url, + root: { queryParams } + } = routerState; + const { params } = route; + + return { url, params, queryParams }; + } +} + +@Component({ + template: '' +}) +class ListMockComponent {} + +describe('Router Selectors', () => { + let store: Store; + let router: Router; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule.withRoutes([ + { + path: 'list/:someId', + component: ListMockComponent + } + ]), + StoreModule.forRoot({ + routerX: combineReducers(reducers) + }), + StoreRouterConnectingModule.forRoot({ + stateKey: 'routerX' + }) + ], + declarations: [ListMockComponent], + providers: [ + { + provide: RouterStateSerializer, + useClass: CustomRouterStateSerializer + } + ] + }); + + store = TestBed.inject(Store); + router = TestBed.inject(Router); + }); + + describe('getRouterStateUrl', () => { + it('should retrieve routerState', (done) => { + const result = { + routerX: { + state: { + url: '/list/123', + params: { someId: '123' }, + queryParams: {} + }, + navigationId: 1 + } as RouterReducerState + }; + router.navigateByUrl('/list/123'); + store + .select(getRouterState) + .pipe(skip(1)) + .subscribe((routerState) => { + // eslint-disable-next-line dot-notation + expect(routerState['routerX']).toEqual(result.routerX); + done(); + }); + + // PRINTS + /** + * + * { router: + * { state: { url: '/list/123', params: {someId: 123}, queryParams: {} }, + * navigationId: 1 }, + * auth: + * { loggedIn: false } } + * + */ + }); + + // it('should retrieve routerStateUrl', () => { + // router.navigateByUrl('/list/123'); + // store.select(getRouterStateUrl).subscribe(value => console.log(value)); + + // PRINTS + // undefined + // TypeError: Cannot read property 'state' of undefined + + // Since the state object returned by getRouterState doesn't expose the router chunk directly but an object wrapping router + auth, then of course, state is not present where the selector tries to reach it. + // }); + + // it('should retrieve isSomeIdParamValid', () => { + // router.navigateByUrl('/list/123'); + // store.select(isSomeIdParamValid).subscribe(value => console.log(value)); + + // PRINTS + // undefined + // TypeError: Cannot read property 'state' of undefined + // }); + }); +}); diff --git a/src/cases/containers/cases/cases.component.html b/src/cases/containers/cases/cases.component.html new file mode 100644 index 000000000..8712d3cc6 --- /dev/null +++ b/src/cases/containers/cases/cases.component.html @@ -0,0 +1,57 @@ +
+ {{ + (selectedOrganisation$ | async)?.name + }} +

{{ pageTitle }}

+ +
+ +
+ + + + +
+ +
+
diff --git a/src/cases/containers/cases/cases.component.scss b/src/cases/containers/cases/cases.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/cases/containers/cases/cases.component.spec.ts b/src/cases/containers/cases/cases.component.spec.ts new file mode 100644 index 000000000..e71f6289f --- /dev/null +++ b/src/cases/containers/cases/cases.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CasesComponent } from './cases.component'; + +describe('CasesComponent', () => { + let component: CasesComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CasesComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CasesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/cases/containers/cases/cases.component.ts b/src/cases/containers/cases/cases.component.ts new file mode 100644 index 000000000..8e95d69b7 --- /dev/null +++ b/src/cases/containers/cases/cases.component.ts @@ -0,0 +1,271 @@ +import { Component, OnInit } from '@angular/core'; +import { CaaCasesService } from 'src/cases/services'; + +import * as organisationStore from '../../../organisation/store'; +import * as userStore from '../../../users/store'; +import * as caaCasesStore from '../../store'; +import { Store, select } from '@ngrx/store'; +import { OrganisationDetails } from 'src/models/organisation.model'; +import { Observable } from 'rxjs'; +import { Router } from '@angular/router'; +import { ErrorMessage } from 'src/shared/models/error-message.model'; +import { SubNavigation, User } from '@hmcts/rpx-xui-common-lib'; +import { SelectedCaseFilter } from 'src/cases/models/selected-case-filter.model'; +import { CaaCases, CaaCasesSessionState, CaaCasesSessionStateValue } from 'src/cases/models/caa-cases.model'; +import { CaaCasesFilterType, CaaCasesPageType } from 'src/cases/models/caa-cases.enum'; +import { HttpErrorResponse } from '@angular/common/http'; +import * as converters from '../../converters/case-converter'; + +@Component({ + selector: 'app-cases', + templateUrl: './cases.component.html', + styleUrls: ['./cases.component.scss'] +}) +export class CasesComponent implements OnInit { + // private caaCasesPageType = 'all-cases-filter'; // todo: this will be all cases since it's a merge of both assigned and unassigned cases + private caaCasesPageType = CaaCasesPageType.UnassignedCases; // todo: this will be all cases since it's a merge of both assigned and unassigned cases + + public selectedOrganisation$: Observable; + public selectedOrganisationUsers$: Observable; + + public pageTitle = 'Cases'; + public showFilterSection = false; + public errorMessages: ErrorMessage[] = []; + public sessionStateValue: CaaCasesSessionStateValue; + + public selectedFilterType: CaaCasesFilterType = CaaCasesFilterType.None; + public selectedFilterValue: string = null; + public selectedCaseType: string; + + // for the results table + public allCaseTypes: SubNavigation[] = []; + public currentPageNo: number = 1; + public paginationPageSize: number = 25; + public casesConfig: CaaCases; + public cases: any; // can we type this? + public casesError$: Observable; + public caseResultsTableShareButtonText: string = 'Share cases'; + private selectedCases: any[] = []; + + constructor(private readonly caaCasesStore: Store, + private readonly organisationStore: Store, + private readonly userStore: Store, + private readonly router: Router, + private readonly service: CaaCasesService) { + } + + public ngOnInit(): void { + // Retrieve session state to check and pre-populate the previous state if any + this.retrieveSessionState(); + // if session state is found, then filter component will emit filter values to avoid double query + if (!this.sessionStateValue) { + this.loadCaseTypes(); + } + + // Load selected organisation details from store + this.organisationStore.dispatch(new organisationStore.LoadOrganisation()); + this.selectedOrganisation$ = this.organisationStore.pipe(select(organisationStore.getOrganisationSel)); + + // Load users of selected organisation from store + this.userStore.dispatch(new userStore.LoadAllUsersNoRoleData()); + this.selectedOrganisationUsers$ = this.userStore.pipe(select(userStore.getGetUserList)); + + // TODO: clean this up to get all cases + this.caaCasesStore.pipe(select(caaCasesStore.getAllUnassignedCases)).subscribe((config: CaaCases) => { + if (config){ + this.casesConfig = config; + } + }); + this.caaCasesStore.pipe(select(caaCasesStore.getAllUnassignedCaseData)).subscribe((items) => { + if (items){ + this.cases = items; + } + }); + this.caaCasesStore.pipe(select(caaCasesStore.getAllAssignedCases)).subscribe((config: CaaCases) => { + if (config){ + this.casesConfig = config; + } + }); + this.caaCasesStore.pipe(select(caaCasesStore.getAllAssignedCaseData)).subscribe((items) => { + if (items){ + this.cases = items; + } + }); + + this.casesError$ = this.caaCasesStore.pipe(select(caaCasesStore.getAllUnassignedCasesError)); + this.casesError$ = this.caaCasesStore.pipe(select(caaCasesStore.getAllAssignedCasesError)); + } + + /** + * This will load all case types based on the selected filter type and value + */ + public loadCaseTypes() { + this.caaCasesStore.dispatch(new caaCasesStore.LoadCaseTypes({ + caaCasesPageType: this.caaCasesPageType, + caaCasesFilterType: this.selectedFilterType, + caaCasesFilterValue: this.selectedFilterValue }) + ); + this.caaCasesStore.pipe(select(caaCasesStore.getAllCaseTypes)).subscribe((items) => { + this.allCaseTypes = items; + if (this.allCaseTypes && this.allCaseTypes.length > 0) { + this.selectedCaseType = this.allCaseTypes[0].text; + this.loadCaseData(); + } + }); + } + + /** + * This will load cases (i.e. unassigned or assigned) from the API with the selected filter type and value + */ + public loadCaseData(){ + if (this.allCaseTypes && this.allCaseTypes.length > 0) { + if (this.caaCasesPageType === CaaCasesPageType.AssignedCases) { + this.caaCasesStore.dispatch(new caaCasesStore.LoadAssignedCases({ + caseType: this.selectedCaseType, + pageNo: this.currentPageNo, + pageSize: this.paginationPageSize, + caaCasesFilterType: this.selectedFilterType, + caaCasesFilterValue: this.selectedFilterValue + })); + } + if (this.caaCasesPageType === CaaCasesPageType.UnassignedCases){ + this.caaCasesStore.dispatch(new caaCasesStore.LoadUnassignedCases({ + caseType: this.selectedCaseType, + pageNo: this.currentPageNo, + pageSize: this.paginationPageSize, + caaCasesFilterType: this.selectedFilterType, + caaCasesFilterValue: this.selectedFilterValue + })); + } + } + } + + public onSelectedFilter(selectedFilter: SelectedCaseFilter): void { + console.log('Selected filter:', selectedFilter); + this.selectedFilterType = selectedFilter.filterType; + this.selectedFilterValue = selectedFilter.filterValue; + + if (selectedFilter.filterType === CaaCasesFilterType.None) { + this.removeSessionState(this.caaCasesPageType); + } else { + this.storeSessionState(selectedFilter); + } + + // load cases types based on filter and value + if (selectedFilter.filterType === CaaCasesFilterType.CaseReferenceNumber) { + // dispatch action to load case by ref number + this.caseResultsTableShareButtonText = 'Accept and assign cases'; + } + if (selectedFilter.filterType === CaaCasesFilterType.CasesAssignedToAUser) { + // dispatch action to load case by assignee name + this.caaCasesPageType = CaaCasesPageType.AssignedCases; + this.caseResultsTableShareButtonText = 'Manage cases'; + } + if (selectedFilter.filterType === CaaCasesFilterType.AllAssignedCases) { + // dispatch action to load all cases + this.caseResultsTableShareButtonText = 'Manage cases'; + } + if (selectedFilter.filterType === CaaCasesFilterType.NewCasesToAccept) { + // dispatch action to load new cases to accept + this.caseResultsTableShareButtonText = 'Accept cases'; + } + if (selectedFilter.filterType === CaaCasesFilterType.UnassignedCases) { + // dispatch action to load unassigned cases + this.caseResultsTableShareButtonText = 'Manage cases'; + } + this.loadCaseTypes(); + } + + public onErrorMessages(errorMessages: ErrorMessage[]): void { + this.errorMessages = errorMessages; + } + + public toggleFilterSection(): void { + this.showFilterSection = !this.showFilterSection; + } + + public isAnyError(): boolean { + return Array.isArray(this.errorMessages) && this.errorMessages.length > 0; + } + + public removeSessionState(key: string): void { + this.service.removeSessionState(key); + } + + public retrieveSessionState(): void { + this.sessionStateValue = this.service.retrieveSessionState(this.caaCasesPageType); + if (this.sessionStateValue) { + this.toggleFilterSection(); + } + } + + public storeSessionState(selectedFilter: SelectedCaseFilter): void { + const sessionStateToUpdate: CaaCasesSessionState = { + key: this.caaCasesPageType, + value: { + filterType: selectedFilter.filterType, + caseReferenceNumber: selectedFilter.filterType === CaaCasesFilterType.CaseReferenceNumber ? selectedFilter.filterValue : null, + assigneeName: selectedFilter.filterType === CaaCasesFilterType.CasesAssignedToAUser ? selectedFilter.filterValue : null + } + }; + this.sessionStateValue = sessionStateToUpdate.value; + this.service.storeSessionState(sessionStateToUpdate); + } + + public onCaseSelected(selectedCases: any[]): void { + // do i need the line below? and remove the selector in ngOnInit + // this.selectedUnassignedCases = selectedCases; + if (this.caaCasesPageType === CaaCasesPageType.AssignedCases){ + this.caaCasesStore.dispatch(new caaCasesStore.SynchronizeStateToStoreAssignedCases( + converters.toShareCaseConverter(selectedCases, CaaCasesPageType.AssignedCases) + )); + } + + if (this.caaCasesPageType === CaaCasesPageType.UnassignedCases){ + this.caaCasesStore.dispatch(new caaCasesStore.SynchronizeStateToStoreUnassignedCases( + converters.toShareCaseConverter(selectedCases, CaaCasesPageType.UnassignedCases) + )); + } + this.selectedCases = selectedCases; + } + + public onPageChanged(pageNo: number): void { + this.currentPageNo = pageNo; + this.loadCaseData(); + } + + onShareButtonClicked($event: void) { + // load cases types based on filter and value + if (this.selectedFilterType === CaaCasesFilterType.CaseReferenceNumber) { + // TODO: need to handle the `new_case` flag + // if returning new case then go to add recipient page + // else if returning non-new case then go to manage case assignments + this.caaCasesStore.dispatch(new caaCasesStore.AddShareAssignedCases({ + sharedCases: converters.toShareCaseConverter(this.selectedCases, this.selectedCaseType) + })); + } + if (this.selectedFilterType === CaaCasesFilterType.CasesAssignedToAUser) { + // todo: go to manage case assignments + this.caaCasesStore.dispatch(new caaCasesStore.AddShareAssignedCases({ + sharedCases: converters.toShareCaseConverter(this.selectedCases, this.selectedCaseType) + } + )); + } + if (this.selectedFilterType === CaaCasesFilterType.AllAssignedCases) { + this.caaCasesStore.dispatch(new caaCasesStore.AddShareAssignedCases({ + sharedCases: converters.toShareCaseConverter(this.selectedCases, this.selectedCaseType) + } + )); + } + if (this.selectedFilterType === CaaCasesFilterType.NewCasesToAccept) { + // dispatch action to load new cases to accept + // if group_access is enabled then go to accept cases page + // else go to add recipient + } + if (this.selectedFilterType === CaaCasesFilterType.UnassignedCases) { + this.caaCasesStore.dispatch(new caaCasesStore.AddShareUnassignedCases({ + sharedCases: converters.toShareCaseConverter(this.selectedCases, this.selectedCaseType) + })); + } + } +} diff --git a/src/cases/containers/index.ts b/src/cases/containers/index.ts new file mode 100644 index 000000000..eb154eea7 --- /dev/null +++ b/src/cases/containers/index.ts @@ -0,0 +1,17 @@ +import { CaseShareCompleteComponent } from './case-share-complete/case-share-complete.component'; +import { CaseShareConfirmComponent } from './case-share-confirm/case-share-confirm.component'; +import { CaseShareComponent } from './case-share/case-share.component'; +import { CasesComponent } from './cases/cases.component'; + +export const containers: any[] = [ + CaseShareComponent, + CaseShareConfirmComponent, + CaseShareCompleteComponent, + CasesComponent +]; + +export * from './case-share-complete/case-share-complete.component'; +export * from './case-share-confirm/case-share-confirm.component'; +export * from './case-share/case-share.component'; +export * from './cases/cases.component'; + diff --git a/src/cases/converters/case-converter.ts b/src/cases/converters/case-converter.ts new file mode 100644 index 000000000..9e0f52bbd --- /dev/null +++ b/src/cases/converters/case-converter.ts @@ -0,0 +1,75 @@ +import { SearchResultViewItem } from '@hmcts/ccd-case-ui-toolkit'; +import { SharedCase } from '@hmcts/rpx-xui-common-lib/lib/models/case-share.model'; + +const BLANK_SPACE: string = ' '; +const EMPTY_SPACE: string = ''; +const VERSUS_SPACE: string = ' Vs '; +export function toShareCaseConverter(selectedCases: any[], theCaseTypeId: string): SharedCase[] { + const sharedCases: SharedCase[] = []; + for (const selectCase of selectedCases) { + const caseTypeId = getValueByPropertyName(selectCase, 'caseType') ? getValueByPropertyName(selectCase, 'caseType') : theCaseTypeId; + const caseTitle = getValueByPropertyName(selectCase, 'case_title'); + const shareCase = { + caseId: selectCase.case_id, + caseTitle: caseTitle ? caseTitle : combineCaseTitleByCaseType(caseTypeId, selectCase), + caseTypeId + }; + sharedCases.push(shareCase); + } + return sharedCases; +} + +export function toSearchResultViewItemConverter(shareCases: SharedCase[]): any[] { + const searchResultViewItems: any[] = []; + for (const shareCase of shareCases) { + const searchResultViewItem = { + case_id: shareCase.caseId, + caseType: shareCase.caseTypeId, + case_title: shareCase.caseTitle + }; + searchResultViewItems.push(searchResultViewItem); + } + return searchResultViewItems; +} + +function getValueByPropertyName(selectCase: SearchResultViewItem, propName: string): any { + return selectCase.hasOwnProperty(propName) ? selectCase[propName] : ''; +} + +function combineCaseTitleByCaseType(caseTypeId: string, selectCase: SearchResultViewItem): string { + if (caseTypeId.includes('FinancialRemedy')) { + const applicantName = getApplicantName(selectCase); + const respondentName = getRespondentName(selectCase); + return applicantName + showVersus(applicantName, respondentName) + respondentName; + } else if (caseTypeId.includes('DIVORCE')) { + const marriagePetitionerName = getValueByPropertyName(selectCase, 'D8PetitionerFirstName') + BLANK_SPACE + getValueByPropertyName(selectCase, 'D8PetitionerLastName'); + const marriageRespondentName = getValueByPropertyName(selectCase, 'D8RespondentFirstName') + BLANK_SPACE + getValueByPropertyName(selectCase, 'D8RespondentLastName'); + return marriagePetitionerName + showVersus(marriagePetitionerName, marriageRespondentName) + marriageRespondentName; + } + return selectCase.case_id; +} + +function getApplicantName(selectCase: SearchResultViewItem) { + return getValueByPropertyName(selectCase, 'applicantFMName') && getValueByPropertyName(selectCase, 'applicantLName') ? + getValueByPropertyName(selectCase, 'applicantFMName') + BLANK_SPACE + getValueByPropertyName(selectCase, 'applicantLName') : + getValueByPropertyName(selectCase, 'applicantLName') ? getValueByPropertyName(selectCase, 'applicantLName') : EMPTY_SPACE; +} + +function getRespondentName(selectCase: SearchResultViewItem) { + let respondentName = getValueByPropertyName(selectCase, 'appRespondentFMName') && getValueByPropertyName(selectCase, 'appRespondentLName') ? + getValueByPropertyName(selectCase, 'appRespondentFMName') + BLANK_SPACE + getValueByPropertyName(selectCase, 'appRespondentLName') : + getValueByPropertyName(selectCase, 'appRespondentLName') ? getValueByPropertyName(selectCase, 'appRespondentLName') : EMPTY_SPACE; + if (!respondentName) { + respondentName = getValueByPropertyName(selectCase, 'respondentFMName') && getValueByPropertyName(selectCase, 'respondentLName') ? + getValueByPropertyName(selectCase, 'respondentFMName') + BLANK_SPACE + getValueByPropertyName(selectCase, 'respondentLName') : + getValueByPropertyName(selectCase, 'respondentLName') ? getValueByPropertyName(selectCase, 'respondentLName') : EMPTY_SPACE; + } + return respondentName; +} + +function showVersus(v1, v2) { + if (v1 && v2) { + return VERSUS_SPACE; + } + return EMPTY_SPACE; +} diff --git a/src/cases/converters/case-converters.spec.ts b/src/cases/converters/case-converters.spec.ts new file mode 100644 index 000000000..6db5a8c32 --- /dev/null +++ b/src/cases/converters/case-converters.spec.ts @@ -0,0 +1,104 @@ +import { SharedCase } from '@hmcts/rpx-xui-common-lib/lib/models/case-share.model'; +import * as converts from './case-converter'; + +describe('case-converter', () => { + describe('toShareCaseConverter()', () => { + it('should convert FinancialRemedy to share case - single case with title', () => { + const selectedCases = [{ + case_id: '1', + case_title: 'OJ VS the people', + applicantFMName: 'James', + applicantLName: 'Priest', + appRespondentFMName: 'Charlotte', + appRespondentLName: 'Godard' + }]; + + const expectedShareCases = [ + { caseId: '1', caseTitle: 'OJ VS the people', caseTypeId: 'FinancialRemedyContested' } + ]; + + const shareCases: SharedCase[] = converts.toShareCaseConverter(selectedCases, 'FinancialRemedyContested'); + expect(shareCases).toEqual(expectedShareCases); + }); + + it('should convert FinancialRemedy to share case - single case without title', () => { + const selectedCases = [{ + case_id: '1', + caseType: 'FinancialRemedyContested', + applicantFMName: 'James', + applicantLName: 'Priest', + appRespondentFMName: 'Charlotte', + appRespondentLName: 'Godard' + }]; + + const expectedShareCases = [ + { caseId: '1', caseTitle: 'James Priest Vs Charlotte Godard', caseTypeId: 'FinancialRemedyContested' } + ]; + + const shareCases: SharedCase[] = converts.toShareCaseConverter(selectedCases, 'FinancialRemedyContested'); + expect(shareCases).toEqual(expectedShareCases); + }); + + it('should convert Divorce to share case - single case without title', () => { + const selectedCases = [{ + case_id: '1', + caseType: 'DIVORCEContested', + D8PetitionerFirstName: 'James', + D8PetitionerLastName: 'Priest', + D8RespondentFirstName: 'Charlotte', + D8RespondentLastName: 'Godard' + }]; + + const expectedShareCases = [ + { caseId: '1', caseTitle: 'James Priest Vs Charlotte Godard', caseTypeId: 'DIVORCEContested' } + ]; + + const shareCases: SharedCase[] = converts.toShareCaseConverter(selectedCases, 'DIVORCEContested'); + expect(shareCases).toEqual(expectedShareCases); + }); + + it('should convert OTHER to share case - single case without title', () => { + const selectedCases = [{ + case_id: '1', + caseType: 'Contested' + }]; + + const expectedShareCases = [ + { caseId: '1', caseTitle: '1', caseTypeId: 'Contested' } + ]; + + const shareCases: SharedCase[] = converts.toShareCaseConverter(selectedCases, 'DIVORCEContested'); + expect(shareCases).toEqual(expectedShareCases); + }); + }); + + describe('toSearchResultViewItemConverter()', () => { + it('should convert to search result view item', () => { + const sharedCases = [ + { caseId: '1', caseTitle: '', caseTypeId: 'FinancialRemedyContested' }, + { caseId: '2', caseTitle: '', caseTypeId: 'FinancialRemedyContested' } + ]; + + const expectedSearchResultViewItem = [{ + case_id: '1', + caseType: 'FinancialRemedyContested', + case_title: '' + }, + { + case_id: '2', + caseType: 'FinancialRemedyContested', + case_title: '' + }]; + + const searchResultViewItem = converts.toSearchResultViewItemConverter(sharedCases); + + expect(searchResultViewItem).toEqual(expectedSearchResultViewItem); + }); + + it('should return empty if no items are passed', () => { + const searchResultViewItem = converts.toSearchResultViewItemConverter([]); + + expect(searchResultViewItem).toEqual([]); + }); + }); +}); diff --git a/src/cases/guards/feature-toggle.guard.ts b/src/cases/guards/feature-toggle.guard.ts new file mode 100644 index 000000000..cae69a7f0 --- /dev/null +++ b/src/cases/guards/feature-toggle.guard.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@angular/core'; +import { CanActivate } from '@angular/router'; +import { select, Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import * as fromRoot from '../../app/store'; + +@Injectable() +export class FeatureToggleAccountGuard implements CanActivate { + constructor(private readonly appStore: Store) {} + + public canActivate(): Observable { + return this.appStore.pipe(select(fromRoot.getCaaNewCasesMenuItemsFeatureIsEnabled)); + } +} diff --git a/src/cases/guards/user-role.guard.ts b/src/cases/guards/user-role.guard.ts new file mode 100644 index 000000000..729a9bdde --- /dev/null +++ b/src/cases/guards/user-role.guard.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@angular/core'; +import { CanActivate } from '@angular/router'; +import { select, Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import * as fromStore from '../../organisation/store'; +import * as fromUserProfile from '../../user-profile/store'; + +@Injectable() +export class RoleGuard implements CanActivate { + constructor(private readonly store: Store) {} + + public canActivate(): Observable { + return this.store.pipe(select(fromUserProfile.getIsUserCaaAdmin)); + } +} diff --git a/src/cases/models/caa-cases.enum.ts b/src/cases/models/caa-cases.enum.ts new file mode 100644 index 000000000..edd89f7ad --- /dev/null +++ b/src/cases/models/caa-cases.enum.ts @@ -0,0 +1,42 @@ +export enum CaaCasesPageType { + AssignedCases = 'assigned-cases', + UnassignedCases = 'unassigned-cases' +} + +export enum CaaCasesShowHideFilterButtonText { + AssignedCasesShow = 'Show assigned cases filter', + AssignedCasesHide = 'Hide assigned cases filter', + UnassignedCasesShow = 'Show unassigned cases filter', + UnassignedCasesHide = 'Hide unassigned cases filter' +} + +export enum CaaCasesFilterType { + AllAssignedCases = 'all-assignees', + CasesAssignedToAUser = 'assignee-name', + CaseReferenceNumber = 'case-reference-number', + NewCasesToAccept = 'new-cases-to-accept', // new enum + UnassignedCases = 'unassigned-cases', // new enum + None = 'none', +} + +export enum CaaCasesPageTitle { + AssignedCases = 'Assigned Cases', + UnassignedCases = 'Unassigned Cases' +} + +export enum CaaCasesFilterHeading { + AssignedCases = 'Filter assigned cases', + UnassignedCases = 'Search for an unassigned case' +} + +export enum CaaCasesFilterErrorMessage { + InvalidCaseReference = 'Enter a valid HMCTS case reference number', + InvalidAssigneeName = 'Enter a valid assignee name' +} + +export enum CaaCasesNoDataMessage { + NoAssignedCases = 'There are no assigned cases available to be shared.', + NoUnassignedCases = 'There are no unassigned cases available to be shared.', + AssignedCasesFilterMessage = 'Try again using a different case reference or assignee.', + UnassignedCasesFilterMessage = 'Try again using a different case reference.' +} diff --git a/src/cases/models/caa-cases.model.ts b/src/cases/models/caa-cases.model.ts new file mode 100644 index 000000000..c35509430 --- /dev/null +++ b/src/cases/models/caa-cases.model.ts @@ -0,0 +1,37 @@ +export interface CaaCasesColumnConfig { + header: string; + key: string; + type: string; +} + +export interface CaaCases { + idField: string; + columnConfigs: CaaCasesColumnConfig []; + data: any[]; +} + +export interface CaseTypesResultsResponse { + total: number; + cases: any[]; + case_types_results?: CaseTypesResults []; +} + +export interface CaseTypesResults { + total: number; + case_type_id: string; +} + +export interface SelectedCases { + [key: string]: string []; +} + +export interface CaaCasesSessionStateValue { + filterType: string; + caseReferenceNumber?: string; + assigneeName?: string; +} + +export interface CaaCasesSessionState { + key: string; + value: CaaCasesSessionStateValue; +} diff --git a/src/cases/models/selected-case-filter.model.ts b/src/cases/models/selected-case-filter.model.ts new file mode 100644 index 000000000..77a3e7548 --- /dev/null +++ b/src/cases/models/selected-case-filter.model.ts @@ -0,0 +1,6 @@ +import { CaaCasesFilterType } from './caa-cases.enum'; + +export interface SelectedCaseFilter { + filterType: CaaCasesFilterType; + filterValue: string; +} diff --git a/src/cases/services/caa-cases.service.spec.ts b/src/cases/services/caa-cases.service.spec.ts new file mode 100644 index 000000000..89a0f1ab7 --- /dev/null +++ b/src/cases/services/caa-cases.service.spec.ts @@ -0,0 +1,101 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; +import { of } from 'rxjs'; +import { CaaCasesFilterType, CaaCasesPageType } from '../models/caa-cases.enum'; +import { CaaCasesSessionState, CaaCasesSessionStateValue } from '../models/caa-cases.model'; +import { CaaCasesService } from './caa-cases.service'; + +describe('CaaCasesService', () => { + let service: CaaCasesService; + let mockSessionStorage: any; + let mockHttp: any; + const sessionStateValue: CaaCasesSessionStateValue = { + filterType: CaaCasesFilterType.CasesAssignedToAUser, + caseReferenceNumber: null, + assigneeName: 'assignee123' + }; + const sessionState: CaaCasesSessionState = { + key: 'assigned-cases', + value: sessionStateValue + }; + + beforeEach(() => { + const store = {}; + mockSessionStorage = { + getItem: (key: string): string => { + return key in store ? store[key] : null; + }, + setItem: (key: string, value: string) => { + store[key] = `${value}`; + } + }; + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [CaaCasesService] + }); + mockHttp = jasmine.createSpyObj('http', ['post']); + mockHttp.post.and.returnValue(of({})); + service = new CaaCasesService(mockHttp); + }); + + it('should getCaaAssignedCases', () => { + service.getCaaCases('caseTypeId1', 1, 10, CaaCasesPageType.AssignedCases, null, null); + expect(mockHttp.post).toHaveBeenCalledWith(`${CaaCasesService.caaCasesUrl}?caseTypeId=caseTypeId1&pageNo=1&pageSize=10&caaCasesPageType=assigned-cases`, null); + }); + + it('should getCaaAssignedCases with filter value', () => { + service.getCaaCases('caseTypeId1', 1, 10, CaaCasesPageType.AssignedCases, CaaCasesFilterType.CaseReferenceNumber, '1111222233334444'); + expect(mockHttp.post).toHaveBeenCalledWith(`${CaaCasesService.caaCasesUrl}?caseTypeId=caseTypeId1&pageNo=1&pageSize=10&caaCasesPageType=assigned-cases&caaCasesFilterType=case-reference-number&caaCasesFilterValue=1111222233334444`, null); + }); + + it('should getCaaUnassignedCases', () => { + service.getCaaCases('caseTypeId1', 1, 10, CaaCasesPageType.UnassignedCases, null, null); + expect(mockHttp.post).toHaveBeenCalledWith(`${CaaCasesService.caaCasesUrl}?caseTypeId=caseTypeId1&pageNo=1&pageSize=10&caaCasesPageType=unassigned-cases`, null); + }); + + it('should getCaaUnassignedCases with filter value', () => { + service.getCaaCases('caseTypeId1', 1, 10, CaaCasesPageType.UnassignedCases, CaaCasesFilterType.CaseReferenceNumber, '1111222233334444'); + expect(mockHttp.post).toHaveBeenCalledWith(`${CaaCasesService.caaCasesUrl}?caseTypeId=caseTypeId1&pageNo=1&pageSize=10&caaCasesPageType=unassigned-cases&caaCasesFilterType=case-reference-number&caaCasesFilterValue=1111222233334444`, null); + }); + + it('should getCaaCaseTypes for assigned cases', () => { + service.getCaaCaseTypes(CaaCasesPageType.AssignedCases, null, null); + expect(mockHttp.post).toHaveBeenCalledWith(`${CaaCasesService.caaCaseTypesUrl}?caaCasesPageType=assigned-cases`, null); + }); + + it('should getCaaCaseTypes for assigned cases with filter value', () => { + service.getCaaCaseTypes(CaaCasesPageType.AssignedCases, CaaCasesFilterType.CaseReferenceNumber, '1111222233334444'); + expect(mockHttp.post).toHaveBeenCalledWith(`${CaaCasesService.caaCaseTypesUrl}?caaCasesPageType=assigned-cases&caaCasesFilterType=case-reference-number&caaCasesFilterValue=1111222233334444`, null); + }); + + it('should getCaaCaseTypes for unassigned cases', () => { + service.getCaaCaseTypes(CaaCasesPageType.UnassignedCases, null, null); + expect(mockHttp.post).toHaveBeenCalledWith(`${CaaCasesService.caaCaseTypesUrl}?caaCasesPageType=unassigned-cases`, null); + }); + + it('should getCaaCaseTypes for unassigned cases with filter value', () => { + service.getCaaCaseTypes(CaaCasesPageType.UnassignedCases, CaaCasesFilterType.CaseReferenceNumber, '1111222233334444'); + expect(mockHttp.post).toHaveBeenCalledWith(`${CaaCasesService.caaCaseTypesUrl}?caaCasesPageType=unassigned-cases&caaCasesFilterType=case-reference-number&caaCasesFilterValue=1111222233334444`, null); + }); + + it('should store session state', () => { + spyOn(window.sessionStorage, 'setItem'); + service.storeSessionState(sessionState); + expect(window.sessionStorage.setItem).toHaveBeenCalledWith('assigned-cases', JSON.stringify(sessionState.value)); + }); + + it('should retrieve session state', () => { + mockSessionStorage.setItem('assigned-cases', JSON.stringify(sessionState.value)); + spyOn(window.sessionStorage, 'getItem').and.callFake(mockSessionStorage.getItem); + const assignedCasesSessionStateValue = service.retrieveSessionState('assigned-cases'); + expect(assignedCasesSessionStateValue).toEqual(sessionState.value); + expect(window.sessionStorage.getItem).toHaveBeenCalledWith('assigned-cases'); + }); + + it('should remove session state', () => { + spyOn(window.sessionStorage, 'removeItem'); + mockSessionStorage.setItem(sessionState.key, sessionState.value); + service.removeSessionState('assigned-cases'); + expect(window.sessionStorage.removeItem).toHaveBeenCalledWith('assigned-cases'); + }); +}); diff --git a/src/cases/services/caa-cases.service.ts b/src/cases/services/caa-cases.service.ts new file mode 100644 index 000000000..3eb4b9fde --- /dev/null +++ b/src/cases/services/caa-cases.service.ts @@ -0,0 +1,54 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { CaaCases, CaaCasesSessionState, CaaCasesSessionStateValue } from '../models/caa-cases.model'; + +@Injectable() +export class CaaCasesService { + public static caaCasesUrl: string = '/api/caaCases'; + public static caaCaseTypesUrl: string = '/api/caaCaseTypes'; + + constructor(private readonly http: HttpClient) { + } + + public getCaaCases( + caseTypeId: string, pageNo: number, pageSize: number, caaCasesPageType: string, caaCasesFilterType: string | null, caaCasesFilterValue: string | null): Observable { + let url = `${CaaCasesService.caaCasesUrl}?caseTypeId=${caseTypeId}&pageNo=${pageNo}&pageSize=${pageSize}&caaCasesPageType=${caaCasesPageType}`; + url += this.getFilterType(caaCasesFilterType); + url += this.getFilterValue(caaCasesFilterValue); + return this.http.post(url, null); + } + + public getCaaCaseTypes(caaCasesPageType: string, caaCasesFilterType: string | null, caaCasesFilterValue: string | null): Observable { + let url = `${CaaCasesService.caaCaseTypesUrl}?caaCasesPageType=${caaCasesPageType}`; + url += this.getFilterType(caaCasesFilterType); + url += this.getFilterValue(caaCasesFilterValue); + return this.http.post(url, null); + } + + public storeSessionState(sessionState: CaaCasesSessionState): void { + window.sessionStorage.setItem(sessionState.key, JSON.stringify(sessionState.value)); + } + + public retrieveSessionState(key: string): CaaCasesSessionStateValue { + return window.sessionStorage.getItem(key) ? JSON.parse(window.sessionStorage.getItem(key)) : null; + } + + public removeSessionState(key: string): void { + window.sessionStorage.removeItem(key); + } + + private getFilterType(caaCasesFilterType: string | null): string { + if (caaCasesFilterType) { + return `&caaCasesFilterType=${caaCasesFilterType}`; + } + return ''; + } + + private getFilterValue(caaCasesFilterValue: string | null): string { + if (caaCasesFilterValue) { + return `&caaCasesFilterValue=${caaCasesFilterValue}`; + } + return ''; + } +} diff --git a/src/cases/services/index.ts b/src/cases/services/index.ts new file mode 100644 index 000000000..77925f021 --- /dev/null +++ b/src/cases/services/index.ts @@ -0,0 +1,10 @@ +import { CaaCasesService } from './caa-cases.service'; +import { CaseShareService } from './share-case.service'; + +export const services: any[] = [ + CaaCasesService, + CaseShareService +]; + +export * from './caa-cases.service'; +export * from './share-case.service'; diff --git a/src/cases/services/share-case.service.spec.ts b/src/cases/services/share-case.service.spec.ts new file mode 100644 index 000000000..8ac638b09 --- /dev/null +++ b/src/cases/services/share-case.service.spec.ts @@ -0,0 +1,54 @@ +import { SharedCase } from '@hmcts/rpx-xui-common-lib/lib/models/case-share.model'; +import { of } from 'rxjs'; +import { CaseShareService } from './share-case.service'; + +describe('CaseShareService', () => { + describe('getUsersFromOrg()', () => { + it('should make a get request to the user details end-point', () => { + const mockHttp = jasmine.createSpyObj('http', ['get']); + mockHttp.get.and.returnValue(of({})); + const service = new CaseShareService(mockHttp); + service.getUsersFromOrg(); + expect(mockHttp.get).toHaveBeenCalledWith('api/caseshare/users'); + }); + }); + + describe('getShareCases()', () => { + it('should get share cases with correct params', () => { + const cases: SharedCase[] = [ + { caseId: '1', caseTitle: '1' }, + { caseId: '2', caseTitle: '2' } + ]; + + const mockHttp = jasmine.createSpyObj('http', ['get']); + mockHttp.get.and.returnValue(of({})); + const service = new CaseShareService(mockHttp); + service.getShareCases(cases); + + const expectedOptions = { + params: { + case_ids: '1,2' + } + }; + expect(mockHttp.get).toHaveBeenCalledWith('api/caseshare/cases', expectedOptions); + }); + + it('should get share case with correct params', () => { + const cases = [ + { caseId: '1' } + ] as SharedCase[]; + + const mockHttp = jasmine.createSpyObj('http', ['get']); + mockHttp.get.and.returnValue(of({})); + const service = new CaseShareService(mockHttp); + service.getShareCases(cases); + + const expectedOptions = { + params: { + case_ids: '1' + } + }; + expect(mockHttp.get).toHaveBeenCalledWith('api/caseshare/cases', expectedOptions); + }); + }); +}); diff --git a/src/cases/services/share-case.service.ts b/src/cases/services/share-case.service.ts new file mode 100644 index 000000000..82183bd43 --- /dev/null +++ b/src/cases/services/share-case.service.ts @@ -0,0 +1,28 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { SharedCase } from '@hmcts/rpx-xui-common-lib/lib/models/case-share.model'; +import { UserDetails } from '@hmcts/rpx-xui-common-lib/lib/models/user-details.model'; +import { Observable } from 'rxjs'; + +@Injectable() +export class CaseShareService { + constructor(private readonly http: HttpClient) {} + + public getUsersFromOrg(): Observable { + return this.http.get('api/caseshare/users'); + } + + public getShareCases(shareCases: SharedCase[]): Observable { + const caseIds = shareCases.map((aCase) => aCase.caseId).join(','); + const options = { + params: { + case_ids: caseIds + } + }; + return this.http.get('api/caseshare/cases', options); + } + + public assignUsersWithCases(sharedCases: SharedCase[]): Observable { + return this.http.post('api/caseshare/case-assignments', { sharedCases }); + } +} diff --git a/src/cases/store/actions/caa-cases.actions.spec.ts b/src/cases/store/actions/caa-cases.actions.spec.ts new file mode 100644 index 000000000..ef002b0c7 --- /dev/null +++ b/src/cases/store/actions/caa-cases.actions.spec.ts @@ -0,0 +1,99 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { CaaCasesPageType } from '../../models/caa-cases.enum'; +import { CaaCases } from '../../models/caa-cases.model'; +import * as fromActions from './caa-cases.actions'; + +describe('Caa actions', () => { + it('load assigned cases action', () => { + const caseType = 'caseTypeId1'; + const pageNo = 1; + const pageSize = 10; + const caaCasesFilterType = null; + const caaCasesFilterValue = null; + const payload = { caseType, pageNo, pageSize, caaCasesFilterType, caaCasesFilterValue }; + const action = new fromActions.LoadAssignedCases(payload); + expect({ ...action }).toEqual({ + payload, + type: fromActions.LOAD_ASSIGNED_CASES + }); + }); + + it('load assigned cases success action', () => { + const payload = {} as CaaCases; + const action = new fromActions.LoadAssignedCasesSuccess(payload); + expect({ ...action }).toEqual({ + payload, + type: fromActions.LOAD_ASSIGNED_CASES_SUCCESS + }); + }); + + it('load assigned cases failure action', () => { + const payload = new HttpErrorResponse({ error: 'assigned cases error' }); + const action = new fromActions.LoadAssignedCasesFailure(payload); + expect({ ...action }).toEqual({ + payload, + type: fromActions.LOAD_ASSIGNED_CASES_FAILURE + }); + }); + + it('load unassigned cases action', () => { + const caseType = 'caseTypeId1'; + const pageNo = 1; + const pageSize = 10; + const caaCasesFilterType = null; + const caaCasesFilterValue = null; + const payload = { caseType, pageNo, pageSize, caaCasesFilterType, caaCasesFilterValue }; + const action = new fromActions.LoadUnassignedCases(payload); + expect({ ...action }).toEqual({ + payload, + type: fromActions.LOAD_UNASSIGNED_CASES + }); + }); + + it('load unassigned cases success action', () => { + const payload = {} as CaaCases; + const action = new fromActions.LoadUnassignedCasesSuccess(payload); + expect({ ...action }).toEqual({ + payload, + type: fromActions.LOAD_UNASSIGNED_CASES_SUCCESS + }); + }); + + it('load unassigned cases failure action', () => { + const payload = new HttpErrorResponse({ error: 'unassigned cases error' }); + const action = new fromActions.LoadUnassignedCasesFailure(payload); + expect({ ...action }).toEqual({ + payload, + type: fromActions.LOAD_UNASSIGNED_CASES_FAILURE + }); + }); + + it('load case types action', () => { + const caaCasesFilterType = null; + const caaCasesFilterValue = null; + const payload = { caaCasesPageType: CaaCasesPageType.AssignedCases, caaCasesFilterType, caaCasesFilterValue }; + const action = new fromActions.LoadCaseTypes(payload); + expect({ ...action }).toEqual({ + payload, + type: fromActions.LOAD_CASE_TYPES + }); + }); + + it('load case types success action', () => { + const payload = []; + const action = new fromActions.LoadCaseTypesSuccess(payload); + expect({ ...action }).toEqual({ + payload, + type: fromActions.LOAD_CASE_TYPES_SUCCESS + }); + }); + + it('load case types failure action', () => { + const payload = {}; + const action = new fromActions.LoadCaseTypesFailure(payload); + expect({ ...action }).toEqual({ + payload, + type: fromActions.LOAD_CASE_TYPES_FAILURE + }); + }); +}); diff --git a/src/cases/store/actions/caa-cases.actions.ts b/src/cases/store/actions/caa-cases.actions.ts new file mode 100644 index 000000000..3731eb040 --- /dev/null +++ b/src/cases/store/actions/caa-cases.actions.ts @@ -0,0 +1,76 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { Action } from '@ngrx/store'; +import { CaaCases } from '../../models/caa-cases.model'; + +export const LOAD_ASSIGNED_CASES = '[CAA CASES] Load Assigned Cases'; +export const LOAD_ASSIGNED_CASES_SUCCESS = '[CAA CASES] Load Assigned Cases Success'; +export const LOAD_ASSIGNED_CASES_FAILURE = '[CAA CASES] Load Assigned Cases Failure'; +export const LOAD_UNASSIGNED_CASES = '[CAA CASES] Load Unassigned Cases'; +export const LOAD_UNASSIGNED_CASES_SUCCESS = '[CAA CASES] Load Unassigned Cases Success'; +export const LOAD_UNASSIGNED_CASES_FAILURE = '[CAA CASES] Load Unassigned Cases Failure'; +export const LOAD_CASE_TYPES = '[CAA CASES] Load Case Types'; +export const LOAD_CASE_TYPES_SUCCESS = '[CAA CASES] Load Case Types Success'; +export const LOAD_CASE_TYPES_FAILURE = '[CAA CASES] Load Case Types Failure'; +export const UPDATE_SELECTION_FOR_CASE_TYPE = '[CAA CASES] Update Selection For Case Types'; + +export class LoadAssignedCases implements Action { + public readonly type = LOAD_ASSIGNED_CASES; + constructor(public payload: {caseType: string, pageNo: number, pageSize: number, caaCasesFilterType: string | null, caaCasesFilterValue: string | null}) {} +} + +export class LoadAssignedCasesSuccess implements Action { + public readonly type = LOAD_ASSIGNED_CASES_SUCCESS; + constructor(public payload: CaaCases) {} +} + +export class LoadAssignedCasesFailure implements Action { + public readonly type = LOAD_ASSIGNED_CASES_FAILURE; + constructor(public payload: HttpErrorResponse) {} +} + +export class LoadUnassignedCases implements Action { + public readonly type = LOAD_UNASSIGNED_CASES; + constructor(public payload: {caseType: string, pageNo: number, pageSize: number, caaCasesFilterType: string | null, caaCasesFilterValue: string | null}) {} +} + +export class LoadUnassignedCasesSuccess implements Action { + public readonly type = LOAD_UNASSIGNED_CASES_SUCCESS; + constructor(public payload: CaaCases) {} +} + +export class LoadUnassignedCasesFailure implements Action { + public readonly type = LOAD_UNASSIGNED_CASES_FAILURE; + constructor(public payload: HttpErrorResponse) {} +} + +export class LoadCaseTypes implements Action { + public readonly type = LOAD_CASE_TYPES; + constructor(public payload: {caaCasesPageType: string, caaCasesFilterType: string | null, caaCasesFilterValue: string | null}) {} +} + +export class LoadCaseTypesSuccess implements Action { + public readonly type = LOAD_CASE_TYPES_SUCCESS; + constructor(public payload: any[]) {} +} + +export class LoadCaseTypesFailure implements Action { + public readonly type = LOAD_CASE_TYPES_FAILURE; + constructor(public payload: any) {} +} + +export class UpdateSelectionForCaseType implements Action { + public readonly type = UPDATE_SELECTION_FOR_CASE_TYPE; + constructor(public payload: {casetype: string; cases: any [] }) {} +} + +export type CaaCasesActions = + LoadAssignedCases + | LoadAssignedCasesSuccess + | LoadAssignedCasesFailure + | LoadUnassignedCases + | LoadUnassignedCasesSuccess + | LoadUnassignedCasesFailure + | LoadCaseTypes + | LoadCaseTypesSuccess + | LoadCaseTypesFailure + | UpdateSelectionForCaseType; diff --git a/src/cases/store/actions/index.ts b/src/cases/store/actions/index.ts new file mode 100644 index 000000000..55c8e9243 --- /dev/null +++ b/src/cases/store/actions/index.ts @@ -0,0 +1,20 @@ +import { + LoadAssignedCases, + LoadAssignedCasesFailure, + LoadAssignedCasesSuccess, + LoadUnassignedCases, + LoadUnassignedCasesFailure, + LoadUnassignedCasesSuccess +} from './caa-cases.actions'; + +export const actions: any[] = [ + LoadAssignedCases, + LoadAssignedCasesSuccess, + LoadAssignedCasesFailure, + LoadUnassignedCases, + LoadUnassignedCasesSuccess, + LoadUnassignedCasesFailure +]; + +export * from './caa-cases.actions'; +export * from './share-case.action'; diff --git a/src/cases/store/actions/share-case.action.spec.ts b/src/cases/store/actions/share-case.action.spec.ts new file mode 100644 index 000000000..3775c8238 --- /dev/null +++ b/src/cases/store/actions/share-case.action.spec.ts @@ -0,0 +1,195 @@ +import * as fromCaseShare from './share-case.action'; + +describe('Case Share Actions', () => { + it('NavigateToShareAssignedCases', () => { + const payload = []; + const action = new fromCaseShare.NavigateToShareAssignedCases(payload); + expect({ ...action }).toEqual({ + type: fromCaseShare.NAVIGATE_TO_SHARE_ASSIGNED_CASES, + payload + }); + }); + + it('NavigateToShareUnassignedCases', () => { + const payload = []; + const action = new fromCaseShare.NavigateToShareUnassignedCases(payload); + expect({ ...action }).toEqual({ + type: fromCaseShare.NAVIGATE_TO_SHARE_UNASSIGNED_CASES, + payload + }); + }); + + it('SynchronizeStateToStoreAssignedCases', () => { + const payload = []; + const action = new fromCaseShare.SynchronizeStateToStoreAssignedCases(payload); + expect({ ...action }).toEqual({ + type: fromCaseShare.SYNCHRONIZE_STATE_TO_STORE_ASSIGNED_CASES, + payload + }); + }); + + it('SynchronizeStateToStoreUnassignedCases', () => { + const payload = []; + const action = new fromCaseShare.SynchronizeStateToStoreUnassignedCases(payload); + expect({ ...action }).toEqual({ + type: fromCaseShare.SYNCHRONIZE_STATE_TO_STORE_UNASSIGNED_CASES, + payload + }); + }); + + it('LoadShareAssignedCases', () => { + const payload = []; + const action = new fromCaseShare.LoadShareAssignedCases(payload); + expect({ ...action }).toEqual({ + type: fromCaseShare.LOAD_SHARE_ASSIGNED_CASES, + payload + }); + }); + + it('LoadShareAssignedCasesSuccess', () => { + const payload = []; + const action = new fromCaseShare.LoadShareAssignedCasesSuccess(payload); + expect({ ...action }).toEqual({ + type: fromCaseShare.LOAD_SHARE_ASSIGNED_CASES_SUCCESS, + payload + }); + }); + + it('LoadShareAssignedCaseFailure', () => { + const payload: Error = new Error(); + const action = new fromCaseShare.LoadShareAssignedCaseFailure(payload); + expect({ ...action }).toEqual({ + type: fromCaseShare.LOAD_SHARE_ASSIGNED_CASES_FAILURE, + payload + }); + }); + + it('LoadShareUnassignedCases', () => { + const payload = []; + const action = new fromCaseShare.LoadShareUnassignedCases(payload); + expect({ ...action }).toEqual({ + type: fromCaseShare.LOAD_SHARE_UNASSIGNED_CASES, + payload + }); + }); + + it('LoadShareUnassignedAssignedCasesSuccess', () => { + const payload = []; + const action = new fromCaseShare.LoadShareUnassignedCasesSuccess(payload); + expect({ ...action }).toEqual({ + type: fromCaseShare.LOAD_SHARE_UNASSIGNED_CASES_SUCCESS, + payload + }); + }); + + it('LoadShareUnassignedCaseFailure', () => { + const payload: Error = new Error(); + const action = new fromCaseShare.LoadShareUnassignedCaseFailure(payload); + expect({ ...action }).toEqual({ + type: fromCaseShare.LOAD_SHARE_UNASSIGNED_CASES_FAILURE, + payload + }); + }); + + it('LoadUserFromOrgForCase', () => { + const action = new fromCaseShare.LoadUserFromOrgForCase(); + expect({ ...action }).toEqual({ + type: fromCaseShare.LOAD_USERS_FROM_ORG_FOR_CASE + }); + }); + + it('AddShareAssignedCases', () => { + const payload = { + sharedCases: [] + }; + const action = new fromCaseShare.AddShareAssignedCases(payload); + expect({ ...action }).toEqual({ + type: fromCaseShare.ADD_SHARE_ASSIGNED_CASES, + payload + }); + }); + + it('AddShareAssignedCaseGo', () => { + const payload = { + path: [], + sharedCases: [] + }; + const action = new fromCaseShare.AddShareAssignedCaseGo(payload); + expect({ ...action }).toEqual({ + type: fromCaseShare.ADD_SHARE_ASSIGNED_CASES_GO, + payload + }); + }); + + it('AddShareUnassignedCases', () => { + const payload = { + sharedCases: [] + }; + const action = new fromCaseShare.AddShareUnassignedCases(payload); + expect({ ...action }).toEqual({ + type: fromCaseShare.ADD_SHARE_UNASSIGNED_CASES, + payload + }); + }); + + it('AddShareUnassignedCaseGo', () => { + const payload = { + path: [], + sharedCases: [] + }; + const action = new fromCaseShare.AddShareUnassignedCaseGo(payload); + expect({ ...action }).toEqual({ + type: fromCaseShare.ADD_SHARE_UNASSIGNED_CASES_GO, + payload + }); + }); + + it('DeleteAShareAssignedCase', () => { + const payload = { + caseId: '1' + }; + const action = new fromCaseShare.DeleteAShareAssignedCase(payload); + expect({ ...action }).toEqual({ + type: fromCaseShare.DELETE_A_SHARE_ASSIGNED_CASE, + payload + }); + }); + + it('DeleteAShareUnassignedCase', () => { + const payload = { + caseId: '1' + }; + const action = new fromCaseShare.DeleteAShareUnassignedCase(payload); + expect({ ...action }).toEqual({ + type: fromCaseShare.DELETE_A_SHARE_UNASSIGNED_CASE, + payload + }); + }); + + it('AssignUsersToAssignedCase', () => { + const payload = []; + const action = new fromCaseShare.AssignUsersToAssignedCase(payload); + expect({ ...action }).toEqual({ + type: fromCaseShare.ASSIGN_USERS_TO_ASSIGNED_CASE, + payload + }); + }); + + it('AssignUsersToUnassignedCase', () => { + const payload = []; + const action = new fromCaseShare.AssignUsersToUnassignedCase(payload); + expect({ ...action }).toEqual({ + type: fromCaseShare.ASSIGN_USERS_TO_UNASSIGNED_CASE, + payload + }); + }); + + it('LoadUserFromOrgForCaseSuccess', () => { + const payload = []; + const action = new fromCaseShare.LoadUserFromOrgForCaseSuccess(payload); + expect({ ...action }).toEqual({ + type: fromCaseShare.LOAD_USERS_FROM_ORG_FOR_CASE_SUCCESS, + payload + }); + }); +}); diff --git a/src/cases/store/actions/share-case.action.ts b/src/cases/store/actions/share-case.action.ts new file mode 100644 index 000000000..4cd394632 --- /dev/null +++ b/src/cases/store/actions/share-case.action.ts @@ -0,0 +1,208 @@ +import { NavigationExtras } from '@angular/router'; +import { SharedCase } from '@hmcts/rpx-xui-common-lib/lib/models/case-share.model'; +import { UserDetails } from '@hmcts/rpx-xui-common-lib/lib/models/user-details.model'; +import { Action } from '@ngrx/store'; + +// Assigned cases actions +export const NAVIGATE_TO_SHARE_ASSIGNED_CASES = '[ShareCase] Navigate To Share Assigned Cases'; +export const LOAD_SHARE_ASSIGNED_CASES = '[ShareCase] Load Share Assigned Cases'; +export const LOAD_SHARE_ASSIGNED_CASES_SUCCESS = '[ShareCase] Load Share Assigned Cases Success'; +export const LOAD_SHARE_ASSIGNED_CASES_FAILURE = '[ShareCase] Load Share Assigned Cases Failure'; +export const ADD_SHARE_ASSIGNED_CASES = '[ShareCase] Add Share Assigned Cases'; +export const ADD_SHARE_ASSIGNED_CASES_GO = '[Router] Add Share Assigned Case Go'; +export const DELETE_A_SHARE_ASSIGNED_CASE = '[ShareCase] Delete A Share Assigned Case'; +export const ASSIGN_USERS_TO_ASSIGNED_CASE = '[ShareCase] Assign Users to Assigned Case'; +export const ASSIGN_USERS_TO_ASSIGNED_CASE_SUCCESS = '[ShareCase] Assign Users to Assigned Case Success'; +export const RESET_ASSIGNED_CASE_SELECTION = '[ShareCase] Reset Assigned Case Selection'; + +// Unassigned cases actions +export const NAVIGATE_TO_SHARE_UNASSIGNED_CASES = '[ShareCase] Navigate To Share Unassigned Cases'; +export const LOAD_SHARE_UNASSIGNED_CASES = '[ShareCase] Load Share Unassigned Cases'; +export const LOAD_SHARE_UNASSIGNED_CASES_SUCCESS = '[ShareCase] Load Share Unassigned Cases Success'; +export const LOAD_SHARE_UNASSIGNED_CASES_FAILURE = '[ShareCase] Load Share Unassigned Cases Failure'; +export const ADD_SHARE_UNASSIGNED_CASES = '[ShareCase] Add Share Unassigned Cases'; +export const ADD_SHARE_UNASSIGNED_CASES_GO = '[Router] Add Share Unassigned Case Go'; +export const DELETE_A_SHARE_UNASSIGNED_CASE = '[ShareCase] Delete A Share Unassigned Case'; +export const ASSIGN_USERS_TO_UNASSIGNED_CASE = '[ShareCase] Assign Users to Unassigned Case'; +export const ASSIGN_USERS_TO_UNASSIGNED_CASE_SUCCESS = '[ShareCase] Assign Users to Unassigned Case Success'; +export const RESET_UNASSIGNED_CASE_SELECTION = '[ShareCase] Reset Unassigned Case Selection'; + +export const LOAD_USERS_FROM_ORG_FOR_CASE = '[LoadUsers] From ORG For A Case'; +export const LOAD_USERS_FROM_ORG_FOR_CASE_SUCCESS = '[LoadUsers] From ORG For A Case Success'; +export const SYNCHRONIZE_STATE_TO_STORE_ASSIGNED_CASES = '[ShareCase] Synchronize State To Store Assigned Cases'; +export const SYNCHRONIZE_STATE_TO_STORE_UNASSIGNED_CASES = '[ShareCase] Synchronize State To Store Unassigned Cases'; + +export class NavigateToShareAssignedCases implements Action { + public readonly type = NAVIGATE_TO_SHARE_ASSIGNED_CASES; + constructor(public payload: SharedCase[]) {} +} + +export class NavigateToShareUnassignedCases implements Action { + public readonly type = NAVIGATE_TO_SHARE_UNASSIGNED_CASES; + constructor(public payload: SharedCase[]) {} +} + +export class SynchronizeStateToStoreAssignedCases implements Action { + public readonly type = SYNCHRONIZE_STATE_TO_STORE_ASSIGNED_CASES; + constructor(public payload: SharedCase[]) {} +} + +export class SynchronizeStateToStoreUnassignedCases implements Action { + public readonly type = SYNCHRONIZE_STATE_TO_STORE_UNASSIGNED_CASES; + constructor(public payload: SharedCase[]) {} +} + +export class LoadShareAssignedCases implements Action { + public readonly type = LOAD_SHARE_ASSIGNED_CASES; + constructor(public payload: SharedCase[]) {} +} + +export class LoadShareAssignedCasesSuccess implements Action { + public readonly type = LOAD_SHARE_ASSIGNED_CASES_SUCCESS; + constructor(public payload: SharedCase[]) {} +} + +export class LoadShareAssignedCaseFailure implements Action { + public readonly type = LOAD_SHARE_ASSIGNED_CASES_FAILURE; + constructor(public payload: Error) {} +} + +export class LoadShareUnassignedCases implements Action { + public readonly type = LOAD_SHARE_UNASSIGNED_CASES; + constructor(public payload: SharedCase[]) {} +} + +export class LoadShareUnassignedCasesSuccess implements Action { + public readonly type = LOAD_SHARE_UNASSIGNED_CASES_SUCCESS; + constructor(public payload: SharedCase[]) {} +} + +export class LoadShareUnassignedCaseFailure implements Action { + public readonly type = LOAD_SHARE_UNASSIGNED_CASES_FAILURE; + constructor(public payload: Error) {} +} + +export class AddShareAssignedCases implements Action { + public readonly type = ADD_SHARE_ASSIGNED_CASES; + constructor(public payload: { + path?: any[]; + query?: object; + extras?: NavigationExtras; + sharedCases: SharedCase[] + }) {} +} + +export class AddShareAssignedCaseGo implements Action { + public readonly type = ADD_SHARE_ASSIGNED_CASES_GO; + constructor( + public payload: { + path: any[]; + query?: object; + extras?: NavigationExtras; + sharedCases: SharedCase[] + } + ) {} +} + +export class AddShareUnassignedCases implements Action { + public readonly type = ADD_SHARE_UNASSIGNED_CASES; + constructor(public payload: { + path?: any[]; + query?: object; + extras?: NavigationExtras; + sharedCases: SharedCase[] + }) {} +} + +export class AddShareUnassignedCaseGo implements Action { + public readonly type = ADD_SHARE_UNASSIGNED_CASES_GO; + constructor( + public payload: { + path: any[]; + query?: object; + extras?: NavigationExtras; + sharedCases: SharedCase[] + } + ) {} +} + +export class DeleteAShareAssignedCase implements Action { + public readonly type = DELETE_A_SHARE_ASSIGNED_CASE; + constructor( + public payload: { + caseId: string; + } + ) {} +} + +export class DeleteAShareUnassignedCase implements Action { + public readonly type = DELETE_A_SHARE_UNASSIGNED_CASE; + constructor( + public payload: { + caseId: string; + } + ) {} +} + +export class AssignUsersToAssignedCase implements Action { + public readonly type = ASSIGN_USERS_TO_ASSIGNED_CASE; + constructor(public payload: SharedCase[]) {} +} + +export class AssignUsersToAssignedCaseSuccess implements Action { + public readonly type = ASSIGN_USERS_TO_ASSIGNED_CASE_SUCCESS; + constructor(public payload: SharedCase[]) {} +} + +export class AssignUsersToUnassignedCase implements Action { + public readonly type = ASSIGN_USERS_TO_UNASSIGNED_CASE; + constructor(public payload: SharedCase[]) {} +} + +export class AssignUsersToUnassignedCaseSuccess implements Action { + public readonly type = ASSIGN_USERS_TO_UNASSIGNED_CASE_SUCCESS; + constructor(public payload: SharedCase[]) {} +} + +export class ResetAssignedCaseSelection implements Action { + public readonly type = RESET_ASSIGNED_CASE_SELECTION; +} + +export class ResetUnassignedCaseSelection implements Action { + public readonly type = RESET_UNASSIGNED_CASE_SELECTION; +} + +export class LoadUserFromOrgForCase implements Action { + public readonly type = LOAD_USERS_FROM_ORG_FOR_CASE; +} + +export class LoadUserFromOrgForCaseSuccess implements Action { + public readonly type = LOAD_USERS_FROM_ORG_FOR_CASE_SUCCESS; + constructor(public payload: UserDetails[]) {} +} + +export type Actions = + NavigateToShareAssignedCases + | NavigateToShareUnassignedCases + | SynchronizeStateToStoreAssignedCases + | SynchronizeStateToStoreUnassignedCases + | LoadShareAssignedCases + | LoadShareAssignedCasesSuccess + | LoadShareAssignedCaseFailure + | LoadShareUnassignedCases + | LoadShareUnassignedCasesSuccess + | LoadShareUnassignedCaseFailure + | AddShareAssignedCases + | AddShareAssignedCaseGo + | AddShareUnassignedCases + | AddShareUnassignedCaseGo + | DeleteAShareAssignedCase + | DeleteAShareUnassignedCase + | AssignUsersToAssignedCase + | AssignUsersToAssignedCaseSuccess + | AssignUsersToUnassignedCase + | AssignUsersToUnassignedCaseSuccess + | ResetAssignedCaseSelection + | ResetUnassignedCaseSelection + | LoadUserFromOrgForCase + | LoadUserFromOrgForCaseSuccess; diff --git a/src/cases/store/effects/caa-cases.effects.spec.ts b/src/cases/store/effects/caa-cases.effects.spec.ts new file mode 100644 index 000000000..55e320545 --- /dev/null +++ b/src/cases/store/effects/caa-cases.effects.spec.ts @@ -0,0 +1,160 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { TestBed } from '@angular/core/testing'; +import { provideMockActions } from '@ngrx/effects/testing'; +import { addMatchers, cold, hot, initTestScheduler } from 'jasmine-marbles'; +import { of, throwError } from 'rxjs'; +import { NavItemModel } from '../../../app/models/nav-items.model'; +import { LoggerService } from '../../../shared/services/logger.service'; +import { CaaCasesPageType } from '../../models/caa-cases.enum'; +import { CaaCases } from '../../models/caa-cases.model'; +import { CaaCasesService } from '../../services'; +import * as caaCasesActions from '../actions/caa-cases.actions'; +import { CaaCasesEffects } from './caa-cases.effects'; + +describe('CaaCasesEffects', () => { + let actions$; + let effects: CaaCasesEffects; + const caaCasesServiceMock = jasmine.createSpyObj('CaaCasesService', ['getCaaCases', 'getCaaCaseTypes']); + const loggerServiceMock = jasmine.createSpyObj('LoggerService', ['error']); + const assignedCases = {} as CaaCases; + const unassignedCases = {} as CaaCases; + const navItems = [] as NavItemModel[]; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [], + providers: [ + { provide: CaaCasesService, useValue: caaCasesServiceMock }, + { provide: LoggerService, useValue: loggerServiceMock }, + CaaCasesEffects, + provideMockActions(() => actions$) + ] + }); + effects = TestBed.inject(CaaCasesEffects); + initTestScheduler(); + addMatchers(); + }); + + describe('loadAssignedCases$', () => { + it('loadAssignedCases successful', () => { + caaCasesServiceMock.getCaaCases.and.returnValue(of(assignedCases)); + const caseType = ''; + const pageNo = 1; + const pageSize = 10; + const caaCasesFilterType = null; + const caaCasesFilterValue = null; + const payload = { caseType, pageNo, pageSize, caaCasesFilterType, caaCasesFilterValue }; + const action = new caaCasesActions.LoadAssignedCases(payload); + const completion = new caaCasesActions.LoadAssignedCasesSuccess(assignedCases); + actions$ = hot('-a', { a: action }); + const expected = cold('-b', { b: completion }); + expect(effects.loadAssignedCases$).toBeObservable(expected); + }); + + it('loadAssignedCases error', () => { + const error: HttpErrorResponse = { + error: 'Error', + status: 400, + message: 'Error', + headers: null, + statusText: null, + name: null, + ok: false, + type: null, + url: null + }; + caaCasesServiceMock.getCaaCases.and.returnValue(throwError(error)); + const caseType = ''; + const pageNo = 1; + const pageSize = 10; + const caaCasesFilterType = null; + const caaCasesFilterValue = null; + const payload = { caseType, pageNo, pageSize, caaCasesFilterType, caaCasesFilterValue }; + const action = new caaCasesActions.LoadAssignedCases(payload); + const completion = new caaCasesActions.LoadAssignedCasesFailure(error); + actions$ = hot('-a', { a: action }); + const expected = cold('-b', { b: completion }); + expect(effects.loadAssignedCases$).toBeObservable(expected); + }); + }); + + describe('loadUnassignedCases$', () => { + it('loadUnassignedCases successful', () => { + caaCasesServiceMock.getCaaCases.and.returnValue(of(unassignedCases)); + const caseType = ''; + const pageNo = 1; + const pageSize = 10; + const caaCasesFilterType = null; + const caaCasesFilterValue = null; + const payload = { caseType, pageNo, pageSize, caaCasesFilterType, caaCasesFilterValue }; + const action = new caaCasesActions.LoadUnassignedCases(payload); + const completion = new caaCasesActions.LoadUnassignedCasesSuccess(unassignedCases); + actions$ = hot('-a', { a: action }); + const expected = cold('-b', { b: completion }); + expect(effects.loadUnassignedCases$).toBeObservable(expected); + }); + + it('loadUnassignedCases error', () => { + const error: HttpErrorResponse = { + error: 'Error', + status: 400, + message: 'Error', + headers: null, + statusText: null, + name: null, + ok: false, + type: null, + url: null + }; + caaCasesServiceMock.getCaaCases.and.returnValue(throwError(error)); + const caseType = ''; + const pageNo = 1; + const pageSize = 10; + const caaCasesFilterType = null; + const caaCasesFilterValue = null; + const payload = { caseType, pageNo, pageSize, caaCasesFilterType, caaCasesFilterValue }; + const action = new caaCasesActions.LoadUnassignedCases(payload); + const completion = new caaCasesActions.LoadUnassignedCasesFailure(error); + actions$ = hot('-a', { a: action }); + const expected = cold('-b', { b: completion }); + expect(effects.loadUnassignedCases$).toBeObservable(expected); + }); + }); + + describe('loadCaseTypes$', () => { + it('loadCaseTypes successful', () => { + caaCasesServiceMock.getCaaCaseTypes.and.returnValue(of(navItems)); + const caaCasesFilterType = null; + const caaCasesFilterValue = null; + const payload = { caaCasesPageType: CaaCasesPageType.AssignedCases, caaCasesFilterType, caaCasesFilterValue }; + const action = new caaCasesActions.LoadCaseTypes(payload); + const completion = new caaCasesActions.LoadCaseTypesSuccess(navItems); + actions$ = hot('-a', { a: action }); + const expected = cold('-b', { b: completion }); + expect(effects.loadCaseTypes$).toBeObservable(expected); + }); + + it('loadCaseTypes error', () => { + const error: HttpErrorResponse = { + error: 'Error', + status: 400, + message: 'Error', + headers: null, + statusText: null, + name: null, + ok: false, + type: null, + url: null + }; + caaCasesServiceMock.getCaaCaseTypes.and.returnValue(throwError(error)); + const caaCasesFilterType = null; + const caaCasesFilterValue = null; + const payload = { caaCasesPageType: CaaCasesPageType.AssignedCases, caaCasesFilterType, caaCasesFilterValue }; + const action = new caaCasesActions.LoadCaseTypes(payload); + const completion = new caaCasesActions.LoadCaseTypesFailure(error); + actions$ = hot('-a', { a: action }); + const expected = cold('-b', { b: completion }); + expect(effects.loadCaseTypes$).toBeObservable(expected); + }); + }); +}); diff --git a/src/cases/store/effects/caa-cases.effects.ts b/src/cases/store/effects/caa-cases.effects.ts new file mode 100644 index 000000000..337104295 --- /dev/null +++ b/src/cases/store/effects/caa-cases.effects.ts @@ -0,0 +1,74 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; +import { Action } from '@ngrx/store'; +import { Observable, of } from 'rxjs'; +import { catchError, map, switchMap } from 'rxjs/operators'; +import * as fromRoot from '../../../app/store/index'; +import { LoggerService } from '../../../shared/services/logger.service'; +import { CaaCasesPageType } from '../../models/caa-cases.enum'; +import { CaaCasesService } from '../../services/caa-cases.service'; +import { CaaCasesUtil } from '../../util/caa-cases.util'; +import * as fromCaaActions from '../actions/caa-cases.actions'; + +@Injectable() +export class CaaCasesEffects { + constructor(private readonly actions$: Actions, + private readonly caaCasesService: CaaCasesService, + private readonly loggerService: LoggerService) { + } + + public loadAssignedCases$ = createEffect(() => + this.actions$.pipe( + ofType(fromCaaActions.LOAD_ASSIGNED_CASES), + switchMap((action: fromCaaActions.LoadAssignedCases) => { + const payload = action.payload; + return this.caaCasesService.getCaaCases(payload.caseType, payload.pageNo, payload.pageSize, CaaCasesPageType.AssignedCases, payload.caaCasesFilterType, payload.caaCasesFilterValue).pipe( + map((caaCases) => new fromCaaActions.LoadAssignedCasesSuccess(caaCases)), + catchError((error) => CaaCasesEffects.handleError(error, this.loggerService, CaaCasesPageType.AssignedCases)) + ); + }) + ) + ); + + public loadUnassignedCases$ = createEffect(() => + this.actions$.pipe( + ofType(fromCaaActions.LOAD_UNASSIGNED_CASES), + switchMap((action: fromCaaActions.LoadUnassignedCases) => { + const payload = action.payload; + return this.caaCasesService.getCaaCases(payload.caseType, payload.pageNo, payload.pageSize, CaaCasesPageType.UnassignedCases, payload.caaCasesFilterType, payload.caaCasesFilterValue).pipe( + map((caaCases) => new fromCaaActions.LoadUnassignedCasesSuccess(caaCases)), + catchError((error) => CaaCasesEffects.handleError(error, this.loggerService, CaaCasesPageType.UnassignedCases)) + ); + }) + ) + ); + + public loadCaseTypes$ = createEffect(() => + this.actions$.pipe( + ofType(fromCaaActions.LOAD_CASE_TYPES), + switchMap((action: fromCaaActions.LoadCaseTypes) => { + const payload = action.payload; + return this.caaCasesService.getCaaCaseTypes(payload.caaCasesPageType, payload.caaCasesFilterType, payload.caaCasesFilterValue).pipe( + map((caaCaseTypes) => { + const navItems = CaaCasesUtil.getCaaNavItems(caaCaseTypes); + return new fromCaaActions.LoadCaseTypesSuccess(navItems); + }), + catchError((error) => { + this.loggerService.error(error); + return of(new fromCaaActions.LoadCaseTypesFailure(error)); + }) + ); + }) + ) + ); + + public static handleError(error: HttpErrorResponse, loggerService: LoggerService, caaCasesPageType: string): Observable { + loggerService.error(error); + return error.status === 400 + ? caaCasesPageType === CaaCasesPageType.UnassignedCases + ? of(new fromCaaActions.LoadUnassignedCasesFailure(error)) + : of(new fromCaaActions.LoadAssignedCasesFailure(error)) + : of(new fromRoot.Go({ path: ['/service-down'] })); + } +} diff --git a/src/cases/store/effects/index.ts b/src/cases/store/effects/index.ts new file mode 100644 index 000000000..fa409d976 --- /dev/null +++ b/src/cases/store/effects/index.ts @@ -0,0 +1,11 @@ +import { CaaCasesEffects } from './caa-cases.effects'; +import { ShareCaseEffects } from './share-case.effects'; + +export const effects: any[] = [ + CaaCasesEffects, + ShareCaseEffects +]; + +export * from './caa-cases.effects'; +export * from './share-case.effects'; + diff --git a/src/cases/store/effects/share-case.effects.spec.ts b/src/cases/store/effects/share-case.effects.spec.ts new file mode 100644 index 000000000..80f45974f --- /dev/null +++ b/src/cases/store/effects/share-case.effects.spec.ts @@ -0,0 +1,265 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { TestBed, waitForAsync } from '@angular/core/testing'; +import { Router } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { provideMockActions } from '@ngrx/effects/testing'; +import { Store, StoreModule } from '@ngrx/store'; +import { addMatchers, cold, hot, initTestScheduler } from 'jasmine-marbles'; +import { of } from 'rxjs'; +import { CaseShareService } from '../../services'; +import { + AddShareAssignedCaseGo, + AddShareAssignedCases, + AddShareUnassignedCaseGo, + AddShareUnassignedCases, + AssignUsersToAssignedCase, + AssignUsersToAssignedCaseSuccess, + AssignUsersToUnassignedCase, + AssignUsersToUnassignedCaseSuccess, + LoadShareAssignedCases, + LoadShareAssignedCasesSuccess, + LoadShareUnassignedCases, + LoadShareUnassignedCasesSuccess, + LoadUserFromOrgForCase, + LoadUserFromOrgForCaseSuccess +} from '../actions'; +import * as shareCases from '../reducers/share-case.reducer'; +import * as fromShareCaseEffects from './share-case.effects'; + +describe('Share Case Effects', () => { + let actions$; + let effects: fromShareCaseEffects.ShareCaseEffects; + let store: Store; + const routerMock = jasmine.createSpyObj('Router', [ + 'navigate' + ]); + routerMock.url = '/unassigned-cases'; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let spyOnDispatchToStore = jasmine.createSpy(); + const caseShareServiceMock = jasmine.createSpyObj('CaseShareService', ['getShareCases', 'getUsersFromOrg', 'assignUsersWithCases']); + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ + StoreModule.forRoot({}), + HttpClientTestingModule, + RouterTestingModule], + providers: [ + { + provide: CaseShareService, + useValue: caseShareServiceMock + }, + { + provide: Router, + useValue: routerMock + }, + fromShareCaseEffects.ShareCaseEffects, + provideMockActions(() => actions$) + ] + }); + + store = TestBed.inject(Store); + + spyOnDispatchToStore = spyOn(store, 'dispatch').and.callThrough(); + effects = TestBed.inject(fromShareCaseEffects.ShareCaseEffects); + + initTestScheduler(); + addMatchers(); + })); + + describe('addShareAssignedCases$', () => { + it('should add share assigned case action', () => { + const action = new AddShareAssignedCases({ + sharedCases: [ + { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, + { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' }] + }); + const completion = new AddShareAssignedCaseGo({ + path: ['/unassigned-cases/case-share'], + sharedCases: [ + { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, + { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' }] + }); + actions$ = hot('-a', { a: action }); + const expected = cold('-b', { b: completion }); + expect(effects.addShareAssignedCases$).toBeObservable(expected); + }); + }); + + describe('addShareUnassignedCases$', () => { + it('should add share unassigned case action', () => { + const action = new AddShareUnassignedCases({ + sharedCases: [ + { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, + { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' }] + }); + const completion = new AddShareUnassignedCaseGo({ + path: ['/unassigned-cases/case-share'], + sharedCases: [ + { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, + { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' }] + }); + actions$ = hot('-a', { a: action }); + const expected = cold('-b', { b: completion }); + expect(effects.addShareUnassignedCases$).toBeObservable(expected); + }); + }); + + describe('navigateToAddShareAssignedCase$', () => { + it('should add share assigned case go', () => { + const payload = { + path: ['/unassigned-cases/case-share'], + sharedCases: [ + { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, + { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' }] + }; + routerMock.navigate.and.returnValue(Promise.resolve(true)); + const action = new AddShareAssignedCaseGo(payload); + actions$ = hot('-a', { a: action }); + effects.navigateToAddShareAssignedCase$.subscribe(() => { + expect(routerMock.navigate).toHaveBeenCalled(); + }); + }); + }); + + describe('navigateToAddShareUnassignedCase$', () => { + it('should add share unassigned case go', () => { + const payload = { + path: ['/unassigned-cases/case-share'], + sharedCases: [ + { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, + { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' }] + }; + routerMock.navigate.and.returnValue(Promise.resolve(true)); + const action = new AddShareUnassignedCaseGo(payload); + actions$ = hot('-a', { a: action }); + effects.navigateToAddShareUnassignedCase$.subscribe(() => { + expect(routerMock.navigate).toHaveBeenCalled(); + }); + }); + }); + + describe('loadShareAssignedCases$', () => { + it('should load share assigned cases', () => { + const requestPayload = [ + { caseId: '1', caseTitle: 'James123' }, + { caseId: '2', caseTitle: 'Steve321' }]; + const returnPayload = [ + { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, + { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' }]; + caseShareServiceMock.getShareCases.and.returnValue(of(returnPayload)); + const action = new LoadShareAssignedCases(requestPayload); + const completion = new LoadShareAssignedCasesSuccess(returnPayload); + actions$ = hot('-a', { a: action }); + const expected = cold('-b', { b: completion }); + expect(effects.loadShareAssignedCases$).toBeObservable(expected); + }); + }); + + describe('loadShareUnassignedCases$', () => { + it('should load share unassigned cases', () => { + const requestPayload = [ + { caseId: '1', caseTitle: 'James123' }, + { caseId: '2', caseTitle: 'Steve321' }]; + const returnPayload = [ + { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, + { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' }]; + caseShareServiceMock.getShareCases.and.returnValue(of(returnPayload)); + const action = new LoadShareUnassignedCases(requestPayload); + const completion = new LoadShareUnassignedCasesSuccess(returnPayload); + actions$ = hot('-a', { a: action }); + const expected = cold('-b', { b: completion }); + expect(effects.loadShareUnassignedCases$).toBeObservable(expected); + }); + }); + + describe('loadOrgUsers$', () => { + it('should load organisation users', () => { + const returnPayload = [ + { + idamId: 'U111111', + firstName: 'James', + lastName: 'Priest', + email: 'james.priest@test.com' + }, + { + idamId: 'U222222', + firstName: 'Shaun', + lastName: 'Godard', + email: 'shaun.godard@test.com' + }]; + caseShareServiceMock.getUsersFromOrg.and.returnValue(of(returnPayload)); + const action = new LoadUserFromOrgForCase(); + const completion = new LoadUserFromOrgForCaseSuccess(returnPayload); + actions$ = hot('-a', { a: action }); + const expected = cold('-b', { b: completion }); + expect(effects.loadOrgUsers$).toBeObservable(expected); + }); + }); + + describe('assignUsersToAssignedCases$', () => { + it('should assign users with assigned cases', () => { + const requestPayload = [ + { + caseId: '1', caseTitle: 'James123', caseTypeId: 'type1', sharedWith: [ + { + idamId: 'U111111', + firstName: 'James', + lastName: 'Priest', + email: 'james.priest@test.com' + }] + }, + { + caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2', sharedWith: [ + { + idamId: 'U222222', + firstName: 'Shaun', + lastName: 'Godard', + email: 'shaun.godard@test.com' + }] + }]; + const returnPayload = [ + { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, + { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' }]; + caseShareServiceMock.assignUsersWithCases.and.returnValue(of(returnPayload)); + const action = new AssignUsersToAssignedCase(requestPayload); + const completion = new AssignUsersToAssignedCaseSuccess(returnPayload); + actions$ = hot('-a', { a: action }); + const expected = cold('-b', { b: completion }); + expect(effects.assignUsersToAssignedCases$).toBeObservable(expected); + }); + }); + + describe('assignUsersToUnassignedCases$', () => { + it('should assign users with unassigned cases', () => { + const requestPayload = [ + { + caseId: '1', caseTitle: 'James123', caseTypeId: 'type1', sharedWith: [ + { + idamId: 'U111111', + firstName: 'James', + lastName: 'Priest', + email: 'james.priest@test.com' + }] + }, + { + caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2', sharedWith: [ + { + idamId: 'U222222', + firstName: 'Shaun', + lastName: 'Godard', + email: 'shaun.godard@test.com' + }] + }]; + const returnPayload = [ + { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, + { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' }]; + caseShareServiceMock.assignUsersWithCases.and.returnValue(of(returnPayload)); + const action = new AssignUsersToUnassignedCase(requestPayload); + const completion = new AssignUsersToUnassignedCaseSuccess(returnPayload); + actions$ = hot('-a', { a: action }); + const expected = cold('-b', { b: completion }); + expect(effects.assignUsersToUnassignedCases$).toBeObservable(expected); + }); + }); +}); diff --git a/src/cases/store/effects/share-case.effects.ts b/src/cases/store/effects/share-case.effects.ts new file mode 100644 index 000000000..b5e3c6e68 --- /dev/null +++ b/src/cases/store/effects/share-case.effects.ts @@ -0,0 +1,148 @@ +import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; +import { Store } from '@ngrx/store'; +import { of } from 'rxjs'; +import { catchError, map, switchMap, tap } from 'rxjs/operators'; +import * as fromRoot from '../../../app/store/index'; +import { CaaCasesPageType } from '../../models/caa-cases.enum'; +import { CaseShareService } from '../../services'; +import * as shareCaseActions from '../actions/share-case.action'; +import * as shareCases from '../reducers/share-case.reducer'; + +@Injectable() +export class ShareCaseEffects { + public payload: any; + + constructor( + private readonly actions$: Actions, + private readonly caseShareService: CaseShareService, + private readonly router: Router, + private readonly store: Store + ) { + } + + public addShareAssignedCases$ = createEffect(() => + this.actions$.pipe( + ofType(shareCaseActions.ADD_SHARE_ASSIGNED_CASES), + map((action: shareCaseActions.AddShareAssignedCases) => action.payload), + map((newCases) => { + return new shareCaseActions.AddShareAssignedCaseGo({ + path: [`${this.router.url}/case-share`], + sharedCases: newCases.sharedCases + }); + }) + ) + ); + + public addShareUnassignedCases$ = createEffect(() => + this.actions$.pipe( + ofType(shareCaseActions.ADD_SHARE_UNASSIGNED_CASES), + map((action: shareCaseActions.AddShareUnassignedCases) => action.payload), + map((newCases) => { + return new shareCaseActions.AddShareUnassignedCaseGo({ + path: [`${this.router.url}/case-share`], + sharedCases: newCases.sharedCases + }); + }) + ) + ); + + public navigateToAddShareAssignedCase$ = createEffect(() => + this.actions$.pipe( + ofType(shareCaseActions.ADD_SHARE_ASSIGNED_CASES_GO), + map((action: shareCaseActions.AddShareAssignedCaseGo) => action.payload), + tap(({ path, query: queryParams, extras, sharedCases }) => { + const thatSharedCases = sharedCases; + queryParams = { init: true, pageType: CaaCasesPageType.AssignedCases }; + return this.router.navigate(path, { queryParams, ...extras }).then(() => { + this.store.dispatch(new shareCaseActions.NavigateToShareAssignedCases(thatSharedCases)); + }); + }) + ), + { dispatch: false } + ); + + public navigateToAddShareUnassignedCase$ = createEffect(() => + this.actions$.pipe( + ofType(shareCaseActions.ADD_SHARE_UNASSIGNED_CASES_GO), + map((action: shareCaseActions.AddShareUnassignedCaseGo) => action.payload), + tap(({ path, query: queryParams, extras, sharedCases }) => { + const thatSharedCases = sharedCases; + queryParams = { init: true, pageType: CaaCasesPageType.UnassignedCases }; + return this.router.navigate(path, { queryParams, ...extras }).then(() => { + this.store.dispatch(new shareCaseActions.NavigateToShareUnassignedCases(thatSharedCases)); + }); + }) + ), + { dispatch: false } + ); + + public loadShareAssignedCases$ = createEffect(() => + this.actions$.pipe( + ofType(shareCaseActions.LOAD_SHARE_ASSIGNED_CASES), + map((action: shareCaseActions.LoadShareAssignedCases) => action.payload), + switchMap((payload) => { + this.payload = payload; + return this.caseShareService.getShareCases(payload).pipe( + map((response) => new shareCaseActions.LoadShareAssignedCasesSuccess(response)), + catchError(() => of(new fromRoot.Go({ path: ['/service-down'] }))) + ); + }) + ) + ); + + public loadShareUnassignedCases$ = createEffect(() => + this.actions$.pipe( + ofType(shareCaseActions.LOAD_SHARE_UNASSIGNED_CASES), + map((action: shareCaseActions.LoadShareUnassignedCases) => action.payload), + switchMap((payload) => { + this.payload = payload; + return this.caseShareService.getShareCases(payload).pipe( + map((response) => new shareCaseActions.LoadShareUnassignedCasesSuccess(response)), + catchError(() => of(new fromRoot.Go({ path: ['/service-down'] }))) + ); + }) + ) + ); + + public loadOrgUsers$ = createEffect(() => + this.actions$.pipe( + ofType(shareCaseActions.LOAD_USERS_FROM_ORG_FOR_CASE), + switchMap(() => { + return this.caseShareService.getUsersFromOrg().pipe( + map((response) => new shareCaseActions.LoadUserFromOrgForCaseSuccess(response)), + catchError(() => of(new fromRoot.Go({ path: ['/service-down'] }))) + ); + }) + ) + ); + + public assignUsersToAssignedCases$ = createEffect(() => + this.actions$.pipe( + ofType(shareCaseActions.ASSIGN_USERS_TO_ASSIGNED_CASE), + map((action: shareCaseActions.AssignUsersToAssignedCase) => action.payload), + switchMap((payload) => { + this.payload = payload; + return this.caseShareService.assignUsersWithCases(payload).pipe( + map((response) => new shareCaseActions.AssignUsersToAssignedCaseSuccess(response)), + catchError(() => of(new fromRoot.Go({ path: ['/service-down'] }))) + ); + }) + ) + ); + + public assignUsersToUnassignedCases$ = createEffect(() => + this.actions$.pipe( + ofType(shareCaseActions.ASSIGN_USERS_TO_UNASSIGNED_CASE), + map((action: shareCaseActions.AssignUsersToUnassignedCase) => action.payload), + switchMap((payload) => { + this.payload = payload; + return this.caseShareService.assignUsersWithCases(payload).pipe( + map((response) => new shareCaseActions.AssignUsersToUnassignedCaseSuccess(response)), + catchError(() => of(new fromRoot.Go({ path: ['/service-down'] }))) + ); + }) + ) + ); +} diff --git a/src/cases/store/index.ts b/src/cases/store/index.ts new file mode 100644 index 000000000..772e0e89d --- /dev/null +++ b/src/cases/store/index.ts @@ -0,0 +1,4 @@ +export * from './reducers'; +export * from './effects'; +export * from './actions'; +export * from './selectors'; diff --git a/src/cases/store/reducers/caa-cases.reducer.spec.ts b/src/cases/store/reducers/caa-cases.reducer.spec.ts new file mode 100644 index 000000000..41ec429b7 --- /dev/null +++ b/src/cases/store/reducers/caa-cases.reducer.spec.ts @@ -0,0 +1,61 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import * as fromActions from '../actions/caa-cases.actions'; +import * as fromCaaCasesReducer from './caa-cases.reducer'; + +describe('CaaCases Reducer', () => { + const initialState: fromCaaCasesReducer.CaaCasesState = { + assignedCases: { + idField: 'id1', + columnConfigs: null, + data: null + }, + unassignedCases: { + idField: 'id2', + columnConfigs: null, + data: null + }, + caseTypes: [], + selectedCases: {}, + assignedCasesLastError: new HttpErrorResponse({ error: 'assigned cases error' }), + unassignedCasesLastError: new HttpErrorResponse({ error: 'unassigned cases error' }) + }; + + it('should undefined action return default state', () => { + const caaCasesState = fromCaaCasesReducer.initialState; + const action = {} as any; + const state = fromCaaCasesReducer.caaCasesReducer(undefined, action); + expect(state).toBe(caaCasesState); + }); + + it('should loadAssignedCasesSuccess action set correct state', () => { + const action = new fromActions.LoadAssignedCasesSuccess(initialState.assignedCases); + const state = fromCaaCasesReducer.caaCasesReducer(initialState, action); + expect(state.assignedCases).toBe(initialState.assignedCases); + }); + + it('should LoadAssignedCasesFailure action set error', () => { + const error = new HttpErrorResponse({ error: 'assigned cases error' }); + const action = new fromActions.LoadAssignedCasesFailure(initialState.assignedCasesLastError); + const state = fromCaaCasesReducer.caaCasesReducer(initialState, action); + expect(state.assignedCasesLastError).toEqual(error); + }); + + it('should loadUnassignedCasesSuccess action set correct state', () => { + const action = new fromActions.LoadUnassignedCasesSuccess(initialState.unassignedCases); + const state = fromCaaCasesReducer.caaCasesReducer(initialState, action); + expect(state.unassignedCases).toBe(initialState.unassignedCases); + }); + + it('should LoadUnassignedCasesFailure action set error', () => { + const error = new HttpErrorResponse({ error: 'unassigned cases error' }); + const action = new fromActions.LoadUnassignedCasesFailure(initialState.unassignedCasesLastError); + const state = fromCaaCasesReducer.caaCasesReducer(initialState, action); + expect(state.unassignedCasesLastError).toEqual(error); + }); + + it('should loadCaseTypesSuccess action set correct state', () => { + const action = new fromActions.LoadCaseTypesSuccess(initialState.caseTypes); + const state = fromCaaCasesReducer.caaCasesReducer(initialState, action); + expect(state.caseTypes).toBe(initialState.caseTypes); + }); +}); diff --git a/src/cases/store/reducers/caa-cases.reducer.ts b/src/cases/store/reducers/caa-cases.reducer.ts new file mode 100644 index 000000000..785943aee --- /dev/null +++ b/src/cases/store/reducers/caa-cases.reducer.ts @@ -0,0 +1,49 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { SubNavigation } from '@hmcts/rpx-xui-common-lib/lib/gov-ui/components/hmcts-sub-navigation/hmcts-sub-navigation.component'; +import { CaaCases, SelectedCases } from '../../models/caa-cases.model'; +import * as fromCaaActions from '../actions/caa-cases.actions'; + +export interface CaaCasesState { + assignedCases: CaaCases; + unassignedCases: CaaCases; + caseTypes: SubNavigation[]; + selectedCases: SelectedCases; + assignedCasesLastError: HttpErrorResponse; + unassignedCasesLastError: HttpErrorResponse; +} + +export const initialState: CaaCasesState = { + assignedCases: null, + unassignedCases: null, + caseTypes: [], + selectedCases: {}, + assignedCasesLastError: null, + unassignedCasesLastError: null +}; + +export function caaCasesReducer(state = initialState, action: fromCaaActions.CaaCasesActions): CaaCasesState { + switch (action.type) { + case fromCaaActions.LOAD_ASSIGNED_CASES_SUCCESS: + return { ...state, assignedCases: action.payload, assignedCasesLastError: null }; + case fromCaaActions.LOAD_ASSIGNED_CASES_FAILURE: + return { ...state, assignedCases: { idField: '', columnConfigs: [], data: [] }, assignedCasesLastError: action.payload }; + case fromCaaActions.LOAD_UNASSIGNED_CASES_SUCCESS: + return { ...state, unassignedCases: action.payload, unassignedCasesLastError: null }; + case fromCaaActions.LOAD_UNASSIGNED_CASES_FAILURE: + return { ...state, unassignedCases: { idField: '', columnConfigs: [], data: [] }, unassignedCasesLastError: action.payload }; + case fromCaaActions.LOAD_CASE_TYPES_SUCCESS: + return { ...state, caseTypes: action.payload }; + case fromCaaActions.UPDATE_SELECTION_FOR_CASE_TYPE: + const selectedCases: SelectedCases = { ...state.selectedCases }; + selectedCases[action.payload.casetype] = action.payload.cases; + return { ...state, selectedCases }; + default: + return state; + } +} + +export const getAssignedCases = (state: CaaCasesState) => state.assignedCases; +export const getAssignedCasesError = (state: CaaCasesState) => state.assignedCasesLastError; +export const getUnassignedCases = (state: CaaCasesState) => state.unassignedCases; +export const getUnassignedCasesError = (state: CaaCasesState) => state.unassignedCasesLastError; +export const getCaseTypes = (state: CaaCasesState) => state.caseTypes; diff --git a/src/cases/store/reducers/index.ts b/src/cases/store/reducers/index.ts new file mode 100644 index 000000000..8963c3c70 --- /dev/null +++ b/src/cases/store/reducers/index.ts @@ -0,0 +1,21 @@ +import { ActionReducerMap, createFeatureSelector } from '@ngrx/store'; +import * as fromCaaCases from './caa-cases.reducer'; +import * as fromCaseShare from './share-case.reducer'; + +export interface CaaCasesState { + caaCases: fromCaaCases.CaaCasesState; + caseShare: fromCaseShare.ShareCasesState; +} + +export const reducers: ActionReducerMap = { + caaCases: fromCaaCases.caaCasesReducer, + caseShare: fromCaseShare.shareCasesReducer +}; + +export const getRootCaaCases = createFeatureSelector( + 'caaCases' +); + +export * from './caa-cases.reducer'; +export * from './share-case.reducer'; + diff --git a/src/cases/store/reducers/share-case.reducer.spec.ts b/src/cases/store/reducers/share-case.reducer.spec.ts new file mode 100644 index 000000000..d7e17ee4e --- /dev/null +++ b/src/cases/store/reducers/share-case.reducer.spec.ts @@ -0,0 +1,273 @@ +import { SharedCase } from '@hmcts/rpx-xui-common-lib/lib/models/case-share.model'; +import * as fromActions from '../actions/share-case.action'; +import * as fromReducer from './share-case.reducer'; + +describe('Share case reducer', () => { + describe('Actions', () => { + let initialState; + + beforeEach(() => { + initialState = fromReducer.initialSharedCasesState; + }); + + it('should set correct object', () => { + const payload = { + sharedCases: [] + }; + const action = new fromActions.AddShareAssignedCases(payload); + const state = fromReducer.shareCasesReducer(initialState, action); + const mockState = { shareAssignedCases: [], shareUnassignedCases: [], loading: false, error: undefined, users: [] }; + expect(state).toEqual(mockState); + }); + + it('should load state when navigate to share assigned case', () => { + const selectedCases = [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }]; + const action = new fromActions.NavigateToShareAssignedCases(selectedCases); + const state = fromReducer.shareCasesReducer(initialState, action); + expect(state.shareAssignedCases.length).toEqual(2); + }); + + it('should load state when navigate to share unassigned case', () => { + const selectedCases = [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }]; + const action = new fromActions.NavigateToShareUnassignedCases(selectedCases); + const state = fromReducer.shareCasesReducer(initialState, action); + expect(state.shareUnassignedCases.length).toEqual(2); + }); + + it('should load share assigned case', () => { + const selectedCases = []; + const action = new fromActions.LoadShareAssignedCases(selectedCases); + const state = fromReducer.shareCasesReducer(initialState, action); + expect(state.shareAssignedCases.length).toEqual(0); + expect(state.loading).toBeTruthy(); + }); + + it('should load share unassigned case', () => { + const selectedCases = []; + const action = new fromActions.LoadShareUnassignedCases(selectedCases); + const state = fromReducer.shareCasesReducer(initialState, action); + expect(state.shareUnassignedCases.length).toEqual(0); + expect(state.loading).toBeTruthy(); + }); + + it('should load share assigned case', () => { + const payload = { + path: [], + sharedCases: [ + { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, + { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' } + ] + }; + const action = new fromActions.AddShareAssignedCaseGo(payload); + const state = fromReducer.shareCasesReducer(initialState, action); + expect(state.shareAssignedCases.length).toEqual(2); + }); + + it('should load share unassigned case', () => { + const payload = { + path: [], + sharedCases: [ + { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, + { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' } + ] + }; + const action = new fromActions.AddShareUnassignedCaseGo(payload); + const state = fromReducer.shareCasesReducer(initialState, action); + expect(state.shareUnassignedCases.length).toEqual(2); + }); + + it('should load share assigned case with case type', () => { + initialState = { + shareAssignedCases: [ + { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, + { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' } + ] + }; + const caseFromNode = [{ caseId: '1', caseTitle: '' }, { caseId: '2', caseTitle: '' }]; + const action = new fromActions.LoadShareAssignedCasesSuccess(caseFromNode); + const state = fromReducer.shareCasesReducer(initialState, action); + expect(state.shareAssignedCases.length).toEqual(2); + expect(state.shareAssignedCases[0].caseTypeId).toEqual('type1'); + expect(state.shareAssignedCases[0].caseTitle).toEqual('James123'); + }); + + it('should load share unassigned case with case type', () => { + initialState = { + shareUnassignedCases: [ + { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, + { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' } + ] + }; + const caseFromNode = [{ caseId: '1', caseTitle: '' }, { caseId: '2', caseTitle: '' }]; + const action = new fromActions.LoadShareUnassignedCasesSuccess(caseFromNode); + const state = fromReducer.shareCasesReducer(initialState, action); + expect(state.shareUnassignedCases.length).toEqual(2); + expect(state.shareUnassignedCases[0].caseTypeId).toEqual('type1'); + expect(state.shareUnassignedCases[0].caseTitle).toEqual('James123'); + }); + + it('should save selected share assigned cases into store', () => { + const selectedCases = { + sharedCases: [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }] + }; + const action = new fromActions.AddShareAssignedCases(selectedCases); + const state = fromReducer.shareCasesReducer(initialState, action); + expect(state.shareAssignedCases.length).toEqual(2); + }); + + it('should save selected share unassigned cases into store', () => { + const selectedCases = { + sharedCases: [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }] + }; + const action = new fromActions.AddShareUnassignedCases(selectedCases); + const state = fromReducer.shareCasesReducer(initialState, action); + expect(state.shareUnassignedCases.length).toEqual(2); + }); + + it('should save selected share cases without duplication', () => { + const selectedCases = { + sharedCases: [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }] + }; + const addedSelectedCases = { + sharedCases: [{ caseId: '2', caseTitle: 'Steve321' }, { caseId: '3', caseTitle: 'Kenny456' }] + }; + const oldAction = new fromActions.AddShareAssignedCases(selectedCases); + const oldState = fromReducer.shareCasesReducer(initialState, oldAction); + const newAction = new fromActions.AddShareAssignedCases(addedSelectedCases); + const newState = fromReducer.shareCasesReducer(oldState, newAction); + expect(newState.shareAssignedCases.length).toEqual(3); + }); + + it('should delete an assigned case from store', () => { + const selectedCases = { + sharedCases: [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }] + }; + const oldAction = new fromActions.AddShareAssignedCases(selectedCases); + const oldState = fromReducer.shareCasesReducer(initialState, oldAction); + const payload = { + caseId: '1' + }; + const newAction = new fromActions.DeleteAShareAssignedCase(payload); + const newState = fromReducer.shareCasesReducer(oldState, newAction); + expect(newState.shareAssignedCases.length).toEqual(1); + }); + + it('should delete an unassigned case from store', () => { + const selectedCases = { + sharedCases: [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }] + }; + const oldAction = new fromActions.AddShareUnassignedCases(selectedCases); + const oldState = fromReducer.shareCasesReducer(initialState, oldAction); + const payload = { + caseId: '1' + }; + const newAction = new fromActions.DeleteAShareUnassignedCase(payload); + const newState = fromReducer.shareCasesReducer(oldState, newAction); + expect(newState.shareUnassignedCases.length).toEqual(1); + }); + + it('should get state properties for assigned cases', () => { + const selectedCases = { + sharedCases: [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }] + }; + const action = new fromActions.AddShareAssignedCases(selectedCases); + const state = fromReducer.shareCasesReducer(initialState, action); + expect(fromReducer.getShareAssignedCases(state).length).toEqual(2); + }); + + it('should get state properties for unassigned cases', () => { + const selectedCases = { + sharedCases: [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }] + }; + const action = new fromActions.AddShareUnassignedCases(selectedCases); + const state = fromReducer.shareCasesReducer(initialState, action); + expect(fromReducer.getShareUnassignedCases(state).length).toEqual(2); + }); + + it('should load user from org for case success', () => { + const sharedCases = [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }]; + const action = new fromActions.LoadShareAssignedCasesSuccess(sharedCases); + const state = fromReducer.shareCasesReducer(initialState, action); + expect(fromReducer.getOrganisationUsers(state)).toBeTruthy(); + }); + + it('should synchronize state to store for assigned cases', () => { + const sharedCases = [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }]; + const action = new fromActions.SynchronizeStateToStoreAssignedCases(sharedCases); + const state = fromReducer.shareCasesReducer(initialState, action); + expect(fromReducer.getShareAssignedCases(state).length).toEqual(2); + }); + + it('should synchronize state to store for unassigned cases', () => { + const sharedCases = [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }]; + const action = new fromActions.SynchronizeStateToStoreUnassignedCases(sharedCases); + const state = fromReducer.shareCasesReducer(initialState, action); + expect(fromReducer.getShareUnassignedCases(state).length).toEqual(2); + }); + + it('should assign user to case success', () => { + const sharedCases = [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }]; + const action = new fromActions.AssignUsersToAssignedCaseSuccess(sharedCases); + const state = fromReducer.shareCasesReducer(initialState, action); + expect(fromReducer.getShareAssignedCases(state).length).toEqual(2); + }); + + it('should reset state if share case completed', () => { + const action = new fromActions.ResetAssignedCaseSelection(); + const state = fromReducer.shareCasesReducer(initialState, action); + expect(fromReducer.getShareAssignedCases(state).length).toEqual(0); + }); + + it('should sort users', () => { + const sharedCases = [ + { + caseId: '9417373995765131', + caseTitle: 'Neha Vs Sanjet', + sharedWith: [ + { + idamId: 'u444444', + firstName: 'Shaun', + lastName: 'Coldwell', + email: 'shaun.coldwell@woodford.com' + }, + { + idamId: 'u333333', + firstName: 'James', + lastName: 'Priest', + email: 'james.priest@woodford.com' + } + ] + }, + { + caseId: '9417373995765133', + caseTitle: 'Sam Green Vs Williams Lee', + sharedWith: [ + { + idamId: 'u666666', + firstName: 'Kate', + lastName: 'Grant', + email: 'kate.grant@lambbrooks.com' + }, + { + idamId: 'u888888', + firstName: 'Joel', + lastName: 'Molloy', + email: 'joel.molloy@lambbrooks.com' + } + ] + } + ]; + const sortedCases: SharedCase[] = fromReducer.sortedUserInCases(sharedCases); + expect(sortedCases[0].caseId).toEqual('9417373995765131'); + expect(sortedCases[0].sharedWith[0].firstName).toEqual('James'); + expect(sortedCases[0].sharedWith[1].firstName).toEqual('Shaun'); + expect(sortedCases[1].caseId).toEqual('9417373995765133'); + expect(sortedCases[1].sharedWith[0].firstName).toEqual('Joel'); + expect(sortedCases[1].sharedWith[1].firstName).toEqual('Kate'); + }); + + afterEach(() => { + initialState = {}; + }); + }); +}); diff --git a/src/cases/store/reducers/share-case.reducer.ts b/src/cases/store/reducers/share-case.reducer.ts new file mode 100644 index 000000000..abcc1391e --- /dev/null +++ b/src/cases/store/reducers/share-case.reducer.ts @@ -0,0 +1,250 @@ +import { SharedCase } from '@hmcts/rpx-xui-common-lib/lib/models/case-share.model'; +import { UserDetails } from '@hmcts/rpx-xui-common-lib/lib/models/user-details.model'; +import * as ShareCasesActions from '../actions/share-case.action'; + +export interface ShareCasesState { + shareAssignedCases: SharedCase[]; + shareUnassignedCases: SharedCase[]; + loading: boolean; + error: Error; + users: UserDetails[]; +} + +export const initialSharedCasesState: ShareCasesState = { + shareAssignedCases: [], + shareUnassignedCases: [], + loading: false, + error: undefined, + users: [] +}; + +export function shareCasesReducer( + state: ShareCasesState = initialSharedCasesState, + action: ShareCasesActions.Actions): ShareCasesState { + switch (action.type) { + case ShareCasesActions.NAVIGATE_TO_SHARE_ASSIGNED_CASES: + const navigateToShareAssignedCases = state.shareAssignedCases.slice(); + for (const aCase of action.payload) { + if (!navigateToShareAssignedCases.some((hasACase) => hasACase.caseId === aCase.caseId)) { + navigateToShareAssignedCases.push(aCase); + } + } + return { + ...state, + shareAssignedCases: navigateToShareAssignedCases + }; + case ShareCasesActions.LOAD_SHARE_ASSIGNED_CASES: + return { + ...state, + loading: true + }; + case ShareCasesActions.LOAD_SHARE_ASSIGNED_CASES_SUCCESS: + const casesInStore = state.shareAssignedCases.slice(); + const casesFromNode: SharedCase[] = sortedUserInCases(action.payload); + const casesWithTypes = []; + for (const aCase of casesInStore) { + const intersectionCase = casesFromNode.find((nodeCase) => nodeCase.caseId === aCase.caseId); + if (intersectionCase && intersectionCase.caseId) { + const caseTypeId = aCase.caseTypeId ? aCase.caseTypeId : null; + const caseTitle = aCase.caseTitle ? aCase.caseTitle : null; + const newCase: SharedCase = { + ...intersectionCase, + caseTypeId, + caseTitle + }; + casesWithTypes.push(newCase); + } else { + casesWithTypes.push(aCase); + } + } + return { + ...state, + shareAssignedCases: casesWithTypes, + loading: false + }; + case ShareCasesActions.LOAD_SHARE_ASSIGNED_CASES_FAILURE: + return { + ...state, + error: action.payload, + loading: false + }; + case ShareCasesActions.ADD_SHARE_ASSIGNED_CASES: + const addShareAssignedCases = state.shareAssignedCases.slice(); + for (const aCase of action.payload.sharedCases) { + if (!addShareAssignedCases.some((hasACase) => hasACase.caseId === aCase.caseId)) { + addShareAssignedCases.push(aCase); + } + } + return { + ...state, + shareAssignedCases: addShareAssignedCases + }; + case ShareCasesActions.ADD_SHARE_ASSIGNED_CASES_GO: + const addShareAssignedCasesGo = state.shareAssignedCases.slice(); + for (const aCase of action.payload.sharedCases) { + if (!addShareAssignedCasesGo.some((hasACase) => hasACase.caseId === aCase.caseId)) { + addShareAssignedCasesGo.push(aCase); + } + } + return { + ...state, + shareAssignedCases: addShareAssignedCasesGo + }; + case ShareCasesActions.DELETE_A_SHARE_ASSIGNED_CASE: + const caseInStore4Delete = state.shareAssignedCases.slice(); + for (let i = 0, l = caseInStore4Delete.length; i < l; i++) { + if (caseInStore4Delete[i].caseId === action.payload.caseId) { + caseInStore4Delete.splice(i, 1); + break; + } + } + return { + ...state, + shareAssignedCases: caseInStore4Delete + }; + case ShareCasesActions.SYNCHRONIZE_STATE_TO_STORE_ASSIGNED_CASES: + return { + ...state, + shareAssignedCases: action.payload + }; + case ShareCasesActions.ASSIGN_USERS_TO_ASSIGNED_CASE_SUCCESS: + return { + ...state, + shareAssignedCases: action.payload, + loading: true + }; + case ShareCasesActions.RESET_ASSIGNED_CASE_SELECTION: + return { + ...state, + shareAssignedCases: [], + loading: false + }; + case ShareCasesActions.NAVIGATE_TO_SHARE_UNASSIGNED_CASES: + const navigateToShareUnassignedCases = state.shareUnassignedCases.slice(); + for (const aCase of action.payload) { + if (!navigateToShareUnassignedCases.some((hasACase) => hasACase.caseId === aCase.caseId)) { + navigateToShareUnassignedCases.push(aCase); + } + } + return { + ...state, + shareUnassignedCases: navigateToShareUnassignedCases + }; + case ShareCasesActions.LOAD_SHARE_UNASSIGNED_CASES: + return { + ...state, + loading: true + }; + case ShareCasesActions.LOAD_SHARE_UNASSIGNED_CASES_SUCCESS: + const unassignedCasesInStore = state.shareUnassignedCases.slice(); + const unassignedCasesFromNode: SharedCase[] = sortedUserInCases(action.payload); + const unassignedCasesWithTypes = []; + for (const aCase of unassignedCasesInStore) { + const intersectionCase = unassignedCasesFromNode.find((nodeCase) => nodeCase.caseId === aCase.caseId); + if (intersectionCase && intersectionCase.caseId) { + const caseTypeId = aCase.caseTypeId ? aCase.caseTypeId : null; + const caseTitle = aCase.caseTitle ? aCase.caseTitle : null; + const newCase: SharedCase = { + ...intersectionCase, + caseTypeId, + caseTitle + }; + unassignedCasesWithTypes.push(newCase); + } else { + unassignedCasesWithTypes.push(aCase); + } + } + return { + ...state, + shareUnassignedCases: unassignedCasesWithTypes, + loading: false + }; + case ShareCasesActions.LOAD_SHARE_UNASSIGNED_CASES_FAILURE: + return { + ...state, + error: action.payload, + loading: false + }; + case ShareCasesActions.ADD_SHARE_UNASSIGNED_CASES: + const addShareUnassignedCases = state.shareUnassignedCases.slice(); + for (const aCase of action.payload.sharedCases) { + if (!addShareUnassignedCases.some((hasACase) => hasACase.caseId === aCase.caseId)) { + addShareUnassignedCases.push(aCase); + } + } + return { + ...state, + shareUnassignedCases: addShareUnassignedCases + }; + case ShareCasesActions.ADD_SHARE_UNASSIGNED_CASES_GO: + const addShareUnassignedCasesGo = state.shareUnassignedCases.slice(); + for (const aCase of action.payload.sharedCases) { + if (!addShareUnassignedCasesGo.some((hasACase) => hasACase.caseId === aCase.caseId)) { + addShareUnassignedCasesGo.push(aCase); + } + } + return { + ...state, + shareUnassignedCases: addShareUnassignedCasesGo + }; + case ShareCasesActions.DELETE_A_SHARE_UNASSIGNED_CASE: + const unassignedCaseInStore4Delete = state.shareUnassignedCases.slice(); + for (let i = 0, l = unassignedCaseInStore4Delete.length; i < l; i++) { + if (unassignedCaseInStore4Delete[i].caseId === action.payload.caseId) { + unassignedCaseInStore4Delete.splice(i, 1); + break; + } + } + return { + ...state, + shareUnassignedCases: unassignedCaseInStore4Delete + }; + case ShareCasesActions.LOAD_USERS_FROM_ORG_FOR_CASE_SUCCESS: + return { + ...state, + users: action.payload + }; + case ShareCasesActions.SYNCHRONIZE_STATE_TO_STORE_UNASSIGNED_CASES: + return { + ...state, + shareUnassignedCases: action.payload + }; + case ShareCasesActions.ASSIGN_USERS_TO_UNASSIGNED_CASE_SUCCESS: + return { + ...state, + shareUnassignedCases: action.payload, + loading: true + }; + case ShareCasesActions.RESET_UNASSIGNED_CASE_SELECTION: + return { + ...state, + shareUnassignedCases: [], + loading: false + }; + default: + return state; + } +} + +export function sortedUserInCases(pendingSortedCases: SharedCase[]): SharedCase[] { + const cases: SharedCase[] = []; + for (const aCase of pendingSortedCases) { + if (aCase.sharedWith) { + const sortedUsers: UserDetails[] = aCase.sharedWith.slice().sort((user1, user2) => { + return user1.firstName > user2.firstName ? 1 : (user2.firstName > user1.firstName ? -1 : 0); + }); + const caseWithSortedUser = { + ...aCase, + sharedWith: sortedUsers + }; + cases.push(caseWithSortedUser); + } else { + cases.push(aCase); + } + } + return cases; +} + +export const getShareAssignedCases = (state: ShareCasesState) => state.shareAssignedCases; +export const getShareUnassignedCases = (state: ShareCasesState) => state.shareUnassignedCases; +export const getOrganisationUsers = (state: ShareCasesState) => state.users; diff --git a/src/cases/store/selectors/caa-cases.selector.spec.ts b/src/cases/store/selectors/caa-cases.selector.spec.ts new file mode 100644 index 000000000..6bdb53907 --- /dev/null +++ b/src/cases/store/selectors/caa-cases.selector.spec.ts @@ -0,0 +1,75 @@ +import { TestBed } from '@angular/core/testing'; +import { select, Store, StoreModule } from '@ngrx/store'; +import { reducers } from '../reducers'; +import { CaaCasesState, initialState } from '../reducers/caa-cases.reducer'; +import { + getAllAssignedCases, + getAllAssignedCasesError, + getAllCaseTypes, + getAllUnassignedCases, + getAllUnassignedCasesError, + getSelectedCases +} from './caa-cases.selector'; + +describe('CaaCases selectors', () => { + let store: Store; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + StoreModule.forRoot({}), + StoreModule.forFeature('caaCases', reducers) + ] + }); + store = TestBed.inject(Store); + spyOn(store, 'dispatch').and.callThrough(); + }); + + it('should return all assigned cases', () => { + let result; + store.pipe(select(getAllAssignedCases)).subscribe((value) => { + result = value; + }); + expect(result).toEqual(initialState.assignedCases); + }); + + it('should return all assigned cases error', () => { + let result; + store.pipe(select(getAllAssignedCasesError)).subscribe((value) => { + result = value; + }); + expect(result).toEqual(initialState.assignedCasesLastError); + }); + + it('should return all unassigned cases', () => { + let result; + store.pipe(select(getAllUnassignedCases)).subscribe((value) => { + result = value; + }); + expect(result).toEqual(initialState.unassignedCases); + }); + + it('should return all unassigned cases error', () => { + let result; + store.pipe(select(getAllUnassignedCasesError)).subscribe((value) => { + result = value; + }); + expect(result).toEqual(initialState.assignedCasesLastError); + }); + + it('should return all case types', () => { + let result; + store.pipe(select(getAllCaseTypes)).subscribe((value) => { + result = value; + }); + expect(result).toEqual(initialState.caseTypes); + }); + + it('should return selected cases', () => { + let result; + store.pipe(select(getSelectedCases)).subscribe((value) => { + result = value; + }); + expect(result).toEqual(initialState.selectedCases); + }); +}); diff --git a/src/cases/store/selectors/caa-cases.selector.ts b/src/cases/store/selectors/caa-cases.selector.ts new file mode 100644 index 000000000..83966be27 --- /dev/null +++ b/src/cases/store/selectors/caa-cases.selector.ts @@ -0,0 +1,58 @@ +import { createSelector } from '@ngrx/store'; +import { AppUtils } from '../../../app/utils/app-utils'; +import * as fromFeature from '../reducers'; + +export const getCaaCasesState = createSelector( + fromFeature.getRootCaaCases, + (state: fromFeature.CaaCasesState) => state.caaCases +); + +export const getAllAssignedCases = createSelector( + getCaaCasesState, + (caaCases) => caaCases.assignedCases +); + +export const getAllAssignedCasesError = createSelector( + getCaaCasesState, + fromFeature.getAssignedCasesError +); + +export const getAllAssignedCaseData = createSelector( + getAllAssignedCases, + (caaCases) => caaCases ? caaCases.data : null +); + +export const getAllUnassignedCases = createSelector( + getCaaCasesState, + (caaCases) => caaCases.unassignedCases +); + +export const getAllUnassignedCasesError = createSelector( + getCaaCasesState, + fromFeature.getUnassignedCasesError +); + +export const getAllUnassignedCaseData = createSelector( + getAllUnassignedCases, + (caaCases) => caaCases ? caaCases.data : null +); + +export const getAllCaseTypes = createSelector( + getCaaCasesState, + (caaCases) => caaCases.caseTypes +); + +export const getSelectedCases = createSelector( + getCaaCasesState, + (caaCases) => caaCases.selectedCases +); + +export const anySelectedCases = createSelector( + getSelectedCases, + (selectedCases) => AppUtils.atLeastOneCase(selectedCases) +); + +export const getSelectedCasesList = createSelector( + getSelectedCases, + (selectedCases) => AppUtils.getSelectedItemsList(selectedCases) +); diff --git a/src/cases/store/selectors/index.ts b/src/cases/store/selectors/index.ts new file mode 100644 index 000000000..bcdc699d1 --- /dev/null +++ b/src/cases/store/selectors/index.ts @@ -0,0 +1,2 @@ +export * from './caa-cases.selector'; +export * from './share-case.selector'; diff --git a/src/cases/store/selectors/share-case.selector.ts b/src/cases/store/selectors/share-case.selector.ts new file mode 100644 index 000000000..289872492 --- /dev/null +++ b/src/cases/store/selectors/share-case.selector.ts @@ -0,0 +1,23 @@ +import { createSelector } from '@ngrx/store'; + +import * as fromFeature from '../reducers'; + +export const getCaseShareState = createSelector( + fromFeature.getRootCaaCases, + (state: fromFeature.CaaCasesState) => state.caseShare +); + +export const getShareAssignedCaseListState = createSelector( + getCaseShareState, + fromFeature.getShareAssignedCases +); + +export const getShareUnassignedCaseListState = createSelector( + getCaseShareState, + fromFeature.getShareUnassignedCases +); + +export const getOrganisationUsersState = createSelector( + getCaseShareState, + fromFeature.getOrganisationUsers +); diff --git a/src/cases/store/selectors/share-case.selectors.spec.ts b/src/cases/store/selectors/share-case.selectors.spec.ts new file mode 100644 index 000000000..b11b194d7 --- /dev/null +++ b/src/cases/store/selectors/share-case.selectors.spec.ts @@ -0,0 +1,68 @@ +import { TestBed } from '@angular/core/testing'; +import { Router } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { select, Store, StoreModule } from '@ngrx/store'; +import { OrganisationState } from '../../../organisation/store'; +import { UserState } from '../../../users/store'; +import { CaaCasesComponent } from '../../containers'; +import { CaaCasesService } from '../../services'; +import { CaaCasesState, getShareAssignedCaseListState, reducers } from '../index'; + +describe('Share case selectors', () => { + let store: Store; + let organisationStore: Store; + let userStore: Store; + let caaCasesService: jasmine.SpyObj; + const router: any = {}; + + beforeEach(() => { + caaCasesService = jasmine.createSpyObj( + 'caaCasesService', + [ + 'getCaaCases', + 'getCaaCaseTypes', + 'storeSessionState', + 'retrieveSessionState', + 'removeSessionState' + ] + ); + TestBed.configureTestingModule({ + imports: [ + StoreModule.forRoot({}), + StoreModule.forFeature('cases', reducers), + RouterTestingModule + ], + providers: [ + { provide: Router, useValue: router }, + { provide: CaaCasesService, useValue: caaCasesService } + ] + }); + store = TestBed.inject(Store); + organisationStore = TestBed.inject(Store); + userStore = TestBed.inject(Store); + spyOn(store, 'dispatch').and.callThrough(); + }); + + describe('get share case state', () => { + xit('should return search state', () => { + const caseListComponent = new CaaCasesComponent(store, organisationStore, userStore, router, caaCasesService); + caseListComponent.selectedCases = [{ + case_id: '1', + case_fields: { + solsSolicitorAppReference: 'James123' + } + }, { + case_id: '2', + case_fields: { + solsSolicitorAppReference: 'Steve321' + } + }]; + caseListComponent.shareAssignedCaseSubmit(); + let result = []; + store.pipe(select(getShareAssignedCaseListState)).subscribe((value) => { + result = value; + }); + expect(result.length).toEqual(2); + }); + }); +}); diff --git a/src/cases/util/caa-cases.util.spec.ts b/src/cases/util/caa-cases.util.spec.ts new file mode 100644 index 000000000..bbd348a21 --- /dev/null +++ b/src/cases/util/caa-cases.util.spec.ts @@ -0,0 +1,115 @@ +import { FormControl } from '@angular/forms'; +import { User } from '@hmcts/rpx-xui-common-lib'; +import { CaaCasesUtil } from './caa-cases.util'; + +describe('CaaCasesUtil', () => { + let control: FormControl; + + beforeEach(() => { + control = new FormControl({}); + }); + + it('getCaaNavItems', () => { + const response = { + total: 11, + cases: [], + case_types_results: [ + { + total: 1, + case_type_id: 'FT_MasterCaseType' + }, + { + total: 1, + case_type_id: 'FT_ComplexCollectionComplex' + }, + { + total: 5, + case_type_id: 'FT_Conditionals' + }, + { + total: 4, + case_type_id: 'FT_ComplexOrganisation' + } + ] + }; + const results = CaaCasesUtil.getCaaNavItems(response); + expect(results.length).toEqual(4); + expect(results[0].text).toEqual('FT_MasterCaseType'); + expect(results[1].text).toEqual('FT_ComplexCollectionComplex'); + expect(results[2].text).toEqual('FT_Conditionals'); + expect(results[3].text).toEqual('FT_ComplexOrganisation'); + expect(results[3].total).toEqual(4); + }); + + it('should fail caseReference validation if input is less than 16 digits after removing separators', () => { + control.setValue('1234 12-- -34-1234 123-'); + const caseReferenceValidator = CaaCasesUtil.caseReferenceValidator(); + expect(caseReferenceValidator(control)).toEqual({ caseReference: true }); + }); + + it('should fail caseReference validation if input is more than 16 digits after removing separators', () => { + control.setValue('1234 12-- -34-1234 12345'); + const caseReferenceValidator = CaaCasesUtil.caseReferenceValidator(); + expect(caseReferenceValidator(control)).toEqual({ caseReference: true }); + }); + + it('should pass caseReference validation if input is exactly 16 digits after removing separators', () => { + control.setValue('1234 12-- -34-1234 1234-'); + const caseReferenceValidator = CaaCasesUtil.caseReferenceValidator(); + expect(caseReferenceValidator(control)).toBeNull(); + }); + + it('should fail caseReference validation if input is null', () => { + control.setValue(null); + const caseReferenceValidator = CaaCasesUtil.caseReferenceValidator(); + expect(caseReferenceValidator(control)).toEqual({ caseReference: true }); + }); + + it('should fail caseReference validation if input is the empty string', () => { + control.setValue(''); + const caseReferenceValidator = CaaCasesUtil.caseReferenceValidator(); + expect(caseReferenceValidator(control)).toEqual({ caseReference: true }); + }); + + it('should fail caseReference validation if input contains one or more letters', () => { + control.setValue('1234-1234 1234123A'); + const caseReferenceValidator = CaaCasesUtil.caseReferenceValidator(); + expect(caseReferenceValidator(control)).toEqual({ caseReference: true }); + }); + + it('should fail caseReference validation if input contains one or more symbols (except for "-")', () => { + control.setValue('1234-1234 1234_1234'); + const caseReferenceValidator = CaaCasesUtil.caseReferenceValidator(); + expect(caseReferenceValidator(control)).toEqual({ caseReference: true }); + }); + + it('should pass assigneeName validation if input contains one or more characters', () => { + const user: User = { + userIdentifier: 'user123', + fullName: 'Lindsey Johnson', + email: 'user@test.com', + status: 'pending' + }; + control.setValue(user); + const assigneeNameValidator = CaaCasesUtil.assigneeNameValidator(); + expect(assigneeNameValidator(control)).toBeNull(); + }); + + it('should fail assigneeName validation if input is empty', () => { + control.setValue(''); + const assigneeNameValidator = CaaCasesUtil.assigneeNameValidator(); + expect(assigneeNameValidator(control)).toEqual({ assigneeName: true }); + }); + + it('should pass assigneeName validation if input string is in correct format', () => { + control.setValue('Lindsey Johnson - user@test.com'); + const assigneeNameValidator = CaaCasesUtil.assigneeNameValidator(); + expect(assigneeNameValidator(control)).toBeNull(); + }); + + it('should fail assigneeName validation if input is not in expected format string', () => { + control.setValue('test string'); + const assigneeNameValidator = CaaCasesUtil.assigneeNameValidator(); + expect(assigneeNameValidator(control)).toEqual({ assigneeName: true }); + }); +}); diff --git a/src/cases/util/caa-cases.util.ts b/src/cases/util/caa-cases.util.ts new file mode 100644 index 000000000..c8ef2a00e --- /dev/null +++ b/src/cases/util/caa-cases.util.ts @@ -0,0 +1,62 @@ +import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms'; +import { SubNavigation } from '@hmcts/rpx-xui-common-lib/lib/gov-ui/components/hmcts-sub-navigation/hmcts-sub-navigation.component'; +import { CaseTypesResultsResponse } from '../models/caa-cases.model'; + +export class CaaCasesUtil { + public static getCaaNavItems(response: CaseTypesResultsResponse): SubNavigation[] { + const result = new Array(); + if (response.case_types_results) { + response.case_types_results.forEach((caseType) => { + if (caseType.total > 0) { + result.push({ + text: caseType.case_type_id, + href: caseType.case_type_id, + active: false, + total: caseType.total + }); + } + }); + } + return result; + } + + /** + * Validates case reference entry. Excluding spaces and '-' characters, it accepts exactly 16 digits only. All other + * characters are invalid. (Taken from + * https://github.com/hmcts/rpx-xui-webapp/blob/feature/global-search/src/search/utils/search-validators.ts) + * @returns `ValidationErrors` object if validation fails; `null` if it passes + */ + public static caseReferenceValidator(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + // Use template literal to coerce control.value to a string in case it is null + if (!(`${control.value}`).replace(/[\s-]/g, '').match(/^\d{16}$/)) { + return { caseReference: true }; + } + return null; + }; + } + + public static assigneeNameValidator(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + if (!control.value) { + return { assigneeName: true }; + } + if (typeof control.value === 'string' && (!control.value.includes('@') || !control.value.includes('-'))) { + return { assigneeName: true }; + } + return null; + }; + } + + public static assigneeNameValidator2(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + if (!control.value) { + return { assigneeName: true }; + } + if (typeof control.value !== 'string' || control.value.includes('@') || control.value.includes('-')) { + return { assigneeName: true }; + } + return null; + }; + } +} From 01cfeca9898b56994e577deada1de6f3c54a499f Mon Sep 17 00:00:00 2001 From: Shaed Parkar Date: Wed, 27 Mar 2024 12:36:48 +0000 Subject: [PATCH 09/44] Update nodejs version to 3.1.0 in Chart.yaml --- charts/xui-mo-webapp/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/xui-mo-webapp/Chart.yaml b/charts/xui-mo-webapp/Chart.yaml index 64b24f350..b28d5f121 100644 --- a/charts/xui-mo-webapp/Chart.yaml +++ b/charts/xui-mo-webapp/Chart.yaml @@ -7,7 +7,7 @@ maintainers: - name: HMCTS RPX XUI dependencies: - name: nodejs - version: 3.0.0 + version: 3.1.0 repository: 'https://hmctspublic.azurecr.io/helm/v1/repo/' - name: redis version: 16.13.0 From 66a16635b15c9d853d6f188c057dc7b564a3dfd6 Mon Sep 17 00:00:00 2001 From: Shaed Parkar Date: Thu, 28 Mar 2024 13:38:33 +0000 Subject: [PATCH 10/44] Refactor case sharing component and filter component --- .../cases-filter/cases-filter.component.ts | 1 + .../case-share-confirm.component.ts | 18 +++++---------- .../case-share/case-share.component.ts | 22 ++++++------------- 3 files changed, 13 insertions(+), 28 deletions(-) diff --git a/src/cases/components/cases-filter/cases-filter.component.ts b/src/cases/components/cases-filter/cases-filter.component.ts index 0531f8ade..f25814ad7 100644 --- a/src/cases/components/cases-filter/cases-filter.component.ts +++ b/src/cases/components/cases-filter/cases-filter.component.ts @@ -63,6 +63,7 @@ export class CasesFilterComponent implements OnInit, OnChanges{ }); this.form.controls.filterOption.valueChanges.subscribe((value: CaaCasesFilterType) => { + this.filterApplied = false; this.selectFilterOption(value); this.handleOnFilterOptionChange(value); }); diff --git a/src/cases/containers/case-share-confirm/case-share-confirm.component.ts b/src/cases/containers/case-share-confirm/case-share-confirm.component.ts index 85aed976f..8feae1f13 100644 --- a/src/cases/containers/case-share-confirm/case-share-confirm.component.ts +++ b/src/cases/containers/case-share-confirm/case-share-confirm.component.ts @@ -30,19 +30,11 @@ export class CaseShareConfirmComponent implements OnInit { } public ngOnInit(): void { - // Set fnTitle, backLink, changeLink (these two links are the same as each other) and confirmLink depending on - // whether navigation is via the Unassigned Cases or Assigned Cases page - if (this.url.startsWith('/unassigned-cases')) { - this.fnTitle = 'Share a case'; - this.backLink = '/unassigned-cases/case-share'; - this.changeLink = '/unassigned-cases/case-share'; - this.completeLink = `/unassigned-cases/case-share-complete/${this.pageType}`; - } else { - this.fnTitle = 'Manage case sharing'; - this.backLink = '/assigned-cases/case-share'; - this.changeLink = '/assigned-cases/case-share'; - this.completeLink = `/assigned-cases/case-share-complete/${this.pageType}`; - } + this.fnTitle = 'Manage case assignments'; + this.backLink = '/cases/case-share'; + this.changeLink = '/cases/case-share'; + this.completeLink = `/cases/case-share-complete/${this.pageType}`; + this.shareCases$ = this.pageType === CaaCasesPageType.UnassignedCases ? this.store.pipe(select(fromCasesFeature.getShareUnassignedCaseListState)) : this.store.pipe(select(fromCasesFeature.getShareAssignedCaseListState)); diff --git a/src/cases/containers/case-share/case-share.component.ts b/src/cases/containers/case-share/case-share.component.ts index 2217c1d83..0b8ad0acb 100644 --- a/src/cases/containers/case-share/case-share.component.ts +++ b/src/cases/containers/case-share/case-share.component.ts @@ -46,21 +46,13 @@ export class CaseShareComponent implements OnInit { // is via the Unassigned Cases or Assigned Cases page const url = router.state.url.substring(0, router.state.url.indexOf('/', 1)); // Set backLink and confirmLink only if the URL is either "/unassigned-cases" or "/assigned-cases" - if (url === '/unassigned-cases' || url === '/assigned-cases') { - this.backLink = `${url}`; - this.confirmLink = `${url}/case-share-confirm/${this.pageType}`; - } - if (url === '/unassigned-cases') { - this.fnTitle = 'Share a case'; - this.title = 'Add recipient'; - this.addUserLabel = 'Enter email address'; - this.showRemoveUsers = false; - } else if (url === '/assigned-cases') { - this.fnTitle = 'Manage case sharing'; - this.title = 'Manage shared access to a case'; - this.addUserLabel = 'Add people to share access to the selected cases'; - this.showRemoveUsers = true; - } + this.backLink = '/cases'; + this.confirmLink = `${url}/case-share-confirm/${this.pageType}`; + + this.fnTitle = 'Action a case'; + this.title = 'Manage case assignments'; + this.addUserLabel = 'Add people to share cases to the selected cases'; + this.showRemoveUsers = true; this.shareCases$ = this.pageType === CaaCasesPageType.UnassignedCases ? this.store.pipe(select(fromCasesFeature.getShareUnassignedCaseListState)) From 45d877e026699c5753f1c45ed423700bee5b2726 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 8 Nov 2024 11:13:08 +0000 Subject: [PATCH 11/44] unassigned cases flow --- api/caaCaseTypes/caaCaseTypes.util.ts | 13 +++++ api/caaCaseTypes/index.ts | 3 +- api/configuration/references.ts | 1 + .../case-share/case-share.component.ts | 1 + src/cases/cases.routing.ts | 10 ++++ .../cases-filter/cases-filter.component.html | 16 ------ .../cases-filter/cases-filter.component.ts | 1 + .../cases-results-table.component.html | 4 ++ .../cases-results-table.component.ts | 35 ++++++++++-- .../accept-cases/accept-cases.component.html | 55 +++++++++++++++++++ .../accept-cases/accept-cases.component.scss | 0 .../accept-cases.component.spec.ts | 0 .../accept-cases/accept-cases.component.ts | 45 +++++++++++++++ .../case-share/case-share.component.ts | 17 ++++-- .../containers/cases/cases.component.html | 17 +++++- src/cases/containers/cases/cases.component.ts | 48 +++++++++++++--- src/cases/containers/index.ts | 4 +- src/cases/models/caa-cases.enum.ts | 8 ++- src/cases/models/caa-cases.model.ts | 4 ++ src/cases/store/actions/share-case.action.ts | 4 +- src/cases/store/effects/caa-cases.effects.ts | 1 + src/cases/store/effects/share-case.effects.ts | 9 ++- src/cases/util/caa-cases.util.ts | 3 +- 23 files changed, 257 insertions(+), 42 deletions(-) create mode 100644 src/cases/containers/accept-cases/accept-cases.component.html create mode 100644 src/cases/containers/accept-cases/accept-cases.component.scss create mode 100644 src/cases/containers/accept-cases/accept-cases.component.spec.ts create mode 100644 src/cases/containers/accept-cases/accept-cases.component.ts diff --git a/api/caaCaseTypes/caaCaseTypes.util.ts b/api/caaCaseTypes/caaCaseTypes.util.ts index a840833ab..94366e605 100644 --- a/api/caaCaseTypes/caaCaseTypes.util.ts +++ b/api/caaCaseTypes/caaCaseTypes.util.ts @@ -1,5 +1,7 @@ import { CaaCasesPageType } from '../caaCases/enums'; import { searchCasesString } from './caaCaseTypes.constants'; +import { getConfigValue } from '../configuration'; +import { UNASSIGNED_CASE_TYPES } from '../configuration/references'; export function getRequestBody(organisationID: string, caaCasesPageType: string, caaCasesFilterValue?: string | string[]) { const organisationAssignedUsersKey = `supplementary_data.orgs_assigned_users.${organisationID}`; @@ -68,3 +70,14 @@ export function getRequestBody(organisationID: string, caaCasesPageType: string, export function getApiPath(ccdPath: string, caseTypes: string) { return `${ccdPath}${searchCasesString}${caseTypes}`; } + +export function addCaseConfiguration(response) { + const resData = response.data; + const unassignedCaseConfig = getConfigValue(UNASSIGNED_CASE_TYPES); + resData.case_types_results.forEach((caseTypeResult) => { + const { case_type_id } = caseTypeResult; + if (unassignedCaseConfig[case_type_id]) { + caseTypeResult.caseConfig = unassignedCaseConfig[case_type_id]; + } + }); +} diff --git a/api/caaCaseTypes/index.ts b/api/caaCaseTypes/index.ts index c2cbcd4ad..33bd287be 100644 --- a/api/caaCaseTypes/index.ts +++ b/api/caaCaseTypes/index.ts @@ -5,7 +5,7 @@ import { RoleAssignmentResponse } from '../caaCases/models/roleAssignmentRespons import { getConfigValue } from '../configuration'; import { CASE_TYPES, SERVICES_MCA_PROXY_API_PATH } from '../configuration/references'; import { EnhancedRequest } from '../models/enhanced-request.interface'; -import { getApiPath, getRequestBody } from './caaCaseTypes.util'; +import { getApiPath, getRequestBody, addCaseConfiguration } from './caaCaseTypes.util'; export async function handleCaaCaseTypes(req: EnhancedRequest, res: Response, next: NextFunction) { const caaCasesPageType = req.query.caaCasesPageType as string; @@ -31,6 +31,7 @@ export async function handleCaaCaseTypes(req: EnhancedRequest, res: Response, ne const payload = getRequestBody(orgId, caaCasesPageType, caaCasesFilterValue); const response = await req.http.post(path, payload); + addCaseConfiguration(response); res.send(response.data); } catch (error) { res.status(500).send({ diff --git a/api/configuration/references.ts b/api/configuration/references.ts index d43307a35..d17234c00 100644 --- a/api/configuration/references.ts +++ b/api/configuration/references.ts @@ -77,6 +77,7 @@ export const REDIS_KEY_PREFIX = 'redis.prefix'; export const SESSION_TIMEOUTS = 'sessionTimeouts'; export const CASE_TYPES = 'caseTypes'; +export const UNASSIGNED_CASE_TYPES = 'unassignedCaseTypes'; // PACT export const PACT_BROKER_URL = 'pact.brokerUrl'; diff --git a/src/caa-cases/containers/case-share/case-share.component.ts b/src/caa-cases/containers/case-share/case-share.component.ts index 2217c1d83..17ab796fa 100644 --- a/src/caa-cases/containers/case-share/case-share.component.ts +++ b/src/caa-cases/containers/case-share/case-share.component.ts @@ -40,6 +40,7 @@ export class CaseShareComponent implements OnInit { public ngOnInit(): void { this.routerState$ = this.store.pipe(select(getRouterState)); this.routerState$.subscribe((router) => { + console.log(router.state); this.init = router.state.queryParams.init; this.pageType = router.state.queryParams.pageType; // Set backLink, fnTitle, title, confirmLink, addUserLabel, and showRemoveUsers depending on whether navigation diff --git a/src/cases/cases.routing.ts b/src/cases/cases.routing.ts index a42866983..b076dc455 100644 --- a/src/cases/cases.routing.ts +++ b/src/cases/cases.routing.ts @@ -5,6 +5,7 @@ import { CaaCasesModule } from './cases.module'; import { CaseShareCompleteComponent, CaseShareComponent, CaseShareConfirmComponent, CasesComponent } from './containers'; import { FeatureToggleAccountGuard } from './guards/feature-toggle.guard'; import { RoleGuard } from './guards/user-role.guard'; +import { AcceptCasesComponent } from './containers/accept-cases/accept-cases.component'; export const ROUTES: Routes = [ { @@ -42,6 +43,15 @@ export const ROUTES: Routes = [ FeatureToggleAccountGuard, RoleGuard ] + }, + { + path: 'accept-cases', + component: AcceptCasesComponent, + canActivate: [ + AuthGuard, + FeatureToggleAccountGuard, + RoleGuard + ] } ]; diff --git a/src/cases/components/cases-filter/cases-filter.component.html b/src/cases/components/cases-filter/cases-filter.component.html index c203d4047..ba9cf3a7b 100644 --- a/src/cases/components/cases-filter/cases-filter.component.html +++ b/src/cases/components/cases-filter/cases-filter.component.html @@ -252,22 +252,6 @@

Filter cases

-
- -
- - - Warning - The tabs below list all of your organisation's cases which are not assigned to any users. You can assign - cases to users by selecting 'Manage cases'. - - You do not need to assigned cases to users if they have 'Access all cases in the organisation' enabled for - that case type. - -
-
-
diff --git a/src/cases/components/cases-filter/cases-filter.component.ts b/src/cases/components/cases-filter/cases-filter.component.ts index f25814ad7..84221990f 100644 --- a/src/cases/components/cases-filter/cases-filter.component.ts +++ b/src/cases/components/cases-filter/cases-filter.component.ts @@ -107,6 +107,7 @@ export class CasesFilterComponent implements OnInit, OnChanges{ } public filterSelectedOrganisationUsers(searchTerm?: string | User): Observable> { + console.log('1232112321'); const filteredUsers = searchTerm && searchTerm.length > 0 ? typeof(searchTerm) === 'string' ? this.selectedOrganisationUsers.filter((user) => this.getDisplayName(user).toLowerCase().includes(searchTerm.toLowerCase())) diff --git a/src/cases/components/cases-results-table/cases-results-table.component.html b/src/cases/components/cases-results-table/cases-results-table.component.html index 8342c913d..80bbc3f0d 100644 --- a/src/cases/components/cases-results-table/cases-results-table.component.html +++ b/src/cases/components/cases-results-table/cases-results-table.component.html @@ -59,3 +59,7 @@ +{{navItems}} +
+ {{noCasesFoundMessage}} +
diff --git a/src/cases/components/cases-results-table/cases-results-table.component.ts b/src/cases/components/cases-results-table/cases-results-table.component.ts index 5c857f2d9..9b2d7c8b8 100644 --- a/src/cases/components/cases-results-table/cases-results-table.component.ts +++ b/src/cases/components/cases-results-table/cases-results-table.component.ts @@ -8,6 +8,7 @@ import { Observable } from 'rxjs'; import * as fromStore from '../../store'; import { CaaCases } from 'api/caaCases/interfaces'; +import { CaaCasesNoDataMessage } from 'src/cases/models/caa-cases.enum'; @Component({ selector: 'app-cases-results-table', @@ -41,11 +42,12 @@ export class CasesResultsTableComponent { } @Input() paginationPageSize: number = 25; - @Input() shareButtonText = 'Share case'; + @Input() shareButtonText; + @Input() selectedFilterType; @Output() public caseSelected = new EventEmitter(); @Output() public pageChanged = new EventEmitter(); - @Output() public shareButtonClicked = new EventEmitter(); + @Output() public shareButtonClicked = new EventEmitter(); // Needed for the tab group public navItems: any[]; @@ -72,6 +74,7 @@ export class CasesResultsTableComponent { } public tabChanged(event: { tab: { textLabel: string }}): void { + console.log(event); this.totalCases = this.navItems.find((data) => data.text === event.tab.textLabel) ? this.navItems.find((data) => data.text === event.tab.textLabel).total : 0; @@ -123,17 +126,22 @@ export class CasesResultsTableComponent { // sharedCases: converters.toShareCaseConverter(this.selectedUnassignedCases, this.currentCaseType) // })); // TODO: emit this action - this.shareButtonClicked.emit(); + this.shareButtonClicked.emit(this.currentCaseType); } private fixCurrentTab(items: any): void { + // TESTING: Add another tab to test case specific navigation + console.log(`test items: ${items}`); + if (items.length > 0){ + items = [...items, { text: 'Asylum', href: 'Asylum', active: false, total: 3 }]; + } this.navItems = items; if (items && items.length > 0) { this.totalCases = items[0].total ? items[0].total : 0; this.setTabItems(items[0].text); } else { this.totalCases = 0; - // this.noCasesFoundMessage = this.getNoCasesFoundMessage(); + this.noCasesFoundMessage = this.getNoCasesFoundMessage(); } } @@ -152,4 +160,23 @@ export class CasesResultsTableComponent { } // this.loadDataFromStore(); } + + public getNoCasesFoundMessage(): string { + console.log(this.navItems); + console.log(this.selectedFilterType); + switch (this.selectedFilterType){ + case 'all-assignees': + return CaaCasesNoDataMessage.NoAssignedCases; + case 'unassigned-cases': + return CaaCasesNoDataMessage.NoUnassignedCases; + case 'assignee-name': + return CaaCasesNoDataMessage.CasesFilterMessage; + case 'new-cases-to-accept': + return CaaCasesNoDataMessage.NoNewCases; + case 'case-reference-number': + return CaaCasesNoDataMessage.NoCaseIdMatches; + default: + return ''; + } + } } diff --git a/src/cases/containers/accept-cases/accept-cases.component.html b/src/cases/containers/accept-cases/accept-cases.component.html new file mode 100644 index 000000000..bdd79cf1f --- /dev/null +++ b/src/cases/containers/accept-cases/accept-cases.component.html @@ -0,0 +1,55 @@ +Back +
+ {{ + (selectedOrganisation$ | async)?.name + }} +

{{ pageTitle }}

+ +
+
+ +

+ You are accepting new cases into your organisation. +

+ You do not need to assign cases to users if they have 'Access all cases in the organisation' + enabled for that case type. +

+ If users do not have access to all cases in the organisation, you must assign this case to at + least one user. +

+
+
+
+
+ + +
+ Choose this option if you have given your users 'Access to all cases in the organisation' +
+
+
+ + +
+ Choose this option if you have not given your users 'Access to all cases in the organisation' +
+
+
+
+
+
+ + +
+
+
\ No newline at end of file diff --git a/src/cases/containers/accept-cases/accept-cases.component.scss b/src/cases/containers/accept-cases/accept-cases.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/cases/containers/accept-cases/accept-cases.component.spec.ts b/src/cases/containers/accept-cases/accept-cases.component.spec.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/cases/containers/accept-cases/accept-cases.component.ts b/src/cases/containers/accept-cases/accept-cases.component.ts new file mode 100644 index 000000000..aa317909f --- /dev/null +++ b/src/cases/containers/accept-cases/accept-cases.component.ts @@ -0,0 +1,45 @@ +import { Component, OnInit } from '@angular/core'; +import { Store, select } from '@ngrx/store'; + +import * as organisationStore from '../../../organisation/store'; +import { OrganisationDetails } from 'src/models/organisation.model'; +import { Observable } from 'rxjs'; +import { FormBuilder, FormControl } from '@angular/forms'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-exui-case-share', + templateUrl: './accept-cases.component.html', + styleUrls: ['./accept-cases.component.scss'] +}) +export class AcceptCasesComponent implements OnInit { + permissionsForm: any; + public selectedOrganisation$: Observable; + pageTitle: string; + + constructor( + private readonly organisationStore: Store, + private fb: FormBuilder, + private readonly router: Router){ + } + + public ngOnInit(): void { + // Load selected organisation details from store + this.organisationStore.dispatch(new organisationStore.LoadOrganisation()); + this.selectedOrganisation$ = this.organisationStore.pipe(select(organisationStore.getOrganisationSel)); + this.pageTitle = 'Accept cases'; + + this.permissionsForm = this.fb.group({ + assignmentType: ['notAssigning'] + }); + } + + public continue(){ + console.log('testing 134'); + console.log(this.permissionsForm); + } + + public goBack(){ + this.router.navigate(['/cases']); + } +} diff --git a/src/cases/containers/case-share/case-share.component.ts b/src/cases/containers/case-share/case-share.component.ts index 0b8ad0acb..0291fb076 100644 --- a/src/cases/containers/case-share/case-share.component.ts +++ b/src/cases/containers/case-share/case-share.component.ts @@ -40,6 +40,8 @@ export class CaseShareComponent implements OnInit { public ngOnInit(): void { this.routerState$ = this.store.pipe(select(getRouterState)); this.routerState$.subscribe((router) => { + console.log('router.state'); + console.log(router.state); this.init = router.state.queryParams.init; this.pageType = router.state.queryParams.pageType; // Set backLink, fnTitle, title, confirmLink, addUserLabel, and showRemoveUsers depending on whether navigation @@ -49,10 +51,17 @@ export class CaseShareComponent implements OnInit { this.backLink = '/cases'; this.confirmLink = `${url}/case-share-confirm/${this.pageType}`; - this.fnTitle = 'Action a case'; - this.title = 'Manage case assignments'; - this.addUserLabel = 'Add people to share cases to the selected cases'; - this.showRemoveUsers = true; + if (this.pageType === 'unassigned-cases') { + this.fnTitle = 'Share a case'; + this.title = 'Add recipient'; + this.addUserLabel = 'Enter email address'; + this.showRemoveUsers = false; + } else if (this.pageType === 'assigned-cases') { + this.fnTitle = 'Manage case sharing'; + this.title = 'Manage shared access to a case'; + this.addUserLabel = 'Add people to share access to the selected cases'; + this.showRemoveUsers = true; + } this.shareCases$ = this.pageType === CaaCasesPageType.UnassignedCases ? this.store.pipe(select(fromCasesFeature.getShareUnassignedCaseListState)) diff --git a/src/cases/containers/cases/cases.component.html b/src/cases/containers/cases/cases.component.html index 8712d3cc6..d116fce3d 100644 --- a/src/cases/containers/cases/cases.component.html +++ b/src/cases/containers/cases/cases.component.html @@ -42,13 +42,28 @@

(emitErrorMessages)="onErrorMessages($event)" > +
+ +
+ + + Warning + The tabs below list all of your organisation's cases which are not assigned to any users. You can assign + cases to users by selecting 'Manage cases'. + + You do not need to assigned cases to users if they have 'Access all cases in the organisation' enabled for + that case type. + +
+
+
{ + caaCasesFilterValue: this.selectedFilterValue + })); + + this.caaCasesStore.pipe( + select(caaCasesStore.getAllCaseTypes), + take(1) + ).subscribe((items) => { + console.log(items); this.allCaseTypes = items; if (this.allCaseTypes && this.allCaseTypes.length > 0) { this.selectedCaseType = this.allCaseTypes[0].text; @@ -120,6 +127,7 @@ export class CasesComponent implements OnInit { public loadCaseData(){ if (this.allCaseTypes && this.allCaseTypes.length > 0) { if (this.caaCasesPageType === CaaCasesPageType.AssignedCases) { + console.log('loadCaseData: loadAssignedCases'); this.caaCasesStore.dispatch(new caaCasesStore.LoadAssignedCases({ caseType: this.selectedCaseType, pageNo: this.currentPageNo, @@ -163,15 +171,18 @@ export class CasesComponent implements OnInit { } if (selectedFilter.filterType === CaaCasesFilterType.AllAssignedCases) { // dispatch action to load all cases - this.caseResultsTableShareButtonText = 'Manage cases'; + this.caaCasesPageType = CaaCasesPageType.AssignedCases; + this.caseResultsTableShareButtonText = 'Manage case sharing'; } if (selectedFilter.filterType === CaaCasesFilterType.NewCasesToAccept) { // dispatch action to load new cases to accept + this.caaCasesPageType = CaaCasesPageType.UnassignedCases; this.caseResultsTableShareButtonText = 'Accept cases'; } if (selectedFilter.filterType === CaaCasesFilterType.UnassignedCases) { // dispatch action to load unassigned cases - this.caseResultsTableShareButtonText = 'Manage cases'; + this.caaCasesPageType = CaaCasesPageType.UnassignedCases; + this.caseResultsTableShareButtonText = 'Share Case'; } this.loadCaseTypes(); } @@ -193,7 +204,7 @@ export class CasesComponent implements OnInit { } public retrieveSessionState(): void { - this.sessionStateValue = this.service.retrieveSessionState(this.caaCasesPageType); + this.sessionStateValue = this.service.retrieveSessionState(this.sessionStateKey); if (this.sessionStateValue) { this.toggleFilterSection(); } @@ -201,7 +212,7 @@ export class CasesComponent implements OnInit { public storeSessionState(selectedFilter: SelectedCaseFilter): void { const sessionStateToUpdate: CaaCasesSessionState = { - key: this.caaCasesPageType, + key: this.sessionStateKey, value: { filterType: selectedFilter.filterType, caseReferenceNumber: selectedFilter.filterType === CaaCasesFilterType.CaseReferenceNumber ? selectedFilter.filterValue : null, @@ -230,11 +241,23 @@ export class CasesComponent implements OnInit { } public onPageChanged(pageNo: number): void { + console.log('is the page being changed somehow?'); this.currentPageNo = pageNo; this.loadCaseData(); } - onShareButtonClicked($event: void) { + onShareButtonClicked($event: string) { + console.log(this.selectedFilterType); + let newCasesEnabled = false; + let groupAccessEnabled = false; + //match the caseType ($event) to any in the allCaseTypes + this.allCaseTypes.forEach((caseType) => { + const { text, caseConfig } = caseType; + if (text === $event && caseConfig) { + newCasesEnabled = caseConfig.new_cases; + groupAccessEnabled = caseConfig.group_access; + } + }); // load cases types based on filter and value if (this.selectedFilterType === CaaCasesFilterType.CaseReferenceNumber) { // TODO: need to handle the `new_case` flag @@ -261,6 +284,13 @@ export class CasesComponent implements OnInit { // dispatch action to load new cases to accept // if group_access is enabled then go to accept cases page // else go to add recipient + if (groupAccessEnabled) { + this.caaCasesStore.dispatch(new caaCasesStore.AddShareUnassignedCases({ + sharedCases: converters.toShareCaseConverter(this.selectedCases, this.selectedCaseType), + group_access: true + } + )); + } } if (this.selectedFilterType === CaaCasesFilterType.UnassignedCases) { this.caaCasesStore.dispatch(new caaCasesStore.AddShareUnassignedCases({ diff --git a/src/cases/containers/index.ts b/src/cases/containers/index.ts index eb154eea7..868301a7b 100644 --- a/src/cases/containers/index.ts +++ b/src/cases/containers/index.ts @@ -1,3 +1,4 @@ +import { AcceptCasesComponent } from './accept-cases/accept-cases.component'; import { CaseShareCompleteComponent } from './case-share-complete/case-share-complete.component'; import { CaseShareConfirmComponent } from './case-share-confirm/case-share-confirm.component'; import { CaseShareComponent } from './case-share/case-share.component'; @@ -7,7 +8,8 @@ export const containers: any[] = [ CaseShareComponent, CaseShareConfirmComponent, CaseShareCompleteComponent, - CasesComponent + CasesComponent, + AcceptCasesComponent ]; export * from './case-share-complete/case-share-complete.component'; diff --git a/src/cases/models/caa-cases.enum.ts b/src/cases/models/caa-cases.enum.ts index edd89f7ad..91599da6b 100644 --- a/src/cases/models/caa-cases.enum.ts +++ b/src/cases/models/caa-cases.enum.ts @@ -1,6 +1,7 @@ export enum CaaCasesPageType { AssignedCases = 'assigned-cases', - UnassignedCases = 'unassigned-cases' + UnassignedCases = 'unassigned-cases', + NewCases = 'new-cases' } export enum CaaCasesShowHideFilterButtonText { @@ -37,6 +38,7 @@ export enum CaaCasesFilterErrorMessage { export enum CaaCasesNoDataMessage { NoAssignedCases = 'There are no assigned cases available to be shared.', NoUnassignedCases = 'There are no unassigned cases available to be shared.', - AssignedCasesFilterMessage = 'Try again using a different case reference or assignee.', - UnassignedCasesFilterMessage = 'Try again using a different case reference.' + CasesFilterMessage = 'There are no assigned cases associated with this user to be shared.', + NoNewCases = 'There are no new cases to be shared.', + NoCaseIdMatches = 'There are no cases with case reference to be shared.' } diff --git a/src/cases/models/caa-cases.model.ts b/src/cases/models/caa-cases.model.ts index c35509430..a6389f5bd 100644 --- a/src/cases/models/caa-cases.model.ts +++ b/src/cases/models/caa-cases.model.ts @@ -19,6 +19,10 @@ export interface CaseTypesResultsResponse { export interface CaseTypesResults { total: number; case_type_id: string; + caseConfig: { + new_cases: boolean; + group_access: boolean; + } } export interface SelectedCases { diff --git a/src/cases/store/actions/share-case.action.ts b/src/cases/store/actions/share-case.action.ts index 4cd394632..39938045c 100644 --- a/src/cases/store/actions/share-case.action.ts +++ b/src/cases/store/actions/share-case.action.ts @@ -110,7 +110,9 @@ export class AddShareUnassignedCases implements Action { path?: any[]; query?: object; extras?: NavigationExtras; - sharedCases: SharedCase[] + sharedCases: SharedCase[], + group_access?: boolean, + new_cases?: boolean }) {} } diff --git a/src/cases/store/effects/caa-cases.effects.ts b/src/cases/store/effects/caa-cases.effects.ts index 337104295..6bc1e8f2d 100644 --- a/src/cases/store/effects/caa-cases.effects.ts +++ b/src/cases/store/effects/caa-cases.effects.ts @@ -23,6 +23,7 @@ export class CaaCasesEffects { ofType(fromCaaActions.LOAD_ASSIGNED_CASES), switchMap((action: fromCaaActions.LoadAssignedCases) => { const payload = action.payload; + console.log('load assigned cases'); return this.caaCasesService.getCaaCases(payload.caseType, payload.pageNo, payload.pageSize, CaaCasesPageType.AssignedCases, payload.caaCasesFilterType, payload.caaCasesFilterValue).pipe( map((caaCases) => new fromCaaActions.LoadAssignedCasesSuccess(caaCases)), catchError((error) => CaaCasesEffects.handleError(error, this.loggerService, CaaCasesPageType.AssignedCases)) diff --git a/src/cases/store/effects/share-case.effects.ts b/src/cases/store/effects/share-case.effects.ts index b5e3c6e68..e8aaa7bfe 100644 --- a/src/cases/store/effects/share-case.effects.ts +++ b/src/cases/store/effects/share-case.effects.ts @@ -41,7 +41,7 @@ export class ShareCaseEffects { map((action: shareCaseActions.AddShareUnassignedCases) => action.payload), map((newCases) => { return new shareCaseActions.AddShareUnassignedCaseGo({ - path: [`${this.router.url}/case-share`], + path: this.getPathFromCaseConfig(newCases.group_access, newCases.new_cases), sharedCases: newCases.sharedCases }); }) @@ -145,4 +145,11 @@ export class ShareCaseEffects { }) ) ); + + public getPathFromCaseConfig(groupAccess?: boolean, newCases?: boolean) { + if (groupAccess){ + return [`${this.router.url}/accept-cases`]; + } + return [`${this.router.url}/case-share`]; + } } diff --git a/src/cases/util/caa-cases.util.ts b/src/cases/util/caa-cases.util.ts index c8ef2a00e..921b1444b 100644 --- a/src/cases/util/caa-cases.util.ts +++ b/src/cases/util/caa-cases.util.ts @@ -12,7 +12,8 @@ export class CaaCasesUtil { text: caseType.case_type_id, href: caseType.case_type_id, active: false, - total: caseType.total + total: caseType.total, + caseConfig: caseType.caseConfig }); } }); From 898835d119efe8e3c9cdd04404d9e1726885a023 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 31 Jan 2025 11:26:44 +0000 Subject: [PATCH 12/44] fix loading spinner so it sticks to scrolled page --- src/shared/modules/loader/components/loader.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/modules/loader/components/loader.component.scss b/src/shared/modules/loader/components/loader.component.scss index 58469eb94..2cfe5d83f 100644 --- a/src/shared/modules/loader/components/loader.component.scss +++ b/src/shared/modules/loader/components/loader.component.scss @@ -4,7 +4,7 @@ } .overlay { - position: absolute; + position: fixed; z-index: 1002; background-color: rgba(255, 255, 255, 0.5); width: 100%; From b4813b8b3fd0d6b0c3b7d27ede74afa84a1da5be Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 31 Jan 2025 11:39:04 +0000 Subject: [PATCH 13/44] Update store to support all types, optimise page load, add dynamic path to accept cases page, update confirm screen to support new-cases, add temp new endpoint for new-cases --- api/caaCases/caaCases.util.ts | 13 +- api/caaCases/enums/index.ts | 1 + api/caaCases/index.ts | 4 +- api/caseshare/index.ts | 7 + api/caseshare/real-api.ts | 11 + api/caseshare/routes.ts | 1 + .../cases-results-table.component.html | 2 +- .../cases-results-table.component.ts | 17 -- .../accept-cases/accept-cases.component.html | 2 +- .../accept-cases/accept-cases.component.ts | 29 ++- .../case-share-complete.component.html | 12 +- .../case-share-complete.component.ts | 25 +-- .../case-share-confirm.component.html | 3 +- .../case-share-confirm.component.ts | 14 +- .../case-share/case-share.component.ts | 86 ++++---- src/cases/containers/cases/cases.component.ts | 184 +++++++++-------- src/cases/store/actions/caa-cases.actions.ts | 47 ++--- src/cases/store/actions/index.ts | 18 +- src/cases/store/actions/share-case.action.ts | 191 +++++------------- src/cases/store/effects/caa-cases.effects.ts | 31 +-- src/cases/store/effects/share-case.effects.ts | 103 +++------- src/cases/store/reducers/caa-cases.reducer.ts | 30 +-- .../store/reducers/share-case.reducer.ts | 164 +++------------ .../store/selectors/caa-cases.selector.ts | 27 +-- .../store/selectors/share-case.selector.ts | 9 +- 25 files changed, 365 insertions(+), 666 deletions(-) diff --git a/api/caaCases/caaCases.util.ts b/api/caaCases/caaCases.util.ts index 816be06c0..e5499bfff 100644 --- a/api/caaCases/caaCases.util.ts +++ b/api/caaCases/caaCases.util.ts @@ -1,5 +1,5 @@ import { caseAssignment, caseId, caseTypeStr } from './caaCases.constants'; -import { CaaCasesPageType } from './enums'; +import { CaaCasesFilterType, CaaCasesPageType } from './enums'; import { CaaCases, CaseHeader, CcdCase, CcdCaseData, CcdColumnConfig } from './interfaces'; export function getApiPath(ccdPath: string, caseTypeId: string) { @@ -17,11 +17,15 @@ export function mapCcdCases(caseType: string, ccdCase: CcdCase): CaaCases { }; } -export function getRequestBody(organisationID: string, pageNo: number, pageSize: number, caaCasesPageType: string, caaCasesFilterValue?: string | string[]) { +export function getRequestBody(organisationID: string, pageNo: number, pageSize: number, caaCasesPageType: string, caseFilterType: string, caaCasesFilterValue?: string | string[]) { const organisationAssignedUsersKey = `supplementary_data.orgs_assigned_users.${organisationID}`; + const newCasesKey = `supplementary_data.new_case.${organisationID}`; const reference = 'reference.keyword'; const caseReferenceFilter: any[] = []; + if (caseFilterType === CaaCasesFilterType.NewCasesToAccept){ + caaCasesPageType = CaaCasesPageType.NewCasesToAccept; + } if (caaCasesFilterValue) { if (Array.isArray(caaCasesFilterValue)) { caaCasesFilterValue.forEach((caseReference) => { @@ -56,6 +60,11 @@ export function getRequestBody(organisationID: string, pageNo: number, pageSize: { range: { [organisationAssignedUsersKey]: { gt: 0 } } } ] }) + // ...(caaCasesPageType === CaaCasesFilterType.NewCasesToAccept && { + // must: [ + // { range: { [newCasesKey]: { gt: 0 } } } + // ] + // }) } }, { diff --git a/api/caaCases/enums/index.ts b/api/caaCases/enums/index.ts index 63c22a790..80ff93562 100644 --- a/api/caaCases/enums/index.ts +++ b/api/caaCases/enums/index.ts @@ -1,6 +1,7 @@ export enum CaaCasesPageType { AssignedCases = 'assigned-cases', UnassignedCases = 'unassigned-cases', + NewCasesToAccept = 'new-cases-to-accept', } export enum CaaCasesFilterType { diff --git a/api/caaCases/index.ts b/api/caaCases/index.ts index b9127d45c..c498f62bc 100644 --- a/api/caaCases/index.ts +++ b/api/caaCases/index.ts @@ -16,7 +16,7 @@ export async function handleCaaCases(req: EnhancedRequest, res: Response, next: const fromNo: number = page * size; let caaCasesFilterValue: string | string[] = req.query.caaCasesFilterValue as string; - + const caseFilterType = req.query.caaCasesFilterType as string; try { if (caaCasesFilterType === CaaCasesFilterType.AssigneeName) { const roleAssignments = await handleRoleAssignments(req, next); @@ -35,7 +35,7 @@ export async function handleCaaCases(req: EnhancedRequest, res: Response, next: res.status(errReport.apiStatusCode).send(errReport); } - const payload = getRequestBody(orgId, fromNo, size, caaCasesPageType, caaCasesFilterValue); + const payload = getRequestBody(orgId, fromNo, size, caaCasesPageType, caseFilterType, caaCasesFilterValue); const response = await req.http.post(path, payload); const caaCases = mapCcdCases(caseTypeId, response.data); diff --git a/api/caseshare/index.ts b/api/caseshare/index.ts index da259eb68..3f095986d 100644 --- a/api/caseshare/index.ts +++ b/api/caseshare/index.ts @@ -35,3 +35,10 @@ export async function assignCasesToUsers(req: EnhancedRequest, res: Response) { } return realAPI.assignCases(req, res); } + +export async function acceptNewCasesForOrg(req: EnhancedRequest, res: Response) { + // if (stub) { + // return stubAPI.acceptNewCases(req, res); + // } + return realAPI.acceptNewCases(req, res); +} diff --git a/api/caseshare/real-api.ts b/api/caseshare/real-api.ts index 8a87d9ed2..ed7cc328b 100644 --- a/api/caseshare/real-api.ts +++ b/api/caseshare/real-api.ts @@ -47,6 +47,17 @@ export async function getCases(req: EnhancedRequest, res: Response, next: NextFu } } +export async function acceptNewCases(req: EnhancedRequest, res: Response): Promise { + const path = `${ccdUrl}/case-users`; + try { + const casesToAccept = req.body.casesToAccept; + const { status, data }: {status: number, data: any} = await handlePost(path, casesToAccept, req); + return res.status(status).send(data); + } catch (err) { + res.status(500); + } +} + export async function assignCases(req: EnhancedRequest, res: Response): Promise { const shareCases: SharedCase[] = req.body.sharedCases.slice(); diff --git a/api/caseshare/routes.ts b/api/caseshare/routes.ts index 0d0675f64..765947edd 100644 --- a/api/caseshare/routes.ts +++ b/api/caseshare/routes.ts @@ -6,3 +6,4 @@ router.get('/users', restAPI.getUsers); router.get('/cases', restAPI.getCases); router.post('/case-assignments', restAPI.assignCasesToUsers); router.get('/case-assignments', restAPI.getCases); +router.post('/case-users', restAPI.acceptNewCasesForOrg); diff --git a/src/cases/components/cases-results-table/cases-results-table.component.html b/src/cases/components/cases-results-table/cases-results-table.component.html index 80bbc3f0d..a64212401 100644 --- a/src/cases/components/cases-results-table/cases-results-table.component.html +++ b/src/cases/components/cases-results-table/cases-results-table.component.html @@ -59,7 +59,7 @@ -{{navItems}} +
{{noCasesFoundMessage}}
diff --git a/src/cases/components/cases-results-table/cases-results-table.component.ts b/src/cases/components/cases-results-table/cases-results-table.component.ts index 9b2d7c8b8..d52261f38 100644 --- a/src/cases/components/cases-results-table/cases-results-table.component.ts +++ b/src/cases/components/cases-results-table/cases-results-table.component.ts @@ -74,7 +74,6 @@ export class CasesResultsTableComponent { } public tabChanged(event: { tab: { textLabel: string }}): void { - console.log(event); this.totalCases = this.navItems.find((data) => data.text === event.tab.textLabel) ? this.navItems.find((data) => data.text === event.tab.textLabel).total : 0; @@ -122,19 +121,10 @@ export class CasesResultsTableComponent { } public onShareButtonClicked(): void { - // this.store.dispatch(new fromStore.AddShareUnassignedCases({ - // sharedCases: converters.toShareCaseConverter(this.selectedUnassignedCases, this.currentCaseType) - // })); - // TODO: emit this action this.shareButtonClicked.emit(this.currentCaseType); } private fixCurrentTab(items: any): void { - // TESTING: Add another tab to test case specific navigation - console.log(`test items: ${items}`); - if (items.length > 0){ - items = [...items, { text: 'Asylum', href: 'Asylum', active: false, total: 3 }]; - } this.navItems = items; if (items && items.length > 0) { this.totalCases = items[0].total ? items[0].total : 0; @@ -147,13 +137,6 @@ export class CasesResultsTableComponent { private setTabItems(tabName: string, fromTabChangedEvent?: boolean): void { this.resetPaginationParameters(); - // if (this.caaCasesPageType === CaaCasesPageType.UnassignedCases) { - // this.store.pipe(select(fromStore.getAllUnassignedCases)); - // } else { - // this.store.pipe(select(fromStore.getAllAssignedCases)); - // } - // this.shareAssignedCases$ = this.store.pipe(select(fromStore.getShareAssignedCaseListState)); - // this.shareUnassignedCases$ = this.store.pipe(select(fromStore.getShareUnassignedCaseListState)); this.currentCaseType = tabName; if (!fromTabChangedEvent && this.tabGroup) { this.tabGroup.selectedIndex = 0; diff --git a/src/cases/containers/accept-cases/accept-cases.component.html b/src/cases/containers/accept-cases/accept-cases.component.html index bdd79cf1f..bff8be93e 100644 --- a/src/cases/containers/accept-cases/accept-cases.component.html +++ b/src/cases/containers/accept-cases/accept-cases.component.html @@ -1,4 +1,4 @@ -Back +Back
{{ (selectedOrganisation$ | async)?.name diff --git a/src/cases/containers/accept-cases/accept-cases.component.ts b/src/cases/containers/accept-cases/accept-cases.component.ts index aa317909f..f271d6df6 100644 --- a/src/cases/containers/accept-cases/accept-cases.component.ts +++ b/src/cases/containers/accept-cases/accept-cases.component.ts @@ -1,11 +1,11 @@ import { Component, OnInit } from '@angular/core'; import { Store, select } from '@ngrx/store'; - import * as organisationStore from '../../../organisation/store'; import { OrganisationDetails } from 'src/models/organisation.model'; import { Observable } from 'rxjs'; -import { FormBuilder, FormControl } from '@angular/forms'; -import { Router } from '@angular/router'; +import { FormBuilder } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { CaaCasesPageType } from '../../models/caa-cases.enum'; @Component({ selector: 'app-exui-case-share', @@ -13,13 +13,17 @@ import { Router } from '@angular/router'; styleUrls: ['./accept-cases.component.scss'] }) export class AcceptCasesComponent implements OnInit { - permissionsForm: any; + public permissionsForm: any; public selectedOrganisation$: Observable; - pageTitle: string; + public pageTitle: string; + + private readonly caseShare = '/cases/case-share'; + private readonly caseShareConfirmUrl = '/cases/case-share-confirm/new-cases'; constructor( private readonly organisationStore: Store, - private fb: FormBuilder, + private readonly route: ActivatedRoute, + private readonly fb: FormBuilder, private readonly router: Router){ } @@ -27,16 +31,21 @@ export class AcceptCasesComponent implements OnInit { // Load selected organisation details from store this.organisationStore.dispatch(new organisationStore.LoadOrganisation()); this.selectedOrganisation$ = this.organisationStore.pipe(select(organisationStore.getOrganisationSel)); - this.pageTitle = 'Accept cases'; - + const caseType = this.route.snapshot.queryParams.caseType; + this.pageTitle = `Accept ${caseType} cases`; this.permissionsForm = this.fb.group({ assignmentType: ['notAssigning'] }); } public continue(){ - console.log('testing 134'); - console.log(this.permissionsForm); + if (this.permissionsForm.value.assignmentType === 'assigning'){ + const queryParams = { init: true, pageType: 'unassigned-cases', caseAccept: true }; + this.router.navigate([this.caseShare], { queryParams }); + } else { + const queryParams = { caseAccept: true, pageType: CaaCasesPageType.NewCases }; + this.router.navigate([this.caseShareConfirmUrl], { queryParams }); + } } public goBack(){ diff --git a/src/cases/containers/case-share-complete/case-share-complete.component.html b/src/cases/containers/case-share-complete/case-share-complete.component.html index e31e795c2..f239d34f9 100644 --- a/src/cases/containers/case-share-complete/case-share-complete.component.html +++ b/src/cases/containers/case-share-complete/case-share-complete.component.html @@ -9,13 +9,15 @@

What happens next

-

The people you added can now access and update the selected cases from their case list.

-

If you removed someone from a case, they cannot access the case anymore.

-

If you've shared one or more cases, your colleagues will now be able to access them from their case list.

+ +

The people you added can now access and update the selected cases from their case list.

+

If you removed someone from a case, they cannot access the case anymore.

+

If you've shared one or more cases, your colleagues will now be able to access them from their case list.

+

some text about new cases stuff.

+

To continue managing case sharing for other users or case types:

- Go to assigned cases list
- Go to unassigned cases list + Go to cases list
diff --git a/src/cases/containers/case-share-complete/case-share-complete.component.ts b/src/cases/containers/case-share-complete/case-share-complete.component.ts index d70eca271..6a08083ae 100644 --- a/src/cases/containers/case-share-complete/case-share-complete.component.ts +++ b/src/cases/containers/case-share-complete/case-share-complete.component.ts @@ -4,7 +4,6 @@ import { FeatureToggleService } from '@hmcts/rpx-xui-common-lib'; import { SharedCase } from '@hmcts/rpx-xui-common-lib/lib/models/case-share.model'; import { select, Store } from '@ngrx/store'; import { Observable } from 'rxjs'; -import { CaaCasesPageType } from '../../models/caa-cases.enum'; import * as fromCasesFeature from '../../store'; import * as fromCaseList from '../../store/reducers'; @@ -23,7 +22,7 @@ export class CaseShareCompleteComponent implements OnInit, OnDestroy { public isLoading: boolean; public completeScreenMode: string; public removeUserFromCaseToggleOn$: Observable; - public isFromAssignedCasesRoute: boolean = false; + public confirmPageType: string; constructor( private readonly store: Store, @@ -38,18 +37,11 @@ export class CaseShareCompleteComponent implements OnInit, OnDestroy { this.shareCaseState$ = this.store.pipe(select(fromCasesFeature.getCaseShareState)); this.shareCaseState$.subscribe((state) => this.isLoading = state.loading); - this.shareCases$ = this.pageType === CaaCasesPageType.UnassignedCases - ? this.store.pipe(select(fromCasesFeature.getShareUnassignedCaseListState)) - : this.store.pipe(select(fromCasesFeature.getShareAssignedCaseListState)); + this.shareCases$ = this.store.pipe(select(fromCasesFeature.getShareCaseListState)); this.shareCases$.subscribe((shareCases) => this.shareCases = shareCases); - if (this.pageType === CaaCasesPageType.UnassignedCases) { - this.store.dispatch(new fromCasesFeature.AssignUsersToUnassignedCase(this.shareCases)); - this.newShareCases$ = this.store.pipe(select(fromCasesFeature.getShareUnassignedCaseListState)); - } else { - this.store.dispatch(new fromCasesFeature.AssignUsersToAssignedCase(this.shareCases)); - this.newShareCases$ = this.store.pipe(select(fromCasesFeature.getShareAssignedCaseListState)); - } + // this.store.dispatch(new fromCasesFeature.AssignUsersToCase(this.shareCases)); + this.newShareCases$ = this.store.pipe(select(fromCasesFeature.getShareCaseListState)); this.newShareCases$.subscribe((shareCases) => { this.completeScreenMode = this.checkIfIncomplete(shareCases); @@ -57,16 +49,13 @@ export class CaseShareCompleteComponent implements OnInit, OnDestroy { }); this.removeUserFromCaseToggleOn$ = this.featureToggleService.getValue('remove-user-from-case-mo', false); - this.isFromAssignedCasesRoute = this.router.url.startsWith('/assigned-cases'); + + this.completeScreenMode = 'COMPLETE'; } public ngOnDestroy(): void { if (this.completeScreenMode === 'COMPLETE') { - if (this.pageType === CaaCasesPageType.UnassignedCases) { - this.store.dispatch(new fromCasesFeature.ResetUnassignedCaseSelection()); - } else { - this.store.dispatch(new fromCasesFeature.ResetAssignedCaseSelection()); - } + this.store.dispatch(new fromCasesFeature.ResetCaseSelection()); } } diff --git a/src/cases/containers/case-share-confirm/case-share-confirm.component.html b/src/cases/containers/case-share-confirm/case-share-confirm.component.html index 16f0afca5..26fa87ef9 100644 --- a/src/cases/containers/case-share-confirm/case-share-confirm.component.html +++ b/src/cases/containers/case-share-confirm/case-share-confirm.component.html @@ -5,6 +5,7 @@ + [completeLink]="completeLink" + [acceptCases]="acceptCases"> diff --git a/src/cases/containers/case-share-confirm/case-share-confirm.component.ts b/src/cases/containers/case-share-confirm/case-share-confirm.component.ts index 8feae1f13..290f048d4 100644 --- a/src/cases/containers/case-share-confirm/case-share-confirm.component.ts +++ b/src/cases/containers/case-share-confirm/case-share-confirm.component.ts @@ -21,23 +21,27 @@ export class CaseShareConfirmComponent implements OnInit { public backLink: string; public changeLink: string; public completeLink: string; + public acceptCases: boolean = false; + private readonly acceptLinks = '/cases/accept-cases'; + private readonly caseShareLinks = '/cases/case-share'; constructor(private readonly store: Store, private readonly route: ActivatedRoute, private readonly router: Router) { this.url = this.router?.url; this.pageType = this.route.snapshot.params.pageType; + this.acceptCases = this.route.snapshot.queryParams.caseAccept; } public ngOnInit(): void { this.fnTitle = 'Manage case assignments'; - this.backLink = '/cases/case-share'; - this.changeLink = '/cases/case-share'; + console.log(this.route.snapshot.queryParams); + this.backLink = this.acceptCases ? this.acceptLinks : this.caseShareLinks; + this.changeLink = this.acceptCases ? this.acceptLinks : this.caseShareLinks; this.completeLink = `/cases/case-share-complete/${this.pageType}`; - this.shareCases$ = this.pageType === CaaCasesPageType.UnassignedCases - ? this.store.pipe(select(fromCasesFeature.getShareUnassignedCaseListState)) - : this.store.pipe(select(fromCasesFeature.getShareAssignedCaseListState)); + this.shareCases$ = this.store.pipe(select(fromCasesFeature.getShareCaseListState)); this.shareCases$.subscribe((shareCases) => this.shareCases = shareCases); + console.log('shared csese', this.shareCases); } } diff --git a/src/cases/containers/case-share/case-share.component.ts b/src/cases/containers/case-share/case-share.component.ts index 0291fb076..e8e0c0beb 100644 --- a/src/cases/containers/case-share/case-share.component.ts +++ b/src/cases/containers/case-share/case-share.component.ts @@ -1,15 +1,14 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { FeatureToggleService } from '@hmcts/rpx-xui-common-lib'; import { SharedCase } from '@hmcts/rpx-xui-common-lib/lib/models/case-share.model'; import { UserDetails } from '@hmcts/rpx-xui-common-lib/lib/models/user-details.model'; import { RouterReducerState } from '@ngrx/router-store'; import { select, Store } from '@ngrx/store'; import { initAll } from 'govuk-frontend'; -import { Observable } from 'rxjs'; +import { Observable, Subject, takeUntil } from 'rxjs'; import { getRouterState, RouterStateUrl } from '../../../app/store/reducers'; -import { CaaCasesPageType } from '../../models/caa-cases.enum'; import * as fromCasesFeature from '../../store'; -import { LoadShareAssignedCases, LoadShareUnassignedCases, LoadUserFromOrgForCase } from '../../store/actions'; +import { LoadShareCases, LoadUserFromOrgForCase } from '../../store/actions'; import * as fromCaseList from '../../store/reducers'; @Component({ @@ -17,7 +16,8 @@ import * as fromCaseList from '../../store/reducers'; templateUrl: './case-share.component.html', styleUrls: ['./case-share.component.scss'] }) -export class CaseShareComponent implements OnInit { +export class CaseShareComponent implements OnInit, OnDestroy { + private destroy$ = new Subject(); public routerState$: Observable>; public init: boolean; public pageType: string; @@ -38,67 +38,63 @@ export class CaseShareComponent implements OnInit { ) {} public ngOnInit(): void { + console.log('initid the case share comp'); this.routerState$ = this.store.pipe(select(getRouterState)); - this.routerState$.subscribe((router) => { - console.log('router.state'); - console.log(router.state); + this.routerState$.pipe(takeUntil(this.destroy$)).subscribe((router) => { this.init = router.state.queryParams.init; this.pageType = router.state.queryParams.pageType; - // Set backLink, fnTitle, title, confirmLink, addUserLabel, and showRemoveUsers depending on whether navigation - // is via the Unassigned Cases or Assigned Cases page + const cameFromAcceptCases = router.state.queryParams.caseAccept; const url = router.state.url.substring(0, router.state.url.indexOf('/', 1)); - // Set backLink and confirmLink only if the URL is either "/unassigned-cases" or "/assigned-cases" - this.backLink = '/cases'; - this.confirmLink = `${url}/case-share-confirm/${this.pageType}`; + if (cameFromAcceptCases) { + this.backLink = '/cases/accept-cases'; + } else { + this.backLink = '/cases'; + } + this.confirmLink = `${url}/case-share-confirm/${cameFromAcceptCases ? 'new-cases' : this.pageType}`; - if (this.pageType === 'unassigned-cases') { - this.fnTitle = 'Share a case'; - this.title = 'Add recipient'; - this.addUserLabel = 'Enter email address'; - this.showRemoveUsers = false; - } else if (this.pageType === 'assigned-cases') { - this.fnTitle = 'Manage case sharing'; - this.title = 'Manage shared access to a case'; - this.addUserLabel = 'Add people to share access to the selected cases'; - this.showRemoveUsers = true; + switch (this.pageType) { + case 'unassigned-cases': + this.fnTitle = 'Share a case'; + this.title = 'Add recipient'; + this.addUserLabel = 'Enter email address'; + this.showRemoveUsers = false; + break; + case 'assigned-cases': + this.fnTitle = 'Manage case sharing'; + this.title = 'Manage shared access to a case'; + this.addUserLabel = 'Add people to share access to the selected cases'; + this.showRemoveUsers = true; + break; } - this.shareCases$ = this.pageType === CaaCasesPageType.UnassignedCases - ? this.store.pipe(select(fromCasesFeature.getShareUnassignedCaseListState)) - : this.store.pipe(select(fromCasesFeature.getShareAssignedCaseListState)); - this.shareCases$.subscribe((shareCases) => this.shareCases = shareCases); + this.shareCases$ = this.store.pipe(select(fromCasesFeature.getShareCaseListState)); + this.shareCases$.pipe(takeUntil(this.destroy$)).subscribe((shareCases) => this.shareCases = shareCases); }); this.orgUsers$ = this.store.pipe(select(fromCasesFeature.getOrganisationUsersState)); + this.orgUsers$.pipe(takeUntil(this.destroy$)).subscribe(); + if (this.init) { - // call api to retrieve case share users - if (this.pageType === CaaCasesPageType.UnassignedCases) { - this.store.dispatch(new LoadShareUnassignedCases(this.shareCases)); - } else { - this.store.dispatch(new LoadShareAssignedCases(this.shareCases)); - } - // call api to retrieve users in the same organisation + this.store.dispatch(new LoadShareCases(this.shareCases)); this.store.dispatch(new LoadUserFromOrgForCase()); } + this.removeUserFromCaseToggleOn$ = this.featureToggleService.getValue('remove-user-from-case-mo', false); + this.removeUserFromCaseToggleOn$.pipe(takeUntil(this.destroy$)).subscribe(); - // initialize javascript for accordion component to enable open/close button setTimeout(() => initAll(), 1000); } + public ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + public deselect($event): void { - if (this.pageType === CaaCasesPageType.UnassignedCases) { - this.store.dispatch(new fromCasesFeature.DeleteAShareUnassignedCase($event)); - } else { - this.store.dispatch(new fromCasesFeature.DeleteAShareAssignedCase($event)); - } + this.store.dispatch(new fromCasesFeature.DeleteAShareCase($event)); } public synchronizeStore($event): void { - if (this.pageType === CaaCasesPageType.UnassignedCases) { - this.store.dispatch(new fromCasesFeature.SynchronizeStateToStoreUnassignedCases($event)); - } else { - this.store.dispatch(new fromCasesFeature.SynchronizeStateToStoreAssignedCases($event)); - } + this.store.dispatch(new fromCasesFeature.SynchronizeStateToStoreCases($event)); } } diff --git a/src/cases/containers/cases/cases.component.ts b/src/cases/containers/cases/cases.component.ts index b561d9761..eac5690f5 100644 --- a/src/cases/containers/cases/cases.component.ts +++ b/src/cases/containers/cases/cases.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { CaaCasesService } from 'src/cases/services'; import * as organisationStore from '../../../organisation/store'; @@ -6,7 +6,7 @@ import * as userStore from '../../../users/store'; import * as caaCasesStore from '../../store'; import { Store, select } from '@ngrx/store'; import { OrganisationDetails } from 'src/models/organisation.model'; -import { Observable, take } from 'rxjs'; +import { Observable, skip, Subject, take, takeUntil } from 'rxjs'; import { Router } from '@angular/router'; import { ErrorMessage } from 'src/shared/models/error-message.model'; import { SubNavigation, User } from '@hmcts/rpx-xui-common-lib'; @@ -37,9 +37,10 @@ export class CasesComponent implements OnInit { public selectedFilterType: CaaCasesFilterType = CaaCasesFilterType.None; public selectedFilterValue: string = null; public selectedCaseType: string; + private readonly destroy$ = new Subject(); // for the results table - public allCaseTypes: SubNavigation[] = []; + public allCaseTypes = []; public currentPageNo: number = 1; public paginationPageSize: number = 25; public casesConfig: CaaCases; @@ -52,18 +53,11 @@ export class CasesComponent implements OnInit { private readonly organisationStore: Store, private readonly userStore: Store, private readonly router: Router, - private readonly service: CaaCasesService) { + private readonly service: CaaCasesService, + private cdr: ChangeDetectorRef) { } public ngOnInit(): void { - console.log('oninit...'); - // Retrieve session state to check and pre-populate the previous state if any - this.retrieveSessionState(); - // if session state is found, then filter component will emit filter values to avoid double query - if (!this.sessionStateValue) { - this.loadCaseTypes(); - } - // Load selected organisation details from store this.organisationStore.dispatch(new organisationStore.LoadOrganisation()); this.selectedOrganisation$ = this.organisationStore.pipe(select(organisationStore.getOrganisationSel)); @@ -72,30 +66,54 @@ export class CasesComponent implements OnInit { this.userStore.dispatch(new userStore.LoadAllUsersNoRoleData()); this.selectedOrganisationUsers$ = this.userStore.pipe(select(userStore.getGetUserList)); - // TODO: clean this up to get all cases - this.caaCasesStore.pipe(select(caaCasesStore.getAllUnassignedCases)).subscribe((config: CaaCases) => { - if (config){ + this.caaCasesStore.pipe( + select(caaCasesStore.getAllCases), + takeUntil(this.destroy$) + ).subscribe((config: CaaCases) => { + if (config) { this.casesConfig = config; } }); - this.caaCasesStore.pipe(select(caaCasesStore.getAllUnassignedCaseData)).subscribe((items) => { - if (items){ + + this.caaCasesStore.pipe( + select(caaCasesStore.getAllCaseData), + takeUntil(this.destroy$) + ).subscribe((items) => { + if (items) { this.cases = items; + if (this.selectedFilterType === CaaCasesFilterType.CaseReferenceNumber){ + this.checkShareButtonText(); + } } }); - this.caaCasesStore.pipe(select(caaCasesStore.getAllAssignedCases)).subscribe((config: CaaCases) => { - if (config){ - this.casesConfig = config; - } - }); - this.caaCasesStore.pipe(select(caaCasesStore.getAllAssignedCaseData)).subscribe((items) => { - if (items){ - this.cases = items; + // as this returns an [] as default first call and this could be possible for acutal load we should skip running + // the code inside as to not double load when user gets to page + this.caaCasesStore.pipe( + select(caaCasesStore.getAllCaseTypes), + skip(1), + takeUntil(this.destroy$) + ).subscribe((items) => { + this.allCaseTypes = items; + if (this.allCaseTypes && this.allCaseTypes.length > 0) { + this.selectedCaseType = this.allCaseTypes[0].text; + this.loadCaseData(); } }); + this.populateFirstLoad(); + } - this.casesError$ = this.caaCasesStore.pipe(select(caaCasesStore.getAllUnassignedCasesError)); - this.casesError$ = this.caaCasesStore.pipe(select(caaCasesStore.getAllAssignedCasesError)); + public ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + public populateFirstLoad() { + // Retrieve session state to check and pre-populate the previous state if any + this.retrieveSessionState(); + // if session state is found, then filter component will emit filter values to avoid double query + if (!this.sessionStateValue) { + this.loadCaseTypes(); + } } /** @@ -107,18 +125,6 @@ export class CasesComponent implements OnInit { caaCasesFilterType: this.selectedFilterType, caaCasesFilterValue: this.selectedFilterValue })); - - this.caaCasesStore.pipe( - select(caaCasesStore.getAllCaseTypes), - take(1) - ).subscribe((items) => { - console.log(items); - this.allCaseTypes = items; - if (this.allCaseTypes && this.allCaseTypes.length > 0) { - this.selectedCaseType = this.allCaseTypes[0].text; - this.loadCaseData(); - } - }); } /** @@ -126,30 +132,37 @@ export class CasesComponent implements OnInit { */ public loadCaseData(){ if (this.allCaseTypes && this.allCaseTypes.length > 0) { - if (this.caaCasesPageType === CaaCasesPageType.AssignedCases) { - console.log('loadCaseData: loadAssignedCases'); - this.caaCasesStore.dispatch(new caaCasesStore.LoadAssignedCases({ - caseType: this.selectedCaseType, - pageNo: this.currentPageNo, - pageSize: this.paginationPageSize, - caaCasesFilterType: this.selectedFilterType, - caaCasesFilterValue: this.selectedFilterValue - })); + this.caaCasesStore.dispatch(new caaCasesStore.LoadCases({ + caseType: this.selectedCaseType, + pageNo: this.currentPageNo, + pageSize: this.paginationPageSize, + caaCasesFilterType: this.selectedFilterType, + caaCasesPage: this.caaCasesPageType, + caaCasesFilterValue: this.selectedFilterValue + })); + } + } + + public checkShareButtonText(): void { + if (this.cases && this.allCaseTypes){ + const caseType = this.cases[0].caseType; + let caseConfig; + for (const caseTypeItem of this.allCaseTypes) { + if (caseTypeItem.text === caseType) { + caseConfig = caseTypeItem; + break; + } } - if (this.caaCasesPageType === CaaCasesPageType.UnassignedCases){ - this.caaCasesStore.dispatch(new caaCasesStore.LoadUnassignedCases({ - caseType: this.selectedCaseType, - pageNo: this.currentPageNo, - pageSize: this.paginationPageSize, - caaCasesFilterType: this.selectedFilterType, - caaCasesFilterValue: this.selectedFilterValue - })); + if (caseConfig.caseConfig.group_access) { + this.caseResultsTableShareButtonText = 'Accept cases'; + } else { + this.caseResultsTableShareButtonText = 'Manage case sharing'; } + this.cdr.detectChanges(); } } public onSelectedFilter(selectedFilter: SelectedCaseFilter): void { - console.log('Selected filter:', selectedFilter); this.selectedFilterType = selectedFilter.filterType; this.selectedFilterValue = selectedFilter.filterValue; @@ -158,32 +171,30 @@ export class CasesComponent implements OnInit { } else { this.storeSessionState(selectedFilter); } - - // load cases types based on filter and value - if (selectedFilter.filterType === CaaCasesFilterType.CaseReferenceNumber) { - // dispatch action to load case by ref number - this.caseResultsTableShareButtonText = 'Accept and assign cases'; + if (this.selectedFilterType === CaaCasesFilterType.CaseReferenceNumber) { + this.caseResultsTableShareButtonText = 'Accept cases'; } if (selectedFilter.filterType === CaaCasesFilterType.CasesAssignedToAUser) { // dispatch action to load case by assignee name - this.caaCasesPageType = CaaCasesPageType.AssignedCases; this.caseResultsTableShareButtonText = 'Manage cases'; + this.caaCasesPageType = CaaCasesPageType.AssignedCases; } if (selectedFilter.filterType === CaaCasesFilterType.AllAssignedCases) { // dispatch action to load all cases - this.caaCasesPageType = CaaCasesPageType.AssignedCases; this.caseResultsTableShareButtonText = 'Manage case sharing'; + this.caaCasesPageType = CaaCasesPageType.AssignedCases; } if (selectedFilter.filterType === CaaCasesFilterType.NewCasesToAccept) { // dispatch action to load new cases to accept - this.caaCasesPageType = CaaCasesPageType.UnassignedCases; this.caseResultsTableShareButtonText = 'Accept cases'; + this.caaCasesPageType = CaaCasesPageType.NewCases; } if (selectedFilter.filterType === CaaCasesFilterType.UnassignedCases) { // dispatch action to load unassigned cases - this.caaCasesPageType = CaaCasesPageType.UnassignedCases; this.caseResultsTableShareButtonText = 'Share Case'; + this.caaCasesPageType = CaaCasesPageType.UnassignedCases; } + this.cdr.detectChanges(); this.loadCaseTypes(); } @@ -226,28 +237,18 @@ export class CasesComponent implements OnInit { public onCaseSelected(selectedCases: any[]): void { // do i need the line below? and remove the selector in ngOnInit // this.selectedUnassignedCases = selectedCases; - if (this.caaCasesPageType === CaaCasesPageType.AssignedCases){ - this.caaCasesStore.dispatch(new caaCasesStore.SynchronizeStateToStoreAssignedCases( - converters.toShareCaseConverter(selectedCases, CaaCasesPageType.AssignedCases) - )); - } - - if (this.caaCasesPageType === CaaCasesPageType.UnassignedCases){ - this.caaCasesStore.dispatch(new caaCasesStore.SynchronizeStateToStoreUnassignedCases( - converters.toShareCaseConverter(selectedCases, CaaCasesPageType.UnassignedCases) - )); - } + this.caaCasesStore.dispatch(new caaCasesStore.SynchronizeStateToStoreCases( + converters.toShareCaseConverter(selectedCases, CaaCasesPageType.AssignedCases) + )); this.selectedCases = selectedCases; } public onPageChanged(pageNo: number): void { - console.log('is the page being changed somehow?'); this.currentPageNo = pageNo; this.loadCaseData(); } onShareButtonClicked($event: string) { - console.log(this.selectedFilterType); let newCasesEnabled = false; let groupAccessEnabled = false; //match the caseType ($event) to any in the allCaseTypes @@ -263,20 +264,23 @@ export class CasesComponent implements OnInit { // TODO: need to handle the `new_case` flag // if returning new case then go to add recipient page // else if returning non-new case then go to manage case assignments - this.caaCasesStore.dispatch(new caaCasesStore.AddShareAssignedCases({ - sharedCases: converters.toShareCaseConverter(this.selectedCases, this.selectedCaseType) + this.caaCasesStore.dispatch(new caaCasesStore.AddShareCases({ + sharedCases: converters.toShareCaseConverter(this.selectedCases, this.selectedCaseType), + caaPageType: this.caaCasesPageType })); } if (this.selectedFilterType === CaaCasesFilterType.CasesAssignedToAUser) { // todo: go to manage case assignments - this.caaCasesStore.dispatch(new caaCasesStore.AddShareAssignedCases({ - sharedCases: converters.toShareCaseConverter(this.selectedCases, this.selectedCaseType) + this.caaCasesStore.dispatch(new caaCasesStore.AddShareCases({ + sharedCases: converters.toShareCaseConverter(this.selectedCases, this.selectedCaseType), + caaPageType: this.caaCasesPageType } )); } if (this.selectedFilterType === CaaCasesFilterType.AllAssignedCases) { - this.caaCasesStore.dispatch(new caaCasesStore.AddShareAssignedCases({ - sharedCases: converters.toShareCaseConverter(this.selectedCases, this.selectedCaseType) + this.caaCasesStore.dispatch(new caaCasesStore.AddShareCases({ + sharedCases: converters.toShareCaseConverter(this.selectedCases, this.selectedCaseType), + caaPageType: this.caaCasesPageType } )); } @@ -285,16 +289,18 @@ export class CasesComponent implements OnInit { // if group_access is enabled then go to accept cases page // else go to add recipient if (groupAccessEnabled) { - this.caaCasesStore.dispatch(new caaCasesStore.AddShareUnassignedCases({ + this.caaCasesStore.dispatch(new caaCasesStore.AddShareCases({ sharedCases: converters.toShareCaseConverter(this.selectedCases, this.selectedCaseType), + caaPageType: this.caaCasesPageType, group_access: true } )); } } - if (this.selectedFilterType === CaaCasesFilterType.UnassignedCases) { - this.caaCasesStore.dispatch(new caaCasesStore.AddShareUnassignedCases({ - sharedCases: converters.toShareCaseConverter(this.selectedCases, this.selectedCaseType) + if (this.selectedFilterType === CaaCasesFilterType.UnassignedCases || this.selectedFilterType === 'none') { + this.caaCasesStore.dispatch(new caaCasesStore.AddShareCases({ + sharedCases: converters.toShareCaseConverter(this.selectedCases, this.selectedCaseType), + caaPageType: this.caaCasesPageType })); } } diff --git a/src/cases/store/actions/caa-cases.actions.ts b/src/cases/store/actions/caa-cases.actions.ts index 3731eb040..57157e691 100644 --- a/src/cases/store/actions/caa-cases.actions.ts +++ b/src/cases/store/actions/caa-cases.actions.ts @@ -2,44 +2,26 @@ import { HttpErrorResponse } from '@angular/common/http'; import { Action } from '@ngrx/store'; import { CaaCases } from '../../models/caa-cases.model'; -export const LOAD_ASSIGNED_CASES = '[CAA CASES] Load Assigned Cases'; -export const LOAD_ASSIGNED_CASES_SUCCESS = '[CAA CASES] Load Assigned Cases Success'; -export const LOAD_ASSIGNED_CASES_FAILURE = '[CAA CASES] Load Assigned Cases Failure'; -export const LOAD_UNASSIGNED_CASES = '[CAA CASES] Load Unassigned Cases'; -export const LOAD_UNASSIGNED_CASES_SUCCESS = '[CAA CASES] Load Unassigned Cases Success'; -export const LOAD_UNASSIGNED_CASES_FAILURE = '[CAA CASES] Load Unassigned Cases Failure'; +export const LOAD_CASES = '[CAA CASES] Load Cases'; +export const LOAD_CASES_SUCCESS = '[CAA CASES] Load Cases Success'; +export const LOAD_CASES_FAILURE = '[CAA CASES] Load Cases Failure'; export const LOAD_CASE_TYPES = '[CAA CASES] Load Case Types'; export const LOAD_CASE_TYPES_SUCCESS = '[CAA CASES] Load Case Types Success'; export const LOAD_CASE_TYPES_FAILURE = '[CAA CASES] Load Case Types Failure'; export const UPDATE_SELECTION_FOR_CASE_TYPE = '[CAA CASES] Update Selection For Case Types'; -export class LoadAssignedCases implements Action { - public readonly type = LOAD_ASSIGNED_CASES; - constructor(public payload: {caseType: string, pageNo: number, pageSize: number, caaCasesFilterType: string | null, caaCasesFilterValue: string | null}) {} +export class LoadCases implements Action { + public readonly type = LOAD_CASES; + constructor(public payload: {caseType: string, pageNo: number, pageSize: number, caaCasesPage: string, caaCasesFilterType: string | null, caaCasesFilterValue: string | null}) {} } -export class LoadAssignedCasesSuccess implements Action { - public readonly type = LOAD_ASSIGNED_CASES_SUCCESS; +export class LoadCasesSuccess implements Action { + public readonly type = LOAD_CASES_SUCCESS; constructor(public payload: CaaCases) {} } -export class LoadAssignedCasesFailure implements Action { - public readonly type = LOAD_ASSIGNED_CASES_FAILURE; - constructor(public payload: HttpErrorResponse) {} -} - -export class LoadUnassignedCases implements Action { - public readonly type = LOAD_UNASSIGNED_CASES; - constructor(public payload: {caseType: string, pageNo: number, pageSize: number, caaCasesFilterType: string | null, caaCasesFilterValue: string | null}) {} -} - -export class LoadUnassignedCasesSuccess implements Action { - public readonly type = LOAD_UNASSIGNED_CASES_SUCCESS; - constructor(public payload: CaaCases) {} -} - -export class LoadUnassignedCasesFailure implements Action { - public readonly type = LOAD_UNASSIGNED_CASES_FAILURE; +export class LoadCasesFailure implements Action { + public readonly type = LOAD_CASES_FAILURE; constructor(public payload: HttpErrorResponse) {} } @@ -64,12 +46,9 @@ export class UpdateSelectionForCaseType implements Action { } export type CaaCasesActions = - LoadAssignedCases - | LoadAssignedCasesSuccess - | LoadAssignedCasesFailure - | LoadUnassignedCases - | LoadUnassignedCasesSuccess - | LoadUnassignedCasesFailure + LoadCases + | LoadCasesSuccess + | LoadCasesFailure | LoadCaseTypes | LoadCaseTypesSuccess | LoadCaseTypesFailure diff --git a/src/cases/store/actions/index.ts b/src/cases/store/actions/index.ts index 55c8e9243..e8d7f5fea 100644 --- a/src/cases/store/actions/index.ts +++ b/src/cases/store/actions/index.ts @@ -1,19 +1,13 @@ import { - LoadAssignedCases, - LoadAssignedCasesFailure, - LoadAssignedCasesSuccess, - LoadUnassignedCases, - LoadUnassignedCasesFailure, - LoadUnassignedCasesSuccess + LoadCases, + LoadCasesFailure, + LoadCasesSuccess } from './caa-cases.actions'; export const actions: any[] = [ - LoadAssignedCases, - LoadAssignedCasesSuccess, - LoadAssignedCasesFailure, - LoadUnassignedCases, - LoadUnassignedCasesSuccess, - LoadUnassignedCasesFailure + LoadCases, + LoadCasesSuccess, + LoadCasesFailure ]; export * from './caa-cases.actions'; diff --git a/src/cases/store/actions/share-case.action.ts b/src/cases/store/actions/share-case.action.ts index 39938045c..de0b223d5 100644 --- a/src/cases/store/actions/share-case.action.ts +++ b/src/cases/store/actions/share-case.action.ts @@ -3,175 +3,91 @@ import { SharedCase } from '@hmcts/rpx-xui-common-lib/lib/models/case-share.mode import { UserDetails } from '@hmcts/rpx-xui-common-lib/lib/models/user-details.model'; import { Action } from '@ngrx/store'; -// Assigned cases actions -export const NAVIGATE_TO_SHARE_ASSIGNED_CASES = '[ShareCase] Navigate To Share Assigned Cases'; -export const LOAD_SHARE_ASSIGNED_CASES = '[ShareCase] Load Share Assigned Cases'; -export const LOAD_SHARE_ASSIGNED_CASES_SUCCESS = '[ShareCase] Load Share Assigned Cases Success'; -export const LOAD_SHARE_ASSIGNED_CASES_FAILURE = '[ShareCase] Load Share Assigned Cases Failure'; -export const ADD_SHARE_ASSIGNED_CASES = '[ShareCase] Add Share Assigned Cases'; -export const ADD_SHARE_ASSIGNED_CASES_GO = '[Router] Add Share Assigned Case Go'; -export const DELETE_A_SHARE_ASSIGNED_CASE = '[ShareCase] Delete A Share Assigned Case'; -export const ASSIGN_USERS_TO_ASSIGNED_CASE = '[ShareCase] Assign Users to Assigned Case'; -export const ASSIGN_USERS_TO_ASSIGNED_CASE_SUCCESS = '[ShareCase] Assign Users to Assigned Case Success'; -export const RESET_ASSIGNED_CASE_SELECTION = '[ShareCase] Reset Assigned Case Selection'; - -// Unassigned cases actions -export const NAVIGATE_TO_SHARE_UNASSIGNED_CASES = '[ShareCase] Navigate To Share Unassigned Cases'; -export const LOAD_SHARE_UNASSIGNED_CASES = '[ShareCase] Load Share Unassigned Cases'; -export const LOAD_SHARE_UNASSIGNED_CASES_SUCCESS = '[ShareCase] Load Share Unassigned Cases Success'; -export const LOAD_SHARE_UNASSIGNED_CASES_FAILURE = '[ShareCase] Load Share Unassigned Cases Failure'; -export const ADD_SHARE_UNASSIGNED_CASES = '[ShareCase] Add Share Unassigned Cases'; -export const ADD_SHARE_UNASSIGNED_CASES_GO = '[Router] Add Share Unassigned Case Go'; -export const DELETE_A_SHARE_UNASSIGNED_CASE = '[ShareCase] Delete A Share Unassigned Case'; -export const ASSIGN_USERS_TO_UNASSIGNED_CASE = '[ShareCase] Assign Users to Unassigned Case'; -export const ASSIGN_USERS_TO_UNASSIGNED_CASE_SUCCESS = '[ShareCase] Assign Users to Unassigned Case Success'; -export const RESET_UNASSIGNED_CASE_SELECTION = '[ShareCase] Reset Unassigned Case Selection'; +// cases actions +export const NAVIGATE_TO_SHARE_CASES = '[ShareCase] Navigate To Share Cases'; +export const LOAD_SHARE_CASES = '[ShareCase] Load Share Cases'; +export const LOAD_SHARE_CASES_SUCCESS = '[ShareCase] Load Share Cases Success'; +export const LOAD_SHARE_CASES_FAILURE = '[ShareCase] Load Share Cases Failure'; +export const ADD_SHARE_CASES = '[ShareCase] Add Share Cases'; +export const ADD_SHARE_CASES_GO = '[Router] Add Share Case Go'; +export const DELETE_A_SHARE_CASE = '[ShareCase] Delete A Share Case'; +export const ASSIGN_USERS_TO_CASE = '[ShareCase] Assign Users to Case'; +export const ASSIGN_USERS_TO_CASE_SUCCESS = '[ShareCase] Assign Users to Case Success'; +export const RESET_CASE_SELECTION = '[ShareCase] Reset Case Selection'; export const LOAD_USERS_FROM_ORG_FOR_CASE = '[LoadUsers] From ORG For A Case'; export const LOAD_USERS_FROM_ORG_FOR_CASE_SUCCESS = '[LoadUsers] From ORG For A Case Success'; -export const SYNCHRONIZE_STATE_TO_STORE_ASSIGNED_CASES = '[ShareCase] Synchronize State To Store Assigned Cases'; -export const SYNCHRONIZE_STATE_TO_STORE_UNASSIGNED_CASES = '[ShareCase] Synchronize State To Store Unassigned Cases'; - -export class NavigateToShareAssignedCases implements Action { - public readonly type = NAVIGATE_TO_SHARE_ASSIGNED_CASES; - constructor(public payload: SharedCase[]) {} -} - -export class NavigateToShareUnassignedCases implements Action { - public readonly type = NAVIGATE_TO_SHARE_UNASSIGNED_CASES; - constructor(public payload: SharedCase[]) {} -} +export const SYNCHRONIZE_STATE_TO_STORE_CASES = '[ShareCase] Synchronize State To Store Cases'; -export class SynchronizeStateToStoreAssignedCases implements Action { - public readonly type = SYNCHRONIZE_STATE_TO_STORE_ASSIGNED_CASES; +export class NavigateToShareCases implements Action { + public readonly type = NAVIGATE_TO_SHARE_CASES; constructor(public payload: SharedCase[]) {} } -export class SynchronizeStateToStoreUnassignedCases implements Action { - public readonly type = SYNCHRONIZE_STATE_TO_STORE_UNASSIGNED_CASES; +export class SynchronizeStateToStoreCases implements Action { + public readonly type = SYNCHRONIZE_STATE_TO_STORE_CASES; constructor(public payload: SharedCase[]) {} } -export class LoadShareAssignedCases implements Action { - public readonly type = LOAD_SHARE_ASSIGNED_CASES; +export class LoadShareCases implements Action { + public readonly type = LOAD_SHARE_CASES; constructor(public payload: SharedCase[]) {} } -export class LoadShareAssignedCasesSuccess implements Action { - public readonly type = LOAD_SHARE_ASSIGNED_CASES_SUCCESS; +export class LoadShareCasesSuccess implements Action { + public readonly type = LOAD_SHARE_CASES_SUCCESS; constructor(public payload: SharedCase[]) {} } -export class LoadShareAssignedCaseFailure implements Action { - public readonly type = LOAD_SHARE_ASSIGNED_CASES_FAILURE; +export class LoadShareCaseFailure implements Action { + public readonly type = LOAD_SHARE_CASES_FAILURE; constructor(public payload: Error) {} } -export class LoadShareUnassignedCases implements Action { - public readonly type = LOAD_SHARE_UNASSIGNED_CASES; - constructor(public payload: SharedCase[]) {} -} - -export class LoadShareUnassignedCasesSuccess implements Action { - public readonly type = LOAD_SHARE_UNASSIGNED_CASES_SUCCESS; - constructor(public payload: SharedCase[]) {} -} - -export class LoadShareUnassignedCaseFailure implements Action { - public readonly type = LOAD_SHARE_UNASSIGNED_CASES_FAILURE; - constructor(public payload: Error) {} -} - -export class AddShareAssignedCases implements Action { - public readonly type = ADD_SHARE_ASSIGNED_CASES; - constructor(public payload: { - path?: any[]; - query?: object; - extras?: NavigationExtras; - sharedCases: SharedCase[] - }) {} -} - -export class AddShareAssignedCaseGo implements Action { - public readonly type = ADD_SHARE_ASSIGNED_CASES_GO; - constructor( - public payload: { - path: any[]; - query?: object; - extras?: NavigationExtras; - sharedCases: SharedCase[] - } - ) {} -} - -export class AddShareUnassignedCases implements Action { - public readonly type = ADD_SHARE_UNASSIGNED_CASES; +export class AddShareCases implements Action { + public readonly type = ADD_SHARE_CASES; constructor(public payload: { path?: any[]; query?: object; extras?: NavigationExtras; sharedCases: SharedCase[], group_access?: boolean, - new_cases?: boolean + caaPageType?: string }) {} } -export class AddShareUnassignedCaseGo implements Action { - public readonly type = ADD_SHARE_UNASSIGNED_CASES_GO; +export class AddShareCaseGo implements Action { + public readonly type = ADD_SHARE_CASES_GO; constructor( public payload: { path: any[]; query?: object; extras?: NavigationExtras; - sharedCases: SharedCase[] - } - ) {} -} - -export class DeleteAShareAssignedCase implements Action { - public readonly type = DELETE_A_SHARE_ASSIGNED_CASE; - constructor( - public payload: { - caseId: string; + sharedCases: SharedCase[]; + caaPageType?: string } ) {} } - -export class DeleteAShareUnassignedCase implements Action { - public readonly type = DELETE_A_SHARE_UNASSIGNED_CASE; +export class DeleteAShareCase implements Action { + public readonly type = DELETE_A_SHARE_CASE; constructor( public payload: { caseId: string; } ) {} } - -export class AssignUsersToAssignedCase implements Action { - public readonly type = ASSIGN_USERS_TO_ASSIGNED_CASE; - constructor(public payload: SharedCase[]) {} -} - -export class AssignUsersToAssignedCaseSuccess implements Action { - public readonly type = ASSIGN_USERS_TO_ASSIGNED_CASE_SUCCESS; - constructor(public payload: SharedCase[]) {} -} - -export class AssignUsersToUnassignedCase implements Action { - public readonly type = ASSIGN_USERS_TO_UNASSIGNED_CASE; +export class AssignUsersToCase implements Action { + public readonly type = ASSIGN_USERS_TO_CASE; constructor(public payload: SharedCase[]) {} } -export class AssignUsersToUnassignedCaseSuccess implements Action { - public readonly type = ASSIGN_USERS_TO_UNASSIGNED_CASE_SUCCESS; +export class AssignUsersToCaseSuccess implements Action { + public readonly type = ASSIGN_USERS_TO_CASE_SUCCESS; constructor(public payload: SharedCase[]) {} } -export class ResetAssignedCaseSelection implements Action { - public readonly type = RESET_ASSIGNED_CASE_SELECTION; -} - -export class ResetUnassignedCaseSelection implements Action { - public readonly type = RESET_UNASSIGNED_CASE_SELECTION; +export class ResetCaseSelection implements Action { + public readonly type = RESET_CASE_SELECTION; } export class LoadUserFromOrgForCase implements Action { @@ -184,27 +100,16 @@ export class LoadUserFromOrgForCaseSuccess implements Action { } export type Actions = - NavigateToShareAssignedCases - | NavigateToShareUnassignedCases - | SynchronizeStateToStoreAssignedCases - | SynchronizeStateToStoreUnassignedCases - | LoadShareAssignedCases - | LoadShareAssignedCasesSuccess - | LoadShareAssignedCaseFailure - | LoadShareUnassignedCases - | LoadShareUnassignedCasesSuccess - | LoadShareUnassignedCaseFailure - | AddShareAssignedCases - | AddShareAssignedCaseGo - | AddShareUnassignedCases - | AddShareUnassignedCaseGo - | DeleteAShareAssignedCase - | DeleteAShareUnassignedCase - | AssignUsersToAssignedCase - | AssignUsersToAssignedCaseSuccess - | AssignUsersToUnassignedCase - | AssignUsersToUnassignedCaseSuccess - | ResetAssignedCaseSelection - | ResetUnassignedCaseSelection + NavigateToShareCases + | SynchronizeStateToStoreCases + | LoadShareCases + | LoadShareCasesSuccess + | LoadShareCaseFailure + | AddShareCases + | AddShareCaseGo + | DeleteAShareCase + | AssignUsersToCase + | AssignUsersToCaseSuccess + | ResetCaseSelection | LoadUserFromOrgForCase | LoadUserFromOrgForCaseSuccess; diff --git a/src/cases/store/effects/caa-cases.effects.ts b/src/cases/store/effects/caa-cases.effects.ts index 6bc1e8f2d..7ce3a4cbd 100644 --- a/src/cases/store/effects/caa-cases.effects.ts +++ b/src/cases/store/effects/caa-cases.effects.ts @@ -18,28 +18,15 @@ export class CaaCasesEffects { private readonly loggerService: LoggerService) { } - public loadAssignedCases$ = createEffect(() => + public loadCases$ = createEffect(() => this.actions$.pipe( - ofType(fromCaaActions.LOAD_ASSIGNED_CASES), - switchMap((action: fromCaaActions.LoadAssignedCases) => { + ofType(fromCaaActions.LOAD_CASES), + switchMap((action: fromCaaActions.LoadCases) => { const payload = action.payload; - console.log('load assigned cases'); - return this.caaCasesService.getCaaCases(payload.caseType, payload.pageNo, payload.pageSize, CaaCasesPageType.AssignedCases, payload.caaCasesFilterType, payload.caaCasesFilterValue).pipe( - map((caaCases) => new fromCaaActions.LoadAssignedCasesSuccess(caaCases)), - catchError((error) => CaaCasesEffects.handleError(error, this.loggerService, CaaCasesPageType.AssignedCases)) - ); - }) - ) - ); - - public loadUnassignedCases$ = createEffect(() => - this.actions$.pipe( - ofType(fromCaaActions.LOAD_UNASSIGNED_CASES), - switchMap((action: fromCaaActions.LoadUnassignedCases) => { - const payload = action.payload; - return this.caaCasesService.getCaaCases(payload.caseType, payload.pageNo, payload.pageSize, CaaCasesPageType.UnassignedCases, payload.caaCasesFilterType, payload.caaCasesFilterValue).pipe( - map((caaCases) => new fromCaaActions.LoadUnassignedCasesSuccess(caaCases)), - catchError((error) => CaaCasesEffects.handleError(error, this.loggerService, CaaCasesPageType.UnassignedCases)) + const pageType = payload.caaCasesPage === CaaCasesPageType.AssignedCases ? CaaCasesPageType.AssignedCases : CaaCasesPageType.UnassignedCases; + return this.caaCasesService.getCaaCases(payload.caseType, payload.pageNo, payload.pageSize, pageType, payload.caaCasesFilterType, payload.caaCasesFilterValue).pipe( + map((caaCases) => new fromCaaActions.LoadCasesSuccess(caaCases)), + catchError((error) => CaaCasesEffects.handleError(error, this.loggerService, pageType)) ); }) ) @@ -67,9 +54,7 @@ export class CaaCasesEffects { public static handleError(error: HttpErrorResponse, loggerService: LoggerService, caaCasesPageType: string): Observable { loggerService.error(error); return error.status === 400 - ? caaCasesPageType === CaaCasesPageType.UnassignedCases - ? of(new fromCaaActions.LoadUnassignedCasesFailure(error)) - : of(new fromCaaActions.LoadAssignedCasesFailure(error)) + ? of(new fromCaaActions.LoadCasesFailure(error)) : of(new fromRoot.Go({ path: ['/service-down'] })); } } diff --git a/src/cases/store/effects/share-case.effects.ts b/src/cases/store/effects/share-case.effects.ts index e8aaa7bfe..450637ff9 100644 --- a/src/cases/store/effects/share-case.effects.ts +++ b/src/cases/store/effects/share-case.effects.ts @@ -22,84 +22,45 @@ export class ShareCaseEffects { ) { } - public addShareAssignedCases$ = createEffect(() => + public addShareCases$ = createEffect(() => this.actions$.pipe( - ofType(shareCaseActions.ADD_SHARE_ASSIGNED_CASES), - map((action: shareCaseActions.AddShareAssignedCases) => action.payload), + ofType(shareCaseActions.ADD_SHARE_CASES), + map((action: shareCaseActions.AddShareCases) => action.payload), map((newCases) => { - return new shareCaseActions.AddShareAssignedCaseGo({ - path: [`${this.router.url}/case-share`], - sharedCases: newCases.sharedCases + return new shareCaseActions.AddShareCaseGo({ + path: this.getPathFromCaseConfig(newCases.caaPageType, newCases.group_access), + sharedCases: newCases.sharedCases, + caaPageType: newCases.caaPageType }); }) ) ); - public addShareUnassignedCases$ = createEffect(() => + public navigateToAddShareCase$ = createEffect(() => this.actions$.pipe( - ofType(shareCaseActions.ADD_SHARE_UNASSIGNED_CASES), - map((action: shareCaseActions.AddShareUnassignedCases) => action.payload), - map((newCases) => { - return new shareCaseActions.AddShareUnassignedCaseGo({ - path: this.getPathFromCaseConfig(newCases.group_access, newCases.new_cases), - sharedCases: newCases.sharedCases - }); - }) - ) - ); - - public navigateToAddShareAssignedCase$ = createEffect(() => - this.actions$.pipe( - ofType(shareCaseActions.ADD_SHARE_ASSIGNED_CASES_GO), - map((action: shareCaseActions.AddShareAssignedCaseGo) => action.payload), - tap(({ path, query: queryParams, extras, sharedCases }) => { - const thatSharedCases = sharedCases; - queryParams = { init: true, pageType: CaaCasesPageType.AssignedCases }; - return this.router.navigate(path, { queryParams, ...extras }).then(() => { - this.store.dispatch(new shareCaseActions.NavigateToShareAssignedCases(thatSharedCases)); - }); - }) - ), - { dispatch: false } - ); - - public navigateToAddShareUnassignedCase$ = createEffect(() => - this.actions$.pipe( - ofType(shareCaseActions.ADD_SHARE_UNASSIGNED_CASES_GO), - map((action: shareCaseActions.AddShareUnassignedCaseGo) => action.payload), - tap(({ path, query: queryParams, extras, sharedCases }) => { + ofType(shareCaseActions.ADD_SHARE_CASES_GO), + map((action: shareCaseActions.AddShareCaseGo) => action.payload), + tap(({ path, query: queryParams, extras, sharedCases, caaPageType }) => { const thatSharedCases = sharedCases; - queryParams = { init: true, pageType: CaaCasesPageType.UnassignedCases }; + const currentPageType = caaPageType === CaaCasesPageType.UnassignedCases ? CaaCasesPageType.UnassignedCases : CaaCasesPageType.AssignedCases; + console.log('current page type', currentPageType); + queryParams = { init: true, pageType: currentPageType }; return this.router.navigate(path, { queryParams, ...extras }).then(() => { - this.store.dispatch(new shareCaseActions.NavigateToShareUnassignedCases(thatSharedCases)); + this.store.dispatch(new shareCaseActions.NavigateToShareCases(thatSharedCases)); }); }) ), { dispatch: false } ); - public loadShareAssignedCases$ = createEffect(() => - this.actions$.pipe( - ofType(shareCaseActions.LOAD_SHARE_ASSIGNED_CASES), - map((action: shareCaseActions.LoadShareAssignedCases) => action.payload), - switchMap((payload) => { - this.payload = payload; - return this.caseShareService.getShareCases(payload).pipe( - map((response) => new shareCaseActions.LoadShareAssignedCasesSuccess(response)), - catchError(() => of(new fromRoot.Go({ path: ['/service-down'] }))) - ); - }) - ) - ); - - public loadShareUnassignedCases$ = createEffect(() => + public loadShareCases$ = createEffect(() => this.actions$.pipe( - ofType(shareCaseActions.LOAD_SHARE_UNASSIGNED_CASES), - map((action: shareCaseActions.LoadShareUnassignedCases) => action.payload), + ofType(shareCaseActions.LOAD_SHARE_CASES), + map((action: shareCaseActions.LoadShareCases) => action.payload), switchMap((payload) => { this.payload = payload; return this.caseShareService.getShareCases(payload).pipe( - map((response) => new shareCaseActions.LoadShareUnassignedCasesSuccess(response)), + map((response) => new shareCaseActions.LoadShareCasesSuccess(response)), catchError(() => of(new fromRoot.Go({ path: ['/service-down'] }))) ); }) @@ -118,36 +79,22 @@ export class ShareCaseEffects { ) ); - public assignUsersToAssignedCases$ = createEffect(() => - this.actions$.pipe( - ofType(shareCaseActions.ASSIGN_USERS_TO_ASSIGNED_CASE), - map((action: shareCaseActions.AssignUsersToAssignedCase) => action.payload), - switchMap((payload) => { - this.payload = payload; - return this.caseShareService.assignUsersWithCases(payload).pipe( - map((response) => new shareCaseActions.AssignUsersToAssignedCaseSuccess(response)), - catchError(() => of(new fromRoot.Go({ path: ['/service-down'] }))) - ); - }) - ) - ); - - public assignUsersToUnassignedCases$ = createEffect(() => + public assignUsersToCases$ = createEffect(() => this.actions$.pipe( - ofType(shareCaseActions.ASSIGN_USERS_TO_UNASSIGNED_CASE), - map((action: shareCaseActions.AssignUsersToUnassignedCase) => action.payload), + ofType(shareCaseActions.ASSIGN_USERS_TO_CASE), + map((action: shareCaseActions.AssignUsersToCase) => action.payload), switchMap((payload) => { this.payload = payload; return this.caseShareService.assignUsersWithCases(payload).pipe( - map((response) => new shareCaseActions.AssignUsersToUnassignedCaseSuccess(response)), + map((response) => new shareCaseActions.AssignUsersToCaseSuccess(response)), catchError(() => of(new fromRoot.Go({ path: ['/service-down'] }))) ); }) ) ); - public getPathFromCaseConfig(groupAccess?: boolean, newCases?: boolean) { - if (groupAccess){ + public getPathFromCaseConfig(caaCasesPageType: string, groupAccess?: boolean, newCases?: boolean) { + if (groupAccess && caaCasesPageType === CaaCasesPageType.NewCases) { return [`${this.router.url}/accept-cases`]; } return [`${this.router.url}/case-share`]; diff --git a/src/cases/store/reducers/caa-cases.reducer.ts b/src/cases/store/reducers/caa-cases.reducer.ts index 785943aee..cf61babc0 100644 --- a/src/cases/store/reducers/caa-cases.reducer.ts +++ b/src/cases/store/reducers/caa-cases.reducer.ts @@ -4,33 +4,25 @@ import { CaaCases, SelectedCases } from '../../models/caa-cases.model'; import * as fromCaaActions from '../actions/caa-cases.actions'; export interface CaaCasesState { - assignedCases: CaaCases; - unassignedCases: CaaCases; + Cases: CaaCases; caseTypes: SubNavigation[]; selectedCases: SelectedCases; - assignedCasesLastError: HttpErrorResponse; - unassignedCasesLastError: HttpErrorResponse; + CasesLastError: HttpErrorResponse; } export const initialState: CaaCasesState = { - assignedCases: null, - unassignedCases: null, + Cases: null, caseTypes: [], selectedCases: {}, - assignedCasesLastError: null, - unassignedCasesLastError: null + CasesLastError: null }; export function caaCasesReducer(state = initialState, action: fromCaaActions.CaaCasesActions): CaaCasesState { switch (action.type) { - case fromCaaActions.LOAD_ASSIGNED_CASES_SUCCESS: - return { ...state, assignedCases: action.payload, assignedCasesLastError: null }; - case fromCaaActions.LOAD_ASSIGNED_CASES_FAILURE: - return { ...state, assignedCases: { idField: '', columnConfigs: [], data: [] }, assignedCasesLastError: action.payload }; - case fromCaaActions.LOAD_UNASSIGNED_CASES_SUCCESS: - return { ...state, unassignedCases: action.payload, unassignedCasesLastError: null }; - case fromCaaActions.LOAD_UNASSIGNED_CASES_FAILURE: - return { ...state, unassignedCases: { idField: '', columnConfigs: [], data: [] }, unassignedCasesLastError: action.payload }; + case fromCaaActions.LOAD_CASES_SUCCESS: + return { ...state, Cases: action.payload, CasesLastError: null }; + case fromCaaActions.LOAD_CASES_FAILURE: + return { ...state, Cases: { idField: '', columnConfigs: [], data: [] }, CasesLastError: action.payload }; case fromCaaActions.LOAD_CASE_TYPES_SUCCESS: return { ...state, caseTypes: action.payload }; case fromCaaActions.UPDATE_SELECTION_FOR_CASE_TYPE: @@ -42,8 +34,6 @@ export function caaCasesReducer(state = initialState, action: fromCaaActions.Caa } } -export const getAssignedCases = (state: CaaCasesState) => state.assignedCases; -export const getAssignedCasesError = (state: CaaCasesState) => state.assignedCasesLastError; -export const getUnassignedCases = (state: CaaCasesState) => state.unassignedCases; -export const getUnassignedCasesError = (state: CaaCasesState) => state.unassignedCasesLastError; +export const getCases = (state: CaaCasesState) => state.Cases; +export const getCasesError = (state: CaaCasesState) => state.CasesLastError; export const getCaseTypes = (state: CaaCasesState) => state.caseTypes; diff --git a/src/cases/store/reducers/share-case.reducer.ts b/src/cases/store/reducers/share-case.reducer.ts index abcc1391e..c942f8217 100644 --- a/src/cases/store/reducers/share-case.reducer.ts +++ b/src/cases/store/reducers/share-case.reducer.ts @@ -3,16 +3,14 @@ import { UserDetails } from '@hmcts/rpx-xui-common-lib/lib/models/user-details.m import * as ShareCasesActions from '../actions/share-case.action'; export interface ShareCasesState { - shareAssignedCases: SharedCase[]; - shareUnassignedCases: SharedCase[]; + shareCases: SharedCase[]; loading: boolean; error: Error; users: UserDetails[]; } export const initialSharedCasesState: ShareCasesState = { - shareAssignedCases: [], - shareUnassignedCases: [], + shareCases: [], loading: false, error: undefined, users: [] @@ -22,24 +20,24 @@ export function shareCasesReducer( state: ShareCasesState = initialSharedCasesState, action: ShareCasesActions.Actions): ShareCasesState { switch (action.type) { - case ShareCasesActions.NAVIGATE_TO_SHARE_ASSIGNED_CASES: - const navigateToShareAssignedCases = state.shareAssignedCases.slice(); + case ShareCasesActions.NAVIGATE_TO_SHARE_CASES: + const navigateToShareCases = state.shareCases.slice(); for (const aCase of action.payload) { - if (!navigateToShareAssignedCases.some((hasACase) => hasACase.caseId === aCase.caseId)) { - navigateToShareAssignedCases.push(aCase); + if (!navigateToShareCases.some((hasACase) => hasACase.caseId === aCase.caseId)) { + navigateToShareCases.push(aCase); } } return { ...state, - shareAssignedCases: navigateToShareAssignedCases + shareCases: navigateToShareCases }; - case ShareCasesActions.LOAD_SHARE_ASSIGNED_CASES: + case ShareCasesActions.LOAD_SHARE_CASES: return { ...state, loading: true }; - case ShareCasesActions.LOAD_SHARE_ASSIGNED_CASES_SUCCESS: - const casesInStore = state.shareAssignedCases.slice(); + case ShareCasesActions.LOAD_SHARE_CASES_SUCCESS: + const casesInStore = state.shareCases.slice(); const casesFromNode: SharedCase[] = sortedUserInCases(action.payload); const casesWithTypes = []; for (const aCase of casesInStore) { @@ -59,39 +57,39 @@ export function shareCasesReducer( } return { ...state, - shareAssignedCases: casesWithTypes, + shareCases: casesWithTypes, loading: false }; - case ShareCasesActions.LOAD_SHARE_ASSIGNED_CASES_FAILURE: + case ShareCasesActions.LOAD_SHARE_CASES_FAILURE: return { ...state, error: action.payload, loading: false }; - case ShareCasesActions.ADD_SHARE_ASSIGNED_CASES: - const addShareAssignedCases = state.shareAssignedCases.slice(); + case ShareCasesActions.ADD_SHARE_CASES: + const addShareCases = state.shareCases.slice(); for (const aCase of action.payload.sharedCases) { - if (!addShareAssignedCases.some((hasACase) => hasACase.caseId === aCase.caseId)) { - addShareAssignedCases.push(aCase); + if (!addShareCases.some((hasACase) => hasACase.caseId === aCase.caseId)) { + addShareCases.push(aCase); } } return { ...state, - shareAssignedCases: addShareAssignedCases + shareCases: addShareCases }; - case ShareCasesActions.ADD_SHARE_ASSIGNED_CASES_GO: - const addShareAssignedCasesGo = state.shareAssignedCases.slice(); + case ShareCasesActions.ADD_SHARE_CASES_GO: + const addShareCasesGo = state.shareCases.slice(); for (const aCase of action.payload.sharedCases) { - if (!addShareAssignedCasesGo.some((hasACase) => hasACase.caseId === aCase.caseId)) { - addShareAssignedCasesGo.push(aCase); + if (!addShareCasesGo.some((hasACase) => hasACase.caseId === aCase.caseId)) { + addShareCasesGo.push(aCase); } } return { ...state, - shareAssignedCases: addShareAssignedCasesGo + shareCases: addShareCasesGo }; - case ShareCasesActions.DELETE_A_SHARE_ASSIGNED_CASE: - const caseInStore4Delete = state.shareAssignedCases.slice(); + case ShareCasesActions.DELETE_A_SHARE_CASE: + const caseInStore4Delete = state.shareCases.slice(); for (let i = 0, l = caseInStore4Delete.length; i < l; i++) { if (caseInStore4Delete[i].caseId === action.payload.caseId) { caseInStore4Delete.splice(i, 1); @@ -100,127 +98,30 @@ export function shareCasesReducer( } return { ...state, - shareAssignedCases: caseInStore4Delete + shareCases: caseInStore4Delete }; - case ShareCasesActions.SYNCHRONIZE_STATE_TO_STORE_ASSIGNED_CASES: + case ShareCasesActions.SYNCHRONIZE_STATE_TO_STORE_CASES: return { ...state, - shareAssignedCases: action.payload + shareCases: action.payload }; - case ShareCasesActions.ASSIGN_USERS_TO_ASSIGNED_CASE_SUCCESS: + case ShareCasesActions.ASSIGN_USERS_TO_CASE_SUCCESS: return { ...state, - shareAssignedCases: action.payload, + shareCases: action.payload, loading: true }; - case ShareCasesActions.RESET_ASSIGNED_CASE_SELECTION: + case ShareCasesActions.RESET_CASE_SELECTION: return { ...state, - shareAssignedCases: [], + shareCases: [], loading: false }; - case ShareCasesActions.NAVIGATE_TO_SHARE_UNASSIGNED_CASES: - const navigateToShareUnassignedCases = state.shareUnassignedCases.slice(); - for (const aCase of action.payload) { - if (!navigateToShareUnassignedCases.some((hasACase) => hasACase.caseId === aCase.caseId)) { - navigateToShareUnassignedCases.push(aCase); - } - } - return { - ...state, - shareUnassignedCases: navigateToShareUnassignedCases - }; - case ShareCasesActions.LOAD_SHARE_UNASSIGNED_CASES: - return { - ...state, - loading: true - }; - case ShareCasesActions.LOAD_SHARE_UNASSIGNED_CASES_SUCCESS: - const unassignedCasesInStore = state.shareUnassignedCases.slice(); - const unassignedCasesFromNode: SharedCase[] = sortedUserInCases(action.payload); - const unassignedCasesWithTypes = []; - for (const aCase of unassignedCasesInStore) { - const intersectionCase = unassignedCasesFromNode.find((nodeCase) => nodeCase.caseId === aCase.caseId); - if (intersectionCase && intersectionCase.caseId) { - const caseTypeId = aCase.caseTypeId ? aCase.caseTypeId : null; - const caseTitle = aCase.caseTitle ? aCase.caseTitle : null; - const newCase: SharedCase = { - ...intersectionCase, - caseTypeId, - caseTitle - }; - unassignedCasesWithTypes.push(newCase); - } else { - unassignedCasesWithTypes.push(aCase); - } - } - return { - ...state, - shareUnassignedCases: unassignedCasesWithTypes, - loading: false - }; - case ShareCasesActions.LOAD_SHARE_UNASSIGNED_CASES_FAILURE: - return { - ...state, - error: action.payload, - loading: false - }; - case ShareCasesActions.ADD_SHARE_UNASSIGNED_CASES: - const addShareUnassignedCases = state.shareUnassignedCases.slice(); - for (const aCase of action.payload.sharedCases) { - if (!addShareUnassignedCases.some((hasACase) => hasACase.caseId === aCase.caseId)) { - addShareUnassignedCases.push(aCase); - } - } - return { - ...state, - shareUnassignedCases: addShareUnassignedCases - }; - case ShareCasesActions.ADD_SHARE_UNASSIGNED_CASES_GO: - const addShareUnassignedCasesGo = state.shareUnassignedCases.slice(); - for (const aCase of action.payload.sharedCases) { - if (!addShareUnassignedCasesGo.some((hasACase) => hasACase.caseId === aCase.caseId)) { - addShareUnassignedCasesGo.push(aCase); - } - } - return { - ...state, - shareUnassignedCases: addShareUnassignedCasesGo - }; - case ShareCasesActions.DELETE_A_SHARE_UNASSIGNED_CASE: - const unassignedCaseInStore4Delete = state.shareUnassignedCases.slice(); - for (let i = 0, l = unassignedCaseInStore4Delete.length; i < l; i++) { - if (unassignedCaseInStore4Delete[i].caseId === action.payload.caseId) { - unassignedCaseInStore4Delete.splice(i, 1); - break; - } - } - return { - ...state, - shareUnassignedCases: unassignedCaseInStore4Delete - }; case ShareCasesActions.LOAD_USERS_FROM_ORG_FOR_CASE_SUCCESS: return { ...state, users: action.payload }; - case ShareCasesActions.SYNCHRONIZE_STATE_TO_STORE_UNASSIGNED_CASES: - return { - ...state, - shareUnassignedCases: action.payload - }; - case ShareCasesActions.ASSIGN_USERS_TO_UNASSIGNED_CASE_SUCCESS: - return { - ...state, - shareUnassignedCases: action.payload, - loading: true - }; - case ShareCasesActions.RESET_UNASSIGNED_CASE_SELECTION: - return { - ...state, - shareUnassignedCases: [], - loading: false - }; default: return state; } @@ -245,6 +146,5 @@ export function sortedUserInCases(pendingSortedCases: SharedCase[]): SharedCase[ return cases; } -export const getShareAssignedCases = (state: ShareCasesState) => state.shareAssignedCases; -export const getShareUnassignedCases = (state: ShareCasesState) => state.shareUnassignedCases; +export const getShareCases = (state: ShareCasesState) => state.shareCases; export const getOrganisationUsers = (state: ShareCasesState) => state.users; diff --git a/src/cases/store/selectors/caa-cases.selector.ts b/src/cases/store/selectors/caa-cases.selector.ts index 83966be27..c01937379 100644 --- a/src/cases/store/selectors/caa-cases.selector.ts +++ b/src/cases/store/selectors/caa-cases.selector.ts @@ -7,33 +7,18 @@ export const getCaaCasesState = createSelector( (state: fromFeature.CaaCasesState) => state.caaCases ); -export const getAllAssignedCases = createSelector( +export const getAllCases = createSelector( getCaaCasesState, - (caaCases) => caaCases.assignedCases + (caaCases) => caaCases.Cases ); -export const getAllAssignedCasesError = createSelector( +export const getAllCasesError = createSelector( getCaaCasesState, - fromFeature.getAssignedCasesError + fromFeature.getCasesError ); -export const getAllAssignedCaseData = createSelector( - getAllAssignedCases, - (caaCases) => caaCases ? caaCases.data : null -); - -export const getAllUnassignedCases = createSelector( - getCaaCasesState, - (caaCases) => caaCases.unassignedCases -); - -export const getAllUnassignedCasesError = createSelector( - getCaaCasesState, - fromFeature.getUnassignedCasesError -); - -export const getAllUnassignedCaseData = createSelector( - getAllUnassignedCases, +export const getAllCaseData = createSelector( + getAllCases, (caaCases) => caaCases ? caaCases.data : null ); diff --git a/src/cases/store/selectors/share-case.selector.ts b/src/cases/store/selectors/share-case.selector.ts index 289872492..f3fb185e0 100644 --- a/src/cases/store/selectors/share-case.selector.ts +++ b/src/cases/store/selectors/share-case.selector.ts @@ -7,14 +7,9 @@ export const getCaseShareState = createSelector( (state: fromFeature.CaaCasesState) => state.caseShare ); -export const getShareAssignedCaseListState = createSelector( +export const getShareCaseListState = createSelector( getCaseShareState, - fromFeature.getShareAssignedCases -); - -export const getShareUnassignedCaseListState = createSelector( - getCaseShareState, - fromFeature.getShareUnassignedCases + fromFeature.getShareCases ); export const getOrganisationUsersState = createSelector( From b56c6cde0002c55031b87d78e4c2f2ee55654577 Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 18 Feb 2025 10:46:55 +0000 Subject: [PATCH 14/44] WIP --- package.json | 2 +- .../case-share-complete.component.spec.ts | 4 +- src/cases/containers/cases/cases.component.ts | 2 +- .../store/actions/caa-cases.actions.spec.ts | 46 ++++---- .../store/actions/share-case.action.spec.ts | 106 ++++++++--------- .../store/effects/caa-cases.effects.spec.ts | 72 +++--------- .../store/effects/share-case.effects.spec.ts | 107 ++++-------------- .../store/reducers/caa-cases.reducer.spec.ts | 31 +---- .../store/reducers/share-case.reducer.spec.ts | 100 ++++++++-------- .../selectors/caa-cases.selector.spec.ts | 22 ++-- .../selectors/share-case.selectors.spec.ts | 13 ++- src/cases/util/caa-cases.util.spec.ts | 3 +- yarn.lock | 17 +-- 13 files changed, 201 insertions(+), 324 deletions(-) diff --git a/package.json b/package.json index cb29b2be3..bad31df5e 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "@hmcts/media-viewer": "4.0.4", "@hmcts/nodejs-healthcheck": "1.7.0", "@hmcts/properties-volume": "0.0.13", - "@hmcts/rpx-xui-common-lib": "2.0.29", + "@hmcts/rpx-xui-common-lib": "link:../rpx-xui-common-lib/dist/exui-common-lib", "@hmcts/rpx-xui-node-lib": "2.29.1", "@ng-idle/core": "^14.0.0", "@ng-idle/keepalive": "^14.0.0", diff --git a/src/cases/containers/case-share-complete/case-share-complete.component.spec.ts b/src/cases/containers/case-share-complete/case-share-complete.component.spec.ts index 51c760c11..57c4df5ae 100644 --- a/src/cases/containers/case-share-complete/case-share-complete.component.spec.ts +++ b/src/cases/containers/case-share-complete/case-share-complete.component.spec.ts @@ -180,7 +180,7 @@ describe('CaseShareCompleteComponent', () => { component.ngOnInit(); component.completeScreenMode = 'COMPLETE'; fixture.detectChanges(); - expect(component.isFromAssignedCasesRoute).toBe(true); + expect(component.pageType).toBe('assigned-cases'); const successTextAssignedCases1 = fixture.debugElement.nativeElement.querySelector('#what-happens-next-added'); expect(successTextAssignedCases1).toBeTruthy(); expect(successTextAssignedCases1.textContent).toContain('The people you added'); @@ -197,7 +197,7 @@ describe('CaseShareCompleteComponent', () => { component.ngOnInit(); component.completeScreenMode = 'COMPLETE'; fixture.detectChanges(); - expect(component.isFromAssignedCasesRoute).toBe(false); + expect(component.pageType).toBe('unassgined-cases'); const successTextAssignedCases1 = fixture.debugElement.nativeElement.querySelector('#what-happens-next-added'); expect(successTextAssignedCases1).toBeNull(); const successTextAssignedCases2 = fixture.debugElement.nativeElement.querySelector('#what-happens-next-removed'); diff --git a/src/cases/containers/cases/cases.component.ts b/src/cases/containers/cases/cases.component.ts index eac5690f5..80594b342 100644 --- a/src/cases/containers/cases/cases.component.ts +++ b/src/cases/containers/cases/cases.component.ts @@ -47,7 +47,7 @@ export class CasesComponent implements OnInit { public cases: any; // can we type this? public casesError$: Observable; public caseResultsTableShareButtonText: string = 'Share cases'; - private selectedCases: any[] = []; + public selectedCases: any[] = []; constructor(private readonly caaCasesStore: Store, private readonly organisationStore: Store, diff --git a/src/cases/store/actions/caa-cases.actions.spec.ts b/src/cases/store/actions/caa-cases.actions.spec.ts index ef002b0c7..9be55981f 100644 --- a/src/cases/store/actions/caa-cases.actions.spec.ts +++ b/src/cases/store/actions/caa-cases.actions.spec.ts @@ -4,67 +4,69 @@ import { CaaCases } from '../../models/caa-cases.model'; import * as fromActions from './caa-cases.actions'; describe('Caa actions', () => { - it('load assigned cases action', () => { + it('load cases action', () => { const caseType = 'caseTypeId1'; const pageNo = 1; const pageSize = 10; const caaCasesFilterType = null; const caaCasesFilterValue = null; - const payload = { caseType, pageNo, pageSize, caaCasesFilterType, caaCasesFilterValue }; - const action = new fromActions.LoadAssignedCases(payload); + const caaCasesPage = 'assigned-cases'; + const payload = { caseType, pageNo, pageSize, caaCasesPage, caaCasesFilterType, caaCasesFilterValue }; + const action = new fromActions.LoadCases(payload); expect({ ...action }).toEqual({ payload, - type: fromActions.LOAD_ASSIGNED_CASES + type: fromActions.LOAD_CASES }); }); - it('load assigned cases success action', () => { + it('load cases success action', () => { const payload = {} as CaaCases; - const action = new fromActions.LoadAssignedCasesSuccess(payload); + const action = new fromActions.LoadCasesSuccess(payload); expect({ ...action }).toEqual({ payload, - type: fromActions.LOAD_ASSIGNED_CASES_SUCCESS + type: fromActions.LOAD_CASES_SUCCESS }); }); - it('load assigned cases failure action', () => { - const payload = new HttpErrorResponse({ error: 'assigned cases error' }); - const action = new fromActions.LoadAssignedCasesFailure(payload); + it('load cases failure action', () => { + const payload = new HttpErrorResponse({ error: ' cases error' }); + const action = new fromActions.LoadCasesFailure(payload); expect({ ...action }).toEqual({ payload, - type: fromActions.LOAD_ASSIGNED_CASES_FAILURE + type: fromActions.LOAD_CASES_FAILURE }); }); - it('load unassigned cases action', () => { + it('load un cases action', () => { const caseType = 'caseTypeId1'; const pageNo = 1; const pageSize = 10; const caaCasesFilterType = null; const caaCasesFilterValue = null; - const payload = { caseType, pageNo, pageSize, caaCasesFilterType, caaCasesFilterValue }; - const action = new fromActions.LoadUnassignedCases(payload); + const caaCasesPage = 'assigned-cases'; + const payload = { caseType, pageNo, pageSize, caaCasesPage, caaCasesFilterType, caaCasesFilterValue }; + const action = new fromActions.LoadCases(payload); expect({ ...action }).toEqual({ payload, - type: fromActions.LOAD_UNASSIGNED_CASES + type: fromActions.LOAD_CASES }); }); - it('load unassigned cases success action', () => { + it('load un cases success action', () => { const payload = {} as CaaCases; - const action = new fromActions.LoadUnassignedCasesSuccess(payload); + const action = new fromActions.LoadCasesSuccess(payload); expect({ ...action }).toEqual({ payload, - type: fromActions.LOAD_UNASSIGNED_CASES_SUCCESS + type: fromActions.LOAD_CASES_SUCCESS }); }); - it('load unassigned cases failure action', () => { - const payload = new HttpErrorResponse({ error: 'unassigned cases error' }); - const action = new fromActions.LoadUnassignedCasesFailure(payload); + it('load un cases failure action', () => { + const payload = new HttpErrorResponse({ error: 'un cases error' }); + const action = new fromActions.LoadCasesFailure(payload); expect({ ...action }).toEqual({ payload, - type: fromActions.LOAD_UNASSIGNED_CASES_FAILURE + type: fromActions.LOAD_CASES_FAILURE }); }); diff --git a/src/cases/store/actions/share-case.action.spec.ts b/src/cases/store/actions/share-case.action.spec.ts index 3775c8238..505696507 100644 --- a/src/cases/store/actions/share-case.action.spec.ts +++ b/src/cases/store/actions/share-case.action.spec.ts @@ -1,92 +1,92 @@ import * as fromCaseShare from './share-case.action'; describe('Case Share Actions', () => { - it('NavigateToShareAssignedCases', () => { + it('NavigateToShareCases', () => { const payload = []; - const action = new fromCaseShare.NavigateToShareAssignedCases(payload); + const action = new fromCaseShare.NavigateToShareCases(payload); expect({ ...action }).toEqual({ - type: fromCaseShare.NAVIGATE_TO_SHARE_ASSIGNED_CASES, + type: fromCaseShare.NAVIGATE_TO_SHARE_CASES, payload }); }); - it('NavigateToShareUnassignedCases', () => { + it('NavigateToShareCases', () => { const payload = []; - const action = new fromCaseShare.NavigateToShareUnassignedCases(payload); + const action = new fromCaseShare.NavigateToShareCases(payload); expect({ ...action }).toEqual({ - type: fromCaseShare.NAVIGATE_TO_SHARE_UNASSIGNED_CASES, + type: fromCaseShare.NAVIGATE_TO_SHARE_CASES, payload }); }); - it('SynchronizeStateToStoreAssignedCases', () => { + it('SynchronizeStateToStoreCases', () => { const payload = []; - const action = new fromCaseShare.SynchronizeStateToStoreAssignedCases(payload); + const action = new fromCaseShare.SynchronizeStateToStoreCases(payload); expect({ ...action }).toEqual({ - type: fromCaseShare.SYNCHRONIZE_STATE_TO_STORE_ASSIGNED_CASES, + type: fromCaseShare.SYNCHRONIZE_STATE_TO_STORE_CASES, payload }); }); - it('SynchronizeStateToStoreUnassignedCases', () => { + it('SynchronizeStateToStoreCases', () => { const payload = []; - const action = new fromCaseShare.SynchronizeStateToStoreUnassignedCases(payload); + const action = new fromCaseShare.SynchronizeStateToStoreCases(payload); expect({ ...action }).toEqual({ - type: fromCaseShare.SYNCHRONIZE_STATE_TO_STORE_UNASSIGNED_CASES, + type: fromCaseShare.SYNCHRONIZE_STATE_TO_STORE_CASES, payload }); }); - it('LoadShareAssignedCases', () => { + it('LoadShareCases', () => { const payload = []; - const action = new fromCaseShare.LoadShareAssignedCases(payload); + const action = new fromCaseShare.LoadShareCases(payload); expect({ ...action }).toEqual({ - type: fromCaseShare.LOAD_SHARE_ASSIGNED_CASES, + type: fromCaseShare.LOAD_SHARE_CASES, payload }); }); - it('LoadShareAssignedCasesSuccess', () => { + it('LoadShareCasesSuccess', () => { const payload = []; - const action = new fromCaseShare.LoadShareAssignedCasesSuccess(payload); + const action = new fromCaseShare.LoadShareCasesSuccess(payload); expect({ ...action }).toEqual({ - type: fromCaseShare.LOAD_SHARE_ASSIGNED_CASES_SUCCESS, + type: fromCaseShare.LOAD_SHARE_CASES_SUCCESS, payload }); }); - it('LoadShareAssignedCaseFailure', () => { + it('LoadShareCaseFailure', () => { const payload: Error = new Error(); - const action = new fromCaseShare.LoadShareAssignedCaseFailure(payload); + const action = new fromCaseShare.LoadShareCaseFailure(payload); expect({ ...action }).toEqual({ - type: fromCaseShare.LOAD_SHARE_ASSIGNED_CASES_FAILURE, + type: fromCaseShare.LOAD_SHARE_CASES_FAILURE, payload }); }); - it('LoadShareUnassignedCases', () => { + it('LoadShareCases', () => { const payload = []; - const action = new fromCaseShare.LoadShareUnassignedCases(payload); + const action = new fromCaseShare.LoadShareCases(payload); expect({ ...action }).toEqual({ - type: fromCaseShare.LOAD_SHARE_UNASSIGNED_CASES, + type: fromCaseShare.LOAD_SHARE_CASES, payload }); }); - it('LoadShareUnassignedAssignedCasesSuccess', () => { + it('LoadShareCasesSuccess', () => { const payload = []; - const action = new fromCaseShare.LoadShareUnassignedCasesSuccess(payload); + const action = new fromCaseShare.LoadShareCasesSuccess(payload); expect({ ...action }).toEqual({ - type: fromCaseShare.LOAD_SHARE_UNASSIGNED_CASES_SUCCESS, + type: fromCaseShare.LOAD_SHARE_CASES_SUCCESS, payload }); }); - it('LoadShareUnassignedCaseFailure', () => { + it('LoadShareCaseFailure', () => { const payload: Error = new Error(); - const action = new fromCaseShare.LoadShareUnassignedCaseFailure(payload); + const action = new fromCaseShare.LoadShareCaseFailure(payload); expect({ ...action }).toEqual({ - type: fromCaseShare.LOAD_SHARE_UNASSIGNED_CASES_FAILURE, + type: fromCaseShare.LOAD_SHARE_CASES_FAILURE, payload }); }); @@ -98,88 +98,88 @@ describe('Case Share Actions', () => { }); }); - it('AddShareAssignedCases', () => { + it('AddShareCases', () => { const payload = { sharedCases: [] }; - const action = new fromCaseShare.AddShareAssignedCases(payload); + const action = new fromCaseShare.AddShareCases(payload); expect({ ...action }).toEqual({ - type: fromCaseShare.ADD_SHARE_ASSIGNED_CASES, + type: fromCaseShare.ADD_SHARE_CASES, payload }); }); - it('AddShareAssignedCaseGo', () => { + it('AddShareCaseGo', () => { const payload = { path: [], sharedCases: [] }; - const action = new fromCaseShare.AddShareAssignedCaseGo(payload); + const action = new fromCaseShare.AddShareCaseGo(payload); expect({ ...action }).toEqual({ - type: fromCaseShare.ADD_SHARE_ASSIGNED_CASES_GO, + type: fromCaseShare.ADD_SHARE_CASES_GO, payload }); }); - it('AddShareUnassignedCases', () => { + it('AddShareCases', () => { const payload = { sharedCases: [] }; - const action = new fromCaseShare.AddShareUnassignedCases(payload); + const action = new fromCaseShare.AddShareCases(payload); expect({ ...action }).toEqual({ - type: fromCaseShare.ADD_SHARE_UNASSIGNED_CASES, + type: fromCaseShare.ADD_SHARE_CASES, payload }); }); - it('AddShareUnassignedCaseGo', () => { + it('AddShareCaseGo', () => { const payload = { path: [], sharedCases: [] }; - const action = new fromCaseShare.AddShareUnassignedCaseGo(payload); + const action = new fromCaseShare.AddShareCaseGo(payload); expect({ ...action }).toEqual({ - type: fromCaseShare.ADD_SHARE_UNASSIGNED_CASES_GO, + type: fromCaseShare.ADD_SHARE_CASES_GO, payload }); }); - it('DeleteAShareAssignedCase', () => { + it('DeleteAShareCase', () => { const payload = { caseId: '1' }; - const action = new fromCaseShare.DeleteAShareAssignedCase(payload); + const action = new fromCaseShare.DeleteAShareCase(payload); expect({ ...action }).toEqual({ - type: fromCaseShare.DELETE_A_SHARE_ASSIGNED_CASE, + type: fromCaseShare.DELETE_A_SHARE_CASE, payload }); }); - it('DeleteAShareUnassignedCase', () => { + it('DeleteAShareCase', () => { const payload = { caseId: '1' }; - const action = new fromCaseShare.DeleteAShareUnassignedCase(payload); + const action = new fromCaseShare.DeleteAShareCase(payload); expect({ ...action }).toEqual({ - type: fromCaseShare.DELETE_A_SHARE_UNASSIGNED_CASE, + type: fromCaseShare.DELETE_A_SHARE_CASE, payload }); }); it('AssignUsersToAssignedCase', () => { const payload = []; - const action = new fromCaseShare.AssignUsersToAssignedCase(payload); + const action = new fromCaseShare.AssignUsersToCase(payload); expect({ ...action }).toEqual({ - type: fromCaseShare.ASSIGN_USERS_TO_ASSIGNED_CASE, + type: fromCaseShare.ASSIGN_USERS_TO_CASE, payload }); }); - it('AssignUsersToUnassignedCase', () => { + it('AssignUsersToCase', () => { const payload = []; - const action = new fromCaseShare.AssignUsersToUnassignedCase(payload); + const action = new fromCaseShare.AssignUsersToCase(payload); expect({ ...action }).toEqual({ - type: fromCaseShare.ASSIGN_USERS_TO_UNASSIGNED_CASE, + type: fromCaseShare.ASSIGN_USERS_TO_CASE, payload }); }); diff --git a/src/cases/store/effects/caa-cases.effects.spec.ts b/src/cases/store/effects/caa-cases.effects.spec.ts index 55e320545..9534cbe4a 100644 --- a/src/cases/store/effects/caa-cases.effects.spec.ts +++ b/src/cases/store/effects/caa-cases.effects.spec.ts @@ -16,8 +16,7 @@ describe('CaaCasesEffects', () => { let effects: CaaCasesEffects; const caaCasesServiceMock = jasmine.createSpyObj('CaaCasesService', ['getCaaCases', 'getCaaCaseTypes']); const loggerServiceMock = jasmine.createSpyObj('LoggerService', ['error']); - const assignedCases = {} as CaaCases; - const unassignedCases = {} as CaaCases; + const cases = {} as CaaCases; const navItems = [] as NavItemModel[]; beforeEach(() => { @@ -35,23 +34,24 @@ describe('CaaCasesEffects', () => { addMatchers(); }); - describe('loadAssignedCases$', () => { - it('loadAssignedCases successful', () => { - caaCasesServiceMock.getCaaCases.and.returnValue(of(assignedCases)); + describe('loadCases$', () => { + it('loadCases successful', () => { + caaCasesServiceMock.getCaaCases.and.returnValue(of(cases)); const caseType = ''; const pageNo = 1; const pageSize = 10; const caaCasesFilterType = null; const caaCasesFilterValue = null; - const payload = { caseType, pageNo, pageSize, caaCasesFilterType, caaCasesFilterValue }; - const action = new caaCasesActions.LoadAssignedCases(payload); - const completion = new caaCasesActions.LoadAssignedCasesSuccess(assignedCases); + const caaCasesPage = CaaCasesPageType.AssignedCases; + const payload = { caseType, pageNo, pageSize, caaCasesPage, caaCasesFilterType, caaCasesFilterValue }; + const action = new caaCasesActions.LoadCases(payload); + const completion = new caaCasesActions.LoadCasesSuccess(cases); actions$ = hot('-a', { a: action }); const expected = cold('-b', { b: completion }); - expect(effects.loadAssignedCases$).toBeObservable(expected); + expect(effects.loadCases$).toBeObservable(expected); }); - it('loadAssignedCases error', () => { + it('loadCases error', () => { const error: HttpErrorResponse = { error: 'Error', status: 400, @@ -69,55 +69,13 @@ describe('CaaCasesEffects', () => { const pageSize = 10; const caaCasesFilterType = null; const caaCasesFilterValue = null; - const payload = { caseType, pageNo, pageSize, caaCasesFilterType, caaCasesFilterValue }; - const action = new caaCasesActions.LoadAssignedCases(payload); - const completion = new caaCasesActions.LoadAssignedCasesFailure(error); + const caaCasesPage = CaaCasesPageType.AssignedCases; + const payload = { caseType, pageNo, pageSize, caaCasesPage, caaCasesFilterType, caaCasesFilterValue }; + const action = new caaCasesActions.LoadCases(payload); + const completion = new caaCasesActions.LoadCasesFailure(error); actions$ = hot('-a', { a: action }); const expected = cold('-b', { b: completion }); - expect(effects.loadAssignedCases$).toBeObservable(expected); - }); - }); - - describe('loadUnassignedCases$', () => { - it('loadUnassignedCases successful', () => { - caaCasesServiceMock.getCaaCases.and.returnValue(of(unassignedCases)); - const caseType = ''; - const pageNo = 1; - const pageSize = 10; - const caaCasesFilterType = null; - const caaCasesFilterValue = null; - const payload = { caseType, pageNo, pageSize, caaCasesFilterType, caaCasesFilterValue }; - const action = new caaCasesActions.LoadUnassignedCases(payload); - const completion = new caaCasesActions.LoadUnassignedCasesSuccess(unassignedCases); - actions$ = hot('-a', { a: action }); - const expected = cold('-b', { b: completion }); - expect(effects.loadUnassignedCases$).toBeObservable(expected); - }); - - it('loadUnassignedCases error', () => { - const error: HttpErrorResponse = { - error: 'Error', - status: 400, - message: 'Error', - headers: null, - statusText: null, - name: null, - ok: false, - type: null, - url: null - }; - caaCasesServiceMock.getCaaCases.and.returnValue(throwError(error)); - const caseType = ''; - const pageNo = 1; - const pageSize = 10; - const caaCasesFilterType = null; - const caaCasesFilterValue = null; - const payload = { caseType, pageNo, pageSize, caaCasesFilterType, caaCasesFilterValue }; - const action = new caaCasesActions.LoadUnassignedCases(payload); - const completion = new caaCasesActions.LoadUnassignedCasesFailure(error); - actions$ = hot('-a', { a: action }); - const expected = cold('-b', { b: completion }); - expect(effects.loadUnassignedCases$).toBeObservable(expected); + expect(effects.loadCases$).toBeObservable(expected); }); }); diff --git a/src/cases/store/effects/share-case.effects.spec.ts b/src/cases/store/effects/share-case.effects.spec.ts index 80f45974f..2950ec7d0 100644 --- a/src/cases/store/effects/share-case.effects.spec.ts +++ b/src/cases/store/effects/share-case.effects.spec.ts @@ -8,18 +8,12 @@ import { addMatchers, cold, hot, initTestScheduler } from 'jasmine-marbles'; import { of } from 'rxjs'; import { CaseShareService } from '../../services'; import { - AddShareAssignedCaseGo, - AddShareAssignedCases, - AddShareUnassignedCaseGo, - AddShareUnassignedCases, - AssignUsersToAssignedCase, - AssignUsersToAssignedCaseSuccess, - AssignUsersToUnassignedCase, - AssignUsersToUnassignedCaseSuccess, - LoadShareAssignedCases, - LoadShareAssignedCasesSuccess, - LoadShareUnassignedCases, - LoadShareUnassignedCasesSuccess, + AddShareCaseGo, + AddShareCases, + AssignUsersToCase, + AssignUsersToCaseSuccess, + LoadShareCases, + LoadShareCasesSuccess, LoadUserFromOrgForCase, LoadUserFromOrgForCaseSuccess } from '../actions'; @@ -67,14 +61,14 @@ describe('Share Case Effects', () => { addMatchers(); })); - describe('addShareAssignedCases$', () => { + describe('addShareCases$', () => { it('should add share assigned case action', () => { - const action = new AddShareAssignedCases({ + const action = new AddShareCases({ sharedCases: [ { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' }] }); - const completion = new AddShareAssignedCaseGo({ + const completion = new AddShareCaseGo({ path: ['/unassigned-cases/case-share'], sharedCases: [ { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, @@ -82,30 +76,11 @@ describe('Share Case Effects', () => { }); actions$ = hot('-a', { a: action }); const expected = cold('-b', { b: completion }); - expect(effects.addShareAssignedCases$).toBeObservable(expected); + expect(effects.addShareCases$).toBeObservable(expected); }); }); - describe('addShareUnassignedCases$', () => { - it('should add share unassigned case action', () => { - const action = new AddShareUnassignedCases({ - sharedCases: [ - { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, - { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' }] - }); - const completion = new AddShareUnassignedCaseGo({ - path: ['/unassigned-cases/case-share'], - sharedCases: [ - { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, - { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' }] - }); - actions$ = hot('-a', { a: action }); - const expected = cold('-b', { b: completion }); - expect(effects.addShareUnassignedCases$).toBeObservable(expected); - }); - }); - - describe('navigateToAddShareAssignedCase$', () => { + describe('navigateToAddShareCase$', () => { it('should add share assigned case go', () => { const payload = { path: ['/unassigned-cases/case-share'], @@ -114,50 +89,16 @@ describe('Share Case Effects', () => { { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' }] }; routerMock.navigate.and.returnValue(Promise.resolve(true)); - const action = new AddShareAssignedCaseGo(payload); + const action = new AddShareCaseGo(payload); actions$ = hot('-a', { a: action }); - effects.navigateToAddShareAssignedCase$.subscribe(() => { + effects.navigateToAddShareCase$.subscribe(() => { expect(routerMock.navigate).toHaveBeenCalled(); }); }); }); - describe('navigateToAddShareUnassignedCase$', () => { - it('should add share unassigned case go', () => { - const payload = { - path: ['/unassigned-cases/case-share'], - sharedCases: [ - { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, - { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' }] - }; - routerMock.navigate.and.returnValue(Promise.resolve(true)); - const action = new AddShareUnassignedCaseGo(payload); - actions$ = hot('-a', { a: action }); - effects.navigateToAddShareUnassignedCase$.subscribe(() => { - expect(routerMock.navigate).toHaveBeenCalled(); - }); - }); - }); - - describe('loadShareAssignedCases$', () => { - it('should load share assigned cases', () => { - const requestPayload = [ - { caseId: '1', caseTitle: 'James123' }, - { caseId: '2', caseTitle: 'Steve321' }]; - const returnPayload = [ - { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, - { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' }]; - caseShareServiceMock.getShareCases.and.returnValue(of(returnPayload)); - const action = new LoadShareAssignedCases(requestPayload); - const completion = new LoadShareAssignedCasesSuccess(returnPayload); - actions$ = hot('-a', { a: action }); - const expected = cold('-b', { b: completion }); - expect(effects.loadShareAssignedCases$).toBeObservable(expected); - }); - }); - - describe('loadShareUnassignedCases$', () => { - it('should load share unassigned cases', () => { + describe('loadShareCases$', () => { + it('should load share cases', () => { const requestPayload = [ { caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }]; @@ -165,11 +106,11 @@ describe('Share Case Effects', () => { { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' }]; caseShareServiceMock.getShareCases.and.returnValue(of(returnPayload)); - const action = new LoadShareUnassignedCases(requestPayload); - const completion = new LoadShareUnassignedCasesSuccess(returnPayload); + const action = new LoadShareCases(requestPayload); + const completion = new LoadShareCasesSuccess(returnPayload); actions$ = hot('-a', { a: action }); const expected = cold('-b', { b: completion }); - expect(effects.loadShareUnassignedCases$).toBeObservable(expected); + expect(effects.loadShareCases$).toBeObservable(expected); }); }); @@ -222,11 +163,11 @@ describe('Share Case Effects', () => { { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' }]; caseShareServiceMock.assignUsersWithCases.and.returnValue(of(returnPayload)); - const action = new AssignUsersToAssignedCase(requestPayload); - const completion = new AssignUsersToAssignedCaseSuccess(returnPayload); + const action = new AssignUsersToCase(requestPayload); + const completion = new AssignUsersToCaseSuccess(returnPayload); actions$ = hot('-a', { a: action }); const expected = cold('-b', { b: completion }); - expect(effects.assignUsersToAssignedCases$).toBeObservable(expected); + expect(effects.assignUsersToCases$).toBeObservable(expected); }); }); @@ -255,11 +196,11 @@ describe('Share Case Effects', () => { { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' }]; caseShareServiceMock.assignUsersWithCases.and.returnValue(of(returnPayload)); - const action = new AssignUsersToUnassignedCase(requestPayload); - const completion = new AssignUsersToUnassignedCaseSuccess(returnPayload); + const action = new AssignUsersToCase(requestPayload); + const completion = new AssignUsersToCaseSuccess(returnPayload); actions$ = hot('-a', { a: action }); const expected = cold('-b', { b: completion }); - expect(effects.assignUsersToUnassignedCases$).toBeObservable(expected); + expect(effects.assignUsersToCases$).toBeObservable(expected); }); }); }); diff --git a/src/cases/store/reducers/caa-cases.reducer.spec.ts b/src/cases/store/reducers/caa-cases.reducer.spec.ts index 41ec429b7..c71b75083 100644 --- a/src/cases/store/reducers/caa-cases.reducer.spec.ts +++ b/src/cases/store/reducers/caa-cases.reducer.spec.ts @@ -4,20 +4,14 @@ import * as fromCaaCasesReducer from './caa-cases.reducer'; describe('CaaCases Reducer', () => { const initialState: fromCaaCasesReducer.CaaCasesState = { - assignedCases: { + Cases: { idField: 'id1', columnConfigs: null, data: null }, - unassignedCases: { - idField: 'id2', - columnConfigs: null, - data: null - }, caseTypes: [], selectedCases: {}, - assignedCasesLastError: new HttpErrorResponse({ error: 'assigned cases error' }), - unassignedCasesLastError: new HttpErrorResponse({ error: 'unassigned cases error' }) + CasesLastError: new HttpErrorResponse({ error: 'assigned cases error' }) }; it('should undefined action return default state', () => { @@ -28,29 +22,16 @@ describe('CaaCases Reducer', () => { }); it('should loadAssignedCasesSuccess action set correct state', () => { - const action = new fromActions.LoadAssignedCasesSuccess(initialState.assignedCases); + const action = new fromActions.LoadCasesSuccess(initialState.Cases); const state = fromCaaCasesReducer.caaCasesReducer(initialState, action); - expect(state.assignedCases).toBe(initialState.assignedCases); + expect(state.Cases).toBe(initialState.Cases); }); it('should LoadAssignedCasesFailure action set error', () => { const error = new HttpErrorResponse({ error: 'assigned cases error' }); - const action = new fromActions.LoadAssignedCasesFailure(initialState.assignedCasesLastError); - const state = fromCaaCasesReducer.caaCasesReducer(initialState, action); - expect(state.assignedCasesLastError).toEqual(error); - }); - - it('should loadUnassignedCasesSuccess action set correct state', () => { - const action = new fromActions.LoadUnassignedCasesSuccess(initialState.unassignedCases); - const state = fromCaaCasesReducer.caaCasesReducer(initialState, action); - expect(state.unassignedCases).toBe(initialState.unassignedCases); - }); - - it('should LoadUnassignedCasesFailure action set error', () => { - const error = new HttpErrorResponse({ error: 'unassigned cases error' }); - const action = new fromActions.LoadUnassignedCasesFailure(initialState.unassignedCasesLastError); + const action = new fromActions.LoadCasesFailure(initialState.CasesLastError); const state = fromCaaCasesReducer.caaCasesReducer(initialState, action); - expect(state.unassignedCasesLastError).toEqual(error); + expect(state.CasesLastError).toEqual(error); }); it('should loadCaseTypesSuccess action set correct state', () => { diff --git a/src/cases/store/reducers/share-case.reducer.spec.ts b/src/cases/store/reducers/share-case.reducer.spec.ts index d7e17ee4e..cf93b3872 100644 --- a/src/cases/store/reducers/share-case.reducer.spec.ts +++ b/src/cases/store/reducers/share-case.reducer.spec.ts @@ -14,39 +14,39 @@ describe('Share case reducer', () => { const payload = { sharedCases: [] }; - const action = new fromActions.AddShareAssignedCases(payload); + const action = new fromActions.AddShareCases(payload); const state = fromReducer.shareCasesReducer(initialState, action); - const mockState = { shareAssignedCases: [], shareUnassignedCases: [], loading: false, error: undefined, users: [] }; + const mockState = { shareCases: [], loading: false, error: undefined, users: [] }; expect(state).toEqual(mockState); }); it('should load state when navigate to share assigned case', () => { const selectedCases = [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }]; - const action = new fromActions.NavigateToShareAssignedCases(selectedCases); + const action = new fromActions.NavigateToShareCases(selectedCases); const state = fromReducer.shareCasesReducer(initialState, action); - expect(state.shareAssignedCases.length).toEqual(2); + expect(state.shareCases.length).toEqual(2); }); it('should load state when navigate to share unassigned case', () => { const selectedCases = [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }]; - const action = new fromActions.NavigateToShareUnassignedCases(selectedCases); + const action = new fromActions.NavigateToShareCases(selectedCases); const state = fromReducer.shareCasesReducer(initialState, action); - expect(state.shareUnassignedCases.length).toEqual(2); + expect(state.shareCases.length).toEqual(2); }); it('should load share assigned case', () => { const selectedCases = []; - const action = new fromActions.LoadShareAssignedCases(selectedCases); + const action = new fromActions.LoadShareCases(selectedCases); const state = fromReducer.shareCasesReducer(initialState, action); - expect(state.shareAssignedCases.length).toEqual(0); + expect(state.shareCases.length).toEqual(0); expect(state.loading).toBeTruthy(); }); it('should load share unassigned case', () => { const selectedCases = []; - const action = new fromActions.LoadShareUnassignedCases(selectedCases); + const action = new fromActions.LoadShareCases(selectedCases); const state = fromReducer.shareCasesReducer(initialState, action); - expect(state.shareUnassignedCases.length).toEqual(0); + expect(state.shareCases.length).toEqual(0); expect(state.loading).toBeTruthy(); }); @@ -58,9 +58,9 @@ describe('Share case reducer', () => { { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' } ] }; - const action = new fromActions.AddShareAssignedCaseGo(payload); + const action = new fromActions.AddShareCaseGo(payload); const state = fromReducer.shareCasesReducer(initialState, action); - expect(state.shareAssignedCases.length).toEqual(2); + expect(state.shareCases.length).toEqual(2); }); it('should load share unassigned case', () => { @@ -71,57 +71,57 @@ describe('Share case reducer', () => { { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' } ] }; - const action = new fromActions.AddShareUnassignedCaseGo(payload); + const action = new fromActions.AddShareCaseGo(payload); const state = fromReducer.shareCasesReducer(initialState, action); - expect(state.shareUnassignedCases.length).toEqual(2); + expect(state.shareCases.length).toEqual(2); }); it('should load share assigned case with case type', () => { initialState = { - shareAssignedCases: [ + shareCases: [ { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' } ] }; const caseFromNode = [{ caseId: '1', caseTitle: '' }, { caseId: '2', caseTitle: '' }]; - const action = new fromActions.LoadShareAssignedCasesSuccess(caseFromNode); + const action = new fromActions.LoadShareCasesSuccess(caseFromNode); const state = fromReducer.shareCasesReducer(initialState, action); - expect(state.shareAssignedCases.length).toEqual(2); - expect(state.shareAssignedCases[0].caseTypeId).toEqual('type1'); - expect(state.shareAssignedCases[0].caseTitle).toEqual('James123'); + expect(state.shareCases.length).toEqual(2); + expect(state.shareCases[0].caseTypeId).toEqual('type1'); + expect(state.shareCases[0].caseTitle).toEqual('James123'); }); it('should load share unassigned case with case type', () => { initialState = { - shareUnassignedCases: [ + shareCases: [ { caseId: '1', caseTitle: 'James123', caseTypeId: 'type1' }, { caseId: '2', caseTitle: 'Steve321', caseTypeId: 'type2' } ] }; const caseFromNode = [{ caseId: '1', caseTitle: '' }, { caseId: '2', caseTitle: '' }]; - const action = new fromActions.LoadShareUnassignedCasesSuccess(caseFromNode); + const action = new fromActions.LoadShareCasesSuccess(caseFromNode); const state = fromReducer.shareCasesReducer(initialState, action); - expect(state.shareUnassignedCases.length).toEqual(2); - expect(state.shareUnassignedCases[0].caseTypeId).toEqual('type1'); - expect(state.shareUnassignedCases[0].caseTitle).toEqual('James123'); + expect(state.shareCases.length).toEqual(2); + expect(state.shareCases[0].caseTypeId).toEqual('type1'); + expect(state.shareCases[0].caseTitle).toEqual('James123'); }); it('should save selected share assigned cases into store', () => { const selectedCases = { sharedCases: [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }] }; - const action = new fromActions.AddShareAssignedCases(selectedCases); + const action = new fromActions.AddShareCases(selectedCases); const state = fromReducer.shareCasesReducer(initialState, action); - expect(state.shareAssignedCases.length).toEqual(2); + expect(state.shareCases.length).toEqual(2); }); it('should save selected share unassigned cases into store', () => { const selectedCases = { sharedCases: [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }] }; - const action = new fromActions.AddShareUnassignedCases(selectedCases); + const action = new fromActions.AddShareCases(selectedCases); const state = fromReducer.shareCasesReducer(initialState, action); - expect(state.shareUnassignedCases.length).toEqual(2); + expect(state.shareCases.length).toEqual(2); }); it('should save selected share cases without duplication', () => { @@ -131,91 +131,91 @@ describe('Share case reducer', () => { const addedSelectedCases = { sharedCases: [{ caseId: '2', caseTitle: 'Steve321' }, { caseId: '3', caseTitle: 'Kenny456' }] }; - const oldAction = new fromActions.AddShareAssignedCases(selectedCases); + const oldAction = new fromActions.AddShareCases(selectedCases); const oldState = fromReducer.shareCasesReducer(initialState, oldAction); - const newAction = new fromActions.AddShareAssignedCases(addedSelectedCases); + const newAction = new fromActions.AddShareCases(addedSelectedCases); const newState = fromReducer.shareCasesReducer(oldState, newAction); - expect(newState.shareAssignedCases.length).toEqual(3); + expect(newState.shareCases.length).toEqual(3); }); it('should delete an assigned case from store', () => { const selectedCases = { sharedCases: [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }] }; - const oldAction = new fromActions.AddShareAssignedCases(selectedCases); + const oldAction = new fromActions.AddShareCases(selectedCases); const oldState = fromReducer.shareCasesReducer(initialState, oldAction); const payload = { caseId: '1' }; - const newAction = new fromActions.DeleteAShareAssignedCase(payload); + const newAction = new fromActions.DeleteAShareCase(payload); const newState = fromReducer.shareCasesReducer(oldState, newAction); - expect(newState.shareAssignedCases.length).toEqual(1); + expect(newState.shareCases.length).toEqual(1); }); it('should delete an unassigned case from store', () => { const selectedCases = { sharedCases: [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }] }; - const oldAction = new fromActions.AddShareUnassignedCases(selectedCases); + const oldAction = new fromActions.AddShareCases(selectedCases); const oldState = fromReducer.shareCasesReducer(initialState, oldAction); const payload = { caseId: '1' }; - const newAction = new fromActions.DeleteAShareUnassignedCase(payload); + const newAction = new fromActions.DeleteAShareCase(payload); const newState = fromReducer.shareCasesReducer(oldState, newAction); - expect(newState.shareUnassignedCases.length).toEqual(1); + expect(newState.shareCases.length).toEqual(1); }); it('should get state properties for assigned cases', () => { const selectedCases = { sharedCases: [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }] }; - const action = new fromActions.AddShareAssignedCases(selectedCases); + const action = new fromActions.AddShareCases(selectedCases); const state = fromReducer.shareCasesReducer(initialState, action); - expect(fromReducer.getShareAssignedCases(state).length).toEqual(2); + expect(fromReducer.getShareCases(state).length).toEqual(2); }); it('should get state properties for unassigned cases', () => { const selectedCases = { sharedCases: [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }] }; - const action = new fromActions.AddShareUnassignedCases(selectedCases); + const action = new fromActions.AddShareCases(selectedCases); const state = fromReducer.shareCasesReducer(initialState, action); - expect(fromReducer.getShareUnassignedCases(state).length).toEqual(2); + expect(fromReducer.getShareCases(state).length).toEqual(2); }); it('should load user from org for case success', () => { const sharedCases = [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }]; - const action = new fromActions.LoadShareAssignedCasesSuccess(sharedCases); + const action = new fromActions.LoadShareCasesSuccess(sharedCases); const state = fromReducer.shareCasesReducer(initialState, action); expect(fromReducer.getOrganisationUsers(state)).toBeTruthy(); }); it('should synchronize state to store for assigned cases', () => { const sharedCases = [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }]; - const action = new fromActions.SynchronizeStateToStoreAssignedCases(sharedCases); + const action = new fromActions.SynchronizeStateToStoreCases(sharedCases); const state = fromReducer.shareCasesReducer(initialState, action); - expect(fromReducer.getShareAssignedCases(state).length).toEqual(2); + expect(fromReducer.getShareCases(state).length).toEqual(2); }); it('should synchronize state to store for unassigned cases', () => { const sharedCases = [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }]; - const action = new fromActions.SynchronizeStateToStoreUnassignedCases(sharedCases); + const action = new fromActions.SynchronizeStateToStoreCases(sharedCases); const state = fromReducer.shareCasesReducer(initialState, action); - expect(fromReducer.getShareUnassignedCases(state).length).toEqual(2); + expect(fromReducer.getShareCases(state).length).toEqual(2); }); it('should assign user to case success', () => { const sharedCases = [{ caseId: '1', caseTitle: 'James123' }, { caseId: '2', caseTitle: 'Steve321' }]; - const action = new fromActions.AssignUsersToAssignedCaseSuccess(sharedCases); + const action = new fromActions.AssignUsersToCaseSuccess(sharedCases); const state = fromReducer.shareCasesReducer(initialState, action); - expect(fromReducer.getShareAssignedCases(state).length).toEqual(2); + expect(fromReducer.getShareCases(state).length).toEqual(2); }); it('should reset state if share case completed', () => { - const action = new fromActions.ResetAssignedCaseSelection(); + const action = new fromActions.ResetCaseSelection(); const state = fromReducer.shareCasesReducer(initialState, action); - expect(fromReducer.getShareAssignedCases(state).length).toEqual(0); + expect(fromReducer.getShareCases(state).length).toEqual(0); }); it('should sort users', () => { diff --git a/src/cases/store/selectors/caa-cases.selector.spec.ts b/src/cases/store/selectors/caa-cases.selector.spec.ts index 6bdb53907..a67cbcf45 100644 --- a/src/cases/store/selectors/caa-cases.selector.spec.ts +++ b/src/cases/store/selectors/caa-cases.selector.spec.ts @@ -3,11 +3,9 @@ import { select, Store, StoreModule } from '@ngrx/store'; import { reducers } from '../reducers'; import { CaaCasesState, initialState } from '../reducers/caa-cases.reducer'; import { - getAllAssignedCases, - getAllAssignedCasesError, + getAllCases, + getAllCasesError, getAllCaseTypes, - getAllUnassignedCases, - getAllUnassignedCasesError, getSelectedCases } from './caa-cases.selector'; @@ -27,34 +25,34 @@ describe('CaaCases selectors', () => { it('should return all assigned cases', () => { let result; - store.pipe(select(getAllAssignedCases)).subscribe((value) => { + store.pipe(select(getAllCases)).subscribe((value) => { result = value; }); - expect(result).toEqual(initialState.assignedCases); + expect(result).toEqual(initialState.Cases); }); it('should return all assigned cases error', () => { let result; - store.pipe(select(getAllAssignedCasesError)).subscribe((value) => { + store.pipe(select(getAllCasesError)).subscribe((value) => { result = value; }); - expect(result).toEqual(initialState.assignedCasesLastError); + expect(result).toEqual(initialState.CasesLastError); }); it('should return all unassigned cases', () => { let result; - store.pipe(select(getAllUnassignedCases)).subscribe((value) => { + store.pipe(select(getAllCases)).subscribe((value) => { result = value; }); - expect(result).toEqual(initialState.unassignedCases); + expect(result).toEqual(initialState.Cases); }); it('should return all unassigned cases error', () => { let result; - store.pipe(select(getAllUnassignedCasesError)).subscribe((value) => { + store.pipe(select(getAllCasesError)).subscribe((value) => { result = value; }); - expect(result).toEqual(initialState.assignedCasesLastError); + expect(result).toEqual(initialState.CasesLastError); }); it('should return all case types', () => { diff --git a/src/cases/store/selectors/share-case.selectors.spec.ts b/src/cases/store/selectors/share-case.selectors.spec.ts index b11b194d7..985ed91fb 100644 --- a/src/cases/store/selectors/share-case.selectors.spec.ts +++ b/src/cases/store/selectors/share-case.selectors.spec.ts @@ -4,15 +4,17 @@ import { RouterTestingModule } from '@angular/router/testing'; import { select, Store, StoreModule } from '@ngrx/store'; import { OrganisationState } from '../../../organisation/store'; import { UserState } from '../../../users/store'; -import { CaaCasesComponent } from '../../containers'; +import { CasesComponent } from '../../containers'; import { CaaCasesService } from '../../services'; -import { CaaCasesState, getShareAssignedCaseListState, reducers } from '../index'; +import { CaaCasesState, getShareCaseListState, reducers } from '../index'; +import { ChangeDetectorRef } from '@angular/core'; describe('Share case selectors', () => { let store: Store; let organisationStore: Store; let userStore: Store; let caaCasesService: jasmine.SpyObj; + let cdr: ChangeDetectorRef; const router: any = {}; beforeEach(() => { @@ -40,12 +42,13 @@ describe('Share case selectors', () => { store = TestBed.inject(Store); organisationStore = TestBed.inject(Store); userStore = TestBed.inject(Store); + cdr = TestBed.inject(ChangeDetectorRef); spyOn(store, 'dispatch').and.callThrough(); }); describe('get share case state', () => { xit('should return search state', () => { - const caseListComponent = new CaaCasesComponent(store, organisationStore, userStore, router, caaCasesService); + const caseListComponent = new CasesComponent(store, organisationStore, userStore, router, caaCasesService, cdr); caseListComponent.selectedCases = [{ case_id: '1', case_fields: { @@ -57,9 +60,9 @@ describe('Share case selectors', () => { solsSolicitorAppReference: 'Steve321' } }]; - caseListComponent.shareAssignedCaseSubmit(); + //caseListComponent.shareAssignedCaseSubmit(); let result = []; - store.pipe(select(getShareAssignedCaseListState)).subscribe((value) => { + store.pipe(select(getShareCaseListState)).subscribe((value) => { result = value; }); expect(result.length).toEqual(2); diff --git a/src/cases/util/caa-cases.util.spec.ts b/src/cases/util/caa-cases.util.spec.ts index bbd348a21..f5717d272 100644 --- a/src/cases/util/caa-cases.util.spec.ts +++ b/src/cases/util/caa-cases.util.spec.ts @@ -1,6 +1,7 @@ import { FormControl } from '@angular/forms'; import { User } from '@hmcts/rpx-xui-common-lib'; import { CaaCasesUtil } from './caa-cases.util'; +import { CaseTypesResultsResponse } from '../models/caa-cases.model'; describe('CaaCasesUtil', () => { let control: FormControl; @@ -31,7 +32,7 @@ describe('CaaCasesUtil', () => { case_type_id: 'FT_ComplexOrganisation' } ] - }; + } as CaseTypesResultsResponse; const results = CaaCasesUtil.getCaaNavItems(response); expect(results.length).toEqual(4); expect(results[0].text).toEqual('FT_MasterCaseType'); diff --git a/yarn.lock b/yarn.lock index fd7d21e29..f3d9f7a5e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2922,18 +2922,11 @@ __metadata: languageName: node linkType: hard -"@hmcts/rpx-xui-common-lib@npm:2.0.29": - version: 2.0.29 - resolution: "@hmcts/rpx-xui-common-lib@npm:2.0.29" - dependencies: - tslib: ^2.0.0 - peerDependencies: - launchdarkly-js-client-sdk: ^3.3.0 - ngx-pagination: ^3.2.1 - rpx-xui-translation: ^0.1.1 - checksum: c6fa9dddbbcf714f92c44a1240ac50445ef5695255a8f66d4724894c7bf2426b834574f34acc8e13e38a5b6b936d7b15edf4bc1083d4fd9314f8e8863281dc4b +"@hmcts/rpx-xui-common-lib@link:../rpx-xui-common-lib/dist/exui-common-lib::locator=rpx-xui-manage-organisations%40workspace%3A.": + version: 0.0.0-use.local + resolution: "@hmcts/rpx-xui-common-lib@link:../rpx-xui-common-lib/dist/exui-common-lib::locator=rpx-xui-manage-organisations%40workspace%3A." languageName: node - linkType: hard + linkType: soft "@hmcts/rpx-xui-node-lib@npm:2.29.1": version: 2.29.1 @@ -19750,7 +19743,7 @@ __metadata: "@hmcts/media-viewer": 4.0.4 "@hmcts/nodejs-healthcheck": 1.7.0 "@hmcts/properties-volume": 0.0.13 - "@hmcts/rpx-xui-common-lib": 2.0.29 + "@hmcts/rpx-xui-common-lib": "link:../rpx-xui-common-lib/dist/exui-common-lib" "@hmcts/rpx-xui-node-lib": 2.29.1 "@ng-idle/core": ^14.0.0 "@ng-idle/keepalive": ^14.0.0 From 6ebdd42d9738c9fb2d659304f3ac4fd0846947be Mon Sep 17 00:00:00 2001 From: Josh Date: Wed, 4 Jun 2025 10:26:15 +0100 Subject: [PATCH 15/44] fix tests --- package.json | 2 +- .../hmcts-global-footer.component.html | 2 +- src/app/containers/app/app.component.html | 2 +- .../cases-filter.component.spec.ts | 24 ++++++++++++++-- .../cases-results-table.component.spec.ts | 24 ++++++++++++++-- .../case-share-complete.component.html | 2 +- .../case-share-complete.component.spec.ts | 2 +- .../containers/cases/cases.component.spec.ts | 26 +++++++++++++++-- .../store/effects/share-case.effects.spec.ts | 2 +- .../cases-filter.component.spec.ts | 26 +++++++++++++++-- .../cases-results-table.component.spec.ts | 28 ++++++++++++++++--- .../case-share-complete.component.html | 2 +- .../case-share-complete.component.spec.ts | 2 +- .../case-share-confirm.component.spec.ts | 4 +-- .../containers/cases/cases.component.spec.ts | 24 ++++++++++++++-- .../store/effects/share-case.effects.spec.ts | 2 +- .../account-overview.component.html | 2 +- .../containers/profile/profile.component.html | 4 +-- .../invite-user-success.component.html | 2 +- .../containers/users/users.component.html | 2 +- yarn.lock | 12 ++++---- 21 files changed, 158 insertions(+), 38 deletions(-) diff --git a/package.json b/package.json index f633a3d62..4285bcd0c 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "@hmcts/media-viewer": "4.1.0", "@hmcts/nodejs-healthcheck": "1.7.0", "@hmcts/properties-volume": "0.0.13", - "@hmcts/rpx-xui-common-lib": "2.1.0", + "@hmcts/rpx-xui-common-lib": "2.1.0-uc-rc1", "@hmcts/rpx-xui-node-lib": "2.30.6", "@ng-idle/core": "^14.0.0", "@ng-idle/keepalive": "^14.0.0", diff --git a/src/app/components/hmcts-global-footer/hmcts-global-footer.component.html b/src/app/components/hmcts-global-footer/hmcts-global-footer.component.html index 0bf4cbbae..d0ae3b141 100644 --- a/src/app/components/hmcts-global-footer/hmcts-global-footer.component.html +++ b/src/app/components/hmcts-global-footer/hmcts-global-footer.component.html @@ -1,5 +1,5 @@