diff --git a/admin-dashboard.html b/admin-dashboard.html index a2d1898..f1e7bc5 100644 --- a/admin-dashboard.html +++ b/admin-dashboard.html @@ -714,8 +714,26 @@
- -
Načítám seznam aplikací...
+
+

Přednastavené aplikace

+
+ +
Načítám přednastavené aplikace...
+
+
+ +
+
+

Vlastní aplikace

+ +
+
+ +
Načítám vlastní aplikace...
+
+
@@ -749,8 +767,19 @@
- - Doporučená velikost: 64x64px +
+
+ + +
Nebyl vybrán žádný soubor
+
+
+
+ + Doporučená velikost: 64x64px (PNG, JPG, SVG) +
@@ -1360,44 +1389,121 @@ function handleImageUpload(event) { reader.readAsDataURL(file); } -// App Management Functions -async function loadApps() { +// Hardcoded apps data - should match the ones in index.html +const 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' + } +]; + +// Load hardcoded apps +function loadHardcodedApps() { + const hardcodedAppsList = document.getElementById('hardcodedAppsList'); + + if (HARDCODED_APPS.length === 0) { + hardcodedAppsList.innerHTML = ` +
+ Žádné přednastavené aplikace nebyly nalezeny +
+ `; + return; + } + + hardcodedAppsList.innerHTML = HARDCODED_APPS.map(app => ` +
+
+
+ +
+
+

${app.name}

+

${app.url}

+

${app.description}

+
+
+ + Přednastaveno + +
+ `).join(''); +} + +// Load dynamic apps +async function loadDynamicApps() { + const dynamicAppsList = document.getElementById('dynamicAppsList'); + try { const response = await fetch('/api/apps'); if (!response.ok) throw new Error('Nepodařilo se načíst seznam aplikací'); const apps = await response.json(); - const appsList = document.getElementById('appsList'); if (apps.length === 0) { - appsList.innerHTML = '
Žádné aplikace nebyly nalezeny.
'; + dynamicAppsList.innerHTML = ` +
+ +

Žádné vlastní aplikace nebyly nalezeny

+
+ `; return; } - appsList.innerHTML = apps.map(app => ` -
-
- ${app.icon ? - `${app.name}` : - `
- -
` - } -
-

${app.name}

-

${app.url}

+ dynamicAppsList.innerHTML = apps + .filter(app => !app.id || !app.id.startsWith('hardcoded-')) + .map(app => ` +
+
+ ${app.icon ? + `${app.name}` : + `
+ +
` + } +
+

${app.name}

+

${app.url}

+ ${app.description ? `

${app.description}

` : ''} +
+
+
+ +
-
- - -
-
- `).join(''); + `).join(''); // Add event listeners to buttons document.querySelectorAll('.edit-app-btn').forEach(btn => { @@ -1419,11 +1525,28 @@ async function loadApps() { }); } catch (error) { - console.error('Chyba při načítání aplikací:', error); - showNotification('Nepodařilo se načíst seznam aplikací', 'error'); + console.error('Chyba při načítání vlastních aplikací:', error); + dynamicAppsList.innerHTML = ` +
+
+
+ +
+
+

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

