feat: add waiting-list off-duty and return quick buttons
This commit is contained in:
1 parent
848e12e2d6
commit
7c5384621a
13 files changed
+450
-9
No files matched your search
@@ -161,6 +161,18 @@
|
||||
<ion-label>{{ umpire|fullname }}</ion-label>
|
||||
</div>
|
||||
<ion-label>{{ umpire|fullname }}</ion-label>
|
||||
@if (waitingQuickButtonsEnabled()) {
|
||||
<ion-button
|
||||
class="waiting-duty-button"
|
||||
size="small"
|
||||
fill="solid"
|
||||
[attr.aria-label]="('moveOffDuty' | translate) + ': ' + (umpire | fullname)"
|
||||
[attr.title]="'moveOffDuty' | translate"
|
||||
(pointerdown)="$event.stopPropagation()"
|
||||
(click)="$event.stopPropagation(); changeWaitingDuty(umpire, true)">
|
||||
<ion-icon slot="icon-only" name="exit-outline"></ion-icon>
|
||||
</ion-button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ion-list>
|
||||
@@ -179,6 +191,18 @@
|
||||
<ion-label>{{ umpire|fullname }}</ion-label>
|
||||
</div>
|
||||
<ion-label>{{ umpire|fullname }}</ion-label>
|
||||
@if (waitingQuickButtonsEnabled()) {
|
||||
<ion-button
|
||||
class="waiting-duty-button"
|
||||
size="small"
|
||||
fill="solid"
|
||||
[attr.aria-label]="('moveOffDuty' | translate) + ': ' + (umpire | fullname)"
|
||||
[attr.title]="'moveOffDuty' | translate"
|
||||
(pointerdown)="$event.stopPropagation()"
|
||||
(click)="$event.stopPropagation(); changeWaitingDuty(umpire, true)">
|
||||
<ion-icon slot="icon-only" name="exit-outline"></ion-icon>
|
||||
</ion-button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ion-list>
|
||||
@@ -198,6 +222,18 @@
|
||||
<ion-label>{{ umpire|fullname }}</ion-label>
|
||||
</div>
|
||||
<ion-label>{{ umpire|fullname }}</ion-label>
|
||||
@if (waitingQuickButtonsEnabled() && umpire.returnToQueue) {
|
||||
<ion-button
|
||||
class="waiting-duty-button"
|
||||
size="small"
|
||||
fill="solid"
|
||||
[attr.aria-label]="('returnToQueue' | translate) + ': ' + (umpire | fullname)"
|
||||
[attr.title]="'returnToQueue' | translate"
|
||||
(pointerdown)="$event.stopPropagation()"
|
||||
(click)="$event.stopPropagation(); changeWaitingDuty(umpire, false)">
|
||||
<ion-icon slot="icon-only" name="enter-outline"></ion-icon>
|
||||
</ion-button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ion-list>
|
||||
|
||||
@@ -38,3 +38,18 @@
|
||||
background-color: var(--ion-color-step-150, #d9d9d9);
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-lists .custom-ion-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
ion-label {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.waiting-duty-button {
|
||||
flex-shrink: 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { CdkDragDrop } from '@angular/cdk/drag-drop';
|
||||
import { signal } from '@angular/core';
|
||||
import { AlertController } from '@ionic/angular';
|
||||
import { CourtServiceJudge, CourtUmpire, Settings, Umpire } from 'db';
|
||||
import { WaitingDutyService } from '../services/waiting-duty.service';
|
||||
import { Tab1Page } from './tab1.page';
|
||||
import { SettingsService } from '../services/settings-service';
|
||||
import { UmpireService } from '../services/umpire.service';
|
||||
@@ -34,6 +36,7 @@ describe('Tab1Page court rotation', () => {
|
||||
let waitingJudge: jasmine.SpyObj<WaitingServiceJudgesService>;
|
||||
let umpireCourt: jasmine.SpyObj<CourtUmpireService>;
|
||||
let judgeCourt: jasmine.SpyObj<CourtServiceJudgeService>;
|
||||
let duty: jasmine.SpyObj<WaitingDutyService>;
|
||||
let alerts: jasmine.SpyObj<AlertController>;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -72,6 +75,9 @@ describe('Tab1Page court rotation', () => {
|
||||
['save', 'removeByUmpireId'],
|
||||
{ umpires: courtJudges }
|
||||
);
|
||||
duty = jasmine.createSpyObj('WaitingDutyService', ['move', 'moveToRest']);
|
||||
duty.move.and.resolveTo();
|
||||
duty.moveToRest.and.resolveTo();
|
||||
alerts = jasmine.createSpyObj('AlertController', ['create']);
|
||||
waitingUmpire.getCurrentUmpire.and.resolveTo(official(1));
|
||||
waitingJudge.getCurrentUmpire.and.resolveTo(official(2));
|
||||
@@ -85,6 +91,7 @@ describe('Tab1Page court rotation', () => {
|
||||
judgeCourt.removeByUmpireId.and.resolveTo();
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{ provide: WaitingDutyService, useValue: duty },
|
||||
{ provide: SettingsService, useValue: { settings } },
|
||||
{ provide: UmpireService, useValue: { umpires } },
|
||||
{ provide: WaitingUmpiresService, useValue: waitingUmpire },
|
||||
@@ -98,6 +105,74 @@ describe('Tab1Page court rotation', () => {
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
for (const source of [
|
||||
'list-waiting-umpires',
|
||||
'list-waiting-service-judges'
|
||||
]) {
|
||||
it(`saves the waiting origin when dropped from ${source} to rest`, async () => {
|
||||
await component.dropToRest({
|
||||
previousContainer: { id: source },
|
||||
container: { id: 'list-on-rest' },
|
||||
item: { data: official(1) }
|
||||
} as CdkDragDrop<Umpire[]>);
|
||||
expect(duty.moveToRest).toHaveBeenCalledOnceWith(1);
|
||||
expect(waitingUmpire.removeByUmpireId).not.toHaveBeenCalled();
|
||||
expect(waitingJudge.removeByUmpireId).not.toHaveBeenCalled();
|
||||
});
|
||||
}
|
||||
|
||||
it('moves directly when quick-button alerts are disabled', async () => {
|
||||
settings.update((value) => ({ ...value, waitingListQuickButtons: true }));
|
||||
await component.changeWaitingDuty(official(1), true);
|
||||
expect(duty.move).toHaveBeenCalledOnceWith(1, 'rest');
|
||||
expect(alerts.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('ignores return actions without a saved origin', async () => {
|
||||
settings.update((value) => ({
|
||||
...value,
|
||||
waitingListQuickButtons: true,
|
||||
showAlert: true
|
||||
}));
|
||||
await component.changeWaitingDuty(official(5), false);
|
||||
expect(duty.move).not.toHaveBeenCalled();
|
||||
expect(alerts.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('confirms returning to the saved service-judge queue', async () => {
|
||||
settings.update((value) => ({
|
||||
...value,
|
||||
waitingListQuickButtons: true,
|
||||
showAlert: true
|
||||
}));
|
||||
alerts.create.and.resolveTo({
|
||||
present: async () => undefined
|
||||
} as unknown as HTMLIonAlertElement);
|
||||
await component.changeWaitingDuty(
|
||||
{ ...official(5), returnToQueue: 'serviceJudge' },
|
||||
false
|
||||
);
|
||||
expect(duty.move).not.toHaveBeenCalled();
|
||||
const buttons = alerts.create.calls.mostRecent().args[0]?.buttons;
|
||||
const confirm = buttons?.find(
|
||||
(button) => typeof button !== 'string' && button.role === 'confirm'
|
||||
);
|
||||
if (typeof confirm === 'object' && typeof confirm.handler === 'function')
|
||||
confirm.handler(undefined);
|
||||
expect(duty.move).toHaveBeenCalledOnceWith(5, 'serviceJudge');
|
||||
});
|
||||
|
||||
it('hides and ignores quick actions when service judges are disabled', async () => {
|
||||
settings.update((value) => ({
|
||||
...value,
|
||||
waitingListQuickButtons: true,
|
||||
withServiceJudge: false
|
||||
}));
|
||||
expect(component.waitingQuickButtonsEnabled()).toBeFalse();
|
||||
await component.changeWaitingDuty(official(1), true);
|
||||
expect(duty.move).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('waits for confirmation before assigning officials', async () => {
|
||||
settings.update((value) => ({ ...value, showAlert: true }));
|
||||
const present = jasmine.createSpy('present').and.resolveTo();
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
import { SettingsService } from '../services/settings-service';
|
||||
import { UmpireService } from '../services/umpire.service';
|
||||
import { Umpire } from 'db';
|
||||
import { WaitingDutyService } from '../services/waiting-duty.service';
|
||||
import { WaitingUmpiresService } from '../services/waiting-umpires.service';
|
||||
import { WaitingServiceJudgesService } from '../services/waiting-service-judges.service';
|
||||
import { FullnamePipe } from '../fullname-pipe';
|
||||
@@ -71,6 +72,8 @@ export class Tab1Page {
|
||||
addIcons({ exitOutline, enterOutline });
|
||||
}
|
||||
|
||||
readonly waitingDutyService = inject(WaitingDutyService);
|
||||
|
||||
readonly settingsService = inject(SettingsService);
|
||||
readonly umpireService = inject(UmpireService);
|
||||
readonly waitingUmpireService = inject(WaitingUmpiresService);
|
||||
@@ -285,12 +288,53 @@ export class Tab1Page {
|
||||
return this.settings()?.withServiceJudge ?? false;
|
||||
});
|
||||
|
||||
dropToRest(event: CdkDragDrop<Umpire[]>) {
|
||||
readonly waitingQuickButtonsEnabled = computed(
|
||||
() =>
|
||||
this.serviceJudgeEnabled() && !!this.settings()?.waitingListQuickButtons
|
||||
);
|
||||
|
||||
async changeWaitingDuty(umpire: Umpire, toRest: boolean): Promise<void> {
|
||||
if (!this.waitingQuickButtonsEnabled()) return;
|
||||
const destination = toRest ? 'rest' : umpire.returnToQueue;
|
||||
if (!destination) return;
|
||||
const move = () => this.waitingDutyService.move(umpire.id, destination);
|
||||
if (!this.settings()?.showAlert) {
|
||||
await move();
|
||||
return;
|
||||
}
|
||||
const alert = await this.alertController.create({
|
||||
header: this.translationService.translate('confirmation'),
|
||||
subHeader: this.translationService.translate(
|
||||
toRest ? 'confirmMoveOffDuty' : 'confirmReturnToQueue'
|
||||
),
|
||||
message: this.fullnamePipe.transform(umpire),
|
||||
buttons: [
|
||||
{ text: this.translationService.translate('no'), role: 'cancel' },
|
||||
{
|
||||
text: this.translationService.translate('yes'),
|
||||
role: 'confirm',
|
||||
handler: () => {
|
||||
void move();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
await alert.present();
|
||||
}
|
||||
|
||||
async dropToRest(event: CdkDragDrop<Umpire[]>): Promise<void> {
|
||||
if (event.previousContainer === event.container) {
|
||||
return;
|
||||
} else {
|
||||
const comingFrom = event.previousContainer.id;
|
||||
const umpireToMove = event.item.data;
|
||||
if (
|
||||
comingFrom === 'list-waiting-umpires' ||
|
||||
comingFrom === 'list-waiting-service-judges'
|
||||
) {
|
||||
await this.waitingDutyService.moveToRest(umpireToMove.id);
|
||||
return;
|
||||
}
|
||||
this.removeFromOriginalPlace(umpireToMove, comingFrom);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user