Perf: Fase 2 optimización de imágenes y asincronía en interfaz finalizada
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
<template>
|
||||
<template>
|
||||
<div class="admin-drivers">
|
||||
<div class="header">
|
||||
<button class="back-link" @click="router.push('/admin')">← Volver al Panel</button>
|
||||
@ -19,7 +19,7 @@
|
||||
<div v-for="taxi in taxis" :key="taxi.id" class="item-card taxi-card">
|
||||
<div class="card-header">
|
||||
<div class="avatar">
|
||||
<img v-if="taxi.image_url" :src="getImageUrl(taxi.image_url)" alt="Taxi">
|
||||
<AppImage v-if="taxi.image_url" :src="taxi.image_url" type="taxi" alt="Taxi" />
|
||||
<span v-else class="material-icons">local_taxi</span>
|
||||
</div>
|
||||
<div class="info">
|
||||
@ -167,9 +167,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, reactive, computed } from 'vue'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { supabase } from '@/supabase'
|
||||
import AppImage from '@/components/AppImage.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const isLoading = ref(false)
|
||||
@ -324,13 +325,6 @@ function getShiftLabel(shift: string) {
|
||||
return labels[shift] || shift
|
||||
}
|
||||
|
||||
function getImageUrl(path: string) {
|
||||
if (!path) return ''
|
||||
if (path.startsWith('http')) return path
|
||||
return path
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -5,7 +5,7 @@ import { businessService } from '@/services/businessService'
|
||||
import { couponsService } from '@/services/couponsService'
|
||||
import type { Business, Coupon } from '@/types'
|
||||
import FavoriteButton from '@/components/FavoriteButton.vue'
|
||||
import { getImageUrl as utilGetImageUrl } from '@/utils/imageUrl'
|
||||
import AppImage from '@/components/AppImage.vue'
|
||||
import { analyticsService } from '@/services/analyticsService'
|
||||
import LoadingBranded from '@/components/common/LoadingBranded.vue'
|
||||
|
||||
@ -67,10 +67,6 @@ async function fetchData() {
|
||||
|
||||
onMounted(fetchData)
|
||||
|
||||
function getImageUrl(path: string | null | undefined) {
|
||||
return utilGetImageUrl(path, 'business')
|
||||
}
|
||||
|
||||
const goBack = () => router.back()
|
||||
|
||||
function openMaps() {
|
||||
@ -189,10 +185,11 @@ const hasSocials = computed(() =>
|
||||
<!-- SECTION 1 · HERO -->
|
||||
<!-- ══════════════════════════════════════════ -->
|
||||
<section class="hero">
|
||||
<img
|
||||
:src="getImageUrl(business.image_url)"
|
||||
<AppImage
|
||||
:src="business.image_url"
|
||||
type="business"
|
||||
:alt="business.name"
|
||||
class="hero-img"
|
||||
imgClass="hero-img"
|
||||
/>
|
||||
<!-- Gradient overlay -->
|
||||
<div class="hero-gradient"></div>
|
||||
@ -266,11 +263,11 @@ const hasSocials = computed(() =>
|
||||
class="carousel-slide"
|
||||
:class="{ 'slide-active': idx === carouselIndex, 'slide-prev': idx < carouselIndex, 'slide-next': idx > carouselIndex }"
|
||||
>
|
||||
<img
|
||||
:src="getImageUrl(img)"
|
||||
<AppImage
|
||||
:src="img"
|
||||
type="business"
|
||||
:alt="`Foto ${idx + 1} de ${business.name}`"
|
||||
class="carousel-img"
|
||||
loading="lazy"
|
||||
imgClass="carousel-img"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -5,7 +5,7 @@ import type { Business } from '@/types'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { analyticsService } from '@/services/analyticsService'
|
||||
import { getImageUrl } from '@/utils/imageUrl'
|
||||
import AppImage from '@/components/AppImage.vue'
|
||||
import AuthGuard from '@/components/common/AuthGuard.vue'
|
||||
import LoadingBranded from '@/components/common/LoadingBranded.vue'
|
||||
|
||||
@ -215,8 +215,7 @@ function resetFilters() {
|
||||
<TransitionGroup v-if="filteredBusinesses.length > 0" name="fade" tag="div" class="activity-grid">
|
||||
<div v-for="biz in filteredBusinesses" :key="biz.id" class="activity-card" @click="handleExplore(biz)">
|
||||
<div class="card-img-wrap">
|
||||
<img :src="getImageUrl(biz.image_url, 'business')" :alt="biz.name" loading="lazy" class="card-img"
|
||||
@error="(e) => (e.target as HTMLImageElement).src = getImageUrl(null, 'business')" />
|
||||
<AppImage :src="biz.image_url" type="business" :alt="biz.name" imgClass="card-img" />
|
||||
</div>
|
||||
<div class="card-info">
|
||||
<p class="card-title">{{ biz.name }}</p>
|
||||
@ -244,8 +243,7 @@ function resetFilters() {
|
||||
<div class="activity-grid">
|
||||
<div v-for="biz in featuredBusinesses" :key="biz.id" class="activity-card" @click="handleExplore(biz)">
|
||||
<div class="card-img-wrap">
|
||||
<img :src="getImageUrl(biz.image_url, 'business')" :alt="biz.name" loading="lazy" class="card-img"
|
||||
@error="(e) => (e.target as HTMLImageElement).src = getImageUrl(null, 'business')" />
|
||||
<AppImage :src="biz.image_url" type="business" :alt="biz.name" imgClass="card-img" />
|
||||
</div>
|
||||
<div class="card-info">
|
||||
<p class="card-title">{{ biz.name }}</p>
|
||||
@ -260,8 +258,7 @@ function resetFilters() {
|
||||
<TransitionGroup name="fade" tag="div" class="activity-grid">
|
||||
<div v-for="biz in gridBusinesses" :key="biz.id" class="activity-card" @click="handleExplore(biz)">
|
||||
<div class="card-img-wrap">
|
||||
<img :src="getImageUrl(biz.image_url, 'business')" :alt="biz.name" loading="lazy" class="card-img"
|
||||
@error="(e) => (e.target as HTMLImageElement).src = getImageUrl(null, 'business')" />
|
||||
<AppImage :src="biz.image_url" type="business" :alt="biz.name" imgClass="card-img" />
|
||||
</div>
|
||||
<div class="card-info">
|
||||
<p class="card-title">{{ biz.name }}</p>
|
||||
|
||||
@ -16,12 +16,14 @@ import { useETA } from "@/composables/useETA";
|
||||
import { useFlujoPrincipal } from "@/composables/useFlujoPrincipal";
|
||||
import { useMapState } from "@/composables/useMapState";
|
||||
|
||||
// Optimized Components (Extracted)
|
||||
import SearchOverlay from "@/components/map/SearchOverlay.vue";
|
||||
import PromoCarousel from "@/components/map/PromoCarousel.vue";
|
||||
import ArrivalBanner from "@/components/map/ArrivalBanner.vue";
|
||||
// Optimized Components (Extracted) - Lazy Loaded
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
|
||||
const SearchOverlay = defineAsyncComponent(() => import("@/components/map/SearchOverlay.vue"));
|
||||
const PromoCarousel = defineAsyncComponent(() => import("@/components/map/PromoCarousel.vue"));
|
||||
const ArrivalBanner = defineAsyncComponent(() => import("@/components/map/ArrivalBanner.vue"));
|
||||
const ETACard = defineAsyncComponent(() => import("@/components/map/ETACard.vue"));
|
||||
|
||||
import ETACard from "@/components/map/ETACard.vue";
|
||||
import type { BusStop } from '@/types'
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
@ -7,6 +7,7 @@ import { shuttlesService } from '@/services/shuttlesService'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { supabase } from '@/supabase'
|
||||
import type { Coupon, Business, Shuttle } from '@/types'
|
||||
import AppImage from '@/components/AppImage.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const authStore = useAuthStore()
|
||||
@ -203,11 +204,6 @@ function openEditModal(coupon: Coupon) {
|
||||
showModal.value = true
|
||||
}
|
||||
|
||||
function getImageUrl(path: string | null | undefined) {
|
||||
if (!path) return '/default-coupon.png'
|
||||
return path
|
||||
}
|
||||
|
||||
async function saveCoupon() {
|
||||
try {
|
||||
if (!currentCoupon.value.title?.trim()) {
|
||||
@ -378,7 +374,7 @@ async function toggleCouponStatus(coupon: Coupon) {
|
||||
<td>
|
||||
<div class="title-cell">
|
||||
<div class="coupon-header-cell">
|
||||
<img :src="getImageUrl(coupon.image_url)" class="coupon-mini-img" />
|
||||
<AppImage :src="coupon.image_url" type="coupon" imgClass="coupon-mini-img" />
|
||||
<div>
|
||||
<strong>{{ coupon.title }}</strong>
|
||||
<div class="business-tag">
|
||||
@ -443,7 +439,7 @@ async function toggleCouponStatus(coupon: Coupon) {
|
||||
<td>
|
||||
<div class="title-cell">
|
||||
<div class="coupon-header-cell">
|
||||
<img :src="getImageUrl(biz.image_url)" class="coupon-mini-img" />
|
||||
<AppImage :src="biz.image_url" type="business" imgClass="coupon-mini-img" />
|
||||
<strong>{{ biz.name }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
@ -495,7 +491,7 @@ async function toggleCouponStatus(coupon: Coupon) {
|
||||
<td>
|
||||
<div class="title-cell">
|
||||
<div class="coupon-header-cell">
|
||||
<img :src="getImageUrl(shuttle.image_url)" class="coupon-mini-img" />
|
||||
<AppImage :src="shuttle.image_url" type="shuttle" imgClass="coupon-mini-img" />
|
||||
<div>
|
||||
<strong>{{ shuttle.route_name }}</strong>
|
||||
<div class="business-tag">
|
||||
|
||||
@ -3,7 +3,7 @@ import { ref, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { supabase } from '@/supabase'
|
||||
import type { Shuttle } from '@/types'
|
||||
import { getImageUrl } from '@/utils/imageUrl'
|
||||
import AppImage from '@/components/AppImage.vue'
|
||||
import { analyticsService } from '@/services/analyticsService'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import LoadingBranded from '@/components/common/LoadingBranded.vue'
|
||||
@ -101,11 +101,11 @@ const getTripTypeLabel = (type: string) => {
|
||||
<div v-else-if="shuttle" class="px-4 py-4 space-y-4 max-w-lg mx-auto animate-fade-in">
|
||||
<!-- Imagen -->
|
||||
<div v-if="shuttle.image_url" class="relative w-full h-56 md:h-64 rounded-2xl overflow-hidden shadow-sm">
|
||||
<img
|
||||
:src="getImageUrl(shuttle.image_url, 'shuttle')"
|
||||
<AppImage
|
||||
:src="shuttle.image_url"
|
||||
type="shuttle"
|
||||
:alt="shuttle.company_name"
|
||||
class="w-full h-full object-cover"
|
||||
@error="(e) => (e.target as HTMLImageElement).src = getImageUrl(null, 'shuttle')"
|
||||
imgClass="w-full h-full object-cover"
|
||||
/>
|
||||
<div class="absolute bottom-3 left-3 bg-[var(--bg-primary)]/90 backdrop-blur-sm px-3 py-1 rounded-full text-sm font-bold shadow-sm flex items-center gap-1">
|
||||
<span class="material-icons text-sm" style="color: var(--active-color)">directions_bus</span>
|
||||
|
||||
@ -5,7 +5,7 @@ import { useTaxiStore } from '@/stores/taxi'
|
||||
import { analyticsService } from '@/services/analyticsService'
|
||||
import type { Taxi } from '@/types'
|
||||
import FavoriteButton from '@/components/FavoriteButton.vue'
|
||||
import { getImageUrl } from '@/utils/imageUrl'
|
||||
import AppImage from '@/components/AppImage.vue'
|
||||
import AuthGuard from '@/components/common/AuthGuard.vue'
|
||||
import LoadingBranded from '@/components/common/LoadingBranded.vue'
|
||||
|
||||
@ -143,13 +143,11 @@ function getShiftLabel(shift: string) {
|
||||
<div class="card-top">
|
||||
<div class="driver-avatar-wrap">
|
||||
<div class="driver-avatar">
|
||||
<img
|
||||
:src="getImageUrl(taxi.image_url, 'taxi')"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
<AppImage
|
||||
:src="taxi.image_url"
|
||||
type="taxi"
|
||||
alt="Driver"
|
||||
@error="(e) => (e.target as HTMLImageElement).src = getImageUrl(null, 'taxi')"
|
||||
>
|
||||
/>
|
||||
</div>
|
||||
<div class="driver-status" :class="{ 'status-online': isOnline(taxi) }"></div>
|
||||
</div>
|
||||
|
||||
@ -4,7 +4,7 @@ import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useShuttleStore } from '@/stores/shuttle'
|
||||
import AuthGuard from '@/components/common/AuthGuard.vue'
|
||||
import { getImageUrl } from '@/utils/imageUrl'
|
||||
import AppImage from '@/components/AppImage.vue'
|
||||
import { analyticsService } from '@/services/analyticsService'
|
||||
import LoadingBranded from '@/components/common/LoadingBranded.vue'
|
||||
|
||||
@ -127,13 +127,11 @@ onUnmounted(() => {
|
||||
@click="verDetalle(shuttle.id, shuttle.company_name || `${shuttle.origin}-${shuttle.destination}`)"
|
||||
>
|
||||
<div class="card-image-wrap">
|
||||
<img
|
||||
:src="getImageUrl(shuttle.image_url, 'shuttle')"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
class="shuttle-img"
|
||||
<AppImage
|
||||
:src="shuttle.image_url"
|
||||
type="shuttle"
|
||||
imgClass="shuttle-img"
|
||||
alt="Shuttle"
|
||||
@error="(e) => (e.target as HTMLImageElement).src = getImageUrl(null, 'shuttle')"
|
||||
/>
|
||||
<div class="company-tag" v-if="shuttle.company_name">
|
||||
<span class="material-icons">business</span>
|
||||
|
||||
Reference in New Issue
Block a user