import { state } from './app.js'; import { createEmployee, removeEmployee } from './data.js'; import { readEmployeeNames } from './excel.js'; const WD_LABELS = ['Mo','Di','Mi','Do','Fr']; const WD_VALUES = [1,2,3,4,5]; export function renderEmployees() { const container = document.getElementById('tab-employees'); // Build static shell const shell = document.createElement('div'); const title = document.createElement('h2'); title.className = 'section-title'; title.textContent = 'Mitarbeiter'; shell.appendChild(title); const row = document.createElement('div'); row.className = 'row'; const nameInput = document.createElement('input'); nameInput.id = 'emp-name-input'; nameInput.type = 'text'; nameInput.placeholder = 'Name eingeben...'; nameInput.style.width = '220px'; const addBtn = document.createElement('button'); addBtn.type = 'button'; addBtn.className = 'primary'; addBtn.textContent = 'Hinzufügen'; const excelBtn = document.createElement('button'); excelBtn.type = 'button'; excelBtn.textContent = 'Excel importieren'; const fileInput = document.createElement('input'); fileInput.id = 'emp-excel-input'; fileInput.type = 'file'; fileInput.accept = '.xlsx,.xls'; fileInput.style.display = 'none'; row.append(nameInput, addBtn, excelBtn, fileInput); const list = document.createElement('div'); list.id = 'employee-list'; shell.append(row, list); container.replaceChildren(shell); refreshList(); addBtn.addEventListener('click', () => { const name = nameInput.value.trim(); if (!name) return; try { state.employees.push(createEmployee(name)); } catch { return; } nameInput.value = ''; refreshList(); }); nameInput.addEventListener('keydown', e => { if (e.key === 'Enter') addBtn.click(); }); excelBtn.addEventListener('click', () => fileInput.click()); fileInput.addEventListener('change', async e => { const file = e.target.files[0]; if (!file) return; const buf = await file.arrayBuffer(); const names = readEmployeeNames(buf); for (const name of names) { if (!state.employees.some(emp => emp.name === name)) { try { state.employees.push(createEmployee(name)); } catch { /* skip invalid */ } } } e.target.value = ''; refreshList(); }); } function refreshList() { const list = document.getElementById('employee-list'); list.replaceChildren(); if (state.employees.length === 0) { const msg = document.createElement('p'); msg.style.cssText = 'color:#9ca3af;margin-top:16px'; msg.textContent = 'Noch keine Mitarbeiter hinzugefügt.'; list.appendChild(msg); return; } for (const emp of state.employees) { const item = document.createElement('div'); item.className = 'employee-item'; const nameSpan = document.createElement('span'); nameSpan.className = 'employee-name'; nameSpan.textContent = emp.name; // textContent: safe, no XSS const editBtn = document.createElement('button'); editBtn.type = 'button'; editBtn.textContent = 'Einschränkungen'; const removeBtn = document.createElement('button'); removeBtn.type = 'button'; removeBtn.className = 'danger'; removeBtn.textContent = 'Entfernen'; item.append(nameSpan, editBtn, removeBtn); list.appendChild(item); const editorDiv = document.createElement('div'); editorDiv.id = 'editor-' + emp.id; editorDiv.style.display = 'none'; list.appendChild(editorDiv); removeBtn.addEventListener('click', () => { removeEmployee(state, emp.id); refreshList(); }); editBtn.addEventListener('click', () => { if (editorDiv.style.display === 'none') { renderConstraintEditor(emp.id); editorDiv.style.display = 'block'; } else { editorDiv.style.display = 'none'; } }); } } function renderConstraintEditor(empId) { const emp = state.employees.find(e => e.id === empId); if (!emp) return; const c = emp.constraints; const el = document.getElementById('editor-' + empId); el.replaceChildren(); const form = document.createElement('div'); form.className = 'constraint-form'; // Helper: day toggle group function makeDayGroup(label, constraintKey) { const row = document.createElement('div'); row.className = 'constraint-row'; const lbl = document.createElement('label'); lbl.textContent = label; const group = document.createElement('div'); group.className = 'day-toggle-group'; for (let i = 0; i < 5; i++) { const day = WD_VALUES[i]; const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'day-toggle' + (c[constraintKey].includes(day) ? ' active' : ''); btn.textContent = WD_LABELS[i]; btn.addEventListener('click', () => { const arr = emp.constraints[constraintKey]; const idx = arr.indexOf(day); if (idx >= 0) arr.splice(idx, 1); else arr.push(day); btn.classList.toggle('active'); }); group.appendChild(btn); } row.append(lbl, group); return row; } // Helper: number input function makeNumberInput(label, constraintKey, placeholder) { const row = document.createElement('div'); row.className = 'constraint-row'; const lbl = document.createElement('label'); lbl.textContent = label; const input = document.createElement('input'); input.type = 'number'; input.min = '0'; input.style.width = '120px'; input.placeholder = placeholder; input.value = c[constraintKey] != null ? c[constraintKey] : ''; input.addEventListener('change', () => { const val = input.value.trim(); emp.constraints[constraintKey] = val === '' ? null : parseInt(val, 10); }); row.append(lbl, input); return row; } form.appendChild(makeDayGroup('Nie verfügbar', 'neverDays')); form.appendChild(makeDayGroup('Niedrige Priorität / Home-Office', 'lowPriorityDays')); form.appendChild(makeDayGroup('Nur an diesen Tagen (leer = alle)', 'onlyDays')); form.appendChild(makeNumberInput('Max. Einsätze pro Monat', 'maxPerMonth', 'Kein Limit')); form.appendChild(makeNumberInput('Mindestabstand (Tage)', 'minGapDays', '0')); // Vacation section const vacRow = document.createElement('div'); vacRow.className = 'constraint-row'; const vacLabel = document.createElement('label'); vacLabel.textContent = 'Urlaub'; const vacContainer = document.createElement('div'); vacContainer.id = 'vacations-' + empId; const addVacBtn = document.createElement('button'); addVacBtn.type = 'button'; addVacBtn.textContent = '+ Urlaub hinzufügen'; addVacBtn.style.marginTop = '6px'; vacRow.append(vacLabel, vacContainer, addVacBtn); form.appendChild(vacRow); el.appendChild(form); renderVacationList(empId); addVacBtn.addEventListener('click', () => { emp.constraints.vacations.push({ from: '', to: '' }); renderVacationList(empId); }); } function renderVacationList(empId) { const emp = state.employees.find(e => e.id === empId); const container = document.getElementById('vacations-' + empId); container.replaceChildren(); if (emp.constraints.vacations.length === 0) { const msg = document.createElement('span'); msg.style.cssText = 'color:#9ca3af;font-size:12px'; msg.textContent = 'Kein Urlaub eingetragen'; container.appendChild(msg); return; } emp.constraints.vacations.forEach((v, i) => { const row = document.createElement('div'); row.className = 'row'; row.style.marginBottom = '4px'; const fromInput = document.createElement('input'); fromInput.type = 'date'; fromInput.value = v.from; fromInput.addEventListener('change', () => { emp.constraints.vacations[i].from = fromInput.value; }); const sep = document.createElement('span'); sep.textContent = 'bis'; const toInput = document.createElement('input'); toInput.type = 'date'; toInput.value = v.to; toInput.addEventListener('change', () => { emp.constraints.vacations[i].to = toInput.value; }); const delBtn = document.createElement('button'); delBtn.type = 'button'; delBtn.textContent = '✕'; delBtn.addEventListener('click', () => { emp.constraints.vacations.splice(i, 1); renderVacationList(empId); }); row.append(fromInput, sep, toInput, delBtn); container.appendChild(row); }); }