feat: add waiting-list off-duty and return quick buttons

This commit is contained in:
tothbt committed 2026-11-02 18:01:13 +01:00
1 parent 848e12e2d6
commit 7c5384621a
13 files changed
+446 -5

No files matched your search

+20
View File
@@ -1,5 +1,7 @@
import Dexie, { type EntityTable } from 'dexie'; import Dexie, { type EntityTable } from 'dexie';
export type WaitingRole = 'umpire' | 'serviceJudge';
export interface Umpire { export interface Umpire {
id: number; id: number;
firstName: string; firstName: string;
@@ -7,6 +9,7 @@ export interface Umpire {
country: string; country: string;
gender: string; gender: string;
courtNo?: number; courtNo?: number;
returnToQueue?: WaitingRole;
} }
export interface CourtUmpire { export interface CourtUmpire {
@@ -38,6 +41,7 @@ export interface Settings {
withServiceJudge: boolean; withServiceJudge: boolean;
numberOfCourts: number; numberOfCourts: number;
showAlert: boolean; showAlert: boolean;
waitingListQuickButtons?: boolean;
} }
const db = new Dexie('CourtPilot') as Dexie & { const db = new Dexie('CourtPilot') as Dexie & {
@@ -58,4 +62,20 @@ db.version(1).stores({
waitingServiceJudges: '++id, order, serviceJudgeId' waitingServiceJudges: '++id, order, serviceJudgeId'
}); });
db.version(2)
.stores({
umpires: '++id, lastName',
courtUmpires: '++id, courtNo, umpireId',
courtServiceJudges: '++id, courtNo, umpireId',
settings: '++id',
waitingUmpires: '++id, order, umpireId',
waitingServiceJudges: '++id, order, serviceJudgeId'
})
.upgrade(async (transaction) => {
await transaction
.table('settings')
.toCollection()
.modify({ waitingListQuickButtons: false });
});
export { db }; export { db };
+17 -2
View File
@@ -22,7 +22,7 @@ flowchart TD
The root route lazy-loads the tab routes. The three primary routes are: The root route lazy-loads the tab routes. The three primary routes are:
| Route | Source | Responsibility | | Route | Source | Responsibility |
|---|---|---| | ---------------- | --------------- | ---------------------------------------------------------------------- |
| `/tabs/pilot` | `src/app/tab1/` | Waiting queues, court assignments, drag and drop, and rotation actions | | `/tabs/pilot` | `src/app/tab1/` | Waiting queues, court assignments, drag and drop, and rotation actions |
| `/tabs/umpires` | `src/app/tab2/` | Court-official roster management | | `/tabs/umpires` | `src/app/tab2/` | Court-official roster management |
| `/tabs/settings` | `src/app/tab3/` | Court count, theme, service-judge mode, alerts, and language | | `/tabs/settings` | `src/app/tab3/` | Court count, theme, service-judge mode, alerts, and language |
@@ -37,7 +37,7 @@ Persistence services use Dexie's `liveQuery()` and Angular's `toSignal()` to exp
## Persistence model ## Persistence model
`db.ts` declares a `CourtPilot` Dexie database at schema version 1. `db.ts` declares a `CourtPilot` Dexie database at schema version 2.
```mermaid ```mermaid
erDiagram erDiagram
@@ -85,6 +85,21 @@ component starts and toggles Ionic's `ion-palette-dark` class. Device theme chan
apply while the app is open when device-preferred is selected. This does not apply while the app is open when device-preferred is selected. This does not
change the Dexie schema or existing settings records. change the Dexie schema or existing settings records.
### Waiting-list quick buttons
The optional waiting-list quick buttons default to disabled and require service
judges. Disabling service judges disables this setting. Both waiting lists have
an off-duty action; off-duty officials with a saved origin have a return action. Quick-button alerts
apply to both actions. Dragging an official from either waiting list to Not on Duty
also saves the origin, even when quick buttons are disabled. Returning appends to the saved role queue.
Schema version 2 adds `waitingListQuickButtons` to settings and optional
`returnToQueue` metadata to officials. The upgrade explicitly sets the setting to
false for existing settings rows. Existing officials need no transformation;
officials without return metadata have no return action. Queue indexes and
existing records are preserved. Older application versions do not understand the
new metadata; downgrading an IndexedDB database is not supported.
### Schema-change policy ### Schema-change policy
Once data may exist in released clients, do not modify schema version 1 in place. Add a new `db.version(n)` declaration and define upgrade behavior when existing records need transformation. Once data may exist in released clients, do not modify schema version 1 in place. Add a new `db.version(n)` declaration and define upgrade behavior when existing records need transformation.
+16
View File
@@ -9,6 +9,14 @@ export const translations = {
themeLight: 'Light', themeLight: 'Light',
themeDark: 'Dark', themeDark: 'Dark',
themeSystem: 'Device-preferred', themeSystem: 'Device-preferred',
waitingListQuickButtons: 'Quick buttons for waiting lists',
waitingListQuickButtonsHelp:
'Move waiting officials off duty and return them to the end of their previous list. Only officials with a saved list have a return button. Requires Service Judge to be enabled.',
moveOffDuty: 'Move off duty',
returnToQueue: 'Return to waiting list',
confirmMoveOffDuty: 'Move this official to Not on Duty?',
confirmReturnToQueue:
'Return this official to the bottom of the waiting list?',
showAlerts: 'Show alerts on quick buttons', showAlerts: 'Show alerts on quick buttons',
serviceJudge: 'Service Judge', serviceJudge: 'Service Judge',
language: 'Language', language: 'Language',
@@ -62,6 +70,14 @@ export const translations = {
themeLight: 'Világos', themeLight: 'Világos',
themeDark: 'Sötét', themeDark: 'Sötét',
themeSystem: 'Eszközbeállítás szerint', themeSystem: 'Eszközbeállítás szerint',
waitingListQuickButtons: 'Gyors gombok a várólistákhoz',
waitingListQuickButtonsHelp:
'A várakozó tisztségviselőket pihenőre küldheted, majd visszaküldheted őket az előző várólistájuk végére. A visszaküldés gomb csak mentett várólista esetén jelenik meg. Az Adogatásbíró beállítás bekapcsolása szükséges.',
moveOffDuty: 'Pihenőre küldés',
returnToQueue: 'Vissza a várólistára',
confirmMoveOffDuty: 'Pihenőre küldöd ezt a tisztségviselőt?',
confirmReturnToQueue:
'Visszaküldöd ezt a tisztségviselőt a várólista végére?',
showAlerts: 'Figyelmeztetések megjelenítése a gyors gomboknál', showAlerts: 'Figyelmeztetések megjelenítése a gyors gomboknál',
serviceJudge: 'Adogatásbíró', serviceJudge: 'Adogatásbíró',
language: 'Nyelv', language: 'Nyelv',
+8 -2
View File
@@ -29,7 +29,12 @@ export class SettingsService {
return; return;
} }
await db.settings.update(1, changes); await db.settings.update(1, {
...changes,
...(!(changes.withServiceJudge ?? current.withServiceJudge)
? { waitingListQuickButtons: false }
: {})
});
} }
private async ensureSettings() { private async ensureSettings() {
@@ -40,7 +45,8 @@ export class SettingsService {
id: 1, id: 1,
numberOfCourts: 5, numberOfCourts: 5,
withServiceJudge: true, withServiceJudge: true,
showAlert: true showAlert: true,
waitingListQuickButtons: false
}); });
} }
} }
@@ -0,0 +1,95 @@
import { TestBed } from '@angular/core/testing';
import { db } from 'db';
import { WaitingDutyService } from './waiting-duty.service';
describe('WaitingDutyService', () => {
let service: WaitingDutyService;
beforeEach(async () => {
await Promise.all([
db.settings.clear(),
db.umpires.clear(),
db.waitingUmpires.clear(),
db.waitingServiceJudges.clear(),
db.courtUmpires.clear(),
db.courtServiceJudges.clear()
]);
await db.settings.put({
id: 1,
withServiceJudge: true,
waitingListQuickButtons: true,
showAlert: false,
numberOfCourts: 5
});
await db.umpires.put({
id: 1,
firstName: 'Alex',
lastName: 'Smith',
gender: 'M',
country: 'GB'
});
service = TestBed.inject(WaitingDutyService);
});
afterEach(async () => {
TestBed.resetTestingModule();
await Promise.all([
db.settings.clear(),
db.umpires.clear(),
db.waitingUmpires.clear(),
db.waitingServiceJudges.clear(),
db.courtUmpires.clear(),
db.courtServiceJudges.clear()
]);
});
for (const role of ['umpire', 'serviceJudge'] as const) {
it(`remembers the ${role} queue and returns at its bottom only once`, async () => {
if (role === 'umpire')
await db.waitingUmpires.add({ umpireId: 1, order: 1 });
else await db.waitingServiceJudges.add({ serviceJudgeId: 1, order: 1 });
await service.move(1, 'rest');
expect((await db.umpires.get(1))?.returnToQueue).toBe(role);
expect(
(await db.waitingUmpires.count()) +
(await db.waitingServiceJudges.count())
).toBe(0);
if (role === 'umpire')
await db.waitingUmpires.add({ umpireId: 2, order: 1 });
else await db.waitingServiceJudges.add({ serviceJudgeId: 2, order: 1 });
await Promise.all([service.move(1, 'umpire'), service.move(1, 'umpire')]);
const items =
role === 'umpire'
? await db.waitingUmpires.orderBy('order').toArray()
: await db.waitingServiceJudges.orderBy('order').toArray();
expect(items.map((item) => item.order)).toEqual([1, 2]);
expect((await db.umpires.get(1))?.returnToQueue).toBeUndefined();
});
}
for (const role of ['umpire', 'serviceJudge'] as const) {
it(`saves the ${role} origin on a drag move even with quick buttons disabled`, async () => {
await db.settings.update(1, { waitingListQuickButtons: false });
if (role === 'umpire')
await db.waitingUmpires.add({ umpireId: 1, order: 1 });
else await db.waitingServiceJudges.add({ serviceJudgeId: 1, order: 1 });
await service.moveToRest(1);
expect((await db.umpires.get(1))?.returnToQueue).toBe(role);
expect(await db.waitingUmpires.count()).toBe(0);
expect(await db.waitingServiceJudges.count()).toBe(0);
});
}
it('does not return officials without a saved origin', async () => {
await service.move(1, 'umpire');
expect(await db.waitingUmpires.count()).toBe(0);
expect(await db.waitingServiceJudges.count()).toBe(0);
});
it('ignores stale return actions for assigned officials', async () => {
await db.courtUmpires.add({ umpireId: 1, courtNo: 1 });
await service.move(1, 'umpire');
expect(await db.waitingUmpires.count()).toBe(0);
});
it('does nothing when service judges are disabled', async () => {
await db.settings.update(1, { withServiceJudge: false });
await service.move(1, 'umpire');
expect(await db.waitingUmpires.count()).toBe(0);
});
});
+81
View File
@@ -0,0 +1,81 @@
import { Injectable, inject } from '@angular/core';
import { db, WaitingRole } from 'db';
import { WaitingUmpiresService } from './waiting-umpires.service';
import { WaitingServiceJudgesService } from './waiting-service-judges.service';
@Injectable({ providedIn: 'root' })
export class WaitingDutyService {
private readonly umpires = inject(WaitingUmpiresService);
private readonly judges = inject(WaitingServiceJudgesService);
async moveToRest(officialId: number): Promise<void> {
await this.moveOfficial(officialId, 'rest', false);
}
async move(
officialId: number,
destination: WaitingRole | 'rest'
): Promise<void> {
await this.moveOfficial(officialId, destination, true);
}
private async moveOfficial(
officialId: number,
destination: WaitingRole | 'rest',
requireQuickButtons: boolean
): Promise<void> {
await db.transaction(
'rw',
[
db.settings,
db.umpires,
db.waitingUmpires,
db.waitingServiceJudges,
db.courtUmpires,
db.courtServiceJudges
],
async () => {
const settings = await db.settings.get(1);
const official = await db.umpires.get(officialId);
if (
!official ||
(requireQuickButtons &&
(!settings?.withServiceJudge || !settings.waitingListQuickButtons))
)
return;
const umpire = await db.waitingUmpires
.where('umpireId')
.equals(officialId)
.first();
const judge = await db.waitingServiceJudges
.where('serviceJudgeId')
.equals(officialId)
.first();
const onCourt =
(await db.courtUmpires
.where('umpireId')
.equals(officialId)
.count()) ||
(await db.courtServiceJudges
.where('umpireId')
.equals(officialId)
.count());
if (onCourt) return;
if (destination === 'rest') {
if (!umpire && !judge) return;
await db.umpires.update(officialId, {
returnToQueue: umpire ? 'umpire' : 'serviceJudge'
});
await this.umpires.removeByUmpireId(officialId);
await this.judges.removeByUmpireId(officialId);
} else {
if (umpire || judge || !official.returnToQueue) return;
const role = official.returnToQueue;
if (role === 'serviceJudge') await this.judges.add(officialId);
else await this.umpires.add(officialId);
await db.umpires.update(officialId, { returnToQueue: undefined });
}
}
);
}
}
+36
View File
@@ -161,6 +161,18 @@
<ion-label>{{ umpire|fullname }}</ion-label> <ion-label>{{ umpire|fullname }}</ion-label>
</div> </div>
<ion-label>{{ umpire|fullname }}</ion-label> <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> </div>
} }
</ion-list> </ion-list>
@@ -179,6 +191,18 @@
<ion-label>{{ umpire|fullname }}</ion-label> <ion-label>{{ umpire|fullname }}</ion-label>
</div> </div>
<ion-label>{{ umpire|fullname }}</ion-label> <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> </div>
} }
</ion-list> </ion-list>
@@ -198,6 +222,18 @@
<ion-label>{{ umpire|fullname }}</ion-label> <ion-label>{{ umpire|fullname }}</ion-label>
</div> </div>
<ion-label>{{ umpire|fullname }}</ion-label> <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> </div>
} }
</ion-list> </ion-list>
+15
View File
@@ -38,3 +38,18 @@
background-color: var(--ion-color-step-150, #d9d9d9); 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;
}
}
+75
View File
@@ -1,7 +1,9 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { signal } from '@angular/core'; import { signal } from '@angular/core';
import { AlertController } from '@ionic/angular'; import { AlertController } from '@ionic/angular';
import { CourtServiceJudge, CourtUmpire, Settings, Umpire } from 'db'; import { CourtServiceJudge, CourtUmpire, Settings, Umpire } from 'db';
import { WaitingDutyService } from '../services/waiting-duty.service';
import { Tab1Page } from './tab1.page'; import { Tab1Page } from './tab1.page';
import { SettingsService } from '../services/settings-service'; import { SettingsService } from '../services/settings-service';
import { UmpireService } from '../services/umpire.service'; import { UmpireService } from '../services/umpire.service';
@@ -34,6 +36,7 @@ describe('Tab1Page court rotation', () => {
let waitingJudge: jasmine.SpyObj<WaitingServiceJudgesService>; let waitingJudge: jasmine.SpyObj<WaitingServiceJudgesService>;
let umpireCourt: jasmine.SpyObj<CourtUmpireService>; let umpireCourt: jasmine.SpyObj<CourtUmpireService>;
let judgeCourt: jasmine.SpyObj<CourtServiceJudgeService>; let judgeCourt: jasmine.SpyObj<CourtServiceJudgeService>;
let duty: jasmine.SpyObj<WaitingDutyService>;
let alerts: jasmine.SpyObj<AlertController>; let alerts: jasmine.SpyObj<AlertController>;
beforeEach(() => { beforeEach(() => {
@@ -72,6 +75,9 @@ describe('Tab1Page court rotation', () => {
['save', 'removeByUmpireId'], ['save', 'removeByUmpireId'],
{ umpires: courtJudges } { umpires: courtJudges }
); );
duty = jasmine.createSpyObj('WaitingDutyService', ['move', 'moveToRest']);
duty.move.and.resolveTo();
duty.moveToRest.and.resolveTo();
alerts = jasmine.createSpyObj('AlertController', ['create']); alerts = jasmine.createSpyObj('AlertController', ['create']);
waitingUmpire.getCurrentUmpire.and.resolveTo(official(1)); waitingUmpire.getCurrentUmpire.and.resolveTo(official(1));
waitingJudge.getCurrentUmpire.and.resolveTo(official(2)); waitingJudge.getCurrentUmpire.and.resolveTo(official(2));
@@ -85,6 +91,7 @@ describe('Tab1Page court rotation', () => {
judgeCourt.removeByUmpireId.and.resolveTo(); judgeCourt.removeByUmpireId.and.resolveTo();
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [ providers: [
{ provide: WaitingDutyService, useValue: duty },
{ provide: SettingsService, useValue: { settings } }, { provide: SettingsService, useValue: { settings } },
{ provide: UmpireService, useValue: { umpires } }, { provide: UmpireService, useValue: { umpires } },
{ provide: WaitingUmpiresService, useValue: waitingUmpire }, { provide: WaitingUmpiresService, useValue: waitingUmpire },
@@ -98,6 +105,74 @@ describe('Tab1Page court rotation', () => {
component = fixture.componentInstance; 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 () => { it('waits for confirmation before assigning officials', async () => {
settings.update((value) => ({ ...value, showAlert: true })); settings.update((value) => ({ ...value, showAlert: true }));
const present = jasmine.createSpy('present').and.resolveTo(); const present = jasmine.createSpy('present').and.resolveTo();
+45 -1
View File
@@ -22,6 +22,7 @@ import {
import { SettingsService } from '../services/settings-service'; import { SettingsService } from '../services/settings-service';
import { UmpireService } from '../services/umpire.service'; import { UmpireService } from '../services/umpire.service';
import { Umpire } from 'db'; import { Umpire } from 'db';
import { WaitingDutyService } from '../services/waiting-duty.service';
import { WaitingUmpiresService } from '../services/waiting-umpires.service'; import { WaitingUmpiresService } from '../services/waiting-umpires.service';
import { WaitingServiceJudgesService } from '../services/waiting-service-judges.service'; import { WaitingServiceJudgesService } from '../services/waiting-service-judges.service';
import { FullnamePipe } from '../fullname-pipe'; import { FullnamePipe } from '../fullname-pipe';
@@ -71,6 +72,8 @@ export class Tab1Page {
addIcons({ exitOutline, enterOutline }); addIcons({ exitOutline, enterOutline });
} }
readonly waitingDutyService = inject(WaitingDutyService);
readonly settingsService = inject(SettingsService); readonly settingsService = inject(SettingsService);
readonly umpireService = inject(UmpireService); readonly umpireService = inject(UmpireService);
readonly waitingUmpireService = inject(WaitingUmpiresService); readonly waitingUmpireService = inject(WaitingUmpiresService);
@@ -285,12 +288,53 @@ export class Tab1Page {
return this.settings()?.withServiceJudge ?? false; 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) { if (event.previousContainer === event.container) {
return; return;
} else { } else {
const comingFrom = event.previousContainer.id; const comingFrom = event.previousContainer.id;
const umpireToMove = event.item.data; 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); this.removeFromOriginalPlace(umpireToMove, comingFrom);
} }
} }
+15
View File
@@ -35,6 +35,21 @@
</ion-toggle> </ion-toggle>
</ion-item> </ion-item>
<ion-item>
<div class="waiting-list-setting">
<ion-toggle
[checked]="currentSettings.waitingListQuickButtons ?? false"
[disabled]="!currentSettings.withServiceJudge"
aria-describedby="waiting-list-help"
(ionChange)="updateWaitingListQuickButtons($event)">
{{ 'waitingListQuickButtons' | translate }}
</ion-toggle>
<p id="waiting-list-help" class="setting-help">
{{ 'waitingListQuickButtonsHelp' | translate }}
</p>
</div>
</ion-item>
<ion-item> <ion-item>
<ion-toggle <ion-toggle
[checked]="currentSettings.showAlert" [checked]="currentSettings.showAlert"
+15
View File
@@ -0,0 +1,15 @@
.waiting-list-setting {
width: 100%;
padding-block: 12px;
ion-toggle {
width: 100%;
}
}
.setting-help {
margin: 8px 0 0;
color: var(--ion-color-medium-shade);
font-size: 0.875rem;
line-height: 1.4;
}
+8
View File
@@ -99,6 +99,14 @@ export class Tab3Page {
} }
} }
async updateWaitingListQuickButtons(
event: CustomEvent<{ checked: boolean }>
): Promise<void> {
await this.settingsService.update({
waitingListQuickButtons: event.detail.checked
});
}
async updateShowAlert(event: CustomEvent): Promise<void> { async updateShowAlert(event: CustomEvent): Promise<void> {
await this.settingsService.update({ await this.settingsService.update({
showAlert: event.detail.checked showAlert: event.detail.checked