8 Commits
80 changed files with 4143 additions and 1849 deletions
+1
View File
@@ -37,6 +37,7 @@ export interface Settings {
id: number;
withServiceJudge: boolean;
numberOfCourts: number;
showAlert: boolean;
}
const db = new Dexie('CourtPilot') as Dexie & {
+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",
"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",
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",
"productName": "court-pilot",
"version": "0.1.0",
"version": "1.0.0",
"identifier": "com.tauri.dev",
"build": {
"frontendDist": "../wwww",
@@ -15,8 +15,8 @@
"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;
}
+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 { TranslationService } from './i18n/translation.service';
@Pipe({
name: 'fullname',
standalone: true
standalone: true,
pure: false
})
export class FullnamePipe implements PipeTransform {
readonly translationService = inject(TranslationService);
transform(value: Umpire | undefined, ...args: unknown[]): string {
if (typeof value === 'undefined') {
return '';
}
// TODO: in case of multilang, change here
if (this.translationService.language() === 'en') {
return value.firstName + ' ' + value.lastName;
}
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>;
+2 -1
View File
@@ -39,7 +39,8 @@ export class SettingsService {
await db.settings.put({
id: 1,
numberOfCourts: 5,
withServiceJudge: true
withServiceJudge: true,
showAlert: true
});
}
}
+25 -15
View File
@@ -1,34 +1,36 @@
<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" 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'" class="bottom-lists">
<ion-row>
<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 [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-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-col>
@if (serviceJudgeEnabled()) {
<ion-col>
<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-col>
}
<ion-col size="2" sizeLg="1"></ion-col>
</ion-row>
@for (item of [].constructor(settings()?.numberOfCourts); track $index) {
@@ -58,6 +60,7 @@
}
</ion-list>
</ion-col>
@if (serviceJudgeEnabled()) {
<ion-col>
<ion-list
[lines]="'none'"
@@ -77,11 +80,13 @@
}
</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)) {
@if ( (umpireByCourt().get($index + 1) &&
serviceJudgeByCourt().get($index + 1)) ||
(umpireByCourt().get($index + 1) && !serviceJudgeEnabled()) ) {
<ion-button
[color]="'danger'"
(click)="showRemoveConfirmation($index + 1)">
@@ -90,9 +95,10 @@
size="large"
name="exit-outline"></ion-icon>
</ion-button>
} @else if (!umpireByCourt().get($index + 1) &&
} @else if ((!umpireByCourt().get($index + 1) &&
!serviceJudgeByCourt().get($index + 1) && waitingUmpires().length &&
waitingServiceJudges().length) {
waitingServiceJudges().length) || (!umpireByCourt().get($index + 1)
&& waitingUmpires().length && !serviceJudgeEnabled())) {
<ion-button
[color]="'success'"
[disabled]=""
@@ -111,19 +117,21 @@
</ion-grid>
<ion-grid [fixed]="'fixed'" class="ion-margin-top bottom-lists">
<ion-row>
<ion-col [size]="4">
<ion-col [size]="!serviceJudgeEnabled() ? 6 : 4">
<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-col>
@if (serviceJudgeEnabled()) {
<ion-col [size]="4">
<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-col>
<ion-col [size]="4">
}
<ion-col [size]="!serviceJudgeEnabled() ? 6 : 4">
<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-col>
</ion-row>
@@ -145,6 +153,7 @@
}
</ion-list>
</ion-col>
@if (serviceJudgeEnabled()) {
<ion-col>
<ion-list
[lines]="'none'"
@@ -162,6 +171,7 @@
}
</ion-list>
</ion-col>
}
<ion-col>
<ion-list
[lines]="'none'"
+101 -15
View File
@@ -32,6 +32,8 @@ import { CourtUmpireService } from '../services/court.umpire.service';
import { CourtServiceJudgeService } from '../services/court.service.judge.service';
import { addIcons } from 'ionicons';
import { enterOutline, exitOutline } from 'ionicons/icons';
import { TranslatePipe } from '../i18n/translation.pipe';
import { TranslationService } from '../i18n/translation.service';
@Component({
selector: 'app-tab1',
@@ -56,7 +58,8 @@ import { enterOutline, exitOutline } from 'ionicons/icons';
CdkDrag,
CommonModule,
CdkDragPlaceholder,
CdkDropListGroup
CdkDropListGroup,
TranslatePipe
]
})
export class Tab1Page {
@@ -70,6 +73,7 @@ export class Tab1Page {
readonly waitingServiceJudgeService = inject(WaitingServiceJudgesService);
readonly courtUmpireService = inject(CourtUmpireService);
readonly courtServiceJudgeService = inject(CourtServiceJudgeService);
readonly translationService = inject(TranslationService);
private alertController = inject(AlertController);
private fullnamePipe = inject(FullnamePipe);
@@ -270,6 +274,13 @@ export class Tab1Page {
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) {
return;
@@ -378,25 +389,48 @@ export class Tab1Page {
const umpire = this.umpireByCourt().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;
}
const alert = await this.alertController.create({
header: `Pálya ${courtNo}`,
subHeader: 'Biztos leveszed őket pályáról?',
header: this.translationService.translate('courtConfirm', {
courtNo
}),
subHeader: this.translationService.translate(
serviceJudge ? 'removeUmpiresFromCourt' : 'removeUmpireFromCourt'
),
cssClass: 'wide',
message: `${this.fullnamePipe.transform(umpire)} - ${this.fullnamePipe.transform(serviceJudge)}`,
message: `${this.fullnamePipe.transform(umpire)}${
serviceJudge ? ' - ' : ''
}${this.fullnamePipe.transform(serviceJudge)}`,
buttons: [
{
text: 'Nem',
text: this.translationService.translate('no'),
role: 'cancel'
},
{
text: 'Igen',
text: this.translationService.translate('yes'),
role: 'confirm',
handler: () => {
this.removeUmpiresFromCourt(courtNo);
if (this.serviceJudgeEnabled()) {
this.removeUmpiresFromCourt(courtNo);
return;
}
this.removeUmpireFromCourt(courtNo);
}
}
]
@@ -405,6 +439,9 @@ export class Tab1Page {
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);
@@ -419,30 +456,74 @@ export class Tab1Page {
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') {
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: `Pálya ${courtNo}`,
header: this.translationService.translate('court', {
courtNo
}),
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: [
{
text: 'Nem',
text: this.translationService.translate('no'),
role: 'cancel'
},
{
text: 'Igen',
text: this.translationService.translate('yes'),
role: 'confirm',
handler: () => {
this.addUmpiresToCourt(courtNo, umpire, serviceJudge);
if (this.serviceJudgeEnabled() && 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.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-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>
+21 -15
View File
@@ -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
+59 -11
View File
@@ -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>
+99 -5
View File
@@ -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);
}
}
+6 -11
View File
@@ -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-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-icon aria-hidden="true" name="people-outline"></ion-icon>
<ion-label>{{ 'courtOfficials' | translate }}</ion-label>
</ion-tab-button>
<ion-tab-button tab="settings" href="/tabs/settings">
<ion-icon aria-hidden="true" name="square"></ion-icon>
<ion-label>Beállítások</ion-label>
<ion-icon aria-hidden="true" name="settings-outline"></ion-icon>
<ion-label>{{ 'settings' | translate }}</ion-label>
</ion-tab-button>
</ion-tab-bar>
</ion-tabs>
+11 -4
View File
@@ -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 });
}
}
+1 -1
View File
@@ -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)
},
{
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 930 B

+3 -2
View File
@@ -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 -1
View File
@@ -18,7 +18,7 @@
"importHelpers": true,
"target": "es2022",
"module": "es2020",
"lib": ["es2018", "dom"],
"lib": ["es2021", "dom"],
"skipLibCheck": true,
"useDefineForClassFields": false
},