diff --git a/db.ts b/db.ts index c309d3c..77dd081 100644 --- a/db.ts +++ b/db.ts @@ -1,4 +1,5 @@ import Dexie, { type EntityTable } from 'dexie'; +import { settings } from 'ionicons/icons'; export interface Umpire { id: number; @@ -15,14 +16,38 @@ export interface Court { order: number; } +export interface WaitingAsUmpire { + id: number; + umpireId: number; + order: number; +} + +export interface WaitingAsServiceJudge { + id: number; + serviceJudgeId: number; + order: number; +} + +export interface Settings { + id: number; + withServiceJudge: boolean; + numberOfCourts: number; +} + const db = new Dexie('CourtPilot') as Dexie & { umpires: EntityTable; courts: EntityTable; + settings: EntityTable; + waitingUmpires: EntityTable; + waitingServiceJudges: EntityTable; }; db.version(1).stores({ umpires: '++id, lastName', - courts: '++id, umpireId, serviceJudgeId' + courts: '++id, umpireId, serviceJudgeId', + settings: '++id', + waitingUmpires: '++id, order, umpireId', + waitingServiceJudges: '++id, order, serviceJudgeId' }); export { db }; diff --git a/src/app/services/settings-service.spec.ts b/src/app/services/settings-service.spec.ts new file mode 100644 index 0000000..c6b89fc --- /dev/null +++ b/src/app/services/settings-service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { SettingsService } from './settings-service'; + +describe('SettingsService', () => { + let service: SettingsService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(SettingsService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/settings-service.ts b/src/app/services/settings-service.ts new file mode 100644 index 0000000..aedec29 --- /dev/null +++ b/src/app/services/settings-service.ts @@ -0,0 +1,46 @@ +import { Injectable } from '@angular/core'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { db, Settings } from 'db'; +import { liveQuery } from 'dexie'; + +@Injectable({ + providedIn: 'root' +}) +export class SettingsService { + constructor() { + void this.ensureSettings(); + } + + /** + * Reactive settings signal + */ + readonly settings = toSignal( + liveQuery(() => db.settings.get(1)), + { initialValue: undefined } + ); + + /** + * Update settings + */ + async update(changes: Partial>): Promise { + const current = await db.settings.get(1); + + if (!current) { + return; + } + + await db.settings.update(1, changes); + } + + private async ensureSettings() { + const existing = await db.settings.get(1); + + if (!existing) { + await db.settings.put({ + id: 1, + numberOfCourts: 5, + withServiceJudge: true + }); + } + } +} diff --git a/src/app/services/waiting-service-judges.service.spec.ts b/src/app/services/waiting-service-judges.service.spec.ts new file mode 100644 index 0000000..1ce147a --- /dev/null +++ b/src/app/services/waiting-service-judges.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { WaitingServiceJudgesService } from './waiting-service-judges.service'; + +describe('WaitingServiceJudgesService', () => { + let service: WaitingServiceJudgesService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(WaitingServiceJudgesService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/waiting-service-judges.service.ts b/src/app/services/waiting-service-judges.service.ts new file mode 100644 index 0000000..c91e1b5 --- /dev/null +++ b/src/app/services/waiting-service-judges.service.ts @@ -0,0 +1,70 @@ +import { Injectable } from '@angular/core'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { db } from 'db'; +import { liveQuery } from 'dexie'; +import { from } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class WaitingServiceJudgesService { + /** + * Reactive list sorted by order + */ + readonly waitingServiceJudges = toSignal( + from(liveQuery(() => db.waitingServiceJudges.orderBy('order').toArray())), + { + initialValue: [] + } + ); + + /** + * Add new umpire to queue with max(order) + 1 + */ + async add(serviceJudgeId: number): Promise { + return db.transaction('rw', db.waitingServiceJudges, async () => { + const last = await db.waitingServiceJudges.orderBy('order').last(); + + const nextOrder = last ? last.order + 1 : 1; + + return db.waitingServiceJudges.add({ + serviceJudgeId, + order: nextOrder + }); + }); + } + + /** + * Remove from queue + */ + async remove(id: number): Promise { + await db.waitingServiceJudges.delete(id); + } + + async removeByUmpireId(umpireId: number): Promise { + const item = await db.waitingServiceJudges + .where('serviceJudgeId') + .equals(umpireId) + .first(); + + if (!item?.id) { + return; + } + + await db.waitingServiceJudges.delete(item.id); + } + + /** + * Reorder (optional helper) + */ + async updateOrder(id: number, order: number): Promise { + await db.waitingServiceJudges.update(id, { order }); + } + + /** + * Clear queue + */ + async clear(): Promise { + await db.waitingServiceJudges.clear(); + } +} diff --git a/src/app/services/waiting-umpires.service.spec.ts b/src/app/services/waiting-umpires.service.spec.ts new file mode 100644 index 0000000..3987a3d --- /dev/null +++ b/src/app/services/waiting-umpires.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { WaitingUmpiresService } from './waiting-umpires.service'; + +describe('WaitingUmpiresService', () => { + let service: WaitingUmpiresService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(WaitingUmpiresService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/waiting-umpires.service.ts b/src/app/services/waiting-umpires.service.ts new file mode 100644 index 0000000..0f4bff6 --- /dev/null +++ b/src/app/services/waiting-umpires.service.ts @@ -0,0 +1,70 @@ +import { Injectable } from '@angular/core'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { db } from 'db'; +import { liveQuery } from 'dexie'; +import { from } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class WaitingUmpiresService { + /** + * Reactive list sorted by order + */ + readonly waitingUmpires = toSignal( + from(liveQuery(() => db.waitingUmpires.orderBy('order').toArray())), + { + initialValue: [] + } + ); + + /** + * Add new umpire to queue with max(order) + 1 + */ + async add(umpireId: number): Promise { + return db.transaction('rw', db.waitingUmpires, async () => { + const last = await db.waitingUmpires.orderBy('order').last(); + + const nextOrder = last ? last.order + 1 : 1; + + return db.waitingUmpires.add({ + umpireId, + order: nextOrder + }); + }); + } + + /** + * Remove from queue + */ + async remove(id: number): Promise { + await db.waitingUmpires.delete(id); + } + + async removeByUmpireId(umpireId: number): Promise { + const item = await db.waitingUmpires + .where('umpireId') + .equals(umpireId) + .first(); + + if (!item?.id) { + return; + } + + await db.waitingUmpires.delete(item.id); + } + + /** + * Reorder (optional helper) + */ + async updateOrder(id: number, order: number): Promise { + await db.waitingUmpires.update(id, { order }); + } + + /** + * Clear queue + */ + async clear(): Promise { + await db.waitingUmpires.clear(); + } +} diff --git a/src/app/tab1/tab1.page.html b/src/app/tab1/tab1.page.html index 22e11e4..3db6976 100644 --- a/src/app/tab1/tab1.page.html +++ b/src/app/tab1/tab1.page.html @@ -1,17 +1,97 @@ - - Tab 1 - + Pályák - Tab 1 + Pályák - + + + Pálya + Játékvezető + Adogatásbíró + + @for (item of [].constructor(settings()?.numberOfCourts); track $index) { + + {{ $index + 1 }}. + név1 + név2 + + } + + + + Játékvezetők + Adogatásbírók + Pihenők + + + + + @for (umpire of waitingUmpires; track umpire.id) { + + {{ umpire|fullname }} + + } @empty { + + + + + + } + + + + + @for (umpire of waitingServiceJudges; track umpire.id) { + + {{ umpire|fullname }} + + } @empty { + + + + + + } + + + + + @for (umpire of onRest; track umpire.id) { + + {{ umpire|fullname }} + + } + + + + diff --git a/src/app/tab1/tab1.page.ts b/src/app/tab1/tab1.page.ts index 2c0d1ee..08afcba 100644 --- a/src/app/tab1/tab1.page.ts +++ b/src/app/tab1/tab1.page.ts @@ -1,13 +1,166 @@ -import { Component } from '@angular/core'; -import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone'; -import { ExploreContainerComponent } from '../explore-container/explore-container.component'; +import { Component, effect, inject } from '@angular/core'; +import { + IonHeader, + IonToolbar, + IonTitle, + IonContent, + IonGrid, + IonCol, + IonRow, + IonList, + IonItem, + IonLabel, + IonIcon +} from '@ionic/angular/standalone'; +import { SettingsService } from '../services/settings-service'; +import { UmpireService } from '../services/umpire.service'; +import { Umpire, WaitingAsServiceJudge, WaitingAsUmpire } from 'db'; +import { WaitingUmpiresService } from '../services/waiting-umpires.service'; +import { WaitingServiceJudgesService } from '../services/waiting-service-judges.service'; +import { FullnamePipe } from '../fullname-pipe'; +import { + CdkDrag, + CdkDragDrop, + CdkDropList, + DragDropModule +} from '@angular/cdk/drag-drop'; +import { CommonModule } from '@angular/common'; +import { addIcons } from 'ionicons'; +import { add } from 'ionicons/icons'; @Component({ selector: 'app-tab1', templateUrl: 'tab1.page.html', styleUrls: ['tab1.page.scss'], - imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent], + imports: [ + IonIcon, + IonLabel, + IonItem, + IonList, + IonRow, + IonCol, + IonGrid, + IonHeader, + IonToolbar, + IonTitle, + IonContent, + FullnamePipe, + CdkDropList, + CdkDrag, + CommonModule, + DragDropModule + ] }) export class Tab1Page { - constructor() {} + readonly settingsService = inject(SettingsService); + readonly umpireService = inject(UmpireService); + readonly waitingUmpireService = inject(WaitingUmpiresService); + readonly waitingServiceJudgeService = inject(WaitingServiceJudgesService); + + public readonly settings = this.settingsService.settings; + public readonly _umpires = this.umpireService.umpires; + public readonly _waitingUmpires = this.waitingUmpireService.waitingUmpires; + public readonly _waitingServiceJudges = + this.waitingServiceJudgeService.waitingServiceJudges; + + public onRest: Umpire[] = []; + public waitingUmpires: Umpire[] = []; + public waitingServiceJudges: Umpire[] = []; + + constructor() { + addIcons({ add }); + + effect(() => { + this.onRest = this._umpires().filter((umpire) => { + return ( + !this.isUmpireOnCourt(umpire) && + !this.isWaitingUmpire(umpire) && + !this.isWaitingServiceJudge(umpire) + ); + }); + + this.waitingUmpires = this._waitingUmpires() + .map((_wa) => { + return this._umpires().find((u) => u.id === _wa.umpireId); + }) + .filter((u) => typeof u !== 'undefined'); + + this.waitingServiceJudges = this._waitingServiceJudges() + .map((_wsj) => { + return this._umpires().find((u) => u.id === _wsj.serviceJudgeId); + }) + .filter((u) => typeof u !== 'undefined'); + }); + } + + private isUmpireOnCourt(umpire: Umpire): boolean { + return false; + } + + private isWaitingUmpire(umpire: Umpire): boolean { + return ( + typeof this._waitingUmpires().find((wu) => wu.umpireId === umpire.id) !== + 'undefined' + ); + } + + private isWaitingServiceJudge(umpire: Umpire): boolean { + return ( + typeof this._waitingServiceJudges().find( + (wsj) => wsj.serviceJudgeId === umpire.id + ) !== 'undefined' + ); + } + + dropToRest(event: CdkDragDrop) { + if (event.previousContainer === event.container) { + return; + } else { + const comingFrom = event.previousContainer.id; + const umpireToMove = event.item.data; + if ('list-waiting-service-judges' === comingFrom) { + // Remove from waiting service judges + this.waitingServiceJudgeService.removeByUmpireId(umpireToMove.id); + } + + if ('list-waiting-umpires' === comingFrom) { + this.waitingUmpireService.removeByUmpireId(umpireToMove.id); + } + } + } + + dropToWaitingServiceJudge(event: CdkDragDrop) { + if (event.previousContainer === event.container) { + // TODO + } else { + const comingFrom = event.previousContainer.id; + const umpireToMove = event.item.data; + this.waitingServiceJudgeService.add(umpireToMove.id); + + if ('list-waiting-umpires' === comingFrom) { + this.waitingUmpireService.removeByUmpireId(umpireToMove.id); + } + } + } + + dropToWaitingUmpire(event: CdkDragDrop) { + console.log( + event.container.data, + event.previousContainer.data, + event.previousContainer.id, + event.container.id + ); + if (event.previousContainer === event.container) { + // TODO + } else { + const comingFrom = event.previousContainer.id; + const umpireToMove = event.item.data; + this.waitingUmpireService.add(umpireToMove.id); + + if ('list-waiting-service-judges' === comingFrom) { + // Remove from waiting service judges + this.waitingServiceJudgeService.removeByUmpireId(umpireToMove.id); + } + } + } } diff --git a/src/app/tabs/tabs.page.html b/src/app/tabs/tabs.page.html index 6b27a1a..37ef5c2 100644 --- a/src/app/tabs/tabs.page.html +++ b/src/app/tabs/tabs.page.html @@ -14,5 +14,10 @@ Tab 3 + + + + Beállítások +