feat: add data change notifications via 30s polling for all roles
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -391,6 +391,39 @@
|
|||||||
setter(new Chart(ctx, config));
|
setter(new Chart(ctx, config));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── NOTIFIKASI PERUBAHAN DATA ────────────────────────────────────────
|
||||||
|
(function initNotifPolling() {
|
||||||
|
const POLL_MS = 30000;
|
||||||
|
const KEY_IBADAH = 'notif_latest_ibadah';
|
||||||
|
const KEY_MISKIN = 'notif_latest_miskin';
|
||||||
|
|
||||||
|
async function pollNotifications() {
|
||||||
|
try {
|
||||||
|
const since = localStorage.getItem(KEY_IBADAH) || new Date(0).toISOString();
|
||||||
|
const res = await fetch(
|
||||||
|
`{{ url('/api/notifications') }}?since=${encodeURIComponent(since)}`,
|
||||||
|
{ headers: { 'X-CSRF-TOKEN': csrfToken } }
|
||||||
|
);
|
||||||
|
if (!res.ok) return;
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.new_ibadah > 0 || data.new_miskin > 0) {
|
||||||
|
if (data.new_ibadah > 0) showToast(`${data.new_ibadah} rumah ibadah baru ditambahkan`, 'info');
|
||||||
|
if (data.new_miskin > 0) showToast(`${data.new_miskin} keluarga miskin baru ditambahkan`, 'info');
|
||||||
|
if (typeof loadDashboard !== 'undefined') loadDashboard();
|
||||||
|
}
|
||||||
|
if (data.latest_ibadah) localStorage.setItem(KEY_IBADAH, data.latest_ibadah);
|
||||||
|
if (data.latest_miskin) localStorage.setItem(KEY_MISKIN, data.latest_miskin);
|
||||||
|
} catch (e) { /* silent */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
if (!localStorage.getItem(KEY_IBADAH)) localStorage.setItem(KEY_IBADAH, new Date().toISOString());
|
||||||
|
if (!localStorage.getItem(KEY_MISKIN)) localStorage.setItem(KEY_MISKIN, new Date().toISOString());
|
||||||
|
setInterval(pollNotifications, POLL_MS);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
function showToast(msg, type = 'info') {
|
function showToast(msg, type = 'info') {
|
||||||
const t = document.createElement('div');
|
const t = document.createElement('div');
|
||||||
const cls = type === 'success' ? 'alert-success' : type === 'error' ? 'alert-error' : 'alert-info';
|
const cls = type === 'success' ? 'alert-success' : type === 'error' ? 'alert-error' : 'alert-info';
|
||||||
|
|||||||
@@ -705,6 +705,42 @@
|
|||||||
document.getElementById('toastBox').appendChild(t);
|
document.getElementById('toastBox').appendChild(t);
|
||||||
setTimeout(() => t.remove(), 3000);
|
setTimeout(() => t.remove(), 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── NOTIFIKASI PERUBAHAN DATA ────────────────────────────────────────
|
||||||
|
(function initNotifPolling() {
|
||||||
|
const POLL_MS = 30000;
|
||||||
|
const KEY_IBADAH = 'notif_latest_ibadah';
|
||||||
|
const KEY_MISKIN = 'notif_latest_miskin';
|
||||||
|
|
||||||
|
async function pollNotifications() {
|
||||||
|
try {
|
||||||
|
const since = localStorage.getItem(KEY_IBADAH) || new Date(0).toISOString();
|
||||||
|
const res = await fetch(
|
||||||
|
`{{ url('/api/notifications') }}?since=${encodeURIComponent(since)}`,
|
||||||
|
{ headers: { 'X-CSRF-TOKEN': csrfToken } }
|
||||||
|
);
|
||||||
|
if (!res.ok) return;
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.new_ibadah > 0 || data.new_miskin > 0) {
|
||||||
|
if (data.new_ibadah > 0) showToast(`${data.new_ibadah} rumah ibadah baru ditambahkan`, 'info');
|
||||||
|
if (data.new_miskin > 0) showToast(`${data.new_miskin} keluarga miskin baru ditambahkan`, 'info');
|
||||||
|
const fn = typeof loadAllData !== 'undefined' ? loadAllData
|
||||||
|
: typeof loadData !== 'undefined' ? loadData
|
||||||
|
: null;
|
||||||
|
if (fn) fn();
|
||||||
|
}
|
||||||
|
if (data.latest_ibadah) localStorage.setItem(KEY_IBADAH, data.latest_ibadah);
|
||||||
|
if (data.latest_miskin) localStorage.setItem(KEY_MISKIN, data.latest_miskin);
|
||||||
|
} catch (e) { /* silent */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
if (!localStorage.getItem(KEY_IBADAH)) localStorage.setItem(KEY_IBADAH, new Date().toISOString());
|
||||||
|
if (!localStorage.getItem(KEY_MISKIN)) localStorage.setItem(KEY_MISKIN, new Date().toISOString());
|
||||||
|
setInterval(pollNotifications, POLL_MS);
|
||||||
|
});
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1044,6 +1044,43 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── NOTIFIKASI PERUBAHAN DATA ────────────────────────────────────────
|
||||||
|
(function initNotifPolling() {
|
||||||
|
const POLL_MS = 30000;
|
||||||
|
const KEY_IBADAH = 'notif_latest_ibadah';
|
||||||
|
const KEY_MISKIN = 'notif_latest_miskin';
|
||||||
|
|
||||||
|
async function pollNotifications() {
|
||||||
|
try {
|
||||||
|
const since = localStorage.getItem(KEY_IBADAH) || new Date(0).toISOString();
|
||||||
|
const res = await fetch(
|
||||||
|
`{{ url('/api/notifications') }}?since=${encodeURIComponent(since)}`,
|
||||||
|
{ headers: { 'X-CSRF-TOKEN': csrfToken } }
|
||||||
|
);
|
||||||
|
if (!res.ok) return;
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.new_ibadah > 0 || data.new_miskin > 0) {
|
||||||
|
if (data.new_ibadah > 0) showToast(`${data.new_ibadah} rumah ibadah baru ditambahkan`, 'info');
|
||||||
|
if (data.new_miskin > 0) showToast(`${data.new_miskin} keluarga miskin baru ditambahkan`, 'info');
|
||||||
|
const fn = typeof loadAllData !== 'undefined' ? loadAllData
|
||||||
|
: typeof loadData !== 'undefined' ? loadData
|
||||||
|
: typeof loadDashboard !== 'undefined' ? loadDashboard
|
||||||
|
: null;
|
||||||
|
if (fn) fn();
|
||||||
|
}
|
||||||
|
if (data.latest_ibadah) localStorage.setItem(KEY_IBADAH, data.latest_ibadah);
|
||||||
|
if (data.latest_miskin) localStorage.setItem(KEY_MISKIN, data.latest_miskin);
|
||||||
|
} catch (e) { /* silent */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
if (!localStorage.getItem(KEY_IBADAH)) localStorage.setItem(KEY_IBADAH, new Date().toISOString());
|
||||||
|
if (!localStorage.getItem(KEY_MISKIN)) localStorage.setItem(KEY_MISKIN, new Date().toISOString());
|
||||||
|
setInterval(pollNotifications, POLL_MS);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
// ── TOAST ──────────────────────────────────────────────────────────
|
// ── TOAST ──────────────────────────────────────────────────────────
|
||||||
function showToast(msg, type = 'info') {
|
function showToast(msg, type = 'info') {
|
||||||
const t = document.createElement('div');
|
const t = document.createElement('div');
|
||||||
|
|||||||
@@ -33,6 +33,19 @@ Route::middleware('auth')->group(function () {
|
|||||||
// API routes moved here for session + CSRF middleware
|
// API routes moved here for session + CSRF middleware
|
||||||
Route::prefix('api')->group(function () {
|
Route::prefix('api')->group(function () {
|
||||||
|
|
||||||
|
// Notifikasi — semua role authenticated
|
||||||
|
Route::get('/notifications', function (\Illuminate\Http\Request $request) {
|
||||||
|
$since = $request->query('since');
|
||||||
|
$after = $since ? \Carbon\Carbon::parse($since) : now()->subMinutes(1);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'new_ibadah' => \App\Models\RumahIbadah::where('created_at', '>', $after)->count(),
|
||||||
|
'new_miskin' => \App\Models\RumahMiskin::where('created_at', '>', $after)->count(),
|
||||||
|
'latest_ibadah' => \App\Models\RumahIbadah::max('created_at'),
|
||||||
|
'latest_miskin' => \App\Models\RumahMiskin::max('created_at'),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
// Rumah Ibadah — read: all roles, write: administrator + pengurus_ibadah
|
// Rumah Ibadah — read: all roles, write: administrator + pengurus_ibadah
|
||||||
Route::get('/ibadah', [IbadahController::class, 'index']);
|
Route::get('/ibadah', [IbadahController::class, 'index']);
|
||||||
Route::middleware('role:administrator|pengurus_ibadah')->group(function () {
|
Route::middleware('role:administrator|pengurus_ibadah')->group(function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user