Implementierung ins Dashboard und Layout anpassungen
This commit is contained in:
@@ -9,6 +9,12 @@ let weekendPercentages = { saturday: 100, sunday: 100 }; // Wochenend-Prozentsä
|
||||
let defaultBreakMinutes = 30; // Standard-Pausenzeit des Mitarbeiters (Vorbelegung)
|
||||
let latestSubmittedTimesheetId = null; // ID der neuesten eingereichten Version
|
||||
|
||||
// Projektsuche-Overlay im Dashboard
|
||||
let activeProjectSearchInput = null;
|
||||
let projectSearchDebounceTimer = null;
|
||||
const PROJECT_SEARCH_DEBOUNCE_MS = 300;
|
||||
const PROJECT_SEARCH_CLOSE_DELAY_MS = 200;
|
||||
|
||||
// Wochenend-Prozentsätze laden
|
||||
async function loadWeekendPercentages() {
|
||||
try {
|
||||
@@ -38,10 +44,10 @@ function getWeekendPercentage(date) {
|
||||
return 100; // Kein Wochenende = 100% (normal)
|
||||
}
|
||||
|
||||
/** Plausibilitätsprüfung Projektnummer: 7 Ziffern, beginnt mit 5, dann YY (Jahr), dann 4 Ziffern (z.B. 5260001). */
|
||||
/** Plausibilitätsprüfung Projektnummer: beginnt mit 5, dann 6–7 Ziffern (z.B. 5260001 oder 52600001). */
|
||||
function isValidProjectNumber(value) {
|
||||
if (value === null || value === undefined || String(value).trim() === '') return true;
|
||||
return /^5\d{6}$/.test(String(value).trim());
|
||||
return /^5\d{6,7}$/.test(String(value).trim());
|
||||
}
|
||||
|
||||
function validateActivityProjectNumbers(activities) {
|
||||
@@ -699,7 +705,7 @@ function renderWeek() {
|
||||
data-field="activity${idx + 1}_project_number"
|
||||
value="${activity.projectNumber || ''}"
|
||||
placeholder="z. B. 5260001"
|
||||
title="7 Ziffern: 5 + Jahr (YY) + 4 Ziffern"
|
||||
title="Mit 5 beginnen, dann 6–7 Ziffern (z.B. 5260001)"
|
||||
${timeFieldsDisabled} ${disabled}
|
||||
onblur="saveEntry(this)"
|
||||
class="activity-project-input">
|
||||
@@ -1301,7 +1307,6 @@ async function saveEntry(input) {
|
||||
if (activities[idx - 1]) {
|
||||
activities[idx - 1].projectNumber = null;
|
||||
}
|
||||
alert(`Ungültige Projektnummer in Tätigkeit ${projectNumberCheck.activityIndex}: Die Projektnummer muss 7 Ziffern haben, mit 5 beginnen, gefolgt vom Jahr (YY) und 4 Ziffern (z.B. 5260001). Die Eingabe wurde geleert.`);
|
||||
}
|
||||
|
||||
// Aktualisiere currentEntries mit den DOM-Werten
|
||||
@@ -2450,3 +2455,169 @@ window.savePingIP = async function() {
|
||||
alert('Fehler beim Speichern der IP-Adresse');
|
||||
}
|
||||
};
|
||||
|
||||
// --- Projektsuche-Overlay im Dashboard ---
|
||||
(function initProjectSearchOverlay() {
|
||||
const overlay = document.getElementById('projectSearchOverlay');
|
||||
const statusEl = document.getElementById('projectSearchOverlayStatus');
|
||||
const tableEl = document.getElementById('projectSearchOverlayTable');
|
||||
const tbody = tableEl ? tableEl.querySelector('tbody') : null;
|
||||
let projectSearchBlurCloseTimer = null;
|
||||
|
||||
if (!overlay || !statusEl || !tableEl || !tbody) return;
|
||||
|
||||
function positionOverlayAboveInput(input) {
|
||||
// Breite/Position an der Tätigkeitsbeschreibung (activity-input) ausrichten
|
||||
let anchor = null;
|
||||
const row = input.closest('.activity-row');
|
||||
if (row) {
|
||||
anchor = row.querySelector('.activity-input') || input;
|
||||
} else {
|
||||
anchor = input;
|
||||
}
|
||||
|
||||
const rect = anchor.getBoundingClientRect();
|
||||
const minLeft = 20;
|
||||
const width = Math.max(520, Math.min(1100, rect.width));
|
||||
overlay.style.width = width + 'px';
|
||||
overlay.style.left = Math.max(minLeft, rect.left) + 'px';
|
||||
overlay.style.bottom = 'auto';
|
||||
const overlayHeight = overlay.offsetHeight || (window.innerHeight * 0.7);
|
||||
let top = rect.top - overlayHeight - 8; // Overlay über dem Activity-Input platzieren
|
||||
if (top < 10) {
|
||||
top = 10; // nicht über den oberen Rand hinaus
|
||||
}
|
||||
overlay.style.top = top + 'px';
|
||||
}
|
||||
|
||||
function showOverlay(input) {
|
||||
activeProjectSearchInput = input;
|
||||
overlay.style.display = 'block';
|
||||
overlay.setAttribute('aria-hidden', 'false');
|
||||
positionOverlayAboveInput(input);
|
||||
const term = (input.value || '').trim();
|
||||
if (term) {
|
||||
runProjectSearch(term);
|
||||
} else {
|
||||
statusEl.textContent = 'Suchbegriff eingeben (Projektnummer oder Beschreibung).';
|
||||
tableEl.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function hideOverlay() {
|
||||
overlay.style.display = 'none';
|
||||
overlay.setAttribute('aria-hidden', 'true');
|
||||
activeProjectSearchInput = null;
|
||||
}
|
||||
|
||||
function runProjectSearch(term) {
|
||||
statusEl.textContent = 'Suche…';
|
||||
tableEl.style.display = 'none';
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (!term) {
|
||||
statusEl.textContent = 'Suchbegriff eingeben (Projektnummer oder Beschreibung).';
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/api/projects/search?term=' + encodeURIComponent(term), { headers: { 'Accept': 'application/json' } })
|
||||
.then(function (res) { return res.json(); })
|
||||
.then(function (result) {
|
||||
const rows = result.results || [];
|
||||
if (rows.length === 0) {
|
||||
statusEl.textContent = 'Keine Projekte gefunden.';
|
||||
return;
|
||||
}
|
||||
statusEl.textContent = '';
|
||||
tableEl.style.display = '';
|
||||
rows.forEach(function (row) {
|
||||
const tr = document.createElement('tr');
|
||||
tr.setAttribute('data-auftrag', row.auftrag || '');
|
||||
tr.innerHTML =
|
||||
'<td>' + (row.auftrag || '') + '</td>' +
|
||||
'<td>' + (row.proj || '') + '</td>' +
|
||||
'<td>' + (row.such || '') + '</td>' +
|
||||
'<td>' + (row.bez || '') + '</td>';
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.error('Projektsuche:', err);
|
||||
statusEl.textContent = 'Projektsuche ist aktuell nicht verfügbar. Bitte später erneut versuchen.';
|
||||
});
|
||||
}
|
||||
|
||||
function onProjectInputFocus(ev) {
|
||||
const input = ev.target;
|
||||
if (!input.classList.contains('activity-project-input')) return;
|
||||
if (projectSearchDebounceTimer) {
|
||||
clearTimeout(projectSearchDebounceTimer);
|
||||
projectSearchDebounceTimer = null;
|
||||
}
|
||||
showOverlay(input);
|
||||
}
|
||||
|
||||
function onProjectInputInput(ev) {
|
||||
const input = ev.target;
|
||||
if (!input.classList.contains('activity-project-input')) return;
|
||||
if (!activeProjectSearchInput || activeProjectSearchInput !== input) showOverlay(input);
|
||||
|
||||
if (projectSearchDebounceTimer) clearTimeout(projectSearchDebounceTimer);
|
||||
const term = (input.value || '').trim();
|
||||
projectSearchDebounceTimer = setTimeout(function () {
|
||||
projectSearchDebounceTimer = null;
|
||||
if (term) {
|
||||
runProjectSearch(term);
|
||||
} else {
|
||||
statusEl.textContent = 'Suchbegriff eingeben (Projektnummer oder Beschreibung).';
|
||||
tableEl.style.display = 'none';
|
||||
tbody.innerHTML = '';
|
||||
}
|
||||
}, PROJECT_SEARCH_DEBOUNCE_MS);
|
||||
}
|
||||
|
||||
function onProjectInputBlur() {
|
||||
if (projectSearchBlurCloseTimer) clearTimeout(projectSearchBlurCloseTimer);
|
||||
projectSearchBlurCloseTimer = setTimeout(function () {
|
||||
projectSearchBlurCloseTimer = null;
|
||||
if (activeProjectSearchInput && document.activeElement && overlay.contains(document.activeElement)) return;
|
||||
hideOverlay();
|
||||
}, PROJECT_SEARCH_CLOSE_DELAY_MS);
|
||||
}
|
||||
|
||||
overlay.addEventListener('mousedown', function (ev) {
|
||||
var tr = ev.target.closest('tbody tr');
|
||||
if (!tr) return;
|
||||
var inputToUpdate = activeProjectSearchInput;
|
||||
if (!inputToUpdate) return;
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
var auftrag = tr.getAttribute('data-auftrag');
|
||||
if (auftrag === null || auftrag === undefined) return;
|
||||
auftrag = String(auftrag).trim();
|
||||
if (!auftrag) return;
|
||||
if (projectSearchBlurCloseTimer) {
|
||||
clearTimeout(projectSearchBlurCloseTimer);
|
||||
projectSearchBlurCloseTimer = null;
|
||||
}
|
||||
inputToUpdate.value = auftrag;
|
||||
inputToUpdate.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
inputToUpdate.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
if (typeof saveEntry === 'function') saveEntry(inputToUpdate);
|
||||
hideOverlay();
|
||||
inputToUpdate.focus();
|
||||
});
|
||||
|
||||
document.addEventListener('focusin', onProjectInputFocus);
|
||||
document.addEventListener('input', onProjectInputInput);
|
||||
document.addEventListener('focusout', function (ev) {
|
||||
if (ev.target.classList.contains('activity-project-input')) onProjectInputBlur();
|
||||
});
|
||||
|
||||
document.addEventListener('click', function (ev) {
|
||||
if (!overlay.style.display || overlay.style.display === 'none') return;
|
||||
if (overlay.contains(ev.target)) return;
|
||||
if (ev.target.classList.contains('activity-project-input')) return;
|
||||
hideOverlay();
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user