feat(planner): add widget layout saving and scratchpad note functionality

feat(notifications): implement real-time live sync for mail, Jira, and calendar updates

fix(database): add new columns for widget layout and last check timestamps

fix(schema): update users table schema to include new fields for widget layout and last checks

feat(microsoft): enhance Microsoft integration with demo email data and inbox fetching

feat(jira): add listener for live updates on Jira tickets
This commit is contained in:
2026-09-10 14:16:45 +02:00
parent dc0b33a278
commit fb38c42fe9
7 changed files with 636 additions and 7 deletions
+102 -2
View File
@@ -408,13 +408,113 @@
}
};
// 7b. Real-time Live Sync (Mail, Jira & Agenda)
let lastKnownMailId = localStorage.getItem('fociz_last_mail_id') || '';
let lastKnownJiraKey = localStorage.getItem('fociz_last_jira_key') || '';
let lastCalendarHash = '';
let isLiveSyncRunning = false;
let isInitialLiveSync = true;
async function checkLiveSync() {
if (isLiveSyncRunning) return;
isLiveSyncRunning = true;
try {
const url = `/api/live_sync.php?last_mail_id=${encodeURIComponent(lastKnownMailId)}&last_jira_key=${encodeURIComponent(lastKnownJiraKey)}&calendar_hash=${encodeURIComponent(lastCalendarHash)}`;
const res = await fetch(url, { cache: 'no-store' });
if (res.status === 401) return;
if (res.ok) {
const data = await res.json();
if (data && data.success) {
// Update mail badge in navigatie
const mailBadge = document.getElementById('navMailBadge');
if (mailBadge && typeof data.mail?.unread_count !== 'undefined') {
if (data.mail.unread_count > 0) {
mailBadge.innerText = data.mail.unread_count;
mailBadge.classList.remove('hidden');
} else {
mailBadge.classList.add('hidden');
}
}
// E-mail melding
if (!isInitialLiveSync && data.mail?.has_new && data.mail.new_mail) {
const m = data.mail.new_mail;
sendBrowserNotification(
`✉️ Nieuwe mail: ${m.from_name}`,
`${m.subject} • ${m.received_human || 'Zojuist'}`,
m.web_link || '/mail.php',
`fociz-mail-${m.id}`
);
}
// Jira ticket melding
if (!isInitialLiveSync && data.jira?.has_new && data.jira.new_issue) {
const j = data.jira.new_issue;
sendBrowserNotification(
`⚡ Nieuw Jira ticket: [${j.key}]`,
`${j.summary} (${j.priority})`,
j.url || '/jira.php',
`fociz-jira-${j.key}`
);
}
// Dispatch custom event voor actieve pagina's
if (data.mail?.has_new || (data.mail?.latest_id && data.mail.latest_id !== lastKnownMailId)) {
window.dispatchEvent(new CustomEvent('fociz:mail-updated', { detail: data.mail }));
}
if (data.jira?.has_new || (data.jira?.newest_key && data.jira.newest_key !== lastKnownJiraKey)) {
window.dispatchEvent(new CustomEvent('fociz:jira-updated', { detail: data.jira }));
}
if (data.calendar?.has_changes) {
window.dispatchEvent(new CustomEvent('fociz:calendar-updated', { detail: data.calendar }));
}
if (data.mail?.latest_id) {
lastKnownMailId = data.mail.latest_id;
localStorage.setItem('fociz_last_mail_id', lastKnownMailId);
}
if (data.jira?.newest_key) {
lastKnownJiraKey = data.jira.newest_key;
localStorage.setItem('fociz_last_jira_key', lastKnownJiraKey);
}
if (data.calendar?.hash) {
lastCalendarHash = data.calendar.hash;
}
isInitialLiveSync = false;
}
}
} catch(e) {
// Stil negeren bij netwerkvertraging
} finally {
isLiveSyncRunning = false;
}
}
// 10. Initialisatie
document.addEventListener('DOMContentLoaded', () => {
refreshEvents();
// Polling interval elke 60 seconden
checkLiveSync();
// Polling interval elke 60 seconden voor agenda evaluatie
setInterval(refreshEvents, 60000);
// Evaluatie interval elke 15 seconden
// Evaluatie interval elke 15 seconden voor agenda popups
setInterval(evaluateEvents, 15000);
// Live sync interval elke 12 seconden voor instant mail, Jira & calendar updates
setInterval(checkLiveSync, 12000);
window.addEventListener('focus', () => {
checkLiveSync();
refreshEvents();
});
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
checkLiveSync();
refreshEvents();
}
});
updateDotStatus();
});