Enhancement: Multi-shift taxis, vehicle type, accessibility flag and filter label update
This commit is contained in:
@ -4,8 +4,9 @@ import type { Taxi } from '@/types'
|
|||||||
|
|
||||||
export interface TaxiFilters {
|
export interface TaxiFilters {
|
||||||
corregimiento?: string
|
corregimiento?: string
|
||||||
shift?: string
|
shifts?: string[] // Soporte para múltiples turnos
|
||||||
english_speaking?: boolean
|
english_speaking?: boolean
|
||||||
|
is_accessible?: boolean // Filtro de accesibilidad
|
||||||
is_active?: boolean
|
is_active?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -15,7 +16,10 @@ export const taxisService = {
|
|||||||
let query = supabase.from('taxis').select('*')
|
let query = supabase.from('taxis').select('*')
|
||||||
|
|
||||||
if (filters?.corregimiento) query = query.eq('corregimiento', filters.corregimiento)
|
if (filters?.corregimiento) query = query.eq('corregimiento', filters.corregimiento)
|
||||||
if (filters?.shift) query = query.eq('shift', filters.shift)
|
if (filters?.shifts && filters.shifts.length > 0) {
|
||||||
|
query = query.overlaps('shifts', filters.shifts) // Busca si hay solapamiento de turnos
|
||||||
|
}
|
||||||
|
if (filters?.is_accessible !== undefined) query = query.eq('is_accessible', filters.is_accessible)
|
||||||
if (filters?.english_speaking !== undefined) query = query.eq('english_speaking', filters.english_speaking)
|
if (filters?.english_speaking !== undefined) query = query.eq('english_speaking', filters.english_speaking)
|
||||||
if (filters?.is_active !== undefined) query = query.eq('is_active', filters.is_active)
|
if (filters?.is_active !== undefined) query = query.eq('is_active', filters.is_active)
|
||||||
|
|
||||||
|
|||||||
@ -118,9 +118,11 @@ export interface Taxi {
|
|||||||
license_plate: string
|
license_plate: string
|
||||||
cooperative?: string
|
cooperative?: string
|
||||||
corregimiento: string
|
corregimiento: string
|
||||||
shift: string
|
vehicle_type?: string
|
||||||
|
shifts: string[] // Cambiado de 'shift: string' a 'shifts: string[]' para múltiples jornadas
|
||||||
rating?: number
|
rating?: number
|
||||||
english_speaking?: boolean
|
english_speaking?: boolean
|
||||||
|
is_accessible?: boolean // Facilidad para personas con discapacidad
|
||||||
image_url?: string
|
image_url?: string
|
||||||
is_active: boolean
|
is_active: boolean
|
||||||
created_at?: string
|
created_at?: string
|
||||||
|
|||||||
@ -42,8 +42,19 @@
|
|||||||
<span>{{ taxi.corregimiento }}</span>
|
<span>{{ taxi.corregimiento }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-row">
|
<div class="info-row">
|
||||||
<span class="label">Horario:</span>
|
<span class="label">Horarios:</span>
|
||||||
<span>{{ getShiftLabel(taxi.shift) }}</span>
|
<span>{{ getShiftsLabel(taxi.shifts) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-row" v-if="taxi.vehicle_type">
|
||||||
|
<span class="label">Vehículo:</span>
|
||||||
|
<span>{{ taxi.vehicle_type }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="label">Accesible:</span>
|
||||||
|
<span class="flex items-center gap-1">
|
||||||
|
{{ taxi.is_accessible ? 'Sí' : 'No' }}
|
||||||
|
<span v-if="taxi.is_accessible" class="material-icons text-blue-500 text-sm">accessible</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-row" v-if="taxi.cooperative">
|
<div class="info-row" v-if="taxi.cooperative">
|
||||||
<span class="label">Cooperativa:</span>
|
<span class="label">Cooperativa:</span>
|
||||||
@ -109,14 +120,27 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group full-width">
|
||||||
<label>Horario *</label>
|
<label>Tipo de Vehículo</label>
|
||||||
<select v-model="taxiForm.shift" required>
|
<input v-model="taxiForm.vehicle_type" type="text" placeholder="Toyota Corolla / SUV / Van">
|
||||||
<option value="">Seleccionar...</option>
|
</div>
|
||||||
<option value="dia">Día</option>
|
|
||||||
<option value="tarde">Tarde</option>
|
<div class="form-group full-width">
|
||||||
<option value="noche">Noche</option>
|
<label>Horarios de Servicio *</label>
|
||||||
</select>
|
<div class="checkbox-list">
|
||||||
|
<label class="checkbox-item">
|
||||||
|
<input type="checkbox" value="dia" v-model="taxiForm.shifts">
|
||||||
|
<span>Día</span>
|
||||||
|
</label>
|
||||||
|
<label class="checkbox-item">
|
||||||
|
<input type="checkbox" value="tarde" v-model="taxiForm.shifts">
|
||||||
|
<span>Tarde</span>
|
||||||
|
</label>
|
||||||
|
<label class="checkbox-item">
|
||||||
|
<input type="checkbox" value="noche" v-model="taxiForm.shifts">
|
||||||
|
<span>Noche</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@ -136,6 +160,13 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group checkbox-group">
|
||||||
|
<label>
|
||||||
|
<input v-model="taxiForm.is_accessible" type="checkbox">
|
||||||
|
<span class="flex items-center gap-1">Accesible <span class="material-icons text-sm">accessible</span></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group checkbox-group">
|
<div class="form-group checkbox-group">
|
||||||
<label>
|
<label>
|
||||||
<input v-model="taxiForm.is_active" type="checkbox">
|
<input v-model="taxiForm.is_active" type="checkbox">
|
||||||
@ -184,10 +215,12 @@ const taxiForm = reactive({
|
|||||||
phone_number: '',
|
phone_number: '',
|
||||||
license_plate: '',
|
license_plate: '',
|
||||||
corregimiento: '',
|
corregimiento: '',
|
||||||
shift: '',
|
vehicle_type: '',
|
||||||
|
shifts: [] as string[],
|
||||||
cooperative: '',
|
cooperative: '',
|
||||||
rating: 5.0,
|
rating: 5.0,
|
||||||
english_speaking: false,
|
english_speaking: false,
|
||||||
|
is_accessible: false,
|
||||||
is_active: true
|
is_active: true
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -217,10 +250,12 @@ function openModal(taxi?: any) {
|
|||||||
phone_number: taxi.phone_number,
|
phone_number: taxi.phone_number,
|
||||||
license_plate: taxi.license_plate,
|
license_plate: taxi.license_plate,
|
||||||
corregimiento: taxi.corregimiento,
|
corregimiento: taxi.corregimiento,
|
||||||
shift: taxi.shift,
|
vehicle_type: taxi.vehicle_type || '',
|
||||||
|
shifts: taxi.shifts || [],
|
||||||
cooperative: taxi.cooperative || '',
|
cooperative: taxi.cooperative || '',
|
||||||
rating: taxi.rating || 5.0,
|
rating: taxi.rating || 5.0,
|
||||||
english_speaking: taxi.english_speaking || false,
|
english_speaking: taxi.english_speaking || false,
|
||||||
|
is_accessible: taxi.is_accessible || false,
|
||||||
is_active: taxi.is_active
|
is_active: taxi.is_active
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -230,10 +265,12 @@ function openModal(taxi?: any) {
|
|||||||
phone_number: '',
|
phone_number: '',
|
||||||
license_plate: '',
|
license_plate: '',
|
||||||
corregimiento: '',
|
corregimiento: '',
|
||||||
shift: '',
|
vehicle_type: '',
|
||||||
|
shifts: [],
|
||||||
cooperative: '',
|
cooperative: '',
|
||||||
rating: 5.0,
|
rating: 5.0,
|
||||||
english_speaking: false,
|
english_speaking: false,
|
||||||
|
is_accessible: false,
|
||||||
is_active: true
|
is_active: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -305,13 +342,14 @@ async function deleteTaxi(taxi: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getShiftLabel(shift: string) {
|
function getShiftsLabel(shifts: string[]) {
|
||||||
|
if (!shifts || shifts.length === 0) return 'No definido'
|
||||||
const labels: Record<string, string> = {
|
const labels: Record<string, string> = {
|
||||||
'dia': 'Día',
|
'dia': 'Día',
|
||||||
'tarde': 'Tarde',
|
'tarde': 'Tarde',
|
||||||
'noche': 'Noche'
|
'noche': 'Noche'
|
||||||
}
|
}
|
||||||
return labels[shift] || shift
|
return shifts.map(s => labels[s] || s).join(', ')
|
||||||
}
|
}
|
||||||
|
|
||||||
function getImageUrl(path: string) {
|
function getImageUrl(path: string) {
|
||||||
@ -674,6 +712,24 @@ h1 {
|
|||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.checkbox-list {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
.error-text {
|
.error-text {
|
||||||
color: #f44;
|
color: #f44;
|
||||||
margin: 16px 0;
|
margin: 16px 0;
|
||||||
|
|||||||
@ -27,12 +27,23 @@ onMounted(async () => {
|
|||||||
const filteredTaxis = computed(() => {
|
const filteredTaxis = computed(() => {
|
||||||
return taxiStore.taxis.filter(taxi => {
|
return taxiStore.taxis.filter(taxi => {
|
||||||
const matchesZone = selectedZone.value === 'all' || taxi.corregimiento === selectedZone.value
|
const matchesZone = selectedZone.value === 'all' || taxi.corregimiento === selectedZone.value
|
||||||
const matchesShift = selectedShift.value === 'all' || taxi.shift === selectedShift.value
|
// Ahora comprueba si el turno seleccionado está en el array de turnos del taxi
|
||||||
|
const matchesShift = selectedShift.value === 'all' || (taxi.shifts && taxi.shifts.includes(selectedShift.value))
|
||||||
const matchesEnglish = !onlyEnglish.value || taxi.english_speaking
|
const matchesEnglish = !onlyEnglish.value || taxi.english_speaking
|
||||||
return matchesZone && matchesShift && matchesEnglish
|
return matchesZone && matchesShift && matchesEnglish
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isOnline = (taxi: Taxi) => {
|
||||||
|
if (!taxi.shifts) return false
|
||||||
|
return taxi.shifts.includes('dia') || taxi.shifts.includes('tarde')
|
||||||
|
}
|
||||||
|
|
||||||
|
const getShiftsDisplay = (taxi: Taxi) => {
|
||||||
|
if (!taxi.shifts || taxi.shifts.length === 0) return ''
|
||||||
|
return taxi.shifts.map(s => getShiftLabel(s)).join(' · ')
|
||||||
|
}
|
||||||
|
|
||||||
const handleCall = (taxi: Taxi) => {
|
const handleCall = (taxi: Taxi) => {
|
||||||
analyticsService.logEvent({
|
analyticsService.logEvent({
|
||||||
event_name: 'taxi_click',
|
event_name: 'taxi_click',
|
||||||
@ -119,17 +130,20 @@ function getShiftLabel(shift: string) {
|
|||||||
@error="(e) => (e.target as HTMLImageElement).src = getImageUrl(null, 'taxi')"
|
@error="(e) => (e.target as HTMLImageElement).src = getImageUrl(null, 'taxi')"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="driver-status" :class="{ 'status-online': taxi.shift === 'dia' || taxi.shift === 'tarde' }"></div>
|
<div class="driver-status" :class="{ 'status-online': isOnline(taxi) }"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="driver-info">
|
<div class="driver-info">
|
||||||
|
<div class="flex items-center gap-2 mb-0.5">
|
||||||
<h3 class="driver-name">{{ taxi.owner_name }}</h3>
|
<h3 class="driver-name">{{ taxi.owner_name }}</h3>
|
||||||
|
<span v-if="taxi.is_accessible" class="material-icons text-blue-500 text-sm" title="Accesible para personas con discapacidad">accessible</span>
|
||||||
|
</div>
|
||||||
<div class="driver-meta">
|
<div class="driver-meta">
|
||||||
<div class="rating-stars">
|
<div class="rating-stars">
|
||||||
<span class="material-icons star-filled">star</span>
|
<span class="material-icons star-filled">star</span>
|
||||||
<span class="rating-value">{{ (taxi.rating || 5).toFixed(1) }}</span>
|
<span class="rating-value">{{ (taxi.rating || 5).toFixed(1) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="meta-dot">·</span>
|
<span class="meta-dot">·</span>
|
||||||
<span class="shift-badge">{{ getShiftLabel(taxi.shift || '') }}</span>
|
<span class="shift-badge">{{ getShiftsDisplay(taxi) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="fav-icon-wrapper">
|
<div class="fav-icon-wrapper">
|
||||||
@ -147,6 +161,10 @@ function getShiftLabel(shift: string) {
|
|||||||
<span class="material-icons detail-icon">location_on</span>
|
<span class="material-icons detail-icon">location_on</span>
|
||||||
<span class="detail-text">{{ taxi.corregimiento }}</span>
|
<span class="detail-text">{{ taxi.corregimiento }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="detail-item" v-if="taxi.vehicle_type">
|
||||||
|
<span class="material-icons detail-icon">local_taxi</span>
|
||||||
|
<span class="detail-text">{{ taxi.vehicle_type }}</span>
|
||||||
|
</div>
|
||||||
<div class="detail-item" v-if="taxi.english_speaking">
|
<div class="detail-item" v-if="taxi.english_speaking">
|
||||||
<span class="material-icons detail-icon">g_translate</span>
|
<span class="material-icons detail-icon">g_translate</span>
|
||||||
<span class="detail-text">{{ t('taxi.englishLabel') }}</span>
|
<span class="detail-text">{{ t('taxi.englishLabel') }}</span>
|
||||||
|
|||||||
@ -50,7 +50,7 @@ onMounted(async () => {
|
|||||||
<div class="group-content">
|
<div class="group-content">
|
||||||
<label>{{ t('shuttle.category') || 'Categoría' }}</label>
|
<label>{{ t('shuttle.category') || 'Categoría' }}</label>
|
||||||
<select v-model="shuttleCategoryFilter">
|
<select v-model="shuttleCategoryFilter">
|
||||||
<option value="all">{{ t('shuttle.allRoutes') || 'Todas las rutas' }}</option>
|
<option value="all">{{ t('shuttle.allAreas') || 'Todas las áreas' }}</option>
|
||||||
<option value="local">Local</option>
|
<option value="local">Local</option>
|
||||||
<option value="interprovincial">Interprovincial</option>
|
<option value="interprovincial">Interprovincial</option>
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
Reference in New Issue
Block a user