Implementierung ins Dashboard und Layout anpassungen
This commit is contained in:
@@ -964,6 +964,58 @@ table input[type="text"] {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Floating Overlay für Projektsuche im Dashboard */
|
||||||
|
.project-search-overlay {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 9000;
|
||||||
|
max-height: 70vh;
|
||||||
|
min-width: 520px;
|
||||||
|
max-width: 1100px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-search-overlay-inner {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-search-overlay-status {
|
||||||
|
padding: 8px 0;
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-search-overlay-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-search-overlay-table th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
background: #f5f5f5;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-search-overlay-table td {
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-search-overlay-table tbody tr {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-search-overlay-table tbody tr:hover {
|
||||||
|
background: #e8f4fc;
|
||||||
|
}
|
||||||
|
|
||||||
.overtime-vacation-controls {
|
.overtime-vacation-controls {
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ let weekendPercentages = { saturday: 100, sunday: 100 }; // Wochenend-Prozentsä
|
|||||||
let defaultBreakMinutes = 30; // Standard-Pausenzeit des Mitarbeiters (Vorbelegung)
|
let defaultBreakMinutes = 30; // Standard-Pausenzeit des Mitarbeiters (Vorbelegung)
|
||||||
let latestSubmittedTimesheetId = null; // ID der neuesten eingereichten Version
|
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
|
// Wochenend-Prozentsätze laden
|
||||||
async function loadWeekendPercentages() {
|
async function loadWeekendPercentages() {
|
||||||
try {
|
try {
|
||||||
@@ -38,10 +44,10 @@ function getWeekendPercentage(date) {
|
|||||||
return 100; // Kein Wochenende = 100% (normal)
|
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) {
|
function isValidProjectNumber(value) {
|
||||||
if (value === null || value === undefined || String(value).trim() === '') return true;
|
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) {
|
function validateActivityProjectNumbers(activities) {
|
||||||
@@ -699,7 +705,7 @@ function renderWeek() {
|
|||||||
data-field="activity${idx + 1}_project_number"
|
data-field="activity${idx + 1}_project_number"
|
||||||
value="${activity.projectNumber || ''}"
|
value="${activity.projectNumber || ''}"
|
||||||
placeholder="z. B. 5260001"
|
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}
|
${timeFieldsDisabled} ${disabled}
|
||||||
onblur="saveEntry(this)"
|
onblur="saveEntry(this)"
|
||||||
class="activity-project-input">
|
class="activity-project-input">
|
||||||
@@ -1301,7 +1307,6 @@ async function saveEntry(input) {
|
|||||||
if (activities[idx - 1]) {
|
if (activities[idx - 1]) {
|
||||||
activities[idx - 1].projectNumber = null;
|
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
|
// Aktualisiere currentEntries mit den DOM-Werten
|
||||||
@@ -2450,3 +2455,169 @@ window.savePingIP = async function() {
|
|||||||
alert('Fehler beim Speichern der IP-Adresse');
|
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();
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|||||||
@@ -197,6 +197,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Floating Overlay für Projektsuche (oberhalb des Projektnummer-Feldes) -->
|
||||||
|
<div id="projectSearchOverlay" class="project-search-overlay" style="display: none;" aria-hidden="true">
|
||||||
|
<div class="project-search-overlay-inner">
|
||||||
|
<div id="projectSearchOverlayStatus" class="project-search-overlay-status"></div>
|
||||||
|
<table id="projectSearchOverlayTable" class="project-search-overlay-table" style="display: none;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Projektnummer (Auftrag)</th>
|
||||||
|
<th>Beschreibung (Proj)</th>
|
||||||
|
<th>Seriennummer</th>
|
||||||
|
<th>Kundenbezeichnung</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="/js/format-hours.js"></script>
|
<script src="/js/format-hours.js"></script>
|
||||||
<script src="/js/dashboard.js"></script>
|
<script src="/js/dashboard.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
Reference in New Issue
Block a user