From d472026df43e45fbb375f73fdc543b0a2d855f53 Mon Sep 17 00:00:00 2001 From: fs Date: Tue, 14 Apr 2026 21:34:32 +0200 Subject: [PATCH] feat(helpdesk): add Domains list page with BC OData contract type enrichment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new "Domains" page to the helpdesk module that fetches domain data from the FS_Contract_Domains OData endpoint and enriches each domain with its contract type (PI_Header_Type) by joining with FS_Contract_Lines_Test where Type = 'Domain'. New files: - domains/index().php, index(default).phtml, filter-schema.php — list page - domains-data().php — data endpoint with PHP-side filtering/sorting - helpdesk-domains-index.js — Grid.js via initStandardListPage() Gateway additions: - listDomains() — fetch all contract domains - listDomainContractLines() — fetch domain-type contract lines for type lookup Sidebar restructured into three collapsible groups (Lookup, Monitoring, Administration) with per-group icons and color coding, matching the core admin panel pattern. Co-Authored-By: Claude Opus 4.6 (1M context) --- modules/helpdesk/i18n/default_de.json | 13 +- modules/helpdesk/i18n/default_en.json | 13 +- .../Helpdesk/Service/BcODataGateway.php | 52 ++++++ modules/helpdesk/module.php | 2 + .../pages/helpdesk/domains-data().php | 157 ++++++++++++++++++ .../pages/helpdesk/domains/filter-schema.php | 76 +++++++++ .../pages/helpdesk/domains/index().php | 62 +++++++ .../helpdesk/domains/index(default).phtml | 51 ++++++ .../templates/aside-helpdesk-panel.phtml | 102 ++++++++---- .../web/js/pages/helpdesk-domains-index.js | 85 ++++++++++ web/css/layout/app-sidebar.css | 11 ++ 11 files changed, 586 insertions(+), 38 deletions(-) create mode 100644 modules/helpdesk/pages/helpdesk/domains-data().php create mode 100644 modules/helpdesk/pages/helpdesk/domains/filter-schema.php create mode 100644 modules/helpdesk/pages/helpdesk/domains/index().php create mode 100644 modules/helpdesk/pages/helpdesk/domains/index(default).phtml create mode 100644 modules/helpdesk/web/js/pages/helpdesk-domains-index.js diff --git a/modules/helpdesk/i18n/default_de.json b/modules/helpdesk/i18n/default_de.json index 93d2542..5400c1e 100644 --- a/modules/helpdesk/i18n/default_de.json +++ b/modules/helpdesk/i18n/default_de.json @@ -345,5 +345,16 @@ "Upcoming meetings": "Kommende Termine", "Past meetings": "Vergangene Termine", "No meetings found for this customer.": "Keine Termine für diesen Kunden gefunden.", - "Could not load meetings.": "Termine konnten nicht geladen werden." + "Could not load meetings.": "Termine konnten nicht geladen werden.", + "Domains": "Domains", + "Search domains...": "Domains suchen...", + "Customer No.": "Kunden-Nr.", + "Customer Name": "Kundenname", + "Administration": "Verwaltung", + "No domains found": "Keine Domains gefunden", + "Could not load domains.": "Domains konnten nicht geladen werden.", + "Lookup": "Nachschlagen", + "Monitoring": "Monitoring", + "Contract type": "Vertragsart", + "Customer": "Kunde" } diff --git a/modules/helpdesk/i18n/default_en.json b/modules/helpdesk/i18n/default_en.json index e03a598..c32a78b 100644 --- a/modules/helpdesk/i18n/default_en.json +++ b/modules/helpdesk/i18n/default_en.json @@ -345,5 +345,16 @@ "Upcoming meetings": "Upcoming meetings", "Past meetings": "Past meetings", "No meetings found for this customer.": "No meetings found for this customer.", - "Could not load meetings.": "Could not load meetings." + "Could not load meetings.": "Could not load meetings.", + "Domains": "Domains", + "Search domains...": "Search domains...", + "Customer No.": "Customer No.", + "Customer Name": "Customer Name", + "Administration": "Administration", + "No domains found": "No domains found", + "Could not load domains.": "Could not load domains.", + "Lookup": "Lookup", + "Monitoring": "Monitoring", + "Contract type": "Contract type", + "Customer": "Customer" } diff --git a/modules/helpdesk/lib/Module/Helpdesk/Service/BcODataGateway.php b/modules/helpdesk/lib/Module/Helpdesk/Service/BcODataGateway.php index 450f911..d54e89b 100644 --- a/modules/helpdesk/lib/Module/Helpdesk/Service/BcODataGateway.php +++ b/modules/helpdesk/lib/Module/Helpdesk/Service/BcODataGateway.php @@ -22,6 +22,7 @@ class BcODataGateway public const ENTITY_TICKET_LOG_LV = 'PBI_LV_SupportTicketLog'; public const ENTITY_CONTRACT_LINES = 'FS_Contract_Lines_Test'; public const ENTITY_MEETINGS = 'FS_Debitor_Meetings'; + public const ENTITY_CONTRACT_DOMAINS = 'FS_Contract_Domains'; private const CONNECT_TIMEOUT = 10; private const REQUEST_TIMEOUT = 30; @@ -825,6 +826,57 @@ class BcODataGateway return $this->extractODataValues($response); } + /** + * Get all contract domains. + * + * Fetches all domains from the FS_Contract_Domains entity. + * Filtering, sorting and pagination are handled PHP-side by the caller. + * + * @return array> + */ + public function listDomains(): array + { + $select = 'No,Customer_No,Customer_Name,URL,State,Administration'; + $url = $this->settingsGateway->buildEntityUrl(self::ENTITY_CONTRACT_DOMAINS) + . '?$top=5000' + . '&$select=' . rawurlencode($select) + . '&$orderby=' . rawurlencode('Customer_Name asc'); + + $response = $this->request('GET', $url); + if ($response === null) { + return []; + } + + return $this->extractODataValues($response); + } + + /** + * Get all contract lines of type "Domain". + * + * Returns contract line metadata for domain-type lines, + * keyed by DNS number (No field). Used to enrich the domain + * list with contract type information (PI_Header_Type). + * + * @return array> + */ + public function listDomainContractLines(): array + { + $filter = "Type eq 'Domain'"; + $select = 'No,Header_No,PI_Header_Type,PI_Header_Description,PI_Header_State,Line_State'; + $url = $this->settingsGateway->buildEntityUrl(self::ENTITY_CONTRACT_LINES) + . '?$filter=' . rawurlencode($filter) + . '&$top=5000' + . '&$select=' . rawurlencode($select) + . '&$orderby=' . rawurlencode('No asc'); + + $response = $this->request('GET', $url); + if ($response === null) { + return []; + } + + return $this->extractODataValues($response); + } + /** * Get a single ticket by ticket number. * diff --git a/modules/helpdesk/module.php b/modules/helpdesk/module.php index 6c20413..31c07da 100644 --- a/modules/helpdesk/module.php +++ b/modules/helpdesk/module.php @@ -15,6 +15,8 @@ return [ 'routes' => [ ['path' => 'helpdesk/search-data', 'target' => 'helpdesk/search-data'], + ['path' => 'helpdesk/domains', 'target' => 'helpdesk/domains'], + ['path' => 'helpdesk/domains-data', 'target' => 'helpdesk/domains-data'], ['path' => 'helpdesk/debitor/{id}', 'target' => 'helpdesk/debitor'], ['path' => 'helpdesk/ticket/{id}', 'target' => 'helpdesk/ticket'], ['path' => 'helpdesk/settings', 'target' => 'helpdesk/settings'], diff --git a/modules/helpdesk/pages/helpdesk/domains-data().php b/modules/helpdesk/pages/helpdesk/domains-data().php new file mode 100644 index 0000000..41ebce2 --- /dev/null +++ b/modules/helpdesk/pages/helpdesk/domains-data().php @@ -0,0 +1,157 @@ +listDomains(); +} catch (\Throwable) { + gridJsonDataResult([], 0); + + return; +} + +// Build contract type lookup: DNS number → {contract_type, contract_no, contract_description} +$contractLookup = []; +try { + $contractLines = $gateway->listDomainContractLines(); + foreach ($contractLines as $line) { + $dnsNo = trim((string) ($line['No'] ?? '')); + if ($dnsNo !== '' && !isset($contractLookup[$dnsNo])) { + $contractLookup[$dnsNo] = [ + 'contract_type' => trim((string) ($line['PI_Header_Type'] ?? '')), + 'contract_no' => trim((string) ($line['Header_No'] ?? '')), + 'contract_description' => trim((string) ($line['PI_Header_Description'] ?? '')), + ]; + } + } +} catch (\Throwable) { + // Contract enrichment is best-effort — continue without it +} + +// Enrich domains with contract type before filtering (so search/sort can use it) +foreach ($allDomains as &$domain) { + $domainNo = trim((string) ($domain['No'] ?? '')); + $contract = $contractLookup[$domainNo] ?? null; + $domain['contract_type'] = $contract['contract_type'] ?? ''; + $domain['contract_no'] = $contract['contract_no'] ?? ''; + $domain['contract_description'] = $contract['contract_description'] ?? ''; +} +unset($domain); + +$filtered = $allDomains; + +// Text search across Customer_No, Customer_Name, URL, No, contract_type +if ($search !== '') { + $searchLower = mb_strtolower($search); + $filtered = array_values(array_filter($filtered, static function (array $domain) use ($searchLower): bool { + $haystack = mb_strtolower( + ($domain['Customer_No'] ?? '') . ' ' + . ($domain['Customer_Name'] ?? '') . ' ' + . ($domain['URL'] ?? '') . ' ' + . ($domain['No'] ?? '') . ' ' + . ($domain['contract_type'] ?? '') + ); + + return str_contains($haystack, $searchLower); + })); +} + +// Customer filter (partial match on Customer_No + Customer_Name) +if ($customer !== '') { + $customerLower = mb_strtolower($customer); + $filtered = array_values(array_filter($filtered, static function (array $domain) use ($customerLower): bool { + $haystack = mb_strtolower( + ($domain['Customer_No'] ?? '') . ' ' + . ($domain['Customer_Name'] ?? '') + ); + + return str_contains($haystack, $customerLower); + })); +} + +// Contract type filter (partial match) +if ($contractType !== '') { + $contractTypeLower = mb_strtolower($contractType); + $filtered = array_values(array_filter($filtered, static function (array $domain) use ($contractTypeLower): bool { + return str_contains(mb_strtolower((string) ($domain['contract_type'] ?? '')), $contractTypeLower); + })); +} + +// State filter (exact match) +if ($state !== '') { + $filtered = array_values(array_filter($filtered, static function (array $domain) use ($state): bool { + return trim((string) ($domain['State'] ?? '')) === $state; + })); +} + +// Administration filter (exact match) +if ($administration !== '') { + $filtered = array_values(array_filter($filtered, static function (array $domain) use ($administration): bool { + return trim((string) ($domain['Administration'] ?? '')) === $administration; + })); +} + +// Sorting +$total = count($filtered); + +usort($filtered, static function (array $a, array $b) use ($order, $dir): int { + $va = (string) ($a[$order] ?? ''); + $vb = (string) ($b[$order] ?? ''); + $cmp = strnatcasecmp($va, $vb); + + return $dir === 'desc' ? -$cmp : $cmp; +}); + +// Pagination +$rows = array_slice($filtered, $offset, $limit); + +// Row preparation +$stateVariantMap = [ + 'Aktiv' => 'success', + 'In Vorbereitung' => 'warning', +]; + +$debitorBaseUrl = lurl('helpdesk/debitor/'); + +$preparedRows = []; +foreach ($rows as $domain) { + $customerNo = trim((string) ($domain['Customer_No'] ?? '')); + $domainState = (string) ($domain['State'] ?? ''); + + $preparedRows[] = [ + 'No' => (string) ($domain['No'] ?? ''), + 'Customer_No' => $customerNo, + 'Customer_Name' => (string) ($domain['Customer_Name'] ?? ''), + 'URL' => (string) ($domain['URL'] ?? ''), + 'State' => $domainState, + 'state_variant' => $stateVariantMap[$domainState] ?? 'neutral', + 'Administration' => (string) ($domain['Administration'] ?? ''), + 'contract_type' => (string) ($domain['contract_type'] ?? ''), + 'contract_no' => (string) ($domain['contract_no'] ?? ''), + 'debitor_url' => $customerNo !== '' ? $debitorBaseUrl . rawurlencode($customerNo) : '', + ]; +} + +gridJsonDataResult($preparedRows, $total); diff --git a/modules/helpdesk/pages/helpdesk/domains/filter-schema.php b/modules/helpdesk/pages/helpdesk/domains/filter-schema.php new file mode 100644 index 0000000..e9061ac --- /dev/null +++ b/modules/helpdesk/pages/helpdesk/domains/filter-schema.php @@ -0,0 +1,76 @@ + [ + 'search' => ['type' => 'string'], + 'customer' => ['type' => 'string'], + 'contract_type' => ['type' => 'string'], + 'state' => ['type' => 'string', 'default' => 'all'], + 'administration' => ['type' => 'string', 'default' => 'all'], + 'limit' => ['type' => 'int', 'default' => 10, 'min' => 1, 'max' => 100], + 'offset' => ['type' => 'int', 'default' => 0, 'min' => 0], + 'order' => [ + 'type' => 'order', + 'allowed' => ['Customer_No', 'Customer_Name', 'URL', 'State', 'Administration', 'No', 'contract_type'], + 'default' => 'Customer_Name', + ], + 'dir' => ['type' => 'dir', 'default' => 'asc'], + ], + 'toolbar' => [ + [ + 'key' => 'search', + 'type' => 'text', + 'label' => 'Search', + 'placeholder' => 'Search domains...', + 'input_id' => 'helpdesk-domains-search-input', + 'search' => true, + 'query' => ['type' => 'string'], + ], + [ + 'key' => 'customer', + 'type' => 'text', + 'label' => 'Customer', + 'placeholder' => 'Customer name or no...', + 'input_id' => 'helpdesk-domains-customer-filter', + 'query' => ['type' => 'string'], + 'label_attributes' => ['data-filter-optional' => true], + ], + [ + 'key' => 'contract_type', + 'type' => 'text', + 'label' => 'Contract type', + 'placeholder' => 'e.g. WEBSITE, SW&H...', + 'input_id' => 'helpdesk-domains-contract-type-filter', + 'query' => ['type' => 'string'], + 'label_attributes' => ['data-filter-optional' => true], + ], + [ + 'key' => 'state', + 'type' => 'select', + 'label' => 'State', + 'input_id' => 'helpdesk-domains-state-filter', + 'default' => 'all', + 'normalize' => 'all_to_empty', + 'allowed' => [ + ['id' => 'all', 'description' => 'All'], + ['id' => 'Aktiv', 'description' => 'Aktiv'], + ['id' => 'In Vorbereitung', 'description' => 'In Vorbereitung'], + ], + 'label_attributes' => ['data-filter-optional' => true], + ], + [ + 'key' => 'administration', + 'type' => 'select', + 'label' => 'Administration', + 'input_id' => 'helpdesk-domains-admin-filter', + 'default' => 'all', + 'normalize' => 'all_to_empty', + 'allowed' => [ + ['id' => 'all', 'description' => 'All'], + ['id' => 'Intern', 'description' => 'Intern'], + ['id' => 'Extern', 'description' => 'Extern'], + ], + 'label_attributes' => ['data-filter-optional' => true], + ], + ], +]); diff --git a/modules/helpdesk/pages/helpdesk/domains/index().php b/modules/helpdesk/pages/helpdesk/domains/index().php new file mode 100644 index 0000000..a72d86c --- /dev/null +++ b/modules/helpdesk/pages/helpdesk/domains/index().php @@ -0,0 +1,62 @@ +queryAll(); + +$filterSchema = require __DIR__ . '/filter-schema.php'; +$listFilterContext = gridBuildListFilterContext($filterSchema, [ + 'query' => $query, + 'search_keys' => ['search'], +]); +$toolbarFilterSchema = $listFilterContext['toolbarFilterSchema']; +$searchToolbarFilterSchema = $listFilterContext['searchToolbarFilterSchema']; +$drawerToolbarFilterSchema = $listFilterContext['drawerToolbarFilterSchema']; +$toolbarFilterState = $listFilterContext['toolbarFilterState']; +$clientFilterSchema = $listFilterContext['clientFilterSchema']; +$searchConfig = $listFilterContext['searchConfig']; +$schemaByKey = $listFilterContext['schemaByKey'] ?? []; +$filterChipMeta = [ + 'search' => [ + 'label' => t('Search'), + 'type' => 'text', + ], + 'customer' => [ + 'label' => t('Customer'), + 'type' => 'text', + ], + 'contract_type' => [ + 'label' => t('Contract type'), + 'type' => 'text', + ], + 'state' => [ + 'label' => t('State'), + 'type' => 'select', + 'default' => (string) ($schemaByKey['state']['default'] ?? 'all'), + 'options' => gridOptionMapFromAllowed((array) ($schemaByKey['state'] ?? [])), + ], + 'administration' => [ + 'label' => t('Administration'), + 'type' => 'select', + 'default' => (string) ($schemaByKey['administration']['default'] ?? 'all'), + 'options' => gridOptionMapFromAllowed((array) ($schemaByKey['administration'] ?? [])), + ], +]; + +$settingsGateway = app(HelpdeskSettingsGateway::class); +$isConfigured = $settingsGateway->isConfigured(); + +Buffer::set('title', t('Domains')); +Buffer::set('style_groups', json_encode(['helpdesk'])); +$breadcrumbs = [ + ['label' => t('Home'), 'path' => 'admin'], + ['label' => t('Helpdesk'), 'path' => 'helpdesk'], + ['label' => t('Domains')], +]; diff --git a/modules/helpdesk/pages/helpdesk/domains/index(default).phtml b/modules/helpdesk/pages/helpdesk/domains/index(default).phtml new file mode 100644 index 0000000..a4e5c16 --- /dev/null +++ b/modules/helpdesk/pages/helpdesk/domains/index(default).phtml @@ -0,0 +1,51 @@ + + + + + + + + + +
+
+
+ + + + diff --git a/modules/helpdesk/templates/aside-helpdesk-panel.phtml b/modules/helpdesk/templates/aside-helpdesk-panel.phtml index c8a3733..056405e 100644 --- a/modules/helpdesk/templates/aside-helpdesk-panel.phtml +++ b/modules/helpdesk/templates/aside-helpdesk-panel.phtml @@ -9,55 +9,85 @@ $canViewRiskRadar = !empty($helpdeskNav['can_view_risk_radar']); $settingsActive = navActive('helpdesk/settings', true); $teamActive = navActive('helpdesk/team', true); $riskRadarActive = navActive('helpdesk/risk-radar', true); +$domainsActive = navActive('helpdesk/domains', true); $customersActive = navActive('helpdesk', true); -if ($settingsActive['isActive'] || $teamActive['isActive'] || $riskRadarActive['isActive']) { +if ($settingsActive['isActive'] || $teamActive['isActive'] || $riskRadarActive['isActive'] || $domainsActive['isActive']) { $customersActive = ['class' => '', 'aria' => '', 'isActive' => false]; } -$helpdeskNavItems = [ +$helpdeskNavGroups = [ [ - 'label' => t('Customers'), - 'path' => 'helpdesk', - 'active' => $customersActive, - 'visible' => true, + 'key' => 'helpdesk-lookup', + 'label' => t('Lookup'), + 'icon' => 'bi-search', + 'items' => [ + [ + 'label' => t('Customers'), + 'path' => 'helpdesk', + 'active' => $customersActive, + 'visible' => true, + ], + [ + 'label' => t('Domains'), + 'path' => 'helpdesk/domains', + 'active' => $domainsActive, + 'visible' => true, + ], + ], ], [ - 'label' => t('Team workload'), - 'path' => 'helpdesk/team', - 'active' => $teamActive, - 'visible' => $canViewTeam, + 'key' => 'helpdesk-monitoring', + 'label' => t('Monitoring'), + 'icon' => 'bi-bar-chart-line', + 'items' => [ + [ + 'label' => t('Team workload'), + 'path' => 'helpdesk/team', + 'active' => $teamActive, + 'visible' => $canViewTeam, + ], + [ + 'label' => t('Risk radar'), + 'path' => 'helpdesk/risk-radar', + 'active' => $riskRadarActive, + 'visible' => $canViewRiskRadar, + ], + ], ], [ - 'label' => t('Risk radar'), - 'path' => 'helpdesk/risk-radar', - 'active' => $riskRadarActive, - 'visible' => $canViewRiskRadar, - ], - [ - 'label' => t('Settings'), - 'path' => 'helpdesk/settings', - 'active' => $settingsActive, - 'visible' => $canManageSettings, + 'key' => 'helpdesk-administration', + 'label' => t('Administration'), + 'icon' => 'bi-sliders', + 'items' => [ + [ + 'label' => t('Settings'), + 'path' => 'helpdesk/settings', + 'active' => $settingsActive, + 'visible' => $canManageSettings, + ], + ], ], ]; - -$visibleItems = array_values(array_filter($helpdeskNavItems, static fn (array $item): bool => !empty($item['visible']))); -$groupIsActive = false; -foreach ($visibleItems as $item) { - $activeState = $item['active'] ?? []; - if (!empty($activeState['isActive'])) { - $groupIsActive = true; - break; - } -} ?> -
    - +
      + !empty($item['visible']))); + if (!$visibleItems) { + continue; + } + $groupIsActive = false; + foreach ($visibleItems as $item) { + if (!empty(($item['active'] ?? [])['isActive'])) { + $groupIsActive = true; + break; + } + } + ?>
    • -
      open> +
      open> - - + +
    • - +
    diff --git a/modules/helpdesk/web/js/pages/helpdesk-domains-index.js b/modules/helpdesk/web/js/pages/helpdesk-domains-index.js new file mode 100644 index 0000000..3a495e9 --- /dev/null +++ b/modules/helpdesk/web/js/pages/helpdesk-domains-index.js @@ -0,0 +1,85 @@ +import { initStandardListPage } from '/js/core/app-grid-factory.js'; +import { readPageConfig } from '/js/core/app-page-config.js'; +import { warnOnce } from '/js/core/app-dom.js'; +import { escapeHtml, getAppBase, withCurrentListReturn } from '/js/pages/app-list-utils.js'; + +const config = readPageConfig('helpdesk-domains'); +if (config) { + const gridjs = window.gridjs; + if (!gridjs) { + warnOnce('UI_INIT_FAIL', 'Helpdesk domains grid init failed: Grid.js missing', { module: 'helpdesk-domains', component: 'grid' }); + } else { + const appBase = getAppBase(); + const labels = config.labels || {}; + + const debitorUrlIndex = 6; + + const gridOptions = { + gridjs, + container: '#helpdesk-domains-grid', + dataUrl: config.dataUrl || 'helpdesk/domains-data', + appBase, + columns: [ + { name: labels.no || 'No.', sort: true }, + { + name: labels.customerName || 'Customer Name', + sort: true, + formatter: (cell) => { + const name = escapeHtml(cell?.name || ''); + const detailUrl = String(cell?.url || '').trim(); + if (detailUrl === '') { + return gridjs.html(name); + } + const href = escapeHtml(withCurrentListReturn(detailUrl)); + return gridjs.html(`${name}`); + }, + }, + { name: labels.url || 'URL', sort: true }, + { name: labels.contractType || 'Contract Type', sort: true }, + { + name: labels.state || 'State', + sort: true, + formatter: (cell) => { + const state = escapeHtml(cell?.state || ''); + const variant = cell?.variant || 'neutral'; + return gridjs.html(`${state}`); + }, + }, + { name: labels.administration || 'Administration', sort: true }, + { name: 'debitor_url', hidden: true }, + ], + sortColumns: ['No', 'Customer_Name', 'URL', 'contract_type', 'State', 'Administration', null], + paginationLimit: 10, + language: config.gridLang || {}, + mapData: (data) => (data.data || []).map((row) => [ + row.No || '', + { name: row.Customer_Name || '', url: row.debitor_url || '' }, + row.URL || '', + row.contract_type || '', + { state: row.State || '', variant: row.state_variant || 'neutral' }, + row.Administration || '', + row.debitor_url || '', + ]), + search: config.gridSearch || null, + filterSchema: config.filterSchema || [], + urlSync: true, + rowInteraction: { linkColumn: 1 }, + rowDblClick: { + getUrl: (rowData) => rowData?.cells?.[debitorUrlIndex]?.data || '', + }, + }; + + const { gridConfig } = initStandardListPage({ + grid: gridOptions, + filters: { + mode: 'drawer', + chipMeta: config.filterChipMeta || {}, + watchInputs: ['#helpdesk-domains-search-input'], + }, + }); + + if (!gridConfig || !gridConfig.grid) { + warnOnce('UI_INIT_FAIL', 'Helpdesk domains grid init failed', { module: 'helpdesk-domains', component: 'grid' }); + } + } +} diff --git a/web/css/layout/app-sidebar.css b/web/css/layout/app-sidebar.css index 7210a03..cde6f9a 100644 --- a/web/css/layout/app-sidebar.css +++ b/web/css/layout/app-sidebar.css @@ -75,6 +75,17 @@ --app-nav-icon-color: #6b7280; } + /* Helpdesk module groups */ + .app-sidebar-admin-group [data-details-key="helpdesk-lookup"] { + --app-nav-icon-color: #2563eb; + } + .app-sidebar-admin-group [data-details-key="helpdesk-monitoring"] { + --app-nav-icon-color: #059669; + } + .app-sidebar-admin-group [data-details-key="helpdesk-administration"] { + --app-nav-icon-color: #6b7280; + } + aside.app-sidebar li:has(> .app-empty-state) { padding-inline: 19px; }