feat: dark mode for all pages, calendar view for events

This commit is contained in:
2026-01-29 14:12:35 +00:00
parent 8c27b7b522
commit d5706d4ead
16 changed files with 710 additions and 457 deletions

View File

@@ -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>