diff --git a/admin-dashboard.html b/admin-dashboard.html index 4469683..a2d1898 100644 --- a/admin-dashboard.html +++ b/admin-dashboard.html @@ -703,6 +703,64 @@

Vítejte v administraci

+ +
+

Správa aplikací

+ +
+ +
+ +
+ +
Načítám seznam aplikací...
+
+
+ + + +

Správa banneru

@@ -1302,6 +1360,169 @@ function handleImageUpload(event) { reader.readAsDataURL(file); } +// App Management Functions +async function loadApps() { + 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.
'; + return; + } + + appsList.innerHTML = apps.map(app => ` +
+
+ ${app.icon ? + `${app.name}` : + `
+ +
` + } +
+

${app.name}

+

${app.url}

+
+
+
+ + +
+
+ `).join(''); + + // Add event listeners to buttons + document.querySelectorAll('.edit-app-btn').forEach(btn => { + btn.addEventListener('click', (e) => { + e.stopPropagation(); + const appId = btn.dataset.appId; + editApp(appId); + }); + }); + + document.querySelectorAll('.delete-app-btn').forEach(btn => { + btn.addEventListener('click', (e) => { + e.stopPropagation(); + const appId = btn.dataset.appId; + if (confirm('Opravdu chcete tuto aplikaci smazat?')) { + deleteApp(appId); + } + }); + }); + + } catch (error) { + console.error('Chyba při načítání aplikací:', error); + showNotification('Nepodařilo se načíst seznam aplikací', 'error'); + } +} + +async function saveApp(event) { + event.preventDefault(); + + const form = event.target; + 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); + + 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 response = await fetch(url, { + method, + body: formData, + headers: { + 'Authorization': `Bearer ${localStorage.getItem('token')}` + } + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.message || 'Nepodařilo se uložit aplikaci'); + } + + closeAppModal(); + await loadApps(); + showNotification('Aplikace byla úspěšně uložena', 'success'); + + } catch (error) { + console.error('Chyba při ukládání aplikace:', error); + showNotification(error.message || 'Nepodařilo se uložit aplikaci', 'error'); + } +} + +async function editApp(appId) { + 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(); + + 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'; + + document.getElementById('appModal').classList.remove('hidden'); + + } catch (error) { + console.error('Chyba při načítání aplikace:', error); + showNotification('Nepodařilo se načíst data aplikace', 'error'); + } +} + +async function deleteApp(appId) { + try { + const response = await fetch(`/api/apps/${appId}`, { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${localStorage.getItem('token')}`, + 'Content-Type': 'application/json' + } + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.message || 'Nepodařilo se smazat aplikaci'); + } + + await loadApps(); + showNotification('Aplikace byla úspěšně smazána', 'success'); + + } catch (error) { + console.error('Chyba při mazání aplikace:', error); + showNotification(error.message || 'Nepodařilo se smazat aplikaci', 'error'); + } +} + +function openAddAppModal() { + document.getElementById('appForm').reset(); + document.getElementById('appId').value = ''; + document.getElementById('appModalTitle').textContent = 'Přidat aplikaci'; + document.getElementById('appModal').classList.remove('hidden'); +} + +function closeAppModal() { + document.getElementById('appModal').classList.add('hidden'); +} + // Logout functionality document.getElementById('logoutBtn').addEventListener('click', function() { localStorage.removeItem('token'); @@ -2241,6 +2462,20 @@ function debounce(func, wait) { }; } +// App management event listeners +document.getElementById('addAppBtn').addEventListener('click', openAddAppModal); +document.getElementById('closeAppModal').addEventListener('click', closeAppModal); +document.getElementById('cancelAppBtn').addEventListener('click', closeAppModal); +document.getElementById('appForm').addEventListener('submit', saveApp); + +// Close modal when clicking outside +const appModal = document.getElementById('appModal'); +appModal.addEventListener('click', (e) => { + if (e.target === appModal) { + closeAppModal(); + } +}); + // These event listeners will be set up after the DOM is fully loaded // Setup draggable image functionality diff --git a/main.go b/main.go index 3b3f2d4..9974721 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( "net/url" "os" "os/exec" + "path/filepath" "strings" "time" @@ -18,6 +19,16 @@ import ( "gopkg.in/gomail.v2" ) +type App struct { + ID string `json:"id"` + Name string `json:"name"` + URL string `json:"url"` + Description string `json:"description,omitempty"` + Icon string `json:"icon,omitempty"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + type TripEntry struct { Name string `json:"name"` Vehicle string `json:"vehicle"` @@ -70,6 +81,13 @@ func main() { api.Use(AuthMiddleware) api.HandleFunc("/submit", handleSubmit).Methods("POST") api.HandleFunc("/banner/update", UpdateBannerHandler).Methods("POST", "OPTIONS") + + // App management routes + api.HandleFunc("/apps", GetAppsHandler).Methods("GET") + api.HandleFunc("/apps", CreateAppHandler).Methods("POST") + api.HandleFunc("/apps/{id}", GetAppHandler).Methods("GET") + api.HandleFunc("/apps/{id}", UpdateAppHandler).Methods("PUT") + api.HandleFunc("/apps/{id}", DeleteAppHandler).Methods("DELETE") // Public endpoints r.HandleFunc("/api/banner", GetBannerHandler).Methods("GET", "OPTIONS") @@ -170,6 +188,196 @@ func enableCORS(next http.Handler) http.Handler { }) } +// App Handlers +func GetAppsHandler(w http.ResponseWriter, r *http.Request) { + // In a real app, this would fetch from a database + apps := []App{ + { + ID: "1", + Name: "Kontakt", + URL: "/kontakt", + Description: "Kontaktní formulář", + Icon: "", + CreatedAt: time.Now().Format(time.RFC3339), + UpdatedAt: time.Now().Format(time.RFC3339), + }, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(apps) +} + +func GetAppHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id := vars["id"] + + // In a real app, this would fetch from a database + if id != "1" { + http.Error(w, "App not found", http.StatusNotFound) + return + } + + app := App{ + ID: id, + Name: "Kontakt", + URL: "/kontakt", + Description: "Kontaktní formulář", + Icon: "", + CreatedAt: time.Now().Format(time.RFC3339), + UpdatedAt: time.Now().Format(time.RFC3339), + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(app) +} + +func CreateAppHandler(w http.ResponseWriter, r *http.Request) { + // Parse form data + err := r.ParseMultipartForm(10 << 20) // 10 MB max file size + if err != nil { + http.Error(w, "Error parsing form data", http.StatusBadRequest) + return + } + + // Get form values + name := r.FormValue("name") + url := r.FormValue("url") + description := r.FormValue("description") + + // Handle file upload + var iconPath string + file, handler, err := r.FormFile("icon") + if err == nil { + defer file.Close() + + // Create uploads directory if it doesn't exist + if _, err := os.Stat("uploads"); os.IsNotExist(err) { + os.Mkdir("uploads", 0755) + } + + // Generate a unique filename + ext := "" + if parts := strings.Split(handler.Filename, "."); len(parts) > 1 { + ext = "." + parts[len(parts)-1] + } + iconPath = fmt.Sprintf("icon_%d%s", time.Now().UnixNano(), ext) + + // Create the file + f, err := os.Create(filepath.Join("uploads", iconPath)) + if err != nil { + http.Error(w, "Error saving file", http.StatusInternalServerError) + return + } + defer f.Close() + + // Copy the uploaded file to the created file + _, err = io.Copy(f, file) + if err != nil { + http.Error(w, "Error saving file", http.StatusInternalServerError) + return + } + } + + // In a real app, this would save to a database + app := App{ + ID: fmt.Sprintf("%d", time.Now().UnixNano()), + Name: name, + URL: url, + Description: description, + Icon: iconPath, + CreatedAt: time.Now().Format(time.RFC3339), + UpdatedAt: time.Now().Format(time.RFC3339), + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(app) +} + +func UpdateAppHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id := vars["id"] + + // In a real app, this would update in a database + if id != "1" { + http.Error(w, "App not found", http.StatusNotFound) + return + } + + // Parse form data + err := r.ParseMultipartForm(10 << 20) // 10 MB max file size + if err != nil { + http.Error(w, "Error parsing form data", http.StatusBadRequest) + return + } + + // Get form values + name := r.FormValue("name") + url := r.FormValue("url") + description := r.FormValue("description") + + // Handle file upload if a new file is provided + var iconPath string + file, handler, err := r.FormFile("icon") + if err == nil { + defer file.Close() + + // Create uploads directory if it doesn't exist + if _, err := os.Stat("uploads"); os.IsNotExist(err) { + os.Mkdir("uploads", 0755) + } + + // Generate a unique filename + ext := "" + if parts := strings.Split(handler.Filename, "."); len(parts) > 1 { + ext = "." + parts[len(parts)-1] + } + iconPath = fmt.Sprintf("icon_%d%s", time.Now().UnixNano(), ext) + + // Create the file + f, err := os.Create(filepath.Join("uploads", iconPath)) + if err != nil { + http.Error(w, "Error saving file", http.StatusInternalServerError) + return + } + defer f.Close() + + // Copy the uploaded file to the created file + _, err = io.Copy(f, file) + if err != nil { + http.Error(w, "Error saving file", http.StatusInternalServerError) + return + } + } + + // In a real app, this would update in a database + app := App{ + ID: id, + Name: name, + URL: url, + Description: description, + Icon: iconPath, // This would be updated only if a new file was uploaded + CreatedAt: time.Now().Format(time.RFC3339), // In a real app, this would be fetched from the database + UpdatedAt: time.Now().Format(time.RFC3339), + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(app) +} + +func DeleteAppHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id := vars["id"] + + // In a real app, this would delete from a database + if id != "1" { + http.Error(w, "App not found", http.StatusNotFound) + return + } + + w.WriteHeader(http.StatusNoContent) +} + func handleSubmit(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json")