+
+
+
+ `; } } +// Load all apps (both hardcoded and dynamic) +async function loadApps() { + loadHardcodedApps(); + await loadDynamicApps(); +} + async function saveApp(event) { event.preventDefault(); @@ -1431,55 +1554,126 @@ async function saveApp(event) { const formData = new FormData(); const appId = document.getElementById('appId').value; - formData.append('name', document.getElementById('appName').value); - formData.append('url', document.getElementById('appUrl').value); - formData.append('description', document.getElementById('appDescription').value); + // Basic validation + const name = document.getElementById('appName').value.trim(); + const url = document.getElementById('appUrl').value.trim(); + if (!name) { + showNotification('Název aplikace je povinný', 'error'); + return; + } + + if (!url) { + showNotification('URL adresa je povinná', 'error'); + return; + } + + formData.append('name', name); + formData.append('url', url); + formData.append('description', document.getElementById('appDescription').value.trim()); + + // Handle icon upload if a new file is selected const iconInput = document.getElementById('appIcon'); if (iconInput.files.length > 0) { formData.append('icon', iconInput.files[0]); } try { - const url = appId ? `/api/apps/${appId}` : '/api/apps'; - const method = appId ? 'PUT' : 'POST'; + 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) { - const error = await response.json(); - throw new Error(error.message || 'Nepodařilo se uložit aplikaci'); + 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(); - await loadApps(); - showNotification('Aplikace byla úspěšně uložena', 'success'); + + // 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 || 'Nepodařilo se uložit aplikaci', '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('appUrl').value = app.url; document.getElementById('appDescription').value = app.description || ''; document.getElementById('appModalTitle').textContent = 'Upravit aplikaci'; + // Update file name display + const fileNameDisplay = document.getElementById('fileName'); + if (fileNameDisplay) { + fileNameDisplay.textContent = app.icon ? 'Stávající soubor: ' + app.icon : 'Nebyl vybrán žádný soubor'; + } + + // Show icon preview if exists + const iconPreview = document.getElementById('appIconPreview'); + if (app.icon) { + iconPreview.src = `/uploads/${app.icon}`; + iconPreview.classList.remove('hidden'); + } else { + iconPreview.classList.add('hidden'); + } + + // Clear file input to allow re-selecting the same file + const fileInput = document.getElementById('appIcon'); + if (fileInput) { + fileInput.value = ''; + } + + // Show the modal document.getElementById('appModal').classList.remove('hidden'); } catch (error) { @@ -1489,6 +1683,16 @@ async function editApp(appId) { } async function deleteApp(appId) { + // Prevent deleting hardcoded apps + if (appId && appId.startsWith('hardcoded-')) { + showNotification('Tuto přednastavenou aplikaci nelze smazat', 'warning'); + return; + } + + if (!confirm('Opravdu chcete tuto aplikaci smazat? Tato akce je nevratná.')) { + return; + } + try { const response = await fetch(`/api/apps/${appId}`, { method: 'DELETE', @@ -1503,7 +1707,8 @@ async function deleteApp(appId) { throw new Error(error.message || 'Nepodařilo se smazat aplikaci'); } - await loadApps(); + // Reload only dynamic apps + await loadDynamicApps(); showNotification('Aplikace byla úspěšně smazána', 'success'); } catch (error) { @@ -1513,9 +1718,29 @@ async function deleteApp(appId) { } function openAddAppModal() { - document.getElementById('appForm').reset(); + // 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 file input and preview + const fileInput = document.getElementById('appIcon'); + const fileNameDisplay = document.getElementById('fileName'); + const previewImg = document.getElementById('appIconPreview'); + + if (fileInput) fileInput.value = ''; + if (fileNameDisplay) fileNameDisplay.textContent = 'Nebyl vybrán žádný soubor'; + if (previewImg) { + previewImg.src = ''; + previewImg.classList.add('hidden'); + } + + // Show the modal document.getElementById('appModal').classList.remove('hidden'); } @@ -1523,6 +1748,60 @@ function closeAppModal() { document.getElementById('appModal').classList.add('hidden'); } +// Handle file input change and preview +function setupFileInput() { + const fileInput = document.getElementById('appIcon'); + const fileNameDisplay = document.getElementById('fileName'); + const previewImg = document.getElementById('appIconPreview'); + + if (!fileInput || !fileNameDisplay || !previewImg) return; + + fileInput.addEventListener('change', (e) => { + const file = e.target.files[0]; + + if (file) { + // Update file name display + fileNameDisplay.textContent = file.name; + + // Show preview if it's an image + if (file.type.startsWith('image/')) { + const reader = new FileReader(); + reader.onload = (e) => { + previewImg.src = e.target.result; + previewImg.classList.remove('hidden'); + }; + reader.readAsDataURL(file); + } else { + previewImg.classList.add('hidden'); + } + } else { + fileNameDisplay.textContent = 'Nebyl vybrán žádný soubor'; + previewImg.classList.add('hidden'); + } + }); +} + +// Reset form when modal is closed +document.getElementById('appModal').addEventListener('hidden.bs.modal', function () { + const form = document.getElementById('appForm'); + if (form) form.reset(); + document.getElementById('appId').value = ''; + const previewImg = document.getElementById('appIconPreview'); + if (previewImg) { + previewImg.classList.add('hidden'); + previewImg.src = ''; + } + const fileNameDisplay = document.getElementById('fileName'); + if (fileNameDisplay) { + fileNameDisplay.textContent = 'Nebyl vybrán žádný soubor'; + } +}); + +// Initialize file input handling when the page loads +document.addEventListener('DOMContentLoaded', () => { + setupFileInput(); +}); + // Logout functionality document.getElementById('logoutBtn').addEventListener('click', function() { localStorage.removeItem('token'); diff --git a/index.html b/index.html index 54dcc59..a8663aa 100644 --- a/index.html +++ b/index.html @@ -85,10 +85,10 @@ `; } - // Function to load and display apps + // Function to load and display dynamic apps async function loadApps() { const loadingIndicator = document.getElementById('loadingIndicator'); - const appsGrid = document.querySelector('.grid.grid-cols-1.md\:grid-cols-2.lg\:grid-cols-4.gap-6'); + const appsGrid = document.getElementById('appsGrid'); try { // Show loading indicator @@ -99,24 +99,22 @@ const apps = await response.json(); - // Clear existing content - appsGrid.innerHTML = ''; + // Remove any existing dynamic apps (keep hardcoded ones) + const dynamicApps = Array.from(appsGrid.querySelectorAll('[data-id^="dynamic-"]')); + dynamicApps.forEach(app => app.remove()); if (apps.length > 0) { - // Add each app to the grid + // Add each dynamic app to the grid apps.forEach(app => { + // Skip if this is a hardcoded app that's already displayed + const hardcodedIds = ['hardcoded-car', 'hardcoded-lunch', 'hardcoded-osticket', 'hardcoded-kanboard']; + if (hardcodedIds.includes(app.id)) return; + const appCard = document.createElement('div'); + appCard.setAttribute('data-id', 'dynamic-' + app.id); appCard.innerHTML = createAppCard(app); appsGrid.appendChild(appCard.firstElementChild); }); - } else { - // Show message if no apps found - appsGrid.innerHTML = ` -
- -

