versión casi final
This commit is contained in:
parent
ff235ca40b
commit
e43e6ed2b2
@ -87,19 +87,3 @@ CREATE TABLE carousel (
|
||||
);
|
||||
|
||||
ALTER DATABASE formha SET timezone TO 'America/Mexico_City';
|
||||
|
||||
|
||||
SELECT
|
||||
CASE source_name
|
||||
WHEN 'li' THEN 'LinkedIn'
|
||||
WHEN 'wa' THEN 'WhatsApp'
|
||||
WHEN 'fb' THEN 'Facebook'
|
||||
WHEN 'x' THEN 'X'
|
||||
ELSE 'Otro'
|
||||
END AS nombre_legible,
|
||||
COUNT(source_name) AS total
|
||||
FROM visited_from
|
||||
GROUP BY source_name
|
||||
ORDER BY total DESC;
|
||||
|
||||
-- select * from visited_from;
|
Binary file not shown.
@ -50,7 +50,7 @@ class Carousel(FlaskForm):
|
||||
'Imagen de fondo: ',
|
||||
validators=[
|
||||
DataRequired(),
|
||||
FileAllowed(['jpg', 'jpeg', 'png', 'mp4', 'avif', 'webp'], 'Solo se permiten archivos .jpg, .png o .mp4')
|
||||
FileAllowed(['jpg', 'jpeg', 'png', 'mp4', 'avif', 'webp'], 'Solo se permiten archivos: .jpg, jpeg, mp4, avif o webp')
|
||||
]
|
||||
)
|
||||
|
||||
|
103
main.py
103
main.py
@ -968,6 +968,9 @@ def manage_profiles():
|
||||
t = None
|
||||
subject = None
|
||||
html_content = None
|
||||
|
||||
r_str_isAdmin = 'Sí' if f_isAdmin == 'TRUE' else 'No'
|
||||
r_str_isContactNoti = 'Sí' if f_isContactNoti == 'TRUE' else 'No'
|
||||
|
||||
# si el f_id no es igual a '' entonces es una actualización de datos
|
||||
if f_id != '':
|
||||
@ -976,13 +979,46 @@ def manage_profiles():
|
||||
f_mnsj = l_flash_msj('Éxito', f'Usuario actualizado: {f_nombre}', 'success')
|
||||
subject = "Usuario actualizado"
|
||||
html_content = """
|
||||
<h1>¡Hola!</h1>
|
||||
<p>Tu cuenta ha sido actualizada con éxito.</p>
|
||||
<p><strong>Nombre:</strong> {}</p>
|
||||
<p><strong>Apellido:</strong> {}</p>
|
||||
<p><strong>Género:</strong> {}</p>
|
||||
<p><strong>Email:</strong> {}</p>
|
||||
""".format(f_nombre, f_apellido, f_genero, f_email)
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="font-family: Arial, sans-serif; background-color: #f4f4f4; padding: 20px;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 8px; padding: 40px;">
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom: 20px;">
|
||||
<img src="https://formha.temporal.work/static/y_img/logos/formha_blanco_vertical.png" alt="Logo Formha" style="width: 150px; height: auto;">
|
||||
<p style="color: #4a5568; font-size: 16px; margin-top: 10px;"><strong>Tu cuenta ha sido actualizada con éxito.</strong></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="color: #2d3748; font-size: 15px; line-height: 1.6;">
|
||||
<p>
|
||||
<strong>Nombre:</strong> {} <br>
|
||||
<strong>Apellido:</strong> {}<br>
|
||||
<strong>Género:</strong> {} <br>
|
||||
<strong>Email:</strong> {} <br>
|
||||
<strong>Permisos de Admin:</strong> {}<br>
|
||||
<strong>Notificaciones por email:</strong> {}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="padding-top: 30px;">
|
||||
<a href="https://formha.temporal.work/login" style="background-color: #4299e1; color: #ffffff; text-decoration: none; padding: 12px 24px; border-radius: 5px; font-weight: bold; display: inline-block;">
|
||||
Ir al sitio Formha
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="padding-top: 30px; font-size: 12px; color: #a0aec0;">
|
||||
Si no realizaste esta actualización, por favor contacta con el soporte técnico.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
""".format(f_nombre, f_apellido, f_genero, f_email, r_str_isAdmin, r_str_isContactNoti)
|
||||
|
||||
|
||||
# en caso de que el f_id = '' quiere decir que es un nuevo registro y debo checar que no sea un registro preexiste a partir del email
|
||||
else:
|
||||
@ -999,20 +1035,49 @@ def manage_profiles():
|
||||
random_id = getRandomId()
|
||||
tmp_pswd = generar_contrasena()
|
||||
hashed_pswd = hash_password(tmp_pswd)
|
||||
q = "INSERT INTO users (id, nombre, apellido, genero, email, pswd, is_admin, is_pswd_reseted ) values (%s, %s, %s, %s, %s, %s, %s, %s);"
|
||||
t = (random_id, f_nombre, f_apellido, f_genero, f_email, hashed_pswd, f_isAdmin, True)
|
||||
q = "INSERT INTO users (id, nombre, apellido, genero, email, pswd, is_admin, is_pswd_reseted, is_contact_noti ) values (%s, %s, %s, %s, %s, %s, %s, %s, %s);"
|
||||
t = (random_id, f_nombre, f_apellido, f_genero, f_email, hashed_pswd, f_isAdmin, True, f_isContactNoti)
|
||||
f_mnsj = l_flash_msj('Éxito', f'Usuario creado: {f_nombre}', 'success')
|
||||
|
||||
subject = "Nueva cuenta creada"
|
||||
html_content = """
|
||||
<h1>¡Bienvenido a Forma!</h1>
|
||||
<p>Tu cuenta ha sido creada con éxito.</p>
|
||||
<p><strong>Nombre:</strong> {}</p>
|
||||
<p><strong>Apellido:</strong> {}</p>
|
||||
<p><strong>Género:</strong> {}</p>
|
||||
<p><strong>Email:</strong> {}</p>
|
||||
<p><strong>Contraseña temporal:</strong> {}</p>
|
||||
<p><strong>Una vez inicies sesión debes de cambiar la contraseña a una nueva que puedas recordar</strong></p>
|
||||
""".format(f_nombre, f_apellido, f_genero, f_email, tmp_pswd)
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="font-family: Arial, sans-serif; background-color: #f4f4f4; padding: 20px;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 8px; padding: 40px;">
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom: 20px;">
|
||||
<img src="https://formha.temporal.work/static/y_img/logos/formha_blanco_vertical.png" alt="Logo Formha" style="width: 150px; height: auto;">
|
||||
<h1 style="color: #2c5282; margin: 0;">¡Bienvenido a <span style="color:#4299e1;">Formha</span>!</h1>
|
||||
<p style="color: #4a5568; font-size: 16px; margin-top: 10px;">Tu cuenta ha sido creada con éxito.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="color: #2d3748; font-size: 15px; line-height: 1.6;">
|
||||
<p>
|
||||
<strong>Nombre:</strong> {}<br>
|
||||
<strong>Apellido:</strong> {}<br>
|
||||
<strong>Género:</strong> {}<br>
|
||||
<strong>Email:</strong> {}<br>
|
||||
<strong>Contraseña temporal:</strong> <code style="background-color:#edf2f7; padding: 4px 8px; border-radius: 4px;">{}</code><br>
|
||||
<strong>Permisos de Admin:</strong> {}<br>
|
||||
<strong>Notificaciones por email:</strong> {}
|
||||
</p>
|
||||
<p style="margin-top: 20px;"><strong style="color: #e53e3e;">Importante:</strong> Una vez inicies sesión, debes cambiar la contraseña por una nueva que recuerdes.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="padding-top: 30px;">
|
||||
<a href="https://formha.temporal.work/login" style="background-color: #4299e1; color: #ffffff; text-decoration: none; padding: 12px 24px; border-radius: 5px; font-weight: bold; display: inline-block;">
|
||||
Ir al sitio Formha
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
""".format(f_nombre, f_apellido, f_genero, f_email, tmp_pswd, r_str_isAdmin, r_str_isContactNoti)
|
||||
|
||||
# Crear el mensaje de correo con todo el contenido
|
||||
msg = Message(
|
||||
@ -1143,7 +1208,7 @@ def carousel():
|
||||
flash(mnsj_flash)
|
||||
return redirect(url_for('carousel'))
|
||||
|
||||
return render_template(v['tmp_user']['carousel'], form=form, data=data, active_page='metrics')
|
||||
return render_template(v['tmp_user']['carousel'], form=form, data=data, active_page='carousel')
|
||||
|
||||
|
||||
@app.route('/user/carousel/delete-slide/<int:id>', methods=["POST", "DELETE"])
|
||||
|
BIN
static/y_img/logos/chat_ia_formha.png
Normal file
BIN
static/y_img/logos/chat_ia_formha.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 136 KiB |
@ -8,19 +8,120 @@
|
||||
<link rel="stylesheet" href="https://htmlguyllc.github.io/jConfirm/jConfirm.min.css">
|
||||
<!-- {# f jconfirm #} -->
|
||||
|
||||
|
||||
<!-- <link rel="stylesheet" href="{{ url_for('static', filename='f_contact/form.css') }}"> -->
|
||||
|
||||
|
||||
|
||||
|
||||
{% endblock css %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
|
||||
<style>
|
||||
/* Smartphones (hasta 767px) */
|
||||
@media (max-width: 767px) {
|
||||
/* main{ background-color: black; } */
|
||||
main { min-height: 80dvh; }
|
||||
}
|
||||
|
||||
/* Tablets (768px - 1023px) */
|
||||
@media (min-width: 768px) and (max-width: 1023px) {
|
||||
/* main{ background-color: pink; } */
|
||||
main { min-height: 80dvh; }
|
||||
}
|
||||
|
||||
/* Laptops (1024px - 1439px) monitores resulición baja */
|
||||
@media (min-width: 1024px) and (max-width: 1439px) {
|
||||
/* main{ background-color: purple; } */
|
||||
main { min-height: 80dvh; }
|
||||
}
|
||||
|
||||
/* PCs de escritorio (1440px - 1919px) macbook */
|
||||
@media (min-width: 1440px) and (max-width: 1919px) {
|
||||
/* main{ background-color: greenyellow; } */
|
||||
main { min-height: 80dvh; }
|
||||
}
|
||||
|
||||
/* Pantallas Ultrawide (1920px en adelante) */
|
||||
@media (min-width: 1920px) {
|
||||
/* main{ background-color: red; } */
|
||||
main { min-height: 80dvh; }
|
||||
}
|
||||
|
||||
#dt-length-0 {
|
||||
width: 6em;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
/* Estilos generales de la tabla (Bootstrap ya maneja gran parte de esto) */
|
||||
#tblUsers {
|
||||
/* Asegúrate de que no haya un min-width fijo que rompa la responsividad */
|
||||
width: 100% !important; /* !important para asegurar que sobreescribe estilos de DataTables/Bootstrap si es necesario */
|
||||
}
|
||||
|
||||
/* Ocultar las cabeceras de la tabla en pantallas pequeñas */
|
||||
@media screen and (max-width: 768px) {
|
||||
#tblUsers thead {
|
||||
display: none; /* Oculta las cabeceras en móviles */
|
||||
}
|
||||
|
||||
#tblUsers,
|
||||
#tblUsers tbody,
|
||||
#tblUsers tr,
|
||||
#tblUsers td {
|
||||
display: block; /* Hace que todos los elementos de la tabla se comporten como bloques */
|
||||
width: 100%; /* Ocupan todo el ancho disponible */
|
||||
}
|
||||
|
||||
#tblUsers tr {
|
||||
margin-bottom: 1rem; /* Espacio entre las "tarjetas" (filas) */
|
||||
border: 1px solid #dee2e6; /* Borde para simular la tarjeta */
|
||||
border-radius: 0.5rem; /* Bordes redondeados */
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); /* Sombra suave */
|
||||
padding: 1rem; /* Espacio interno de la tarjeta */
|
||||
box-sizing: border-box; /* Incluir padding y borde en el ancho total */
|
||||
}
|
||||
|
||||
#tblUsers td {
|
||||
border: none; /* Elimina los bordes de las celdas individuales */
|
||||
position: relative; /* Necesario para posicionar el data-label */
|
||||
padding-left: 50% !important; /* Espacio para el data-label */
|
||||
text-align: left !important; /* Asegura que el texto se alinee a la izquierda */
|
||||
white-space: normal; /* Permite que el texto se ajuste y no esté en una sola línea */
|
||||
}
|
||||
|
||||
#tblUsers td::before {
|
||||
/* Muestra el contenido del data-label como pseudo-elemento */
|
||||
content: attr(data-label);
|
||||
position: absolute;
|
||||
left: 0.5rem; /* Posición del label */
|
||||
width: 45%; /* Ancho del label */
|
||||
padding-right: 1rem;
|
||||
white-space: nowrap; /* Evita que el label se rompa */
|
||||
font-weight: bold;
|
||||
text-align: right; /* Alinea el label a la derecha dentro de su espacio */
|
||||
color: #495057; /* Color para el label */
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div>
|
||||
<h1>Administrar Perfiles</h1>
|
||||
<!-- Botón para abrir el modal -->
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalFormAddUsr">
|
||||
<i class="bi bi-person-fill-add"></i> Añadir Usuario
|
||||
</button>
|
||||
|
||||
<table id="tblUsers" class="table table-striped" style="width:100%" data-aos="fade-up" data-aos-delay="0"
|
||||
|
||||
<table id="tblUsers" class="table table-striped" style="width:100%" data-aos="fade-up" data-aos-delay="0"
|
||||
data-aos-duration="800">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -30,21 +131,21 @@
|
||||
<th>Última Conexión</th>
|
||||
<th>Admin</th>
|
||||
<th>Posts</th>
|
||||
<th>Notificiones Contactos</th>
|
||||
<th>Notificaciones Email</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for ele in data_all_users %}
|
||||
<tr data-id="{{ele[0]}}">
|
||||
<td>{{ ele[1] }}</td>
|
||||
<td>{{ ele[2] }}</td>
|
||||
<td>{{ ele[3] }}</td>
|
||||
<td>{{ ele[4] }}</td>
|
||||
<td>{{ ele[5] }}</td>
|
||||
<td>{{ ele[6] }}</td>
|
||||
<td>{{ ele[7] }}</td>
|
||||
<td>
|
||||
<td data-label="Nombre">{{ ele[1] }}</td>
|
||||
<td data-label="Apellidos">{{ ele[2] }}</td>
|
||||
<td data-label="Email">{{ ele[3] }}</td>
|
||||
<td data-label="Últ. Conexión">{{ ele[4] }}</td>
|
||||
<td data-label="Admin">{{ ele[5] }}</td>
|
||||
<td data-label="Posts">{{ ele[6] }}</td>
|
||||
<td data-label="Notif. Email">{{ ele[7] }}</td>
|
||||
<td data-label="Acciones"> {# Mantén data-label para Acciones también #}
|
||||
<a href="" class="btn btn-primary" data-bs-toggle="modal" data-id="{{ele[0]}}"
|
||||
data-bs-target="#modalFormAddUsr">
|
||||
<i class="bi bi-pencil-square"></i>
|
||||
@ -59,11 +160,12 @@
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- Modal Bootstrap 5 -->
|
||||
<div class="modal fade" id="modalFormAddUsr" tabindex="-1" aria-labelledby="modalFormAddUsrLabel" aria-hidden="true" >
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
|
||||
@ -74,7 +176,7 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- {# i form add user #} -->
|
||||
<form method="POST" action="{{ url_for('manage_profiles') }}" class="login-form">
|
||||
<form method="POST" action="{{ url_for('manage_profiles') }}" class="form-container">
|
||||
{{ form.hidden_tag() }}
|
||||
{{ form.id }}
|
||||
|
||||
@ -125,6 +227,14 @@
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{% endblock body %}
|
||||
|
||||
{% block js %}
|
||||
@ -307,32 +417,16 @@ document.getElementById('tblUsers').addEventListener('click', (event) => {
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// new DataTable('#tblUsers');
|
||||
|
||||
new DataTable('#tblUsers', {
|
||||
initComplete: function() {
|
||||
// Agrega campos de filtro para cada columna
|
||||
this.api().columns().every(function() {
|
||||
let column = this;
|
||||
let header = $(column.header());
|
||||
let title = header.text().trim();
|
||||
|
||||
// Excluir la columna "Estatus" del filtro
|
||||
if (title !== 'Acciones') {
|
||||
// Crea input de filtro
|
||||
header.append('<div class="filter"><input type="text" class="form-control" placeholder="'+title+'" /></div>');
|
||||
|
||||
// Aplica el filtro al escribir
|
||||
$('input', header)
|
||||
.on('keyup change', function() {
|
||||
if (column.search() !== this.value) {
|
||||
column.search(this.value).draw();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
$(document).ready(function() {
|
||||
$('#tblUsers').DataTable({
|
||||
// Puedes añadir tus opciones de DataTables aquí
|
||||
"language": {
|
||||
url: "https://cdn.datatables.net/plug-ins/1.13.6/i18n/es-ES.json" // Idioma español
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
@ -341,9 +435,7 @@ document.getElementById('tblUsers').addEventListener('click', (event) => {
|
||||
{% include 'z_comps/if_flash.html' %}
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
@ -216,7 +216,7 @@
|
||||
|
||||
/* Manejo específico de columnas con contenido largo */
|
||||
#tblCarousel td[data-label="Archivo"],
|
||||
#tblCarousel td[data-label="Texto"] {
|
||||
#tblCarousel td[data-label="Metadatos"] {
|
||||
white-space: normal;
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
@ -229,59 +229,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
<style>
|
||||
/* formulario */
|
||||
/* Ajustes para móviles */
|
||||
@media (max-width: 576px) {
|
||||
.form-control-color {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
.input-group-text {
|
||||
font-size: 0.8rem;
|
||||
padding: 0.375rem 0.5rem;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mejora para los contadores de caracteres */
|
||||
.form-text {
|
||||
text-align: right;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* Estilo para los inputs de color */
|
||||
.input-group .form-control-color {
|
||||
flex: 0 0 auto;
|
||||
width: 3.5rem;
|
||||
}
|
||||
#dt-length-0 {
|
||||
width: 6em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="home-tab" data-bs-toggle="tab" data-bs-target="#home" type="button" role="tab"
|
||||
@ -304,10 +265,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Creado</th>
|
||||
<th>Archivo</th>
|
||||
<th>New Tab</th>
|
||||
<th>Texto</th>
|
||||
<th>Metadatos</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -315,12 +273,12 @@
|
||||
{% for ele in data %}
|
||||
<tr data-id="{{ ele[0] }}">
|
||||
<td data-label="ID">{{ loop.index }}</td>
|
||||
<td data-label="Creado">{{ ele[5] }}</td>
|
||||
<td data-label="Archivo">{{ ele[1] }}</td>
|
||||
<td data-label="New Tab">{% if ele[7] %}✔️{% else %}❌{% endif %}</td>
|
||||
<td data-label="Texto" style="background-color: {{ ele[2] }}; color: {{ ele[3] }};">
|
||||
{{ ele[4] }}
|
||||
|
||||
|
||||
<td data-label="Metadatos" style="background-color: {{ ele[2] }}; color: {{ ele[3] }};">
|
||||
Texto {{ ele[4][:60] }}... <br>
|
||||
Fecha creado: {{ ele[5] }} <br>
|
||||
Archivo: {{ ele[1] }} <br>
|
||||
New Tab: {% if ele[7] %}✔️{% else %}❌{% endif %}
|
||||
</td>
|
||||
<td data-label="Acciones">
|
||||
<div class="field_btns">
|
||||
@ -350,116 +308,117 @@
|
||||
<h2>Agregar Slide</h2>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ url_for('carousel') }}" enctype="multipart/form-data" class="needs-validation" novalidate>
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
<!-- Fila 1: Campos de imagen y colores -->
|
||||
<div class="row">
|
||||
<!-- Input imagen - Ocupa todo el ancho en móviles, mitad en pantallas medianas/grandes -->
|
||||
<div class="col-12 col-md-6 mb-3">
|
||||
{{ form.img.label(class="form-label fw-bold") }}
|
||||
{{ form.img(class="form-control") }}
|
||||
<div class="invalid-feedback">
|
||||
Por favor selecciona un archivo válido.
|
||||
</div>
|
||||
<small class="form-text text-muted">
|
||||
Formatos soportados: JPG, PNG, GIF, MP4 (max 5MB)
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!-- bg color picker - Apila en móviles, 2 por fila en pantallas medianas/grandes -->
|
||||
<div class="col-6 col-md-3 mb-3">
|
||||
{{ form.bg_color.label(class="form-label fw-bold") }}
|
||||
<div class="input-group">
|
||||
{{ form.bg_color(type="color", class="form-control form-control-color", value="#ffffff") }}
|
||||
<span class="input-group-text">{{ form.bg_color.data }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- txt color picker -->
|
||||
<div class="col-6 col-md-3 mb-3">
|
||||
{{ form.txt_color.label(class="form-label fw-bold") }}
|
||||
<div class="input-group">
|
||||
{{ form.txt_color(type="color", class="form-control form-control-color", value="#000000") }}
|
||||
<span class="input-group-text">{{ form.txt_color.data }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fila 2: Campos de texto y URL -->
|
||||
<div class="row">
|
||||
<!-- txt - Ocupa todo el ancho en móviles, 2/3 en pantallas medianas/grandes -->
|
||||
<div class="col-12 col-md-8 mb-3">
|
||||
{{ form.txt.label(class="form-label fw-bold") }}
|
||||
{{ form.txt(class="form-control", maxlength="350", rows="3", placeholder="Texto que aparecerá en el slide") }}
|
||||
<div class="form-text">
|
||||
<span id="txt-counter">0</span>/350 caracteres
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- url - Ocupa todo el ancho en móviles, 1/3 en pantallas medianas/grandes -->
|
||||
<div class="col-12 col-md-4 mb-3">
|
||||
{{ form.url.label(class="form-label fw-bold") }}
|
||||
{{ form.url(class="form-control", maxlength="250", placeholder="https://ejemplo.com") }}
|
||||
<div class="form-text">
|
||||
<span id="url-counter">0</span>/250 caracteres
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Switch para new tab -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="form-check form-switch">
|
||||
{{ form.isNewTab(class="form-check-input", type="checkbox", role="switch") }}
|
||||
{{ form.isNewTab.label(class="form-check-label fw-bold") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Botón de submit -->
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<button type="submit" class="btn btn-primary btn-lg">
|
||||
<i class="bi bi-database-fill-up me-2"></i>Guardar Slide
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<form method="POST" action="{{ url_for('carousel') }}" enctype="multipart/form-data" class="needs-validation"
|
||||
novalidate>
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
<!-- Fila 1: Campos de imagen y colores -->
|
||||
<div class="row">
|
||||
<!-- Input imagen - Ocupa todo el ancho en móviles, mitad en pantallas medianas/grandes -->
|
||||
<div class="col-12 col-md-6 mb-3">
|
||||
{{ form.img.label(class="form-label fw-bold") }}
|
||||
{{ form.img(class="form-control") }}
|
||||
<div class="invalid-feedback">
|
||||
Por favor selecciona un archivo válido.
|
||||
</div>
|
||||
<small class="form-text text-muted">
|
||||
Formatos soportados: JPG, JPEG, PNG, MP4, AVIF Y WEBP.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!-- bg color picker - Apila en móviles, 2 por fila en pantallas medianas/grandes -->
|
||||
<div class="col-6 col-md-3 mb-3">
|
||||
{{ form.bg_color.label(class="form-label fw-bold") }}
|
||||
<div class="input-group">
|
||||
{{ form.bg_color(type="color", class="form-control form-control-color", value="#ffffff") }}
|
||||
<span class="input-group-text">{{ form.bg_color.data }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- txt color picker -->
|
||||
<div class="col-6 col-md-3 mb-3">
|
||||
{{ form.txt_color.label(class="form-label fw-bold") }}
|
||||
<div class="input-group">
|
||||
{{ form.txt_color(type="color", class="form-control form-control-color", value="#ffffff") }}
|
||||
<span class="input-group-text">{{ form.txt_color.data }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Fila 2: Campos de texto y URL -->
|
||||
<div class="row">
|
||||
<!-- txt - Ocupa todo el ancho en móviles, 2/3 en pantallas medianas/grandes -->
|
||||
<div class="col-12 col-md-8 mb-3">
|
||||
{{ form.txt.label(class="form-label fw-bold") }}
|
||||
{{ form.txt(class="form-control", maxlength="350", rows="3", placeholder="Texto que aparecerá en el slide") }}
|
||||
<div class="form-text">
|
||||
<span id="txt-counter">0</span>/350 caracteres
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- url - Ocupa todo el ancho en móviles, 1/3 en pantallas medianas/grandes -->
|
||||
<div class="col-12 col-md-4 mb-3">
|
||||
{{ form.url.label(class="form-label fw-bold") }}
|
||||
{{ form.url(class="form-control", maxlength="250", placeholder="https://ejemplo.com") }}
|
||||
<div class="form-text">
|
||||
<span id="url-counter">0</span>/250 caracteres
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Switch para new tab -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="form-check form-switch">
|
||||
{{ form.isNewTab(class="form-check-input", type="checkbox", role="switch") }}
|
||||
{{ form.isNewTab.label(class="form-check-label fw-bold") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Botón de submit -->
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<button type="submit" class="btn btn-primary btn-lg">
|
||||
<i class="bi bi-database-fill-up me-2"></i>Guardar Slide
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- JavaScript para contadores de caracteres -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Contador para el campo de texto
|
||||
const txtField = document.querySelector('#{{ form.txt.id }}');
|
||||
const txtCounter = document.querySelector('#txt-counter');
|
||||
|
||||
txtField.addEventListener('input', function() {
|
||||
txtCounter.textContent = this.value.length;
|
||||
});
|
||||
|
||||
// Contador para el campo URL
|
||||
const urlField = document.querySelector('#{{ form.url.id }}');
|
||||
const urlCounter = document.querySelector('#url-counter');
|
||||
|
||||
urlField.addEventListener('input', function() {
|
||||
urlCounter.textContent = this.value.length;
|
||||
});
|
||||
|
||||
// Validación del formulario
|
||||
const forms = document.querySelectorAll('.needs-validation');
|
||||
|
||||
Array.from(forms).forEach(form => {
|
||||
form.addEventListener('submit', event => {
|
||||
if (!form.checkValidity()) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
form.classList.add('was-validated');
|
||||
}, false);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Contador para el campo de texto
|
||||
const txtField = document.querySelector('#{{ form.txt.id }}');
|
||||
const txtCounter = document.querySelector('#txt-counter');
|
||||
|
||||
txtField.addEventListener('input', function () {
|
||||
txtCounter.textContent = this.value.length;
|
||||
});
|
||||
|
||||
// Contador para el campo URL
|
||||
const urlField = document.querySelector('#{{ form.url.id }}');
|
||||
const urlCounter = document.querySelector('#url-counter');
|
||||
|
||||
urlField.addEventListener('input', function () {
|
||||
urlCounter.textContent = this.value.length;
|
||||
});
|
||||
|
||||
// Validación del formulario
|
||||
const forms = document.querySelectorAll('.needs-validation');
|
||||
|
||||
Array.from(forms).forEach(form => {
|
||||
form.addEventListener('submit', event => {
|
||||
if (!form.checkValidity()) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
form.classList.add('was-validated');
|
||||
}, false);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
@ -104,16 +104,31 @@
|
||||
<li class="nav-item"><a class="nav-link {% if active_page == 'change_pswd' %}active{% endif %}" href="{{ url_for('change_pswd') }}"><i class="bi bi-file-earmark-lock2"></i> Cambiar Contraseña</a></li>
|
||||
{% if is_admin %}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle {% if active_page in ['metrics', 'manage_profiles'] %}active{% endif %}" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-gear-wide"></i> Admin Opt.
|
||||
<a class="nav-link dropdown-toggle {% if active_page in [ 'carousel', 'metrics', 'manage_profiles'] %}active{% endif %}" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-gear-wide"></i> Admin Opc.
|
||||
</a>
|
||||
|
||||
<ul class="dropdown-menu">
|
||||
<!-- <li><hr class="dropdown-divider"></li> -->
|
||||
|
||||
<li><a class="dropdown-item" href="{{ url_for('carousel') }}"><i class="bi bi-easel-fill"></i> Carousel</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('metrics') }}"><i class="bi bi-bar-chart-line-fill"></i> Métricas</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('manage_profiles') }}"><i class="bi bi-person-fill-gear"></i> Administrar Perfiles</a></li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="{{ url_for('carousel') }}">
|
||||
{% if active_page == 'carousel' %}<i class="bi bi-arrow-return-right"></i>{% endif %}
|
||||
<i class="bi bi-easel-fill"></i> Carousel
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="{{ url_for('metrics') }}">
|
||||
{% if active_page == 'metrics' %}<i class="bi bi-arrow-return-right"></i>{% endif %}
|
||||
<i class="bi bi-bar-chart-line-fill"></i> Métricas
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="{{ url_for('manage_profiles') }}">
|
||||
{% if active_page == 'manage_profiles' %}<i class="bi bi-arrow-return-right"></i>{% endif %}
|
||||
<i class="bi bi-person-fill-gear"></i> Perfiles
|
||||
</a>
|
||||
</li>
|
||||
<!-- <li><hr class="dropdown-divider"></li> -->
|
||||
</ul>
|
||||
|
||||
|
@ -26,14 +26,10 @@
|
||||
|
||||
<div class="floating-btn border border-light shadow-lg" id="floatingBtn">
|
||||
<a id="floatingBtnLink" target="_blank" href="https://chatgpt.com/g/g-6828126fba608191a2803ac89f54f504-formha-rh-para-pymes">
|
||||
<img src="{{ url_for('static', filename='y_img/logos/chat_ia_formha.svg') }}"
|
||||
alt="logo"
|
||||
class="img-fluid rounded-circle rotating"
|
||||
style="width: 100%; height: 100%;">
|
||||
<img src="../../static/y_img/logos/chat_ia_formha.png" alt="logo" style="width: 100%; height: 100%;">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const btn = document.getElementById('floatingBtn');
|
||||
@ -125,6 +121,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
function startInteraction(e) {
|
||||
e.preventDefault();
|
||||
btn.style.transition = 'none'; // Desactiva transición
|
||||
const clientX = e.clientX;
|
||||
const clientY = e.clientY;
|
||||
|
||||
@ -170,6 +167,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
function endInteraction() {
|
||||
startClientX = null;
|
||||
startClientY = null;
|
||||
btn.style.transition = ''; // Restaura transición CSS
|
||||
|
||||
if (isDragging) {
|
||||
isDragging = false;
|
||||
|
Loading…
x
Reference in New Issue
Block a user