6 Commits
69 changed files with 4019 additions and 1757 deletions
+3601 -1663
View File
File diff suppressed because it is too large Load Diff
+19 -18
View File
@@ -9,19 +9,20 @@
"build": "ng build", "build": "ng build",
"watch": "ng build --watch --configuration development", "watch": "ng build --watch --configuration development",
"test": "ng test", "test": "ng test",
"lint": "ng lint" "lint": "ng lint",
"tauri": "tauri"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^20.0.0", "@angular/animations": "^20.3.28",
"@angular/cdk": "^20.2.14", "@angular/cdk": "^20.2.14",
"@angular/common": "^20.0.0", "@angular/common": "^20.3.28",
"@angular/compiler": "^20.0.0", "@angular/compiler": "^20.3.28",
"@angular/core": "^20.0.0", "@angular/core": "^20.3.28",
"@angular/forms": "^20.0.0", "@angular/forms": "^20.3.28",
"@angular/platform-browser": "^20.0.0", "@angular/platform-browser": "^20.3.28",
"@angular/platform-browser-dynamic": "^20.0.0", "@angular/platform-browser-dynamic": "^20.3.28",
"@angular/router": "^20.0.0", "@angular/router": "^20.3.28",
"@capacitor/app": "8.1.0", "@capacitor/app": "8.1.0",
"@capacitor/core": "8.3.4", "@capacitor/core": "8.3.4",
"@capacitor/haptics": "8.0.2", "@capacitor/haptics": "8.0.2",
@@ -35,15 +36,15 @@
"zone.js": "~0.15.0" "zone.js": "~0.15.0"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^20.0.0", "@angular-devkit/build-angular": "^20.3.34",
"@angular-eslint/builder": "^20.0.0", "@angular-eslint/builder": "^20.3.0",
"@angular-eslint/eslint-plugin": "^20.0.0", "@angular-eslint/eslint-plugin": "^20.3.0",
"@angular-eslint/eslint-plugin-template": "^20.0.0", "@angular-eslint/eslint-plugin-template": "^20.3.0",
"@angular-eslint/schematics": "^20.0.0", "@angular-eslint/schematics": "^20.3.0",
"@angular-eslint/template-parser": "^20.0.0", "@angular-eslint/template-parser": "^20.3.0",
"@angular/cli": "^20.0.0", "@angular/cli": "^20.3.34",
"@angular/compiler-cli": "^20.0.0", "@angular/compiler-cli": "^20.3.28",
"@angular/language-service": "^20.0.0", "@angular/language-service": "^20.3.28",
"@capacitor/cli": "8.3.4", "@capacitor/cli": "8.3.4",
"@ionic/angular-toolkit": "^12.0.0", "@ionic/angular-toolkit": "^12.0.0",
"@tauri-apps/cli": "^2.11.2", "@tauri-apps/cli": "^2.11.2",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 838 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

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>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

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>
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 751 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

