feat(pilot): do not add 2 umpires to the same splace

* placeholder fixes
This commit is contained in:
2026-06-05 11:37:47 +02:00
parent 12871ef7d2
commit 2fb8d34ec1
6 changed files with 268 additions and 54 deletions
@@ -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,81 @@ 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
});
}
}
});
}
} }
+89 -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,81 @@ 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
});
}
}
});
}
} }
+30 -18
View File
@@ -34,14 +34,17 @@
[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]="courtUmpires()" [cdkDropListEnterPredicate]="canDropUmpire"
(cdkDropListDropped)="dropToUmpire($event, $index + 1)"> (cdkDropListDropped)="dropToUmpire($event, $index + 1)">
@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>
@@ -51,14 +54,16 @@
[lines]="'none'" [lines]="'none'"
cdkDropList cdkDropList
[id]="`court-service-judge-${$index + 1}`" [id]="`court-service-judge-${$index + 1}`"
cdkDropListSortingDisabled [cdkDropListData]="serviceJudgeByCourt().get($index + 1) ? [serviceJudgeByCourt().get($index + 1)] : []"
[cdkDropListData]="courtServiceJudges()"
(cdkDropListDropped)="dropToServiceJudge($event, $index + 1)"> (cdkDropListDropped)="dropToServiceJudge($event, $index + 1)">
@if (serviceJudgeByCourt().get($index + 1); as umpire) { @if (serviceJudgeByCourt().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>
@@ -89,14 +94,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>
</div>
<ion-label>{{ umpire|fullname }}</ion-label> <ion-label>{{ umpire|fullname }}</ion-label>
</ion-item> </div>
} }
</ion-list> </ion-list>
</ion-col> </ion-col>
@@ -104,14 +111,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>
</div>
<ion-label>{{ umpire|fullname }}</ion-label> <ion-label>{{ umpire|fullname }}</ion-label>
</ion-item> </div>
} }
</ion-list> </ion-list>
</ion-col> </ion-col>
@@ -124,9 +133,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>
</div>
<ion-label>{{ umpire|fullname }}</ion-label> <ion-label>{{ umpire|fullname }}</ion-label>
</ion-item> </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);
}
} }
+50 -27
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,
@@ -9,20 +9,20 @@ import {
IonRow, IonRow,
IonList, IonList,
IonItem, IonItem,
IonLabel, IonLabel
IonIcon
} 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';
@@ -47,7 +47,8 @@ import { CourtServiceJudgeService } from '../services/court.service.judge.servic
CdkDropList, CdkDropList,
CdkDrag, CdkDrag,
CommonModule, CommonModule,
DragDropModule CdkDragPlaceholder,
CdkDropListGroup
] ]
}) })
export class Tab1Page { export class Tab1Page {
@@ -255,7 +256,6 @@ 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 {
@@ -266,37 +266,46 @@ export class Tab1Page {
} }
dropToWaitingServiceJudge(event: CdkDragDrop<Umpire[]>) { dropToWaitingServiceJudge(event: CdkDragDrop<Umpire[]>) {
const umpireToMove = event.item.data;
if (event.previousContainer === event.container) { if (event.previousContainer === event.container) {
// TODO this.waitingServiceJudgeService.moveToPosition(
} else { umpireToMove.id,
const comingFrom = event.previousContainer.id; event.currentIndex + 1
const umpireToMove = event.item.data; );
this.waitingServiceJudgeService.add(umpireToMove.id); return;
this.removeFromOriginalPlace(umpireToMove, comingFrom);
} }
const comingFrom = event.previousContainer.id;
this.waitingServiceJudgeService.add(
umpireToMove.id,
event.currentIndex + 1
);
this.removeFromOriginalPlace(umpireToMove, comingFrom);
} }
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);
this.removeFromOriginalPlace(umpireToMove, comingFrom); this.removeFromOriginalPlace(umpireToMove, comingFrom);
} }
} }
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;
@@ -305,8 +314,15 @@ export class Tab1Page {
this.removeFromOriginalPlace(umpireToMove, comingFrom); this.removeFromOriginalPlace(umpireToMove, comingFrom);
} }
dropToServiceJudge(event: CdkDragDrop<Umpire[]>, courtNo: number) { dropToServiceJudge(
// TODO: stop dropping if there is already another umpire event: CdkDragDrop<(Umpire | undefined)[]>,
courtNo: number
) {
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;
@@ -335,4 +351,11 @@ export class Tab1Page {
this.courtServiceJudgeService.removeByUmpireId(umpireToMove.id); this.courtServiceJudgeService.removeByUmpireId(umpireToMove.id);
} }
} }
canDropUmpire = (
drag: CdkDrag<Umpire>,
drop: CdkDropList<Umpire[]>
): boolean => {
return drop.data.length === 0;
};
} }
+1 -1
View File
@@ -15,7 +15,7 @@
<ion-label>Tab 3</ion-label> <ion-label>Tab 3</ion-label>
</ion-tab-button> </ion-tab-button>
<ion-tab-button tab="stats" href="/tabs/settings"> <ion-tab-button tab="settings" href="/tabs/settings">
<ion-icon aria-hidden="true" name="square"></ion-icon> <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>