Skip to content

Commit dfcc26c

Browse files
Nils1729lukasrad02
andauthored
Feature/967 create default hospital (#981)
* Add new catch-all hospital * Hide delete button for catch-all hospital * Update changelog * Update test scenarios * Improve cypress tests * Apply suggestions from code review Co-authored-by: Lukas Radermacher <[email protected]> * Show badge for nonremovable hospital * lint --------- Co-authored-by: Lukas Radermacher <[email protected]>
1 parent a58ceac commit dfcc26c

File tree

11 files changed

+84
-13
lines changed

11 files changed

+84
-13
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project does **not** adhere to [Semantic Versioning](https://semver.org
1414
- Selected material, personnel and vehicles are now highlighted on the map.
1515
- When material or personnel is selected, the corresponding vehicle is highlighted as well.
1616
- When a vehicle is selected, the corresponding material and personnel are highlighted as well.
17+
- A generic catch-all hospital is present in every exercise and cannot be deleted.
1718
- The reports behavior can generate reports on the counts of transferred patients per triage category.
1819
- The reports behavior can generate event-based reports when the last patient of a triage category has been transferred to a hospital.
1920

frontend/cypress/e2e/exercise.cy.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -341,8 +341,12 @@ describe('A trainer on the exercise page', () => {
341341

342342
cy.log('add a hospital').get('[data-cy="hospitalAddButton"]').click();
343343
cy.get('@trainerSocketPerformedActions')
344-
.lastElement()
344+
.firstElement()
345345
.should('have.property', 'type', '[Hospital] Add hospital');
346+
cy.get('@trainerSocketPerformedActions')
347+
.firstElement()
348+
.its('hospital')
349+
.as('createdHospital');
346350

347351
cy.getState()
348352
.its('exerciseState')
@@ -351,7 +355,7 @@ describe('A trainer on the exercise page', () => {
351355

352356
cy.log('update a hospitals transport time')
353357
.get('[data-cy="hospitalUpdateTransportTimeInput"]')
354-
.first()
358+
.last()
355359
.clear()
356360
.type('30');
357361

@@ -367,7 +371,7 @@ describe('A trainer on the exercise page', () => {
367371
.its('exerciseState')
368372
.its('hospitals')
369373
.itsValues()
370-
.firstElement()
374+
.lastElement()
371375
.should('have.property', 'transportDuration', 1800000);
372376

373377
cy.get('[data-cy=hospitalClosePopupButton]').click({ force: true });
@@ -377,7 +381,7 @@ describe('A trainer on the exercise page', () => {
377381
cy.get('[data-cy=transferPointPopupHospitalNav]').click();
378382
cy.get('[data-cy=transferPointPopupAddHospitalButton]').click();
379383
cy.get('[data-cy=transferPointPopupAddHospitalDropdownButton]')
380-
.first()
384+
.last()
381385
.click({ force: true });
382386

383387
cy.get('@trainerSocketPerformedActions')
@@ -392,7 +396,7 @@ describe('A trainer on the exercise page', () => {
392396
.its('exerciseState')
393397
.its('transferPoints')
394398
.itsValues()
395-
.firstElement()
399+
.lastElement()
396400
.its('reachableHospitals')
397401
.should('not.be.empty');
398402

@@ -416,7 +420,7 @@ describe('A trainer on the exercise page', () => {
416420
.its('exerciseState')
417421
.its('hospitals')
418422
.itsValues()
419-
.firstElement()
423+
.lastElement()
420424
.its('patientIds')
421425
.should('not.be.empty');
422426

@@ -437,7 +441,7 @@ describe('A trainer on the exercise page', () => {
437441
.its('exerciseState')
438442
.its('transferPoints')
439443
.itsValues()
440-
.firstElement()
444+
.lastElement()
441445
.its('reachableHospitals')
442446
.should('be.empty');
443447

@@ -451,7 +455,12 @@ describe('A trainer on the exercise page', () => {
451455
.lastElement()
452456
.should('have.property', 'type', '[Hospital] Remove hospital');
453457

454-
cy.getState().its('exerciseState').its('hospitals').should('be.empty');
458+
cy.get('@createdHospital').then((hospital: any) => {
459+
cy.getState()
460+
.its('exerciseState')
461+
.its('hospitals')
462+
.should('not.have.keys', hospital.id);
463+
});
455464
});
456465

457466
it('can manage transfer points (within simulated regions) and transfer vehicles', () => {

frontend/src/app/pages/exercises/exercise/shared/hospital-editor/hospital-editor-modal/hospital-editor-modal.component.html

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,21 @@ <h4 class="modal-title">
6969
/>
7070
<span class="ps-2 pe-2" title="Minuten">min</span>
7171
</td>
72-
<td>
72+
<td class="align-middle">
7373
<button
7474
(click)="removeHospital(hospital.id)"
7575
class="btn btn-outline-danger float-end"
7676
data-cy="hospitalDeleteButton"
77+
*ngIf="
78+
hospital.id !== catchAllHospitalId;
79+
else catchAllHospital
80+
"
7781
>
7882
<i class="bi bi-trash"></i> Löschen
7983
</button>
84+
<ng-template #catchAllHospital>
85+
<span class="badge text-bg-secondary">Standard</span>
86+
</ng-template>
8087
</td>
8188
</tr>
8289
</tbody>

frontend/src/app/pages/exercises/exercise/shared/hospital-editor/hospital-editor-modal/hospital-editor-modal.component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Component } from '@angular/core';
22
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
33
import { Store } from '@ngrx/store';
44
import type { UUID } from 'digital-fuesim-manv-shared';
5-
import { Hospital } from 'digital-fuesim-manv-shared';
5+
import { Hospital, catchAllHospitalId } from 'digital-fuesim-manv-shared';
66
import { ExerciseService } from 'src/app/core/exercise.service';
77
import type { AppState } from 'src/app/state/app.state';
88
import { selectHospitals } from 'src/app/state/application/selectors/exercise.selectors';
@@ -14,6 +14,7 @@ import { selectHospitals } from 'src/app/state/application/selectors/exercise.se
1414
})
1515
export class HospitalEditorModalComponent {
1616
public hospitals$ = this.store.select(selectHospitals);
17+
public catchAllHospitalId = catchAllHospitalId;
1718

1819
constructor(
1920
private readonly store: Store<AppState>,
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { Hospital } from '../../models';
2+
import type { UUID } from '../../utils';
3+
import { cloneDeepMutable } from '../../utils';
4+
5+
export const catchAllHospitalId: UUID = '00000000-0000-4000-8000-000000000000';
6+
7+
const catchAllHospital: Hospital = {
8+
type: 'hospital',
9+
id: catchAllHospitalId,
10+
name: 'Beliebiges Krankenhaus',
11+
transportDuration: 60 * 60 * 1000,
12+
patientIds: {},
13+
};
14+
15+
export function createCatchAllHospital() {
16+
return cloneDeepMutable(catchAllHospital);
17+
}

shared/src/data/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ export * from './default-state/map-images-templates';
22
export * from './default-state/patient-templates';
33
export * from './default-state/vehicle-templates';
44
export * from './default-state/tile-map-properties';
5+
export * from './default-state/catch-all-hospital';
56
export * from './dummy-objects/patient';
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { Migration } from './migration-functions';
2+
3+
export const addCatchAllHospital34: Migration = {
4+
action: null,
5+
state: (state) => {
6+
const typedState = state as {
7+
hospitals: {
8+
[key: string]: any;
9+
};
10+
};
11+
const catchAllHospitalId = '00000000-0000-4000-8000-000000000000';
12+
typedState.hospitals[catchAllHospitalId] = {
13+
type: 'hospital',
14+
id: catchAllHospitalId,
15+
name: 'Beliebiges Krankenhaus',
16+
transportDuration: 60 * 60 * 1000,
17+
patientIds: {},
18+
};
19+
},
20+
};

shared/src/state-migrations/migration-functions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { reportTreatmentStatusChanges30 } from './30-report-treatment-status-cha
2323
import { improveLoadVehicleActivity31 } from './31-improve-load-vehicle-activity';
2424
import { removeIdFromEvents32 } from './32-remove-id-from-events';
2525
import { reportTransferCategoryCompleted33 } from './33-report-transfer-category-completed';
26+
import { addCatchAllHospital34 } from './34-add-catch-all-hospital';
2627
import { removeSetParticipantIdAction4 } from './4-remove-set-participant-id-action';
2728
import { removeStatistics5 } from './5-remove-statistics';
2829
import { removeStateHistory6 } from './6-remove-state-history';
@@ -89,4 +90,5 @@ export const migrations: {
8990
31: improveLoadVehicleActivity31,
9091
32: removeIdFromEvents32,
9192
33: reportTransferCategoryCompleted33,
93+
34: addCatchAllHospital34,
9294
};

shared/src/state.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ import type { SpatialElementPlural } from './store/action-reducers/utils/spatial
5151
import type { UUID } from './utils';
5252
import { uuid, uuidValidationOptions } from './utils';
5353
import { IsIdMap, IsLiteralUnion, IsMultiTypedIdMap } from './utils/validators';
54+
import {
55+
createCatchAllHospital,
56+
catchAllHospitalId,
57+
} from './data/default-state/catch-all-hospital';
5458

5559
export class ExerciseState {
5660
@IsUUID(4, uuidValidationOptions)
@@ -92,7 +96,9 @@ export class ExerciseState {
9296
public readonly transferPoints: { readonly [key: UUID]: TransferPoint } =
9397
{};
9498
@IsIdMap(Hospital)
95-
public readonly hospitals: { readonly [key: UUID]: Hospital } = {};
99+
public readonly hospitals: { readonly [key: UUID]: Hospital } = {
100+
[catchAllHospitalId]: createCatchAllHospital(),
101+
};
96102
@IsIdMap(HospitalPatient, (hospitalPatient) => hospitalPatient.patientId)
97103
public readonly hospitalPatients: {
98104
readonly [key: UUID]: HospitalPatient;
@@ -160,5 +166,5 @@ export class ExerciseState {
160166
*
161167
* This number MUST be increased every time a change to any object (that is part of the state or the state itself) is made in a way that there may be states valid before that are no longer valid.
162168
*/
163-
static readonly currentStateVersion = 33;
169+
static readonly currentStateVersion = 34;
164170
}

shared/src/store/action-reducers/hospital.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { cloneDeepMutable, UUID, uuidValidationOptions } from '../../utils';
1212
import { IsValue } from '../../utils/validators';
1313
import type { Action, ActionReducer } from '../action-reducer';
1414
import { ExpectedReducerError } from '../reducer-error';
15+
import { catchAllHospitalId } from '../../data/default-state/catch-all-hospital';
1516
import { isCompletelyLoaded } from './utils/completely-load-vehicle';
1617
import { getElement } from './utils/get-element';
1718
import { deleteVehicle } from './vehicle';
@@ -96,6 +97,12 @@ export namespace HospitalActionReducers {
9697
export const removeHospital: ActionReducer<RemoveHospitalAction> = {
9798
action: RemoveHospitalAction,
9899
reducer: (draftState, { hospitalId }) => {
100+
if (hospitalId === catchAllHospitalId) {
101+
throw new ExpectedReducerError(
102+
'Dieses Krankenhaus darf aus technischen Gründen nicht gelöscht werden.'
103+
);
104+
}
105+
99106
const hospital = getElement(draftState, 'hospital', hospitalId);
100107
// TODO: maybe make a hospital undeletable (if at least one patient is in it)
101108
for (const patientId of Object.keys(hospital.patientIds)) {

0 commit comments

Comments
 (0)