Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 56b9a186d4 | |||
| 0f46cb3c93 | |||
| 08d64bbaaf | |||
| a79c082d45 |
@@ -0,0 +1,28 @@
|
|||||||
|
import Dexie, { type EntityTable } from 'dexie';
|
||||||
|
|
||||||
|
export interface Umpire {
|
||||||
|
id: number;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
country: string;
|
||||||
|
gender: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Court {
|
||||||
|
id: number;
|
||||||
|
umpireId: number | null;
|
||||||
|
serviceJudgeId: number | null;
|
||||||
|
order: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = new Dexie('CourtPilot') as Dexie & {
|
||||||
|
umpires: EntityTable<Umpire, 'id'>;
|
||||||
|
courts: EntityTable<Court, 'id'>;
|
||||||
|
};
|
||||||
|
|
||||||
|
db.version(1).stores({
|
||||||
|
umpires: '++id, lastName',
|
||||||
|
courts: '++id, umpireId, serviceJudgeId'
|
||||||
|
});
|
||||||
|
|
||||||
|
export { db };
|
||||||
Generated
+7
@@ -22,6 +22,7 @@
|
|||||||
"@capacitor/keyboard": "8.0.3",
|
"@capacitor/keyboard": "8.0.3",
|
||||||
"@capacitor/status-bar": "8.0.2",
|
"@capacitor/status-bar": "8.0.2",
|
||||||
"@ionic/angular": "^8.0.0",
|
"@ionic/angular": "^8.0.0",
|
||||||
|
"dexie": "^4.4.2",
|
||||||
"ionicons": "^7.0.0",
|
"ionicons": "^7.0.0",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
@@ -7977,6 +7978,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/dexie": {
|
||||||
|
"version": "4.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/dexie/-/dexie-4.4.2.tgz",
|
||||||
|
"integrity": "sha512-zMtV8q79EFE5U8FKZvt0Y/77PCU/Hr/RDxv1EDeo228L+m/HTbeN2AjoQm674rhQCX8n3ljK87lajt7UQuZfvw==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/di": {
|
"node_modules/di": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"@capacitor/keyboard": "8.0.3",
|
"@capacitor/keyboard": "8.0.3",
|
||||||
"@capacitor/status-bar": "8.0.2",
|
"@capacitor/status-bar": "8.0.2",
|
||||||
"@ionic/angular": "^8.0.0",
|
"@ionic/angular": "^8.0.0",
|
||||||
|
"dexie": "^4.4.2",
|
||||||
"ionicons": "^7.0.0",
|
"ionicons": "^7.0.0",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { FullnamePipe } from './fullname-pipe';
|
||||||
|
|
||||||
|
describe('FullnamePipePipe', () => {
|
||||||
|
it('create an instance', () => {
|
||||||
|
const pipe = new FullnamePipe();
|
||||||
|
expect(pipe).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
import { Umpire } from 'db';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'fullname',
|
||||||
|
standalone: true
|
||||||
|
})
|
||||||
|
export class FullnamePipe implements PipeTransform {
|
||||||
|
transform(value: Umpire, ...args: unknown[]): string {
|
||||||
|
// TODO: in case of multilang, change here
|
||||||
|
return value.lastName + ' ' + value.firstName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { UmpireService } from './umpire.service';
|
||||||
|
|
||||||
|
describe('Umpire', () => {
|
||||||
|
let service: UmpireService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(UmpireService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { db } from 'db';
|
||||||
|
import { liveQuery } from 'dexie';
|
||||||
|
import { from } from 'rxjs';
|
||||||
|
import { Umpire } from '../../../db';
|
||||||
|
import { toSignal } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class UmpireService {
|
||||||
|
/**
|
||||||
|
* Reactive signal with all umpires
|
||||||
|
*/
|
||||||
|
readonly umpires = toSignal(
|
||||||
|
from(liveQuery(() => db.umpires.orderBy('lastName').toArray())),
|
||||||
|
{
|
||||||
|
initialValue: []
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create
|
||||||
|
*/
|
||||||
|
async create(umpire: Omit<Umpire, 'id'>): Promise<number> {
|
||||||
|
return await db.umpires.add({
|
||||||
|
...umpire
|
||||||
|
} as Umpire);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read one
|
||||||
|
*/
|
||||||
|
async getById(id: number): Promise<Umpire | undefined> {
|
||||||
|
return await db.umpires.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update
|
||||||
|
*/
|
||||||
|
async update(
|
||||||
|
id: number,
|
||||||
|
changes: Partial<Omit<Umpire, 'id'>>
|
||||||
|
): Promise<number> {
|
||||||
|
return await db.umpires.update(id, changes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete
|
||||||
|
*/
|
||||||
|
async delete(id: number): Promise<void> {
|
||||||
|
await db.umpires.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete all
|
||||||
|
*/
|
||||||
|
async clear(): Promise<void> {
|
||||||
|
await db.umpires.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,99 @@
|
|||||||
<ion-header [translucent]="true">
|
<ion-header [translucent]="true">
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-title>
|
<ion-title> Játékvezetők </ion-title>
|
||||||
Tab 2
|
<ion-buttons [slot]="'end'">
|
||||||
</ion-title>
|
<ion-button [color]="'primary'" [fill]="'solid'" id="open-modal">
|
||||||
|
<ion-icon slot="start" name="add"></ion-icon>
|
||||||
|
Hozzáadás
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content [fullscreen]="true">
|
<ion-content [fullscreen]="true">
|
||||||
<ion-header collapse="condense">
|
<ion-header collapse="condense">
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-title size="large">Tab 2</ion-title>
|
<ion-title size="large">Játékvezetők</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<app-explore-container name="Tab 2 page"></app-explore-container>
|
<ion-grid>
|
||||||
|
<ion-row>
|
||||||
|
<ion-col size="12" sizeMd="6" pushMd="3">
|
||||||
|
<ion-list>
|
||||||
|
@for (umpire of umpires(); track umpire.id) {
|
||||||
|
<ion-item>
|
||||||
|
<ion-label> {{ umpire | fullname }} </ion-label>
|
||||||
|
<ion-button
|
||||||
|
[color]="'danger'"
|
||||||
|
slot="end"
|
||||||
|
(click)="showDeleteConfirmation(umpire)">
|
||||||
|
<ion-icon slot="icon-only" name="trash"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-item>
|
||||||
|
} @empty {
|
||||||
|
<ion-item> Nincsenek játékvezetők </ion-item>
|
||||||
|
}
|
||||||
|
</ion-list>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</ion-grid>
|
||||||
|
|
||||||
|
<ion-modal trigger="open-modal" (willDismiss)="onWillDismiss($event)">
|
||||||
|
<ng-template>
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-button (click)="cancel()">Mégse</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>Új játékvezető</ion-title>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button
|
||||||
|
[disabled]="!isValidUmpire()"
|
||||||
|
(click)="confirm()"
|
||||||
|
[strong]="true">
|
||||||
|
Mentés
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content class="ion-padding">
|
||||||
|
<ion-list>
|
||||||
|
<ion-item>
|
||||||
|
<ion-input
|
||||||
|
label="Vezetéknév"
|
||||||
|
labelPlacement="stacked"
|
||||||
|
type="text"
|
||||||
|
placeholder="Vezetéknév"
|
||||||
|
[(ngModel)]="lastName" />
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-input
|
||||||
|
label="Keresztnév"
|
||||||
|
labelPlacement="stacked"
|
||||||
|
type="text"
|
||||||
|
placeholder="Keresztnév"
|
||||||
|
[(ngModel)]="firstName" />
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-input
|
||||||
|
label="Ország"
|
||||||
|
labelPlacement="stacked"
|
||||||
|
type="text"
|
||||||
|
placeholder="Ország"
|
||||||
|
[(ngModel)]="country" />
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-select label="Nem" placeholder="Válassz" [(ngModel)]="gender">
|
||||||
|
@for (item of genders; track item.code) {
|
||||||
|
<ion-select-option [value]="item.code">
|
||||||
|
{{item.label}}
|
||||||
|
</ion-select-option>
|
||||||
|
}
|
||||||
|
</ion-select>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
</ion-content>
|
||||||
|
</ng-template>
|
||||||
|
</ion-modal>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
+132
-5
@@ -1,15 +1,142 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component, ViewChild } from '@angular/core';
|
||||||
import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone';
|
import {
|
||||||
import { ExploreContainerComponent } from '../explore-container/explore-container.component';
|
IonHeader,
|
||||||
|
IonToolbar,
|
||||||
|
IonTitle,
|
||||||
|
IonContent,
|
||||||
|
IonButtons,
|
||||||
|
IonButton,
|
||||||
|
IonIcon,
|
||||||
|
IonModal,
|
||||||
|
IonItem,
|
||||||
|
IonInput,
|
||||||
|
IonSelect,
|
||||||
|
IonSelectOption,
|
||||||
|
IonList,
|
||||||
|
IonGrid,
|
||||||
|
IonRow,
|
||||||
|
IonCol,
|
||||||
|
IonLabel,
|
||||||
|
AlertController
|
||||||
|
} from '@ionic/angular/standalone';
|
||||||
|
import { UmpireService } from '../services/umpire.service';
|
||||||
|
import { Umpire } from 'db';
|
||||||
|
import { addIcons } from 'ionicons';
|
||||||
|
import { add, trash } from 'ionicons/icons';
|
||||||
|
import { OverlayEventDetail } from '@ionic/core/components';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { FullnamePipe } from '../fullname-pipe';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tab2',
|
selector: 'app-tab2',
|
||||||
templateUrl: 'tab2.page.html',
|
templateUrl: 'tab2.page.html',
|
||||||
styleUrls: ['tab2.page.scss'],
|
styleUrls: ['tab2.page.scss'],
|
||||||
imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent]
|
providers: [FullnamePipe],
|
||||||
|
imports: [
|
||||||
|
IonLabel,
|
||||||
|
IonCol,
|
||||||
|
IonRow,
|
||||||
|
IonGrid,
|
||||||
|
IonInput,
|
||||||
|
IonItem,
|
||||||
|
IonModal,
|
||||||
|
IonIcon,
|
||||||
|
IonButton,
|
||||||
|
IonButtons,
|
||||||
|
IonHeader,
|
||||||
|
IonToolbar,
|
||||||
|
IonTitle,
|
||||||
|
IonContent,
|
||||||
|
FormsModule,
|
||||||
|
IonSelect,
|
||||||
|
IonSelectOption,
|
||||||
|
IonList,
|
||||||
|
FullnamePipe
|
||||||
|
]
|
||||||
})
|
})
|
||||||
export class Tab2Page {
|
export class Tab2Page {
|
||||||
|
@ViewChild(IonModal) modal!: IonModal;
|
||||||
|
|
||||||
constructor() {}
|
public readonly umpires = this.umpireService.umpires;
|
||||||
|
|
||||||
|
public lastName: string = '';
|
||||||
|
public firstName: string = '';
|
||||||
|
public country: string = '';
|
||||||
|
public gender: string = '';
|
||||||
|
|
||||||
|
public genders = [
|
||||||
|
{ label: 'gender.male', code: 'M' },
|
||||||
|
{ label: 'gender.female', code: 'W' }
|
||||||
|
];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private umpireService: UmpireService,
|
||||||
|
private alertController: AlertController,
|
||||||
|
private fullnamePipe: FullnamePipe
|
||||||
|
) {
|
||||||
|
addIcons({ add, trash });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async onWillDismiss(event: CustomEvent<OverlayEventDetail>) {
|
||||||
|
if (event.detail.role !== 'confirm') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newUmpire: Umpire = event.detail.data;
|
||||||
|
|
||||||
|
await this.umpireService.create({
|
||||||
|
firstName: newUmpire.firstName,
|
||||||
|
lastName: newUmpire.lastName,
|
||||||
|
country: newUmpire.country,
|
||||||
|
gender: newUmpire.gender
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
this.modal.dismiss(null, 'cancel');
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm() {
|
||||||
|
this.modal.dismiss(
|
||||||
|
{
|
||||||
|
lastName: this.lastName,
|
||||||
|
firstName: this.firstName,
|
||||||
|
country: this.country,
|
||||||
|
gender: this.gender
|
||||||
|
},
|
||||||
|
'confirm'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isValidUmpire(): boolean {
|
||||||
|
return this.lastName !== '' && this.firstName !== '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public async showDeleteConfirmation(umpire: Umpire) {
|
||||||
|
const alert = await this.alertController.create({
|
||||||
|
header: 'Megerősítés',
|
||||||
|
subHeader: 'Biztos törlöd a következő játékvezetőt?',
|
||||||
|
message: this.fullnamePipe.transform(umpire),
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: 'Nem',
|
||||||
|
role: 'cancel'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Igen',
|
||||||
|
role: 'confirm',
|
||||||
|
handler: () => {
|
||||||
|
// TODO: also remove from first tab
|
||||||
|
this.removeUmpire(umpire);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
await alert.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeUmpire(umpire: Umpire): void {
|
||||||
|
this.umpireService.delete(umpire.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
<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="triangle"></ion-icon>
|
<ion-icon aria-hidden="true" name="triangle"></ion-icon>
|
||||||
<ion-label>Tab 1</ion-label>
|
<ion-label>Pályák</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="ellipse"></ion-icon>
|
<ion-icon aria-hidden="true" name="ellipse"></ion-icon>
|
||||||
<ion-label>Tab 2</ion-label>
|
<ion-label>Játékvezetők</ion-label>
|
||||||
</ion-tab-button>
|
</ion-tab-button>
|
||||||
|
|
||||||
<ion-tab-button tab="stats" href="/tabs/stats">
|
<ion-tab-button tab="stats" href="/tabs/stats">
|
||||||
|
|||||||
Reference in New Issue
Block a user