CSVをテーブル表示してフィルタリングやソートできるHTMLファイル

自宅の検証環境のWindowsでイベントログが結構出ているので、csv出力してログを軽く眺めたいなと思いまして、CSVファイルをテーブル表示してかつ、
フィルタリングやソートのような簡単な機能を持ったHTMLコードを作ってみました。
ちなみに、自宅の検証環境が一部インターネットにつながっていないので、
一応オフラインの環境でも動くようにコードが作られているはずです。
（Google Geminiを一部使用してコード作成していますので、使用は自己責任で。）

```html:CSViewer.html
<!DOCTYPE html>
<html>
<head>
    <title>CSViewer</title>
    <style>
        table {
            border-collapse: collapse;
            width: 100%;
        }
        th, td {
            border: 1px solid black;
            padding: 8px;
            text-align: left;
        }
        th {
            background-color: #f2f2f2;
            cursor: pointer;
        }
        .filter {
            width: 100%;
            padding: 8px;
            margin-bottom: 8px;
        }
    </style>
</head>
<body>
    <input type="file" id="csvFileInput">
    <p>使い方<br>①CSVファイルを選択して読み込むとテーブルが表示されます
        <br>②検索条件をフィルター欄に入力すると検索されます
        <br>③テーブルの並び替えはカラム（先頭行）をダブルクリックします</p>
    <div id="filters"></div>
    <br>
    <table id="csvTable">
        <thead>
            <tr></tr>
        </thead>
        <tbody></tbody>
    </table>

    <script>
        const csvFileInput = document.getElementById('csvFileInput');
        const filtersDiv = document.getElementById('filters');
        const csvTable = document.getElementById('csvTable');

        csvFileInput.addEventListener('change', function(event) {
            const file = event.target.files[0];
            const reader = new FileReader();

            reader.onload = function(e) {
                const csv = e.target.result;
                const data = parseCSV(csv);
                renderTable(data);
                renderFilters(data[0]);
            };

            reader.readAsText(file);
        });

        function parseCSV(csv) {
            const lines = csv.split('\n');
            return lines.map(line => line.split(','));
        }

        function renderTable(data) {
            const theadRow = csvTable.querySelector('thead tr');
            theadRow.innerHTML = '';
            const tbody = csvTable.querySelector('tbody');
            tbody.innerHTML = '';

            if (data.length === 0) return;

            data[0].forEach(header => {
                const th = document.createElement('th');
                th.textContent = header;
                th.addEventListener('click', () => sortTable(data, header));
                theadRow.appendChild(th);
            });

            for (let i = 1; i < data.length; i++) {
                const row = document.createElement('tr');
                data[i].forEach(cell => {
                    const td = document.createElement('td');
                    td.textContent = cell;
                    row.appendChild(td);
                });
                // 特定列の値に応じて背景色を変更（重大度を視覚的にわかりやすくするため）
                const targetColumnIndex = 2; // 例)3列目を指定（1列目は0から数える）
                if (data[i][targetColumnIndex] === 'エラー') {
                    row.style.backgroundColor = 'red';
                } else if (data[i][targetColumnIndex] === '警告') {
                    row.style.backgroundColor = 'orange';
                }
                tbody.appendChild(row);
            }
        }

        function renderFilters(headers) {
            filtersDiv.innerHTML = '';
            headers.forEach(header => {
                const input = document.createElement('input');
                input.type = 'text';
                input.placeholder = `Filter ${header}`;
                input.className = 'filter';
                input.addEventListener('input', () => filterTable());
                filtersDiv.appendChild(input);
            });
        }

        function filterTable() {
            const filters = Array.from(filtersDiv.querySelectorAll('.filter'));
            const tbodyRows = Array.from(csvTable.querySelector('tbody').rows);

            tbodyRows.forEach(row => {
                let display = true;
                filters.forEach((filter, index) => {
                    if (filter.value && !row.cells[index].textContent.includes(filter.value)) {
                        display = false;
                    }
                });
                row.style.display = display ? '' : 'none';
            });
        }

        function sortTable(data, header) {
            const index = data[0].indexOf(header);
            const body = csvTable.querySelector("tbody");
            const new_data = data.slice(1)

            new_data.sort((a, b) => {
                if (a[index] < b[index]) {
                    return -1;
                } else if (a[index] > b[index]) {
                    return 1;
                } else {
                    return 0;
                }
            });

            const newData = [data[0], ...new_data];
            renderTable(newData)

        }
    </script>
</body>
</html>
```

ついでに、csvファイルを結合するbatコードも追記。

```bat
@echo off
setlocal

set "outputFile=結合結果.csv"
del "%outputFile%" 2>nul

powershell.exe -NoProfile -Command ^
    "Get-ChildItem *.csv | Where-Object {$_.Name -ne '%outputFile%'} | ForEach-Object {(Get-Content $_.FullName | Select-Object -Skip 1) | Add-Content '%outputFile%'}"

if %errorlevel% == 0 (
    echo 結合が完了しました。
) else (
    echo エラーが発生しました。
)

endlocal
pause
```