diff options
| author | Ariel Costas Guerrero <ariel@costas.dev> | 2025-11-19 23:54:49 +0100 |
|---|---|---|
| committer | Ariel Costas Guerrero <ariel@costas.dev> | 2025-11-19 23:54:49 +0100 |
| commit | f030f1806255c66b86689489d24f8f5ad9b832ce (patch) | |
| tree | a776e6a6670b50bb43609633cdbd1fe9857b8065 /src/frontend/app/components/Stops | |
| parent | 3ebb062e99dbd8a63d5642d67ba4be753e61a34d (diff) | |
feat: Implement StopMapModal component for displaying bus stop locations with live tracking; enhance styles and add interaction features
Diffstat (limited to 'src/frontend/app/components/Stops')
3 files changed, 82 insertions, 5 deletions
diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx b/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx index f725b8c..47fa56b 100644 --- a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx +++ b/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx @@ -9,6 +9,7 @@ import "./ConsolidatedCirculationList.css"; interface ConsolidatedCirculationCardProps { estimate: ConsolidatedCirculation; regionConfig: RegionConfig; + onMapClick?: () => void; } // Utility function to parse service ID and get the turn number @@ -70,7 +71,7 @@ const parseServiceId = (serviceId: string): string => { export const ConsolidatedCirculationCard: React.FC< ConsolidatedCirculationCardProps -> = ({ estimate, regionConfig }) => { +> = ({ estimate, regionConfig, onMapClick }) => { const { t } = useTranslation(); const absoluteArrivalTime = (minutes: number) => { @@ -168,8 +169,19 @@ export const ConsolidatedCirculationCard: React.FC< return chips; }, [delayChip, estimate.schedule, estimate.realTime]); + // Check if bus has GPS position (live tracking) + const hasGpsPosition = !!estimate.currentPosition; + return ( - <div className="consolidated-circulation-card"> + <button + className={`consolidated-circulation-card ${ + hasGpsPosition ? "has-gps" : "no-gps" + }`} + onClick={onMapClick} + type="button" + disabled={!hasGpsPosition} + 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} rounded /> @@ -183,6 +195,11 @@ export const ConsolidatedCirculationCard: React.FC< <span className="eta-unit">{etaUnit}</span> </div> </div> + {hasGpsPosition && ( + <div className="gps-indicator" title="Live GPS tracking"> + <span className="gps-pulse" /> + </div> + )} </div> {metaChips.length > 0 && ( <div className="card-row meta"> @@ -196,6 +213,6 @@ export const ConsolidatedCirculationCard: React.FC< ))} </div> )} - </div> + </button> ); }; diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationList.css b/src/frontend/app/components/Stops/ConsolidatedCirculationList.css index 7e757fb..680a511 100644 --- a/src/frontend/app/components/Stops/ConsolidatedCirculationList.css +++ b/src/frontend/app/components/Stops/ConsolidatedCirculationList.css @@ -13,6 +13,7 @@ } .consolidated-circulation-card { + all: unset; flex: 0 0 auto; display: flex; flex-direction: column; @@ -21,11 +22,34 @@ border-radius: 12px; border: 1px solid var(--border-color); padding: 0.65rem 0.85rem; - transition: box-shadow 0.2s ease; + transition: all 0.2s ease; } -.consolidated-circulation-card:hover { +.consolidated-circulation-card.has-gps { + cursor: pointer; +} + +.consolidated-circulation-card.no-gps { + cursor: not-allowed; + opacity: 0.7; +} + +.consolidated-circulation-card.has-gps:hover { box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08); + border-color: var(--button-background-color); + background-color: color-mix( + in oklab, + var(--button-background-color) 5%, + var(--message-background-color) + ); +} + +.consolidated-circulation-card.has-gps:active { + transform: scale(0.98); +} + +.consolidated-circulation-card:disabled { + pointer-events: none; } @@ -149,6 +173,39 @@ color: #1d4ed8; } +/* GPS Indicator */ +.gps-indicator { + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + flex-shrink: 0; + position: relative; +} + +.gps-pulse { + position: absolute; + width: 8px; + height: 8px; + background: #22c55e; + border-radius: 50%; + box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2); + animation: gpsPulse 2s ease-in-out infinite; +} + +@keyframes gpsPulse { + 0% { + box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2); + } + 50% { + box-shadow: 0 0 0 6px rgba(34, 197, 94, 0.1); + } + 100% { + box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2); + } +} + @media (max-width: 480px) { .consolidated-circulation-card { padding: 0.65rem 0.75rem; diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx b/src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx index 047dfd4..4c2916a 100644 --- a/src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx +++ b/src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx @@ -9,12 +9,14 @@ 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(); @@ -43,6 +45,7 @@ export const ConsolidatedCirculationList: React.FC<RegularTableProps> = ({ key={idx} estimate={estimate} regionConfig={regionConfig} + onMapClick={() => onCirculationClick?.(estimate, idx)} /> ))} </> |
