aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/app/components/Stops
diff options
context:
space:
mode:
Diffstat (limited to 'src/frontend/app/components/Stops')
-rw-r--r--src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx23
-rw-r--r--src/frontend/app/components/Stops/ConsolidatedCirculationList.css61
-rw-r--r--src/frontend/app/components/Stops/ConsolidatedCirculationList.tsx3
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)}
/>
))}
</>