- ${app.icon ?
- `

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

` :
+ `
+
+
`
+ }
+
+
${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