rewritten UI
This commit is contained in:
parent
328e2b9916
commit
fb3fdb275e
@ -18,19 +18,36 @@ public class HttpServerApp {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
TransmissionService service = new TransmissionService();
|
||||
service.loadNextDays(10);
|
||||
|
||||
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
// /transmissions -> all data
|
||||
// /transmissions -> all data nebo jen pro konkrétní datum
|
||||
server.createContext("/transmissions", exchange -> {
|
||||
try {
|
||||
setCors(exchange);
|
||||
if (!checkApiKey(exchange)) return;
|
||||
|
||||
List<Transmission> all = service.getAll();
|
||||
respondJson(exchange, mapper.writeValueAsString(all));
|
||||
URI uri = exchange.getRequestURI();
|
||||
String query = uri.getQuery();
|
||||
String date = null;
|
||||
|
||||
if (query != null) {
|
||||
for (String part : query.split("&")) {
|
||||
if (part.startsWith("date=")) {
|
||||
date = java.net.URLDecoder.decode(part.substring(5), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Transmission> result;
|
||||
if (date != null && !date.isBlank()) {
|
||||
result = service.getByDateLazy(date);
|
||||
} else {
|
||||
result = service.getAll();
|
||||
}
|
||||
|
||||
respondJson(exchange, mapper.writeValueAsString(result));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
sendError(exchange, 500, e.getMessage());
|
||||
@ -68,8 +85,28 @@ public class HttpServerApp {
|
||||
if (!checkApiKey(exchange)) return;
|
||||
|
||||
if ("GET".equalsIgnoreCase(exchange.getRequestMethod())) {
|
||||
service.reloadDataAsync();
|
||||
respondJson(exchange, "{\"status\":\"ok\",\"message\":\"Data se obnovují na pozadí.\"}");
|
||||
// přečti volitelné datum z query stringu
|
||||
String dateParam = null;
|
||||
String query = exchange.getRequestURI().getQuery();
|
||||
if (query != null) {
|
||||
for (String part : query.split("&")) {
|
||||
if (part.startsWith("date=")) {
|
||||
dateParam = java.net.URLDecoder.decode(part.substring(5), StandardCharsets.UTF_8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pokud není zadané, použij dnešní den
|
||||
String dateStr = (dateParam != null && !dateParam.isBlank())
|
||||
? dateParam
|
||||
: java.time.LocalDate.now().toString();
|
||||
|
||||
// načti data pro konkrétní den
|
||||
System.out.println("🔄 Obnovuji data pro den: " + dateStr);
|
||||
service.reloadDayAsync(dateStr);
|
||||
|
||||
respondJson(exchange, "{\"status\":\"ok\",\"message\":\"Data pro den " + dateStr + " se obnovují.\"}");
|
||||
} else {
|
||||
exchange.sendResponseHeaders(405, -1);
|
||||
}
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
package cz.kamma.tvcom;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
public class Searcher {
|
||||
private static final String URL = "jdbc:mysql://server01:3306/tvcom?useSSL=false&characterEncoding=UTF-8";
|
||||
private static final String USER = "tvcom";
|
||||
private static final String PASS = "Passw0rd";
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
String search = args.length > 0 ? args[0] : "Nymburk";
|
||||
|
||||
try (Connection conn = DriverManager.getConnection(URL, USER, PASS)) {
|
||||
String sql = "SELECT * FROM transmissions WHERE MATCH(title, sport, league) AGAINST (? IN NATURAL LANGUAGE MODE)";
|
||||
PreparedStatement ps = conn.prepareStatement(sql);
|
||||
ps.setString(1, search);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
|
||||
while (rs.next()) {
|
||||
System.out.printf("%s | %s | %s | %s | %s%n",
|
||||
rs.getString("date"),
|
||||
rs.getString("time"),
|
||||
rs.getString("title"),
|
||||
rs.getString("sport"),
|
||||
rs.getString("league"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,33 +1,45 @@
|
||||
package cz.kamma.tvcom;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class TransmissionService {
|
||||
private final List<Transmission> transmissions = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
public void loadNextDays(int daysToLoad) throws InterruptedException {
|
||||
LocalDate today = LocalDate.now();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
ExecutorService executor = Executors.newFixedThreadPool(5);
|
||||
|
||||
for (int i = 0; i < daysToLoad; i++) {
|
||||
LocalDate targetDate = today.plusDays(i);
|
||||
String dateStr = targetDate.format(formatter);
|
||||
executor.submit(() -> loadDay(dateStr));
|
||||
public List<Transmission> getByDateLazy(String dateStr) {
|
||||
if (dateStr == null || dateStr.isBlank()) return Collections.emptyList();
|
||||
synchronized (transmissions) {
|
||||
boolean exists = transmissions.stream().anyMatch(t -> dateStr.equals(t.date));
|
||||
if (!exists) {
|
||||
System.out.println("📅 Data pro " + dateStr + " nejsou v paměti – načítám...");
|
||||
loadDay(dateStr);
|
||||
}
|
||||
return transmissions.stream()
|
||||
.filter(t -> dateStr.equals(t.date))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
executor.shutdown();
|
||||
executor.awaitTermination(5, TimeUnit.MINUTES);
|
||||
System.out.println("✅ Načteno celkem přenosů: " + transmissions.size());
|
||||
}
|
||||
|
||||
public void reloadDayAsync(String dateStr) {
|
||||
new Thread(() -> {
|
||||
synchronized (transmissions) {
|
||||
transmissions.removeIf(t -> dateStr.equals(t.date)); // smaž jen konkrétní den
|
||||
}
|
||||
System.out.println("🔄 Načítám znovu data pro " + dateStr);
|
||||
try {
|
||||
loadDay(dateStr);
|
||||
System.out.println("✅ Den " + dateStr + " znovu načten.");
|
||||
} catch (Exception e) {
|
||||
System.err.println("❌ Chyba při načítání dne " + dateStr + ": " + e.getMessage());
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void loadDay(String dateStr) {
|
||||
@ -74,19 +86,4 @@ public class TransmissionService {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
public void reloadDataAsync() {
|
||||
new Thread(() -> {
|
||||
synchronized (transmissions) {
|
||||
transmissions.clear();
|
||||
}
|
||||
System.out.println("🔄 Obnovuji data z webu TVCOM...");
|
||||
try {
|
||||
loadNextDays(10);
|
||||
System.out.println("✅ Data znovu načtena, přenosů celkem: " + transmissions.size());
|
||||
} catch (Exception e) {
|
||||
System.err.println("❌ Chyba při opětovném načítání: " + e.getMessage());
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,258 +1,238 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="cs">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>TVCOM — Přenosy (lokální UI)</title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>TVCOM Přenosy</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, sans-serif;
|
||||
margin: 20px;
|
||||
background: #f5f5f5;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"/>
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
<style>
|
||||
body { padding: 18px; font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial; }
|
||||
.controls { margin-bottom: 12px; display:flex; gap:8px; flex-wrap:wrap; }
|
||||
.table-container { max-height: 70vh; overflow:auto; margin-top:8px; }
|
||||
.small-muted { color:#666; font-size:0.9rem; }
|
||||
.img-thumb { width:60px; height:40px; object-fit:cover; border-radius:4px; }
|
||||
</style>
|
||||
.filters {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
input, select, button {
|
||||
padding: 6px 10px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.card img {
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.meta {
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
#loading {
|
||||
text-align: center;
|
||||
font-size: 1.2em;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
.card img {
|
||||
height: 120px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2>TVCOM — Přenosy (následujících 10 dní)</h2>
|
||||
<h1>📺 TVCOM Přenosy</h1>
|
||||
|
||||
<div class="controls">
|
||||
<input id="searchBox" class="form-control" style="max-width:320px" placeholder="Hledat (např. Brno, basketbal, Slavia)"/>
|
||||
<select id="sportFilter" class="form-select" style="max-width:220px">
|
||||
<option value="">— Všechny sporty —</option>
|
||||
</select>
|
||||
<select id="leagueFilter" class="form-select" style="max-width:220px">
|
||||
<option value="">— Všechny ligy —</option>
|
||||
</select>
|
||||
<select id="dateFilter" class="form-select" style="max-width:180px">
|
||||
<option value="">— Všechna data —</option>
|
||||
</select>
|
||||
<select id="sortOrder" class="form-select" style="max-width:180px">
|
||||
<option value="date_asc">Datum (vzestupně)</option>
|
||||
<option value="date_desc">Datum (sestupně)</option>
|
||||
<option value="time_asc">Čas (vzestupně)</option>
|
||||
<option value="time_desc">Čas (sestupně)</option>
|
||||
</select>
|
||||
<div class="filters">
|
||||
<input id="search" type="text" placeholder="Hledat přenos..." />
|
||||
<input id="datePicker" type="date" />
|
||||
<select id="sportFilter">
|
||||
<option value="">Všechny sporty</option>
|
||||
</select>
|
||||
<button id="refreshBtn">♻️ Obnovit data</button>
|
||||
</div>
|
||||
|
||||
<div style="margin-left:auto; display:flex; gap:8px;">
|
||||
<button id="refreshBtn" class="btn btn-outline-primary">Reload data</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<table id="tbl" class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Obr</th>
|
||||
<th>Datum</th>
|
||||
<th>Čas</th>
|
||||
<th>Název / Týmy</th>
|
||||
<th>Sport</th>
|
||||
<th>Liga</th>
|
||||
<th>Odkaz</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tbody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<ul id="pager" class="pagination"></ul>
|
||||
</nav>
|
||||
</div>
|
||||
<div id="loading">Načítám data...</div>
|
||||
<div id="list" class="grid" style="display: none"></div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const apiKey = urlParams.get('apiKey') || '';
|
||||
const API_BASE = "http://localhost:8080";
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const apiKey = urlParams.get('apiKey');
|
||||
const listEl = document.getElementById('list');
|
||||
const loadingEl = document.getElementById('loading');
|
||||
const searchEl = document.getElementById('search');
|
||||
const sportFilterEl = document.getElementById('sportFilter');
|
||||
const datePicker = document.getElementById('datePicker');
|
||||
const refreshBtn = document.getElementById('refreshBtn');
|
||||
let all = [];
|
||||
|
||||
const API = '/transmissions';
|
||||
let all = [];
|
||||
let filtered = [];
|
||||
const pageSize = 25;
|
||||
let currentPage = 1;
|
||||
// === inicializace ===
|
||||
const today = new Date().toISOString().substring(0, 10);
|
||||
datePicker.value = today;
|
||||
fetchForDate(today);
|
||||
|
||||
const searchBox = document.getElementById('searchBox');
|
||||
const sportFilter = document.getElementById('sportFilter');
|
||||
const leagueFilter = document.getElementById('leagueFilter');
|
||||
const dateFilter = document.getElementById('dateFilter');
|
||||
const sortOrder = document.getElementById('sortOrder');
|
||||
const tbody = document.getElementById('tbody');
|
||||
const pager = document.getElementById('pager');
|
||||
const refreshBtn = document.getElementById('refreshBtn');
|
||||
// === změna datumu ===
|
||||
datePicker.addEventListener('change', () => {
|
||||
const dateStr = datePicker.value;
|
||||
if (dateStr) fetchForDate(dateStr);
|
||||
});
|
||||
|
||||
function fetchAll() {
|
||||
fetch(API + '?apiKey=' + encodeURIComponent(apiKey))
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
all = data || [];
|
||||
populateFilters();
|
||||
applyFilters();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
alert('Chyba při načítání dat: ' + err);
|
||||
});
|
||||
}
|
||||
// === debounce funkce ===
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function(...args) {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => func.apply(this, args), wait);
|
||||
};
|
||||
}
|
||||
|
||||
function populateFilters() {
|
||||
const sports = Array.from(new Set(all.map(x => x.sport).filter(Boolean))).sort();
|
||||
sportFilter.innerHTML = '<option value="">— Všechny sporty —</option>';
|
||||
sports.forEach(s => {
|
||||
const opt = document.createElement('option'); opt.value = s; opt.textContent = s; sportFilter.appendChild(opt);
|
||||
});
|
||||
// === vyhledávání ===
|
||||
const debouncedApplyFilters = debounce(applyFilters, 300);
|
||||
searchEl.addEventListener('input', debouncedApplyFilters);
|
||||
sportFilterEl.addEventListener('change', applyFilters);
|
||||
|
||||
populateLeagueFilter();
|
||||
// === tlačítko obnovit ===
|
||||
refreshBtn.addEventListener('click', () => {
|
||||
loadingEl.style.display = 'block';
|
||||
listEl.style.display = 'none';
|
||||
const now = new Date().toISOString().substring(0, 10);
|
||||
datePicker.value = now;
|
||||
|
||||
const dates = Array.from(new Set(all.map(x => x.date).filter(Boolean))).sort();
|
||||
dateFilter.innerHTML = '<option value="">— Všechna data —</option>';
|
||||
dates.forEach(d => {
|
||||
const opt = document.createElement('option'); opt.value = d; opt.textContent = d; dateFilter.appendChild(opt);
|
||||
});
|
||||
}
|
||||
fetch(`${API_BASE}/refresh?apiKey=${apiKey}`)
|
||||
.then(r => {
|
||||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||
return r.json();
|
||||
})
|
||||
.then(() => {
|
||||
// po úspěšném refresh načteme aktuální den
|
||||
const now = new Date().toISOString().substring(0, 10);
|
||||
datePicker.value = now;
|
||||
return fetchForDate(now);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
loadingEl.textContent = "❌ Chyba při obnově dat: " + err;
|
||||
});
|
||||
});
|
||||
|
||||
function populateLeagueFilter() {
|
||||
const selectedSport = sportFilter.value;
|
||||
const leagues = Array.from(new Set(all.filter(x => !selectedSport || x.sport === selectedSport)
|
||||
.map(x => x.league).filter(Boolean))).sort();
|
||||
leagueFilter.innerHTML = '<option value="">— Všechny ligy —</option>';
|
||||
leagues.forEach(l => {
|
||||
const opt = document.createElement('option'); opt.value = l; opt.textContent = l; leagueFilter.appendChild(opt);
|
||||
});
|
||||
}
|
||||
// === načtení dat pro daný den ===
|
||||
function fetchForDate(dateStr) {
|
||||
loadingEl.style.display = 'block';
|
||||
listEl.style.display = 'none';
|
||||
return fetch(`${API_BASE}/transmissions?date=${encodeURIComponent(dateStr)}&apiKey=${apiKey}`)
|
||||
.then(r => {
|
||||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||
return r.json();
|
||||
})
|
||||
.then(data => {
|
||||
all = data || [];
|
||||
populateSports();
|
||||
applyFilters();
|
||||
loadingEl.style.display = 'none';
|
||||
listEl.style.display = 'grid';
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
loadingEl.textContent = "❌ Chyba při načítání dat: " + err;
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeText(s) {
|
||||
if (!s) return '';
|
||||
return s.normalize('NFD').replace(/\p{Diacritic}/gu, '').toLowerCase();
|
||||
}
|
||||
// === naplnění filtrů podle dostupných sportů ===
|
||||
function populateSports() {
|
||||
const sports = [...new Set(all.map(t => t.sport).filter(Boolean))].sort();
|
||||
sportFilterEl.innerHTML = '<option value="">Všechny sporty</option>';
|
||||
for (const s of sports) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = s;
|
||||
opt.textContent = s;
|
||||
sportFilterEl.appendChild(opt);
|
||||
}
|
||||
}
|
||||
|
||||
function applyFilters() {
|
||||
const q = normalizeText(searchBox.value.trim());
|
||||
const sport = sportFilter.value;
|
||||
const league = leagueFilter.value;
|
||||
const date = dateFilter.value;
|
||||
// === aplikace filtrů a vykreslení ===
|
||||
function applyFilters() {
|
||||
const query = searchEl.value.toLowerCase();
|
||||
const sport = sportFilterEl.value;
|
||||
const filtered = all.filter(t => {
|
||||
return (!query || t.title.toLowerCase().includes(query) || (t.league||'').toLowerCase().includes(query)) &&
|
||||
(!sport || t.sport === sport);
|
||||
});
|
||||
renderList(filtered);
|
||||
}
|
||||
|
||||
filtered = all.filter(t => {
|
||||
if (sport && t.sport !== sport) return false;
|
||||
if (league && t.league !== league) return false;
|
||||
if (date && t.date !== date) return false;
|
||||
if (!q) return true;
|
||||
const text = normalizeText((t.title||'') + ' ' + (t.sport||'') + ' ' + (t.league||''));
|
||||
return text.indexOf(q) !== -1;
|
||||
});
|
||||
// === formátování data a času ===
|
||||
function formatDateTime(dateStr, timeStr) {
|
||||
if (!dateStr || !timeStr) return '';
|
||||
const dt = new Date(dateStr + 'T' + timeStr);
|
||||
return dt.toLocaleString('cs-CZ', { dateStyle: 'short', timeStyle: 'short' });
|
||||
}
|
||||
|
||||
sortFiltered();
|
||||
currentPage = 1;
|
||||
renderTable();
|
||||
renderPager();
|
||||
}
|
||||
|
||||
function sortFiltered() {
|
||||
const ord = sortOrder.value;
|
||||
filtered.sort((a,b) => {
|
||||
if (ord === 'date_asc') return (a.date||'').localeCompare(b.date||'') || (a.time||'').localeCompare(b.time||'');
|
||||
if (ord === 'date_desc') return (b.date||'').localeCompare(a.date||'') || (b.time||'').localeCompare(a.time||'');
|
||||
if (ord === 'time_asc') return (a.time||'').localeCompare(b.time||'');
|
||||
if (ord === 'time_desc') return (b.time||'').localeCompare(a.time||'');
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
function renderTable() {
|
||||
tbody.innerHTML = '';
|
||||
const start = (currentPage-1)*pageSize;
|
||||
const pageItems = filtered.slice(start, start + pageSize);
|
||||
for (const t of pageItems) {
|
||||
const tr = document.createElement('tr');
|
||||
|
||||
const imgTd = document.createElement('td');
|
||||
if (t.image) {
|
||||
const img = document.createElement('img');
|
||||
img.src = t.image;
|
||||
img.className = 'img-thumb';
|
||||
imgTd.appendChild(img);
|
||||
}
|
||||
tr.appendChild(imgTd);
|
||||
|
||||
const dateTd = document.createElement('td'); dateTd.textContent = t.date; tr.appendChild(dateTd);
|
||||
const timeTd = document.createElement('td'); timeTd.textContent = t.time; tr.appendChild(timeTd);
|
||||
|
||||
const titleTd = document.createElement('td');
|
||||
titleTd.innerHTML = '<strong>' + escapeHtml(t.title||'') + '</strong><div class="small-muted">' + escapeHtml(t.leaguePart||'') + '</div>';
|
||||
tr.appendChild(titleTd);
|
||||
|
||||
const sportTd = document.createElement('td'); sportTd.textContent = t.sport; tr.appendChild(sportTd);
|
||||
const leagueTd = document.createElement('td'); leagueTd.textContent = t.league; tr.appendChild(leagueTd);
|
||||
|
||||
const linkTd = document.createElement('td');
|
||||
if (t.link) {
|
||||
const a = document.createElement('a'); a.href = t.link; a.textContent = 'Otevřít'; a.target = '_blank';
|
||||
linkTd.appendChild(a);
|
||||
}
|
||||
tr.appendChild(linkTd);
|
||||
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
}
|
||||
|
||||
function renderPager() {
|
||||
pager.innerHTML = '';
|
||||
const pages = Math.max(1, Math.ceil(filtered.length / pageSize));
|
||||
const maxButtons = 7;
|
||||
let start = Math.max(1, currentPage - Math.floor(maxButtons/2));
|
||||
let end = Math.min(pages, start + maxButtons - 1);
|
||||
if (end - start + 1 < maxButtons) start = Math.max(1, end - maxButtons + 1);
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
const li = document.createElement('li'); li.className = 'page-item' + (i===currentPage ? ' active' : '');
|
||||
const a = document.createElement('a'); a.className = 'page-link'; a.href = '#';
|
||||
a.textContent = i;
|
||||
a.onclick = (ev => { ev.preventDefault(); currentPage = i; renderTable(); renderPager(); });
|
||||
li.appendChild(a);
|
||||
pager.appendChild(li);
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(s) {
|
||||
if (!s) return '';
|
||||
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||
}
|
||||
|
||||
let debounceTimer;
|
||||
searchBox.addEventListener('input', () => { clearTimeout(debounceTimer); debounceTimer = setTimeout(applyFilters, 250); });
|
||||
sportFilter.addEventListener('change', () => { populateLeagueFilter(); applyFilters(); });
|
||||
leagueFilter.addEventListener('change', applyFilters);
|
||||
dateFilter.addEventListener('change', applyFilters);
|
||||
sortOrder.addEventListener('change', () => { sortFiltered(); renderTable(); });
|
||||
|
||||
function refreshData() {
|
||||
refreshBtn.disabled = true;
|
||||
refreshBtn.textContent = "Načítám...";
|
||||
fetch('/refresh?apiKey=' + encodeURIComponent(apiKey))
|
||||
.then(r => r.json())
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
fetchAll();
|
||||
refreshBtn.disabled = false;
|
||||
refreshBtn.textContent = "Refresh";
|
||||
}, 7000);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
alert('Chyba při obnově dat: ' + err);
|
||||
refreshBtn.disabled = false;
|
||||
refreshBtn.textContent = "Refresh";
|
||||
});
|
||||
}
|
||||
|
||||
refreshBtn.addEventListener('click', refreshData);
|
||||
|
||||
fetchAll();
|
||||
|
||||
})();
|
||||
// === vykreslení seznamu ===
|
||||
function renderList(items) {
|
||||
listEl.innerHTML = '';
|
||||
if (!items.length) {
|
||||
listEl.innerHTML = '<div style="grid-column:1/-1;text-align:center;">Žádné přenosy</div>';
|
||||
return;
|
||||
}
|
||||
for (const t of items) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'card';
|
||||
const dateTimeStr = formatDateTime(t.date, t.time);
|
||||
div.innerHTML = `
|
||||
<a href="${t.link}" target="_blank">
|
||||
<img src="${t.image || 'https://via.placeholder.com/400x160?text=Žádný+obrázek'}" alt="">
|
||||
<div class="card-content">
|
||||
<div class="title">${t.title}</div>
|
||||
<div class="meta">${dateTimeStr}</div>
|
||||
<div class="meta">${t.sport} • ${t.league || ''} ${t.leaguePart || ''}</div>
|
||||
</div>
|
||||
</a>
|
||||
`;
|
||||
listEl.appendChild(div);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user