Files
SIB/frontend/src/views/SchedulesView.vue

215 lines
8.7 KiB
Vue
Raw Normal View History

2026-02-21 09:53:31 -05:00
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useScheduleStore } from '@/stores/schedule'
import { useRouteStore } from '@/stores/route'
import { formatTo12Hour } from '@/utils/timeFormatter'
import { analyticsService } from '@/services/analyticsService'
import { useRouter, useRoute } from 'vue-router'
const { t } = useI18n()
const route = useRoute()
const router = useRouter()
const scheduleStore = useScheduleStore()
const routeStore = useRouteStore()
onMounted(async () => {
analyticsService.logEvent({ event_name: 'screen_view', screen_name: 'Schedules' })
await routeStore.loadRoutes()
const queryRouteId = route.query.routeId as string
if (queryRouteId) {
const foundRoute = routeStore.allRoutes.find(r => r.id === queryRouteId)
if (foundRoute) {
syncRouteSelection(foundRoute.id, foundRoute.name)
}
}
})
const unwatchQuery = watch(
() => route.query.routeId,
(newRouteId) => {
if (newRouteId) {
const foundRoute = routeStore.allRoutes.find(r => r.id === newRouteId as string)
if (foundRoute) {
syncRouteSelection(foundRoute.id, foundRoute.name)
}
}
}
)
onUnmounted(() => {
unwatchQuery()
})
function syncRouteSelection(routeId: string, routeName: string) {
routeStore.selectRoute(routeId, routeName)
scheduleStore.loadRouteSchedules(routeId)
}
function selectRouteAndClose(routeId: string, routeName: string) {
analyticsService.logEvent({
event_name: 'schedule_viewed',
item_id: routeName,
properties: { route_id: routeId }
})
routeStore.selectRoute(routeId, routeName)
scheduleStore.loadRouteSchedules(routeId)
}
function goToMap() {
if (routeStore.selectedRouteId) {
router.push({
path: '/map',
query: { routeId: routeStore.selectedRouteId }
})
}
}
</script>
<template>
<div class="schedules-view-redesign bg-slate-50 dark:bg-zinc-950 text-slate-900 dark:text-slate-100 min-h-screen">
<!-- Header Sticky con Blur -->
<header class="sticky top-0 z-50 bg-white/90 dark:bg-zinc-900/90 backdrop-blur-md px-6 pt-10 pb-4 border-b border-slate-200 dark:border-zinc-800">
<div class="flex items-center justify-between mb-6">
<button @click="router.back()" class="w-10 h-10 flex items-center justify-center -ml-2 rounded-full active:bg-slate-100 dark:active:bg-zinc-800 transition-colors">
<span class="material-icons text-2xl">arrow_back</span>
</button>
<h1 class="text-xl font-bold tracking-tight">{{ t('schedules.title') }}</h1>
<div class="w-10"></div>
</div>
<!-- Selector de Línea -->
<div class="relative group" v-if="!routeStore.isLoadingRoutes && routeStore.allRoutes.length > 0">
<label class="block text-[10px] font-bold text-slate-400 dark:text-zinc-500 uppercase tracking-widest mb-1.5 ml-1">{{ t('schedules.selectRoute') || 'Seleccionar Línea' }}</label>
<div class="relative">
<select
class="w-full pl-4 pr-12 py-4 bg-slate-50 dark:bg-zinc-800 border-2 border-transparent focus:border-primary focus:ring-0 rounded-2xl text-lg font-bold transition-all cursor-pointer appearance-none"
id="route-select"
:value="routeStore.selectedRouteId"
@change="e => {
const target = e.target as HTMLSelectElement;
if (target) {
const selectedRoute = routeStore.allRoutes.find(r => r.id === target.value);
if (selectedRoute) selectRouteAndClose(target.value, selectedRoute.name);
}
}"
2026-02-21 09:53:31 -05:00
>
<option value="" disabled>{{ t('schedules.placeholder') || 'Elige una ruta...' }}</option>
<option v-for="route in routeStore.allRoutes" :key="route.id" :value="route.id">
{{ route.name }}
</option>
</select>
<span class="material-icons absolute right-4 top-1/2 -translate-y-1/2 text-slate-400 pointer-events-none">expand_more</span>
2026-02-21 09:53:31 -05:00
</div>
</div>
</header>
<main class="flex-1 px-6 pt-6 pb-32">
<div v-if="routeStore.isLoadingRoutes" class="flex flex-col items-center justify-center py-20">
<div class="w-12 h-12 border-4 border-primary/20 border-t-primary rounded-full animate-spin mb-4"></div>
<p class="text-slate-500 dark:text-zinc-400 font-medium">{{ t('schedules.loadingRoutes') }}</p>
</div>
<div v-else-if="!routeStore.selectedRouteId" class="flex flex-col items-center justify-center py-20 text-center">
<span class="material-icons text-6xl text-slate-200 dark:text-zinc-800 mb-4">route</span>
<p class="text-slate-500 dark:text-zinc-400 font-medium italic">Selecciona una ruta para ver sus horarios</p>
</div>
<template v-else>
<!-- Estado Próximas Salidas -->
<div class="flex items-center justify-between mb-6">
<h2 class="text-sm font-semibold text-slate-500 dark:text-zinc-400">{{ t('schedules.upcoming') || 'Próximas salidas' }}</h2>
<div class="flex items-center gap-1.5 px-2.5 py-1 bg-[#fee715]/10 rounded-full">
<span class="w-1.5 h-1.5 rounded-full bg-[#fee715] animate-pulse"></span>
<span class="text-[10px] font-bold text-slate-700 dark:text-[#fee715] tracking-wide">EN VIVO</span>
</div>
2026-02-21 09:53:31 -05:00
</div>
<!-- Listado de Horarios -->
<div v-if="scheduleStore.isLoading" class="flex justify-center py-10">
<div class="w-8 h-8 border-2 border-primary/20 border-t-primary rounded-full animate-spin"></div>
2026-02-21 09:53:31 -05:00
</div>
<div v-else-if="scheduleStore.schedules.length === 0" class="bg-white dark:bg-zinc-800/50 p-8 rounded-2xl border border-slate-100 dark:border-zinc-800 text-center">
<span class="material-icons text-slate-300 dark:text-zinc-700 text-4xl mb-2">event_busy</span>
<p class="text-slate-500 dark:text-zinc-400">{{ t('schedules.noSchedules') || 'No hay salidas programadas para hoy' }}</p>
2026-02-21 09:53:31 -05:00
</div>
<div v-else class="space-y-4">
<div
v-for="schedule in scheduleStore.schedules"
:key="schedule.id"
class="p-4 bg-white dark:bg-zinc-800/50 border border-slate-100 dark:border-zinc-800 rounded-2xl flex items-center justify-between hover:border-primary/50 transition-all shadow-sm"
>
<div class="flex items-center gap-4">
<div class="text-3xl font-extrabold tracking-tighter text-slate-900 dark:text-white">
{{ formatTo12Hour(schedule.departure_time) }}
2026-02-21 09:53:31 -05:00
</div>
<div class="h-8 w-px bg-slate-200 dark:bg-zinc-700"></div>
<div>
<p class="text-[10px] font-medium text-slate-400 dark:text-zinc-500 uppercase tracking-wider">Línea</p>
<p class="font-bold text-slate-900 dark:text-white leading-tight">
{{ routeStore.selectedRouteName }}
</p>
</div>
</div>
<span
class="px-3 py-1 text-[10px] font-bold uppercase tracking-wider rounded-full"
:class="schedule.schedule_type === 'weekday' ? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400' : 'bg-slate-100 dark:bg-zinc-700 text-slate-500 dark:text-zinc-400'"
>
{{ t(`schedules.types.${schedule.schedule_type}`) || schedule.schedule_type }}
</span>
</div>
2026-02-21 09:53:31 -05:00
</div>
</template>
</main>
<!-- Botón Flotante para ir al Mapa -->
<Transition name="fade">
<button
v-if="routeStore.selectedRouteId"
@click="goToMap"
class="fixed bottom-24 right-6 w-14 h-14 bg-primary text-slate-900 rounded-full shadow-2xl flex items-center justify-center active:scale-95 transition-all z-40"
>
<span class="material-icons text-3xl">map</span>
</button>
</Transition>
2026-02-21 09:53:31 -05:00
</div>
</template>
<style scoped>
.schedules-view-redesign {
font-family: 'Plus Jakarta Sans', sans-serif;
2026-02-21 09:53:31 -05:00
overflow-x: hidden;
}
select {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%2364748b'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 1rem center;
background-size: 1.25rem;
2026-02-21 09:53:31 -05:00
}
.fade-enter-active, .fade-leave-active {
transition: opacity 0.3s ease, transform 0.3s ease;
2026-02-21 09:53:31 -05:00
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
transform: translateY(20px);
2026-02-21 09:53:31 -05:00
}
/* Transiciones para la lista */
.space-y-4 > * {
animation: slideUp 0.4s ease-out forwards;
2026-02-21 09:53:31 -05:00
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
2026-02-21 09:53:31 -05:00
}
</style>