const API = 'api.php'; let customers = []; let sortField = 'yritys'; let sortAsc = true; let currentDetailId = null; // Elements const loginScreen = document.getElementById('login-screen'); const dashboard = document.getElementById('dashboard'); const loginForm = document.getElementById('login-form'); const loginError = document.getElementById('login-error'); const searchInput = document.getElementById('search-input'); const tbody = document.getElementById('customer-tbody'); const noCustomers = document.getElementById('no-customers'); const customerCount = document.getElementById('customer-count'); const totalBilling = document.getElementById('total-billing'); const customerModal = document.getElementById('customer-modal'); const detailModal = document.getElementById('detail-modal'); const customerForm = document.getElementById('customer-form'); // API helpers async function apiCall(action, method = 'GET', body = null) { const opts = { method, credentials: 'include' }; if (body) { opts.headers = { 'Content-Type': 'application/json' }; opts.body = JSON.stringify(body); } const res = await fetch(`${API}?action=${action}`, opts); const data = await res.json(); if (!res.ok) throw new Error(data.error || 'Virhe'); return data; } // Auth async function checkAuth() { try { const data = await apiCall('check_auth'); if (data.authenticated) { showDashboard(); } } catch (e) { /* not logged in */ } } loginForm.addEventListener('submit', async (e) => { e.preventDefault(); const password = document.getElementById('login-password').value; try { await apiCall('login', 'POST', { password }); loginError.style.display = 'none'; showDashboard(); } catch (err) { loginError.textContent = err.message; loginError.style.display = 'block'; } }); document.getElementById('btn-logout').addEventListener('click', async () => { await apiCall('logout'); dashboard.style.display = 'none'; loginScreen.style.display = 'flex'; document.getElementById('login-password').value = ''; }); async function showDashboard() { loginScreen.style.display = 'none'; dashboard.style.display = 'block'; await loadCustomers(); } // Customers async function loadCustomers() { customers = await apiCall('customers'); renderTable(); } function renderTable() { const query = searchInput.value.toLowerCase().trim(); let filtered = customers; if (query) { filtered = customers.filter(c => c.yritys.toLowerCase().includes(query) || (c.asennusosoite || '').toLowerCase().includes(query) || (c.postinumero || '').toLowerCase().includes(query) || (c.kaupunki || '').toLowerCase().includes(query) || (c.yhteyshenkilö || '').toLowerCase().includes(query) || (c.liittymanopeus || '').toLowerCase().includes(query) ); } // Sort filtered.sort((a, b) => { let va = a[sortField] ?? ''; let vb = b[sortField] ?? ''; if (sortField === 'hinta') { va = parseFloat(va) || 0; vb = parseFloat(vb) || 0; } else { va = String(va).toLowerCase(); vb = String(vb).toLowerCase(); } if (va < vb) return sortAsc ? -1 : 1; if (va > vb) return sortAsc ? 1 : -1; return 0; }); if (filtered.length === 0) { tbody.innerHTML = ''; noCustomers.style.display = 'block'; document.getElementById('customer-table').style.display = 'none'; } else { noCustomers.style.display = 'none'; document.getElementById('customer-table').style.display = 'table'; tbody.innerHTML = filtered.map(c => ` ${esc(c.yritys)} ${esc(c.asennusosoite)} ${esc(c.postinumero)} ${esc(c.kaupunki)} ${esc(c.liittymanopeus)} ${formatPrice(c.hinta)} `).join(''); } updateSummary(filtered); } function updateSummary(filtered) { const count = customers.length; const total = customers.reduce((sum, c) => sum + (parseFloat(c.hinta) || 0), 0); customerCount.textContent = `${count} asiakasta`; totalBilling.textContent = `Laskutus yhteensä: ${formatPrice(total)}/kk`; // Stat-kortit const statCount = document.getElementById('stat-count'); const statBilling = document.getElementById('stat-billing'); const statYearly = document.getElementById('stat-yearly'); if (statCount) statCount.textContent = count; if (statBilling) statBilling.textContent = formatPrice(total); if (statYearly) statYearly.textContent = formatPrice(total * 12); // Nippelitilastot updateTrivia(); } function updateTrivia() { const count = customers.length; if (count === 0) { setTrivia('stat-top-zip', '-', ''); setTrivia('stat-top-speed', '-', ''); setText('stat-avg-price', '-'); return; } // Suosituin postinumero const zipCounts = {}; customers.forEach(c => { const zip = (c.postinumero || '').trim(); if (zip) zipCounts[zip] = (zipCounts[zip] || 0) + 1; }); const topZip = Object.entries(zipCounts).sort((a, b) => b[1] - a[1])[0]; if (topZip) { const city = customers.find(c => (c.postinumero || '').trim() === topZip[0]); setTrivia('stat-top-zip', topZip[0], `${topZip[1]} liittymää` + (city && city.kaupunki ? ` (${city.kaupunki})` : '')); } else { setTrivia('stat-top-zip', '-', 'ei postinumeroita'); } // Suosituin nopeus const speedCounts = {}; customers.forEach(c => { const speed = (c.liittymanopeus || '').trim(); if (speed) speedCounts[speed] = (speedCounts[speed] || 0) + 1; }); const topSpeed = Object.entries(speedCounts).sort((a, b) => b[1] - a[1])[0]; if (topSpeed) { setTrivia('stat-top-speed', topSpeed[0], `${topSpeed[1]} liittymää`); } else { setTrivia('stat-top-speed', '-', ''); } // Keskihinta const total = customers.reduce((sum, c) => sum + (parseFloat(c.hinta) || 0), 0); setText('stat-avg-price', formatPrice(total / count)); } function setTrivia(id, value, sub) { const el = document.getElementById(id); const subEl = document.getElementById(id + '-detail'); if (el) el.textContent = value; if (subEl) subEl.textContent = sub; } function setText(id, value) { const el = document.getElementById(id); if (el) el.textContent = value; } function formatPrice(val) { return parseFloat(val || 0).toFixed(2).replace('.', ',') + ' €'; } function esc(str) { if (!str) return ''; const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } // Search searchInput.addEventListener('input', () => renderTable()); // Sort document.querySelectorAll('th[data-sort]').forEach(th => { th.addEventListener('click', () => { const field = th.dataset.sort; if (sortField === field) { sortAsc = !sortAsc; } else { sortField = field; sortAsc = true; } renderTable(); }); }); // Row click -> detail tbody.addEventListener('click', (e) => { const row = e.target.closest('tr'); if (!row) return; const id = row.dataset.id; showDetail(id); }); function detailVal(val) { return val ? esc(val) : '-'; } function detailLink(val, type) { if (!val) return '-'; if (type === 'tel') return `${esc(val)}`; if (type === 'email') return `${esc(val)}`; return esc(val); } function showDetail(id) { const c = customers.find(x => x.id === id); if (!c) return; currentDetailId = id; const fullAddress = [c.asennusosoite, c.postinumero, c.kaupunki].filter(Boolean).join(', '); const fullBillingAddress = [c.laskutusosoite, c.laskutuspostinumero, c.laskutuskaupunki].filter(Boolean).join(', '); document.getElementById('detail-title').textContent = c.yritys; document.getElementById('detail-body').innerHTML = `

