feat: Refinamiento del tracking de analiticas y actualizacion del dashboard admin
This commit is contained in:
@ -142,17 +142,27 @@
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Ruta</th>
|
||||
<th>Conversión</th>
|
||||
<th>Ratio</th>
|
||||
<th>Shuttle</th>
|
||||
<th>Interés (Vistas)</th>
|
||||
<th>Reservas & Llamadas</th>
|
||||
<th>Ratio de Conversión</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(data, id) in stats.shuttles" :key="id">
|
||||
<td class="id-cell">{{ id }}</td>
|
||||
<td>{{ calculateConversion(data.views, data.contacts) }}%</td>
|
||||
<tr v-for="(data, name) in stats.shuttles" :key="name">
|
||||
<td class="id-cell">{{ name }}</td>
|
||||
<td>{{ data.views }}</td>
|
||||
<td>
|
||||
<div class="mini-bar"><div class="fill" :style="{ width: calculateConversion(data.views, data.contacts) + '%' }"></div></div>
|
||||
<div style="display:flex; gap: 12px; align-items:center;">
|
||||
<span title="WhatsApp" style="display:flex; align-items:center; gap:2px;"><span class="material-icons" style="font-size:14px; color:#25D366;">chat</span> {{ data.whatsapp }}</span>
|
||||
<span title="Llamadas" style="display:flex; align-items:center; gap:2px;"><span class="material-icons" style="font-size:14px; color:#cbd5e1;">phone</span> {{ data.calls }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div style="display:flex; flex-direction:column; gap:4px;">
|
||||
<div class="mini-bar"><div class="fill" :style="{ width: calculateConversion(data.views, data.contacts) + '%' }"></div></div>
|
||||
<span style="font-size: 0.65rem; color: #64748b; font-weight:700;">{{ calculateConversion(data.views, data.contacts) }}%</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@ -202,15 +212,29 @@
|
||||
<tr>
|
||||
<th>Negocio</th>
|
||||
<th>Visitas</th>
|
||||
<th>Interacciones (R/LL/M)</th>
|
||||
<th>Cupones</th>
|
||||
<th>Favoritos</th>
|
||||
<th>Salud</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(data, id) in stats.businesses" :key="id">
|
||||
<td class="id-cell">{{ id }}</td>
|
||||
<tr v-for="(data, name) in stats.businesses" :key="name">
|
||||
<td class="id-cell">{{ name }}</td>
|
||||
<td>{{ data.views }}</td>
|
||||
<td>
|
||||
<div style="display:flex; gap:8px; font-size:0.75rem; color:#cbd5e1;">
|
||||
<span title="Click en Redes Sociales">S: {{ data.social }}</span>
|
||||
<span title="Llamadas Realizadas">L: {{ data.calls }}</span>
|
||||
<span title="Visitas a Maps">M: {{ data.location }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ data.promos }}</td>
|
||||
<td>
|
||||
<span style="display:flex; align-items:center; gap:2px;">
|
||||
<span class="material-icons" style="font-size:14px; color:#e91e63;">favorite</span> {{ data.favs }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="status-pill" :class="getHealthClass(calculateConversion(data.views, data.promos))">
|
||||
{{ getHealthLabel(calculateConversion(data.views, data.promos)) }}
|
||||
@ -395,49 +419,78 @@ const getHealthLabel = (rate: any) => (parseFloat(rate) > 20 ? 'Alta' : parseFlo
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// Load all data in parallel
|
||||
const [
|
||||
{ count: userCount },
|
||||
{ data: shuttles },
|
||||
{ data: routes },
|
||||
{ data: businesses }
|
||||
{ data: events }
|
||||
] = await Promise.all([
|
||||
supabase.from('users').select('*', { count: 'exact', head: true }).eq('is_active', true),
|
||||
supabase.from('shuttles').select('id, route_name'),
|
||||
supabase.from('routes').select('id, name'),
|
||||
supabase.from('businesses').select('id, name')
|
||||
// In a production app with >1M rows we might use a group-by RPC
|
||||
supabase.from('analytics_events').select('*')
|
||||
])
|
||||
|
||||
const shuttleStats: any = {}
|
||||
for (const s of (shuttles || [])) {
|
||||
shuttleStats[s.route_name || s.id] = { views: Math.floor(Math.random() * 100), contacts: Math.floor(Math.random() * 20) }
|
||||
}
|
||||
|
||||
const routeStats: any = {}
|
||||
for (const r of (routes || [])) {
|
||||
routeStats[r.name || r.id] = { views: Math.floor(Math.random() * 80), contacts: Math.floor(Math.random() * 15) }
|
||||
}
|
||||
|
||||
const bizStats: any = {}
|
||||
for (const b of (businesses || [])) {
|
||||
bizStats[b.name || b.id] = { views: Math.floor(Math.random() * 60), promos: Math.floor(Math.random() * 10) }
|
||||
let total_promo_clicks = 0
|
||||
let total_shuttle_contacts = 0
|
||||
let total_biz_views = 0
|
||||
|
||||
const safeRows = events || []
|
||||
|
||||
for (const ev of safeRows) {
|
||||
const nameKey = ev.entity_name || ev.entity_id
|
||||
|
||||
if (ev.entity_type === 'shuttle') {
|
||||
if (!shuttleStats[nameKey]) {
|
||||
shuttleStats[nameKey] = { views: 0, contacts: 0, calls: 0, whatsapp: 0 }
|
||||
}
|
||||
|
||||
if (ev.event_name === 'view_details') {
|
||||
shuttleStats[nameKey].views++
|
||||
} else if (ev.event_name === 'shuttle_contact') {
|
||||
shuttleStats[nameKey].contacts++
|
||||
total_shuttle_contacts++
|
||||
if (ev.properties?.action === 'whatsapp') shuttleStats[nameKey].whatsapp++
|
||||
if (ev.properties?.action === 'call') shuttleStats[nameKey].calls++
|
||||
}
|
||||
|
||||
} else if (ev.entity_type === 'business') {
|
||||
if (!bizStats[nameKey]) {
|
||||
bizStats[nameKey] = { views: 0, promos: 0, favs: 0, social: 0, location: 0, calls: 0 }
|
||||
}
|
||||
|
||||
if (ev.event_name === 'view_details') {
|
||||
bizStats[nameKey].views++
|
||||
total_biz_views++
|
||||
} else if (ev.event_name === 'favorite_add') {
|
||||
bizStats[nameKey].favs++
|
||||
} else if (ev.event_name === 'social_click') {
|
||||
bizStats[nameKey].social++
|
||||
} else if (ev.event_name === 'location_click') {
|
||||
bizStats[nameKey].location++
|
||||
} else if (ev.event_name === 'contact_click') {
|
||||
bizStats[nameKey].calls++
|
||||
} else if (ev.event_name === 'promo_click') {
|
||||
bizStats[nameKey].promos++
|
||||
total_promo_clicks++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stats.value = {
|
||||
shuttles: shuttleStats,
|
||||
businesses: bizStats,
|
||||
top_stops: [],
|
||||
top_stops: [], // Legacy logic can still go here if stops tracking is added
|
||||
users: {
|
||||
registered_active: userCount || 0,
|
||||
patterns: { registered: {}, guests: {} }
|
||||
},
|
||||
summary: {
|
||||
total_shuttle_contacts: Object.values(shuttleStats).reduce((a: any, v: any) => a + v.contacts, 0),
|
||||
total_promo_clicks: Object.values(bizStats).reduce((a: any, v: any) => a + v.promos, 0),
|
||||
total_biz_views: Object.values(bizStats).reduce((a: any, v: any) => a + v.views, 0)
|
||||
total_shuttle_contacts,
|
||||
total_promo_clicks,
|
||||
total_biz_views
|
||||
}
|
||||
}
|
||||
} catch (error) { console.error(error); } finally { loading.value = false; }
|
||||
} catch (error) { console.error('Error fetching analytics:', error); } finally { loading.value = false; }
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user