diff --git a/admin-dashboard.html b/admin-dashboard.html index 7868f6e..933cdb3 100644 --- a/admin-dashboard.html +++ b/admin-dashboard.html @@ -1664,213 +1664,1018 @@ function preventDefaults(e) { e.stopPropagation(); } -/* Reservations Management Section */ - -// Function to load and display reservations -async function loadReservations() { - const tbody = document.getElementById('reservationsTable'); - try { - const response = await fetch('/api/reservations'); - if (!response.ok) throw new Error('Failed to load reservations'); +// Handle image upload +function handleImageUpload(event) { + const fileInput = event.target; + const file = fileInput.files[0]; + + if (!file) return; + + // Check file type + const validImageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml']; + if (!validImageTypes.includes(file.type)) { + showNotification('Vyberte prosím soubor obrázku (JPG, PNG, GIF, SVG)', 'warning'); + fileInput.value = ''; // Reset file input + return; + } + + // Check file size (max 5MB) + const maxSize = 5 * 1024 * 1024; // 5MB + if (file.size > maxSize) { + showNotification('Maximální velikost souboru je 5MB', 'warning'); + fileInput.value = ''; // Reset file input + return; + } + + // Show loading state + const previewContainer = document.getElementById('imagePreviewContainer'); + const dragDropMessage = document.querySelector('.drag-drop-message'); + const bannerPreview = document.getElementById('bannerPreview'); + + if (previewContainer) { + previewContainer.style.display = 'block'; + previewContainer.innerHTML = '
Načítání...
'; + } + + // Hide the drag & drop message + if (dragDropMessage) { + dragDropMessage.style.display = 'none'; + } + + // Process the image + const reader = new FileReader(); + + reader.onload = function(e) { + // Update the image preview + const bannerImagePreview = document.getElementById('bannerImagePreview'); + if (bannerImagePreview) { + bannerImagePreview.src = e.target.result; + bannerImagePreview.style.display = 'block'; + bannerImagePreview.classList.remove('d-none'); + + // Show remove button + const removeBtn = document.getElementById('removeImageBtn'); + if (removeBtn) removeBtn.style.display = 'inline-block'; + + // Update the current image + currentImage = e.target.result; + + // Update the banner preview + updateBannerPreview(); + + // Show the preview container + if (previewContainer) { + previewContainer.style.display = 'block'; + previewContainer.innerHTML = ''; + previewContainer.appendChild(bannerImagePreview); + } + } - const reservations = await response.json(); - window.allReservations = reservations; // Store for filtering + // Hide loading container + if (previewContainer) { + previewContainer.style.display = 'none'; + } + + // Show templates section if it exists + const bannerTemplates = document.getElementById('bannerTemplates'); + if (bannerTemplates) { + bannerTemplates.style.display = 'block'; + } + + // Update banner preview with the new image + updateBannerPreview(); + }; + + reader.onerror = function() { + showNotification('Při načítání obrázku došlo k chybě. Zkuste to prosím znovu.', 'error'); + fileInput.value = ''; // Reset file input + + // Reset preview + if (previewContainer) { + previewContainer.innerHTML = ''; + previewContainer.style.display = 'none'; + } + + // Show drag & drop message again + if (dragDropMessage) { + dragDropMessage.style.display = 'flex'; + } + }; + + reader.readAsDataURL(file); +} - displayReservations(reservations); - updateVehicleFilter(reservations); +// Hardcoded apps data - should match the ones in index.html +window.HARDCODED_APPS = [ + { + id: 'hardcoded-car', + name: 'Záznam služebních jízd', + url: '/evidence-aut', + description: 'Jednoduchý systém pro evidenci a správu jízd služebními vozidly.', + icon: 'fa-car-side', + color: 'blue' + }, + { + id: 'hardcoded-lunch', + name: 'Objednávka obědů', + url: 'http://ppc-app/pwkweb2/', + description: 'Portál pro objednávku a přehled firemních obědů', + icon: 'fa-utensils', + color: 'green' + }, + { + id: 'hardcoded-osticket', + name: 'OSTicket', + url: 'http://osticket/', + description: 'Systém technické podpory a hlášení problémů', + icon: 'fa-headset', + color: 'orange' + }, + { + id: 'hardcoded-kanboard', + name: 'Kanboard', + url: 'http://kanboard/', + description: 'Správa úkolů a projektů v přehledném kanban stylu', + icon: 'fa-tasks', + color: 'purple' + } +]; + +console.log("HARDCODED_APPS defined:", window.HARDCODED_APPS); + +// Load hardcoded apps +function loadHardcodedApps() { + console.log("Loading hardcoded apps..."); + try { + const hardcodedAppsList = document.getElementById('hardcodedAppsList'); + + if (!hardcodedAppsList) { + console.error("hardcodedAppsList element not found"); + return; + } + + if (!window.HARDCODED_APPS || !Array.isArray(window.HARDCODED_APPS) || window.HARDCODED_APPS.length === 0) { + console.log("No hardcoded apps found"); + hardcodedAppsList.innerHTML = ` +
+ Žádné přednastavené aplikace nebyly nalezeny +
+ `; + return; + } + + console.log("Rendering", window.HARDCODED_APPS.length, "hardcoded apps"); + hardcodedAppsList.innerHTML = window.HARDCODED_APPS.map(app => ` +
+
+
+ +
+
+

