168 lines
6.4 KiB
TypeScript
168 lines
6.4 KiB
TypeScript
|
|
/**
|
||
|
|
* 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<NotificationManagerProps> = () => {
|
||
|
|
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;
|