import React, { useState, useEffect, useRef } from "react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Clock, Lock, Coffee, Users, Mail, PanelRight, Bell, X, BellOff, BellRing } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; interface ScheduleItem { time: string; activity: string; type: "focused" | "collaborative" | "rest" | "light"; icon: React.ReactNode; } export default function WorkSchedule() { const [currentTime, setCurrentTime] = useState(new Date()); const [currentActivityIndex, setCurrentActivityIndex] = useState(-1); const [lastNotifiedIndex, setLastNotifiedIndex] = useState(-1); const [showNotificationModal, setShowNotificationModal] = useState(false); const [notificationsEnabled, setNotificationsEnabled] = useState(true); const [browserNotificationsEnabled, setBrowserNotificationsEnabled] = useState(false); const [browserNotificationsPermission, setBrowserNotificationsPermission] = useState("default"); const [currentNotification, setCurrentNotification] = useState<{ title: string; description: string; type: "focused" | "collaborative" | "rest" | "light"; } | null>(null); const [audioInitialized, setAudioInitialized] = useState(false); // Audio element ref for better control and preloading const notificationAudioRef = useRef(null); // Initialize audio system on first user interaction const initializeAudio = () => { if (audioInitialized) return; console.log("Initializing audio system from user interaction"); try { // Try to play a silent sound to unlock audio const audioElement = document.getElementById('notification-sound-test') as HTMLAudioElement; if (audioElement) { audioElement.volume = 0.5; // Nearly silent const promise = audioElement.play(); if (promise !== undefined) { promise .then(() => { console.log("Audio system unlocked successfully"); // Pause immediately after unlocking audioElement.pause(); audioElement.currentTime = 0; setAudioInitialized(true); }) .catch(err => { console.error("Could not unlock audio system:", err); }); } } // Also try to use the ref-based audio if (notificationAudioRef.current) { notificationAudioRef.current.volume = 0.01; const promise = notificationAudioRef.current.play(); if (promise !== undefined) { promise .then(() => { console.log("Ref audio system unlocked successfully"); notificationAudioRef.current?.pause(); if (notificationAudioRef.current) { notificationAudioRef.current.currentTime = 0; } setAudioInitialized(true); }) .catch(err => { console.error("Could not unlock ref audio system:", err); }); } } } catch (error) { console.error("Error initializing audio:", error); } }; // Add global click listener to initialize audio on first interaction useEffect(() => { const handleUserInteraction = () => { initializeAudio(); // Remove listeners after initialization if (audioInitialized) { document.removeEventListener('click', handleUserInteraction); document.removeEventListener('touchstart', handleUserInteraction); document.removeEventListener('keydown', handleUserInteraction); } }; document.addEventListener('click', handleUserInteraction); document.addEventListener('touchstart', handleUserInteraction); document.addEventListener('keydown', handleUserInteraction); return () => { document.removeEventListener('click', handleUserInteraction); document.removeEventListener('touchstart', handleUserInteraction); document.removeEventListener('keydown', handleUserInteraction); }; }, [audioInitialized]); // Initialize audio element on component mount useEffect(() => { // Create audio element and preload it const audio = new Audio('/notification.mp3'); audio.preload = 'auto'; audio.volume = 1.0; // Log loading events for debugging audio.addEventListener('canplaythrough', () => { console.log("Notification sound loaded and ready to play"); }); audio.addEventListener('error', (e) => { console.error("Error loading notification sound:", e); // Try with an alternative sound file as fallback console.log("Trying fallback notification sound"); audio.src = '/notification-fallback.mp3'; // If that fails too, create a simple beep using the Web Audio API audio.addEventListener('error', () => { console.log("Creating synthetic beep as last resort"); try { // Create a simple beep using Web Audio API as last resort const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)(); const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); oscillator.type = 'sine'; oscillator.frequency.value = 800; // frequency in hertz gainNode.gain.value = 0.5; oscillator.connect(gainNode); gainNode.connect(audioContext.destination); // Store the audio context for cleanup (window as any).scheduleAudioContext = audioContext; // Define a method to play this synthetic beep notificationAudioRef.current = { play: () => { oscillator.start(); oscillator.stop(audioContext.currentTime + 0.3); // beep for 300ms return Promise.resolve(); }, pause: () => {}, currentTime: 0 } as any; console.log("Synthetic beep created successfully"); } catch (err) { console.error("Failed to create synthetic beep:", err); } }); }); // Store the reference notificationAudioRef.current = audio; // Clean up on unmount return () => { if (notificationAudioRef.current) { if ('pause' in notificationAudioRef.current) { notificationAudioRef.current.pause(); } notificationAudioRef.current = null; } // Clean up audio context if it was created if ((window as any).scheduleAudioContext) { (window as any).scheduleAudioContext.close().catch(console.error); delete (window as any).scheduleAudioContext; } }; }, []); // Helper function to play notification sound const playNotificationSound = () => { if (!notificationAudioRef.current) { console.error("Audio element not initialized"); return; } console.log("Attempting to play notification sound"); // Try to initialize audio system if not already done if (!audioInitialized) { console.log("Audio not initialized, attempting to initialize first"); initializeAudio(); // Show a toast prompting user interaction toast.info("Click anywhere to enable sounds", { duration: 3000, action: { label: "Enable", onClick: initializeAudio } }); } try { // Reset audio to beginning in case it was already played notificationAudioRef.current.currentTime = 0; // Play the sound const playPromise = notificationAudioRef.current.play(); if (playPromise !== undefined) { playPromise .then(() => console.log("Audio played successfully")) .catch(err => { console.error("Could not play notification sound", err); // Try to play using the DOM audio element as fallback const audioElement = document.getElementById('notification-sound-test') as HTMLAudioElement; if (audioElement) { console.log("Trying to play via DOM audio element as fallback"); audioElement.currentTime = 0; audioElement.volume = 0.5; audioElement.play() .then(() => console.log("DOM audio played successfully as fallback")) .catch(domErr => { console.error("DOM audio fallback also failed:", domErr); // Most browsers require user interaction before playing audio // Create a one-time click event listener to try playing again const playOnNextClick = () => { initializeAudio(); document.removeEventListener('click', playOnNextClick); }; document.addEventListener('click', playOnNextClick, { once: true }); toast.info("Click anywhere to enable sound notifications", { duration: 3000, }); }); } }); } } catch (error) { console.error("Error playing notification sound:", error); } }; // Check if browser notifications are supported const browserNotificationsSupported = typeof window !== 'undefined' && 'Notification' in window; // Load notification preferences from localStorage on component mount useEffect(() => { const savedPreference = localStorage.getItem('workScheduleNotificationsEnabled'); if (savedPreference !== null) { const enabled = savedPreference === 'true'; console.log('Loading notification preference from localStorage:', enabled); setNotificationsEnabled(enabled); } const savedBrowserNotifPreference = localStorage.getItem('workScheduleBrowserNotificationsEnabled'); if (savedBrowserNotifPreference !== null) { const enabled = savedBrowserNotifPreference === 'true'; console.log('Loading browser notification preference from localStorage:', enabled); setBrowserNotificationsEnabled(enabled); } // Check current browser notification permission if (browserNotificationsSupported) { setBrowserNotificationsPermission(Notification.permission); } }, [browserNotificationsSupported]); // Save notification preferences to localStorage when changed useEffect(() => { console.log('Saving notification preference to localStorage:', notificationsEnabled); localStorage.setItem('workScheduleNotificationsEnabled', notificationsEnabled.toString()); }, [notificationsEnabled]); useEffect(() => { console.log('Saving browser notification preference to localStorage:', browserNotificationsEnabled); localStorage.setItem('workScheduleBrowserNotificationsEnabled', browserNotificationsEnabled.toString()); }, [browserNotificationsEnabled]); // Request browser notification permission const requestBrowserNotificationPermission = async () => { if (!browserNotificationsSupported) return; try { const permission = await Notification.requestPermission(); setBrowserNotificationsPermission(permission); if (permission === 'granted') { setBrowserNotificationsEnabled(true); toast.success('Browser notifications enabled!'); } else { setBrowserNotificationsEnabled(false); toast.error('Browser notification permission denied'); } } catch (error) { console.error('Error requesting notification permission:', error); toast.error('Failed to enable browser notifications'); } }; // Show a browser notification const showBrowserNotification = (title: string, body: string, icon: string) => { if (!browserNotificationsSupported || !browserNotificationsEnabled || Notification.permission !== 'granted') { return; } try { // Create notification options with specific settings for Windows const options = { body, icon, silent: false, // Windows-specific options requireInteraction: true, // Keep notification until user dismisses it tag: 'work-schedule', // Group notifications with same tag // Add badge for supported platforms badge: icon, // Vibration pattern for mobile devices (doesn't affect Windows) vibrate: [200, 100, 200] }; // Create notification with enhanced options const notification = new Notification(title, options); // Close the notification after 30 seconds (increased from 10 to ensure visibility) setTimeout(() => notification.close(), 30000); // Focus the window when notification is clicked notification.onclick = (event) => { console.log('Notification clicked:', event); window.focus(); notification.close(); }; // Log when notification is shown or closed notification.onshow = () => console.log('Notification shown'); notification.onclose = () => console.log('Notification closed'); notification.onerror = (err) => console.error('Notification error:', err); } catch (error) { console.error('Error showing browser notification:', error); } }; // Update current time every minute useEffect(() => { const timer = setInterval(() => { setCurrentTime(new Date()); }, 60000); return () => clearInterval(timer); }, []); // Daily schedule data const scheduleItems: ScheduleItem[] = [ { time: "9:30 AM – 10:30 AM", activity: "Team Collaboration / Planning", type: "light", icon: , }, { time: "10:30 AM – 11:30 AM", activity: "Deep Work Session 1", type: "focused", icon: , }, { time: "11:30 AM – 12:00 PM", activity: "Team Discussion / Quick Sync", type: "collaborative", icon: , }, { time: "12:00 PM – 1:00 PM", activity: "Deep Work Session 2", type: "focused", icon: , }, { time: "1:00 PM – 1:45 PM", activity: "Lunch Break", type: "rest", icon: , }, { time: "1:45 PM – 2:30 PM", activity: "Team Collaboration / Emails", type: "light", icon: , }, { time: "2:30 PM – 3:30 PM", activity: "Deep Work Session 3", type: "focused", icon: , }, { time: "3:30 PM – 4:00 PM", activity: "Team Discussion / Admin Tasks", type: "collaborative", icon: , }, { time: "4:00 PM – 4:15 PM", activity: "Tea Break", type: "rest", icon: , }, { time: "4:15 PM – 4:30 PM", activity: "Flex / Light Catch-Up", type: "light", icon: , }, { time: "4:30 PM – 5:30 PM", activity: "Deep Work Session 4", type: "focused", icon: , }, { time: "5:30 PM – 6:30 PM", activity: "Team Support / Wrap-Up", type: "light", icon: , }, ]; // Helper function to show a notification const showNotification = (item: ScheduleItem, isAutomatic: boolean = true) => { // If notifications are disabled and this is an automatic notification, don't show it if (!notificationsEnabled && isAutomatic) { console.log("Notifications are disabled, not showing notification"); return; } // Play notification sound if notifications are enabled if (notificationsEnabled) { playNotificationSound(); } const title = isAutomatic ? `Time for ${item.activity}` : `Current Activity: ${item.activity}`; const description = `${item.time} - ${ item.type === "focused" ? "Focus on deep work" : item.type === "collaborative" ? "Connect with your team" : item.type === "rest" ? "Take a break to recharge" : "Handle lighter tasks" }`; // Show toast notification toast.info(title, { description }); // Show browser notification if enabled if (browserNotificationsEnabled && browserNotificationsPermission === 'granted') { // Select the appropriate icon based on activity type let iconPath = '/notification-icon.png'; // Default icon if (item.type === 'focused') iconPath = '/focused-icon.png'; else if (item.type === 'collaborative') iconPath = '/collaborative-icon.png'; else if (item.type === 'rest') iconPath = '/break-icon.png'; else if (item.type === 'light') iconPath = '/light-icon.png'; showBrowserNotification(title, description, iconPath); } // For prominent notification, show custom modal setCurrentNotification({ title, description, type: item.type }); setShowNotificationModal(true); // Add a class to the body to prevent scrolling when modal is open document.body.classList.add('overflow-hidden'); }; // Determine current activity based on time and show notifications useEffect(() => { const now = currentTime; const hour = now.getHours(); const minute = now.getMinutes(); const currentTimeInMinutes = hour * 60 + minute; // Find current activity const index = scheduleItems.findIndex((item) => { const [startTime, endTime] = item.time.split(" – "); const [startHour, startMinutePart] = startTime.split(":"); const startMinute = parseInt(startMinutePart); const startHourNum = parseInt(startHour) + (startTime.includes("PM") && parseInt(startHour) !== 12 ? 12 : 0); const startTimeInMinutes = startHourNum * 60 + startMinute; const [endHour, endMinutePart] = endTime.split(":"); const endMinute = parseInt(endMinutePart); const endHourNum = parseInt(endHour) + (endTime.includes("PM") && parseInt(endHour) !== 12 ? 12 : 0); const endTimeInMinutes = endHourNum * 60 + endMinute; return currentTimeInMinutes >= startTimeInMinutes && currentTimeInMinutes < endTimeInMinutes; }); // Show notification if the activity has changed if (index !== -1 && index !== lastNotifiedIndex) { const item = scheduleItems[index]; // Only show notification if notifications are enabled if (notificationsEnabled) { console.log('Notifications enabled, showing notification for:', item.activity); showNotification(item); } else { console.log('Notifications disabled, not showing notification for:', item.activity); } setLastNotifiedIndex(index); } setCurrentActivityIndex(index); }, [currentTime, lastNotifiedIndex, scheduleItems, notificationsEnabled]); // Background color and icon helpers const getModalStyles = (type: "focused" | "collaborative" | "rest" | "light") => { switch (type) { case "focused": return "bg-purple-50 border-purple-200 text-purple-800"; case "collaborative": return "bg-blue-50 border-blue-200 text-blue-800"; case "rest": return "bg-red-50 border-red-200 text-red-800"; case "light": return "bg-green-50 border-green-200 text-green-800"; default: return ""; } }; const getModalIcon = (type: "focused" | "collaborative" | "rest" | "light") => { switch (type) { case "focused": return ; case "collaborative": return ; case "rest": return ; case "light": return ; default: return null; } }; // Get badge style based on activity type const getBadgeVariant = (type: string, isActive: boolean) => { if (isActive) { return "default"; } switch (type) { case "focused": return "outline"; case "collaborative": return "secondary"; case "rest": return "destructive"; case "light": return "outline"; default: return "outline"; } }; // Close the modal const closeModal = () => { setShowNotificationModal(false); document.body.classList.remove('overflow-hidden'); }; // Close notification modal when notifications are disabled useEffect(() => { if (!notificationsEnabled && showNotificationModal) { console.log('Notifications disabled, closing notification modal'); closeModal(); } }, [notificationsEnabled]); // Handle escape key press to close modal useEffect(() => { const handleEscapeKey = (event: KeyboardEvent) => { if (event.key === 'Escape' && showNotificationModal) { closeModal(); } }; document.addEventListener('keydown', handleEscapeKey); return () => { document.removeEventListener('keydown', handleEscapeKey); }; }, [showNotificationModal]); // Test browser notifications with Windows notification features const testBrowserNotification = () => { console.log("Testing browser notification"); console.log("Browser notifications supported:", browserNotificationsSupported); console.log("Browser notifications enabled:", browserNotificationsEnabled); console.log("Browser notifications permission:", browserNotificationsPermission); if (!browserNotificationsSupported) { toast.error("Browser notifications are not supported in this browser"); return; } if (browserNotificationsPermission !== 'granted') { toast.warning("Browser notification permission not granted", { description: "Click the button below to request permission", action: { label: "Request Permission", onClick: () => requestBrowserNotificationPermission() } }); return; } if (!browserNotificationsEnabled) { toast.warning("Browser notifications are disabled", { description: "Enable them in the notification settings", action: { label: "Enable", onClick: () => setBrowserNotificationsEnabled(true) } }); return; } // Show a test notification try { // Create notification options with specific settings for Windows const options = { body: "This is a test Windows notification. If you can see this outside your browser, notifications are working correctly!", icon: "/notification-icon.png", silent: false, // Windows-specific options requireInteraction: true, // Keep notification until user dismisses it tag: 'work-schedule-test', // Group notifications with same tag // Add badge for supported platforms badge: "/notification-icon.png", // Vibration pattern for mobile devices (doesn't affect Windows) vibrate: [200, 100, 200] }; // Create notification with enhanced options const notification = new Notification("Windows Test Notification", options); // Handle notification events notification.onclick = () => { window.focus(); notification.close(); }; // Log notification events notification.onshow = () => console.log('Test notification shown'); notification.onclose = () => console.log('Test notification closed'); notification.onerror = (err) => console.error('Test notification error:', err); toast.success("Test Windows notification sent!", { description: "Check your Windows notification center if you don't see it in the browser." }); } catch (error) { console.error('Error showing test notification:', error); toast.error("Failed to show test notification", { description: error.toString() }); } }; const testNotificationSound = () => { console.log("Testing notification sound"); // Initialize audio system if not already done if (!audioInitialized) { console.log("Audio not initialized, initializing now"); initializeAudio(); } // Try different methods to play the sound for testing // Method 1: DOM audio element const audioElement = document.getElementById('notification-sound-test') as HTMLAudioElement; if (audioElement) { console.log("Testing via DOM audio element"); audioElement.currentTime = 0; audioElement.volume = 0.5; const playPromise = audioElement.play(); if (playPromise !== undefined) { playPromise .then(() => { console.log("DOM audio played successfully"); setAudioInitialized(true); toast.success("Sound played successfully!"); }) .catch(err => { console.error("Could not play DOM audio", err); // Fall back to our ref method console.log("Falling back to ref-based audio"); // Method 2: Ref-based audio if (notificationAudioRef.current) { notificationAudioRef.current.currentTime = 0; notificationAudioRef.current.volume = 0.5; notificationAudioRef.current.play() .then(() => { console.log("Ref audio played successfully"); setAudioInitialized(true); toast.success("Sound played successfully (fallback method)!"); }) .catch(refErr => { console.error("Ref audio also failed:", refErr); // Method 3: Try Web Audio API try { console.log("Trying Web Audio API beep"); const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)(); const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); oscillator.type = 'sine'; oscillator.frequency.value = 800; // frequency in hertz gainNode.gain.value = 0.5; oscillator.connect(gainNode); gainNode.connect(audioContext.destination); oscillator.start(); oscillator.stop(audioContext.currentTime + 0.3); // beep for 300ms toast.success("Using synthetic beep instead", { description: "Your browser is blocking normal audio playback" }); // Clean up context after use setTimeout(() => { audioContext.close().catch(console.error); }, 500); } catch (apiErr) { console.error("Web Audio API also failed:", apiErr); toast.error("Could not play any sound", { description: "Browser is blocking audio playback. Try clicking this toast to enable audio.", action: { label: "Try Again", onClick: initializeAudio } }); } }); } else { toast.error("Audio system not initialized properly"); } }); } } else { // Fall back directly to playNotificationSound playNotificationSound(); toast.info("Testing sound playback..."); } }; return ( <>
Daily Work Schedule Office Hours: 9:30 AM – 6:30 PM
{notificationsEnabled ? ( On ) : ( Off )}
Current Time: {currentTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
{/* Audio element for testing purposes (can be hidden with CSS) */}
{/* Custom Large Notification Modal */} {showNotificationModal && currentNotification && (
{/* Backdrop */}
{/* Modal Content */}
{/* Close button */}
{/* Header */}
{getModalIcon(currentNotification.type)}

{currentNotification.title}

{currentNotification.description}

{/* Additional information based on activity type */}
{currentNotification.type === "focused" ? (

🧠 Time to focus! Close distractions, silence notifications, and dedicate this time to deep, meaningful work.

) : currentNotification.type === "collaborative" ? (

👥 Connect with your team! This is a good time for meetings, discussions, and collaborative problem-solving.

) : currentNotification.type === "rest" ? (

🔋 Take a break! Step away from your desk, stretch, or grab a refreshment to recharge your energy.

) : (

📋 Handle lighter tasks! This is a good time for emails, planning, and administrative tasks.

)}
{/* Footer */}
{browserNotificationsSupported && (
{ if (checked && browserNotificationsPermission !== 'granted') { requestBrowserNotificationPermission(); } else { setBrowserNotificationsEnabled(checked); } }} disabled={browserNotificationsPermission === 'denied'} aria-label="Toggle browser notifications" />
)}
)} {/* Add the animation styles */}