Projektvorschläge toggelbar
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -77,8 +77,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Rechte Seitenleiste mit Statistiken und Erfassungs-URLs -->
|
<!-- Rechte Seitenleiste mit Statistiken, Optionen und Erfassungs-URLs -->
|
||||||
<div class="user-stats-panel">
|
<div class="sidebar-panels">
|
||||||
|
<div class="user-stats-panel">
|
||||||
<!-- Statistik-Karten -->
|
<!-- Statistik-Karten -->
|
||||||
<div class="stat-card stat-card-overtime">
|
<div class="stat-card stat-card-overtime">
|
||||||
<div class="stat-label" style="display: flex; align-items: center; gap: 5px;">
|
<div class="stat-label" style="display: flex; align-items: center; gap: 5px;">
|
||||||
@@ -193,6 +194,23 @@
|
|||||||
</div>
|
</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>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user