| | | 1 | | @using OzonOrdersWeb.Areas.Studio2.ViewModels.Grid |
| | | 2 | | @using OzonOrdersWeb.WebServices.Partners |
| | | 3 | | @model IReadOnlyList<PartnerGridRow> |
| | | 4 | | @{ |
| | 0 | 5 | | ViewData["Title"] = "Кабинет партнёров"; |
| | | 6 | | } |
| | | 7 | | |
| | 0 | 8 | | @await Html.PartialAsync("~/Areas/Studio2/Views/Shared/_BsxGridShell.cshtml") |
| | | 9 | | |
| | | 10 | | <div class="container-fluid py-3 bsx-surface"> |
| | | 11 | | <div class="app-section-hero app-section-hero--import d-flex flex-wrap justify-content-between align-items-start gap |
| | | 12 | | <div> |
| | | 13 | | <h1><i class="fas fa-handshake me-2 text-primary"></i>Кабинет партнёров</h1> |
| | | 14 | | <div class="subtitle">Партнёры, правила видимости заказов и возможности</div> |
| | | 15 | | <div class="subtitle-help"> |
| | | 16 | | Здесь вы заводите партнёров, задаёте, какие заказы они видят, и включаете возможности. |
| | | 17 | | Кабинет партнёра работает в отдельном сервисе PartnerOffice и ходит в Studio2 через API. |
| | | 18 | | <strong>Партнёр без правил не видит ни одного заказа.</strong> |
| | | 19 | | </div> |
| | | 20 | | <div class="app-section-hero__quick-links"> |
| | | 21 | | <a asp-area="PartnerOffice" asp-controller="Partners" asp-action="Audit"><i class="fas fa-clipboard-list |
| | | 22 | | <a href="/swagger/index.html" target="_blank" rel="noopener"><i class="fas fa-code"></i>Swagger API парт |
| | | 23 | | <a asp-area="Studio2" asp-controller="AssemblyListAccessKeys" asp-action="Index"><i class="fas fa-key">< |
| | | 24 | | </div> |
| | | 25 | | </div> |
| | | 26 | | <div class="d-flex flex-wrap gap-2 align-items-center"> |
| | | 27 | | <div class="bsx-dir-stats"> |
| | 0 | 28 | | <span class="bsx-dir-stat">Всего <b>@Model.Count</b></span> |
| | 0 | 29 | | <span class="bsx-dir-stat">Активных <b>@Model.Count(x => x.IsActive)</b></span> |
| | 0 | 30 | | <span class="bsx-dir-stat">Без правил <b>@Model.Count(x => x.RulesCount == 0)</b></span> |
| | | 31 | | </div> |
| | | 32 | | <a asp-action="Create" class="btn btn-primary btn-sm"><i class="fas fa-plus me-1"></i>Создать партнёра</a> |
| | | 33 | | </div> |
| | | 34 | | </div> |
| | | 35 | | |
| | 0 | 36 | | @if (TempData["Success"] is string ok) |
| | | 37 | | { |
| | | 38 | | <div class="alert alert-success alert-dismissible fade show py-2 small" role="alert"> |
| | 0 | 39 | | <i class="fas fa-check-circle me-2"></i>@ok |
| | | 40 | | <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Закрыть"></button> |
| | | 41 | | </div> |
| | | 42 | | } |
| | 0 | 43 | | @if (TempData["Error"] is string err) |
| | | 44 | | { |
| | | 45 | | <div class="alert alert-danger alert-dismissible fade show py-2 small" role="alert"> |
| | 0 | 46 | | <i class="fas fa-exclamation-circle me-2"></i>@err |
| | | 47 | | <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Закрыть"></button> |
| | | 48 | | </div> |
| | | 49 | | } |
| | | 50 | | |
| | 0 | 51 | | @await Html.PartialAsync("~/Areas/Studio2/Views/Shared/_BsxDirectoryGrid.cshtml", new BsxDirectoryGridViewModel("par |
| | 0 | 52 | | "Двойной клик по строке открывает настройки партнёра.")) |
| | | 53 | | </div> |
| | | 54 | | |
| | | 55 | | @section Scripts { |
| | | 56 | | <script src="~/js/Studio2/Grid/bsx-directory.js" asp-append-version="true"></script> |
| | | 57 | | <script> |
| | | 58 | | (function () { |
| | | 59 | | const D = window.BsxDirectory; |
| | | 60 | | const esc = v => window.BsxGrid.esc(v); |
| | 0 | 61 | | const editUrl = id => '@Url.Action("Edit", "Partners", new { area = "PartnerOffice" })/' + id; |
| | 0 | 62 | | const auditUrl = id => '@Url.Action("Audit", "Partners", new { area = "PartnerOffice" })?partnerId=' + id; |
| | | 63 | | const fmtDateTime = v => { |
| | | 64 | | if (!v) return ''; |
| | | 65 | | const d = new Date(v + (/[zZ]|[+-]\d\d:?\d\d$/.test(v) ? '' : 'Z')); |
| | | 66 | | return isNaN(d) ? String(v) : d.toLocaleString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric', |
| | | 67 | | }; |
| | | 68 | | |
| | | 69 | | D.grid({ |
| | | 70 | | key: 'dirPartner', |
| | | 71 | | prefix: 'partners', |
| | 0 | 72 | | rows: @Json.Serialize(Model), |
| | | 73 | | rowUrl: r => editUrl(r.id), |
| | | 74 | | columnDefs: [ |
| | | 75 | | D.numCol({ field: 'id', headerName: 'ID', width: 80, hide: true }), |
| | | 76 | | { field: 'name', headerName: 'Партнёр', flex: 1, minWidth: 220, cellClass: 'fw-semibold', sort: 'asc', |
| | | 77 | | cellRenderer: p => p.data ? '<a href="' + esc(editUrl(p.data.id)) + '">' + esc(p.value) + '</a>' : '' |
| | | 78 | | { field: 'email', headerName: 'Почта', width: 250, tooltipField: 'email', |
| | | 79 | | cellRenderer: p => !p.data ? '' : p.value ? esc(p.value) : D.tone('нет почты — вход закрыт', 'danger', |
| | | 80 | | { field: 'googleEmail', headerName: 'Google-почта', width: 230, tooltipField: 'googleEmail', |
| | | 81 | | cellRenderer: p => p.value ? esc(p.value) : '<span class="bsx-muted">не указана</span>' }, |
| | | 82 | | D.numCol({ field: 'rulesCount', headerName: 'Правил', width: 110, |
| | | 83 | | headerTooltip: 'Без правил партнёр не видит ни одного заказа', |
| | | 84 | | cellRenderer: p => !p.data ? '' : p.value > 0 ? String(p.value) : D.tone('нет правил', 'danger', 'fa-t |
| | | 85 | | { field: 'permissionsText', headerName: 'Возможности', width: 260, tooltipField: 'permissionsText', |
| | 0 | 86 | | cellRenderer: p => !p.data ? '' : p.value === '@PartnerGridRows.ViewOnlyText' |
| | | 87 | | ? '<span class="bsx-muted">' + esc(p.value) + '</span>' |
| | | 88 | | : p.value.split(', ').map(t => D.tone(t, 'accent')).join(' ') }, |
| | | 89 | | D.boolCol({ field: 'labelPrint', headerName: 'Этикетки', width: 110, hide: true }), |
| | | 90 | | D.boolCol({ field: 'assemble', headerName: 'Сборка', width: 110, hide: true }), |
| | | 91 | | D.boolCol({ field: 'trackNumber', headerName: 'Трек', width: 110, hide: true }), |
| | | 92 | | { field: 'lastLoginAtUtc', headerName: 'Последний вход', width: 170, filter: 'agDateColumnFilter', |
| | | 93 | | filterParams: { maxNumConditions: 1, inRangeInclusive: true }, |
| | | 94 | | valueFormatter: p => p.data ? (fmtDateTime(p.value) || 'не входил') : '' }, |
| | | 95 | | D.boolCol({ field: 'isActive', headerName: 'Статус', width: 130, |
| | | 96 | | filterParams: { labels: { 'true': 'Активен', 'false': 'Отключён' } }, |
| | | 97 | | cellRenderer: p => D.yesNo(p.value, 'Активен', 'Отключён') }), |
| | | 98 | | D.actionsCol(r => [ |
| | | 99 | | { icon: 'fa-pen', title: 'Настройки партнёра', kind: 'view', href: editUrl(r.id) }, |
| | | 100 | | { icon: 'fa-clipboard-list', title: 'Журнал партнёра', kind: 'view', href: auditUrl(r.id) } |
| | | 101 | | ], { width: 100 }) |
| | | 102 | | ] |
| | | 103 | | }); |
| | | 104 | | })(); |
| | | 105 | | </script> |
| | | 106 | | } |