import React, { useState } from "react"; import { base44 } from "@/api/base44Client"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useNavigate } from "react-router-dom"; import { createPageUrl } from "@/utils"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { X, Bell, Calendar, UserPlus, FileText, MessageSquare, AlertCircle, CheckCircle, ArrowRight, MoreVertical, CheckSquare, Package } from "lucide-react"; import { motion, AnimatePresence } from "framer-motion"; import { formatDistanceToNow, format, isToday, isYesterday, isThisWeek, startOfDay } from "date-fns"; const iconMap = { calendar: Calendar, user: UserPlus, invoice: FileText, message: MessageSquare, alert: AlertCircle, check: CheckCircle, }; const colorMap = { blue: "bg-blue-100 text-blue-600", red: "bg-red-100 text-red-600", green: "bg-green-100 text-green-600", yellow: "bg-yellow-100 text-yellow-600", purple: "bg-purple-100 text-purple-600", }; export default function NotificationPanel({ isOpen, onClose }) { const navigate = useNavigate(); const queryClient = useQueryClient(); const [activeFilter, setActiveFilter] = useState('all'); const { data: user } = useQuery({ queryKey: ['current-user-notifications'], queryFn: () => base44.auth.me(), }); const { data: notifications = [] } = useQuery({ queryKey: ['activity-logs', user?.id], queryFn: async () => { if (!user?.id) return []; // Create sample notifications if none exist const existing = await base44.entities.ActivityLog.filter({ userId: user.id }, '-created_date', 50); if (existing.length === 0 && user?.id) { // Create initial sample notifications await base44.entities.ActivityLog.bulkCreate([ { title: "Event Rescheduled", description: "Team Meeting was moved to July 15, 3:00 PM", activity_type: "event_rescheduled", related_entity_type: "event", action_label: "View Event", icon_type: "calendar", icon_color: "blue", is_read: false, user_id: user.id }, { title: "Event Canceled", description: "Product Demo scheduled for May 20 has been canceled", activity_type: "event_canceled", related_entity_type: "event", action_label: "View Event", icon_type: "calendar", icon_color: "red", is_read: false, user_id: user.id }, { title: "Invoice Paid", description: "You've been added to Client Kickoff on June 8, 10:00 AM", activity_type: "invoice_paid", related_entity_type: "invoice", action_label: "View Invoice", icon_type: "invoice", icon_color: "green", is_read: false, user_id: user.id }, { title: "Staff Selected", description: "10 staff members selected to fill remaining 10 slots", activity_type: "staff_assigned", related_entity_type: "event", icon_type: "user", icon_color: "purple", is_read: true, user_id: user.id } ]); return await base44.entities.ActivityLog.filter({ userId: user.id }, '-created_date', 50); } return existing; }, enabled: !!user?.id, initialData: [], }); const markAsReadMutation = useMutation({ mutationFn: ({ id }) => base44.entities.ActivityLog.update(id, { is_read: true }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['activity-logs'] }); }, }); const deleteMutation = useMutation({ mutationFn: ({ id }) => base44.entities.ActivityLog.delete(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['activity-logs'] }); }, }); // Categorize by type const categorizeByType = (notif) => { const type = notif.activity_type || ''; const title = (notif.title || '').toLowerCase(); if (type.includes('message') || title.includes('message') || title.includes('comment') || title.includes('mentioned')) { return 'mentions'; } else if (type.includes('staff_assigned') || type.includes('user') || title.includes('invited') || title.includes('followed')) { return 'invites'; } else { return 'all'; } }; // Filter notifications based on active filter const filteredNotifications = notifications.filter(notif => { if (activeFilter === 'all') return true; return categorizeByType(notif) === activeFilter; }); // Group by day const groupByDay = (notifList) => { const groups = { today: [], yesterday: [], thisWeek: [], older: [] }; notifList.forEach(notif => { const date = new Date(notif.created_date); if (isToday(date)) { groups.today.push(notif); } else if (isYesterday(date)) { groups.yesterday.push(notif); } else if (isThisWeek(date)) { groups.thisWeek.push(notif); } else { groups.older.push(notif); } }); return groups; }; const groupedNotifications = groupByDay(filteredNotifications); // Count by type const allCount = notifications.length; const mentionsCount = notifications.filter(n => categorizeByType(n) === 'mentions').length; const invitesCount = notifications.filter(n => categorizeByType(n) === 'invites').length; const handleAction = (notification) => { // Mark as read when clicking if (!notification.is_read) { markAsReadMutation.mutate({ id: notification.id }); } const entityType = notification.related_entity_type; const entityId = notification.related_entity_id; const activityType = notification.activity_type || ''; // Route based on entity type if (entityType === 'event' || activityType.includes('event') || activityType.includes('order')) { if (entityId) { navigate(createPageUrl(`EventDetail?id=${entityId}`)); } else { navigate(createPageUrl('Events')); } } else if (entityType === 'task' || activityType.includes('task')) { navigate(createPageUrl('TaskBoard')); } else if (entityType === 'invoice' || activityType.includes('invoice')) { if (entityId) { navigate(createPageUrl(`Invoices?id=${entityId}`)); } else { navigate(createPageUrl('Invoices')); } } else if (entityType === 'staff' || activityType.includes('staff')) { if (entityId) { navigate(createPageUrl(`EditStaff?id=${entityId}`)); } else { navigate(createPageUrl('StaffDirectory')); } } else if (entityType === 'message' || activityType.includes('message')) { navigate(createPageUrl('Messages')); } else if (notification.action_link) { navigate(createPageUrl(notification.action_link)); } onClose(); }; return ( {isOpen && ( <> {/* Backdrop */} {/* Panel */} {/* Header */}

Notifications

{/* Filter Tabs */}
{/* Notifications List */}
{filteredNotifications.length === 0 ? (

No notifications

You're all caught up!

) : ( <> {/* TODAY */} {groupedNotifications.today.length > 0 && (

TODAY

{groupedNotifications.today.map((notification) => { const Icon = iconMap[notification.icon_type] || AlertCircle; const isUnread = !notification.is_read; const notifDate = new Date(notification.created_date); return (
{isUnread && (
)}
handleAction(notification)} className={`w-12 h-12 rounded-full flex items-center justify-center flex-shrink-0 cursor-pointer ${ notification.icon_color === 'blue' ? 'bg-blue-100' : notification.icon_color === 'green' ? 'bg-green-100' : notification.icon_color === 'red' ? 'bg-red-100' : notification.icon_color === 'purple' ? 'bg-purple-100' : 'bg-slate-100' }`}>
handleAction(notification)} >

{notification.title} {notification.description}

{formatDistanceToNow(notifDate, { addSuffix: true })}
{notification.action_link && ( {notification.action_label} )} {format(notifDate, 'h:mm a')} {isUnread && ( )}
); })}
)} {/* YESTERDAY */} {groupedNotifications.yesterday.length > 0 && (

YESTERDAY

{groupedNotifications.yesterday.map((notification) => { const Icon = iconMap[notification.icon_type] || AlertCircle; const isUnread = !notification.is_read; const notifDate = new Date(notification.created_date); return (
{isUnread && (
)}
handleAction(notification)} className={`w-12 h-12 rounded-full flex items-center justify-center flex-shrink-0 cursor-pointer ${ notification.icon_color === 'blue' ? 'bg-blue-100' : notification.icon_color === 'green' ? 'bg-green-100' : notification.icon_color === 'red' ? 'bg-red-100' : notification.icon_color === 'purple' ? 'bg-purple-100' : 'bg-slate-100' }`}>
notification.action_link && handleAction(notification)} >

{notification.title} {notification.description}

{formatDistanceToNow(notifDate, { addSuffix: true })}
{notification.action_link && ( {notification.action_label} )} {format(notifDate, 'MM/dd/yy • h:mm a')} {isUnread && ( )}
); })}
)} {/* THIS WEEK */} {groupedNotifications.thisWeek.length > 0 && (

THIS WEEK

{groupedNotifications.thisWeek.map((notification) => { const Icon = iconMap[notification.icon_type] || AlertCircle; const isUnread = !notification.is_read; const notifDate = new Date(notification.created_date); return (
{isUnread && (
)}
handleAction(notification)} className={`w-12 h-12 rounded-full flex items-center justify-center flex-shrink-0 cursor-pointer ${ notification.icon_color === 'blue' ? 'bg-blue-100' : notification.icon_color === 'green' ? 'bg-green-100' : notification.icon_color === 'red' ? 'bg-red-100' : notification.icon_color === 'purple' ? 'bg-purple-100' : 'bg-slate-100' }`}>
notification.action_link && handleAction(notification)} >

{notification.title} {notification.description}

{formatDistanceToNow(notifDate, { addSuffix: true })}
{notification.action_link && ( {notification.action_label} )} {format(notifDate, 'MM/dd/yy • h:mm a')} {isUnread && ( )}
); })}
)} {/* OLDER */} {groupedNotifications.older.length > 0 && (

OLDER

{groupedNotifications.older.map((notification) => { const Icon = iconMap[notification.icon_type] || AlertCircle; const isUnread = !notification.is_read; const notifDate = new Date(notification.created_date); return (
{isUnread && (
)}
handleAction(notification)} className={`w-12 h-12 rounded-full flex items-center justify-center flex-shrink-0 cursor-pointer ${ notification.icon_color === 'blue' ? 'bg-blue-100' : notification.icon_color === 'green' ? 'bg-green-100' : notification.icon_color === 'red' ? 'bg-red-100' : notification.icon_color === 'purple' ? 'bg-purple-100' : 'bg-slate-100' }`}>
notification.action_link && handleAction(notification)} >

{notification.title} {notification.description}

{formatDistanceToNow(notifDate, { addSuffix: true })}
{notification.action_link && ( {notification.action_label} )} {format(notifDate, 'MM/dd/yy • h:mm a')} {isUnread && ( )}
); })}
)} )}
)} ); }