feat: add waiting-list off-duty and return quick buttons
This commit is contained in:
1 parent
848e12e2d6
commit
7c5384621a
13 files changed
+446
-5
No files matched your search
@@ -1,5 +1,7 @@
|
||||
import Dexie, { type EntityTable } from 'dexie';
|
||||
|
||||
export type WaitingRole = 'umpire' | 'serviceJudge';
|
||||
|
||||
export interface Umpire {
|
||||
id: number;
|
||||
firstName: string;
|
||||
@@ -7,6 +9,7 @@ export interface Umpire {
|
||||
country: string;
|
||||
gender: string;
|
||||
courtNo?: number;
|
||||
returnToQueue?: WaitingRole;
|
||||
}
|
||||
|
||||
export interface CourtUmpire {
|
||||
@@ -38,6 +41,7 @@ export interface Settings {
|
||||
withServiceJudge: boolean;
|
||||
numberOfCourts: number;
|
||||
showAlert: boolean;
|
||||
waitingListQuickButtons?: boolean;
|
||||
}
|
||||
|
||||
const db = new Dexie('CourtPilot') as Dexie & {
|
||||
@@ -58,4 +62,20 @@ db.version(1).stores({
|
||||
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 };
|
||||
+17
-2
@@ -22,7 +22,7 @@ flowchart TD
|
||||
The root route lazy-loads the tab routes. The three primary routes are:
|
||||
|
||||
| Route | Source | Responsibility |
|
||||
|---|---|---|
|
||||
| ---------------- | --------------- | ---------------------------------------------------------------------- |
|
||||
| `/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/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
|
||||
|
||||
`db.ts` declares a `CourtPilot` Dexie database at schema version 1.
|
||||
`db.ts` declares a `CourtPilot` Dexie database at schema version 2.
|
||||
|
||||
```mermaid
|
||||
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
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
@@ -9,6 +9,14 @@ export const translations = {
|
||||
themeLight: 'Light',
|
||||
themeDark: 'Dark',
|
||||
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',
|
||||
serviceJudge: 'Service Judge',
|
||||
language: 'Language',
|
||||
@@ -62,6 +70,14 @@ export const translations = {
|
||||
themeLight: 'Világos',
|
||||
themeDark: 'Sötét',
|
||||
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',
|
||||
serviceJudge: 'Adogatásbíró',
|
||||
language: 'Nyelv',
|
||||
|
||||
@@ -29,7 +29,12 @@ export class SettingsService {
|
||||
return;
|
||||
}
|
||||
|
||||
await db.settings.update(1, changes);
|
||||
await db.settings.update(1, {
|
||||
...changes,
|
||||
...(!(changes.withServiceJudge ?? current.withServiceJudge)
|
||||
? { waitingListQuickButtons: false }
|
||||
: {})
|
||||
});
|
||||
}
|
||||
|
||||
private async ensureSettings() {
|
||||
@@ -40,7 +45,8 @@ export class SettingsService {
|
||||
id: 1,
|
||||
numberOfCourts: 5,
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,21 @@
|
||||
</ion-toggle>
|
||||
</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-toggle
|
||||
[checked]="currentSettings.showAlert"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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> {
|
||||
await this.settingsService.update({
|
||||
showAlert: event.detail.checked
|
||||
|
||||
Reference in new issue
Block a user