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-wrap{overflow:auto;-webkit-overflow-scrolling:touch}
|
||||||
table{width:100%;border-collapse:collapse;font-size:13px;min-width:760px}
|
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,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{color:#9ec5ff;text-decoration:underline;text-underline-offset:2px}
|
||||||
a:hover{color:#cfe2ff}
|
a:hover{color:#cfe2ff}
|
||||||
td b{color:#ffffff;font-weight:700;letter-spacing:0.1px}
|
td b{color:#ffffff;font-weight:700;letter-spacing:0.1px}
|
||||||
@ -53,6 +55,37 @@
|
|||||||
<label>TLD
|
<label>TLD
|
||||||
<select id="tld"><option value="">Wszystkie</option><option>pl</option><option>com</option><option>ai</option></select>
|
<select id="tld"><option value="">Wszystkie</option><option>pl</option><option>com</option><option>ai</option></select>
|
||||||
</label>
|
</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="onlyNew" /> Tylko nowe domeny</label>
|
||||||
<label class="small"><input type="checkbox" id="autoRefresh" checked /> Auto-refresh 60s</label>
|
<label class="small"><input type="checkbox" id="autoRefresh" checked /> Auto-refresh 60s</label>
|
||||||
<button id="reload">Odśwież</button>
|
<button id="reload">Odśwież</button>
|
||||||
@ -64,7 +97,7 @@
|
|||||||
<h2>TOP domen + porównanie cen (rejestracja / odnowienie)</h2>
|
<h2>TOP domen + porównanie cen (rejestracja / odnowienie)</h2>
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
<table>
|
<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>
|
<tbody id="rows"></tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@ -73,8 +106,10 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
let domains = [];
|
let domains = [];
|
||||||
|
let visibleDomains = [];
|
||||||
let livePriceByTld = {};
|
let livePriceByTld = {};
|
||||||
let viewMode = 'current';
|
let viewMode = 'current';
|
||||||
|
let metaBase = '';
|
||||||
|
|
||||||
function money(v){ return (v===null || v===undefined || Number.isNaN(Number(v))) ? '—' : `${Number(v).toFixed(2)} zł`; }
|
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();
|
const d = await (await fetch(u)).json();
|
||||||
domains = d.domains || [];
|
domains = d.domains || [];
|
||||||
if(viewMode === 'all'){
|
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 {
|
} 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(){
|
function renderRows(){
|
||||||
|
applyFiltersAndSort();
|
||||||
const rows = document.getElementById('rows');
|
const rows = document.getElementById('rows');
|
||||||
rows.innerHTML='';
|
rows.innerHTML='';
|
||||||
for(const d of domains.slice(0,300)){
|
for(const d of visibleDomains.slice(0,300)){
|
||||||
const tr = document.createElement('tr');
|
const tr = document.createElement('tr');
|
||||||
const checkUrl = domainCheckUrl(d.domain);
|
const checkUrl = domainCheckUrl(d.domain);
|
||||||
const offers = topOffersByTld(d.tld);
|
const offers = topOffersByTld(d.tld);
|
||||||
@ -181,6 +261,10 @@ function renderRows(){
|
|||||||
<td>${offersHtml}</td>`;
|
<td>${offersHtml}</td>`;
|
||||||
rows.appendChild(tr);
|
rows.appendChild(tr);
|
||||||
}
|
}
|
||||||
|
const meta = document.getElementById('meta');
|
||||||
|
if(meta){
|
||||||
|
meta.textContent = `${metaBase} | po filtrach: ${visibleDomains.length}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshAll(){
|
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('tld').addEventListener('change', async()=>{await loadDomains(); await loadLivePricesForDomains(domains); renderRows();});
|
||||||
document.getElementById('onlyNew').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);
|
document.getElementById('autoRefresh').addEventListener('change', setupAutoRefresh);
|
||||||
|
|
||||||
refreshAll();
|
refreshAll();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user