diff --git a/database.js b/database.js index 4f51f3d..548a5d7 100644 --- a/database.js +++ b/database.js @@ -261,6 +261,14 @@ function initDatabase() { // Fehler ignorieren wenn Spalte bereits existiert }); + // Migration: project_search_enabled Spalte hinzufügen (Standard: 1 = aktiviert) + db.run(`ALTER TABLE users ADD COLUMN project_search_enabled INTEGER DEFAULT 1`, (err) => { + // Fehler ignorieren wenn Spalte bereits existiert + if (err && !err.message.includes('duplicate column')) { + console.warn('Warnung beim Hinzufügen der Spalte project_search_enabled:', err.message); + } + }); + // Ping-Status-Tabelle für IP-basierte Zeiterfassung db.run(`CREATE TABLE IF NOT EXISTS ping_status ( user_id INTEGER NOT NULL, diff --git a/public/css/style.css b/public/css/style.css index 64a7b02..560e289 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -343,6 +343,25 @@ body { font-weight: 600; } +/* Sidebar Panels Container */ +.sidebar-panels { + display: flex; + flex-direction: column; + gap: 20px; + width: 280px; + flex-shrink: 0; +} + +/* User Options Panel */ +.user-options-panel { + background: white; + padding: 20px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + width: 100%; + box-sizing: border-box; +} + .stat-card { background: #f8f9fa; padding: 15px; @@ -415,6 +434,63 @@ body { color: #999; } +/* Apple-Style Toggle Switch */ +.apple-toggle-switch { + position: relative; + display: inline-block; + width: 51px; + height: 31px; + flex-shrink: 0; +} + +.apple-toggle-switch input { + opacity: 0; + width: 0; + height: 0; +} + +.apple-toggle-slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + transition: 0.3s; + border-radius: 31px; +} + +.apple-toggle-slider:before { + position: absolute; + content: ""; + height: 27px; + width: 27px; + left: 2px; + bottom: 2px; + background-color: white; + transition: 0.3s; + border-radius: 50%; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +} + +.apple-toggle-switch input:checked + .apple-toggle-slider { + background-color: #34c759; +} + +.apple-toggle-switch input:checked + .apple-toggle-slider:before { + transform: translateX(20px); +} + +.apple-toggle-switch input:focus + .apple-toggle-slider { + box-shadow: 0 0 1px #34c759; +} + +.apple-toggle-switch input:disabled + .apple-toggle-slider { + opacity: 0.5; + cursor: not-allowed; +} + .week-selector { display: flex; flex-wrap: wrap; diff --git a/public/js/dashboard.js b/public/js/dashboard.js index 91481a3..a87b138 100644 --- a/public/js/dashboard.js +++ b/public/js/dashboard.js @@ -14,6 +14,7 @@ let activeProjectSearchInput = null; let projectSearchDebounceTimer = null; const PROJECT_SEARCH_DEBOUNCE_MS = 300; const PROJECT_SEARCH_CLOSE_DELAY_MS = 200; +let projectSearchEnabled = true; // Standard: aktiviert // Wochenend-Prozentsätze laden async function loadWeekendPercentages() { @@ -179,6 +180,9 @@ document.addEventListener('DOMContentLoaded', async function() { // Ping-IP laden loadPingIP(); + // Projektvorschläge-Einstellung laden + loadProjectSearchEnabled(); + // Wochenend-Prozentsätze laden loadWeekendPercentages(); @@ -2456,6 +2460,68 @@ window.savePingIP = async function() { } }; +// Projektvorschläge-Einstellung laden +async function loadProjectSearchEnabled() { + try { + const response = await fetch('/api/user/project-search-enabled'); + if (!response.ok) { + throw new Error('Fehler beim Laden der Einstellung'); + } + const data = await response.json(); + projectSearchEnabled = data.project_search_enabled !== false; // Standard: true + + // Toggle-Switch aktualisieren + const toggle = document.getElementById('projectSearchToggle'); + if (toggle) { + toggle.checked = projectSearchEnabled; + } + } catch (error) { + console.error('Fehler beim Laden der Projektvorschläge-Einstellung:', error); + // Bei Fehler: Standard auf aktiviert setzen + projectSearchEnabled = true; + } +} + +// Projektvorschläge-Einstellung speichern +async function saveProjectSearchEnabled(enabled) { + try { + const response = await fetch('/api/user/project-search-enabled', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ project_search_enabled: enabled }) + }); + + const result = await response.json(); + + if (!response.ok) { + throw new Error(result.error || 'Fehler beim Speichern der Einstellung'); + } + + projectSearchEnabled = enabled; + console.log('Projektvorschläge-Einstellung gespeichert:', enabled); + } catch (error) { + console.error('Fehler beim Speichern der Projektvorschläge-Einstellung:', error); + alert('Fehler beim Speichern der Einstellung'); + // Toggle zurücksetzen bei Fehler + const toggle = document.getElementById('projectSearchToggle'); + if (toggle) { + toggle.checked = projectSearchEnabled; + } + } +} + +// Toggle-Event-Handler registrieren +document.addEventListener('DOMContentLoaded', function() { + const toggle = document.getElementById('projectSearchToggle'); + if (toggle) { + toggle.addEventListener('change', function() { + saveProjectSearchEnabled(this.checked); + }); + } +}); + // --- Projektsuche-Overlay im Dashboard --- (function initProjectSearchOverlay() { const overlay = document.getElementById('projectSearchOverlay'); @@ -2491,6 +2557,10 @@ window.savePingIP = async function() { } function showOverlay(input) { + // Nur anzeigen wenn Projektvorschläge aktiviert sind + if (!projectSearchEnabled) { + return; + } activeProjectSearchInput = input; overlay.style.display = 'block'; overlay.setAttribute('aria-hidden', 'false'); @@ -2550,6 +2620,10 @@ window.savePingIP = async function() { function onProjectInputFocus(ev) { const input = ev.target; if (!input.classList.contains('activity-project-input')) return; + // Nur Overlay anzeigen wenn Projektvorschläge aktiviert sind + if (!projectSearchEnabled) { + return; + } if (projectSearchDebounceTimer) { clearTimeout(projectSearchDebounceTimer); projectSearchDebounceTimer = null; @@ -2560,6 +2634,14 @@ window.savePingIP = async function() { function onProjectInputInput(ev) { const input = ev.target; if (!input.classList.contains('activity-project-input')) return; + // Nur Overlay anzeigen wenn Projektvorschläge aktiviert sind + if (!projectSearchEnabled) { + // Wenn deaktiviert, verstecke Overlay falls es noch angezeigt wird + if (activeProjectSearchInput === input) { + hideOverlay(); + } + return; + } if (!activeProjectSearchInput || activeProjectSearchInput !== input) showOverlay(input); if (projectSearchDebounceTimer) clearTimeout(projectSearchDebounceTimer); diff --git a/routes/user-routes.js b/routes/user-routes.js index 3a2bf77..58c2eac 100644 --- a/routes/user-routes.js +++ b/routes/user-routes.js @@ -130,6 +130,50 @@ function registerUserRoutes(app) { }); }); + // API: Projektvorschläge-Einstellung abrufen + app.get('/api/user/project-search-enabled', requireAuth, (req, res) => { + const userId = req.session.userId; + + db.get('SELECT project_search_enabled FROM users WHERE id = ?', [userId], (err, user) => { + if (err) { + return res.status(500).json({ error: 'Fehler beim Abrufen der Einstellung' }); + } + + // Standardwert: 1 (aktiviert) wenn nicht gesetzt + const enabled = user?.project_search_enabled !== null && user?.project_search_enabled !== undefined + ? (user.project_search_enabled === 1) + : true; + + res.json({ project_search_enabled: enabled }); + }); + }); + + // API: Projektvorschläge-Einstellung speichern + app.post('/api/user/project-search-enabled', requireAuth, (req, res) => { + const userId = req.session.userId; + const { project_search_enabled } = req.body; + + // Validierung: Muss boolean oder 0/1 sein + let enabledValue; + if (typeof project_search_enabled === 'boolean') { + enabledValue = project_search_enabled ? 1 : 0; + } else if (project_search_enabled === 1 || project_search_enabled === '1' || project_search_enabled === true) { + enabledValue = 1; + } else if (project_search_enabled === 0 || project_search_enabled === '0' || project_search_enabled === false) { + enabledValue = 0; + } else { + return res.status(400).json({ error: 'Ungültiger Wert für project_search_enabled' }); + } + + db.run('UPDATE users SET project_search_enabled = ? WHERE id = ?', [enabledValue, userId], (err) => { + if (err) { + return res.status(500).json({ error: 'Fehler beim Speichern der Einstellung' }); + } + + res.json({ success: true, project_search_enabled: enabledValue === 1 }); + }); + }); + // API: Rollenwechsel app.post('/api/user/switch-role', requireAuth, (req, res) => { const { role } = req.body; diff --git a/services/mssql-infra-service.js b/services/mssql-infra-service.js index fc502a4..2f060e9 100644 --- a/services/mssql-infra-service.js +++ b/services/mssql-infra-service.js @@ -58,7 +58,7 @@ async function searchProjectsByDescription(searchTerm) { } const query = ` - SELECT TOP 50 + SELECT TOP 25 kk.Auftrag AS auftrag, kk.Proj AS proj, kk.Such AS such, diff --git a/views/dashboard.ejs b/views/dashboard.ejs index d9bccf2..2ca8af2 100644 --- a/views/dashboard.ejs +++ b/views/dashboard.ejs @@ -77,8 +77,9 @@ - -
+ + + + +
+

+ Optionen +

+
+ + +
+