- Going further back fetches the older runs over the next few syncs.
+ Going further back fetches the older runs when you next sync.
diff --git a/apps/web/src/lib/integrations.ts b/apps/web/src/lib/integrations.ts
index 6518c2c..77a7293 100644
--- a/apps/web/src/lib/integrations.ts
+++ b/apps/web/src/lib/integrations.ts
@@ -1,5 +1,9 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
-import { type ConnectIntervals, integrationsSchema } from '@openrun/schemas';
+import {
+ type ConnectIntervals,
+ type Integrations,
+ integrationsSchema,
+} from '@openrun/schemas';
import { api } from './api';
const KEY = ['integrations'];
@@ -39,10 +43,35 @@ export const useUpdateIntervals = () =>
}),
);
-export const useSyncIntervals = () =>
- useIntegrationMutation(() =>
- api('/integrations/intervals/sync', integrationsSchema, { method: 'POST' }),
- );
+/** Enough for about 1,000 runs at 25 a sync; scheduled syncs fetch the rest. */
+const MAX_SYNC_ROUNDS = 40;
+
+/**
+ * Each sync imports a batch of runs, so keep syncing until nothing is left,
+ * updating the counts after each batch.
+ */
+export function useSyncIntervals() {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: async () => {
+ let data: Integrations | undefined;
+ for (let round = 0; round < MAX_SYNC_ROUNDS; round++) {
+ data = await api('/integrations/intervals/sync', integrationsSchema, {
+ method: 'POST',
+ });
+ queryClient.setQueryData(KEY, data);
+ void queryClient.invalidateQueries({ queryKey: ['activities'] });
+ const latest = data.intervals.connection?.syncs[0];
+ // Stop if a batch got nowhere, rather than asking again and again.
+ const handled = latest
+ ? latest.imported + latest.duplicates + latest.failed
+ : 0;
+ if (!latest?.more || handled === 0) break;
+ }
+ return data;
+ },
+ });
+}
export const useDisconnectIntervals = () =>
useIntegrationMutation(() =>
diff --git a/apps/web/src/pages/club-manage.tsx b/apps/web/src/pages/club-manage.tsx
index e229adc..b50eab4 100644
--- a/apps/web/src/pages/club-manage.tsx
+++ b/apps/web/src/pages/club-manage.tsx
@@ -1,6 +1,7 @@
import { type FormEvent, Suspense, lazy, useMemo, useState } from 'react';
import { Link, useNavigate, useParams } from 'react-router-dom';
import {
+ NO_CUTOFF,
WEEKDAYS,
formatMenuOptions,
parseMenuOptions,
@@ -113,7 +114,7 @@ function RepeatingRuns({ club }: { club: Club }) {
startTime: String(form.get('startTime')),
locationId: String(form.get('seriesLocation') ?? '') || null,
notes: String(form.get('seriesNotes') ?? '') || null,
- orderCutoffMin: Number(form.get('seriesCutoff') ?? 0),
+ orderCutoffMin: Number(form.get('seriesCutoff') ?? NO_CUTOFF),
},
{ onSuccess: () => setAdding(false) },
);
@@ -319,7 +320,7 @@ function OneOffRun({ club }: { club: Club }) {
startsAt: zonedTime(day, time, club.timezone).toISOString(),
locationId: String(form.get('runLocation') ?? '') || null,
notes: String(form.get('runNotes') ?? '') || null,
- orderCutoffMin: Number(form.get('runCutoff') ?? 0),
+ orderCutoffMin: Number(form.get('runCutoff') ?? NO_CUTOFF),
},
{ onSuccess: (run) => navigate(`/clubs/${club.id}/runs/${run.id}`) },
);
@@ -381,6 +382,7 @@ const hasMenu = (club: Club) => club.menu.some((i) => !i.hidden);
/** Minutes before the start that coffee orders lock. */
const CUTOFFS: [number, string][] = [
+ [NO_CUTOFF, 'No cut-off'],
[0, 'When the run starts'],
[15, '15 minutes before'],
[30, '30 minutes before'],
@@ -395,7 +397,7 @@ function CutoffSelect({ club, name }: { club: Club; name: string }) {