feat: dark mode for all pages, calendar view for events
This commit is contained in:
@@ -31,16 +31,16 @@ const typeIcons: Record<string, typeof Calendar> = {
|
||||
};
|
||||
|
||||
const typeColors: Record<string, string> = {
|
||||
overdue: 'text-red-500 bg-red-50',
|
||||
upcoming: 'text-blue-500 bg-blue-50',
|
||||
stale: 'text-amber-500 bg-amber-50',
|
||||
drafts: 'text-purple-500 bg-purple-50',
|
||||
overdue: 'text-red-500 bg-red-50 dark:bg-red-900/30',
|
||||
upcoming: 'text-blue-500 bg-blue-50 dark:bg-blue-900/30',
|
||||
stale: 'text-amber-500 bg-amber-50 dark:bg-amber-900/30',
|
||||
drafts: 'text-purple-500 bg-purple-50 dark:bg-purple-900/30',
|
||||
};
|
||||
|
||||
const priorityDot: Record<string, string> = {
|
||||
high: 'bg-red-500',
|
||||
medium: 'bg-amber-400',
|
||||
low: 'bg-slate-300',
|
||||
low: 'bg-slate-300 dark:bg-slate-500',
|
||||
};
|
||||
|
||||
export default function NotificationBell() {
|
||||
@@ -52,12 +52,10 @@ export default function NotificationBell() {
|
||||
|
||||
useEffect(() => {
|
||||
fetchNotifications();
|
||||
// Refresh every 5 minutes
|
||||
const interval = setInterval(fetchNotifications, 5 * 60 * 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
// Close on outside click
|
||||
useEffect(() => {
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
if (ref.current && !ref.current.contains(e.target as Node)) {
|
||||
@@ -92,7 +90,7 @@ export default function NotificationBell() {
|
||||
onClick={() => setOpen(!open)}
|
||||
className={cn(
|
||||
'relative p-2 rounded-lg transition-colors',
|
||||
open ? 'bg-slate-100 text-slate-700' : 'text-slate-400 hover:bg-slate-100 hover:text-slate-600'
|
||||
open ? 'bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-200' : 'text-slate-400 dark:text-slate-500 hover:bg-slate-100 dark:hover:bg-slate-700 hover:text-slate-600 dark:hover:text-slate-300'
|
||||
)}
|
||||
>
|
||||
<Bell className="w-5 h-5" />
|
||||
@@ -107,13 +105,13 @@ export default function NotificationBell() {
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
<div className="absolute right-0 top-full mt-2 w-80 sm:w-96 bg-white rounded-xl border border-slate-200 shadow-xl z-50 overflow-hidden">
|
||||
<div className="flex items-center justify-between px-4 py-3 border-b border-slate-100">
|
||||
<h3 className="text-sm font-semibold text-slate-800">Notifications</h3>
|
||||
<div className="absolute right-0 top-full mt-2 w-80 sm:w-96 bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 shadow-xl z-50 overflow-hidden">
|
||||
<div className="flex items-center justify-between px-4 py-3 border-b border-slate-100 dark:border-slate-700">
|
||||
<h3 className="text-sm font-semibold text-slate-800 dark:text-slate-200">Notifications</h3>
|
||||
{counts && (
|
||||
<div className="flex items-center gap-2 text-xs text-slate-400">
|
||||
<div className="flex items-center gap-2 text-xs text-slate-400 dark:text-slate-500">
|
||||
{counts.overdue > 0 && (
|
||||
<span className="text-red-500 font-medium">{counts.overdue} overdue</span>
|
||||
<span className="text-red-500 dark:text-red-400 font-medium">{counts.overdue} overdue</span>
|
||||
)}
|
||||
{counts.upcoming > 0 && (
|
||||
<span>{counts.upcoming} upcoming</span>
|
||||
@@ -124,7 +122,7 @@ export default function NotificationBell() {
|
||||
|
||||
<div className="max-h-[400px] overflow-y-auto">
|
||||
{visibleNotifs.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-8 text-slate-400">
|
||||
<div className="flex flex-col items-center justify-center py-8 text-slate-400 dark:text-slate-500">
|
||||
<Clock className="w-8 h-8 mb-2" />
|
||||
<p className="text-sm">All caught up!</p>
|
||||
</div>
|
||||
@@ -132,20 +130,20 @@ export default function NotificationBell() {
|
||||
visibleNotifs.map(n => {
|
||||
const Icon = typeIcons[n.type] || Calendar;
|
||||
return (
|
||||
<div key={n.id} className="flex items-start gap-3 px-4 py-3 hover:bg-slate-50 border-b border-slate-50 last:border-0">
|
||||
<div className={cn('p-1.5 rounded-lg mt-0.5', typeColors[n.type] || 'bg-slate-50 text-slate-500')}>
|
||||
<div key={n.id} className="flex items-start gap-3 px-4 py-3 hover:bg-slate-50 dark:hover:bg-slate-700 border-b border-slate-50 dark:border-slate-700 last:border-0">
|
||||
<div className={cn('p-1.5 rounded-lg mt-0.5', typeColors[n.type] || 'bg-slate-50 dark:bg-slate-700 text-slate-500')}>
|
||||
<Icon className="w-4 h-4" />
|
||||
</div>
|
||||
<Link to={n.link} onClick={() => setOpen(false)} className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className={cn('w-1.5 h-1.5 rounded-full flex-shrink-0', priorityDot[n.priority])} />
|
||||
<p className="text-sm font-medium text-slate-800 truncate">{n.title}</p>
|
||||
<p className="text-sm font-medium text-slate-800 dark:text-slate-200 truncate">{n.title}</p>
|
||||
</div>
|
||||
<p className="text-xs text-slate-400 mt-0.5">{n.description}</p>
|
||||
<p className="text-xs text-slate-400 dark:text-slate-500 mt-0.5">{n.description}</p>
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => dismiss(n.id)}
|
||||
className="p-1 rounded text-slate-300 hover:text-slate-500 hover:bg-slate-100 flex-shrink-0"
|
||||
className="p-1 rounded text-slate-300 dark:text-slate-500 hover:text-slate-500 dark:hover:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-600 flex-shrink-0"
|
||||
>
|
||||
<X className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
@@ -156,11 +154,11 @@ export default function NotificationBell() {
|
||||
</div>
|
||||
|
||||
{visibleNotifs.length > 0 && (
|
||||
<div className="border-t border-slate-100 px-4 py-2.5">
|
||||
<div className="border-t border-slate-100 dark:border-slate-700 px-4 py-2.5">
|
||||
<Link
|
||||
to="/reports"
|
||||
onClick={() => setOpen(false)}
|
||||
className="text-xs text-blue-600 hover:text-blue-700 font-medium"
|
||||
className="text-xs text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 font-medium"
|
||||
>
|
||||
View Reports →
|
||||
</Link>
|
||||
|
||||
Reference in New Issue
Block a user