aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/app/components/Stops
diff options
context:
space:
mode:
authorAriel Costas Guerrero <ariel@costas.dev>2025-11-30 17:58:55 +0100
committerAriel Costas Guerrero <ariel@costas.dev>2025-11-30 17:58:55 +0100
commite7283ba10d45b42e1274cd13c3d6aabec57c85b4 (patch)
tree3e753b79c0835a0ecd4674b97039fcd3b53275a7 /src/frontend/app/components/Stops
parent0798325688049a87df02910ab9141c0b1c13ffcb (diff)
feat: add Tailwind CSS support and create ConsolidatedCirculationCard styles
- Added Tailwind CSS and its Vite plugin to the project dependencies. - Updated Vite configuration to include Tailwind CSS plugin. - Created a new CSS file for the Consolidated Circulation Card component with styles for various states and responsive design.
Diffstat (limited to 'src/frontend/app/components/Stops')
-rw-r--r--src/frontend/app/components/Stops/ConsolidatedCirculationCard.css214
-rw-r--r--src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx26
-rw-r--r--src/frontend/app/components/Stops/ConsolidatedCirculationList.css226
3 files changed, 224 insertions, 242 deletions
diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.css b/src/frontend/app/components/Stops/ConsolidatedCirculationCard.css
new file mode 100644
index 0000000..8a442c3
--- /dev/null
+++ b/src/frontend/app/components/Stops/ConsolidatedCirculationCard.css
@@ -0,0 +1,214 @@
+@reference "../../root.css";
+
+.consolidated-circulation-card {
+ all: unset;
+ flex: 0 0 auto;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ background-color: var(--message-background-color);
+ border-radius: 12px;
+ border: 1px solid var(--border-color);
+ padding: 0.65rem 0.85rem;
+ transition: all 0.2s ease;
+}
+
+.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;
+}
+
+
+.consolidated-circulation-card .card-row {
+ display: flex;
+ align-items: center;
+ gap: 0.65rem;
+}
+
+.consolidated-circulation-card .card-row.main {
+ min-height: 48px;
+}
+
+.consolidated-circulation-card .line-info {
+ flex-shrink: 0;
+}
+
+.consolidated-circulation-card .route-info {
+ flex: 1;
+ min-width: 0;
+}
+
+.consolidated-circulation-card .route-info strong {
+ font-size: 1rem;
+ color: var(--text-color);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ line-clamp: 2;
+ -webkit-box-orient: vertical;
+ line-height: 1.25;
+}
+
+.consolidated-circulation-card .eta-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0.3rem 0.45rem;
+ border-radius: 12px;
+}
+
+.consolidated-circulation-card .eta-text {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ line-height: 1;
+}
+
+.consolidated-circulation-card .eta-value {
+ font-size: 1.15rem;
+ font-weight: 700;
+}
+
+.consolidated-circulation-card .eta-unit {
+ font-size: 0.65rem;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+}
+
+.consolidated-circulation-card .eta-badge.time-running {
+ background: rgba(34, 197, 94, 0.12);
+ color: #1a9e56;
+}
+
+.consolidated-circulation-card .eta-badge.time-delayed {
+ background: rgba(255, 106, 0, 0.12);
+ color: #d06100;
+}
+
+.consolidated-circulation-card .eta-badge.time-scheduled {
+ background: rgba(11, 61, 145, 0.12);
+ color: #0b3d91;
+}
+
+[data-theme="dark"] .consolidated-circulation-card .eta-badge.time-scheduled {
+ color: #8fb4ff;
+}
+
+.consolidated-circulation-card .card-row.meta {
+ flex-wrap: wrap;
+ justify-content: flex-start;
+ gap: 0.4rem;
+}
+
+.meta-chip {
+ font-size: 0.75rem;
+ padding: 0.2rem 0.55rem;
+ border-radius: 999px;
+ background: rgba(0, 0, 0, 0.03);
+
+ @apply flex items-center justify-center gap-1 flex-shrink-0 bg-gray-200/30 dark:bg-gray-600/30;
+}
+
+.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;
+}
+
+.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;
+}
+
+.meta-chip.delay-critical {
+ @apply bg-red-400/30 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;
+}
+
+/* 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;
+}
+
+.gps-pulse.previous-trip {
+ background: #ff9800;
+ box-shadow: 0 0 0 2px rgba(255, 152, 0, 0.2);
+ animation: gpsPulseOrange 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);
+ }
+}
+
+@keyframes gpsPulseOrange {
+ 0% {
+ box-shadow: 0 0 0 2px rgba(255, 152, 0, 0.2);
+ }
+ 50% {
+ box-shadow: 0 0 0 6px rgba(255, 152, 0, 0.1);
+ }
+ 100% {
+ box-shadow: 0 0 0 2px rgba(255, 152, 0, 0.2);
+ }
+}
+
+@media (max-width: 480px) {
+ .consolidated-circulation-card {
+ padding: 0.65rem 0.75rem;
+ }
+
+ .consolidated-circulation-card .card-row {
+ gap: 0.5rem;
+ }
+
+ .consolidated-circulation-card .eta-badge {
+ padding: 0.25rem 0.4rem;
+ }
+}
diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx b/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx
index 707ecce..0b97c11 100644
--- a/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx
+++ b/src/frontend/app/components/Stops/ConsolidatedCirculationCard.tsx
@@ -4,7 +4,7 @@ import { type RegionConfig } from "~/config/RegionConfig";
import LineIcon from "~components/LineIcon";
import { type ConsolidatedCirculation } from "~routes/stops-$id";
-import "./ConsolidatedCirculationList.css";
+import "./ConsolidatedCirculationCard.css";
interface ConsolidatedCirculationCardProps {
estimate: ConsolidatedCirculation;
@@ -75,18 +75,6 @@ export const ConsolidatedCirculationCard: React.FC<
> = ({ estimate, regionConfig, onMapClick, readonly }) => {
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`;
@@ -123,9 +111,12 @@ export const ConsolidatedCirculationCard: React.FC<
return null;
}
- const delta = Math.round(estimate.realTime.minutes - estimate.schedule.minutes);
+ const delta = Math.round(
+ estimate.realTime.minutes - estimate.schedule.minutes
+ );
const absDelta = Math.abs(delta);
+ // On time
if (delta === 0) {
return {
label: t("estimates.delay_on_time", "En hora (0 min)"),
@@ -133,8 +124,10 @@ export const ConsolidatedCirculationCard: React.FC<
} as const;
}
+ // Delayed
if (delta > 0) {
- const tone = delta <= 2 ? "delay-ok" : delta <= 10 ? "delay-warn" : "delay-critical";
+ const tone =
+ delta <= 2 ? "delay-ok" : delta <= 10 ? "delay-warn" : "delay-critical";
return {
label: t("estimates.delay_positive", "Retraso de {{minutes}} min", {
minutes: delta,
@@ -143,6 +136,7 @@ export const ConsolidatedCirculationCard: React.FC<
} as const;
}
+ // Early
const tone = absDelta <= 2 ? "delay-ok" : "delay-early";
return {
label: t("estimates.delay_negative", "Adelanto de {{minutes}} min", {
@@ -200,7 +194,7 @@ export const ConsolidatedCirculationCard: React.FC<
>
<div className="card-row main">
<div className="line-info">
- <LineIcon line={estimate.line} region={regionConfig.id} rounded />
+ <LineIcon line={estimate.line} region={regionConfig.id} mode="pill" />
</div>
<div className="route-info">
<strong>{estimate.route}</strong>
diff --git a/src/frontend/app/components/Stops/ConsolidatedCirculationList.css b/src/frontend/app/components/Stops/ConsolidatedCirculationList.css
index 5afdeaa..044b4a3 100644
--- a/src/frontend/app/components/Stops/ConsolidatedCirculationList.css
+++ b/src/frontend/app/components/Stops/ConsolidatedCirculationList.css
@@ -11,229 +11,3 @@
color: var(--subtitle-color);
font-size: 0.95rem;
}
-
-.consolidated-circulation-card {
- all: unset;
- flex: 0 0 auto;
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
- background-color: var(--message-background-color);
- border-radius: 12px;
- border: 1px solid var(--border-color);
- padding: 0.65rem 0.85rem;
- transition: all 0.2s ease;
-}
-
-.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;
-}
-
-
-.consolidated-circulation-card .card-row {
- display: flex;
- align-items: center;
- gap: 0.65rem;
-}
-
-.consolidated-circulation-card .card-row.main {
- min-height: 48px;
-}
-
-.consolidated-circulation-card .line-info {
- flex-shrink: 0;
-}
-
-.consolidated-circulation-card .route-info {
- flex: 1;
- min-width: 0;
-}
-
-.consolidated-circulation-card .route-info strong {
- font-size: 1rem;
- color: var(--text-color);
- overflow: hidden;
- text-overflow: ellipsis;
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- line-height: 1.25;
-}
-
-.consolidated-circulation-card .eta-badge {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- padding: 0.3rem 0.45rem;
- border-radius: 12px;
-}
-
-.consolidated-circulation-card .eta-text {
- display: flex;
- flex-direction: column;
- align-items: center;
- line-height: 1;
-}
-
-.consolidated-circulation-card .eta-value {
- font-size: 1.15rem;
- font-weight: 700;
-}
-
-.consolidated-circulation-card .eta-unit {
- font-size: 0.65rem;
- text-transform: uppercase;
- letter-spacing: 0.08em;
-}
-
-.consolidated-circulation-card .eta-badge.time-running {
- background: rgba(34, 197, 94, 0.12);
- color: #1a9e56;
-}
-
-.consolidated-circulation-card .eta-badge.time-delayed {
- background: rgba(255, 106, 0, 0.12);
- color: #d06100;
-}
-
-.consolidated-circulation-card .eta-badge.time-scheduled {
- background: rgba(11, 61, 145, 0.12);
- color: #0b3d91;
-}
-
-[data-theme="dark"] .consolidated-circulation-card .eta-badge.time-scheduled {
- color: #8fb4ff;
-}
-
-.consolidated-circulation-card .card-row.meta {
- flex-wrap: wrap;
- justify-content: flex-start;
- gap: 0.4rem;
-}
-
-.meta-chip {
- font-size: 0.75rem;
- padding: 0.2rem 0.55rem;
- border-radius: 999px;
- background: rgba(0, 0, 0, 0.03);
- color: var(--subtitle-color);
- border: 1px solid var(--border-color);
-}
-
-@media (prefers-color-scheme: dark) {
- .meta-chip {
- background: rgba(255, 255, 255, 0.05);
- }
-}
-
-.meta-chip.delay-ok {
- background: hsla(142, 71%, 45%, 0.15);
- border-color: hsla(142, 71%, 45%, 0.5);
- color: #1a9e56;
-}
-
-.meta-chip.delay-warn {
- background: hsla(43, 96%, 56%, 0.25);
- border-color: hsla(43, 96%, 56%, 0.6);
- color: hsl(43, 96%, 40%);
-}
-
-.meta-chip.delay-critical {
- background: hsla(0, 84%, 60%, 0.2);
- border-color: hsla(0, 84%, 60%, 0.35);
- color: hsl(0, 74%, 42%);
-}
-
-.meta-chip.delay-early {
- background: hsla(217, 91%, 60%, 0.17);
- border-color: hsla(217, 91%, 60%, 0.3);
- color: hsl(224, 90%, 50%);
-}
-
-/* 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;
-}
-
-.gps-pulse.previous-trip {
- background: #ff9800;
- box-shadow: 0 0 0 2px rgba(255, 152, 0, 0.2);
- animation: gpsPulseOrange 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);
- }
-}
-
-@keyframes gpsPulseOrange {
- 0% {
- box-shadow: 0 0 0 2px rgba(255, 152, 0, 0.2);
- }
- 50% {
- box-shadow: 0 0 0 6px rgba(255, 152, 0, 0.1);
- }
- 100% {
- box-shadow: 0 0 0 2px rgba(255, 152, 0, 0.2);
- }
-}
-
-@media (max-width: 480px) {
- .consolidated-circulation-card {
- padding: 0.65rem 0.75rem;
- }
-
- .consolidated-circulation-card .card-row {
- gap: 0.5rem;
- }
-
- .consolidated-circulation-card .eta-badge {
- padding: 0.25rem 0.4rem;
- }
-}