< Summary

Information
Class: AspNetCoreGeneratedDocument.Areas_Studio2_Views_Shared__Layout
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Views/Shared/_Layout.cshtml
Line coverage
0%
Covered lines: 0
Uncovered lines: 6
Coverable lines: 6
Total lines: 1002
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 110
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%702260%
<ExecuteAsync()0%7140840%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Views/Shared/_Layout.cshtml

#LineLine coverage
 1<!DOCTYPE html>
 2<html lang="en">
 3<head>
 4    <meta charset="utf-8"/>
 5    <partial name="_ConsoleGuard" />
 6    <meta name="studio2-instance" content="@OzonOrdersWeb.Observability.AppInstanceInfo.InstanceId"/>
 7    @* Наблюдатель доступности сервера. Грузится ДО телеметрии: так он захватывает нативный fetch
 8         для health-проб, а телеметрия ложится поверх и продолжает видеть обычные запросы. *@
 9    <script src="~/js/layout/server-availability.js" asp-append-version="true"></script>
 10    <script src="~/js/observability/client-telemetry.js" asp-append-version="true"></script>
 11    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
 012    <title>@ViewData["Title"] - OzonOrdersWeb</title>
 13    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css"/>
 14    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true"/>
 15    <link rel="stylesheet" href="~/css/app-toast.css" asp-append-version="true"/>
 16    <link rel="stylesheet" href="~/css/system-notification-modal.css" asp-append-version="true"/>
 17    <link rel="stylesheet" href="~/css/server-availability.css" asp-append-version="true"/>
 18    <link rel="stylesheet" href="~/css/pagination.css" asp-append-version="true"/>
 19    <link rel="stylesheet" href="~/css/order-number-emphasis.css" asp-append-version="true"/>
 20    <!-- Bootstrap Icons -->
 21    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css"/>
 22
 23    <script src="~/lib/microsoft-signalr/signalr.min.js" asp-append-version="true"></script>
 24    <script src="~/js/layout/signalr-logging.js" asp-append-version="true"></script>
 25    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
 26    <script src="~/js/app-toast.js" asp-append-version="true" defer></script>
 27
 28    <style>
 29        scrollbar-width: thin
 30
 31        ;
 32        scrollbar-color:
 33        rgba
 34
 35        (
 36        0
 37        ,
 38        0
 39        ,
 40        0
 41        ,
 42        0.5
 43        )
 44        transparent
 45
 46        ;
 47
 48        .pagination-wrapper {
 49            display: flex;
 50            align-items: center;
 51        }
 52
 53        /* Для веб-браузеров на основе WebKit (Chrome, Safari) */
 54        ::-webkit-scrollbar {
 55            width: 7px; /* Ширина вертикальной полосы прокрутки */
 56            height: 7px; /* Высота горизонтальной полосы прокрутки */
 57        }
 58
 59        ::-webkit-scrollbar-track {
 60            background: transparent; /* Фон полосы прокрутки */
 61        }
 62
 63        ::-webkit-scrollbar-thumb {
 64            background: rgba(0, 0, 0, 0.3); /* Цвет ползунка прокрутки */
 65        }
 66
 67        .btn-link svg {
 68            transition: transform 0.1s ease; /* Плавное увеличение */
 69        }
 70
 71        .btn-link:hover svg {
 72            transform: scale(1.2); /* Увеличиваем SVG на 20% */
 73        }
 74
 75        .chat-widget-new {
 76            position: fixed;
 77            bottom: 20px;
 78            right: 20px;
 79            z-index: 2147483647;
 80        }
 81
 82        .chat-toggle {
 83            width: 50px;
 84            height: 50px;
 85            border-radius: 50%;
 86            font-size: 28px;
 87            display: flex;
 88            align-items: center;
 89            justify-content: center;
 90            box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
 91            transition: opacity 0.3s;
 92        }
 93
 94        .chat-card {
 95            width: 600px;
 96            height: 600px;
 97            display: flex;
 98            flex-direction: column;
 99        }
 100
 101        .card-header {
 102            flex-shrink: 0;
 103        }
 104
 105        .chat-body {
 106            flex-grow: 1;
 107            overflow-y: auto;
 108            padding: 8px 10px;
 109            max-height: none;
 110            word-wrap: break-word;
 111        }
 112
 113        #chatBadge {
 114            position: absolute;
 115            top: 10px;
 116            left: 10px;
 117            width: 10px;
 118            height: 10px;
 119            background-color: #EE204D;
 120            border-radius: 50%;
 121        }
 122
 123        .chat-message {
 124            padding: 7px 4px;
 125            border-bottom: 1px solid rgba(0, 0, 0, 0.06);
 126        }
 127
 128        .chat-message:last-child {
 129            border-bottom: none;
 130        }
 131
 132        .chat-message-head {
 133            display: flex;
 134            align-items: center;
 135            gap: 6px;
 136            margin-bottom: 3px;
 137        }
 138
 139        .chat-message-icon {
 140            font-size: 12px;
 141            color: #6c757d;
 142            flex-shrink: 0;
 143        }
 144
 145        .chat-message-source {
 146            font-size: 12px;
 147            font-weight: 600;
 148            display: flex;
 149            align-items: center;
 150            gap: 4px;
 151        }
 152
 153        .chat-message-source-dot {
 154            width: 6px;
 155            height: 6px;
 156            border-radius: 50%;
 157            flex-shrink: 0;
 158        }
 159
 160        .chat-message-time {
 161            font-size: 11px;
 162            color: #6c757d;
 163            margin-left: auto;
 164            white-space: nowrap;
 165        }
 166
 167        .chat-message-content {
 168            font-size: 13px;
 169            line-height: 1.4;
 170            color: #212529;
 171            word-break: break-word;
 172        }
 173    </style>
 174</head>
 175<body>
 176<header>
 177    <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
 178        <div class="container-fluid">
 179            <a class="navbar-brand" href="#">Studio2</a>
 180            <!-- <a class="navbar-brand" asp-area="Studio2" asp-controller="Home" asp-action="Index">Studio2</a>-->
 181            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse"
 182                    aria-controls="navbarSupportedContent"
 183                    aria-expanded="false" aria-label="Toggle navigation">
 184                <span class="navbar-toggler-icon"></span>
 185            </button>
 186
 187
 188            <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
 189                <!-- Левый блок -->
 190                <ul class="navbar-nav flex-grow-1 d-flex justify-content-start">
 191
 0192                    @if (User.IsInRole("Admin"))
 193                    {
 194                        <li class="nav-item">
 195                            <div class="btn-group">
 196                                <button type="button" class="btn btn-defult">
 197                                    <a class="nav-link text-dark" asp-area="Studio2" asp-controller="" asp-action="">Адм
 198                                </button>
 199                                <button type="button" class="btn btn-defult dropdown-toggle dropdown-toggle-split"
 200                                        data-bs-toggle="dropdown" aria-expanded="false">
 201                                    <span class="visually-hidden">Toggle Dropdown</span>
 202                                </button>
 203                                <ul class="dropdown-menu">
 204                                    <li>
 205                                        <a class="dropdown-item fw-semibold" asp-area="Admin" asp-controller="Home" asp-
 206                                            <i class="fas fa-user-shield text-primary me-2"></i>
 207                                            Центр администрирования
 208                                        </a>
 209                                    </li>
 210                                    <li><hr class="dropdown-divider"></li>
 211                                    <li><a class="dropdown-item" asp-area="Studio2" asp-controller="OzonClients"
 212                                           asp-action="Index">Клиенты</a></li>
 213                                    <li><a class="dropdown-item" asp-area="Studio2" asp-controller="Job"
 214                                           asp-action="Index">Автоматическая подгрузка заказов</a></li>
 215                                    <li><a class="dropdown-item" asp-area="Studio2" asp-controller="OrderReturns"
 216                                           asp-action="Index">Возвраты</a></li>
 217                                    <li><a class="dropdown-item" asp-area="Studio2" asp-controller="Orders"
 218                                           asp-action="Returns">Журнал возвратов</a></li>
 219                                    <li><a class="dropdown-item" asp-area="Studio2" asp-controller="Producers"
 220                                           asp-action="Index">Производители ET</a></li>
 221                                    <li><a class="dropdown-item" asp-area="Studio2" asp-controller="Manufacturers"
 222                                           asp-action="Index">Производители (Ozon код)</a></li>
 223                                    <li><a class="dropdown-item" asp-area="Studio2" asp-controller="Supplier"
 224                                           asp-action="Index">Поставщики</a></li>
 225                                    <li><a class="dropdown-item" asp-area="Studio2" asp-controller="AppStatus"
 226                                           asp-action="Index">Статусы</a></li>
 227                                    <li><a class="dropdown-item" asp-area="Studio2" asp-controller="ClientStatus"
 228                                           asp-action="Index">Статусы клиентов</a></li>
 229                                    <li><a class="dropdown-item" asp-area="Studio2" asp-controller="SupplierStatus"
 230                                           asp-action="Index">Статусы поставщиков</a></li>
 231                                    <li><a class="dropdown-item" asp-area="Studio2" asp-controller="ExpenseType"
 232                                           asp-action="Index">Типы расходов</a></li>
 233                                    <li><a class="dropdown-item" asp-area="Studio2" asp-controller="ColumnMappings"
 234                                           asp-action="Index">Сопоставления</a></li>
 235                                                                             <li>
 236                                        <a class="dropdown-item" asp-area="Studio2" asp-controller="OzonWebhookLog"
 237                                           asp-action="Index">
 238                                            <i class="fas fa-bell text-primary me-2"></i>
 239                                            Push-уведомления Ozon
 240                                        </a>
 241                                    </li>
 242                                    <li><a class="dropdown-item" asp-area="Studio2" asp-controller="Config"
 243                                           asp-action="Login">
 244                                            <i class="fas fa-cog text-primary me-2"></i>
 245                                            Конфигурация
 246                                        </a></li>
 247                                    <li><a class="dropdown-item" asp-area="Studio2" asp-controller="Database"
 248                                           asp-action="Login">
 249                                            <i class="fas fa-database text-primary me-2"></i>
 250                                            База данных
 251                                        </a></li>
 252                                    <li><a class="dropdown-item" asp-area="Studio2" asp-controller="EmailRoutingRules"
 253                                           asp-action="Index">
 254                                            <i class="fas fa-envelope text-primary me-2"></i>
 255                                            Импорт из почты: правила
 256                                        </a></li>
 257                                    <li><a class="dropdown-item" asp-area="Studio2" asp-controller="EmailRoutingResults"
 258                                           asp-action="Index">
 259                                            <i class="fas fa-folder-open text-primary me-2"></i>
 260                                            Импорт заказов из почты
 261                                        </a></li>
 262                                    <li><a class="dropdown-item" asp-area="Studio2" asp-controller="TransactionTimeTrack
 263                                           asp-action="Index">
 264                                            <i class="fas fa-stopwatch text-primary me-2"></i>
 265                                            Трекинг времени транзакций
 266                                        </a></li>
 267                                    <li><a class="dropdown-item" href="/Hangfire" target="_blank">Задачи</a></li>
 268                                </ul>
 269                            </div>
 270                        </li>
 271                    }
 272                </ul>
 273
 274                <!-- Центральный блок -->
 275                <ul class="navbar-nav flex-grow-1 d-flex justify-content-center">
 276                    <!-- Здесь можно добавить другие элементы, которые должны располагаться по центру -->
 277
 278                    <li class="nav-item" style="display: flex; justify-content: space-between; align-items: center;">
 279                        <div style="display: flex; align-items: center;">
 280                            <a asp-area="Studio2" asp-controller="Orders" asp-action="Index" title="Новая версия"
 281                               class="btn-link">
 282                                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor"
 283                                     class="bi bi-table" viewBox="0 0 16 16">
 284                                    <path
 285                                        d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm15 2h-4
 286                                </svg>
 287                            </a>
 0288                            @if (User.IsInRole("Admin"))
 289                            {
 290                            <p>&nbsp;&nbsp;</p>
 291                            <a asp-area="Studio2" asp-controller="Orders" asp-action="Returns" title="Журнал возвратов"
 292                               class="btn-link">
 293                                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor"
 294                                     class="bi bi-arrow-return-left" viewBox="0 0 16 16">
 295                                    <path fill-rule="evenodd" d="M14.5 1a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0v-4a.5.5 0 0 1 
 296                                    <path fill-rule="evenodd" d="M6.854 4.146a.5.5 0 0 1 0 .708L4.207 7.5H13a2 2 0 0 1 2
 297                                </svg>
 298                            </a>
 299                            <p>&nbsp;&nbsp;</p>
 300                            <a asp-area="Studio2" asp-controller="Transaction" asp-action="Index"
 301                               title="Журнал документов" class="btn-link">
 302                                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor"
 303                                     class="bi bi-book" viewBox="0 0 16 16">
 304                                    <path
 305                                        d="M1 2.828c.885-.37 2.154-.769 3.388-.893 1.33-.134 2.458.063 3.112.752v9.746c-
 306                                </svg>
 307                            </a>
 308                            <p>&nbsp;&nbsp;</p>
 309                            }
 310                        </div>
 311
 0312                        @if (User.IsInRole("Admin"))
 313                        {
 314                            <a asp-area="Studio2" asp-controller="Orders" asp-action="Upload" title="Загрузить заказы"
 315                               class="btn-link">
 316                                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor"
 317                                     class="bi bi-download" viewBox="0 0 16 16">
 318                                    <path
 319                                        d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 
 320                                    <path
 321                                        d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 
 322                                </svg>
 323                            </a>
 324                            <p>&nbsp;&nbsp;</p>
 325                            <a asp-area="Studio2" asp-controller="Orders" asp-action="UpdateV2" title="Обновить таблицу"
 326                               class="btn-link">
 327                                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor"
 328                                     class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
 329                                    <path fill-rule="evenodd"
 330                                          d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2z"/>
 331                                    <path
 332                                        d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658
 333                                </svg>
 334                            </a>
 335                            <p>&nbsp;&nbsp;</p>
 336                            <a asp-area="Studio2" asp-controller="Orders" asp-action="DeleteDuplicateOrders"
 337                               title="Удалить дубли" class="btn-link">
 338                                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor"
 339                                     class="bi bi-trash" viewBox="0 0 16 16">
 340                                    <path
 341                                        d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m2.5 0a.5.5 0 0 1 
 342                                    <path
 343                                        d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V
 344                                </svg>
 345                            </a>
 346                            <p>&nbsp;&nbsp;</p>
 347                            <a asp-area="Studio2" asp-controller="MatchFileData" asp-action="Index"
 348                               title="Сравнение файлов" class="btn-link">
 349                                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor"
 350                                     class="bi bi-files" viewBox="0 0 16 16">
 351                                    <path
 352                                        d="M13 0H6a2 2 0 0 0-2 2 2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h7a2 2 0 0 0 2-2 2 2 0 0 
 353                                </svg>
 354                            </a>
 355                            <p>&nbsp;&nbsp;</p>
 356                            <a asp-area="Studio2" asp-controller="Labels" asp-action="Index"
 357                               title="Просмотр этикеток" class="btn-link">
 358                                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor"
 359                                     class="bi bi-file-earmark-pdf-fill" viewBox="0 0 16 16">
 360                                    <path d="M5.523 12.424c.14-.082.293-.162.459-.238a7.7 7.7 0 0 1-.45.606c-.28.337-.49
 361                                </svg>
 362                            </a>
 363                            <p>&nbsp;&nbsp;</p>
 364                            <a href="https://www.dropbox.com/home/%D0%A2%D0%BE%D1%82%20%D0%95%D1%89%D0%B5/%D0%9F%D1%80%D
 365                               title="Dropbox" class="btn-link">
 366                                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor"
 367                                     class="bi bi-dropbox" viewBox="0 0 16 16">
 368                                    <path
 369                                        d="M8.01 4.555 4.005 7.11 8.01 9.665 4.005 12.22 0 9.651l4.005-2.555L0 4.555 4.0
 370                                </svg>
 371                            </a>
 372                            <p>&nbsp;&nbsp;</p>
 373                            <a asp-area="Studio2" asp-controller="OzonChat" asp-action="Index" title="Чаты Ozon" class="
 374                                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor"
 375                                     class="bi bi-chat-dots" viewBox="0 0 16 16">
 376                                    <path d="M5 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0m4 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0m3 1a1 1 0 
 377                                    <path d="m2.165 15.803.02-.004c1.83-.363 2.948-.842 3.468-1.105A9 9 0 0 0 8 15c4.418
 378                                </svg>
 379                                <span id="studio2-ozon-chat-badge" class="position-absolute top-0 start-100 translate-mi
 380                            </a>
 381                        }
 382                    </li>
 383
 384
 385                </ul>
 386
 387                <!-- Правый блок -->
 388                <ul class="navbar-nav flex-grow-1 d-flex justify-content-end">
 389                    <li class="nav-item">
 390                        <a asp-area="OneCFresh" asp-controller="Home" asp-action="Index"
 391                           class="nav-link px-2"
 392                           title="1С Fresh — справочники, документы, остатки"
 393                           style="color:#1a3a5c; font-weight:600; border-left:3px solid #1a3a5c;">
 394                            <i class="bi bi-database-fill me-1"></i>1С Fresh
 395                        </a>
 396                    </li>
 397                </ul>
 398            </div>
 399        </div>
 400
 401
 402        <partial name="_LoginPartial"/>
 403    </nav>
 404</header>
 405<div class="container-fluid">
 406    <main role="main" class="pb-3">
 407        <partial name="_AppToastNotifications" />
 0408        @RenderBody()
 409    </main>
 410</div>
 411
 412<!-- Chat widget -->
 413<div class="chat-widget-new" style="
 414    position: fixed;
 415    bottom: 20px;
 416    right: 20px;
 417    z-index: 2147483647;
 418    pointer-events: auto;">
 419
 420    <button id="chatToggle"
 421            class="btn btn-primary chat-toggle"
 422            type="button"
 423            data-bs-toggle="collapse"
 424            data-bs-target="#chatCollapse"
 425            aria-expanded="false">
 426        i
 427        <span id="chatBadge" class="d-none"></span>
 428    </button>
 429
 430    <div class="collapse" id="chatCollapse">
 431        <div class="card shadow-lg rounded-2xl chat-card d-flex flex-column">
 432            <div class="card-header d-flex justify-content-between align-items-center">
 433                <span>Процессы</span>
 434                <button id="chatClose" type="button" class="btn-close"></button>
 435            </div>
 436            <div id="chatBody" class="card-body chat-body flex-grow-1 px-2">
 437                <!-- Сообщения будут загружаться сюда -->
 438            </div>
 439            <div class="card-footer p-2"></div>
 440        </div>
 441    </div>
 442</div>
 443
 444
 445
 446<script src="~/lib/jquery/dist/jquery.min.js"></script>
 447<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
 448<script>
 449    // Глобальный объект для управления SignalR соединением
 450    window.ChatManager = window.ChatManager || {
 451        connection: null,
 452        isInitialized: false,
 453        reconnectAttempts: 0,
 454        maxReconnectAttempts: 10,
 455        reconnectDelay: 1000
 456    };
 457
 458    function escapeHtml(str) {
 459        return String(str)
 460            .replace(/&/g, '&amp;')
 461            .replace(/</g, '&lt;')
 462            .replace(/>/g, '&gt;')
 463            .replace(/"/g, '&quot;');
 464    }
 465
 466    // Рендер одного сообщения: поддерживает структурированный и plain форматы
 467    function renderChatMessage(entry) {
 468        const div = document.createElement('div');
 469        if (entry.source) {
 470            div.className = 'message chat-message';
 471            const color = escapeHtml(entry.color || '#6c757d');
 472            const icon = escapeHtml(entry.icon || 'fa-circle-info');
 473            div.innerHTML =
 474                '<div class="chat-message-head">' +
 475                  '<span class="chat-message-icon"><i class="fas ' + icon + '"></i></span>' +
 476                  '<span class="chat-message-source" style="color:' + color + '">' +
 477                    '<span class="chat-message-source-dot" style="background:' + color + '"></span>' +
 478                    escapeHtml(entry.source) +
 479                  '</span>' +
 480                  '<span class="chat-message-time">' + escapeHtml(entry.from) + '</span>' +
 481                '</div>' +
 482                '<div class="chat-message-content">' + escapeHtml(entry.text || '') + '</div>';
 483        } else {
 484            div.className = 'message mb-2';
 485            div.innerHTML = '<strong>' + escapeHtml(entry.from) + ':</strong> ' + escapeHtml(entry.text || '');
 486        }
 487        return div;
 488    }
 489
 490    // Восстановление сообщений из localStorage
 491    function loadMessages() {
 492        const stored = localStorage.getItem('chatMessages');
 493        if (!stored) return;
 494        const body = document.getElementById('chatBody');
 495        if (!body) return;
 496
 497        try {
 498            const msgs = JSON.parse(stored);
 499            body.innerHTML = '';
 500            msgs.forEach(m => body.appendChild(renderChatMessage(m)));
 501            body.scrollTop = body.scrollHeight;
 502        } catch (e) {
 503            console.error('Error loading messages:', e);
 504        }
 505    }
 506
 507    // Сохранение нового сообщения
 508    function saveMessage(from, rawMessage) {
 509        try {
 510            let msgs = JSON.parse(localStorage.getItem('chatMessages') || '[]');
 511
 512            // rawMessage может быть JSON {"source","color","icon","text"} или plain string
 513            let entry = { from };
 514            try {
 515                const parsed = JSON.parse(rawMessage);
 516                if (parsed && typeof parsed === 'object') {
 517                    entry = { ...entry, ...parsed };
 518                } else {
 519                    entry.text = rawMessage;
 520                }
 521            } catch {
 522                entry.text = rawMessage;
 523            }
 524
 525            const isDuplicate = msgs.some(m => m.from === entry.from && m.text === entry.text && m.source === entry.sour
 526            if (!isDuplicate) {
 527                if (msgs.length >= 50) msgs = msgs.slice(-49);
 528                msgs.push(entry);
 529                localStorage.setItem('chatMessages', JSON.stringify(msgs));
 530
 531                const chatBody = document.getElementById('chatBody');
 532                if (chatBody && document.getElementById('chatCollapse')?.classList.contains('show')) {
 533                    chatBody.appendChild(renderChatMessage(entry));
 534                    chatBody.scrollTop = chatBody.scrollHeight;
 535                }
 536            }
 537        } catch (e) {
 538            console.error('Error saving message:', e);
 539        }
 540    }
 541
 542    // Очистка старых сообщений (старше 30 дней)
 543    function cleanupOldMessages() {
 544        try {
 545            const stored = localStorage.getItem('chatMessages');
 546            if (!stored) return;
 547
 548            const msgs = JSON.parse(stored);
 549            const thirtyDaysAgo = new Date();
 550            thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
 551
 552            const filteredMsgs = msgs.filter(msg => {
 553                try {
 554                    // Пытаемся распарсить дату из формата "DD.MM.YYYY HH:mm"
 555                    const dateTimeParts = msg.from.split(' ');
 556                    if (dateTimeParts.length >= 1) {
 557                        const dateParts = dateTimeParts[0].split('.');
 558                        if (dateParts.length === 3) {
 559                            const msgDate = new Date(
 560                                parseInt(dateParts[2]), // год
 561                                parseInt(dateParts[1]) - 1, // месяц (0-11)
 562                                parseInt(dateParts[0]) // день
 563                            );
 564                            return msgDate >= thirtyDaysAgo;
 565                        }
 566                    }
 567                    // Если не удалось распарсить дату, сохраняем сообщение
 568                    return true;
 569                } catch {
 570                    // Сохраняем сообщения с некорректной датой
 571                    return true;
 572                }
 573            });
 574
 575            // Сохраняем только если что-то изменилось
 576            if (filteredMsgs.length !== msgs.length) {
 577                localStorage.setItem('chatMessages', JSON.stringify(filteredMsgs));
 578                console.log(`Очищено ${msgs.length - filteredMsgs.length} старых сообщений`);
 579            }
 580        } catch (e) {
 581            console.error('Error cleaning messages:', e);
 582        }
 583    }
 584
 585    // Инициализация SignalR соединения
 586    function initializeSignalR() {
 587        if (window.ChatManager.isInitialized && window.ChatManager.connection) {
 588            return window.ChatManager.connection;
 589        }
 590
 591        if (!window.signalR) {
 592            console.error('SignalR library not loaded');
 593            return null;
 594        }
 595
 596        const connection = new signalR.HubConnectionBuilder()
 597            .withUrl('/notificationHub')
 598            .configureLogging(window.Studio2SignalR ? window.Studio2SignalR.createLogger('chat') : signalR.LogLevel.Warn
 599            .withAutomaticReconnect({
 600                nextRetryDelayInMilliseconds: (retryContext) => {
 601                    if (retryContext.previousRetryCount < 3) {
 602                        return 1000;
 603                    } else if (retryContext.previousRetryCount < 6) {
 604                        return 5000;
 605                    } else {
 606                        return 10000;
 607                    }
 608                }
 609            })
 610            .build();
 611
 612        // Обработчик получения сообщений
 613        connection.on('PopupNotification', (message) => {
 614            try {
 615                const now = new Date();
 616                const formattedDateTime = now.toLocaleString('ru-RU', {
 617                    day: '2-digit',
 618                    month: '2-digit',
 619                    year: 'numeric',
 620                    hour: '2-digit',
 621                    minute: '2-digit'
 622                }).replace(',', '');
 623
 624                saveMessage(formattedDateTime, message);
 625
 626                const chatCollapse = document.getElementById('chatCollapse');
 627                const chatBadge = document.getElementById('chatBadge');
 628
 629                if (chatCollapse && !chatCollapse.classList.contains('show')) {
 630                    if (chatBadge) chatBadge.classList.remove('d-none');
 631                    localStorage.setItem('chatViewed', 'false');
 632                }
 633
 634                // Всплывающее уведомление в правом верхнем углу
 635                let container = document.getElementById('server-notification-container');
 636                if (!container) {
 637                    container = document.createElement('div');
 638                    container.id = 'server-notification-container';
 639                    container.style.cssText = 'position:fixed;top:1rem;right:1rem;z-index:1060;display:flex;flex-directi
 640                    document.body.appendChild(container);
 641                }
 642
 643                // Текст для всплывашки: если JSON — берём поле text
 644                let toastText = message;
 645                try {
 646                    const p = JSON.parse(message);
 647                    if (p && p.text) toastText = (p.source ? p.source + ': ' : '') + p.text;
 648                } catch { /* plain string */ }
 649
 650                const alert = document.createElement('div');
 651                alert.className = 'alert alert-info alert-dismissible fade show shadow-sm';
 652                alert.style.cssText = 'min-width:260px;max-width:360px;';
 653                alert.innerHTML =
 654                    '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>' +
 655                    '<div style="font-size:0.85rem;">' + escapeHtml(toastText) + '</div>';
 656                container.appendChild(alert);
 657
 658                setTimeout(() => {
 659                    if (alert.parentNode) {
 660                        alert.classList.remove('show');
 661                        alert.classList.add('hide');
 662                        setTimeout(() => alert.remove(), 200);
 663                    }
 664                }, 10000);
 665            } catch (e) {
 666                console.error('Error handling notification:', e);
 667            }
 668        });
 669
 670        // Обработчик системных уведомлений (только для модального окна)
 671        connection.on('SystemNotification', (message) => {
 672            try {
 673                const now = new Date();
 674                const formattedDateTime = now.toLocaleString('ru-RU', {
 675                    day: '2-digit',
 676                    month: '2-digit',
 677                    year: 'numeric',
 678                    hour: '2-digit',
 679                    minute: '2-digit'
 680                }).replace(',', '');
 681
 682                // Показываем модальное окно для системных уведомлений
 683                showSystemNotificationModal(message, formattedDateTime);
 684            } catch (e) {
 685                console.error('Error handling system notification:', e);
 686            }
 687        });
 688
 689        // Обработка состояний соединения
 690        connection.onreconnecting((error) => {
 691            console.log('SignalR reconnecting...', error);
 692            window.ChatManager.reconnectAttempts++;
 693            if (window.AppToast && typeof window.AppToast.setServerStatus === 'function') {
 694                window.AppToast.setServerStatus('reconnecting');
 695            }
 696        });
 697
 698        connection.onreconnected((connectionId) => {
 699            console.log('SignalR reconnected:', connectionId);
 700            window.ChatManager.reconnectAttempts = 0;
 701            if (window.AppToast && typeof window.AppToast.setServerStatus === 'function') {
 702                window.AppToast.setServerStatus('restored');
 703            }
 704            document.dispatchEvent(new CustomEvent('st2:signalr-reconnected'));
 705        });
 706
 707        connection.onclose((error) => {
 708            console.log('SignalR connection closed:', error);
 709            window.ChatManager.isInitialized = false;
 710
 711            if (window.ChatManager.reconnectAttempts < window.ChatManager.maxReconnectAttempts) {
 712                if (window.AppToast && typeof window.AppToast.setServerStatus === 'function') {
 713                    window.AppToast.setServerStatus('reconnecting');
 714                }
 715                setTimeout(() => {
 716                    if (!window.ChatManager.isInitialized) {
 717                        startConnection();
 718                    }
 719                }, window.ChatManager.reconnectDelay);
 720            } else {
 721                if (window.AppToast && typeof window.AppToast.setServerStatus === 'function') {
 722                    window.AppToast.setServerStatus('closed', 'Сервер пока не доступен из-за обновления. Пожалуйста, обн
 723                }
 724            }
 725        });
 726
 727        window.ChatManager.connection = connection;
 728        return connection;
 729    }
 730
 731    // Запуск соединения
 732    async function startConnection() {
 733        try {
 734            const connection = initializeSignalR();
 735            if (!connection) return;
 736
 737            if (connection.state === signalR.HubConnectionState.Disconnected) {
 738                await connection.start();
 739                console.log('Chat connected to SignalR');
 740                const wasReconnecting = !window.ChatManager.isInitialized && window.ChatManager.reconnectAttempts > 0;
 741                window.ChatManager.isInitialized = true;
 742                window.ChatManager.reconnectAttempts = 0;
 743                if (wasReconnecting && window.AppToast && typeof window.AppToast.setServerStatus === 'function') {
 744                    window.AppToast.setServerStatus('restored');
 745                }
 746            }
 747        } catch (err) {
 748            console.debug('SignalR connection transient error during start:', err?.message || err);
 749            window.ChatManager.isInitialized = false;
 750            window.ChatManager.reconnectAttempts++;
 751
 752            if (window.AppToast && typeof window.AppToast.setServerStatus === 'function') {
 753                window.AppToast.setServerStatus('reconnecting');
 754            }
 755
 756            if (window.ChatManager.reconnectAttempts < window.ChatManager.maxReconnectAttempts) {
 757                setTimeout(() => {
 758                    startConnection();
 759                }, window.ChatManager.reconnectDelay * 2);
 760            } else {
 761                if (window.AppToast && typeof window.AppToast.setServerStatus === 'function') {
 762                    window.AppToast.setServerStatus('closed', 'Сервер пока не доступен из-за обновления. Пожалуйста, обн
 763                }
 764            }
 765        }
 766    }
 767
 768    document.addEventListener('DOMContentLoaded', () => {
 769        const chatCollapse = document.getElementById('chatCollapse');
 770        const chatToggle = document.getElementById('chatToggle');
 771        const chatClose = document.getElementById('chatClose');
 772        const chatBadge = document.getElementById('chatBadge');
 773
 774        if (!chatCollapse || !chatToggle || !chatClose || !chatBadge) {
 775            // На специализированных страницах виджет может отсутствовать; это штатно.
 776            console.debug('Chat widget UI is unavailable on this page');
 777            return;
 778        }
 779
 780        // Очистка старых сообщений при загрузке
 781        cleanupOldMessages();
 782
 783        // Показать бейдж, если есть непросмотренные сообщения
 784        const messages = localStorage.getItem('chatMessages');
 785        const wasViewed = localStorage.getItem('chatViewed') === 'true';
 786        if (messages && !wasViewed) {
 787            chatBadge.classList.remove('d-none');
 788        }
 789
 790        // Загрузка при разворачивании
 791        chatCollapse.addEventListener('shown.bs.collapse', () => {
 792            loadMessages();
 793            chatToggle.style.opacity = '0';
 794            chatToggle.style.pointerEvents = 'none';
 795            chatBadge.classList.add('d-none');
 796            localStorage.setItem('chatViewed', 'true');
 797        });
 798
 799        chatCollapse.addEventListener('hidden.bs.collapse', () => {
 800            chatToggle.style.opacity = '1';
 801            chatToggle.style.pointerEvents = 'auto';
 802        });
 803
 804        chatClose.addEventListener('click', () => {
 805            const collapse = bootstrap.Collapse.getInstance(chatCollapse);
 806            if (collapse) {
 807                collapse.hide();
 808            }
 809        });
 810
 811        // Инициализация SignalR соединения
 812        if (window.signalR) {
 813            startConnection();
 814        } else {
 815            window.addEventListener('load', () => {
 816                if (window.signalR) {
 817                    startConnection();
 818                }
 819            });
 820        }
 821
 822        // Сохранение состояния чата при переходе
 823        window.addEventListener('beforeunload', () => {
 824            const isOpen = chatCollapse.classList.contains('show');
 825            localStorage.setItem('chatWasOpen', isOpen ? 'true' : 'false');
 826        });
 827
 828        // Восстановление состояния чата
 829        const wasOpen = localStorage.getItem('chatWasOpen') === 'true';
 830        if (wasOpen && chatCollapse) {
 831            const collapse = new bootstrap.Collapse(chatCollapse, { toggle: false });
 832            collapse.show();
 833            // Загружаем сообщения сразу, если чат был открыт
 834            setTimeout(() => {
 835                loadMessages();
 836            }, 100);
 837        }
 838    });
 839
 840</script>
 841
 842<script src="~/js/system-notification-modal.js" asp-append-version="true"></script>
 843
 844<script src="~/js/site.js" asp-append-version="true"></script>
 845<script src="~/js/page-loading-bar.js" asp-append-version="true"></script>
 846<script src="~/js/shared/order-number-emphasis.js" asp-append-version="true"></script>
 847<script src="~/js/shared/skeleton-loader.js" asp-append-version="true"></script>
 0848@await RenderSectionAsync("Scripts", required: false)
 849
 850<!-- Скрипт отслеживания истории посещений -->
 851<script>
 852(function() {
 853    'use strict';
 854
 855    // Функция для сохранения истории в local storage
 856    function savePageToHistory(path, title) {
 857        try {
 858            const historyKey = 'userPageHistory';
 859            const maxHistoryItems = 50; // Максимальное количество записей
 860
 861            let history = JSON.parse(localStorage.getItem(historyKey) || '[]');
 862
 863            // Добавляем текущую страницу
 864            const historyItem = {
 865                path: path,
 866                title: title || document.title || path,
 867                timestamp: new Date().toISOString(),
 868                displayTime: new Date().toLocaleString('ru-RU', {
 869                    day: '2-digit',
 870                    month: '2-digit',
 871                    year: 'numeric',
 872                    hour: '2-digit',
 873                    minute: '2-digit',
 874                    second: '2-digit'
 875                })
 876            };
 877
 878            // Удаляем дубликаты (если та же страница уже есть)
 879            history = history.filter(item => item.path !== path);
 880
 881            // Добавляем новую запись в начало
 882            history.unshift(historyItem);
 883
 884            // Ограничиваем количество записей
 885            if (history.length > maxHistoryItems) {
 886                history = history.slice(0, maxHistoryItems);
 887            }
 888
 889            // Сохраняем в local storage
 890            localStorage.setItem(historyKey, JSON.stringify(history));
 891            console.log('История сохранена:', historyItem);
 892        } catch (e) {
 893            console.error('Ошибка при сохранении истории:', e);
 894        }
 895    }
 896
 897    // Сохраняем текущую страницу при загрузке
 898    function initHistoryTracking() {
 899        // Используем полный путь включая query string
 900        const currentPath = window.location.pathname + window.location.search;
 901        // Получаем заголовок страницы или последний сегмент пути
 902        let currentTitle = document.title;
 903        if (!currentTitle || currentTitle.trim() === '') {
 904            const pathSegments = currentPath.split('/').filter(s => s);
 905            currentTitle = pathSegments.length > 0 ? pathSegments[pathSegments.length - 1] : 'Страница';
 906        }
 907        console.log('Инициализация отслеживания истории:', { path: currentPath, title: currentTitle });
 908        savePageToHistory(currentPath, currentTitle);
 909    }
 910
 911    if (document.readyState === 'loading') {
 912        document.addEventListener('DOMContentLoaded', initHistoryTracking);
 913    } else {
 914        initHistoryTracking();
 915    }
 916
 917    // Отслеживаем переходы по ссылкам
 918    document.addEventListener('click', function(e) {
 919        const link = e.target.closest('a[href]');
 920        if (link && link.href && !link.href.startsWith('javascript:') && !link.href.startsWith('#') && !link.hasAttribut
 921            const href = link.getAttribute('href');
 922            if (href) {
 923                const fullPath = href.startsWith('http') ? href : (window.location.origin + (href.startsWith('/') ? href
 924                const linkTitle = link.textContent.trim() || link.title || href;
 925                savePageToHistory(fullPath, linkTitle);
 926            }
 927        }
 928    });
 929
 930    // Отслеживаем изменения истории браузера (назад/вперед)
 931    window.addEventListener('popstate', function() {
 932        setTimeout(function() {
 933            const currentPath = window.location.pathname + window.location.search;
 934            savePageToHistory(currentPath, document.title);
 935        }, 100);
 936    });
 937})();
 938</script>
 939
 940<script>
 941    // === Счетчик непрочитанных сообщений Ozon Chat (Studio2) ===
 942    (function() {
 943        let isUpdating = false;
 944
 945        async function updateOzonChatBadge() {
 946            if (isUpdating) return;
 947            isUpdating = true;
 948
 949            try {
 950                const response = await fetch('/Studio2/OzonChat/GetTotalUnreadCount', {
 951                    headers: { 'Accept': 'application/json' }
 952                });
 953                if (!response.ok || response.redirected) return;
 954                const contentType = response.headers.get('content-type') || '';
 955                if (!contentType.includes('application/json') && !contentType.includes('text/json')) return;
 956                let data;
 957                try {
 958                    data = await response.json();
 959                } catch {
 960                    return;
 961                }
 962
 963                if (data && data.success && data.totalUnread > 0) {
 964                    const badge = document.getElementById('studio2-ozon-chat-badge');
 965                    if (badge) {
 966                        badge.textContent = data.totalUnread;
 967                        badge.style.display = 'inline-block';
 968                    }
 969                } else {
 970                    const badge = document.getElementById('studio2-ozon-chat-badge');
 971                    if (badge) {
 972                        badge.style.display = 'none';
 973                    }
 974                }
 975            } catch (err) {
 976                console.debug('Ozon chat badge update skipped:', err);
 977            } finally {
 978                isUpdating = false;
 979            }
 980        }
 981
 982        // Запускаем сразу
 983        updateOzonChatBadge();
 984        // Обновляем каждые 30 секунд
 985        setInterval(updateOzonChatBadge, 30000);
 986    })();
 987</script>
 988</body>
 989</html>
 990
 991<script>
 992    function updateTable() {
 993        var button = document.getElementById('updateButton');
 994        var spinner = document.getElementById('spinner');
 995        var buttonText = document.getElementById('buttonText');
 996
 997        button.disabled = true;
 998        spinner.classList.remove('visually-hidden');
 999        buttonText.textContent = 'Обновление...';
 1000
 1001    }
 1002</script>

Methods/Properties

<ExecuteAsync()
<ExecuteAsync()