diff --git a/js/employees.js b/js/employees.js new file mode 100644 index 0000000..97700b2 --- /dev/null +++ b/js/employees.js @@ -0,0 +1,264 @@ +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); + }); +}