1238 lines
50 KiB
PHP
1238 lines
50 KiB
PHP
@extends('layoutuser.app')
|
|
|
|
@section('title', 'Notifikasi')
|
|
|
|
@section('content')
|
|
<div class="container py-5">
|
|
<div class="row justify-content-center">
|
|
<div class="col-lg-10">
|
|
<!-- Header -->
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h4 class="mb-0">
|
|
<i class="fas fa-bell me-2 text-primary"></i>Pengaturan Notifikasi
|
|
</h4>
|
|
<div>
|
|
<button type="button" class="btn btn-warning btn-sm me-2" onclick="testNotification()">
|
|
<i class="fas fa-bell me-1"></i>Test Database
|
|
</button>
|
|
<button type="button" class="btn btn-success btn-sm me-2" onclick="testBrowserNotification()">
|
|
<i class="fas fa-desktop me-1"></i>Test Popup Device
|
|
</button>
|
|
<button type="button" class="btn btn-info btn-sm me-2" onclick="testBackgroundNotification()">
|
|
<i class="fas fa-broadcast-tower me-1"></i>Test Background
|
|
</button>
|
|
<button type="button" class="btn btn-danger btn-sm" onclick="testRepeatingNotification()">
|
|
<i class="fas fa-alarm me-1"></i>Test Alarm Berulang
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tabs -->
|
|
<ul class="nav nav-tabs mb-4" id="notificationTabs" role="tablist">
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link active" id="notifications-tab" data-bs-toggle="tab" data-bs-target="#notifications" type="button" role="tab">
|
|
<i class="fas fa-list me-2"></i>Daftar Notifikasi
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="sound-tab" data-bs-toggle="tab" data-bs-target="#sound" type="button" role="tab">
|
|
<i class="fas fa-volume-up me-2"></i>Pengaturan Suara
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="permission-tab" data-bs-toggle="tab" data-bs-target="#permission" type="button" role="tab">
|
|
<i class="fas fa-shield-alt me-2"></i>Permission
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
|
|
<!-- Tab Content -->
|
|
<div class="tab-content" id="notificationTabsContent">
|
|
<!-- Tab 1: Daftar Notifikasi -->
|
|
<div class="tab-pane fade show active" id="notifications" role="tabpanel">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
|
<h6 class="mb-0">Riwayat Notifikasi</h6>
|
|
<button type="button" class="btn btn-sm btn-outline-primary" onclick="markAllAsRead()">
|
|
<i class="fas fa-check-double me-1"></i>Tandai Semua Dibaca
|
|
</button>
|
|
</div>
|
|
<div class="card-body">
|
|
@if($notifications->count() > 0)
|
|
<div class="list-group">
|
|
@foreach($notifications as $notification)
|
|
<div class="list-group-item list-group-item-action {{ !$notification->is_read ? 'list-group-item-warning' : '' }}"
|
|
id="notification-{{ $notification->id }}">
|
|
<div class="d-flex w-100 justify-content-between align-items-start">
|
|
<div class="flex-grow-1">
|
|
<div class="d-flex align-items-center mb-2">
|
|
<h6 class="mb-1">
|
|
@if(!$notification->is_read)
|
|
<span class="badge bg-warning me-2">Baru</span>
|
|
@endif
|
|
{{ $notification->title }}
|
|
</h6>
|
|
<span class="badge bg-{{ $notification->type == 'reminder' ? 'warning' : ($notification->type == 'success' ? 'success' : 'info') }} ms-2">
|
|
{{ ucfirst($notification->type) }}
|
|
</span>
|
|
</div>
|
|
<p class="mb-1">{{ $notification->message }}</p>
|
|
<small class="text-muted">
|
|
<i class="fas fa-clock me-1"></i>
|
|
{{ $notification->created_at->diffForHumans() }}
|
|
</small>
|
|
</div>
|
|
<div class="ms-3">
|
|
@if(!$notification->is_read)
|
|
<button type="button" class="btn btn-sm btn-outline-success me-1"
|
|
onclick="markAsRead({{ $notification->id }})" title="Tandai Dibaca">
|
|
<i class="fas fa-check"></i>
|
|
</button>
|
|
@endif
|
|
<button type="button" class="btn btn-sm btn-outline-danger"
|
|
onclick="deleteNotification({{ $notification->id }})" title="Hapus">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<div class="d-flex justify-content-center mt-4">
|
|
{{ $notifications->links() }}
|
|
</div>
|
|
@else
|
|
<div class="text-center py-5">
|
|
<i class="fas fa-bell-slash fa-3x text-muted mb-3"></i>
|
|
<h5 class="text-muted">Belum ada notifikasi</h5>
|
|
<p class="text-muted">Notifikasi akan muncul di sini ketika ada informasi penting untuk Anda.</p>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab 2: Pengaturan Suara -->
|
|
<div class="tab-pane fade" id="sound" role="tabpanel">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header bg-white">
|
|
<h6 class="mb-0">Pengaturan Suara Notifikasi</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Toggle Suara -->
|
|
<div class="mb-4">
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" id="sound_enabled" checked>
|
|
<label class="form-check-label" for="sound_enabled">
|
|
<strong>Aktifkan Suara Notifikasi</strong>
|
|
</label>
|
|
</div>
|
|
<small class="text-muted">Putar suara saat notifikasi muncul</small>
|
|
</div>
|
|
|
|
<!-- Auto Sound Control -->
|
|
<div class="mb-4">
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" id="auto_sound_enabled">
|
|
<label class="form-check-label" for="auto_sound_enabled">
|
|
<strong>Suara Otomatis Saat Berpindah Menu</strong>
|
|
</label>
|
|
</div>
|
|
<small class="text-muted">Nonaktifkan untuk mencegah suara muncul saat berpindah halaman</small>
|
|
</div>
|
|
|
|
<!-- Volume Control -->
|
|
<div class="mb-4">
|
|
<label class="form-label">
|
|
<strong>Volume Suara:</strong>
|
|
<span id="volume_value" class="text-primary">50</span>%
|
|
</label>
|
|
<input type="range" class="form-range" id="sound_volume" min="0" max="100" value="50">
|
|
<div class="d-flex justify-content-between">
|
|
<small class="text-muted">0%</small>
|
|
<small class="text-muted">100%</small>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pilihan Suara -->
|
|
<div class="mb-4">
|
|
<label class="form-label"><strong>Pilih Suara Notifikasi:</strong></label>
|
|
<div id="sound-options" class="row">
|
|
<div class="col-12 text-center">
|
|
<i class="fas fa-spinner fa-spin"></i>
|
|
<small class="d-block mt-1">Memuat pilihan suara...</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Test Suara -->
|
|
<div class="text-center">
|
|
<button type="button" class="btn btn-primary" onclick="testSound()">
|
|
<i class="fas fa-play me-2"></i>Test Suara
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Pengaturan Alarm Berulang -->
|
|
<hr class="my-4">
|
|
<h6 class="mb-3"><i class="fas fa-alarm me-2"></i>Pengaturan Alarm Berulang</h6>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<label class="form-label">
|
|
<strong>Interval Notifikasi:</strong>
|
|
<span id="notification_interval_value" class="text-primary">30</span> detik
|
|
</label>
|
|
<input type="range" class="form-range" id="notification_interval" min="10" max="120" value="30">
|
|
<div class="d-flex justify-content-between">
|
|
<small class="text-muted">10 detik</small>
|
|
<small class="text-muted">2 menit</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">
|
|
<strong>Interval Suara:</strong>
|
|
<span id="audio_interval_value" class="text-primary">10</span> detik
|
|
</label>
|
|
<input type="range" class="form-range" id="audio_interval" min="5" max="60" value="10">
|
|
<div class="d-flex justify-content-between">
|
|
<small class="text-muted">5 detik</small>
|
|
<small class="text-muted">1 menit</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab 3: Permission -->
|
|
<div class="tab-pane fade" id="permission" role="tabpanel">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header bg-white">
|
|
<h6 class="mb-0">Pengaturan Permission Notifikasi</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Status Permission -->
|
|
<div class="mb-4">
|
|
<h6>Status Permission:</h6>
|
|
<div id="permission-status" class="alert alert-info">
|
|
<i class="fas fa-info-circle me-2"></i>
|
|
<span id="permission-text">Memeriksa status permission...</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Browser Notifications -->
|
|
<div class="mb-4">
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" id="browser_notifications_enabled">
|
|
<label class="form-check-label" for="browser_notifications_enabled">
|
|
<strong>Aktifkan Notifikasi Browser</strong>
|
|
</label>
|
|
</div>
|
|
<small class="text-muted">Notifikasi akan muncul di sistem operasi (Windows, macOS, Linux)</small>
|
|
</div>
|
|
|
|
<!-- Background Notifications -->
|
|
<div class="mb-4">
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" id="background_notifications_enabled">
|
|
<label class="form-check-label" for="background_notifications_enabled">
|
|
<strong>Notifikasi Background</strong>
|
|
</label>
|
|
</div>
|
|
<small class="text-muted">Notifikasi akan muncul meski tab browser tidak aktif</small>
|
|
</div>
|
|
|
|
<!-- Action Buttons -->
|
|
<div class="text-center">
|
|
<button type="button" class="btn btn-primary me-2" onclick="requestNotificationPermission()">
|
|
<i class="fas fa-shield-alt me-2"></i>Minta Permission
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="resetNotificationSettings()">
|
|
<i class="fas fa-undo me-2"></i>Reset Pengaturan
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Test Notifications Info -->
|
|
<div class="mt-4">
|
|
<div class="alert alert-info">
|
|
<h6><i class="fas fa-info-circle me-2"></i>Test Notifikasi:</h6>
|
|
<div class="row">
|
|
<div class="col-md-3">
|
|
<strong>Test Database:</strong>
|
|
<ul class="mb-0 small">
|
|
<li>Menyimpan notifikasi ke database</li>
|
|
<li>Muncul di daftar notifikasi</li>
|
|
<li>Tidak memerlukan permission browser</li>
|
|
</ul>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<strong>Test Popup Device:</strong>
|
|
<ul class="mb-0 small">
|
|
<li>Menampilkan popup di sistem operasi</li>
|
|
<li>Memerlukan permission browser</li>
|
|
<li>Muncul saat tab aktif</li>
|
|
</ul>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<strong>Test Background:</strong>
|
|
<ul class="mb-0 small">
|
|
<li>Notifikasi background/offline</li>
|
|
<li>Muncul meski tab tidak aktif</li>
|
|
<li>Menggunakan Service Worker</li>
|
|
</ul>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<strong>Test Alarm Berulang:</strong>
|
|
<ul class="mb-0 small">
|
|
<li>Notifikasi berulang setiap 30 detik</li>
|
|
<li>Suara berulang setiap 10 detik</li>
|
|
<li>Tombol Dismiss & Snooze</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Info -->
|
|
<div class="mt-4">
|
|
<div class="alert alert-warning">
|
|
<h6><i class="fas fa-exclamation-triangle me-2"></i>Penting:</h6>
|
|
<ul class="mb-0">
|
|
<li>Notifikasi browser memerlukan permission dari pengguna</li>
|
|
<li>Website harus menggunakan HTTPS untuk notifikasi background</li>
|
|
<li>Jika permission ditolak, notifikasi akan menggunakan fallback (visual + audio)</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Sound Settings
|
|
let currentVolume = 50;
|
|
let currentSound = 'default';
|
|
let soundEnabled = true;
|
|
let autoSoundEnabled = false;
|
|
let availableSounds = [];
|
|
|
|
// Permission Settings
|
|
let browserNotificationsEnabled = false;
|
|
let backgroundNotificationsEnabled = false;
|
|
|
|
// Global variables untuk notifikasi berulang
|
|
let repeatingNotificationInterval = null;
|
|
let repeatingAudioInterval = null;
|
|
let currentRepeatingNotification = null;
|
|
let isRepeatingNotificationActive = false;
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
loadSoundSettings();
|
|
loadPermissionSettings();
|
|
updatePermissionStatus();
|
|
loadAvailableSounds();
|
|
registerServiceWorker();
|
|
});
|
|
|
|
// Load available sounds from server
|
|
function loadAvailableSounds() {
|
|
fetch('{{ route("user.notifications.available-sounds") }}')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
availableSounds = data.sounds;
|
|
renderSoundOptions();
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading sounds:', error);
|
|
renderSoundOptions(); // Render with fallback
|
|
});
|
|
}
|
|
|
|
// Render sound options dynamically
|
|
function renderSoundOptions() {
|
|
const container = document.getElementById('sound-options');
|
|
|
|
if (availableSounds.length === 0) {
|
|
container.innerHTML = `
|
|
<div class="col-12">
|
|
<div class="alert alert-warning">
|
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
|
Tidak ada file audio yang tersedia. Upload file audio ke folder <code>public/audio/</code>
|
|
</div>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
availableSounds.forEach((sound, index) => {
|
|
const isChecked = index === 0 ? 'checked' : '';
|
|
const icon = getSoundIcon(sound.name);
|
|
|
|
html += `
|
|
<div class="col-md-6 mb-2">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="radio" name="notification_sound"
|
|
id="sound_${sound.name}" value="${sound.name}" ${isChecked}>
|
|
<label class="form-check-label" for="sound_${sound.name}">
|
|
<i class="${icon} me-2"></i>${sound.display_name}
|
|
</label>
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
container.innerHTML = html;
|
|
|
|
// Add event listeners to new radio buttons
|
|
document.querySelectorAll('input[name="notification_sound"]').forEach(radio => {
|
|
radio.addEventListener('change', function() {
|
|
currentSound = this.value;
|
|
localStorage.setItem('notification_sound', currentSound);
|
|
});
|
|
});
|
|
|
|
// Update sound selection after rendering
|
|
updateSoundSelection();
|
|
}
|
|
|
|
// Get appropriate icon for sound type
|
|
function getSoundIcon(soundName) {
|
|
const name = soundName.toLowerCase();
|
|
|
|
if (name.includes('bell') || name.includes('notification')) {
|
|
return 'fas fa-bell';
|
|
} else if (name.includes('chime') || name.includes('music')) {
|
|
return 'fas fa-music';
|
|
} else if (name.includes('beep') || name.includes('alert')) {
|
|
return 'fas fa-volume-up';
|
|
} else if (name.includes('ding') || name.includes('ping')) {
|
|
return 'fas fa-bell-ring';
|
|
} else {
|
|
return 'fas fa-volume-up';
|
|
}
|
|
}
|
|
|
|
// Sound Functions
|
|
function loadSoundSettings() {
|
|
// Load from localStorage
|
|
const savedVolume = localStorage.getItem('notification_volume');
|
|
const savedSound = localStorage.getItem('notification_sound');
|
|
const savedEnabled = localStorage.getItem('notification_sound_enabled');
|
|
const savedAutoSound = localStorage.getItem('notification_auto_sound_enabled');
|
|
const savedNotificationInterval = localStorage.getItem('notification_interval') || 30;
|
|
const savedAudioInterval = localStorage.getItem('audio_interval') || 10;
|
|
|
|
if (savedVolume) {
|
|
currentVolume = parseInt(savedVolume * 100);
|
|
document.getElementById('sound_volume').value = currentVolume;
|
|
document.getElementById('volume_value').textContent = currentVolume;
|
|
}
|
|
|
|
if (savedEnabled !== null) {
|
|
soundEnabled = savedEnabled === 'true';
|
|
document.getElementById('sound_enabled').checked = soundEnabled;
|
|
}
|
|
|
|
if (savedAutoSound !== null) {
|
|
autoSoundEnabled = savedAutoSound === 'true';
|
|
document.getElementById('auto_sound_enabled').checked = autoSoundEnabled;
|
|
}
|
|
|
|
// Load interval settings
|
|
document.getElementById('notification_interval').value = savedNotificationInterval;
|
|
document.getElementById('notification_interval_value').textContent = savedNotificationInterval;
|
|
document.getElementById('audio_interval').value = savedAudioInterval;
|
|
document.getElementById('audio_interval_value').textContent = savedAudioInterval;
|
|
|
|
// Event listeners
|
|
document.getElementById('sound_volume').addEventListener('input', function() {
|
|
currentVolume = this.value;
|
|
document.getElementById('volume_value').textContent = currentVolume;
|
|
localStorage.setItem('notification_volume', currentVolume / 100);
|
|
});
|
|
|
|
document.getElementById('sound_enabled').addEventListener('change', function() {
|
|
soundEnabled = this.checked;
|
|
localStorage.setItem('notification_sound_enabled', soundEnabled);
|
|
});
|
|
|
|
document.getElementById('auto_sound_enabled').addEventListener('change', function() {
|
|
autoSoundEnabled = this.checked;
|
|
localStorage.setItem('notification_auto_sound_enabled', autoSoundEnabled);
|
|
});
|
|
|
|
// Interval event listeners
|
|
document.getElementById('notification_interval').addEventListener('input', function() {
|
|
const value = this.value;
|
|
document.getElementById('notification_interval_value').textContent = value;
|
|
localStorage.setItem('notification_interval', value);
|
|
});
|
|
|
|
document.getElementById('audio_interval').addEventListener('input', function() {
|
|
const value = this.value;
|
|
document.getElementById('audio_interval_value').textContent = value;
|
|
localStorage.setItem('audio_interval', value);
|
|
});
|
|
}
|
|
|
|
// Update sound selection after sounds are loaded
|
|
function updateSoundSelection() {
|
|
const savedSound = localStorage.getItem('notification_sound');
|
|
|
|
if (savedSound && availableSounds.length > 0) {
|
|
// Check if saved sound exists in available sounds
|
|
const soundExists = availableSounds.find(sound => sound.name === savedSound);
|
|
if (soundExists) {
|
|
currentSound = savedSound;
|
|
const radioButton = document.getElementById(`sound_${savedSound}`);
|
|
if (radioButton) {
|
|
radioButton.checked = true;
|
|
}
|
|
} else {
|
|
// If saved sound doesn't exist, use first available
|
|
currentSound = availableSounds[0].name;
|
|
localStorage.setItem('notification_sound', currentSound);
|
|
const radioButton = document.getElementById(`sound_${currentSound}`);
|
|
if (radioButton) {
|
|
radioButton.checked = true;
|
|
}
|
|
}
|
|
} else if (availableSounds.length > 0) {
|
|
// Set default sound if none selected
|
|
currentSound = availableSounds[0].name;
|
|
localStorage.setItem('notification_sound', currentSound);
|
|
const radioButton = document.getElementById(`sound_${currentSound}`);
|
|
if (radioButton) {
|
|
radioButton.checked = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
function testSound() {
|
|
if (!soundEnabled) {
|
|
alert('Suara notifikasi dinonaktifkan!');
|
|
return;
|
|
}
|
|
|
|
if (availableSounds.length === 0) {
|
|
alert('Tidak ada file audio yang tersedia!');
|
|
return;
|
|
}
|
|
|
|
// Find the selected sound
|
|
const selectedSound = availableSounds.find(sound => sound.name === currentSound);
|
|
if (!selectedSound) {
|
|
alert('File audio tidak ditemukan!');
|
|
return;
|
|
}
|
|
|
|
const audio = new Audio(selectedSound.path);
|
|
audio.volume = currentVolume / 100;
|
|
audio.play().catch(e => {
|
|
console.log('Error playing sound:', e);
|
|
alert('Tidak bisa memutar suara. Pastikan file audio tersedia.');
|
|
});
|
|
}
|
|
|
|
// Permission Functions
|
|
function loadPermissionSettings() {
|
|
const savedBrowser = localStorage.getItem('browser_notifications_enabled');
|
|
const savedBackground = localStorage.getItem('background_notifications_enabled');
|
|
|
|
if (savedBrowser !== null) {
|
|
browserNotificationsEnabled = savedBrowser === 'true';
|
|
document.getElementById('browser_notifications_enabled').checked = browserNotificationsEnabled;
|
|
}
|
|
|
|
if (savedBackground !== null) {
|
|
backgroundNotificationsEnabled = savedBackground === 'true';
|
|
document.getElementById('background_notifications_enabled').checked = backgroundNotificationsEnabled;
|
|
}
|
|
|
|
// Event listeners
|
|
document.getElementById('browser_notifications_enabled').addEventListener('change', function() {
|
|
browserNotificationsEnabled = this.checked;
|
|
localStorage.setItem('browser_notifications_enabled', browserNotificationsEnabled);
|
|
});
|
|
|
|
document.getElementById('background_notifications_enabled').addEventListener('change', function() {
|
|
backgroundNotificationsEnabled = this.checked;
|
|
localStorage.setItem('background_notifications_enabled', backgroundNotificationsEnabled);
|
|
});
|
|
}
|
|
|
|
function updatePermissionStatus() {
|
|
const statusDiv = document.getElementById('permission-status');
|
|
const textSpan = document.getElementById('permission-text');
|
|
|
|
if (!('Notification' in window)) {
|
|
statusDiv.className = 'alert alert-danger';
|
|
textSpan.textContent = 'Browser tidak mendukung notifikasi';
|
|
return;
|
|
}
|
|
|
|
switch(Notification.permission) {
|
|
case 'granted':
|
|
statusDiv.className = 'alert alert-success';
|
|
textSpan.textContent = 'Permission diberikan. Notifikasi browser aktif.';
|
|
break;
|
|
case 'denied':
|
|
statusDiv.className = 'alert alert-warning';
|
|
textSpan.textContent = 'Permission ditolak. Notifikasi akan menggunakan fallback.';
|
|
break;
|
|
default:
|
|
statusDiv.className = 'alert alert-info';
|
|
textSpan.textContent = 'Permission belum diatur. Klik "Minta Permission" untuk mengaktifkan.';
|
|
break;
|
|
}
|
|
}
|
|
|
|
async function requestNotificationPermission() {
|
|
if (!('Notification' in window)) {
|
|
alert('Browser Anda tidak mendukung notifikasi!');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const permission = await Notification.requestPermission();
|
|
updatePermissionStatus();
|
|
|
|
if (permission === 'granted') {
|
|
// Register service worker for background notifications
|
|
if ('serviceWorker' in navigator && backgroundNotificationsEnabled) {
|
|
await registerServiceWorker();
|
|
}
|
|
alert('Permission diberikan! Notifikasi browser sekarang aktif.');
|
|
} else {
|
|
alert('Permission ditolak. Notifikasi akan menggunakan fallback (visual + audio).');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error requesting permission:', error);
|
|
alert('Terjadi kesalahan saat meminta permission.');
|
|
}
|
|
}
|
|
|
|
function resetNotificationSettings() {
|
|
if (confirm('Apakah Anda yakin ingin mereset semua pengaturan notifikasi?')) {
|
|
localStorage.removeItem('notification_volume');
|
|
localStorage.removeItem('notification_sound');
|
|
localStorage.removeItem('notification_sound_enabled');
|
|
localStorage.removeItem('notification_auto_sound_enabled');
|
|
localStorage.removeItem('notification_interval');
|
|
localStorage.removeItem('audio_interval');
|
|
localStorage.removeItem('browser_notifications_enabled');
|
|
localStorage.removeItem('background_notifications_enabled');
|
|
|
|
location.reload();
|
|
}
|
|
}
|
|
|
|
// Notification Functions
|
|
function markAsRead(id) {
|
|
fetch(`/user/notifications/${id}/mark-read`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
|
'Content-Type': 'application/json',
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
location.reload();
|
|
}
|
|
});
|
|
}
|
|
|
|
function markAllAsRead() {
|
|
fetch('/user/notifications/mark-all-read', {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
|
'Content-Type': 'application/json',
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
location.reload();
|
|
}
|
|
});
|
|
}
|
|
|
|
function deleteNotification(id) {
|
|
if (confirm('Apakah Anda yakin ingin menghapus notifikasi ini?')) {
|
|
fetch(`/user/notifications/${id}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
|
'Content-Type': 'application/json',
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
document.getElementById(`notification-${id}`).remove();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function testNotification() {
|
|
fetch('/user/notifications/test', {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
|
'Content-Type': 'application/json',
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
alert('Test notifikasi berhasil dikirim!');
|
|
location.reload();
|
|
}
|
|
});
|
|
}
|
|
|
|
function testBrowserNotification() {
|
|
if (!('Notification' in window)) {
|
|
alert('Browser ini tidak mendukung notifikasi desktop');
|
|
return;
|
|
}
|
|
|
|
if (Notification.permission === 'denied') {
|
|
alert('Izin notifikasi ditolak. Silakan aktifkan di pengaturan browser.');
|
|
return;
|
|
}
|
|
|
|
if (Notification.permission === 'default') {
|
|
Notification.requestPermission().then(permission => {
|
|
if (permission === 'granted') {
|
|
showTestNotification();
|
|
} else {
|
|
alert('Izin notifikasi diperlukan untuk fitur ini');
|
|
}
|
|
});
|
|
} else {
|
|
showTestNotification();
|
|
}
|
|
}
|
|
|
|
function showTestNotification() {
|
|
const notificationData = {
|
|
title: '🧪 Test Notifikasi',
|
|
body: 'Ini adalah test notifikasi popup dengan audio dan tombol action!',
|
|
icon: '/logo/baru/dutdut.png',
|
|
badge: '/logo/baru/dutdut.png',
|
|
tag: 'test-notification',
|
|
vibrate: [200, 100, 200, 100, 200],
|
|
data: {
|
|
url: '/user/notifications',
|
|
type: 'test'
|
|
}
|
|
};
|
|
|
|
try {
|
|
showTestBrowserNotification(notificationData);
|
|
console.log('Test notification sent successfully');
|
|
} catch (error) {
|
|
console.error('Error showing test notification:', error);
|
|
alert('Gagal menampilkan notifikasi: ' + error.message);
|
|
}
|
|
}
|
|
|
|
function showTestBrowserNotification(notificationData) {
|
|
// Buat notifikasi test dengan action buttons
|
|
const notification = new Notification(notificationData.title, {
|
|
body: notificationData.body,
|
|
icon: notificationData.icon,
|
|
badge: notificationData.badge,
|
|
tag: notificationData.tag,
|
|
requireInteraction: false,
|
|
silent: false, // Akan memutar suara sistem
|
|
vibrate: notificationData.vibrate,
|
|
data: notificationData.data,
|
|
actions: [
|
|
{
|
|
action: 'view',
|
|
title: 'Lihat',
|
|
icon: notificationData.icon
|
|
},
|
|
{
|
|
action: 'dismiss',
|
|
title: 'Tutup',
|
|
icon: notificationData.icon
|
|
}
|
|
]
|
|
});
|
|
|
|
// Event handlers
|
|
notification.onclick = function(event) {
|
|
// Jika user mengklik notifikasi (bukan action button)
|
|
if (!event.action) {
|
|
window.focus();
|
|
notification.close();
|
|
|
|
// Navigate ke halaman notifikasi
|
|
if (notificationData.data && notificationData.data.url) {
|
|
window.location.href = notificationData.data.url;
|
|
}
|
|
}
|
|
};
|
|
|
|
// Handle action buttons
|
|
notification.onactionclick = function(event) {
|
|
if (event.action === 'view') {
|
|
window.focus();
|
|
notification.close();
|
|
if (notificationData.data && notificationData.data.url) {
|
|
window.location.href = notificationData.data.url;
|
|
}
|
|
} else if (event.action === 'dismiss') {
|
|
notification.close();
|
|
}
|
|
};
|
|
|
|
notification.onclose = function() {
|
|
console.log('Test notification closed');
|
|
};
|
|
|
|
notification.onshow = function() {
|
|
console.log('Test notification shown');
|
|
// Putar suara custom jika diaktifkan
|
|
playCustomNotificationSound();
|
|
};
|
|
|
|
// Auto close setelah 10 detik
|
|
setTimeout(() => {
|
|
notification.close();
|
|
}, 10000);
|
|
}
|
|
|
|
// Fungsi untuk memutar suara custom
|
|
function playCustomNotificationSound() {
|
|
const soundEnabled = localStorage.getItem('notification_sound_enabled') !== 'false';
|
|
if (!soundEnabled) {
|
|
console.log('Notification sound disabled by user');
|
|
return;
|
|
}
|
|
|
|
const currentSound = localStorage.getItem('notification_sound') || 'default';
|
|
const volume = localStorage.getItem('notification_volume') || 0.3;
|
|
|
|
console.log('Playing notification sound:', currentSound, 'at volume:', volume);
|
|
|
|
try {
|
|
const audio = new Audio(`/audio/${currentSound}.mp3`);
|
|
audio.volume = parseFloat(volume);
|
|
|
|
// Tambahkan event listeners untuk debugging
|
|
audio.addEventListener('loadstart', () => console.log('Audio loading started'));
|
|
audio.addEventListener('canplay', () => console.log('Audio can play'));
|
|
audio.addEventListener('play', () => console.log('Audio started playing'));
|
|
audio.addEventListener('ended', () => console.log('Audio finished playing'));
|
|
audio.addEventListener('error', (e) => console.log('Audio error:', e));
|
|
|
|
audio.play().then(() => {
|
|
console.log('Custom notification sound played successfully');
|
|
}).catch(e => {
|
|
console.log('Error playing custom notification sound:', e);
|
|
// Fallback ke suara sistem
|
|
console.log('Using system sound as fallback');
|
|
});
|
|
} catch (error) {
|
|
console.log('Error with custom audio:', error);
|
|
}
|
|
}
|
|
|
|
function showSuccessMessage(message) {
|
|
// Buat elemen pesan sukses
|
|
const successDiv = document.createElement('div');
|
|
successDiv.className = 'alert alert-success alert-dismissible fade show position-fixed';
|
|
successDiv.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;';
|
|
successDiv.innerHTML = `
|
|
<i class="fas fa-check-circle me-2"></i>
|
|
${message}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
`;
|
|
|
|
document.body.appendChild(successDiv);
|
|
|
|
// Auto remove setelah 3 detik
|
|
setTimeout(() => {
|
|
if (successDiv.parentNode) {
|
|
successDiv.remove();
|
|
}
|
|
}, 3000);
|
|
}
|
|
|
|
function testBackgroundNotification() {
|
|
if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
|
|
alert('Browser tidak mendukung background notifications');
|
|
return;
|
|
}
|
|
|
|
navigator.serviceWorker.ready.then(registration => {
|
|
// Kirim message ke service worker untuk menampilkan notifikasi
|
|
registration.active.postMessage({
|
|
type: 'SHOW_NOTIFICATION',
|
|
notification: {
|
|
title: '🔔 Background Test',
|
|
body: 'Ini adalah test notifikasi background dengan audio!',
|
|
icon: '/logo/baru/dutdut.png',
|
|
badge: '/logo/baru/dutdut.png',
|
|
tag: 'background-test',
|
|
requireInteraction: false,
|
|
silent: false,
|
|
vibrate: [200, 100, 200, 100, 200],
|
|
data: {
|
|
url: '/user/notifications',
|
|
type: 'background-test'
|
|
}
|
|
}
|
|
});
|
|
|
|
console.log('Background notification test sent to service worker');
|
|
}).catch(error => {
|
|
console.error('Error with service worker:', error);
|
|
alert('Gagal mengirim background notification: ' + error.message);
|
|
});
|
|
}
|
|
|
|
function testRepeatingNotification() {
|
|
// Cek apakah browser mendukung notifikasi
|
|
if (!('Notification' in window)) {
|
|
alert('Browser Anda tidak mendukung notifikasi popup!');
|
|
return;
|
|
}
|
|
|
|
// Cek permission
|
|
if (Notification.permission === 'denied') {
|
|
alert('Permission notifikasi ditolak. Silakan aktifkan permission di pengaturan browser.');
|
|
return;
|
|
}
|
|
|
|
if (Notification.permission === 'default') {
|
|
// Minta permission terlebih dahulu
|
|
Notification.requestPermission().then(permission => {
|
|
if (permission === 'granted') {
|
|
startRepeatingNotification();
|
|
} else {
|
|
alert('Permission ditolak. Tidak bisa menampilkan notifikasi popup.');
|
|
}
|
|
});
|
|
} else if (Notification.permission === 'granted') {
|
|
startRepeatingNotification();
|
|
}
|
|
}
|
|
|
|
function startRepeatingNotification() {
|
|
if (isRepeatingNotificationActive) {
|
|
alert('Notifikasi berulang sudah aktif! Silakan dismiss notifikasi yang sedang berjalan.');
|
|
return;
|
|
}
|
|
|
|
isRepeatingNotificationActive = true;
|
|
|
|
// Ambil interval dari pengaturan
|
|
const notificationInterval = parseInt(localStorage.getItem('notification_interval')) || 30;
|
|
const audioInterval = parseInt(localStorage.getItem('audio_interval')) || 10;
|
|
|
|
// Tampilkan notifikasi pertama
|
|
showRepeatingNotification();
|
|
|
|
// Mulai interval untuk notifikasi berulang
|
|
repeatingNotificationInterval = setInterval(() => {
|
|
showRepeatingNotification();
|
|
}, notificationInterval * 1000); // Konversi ke milidetik
|
|
|
|
// Mulai interval untuk suara berulang
|
|
repeatingAudioInterval = setInterval(() => {
|
|
playRepeatingNotificationSound();
|
|
}, audioInterval * 1000); // Konversi ke milidetik
|
|
|
|
// Tampilkan pesan sukses dengan informasi interval
|
|
showSuccessMessage(`Alarm berulang dimulai! Notifikasi setiap ${notificationInterval} detik, suara setiap ${audioInterval} detik. Klik "Dismiss" untuk menghentikan.`);
|
|
}
|
|
|
|
function showRepeatingNotification() {
|
|
// Tutup notifikasi sebelumnya jika ada
|
|
if (currentRepeatingNotification) {
|
|
currentRepeatingNotification.close();
|
|
}
|
|
|
|
// Buat notifikasi dengan tombol action
|
|
const notification = new Notification('HeartChoice - Alarm Berulang', {
|
|
body: 'Ini adalah alarm berulang. Klik "Dismiss" untuk menghentikan alarm ini.',
|
|
icon: '{{ asset("logo/baru/dutdut.png") }}',
|
|
badge: '{{ asset("logo/baru/dutdut.png") }}',
|
|
tag: 'repeating-alarm',
|
|
requireInteraction: true, // Notifikasi tidak akan hilang otomatis
|
|
silent: false,
|
|
vibrate: [200, 100, 200, 100, 200], // Vibrasi lebih panjang
|
|
data: {
|
|
type: 'repeating_alarm',
|
|
timestamp: new Date().toISOString()
|
|
},
|
|
actions: [
|
|
{
|
|
action: 'dismiss',
|
|
title: 'Dismiss',
|
|
icon: '{{ asset("logo/baru/dutdut.png") }}'
|
|
},
|
|
{
|
|
action: 'snooze',
|
|
title: 'Snooze 5m',
|
|
icon: '{{ asset("logo/baru/dutdut.png") }}'
|
|
}
|
|
]
|
|
});
|
|
|
|
currentRepeatingNotification = notification;
|
|
|
|
// Event handlers
|
|
notification.onclick = function(event) {
|
|
// Jika user mengklik notifikasi (bukan action button)
|
|
if (!event.action) {
|
|
window.focus();
|
|
showDismissDialog();
|
|
}
|
|
};
|
|
|
|
notification.onclose = function() {
|
|
console.log('Repeating notification closed');
|
|
};
|
|
|
|
notification.onshow = function() {
|
|
console.log('Repeating notification shown');
|
|
// Putar suara saat notifikasi muncul
|
|
playRepeatingNotificationSound();
|
|
};
|
|
|
|
// Handle action buttons
|
|
notification.onactionclick = function(event) {
|
|
if (event.action === 'dismiss') {
|
|
dismissRepeatingNotification();
|
|
} else if (event.action === 'snooze') {
|
|
snoozeRepeatingNotification();
|
|
}
|
|
};
|
|
}
|
|
|
|
function playRepeatingNotificationSound() {
|
|
const soundEnabled = localStorage.getItem('notification_sound_enabled') !== 'false';
|
|
if (!soundEnabled) return;
|
|
|
|
const currentSound = localStorage.getItem('notification_sound') || 'default';
|
|
const volume = localStorage.getItem('notification_volume') || 0.3;
|
|
|
|
try {
|
|
const audio = new Audio(`/audio/${currentSound}.mp3`);
|
|
audio.volume = volume;
|
|
audio.play().catch(e => {
|
|
console.log('Error playing repeating notification sound:', e);
|
|
});
|
|
} catch (error) {
|
|
console.log('Error with audio:', error);
|
|
}
|
|
}
|
|
|
|
function dismissRepeatingNotification() {
|
|
// Hentikan semua interval
|
|
if (repeatingNotificationInterval) {
|
|
clearInterval(repeatingNotificationInterval);
|
|
repeatingNotificationInterval = null;
|
|
}
|
|
|
|
if (repeatingAudioInterval) {
|
|
clearInterval(repeatingAudioInterval);
|
|
repeatingAudioInterval = null;
|
|
}
|
|
|
|
// Tutup notifikasi yang sedang aktif
|
|
if (currentRepeatingNotification) {
|
|
currentRepeatingNotification.close();
|
|
currentRepeatingNotification = null;
|
|
}
|
|
|
|
// Reset status
|
|
isRepeatingNotificationActive = false;
|
|
|
|
// Tampilkan pesan sukses
|
|
showSuccessMessage('Alarm berulang berhasil dihentikan!');
|
|
}
|
|
|
|
function snoozeRepeatingNotification() {
|
|
// Hentikan sementara untuk 5 menit
|
|
if (repeatingNotificationInterval) {
|
|
clearInterval(repeatingNotificationInterval);
|
|
}
|
|
|
|
if (repeatingAudioInterval) {
|
|
clearInterval(repeatingAudioInterval);
|
|
}
|
|
|
|
// Tutup notifikasi yang sedang aktif
|
|
if (currentRepeatingNotification) {
|
|
currentRepeatingNotification.close();
|
|
currentRepeatingNotification = null;
|
|
}
|
|
|
|
// Tampilkan pesan snooze
|
|
showSuccessMessage('Alarm di-snooze selama 5 menit...');
|
|
|
|
// Mulai kembali setelah 5 menit
|
|
setTimeout(() => {
|
|
if (isRepeatingNotificationActive) {
|
|
startRepeatingNotification();
|
|
}
|
|
}, 300000); // 5 menit
|
|
}
|
|
|
|
function showDismissDialog() {
|
|
// Tampilkan dialog konfirmasi dismiss
|
|
const dialogHtml = `
|
|
<div class="modal fade" id="dismissDialog" tabindex="-1">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header bg-danger text-white">
|
|
<h5 class="modal-title">
|
|
<i class="fas fa-alarm me-2"></i>Alarm Berulang
|
|
</h5>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p>Apakah Anda ingin menghentikan alarm berulang ini?</p>
|
|
<div class="alert alert-warning">
|
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
|
<strong>Peringatan:</strong> Alarm akan terus berulang sampai Anda menekan "Dismiss"
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
|
<i class="fas fa-clock me-1"></i>Snooze 5m
|
|
</button>
|
|
<button type="button" class="btn btn-danger" onclick="dismissRepeatingNotification(); $('#dismissDialog').modal('hide');">
|
|
<i class="fas fa-stop me-1"></i>Dismiss
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Hapus dialog lama jika ada
|
|
const existingDialog = document.getElementById('dismissDialog');
|
|
if (existingDialog) {
|
|
existingDialog.remove();
|
|
}
|
|
|
|
// Tambahkan dialog baru
|
|
document.body.insertAdjacentHTML('beforeend', dialogHtml);
|
|
|
|
// Tampilkan dialog
|
|
const dialog = new bootstrap.Modal(document.getElementById('dismissDialog'));
|
|
dialog.show();
|
|
|
|
// Event untuk snooze button
|
|
document.querySelector('#dismissDialog .btn-secondary').addEventListener('click', function() {
|
|
snoozeRepeatingNotification();
|
|
});
|
|
}
|
|
|
|
// Register Service Worker untuk background notifications
|
|
async function registerServiceWorker() {
|
|
if ('serviceWorker' in navigator) {
|
|
try {
|
|
const registration = await navigator.serviceWorker.register('/sw.js');
|
|
console.log('Service Worker registered successfully:', registration);
|
|
|
|
// Update service worker jika ada versi baru
|
|
registration.addEventListener('updatefound', () => {
|
|
const newWorker = registration.installing;
|
|
newWorker.addEventListener('statechange', () => {
|
|
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
|
|
console.log('New service worker available');
|
|
}
|
|
});
|
|
});
|
|
|
|
return registration;
|
|
} catch (error) {
|
|
console.error('Service Worker registration failed:', error);
|
|
return null;
|
|
}
|
|
} else {
|
|
console.log('Service Worker not supported');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Load audio files untuk dropdown
|
|
function loadAudioFiles() {
|
|
const audioSelect = document.getElementById('notification_sound');
|
|
if (!audioSelect) return;
|
|
|
|
// Daftar file audio yang tersedia
|
|
const audioFiles = [
|
|
{ value: 'default', label: 'Default Notification' },
|
|
{ value: 'notification', label: 'Notification Sound' },
|
|
{ value: 'funny-alarm-317531', label: 'Funny Alarm' }
|
|
];
|
|
|
|
// Clear existing options
|
|
audioSelect.innerHTML = '';
|
|
|
|
// Add options
|
|
audioFiles.forEach(audio => {
|
|
const option = document.createElement('option');
|
|
option.value = audio.value;
|
|
option.textContent = audio.label;
|
|
audioSelect.appendChild(option);
|
|
});
|
|
|
|
// Set current value
|
|
const currentSound = localStorage.getItem('notification_sound') || 'default';
|
|
audioSelect.value = currentSound;
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.form-range::-webkit-slider-thumb {
|
|
background: #007bff;
|
|
}
|
|
|
|
.form-range::-moz-range-thumb {
|
|
background: #007bff;
|
|
}
|
|
|
|
.nav-tabs .nav-link {
|
|
border: none;
|
|
border-bottom: 2px solid transparent;
|
|
color: #6c757d;
|
|
}
|
|
|
|
.nav-tabs .nav-link.active {
|
|
border-bottom-color: #007bff;
|
|
color: #007bff;
|
|
background: none;
|
|
}
|
|
|
|
.list-group-item-action:hover {
|
|
background-color: #f8f9fa;
|
|
}
|
|
|
|
.alert {
|
|
border-radius: 0.5rem;
|
|
}
|
|
</style>
|
|
@endsection
|