portal_publico/static/js/notifications.js

197 lines
8.3 KiB
JavaScript

const PID_NEW_TOOLS_NOTIFICATIONS = [
{ id: "tool-calendario-individual", date: "01/08", title: "CALENDÁRIO INDIVIDUAL", breadcrumb: "Menu > Calendário Individual" },
{ id: "tool-perfis-acesso", date: "03/08", title: "PERFIS DE ACESSO", breadcrumb: "Menu > Administração > Perfis de Acesso" },
{ id: "tool-usuarios", date: "03/08", title: "USUÁRIOS", breadcrumb: "Menu > Administração > Usuários" },
{ id: "tool-widgets", date: "10/08", title: "WIDGETS NA TELA INICIAL", breadcrumb: "Menu > Principal > Widgets" },
];
const PID_NOTIF_ICON_TOOL =
'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2 3 14h7l-1 8 10-12h-7l1-8z" stroke-linejoin="round"/></svg>';
const PID_NOTIF_ICON_EVENT =
'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 9h18M8 3v4M16 3v4" stroke-linecap="round"/><path d="M9 15l2 2 4-4" stroke-linecap="round" stroke-linejoin="round"/></svg>';
const PID_NOTIF_ICON_RESTORE =
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12a9 9 0 1 0 3-6.7L3 8" stroke-linecap="round" stroke-linejoin="round"/><path d="M3 3v5h5" stroke-linecap="round" stroke-linejoin="round"/></svg>';
function pidFormatNotifDate(iso) {
const [, month, day] = iso.split("-");
return `${day}/${month}`;
}
async function pidFetchDismissedNotifIds() {
const list = await pidApiRequest("/notificacoes-dispensadas/");
return (list || []).map((n) => n.notif_id);
}
async function pidDismissNotification(id) {
await pidApiRequest("/notificacoes-dispensadas/", { method: "POST", body: { notif_id: id } });
}
async function pidRestoreNotification(id) {
await pidApiRequest(`/notificacoes-dispensadas/${id}/`, { method: "DELETE" });
}
function pidSortEventsByDate(a, b) {
const dateCompare = a.data.localeCompare(b.data);
return dateCompare !== 0 ? dateCompare : (a.horario || "").localeCompare(b.horario || "");
}
function pidMapEventToNotification(ev) {
return {
id: `event-${ev.id}`,
type: "event",
date: pidFormatNotifDate(ev.data),
title: ev.titulo.toUpperCase(),
breadcrumb: ev.sou_dono
? "Menu &gt; Calendário Individual"
: `Menu &gt; Calendário Individual &gt; Compartilhado por ${ev.dono_nome}`,
};
}
/** Só os compromissos "elegíveis" a notificação agora (lembrete configurado, já
* atingido e não no passado) — a mesma regra do sino. O histórico (ver
* `renderHistory` abaixo) reconstrói a partir de todos os eventos, sem essas
* duas restrições, já que um item dispensado pode ter saído desse recorte. */
function pidEventosElegiveisAgora(events) {
const now = new Date();
const todayIso = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
return events
.filter((ev) => ev.data >= todayIso && ev.notificar_em && new Date(ev.notificar_em) <= now)
.sort(pidSortEventsByDate)
.slice(0, 5)
.map(pidMapEventToNotification);
}
document.addEventListener("DOMContentLoaded", async () => {
const bell = document.getElementById("notif-bell");
const dropdown = document.getElementById("notif-dropdown");
const list = document.getElementById("notif-list");
const badge = document.getElementById("notif-badge");
const clearAllBtn = document.getElementById("notif-clear-all");
const historyBtn = document.getElementById("notif-history-btn");
const historyModal = document.getElementById("notif-history-modal");
const historyList = document.getElementById("notif-history-list");
const historyCloseBtn = document.getElementById("notif-history-close-btn");
if (!bell || !dropdown || !list) return;
const me = await pidGetMe();
const events = me ? await pidFetchEvents() : [];
const eventNotifications = pidEventosElegiveisAgora(events);
const allEventNotifications = events.slice().sort(pidSortEventsByDate).map(pidMapEventToNotification);
const toolNotifications = PID_NEW_TOOLS_NOTIFICATIONS.map((n) => ({ ...n, type: "tool" }));
let dismissedIds = me ? await pidFetchDismissedNotifIds() : [];
let notifications = [...eventNotifications, ...toolNotifications].filter((n) => !dismissedIds.includes(n.id));
function render() {
list.innerHTML = notifications.length
? notifications
.map(
(n) => `
<li class="notif-item notif-item--${n.type}" data-notif-id="${n.id}">
<span class="notif-item__icon">${n.type === "event" ? PID_NOTIF_ICON_EVENT : PID_NOTIF_ICON_TOOL}</span>
<div class="notif-item__body">
<p class="notif-item__title">${n.date} - ${n.title}</p>
<p class="notif-item__breadcrumb">${n.breadcrumb}</p>
</div>
<button type="button" class="notif-item__dismiss" data-notif-dismiss="${n.id}" aria-label="Remover notificação">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12" stroke-linecap="round"/></svg>
</button>
</li>`
)
.join("")
: `<li class="notif-empty">Nenhuma notificação no momento.</li>`;
if (badge) {
badge.hidden = notifications.length === 0;
badge.textContent = notifications.length;
}
}
bell.addEventListener("click", (event) => {
event.stopPropagation();
dropdown.hidden = !dropdown.hidden;
});
list.addEventListener("click", async (event) => {
const dismissBtn = event.target.closest("[data-notif-dismiss]");
if (!dismissBtn) return;
event.stopPropagation();
const id = dismissBtn.getAttribute("data-notif-dismiss");
await pidDismissNotification(id);
dismissedIds.push(id);
notifications = notifications.filter((n) => n.id !== id);
render();
});
if (clearAllBtn) {
clearAllBtn.addEventListener("click", async () => {
await Promise.all(notifications.map((n) => pidDismissNotification(n.id)));
dismissedIds.push(...notifications.map((n) => n.id));
notifications = [];
render();
});
}
document.addEventListener("click", (event) => {
if (!dropdown.hidden && !dropdown.contains(event.target) && event.target !== bell) {
dropdown.hidden = true;
}
if (historyModal && !historyModal.hidden && event.target === historyModal) {
historyModal.hidden = true;
}
});
function renderHistory() {
if (!historyList) return;
const historyItems = [...allEventNotifications, ...toolNotifications].filter((n) => dismissedIds.includes(n.id));
historyList.innerHTML = historyItems.length
? historyItems
.map(
(n) => `
<li class="notif-item notif-item--${n.type}" data-notif-id="${n.id}">
<span class="notif-item__icon">${n.type === "event" ? PID_NOTIF_ICON_EVENT : PID_NOTIF_ICON_TOOL}</span>
<div class="notif-item__body">
<p class="notif-item__title">${n.date} - ${n.title}</p>
<p class="notif-item__breadcrumb">${n.breadcrumb}</p>
</div>
<button type="button" class="notif-item__restore" data-notif-restore="${n.id}" aria-label="Restaurar notificação" title="Restaurar">
${PID_NOTIF_ICON_RESTORE}
</button>
</li>`
)
.join("")
: `<li class="notif-empty">Nenhuma notificação no histórico.</li>`;
}
if (historyBtn && historyModal) {
historyBtn.addEventListener("click", (event) => {
event.stopPropagation();
dropdown.hidden = true;
renderHistory();
historyModal.hidden = false;
});
}
if (historyCloseBtn) {
historyCloseBtn.addEventListener("click", () => {
historyModal.hidden = true;
});
}
if (historyList) {
historyList.addEventListener("click", async (event) => {
const restoreBtn = event.target.closest("[data-notif-restore]");
if (!restoreBtn) return;
const id = restoreBtn.getAttribute("data-notif-restore");
await pidRestoreNotification(id);
dismissedIds = dismissedIds.filter((dismissedId) => dismissedId !== id);
notifications = [...eventNotifications, ...toolNotifications].filter((n) => !dismissedIds.includes(n.id));
renderHistory();
render();
});
}
render();
});