Skip to content

Commit

Permalink
feat(attendance): allow reviewing/editing of an event after roll call (
Browse files Browse the repository at this point in the history
…#1261)

fixes: #953 #1258

Co-authored-by: Simon <therealslimv@yahoo.de>
Co-authored-by: Simon <33730997+TheSlimvReal@users.noreply.github.com>
  • Loading branch information
3 people authored May 12, 2022
1 parent 2b48bef commit 5bb93f8
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 74 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<!-- Individual Student's Page -->
<div *ngIf="entries?.length > 0">
<div *ngIf="children?.length > 0">
<div class="attendance-progress-bar">
<mat-progress-bar
class="filler"
mode="determinate"
[value]="(currentIndex / entries.length) * 100"
[value]="(currentIndex / children.length) * 100"
></mat-progress-bar>
<div class="progress-label" [style.visibility]="currentIndex < entries.length ? 'visible' : 'hidden'">
{{ currentIndex+1 }} / {{ entries.length }}
<div class="progress-label" [style.visibility]="currentIndex < children.length ? 'visible' : 'hidden'">
{{ currentIndex+1 }} / {{ children.length }}
</div>

<div class="navigation-bar">
Expand All @@ -28,7 +28,7 @@
</div>

<div
*ngIf="!isFinished"
*ngIf="!isFinished && currentAttendance"
fxLayout="column"
fxLayoutAlign="space-between stretch"
class="options-wrapper"
Expand All @@ -38,13 +38,13 @@
*ngFor="let option of availableStatus"
(click)="markAttendance(option)"
[ngClass]="
currentStatus.id === option.id
currentAttendance.status.id === option.id
? option.style
: ''
"
>
<fa-icon
*ngIf="currentStatus.id === option.id"
*ngIf="currentAttendance.status.id === option.id"
icon="check"
></fa-icon>
{{ option.label }}
Expand All @@ -53,20 +53,32 @@
</div>

<!-- Completion Page -->
<div *ngIf="isFinished" class="roll-call-complete" [@completeRollCall] (click)="finish()">
<fa-icon icon="check-circle" class="roll-call-complete-icon"></fa-icon>
<div
i18n="
Attendance completed|shows when the user has registered the attendance of
all children
"
>
Attendance completed.
<div *ngIf="isFinished">
<div class="roll-call-complete" [@completeRollCall]>
<fa-icon icon="check-circle" class="roll-call-complete-icon"></fa-icon>
<div
i18n="
Attendance completed|shows when the user has registered the attendance of
all children
"
(click)="finish()"
>
Attendance completed.
</div>

<button
(click)="showDetails()"
class="finished-screen-button"
mat-stroked-button
i18n="Open details of recorded event for review"
>
Review Details
</button>
</div>

<button
(click)="finish()"
class="completed-button"
class="finished-screen-button full-width"
mat-stroked-button
i18n="Back to overview button after finishing a roll call"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@
justify-content: center;
}

