diff --git a/admin-dashboard.html b/admin-dashboard.html index 933cdb3..7868f6e 100644 --- a/admin-dashboard.html +++ b/admin-dashboard.html @@ -1664,1018 +1664,213 @@ function preventDefaults(e) { e.stopPropagation(); } -// 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); - } - } - - // 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); -} +/* Reservations Management Section */ -// 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..."); +// Function to load and display reservations +async function loadReservations() { + const tbody = document.getElementById('reservationsTable'); try { - const hardcodedAppsList = document.getElementById('hardcodedAppsList'); + const response = await fetch('/api/reservations'); + if (!response.ok) throw new Error('Failed to load reservations'); - 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 in loadHardcodedApps:", error); - const hardcodedAppsList = document.getElementById('hardcodedAppsList'); - if (hardcodedAppsList) { - hardcodedAppsList.innerHTML = ` -
- Chyba při načítání přednastavených aplikací -
- `; - } - } -} + const reservations = await response.json(); + window.allReservations = reservations; // Store for filtering -// 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(''); - + displayReservations(reservations); + updateVehicleFilter(reservations); } catch (error) { - console.error('Error loading dynamic apps:', error); - dynamicAppsList.innerHTML = ` -
-
-
- -
-
-

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

-
-
-
+ console.error('Error loading reservations:', error); + tbody.innerHTML = ` + + + + Chyba při načítání rezervací: ${error.message} + + `; } } -// 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 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' }); } -// 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} -
`; - }); - } - } - - 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 display reservations +function displayReservations(reservations) { + const tbody = document.getElementById('reservationsTable'); + if (!tbody) return; -// Filter icons based on search input (now handled in renderIcons) - -// 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'); - - // 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`; - } - - // 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); - } - }); - } -}); - -// Initialize file input handling when the page loads -document.addEventListener('DOMContentLoaded', () => { - setupFileInput(); -}); - -// Delete app function -async function deleteApp(appId) { - if (!confirm('Opravdu chcete smazat tuto aplikaci? Tuto akci nelze vrátit zpět.')) { + if (!reservations || reservations.length === 0) { + tbody.innerHTML = ` + + + + Žádné rezervace k zobrazení + + + `; return; } - try { - const token = localStorage.getItem('token'); - if (!token) { - window.location.href = '/login.html'; - return; - } + tbody.innerHTML = reservations.map(res => ` + + +
${res.driverName}
+ + +
${res.vehicle}
+ + +
${formatDateTime(res.start)}
+ + +
${formatDateTime(res.end)}
+ + +
${res.purpose}
+ + + + + + `).join(''); +} - const response = await fetch(`/api/apps/${appId}`, { +// 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.includes(vehicleFilter)); + } + + 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; + }); + } + + 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('')} + `; +} + +// 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}`, { method: 'DELETE', headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' + 'Authorization': `Bearer ${localStorage.getItem('token')}` } }); - if (!response.ok) { - throw new Error('Nepodařilo se smazat aplikaci'); - } + if (!response.ok) throw new Error('Failed to delete reservation'); - // Reload the apps list - await loadDynamicApps(); - showNotification('Aplikace byla úspěšně smazána', 'success'); + showNotification('Rezervace byla úspěšně smazána', 'success'); + loadReservations(); // Reload the list } catch (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'); + console.error('Error deleting reservation:', error); + showNotification('Chyba při mazání rezervace', 'error'); } } @@ -3257,13 +2452,6 @@ 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; @@ -4257,173 +3445,6 @@ 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