< Summary

Information
Class: AspNetCoreGeneratedDocument.Areas_Studio2_Views_OrderHistory_Index
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Views/OrderHistory/Index.cshtml
Line coverage
0%
Covered lines: 0
Uncovered lines: 10
Coverable lines: 10
Total lines: 406
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 8
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%7280%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Views/OrderHistory/Index.cshtml

#LineLine coverage
 1@model List<OzonDomains.Models.OrderHistory>
 2
 3@{
 04ViewData["Title"] = "История изменений заказов";
 05Layout = "~/Views/Shared/_Layout.cshtml";
 6}
 7
 8<style>
 9    .card {
 10        border: none;
 11        box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
 12        border-radius: 0.5rem;
 13        margin-bottom: 1.5rem;
 14    }
 15
 16    .card-header {
 17        background-color: #f8f9fa;
 18        border-bottom: 1px solid #e9ecef;
 19        padding: 1.25rem 1.5rem;
 20        display: flex;
 21        justify-content: space-between;
 22        align-items: center;
 23    }
 24
 25    .card-body {
 26        padding: 0;
 27    }
 28
 29    .table th {
 30        border-top: none;
 31        font-weight: 600;
 32        color: #495057;
 33        font-size: 0.9rem;
 34        padding: 1rem;
 35        background-color: #f8f9fa;
 36    }
 37
 38    .table td {
 39        padding: 1rem;
 40        vertical-align: middle;
 41        font-size: 0.9rem;
 42    }
 43
 44    .btn-primary {
 45        background-color: #007bff;
 46        border-color: #007bff;
 47        padding: 0.5rem 1rem;
 48        font-size: 0.9rem;
 49    }
 50
 51    .btn-outline-secondary {
 52        border-color: #6c757d;
 53        color: #6c757d;
 54        padding: 0.5rem 1rem;
 55        font-size: 0.9rem;
 56    }
 57
 58    .table-hover tbody tr:hover {
 59        background-color: #f8f9fa;
 60    }
 61
 62    .text-muted-small {
 63        font-size: 0.8rem;
 64        color: #6c757d;
 65    }
 66
 67    .action-buttons {
 68        display: flex;
 69        gap: 0.5rem;
 70        align-items: center;
 71    }
 72
 73    .spinner-border-sm {
 74        width: 1rem;
 75        height: 1rem;
 76    }
 77
 78    .status-indicator {
 79        display: inline-flex;
 80        align-items: center;
 81        gap: 0.5rem;
 82        font-size: 0.8rem;
 83    }
 84
 85    .status-connected {
 86        color: #28a745;
 87    }
 88
 89    .status-disconnected {
 90        color: #dc3545;
 91    }
 92
 93    .status-connecting {
 94        color: #ffc107;
 95    }
 96
 97    .history-container {
 98        max-height: 600px;
 99        overflow-y: auto;
 100    }
 101</style>
 102
 103<link rel="stylesheet" href="~/css/Studio2/shared/legacy-directory.css" asp-append-version="true"/>
 104
 105<div class="container py-3 ld-page">
 106    <div class="row">
 107        <div class="col-12">
 108            <div class="app-section-hero app-section-hero--import d-flex flex-wrap justify-content-between align-items-s
 109                <div>
 110                    <h1><i class="fas fa-clock-rotate-left me-2 text-primary"></i>История изменений заказов</h1>
 111                    <div class="subtitle">Журнал изменений заказов в реальном времени</div>
 112                    <div class="subtitle-help">Наблюдайте за полями, авторами и временем изменений; обновление не переза
 113                    <div class="app-section-hero__quick-links"><a asp-area="Studio2" asp-controller="Orders" asp-action=
 114                </div>
 115                <div id="connectionStatus" class="status-indicator status-connecting"><i class="fas fa-circle"></i><span
 116            </div>
 117            <div class="card ld-card">
 118                <div class="card-header">
 119                    <span class="fw-semibold">Последние изменения</span>
 120                    <div class="action-buttons">
 121                        <button id="refreshBtn" class="btn btn-primary">
 122                            <span id="buttonText">Обновить</span>
 123                            <span id="spinner" class="spinner-border spinner-border-sm visually-hidden" role="status"></
 124                        </button>
 125                    </div>
 126                </div>
 127                <div class="card-body">
 128                    <div class="history-container">
 129                        <div class="table-responsive">
 130                            <table class="table table-hover">
 131                                <thead>
 132                                <tr>
 133                                    <th width="15%">Время изменения</th>
 134                                    <th width="10%">ID заказа</th>
 135                                    <th width="15%">Измененное поле</th>
 136                                    <th width="20%">Старое значение</th>
 137                                    <th width="20%">Новое значение</th>
 138                                    <th width="15%">Автор изменения</th>
 139                                </tr>
 140                                </thead>
 141                                <tbody id="historyTableBody">
 0142                                @foreach (var item in Model)
 143                                {
 144                                <tr>
 145                                    <td>
 0146                                        <span class="text-muted-small">@item.FormattedChangedAt</span>
 147                                    </td>
 148                                    <td>
 0149                                        <strong>@item.OrderId</strong>
 150                                    </td>
 151                                    <td>
 0152                                        <span class="text-muted-small">@item.ColumnDisplayName</span>
 153                                    </td>
 154                                    <td>
 0155                                        <span class="text-muted">@item.FormattedOldValue</span>
 156                                    </td>
 157                                    <td>
 0158                                        <span class="text-success">@item.FormattedNewValue</span>
 159                                    </td>
 160                                    <td>
 0161                                        <span class="text-muted-small">@item.ChangedBy</span>
 162                                    </td>
 163                                </tr>
 164                                }
 165                                </tbody>
 166                            </table>
 167                        </div>
 168                    </div>
 169
 170                    <!-- Информация о записях -->
 171                    <div class="text-center text-muted mt-3 mx-3">
 172                        <small>
 0173                            Показано <span id="recordCount">@Model.Count</span> последних изменений
 174                            <span id="lastUpdate" class="ms-2"></span>
 175                        </small>
 176                    </div>
 177                </div>
 178            </div>
 179        </div>
 180    </div>
 181</div>
 182
 183@section Scripts {
 184<script src="~/lib/microsoft-signalr/signalr.min.js" asp-append-version="true"></script>
 185<script>
 186    document.addEventListener('DOMContentLoaded', () => {
 187        // Элементы интерфейса
 188        const connectionStatus = document.getElementById('connectionStatus');
 189        const refreshBtn = document.getElementById('refreshBtn');
 190        const spinner = document.getElementById('spinner');
 191        const buttonText = document.getElementById('buttonText');
 192        const recordCount = document.getElementById('recordCount');
 193        const lastUpdate = document.getElementById('lastUpdate');
 194
 195        // Функция обновления статуса подключения
 196        function updateConnectionStatus(status, message) {
 197            const statusIcon = connectionStatus.querySelector('i');
 198            const statusText = connectionStatus.querySelector('span:last-child');
 199
 200            connectionStatus.className = `status-indicator status-${status}`;
 201            statusText.textContent = message;
 202
 203            switch(status) {
 204                case 'connected':
 205                    statusIcon.className = 'fas fa-circle';
 206                    break;
 207                case 'disconnected':
 208                    statusIcon.className = 'fas fa-circle';
 209                    break;
 210                case 'connecting':
 211                    statusIcon.className = 'fas fa-circle';
 212                    break;
 213                case 'reconnecting':
 214                    statusIcon.className = 'fas fa-sync-alt fa-spin';
 215                    break;
 216            }
 217        }
 218
 219        // Функция обновления времени последнего обновления
 220        function updateLastUpdateTime() {
 221            const now = new Date();
 222            lastUpdate.textContent = `Последнее обновление: ${now.toLocaleTimeString('ru-RU')}`;
 223        }
 224
 225        // Функция для форматирования числовых значений
 226        function formatNumericValue(value) {
 227            if (!value) return '';
 228
 229            const number = parseFloat(value);
 230            if (!isNaN(number)) {
 231                return number.toFixed(2).replace(/\.?0+$/, '');
 232            }
 233
 234            return value;
 235        }
 236
 237        // Функция для обновления таблицы
 238        function updateTable() {
 239            refreshBtn.disabled = true;
 240            spinner.classList.remove('visually-hidden');
 241            buttonText.textContent = 'Обновление...';
 242
 243            connection.invoke('RequestRecentChanges')
 244                .catch(err => {
 245                    console.error('Ошибка запроса обновлений:', err);
 246                    restoreButtonState();
 247                });
 248        }
 249
 250        function restoreButtonState() {
 251            refreshBtn.disabled = false;
 252            spinner.classList.add('visually-hidden');
 253            buttonText.textContent = 'Обновить';
 254        }
 255
 256        // SignalR подключение
 257        const connection = new signalR.HubConnectionBuilder()
 258            .withUrl('/orderHistoryHub')
 259            .withAutomaticReconnect({
 260                nextRetryDelayInMilliseconds: retryContext => {
 261                    if (retryContext.elapsedMilliseconds < 60000) {
 262                        return 2000;
 263                    } else {
 264                        return 5000;
 265                    }
 266                }
 267            })
 268            .build();
 269
 270        // Обработчики событий подключения
 271        connection.onreconnecting(() => {
 272            updateConnectionStatus('reconnecting', 'Переподключение...');
 273        });
 274
 275        connection.onreconnected(() => {
 276            updateConnectionStatus('connected', 'Подключено');
 277            updateTable();
 278        });
 279
 280        connection.onclose(() => {
 281            updateConnectionStatus('disconnected', 'Отключено');
 282        });
 283
 284        // Обработчик получения изменений
 285        connection.on("ReceiveChange", function (change) {
 286            console.log('Получено новое изменение:', change);
 287            addChangeToTable(change);
 288            updateLastUpdateTime();
 289        });
 290
 291        // Обработчик получения списка изменений
 292        connection.on("ReceiveRecentChanges", function (changes) {
 293            console.log('Получены обновления:', changes.length, 'записей');
 294            updateTableWithData(changes);
 295            restoreButtonState();
 296            updateLastUpdateTime();
 297        });
 298
 299        // Функция добавления одной записи в таблицу
 300        function addChangeToTable(change) {
 301            const tbody = document.getElementById('historyTableBody');
 302            if (!tbody) return;
 303
 304            const row = document.createElement('tr');
 305
 306            const changedAt = new Date(change.changedAt);
 307            const formattedDate = changedAt.toLocaleString('ru-RU', {
 308                day: '2-digit',
 309                month: '2-digit',
 310                year: 'numeric',
 311                hour: '2-digit',
 312                minute: '2-digit',
 313                second: '2-digit'
 314            });
 315
 316            const formattedOldValue = formatNumericValue(change.oldValue);
 317            const formattedNewValue = formatNumericValue(change.newValue);
 318
 319            row.innerHTML = `
 320                <td><span class="text-muted-small">${formattedDate}</span></td>
 321                <td><strong>${change.orderId}</strong></td>
 322                <td><span class="text-muted-small">${change.columnDisplayName || ''}</span></td>
 323                <td><span class="text-muted">${formattedOldValue}</span></td>
 324                <td><span class="text-success">${formattedNewValue}</span></td>
 325                <td><span class="text-muted-small">${change.changedBy || ''}</span></td>
 326            `;
 327
 328            tbody.insertBefore(row, tbody.firstChild);
 329
 330            // Ограничиваем количество отображаемых записей
 331            if (tbody.children.length > 100) {
 332                tbody.removeChild(tbody.lastChild);
 333            }
 334
 335            // Обновляем счетчик записей
 336            recordCount.textContent = tbody.children.length;
 337        }
 338
 339        // Функция полного обновления таблицы
 340        function updateTableWithData(changes) {
 341            const tbody = document.getElementById('historyTableBody');
 342            if (!tbody) return;
 343
 344            tbody.innerHTML = '';
 345
 346            const sortedChanges = changes.sort((a, b) =>
 347                new Date(b.changedAt) - new Date(a.changedAt)
 348            );
 349
 350            sortedChanges.forEach(change => {
 351                const row = document.createElement('tr');
 352
 353                const changedAt = new Date(change.changedAt);
 354                const formattedDate = changedAt.toLocaleString('ru-RU', {
 355                    day: '2-digit',
 356                    month: '2-digit',
 357                    year: 'numeric',
 358                    hour: '2-digit',
 359                    minute: '2-digit',
 360                    second: '2-digit'
 361                });
 362
 363                const formattedOldValue = formatNumericValue(change.oldValue);
 364                const formattedNewValue = formatNumericValue(change.newValue);
 365
 366                row.innerHTML = `
 367                    <td><span class="text-muted-small">${formattedDate}</span></td>
 368                    <td><strong>${change.orderId}</strong></td>
 369                    <td><span class="text-muted-small">${change.columnDisplayName || ''}</span></td>
 370                    <td><span class="text-muted">${formattedOldValue}</span></td>
 371                    <td><span class="text-success">${formattedNewValue}</span></td>
 372                    <td><span class="text-muted-small">${change.changedBy || ''}</span></td>
 373                `;
 374
 375                tbody.appendChild(row);
 376            });
 377
 378            recordCount.textContent = changes.length;
 379        }
 380
 381        // Обработчик кнопки обновления
 382        refreshBtn.addEventListener('click', updateTable);
 383
 384        // Запуск соединения
 385        connection.start()
 386            .then(() => {
 387                updateConnectionStatus('connected', 'Подключено');
 388                console.log('SignalR connected to orderHistoryHub');
 389                updateTable();
 390                updateLastUpdateTime();
 391            })
 392            .catch(err => {
 393                updateConnectionStatus('disconnected', 'Ошибка подключения');
 394                console.error('SignalR connection error:', err.toString());
 395                restoreButtonState();
 396            });
 397
 398        // Автообновление каждые 10 секунд
 399        setInterval(() => {
 400            if (connection.state === signalR.HubConnectionState.Connected) {
 401                updateTable();
 402            }
 403        }, 10000);
 404    });
 405</script>
 406}

Methods/Properties

ExecuteAsync()