Docfile's picture
Update api/static/js/admin.js
74828dc verified
// Admin JavaScript for the backend management interface
document.addEventListener('DOMContentLoaded', function() {
// Initialize theme
initTheme();
// Setup dashboard functionality
setupDashboardCards();
// Setup admin forms
setupMatiereForm();
setupSousCategorieForm();
setupTexteForm();
// Setup content block editor
setupContentBlockEditor();
// Setup image management
setupImageUploader();
setupImageGallery();
// Setup theme toggle
setupThemeToggle();
});
// Initialize theme based on user preference
function initTheme() {
const userPreference = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', userPreference);
// Update theme icon
updateThemeIcon(userPreference);
}
// Setup theme toggle functionality
function setupThemeToggle() {
const themeToggle = document.getElementById('theme-toggle');
if (!themeToggle) return;
themeToggle.addEventListener('click', function() {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
// Update theme attribute
document.documentElement.setAttribute('data-theme', newTheme);
// Save preference to localStorage
localStorage.setItem('theme', newTheme);
// Update icon
updateThemeIcon(newTheme);
// Send theme preference to server
saveThemePreference(newTheme);
});
}
// Update the theme toggle icon based on current theme
function updateThemeIcon(theme) {
const themeToggle = document.getElementById('theme-toggle');
if (!themeToggle) return;
// Update icon based on theme
if (theme === 'dark') {
themeToggle.innerHTML = '<i class="fas fa-sun"></i>';
themeToggle.setAttribute('title', 'Activer le mode clair');
} else {
themeToggle.innerHTML = '<i class="fas fa-moon"></i>';
themeToggle.setAttribute('title', 'Activer le mode sombre');
}
}
// Save theme preference to server
function saveThemePreference(theme) {
const formData = new FormData();
formData.append('theme', theme);
fetch('/set_theme', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
console.log('Theme preference saved:', data);
})
.catch(error => {
console.error('Error saving theme preference:', error);
});
}
// Setup dashboard cards with hover effects
function setupDashboardCards() {
const dashboardCards = document.querySelectorAll('.admin-card');
dashboardCards.forEach(card => {
card.addEventListener('mouseenter', function() {
this.style.transform = 'translateY(-5px)';
this.style.boxShadow = 'var(--hover-shadow)';
this.style.transition = 'transform 0.3s ease, box-shadow 0.3s ease';
});
card.addEventListener('mouseleave', function() {
this.style.transform = 'translateY(0)';
this.style.boxShadow = 'var(--shadow)';
});
});
}
// Setup matiere form functionality
function setupMatiereForm() {
// Show edit form when edit button is clicked
const editButtons = document.querySelectorAll('.edit-matiere-btn');
editButtons.forEach(button => {
button.addEventListener('click', function() {
const matiereId = this.getAttribute('data-id');
const matiereName = this.getAttribute('data-name');
const matiereColor = this.getAttribute('data-color');
const editForm = document.getElementById('edit-matiere-form');
if (editForm) {
const idInput = editForm.querySelector('input[name="matiere_id"]');
const nameInput = editForm.querySelector('input[name="nom"]');
const colorInput = editForm.querySelector('input[name="color_code"]');
idInput.value = matiereId;
nameInput.value = matiereName;
colorInput.value = matiereColor;
// Show the edit form
document.getElementById('add-matiere-section').classList.add('d-none');
document.getElementById('edit-matiere-section').classList.remove('d-none');
// Scroll to edit form
editForm.scrollIntoView({ behavior: 'smooth' });
}
});
});
// Cancel edit button
const cancelEditButton = document.getElementById('cancel-edit-matiere');
if (cancelEditButton) {
cancelEditButton.addEventListener('click', function(e) {
e.preventDefault();
document.getElementById('add-matiere-section').classList.remove('d-none');
document.getElementById('edit-matiere-section').classList.add('d-none');
});
}
// Color picker preview
const colorPickers = document.querySelectorAll('input[type="color"]');
colorPickers.forEach(picker => {
picker.addEventListener('input', function() {
// Find adjacent preview element or create one
let preview = this.nextElementSibling;
if (!preview || !preview.classList.contains('color-preview')) {
preview = document.createElement('span');
preview.className = 'color-preview';
preview.style.display = 'inline-block';
preview.style.width = '24px';
preview.style.height = '24px';
preview.style.borderRadius = '50%';
preview.style.marginLeft = '10px';
this.parentNode.insertBefore(preview, this.nextSibling);
}
preview.style.backgroundColor = this.value;
});
// Trigger once to initialize
const event = new Event('input');
picker.dispatchEvent(event);
});
}
// Setup sous categorie form functionality
function setupSousCategorieForm() {
// Show edit form when edit button is clicked
const editButtons = document.querySelectorAll('.edit-sous-categorie-btn');
editButtons.forEach(button => {
button.addEventListener('click', function() {
const sousCategorieId = this.getAttribute('data-id');
const sousCategorieName = this.getAttribute('data-name');
const matiereId = this.getAttribute('data-matiere-id');
const editForm = document.getElementById('edit-sous-categorie-form');
if (editForm) {
const idInput = editForm.querySelector('input[name="sous_categorie_id"]');
const nameInput = editForm.querySelector('input[name="nom"]');
const matiereSelect = editForm.querySelector('select[name="matiere_id"]');
idInput.value = sousCategorieId;
nameInput.value = sousCategorieName;
matiereSelect.value = matiereId;
// Show the edit form
document.getElementById('add-sous-categorie-section').classList.add('d-none');
document.getElementById('edit-sous-categorie-section').classList.remove('d-none');
// Scroll to edit form
editForm.scrollIntoView({ behavior: 'smooth' });
}
});
});
// Cancel edit button
const cancelEditButton = document.getElementById('cancel-edit-sous-categorie');
if (cancelEditButton) {
cancelEditButton.addEventListener('click', function(e) {
e.preventDefault();
document.getElementById('add-sous-categorie-section').classList.remove('d-none');
document.getElementById('edit-sous-categorie-section').classList.add('d-none');
});
}
// Matiere select filter
const matiereFilterSelect = document.getElementById('matiere-filter');
if (matiereFilterSelect) {
matiereFilterSelect.addEventListener('change', function() {
const selectedMatiereId = this.value;
const sousCategorieRows = document.querySelectorAll('.sous-categorie-row');
sousCategorieRows.forEach(row => {
if (selectedMatiereId === '' || row.getAttribute('data-matiere-id') === selectedMatiereId) {
row.style.display = '';
} else {
row.style.display = 'none';
}
});
});
}
}
// Setup texte form functionality
function setupTexteForm() {
// Matiere select change - populate sous-categories
const matiereSelect = document.getElementById('matiere-select');
if (matiereSelect) {
matiereSelect.addEventListener('change', function() {
const matiereId = this.value;
const sousCategorieSelect = document.getElementById('sous-categorie-select');
if (matiereId && sousCategorieSelect) {
// Clear current options
sousCategorieSelect.innerHTML = '<option value="">Sélectionnez une sous-catégorie</option>';
// Fetch sous-categories for the selected matiere
fetch(`/get_sous_categories/${matiereId}`)
.then(response => response.json())
.then(data => {
data.forEach(sousCategorie => {
const option = document.createElement('option');
option.value = sousCategorie.id;
option.textContent = sousCategorie.nom;
sousCategorieSelect.appendChild(option);
});
})
.catch(error => {
console.error('Error loading sous-categories:', error);
});
}
});
}
}
// Setup content block editor
function setupContentBlockEditor() {
const blocksContainer = document.getElementById('blocks-container');
const addBlockButton = document.getElementById('add-block-button');
const saveBlocksButton = document.getElementById('save-blocks-button');
if (!blocksContainer) return;
// Add new block
if (addBlockButton) {
addBlockButton.addEventListener('click', function() {
addContentBlock();
});
}
// Make blocks sortable
if (window.Sortable) {
new Sortable(blocksContainer, {
animation: 150,
handle: '.block-handle',
ghostClass: 'block-ghost',
onEnd: function() {
// Update order numbers
updateBlockOrder();
}
});
}
// Save blocks
if (saveBlocksButton) {
saveBlocksButton.addEventListener('click', function() {
saveContentBlocks();
});
}
// Add event listeners for existing blocks
setupExistingBlockControls();
}
// Setup controls for existing blocks
function setupExistingBlockControls() {
// Setup delete buttons
const deleteButtons = document.querySelectorAll('.delete-block-btn');
deleteButtons.forEach(button => {
button.addEventListener('click', function() {
if (confirm('Êtes-vous sûr de vouloir supprimer ce bloc ?')) {
const blockEditor = this.closest('.block-editor');
blockEditor.remove();
updateBlockOrder();
}
});
});
// Setup image position selects
const positionSelects = document.querySelectorAll('.image-position-select');
positionSelects.forEach(select => {
select.addEventListener('change', function() {
updateBlockImagePreview(this.closest('.block-editor'));
});
});
// Setup image selection buttons
const imageSelectButtons = document.querySelectorAll('.select-image-btn');
imageSelectButtons.forEach(button => {
button.addEventListener('click', function() {
const blockEditor = this.closest('.block-editor');
const galleryModal = document.getElementById('image-gallery-modal');
if (galleryModal) {
// Set current block ID as data attribute for the modal
galleryModal.setAttribute('data-target-block', blockEditor.getAttribute('data-block-id'));
// Show the modal
const modal = new bootstrap.Modal(galleryModal);
modal.show();
}
});
});
// Setup image remove buttons
const removeImageButtons = document.querySelectorAll('.remove-image-btn');
removeImageButtons.forEach(button => {
button.addEventListener('click', function() {
const blockEditor = this.closest('.block-editor');
const imageIdInput = blockEditor.querySelector('.block-image-id');
const imagePreview = blockEditor.querySelector('.image-preview');
if (imageIdInput) {
imageIdInput.value = '';
}
if (imagePreview) {
imagePreview.src = '';
imagePreview.style.display = 'none';
}
// Hide remove button
this.style.display = 'none';
// Show select button
const selectButton = blockEditor.querySelector('.select-image-btn');
if (selectButton) {
selectButton.style.display = 'inline-block';
}
});
});
}
// Add a new content block to the editor
function addContentBlock(data = null) {
const blocksContainer = document.getElementById('blocks-container');
if (!blocksContainer) return;
// Generate a unique ID for the block
const blockId = 'block-' + Date.now();
// Create block HTML
const blockHtml = `
<div class="block-editor" data-block-id="${blockId}">
<div class="block-editor-header">
<div class="d-flex align-items-center">
<span class="block-handle"><i class="fas fa-grip-vertical"></i></span>
<h3 class="block-editor-title">Bloc #${blocksContainer.children.length + 1}</h3>
</div>
<div class="block-editor-actions">
<button type="button" class="btn btn-danger btn-sm delete-block-btn">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
<div class="form-group">
<label for="${blockId}-title">Titre du bloc (optionnel)</label>
<input type="text" class="form-control block-title" id="${blockId}-title" value="${data?.title || ''}">
</div>
<div class="form-group">
<label for="${blockId}-content">Contenu du bloc</label>
<textarea class="form-control block-content" id="${blockId}-content" rows="5">${data?.content || ''}</textarea>
</div>
<div class="form-group">
<label>Image</label>
<div class="d-flex align-items-center mb-2">
<button type="button" class="btn btn-primary btn-sm select-image-btn" style="${data?.image ? 'display:none;' : ''}">
<i class="fas fa-image"></i> Sélectionner une image
</button>
<button type="button" class="btn btn-warning btn-sm remove-image-btn ml-2" style="${data?.image ? '' : 'display:none;'}">
<i class="fas fa-times"></i> Retirer l'image
</button>
</div>
<input type="hidden" class="block-image-id" value="${data?.image?.id || ''}">
<img src="${data?.image?.src || ''}" alt="Preview" class="image-preview mb-2" style="${data?.image ? '' : 'display:none;'}">
<div class="form-group">
<label for="${blockId}-image-position">Position de l'image</label>
<select class="form-control image-position-select" id="${blockId}-image-position">
<option value="left" ${data?.image_position === 'left' ? 'selected' : ''}>Gauche</option>
<option value="right" ${data?.image_position === 'right' ? 'selected' : ''}>Droite</option>
<option value="top" ${data?.image_position === 'top' ? 'selected' : ''}>Haut</option>
</select>
</div>
</div>
</div>
`;
// Add the block to the container
blocksContainer.insertAdjacentHTML('beforeend', blockHtml);
// Setup event listeners for the new block
const newBlock = blocksContainer.lastElementChild;
// Delete button
const deleteButton = newBlock.querySelector('.delete-block-btn');
if (deleteButton) {
deleteButton.addEventListener('click', function() {
if (confirm('Êtes-vous sûr de vouloir supprimer ce bloc ?')) {
newBlock.remove();
updateBlockOrder();
}
});
}
// Image position select
const positionSelect = newBlock.querySelector('.image-position-select');
if (positionSelect) {
positionSelect.addEventListener('change', function() {
updateBlockImagePreview(newBlock);
});
}
// Image selection button
const imageSelectButton = newBlock.querySelector('.select-image-btn');
if (imageSelectButton) {
imageSelectButton.addEventListener('click', function() {
const galleryModal = document.getElementById('image-gallery-modal');
if (galleryModal) {
// Set current block ID as data attribute for the modal
galleryModal.setAttribute('data-target-block', blockId);
// Show the modal
const modal = new bootstrap.Modal(galleryModal);
modal.show();
}
});
}
// Image remove button
const removeImageButton = newBlock.querySelector('.remove-image-btn');
if (removeImageButton) {
removeImageButton.addEventListener('click', function() {
const imageIdInput = newBlock.querySelector('.block-image-id');
const imagePreview = newBlock.querySelector('.image-preview');
if (imageIdInput) {
imageIdInput.value = '';
}
if (imagePreview) {
imagePreview.src = '';
imagePreview.style.display = 'none';
}
// Hide remove button
removeImageButton.style.display = 'none';
// Show select button
if (imageSelectButton) {
imageSelectButton.style.display = 'inline-block';
}
});
}
// Scroll to the new block
newBlock.scrollIntoView({ behavior: 'smooth' });
}
// Update block order numbers in the UI
function updateBlockOrder() {
const blocks = document.querySelectorAll('.block-editor');
blocks.forEach((block, index) => {
const titleEl = block.querySelector('.block-editor-title');
if (titleEl) {
titleEl.textContent = `Bloc #${index + 1}`;
}
});
}
// Update image preview based on position
function updateBlockImagePreview(blockEditor) {
// This function would apply CSS classes to show how the image position
// will look in the frontend
const positionSelect = blockEditor.querySelector('.image-position-select');
const imagePreview = blockEditor.querySelector('.image-preview');
if (!positionSelect || !imagePreview || imagePreview.style.display === 'none') {
return;
}
const position = positionSelect.value;
// Remove existing position classes
imagePreview.classList.remove('position-left', 'position-right', 'position-top');
// Add the selected position class
imagePreview.classList.add(`position-${position}`);
// Apply some simple styling to demonstrate the position
switch (position) {
case 'left':
imagePreview.style.float = 'left';
imagePreview.style.marginRight = '15px';
imagePreview.style.marginBottom = '10px';
imagePreview.style.width = '30%';
break;
case 'right':
imagePreview.style.float = 'right';
imagePreview.style.marginLeft = '15px';
imagePreview.style.marginBottom = '10px';
imagePreview.style.width = '30%';
break;
case 'top':
imagePreview.style.float = 'none';
imagePreview.style.marginRight = '0';
imagePreview.style.marginLeft = '0';
imagePreview.style.marginBottom = '15px';
imagePreview.style.width = '100%';
break;
}
}
// Save content blocks
function saveContentBlocks() {
const blocksContainer = document.getElementById('blocks-container');
const blocksDataInput = document.getElementById('blocks-data');
if (!blocksContainer || !blocksDataInput) return;
const blocks = blocksContainer.querySelectorAll('.block-editor');
const blocksData = [];
blocks.forEach((block, index) => {
const blockId = block.getAttribute('data-block-id');
const title = block.querySelector('.block-title').value;
const content = block.querySelector('.block-content').value;
const imageId = block.querySelector('.block-image-id').value;
const imagePosition = block.querySelector('.image-position-select').value;
blocksData.push({
id: blockId,
title: title,
content: content,
image_id: imageId,
image_position: imagePosition,
order: index
});
});
// Set the blocks data as JSON in the hidden input
blocksDataInput.value = JSON.stringify(blocksData);
// Submit the form
const form = document.getElementById('blocks-form');
if (form) {
form.submit();
}
}
// Setup image uploader
function setupImageUploader() {
const imageUploadForm = document.getElementById('image-upload-form');
const imageFileInput = document.getElementById('image-file');
const imagePreview = document.getElementById('upload-image-preview');
if (imageFileInput && imagePreview) {
imageFileInput.addEventListener('change', function() {
if (this.files && this.files[0]) {
const reader = new FileReader();
reader.onload = function(e) {
imagePreview.src = e.target.result;
imagePreview.style.display = 'block';
};
reader.readAsDataURL(this.files[0]);
}
});
}
if (imageUploadForm) {
imageUploadForm.addEventListener('submit', function(e) {
const fileInput = this.querySelector('#image-file');
if (!fileInput.files || fileInput.files.length === 0) {
e.preventDefault();
alert('Veuillez sélectionner une image.');
}
});
}
}
// Setup image gallery
function setupImageGallery() {
// Handle image selection from gallery
const galleryItems = document.querySelectorAll('.gallery-item');
galleryItems.forEach(item => {
item.addEventListener('click', function() {
const imageId = this.getAttribute('data-image-id');
const imageSrc = this.querySelector('img').src;
const galleryModal = document.getElementById('image-gallery-modal');
if (galleryModal) {
const targetBlockId = galleryModal.getAttribute('data-target-block');
const blockEditor = document.querySelector(`.block-editor[data-block-id="${targetBlockId}"]`);
if (blockEditor) {
// Update the image ID input
const imageIdInput = blockEditor.querySelector('.block-image-id');
if (imageIdInput) {
imageIdInput.value = imageId;
}
// Update the image preview
const imagePreview = blockEditor.querySelector('.image-preview');
if (imagePreview) {
imagePreview.src = imageSrc;
imagePreview.style.display = 'block';
}
// Hide select button and show remove button
const selectButton = blockEditor.querySelector('.select-image-btn');
const removeButton = blockEditor.querySelector('.remove-image-btn');
if (selectButton) {
selectButton.style.display = 'none';
}
if (removeButton) {
removeButton.style.display = 'inline-block';
}
// Update image preview position
updateBlockImagePreview(blockEditor);
}
// Close the modal
const modal = bootstrap.Modal.getInstance(galleryModal);
if (modal) {
modal.hide();
}
}
});
});
}