modalized edit worker + global css
This commit is contained in:
5
app.py
5
app.py
@@ -290,7 +290,7 @@ def manage_workers():
|
|||||||
flash("El RUT ya existe en el sistema.", "danger")
|
flash("El RUT ya existe en el sistema.", "danger")
|
||||||
|
|
||||||
# Fetch workers and JOIN their module name
|
# Fetch workers and JOIN their module name
|
||||||
c.execute('''SELECT w.id, w.rut, w.name, w.phone, m.name
|
c.execute('''SELECT w.id, w.rut, w.name, w.phone, m.name, w.modulo_id
|
||||||
FROM workers w
|
FROM workers w
|
||||||
LEFT JOIN modulos m ON w.modulo_id = m.id
|
LEFT JOIN modulos m ON w.modulo_id = m.id
|
||||||
WHERE w.is_admin = 0''')
|
WHERE w.is_admin = 0''')
|
||||||
@@ -378,7 +378,8 @@ def admin_reset_password(id):
|
|||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
flash(f"Contraseña de {worker[0]} restablecida. La nueva contraseña es: <strong>{new_password}</strong>", "warning")
|
flash(f"Contraseña de {worker[0]} restablecida. La nueva contraseña es: <strong>{new_password}</strong>", "warning")
|
||||||
return redirect(url_for('edit_worker', id=id))
|
|
||||||
|
return redirect(url_for('manage_workers'))
|
||||||
|
|
||||||
|
|
||||||
@app.route('/admin/estructura', methods=['GET', 'POST'])
|
@app.route('/admin/estructura', methods=['GET', 'POST'])
|
||||||
|
|||||||
12
static/style.css
Normal file
12
static/style.css
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
[data-bs-theme="dark"] .form-control[readonly] {
|
||||||
|
background-color: #1a1d21; /* Un fondo casi negro, más oscuro que el modal */
|
||||||
|
border-color: #373b3e; /* Un borde sutil */
|
||||||
|
color: #e3e6e8; /* Texto blanco grisáceo, muy legible */
|
||||||
|
opacity: 1; /* Evita la opacidad predeterminada de Bootstrap */
|
||||||
|
cursor: not-allowed; /* Refuerza visualmente que no es editable */
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .text-muted-rut {
|
||||||
|
color: #adb5bd !important; /* Un gris medio para el label "RUT (No editable)" */
|
||||||
|
}
|
||||||
@@ -7,7 +7,6 @@
|
|||||||
<!-- HEAD -->
|
<!-- HEAD -->
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2 class="mb-4">Catálogo de Productos por Zona</h2>
|
<h2 class="mb-4">Catálogo de Productos por Zona</h2>
|
||||||
|
|
||||||
@@ -148,9 +147,7 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
document.querySelectorAll('.money-input').forEach(function(input) {
|
document.querySelectorAll('.money-input').forEach(function(input) {
|
||||||
input.addEventListener('input', function(e) {
|
input.addEventListener('input', function(e) {
|
||||||
let value = this.value.replace(/\D/g, '');
|
let value = this.value.replace(/\D/g, '');
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{% extends "macros/base.html" %}
|
{% extends "macros/base.html" %}
|
||||||
|
{% from 'macros/modals.html' import confirm_modal, edit_worker_modal %}
|
||||||
|
|
||||||
{% block title %}Gestión de Trabajadores{% endblock %}
|
{% block title %}Gestión de Trabajadores{% endblock %}
|
||||||
|
|
||||||
@@ -18,6 +18,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
|
{{ edit_worker_modal(modulos) }}
|
||||||
|
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-header bg-secondary text-white">Agregar Nuevo Trabajador</div>
|
<div class="card-header bg-secondary text-white">Agregar Nuevo Trabajador</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@@ -70,10 +72,28 @@
|
|||||||
<td class="align-middle">{{ worker[3] }}</td>
|
<td class="align-middle">{{ worker[3] }}</td>
|
||||||
<td class="align-middle"><span class="badge bg-info text-dark">{{ worker[4] }}</span></td>
|
<td class="align-middle"><span class="badge bg-info text-dark">{{ worker[4] }}</span></td>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
<a href="{{ url_for('edit_worker', id=worker[0]) }}" class="btn btn-sm btn-outline-info">Editar</a>
|
<button type="button"
|
||||||
<form action="{{ url_for('delete_worker', id=worker[0]) }}" method="POST" class="d-inline">
|
class="btn btn-sm btn-outline-info"
|
||||||
<button type="submit" class="btn btn-sm btn-outline-danger" onclick="return confirm('¿Eliminar a este trabajador?');">Eliminar</button>
|
data-bs-toggle="modal"
|
||||||
</form>
|
data-bs-target="#editWorkerModal"
|
||||||
|
data-id="{{ worker[0] }}"
|
||||||
|
data-rut="{{ worker[1] }}"
|
||||||
|
data-name="{{ worker[2] }}"
|
||||||
|
data-phone="{{ worker[3] }}"
|
||||||
|
data-modulo="{{ worker[5] }}"> Editar
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-danger" data-bs-toggle="modal" data-bs-target="#delWorker{{ worker[0] }}">
|
||||||
|
Eliminar
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{{ confirm_modal(
|
||||||
|
id='delWorker' ~ worker[0],
|
||||||
|
title='Eliminar Trabajador',
|
||||||
|
message='¿Eliminar a ' ~ worker[2] ~ '?',
|
||||||
|
action_url=url_for('delete_worker', id=worker[0]),
|
||||||
|
btn_class='btn-danger'
|
||||||
|
) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -87,6 +107,27 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
const editWorkerModal = document.getElementById('editWorkerModal');
|
||||||
|
if (editWorkerModal) {
|
||||||
|
editWorkerModal.addEventListener('show.bs.modal', function (event) {
|
||||||
|
const button = event.relatedTarget;
|
||||||
|
const id = button.getAttribute('data-id');
|
||||||
|
|
||||||
|
// IMPORTANTE: Definir las rutas correctamente
|
||||||
|
const editForm = editWorkerModal.querySelector('#editWorkerForm');
|
||||||
|
const resetForm = editWorkerModal.querySelector('#resetPasswordForm');
|
||||||
|
|
||||||
|
editForm.action = "/admin/workers/edit/" + id;
|
||||||
|
resetForm.action = "/admin/workers/reset_password/" + id;
|
||||||
|
|
||||||
|
// Rellenar campos
|
||||||
|
editWorkerModal.querySelector('#edit_worker_rut').value = button.getAttribute('data-rut');
|
||||||
|
editWorkerModal.querySelector('#edit_worker_name').value = button.getAttribute('data-name');
|
||||||
|
editWorkerModal.querySelector('#edit_worker_phone').value = button.getAttribute('data-phone');
|
||||||
|
editWorkerModal.querySelector('#edit_worker_modulo').value = button.getAttribute('data-modulo');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('rutInput').addEventListener('input', function(e) {
|
document.getElementById('rutInput').addEventListener('input', function(e) {
|
||||||
let value = this.value.replace(/[^0-9kK]/g, '').toUpperCase();
|
let value = this.value.replace(/[^0-9kK]/g, '').toUpperCase();
|
||||||
if (value.length > 1) {
|
if (value.length > 1) {
|
||||||
@@ -99,18 +140,17 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('phoneInput').addEventListener('input', function(e) {
|
function formatPhone(input) {
|
||||||
let value = this.value.replace(/\D/g, '');
|
let value = input.value.replace(/\D/g, '');
|
||||||
if (value.startsWith('56')) value = value.substring(2);
|
if (value.startsWith('56')) value = value.substring(2);
|
||||||
value = value.substring(0, 9);
|
value = value.substring(0, 9);
|
||||||
|
if (value.length > 5) input.value = value.replace(/(\d{1})(\d{4})(\d+)/, '$1 $2 $3');
|
||||||
|
else if (value.length > 1) input.value = value.replace(/(\d{1})(\d+)/, '$1 $2');
|
||||||
|
else input.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
if (value.length > 5) {
|
document.querySelectorAll('.phone-input, #phoneInput').forEach(inp => {
|
||||||
this.value = value.replace(/(\d{1})(\d{4})(\d+)/, '$1 $2 $3');
|
inp.addEventListener('input', () => formatPhone(inp));
|
||||||
} else if (value.length > 1) {
|
|
||||||
this.value = value.replace(/(\d{1})(\d+)/, '$1 $2');
|
|
||||||
} else {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
{% extends "macros/base.html" %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block title %}Editar Trabajador{% endblock %}
|
|
||||||
|
|
||||||
{% block head %}
|
|
||||||
<!-- HEAD -->
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-md-8">
|
|
||||||
<h2 class="mb-4">Editar Trabajador</h2>
|
|
||||||
|
|
||||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
||||||
{% if messages %}
|
|
||||||
{% for category, message in messages %}
|
|
||||||
<div class="alert alert-{{ category }}">{{ message|safe }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
|
|
||||||
<div class="card mb-4">
|
|
||||||
<div class="card-body">
|
|
||||||
<form method="POST">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">RUT</label>
|
|
||||||
<input type="text" class="form-control text-muted" value="{{ worker[1] }}" readonly disabled>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">Nombre Completo</label>
|
|
||||||
<input type="text" class="form-control" name="name" value="{{ worker[2] }}" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-4">
|
|
||||||
<label class="form-label">Teléfono</label>
|
|
||||||
<input type="text" class="form-control" name="phone" id="phoneInput" value="{{ worker[3] }}" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-4">
|
|
||||||
<label class="form-label">Módulo Asignado</label>
|
|
||||||
<select class="form-select" name="modulo_id" required>
|
|
||||||
<option value="" disabled>Seleccionar Módulo...</option>
|
|
||||||
{% for mod in modulos %}
|
|
||||||
<option value="{{ mod[0] }}" {% if mod[0] == worker[4] %}selected{% endif %}>
|
|
||||||
{{ mod[2] }} - {{ mod[1] }}
|
|
||||||
</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-between">
|
|
||||||
<a href="{{ url_for('manage_workers') }}" class="btn btn-secondary">Cancelar</a>
|
|
||||||
<button type="submit" class="btn btn-primary">Actualizar Datos</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card border-warning">
|
|
||||||
<div class="card-header bg-warning text-dark border-warning">
|
|
||||||
<strong>Opciones de Seguridad</strong>
|
|
||||||
</div>
|
|
||||||
<div class="card-body d-flex justify-content-between align-items-center">
|
|
||||||
<div>
|
|
||||||
<h6 class="mb-1">Restablecer Contraseña</h6>
|
|
||||||
<small class="text-muted">Genera una nueva contraseña aleatoria si el trabajador olvidó la suya.</small>
|
|
||||||
</div>
|
|
||||||
<form action="{{ url_for('admin_reset_password', id=worker[0]) }}" method="POST">
|
|
||||||
<button type="submit" class="btn btn-outline-warning" onclick="return confirm('¿Generar nueva contraseña? La anterior dejará de funcionar inmediatamente.');">
|
|
||||||
Generar Nueva
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
document.getElementById('phoneInput').addEventListener('input', function(e) {
|
|
||||||
let value = this.value.replace(/\D/g, '');
|
|
||||||
if (value.startsWith('56')) value = value.substring(2);
|
|
||||||
value = value.substring(0, 9);
|
|
||||||
|
|
||||||
if (value.length > 5) {
|
|
||||||
this.value = value.replace(/(\d{1})(\d{4})(\d+)/, '$1 $2 $3');
|
|
||||||
} else if (value.length > 1) {
|
|
||||||
this.value = value.replace(/(\d{1})(\d+)/, '$1 $2');
|
|
||||||
} else {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
|
||||||
document.getElementById('phoneInput').dispatchEvent(new Event('input'));
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Sistema de Rendiciones - {% block title %}{% endblock %}</title>
|
<title>Sistema de Rendiciones - {% block title %}{% endblock %}</title>
|
||||||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.png') }}" type="image/x-icon">
|
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.png') }}" type="image/x-icon">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -62,3 +62,57 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro edit_worker_modal(modulos) %}
|
||||||
|
<div class="modal fade" id="editWorkerModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Editar Trabajador</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="editWorkerForm" method="POST" action="">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label text-muted-rut">RUT (No editable)</label>
|
||||||
|
<input type="text" class="form-control" id="edit_worker_rut" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Nombre Completo</label>
|
||||||
|
<input type="text" class="form-control" name="name" id="edit_worker_name" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Teléfono</label>
|
||||||
|
<input type="text" class="form-control phone-input" name="phone" id="edit_worker_phone" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Módulo Asignado</label>
|
||||||
|
<select class="form-select" name="modulo_id" id="edit_worker_modulo" required>
|
||||||
|
{% for mod in modulos %}
|
||||||
|
<option value="{{ mod[0] }}">{{ mod[2] }} - {{ mod[1] }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="d-grid">
|
||||||
|
<form id="resetPasswordForm" method="POST" action="" onsubmit="return confirm('¿Generar nueva contraseña?');">
|
||||||
|
<button type="submit" class="btn btn-outline-warning btn-sm w-100">
|
||||||
|
<i class="bi bi-key me-1"></i> Restablecer Contraseña
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<small class="text-muted text-center mt-1">Generará una nueva clave aleatoria.</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||||
|
<button type="submit" form="editWorkerForm" class="btn btn-primary">Guardar Cambios</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
Reference in New Issue
Block a user