Przejdź do treści
<code>pen.pl

Ranking szachowy — arkusz turniejowy

Wizualizacja danych

Podgląd

Ładowanie podglądu…

Turniejowy arkusz z filtrem sekcji, sortowaniem wyników i stronicowaniem pełnej klasyfikacji.

0,0 / 5 Brak ocen

Kod źródłowy

Licencja MIT — także do użytku komercyjnego. Zachowaj informacje o autorze i licencji.

HTML
<main class="chess-sheet" id="chess-table">
  <header><div class="round">Runda<br><b>09</b></div><div><p>TURNIEJ MIEJSKI · FINAŁ</p><h1>Tabela wyników</h1></div><div class="seal" aria-hidden="true">64</div></header>
  <section class="chess-controls" aria-label="Filtry wyników">
    <label for="chess-search">Zawodnik lub klub</label><input id="chess-search" type="search" data-search placeholder="Wpisz nazwisko albo klub">
    <label for="chess-division">Sekcja</label><select id="chess-division" data-filter><option value="">Wszystkie sekcje</option><option>Open</option><option>Junior</option><option>Klubowa</option></select>
    <button type="button" data-reset>Resetuj tabelę</button>
  </section>
  <p class="chess-status" role="status" aria-live="polite"></p>
  <div class="table-scroll" tabindex="0" aria-label="Tabela turniejowa — przewijaj poziomo na małym ekranie"><table>
    <caption>Klasyfikacja po dziewięciu rundach</caption><thead><tr>
      <th scope="col"><button type="button" data-sort="player">Zawodnik <span data-sort-mark aria-hidden="true">↕</span></button></th>
      <th scope="col"><button type="button" data-sort="club">Klub <span data-sort-mark aria-hidden="true">↕</span></button></th>
      <th scope="col"><button type="button" data-sort="rating">Ranking <span data-sort-mark aria-hidden="true">↕</span></button></th>
      <th scope="col"><button type="button" data-sort="points">Pkt <span data-sort-mark aria-hidden="true">↕</span></button></th>
      <th scope="col"><button type="button" data-sort="buchholz">Buchholz <span data-sort-mark aria-hidden="true">↕</span></button></th>
      <th scope="col"><button type="button" data-sort="wins">Wygrane <span data-sort-mark aria-hidden="true">↕</span></button></th>
    </tr></thead><tbody><tr><td colspan="6">Układanie klasyfikacji…</td></tr></tbody>
  </table></div><nav class="chess-pages" data-pages aria-label="Stronicowanie tabeli"></nav>
