Technician

This commit is contained in:
2026-05-26 13:55:27 -05:00
parent 1885224a5a
commit f062bd8168
8 changed files with 216 additions and 23 deletions

View File

@@ -8,9 +8,35 @@
*/
import { addItem, getItems, resetItemCounter } from '../utils/item-editor.js';
import { setDefaultDate, showSpinner, hideSpinner } from '../utils/helpers.js';
import '../utils/api.js';
let currentInvoiceId = null;
let qboLaborRate = null;
let workerList = [];
export async function loadWorkers() {
try {
const result = await window.API.settings.get('invoice_workers');
if (result && result.value) {
workerList = result.value.split(',').map(w => w.trim()).filter(Boolean);
} else {
workerList = [];
}
populateWorkerDropdown();
console.log(`👷 ${workerList.length} Bearbeiter geladen`);
} catch (e) {
console.log('Worker-Liste konnte nicht geladen werden.');
}
}
function populateWorkerDropdown() {
const sel = document.getElementById('invoice-worker');
if (!sel) return;
const current = sel.value;
sel.innerHTML = `<option value="">— None —</option>` +
workerList.map(w => `<option value="${w}">${w}</option>`).join('');
if (current) sel.value = current;
}
export async function loadLaborRate() {
try {
@@ -179,6 +205,11 @@ async function loadInvoiceForEdit(invoiceId) {
document.getElementById('invoice-authorization').value = data.invoice.auth_code || '';
document.getElementById('invoice-tax-exempt').checked = data.invoice.tax_exempt;
document.getElementById('invoice-bill-to-name').value = data.invoice.bill_to_name || '';
// Worker
populateWorkerDropdown();
const workerEl = document.getElementById('invoice-worker');
if (workerEl) workerEl.value = data.invoice.worker || '';
const sendDateEl = document.getElementById('invoice-send-date');
if (sendDateEl) {
@@ -226,7 +257,12 @@ function prepareNewInvoice() {
document.getElementById('invoice-terms').value = 'Net 14';
document.getElementById('invoice-number').value = '';
document.getElementById('invoice-send-date').value = '';
// Worker zurücksetzen
populateWorkerDropdown();
const workerEl = document.getElementById('invoice-worker');
if (workerEl) workerEl.value = '';
// Reset recurring
const recurringCb = document.getElementById('invoice-recurring');
const recurringGroup = document.getElementById('invoice-recurring-group');
@@ -278,6 +314,7 @@ export async function handleInvoiceSubmit(e) {
tax_exempt: document.getElementById('invoice-tax-exempt').checked,
scheduled_send_date: document.getElementById('invoice-send-date')?.value || null,
bill_to_name: document.getElementById('invoice-bill-to-name')?.value || null,
worker: document.getElementById('invoice-worker')?.value || null,
is_recurring: isRecurring,
recurring_interval: recurringInterval,
items: getItems('invoice-items')
@@ -314,7 +351,9 @@ export async function handleInvoiceSubmit(e) {
export function initInvoiceModal() {
const form = document.getElementById('invoice-form');
if (form) form.addEventListener('submit', handleInvoiceSubmit);
loadWorkers(); // Bearbeiterliste laden
const taxExempt = document.getElementById('invoice-tax-exempt');
if (taxExempt) taxExempt.addEventListener('change', updateInvoiceTotals);
@@ -342,4 +381,5 @@ export function initInvoiceModal() {
window.openInvoiceModal = openInvoiceModal;
window.closeInvoiceModal = closeInvoiceModal;
window.addInvoiceItem = addInvoiceItem;
window.addInvoiceItem = addInvoiceItem;
window.reloadInvoiceWorkers = loadWorkers;

View File

@@ -212,7 +212,13 @@ const API = {
method: 'POST',
body: formData
}).then(r => r.json());
}
},
get: (key) => fetch(`/api/settings/${encodeURIComponent(key)}`).then(r => r.json()),
set: (key, value) => fetch(`/api/settings/${encodeURIComponent(key)}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ value })
}).then(r => r.json())
}
};

View File

@@ -5,6 +5,7 @@ let invoices = [];
let filterCustomer = localStorage.getItem('inv_filterCustomer') || '';
let filterStatus = localStorage.getItem('inv_filterStatus') || 'unpaid';
let groupBy = localStorage.getItem('inv_groupBy') || 'none';
let filterWorker = localStorage.getItem('inv_filterWorker') || '';
const OVERDUE_DAYS = 30;
@@ -194,6 +195,7 @@ function saveSettings() {
localStorage.setItem('inv_filterStatus', filterStatus);
localStorage.setItem('inv_groupBy', groupBy);
localStorage.setItem('inv_filterCustomer', filterCustomer);
localStorage.setItem('inv_filterWorker', filterWorker);
}
// ============================================================
@@ -237,6 +239,13 @@ function getFilteredInvoices() {
const s = filterCustomer.toLowerCase();
f = f.filter(i => (i.customer_name || '').toLowerCase().includes(s));
}
if (filterWorker) {
if (filterWorker === '__none__') {
f = f.filter(i => !i.worker);
} else {
f = f.filter(i => i.worker === filterWorker);
}
}
f.sort((a, b) => (parseLocalDate(b.invoice_date) || 0) - (parseLocalDate(a.invoice_date) || 0));
return f;
}
@@ -563,6 +572,13 @@ export function injectToolbar() {
class="px-3 py-1.5 border border-gray-300 rounded-md text-sm w-48 focus:ring-blue-500 focus:border-blue-500">
</div>
<div class="w-px h-8 bg-gray-300"></div>
<div class="flex items-center gap-2">
<label class="text-sm font-medium text-gray-700">Worker:</label>
<select id="invoice-filter-worker" class="px-3 py-1.5 border border-gray-300 rounded-md text-sm bg-white">
<option value="">All</option>
</select>
</div>
<div class="w-px h-8 bg-gray-300"></div>
<div class="flex items-center gap-2">
<label class="text-sm font-medium text-gray-700">Group:</label>
<select id="invoice-group-by" class="px-3 py-1.5 border border-gray-300 rounded-md text-sm bg-white">
@@ -592,6 +608,11 @@ export function injectToolbar() {
document.getElementById('invoice-group-by').addEventListener('change', (e) => {
groupBy = e.target.value; saveSettings(); renderInvoiceView();
});
// Worker-Filter befüllen und verdrahten
populateWorkerFilter();
document.getElementById('invoice-filter-worker').addEventListener('change', (e) => {
filterWorker = e.target.value; saveSettings(); renderInvoiceView();
});
}
// ============================================================
@@ -806,6 +827,33 @@ async function _saveSentDates(invoiceId) {
alert('Network error.');
}
}
async function populateWorkerFilter() {
const sel = document.getElementById('invoice-filter-worker');
if (!sel) return;
// Worker aus den geladenen Rechnungen
const fromInvoices = new Set(invoices.map(i => i.worker).filter(Boolean));
// Plus konfigurierte Liste aus Settings
try {
const result = await window.API.settings.get('invoice_workers');
if (result && result.value) {
result.value.split(',').map(w => w.trim()).filter(Boolean)
.forEach(w => fromInvoices.add(w));
}
} catch (e) { /* ignore */ }
const workers = [...fromInvoices].sort();
const hasNoWorker = invoices.some(i => !i.worker);
sel.innerHTML =
`<option value="">All</option>` +
workers.map(w => `<option value="${w}">${w}</option>`).join('') +
(hasNoWorker ? `<option value="__none__">— No worker —</option>` : '');
sel.value = filterWorker;
}
// ============================================================
// Expose
// ============================================================

View File

@@ -2,6 +2,7 @@
* settings-view.js — Logo upload, QBO import, QBO connection test
* Extracted from app.js
*/
import '../utils/api.js';
let currentLogoFile = null;
@@ -69,6 +70,8 @@ export function initSettingsView() {
}
});
}
loadWorkerSettings();
}
export async function checkQboOverdue() {
@@ -175,8 +178,42 @@ export async function importFromQBO() {
btn.disabled = false;
}
}
export async function loadWorkerSettings() {
try {
const result = await window.API.settings.get('invoice_workers');
const input = document.getElementById('workers-input');
if (input) input.value = (result && result.value) ? result.value : '';
} catch (e) {
console.error('Error loading worker settings:', e);
}
}
export async function saveWorkerSettings() {
const input = document.getElementById('workers-input');
const statusEl = document.getElementById('workers-status');
if (!input) return;
const value = input.value.trim();
statusEl.innerHTML = '<p class="text-blue-600">Saving...</p>';
try {
const result = await window.API.settings.set('invoice_workers', value);
if (result.success) {
statusEl.innerHTML = '<p class="text-green-600">✓ Worker list saved.</p>';
// Dropdown im Invoice-Modal aktualisieren
if (typeof window.reloadInvoiceWorkers === 'function') {
window.reloadInvoiceWorkers();
}
} else {
statusEl.innerHTML = `<p class="text-red-600">✗ ${result.error || 'Save failed'}</p>`;
}
} catch (e) {
console.error('Error saving worker settings:', e);
statusEl.innerHTML = '<p class="text-red-600">✗ Save failed</p>';
}
}
// Expose for onclick handlers
window.uploadLogo = uploadLogo;
window.checkQboOverdue = checkQboOverdue;
window.importFromQBO = importFromQBO;
window.importFromQBO = importFromQBO;
window.saveWorkerSettings = saveWorkerSettings;