Spaces:
Sleeping
Sleeping
| // 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(); | |
| } | |
| } | |
| }); | |
| }); | |
| } | |