From f81ff82f2a07f87f6eb4f43de49ede64215519e5 Mon Sep 17 00:00:00 2001
From: Ariel Costas Guerrero <ariel@costas.dev>
Date: Sat, 27 Dec 2025 16:39:09 +0100
Subject: Refactor route planner to use new GraphQL backend

---
 src/frontend/app/routes/planner.tsx | 106 +++++++++++++++---------------------
 1 file changed, 44 insertions(+), 62 deletions(-)

(limited to 'src/frontend/app/routes/planner.tsx')

diff --git a/src/frontend/app/routes/planner.tsx b/src/frontend/app/routes/planner.tsx
index e99cb03..44488c8 100644
--- a/src/frontend/app/routes/planner.tsx
+++ b/src/frontend/app/routes/planner.tsx
@@ -1,53 +1,24 @@
 import { Coins, CreditCard, Footprints } from "lucide-react";
-import maplibregl, { type StyleSpecification } from "maplibre-gl";
+import maplibregl from "maplibre-gl";
 import "maplibre-gl/dist/maplibre-gl.css";
 import React, { useEffect, useMemo, useRef, useState } from "react";
 import { useTranslation } from "react-i18next";
 import { Layer, Source, type MapRef } from "react-map-gl/maplibre";
 import { useLocation } from "react-router";
 
-import { useApp } from "~/AppContext";
+import { type ConsolidatedCirculation, type Itinerary } from "~/api/schema";
 import LineIcon from "~/components/LineIcon";
 import { PlannerOverlay } from "~/components/PlannerOverlay";
 import { AppMap } from "~/components/shared/AppMap";
 import { APP_CONSTANTS } from "~/config/constants";
 import { usePageTitle } from "~/contexts/PageTitleContext";
-import { type Itinerary } from "~/data/PlannerApi";
 import { usePlanner } from "~/hooks/usePlanner";
 import "../tailwind-full.css";
 
