-
-
-
-
-
-
- local_taxi
-
-
-
- {{ taxi.owner_name }}
-📞 {{ taxi.phone_number }}
-
- {{ taxi.license_plate }}
- {{ taxi.corregimiento }}
- {{ taxi.vehicle_type }}
- 🌐 Inglés
- ♿ Accesible
-
-
-
- {{ getShiftLabel(s) }}
-
- ⭐ {{ taxi.rating || 5.0 }}
- {{ taxi.cooperative }}
-
- {{ taxi.is_active ? '● Activo' : '○ Inactivo' }}
-
-
-
-
-
-
+
+
+
+
+
-
+
+
-
+
+
+
+
- local_taxi
-
+
+
+ No hay taxis en el directorio
-
+ {{ taxi.license_plate }}
+ {{ taxi.corregimiento }}
+ {{ taxi.vehicle_type }}
+ EN
+ ♿
+
+
+
+
+
+
+ {{ getShiftLabel(s) }}
+
+
+
+ star
+ {{ (taxi.rating || 5).toFixed(1) }}
+
+
+ {{ taxi.is_active ? 'Activo' : 'Inactivo' }}
+
+
+
-
- ([])
-// Modal state
const showModal = ref(false)
-const modalMode = ref<'driver' | 'taxi'>('taxi')
const editingTaxi = ref(null)
-
-// Taxi form
const isSaving = ref(false)
const taxiError = ref('')
const photoFile = ref(null)
+
const taxiForm = reactive({
owner_name: '',
phone_number: '',
@@ -199,30 +226,24 @@ const taxiForm = reactive({
is_active: true
})
-const modalTitle = computed(() => {
- return editingTaxi.value ? 'Editar Taxi' : 'Nuevo Taxi'
-})
+const modalTitle = computed(() => editingTaxi.value ? 'Editar taxi' : 'Nuevo taxi')
-onMounted(() => {
- loadData()
-})
+onMounted(loadData)
async function loadData() {
isLoading.value = true
try {
- // Load taxis from Supabase
- const { data: taxisData, error: errorTaxis } = await supabase.from('taxis').select('*').order('owner_name')
- if (errorTaxis) throw errorTaxis
- taxis.value = taxisData || []
+ const { data, error } = await supabase.from('taxis').select('*').order('owner_name')
+ if (error) throw error
+ taxis.value = data || []
} catch (e) {
- console.error('Error cargando datos:', e)
+ console.error('Error cargando taxis:', e)
} finally {
isLoading.value = false
}
}
function openRegisterModal() {
- modalMode.value = 'taxi'
showModal.value = true
}
@@ -231,12 +252,15 @@ function closeModal() {
editingTaxi.value = null
photoFile.value = null
taxiError.value = ''
+ Object.assign(taxiForm, {
+ owner_name: '', phone_number: '', license_plate: '', vehicle_type: '',
+ corregimiento: '', shifts: [], cooperative: '', rating: 5.0,
+ english_speaking: false, is_accessible: false, is_active: true
+ })
}
function editTaxi(taxi: any) {
editingTaxi.value = taxi
- modalMode.value = 'taxi'
- // shifts: preferimos el array `shifts`, pero si solo tiene el legacy `shift` lo convertimos
const shiftsArr: string[] = Array.isArray(taxi.shifts) && taxi.shifts.length
? taxi.shifts
: (taxi.shift ? [taxi.shift] : [])
@@ -260,17 +284,14 @@ function editTaxi(taxi: any) {
function handleTaxiFileChange(event: Event) {
const target = event.target as HTMLInputElement
- if (target.files && target.files[0]) {
- photoFile.value = target.files[0]
- }
+ if (target.files?.[0]) photoFile.value = target.files[0]
}
async function saveTaxi() {
isSaving.value = true
taxiError.value = ''
-
try {
- let image_url = editingTaxi.value?.image_url || null
+ let image_url = editingTaxi.value?.image_url ?? null
if (photoFile.value) {
const ext = photoFile.value.name.split('.').pop()
const filename = `taxis/${Date.now()}.${ext}`
@@ -279,29 +300,18 @@ async function saveTaxi() {
const { data: urlData } = supabase.storage.from('uploads').getPublicUrl(filename)
image_url = urlData.publicUrl
}
- // Guardamos shifts (array) y tambien shift (primer valor) para compatibilidad legacy
- const payload = {
- ...taxiForm,
- shift: taxiForm.shifts[0] || null, // backward compat
- image_url
- }
+ const payload = { ...taxiForm, shift: taxiForm.shifts[0] || null, image_url }
if (editingTaxi.value) {
- const { error: e } = await supabase.from('taxis').update(payload).eq('id', editingTaxi.value.id)
- if (e) throw e
+ const { error } = await supabase.from('taxis').update(payload).eq('id', editingTaxi.value.id)
+ if (error) throw error
} else {
- const { error: e } = await supabase.from('taxis').insert([payload])
- if (e) throw e
+ const { error } = await supabase.from('taxis').insert([payload])
+ if (error) throw error
}
closeModal()
- Object.assign(taxiForm, {
- owner_name: '', phone_number: '', license_plate: '', vehicle_type: '', corregimiento: '',
- shifts: [], cooperative: '', rating: 5.0, english_speaking: false,
- is_accessible: false, is_active: true
- })
await loadData()
} catch (e: any) {
- taxiError.value = e.message || 'Error al guardar el taxi'
- console.error('Error saving taxi:', e)
+ taxiError.value = e.message || 'Error al guardar'
} finally {
isSaving.value = false
}
@@ -310,645 +320,600 @@ async function saveTaxi() {
async function deleteTaxi(taxi: any) {
if (!confirm(`¿Eliminar a ${taxi.owner_name} del directorio?`)) return
try {
- const { error: e } = await supabase.from('taxis').delete().eq('id', taxi.id)
- if (e) throw e
+ const { error } = await supabase.from('taxis').delete().eq('id', taxi.id)
+ if (error) throw error
await loadData()
- } catch (e) {
+ } catch {
alert('Error al eliminar el taxi')
}
}
function getShiftLabel(shift: string) {
const labels: Record = {
- 'dia': 'Día', 'tarde': 'Tarde', 'noche': 'Noche', 'aeropuerto': 'Aeropuerto'
+ dia: 'Día', tarde: 'Tarde', noche: 'Noche', aeropuerto: 'Aeropuerto'
}
return labels[shift] || shift
}
-
-
-
diff --git a/frontend/src/views/AdminPanel.vue b/frontend/src/views/AdminPanel.vue
index 27a5691..e8e84a8 100644
--- a/frontend/src/views/AdminPanel.vue
+++ b/frontend/src/views/AdminPanel.vue
@@ -1,288 +1,284 @@
+
+
-
-
- {{ modalTitle }}
- -
-
-
@@ -171,20 +199,19 @@ import { computed, onMounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { supabase } from '@/supabase'
import AppImage from '@/components/AppImage.vue'
+import AdminPageHeader from '@/components/admin/AdminPageHeader.vue'
+import LoadingBranded from '@/components/common/LoadingBranded.vue'
const router = useRouter()
const isLoading = ref(false)
const taxis = ref
-
-
+
+ SISTEMA CENTRAL
+
+
+ admin_panel_settings
+ SISTEMA CENTRAL
+
Panel de Control
-Ecosistema Administrativo SIB
-SIBU — Administración
+ -
-
-
-
- insights
-
Inteligencia y Control
+ +
+
+
-
-
-
+ {{ section.icon }}
+ {{ section.label }}
-
-
-
-
- analytics
-
-
- Análisis
-Métricas en tiempo real.
-
-
- report_problem
-
-
- Reportes
-Incidencias de usuarios.
-
- settings_input_component
-
- Infraestructura
-
-
-
-
-
diff --git a/frontend/src/views/AdminReports.vue b/frontend/src/views/AdminReports.vue
index bf17a13..ad42a0c 100644
--- a/frontend/src/views/AdminReports.vue
+++ b/frontend/src/views/AdminReports.vue
@@ -1,70 +1,80 @@
navigation
-
-
-
-
-
-
-
Rutas
-Gestión de trayectos.
+
+
-
-
-
-
-
- location_on
-
-
- Paradas
-Puntos de abordaje.
+
+ {{ item.label }}
+ {{ item.desc }}
-
-
- schedule
-
-
- Horarios
-Frecuencias y salidas.
-
- delivery_dining
-
- Flota y Servicios
-
-
-
-
-
- airport_shuttle
-
-
- Shuttles
-Viajes turísticos.
-
-
- badge
-
-
- Conductores
-Gestión de personal.
-
- hub
-
- Ecosistema Comercial
-
-
-
- storefront
-
-
- Negocios
-Promos y locales.
-
-
+ chevron_right
+
local_activity
-
-
- Actividades
-Experiencias y tours.
-
-
-
-
-
+ Reportes de Usuarios
-
+
+
-
-
+
+
+
+
([])
const isLoading = ref(true)
-const sortedReports = computed(() => {
- return [...reports.value].sort((a, b) =>
- new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
- )
-})
+const sortedReports = computed(() =>
+ [...reports.value].sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
+)
-onMounted(async () => {
- await fetchReports()
-})
+const pending = computed(() => reports.value.filter(r => r.status === 'pending').length)
+const resolved = computed(() => reports.value.filter(r => r.status === 'resolved').length)
+
+onMounted(fetchReports)
async function fetchReports() {
isLoading.value = true
try {
- const data = await reportsService.getReports()
- reports.value = data || []
+ reports.value = (await reportsService.getReports()) || []
} catch (e) {
console.error('Error fetching reports:', e)
} finally {
@@ -103,155 +112,205 @@ async function handleUpdateStatus(id: string, status: string) {
try {
await reportsService.updateReportStatus(id, status)
await fetchReports()
- } catch (e) {
- alert('Error al actualizar el estado del reporte')
+ } catch {
+ alert('Error al actualizar el reporte')
}
}
function formatDate(dateStr: string) {
return new Date(dateStr).toLocaleString('es-ES', {
- day: '2-digit',
- month: 'short',
- hour: '2-digit',
- minute: '2-digit'
+ day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit'
})
}
function statusDisplay(status: string) {
- const mapping: Record = {
- 'pending': 'Pendiente',
- 'resolved': 'Resuelto',
- 'archived': 'Archivado'
- }
- return mapping[status] || status
+ const m: Record = { pending: 'Pendiente', resolved: 'Resuelto', archived: 'Archivado' }
+ return m[status] || status
}
- {{ reports.length }}
- Total Reportes
+ {{ reports.length }}
+ Total
-
- {{ reports.filter(r => r.status === 'pending').length }}
+
-
-
+ {{ pending }}
Pendientes
-
-
-
-
+
+
+
-
-
-
- account_circle
-
-
-
- {{ report.user_name || 'Usuario Anónimo' }}
- {{ formatDate(report.created_at) }} -
- {{ statusDisplay(report.status) }}
-
-
-
-
- {{ report.message }}
-
-
-
-
+
+ {{ resolved }}
+ Resueltos
-
+
-
+ ++ ++ account_circle ++ + {{ statusDisplay(report.status) }} + +++
{{ report.user_name || 'Usuario anónimo' }}
+ +{{ report.message }}
+ ++ + ++
+
- info
-
@@ -73,25 +83,24 @@
import { ref, onMounted, computed } from 'vue'
import { reportsService, type Report } from '@/services/reportsService'
import LoadingBranded from '@/components/common/LoadingBranded.vue'
+import AdminPageHeader from '@/components/admin/AdminPageHeader.vue'
const reports = refNo hay reportes nuevos en este momento.
+ +
+ mark_email_read
+
Sin reportes pendientes
+Todo está en orden por ahora.