Žádné aplikace nebyly nalezeny

-
- `; } // Initialize search functionality @@ -124,13 +122,14 @@ } catch (error) { console.error('Error loading apps:', error); - appsGrid.innerHTML = ` -
- -

Nepodařilo se načíst aplikace

-

${error.message}

-
+ const errorDiv = document.createElement('div'); + errorDiv.className = 'col-span-full text-center py-8'; + errorDiv.innerHTML = ` + +

Nepodařilo se načíst další aplikace

+

${error.message}

`; + appsGrid.appendChild(errorDiv); } finally { // Hide loading indicator if (loadingIndicator) loadingIndicator.style.display = 'none'; @@ -385,7 +384,7 @@ Obědy OSticket Kanboard - Kontakt + Kontakt
@@ -422,14 +421,59 @@
-
- +
+ +
+
+ +
+

Záznam služebních jízd

+

Jednoduchý systém pro evidenci a správu jízd služebními vozidly.

+ + Otevřít aplikaci + +
+ +
+
+ +
+

Objednávka obědů

+

Portál pro objednávku a přehled firemních obědů

+ + Otevřít aplikaci + +
+ +
+
+ +
+

OSTicket

+

Systém technické podpory a hlášení problémů

+ + Otevřít aplikaci + +
+ +
+
+ +
+

Kanboard

+

Správa úkolů a projektů v přehledném kanban stylu

+ + Otevřít aplikaci + +
+ +
- -
+ +