Initial commit: SIBU 2.0 MISSION

This commit is contained in:
2026-02-21 09:53:31 -05:00
commit 0c7aa53c8b
400 changed files with 67708 additions and 0 deletions

View File

@ -0,0 +1,253 @@
<template>
<div class="admin-bus-stops">
<div class="header">
<button class="back-link" @click="$router.push('/admin')">&larr; 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>