.completed-button {
.finished-screen-button {
margin-top: 1em;
}

.full-width {
width: 100%;
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ describe("RollCallComponent", () => {
component.ngOnChanges(dummyChanges);
tick();

expect(component.entries.map((e) => e.child)).toEqual([participant1]);
expect(component.children).toEqual([participant1]);
expect(component.eventEntity.children).not.toContain(nonExistingChildId);
expect(mockLoggingService.warn).toHaveBeenCalled();
flush();
Expand Down Expand Up @@ -284,9 +284,7 @@ describe("RollCallComponent", () => {
});
tick();

expect(component.entries.map((e) => e.child)).toEqual(
expectedParticipantsOrder
);
expect(component.children).toEqual(expectedParticipantsOrder);
expect(component.eventEntity.children).toEqual(
expectedParticipantsOrder.map((p) => p.getId())
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { Child } from "../../../children/model/child";
import { LoggingService } from "../../../../core/logging/logging.service";
import { FormGroup } from "@angular/forms";
import { sortByAttribute } from "../../../../utils/utils";
import { NoteDetailsComponent } from "../../../notes/note-details/note-details.component";
import { FormDialogService } from "../../../../core/form-dialog/form-dialog.service";

/**
* Displays the participants of the given event one by one to mark attendance status.
Expand Down Expand Up @@ -58,40 +60,27 @@ export class RollCallComponent implements OnChanges {
@Output() exit = new EventEmitter();

/**
* private model; should only be set within this component
* @private
* The index, child and attendance that is currently being processed
*/
private _currentIndex: number;
currentIndex = 0;
currentChild: Child;
currentAttendance: EventAttendance;
/**
* whether any changes have been made to the model
*/
isDirty: boolean = false;
/**
* The index of the child that is currently being processed
*/
get currentIndex(): number {
return this._currentIndex;
}
get currentStatus(): AttendanceStatusType {
return this.entries[this.currentIndex].attendance.status;
}
set currentStatus(newStatus: AttendanceStatusType) {
this.entries[this.currentIndex].attendance.status = newStatus;
}
get currentChild(): Child {
return this.entries[this.currentIndex].child;
}

/** options available for selecting an attendance status */
availableStatus: AttendanceStatusType[];

entries: { child: Child; attendance: EventAttendance }[] = [];
children: Child[] = [];
form: FormGroup;
private transitionInProgress;

constructor(
private configService: ConfigService,
private entityMapper: EntityMapperService,
private formDialog: FormDialogService,
private loggingService: LoggingService
) {}

Expand All @@ -115,19 +104,19 @@ export class RollCallComponent implements OnChanges {
*/
private setInitialIndex() {
let index = 0;
for (const entry of this.entries) {
if (!this.eventEntity.getAttendance(entry.child.getId())?.status?.id) {
for (const entry of this.children) {
if (!this.eventEntity.getAttendance(entry.getId())?.status?.id) {
break;
}
index += 1;
}

// do not jump to end - if all participants are recorded, start with first instead
if (index >= this.entries.length) {
if (index >= this.children.length) {
index = 0;
}

this._currentIndex = index;
this.goToParticipantWithIndex(index);
}

private loadAttendanceStatusTypes() {
Expand All @@ -137,8 +126,7 @@ export class RollCallComponent implements OnChanges {
}

private async loadParticipants() {
this.entries = [];
this._currentIndex = 0;
this.children = [];
for (const childId of this.eventEntity.children) {
let child;
try {
Expand All @@ -153,10 +141,7 @@ export class RollCallComponent implements OnChanges {
this.eventEntity.removeChild(childId);
continue;
}
this.entries.push({
child: child,
attendance: this.eventEntity.getAttendance(childId),
});
this.children.push(child);
}
this.sortParticipants();
}
Expand All @@ -166,18 +151,16 @@ export class RollCallComponent implements OnChanges {
return;
}

this.entries.sort((a, b) =>
sortByAttribute<any>(this.sortParticipantsBy, "asc")(a.child, b.child)
);
this.children.sort(sortByAttribute<any>(this.sortParticipantsBy, "asc"));
// also sort the participants in the Note entity itself for display in details view later
this.eventEntity.children = this.entries.map((e) => e.child.getId());
this.eventEntity.children = this.children.map((e) => e.getId());
}

markAttendance(status: AttendanceStatusType) {
if (this.transitionInProgress) {
return;
}
this.currentStatus = status;
this.currentAttendance.status = status;
this.isDirty = true;

// automatically move to next participant after a short delay giving the user visual feedback on the selected status
Expand All @@ -188,10 +171,15 @@ export class RollCallComponent implements OnChanges {
}

goToParticipantWithIndex(newIndex: number) {
this._currentIndex = newIndex;
this.currentIndex = newIndex;

if (this.isFinished) {
this.complete.emit(this.eventEntity);
} else {
this.currentChild = this.children[this.currentIndex];
this.currentAttendance = this.eventEntity.getAttendance(
this.currentChild.getId()
);
}
}

Expand All @@ -208,14 +196,18 @@ export class RollCallComponent implements OnChanges {
}

get isLast(): boolean {
return this.currentIndex === this.entries.length - 1;
return this.currentIndex === this.children.length - 1;
}

get isFinished(): boolean {
return this.currentIndex >= this.entries.length;
return this.currentIndex >= this.children.length;
}

finish() {
this.exit.emit();
}

showDetails() {
this.formDialog.openDialog(NoteDetailsComponent, this.eventEntity);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,6 @@ export class NotesManagerComponent implements OnInit {
}

showDetails(entity: Note) {
this.formDialog.openDialog(NoteDetailsComponent, entity.copy());
this.formDialog.openDialog(NoteDetailsComponent, entity);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
</button>
<button
mat-stroked-button
(click)="cancel()"
(click)="onClose.emit()"
angulartics2On="click"
[angularticsCategory]="entity?.getType()"
angularticsAction="form_dialog_cancel"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import { MatDialogRef } from "@angular/material/dialog";
import { Subject } from "rxjs";
import { MockedTestingModule } from "../../../utils/mocked-testing.module";
import { MatSnackBarModule } from "@angular/material/snack-bar";
import { Child } from "../../../child-dev-project/children/model/child";

describe("FormDialogWrapperComponent", () => {
let component: FormDialogWrapperComponent;
let component: FormDialogWrapperComponent<Child>;
let fixture: ComponentFixture<FormDialogWrapperComponent>;

let saveEntitySpy: jasmine.Spy;
Expand All @@ -31,7 +32,7 @@ describe("FormDialogWrapperComponent", () => {

beforeEach(() => {
fixture = TestBed.createComponent(FormDialogWrapperComponent);
component = fixture.componentInstance;
component = fixture.componentInstance as FormDialogWrapperComponent<Child>;
component.contentForm = {
form: { dirty: false, statusChanges: new Subject() },
};
Expand All @@ -43,19 +44,19 @@ describe("FormDialogWrapperComponent", () => {
});

it("should reset entity on cancel", async () => {
const testEntity: any = { value: 1 };
const testEntity = Child.create("old name");
component.entity = testEntity;

testEntity.value = 2;
// @ts-ignore
expect(component.entity.value).toBe(2);
testEntity.name = "new name";
expect(component.entity.name).toBe("new name");

await component.cancel();
expect(testEntity.value).toBe(1);
expect(testEntity.name).toBe("old name");
});

it("should save without beforeSave hook", async () => {
const testEntity: any = { value: 1 };
const testEntity = new Child();

component.entity = testEntity;

await component.save();
Expand All @@ -64,7 +65,7 @@ describe("FormDialogWrapperComponent", () => {
});

it("should allow aborting save", async () => {
const testEntity: any = { value: 1 };
const testEntity = new Child();
component.entity = testEntity;
component.beforeSave = () => undefined; // abort save by returning undefined from the beforeSave transformation hook
spyOn(component, "beforeSave").and.callThrough();
Expand All @@ -76,9 +77,9 @@ describe("FormDialogWrapperComponent", () => {
});

it("should save entity as transformed by beforeSave", async () => {
const testEntity: any = { value: 1 };
const testEntity = Child.create("old name");
component.entity = testEntity;
const transformedEntity: any = { value: 99 };
const transformedEntity = Child.create("transformed name");
component.beforeSave = () => Promise.resolve(transformedEntity);
spyOn(component, "beforeSave").and.callThrough();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class FormDialogWrapperComponent<E extends Entity = Entity>
implements AfterViewInit {
/** entity to be edited */
@Input() set entity(value: E) {
this.originalEntity = Object.assign({}, value);
this.originalEntity = value.copy() as E;
this._entity = value;
}
get entity(): E {
Expand Down Expand Up @@ -112,7 +112,7 @@ export class FormDialogWrapperComponent<E extends Entity = Entity>
this.entity = transformedEntity;
}

await this.entityMapper.save<Entity>(this.entity);
await this.entityMapper.save(this.entity);
this.onClose.emit(this.entity);
}

Expand Down

0 comments on commit 5bb93f8

Please sign in to comment.