Piilota asiakashinnat oletuksena + näytä sopimuksen jäljellä oleva aika

Hinnat:
- Asiakastaulukon hinnat blurrattu oletuksena (privacy)
- Hinta/kk -otsikossa silmä-checkbox jolla saa hinnat näkyviin/piiloon
- Blur pätee myös asiakaskortin detail-näkymään

Sopimuskausi:
- Näyttää jäljellä olevan ajan sulkeissa: "36 kk (30 kk jäljellä)"
- Päättyneet sopimukset punaisella: "36 kk (päättynyt)"
- Aloituspäivä poistettu luettelonäkymästä (näkyy detail-kortissa)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 08:24:38 +02:00
parent 64bf6aa2da
commit 417728683c
3 changed files with 43 additions and 6 deletions

View File

@@ -105,7 +105,7 @@
<th data-sort="asennusosoite">Osoite &#8597;</th> <th data-sort="asennusosoite">Osoite &#8597;</th>
<th data-sort="kaupunki">Kaupunki &#8597;</th> <th data-sort="kaupunki">Kaupunki &#8597;</th>
<th data-sort="liittymanopeus">Nopeus &#8597;</th> <th data-sort="liittymanopeus">Nopeus &#8597;</th>
<th data-sort="hinta">Hinta/kk &#8597;</th> <th data-sort="hinta">Hinta/kk &#8597; <label title="Näytä/piilota hinnat" style="cursor:pointer;font-weight:normal;font-size:0.75rem;margin-left:0.3rem;"><input type="checkbox" id="toggle-prices" style="vertical-align:middle;"> 👁</label></th>
<th data-sort="sopimuskausi">Sopimus &#8597;</th> <th data-sort="sopimuskausi">Sopimus &#8597;</th>
<th>Toiminnot</th> <th>Toiminnot</th>
</tr> </tr>

View File

