diff --git a/apps/journal/app/lib/activities.server.ts b/apps/journal/app/lib/activities.server.ts index 2fc336e..ca7523c 100644 --- a/apps/journal/app/lib/activities.server.ts +++ b/apps/journal/app/lib/activities.server.ts @@ -1,7 +1,7 @@ import { randomUUID } from "node:crypto"; -import { eq, desc, sql } from "drizzle-orm"; +import { eq, desc, and, sql } from "drizzle-orm"; import { getDb } from "./db.ts"; -import { activities, routes } from "@trails-cool/db/schema/journal"; +import { activities, routes, syncImports } from "@trails-cool/db/schema/journal"; import { parseGpxAsync } from "@trails-cool/gpx"; import { setGeomFromGpx } from "./routes.server.ts"; @@ -61,7 +61,26 @@ export async function getActivity(id: string) { const [activity] = await db.select().from(activities).where(eq(activities.id, id)); if (!activity) return null; const geojson = await getActivityGeojson(id); - return { ...activity, geojson }; + const importSource = await getImportSource(id); + return { ...activity, geojson, importSource }; +} + +export async function deleteActivity(id: string, ownerId: string): Promise { + const db = getDb(); + const [activity] = await db.select({ id: activities.id }).from(activities) + .where(and(eq(activities.id, id), eq(activities.ownerId, ownerId))); + if (!activity) return false; + await db.delete(activities).where(eq(activities.id, id)); + return true; +} + +async function getImportSource(activityId: string): Promise<{ provider: string; externalWorkoutId: string } | null> { + const db = getDb(); + const [row] = await db + .select({ provider: syncImports.provider, externalWorkoutId: syncImports.externalWorkoutId }) + .from(syncImports) + .where(eq(syncImports.activityId, activityId)); + return row ?? null; } export async function listActivities(ownerId: string) { diff --git a/apps/journal/app/lib/sync/imports.server.ts b/apps/journal/app/lib/sync/imports.server.ts index 7eddfa6..2be87c1 100644 --- a/apps/journal/app/lib/sync/imports.server.ts +++ b/apps/journal/app/lib/sync/imports.server.ts @@ -38,6 +38,11 @@ export async function isAlreadyImported( return !!row; } +export async function deleteImportByActivity(activityId: string) { + const db = getDb(); + await db.delete(syncImports).where(eq(syncImports.activityId, activityId)); +} + export async function getImportedIds( userId: string, provider: string, diff --git a/apps/journal/app/routes/activities.$id.tsx b/apps/journal/app/routes/activities.$id.tsx index 0d32446..2ddb6f5 100644 --- a/apps/journal/app/routes/activities.$id.tsx +++ b/apps/journal/app/routes/activities.$id.tsx @@ -1,8 +1,10 @@ import { Suspense, lazy } from "react"; import { data, redirect } from "react-router"; +import { useTranslation } from "react-i18next"; import type { Route } from "./+types/activities.$id"; import { getSessionUser } from "~/lib/auth.server"; -import { getActivity, linkActivityToRoute, createRouteFromActivity } from "~/lib/activities.server"; +import { getActivity, deleteActivity, linkActivityToRoute, createRouteFromActivity } from "~/lib/activities.server"; +import { deleteImportByActivity } from "~/lib/sync/imports.server"; import { listRoutes } from "~/lib/routes.server"; import { ClientDate } from "~/components/ClientDate"; @@ -33,6 +35,7 @@ export async function loader({ params, request }: Route.LoaderArgs) { geojson: activity.geojson ?? null, startedAt: activity.startedAt?.toISOString() ?? null, createdAt: activity.createdAt.toISOString(), + importSource: activity.importSource, }, isOwner, routes: userRoutes.map((r) => ({ id: r.id, name: r.name })), @@ -60,6 +63,13 @@ export async function action({ params, request }: Route.ActionArgs) { return data({ error: "No GPX data to create route from" }, { status: 400 }); } + if (intent === "delete") { + await deleteImportByActivity(params.id); + const deleted = await deleteActivity(params.id, user.id); + if (deleted) return redirect("/activities"); + return data({ error: "Activity not found" }, { status: 404 }); + } + return data({ error: "Unknown action" }, { status: 400 }); } @@ -70,10 +80,18 @@ export function meta({ data: loaderData }: Route.MetaArgs) { export default function ActivityDetailPage({ loaderData }: Route.ComponentProps) { const { activity, isOwner, routes } = loaderData; + const { t } = useTranslation("journal"); return (
-

{activity.name}

+
+

{activity.name}

+ {activity.importSource && ( + + {t("activities.importedFrom", { provider: activity.importSource.provider })} + + )} +
{activity.description && (

{activity.description}

)} @@ -128,7 +146,7 @@ export default function ActivityDetailPage({ loaderData }: Route.ComponentProps)
+ + +
+ )}
); } diff --git a/apps/journal/app/routes/sync.import.$provider.tsx b/apps/journal/app/routes/sync.import.$provider.tsx index 5d01e62..d4fd492 100644 --- a/apps/journal/app/routes/sync.import.$provider.tsx +++ b/apps/journal/app/routes/sync.import.$provider.tsx @@ -128,7 +128,17 @@ export default function SyncImportPage({ loaderData, actionData }: Route.Compone className="flex items-center justify-between rounded-lg border border-gray-200 p-4" >
-

{w.name}

+
+

{w.name}

+ {!w.fileUrl && ( + + {t("sync.noGps")} + + )} +
{w.startedAt && } {w.distance != null && {(w.distance / 1000).toFixed(1)} km} diff --git a/packages/i18n/src/locales/de.ts b/packages/i18n/src/locales/de.ts index cd90218..f4c8f65 100644 --- a/packages/i18n/src/locales/de.ts +++ b/packages/i18n/src/locales/de.ts @@ -134,6 +134,9 @@ export default { duration: "Dauer", linkToRoute: "Mit Route verknüpfen", createRouteFromActivity: "Route aus Aktivität erstellen", + delete: "Aktivität löschen", + deleteConfirm: "Möchtest du diese Aktivität wirklich löschen?", + importedFrom: "Importiert von {{provider}}", }, settings: { title: "Einstellungen", @@ -181,6 +184,8 @@ export default { importFrom: "Import von {{provider}}", imported: "Importiert", noWorkouts: "Keine Workouts gefunden.", + noGps: "Kein GPS", + noGpsTooltip: "{{provider}} stellt für diese Aktivität keine Routendaten bereit", previous: "Zurück", next: "Weiter", }, diff --git a/packages/i18n/src/locales/en.ts b/packages/i18n/src/locales/en.ts index 30fe2ec..fdd94ab 100644 --- a/packages/i18n/src/locales/en.ts +++ b/packages/i18n/src/locales/en.ts @@ -134,6 +134,9 @@ export default { duration: "Duration", linkToRoute: "Link to Route", createRouteFromActivity: "Create Route from Activity", + delete: "Delete Activity", + deleteConfirm: "Are you sure you want to delete this activity?", + importedFrom: "Imported from {{provider}}", }, settings: { title: "Settings", @@ -181,6 +184,8 @@ export default { importFrom: "Import from {{provider}}", imported: "Imported", noWorkouts: "No workouts found.", + noGps: "No GPS", + noGpsTooltip: "{{provider}} does not provide route data for this activity", previous: "Previous", next: "Next", },