| | | 1 | | @using OzonOrdersWeb.WebServices.Sidebar |
| | | 2 | | @using OzonOrdersWeb.Areas.Studio2.ViewModels.Grid |
| | | 3 | | @model IReadOnlyList<SidebarNode> |
| | | 4 | | @* |
| | | 5 | | Навигация Studio2 на AG Grid: те же разделы, что в боковом меню (учитывает права), с назначением каждого раздела, |
| | | 6 | | поиском по названию, назначению и ключевым словам и быстрыми кнопками групп. Строки — NavigationPageRows. |
| | | 7 | | *@ |
| | | 8 | | @{ |
| | 0 | 9 | | ViewData["Title"] = "Навигация"; |
| | 0 | 10 | | Layout = "~/Views/Shared/_Layout.cshtml"; |
| | 0 | 11 | | var rows = NavigationPageRows.Build(Model); |
| | 0 | 12 | | var groups = rows.Select(r => new { r.Group, r.GroupIcon }).DistinctBy(g => g.Group).ToList(); |
| | 0 | 13 | | var json = new System.Text.Json.JsonSerializerOptions(System.Text.Json.JsonSerializerDefaults.Web); |
| | | 14 | | } |
| | | 15 | | |
| | 0 | 16 | | @await Html.PartialAsync("_BsxGridShell") |
| | | 17 | | <link rel="stylesheet" href="~/css/Studio2/Navigation/navigation-index.css" asp-append-version="true"/> |
| | | 18 | | |
| | | 19 | | <div class="container-fluid py-3 navigation-page bsx-surface"> |
| | | 20 | | <div class="app-section-hero"> |
| | | 21 | | <h1><i class="fas fa-compass me-2" aria-hidden="true"></i>Навигация Studio2</h1> |
| | | 22 | | <div class="subtitle">Все рабочие разделы с назначением — состав совпадает с боковым меню и учитывает ваши права |
| | | 23 | | <div class="subtitle-help">Начните вводить, что нужно сделать: поиск идёт по названию, назначению и ключевым сло |
| | | 24 | | </div> |
| | | 25 | | |
| | | 26 | | <div class="bsx-quick navigation-groups-filter" id="navHomeGroups" role="group" aria-label="Группы разделов"> |
| | 0 | 27 | | <button type="button" class="is-active" data-group="">Все <span class="navigation-count">@rows.Count</span></but |
| | 0 | 28 | | @foreach (var g in groups) |
| | | 29 | | { |
| | 0 | 30 | | <button type="button" data-group="@g.Group"><i class="fas @g.GroupIcon" aria-hidden="true"></i>@g.Group <spa |
| | | 31 | | } |
| | | 32 | | </div> |
| | | 33 | | |
| | 0 | 34 | | @await Html.PartialAsync("_BsxDirectoryGrid", new BsxDirectoryGridViewModel("navHome", "Разделы", "fa-compass", "Что |
| | | 35 | | </div> |
| | 0 | 36 | | <script type="application/json" id="navHomeData">@Html.Raw(System.Text.Json.JsonSerializer.Serialize(rows, json))</scrip |
| | | 37 | | |
| | | 38 | | @section Scripts { |
| | | 39 | | <script src="~/js/Studio2/Grid/bsx-directory.js" asp-append-version="true"></script> |
| | | 40 | | <script> |
| | | 41 | | (function () { |
| | | 42 | | const D = window.BsxDirectory, esc = BsxGrid.esc; |
| | | 43 | | const rows = JSON.parse(document.getElementById('navHomeData').textContent || '[]'); |
| | | 44 | | const open = r => { if (r.external) window.open(r.href, '_blank', 'noopener'); else location.href = r.href; |
| | | 45 | | let api = null; |
| | | 46 | | const grid = D.grid({ |
| | | 47 | | key: 'navHome', prefix: 'navHome', rows: rows, rowHeight: 48, |
| | | 48 | | rowUrl: r => r.external ? null : r.href, |
| | | 49 | | columnDefs: [ |
| | | 50 | | D.numCol({ colId: 'order', field: 'order', headerName: '№', width: 70, hide: true, sort: 'asc' }), |
| | | 51 | | { |
| | | 52 | | colId: 'title', field: 'title', headerName: 'Раздел', minWidth: 240, flex: 1, |
| | | 53 | | getQuickFilterText: p => [p.data.title, p.data.description, p.data.group, p.data.subgroup, p.dat |
| | | 54 | | cellRenderer: p => { |
| | | 55 | | const r = p.data; if (!r) return ''; |
| | | 56 | | return '<a class="navigation-link" href="' + esc(r.href) + '"' + (r.external ? ' target="_bl |
| | | 57 | | '<i class="fas ' + esc(r.icon) + '" aria-hidden="true"></i><span>' + esc(r.title) + '</s |
| | | 58 | | (r.external ? '<i class="fas fa-arrow-up-right-from-square navigation-link__external" ti |
| | | 59 | | } |
| | | 60 | | }, |
| | | 61 | | { colId: 'description', field: 'description', headerName: 'Назначение', minWidth: 320, flex: 3, wrap |
| | | 62 | | D.setCol({ |
| | | 63 | | colId: 'group', field: 'group', headerName: 'Группа', minWidth: 200, flex: 1, |
| | | 64 | | cellRenderer: p => p.data ? '<span class="bsx-cell-stack"><span><i class="fas ' + esc(p.data.gro |
| | | 65 | | (p.data.subgroup ? '<small class="bsx-muted">' + esc(p.data.subgroup) + '</small>' : '') + ' |
| | | 66 | | }), |
| | | 67 | | { colId: 'href', field: 'href', headerName: 'Адрес', minWidth: 200, flex: 1, hide: true, cellClass: |
| | | 68 | | ], |
| | | 69 | | }); |
| | | 70 | | api = grid && grid.api(); |
| | | 71 | | |
| | | 72 | | // Быстрые кнопки групп — тот же set-фильтр колонки «Группа» (чипы и «последний вид» его видят). |
| | | 73 | | const buttons = document.getElementById('navHomeGroups'); |
| | | 74 | | const syncButtons = () => { |
| | | 75 | | const model = api && api.getColumnFilterModel('group'); |
| | | 76 | | const active = model && model.values && model.values.length === 1 ? model.values[0] : ''; |
| | | 77 | | buttons.querySelectorAll('button').forEach(b => b.classList.toggle('is-active', b.dataset.group === acti |
| | | 78 | | }; |
| | | 79 | | buttons.addEventListener('click', e => { |
| | | 80 | | const b = e.target.closest('button[data-group]'); |
| | | 81 | | if (!b || !api) return; |
| | | 82 | | const g = b.dataset.group; |
| | | 83 | | api.setColumnFilterModel('group', g ? { filterType: 'set', values: [g] } : null).then(() => api.onFilter |
| | | 84 | | }); |
| | | 85 | | if (api) { api.addEventListener('filterChanged', syncButtons); syncButtons(); } |
| | | 86 | | |
| | | 87 | | // Поиск сразу в фокусе; Enter — открыть первый найденный раздел. |
| | | 88 | | const search = document.getElementById('navHomeSearch'); |
| | | 89 | | search.focus(); |
| | | 90 | | search.addEventListener('keydown', e => { |
| | | 91 | | if (e.key !== 'Enter' || !api) return; |
| | | 92 | | const first = api.getDisplayedRowAtIndex(0); |
| | | 93 | | if (first && first.data) { e.preventDefault(); open(first.data); } |
| | | 94 | | }); |
| | | 95 | | })(); |
| | | 96 | | </script> |
| | | 97 | | } |