${app.name || 'Neznámá aplikace'}

+

${app.url || ''}

+ ${app.description ? `

${app.description}

` : ''} +
+
+ + Přednastaveno + +
+ `).join(''); } catch (error) { - console.error('Error loading reservations:', error); - tbody.innerHTML = ` - - - - Chyba při načítání rezervací: ${error.message} - - + console.error("Error in loadHardcodedApps:", error); + const hardcodedAppsList = document.getElementById('hardcodedAppsList'); + if (hardcodedAppsList) { + hardcodedAppsList.innerHTML = ` +
+ Chyba při načítání přednastavených aplikací +
+ `; + } + } +} + +// Load dynamic apps +async function loadDynamicApps() { + console.log("Loading dynamic apps..."); + const dynamicAppsContainer = document.getElementById('dynamicApps'); + + try { + const token = localStorage.getItem('token'); + if (!token) { + window.location.href = '/login.html'; + return; + } + + const response = await fetch('/api/apps', { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + } + }); + + if (!response.ok) { + if (response.status === 401) { + // Token expired or invalid, redirect to login + window.location.href = '/login.html'; + return; + } + throw new Error(`HTTP error! status: ${response.status}`); + } + + const apps = await response.json(); + console.log("Loaded dynamic apps:", apps); + + // Filter out hardcoded apps and map only custom apps to HTML + const customApps = Array.isArray(apps) + ? apps.filter(app => !app.id || !app.id.startsWith('hardcoded-')) + : []; + + if (customApps.length === 0) { + dynamicAppsList.innerHTML = ` +
+ +

Žádné vlastní aplikace nebyly nalezeny

+
+ `; + return; + } + + console.log("Rendering", customApps.length, "dynamic apps"); + dynamicAppsList.innerHTML = customApps.map(app => { + const iconToUse = app.iconClass || app.icon || 'fa-question'; + return ` +
+
+
+ +
+
+

${app.name || 'Neznámá aplikace'}

+

${app.url || ''}

+ ${app.description ? `

${app.description}

` : ''} +
+
+
+ + +
+
`; + }).join(''); + + } catch (error) { + console.error('Error loading dynamic apps:', error); + dynamicAppsList.innerHTML = ` +
+
+
+ +
+
+

+ Chyba při načítání aplikací: ${error.message} +

+
+
+
`; } } -// Function to format date and time -function formatDateTime(dateTimeStr) { - const date = new Date(dateTimeStr); - return date.toLocaleString('cs-CZ', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit' +// Load all apps (both hardcoded and dynamic) +async function loadApps() { + console.log("Starting to load all apps..."); + try { + // First load hardcoded apps (synchronous) + console.log("Loading hardcoded apps..."); + loadHardcodedApps(); + + // Then load dynamic apps (asynchronous) + console.log("Loading dynamic apps..."); + await loadDynamicApps(); + + console.log("All apps loaded successfully"); + } catch (error) { + console.error('Error loading apps:', error); + + // Show error message in the UI + const appsList = document.getElementById('appsList'); + if (appsList) { + const errorDiv = document.createElement('div'); + errorDiv.className = 'bg-red-50 border-l-4 border-red-400 p-4 mb-4'; + errorDiv.innerHTML = ` +
+
+ +
+
+

+ Chyba při načítání aplikací: ${error.message} +

