Initial commit: SIBU 2.0 MISSION
This commit is contained in:
253
frontend/src/views/AdminBusStops.vue
Normal file
253
frontend/src/views/AdminBusStops.vue
Normal file
@ -0,0 +1,253 @@
|
||||
<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 { busStopsService } from '@/services/busStopsService'
|
||||
import type { BusStop } from '@/types'
|
||||
import BusStopEditor from '@/components/BusStopEditor.vue'
|
||||
|
||||
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 {
|
||||
stops.value = await busStopsService.getAllBusStops()
|
||||
} 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'
|
||||
}
|
||||
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>
|
||||
Reference in New Issue
Block a user