Yritys ja liittymä

Yritys
${detailVal(c.yritys)}
Y-tunnus
${detailVal(c.ytunnus)}
Asennusosoite
${detailVal(fullAddress)}
Nopeus
${detailVal(c.liittymanopeus)}
Hinta / kk
${formatPrice(c.hinta)}

Yhteystiedot

Yhteyshenkilö
${detailVal(c.yhteyshenkilö)}
Puhelin
${detailLink(c.puhelin, 'tel')}
Sähköposti
${detailLink(c.sahkoposti, 'email')}

Laskutustiedot

Laskutusosoite
${detailVal(fullBillingAddress)}
Laskutussähköposti
${detailLink(c.laskutussahkoposti, 'email')}
E-laskuosoite
${detailVal(c.elaskuosoite)}
E-laskuvälittäjä
${detailVal(c.elaskuvalittaja)}
${c.lisatiedot ? `

Lisätiedot

${esc(c.lisatiedot)}

` : ''} `; detailModal.style.display = 'flex'; } // Detail modal actions document.getElementById('detail-close').addEventListener('click', () => detailModal.style.display = 'none'); document.getElementById('detail-cancel').addEventListener('click', () => detailModal.style.display = 'none'); document.getElementById('detail-edit').addEventListener('click', () => { detailModal.style.display = 'none'; editCustomer(currentDetailId); }); document.getElementById('detail-delete').addEventListener('click', () => { const c = customers.find(x => x.id === currentDetailId); if (c) { detailModal.style.display = 'none'; deleteCustomer(currentDetailId, c.yritys); } }); // Add/Edit modal document.getElementById('btn-add').addEventListener('click', () => openCustomerForm()); document.getElementById('modal-close').addEventListener('click', () => customerModal.style.display = 'none'); document.getElementById('form-cancel').addEventListener('click', () => customerModal.style.display = 'none'); function openCustomerForm(customer = null) { const c = customer; document.getElementById('modal-title').textContent = c ? 'Muokkaa asiakasta' : 'Lisää asiakas'; document.getElementById('form-submit').textContent = c ? 'Päivitä' : 'Tallenna'; document.getElementById('form-id').value = c ? c.id : ''; document.getElementById('form-yritys').value = c ? c.yritys : ''; document.getElementById('form-ytunnus').value = c ? c.ytunnus : ''; document.getElementById('form-asennusosoite').value = c ? c.asennusosoite : ''; document.getElementById('form-postinumero').value = c ? (c.postinumero || '') : ''; document.getElementById('form-kaupunki').value = c ? (c.kaupunki || '') : ''; document.getElementById('form-liittymanopeus').value = c ? c.liittymanopeus : ''; document.getElementById('form-hinta').value = c ? c.hinta : ''; document.getElementById('form-yhteyshenkilo').value = c ? c.yhteyshenkilö : ''; document.getElementById('form-puhelin').value = c ? c.puhelin : ''; document.getElementById('form-sahkoposti').value = c ? c.sahkoposti : ''; document.getElementById('form-laskutusosoite').value = c ? c.laskutusosoite : ''; document.getElementById('form-laskutuspostinumero').value = c ? (c.laskutuspostinumero || '') : ''; document.getElementById('form-laskutuskaupunki').value = c ? (c.laskutuskaupunki || '') : ''; document.getElementById('form-laskutussahkoposti').value = c ? c.laskutussahkoposti : ''; document.getElementById('form-elaskuosoite').value = c ? (c.elaskuosoite || '') : ''; document.getElementById('form-elaskuvalittaja').value = c ? (c.elaskuvalittaja || '') : ''; document.getElementById('form-lisatiedot').value = c ? c.lisatiedot : ''; customerModal.style.display = 'flex'; document.getElementById('form-yritys').focus(); } function editCustomer(id) { const c = customers.find(x => x.id === id); if (c) openCustomerForm(c); } async function deleteCustomer(id, name) { if (!confirm(`Poistetaanko asiakas "${name}"?`)) return; await apiCall('customer_delete', 'POST', { id }); await loadCustomers(); } customerForm.addEventListener('submit', async (e) => { e.preventDefault(); const id = document.getElementById('form-id').value; const data = { yritys: document.getElementById('form-yritys').value, ytunnus: document.getElementById('form-ytunnus').value, asennusosoite: document.getElementById('form-asennusosoite').value, postinumero: document.getElementById('form-postinumero').value, kaupunki: document.getElementById('form-kaupunki').value, liittymanopeus: document.getElementById('form-liittymanopeus').value, hinta: document.getElementById('form-hinta').value, yhteyshenkilö: document.getElementById('form-yhteyshenkilo').value, puhelin: document.getElementById('form-puhelin').value, sahkoposti: document.getElementById('form-sahkoposti').value, laskutusosoite: document.getElementById('form-laskutusosoite').value, laskutuspostinumero: document.getElementById('form-laskutuspostinumero').value, laskutuskaupunki: document.getElementById('form-laskutuskaupunki').value, laskutussahkoposti: document.getElementById('form-laskutussahkoposti').value, elaskuosoite: document.getElementById('form-elaskuosoite').value, elaskuvalittaja: document.getElementById('form-elaskuvalittaja').value, lisatiedot: document.getElementById('form-lisatiedot').value, }; if (id) { data.id = id; await apiCall('customer_update', 'POST', data); } else { await apiCall('customer', 'POST', data); } customerModal.style.display = 'none'; await loadCustomers(); }); // Close modals on backdrop click customerModal.addEventListener('click', (e) => { if (e.target === customerModal) customerModal.style.display = 'none'; }); detailModal.addEventListener('click', (e) => { if (e.target === detailModal) detailModal.style.display = 'none'; }); // ESC to close modals document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { customerModal.style.display = 'none'; detailModal.style.display = 'none'; } }); // Init checkAuth();