feat: implement photo carousel for remark images with navigation, captions, and delete support
This commit is contained in:
parent
4e4d84b3a4
commit
f34e26f8f5
4 changed files with 371 additions and 54 deletions
|
|
@ -19,6 +19,16 @@ body {
|
|||
object-fit: cover; /* Garde le ratio et remplit */
|
||||
border-radius: 8px; /* Coins arrondis */
|
||||
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1); /* Ombre légère */
|
||||
transition: transform 0.15s ease-in-out, opacity 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.remark-photo-thumb {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.remark-photo-thumb:hover {
|
||||
transform: scale(1.05);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.remark-images a {
|
||||
|
|
@ -35,6 +45,16 @@ body {
|
|||
z-index: 5;
|
||||
}
|
||||
|
||||
#remarkPhotoCarouselModal .modal-body {
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
#remarkPhotoCarousel .carousel-control-prev-icon,
|
||||
#remarkPhotoCarousel .carousel-control-next-icon {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
#remark-form-container {
|
||||
position: fixed;
|
||||
top: 0; left: 0; width: 100%; height: 100%;
|
||||
|
|
|
|||
|
|
@ -1795,44 +1795,198 @@ function bindAutocompleteForRow(card) {
|
|||
}
|
||||
})();
|
||||
|
||||
// Fonction pour afficher une image dans une modal
|
||||
function showImageModal(imageUrl) {
|
||||
// Créer une modal pour afficher l'image en plein écran
|
||||
let modal = document.getElementById('image-preview-modal');
|
||||
// Global variables for remark photo carousel
|
||||
let currentRemarkCarouselPhotos = [];
|
||||
let currentRemarkCarouselIndex = 0;
|
||||
|
||||
if (!modal) {
|
||||
// Créer la modal si elle n'existe pas
|
||||
modal = document.createElement('div');
|
||||
modal.id = 'image-preview-modal';
|
||||
modal.className = 'modal fade';
|
||||
modal.tabIndex = -1;
|
||||
modal.setAttribute('aria-hidden', 'true');
|
||||
function openRemarkPhotoCarousel(thumbElement) {
|
||||
if (!thumbElement) return;
|
||||
const remarkId = thumbElement.dataset.remarkId;
|
||||
|
||||
modal.innerHTML = `
|
||||
<div class="modal-dialog modal-fullscreen">
|
||||
<div class="modal-content bg-dark">
|
||||
<div class="modal-header border-0">
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Fermer"></button>
|
||||
</div>
|
||||
<div class="modal-body d-flex align-items-center justify-content-center">
|
||||
<img id="modal-preview-image" src="" class="img-fluid" style="max-width: 100%; max-height: 90vh; object-fit: contain;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
// Find all photo thumbnails belonging to this remark
|
||||
let thumbs = [];
|
||||
const remarkCard = document.getElementById(`remark-${remarkId}`);
|
||||
if (remarkCard) {
|
||||
thumbs = Array.from(remarkCard.querySelectorAll('.remark-photo-thumb'));
|
||||
} else if (remarkId) {
|
||||
thumbs = Array.from(document.querySelectorAll(`.remark-photo-thumb[data-remark-id="${remarkId}"]`));
|
||||
}
|
||||
|
||||
// Mettre à jour l'image
|
||||
const img = modal.querySelector('#modal-preview-image');
|
||||
if (img) {
|
||||
img.src = imageUrl;
|
||||
if (thumbs.length === 0) {
|
||||
thumbs = [thumbElement];
|
||||
}
|
||||
|
||||
// Afficher la modal
|
||||
if (typeof bootstrap !== 'undefined' && bootstrap.Modal) {
|
||||
const modalInstance = new bootstrap.Modal(modal);
|
||||
modalInstance.show();
|
||||
currentRemarkCarouselPhotos = thumbs.map(t => ({
|
||||
fullUrl: t.dataset.fullUrl || t.src,
|
||||
thumbUrl: t.dataset.thumbUrl || t.src,
|
||||
imageId: t.dataset.imageId || null,
|
||||
caption: t.dataset.caption || '',
|
||||
remarkId: t.dataset.remarkId || remarkId,
|
||||
element: t
|
||||
}));
|
||||
|
||||
let startIndex = thumbs.indexOf(thumbElement);
|
||||
if (startIndex < 0) startIndex = 0;
|
||||
|
||||
showRemarkCarouselModal(startIndex);
|
||||
}
|
||||
|
||||
function openRemarkPhotoCarouselByRemarkId(remarkId) {
|
||||
const remarkCard = document.getElementById(`remark-${remarkId}`);
|
||||
let firstThumb = null;
|
||||
if (remarkCard) {
|
||||
firstThumb = remarkCard.querySelector('.remark-photo-thumb');
|
||||
} else {
|
||||
firstThumb = document.querySelector(`.remark-photo-thumb[data-remark-id="${remarkId}"]`);
|
||||
}
|
||||
if (firstThumb) {
|
||||
openRemarkPhotoCarousel(firstThumb);
|
||||
}
|
||||
}
|
||||
|
||||
function showRemarkCarouselModal(startIndex) {
|
||||
const modalEl = document.getElementById('remarkPhotoCarouselModal');
|
||||
if (!modalEl) {
|
||||
if (currentRemarkCarouselPhotos[startIndex]) {
|
||||
window.open(currentRemarkCarouselPhotos[startIndex].fullUrl, '_blank');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const carouselInner = document.getElementById('remark-carousel-inner');
|
||||
const carouselIndicators = document.getElementById('remark-carousel-indicators');
|
||||
const total = currentRemarkCarouselPhotos.length;
|
||||
currentRemarkCarouselIndex = startIndex;
|
||||
|
||||
if (!carouselInner) return;
|
||||
|
||||
carouselInner.innerHTML = '';
|
||||
if (carouselIndicators) carouselIndicators.innerHTML = '';
|
||||
|
||||
currentRemarkCarouselPhotos.forEach((photo, idx) => {
|
||||
const item = document.createElement('div');
|
||||
item.className = `carousel-item ${idx === startIndex ? 'active' : ''} h-100`;
|
||||
item.innerHTML = `
|
||||
<div class="d-flex align-items-center justify-content-center h-100 p-2">
|
||||
<img src="${photo.fullUrl}" class="d-block img-fluid mh-100 mw-100" style="object-fit: contain; max-height: 75vh;" alt="${photo.caption}">
|
||||
</div>
|
||||
`;
|
||||
carouselInner.appendChild(item);
|
||||
|
||||
if (carouselIndicators && total > 1) {
|
||||
const ind = document.createElement('button');
|
||||
ind.type = 'button';
|
||||
ind.setAttribute('data-bs-target', '#remarkPhotoCarousel');
|
||||
ind.setAttribute('data-bs-slide-to', idx.toString());
|
||||
if (idx === startIndex) {
|
||||
ind.className = 'active';
|
||||
ind.setAttribute('aria-current', 'true');
|
||||
}
|
||||
ind.setAttribute('aria-label', `Photo ${idx + 1}`);
|
||||
carouselIndicators.appendChild(ind);
|
||||
}
|
||||
});
|
||||
|
||||
const prevBtn = modalEl.querySelector('.carousel-control-prev');
|
||||
const nextBtn = modalEl.querySelector('.carousel-control-next');
|
||||
if (prevBtn) prevBtn.style.display = total > 1 ? 'flex' : 'none';
|
||||
if (nextBtn) nextBtn.style.display = total > 1 ? 'flex' : 'none';
|
||||
if (carouselIndicators) carouselIndicators.style.display = total > 1 ? 'flex' : 'none';
|
||||
|
||||
updateRemarkCarouselActiveSlide(startIndex);
|
||||
|
||||
const carouselEl = document.getElementById('remarkPhotoCarousel');
|
||||
if (carouselEl && typeof bootstrap !== 'undefined' && bootstrap.Carousel) {
|
||||
let bsCarousel = bootstrap.Carousel.getInstance(carouselEl);
|
||||
if (bsCarousel) {
|
||||
bsCarousel.dispose();
|
||||
}
|
||||
bsCarousel = new bootstrap.Carousel(carouselEl, { ride: false, interval: false });
|
||||
bsCarousel.to(startIndex);
|
||||
|
||||
carouselEl.addEventListener('slid.bs.carousel', function (e) {
|
||||
updateRemarkCarouselActiveSlide(e.to);
|
||||
}, { once: false });
|
||||
}
|
||||
|
||||
if (typeof bootstrap !== 'undefined' && bootstrap.Modal) {
|
||||
const bsModal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
||||
bsModal.show();
|
||||
}
|
||||
}
|
||||
|
||||
function updateRemarkCarouselActiveSlide(idx) {
|
||||
currentRemarkCarouselIndex = idx;
|
||||
const photo = currentRemarkCarouselPhotos[idx];
|
||||
const total = currentRemarkCarouselPhotos.length;
|
||||
|
||||
const counterBadge = document.getElementById('remark-carousel-counter');
|
||||
const captionEl = document.getElementById('remark-carousel-caption');
|
||||
const hdBtn = document.getElementById('remark-carousel-hd-btn');
|
||||
const deleteBtn = document.getElementById('remark-carousel-delete-btn');
|
||||
|
||||
if (counterBadge) {
|
||||
counterBadge.textContent = `${idx + 1} / ${total}`;
|
||||
}
|
||||
if (captionEl && photo) {
|
||||
captionEl.textContent = photo.caption || '';
|
||||
}
|
||||
if (hdBtn && photo) {
|
||||
hdBtn.href = photo.fullUrl;
|
||||
}
|
||||
if (deleteBtn) {
|
||||
if (photo && photo.imageId) {
|
||||
deleteBtn.style.display = 'inline-flex';
|
||||
deleteBtn.dataset.imageId = photo.imageId;
|
||||
deleteBtn.dataset.remarkId = photo.remarkId;
|
||||
deleteBtn.onclick = function () {
|
||||
if (photo.element) {
|
||||
const trashBtn = photo.element.parentElement ? photo.element.parentElement.querySelector('.remark-image-delete') : null;
|
||||
if (trashBtn) {
|
||||
trashBtn.click();
|
||||
} else {
|
||||
deleteRemarkImage(photo.imageId, photo.element);
|
||||
}
|
||||
} else {
|
||||
deleteRemarkImage(photo.imageId, deleteBtn);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
deleteBtn.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Support navigation au clavier (flèche gauche / droite) pour le carrousel
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const modalEl = document.getElementById('remarkPhotoCarouselModal');
|
||||
if (modalEl) {
|
||||
modalEl.addEventListener('keydown', function(e) {
|
||||
const carouselEl = document.getElementById('remarkPhotoCarousel');
|
||||
if (!carouselEl || typeof bootstrap === 'undefined' || !bootstrap.Carousel) return;
|
||||
const bsCarousel = bootstrap.Carousel.getInstance(carouselEl);
|
||||
if (!bsCarousel) return;
|
||||
|
||||
if (e.key === 'ArrowLeft') {
|
||||
e.preventDefault();
|
||||
bsCarousel.prev();
|
||||
} else if (e.key === 'ArrowRight') {
|
||||
e.preventDefault();
|
||||
bsCarousel.next();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Backward compatibility for single image preview
|
||||
function showImageModal(imageUrl) {
|
||||
currentRemarkCarouselPhotos = [{
|
||||
fullUrl: imageUrl,
|
||||
thumbUrl: imageUrl,
|
||||
imageId: null,
|
||||
caption: '',
|
||||
remarkId: null,
|
||||
element: null
|
||||
}];
|
||||
showRemarkCarouselModal(0);
|
||||
}
|
||||
|
|
@ -365,8 +365,15 @@
|
|||
<!-- ✅ Première Ligne : Numéro, Statut -->
|
||||
<div class="d-flex justify-content-between align-items-center pe-5">
|
||||
<!-- Numéro -->
|
||||
<div class="remark-number">
|
||||
{{ remark.remark_number }}
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="remark-number">
|
||||
{{ remark.remark_number }}
|
||||
</div>
|
||||
{% if remark.images.exists %}
|
||||
<button type="button" class="btn btn-sm btn-outline-primary rounded-pill px-2 py-0" onclick="openRemarkPhotoCarouselByRemarkId({{ remark.id }})" title="{% translate 'Visualiser les photos dans le carrousel' %}">
|
||||
<i class="bi bi-images me-1"></i>{{ remark.images.count }}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- ✅ Statut -->
|
||||
|
|
@ -408,9 +415,16 @@
|
|||
<div class="remark-image-wrapper">
|
||||
{% private_file_url image 'file' as image_file_url %}
|
||||
{% private_file_url image 'thumbnail' as image_thumbnail_url %}
|
||||
<a href="{{ image_file_url }}" target="_blank">
|
||||
<img src="{{ image_thumbnail_url }}" alt="{% translate 'Image de la remarque' %}">
|
||||
</a>
|
||||
<img src="{{ image_thumbnail_url }}"
|
||||
alt="{% translate 'Image de la remarque' %}"
|
||||
class="remark-photo-thumb shadow-sm"
|
||||
style="cursor: pointer;"
|
||||
data-remark-id="{{ remark.id }}"
|
||||
data-full-url="{{ image_file_url }}"
|
||||
data-thumb-url="{{ image_thumbnail_url }}"
|
||||
data-image-id="{{ image.id }}"
|
||||
data-caption="{% translate 'Remarque' %} #{{ remark.remark_number }}{% if entry.created_at %} — {{ entry.created_at|timezone:'Europe/Paris'|date:'d/m/Y H:i' }}{% endif %}"
|
||||
onclick="openRemarkPhotoCarousel(this)">
|
||||
{% if can_change_control %}
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-danger remark-image-delete"
|
||||
|
|
@ -445,9 +459,16 @@
|
|||
<div class="remark-image-wrapper">
|
||||
{% private_file_url image 'file' as image_file_url %}
|
||||
{% private_file_url image 'thumbnail' as image_thumbnail_url %}
|
||||
<a href="{{ image_file_url }}" target="_blank">
|
||||
<img src="{{ image_thumbnail_url }}" alt="{% translate 'Image de la remarque' %}">
|
||||
</a>
|
||||
<img src="{{ image_thumbnail_url }}"
|
||||
alt="{% translate 'Image de la remarque' %}"
|
||||
class="remark-photo-thumb shadow-sm"
|
||||
style="cursor: pointer;"
|
||||
data-remark-id="{{ remark.id }}"
|
||||
data-full-url="{{ image_file_url }}"
|
||||
data-thumb-url="{{ image_thumbnail_url }}"
|
||||
data-image-id="{{ image.id }}"
|
||||
data-caption="{% translate 'Remarque' %} #{{ remark.remark_number }}"
|
||||
onclick="openRemarkPhotoCarousel(this)">
|
||||
{% if can_change_control %}
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-danger remark-image-delete"
|
||||
|
|
@ -646,9 +667,16 @@
|
|||
<div class="remark-image-wrapper">
|
||||
{% private_file_url image 'file' as image_file_url %}
|
||||
{% private_file_url image 'thumbnail' as image_thumbnail_url %}
|
||||
<a href="{{ image_file_url }}" target="_blank">
|
||||
<img src="{{ image_thumbnail_url }}" alt="{% translate 'Image de la remarque' %}">
|
||||
</a>
|
||||
<img src="{{ image_thumbnail_url }}"
|
||||
alt="{% translate 'Image de la remarque' %}"
|
||||
class="remark-photo-thumb shadow-sm"
|
||||
style="cursor: pointer;"
|
||||
data-remark-id="{{ remark.id }}"
|
||||
data-full-url="{{ image_file_url }}"
|
||||
data-thumb-url="{{ image_thumbnail_url }}"
|
||||
data-image-id="{{ image.id }}"
|
||||
data-caption="{% translate 'Remarque' %} #{{ remark.remark_number }} ({% translate 'Archivée' %})"
|
||||
onclick="openRemarkPhotoCarousel(this)">
|
||||
{% if can_change_control %}
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-danger remark-image-delete"
|
||||
|
|
@ -748,6 +776,50 @@
|
|||
|
||||
</div>
|
||||
|
||||
<!-- Modal Carrousel pour les photos d'une remarque -->
|
||||
<div class="modal fade" id="remarkPhotoCarouselModal" tabindex="-1" aria-labelledby="remarkPhotoCarouselModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl modal-dialog-centered">
|
||||
<div class="modal-content bg-dark text-white shadow-lg border-0">
|
||||
<div class="modal-header border-bottom border-secondary py-2 px-3">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<i class="bi bi-images text-primary fs-5"></i>
|
||||
<h5 class="modal-title fs-6 mb-0 text-white" id="remarkPhotoCarouselModalLabel">{% translate "Photos de la remarque" %}</h5>
|
||||
<span class="badge bg-primary rounded-pill px-2 py-1 fs-7" id="remark-carousel-counter">1 / 1</span>
|
||||
</div>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="{% translate 'Fermer' %}"></button>
|
||||
</div>
|
||||
<div class="modal-body p-0 position-relative bg-black d-flex align-items-center justify-content-center overflow-hidden" style="min-height: 350px; max-height: 80vh;">
|
||||
<div id="remarkPhotoCarousel" class="carousel slide w-100 h-100 d-flex flex-column justify-content-center" data-bs-ride="false">
|
||||
<div class="carousel-indicators mb-2" id="remark-carousel-indicators"></div>
|
||||
<div class="carousel-inner h-100" id="remark-carousel-inner"></div>
|
||||
<button class="carousel-control-prev" type="button" data-bs-target="#remarkPhotoCarousel" data-bs-slide="prev" style="z-index: 10;">
|
||||
<span class="carousel-control-prev-icon p-3 rounded-circle bg-dark bg-opacity-75" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">{% translate "Précédent" %}</span>
|
||||
</button>
|
||||
<button class="carousel-control-next" type="button" data-bs-target="#remarkPhotoCarousel" data-bs-slide="next" style="z-index: 10;">
|
||||
<span class="carousel-control-next-icon p-3 rounded-circle bg-dark bg-opacity-75" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">{% translate "Suivant" %}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-top border-secondary py-2 px-3 d-flex justify-content-between align-items-center bg-dark">
|
||||
<div class="small text-muted text-truncate me-2" id="remark-carousel-caption"></div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<a id="remark-carousel-hd-btn" href="#" target="_blank" class="btn btn-sm btn-outline-light d-inline-flex align-items-center gap-1" title="{% translate 'Ouvrir en plein écran HD' %}">
|
||||
<i class="bi bi-box-arrow-up-right"></i> <span class="d-none d-sm-inline">{% translate "Plein écran" %}</span>
|
||||
</a>
|
||||
{% if can_change_control %}
|
||||
<button type="button" id="remark-carousel-delete-btn" class="btn btn-sm btn-outline-danger d-inline-flex align-items-center gap-1" title="{% translate 'Supprimer cette photo' %}">
|
||||
<i class="bi bi-trash"></i> <span class="d-none d-sm-inline">{% translate "Supprimer" %}</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">{% translate "Fermer" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Mettre à jour le badge du nombre de remarques en attente
|
||||
async function updatePendingBadge() {
|
||||
|
|
|
|||
|
|
@ -411,8 +411,15 @@
|
|||
|
||||
<!-- ✅ Première Ligne : Numéro, Statut -->
|
||||
<div class="d-flex justify-content-between align-items-center pe-5">
|
||||
<div class="remark-number">
|
||||
{{ remark.remark_number }}
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="remark-number">
|
||||
{{ remark.remark_number }}
|
||||
</div>
|
||||
{% if remark.images.exists %}
|
||||
<button type="button" class="btn btn-sm btn-outline-primary rounded-pill px-2 py-0" onclick="openRemarkPhotoCarouselByRemarkId({{ remark.id }})" title="{% translate 'Visualiser les photos dans le carrousel' %}">
|
||||
<i class="bi bi-images me-1"></i>{{ remark.images.count }}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<span id="status-{{ remark.id }}" class="badge {{ remark.status|status_badge }}">
|
||||
|
|
@ -454,9 +461,16 @@
|
|||
<div class="remark-image-wrapper">
|
||||
{% private_file_url image 'file' as image_file_url %}
|
||||
{% private_file_url image 'thumbnail' as image_thumb_url %}
|
||||
<a href="{{ image_file_url }}" target="_blank">
|
||||
<img src="{{ image_thumb_url }}" alt="{% translate 'Image de la remarque' %}">
|
||||
</a>
|
||||
<img src="{{ image_thumb_url }}"
|
||||
alt="{% translate 'Image de la remarque' %}"
|
||||
class="remark-photo-thumb shadow-sm"
|
||||
style="cursor: pointer;"
|
||||
data-remark-id="{{ remark.id }}"
|
||||
data-full-url="{{ image_file_url }}"
|
||||
data-thumb-url="{{ image_thumb_url }}"
|
||||
data-image-id="{{ image.id }}"
|
||||
data-caption="{% translate 'Remarque' %} #{{ remark.remark_number }}{% if entry.created_at %} — {{ entry.created_at|timezone:'Europe/Paris'|date:'d/m/Y H:i' }}{% endif %}"
|
||||
onclick="openRemarkPhotoCarousel(this)">
|
||||
{% if can_change_control %}
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-danger remark-image-delete"
|
||||
|
|
@ -491,9 +505,16 @@
|
|||
<div class="remark-image-wrapper">
|
||||
{% private_file_url image 'file' as image_file_url %}
|
||||
{% private_file_url image 'thumbnail' as image_thumb_url %}
|
||||
<a href="{{ image_file_url }}" target="_blank">
|
||||
<img src="{{ image_thumb_url }}" alt="{% translate 'Image de la remarque' %}">
|
||||
</a>
|
||||
<img src="{{ image_thumb_url }}"
|
||||
alt="{% translate 'Image de la remarque' %}"
|
||||
class="remark-photo-thumb shadow-sm"
|
||||
style="cursor: pointer;"
|
||||
data-remark-id="{{ remark.id }}"
|
||||
data-full-url="{{ image_file_url }}"
|
||||
data-thumb-url="{{ image_thumb_url }}"
|
||||
data-image-id="{{ image.id }}"
|
||||
data-caption="{% translate 'Remarque' %} #{{ remark.remark_number }}"
|
||||
onclick="openRemarkPhotoCarousel(this)">
|
||||
{% if can_change_control %}
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-danger remark-image-delete"
|
||||
|
|
@ -686,9 +707,16 @@
|
|||
{% private_file_url image 'file' as image_file_url %}
|
||||
{% private_file_url image 'thumbnail' as image_thumb_url %}
|
||||
<div class="remark-image-wrapper">
|
||||
<a href="{{ image_file_url }}" target="_blank">
|
||||
<img src="{{ image_thumb_url }}" alt="{% translate 'Image de la remarque' %}">
|
||||
</a>
|
||||
<img src="{{ image_thumb_url }}"
|
||||
alt="{% translate 'Image de la remarque' %}"
|
||||
class="remark-photo-thumb shadow-sm"
|
||||
style="cursor: pointer;"
|
||||
data-remark-id="{{ remark.id }}"
|
||||
data-full-url="{{ image_file_url }}"
|
||||
data-thumb-url="{{ image_thumb_url }}"
|
||||
data-image-id="{{ image.id }}"
|
||||
data-caption="{% translate 'Remarque' %} #{{ remark.remark_number }} ({% translate 'Archivée' %})"
|
||||
onclick="openRemarkPhotoCarousel(this)">
|
||||
{% if can_change_control %}
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-danger remark-image-delete"
|
||||
|
|
@ -1123,6 +1151,49 @@ document.getElementById('force-sync-btn')?.addEventListener('click', async () =>
|
|||
await displayPendingRemarks();
|
||||
}
|
||||
});
|
||||
<!-- Modal Carrousel pour les photos d'une remarque -->
|
||||
<div class="modal fade" id="remarkPhotoCarouselModal" tabindex="-1" aria-labelledby="remarkPhotoCarouselModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl modal-dialog-centered">
|
||||
<div class="modal-content bg-dark text-white shadow-lg border-0">
|
||||
<div class="modal-header border-bottom border-secondary py-2 px-3">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<i class="bi bi-images text-primary fs-5"></i>
|
||||
<h5 class="modal-title fs-6 mb-0 text-white" id="remarkPhotoCarouselModalLabel">{% translate "Photos de la remarque" %}</h5>
|
||||
<span class="badge bg-primary rounded-pill px-2 py-1 fs-7" id="remark-carousel-counter">1 / 1</span>
|
||||
</div>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="{% translate 'Fermer' %}"></button>
|
||||
</div>
|
||||
<div class="modal-body p-0 position-relative bg-black d-flex align-items-center justify-content-center overflow-hidden" style="min-height: 300px; max-height: 80vh;">
|
||||
<div id="remarkPhotoCarousel" class="carousel slide w-100 h-100 d-flex flex-column justify-content-center" data-bs-ride="false">
|
||||
<div class="carousel-indicators mb-2" id="remark-carousel-indicators"></div>
|
||||
<div class="carousel-inner h-100" id="remark-carousel-inner"></div>
|
||||
<button class="carousel-control-prev" type="button" data-bs-target="#remarkPhotoCarousel" data-bs-slide="prev" style="z-index: 10;">
|
||||
<span class="carousel-control-prev-icon p-3 rounded-circle bg-dark bg-opacity-75" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">{% translate "Précédent" %}</span>
|
||||
</button>
|
||||
<button class="carousel-control-next" type="button" data-bs-target="#remarkPhotoCarousel" data-bs-slide="next" style="z-index: 10;">
|
||||
<span class="carousel-control-next-icon p-3 rounded-circle bg-dark bg-opacity-75" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">{% translate "Suivant" %}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-top border-secondary py-2 px-3 d-flex justify-content-between align-items-center bg-dark">
|
||||
<div class="small text-muted text-truncate me-2" id="remark-carousel-caption"></div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<a id="remark-carousel-hd-btn" href="#" target="_blank" class="btn btn-sm btn-outline-light d-inline-flex align-items-center gap-1" title="{% translate 'Ouvrir en plein écran HD' %}">
|
||||
<i class="bi bi-box-arrow-up-right"></i> <span class="d-none d-sm-inline">{% translate "Plein écran" %}</span>
|
||||
</a>
|
||||
{% if can_change_control %}
|
||||
<button type="button" id="remark-carousel-delete-btn" class="btn btn-sm btn-outline-danger d-inline-flex align-items-center gap-1" title="{% translate 'Supprimer cette photo' %}">
|
||||
<i class="bi bi-trash"></i> <span class="d-none d-sm-inline">{% translate "Supprimer" %}</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">{% translate "Fermer" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
{% endblock content %}
|
||||
|
|
|
|||
Loading…
Reference in a new issue