pmtool / src /components /dashboard /work-schedule.tsx
devarshia5's picture
Upload 487 files
d97b8f9 verified
Raw
History Blame Contribute Delete
40.5 kB
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<Date>(new Date());
const [currentActivityIndex, setCurrentActivityIndex] = useState<number>(-1);
const [lastNotifiedIndex, setLastNotifiedIndex] = useState<number>(-1);
const [showNotificationModal, setShowNotificationModal] = useState<boolean>(false);
const [notificationsEnabled, setNotificationsEnabled] = useState<boolean>(true);
const [browserNotificationsEnabled, setBrowserNotificationsEnabled] = useState<boolean>(false);
const [browserNotificationsPermission, setBrowserNotificationsPermission] = useState<string>("default");
const [currentNotification, setCurrentNotification] = useState<{
title: string;
description: string;
type: "focused" | "collaborative" | "rest" | "light";
} | null>(null);
const [audioInitialized, setAudioInitialized] = useState<boolean>(false);
// Audio element ref for better control and preloading
const notificationAudioRef = useRef<HTMLAudioElement | null>(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: <Users className="h-4 w-4" />,
},
{
time: "10:30 AM – 11:30 AM",
activity: "Deep Work Session 1",
type: "focused",
icon: <Lock className="h-4 w-4" />,
},
{
time: "11:30 AM – 12:00 PM",
activity: "Team Discussion / Quick Sync",
type: "collaborative",
icon: <Users className="h-4 w-4" />,
},
{
time: "12:00 PM – 1:00 PM",
activity: "Deep Work Session 2",
type: "focused",
icon: <Lock className="h-4 w-4" />,
},
{
time: "1:00 PM – 1:45 PM",
activity: "Lunch Break",
type: "rest",
icon: <Coffee className="h-4 w-4" />,
},
{
time: "1:45 PM – 2:30 PM",
activity: "Team Collaboration / Emails",
type: "light",
icon: <Mail className="h-4 w-4" />,
},
{
time: "2:30 PM – 3:30 PM",
activity: "Deep Work Session 3",
type: "focused",
icon: <Lock className="h-4 w-4" />,
},
{
time: "3:30 PM – 4:00 PM",
activity: "Team Discussion / Admin Tasks",
type: "collaborative",
icon: <PanelRight className="h-4 w-4" />,
},
{
time: "4:00 PM – 4:15 PM",
activity: "Tea Break",
type: "rest",
icon: <Coffee className="h-4 w-4" />,
},
{
time: "4:15 PM – 4:30 PM",
activity: "Flex / Light Catch-Up",
type: "light",
icon: <Users className="h-4 w-4" />,
},
{
time: "4:30 PM – 5:30 PM",
activity: "Deep Work Session 4",
type: "focused",
icon: <Lock className="h-4 w-4" />,
},
{
time: "5:30 PM – 6:30 PM",
activity: "Team Support / Wrap-Up",
type: "light",
icon: <Users className="h-4 w-4" />,
},
];
// 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 <Lock className="h-12 w-12 text-purple-600" />;
case "collaborative":
return <Users className="h-12 w-12 text-blue-600" />;
case "rest":
return <Coffee className="h-12 w-12 text-red-600" />;
case "light":
return <Mail className="h-12 w-12 text-green-600" />;
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 (
<>
<Card className="hover-scale">
<CardHeader className="flex flex-row items-center justify-between pb-2 space-y-0">
<div>
<CardTitle className="text-lg flex items-center gap-2">
<Clock className="h-5 w-5 text-muted-foreground" />
Daily Work Schedule
</CardTitle>
<CardDescription>
Office Hours: 9:30 AM – 6:30 PM
</CardDescription>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<span className="text-sm text-muted-foreground font-medium">
{notificationsEnabled ? (
<span className="flex items-center gap-1">
<Bell className="h-3.5 w-3.5" />
On
</span>
) : (
<span className="flex items-center gap-1">
<BellOff className="h-3.5 w-3.5" />
Off
</span>
)}
</span>
<Switch
checked={notificationsEnabled}
onCheckedChange={setNotificationsEnabled}
aria-label="Toggle notifications"
/>
</div>
<div className="text-sm font-medium">
Current Time: {currentTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
</div>
<button
onClick={() => {
if (currentActivityIndex >= 0) {
const item = scheduleItems[currentActivityIndex];
console.log("Manual notification clicked:", item);
// Use the common notification logic, passing false to indicate it's not automatic
showNotification(item, false);
} else {
console.log("No current activity to notify about");
toast.warning("No Current Activity", {
description: "There is no scheduled activity at this time.",
});
}
}}
className="p-1 rounded-full hover:bg-accent"
aria-label="Show current activity notification"
>
<Bell className="h-4 w-4 text-muted-foreground" />
</button>
<button
onClick={testNotificationSound}
className="p-1 rounded-full hover:bg-accent"
title="Test notification sound"
aria-label="Test notification sound"
>
<BellRing className="h-4 w-4 text-muted-foreground" />
</button>
</div>
</CardHeader>
{/* Audio element for testing purposes (can be hidden with CSS) */}
<audio
id="notification-sound-test"
src="/notification.mp3"
preload="auto"
style={{ display: 'none' }}
/>
<CardContent>
<div className="space-y-3">
{scheduleItems.map((item, index) => {
const isCurrentActivity = index === currentActivityIndex;
return (
<div
key={index}
className={`flex items-center justify-between p-2 rounded-md ${
isCurrentActivity
? 'bg-accent border-l-4 border-primary animate-pulse'
: ''
}`}
>
<div className="flex items-center gap-3">
<Badge
variant={getBadgeVariant(item.type, isCurrentActivity)}
className={`w-8 h-8 rounded-full flex items-center justify-center ${
item.type === "focused" ? "bg-purple-100 text-purple-600 hover:bg-purple-200" :
item.type === "rest" ? "bg-red-100 text-red-600 hover:bg-red-200" :
item.type === "collaborative" ? "bg-blue-100 text-blue-600 hover:bg-blue-200" :
"bg-green-100 text-green-600 hover:bg-green-200"
}`}
>
{item.icon}
</Badge>
<div>
<p className="text-sm font-medium">{item.activity}</p>
<p className="text-xs text-muted-foreground">{item.time}</p>
</div>
</div>
<div>
<Badge
variant={item.type === "focused" ? "default" : "outline"}
className={`
${item.type === "focused" ? "bg-purple-100 text-purple-600 hover:bg-purple-200 border-purple-200" :
item.type === "rest" ? "bg-red-100 text-red-600 hover:bg-red-200 border-red-200" :
item.type === "collaborative" ? "bg-blue-100 text-blue-600 hover:bg-blue-200 border-blue-200" :
"bg-green-100 text-green-600 hover:bg-green-200 border-green-200"}
${isCurrentActivity ? "ring-2 ring-offset-1 ring-primary" : ""}
`}
>
{item.type === "focused" ? "Deep Work" :
item.type === "collaborative" ? "Collaborative" :
item.type === "rest" ? "Break" : "Light Work"}
</Badge>
</div>
</div>
);
})}
</div>
</CardContent>
</Card>
{/* Custom Large Notification Modal */}
{showNotificationModal && currentNotification && (
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black bg-opacity-50 backdrop-blur-sm"
onClick={closeModal}
/>
{/* Modal Content */}
<div
className={`relative mx-4 w-full max-w-md rounded-lg border shadow-lg transform transition-all ${getModalStyles(currentNotification.type)}`}
style={{ animation: 'scaleIn 0.3s ease-out' }}
>
{/* Close button */}
<button
onClick={closeModal}
className="absolute right-4 top-4 rounded-full p-1 text-gray-500 hover:bg-gray-100"
>
<X className="h-5 w-5" />
</button>
<div className="p-6">
{/* Header */}
<div className="flex items-start gap-4">
<div className="p-3 rounded-full bg-white shadow-md">
{getModalIcon(currentNotification.type)}
</div>
<div className="flex-1 pt-2">
<h2 className="text-xl font-bold">{currentNotification.title}</h2>
<p className="text-base mt-1 opacity-90">
{currentNotification.description}
</p>
</div>
</div>
{/* Additional information based on activity type */}
<div className="mt-6 p-4 rounded-md bg-white bg-opacity-50">
{currentNotification.type === "focused" ? (
<p className="text-sm">🧠 Time to focus! Close distractions, silence notifications, and dedicate this time to deep, meaningful work.</p>
) : currentNotification.type === "collaborative" ? (
<p className="text-sm">👥 Connect with your team! This is a good time for meetings, discussions, and collaborative problem-solving.</p>
) : currentNotification.type === "rest" ? (
<p className="text-sm">🔋 Take a break! Step away from your desk, stretch, or grab a refreshment to recharge your energy.</p>
) : (
<p className="text-sm">📋 Handle lighter tasks! This is a good time for emails, planning, and administrative tasks.</p>
)}
</div>
{/* Footer */}
<div className="mt-6 space-y-3">
<div className="flex justify-between items-center">
<div className="flex items-center gap-2">
<Switch
id="notification-toggle"
checked={notificationsEnabled}
onCheckedChange={setNotificationsEnabled}
aria-label="Toggle notifications"
/>
<label
htmlFor="notification-toggle"
className="text-sm cursor-pointer"
>
{notificationsEnabled ? "Notifications On" : "Notifications Off"}
</label>
</div>
{browserNotificationsSupported && (
<div className="flex items-center gap-2">
<Switch
id="browser-notification-toggle"
checked={browserNotificationsEnabled}
onCheckedChange={(checked) => {
if (checked && browserNotificationsPermission !== 'granted') {
requestBrowserNotificationPermission();
} else {
setBrowserNotificationsEnabled(checked);
}
}}
disabled={browserNotificationsPermission === 'denied'}
aria-label="Toggle browser notifications"
/>
<label
htmlFor="browser-notification-toggle"
className="text-sm cursor-pointer"
>
Browser Notifications
</label>
</div>
)}
</div>
<div className="flex justify-end">
<Button
onClick={closeModal}
className={`font-semibold ${
currentNotification.type === "focused" ? "bg-purple-600 hover:bg-purple-700" :
currentNotification.type === "collaborative" ? "bg-blue-600 hover:bg-blue-700" :
currentNotification.type === "rest" ? "bg-red-600 hover:bg-red-700" :
"bg-green-600 hover:bg-green-700"
} text-white`}
>
Acknowledge
</Button>
</div>
</div>
</div>
</div>
</div>
)}
{/* Add the animation styles */}
<style dangerouslySetInnerHTML={{
__html: `
@keyframes scaleIn {
from { opacity: 0; transform: scale(0.9); }
to { opacity: 1; transform: scale(1); }
}
.overflow-hidden {
overflow: hidden;
}
`}} />
</>
);
}