refactor(admin): unify AdminTaxis into AdminDrivers - multi-shifts, vehicle_type, is_accessible; remove redundant AdminTaxis.vue + DB migration

This commit is contained in:
2026-03-04 15:09:10 -05:00
parent 927d6549bf
commit c178523c7e
3 changed files with 103 additions and 812 deletions

View File

@ -75,11 +75,19 @@
<p class="phone">📞 {{ taxi.phone_number }}</p>
<div class="badges">
<span class="badge plate">{{ taxi.license_plate }}</span>
<span class="badge">{{ taxi.corregimiento }}</span>
<span class="badge">{{ getShiftLabel(taxi.shift) }}</span>
<span class="badge" v-if="taxi.corregimiento">{{ taxi.corregimiento }}</span>
<span class="badge" v-if="taxi.vehicle_type">{{ taxi.vehicle_type }}</span>
<span v-if="taxi.english_speaking" class="badge english">🌐 Inglés</span>
<span v-if="taxi.is_accessible" class="badge accessible"> Accesible</span>
</div>
<div class="taxi-meta">
<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>
<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'">
@ -265,6 +273,11 @@
<input v-model="taxiForm.license_plate" type="text" placeholder="CHI-1234" required>
</div>
<div class="form-group">
<label>Tipo de Vehículo</label>
<input v-model="taxiForm.vehicle_type" type="text" placeholder="Toyota Corolla / SUV / Van">
</div>
<div class="form-group">
<label>Zona de Servicio *</label>
<select v-model="taxiForm.corregimiento" required>
@ -276,41 +289,44 @@
</select>
</div>
<div class="form-group">
<label>Horario *</label>
<select v-model="taxiForm.shift" required>
<option value="">Seleccionar...</option>
<option value="dia">Día</option>
<option value="tarde">Tarde</option>
<option value="noche">Noche</option>
</select>
</div>
<div class="form-group">
<label>Cooperativa</label>
<input v-model="taxiForm.cooperative" type="text" placeholder="Cooperativa Boquete">
</div>
<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>
<div class="form-group checkbox-group">
<label>
<input v-model="taxiForm.english_speaking" type="checkbox">
<span>Habla Inglés</span>
</label>
<!-- 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>
<div class="form-group checkbox-group">
<label>
<!-- Flags -->
<div class="form-group full-col flags-row">
<label class="checkbox-item">
<input v-model="taxiForm.english_speaking" type="checkbox">
<span>🌐 Habla Inglés</span>
</label>
<label class="checkbox-item">
<input v-model="taxiForm.is_accessible" type="checkbox">
<span>&#9855; Accesible para discapacitados</span>
</label>
<label class="checkbox-item">
<input v-model="taxiForm.is_active" type="checkbox">
<span>Activo en el directorio</span>
</label>
</div>
<div class="form-group full-width">
<div class="form-group full-col">
<label>Foto del Conductor</label>
<input type="file" @change="handleTaxiFileChange" accept="image/*">
<small>Opcional - Foto para el directorio público</small>
@ -378,11 +394,13 @@ const taxiForm = reactive({
owner_name: '',
phone_number: '',
license_plate: '',
vehicle_type: '',
corregimiento: '',
shift: '',
shifts: [] as string[],
cooperative: '',
rating: 5.0,
english_speaking: false,
is_accessible: false,
is_active: true
})
@ -487,15 +505,21 @@ const handleRegisterDriver = async () => {
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] : [])
Object.assign(taxiForm, {
owner_name: taxi.owner_name,
phone_number: taxi.phone_number,
license_plate: taxi.license_plate,
vehicle_type: taxi.vehicle_type || '',
corregimiento: taxi.corregimiento,
shift: taxi.shift,
shifts: shiftsArr,
cooperative: taxi.cooperative || '',
rating: taxi.rating || 5.0,
english_speaking: taxi.english_speaking || false,
is_accessible: taxi.is_accessible || false,
is_active: taxi.is_active
})
photoFile.value = null
@ -524,7 +548,12 @@ async function saveTaxi() {
const { data: urlData } = supabase.storage.from('uploads').getPublicUrl(filename)
image_url = urlData.publicUrl
}
const payload = { ...taxiForm, image_url }
// Guardamos shifts (array) y tambien shift (primer valor) para compatibilidad legacy
const payload = {
...taxiForm,
shift: taxiForm.shifts[0] || null, // backward compat
image_url
}
if (editingTaxi.value) {
const { error: e } = await supabase.from('taxis').update(payload).eq('id', editingTaxi.value.id)
if (e) throw e
@ -534,8 +563,9 @@ async function saveTaxi() {
}
closeModal()
Object.assign(taxiForm, {
owner_name: '', phone_number: '', license_plate: '', corregimiento: '',
shift: '', cooperative: '', rating: 5.0, english_speaking: false, is_active: true
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) {
@ -559,9 +589,7 @@ async function deleteTaxi(taxi: any) {
function getShiftLabel(shift: string) {
const labels: Record<string, string> = {
'dia': 'Día',
'tarde': 'Tarde',
'noche': 'Noche'
'dia': 'Día', 'tarde': 'Tarde', 'noche': 'Noche', 'aeropuerto': 'Aeropuerto'
}
return labels[shift] || shift
}
@ -803,6 +831,29 @@ h1 {
border-color: #4a90e2;
}
.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;
}
.taxi-meta {
display: flex;
gap: 12px;
@ -972,6 +1023,27 @@ h1 {
grid-column: 1 / -1;
}
.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;
}
.form-group.checkbox-group {
flex-direction: row;
align-items: center;