< Summary

Information
Class: AspNetCoreGeneratedDocument.Areas_Studio2_Views_MatchFileData_MatchResults
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Views/MatchFileData/MatchResults.cshtml
Line coverage
0%
Covered lines: 0
Uncovered lines: 19
Coverable lines: 19
Total lines: 515
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 30
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
ExecuteAsync()0%1980440%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Views/MatchFileData/MatchResults.cshtml

#LineLine coverage
 1@using OzonOrdersWeb.ViewModels.MatchFileDataViewModel
 2
 3@model MatchedRowViewModel
 4
 5<style>
 6    .bg-green-300 {
 7        background-color: #73c487 !important; /* Bootstrap's 'bg-success' color */
 8    }
 9
 10    .selected-row {
 11        background-color: #cce5ff !important; /* Голубой цвет */
 12    }
 13
 14    .loader {
 15        border: 5px solid #f3f3f3;
 16        border-top: 5px solid #3498db;
 17        border-radius: 50%;
 18        width: 50px;
 19        height: 50px;
 20        animation: spin 1s linear infinite;
 21        display: flex; /* Скрыто по умолчанию */
 22        position: absolute;
 23        top: 50%; /* Центр по вертикали */
 24        left: 50%; /* Центр по горизонтали */
 25        transform: translate(-50%, -50%); /* Центрирование */
 26    }
 27
 28    @@keyframes spin {
 29        0% { transform: rotate(0deg); }
 30        100% { transform: rotate(360deg); }
 31    }
 32
 33    .spinner-overlay {
 34        display: flex;
 35        position: fixed;
 36        top: 50%; /* Центрируем по вертикали */
 37        left: 50%; /* Центрируем по горизонтали */
 38        transform: translate(-50%, -50%); /* Смещаем на половину ширины и высоты спиннера */
 39        z-index: 1050; /* Убедимся, что он выше всех элементов */
 40    }
 41
 42    .spinner-border {
 43        width: 3rem;
 44        height: 3rem;
 45        color: #007bff; /* Синий цвет Bootstrap */
 46    }
 47
 48
 49</style>
 50
 51<div class="container-fluid">
 52    <!-- Спиннер загрузки -->
 53    <div id="loadingSpinner" class="spinner-overlay">
 54        <div class="spinner-border text-primary" role="status"></div>
 55    </div>
 56
 57
 58
 059    @if (ViewBag.Message != null)
 60    {
 61        <div class="alert alert-dismissible alert-success">
 62            <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
 063            <p>@ViewBag.Message</p>
 64        </div>
 65    }
 66
 67    <div id="messageContainer" class="position-fixed top-0 end-0 p-3" style="z-index: 1050;">
 68        <div id="flashMessage" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
 69            <div class="toast-header">
 70                <strong class="me-auto">Сообщение</strong>
 71                <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
 72            </div>
 73            <div class="toast-body">
 74                <span id="flashMessageText"></span>
 75            </div>
 76        </div>
 77    </div>
 78
 79
 80    <nav class="navbar navbar-expand-lg navbar-light sticky-top">
 81        <button class="btn btn-primary me-2" onclick="submitMatchedRows()">Сохранить</button>
 82        <div class="container">
 83            <div class="input-group mb-3">
 84                <button class="btn btn-secondary" onclick="previousMatch()">←</button>
 85                <input type="text" id="searchInput" placeholder="Поиск..." class="form-control" onkeyup="searchInTables(
 86                <span id="matchCount" class="input-group-text" style="display: none;">Найдено: 0</span>
 87                <button class="btn btn-secondary" onclick="nextMatch()">→</button>
 88            </div>
 89        </div>
 90        <div class="d-block">
 91            <label class="me-2">
 92                <input type="checkbox" id="showFilled" checked onchange="filterTables()"> Не сопоставленные
 93            </label>
 94            <br>
 95            <label class="me-2">
 96                <input type="checkbox" id="showEmpty" checked onchange="filterTables()"> Сопоставленные
 97            </label>
 98        </div>
 99    </nav>
 100
 101
 102
 103
 104    <div class="row">
 105        <table class="table table-bordered">
 106            <thead>
 107                <tr>
 108                    <th style="width: 50%;">
 109                        <h3>Заказы</h3>
 0110                        @Model.MainFileName
 111                    </th>
 112                    <th style="width: 50%;">
 113                        <h2>Платежи</h2>
 0114                        @Model.ScondaryFileName
 115                    </th>
 116                </tr>
 117            </thead>
 118            <tbody>
 0119                @foreach (var matchedRow in Model.MatchedResults.Select((value, index) => new { value, index }))
 120                {
 121                    <tr class="@(matchedRow.value.File1Data != null && matchedRow.value.File1Data.Any() &&
 122                                 matchedRow.value.File2Data != null && matchedRow.value.File2Data.Any() ?
 123                                 "full-file-data" : "empty-file-data")">
 124                        <td style="max-height: 300px; max-width: 300px; overflow-y: auto; overflow-x: auto; white-space:
 0125                            @if (matchedRow.value.File1Data != null && matchedRow.value.File1Data.Any())
 126                            {
 127                                <table class="table table-sm filled-file1" style="width: 100%;">
 128                                    <thead>
 129                                        <tr>
 0130                                            @foreach (var kvp in matchedRow.value.File1Data)
 131                                            {
 0132                                                bool isInMatchingColumns = Model.MatchingColumns.Any(mc => mc.Item1 == k
 133                                                <th style="@(isInMatchingColumns ? "background-color: #73c487;" : "")">
 0134                                                    <strong>@kvp.Key</strong>
 135                                                </th>
 136                                            }
 137                                        </tr>
 138                                    </thead>
 139                                    <tbody>
 140                                        <tr onclick="selectTargetRow(this, @matchedRow.index)">
 0141                                            @foreach (var kvp in matchedRow.value.File1Data)
 142                                            {
 0143                                                <td>@kvp.Value</td>
 144                                            }
 145                                        </tr>
 146                                    </tbody>
 147                                </table>
 148                            }
 149                            else
 150                            {
 151                                <span class="empty-file1">Нет записей</span>
 152                            }
 153                        </td>
 154
 155                        <td id="payment-block-@matchedRow.index" style="max-height: 300px; max-width: 300px; overflow-y:
 0156                            @if (matchedRow.value.File2Data != null && matchedRow.value.File2Data.Any())
 157                            {
 158                                <table class="table table-sm filled-file2" style="width: 100%;">
 159                                    <thead>
 160                                        <tr>
 0161                                            @foreach (var kvp in matchedRow.value.File2Data[0])
 162                                            {
 0163                                                bool isInMatchingColumns = Model.MatchingColumns.Any(mc => mc.Item2 == k
 164                                                <th style="@(isInMatchingColumns ? "background-color: #73c487;" : "")">
 0165                                                    <strong>@kvp.Key</strong>
 166                                                </th>
 167                                            }
 168                                        </tr>
 169                                    </thead>
 170                                    <tbody>
 0171                                        @foreach (var file2Row in matchedRow.value.File2Data)
 172                                        {
 173                                            <tr onclick="selectPaymentRow(this)">
 0174                                                @foreach (var kvp in file2Row)
 175                                                {
 0176                                                    <td>@kvp.Value</td>
 177                                                }
 178                                            </tr>
 179                                        }
 180                                    </tbody>
 181                                </table>
 182                            }
 183                            else
 184                            {
 185                                <span class="empty-file2">Нет соответствий</span>
 186                            }
 187                        </td>
 188                    </tr>
 189                }
 190            </tbody>
 191        </table>
 192    </div>
 193</div>
 194
 195<script>
 196    function filterTables() {
 197        var showFilled = document.getElementById('showFilled').checked;
 198        var showEmpty = document.getElementById('showEmpty').checked;
 199
 200        var filledTables = document.querySelectorAll('.empty-file-data');
 201        filledTables.forEach(function (table) {
 202            table.style.display = showFilled ? '' : 'none';
 203        });
 204
 205        var emptyTables = document.querySelectorAll('.full-file-data');
 206        emptyTables.forEach(function (table) {
 207            table.style.display = showEmpty ? '' : 'none';
 208        });
 209    }
 210</script>
 211
 212
 213<script>
 214    // Скрываем спиннер после полной загрузки страницы
 215    window.addEventListener('load', function () {
 216        const loader = document.getElementById('loadingSpinner');
 217        loader.style.display = 'none'; // Скрываем спиннер
 218    });
 219
 220    function showToast(message) {
 221        const toastBody = document.getElementById('flashMessageText');
 222        toastBody.textContent = message;
 223        const toast = new bootstrap.Toast(document.getElementById('flashMessage'));
 224        toast.show();
 225    }
 226</script>
 227
 228<script>
 229    function gatherMatchedRowsData() {
 230        const matchedRows = [];
 231
 232        // Перебираем строки таблицы
 233        document.querySelectorAll("tbody tr").forEach(row => {
 234            const tables = row.querySelectorAll("td table");
 235            let file1Data = null; // Изначально задаём null
 236            let file2Data = null; // Изначально задаём null
 237
 238            const file1Table = tables[0]; // Первая таблица (File1Data), если она есть
 239            const file2Table = tables[1]; // Вторая таблица (File2Data), если она есть
 240
 241            // Проверяем на наличие записей в File1Data, если таблица существует
 242            if (file1Table) {
 243                const noRecords = file1Table.querySelector("tbody tr td span")?.textContent.trim();
 244                if (noRecords === "Нет записей" || noRecords === "Нет соответствий") {
 245                    file1Data = null; // Если нет записей, оставляем null
 246                } else {
 247                    file1Data = {}; // Инициализируем объект
 248                    // Получаем данные для File1Data из первой таблицы
 249                    file1Table.querySelectorAll('tbody tr').forEach(subRow => {
 250                        const rowData = {};
 251                        subRow.querySelectorAll('td').forEach((td, index) => {
 252                            const key = file1Table.querySelectorAll('thead th')[index]?.textContent.trim();
 253                            if (key) {
 254                                rowData[key] = td.textContent.trim();
 255                            }
 256                        });
 257                        // Добавляем данные только если есть содержимое
 258                        if (Object.keys(rowData).length > 0) {
 259                            Object.assign(file1Data, rowData);
 260                        }
 261                    });
 262                }
 263            }
 264
 265            // Проверяем на наличие записей в File2Data, если таблица существует
 266            if (file2Table) {
 267                const noRecords = file2Table.querySelector("tbody tr td span")?.textContent.trim();
 268                if (noRecords === "Нет записей" || noRecords === "Нет соответствий") {
 269                    file2Data = null; // Если нет записей, оставляем null
 270                } else {
 271                    file2Data = []; // Инициализируем массив
 272                    // Получаем данные для File2Data из второй таблицы
 273                    file2Table.querySelectorAll('tbody tr').forEach(subRow => {
 274                        const rowData = {};
 275                        subRow.querySelectorAll('td').forEach((td, index) => {
 276                            const key = file2Table.querySelectorAll('thead th')[index]?.textContent.trim();
 277                            if (key) {
 278                                rowData[key] = td.textContent.trim();
 279                            }
 280                        });
 281                        // Добавляем данные в массив только если есть содержимое
 282                        if (Object.keys(rowData).length > 0) {
 283                            file2Data.push(rowData);
 284                        }
 285                    });
 286                }
 287            }
 288
 289            // Проверяем, чтобы не добавлять объект с двумя значениями null
 290            if (file1Data !== null || file2Data !== null) {
 291                matchedRows.push({
 292                    File1Data: file1Data,
 293                    File2Data: file2Data
 294                });
 295            }
 296        });
 297
 298        console.log(matchedRows.length);
 299        return matchedRows;
 300    }
 301
 302
 303    function submitMatchedRows() {
 304        const matchedRowsData = gatherMatchedRowsData(); // Собираем данные
 305        const loader = document.getElementById('loadingSpinner'); // Получаем элемент загрузки
 0306        const matchedResultsId = @Model.MatchedResultsId;
 307
 308        loader.style.display = 'block'; // Показываем анимацию загрузки
 309
 310        // Включаем MatchedResultsId в отправляемый объект
 311        const dataToSend = {
 312            matchedResultsId: matchedResultsId,
 313            matchedRows: matchedRowsData
 314        };
 315
 316        fetch("/MatchFileData/ProcessMatchedRows", {
 317            method: "POST",
 318            headers: {
 319                "Content-Type": "application/json"
 320            },
 321            body: JSON.stringify(dataToSend) // Отправляем данные в JSON формате
 322        })
 323            .then(response => {
 324                if (!response.ok) {
 325                    return response.json().then(error => { throw new Error(error.message); });
 326                }
 327                return response.json(); // Получаем ответ от сервера
 328            })
 329            .then(data => {
 330                loader.style.display = 'none'; // Скрываем анимацию загрузки
 331
 332                if (data.message) {
 333                    // Выводим сообщение от сервера
 334                    showToast(data.message);
 335                }
 336            })
 337            .catch(error => {
 338                loader.style.display = 'none'; // Скрываем анимацию загрузки
 339
 340                // Обработка ошибки
 341                console.error("Ошибка:", error);
 342                showToast("Произошла ошибка при отправке данных: " + error.message);
 343            });
 344    }
 345
 346
 347
 348</script>
 349
 350
 351<script>
 352    let selectedPaymentRow = null;
 353
 354    function selectPaymentRow(row) {
 355        if (selectedPaymentRow) {
 356            selectedPaymentRow.classList.remove('selected-row');
 357        }
 358        selectedPaymentRow = row;
 359        selectedPaymentRow.classList.add('selected-row');
 360        showToast("Выбрана строка из 'Платежи'. Теперь выберите строку в соответствующем блоке 'Заказы'.");
 361    }
 362
 363    function selectTargetRow(targetRow, index) {
 364        if (selectedPaymentRow) {
 365            const targetBlockId = `payment-block-${index}`;
 366            const targetCell = document.getElementById(targetBlockId);
 367
 368            if (targetCell.innerHTML.includes('Нет соответствий')) {
 369                targetCell.innerHTML = '';
 370
 371                const newTable = document.createElement('table');
 372                newTable.className = 'table table-sm';
 373                newTable.style.width = '100%';
 374                const thead = document.createElement('thead');
 375                const tbody = document.createElement('tbody');
 376                const originalTable = selectedPaymentRow.closest('table');
 377                const originalHeader = originalTable.querySelector('thead');
 378
 379                if (originalHeader) {
 380                    thead.innerHTML = originalHeader.innerHTML;
 381                }
 382
 383                newTable.appendChild(thead);
 384                tbody.appendChild(selectedPaymentRow);
 385                newTable.appendChild(tbody);
 386                targetCell.appendChild(newTable);
 387                const originalTableBody = originalTable.querySelector('tbody');
 388
 389                if (originalTableBody.children.length === 0) {
 390                    originalTable.innerHTML = '<span>Нет соответствий</span>';
 391                }
 392
 393            } else {
 394                const originalTable = selectedPaymentRow.closest('table');
 395                const originalTableBody = originalTable.querySelector('tbody');
 396                const targetTableBody = targetCell.querySelector('tbody');
 397                targetTableBody.appendChild(selectedPaymentRow);
 398
 399                if (originalTableBody.children.length === 0) {
 400                    originalTable.innerHTML = '<span>Нет соответствий</span>';
 401                }
 402            }
 403
 404            // Убираем класс выделенной строки
 405            selectedPaymentRow.classList.remove('selected-row');
 406            selectedPaymentRow = null;
 407
 408
 409            showToast("Строка успешно перемещена в соответствующий блок 'Платежи'.");
 410        } else {
 411            showToast("Сначала выберите строку из 'Платежи'.");
 412        }
 413    }
 414
 415
 416
 417</script>
 418
 419<script>
 420    let currentMatchIndex = -1; // Индекс текущего совпадения
 421    let matches = []; // Массив для хранения совпадений
 422
 423    function searchInTables() {
 424        const input = document.getElementById('searchInput');
 425        const filter = input.value.toLowerCase();
 426        const tables = document.querySelectorAll('.table-sm');
 427        matches = []; // Сброс массива совпадений
 428
 429        // Проверка, если поле ввода пустое
 430        if (filter === "") {
 431            // Если поле пустое, сбрасываем все выделения
 432            const cells = document.querySelectorAll('td');
 433            cells.forEach(cell => {
 434                cell.style.backgroundColor = ''; // Сброс выделения
 435                cell.style.border = ''; // Сброс границ
 436            });
 437
 438            // Сброс окраски строк
 439            const rows = document.querySelectorAll('tbody tr');
 440            rows.forEach(row => {
 441                row.style.backgroundColor = ''; // Сброс цвета строки
 442            });
 443
 444            // Сброс количества найденных записей
 445            document.getElementById('matchCount').innerText = "Найдено: 0";
 446            document.getElementById('matchCount').style.display = 'none'; // Скрытие счетчика
 447
 448            currentMatchIndex = -1; // Сброс индекса текущего совпадения
 449            return; // Выход из функции
 450        }
 451
 452        tables.forEach(table => {
 453            const rows = table.querySelectorAll('tbody tr');
 454
 455            rows.forEach((row) => {
 456                let rowContainsText = false; // Флаг для проверки наличия текста в строке
 457                const cells = row.querySelectorAll('td');
 458
 459                cells.forEach(cell => {
 460                    if (cell.textContent.toLowerCase().includes(filter)) {
 461                        rowContainsText = true; // Если найдено совпадение
 462                        matches.push({ cell, index: currentMatchIndex }); // Добавление совпадения в массив
 463                        cell.style.backgroundColor = '#ffeb3b'; // Выделение совпадений желтым цветом
 464                        row.style.backgroundColor = '#ff5722'; // Выделение всей строки цветом #ff5722
 465                    } else {
 466                        cell.style.backgroundColor = ''; // Сброс выделения
 467                    }
 468                });
 469
 470                // Сброс цвета строки, если нет совпадений
 471                if (!rowContainsText) {
 472                    row.style.backgroundColor = ''; // Сброс цвета строки
 473                }
 474            });
 475        });
 476
 477        // Обновление количества найденных записей
 478        document.getElementById('matchCount').innerText = `Найдено: ${matches.length}`;
 479
 480        // Показ счетчика, если есть совпадения
 481        document.getElementById('matchCount').style.display = matches.length > 0 ? 'inline' : 'none';
 482
 483        // Сброс текущего индекса при новом поиске
 484        currentMatchIndex = -1;
 485    }
 486
 487    function highlightMatch(index) {
 488        if (matches.length === 0) return;
 489        // Сброс фона и границ для всех совпадений
 490        matches.forEach(match => {
 491            match.cell.style.backgroundColor = '#ffeb3b'; // Сохранение желтого фона
 492            match.cell.style.border = ''; // Сброс границ
 493        });
 494
 495        // Подсветка текущего совпадения
 496        const currentMatch = matches[index];
 497        if (currentMatch) {
 498            currentMatch.cell.style.border = '3px solid black'; // Толстая черная рамка для текущего совпадения
 499            currentMatch.cell.style.borderRadius = '5px'; // Скругление углов рамки
 500            currentMatch.cell.scrollIntoView({ behavior: "smooth", block: "center" });
 501        }
 502    }
 503
 504    function previousMatch() {
 505        if (matches.length === 0) return;
 506        currentMatchIndex = (currentMatchIndex <= 0) ? matches.length - 1 : currentMatchIndex - 1;
 507        highlightMatch(currentMatchIndex);
 508    }
 509
 510    function nextMatch() {
 511        if (matches.length === 0) return;
 512        currentMatchIndex = (currentMatchIndex >= matches.length - 1) ? 0 : currentMatchIndex + 1;
 513        highlightMatch(currentMatchIndex);
 514    }
 515</script>

Methods/Properties

ExecuteAsync()