/** * NotificationManager Component * Handles browser notifications for submission completion * Uses Supabase Realtime to listen for updates and localStorage to filter notifications */ import { useEffect } from 'react'; import { supabase } from '../utils/supabase'; interface NotificationManagerProps { // No props needed - component manages its own state } const NotificationManager: React.FC = () => { useEffect(() => { console.log('[NotificationManager] Component mounted'); // Request notification permission on mount if ('Notification' in window && Notification.permission === 'default') { Notification.requestPermission().then(permission => { console.log('[NotificationManager] Notification permission:', permission); }); } else { console.log('[NotificationManager] Notification permission status:', Notification.permission); } console.log('[NotificationManager] Setting up Supabase subscription...'); // Subscribe to Supabase Realtime for submission updates const channel = supabase .channel('submissions-notifications') .on( 'postgres_changes', { event: 'UPDATE', schema: 'public', table: 'submissions', }, (payload) => { console.log('[NotificationManager] Submission updated:', payload); handleSubmissionUpdate(payload.new); } ) .subscribe((status) => { console.log('[NotificationManager] Subscription status:', status); }); console.log('[NotificationManager] Subscription created'); // Cleanup on unmount return () => { console.log('[NotificationManager] Cleaning up subscription'); supabase.removeChannel(channel); }; }, []); const handleSubmissionUpdate = (submission: any) => { console.log('[NotificationManager] handleSubmissionUpdate called with:', submission); // Only process completed or failed submissions if (submission.status !== 'completed' && submission.status !== 'failed') { console.log('[NotificationManager] Ignoring submission with status:', submission.status); return; } // Check if this submission was initiated by current browser const mySubmissions = getMySubmissions(); console.log('[NotificationManager] My submissions:', mySubmissions); console.log('[NotificationManager] Current submission_id:', submission.submission_id); if (!mySubmissions.includes(submission.submission_id)) { console.log('[NotificationManager] Not our submission, ignoring'); return; // Not our submission, ignore } console.log('[NotificationManager] Showing notification...'); // Show notification if ('Notification' in window && Notification.permission === 'granted') { // Parse results to count errors const results = submission.results || []; const errorCount = results.filter((r: any) => r.status === 'error').length; const totalCount = results.length; const hasErrors = errorCount > 0; // Determine title based on status let title: string; if (submission.status === 'failed' || hasErrors) { title = `❌ Có ${errorCount} link bị lỗi`; } else { title = '✅ Cấp quyền TMS hoàn tất'; } // Build body content with username and GE info const input = submission.input || {}; const usernameList = input.username_list || []; const geInput = input.ge_input || ''; // Format username (show first 3, then "...") const usernameDisplay = usernameList.length > 3 ? `${usernameList.slice(0, 3).join(', ')}...` : usernameList.join(', '); // Format GE input (show first 3 lines, then "...") const geLines = geInput.split('\n').filter((line: string) => line.trim()); const geDisplay = geLines.length > 3 ? `${geLines.slice(0, 3).join(', ')}...` : geLines.join(', '); const body = `${usernameDisplay}\n${geDisplay}`; const notification = new Notification(title, { body, icon: '/push_noti.png', tag: submission.submission_id, // Prevent duplicate notifications }); // Focus window when notification is clicked notification.onclick = () => { window.focus(); notification.close(); }; // Auto close after 10 seconds setTimeout(() => notification.close(), 10000); console.log('[NotificationManager] Notification shown'); } else { console.log('[NotificationManager] Notification permission not granted:', Notification.permission); } // Remove from localStorage after notification removeMySubmission(submission.submission_id); }; return null; // This component doesn't render anything }; // Helper functions for localStorage management const STORAGE_KEY = 'my_submissions'; export const addMySubmission = (submissionId: string) => { const existing = getMySubmissions(); if (!existing.includes(submissionId)) { existing.push(submissionId); localStorage.setItem(STORAGE_KEY, JSON.stringify(existing)); console.log('[NotificationManager] Added submission to localStorage:', submissionId); } }; export const getMySubmissions = (): string[] => { try { const stored = localStorage.getItem(STORAGE_KEY); return stored ? JSON.parse(stored) : []; } catch { return []; } }; export const removeMySubmission = (submissionId: string) => { const existing = getMySubmissions(); const filtered = existing.filter(id => id !== submissionId); localStorage.setItem(STORAGE_KEY, JSON.stringify(filtered)); console.log('[NotificationManager] Removed submission from localStorage:', submissionId); }; export default NotificationManager;