+
+
+ `; + appsList.insertBefore(errorDiv, appsList.firstChild); + } + } +} + +async function saveApp(event) { + event.preventDefault(); + + const form = event.target; + const formData = new FormData(); + const appId = document.getElementById('appId').value; + + // Basic validation + const name = document.getElementById('appName')?.value.trim() || ''; + const url = document.getElementById('appLink')?.value.trim() || ''; + const description = document.getElementById('appDescription')?.value.trim() || ''; + const iconClass = form.iconClass || 'fa-globe'; // Use stored icon class + const color = document.getElementById('appColor')?.value || '#4a6cf7'; + + console.log('Saving app with data:', { name, url, description, iconClass, color }); + + if (!name) { + showNotification('Název aplikace je povinný', 'error'); + return; + } + + if (!url) { + showNotification('URL adresa je povinná', 'error'); + return; + } + + // Prepare form data + formData.append('name', name); + formData.append('url', url); + formData.append('description', description); + formData.append('iconClass', iconClass); // Use iconClass instead of icon + formData.append('color', color); + + try { + const isEdit = !!appId; + const url = isEdit ? `/api/apps/${appId}` : '/api/apps'; + const method = isEdit ? 'PUT' : 'POST'; + + // Show loading state + const submitBtn = form.querySelector('button[type="submit"]'); + const originalBtnText = submitBtn.innerHTML; + submitBtn.disabled = true; + submitBtn.innerHTML = 'Ukládám...'; + + const response = await fetch(url, { + method, + body: formData, + headers: { + 'Authorization': `Bearer ${localStorage.getItem('token')}` + // Don't set Content-Type header when using FormData, let the browser set it with the correct boundary + } + }); + + // Reset button state + submitBtn.disabled = false; + submitBtn.innerHTML = originalBtnText; + + if (!response.ok) { + let errorMessage = 'Nepodařilo se uložit aplikaci'; + try { + const errorData = await response.json(); + errorMessage = errorData.message || errorMessage; + } catch (e) { + console.error('Error parsing error response:', e); + } + throw new Error(errorMessage); + } + + closeAppModal(); + + // Reload only dynamic apps (faster than reloading everything) + await loadDynamicApps(); + + showNotification( + `Aplikace byla úspěšně ${isEdit ? 'aktualizována' : 'vytvořena'}`, + 'success' + ); + + } catch (error) { + console.error('Chyba při ukládání aplikace:', error); + showNotification( + error.message || 'Došlo k chybě při ukládání aplikace', + 'error' + ); + } +} + +async function editApp(appId) { + // Prevent editing hardcoded apps + if (appId && appId.startsWith('hardcoded-')) { + showNotification('Tuto přednastavenou aplikaci nelze upravit', 'warning'); + return; + } + + try { + const response = await fetch(`/api/apps/${appId}`); + if (!response.ok) throw new Error('Nepodařilo se načíst data aplikace'); + + const app = await response.json(); + + // Set form values + document.getElementById('appId').value = app.id; + document.getElementById('appName').value = app.name; + document.getElementById('appLink').value = app.url; + document.getElementById('appDescription').value = app.description || ''; + document.getElementById('appModalTitle').textContent = 'Upravit aplikaci'; + + // Set color if exists + if (app.color) { + document.getElementById('appColor').value = app.color; + document.getElementById('appColorText').value = app.color; + } + + // Handle icon preview + const iconPreview = document.getElementById('customIconPreview'); + const selectedIcon = document.getElementById('selectedIcon'); + const appIcon = document.getElementById('appIcon'); + const customIconInput = document.getElementById('customIconInput'); + + // Reset all icon states first + if (iconPreview) { + iconPreview.src = ''; + iconPreview.classList.add('hidden'); + } + if (selectedIcon) { + selectedIcon.className = 'fas fa-cube text-2xl text-gray-400'; + selectedIcon.classList.add('hidden'); + } + + // Set the appropriate icon based on the app data + const iconToUse = app.iconClass || app.icon; // Use iconClass if available, fallback to icon + if (iconToUse) { + if (iconToUse.startsWith('http') || iconToUse.startsWith('/') || iconToUse.startsWith('data:')) { + // Custom uploaded image + if (iconPreview) { + iconPreview.src = iconToUse; + iconPreview.classList.remove('hidden'); + if (selectedIcon) selectedIcon.classList.add('hidden'); + } + if (appIcon) appIcon.value = 'custom'; + } else if (iconToUse.startsWith('fa-')) { + // Font Awesome icon + if (selectedIcon) { + selectedIcon.className = `fas ${iconToUse} text-2xl text-gray-400`; + selectedIcon.classList.remove('hidden'); + if (iconPreview) iconPreview.classList.add('hidden'); + } + if (appIcon) appIcon.value = iconToUse; + + // Highlight the selected icon in the picker + const iconElements = document.querySelectorAll('.icon-option'); + iconElements.forEach(el => { + if (el.getAttribute('data-icon') === app.icon) { + el.classList.add('selected'); + } else { + el.classList.remove('selected'); + } + }); + } + } else { + // No icon + if (selectedIcon) { + selectedIcon.className = 'fas fa-cube text-2xl text-gray-400'; + selectedIcon.classList.remove('hidden'); + } + if (appIcon) appIcon.value = ''; + } + + // Reset file input + if (customIconInput) customIconInput.value = ''; + + // Show the modal + document.getElementById('appModal').classList.remove('hidden'); + + } catch (error) { + console.error('Error loading app:', error); + showNotification(error.message || 'Nastala chyba při načítání aplikace', 'error'); + } +} + +function openAddAppModal() { + // Reset form + const form = document.getElementById('appForm'); + if (form) form.reset(); + + // Clear any existing ID + document.getElementById('appId').value = ''; + + // Update title + document.getElementById('appModalTitle').textContent = 'Přidat aplikaci'; + + + // Reset icon selection + const appIcon = document.getElementById('appIcon'); + const customIconInput = document.getElementById('customIconInput'); + const customIconPreview = document.getElementById('customIconPreview'); + const selectedIcon = document.getElementById('selectedIcon'); + + if (appIcon) appIcon.value = ''; + if (customIconInput) customIconInput.value = ''; + if (customIconPreview) { + customIconPreview.src = ''; + customIconPreview.classList.add('hidden'); + } + if (selectedIcon) { + selectedIcon.className = 'fas fa-cube text-2xl text-gray-400'; + selectedIcon.classList.remove('hidden'); + } + + // Reset color picker to default + const colorInput = document.getElementById('appColor'); + const colorText = document.getElementById('appColorText'); + if (colorInput) colorInput.value = '#4a6cf7'; + if (colorText) colorText.value = '#4a6cf7'; + + // Reset icon picker selection + const selectedIcons = document.querySelectorAll('.icon-option.selected'); + selectedIcons.forEach(icon => icon.classList.remove('selected')); + + // Show the modal + const modal = document.getElementById('appModal'); + if (modal) { + modal.classList.remove('hidden'); + } + + // Set focus to the first input field + const firstInput = form?.querySelector('input, textarea, select'); + if (firstInput) firstInput.focus(); +} + +function closeAppModal() { + document.getElementById('appModal').classList.add('hidden'); +} + +// Handle file input change and preview +function setupFileInput() { + const fileInput = document.getElementById('customIconInput'); + const previewImg = document.getElementById('customIconPreview'); + const selectedIcon = document.getElementById('selectedIcon'); + const appIcon = document.getElementById('appIcon'); + + if (!fileInput || !previewImg || !selectedIcon || !appIcon) return; + + fileInput.addEventListener('change', function() { + const file = this.files[0]; + if (!file) { + resetIconSelection(); + return; + } + + // Check file type + if (!file.type.startsWith('image/')) { + showNotification('Vyberte prosím obrázek (JPG, PNG, GIF, SVG)', 'warning'); + resetIconSelection(); + return; + } + + // Check file size (max 2MB) + if (file.size > 2 * 1024 * 1024) { + showNotification('Obrázek je příliš velký. Maximální velikost je 2MB.', 'error'); + resetIconSelection(); + return; + } + + // Create preview + const reader = new FileReader(); + reader.onload = function(e) { + previewImg.src = e.target.result; + previewImg.classList.remove('hidden'); + selectedIcon.classList.add('hidden'); + + // Set a special value to indicate a custom icon is being used + appIcon.value = 'custom'; + + // Show success message + showNotification('Vlastní ikona byla úspěšně nahrána', 'success'); + }; + + reader.onerror = function() { + showNotification('Chyba při načítání obrázku', 'error'); + resetIconSelection(); + }; + + reader.readAsDataURL(file); + }); + + function resetIconSelection() { + fileInput.value = ''; + previewImg.src = ''; + previewImg.classList.add('hidden'); + selectedIcon.classList.remove('hidden'); + appIcon.value = ''; + } + + // Color picker setup + const appColor = document.getElementById('appColor'); + const appColorText = document.getElementById('appColorText'); + + if (appColor && appColorText) { + // Update text input when color picker changes + appColor.addEventListener('input', function() { + appColorText.value = this.value.toUpperCase(); + }); + + // Update color picker when text input changes + appColorText.addEventListener('input', function() { + // Validate hex color + const colorRegex = /^#?([a-f0-9]{6}|[a-f0-9]{3})$/i; + if (colorRegex.test(this.value)) { + // Ensure the # prefix is present + const hexColor = this.value.startsWith('#') ? this.value : `#${this.value}`; + appColor.value = hexColor; + } + }); + } +}; + +// Reset form when modal is closed +// Initialize icon picker when the modal is shown +document.getElementById('appModal').addEventListener('show.bs.modal', function () { + // Initialize file input + setupFileInput(); + + // Initialize icon picker + initIconPicker(); + + // Set focus to search input when dropdown is shown + const iconInput = document.getElementById('appIcon'); + if (iconInput) { + iconInput.addEventListener('focus', function() { + const dropdown = document.getElementById('iconDropdown'); + if (dropdown) dropdown.classList.remove('hidden'); + const search = document.getElementById('iconSearch'); + if (search) search.focus(); + }); + } +}); + +// Handle modal hidden event +document.getElementById('appModal').addEventListener('hidden.bs.modal', function () { + const form = document.getElementById('appForm'); + if (form) form.reset(); + document.getElementById('appId').value = ''; + document.getElementById('fileName').textContent = 'Výchozí ikona'; + document.getElementById('appIconClass').value = 'fa-globe'; + document.getElementById('selectedIcon').className = 'fas fa-globe text-xl'; + document.getElementById('iconPreview').className = 'w-12 h-12 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center mr-3'; + + // Reset file input + const fileInput = document.getElementById('appIcon'); + if (fileInput) { + fileInput.value = ''; + } + + // Hide dropdown if it's still visible + const dropdown = document.getElementById('iconDropdown'); + if (dropdown) dropdown.classList.add('hidden'); +}); + +// Icon picker functionality with more categories and icons +const iconCategories = { + 'Doprava': ['car', 'car-side', 'truck', 'bus', 'bicycle', 'motorcycle', 'plane', 'plane-departure', 'ship', 'subway', 'train', 'train-subway', 'walking', 'gas-pump', 'map-marker-alt', 'route'], + 'Jídlo a nápoje': ['utensils', 'hamburger', 'pizza-slice', 'ice-cream', 'coffee', 'mug-hot', 'beer', 'wine-glass', 'wine-bottle', 'wine-glass-alt', 'wine-bottle-alt', 'apple-alt', 'bread-slice', 'cheese', 'drumstick-bite', 'egg', 'fish', 'hotdog', 'ice-cream', 'lemon', 'pepper-hot', 'shrimp', 'stroopwafel'], + 'Nástroje': ['tools', 'wrench', 'screwdriver', 'hammer', 'toolbox', 'ruler', 'ruler-combined', 'ruler-horizontal', 'ruler-vertical', 'screwdriver-wrench', 'screwdriver', 'hammer', 'paint-roller', 'paint-brush', 'pencil-ruler', 'ruler', 'screwdriver', 'toolbox', 'wrench'], + 'Kancelář': ['briefcase', 'folder', 'folder-open', 'file', 'file-alt', 'file-archive', 'file-audio', 'file-code', 'file-excel', 'file-image', 'file-pdf', 'file-word', 'file-powerpoint', 'file-signature', 'file-upload', 'file-download', 'file-export', 'file-import', 'file-invoice', 'file-invoice-dollar', 'file-medical', 'file-prescription'], + 'Lidé': ['user', 'user-alt', 'user-astronaut', 'user-check', 'user-circle', 'user-clock', 'user-cog', 'user-edit', 'user-friends', 'user-graduate', 'user-injured', 'user-lock', 'user-md', 'user-ninja', 'user-nurse', 'user-plus', 'user-secret', 'user-shield', 'user-tag', 'user-tie', 'users', 'users-cog', 'user-tie'], + 'Komunikace': ['envelope', 'envelope-open', 'envelope-open-text', 'envelope-square', 'inbox', 'comment', 'comments', 'comment-alt', 'comment-dots', 'comment-medical', 'comment-slash', 'comment-alt', 'comments', 'inbox', 'mail-bulk', 'phone', 'phone-alt', 'phone-slash', 'phone-square', 'phone-square-alt', 'phone-volume', 'sms', 'voicemail'], + 'Sociální sítě': ['thumbs-up', 'thumbs-down', 'share', 'share-alt', 'share-square', 'retweet', 'reply', 'comment', 'comments', 'heart', 'heart-broken', 'star', 'star-half-alt', 'thumbs-up', 'thumbs-down', 'user-plus', 'user-friends', 'user-check', 'user-tag', 'user-shield'], + 'Finance': ['money-bill', 'money-bill-wave', 'money-bill-alt', 'money-check', 'money-check-alt', 'credit-card', 'credit-card-alt', 'wallet', 'donate', 'dollar-sign', 'euro-sign', 'lira-sign', 'pound-sign', 'rupee-sign', 'shekel-sign', 'yen-sign', 'bitcoin', 'ethereum', 'btc', 'euro', 'gg', 'gg-circle', 'ils', 'krw', 'money-bill', 'money-bill-alt', 'money-bill-wave', 'money-bill-wave-alt', 'money-check', 'money-check-alt', 'receipt', 'ruble-sign', 'rupee-sign', 'shekel-sign', 'tenge', 'won-sign', 'yen-sign'], + 'Zdraví': ['heart', 'heartbeat', 'heart-broken', 'hospital', 'hospital-alt', 'hospital-symbol', 'ambulance', 'band-aid', 'briefcase-medical', 'capsules', 'clinic-medical', 'diagnoses', 'disease', 'dna', 'file-medical', 'file-medical-alt', 'file-prescription', 'first-aid', 'heart', 'heartbeat', 'hospital', 'hospital-alt', 'hospital-symbol', 'hospital-user', 'id-card', 'id-card-alt', 'notes-medical', 'pills', 'plus', 'plus-circle', 'plus-square', 'prescription', 'prescription-bottle', 'prescription-bottle-alt', 'procedures', 'sign-in-alt', 'sign-out-alt', 'stethoscope', 'syringe', 'tablets', 'teeth', 'teeth-open', 'thermometer', 'user-md', 'user-nurse', 'vial', 'vials', 'weight', 'weight-hanging', 'wheelchair'], + 'Vzdělání': ['graduation-cap', 'university', 'school', 'chalkboard', 'chalkboard-teacher', 'book', 'book-open', 'book-reader', 'bookmark', 'brain', 'calculator', 'chalkboard', 'chalkboard-teacher', 'graduation-cap', 'school', 'university', 'user-graduate', 'user-tie'] +}; + +// Initialize icon picker with simplified modal +function initIconPicker() { + const iconInput = document.getElementById('appIcon'); + const iconPickerModal = document.getElementById('iconPickerModal'); + const closeButton = document.getElementById('closeIconPicker'); + const iconSearch = document.getElementById('iconSearch'); + const iconList = document.getElementById('iconList'); + + if (!iconInput || !iconPickerModal) return; + + let isModalOpen = false; + + // Simple show/hide functions + const toggleModal = () => { + isModalOpen = !isModalOpen; + document.body.style.overflow = isModalOpen ? 'hidden' : ''; + iconPickerModal.style.display = isModalOpen ? 'block' : 'none'; + + if (isModalOpen) { + // Focus search input only after modal is visible + requestAnimationFrame(() => { + if (iconSearch) { + iconSearch.focus(); + } + }); + } + }; + + // Toggle modal on icon input click + iconInput.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + toggleModal(); + }); + + // Close modal handlers + if (closeButton) { + closeButton.addEventListener('click', () => { + isModalOpen = false; + document.body.style.overflow = ''; + iconPickerModal.style.display = 'none'; + }); + } + + // Close when clicking outside the modal content + iconPickerModal.addEventListener('click', (e) => { + if (e.target === iconPickerModal) { + isModalOpen = false; + document.body.style.overflow = ''; + iconPickerModal.style.display = 'none'; + } + }); + + // Handle icon selection + if (iconList) { + iconList.addEventListener('click', (e) => { + const iconOption = e.target.closest('.icon-option'); + if (iconOption) { + const iconClass = iconOption.getAttribute('data-icon'); + selectIcon(iconClass); + } + }); + } + + // Handle search + if (iconSearch) { + iconSearch.addEventListener('input', () => { + // Debounce search to prevent excessive re-renders + clearTimeout(iconSearch.dataset.searchTimeout); + iconSearch.dataset.searchTimeout = setTimeout(() => { + renderIcons(iconSearch.value.toLowerCase()); + }, 200); + }); + } + + // Initial render + renderIcons(''); + + // Close on Escape + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && isModalOpen) { + isModalOpen = false; + document.body.style.overflow = ''; + iconPickerModal.style.display = 'none'; + } }); } -// Function to display reservations -function displayReservations(reservations) { - const tbody = document.getElementById('reservationsTable'); - if (!tbody) return; - - if (!reservations || reservations.length === 0) { - tbody.innerHTML = ` - - - - Žádné rezervace k zobrazení - - - `; - return; +// Render icons based on search term +function renderIcons(searchTerm) { + const iconList = document.getElementById('iconList'); + if (!iconList) return; + + let iconsHtml = ''; + let hasVisibleIcons = false; + + // Add icons from all categories + for (const [category, icons] of Object.entries(iconCategories)) { + const filteredIcons = icons.filter(icon => + icon.toLowerCase().includes(searchTerm) || + category.toLowerCase().includes(searchTerm) + ); + + if (filteredIcons.length > 0) { + hasVisibleIcons = true; + iconsHtml += `
${category}
`; + + filteredIcons.forEach(icon => { + const iconClass = `fa-${icon}`; + const displayName = icon.replace(/-/g, ' '); + + iconsHtml += ` +
+ + ${displayName} +
`; + }); + } } - - tbody.innerHTML = reservations.map(res => ` - - -
${res.driverName}
- - -
${res.vehicle}
- - -
${formatDateTime(res.start)}
- - -
${formatDateTime(res.end)}
- - -
${res.purpose}
- - - - - - `).join(''); + + if (!hasVisibleIcons) { + iconsHtml = ` +
+ +

