formha/templates/h_tmp_usr/i_carousel_form.html

558 lines
16 KiB
HTML

{% 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>
<style>
/* Smartphones (hasta 767px) */
@media (max-width: 767px) {
/* main{ background-color: black; } */
#myTabContent{
min-height: 80dvh;
}
}
/* Tablets (768px - 1023px) */
@media (min-width: 768px) and (max-width: 1023px) {
/* main{ background-color: pink; } */
#myTabContent{
min-height: 80dvh;
}
}
/* Laptops (1024px - 1439px) monitores resulición baja */
@media (min-width: 1024px) and (max-width: 1439px) {
/* main{ background-color: purple; } */
#myTabContent{
min-height: 80dvh;
}
}
/* PCs de escritorio (1440px - 1919px) macbook */
@media (min-width: 1440px) and (max-width: 1919px) {
/* main{ background-color: greenyellow; } */
#myTabContent{
min-height: 80dvh;
}
}
/* Pantallas Ultrawide (1920px en adelante) */
@media (min-width: 1920px) {
/* main{ background-color: red; } */
#myTabContent{
min-height: 80dvh;
}
}
</style>
<style>
/* Estilo responsive para móviles */
@media screen and (max-width: 768px) {
#tblCarousel {
width: 100%;
margin: 0;
padding: 0;
}
#tblCarousel_wrapper {
padding: 0 !important;
margin: 0 -0.5rem; /* Compensa el padding de las celdas */
}
#tblCarousel thead {
display: none;
}
#tblCarousel tbody {
display: block;
width: 100%;
}
#tblCarousel tr {
display: block;
width: calc(100% - 2rem); /* Resta el padding horizontal */
margin: 0.5rem 1rem 1rem;
padding: 1rem;
border: 1px solid #ddd;
border-radius: 0.75rem;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
background-color: white;
box-sizing: border-box;
}
#tblCarousel td {
display: block;
width: 100%;
position: relative;
padding: 0.75rem 1rem 0.75rem 7.5rem; /* Más espacio para etiquetas */
border: none;
border-bottom: 1px solid #eee;
text-align: left;
word-break: break-word;
box-sizing: border-box;
min-height: 3rem;
}
#tblCarousel td::before {
content: attr(data-label);
position: absolute;
top: 0.75rem;
left: 1rem;
width: 6rem; /* Ancho fijo para etiquetas */
font-weight: 600;
color: #444;
font-size: 0.85rem;
white-space: normal;
overflow: hidden;
text-overflow: ellipsis;
}
#tblCarousel td:last-child {
border-bottom: none;
padding-bottom: 0;
}
.field_btns {
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 0.5rem;
}
.field_btns a {
flex: 0 1 calc(50% - 0.5rem); /* Dos botones por fila */
min-width: 0;
text-align: center;
padding: 0.375rem 0.5rem;
}
/* Manejo específico de columnas con contenido largo */
#tblCarousel td[data-label="Archivo"],
#tblCarousel td[data-label="Texto"] {
white-space: normal;
max-height: none;
overflow: visible;
}
/* Ajuste para filas con menos contenido */
#tblCarousel td[data-label="ID"],
#tblCarousel td[data-label="New Tab"] {
min-height: 2.5rem;
}
}
</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;
}
</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 table-responsive" id="home" role="tabpanel" aria-labelledby="home-tab">
<!-- {# --------------------------------------------------------------- #} -->
<!-- {# i tabla slides #} -->
<table id="tblCarousel" class="table table-striped display responsive nowrap" style="width:100%">
<thead>
<tr>
<th>ID</th>
<th>Creado</th>
<th>Archivo</th>
<th>New Tab</th>
<th>Texto</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
{% 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>
<td data-label="Acciones">
<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="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>
<!-- 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>
</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" data-bs-touch="true">
<!-- <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>
{% if s[6] is not none %}
<a href="{{ s[6] }}" type="button" class="btn btn-success" target="_blank">ver más</a>
{% endif %}
</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>
<!-- {# 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>
<script type="module" src="{{ url_for('static', filename='h_tmp_user/i_carousel_form/delete_slide.js') }}"></script>
<script src="{{ url_for('static', filename='h_tmp_user/i_carousel_form/datatable_carousel.js') }}"></script>
{% endblock js %}