Compare commits

..

8 Commits

22 changed files with 627 additions and 177 deletions
+1
View File
@@ -37,6 +37,7 @@ export interface Settings {
id: number; id: number;
withServiceJudge: boolean; withServiceJudge: boolean;
numberOfCourts: number; numberOfCourts: number;
showAlert: boolean;
} }
const db = new Dexie('CourtPilot') as Dexie & { const db = new Dexie('CourtPilot') as Dexie & {
@@ -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;
}
@@ -45,4 +45,17 @@ export class CourtServiceJudgeService {
async delete(id: number): Promise<void> { async delete(id: number): Promise<void> {
await db.courtServiceJudges.delete(id); 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);
}
} }
+2 -1
View File
@@ -39,7 +39,8 @@ export class SettingsService {
await db.settings.put({ await db.settings.put({
id: 1, id: 1,
numberOfCourts: 5, numberOfCourts: 5,
withServiceJudge: true withServiceJudge: true,
showAlert: true
}); });
} }
} }
@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop'; import { toSignal } from '@angular/core/rxjs-interop';
import { db } from 'db'; import { db, Umpire } from 'db';
import { liveQuery } from 'dexie'; import { liveQuery } from 'dexie';
import { from } from 'rxjs'; import { from } from 'rxjs';
@@ -21,8 +21,8 @@ export class WaitingServiceJudgesService {
/** /**
* Add new umpire to queue with max(order) + 1 * Add new umpire to queue with max(order) + 1
*/ */
async add(serviceJudgeId: number): Promise<number> { async add(serviceJudgeId: number, toPosition?: number): Promise<number> {
return db.transaction('rw', db.waitingServiceJudges, async () => { const id = await db.transaction('rw', db.waitingServiceJudges, async () => {
const last = await db.waitingServiceJudges.orderBy('order').last(); const last = await db.waitingServiceJudges.orderBy('order').last();
const nextOrder = last ? last.order + 1 : 1; const nextOrder = last ? last.order + 1 : 1;
@@ -32,6 +32,12 @@ export class WaitingServiceJudgesService {
order: nextOrder 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> { async remove(id: number): Promise<void> {
await db.waitingServiceJudges.delete(id); await db.waitingServiceJudges.delete(id);
await this.reinitOrders();
} }
async removeByUmpireId(umpireId: number): Promise<void> { async removeByUmpireId(umpireId: number): Promise<void> {
@@ -51,7 +59,7 @@ export class WaitingServiceJudgesService {
return; return;
} }
await db.waitingServiceJudges.delete(item.id); await this.remove(item.id);
} }
/** /**
@@ -67,4 +75,87 @@ export class WaitingServiceJudgesService {
async clear(): Promise<void> { async clear(): Promise<void> {
await db.waitingServiceJudges.clear(); 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;
}
} }
+95 -4
View File
@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop'; import { toSignal } from '@angular/core/rxjs-interop';
import { db } from 'db'; import { db, Umpire } from 'db';
import { liveQuery } from 'dexie'; import { liveQuery } from 'dexie';
import { from } from 'rxjs'; import { from } from 'rxjs';
@@ -21,8 +21,8 @@ export class WaitingUmpiresService {
/** /**
* Add new umpire to queue with max(order) + 1 * Add new umpire to queue with max(order) + 1
*/ */
async add(umpireId: number): Promise<number> { async add(umpireId: number, toPosition?: number): Promise<number> {
return db.transaction('rw', db.waitingUmpires, async () => { const id = await db.transaction('rw', db.waitingUmpires, async () => {
const last = await db.waitingUmpires.orderBy('order').last(); const last = await db.waitingUmpires.orderBy('order').last();
const nextOrder = last ? last.order + 1 : 1; const nextOrder = last ? last.order + 1 : 1;
@@ -32,6 +32,12 @@ export class WaitingUmpiresService {
order: nextOrder 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> { async remove(id: number): Promise<void> {
await db.waitingUmpires.delete(id); await db.waitingUmpires.delete(id);
await this.reinitOrders();
} }
async removeByUmpireId(umpireId: number): Promise<void> { async removeByUmpireId(umpireId: number): Promise<void> {
@@ -51,7 +59,7 @@ export class WaitingUmpiresService {
return; return;
} }
await db.waitingUmpires.delete(item.id); await this.remove(item.id);
} }
/** /**
@@ -67,4 +75,87 @@ export class WaitingUmpiresService {
async clear(): Promise<void> { async clear(): Promise<void> {
await db.waitingUmpires.clear(); 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;
}
} }
+82 -19
View File
@@ -19,8 +19,17 @@
<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'">P</ion-item>
</ion-col> </ion-col>
<ion-col><ion-item [lines]="'none'">Játékvezető</ion-item></ion-col> <ion-col>
<ion-col><ion-item [lines]="'none'">Adogatásbíró</ion-item></ion-col> <ion-item [lines]="'none'">
<ion-label class="ion-text-center"> Játékvezető </ion-label>
</ion-item>
</ion-col>
<ion-col>
<ion-item [lines]="'none'">
<ion-label class="ion-text-center"> Adogatásbíró </ion-label>
</ion-item>
</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) {
<ion-row> <ion-row>
@@ -34,21 +43,68 @@
[lines]="'none'" [lines]="'none'"
cdkDropList cdkDropList
[id]="`court-umpire-${$index + 1}`" [id]="`court-umpire-${$index + 1}`"
cdkDropListSortingDisabled [cdkDropListData]="umpireByCourt().get($index + 1) ? [umpireByCourt().get($index + 1)] : []"
[cdkDropListData]="waitingServiceJudges()" [cdkDropListEnterPredicate]="canDropUmpire"
(cdkDropListDropped)="dropToUmpire($event, $index + 1)" (cdkDropListDropped)="dropToUmpire($event, $index + 1)">
[cdkDropListConnectedTo]="['list-on-rest', 'list-waiting-umpires']">
@if (umpireByCourt().get($index + 1); as umpire) { @if (umpireByCourt().get($index + 1); as umpire) {
<ion-item cdkDrag [cdkDragData]="umpire"> <div class="custom-ion-item" cdkDrag [cdkDragData]="umpire">
<ion-label>{{ umpire | fullname }}</ion-label> <div *cdkDragPlaceholder class="custom-ion-item primary">
</ion-item> <ion-label>{{ umpire|fullname }}</ion-label>
</div>
<ion-label>{{ umpire|fullname }}</ion-label>
</div>
} }
</ion-list> </ion-list>
</ion-col> </ion-col>
<ion-col> <ion-col>
<ion-item [lines]="'none'"> aaa </ion-item> <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)) {
<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) {
<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-col>
</ion-row> </ion-row>
} }
@@ -66,7 +122,7 @@
</ion-item> </ion-item>
</ion-col> </ion-col>
<ion-col [size]="4"> <ion-col [size]="4">
<ion-item color="primary"> <ion-item color="medium">
<ion-label class="ion-text-center"> Pihenők </ion-label> <ion-label class="ion-text-center"> Pihenők </ion-label>
</ion-item> </ion-item>
</ion-col> </ion-col>
@@ -76,14 +132,16 @@
<ion-list <ion-list
[lines]="'none'" [lines]="'none'"
cdkDropList cdkDropList
cdkDropListSortingDisabled
id="list-waiting-umpires" id="list-waiting-umpires"
[cdkDropListData]="waitingUmpires()" [cdkDropListData]="waitingUmpires()"
(cdkDropListDropped)="dropToWaitingUmpire($event)"> (cdkDropListDropped)="dropToWaitingUmpire($event)">
@for (umpire of waitingUmpires(); track umpire.id) { @for (umpire of waitingUmpires(); track umpire.id) {
<ion-item cdkDrag [cdkDragData]="umpire"> <div class="custom-ion-item" cdkDrag [cdkDragData]="umpire">
<div *cdkDragPlaceholder class="custom-ion-item primary">
<ion-label>{{ umpire|fullname }}</ion-label> <ion-label>{{ umpire|fullname }}</ion-label>
</ion-item> </div>
<ion-label>{{ umpire|fullname }}</ion-label>
</div>
} }
</ion-list> </ion-list>
</ion-col> </ion-col>
@@ -91,14 +149,16 @@
<ion-list <ion-list
[lines]="'none'" [lines]="'none'"
cdkDropList cdkDropList
cdkDropListSortingDisabled
id="list-waiting-service-judges" id="list-waiting-service-judges"
[cdkDropListData]="waitingServiceJudges()" [cdkDropListData]="waitingServiceJudges()"
(cdkDropListDropped)="dropToWaitingServiceJudge($event)"> (cdkDropListDropped)="dropToWaitingServiceJudge($event)">
@for (umpire of waitingServiceJudges(); track umpire.id) { @for (umpire of waitingServiceJudges(); track umpire.id) {
<ion-item cdkDrag [cdkDragData]="umpire"> <div class="custom-ion-item" cdkDrag [cdkDragData]="umpire">
<div *cdkDragPlaceholder class="custom-ion-item primary">
<ion-label>{{ umpire|fullname }}</ion-label> <ion-label>{{ umpire|fullname }}</ion-label>
</ion-item> </div>
<ion-label>{{ umpire|fullname }}</ion-label>
</div>
} }
</ion-list> </ion-list>
</ion-col> </ion-col>
@@ -111,9 +171,12 @@
(cdkDropListDropped)="dropToRest($event)" (cdkDropListDropped)="dropToRest($event)"
[cdkDropListData]="onRest()"> [cdkDropListData]="onRest()">
@for (umpire of onRest(); track umpire.id) { @for (umpire of onRest(); track umpire.id) {
<ion-item cdkDrag [cdkDragData]="umpire"> <div class="custom-ion-item" cdkDrag [cdkDragData]="umpire">
<div *cdkDragPlaceholder class="custom-ion-item primary">
<ion-label>{{ umpire|fullname }}</ion-label> <ion-label>{{ umpire|fullname }}</ion-label>
</ion-item> </div>
<ion-label>{{ umpire|fullname }}</ion-label>
</div>
} }
</ion-list> </ion-list>
</ion-col> </ion-col>
+9
View File
@@ -1,3 +1,12 @@
.bottom-lists ion-list { .bottom-lists ion-list {
min-height: 64px; min-height: 64px;
} }
.custom-ion-item {
cursor: move;
padding: 10px 15px;
&.primary {
background-color: var(--ion-color-primary);
}
}
+190 -49
View File
@@ -1,4 +1,4 @@
import { Component, computed, effect, inject } from '@angular/core'; import { Component, computed, inject } from '@angular/core';
import { import {
IonHeader, IonHeader,
IonToolbar, IonToolbar,
@@ -10,29 +10,37 @@ import {
IonList, IonList,
IonItem, IonItem,
IonLabel, IonLabel,
IonIcon IonIcon,
IonButton,
AlertController
} from '@ionic/angular/standalone'; } from '@ionic/angular/standalone';
import { SettingsService } from '../services/settings-service'; import { SettingsService } from '../services/settings-service';
import { UmpireService } from '../services/umpire.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 { WaitingUmpiresService } from '../services/waiting-umpires.service';
import { WaitingServiceJudgesService } from '../services/waiting-service-judges.service'; import { WaitingServiceJudgesService } from '../services/waiting-service-judges.service';
import { FullnamePipe } from '../fullname-pipe'; import { FullnamePipe } from '../fullname-pipe';
import { import {
CdkDrag, CdkDrag,
CdkDragDrop, CdkDragDrop,
CdkDragPlaceholder,
CdkDropList, CdkDropList,
DragDropModule CdkDropListGroup
} from '@angular/cdk/drag-drop'; } from '@angular/cdk/drag-drop';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
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 { addIcons } from 'ionicons';
import { enterOutline, exitOutline } from 'ionicons/icons';
@Component({ @Component({
selector: 'app-tab1', selector: 'app-tab1',
templateUrl: 'tab1.page.html', templateUrl: 'tab1.page.html',
styleUrls: ['tab1.page.scss'], styleUrls: ['tab1.page.scss'],
providers: [FullnamePipe],
imports: [ imports: [
IonButton,
IonIcon,
IonLabel, IonLabel,
IonItem, IonItem,
IonList, IonList,
@@ -47,10 +55,15 @@ import { CourtServiceJudgeService } from '../services/court.service.judge.servic
CdkDropList, CdkDropList,
CdkDrag, CdkDrag,
CommonModule, CommonModule,
DragDropModule CdkDragPlaceholder,
CdkDropListGroup
] ]
}) })
export class Tab1Page { export class Tab1Page {
constructor() {
addIcons({ exitOutline, enterOutline });
}
readonly settingsService = inject(SettingsService); readonly settingsService = inject(SettingsService);
readonly umpireService = inject(UmpireService); readonly umpireService = inject(UmpireService);
readonly waitingUmpireService = inject(WaitingUmpiresService); readonly waitingUmpireService = inject(WaitingUmpiresService);
@@ -58,6 +71,9 @@ export class Tab1Page {
readonly courtUmpireService = inject(CourtUmpireService); readonly courtUmpireService = inject(CourtUmpireService);
readonly courtServiceJudgeService = inject(CourtServiceJudgeService); readonly courtServiceJudgeService = inject(CourtServiceJudgeService);
private alertController = inject(AlertController);
private fullnamePipe = inject(FullnamePipe);
/** /**
* Raw signals from services * Raw signals from services
*/ */
@@ -255,77 +271,85 @@ export class Tab1Page {
}); });
dropToRest(event: CdkDragDrop<Umpire[]>) { dropToRest(event: CdkDragDrop<Umpire[]>) {
console.log('drop to rest');
if (event.previousContainer === event.container) { if (event.previousContainer === event.container) {
return; return;
} else { } else {
const comingFrom = event.previousContainer.id; const comingFrom = event.previousContainer.id;
const umpireToMove = event.item.data; const umpireToMove = event.item.data;
if ('list-waiting-service-judges' === comingFrom) { this.removeFromOriginalPlace(umpireToMove, comingFrom);
// Remove from waiting service judges
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);
}
} }
} }
dropToWaitingServiceJudge(event: CdkDragDrop<Umpire[]>) { dropToWaitingServiceJudge(event: CdkDragDrop<Umpire[]>) {
if (event.previousContainer === event.container) {
// TODO
} else {
const comingFrom = event.previousContainer.id;
const umpireToMove = event.item.data; const umpireToMove = event.item.data;
this.waitingServiceJudgeService.add(umpireToMove.id); if (event.previousContainer === event.container) {
this.waitingServiceJudgeService.moveToPosition(
umpireToMove.id,
event.currentIndex + 1
);
return;
}
const comingFrom = event.previousContainer.id;
if ('list-waiting-umpires' === comingFrom) { this.waitingServiceJudgeService.add(
this.waitingUmpireService.removeByUmpireId(umpireToMove.id); umpireToMove.id,
} event.currentIndex + 1
);
if (comingFrom.startsWith('court-umpire')) { this.removeFromOriginalPlace(umpireToMove, comingFrom);
this.courtUmpireService.removeByUmpireId(umpireToMove.id);
}
}
} }
dropToWaitingUmpire(event: CdkDragDrop<Umpire[]>) { dropToWaitingUmpire(event: CdkDragDrop<Umpire[]>) {
console.log( const umpireToMove = event.item.data;
event.container.data,
event.previousContainer.data,
event.previousContainer.id,
event.container.id
);
if (event.previousContainer === event.container) { if (event.previousContainer === event.container) {
// TODO this.waitingUmpireService.moveToPosition(
umpireToMove.id,
event.currentIndex + 1
);
return;
} else { } else {
const comingFrom = event.previousContainer.id; const comingFrom = event.previousContainer.id;
const umpireToMove = event.item.data; this.waitingUmpireService.add(umpireToMove.id, event.currentIndex + 1);
this.waitingUmpireService.add(umpireToMove.id);
if ('list-waiting-service-judges' === comingFrom) { this.removeFromOriginalPlace(umpireToMove, comingFrom);
// Remove from waiting service judges
this.waitingServiceJudgeService.removeByUmpireId(umpireToMove.id);
}
if (comingFrom.startsWith('court-umpire')) {
this.courtUmpireService.removeByUmpireId(umpireToMove.id);
}
} }
} }
dropToUmpire(event: CdkDragDrop<Umpire[]>, courtNo: number) { dropToUmpire(event: CdkDragDrop<(Umpire | undefined)[]>, courtNo: number) {
// TODO: stop dropping if there is already another umpire const targetUmpires = event.container.data;
if (targetUmpires.length > 0) {
return;
}
const comingFrom = event.previousContainer.id; const comingFrom = event.previousContainer.id;
const umpireToMove = event.item.data; const umpireToMove = event.item.data;
this.courtUmpireService.save({ umpireId: umpireToMove.id, courtNo }); 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) { if ('list-waiting-service-judges' === comingFrom) {
this.waitingServiceJudgeService.removeByUmpireId(umpireToMove.id); this.waitingServiceJudgeService.removeByUmpireId(umpireToMove.id);
} }
@@ -333,5 +357,122 @@ export class Tab1Page {
if ('list-waiting-umpires' === comingFrom) { if ('list-waiting-umpires' === comingFrom) {
this.waitingUmpireService.removeByUmpireId(umpireToMove.id); 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') {
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?',
cssClass: 'wide',
message: `${this.fullnamePipe.transform(umpire)} - ${this.fullnamePipe.transform(serviceJudge)}`,
buttons: [
{
text: 'Nem',
role: 'cancel'
},
{
text: 'Igen',
role: 'confirm',
handler: () => {
this.removeUmpiresFromCourt(courtNo);
}
}
]
});
await alert.present();
}
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);
}
public async showAddConfirmation(courtNo: number) {
const umpire = await this.waitingUmpireService.getCurrentUmpire();
const serviceJudge =
await this.waitingServiceJudgeService.getCurrentUmpire();
if (typeof umpire === 'undefined' || typeof serviceJudge === 'undefined') {
return;
}
if (!this.settings()?.showAlert) {
this.addUmpiresToCourt(courtNo, umpire, serviceJudge);
return;
}
const alert = await this.alertController.create({
header: `Pálya ${courtNo}`,
cssClass: 'wide',
subHeader: 'Biztos pályára küldöd őket?',
message: `${this.fullnamePipe.transform(umpire)} - ${this.fullnamePipe.transform(serviceJudge)}`,
buttons: [
{
text: 'Nem',
role: 'cancel'
},
{
text: 'Igen',
role: 'confirm',
handler: () => {
this.addUmpiresToCourt(courtNo, umpire, serviceJudge);
}
}
]
});
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);
} }
} }
+40 -11
View File
@@ -1,17 +1,46 @@
<ion-header [translucent]="true"> <ion-header>
<ion-toolbar> <ion-toolbar>
<ion-title> <ion-title>Beállítások</ion-title>
Tab 3
</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content [fullscreen]="true"> <ion-content>
<ion-header collapse="condense"> <ion-grid [fixed]="true">
<ion-toolbar> <ion-row>
<ion-title size="large">Tab 3</ion-title> <ion-col sizeLg="6" pushLg="3">
</ion-toolbar> @if (settings(); as currentSettings) {
</ion-header>
<app-explore-container name="Tab 3 page"></app-explore-container> <ion-list>
<ion-item>
<ion-toggle
[checked]="currentSettings.withServiceJudge"
(ionChange)="updateWithServiceJudge($event)">
Adogatásbíró
</ion-toggle>
</ion-item>
<ion-item>
<ion-toggle
[checked]="currentSettings.showAlert"
(ionChange)="updateShowAlert($event)">
Figyelmeztetések megjelenítése a gyors gomboknál
</ion-toggle>
</ion-item>
<ion-item>
<ion-input
label="Pályák száma"
type="number"
min="1"
labelPlacement="floating"
[value]="currentSettings.numberOfCourts"
(ionInput)="updateNumberOfCourts($event)">
</ion-input>
</ion-item>
</ion-list>
}
</ion-col>
</ion-row>
</ion-grid>
</ion-content> </ion-content>
+71 -5
View File
@@ -1,13 +1,79 @@
import { Component } from '@angular/core'; import { Component, inject } 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,
IonList,
IonItem,
IonToggle,
IonInput,
IonGrid,
IonCol,
IonRow
} 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';
@Component({ @Component({
selector: 'app-tab3', selector: 'app-tab3',
templateUrl: 'tab3.page.html', templateUrl: 'tab3.page.html',
styleUrls: ['tab3.page.scss'], styleUrls: ['tab3.page.scss'],
imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent], imports: [
IonRow,
IonCol,
IonGrid,
IonInput,
IonToggle,
IonItem,
IonList,
IonHeader,
IonToolbar,
IonTitle,
IonContent
]
}) })
export class Tab3Page { export class Tab3Page {
constructor() {} readonly settingsService = inject(SettingsService);
readonly courtUmpireService = inject(CourtUmpireService);
readonly courtServiceJudgeService = inject(CourtServiceJudgeService);
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
});
}
async updateShowAlert(event: CustomEvent): Promise<void> {
await this.settingsService.update({
showAlert: event.detail.checked
});
}
} }
+4 -9
View File
@@ -1,22 +1,17 @@
<ion-tabs> <ion-tabs>
<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="grid-outline"></ion-icon>
<ion-label>Pályák</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="people-outline"></ion-icon>
<ion-label>Játékvezetők</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="settings" href="/tabs/settings">
<ion-icon aria-hidden="true" name="square"></ion-icon> <ion-icon aria-hidden="true" name="settings-outline"></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-label>Beállítások</ion-label>
</ion-tab-button> </ion-tab-button>
</ion-tab-bar> </ion-tab-bar>
+10 -4
View File
@@ -1,18 +1,24 @@
import { Component, EnvironmentInjector, inject } from '@angular/core'; 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 { addIcons } from 'ionicons';
import { triangle, ellipse, square } from 'ionicons/icons'; import { gridOutline, peopleOutline, settingsOutline } from 'ionicons/icons';
@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]
}) })
export class TabsPage { export class TabsPage {
public environmentInjector = inject(EnvironmentInjector); public environmentInjector = inject(EnvironmentInjector);
constructor() { 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) loadComponent: () => import('../tab2/tab2.page').then((m) => m.Tab2Page)
}, },
{ {
path: 'stats', path: 'settings',
loadComponent: () => import('../tab3/tab3.page').then((m) => m.Tab3Page) loadComponent: () => import('../tab3/tab3.page').then((m) => m.Tab3Page)
}, },
{ {
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

+10
View File
@@ -35,3 +35,13 @@
/* @import "@ionic/angular/css/palettes/dark.always.css"; */ /* @import "@ionic/angular/css/palettes/dark.always.css"; */
/* @import "@ionic/angular/css/palettes/dark.class.css"; */ /* @import "@ionic/angular/css/palettes/dark.class.css"; */
@import '@ionic/angular/css/palettes/dark.system.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 -2
View File
@@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>Ionic App</title> <title>Court Pilot</title>
<base href="/" /> <base href="/" />
@@ -12,7 +12,8 @@
<meta name="format-detection" content="telephone=no" /> <meta name="format-detection" content="telephone=no" />
<meta name="msapplication-tap-highlight" content="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 --> <!-- add to homescreen for ios -->
<meta name="mobile-web-app-capable" content="yes" /> <meta name="mobile-web-app-capable" content="yes" />