</main>
CSS
*{box-sizing:border-box}body{margin:0;background:#d8d2c3;color:#171717;font-family:Arial,Helvetica,sans-serif}.chess-sheet{min-height:100vh;margin:0 auto;padding:clamp(16px,4vw,48px);background:#f3f0e7;border-left:clamp(0px,3vw,34px) solid #1546d8;max-width:1120px}.chess-sheet>header{display:grid;grid-template-columns:auto 1fr auto;gap:20px;align-items:center;border-top:4px solid #171717;border-bottom:4px solid #171717;padding:12px 0}.chess-sheet header p{margin:0 0 3px;font-size:12px;letter-spacing:.12em}.chess-sheet h1{font:700 clamp(34px,6vw,68px)/.95 Georgia,serif;margin:0;letter-spacing:-.045em}.round{border-right:2px solid #171717;padding-right:18px;text-transform:uppercase;font-size:10px;letter-spacing:.13em}.round b{font:700 34px/1 Georgia,serif}.seal{width:54px;height:54px;display:grid;place-items:center;background:#d33b2f;color:#fff;font:700 22px Georgia,serif;transform:rotate(3deg)}.chess-controls{display:grid;grid-template-columns:auto minmax(200px,1fr) auto minmax(150px,.4fr) auto;align-items:end;gap:10px;padding:22px 0 12px}.chess-controls label{font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.06em}.chess-controls input,.chess-controls select,.chess-controls button{height:46px;border:2px solid #171717;border-radius:0;background:#fff;color:#171717;font:inherit;padding:0 12px}.chess-controls button{background:#171717;color:#fff;cursor:pointer}.chess-status{min-height:20px;margin:0 0 8px;font-size:13px}.table-scroll{overflow-x:auto;max-width:100%;border:2px solid #171717;background:#fff}.table-scroll:focus-visible{outline:4px solid #1546d8;outline-offset:3px}table{width:100%;min-width:760px;border-collapse:collapse}caption{padding:12px 14px;text-align:left;background:#1546d8;color:#fff;font-weight:700}th,td{border-right:1px solid #aaa391;border-bottom:1px solid #aaa391;padding:11px 12px;text-align:left}thead{background:#e7e1d4}thead th{padding:0 10px}th button{min-height:48px;width:100%;border:0;background:transparent;color:#171717;font:700 12px Arial,sans-serif;text-align:left;text-transform:uppercase;cursor:pointer}th[aria-sort]{background:#ffda3e}tbody th{font-family:Georgia,serif;font-size:16px}tbody th small{display:block;margin-top:3px;color:#625e55;font:10px Arial,sans-serif;text-transform:uppercase;letter-spacing:.04em}tbody tr:nth-child(even){background:#f5f1e9}.points{font-size:21px;font-weight:800;color:#1546d8}.empty-row{text-align:center;padding:42px}.chess-pages{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:0;padding-top:14px}.chess-pages button{min-width:46px;min-height:46px;border:2px solid #171717;border-right:0;border-radius:0;background:#f3f0e7;color:#171717;font-weight:700;cursor:pointer}.chess-pages button:last-child{border-right:2px solid #171717}.chess-pages button[aria-current="page"]{background:#1546d8;color:#fff}.chess-pages button:disabled{color:#797468;cursor:not-allowed}button:focus-visible,input:focus-visible,select:focus-visible{outline:4px solid #1546d8;outline-offset:2px}@media(max-width:760px){.chess-sheet{padding:14px;border-left-width:8px}.chess-sheet>header{grid-template-columns:auto 1fr}.seal{display:none}.chess-controls{grid-template-columns:1fr}.chess-controls label{margin-top:5px}.chess-pages{justify-content:stretch}.chess-pages button:first-child,.chess-pages button:last-child{flex:1}}@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}
JavaScript
$(() => {
  const records = [{"player":"Maja Czarnecka","club":"Hetman Gdańsk","division":"Open","rating":2184,"points":7.5,"buchholz":46.0,"wins":7},{"player":"Jan Rosiński","club":"Polonia Wrocław","division":"Open","rating":2240,"points":7.0,"buchholz":48.5,"wins":6},{"player":"Nina Wysocka","club":"Korona Kraków","division":"Open","rating":2112,"points":6.5,"buchholz":44.0,"wins":5},{"player":"Oskar Lemański","club":"Skoczek Poznań","division":"Open","rating":2078,"points":6.0,"buchholz":45.5,"wins":5},{"player":"Iga Lorenc","club":"Wieża Płock","division":"Open","rating":2031,"points":5.5,"buchholz":41.0,"wins":5},{"player":"Adam Górski","club":"Caissa Opole","division":"Open","rating":1998,"points":5.0,"buchholz":39.5,"wins":4},{"player":"Lena Borkowska","club":"Gambit Toruń","division":"Junior","rating":1886,"points":7.0,"buchholz":43.0,"wins":6},{"player":"Mikołaj Serafin","club":"Start Lublin","division":"Junior","rating":1832,"points":6.5,"buchholz":40.5,"wins":6},{"player":"Zofia Kania","club":"Szach Mat Łódź","division":"Junior","rating":1795,"points":6.0,"buchholz":42.0,"wins":5},{"player":"Leon Witkowski","club":"Orkan Rzeszów","division":"Junior","rating":1768,"points":5.5,"buchholz":38.0,"wins":5},{"player":"Ewa Maj","club":"Wieża Płock","division":"Klubowa","rating":1920,"points":6.5,"buchholz":41.5,"wins":5},{"player":"Piotr Wrona","club":"Hetman Gdańsk","division":"Klubowa","rating":1904,"points":6.0,"buchholz":40.0,"wins":5},{"player":"Sara Biel","club":"Gambit Toruń","division":"Klubowa","rating":1875,"points":5.5,"buchholz":39.0,"wins":4},{"player":"Tomasz Jura","club":"Start Lublin","division":"Klubowa","rating":1850,"points":5.0,"buchholz":37.5,"wins":4}];
  const numericColumns = new Set(["rating", "points", "buchholz", "wins"]);
  const collator = new Intl.Collator('pl', { numeric: true, sensitivity: 'base' });
  const state = { query: '', sort: 'points', direction: 'desc', page: 1 };
  const pageSize = 5;
  const $root = $('#chess-table');
  const $body = $root.find('tbody');
  const $pages = $root.find('[data-pages]');
  const $status = $root.find('[role="status"]');

  function filteredRecords() {
    const query = state.query.trim().toLocaleLowerCase('pl');
    return records.filter((record) => {
      const matchesQuery = !query || Object.values(record).join(' ').toLocaleLowerCase('pl').includes(query);
      const division = $('#chess-division').val();
      const matchesFilters = !division || record.division === division;
      return matchesQuery && matchesFilters;
    });
  }

  function compare(left, right) {
    const key = state.sort;
    const result = numericColumns.has(key)
      ? Number(left[key]) - Number(right[key])
      : collator.compare(String(left[key]), String(right[key]));
    return state.direction === 'asc' ? result : -result;
  }

  function rowTemplate(record) {
    return `<tr><th scope="row">${record.player}<small>${record.division}</small></th><td>${record.club}</td><td>${record.rating}</td><td class="points">${record.points.toFixed(1)}</td><td>${record.buchholz.toFixed(1)}</td><td>${record.wins}</td></tr>`;
  }

  function render() {
    const result = filteredRecords().sort(compare);
    const totalPages = Math.max(1, Math.ceil(result.length / pageSize));
    state.page = Math.min(state.page, totalPages);
    const visible = result.slice((state.page - 1) * pageSize, state.page * pageSize);

    $body.html(visible.length
      ? visible.map(rowTemplate).join('')
      : '<tr><td class="empty-row" colspan="6">Brak wyników. Zmień filtry lub wyczyść wyszukiwanie.</td></tr>');

    $root.find('thead th').removeAttr('aria-sort');
    $root.find('[data-sort]').each(function () {
      const active = this.dataset.sort === state.sort;
      $(this).find('[data-sort-mark]').text(active ? (state.direction === 'asc' ? '↑' : '↓') : '↕');
      if (active) $(this).closest('th').attr('aria-sort', state.direction === 'asc' ? 'ascending' : 'descending');
    });

    const buttons = [];
    buttons.push(`<button type="button" data-page="${state.page - 1}" ${state.page === 1 ? 'disabled' : ''}>Poprzednia</button>`);
    for (let page = 1; page <= totalPages; page += 1) {
      buttons.push(`<button type="button" data-page="${page}" ${page === state.page ? 'aria-current="page"' : ''} aria-label="Strona ${page}">${page}</button>`);
    }
    buttons.push(`<button type="button" data-page="${state.page + 1}" ${state.page === totalPages ? 'disabled' : ''}>Następna</button>`);
    $pages.html(buttons.join(''));

    const direction = state.direction === 'asc' ? 'rosnąco' : 'malejąco';
    $status.text(`Wyniki: ${result.length} · Strona ${state.page} z ${totalPages} · Sortowanie: ${direction}.`);
  }

  $root.on('input', '[data-search]', function () {
    state.query = this.value;
    state.page = 1;
    render();
  });
  $root.on('change', '[data-filter]', function () {
    state.page = 1;
    render();
  });
  $root.on('click', '[data-sort]', function () {
    const key = this.dataset.sort;
    state.direction = state.sort === key && state.direction === 'asc' ? 'desc' : 'asc';
    state.sort = key;
    state.page = 1;
    render();
    this.focus();
  });
  $root.on('click', '[data-page]', function () {
    if (this.disabled) return;
    state.page = Number(this.dataset.page);
    render();
    $pages.find(`[data-page="${state.page}"]`).focus();
  });
  $root.on('click', '[data-reset]', () => {
    $root.find('[data-search]').val('');
    $root.find('select[data-filter]').prop('selectedIndex', 0);
    $root.find('input[type="radio"][data-filter]').first().prop('checked', true);
    state.query = '';
    state.sort = 'points';
    state.direction = 'desc';
    state.page = 1;
    render();
    $root.find('[data-search]').focus();
  });

  render();
});
Biblioteki

JavaScript

Edytuj swój pen

Podaj adres e-mail użyty przy zapisie. Wyślemy link do edycji.

Link jest ważny przez 30 minut.