refactor: simplify admin drivers panel and remove taxis tab
This commit is contained in:
@ -2,67 +2,19 @@
|
||||
<div class="admin-drivers">
|
||||
<div class="header">
|
||||
<button class="back-link" @click="router.push('/admin')">← Volver al Panel</button>
|
||||
<h1>Gestión de Conductores y Taxis</h1>
|
||||
<h1>Gestión de Taxis</h1>
|
||||
<div class="header-actions">
|
||||
<button class="btn-primary" @click="openRegisterModal">
|
||||
<span class="material-icons">add</span>
|
||||
{{ activeTab === 'drivers' ? 'Nuevo Conductor' : 'Nuevo Taxi' }}
|
||||
Nuevo Taxi
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div class="tabs">
|
||||
<button
|
||||
:class="{ active: activeTab === 'drivers' }"
|
||||
@click="activeTab = 'drivers'"
|
||||
>
|
||||
<span class="material-icons">badge</span>
|
||||
Conductores de la App
|
||||
</button>
|
||||
<button
|
||||
:class="{ active: activeTab === 'taxis' }"
|
||||
@click="activeTab = 'taxis'"
|
||||
>
|
||||
<span class="material-icons">local_taxi</span>
|
||||
Directorio de Taxis
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="isLoading" class="loading">Cargando...</div>
|
||||
|
||||
<!-- Drivers Tab -->
|
||||
<div v-else-if="activeTab === 'drivers'" class="content">
|
||||
<div v-if="activeDrivers.length > 0" class="items-grid">
|
||||
<div v-for="driver in activeDrivers" :key="driver.id" class="item-card" @click="viewDriverDetails(driver.id)">
|
||||
<div class="card-header">
|
||||
<div class="avatar">
|
||||
<img v-if="driver.driver_profiles?.photo_url" :src="getImageUrl(driver.driver_profiles.photo_url)" alt="Avatar">
|
||||
<span v-else class="material-icons">account_circle</span>
|
||||
</div>
|
||||
<div class="info">
|
||||
<h3>{{ driver.full_name }}</h3>
|
||||
<p class="email">{{ driver.email }}</p>
|
||||
<div class="badges">
|
||||
<span class="badge" :class="driver.driver_profile?.vehicle_type">
|
||||
{{ driver.driver_profile?.vehicle_type === 'taxi' ? 'Taxi' : 'Autobús' }}
|
||||
</span>
|
||||
<span class="badge plate">{{ driver.driver_profile?.license_plate }}</span>
|
||||
<span v-if="driver.driver_profile?.speaks_english" class="badge english">🌐 Inglés</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="material-icons">chevron_right</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="empty-state">
|
||||
<span class="material-icons">badge</span>
|
||||
<p>No hay conductores registrados en la app</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Taxis Tab -->
|
||||
<div v-else-if="activeTab === 'taxis'" class="content">
|
||||
<div v-else class="content">
|
||||
<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">
|
||||
@ -112,49 +64,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Driver Details Modal -->
|
||||
<div v-if="selectedUser" class="modal-overlay" @click.self="selectedUser = null">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<h2>Detalles del Conductor</h2>
|
||||
<button @click="selectedUser = null">×</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<section class="basic-info">
|
||||
<p><strong>ID:</strong> {{ selectedUser.id }}</p>
|
||||
<p><strong>Nombre:</strong> {{ selectedUser.full_name }}</p>
|
||||
<p><strong>Correo:</strong> {{ selectedUser.email }}</p>
|
||||
<p><strong>Registrado el:</strong> {{ formatDate(selectedUser.created_at) }}</p>
|
||||
</section>
|
||||
|
||||
<section v-if="selectedUser.driver_profile" class="driver-info">
|
||||
<h3>Perfil del Conductor</h3>
|
||||
<div class="info-grid">
|
||||
<p><strong>Cédula:</strong> {{ selectedUser.driver_profile.cedula }}</p>
|
||||
<p><strong>Placa:</strong> {{ selectedUser.driver_profile.license_plate }}</p>
|
||||
<p><strong>Tipo:</strong> {{ selectedUser.driver_profile.vehicle_type === 'taxi' ? 'Taxi' : 'Autobús' }}</p>
|
||||
<p v-if="selectedUser.driver_profile.cooperative_name"><strong>Cooperativa:</strong> {{ selectedUser.driver_profile.cooperative_name }}</p>
|
||||
<p v-if="selectedUser.driver_profile.shift"><strong>Horario:</strong> {{ selectedUser.driver_profile.shift }}</p>
|
||||
<p v-if="selectedUser.driver_profile.payment_methods"><strong>Pagos:</strong> {{ selectedUser.driver_profile.payment_methods }}</p>
|
||||
<p><strong>Inglés:</strong> {{ selectedUser.driver_profile.speaks_english ? 'Sí' : 'No' }}</p>
|
||||
</div>
|
||||
|
||||
<div class="photo-viewer">
|
||||
<div v-if="selectedUser.driver_profile.photo_url">
|
||||
<label>Foto de Perfil:</label>
|
||||
<img :src="getImageUrl(selectedUser.driver_profile.photo_url)" alt="Perfil">
|
||||
</div>
|
||||
<div v-if="selectedUser.driver_profile.vehicle_photo_url">
|
||||
<label>Foto de Vehículo:</label>
|
||||
<img :src="getImageUrl(selectedUser.driver_profile.vehicle_photo_url)" alt="Vehículo">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Register/Edit Modal -->
|
||||
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
|
||||
<div class="modal large">
|
||||
@ -164,99 +73,8 @@
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<!-- Driver Registration Form -->
|
||||
<form v-if="modalMode === 'driver'" @submit.prevent="handleRegisterDriver" class="register-form">
|
||||
<div class="vehicle-tabs">
|
||||
<button type="button" :class="{ active: driverForm.vehicle_type === 'taxi' }" @click="driverForm.vehicle_type = 'taxi'">Taxi</button>
|
||||
<button type="button" :class="{ active: driverForm.vehicle_type === 'bus' }" @click="driverForm.vehicle_type = 'bus'">Autobús</button>
|
||||
</div>
|
||||
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label>Nombre Completo</label>
|
||||
<input v-model="driverForm.full_name" type="text" placeholder="Ej: Juan Pérez" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Correo Electrónico</label>
|
||||
<input v-model="driverForm.email" type="email" placeholder="juan@correo.com" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Teléfono</label>
|
||||
<input v-model="driverForm.phone_number" type="tel" placeholder="+507 1234-5678" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Contraseña</label>
|
||||
<input v-model="driverForm.password" type="password" placeholder="********" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Cédula</label>
|
||||
<input v-model="driverForm.cedula" type="text" placeholder="1-234-567" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Matrícula (Placa)</label>
|
||||
<input v-model="driverForm.license_plate" type="text" placeholder="ABC-123" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Nombre de Cooperativa</label>
|
||||
<input v-model="driverForm.cooperative_name" type="text" placeholder="Coope Rápido">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="options-row">
|
||||
<div v-if="driverForm.vehicle_type === 'taxi'" class="option-block">
|
||||
<label class="section-label">Horario</label>
|
||||
<div class="checkbox-group">
|
||||
<label v-for="s in ['Dia', 'Tarde', 'Noche', 'Aeropuerto']" :key="s" class="checkbox-item">
|
||||
<input type="checkbox" v-model="driverForm.selectedShifts" :value="s">
|
||||
<span>{{ s }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="option-block">
|
||||
<label class="section-label">Método de Pago</label>
|
||||
<div class="checkbox-group">
|
||||
<label v-for="p in ['Efectivo', 'Yappi', 'Tarjeta']" :key="p" class="checkbox-item">
|
||||
<input type="checkbox" v-model="driverForm.selectedPayments" :value="p">
|
||||
<span>{{ p }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="option-block">
|
||||
<label class="section-label">Idiomas</label>
|
||||
<div class="checkbox-group">
|
||||
<label class="checkbox-item">
|
||||
<input type="checkbox" v-model="driverForm.speaksEnglish">
|
||||
<span>Habla Inglés</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="file-inputs">
|
||||
<div class="form-group">
|
||||
<label>Foto de Perfil</label>
|
||||
<input type="file" @change="e => handleFileChange(e, 'profile')" accept="image/*">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Foto del Vehículo</label>
|
||||
<input type="file" @change="e => handleFileChange(e, 'vehicle')" accept="image/*" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="registerError" class="error-text">{{ registerError }}</p>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="button" class="btn-secondary" @click="closeModal">Cancelar</button>
|
||||
<button type="submit" class="btn-primary" :disabled="isRegistering">
|
||||
{{ isRegistering ? 'Registrando...' : 'Registrar Conductor' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Taxi Form -->
|
||||
<form v-else-if="modalMode === 'taxi'" @submit.prevent="saveTaxi">
|
||||
<form @submit.prevent="saveTaxi">
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label>Nombre del Conductor *</label>
|
||||
@ -350,42 +168,18 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, reactive, computed } from 'vue'
|
||||
import { usersService } from '@/services/usersService'
|
||||
import { authService } from '@/services/authService'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { supabase } from '@/supabase'
|
||||
|
||||
const router = useRouter()
|
||||
const activeTab = ref<'drivers' | 'taxis'>('drivers')
|
||||
const isLoading = ref(false)
|
||||
const activeDrivers = ref<any[]>([])
|
||||
const taxis = ref<any[]>([])
|
||||
const selectedUser = ref<any>(null)
|
||||
|
||||
// Modal state
|
||||
const showModal = ref(false)
|
||||
const modalMode = ref<'driver' | 'taxi'>('driver')
|
||||
const modalMode = ref<'driver' | 'taxi'>('taxi')
|
||||
const editingTaxi = ref<any>(null)
|
||||
|
||||
// Driver registration
|
||||
const isRegistering = ref(false)
|
||||
const registerError = ref('')
|
||||
const driverForm = reactive({
|
||||
full_name: '',
|
||||
email: '',
|
||||
phone_number: '',
|
||||
password: '',
|
||||
cedula: '',
|
||||
license_plate: '',
|
||||
vehicle_type: 'taxi' as 'taxi' | 'bus',
|
||||
cooperative_name: '',
|
||||
selectedShifts: [] as string[],
|
||||
selectedPayments: [] as string[],
|
||||
speaksEnglish: false
|
||||
})
|
||||
const profilePhoto = ref<File | null>(null)
|
||||
const vehiclePhoto = ref<File | null>(null)
|
||||
|
||||
// Taxi form
|
||||
const isSaving = ref(false)
|
||||
const taxiError = ref('')
|
||||
@ -405,7 +199,6 @@ const taxiForm = reactive({
|
||||
})
|
||||
|
||||
const modalTitle = computed(() => {
|
||||
if (modalMode.value === 'driver') return 'Registrar Nuevo Conductor'
|
||||
return editingTaxi.value ? 'Editar Taxi' : 'Nuevo Taxi'
|
||||
})
|
||||
|
||||
@ -416,11 +209,6 @@ onMounted(() => {
|
||||
async function loadData() {
|
||||
isLoading.value = true
|
||||
try {
|
||||
// Load drivers from Supabase
|
||||
const { data: drivers, error: errorDrivers } = await supabase.from('users').select('*, driver_profiles(*)').eq('role', 'DRIVER')
|
||||
if (errorDrivers) throw errorDrivers
|
||||
activeDrivers.value = drivers || []
|
||||
|
||||
// Load taxis from Supabase
|
||||
const { data: taxisData, error: errorTaxis } = await supabase.from('taxis').select('*').order('owner_name')
|
||||
if (errorTaxis) throw errorTaxis
|
||||
@ -433,7 +221,7 @@ async function loadData() {
|
||||
}
|
||||
|
||||
function openRegisterModal() {
|
||||
modalMode.value = activeTab.value === 'drivers' ? 'driver' : 'taxi'
|
||||
modalMode.value = 'taxi'
|
||||
showModal.value = true
|
||||
}
|
||||
|
||||
@ -441,67 +229,9 @@ function closeModal() {
|
||||
showModal.value = false
|
||||
editingTaxi.value = null
|
||||
photoFile.value = null
|
||||
registerError.value = ''
|
||||
taxiError.value = ''
|
||||
}
|
||||
|
||||
async function viewDriverDetails(userId: string) {
|
||||
try {
|
||||
selectedUser.value = await usersService.getUserDetails(userId)
|
||||
} catch (e) {
|
||||
alert('Error al cargar detalles')
|
||||
}
|
||||
}
|
||||
|
||||
const handleFileChange = (event: Event, type: 'profile' | 'vehicle') => {
|
||||
const target = event.target as HTMLInputElement
|
||||
if (target.files && target.files[0]) {
|
||||
if (type === 'profile') profilePhoto.value = target.files[0]
|
||||
else vehiclePhoto.value = target.files[0]
|
||||
}
|
||||
}
|
||||
|
||||
const handleRegisterDriver = async () => {
|
||||
isRegistering.value = true
|
||||
registerError.value = ''
|
||||
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append('full_name', driverForm.full_name)
|
||||
formData.append('email', driverForm.email)
|
||||
formData.append('phone_number', driverForm.phone_number)
|
||||
formData.append('password', driverForm.password)
|
||||
formData.append('cedula', driverForm.cedula)
|
||||
formData.append('vehicle_type', driverForm.vehicle_type)
|
||||
formData.append('license_plate', driverForm.license_plate)
|
||||
formData.append('speaks_english', String(driverForm.speaksEnglish))
|
||||
|
||||
if (driverForm.cooperative_name) formData.append('cooperative_name', driverForm.cooperative_name)
|
||||
if (profilePhoto.value) formData.append('profile_photo', profilePhoto.value)
|
||||
if (vehiclePhoto.value) formData.append('vehicle_photo', vehiclePhoto.value)
|
||||
|
||||
if (driverForm.selectedShifts.length > 0) formData.append('shift', driverForm.selectedShifts.join(','))
|
||||
if (driverForm.selectedPayments.length > 0) formData.append('payment_methods', driverForm.selectedPayments.join(','))
|
||||
|
||||
await authService.registerDriver(formData)
|
||||
|
||||
closeModal()
|
||||
Object.assign(driverForm, {
|
||||
full_name: '', email: '', phone_number: '', password: '', cedula: '',
|
||||
license_plate: '', vehicle_type: 'taxi', cooperative_name: '',
|
||||
selectedShifts: [], selectedPayments: [], speaksEnglish: false
|
||||
})
|
||||
profilePhoto.value = null
|
||||
vehiclePhoto.value = null
|
||||
|
||||
await loadData()
|
||||
} catch (e: any) {
|
||||
registerError.value = e.response?.data?.detail || 'Error al registrar conductor'
|
||||
} finally {
|
||||
isRegistering.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function editTaxi(taxi: any) {
|
||||
editingTaxi.value = taxi
|
||||
modalMode.value = 'taxi'
|
||||
@ -600,9 +330,7 @@ function getImageUrl(path: string) {
|
||||
return path
|
||||
}
|
||||
|
||||
function formatDate(dateStr: string) {
|
||||
return new Date(dateStr).toLocaleDateString('es-PA')
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -76,13 +76,7 @@
|
||||
<p>Viajes turísticos.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-card" @click="router.push('/admin/taxis')">
|
||||
<div class="card-icon"><span class="material-icons">local_taxi</span></div>
|
||||
<div class="card-content">
|
||||
<h3>Taxis</h3>
|
||||
<p>Directorio de apoyo.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-card" @click="router.push('/admin/drivers')">
|
||||
<div class="card-icon"><span class="material-icons">badge</span></div>
|
||||
<div class="card-content">
|
||||
|
||||
Reference in New Issue
Block a user