refactor: migrate fully to Supabase, remove Firebase/Render/Python backend
- DELETED: entire backend/ (Python/FastAPI — replaced by Supabase)
- DELETED: old/ directory (obsolete code)
- DELETED: render.yaml, inject_api.py, check_tags.py, PENDING_FOR_TOMORROW.md
- DELETED: frontend/src/firebaseConfig.ts (Firebase Auth replaced by Supabase Auth)
- DELETED: frontend/src/services/apiClient.ts (HTTP client for dead backend)
- MIGRATED services to Supabase native:
schedulesService, favoritesService, usersService,
telemetryService (stub), reportsService, analyticsService (stub)
- MIGRATED stores/favorites.ts to Supabase direct queries
- MIGRATED views: SplashScreen, AdminTaxis, AdminDrivers, StrategicAnalytics
- MIGRATED utils/imageUrl.ts to Supabase Storage URLs
- FIXED router/index.ts: guard now uses supabase.auth.getSession()
instead of old localStorage auth_token (fixes logout + map loading)
- FIXED AuthView.vue: removed aggressive watch({ immediate: true })
that caused wrong redirects on map route
- FIXED SplashScreen.vue: navigate() now reads Supabase session + role
- FIXED RLS: added INSERT policy on public.users for trigger
- CONFIRMED: admin@sibu.com assigned ADMIN role in Supabase
2026-02-25 21:49:26 -05:00
|
|
|
|
<template>
|
2026-02-21 09:53:31 -05:00
|
|
|
|
<div class="admin-drivers">
|
|
|
|
|
|
<div class="header">
|
2026-02-25 23:38:05 -05:00
|
|
|
|
<button class="back-link" @click="router.push('/admin')">← Volver al Panel</button>
|
2026-03-04 17:48:48 -05:00
|
|
|
|
<h1>Gestión de Taxis</h1>
|
2026-02-21 09:53:31 -05:00
|
|
|
|
<div class="header-actions">
|
|
|
|
|
|
<button class="btn-primary" @click="openRegisterModal">
|
|
|
|
|
|
<span class="material-icons">add</span>
|
2026-03-04 17:48:48 -05:00
|
|
|
|
Nuevo Taxi
|
2026-02-21 09:53:31 -05:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div v-if="isLoading" class="loading">Cargando...</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Taxis Tab -->
|
2026-03-04 17:48:48 -05:00
|
|
|
|
<div v-else class="content">
|
2026-02-21 09:53:31 -05:00
|
|
|
|
<div v-if="taxis.length > 0" class="items-grid">
|
|
|
|
|
|
<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">
|
|
|
|
|
|
<span v-else class="material-icons">local_taxi</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="info">
|
|
|
|
|
|
<h3>{{ taxi.owner_name }}</h3>
|
|
|
|
|
|
<p class="phone">📞 {{ taxi.phone_number }}</p>
|
|
|
|
|
|
<div class="badges">
|
|
|
|
|
|
<span class="badge plate">{{ taxi.license_plate }}</span>
|
2026-03-04 15:09:10 -05:00
|
|
|
|
<span class="badge" v-if="taxi.corregimiento">{{ taxi.corregimiento }}</span>
|
|
|
|
|
|
<span class="badge" v-if="taxi.vehicle_type">{{ taxi.vehicle_type }}</span>
|
2026-02-21 09:53:31 -05:00
|
|
|
|
<span v-if="taxi.english_speaking" class="badge english">🌐 Inglés</span>
|
2026-03-04 15:09:10 -05:00
|
|
|
|
<span v-if="taxi.is_accessible" class="badge accessible">♿ Accesible</span>
|
2026-02-21 09:53:31 -05:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="taxi-meta">
|
2026-03-04 15:09:10 -05:00
|
|
|
|
<span class="shifts-badges">
|
|
|
|
|
|
<span
|
|
|
|
|
|
v-for="s in (taxi.shifts?.length ? taxi.shifts : (taxi.shift ? [taxi.shift] : []))"
|
|
|
|
|
|
:key="s"
|
|
|
|
|
|
class="shift-pill"
|
|
|
|
|
|
>{{ getShiftLabel(s) }}</span>
|
|
|
|
|
|
</span>
|
2026-02-21 09:53:31 -05:00
|
|
|
|
<span class="rating">⭐ {{ taxi.rating || 5.0 }}</span>
|
|
|
|
|
|
<span v-if="taxi.cooperative" class="cooperative">{{ taxi.cooperative }}</span>
|
|
|
|
|
|
<span :class="taxi.is_active ? 'status-active' : 'status-inactive'">
|
|
|
|
|
|
{{ taxi.is_active ? '● Activo' : '○ Inactivo' }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="card-actions">
|
|
|
|
|
|
<button class="btn-icon" @click.stop="editTaxi(taxi)" title="Editar">
|
|
|
|
|
|
<span class="material-icons">edit</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button class="btn-icon delete" @click.stop="deleteTaxi(taxi)" title="Eliminar">
|
|
|
|
|
|
<span class="material-icons">delete</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-else class="empty-state">
|
|
|
|
|
|
<span class="material-icons">local_taxi</span>
|
|
|
|
|
|
<p>No hay taxis en el directorio</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Register/Edit Modal -->
|
|
|
|
|
|
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
|
|
|
|
|
|
<div class="modal large">
|
|
|
|
|
|
<div class="modal-header">
|
|
|
|
|
|
<h2>{{ modalTitle }}</h2>
|
|
|
|
|
|
<button @click="closeModal">×</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="modal-body">
|
|
|
|
|
|
<!-- Taxi Form -->
|
2026-03-04 17:48:48 -05:00
|
|
|
|
<form @submit.prevent="saveTaxi">
|
2026-02-21 09:53:31 -05:00
|
|
|
|
<div class="form-grid">
|
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
|
<label>Nombre del Conductor *</label>
|
|
|
|
|
|
<input v-model="taxiForm.owner_name" type="text" placeholder="Juan Pérez" required>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
|
<label>Teléfono *</label>
|
|
|
|
|
|
<input v-model="taxiForm.phone_number" type="tel" placeholder="+507 1234-5678" required>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
|
<label>Placa del Vehículo *</label>
|
|
|
|
|
|
<input v-model="taxiForm.license_plate" type="text" placeholder="CHI-1234" required>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-04 15:09:10 -05:00
|
|
|
|
<div class="form-group">
|
|
|
|
|
|
<label>Tipo de Vehículo</label>
|
|
|
|
|
|
<input v-model="taxiForm.vehicle_type" type="text" placeholder="Toyota Corolla / SUV / Van">
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-02-21 09:53:31 -05:00
|
|
|
|
<div class="form-group">
|
|
|
|
|
|
<label>Zona de Servicio *</label>
|
|
|
|
|
|
<select v-model="taxiForm.corregimiento" required>
|
|
|
|
|
|
<option value="">Seleccionar...</option>
|
|
|
|
|
|
<option value="Boquete">Boquete</option>
|
|
|
|
|
|
<option value="David - Boquete">David - Boquete</option>
|
|
|
|
|
|
<option value="Boquete - David">Boquete - David</option>
|
|
|
|
|
|
<option value="Aeropuerto - Boquete">Aeropuerto - Boquete</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
|
<label>Cooperativa</label>
|
|
|
|
|
|
<input v-model="taxiForm.cooperative" type="text" placeholder="Cooperativa Boquete">
|
|
|
|
|
|
</div>
|
2026-03-04 15:09:10 -05:00
|
|
|
|
|
2026-02-21 09:53:31 -05:00
|
|
|
|
<div class="form-group">
|
|
|
|
|
|
<label>Rating (1-5)</label>
|
|
|
|
|
|
<input v-model.number="taxiForm.rating" type="number" min="1" max="5" step="0.1" placeholder="5.0">
|
|
|
|
|
|
</div>
|
2026-03-04 15:09:10 -05:00
|
|
|
|
|
|
|
|
|
|
<!-- Horarios (múltiples) -->
|
|
|
|
|
|
<div class="form-group full-col">
|
|
|
|
|
|
<label>Horarios de Servicio</label>
|
|
|
|
|
|
<div class="checkbox-group horizontal">
|
|
|
|
|
|
<label v-for="s in ['dia', 'tarde', 'noche', 'aeropuerto']" :key="s" class="checkbox-item">
|
|
|
|
|
|
<input type="checkbox" v-model="taxiForm.shifts" :value="s">
|
|
|
|
|
|
<span>{{ getShiftLabel(s) }}</span>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Flags -->
|
|
|
|
|
|
<div class="form-group full-col flags-row">
|
|
|
|
|
|
<label class="checkbox-item">
|
2026-02-21 09:53:31 -05:00
|
|
|
|
<input v-model="taxiForm.english_speaking" type="checkbox">
|
2026-03-04 15:09:10 -05:00
|
|
|
|
<span>🌐 Habla Inglés</span>
|
2026-02-21 09:53:31 -05:00
|
|
|
|
</label>
|
2026-03-04 15:09:10 -05:00
|
|
|
|
<label class="checkbox-item">
|
|
|
|
|
|
<input v-model="taxiForm.is_accessible" type="checkbox">
|
|
|
|
|
|
<span>♿ Accesible para discapacitados</span>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label class="checkbox-item">
|
2026-02-21 09:53:31 -05:00
|
|
|
|
<input v-model="taxiForm.is_active" type="checkbox">
|
|
|
|
|
|
<span>Activo en el directorio</span>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-04 15:09:10 -05:00
|
|
|
|
<div class="form-group full-col">
|
2026-02-21 09:53:31 -05:00
|
|
|
|
<label>Foto del Conductor</label>
|
|
|
|
|
|
<input type="file" @change="handleTaxiFileChange" accept="image/*">
|
|
|
|
|
|
<small>Opcional - Foto para el directorio público</small>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<p v-if="taxiError" class="error-text">{{ taxiError }}</p>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="form-actions">
|
|
|
|
|
|
<button type="button" class="btn-secondary" @click="closeModal">Cancelar</button>
|
|
|
|
|
|
<button type="submit" class="btn-primary" :disabled="isSaving">
|
|
|
|
|
|
{{ isSaving ? 'Guardando...' : 'Guardar' }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import { ref, onMounted, reactive, computed } from 'vue'
|
2026-02-25 23:38:05 -05:00
|
|
|
|
import { useRouter } from 'vue-router'
|
refactor: migrate fully to Supabase, remove Firebase/Render/Python backend
- DELETED: entire backend/ (Python/FastAPI — replaced by Supabase)
- DELETED: old/ directory (obsolete code)
- DELETED: render.yaml, inject_api.py, check_tags.py, PENDING_FOR_TOMORROW.md
- DELETED: frontend/src/firebaseConfig.ts (Firebase Auth replaced by Supabase Auth)
- DELETED: frontend/src/services/apiClient.ts (HTTP client for dead backend)
- MIGRATED services to Supabase native:
schedulesService, favoritesService, usersService,
telemetryService (stub), reportsService, analyticsService (stub)
- MIGRATED stores/favorites.ts to Supabase direct queries
- MIGRATED views: SplashScreen, AdminTaxis, AdminDrivers, StrategicAnalytics
- MIGRATED utils/imageUrl.ts to Supabase Storage URLs
- FIXED router/index.ts: guard now uses supabase.auth.getSession()
instead of old localStorage auth_token (fixes logout + map loading)
- FIXED AuthView.vue: removed aggressive watch({ immediate: true })
that caused wrong redirects on map route
- FIXED SplashScreen.vue: navigate() now reads Supabase session + role
- FIXED RLS: added INSERT policy on public.users for trigger
- CONFIRMED: admin@sibu.com assigned ADMIN role in Supabase
2026-02-25 21:49:26 -05:00
|
|
|
|
import { supabase } from '@/supabase'
|
2026-02-21 09:53:31 -05:00
|
|
|
|
|
2026-02-25 23:38:05 -05:00
|
|
|
|
const router = useRouter()
|
2026-02-21 09:53:31 -05:00
|
|
|
|
const isLoading = ref(false)
|
|
|
|
|
|
const taxis = ref<any[]>([])
|
|
|
|
|
|
|
|
|
|
|
|
// Modal state
|
|
|
|
|
|
const showModal = ref(false)
|
2026-03-04 17:48:48 -05:00
|
|
|
|
const modalMode = ref<'driver' | 'taxi'>('taxi')
|
2026-02-21 09:53:31 -05:00
|
|
|
|
const editingTaxi = ref<any>(null)
|
|
|
|
|
|
|
|
|
|
|
|
// Taxi form
|
|
|
|
|
|
const isSaving = ref(false)
|
|
|
|
|
|
const taxiError = ref('')
|
|
|
|
|
|
const photoFile = ref<File | null>(null)
|
|
|
|
|
|
const taxiForm = reactive({
|
|
|
|
|
|
owner_name: '',
|
|
|
|
|
|
phone_number: '',
|
|
|
|
|
|
license_plate: '',
|
2026-03-04 15:09:10 -05:00
|
|
|
|
vehicle_type: '',
|
2026-02-21 09:53:31 -05:00
|
|
|
|
corregimiento: '',
|
2026-03-04 15:09:10 -05:00
|
|
|
|
shifts: [] as string[],
|
2026-02-21 09:53:31 -05:00
|
|
|
|
cooperative: '',
|
|
|
|
|
|
rating: 5.0,
|
|
|
|
|
|
english_speaking: false,
|
2026-03-04 15:09:10 -05:00
|
|
|
|
is_accessible: false,
|
2026-02-21 09:53:31 -05:00
|
|
|
|
is_active: true
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const modalTitle = computed(() => {
|
|
|
|
|
|
return editingTaxi.value ? 'Editar Taxi' : 'Nuevo Taxi'
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
loadData()
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
async function loadData() {
|
|
|
|
|
|
isLoading.value = true
|
|
|
|
|
|
try {
|
refactor: migrate fully to Supabase, remove Firebase/Render/Python backend
- DELETED: entire backend/ (Python/FastAPI — replaced by Supabase)
- DELETED: old/ directory (obsolete code)
- DELETED: render.yaml, inject_api.py, check_tags.py, PENDING_FOR_TOMORROW.md
- DELETED: frontend/src/firebaseConfig.ts (Firebase Auth replaced by Supabase Auth)
- DELETED: frontend/src/services/apiClient.ts (HTTP client for dead backend)
- MIGRATED services to Supabase native:
schedulesService, favoritesService, usersService,
telemetryService (stub), reportsService, analyticsService (stub)
- MIGRATED stores/favorites.ts to Supabase direct queries
- MIGRATED views: SplashScreen, AdminTaxis, AdminDrivers, StrategicAnalytics
- MIGRATED utils/imageUrl.ts to Supabase Storage URLs
- FIXED router/index.ts: guard now uses supabase.auth.getSession()
instead of old localStorage auth_token (fixes logout + map loading)
- FIXED AuthView.vue: removed aggressive watch({ immediate: true })
that caused wrong redirects on map route
- FIXED SplashScreen.vue: navigate() now reads Supabase session + role
- FIXED RLS: added INSERT policy on public.users for trigger
- CONFIRMED: admin@sibu.com assigned ADMIN role in Supabase
2026-02-25 21:49:26 -05:00
|
|
|
|
// Load taxis from Supabase
|
2026-02-26 16:20:59 -05:00
|
|
|
|
const { data: taxisData, error: errorTaxis } = await supabase.from('taxis').select('*').order('owner_name')
|
|
|
|
|
|
if (errorTaxis) throw errorTaxis
|
refactor: migrate fully to Supabase, remove Firebase/Render/Python backend
- DELETED: entire backend/ (Python/FastAPI — replaced by Supabase)
- DELETED: old/ directory (obsolete code)
- DELETED: render.yaml, inject_api.py, check_tags.py, PENDING_FOR_TOMORROW.md
- DELETED: frontend/src/firebaseConfig.ts (Firebase Auth replaced by Supabase Auth)
- DELETED: frontend/src/services/apiClient.ts (HTTP client for dead backend)
- MIGRATED services to Supabase native:
schedulesService, favoritesService, usersService,
telemetryService (stub), reportsService, analyticsService (stub)
- MIGRATED stores/favorites.ts to Supabase direct queries
- MIGRATED views: SplashScreen, AdminTaxis, AdminDrivers, StrategicAnalytics
- MIGRATED utils/imageUrl.ts to Supabase Storage URLs
- FIXED router/index.ts: guard now uses supabase.auth.getSession()
instead of old localStorage auth_token (fixes logout + map loading)
- FIXED AuthView.vue: removed aggressive watch({ immediate: true })
that caused wrong redirects on map route
- FIXED SplashScreen.vue: navigate() now reads Supabase session + role
- FIXED RLS: added INSERT policy on public.users for trigger
- CONFIRMED: admin@sibu.com assigned ADMIN role in Supabase
2026-02-25 21:49:26 -05:00
|
|
|
|
taxis.value = taxisData || []
|
2026-02-21 09:53:31 -05:00
|
|
|
|
} catch (e) {
|
2026-02-26 16:20:59 -05:00
|
|
|
|
console.error('Error cargando datos:', e)
|
2026-02-21 09:53:31 -05:00
|
|
|
|
} finally {
|
|
|
|
|
|
isLoading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openRegisterModal() {
|
2026-03-04 17:48:48 -05:00
|
|
|
|
modalMode.value = 'taxi'
|
2026-02-21 09:53:31 -05:00
|
|
|
|
showModal.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closeModal() {
|
|
|
|
|
|
showModal.value = false
|
|
|
|
|
|
editingTaxi.value = null
|
|
|
|
|
|
photoFile.value = null
|
|
|
|
|
|
taxiError.value = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function editTaxi(taxi: any) {
|
|
|
|
|
|
editingTaxi.value = taxi
|
|
|
|
|
|
modalMode.value = 'taxi'
|
2026-03-04 15:09:10 -05:00
|
|
|
|
// 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] : [])
|
2026-02-21 09:53:31 -05:00
|
|
|
|
Object.assign(taxiForm, {
|
|
|
|
|
|
owner_name: taxi.owner_name,
|
|
|
|
|
|
phone_number: taxi.phone_number,
|
|
|
|
|
|
license_plate: taxi.license_plate,
|
2026-03-04 15:09:10 -05:00
|
|
|
|
vehicle_type: taxi.vehicle_type || '',
|
2026-02-21 09:53:31 -05:00
|
|
|
|
corregimiento: taxi.corregimiento,
|
2026-03-04 15:09:10 -05:00
|
|
|
|
shifts: shiftsArr,
|
2026-02-21 09:53:31 -05:00
|
|
|
|
cooperative: taxi.cooperative || '',
|
|
|
|
|
|
rating: taxi.rating || 5.0,
|
|
|
|
|
|
english_speaking: taxi.english_speaking || false,
|
2026-03-04 15:09:10 -05:00
|
|
|
|
is_accessible: taxi.is_accessible || false,
|
2026-02-21 09:53:31 -05:00
|
|
|
|
is_active: taxi.is_active
|
|
|
|
|
|
})
|
|
|
|
|
|
photoFile.value = null
|
|
|
|
|
|
taxiError.value = ''
|
|
|
|
|
|
showModal.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleTaxiFileChange(event: Event) {
|
|
|
|
|
|
const target = event.target as HTMLInputElement
|
|
|
|
|
|
if (target.files && target.files[0]) {
|
|
|
|
|
|
photoFile.value = target.files[0]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function saveTaxi() {
|
|
|
|
|
|
isSaving.value = true
|
|
|
|
|
|
taxiError.value = ''
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
refactor: migrate fully to Supabase, remove Firebase/Render/Python backend
- DELETED: entire backend/ (Python/FastAPI — replaced by Supabase)
- DELETED: old/ directory (obsolete code)
- DELETED: render.yaml, inject_api.py, check_tags.py, PENDING_FOR_TOMORROW.md
- DELETED: frontend/src/firebaseConfig.ts (Firebase Auth replaced by Supabase Auth)
- DELETED: frontend/src/services/apiClient.ts (HTTP client for dead backend)
- MIGRATED services to Supabase native:
schedulesService, favoritesService, usersService,
telemetryService (stub), reportsService, analyticsService (stub)
- MIGRATED stores/favorites.ts to Supabase direct queries
- MIGRATED views: SplashScreen, AdminTaxis, AdminDrivers, StrategicAnalytics
- MIGRATED utils/imageUrl.ts to Supabase Storage URLs
- FIXED router/index.ts: guard now uses supabase.auth.getSession()
instead of old localStorage auth_token (fixes logout + map loading)
- FIXED AuthView.vue: removed aggressive watch({ immediate: true })
that caused wrong redirects on map route
- FIXED SplashScreen.vue: navigate() now reads Supabase session + role
- FIXED RLS: added INSERT policy on public.users for trigger
- CONFIRMED: admin@sibu.com assigned ADMIN role in Supabase
2026-02-25 21:49:26 -05:00
|
|
|
|
let image_url = editingTaxi.value?.image_url || null
|
|
|
|
|
|
if (photoFile.value) {
|
|
|
|
|
|
const ext = photoFile.value.name.split('.').pop()
|
|
|
|
|
|
const filename = `taxis/${Date.now()}.${ext}`
|
|
|
|
|
|
const { error: upErr } = await supabase.storage.from('uploads').upload(filename, photoFile.value)
|
|
|
|
|
|
if (upErr) throw upErr
|
|
|
|
|
|
const { data: urlData } = supabase.storage.from('uploads').getPublicUrl(filename)
|
|
|
|
|
|
image_url = urlData.publicUrl
|
|
|
|
|
|
}
|
2026-03-04 15:09:10 -05:00
|
|
|
|
// Guardamos shifts (array) y tambien shift (primer valor) para compatibilidad legacy
|
|
|
|
|
|
const payload = {
|
|
|
|
|
|
...taxiForm,
|
|
|
|
|
|
shift: taxiForm.shifts[0] || null, // backward compat
|
|
|
|
|
|
image_url
|
|
|
|
|
|
}
|
2026-02-21 09:53:31 -05:00
|
|
|
|
if (editingTaxi.value) {
|
refactor: migrate fully to Supabase, remove Firebase/Render/Python backend
- DELETED: entire backend/ (Python/FastAPI — replaced by Supabase)
- DELETED: old/ directory (obsolete code)
- DELETED: render.yaml, inject_api.py, check_tags.py, PENDING_FOR_TOMORROW.md
- DELETED: frontend/src/firebaseConfig.ts (Firebase Auth replaced by Supabase Auth)
- DELETED: frontend/src/services/apiClient.ts (HTTP client for dead backend)
- MIGRATED services to Supabase native:
schedulesService, favoritesService, usersService,
telemetryService (stub), reportsService, analyticsService (stub)
- MIGRATED stores/favorites.ts to Supabase direct queries
- MIGRATED views: SplashScreen, AdminTaxis, AdminDrivers, StrategicAnalytics
- MIGRATED utils/imageUrl.ts to Supabase Storage URLs
- FIXED router/index.ts: guard now uses supabase.auth.getSession()
instead of old localStorage auth_token (fixes logout + map loading)
- FIXED AuthView.vue: removed aggressive watch({ immediate: true })
that caused wrong redirects on map route
- FIXED SplashScreen.vue: navigate() now reads Supabase session + role
- FIXED RLS: added INSERT policy on public.users for trigger
- CONFIRMED: admin@sibu.com assigned ADMIN role in Supabase
2026-02-25 21:49:26 -05:00
|
|
|
|
const { error: e } = await supabase.from('taxis').update(payload).eq('id', editingTaxi.value.id)
|
|
|
|
|
|
if (e) throw e
|
2026-02-21 09:53:31 -05:00
|
|
|
|
} else {
|
refactor: migrate fully to Supabase, remove Firebase/Render/Python backend
- DELETED: entire backend/ (Python/FastAPI — replaced by Supabase)
- DELETED: old/ directory (obsolete code)
- DELETED: render.yaml, inject_api.py, check_tags.py, PENDING_FOR_TOMORROW.md
- DELETED: frontend/src/firebaseConfig.ts (Firebase Auth replaced by Supabase Auth)
- DELETED: frontend/src/services/apiClient.ts (HTTP client for dead backend)
- MIGRATED services to Supabase native:
schedulesService, favoritesService, usersService,
telemetryService (stub), reportsService, analyticsService (stub)
- MIGRATED stores/favorites.ts to Supabase direct queries
- MIGRATED views: SplashScreen, AdminTaxis, AdminDrivers, StrategicAnalytics
- MIGRATED utils/imageUrl.ts to Supabase Storage URLs
- FIXED router/index.ts: guard now uses supabase.auth.getSession()
instead of old localStorage auth_token (fixes logout + map loading)
- FIXED AuthView.vue: removed aggressive watch({ immediate: true })
that caused wrong redirects on map route
- FIXED SplashScreen.vue: navigate() now reads Supabase session + role
- FIXED RLS: added INSERT policy on public.users for trigger
- CONFIRMED: admin@sibu.com assigned ADMIN role in Supabase
2026-02-25 21:49:26 -05:00
|
|
|
|
const { error: e } = await supabase.from('taxis').insert([payload])
|
|
|
|
|
|
if (e) throw e
|
2026-02-21 09:53:31 -05:00
|
|
|
|
}
|
|
|
|
|
|
closeModal()
|
|
|
|
|
|
Object.assign(taxiForm, {
|
2026-03-04 15:09:10 -05:00
|
|
|
|
owner_name: '', phone_number: '', license_plate: '', vehicle_type: '', corregimiento: '',
|
|
|
|
|
|
shifts: [], cooperative: '', rating: 5.0, english_speaking: false,
|
|
|
|
|
|
is_accessible: false, is_active: true
|
2026-02-21 09:53:31 -05:00
|
|
|
|
})
|
|
|
|
|
|
await loadData()
|
|
|
|
|
|
} catch (e: any) {
|
refactor: migrate fully to Supabase, remove Firebase/Render/Python backend
- DELETED: entire backend/ (Python/FastAPI — replaced by Supabase)
- DELETED: old/ directory (obsolete code)
- DELETED: render.yaml, inject_api.py, check_tags.py, PENDING_FOR_TOMORROW.md
- DELETED: frontend/src/firebaseConfig.ts (Firebase Auth replaced by Supabase Auth)
- DELETED: frontend/src/services/apiClient.ts (HTTP client for dead backend)
- MIGRATED services to Supabase native:
schedulesService, favoritesService, usersService,
telemetryService (stub), reportsService, analyticsService (stub)
- MIGRATED stores/favorites.ts to Supabase direct queries
- MIGRATED views: SplashScreen, AdminTaxis, AdminDrivers, StrategicAnalytics
- MIGRATED utils/imageUrl.ts to Supabase Storage URLs
- FIXED router/index.ts: guard now uses supabase.auth.getSession()
instead of old localStorage auth_token (fixes logout + map loading)
- FIXED AuthView.vue: removed aggressive watch({ immediate: true })
that caused wrong redirects on map route
- FIXED SplashScreen.vue: navigate() now reads Supabase session + role
- FIXED RLS: added INSERT policy on public.users for trigger
- CONFIRMED: admin@sibu.com assigned ADMIN role in Supabase
2026-02-25 21:49:26 -05:00
|
|
|
|
taxiError.value = e.message || 'Error al guardar el taxi'
|
2026-02-21 09:53:31 -05:00
|
|
|
|
console.error('Error saving taxi:', e)
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
isSaving.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function deleteTaxi(taxi: any) {
|
|
|
|
|
|
if (!confirm(`¿Eliminar a ${taxi.owner_name} del directorio?`)) return
|
|
|
|
|
|
try {
|
refactor: migrate fully to Supabase, remove Firebase/Render/Python backend
- DELETED: entire backend/ (Python/FastAPI — replaced by Supabase)
- DELETED: old/ directory (obsolete code)
- DELETED: render.yaml, inject_api.py, check_tags.py, PENDING_FOR_TOMORROW.md
- DELETED: frontend/src/firebaseConfig.ts (Firebase Auth replaced by Supabase Auth)
- DELETED: frontend/src/services/apiClient.ts (HTTP client for dead backend)
- MIGRATED services to Supabase native:
schedulesService, favoritesService, usersService,
telemetryService (stub), reportsService, analyticsService (stub)
- MIGRATED stores/favorites.ts to Supabase direct queries
- MIGRATED views: SplashScreen, AdminTaxis, AdminDrivers, StrategicAnalytics
- MIGRATED utils/imageUrl.ts to Supabase Storage URLs
- FIXED router/index.ts: guard now uses supabase.auth.getSession()
instead of old localStorage auth_token (fixes logout + map loading)
- FIXED AuthView.vue: removed aggressive watch({ immediate: true })
that caused wrong redirects on map route
- FIXED SplashScreen.vue: navigate() now reads Supabase session + role
- FIXED RLS: added INSERT policy on public.users for trigger
- CONFIRMED: admin@sibu.com assigned ADMIN role in Supabase
2026-02-25 21:49:26 -05:00
|
|
|
|
const { error: e } = await supabase.from('taxis').delete().eq('id', taxi.id)
|
|
|
|
|
|
if (e) throw e
|
2026-02-21 09:53:31 -05:00
|
|
|
|
await loadData()
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
alert('Error al eliminar el taxi')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getShiftLabel(shift: string) {
|
|
|
|
|
|
const labels: Record<string, string> = {
|
2026-03-04 15:09:10 -05:00
|
|
|
|
'dia': 'Día', 'tarde': 'Tarde', 'noche': 'Noche', 'aeropuerto': 'Aeropuerto'
|
2026-02-21 09:53:31 -05:00
|
|
|
|
}
|
|
|
|
|
|
return labels[shift] || shift
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getImageUrl(path: string) {
|
refactor: migrate fully to Supabase, remove Firebase/Render/Python backend
- DELETED: entire backend/ (Python/FastAPI — replaced by Supabase)
- DELETED: old/ directory (obsolete code)
- DELETED: render.yaml, inject_api.py, check_tags.py, PENDING_FOR_TOMORROW.md
- DELETED: frontend/src/firebaseConfig.ts (Firebase Auth replaced by Supabase Auth)
- DELETED: frontend/src/services/apiClient.ts (HTTP client for dead backend)
- MIGRATED services to Supabase native:
schedulesService, favoritesService, usersService,
telemetryService (stub), reportsService, analyticsService (stub)
- MIGRATED stores/favorites.ts to Supabase direct queries
- MIGRATED views: SplashScreen, AdminTaxis, AdminDrivers, StrategicAnalytics
- MIGRATED utils/imageUrl.ts to Supabase Storage URLs
- FIXED router/index.ts: guard now uses supabase.auth.getSession()
instead of old localStorage auth_token (fixes logout + map loading)
- FIXED AuthView.vue: removed aggressive watch({ immediate: true })
that caused wrong redirects on map route
- FIXED SplashScreen.vue: navigate() now reads Supabase session + role
- FIXED RLS: added INSERT policy on public.users for trigger
- CONFIRMED: admin@sibu.com assigned ADMIN role in Supabase
2026-02-25 21:49:26 -05:00
|
|
|
|
if (!path) return ''
|
2026-02-21 09:53:31 -05:00
|
|
|
|
if (path.startsWith('http')) return path
|
refactor: migrate fully to Supabase, remove Firebase/Render/Python backend
- DELETED: entire backend/ (Python/FastAPI — replaced by Supabase)
- DELETED: old/ directory (obsolete code)
- DELETED: render.yaml, inject_api.py, check_tags.py, PENDING_FOR_TOMORROW.md
- DELETED: frontend/src/firebaseConfig.ts (Firebase Auth replaced by Supabase Auth)
- DELETED: frontend/src/services/apiClient.ts (HTTP client for dead backend)
- MIGRATED services to Supabase native:
schedulesService, favoritesService, usersService,
telemetryService (stub), reportsService, analyticsService (stub)
- MIGRATED stores/favorites.ts to Supabase direct queries
- MIGRATED views: SplashScreen, AdminTaxis, AdminDrivers, StrategicAnalytics
- MIGRATED utils/imageUrl.ts to Supabase Storage URLs
- FIXED router/index.ts: guard now uses supabase.auth.getSession()
instead of old localStorage auth_token (fixes logout + map loading)
- FIXED AuthView.vue: removed aggressive watch({ immediate: true })
that caused wrong redirects on map route
- FIXED SplashScreen.vue: navigate() now reads Supabase session + role
- FIXED RLS: added INSERT policy on public.users for trigger
- CONFIRMED: admin@sibu.com assigned ADMIN role in Supabase
2026-02-25 21:49:26 -05:00
|
|
|
|
return path
|
2026-02-21 09:53:31 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-04 17:48:48 -05:00
|
|
|
|
|
2026-02-21 09:53:31 -05:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.admin-drivers {
|
|
|
|
|
|
padding: 24px;
|
|
|
|
|
|
background: var(--bg-primary);
|
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.back-link {
|
|
|
|
|
|
background: var(--bg-secondary);
|
|
|
|
|
|
border: 1px solid var(--border-color);
|
|
|
|
|
|
color: var(--text-primary);
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
font-size: 0.95rem;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
padding: 10px 16px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.back-link:hover {
|
|
|
|
|
|
background: var(--hover-bg);
|
|
|
|
|
|
border-color: var(--accent-color);
|
|
|
|
|
|
color: var(--accent-color);
|
|
|
|
|
|
transform: translateX(-2px);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
h1 {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
font-size: 28px;
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
color: var(--text-primary);
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn-primary {
|
|
|
|
|
|
background: var(--accent-color);
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
padding: 12px 24px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
transition: background 0.2s;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn-primary:hover {
|
|
|
|
|
|
background: var(--accent-hover);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn-primary:disabled {
|
|
|
|
|
|
opacity: 0.5;
|
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Tabs */
|
|
|
|
|
|
.tabs {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
|
border-bottom: 2px solid var(--border-color);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tabs button {
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
padding: 12px 24px;
|
|
|
|
|
|
font-size: 1rem;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: var(--text-secondary);
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
border-bottom: 3px solid transparent;
|
|
|
|
|
|
margin-bottom: -2px;
|
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tabs button:hover {
|
|
|
|
|
|
color: var(--text-primary);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tabs button.active {
|
|
|
|
|
|
color: var(--accent-color);
|
|
|
|
|
|
border-bottom-color: var(--accent-color);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.loading, .empty-state {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
padding: 60px 20px;
|
|
|
|
|
|
color: var(--text-secondary);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.empty-state .material-icons {
|
|
|
|
|
|
font-size: 64px;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
opacity: 0.3;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Items Grid */
|
|
|
|
|
|
.items-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.item-card {
|
|
|
|
|
|
background: var(--card-bg);
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
border: 1px solid var(--border-color);
|
|
|
|
|
|
box-shadow: 0 2px 8px var(--shadow);
|
|
|
|
|
|
transition: transform 0.2s, box-shadow 0.2s;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.item-card:hover {
|
|
|
|
|
|
transform: translateY(-2px);
|
|
|
|
|
|
box-shadow: 0 4px 16px var(--shadow);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.taxi-card {
|
|
|
|
|
|
cursor: default;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.avatar {
|
|
|
|
|
|
width: 60px;
|
|
|
|
|
|
height: 60px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
background: var(--bg-secondary);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
border: 2px solid var(--border-color);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.avatar img {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.avatar .material-icons {
|
|
|
|
|
|
font-size: 32px;
|
|
|
|
|
|
color: var(--text-secondary);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.info {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.info h3 {
|
|
|
|
|
|
margin: 0 0 4px;
|
|
|
|
|
|
font-size: 1.1rem;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
color: var(--text-primary);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.email, .phone {
|
|
|
|
|
|
color: var(--text-secondary);
|
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
|
margin: 4px 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.badges {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.badge {
|
|
|
|
|
|
background: var(--bg-secondary);
|
|
|
|
|
|
padding: 4px 10px;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: var(--text-primary);
|
|
|
|
|
|
border: 1px solid var(--border-color);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.badge.taxi {
|
|
|
|
|
|
background: #fee715;
|
|
|
|
|
|
color: #101820;
|
|
|
|
|
|
border-color: #fee715;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.badge.bus {
|
|
|
|
|
|
background: var(--accent-color);
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
border-color: var(--accent-color);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.badge.plate {
|
|
|
|
|
|
font-family: 'Courier New', monospace;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.badge.english {
|
|
|
|
|
|
background: #4a90e2;
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
border-color: #4a90e2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-04 15:09:10 -05:00
|
|
|
|
.badge.accessible {
|
|
|
|
|
|
background: #0ea5e9;
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
border-color: #0ea5e9;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Shift pills in taxi card */
|
|
|
|
|
|
.shifts-badges {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.shift-pill {
|
|
|
|
|
|
background: #fee715;
|
|
|
|
|
|
color: #101820;
|
|
|
|
|
|
padding: 2px 8px;
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
font-size: 0.72rem;
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-21 09:53:31 -05:00
|
|
|
|
.taxi-meta {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.rating {
|
|
|
|
|
|
color: #fee715;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cooperative {
|
|
|
|
|
|
color: var(--text-secondary);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.status-active {
|
|
|
|
|
|
color: var(--accent-color);
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.status-inactive {
|
|
|
|
|
|
color: var(--text-secondary);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn-icon {
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
border: 1px solid var(--border-color);
|
|
|
|
|
|
width: 36px;
|
|
|
|
|
|
height: 36px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
|
color: var(--text-secondary);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn-icon:hover {
|
|
|
|
|
|
background: var(--hover-bg);
|
|
|
|
|
|
color: var(--text-primary);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn-icon.delete:hover {
|
|
|
|
|
|
background: #fee;
|
|
|
|
|
|
border-color: #f44;
|
|
|
|
|
|
color: #f44;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Modal Styles */
|
|
|
|
|
|
.modal-overlay {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
background: rgba(0, 0, 0, 0.5);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
z-index: 1000;
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal {
|
|
|
|
|
|
background: var(--card-bg);
|
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
|
max-width: 600px;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
max-height: 90vh;
|
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal.large {
|
|
|
|
|
|
max-width: 800px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 24px;
|
|
|
|
|
|
border-bottom: 1px solid var(--border-color);
|
|
|
|
|
|
position: sticky;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
background: var(--card-bg);
|
|
|
|
|
|
z-index: 10;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-header h2 {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
font-size: 1.5rem;
|
|
|
|
|
|
color: var(--text-primary);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-header button {
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
font-size: 2rem;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
color: var(--text-secondary);
|
|
|
|
|
|
width: 36px;
|
|
|
|
|
|
height: 36px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-header button:hover {
|
|
|
|
|
|
background: var(--hover-bg);
|
|
|
|
|
|
color: var(--text-primary);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-body {
|
|
|
|
|
|
padding: 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Form Styles */
|
|
|
|
|
|
.vehicle-tabs {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.vehicle-tabs button {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
border: 2px solid var(--border-color);
|
|
|
|
|
|
background: var(--bg-secondary);
|
|
|
|
|
|
color: var(--text-primary);
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.vehicle-tabs button.active {
|
|
|
|
|
|
border-color: var(--accent-color);
|
|
|
|
|
|
background: var(--accent-color);
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(2, 1fr);
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-group {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-group.full-width {
|
|
|
|
|
|
grid-column: 1 / -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-04 15:09:10 -05:00
|
|
|
|
.form-group.full-col {
|
|
|
|
|
|
grid-column: 1 / -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.checkbox-group.horizontal {
|
|
|
|
|
|
flex-direction: row;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
background: var(--bg-secondary);
|
|
|
|
|
|
border: 1px solid var(--border-color);
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
padding: 10px 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.flags-row {
|
|
|
|
|
|
flex-direction: row !important;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 20px;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-21 09:53:31 -05:00
|
|
|
|
.form-group.checkbox-group {
|
|
|
|
|
|
flex-direction: row;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-group.checkbox-group label {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-group label {
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: var(--text-primary);
|
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-group input[type="text"],
|
|
|
|
|
|
.form-group input[type="email"],
|
|
|
|
|
|
.form-group input[type="tel"],
|
|
|
|
|
|
.form-group input[type="password"],
|
|
|
|
|
|
.form-group input[type="number"],
|
|
|
|
|
|
.form-group select {
|
|
|
|
|
|
padding: 10px 12px;
|
|
|
|
|
|
border: 1px solid var(--border-color);
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
background: var(--bg-secondary);
|
|
|
|
|
|
color: var(--text-primary);
|
|
|
|
|
|
font-size: 1rem;
|
|
|
|
|
|
transition: border-color 0.2s;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-group input:focus,
|
|
|
|
|
|
.form-group select:focus {
|
|
|
|
|
|
outline: none;
|
|
|
|
|
|
border-color: var(--accent-color);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-group input[type="file"] {
|
|
|
|
|
|
padding: 8px;
|
|
|
|
|
|
border: 1px dashed var(--border-color);
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
background: var(--bg-secondary);
|
|
|
|
|
|
color: var(--text-primary);
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-group small {
|
|
|
|
|
|
color: var(--text-secondary);
|
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.options-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.option-block {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-width: 200px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section-label {
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
color: var(--text-primary);
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.checkbox-group {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.checkbox-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
color: var(--text-primary);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.file-inputs {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(2, 1fr);
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.error-text {
|
|
|
|
|
|
color: #f44;
|
|
|
|
|
|
margin: 16px 0;
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
background: #fee;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
margin-top: 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn-secondary {
|
|
|
|
|
|
background: var(--bg-secondary);
|
|
|
|
|
|
color: var(--text-primary);
|
|
|
|
|
|
border: 1px solid var(--border-color);
|
|
|
|
|
|
padding: 12px 24px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn-secondary:hover {
|
|
|
|
|
|
background: var(--hover-bg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Driver Details */
|
|
|
|
|
|
.basic-info, .driver-info {
|
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.driver-info h3 {
|
|
|
|
|
|
margin: 0 0 16px;
|
|
|
|
|
|
color: var(--text-primary);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.info-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(2, 1fr);
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.info-grid p {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
color: var(--text-primary);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.photo-viewer {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(2, 1fr);
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.photo-viewer label {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
color: var(--text-primary);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.photo-viewer img {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
border: 1px solid var(--border-color);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
|
.form-grid, .info-grid, .file-inputs, .photo-viewer {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.items-grid {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.header {
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: stretch;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
h1 {
|
|
|
|
|
|
font-size: 1.5rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
refactor: migrate fully to Supabase, remove Firebase/Render/Python backend
- DELETED: entire backend/ (Python/FastAPI — replaced by Supabase)
- DELETED: old/ directory (obsolete code)
- DELETED: render.yaml, inject_api.py, check_tags.py, PENDING_FOR_TOMORROW.md
- DELETED: frontend/src/firebaseConfig.ts (Firebase Auth replaced by Supabase Auth)
- DELETED: frontend/src/services/apiClient.ts (HTTP client for dead backend)
- MIGRATED services to Supabase native:
schedulesService, favoritesService, usersService,
telemetryService (stub), reportsService, analyticsService (stub)
- MIGRATED stores/favorites.ts to Supabase direct queries
- MIGRATED views: SplashScreen, AdminTaxis, AdminDrivers, StrategicAnalytics
- MIGRATED utils/imageUrl.ts to Supabase Storage URLs
- FIXED router/index.ts: guard now uses supabase.auth.getSession()
instead of old localStorage auth_token (fixes logout + map loading)
- FIXED AuthView.vue: removed aggressive watch({ immediate: true })
that caused wrong redirects on map route
- FIXED SplashScreen.vue: navigate() now reads Supabase session + role
- FIXED RLS: added INSERT policy on public.users for trigger
- CONFIRMED: admin@sibu.com assigned ADMIN role in Supabase
2026-02-25 21:49:26 -05:00
|
|
|
|
|
|
|
|
|
|
|