+3 -3
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "court-pilot", "productName": "court-pilot",
"version": "0.1.0", "version": "1.0.0",
"identifier": "com.tauri.dev", "identifier": "com.tauri.dev",
"build": { "build": {
"frontendDist": "../wwww", "frontendDist": "../wwww",
@@ -15,8 +15,8 @@
"minHeight": 600, "minHeight": 600,
"minWidth": 800, "minWidth": 800,
"title": "Court Pilot", "title": "Court Pilot",
"width": 800, "width": 1366,
"height": 600, "height": 768,
"resizable": true, "resizable": true,
"fullscreen": false "fullscreen": false
} }
+11 -3
View File
@@ -1,16 +1,24 @@
import { Pipe, PipeTransform } from '@angular/core'; import { inject, Pipe, PipeTransform } from '@angular/core';
import { Umpire } from 'db'; import { Umpire } from 'db';
import { TranslationService } from './i18n/translation.service';
@Pipe({ @Pipe({
name: 'fullname', name: 'fullname',
standalone: true standalone: true,
pure: false
}) })
export class FullnamePipe implements PipeTransform { export class FullnamePipe implements PipeTransform {
readonly translationService = inject(TranslationService);
transform(value: Umpire | undefined, ...args: unknown[]): string { transform(value: Umpire | undefined, ...args: unknown[]): string {
if (typeof value === 'undefined') { if (typeof value === 'undefined') {
return ''; return '';
} }
// TODO: in case of multilang, change here
if (this.translationService.language() === 'en') {
return value.firstName + ' ' + value.lastName;
}
return value.lastName + ' ' + value.firstName; return value.lastName + ' ' + value.firstName;
} }
} }
+16
View File
@@ -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);
}
}
+47
View File
@@ -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';
}
}
+88
View File
@@ -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>;
+25 -15
View File
@@ -1,34 +1,36 @@
<ion-header [translucent]="true"> <ion-header [translucent]="true">
<ion-toolbar> <ion-toolbar>
<ion-title> Pályák </ion-title> <ion-title> {{ 'courts' | translate }} </ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content [fullscreen]="true" cdkDropListGroup> <ion-content [fullscreen]="true" cdkDropListGroup>
<ion-header collapse="condense"> <ion-header collapse="condense">
<ion-toolbar> <ion-toolbar>
<ion-title size="large">Pályák</ion-title> <ion-title size="large">{{ 'courts' | translate }}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-grid [fixed]="'fixed'" class="bottom-lists"> <ion-grid [fixed]="'fixed'" class="bottom-lists">
<ion-row> <ion-row>
<ion-col [size]="1" class="ion-display-none ion-display-xl-block"> <ion-col [size]="1" class="ion-display-none ion-display-xl-block">
<ion-item [lines]="'none'">Pálya</ion-item> <ion-item [lines]="'none'">{{ 'court' | translate }}</ion-item>
</ion-col> </ion-col>
<ion-col [size]="1" class="ion-display-xl-none"> <ion-col [size]="1" class="ion-display-xl-none">
<ion-item [lines]="'none'">P</ion-item> <ion-item [lines]="'none'">{{ 'shortCourtName' | translate }}</ion-item>
</ion-col> </ion-col>
<ion-col> <ion-col>
<ion-item [lines]="'none'"> <ion-item [lines]="'none'">
<ion-label class="ion-text-center"> Játékvezető </ion-label> <ion-label class="ion-text-center"> {{ 'umpire' | translate }} </ion-label>
</ion-item> </ion-item>
</ion-col> </ion-col>
@if (serviceJudgeEnabled()) {
<ion-col> <ion-col>
<ion-item [lines]="'none'"> <ion-item [lines]="'none'">
<ion-label class="ion-text-center"> Adogatásbíró </ion-label> <ion-label class="ion-text-center"> {{ 'serviceJudge' | translate }} </ion-label>
</ion-item> </ion-item>
</ion-col> </ion-col>
}
<ion-col size="2" sizeLg="1"></ion-col> <ion-col size="2" sizeLg="1"></ion-col>
</ion-row> </ion-row>
@for (item of [].constructor(settings()?.numberOfCourts); track $index) { @for (item of [].constructor(settings()?.numberOfCourts); track $index) {
@@ -58,6 +60,7 @@
} }
</ion-list> </ion-list>
</ion-col> </ion-col>
@if (serviceJudgeEnabled()) {
<ion-col> <ion-col>
<ion-list <ion-list
[lines]="'none'" [lines]="'none'"
@@ -77,11 +80,13 @@
} }
</ion-list> </ion-list>
</ion-col> </ion-col>
}
<ion-col size="2" sizeLg="1"> <ion-col size="2" sizeLg="1">
<ion-list> <ion-list>
<ion-item lines="none"> <ion-item lines="none">
@if (umpireByCourt().get($index + 1) && @if ( (umpireByCourt().get($index + 1) &&
serviceJudgeByCourt().get($index + 1)) { serviceJudgeByCourt().get($index + 1)) ||
(umpireByCourt().get($index + 1) && !serviceJudgeEnabled()) ) {
<ion-button <ion-button
[color]="'danger'" [color]="'danger'"
(click)="showRemoveConfirmation($index + 1)"> (click)="showRemoveConfirmation($index + 1)">
@@ -90,9 +95,10 @@
size="large" size="large"
name="exit-outline"></ion-icon> name="exit-outline"></ion-icon>
</ion-button> </ion-button>
} @else if (!umpireByCourt().get($index + 1) && } @else if ((!umpireByCourt().get($index + 1) &&
!serviceJudgeByCourt().get($index + 1) && waitingUmpires().length && !serviceJudgeByCourt().get($index + 1) && waitingUmpires().length &&
waitingServiceJudges().length) { waitingServiceJudges().length) || (!umpireByCourt().get($index + 1)
&& waitingUmpires().length && !serviceJudgeEnabled())) {
<ion-button <ion-button
[color]="'success'" [color]="'success'"
[disabled]="" [disabled]=""
@@ -111,19 +117,21 @@
</ion-grid> </ion-grid>
<ion-grid [fixed]="'fixed'" class="ion-margin-top bottom-lists"> <ion-grid [fixed]="'fixed'" class="ion-margin-top bottom-lists">
<ion-row> <ion-row>
<ion-col [size]="4"> <ion-col [size]="!serviceJudgeEnabled() ? 6 : 4">
<ion-item color="primary"> <ion-item color="primary">
<ion-label class="ion-text-center"> Játékvezetők </ion-label> <ion-label class="ion-text-center"> {{ 'umpires' | translate }} </ion-label>
</ion-item> </ion-item>
</ion-col> </ion-col>
@if (serviceJudgeEnabled()) {
<ion-col [size]="4"> <ion-col [size]="4">
<ion-item color="primary"> <ion-item color="primary">
<ion-label class="ion-text-center"> Adogatásbírók </ion-label> <ion-label class="ion-text-center"> {{ 'serviceJudges' | translate }} </ion-label>
</ion-item> </ion-item>
</ion-col> </ion-col>
<ion-col [size]="4"> }
<ion-col [size]="!serviceJudgeEnabled() ? 6 : 4">
<ion-item color="medium"> <ion-item color="medium">
<ion-label class="ion-text-center"> Pihenők </ion-label> <ion-label class="ion-text-center"> {{ 'notOnDuty' | translate }} </ion-label>
</ion-item> </ion-item>
</ion-col> </ion-col>
</ion-row> </ion-row>
@@ -145,6 +153,7 @@
} }
</ion-list> </ion-list>
</ion-col> </ion-col>
@if (serviceJudgeEnabled()) {
<ion-col> <ion-col>
<ion-list <ion-list
[lines]="'none'" [lines]="'none'"
@@ -162,6 +171,7 @@
} }
</ion-list> </ion-list>
</ion-col> </ion-col>
}
<ion-col> <ion-col>
<ion-list <ion-list
[lines]="'none'" [lines]="'none'"
+99 -13
View File
@@ -32,6 +32,8 @@ import { CourtUmpireService } from '../services/court.umpire.service';
import { CourtServiceJudgeService } from '../services/court.service.judge.service'; import { CourtServiceJudgeService } from '../services/court.service.judge.service';
import { addIcons } from 'ionicons'; import { addIcons } from 'ionicons';
import { enterOutline, exitOutline } from 'ionicons/icons'; import { enterOutline, exitOutline } from 'ionicons/icons';
import { TranslatePipe } from '../i18n/translation.pipe';
import { TranslationService } from '../i18n/translation.service';
@Component({ @Component({
selector: 'app-tab1', selector: 'app-tab1',
@@ -56,7 +58,8 @@ import { enterOutline, exitOutline } from 'ionicons/icons';
CdkDrag, CdkDrag,
CommonModule, CommonModule,
CdkDragPlaceholder, CdkDragPlaceholder,
CdkDropListGroup CdkDropListGroup,
TranslatePipe
] ]
}) })
export class Tab1Page { export class Tab1Page {
@@ -70,6 +73,7 @@ export class Tab1Page {
readonly waitingServiceJudgeService = inject(WaitingServiceJudgesService); readonly waitingServiceJudgeService = inject(WaitingServiceJudgesService);
readonly courtUmpireService = inject(CourtUmpireService); readonly courtUmpireService = inject(CourtUmpireService);
readonly courtServiceJudgeService = inject(CourtServiceJudgeService); readonly courtServiceJudgeService = inject(CourtServiceJudgeService);
readonly translationService = inject(TranslationService);
private alertController = inject(AlertController); private alertController = inject(AlertController);
private fullnamePipe = inject(FullnamePipe); private fullnamePipe = inject(FullnamePipe);
@@ -270,6 +274,13 @@ export class Tab1Page {
return Array.from({ length: count }, (_, i) => i + 1); 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[]>) { dropToRest(event: CdkDragDrop<Umpire[]>) {
if (event.previousContainer === event.container) { if (event.previousContainer === event.container) {
return; return;
@@ -378,25 +389,48 @@ export class Tab1Page {
const umpire = this.umpireByCourt().get(courtNo); const umpire = this.umpireByCourt().get(courtNo);
const serviceJudge = this.serviceJudgeByCourt().get(courtNo); const serviceJudge = this.serviceJudgeByCourt().get(courtNo);
if (typeof umpire === 'undefined' || typeof serviceJudge === 'undefined') { if (
typeof umpire === 'undefined' ||
(typeof serviceJudge === 'undefined' && this.serviceJudgeEnabled())
) {
return;
}
if (!this.settings()?.showAlert) {
this.removeUmpiresFromCourt(courtNo);
return; return;
} }
const alert = await this.alertController.create({ const alert = await this.alertController.create({
header: `Pálya ${courtNo}`, header: this.translationService.translate('courtConfirm', {
subHeader: 'Biztos leveszed őket pályáról?', courtNo
}),
subHeader: this.translationService.translate(
serviceJudge ? 'removeUmpiresFromCourt' : 'removeUmpireFromCourt'
),
cssClass: 'wide', cssClass: 'wide',
message: `${this.fullnamePipe.transform(umpire)} - ${this.fullnamePipe.transform(serviceJudge)}`,
message: `${this.fullnamePipe.transform(umpire)}${
serviceJudge ? ' - ' : ''
}${this.fullnamePipe.transform(serviceJudge)}`,
buttons: [ buttons: [
{ {
text: 'Nem', text: this.translationService.translate('no'),
role: 'cancel' role: 'cancel'
}, },
{ {
text: 'Igen', text: this.translationService.translate('yes'),
role: 'confirm', role: 'confirm',
handler: () => { handler: () => {
if (this.serviceJudgeEnabled()) {
this.removeUmpiresFromCourt(courtNo); this.removeUmpiresFromCourt(courtNo);
return;
}
this.removeUmpireFromCourt(courtNo);
} }
} }
] ]
@@ -405,6 +439,9 @@ export class Tab1Page {
await alert.present(); 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) { private async removeUmpiresFromCourt(courtNo: number) {
const umpire = this.umpireByCourt().get(courtNo); const umpire = this.umpireByCourt().get(courtNo);
const serviceJudge = this.serviceJudgeByCourt().get(courtNo); const serviceJudge = this.serviceJudgeByCourt().get(courtNo);
@@ -419,30 +456,74 @@ export class Tab1Page {
await this.waitingServiceJudgeService.add(umpire.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) { public async showAddConfirmation(courtNo: number) {
const umpire = await this.waitingUmpireService.getCurrentUmpire(); const umpire = await this.waitingUmpireService.getCurrentUmpire();
const serviceJudge = const serviceJudge =
await this.waitingServiceJudgeService.getCurrentUmpire(); await this.waitingServiceJudgeService.getCurrentUmpire();
if (typeof umpire === 'undefined' || typeof serviceJudge === 'undefined') { 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; return;
} }
const alert = await this.alertController.create({ const alert = await this.alertController.create({
header: `Pálya ${courtNo}`, header: this.translationService.translate('court', {
courtNo
}),
cssClass: 'wide', cssClass: 'wide',
subHeader: 'Biztos pályára küldöd őket?',
message: `${this.fullnamePipe.transform(umpire)} - ${this.fullnamePipe.transform(serviceJudge)}`, 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: [ buttons: [
{ {
text: 'Nem', text: this.translationService.translate('no'),
role: 'cancel' role: 'cancel'
}, },
{ {
text: 'Igen', text: this.translationService.translate('yes'),
role: 'confirm', role: 'confirm',
handler: () => { handler: () => {
if (this.serviceJudgeEnabled() && serviceJudge) {
this.addUmpiresToCourt(courtNo, umpire, serviceJudge); this.addUmpiresToCourt(courtNo, umpire, serviceJudge);
} else {
this.addUmpireToCourt(courtNo, umpire);
}
} }
} }
] ]
@@ -465,4 +546,9 @@ export class Tab1Page {
await this.waitingUmpireService.removeByUmpireId(umpire.id); await this.waitingUmpireService.removeByUmpireId(umpire.id);
await this.waitingServiceJudgeService.removeByUmpireId(serviceJudge.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);
}
} }
+19 -14
View File
@@ -1,10 +1,10 @@
<ion-header [translucent]="true"> <ion-header [translucent]="true">
<ion-toolbar> <ion-toolbar>
<ion-title> Játékvezetők </ion-title> <ion-title> {{ 'courtOfficials' | translate }} </ion-title>
<ion-buttons [slot]="'end'"> <ion-buttons [slot]="'end'">
<ion-button [color]="'primary'" [fill]="'solid'" id="open-modal"> <ion-button [color]="'primary'" [fill]="'solid'" id="open-modal">
<ion-icon slot="start" name="add"></ion-icon> <ion-icon slot="start" name="add"></ion-icon>
Hozzáadás {{ 'add' | translate }}
</ion-button> </ion-button>
</ion-buttons> </ion-buttons>
</ion-toolbar> </ion-toolbar>
@@ -32,7 +32,7 @@
</ion-button> </ion-button>
</ion-item> </ion-item>
} @empty { } @empty {
<ion-item> Nincsenek játékvezetők </ion-item> <ion-item> {{ 'noCourtOfficials' | translate }} </ion-item>
} }
</ion-list> </ion-list>
</ion-col> </ion-col>
@@ -44,15 +44,17 @@
<ion-header> <ion-header>
<ion-toolbar> <ion-toolbar>
<ion-buttons slot="start"> <ion-buttons slot="start">
<ion-button (click)="cancel()">Mégse</ion-button> <ion-button (click)="cancel()"
>{{ 'cancel' | translate }}</ion-button
>
</ion-buttons> </ion-buttons>
<ion-title>Új játékvezető</ion-title> <ion-title>{{ 'newCourtOfficial' | translate }}</ion-title>
<ion-buttons slot="end"> <ion-buttons slot="end">
<ion-button <ion-button
[disabled]="!isValidUmpire()" [disabled]="!isValidUmpire()"
(click)="confirm()" (click)="confirm()"
[strong]="true"> [strong]="true">
Mentés {{ 'save' | translate }}
</ion-button> </ion-button>
</ion-buttons> </ion-buttons>
</ion-toolbar> </ion-toolbar>
@@ -61,33 +63,36 @@
<ion-list> <ion-list>
<ion-item> <ion-item>
<ion-input <ion-input
label="Vezetéknév" [label]="'lastName' | translate"
labelPlacement="stacked" labelPlacement="stacked"
type="text" type="text"
placeholder="Vezetéknév" placeholder="{{ 'lastName' | translate }}"
[(ngModel)]="lastName" /> [(ngModel)]="lastName" />
</ion-item> </ion-item>
<ion-item> <ion-item>
<ion-input <ion-input
label="Keresztnév" [label]="'firstName' | translate"
labelPlacement="stacked" labelPlacement="stacked"
type="text" type="text"
placeholder="Keresztnév" placeholder="{{ 'firstName' | translate }}"
[(ngModel)]="firstName" /> [(ngModel)]="firstName" />
</ion-item> </ion-item>
<ion-item> <ion-item>
<ion-input <ion-input
label="Ország" [label]="'country' | translate"
labelPlacement="stacked" labelPlacement="stacked"
type="text" type="text"
placeholder="Ország" placeholder="{{ 'country' | translate }}"
[(ngModel)]="country" /> [(ngModel)]="country" />
</ion-item> </ion-item>
<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) { @for (item of genders; track item.code) {
<ion-select-option [value]="item.code"> <ion-select-option [value]="item.code">
{{item.label}} {{item.label | translate }}
</ion-select-option> </ion-select-option>
} }
</ion-select> </ion-select>
+21 -15
View File
@@ -1,4 +1,4 @@
import { Component, ViewChild } from '@angular/core'; import { Component, inject, ViewChild } from '@angular/core';
import { import {
IonHeader, IonHeader,
IonToolbar, IonToolbar,
@@ -26,6 +26,9 @@ import { add, trash } from 'ionicons/icons';
import { OverlayEventDetail } from '@ionic/core/components'; import { OverlayEventDetail } from '@ionic/core/components';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { FullnamePipe } from '../fullname-pipe'; import { FullnamePipe } from '../fullname-pipe';
import { TranslatePipe } from '../i18n/translation.pipe';
import { TranslationKey } from '../i18n/translations';
import { TranslationService } from '../i18n/translation.service';
@Component({ @Component({
selector: 'app-tab2', selector: 'app-tab2',
@@ -51,12 +54,19 @@ import { FullnamePipe } from '../fullname-pipe';
IonSelect, IonSelect,
IonSelectOption, IonSelectOption,
IonList, IonList,
FullnamePipe FullnamePipe,
TranslatePipe
] ]
}) })
export class Tab2Page { export class Tab2Page {
readonly translationService = inject(TranslationService);
@ViewChild(IonModal) modal!: IonModal; @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 readonly umpires = this.umpireService.umpires;
public lastName: string = ''; public lastName: string = '';
@@ -64,16 +74,12 @@ export class Tab2Page {
public country: string = ''; public country: string = '';
public gender: string = ''; public gender: string = '';
public genders = [ public genders: { label: TranslationKey; code: string }[] = [
{ label: 'gender.male', code: 'M' }, { label: 'male', code: 'M' },
{ label: 'gender.female', code: 'W' } { label: 'female', code: 'W' }
]; ];
constructor( constructor() {
private umpireService: UmpireService,
private alertController: AlertController,
private fullnamePipe: FullnamePipe
) {
addIcons({ add, trash }); addIcons({ add, trash });
} }
@@ -112,18 +118,18 @@ export class Tab2Page {
return this.lastName !== '' && this.firstName !== ''; return this.lastName !== '' && this.firstName !== '';
} }
public async showDeleteConfirmation(umpire: Umpire) { public async showDeleteConfirmation(umpire: Umpire): Promise<void> {
const alert = await this.alertController.create({ const alert = await this.alertController.create({
header: 'Megerősítés', header: this.translationService.translate('confirmation'),
subHeader: 'Biztos törlöd a következő játékvezetőt?', subHeader: this.translationService.translate('confirmDeleteUmpire'),
message: this.fullnamePipe.transform(umpire), message: this.fullnamePipe.transform(umpire),
buttons: [ buttons: [
{ {
text: 'Nem', text: this.translationService.translate('no'),
role: 'cancel' role: 'cancel'
}, },
{ {
text: 'Igen', text: this.translationService.translate('yes'),
role: 'confirm', role: 'confirm',
handler: () => { handler: () => {
// TODO: also remove from first tab // TODO: also remove from first tab
+23 -4
View File
@@ -1,6 +1,6 @@
<ion-header> <ion-header>
<ion-toolbar> <ion-toolbar>
<ion-title>Beállítások</ion-title> <ion-title>{{ 'settings' | translate }}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
@@ -11,11 +11,23 @@
@if (settings(); as currentSettings) { @if (settings(); as currentSettings) {
<ion-list> <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-item>
<ion-toggle <ion-toggle
[checked]="currentSettings.withServiceJudge" [checked]="currentSettings.withServiceJudge"
(ionChange)="updateWithServiceJudge($event)"> (ionChange)="updateWithServiceJudge($event)">
Adogatásbíró {{ 'serviceJudge' | translate }}
</ion-toggle> </ion-toggle>
</ion-item> </ion-item>
@@ -23,13 +35,13 @@
<ion-toggle <ion-toggle
[checked]="currentSettings.showAlert" [checked]="currentSettings.showAlert"
(ionChange)="updateShowAlert($event)"> (ionChange)="updateShowAlert($event)">
Figyelmeztetések megjelenítése a gyors gomboknál {{ 'showAlerts' | translate }}
</ion-toggle> </ion-toggle>
</ion-item> </ion-item>
<ion-item> <ion-item>
<ion-input <ion-input
label="Pályák száma" [label]="'numberOfCourts' | translate"
type="number" type="number"
min="1" min="1"
labelPlacement="floating" labelPlacement="floating"
@@ -42,5 +54,12 @@
} }
</ion-col> </ion-col>
</ion-row> </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-grid>
</ion-content> </ion-content>
+30 -2
View File
@@ -10,11 +10,19 @@ import {
IonInput, IonInput,
IonGrid, IonGrid,
IonCol, IonCol,
IonRow IonRow,
IonSelect,
IonSelectOption,
IonLabel
} from '@ionic/angular/standalone'; } from '@ionic/angular/standalone';
import { SettingsService } from '../services/settings-service'; import { SettingsService } from '../services/settings-service';
import { CourtUmpireService } from '../services/court.umpire.service'; import { CourtUmpireService } from '../services/court.umpire.service';
import { CourtServiceJudgeService } from '../services/court.service.judge.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({ @Component({
selector: 'app-tab3', selector: 'app-tab3',
@@ -31,13 +39,19 @@ import { CourtServiceJudgeService } from '../services/court.service.judge.servic
IonHeader, IonHeader,
IonToolbar, IonToolbar,
IonTitle, IonTitle,
IonContent IonContent,
IonSelect,
IonSelectOption,
TranslatePipe,
IonLabel
] ]
}) })
export class Tab3Page { export class Tab3Page {
readonly settingsService = inject(SettingsService); readonly settingsService = inject(SettingsService);
readonly courtUmpireService = inject(CourtUmpireService); readonly courtUmpireService = inject(CourtUmpireService);
readonly courtServiceJudgeService = inject(CourtServiceJudgeService); readonly courtServiceJudgeService = inject(CourtServiceJudgeService);
readonly waitingServiceJudgeService = inject(WaitingServiceJudgesService);
readonly translationService = inject(TranslationService);
readonly settings = this.settingsService.settings; readonly settings = this.settingsService.settings;
@@ -69,6 +83,16 @@ export class Tab3Page {
await this.settingsService.update({ await this.settingsService.update({
withServiceJudge: event.detail.checked 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> { async updateShowAlert(event: CustomEvent): Promise<void> {
@@ -76,4 +100,8 @@ export class Tab3Page {
showAlert: event.detail.checked showAlert: event.detail.checked
}); });
} }
updateLanguage(event: CustomEvent): void {
this.translationService.setLanguage(event.detail.value as Language);
}
} }
+3 -3
View File
@@ -2,17 +2,17 @@
<ion-tab-bar slot="bottom"> <ion-tab-bar slot="bottom">
<ion-tab-button tab="pilot" href="/tabs/pilot"> <ion-tab-button tab="pilot" href="/tabs/pilot">
<ion-icon aria-hidden="true" name="grid-outline"></ion-icon> <ion-icon aria-hidden="true" name="grid-outline"></ion-icon>
<ion-label>Pályák</ion-label> <ion-label>{{ 'courts' | translate }}</ion-label>
</ion-tab-button> </ion-tab-button>
<ion-tab-button tab="umpires" href="/tabs/umpires"> <ion-tab-button tab="umpires" href="/tabs/umpires">
<ion-icon aria-hidden="true" name="people-outline"></ion-icon> <ion-icon aria-hidden="true" name="people-outline"></ion-icon>
<ion-label>Játékvezetők</ion-label> <ion-label>{{ 'courtOfficials' | translate }}</ion-label>
</ion-tab-button> </ion-tab-button>
<ion-tab-button tab="settings" href="/tabs/settings"> <ion-tab-button tab="settings" href="/tabs/settings">
<ion-icon aria-hidden="true" name="settings-outline"></ion-icon> <ion-icon aria-hidden="true" name="settings-outline"></ion-icon>
<ion-label>Beállítások</ion-label> <ion-label>{{ 'settings' | translate }}</ion-label>
</ion-tab-button> </ion-tab-button>
</ion-tab-bar> </ion-tab-bar>
</ion-tabs> </ion-tabs>
+2 -1
View File
@@ -8,12 +8,13 @@ import {
} from '@ionic/angular/standalone'; } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons'; import { addIcons } from 'ionicons';
import { gridOutline, peopleOutline, settingsOutline } from 'ionicons/icons'; import { gridOutline, peopleOutline, settingsOutline } from 'ionicons/icons';
import { TranslatePipe } from '../i18n/translation.pipe';
@Component({ @Component({
selector: 'app-tabs', selector: 'app-tabs',
templateUrl: 'tabs.page.html', templateUrl: 'tabs.page.html',
styleUrls: ['tabs.page.scss'], styleUrls: ['tabs.page.scss'],
imports: [IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel] imports: [IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel, TranslatePipe]
}) })
export class TabsPage { export class TabsPage {
public environmentInjector = inject(EnvironmentInjector); public environmentInjector = inject(EnvironmentInjector);
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

+1 -1
View File
@@ -18,7 +18,7 @@
"importHelpers": true, "importHelpers": true,
"target": "es2022", "target": "es2022",
"module": "es2020", "module": "es2020",
"lib": ["es2018", "dom"], "lib": ["es2021", "dom"],
"skipLibCheck": true, "skipLibCheck": true,
"useDefineForClassFields": false "useDefineForClassFields": false
}, },