Dev #76
@@ -544,7 +544,7 @@ require_once __DIR__ . '/includes/header.php';
|
|||||||
Systeembeheer & Integraties
|
Systeembeheer & Integraties
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-sm text-slate-600 dark:text-slate-400 max-w-2xl leading-relaxed">
|
<p class="text-sm text-slate-600 dark:text-slate-400 max-w-2xl leading-relaxed">
|
||||||
Beheer hier de centrale organisatiekoppelingen zoals Microsoft 365 Azure App Registratie en Jira, inspecteer de teamstatus en configureer toekomstige modules.
|
Centrale organisatiekoppelingen (Microsoft 365 & Jira), beveiligingsinstellingen en teamstatus.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -391,7 +391,7 @@ try {
|
|||||||
|
|
||||||
$resCreate = microsoft_create_event($assigneeId, $eventPayload);
|
$resCreate = microsoft_create_event($assigneeId, $eventPayload);
|
||||||
if (!empty($resCreate['success'])) {
|
if (!empty($resCreate['success'])) {
|
||||||
$outlookEventId = $resCreate['id'] ?? null;
|
$outlookEventId = $resCreate['event_id'] ?? ($resCreate['id'] ?? null);
|
||||||
$outlookSyncedAt = date('Y-m-d H:i:s');
|
$outlookSyncedAt = date('Y-m-d H:i:s');
|
||||||
$outlookSyncMsg = 'Taak succesvol ingepland in de Outlook agenda van de collega!';
|
$outlookSyncMsg = 'Taak succesvol ingepland in de Outlook agenda van de collega!';
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+46
-4
@@ -112,10 +112,50 @@ try {
|
|||||||
$planId = (int)$db->lastInsertId();
|
$planId = (int)$db->lastInsertId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bereken eindtijd indien starttijd bekend is
|
||||||
|
if (!empty($timeBlockStart) && empty($timeBlockEnd)) {
|
||||||
|
try {
|
||||||
|
$dtEnd = new DateTime("{$planDate} {$timeBlockStart}:00", new DateTimeZone('Europe/Amsterdam'));
|
||||||
|
$dtEnd->modify("+{$durationMinutes} minutes");
|
||||||
|
$timeBlockEnd = $dtEnd->format('H:i');
|
||||||
|
} catch (Throwable $e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
$source = 'JIRA';
|
||||||
|
$externalId = $key;
|
||||||
|
$externalUrl = $url;
|
||||||
|
$outlookSyncMsg = null;
|
||||||
|
|
||||||
|
// Automatisch synchroniseren naar Microsoft Outlook indien gekoppeld
|
||||||
|
if (function_exists('microsoft_create_event')) {
|
||||||
|
try {
|
||||||
|
$syncStartTime = !empty($timeBlockStart) ? $timeBlockStart : '09:00';
|
||||||
|
$msResult = microsoft_create_event($user['id'], [
|
||||||
|
'subject' => $title,
|
||||||
|
'description' => "Jira ticket {$key} gepland via Fociz ({$priority})\n\n{$url}",
|
||||||
|
'date' => $planDate,
|
||||||
|
'start_time' => $syncStartTime,
|
||||||
|
'end_time' => $timeBlockEnd,
|
||||||
|
'duration_minutes' => $durationMinutes,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!empty($msResult['success']) && !empty($msResult['event_id'])) {
|
||||||
|
$source = 'OUTLOOK';
|
||||||
|
$externalId = $msResult['event_id'];
|
||||||
|
$externalUrl = $msResult['web_link'] ?? ($url ?: 'https://outlook.office.com');
|
||||||
|
$timeBlockStart = $msResult['start_time'] ?? $syncStartTime;
|
||||||
|
$timeBlockEnd = $msResult['end_time'] ?? $timeBlockEnd;
|
||||||
|
$outlookSyncMsg = 'Gesynchroniseerd naar Microsoft Outlook.';
|
||||||
|
}
|
||||||
|
} catch (Throwable $t) {
|
||||||
|
error_log("Fout bij auto-sync Jira naar Outlook: " . $t->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Voeg taak toe
|
// Voeg taak toe
|
||||||
$stmt = $db->prepare("
|
$stmt = $db->prepare("
|
||||||
INSERT INTO tasks (user_id, daily_plan_id, title, priority, time_block_start, time_block_end, duration_minutes, status, source, external_id, external_url)
|
INSERT INTO tasks (user_id, daily_plan_id, title, priority, time_block_start, time_block_end, duration_minutes, status, source, external_id, external_url)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, 'TODO', 'JIRA', ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, 'TODO', ?, ?, ?)
|
||||||
");
|
");
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
$user['id'],
|
$user['id'],
|
||||||
@@ -125,15 +165,17 @@ try {
|
|||||||
$timeBlockStart,
|
$timeBlockStart,
|
||||||
$timeBlockEnd,
|
$timeBlockEnd,
|
||||||
$durationMinutes,
|
$durationMinutes,
|
||||||
$key,
|
$source,
|
||||||
$url
|
$externalId,
|
||||||
|
$externalUrl
|
||||||
]);
|
]);
|
||||||
$taskId = (int)$db->lastInsertId();
|
$taskId = (int)$db->lastInsertId();
|
||||||
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'task_id' => $taskId,
|
'task_id' => $taskId,
|
||||||
'message' => "Ticket {$key} is succesvol ingepland voor " . date('d-m-Y', strtotime($planDate)) . "!",
|
'outlook_synced' => ($source === 'OUTLOOK'),
|
||||||
|
'message' => "Ticket {$key} is succesvol ingepland voor " . date('d-m-Y', strtotime($planDate)) . "!" . ($outlookSyncMsg ? " ({$outlookSyncMsg})" : ""),
|
||||||
]);
|
]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-7
@@ -164,9 +164,40 @@ try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$source = 'OUTLOOK_MAIL';
|
||||||
|
$externalId = $messageId;
|
||||||
|
$externalUrl = $webLink;
|
||||||
|
$outlookSyncMsg = null;
|
||||||
|
|
||||||
|
// Automatisch synchroniseren naar Microsoft Outlook agenda
|
||||||
|
if (function_exists('microsoft_create_event')) {
|
||||||
|
try {
|
||||||
|
$syncStartTime = !empty($timeBlockStart) ? $timeBlockStart : '09:00';
|
||||||
|
$msResult = microsoft_create_event($userId, [
|
||||||
|
'subject' => $taskTitle,
|
||||||
|
'description' => "E-mail actiepunt gepland via Fociz ({$priority})\n\nAfzender: {$sender}\nWeblink: {$webLink}",
|
||||||
|
'date' => $planDate,
|
||||||
|
'start_time' => $syncStartTime,
|
||||||
|
'end_time' => $timeBlockEnd,
|
||||||
|
'duration_minutes' => $durationMinutes,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!empty($msResult['success']) && !empty($msResult['event_id'])) {
|
||||||
|
$source = 'OUTLOOK';
|
||||||
|
$externalId = $msResult['event_id'];
|
||||||
|
$externalUrl = $msResult['web_link'] ?? ($webLink ?: 'https://outlook.office.com');
|
||||||
|
$timeBlockStart = $msResult['start_time'] ?? $syncStartTime;
|
||||||
|
$timeBlockEnd = $msResult['end_time'] ?? $timeBlockEnd;
|
||||||
|
$outlookSyncMsg = 'Gesynchroniseerd naar Microsoft Outlook.';
|
||||||
|
}
|
||||||
|
} catch (Throwable $t) {
|
||||||
|
error_log("Fout bij auto-sync Mail naar Outlook: " . $t->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$stmtTask = $db->prepare("
|
$stmtTask = $db->prepare("
|
||||||
INSERT INTO tasks (user_id, daily_plan_id, title, priority, time_block_start, time_block_end, duration_minutes, source, external_id, external_url)
|
INSERT INTO tasks (user_id, daily_plan_id, title, priority, time_block_start, time_block_end, duration_minutes, source, external_id, external_url)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, 'OUTLOOK_MAIL', ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
");
|
");
|
||||||
$stmtTask->execute([
|
$stmtTask->execute([
|
||||||
$userId,
|
$userId,
|
||||||
@@ -176,16 +207,18 @@ try {
|
|||||||
$timeBlockStart,
|
$timeBlockStart,
|
||||||
$timeBlockEnd,
|
$timeBlockEnd,
|
||||||
$durationMinutes,
|
$durationMinutes,
|
||||||
$messageId,
|
$source,
|
||||||
$webLink
|
$externalId,
|
||||||
|
$externalUrl
|
||||||
]);
|
]);
|
||||||
$taskId = (int)$db->lastInsertId();
|
$taskId = (int)$db->lastInsertId();
|
||||||
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'task_id' => $taskId,
|
'task_id' => $taskId,
|
||||||
'plan_date' => $planDate,
|
'plan_date' => $planDate,
|
||||||
'message' => "E-mail succesvol ingepland voor {$planDate}."
|
'outlook_synced' => ($source === 'OUTLOOK'),
|
||||||
|
'message' => "E-mail succesvol ingepland voor {$planDate}." . ($outlookSyncMsg ? " ({$outlookSyncMsg})" : "")
|
||||||
]);
|
]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|||||||
+63
-15
@@ -139,18 +139,28 @@ try {
|
|||||||
$externalUrl = !empty($data['external_url']) ? trim($data['external_url']) : null;
|
$externalUrl = !empty($data['external_url']) ? trim($data['external_url']) : null;
|
||||||
$outlookSyncError = null;
|
$outlookSyncError = null;
|
||||||
|
|
||||||
// Als synchronisatie naar Outlook gewenst is (of source is OUTLOOK) en nog geen extern ID heeft
|
// Controleer of de gebruiker Outlook 365 heeft gekoppeld
|
||||||
if (($syncOutlook || $source === 'OUTLOOK') && empty($externalId)) {
|
$userHasOutlook = false;
|
||||||
// Als er geen starttijd is ingevuld maar wel sync gewenst is, gebruik 09:00 als veilige standaard
|
if (function_exists('microsoft_create_event')) {
|
||||||
if (empty($timeBlockStart)) {
|
$stmtChk = $db->prepare("SELECT id FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK' AND is_enabled = 1 AND access_token IS NOT NULL LIMIT 1");
|
||||||
$timeBlockStart = '09:00';
|
$stmtChk->execute([$user['id']]);
|
||||||
|
$userHasOutlook = (bool)$stmtChk->fetchColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Automatisch synchroniseren naar Outlook indien gevraagd OF indien gebruiker Outlook heeft gekoppeld
|
||||||
|
if (($syncOutlook || $source === 'OUTLOOK' || $userHasOutlook) && empty($externalId)) {
|
||||||
|
$syncStartTime = !empty($timeBlockStart) ? $timeBlockStart : '09:00';
|
||||||
|
|
||||||
|
$itemDesc = !empty($data['description']) ? trim((string)$data['description']) : "Gepland via Fociz Dagplanning ({$priority})";
|
||||||
|
if ($source === 'JIRA' && !empty($data['external_url'])) {
|
||||||
|
$itemDesc .= "\n\nJira: " . $data['external_url'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$msResult = microsoft_create_event($user['id'], [
|
$msResult = microsoft_create_event($user['id'], [
|
||||||
'subject' => $title,
|
'subject' => $title,
|
||||||
'description' => "Gepland via Fociz Dagplanning ({$priority})",
|
'description' => $itemDesc,
|
||||||
'date' => $date,
|
'date' => $date,
|
||||||
'start_time' => $timeBlockStart,
|
'start_time' => $syncStartTime,
|
||||||
'end_time' => $timeBlockEnd,
|
'end_time' => $timeBlockEnd,
|
||||||
'duration_minutes' => $durationMinutes,
|
'duration_minutes' => $durationMinutes,
|
||||||
]);
|
]);
|
||||||
@@ -158,8 +168,8 @@ try {
|
|||||||
if (!empty($msResult['success']) && !empty($msResult['event_id'])) {
|
if (!empty($msResult['success']) && !empty($msResult['event_id'])) {
|
||||||
$source = 'OUTLOOK';
|
$source = 'OUTLOOK';
|
||||||
$externalId = $msResult['event_id'];
|
$externalId = $msResult['event_id'];
|
||||||
$externalUrl = $msResult['web_link'] ?? 'https://outlook.office.com';
|
$externalUrl = $msResult['web_link'] ?? ($externalUrl ?: 'https://outlook.office.com');
|
||||||
$timeBlockStart = $msResult['start_time'] ?? $timeBlockStart;
|
$timeBlockStart = $msResult['start_time'] ?? $syncStartTime;
|
||||||
$timeBlockEnd = $msResult['end_time'] ?? $timeBlockEnd;
|
$timeBlockEnd = $msResult['end_time'] ?? $timeBlockEnd;
|
||||||
} else {
|
} else {
|
||||||
$outlookSyncError = $msResult['error'] ?? 'Kon afspraak niet aanmaken in Microsoft Outlook';
|
$outlookSyncError = $msResult['error'] ?? 'Kon afspraak niet aanmaken in Microsoft Outlook';
|
||||||
@@ -257,10 +267,22 @@ try {
|
|||||||
$planDate = $stmtPlan->fetchColumn() ?: date('Y-m-d');
|
$planDate = $stmtPlan->fetchColumn() ?: date('Y-m-d');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Controleer of de gebruiker Outlook 365 heeft gekoppeld
|
||||||
|
$userHasOutlook = false;
|
||||||
|
if (function_exists('microsoft_create_event')) {
|
||||||
|
$stmtChk = $db->prepare("SELECT id FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK' AND is_enabled = 1 AND access_token IS NOT NULL LIMIT 1");
|
||||||
|
$stmtChk->execute([$user['id']]);
|
||||||
|
$userHasOutlook = (bool)$stmtChk->fetchColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
$source = $existingTask['source'] ?? 'FOCIZ';
|
||||||
|
$externalId = $existingTask['external_id'] ?? null;
|
||||||
|
$externalUrl = $existingTask['external_url'] ?? null;
|
||||||
|
|
||||||
// Indien gekoppeld aan Outlook, werk afspraak bij via Graph API
|
// Indien gekoppeld aan Outlook, werk afspraak bij via Graph API
|
||||||
if (!empty($existingTask['external_id']) && $existingTask['source'] === 'OUTLOOK') {
|
if (!empty($externalId) && $source === 'OUTLOOK') {
|
||||||
try {
|
try {
|
||||||
microsoft_update_event($user['id'], $existingTask['external_id'], [
|
microsoft_update_event($user['id'], $externalId, [
|
||||||
'subject' => $title,
|
'subject' => $title,
|
||||||
'date' => $planDate,
|
'date' => $planDate,
|
||||||
'start_time' => $timeBlockStart ?: '09:00',
|
'start_time' => $timeBlockStart ?: '09:00',
|
||||||
@@ -269,6 +291,32 @@ try {
|
|||||||
} catch (Throwable $t) {
|
} catch (Throwable $t) {
|
||||||
error_log("Fout bij bijwerken Outlook event: " . $t->getMessage());
|
error_log("Fout bij bijwerken Outlook event: " . $t->getMessage());
|
||||||
}
|
}
|
||||||
|
} elseif ($userHasOutlook && empty($externalId) && function_exists('microsoft_create_event')) {
|
||||||
|
// Nog niet in Outlook gesynchroniseerd, maar gebruiker heeft Outlook: maak afspraak aan
|
||||||
|
try {
|
||||||
|
$syncStartTime = !empty($timeBlockStart) ? $timeBlockStart : '09:00';
|
||||||
|
$itemDesc = !empty($existingTask['description']) ? trim((string)$existingTask['description']) : "Gepland via Fociz Dagplanning ({$priority})";
|
||||||
|
if ($source === 'JIRA' && !empty($externalUrl)) {
|
||||||
|
$itemDesc .= "\n\nJira: " . $externalUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
$msResult = microsoft_create_event($user['id'], [
|
||||||
|
'subject' => $title,
|
||||||
|
'description' => $itemDesc,
|
||||||
|
'date' => $planDate,
|
||||||
|
'start_time' => $syncStartTime,
|
||||||
|
'end_time' => $timeBlockEnd,
|
||||||
|
'duration_minutes' => $durationMinutes,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!empty($msResult['success']) && !empty($msResult['event_id'])) {
|
||||||
|
$source = 'OUTLOOK';
|
||||||
|
$externalId = $msResult['event_id'];
|
||||||
|
$externalUrl = $msResult['web_link'] ?? ($externalUrl ?: 'https://outlook.office.com');
|
||||||
|
}
|
||||||
|
} catch (Throwable $t) {
|
||||||
|
error_log("Fout bij auto-create Outlook event in update_task: " . $t->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$description = isset($data['description']) ? trim((string)$data['description']) : ($existingTask['description'] ?? null);
|
$description = isset($data['description']) ? trim((string)$data['description']) : ($existingTask['description'] ?? null);
|
||||||
@@ -276,17 +324,17 @@ try {
|
|||||||
if ($newPlanId) {
|
if ($newPlanId) {
|
||||||
$stmtUpdate = $db->prepare("
|
$stmtUpdate = $db->prepare("
|
||||||
UPDATE tasks
|
UPDATE tasks
|
||||||
SET title = ?, description = ?, priority = ?, time_block_start = ?, time_block_end = ?, duration_minutes = ?, daily_plan_id = ?
|
SET title = ?, description = ?, priority = ?, time_block_start = ?, time_block_end = ?, duration_minutes = ?, source = ?, external_id = ?, external_url = ?, daily_plan_id = ?
|
||||||
WHERE id = ? AND user_id = ?
|
WHERE id = ? AND user_id = ?
|
||||||
");
|
");
|
||||||
$stmtUpdate->execute([$title, $description, $priority, $timeBlockStart, $timeBlockEnd, $durationMinutes, $newPlanId, $taskId, $user['id']]);
|
$stmtUpdate->execute([$title, $description, $priority, $timeBlockStart, $timeBlockEnd, $durationMinutes, $source, $externalId, $externalUrl, $newPlanId, $taskId, $user['id']]);
|
||||||
} else {
|
} else {
|
||||||
$stmtUpdate = $db->prepare("
|
$stmtUpdate = $db->prepare("
|
||||||
UPDATE tasks
|
UPDATE tasks
|
||||||
SET title = ?, description = ?, priority = ?, time_block_start = ?, time_block_end = ?, duration_minutes = ?
|
SET title = ?, description = ?, priority = ?, time_block_start = ?, time_block_end = ?, duration_minutes = ?, source = ?, external_id = ?, external_url = ?
|
||||||
WHERE id = ? AND user_id = ?
|
WHERE id = ? AND user_id = ?
|
||||||
");
|
");
|
||||||
$stmtUpdate->execute([$title, $description, $priority, $timeBlockStart, $timeBlockEnd, $durationMinutes, $taskId, $user['id']]);
|
$stmtUpdate->execute([$title, $description, $priority, $timeBlockStart, $timeBlockEnd, $durationMinutes, $source, $externalId, $externalUrl, $taskId, $user['id']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
|
|||||||
@@ -101,9 +101,7 @@ $isAdmin = ($userRole === 'ADMIN');
|
|||||||
Beheerder
|
Beheerder
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p id="boardDescriptionText" class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">
|
<p id="boardDescriptionText" class="hidden text-xs text-slate-500 dark:text-slate-400 mt-0.5"></p>
|
||||||
Teamtaken, opdrachten & Outlook agendablokkering
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -134,10 +132,10 @@ $isAdmin = ($userRole === 'ADMIN');
|
|||||||
<!-- Filter: Prioriteit -->
|
<!-- Filter: Prioriteit -->
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<select id="filterPriority" onchange="renderCards()" class="bg-slate-50 dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-2 text-xs text-slate-700 dark:text-slate-300 font-medium focus:ring-2 focus:ring-indigo-500/30 cursor-pointer shadow-2xs">
|
<select id="filterPriority" onchange="renderCards()" class="bg-slate-50 dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-2 text-xs text-slate-700 dark:text-slate-300 font-medium focus:ring-2 focus:ring-indigo-500/30 cursor-pointer shadow-2xs">
|
||||||
<option value="ALL">Alle prioriteiten</option>
|
<option value="ALL">Prioriteiten (Alle)</option>
|
||||||
<option value="P1">P1 — Urgent</option>
|
<option value="P1">P1 Urgent</option>
|
||||||
<option value="P2">P2 — Hoog</option>
|
<option value="P2">P2 Hoog</option>
|
||||||
<option value="P3">P3 — Normaal</option>
|
<option value="P3">P3 Normaal</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -637,7 +635,14 @@ async function loadBoardData(boardId, showLoader = true) {
|
|||||||
currentBoardData = data;
|
currentBoardData = data;
|
||||||
|
|
||||||
// Titels en beheerknop
|
// Titels en beheerknop
|
||||||
document.getElementById('boardDescriptionText').innerText = data.board.description || 'Geen toelichting';
|
const descEl = document.getElementById('boardDescriptionText');
|
||||||
|
if (data.board.description && data.board.description.trim() !== '') {
|
||||||
|
descEl.innerText = data.board.description;
|
||||||
|
descEl.classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
descEl.innerText = '';
|
||||||
|
descEl.classList.add('hidden');
|
||||||
|
}
|
||||||
const btnManage = document.getElementById('btnManageBoard');
|
const btnManage = document.getElementById('btnManageBoard');
|
||||||
const roleBadge = document.getElementById('boardRoleBadge');
|
const roleBadge = document.getElementById('boardRoleBadge');
|
||||||
|
|
||||||
|
|||||||
@@ -170,12 +170,12 @@ $categories = [
|
|||||||
<?php if ($aiSettings['ai_enabled'] && $aiSettings['has_key']): ?>
|
<?php if ($aiSettings['ai_enabled'] && $aiSettings['has_key']): ?>
|
||||||
<button onclick="aiGenerateIdeas(this)" class="flex items-center space-x-1.5 px-3 py-2 rounded-xl bg-violet-50 hover:bg-violet-100 dark:bg-violet-950/50 dark:hover:bg-violet-900/50 text-violet-700 dark:text-violet-300 border border-violet-200 dark:border-violet-800/60 text-xs font-bold transition cursor-pointer shadow-xs" title="Genereer 4 innovatieve ideeën via AI Co-Pilot">
|
<button onclick="aiGenerateIdeas(this)" class="flex items-center space-x-1.5 px-3 py-2 rounded-xl bg-violet-50 hover:bg-violet-100 dark:bg-violet-950/50 dark:hover:bg-violet-900/50 text-violet-700 dark:text-violet-300 border border-violet-200 dark:border-violet-800/60 text-xs font-bold transition cursor-pointer shadow-xs" title="Genereer 4 innovatieve ideeën via AI Co-Pilot">
|
||||||
<i data-lucide="sparkles" class="w-4 h-4 text-violet-500"></i>
|
<i data-lucide="sparkles" class="w-4 h-4 text-violet-500"></i>
|
||||||
<span>💡 AI Ideeën</span>
|
<span>AI Ideeën</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button onclick="aiCategorizeCards(this)" class="flex items-center space-x-1.5 px-3 py-2 rounded-xl bg-indigo-50 hover:bg-indigo-100 dark:bg-indigo-950/50 dark:hover:bg-indigo-900/50 text-indigo-700 dark:text-indigo-300 border border-indigo-200 dark:border-indigo-800/60 text-xs font-bold transition cursor-pointer shadow-xs" title="Sorteer alle kaartjes automatisch in Ideeën, Knelpunten, Oplossingen of Quick Wins">
|
<button onclick="aiCategorizeCards(this)" class="flex items-center space-x-1.5 px-3 py-2 rounded-xl bg-indigo-50 hover:bg-indigo-100 dark:bg-indigo-950/50 dark:hover:bg-indigo-900/50 text-indigo-700 dark:text-indigo-300 border border-indigo-200 dark:border-indigo-800/60 text-xs font-bold transition cursor-pointer shadow-xs" title="Sorteer alle kaartjes automatisch in Ideeën, Knelpunten, Oplossingen of Quick Wins">
|
||||||
<i data-lucide="wand-2" class="w-4 h-4 text-indigo-500"></i>
|
<i data-lucide="wand-2" class="w-4 h-4 text-indigo-500"></i>
|
||||||
<span>✨ Auto-Categoriseer</span>
|
<span>Categoriseren</span>
|
||||||
</button>
|
</button>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
|||||||
@@ -117,9 +117,6 @@ $boardsJson = json_encode($boards, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT
|
|||||||
</span>
|
</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-slate-500 dark:text-slate-400 mt-1">
|
|
||||||
Beheer tickets toegewezen aan jou en je projectteams. Plan ze direct in je dagplanning of zet ze op een Kanban bord.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+135
-59
@@ -527,50 +527,29 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
|||||||
<h1 class="text-2xl sm:text-3xl font-black tracking-tight text-slate-900 dark:text-white">
|
<h1 class="text-2xl sm:text-3xl font-black tracking-tight text-slate-900 dark:text-white">
|
||||||
Fociz
|
Fociz
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-xs sm:text-sm text-slate-500 dark:text-slate-400 mt-0.5">
|
|
||||||
Jouw centrale werkstation • Taken, Agenda, Mail & Jira
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Integratie Status Badges & Widget Beheer Knop -->
|
<!-- Instellingen Dropdown (Widgets, Indeling & Dagen Blokkeren) -->
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<div class="relative">
|
||||||
<button onclick="openCalendarBlockModal()" class="flex items-center space-x-1.5 px-3.5 py-1.5 rounded-xl bg-amber-50 hover:bg-amber-100 dark:bg-amber-500/10 dark:hover:bg-amber-500/20 text-amber-800 dark:text-amber-300 border border-amber-200/80 dark:border-amber-500/30 text-xs font-bold transition shadow-2xs cursor-pointer" title="Hele of meerdere dagen blokkeren voor vakantie of verlof">
|
<button id="plannerSettingsDropdownBtn" onclick="togglePlannerSettingsMenu(event)" class="p-2 rounded-xl text-slate-400 hover:text-slate-700 dark:hover:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-800/80 transition cursor-pointer border border-transparent hover:border-slate-200 dark:hover:border-slate-700" title="Dashboard Opties & Instellingen">
|
||||||
<i data-lucide="palmtree" class="w-3.5 h-3.5 text-amber-600 dark:text-amber-400"></i>
|
<i data-lucide="settings" class="w-5 h-5"></i>
|
||||||
<span>Vakantie / Dagen Blokkeren</span>
|
|
||||||
</button>
|
</button>
|
||||||
|
<div id="plannerSettingsDropdownMenu" class="hidden absolute right-0 mt-2 w-56 rounded-2xl bg-white dark:bg-[#111624] border border-slate-200 dark:border-slate-800 shadow-xl py-1.5 z-50 text-xs text-slate-700 dark:text-slate-300">
|
||||||
<button onclick="openWidgetManagerModal()" class="flex items-center space-x-1.5 px-3.5 py-1.5 rounded-xl bg-indigo-50 hover:bg-indigo-100 dark:bg-indigo-500/10 dark:hover:bg-indigo-500/20 text-indigo-700 dark:text-indigo-300 border border-indigo-200/80 dark:border-indigo-500/30 text-xs font-bold transition shadow-2xs cursor-pointer">
|
<button onclick="closePlannerSettingsMenu(); openWidgetManagerModal();" class="w-full text-left px-3.5 py-2 hover:bg-slate-50 dark:hover:bg-slate-800/70 flex items-center space-x-2.5 transition cursor-pointer">
|
||||||
<i data-lucide="layout-grid" class="w-3.5 h-3.5"></i>
|
<i data-lucide="layout-grid" class="w-4 h-4 text-indigo-500"></i>
|
||||||
<span>Widgets & Indeling</span>
|
<span class="font-medium">Widgets & Indeling</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button onclick="closePlannerSettingsMenu(); openCalendarBlockModal();" class="w-full text-left px-3.5 py-2 hover:bg-slate-50 dark:hover:bg-slate-800/70 flex items-center space-x-2.5 transition cursor-pointer">
|
||||||
<?php if ($isOutlookConnected): ?>
|
<i data-lucide="palmtree" class="w-4 h-4 text-amber-500"></i>
|
||||||
<div id="outlookSyncBadge" class="flex items-center space-x-2 px-3.5 py-1.5 rounded-xl bg-sky-50 dark:bg-sky-500/10 border border-sky-200/80 dark:border-sky-500/20 text-sky-800 dark:text-sky-300 text-xs font-semibold shadow-2xs transition">
|
<span class="font-medium">Vakantie / Dagen Blokkeren</span>
|
||||||
<span id="outlookSyncDot" class="w-2 h-2 rounded-full bg-sky-500"></span>
|
</button>
|
||||||
<span id="outlookSyncStatusText">Outlook 365 Live</span>
|
<?php if ($isOutlookConnected): ?>
|
||||||
<span id="outlookLastSyncText" class="text-[10px] text-sky-600/80 dark:text-sky-400/70 font-mono hidden sm:inline">(synced <span id="syncTimeDisplay"><?= date('H:i') ?></span>)</span>
|
<button onclick="closePlannerSettingsMenu(); triggerManualSync();" class="w-full text-left px-3.5 py-2 hover:bg-slate-50 dark:hover:bg-slate-800/70 flex items-center space-x-2.5 transition cursor-pointer border-t border-slate-100 dark:border-slate-800/80">
|
||||||
<button id="manualSyncBtn" onclick="triggerManualSync()" title="Nu handmatig synchroniseren met Outlook" class="p-0.5 hover:bg-sky-100 dark:hover:bg-sky-500/20 text-sky-600 hover:text-sky-900 dark:text-sky-400 dark:hover:text-white rounded transition cursor-pointer">
|
<i data-lucide="refresh-cw" class="w-4 h-4 text-sky-500"></i>
|
||||||
<i id="manualSyncIcon" data-lucide="refresh-cw" class="w-3 h-3"></i>
|
<span class="font-medium">Outlook Synchroniseren</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
<?php endif; ?>
|
||||||
<?php else: ?>
|
</div>
|
||||||
<a href="/profile.php" class="flex items-center space-x-1.5 px-3 py-1.5 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800/80 dark:hover:bg-slate-700 border border-slate-200 dark:border-slate-700 text-slate-700 dark:text-slate-300 hover:text-slate-900 dark:hover:text-white text-xs font-semibold transition shadow-2xs">
|
|
||||||
<i data-lucide="link-2" class="w-3.5 h-3.5 text-sky-500"></i>
|
|
||||||
<span>Koppel Outlook</span>
|
|
||||||
</a>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php if ($isJiraLive): ?>
|
|
||||||
<a href="/jira.php" class="flex items-center space-x-1.5 px-3.5 py-1.5 rounded-xl bg-slate-100 dark:bg-slate-800/80 border border-slate-200 dark:border-slate-700 text-slate-700 dark:text-slate-300 hover:text-slate-900 dark:hover:text-white text-xs font-semibold transition shadow-2xs">
|
|
||||||
<span class="w-2 h-2 rounded-full bg-indigo-500 animate-pulse"></span>
|
|
||||||
<span>Jira: <?= count($jiraIssues) ?> tickets</span>
|
|
||||||
</a>
|
|
||||||
<?php else: ?>
|
|
||||||
<a href="/profile.php" class="flex items-center space-x-1.5 px-3 py-1.5 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800/80 dark:hover:bg-slate-700 border border-slate-200 dark:border-slate-700 text-slate-700 dark:text-slate-300 hover:text-slate-900 dark:hover:text-white text-xs font-semibold transition shadow-2xs">
|
|
||||||
<i data-lucide="trello" class="w-3.5 h-3.5 text-indigo-500"></i>
|
|
||||||
<span>Koppel Jira</span>
|
|
||||||
</a>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1571,8 +1550,11 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
|||||||
|
|
||||||
<?php $isJiraPlanned = !empty($plannedExternalIds[$issue['key']]); ?>
|
<?php $isJiraPlanned = !empty($plannedExternalIds[$issue['key']]); ?>
|
||||||
<button
|
<button
|
||||||
onclick="quickImportJiraByKey(this, '<?= htmlspecialchars($issue['key'] ?? '') ?>')"
|
onclick="quickImportJiraByKey(this, '<?= htmlspecialchars($issue['key'] ?? '') ?>', event)"
|
||||||
data-jira-key="<?= htmlspecialchars($issue['key'] ?? '') ?>"
|
data-jira-key="<?= htmlspecialchars($issue['key'] ?? '') ?>"
|
||||||
|
data-jira-summary="<?= htmlspecialchars($issue['summary'] ?? '') ?>"
|
||||||
|
data-jira-status="<?= htmlspecialchars($issue['status'] ?? 'To Do') ?>"
|
||||||
|
data-jira-priority="<?= htmlspecialchars($issue['priority'] ?? 'Medium') ?>"
|
||||||
class="plan-btn-jira flex items-center space-x-1 text-[11px] <?= $isJiraPlanned ? 'font-bold text-white bg-emerald-600 hover:bg-emerald-500 border border-emerald-600 shadow-xs' : 'font-semibold text-indigo-600 dark:text-indigo-400 hover:text-indigo-700 dark:hover:text-indigo-300 bg-indigo-50 dark:bg-indigo-500/10 hover:bg-indigo-100 dark:hover:bg-indigo-500/20 border border-indigo-200 dark:border-indigo-500/20' ?> px-2.5 py-1 rounded-lg transition flex-shrink-0 cursor-pointer"
|
class="plan-btn-jira flex items-center space-x-1 text-[11px] <?= $isJiraPlanned ? 'font-bold text-white bg-emerald-600 hover:bg-emerald-500 border border-emerald-600 shadow-xs' : 'font-semibold text-indigo-600 dark:text-indigo-400 hover:text-indigo-700 dark:hover:text-indigo-300 bg-indigo-50 dark:bg-indigo-500/10 hover:bg-indigo-100 dark:hover:bg-indigo-500/20 border border-indigo-200 dark:border-indigo-500/20' ?> px-2.5 py-1 rounded-lg transition flex-shrink-0 cursor-pointer"
|
||||||
title="<?= $isJiraPlanned ? 'Dit ticket is al ingepland als dagtaak (klik om status te bekijken of te herplannen)' : 'Plan in als dagtaak' ?>"
|
title="<?= $isJiraPlanned ? 'Dit ticket is al ingepland als dagtaak (klik om status te bekijken of te herplannen)' : 'Plan in als dagtaak' ?>"
|
||||||
>
|
>
|
||||||
@@ -2850,6 +2832,32 @@ if ($isAjaxRequest) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
// --- Dashboard Banner Instellingen Dropdown Menu ---
|
||||||
|
function togglePlannerSettingsMenu(e) {
|
||||||
|
if (e) e.stopPropagation();
|
||||||
|
const menu = document.getElementById('plannerSettingsDropdownMenu');
|
||||||
|
if (menu) {
|
||||||
|
menu.classList.toggle('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closePlannerSettingsMenu() {
|
||||||
|
const menu = document.getElementById('plannerSettingsDropdownMenu');
|
||||||
|
if (menu) {
|
||||||
|
menu.classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
const menu = document.getElementById('plannerSettingsDropdownMenu');
|
||||||
|
const btn = document.getElementById('plannerSettingsDropdownBtn');
|
||||||
|
if (menu && !menu.classList.contains('hidden')) {
|
||||||
|
if (!menu.contains(e.target) && !btn.contains(e.target)) {
|
||||||
|
closePlannerSettingsMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// --- Fociz Widget Beheer & Configuratielogica (v2.0) ---
|
// --- Fociz Widget Beheer & Configuratielogica (v2.0) ---
|
||||||
let focizWidgets = <?= json_encode(array_values($userWidgetLayout), JSON_UNESCAPED_UNICODE) ?>;
|
let focizWidgets = <?= json_encode(array_values($userWidgetLayout), JSON_UNESCAPED_UNICODE) ?>;
|
||||||
const defaultWidgetLayout = <?= json_encode(array_values($availableWidgets), JSON_UNESCAPED_UNICODE) ?>;
|
const defaultWidgetLayout = <?= json_encode(array_values($availableWidgets), JSON_UNESCAPED_UNICODE) ?>;
|
||||||
@@ -4633,17 +4641,22 @@ document.addEventListener('click', (e) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// --- Jira Queue & Tickets Widget Engine (v2.0) ---
|
// --- Jira Queue & Tickets Widget Engine (v2.0) ---
|
||||||
const plannerJiraAssigned = <?= json_encode($jiraAssignedIssues, JSON_UNESCAPED_UNICODE) ?>;
|
const plannerJiraAssigned = <?= json_encode($jiraAssignedIssues, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
|
||||||
const plannerJiraAll = <?= json_encode($jiraAllIssues, JSON_UNESCAPED_UNICODE) ?>;
|
const plannerJiraAll = <?= json_encode($jiraAllIssues, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
|
||||||
const plannerTicketStore = new Map();
|
const plannerTicketStore = new Map();
|
||||||
plannerJiraAll.forEach(iss => {
|
(Array.isArray(plannerJiraAll) ? plannerJiraAll : []).forEach(iss => {
|
||||||
if (iss && iss.key) plannerTicketStore.set(iss.key, iss);
|
if (iss && iss.key) plannerTicketStore.set(String(iss.key).trim().toUpperCase(), iss);
|
||||||
});
|
});
|
||||||
plannerJiraAssigned.forEach(iss => {
|
(Array.isArray(plannerJiraAssigned) ? plannerJiraAssigned : []).forEach(iss => {
|
||||||
if (iss && iss.key && !plannerTicketStore.has(iss.key)) plannerTicketStore.set(iss.key, iss);
|
if (iss && iss.key) {
|
||||||
|
const normKey = String(iss.key).trim().toUpperCase();
|
||||||
|
if (!plannerTicketStore.has(normKey)) {
|
||||||
|
plannerTicketStore.set(normKey, iss);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const plannedExternalTasksMap = <?= json_encode($plannedExternalIds, JSON_UNESCAPED_UNICODE) ?> || {};
|
const plannedExternalTasksMap = <?= json_encode($plannedExternalIds, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?> || {};
|
||||||
const plannedExternalIdsSet = new Set(Object.keys(plannedExternalTasksMap));
|
const plannedExternalIdsSet = new Set(Object.keys(plannedExternalTasksMap));
|
||||||
let activePlannerJiraQueue = ''; // '': Alle, 'my': Mijn, 'DX': DX Queue, 'SD': SD Queue
|
let activePlannerJiraQueue = ''; // '': Alle, 'my': Mijn, 'DX': DX Queue, 'SD': SD Queue
|
||||||
|
|
||||||
@@ -4749,8 +4762,11 @@ function filterPlannerJira() {
|
|||||||
const isPlanned = plannedExternalIdsSet.has(issue.key);
|
const isPlanned = plannedExternalIdsSet.has(issue.key);
|
||||||
const planBtn = isPlanned
|
const planBtn = isPlanned
|
||||||
? `<button
|
? `<button
|
||||||
onclick="quickImportJiraByKey(this, '${keyEsc}')"
|
onclick="quickImportJiraByKey(this, '${keyEsc}', event)"
|
||||||
data-jira-key="${keyEsc}"
|
data-jira-key="${keyEsc}"
|
||||||
|
data-jira-summary="${summaryEsc}"
|
||||||
|
data-jira-status="${statusEsc}"
|
||||||
|
data-jira-priority="${priorityEsc}"
|
||||||
class="plan-btn-jira flex items-center space-x-1 text-[11px] font-bold text-white bg-emerald-600 hover:bg-emerald-500 border border-emerald-600 shadow-xs px-2.5 py-1 rounded-lg transition flex-shrink-0 cursor-pointer"
|
class="plan-btn-jira flex items-center space-x-1 text-[11px] font-bold text-white bg-emerald-600 hover:bg-emerald-500 border border-emerald-600 shadow-xs px-2.5 py-1 rounded-lg transition flex-shrink-0 cursor-pointer"
|
||||||
title="Dit ticket is al ingepland als dagtaak (klik om status te bekijken of te herplannen)"
|
title="Dit ticket is al ingepland als dagtaak (klik om status te bekijken of te herplannen)"
|
||||||
>
|
>
|
||||||
@@ -4758,8 +4774,11 @@ function filterPlannerJira() {
|
|||||||
<span>Ingepland</span>
|
<span>Ingepland</span>
|
||||||
</button>`
|
</button>`
|
||||||
: `<button
|
: `<button
|
||||||
onclick="quickImportJiraByKey(this, '${keyEsc}')"
|
onclick="quickImportJiraByKey(this, '${keyEsc}', event)"
|
||||||
data-jira-key="${keyEsc}"
|
data-jira-key="${keyEsc}"
|
||||||
|
data-jira-summary="${summaryEsc}"
|
||||||
|
data-jira-status="${statusEsc}"
|
||||||
|
data-jira-priority="${priorityEsc}"
|
||||||
class="plan-btn-jira flex items-center space-x-1 text-[11px] font-semibold text-indigo-600 dark:text-indigo-400 hover:text-indigo-700 dark:hover:text-indigo-300 bg-indigo-50 dark:bg-indigo-500/10 hover:bg-indigo-100 dark:hover:bg-indigo-500/20 border border-indigo-200 dark:border-indigo-500/20 px-2.5 py-1 rounded-lg transition flex-shrink-0 cursor-pointer"
|
class="plan-btn-jira flex items-center space-x-1 text-[11px] font-semibold text-indigo-600 dark:text-indigo-400 hover:text-indigo-700 dark:hover:text-indigo-300 bg-indigo-50 dark:bg-indigo-500/10 hover:bg-indigo-100 dark:hover:bg-indigo-500/20 border border-indigo-200 dark:border-indigo-500/20 px-2.5 py-1 rounded-lg transition flex-shrink-0 cursor-pointer"
|
||||||
title="Plan in als dagtaak"
|
title="Plan in als dagtaak"
|
||||||
>
|
>
|
||||||
@@ -4798,8 +4817,27 @@ function filterPlannerJira() {
|
|||||||
let _jiraDetailCurrentIssue = null;
|
let _jiraDetailCurrentIssue = null;
|
||||||
|
|
||||||
function openJiraTicketDetail(issueKey) {
|
function openJiraTicketDetail(issueKey) {
|
||||||
const issue = plannerTicketStore.get(issueKey);
|
const rawKey = String(issueKey || '').trim();
|
||||||
if (!issue) return;
|
const normKey = rawKey.toUpperCase();
|
||||||
|
let issue = plannerTicketStore.get(normKey) || plannerTicketStore.get(rawKey);
|
||||||
|
|
||||||
|
if (!issue) {
|
||||||
|
// Fallback: probeer element op te halen uit de lijst
|
||||||
|
const cardBtn = document.querySelector(`[data-jira-key="${rawKey}"]`);
|
||||||
|
const summary = cardBtn?.getAttribute('data-jira-summary') || '';
|
||||||
|
const status = cardBtn?.getAttribute('data-jira-status') || 'To Do';
|
||||||
|
const priority = cardBtn?.getAttribute('data-jira-priority') || 'Medium';
|
||||||
|
|
||||||
|
issue = {
|
||||||
|
key: rawKey,
|
||||||
|
summary: summary || `Jira item ${rawKey}`,
|
||||||
|
status: status,
|
||||||
|
priority: priority,
|
||||||
|
description: '',
|
||||||
|
url: ''
|
||||||
|
};
|
||||||
|
plannerTicketStore.set(normKey, issue);
|
||||||
|
}
|
||||||
_jiraDetailCurrentIssue = issue;
|
_jiraDetailCurrentIssue = issue;
|
||||||
|
|
||||||
// Vul header
|
// Vul header
|
||||||
@@ -4925,9 +4963,30 @@ function toggleJiraPlanDescription() {
|
|||||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||||
}
|
}
|
||||||
|
|
||||||
function openJiraPlanModal(issueKey) {
|
function openJiraPlanModal(issueKey, fallbackData = null) {
|
||||||
const issue = plannerTicketStore.get(issueKey);
|
const rawKey = String(issueKey || '').trim();
|
||||||
if (!issue) return;
|
const normKey = rawKey.toUpperCase();
|
||||||
|
let issue = plannerTicketStore.get(normKey) || plannerTicketStore.get(rawKey);
|
||||||
|
|
||||||
|
if (!issue) {
|
||||||
|
// Zoek fallback gegevens op de plan-knop in de DOM
|
||||||
|
const btn = document.querySelector(`[data-jira-key="${rawKey}"]`) || document.querySelector(`[data-jira-key="${normKey}"]`);
|
||||||
|
const summary = fallbackData?.summary || btn?.getAttribute('data-jira-summary') || '';
|
||||||
|
const status = fallbackData?.status || btn?.getAttribute('data-jira-status') || 'To Do';
|
||||||
|
const priority = fallbackData?.priority || btn?.getAttribute('data-jira-priority') || 'Medium';
|
||||||
|
|
||||||
|
issue = {
|
||||||
|
key: rawKey || 'JIRA-ITEM',
|
||||||
|
summary: summary || (rawKey ? `Oppakken Jira item ${rawKey}` : 'Jira taak'),
|
||||||
|
status: status,
|
||||||
|
priority: priority,
|
||||||
|
description: fallbackData?.description || '',
|
||||||
|
url: fallbackData?.url || ''
|
||||||
|
};
|
||||||
|
if (rawKey) {
|
||||||
|
plannerTicketStore.set(normKey, issue);
|
||||||
|
}
|
||||||
|
}
|
||||||
_jiraPlanCurrentIssue = issue;
|
_jiraPlanCurrentIssue = issue;
|
||||||
|
|
||||||
// Vul header
|
// Vul header
|
||||||
@@ -5345,9 +5404,26 @@ async function submitJiraPlanModal() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function quickImportJiraByKey(btn, issueKey) {
|
async function quickImportJiraByKey(btn, issueKey, event = null) {
|
||||||
// Plan knop → open de plan modal (niet direct importeren)
|
if (event) {
|
||||||
openJiraPlanModal(issueKey);
|
try {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawKey = String(issueKey || '').trim();
|
||||||
|
let fallbackData = null;
|
||||||
|
|
||||||
|
if (btn) {
|
||||||
|
fallbackData = {
|
||||||
|
summary: btn.getAttribute('data-jira-summary') || '',
|
||||||
|
status: btn.getAttribute('data-jira-status') || 'To Do',
|
||||||
|
priority: btn.getAttribute('data-jira-priority') || 'Medium'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
openJiraPlanModal(rawKey, fallbackData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+11
-5
@@ -71,9 +71,7 @@ html.dark #timelineGridWrapper::-webkit-scrollbar-thumb {
|
|||||||
Beheerder
|
Beheerder
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p id="boardDescriptionText" class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">
|
<p id="boardDescriptionText" class="hidden text-xs text-slate-500 dark:text-slate-400 mt-0.5"></p>
|
||||||
Gantt-planning, mijlpalen & datumbereiken
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -534,7 +532,14 @@ async function loadBoardData(boardId) {
|
|||||||
if (data.success && data.has_board) {
|
if (data.success && data.has_board) {
|
||||||
currentBoardData = data;
|
currentBoardData = data;
|
||||||
|
|
||||||
document.getElementById('boardDescriptionText').innerText = data.board.description || 'Geen toelichting';
|
const descEl = document.getElementById('boardDescriptionText');
|
||||||
|
if (data.board.description && data.board.description.trim() !== '') {
|
||||||
|
descEl.innerText = data.board.description;
|
||||||
|
descEl.classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
descEl.innerText = '';
|
||||||
|
descEl.classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
// Beheerdersbadge
|
// Beheerdersbadge
|
||||||
const badge = document.getElementById('boardRoleBadge');
|
const badge = document.getElementById('boardRoleBadge');
|
||||||
@@ -1095,13 +1100,14 @@ function openCardModal(cardId = null) {
|
|||||||
if (hasSched) {
|
if (hasSched) {
|
||||||
document.getElementById('cardStartDate').value = foundCard.start_date || '';
|
document.getElementById('cardStartDate').value = foundCard.start_date || '';
|
||||||
document.getElementById('cardEndDate').value = foundCard.end_date || '';
|
document.getElementById('cardEndDate').value = foundCard.end_date || '';
|
||||||
document.getElementById('cardSyncOutlook').checked = !!foundCard.outlook_event_id;
|
document.getElementById('cardSyncOutlook').checked = foundCard.outlook_event_id ? true : true; // Standaard gesynchroniseerd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
titleEl.innerText = 'Nieuwe Taak Toevoegen';
|
titleEl.innerText = 'Nieuwe Taak Toevoegen';
|
||||||
deleteBtn.classList.add('hidden');
|
deleteBtn.classList.add('hidden');
|
||||||
document.getElementById('cardScheduleToggle').checked = false;
|
document.getElementById('cardScheduleToggle').checked = false;
|
||||||
|
document.getElementById('cardSyncOutlook').checked = true;
|
||||||
toggleScheduleFields(false);
|
toggleScheduleFields(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ require_once __DIR__ . '/includes/header.php';
|
|||||||
Team & Uitnodigingen
|
Team & Uitnodigingen
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-slate-500 dark:text-slate-400 text-xs sm:text-sm mt-1 max-w-2xl">
|
<p class="text-slate-500 dark:text-slate-400 text-xs sm:text-sm mt-1 max-w-2xl">
|
||||||
Fociz is strikt afgesloten: nieuwe collega's kunnen enkel toegang krijgen na het ontvangen van een uitnodigingslink en verplichte 2FA-activatie.
|
Beheer collega's, rollen en uitnodigingslinks voor toegang tot de Fociz omgeving.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user