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")}
) : (
{workouts.map((w) => (
))}
)}
{totalPages > 1 && (
)}
);
}