diff options
Diffstat (limited to 'src/frontend/app/components')
22 files changed, 121 insertions, 992 deletions
diff --git a/src/frontend/app/components/GroupedTable.tsx b/src/frontend/app/components/GroupedTable.tsx deleted file mode 100644 index 175899a..0000000 --- a/src/frontend/app/components/GroupedTable.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { type RegionConfig } from "../config/RegionConfig"; -import { type Estimate } from "../routes/estimates-$id"; -import LineIcon from "./LineIcon"; - -interface GroupedTable { - data: Estimate[]; - dataDate: Date | null; - regionConfig: RegionConfig; -} - -export const GroupedTable: React.FC<GroupedTable> = ({ - data, - dataDate, - regionConfig, -}) => { - const formatDistance = (meters: number) => { - if (meters > 1024) { - return `${(meters / 1000).toFixed(1)} km`; - } else { - return `${meters} m`; - } - }; - - const groupedEstimates = data.reduce( - (acc, estimate) => { - if (!acc[estimate.line]) { - acc[estimate.line] = []; - } - acc[estimate.line].push(estimate); - return acc; - }, - {} as Record<string, typeof data> - ); - - const sortedLines = Object.keys(groupedEstimates).sort((a, b) => { - const firstArrivalA = groupedEstimates[a][0].minutes; - const firstArrivalB = groupedEstimates[b][0].minutes; - return firstArrivalA - firstArrivalB; - }); - - return ( - <table className="table"> - <caption> - Estimaciones de llegadas a las {dataDate?.toLocaleTimeString()} - </caption> - - <thead> - <tr> - <th>Línea</th> - <th>Ruta</th> - <th>Llegada</th> - {regionConfig.showMeters && <th>Distancia</th>} - </tr> - </thead> - - <tbody> - {sortedLines.map((line) => - groupedEstimates[line].map((estimate, idx) => ( - <tr key={`${line}-${idx}`}> - {idx === 0 && ( - <td rowSpan={groupedEstimates[line].length}> - <LineIcon line={line} region={regionConfig.id} /> - </td> - )} - <td>{estimate.route}</td> - <td>{`${estimate.minutes} min`}</td> - {regionConfig.showMeters && ( - <td> - {estimate.meters > -1 - ? formatDistance(estimate.meters) - : "No disponible"} - </td> - )} - </tr> - )) - )} - </tbody> - - {data?.length === 0 && ( - <tfoot> - <tr> - <td colSpan={regionConfig.showMeters ? 4 : 3}> - No hay estimaciones disponibles - </td> - </tr> - </tfoot> - )} - </table> - ); -}; diff --git a/src/frontend/app/components/LineIcon.css b/src/frontend/app/components/LineIcon.css index 01ff2bd..89f8bdb 100644 --- a/src/frontend/app/components/LineIcon.css +++ b/src/frontend/app/components/LineIcon.css @@ -71,6 +71,18 @@ --line-u2-text: hsl(0, 0%, 100%); --line-vts: hsl(300, 33%, 30%); --line-vts-text: hsl(0, 0%, 100%); + + /* Special christmas line - Touristic bus */ + --line-nad: hsl(0, 100%, 40%); + --line-nad-text: hsl(0, 0%, 100%); + + --line-mar: hsl(208, 68%, 66%); + --line-mar-text: hsl(0, 0%, 100%); + --line-rio: hsl(208, 68%, 66%); + --line-rio-text: hsl(0, 0%, 100%); + --line-gol: hsl(208, 68%, 66%); + --line-gol-text: hsl(0, 0%, 100%); + } .line-icon-default { diff --git a/src/frontend/app/components/LineIcon.tsx b/src/frontend/app/components/LineIcon.tsx index 5ccf80a..fc40824 100644 --- a/src/frontend/app/components/LineIcon.tsx +++ b/src/frontend/app/components/LineIcon.tsx @@ -1,25 +1,23 @@ import React, { useMemo } from "react"; -import { type RegionId } from "../config/RegionConfig"; import "./LineIcon.css"; interface LineIconProps { line: string; - - /** - * @deprecated Unused since region is only Vigo - */ - region?: RegionId; - - mode?: "rounded"|"pill"|"default"; + mode?: "rounded" | "pill" | "default"; } const LineIcon: React.FC<LineIconProps> = ({ line, mode = "default", }) => { + const actualLine = useMemo(() => { + return line.trim().replace('510', 'NAD'); + }, [line]) + const formattedLine = useMemo(() => { - return /^[a-zA-Z]/.test(line) ? line : `L${line}`; - }, [line]); + return /^[a-zA-Z]/.test(actualLine) ? actualLine : `L${actualLine}`; + }, [actualLine]); + const cssVarName = `--line-${formattedLine.toLowerCase()}`; const cssTextVarName = `--line-${formattedLine.toLowerCase()}-text`; @@ -33,7 +31,7 @@ const LineIcon: React.FC<LineIconProps> = ({ } as React.CSSProperties } > - {line} + {actualLine} </span> ); }; diff --git a/src/frontend/app/components/PullToRefresh.css b/src/frontend/app/components/PullToRefresh.css index d4946d2..3e8f802 100644 --- a/src/frontend/app/components/PullToRefresh.css +++ b/src/frontend/app/components/PullToRefresh.css @@ -6,7 +6,7 @@ .pull-to-refresh-indicator { position: fixed; - top: 20px; + top: 80px; left: 50%; transform: translateX(-50%); z-index: 1000; diff --git a/src/frontend/app/components/PullToRefresh.tsx b/src/frontend/app/components/PullToRefresh.tsx index b3abe86..2de1a4f 100644 --- a/src/frontend/app/components/PullToRefresh.tsx +++ b/src/frontend/app/components/PullToRefresh.tsx @@ -1,6 +1,6 @@ -import React, { useRef, useState, useEffect, useCallback } from "react"; import { motion, useMotionValue, useTransform } from "framer-motion"; import { RefreshCw } from "lucide-react"; +import React, { useCallback, useEffect, useRef, useState } from "react"; import "./PullToRefresh.css"; interface PullToRefreshProps { @@ -26,15 +26,6 @@ export const PullToRefresh: React.FC<PullToRefreshProps> = ({ const scale = useTransform(y, [0, threshold], [0.5, 1]); const rotate = useTransform(y, [0, threshold], [0, 180]); - const isAtPageTop = useCallback(() => { - const scrollTop = - window.pageYOffset || - document.documentElement.scrollTop || - document.body.scrollTop || - 0; - return scrollTop <= 10; // Increased tolerance to 10px - }, []); - const handleTouchStart = useCallback( (e: TouchEvent) => { // Very strict check - must be at absolute top @@ -160,7 +151,6 @@ export const PullToRefresh: React.FC<PullToRefreshProps> = ({ return ( <div className="pull-to-refresh-container" ref={containerRef}> - {/* Simple indicator */} {isPulling && ( <motion.div className="pull-to-refresh-indicator" style={{ opacity }}> <motion.div @@ -174,7 +164,6 @@ export const PullToRefresh: React.FC<PullToRefreshProps> = ({ </motion.div> )} - {/* Normal content - no transform interference */} <div className="pull-to-refresh-content">{children}</div> </div> ); diff --git a/src/frontend/app/components/RegularTable.tsx b/src/frontend/app/components/RegularTable.tsx deleted file mode 100644 index a738d03..0000000 --- a/src/frontend/app/components/RegularTable.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { type RegionConfig } from "../config/RegionConfig"; -import { type Estimate } from "../routes/estimates-$id"; -import LineIcon from "./LineIcon"; - -interface RegularTableProps { - data: Estimate[]; - dataDate: Date | null; - regionConfig: RegionConfig; -} - -export const RegularTable: React.FC<RegularTableProps> = ({ - data, - dataDate, - regionConfig, -}) => { - const { t } = useTranslation(); - - const absoluteArrivalTime = (minutes: number) => { - const now = new Date(); - const arrival = new Date(now.getTime() + minutes * 60000); - return Intl.DateTimeFormat( - typeof navigator !== "undefined" ? navigator.language : "en", - { - hour: "2-digit", - minute: "2-digit", - } - ).format(arrival); - }; - - const formatDistance = (meters: number) => { - if (meters > 1024) { - return `${(meters / 1000).toFixed(1)} km`; - } else { - return `${meters} ${t("estimates.meters", "m")}`; - } - }; - - return ( - <table className="table"> - <caption> - {t("estimates.caption", "Estimaciones de llegadas a las {{time}}", { - time: dataDate?.toLocaleTimeString(), - })} - </caption> - - <thead> - <tr> - <th>{t("estimates.line", "Línea")}</th> - <th>{t("estimates.route", "Ruta")}</th> - <th>{t("estimates.arrival", "Llegada")}</th> - {regionConfig.showMeters && ( - <th>{t("estimates.distance", "Distancia")}</th> - )} - </tr> - </thead> - - <tbody> - {data - .sort((a, b) => a.minutes - b.minutes) - .map((estimate, idx) => ( - <tr key={idx}> - <td> - <LineIcon line={estimate.line} region={regionConfig.id} /> - </td> - <td>{estimate.route}</td> - <td> - {estimate.minutes > 15 - ? absoluteArrivalTime(estimate.minutes) - : `${estimate.minutes} ${t("estimates.minutes", "min")}`} - </td> - {regionConfig.showMeters && ( - <td> - {estimate.meters > -1 - ? formatDistance(estimate.meters) - : t("estimates.not_available", "No disponible")} - </td> - )} - </tr> - ))} - </tbody> - - {data?.length === 0 && ( - <tfoot> - <tr> - <td colSpan={regionConfig.showMeters ? 4 : 3}> - {t("estimates.none", "No hay estimaciones disponibles")} - </td> - </tr> - </tfoot> - )} - </table> - ); -}; diff --git a/src/frontend/app/components/SchedulesTable.css b/src/frontend/app/components/SchedulesTable.css deleted file mode 100644 index c0c5cb7..0000000 --- a/src/frontend/app/components/SchedulesTable.css +++ /dev/null @@ -1,208 +0,0 @@ -.timetable-container { - margin-top: 2rem; -} - -.timetable-caption { - font-weight: bold; - margin-bottom: 1rem; - text-align: left; - font-size: 1.1rem; - color: var(--text-primary); -} - -.timetable-cards { - display: flex; - flex-direction: column; - gap: 1rem; - margin-bottom: 1rem; -} - -.timetable-card { - background-color: var(--surface-future); - border: 1px solid var(--card-border); - border-radius: 10px; - padding: 1.25rem; - transition: - background-color 0.2s ease, - border 0.2s ease; -} - -/* Next upcoming service: slight emphasis */ -.timetable-card.timetable-next { - background-color: var(--surface-next); - border-color: var(--card-border); - position: relative; -} - -.timetable-card.timetable-next::before { - content: ""; - position: absolute; - left: 0; - top: 0; - bottom: 0; - width: 4px; - border-top-left-radius: 10px; - border-bottom-left-radius: 10px; - background: var(--accent-next); -} - -.timetable-card.timetable-past { - background-color: var(--surface-past); - color: var(--text-secondary); - border: 1px solid var(--card-border); -} - -.timetable-card .card-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 1rem; -} - -.timetable-card .line-info { - flex-shrink: 0; -} - -.timetable-card .destination-info { - flex: 1; - text-align: left; - margin: 0 1rem; - color: var(--text-primary); -} - -.timetable-card .destination-info strong { - font-size: 0.95rem; -} - -.timetable-card.timetable-past .destination-info { - color: var(--text-secondary); -} - -.timetable-card .time-info { - display: flex; - flex-direction: column; - align-items: flex-end; - flex-shrink: 0; -} - -.timetable-card .timetable-card .departure-time { - font-weight: bold; - font-family: monospace; - font-size: 1.1rem; - color: var(--text-primary); -} - -.timetable-card.timetable-past .departure-time { - color: var(--text-secondary); -} - -.timetable-card .card-body { - line-height: 1.4; -} - -.timetable-card .route-streets { - font-size: 0.85rem; - color: var(--text-secondary); - line-height: 1.8; - word-break: break-word; -} - -.timetable-card .timetable-card .service-id { - font-family: monospace; - font-size: 0.8rem; - color: var(--text-secondary); - background: var(--service-background); - padding: 0.15rem 0.4rem; - border-radius: 3px; - font-weight: 500; - display: inline; - margin-right: 0.2em; -} - -.timetable-card.timetable-past .service-id { - color: var(--text-secondary); - background: var(--service-background-past); -} - -.timetable-container .no-data { - text-align: center; - color: var(--text-secondary); - font-style: italic; - padding: 2rem; - background: var(--card-background); - border-radius: 8px; - border: 1px solid var(--card-border); -} - -/* Responsive design */ -@media (max-width: 768px) { - .timetable-cards { - gap: 0.5rem; - } - .timetable-card { - padding: 0.75rem; - } - .timetable-card .card-header { - margin-bottom: 0.5rem; - } - .timetable-card .destination-info { - margin: 0 0.5rem; - } - .timetable-card .destination-info strong { - font-size: 0.9rem; - } - .timetable-card .departure-time { - font-size: 1rem; - } - .timetable-card .service-id { - font-size: 0.8rem; - padding: 0.2rem 0.4rem; - } -} - -@media (max-width: 480px) { - .timetable-card .card-header { - flex-direction: column; - align-items: stretch; - gap: 0.5rem; - } - - .timetable-card .destination-info { - text-align: left; - margin: 0; - order: 2; - } - - .timetable-card .time-info { - align-items: flex-start; - order: 1; - align-self: flex-end; - } - - .timetable-card .line-info { - order: 0; - align-self: flex-start; - } - - /* Create a flex container for line and time on mobile */ - .timetable-card .card-header { - position: relative; - } - - .timetable-card .line-info { - position: absolute; - left: 0; - top: 0; - } - - .timetable-card .time-info { - position: absolute; - right: 0; - top: 0; - } - - .timetable-card .destination-info { - margin-top: 2rem; - text-align: left; - } -} diff --git a/src/frontend/app/components/SchedulesTable.tsx b/src/frontend/app/components/SchedulesTable.tsx deleted file mode 100644 index faf7b01..0000000 --- a/src/frontend/app/components/SchedulesTable.tsx +++ /dev/null @@ -1,213 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { useApp } from "~/AppContext"; -import LineIcon from "./LineIcon"; -import "./SchedulesTable.css"; - -export type ScheduledTable = { - trip_id: string; - service_id: string; - - line: string; - route: string; - - stop_sequence: number; - shape_dist_traveled: number; - next_streets: string[]; - - starting_code: string; - starting_name: string; - starting_time: string; - - calling_time: string; - calling_ssm: number; - - terminus_code: string; - terminus_name: string; - terminus_time: string; -}; - -interface TimetableTableProps { - data: ScheduledTable[]; - showAll?: boolean; - currentTime?: string; // HH:MM:SS format -} - -// Utility function to parse service ID and get the turn number -const parseServiceId = (serviceId: string): string => { - const parts = serviceId.split("_"); - if (parts.length === 0) return ""; - - const lastPart = parts[parts.length - 1]; - if (lastPart.length < 6) return ""; - - const last6 = lastPart.slice(-6); - const lineCode = last6.slice(0, 3); - const turnCode = last6.slice(-3); - - // Remove leading zeros from turn - const turnNumber = parseInt(turnCode, 10).toString(); - - // Parse line number with special cases - const lineNumber = parseInt(lineCode, 10); - let displayLine: string; - - switch (lineNumber) { - case 1: - displayLine = "C1"; - break; - case 3: - displayLine = "C3"; - break; - case 30: - displayLine = "N1"; - break; - case 33: - displayLine = "N4"; - break; - case 8: - displayLine = "A"; - break; - case 101: - displayLine = "H"; - break; - case 150: - displayLine = "REF"; - break; - case 201: - displayLine = "U1"; - break; - case 202: - displayLine = "U2"; - break; - case 500: - displayLine = "TUR"; - break; - default: - displayLine = `L${lineNumber}`; - } - - return `${displayLine}-${turnNumber}`; -}; - -// Utility function to compare times -const timeToMinutes = (time: string): number => { - const [hours, minutes] = time.split(":").map(Number); - return hours * 60 + minutes; -}; - -// Utility function to format GTFS time for display (handle hours >= 24) -const formatTimeForDisplay = (time: string): string => { - const [hours, minutes] = time.split(":").map(Number); - const normalizedHours = hours % 24; - return `${normalizedHours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`; -}; - -// Utility function to find nearby entries -const findNearbyEntries = ( - entries: ScheduledTable[], - currentTime: string, - before: number = 4, - after: number = 4 -): ScheduledTable[] => { - if (!currentTime) return entries.slice(0, before + after); - - const currentMinutes = timeToMinutes(currentTime); - const sortedEntries = [...entries].sort( - (a, b) => timeToMinutes(a.calling_time) - timeToMinutes(b.calling_time) - ); - - let currentIndex = sortedEntries.findIndex( - (entry) => timeToMinutes(entry.calling_time) >= currentMinutes - ); - - if (currentIndex === -1) { - // All entries are before current time, show last ones - return sortedEntries.slice(-before - after); - } - - const startIndex = Math.max(0, currentIndex - before); - const endIndex = Math.min(sortedEntries.length, currentIndex + after); - - return sortedEntries.slice(startIndex, endIndex); -}; - -export const SchedulesTable: React.FC<TimetableTableProps> = ({ - data, - showAll = false, - currentTime, -}) => { - const { t } = useTranslation(); - const { region } = useApp(); - - const displayData = showAll - ? data - : findNearbyEntries(data, currentTime || ""); - const nowMinutes = currentTime - ? timeToMinutes(currentTime) - : timeToMinutes(new Date().toTimeString().slice(0, 8)); - - return ( - <div className="timetable-container"> - <div className="timetable-caption"> - {showAll - ? t("timetable.fullCaption", "Horarios teóricos de la parada") - : t("timetable.nearbyCaption", "Próximos horarios teóricos")} - </div> - - <div className="timetable-cards"> - {displayData.map((entry, index) => { - const entryMinutes = timeToMinutes(entry.calling_time); - const isPast = entryMinutes < nowMinutes; - return ( - <div - key={`${entry.trip_id}-${index}`} - className={`timetable-card${isPast ? " timetable-past" : ""}`} - style={{ - background: isPast - ? "var(--surface-past, #f3f3f3)" - : "var(--surface-future, #fff)", - }} - > - <div className="card-header"> - <div className="line-info"> - <LineIcon line={entry.line} region={region} /> - </div> - - <div className="destination-info"> - {entry.route && entry.route.trim() ? ( - <strong>{entry.route}</strong> - ) : ( - <strong> - {t("timetable.noDestination", "Línea")} {entry.line} - </strong> - )} - </div> - - <div className="time-info"> - <span className="departure-time"> - {formatTimeForDisplay(entry.calling_time)} - </span> - </div> - </div> - <div className="card-body"> - <div className="route-streets"> - <span className="service-id"> - {parseServiceId(entry.service_id)} - </span> - {entry.next_streets.length > 0 && ( - <span> — {entry.next_streets.join(" — ")}</span> - )} - </div> - </div> - </div> - ); - })} - </div> - {displayData.length === 0 && ( - <p className="no-data"> - {t("timetable.noData", "No hay datos de horarios disponibles")} - </p> - )} - </div> - ); -}; diff --git a/src/frontend/app/components/SchedulesTableSkeleton.tsx b/src/frontend/app/components/SchedulesTableSkeleton.tsx deleted file mode 100644 index 3ae9729..0000000 --- a/src/frontend/app/components/SchedulesTableSkeleton.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import React from "react"; -import Skeleton, { SkeletonTheme } from "react-loading-skeleton"; -import "react-loading-skeleton/dist/skeleton.css"; -import { useTranslation } from "react-i18next"; - -interface EstimatesTableSkeletonProps { - rows?: number; -} - -export const SchedulesTableSkeleton: React.FC<EstimatesTableSkeletonProps> = ({ - rows = 3, -}) => { - const { t } = useTranslation(); - - return ( - <SkeletonTheme baseColor="#f0f0f0" highlightColor="#e0e0e0"> - <table className="table"> - <caption> - <Skeleton width="250px" /> - </caption> - - <thead> - <tr> - <th>{t("estimates.line", "Línea")}</th> - <th>{t("estimates.route", "Ruta")}</th> - <th>{t("estimates.arrival", "Llegada")}</th> - <th>{t("estimates.distance", "Distancia")}</th> - </tr> - </thead> - - <tbody> - {Array.from({ length: rows }, (_, index) => ( - <tr key={`skeleton-${index}`}> - <td> - <Skeleton - width="40px" - height="24px" - style={{ borderRadius: "4px" }} - /> - </td> - <td> - <Skeleton width="120px" /> - </td> - <td> - <div - style={{ - display: "flex", - flexDirection: "column", - gap: "2px", - }} - > - <Skeleton width="60px" /> - <Skeleton width="40px" /> - </div> - </td> - <td> - <Skeleton width="50px" /> - </td> - </tr> - ))} - </tbody> - </table> - </SkeletonTheme> - ); -}; - -interface EstimatesGroupedSkeletonProps { - groups?: number; - rowsPerGroup?: number; -} - -export const EstimatesGroupedSkeleton: React.FC< - EstimatesGroupedSkeletonProps -> = ({ groups = 3, rowsPerGroup = 2 }) => { - const { t } = useTranslation(); - - return ( - <SkeletonTheme baseColor="#f0f0f0" highlightColor="#e0e0e0"> - <table className="table grouped-table"> - <caption> - <Skeleton width="250px" /> - </caption> - - <thead> - <tr> - <th>{t("estimates.line", "Línea")}</th> - <th>{t("estimates.route", "Ruta")}</th> - <th>{t("estimates.arrival", "Llegada")}</th> - <th>{t("estimates.distance", "Distancia")}</th> - </tr> - </thead> - - <tbody> - {Array.from({ length: groups }, (_, groupIndex) => ( - <React.Fragment key={`group-${groupIndex}`}> - {Array.from({ length: rowsPerGroup }, (_, rowIndex) => ( - <tr - key={`skeleton-${groupIndex}-${rowIndex}`} - className={rowIndex === 0 ? "group-start" : ""} - > - <td> - {rowIndex === 0 && ( - <Skeleton - width="40px" - height="24px" - style={{ borderRadius: "4px" }} - /> - )} - </td> - <td> - <Skeleton width="120px" /> - </td> - <td> - <div - style={{ - display: "flex", - flexDirection: "column", - gap: "2px", - }} - > - <Skeleton width="60px" /> - <Skeleton width="40px" /> - </div> - </td> - <td> - <Skeleton width="50px" /> - </td> - </tr> - ))} - </React.Fragment> - ))} - </tbody> - </table> - </SkeletonTheme> - ); -}; diff --git a/src/frontend/app/components/StopGalleryItem.tsx b/src/frontend/app/components/StopGalleryItem.tsx index b32661a..72a13e5 100644 --- a/src/frontend/app/components/StopGalleryItem.tsx +++ b/src/frontend/app/components/StopGalleryItem.tsx @@ -1,30 +1,26 @@ import React from "react"; import { Link } from "react-router"; -import { type Stop } from "../data/StopDataProvider"; +import StopDataProvider, { type Stop } from "../data/StopDataProvider"; import LineIcon from "./LineIcon"; -import { useApp } from "../AppContext"; -import StopDataProvider from "../data/StopDataProvider"; interface StopGalleryItemProps { stop: Stop; } const StopGalleryItem: React.FC<StopGalleryItemProps> = ({ stop }) => { - const { region } = useApp(); - return ( <div className="gallery-item"> - <Link className="gallery-item-link" to={`/estimates/${stop.stopId}`}> + <Link className="gallery-item-link" to={`/stops/${stop.stopId}`}> <div className="gallery-item-header"> {stop.favourite && <span className="favourite-icon">★</span>} <span className="gallery-item-code">({stop.stopId})</span> </div> <div className="gallery-item-name"> - {StopDataProvider.getDisplayName(region, stop)} + {StopDataProvider.getDisplayName(stop)} </div> <div className="gallery-item-lines"> {stop.lines?.slice(0, 5).map((line) => ( - <LineIcon key={line} line={line} region={region} /> + <LineIcon key={line} line={line} /> ))} {stop.lines && stop.lines.length > 5 && ( <span className="more-lines">+{stop.lines.length - 5}</span> diff --git a/src/frontend/app/components/StopItem.tsx b/src/frontend/app/components/StopItem.tsx index de51576..7875b59 100644 --- a/src/frontend/app/components/StopItem.tsx +++ b/src/frontend/app/components/StopItem.tsx @@ -1,6 +1,5 @@ import React from "react"; import { Link } from "react-router"; -import { useApp } from "../AppContext"; import StopDataProvider, { type Stop } from "../data/StopDataProvider"; import LineIcon from "./LineIcon"; @@ -9,15 +8,13 @@ interface StopItemProps { } const StopItem: React.FC<StopItemProps> = ({ stop }) => { - const { region } = useApp(); - return ( <li className="list-item"> - <Link className="list-item-link" to={`/estimates/${stop.stopId}`}> + <Link className="list-item-link" to={`/stops/${stop.stopId}`}> <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline" }}> <span style={{ fontWeight: 600 }}> {stop.favourite && <span className="favourite-icon">★</span>} - {StopDataProvider.getDisplayName(region, stop)} + {StopDataProvider.getDisplayName(stop)} </span> <span style={{ fontSize: "0.85em", color: "var(--subtitle-color)", marginLeft: "0.5rem" }}> ({stop.stopId}) @@ -25,7 +22,7 @@ const StopItem: React.FC<StopItemProps> = ({ stop }) => { </div> <div className="line-icons" style={{ marginTop: "0.25rem" }}> {stop.lines?.map((line) => ( - <LineIcon key={line} line={line} region={region} /> + <LineIcon key={line} line={line} /> ))} </div> </Link> diff --git a/src/frontend/app/components/StopMapModal.tsx b/src/frontend/app/components/StopMapModal.tsx index 74f20d9..55ad848 100644 --- a/src/frontend/app/components/StopMapModal.tsx +++ b/src/frontend/app/components/StopMapModal.tsx @@ -1,15 +1,15 @@ import maplibregl from "maplibre-gl"; import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import Map, { - Layer, - Marker, - Source, - type MapRef + Layer, + Marker, + Source, + type MapRef } from "react-map-gl/maplibre"; import { Sheet } from "react-modal-sheet"; import { useApp } from "~/AppContext"; -import { getRegionConfig, type RegionId } from "~/config/RegionConfig"; -import { getLineColor } from "~/data/LineColors"; +import { REGION_DATA } from "~/config/RegionConfig"; +import { getLineColour } from "~/data/LineColors"; import type { Stop } from "~/data/StopDataProvider"; import { loadStyle } from "~/maps/styleloader"; import "./StopMapModal.css"; @@ -37,7 +37,6 @@ export interface ConsolidatedCirculationForMap { interface StopMapModalProps { stop: Stop; circulations: ConsolidatedCirculationForMap[]; - region: RegionId; isOpen: boolean; onClose: () => void; selectedCirculationId?: string; @@ -46,7 +45,6 @@ interface StopMapModalProps { export const StopMapModal: React.FC<StopMapModalProps> = ({ stop, circulations, - region, isOpen, onClose, selectedCirculationId, @@ -59,8 +57,6 @@ export const StopMapModal: React.FC<StopMapModalProps> = ({ const [shapeData, setShapeData] = useState<any | null>(null); const [previousShapeData, setPreviousShapeData] = useState<any | null>(null); - const regionConfig = getRegionConfig(region); - // Filter circulations that have GPS coordinates const busesWithPosition = useMemo( () => circulations.filter((c) => !!c.currentPosition), @@ -165,7 +161,7 @@ export const StopMapModal: React.FC<StopMapModalProps> = ({ maxZoom: 17, } as any); } - } catch {} + } catch { } }, [stop, selectedBus, shapeData, previousShapeData]); // Load style without traffic layers for the stop map @@ -246,7 +242,7 @@ export const StopMapModal: React.FC<StopMapModalProps> = ({ !selectedBus || !selectedBus.schedule?.shapeId || selectedBus.currentPosition?.shapeIndex === undefined || - !regionConfig.shapeEndpoint + !REGION_DATA.shapeEndpoint ) { setShapeData(null); setPreviousShapeData(null); @@ -266,7 +262,7 @@ export const StopMapModal: React.FC<StopMapModalProps> = ({ sLat?: number, sLon?: number ) => { - let url = `${regionConfig.shapeEndpoint}?shapeId=${sId}`; + let url = `${REGION_DATA.shapeEndpoint}?shapeId=${sId}`; if (bIndex !== undefined) url += `&busShapeIndex=${bIndex}`; if (sIndex !== undefined) url += `&stopShapeIndex=${sIndex}`; else if (sLat && sLon) url += `&stopLat=${sLat}&stopLon=${sLon}`; @@ -334,7 +330,7 @@ export const StopMapModal: React.FC<StopMapModalProps> = ({ }; loadShapes().catch((err) => console.error("Failed to load shape", err)); - }, [isOpen, selectedBus, regionConfig.shapeEndpoint]); + }, [isOpen, selectedBus]); if (busesWithPosition.length === 0) { return null; // Don't render if no buses with GPS coordinates @@ -362,7 +358,7 @@ export const StopMapModal: React.FC<StopMapModalProps> = ({ }} style={{ width: "100%", height: "50vh" }} mapStyle={styleSpec} - attributionControl={{compact: false, customAttribution: "Concello de Vigo & Viguesa de Transportes SL"}} + attributionControl={{ compact: false, customAttribution: "Concello de Vigo & Viguesa de Transportes SL" }} ref={mapRef} interactive={true} onMove={(e) => { @@ -422,7 +418,7 @@ export const StopMapModal: React.FC<StopMapModalProps> = ({ id="prev-route-shape-inner" type="line" paint={{ - "line-color": getLineColor(region, selectedBus.line) + "line-color": getLineColour(selectedBus.line) .background, "line-width": 4, "line-dasharray": [2, 2], @@ -455,7 +451,7 @@ export const StopMapModal: React.FC<StopMapModalProps> = ({ id="route-shape-inner" type="line" paint={{ - "line-color": getLineColor(region, selectedBus.line) + "line-color": getLineColour(selectedBus.line) .background, "line-width": 3, "line-opacity": 0.7, @@ -534,7 +530,7 @@ export const StopMapModal: React.FC<StopMapModalProps> = ({ > <path d="M12 2 L22 22 L12 17 L2 22 Z" - fill={getLineColor(region, selectedBus.line).background} + fill={getLineColour(selectedBus.line).background} stroke="#000" strokeWidth="2" strokeLinejoin="round" diff --git a/src/frontend/app/components/StopMapSheet.tsx b/src/frontend/app/components/StopMapSheet.tsx index afad1c3..b00ca1c 100644 --- a/src/frontend/app/components/StopMapSheet.tsx +++ b/src/frontend/app/components/StopMapSheet.tsx @@ -2,8 +2,8 @@ import maplibregl from "maplibre-gl"; import React, { useEffect, useMemo, useRef, useState } from "react"; import Map, { Layer, Marker, Source, type MapRef } from "react-map-gl/maplibre"; import { useApp } from "~/AppContext"; -import { getRegionConfig, type RegionId } from "~/config/RegionConfig"; -import { getLineColor } from "~/data/LineColors"; +import { REGION_DATA } from "~/config/RegionConfig"; +import { getLineColour } from "~/data/LineColors"; import type { Stop } from "~/data/StopDataProvider"; import { loadStyle } from "~/maps/styleloader"; import "./StopMapSheet.css"; @@ -28,13 +28,11 @@ export interface ConsolidatedCirculationForMap { interface StopMapProps { stop: Stop; circulations: ConsolidatedCirculationForMap[]; - region: RegionId; } export const StopMap: React.FC<StopMapProps> = ({ stop, - circulations, - region, + circulations }) => { const { theme } = useApp(); const [styleSpec, setStyleSpec] = useState<any | null>(null); @@ -70,18 +68,16 @@ export const StopMap: React.FC<StopMapProps> = ({ const [showAttribution, setShowAttribution] = useState(false); const [shapes, setShapes] = useState<Record<string, any>>({}); - const regionConfig = getRegionConfig(region); - useEffect(() => { circulations.forEach((c) => { if ( c.schedule?.shapeId && c.currentPosition?.shapeIndex !== undefined && - regionConfig.shapeEndpoint + REGION_DATA.shapeEndpoint ) { const key = `${c.schedule.shapeId}_${c.currentPosition.shapeIndex}`; if (!shapes[key]) { - let url = `${regionConfig.shapeEndpoint}?shapeId=${c.schedule.shapeId}&busShapeIndex=${c.currentPosition.shapeIndex}`; + let url = `${REGION_DATA.shapeEndpoint}?shapeId=${c.schedule.shapeId}&busShapeIndex=${c.currentPosition.shapeIndex}`; if (c.stopShapeIndex !== undefined) { url += `&stopShapeIndex=${c.stopShapeIndex}`; } else { @@ -102,7 +98,7 @@ export const StopMap: React.FC<StopMapProps> = ({ } } }); - }, [circulations, regionConfig.shapeEndpoint, shapes]); + }, [circulations, shapes]); type Pt = { lat: number; lon: number }; const haversineKm = (a: Pt, b: Pt) => { @@ -195,7 +191,7 @@ export const StopMap: React.FC<StopMapProps> = ({ accuracy: pos.coords.accuracy, }); }, - () => {}, + () => { }, { enableHighAccuracy: true, maximumAge: 15000, timeout: 5000 } ); geoWatchId.current = navigator.geolocation.watchPosition( @@ -206,15 +202,15 @@ export const StopMap: React.FC<StopMapProps> = ({ accuracy: pos.coords.accuracy, }); }, - () => {}, + () => { }, { enableHighAccuracy: true, maximumAge: 30000, timeout: 10000 } ); - } catch {} + } catch { } return () => { if (geoWatchId.current != null && "geolocation" in navigator) { try { navigator.geolocation.clearWatch(geoWatchId.current); - } catch {} + } catch { } } }; }, []); @@ -278,7 +274,7 @@ export const StopMap: React.FC<StopMapProps> = ({ maxZoom: 17, } as any); } - } catch {} + } catch { } }; const handleCenter = () => { @@ -318,7 +314,7 @@ export const StopMap: React.FC<StopMapProps> = ({ maxZoom: 17, } as any); } - } catch {} + } catch { } }; return ( @@ -347,7 +343,7 @@ export const StopMap: React.FC<StopMapProps> = ({ const key = `${c.schedule.shapeId}_${c.currentPosition.shapeIndex}`; const shapeData = shapes[key]; if (!shapeData) return null; - const lineColor = getLineColor(region, c.line); + const lineColor = getLineColour(c.line); return ( <Source @@ -439,9 +435,9 @@ export const StopMap: React.FC<StopMapProps> = ({ const pts = busPositions.map((c) => c.currentPosition ? map.project([ - c.currentPosition.longitude, - c.currentPosition.latitude, - ]) + c.currentPosition.longitude, + c.currentPosition.latitude, + ]) : null ); for (let i = 0; i < pts.length; i++) { @@ -462,7 +458,7 @@ export const StopMap: React.FC<StopMapProps> = ({ return busPositions.map((c, idx) => { const p = c.currentPosition!; - const lineColor = getLineColor(region, c.line); + const lineColor = getLineColour(c.line); const showLabel = zoom >= 13; const labelGap = gaps[idx] ?? baseGap; return ( diff --git a/src/frontend/app/components/StopSheet.tsx b/src/frontend/app/components/StopSheet.tsx index 6d2abf0..77bb5f1 100644 --- a/src/frontend/app/components/StopSheet.tsx +++ b/src/frontend/app/components/StopSheet.tsx @@ -3,9 +3,8 @@ import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Sheet } from "react-modal-sheet"; import { Link } from "react-router"; +import { REGION_DATA } from "~/config/RegionConfig"; import type { Stop } from "~/data/StopDataProvider"; -import { useApp } from "../AppContext"; -import { type RegionId, getRegionConfig } from "../config/RegionConfig"; import { type ConsolidatedCirculation } from "../routes/stops-$id"; import { ErrorDisplay } from "./ErrorDisplay"; import LineIcon from "./LineIcon"; @@ -27,12 +26,10 @@ interface ErrorInfo { } const loadConsolidatedData = async ( - region: RegionId, stopId: number ): Promise<ConsolidatedCirculation[]> => { - const regionConfig = getRegionConfig(region); const resp = await fetch( - `${regionConfig.consolidatedCirculationsEndpoint}?stopId=${stopId}`, + `${REGION_DATA.consolidatedCirculationsEndpoint}?stopId=${stopId}`, { headers: { Accept: "application/json", @@ -53,8 +50,6 @@ export const StopSheet: React.FC<StopSheetProps> = ({ stop, }) => { const { t } = useTranslation(); - const { region } = useApp(); - const regionConfig = getRegionConfig(region); const [data, setData] = useState<ConsolidatedCirculation[] | null>(null); const [loading, setLoading] = useState(false); const [error, setError] = useState<ErrorInfo | null>(null); @@ -87,7 +82,7 @@ export const StopSheet: React.FC<StopSheetProps> = ({ setError(null); setData(null); - const stopData = await loadConsolidatedData(region, stop.stopId); + const stopData = await loadConsolidatedData(stop.stopId); setData(stopData); setLastUpdated(new Date()); } catch (err) { @@ -102,15 +97,15 @@ export const StopSheet: React.FC<StopSheetProps> = ({ if (isOpen && stop.stopId) { loadData(); } - }, [isOpen, stop.stopId, region]); + }, [isOpen, stop.stopId]); // Show only the next 4 arrivals const sortedData = data ? [...data].sort( - (a, b) => - (a.realTime?.minutes ?? a.schedule?.minutes ?? 999) - - (b.realTime?.minutes ?? b.schedule?.minutes ?? 999) - ) + (a, b) => + (a.realTime?.minutes ?? a.schedule?.minutes ?? 999) - + (b.realTime?.minutes ?? b.schedule?.minutes ?? 999) + ) : []; const limitedEstimates = sortedData.slice(0, 4); @@ -130,7 +125,7 @@ export const StopSheet: React.FC<StopSheetProps> = ({ > {stop.lines.map((line) => ( <div key={line} className="stop-sheet-line-icon"> - <LineIcon line={line} region={region} mode="rounded" /> + <LineIcon line={line} mode="rounded" /> </div> ))} </div> @@ -166,7 +161,6 @@ export const StopSheet: React.FC<StopSheetProps> = ({ <ConsolidatedCirculationCard key={idx} estimate={estimate} - regionConfig={regionConfig} readonly /> ))} @@ -179,39 +173,39 @@ export const StopSheet: React.FC<StopSheetProps> = ({ </Sheet.Content> <div className="stop-sheet-footer"> - {lastUpdated && ( - <div className="stop-sheet-timestamp"> - {t("estimates.last_updated", "Actualizado a las")}{" "} - {lastUpdated.toLocaleTimeString(undefined, { - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - })} - </div> - )} + {lastUpdated && ( + <div className="stop-sheet-timestamp"> + {t("estimates.last_updated", "Actualizado a las")}{" "} + {lastUpdated.toLocaleTimeString(undefined, { + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + })} + </div> + )} - <div className="stop-sheet-actions"> - <button - className="stop-sheet-reload" - onClick={loadData} - disabled={loading} - title={t("estimates.reload", "Recargar estimaciones")} - > - <RefreshCw - className={`reload-icon ${loading ? "spinning" : ""}`} - /> - {t("estimates.reload", "Recargar")} - </button> + <div className="stop-sheet-actions"> + <button + className="stop-sheet-reload" + onClick={loadData} + disabled={loading} + title={t("estimates.reload", "Recargar estimaciones")} + > + <RefreshCw + className={`reload-icon ${loading ? "spinning" : ""}`} + /> + {t("estimates.reload", "Recargar")} + </button> - <Link - to={`/stops/${stop.stopId}`} - className="stop-sheet-view-all" - onClick={onClose} - > - {t("map.view_all_estimates", "Ver todas las estimaciones")} - </Link> - </div> + <Link + to={`/stops/${stop.stopId}`} + className="stop-sheet-view-all" + onClick={onClose} + > + {t("map.view_all_estimates", "Ver todas las estimaciones")} + </Link> </div> + </div> </Sheet.Container> <Sheet.Backdrop onTap={onClose} /> </Sheet> diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.css b/src/frontend/app/components/Stops/ConsolidatedCirculationCard.css index 3dc33ea..e61ac25 100644 --- a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.css +++ b/src/frontend/app/components/Stops/ConsolidatedCirculationCard.css @@ -1,4 +1,4 @@ -@import "tailwindcss"; +@import '../../tailwind.css'; .consolidated-circulation-card { all: unset; @@ -133,19 +133,19 @@ } .meta-chip.delay-ok { - @apply bg-green-400/30 dark:bg-green-600/30 border-green-500 dark:border-green-700 text-green-800 dark:text-green-200; + @apply bg-green-600/80 dark:bg-green-600/30 border-green-500 dark:border-green-700 text-white dark:text-green-200; } .meta-chip.delay-warn { - @apply bg-yellow-400/30 dark:bg-yellow-600/30 border-yellow-500 dark:border-yellow-700 text-yellow-800 dark:text-yellow-200; + @apply bg-amber-600/80 dark:bg-yellow-600/30 border-yellow-500 dark:border-yellow-700 text-white dark:text-yellow-200; } .meta-chip.delay-critical { - @apply bg-red-400/30 dark:bg-red-600/30 border-red-500 dark:border-red-700 text-white; + @apply bg-red-400/80 dark:bg-red-600/30 border-red-500 dark:border-red-700 text-white; } .meta-chip.delay-early { - @apply bg-blue-400/30 dark:bg-blue-600/30 border-blue-500 dark:border-blue-700 text-blue-800 dark:text-blue-200; + @apply bg-blue-400/80 dark:bg-blue-600/30 border-blue-500 dark:border-blue-700 text-white dark:text-blue-200; } /* GPS Indicator */ diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx b/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx index 0b97c11..6f92644 100644 --- a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx +++ b/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx @@ -1,6 +1,5 @@ import { useMemo } from "react"; import { useTranslation } from "react-i18next"; -import { type RegionConfig } from "~/config/RegionConfig"; import LineIcon from "~components/LineIcon"; import { type ConsolidatedCirculation } from "~routes/stops-$id"; @@ -8,7 +7,6 @@ import "./ConsolidatedCirculationCard.css"; interface ConsolidatedCirculationCardProps { estimate: ConsolidatedCirculation; - regionConfig: RegionConfig; onMapClick?: () => void; readonly?: boolean; } @@ -72,7 +70,7 @@ const parseServiceId = (serviceId: string): string => { export const ConsolidatedCirculationCard: React.FC< ConsolidatedCirculationCardProps -> = ({ estimate, regionConfig, onMapClick, readonly }) => { +> = ({ estimate, onMapClick, readonly }) => { const { t } = useTranslation(); const formatDistance = (meters: number) => { @@ -171,30 +169,28 @@ export const ConsolidatedCirculationCard: React.FC< const interactiveProps = readonly ? {} : { - onClick: onMapClick, - type: "button" as const, - disabled: !hasGpsPosition, - }; + onClick: onMapClick, + type: "button" as const, + disabled: !hasGpsPosition, + }; return ( <Tag - className={`consolidated-circulation-card ${ - readonly - ? !hasGpsPosition - ? "no-gps" - : "" - : hasGpsPosition + className={`consolidated-circulation-card ${readonly + ? !hasGpsPosition + ? "no-gps" + : "" + : hasGpsPosition ? "has-gps" : "no-gps" - }`} + }`} {...interactiveProps} - aria-label={`${hasGpsPosition ? "View" : "No GPS data for"} ${ - estimate.line - } to ${estimate.route}${hasGpsPosition ? " on map" : ""}`} + aria-label={`${hasGpsPosition ? "View" : "No GPS data for"} ${estimate.line + } to ${estimate.route}${hasGpsPosition ? " on map" : ""}`} > <div className="card-row main"> <div className="line-info"> - <LineIcon line={estimate.line} region={regionConfig.id} mode="pill" /> + <LineIcon line={estimate.line} mode="pill" /> </div> <div className="route-info"> <strong>{estimate.route}</strong> @@ -202,9 +198,8 @@ export const ConsolidatedCirculationCard: React.FC< {hasGpsPosition && ( <div className="gps-indicator" title="Live GPS tracking"> <span - className={`gps-pulse ${ - estimate.isPreviousTrip ? "previous-trip" : "" - }`} + className={`gps-pulse ${estimate.isPreviousTrip ? "previous-trip" : "" + }`} /> </div> )} diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx b/src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx index 4c2916a..547fdf7 100644 --- a/src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx +++ b/src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx @@ -1,5 +1,4 @@ import { useTranslation } from "react-i18next"; -import { type RegionConfig } from "~/config/RegionConfig"; import { type ConsolidatedCirculation } from "~routes/stops-$id"; import { ConsolidatedCirculationCard } from "./ConsolidatedCirculationCard"; @@ -8,14 +7,12 @@ import "./ConsolidatedCirculationList.css"; interface RegularTableProps { data: ConsolidatedCirculation[]; dataDate: Date | null; - regionConfig: RegionConfig; onCirculationClick?: (estimate: ConsolidatedCirculation, index: number) => void; } export const ConsolidatedCirculationList: React.FC<RegularTableProps> = ({ data, dataDate, - regionConfig, onCirculationClick, }) => { const { t } = useTranslation(); @@ -44,7 +41,6 @@ export const ConsolidatedCirculationList: React.FC<RegularTableProps> = ({ <ConsolidatedCirculationCard key={idx} estimate={estimate} - regionConfig={regionConfig} onMapClick={() => onCirculationClick?.(estimate, idx)} /> ))} diff --git a/src/frontend/app/components/layout/Header.tsx b/src/frontend/app/components/layout/Header.tsx index 2bdd764..b8184f1 100644 --- a/src/frontend/app/components/layout/Header.tsx +++ b/src/frontend/app/components/layout/Header.tsx @@ -1,5 +1,4 @@ import { Menu } from "lucide-react"; -import React from "react"; import "./Header.css"; interface HeaderProps { diff --git a/src/frontend/app/components/ui/Button.css b/src/frontend/app/components/ui/Button.css deleted file mode 100644 index bf02a7c..0000000 --- a/src/frontend/app/components/ui/Button.css +++ /dev/null @@ -1,39 +0,0 @@ -.ui-button { - display: inline-flex; - align-items: center; - gap: 0.5rem; - padding: 0.75rem 1.5rem; - border-radius: 0.5rem; - font-size: 0.9rem; - font-weight: 500; - cursor: pointer; - transition: background-color 0.2s ease, transform 0.1s ease; - border: none; -} - -.ui-button:active { - transform: translateY(1px); -} - -.ui-button--primary { - background: var(--button-background-color); - color: white; -} - -.ui-button--primary:hover { - background: var(--button-hover-background-color); -} - -.ui-button--secondary { - background: var(--border-color); - color: var(--text-color); -} - -.ui-button--secondary:hover { - background: #e0e0e0; -} - -.ui-button__icon { - display: flex; - align-items: center; -} diff --git a/src/frontend/app/components/ui/Button.tsx b/src/frontend/app/components/ui/Button.tsx deleted file mode 100644 index 18a15b2..0000000 --- a/src/frontend/app/components/ui/Button.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from "react"; -import "./Button.css"; - -interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { - variant?: "primary" | "secondary" | "outline"; - icon?: React.ReactNode; -} - -export const Button: React.FC<ButtonProps> = ({ - children, - variant = "primary", - icon, - className = "", - ...props -}) => { - return ( - <button - className={`ui-button ui-button--${variant} ${className}`} - {...props} - > - {icon && <span className="ui-button__icon">{icon}</span>} - {children} - </button> - ); -}; diff --git a/src/frontend/app/components/ui/PageContainer.css b/src/frontend/app/components/ui/PageContainer.css deleted file mode 100644 index 8a86035..0000000 --- a/src/frontend/app/components/ui/PageContainer.css +++ /dev/null @@ -1,20 +0,0 @@ -.page-container { - max-width: 100%; - padding: 0 16px; - background-color: var(--background-color); - color: var(--text-color); -} - -@media (min-width: 768px) { - .page-container { - width: 90%; - max-width: 768px; - margin: 0 auto; - } -} - -@media (min-width: 1024px) { - .page-container { - max-width: 1024px; - } -} diff --git a/src/frontend/app/components/ui/PageContainer.tsx b/src/frontend/app/components/ui/PageContainer.tsx deleted file mode 100644 index 4c9684a..0000000 --- a/src/frontend/app/components/ui/PageContainer.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from "react"; -import "./PageContainer.css"; - -interface PageContainerProps { - children: React.ReactNode; - className?: string; -} - -export const PageContainer: React.FC<PageContainerProps> = ({ - children, - className = "", -}) => { - return <div className={`page-container ${className}`}>{children}</div>; -}; |
