From 4bf75d330251f438285454c5f782bf50451a2413 Mon Sep 17 00:00:00 2001 From: Hanzo_dev <2002samudiojohan@gmail.com> Date: Tue, 24 Feb 2026 21:55:52 -0500 Subject: [PATCH] Fix favorites system, add support for bus stops and tourist trips, and improve UI consistency --- frontend/src/components/BusStopInfoModal.vue | 58 ++------------------ frontend/src/components/FavoriteButton.vue | 2 +- frontend/src/stores/favorites.ts | 8 ++- frontend/src/views/FavoritesView.vue | 52 +++++++++++++++--- frontend/src/views/MapView.vue | 43 +++++++++++++++ frontend/src/views/RoutesView.vue | 8 ++- frontend/src/views/TaxiView.vue | 8 +++ 7 files changed, 114 insertions(+), 65 deletions(-) diff --git a/frontend/src/components/BusStopInfoModal.vue b/frontend/src/components/BusStopInfoModal.vue index 7a401f5..eca02fe 100644 --- a/frontend/src/components/BusStopInfoModal.vue +++ b/frontend/src/components/BusStopInfoModal.vue @@ -2,7 +2,7 @@ import { ref, watch } from 'vue' import type { BusStop } from '@/types' import { busStopsService } from '@/services/busStopsService' -import { favoritesService } from '@/services/favoritesService' +import FavoriteButton from '@/components/FavoriteButton.vue' import { formatTo12Hour } from '@/utils/timeFormatter' interface Props { @@ -15,8 +15,6 @@ const emit = defineEmits(['close', 'navigate']) const upcomingArrivals = ref<{ routeName: string; arrivalTime: string }[]>([]) const isLoading = ref(false) -const isFavorited = ref(false) -const favoriteId = ref(null) // Function to fetch arrivals async function loadArrivals() { @@ -33,48 +31,6 @@ async function loadArrivals() { } } -async function checkFavoriteStatus() { - const token = localStorage.getItem('auth_token') - if (!token || !props.busStop) { - isFavorited.value = false - favoriteId.value = null - return - } - - try { - const favorites = await favoritesService.getMyFavorites() - const found = favorites.find(f => f.item_type === 'stop' && f.item_id === props.busStop?.id) - isFavorited.value = !!found - favoriteId.value = found ? found.id : null - } catch (e) { - console.error("Error checking favorite status", e) - } -} - -async function toggleFavorite() { - const token = localStorage.getItem('auth_token') - if (!token) { - alert("Debes iniciar sesión para guardar favoritos") - return - } - - if (!props.busStop) return - - try { - if (isFavorited.value && props.busStop) { - await favoritesService.removeFavorite('stop', props.busStop.id) - isFavorited.value = false - favoriteId.value = null - } else { - const fav = await favoritesService.addFavorite('stop', props.busStop.id) - isFavorited.value = true - favoriteId.value = fav.id - } - } catch (e) { - alert("Error al actualizar favorito") - } -} - function startInternalNavigation() { if (props.busStop) { emit('navigate', props.busStop) @@ -85,14 +41,12 @@ function startInternalNavigation() { watch(() => props.busStop, async (newStop) => { if (newStop && props.isOpen) { await loadArrivals() - await checkFavoriteStatus() } }) watch(() => props.isOpen, async (isOpen) => { if (isOpen && props.busStop) { await loadArrivals() - await checkFavoriteStatus() } }) @@ -107,11 +61,11 @@ watch(() => props.isOpen, async (isOpen) => {

{{ busStop.name }}

- +

location_on diff --git a/frontend/src/components/FavoriteButton.vue b/frontend/src/components/FavoriteButton.vue index 7fc1a5d..19cef7d 100644 --- a/frontend/src/components/FavoriteButton.vue +++ b/frontend/src/components/FavoriteButton.vue @@ -17,7 +17,7 @@ import { useFavoritesStore } from '@/stores/favorites' import { useAuthStore } from '@/stores/auth' const props = defineProps<{ - itemType: 'coupon' | 'business' | 'taxi' | 'route' + itemType: 'coupon' | 'business' | 'taxi' | 'route' | 'stop' itemId: string itemName?: string itemImage?: string diff --git a/frontend/src/stores/favorites.ts b/frontend/src/stores/favorites.ts index 3f2b576..c0fc5a2 100644 --- a/frontend/src/stores/favorites.ts +++ b/frontend/src/stores/favorites.ts @@ -5,7 +5,7 @@ import { apiClient } from '@/services/apiClient' export interface Favorite { id: string user_id: string - item_type: 'coupon' | 'business' | 'taxi' | 'route' + item_type: 'coupon' | 'business' | 'taxi' | 'route' | 'stop' item_id: string item_name?: string item_image?: string @@ -21,6 +21,7 @@ export const useFavoritesStore = defineStore('favorites', () => { const businesses = computed(() => favorites.value.filter(f => f.item_type === 'business')) const taxis = computed(() => favorites.value.filter(f => f.item_type === 'taxi')) const routes = computed(() => favorites.value.filter(f => f.item_type === 'route')) + const stops = computed(() => favorites.value.filter(f => f.item_type === 'stop')) // Actions async function loadFavorites() { @@ -36,7 +37,7 @@ export const useFavoritesStore = defineStore('favorites', () => { } async function addFavorite( - itemType: 'coupon' | 'business' | 'taxi' | 'route', + itemType: 'coupon' | 'business' | 'taxi' | 'route' | 'stop', itemId: string, itemName?: string, itemImage?: string @@ -74,7 +75,7 @@ export const useFavoritesStore = defineStore('favorites', () => { } async function toggleFavorite( - itemType: 'coupon' | 'business' | 'taxi' | 'route', + itemType: 'coupon' | 'business' | 'taxi' | 'route' | 'stop', itemId: string, itemName?: string, itemImage?: string @@ -105,6 +106,7 @@ export const useFavoritesStore = defineStore('favorites', () => { businesses, taxis, routes, + stops, loadFavorites, addFavorite, removeFavorite, diff --git a/frontend/src/views/FavoritesView.vue b/frontend/src/views/FavoritesView.vue index eb2d50c..6440dfd 100644 --- a/frontend/src/views/FavoritesView.vue +++ b/frontend/src/views/FavoritesView.vue @@ -12,10 +12,11 @@ const selectedFilter = ref('all') const filters = [ { key: 'all', label: 'Todos', icon: 'star' }, - { key: 'routes', label: 'Rutas', icon: 'directions_bus' }, + { key: 'routes', label: 'Buses', icon: 'directions_bus' }, { key: 'taxis', label: 'Taxis', icon: 'local_taxi' }, - { key: 'businesses',label: 'Negocios', icon: 'store' }, - { key: 'coupons', label: 'Eventos', icon: 'confirmation_number' }, + { key: 'businesses',label: 'Comercios', icon: 'store' }, + { key: 'coupons', label: 'Ofertas', icon: 'confirmation_number' }, + { key: 'stops', label: 'Paradas', icon: 'location_on' }, ] onMounted(async () => { @@ -34,9 +35,11 @@ async function removeFavorite(event: Event, itemType: string, itemId: string) { } function navigateToItem(item: any) { - if (item.item_type === 'route') router.push('/schedules') + if (item.item_type === 'route') router.push({ path: '/schedules', query: { routeId: item.item_id } }) else if (item.item_type === 'taxi') router.push('/taxi') else if (item.item_type === 'business') router.push('/business/' + item.item_id) + else if (item.item_type === 'coupon') router.push('/coupons') + else if (item.item_type === 'stop') router.push({ path: '/map', query: { stopId: item.item_id } }) } const visibleRoutes = computed(() => @@ -51,11 +54,15 @@ const visibleBusinesses = computed(() => const visibleCoupons = computed(() => (selectedFilter.value === 'all' || selectedFilter.value === 'coupons') ? favoritesStore.coupons : [] ) +const visibleStops = computed(() => + (selectedFilter.value === 'all' || selectedFilter.value === 'stops') ? favoritesStore.stops : [] +) const totalFavorites = computed(() => favoritesStore.favorites.length) const hasVisibleItems = computed(() => visibleRoutes.value.length + visibleTaxis.value.length + - visibleBusinesses.value.length + visibleCoupons.value.length > 0 + visibleBusinesses.value.length + visibleCoupons.value.length + + visibleStops.value.length > 0 ) @@ -206,20 +213,22 @@ const hasVisibleItems = computed(() =>

- local_activity + + local_activity

{{ item.item_name }}

- Disponible + Cupón
+ +
+ +
+
+
+ location_on +
+
+

{{ item.item_name }}

+ Favorito +
+ +
+
+
+
diff --git a/frontend/src/views/MapView.vue b/frontend/src/views/MapView.vue index a6fa9bd..86536f6 100644 --- a/frontend/src/views/MapView.vue +++ b/frontend/src/views/MapView.vue @@ -12,6 +12,7 @@ import { telemetryService } from "@/services/telemetryService"; import { analyticsService } from "@/services/analyticsService"; import BusStopInfoModal from "@/components/BusStopInfoModal.vue"; +import FavoriteButton from "@/components/FavoriteButton.vue"; import type { BusStop } from '@/types' const router = useRouter(); @@ -231,6 +232,19 @@ onMounted(async () => { } } + // Handle stopId if present + const queryStopId = router.currentRoute.value.query.stopId as string; + if (queryStopId) { + await busStopStore.loadBusStops(); + const foundStop = busStopStore.busStops.find(s => s.id === queryStopId); + if (foundStop) { + selectedBusStop.value = foundStop; + showBusStopModal.value = true; + setCenter(foundStop.latitude, foundStop.longitude); + setZoom(17); + } + } + // Wait for Google Maps to load if (isLoaded.value) { await initializeMap(); @@ -1141,6 +1155,14 @@ function clearNavigation() { -{{ currentPromo.discount_percentage }}% +
+ +
@@ -1192,6 +1214,14 @@ function clearNavigation() {
PROMO
+
+ +

{{ selectedPromo.title }}

@@ -2155,4 +2185,17 @@ function clearNavigation() { .nav-destination { color: #e8eaed; } .nav-btn-close { background: #3c4043; color: #bdc1c6; } } + +.sheet-fav-pos { + position: absolute; + top: 6px; + right: 6px; + z-index: 10; +} +.promo-modal-fav { + position: absolute; + top: 15px; + left: 15px; + z-index: 10; +} diff --git a/frontend/src/views/RoutesView.vue b/frontend/src/views/RoutesView.vue index 21ddab1..151bb7d 100644 --- a/frontend/src/views/RoutesView.vue +++ b/frontend/src/views/RoutesView.vue @@ -237,8 +237,14 @@ const getStatusClass = (status: string) => {

-
+

{{ taxi.shift }}

+
diff --git a/frontend/src/views/TaxiView.vue b/frontend/src/views/TaxiView.vue index cc926ef..ec2e41d 100644 --- a/frontend/src/views/TaxiView.vue +++ b/frontend/src/views/TaxiView.vue @@ -308,6 +308,14 @@ function getShiftLabel(shift: string) { {{ shuttle.price_per_person }} /p
+
+ +