feat: Add story points support and AI daily briefing feature

- Enhanced JIRA integration to fetch story points from multiple custom fields.
- Updated task display to show story points and their estimated duration.
- Introduced AI daily briefing banner with dynamic content loading.
- Implemented subtasks functionality with add, toggle, and delete capabilities.
- Improved task management UI with better accessibility and interaction.
This commit is contained in:
2026-09-10 22:38:06 +02:00
parent c0e0dd5947
commit 5ce08443cc
8 changed files with 780 additions and 69 deletions
+83
View File
@@ -195,3 +195,86 @@ function fociz_ai_smart_schedule(array $tasks, array $events, array $workdaySett
return ['success' => true, 'scheduled' => $aiRes['json']['scheduled']];
}
/**
* Genereert een persoonlijke AI dagbriefing op basis van taken, afspraken en Jira tickets
*
* @param string $userName Naam van de gebruiker
* @param string $date Datum (Y-m-d)
* @param array $tasks Openstaande taken van vandaag
* @param array $meetings Outlook meetings van vandaag
* @param array $jiraIssues Jira tickets (P1/blokkades)
* @param array $unfinishedYest Gisteren niet voltooide taken
* @return array ['success' => bool, 'briefing' => string, 'focus_tip' => string, 'warnings' => array]
*/
function fociz_ai_daily_briefing(
string $userName,
string $date,
array $tasks,
array $meetings,
array $jiraIssues = [],
array $unfinishedYest = []
): array {
$dutchDays = ['', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag', 'zondag'];
$dayName = $dutchDays[(int)date('N', strtotime($date))] ?? 'vandaag';
$dateLabel = $dayName . ' ' . date('j', strtotime($date)) . ' ' . date('F', strtotime($date));
$taskSummary = array_map(fn($t) => [
'title' => $t['title'],
'priority' => $t['priority'] ?? 'P2',
'time' => $t['time_block_start'] ?? null,
'duration' => (int)($t['duration_minutes'] ?? 30),
], array_slice($tasks, 0, 10));
$meetingSummary = array_map(fn($e) => [
'subject' => $e['subject'] ?? 'Afspraak',
'start' => $e['start'] ?? null,
'end' => $e['end'] ?? null,
], array_slice($meetings, 0, 8));
$jiraSummary = array_map(fn($j) => [
'key' => $j['key'] ?? '',
'title' => $j['summary'] ?? '',
'priority' => $j['priority'] ?? 'Medium',
'status' => $j['status'] ?? 'Open',
], array_slice(array_filter($jiraIssues, fn($j) => in_array($j['priority'] ?? '', ['Highest', 'High'])), 0, 5));
$yesterdaySummary = array_map(fn($t) => $t['title'], array_slice($unfinishedYest, 0, 5));
$systemPrompt = "Je bent de persoonlijke AI Co-Pilot van {$userName} in Fociz. "
. "Schrijf een korte, energieke en persoonlijke dagbriefing in het Nederlands voor {$dateLabel}. "
. "Wees direct, concreet en vriendelijk. Gebruik geen markdown headers, geen bullet symbolen zoals *, -, •. "
. "Geef 1-2 zinnen dagopening + 1 zin over de grootste prioriteit + 1 praktisch focustip. "
. "Maximaal 60 woorden totaal. "
. "Retourneer een JSON-object met:\n"
. "- 'greeting': openingszin (max 20 woorden)\n"
. "- 'focus_task': de meest urgente taak of meeting vandaag (max 15 woorden)\n"
. "- 'tip': één concrete focustip voor vandaag (max 20 woorden)\n"
. "- 'warnings': array van strings, max 2 items — alleen als er iets opvalt (gisteren niet afgerond, urgent Jira, overvolle dag)";
$userPrompt = json_encode([
'date' => $dateLabel,
'tasks_today' => $taskSummary,
'meetings_today' => $meetingSummary,
'urgent_jira' => $jiraSummary,
'unfinished_yesterday' => $yesterdaySummary,
'total_tasks' => count($tasks),
'total_meetings' => count($meetings),
], JSON_UNESCAPED_UNICODE);
$aiRes = fociz_ai_chat([
['role' => 'system', 'content' => $systemPrompt],
['role' => 'user', 'content' => $userPrompt],
], ['json_mode' => true, 'temperature' => 0.6, 'max_tokens' => 300]);
if (!$aiRes['success'] || empty($aiRes['json'])) {
return ['success' => false, 'error' => $aiRes['error'] ?? 'Kon geen dagbriefing genereren'];
}
return [
'success' => true,
'greeting' => $aiRes['json']['greeting'] ?? "Goedemorgen {$userName}, een nieuwe dag!",
'focus_task'=> $aiRes['json']['focus_task'] ?? '',
'tip' => $aiRes['json']['tip'] ?? '',
'warnings' => $aiRes['json']['warnings'] ?? [],
];
}
+14
View File
@@ -236,6 +236,9 @@ function jira_fetch_issues(string $domain, string $email, string $apiToken, stri
'description',
'labels',
'components',
'story_points',
'customfield_10016', // Story Points (Jira Cloud standaard)
'customfield_10028', // Story Points (alternatief veld sommige instanties)
];
// 1. Moderne Atlassian Jira Cloud endpoint (/rest/api/3/search/jql) conform CHANGE-2046 via POST
@@ -389,6 +392,16 @@ function jira_fetch_issues(string $domain, string $email, string $apiToken, stri
elseif (str_contains($sumLower, 'koppeling')) $onderwerpLogging = 'Koppelingen';
}
// Detectie van story points (customfield_10016 is Jira Cloud standaard, met fallback naar alternatieven)
$storyPoints = null;
if (isset($fields['customfield_10016']) && is_numeric($fields['customfield_10016'])) {
$storyPoints = (float)$fields['customfield_10016'];
} elseif (isset($fields['customfield_10028']) && is_numeric($fields['customfield_10028'])) {
$storyPoints = (float)$fields['customfield_10028'];
} elseif (isset($fields['story_points']) && is_numeric($fields['story_points'])) {
$storyPoints = (float)$fields['story_points'];
}
$issues[] = [
'key' => $key,
'summary' => $summary,
@@ -409,6 +422,7 @@ function jira_fetch_issues(string $domain, string $email, string $apiToken, stri
'created' => $created,
'updated' => $updated,
'url' => "{$cleanDomain}/browse/{$key}",
'story_points' => $storyPoints,
'source' => 'JIRA_LIVE',
];
}