actualizado 3-sept
This commit is contained in:
+12
-69
@@ -12,10 +12,8 @@
|
||||
</script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Space+Grotesk:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" type="text/css" href="static/css/animate.min.css"/>
|
||||
<!-- <link rel="stylesheet" href="{{ url_for('static', filename='css/animate.css') }}"> -->
|
||||
<!-- <link rel="stylesheet" type="text/css" href="DataTables/DataTables-1.11.4/css/dataTables.bootstrap5.css"/> -->
|
||||
<!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"> -->
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/animate.min.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='bootstrap-icons-1.13.1/bootstrap-icons.css') }}">
|
||||
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
@@ -647,8 +645,7 @@
|
||||
</div>
|
||||
|
||||
<div class="nav-logo">
|
||||
<span class="dot"></span>PyFleet
|
||||
<!-- <span class="theme-switch-label">Fleet Operations Platform</span> -->
|
||||
<span></span><h3>PyFleet</h3>
|
||||
</div>
|
||||
|
||||
<div class="theme-switch-row">
|
||||
@@ -660,103 +657,49 @@
|
||||
|
||||
<div class="nav-links">
|
||||
<a href="{{ url_for('index') }}" class="{% if page == 'inicio' %}active{% endif %}">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/>
|
||||
</svg>
|
||||
<i class="bi bi-house"style="font-size: 1rem;"></i>
|
||||
Inicio
|
||||
<!-- {% if session.get('mensaje') and page == 'inicio' %}<span class="msg-badge">!</span>{% endif %} -->
|
||||
</a>
|
||||
|
||||
<!-- <a href="{{ url_for('explorar') }}" class="{% if page == 'explorar' %}active{% endif %}">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
||||
</svg>
|
||||
Explorar
|
||||
{% if session.get('mensaje') and page == 'explorar' %}<span class="msg-badge">!</span>{% endif %}
|
||||
</a> -->
|
||||
|
||||
|
||||
<a href="{{ url_for('vehiculos_list') }}" class="{% if page == 'vehiculos' %}active{% endif %}">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M5 17H3a2 2 0 01-2-2V9a2 2 0 012-2h11l4 4v4a2 2 0 01-2 2h-1"/><circle cx="7" cy="17" r="2"/><circle cx="17" cy="17" r="2"/>
|
||||
</svg>
|
||||
<i class="bi bi-car-front" style="font-size: 1rem;"></i>
|
||||
Vehículos
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('itv_list') }}" class="{% if page == 'itv' %}active{% endif %}">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/>
|
||||
<line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/>
|
||||
<path d="M9 16l2 2 4-4"/>
|
||||
</svg>
|
||||
<i class="bi bi-calendar-check" style="font-size: 1rem;"></i>
|
||||
ITV
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('viat_list') }}" class="{% if page == 'viat' %}active{% endif %}">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="5" y="7" width="10" height="10" rx="2"/>
|
||||
<circle cx="10" cy="12" r="1"/>
|
||||
<path d="M17 9c1.7 1.7 1.7 4.3 0 6"/>
|
||||
<path d="M19.5 6.5c3 3 3 8 0 11"/>
|
||||
</svg>
|
||||
<i class="bi bi-broadcast" style="font-size: 1rem;"></i>
|
||||
Via-T
|
||||
</a>
|
||||
|
||||
<!-- <a href="{{ url_for('campus_list') }}" class="{% if page == 'campus' %}active{% endif %}">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="2" y="7" width="20" height="14" rx="2"/><path d="M16 7V5a2 2 0 00-2-2h-4a2 2 0 00-2 2v2"/><line x1="12" y1="12" x2="12" y2="16"/><line x1="10" y1="14" x2="14" y2="14"/>
|
||||
</svg>
|
||||
Campus
|
||||
</a> -->
|
||||
|
||||
<a href="{{ url_for('contacto') }}" class="{% if page == 'contacto' %}active{% endif %}">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/>
|
||||
</svg>
|
||||
<i class="bi bi-person-vcard" style="font-size: 1rem;"></i>
|
||||
Contacto
|
||||
{% if session.get('mensaje') and page == 'contacto' %}<span class="msg-badge">!</span>{% endif %}
|
||||
</a>
|
||||
|
||||
|
||||
<div class="nav-submenu" style="margin-top: 100px;">
|
||||
<hr style="opacity: 0.5;margin-bottom: 5px;">
|
||||
<button class="nav-submenu-toggle {% if page in ['poblaciones','unidades','tipo'] %}active{% endif %}"
|
||||
onclick="toggleSubmenu(this)">
|
||||
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" style="width: 15px;" stroke="currentColor" stroke-width="2">
|
||||
<ellipse cx="12" cy="5" rx="9" ry="3"/>
|
||||
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/>
|
||||
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/>
|
||||
</svg>
|
||||
<i class="bi bi-database-check" style="font-size: 1rem;"></i>
|
||||
Configuración
|
||||
<svg class="chevron" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="margin-top:10px;margin-left:auto;transition:transform .2s">
|
||||
<polyline points="6 9 12 15 18 9"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="nav-submenu-items {% if page in ['poblaciones','unidades','tipo'] %}open{% endif %}">
|
||||
<!-- <a href="{{ url_for('poblaciones_list') }}" class="{% if page == 'poblaciones' %}active{% endif %}">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/>
|
||||
<path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/>
|
||||
</svg>
|
||||
Poblaciones
|
||||
</a> -->
|
||||
<a href="{{ url_for('unidades_list') }}" class="{% if page == 'unidades' %}active{% endif %}">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/>
|
||||
<rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>
|
||||
</svg>
|
||||
<i class="bi bi-building"></i>
|
||||
Unidades
|
||||
</a>
|
||||
<a href="{{ url_for('tipo_list') }}" class="{% if page == 'tipo' %}active{% endif %}">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/>
|
||||
<line x1="8" y1="18" x2="21" y2="18"/>
|
||||
<line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/>
|
||||
<line x1="3" y1="18" x2="3.01" y2="18"/>
|
||||
</svg>
|
||||
<i class="bi bi-bus-front"></i>
|
||||
Tipo vehiculo
|
||||
</a>
|
||||
</div>
|
||||
@@ -867,6 +810,6 @@
|
||||
items.classList.contains('open') ? 'rotate(180deg)' : '';
|
||||
}
|
||||
</script>
|
||||
∫
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<div class="page-header">
|
||||
<div class="page-eyebrow">Comunicación</div>
|
||||
<div class="page-title">Contacto</div>
|
||||
<div class="page-title"><i class="bi bi-person-vcard"></i> Contacto</div>
|
||||
</div>
|
||||
|
||||
<div class="card-grid">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<div class="page-eyebrow">Panel principal</div>
|
||||
<div class="page-title">Bienvenido a PyFleet</div>
|
||||
<div class="page-title"><i class="bi bi-house"></i> Bienvenido a PyFleet</div>
|
||||
<div class="page-subtitle">Gestión de flota.</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<div class="page-eyebrow">Revisiones</div>
|
||||
<div class="page-title">Control de ITV</div>
|
||||
<div class="page-title"><i class="bi bi-calendar-check"></i> Control de ITV</div>
|
||||
<div class="page-subtitle">Filtra los vehículos por fecha de vencimiento de la ITV, población, unidad, tipo vehículo.</div>
|
||||
</div>
|
||||
|
||||
@@ -170,7 +170,7 @@
|
||||
<tr>
|
||||
<th hidden>ID</th>
|
||||
<th>M. Oficial</th>
|
||||
<th>M. Civil</th>
|
||||
<th>M. Reservada</th>
|
||||
<th>Marca</th>
|
||||
<th>Modelo</th>
|
||||
<th>Tipo</th>
|
||||
@@ -190,7 +190,7 @@
|
||||
onmouseout="this.style.background=''">
|
||||
<td hidden class="td-id">#{{ r['id'] }}</td>
|
||||
<td>{{ r['m_oficial'] or '—' }}</td>
|
||||
<td>{{ r['m_civil'] or '—' }}</td>
|
||||
<td>{{ r['m_reservada'] or '—' }}</td>
|
||||
<td><strong>{{ r['marca'] or '—' }}</strong></td>
|
||||
<td>{{ r['modelo'] or '—' }}</td>
|
||||
<td>{{ r['tipo'] or '—' }}</td>
|
||||
@@ -261,7 +261,7 @@
|
||||
var desde = '{{ desde }}', hasta = '{{ hasta }}';
|
||||
var titulo = 'Control ITV' + (desde ? ' — Desde: ' + desde : '') + (hasta ? ' Hasta: ' + hasta : '');
|
||||
var fecha = new Date().toLocaleDateString('es-ES');
|
||||
var heads = ['M. Oficial','M. Civil','Marca','Modelo','Tipo','Poblacion',"Unidad",'Fecha ITV','Estado'];
|
||||
var heads = ['M. Oficial','M. Reservada','Marca','Modelo','Tipo','Poblacion',"Unidad",'Fecha ITV','Estado'];
|
||||
var filas = '';
|
||||
|
||||
document.querySelectorAll('#tablaITV tbody tr').forEach(function(tr) {
|
||||
@@ -296,7 +296,7 @@
|
||||
// COPIAR
|
||||
document.getElementById('btnCopiar') && document.getElementById('btnCopiar').addEventListener('click', function() {
|
||||
var btn = this;
|
||||
var heads = ['M. Oficial','M. Civil','Marca','Modelo','Tipo','Poblacion ','Unidad','Fecha ITV','Estado'];
|
||||
var heads = ['M. Oficial','M. Reservada','Marca','Modelo','Tipo','Poblacion ','Unidad','Fecha ITV','Estado'];
|
||||
var lineas = [heads.join('\t')];
|
||||
|
||||
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Poblaciones{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='bootstrap-icons/bootstrap-icons.css') }}">
|
||||
|
||||
|
||||
<div class="page-header">
|
||||
<div class="page-eyebrow">Configuración</div>
|
||||
<div class="page-title">Poblaciones</div>
|
||||
<div class="page-subtitle">Gestiona las poblaciones administradas.</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div style="display:flex;gap:24px;align-items:flex-start;flex-wrap:wrap;">
|
||||
|
||||
{# Formulario añadir #}
|
||||
<div style="background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:24px;min-width:260px;">
|
||||
<p class="form-label" style="margin-bottom:14px;">Nueva población</p>
|
||||
<form method="POST" action="{{ url_for('poblaciones_nueva') }}">
|
||||
<div class="form-group">
|
||||
<input class="form-input" type="text" name="poblacion"
|
||||
placeholder="Nombre de la población" required autofocus>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" style="width:100%;margin-top:8px;">
|
||||
✚ Añadir
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{# Listado #}
|
||||
<div style="flex:1;min-width:280px;max-width: 800px;">
|
||||
<p style="margin-top:8px;font-size:12px;color:var(--muted)">{{ rows|length }} poblaciones</p>
|
||||
<div class="table-wrap border">
|
||||
<table id="tabla" class="">
|
||||
<thead class="">
|
||||
<tr class="">
|
||||
<th hidden >ID</th>
|
||||
<th>Población</th>
|
||||
<!-- <th style="text-align:right">Acción</th> -->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in rows %}
|
||||
{% set nombre_pob = r['poblacion'] %}
|
||||
<tr class="border-bottom">
|
||||
<td hidden class="td-id">{{ r['id'] }}</td>
|
||||
<td>{{ r['poblacion'] }}</td>
|
||||
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="3"><div class="empty-state">Sin poblaciones registradas.</div></td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="{{ url_for('static', filename='js/jquery-3.7.1.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/tabledit.min.js') }}"></script>
|
||||
|
||||
|
||||
<script>
|
||||
$('#tabla').Tabledit({
|
||||
url:'/editar_poblaciones',
|
||||
|
||||
editButton:true,
|
||||
deleteButton:true,
|
||||
restoreButton:false,
|
||||
hideIdentifier:false,
|
||||
|
||||
buttons:{
|
||||
edit:{
|
||||
class:'btn btn-sm border rounded me-1',
|
||||
html:'<i class="bi bi-pencil"></i> '
|
||||
},
|
||||
delete:{
|
||||
class:'btn btn-sm btn-light text-danger border me-1',
|
||||
html:'<i class="bi bi-trash"></i>'
|
||||
// html:'Borrar'
|
||||
},
|
||||
save:{
|
||||
class:'btn btn-sm btn-success',
|
||||
html:'Guardar'
|
||||
},
|
||||
confirm:{
|
||||
class:'btn btn-sm btn-warning',
|
||||
html:'¿Seguro?'
|
||||
}
|
||||
},
|
||||
|
||||
columns:{
|
||||
identifier:[0,'id'],
|
||||
editable:[
|
||||
[1,'poblacion']
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
onSuccess:function(data){
|
||||
if (data.action == "edit") {
|
||||
location.reload();
|
||||
}
|
||||
|
||||
if (data.action == "delete") {
|
||||
location.reload();
|
||||
}
|
||||
|
||||
console.log(data);
|
||||
}
|
||||
});
|
||||
|
||||
$('#tabla td:last-child').attr(
|
||||
'style',
|
||||
'white-space: nowrap; width:185px;'
|
||||
);
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<div class="page-header">
|
||||
<div class="page-eyebrow">Configuración</div>
|
||||
<div class="page-title">Tipo vehículo</div>
|
||||
<div class="page-title"><i class="bi bi-bus-front"></i> Tipo vehículo</div>
|
||||
<div class="page-subtitle">Gestiona los tipos de vehículos.</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<div class="page-header">
|
||||
<div class="page-eyebrow">Configuración</div>
|
||||
<div class="page-title">Unidades</div>
|
||||
<div class="page-title"><i class="bi bi-building"></i> Unidades</div>
|
||||
<div class="page-subtitle">Gestiona los unidades a las que se asignan los vehículos.</div>
|
||||
</div>
|
||||
|
||||
|
||||
Executable
+828
@@ -0,0 +1,828 @@
|
||||
```html
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Unidades{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://unpkg.com/[email protected]/dist/css/tabulator_bootstrap5.min.css"
|
||||
>
|
||||
|
||||
<style>
|
||||
|
||||
/* CONTENEDOR TABULATOR */
|
||||
|
||||
#tabla-unidades {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* CABECERA */
|
||||
|
||||
.unidades-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
|
||||
.unidades-search {
|
||||
position: relative;
|
||||
width: 500px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.unidades-search svg {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
color: var(--muted);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
.unidades-search input {
|
||||
width: 100%;
|
||||
padding: 9px 38px 9px 38px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
|
||||
.unidades-search input:focus {
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
|
||||
.clear-search {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
|
||||
.clear-search:hover {
|
||||
background: var(--border);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
|
||||
/* =====================================================
|
||||
TABULATOR
|
||||
===================================================== */
|
||||
|
||||
.tabulator {
|
||||
border: 0 !important;
|
||||
background: var(--surface);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
||||
.tabulator .tabulator-header {
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
|
||||
.tabulator .tabulator-header .tabulator-col {
|
||||
background: transparent;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
|
||||
.tabulator .tabulator-row {
|
||||
background: var(--surface);
|
||||
border-bottom: 1px solid var(--border);
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
|
||||
.tabulator .tabulator-row:hover {
|
||||
background: rgba(0,0,0,.025);
|
||||
}
|
||||
|
||||
|
||||
/* .tabulator .tabulator-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-right: 0;
|
||||
} */
|
||||
|
||||
|
||||
/* ESTADO */
|
||||
|
||||
#estadoTabla {
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
margin-top: 8px;
|
||||
min-height: 18px;
|
||||
}
|
||||
|
||||
|
||||
/* =====================================================
|
||||
RESPONSIVE
|
||||
===================================================== */
|
||||
|
||||
@media (max-width: 700px) {
|
||||
|
||||
.unidades-toolbar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.unidades-search {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
<!-- =========================================================
|
||||
CABECERA
|
||||
========================================================== -->
|
||||
|
||||
<div class="page-header">
|
||||
|
||||
<div class="page-eyebrow">
|
||||
Configuración
|
||||
</div>
|
||||
|
||||
<div class="page-title">
|
||||
<i class="bi bi-building"></i>
|
||||
Unidades
|
||||
</div>
|
||||
|
||||
<div class="page-subtitle">
|
||||
Gestiona las unidades a las que se asignan los vehículos.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div style="
|
||||
display:flex;
|
||||
gap:24px;
|
||||
align-items:flex-start;
|
||||
flex-wrap:wrap;
|
||||
">
|
||||
|
||||
|
||||
<!-- =====================================================
|
||||
FORMULARIO NUEVA UNIDAD
|
||||
====================================================== -->
|
||||
|
||||
<div style="
|
||||
background:var(--surface);
|
||||
border:1px solid var(--border);
|
||||
border-radius:var(--radius);
|
||||
padding:24px;
|
||||
min-width:400px;
|
||||
">
|
||||
|
||||
<p
|
||||
class="form-label"
|
||||
style="margin-bottom:14px;"
|
||||
>
|
||||
Nueva unidad
|
||||
</p>
|
||||
|
||||
|
||||
<form
|
||||
method="POST"
|
||||
action="{{ url_for('unidades_nueva') }}"
|
||||
>
|
||||
|
||||
|
||||
<!-- POBLACIÓN -->
|
||||
|
||||
<div class="form-group">
|
||||
|
||||
<label class="form-label">
|
||||
Población
|
||||
</label>
|
||||
|
||||
<div class="select-wrap">
|
||||
|
||||
<select
|
||||
class="form-input form-select"
|
||||
name="poblacion_id"
|
||||
required
|
||||
>
|
||||
|
||||
<option value="">
|
||||
— Selecciona población —
|
||||
</option>
|
||||
|
||||
{% for p in poblaciones %}
|
||||
|
||||
<option value="{{ p['id'] }}">
|
||||
{{ p['poblacion'] }}
|
||||
</option>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
</select>
|
||||
|
||||
<svg
|
||||
class="select-arrow"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.5"
|
||||
>
|
||||
<polyline points="6 9 12 15 18 9"/>
|
||||
</svg>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- UNIDAD -->
|
||||
|
||||
<div class="form-group">
|
||||
|
||||
<label class="form-label">
|
||||
Unidad
|
||||
</label>
|
||||
|
||||
<input
|
||||
class="form-input"
|
||||
type="text"
|
||||
name="unidad"
|
||||
placeholder="Nombre de la unidad"
|
||||
required
|
||||
>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-sm btn-primary"
|
||||
style="
|
||||
width:50%;
|
||||
margin-top:12px;
|
||||
display:block;
|
||||
margin-left:auto;
|
||||
margin-right:auto;
|
||||
"
|
||||
>
|
||||
|
||||
✚ Añadir
|
||||
|
||||
</button>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- =====================================================
|
||||
LISTADO
|
||||
====================================================== -->
|
||||
|
||||
<div style="
|
||||
flex:1;
|
||||
min-width:320px;
|
||||
max-width:800px;
|
||||
">
|
||||
|
||||
|
||||
<!-- TOOLBAR -->
|
||||
|
||||
<div class="unidades-toolbar">
|
||||
|
||||
|
||||
<!-- BUSCADOR -->
|
||||
|
||||
<div class="unidades-search">
|
||||
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<circle
|
||||
cx="11"
|
||||
cy="11"
|
||||
r="8"
|
||||
/>
|
||||
<line
|
||||
x1="21"
|
||||
y1="21"
|
||||
x2="16.65"
|
||||
y2="16.65"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
|
||||
<input
|
||||
type="text"
|
||||
id="searchUnidades"
|
||||
placeholder="Buscar por población o unidad…"
|
||||
autocomplete="off"
|
||||
autofocus
|
||||
>
|
||||
|
||||
|
||||
<button
|
||||
type="button"
|
||||
id="clearSearch"
|
||||
class="clear-search"
|
||||
>
|
||||
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.5"
|
||||
>
|
||||
<line
|
||||
x1="18"
|
||||
y1="6"
|
||||
x2="6"
|
||||
y2="18"
|
||||
/>
|
||||
|
||||
<line
|
||||
x1="6"
|
||||
y1="6"
|
||||
x2="18"
|
||||
y2="18"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- CONTADOR -->
|
||||
|
||||
<div
|
||||
id="contador"
|
||||
style="
|
||||
margin:8px 0;
|
||||
font-size:12px;
|
||||
color:var(--muted);
|
||||
"
|
||||
>
|
||||
{{ rows|length }}
|
||||
unidad{% if rows|length != 1 %}es{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
<!-- TABLA -->
|
||||
|
||||
<div id="tabla-unidades"></div>
|
||||
|
||||
|
||||
<!-- ESTADO -->
|
||||
|
||||
<div id="estadoTabla"></div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- DATOS PARA TABULATOR=============================== -->
|
||||
|
||||
<script>
|
||||
|
||||
const unidades = [
|
||||
|
||||
{% for r in rows %}
|
||||
|
||||
{
|
||||
id: {{ r['id'] | tojson }},
|
||||
|
||||
poblacion_id:
|
||||
{{ r['poblacion_id'] | tojson }},
|
||||
|
||||
poblacion:
|
||||
{{ (r['poblacion'] or '') | tojson }},
|
||||
|
||||
unidad:
|
||||
{{ r['unidad'] | tojson }}
|
||||
}
|
||||
|
||||
{% if not loop.last %},{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
];
|
||||
|
||||
|
||||
const poblaciones = [
|
||||
|
||||
{% for p in poblaciones %}
|
||||
|
||||
{
|
||||
id: {{ p['id'] | tojson }},
|
||||
|
||||
nombre:
|
||||
{{ p['poblacion'] | tojson }}
|
||||
}
|
||||
|
||||
{% if not loop.last %},{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
];
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<!-- TABULATOR -->
|
||||
|
||||
<script
|
||||
src="https://unpkg.com/[email protected]/dist/js/tabulator.min.js">
|
||||
</script>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
// =========================================================
|
||||
// ELEMENTOS
|
||||
// =========================================================
|
||||
|
||||
const buscador =
|
||||
document.getElementById(
|
||||
"searchUnidades"
|
||||
);
|
||||
|
||||
const clearSearch =
|
||||
document.getElementById(
|
||||
"clearSearch"
|
||||
);
|
||||
|
||||
const contador =
|
||||
document.getElementById(
|
||||
"contador"
|
||||
);
|
||||
|
||||
const estadoTabla =
|
||||
document.getElementById(
|
||||
"estadoTabla"
|
||||
);
|
||||
|
||||
|
||||
// =========================================================
|
||||
// LISTA DE POBLACIONES PARA EL SELECT
|
||||
// =========================================================
|
||||
|
||||
const opcionesPoblaciones = {};
|
||||
|
||||
poblaciones.forEach(function(p) {
|
||||
|
||||
opcionesPoblaciones[p.id] =
|
||||
p.nombre;
|
||||
|
||||
});
|
||||
|
||||
|
||||
// =========================================================
|
||||
// CONTADOR
|
||||
// =========================================================
|
||||
|
||||
function actualizarContador() {
|
||||
|
||||
const cantidad =
|
||||
tabla.getDataCount("active");
|
||||
|
||||
|
||||
contador.textContent =
|
||||
cantidad +
|
||||
" unidad" +
|
||||
(cantidad !== 1 ? "es" : "");
|
||||
|
||||
}
|
||||
|
||||
|
||||
// =========================================================
|
||||
// MENSAJE
|
||||
// =========================================================
|
||||
|
||||
function mostrarEstado(texto, error = false) {
|
||||
|
||||
estadoTabla.textContent = texto;
|
||||
|
||||
estadoTabla.style.color =
|
||||
error
|
||||
? "#dc3545"
|
||||
: "#198754";
|
||||
|
||||
|
||||
setTimeout(function() {
|
||||
|
||||
estadoTabla.textContent = "";
|
||||
|
||||
}, 3000);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// TABULATOR
|
||||
|
||||
const tabla = new Tabulator(
|
||||
"#tabla-unidades",
|
||||
{
|
||||
|
||||
data: unidades,
|
||||
layout: "fitColumns",
|
||||
height: "500px",
|
||||
placeholder:
|
||||
"Sin unidades registradas.",
|
||||
editTriggerEvent: "click",
|
||||
|
||||
columns: [
|
||||
|
||||
|
||||
{ title: "ID",
|
||||
field: "id",
|
||||
visible: false
|
||||
},
|
||||
|
||||
|
||||
{ title: "Población",
|
||||
field: "poblacion_id",
|
||||
minWidth: 220,
|
||||
editor: "list",
|
||||
editorParams: {
|
||||
values:
|
||||
opcionesPoblaciones,
|
||||
autocomplete: true,
|
||||
listOnEmpty: true
|
||||
},
|
||||
|
||||
formatter: function(cell) {
|
||||
const id =
|
||||
cell.getValue();
|
||||
return opcionesPoblaciones[id]
|
||||
|| "—";
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
title: "Unidad",
|
||||
field: "unidad",
|
||||
editor: "input",
|
||||
validator: "required",
|
||||
minWidth: 250,
|
||||
formatter: function(cell) {
|
||||
const valor =
|
||||
cell.getValue();
|
||||
return valor
|
||||
? `<strong>${escapeHtml(valor)}</strong>`
|
||||
: "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// ESCAPAR HTML
|
||||
|
||||
function escapeHtml(text) {
|
||||
|
||||
const div =
|
||||
document.createElement("div");
|
||||
|
||||
div.textContent = text;
|
||||
|
||||
return div.innerHTML;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// EDICIÓN DE CELDA
|
||||
|
||||
tabla.on(
|
||||
"cellEdited",
|
||||
async function(cell) {
|
||||
|
||||
const fila =
|
||||
cell.getRow();
|
||||
|
||||
const datos =
|
||||
fila.getData();
|
||||
|
||||
const campo =
|
||||
cell.getField();
|
||||
|
||||
const valor =
|
||||
cell.getValue();
|
||||
|
||||
|
||||
console.log(
|
||||
"Edición:",
|
||||
{
|
||||
id: datos.id,
|
||||
campo: campo,
|
||||
valor: valor
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// Guardar en Flask
|
||||
|
||||
try {
|
||||
|
||||
const respuesta =
|
||||
await fetch(
|
||||
`/api/unidades/${datos.id}`,
|
||||
{
|
||||
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type":
|
||||
"application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
campo: campo,
|
||||
valor: valor
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
const resultado =
|
||||
await respuesta.json();
|
||||
|
||||
if (!respuesta.ok) {
|
||||
|
||||
throw new Error(
|
||||
resultado.error ||
|
||||
"No se pudo guardar"
|
||||
);
|
||||
}
|
||||
|
||||
// Actualizar fila con respuesta de Flask
|
||||
|
||||
await fila.update({
|
||||
|
||||
id: resultado.id,
|
||||
poblacion_id: resultado.poblacion_id,
|
||||
poblacion: resultado.poblacion,
|
||||
unidad: resultado.unidad
|
||||
});
|
||||
|
||||
mostrarEstado(
|
||||
"✓ Cambios guardados"
|
||||
);
|
||||
|
||||
}
|
||||
catch (error) {
|
||||
console.error(
|
||||
"Error:",
|
||||
error
|
||||
);
|
||||
|
||||
mostrarEstado(
|
||||
error.message,
|
||||
true
|
||||
);
|
||||
|
||||
// Volvemos a obtener los datos originales
|
||||
// desde el servidor.
|
||||
|
||||
tabla.replaceData(
|
||||
unidades
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// BUSCADOR
|
||||
buscador.addEventListener(
|
||||
"input",
|
||||
function() {
|
||||
|
||||
const texto =
|
||||
this.value
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
|
||||
|
||||
clearSearch.style.display =
|
||||
texto
|
||||
? "flex"
|
||||
: "none";
|
||||
|
||||
|
||||
if (!texto) {
|
||||
|
||||
tabla.clearFilter();
|
||||
|
||||
actualizarContador();
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
|
||||
tabla.setFilter(
|
||||
function(data) {
|
||||
|
||||
const poblacion =
|
||||
String(
|
||||
data.poblacion || ""
|
||||
).toLowerCase();
|
||||
|
||||
|
||||
const unidad =
|
||||
String(
|
||||
data.unidad || ""
|
||||
).toLowerCase();
|
||||
|
||||
|
||||
return (
|
||||
poblacion.includes(texto) ||
|
||||
unidad.includes(texto)
|
||||
);
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
setTimeout(
|
||||
actualizarContador,
|
||||
0
|
||||
);
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// LIMPIAR BUSCADOR
|
||||
|
||||
clearSearch.addEventListener(
|
||||
"click",
|
||||
function() {
|
||||
|
||||
buscador.value = "";
|
||||
|
||||
clearSearch.style.display =
|
||||
"none";
|
||||
|
||||
tabla.clearFilter();
|
||||
|
||||
buscador.focus();
|
||||
|
||||
actualizarContador();
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// CONTADOR INICIAL
|
||||
|
||||
actualizarContador();
|
||||
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
```
|
||||
+327
-48
@@ -25,6 +25,8 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
||||
<div class="page-header">
|
||||
<div class="page-eyebrow">Vehículos</div>
|
||||
<div class="page-title">{{ 'Editar vehículo ' ~ vehiculo['m_oficial'] if vehiculo else 'Nuevo vehículo' }}</div>
|
||||
@@ -55,11 +57,11 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="m_civil">Matrícula civil</label>
|
||||
<input class="form-input" type="text" id="m_civil" name="m_civil"
|
||||
<label class="form-label" for="m_reservada">Matrícula reservada</label>
|
||||
<input class="form-input" type="text" id="m_reservada" name="m_reservada"
|
||||
placeholder="Ej. 5678XYZ"
|
||||
oninput="this.value = this.value.toUpperCase();"
|
||||
value="{{ (vehiculo['m_civil'] or '') if vehiculo else request.form.get('m_civil', '') }}">
|
||||
value="{{ (vehiculo['m_reservada'] or '') if vehiculo else request.form.get('m_reservada', '') }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
@@ -145,19 +147,6 @@
|
||||
value="{{ (vehiculo['color'] or '') if vehiculo else request.form.get('color', '') }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="cilindrada">Cilindrada</label>
|
||||
<input class="form-input" type="text" id="cilindrada" name="cilindrada"
|
||||
placeholder="Ej. 1600cc"
|
||||
value="{{ (vehiculo['cilindrada'] or '') if vehiculo else request.form.get('cilindrada', '') }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="potencia">Potencia (CV)</label>
|
||||
<input class="form-input" type="number" id="potencia" name="potencia"
|
||||
placeholder="Ej. 120"
|
||||
value="{{ (vehiculo['potencia'] or '') if vehiculo else request.form.get('potencia', '') }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="combustible">Combustible</label>
|
||||
@@ -166,13 +155,6 @@
|
||||
value="{{ (vehiculo['combustible'] or '') if vehiculo else request.form.get('combustible', '') }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="neumaticos">Neumáticos</label>
|
||||
<input class="form-input" type="text" id="neumaticos" name="neumaticos"
|
||||
placeholder="Ej. 205/55 R16"
|
||||
value="{{ (vehiculo['neumaticos'] or '') if vehiculo else request.form.get('neumaticos', '') }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="plazas">Plazas</label>
|
||||
<input class="form-input" type="text" id="plazas" name="plazas"
|
||||
@@ -180,27 +162,45 @@
|
||||
value="{{ (vehiculo['plazas'] or '') if vehiculo else request.form.get('plazas', '') }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="width: 280px;">
|
||||
<label class="form-label"> </label>
|
||||
<label style="margin-top:22px;display:flex;align-items:center;gap:10px;cursor:pointer;
|
||||
padding:10px 14px;background:var(--bg);border:1px solid var(--border);
|
||||
border-radius:8px;transition:border-color .15s;"
|
||||
onmouseover="this.style.borderColor='var(--accent)'"
|
||||
onmouseout="this.style.borderColor='var(--border)'">
|
||||
|
||||
<input type="checkbox" name="lanzadestellos" value="1"
|
||||
{% if vehiculo and vehiculo['lanzadestellos'] == 1 %}checked{% endif %}
|
||||
style="width:16px;height:16px;accent-color:var(--accent);cursor:pointer;">
|
||||
|
||||
<span style="font-size:13px;color:var(--text)">Lanzadestellos</span>
|
||||
<span style="font-size:11px;color:var(--muted);margin-left:4px;">
|
||||
<div style="display:flex; align-items:center; gap:12px;">
|
||||
|
||||
({{ '✓ Con lanzadestellos' if vehiculo and vehiculo['lanzadestellos'] == 1 else '✗ Sin lanzadestellos' }})
|
||||
</span>
|
||||
<!-- Caja del checkbox -->
|
||||
<div class="form-group">
|
||||
<label class="form-label"></label>
|
||||
<label style="width: 160px; margin-top:22px; display:flex; align-items:center;
|
||||
gap:10px;
|
||||
cursor:pointer;
|
||||
padding:10px 14px;
|
||||
background:var(--bg);
|
||||
border:1px solid var(--border);
|
||||
border-radius:8px;
|
||||
transition:border-color .15s;"
|
||||
onmouseover="this.style.borderColor='var(--accent)'"
|
||||
onmouseout="this.style.borderColor='var(--border)'">
|
||||
|
||||
<input type="checkbox" name="lanzadestellos" id="lanzadestellos" value="1"
|
||||
{% if vehiculo and vehiculo['lanzadestellos'] == 1 %}checked{% endif %}
|
||||
style=" width:16px; height:16px; accent-color:var(--accent); cursor:pointer;">
|
||||
Lanzadestellos
|
||||
<span
|
||||
style="font-size:11px;color:var(--muted);margin-left:4px;"
|
||||
id="texto-lanzadestellos">
|
||||
<!-- {% if vehiculo and vehiculo['lanzadestellos'] == 1 %}
|
||||
✓ Con lanzadestellos
|
||||
{% else %}
|
||||
✗ Sin lanzadestellos
|
||||
{% endif %} -->
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<i id="icono-lanzadestellos" class="bi bi-brightness-alt-high-fill" style="font-size:2rem;color:cornflowerblue; {% if not (vehiculo and vehiculo['lanzadestellos'] == 1) %}display:none;{% endif %}"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% if vehiculo %}
|
||||
{% set sit = vehiculo['situacion'] or '' %}
|
||||
{% if sit | upper == 'BAJA' %}
|
||||
@@ -329,12 +329,15 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
<div class="form-actions" style="margin-top:28px">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{{ '💾 GUARDAR CAMBIOS' if vehiculo else '✚ Registrar vehículo' }}
|
||||
<button type="submit" class="btn btn-primary"> <i class="bi bi-floppy" style="font-size: 1rem;"></i>
|
||||
{{ 'GUARDAR CAMBIOS' if vehiculo else '✚ Registrar vehículo' }}
|
||||
</button>
|
||||
|
||||
{# Cancelar #}
|
||||
|
||||
{% if origen == 'viat' %}
|
||||
<a href="/viat?q={{ q_viat }}" class="btn btn-ghost">Cancelar</a>
|
||||
{% elif desde or hasta or q_itv %}
|
||||
@@ -346,11 +349,7 @@
|
||||
<!-- <div style="display:flex;justify-content:space-between;align-items:center;margin-top:12px;"> -->
|
||||
{% if vehiculo %}
|
||||
<button type="button" class="btn btn-ghost" onclick="imprimirFicha()" title="Imprimir ficha del vehículo">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="6 9 6 2 18 2 18 9"/>
|
||||
<path d="M6 18H4a2 2 0 01-2-2v-5a2 2 0 012-2h16a2 2 0 012 2v5a2 2 0 01-2 2h-2"/>
|
||||
<rect x="6" y="14" width="12" height="8"/>
|
||||
</svg>
|
||||
<i class="bi bi-printer"></i>
|
||||
Ficha - Imprimir/PDF
|
||||
</button>
|
||||
{% endif %}
|
||||
@@ -511,10 +510,175 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if vehiculo %}
|
||||
{# ── DOCUMENTOS PDF ── #}
|
||||
<div class="section-title" style="margin-top:32px">Documentos PDF</div>
|
||||
|
||||
{# Listado de documentos existentes #}
|
||||
{% if documentos %}
|
||||
<div class="table-wrap" style="margin-bottom:16px;">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nombre</th>
|
||||
<th>Fecha</th>
|
||||
<th style="text-align:right">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for doc in documentos %}
|
||||
<tr>
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<i class="bi bi-file-earmark-text" style="color: salmon;"></i>
|
||||
<strong style="font-size:13px;">{{ doc['nombre'] }}</strong>
|
||||
</div>
|
||||
</td>
|
||||
<td style="font-size:12px;color:var(--muted);">{{ doc['fecha'] }}</td>
|
||||
<td style="text-align:right;">
|
||||
<div style="display:flex;gap:6px;justify-content:flex-end;">
|
||||
<a href="{{ url_for('documentos_ver', doc_id=doc['id']) }}"
|
||||
target="_blank" class="btn btn-ghost btn-sm" title="Ver">
|
||||
<i class="bi bi-eye"></i>
|
||||
Ver
|
||||
</a>
|
||||
<a href="{{ url_for('documentos_descargar', doc_id=doc['id']) }}"
|
||||
class="btn btn-ghost btn-sm" title="Descargar">
|
||||
<i class="bi bi-download"></i>
|
||||
Descargar
|
||||
</a>
|
||||
<form method="POST"
|
||||
action="{{ url_for('documentos_eliminar', doc_id=doc['id']) }}">
|
||||
<input type="hidden" name="vid" value="{{ vehiculo['id'] }}">
|
||||
<input type="hidden" name="_q" value="{{ q|default('') }}">
|
||||
<input type="hidden" name="_incl_bajas" value="{{ incl_bajas|default('0') }}">
|
||||
<button type="button" class="btn btn-danger btn-sm"
|
||||
onclick="if(confirm('¿Eliminar documento «{{ doc['nombre'] }}»?')) this.closest('form').submit()">
|
||||
Eliminar
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p style="color:var(--muted);font-size:13px;margin-bottom:16px;">
|
||||
No hay documentos adjuntos para este vehículo.
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{# Formulario subir nuevo documento #}
|
||||
<div style="background:var(--bg);border:1px solid var(--border);
|
||||
border-radius:10px;padding:20px;">
|
||||
<p style="font-size:11px;font-weight:600;letter-spacing:.7px;
|
||||
text-transform:uppercase;color:var(--muted);margin-bottom:14px;">
|
||||
Añadir documento <5Mb
|
||||
</p>
|
||||
<form method="POST"
|
||||
action="{{ url_for('documentos_upload', id=vehiculo['id']) }}"
|
||||
enctype="multipart/form-data">
|
||||
<input type="hidden" name="_q" value="{{ q|default('') }}">
|
||||
<input type="hidden" name="_incl_bajas" value="{{ incl_bajas|default('0') }}">
|
||||
<input type="hidden" name="_desde" value="{{ desde|default('') }}">
|
||||
<input type="hidden" name="_hasta" value="{{ hasta|default('') }}">
|
||||
<input type="hidden" name="_q_itv" value="{{ q_itv|default('') }}">
|
||||
<input type="hidden" name="_origen" value="{{ origen|default('') }}">
|
||||
<input type="hidden" name="_q_viat" value="{{ q_viat|default('') }}">
|
||||
|
||||
<div class="form-grid" style="margin-bottom:12px;">
|
||||
<div class="form-group" style="margin-bottom:0; width: 300px">
|
||||
<label class="form-label">Nombre del documento</label>
|
||||
<input class="form-input" style="background-color: white;" type="text" name="nombre_doc"
|
||||
placeholder="Ej. Permiso circulación, Seguro, etc…">
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:0;margin-left: 80px;" >
|
||||
<label class="form-label">Archivo PDF</label>
|
||||
<input class="form-control" type="file" name="pdf" id="pdfInput" accept=".pdf"
|
||||
onchange="mostrarArchivo(this)"
|
||||
style="font-size:13px;color:var(--text);padding:8px 0;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- style="font-size:13px;color:var(--text);padding:8px 0;" -->
|
||||
<div id="pdfInfo" style="display:none;margin-bottom:12px;font-size:12px;
|
||||
color:var(--accent);padding:8px 12px;
|
||||
background:rgba(92,110,248,.08);border-radius:6px;">
|
||||
</div>
|
||||
|
||||
<button type="submit" id="btnSubir" class="btn btn-primary btn-sm"
|
||||
style="display:none;">
|
||||
<i class="bi bi-upload"></i>
|
||||
Subir documento
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div> {# ← cierre de form-card #}
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
function mostrarArchivo(input) {
|
||||
var info = document.getElementById('pdfInfo');
|
||||
var btnSubir = document.getElementById('btnSubir');
|
||||
if (input.files && input.files[0]) {
|
||||
var f = input.files[0];
|
||||
var size = (f.size / 1024).toFixed(1) + ' KB';
|
||||
info.textContent = '📄 ' + f.name + ' (' + size + ')';
|
||||
info.style.display = 'block';
|
||||
btnSubir.style.display = 'inline-flex';
|
||||
}
|
||||
}
|
||||
|
||||
// mostrar archivo solo unico documento ficha tecnica.pdf
|
||||
// function mostrarArchivo(input) {
|
||||
// var nombre = document.getElementById('pdfNombre');
|
||||
// var btnSubir = document.getElementById('btnSubir');
|
||||
// if (input.files && input.files[0]) {
|
||||
// nombre.textContent = '📄 ' + input.files[0].name;
|
||||
// nombre.style.display = 'block';
|
||||
// if (btnSubir) btnSubir.style.display = 'inline-flex';
|
||||
// }
|
||||
// }
|
||||
|
||||
function handleDrop(e) {
|
||||
e.preventDefault();
|
||||
document.getElementById('dropZone').style.borderColor = 'var(--border)';
|
||||
var files = e.dataTransfer.files;
|
||||
if (files.length && files[0].type === 'application/pdf') {
|
||||
var input = document.getElementById('pdfInput');
|
||||
input.files = files;
|
||||
mostrarArchivo(input);
|
||||
} else {
|
||||
alert('Solo se permiten archivos PDF.');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
const checkbox = document.getElementById('lanzadestellos');
|
||||
const icono = document.getElementById('icono-lanzadestellos');
|
||||
const texto = document.getElementById('texto-lanzadestellos');
|
||||
|
||||
checkbox.addEventListener('change', function () {
|
||||
|
||||
if (this.checked) {
|
||||
icono.style.display = 'inline';
|
||||
// texto.textContent = '✓ Con lanzadestellos';
|
||||
} else {
|
||||
icono.style.display = 'none';
|
||||
// texto.textContent = '✗ Sin lanzadestellos';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
@@ -524,6 +688,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function formatFecha(f) {
|
||||
if (!f) return '';
|
||||
// Si ya está en formato DD-MM-YYYY lo devuelve tal cual
|
||||
@@ -613,7 +778,7 @@ function imprimirFicha() {
|
||||
var v = {
|
||||
id: '{{ vehiculo["id"] }}',
|
||||
m_oficial: '{{ vehiculo["m_oficial"] or "" }}',
|
||||
m_civil: '{{ vehiculo["m_civil"] or "" }}',
|
||||
m_reservada: '{{ vehiculo["m_reservada"] or "" }}',
|
||||
m_propia: '{{ vehiculo["m_propìa"] or "" }}',
|
||||
denominacion: '{{ vehiculo["denominacion"] or "" }}',
|
||||
marca: '{{ vehiculo["marca"] or "" }}',
|
||||
@@ -740,7 +905,7 @@ function imprimirFicha() {
|
||||
<div class="section-title">Identificación</div>
|
||||
<table class="data-table">
|
||||
${fila('M. oficial', v.m_oficial)}
|
||||
${fila('M. civil', v.m_civil)}
|
||||
${fila('M. reservada', v.m_reservada)}
|
||||
${fila('M. propia', v.m_propia)}
|
||||
${fila('VIN', v.VIN)}
|
||||
${fila('Denominación', v.denominacion)}
|
||||
@@ -803,3 +968,117 @@ function imprimirFicha() {
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
<!-- OPCION CARGAR SOLO PDF FICHA TECNICA -->
|
||||
|
||||
<!-- {% if vehiculo %}
|
||||
{# ── DOCUMENTO PDF ── #}
|
||||
<div class="section-title" style="margin-top:32px;">Ficha técnica PDF</div>
|
||||
|
||||
<div style="background:var(--surface);border:1px solid var(--border);
|
||||
border-radius:var(--radius);padding:24px;margin-bottom:20px;">
|
||||
|
||||
{% if vehiculo['pdf'] %}
|
||||
{# PDF existente #}
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;
|
||||
flex-wrap:wrap;gap:12px;">
|
||||
<div style="display:flex;align-items:center;gap:12px;">
|
||||
<i class="bi bi-file-earmark-text" style="font-size: 2rem; color: salmon;"></i>
|
||||
<div>
|
||||
<div style="font-size:13px;font-weight:600;color:var(--text)">
|
||||
{{ vehiculo['pdf'] }}
|
||||
</div>
|
||||
<div style="font-size:11px;color:var(--muted);margin-top:2px;">
|
||||
Documento adjunto
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;">
|
||||
<a href="{{ url_for('vehiculos_pdf', id=vehiculo['id']) }}"
|
||||
target="_blank" class="btn btn-ghost btn-sm">
|
||||
<i class="bi bi-eye"></i>
|
||||
Ver PDF
|
||||
</a>
|
||||
<a href="{{ url_for('vehiculos_pdf', id=vehiculo['id']) }}"
|
||||
download class="btn btn-ghost btn-sm">
|
||||
<i class="bi bi-download"></i>
|
||||
Descargar
|
||||
</a>
|
||||
<form method="POST"
|
||||
action="{{ url_for('vehiculos_pdf_eliminar', id=vehiculo['id']) }}">
|
||||
<input type="hidden" name="_q" value="{{ q|default('') }}">
|
||||
<input type="hidden" name="_incl_bajas" value="{{ incl_bajas|default('0') }}">
|
||||
<button type="button" class="btn btn-danger btn-sm"
|
||||
onclick="if(confirm('¿Eliminar el documento PDF?')) this.closest('form').submit()">
|
||||
Eliminar PDF
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Reemplazar PDF existente #}
|
||||
<div style="margin-top:16px;padding-top:16px;border-top:1px solid var(--border);">
|
||||
<p style="font-size:11px;color:var(--muted);margin-bottom:10px;">
|
||||
Reemplazar documento actual:
|
||||
</p>
|
||||
<form method="POST"
|
||||
action="{{ url_for('vehiculos_upload', id=vehiculo['id']) }}"
|
||||
enctype="multipart/form-data"
|
||||
style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;">
|
||||
<input type="hidden" name="_q" value="{{ q|default('') }}">
|
||||
<input type="hidden" name="_incl_bajas" value="{{ incl_bajas|default('0') }}">
|
||||
<input type="hidden" name="_desde" value="{{ desde|default('') }}">
|
||||
<input type="hidden" name="_hasta" value="{{ hasta|default('') }}">
|
||||
<input type="hidden" name="_q_itv" value="{{ q_itv|default('') }}">
|
||||
<input type="hidden" name="_origen" value="{{ origen|default('') }}">
|
||||
<input type="hidden" name="_q_viat" value="{{ q_viat|default('') }}">
|
||||
<input type="file" name="pdf" accept=".pdf"
|
||||
style="font-size:13px;color:var(--text);">
|
||||
<button type="submit" class="btn btn-ghost btn-sm">
|
||||
<i class="bi bi-upload"></i>
|
||||
Reemplazar
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
{# Sin PDF - formulario de subida #}
|
||||
<form method="POST"
|
||||
action="{{ url_for('vehiculos_upload', id=vehiculo['id']) }}"
|
||||
enctype="multipart/form-data">
|
||||
<input type="hidden" name="_q" value="{{ q|default('') }}">
|
||||
<input type="hidden" name="_incl_bajas" value="{{ incl_bajas|default('0') }}">
|
||||
<input type="hidden" name="_desde" value="{{ desde|default('') }}">
|
||||
<input type="hidden" name="_hasta" value="{{ hasta|default('') }}">
|
||||
<input type="hidden" name="_q_itv" value="{{ q_itv|default('') }}">
|
||||
<input type="hidden" name="_origen" value="{{ origen|default('') }}">
|
||||
<input type="hidden" name="_q_viat" value="{{ q_viat|default('') }}">
|
||||
|
||||
<div style="border:2px dashed var(--border);border-radius:10px;padding:32px;
|
||||
text-align:center;transition:border-color .2s;"
|
||||
id="dropZone"
|
||||
ondragover="event.preventDefault();this.style.borderColor='var(--accent)'"
|
||||
ondragleave="this.style.borderColor='var(--border)'"
|
||||
ondrop="handleDrop(event)">
|
||||
<i class="bi bi-file-earmark-arrow-down" style="font-size: 2rem; color: gray;"></i>
|
||||
<p style="font-size:13px;color:var(--muted);margin-bottom:12px;">
|
||||
Arrastra un PDF aquí o pulsa para seleccionar
|
||||
</p>
|
||||
<input type="file" name="pdf" id="pdfInput" accept=".pdf"
|
||||
style="display:none;" onchange="mostrarArchivo(this)">
|
||||
<label for="pdfInput" class="btn btn-ghost" style="cursor:pointer;">
|
||||
Seleccionar PDF
|
||||
</label>
|
||||
<p id="pdfNombre" style="margin-top:10px;font-size:12px;color:var(--accent);display:none;"></p>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="btnSubir" class="btn btn-primary"
|
||||
style="margin-top:14px;display:none;">
|
||||
<i class="bi bi-upload"></i>
|
||||
Subir PDF
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %} -->
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
<div class="page-header">
|
||||
<div class="page-eyebrow">Base de datos</div>
|
||||
<div class="page-title">Vehículos</div>
|
||||
<div class="page-subtitle">Gestión del parque móvil. Búsqueda por m.oficial, m.civil, VIN, marca, modelo, poblacion, unidad, denominación, tipo, operacion, situacion.</div>
|
||||
<div class="page-title"><i class="bi bi-taxi-front"></i> Vehículos</div>
|
||||
<div class="page-subtitle">Gestión del parque móvil. Búsqueda por m.oficial, m.reservada, VIN, marca, modelo, poblacion, unidad, denominación, tipo, operacion, situacion, lanzadestellos.</div>
|
||||
</div>
|
||||
|
||||
<div class="crud-toolbar">
|
||||
@@ -100,7 +100,7 @@
|
||||
<tr>
|
||||
<!-- <th hidden>ID</th> -->
|
||||
<th>M. Oficial</th>
|
||||
<th>M. Civil</th>
|
||||
<th>M. Reservada</th>
|
||||
<th>Marca</th>
|
||||
<th>Modelo</th>
|
||||
<th>Poblacion</th>
|
||||
@@ -249,7 +249,7 @@
|
||||
var btn = this;
|
||||
|
||||
// Leer filas EN EL MOMENTO del click, no antes
|
||||
var heads = ['M. Oficial','M. Civil','Marca','Modelo','Poblacion','Unidad','Grupo','Denominación','Situación'];
|
||||
var heads = ['M. Oficial','M. Reservada','Marca','Modelo','Poblacion','Unidad','Grupo','Denominación','Situación'];
|
||||
var lineas = [heads.join('\t')];
|
||||
|
||||
document.querySelectorAll('#tbody tr[data-id]').forEach(function(tr) {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
onmouseout="this.style.background=''">
|
||||
<!-- <td hidden class="td-id">#{{ r['id'] }}</td> -->
|
||||
<td>{{ r['m_oficial'] or '—' }}</td>
|
||||
<td>{{ r['m_civil'] or '—' }}</td>
|
||||
<td>{{ r['m_reservada'] or '—' }}</td>
|
||||
<!-- <td>{{ r['vin'] or '—' }}</td> -->
|
||||
<td><strong>{{ r['marca'] or '—' }}</strong></td>
|
||||
<td>{{ r['modelo'] or '—' }}</td>
|
||||
@@ -25,17 +25,17 @@
|
||||
{{ r['situacion'] }}
|
||||
</span>
|
||||
{% else %}—{% endif %}
|
||||
{% if r['lanzadestellos'] == 1 %}
|
||||
<i class="bi bi-brightness-alt-high-fill" style="margin-left: 10px; font-size: 1rem; color: cornflowerblue;"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="10" rowspan="7">
|
||||
<div class="empty-state" style="color: rgb(202, 145, 145);">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M5 17H3a2 2 0 01-2-2V9a2 2 0 012-2h11l4 4v4a2 2 0 01-2 2h-1"/>
|
||||
<circle cx="7" cy="17" r="2"/><circle cx="17" cy="17" r="2"/>
|
||||
</svg>
|
||||
<div class="empty-state" style="color: rgb(194, 191, 191);">
|
||||
<i class="bi bi-car-front" style="font-size: 2rem;"></i>
|
||||
<br>
|
||||
{% if q %}
|
||||
No se encontraron resultados para <strong>"{{ q }}"</strong>.
|
||||
@@ -57,7 +57,7 @@
|
||||
<tr data-id="{{ r['id'] }}">
|
||||
<td class="td-id">#{{ r['id'] }}</td>
|
||||
<td>{{ r['m_oficial'] or '—' }}</td>
|
||||
<td>{{ r['m_civil'] or '—' }}</td>
|
||||
<td>{{ r['m_reservada'] or '—' }}</td>
|
||||
<td><strong>{{ r['marca'] or '—' }}</strong></td>
|
||||
<td>{{ r['modelo'] or '—' }}</td>
|
||||
<td>{{ r['tipo'] or '—' }}</td>
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<div class="page-eyebrow">Gestión</div>
|
||||
<div class="page-title">Via-T</div>
|
||||
<div class="page-subtitle">Listado de vehículos con dispositivo Via-T. Búsqueda por Via-T, m.oficial, m.civil, marca, modelo, poblacion, unidad tipo.</div>
|
||||
<div class="page-title"><i class="bi bi-broadcast"></i> Via-T</div>
|
||||
<div class="page-subtitle">Listado de vehículos con dispositivo Via-T. Búsqueda por Via-T, m.oficial, m.reservada, marca, modelo, poblacion, unidad, tipo.</div>
|
||||
</div>
|
||||
|
||||
<div class="crud-toolbar">
|
||||
@@ -67,7 +67,7 @@
|
||||
<tr>
|
||||
<th>Via-T</th>
|
||||
<th>M. Oficial</th>
|
||||
<th>M. Civil</th>
|
||||
<th>M. Reservada</th>
|
||||
<th>Marca</th>
|
||||
<th>Modelo</th>
|
||||
<th>Población</th>
|
||||
@@ -129,7 +129,7 @@
|
||||
document.getElementById('btnPDF').addEventListener('click', function() {
|
||||
var titulo = 'Listado Via-T';
|
||||
var fecha = new Date().toLocaleDateString('es-ES');
|
||||
var heads = ['Via-T','M. Oficial','M. Civil','Marca','Modelo','Población','Unidad','Tipo'];
|
||||
var heads = ['Via-T','M. Oficial','M. Reservada','Marca','Modelo','Población','Unidad','Tipo'];
|
||||
var filas = '';
|
||||
document.querySelectorAll('#tablaViat tbody tr[data-id]').forEach(function(tr) {
|
||||
filas += '<tr>';
|
||||
@@ -160,7 +160,7 @@
|
||||
/* ── COPIAR ── */
|
||||
document.getElementById('btnCopiar').addEventListener('click', function() {
|
||||
var btn = this;
|
||||
var heads = ['Via-T','M. Oficial','M. Civil','Marca','Modelo','Población','Unidad','Tipo'];
|
||||
var heads = ['Via-T','M. Oficial','M. Reservada','Marca','Modelo','Población','Unidad','Tipo'];
|
||||
var lineas = [heads.join('\t')];
|
||||
document.querySelectorAll('#tablaViat tbody tr[data-id]').forEach(function(tr) {
|
||||
var fila = [];
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
onmouseout="this.style.background=''">
|
||||
<td><strong>{{ r['viat'] or '—' }}</strong></td>
|
||||
<td>{{ r['m_oficial'] or '—' }}</td>
|
||||
<td>{{ r['m_civil'] or '—' }}</td>
|
||||
<td>{{ r['m_reservada'] or '—' }}</td>
|
||||
<td>{{ r['marca'] or '—' }}</td>
|
||||
<td>{{ r['modelo'] or '—' }}</td>
|
||||
<td>{{ r['poblacion'] or '—' }}</td>
|
||||
|
||||
Reference in New Issue
Block a user