feat: add table sorting and advanced filters
This commit is contained in:
parent
6b5d385463
commit
4f93293927
111
index.html
111
index.html
@ -17,6 +17,8 @@
|
||||
.table-wrap{overflow:auto;-webkit-overflow-scrolling:touch}
|
||||
table{width:100%;border-collapse:collapse;font-size:13px;min-width:760px}
|
||||
th,td{border-bottom:1px solid #24315f;padding:8px;text-align:left;vertical-align:top}
|
||||
th.sortable{cursor:pointer;user-select:none}
|
||||
th.sortable:hover{background:#1a2550}
|
||||
a{color:#9ec5ff;text-decoration:underline;text-underline-offset:2px}
|
||||
a:hover{color:#cfe2ff}
|
||||
td b{color:#ffffff;font-weight:700;letter-spacing:0.1px}
|
||||
@ -53,6 +55,37 @@
|
||||
<label>TLD
|
||||
<select id="tld"><option value="">Wszystkie</option><option>pl</option><option>com</option><option>ai</option></select>
|
||||
</label>
|
||||
<label>LLM decyzja
|
||||
<select id="decisionFilter">
|
||||
<option value="">Wszystkie</option>
|
||||
<option value="buy_now">buy_now</option>
|
||||
<option value="watch">watch</option>
|
||||
<option value="skip">skip</option>
|
||||
<option value="none">brak</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Status
|
||||
<select id="statusFilter">
|
||||
<option value="">Wszystkie</option>
|
||||
<option value="available">available</option>
|
||||
<option value="unknown">unknown</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Min score <input id="minScore" type="number" min="0" max="100" step="1" placeholder="np. 70" /></label>
|
||||
<label>Sortuj po
|
||||
<select id="sortBy">
|
||||
<option value="score">score</option>
|
||||
<option value="llm_score">llm_score</option>
|
||||
<option value="domain">domena</option>
|
||||
<option value="scanned_at">czas skanu</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Kierunek
|
||||
<select id="sortDir">
|
||||
<option value="desc">malejąco</option>
|
||||
<option value="asc">rosnąco</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="small"><input type="checkbox" id="onlyNew" /> Tylko nowe domeny</label>
|
||||
<label class="small"><input type="checkbox" id="autoRefresh" checked /> Auto-refresh 60s</label>
|
||||
<button id="reload">Odśwież</button>
|
||||
@ -64,7 +97,7 @@
|
||||
<h2>TOP domen + porównanie cen (rejestracja / odnowienie)</h2>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead><tr><th>Domena</th><th>TLD</th><th>Score</th><th>LLM</th><th>Status</th><th>Run</th><th>Skan</th><th>Oferty (top 3)</th></tr></thead>
|
||||
<thead><tr><th class="sortable" data-sort="domain">Domena</th><th class="sortable" data-sort="tld">TLD</th><th class="sortable" data-sort="score">Score</th><th class="sortable" data-sort="llm_score">LLM</th><th class="sortable" data-sort="status">Status</th><th>Run</th><th class="sortable" data-sort="scanned_at">Skan</th><th>Oferty (top 3)</th></tr></thead>
|
||||
<tbody id="rows"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -73,8 +106,10 @@
|
||||
|
||||
<script>
|
||||
let domains = [];
|
||||
let visibleDomains = [];
|
||||
let livePriceByTld = {};
|
||||
let viewMode = 'current';
|
||||
let metaBase = '';
|
||||
|
||||
function money(v){ return (v===null || v===undefined || Number.isNaN(Number(v))) ? '—' : `${Number(v).toFixed(2)} zł`; }
|
||||
|
||||
@ -146,16 +181,61 @@ async function loadDomains(){
|
||||
const d = await (await fetch(u)).json();
|
||||
domains = d.domains || [];
|
||||
if(viewMode === 'all'){
|
||||
document.getElementById('meta').textContent = `widok: wszystkie rekordy | limit: ${d.meta?.limit||'—'} | widoczne: ${domains.length}`;
|
||||
metaBase = `widok: wszystkie rekordy | limit: ${d.meta?.limit||'—'} | widoczne: ${domains.length}`;
|
||||
} else {
|
||||
document.getElementById('meta').textContent = `runId: ${d.meta?.runId||'—'} | prev: ${d.meta?.prevRunId||'—'} | scannedAt: ${d.meta?.scannedAt||'—'} | nowe: ${d.meta?.newCount||0} | widoczne: ${domains.length}`;
|
||||
metaBase = `runId: ${d.meta?.runId||'—'} | prev: ${d.meta?.prevRunId||'—'} | scannedAt: ${d.meta?.scannedAt||'—'} | nowe: ${d.meta?.newCount||0} | widoczne: ${domains.length}`;
|
||||
}
|
||||
document.getElementById('meta').textContent = metaBase;
|
||||
}
|
||||
|
||||
function applyFiltersAndSort(){
|
||||
const decision = document.getElementById('decisionFilter').value;
|
||||
const status = document.getElementById('statusFilter').value;
|
||||
const minScoreRaw = document.getElementById('minScore').value;
|
||||
const minScore = minScoreRaw === '' ? null : Number(minScoreRaw);
|
||||
const sortBy = document.getElementById('sortBy').value;
|
||||
const sortDir = document.getElementById('sortDir').value;
|
||||
|
||||
let arr = (domains || []).filter(d => {
|
||||
if (decision === 'none' && d.decision) return false;
|
||||
if (decision && decision !== 'none' && (d.decision || '') !== decision) return false;
|
||||
if (status && (d.status || 'available') !== status) return false;
|
||||
if (minScore !== null && Number(d.score || 0) < minScore) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
const dir = sortDir === 'asc' ? 1 : -1;
|
||||
arr.sort((a,b)=>{
|
||||
let av = a?.[sortBy];
|
||||
let bv = b?.[sortBy];
|
||||
|
||||
if (sortBy === 'llm_score') {
|
||||
av = av === null || av === undefined ? -1 : Number(av);
|
||||
bv = bv === null || bv === undefined ? -1 : Number(bv);
|
||||
} else if (sortBy === 'score') {
|
||||
av = Number(av || 0);
|
||||
bv = Number(bv || 0);
|
||||
} else if (sortBy === 'scanned_at') {
|
||||
av = av ? Date.parse(av) : 0;
|
||||
bv = bv ? Date.parse(bv) : 0;
|
||||
} else {
|
||||
av = (av || '').toString().toLowerCase();
|
||||
bv = (bv || '').toString().toLowerCase();
|
||||
}
|
||||
|
||||
if (av < bv) return -1 * dir;
|
||||
if (av > bv) return 1 * dir;
|
||||
return 0;
|
||||
});
|
||||
|
||||
visibleDomains = arr;
|
||||
}
|
||||
|
||||
function renderRows(){
|
||||
applyFiltersAndSort();
|
||||
const rows = document.getElementById('rows');
|
||||
rows.innerHTML='';
|
||||
for(const d of domains.slice(0,300)){
|
||||
for(const d of visibleDomains.slice(0,300)){
|
||||
const tr = document.createElement('tr');
|
||||
const checkUrl = domainCheckUrl(d.domain);
|
||||
const offers = topOffersByTld(d.tld);
|
||||
@ -181,6 +261,10 @@ function renderRows(){
|
||||
<td>${offersHtml}</td>`;
|
||||
rows.appendChild(tr);
|
||||
}
|
||||
const meta = document.getElementById('meta');
|
||||
if(meta){
|
||||
meta.textContent = `${metaBase} | po filtrach: ${visibleDomains.length}`;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshAll(){
|
||||
@ -223,6 +307,25 @@ document.getElementById('q').addEventListener('input', async()=>{
|
||||
});
|
||||
document.getElementById('tld').addEventListener('change', async()=>{await loadDomains(); await loadLivePricesForDomains(domains); renderRows();});
|
||||
document.getElementById('onlyNew').addEventListener('change', async()=>{await loadDomains(); await loadLivePricesForDomains(domains); renderRows();});
|
||||
document.getElementById('decisionFilter').addEventListener('change', renderRows);
|
||||
document.getElementById('statusFilter').addEventListener('change', renderRows);
|
||||
document.getElementById('minScore').addEventListener('input', renderRows);
|
||||
document.getElementById('sortBy').addEventListener('change', renderRows);
|
||||
document.getElementById('sortDir').addEventListener('change', renderRows);
|
||||
document.querySelectorAll('th.sortable').forEach(th=>{
|
||||
th.addEventListener('click', ()=>{
|
||||
const key = th.dataset.sort;
|
||||
const sortByEl = document.getElementById('sortBy');
|
||||
const sortDirEl = document.getElementById('sortDir');
|
||||
if(sortByEl.value === key){
|
||||
sortDirEl.value = sortDirEl.value === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
sortByEl.value = key;
|
||||
sortDirEl.value = (key === 'domain' || key === 'tld' || key === 'status') ? 'asc' : 'desc';
|
||||
}
|
||||
renderRows();
|
||||
});
|
||||
});
|
||||
document.getElementById('autoRefresh').addEventListener('change', setupAutoRefresh);
|
||||
|
||||
refreshAll();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user