Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2a7bc6100 | ||
|
|
0c1a9f86d6 | ||
|
|
ab32f01e07 | ||
|
|
07f6919617 | ||
|
|
1d84985a76 | ||
|
|
33d635839f | ||
|
|
abf0cd2359 | ||
|
|
02fc32942a | ||
|
|
ccfca0dd25 | ||
|
|
60f12cc333 | ||
|
|
63737e46eb | ||
|
|
2fb8d34ec1 | ||
|
|
12871ef7d2 | ||
|
|
d317ce6179 | ||
|
|
0c071c0621 |
@@ -1,5 +1,4 @@
|
||||
import Dexie, { type EntityTable } from 'dexie';
|
||||
import { settings } from 'ionicons/icons';
|
||||
|
||||
export interface Umpire {
|
||||
id: number;
|
||||
@@ -7,13 +6,19 @@ export interface Umpire {
|
||||
lastName: string;
|
||||
country: string;
|
||||
gender: string;
|
||||
courtNo?: number;
|
||||
}
|
||||
|
||||
export interface Court {
|
||||
export interface CourtUmpire {
|
||||
id: number;
|
||||
umpireId: number | null;
|
||||
serviceJudgeId: number | null;
|
||||
order: number;
|
||||
courtNo: number;
|
||||
}
|
||||
|
||||
export interface CourtServiceJudge {
|
||||
id: number;
|
||||
umpireId: number | null;
|
||||
courtNo: number;
|
||||
}
|
||||
|
||||
export interface WaitingAsUmpire {
|
||||
@@ -32,11 +37,13 @@ export interface Settings {
|
||||
id: number;
|
||||
withServiceJudge: boolean;
|
||||
numberOfCourts: number;
|
||||
showAlert: boolean;
|
||||
}
|
||||
|
||||
const db = new Dexie('CourtPilot') as Dexie & {
|
||||
umpires: EntityTable<Umpire, 'id'>;
|
||||
courts: EntityTable<Court, 'id'>;
|
||||
courtUmpires: EntityTable<CourtUmpire, 'id'>;
|
||||
courtServiceJudges: EntityTable<CourtServiceJudge, 'id'>;
|
||||
settings: EntityTable<Settings, 'id'>;
|
||||
waitingUmpires: EntityTable<WaitingAsUmpire, 'id'>;
|
||||
waitingServiceJudges: EntityTable<WaitingAsServiceJudge, 'id'>;
|
||||
@@ -44,7 +51,8 @@ const db = new Dexie('CourtPilot') as Dexie & {
|
||||
|
||||
db.version(1).stores({
|
||||
umpires: '++id, lastName',
|
||||
courts: '++id, umpireId, serviceJudgeId',
|
||||
courtUmpires: '++id, courtNo, umpireId',
|
||||
courtServiceJudges: '++id, courtNo, umpireId',
|
||||
settings: '++id',
|
||||
waitingUmpires: '++id, order, umpireId',
|
||||
waitingServiceJudges: '++id, order, serviceJudgeId'
|
||||
|
||||
@@ -9,19 +9,20 @@
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint"
|
||||
"lint": "ng lint",
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^20.0.0",
|
||||
"@angular/animations": "^20.3.28",
|
||||
"@angular/cdk": "^20.2.14",
|
||||
"@angular/common": "^20.0.0",
|
||||
"@angular/compiler": "^20.0.0",
|
||||
"@angular/core": "^20.0.0",
|
||||
"@angular/forms": "^20.0.0",
|
||||
"@angular/platform-browser": "^20.0.0",
|
||||
"@angular/platform-browser-dynamic": "^20.0.0",
|
||||
"@angular/router": "^20.0.0",
|
||||
"@angular/common": "^20.3.28",
|
||||
"@angular/compiler": "^20.3.28",
|
||||
"@angular/core": "^20.3.28",
|
||||
"@angular/forms": "^20.3.28",
|
||||
"@angular/platform-browser": "^20.3.28",
|
||||
"@angular/platform-browser-dynamic": "^20.3.28",
|
||||
"@angular/router": "^20.3.28",
|
||||
"@capacitor/app": "8.1.0",
|
||||
"@capacitor/core": "8.3.4",
|
||||
"@capacitor/haptics": "8.0.2",
|
||||
@@ -35,15 +36,15 @@
|
||||
"zone.js": "~0.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^20.0.0",
|
||||
"@angular-eslint/builder": "^20.0.0",
|
||||
"@angular-eslint/eslint-plugin": "^20.0.0",
|
||||
"@angular-eslint/eslint-plugin-template": "^20.0.0",
|
||||
"@angular-eslint/schematics": "^20.0.0",
|
||||
"@angular-eslint/template-parser": "^20.0.0",
|
||||
"@angular/cli": "^20.0.0",
|
||||
"@angular/compiler-cli": "^20.0.0",
|
||||
"@angular/language-service": "^20.0.0",
|
||||
"@angular-devkit/build-angular": "^20.3.34",
|
||||
"@angular-eslint/builder": "^20.3.0",
|
||||
"@angular-eslint/eslint-plugin": "^20.3.0",
|
||||
"@angular-eslint/eslint-plugin-template": "^20.3.0",
|
||||
"@angular-eslint/schematics": "^20.3.0",
|
||||
"@angular-eslint/template-parser": "^20.3.0",
|
||||
"@angular/cli": "^20.3.34",
|
||||
"@angular/compiler-cli": "^20.3.28",
|
||||
"@angular/language-service": "^20.3.28",
|
||||
"@capacitor/cli": "8.3.4",
|
||||
"@ionic/angular-toolkit": "^12.0.0",
|
||||
"@tauri-apps/cli": "^2.11.2",
|
||||
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 838 B |
|
After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 790 B |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
</adaptive-icon>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#fff</color>
|
||||
</resources>
|
||||
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 513 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 751 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||
"productName": "court-pilot",
|
||||
"version": "0.1.0",
|
||||
"version": "1.0.0",
|
||||
"identifier": "com.tauri.dev",
|
||||
"build": {
|
||||
"frontendDist": "../wwww",
|
||||
@@ -12,9 +12,11 @@
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"minHeight": 600,
|
||||
"minWidth": 800,
|
||||
"title": "Court Pilot",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"width": 1366,
|
||||
"height": 768,
|
||||
"resizable": true,
|
||||
"fullscreen": false
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<div id="container">
|
||||
<strong>{{ name }}</strong>
|
||||
<p>
|
||||
Explore
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://ionicframework.com/docs/components"
|
||||
>UI Components</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
@@ -1,27 +0,0 @@
|
||||
#container {
|
||||
text-align: center;
|
||||
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
#container strong {
|
||||
font-size: 20px;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
#container p {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
|
||||
color: #8c8c8c;
|
||||
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#container a {
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ExploreContainerComponent } from './explore-container.component';
|
||||
|
||||
describe('ExploreContainerComponent', () => {
|
||||
let component: ExploreContainerComponent;
|
||||
let fixture: ComponentFixture<ExploreContainerComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
fixture = TestBed.createComponent(ExploreContainerComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-explore-container',
|
||||
templateUrl: './explore-container.component.html',
|
||||
styleUrls: ['./explore-container.component.scss'],
|
||||
})
|
||||
export class ExploreContainerComponent {
|
||||
@Input() name?: string;
|
||||
}
|
||||
@@ -1,13 +1,24 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { inject, Pipe, PipeTransform } from '@angular/core';
|
||||
import { Umpire } from 'db';
|
||||
import { TranslationService } from './i18n/translation.service';
|
||||
|
||||
@Pipe({
|
||||
name: 'fullname',
|
||||
standalone: true
|
||||
standalone: true,
|
||||
pure: false
|
||||
})
|
||||
export class FullnamePipe implements PipeTransform {
|
||||
transform(value: Umpire, ...args: unknown[]): string {
|
||||
// TODO: in case of multilang, change here
|
||||
readonly translationService = inject(TranslationService);
|
||||
|
||||
transform(value: Umpire | undefined, ...args: unknown[]): string {
|
||||
if (typeof value === 'undefined') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (this.translationService.language() === 'en') {
|
||||
return value.firstName + ' ' + value.lastName;
|
||||
}
|
||||
|
||||
return value.lastName + ' ' + value.firstName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Pipe, PipeTransform, inject } from '@angular/core';
|
||||
import { TranslationKey, TranslationParams } from './translations';
|
||||
import { TranslationService } from './translation.service';
|
||||
|
||||
@Pipe({
|
||||
name: 'translate',
|
||||
standalone: true,
|
||||
pure: false
|
||||
})
|
||||
export class TranslatePipe implements PipeTransform {
|
||||
private readonly translationService = inject(TranslationService);
|
||||
|
||||
transform(key: TranslationKey, params: TranslationParams = {}): string {
|
||||
return this.translationService.translate(key, params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Injectable, signal } from '@angular/core';
|
||||
import {
|
||||
translations,
|
||||
Language,
|
||||
TranslationKey,
|
||||
TranslationParams
|
||||
} from './translations';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class TranslationService {
|
||||
private readonly storageKey = 'language';
|
||||
|
||||
readonly language = signal<Language>(this.getInitialLanguage());
|
||||
|
||||
setLanguage(language: Language): void {
|
||||
this.language.set(language);
|
||||
localStorage.setItem(this.storageKey, language);
|
||||
}
|
||||
|
||||
translate(key: TranslationKey, params: TranslationParams = {}): string {
|
||||
let text: string = translations[this.language()][key];
|
||||
|
||||
for (const [param, value] of Object.entries(params)) {
|
||||
text = text.replaceAll(`{${param}}`, String(value));
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
private getInitialLanguage(): Language {
|
||||
const stored = localStorage.getItem(this.storageKey);
|
||||
|
||||
if (stored && stored in translations) {
|
||||
return stored as Language;
|
||||
}
|
||||
|
||||
const browserLanguage = navigator.language.split('-')[0] as Language;
|
||||
|
||||
if (browserLanguage in translations) {
|
||||
return browserLanguage;
|
||||
}
|
||||
|
||||
return 'en';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { language, male, settings } from 'ionicons/icons';
|
||||
import { noop } from 'rxjs';
|
||||
|
||||
export const translations = {
|
||||
en: {
|
||||
courts: 'Courts',
|
||||
numberOfCourts: 'Number of Courts',
|
||||
showAlerts: 'Show alerts on quick buttons',
|
||||
serviceJudge: 'Service Judge',
|
||||
language: 'Language',
|
||||
settings: 'Settings',
|
||||
courtOfficials: 'Court Officials',
|
||||
add: 'Add',
|
||||
noCourtOfficials: 'No court officials',
|
||||
newCourtOfficial: 'New Court Official',
|
||||
cancel: 'Cancel',
|
||||
save: 'Save',
|
||||
firstName: 'First Name',
|
||||
lastName: 'Last Name',
|
||||
country: 'Country',
|
||||
gender: 'Gender',
|
||||
male: 'Male',
|
||||
female: 'Female',
|
||||
confirmation: 'Confirmation',
|
||||
confirmDeleteUmpire: 'Do you really want to delete this court official?',
|
||||
no: 'No',
|
||||
yes: 'Yes',
|
||||
umpires: 'Umpires',
|
||||
serviceJudges: 'Service Judges',
|
||||
notOnDuty: 'Not on Duty',
|
||||
shortCourtName: 'C',
|
||||
court: 'Court',
|
||||
umpire: 'Umpire',
|
||||
removeUmpireFromCourt:
|
||||
'Are you sure you want to remove them from the court?',
|
||||
removeUmpiresFromCourt:
|
||||
'Are you sure you want to remove them from the court?',
|
||||
courtConfirm: 'Court {courtNo}',
|
||||
sendUmpireToCourt: 'Are you sure you want to send them to the court?',
|
||||
sendUmpiresToCourt: 'Are you sure you want to send them to the court?',
|
||||
sendUmpireMessage: '{umpire}',
|
||||
sendUmpiresMessage: '{umpire} - {serviceJudge}',
|
||||
version: 'Version',
|
||||
},
|
||||
|
||||
hu: {
|
||||
courts: 'Pályák',
|
||||
numberOfCourts: 'Pályák száma',
|
||||
showAlerts: 'Figyelmeztetések megjelenítése a gyors gomboknál',
|
||||
serviceJudge: 'Adogatásbíró',
|
||||
language: 'Nyelv',
|
||||
settings: 'Beállítások',
|
||||
courtOfficials: 'Tisztségviselők',
|
||||
add: 'Hozzáadás',
|
||||
noCourtOfficials: 'Nincsenek tisztségviselők',
|
||||
newCourtOfficial: 'Új Tisztségviselő',
|
||||
cancel: 'Mégse',
|
||||
save: 'Mentés',
|
||||
firstName: 'Keresztnév',
|
||||
lastName: 'Vezetéknév',
|
||||
country: 'Ország',
|
||||
gender: 'Nem',
|
||||
male: 'Férfi',
|
||||
female: 'Nő',
|
||||
confirmation: 'Megerősítés',
|
||||
confirmDeleteUmpire: 'Biztosan törölni szeretnéd ezt a tisztségviselőt?',
|
||||
no: 'Nem',
|
||||
yes: 'Igen',
|
||||
umpires: 'Játékeveztők',
|
||||
serviceJudges: 'Adogatásbírók',
|
||||
notOnDuty: 'Pihenő',
|
||||
shortCourtName: 'P',
|
||||
court: 'Pálya',
|
||||
umpire: 'Játékeveztő',
|
||||
removeUmpireFromCourt: 'Biztos leveszed őt a pályáról?',
|
||||
removeUmpiresFromCourt: 'Biztos leveszed őket a pályáról?',
|
||||
courtConfirm: 'Pálya {courtNo}',
|
||||
sendUmpireToCourt: 'Biztos pályára küldöd őt?',
|
||||
sendUmpiresToCourt: 'Biztos pályára küldöd őket?',
|
||||
sendUmpireMessage: '{umpire}',
|
||||
sendUmpiresMessage: '{umpire} - {serviceJudge}',
|
||||
version: 'Verzió',
|
||||
}
|
||||
} as const;
|
||||
|
||||
export type Language = keyof typeof translations;
|
||||
export type TranslationKey = keyof typeof translations.en;
|
||||
export type TranslationParams = Record<string, string | number>;
|
||||
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CourtServiceJudgeService } from './court.service.judge.service';
|
||||
|
||||
describe('CourtServiceJudgeService', () => {
|
||||
let service: CourtServiceJudgeService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(CourtServiceJudgeService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { CourtServiceJudge, db } from 'db';
|
||||
import { liveQuery } from 'dexie';
|
||||
import { from } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CourtServiceJudgeService {
|
||||
/**
|
||||
* All service judges (reactive)
|
||||
*/
|
||||
readonly umpires = toSignal(
|
||||
from(liveQuery(() => db.courtServiceJudges.orderBy('courtNo').toArray())),
|
||||
{ initialValue: [] }
|
||||
);
|
||||
|
||||
/**
|
||||
* Get by court number (reactive)
|
||||
*/
|
||||
getByCourtNo(courtNo: number) {
|
||||
return toSignal<CourtServiceJudge | undefined>(
|
||||
liveQuery(() =>
|
||||
db.courtServiceJudges.where('courtNo').equals(courtNo).first()
|
||||
),
|
||||
{ initialValue: undefined }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create / update
|
||||
*/
|
||||
async save(item: Omit<CourtServiceJudge, 'id'>): Promise<number> {
|
||||
return db.courtServiceJudges.add(item as CourtServiceJudge);
|
||||
}
|
||||
|
||||
async update(
|
||||
id: number,
|
||||
changes: Partial<CourtServiceJudge>
|
||||
): Promise<number> {
|
||||
return db.courtServiceJudges.update(id, changes);
|
||||
}
|
||||
|
||||
async delete(id: number): Promise<void> {
|
||||
await db.courtServiceJudges.delete(id);
|
||||
}
|
||||
|
||||
async removeByUmpireId(umpireId: number): Promise<void> {
|
||||
const item = await db.courtServiceJudges
|
||||
.where('umpireId')
|
||||
.equals(umpireId)
|
||||
.first();
|
||||
|
||||
if (!item?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
await db.courtServiceJudges.delete(item.id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CourtUmpireService } from './court.umpire.service';
|
||||
|
||||
describe('CourtUmpireService', () => {
|
||||
let service: CourtUmpireService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(CourtUmpireService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { CourtUmpire, db } from 'db';
|
||||
import { liveQuery } from 'dexie';
|
||||
import { from } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CourtUmpireService {
|
||||
/**
|
||||
* All court umpires (reactive)
|
||||
*/
|
||||
readonly umpires = toSignal(
|
||||
from(liveQuery(() => db.courtUmpires.orderBy('courtNo').toArray())),
|
||||
{ initialValue: [] }
|
||||
);
|
||||
|
||||
/**
|
||||
* Get by court number (reactive single)
|
||||
*/
|
||||
getByCourtNo(courtNo: number) {
|
||||
return toSignal<CourtUmpire | undefined>(
|
||||
liveQuery(() => db.courtUmpires.where('courtNo').equals(courtNo).first()),
|
||||
{ initialValue: undefined }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create / update
|
||||
*/
|
||||
async save(item: Omit<CourtUmpire, 'id'>): Promise<number> {
|
||||
return db.courtUmpires.add(item as CourtUmpire);
|
||||
}
|
||||
|
||||
async update(id: number, changes: Partial<CourtUmpire>): Promise<number> {
|
||||
return db.courtUmpires.update(id, changes);
|
||||
}
|
||||
|
||||
async delete(id: number): Promise<void> {
|
||||
await db.courtUmpires.delete(id);
|
||||
}
|
||||
|
||||
async removeByUmpireId(umpireId: number): Promise<void> {
|
||||
const item = await db.courtUmpires
|
||||
.where('umpireId')
|
||||
.equals(umpireId)
|
||||
.first();
|
||||
|
||||
if (!item?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
await db.courtUmpires.delete(item.id);
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,8 @@ export class SettingsService {
|
||||
await db.settings.put({
|
||||
id: 1,
|
||||
numberOfCourts: 5,
|
||||
withServiceJudge: true
|
||||
withServiceJudge: true,
|
||||
showAlert: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { db } from 'db';
|
||||
import { db, Umpire } from 'db';
|
||||
import { liveQuery } from 'dexie';
|
||||
import { from } from 'rxjs';
|
||||
|
||||
@@ -21,8 +21,8 @@ export class WaitingServiceJudgesService {
|
||||
/**
|
||||
* Add new umpire to queue with max(order) + 1
|
||||
*/
|
||||
async add(serviceJudgeId: number): Promise<number> {
|
||||
return db.transaction('rw', db.waitingServiceJudges, async () => {
|
||||
async add(serviceJudgeId: number, toPosition?: number): Promise<number> {
|
||||
const id = await db.transaction('rw', db.waitingServiceJudges, async () => {
|
||||
const last = await db.waitingServiceJudges.orderBy('order').last();
|
||||
|
||||
const nextOrder = last ? last.order + 1 : 1;
|
||||
@@ -32,6 +32,12 @@ export class WaitingServiceJudgesService {
|
||||
order: nextOrder
|
||||
});
|
||||
});
|
||||
|
||||
if (toPosition !== undefined) {
|
||||
await this.moveToPosition(serviceJudgeId, toPosition);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,6 +45,8 @@ export class WaitingServiceJudgesService {
|
||||
*/
|
||||
async remove(id: number): Promise<void> {
|
||||
await db.waitingServiceJudges.delete(id);
|
||||
|
||||
await this.reinitOrders();
|
||||
}
|
||||
|
||||
async removeByUmpireId(umpireId: number): Promise<void> {
|
||||
@@ -51,7 +59,7 @@ export class WaitingServiceJudgesService {
|
||||
return;
|
||||
}
|
||||
|
||||
await db.waitingServiceJudges.delete(item.id);
|
||||
await this.remove(item.id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,4 +75,87 @@ export class WaitingServiceJudgesService {
|
||||
async clear(): Promise<void> {
|
||||
await db.waitingServiceJudges.clear();
|
||||
}
|
||||
|
||||
async moveToPosition(umpireId: number, newOrder: number): Promise<void> {
|
||||
await db.transaction('rw', db.waitingServiceJudges, async () => {
|
||||
const current = await db.waitingServiceJudges
|
||||
.where('serviceJudgeId')
|
||||
.equals(umpireId)
|
||||
.first();
|
||||
|
||||
if (!current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldOrder = current.order;
|
||||
|
||||
if (oldOrder === newOrder) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newOrder < oldOrder) {
|
||||
// Moving up: shift affected records down
|
||||
|
||||
const affected = await db.waitingServiceJudges
|
||||
.filter(
|
||||
(item) =>
|
||||
item.order >= newOrder &&
|
||||
item.order < oldOrder &&
|
||||
item.id !== current.id
|
||||
)
|
||||
.toArray();
|
||||
|
||||
for (const item of affected) {
|
||||
await db.waitingServiceJudges.update(item.id!, {
|
||||
order: item.order + 1
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Moving down: shift affected records up
|
||||
|
||||
const affected = await db.waitingServiceJudges
|
||||
.filter(
|
||||
(item) =>
|
||||
item.order > oldOrder &&
|
||||
item.order <= newOrder &&
|
||||
item.id !== current.id
|
||||
)
|
||||
.toArray();
|
||||
|
||||
for (const item of affected) {
|
||||
await db.waitingServiceJudges.update(item.id!, {
|
||||
order: item.order - 1
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await db.waitingServiceJudges.update(current.id!, {
|
||||
order: newOrder
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async reinitOrders(): Promise<void> {
|
||||
await db.transaction('rw', db.waitingServiceJudges, async () => {
|
||||
const items = await db.waitingServiceJudges.orderBy('order').toArray();
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
|
||||
const expectedOrder = i + 1;
|
||||
|
||||
if (item.order !== expectedOrder) {
|
||||
await db.waitingServiceJudges.update(item.id!, {
|
||||
order: expectedOrder
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async getCurrentUmpire(): Promise<Umpire | undefined> {
|
||||
const item = await db.waitingServiceJudges.where('order').equals(1).first();
|
||||
|
||||
return item ? db.umpires.get(item.serviceJudgeId) : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { db } from 'db';
|
||||
import { db, Umpire } from 'db';
|
||||
import { liveQuery } from 'dexie';
|
||||
import { from } from 'rxjs';
|
||||
|
||||
@@ -21,8 +21,8 @@ export class WaitingUmpiresService {
|
||||
/**
|
||||
* Add new umpire to queue with max(order) + 1
|
||||
*/
|
||||
async add(umpireId: number): Promise<number> {
|
||||
return db.transaction('rw', db.waitingUmpires, async () => {
|
||||
async add(umpireId: number, toPosition?: number): Promise<number> {
|
||||
const id = await db.transaction('rw', db.waitingUmpires, async () => {
|
||||
const last = await db.waitingUmpires.orderBy('order').last();
|
||||
|
||||
const nextOrder = last ? last.order + 1 : 1;
|
||||
@@ -32,6 +32,12 @@ export class WaitingUmpiresService {
|
||||
order: nextOrder
|
||||
});
|
||||
});
|
||||
|
||||
if (toPosition !== undefined) {
|
||||
await this.moveToPosition(umpireId, toPosition);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,6 +45,8 @@ export class WaitingUmpiresService {
|
||||
*/
|
||||
async remove(id: number): Promise<void> {
|
||||
await db.waitingUmpires.delete(id);
|
||||
|
||||
await this.reinitOrders();
|
||||
}
|
||||
|
||||
async removeByUmpireId(umpireId: number): Promise<void> {
|
||||
@@ -51,7 +59,7 @@ export class WaitingUmpiresService {
|
||||
return;
|
||||
}
|
||||
|
||||
await db.waitingUmpires.delete(item.id);
|
||||
await this.remove(item.id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,4 +75,87 @@ export class WaitingUmpiresService {
|
||||
async clear(): Promise<void> {
|
||||
await db.waitingUmpires.clear();
|
||||
}
|
||||
|
||||
async moveToPosition(umpireId: number, newOrder: number): Promise<void> {
|
||||
await db.transaction('rw', db.waitingUmpires, async () => {
|
||||
const current = await db.waitingUmpires
|
||||
.where('umpireId')
|
||||
.equals(umpireId)
|
||||
.first();
|
||||
|
||||
if (!current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldOrder = current.order;
|
||||
|
||||
if (oldOrder === newOrder) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newOrder < oldOrder) {
|
||||
// Moving up: shift affected records down
|
||||
|
||||
const affected = await db.waitingUmpires
|
||||
.filter(
|
||||
(item) =>
|
||||
item.order >= newOrder &&
|
||||
item.order < oldOrder &&
|
||||
item.id !== current.id
|
||||
)
|
||||
.toArray();
|
||||
|
||||
for (const item of affected) {
|
||||
await db.waitingUmpires.update(item.id!, {
|
||||
order: item.order + 1
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Moving down: shift affected records up
|
||||
|
||||
const affected = await db.waitingUmpires
|
||||
.filter(
|
||||
(item) =>
|
||||
item.order > oldOrder &&
|
||||
item.order <= newOrder &&
|
||||
item.id !== current.id
|
||||
)
|
||||
.toArray();
|
||||
|
||||
for (const item of affected) {
|
||||
await db.waitingUmpires.update(item.id!, {
|
||||
order: item.order - 1
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await db.waitingUmpires.update(current.id!, {
|
||||
order: newOrder
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async reinitOrders(): Promise<void> {
|
||||
await db.transaction('rw', db.waitingUmpires, async () => {
|
||||
const items = await db.waitingUmpires.orderBy('order').toArray();
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
|
||||
const expectedOrder = i + 1;
|
||||
|
||||
if (item.order !== expectedOrder) {
|
||||
await db.waitingUmpires.update(item.id!, {
|
||||
order: expectedOrder
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async getCurrentUmpire(): Promise<Umpire | undefined> {
|
||||
const item = await db.waitingUmpires.where('order').equals(1).first();
|
||||
|
||||
return item ? db.umpires.get(item.umpireId) : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,81 +1,177 @@
|
||||
<ion-header [translucent]="true">
|
||||
<ion-toolbar>
|
||||
<ion-title> Pályák </ion-title>
|
||||
<ion-title> {{ 'courts' | translate }} </ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content [fullscreen]="true">
|
||||
<ion-content [fullscreen]="true" cdkDropListGroup>
|
||||
<ion-header collapse="condense">
|
||||
<ion-toolbar>
|
||||
<ion-title size="large">Pályák</ion-title>
|
||||
<ion-title size="large">{{ 'courts' | translate }}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-grid [fixed]="'fixed'">
|
||||
<ion-grid [fixed]="'fixed'" class="bottom-lists">
|
||||
<ion-row>
|
||||
<ion-col [size]="1">Pálya</ion-col>
|
||||
<ion-col>Játékvezető</ion-col>
|
||||
<ion-col>Adogatásbíró</ion-col>
|
||||
<ion-col [size]="1" class="ion-display-none ion-display-xl-block">
|
||||
<ion-item [lines]="'none'">{{ 'court' | translate }}</ion-item>
|
||||
</ion-col>
|
||||
<ion-col [size]="1" class="ion-display-xl-none">
|
||||
<ion-item [lines]="'none'">{{ 'shortCourtName' | translate }}</ion-item>
|
||||
</ion-col>
|
||||
<ion-col>
|
||||
<ion-item [lines]="'none'">
|
||||
<ion-label class="ion-text-center"> {{ 'umpire' | translate }} </ion-label>
|
||||
</ion-item>
|
||||
</ion-col>
|
||||
@if (serviceJudgeEnabled()) {
|
||||
<ion-col>
|
||||
<ion-item [lines]="'none'">
|
||||
<ion-label class="ion-text-center"> {{ 'serviceJudge' | translate }} </ion-label>
|
||||
</ion-item>
|
||||
</ion-col>
|
||||
}
|
||||
<ion-col size="2" sizeLg="1"></ion-col>
|
||||
</ion-row>
|
||||
@for (item of [].constructor(settings()?.numberOfCourts); track $index) {
|
||||
<ion-row>
|
||||
<ion-col [size]="1">{{ $index + 1 }}.</ion-col>
|
||||
<ion-col>név1</ion-col>
|
||||
<ion-col>név2</ion-col>
|
||||
<ion-col [size]="1">
|
||||
<ion-list>
|
||||
<ion-item [lines]="'none'"> {{ $index + 1 }}. </ion-item>
|
||||
</ion-list>
|
||||
</ion-col>
|
||||
<ion-col>
|
||||
<ion-list
|
||||
[lines]="'none'"
|
||||
cdkDropList
|
||||
[id]="`court-umpire-${$index + 1}`"
|
||||
[cdkDropListData]="umpireByCourt().get($index + 1) ? [umpireByCourt().get($index + 1)] : []"
|
||||
[cdkDropListEnterPredicate]="canDropUmpire"
|
||||
(cdkDropListDropped)="dropToUmpire($event, $index + 1)">
|
||||
@if (umpireByCourt().get($index + 1); as umpire) {
|
||||
|
||||
<div class="custom-ion-item" cdkDrag [cdkDragData]="umpire">
|
||||
<div *cdkDragPlaceholder class="custom-ion-item primary">
|
||||
<ion-label>{{ umpire|fullname }}</ion-label>
|
||||
</div>
|
||||
<ion-label>{{ umpire|fullname }}</ion-label>
|
||||
</div>
|
||||
|
||||
}
|
||||
</ion-list>
|
||||
</ion-col>
|
||||
@if (serviceJudgeEnabled()) {
|
||||
<ion-col>
|
||||
<ion-list
|
||||
[lines]="'none'"
|
||||
cdkDropList
|
||||
[id]="`court-service-judge-${$index + 1}`"
|
||||
[cdkDropListData]="serviceJudgeByCourt().get($index + 1) ? [serviceJudgeByCourt().get($index + 1)] : []"
|
||||
(cdkDropListDropped)="dropToServiceJudge($event, $index + 1)">
|
||||
@if (serviceJudgeByCourt().get($index + 1); as umpire) {
|
||||
|
||||
<div class="custom-ion-item" cdkDrag [cdkDragData]="umpire">
|
||||
<div *cdkDragPlaceholder class="custom-ion-item primary">
|
||||
<ion-label>{{ umpire|fullname }}</ion-label>
|
||||
</div>
|
||||
<ion-label>{{ umpire|fullname }}</ion-label>
|
||||
</div>
|
||||
|
||||
}
|
||||
</ion-list>
|
||||
</ion-col>
|
||||
}
|
||||
<ion-col size="2" sizeLg="1">
|
||||
<ion-list>
|
||||
<ion-item lines="none">
|
||||
@if ( (umpireByCourt().get($index + 1) &&
|
||||
serviceJudgeByCourt().get($index + 1)) ||
|
||||
(umpireByCourt().get($index + 1) && !serviceJudgeEnabled()) ) {
|
||||
<ion-button
|
||||
[color]="'danger'"
|
||||
(click)="showRemoveConfirmation($index + 1)">
|
||||
<ion-icon
|
||||
slot="icon-only"
|
||||
size="large"
|
||||
name="exit-outline"></ion-icon>
|
||||
</ion-button>
|
||||
} @else if ((!umpireByCourt().get($index + 1) &&
|
||||
!serviceJudgeByCourt().get($index + 1) && waitingUmpires().length &&
|
||||
waitingServiceJudges().length) || (!umpireByCourt().get($index + 1)
|
||||
&& waitingUmpires().length && !serviceJudgeEnabled())) {
|
||||
<ion-button
|
||||
[color]="'success'"
|
||||
[disabled]=""
|
||||
(click)="showAddConfirmation($index + 1)">
|
||||
<ion-icon
|
||||
slot="icon-only"
|
||||
size="large"
|
||||
name="enter-outline"></ion-icon>
|
||||
</ion-button>
|
||||
}
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
}
|
||||
</ion-grid>
|
||||
<ion-grid [fixed]="'fixed'" class="ion-margin-top">
|
||||
<ion-grid [fixed]="'fixed'" class="ion-margin-top bottom-lists">
|
||||
<ion-row>
|
||||
<ion-col [size]="4">Játékvezetők</ion-col>
|
||||
<ion-col [size]="4">Adogatásbírók</ion-col>
|
||||
<ion-col [size]="4">Pihenők</ion-col>
|
||||
<ion-col [size]="!serviceJudgeEnabled() ? 6 : 4">
|
||||
<ion-item color="primary">
|
||||
<ion-label class="ion-text-center"> {{ 'umpires' | translate }} </ion-label>
|
||||
</ion-item>
|
||||
</ion-col>
|
||||
@if (serviceJudgeEnabled()) {
|
||||
<ion-col [size]="4">
|
||||
<ion-item color="primary">
|
||||
<ion-label class="ion-text-center"> {{ 'serviceJudges' | translate }} </ion-label>
|
||||
</ion-item>
|
||||
</ion-col>
|
||||
}
|
||||
<ion-col [size]="!serviceJudgeEnabled() ? 6 : 4">
|
||||
<ion-item color="medium">
|
||||
<ion-label class="ion-text-center"> {{ 'notOnDuty' | translate }} </ion-label>
|
||||
</ion-item>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<ion-list
|
||||
[lines]="'none'"
|
||||
cdkDropList
|
||||
cdkDropListSortingDisabled
|
||||
id="list-waiting-umpires"
|
||||
[cdkDropListData]="waitingUmpires"
|
||||
(cdkDropListDropped)="dropToWaitingUmpire($event)"
|
||||
[cdkDropListConnectedTo]="['list-on-rest', 'list-waiting-service-judges']">
|
||||
@for (umpire of waitingUmpires; track umpire.id) {
|
||||
<ion-item cdkDrag [cdkDragData]="umpire">
|
||||
[cdkDropListData]="waitingUmpires()"
|
||||
(cdkDropListDropped)="dropToWaitingUmpire($event)">
|
||||
@for (umpire of waitingUmpires(); track umpire.id) {
|
||||
<div class="custom-ion-item" cdkDrag [cdkDragData]="umpire">
|
||||
<div *cdkDragPlaceholder class="custom-ion-item primary">
|
||||
<ion-label>{{ umpire|fullname }}</ion-label>
|
||||
</div>
|
||||
<ion-label>{{ umpire|fullname }}</ion-label>
|
||||
</ion-item>
|
||||
} @empty {
|
||||
<ion-item>
|
||||
<ion-label class="ion-text-center">
|
||||
<ion-icon [color]="'primary'" name="add"></ion-icon>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</div>
|
||||
}
|
||||
</ion-list>
|
||||
</ion-col>
|
||||
@if (serviceJudgeEnabled()) {
|
||||
<ion-col>
|
||||
<ion-list
|
||||
[lines]="'none'"
|
||||
cdkDropList
|
||||
cdkDropListSortingDisabled
|
||||
id="list-waiting-service-judges"
|
||||
[cdkDropListData]="waitingServiceJudges"
|
||||
(cdkDropListDropped)="dropToWaitingServiceJudge($event)"
|
||||
[cdkDropListConnectedTo]="['list-on-rest', 'list-waiting-umpires']">
|
||||
@for (umpire of waitingServiceJudges; track umpire.id) {
|
||||
<ion-item cdkDrag [cdkDragData]="umpire">
|
||||
[cdkDropListData]="waitingServiceJudges()"
|
||||
(cdkDropListDropped)="dropToWaitingServiceJudge($event)">
|
||||
@for (umpire of waitingServiceJudges(); track umpire.id) {
|
||||
<div class="custom-ion-item" cdkDrag [cdkDragData]="umpire">
|
||||
<div *cdkDragPlaceholder class="custom-ion-item primary">
|
||||
<ion-label>{{ umpire|fullname }}</ion-label>
|
||||
</div>
|
||||
<ion-label>{{ umpire|fullname }}</ion-label>
|
||||
</ion-item>
|
||||
} @empty {
|
||||
<ion-item>
|
||||
<ion-label class="ion-text-center">
|
||||
<ion-icon [color]="'primary'" name="add"></ion-icon>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</div>
|
||||
}
|
||||
</ion-list>
|
||||
</ion-col>
|
||||
}
|
||||
<ion-col>
|
||||
<ion-list
|
||||
[lines]="'none'"
|
||||
@@ -83,12 +179,14 @@
|
||||
cdkDropListSortingDisabled
|
||||
id="list-on-rest"
|
||||
(cdkDropListDropped)="dropToRest($event)"
|
||||
[cdkDropListData]="onRest"
|
||||
[cdkDropListConnectedTo]="['list-waiting-service-judges', 'list-waiting-umpires']">
|
||||
@for (umpire of onRest; track umpire.id) {
|
||||
<ion-item cdkDrag [cdkDragData]="umpire">
|
||||
[cdkDropListData]="onRest()">
|
||||
@for (umpire of onRest(); track umpire.id) {
|
||||
<div class="custom-ion-item" cdkDrag [cdkDragData]="umpire">
|
||||
<div *cdkDragPlaceholder class="custom-ion-item primary">
|
||||
<ion-label>{{ umpire|fullname }}</ion-label>
|
||||
</div>
|
||||
<ion-label>{{ umpire|fullname }}</ion-label>
|
||||
</ion-item>
|
||||
</div>
|
||||
}
|
||||
</ion-list>
|
||||
</ion-col>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
.bottom-lists ion-list {
|
||||
min-height: 64px;
|
||||
}
|
||||
|
||||
.custom-ion-item {
|
||||
cursor: move;
|
||||
padding: 10px 15px;
|
||||
|
||||
&.primary {
|
||||
background-color: var(--ion-color-primary);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, effect, inject } from '@angular/core';
|
||||
import { Component, computed, inject } from '@angular/core';
|
||||
import {
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
@@ -10,29 +10,38 @@ import {
|
||||
IonList,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonIcon
|
||||
IonIcon,
|
||||
IonButton,
|
||||
AlertController
|
||||
} from '@ionic/angular/standalone';
|
||||
import { SettingsService } from '../services/settings-service';
|
||||
import { UmpireService } from '../services/umpire.service';
|
||||
import { Umpire, WaitingAsServiceJudge, WaitingAsUmpire } from 'db';
|
||||
import { Umpire } 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,
|
||||
CdkDragPlaceholder,
|
||||
CdkDropList,
|
||||
DragDropModule
|
||||
CdkDropListGroup
|
||||
} from '@angular/cdk/drag-drop';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CourtUmpireService } from '../services/court.umpire.service';
|
||||
import { CourtServiceJudgeService } from '../services/court.service.judge.service';
|
||||
import { addIcons } from 'ionicons';
|
||||
import { add } from 'ionicons/icons';
|
||||
import { enterOutline, exitOutline } from 'ionicons/icons';
|
||||
import { TranslatePipe } from '../i18n/translation.pipe';
|
||||
import { TranslationService } from '../i18n/translation.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tab1',
|
||||
templateUrl: 'tab1.page.html',
|
||||
styleUrls: ['tab1.page.scss'],
|
||||
providers: [FullnamePipe],
|
||||
imports: [
|
||||
IonButton,
|
||||
IonIcon,
|
||||
IonLabel,
|
||||
IonItem,
|
||||
@@ -48,69 +57,229 @@ import { add } from 'ionicons/icons';
|
||||
CdkDropList,
|
||||
CdkDrag,
|
||||
CommonModule,
|
||||
DragDropModule
|
||||
CdkDragPlaceholder,
|
||||
CdkDropListGroup,
|
||||
TranslatePipe
|
||||
]
|
||||
})
|
||||
export class Tab1Page {
|
||||
constructor() {
|
||||
addIcons({ exitOutline, enterOutline });
|
||||
}
|
||||
|
||||
readonly settingsService = inject(SettingsService);
|
||||
readonly umpireService = inject(UmpireService);
|
||||
readonly waitingUmpireService = inject(WaitingUmpiresService);
|
||||
readonly waitingServiceJudgeService = inject(WaitingServiceJudgesService);
|
||||
readonly courtUmpireService = inject(CourtUmpireService);
|
||||
readonly courtServiceJudgeService = inject(CourtServiceJudgeService);
|
||||
readonly translationService = inject(TranslationService);
|
||||
|
||||
public readonly settings = this.settingsService.settings;
|
||||
public readonly _umpires = this.umpireService.umpires;
|
||||
public readonly _waitingUmpires = this.waitingUmpireService.waitingUmpires;
|
||||
public readonly _waitingServiceJudges =
|
||||
private alertController = inject(AlertController);
|
||||
private fullnamePipe = inject(FullnamePipe);
|
||||
|
||||
/**
|
||||
* Raw signals from services
|
||||
*/
|
||||
readonly settings = this.settingsService.settings;
|
||||
|
||||
readonly _umpires = this.umpireService.umpires;
|
||||
|
||||
readonly _waitingUmpires = this.waitingUmpireService.waitingUmpires;
|
||||
|
||||
readonly _waitingServiceJudges =
|
||||
this.waitingServiceJudgeService.waitingServiceJudges;
|
||||
|
||||
public onRest: Umpire[] = [];
|
||||
public waitingUmpires: Umpire[] = [];
|
||||
public waitingServiceJudges: Umpire[] = [];
|
||||
readonly _courtUmpires = this.courtUmpireService.umpires;
|
||||
|
||||
constructor() {
|
||||
addIcons({ add });
|
||||
readonly _courtServiceJudges = this.courtServiceJudgeService.umpires;
|
||||
|
||||
effect(() => {
|
||||
this.onRest = this._umpires().filter((umpire) => {
|
||||
return (
|
||||
!this.isUmpireOnCourt(umpire) &&
|
||||
!this.isWaitingUmpire(umpire) &&
|
||||
!this.isWaitingServiceJudge(umpire)
|
||||
);
|
||||
});
|
||||
/**
|
||||
* Fast O(1) umpire lookup
|
||||
*/
|
||||
readonly umpireMap = computed(() => {
|
||||
return new Map<number, Umpire>(this._umpires().map((u) => [u.id, u]));
|
||||
});
|
||||
|
||||
this.waitingUmpires = this._waitingUmpires()
|
||||
.map((_wa) => {
|
||||
return this._umpires().find((u) => u.id === _wa.umpireId);
|
||||
})
|
||||
.filter((u) => typeof u !== 'undefined');
|
||||
/**
|
||||
* Waiting umpire IDs
|
||||
*/
|
||||
readonly waitingUmpireIds = computed(() => {
|
||||
return new Set(this._waitingUmpires().map((w) => w.umpireId));
|
||||
});
|
||||
|
||||
this.waitingServiceJudges = this._waitingServiceJudges()
|
||||
.map((_wsj) => {
|
||||
return this._umpires().find((u) => u.id === _wsj.serviceJudgeId);
|
||||
})
|
||||
.filter((u) => typeof u !== 'undefined');
|
||||
/**
|
||||
* Waiting service judge IDs
|
||||
*/
|
||||
readonly waitingServiceJudgeIds = computed(() => {
|
||||
return new Set(this._waitingServiceJudges().map((w) => w.serviceJudgeId));
|
||||
});
|
||||
|
||||
/**
|
||||
* Court umpire IDs
|
||||
*/
|
||||
readonly courtUmpireIds = computed(() => {
|
||||
return new Set(
|
||||
this._courtUmpires()
|
||||
.map((c) => c.umpireId)
|
||||
.filter((id): id is number => id !== null)
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Court service judge IDs
|
||||
*/
|
||||
readonly courtServiceJudgeIds = computed(() => {
|
||||
return new Set(
|
||||
this._courtServiceJudges()
|
||||
.map((c) => c.umpireId)
|
||||
.filter((id): id is number => id !== null)
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Umpires waiting as umpire
|
||||
*/
|
||||
readonly waitingUmpires = computed(() => {
|
||||
const umpireMap = this.umpireMap();
|
||||
|
||||
return this._waitingUmpires()
|
||||
.map((wu) => umpireMap.get(wu.umpireId))
|
||||
.filter((u): u is Umpire => !!u);
|
||||
});
|
||||
|
||||
/**
|
||||
* Umpires waiting as service judge
|
||||
*/
|
||||
readonly waitingServiceJudges = computed(() => {
|
||||
const umpireMap = this.umpireMap();
|
||||
|
||||
return this._waitingServiceJudges()
|
||||
.map((wsj) => umpireMap.get(wsj.serviceJudgeId))
|
||||
.filter((u): u is Umpire => !!u);
|
||||
});
|
||||
|
||||
/**
|
||||
* Court umpires
|
||||
*/
|
||||
readonly courtUmpires = computed(() => {
|
||||
const umpireMap = this.umpireMap();
|
||||
|
||||
return this._courtUmpires()
|
||||
.map((cu) => {
|
||||
if (cu.umpireId == null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return umpireMap.get(cu.umpireId);
|
||||
})
|
||||
.filter((u): u is Umpire => !!u);
|
||||
});
|
||||
|
||||
/**
|
||||
* Court service judges
|
||||
*/
|
||||
readonly courtServiceJudges = computed(() => {
|
||||
const umpireMap = this.umpireMap();
|
||||
|
||||
return this._courtServiceJudges()
|
||||
.map((csj) => {
|
||||
if (csj.umpireId == null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return umpireMap.get(csj.umpireId);
|
||||
})
|
||||
.filter((u): u is Umpire => !!u);
|
||||
});
|
||||
|
||||
/**
|
||||
* Umpires resting
|
||||
*/
|
||||
readonly onRest = computed(() => {
|
||||
const waitingUmpireIds = this.waitingUmpireIds();
|
||||
|
||||
const waitingServiceJudgeIds = this.waitingServiceJudgeIds();
|
||||
|
||||
const courtUmpireIds = this.courtUmpireIds();
|
||||
|
||||
const courtServiceJudgeIds = this.courtServiceJudgeIds();
|
||||
|
||||
return this._umpires().filter((umpire) => {
|
||||
return (
|
||||
!waitingUmpireIds.has(umpire.id) &&
|
||||
!waitingServiceJudgeIds.has(umpire.id) &&
|
||||
!courtUmpireIds.has(umpire.id) &&
|
||||
!courtServiceJudgeIds.has(umpire.id)
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
private isUmpireOnCourt(umpire: Umpire): boolean {
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Court -> umpire map
|
||||
*/
|
||||
readonly umpireByCourt = computed(() => {
|
||||
const map = new Map<number, Umpire>();
|
||||
|
||||
private isWaitingUmpire(umpire: Umpire): boolean {
|
||||
return (
|
||||
typeof this._waitingUmpires().find((wu) => wu.umpireId === umpire.id) !==
|
||||
'undefined'
|
||||
);
|
||||
}
|
||||
const umpireMap = this.umpireMap();
|
||||
|
||||
private isWaitingServiceJudge(umpire: Umpire): boolean {
|
||||
return (
|
||||
typeof this._waitingServiceJudges().find(
|
||||
(wsj) => wsj.serviceJudgeId === umpire.id
|
||||
) !== 'undefined'
|
||||
);
|
||||
}
|
||||
for (const item of this._courtUmpires()) {
|
||||
if (item.umpireId == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const umpire = umpireMap.get(item.umpireId);
|
||||
|
||||
if (!umpire) {
|
||||
continue;
|
||||
}
|
||||
|
||||
map.set(item.courtNo, umpire);
|
||||
}
|
||||
|
||||
return map;
|
||||
});
|
||||
|
||||
/**
|
||||
* Court -> service judge map
|
||||
*/
|
||||
readonly serviceJudgeByCourt = computed(() => {
|
||||
const map = new Map<number, Umpire>();
|
||||
|
||||
const umpireMap = this.umpireMap();
|
||||
|
||||
for (const item of this._courtServiceJudges()) {
|
||||
if (item.umpireId == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const umpire = umpireMap.get(item.umpireId);
|
||||
|
||||
if (!umpire) {
|
||||
continue;
|
||||
}
|
||||
|
||||
map.set(item.courtNo, umpire);
|
||||
}
|
||||
|
||||
return map;
|
||||
});
|
||||
|
||||
/**
|
||||
* Court indexes for template
|
||||
*/
|
||||
readonly courtIndexes = computed(() => {
|
||||
const count = this.settings()?.numberOfCourts ?? 0;
|
||||
|
||||
return Array.from({ length: count }, (_, i) => i + 1);
|
||||
});
|
||||
|
||||
/**
|
||||
* Is service judge functionality enabled in settings.
|
||||
*/
|
||||
readonly serviceJudgeEnabled = computed(() => {
|
||||
return this.settings()?.withServiceJudge ?? false;
|
||||
});
|
||||
|
||||
dropToRest(event: CdkDragDrop<Umpire[]>) {
|
||||
if (event.previousContainer === event.container) {
|
||||
@@ -118,49 +287,268 @@ export class Tab1Page {
|
||||
} 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);
|
||||
}
|
||||
this.removeFromOriginalPlace(umpireToMove, comingFrom);
|
||||
}
|
||||
}
|
||||
|
||||
dropToWaitingServiceJudge(event: CdkDragDrop<Umpire[]>) {
|
||||
const umpireToMove = event.item.data;
|
||||
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);
|
||||
}
|
||||
this.waitingServiceJudgeService.moveToPosition(
|
||||
umpireToMove.id,
|
||||
event.currentIndex + 1
|
||||
);
|
||||
return;
|
||||
}
|
||||
const comingFrom = event.previousContainer.id;
|
||||
|
||||
this.waitingServiceJudgeService.add(
|
||||
umpireToMove.id,
|
||||
event.currentIndex + 1
|
||||
);
|
||||
|
||||
this.removeFromOriginalPlace(umpireToMove, comingFrom);
|
||||
}
|
||||
|
||||
dropToWaitingUmpire(event: CdkDragDrop<Umpire[]>) {
|
||||
console.log(
|
||||
event.container.data,
|
||||
event.previousContainer.data,
|
||||
event.previousContainer.id,
|
||||
event.container.id
|
||||
);
|
||||
const umpireToMove = event.item.data;
|
||||
if (event.previousContainer === event.container) {
|
||||
// TODO
|
||||
this.waitingUmpireService.moveToPosition(
|
||||
umpireToMove.id,
|
||||
event.currentIndex + 1
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
const comingFrom = event.previousContainer.id;
|
||||
const umpireToMove = event.item.data;
|
||||
this.waitingUmpireService.add(umpireToMove.id);
|
||||
this.waitingUmpireService.add(umpireToMove.id, event.currentIndex + 1);
|
||||
|
||||
if ('list-waiting-service-judges' === comingFrom) {
|
||||
// Remove from waiting service judges
|
||||
this.waitingServiceJudgeService.removeByUmpireId(umpireToMove.id);
|
||||
}
|
||||
this.removeFromOriginalPlace(umpireToMove, comingFrom);
|
||||
}
|
||||
}
|
||||
|
||||
dropToUmpire(event: CdkDragDrop<(Umpire | undefined)[]>, courtNo: number) {
|
||||
const targetUmpires = event.container.data;
|
||||
|
||||
if (targetUmpires.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const comingFrom = event.previousContainer.id;
|
||||
const umpireToMove = event.item.data;
|
||||
this.courtUmpireService.save({ umpireId: umpireToMove.id, courtNo });
|
||||
|
||||
this.removeFromOriginalPlace(umpireToMove, comingFrom);
|
||||
}
|
||||
|
||||
dropToServiceJudge(
|
||||
event: CdkDragDrop<(Umpire | undefined)[]>,
|
||||
courtNo: number
|
||||
) {
|
||||
const targetUmpires = event.container.data;
|
||||
|
||||
if (targetUmpires.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const comingFrom = event.previousContainer.id;
|
||||
const umpireToMove = event.item.data;
|
||||
this.courtServiceJudgeService.save({ umpireId: umpireToMove.id, courtNo });
|
||||
|
||||
this.removeFromOriginalPlace(umpireToMove, comingFrom);
|
||||
}
|
||||
|
||||
private removeFromOriginalPlace(
|
||||
umpireToMove: Umpire,
|
||||
comingFrom: string
|
||||
): void {
|
||||
if ('list-waiting-service-judges' === comingFrom) {
|
||||
this.waitingServiceJudgeService.removeByUmpireId(umpireToMove.id);
|
||||
}
|
||||
|
||||
if ('list-waiting-umpires' === comingFrom) {
|
||||
this.waitingUmpireService.removeByUmpireId(umpireToMove.id);
|
||||
}
|
||||
|
||||
if (comingFrom.startsWith('court-umpire')) {
|
||||
this.courtUmpireService.removeByUmpireId(umpireToMove.id);
|
||||
}
|
||||
|
||||
if (comingFrom.startsWith('court-service-judge')) {
|
||||
this.courtServiceJudgeService.removeByUmpireId(umpireToMove.id);
|
||||
}
|
||||
}
|
||||
|
||||
canDropUmpire = (
|
||||
drag: CdkDrag<Umpire>,
|
||||
drop: CdkDropList<Umpire[]>
|
||||
): boolean => {
|
||||
return drop.data.length === 0;
|
||||
};
|
||||
|
||||
public async showRemoveConfirmation(courtNo: number) {
|
||||
const umpire = this.umpireByCourt().get(courtNo);
|
||||
const serviceJudge = this.serviceJudgeByCourt().get(courtNo);
|
||||
|
||||
if (
|
||||
typeof umpire === 'undefined' ||
|
||||
(typeof serviceJudge === 'undefined' && this.serviceJudgeEnabled())
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.settings()?.showAlert) {
|
||||
this.removeUmpiresFromCourt(courtNo);
|
||||
return;
|
||||
}
|
||||
|
||||
const alert = await this.alertController.create({
|
||||
header: this.translationService.translate('courtConfirm', {
|
||||
courtNo
|
||||
}),
|
||||
|
||||
subHeader: this.translationService.translate(
|
||||
serviceJudge ? 'removeUmpiresFromCourt' : 'removeUmpireFromCourt'
|
||||
),
|
||||
|
||||
cssClass: 'wide',
|
||||
|
||||
message: `${this.fullnamePipe.transform(umpire)}${
|
||||
serviceJudge ? ' - ' : ''
|
||||
}${this.fullnamePipe.transform(serviceJudge)}`,
|
||||
|
||||
buttons: [
|
||||
{
|
||||
text: this.translationService.translate('no'),
|
||||
role: 'cancel'
|
||||
},
|
||||
{
|
||||
text: this.translationService.translate('yes'),
|
||||
role: 'confirm',
|
||||
handler: () => {
|
||||
if (this.serviceJudgeEnabled()) {
|
||||
this.removeUmpiresFromCourt(courtNo);
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeUmpireFromCourt(courtNo);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
await alert.present();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is for removing both umpire and service judge from the court (when service judge is enabled).
|
||||
*/
|
||||
private async removeUmpiresFromCourt(courtNo: number) {
|
||||
const umpire = this.umpireByCourt().get(courtNo);
|
||||
const serviceJudge = this.serviceJudgeByCourt().get(courtNo);
|
||||
|
||||
if (typeof umpire === 'undefined' || typeof serviceJudge === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.courtUmpireService.removeByUmpireId(umpire.id);
|
||||
await this.courtServiceJudgeService.removeByUmpireId(serviceJudge.id);
|
||||
await this.waitingUmpireService.add(serviceJudge.id);
|
||||
await this.waitingServiceJudgeService.add(umpire.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is for removing 1 umpire only (when service judge is not enabled).
|
||||
*/
|
||||
private async removeUmpireFromCourt(courtNo: number) {
|
||||
const umpire = this.umpireByCourt().get(courtNo);
|
||||
|
||||
if (typeof umpire === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.courtUmpireService.removeByUmpireId(umpire.id);
|
||||
await this.waitingUmpireService.add(umpire.id);
|
||||
}
|
||||
|
||||
public async showAddConfirmation(courtNo: number) {
|
||||
const umpire = await this.waitingUmpireService.getCurrentUmpire();
|
||||
const serviceJudge =
|
||||
await this.waitingServiceJudgeService.getCurrentUmpire();
|
||||
|
||||
if (
|
||||
typeof umpire === 'undefined' ||
|
||||
(typeof serviceJudge === 'undefined' && this.serviceJudgeEnabled())
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.settings()?.showAlert) {
|
||||
if (this.serviceJudgeEnabled() && serviceJudge) {
|
||||
this.addUmpiresToCourt(courtNo, umpire, serviceJudge);
|
||||
return;
|
||||
}
|
||||
this.addUmpireToCourt(courtNo, umpire);
|
||||
return;
|
||||
}
|
||||
|
||||
const alert = await this.alertController.create({
|
||||
header: this.translationService.translate('court', {
|
||||
courtNo
|
||||
}),
|
||||
|
||||
cssClass: 'wide',
|
||||
|
||||
subHeader: this.translationService.translate(
|
||||
serviceJudge ? 'sendUmpiresToCourt' : 'sendUmpireToCourt'
|
||||
),
|
||||
|
||||
message: this.translationService.translate(
|
||||
serviceJudge ? 'sendUmpiresMessage' : 'sendUmpireMessage',
|
||||
{
|
||||
umpire: this.fullnamePipe.transform(umpire),
|
||||
serviceJudge: this.fullnamePipe.transform(serviceJudge)
|
||||
}
|
||||
),
|
||||
|
||||
buttons: [
|
||||
{
|
||||
text: this.translationService.translate('no'),
|
||||
role: 'cancel'
|
||||
},
|
||||
{
|
||||
text: this.translationService.translate('yes'),
|
||||
role: 'confirm',
|
||||
handler: () => {
|
||||
if (this.serviceJudgeEnabled() && serviceJudge) {
|
||||
this.addUmpiresToCourt(courtNo, umpire, serviceJudge);
|
||||
} else {
|
||||
this.addUmpireToCourt(courtNo, umpire);
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
await alert.present();
|
||||
}
|
||||
|
||||
private async addUmpiresToCourt(
|
||||
courtNo: number,
|
||||
umpire: Umpire,
|
||||
serviceJudge: Umpire
|
||||
) {
|
||||
await this.courtUmpireService.save({ courtNo, umpireId: umpire.id });
|
||||
await this.courtServiceJudgeService.save({
|
||||
courtNo,
|
||||
umpireId: serviceJudge.id
|
||||
});
|
||||
|
||||
await this.waitingUmpireService.removeByUmpireId(umpire.id);
|
||||
await this.waitingServiceJudgeService.removeByUmpireId(serviceJudge.id);
|
||||
}
|
||||
|
||||
private async addUmpireToCourt(courtNo: number, umpire: Umpire) {
|
||||
await this.courtUmpireService.save({ courtNo, umpireId: umpire.id });
|
||||
await this.waitingUmpireService.removeByUmpireId(umpire.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<ion-header [translucent]="true">
|
||||
<ion-toolbar>
|
||||
<ion-title> Játékvezetők </ion-title>
|
||||
<ion-title> {{ 'courtOfficials' | translate }} </ion-title>
|
||||
<ion-buttons [slot]="'end'">
|
||||
<ion-button [color]="'primary'" [fill]="'solid'" id="open-modal">
|
||||
<ion-icon slot="start" name="add"></ion-icon>
|
||||
Hozzáadás
|
||||
{{ 'add' | translate }}
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
@@ -32,7 +32,7 @@
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
} @empty {
|
||||
<ion-item> Nincsenek játékvezetők </ion-item>
|
||||
<ion-item> {{ 'noCourtOfficials' | translate }} </ion-item>
|
||||
}
|
||||
</ion-list>
|
||||
</ion-col>
|
||||
@@ -44,15 +44,17 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-button (click)="cancel()">Mégse</ion-button>
|
||||
<ion-button (click)="cancel()"
|
||||
>{{ 'cancel' | translate }}</ion-button
|
||||
>
|
||||
</ion-buttons>
|
||||
<ion-title>Új játékvezető</ion-title>
|
||||
<ion-title>{{ 'newCourtOfficial' | translate }}</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button
|
||||
[disabled]="!isValidUmpire()"
|
||||
(click)="confirm()"
|
||||
[strong]="true">
|
||||
Mentés
|
||||
{{ 'save' | translate }}
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
@@ -61,33 +63,36 @@
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-input
|
||||
label="Vezetéknév"
|
||||
[label]="'lastName' | translate"
|
||||
labelPlacement="stacked"
|
||||
type="text"
|
||||
placeholder="Vezetéknév"
|
||||
placeholder="{{ 'lastName' | translate }}"
|
||||
[(ngModel)]="lastName" />
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-input
|
||||
label="Keresztnév"
|
||||
[label]="'firstName' | translate"
|
||||
labelPlacement="stacked"
|
||||
type="text"
|
||||
placeholder="Keresztnév"
|
||||
placeholder="{{ 'firstName' | translate }}"
|
||||
[(ngModel)]="firstName" />
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-input
|
||||
label="Ország"
|
||||
[label]="'country' | translate"
|
||||
labelPlacement="stacked"
|
||||
type="text"
|
||||
placeholder="Ország"
|
||||
placeholder="{{ 'country' | translate }}"
|
||||
[(ngModel)]="country" />
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-select label="Nem" placeholder="Válassz" [(ngModel)]="gender">
|
||||
<ion-select
|
||||
[label]="'gender' | translate"
|
||||
placeholder="{{ 'gender' | translate }}"
|
||||
[(ngModel)]="gender">
|
||||
@for (item of genders; track item.code) {
|
||||
<ion-select-option [value]="item.code">
|
||||
{{item.label}}
|
||||
{{item.label | translate }}
|
||||
</ion-select-option>
|
||||
}
|
||||
</ion-select>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ViewChild } from '@angular/core';
|
||||
import { Component, inject, ViewChild } from '@angular/core';
|
||||
import {
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
@@ -26,6 +26,9 @@ import { add, trash } from 'ionicons/icons';
|
||||
import { OverlayEventDetail } from '@ionic/core/components';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { FullnamePipe } from '../fullname-pipe';
|
||||
import { TranslatePipe } from '../i18n/translation.pipe';
|
||||
import { TranslationKey } from '../i18n/translations';
|
||||
import { TranslationService } from '../i18n/translation.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tab2',
|
||||
@@ -51,12 +54,19 @@ import { FullnamePipe } from '../fullname-pipe';
|
||||
IonSelect,
|
||||
IonSelectOption,
|
||||
IonList,
|
||||
FullnamePipe
|
||||
FullnamePipe,
|
||||
TranslatePipe
|
||||
]
|
||||
})
|
||||
export class Tab2Page {
|
||||
readonly translationService = inject(TranslationService);
|
||||
|
||||
@ViewChild(IonModal) modal!: IonModal;
|
||||
|
||||
public readonly umpireService = inject(UmpireService);
|
||||
public readonly fullnamePipe = inject(FullnamePipe);
|
||||
public readonly alertController = inject(AlertController);
|
||||
|
||||
public readonly umpires = this.umpireService.umpires;
|
||||
|
||||
public lastName: string = '';
|
||||
@@ -64,16 +74,12 @@ export class Tab2Page {
|
||||
public country: string = '';
|
||||
public gender: string = '';
|
||||
|
||||
public genders = [
|
||||
{ label: 'gender.male', code: 'M' },
|
||||
{ label: 'gender.female', code: 'W' }
|
||||
public genders: { label: TranslationKey; code: string }[] = [
|
||||
{ label: 'male', code: 'M' },
|
||||
{ label: 'female', code: 'W' }
|
||||
];
|
||||
|
||||
constructor(
|
||||
private umpireService: UmpireService,
|
||||
private alertController: AlertController,
|
||||
private fullnamePipe: FullnamePipe
|
||||
) {
|
||||
constructor() {
|
||||
addIcons({ add, trash });
|
||||
}
|
||||
|
||||
@@ -112,18 +118,18 @@ export class Tab2Page {
|
||||
return this.lastName !== '' && this.firstName !== '';
|
||||
}
|
||||
|
||||
public async showDeleteConfirmation(umpire: Umpire) {
|
||||
public async showDeleteConfirmation(umpire: Umpire): Promise<void> {
|
||||
const alert = await this.alertController.create({
|
||||
header: 'Megerősítés',
|
||||
subHeader: 'Biztos törlöd a következő játékvezetőt?',
|
||||
header: this.translationService.translate('confirmation'),
|
||||
subHeader: this.translationService.translate('confirmDeleteUmpire'),
|
||||
message: this.fullnamePipe.transform(umpire),
|
||||
buttons: [
|
||||
{
|
||||
text: 'Nem',
|
||||
text: this.translationService.translate('no'),
|
||||
role: 'cancel'
|
||||
},
|
||||
{
|
||||
text: 'Igen',
|
||||
text: this.translationService.translate('yes'),
|
||||
role: 'confirm',
|
||||
handler: () => {
|
||||
// TODO: also remove from first tab
|
||||
|
||||
@@ -1,17 +1,65 @@
|
||||
<ion-header [translucent]="true">
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>
|
||||
Tab 3
|
||||
</ion-title>
|
||||
<ion-title>{{ 'settings' | translate }}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content [fullscreen]="true">
|
||||
<ion-header collapse="condense">
|
||||
<ion-toolbar>
|
||||
<ion-title size="large">Tab 3</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-grid [fixed]="true">
|
||||
<ion-row>
|
||||
<ion-col sizeLg="6" pushLg="3">
|
||||
@if (settings(); as currentSettings) {
|
||||
|
||||
<app-explore-container name="Tab 3 page"></app-explore-container>
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-select
|
||||
[label]="'language' | translate"
|
||||
[value]="translationService.language()"
|
||||
(ionChange)="updateLanguage($event)"
|
||||
interface="popover">
|
||||
<ion-select-option value="en"> English </ion-select-option>
|
||||
|
||||
<ion-select-option value="hu"> Magyar </ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-toggle
|
||||
[checked]="currentSettings.withServiceJudge"
|
||||
(ionChange)="updateWithServiceJudge($event)">
|
||||
{{ 'serviceJudge' | translate }}
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-toggle
|
||||
[checked]="currentSettings.showAlert"
|
||||
(ionChange)="updateShowAlert($event)">
|
||||
{{ 'showAlerts' | translate }}
|
||||
</ion-toggle>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-input
|
||||
[label]="'numberOfCourts' | translate"
|
||||
type="number"
|
||||
min="1"
|
||||
labelPlacement="floating"
|
||||
[value]="currentSettings.numberOfCourts"
|
||||
(ionInput)="updateNumberOfCourts($event)">
|
||||
</ion-input>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
}
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
<ion-row>
|
||||
<ion-col sizeLg="6" pushLg="3">
|
||||
<ion-item>
|
||||
<ion-label class="ion-text-center">{{ 'version' | translate }}: 1.0.0</ion-label>
|
||||
</ion-item>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</ion-content>
|
||||
|
||||
@@ -1,13 +1,107 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone';
|
||||
import { ExploreContainerComponent } from '../explore-container/explore-container.component';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import {
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
IonList,
|
||||
IonItem,
|
||||
IonToggle,
|
||||
IonInput,
|
||||
IonGrid,
|
||||
IonCol,
|
||||
IonRow,
|
||||
IonSelect,
|
||||
IonSelectOption,
|
||||
IonLabel
|
||||
} from '@ionic/angular/standalone';
|
||||
import { SettingsService } from '../services/settings-service';
|
||||
import { CourtUmpireService } from '../services/court.umpire.service';
|
||||
import { CourtServiceJudgeService } from '../services/court.service.judge.service';
|
||||
import { CourtServiceJudge } from 'db';
|
||||
import { WaitingServiceJudgesService } from '../services/waiting-service-judges.service';
|
||||
import { TranslationService } from '../i18n/translation.service';
|
||||
import { Language } from '../i18n/translations';
|
||||
import { TranslatePipe } from '../i18n/translation.pipe';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tab3',
|
||||
templateUrl: 'tab3.page.html',
|
||||
styleUrls: ['tab3.page.scss'],
|
||||
imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent],
|
||||
imports: [
|
||||
IonRow,
|
||||
IonCol,
|
||||
IonGrid,
|
||||
IonInput,
|
||||
IonToggle,
|
||||
IonItem,
|
||||
IonList,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonContent,
|
||||
IonSelect,
|
||||
IonSelectOption,
|
||||
TranslatePipe,
|
||||
IonLabel
|
||||
]
|
||||
})
|
||||
export class Tab3Page {
|
||||
constructor() {}
|
||||
readonly settingsService = inject(SettingsService);
|
||||
readonly courtUmpireService = inject(CourtUmpireService);
|
||||
readonly courtServiceJudgeService = inject(CourtServiceJudgeService);
|
||||
readonly waitingServiceJudgeService = inject(WaitingServiceJudgesService);
|
||||
readonly translationService = inject(TranslationService);
|
||||
|
||||
readonly settings = this.settingsService.settings;
|
||||
|
||||
async updateNumberOfCourts(event: CustomEvent): Promise<void> {
|
||||
this.courtUmpireService
|
||||
.umpires()
|
||||
.filter((cu) => {
|
||||
return cu.courtNo > Number(event.detail.value);
|
||||
})
|
||||
.map((cu) => {
|
||||
this.courtUmpireService.delete(cu.id);
|
||||
});
|
||||
|
||||
this.courtServiceJudgeService
|
||||
.umpires()
|
||||
.filter((cu) => {
|
||||
return cu.courtNo > Number(event.detail.value);
|
||||
})
|
||||
.map((cu) => {
|
||||
this.courtServiceJudgeService.delete(cu.id);
|
||||
});
|
||||
|
||||
await this.settingsService.update({
|
||||
numberOfCourts: Number(event.detail.value)
|
||||
});
|
||||
}
|
||||
|
||||
async updateWithServiceJudge(event: CustomEvent): Promise<void> {
|
||||
await this.settingsService.update({
|
||||
withServiceJudge: event.detail.checked
|
||||
});
|
||||
|
||||
if (!event.detail.checked) {
|
||||
this.courtServiceJudgeService.umpires().map((csj: CourtServiceJudge) => {
|
||||
this.courtServiceJudgeService.delete(csj.id);
|
||||
});
|
||||
|
||||
this.waitingServiceJudgeService.waitingServiceJudges().map((wsj) => {
|
||||
this.waitingServiceJudgeService.remove(wsj.id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async updateShowAlert(event: CustomEvent): Promise<void> {
|
||||
await this.settingsService.update({
|
||||
showAlert: event.detail.checked
|
||||
});
|
||||
}
|
||||
|
||||
updateLanguage(event: CustomEvent): void {
|
||||
this.translationService.setLanguage(event.detail.value as Language);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
<ion-tabs>
|
||||
<ion-tab-bar slot="bottom">
|
||||
<ion-tab-button tab="pilot" href="/tabs/pilot">
|
||||
<ion-icon aria-hidden="true" name="triangle"></ion-icon>
|
||||
<ion-label>Pályák</ion-label>
|
||||
<ion-icon aria-hidden="true" name="grid-outline"></ion-icon>
|
||||
<ion-label>{{ 'courts' | translate }}</ion-label>
|
||||
</ion-tab-button>
|
||||
|
||||
<ion-tab-button tab="umpires" href="/tabs/umpires">
|
||||
<ion-icon aria-hidden="true" name="ellipse"></ion-icon>
|
||||
<ion-label>Játékvezetők</ion-label>
|
||||
<ion-icon aria-hidden="true" name="people-outline"></ion-icon>
|
||||
<ion-label>{{ 'courtOfficials' | translate }}</ion-label>
|
||||
</ion-tab-button>
|
||||
|
||||
<ion-tab-button tab="stats" href="/tabs/stats">
|
||||
<ion-icon aria-hidden="true" name="square"></ion-icon>
|
||||
<ion-label>Tab 3</ion-label>
|
||||
</ion-tab-button>
|
||||
|
||||
<ion-tab-button tab="stats" href="/tabs/settings">
|
||||
<ion-icon aria-hidden="true" name="square"></ion-icon>
|
||||
<ion-label>Beállítások</ion-label>
|
||||
<ion-tab-button tab="settings" href="/tabs/settings">
|
||||
<ion-icon aria-hidden="true" name="settings-outline"></ion-icon>
|
||||
<ion-label>{{ 'settings' | translate }}</ion-label>
|
||||
</ion-tab-button>
|
||||
</ion-tab-bar>
|
||||
</ion-tabs>
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
import { Component, EnvironmentInjector, inject } from '@angular/core';
|
||||
import { IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel } from '@ionic/angular/standalone';
|
||||
import {
|
||||
IonTabs,
|
||||
IonTabBar,
|
||||
IonTabButton,
|
||||
IonIcon,
|
||||
IonLabel
|
||||
} from '@ionic/angular/standalone';
|
||||
import { addIcons } from 'ionicons';
|
||||
import { triangle, ellipse, square } from 'ionicons/icons';
|
||||
import { gridOutline, peopleOutline, settingsOutline } from 'ionicons/icons';
|
||||
import { TranslatePipe } from '../i18n/translation.pipe';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tabs',
|
||||
templateUrl: 'tabs.page.html',
|
||||
styleUrls: ['tabs.page.scss'],
|
||||
imports: [IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel],
|
||||
imports: [IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel, TranslatePipe]
|
||||
})
|
||||
export class TabsPage {
|
||||
public environmentInjector = inject(EnvironmentInjector);
|
||||
|
||||
constructor() {
|
||||
addIcons({ triangle, ellipse, square });
|
||||
addIcons({ gridOutline, peopleOutline, settingsOutline });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export const routes: Routes = [
|
||||
loadComponent: () => import('../tab2/tab2.page').then((m) => m.Tab2Page)
|
||||
},
|
||||
{
|
||||
path: 'stats',
|
||||
path: 'settings',
|
||||
loadComponent: () => import('../tab3/tab3.page').then((m) => m.Tab3Page)
|
||||
},
|
||||
{
|
||||
|
||||
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 346 B |
|
After Width: | Height: | Size: 690 B |
|
Before Width: | Height: | Size: 930 B |
@@ -35,3 +35,13 @@
|
||||
/* @import "@ionic/angular/css/palettes/dark.always.css"; */
|
||||
/* @import "@ionic/angular/css/palettes/dark.class.css"; */
|
||||
@import '@ionic/angular/css/palettes/dark.system.css';
|
||||
|
||||
.wide.sc-ion-alert-md-h {
|
||||
.sc-ion-alert-md {
|
||||
--backdrop-opacity: 0.8;
|
||||
}
|
||||
|
||||
&.sc-ion-alert-md-h {
|
||||
--min-width: 400px;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Ionic App</title>
|
||||
<title>Court Pilot</title>
|
||||
|
||||
<base href="/" />
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta name="msapplication-tap-highlight" content="no" />
|
||||
|
||||
<link rel="icon" type="image/png" href="assets/icon/favicon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="assets/icon/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="assets/icon/favicon-16x16.png">
|
||||
|
||||
<!-- add to homescreen for ios -->
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
// For information on how to create your own theme, please refer to:
|
||||
// https://ionicframework.com/docs/theming/
|
||||
@import '@ionic/angular/css/palettes/dark.always.css';
|
||||
@@ -18,7 +18,7 @@
|
||||
"importHelpers": true,
|
||||
"target": "es2022",
|
||||
"module": "es2020",
|
||||
"lib": ["es2018", "dom"],
|
||||
"lib": ["es2021", "dom"],
|
||||
"skipLibCheck": true,
|
||||
"useDefineForClassFields": false
|
||||
},
|
||||
|
||||