TKK_E32230273/notifications.php

680 lines
32 KiB
PHP

<?php
session_start();
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
exit;
}
include 'koneksi.php';
// Auto Cleanup Trash (> 30 days)
try {
$pdo->exec("DELETE FROM notifications WHERE is_deleted = 1 AND deleted_at < NOW() - INTERVAL 30 DAY");
} catch(PDOException $e) {}
// Auto Move to Trash (Read > 7 days)
try {
// Karena kita tidak punya timestamp khusus 'kapan dibaca', kita hitung dari waktu notifikasi dibuat (time) + sudah is_read
$pdo->exec("UPDATE notifications SET is_deleted = 1, deleted_at = NOW() WHERE is_deleted = 0 AND is_read = 1 AND time < NOW() - INTERVAL 7 DAY");
} catch(PDOException $e) {}
// Fetch active notifications
$excludeCond = "AND title NOT LIKE '%baterai%' AND title NOT LIKE '%battery%' AND title NOT LIKE '%pesan%' AND title NOT LIKE '%message%' AND title NOT LIKE '%chat%'";
$stmt = $pdo->query("SELECT * FROM notifications WHERE is_deleted = 0 $excludeCond ORDER BY time DESC");
$notifications = $stmt->fetchAll();
// Fetch trash
$stmtTrash = $pdo->query("SELECT * FROM notifications WHERE is_deleted = 1 $excludeCond ORDER BY deleted_at DESC");
$trashItems = $stmtTrash->fetchAll();
function timeAgo($dt) {
$diff = (new DateTime())->diff(new DateTime($dt));
if ($diff->d > 0) return $diff->d . ' day(s) ago';
if ($diff->h > 0) return $diff->h . ' hour(s) ago';
if ($diff->i > 0) return $diff->i . ' minute(s) ago';
return 'Just now';
}
function groupNotifications($records) {
$grouped = [];
foreach ($records as $n) {
$dateObj = new DateTime($n['time']);
$today = new DateTime('today');
$yesterday = new DateTime('yesterday');
$dateOnly = $dateObj->format('Y-m-d');
if ($dateOnly === $today->format('Y-m-d')) {
$label = 'Today';
} elseif ($dateOnly === $yesterday->format('Y-m-d')) {
$label = 'Yesterday';
} else {
$label = $dateObj->format('d M Y');
}
$grouped[$label][] = $n;
}
return $grouped;
}
$grouped = groupNotifications($notifications);
$groupedTrash = groupNotifications($trashItems);
$unreadCount = count(array_filter($notifications, fn($n) => !$n['is_read']));
$dangerCount = count(array_filter($notifications, fn($n) => $n['is_danger'] || stripos($n['title'], 'terbuka') !== false || stripos($n['message'], 'terbuka') !== false));
$totalCount = count($notifications);
$trashCount = count($trashItems);
include 'header.php';
?>
<style>
/* Layout copied and adapted from schedule.php */
.sch-layout { display:flex; gap:0; align-items:stretch; min-height:500px; font-family:'Inter', sans-serif; }
.sch-sidebar { width:240px; flex-shrink:0; padding-right:20px; }
.sch-divider { width:1px; background:#e5e9f0; flex-shrink:0; }
.sch-main { flex:1; padding-left:24px; min-width:0; overflow:hidden; display:flex; flex-direction:column; }
@media(max-width:992px){
.sch-layout{ flex-direction:column; }
.sch-sidebar{ width:100%; padding-right:0; padding-bottom:16px; }
.sch-divider{ width:100%; height:1px; margin:8px 0; }
.sch-main{ padding-left:0; }
.ntf-filter-list { flex-direction:row; gap:6px; overflow-x:auto; padding-bottom:4px; }
.ntf-filter-item { white-space:nowrap; padding:6px 12px; font-size:12px; }
.nfi-icon { width:22px; height:22px; margin-right:6px; font-size:12px; }
.nfi-text { font-size:12px; }
.sch-sidebar hr, .sch-sidebar > div:last-child { display:none; }
.ntf-card { padding:10px 36px 10px 12px; }
.ntf-actions { display:block !important; opacity:0.5; right:2px; }
}
/* Sidebar Elements */
.ntf-filter-list { display:flex; flex-direction:column; gap:6px; }
.ntf-filter-item {
display:flex; align-items:center; padding:8px 12px; border-radius:8px;
text-decoration:none; color:#4b5563; transition:all 0.2s;
}
.ntf-filter-item:hover { background:#f9fafb; text-decoration:none; color:#111827; }
.ntf-filter-item.active { background:#f0f4ff; color:#4B49AC; font-weight:600; }
.nfi-icon {
width:28px; height:28px; border-radius:6px; display:flex; align-items:center; justify-content:center;
margin-right:12px; font-size:14px;
}
.nfi-text { flex:1; font-size:13px; }
.nfi-count {
background:#e5e7eb; color:#4b5563; font-size:11px; font-weight:700;
padding:2px 8px; border-radius:20px;
}
.ntf-stats-mini { display:flex; gap:10px; }
.nsm-item { flex:1; background:#f9fafb; border:1px solid #f3f4f6; border-radius:8px; padding:12px; text-align:center; }
.nsm-val { display:block; font-size:20px; font-weight:700; color:#111827; line-height:1; margin-bottom:4px; }
.nsm-lbl { display:block; font-size:11px; color:#6b7280; text-transform:uppercase; letter-spacing:0.5px; }
/* Main Content List */
.ntf-list-container { overflow-y:auto; max-height:600px; padding-right:10px; position:relative; }
.ntf-list-container::-webkit-scrollbar { width:5px; }
.ntf-list-container::-webkit-scrollbar-thumb { background:#ddd; border-radius:4px; }
.ntf-date-hdr { font-size:11px; font-weight:700; color:#9ca3af; text-transform:uppercase; letter-spacing:1px; margin:0 0 10px 4px; }
.ntf-group-wrap { margin-bottom:24px; }
/* Notification Card */
.ntf-card {
display:flex; align-items:center; padding:12px 28px 12px 16px; margin-bottom:8px;
background:#fff; border:1px solid #f3f4f6; border-radius:10px;
cursor:pointer; transition:all 0.2s; position:relative; overflow:hidden;
}
.ntf-card:hover { border-color:#e5e7eb; box-shadow:0 4px 12px rgba(0,0,0,0.03); transform:translateY(-1px); }
/* Hover Actions */
.ntf-actions {
opacity: 0; position:absolute; right:-4px; top:50%; transform:translateY(-50%);
background: transparent; border-radius:6px; padding:4px 8px; box-shadow: none;
color:#6b7280; z-index:10; transition:opacity 0.2s; font-size:22px; line-height:1;
display: none;
}
.ntf-card:not(.unread):hover .ntf-actions { opacity: 1; display: block; }
.ntf-actions:hover { color:#111827; }
/* Remove any borders and accents, rely strictly on background and color */
.ntf-card.unread { background:#eff6ff !important; border-color:#bfdbfe !important; }
.nc-icon {
width:36px; height:36px; border-radius:8px; background:#f3f4f6; color:#6b7280;
display:flex; align-items:center; justify-content:center; font-size:18px; flex-shrink:0; margin-right:14px;
}
/* matching unread strictly to the alert image */
.ntf-card.unread .nc-icon { background:#2563eb !important; color:#ffffff !important; border-radius:50% !important; }
.nc-body { flex:1; min-width:0; padding-right:16px; }
.nc-title { font-size:13px; font-weight:600; color:#1f2937; margin-bottom:2px; }
/* specific unread and danger title colors */
.ntf-card.unread .nc-title { color:#1e293b !important; }
.nc-desc { font-size:12px; color:#6b7280; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.ntf-card.unread .nc-desc { color:#475569 !important; }
.nc-meta { text-align:right; flex-shrink:0; }
.nc-time { display:block; font-size:12px; font-weight:600; color:#9ca3af; }
.ntf-card.unread .nc-time { color:#2563eb !important; }
.ntf-card.danger .nc-time { color:#ef4444 !important; } /* Only the time is red for danger */
.nc-ago { display:block; font-size:10px; color:#d1d5db; margin-top:2px; }
/* match ago text slightly stronger on selected rows */
.ntf-card.unread .nc-ago { color:#94a3b8 !important; }
.ntf-card.danger .nc-ago { color:#ef4444 !important; } /* Only the ago text is red for danger */
/* Modal Simple */
.nt-modal-simple { border:none; border-radius:14px; box-shadow:0 15px 35px rgba(0,0,0,0.1); overflow:hidden; }
.ntms-header { display:flex; align-items:center; padding:20px 24px 16px; border-bottom:1px solid #f3f4f6; background:#fff; }
.ntms-icon { width:42px; height:42px; border-radius:10px; display:flex; align-items:center; justify-content:center; font-size:22px; margin-right:16px; }
.ntms-title { margin:0 0 4px 0; font-size:15px; font-weight:700; color:#111827; }
.ntms-time { font-size:12px; color:#6b7280; font-weight:500; }
.ntms-body { padding:20px 24px 24px; background:#fcfcfd; }
.ntms-msg { font-size:14px; color:#374151; line-height:1.6; margin-bottom:16px; }
.ntms-detail { background:#fff; border:1px solid #e5e7eb; border-radius:8px; padding:12px 16px; font-size:12px; color:#4b5563; line-height:1.5; font-family:'Roboto Mono', monospace; }
.modal-theme-danger .ntms-icon { background:#fef2f2; color:#ef4444; }
.modal-theme-danger .ntms-title { color:#991b1b; }
.modal-theme-danger .ntms-detail { background:#fffafa; border-color:#fee2e2; color:#991b1b; }
.modal-theme-info .ntms-icon { background:#eff6ff; color:#2563eb; }
/* Context Menu Items */
.ctx-item { display:flex; align-items:center; padding:10px 16px; font-size:13px; color:#374151; cursor:pointer; transition:0.1s; }
.ctx-item:hover { background:#f3f4f6; color:#111827; }
.ctx-item i { margin-right:10px; font-size:16px; color:#6b7280; }
.ctx-item.text-danger:hover { background:#fef2f2; }
.ctx-item.text-danger i { color:#ef4444; }
</style>
<div class="main-panel">
<div class="content-wrapper">
<!-- Page Header -->
<div class="row">
<div class="col-md-12 grid-margin">
<div class="row align-items-center">
<div class="col-12 col-xl-8 mb-4 mb-xl-0">
<h3 class="font-weight-bold" style="font-family:'Inter', sans-serif;">Notifications</h3>
<h6 class="font-weight-normal mb-0 text-muted">System activity and security alerts</h6>
</div>
<div class="col-12 col-xl-4">
<div class="justify-content-end d-flex flex-wrap" style="gap:15px; align-items:center;">
<!-- Action buttons relocated to card body -->
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 grid-margin stretch-card">
<div class="card" style="border:1px solid #e5e9f0;box-shadow:none;border-radius:12px;">
<div class="card-body ntf-page-body" style="padding:24px;">
<!-- DUAL COLUMN LAYOUT (Copied from schedule.php & adapted) -->
<div class="sch-layout">
<!-- LEFT SIDEBAR -->
<div class="sch-sidebar">
<h5 style="font-size:14px;font-weight:700;color:#111827;margin-bottom:16px;">
<i class="mdi mdi-filter-variant mr-1 text-primary"></i> Filter
</h5>
<div class="ntf-filter-list">
<a href="#" class="ntf-filter-item active" data-filter="all" onclick="filterNtf(event, 'all', this)">
<div class="nfi-icon" style="color:#4B49AC;background:#f0f4ff;"><i class="mdi mdi-inbox-multiple"></i></div>
<div class="nfi-text">All Notifications</div>
</a>
<a href="#" class="ntf-filter-item" data-filter="unread" onclick="filterNtf(event, 'unread', this)">
<div class="nfi-icon" style="color:#0284c7;background:#f0f9ff;"><i class="mdi mdi-email-open"></i></div>
<div class="nfi-text">Unread</div>
<?php if($unreadCount > 0): ?><div class="nfi-count" id="cntUnread" style="background:#0284c7;color:#fff;"><?= $unreadCount ?></div><?php endif; ?>
</a>
<a href="#" class="ntf-filter-item" data-filter="danger" onclick="filterNtf(event, 'danger', this)">
<div class="nfi-icon" style="color:#ef4444;background:#fef2f2;"><i class="mdi mdi-alert-circle"></i></div>
<div class="nfi-text">Warnings</div>
</a>
<a href="#" class="ntf-filter-item" data-filter="trash" onclick="filterNtf(event, 'trash', this)">
<div class="nfi-icon" style="color:#6b7280;background:#f3f4f6;"><i class="mdi mdi-delete-outline"></i></div>
<div class="nfi-text">Trash (30 Days)</div>
<div class="nfi-count" id="cntTrash" style="background:#e5e7eb;color:#374151;"><?= $trashCount ?></div>
</a>
</div>
<hr style="margin:20px 0;border-color:#f0f0f0;">
<div style="font-size:12px;color:#6b7280;line-height:1.5;padding:0 12px;">
<strong>Info:</strong> Notifications in the trash will be automatically permanently deleted after 30 days.
</div>
</div>
<div class="sch-divider"></div>
<!-- RIGHT MAIN CONTENT -->
<div class="sch-main">
<div class="bch" style="margin-bottom:20px; display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:10px;">
<div style="font-size:16px;font-weight:700;color:#1a1a1a;" id="mainListTitle">
Recent Notifications
</div>
<div class="ntf-main-actions d-flex align-items-center" style="gap:8px;">
<button class="btn btn-sm btn-danger" id="btnDeleteAllToTrash" <?= ($totalCount == 0) ? 'disabled' : '' ?> style="border-radius:6px;font-size:12px;padding:7px 10px;">Delete All to Trash</button>
<button class="btn btn-sm btn-primary" id="btnMarkAll" <?= ($unreadCount == 0) ? 'disabled' : '' ?> style="border-radius:6px;font-size:12px;padding:7px 10px;">Mark All as Read</button>
<button class="btn btn-sm btn-danger" id="btnEmptyTrash" style="display:none;border-radius:6px;font-size:12px;padding:7px 10px;">Empty Trash</button>
</div>
</div>
<!-- Notification List Container -->
<div class="ntf-list-container">
<!-- ACTIVE LIST -->
<div id="activeListWrapper">
<?php if (empty($grouped)): ?>
<div style="text-align:center;padding:50px 20px;color:#9ca3af;">
<i class="mdi mdi-bell-sleep-outline" style="font-size:40px;display:block;margin-bottom:10px;color:#e5e7eb;"></i>
<span style="font-size:14px;">No notifications.</span>
</div>
<?php else: ?>
<?php foreach ($grouped as $label => $items): ?>
<div class="ntf-date-hdr ntf-date-group-<?= md5($label) ?>"><?= $label ?></div>
<div class="ntf-group-wrap ntf-date-group-<?= md5($label) ?>">
<?php foreach ($items as $n):
$is_warning = $n['is_danger'] || stripos($n['title'], 'terbuka') !== false || stripos($n['message'], 'terbuka') !== false;
?>
<div class="ntf-card ntf-card-item <?= !$n['is_read'] ? 'unread' : '' ?> <?= $is_warning ? 'danger' : '' ?>"
id="ncard-<?= $n['id'] ?>"
onclick="showNtf(this, <?= $n['id'] ?>)"
data-id="<?= $n['id'] ?>"
data-icnclass="<?= $n['icon'] ?>"
data-title="<?= htmlspecialchars($n['title']) ?>"
data-msg="<?= htmlspecialchars($n['message']) ?>"
data-detail="<?= htmlspecialchars($n['detail']) ?>"
data-time="<?= date('H:i', strtotime($n['time'])) ?>"
data-danger="<?= $is_warning ? '1' : '0' ?>"
data-unread="<?= !$n['is_read'] ? '1' : '0' ?>"
oncontextmenu="showContextMenu(event, <?= $n['id'] ?>, 'active')">
<div class="nc-icon"><i class="mdi <?= $n['icon'] ?>"></i></div>
<div class="nc-body">
<div class="nc-title"><?= htmlspecialchars($n['title']) ?></div>
<div class="nc-desc"><?= htmlspecialchars($n['message']) ?></div>
</div>
<div class="nc-meta">
<span class="nc-time"><?= date('H:i', strtotime($n['time'])) ?></span>
<span class="nc-ago"><?= timeAgo($n['time']) ?></span>
</div>
<!-- Action Menu Button (Mobile Friendly) -->
<div class="ntf-actions" onclick="event.stopPropagation(); showContextMenu(event, <?= $n['id'] ?>, 'active')">
<i class="mdi mdi-dots-vertical"></i>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<!-- TRASH LIST -->
<div id="trashListWrapper" style="display:none;">
<?php if (empty($groupedTrash)): ?>
<div style="text-align:center;padding:50px 20px;color:#9ca3af;">
<i class="mdi mdi-delete-empty" style="font-size:40px;display:block;margin-bottom:10px;color:#e5e7eb;"></i>
<span style="font-size:14px;">Trash is empty.</span>
</div>
<?php else: ?>
<?php foreach ($groupedTrash as $label => $items): ?>
<div class="ntf-date-hdr"><?= $label ?></div>
<div class="ntf-group-wrap">
<?php foreach ($items as $n): ?>
<div class="ntf-card ntf-card-item" style="opacity:0.75;" id="ncard-<?= $n['id'] ?>"
oncontextmenu="showContextMenu(event, <?= $n['id'] ?>, 'trash')">
<div class="nc-icon"><i class="mdi <?= $n['icon'] ?>"></i></div>
<div class="nc-body">
<div class="nc-title"><?= htmlspecialchars($n['title']) ?></div>
<div class="nc-desc"><?= htmlspecialchars($n['message']) ?></div>
</div>
<div class="nc-meta">
<span class="nc-time"><?= date('H:i', strtotime($n['time'])) ?></span>
<span class="nc-ago">Deleted <?= timeAgo($n['deleted_at']) ?></span>
</div>
<!-- Action Menu Button -->
<div class="ntf-actions" onclick="event.stopPropagation(); showContextMenu(event, <?= $n['id'] ?>, 'trash')">
<i class="mdi mdi-dots-vertical"></i>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</div>
</div>
<!-- END DUAL COLUMN -->
</div>
</div>
</div>
</div>
</div>
<!-- Simple Clean Modal -->
<div class="modal fade" id="modalNtf" tabindex="-1">
<div class="modal-dialog" style="max-width: 420px;">
<div class="modal-content nt-modal-simple">
<div class="ntms-header" id="nmdl-hdr">
<div class="ntms-icon" id="nmdl-icn-wrap"><i class="mdi" id="nmdl-icn"></i></div>
<div style="flex:1;">
<h5 class="ntms-title" id="nmdl-title">Title</h5>
<div class="ntms-time" id="nmdl-time">Time</div>
</div>
<button type="button" class="close" data-dismiss="modal" style="outline:none;">&times;</button>
</div>
<div class="ntms-body">
<p class="ntms-msg" id="nmdl-msg">Message goes here.</p>
<div class="ntms-detail" id="nmdl-dtl">Detail goes here.</div>
</div>
</div>
</div>
</div>
<!-- Context Menu -->
<div id="ctxMenu" style="display:none; position:fixed; z-index:9999; background:#fff; border-radius:8px; box-shadow:0 10px 25px rgba(0,0,0,0.1); border:1px solid #e5e7eb; overflow:hidden; min-width:160px;">
<!-- Filled via JS -->
</div>
<!-- ==================== MODAL: DELETE TRASH ==================== -->
<div class="modal fade" id="modalDeleteTrash" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content nt-modal-simple" style="border-radius:12px;">
<div class="modal-body p-4" style="text-align: left;">
<h5 class="font-weight-bold mb-3">Empty Trash</h5>
<p style="font-size:14px;color:#666;margin-bottom:20px;">Are you sure you want to permanently delete all items in the trash?</p>
<div class="custom-control custom-checkbox mb-4" style="display:flex; align-items:center;">
<input type="checkbox" class="custom-control-input" id="confirmEmptyTrash" style="width:18px;height:18px;margin-right:10px;cursor:pointer;">
<label class="custom-control-label" for="confirmEmptyTrash" style="font-size:14px; color:#1e1f23; padding-top:2px; cursor:pointer;">I understand that all items in the trash will be permanently deleted</label>
</div>
<div class="d-flex justify-content-end" style="gap:8px;">
<button type="button" class="btn btn-light font-weight-bold" style="padding:8px 18px;font-size:13px;border-radius:6px;" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger font-weight-bold" style="padding:8px 18px;font-size:13px;border-radius:6px;" id="btnConfirmEmptyTrash" disabled>Empty Trash</button>
</div>
</div>
</div>
</div>
</div>
<style>
/* Style for custom checkbox to match new_occupant.php feel */
#confirmEmptyTrash:checked + label::before {
background-color: #ef4444 !important;
border-color: #ef4444 !important;
}
</style>
<?php include 'footer.php'; ?>
<script>
// Reveal content once DOM is ready
document.addEventListener('DOMContentLoaded', function() {
var body = document.querySelector('.ntf-page-body');
if (body) body.classList.add('ntf-ready');
});
function filterNtf(event, filterType, el) {
event.preventDefault();
// Update active class on filter tags
document.querySelectorAll('.ntf-filter-item').forEach(e => e.classList.remove('active'));
el.classList.add('active');
// Logic to show/hide based on filterType
const activeWrap = document.getElementById('activeListWrapper');
const trashWrap = document.getElementById('trashListWrapper');
const mainTitle = document.getElementById('mainListTitle');
// Main Actions Buttons
const btnEmpty = document.getElementById('btnEmptyTrash');
const btnMarkAll = document.getElementById('btnMarkAll');
const btnDeleteAll = document.getElementById('btnDeleteAllToTrash');
if(filterType === 'trash') {
activeWrap.style.display = 'none';
trashWrap.style.display = 'block';
mainTitle.textContent = 'Notifications in Trash';
btnEmpty.style.display = 'block';
btnMarkAll.style.display = 'none';
btnDeleteAll.style.display = 'none';
return;
}
activeWrap.style.display = 'block';
trashWrap.style.display = 'none';
mainTitle.textContent = 'Recent Notifications';
btnEmpty.style.display = 'none';
btnMarkAll.style.display = 'block';
btnDeleteAll.style.display = 'block';
const allCards = activeWrap.querySelectorAll('.ntf-card-item');
allCards.forEach(card => {
let isMatch = false;
if(filterType === 'all') isMatch = true;
if(filterType === 'unread' && card.dataset.unread === '1') isMatch = true;
if(filterType === 'danger' && card.dataset.danger === '1') isMatch = true;
card.style.display = isMatch ? 'flex' : 'none';
});
// Hide date headers if they have no visible descendants
document.querySelectorAll('.ntf-group-wrap').forEach(wrap => {
const visibleCards = wrap.querySelectorAll('.ntf-card-item[style*="display: flex"], .ntf-card-item:not([style*="display: none"])');
// Find the corresponding date-hdr using class matching
const classList = Array.from(wrap.classList).find(c => c.startsWith('ntf-date-group-'));
const hdr = document.querySelector('.ntf-date-hdr.'+classList);
if(visibleCards.length === 0) {
wrap.style.display = 'none';
if(hdr) hdr.style.display = 'none';
} else {
wrap.style.display = 'block';
if(hdr) hdr.style.display = 'block';
}
});
}
function showNtf(el, id) {
const isDanger = el.dataset.danger === '1';
// Fill data
document.getElementById('nmdl-title').textContent = el.dataset.title;
document.getElementById('nmdl-msg').textContent = el.dataset.msg;
document.getElementById('nmdl-dtl').textContent = el.dataset.detail;
document.getElementById('nmdl-time').textContent = el.dataset.time + ' - ' + el.querySelector('.nc-ago').textContent;
// Icon
document.getElementById('nmdl-icn').className = 'mdi ' + el.dataset.icnclass;
// Theme
const hdr = document.getElementById('nmdl-hdr');
if(isDanger) {
hdr.parentElement.className = 'modal-content nt-modal-simple modal-theme-danger';
} else {
hdr.parentElement.className = 'modal-content nt-modal-simple modal-theme-info';
}
// Mark as read conceptually and in DB
if (el.classList.contains('unread')) {
el.classList.remove('unread');
el.dataset.unread = '0';
// Update unread count in UI
let cnt = document.getElementById('cntUnread');
if(cnt) {
let n = parseInt(cnt.textContent) - 1;
cnt.textContent = n;
if(n<=0) {
cnt.style.display = 'none';
const btnMarkAll = document.getElementById('btnMarkAll');
if(btnMarkAll) btnMarkAll.disabled = true;
}
}
// AJAX to DB
fetch('api_notifications.php', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=mark_read&id='+id
});
}
$('#modalNtf').modal('show');
}
// Mark All
const btnAll = document.getElementById('btnMarkAll');
if (btnAll) {
btnAll.addEventListener('click', function() {
document.querySelectorAll('.ntf-card.unread').forEach(el => {
el.classList.remove('unread');
el.dataset.unread = '0';
});
this.disabled = true;
const badgeUnread = document.getElementById('cntUnread');
if(badgeUnread) badgeUnread.style.display = 'none';
// AJAX request
fetch('api_notifications.php', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=mark_all_read'
});
});
}
// Delete All to Trash
const btnDeleteAll = document.getElementById('btnDeleteAllToTrash');
if (btnDeleteAll) {
btnDeleteAll.addEventListener('click', function() {
if(!confirm('Move all notifications to trash?')) return;
this.disabled = true;
this.innerHTML = '<i class="mdi mdi-loading mdi-spin mr-1"></i> Processing...';
fetch('api_notifications.php', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=delete_all_to_trash'
}).then(r => r.json()).then(res => {
if(res.status === 'success') {
location.reload();
} else {
this.disabled = false;
this.innerHTML = 'Delete All to Trash';
alert('Failed to move notifications to trash.');
}
});
});
}
// Custom Context Menu Logic
let currCardId = null;
function showContextMenu(e, id, type) {
e.preventDefault();
currCardId = id;
const menu = document.getElementById('ctxMenu');
if (type === 'active') {
menu.innerHTML = `<div class="ctx-item text-danger" onclick="notifAction('move_trash')"><i class="mdi mdi-delete-outline"></i> Move to Trash</div>`;
} else {
menu.innerHTML = `<div class="ctx-item" onclick="notifAction('restore')"><i class="mdi mdi-restore"></i> Restore</div>
<div class="ctx-item text-danger" onclick="notifAction('delete_permanently')"><i class="mdi mdi-delete-forever"></i> Delete Permanently</div>`;
}
menu.style.display = 'block';
let x = e.clientX;
let y = e.clientY;
// Prevent menu overflowing right edge
if(x + menu.offsetWidth > window.innerWidth) x = window.innerWidth - menu.offsetWidth - 10;
menu.style.left = x + 'px';
menu.style.top = y + 'px';
}
function notifAction(action) {
if(!currCardId) return;
fetch('api_notifications.php', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action='+action+'&id='+currCardId
}).then(r => r.json()).then(res => {
if(res.status==='success') {
location.reload(); // Quick refresh to update UI accurately matching db
}
});
}
// Empty Trash Modal Logic
const btnEmptyTrash = document.getElementById('btnEmptyTrash');
const cbConfirmEmpty = document.getElementById('confirmEmptyTrash');
const btnConfirmEmpty = document.getElementById('btnConfirmEmptyTrash');
if (btnEmptyTrash) {
btnEmptyTrash.addEventListener('click', function() {
cbConfirmEmpty.checked = false;
btnConfirmEmpty.disabled = true;
$('#modalDeleteTrash').modal('show');
});
}
if (cbConfirmEmpty) {
cbConfirmEmpty.addEventListener('change', function() {
btnConfirmEmpty.disabled = !this.checked;
});
}
if (btnConfirmEmpty) {
btnConfirmEmpty.addEventListener('click', function() {
this.disabled = true;
this.innerHTML = '<i class="mdi mdi-loading mdi-spin mr-1"></i> Emptying...';
fetch('api_notifications.php', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=empty_trash'
}).then(() => {
location.reload();
});
});
}
document.addEventListener('click', function(e) {
const menu = document.getElementById('ctxMenu');
if(menu && menu.style.display === 'block') {
menu.style.display = 'none';
}
});
</script>