Projektvorschläge toggelbar

This commit is contained in:
2026-03-15 23:43:51 +01:00
parent 2d0bcbbd2f
commit dbeda9c2ae
6 changed files with 231 additions and 3 deletions

View File

@@ -261,6 +261,14 @@ function initDatabase() {
// Fehler ignorieren wenn Spalte bereits existiert // 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 // Ping-Status-Tabelle für IP-basierte Zeiterfassung
db.run(`CREATE TABLE IF NOT EXISTS ping_status ( db.run(`CREATE TABLE IF NOT EXISTS ping_status (
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,

View File

@@ -343,6 +343,25 @@ body {
font-weight: 600; 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 { .stat-card {
background: #f8f9fa; background: #f8f9fa;
padding: 15px; padding: 15px;
@@ -415,6 +434,63 @@ body {
color: #999; 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 { .week-selector {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;

View File

@@ -14,6 +14,7 @@ let activeProjectSearchInput = null;
let projectSearchDebounceTimer = null; let projectSearchDebounceTimer = null;
const PROJECT_SEARCH_DEBOUNCE_MS = 300; const PROJECT_SEARCH_DEBOUNCE_MS = 300;
const PROJECT_SEARCH_CLOSE_DELAY_MS = 200; const PROJECT_SEARCH_CLOSE_DELAY_MS = 200;
let projectSearchEnabled = true; // Standard: aktiviert
// Wochenend-Prozentsätze laden // Wochenend-Prozentsätze laden
async function loadWeekendPercentages() { async function loadWeekendPercentages() {
@@ -179,6 +180,9 @@ document.addEventListener('DOMContentLoaded', async function() {
// Ping-IP laden // Ping-IP laden
loadPingIP(); loadPingIP();
// Projektvorschläge-Einstellung laden
loadProjectSearchEnabled();
// Wochenend-Prozentsätze laden // Wochenend-Prozentsätze laden
loadWeekendPercentages(); 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 --- // --- Projektsuche-Overlay im Dashboard ---
(function initProjectSearchOverlay() { (function initProjectSearchOverlay() {
const overlay = document.getElementById('projectSearchOverlay'); const overlay = document.getElementById('projectSearchOverlay');
@@ -2491,6 +2557,10 @@ window.savePingIP = async function() {
} }
function showOverlay(input) { function showOverlay(input) {
// Nur anzeigen wenn Projektvorschläge aktiviert sind
if (!projectSearchEnabled) {
return;
}
activeProjectSearchInput = input; activeProjectSearchInput = input;
overlay.style.display = 'block'; overlay.style.display = 'block';
overlay.setAttribute('aria-hidden', 'false'); overlay.setAttribute('aria-hidden', 'false');
@@ -2550,6 +2620,10 @@ window.savePingIP = async function() {
function onProjectInputFocus(ev) { function onProjectInputFocus(ev) {
const input = ev.target; const input = ev.target;
if (!input.classList.contains('activity-project-input')) return; if (!input.classList.contains('activity-project-input')) return;
// Nur Overlay anzeigen wenn Projektvorschläge aktiviert sind
if (!projectSearchEnabled) {
return;
}
if (projectSearchDebounceTimer) { if (projectSearchDebounceTimer) {
clearTimeout(projectSearchDebounceTimer); clearTimeout(projectSearchDebounceTimer);
projectSearchDebounceTimer = null; projectSearchDebounceTimer = null;
@@ -2560,6 +2634,14 @@ window.savePingIP = async function() {
function onProjectInputInput(ev) { function onProjectInputInput(ev) {
const input = ev.target; const input = ev.target;
if (!input.classList.contains('activity-project-input')) return; 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 (!activeProjectSearchInput || activeProjectSearchInput !== input) showOverlay(input);
if (projectSearchDebounceTimer) clearTimeout(projectSearchDebounceTimer); if (projectSearchDebounceTimer) clearTimeout(projectSearchDebounceTimer);

View File

@@ -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 // API: Rollenwechsel
app.post('/api/user/switch-role', requireAuth, (req, res) => { app.post('/api/user/switch-role', requireAuth, (req, res) => {
const { role } = req.body; const { role } = req.body;

View File

@@ -58,7 +58,7 @@ async function searchProjectsByDescription(searchTerm) {
} }
const query = ` const query = `
SELECT TOP 50 SELECT TOP 25
kk.Auftrag AS auftrag, kk.Auftrag AS auftrag,
kk.Proj AS proj, kk.Proj AS proj,
kk.Such AS such, kk.Such AS such,

View File

@@ -77,7 +77,8 @@
</div> </div>
</div> </div>
<!-- Rechte Seitenleiste mit Statistiken und Erfassungs-URLs --> <!-- Rechte Seitenleiste mit Statistiken, Optionen und Erfassungs-URLs -->
<div class="sidebar-panels">
<div class="user-stats-panel"> <div class="user-stats-panel">
<!-- Statistik-Karten --> <!-- Statistik-Karten -->
<div class="stat-card stat-card-overtime"> <div class="stat-card stat-card-overtime">
@@ -194,6 +195,23 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Optionen-Container -->
<div class="user-options-panel">
<h3 style="font-size: 14px; margin-top: 0; margin-bottom: 15px; color: #2c3e50; font-weight: 600;">
Optionen
</h3>
<div style="display: flex; align-items: center; justify-content: space-between;">
<label for="projectSearchToggle" style="font-size: 13px; color: #333; cursor: pointer; flex: 1;">
Projektvorschläge aktivieren
</label>
<label class="apple-toggle-switch">
<input type="checkbox" id="projectSearchToggle" checked>
<span class="apple-toggle-slider"></span>
</label>
</div>
</div>
</div>
</div> </div>
</div> </div>