261 lines
5.9 KiB
Vue
261 lines
5.9 KiB
Vue
<template>
|
|
<div class="admin-bus-stops">
|
|
<div class="header">
|
|
<button class="back-link" @click="router.push('/admin')">← Volver al Panel</button>
|
|
<h1>Gestionar Paradas</h1>
|
|
<button class="add-button" @click="openCreate">
|
|
<span class="material-icons">add</span> Nueva Parada
|
|
</button>
|
|
</div>
|
|
|
|
<div v-if="isLoading">Cargando paradas...</div>
|
|
<div v-else-if="error" class="error">{{ error }}</div>
|
|
|
|
<div v-else class="stops-list">
|
|
<div v-for="stop in stops" :key="stop.id" class="stop-card">
|
|
<div class="stop-info">
|
|
<h3>{{ stop.name }}</h3>
|
|
<p>{{ stop.city }} - {{ translateType(stop.stop_type) }}</p>
|
|
<div class="badges">
|
|
<span v-if="stop.has_shelter" class="badge">Con Techo</span>
|
|
<span v-if="stop.has_seating" class="badge">Asientos</span>
|
|
<span v-if="stop.is_accessible" class="badge">Accesible</span>
|
|
</div>
|
|
</div>
|
|
<div class="stop-actions">
|
|
<button class="icon-btn edit" @click="openEdit(stop)">
|
|
<span class="material-icons">edit</span>
|
|
</button>
|
|
<button class="icon-btn delete" @click="confirmDelete(stop)">
|
|
<span class="material-icons">delete</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Editor Modal -->
|
|
<div v-if="showEditor" class="modal-overlay">
|
|
<div class="modal-content">
|
|
<BusStopEditor
|
|
:initial-stop="selectedStop"
|
|
@save="handleSave"
|
|
@cancel="closeEditor"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { busStopsService } from '@/services/busStopsService'
|
|
import type { BusStop } from '@/types'
|
|
import { defineAsyncComponent } from 'vue'
|
|
const BusStopEditor = defineAsyncComponent(() => import('@/components/BusStopEditor.vue'))
|
|
|
|
const router = useRouter()
|
|
const stops = ref<BusStop[]>([])
|
|
const isLoading = ref(true)
|
|
const error = ref<string | null>(null)
|
|
const showEditor = ref(false)
|
|
const selectedStop = ref<BusStop | null>(null)
|
|
|
|
onMounted(loadStops)
|
|
|
|
async function loadStops() {
|
|
isLoading.value = true
|
|
try {
|
|
const data = await busStopsService.getAllBusStops()
|
|
stops.value = data || []
|
|
} catch (e) {
|
|
error.value = 'Error al cargar las paradas'
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
function translateType(type: string) {
|
|
const types: Record<string, string> = {
|
|
'REGULAR': 'Regular',
|
|
'TERMINAL': 'Terminal',
|
|
'EXPRESS_ONLY': 'Solo Expreso',
|
|
'regular': 'Regular',
|
|
'terminal': 'Terminal',
|
|
'express_only': 'Solo Expreso'
|
|
}
|
|
return types[type] || type
|
|
}
|
|
|
|
function openCreate() {
|
|
selectedStop.value = null
|
|
showEditor.value = true
|
|
}
|
|
|
|
function openEdit(stop: BusStop) {
|
|
selectedStop.value = stop
|
|
showEditor.value = true
|
|
}
|
|
|
|
function closeEditor() {
|
|
showEditor.value = false
|
|
selectedStop.value = null
|
|
}
|
|
|
|
async function handleSave(data: any) {
|
|
try {
|
|
if (data.id) {
|
|
// Update
|
|
const { id, ...updateData } = data
|
|
await busStopsService.updateBusStop(id, updateData)
|
|
} else {
|
|
// Create
|
|
await busStopsService.createBusStop(data)
|
|
}
|
|
await loadStops()
|
|
closeEditor()
|
|
} catch (e) {
|
|
alert('Error al guardar la parada')
|
|
}
|
|
}
|
|
|
|
async function confirmDelete(stop: BusStop) {
|
|
if (confirm(`¿Estás seguro de que quieres eliminar la parada ${stop.name}?`)) {
|
|
try {
|
|
await busStopsService.deleteBusStop(stop.id)
|
|
await loadStops()
|
|
} catch (e) {
|
|
alert('Error al eliminar la parada')
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.admin-bus-stops {
|
|
padding: 20px;
|
|
max-width: 1000px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.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);
|
|
}
|
|
|
|
.add-button {
|
|
background: #007bff;
|
|
color: white;
|
|
border: none;
|
|
padding: 10px 16px;
|
|
border-radius: 4px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.stops-list {
|
|
display: grid;
|
|
gap: 12px;
|
|
}
|
|
|
|
.stop-card {
|
|
background: white;
|
|
padding: 16px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.stop-info h3 {
|
|
margin: 0 0 4px 0;
|
|
}
|
|
|
|
.stop-info p {
|
|
margin: 0 0 8px 0;
|
|
color: #666;
|
|
}
|
|
|
|
.badges {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.badge {
|
|
background: #e9ecef;
|
|
padding: 2px 8px;
|
|
border-radius: 12px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.stop-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.icon-btn {
|
|
background: none;
|
|
border: none;
|
|
cursor: pointer;
|
|
padding: 8px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.icon-btn:hover {
|
|
background: #f1f1f1;
|
|
}
|
|
|
|
.edit { color: #f39c12; }
|
|
.delete { color: #e74c3c; }
|
|
|
|
.modal-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0,0,0,0.5);
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.modal-content {
|
|
background: white;
|
|
padding: 24px;
|
|
border-radius: 12px;
|
|
width: 90%;
|
|
max-width: 800px;
|
|
max-height: 90vh;
|
|
overflow-y: auto;
|
|
}
|
|
</style>
|