@@ -347,15 +347,14 @@ function renderTable() {
const c = r.customer, l = r.liittyma; const c = r.customer, l = r.liittyma;
const isFirst = c.id !== prevId; const isFirst = c.id !== prevId;
prevId = c.id; prevId = c.id;
const sopimus = l.sopimuskausi ? l.sopimuskausi + ' kk' : ''; const sopimusStr = contractRemaining(l.sopimuskausi, l.alkupvm);
const alkupvm = l.alkupvm ? ' (' + esc(l.alkupvm) + ')' : '';
return `<tr data-id="${c.id}" class="${isFirst ? '' : 'sub-row'}"> return `<tr data-id="${c.id}" class="${isFirst ? '' : 'sub-row'}">
<td>${isFirst ? '<strong>' + esc(c.yritys) + '</strong>' : '<span class="sub-marker">&#8627;</span>'}</td> <td>${isFirst ? '<strong>' + esc(c.yritys) + '</strong>' : '<span class="sub-marker">&#8627;</span>'}</td>
<td>${esc(l.asennusosoite)}${l.postinumero ? ', ' + esc(l.postinumero) : ''}</td> <td>${esc(l.asennusosoite)}${l.postinumero ? ', ' + esc(l.postinumero) : ''}</td>
<td>${esc(l.kaupunki)}</td> <td>${esc(l.kaupunki)}</td>
<td>${esc(l.liittymanopeus)}</td> <td>${esc(l.liittymanopeus)}</td>
<td class="price-cell">${formatPrice(l.hinta)}</td> <td class="price-cell">${formatPrice(l.hinta)}</td>
<td>${sopimus}${alkupvm}</td> <td>${sopimusStr}</td>
<td class="actions-cell">${isFirst ? `<button onclick="event.stopPropagation();editCustomer('${c.id}')" title="Muokkaa">&#9998;</button><button onclick="event.stopPropagation();deleteCustomer('${c.id}','${esc(c.yritys)}')" title="Arkistoi">&#128451;</button>` : ''}</td> <td class="actions-cell">${isFirst ? `<button onclick="event.stopPropagation();editCustomer('${c.id}')" title="Muokkaa">&#9998;</button><button onclick="event.stopPropagation();deleteCustomer('${c.id}','${esc(c.yritys)}')" title="Arkistoi">&#128451;</button>` : ''}</td>
</tr>`; </tr>`;
}).join(''); }).join('');
@@ -426,6 +425,36 @@ function setTrivia(id, value, sub) {
} }
function setText(id, value) { const el = document.getElementById(id); if (el) el.textContent = value; } 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 formatPrice(val) { return parseFloat(val || 0).toFixed(2).replace('.', ',') + ' €'; }
function contractRemaining(sopimuskausi, alkupvm) {
if (!sopimuskausi) return '';
const months = parseInt(sopimuskausi);
if (!months || !alkupvm) return months + ' kk';
const start = new Date(alkupvm);
if (isNaN(start.getTime())) return months + ' kk';
const end = new Date(start);
end.setMonth(end.getMonth() + months);
const now = new Date();
const diffMs = end - now;
if (diffMs <= 0) return `${months} kk <span style="color:#e74c3c;font-size:0.8rem;">(päättynyt)</span>`;
const remainMonths = Math.ceil(diffMs / (1000 * 60 * 60 * 24 * 30.44));
return `${months} kk <span style="color:#888;font-size:0.8rem;">(${remainMonths} kk jäljellä)</span>`;
}
// Hintojen näyttö/piilotus
(function() {
const toggle = document.getElementById('toggle-prices');
if (!toggle) return;
// Oletuksena piilossa
document.getElementById('customer-table')?.classList.add('prices-hidden');
toggle.addEventListener('change', () => {
document.getElementById('customer-table')?.classList.toggle('prices-hidden', !toggle.checked);
// Blurraa myös asiakaskortin hinnat
document.querySelectorAll('.customer-detail-card').forEach(el => {
el.classList.toggle('prices-hidden', !toggle.checked);
});
});
})();
function esc(str) { if (!str) return ''; const d = document.createElement('div'); d.textContent = str; return d.innerHTML; } function esc(str) { if (!str) return ''; const d = document.createElement('div'); d.textContent = str; return d.innerHTML; }
function timeAgo(dateStr) { function timeAgo(dateStr) {
@@ -486,7 +515,7 @@ function showDetail(id) {
<div class="detail-item"><div class="detail-label">Osoite</div><div class="detail-value">${detailVal(addr)}</div></div> <div class="detail-item"><div class="detail-label">Osoite</div><div class="detail-value">${detailVal(addr)}</div></div>
<div class="detail-item"><div class="detail-label">Nopeus</div><div class="detail-value">${detailVal(l.liittymanopeus)}</div></div> <div class="detail-item"><div class="detail-label">Nopeus</div><div class="detail-value">${detailVal(l.liittymanopeus)}</div></div>
<div class="detail-item"><div class="detail-label">Hinta / kk</div><div class="detail-value price-cell">${formatPrice(l.hinta)}</div></div> <div class="detail-item"><div class="detail-label">Hinta / kk</div><div class="detail-value price-cell">${formatPrice(l.hinta)}</div></div>
<div class="detail-item"><div class="detail-label">Sopimuskausi</div><div class="detail-value">${l.sopimuskausi ? l.sopimuskausi + ' kk' : '-'}</div></div> <div class="detail-item"><div class="detail-label">Sopimuskausi</div><div class="detail-value">${contractRemaining(l.sopimuskausi, l.alkupvm) || '-'}</div></div>
<div class="detail-item"><div class="detail-label">Alkaen</div><div class="detail-value">${detailVal(l.alkupvm)}</div></div> <div class="detail-item"><div class="detail-label">Alkaen</div><div class="detail-value">${detailVal(l.alkupvm)}</div></div>
<div class="detail-item"><div class="detail-label">VLAN</div><div class="detail-value">${detailVal(l.vlan)}</div></div> <div class="detail-item"><div class="detail-label">VLAN</div><div class="detail-value">${detailVal(l.vlan)}</div></div>
<div class="detail-item"><div class="detail-label">Laite</div><div class="detail-value">${detailVal(l.laite)}</div></div> <div class="detail-item"><div class="detail-label">Laite</div><div class="detail-value">${detailVal(l.laite)}</div></div>
@@ -504,7 +533,7 @@ function showDetail(id) {
<div class="detail-item"><div class="detail-label">Y-tunnus</div><div class="detail-value">${detailVal(c.ytunnus)}</div></div> <div class="detail-item"><div class="detail-label">Y-tunnus</div><div class="detail-value">${detailVal(c.ytunnus)}</div></div>
</div></div> </div></div>
<div class="detail-section"><h3>Liittymät (${liittymat.length})</h3>${liittymatHtml} <div class="detail-section"><h3>Liittymät (${liittymat.length})</h3>${liittymatHtml}
${liittymat.length > 1 ? `<div class="liittyma-total">Yhteensä: ${formatPrice(totalH)}/kk</div>` : ''} ${liittymat.length > 1 ? `<div class="liittyma-total"><span class="price-cell">${formatPrice(totalH)}/kk</span></div>` : ''}
</div> </div>
<div class="detail-section"><h3>Yhteystiedot</h3><div class="detail-grid"> <div class="detail-section"><h3>Yhteystiedot</h3><div class="detail-grid">
<div class="detail-item"><div class="detail-label">Yhteyshenkilö</div><div class="detail-value">${detailVal(c.yhteyshenkilö)}</div></div> <div class="detail-item"><div class="detail-label">Yhteyshenkilö</div><div class="detail-value">${detailVal(c.yhteyshenkilö)}</div></div>
@@ -528,6 +557,9 @@ function showDetail(id) {
<div id="file-list" class="file-list" style="margin-top:0.75rem;"></div> <div id="file-list" class="file-list" style="margin-top:0.75rem;"></div>
</div>`; </div>`;
// Synkronoi prices-hidden tila detail-modaliin
const pricesHidden = document.getElementById('customer-table')?.classList.contains('prices-hidden');
detailModal.querySelector('.modal-content')?.classList.toggle('prices-hidden', !!pricesHidden);
detailModal.style.display = 'flex'; detailModal.style.display = 'flex';
loadFiles(id); loadFiles(id);
document.getElementById('file-upload-input').addEventListener('change', async function () { document.getElementById('file-upload-input').addEventListener('change', async function () {

View File

@@ -420,6 +420,11 @@ tbody td {
font-weight: 700; font-weight: 700;
color: var(--primary-color); color: var(--primary-color);
} }
.prices-hidden .price-cell {
filter: blur(6px);
user-select: none;
transition: filter 0.2s;
}
.actions-cell { .actions-cell {
white-space: nowrap; white-space: nowrap;