Žádné ikony nenalezeny

+
`; + } + + iconList.innerHTML = iconsHtml; + + // Add click handlers to icon options + document.querySelectorAll('.icon-option').forEach(option => { + option.addEventListener('click', function() { + const iconClass = this.getAttribute('data-icon'); + selectIcon(iconClass); + }); + }); } -// Function to filter reservations -function filterReservations() { - if (!window.allReservations) return; +// Filter icons based on search input (now handled in renderIcons) - const vehicleFilter = document.getElementById('vehicleFilter').value; - const dateFilter = document.getElementById('dateFilter').value; +// Select an icon +function selectIcon(iconClass) { + const iconPickerModal = document.getElementById('iconPickerModal'); + const selectedIcon = document.getElementById('selectedIcon'); + const iconPreview = document.getElementById('iconPreview'); + const appIcon = document.getElementById('appIcon'); + const iconClassInput = document.getElementById('appIconClass'); - let filtered = window.allReservations; - - if (vehicleFilter) { - filtered = filtered.filter(res => res.vehicle.includes(vehicleFilter)); + // Show the selected icon + if (selectedIcon) { + selectedIcon.className = `fas ${iconClass} text-2xl text-gray-400`; + selectedIcon.classList.remove('hidden'); + } + + // Set the app icon value to the selected icon class + if (appIcon) appIcon.value = iconClass; + if (iconClassInput) iconClassInput.value = iconClass; + + // Update preview with random color + const colors = ['blue', 'green', 'red', 'yellow', 'indigo', 'purple', 'pink', 'gray']; + const randomColor = colors[Math.floor(Math.random() * colors.length)]; + if (iconPreview) { + iconPreview.className = `mt-2 flex items-center justify-center w-16 h-16 bg-${randomColor}-100 rounded-md overflow-hidden`; } - if (dateFilter) { - const filterDate = new Date(dateFilter); - filtered = filtered.filter(res => { - const startDate = new Date(res.start); - const endDate = new Date(res.end); - return startDate <= filterDate && filterDate <= endDate; + // Close the modal + if (iconPickerModal) { + iconPickerModal.classList.add('hidden'); + isModalOpen = false; + document.body.style.overflow = ''; + } + + // Remove active class from all icons + document.querySelectorAll('.icon-option').forEach(option => { + option.classList.remove('active'); + }); + // Add active class to selected icon + const selectedOption = document.querySelector(`.icon-option[data-icon="${iconClass}"]`); + if (selectedOption) { + selectedOption.classList.add('active'); + } + + // Store the icon class in the form data + const form = document.getElementById('appForm'); + if (form) { + form.iconClass = iconClass; + } +} + +// Initialize icon picker when the page loads +document.addEventListener('DOMContentLoaded', function() { + // Initialize banner visibility + bannerVisible = document.getElementById('bannerVisibility'); + + // Initialize icon picker + initIconPicker(); + + // Toggle icon dropdown when clicking the icon input + const iconInput = document.getElementById('appIcon'); + const iconDropdown = document.getElementById('iconDropdown'); + + if (iconInput && iconDropdown) { + iconInput.addEventListener('focus', function() { + iconDropdown.classList.remove('hidden'); + }); + + // Close dropdown when clicking outside + document.addEventListener('click', function(event) { + if (!iconInput.contains(event.target) && !iconDropdown.contains(event.target)) { + iconDropdown.classList.add('hidden'); + } }); } + + // Update icon preview when editing an existing app + const appModal = document.getElementById('appModal'); + if (appModal) { + appModal.addEventListener('shown.bs.modal', function() { + const iconClass = document.getElementById('appIconClass')?.value; + if (iconClass) { + selectIcon(iconClass); + } + }); + } +}); - displayReservations(filtered); -} +// Initialize file input handling when the page loads +document.addEventListener('DOMContentLoaded', () => { + setupFileInput(); +}); -// Function to export reservations to Excel -function exportReservations() { - if (!window.allReservations || !window.allReservations.length) { - showNotification('Žádné rezervace k exportu', 'warning'); +// Delete app function +async function deleteApp(appId) { + if (!confirm('Opravdu chcete smazat tuto aplikaci? Tuto akci nelze vrátit zpět.')) { return; } - // Get filtered reservations - const vehicleFilter = document.getElementById('vehicleFilter').value; - const dateFilter = document.getElementById('dateFilter').value; - - let dataToExport = window.allReservations; - if (vehicleFilter) { - dataToExport = dataToExport.filter(res => res.vehicle === vehicleFilter); - } - if (dateFilter) { - dataToExport = dataToExport.filter(res => res.startDate === dateFilter); - } - - // Create CSV content - const headers = ['Řidič', 'Vozidlo', 'Datum od', 'Čas od', 'Datum do', 'Čas do', 'Účel', 'Doba trvání']; - const csvContent = [ - headers.join(','), - ...dataToExport.map(res => [ - `"${res.driverName}"`, - `"${res.vehicle}"`, - res.startDate, - res.startTime, - res.endDate, - res.endTime, - `"${res.purpose || ''}"`, - `"${calculateDuration(res)}"` - ].join(',')) - ].join('\n'); - - // Create and trigger download - const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' }); - const link = document.createElement('a'); - const date = new Date().toISOString().split('T')[0]; - link.href = URL.createObjectURL(blob); - link.download = `rezervace_${date}.csv`; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); -} - -// Helper function to format date and time -function formatDateTime(date, time) { - return `${date} ${time}`; -} - -// Helper function to calculate duration -function calculateDuration(reservation) { - const start = new Date(`${reservation.startDate}T${reservation.startTime}`); - const end = new Date(`${reservation.endDate}T${reservation.endTime}`); - const diff = end - start; - - const days = Math.floor(diff / (1000 * 60 * 60 * 24)); - const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); - const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); - - let duration = ''; - if (days > 0) { - duration += `${days} ${days === 1 ? 'den' : days < 5 ? 'dny' : 'dní'} `; - } - duration += `${hours}h ${minutes}m`; - return duration; -} - -// Function to update vehicle filter options -function updateVehicleFilter(reservations) { - const vehicleFilter = document.getElementById('vehicleFilter'); - if (!vehicleFilter) return; - - const vehicles = [...new Set(reservations.map(r => r.vehicle))]; - vehicleFilter.innerHTML = ` - - ${vehicles.map(v => ``).join('')} - `; -} - -// Function to delete a reservation -async function deleteReservation(id) { - if (!confirm('Opravdu chcete smazat tuto rezervaci?')) return; - try { - const response = await fetch(`/api/reservations/${id}`, { + const token = localStorage.getItem('token'); + if (!token) { + window.location.href = '/login.html'; + return; + } + + const response = await fetch(`/api/apps/${appId}`, { method: 'DELETE', headers: { - 'Authorization': `Bearer ${localStorage.getItem('token')}` + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' } }); - if (!response.ok) throw new Error('Failed to delete reservation'); + if (!response.ok) { + throw new Error('Nepodařilo se smazat aplikaci'); + } - showNotification('Rezervace byla úspěšně smazána', 'success'); - loadReservations(); // Reload the list + // Reload the apps list + await loadDynamicApps(); + showNotification('Aplikace byla úspěšně smazána', 'success'); } catch (error) { - console.error('Error deleting reservation:', error); - showNotification('Chyba při mazání rezervace', 'error'); + console.error('Error deleting app:', error); + showNotification(error.message || 'Nastala chyba při mazání aplikace', 'error'); + } +} + +// Save app function +async function saveApp(event) { + event.preventDefault(); + + // Get form values + const name = document.getElementById('name').value.trim(); + const url = document.getElementById('url').value.trim(); + const description = document.getElementById('description').value.trim(); + const iconClass = document.getElementById('appIcon').value.trim(); + const appId = document.getElementById('appId').value; + + // Validate required fields + if (!name || !url || !iconClass) { + showNotification('Název, URL a ikona jsou povinné pole', 'error'); + return; + } + + // Create form data + const formData = new URLSearchParams(); + formData.append('name', name); + formData.append('url', url); + formData.append('description', description); + formData.append('iconClass', iconClass); + + // Create request URL + const requestUrl = appId ? `/api/apps/${appId}` : '/api/apps'; + const method = appId ? 'PUT' : 'POST'; + + try { + const response = await fetch(requestUrl, { + method: method, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: formData.toString(), + credentials: 'include' + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error('Server response:', errorText); + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + showNotification('Aplikace byla úspěšně uložena', 'success'); + loadApps(); + closeAppModal(); + } catch (error) { + console.error('Error saving app:', error); + showNotification(`Chyba při ukládání aplikace: ${error.message}`, 'error'); } } @@ -2452,6 +3257,13 @@ function updateBannerPreview() { const bannerBgColor = document.getElementById('bannerBgColor')?.value || template.backgroundColor || '#f8f9fa'; const bannerTextColor = document.getElementById('bannerTextColor')?.value || template.textColor || '#212529'; const bannerTextAlign = document.getElementById('bannerTextAlign')?.value || template.textAlign || 'left'; + + // Debug log for text color + console.log('Text color values:', { + formField: document.getElementById('bannerTextColor')?.value, + template: template.textColor, + final: bannerTextColor + }); const bannerFontSize = document.getElementById('bannerFontSize')?.value || template.fontSize || 16; const bannerPadding = document.getElementById('bannerPadding')?.value || template.padding || 20; const bannerMargin = document.getElementById('bannerMargin')?.value || template.margin || 20; @@ -3445,6 +4257,173 @@ function handleFileSelect(file) { reader.readAsDataURL(file); } - + +// Prevent default drag behaviors +function preventDefaults(e) { + e.preventDefault(); + e.stopPropagation(); +} + +/* Reservations Management Section */ + +// Function to load and display reservations +async function loadReservations() { + const tbody = document.querySelector('#reservationsTable tbody'); + try { + const response = await fetch('/api/reservations'); + if (!response.ok) throw new Error('Failed to load reservations'); + + const reservations = await response.json(); + window.allReservations = reservations; // Store for filtering + + displayReservations(reservations); + updateVehicleFilter(reservations); + } catch (error) { + console.error('Error loading reservations:', error); + tbody.innerHTML = ` + + + Chyba při načítání rezervací: ${error.message} + + + `; + } +} + +// Function to display reservations +function displayReservations(reservations) { + const tbody = document.querySelector('#reservationsTable tbody'); + if (!tbody) return; + + if (!reservations.length) { + tbody.innerHTML = ` + + + Žádné rezervace k zobrazení + + + `; + return; + } + + tbody.innerHTML = reservations.map(res => ` + + ${res.driverName} + ${res.vehicle} + ${formatDateTime(res.startDate, res.startTime)} + ${formatDateTime(res.endDate, res.endTime)} + ${res.purpose || '-'} + ${calculateDuration(res)} + + `).join(''); +} + +// Function to filter reservations +function filterReservations() { + if (!window.allReservations) return; + + const vehicleFilter = document.getElementById('vehicleFilter').value; + const dateFilter = document.getElementById('dateFilter').value; + + let filtered = window.allReservations; + + if (vehicleFilter) { + filtered = filtered.filter(res => res.vehicle === vehicleFilter); + } + + if (dateFilter) { + filtered = filtered.filter(res => res.startDate === dateFilter); + } + + displayReservations(filtered); +} + +// Function to export reservations to Excel +function exportReservations() { + if (!window.allReservations || !window.allReservations.length) { + showNotification('Žádné rezervace k exportu', 'warning'); + return; + } + + // Get filtered reservations + const vehicleFilter = document.getElementById('vehicleFilter').value; + const dateFilter = document.getElementById('dateFilter').value; + + let dataToExport = window.allReservations; + if (vehicleFilter) { + dataToExport = dataToExport.filter(res => res.vehicle === vehicleFilter); + } + if (dateFilter) { + dataToExport = dataToExport.filter(res => res.startDate === dateFilter); + } + + // Create CSV content + const headers = ['Řidič', 'Vozidlo', 'Datum od', 'Čas od', 'Datum do', 'Čas do', 'Účel', 'Doba trvání']; + const csvContent = [ + headers.join(','), + ...dataToExport.map(res => [ + `"${res.driverName}"`, + `"${res.vehicle}"`, + res.startDate, + res.startTime, + res.endDate, + res.endTime, + `"${res.purpose || ''}"`, + `"${calculateDuration(res)}"` + ].join(',')) + ].join('\n'); + + // Create and trigger download + const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' }); + const link = document.createElement('a'); + const date = new Date().toISOString().split('T')[0]; + link.href = URL.createObjectURL(blob); + link.download = `rezervace_${date}.csv`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); +} + +// Helper function to format date and time +function formatDateTime(date, time) { + return `${date} ${time}`; +} + +// Helper function to calculate duration +function calculateDuration(reservation) { + const start = new Date(`${reservation.startDate}T${reservation.startTime}`); + const end = new Date(`${reservation.endDate}T${reservation.endTime}`); + const diff = end - start; + + const days = Math.floor(diff / (1000 * 60 * 60 * 24)); + const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); + + let duration = ''; + if (days > 0) { + duration += `${days} ${days === 1 ? 'den' : days < 5 ? 'dny' : 'dní'} `; + } + duration += `${hours}h ${minutes}m`; + return duration; +} + +// Function to update vehicle filter options +function updateVehicleFilter(reservations) { + const vehicleFilter = document.getElementById('vehicleFilter'); + if (!vehicleFilter) return; + + const vehicles = [...new Set(reservations.map(r => r.vehicle))]; + vehicleFilter.innerHTML = ` + + ${vehicles.map(v => ``).join('')} + `; +} + +// Load reservations when page loads +document.addEventListener('DOMContentLoaded', () => { + // ...existing code... + loadReservations(); +}); + \ No newline at end of file