-export interface ConsolidatedCirculation {
-  line: string;
-  route: string;
-  schedule?: {
-    running: boolean;
-    minutes: number;
-    serviceId: string;
-    tripId: string;
-    shapeId?: string;
-  };
-  realTime?: {
-    minutes: number;
-    distance: number;
-  };
-  currentPosition?: {
-    latitude: number;
-    longitude: number;
-    orientationDegrees: number;
-    shapeIndex?: number;
-  };
-  isPreviousTrip?: boolean;
-  previousTripShapeId?: string;
-  nextStreets?: string[];
-}
-
-const FARE_CASH_PER_BUS = 1.63;
-const FARE_CARD_PER_BUS = 0.67;
-
 const formatDistance = (meters: number) => {
-  const intMeters = Math.round(meters);
-  if (intMeters >= 1000) return `${(intMeters / 1000).toFixed(1)} km`;
-  return `${intMeters} m`;
+  if (meters >= 1000) return `${(meters / 1000).toFixed(1)} km`;
+  const rounded = Math.round(meters / 100) * 100;
+  return `${rounded} m`;
 };
 
 const haversineMeters = (a: [number, number], b: [number, number]) => {
@@ -116,12 +87,8 @@ const ItinerarySummary = ({
   const busLegsCount = itinerary.legs.filter(
     (leg) => leg.mode !== "WALK"
   ).length;
-  const cashFare = (
-    itinerary.cashFareEuro ?? busLegsCount * FARE_CASH_PER_BUS
-  ).toFixed(2);
-  const cardFare = (
-    itinerary.cardFareEuro ?? busLegsCount * FARE_CARD_PER_BUS
-  ).toFixed(2);
+  const cashFare = (itinerary.cashFareEuro ?? 0).toFixed(2);
+  const cardFare = (itinerary.cardFareEuro ?? 0).toFixed(2);
 
   return (
     <div
@@ -132,9 +99,7 @@ const ItinerarySummary = ({
         <div className="font-bold text-lg text-text">
           {startTime} - {endTime}
         </div>
-        <div className="text-muted">
-          {durationMinutes} min
-        </div>
+        <div className="text-muted">{durationMinutes} min</div>
       </div>
 
       <div className="flex items-center gap-2 overflow-x-auto pb-2">
@@ -168,6 +133,8 @@ const ItinerarySummary = ({
                   <LineIcon
                     line={leg.routeShortName || leg.routeName || leg.mode || ""}
                     mode="pill"
+                    colour={leg.routeColor || undefined}
+                    textColour={leg.routeTextColor || undefined}
                   />
                 </div>
               )}
@@ -180,7 +147,7 @@ const ItinerarySummary = ({
         <span>
           {t("planner.walk")}: {formatDistance(walkTotals.meters)}
           {walkTotals.minutes
-            ? ` • ${walkTotals.minutes} {t("estimates.minutes")}`
+            ? ` • ${walkTotals.minutes} ${t("estimates.minutes")}`
             : ""}
         </span>
         <span className="flex items-center gap-3">
@@ -566,7 +533,7 @@ const ItineraryDetail = ({
       </div>
 
       {/* Details Panel */}
-      <div className="h-1/3 md:h-full md:w-96 lg:w-md overflow-y-auto bg-white dark:bg-slate-900 border-t md:border-t-0 md:border-l border-slate-200 dark:border-slate-700">
+      <div className="h-1/3 md:h-full md:w-96 lg:w-lg overflow-y-auto bg-white dark:bg-slate-900 border-t md:border-t-0 md:border-l border-slate-200 dark:border-slate-700">
         <div className="px-4 py-4">
           <h2 className="text-xl font-bold mb-4 text-slate-900 dark:text-slate-100">
             {t("planner.itinerary_details")}
@@ -575,7 +542,7 @@ const ItineraryDetail = ({
           <div>
             {itinerary.legs.map((leg, idx) => (
               <div key={idx} className="flex gap-3">
-                <div className="flex flex-col items-center">
+                <div className="flex flex-col items-center w-20 shrink-0">
                   {leg.mode === "WALK" ? (
                     <div
                       className="w-8 h-8 rounded-full flex items-center justify-center text-xs font-bold shadow-sm"
@@ -587,6 +554,8 @@ const ItineraryDetail = ({
                     <LineIcon
                       line={leg.routeShortName || leg.routeName || ""}
                       mode="rounded"
+                      colour={leg.routeColor || undefined}
+                      textColour={leg.routeTextColor || undefined}
                     />
                   )}
                   {idx < itinerary.legs.length - 1 && (
@@ -598,29 +567,42 @@ const ItineraryDetail = ({
                     {leg.mode === "WALK" ? (
                       t("planner.walk")
                     ) : (
-                      <>
-                        <span>
+                      <div className="flex flex-col">
+                        <span className="text-[10px] uppercase text-muted font-bold leading-none mb-1">
+                          {t("planner.direction")}
+                        </span>
+                        <span className="leading-tight">
                           {leg.headsign ||
                             leg.routeLongName ||
                             leg.routeName ||
                             ""}
                         </span>
-                      </>
+                      </div>
                     )}
                   </div>
-                  <div className="text-sm text-gray-600 dark:text-gray-400">
-                    {new Date(leg.startTime).toLocaleTimeString([], {
-                      hour: "2-digit",
-                      minute: "2-digit",
-                      timeZone: "Europe/Madrid",
-                    })}{" "}
-                    -{" "}
-                    {(
-                      (new Date(leg.endTime).getTime() -
-                        new Date(leg.startTime).getTime()) /
-                      60000
-                    ).toFixed(0)}{" "}
-                    {t("estimates.minutes")}
+                  <div className="text-sm text-gray-600 dark:text-gray-400 flex flex-wrap gap-x-3 gap-y-1 mt-1">
+                    <span>
+                      {new Date(leg.startTime).toLocaleTimeString([], {
+                        hour: "2-digit",
+                        minute: "2-digit",
+                        timeZone: "Europe/Madrid",
+                      })}{" "}
+                      -{" "}
+                      {(
+                        (new Date(leg.endTime).getTime() -
+                          new Date(leg.startTime).getTime()) /
+                        60000
+                      ).toFixed(0)}{" "}
+                      {t("estimates.minutes")}
+                    </span>
+                    <span>•</span>
+                    <span>{formatDistance(leg.distanceMeters)}</span>
+                    {leg.agencyName && (
+                      <>
+                        <span>•</span>
+                        <span className="italic">{leg.agencyName}</span>
+                      </>
+                    )}
                   </div>
                   {leg.mode !== "WALK" &&
                     leg.from?.stopId &&
-- 
cgit v1.3

