se crea el carousel dinámico
This commit is contained in:
parent
fc7e7f2a00
commit
21852c1fff
@ -74,5 +74,29 @@ CREATE TABLE visited_from (
|
|||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
-- cargar a la base de datos de producción
|
||||||
|
CREATE TABLE carousel (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
img_name VARCHAR(150) NOT NULL,
|
||||||
|
bg_color VARCHAR(40) NOT NULL,
|
||||||
|
txt_color VARCHAR(40) NOT NULL,
|
||||||
|
txt VARCHAR(400) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
ALTER DATABASE formha SET timezone TO 'America/Mexico_City';
|
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;
|
||||||
|
|
||||||
|
-- select * from visited_from;
|
BIN
forms_py/__pycache__/cls_form_carousel.cpython-312.pyc
Normal file
BIN
forms_py/__pycache__/cls_form_carousel.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
33
forms_py/cls_form_carousel.py
Normal file
33
forms_py/cls_form_carousel.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import StringField, FileField
|
||||||
|
from wtforms.fields import HiddenField
|
||||||
|
from wtforms.validators import DataRequired
|
||||||
|
from wtforms.widgets import ColorInput
|
||||||
|
from flask_wtf.file import FileAllowed
|
||||||
|
|
||||||
|
import re
|
||||||
|
from wtforms import ValidationError
|
||||||
|
|
||||||
|
# def hex_color_only(form, field):
|
||||||
|
# if not re.match(r'^#(?:[0-9a-fA-F]{3}){1,2}$', field.data):
|
||||||
|
# raise ValidationError('Debe ser un color hexadecimal válido (#RRGGBB o #RGB).')
|
||||||
|
|
||||||
|
class Carousel(FlaskForm):
|
||||||
|
# id = HiddenField() # Descomenta si lo necesitas
|
||||||
|
|
||||||
|
img = FileField(
|
||||||
|
'Imagen de fondo: ',
|
||||||
|
validators=[
|
||||||
|
DataRequired(),
|
||||||
|
FileAllowed(['jpg', 'jpeg', 'png', 'mp4'], 'Solo se permiten archivos .jpg, .png o .mp4')
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
bg_color = StringField('Color de fondo del texto: ', widget=ColorInput(), validators=[DataRequired()])
|
||||||
|
|
||||||
|
txt_color = StringField('Color del texto: ', widget=ColorInput(), validators=[DataRequired()])
|
||||||
|
|
||||||
|
txt = StringField('Texto: ', validators=[DataRequired()])
|
||||||
|
|
||||||
|
|
@ -32,7 +32,8 @@ v = {
|
|||||||
'edit_post': 'h_tmp_usr/e_edit_post.html',
|
'edit_post': 'h_tmp_usr/e_edit_post.html',
|
||||||
'change_pswd': 'h_tmp_usr/f_change_pswd.html',
|
'change_pswd': 'h_tmp_usr/f_change_pswd.html',
|
||||||
'metrics': 'h_tmp_usr/g_metrics.html',
|
'metrics': 'h_tmp_usr/g_metrics.html',
|
||||||
'manage_profiles': 'h_tmp_usr/h_manage_profiles.html'
|
'manage_profiles': 'h_tmp_usr/h_manage_profiles.html',
|
||||||
|
'carousel': 'h_tmp_usr/i_carousel_form.html'
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -137,3 +138,20 @@ def get_date_n_time(timestamp: datetime) -> dict:
|
|||||||
}
|
}
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
def hex_to_rgb(hex_color):
|
||||||
|
# Elimina el símbolo '#' si está presente
|
||||||
|
hex_color = hex_color.lstrip('#')
|
||||||
|
|
||||||
|
# Convierte los pares de caracteres hexadecimales a enteros
|
||||||
|
if len(hex_color) == 6:
|
||||||
|
r = int(hex_color[0:2], 16)
|
||||||
|
g = int(hex_color[2:4], 16)
|
||||||
|
b = int(hex_color[4:6], 16)
|
||||||
|
return (r, g, b)
|
||||||
|
else:
|
||||||
|
raise ValueError("El color HEX debe tener 6 dígitos.")
|
||||||
|
|
||||||
|
|
||||||
|
def rgba_to_string(rgba):
|
||||||
|
r, g, b, a = rgba
|
||||||
|
return f"rgba({r}, {g}, {b}, {a})"
|
||||||
|
79
main.py
79
main.py
@ -1,14 +1,15 @@
|
|||||||
from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity, get_jwt, decode_token, verify_jwt_in_request, set_access_cookies, get_csrf_token
|
from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity, get_jwt, decode_token, verify_jwt_in_request, set_access_cookies, get_csrf_token
|
||||||
from flask_mail import Message, Mail
|
from flask_mail import Message, Mail
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from flask import Flask, render_template, redirect, url_for, flash, current_app, request, jsonify, make_response
|
from flask import Flask, render_template, redirect, url_for, flash, current_app, request, jsonify, make_response, send_from_directory, abort
|
||||||
from forms_py.cls_form_contact import ContactForm
|
from forms_py.cls_form_contact import ContactForm
|
||||||
from forms_py.cls_form_add_usr import AddUser
|
from forms_py.cls_form_add_usr import AddUser
|
||||||
from forms_py.cls_form_login import LogIn
|
from forms_py.cls_form_login import LogIn
|
||||||
from forms_py.cls_db import DBContact
|
from forms_py.cls_db import DBContact
|
||||||
from forms_py.cls_db_usr import DBForma
|
from forms_py.cls_db_usr import DBForma
|
||||||
from forms_py.cls_change_pswd import ChangePwsd
|
from forms_py.cls_change_pswd import ChangePwsd
|
||||||
from forms_py.functions import db_conf_obj, generar_contrasena, hash_password, v, l_flash_msj, saludo_hr, cur_date, min_read_pst, get_date_n_time, getRandomId
|
from forms_py.cls_form_carousel import Carousel
|
||||||
|
from forms_py.functions import db_conf_obj, generar_contrasena, hash_password, v, l_flash_msj, saludo_hr, cur_date, min_read_pst, get_date_n_time, getRandomId, hex_to_rgb, rgba_to_string
|
||||||
from forms_py.cls_recover_pswd import RecoverPswd
|
from forms_py.cls_recover_pswd import RecoverPswd
|
||||||
import os
|
import os
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
@ -21,6 +22,7 @@ from bs4 import BeautifulSoup
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
from flask_caching import Cache
|
from flask_caching import Cache
|
||||||
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
cache = Cache(app, config={'CACHE_TYPE': 'filesystem', 'CACHE_DIR': '/tmp/flask-formha'})
|
cache = Cache(app, config={'CACHE_TYPE': 'filesystem', 'CACHE_DIR': '/tmp/flask-formha'})
|
||||||
|
|
||||||
@ -126,9 +128,18 @@ def validate_user_exists(f):
|
|||||||
# ##################################################################
|
# ##################################################################
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
@cache.cached(timeout=3600)
|
# @cache.cached(timeout=3600)
|
||||||
def home():
|
def home():
|
||||||
return render_template(v['home'], active_page='home')
|
q = """
|
||||||
|
SELECT
|
||||||
|
id, img_name, bg_color, txt_color, txt, TO_CHAR(created_at, 'DD/MM/YYYY HH24:MI') as fecha_creada
|
||||||
|
FROM
|
||||||
|
carousel
|
||||||
|
ORDER BY
|
||||||
|
id DESC;
|
||||||
|
"""
|
||||||
|
data = dbUsers.get_all_data(q)
|
||||||
|
return render_template(v['home'], active_page='home', data=data)
|
||||||
|
|
||||||
@app.route('/about-us')
|
@app.route('/about-us')
|
||||||
@cache.cached(timeout=43200)
|
@cache.cached(timeout=43200)
|
||||||
@ -892,12 +903,70 @@ def update_user():
|
|||||||
t = (f_nombre, f_apellido, f_genero, f_email, f_isAdmin, id_usr)
|
t = (f_nombre, f_apellido, f_genero, f_email, f_isAdmin, id_usr)
|
||||||
|
|
||||||
dbUsers.update_data(q, t)
|
dbUsers.update_data(q, t)
|
||||||
|
|
||||||
|
|
||||||
res = {'ok': True, 'message': 'El elemento se actualizó correctamente', "id": "id_usr"}
|
res = {'ok': True, 'message': 'El elemento se actualizó correctamente', "id": "id_usr"}
|
||||||
return jsonify(res), 200
|
return jsonify(res), 200
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/user/carousel', methods=['GET', 'POST'])
|
||||||
|
@jwt_required()
|
||||||
|
@validate_user_exists
|
||||||
|
@admin_required
|
||||||
|
def carousel():
|
||||||
|
form = Carousel()
|
||||||
|
mnsj_flash = None
|
||||||
|
|
||||||
|
q_all_slides = """
|
||||||
|
SELECT
|
||||||
|
id, img_name, bg_color, txt_color, txt, TO_CHAR(created_at, 'DD/MM/YYYY HH24:MI') as fecha_creada
|
||||||
|
FROM
|
||||||
|
carousel
|
||||||
|
ORDER BY
|
||||||
|
id DESC;
|
||||||
|
"""
|
||||||
|
|
||||||
|
data = dbUsers.get_all_data(q_all_slides)
|
||||||
|
|
||||||
|
if request.method == 'POST' and form.validate_on_submit():
|
||||||
|
# img
|
||||||
|
image_file = form.img.data
|
||||||
|
filename = secure_filename(image_file.filename)
|
||||||
|
|
||||||
|
prev_img = dbUsers.get_all_data("SELECT img_name FROM carousel;")
|
||||||
|
lst_img = [ele[0] for ele in prev_img]
|
||||||
|
|
||||||
|
# validar que el archivo no se haya cargado previamente
|
||||||
|
if filename in lst_img:
|
||||||
|
mnsj_flash = l_flash_msj('Error Archivo', 'La imagen ya ha sido cargada previamente.', 'error')
|
||||||
|
else:
|
||||||
|
image_file.save(f'./static/uploads/{filename}')
|
||||||
|
bg_color = rgba_to_string((*hex_to_rgb(form.bg_color.data), 0.5))
|
||||||
|
txt_color = rgba_to_string((*hex_to_rgb(form.txt_color.data), 1))
|
||||||
|
txt = form.txt.data
|
||||||
|
|
||||||
|
q = "INSERT INTO carousel (img_name, bg_color, txt_color, txt) VALUES (%s, %s, %s, %s);"
|
||||||
|
t = (filename, bg_color, txt_color, txt)
|
||||||
|
dbUsers.update_data(q, t)
|
||||||
|
mnsj_flash = l_flash_msj('Datos Slide', f'Datos agregados para imagen: {filename}', 'success')
|
||||||
|
|
||||||
|
flash(mnsj_flash)
|
||||||
|
return redirect(url_for('carousel'))
|
||||||
|
|
||||||
|
return render_template(v['tmp_user']['carousel'], form=form, data=data, active_page='metrics')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/user/carousel/download/<path:filename>')
|
||||||
|
@jwt_required()
|
||||||
|
@validate_user_exists
|
||||||
|
@admin_required
|
||||||
|
def download_file(filename):
|
||||||
|
uploads_dir = os.path.join(app.root_path, 'static', 'uploads')
|
||||||
|
try:
|
||||||
|
return send_from_directory(uploads_dir, filename, as_attachment=True)
|
||||||
|
except FileNotFoundError:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
# -------------------------------------------------------------
|
# -------------------------------------------------------------
|
||||||
# MANEJO DE ERRORES DE JWT
|
# MANEJO DE ERRORES DE JWT
|
||||||
|
|
||||||
|
@ -1,99 +1,5 @@
|
|||||||
/* Variables para consistencia */
|
/* Variables para consistencia */
|
||||||
:root {
|
|
||||||
--primary-color: #00acc1;
|
|
||||||
--shadow-light: 0 3px 4px rgba(0, 0, 0, 0.4);
|
|
||||||
--shadow-hover: 0 15px 26px rgba(0, 0, 0, 0.5);
|
|
||||||
--card-radius: 5px;
|
|
||||||
--image-height: 250px;
|
|
||||||
--banner-text-color: #ffff;
|
|
||||||
--banner-overlay: rgba(116, 118, 119, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Estructura principal */
|
|
||||||
/* main {
|
|
||||||
width: 95vw;
|
|
||||||
min-height: 100vh;
|
|
||||||
} */
|
|
||||||
|
|
||||||
/* ---------------------------- */
|
|
||||||
/* Fila 1 - Banner con efecto de zoom */
|
|
||||||
.r-i {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.r-i-ii {
|
|
||||||
background-image: url('/static/y_img/home/reunion.avif');
|
|
||||||
background-size: 100%;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
border-radius: 10px;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
transition: background-size 2s ease-in-out;
|
|
||||||
height: 35vh; /* Valor por defecto para móviles */
|
|
||||||
}
|
|
||||||
|
|
||||||
.r-i-ii:hover {
|
|
||||||
background-size: 110%; /* Efecto de zoom mantenido */
|
|
||||||
}
|
|
||||||
|
|
||||||
.r-i-ii spam {
|
|
||||||
background-color: var(--banner-overlay);
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 10px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: clamp(1rem, 3vw, 25px); /* Texto responsivo */
|
|
||||||
color: var(--banner-text-color);
|
|
||||||
max-width: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------------------- */
|
|
||||||
/* Fila 2 - Tarjetas con efectos */
|
|
||||||
.r-ii {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
||||||
gap: 30px;
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-flyer {
|
|
||||||
min-height: 100%;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: var(--card-radius);
|
|
||||||
box-shadow: var(--shadow-light);
|
|
||||||
transition: transform 0.3s, box-shadow 0.3s;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-box img {
|
|
||||||
width: 100%;
|
|
||||||
height: var(--image-height);
|
|
||||||
object-fit: cover;
|
|
||||||
transition: transform 0.9s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-container {
|
|
||||||
padding: 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-container h6 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 18px;
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Efectos hover (completos) */
|
|
||||||
.card-flyer:hover {
|
|
||||||
transform: translateY(-10px);
|
|
||||||
box-shadow: var(--shadow-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-flyer:hover img {
|
|
||||||
transform: scale(1.15); /* Efecto de escala mantenido */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------------------- */
|
/* ---------------------------- */
|
||||||
/* Media Queries optimizadas */
|
/* Media Queries optimizadas */
|
||||||
@ -101,9 +7,7 @@
|
|||||||
main {
|
main {
|
||||||
width: 90vw;
|
width: 90vw;
|
||||||
}
|
}
|
||||||
.r-i-ii {
|
|
||||||
height: 35vh;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
@ -111,9 +15,7 @@
|
|||||||
width: 85vw;
|
width: 85vw;
|
||||||
margin: 20px auto;
|
margin: 20px auto;
|
||||||
}
|
}
|
||||||
.r-i-ii {
|
|
||||||
height: 45vh;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1440px) {
|
@media (min-width: 1440px) {
|
||||||
@ -121,20 +23,14 @@
|
|||||||
width: 80vw;
|
width: 80vw;
|
||||||
margin: 2em auto;
|
margin: 2em auto;
|
||||||
}
|
}
|
||||||
.r-i-ii {
|
|
||||||
height: 50vh;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1920px) {
|
@media (min-width: 1920px) {
|
||||||
.r-i-ii {
|
|
||||||
height: 60vh;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ajuste específico para texto en móviles */
|
/* Ajuste específico para texto en móviles */
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
.r-i-ii spam {
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
}
|
}
|
18
static/h_tmp_user/i_carousel_form/carousel_form.js
Normal file
18
static/h_tmp_user/i_carousel_form/carousel_form.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { simpleNotification } from '../../z_comps/notify.js';
|
||||||
|
|
||||||
|
|
||||||
|
let form = document.querySelector("form");
|
||||||
|
let img = document.getElementById("img");
|
||||||
|
|
||||||
|
form.addEventListener("submit", function (e) {
|
||||||
|
let allowedExtensions = ['jpg', 'jpeg', 'png', 'mp4'];
|
||||||
|
let filePath = img.value;
|
||||||
|
let extension = filePath.split('.').pop().toLowerCase();
|
||||||
|
|
||||||
|
if (!allowedExtensions.includes(extension)) {
|
||||||
|
e.preventDefault();
|
||||||
|
simpleNotification("Error Extensión", "Solo se permiten archivos con extensión: .jpg, .jpeg, .png, .mp4", "error")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
@ -10,96 +10,69 @@
|
|||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
<div class="r-i">
|
<style>
|
||||||
<div class="r-i-ii">
|
@media screen and (orientation: landscape) {
|
||||||
<spam>
|
.carousel img {
|
||||||
Potenciamos el talento con soluciones personalizadas que alinean personas, información y propósito. Diseñamos
|
height: 80vh;
|
||||||
estrategias basadas en información, ordenada, confiable, disponible y segura, dando FORMHä a tus decisiones.
|
object-fit: cover;
|
||||||
</spam>
|
}
|
||||||
</div>
|
}
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="r-ii">
|
|
||||||
<!-- Tarjeta 1 -->
|
|
||||||
<div class="card-flyer">
|
|
||||||
<div class="image-box">
|
|
||||||
<img src="{{url_for('static', filename='y_img/home/software.avif')}}" alt="Software">
|
|
||||||
</div>
|
|
||||||
<div class="text-container">
|
|
||||||
<h6>
|
|
||||||
Actualización del proceso de reclutamiento y selección.
|
|
||||||
</h6>
|
|
||||||
<p>
|
|
||||||
Actualmente desde la AI hasta las técnicas de entrevista, han abierto un nuevo horizonte en el marco del
|
|
||||||
reclutamiento y selección de personal…
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tarjeta 2 -->
|
|
||||||
<div class="card-flyer">
|
|
||||||
<div class="image-box">
|
|
||||||
<img src="{{url_for('static', filename='y_img/home/work.avif')}}" alt="Productividad">
|
|
||||||
</div>
|
|
||||||
<div class="text-container">
|
|
||||||
<h6>
|
|
||||||
Beneficios e impacto en la productividad.
|
|
||||||
</h6>
|
|
||||||
<p>
|
|
||||||
En la medida que adoptemos la NOM 035, factores de riesgos psicosocial en el trabajo, identificación y
|
|
||||||
prevención; como una guía para optimizar el clima y…
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tarjeta 3 -->
|
|
||||||
<div class="card-flyer">
|
|
||||||
<div class="image-box">
|
|
||||||
<img src="{{url_for('static', filename='y_img/home/chair.avif')}}" alt="Ley Silla">
|
|
||||||
</div>
|
|
||||||
<div class="text-container">
|
|
||||||
<h6>
|
|
||||||
Alcances y objetivos de la Ley Silla.
|
|
||||||
</h6>
|
|
||||||
<p>
|
|
||||||
El pasado 19 de diciembre de 2024, se publico en el Diario Oficial de la Federación el decreto por el cual
|
|
||||||
se reforman y adicionan diversas…
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
.carousel-caption.centered {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
pointer-events: none; /* clics pasan a la imagen debajo */
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-caption.centered .caption-content {
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 0 10px rgba(0,0,0,0.3);
|
||||||
|
pointer-events: auto; /* permite clics aquí */
|
||||||
|
width: max-content;
|
||||||
|
max-width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-caption.centered .caption-content h2 {
|
||||||
|
font-size: clamp(1.5rem, 5vw, 3rem);
|
||||||
|
margin: 0;
|
||||||
|
text-shadow: 1px 1px 4px rgba(0,0,0,0.6);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<div id="carouselExampleCaptions" class="carousel slide" data-bs-ride="carousel">
|
<div id="carouselExampleCaptions" class="carousel slide" data-bs-ride="carousel">
|
||||||
<div class="carousel-indicators">
|
<div class="carousel-indicators">
|
||||||
<button type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide-to="0" class="active" aria-current="true" aria-label="Slide 1"></button>
|
{% for s in data %}
|
||||||
<button type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide-to="1" aria-label="Slide 2"></button>
|
<button type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide-to="{{ loop.index0 }}"
|
||||||
<button type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide-to="2" aria-label="Slide 3"></button>
|
class="{% if loop.first %}active{% endif %}" aria-current="{{ 'true' if loop.first else 'false' }}"
|
||||||
|
aria-label="Slide {{ loop.index }}"></button>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="carousel-inner">
|
<div class="carousel-inner">
|
||||||
<div class="carousel-item active">
|
|
||||||
<img src="{{url_for('static', filename='y_img/home/software.avif')}}" class="d-block w-100" alt="...">
|
{% for s in data %}
|
||||||
<div class="carousel-caption d-none d-md-block">
|
<div class="carousel-item {% if loop.first %}active{% endif %}">
|
||||||
<h5>First slide label</h5>
|
{% if s[1].endswith('.mp4') %}
|
||||||
<p>Some representative placeholder content for the first slide.</p>
|
<video class="d-block w-100" autoplay muted loop playsinline>
|
||||||
</div>
|
<source src="{{ url_for('static', filename='uploads/' + s[1]) }}" type="video/mp4">
|
||||||
</div>
|
Tu navegador no soporta el video.
|
||||||
<div class="carousel-item">
|
</video>
|
||||||
<img src="{{url_for('static', filename='y_img/home/work.avif')}}" class="d-block w-100" alt="...">
|
{% else %}
|
||||||
<div class="carousel-caption d-none d-md-block">
|
<img src="{{ url_for('static', filename='uploads/' + s[1]) }}" class="d-block w-100" alt="...">
|
||||||
<h5>Second slide label</h5>
|
{% endif %}
|
||||||
<p>Some representative placeholder content for the second slide.</p>
|
<div class="carousel-caption centered">
|
||||||
</div>
|
<div class="caption-content" style="background-color: {{ s[2] }}; color: {{ s[3] }};">
|
||||||
</div>
|
<h2>{{ s[4] }}</h2>
|
||||||
<div class="carousel-item">
|
</div>
|
||||||
<img src="{{url_for('static', filename='y_img/home/chair.avif')}}" class="d-block w-100" alt="...">
|
|
||||||
<div class="carousel-caption d-none d-md-block">
|
|
||||||
<h5>Third slide label</h5>
|
|
||||||
<p>Some representative placeholder content for the third slide.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<button class="carousel-control-prev" type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide="prev">
|
<button class="carousel-control-prev" type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide="prev">
|
||||||
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
||||||
@ -111,6 +84,5 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{% endblock body %}
|
{% endblock body %}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="expanding-panels" data-aos="zoom-in" data-aos-delay="0" data-aos-duration="800" data-aos-easing="ease-in-out">
|
<div class="expanding-panels" data-aos="zoom-in" data-aos-delay="0" data-aos-duration="800" data-aos-easing="ease-in-out">
|
||||||
<!-- Quiénes Somos -->
|
<!-- Quiénes Somos -->
|
||||||
<div class="panel p1" >
|
<div class="panel p1" >
|
||||||
|
334
templates/h_tmp_usr/i_carousel_form.html
Normal file
334
templates/h_tmp_usr/i_carousel_form.html
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
{% extends 'h_tmp_usr/z_tmp.html' %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='f_contact/form.css') }}">
|
||||||
|
|
||||||
|
<!-- {# datatables #} -->
|
||||||
|
<link rel="stylesheet" href="https://cdn.datatables.net/2.2.2/css/dataTables.bootstrap5.css">
|
||||||
|
|
||||||
|
<!-- {# i jconfirm #} -->
|
||||||
|
<link rel="stylesheet" href="https://htmlguyllc.github.io/jConfirm/jConfirm.min.css">
|
||||||
|
<!-- {# f jconfirm #} -->
|
||||||
|
{% endblock css %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* estilos del carousel */
|
||||||
|
@media screen and (orientation: landscape) {
|
||||||
|
.carousel img {
|
||||||
|
height: 80vh;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-item video {
|
||||||
|
width: 100%;
|
||||||
|
height: 80vh;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-item {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
/* height: 100vh; */
|
||||||
|
}
|
||||||
|
.carousel-inner {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-caption.centered {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-caption.centered .caption-content {
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 0 10px rgba(0,0,0,0.3);
|
||||||
|
pointer-events: auto;
|
||||||
|
width: max-content;
|
||||||
|
max-width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-caption.centered .caption-content h2 {
|
||||||
|
font-size: clamp(1.5rem, 5vw, 3rem);
|
||||||
|
margin: 0;
|
||||||
|
text-shadow: 1px 1px 4px rgba(0,0,0,0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------ */
|
||||||
|
/* Aplica a ambas: imágenes y videos */
|
||||||
|
.carousel-item img,
|
||||||
|
.carousel-item video {
|
||||||
|
/* width: 100%; */
|
||||||
|
/* height: 100%; */
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</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" aria-controls="home" aria-selected="true"><i class="bi bi-file-earmark-slides-fill"></i> Slides</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="profile-tab" data-bs-toggle="tab" data-bs-target="#profile" type="button" role="tab" aria-controls="profile" aria-selected="false"><i class="bi bi-database-fill-up"></i> Añadir Slide</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="contact-tab" data-bs-toggle="tab" data-bs-target="#contact" type="button" role="tab" aria-controls="contact" aria-selected="false"><i class="bi bi-play-circle-fill"></i> Carousel</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content" id="myTabContent">
|
||||||
|
<div class="tab-pane fade show active" id="home" role="tabpanel" aria-labelledby="home-tab">
|
||||||
|
<!-- {# --------------------------------------------------------------- #} -->
|
||||||
|
<!-- {# i tabla slides #} -->
|
||||||
|
<table id="tblCarousel" class="table table-striped" style="width:100%" >
|
||||||
|
<thead>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Creado</th>
|
||||||
|
<th>Archivo</th>
|
||||||
|
<th>Texto</th>
|
||||||
|
<th>Acciones</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for ele in data %}
|
||||||
|
<tr id="{{ ele[0] }}">
|
||||||
|
<td>{{ loop.index }}</td>
|
||||||
|
<td>{{ ele[5] }}</td>
|
||||||
|
<td>{{ ele[1] }}</td>
|
||||||
|
|
||||||
|
<td style="background-color: {{ ele[2] }}; color: {{ ele[3] }};">{{ ele[4] }}</td>
|
||||||
|
<td >
|
||||||
|
<div class="field_btns">
|
||||||
|
<a href="{{ url_for('download_file', filename = ele[1] ) }}" class="btn btn-dark" style="color: white;">
|
||||||
|
<i class="bi bi-download"></i>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="btn btn-danger delete-btn" data-id="{{ ele[0] }}">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- {# f tabla slides #} -->
|
||||||
|
<!-- {# --------------------------------------------------------------- #} -->
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="profile" role="tabpanel" aria-labelledby="profile-tab">
|
||||||
|
<!-- {# --------------------------------------------------------------- #} -->
|
||||||
|
<!-- {# i form add slide #} -->
|
||||||
|
<!-- {# inicio form #} -->
|
||||||
|
<div class="form-container" data-aos="fade-down" data-aos-delay="0" data-aos-duration="800"
|
||||||
|
data-aos-easing="ease-in-out">
|
||||||
|
<div class="form-header">
|
||||||
|
<h2>Agregar Slide</h2>
|
||||||
|
</div>
|
||||||
|
<form method="POST" action="{{ url_for('carousel') }}" enctype="multipart/form-data" class="login-form">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
|
||||||
|
<!-- {# input imagen #} -->
|
||||||
|
<div class="form-row">
|
||||||
|
{{ form.img.label() }}
|
||||||
|
{{ form.img( class_="form-control" ) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- {# bg color picker #} -->
|
||||||
|
<div class="form-row">
|
||||||
|
{{ form.bg_color.label() }}
|
||||||
|
{{ form.bg_color( type="color", class_="form-control form-control-color", value="") }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- {# txt color picker #} -->
|
||||||
|
<div class="form-row">
|
||||||
|
{{ form.txt_color.label() }}
|
||||||
|
{{ form.txt_color( type="color", class_="form-control form-control-color", value="" ) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- {# txt #} -->
|
||||||
|
<div class="form-row">
|
||||||
|
{{ form.txt.label() }}
|
||||||
|
{{ form.txt( class_="form-control" ) }}
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="bi bi-database-fill-up"></i> Enviar Datos
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<!-- {# final form #} -->
|
||||||
|
<!-- {# f form add slide #} -->
|
||||||
|
<!-- {# --------------------------------------------------------------- #} -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="contact" role="tabpanel" aria-labelledby="contact-tab">
|
||||||
|
<!-- {# --------------------------------------------------------------- #} -->
|
||||||
|
<!-- {# i carousel #} -->
|
||||||
|
<div id="carouselExampleCaptions" class="carousel slide" data-bs-ride="carousel">
|
||||||
|
<div class="carousel-indicators">
|
||||||
|
{% for s in data %}
|
||||||
|
<button type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide-to="{{ loop.index0 }}"
|
||||||
|
class="{% if loop.first %}active{% endif %}" aria-current="{{ 'true' if loop.first else 'false' }}"
|
||||||
|
aria-label="Slide {{ loop.index }}"></button>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="carousel-inner">
|
||||||
|
<!-- {# inicio card item slide #} -->
|
||||||
|
{% for s in data %}
|
||||||
|
<div class="carousel-item {% if loop.first %}active{% endif %}">
|
||||||
|
{% if s[1].endswith('.mp4') %}
|
||||||
|
<video class="d-block w-100" autoplay muted loop playsinline>
|
||||||
|
<source src="{{ url_for('static', filename='uploads/' + s[1]) }}" type="video/mp4">
|
||||||
|
Tu navegador no soporta el video.
|
||||||
|
</video>
|
||||||
|
{% else %}
|
||||||
|
<img src="{{ url_for('static', filename='uploads/' + s[1]) }}" class="d-block w-100" alt="...">
|
||||||
|
{% endif %}
|
||||||
|
<div class="carousel-caption centered">
|
||||||
|
<div class="caption-content" style="background-color: {{ s[2] }}; color: {{ s[3] }};">
|
||||||
|
<h2>{{ s[4] }}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<!-- {# fin card item slide #} -->
|
||||||
|
</div>
|
||||||
|
<button class="carousel-control-prev" type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide="prev">
|
||||||
|
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
||||||
|
<span class="visually-hidden">Previous</span>
|
||||||
|
</button>
|
||||||
|
<button class="carousel-control-next" type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide="next">
|
||||||
|
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
||||||
|
<span class="visually-hidden">Next</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<!-- {# f carousel #} -->
|
||||||
|
<!-- {# --------------------------------------------------------------- #} -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock body %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
|
||||||
|
<!-- {# js databases #} -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.7.1.js"></script>
|
||||||
|
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script> -->
|
||||||
|
<script src="https://cdn.datatables.net/2.2.2/js/dataTables.js"></script>
|
||||||
|
<script src="https://cdn.datatables.net/2.2.2/js/dataTables.bootstrap5.js"></script>
|
||||||
|
|
||||||
|
<!-- jQuery y jConfirm -->
|
||||||
|
<!-- <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> -->
|
||||||
|
<script src="https://htmlguyllc.github.io/jConfirm/jConfirm.min.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function delete_file(e){
|
||||||
|
let btn = $(this); // `this` es el botón que lanzó el jConfirm
|
||||||
|
let postId = btn.data('id'); // lee el data-id
|
||||||
|
let data = { 'id': postId };
|
||||||
|
console.log(data)
|
||||||
|
|
||||||
|
}
|
||||||
|
// https://htmlguyllc.github.io/jConfirm/
|
||||||
|
$(function(){
|
||||||
|
$('.delete-btn').jConfirm({
|
||||||
|
//false|array: if provided, this will override the default confirm/deny buttons (see below for an example)
|
||||||
|
btns: false,
|
||||||
|
//string: question displayed to the user
|
||||||
|
question: '¿Deseas continuar con la eliminación?',
|
||||||
|
//string: confirm button text
|
||||||
|
confirm_text: 'Sí',
|
||||||
|
//string: deny button text
|
||||||
|
deny_text: 'No',
|
||||||
|
//boolean: if true, when the confirm button is clicked the user will be redirected to the button's href location
|
||||||
|
// follow_href: true,
|
||||||
|
//boolean: if true and follow_href is true, the href will be opened in a new window
|
||||||
|
open_new_tab: false,
|
||||||
|
//boolean: if true, the tooltip will be hidden if you click outside of it
|
||||||
|
hide_on_click: true,
|
||||||
|
//string ('auto','top','bottom','left','right'): preferred location of the tooltip (defaults to auto if no space)
|
||||||
|
position: 'auto',
|
||||||
|
//boolean: if true, the deny button will be shown
|
||||||
|
show_deny_btn: true,
|
||||||
|
//string ('black', 'white', 'bootstrap-4', 'bootstrap-4-white')
|
||||||
|
theme: 'bootstrap-4',
|
||||||
|
//string ('tiny', 'small', 'medium', 'large')
|
||||||
|
size: 'medium',
|
||||||
|
//boolean: show the tooltip immediately on instantiation
|
||||||
|
show_now: false,
|
||||||
|
//string: class(es) to add to the tooltip
|
||||||
|
'class': ''
|
||||||
|
}).on('confirm', delete_file)
|
||||||
|
.on('deny', function(e){
|
||||||
|
var btn = $(this);
|
||||||
|
//do something on deny
|
||||||
|
}).on('jc-show', function(e, tooltip){
|
||||||
|
// console.log("el tooltip es visible");
|
||||||
|
//do something when tooltip is shown
|
||||||
|
//tooltip dom element is passed as the second parameter
|
||||||
|
}).on('jc-hide', function(e){
|
||||||
|
//do something when tooltip is hidden
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
new DataTable('#tblCarousel', {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- {# if flash #} -->
|
||||||
|
{% include 'z_comps/if_flash.html' %}
|
||||||
|
|
||||||
|
<!-- {# aos script #} -->
|
||||||
|
{% include 'z_comps/aos_script.html' %}
|
||||||
|
|
||||||
|
<!-- {# validador archivos extensión #} -->
|
||||||
|
<script type="module" src="{{ url_for('static', filename='h_tmp_user/i_carousel_form/carousel_form.js') }}"></script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock js %}
|
@ -110,6 +110,8 @@
|
|||||||
|
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<!-- <li><hr class="dropdown-divider"></li> -->
|
<!-- <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('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('manage_profiles') }}"><i class="bi bi-person-fill-gear"></i> Administrar Perfiles</a></li>
|
||||||
<!-- <li><hr class="dropdown-divider"></li> -->
|
<!-- <li><hr class="dropdown-divider"></li> -->
|
||||||
|
Loading…
x
Reference in New Issue
Block a user