+ @if (isResyncing) {
+
+
+
+ {{ (resyncProgress ? 'TRAEWELLING.RESYNC_PROGRESS' : 'TRAEWELLING.RESYNC_RUNNING') | translate:{
+ pages: resyncProgress?.pagesRead, count: resyncProgress?.added } }}
+
+
+ }
+
diff --git a/OV_DB/OVDBFrontend/src/app/traewelling/traewelling.component.scss b/OV_DB/OVDBFrontend/src/app/traewelling/traewelling.component.scss
index e4b4adbc..236d0f92 100644
--- a/OV_DB/OVDBFrontend/src/app/traewelling/traewelling.component.scss
+++ b/OV_DB/OVDBFrontend/src/app/traewelling/traewelling.component.scss
@@ -30,6 +30,16 @@
align-self: center;
}
+ .resync-button {
+ align-self: center;
+ }
+
+ .resync-progress {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+
.conflicts-container {
margin-bottom: 16px;
diff --git a/OV_DB/OVDBFrontend/src/app/traewelling/traewelling.component.ts b/OV_DB/OVDBFrontend/src/app/traewelling/traewelling.component.ts
index 71acb8be..4a7607bc 100644
--- a/OV_DB/OVDBFrontend/src/app/traewelling/traewelling.component.ts
+++ b/OV_DB/OVDBFrontend/src/app/traewelling/traewelling.component.ts
@@ -18,7 +18,9 @@ import {
TrawellingConflict,
TrawellingConflictAction,
TraewellingAlert,
- TraewellingAlertTranslation
+ TraewellingAlertTranslation,
+ TrawellingResyncProgress,
+ TrawellingResyncResult
} from '../models/traewelling.model';
import { TripCardComponent } from './components/trip-card/trip-card.component';
import { ConflictCardComponent } from './components/conflict-card/conflict-card.component';
@@ -54,6 +56,8 @@ export class TrawellingComponent implements OnInit, OnDestroy {
conflicts: TrawellingConflict[] = [];
alerts: TraewellingAlert[] = [];
isLoading = true;
+ isResyncing = false;
+ resyncProgress: TrawellingResyncProgress | null = null;
isLoadingMore = false;
hasMorePages = false;
currentPage = 1;
@@ -64,9 +68,9 @@ export class TrawellingComponent implements OnInit, OnDestroy {
await this.loadConnectionStatus();
if (this.connectionStatus?.connected) {
await Promise.all([this.loadTrips(), this.loadAlerts(), this.loadConflicts()]);
- if (this.connectionStatus.liveSyncEnabled) {
- this.startLiveUpdates();
- }
+ // Also without live sync: a resync started here reports over the same hub.
+ this.startLiveUpdates();
+ this.isResyncing = this.connectionStatus.resyncRunning === true;
}
this.isLoading = false;
}
@@ -88,6 +92,8 @@ export class TrawellingComponent implements OnInit, OnDestroy {
this.liveService.tripRemoved$.subscribe(statusId => this.removeTrip(statusId)),
this.liveService.conflictUpserted$.subscribe(conflict => this.upsertConflict(conflict)),
this.liveService.conflictRemoved$.subscribe(statusId => this.removeConflict(statusId)),
+ this.liveService.resyncProgress$.subscribe(progress => this.resyncProgress = progress),
+ this.liveService.resyncFinished$.subscribe(result => this.finishResync(result)),
);
}
@@ -202,6 +208,41 @@ export class TrawellingComponent implements OnInit, OnDestroy {
}
}
+ /**
+ * Only queues the walk — it takes minutes and reports over the hub, so the spinner runs
+ * until a ResyncFinished arrives rather than until this request returns.
+ */
+ async resyncFullHistory() {
+ if (this.isResyncing) return;
+ this.isResyncing = true;
+ this.resyncProgress = null;
+ try {
+ await this.trawellingService.resyncFullHistory();
+ this.snackBar.open(
+ this.translateService.instant('TRAEWELLING.RESYNC_STARTED'),
+ this.translateService.instant('CLOSE'),
+ { duration: 5000 });
+ } catch {
+ this.isResyncing = false;
+ // The global error interceptor already showed a toast
+ }
+ }
+
+ private async finishResync(result: TrawellingResyncResult) {
+ this.isResyncing = false;
+ this.resyncProgress = null;
+ const messageKey = !result.complete
+ ? 'TRAEWELLING.RESYNC_INCOMPLETE'
+ : result.added > 0 ? 'TRAEWELLING.RESYNC_FOUND' : 'TRAEWELLING.RESYNC_NOTHING_FOUND';
+ this.snackBar.open(
+ this.translateService.instant(messageKey, { count: result.added }),
+ this.translateService.instant('CLOSE'),
+ { duration: 8000 });
+ if (result.added > 0) {
+ await this.loadTrips();
+ }
+ }
+
private async loadTrips(refresh = false) {
try {
const response = await this.trawellingService.getUnimportedTrips(1, refresh);
diff --git a/OV_DB/OVDBFrontend/src/assets/i18n/en.json b/OV_DB/OVDBFrontend/src/assets/i18n/en.json
index 374d13c4..487783f9 100644
--- a/OV_DB/OVDBFrontend/src/assets/i18n/en.json
+++ b/OV_DB/OVDBFrontend/src/assets/i18n/en.json
@@ -728,6 +728,13 @@
"CATEGORY_TAXI": "Taxi",
"CATEGORY_UNKNOWN": "Unknown",
"REFRESH": "Check Träwelling for new trips",
+ "RESYNC": "Search your whole Träwelling history for trips that were skipped",
+ "RESYNC_STARTED": "Searching your whole Träwelling history, this takes a few minutes. You can keep using the site.",
+ "RESYNC_RUNNING": "Searching your whole Träwelling history...",
+ "RESYNC_PROGRESS": "Searching your whole Träwelling history: {{pages}} pages read, {{count}} trips found",
+ "RESYNC_FOUND": "Skipped trips found: {{count}}",
+ "RESYNC_NOTHING_FOUND": "No skipped trips found, your whole Träwelling history is here",
+ "RESYNC_INCOMPLETE": "Could not read your whole history, try again. Trips found so far: {{count}}",
"IGNORE_TRIP": "Ignore trip",
"IGNORE_CONFIRM": "ignore this trip (it will not come back in this list)",
"IGNORING": "Ignoring trip...",
diff --git a/OV_DB/OVDBFrontend/src/assets/i18n/nl.json b/OV_DB/OVDBFrontend/src/assets/i18n/nl.json
index 73341214..d94eea69 100644
--- a/OV_DB/OVDBFrontend/src/assets/i18n/nl.json
+++ b/OV_DB/OVDBFrontend/src/assets/i18n/nl.json
@@ -726,6 +726,13 @@
"CATEGORY_TAXI": "Taxi",
"CATEGORY_UNKNOWN": "Onbekend",
"REFRESH": "Träwelling controleren op nieuwe reizen",
+ "RESYNC": "Je hele Träwelling-geschiedenis doorzoeken op overgeslagen reizen",
+ "RESYNC_STARTED": "Je hele Träwelling-geschiedenis wordt doorzocht, dit duurt een paar minuten. Je kunt de site gewoon blijven gebruiken.",
+ "RESYNC_RUNNING": "Je hele Träwelling-geschiedenis wordt doorzocht...",
+ "RESYNC_PROGRESS": "Je hele Träwelling-geschiedenis wordt doorzocht: {{pages}} pagina's gelezen, {{count}} reizen gevonden",
+ "RESYNC_FOUND": "Overgeslagen reizen gevonden: {{count}}",
+ "RESYNC_NOTHING_FOUND": "Geen overgeslagen reizen gevonden, je hele Träwelling-geschiedenis staat er al in",
+ "RESYNC_INCOMPLETE": "Kon je hele geschiedenis niet lezen, probeer het opnieuw. Tot nu toe gevonden: {{count}}",
"IGNORE_TRIP": "Reis negeren",
"IGNORE_CONFIRM": "deze reis wilt negeren (hij komt niet terug in deze lijst)",
"IGNORING": "Reis negeren...",
diff --git a/OV_DB/Services/ITrawellingService.cs b/OV_DB/Services/ITrawellingService.cs
index 084937a4..d585e721 100644
--- a/OV_DB/Services/ITrawellingService.cs
+++ b/OV_DB/Services/ITrawellingService.cs
@@ -7,6 +7,33 @@
namespace OV_DB.Services
{
+ ///