vlad / Event Express Pro Floor Interface Enhancer with Filter, Collapse, and TOC

// ==UserScript==
// @name         Event Express Pro Floor Interface Enhancer with Filter, Collapse, and TOC
// @namespace    http://tampermonkey.net/
// @version      1.9
// @description  Improve the readability of the Event Express Pro Floor Interface HTML, add a filter by name, collapse/expand tables, and add a TOC
// @author       Vlad Sitalo
// @license MIT
// @match        https://eepro.com/results/*/*.html*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Create a container for the TOC and content
    let container = document.createElement('div');
    container.style.display = 'flex';
    container.style.width = '100%';
    document.body.insertBefore(container, document.body.firstChild);

    // Create a TOC container
    let tocContainer = document.createElement('div');
    tocContainer.style.position = 'sticky';
    tocContainer.style.top = '0';
    tocContainer.style.height = '100vh';
    tocContainer.style.width = '300px';
    tocContainer.style.padding = '10px';
    tocContainer.style.borderRight = '1px solid #ddd';
    tocContainer.style.backgroundColor = '#f8f8f8';
    tocContainer.style.boxShadow = '2px 0 5px rgba(0,0,0,0.1)';
    container.appendChild(tocContainer);

    let tocTitle = document.createElement('h3');
    tocTitle.textContent = 'Table of Contents';
    tocContainer.appendChild(tocTitle);

    let tocList = document.createElement('ul');
    tocContainer.appendChild(tocList);

    // Create a content container
    let contentContainer = document.createElement('div');
    contentContainer.style.flexGrow = '1';
    contentContainer.style.padding = '20px';
    container.appendChild(contentContainer);

    // Move the existing content into the content container
    while (document.body.children.length > 1) {
        contentContainer.appendChild(document.body.children[1]);
    }

    // Create a filter input
    let filterDiv = document.createElement('div');
    filterDiv.style.textAlign = 'center';
    filterDiv.style.margin = '20px';

    let filterInput = document.createElement('input');
    filterInput.type = 'text';
    filterInput.placeholder = 'Filter by Competitor Name...';
    filterInput.style.padding = '10px';
    filterInput.style.width = '300px';
    filterDiv.appendChild(filterInput);

    contentContainer.insertBefore(filterDiv, contentContainer.firstChild);

    filterInput.addEventListener('input', function() {
        let filterValue = filterInput.value.toLowerCase();
        let tables = document.querySelectorAll('table');

        tables.forEach(table => {
            let rows = table.querySelectorAll('tr');
            let tableHasMatch = false;

            rows.forEach((row, index) => {
                if (index <= 1) {
                    row.style.display = ''; // Keep the first two rows visible
                } else {
                    let competitorCell = row.cells[1]; // Assuming the competitor name is in the second cell
                    if (competitorCell) {
                        let competitorName = competitorCell.textContent.toLowerCase();
                        if (competitorName.includes(filterValue)) {
                            row.style.display = '';
                            tableHasMatch = true;
                        } else {
                            row.style.display = 'none';
                        }
                    }
                }
            });

            table.style.display = tableHasMatch ? '' : 'none';
        });
    });

    // Add collapse/expand functionality
    let tables = document.querySelectorAll('table');
    tables.forEach((table, tableIndex) => {
        let titleRow = table.querySelector('tr:first-child');
        if (titleRow) {
            titleRow.style.cursor = 'pointer';
            titleRow.addEventListener('click', () => {
                let isCollapsed = titleRow.classList.toggle('collapsed');
                let rows = table.querySelectorAll('tr');
                rows.forEach((row, index) => {
                    if (index > 0) {
                        row.style.display = isCollapsed ? 'none' : '';
                    }
                });
            });

            // Add TOC entry
            let titleText = titleRow.textContent;
            let match = titleText.match(/Jack & Jill (Leader|Follower) (Novice|Intermediate|Advanced|All Star|Champions) (.*) -/i);
            if (match) {
                let [_, role, division, round] = match;
                let tocDivision = tocList.querySelector(`li[data-division="${division}"]`);
                if (!tocDivision) {
                    tocDivision = document.createElement('li');
                    tocDivision.dataset.division = division;
                    tocDivision.textContent = division;
                    let subList = document.createElement('ul');
                    tocDivision.appendChild(subList);
                    tocList.appendChild(tocDivision);
                }
                let tocRole = tocDivision.querySelector(`li[data-role="${role}"]`);
                if (!tocRole) {
                    tocRole = document.createElement('li');
                    tocRole.dataset.role = role;
                    tocRole.textContent = role;
                    let subList = document.createElement('ul');
                    tocRole.appendChild(subList);
                    tocDivision.querySelector('ul').appendChild(tocRole);
                }
                let tocEntry = document.createElement('li');
                tocEntry.textContent = round.trim();
                tocEntry.style.cursor = 'pointer';
                tocEntry.addEventListener('click', () => {
                    window.scrollTo({
                        top: table.offsetTop,
                        behavior: 'smooth'
                    });
                });
                tocRole.querySelector('ul').appendChild(tocEntry);
            }
        }
    });

    // Hide TOC if empty
    if (tocList.children.length === 0) {
        tocContainer.style.display = 'none';
    }

    // Style for collapsed state
    let style = document.createElement('style');
    style.innerHTML = `
        .collapsed {
            background-color: #ccc;
        }
    `;
    document.head.appendChild(style);

    // Style the table
    tables.forEach(table => {
        table.style.width = '80%';
        table.style.margin = '20px auto';
        table.style.borderCollapse = 'collapse';
    });

    // Style table headers
    let headers = document.querySelectorAll('table th, table td');
    headers.forEach(header => {
        header.style.padding = '10px';
        header.style.border = '1px solid #ddd';
    });

    // Highlight rows on hover
    let rows = document.querySelectorAll('table tr');
    rows.forEach(row => {
        row.addEventListener('mouseover', () => {
            row.style.backgroundColor = '#f1f1f1';
        });
        row.addEventListener('mouseout', () => {
            row.style.backgroundColor = '';
        });
    });

    // Improve readability of text
    document.body.style.fontFamily = 'Arial, sans-serif';
    document.body.style.lineHeight = '1.6';
})();