feat(helpdesk): add Domains list page with BC OData contract type enrichment
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) <noreply@anthropic.com>
This commit is contained in:
@@ -345,5 +345,16 @@
|
|||||||
"Upcoming meetings": "Kommende Termine",
|
"Upcoming meetings": "Kommende Termine",
|
||||||
"Past meetings": "Vergangene Termine",
|
"Past meetings": "Vergangene Termine",
|
||||||
"No meetings found for this customer.": "Keine Termine für diesen Kunden gefunden.",
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -345,5 +345,16 @@
|
|||||||
"Upcoming meetings": "Upcoming meetings",
|
"Upcoming meetings": "Upcoming meetings",
|
||||||
"Past meetings": "Past meetings",
|
"Past meetings": "Past meetings",
|
||||||
"No meetings found for this customer.": "No meetings found for this customer.",
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class BcODataGateway
|
|||||||
public const ENTITY_TICKET_LOG_LV = 'PBI_LV_SupportTicketLog';
|
public const ENTITY_TICKET_LOG_LV = 'PBI_LV_SupportTicketLog';
|
||||||
public const ENTITY_CONTRACT_LINES = 'FS_Contract_Lines_Test';
|
public const ENTITY_CONTRACT_LINES = 'FS_Contract_Lines_Test';
|
||||||
public const ENTITY_MEETINGS = 'FS_Debitor_Meetings';
|
public const ENTITY_MEETINGS = 'FS_Debitor_Meetings';
|
||||||
|
public const ENTITY_CONTRACT_DOMAINS = 'FS_Contract_Domains';
|
||||||
|
|
||||||
private const CONNECT_TIMEOUT = 10;
|
private const CONNECT_TIMEOUT = 10;
|
||||||
private const REQUEST_TIMEOUT = 30;
|
private const REQUEST_TIMEOUT = 30;
|
||||||
@@ -825,6 +826,57 @@ class BcODataGateway
|
|||||||
return $this->extractODataValues($response);
|
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<int, array<string, mixed>>
|
||||||
|
*/
|
||||||
|
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<int, array<string, mixed>>
|
||||||
|
*/
|
||||||
|
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.
|
* Get a single ticket by ticket number.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ return [
|
|||||||
|
|
||||||
'routes' => [
|
'routes' => [
|
||||||
['path' => 'helpdesk/search-data', 'target' => 'helpdesk/search-data'],
|
['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/debitor/{id}', 'target' => 'helpdesk/debitor'],
|
||||||
['path' => 'helpdesk/ticket/{id}', 'target' => 'helpdesk/ticket'],
|
['path' => 'helpdesk/ticket/{id}', 'target' => 'helpdesk/ticket'],
|
||||||
['path' => 'helpdesk/settings', 'target' => 'helpdesk/settings'],
|
['path' => 'helpdesk/settings', 'target' => 'helpdesk/settings'],
|
||||||
|
|||||||
157
modules/helpdesk/pages/helpdesk/domains-data().php
Normal file
157
modules/helpdesk/pages/helpdesk/domains-data().php
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use MintyPHP\Module\Helpdesk\HelpdeskAuthorizationPolicy;
|
||||||
|
use MintyPHP\Module\Helpdesk\Service\BcODataGateway;
|
||||||
|
use MintyPHP\Support\Guard;
|
||||||
|
|
||||||
|
Guard::requireLogin();
|
||||||
|
Guard::requireAbilityOrForbidden(HelpdeskAuthorizationPolicy::ABILITY_ACCESS);
|
||||||
|
gridRequireGetRequest();
|
||||||
|
|
||||||
|
$filters = gridParseFiltersFromSchemaFile(__DIR__ . '/domains/filter-schema.php');
|
||||||
|
|
||||||
|
$search = trim((string) ($filters['search'] ?? ''));
|
||||||
|
$customer = trim((string) ($filters['customer'] ?? ''));
|
||||||
|
$contractType = trim((string) ($filters['contract_type'] ?? ''));
|
||||||
|
$state = trim((string) ($filters['state'] ?? ''));
|
||||||
|
$state = $state === 'all' ? '' : $state;
|
||||||
|
$administration = trim((string) ($filters['administration'] ?? ''));
|
||||||
|
$administration = $administration === 'all' ? '' : $administration;
|
||||||
|
$order = (string) ($filters['order'] ?? 'Customer_Name');
|
||||||
|
$dir = (string) ($filters['dir'] ?? 'asc');
|
||||||
|
$limit = (int) ($filters['limit'] ?? 10);
|
||||||
|
$offset = (int) ($filters['offset'] ?? 0);
|
||||||
|
|
||||||
|
$gateway = app(BcODataGateway::class);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$allDomains = $gateway->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);
|
||||||
76
modules/helpdesk/pages/helpdesk/domains/filter-schema.php
Normal file
76
modules/helpdesk/pages/helpdesk/domains/filter-schema.php
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return gridFilterSchema([
|
||||||
|
'query' => [
|
||||||
|
'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],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
62
modules/helpdesk/pages/helpdesk/domains/index().php
Normal file
62
modules/helpdesk/pages/helpdesk/domains/index().php
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use MintyPHP\Buffer;
|
||||||
|
use MintyPHP\Module\Helpdesk\HelpdeskAuthorizationPolicy;
|
||||||
|
use MintyPHP\Module\Helpdesk\Service\HelpdeskSettingsGateway;
|
||||||
|
use MintyPHP\Support\Guard;
|
||||||
|
|
||||||
|
Guard::requireLogin();
|
||||||
|
Guard::requireAbilityOrForbidden(HelpdeskAuthorizationPolicy::ABILITY_ACCESS);
|
||||||
|
|
||||||
|
$request = requestInput();
|
||||||
|
$query = $request->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')],
|
||||||
|
];
|
||||||
51
modules/helpdesk/pages/helpdesk/domains/index(default).phtml
Normal file
51
modules/helpdesk/pages/helpdesk/domains/index(default).phtml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
$toolbarFilterSchema = is_array($toolbarFilterSchema ?? null) ? $toolbarFilterSchema : [];
|
||||||
|
$searchToolbarFilterSchema = is_array($searchToolbarFilterSchema ?? null) ? $searchToolbarFilterSchema : [];
|
||||||
|
$drawerToolbarFilterSchema = is_array($drawerToolbarFilterSchema ?? null) ? $drawerToolbarFilterSchema : [];
|
||||||
|
$toolbarFilterState = is_array($toolbarFilterState ?? null) ? $toolbarFilterState : [];
|
||||||
|
$toolbarOptionSets = is_array($toolbarOptionSets ?? null) ? $toolbarOptionSets : [];
|
||||||
|
$clientFilterSchema = is_array($clientFilterSchema ?? null) ? $clientFilterSchema : [];
|
||||||
|
$searchConfig = is_array($searchConfig ?? null) ? $searchConfig : null;
|
||||||
|
$filterChipMeta = is_array($filterChipMeta ?? null) ? $filterChipMeta : [];
|
||||||
|
$isConfigured = $isConfigured ?? false;
|
||||||
|
?>
|
||||||
|
<?php require templatePath('partials/app-flash.phtml'); ?>
|
||||||
|
|
||||||
|
<?php if (!$isConfigured): ?>
|
||||||
|
<div class="notice" data-variant="warning" role="alert">
|
||||||
|
<p><?php e(t('BC connection is not configured. Please configure the connection in the settings.')); ?></p>
|
||||||
|
<a href="<?php e(lurl('helpdesk/settings')); ?>" role="button" class="secondary outline small">
|
||||||
|
<?php e(t('Open settings')); ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$listTitle = t('Domains');
|
||||||
|
require templatePath('partials/app-list-titlebar.phtml');
|
||||||
|
?>
|
||||||
|
<?php
|
||||||
|
$filterUiNamespace = 'helpdesk-domains';
|
||||||
|
require templatePath('partials/app-list-filters.phtml');
|
||||||
|
?>
|
||||||
|
<div class="app-list-table">
|
||||||
|
<div id="helpdesk-domains-grid"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="<?php e(assetVersion('vendor/gridjs/gridjs.umd.js')); ?>"></script>
|
||||||
|
<script type="application/json" id="page-config-helpdesk-domains"><?php gridJsonForJs([
|
||||||
|
'dataUrl' => lurl('helpdesk/domains-data'),
|
||||||
|
'gridLang' => gridLang(),
|
||||||
|
'gridSearch' => $searchConfig,
|
||||||
|
'filterSchema' => $clientFilterSchema,
|
||||||
|
'filterChipMeta' => $filterChipMeta,
|
||||||
|
'labels' => [
|
||||||
|
'no' => t('No.'),
|
||||||
|
'customerName' => t('Customer Name'),
|
||||||
|
'url' => t('URL'),
|
||||||
|
'state' => t('State'),
|
||||||
|
'administration' => t('Administration'),
|
||||||
|
'contractType' => t('Contract type'),
|
||||||
|
],
|
||||||
|
]); ?></script>
|
||||||
|
<script type="module" src="<?php e(assetVersion('modules/helpdesk/js/pages/helpdesk-domains-index.js')); ?>"></script>
|
||||||
@@ -9,55 +9,85 @@ $canViewRiskRadar = !empty($helpdeskNav['can_view_risk_radar']);
|
|||||||
$settingsActive = navActive('helpdesk/settings', true);
|
$settingsActive = navActive('helpdesk/settings', true);
|
||||||
$teamActive = navActive('helpdesk/team', true);
|
$teamActive = navActive('helpdesk/team', true);
|
||||||
$riskRadarActive = navActive('helpdesk/risk-radar', true);
|
$riskRadarActive = navActive('helpdesk/risk-radar', true);
|
||||||
|
$domainsActive = navActive('helpdesk/domains', true);
|
||||||
$customersActive = navActive('helpdesk', 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];
|
$customersActive = ['class' => '', 'aria' => '', 'isActive' => false];
|
||||||
}
|
}
|
||||||
|
|
||||||
$helpdeskNavItems = [
|
$helpdeskNavGroups = [
|
||||||
[
|
[
|
||||||
'label' => t('Customers'),
|
'key' => 'helpdesk-lookup',
|
||||||
'path' => 'helpdesk',
|
'label' => t('Lookup'),
|
||||||
'active' => $customersActive,
|
'icon' => 'bi-search',
|
||||||
'visible' => true,
|
'items' => [
|
||||||
|
[
|
||||||
|
'label' => t('Customers'),
|
||||||
|
'path' => 'helpdesk',
|
||||||
|
'active' => $customersActive,
|
||||||
|
'visible' => true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => t('Domains'),
|
||||||
|
'path' => 'helpdesk/domains',
|
||||||
|
'active' => $domainsActive,
|
||||||
|
'visible' => true,
|
||||||
|
],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'label' => t('Team workload'),
|
'key' => 'helpdesk-monitoring',
|
||||||
'path' => 'helpdesk/team',
|
'label' => t('Monitoring'),
|
||||||
'active' => $teamActive,
|
'icon' => 'bi-bar-chart-line',
|
||||||
'visible' => $canViewTeam,
|
'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'),
|
'key' => 'helpdesk-administration',
|
||||||
'path' => 'helpdesk/risk-radar',
|
'label' => t('Administration'),
|
||||||
'active' => $riskRadarActive,
|
'icon' => 'bi-sliders',
|
||||||
'visible' => $canViewRiskRadar,
|
'items' => [
|
||||||
],
|
[
|
||||||
[
|
'label' => t('Settings'),
|
||||||
'label' => t('Settings'),
|
'path' => 'helpdesk/settings',
|
||||||
'path' => 'helpdesk/settings',
|
'active' => $settingsActive,
|
||||||
'active' => $settingsActive,
|
'visible' => $canManageSettings,
|
||||||
'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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
?>
|
||||||
<ul class="app-sidebar-admin-nav" aria-label="<?php e(t('Helpdesk')); ?>">
|
<ul>
|
||||||
<?php if ($visibleItems): ?>
|
<?php foreach ($helpdeskNavGroups as $group):
|
||||||
|
$visibleItems = array_values(array_filter($group['items'], static fn (array $item): bool => !empty($item['visible'])));
|
||||||
|
if (!$visibleItems) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$groupIsActive = false;
|
||||||
|
foreach ($visibleItems as $item) {
|
||||||
|
if (!empty(($item['active'] ?? [])['isActive'])) {
|
||||||
|
$groupIsActive = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
<li class="app-sidebar-group app-sidebar-admin-group">
|
<li class="app-sidebar-group app-sidebar-admin-group">
|
||||||
<details data-details-key="helpdesk-navigation"<?php if ($groupIsActive): ?> open<?php endif; ?>>
|
<details data-details-key="<?php e($group['key']); ?>"<?php if ($groupIsActive): ?> open<?php endif; ?>>
|
||||||
<summary>
|
<summary>
|
||||||
<i class="bi bi-headset"></i>
|
<i class="bi <?php e($group['icon']); ?>"></i>
|
||||||
<span><?php e(t('Helpdesk')); ?></span>
|
<span><?php e($group['label']); ?></span>
|
||||||
</summary>
|
</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<?php foreach ($visibleItems as $item):
|
<?php foreach ($visibleItems as $item):
|
||||||
@@ -72,5 +102,5 @@ foreach ($visibleItems as $item) {
|
|||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</li>
|
</li>
|
||||||
<?php endif; ?>
|
<?php endforeach; ?>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
85
modules/helpdesk/web/js/pages/helpdesk-domains-index.js
Normal file
85
modules/helpdesk/web/js/pages/helpdesk-domains-index.js
Normal file
@@ -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(`<a href="${href}">${name}</a>`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ 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(`<span class="badge" data-variant="${escapeHtml(variant)}">${state}</span>`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ 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' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -75,6 +75,17 @@
|
|||||||
--app-nav-icon-color: #6b7280;
|
--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) {
|
aside.app-sidebar li:has(> .app-empty-state) {
|
||||||
padding-inline: 19px;
|
padding-inline: 19px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user