diff --git a/admin.php b/admin.php
index 5041a67..3c85d83 100644
--- a/admin.php
+++ b/admin.php
@@ -171,15 +171,29 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
}
- // 2d. Teamleden toewijzen aan Jira Wachtrijfilter
+ // 2d. Teamleden toewijzen aan Jira Wachtrijfilters (meervoudig)
elseif ($action === 'save_user_jira_assignments') {
$activeTab = 'jira';
- $assignments = $_POST['user_filter'] ?? [];
- if (is_array($assignments)) {
- $upd = $db->prepare("UPDATE users SET default_jira_filter_code = ? WHERE id = ?");
- foreach ($assignments as $uId => $fCode) {
- $fCode = trim($fCode);
- $upd->execute([$fCode !== '' ? $fCode : null, (int)$uId]);
+ $assignments = $_POST['user_filters'] ?? [];
+ $submittedUserIds = isset($_POST['user_ids']) && is_array($_POST['user_ids']) ? array_map('intval', $_POST['user_ids']) : [];
+
+ // Fallback als user_ids niet meegestuurd werd (bv. oude formulier submission met user_filter)
+ if (empty($submittedUserIds) && !empty($_POST['user_filter']) && is_array($_POST['user_filter'])) {
+ $submittedUserIds = array_map('intval', array_keys($_POST['user_filter']));
+ foreach ($_POST['user_filter'] as $uId => $sCode) {
+ $sCode = trim($sCode);
+ $assignments[$uId] = $sCode !== '' ? [$sCode] : [];
+ }
+ }
+
+ if (!empty($submittedUserIds)) {
+ $upd = $db->prepare("UPDATE users SET default_jira_filter_codes = ?, default_jira_filter_code = ? WHERE id = ?");
+ foreach ($submittedUserIds as $uId) {
+ $rawList = isset($assignments[$uId]) && is_array($assignments[$uId]) ? $assignments[$uId] : [];
+ $cleanCodes = array_values(array_unique(array_filter(array_map('trim', $rawList))));
+ $jsonVal = !empty($cleanCodes) ? json_encode($cleanCodes) : null;
+ $firstVal = !empty($cleanCodes) ? $cleanCodes[0] : null;
+ $upd->execute([$jsonVal, $firstVal, (int)$uId]);
}
fociz_log_audit($userId, 'SETTINGS_UPDATED', 'jira_assignments', null, [], 'SUCCESS', $adminUser['email']);
$message = "Standaard Jira wachtrijtoewijzingen voor het team zijn succesvol opgeslagen.";
@@ -511,7 +525,7 @@ try {
$allUsersForJira = [];
$allJiraFilters = [];
try {
- $allUsersForJira = $db->query("SELECT id, name, email, role, avatar_url, default_jira_filter_code FROM users ORDER BY name ASC")->fetchAll() ?: [];
+ $allUsersForJira = $db->query("SELECT id, name, email, role, avatar_url, default_jira_filter_code, default_jira_filter_codes FROM users ORDER BY name ASC")->fetchAll() ?: [];
$allJiraFilters = $db->query("SELECT * FROM jira_queue_filters ORDER BY sort_order ASC, id ASC")->fetchAll() ?: [];
} catch (Throwable $e) {
error_log("Fout bij ophalen jira filters/users: " . $e->getMessage());
@@ -1373,10 +1387,10 @@ require_once __DIR__ . '/includes/header.php';
- Team Wachtrijmatrix: Standaard Wachtrij per Collega
+ Team Wachtrijmatrix: Gekoppelde Wachtrijen per Collega
- Wijs collega's toe aan hun primaire wachtrij (bijv. DX of SD). Wanneer zij de Jira pagina openen, wordt deze wachtrij direct als standaard getoond.
+ Koppel collega's aan één of meerdere wachtrijfilters (bijv. SD, IN, AAN, GEN of DX). Collega's zien in de Jira Hub direct de tickets van hun gekoppelde wachtrijen.
@@ -1393,13 +1407,25 @@ require_once __DIR__ . '/includes/header.php';
Teamlid
E-mailadres
Rol
- Standaard Wachtrijfilter
+ Gekoppelde Wachtrijen (Meervoudig)
-
+
+
@@ -1423,16 +1449,18 @@ require_once __DIR__ . '/includes/header.php';
-
- Geen voorkeur (alles tonen)
-
-
- >
- = htmlspecialchars($f['filter_code']) ?> — = htmlspecialchars($f['filter_name']) ?>
-
-
+
+
+
+ class="w-3.5 h-3.5 rounded text-indigo-600 border-slate-300 dark:border-slate-700 focus:ring-indigo-500 cursor-pointer">
+ = htmlspecialchars($code) ?>
+
-
+
diff --git a/config/database.php b/config/database.php
index dc0e9e7..ac5746f 100644
--- a/config/database.php
+++ b/config/database.php
@@ -162,6 +162,8 @@ function ensure_tables_exist(PDO $pdo): void {
"ALTER TABLE board_card_schedules ADD UNIQUE KEY uk_sched_card (card_id)",
"CREATE TABLE IF NOT EXISTS `password_resets` (`id` INT AUTO_INCREMENT PRIMARY KEY, `email` VARCHAR(191) NOT NULL, `token` VARCHAR(64) NOT NULL UNIQUE, `expires_at` DATETIME NOT NULL, `used_at` DATETIME NULL, `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, INDEX (`email`), INDEX (`token`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS `jira_queue_filters` (`id` INT AUTO_INCREMENT PRIMARY KEY, `filter_code` VARCHAR(50) NOT NULL UNIQUE, `filter_name` VARCHAR(120) NOT NULL, `project_key` VARCHAR(50) NULL, `jql_clause` TEXT NOT NULL, `description` VARCHAR(255) NULL, `is_active` TINYINT(1) NOT NULL DEFAULT 1, `sort_order` INT NOT NULL DEFAULT 0, `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
+ // Jira: Meerdere wachtrijfilters per teamlid
+ "ALTER TABLE users ADD COLUMN default_jira_filter_codes TEXT NULL AFTER default_jira_filter_code",
// V2.0: Subtaken & Checklists
"CREATE TABLE IF NOT EXISTS `task_subtasks` (`id` INT AUTO_INCREMENT PRIMARY KEY, `task_id` INT NOT NULL, `user_id` INT NOT NULL, `title` VARCHAR(255) NOT NULL, `is_completed` TINYINT(1) NOT NULL DEFAULT 0, `position` INT NOT NULL DEFAULT 0, `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX (`task_id`, `position`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
// Beveiliging: IP tracking & locatie-verificatie
@@ -191,30 +193,61 @@ function ensure_tables_exist(PDO $pdo): void {
}
}
- // Zorg dat de standaard Jira Servicedesk wachtrijfilters (DX en SD) aanwezig zijn
+ // Zorg dat de standaard Jira wachtrijfilters (SD, IN, AAN, GEN, DX) aanwezig zijn
try {
- $filterCount = (int)$pdo->query("SELECT COUNT(*) FROM jira_queue_filters")->fetchColumn();
- if ($filterCount === 0) {
- $insertFilterStmt = $pdo->prepare("
- INSERT INTO jira_queue_filters (filter_code, filter_name, project_key, jql_clause, description, is_active, sort_order)
- VALUES (?, ?, ?, ?, ?, 1, ?)
- ");
- $insertFilterStmt->execute([
- 'DX',
- 'DX — Koppelingen, SSO & Migratie',
- 'SD',
- '"Onderwerp logging[dropdown]" in (Koppelingen, SSO-koppeling, Rest-endpoints, Datadump, UWV-koppeling, Migratie)',
- 'Servicedesk tickets met betrekking tot koppelingen, endpoints en datamigraties.',
- 1
- ]);
- $insertFilterStmt->execute([
- 'SD',
- 'SD — Regulier Servicedesk',
- 'SD',
- '("Onderwerp logging[Dropdown]" = EMPTY OR "Onderwerp logging[Dropdown]" != Koppelingen AND "Onderwerp logging[Dropdown]" != Migratie AND "Onderwerp logging[Dropdown]" != SSO-koppeling AND "Onderwerp logging[Dropdown]" != UWV-koppeling AND "Onderwerp logging[Dropdown]" != Rest-endpoints AND "Onderwerp logging[Dropdown]" != Datadump)',
- 'Reguliere servicedesk incidenten, algemene meldingen en gebruikersvragen.',
- 2
- ]);
+ $existingCodes = $pdo->query("SELECT filter_code FROM jira_queue_filters")->fetchAll(PDO::FETCH_COLUMN) ?: [];
+ $defaultFilters = [
+ [
+ 'code' => 'SD',
+ 'name' => 'SD — Servicedesk',
+ 'project' => 'SD',
+ 'jql' => 'project = "SD"',
+ 'desc' => 'Servicedesk supportvragen en operationele meldingen.',
+ 'order' => 1
+ ],
+ [
+ 'code' => 'IN',
+ 'name' => 'IN — Interfaces',
+ 'project' => 'IN',
+ 'jql' => 'project = "IN"',
+ 'desc' => 'Interface-koppelingen, API-endpoints en data-uitwisselingen.',
+ 'order' => 2
+ ],
+ [
+ 'code' => 'AAN',
+ 'name' => 'AAN — Aanvragen',
+ 'project' => 'AAN',
+ 'jql' => 'project = "AAN"',
+ 'desc' => 'Klant- en gebruikersaanvragen en wijzigingsverzoeken.',
+ 'order' => 3
+ ],
+ [
+ 'code' => 'GEN',
+ 'name' => 'GEN — General',
+ 'project' => 'GEN',
+ 'jql' => 'project = "GEN"',
+ 'desc' => 'Algemene taken, organisatie en platformprojecten.',
+ 'order' => 4
+ ],
+ [
+ 'code' => 'DX',
+ 'name' => 'DX — Koppelingen & SSO',
+ 'project' => 'SD',
+ 'jql' => '"Onderwerp logging[dropdown]" in (Koppelingen, SSO-koppeling, Rest-endpoints, Datadump, UWV-koppeling, Migratie)',
+ 'desc' => 'Servicedesk tickets specifiek voor koppelingen en migraties.',
+ 'order' => 5
+ ]
+ ];
+ $insertFilterStmt = $pdo->prepare("
+ INSERT INTO jira_queue_filters (filter_code, filter_name, project_key, jql_clause, description, is_active, sort_order)
+ VALUES (?, ?, ?, ?, ?, 1, ?)
+ ");
+ foreach ($defaultFilters as $df) {
+ if (!in_array($df['code'], $existingCodes)) {
+ $insertFilterStmt->execute([
+ $df['code'], $df['name'], $df['project'], $df['jql'], $df['desc'], $df['order']
+ ]);
+ }
}
} catch (Throwable $e) {
// Stil negeren indien tabel nog in opbouw is
diff --git a/includes/integrations/jira.php b/includes/integrations/jira.php
index 18f6479..af84df2 100644
--- a/includes/integrations/jira.php
+++ b/includes/integrations/jira.php
@@ -226,20 +226,111 @@ function jira_get_queue_filters(bool $onlyActive = true): array {
}
/**
- * Haalt de standaard wachtrijfilter voorkeur van een gebruiker op
+ * Haalt alle gekoppelde wachtrijfilter codes voor een gebruiker op (meervoudig)
*/
-function jira_get_user_queue_preference(int $userId): ?string {
+function jira_get_user_queue_preferences(int $userId): array {
try {
$db = get_db_connection();
- $stmt = $db->prepare("SELECT default_jira_filter_code FROM users WHERE id = ?");
+ $stmt = $db->prepare("SELECT default_jira_filter_codes, default_jira_filter_code FROM users WHERE id = ?");
$stmt->execute([$userId]);
- $res = $stmt->fetchColumn();
- return $res ? (string)$res : null;
+ $row = $stmt->fetch();
+ if (!$row) return [];
+
+ if (!empty($row['default_jira_filter_codes'])) {
+ $decoded = json_decode($row['default_jira_filter_codes'], true);
+ if (is_array($decoded)) {
+ return array_values(array_filter(array_map('strtoupper', array_map('trim', $decoded))));
+ }
+ $parts = explode(',', $row['default_jira_filter_codes']);
+ return array_values(array_filter(array_map('strtoupper', array_map('trim', $parts))));
+ }
+
+ if (!empty($row['default_jira_filter_code'])) {
+ return [strtoupper(trim($row['default_jira_filter_code']))];
+ }
+
+ return [];
} catch (Throwable $e) {
- return null;
+ return [];
}
}
+/**
+ * Haalt de eerste standaard wachtrijfilter voorkeur van een gebruiker op (backwards compatibility)
+ */
+function jira_get_user_queue_preference(int $userId): ?string {
+ $prefs = jira_get_user_queue_preferences($userId);
+ return !empty($prefs) ? $prefs[0] : null;
+}
+
+/**
+ * Haalt alle officiële statussen op uit de Atlassian Jira Cloud instantie
+ */
+function jira_fetch_statuses(string $domain, string $email, string $apiToken): array {
+ $cleanDomain = jira_normalize_domain($domain);
+ if (empty($cleanDomain) || empty($email) || empty($apiToken)) {
+ return [];
+ }
+
+ $cacheKey = "jira_statuses_" . md5($cleanDomain);
+ $cached = fociz_cache_get($cacheKey);
+ if (is_array($cached) && !empty($cached)) {
+ return $cached;
+ }
+
+ $authHeader = jira_get_auth_header($email, $apiToken);
+ $url = $cleanDomain . '/rest/api/3/status';
+
+ $response = fociz_http_request($url, [
+ 'method' => 'GET',
+ 'headers' => [
+ 'Authorization' => $authHeader,
+ 'Accept' => 'application/json',
+ ],
+ 'timeout' => 10,
+ ]);
+
+ if ($response['status'] === 404 || $response['status'] === 405) {
+ $urlV2 = $cleanDomain . '/rest/api/2/status';
+ $response = fociz_http_request($urlV2, [
+ 'method' => 'GET',
+ 'headers' => [
+ 'Authorization' => $authHeader,
+ 'Accept' => 'application/json',
+ ],
+ 'timeout' => 10,
+ ]);
+ }
+
+ $statuses = [];
+ if ($response['status'] === 200 && is_array($response['json'])) {
+ foreach ($response['json'] as $st) {
+ $catKey = strtolower($st['statusCategory']['key'] ?? 'indeterminate');
+ $catName = $st['statusCategory']['name'] ?? 'In uitvoering';
+ $statuses[] = [
+ 'id' => (string)($st['id'] ?? ''),
+ 'name' => $st['name'] ?? '',
+ 'description' => $st['description'] ?? '',
+ 'status_category' => $catKey, // 'new', 'indeterminate', 'done'
+ 'category_name' => $catName,
+ 'color' => $st['statusCategory']['colorName'] ?? '',
+ ];
+ }
+ // Sorteer: To Do eerst, dan In Progress, dan Done
+ usort($statuses, function($a, $b) {
+ $order = ['new' => 1, 'indeterminate' => 2, 'done' => 3];
+ $oa = $order[$a['status_category']] ?? 2;
+ $ob = $order[$b['status_category']] ?? 2;
+ if ($oa !== $ob) return $oa - $ob;
+ return strcasecmp($a['name'], $b['name']);
+ });
+
+ fociz_cache_set($cacheKey, $statuses, 3600);
+ }
+
+ return $statuses;
+}
+
/**
* Haalt issues op via een flexibele JQL query via Atlassian REST API
*/
@@ -464,7 +555,7 @@ function jira_fetch_issues(string $domain, string $email, string $apiToken, stri
/**
* Haalt alle actieve issues op die zijn toegewezen aan de ingelogde Jira gebruiker
*/
-function jira_fetch_assigned_issues(string $domain, string $email, string $apiToken, int $maxResults = 50): array {
+function jira_fetch_assigned_issues(string $domain, string $email, string $apiToken, int $maxResults = 100): array {
$jql = 'assignee = currentUser() ORDER BY updated DESC';
return jira_fetch_issues($domain, $email, $apiToken, $jql, $maxResults);
}
@@ -472,7 +563,7 @@ function jira_fetch_assigned_issues(string $domain, string $email, string $apiTo
/**
* Haalt openstaande team issues op die horen bij de projecten van de gebruiker
*/
-function jira_fetch_team_issues(string $domain, string $email, string $apiToken, int $maxResults = 50): array {
+function jira_fetch_team_issues(string $domain, string $email, string $apiToken, int $maxResults = 100): array {
$jql = 'assignee != currentUser() AND statusCategory != Done ORDER BY updated DESC';
return jira_fetch_issues($domain, $email, $apiToken, $jql, $maxResults);
}
@@ -486,18 +577,29 @@ function jira_get_dashboard_data(int $userId, bool $forceRefresh = false): array
$stmt->execute([$userId]);
$integration = $stmt->fetch();
- // Ophalen van actieve wachtrijfilters en voorkeur van gebruiker
+ // Ophalen van actieve wachtrijfilters en voorkeuren van gebruiker
$queueFilters = jira_get_queue_filters(true);
$userQueuePref = jira_get_user_queue_preference($userId);
+ $userQueuePrefs = jira_get_user_queue_preferences($userId);
// Sandbox / Demo dataset indien niet gekoppeld of expliciet in demomodus
if (!$integration || !empty($integration['is_demo_mode'])) {
+ $demoStatuses = [
+ ['id' => '1', 'name' => 'To Do', 'status_category' => 'new', 'category_name' => 'Nieuw'],
+ ['id' => '2', 'name' => 'Nieuw', 'status_category' => 'new', 'category_name' => 'Nieuw'],
+ ['id' => '3', 'name' => 'In behandeling', 'status_category' => 'indeterminate', 'category_name' => 'In uitvoering'],
+ ['id' => '4', 'name' => 'Wachten op klant', 'status_category' => 'indeterminate', 'category_name' => 'In uitvoering'],
+ ['id' => '5', 'name' => 'In Review', 'status_category' => 'indeterminate', 'category_name' => 'In uitvoering'],
+ ['id' => '6', 'name' => 'Gereed', 'status_category' => 'done', 'category_name' => 'Gereed'],
+ ['id' => '7', 'name' => 'Gesloten', 'status_category' => 'done', 'category_name' => 'Gereed'],
+ ];
+
$assigned = [
[
'key' => 'SD-401',
'summary' => 'AFAS Profit SSO-koppeling geeft 401 fout bij token refresh',
- 'description' => "Gebruikers die via AFAS SSO inloggen krijgen periodiek een HTTP 401 foutcode bij de geautomatiseerde token verversing.\n\nHet endpoint `/oauth/token` reageert met `invalid_grant` zodra de refresh token ouder is dan 24 uur. Er dient een retry-mechanisme met fallback authenticatie ingebouwd te worden.",
- 'status' => 'In Progress',
+ 'description' => "Gebruikers die via AFAS SSO inloggen krijgen periodiek een HTTP 401 foutcode bij de geautomatiseerde token verversing.\n\nHet endpoint `/oauth/token` reageert met `invalid_grant` zodra de refresh token ouder is dan 24 uur.",
+ 'status' => 'In behandeling',
'status_category' => 'indeterminate',
'priority' => 'Highest',
'issue_type' => 'Bug',
@@ -512,12 +614,13 @@ function jira_get_dashboard_data(int $userId, bool $forceRefresh = false): array
'created' => date('Y-m-d H:i:s', strtotime('-1 day')),
'updated' => date('Y-m-d H:i:s', strtotime('-45 minutes')),
'url' => 'https://jira.atlassian.net/browse/SD-401',
+ 'story_points' => 3,
'source' => 'SANDBOX',
],
[
'key' => 'SD-405',
'summary' => 'REST-endpoints voor medewerker synchronisatie beveiligen met rate limiting',
- 'description' => "De openbare REST-endpoints voor medewerkerssynchronisatie dienen conform security richtlijnen te worden voorzien van rate limiting (max 120 calls per minuut per IP).\n\nBij overschrijding moet een duidelijke HTTP 429 Retry-After header worden geretourneerd.",
+ 'description' => "De openbare REST-endpoints voor medewerkerssynchronisatie dienen conform security richtlijnen te worden voorzien van rate limiting.",
'status' => 'To Do',
'status_category' => 'new',
'priority' => 'High',
@@ -533,14 +636,147 @@ function jira_get_dashboard_data(int $userId, bool $forceRefresh = false): array
'created' => date('Y-m-d H:i:s', strtotime('-2 days')),
'updated' => date('Y-m-d H:i:s', strtotime('-2 hours')),
'url' => 'https://jira.atlassian.net/browse/SD-405',
+ 'story_points' => 5,
+ 'source' => 'SANDBOX',
+ ],
+ [
+ 'key' => 'IN-201',
+ 'summary' => 'Exact Online REST API webhook connectie configureren',
+ 'description' => "Configuratie van de webhook triggers voor realtime debiteuren synchronisatie tussen Fociz en Exact Online.",
+ 'status' => 'In behandeling',
+ 'status_category' => 'indeterminate',
+ 'priority' => 'High',
+ 'issue_type' => 'Story',
+ 'project' => 'Interfaces',
+ 'project_key' => 'IN',
+ 'assignee_name' => 'Mijzelf',
+ 'assignee_avatar' => '',
+ 'reporter_name' => 'Financieel Beheer',
+ 'reporter_avatar' => '',
+ 'onderwerp_logging' => 'Koppelingen',
+ 'due_date' => date('Y-m-d', strtotime('+2 days')),
+ 'created' => date('Y-m-d H:i:s', strtotime('-3 days')),
+ 'updated' => date('Y-m-d H:i:s', strtotime('-30 minutes')),
+ 'url' => 'https://jira.atlassian.net/browse/IN-201',
+ 'story_points' => 8,
+ 'source' => 'SANDBOX',
+ ],
+ [
+ 'key' => 'IN-205',
+ 'summary' => 'OAuth 2.0 PKCE handshake voor mobiele koppeling afronden',
+ 'description' => "Migreer de verouderde implicit flow naar de PKCE authorisatie flow voor alle mobiele apparaten.",
+ 'status' => 'To Do',
+ 'status_category' => 'new',
+ 'priority' => 'Medium',
+ 'issue_type' => 'Task',
+ 'project' => 'Interfaces',
+ 'project_key' => 'IN',
+ 'assignee_name' => 'Mijzelf',
+ 'assignee_avatar' => '',
+ 'reporter_name' => 'Tech Lead',
+ 'reporter_avatar' => '',
+ 'onderwerp_logging' => 'SSO-koppeling',
+ 'due_date' => date('Y-m-d', strtotime('+4 days')),
+ 'created' => date('Y-m-d H:i:s', strtotime('-4 days')),
+ 'updated' => date('Y-m-d H:i:s', strtotime('-1 day')),
+ 'url' => 'https://jira.atlassian.net/browse/IN-205',
+ 'story_points' => 5,
+ 'source' => 'SANDBOX',
+ ],
+ [
+ 'key' => 'AAN-102',
+ 'summary' => 'Aanvraag nieuwe laptop en ergonomische thuiswerkplek beeldschermen',
+ 'description' => "Nieuwe onboarding developer heeft MacBook Pro 16 inch en 2x 4K schermen nodig conform standaardpakket.",
+ 'status' => 'Wachten op klant',
+ 'status_category' => 'indeterminate',
+ 'priority' => 'Medium',
+ 'issue_type' => 'Task',
+ 'project' => 'Aanvragen',
+ 'project_key' => 'AAN',
+ 'assignee_name' => 'Mijzelf',
+ 'assignee_avatar' => '',
+ 'reporter_name' => 'HR Onboarding',
+ 'reporter_avatar' => '',
+ 'onderwerp_logging' => 'Hardware',
+ 'due_date' => date('Y-m-d', strtotime('+5 days')),
+ 'created' => date('Y-m-d H:i:s', strtotime('-2 days')),
+ 'updated' => date('Y-m-d H:i:s', strtotime('-3 hours')),
+ 'url' => 'https://jira.atlassian.net/browse/AAN-102',
+ 'story_points' => 2,
+ 'source' => 'SANDBOX',
+ ],
+ [
+ 'key' => 'AAN-108',
+ 'summary' => 'Autorisatieverzoek rechten AFAS financieel beheer teamleider',
+ 'description' => "Goedkeuring benodigd voor toekennen van geautoriseerde rollen in AFAS Profit.",
+ 'status' => 'In behandeling',
+ 'status_category' => 'indeterminate',
+ 'priority' => 'High',
+ 'issue_type' => 'Task',
+ 'project' => 'Aanvragen',
+ 'project_key' => 'AAN',
+ 'assignee_name' => 'Mijzelf',
+ 'assignee_avatar' => '',
+ 'reporter_name' => 'Teamleider Financiën',
+ 'reporter_avatar' => '',
+ 'onderwerp_logging' => 'Rechten',
+ 'due_date' => date('Y-m-d'),
+ 'created' => date('Y-m-d H:i:s', strtotime('-1 day')),
+ 'updated' => date('Y-m-d H:i:s', strtotime('-1 hour')),
+ 'url' => 'https://jira.atlassian.net/browse/AAN-108',
+ 'story_points' => 1,
+ 'source' => 'SANDBOX',
+ ],
+ [
+ 'key' => 'GEN-301',
+ 'summary' => 'Jaarlijkse ISO-27001 interne security review voorbereiden',
+ 'description' => "Samenstellen van security checklists, access reviews en back-up logs voor auditcommissie.",
+ 'status' => 'In behandeling',
+ 'status_category' => 'indeterminate',
+ 'priority' => 'High',
+ 'issue_type' => 'Task',
+ 'project' => 'General',
+ 'project_key' => 'GEN',
+ 'assignee_name' => 'Mijzelf',
+ 'assignee_avatar' => '',
+ 'reporter_name' => 'CISO',
+ 'reporter_avatar' => '',
+ 'onderwerp_logging' => 'Beveiliging',
+ 'due_date' => date('Y-m-d', strtotime('+6 days')),
+ 'created' => date('Y-m-d H:i:s', strtotime('-5 days')),
+ 'updated' => date('Y-m-d H:i:s', strtotime('-4 hours')),
+ 'url' => 'https://jira.atlassian.net/browse/GEN-301',
+ 'story_points' => 13,
+ 'source' => 'SANDBOX',
+ ],
+ [
+ 'key' => 'GEN-305',
+ 'summary' => 'Kantoorruimte vergadertafel opstelling aanpassen voor hybride team',
+ 'description' => "Herinrichting van de videovergaderruimte met Yealink meetingbar.",
+ 'status' => 'Gesloten',
+ 'status_category' => 'done',
+ 'priority' => 'Low',
+ 'issue_type' => 'Task',
+ 'project' => 'General',
+ 'project_key' => 'GEN',
+ 'assignee_name' => 'Mijzelf',
+ 'assignee_avatar' => '',
+ 'reporter_name' => 'Facility Management',
+ 'reporter_avatar' => '',
+ 'onderwerp_logging' => 'Facilitair',
+ 'due_date' => date('Y-m-d', strtotime('-2 days')),
+ 'created' => date('Y-m-d H:i:s', strtotime('-8 days')),
+ 'updated' => date('Y-m-d H:i:s', strtotime('-1 day')),
+ 'url' => 'https://jira.atlassian.net/browse/GEN-305',
+ 'story_points' => 1,
'source' => 'SANDBOX',
],
[
'key' => 'SD-380',
'summary' => 'Wachtwoord reset e-mail komt niet aan bij nieuwe medewerker',
- 'description' => "Een nieuwe medewerker ontvangt geen welkomstmail en wachtwoord reset via het selfservice portaal.\n\nControleer de SMTP-verbinding van de mailserver mail.fociz.nl en controleer of het mailadres in de spamfilter is beland.",
- 'status' => 'In Progress',
- 'status_category' => 'indeterminate',
+ 'description' => "Controleer de SMTP-verbinding van de mailserver mail.fociz.nl en controleer of het mailadres in de spamfilter is beland.",
+ 'status' => 'Gesloten',
+ 'status_category' => 'done',
'priority' => 'High',
'issue_type' => 'Task',
'project' => 'Servicedesk',
@@ -550,136 +786,11 @@ function jira_get_dashboard_data(int $userId, bool $forceRefresh = false): array
'reporter_name' => 'HR Servicedesk',
'reporter_avatar' => '',
'onderwerp_logging' => 'EMPTY',
- 'due_date' => date('Y-m-d'),
- 'created' => date('Y-m-d H:i:s', strtotime('-3 hours')),
- 'updated' => date('Y-m-d H:i:s', strtotime('-1 hour')),
- 'url' => 'https://jira.atlassian.net/browse/SD-380',
- 'source' => 'SANDBOX',
- ],
- [
- 'key' => 'SD-385',
- 'summary' => 'Rechten aanpassen voor rol Projectleider in urenregistratie',
- 'description' => "Projectleiders hebben momenteel alleen leesrechten op ingediende urenstaten. Graag de rol uitbreiden zodat zij declaraties direct kunnen goedkeuren of afkeuren.",
- 'status' => 'To Do',
- 'status_category' => 'new',
- 'priority' => 'Medium',
- 'issue_type' => 'Task',
- 'project' => 'Servicedesk',
- 'project_key' => 'SD',
- 'assignee_name' => 'Mijzelf',
- 'assignee_avatar' => '',
- 'reporter_name' => 'Marianne Willems',
- 'reporter_avatar' => '',
- 'onderwerp_logging' => 'Algemeen',
- 'due_date' => date('Y-m-d', strtotime('+5 days')),
- 'created' => date('Y-m-d H:i:s', strtotime('-3 days')),
- 'updated' => date('Y-m-d H:i:s', strtotime('-1 day')),
- 'url' => 'https://jira.atlassian.net/browse/SD-385',
- 'source' => 'SANDBOX',
- ],
- [
- 'key' => 'FOC-101',
- 'summary' => 'Tweestapsverificatie (2FA TOTP) flow implementeren',
- 'description' => "Ontwikkeling van een complete Google Authenticator / Microsoft Authenticator TOTP verificatie flow inclusief 8 cijferige noodcodes en QR-code weergave in het gebruikersprofiel.",
- 'status' => 'In Progress',
- 'status_category' => 'indeterminate',
- 'priority' => 'High',
- 'issue_type' => 'Story',
- 'project' => 'Fociz Platform',
- 'project_key' => 'FOC',
- 'assignee_name' => 'Mijzelf',
- 'assignee_avatar' => '',
- 'reporter_name' => 'Product Owner',
- 'reporter_avatar' => '',
- 'onderwerp_logging' => 'Beveiliging',
- 'due_date' => date('Y-m-d', strtotime('+2 days')),
- 'created' => date('Y-m-d H:i:s', strtotime('-4 days')),
- 'updated' => date('Y-m-d H:i:s', strtotime('-2 hours')),
- 'url' => 'https://jira.atlassian.net/browse/FOC-101',
- 'source' => 'SANDBOX',
- ],
- [
- 'key' => 'FOC-102',
- 'summary' => 'Interactief whiteboard canvas voor brainstormsessies',
- 'description' => "Gebruikers moeten live post-its kunnen slepen, kleuren en stemmen tijdens kwartaal brainstormsessies.",
- 'status' => 'To Do',
- 'status_category' => 'new',
- 'priority' => 'High',
- 'issue_type' => 'Feature',
- 'project' => 'Fociz Platform',
- 'project_key' => 'FOC',
- 'assignee_name' => 'Mijzelf',
- 'assignee_avatar' => '',
- 'reporter_name' => 'Scrum Master',
- 'reporter_avatar' => '',
- 'onderwerp_logging' => '',
- 'due_date' => date('Y-m-d', strtotime('+4 days')),
- 'created' => date('Y-m-d H:i:s', strtotime('-5 days')),
- 'updated' => date('Y-m-d H:i:s', strtotime('-1 day')),
- 'url' => 'https://jira.atlassian.net/browse/FOC-102',
- 'source' => 'SANDBOX',
- ],
- [
- 'key' => 'FOC-103',
- 'summary' => 'Koppeling met Microsoft Outlook Calendar & Teams Graph API',
- 'description' => "Synchronisatie van dagplanning blokken naar Microsoft 365 Exchange Online agenda met real-time conflictcontrole.",
- 'status' => 'In Review',
- 'status_category' => 'indeterminate',
- 'priority' => 'Medium',
- 'issue_type' => 'Task',
- 'project' => 'Integrations Hub',
- 'project_key' => 'INT',
- 'assignee_name' => 'Mijzelf',
- 'assignee_avatar' => '',
- 'reporter_name' => 'Architect',
- 'reporter_avatar' => '',
- 'onderwerp_logging' => 'SSO-koppeling',
- 'due_date' => date('Y-m-d'),
- 'created' => date('Y-m-d H:i:s', strtotime('-6 days')),
- 'updated' => date('Y-m-d H:i:s', strtotime('-4 hours')),
- 'url' => 'https://jira.atlassian.net/browse/FOC-103',
- 'source' => 'SANDBOX',
- ],
- [
- 'key' => 'FOC-108',
- 'summary' => 'CSS scrollbar glitch oplossen in Kanban lane op Firefox',
- 'description' => "Op Windows Firefox versies 128+ verspringt de horizontale scrollbalk bij het slepen van kaarten tussen kolommen.",
- 'status' => 'In Progress',
- 'status_category' => 'indeterminate',
- 'priority' => 'Highest',
- 'issue_type' => 'Bug',
- 'project' => 'UI/UX Redesign',
- 'project_key' => 'UI',
- 'assignee_name' => 'Mijzelf',
- 'assignee_avatar' => '',
- 'reporter_name' => 'QA Lead',
- 'reporter_avatar' => '',
- 'onderwerp_logging' => '',
- 'due_date' => date('Y-m-d', strtotime('+1 day')),
- 'created' => date('Y-m-d H:i:s', strtotime('-2 days')),
- 'updated' => date('Y-m-d H:i:s', strtotime('-30 minutes')),
- 'url' => 'https://jira.atlassian.net/browse/FOC-108',
- 'source' => 'SANDBOX',
- ],
- [
- 'key' => 'FOC-112',
- 'summary' => 'Avatar upload beveiliging en compressie optimalisatie',
- 'description' => "Server-side image re-encoding en EXIF stripping voor geüploade gebruikersavatars.",
- 'status' => 'Done',
- 'status_category' => 'done',
- 'priority' => 'Medium',
- 'issue_type' => 'Task',
- 'project' => 'Security',
- 'project_key' => 'SEC',
- 'assignee_name' => 'Mijzelf',
- 'assignee_avatar' => '',
- 'reporter_name' => 'SecOps',
- 'reporter_avatar' => '',
- 'onderwerp_logging' => '',
'due_date' => date('Y-m-d', strtotime('-1 day')),
- 'created' => date('Y-m-d H:i:s', strtotime('-7 days')),
- 'updated' => date('Y-m-d H:i:s', strtotime('-5 hours')),
- 'url' => 'https://jira.atlassian.net/browse/FOC-112',
+ 'created' => date('Y-m-d H:i:s', strtotime('-3 days')),
+ 'updated' => date('Y-m-d H:i:s', strtotime('-6 hours')),
+ 'url' => 'https://jira.atlassian.net/browse/SD-380',
+ 'story_points' => 2,
'source' => 'SANDBOX',
],
];
@@ -688,8 +799,8 @@ function jira_get_dashboard_data(int $userId, bool $forceRefresh = false): array
[
'key' => 'SD-408',
'summary' => 'UWV-koppeling polisadministratie import loopt vast op datumformaat',
- 'description' => "De XML batch import vanuit het UWV portaal faalt op records met afwijkende geboortedatumnotatie.\n\nDe parser dient flexibel zowel ISO-8601 (YYYY-MM-DD) als traditioneel formaat (DD-MM-YYYY) te accepteren.",
- 'status' => 'In Progress',
+ 'description' => "De XML batch import vanuit het UWV portaal faalt op records met afwijkende geboortedatumnotatie.",
+ 'status' => 'In behandeling',
'status_category' => 'indeterminate',
'priority' => 'High',
'issue_type' => 'Bug',
@@ -704,6 +815,7 @@ function jira_get_dashboard_data(int $userId, bool $forceRefresh = false): array
'created' => date('Y-m-d H:i:s', strtotime('-2 days')),
'updated' => date('Y-m-d H:i:s', strtotime('-1 hour')),
'url' => 'https://jira.atlassian.net/browse/SD-408',
+ 'story_points' => 5,
'source' => 'SANDBOX',
],
[
@@ -725,39 +837,19 @@ function jira_get_dashboard_data(int $userId, bool $forceRefresh = false): array
'created' => date('Y-m-d H:i:s', strtotime('-3 days')),
'updated' => date('Y-m-d H:i:s', strtotime('-4 hours')),
'url' => 'https://jira.atlassian.net/browse/SD-412',
+ 'story_points' => 3,
'source' => 'SANDBOX',
],
[
- 'key' => 'SD-416',
- 'summary' => 'Migratie van oude klantportaal database naar MariaDB 11 cluster',
- 'description' => "Voorbereiden en uitvoeren van de dataschemamigratie van het oude portaal naar de nieuwe MariaDB 11 clusteromgeving inclusief regressietests.",
- 'status' => 'To Do',
- 'status_category' => 'new',
- 'priority' => 'High',
- 'issue_type' => 'Story',
- 'project' => 'Servicedesk',
- 'project_key' => 'SD',
- 'assignee_name' => 'Bram Meijer',
- 'assignee_avatar' => '',
- 'reporter_name' => 'Lead Engineer',
- 'reporter_avatar' => '',
- 'onderwerp_logging' => 'Migratie',
- 'due_date' => date('Y-m-d', strtotime('+8 days')),
- 'created' => date('Y-m-d H:i:s', strtotime('-4 days')),
- 'updated' => date('Y-m-d H:i:s', strtotime('-6 hours')),
- 'url' => 'https://jira.atlassian.net/browse/SD-416',
- 'source' => 'SANDBOX',
- ],
- [
- 'key' => 'SD-420',
+ 'key' => 'IN-210',
'summary' => 'Koppelingen API webhook notificaties afleveren met retry-mechanisme',
- 'description' => "Webhooks die naar externe partnersystemen worden verzonden moeten bij een tijdelijke 5xx fout automatisch 3x opnieuw worden aangeboden (exponential backoff).",
- 'status' => 'In Review',
+ 'description' => "Webhooks die naar externe partnersystemen worden verzonden moeten bij een tijdelijke 5xx fout automatisch 3x opnieuw worden aangeboden.",
+ 'status' => 'In behandeling',
'status_category' => 'indeterminate',
'priority' => 'Medium',
'issue_type' => 'Task',
- 'project' => 'Servicedesk',
- 'project_key' => 'SD',
+ 'project' => 'Interfaces',
+ 'project_key' => 'IN',
'assignee_name' => 'Sophie de Vries',
'assignee_avatar' => '',
'reporter_name' => 'Integratie Specialist',
@@ -766,112 +858,118 @@ function jira_get_dashboard_data(int $userId, bool $forceRefresh = false): array
'due_date' => date('Y-m-d', strtotime('+4 days')),
'created' => date('Y-m-d H:i:s', strtotime('-5 days')),
'updated' => date('Y-m-d H:i:s', strtotime('-3 hours')),
- 'url' => 'https://jira.atlassian.net/browse/SD-420',
+ 'url' => 'https://jira.atlassian.net/browse/IN-210',
+ 'story_points' => 5,
'source' => 'SANDBOX',
],
[
- 'key' => 'SD-392',
- 'summary' => 'Outlook Agenda synchronisatie toont dubbele afspraken op mobiel',
- 'description' => "Bij enkele gebruikers met de iOS Outlook app worden terugkerende agenda-afspraken tweemaal weergegeven na het synchroniseren.",
- 'status' => 'In Progress',
+ 'key' => 'IN-215',
+ 'summary' => 'Specificatie documentatie voor externe partner API opvragen',
+ 'description' => "Opvragen van de actuele OpenAPI v3 JSON specificatie bij leverancier.",
+ 'status' => 'Wachten op klant',
'status_category' => 'indeterminate',
- 'priority' => 'Medium',
- 'issue_type' => 'Bug',
- 'project' => 'Servicedesk',
- 'project_key' => 'SD',
- 'assignee_name' => 'Lisa Bakker',
- 'assignee_avatar' => '',
- 'reporter_name' => 'Servicedesk Support',
- 'reporter_avatar' => '',
- 'onderwerp_logging' => 'Incident',
- 'due_date' => date('Y-m-d', strtotime('+3 days')),
- 'created' => date('Y-m-d H:i:s', strtotime('-3 days')),
- 'updated' => date('Y-m-d H:i:s', strtotime('-2 hours')),
- 'url' => 'https://jira.atlassian.net/browse/SD-392',
- 'source' => 'SANDBOX',
- ],
- [
- 'key' => 'SD-399',
- 'summary' => 'Vraag over exportmogelijkheden naar Excel rapportage',
- 'description' => "Klant informeert of het mogelijk is om een wekelijkse CSV / Excel export van de urenstaten automatisch per mail te ontvangen.",
- 'status' => 'To Do',
- 'status_category' => 'new',
'priority' => 'Low',
'issue_type' => 'Task',
- 'project' => 'Servicedesk',
- 'project_key' => 'SD',
+ 'project' => 'Interfaces',
+ 'project_key' => 'IN',
+ 'assignee_name' => 'Sophie de Vries',
+ 'assignee_avatar' => '',
+ 'reporter_name' => 'Partner Manager',
+ 'reporter_avatar' => '',
+ 'onderwerp_logging' => 'Koppelingen',
+ 'due_date' => date('Y-m-d', strtotime('+7 days')),
+ 'created' => date('Y-m-d H:i:s', strtotime('-4 days')),
+ 'updated' => date('Y-m-d H:i:s', strtotime('-2 hours')),
+ 'url' => 'https://jira.atlassian.net/browse/IN-215',
+ 'story_points' => 1,
+ 'source' => 'SANDBOX',
+ ],
+ [
+ 'key' => 'AAN-110',
+ 'summary' => 'Aanvraag extra Microsoft 365 E5 licenties voor marketing team',
+ 'description' => "Bestelling en activering van 5 aanvullende E5 licenties voor het datateam.",
+ 'status' => 'To Do',
+ 'status_category' => 'new',
+ 'priority' => 'Medium',
+ 'issue_type' => 'Task',
+ 'project' => 'Aanvragen',
+ 'project_key' => 'AAN',
'assignee_name' => 'Thomas Jansen',
'assignee_avatar' => '',
- 'reporter_name' => 'Financieel Directeur',
+ 'reporter_name' => 'Marketing Director',
'reporter_avatar' => '',
- 'onderwerp_logging' => 'Vraag',
- 'due_date' => date('Y-m-d', strtotime('+10 days')),
- 'created' => date('Y-m-d H:i:s', strtotime('-5 days')),
- 'updated' => date('Y-m-d H:i:s', strtotime('-1 day')),
- 'url' => 'https://jira.atlassian.net/browse/SD-399',
- 'source' => 'SANDBOX',
- ],
- [
- 'key' => 'FOC-95',
- 'summary' => 'Sessie timeout notificatie verschijnt niet tijdig bij inactieve tab',
- 'description' => "Wanneer een gebruiker de browser 2 uur niet aanraakt, vervalt de PHP sessie zonder dat het login-dialoogvenster netjes openspringt.",
- 'status' => 'In Progress',
- 'status_category' => 'indeterminate',
- 'priority' => 'Highest',
- 'issue_type' => 'Bug',
- 'project' => 'Fociz Platform',
- 'project_key' => 'FOC',
- 'assignee_name' => 'Lisa Bakker',
- 'assignee_avatar' => '',
- 'reporter_name' => 'Product QA',
- 'reporter_avatar' => '',
- 'onderwerp_logging' => '',
- 'due_date' => date('Y-m-d', strtotime('+1 day')),
- 'created' => date('Y-m-d H:i:s', strtotime('-2 days')),
- 'updated' => date('Y-m-d H:i:s', strtotime('-1 hour')),
- 'url' => 'https://jira.atlassian.net/browse/FOC-95',
- 'source' => 'SANDBOX',
- ],
- [
- 'key' => 'FOC-98',
- 'summary' => 'Automatische e-mail triggers voor taakdeadlines en uitnodigingen',
- 'description' => "Cronjob instellen die 24 uur voor de deadline van een taak een herinneringsmail stuurt via SMTP.",
- 'status' => 'To Do',
- 'status_category' => 'new',
- 'priority' => 'High',
- 'issue_type' => 'Story',
- 'project' => 'Notifications',
- 'project_key' => 'NOTIF',
- 'assignee_name' => 'Daan van Dijk',
- 'assignee_avatar' => '',
- 'reporter_name' => 'Operations',
- 'reporter_avatar' => '',
- 'onderwerp_logging' => '',
+ 'onderwerp_logging' => 'Licenties',
'due_date' => date('Y-m-d', strtotime('+3 days')),
- 'created' => date('Y-m-d H:i:s', strtotime('-3 days')),
- 'updated' => date('Y-m-d H:i:s', strtotime('-2 days')),
- 'url' => 'https://jira.atlassian.net/browse/FOC-98',
+ 'created' => date('Y-m-d H:i:s', strtotime('-2 days')),
+ 'updated' => date('Y-m-d H:i:s', strtotime('-1 day')),
+ 'url' => 'https://jira.atlassian.net/browse/AAN-110',
+ 'story_points' => 2,
'source' => 'SANDBOX',
],
[
- 'key' => 'FOC-110',
- 'summary' => 'Real-time websocket synchronisatie voor gelijktijdig bewerken van borden',
- 'description' => "Voorkom overschrijfconflicten wanneer meerdere collega's gelijktijdig kaarten verplaatsen op hetzelfde Kanban bord.",
- 'status' => 'To Do',
- 'status_category' => 'new',
+ 'key' => 'AAN-115',
+ 'summary' => 'Aanvraag tijdelijke VPN toegang externe auditor KPMG',
+ 'description' => "Toegang configureren met verloopdatum 30 september en MFA verplichting.",
+ 'status' => 'Gesloten',
+ 'status_category' => 'done',
'priority' => 'High',
- 'issue_type' => 'Story',
- 'project' => 'Realtime Engine',
- 'project_key' => 'RT',
+ 'issue_type' => 'Task',
+ 'project' => 'Aanvragen',
+ 'project_key' => 'AAN',
'assignee_name' => 'Bram Meijer',
'assignee_avatar' => '',
- 'reporter_name' => 'Architect',
+ 'reporter_name' => 'Security Officer',
'reporter_avatar' => '',
- 'onderwerp_logging' => '',
- 'due_date' => date('Y-m-d', strtotime('+6 days')),
+ 'onderwerp_logging' => 'Rechten',
+ 'due_date' => date('Y-m-d', strtotime('-3 days')),
+ 'created' => date('Y-m-d H:i:s', strtotime('-7 days')),
+ 'updated' => date('Y-m-d H:i:s', strtotime('-2 days')),
+ 'url' => 'https://jira.atlassian.net/browse/AAN-115',
+ 'story_points' => 2,
+ 'source' => 'SANDBOX',
+ ],
+ [
+ 'key' => 'GEN-310',
+ 'summary' => 'Organisatiebeleid hybride werken 2026 actualiseren',
+ 'description' => "Aanpassing van reiskostenvergoeding en thuiswerkvergoeding conform nieuwe cao-afspraken.",
+ 'status' => 'In behandeling',
+ 'status_category' => 'indeterminate',
+ 'priority' => 'Medium',
+ 'issue_type' => 'Story',
+ 'project' => 'General',
+ 'project_key' => 'GEN',
+ 'assignee_name' => 'Lisa Bakker',
+ 'assignee_avatar' => '',
+ 'reporter_name' => 'Directie',
+ 'reporter_avatar' => '',
+ 'onderwerp_logging' => 'Algemeen',
+ 'due_date' => date('Y-m-d', strtotime('+10 days')),
+ 'created' => date('Y-m-d H:i:s', strtotime('-6 days')),
+ 'updated' => date('Y-m-d H:i:s', strtotime('-1 day')),
+ 'url' => 'https://jira.atlassian.net/browse/GEN-310',
+ 'story_points' => 5,
+ 'source' => 'SANDBOX',
+ ],
+ [
+ 'key' => 'GEN-315',
+ 'summary' => 'Kwartaalplanning en doelstellingen inventarisatie Q4',
+ 'description' => "Verzamel OKR's van alle teams voor de centrale kwartaalpresentatie.",
+ 'status' => 'To Do',
+ 'status_category' => 'new',
+ 'priority' => 'High',
+ 'issue_type' => 'Story',
+ 'project' => 'General',
+ 'project_key' => 'GEN',
+ 'assignee_name' => 'Bram Meijer',
+ 'assignee_avatar' => '',
+ 'reporter_name' => 'Operations Lead',
+ 'reporter_avatar' => '',
+ 'onderwerp_logging' => 'Algemeen',
+ 'due_date' => date('Y-m-d', strtotime('+8 days')),
'created' => date('Y-m-d H:i:s', strtotime('-4 days')),
- 'updated' => date('Y-m-d H:i:s', strtotime('-6 hours')),
- 'url' => 'https://jira.atlassian.net/browse/FOC-110',
+ 'updated' => date('Y-m-d H:i:s', strtotime('-5 hours')),
+ 'url' => 'https://jira.atlassian.net/browse/GEN-315',
+ 'story_points' => 8,
'source' => 'SANDBOX',
],
];
@@ -887,6 +985,8 @@ function jira_get_dashboard_data(int $userId, bool $forceRefresh = false): array
'team_issues' => $team,
'queue_filters' => $queueFilters,
'default_queue_filter' => $userQueuePref,
+ 'user_queue_filters' => $userQueuePrefs,
+ 'available_statuses' => $demoStatuses,
];
}
@@ -921,10 +1021,12 @@ function jira_get_dashboard_data(int $userId, bool $forceRefresh = false): array
}
}
- // Ophalen van toegewezen taken
- $assignedRes = jira_fetch_assigned_issues($domain, $email, $apiToken, 50);
- // Ophalen van teamtaken
- $teamRes = jira_fetch_team_issues($domain, $email, $apiToken, 50);
+ // Ophalen van toegewezen taken (max 100)
+ $assignedRes = jira_fetch_assigned_issues($domain, $email, $apiToken, 100);
+ // Ophalen van teamtaken (max 100)
+ $teamRes = jira_fetch_team_issues($domain, $email, $apiToken, 100);
+ // Ophalen van Jira statussen
+ $availableStatuses = jira_fetch_statuses($domain, $email, $apiToken);
if (!$assignedRes['success'] && !$teamRes['success']) {
return [
@@ -950,6 +1052,8 @@ function jira_get_dashboard_data(int $userId, bool $forceRefresh = false): array
'team_issues' => $teamIssues,
'queue_filters' => $queueFilters,
'default_queue_filter' => $userQueuePref,
+ 'user_queue_filters' => $userQueuePrefs,
+ 'available_statuses' => $availableStatuses,
];
fociz_cache_set($cacheKey, $data, 60);
diff --git a/jira.php b/jira.php
index bfc4c05..c6fadcc 100644
--- a/jira.php
+++ b/jira.php
@@ -80,12 +80,24 @@ foreach ($allIssues as $iss) {
$allStatuses[$iss['status']] = $iss['status'];
}
}
+
+// Statussen uitbreiden met officiële Jira instantie statussen
+$availableStatuses = $jiraData['available_statuses'] ?? [];
+if (!empty($availableStatuses)) {
+ foreach ($availableStatuses as $st) {
+ if (!empty($st['name'])) {
+ $allStatuses[$st['name']] = $st['name'];
+ }
+ }
+}
+
ksort($allProjects);
ksort($allTypes);
ksort($allStatuses);
$queueFilters = $jiraData['queue_filters'] ?? jira_get_queue_filters(true);
$defaultQueueFilter = $jiraData['default_queue_filter'] ?? jira_get_user_queue_preference($userId);
+$userQueueFilters = $jiraData['user_queue_filters'] ?? jira_get_user_queue_preferences($userId);
$jiraDataJson = json_encode($jiraData, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
$boardsJson = json_encode($boards, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
@@ -252,97 +264,11 @@ $boardsJson = json_encode($boards, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT
-
+
-
-
-
- Jira Projecten
-
-
= count($allProjects) ?> projecten
-
-
-
-
- Alle Projecten
- = count($allIssues) ?>
-
- $pData): ?>
-
-
- = htmlspecialchars($pData['key']) ?>
-
-
-
- = htmlspecialchars($pName) ?>
- = $projectCounts[$pName] ?? 0 ?>
-
-
-
-
-
-
-
-
-
-
- Servicedesk Wachtrijen
-
-
- Jouw voorkeur: = htmlspecialchars($defaultQueueFilter) ?>
-
-
-
-
-
-
-
-
-
-
+
- Project
-
- Alle projecten
- $pData): ?>
- = htmlspecialchars($pName) ?>
-
-
-
-
-
- Type
+ Issue Type
Alle typen
@@ -362,20 +288,10 @@ $boardsJson = json_encode($boards, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT
-
- Status
-
- Alle statussen
-
- = htmlspecialchars($s) ?>
-
-
-
-
- Herstel Filters
+ Herstel Alle Filters
@@ -384,62 +300,227 @@ $boardsJson = json_encode($boards, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT
-
-
-
-
-
-
- Aan mij toegewezen
-
- = count($assignedIssues) ?>
-
-
+
+
+
+
+
+
+
+
+
+
+
Projecten
+
Direct filteren op projectcode
+
+
-
-
- Teams & Projecten
-
- = count($teamIssues) ?>
-
-
+
+
+
+ Alle Projecten
+ = count($allIssues) ?>
+
-
-
- In Uitvoering
-
- 0
-
-
+
+
+ SD
+ Servicedesk
+ 0
+
-
-
- Recent Afgerond
-
- 0
-
-
+
+
+ IN
+ Interfaces
+ 0
+
+
+
+
+ AAN
+ Aanvragen
+ 0
+
+
+
+
+ GEN
+ General
+ 0
+
+
+ $pData):
+ $pKeyNorm = strtoupper($pKey);
+ $pDataKeyNorm = strtoupper($pData['key'] ?? '');
+ if (in_array($pKeyNorm, ['SD', 'IN', 'AAN', 'GEN']) || in_array($pDataKeyNorm, ['SD', 'IN', 'AAN', 'GEN'])) continue;
+ $displayKey = !empty($pData['key']) ? $pData['key'] : $pKey;
+ ?>
+
+ = htmlspecialchars($displayKey) ?>
+ = htmlspecialchars($pData['name']) ?>
+ = $projectCounts[$pKey] ?? 0 ?>
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
Wachtrij
+
Filter op gekoppelde wachtrijen
+
+
+
+
+
+
+
+
+
+
+
+
+ Aan mij
+
+ = count($assignedIssues) ?>
+
+
+
+
+
+ Team & Projecten
+
+ = count($teamIssues) ?>
+
+
+
+
+
+ In Uitvoering
+
+ 0
+
+
+
+
+
+ Afgerond
+
+ 0
+
-
-
0 tickets
+
+
+
+
+
+
+ Verberg Gesloten
+
+
+
+
+
+
+ In behandeling
+
+
+
+
+
+ Alle statussen
+
+ To Do
+ Nieuw
+
+
+ In behandeling
+ In Progress
+ Wachten op klant
+ In Review
+
+
+ Gereed
+ Gesloten
+ Done
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -794,11 +875,24 @@ const availableBoards = = $boardsJson ?>;
let currentTab = 'assigned'; // 'assigned', 'team', 'in_progress', 'done'
let currentProject = '';
-let currentQueue = jiraData.default_queue_filter || '';
+let currentQueue = (jiraData.user_queue_filters && jiraData.user_queue_filters.length > 0) ? 'MY_QUEUES' : (jiraData.default_queue_filter || '');
+let hideClosed = true; // Standaard gesloten tickets verbergen conform gebruikerswens!
+let onlyInProgress = false;
let activeUrgentOnly = false;
let expandedKeys = new Set();
const ticketStore = new Map();
+function isDoneStatus(st) {
+ const s = (st || '').toLowerCase().trim();
+ return s.includes('done') || s.includes('gesloten') || s.includes('afgerond') || s.includes('gereed') || s.includes('closed') || s.includes('resolved');
+}
+
+function isInProgressStatus(st, sc) {
+ const s = (st || '').toLowerCase().trim();
+ const c = (sc || '').toLowerCase().trim();
+ return c === 'indeterminate' || s.includes('progress') || s.includes('bezig') || s.includes('review') || s.includes('uitvoering') || s.includes('behandeling') || s.includes('wachten');
+}
+
// Bereken statussen en KPI statistieken
function calculateKpis() {
const assigned = jiraData.assigned_issues || [];
@@ -814,10 +908,10 @@ function calculateKpis() {
const sc = (iss.status_category || '').toLowerCase();
const p = (iss.priority || '').toLowerCase();
- if (s.includes('progress') || s.includes('bezig') || s.includes('review') || sc === 'indeterminate') {
+ if (isInProgressStatus(s, sc)) {
inProgressCount++;
}
- if (s.includes('done') || s.includes('afgerond') || s.includes('gesloten') || sc === 'done') {
+ if (isDoneStatus(s) || sc === 'done') {
doneCount++;
}
if (p === 'highest' || p === 'high') {
@@ -825,15 +919,28 @@ function calculateKpis() {
}
});
- document.getElementById('statAssignedCount').innerText = assigned.length;
- document.getElementById('statTeamCount').innerText = team.length;
- document.getElementById('statInProgressCount').innerText = inProgressCount;
- document.getElementById('statHighPriorityCount').innerText = highPriorityCount;
+ const elStatAssigned = document.getElementById('statAssignedCount');
+ const elStatTeam = document.getElementById('statTeamCount');
+ const elStatInProg = document.getElementById('statInProgressCount');
+ const elStatHigh = document.getElementById('statHighPriorityCount');
- document.getElementById('tabCount_assigned').innerText = assigned.length;
- document.getElementById('tabCount_team').innerText = team.length;
- document.getElementById('tabCount_in_progress').innerText = inProgressCount;
- document.getElementById('tabCount_done').innerText = doneCount;
+ if (elStatAssigned) elStatAssigned.innerText = assigned.length;
+ if (elStatTeam) elStatTeam.innerText = team.length;
+ if (elStatInProg) elStatInProg.innerText = inProgressCount;
+ if (elStatHigh) elStatHigh.innerText = highPriorityCount;
+
+ const elTabAssigned = document.getElementById('tabCount_assigned');
+ const elTabTeam = document.getElementById('tabCount_team');
+ const elTabInProg = document.getElementById('tabCount_in_progress');
+ const elTabDone = document.getElementById('tabCount_done');
+
+ if (elTabAssigned) elTabAssigned.innerText = assigned.length;
+ if (elTabTeam) elTabTeam.innerText = team.length;
+ if (elTabInProg) elTabInProg.innerText = inProgressCount;
+ if (elTabDone) elTabDone.innerText = doneCount;
+
+ updateProjectCounts();
+ updateQueueCounts();
}
// Uitklappen en inklappen van de filters & statistieken drawer
@@ -861,10 +968,11 @@ function updateActiveFilterBadge() {
let activeCount = 0;
if (currentProject) activeCount++;
- if (currentQueue) activeCount++;
+ if (currentQueue && currentQueue !== 'MY_QUEUES') activeCount++;
if (document.getElementById('filterType')?.value) activeCount++;
if (document.getElementById('filterPriority')?.value) activeCount++;
if (document.getElementById('filterStatus')?.value) activeCount++;
+ if (onlyInProgress) activeCount++;
if (activeCount > 0) {
badge.innerText = activeCount;
@@ -885,57 +993,122 @@ function switchTab(tab) {
const btn = document.getElementById(`tabBtn_${t}`);
if (!btn) return;
if (t === tab) {
- btn.className = "flex items-center space-x-1.5 px-3.5 py-2 text-xs font-bold rounded-xl transition cursor-pointer bg-indigo-50 dark:bg-indigo-950/50 text-indigo-600 dark:text-indigo-400 border border-indigo-200 dark:border-indigo-800/60 flex-shrink-0";
+ btn.className = "flex items-center space-x-1.5 px-3 py-1.5 text-xs font-bold rounded-xl transition cursor-pointer bg-indigo-50 dark:bg-indigo-950/50 text-indigo-600 dark:text-indigo-400 border border-indigo-200 dark:border-indigo-800/60 flex-shrink-0";
} else {
- btn.className = "flex items-center space-x-1.5 px-3.5 py-2 text-xs font-bold rounded-xl transition cursor-pointer text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white hover:bg-slate-100 dark:hover:bg-slate-800/60 border border-transparent flex-shrink-0";
+ btn.className = "flex items-center space-x-1.5 px-3 py-1.5 text-xs font-bold rounded-xl transition cursor-pointer text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white hover:bg-slate-100 dark:hover:bg-slate-800/60 border border-transparent flex-shrink-0";
}
});
- updateQueueCounts();
+ applyFilters();
+}
+
+// Toggle: Verberg Gesloten
+function toggleHideClosed() {
+ hideClosed = !hideClosed;
+ updateHideClosedButton();
+ applyFilters();
+}
+
+function updateHideClosedButton() {
+ const btn = document.getElementById('btnHideClosed');
+ if (!btn) return;
+ if (hideClosed) {
+ btn.className = "flex items-center space-x-1.5 px-2.5 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-emerald-50 dark:bg-emerald-950/40 text-emerald-700 dark:text-emerald-300 border border-emerald-300 dark:border-emerald-800/80 shadow-2xs";
+ btn.innerHTML = `Verberg Gesloten `;
+ } else {
+ btn.className = "flex items-center space-x-1.5 px-2.5 py-1.5 rounded-xl text-xs font-semibold transition cursor-pointer bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 hover:bg-slate-200 dark:hover:bg-slate-700 border border-slate-200 dark:border-slate-700";
+ btn.innerHTML = `Toon Ook Gesloten `;
+ }
+ if (typeof lucide !== 'undefined') lucide.createIcons();
+}
+
+// Toggle: Snelfilter Alleen In Behandeling
+function toggleOnlyInProgress() {
+ onlyInProgress = !onlyInProgress;
+ updateOnlyInProgressButton();
+ applyFilters();
+}
+
+function updateOnlyInProgressButton() {
+ const btn = document.getElementById('btnOnlyInProgress');
+ if (!btn) return;
+ if (onlyInProgress) {
+ btn.className = "flex items-center space-x-1.5 px-2.5 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-blue-600 text-white shadow-xs";
+ btn.innerHTML = `In behandeling (Actief) `;
+ } else {
+ btn.className = "flex items-center space-x-1.5 px-2.5 py-1.5 rounded-xl text-xs font-semibold transition cursor-pointer bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700 border border-slate-200 dark:border-slate-700";
+ btn.innerHTML = `In behandeling `;
+ }
+ if (typeof lucide !== 'undefined') lucide.createIcons();
+}
+
+// Status dropdown verandering
+function onStatusSelectChange(val) {
+ if (val && isDoneStatus(val)) {
+ hideClosed = false;
+ updateHideClosedButton();
+ }
applyFilters();
}
// Project selectie via pill tabs
function selectProject(proj) {
- currentProject = proj;
- const pSelect = document.getElementById('filterProject');
- if (pSelect) {
- pSelect.value = proj;
- }
+ currentProject = (proj || '').trim();
updateProjectPills();
- updateQueueCounts();
- applyFilters();
-}
-
-// Project selectie via dropdown
-function onProjectSelectChange(proj) {
- currentProject = proj;
- updateProjectPills();
- updateQueueCounts();
applyFilters();
}
function updateProjectPills() {
const pills = document.querySelectorAll('.project-pill');
- const curNorm = (currentProject || '').trim().toLowerCase();
+ const curNorm = (currentProject || '').toUpperCase().trim();
pills.forEach(p => {
- const pKey = (p.getAttribute('data-project') || '').trim().toLowerCase();
- if (pKey === curNorm) {
- p.className = "project-pill flex items-center space-x-2 px-3.5 py-2 rounded-xl text-xs font-bold transition cursor-pointer bg-indigo-600 text-white shadow-sm shadow-indigo-600/20 flex-shrink-0";
+ const pKey = (p.getAttribute('data-project') || '').toUpperCase().trim();
+ const isSelected = (pKey === curNorm);
+
+ if (pKey === '') {
+ p.className = isSelected
+ ? "project-pill flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-slate-900 dark:bg-white text-white dark:text-slate-900 shadow-xs flex-shrink-0"
+ : "project-pill flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-slate-100 dark:bg-slate-800/80 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700 border border-slate-200 dark:border-slate-700 flex-shrink-0";
+ } else if (pKey === 'SD') {
+ p.className = isSelected
+ ? "project-pill flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-sky-600 text-white shadow-xs flex-shrink-0"
+ : "project-pill flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-sky-50 dark:bg-sky-950/40 text-sky-800 dark:text-sky-300 hover:bg-sky-100 dark:hover:bg-sky-900/60 border border-sky-300 dark:border-sky-800 flex-shrink-0";
+ } else if (pKey === 'IN') {
+ p.className = isSelected
+ ? "project-pill flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-amber-600 text-white shadow-xs flex-shrink-0"
+ : "project-pill flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-amber-50 dark:bg-amber-950/40 text-amber-800 dark:text-amber-300 hover:bg-amber-100 dark:hover:bg-amber-900/60 border border-amber-300 dark:border-amber-800 flex-shrink-0";
+ } else if (pKey === 'AAN') {
+ p.className = isSelected
+ ? "project-pill flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-emerald-600 text-white shadow-xs flex-shrink-0"
+ : "project-pill flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-emerald-50 dark:bg-emerald-950/40 text-emerald-800 dark:text-emerald-300 hover:bg-emerald-100 dark:hover:bg-emerald-900/60 border border-emerald-300 dark:border-emerald-800 flex-shrink-0";
+ } else if (pKey === 'GEN') {
+ p.className = isSelected
+ ? "project-pill flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-indigo-600 text-white shadow-xs flex-shrink-0"
+ : "project-pill flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-indigo-50 dark:bg-indigo-950/40 text-indigo-800 dark:text-indigo-300 hover:bg-indigo-100 dark:hover:bg-indigo-900/60 border border-indigo-300 dark:border-indigo-800 flex-shrink-0";
} else {
- p.className = "project-pill flex items-center space-x-2 px-3.5 py-2 rounded-xl text-xs font-bold transition cursor-pointer bg-slate-100 dark:bg-slate-800/80 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700/80 border border-slate-200/80 dark:border-slate-700/80 flex-shrink-0";
+ p.className = isSelected
+ ? "project-pill flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-indigo-600 text-white shadow-xs flex-shrink-0"
+ : "project-pill flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700 border border-slate-200 dark:border-slate-700 flex-shrink-0";
}
});
}
-// Hulpfunctie: Bepaal of een ticket tot een specifieke wachtrij behoort (DX, SD of aangepast)
+// Hulpfunctie: Bepaal of een ticket tot een specifieke wachtrij behoort (SD, IN, AAN, GEN, DX of MY_QUEUES)
function isIssueInQueue(iss, qCode) {
if (!qCode) return true;
- const code = qCode.toUpperCase();
+ const code = qCode.toUpperCase().trim();
const ol = (iss.onderwerp_logging || '').toLowerCase().trim();
const sum = (iss.summary || '').toLowerCase();
const pKey = (iss.project_key || '').toUpperCase().trim();
+ const pName = (iss.project || '').toLowerCase().trim();
+ // Multi-queue matching voor de ingelogde gebruiker
+ if (code === 'MY_QUEUES') {
+ const myQueues = jiraData.user_queue_filters || [];
+ if (myQueues.length === 0) return true;
+ return myQueues.some(q => isIssueInQueue(iss, q));
+ }
+
const dxTerms = ['koppeling', 'sso', 'endpoint', 'rest', 'datadump', 'uwv', 'migratie', 'api', 'webhook', 'integrat'];
const matchesDx = dxTerms.some(t => ol.includes(t) || sum.includes(t));
@@ -943,12 +1116,20 @@ function isIssueInQueue(iss, qCode) {
return matchesDx;
}
if (code === 'SD') {
- // SD is regulier Servicedesk (alle tickets die niet tot DX routering behoren)
- return !matchesDx;
+ return (pKey === 'SD' || pName.includes('servicedesk') || !matchesDx);
+ }
+ if (code === 'IN') {
+ return (pKey === 'IN' || pName.includes('interface') || matchesDx || ol.includes('interface') || sum.includes('interface'));
+ }
+ if (code === 'AAN') {
+ return (pKey === 'AAN' || pName.includes('aanvraag') || ol.includes('aanvraag') || sum.includes('aanvraag'));
+ }
+ if (code === 'GEN') {
+ return (pKey === 'GEN' || pName.includes('general') || pName.includes('algemeen') || ol.includes('general') || ol.includes('algemeen'));
}
// Aangepaste wachtrijfilter uit de database
- const fObj = (jiraData.queue_filters || []).find(f => f.filter_code.toUpperCase() === code);
+ const fObj = (jiraData.queue_filters || []).find(f => f.filter_code && f.filter_code.toUpperCase() === code);
if (fObj) {
if (fObj.project_key && pKey && fObj.project_key.toUpperCase() !== pKey) {
return false;
@@ -958,6 +1139,7 @@ function isIssueInQueue(iss, qCode) {
return true;
}
if (clause.includes('koppeling') && matchesDx) return true;
+ if (clause.includes(pKey.toLowerCase())) return true;
}
return true;
}
@@ -973,15 +1155,56 @@ function updateQueuePills() {
const pills = document.querySelectorAll('.queue-pill');
pills.forEach(p => {
const qCode = p.getAttribute('data-queue') || '';
- if (qCode === currentQueue) {
+ const isSelected = (qCode === currentQueue);
+ if (isSelected) {
p.className = "queue-pill flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-indigo-600 text-white shadow-sm shadow-indigo-600/20 flex-shrink-0";
} else {
- p.className = "queue-pill flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-slate-100 dark:bg-slate-800/80 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700/80 border border-slate-200/80 dark:border-slate-700/80 flex-shrink-0";
+ p.className = "queue-pill flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-white dark:bg-slate-800/80 text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700 border border-slate-200/80 dark:border-slate-700/80 flex-shrink-0";
}
});
}
-// Update de tellers op de DX / SD wachtrijfilter knoppen
+// Update de tellers op de project buttons
+function updateProjectCounts() {
+ const assigned = jiraData.assigned_issues || [];
+ const team = jiraData.team_issues || [];
+ const all = [...assigned, ...team];
+
+ let currentList = all;
+ if (currentTab === 'assigned') currentList = assigned;
+ else if (currentTab === 'team') currentList = team;
+ else if (currentTab === 'in_progress') {
+ currentList = all.filter(iss => isInProgressStatus(iss.status, iss.status_category));
+ } else if (currentTab === 'done') {
+ currentList = all.filter(iss => isDoneStatus(iss.status) || (iss.status_category || '').toLowerCase() === 'done');
+ }
+
+ if (hideClosed && currentTab !== 'done') {
+ currentList = currentList.filter(iss => !isDoneStatus(iss.status) && (iss.status_category || '').toLowerCase() !== 'done');
+ }
+
+ const elAll = document.getElementById('projCount_all');
+ if (elAll) elAll.innerText = currentList.length;
+
+ ['SD', 'IN', 'AAN', 'GEN'].forEach(key => {
+ const el = document.getElementById(`projCount_${key}`);
+ if (el) {
+ const cnt = currentList.filter(iss => {
+ const pk = (iss.project_key || '').toUpperCase().trim();
+ const pn = (iss.project || '').toLowerCase().trim();
+ if (pk === key) return true;
+ if (key === 'SD' && pn.includes('servicedesk')) return true;
+ if (key === 'IN' && pn.includes('interface')) return true;
+ if (key === 'AAN' && pn.includes('aanvraag')) return true;
+ if (key === 'GEN' && (pn.includes('general') || pn.includes('algemeen'))) return true;
+ return false;
+ }).length;
+ el.innerText = cnt;
+ }
+ });
+}
+
+// Update de tellers op de wachtrijfilter knoppen
function updateQueueCounts() {
const assigned = jiraData.assigned_issues || [];
const team = jiraData.team_issues || [];
@@ -991,52 +1214,45 @@ function updateQueueCounts() {
if (currentTab === 'assigned') currentList = assigned;
else if (currentTab === 'team') currentList = team;
else if (currentTab === 'in_progress') {
- currentList = all.filter(iss => {
- const s = (iss.status || '').toLowerCase();
- const sc = (iss.status_category || '').toLowerCase();
- return s.includes('progress') || s.includes('bezig') || s.includes('review') || sc === 'indeterminate';
- });
+ currentList = all.filter(iss => isInProgressStatus(iss.status, iss.status_category));
} else if (currentTab === 'done') {
- currentList = all.filter(iss => {
- const s = (iss.status || '').toLowerCase();
- const sc = (iss.status_category || '').toLowerCase();
- return s.includes('done') || s.includes('afgerond') || s.includes('gesloten') || sc === 'done';
- });
+ currentList = all.filter(iss => isDoneStatus(iss.status) || (iss.status_category || '').toLowerCase() === 'done');
+ }
+
+ if (hideClosed && currentTab !== 'done') {
+ currentList = currentList.filter(iss => !isDoneStatus(iss.status) && (iss.status_category || '').toLowerCase() !== 'done');
}
if (currentProject) {
- const pNorm = currentProject.trim().toLowerCase();
+ const pNorm = currentProject.trim().toUpperCase();
currentList = currentList.filter(iss => {
- const pName = (iss.project || '').trim().toLowerCase();
- const pKey = (iss.project_key || '').trim().toLowerCase();
- return pName === pNorm || pKey === pNorm;
+ const pk = (iss.project_key || '').trim().toUpperCase();
+ const pn = (iss.project || '').trim().toLowerCase();
+ return pk === pNorm || pn.includes(pNorm.toLowerCase());
});
}
- let dxCount = 0;
- let sdCount = 0;
+ const elAll = document.getElementById('queueCount_all');
+ if (elAll) elAll.innerText = currentList.length;
- currentList.forEach(iss => {
- if (isIssueInQueue(iss, 'DX')) {
- dxCount++;
- } else {
- sdCount++;
+ const elMy = document.getElementById('queueCount_MY_QUEUES');
+ if (elMy) {
+ elMy.innerText = currentList.filter(iss => isIssueInQueue(iss, 'MY_QUEUES')).length;
+ }
+
+ ['SD', 'IN', 'AAN', 'GEN', 'DX'].forEach(code => {
+ const el = document.getElementById(`queueCount_${code}`);
+ if (el) {
+ el.innerText = currentList.filter(iss => isIssueInQueue(iss, code)).length;
}
});
- const elAll = document.getElementById('queueCount_all');
- const elDx = document.getElementById('queueCount_DX');
- const elSd = document.getElementById('queueCount_SD');
-
- if (elAll) elAll.innerText = currentList.length;
- if (elDx) elDx.innerText = dxCount;
- if (elSd) elSd.innerText = sdCount;
-
(jiraData.queue_filters || []).forEach(f => {
- const el = document.getElementById(`queueCount_${f.filter_code}`);
- if (el && f.filter_code !== 'DX' && f.filter_code !== 'SD') {
- const cnt = currentList.filter(iss => isIssueInQueue(iss, f.filter_code)).length;
- el.innerText = cnt;
+ if (!['SD', 'IN', 'AAN', 'GEN', 'DX'].includes(f.filter_code)) {
+ const el = document.getElementById(`queueCount_${f.filter_code}`);
+ if (el) {
+ el.innerText = currentList.filter(iss => isIssueInQueue(iss, f.filter_code)).length;
+ }
}
});
}
@@ -1064,29 +1280,31 @@ function clearSearch() {
function resetAllFilters() {
currentProject = '';
currentQueue = '';
+ hideClosed = true;
+ onlyInProgress = false;
+
const searchInput = document.getElementById('jiraSearchInput');
- const projectSelect = document.getElementById('filterProject');
const typeSelect = document.getElementById('filterType');
const prioritySelect = document.getElementById('filterPriority');
const statusSelect = document.getElementById('filterStatus');
if (searchInput) searchInput.value = '';
- if (projectSelect) projectSelect.value = '';
if (typeSelect) typeSelect.value = '';
if (prioritySelect) prioritySelect.value = '';
if (statusSelect) statusSelect.value = '';
document.getElementById('clearSearchBtn')?.classList.add('hidden');
+ updateHideClosedButton();
+ updateOnlyInProgressButton();
updateProjectPills();
updateQueuePills();
- updateQueueCounts();
applyFilters();
}
// Filter en render logic
function applyFilters() {
const searchVal = (document.getElementById('jiraSearchInput')?.value || '').trim().toLowerCase();
- const projectVal = currentProject || (document.getElementById('filterProject')?.value || '');
+ const projectVal = (currentProject || '').toUpperCase().trim();
const typeVal = (document.getElementById('filterType')?.value || '').trim().toLowerCase();
const priorityVal = (document.getElementById('filterPriority')?.value || '').trim();
const statusVal = (document.getElementById('filterStatus')?.value || '').trim().toLowerCase();
@@ -1107,52 +1325,65 @@ function applyFilters() {
} else if (currentTab === 'team') {
sourceList = team;
} else if (currentTab === 'in_progress') {
- sourceList = all.filter(iss => {
- const s = (iss.status || '').toLowerCase();
- const sc = (iss.status_category || '').toLowerCase();
- return s.includes('progress') || s.includes('bezig') || s.includes('review') || sc === 'indeterminate';
- });
+ sourceList = all.filter(iss => isInProgressStatus(iss.status, iss.status_category));
} else if (currentTab === 'done') {
- sourceList = all.filter(iss => {
- const s = (iss.status || '').toLowerCase();
- const sc = (iss.status_category || '').toLowerCase();
- return s.includes('done') || s.includes('afgerond') || s.includes('gesloten') || sc === 'done';
- });
+ sourceList = all.filter(iss => isDoneStatus(iss.status) || (iss.status_category || '').toLowerCase() === 'done');
}
- // Pas zoek-, project-, wachtrij-, type-, prioriteit- en statusfilters toe
+ // Pas filters toe
const filtered = sourceList.filter(iss => {
- if (searchVal) {
- const keyMatch = (iss.key || '').toLowerCase().includes(searchVal);
- const sumMatch = (iss.summary || '').toLowerCase().includes(searchVal);
- const prjMatch = (iss.project || '').toLowerCase().includes(searchVal);
- const prjKeyMatch = (iss.project_key || '').toLowerCase().includes(searchVal);
- const descMatch = (iss.description || '').toLowerCase().includes(searchVal);
- const repMatch = (iss.reporter_name || '').toLowerCase().includes(searchVal);
- const assMatch = (iss.assignee_name || '').toLowerCase().includes(searchVal);
- const olMatch = (iss.onderwerp_logging || '').toLowerCase().includes(searchVal);
- if (!keyMatch && !sumMatch && !prjMatch && !prjKeyMatch && !descMatch && !repMatch && !assMatch && !olMatch) return false;
- }
+ const issPKey = (iss.project_key || '').toUpperCase().trim();
+ const issPName = (iss.project || '').toLowerCase().trim();
+ const issStatus = (iss.status || '').toLowerCase().trim();
+ const issCat = (iss.status_category || '').toLowerCase().trim();
- if (projectVal) {
- const pNorm = projectVal.trim().toLowerCase();
- const pName = (iss.project || '').trim().toLowerCase();
- const pKey = (iss.project_key || '').trim().toLowerCase();
- if (pName !== pNorm && pKey !== pNorm) {
+ // 1. Verberg Gesloten filter (als tab niet 'done' is en er niet expliciet een gesloten status gekozen is)
+ if (currentTab !== 'done' && hideClosed && !isDoneStatus(statusVal)) {
+ if (issCat === 'done' || isDoneStatus(issStatus)) {
return false;
}
}
+ // 2. Snelfilter "Alleen in behandeling"
+ if (onlyInProgress) {
+ if (!isInProgressStatus(issStatus, issCat)) {
+ return false;
+ }
+ }
+
+ // 3. Project Filter (SD, IN, AAN, GEN of ander)
+ if (projectVal) {
+ let matchesProj = (issPKey === projectVal);
+ if (!matchesProj) {
+ if (projectVal === 'SD' && issPName.includes('servicedesk')) matchesProj = true;
+ else if (projectVal === 'IN' && issPName.includes('interface')) matchesProj = true;
+ else if (projectVal === 'AAN' && issPName.includes('aanvraag')) matchesProj = true;
+ else if (projectVal === 'GEN' && (issPName.includes('general') || issPName.includes('algemeen'))) matchesProj = true;
+ else if (issPName === projectVal.toLowerCase()) matchesProj = true;
+ }
+ if (!matchesProj) return false;
+ }
+
+ // 4. Wachtrijfilter
if (currentQueue) {
if (!isIssueInQueue(iss, currentQueue)) {
return false;
}
}
+ // 5. Specifieke status selectie
+ if (statusVal) {
+ if (issStatus !== statusVal) {
+ return false;
+ }
+ }
+
+ // 6. Type filter
if (typeVal && (iss.issue_type || '').toLowerCase() !== typeVal) {
return false;
}
+ // 7. Prioriteit filter
if (priorityVal) {
const pVal = priorityVal.toLowerCase();
const ip = (iss.priority || '').toLowerCase();
@@ -1167,14 +1398,25 @@ function applyFilters() {
}
}
- if (statusVal && (iss.status || '').toLowerCase() !== statusVal) {
- return false;
+ // 8. Zoekterm
+ if (searchVal) {
+ const keyMatch = (iss.key || '').toLowerCase().includes(searchVal);
+ const sumMatch = (iss.summary || '').toLowerCase().includes(searchVal);
+ const prjMatch = issPName.includes(searchVal);
+ const prjKeyMatch = issPKey.includes(searchVal.toUpperCase());
+ const descMatch = (iss.description || '').toLowerCase().includes(searchVal);
+ const repMatch = (iss.reporter_name || '').toLowerCase().includes(searchVal);
+ const assMatch = (iss.assignee_name || '').toLowerCase().includes(searchVal);
+ const olMatch = (iss.onderwerp_logging || '').toLowerCase().includes(searchVal);
+ if (!keyMatch && !sumMatch && !prjMatch && !prjKeyMatch && !descMatch && !repMatch && !assMatch && !olMatch) return false;
}
return true;
});
updateActiveFilterBadge();
+ updateProjectCounts();
+ updateQueueCounts();
renderTickets(filtered);
}
@@ -1262,20 +1504,46 @@ function renderTickets(tickets) {
prioIcon = 'chevron-down';
}
+ // Project badge styling (SD, IN, AAN, GEN)
+ const pKey = (ticket.project_key || '').toUpperCase().trim();
+ const pName = (ticket.project || '').toLowerCase().trim();
+ let projBadgeClass = 'bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300 border border-slate-200/80 dark:border-slate-700/80';
+ let projLabel = escapeHtml(ticket.project || 'Project');
+
+ if (pKey === 'SD' || pName.includes('servicedesk')) {
+ projBadgeClass = 'bg-sky-50 dark:bg-sky-950/40 text-sky-700 dark:text-sky-300 border border-sky-300 dark:border-sky-800/80';
+ projLabel = 'SD • Servicedesk';
+ } else if (pKey === 'IN' || pName.includes('interface')) {
+ projBadgeClass = 'bg-amber-50 dark:bg-amber-950/40 text-amber-700 dark:text-amber-300 border border-amber-300 dark:border-amber-800/80';
+ projLabel = 'IN • Interfaces';
+ } else if (pKey === 'AAN' || pName.includes('aanvraag')) {
+ projBadgeClass = 'bg-emerald-50 dark:bg-emerald-950/40 text-emerald-700 dark:text-emerald-300 border border-emerald-300 dark:border-emerald-800/80';
+ projLabel = 'AAN • Aanvragen';
+ } else if (pKey === 'GEN' || pName.includes('general') || pName.includes('algemeen')) {
+ projBadgeClass = 'bg-indigo-50 dark:bg-indigo-950/40 text-indigo-700 dark:text-indigo-300 border border-indigo-300 dark:border-indigo-800/80';
+ projLabel = 'GEN • General';
+ }
+
// Status badge styling
let statusBadgeClass = 'bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300 border-slate-200 dark:border-slate-700';
let statusDot = '';
const sLower = (ticket.status || '').toLowerCase();
const scLower = (ticket.status_category || '').toLowerCase();
- if (sLower.includes('progress') || sLower.includes('bezig') || scLower === 'indeterminate') {
+ if (sLower.includes('progress') || sLower.includes('bezig') || sLower.includes('behandeling') || sLower.includes('uitvoering') || scLower === 'indeterminate') {
statusBadgeClass = 'bg-blue-50 text-blue-700 dark:bg-blue-950/40 dark:text-blue-300 border-blue-200 dark:border-blue-800/60';
statusDot = ' ';
+ } else if (sLower.includes('wachten') || sLower.includes('klant') || sLower.includes('waiting') || sLower.includes('pending')) {
+ statusBadgeClass = 'bg-amber-50 text-amber-700 dark:bg-amber-950/40 dark:text-amber-300 border-amber-200 dark:border-amber-800/60';
+ statusDot = ' ';
} else if (sLower.includes('review') || sLower.includes('test')) {
statusBadgeClass = 'bg-purple-50 text-purple-700 dark:bg-purple-950/40 dark:text-purple-300 border-purple-200 dark:border-purple-800/60';
statusDot = ' ';
- } else if (sLower.includes('done') || sLower.includes('afgerond') || scLower === 'done') {
+ } else if (sLower.includes('done') || sLower.includes('afgerond') || sLower.includes('gesloten') || sLower.includes('gereed') || sLower.includes('closed') || scLower === 'done') {
statusBadgeClass = 'bg-emerald-50 text-emerald-700 dark:bg-emerald-950/40 dark:text-emerald-300 border-emerald-200 dark:border-emerald-800/60';
statusDot = ' ';
+ } else if (sLower.includes('to do') || sLower.includes('open') || sLower.includes('nieuw') || scLower === 'new') {
+ statusBadgeClass = 'bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300 border-slate-200 dark:border-slate-700';
+ statusDot = ' ';
}
// Due date badge
@@ -1358,10 +1626,10 @@ function renderTickets(tickets) {
-
-
-
- ${escapeHtml(ticket.project || 'Project')}
+
+
+
+ ${projLabel}
@@ -1998,6 +2266,8 @@ document.addEventListener('DOMContentLoaded', () => {
}
calculateKpis();
+ updateHideClosedButton();
+ updateOnlyInProgressButton();
updateProjectPills();
updateQueueCounts();
if (currentQueue) {