import { useCallback, useEffect, useRef, useState } from "react"; import { data, useFetcher } from "react-router"; import { useTranslation } from "react-i18next"; import type { Route } from "./+types/sync.import.$provider"; import { ClientDate } from "~/components/ClientDate"; import { loadSyncImportProvider, syncImportProviderAction } from "./sync.import.$provider.server"; export async function loader({ params, request }: Route.LoaderArgs) { return data(await loadSyncImportProvider(request, params.provider)); } export async function action({ params, request }: Route.ActionArgs) { return data(await syncImportProviderAction(request, params.provider)); } export function meta({ data: loaderData }: Route.MetaArgs) { const name = (loaderData as { provider: { name: string } })?.provider?.name ?? "Import"; return [{ title: `Import from ${name} — trails.cool` }]; } function WorkoutRow({ workout, provider, alreadyImported, }: { workout: { id: string; name: string; fileUrl?: string; startedAt: string; distance: number | null; duration: number | null }; provider: { id: string; name: string }; alreadyImported: boolean; }) { const { t } = useTranslation("journal"); const fetcher = useFetcher<{ imported?: string }>(); const isImporting = fetcher.state !== "idle"; const isImported = alreadyImported || fetcher.data?.imported === workout.id; return (
  • {workout.name}

    {!workout.fileUrl && ( {t("sync.noGps")} )}
    {workout.startedAt && } {workout.distance != null && {(workout.distance / 1000).toFixed(1)} km} {workout.duration != null && {Math.round(workout.duration / 60)} min}
    {isImported ? ( {t("sync.imported")} ) : isImporting ? ( {t("sync.importing")} ) : ( )}
  • ); } function workoutFormData(w: { id: string; name: string; fileUrl?: string; startedAt: string; distance: number | null; duration: number | null }) { const fd = new FormData(); fd.set("workoutId", w.id); fd.set("workoutName", w.name); fd.set("fileUrl", w.fileUrl ?? ""); fd.set("startedAt", w.startedAt ?? ""); fd.set("distance", w.distance != null ? String(w.distance) : ""); fd.set("duration", w.duration != null ? String(w.duration) : ""); return fd; } export default function SyncImportPage({ loaderData }: Route.ComponentProps) { const { provider, workouts, page, totalPages } = loaderData; const { t } = useTranslation("journal"); const importableWorkouts = workouts.filter((w) => !w.imported); const [importAllActive, setImportAllActive] = useState(false); const [importAllIndex, setImportAllIndex] = useState(0); const importAllFetcher = useFetcher<{ imported?: string }>(); const importAllRef = useRef({ index: 0, workouts: importableWorkouts }); useEffect(() => { if (!importAllActive) return; if (importAllFetcher.state !== "idle") return; // Previous import finished — advance to next const nextIndex = importAllFetcher.data ? importAllRef.current.index + 1 : importAllRef.current.index; importAllRef.current.index = nextIndex; setImportAllIndex(nextIndex); if (nextIndex >= importAllRef.current.workouts.length) { setImportAllActive(false); return; } const w = importAllRef.current.workouts[nextIndex]!; importAllFetcher.submit(workoutFormData(w), { method: "post" }); }, [importAllActive, importAllFetcher.state, importAllFetcher.data]); const startImportAll = useCallback(() => { if (importableWorkouts.length === 0) return; importAllRef.current = { index: 0, workouts: importableWorkouts }; setImportAllIndex(0); setImportAllActive(true); importAllFetcher.submit(workoutFormData(importableWorkouts[0]!), { method: "post" }); }, [importableWorkouts, importAllFetcher]); return (

    {t("sync.importFrom", { provider: provider.name })}

    {importableWorkouts.length > 0 && ( importAllActive ? ( {t("sync.importingProgress", { current: importAllIndex + 1, total: importAllRef.current.workouts.length })} ) : ( ) )}
    {workouts.length === 0 ? (

    {t("sync.noWorkouts")}

    ) : ( )} {totalPages > 1 && (
    {page > 1 && ( {t("sync.previous")} )} {page} / {totalPages} {page < totalPages && ( {t("sync.next")} )}
    )}
    ); }