Merge pull request 'Brainstorm fix' (#68) from dev into main
Reviewed-on: #68
This commit was merged in pull request #68.
This commit is contained in:
@@ -296,6 +296,71 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$activeTab = 'calendar';
|
$activeTab = 'calendar';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3c. Brainstorm Categorieën opslaan
|
||||||
|
elseif ($action === 'save_brainstorm_categories') {
|
||||||
|
$rawCategories = $_POST['categories'] ?? [];
|
||||||
|
$sanitizedCategories = [];
|
||||||
|
if (is_array($rawCategories)) {
|
||||||
|
foreach ($rawCategories as $c) {
|
||||||
|
$name = trim((string)($c['name'] ?? ''));
|
||||||
|
$desc = trim((string)($c['description'] ?? ''));
|
||||||
|
$isActive = isset($c['is_active']) && ($c['is_active'] == '1' || $c['is_active'] === 'on') ? 1 : 0;
|
||||||
|
$isDelete = isset($c['is_delete']) && ($c['is_delete'] == '1' || $c['is_delete'] === 'on');
|
||||||
|
|
||||||
|
if ($isDelete || empty($name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sanitizedCategories[] = [
|
||||||
|
'name' => $name,
|
||||||
|
'description' => $desc,
|
||||||
|
'is_active' => $isActive,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nieuwe categorie toevoegen indien ingevuld
|
||||||
|
$newName = trim((string)($_POST['new_category_name'] ?? ''));
|
||||||
|
$newDesc = trim((string)($_POST['new_category_desc'] ?? ''));
|
||||||
|
if (!empty($newName)) {
|
||||||
|
$exists = false;
|
||||||
|
foreach ($sanitizedCategories as &$sc) {
|
||||||
|
if (strcasecmp($sc['name'], $newName) === 0) {
|
||||||
|
$sc['name'] = $newName;
|
||||||
|
if (!empty($newDesc)) $sc['description'] = $newDesc;
|
||||||
|
$sc['is_active'] = 1;
|
||||||
|
$exists = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($sc);
|
||||||
|
if (!$exists) {
|
||||||
|
$sanitizedCategories[] = [
|
||||||
|
'name' => $newName,
|
||||||
|
'description' => $newDesc,
|
||||||
|
'is_active' => 1,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($sanitizedCategories)) {
|
||||||
|
$sanitizedCategories = [
|
||||||
|
['name' => 'Product & Strategie', 'description' => 'Strategische richting, nieuwe producten en visie', 'is_active' => 1],
|
||||||
|
['name' => 'Procesverbetering', 'description' => 'Optimalisatie van werkprocessen en efficiëntie', 'is_active' => 1],
|
||||||
|
['name' => 'Klant & Service', 'description' => 'Klanttevredenheid, support en klantervaring', 'is_active' => 1],
|
||||||
|
['name' => 'Technologie & IT', 'description' => 'Architectuur, tooling, infrastructuur en tech stack','is_active' => 1],
|
||||||
|
['name' => 'Marketing & Groei', 'description' => 'Leadgeneratie, branding en marktpositie', 'is_active' => 1],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
set_system_setting('brainstorm_categories', $sanitizedCategories);
|
||||||
|
fociz_log_audit($userId, 'SETTINGS_UPDATED', 'brainstorm_categories', null, ['count' => count($sanitizedCategories)], 'SUCCESS', $adminUser['email']);
|
||||||
|
|
||||||
|
$message = "Brainstorm categorieën zijn succesvol opgeslagen.";
|
||||||
|
$messageType = 'success';
|
||||||
|
$activeTab = 'system';
|
||||||
|
}
|
||||||
|
|
||||||
// 4. AI Co-Pilot instellingen opslaan (Release v1.5)
|
// 4. AI Co-Pilot instellingen opslaan (Release v1.5)
|
||||||
elseif ($action === 'save_ai_settings') {
|
elseif ($action === 'save_ai_settings') {
|
||||||
$activeTab = 'ai';
|
$activeTab = 'ai';
|
||||||
@@ -375,6 +440,7 @@ $workdaySettings = get_workday_settings();
|
|||||||
$aiSettings = get_ai_settings();
|
$aiSettings = get_ai_settings();
|
||||||
$sessionIdleTimeout = get_session_idle_timeout();
|
$sessionIdleTimeout = get_session_idle_timeout();
|
||||||
$calendarBlockReasons = get_calendar_block_reasons();
|
$calendarBlockReasons = get_calendar_block_reasons();
|
||||||
|
$brainstormCategories = get_brainstorm_categories();
|
||||||
|
|
||||||
// Redirect URI
|
// Redirect URI
|
||||||
$redirectUri = rtrim(APP_URL, '/') . '/auth/callback_microsoft.php';
|
$redirectUri = rtrim(APP_URL, '/') . '/auth/callback_microsoft.php';
|
||||||
@@ -1925,6 +1991,87 @@ require_once __DIR__ . '/includes/header.php';
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Brainstorm Categorieën Beheer Card -->
|
||||||
|
<div class="fociz-card p-6 sm:p-7 space-y-6">
|
||||||
|
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pb-5 border-b border-slate-100 dark:border-slate-800/80">
|
||||||
|
<div class="flex items-center space-x-3.5">
|
||||||
|
<div class="w-12 h-12 rounded-xl bg-indigo-50 dark:bg-indigo-950/40 border border-indigo-200 dark:border-indigo-800/60 flex items-center justify-center text-indigo-600 dark:text-indigo-400 font-bold shadow-xs">
|
||||||
|
<i data-lucide="lightbulb" class="w-6 h-6"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<h2 class="text-lg font-bold text-slate-900 dark:text-white tracking-tight">Brainstorm Categorieën</h2>
|
||||||
|
<span class="px-2.5 py-0.5 rounded-full bg-indigo-50 dark:bg-indigo-950/40 border border-indigo-200 dark:border-indigo-800/60 text-indigo-700 dark:text-indigo-300 text-[10px] font-bold">
|
||||||
|
Beheerbaar
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-slate-500 dark:text-slate-400">Beheer de beschikbare categorieën voor nieuwe brainstormsessies</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST" class="space-y-5">
|
||||||
|
<input type="hidden" name="action" value="save_brainstorm_categories">
|
||||||
|
<input type="hidden" name="active_tab" value="system">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(get_csrf_token()) ?>">
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="grid grid-cols-12 gap-3 px-3 py-1.5 text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-wider border-b border-slate-100 dark:border-slate-800">
|
||||||
|
<div class="col-span-5 sm:col-span-4">Categorienaam</div>
|
||||||
|
<div class="col-span-5 sm:col-span-6">Omschrijving / Toelichting</div>
|
||||||
|
<div class="col-span-1 text-center">Actief</div>
|
||||||
|
<div class="col-span-1 text-right">Verwijder</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php foreach ($brainstormCategories as $bIdx => $bCat): ?>
|
||||||
|
<div class="grid grid-cols-12 gap-3 items-center p-3 rounded-2xl bg-slate-50/70 dark:bg-[#0c0f17] border border-slate-200/70 dark:border-slate-800/70 hover:border-slate-300 dark:hover:border-slate-700 transition">
|
||||||
|
<div class="col-span-5 sm:col-span-4">
|
||||||
|
<input type="text" name="categories[<?= $bIdx ?>][name]" value="<?= htmlspecialchars($bCat['name']) ?>" class="w-full px-3 py-1.5 rounded-xl bg-white dark:bg-[#111624] border border-slate-300 dark:border-slate-700 text-xs font-semibold text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-5 sm:col-span-6">
|
||||||
|
<input type="text" name="categories[<?= $bIdx ?>][description]" value="<?= htmlspecialchars($bCat['description'] ?? '') ?>" placeholder="Optionele beschrijving..." class="w-full px-3 py-1.5 rounded-xl bg-white dark:bg-[#111624] border border-slate-300 dark:border-slate-700 text-xs font-medium text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
||||||
|
</div>
|
||||||
|
<div class="col-span-1 text-center">
|
||||||
|
<label class="relative inline-flex items-center cursor-pointer">
|
||||||
|
<input type="checkbox" name="categories[<?= $bIdx ?>][is_active]" value="1" <?= !empty($bCat['is_active']) ? 'checked' : '' ?> class="w-4 h-4 text-indigo-600 rounded border-slate-300 dark:border-slate-700 focus:ring-indigo-500">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-1 text-right">
|
||||||
|
<label class="cursor-pointer text-slate-400 hover:text-rose-600 dark:hover:text-rose-400 transition" title="Vink aan om deze categorie te verwijderen">
|
||||||
|
<input type="checkbox" name="categories[<?= $bIdx ?>][is_delete]" value="1" class="w-4 h-4 text-rose-600 rounded border-slate-300 dark:border-slate-700 focus:ring-rose-500">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Nieuwe categorie toevoegen regel -->
|
||||||
|
<div class="p-4 rounded-2xl bg-indigo-50/40 dark:bg-indigo-950/20 border border-dashed border-indigo-300/80 dark:border-indigo-800/60 space-y-3">
|
||||||
|
<div class="flex items-center space-x-2 text-xs font-bold text-indigo-900 dark:text-indigo-200">
|
||||||
|
<i data-lucide="plus-circle" class="w-4 h-4 text-indigo-600 dark:text-indigo-400"></i>
|
||||||
|
<span>Nieuwe Categorie Toevoegen</span>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-12 gap-3 items-center">
|
||||||
|
<div class="sm:col-span-5">
|
||||||
|
<label class="block text-[10px] font-bold text-slate-500 dark:text-slate-400 mb-1">Categorienaam</label>
|
||||||
|
<input type="text" name="new_category_name" placeholder="Bijv. Duurzaamheid & MVO" class="w-full px-3 py-1.5 rounded-xl bg-white dark:bg-[#111624] border border-slate-300 dark:border-slate-700 text-xs font-semibold text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
||||||
|
</div>
|
||||||
|
<div class="sm:col-span-7">
|
||||||
|
<label class="block text-[10px] font-bold text-slate-500 dark:text-slate-400 mb-1">Omschrijving</label>
|
||||||
|
<input type="text" name="new_category_desc" placeholder="Doel of richting van deze categorie..." class="w-full px-3 py-1.5 rounded-xl bg-white dark:bg-[#111624] border border-slate-300 dark:border-slate-700 text-xs font-medium text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-end pt-2">
|
||||||
|
<button type="submit" class="px-5 py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-bold transition shadow-xs cursor-pointer flex items-center space-x-1.5">
|
||||||
|
<i data-lucide="check" class="w-4 h-4"></i>
|
||||||
|
<span>Brainstorm Categorieën Opslaan</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1047,6 +1047,71 @@ try {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 15. Outlook Agenda-afspraak verwijderen
|
||||||
|
if ($action === 'delete_outlook_event') {
|
||||||
|
$rawInput = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$data = is_array($rawInput) ? $rawInput : $_POST;
|
||||||
|
$eventId = trim($data['event_id'] ?? '');
|
||||||
|
|
||||||
|
if (empty($eventId)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Geen event ID opgegeven']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('microsoft_delete_event')) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Microsoft integratie niet beschikbaar']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ok = microsoft_delete_event((int)$user['id'], $eventId);
|
||||||
|
if ($ok) {
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Afspraak succesvol verwijderd uit Outlook']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Kon afspraak niet verwijderen uit Outlook (mogelijk al verwijderd of geen bevoegdheid)']);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 16. Outlook Agenda-afspraak bijwerken
|
||||||
|
if ($action === 'update_outlook_event') {
|
||||||
|
$rawInput = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$data = is_array($rawInput) ? $rawInput : $_POST;
|
||||||
|
$eventId = trim($data['event_id'] ?? '');
|
||||||
|
$subject = trim($data['title'] ?? ($data['subject'] ?? ''));
|
||||||
|
$date = trim($data['date'] ?? date('Y-m-d'));
|
||||||
|
$startTime = trim($data['time_block_start'] ?? ($data['start_time'] ?? '09:00'));
|
||||||
|
$endTime = trim($data['time_block_end'] ?? ($data['end_time'] ?? '09:30'));
|
||||||
|
$description = trim($data['description'] ?? '');
|
||||||
|
|
||||||
|
if (empty($eventId) || empty($subject)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Titel of event ID ontbreekt']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('microsoft_update_event')) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Microsoft integratie niet beschikbaar']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$updateData = [
|
||||||
|
'subject' => $subject,
|
||||||
|
'date' => $date,
|
||||||
|
'start_time' => $startTime,
|
||||||
|
'end_time' => $endTime,
|
||||||
|
];
|
||||||
|
if ($description !== '') {
|
||||||
|
$updateData['description'] = $description;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ok = microsoft_update_event((int)$user['id'], $eventId, $updateData);
|
||||||
|
if ($ok) {
|
||||||
|
echo json_encode(['success' => true, 'date' => $date, 'message' => 'Afspraak succesvol bijgewerkt in Outlook']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Kon afspraak niet bijwerken in Microsoft Outlook']);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
echo json_encode(['error' => 'Onbekende actie']);
|
echo json_encode(['error' => 'Onbekende actie']);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
|
|||||||
+20
-3
@@ -4,16 +4,23 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
require_once __DIR__ . '/includes/auth.php';
|
||||||
|
require_once __DIR__ . '/includes/settings.php';
|
||||||
$currentUser = require_auth();
|
$currentUser = require_auth();
|
||||||
$db = get_db_connection();
|
$db = get_db_connection();
|
||||||
|
$brainstormCategories = get_brainstorm_categories();
|
||||||
|
|
||||||
// Verwerk nieuwe sessie aanmaken
|
// Verwerk nieuwe sessie aanmaken
|
||||||
$error = null;
|
$error = null;
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'create_session') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'create_session') {
|
||||||
$title = trim($_POST['title'] ?? '');
|
$title = trim($_POST['title'] ?? '');
|
||||||
$theme = trim($_POST['theme'] ?? 'Product & Strategie');
|
$theme = trim($_POST['category'] ?? ($_POST['theme'] ?? ''));
|
||||||
|
if (empty($theme) && !empty($brainstormCategories)) {
|
||||||
|
$firstActive = array_values(array_filter($brainstormCategories, fn($c) => !empty($c['is_active'])))[0] ?? null;
|
||||||
|
$theme = $firstActive ? $firstActive['name'] : 'Product & Strategie';
|
||||||
|
}
|
||||||
$description = trim($_POST['description'] ?? '');
|
$description = trim($_POST['description'] ?? '');
|
||||||
|
|
||||||
|
|
||||||
if (!empty($title)) {
|
if (!empty($title)) {
|
||||||
$stmt = $db->prepare("
|
$stmt = $db->prepare("
|
||||||
INSERT INTO brainstorm_sessions (title, theme, description, created_by_id)
|
INSERT INTO brainstorm_sessions (title, theme, description, created_by_id)
|
||||||
@@ -148,13 +155,23 @@ require_once __DIR__ . '/includes/header.php';
|
|||||||
|
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Thema / Categorie</label>
|
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Categorie</label>
|
||||||
<select name="theme" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-2 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
|
<select name="category" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-2 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
|
||||||
|
<?php
|
||||||
|
$hasActiveCategory = false;
|
||||||
|
foreach ($brainstormCategories as $bCat):
|
||||||
|
if (isset($bCat['is_active']) && empty($bCat['is_active'])) continue;
|
||||||
|
$hasActiveCategory = true;
|
||||||
|
?>
|
||||||
|
<option value="<?= htmlspecialchars($bCat['name']) ?>"><?= htmlspecialchars($bCat['name']) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php if (!$hasActiveCategory): ?>
|
||||||
<option value="Product & Strategie">Product & Strategie</option>
|
<option value="Product & Strategie">Product & Strategie</option>
|
||||||
<option value="Procesverbetering">Procesverbetering</option>
|
<option value="Procesverbetering">Procesverbetering</option>
|
||||||
<option value="Klant & Service">Klant & Service</option>
|
<option value="Klant & Service">Klant & Service</option>
|
||||||
<option value="Technologie & IT">Technologie & IT</option>
|
<option value="Technologie & IT">Technologie & IT</option>
|
||||||
<option value="Marketing & Groei">Marketing & Groei</option>
|
<option value="Marketing & Groei">Marketing & Groei</option>
|
||||||
|
<?php endif; ?>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -724,6 +724,14 @@ async function aiSuggestRoadmapMeta(btn) {
|
|||||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch(err) {
|
||||||
|
alert('Netwerkfout bij aanroepen van AI');
|
||||||
|
if (btn) {
|
||||||
|
btn.innerHTML = origHtml;
|
||||||
|
btn.disabled = false;
|
||||||
|
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|||||||
@@ -618,6 +618,12 @@ function microsoft_update_event(int $userId, string $eventId, array $eventData):
|
|||||||
if (!empty($eventData['subject'])) {
|
if (!empty($eventData['subject'])) {
|
||||||
$payload['subject'] = $eventData['subject'];
|
$payload['subject'] = $eventData['subject'];
|
||||||
}
|
}
|
||||||
|
if (isset($eventData['description']) && $eventData['description'] !== '') {
|
||||||
|
$payload['body'] = [
|
||||||
|
'contentType' => 'HTML',
|
||||||
|
'content' => nl2br(htmlspecialchars($eventData['description'])),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
$date = !empty($eventData['date']) ? $eventData['date'] : date('Y-m-d');
|
$date = !empty($eventData['date']) ? $eventData['date'] : date('Y-m-d');
|
||||||
$startTime = !empty($eventData['start_time']) ? $eventData['start_time'] : '09:00';
|
$startTime = !empty($eventData['start_time']) ? $eventData['start_time'] : '09:00';
|
||||||
|
|||||||
@@ -239,3 +239,24 @@ function get_calendar_block_reasons(): array {
|
|||||||
return $defaultReasons;
|
return $defaultReasons;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Haalt de beheerbare brainstorm categorieën op.
|
||||||
|
* Standaard voorzien van een duidelijke categorienaam, beschrijving en actief-status.
|
||||||
|
*/
|
||||||
|
function get_brainstorm_categories(): array {
|
||||||
|
$defaultCategories = [
|
||||||
|
['name' => 'Product & Strategie', 'description' => 'Strategische richting, nieuwe producten en visie', 'is_active' => 1],
|
||||||
|
['name' => 'Procesverbetering', 'description' => 'Optimalisatie van werkprocessen en efficiëntie', 'is_active' => 1],
|
||||||
|
['name' => 'Klant & Service', 'description' => 'Klanttevredenheid, support en klantervaring', 'is_active' => 1],
|
||||||
|
['name' => 'Technologie & IT', 'description' => 'Architectuur, tooling, infrastructuur en tech stack','is_active' => 1],
|
||||||
|
['name' => 'Marketing & Groei', 'description' => 'Leadgeneratie, branding en marktpositie', 'is_active' => 1],
|
||||||
|
];
|
||||||
|
|
||||||
|
$saved = get_system_setting('brainstorm_categories', null);
|
||||||
|
if (is_array($saved) && !empty($saved)) {
|
||||||
|
return $saved;
|
||||||
|
}
|
||||||
|
return $defaultCategories;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+459
-69
@@ -186,6 +186,21 @@ try {
|
|||||||
error_log("Fout bij ophalen taken: " . $e->getMessage());
|
error_log("Fout bij ophalen taken: " . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3b. Haal alle reeds ingeplande externe items op (Jira keys & Outlook Mail IDs)
|
||||||
|
$plannedExternalIds = [];
|
||||||
|
try {
|
||||||
|
$stmtPlannedExt = $db->prepare("
|
||||||
|
SELECT external_id FROM tasks
|
||||||
|
WHERE user_id = ? AND external_id IS NOT NULL AND external_id != ''
|
||||||
|
");
|
||||||
|
$stmtPlannedExt->execute([$userId]);
|
||||||
|
foreach ($stmtPlannedExt->fetchAll() as $row) {
|
||||||
|
$plannedExternalIds[$row['external_id']] = true;
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("Fout bij ophalen planned external IDs: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
// 4. Haal weekstatistieken voor taken op (aantal per dag)
|
// 4. Haal weekstatistieken voor taken op (aantal per dag)
|
||||||
$weekTaskStats = [];
|
$weekTaskStats = [];
|
||||||
try {
|
try {
|
||||||
@@ -1025,14 +1040,22 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
|||||||
onchange="if(this.value) { switchPlannerDate(this.value, event); }"
|
onchange="if(this.value) { switchPlannerDate(this.value, event); }"
|
||||||
class="bg-slate-50 dark:bg-slate-950 border border-slate-200 dark:border-slate-800 rounded-xl px-2.5 py-1.5 text-xs text-slate-800 dark:text-slate-200 focus:outline-none focus:ring-2 focus:ring-indigo-500/30 font-medium cursor-pointer shadow-2xs"
|
class="bg-slate-50 dark:bg-slate-950 border border-slate-200 dark:border-slate-800 rounded-xl px-2.5 py-1.5 text-xs text-slate-800 dark:text-slate-200 focus:outline-none focus:ring-2 focus:ring-indigo-500/30 font-medium cursor-pointer shadow-2xs"
|
||||||
>
|
>
|
||||||
|
|
||||||
<button type="button" onclick="toggleDaySelectionCollapse()" id="btnToggleDaySelection" 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/80 text-slate-700 dark:text-slate-300 text-xs font-semibold transition shadow-2xs cursor-pointer" title="Dagen selectie in- of uitklappen">
|
|
||||||
<i data-lucide="chevron-up" id="iconToggleDaySelection" class="w-3.5 h-3.5 transition-transform duration-200"></i>
|
|
||||||
<span id="labelToggleDaySelection" class="hidden sm:inline">Dagen inklappen</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Inklap / Uitklap Pijltje rechts direct boven de laatste dag van de weekstrook -->
|
||||||
|
<div class="flex justify-end -mb-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick="toggleDaySelectionCollapse()"
|
||||||
|
id="btnToggleDaySelection"
|
||||||
|
class="p-1 rounded-lg text-slate-400 hover:text-slate-700 dark:hover:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer"
|
||||||
|
title="Dagen inklappen / tonen"
|
||||||
|
>
|
||||||
|
<i data-lucide="chevron-up" id="iconToggleDaySelection" class="w-4 h-4 transition-transform duration-200"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 2. 7 Dagen Weekstrook (Dagselector met Rechtermuisknop Blokkade Opties) -->
|
<!-- 2. 7 Dagen Weekstrook (Dagselector met Rechtermuisknop Blokkade Opties) -->
|
||||||
<div id="weekDaysStripContainer" class="grid grid-cols-2 sm:grid-cols-4 md:grid-cols-7 gap-2.5 transition-all duration-200">
|
<div id="weekDaysStripContainer" class="grid grid-cols-2 sm:grid-cols-4 md:grid-cols-7 gap-2.5 transition-all duration-200">
|
||||||
<?php foreach ($weekDays as $wd): ?>
|
<?php foreach ($weekDays as $wd): ?>
|
||||||
@@ -1239,10 +1262,25 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- Outlook meetings -->
|
<!-- Outlook meetings -->
|
||||||
<?php foreach ($slotEvents as $evt): ?>
|
<?php foreach ($slotEvents as $evt):
|
||||||
<div class="p-3 rounded-xl bg-sky-500/[0.07] dark:bg-sky-500/[0.12] border-l-4 border-l-sky-500 border border-sky-500/20 text-sky-950 dark:text-sky-100 text-xs shadow-2xs min-w-0 overflow-hidden transition-all hover:bg-sky-500/[0.10] dark:hover:bg-sky-500/[0.16]">
|
$evtJson = [
|
||||||
|
'id' => $evt['id'] ?? '',
|
||||||
|
'title' => $evt['subject'] ?? 'Afspraak',
|
||||||
|
'subject' => $evt['subject'] ?? 'Afspraak',
|
||||||
|
'start' => $evt['start'] ?? '',
|
||||||
|
'end' => $evt['end'] ?? '',
|
||||||
|
'date' => $evt['date'] ?? $selectedDate,
|
||||||
|
'location' => $evt['location'] ?? '',
|
||||||
|
'meeting_url' => $evt['meeting_url'] ?? '',
|
||||||
|
'web_link' => $evt['web_link'] ?? '',
|
||||||
|
'is_all_day' => !empty($evt['is_all_day']),
|
||||||
|
'source' => 'OUTLOOK_LIVE',
|
||||||
|
'is_outlook_event' => true,
|
||||||
|
];
|
||||||
|
?>
|
||||||
|
<div oncontextmenu="handleTaskContextMenu(event, <?= htmlspecialchars(json_encode($evtJson), ENT_QUOTES, 'UTF-8') ?>)" title="Rechtermuisklik voor opties (openen, bewerken, verwijderen)" class="p-3 rounded-xl bg-sky-500/[0.07] dark:bg-sky-500/[0.12] border-l-4 border-l-sky-500 border border-sky-500/20 text-sky-950 dark:text-sky-100 text-xs shadow-2xs min-w-0 overflow-hidden transition-all hover:bg-sky-500/[0.10] dark:hover:bg-sky-500/[0.16] cursor-pointer group/evt">
|
||||||
<div class="flex items-start justify-between gap-2.5 min-w-0">
|
<div class="flex items-start justify-between gap-2.5 min-w-0">
|
||||||
<div class="flex items-start space-x-2 min-w-0 flex-1">
|
<div class="flex items-start space-x-2 min-w-0 flex-1" onclick="openTaskDetailModal(<?= htmlspecialchars(json_encode($evtJson), ENT_QUOTES, 'UTF-8') ?>)">
|
||||||
<span class="px-2 py-0.5 rounded-md bg-sky-500/15 text-sky-800 dark:text-sky-300 font-mono text-[10px] font-bold flex-shrink-0 whitespace-nowrap mt-0.5">
|
<span class="px-2 py-0.5 rounded-md bg-sky-500/15 text-sky-800 dark:text-sky-300 font-mono text-[10px] font-bold flex-shrink-0 whitespace-nowrap mt-0.5">
|
||||||
<?= htmlspecialchars($evt['start']) ?> - <?= htmlspecialchars($evt['end']) ?>
|
<?= htmlspecialchars($evt['start']) ?> - <?= htmlspecialchars($evt['end']) ?>
|
||||||
</span>
|
</span>
|
||||||
@@ -1250,7 +1288,7 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
|||||||
<?= htmlspecialchars($evt['subject']) ?>
|
<?= htmlspecialchars($evt['subject']) ?>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-1 flex-shrink-0">
|
<div class="flex items-center space-x-1 flex-shrink-0" onclick="event.stopPropagation()">
|
||||||
<?php if (!empty($evt['meeting_url'])): ?>
|
<?php if (!empty($evt['meeting_url'])): ?>
|
||||||
<a href="<?= htmlspecialchars($evt['meeting_url']) ?>" target="_blank" class="flex items-center space-x-1 text-[11px] text-white bg-indigo-600 hover:bg-indigo-700 px-2.5 py-1 rounded-lg flex-shrink-0 transition font-semibold shadow-xs">
|
<a href="<?= htmlspecialchars($evt['meeting_url']) ?>" target="_blank" class="flex items-center space-x-1 text-[11px] text-white bg-indigo-600 hover:bg-indigo-700 px-2.5 py-1 rounded-lg flex-shrink-0 transition font-semibold shadow-xs">
|
||||||
<i data-lucide="video" class="w-3 h-3"></i>
|
<i data-lucide="video" class="w-3 h-3"></i>
|
||||||
@@ -1394,13 +1432,20 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center space-x-1 flex-shrink-0">
|
<div class="flex items-center space-x-1 flex-shrink-0">
|
||||||
|
<?php $isMailPlanned = !empty($plannedExternalIds[$mail['id']]); ?>
|
||||||
<button
|
<button
|
||||||
onclick="openPlanEmailDirect('<?= htmlspecialchars(addslashes($mail['id'])) ?>', '<?= htmlspecialchars(addslashes($mail['subject'])) ?>', '<?= htmlspecialchars(addslashes($mail['sender_name'])) ?>', '<?= htmlspecialchars(addslashes($mail['web_link'] ?? '')) ?>', '<?= htmlspecialchars(addslashes($mail['body_preview'] ?? '')) ?>')"
|
onclick="openPlanEmailDirect('<?= htmlspecialchars(addslashes($mail['id'])) ?>', '<?= htmlspecialchars(addslashes($mail['subject'])) ?>', '<?= htmlspecialchars(addslashes($mail['sender_name'])) ?>', '<?= htmlspecialchars(addslashes($mail['web_link'] ?? '')) ?>', '<?= htmlspecialchars(addslashes($mail['body_preview'] ?? '')) ?>')"
|
||||||
class="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 cursor-pointer"
|
data-mail-id="<?= htmlspecialchars($mail['id']) ?>"
|
||||||
title="Plan deze e-mail direct in als dagtaak"
|
class="plan-btn-mail flex items-center space-x-1 text-[11px] <?= $isMailPlanned ? '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 cursor-pointer"
|
||||||
|
title="<?= $isMailPlanned ? 'Deze e-mail is al ingepland als dagtaak (klik om opnieuw/anders in te plannen)' : 'Plan deze e-mail direct in als dagtaak' ?>"
|
||||||
>
|
>
|
||||||
|
<?php if ($isMailPlanned): ?>
|
||||||
|
<i data-lucide="check" class="w-3 h-3 stroke-[2.5]"></i>
|
||||||
|
<span>Ingepland</span>
|
||||||
|
<?php else: ?>
|
||||||
<i data-lucide="calendar-plus" class="w-3 h-3"></i>
|
<i data-lucide="calendar-plus" class="w-3 h-3"></i>
|
||||||
<span>Plan</span>
|
<span>Plan</span>
|
||||||
|
<?php endif; ?>
|
||||||
</button>
|
</button>
|
||||||
<a
|
<a
|
||||||
href="/mail.php?id=<?= urlencode($mail['id']) ?>"
|
href="/mail.php?id=<?= urlencode($mail['id']) ?>"
|
||||||
@@ -1493,13 +1538,20 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<?php $isJiraPlanned = !empty($plannedExternalIds[$issue['key']]); ?>
|
||||||
<button
|
<button
|
||||||
onclick="quickImportJiraByKey(this, '<?= htmlspecialchars($issue['key'] ?? '') ?>')"
|
onclick="quickImportJiraByKey(this, '<?= htmlspecialchars($issue['key'] ?? '') ?>')"
|
||||||
class="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"
|
data-jira-key="<?= htmlspecialchars($issue['key'] ?? '') ?>"
|
||||||
title="Plan in als dagtaak"
|
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 opnieuw/anders in te plannen)' : 'Plan in als dagtaak' ?>"
|
||||||
>
|
>
|
||||||
|
<?php if ($isJiraPlanned): ?>
|
||||||
|
<i data-lucide="check" class="w-3 h-3 stroke-[2.5]"></i>
|
||||||
|
<span>Ingepland</span>
|
||||||
|
<?php else: ?>
|
||||||
<i data-lucide="calendar-plus" class="w-3 h-3"></i>
|
<i data-lucide="calendar-plus" class="w-3 h-3"></i>
|
||||||
<span>Plan</span>
|
<span>Plan</span>
|
||||||
|
<?php endif; ?>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -1544,14 +1596,29 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
|||||||
</div>
|
</div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="space-y-2 max-h-72 overflow-y-auto pr-1">
|
<div class="space-y-2 max-h-72 overflow-y-auto pr-1">
|
||||||
<?php foreach ($displayedOutlookEvents as $evt): ?>
|
<?php foreach ($displayedOutlookEvents as $evt):
|
||||||
<div class="fociz-card-interactive flex items-center justify-between p-3 rounded-xl bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-200/80 dark:border-slate-800/80 hover:border-teal-300 dark:hover:border-teal-600/50 transition shadow-2xs">
|
$widgetEvtJson = [
|
||||||
<div class="truncate mr-2 min-w-0 flex-1">
|
'id' => $evt['id'] ?? '',
|
||||||
|
'title' => $evt['subject'] ?? 'Afspraak',
|
||||||
|
'subject' => $evt['subject'] ?? 'Afspraak',
|
||||||
|
'start' => $evt['start'] ?? '',
|
||||||
|
'end' => $evt['end'] ?? '',
|
||||||
|
'date' => $evt['date'] ?? $selectedDate,
|
||||||
|
'location' => $evt['location'] ?? '',
|
||||||
|
'meeting_url' => $evt['meeting_url'] ?? '',
|
||||||
|
'web_link' => $evt['web_link'] ?? '',
|
||||||
|
'is_all_day' => !empty($evt['is_all_day']),
|
||||||
|
'source' => 'OUTLOOK_LIVE',
|
||||||
|
'is_outlook_event' => true,
|
||||||
|
];
|
||||||
|
?>
|
||||||
|
<div oncontextmenu="handleTaskContextMenu(event, <?= htmlspecialchars(json_encode($widgetEvtJson), ENT_QUOTES, 'UTF-8') ?>)" title="Rechtermuisklik voor opties (openen, bewerken, verwijderen)" class="fociz-card-interactive flex items-center justify-between p-3 rounded-xl bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-200/80 dark:border-slate-800/80 hover:border-teal-300 dark:hover:border-teal-600/50 transition shadow-2xs cursor-pointer group/evt">
|
||||||
|
<div class="truncate mr-2 min-w-0 flex-1" onclick="openTaskDetailModal(<?= htmlspecialchars(json_encode($widgetEvtJson), ENT_QUOTES, 'UTF-8') ?>)">
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<span class="px-2 py-0.5 rounded-md bg-sky-500/15 text-sky-800 dark:text-sky-300 font-mono text-[10px] font-bold flex-shrink-0">
|
<span class="px-2 py-0.5 rounded-md bg-sky-500/15 text-sky-800 dark:text-sky-300 font-mono text-[10px] font-bold flex-shrink-0">
|
||||||
<?= htmlspecialchars($evt['start'] ?? '') ?> - <?= htmlspecialchars($evt['end'] ?? '') ?>
|
<?= htmlspecialchars($evt['start'] ?? '') ?> - <?= htmlspecialchars($evt['end'] ?? '') ?>
|
||||||
</span>
|
</span>
|
||||||
<span class="text-xs font-semibold text-slate-900 dark:text-white truncate">
|
<span class="text-xs font-semibold text-slate-900 dark:text-white truncate" title="<?= htmlspecialchars($evt['subject'] ?? 'Afspraak') ?>">
|
||||||
<?= htmlspecialchars($evt['subject'] ?? 'Afspraak') ?>
|
<?= htmlspecialchars($evt['subject'] ?? 'Afspraak') ?>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -1563,7 +1630,7 @@ for ($h = $startHour; $h <= $endHour; $h++) {
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center space-x-1 flex-shrink-0">
|
<div class="flex items-center space-x-1 flex-shrink-0" onclick="event.stopPropagation()">
|
||||||
<?php if (!empty($evt['meeting_url'])): ?>
|
<?php if (!empty($evt['meeting_url'])): ?>
|
||||||
<a href="<?= htmlspecialchars($evt['meeting_url']) ?>" target="_blank" class="flex items-center space-x-1 text-[11px] text-white bg-indigo-600 hover:bg-indigo-700 px-2.5 py-1 rounded-lg transition font-semibold shadow-xs">
|
<a href="<?= htmlspecialchars($evt['meeting_url']) ?>" target="_blank" class="flex items-center space-x-1 text-[11px] text-white bg-indigo-600 hover:bg-indigo-700 px-2.5 py-1 rounded-lg transition font-semibold shadow-xs">
|
||||||
<i data-lucide="video" class="w-3 h-3"></i>
|
<i data-lucide="video" class="w-3 h-3"></i>
|
||||||
@@ -2103,7 +2170,7 @@ if ($isAjaxRequest) {
|
|||||||
<button type="button" onclick="closeJiraTicketDetailModal()" class="px-3 py-1.5 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-300 font-semibold text-xs transition cursor-pointer">
|
<button type="button" onclick="closeJiraTicketDetailModal()" class="px-3 py-1.5 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-300 font-semibold text-xs transition cursor-pointer">
|
||||||
Sluiten
|
Sluiten
|
||||||
</button>
|
</button>
|
||||||
<button type="button" onclick="openJiraPlanFromDetail()" class="flex items-center space-x-1.5 px-3 py-1.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white font-bold text-xs transition cursor-pointer">
|
<button type="button" onclick="openJiraPlanFromDetail()" id="btnJiraDetailPlan" class="flex items-center space-x-1.5 px-3 py-1.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white font-bold text-xs transition cursor-pointer">
|
||||||
<i data-lucide="calendar-plus" class="w-3.5 h-3.5"></i>
|
<i data-lucide="calendar-plus" class="w-3.5 h-3.5"></i>
|
||||||
<span>Inplannen</span>
|
<span>Inplannen</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -2121,8 +2188,8 @@ if ($isAjaxRequest) {
|
|||||||
<i data-lucide="edit-3" class="w-4 h-4"></i>
|
<i data-lucide="edit-3" class="w-4 h-4"></i>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-sm font-bold text-slate-900 dark:text-white">Taak & Tijdblok Bewerken</h3>
|
<h3 id="editTaskModalHeaderTitle" class="text-sm font-bold text-slate-900 dark:text-white">Taak & Tijdblok Bewerken</h3>
|
||||||
<p class="text-[10px] text-slate-500 dark:text-slate-400">Pas titel, start-/eindtijd en tijdsduur vrij aan</p>
|
<p id="editTaskModalHeaderSubtitle" class="text-[10px] text-slate-500 dark:text-slate-400">Pas titel, start-/eindtijd en tijdsduur vrij aan</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button onclick="closeEditTaskModal()" class="p-1.5 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer">
|
<button onclick="closeEditTaskModal()" class="p-1.5 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer">
|
||||||
@@ -2131,9 +2198,10 @@ if ($isAjaxRequest) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="hidden" id="editTaskId">
|
<input type="hidden" id="editTaskId">
|
||||||
|
<input type="hidden" id="editTaskIsOutlook" value="0">
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Taaktitel</label>
|
<label id="editTaskTitleLabel" class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Taaktitel</label>
|
||||||
<input type="text" id="editTaskTitle" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-2 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
|
<input type="text" id="editTaskTitle" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-2 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -2143,7 +2211,7 @@ if ($isAjaxRequest) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
<div>
|
<div id="editTaskPriorityContainer">
|
||||||
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Prioriteit</label>
|
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">Prioriteit</label>
|
||||||
<select id="editTaskPriority" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-2.5 py-2 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
|
<select id="editTaskPriority" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-2.5 py-2 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
|
||||||
<option value="P1">P1 - Brandend / Focus</option>
|
<option value="P1">P1 - Brandend / Focus</option>
|
||||||
@@ -2151,7 +2219,7 @@ if ($isAjaxRequest) {
|
|||||||
<option value="P3">P3 - Regulier / Lage druk</option>
|
<option value="P3">P3 - Regulier / Lage druk</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div id="editTaskDurationContainer">
|
||||||
<div class="flex items-center justify-between mb-1">
|
<div class="flex items-center justify-between mb-1">
|
||||||
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300">Tijdsduur</label>
|
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300">Tijdsduur</label>
|
||||||
<span id="editTaskDurationDisplay" class="text-[11px] font-mono font-bold text-indigo-600 dark:text-indigo-400">30m</span>
|
<span id="editTaskDurationDisplay" class="text-[11px] font-mono font-bold text-indigo-600 dark:text-indigo-400">30m</span>
|
||||||
@@ -2508,7 +2576,7 @@ if ($isAjaxRequest) {
|
|||||||
</button>
|
</button>
|
||||||
<button type="button" id="taskCtxBtnEdit" class="w-full flex items-center space-x-2.5 px-2.5 py-1.5 rounded-xl hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-700 dark:text-slate-200 hover:text-slate-900 dark:hover:text-white text-xs font-semibold transition cursor-pointer text-left">
|
<button type="button" id="taskCtxBtnEdit" class="w-full flex items-center space-x-2.5 px-2.5 py-1.5 rounded-xl hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-700 dark:text-slate-200 hover:text-slate-900 dark:hover:text-white text-xs font-semibold transition cursor-pointer text-left">
|
||||||
<i data-lucide="edit-3" class="w-3.5 h-3.5 text-slate-500 flex-shrink-0"></i>
|
<i data-lucide="edit-3" class="w-3.5 h-3.5 text-slate-500 flex-shrink-0"></i>
|
||||||
<span>Taak bewerken</span>
|
<span id="taskCtxBtnEditLabel">Taak bewerken</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" id="taskCtxBtnToggle" class="w-full flex items-center space-x-2.5 px-2.5 py-1.5 rounded-xl hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-700 dark:text-slate-200 hover:text-slate-900 dark:hover:text-white text-xs font-semibold transition cursor-pointer text-left">
|
<button type="button" id="taskCtxBtnToggle" class="w-full flex items-center space-x-2.5 px-2.5 py-1.5 rounded-xl hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-700 dark:text-slate-200 hover:text-slate-900 dark:hover:text-white text-xs font-semibold transition cursor-pointer text-left">
|
||||||
<i data-lucide="check-circle-2" class="w-3.5 h-3.5 text-emerald-500 flex-shrink-0"></i>
|
<i data-lucide="check-circle-2" class="w-3.5 h-3.5 text-emerald-500 flex-shrink-0"></i>
|
||||||
@@ -2522,10 +2590,10 @@ if ($isAjaxRequest) {
|
|||||||
<i data-lucide="calendar-plus" class="w-3.5 h-3.5 text-sky-500 flex-shrink-0"></i>
|
<i data-lucide="calendar-plus" class="w-3.5 h-3.5 text-sky-500 flex-shrink-0"></i>
|
||||||
<span>Naar Outlook synchroniseren</span>
|
<span>Naar Outlook synchroniseren</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="border-t border-slate-100 dark:border-slate-800/80 my-1"></div>
|
<div id="taskCtxDivider" class="border-t border-slate-100 dark:border-slate-800/80 my-1"></div>
|
||||||
<button type="button" id="taskCtxBtnDelete" class="w-full flex items-center space-x-2.5 px-2.5 py-1.5 rounded-xl hover:bg-rose-50 dark:hover:bg-rose-950/40 text-rose-600 dark:text-rose-400 text-xs font-semibold transition cursor-pointer text-left">
|
<button type="button" id="taskCtxBtnDelete" class="w-full flex items-center space-x-2.5 px-2.5 py-1.5 rounded-xl hover:bg-rose-50 dark:hover:bg-rose-950/40 text-rose-600 dark:text-rose-400 text-xs font-semibold transition cursor-pointer text-left">
|
||||||
<i data-lucide="trash-2" class="w-3.5 h-3.5 text-rose-500 flex-shrink-0"></i>
|
<i data-lucide="trash-2" class="w-3.5 h-3.5 text-rose-500 flex-shrink-0"></i>
|
||||||
<span>Taak verwijderen</span>
|
<span id="taskCtxBtnDeleteLabel">Taak verwijderen</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2581,7 +2649,7 @@ if ($isAjaxRequest) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Subtaken & Checklist -->
|
<!-- Subtaken & Checklist -->
|
||||||
<div class="space-y-2 pt-2 border-t border-slate-100 dark:border-slate-800/80">
|
<div id="taskDetailSubtasksSection" class="space-y-2 pt-2 border-t border-slate-100 dark:border-slate-800/80">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex items-center space-x-1.5">
|
<div class="flex items-center space-x-1.5">
|
||||||
<i data-lucide="list-checks" class="w-4 h-4 text-indigo-500"></i>
|
<i data-lucide="list-checks" class="w-4 h-4 text-indigo-500"></i>
|
||||||
@@ -2627,7 +2695,7 @@ if ($isAjaxRequest) {
|
|||||||
<div class="flex items-center justify-between pt-3 border-t border-slate-100 dark:border-slate-800/80 flex-shrink-0">
|
<div class="flex items-center justify-between pt-3 border-t border-slate-100 dark:border-slate-800/80 flex-shrink-0">
|
||||||
<button type="button" id="taskDetailBtnDelete" class="px-3.5 py-2 rounded-xl text-xs font-bold text-rose-600 dark:text-rose-400 hover:bg-rose-50 dark:hover:bg-rose-950/40 transition cursor-pointer flex items-center space-x-1.5">
|
<button type="button" id="taskDetailBtnDelete" class="px-3.5 py-2 rounded-xl text-xs font-bold text-rose-600 dark:text-rose-400 hover:bg-rose-50 dark:hover:bg-rose-950/40 transition cursor-pointer flex items-center space-x-1.5">
|
||||||
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
|
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
|
||||||
<span>Verwijderen</span>
|
<span id="taskDetailBtnDeleteLabel">Verwijderen</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
@@ -2636,7 +2704,7 @@ if ($isAjaxRequest) {
|
|||||||
</button>
|
</button>
|
||||||
<button type="button" id="taskDetailBtnEdit" class="px-4 py-2 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-bold transition shadow-xs cursor-pointer flex items-center space-x-1.5">
|
<button type="button" id="taskDetailBtnEdit" class="px-4 py-2 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-bold transition shadow-xs cursor-pointer flex items-center space-x-1.5">
|
||||||
<i data-lucide="edit-3" class="w-3.5 h-3.5"></i>
|
<i data-lucide="edit-3" class="w-3.5 h-3.5"></i>
|
||||||
<span>Taak Bewerken</span>
|
<span id="taskDetailBtnEditLabel">Taak Bewerken</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -3127,7 +3195,7 @@ function closeDayContextMenu() {
|
|||||||
activeContextMenuDay = null;
|
activeContextMenuDay = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Rechtermuisklik Context Menu voor Taken (Release v2.2) ---
|
// --- Rechtermuisklik Context Menu voor Taken & Outlook Events (Release v2.2) ---
|
||||||
let activeContextMenuTask = null;
|
let activeContextMenuTask = null;
|
||||||
|
|
||||||
function handleTaskContextMenu(e, taskData) {
|
function handleTaskContextMenu(e, taskData) {
|
||||||
@@ -3141,14 +3209,28 @@ function handleTaskContextMenu(e, taskData) {
|
|||||||
const titleEl = document.getElementById('taskContextMenuTitle');
|
const titleEl = document.getElementById('taskContextMenuTitle');
|
||||||
const priorityBadge = document.getElementById('taskContextMenuPriorityBadge');
|
const priorityBadge = document.getElementById('taskContextMenuPriorityBadge');
|
||||||
const metaEl = document.getElementById('taskContextMenuMeta');
|
const metaEl = document.getElementById('taskContextMenuMeta');
|
||||||
|
const btnOpen = document.getElementById('taskCtxBtnOpen');
|
||||||
|
const btnEdit = document.getElementById('taskCtxBtnEdit');
|
||||||
|
const btnEditLabel = document.getElementById('taskCtxBtnEditLabel');
|
||||||
|
const btnToggle = document.getElementById('taskCtxBtnToggle');
|
||||||
const toggleLabel = document.getElementById('taskCtxBtnToggleLabel');
|
const toggleLabel = document.getElementById('taskCtxBtnToggleLabel');
|
||||||
|
const btnSubtasks = document.getElementById('taskCtxBtnSubtasks');
|
||||||
const btnOutlook = document.getElementById('taskCtxBtnOutlook');
|
const btnOutlook = document.getElementById('taskCtxBtnOutlook');
|
||||||
|
const btnDelete = document.getElementById('taskCtxBtnDelete');
|
||||||
|
const btnDeleteLabel = document.getElementById('taskCtxBtnDeleteLabel');
|
||||||
|
const divider = document.getElementById('taskCtxDivider');
|
||||||
|
|
||||||
if (!menu) return;
|
if (!menu) return;
|
||||||
|
|
||||||
if (titleEl) titleEl.textContent = taskData.title || 'Taak';
|
const isOutlookEvent = Boolean(taskData && (taskData.is_outlook_event || taskData.source === 'OUTLOOK_LIVE'));
|
||||||
|
|
||||||
|
if (titleEl) titleEl.textContent = (isOutlookEvent ? (taskData.subject || taskData.title) : taskData.title) || 'Item';
|
||||||
|
|
||||||
if (priorityBadge) {
|
if (priorityBadge) {
|
||||||
|
if (isOutlookEvent) {
|
||||||
|
priorityBadge.textContent = 'AFSPRAAK';
|
||||||
|
priorityBadge.className = 'text-[9px] font-extrabold px-1.5 py-0.5 rounded-md bg-sky-100 text-sky-700 dark:bg-sky-950 dark:text-sky-400 border border-sky-300 dark:border-sky-800';
|
||||||
|
} else {
|
||||||
const p = taskData.priority || 'P2';
|
const p = taskData.priority || 'P2';
|
||||||
priorityBadge.textContent = p;
|
priorityBadge.textContent = p;
|
||||||
if (p === 'P1') {
|
if (p === 'P1') {
|
||||||
@@ -3159,24 +3241,44 @@ function handleTaskContextMenu(e, taskData) {
|
|||||||
priorityBadge.className = 'text-[9px] font-extrabold px-1.5 py-0.5 rounded-md bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300 border border-slate-300 dark:border-slate-700';
|
priorityBadge.className = 'text-[9px] font-extrabold px-1.5 py-0.5 rounded-md bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300 border border-slate-300 dark:border-slate-700';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (metaEl) {
|
if (metaEl) {
|
||||||
let meta = '';
|
let meta = '';
|
||||||
if (taskData.start) {
|
if (taskData.start) {
|
||||||
meta += `${taskData.start}${taskData.end ? ' - ' + taskData.end : ''} (${taskData.duration || 30}m)`;
|
meta += `${taskData.start}${taskData.end ? ' - ' + taskData.end : ''}`;
|
||||||
|
if (taskData.duration) meta += ` (${taskData.duration}m)`;
|
||||||
} else if (taskData.duration) {
|
} else if (taskData.duration) {
|
||||||
meta += `${taskData.duration}m`;
|
meta += `${taskData.duration}m`;
|
||||||
}
|
}
|
||||||
if (taskData.source && taskData.source !== 'FOCIZ') {
|
if (isOutlookEvent) {
|
||||||
|
meta += (meta ? ' · ' : '') + 'Outlook';
|
||||||
|
if (taskData.location && taskData.location !== 'Geen locatie') {
|
||||||
|
meta += ` · ${taskData.location}`;
|
||||||
|
}
|
||||||
|
} else if (taskData.source && taskData.source !== 'FOCIZ') {
|
||||||
meta += (meta ? ' · ' : '') + taskData.source;
|
meta += (meta ? ' · ' : '') + taskData.source;
|
||||||
}
|
}
|
||||||
metaEl.textContent = meta || (taskData.date || 'Vandaag');
|
metaEl.textContent = meta || (taskData.date || 'Vandaag');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pas labels en zichtbaarheid aan op basis van type
|
||||||
|
if (isOutlookEvent) {
|
||||||
|
if (btnEditLabel) btnEditLabel.textContent = 'Afspraak bewerken';
|
||||||
|
if (btnDeleteLabel) btnDeleteLabel.textContent = 'Afspraak verwijderen';
|
||||||
|
if (btnToggle) btnToggle.classList.add('hidden');
|
||||||
|
if (btnSubtasks) btnSubtasks.classList.add('hidden');
|
||||||
|
if (btnOutlook) btnOutlook.classList.add('hidden');
|
||||||
|
} else {
|
||||||
|
if (btnEditLabel) btnEditLabel.textContent = 'Taak bewerken';
|
||||||
|
if (btnDeleteLabel) btnDeleteLabel.textContent = 'Taak verwijderen';
|
||||||
|
if (btnToggle) {
|
||||||
|
btnToggle.classList.remove('hidden');
|
||||||
if (toggleLabel) {
|
if (toggleLabel) {
|
||||||
toggleLabel.textContent = taskData.is_completed ? 'Markeren als openstaand' : 'Markeren als voltooid';
|
toggleLabel.textContent = taskData.is_completed ? 'Markeren als openstaand' : 'Markeren als voltooid';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (btnSubtasks) btnSubtasks.classList.remove('hidden');
|
||||||
if (btnOutlook) {
|
if (btnOutlook) {
|
||||||
if (window.isOutlookConnected && !taskData.external_id && !taskData.is_completed) {
|
if (window.isOutlookConnected && !taskData.external_id && !taskData.is_completed) {
|
||||||
btnOutlook.classList.remove('hidden');
|
btnOutlook.classList.remove('hidden');
|
||||||
@@ -3184,9 +3286,9 @@ function handleTaskContextMenu(e, taskData) {
|
|||||||
btnOutlook.classList.add('hidden');
|
btnOutlook.classList.add('hidden');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Koppel actieknoppen
|
// Koppel actieknoppen
|
||||||
const btnOpen = document.getElementById('taskCtxBtnOpen');
|
|
||||||
if (btnOpen) {
|
if (btnOpen) {
|
||||||
btnOpen.onclick = () => {
|
btnOpen.onclick = () => {
|
||||||
closeTaskContextMenu();
|
closeTaskContextMenu();
|
||||||
@@ -3194,7 +3296,6 @@ function handleTaskContextMenu(e, taskData) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const btnEdit = document.getElementById('taskCtxBtnEdit');
|
|
||||||
if (btnEdit) {
|
if (btnEdit) {
|
||||||
btnEdit.onclick = () => {
|
btnEdit.onclick = () => {
|
||||||
closeTaskContextMenu();
|
closeTaskContextMenu();
|
||||||
@@ -3202,7 +3303,6 @@ function handleTaskContextMenu(e, taskData) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const btnToggle = document.getElementById('taskCtxBtnToggle');
|
|
||||||
if (btnToggle) {
|
if (btnToggle) {
|
||||||
btnToggle.onclick = () => {
|
btnToggle.onclick = () => {
|
||||||
closeTaskContextMenu();
|
closeTaskContextMenu();
|
||||||
@@ -3210,7 +3310,6 @@ function handleTaskContextMenu(e, taskData) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const btnSubtasks = document.getElementById('taskCtxBtnSubtasks');
|
|
||||||
if (btnSubtasks) {
|
if (btnSubtasks) {
|
||||||
btnSubtasks.onclick = () => {
|
btnSubtasks.onclick = () => {
|
||||||
closeTaskContextMenu();
|
closeTaskContextMenu();
|
||||||
@@ -3231,11 +3330,14 @@ function handleTaskContextMenu(e, taskData) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const btnDelete = document.getElementById('taskCtxBtnDelete');
|
|
||||||
if (btnDelete) {
|
if (btnDelete) {
|
||||||
btnDelete.onclick = () => {
|
btnDelete.onclick = () => {
|
||||||
closeTaskContextMenu();
|
closeTaskContextMenu();
|
||||||
|
if (isOutlookEvent) {
|
||||||
|
deleteOutlookEvent(taskData.id, taskData.subject || taskData.title);
|
||||||
|
} else {
|
||||||
deleteTask(taskData.id);
|
deleteTask(taskData.id);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3291,7 +3393,10 @@ async function openTaskDetailModal(taskDataOrId) {
|
|||||||
modal.classList.remove('hidden');
|
modal.classList.remove('hidden');
|
||||||
modal.classList.add('flex');
|
modal.classList.add('flex');
|
||||||
|
|
||||||
// Haal live alle volledige details op inclusief beschrijving en subtaken
|
const isOutlookEvent = Boolean(task && (task.is_outlook_event || task.source === 'OUTLOOK_LIVE'));
|
||||||
|
|
||||||
|
// Als het een Outlook event is, hoeven we geen Fociz task ID API aan te roepen
|
||||||
|
if (!isOutlookEvent && taskId) {
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(`/api/planner.php?action=get_task_details&task_id=${taskId}`);
|
const resp = await fetch(`/api/planner.php?action=get_task_details&task_id=${taskId}`);
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
@@ -3316,6 +3421,7 @@ async function openTaskDetailModal(taskDataOrId) {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Fout bij ophalen taakdetails:', err);
|
console.error('Fout bij ophalen taakdetails:', err);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (window.lucide && lucide.createIcons) {
|
if (window.lucide && lucide.createIcons) {
|
||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
@@ -3332,16 +3438,31 @@ function populateTaskDetailModal(task, subtasks) {
|
|||||||
const timeSlotEl = document.getElementById('taskDetailTimeSlot');
|
const timeSlotEl = document.getElementById('taskDetailTimeSlot');
|
||||||
const durationEl = document.getElementById('taskDetailDuration');
|
const durationEl = document.getElementById('taskDetailDuration');
|
||||||
const descEl = document.getElementById('taskDetailDescription');
|
const descEl = document.getElementById('taskDetailDescription');
|
||||||
|
const subtasksSection = document.getElementById('taskDetailSubtasksSection');
|
||||||
const subtasksList = document.getElementById('taskDetailSubtasksList');
|
const subtasksList = document.getElementById('taskDetailSubtasksList');
|
||||||
const extBlock = document.getElementById('taskDetailExternalBlock');
|
const extBlock = document.getElementById('taskDetailExternalBlock');
|
||||||
const extLabel = document.getElementById('taskDetailExternalLabel');
|
const extLabel = document.getElementById('taskDetailExternalLabel');
|
||||||
const extLink = document.getElementById('taskDetailExternalLink');
|
const extLink = document.getElementById('taskDetailExternalLink');
|
||||||
const btnEdit = document.getElementById('taskDetailBtnEdit');
|
const btnEdit = document.getElementById('taskDetailBtnEdit');
|
||||||
|
const btnEditLabel = document.getElementById('taskDetailBtnEditLabel');
|
||||||
const btnDelete = document.getElementById('taskDetailBtnDelete');
|
const btnDelete = document.getElementById('taskDetailBtnDelete');
|
||||||
|
const btnDeleteLabel = document.getElementById('taskDetailBtnDeleteLabel');
|
||||||
|
|
||||||
if (titleEl) titleEl.textContent = task.title || 'Geen titel';
|
const isOutlookEvent = Boolean(task && (task.is_outlook_event || task.source === 'OUTLOOK_LIVE'));
|
||||||
|
|
||||||
// Prioriteit
|
if (titleEl) titleEl.textContent = (isOutlookEvent ? (task.subject || task.title) : task.title) || 'Geen titel';
|
||||||
|
|
||||||
|
// Prioriteit & icoon
|
||||||
|
if (isOutlookEvent) {
|
||||||
|
if (priorityBadge) {
|
||||||
|
priorityBadge.textContent = 'Outlook Afspraak';
|
||||||
|
priorityBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-extrabold uppercase tracking-wide bg-sky-50 dark:bg-sky-500/15 text-sky-700 dark:text-sky-400 border border-sky-200 dark:border-sky-500/30';
|
||||||
|
}
|
||||||
|
if (priorityIcon) {
|
||||||
|
priorityIcon.className = 'w-10 h-10 rounded-2xl bg-sky-50 dark:bg-sky-500/10 border border-sky-200 dark:border-sky-500/20 text-sky-600 dark:text-sky-400 flex items-center justify-center flex-shrink-0 mt-0.5';
|
||||||
|
priorityIcon.innerHTML = '<i data-lucide="calendar" class="w-5 h-5"></i>';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
const p = task.priority || 'P2';
|
const p = task.priority || 'P2';
|
||||||
if (priorityBadge) {
|
if (priorityBadge) {
|
||||||
priorityBadge.textContent = p === 'P1' ? 'P1 · Hoge Prioriteit' : (p === 'P2' ? 'P2 · Belangrijk' : 'P3 · Regulier');
|
priorityBadge.textContent = p === 'P1' ? 'P1 · Hoge Prioriteit' : (p === 'P2' ? 'P2 · Belangrijk' : 'P3 · Regulier');
|
||||||
@@ -3356,9 +3477,17 @@ function populateTaskDetailModal(task, subtasks) {
|
|||||||
if (priorityIcon) priorityIcon.className = 'w-10 h-10 rounded-2xl bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-400 flex items-center justify-center flex-shrink-0 mt-0.5';
|
if (priorityIcon) priorityIcon.className = 'w-10 h-10 rounded-2xl bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-400 flex items-center justify-center flex-shrink-0 mt-0.5';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (priorityIcon) {
|
||||||
|
priorityIcon.innerHTML = '<i data-lucide="check-square" class="w-5 h-5"></i>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Source
|
// Source
|
||||||
if (sourceBadge) {
|
if (sourceBadge) {
|
||||||
|
if (isOutlookEvent) {
|
||||||
|
sourceBadge.textContent = 'OUTLOOK 365';
|
||||||
|
sourceBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-mono font-bold bg-sky-50 dark:bg-sky-500/15 text-sky-700 dark:text-sky-400 border border-sky-200 dark:border-sky-500/30';
|
||||||
|
} else {
|
||||||
const src = (task.source || 'FOCIZ').toUpperCase();
|
const src = (task.source || 'FOCIZ').toUpperCase();
|
||||||
sourceBadge.textContent = src;
|
sourceBadge.textContent = src;
|
||||||
if (src === 'JIRA') {
|
if (src === 'JIRA') {
|
||||||
@@ -3373,9 +3502,14 @@ function populateTaskDetailModal(task, subtasks) {
|
|||||||
sourceBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-mono font-bold bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 border border-slate-200 dark:border-slate-700';
|
sourceBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-mono font-bold bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 border border-slate-200 dark:border-slate-700';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Status
|
// Status
|
||||||
if (statusBadge) {
|
if (statusBadge) {
|
||||||
|
if (isOutlookEvent) {
|
||||||
|
statusBadge.textContent = 'Agenda Afspraak';
|
||||||
|
statusBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-bold bg-indigo-50 dark:bg-indigo-500/15 text-indigo-700 dark:text-indigo-400 border border-indigo-200 dark:border-indigo-500/30';
|
||||||
|
} else {
|
||||||
if (task.is_completed) {
|
if (task.is_completed) {
|
||||||
statusBadge.textContent = 'Voltooid';
|
statusBadge.textContent = 'Voltooid';
|
||||||
statusBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-bold bg-emerald-50 dark:bg-emerald-500/15 text-emerald-700 dark:text-emerald-400 border border-emerald-200 dark:border-emerald-500/30';
|
statusBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-bold bg-emerald-50 dark:bg-emerald-500/15 text-emerald-700 dark:text-emerald-400 border border-emerald-200 dark:border-emerald-500/30';
|
||||||
@@ -3384,6 +3518,7 @@ function populateTaskDetailModal(task, subtasks) {
|
|||||||
statusBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-bold bg-sky-50 dark:bg-sky-500/15 text-sky-700 dark:text-sky-400 border border-sky-200 dark:border-sky-500/30';
|
statusBadge.className = 'px-2 py-0.5 rounded-full text-[10px] font-bold bg-sky-50 dark:bg-sky-500/15 text-sky-700 dark:text-sky-400 border border-sky-200 dark:border-sky-500/30';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Datum & Tijd
|
// Datum & Tijd
|
||||||
if (dateEl) {
|
if (dateEl) {
|
||||||
@@ -3393,11 +3528,33 @@ function populateTaskDetailModal(task, subtasks) {
|
|||||||
timeSlotEl.textContent = (task.start) ? `${task.start} - ${task.end || '?'}` : 'Geen tijdblok';
|
timeSlotEl.textContent = (task.start) ? `${task.start} - ${task.end || '?'}` : 'Geen tijdblok';
|
||||||
}
|
}
|
||||||
if (durationEl) {
|
if (durationEl) {
|
||||||
durationEl.textContent = `${task.duration || 30} minuten`;
|
let dur = task.duration;
|
||||||
|
if (!dur && task.start && task.end) {
|
||||||
|
const sm = parseTimeToMinutes(task.start);
|
||||||
|
const em = parseTimeToMinutes(task.end);
|
||||||
|
if (sm !== null && em !== null && em > sm) dur = em - sm;
|
||||||
|
}
|
||||||
|
durationEl.textContent = `${dur || 30} minuten`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Beschrijving
|
// Beschrijving / Locatie
|
||||||
if (descEl) {
|
if (descEl) {
|
||||||
|
if (isOutlookEvent) {
|
||||||
|
let descParts = [];
|
||||||
|
if (task.location && task.location !== 'Geen locatie') {
|
||||||
|
descParts.push(`Locatie: ${task.location}`);
|
||||||
|
}
|
||||||
|
if (task.description && task.description.trim()) {
|
||||||
|
descParts.push(task.description.trim());
|
||||||
|
}
|
||||||
|
if (descParts.length > 0) {
|
||||||
|
descEl.textContent = descParts.join('\n\n');
|
||||||
|
descEl.classList.remove('italic', 'text-slate-400');
|
||||||
|
} else {
|
||||||
|
descEl.textContent = 'Geen extra notities of locatie voor deze Outlook-afspraak.';
|
||||||
|
descEl.classList.add('italic', 'text-slate-400');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if (task.description && task.description.trim()) {
|
if (task.description && task.description.trim()) {
|
||||||
descEl.textContent = task.description;
|
descEl.textContent = task.description;
|
||||||
descEl.classList.remove('italic', 'text-slate-400');
|
descEl.classList.remove('italic', 'text-slate-400');
|
||||||
@@ -3406,25 +3563,46 @@ function populateTaskDetailModal(task, subtasks) {
|
|||||||
descEl.classList.add('italic', 'text-slate-400');
|
descEl.classList.add('italic', 'text-slate-400');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Subtaken
|
// Subtaken: verbergen voor Outlook afspraken
|
||||||
|
if (subtasksSection) {
|
||||||
|
if (isOutlookEvent) {
|
||||||
|
subtasksSection.classList.add('hidden');
|
||||||
|
} else {
|
||||||
|
subtasksSection.classList.remove('hidden');
|
||||||
if (subtasksList) {
|
if (subtasksList) {
|
||||||
renderSubtasksInDetailModal(task.id, subtasks || []);
|
renderSubtasksInDetailModal(task.id, subtasks || []);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Externe link
|
// Externe link
|
||||||
if (extBlock && extLink && extLabel) {
|
if (extBlock && extLink && extLabel) {
|
||||||
if (task.external_url) {
|
const extUrl = isOutlookEvent ? (task.meeting_url || task.web_link || task.external_url) : task.external_url;
|
||||||
|
if (extUrl) {
|
||||||
extBlock.classList.remove('hidden');
|
extBlock.classList.remove('hidden');
|
||||||
extLink.href = task.external_url;
|
extLink.href = extUrl;
|
||||||
if (task.source === 'JIRA') {
|
if (isOutlookEvent) {
|
||||||
|
if (task.meeting_url && task.meeting_url.includes('teams.microsoft.com')) {
|
||||||
|
extLabel.textContent = 'Deelnemen via Microsoft Teams';
|
||||||
|
extLink.querySelector('span') && (extLink.querySelector('span').textContent = 'Deelnemen');
|
||||||
|
} else {
|
||||||
|
extLabel.textContent = 'Gekoppeld in Microsoft Outlook Agenda';
|
||||||
|
extLink.querySelector('span') && (extLink.querySelector('span').textContent = 'In Outlook');
|
||||||
|
}
|
||||||
|
} else if (task.source === 'JIRA') {
|
||||||
extLabel.textContent = `Gekoppeld Jira Ticket: ${task.external_id || 'Jira'}`;
|
extLabel.textContent = `Gekoppeld Jira Ticket: ${task.external_id || 'Jira'}`;
|
||||||
|
extLink.querySelector('span') && (extLink.querySelector('span').textContent = 'Openen');
|
||||||
} else if (task.source === 'OUTLOOK' || task.source === 'MICROSOFT') {
|
} else if (task.source === 'OUTLOOK' || task.source === 'MICROSOFT') {
|
||||||
extLabel.textContent = 'Gekoppeld in Microsoft Outlook Agenda';
|
extLabel.textContent = 'Gekoppeld in Microsoft Outlook Agenda';
|
||||||
|
extLink.querySelector('span') && (extLink.querySelector('span').textContent = 'Openen');
|
||||||
} else if (task.source === 'OUTLOOK_MAIL') {
|
} else if (task.source === 'OUTLOOK_MAIL') {
|
||||||
extLabel.textContent = 'Afkomstig uit Outlook E-mail';
|
extLabel.textContent = 'Afkomstig uit Outlook E-mail';
|
||||||
|
extLink.querySelector('span') && (extLink.querySelector('span').textContent = 'Openen');
|
||||||
} else {
|
} else {
|
||||||
extLabel.textContent = 'Externe koppeling';
|
extLabel.textContent = 'Externe koppeling';
|
||||||
|
extLink.querySelector('span') && (extLink.querySelector('span').textContent = 'Openen');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
extBlock.classList.add('hidden');
|
extBlock.classList.add('hidden');
|
||||||
@@ -3432,6 +3610,13 @@ function populateTaskDetailModal(task, subtasks) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
|
if (btnEditLabel) {
|
||||||
|
btnEditLabel.textContent = isOutlookEvent ? 'Afspraak Bewerken' : 'Taak Bewerken';
|
||||||
|
}
|
||||||
|
if (btnDeleteLabel) {
|
||||||
|
btnDeleteLabel.textContent = isOutlookEvent ? 'Afspraak Verwijderen' : 'Verwijderen';
|
||||||
|
}
|
||||||
|
|
||||||
if (btnEdit) {
|
if (btnEdit) {
|
||||||
btnEdit.onclick = () => {
|
btnEdit.onclick = () => {
|
||||||
closeTaskDetailModal();
|
closeTaskDetailModal();
|
||||||
@@ -3441,7 +3626,11 @@ function populateTaskDetailModal(task, subtasks) {
|
|||||||
if (btnDelete) {
|
if (btnDelete) {
|
||||||
btnDelete.onclick = () => {
|
btnDelete.onclick = () => {
|
||||||
closeTaskDetailModal();
|
closeTaskDetailModal();
|
||||||
|
if (isOutlookEvent) {
|
||||||
|
deleteOutlookEvent(task.id, task.subject || task.title);
|
||||||
|
} else {
|
||||||
deleteTask(task.id);
|
deleteTask(task.id);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4002,19 +4191,19 @@ function toggleWidgetCollapse(wId) {
|
|||||||
function toggleDaySelectionCollapse() {
|
function toggleDaySelectionCollapse() {
|
||||||
const strip = document.getElementById('weekDaysStripContainer');
|
const strip = document.getElementById('weekDaysStripContainer');
|
||||||
const icon = document.getElementById('iconToggleDaySelection');
|
const icon = document.getElementById('iconToggleDaySelection');
|
||||||
const label = document.getElementById('labelToggleDaySelection');
|
const btn = document.getElementById('btnToggleDaySelection');
|
||||||
if (!strip) return;
|
if (!strip) return;
|
||||||
|
|
||||||
const isCurrentlyHidden = strip.classList.contains('hidden');
|
const isCurrentlyHidden = strip.classList.contains('hidden');
|
||||||
if (isCurrentlyHidden) {
|
if (isCurrentlyHidden) {
|
||||||
strip.classList.remove('hidden');
|
strip.classList.remove('hidden');
|
||||||
if (icon) icon.classList.remove('rotate-180');
|
if (icon) icon.classList.remove('rotate-180');
|
||||||
if (label) label.textContent = 'Dagen inklappen';
|
if (btn) btn.title = 'Dagen inklappen';
|
||||||
try { localStorage.setItem('fociz_days_selection_collapsed', '0'); } catch(e) {}
|
try { localStorage.setItem('fociz_days_selection_collapsed', '0'); } catch(e) {}
|
||||||
} else {
|
} else {
|
||||||
strip.classList.add('hidden');
|
strip.classList.add('hidden');
|
||||||
if (icon) icon.classList.add('rotate-180');
|
if (icon) icon.classList.add('rotate-180');
|
||||||
if (label) label.textContent = 'Dagen tonen';
|
if (btn) btn.title = 'Dagen tonen';
|
||||||
try { localStorage.setItem('fociz_days_selection_collapsed', '1'); } catch(e) {}
|
try { localStorage.setItem('fociz_days_selection_collapsed', '1'); } catch(e) {}
|
||||||
}
|
}
|
||||||
if (typeof lucide !== 'undefined' && lucide.createIcons) {
|
if (typeof lucide !== 'undefined' && lucide.createIcons) {
|
||||||
@@ -4027,10 +4216,10 @@ function initPlannerDaySelectionCollapse() {
|
|||||||
if (localStorage.getItem('fociz_days_selection_collapsed') === '1') {
|
if (localStorage.getItem('fociz_days_selection_collapsed') === '1') {
|
||||||
const strip = document.getElementById('weekDaysStripContainer');
|
const strip = document.getElementById('weekDaysStripContainer');
|
||||||
const icon = document.getElementById('iconToggleDaySelection');
|
const icon = document.getElementById('iconToggleDaySelection');
|
||||||
const label = document.getElementById('labelToggleDaySelection');
|
const btn = document.getElementById('btnToggleDaySelection');
|
||||||
if (strip) strip.classList.add('hidden');
|
if (strip) strip.classList.add('hidden');
|
||||||
if (icon) icon.classList.add('rotate-180');
|
if (icon) icon.classList.add('rotate-180');
|
||||||
if (label) label.textContent = 'Dagen tonen';
|
if (btn) btn.title = 'Dagen tonen';
|
||||||
}
|
}
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
@@ -4407,6 +4596,7 @@ plannerJiraAssigned.forEach(iss => {
|
|||||||
if (iss && iss.key && !plannerTicketStore.has(iss.key)) plannerTicketStore.set(iss.key, iss);
|
if (iss && iss.key && !plannerTicketStore.has(iss.key)) plannerTicketStore.set(iss.key, iss);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const plannedExternalIdsSet = new Set(<?= json_encode(array_keys($plannedExternalIds)) ?>);
|
||||||
let activePlannerJiraQueue = ''; // '': Alle, 'my': Mijn, 'DX': DX Queue, 'SD': SD Queue
|
let activePlannerJiraQueue = ''; // '': Alle, 'my': Mijn, 'DX': DX Queue, 'SD': SD Queue
|
||||||
|
|
||||||
function isIssueInQueue(iss, qCode) {
|
function isIssueInQueue(iss, qCode) {
|
||||||
@@ -4508,6 +4698,27 @@ function filterPlannerJira() {
|
|||||||
? '<span class="px-1.5 py-0.5 rounded text-[9px] font-bold bg-amber-50 dark:bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-200 dark:border-amber-500/20">DX</span>'
|
? '<span class="px-1.5 py-0.5 rounded text-[9px] font-bold bg-amber-50 dark:bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-200 dark:border-amber-500/20">DX</span>'
|
||||||
: '<span class="px-1.5 py-0.5 rounded text-[9px] font-bold bg-blue-50 dark:bg-blue-500/10 text-blue-600 dark:text-blue-400 border border-blue-200 dark:border-blue-500/20">SD</span>';
|
: '<span class="px-1.5 py-0.5 rounded text-[9px] font-bold bg-blue-50 dark:bg-blue-500/10 text-blue-600 dark:text-blue-400 border border-blue-200 dark:border-blue-500/20">SD</span>';
|
||||||
|
|
||||||
|
const isPlanned = plannedExternalIdsSet.has(issue.key);
|
||||||
|
const planBtn = isPlanned
|
||||||
|
? `<button
|
||||||
|
onclick="quickImportJiraByKey(this, '${keyEsc}')"
|
||||||
|
data-jira-key="${keyEsc}"
|
||||||
|
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 opnieuw/anders in te plannen)"
|
||||||
|
>
|
||||||
|
<i data-lucide="check" class="w-3 h-3 stroke-[2.5]"></i>
|
||||||
|
<span>Ingepland</span>
|
||||||
|
</button>`
|
||||||
|
: `<button
|
||||||
|
onclick="quickImportJiraByKey(this, '${keyEsc}')"
|
||||||
|
data-jira-key="${keyEsc}"
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
<i data-lucide="calendar-plus" class="w-3 h-3"></i>
|
||||||
|
<span>Plan</span>
|
||||||
|
</button>`;
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="fociz-card-interactive flex items-center justify-between p-2.5 rounded-xl bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-200/80 dark:border-slate-800/80 hover:border-indigo-300 dark:hover:border-indigo-600/50 transition shadow-2xs">
|
<div class="fociz-card-interactive flex items-center justify-between p-2.5 rounded-xl bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-200/80 dark:border-slate-800/80 hover:border-indigo-300 dark:hover:border-indigo-600/50 transition shadow-2xs">
|
||||||
<div class="truncate mr-2 min-w-0 flex-1 cursor-pointer" onclick="openJiraTicketDetail('${keyEsc}')" title="Klik voor ticketdetails">
|
<div class="truncate mr-2 min-w-0 flex-1 cursor-pointer" onclick="openJiraTicketDetail('${keyEsc}')" title="Klik voor ticketdetails">
|
||||||
@@ -4525,14 +4736,7 @@ function filterPlannerJira() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
${planBtn}
|
||||||
onclick="quickImportJiraByKey(this, '${keyEsc}')"
|
|
||||||
class="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"
|
|
||||||
>
|
|
||||||
<i data-lucide="calendar-plus" class="w-3 h-3"></i>
|
|
||||||
<span>Plan</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
@@ -4608,6 +4812,18 @@ function openJiraTicketDetail(issueKey) {
|
|||||||
linkEl.classList.add('hidden');
|
linkEl.classList.add('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Knop inplannen / ingepland status
|
||||||
|
const detailPlanBtn = document.getElementById('btnJiraDetailPlan');
|
||||||
|
if (detailPlanBtn) {
|
||||||
|
if (plannedExternalIdsSet.has(issue.key)) {
|
||||||
|
detailPlanBtn.className = "flex items-center space-x-1.5 px-3 py-1.5 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white font-bold text-xs transition cursor-pointer shadow-xs";
|
||||||
|
detailPlanBtn.innerHTML = '<i data-lucide="check" class="w-3.5 h-3.5 stroke-[2.5]"></i><span>Ingepland</span>';
|
||||||
|
} else {
|
||||||
|
detailPlanBtn.className = "flex items-center space-x-1.5 px-3 py-1.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white font-bold text-xs transition cursor-pointer";
|
||||||
|
detailPlanBtn.innerHTML = '<i data-lucide="calendar-plus" class="w-3.5 h-3.5"></i><span>Inplannen</span>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const modal = document.getElementById('jiraTicketDetailModal');
|
const modal = document.getElementById('jiraTicketDetailModal');
|
||||||
modal.classList.remove('hidden');
|
modal.classList.remove('hidden');
|
||||||
modal.classList.add('flex');
|
modal.classList.add('flex');
|
||||||
@@ -4960,6 +5176,23 @@ async function submitJiraPlanModal() {
|
|||||||
if (data.success) {
|
if (data.success) {
|
||||||
closeJiraPlanModal();
|
closeJiraPlanModal();
|
||||||
showWidgetToast(`<strong>${escapeHtml(issue.key)}</strong> ingepland als dagtaak`, 'success');
|
showWidgetToast(`<strong>${escapeHtml(issue.key)}</strong> ingepland als dagtaak`, 'success');
|
||||||
|
|
||||||
|
// Live update: voeg toe aan plannedExternalIdsSet en transformeer knoppen naar groen 'Ingepland'
|
||||||
|
if (typeof plannedExternalIdsSet !== 'undefined') {
|
||||||
|
plannedExternalIdsSet.add(issue.key);
|
||||||
|
}
|
||||||
|
document.querySelectorAll(`.plan-btn-jira[data-jira-key="${issue.key}"]`).forEach(btn => {
|
||||||
|
btn.className = "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";
|
||||||
|
btn.title = "Dit ticket is al ingepland als dagtaak (klik om opnieuw/anders in te plannen)";
|
||||||
|
btn.innerHTML = '<i data-lucide="check" class="w-3 h-3 stroke-[2.5]"></i><span>Ingepland</span>';
|
||||||
|
});
|
||||||
|
const detailPlanBtn = document.getElementById('btnJiraDetailPlan');
|
||||||
|
if (detailPlanBtn && typeof _jiraPlanCurrentIssue !== 'undefined' && _jiraPlanCurrentIssue && _jiraPlanCurrentIssue.key === issue.key) {
|
||||||
|
detailPlanBtn.className = "flex items-center space-x-1.5 px-3 py-1.5 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white font-bold text-xs transition cursor-pointer shadow-xs";
|
||||||
|
detailPlanBtn.innerHTML = '<i data-lucide="check" class="w-3.5 h-3.5 stroke-[2.5]"></i><span>Ingepland</span>';
|
||||||
|
}
|
||||||
|
if (typeof lucide !== 'undefined' && lucide.createIcons) lucide.createIcons();
|
||||||
|
|
||||||
if (typeof switchPlannerDate === 'function') {
|
if (typeof switchPlannerDate === 'function') {
|
||||||
switchPlannerDate(planDate, null, false);
|
switchPlannerDate(planDate, null, false);
|
||||||
}
|
}
|
||||||
@@ -5263,6 +5496,20 @@ async function submitPlanEmailDirect() {
|
|||||||
if (data.success) {
|
if (data.success) {
|
||||||
closePlanEmailDirectModal();
|
closePlanEmailDirectModal();
|
||||||
showWidgetToast(`<strong>E-mail</strong> succesvol ingepland als dagtaak`, 'success');
|
showWidgetToast(`<strong>E-mail</strong> succesvol ingepland als dagtaak`, 'success');
|
||||||
|
|
||||||
|
// Live update: voeg toe aan plannedExternalIdsSet en transformeer knoppen naar groen 'Ingepland'
|
||||||
|
if (mailId) {
|
||||||
|
if (typeof plannedExternalIdsSet !== 'undefined') {
|
||||||
|
plannedExternalIdsSet.add(mailId);
|
||||||
|
}
|
||||||
|
document.querySelectorAll(`.plan-btn-mail[data-mail-id="${mailId}"]`).forEach(btn => {
|
||||||
|
btn.className = "plan-btn-mail 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 cursor-pointer";
|
||||||
|
btn.title = "Deze e-mail is al ingepland als dagtaak (klik om opnieuw/anders in te plannen)";
|
||||||
|
btn.innerHTML = '<i data-lucide="check" class="w-3 h-3 stroke-[2.5]"></i><span>Ingepland</span>';
|
||||||
|
});
|
||||||
|
if (typeof lucide !== 'undefined' && lucide.createIcons) lucide.createIcons();
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof switchPlannerDate === 'function') {
|
if (typeof switchPlannerDate === 'function') {
|
||||||
switchPlannerDate(planDate, null, false);
|
switchPlannerDate(planDate, null, false);
|
||||||
}
|
}
|
||||||
@@ -6122,8 +6369,94 @@ function setSelectedEditTaskDuration(mins) {
|
|||||||
if (durationDisplay) durationDisplay.innerText = `${mins}m`;
|
if (durationDisplay) durationDisplay.innerText = `${mins}m`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Taak & Tijdblok Bewerken Modal Logica ---
|
||||||
|
function openEditTaskModal(task) {
|
||||||
|
if (!task) return;
|
||||||
|
const isOutlook = Boolean(task.is_outlook_event || task.source === 'OUTLOOK_LIVE');
|
||||||
|
|
||||||
|
document.getElementById('editTaskId').value = task.id;
|
||||||
|
const isOutlookInput = document.getElementById('editTaskIsOutlook');
|
||||||
|
if (isOutlookInput) isOutlookInput.value = isOutlook ? '1' : '0';
|
||||||
|
|
||||||
|
const headerTitle = document.getElementById('editTaskModalHeaderTitle');
|
||||||
|
const headerSubtitle = document.getElementById('editTaskModalHeaderSubtitle');
|
||||||
|
const titleLabel = document.getElementById('editTaskTitleLabel');
|
||||||
|
const priorityContainer = document.getElementById('editTaskPriorityContainer');
|
||||||
|
const saveBtn = document.getElementById('btnSaveEditTask');
|
||||||
|
|
||||||
|
if (isOutlook) {
|
||||||
|
if (headerTitle) headerTitle.textContent = 'Outlook Afspraak Bewerken';
|
||||||
|
if (headerSubtitle) headerSubtitle.textContent = 'Pas onderwerp, tijdblok en datum van je agenda-afspraak aan';
|
||||||
|
if (titleLabel) titleLabel.textContent = 'Onderwerp';
|
||||||
|
if (priorityContainer) priorityContainer.classList.add('hidden');
|
||||||
|
if (saveBtn) saveBtn.textContent = 'Afspraak Opslaan';
|
||||||
|
} else {
|
||||||
|
if (headerTitle) headerTitle.textContent = 'Taak & Tijdblok Bewerken';
|
||||||
|
if (headerSubtitle) headerSubtitle.textContent = 'Pas titel, start-/eindtijd en tijdsduur vrij aan';
|
||||||
|
if (titleLabel) titleLabel.textContent = 'Taaktitel';
|
||||||
|
if (priorityContainer) priorityContainer.classList.remove('hidden');
|
||||||
|
if (saveBtn) saveBtn.textContent = 'Wijzigingen Opslaan';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('editTaskTitle').value = (isOutlook ? (task.subject || task.title) : task.title) || '';
|
||||||
|
const descEl = document.getElementById('editTaskDescription');
|
||||||
|
if (descEl) descEl.value = task.description || '';
|
||||||
|
document.getElementById('editTaskPriority').value = task.priority || 'P2';
|
||||||
|
document.getElementById('editTaskStartTime').value = task.start || '';
|
||||||
|
document.getElementById('editTaskEndTime').value = task.end || '';
|
||||||
|
|
||||||
|
// Datum
|
||||||
|
const dateInput = document.getElementById('editTaskDate');
|
||||||
|
if (dateInput) {
|
||||||
|
dateInput.value = task.date || (typeof activePlannerDate !== 'undefined' ? activePlannerDate : new Date().toISOString().slice(0, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset statusbericht van open blok
|
||||||
|
const notice = document.getElementById('editTaskOpenBlockNotice');
|
||||||
|
if (notice) notice.classList.add('hidden');
|
||||||
|
|
||||||
|
let dur = task.duration;
|
||||||
|
if (!dur && task.start && task.end) {
|
||||||
|
const sm = parseTimeToMinutes(task.start);
|
||||||
|
const em = parseTimeToMinutes(task.end);
|
||||||
|
if (sm !== null && em !== null && em > sm) dur = em - sm;
|
||||||
|
}
|
||||||
|
dur = dur || 30;
|
||||||
|
setSelectedEditTaskDuration(dur);
|
||||||
|
|
||||||
|
const outlookNotice = document.getElementById('editTaskOutlookNotice');
|
||||||
|
if (outlookNotice) {
|
||||||
|
if (isOutlook) {
|
||||||
|
outlookNotice.classList.remove('hidden');
|
||||||
|
outlookNotice.innerHTML = '<i data-lucide="refresh-cw" class="w-3.5 h-3.5 flex-shrink-0"></i><span>Wijzigingen worden direct gesynchroniseerd naar Microsoft Outlook 365.</span>';
|
||||||
|
} else if (task.source === 'OUTLOOK' || task.external_id) {
|
||||||
|
outlookNotice.classList.remove('hidden');
|
||||||
|
outlookNotice.innerHTML = '<i data-lucide="refresh-cw" class="w-3.5 h-3.5 flex-shrink-0"></i><span>Deze taak is gekoppeld aan Outlook 365. Wijzigingen worden automatisch in uw Outlook agenda gesynchroniseerd.</span>';
|
||||||
|
} else {
|
||||||
|
outlookNotice.classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const modal = document.getElementById('editTaskModal');
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
modal.classList.add('flex');
|
||||||
|
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||||
|
document.getElementById('editTaskTitle')?.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeEditTaskModal() {
|
||||||
|
const modal = document.getElementById('editTaskModal');
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
modal.classList.remove('flex');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function submitEditTask() {
|
async function submitEditTask() {
|
||||||
const taskId = parseInt(document.getElementById('editTaskId').value, 10);
|
const isOutlook = document.getElementById('editTaskIsOutlook')?.value === '1';
|
||||||
|
const taskId = document.getElementById('editTaskId').value;
|
||||||
const title = document.getElementById('editTaskTitle').value.trim();
|
const title = document.getElementById('editTaskTitle').value.trim();
|
||||||
const priority = document.getElementById('editTaskPriority').value;
|
const priority = document.getElementById('editTaskPriority').value;
|
||||||
const dateInput = document.getElementById('editTaskDate');
|
const dateInput = document.getElementById('editTaskDate');
|
||||||
@@ -6134,7 +6467,7 @@ async function submitEditTask() {
|
|||||||
const saveBtn = document.getElementById('btnSaveEditTask');
|
const saveBtn = document.getElementById('btnSaveEditTask');
|
||||||
|
|
||||||
if (!title) {
|
if (!title) {
|
||||||
alert('Vul een taaktitel in.');
|
alert(isOutlook ? 'Vul een onderwerp in voor de afspraak.' : 'Vul een taaktitel in.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6149,7 +6482,7 @@ async function submitEditTask() {
|
|||||||
if (answer === 'NEE') {
|
if (answer === 'NEE') {
|
||||||
// Automatisch naar volgende werkdag met eerder gezette voorwaarden
|
// Automatisch naar volgende werkdag met eerder gezette voorwaarden
|
||||||
try {
|
try {
|
||||||
const nextSlot = await findSlotOnNextWorkday(duration, taskId);
|
const nextSlot = await findSlotOnNextWorkday(duration, isOutlook ? 0 : parseInt(taskId, 10));
|
||||||
taskDate = nextSlot.date;
|
taskDate = nextSlot.date;
|
||||||
startTime = nextSlot.start;
|
startTime = nextSlot.start;
|
||||||
endTime = nextSlot.end;
|
endTime = nextSlot.end;
|
||||||
@@ -6163,17 +6496,49 @@ async function submitEditTask() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (saveBtn) {
|
if (saveBtn) {
|
||||||
saveBtn.innerText = 'Opslaan...';
|
saveBtn.innerText = isOutlook ? 'Afspraak Opslaan...' : 'Opslaan...';
|
||||||
saveBtn.disabled = true;
|
saveBtn.disabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const description = document.getElementById('editTaskDescription')?.value.trim() || '';
|
const description = document.getElementById('editTaskDescription')?.value.trim() || '';
|
||||||
|
|
||||||
|
if (isOutlook) {
|
||||||
|
const res = await fetch('/api/planner.php?action=update_outlook_event', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({
|
||||||
|
event_id: taskId,
|
||||||
|
title: title,
|
||||||
|
description: description,
|
||||||
|
time_block_start: startTime,
|
||||||
|
time_block_end: endTime,
|
||||||
|
date: taskDate
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.success) {
|
||||||
|
closeEditTaskModal();
|
||||||
|
const targetDate = data.date || taskDate;
|
||||||
|
if (typeof switchPlannerDate === 'function') {
|
||||||
|
switchPlannerDate(targetDate, null, false);
|
||||||
|
}
|
||||||
|
if (typeof showWidgetToast === 'function') {
|
||||||
|
showWidgetToast(`Outlook afspraak <strong>${escapeHtml(title)}</strong> succesvol bijgewerkt`, 'success');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert(data.error || 'Kon afspraak niet bijwerken in Microsoft Outlook');
|
||||||
|
if (saveBtn) {
|
||||||
|
saveBtn.innerText = 'Afspraak Opslaan';
|
||||||
|
saveBtn.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
const res = await fetch('/api/planner.php?action=update_task', {
|
const res = await fetch('/api/planner.php?action=update_task', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
task_id: taskId,
|
task_id: parseInt(taskId, 10),
|
||||||
title: title,
|
title: title,
|
||||||
description: description,
|
description: description,
|
||||||
priority: priority,
|
priority: priority,
|
||||||
@@ -6200,15 +6565,40 @@ async function submitEditTask() {
|
|||||||
saveBtn.disabled = false;
|
saveBtn.disabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert('Netwerkfout bij bijwerken van de taak');
|
alert('Netwerkfout bij bijwerken');
|
||||||
if (saveBtn) {
|
if (saveBtn) {
|
||||||
saveBtn.innerText = 'Wijzigingen Opslaan';
|
saveBtn.innerText = isOutlook ? 'Afspraak Opslaan' : 'Wijzigingen Opslaan';
|
||||||
saveBtn.disabled = false;
|
saveBtn.disabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteOutlookEvent(eventId, eventSubject) {
|
||||||
|
const displayTitle = eventSubject ? ` "${eventSubject}"` : '';
|
||||||
|
if (!confirm(`Weet je zeker dat je de Outlook afspraak${displayTitle} wilt verwijderen uit je Microsoft Outlook agenda?`)) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/planner.php?action=delete_outlook_event', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({ event_id: eventId })
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.success) {
|
||||||
|
if (typeof showWidgetToast === 'function') {
|
||||||
|
showWidgetToast(`Afspraak <strong>${escapeHtml(eventSubject || 'Outlook')}</strong> verwijderd`, 'info');
|
||||||
|
}
|
||||||
|
switchPlannerDate(activePlannerDate, null, false);
|
||||||
|
} else {
|
||||||
|
alert(data.error || 'Kon afspraak niet verwijderen uit Outlook');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
alert('Netwerkfout bij verwijderen van Outlook afspraak');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function syncTaskToOutlook(taskId, btn) {
|
async function syncTaskToOutlook(taskId, btn) {
|
||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
const originalHtml = btn.innerHTML;
|
const originalHtml = btn.innerHTML;
|
||||||
|
|||||||
Reference in New Issue
Block a user