refactor Notifications rc
This commit is contained in:
parent
ab1ba65a9f
commit
f865f2b95c
@ -176,6 +176,7 @@ export function Email() {
|
|||||||
|
|
||||||
// Trigger notification if count changed
|
// Trigger notification if count changed
|
||||||
if (currentUnreadCount !== lastUnreadCountRef.current) {
|
if (currentUnreadCount !== lastUnreadCountRef.current) {
|
||||||
|
const previousCount = lastUnreadCountRef.current;
|
||||||
lastUnreadCountRef.current = currentUnreadCount;
|
lastUnreadCountRef.current = currentUnreadCount;
|
||||||
|
|
||||||
// Prepare notification items (unread emails only, max 10)
|
// Prepare notification items (unread emails only, max 10)
|
||||||
@ -197,12 +198,28 @@ export function Email() {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Trigger notification update
|
// Trigger notification update (for badge)
|
||||||
await triggerNotification({
|
await triggerNotification({
|
||||||
source: 'email',
|
source: 'email',
|
||||||
count: currentUnreadCount,
|
count: currentUnreadCount,
|
||||||
items: notificationItems,
|
items: notificationItems,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Dispatch event for Outlook-style notifications (only for new emails)
|
||||||
|
if (previousCount >= 0 && currentUnreadCount > previousCount) {
|
||||||
|
const newEmails = transformedEmails
|
||||||
|
.filter(e => !e.read)
|
||||||
|
.slice(0, currentUnreadCount - previousCount); // Only the new ones
|
||||||
|
|
||||||
|
if (newEmails.length > 0) {
|
||||||
|
window.dispatchEvent(new CustomEvent('new-emails-detected', {
|
||||||
|
detail: {
|
||||||
|
emails: transformedEmails,
|
||||||
|
accountMap: accountMap,
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show error only if all accounts failed
|
// Show error only if all accounts failed
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Phone, PhoneOff, X } from 'lucide-react';
|
import { Phone, PhoneOff } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { OutlookNotification, OutlookNotificationData } from '@/components/outlook-notification';
|
||||||
|
|
||||||
export interface IncomingCall {
|
export interface IncomingCall {
|
||||||
from: {
|
from: {
|
||||||
@ -30,7 +30,7 @@ export function IncomingCallNotification({
|
|||||||
onReject,
|
onReject,
|
||||||
}: IncomingCallNotificationProps) {
|
}: IncomingCallNotificationProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const [notificationData, setNotificationData] = useState<OutlookNotificationData | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (call) {
|
if (call) {
|
||||||
@ -38,98 +38,62 @@ export function IncomingCallNotification({
|
|||||||
from: call.from.name || call.from.username,
|
from: call.from.name || call.from.username,
|
||||||
roomId: call.roomId,
|
roomId: call.roomId,
|
||||||
});
|
});
|
||||||
setIsVisible(true);
|
|
||||||
|
const callerName = call.from.name || call.from.username || 'Inconnu';
|
||||||
|
|
||||||
// Auto-dismiss after 30 seconds if user doesn't interact
|
const notification: OutlookNotificationData = {
|
||||||
const autoDismissTimer = setTimeout(() => {
|
id: `call-${call.roomId}-${Date.now()}`,
|
||||||
console.log('[IncomingCallNotification] ⏰ Auto-dismissing after 30 seconds');
|
source: 'call',
|
||||||
setIsVisible(false);
|
title: 'Parole',
|
||||||
onDismiss();
|
subtitle: 'Appel entrant',
|
||||||
}, 30000); // 30 seconds
|
message: `Vous avez un appel de ${callerName}${call.roomName && call.roomName !== callerName ? ` dans ${call.roomName}` : ''}`,
|
||||||
|
icon: Phone,
|
||||||
return () => {
|
iconColor: 'text-blue-600',
|
||||||
clearTimeout(autoDismissTimer);
|
iconBgColor: 'bg-blue-100',
|
||||||
|
borderColor: 'border-blue-500',
|
||||||
|
timestamp: call.timestamp,
|
||||||
|
autoDismiss: 30000, // 30 seconds
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
label: 'Accepter',
|
||||||
|
onClick: () => {
|
||||||
|
onAccept(call.roomId);
|
||||||
|
router.push(`/parole?room=${call.roomId}`);
|
||||||
|
setNotificationData(null);
|
||||||
|
},
|
||||||
|
variant: 'default',
|
||||||
|
className: 'bg-green-600 hover:bg-green-700 text-white',
|
||||||
|
icon: Phone,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Raccrocher',
|
||||||
|
onClick: () => {
|
||||||
|
onReject();
|
||||||
|
setNotificationData(null);
|
||||||
|
},
|
||||||
|
variant: 'destructive',
|
||||||
|
icon: PhoneOff,
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
setIsVisible(false);
|
|
||||||
}
|
|
||||||
}, [call, onDismiss]);
|
|
||||||
|
|
||||||
if (!call || !isVisible) {
|
setNotificationData(notification);
|
||||||
|
} else {
|
||||||
|
setNotificationData(null);
|
||||||
|
}
|
||||||
|
}, [call, router, onAccept, onReject]);
|
||||||
|
|
||||||
|
if (!notificationData) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAccept = () => {
|
|
||||||
onAccept(call.roomId);
|
|
||||||
// Navigate to parole page with room ID
|
|
||||||
router.push(`/parole?room=${call.roomId}`);
|
|
||||||
setIsVisible(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleReject = () => {
|
|
||||||
onReject();
|
|
||||||
setIsVisible(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDismiss = () => {
|
|
||||||
onDismiss();
|
|
||||||
setIsVisible(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const callerName = call.from.name || call.from.username || 'Inconnu';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed top-4 right-4 z-[9999] animate-in slide-in-from-top-5 duration-300">
|
<OutlookNotification
|
||||||
<div className="bg-white rounded-lg shadow-2xl border-2 border-blue-500 p-5 min-w-[340px] max-w-[420px]">
|
notification={notificationData}
|
||||||
{/* Header with Outlook-like style */}
|
onDismiss={() => {
|
||||||
<div className="flex items-start justify-between mb-4 pb-3 border-b border-gray-200">
|
onDismiss();
|
||||||
<div className="flex items-center gap-3">
|
setNotificationData(null);
|
||||||
<div className="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center flex-shrink-0">
|
}}
|
||||||
<Phone className="w-5 h-5 text-blue-600" />
|
/>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="font-semibold text-base text-gray-900 leading-tight">Parole</h3>
|
|
||||||
<p className="text-xs text-gray-500 mt-0.5">Appel entrant</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={handleDismiss}
|
|
||||||
className="text-gray-400 hover:text-gray-600 transition-colors p-1 -mt-1 -mr-1"
|
|
||||||
aria-label="Fermer"
|
|
||||||
>
|
|
||||||
<X className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Call Info - Outlook style message */}
|
|
||||||
<div className="mb-5">
|
|
||||||
<p className="text-gray-800 text-sm leading-relaxed">
|
|
||||||
Vous avez un appel de <span className="font-semibold text-gray-900">{callerName}</span>
|
|
||||||
</p>
|
|
||||||
{call.roomName && call.roomName !== callerName && (
|
|
||||||
<p className="text-xs text-gray-500 mt-1.5">Dans {call.roomName}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Actions - Outlook style buttons */}
|
|
||||||
<div className="flex gap-2.5">
|
|
||||||
<Button
|
|
||||||
onClick={handleAccept}
|
|
||||||
className="flex-1 bg-green-600 hover:bg-green-700 text-white font-medium py-2.5 text-sm shadow-sm"
|
|
||||||
>
|
|
||||||
<Phone className="w-4 h-4 mr-2" />
|
|
||||||
Accepter
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={handleReject}
|
|
||||||
variant="destructive"
|
|
||||||
className="flex-1 font-medium py-2.5 text-sm shadow-sm"
|
|
||||||
>
|
|
||||||
<PhoneOff className="w-4 h-4 mr-2" />
|
|
||||||
Raccrocher
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,9 @@ import { useBackgroundImage } from "@/components/background-switcher";
|
|||||||
import { clearAuthCookies, clearKeycloakCookies } from "@/lib/session";
|
import { clearAuthCookies, clearKeycloakCookies } from "@/lib/session";
|
||||||
import { useRocketChatCalls } from "@/hooks/use-rocketchat-calls";
|
import { useRocketChatCalls } from "@/hooks/use-rocketchat-calls";
|
||||||
import { IncomingCallNotification } from "@/components/incoming-call-notification";
|
import { IncomingCallNotification } from "@/components/incoming-call-notification";
|
||||||
|
import { useEmailNotifications } from "@/hooks/use-email-notifications";
|
||||||
|
import { OutlookNotification } from "@/components/outlook-notification";
|
||||||
|
import { NotificationStack } from "@/components/notification-stack";
|
||||||
|
|
||||||
interface LayoutWrapperProps {
|
interface LayoutWrapperProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@ -208,32 +211,37 @@ export function LayoutWrapper({ children, isSignInPage, isAuthenticated }: Layou
|
|||||||
{!isSignInPage && isAuthenticated && <Footer />}
|
{!isSignInPage && isAuthenticated && <Footer />}
|
||||||
<Toaster />
|
<Toaster />
|
||||||
|
|
||||||
{/* Incoming call notification */}
|
{/* Notifications stack (calls and emails) */}
|
||||||
{!isSignInPage && isAuthenticated && (
|
{!isSignInPage && isAuthenticated && (
|
||||||
<>
|
<NotificationStack>
|
||||||
{incomingCall && (
|
{incomingCall && (
|
||||||
<div style={{ position: 'fixed', top: 0, right: 0, zIndex: 9999 }}>
|
<IncomingCallNotification
|
||||||
{/* Debug: Show if incomingCall exists */}
|
call={incomingCall}
|
||||||
{console.log('[LayoutWrapper] Rendering IncomingCallNotification', { incomingCall })}
|
onDismiss={() => {
|
||||||
</div>
|
console.log('[LayoutWrapper] Call dismissed');
|
||||||
|
setIncomingCall(null);
|
||||||
|
}}
|
||||||
|
onAccept={(roomId) => {
|
||||||
|
console.log('[LayoutWrapper] Call accepted, navigating to room:', roomId);
|
||||||
|
setIncomingCall(null);
|
||||||
|
}}
|
||||||
|
onReject={() => {
|
||||||
|
console.log('[LayoutWrapper] Call rejected');
|
||||||
|
setIncomingCall(null);
|
||||||
|
// TODO: Send reject signal to RocketChat if needed
|
||||||
|
}}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<IncomingCallNotification
|
{emailNotification && (
|
||||||
call={incomingCall}
|
<OutlookNotification
|
||||||
onDismiss={() => {
|
notification={emailNotification}
|
||||||
console.log('[LayoutWrapper] Call dismissed');
|
onDismiss={() => {
|
||||||
setIncomingCall(null);
|
console.log('[LayoutWrapper] Email notification dismissed');
|
||||||
}}
|
setEmailNotification(null);
|
||||||
onAccept={(roomId) => {
|
}}
|
||||||
console.log('[LayoutWrapper] Call accepted, navigating to room:', roomId);
|
/>
|
||||||
setIncomingCall(null);
|
)}
|
||||||
}}
|
</NotificationStack>
|
||||||
onReject={() => {
|
|
||||||
console.log('[LayoutWrapper] Call rejected');
|
|
||||||
setIncomingCall(null);
|
|
||||||
// TODO: Send reject signal to RocketChat if needed
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</AuthCheck>
|
</AuthCheck>
|
||||||
);
|
);
|
||||||
|
|||||||
34
components/notification-stack.tsx
Normal file
34
components/notification-stack.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
interface NotificationStackProps {
|
||||||
|
children: ReactNode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container component to stack multiple notifications vertically
|
||||||
|
*/
|
||||||
|
export function NotificationStack({ children }: NotificationStackProps) {
|
||||||
|
const notifications = Array.isArray(children) ? children.filter(Boolean) : (children ? [children] : []);
|
||||||
|
|
||||||
|
if (notifications.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed top-4 right-4 z-[9999] flex flex-col gap-3">
|
||||||
|
{notifications.map((notification, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="animate-in slide-in-from-top-5 duration-300"
|
||||||
|
style={{
|
||||||
|
animationDelay: `${index * 100}ms`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{notification}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
161
components/outlook-notification.tsx
Normal file
161
components/outlook-notification.tsx
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useEffect, ReactNode } from 'react';
|
||||||
|
import { X, LucideIcon } from 'lucide-react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
|
export interface OutlookNotificationAction {
|
||||||
|
label: string;
|
||||||
|
onClick: () => void;
|
||||||
|
variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
|
||||||
|
className?: string;
|
||||||
|
icon?: LucideIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OutlookNotificationData {
|
||||||
|
id: string;
|
||||||
|
source: 'email' | 'rocketchat' | 'call' | 'leantime' | 'calendar';
|
||||||
|
title: string;
|
||||||
|
subtitle?: string;
|
||||||
|
message: string;
|
||||||
|
icon: LucideIcon;
|
||||||
|
iconColor?: string;
|
||||||
|
iconBgColor?: string;
|
||||||
|
borderColor?: string;
|
||||||
|
link?: string;
|
||||||
|
timestamp?: Date;
|
||||||
|
actions?: OutlookNotificationAction[];
|
||||||
|
autoDismiss?: number; // milliseconds, default 30s
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OutlookNotificationProps {
|
||||||
|
notification: OutlookNotificationData | null;
|
||||||
|
onDismiss: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OutlookNotification({
|
||||||
|
notification,
|
||||||
|
onDismiss,
|
||||||
|
}: OutlookNotificationProps) {
|
||||||
|
const router = useRouter();
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (notification) {
|
||||||
|
console.log('[OutlookNotification] 📬 Showing notification', {
|
||||||
|
source: notification.source,
|
||||||
|
title: notification.title,
|
||||||
|
});
|
||||||
|
setIsVisible(true);
|
||||||
|
|
||||||
|
// Auto-dismiss after specified time (default 30 seconds)
|
||||||
|
const autoDismissTime = notification.autoDismiss || 30000;
|
||||||
|
const autoDismissTimer = setTimeout(() => {
|
||||||
|
console.log('[OutlookNotification] ⏰ Auto-dismissing after', autoDismissTime, 'ms');
|
||||||
|
setIsVisible(false);
|
||||||
|
onDismiss();
|
||||||
|
}, autoDismissTime);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(autoDismissTimer);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
setIsVisible(false);
|
||||||
|
}
|
||||||
|
}, [notification, onDismiss]);
|
||||||
|
|
||||||
|
if (!notification || !isVisible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDismiss = () => {
|
||||||
|
onDismiss();
|
||||||
|
setIsVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLinkClick = () => {
|
||||||
|
if (notification.link) {
|
||||||
|
router.push(notification.link);
|
||||||
|
setIsVisible(false);
|
||||||
|
onDismiss();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Icon = notification.icon;
|
||||||
|
const borderColor = notification.borderColor || 'border-blue-500';
|
||||||
|
const iconBgColor = notification.iconBgColor || 'bg-blue-100';
|
||||||
|
const iconColor = notification.iconColor || 'text-blue-600';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`bg-white rounded-lg shadow-2xl border-2 ${borderColor} p-5 min-w-[340px] max-w-[420px]`}>
|
||||||
|
{/* Header with Outlook-like style */}
|
||||||
|
<div className="flex items-start justify-between mb-4 pb-3 border-b border-gray-200">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className={`w-10 h-10 rounded-full ${iconBgColor} flex items-center justify-center flex-shrink-0`}>
|
||||||
|
<Icon className={`w-5 h-5 ${iconColor}`} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-base text-gray-900 leading-tight">{notification.title}</h3>
|
||||||
|
{notification.subtitle && (
|
||||||
|
<p className="text-xs text-gray-500 mt-0.5">{notification.subtitle}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleDismiss}
|
||||||
|
className="text-gray-400 hover:text-gray-600 transition-colors p-1 -mt-1 -mr-1"
|
||||||
|
aria-label="Fermer"
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Message - Outlook style */}
|
||||||
|
<div className="mb-5">
|
||||||
|
<p className="text-gray-800 text-sm leading-relaxed">
|
||||||
|
{notification.message}
|
||||||
|
</p>
|
||||||
|
{notification.timestamp && (
|
||||||
|
<p className="text-xs text-gray-500 mt-1.5">
|
||||||
|
{new Date(notification.timestamp).toLocaleTimeString('fr-FR', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Actions - Outlook style buttons */}
|
||||||
|
{notification.actions && notification.actions.length > 0 && (
|
||||||
|
<div className="flex gap-2.5">
|
||||||
|
{notification.actions.map((action, index) => {
|
||||||
|
const ActionIcon = action.icon;
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={index}
|
||||||
|
onClick={action.onClick}
|
||||||
|
variant={action.variant || 'default'}
|
||||||
|
className={`flex-1 font-medium py-2.5 text-sm shadow-sm ${action.className || ''}`}
|
||||||
|
>
|
||||||
|
{ActionIcon && <ActionIcon className="w-4 h-4 mr-2" />}
|
||||||
|
{action.label}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* If no actions but has link, make the whole card clickable */}
|
||||||
|
{!notification.actions && notification.link && (
|
||||||
|
<Button
|
||||||
|
onClick={handleLinkClick}
|
||||||
|
className="w-full font-medium py-2.5 text-sm shadow-sm"
|
||||||
|
>
|
||||||
|
Ouvrir
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
108
hooks/use-email-notifications.ts
Normal file
108
hooks/use-email-notifications.ts
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import { useState, useEffect, useRef } from 'react';
|
||||||
|
import { Mail } from 'lucide-react';
|
||||||
|
import { OutlookNotificationData } from '@/components/outlook-notification';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to manage email notifications and show Outlook-style notifications
|
||||||
|
*/
|
||||||
|
export function useEmailNotifications() {
|
||||||
|
const [emailNotification, setEmailNotification] = useState<OutlookNotificationData | null>(null);
|
||||||
|
const lastEmailIdsRef = useRef<Set<string>>(new Set());
|
||||||
|
const notificationQueueRef = useRef<OutlookNotificationData[]>([]);
|
||||||
|
const isShowingRef = useRef(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Listen for new emails via custom event
|
||||||
|
const handleNewEmails = (event: CustomEvent) => {
|
||||||
|
const emails = event.detail?.emails || [];
|
||||||
|
const accountMap = event.detail?.accountMap || new Map();
|
||||||
|
|
||||||
|
if (!emails || emails.length === 0) return;
|
||||||
|
|
||||||
|
// Find new emails (not in lastEmailIdsRef)
|
||||||
|
const newEmails = emails.filter((email: any) => {
|
||||||
|
const emailId = email.id;
|
||||||
|
if (!lastEmailIdsRef.current.has(emailId) && !email.read) {
|
||||||
|
lastEmailIdsRef.current.add(emailId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update lastEmailIdsRef with all current emails
|
||||||
|
lastEmailIdsRef.current = new Set(emails.map((e: any) => e.id));
|
||||||
|
|
||||||
|
// If there are new unread emails, queue them for notification
|
||||||
|
if (newEmails.length > 0) {
|
||||||
|
newEmails.forEach((email: any) => {
|
||||||
|
const account = accountMap.get(email.accountId);
|
||||||
|
const notification: OutlookNotificationData = {
|
||||||
|
id: `email-${email.id}-${Date.now()}`,
|
||||||
|
source: 'email',
|
||||||
|
title: 'Courrier',
|
||||||
|
subtitle: 'Nouvel email',
|
||||||
|
message: `${email.subject || 'Sans objet'} - De ${email.fromName || email.from.split('@')[0]}`,
|
||||||
|
icon: Mail,
|
||||||
|
iconColor: 'text-green-600',
|
||||||
|
iconBgColor: 'bg-green-100',
|
||||||
|
borderColor: 'border-green-500',
|
||||||
|
link: '/courrier',
|
||||||
|
timestamp: new Date(email.date),
|
||||||
|
autoDismiss: 10000, // 10 seconds for emails
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
label: 'Ouvrir',
|
||||||
|
onClick: () => {
|
||||||
|
window.location.href = '/courrier';
|
||||||
|
},
|
||||||
|
variant: 'default',
|
||||||
|
className: 'bg-green-600 hover:bg-green-700 text-white',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
notificationQueueRef.current.push(notification);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show the first notification if none is currently showing
|
||||||
|
if (!isShowingRef.current && notificationQueueRef.current.length > 0) {
|
||||||
|
showNextNotification();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('new-emails-detected', handleNewEmails as EventListener);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('new-emails-detected', handleNewEmails as EventListener);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const showNextNotification = () => {
|
||||||
|
if (notificationQueueRef.current.length === 0) {
|
||||||
|
isShowingRef.current = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextNotification = notificationQueueRef.current.shift();
|
||||||
|
if (nextNotification) {
|
||||||
|
isShowingRef.current = true;
|
||||||
|
setEmailNotification(nextNotification);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDismiss = () => {
|
||||||
|
setEmailNotification(null);
|
||||||
|
isShowingRef.current = false;
|
||||||
|
|
||||||
|
// Show next notification after a short delay
|
||||||
|
setTimeout(() => {
|
||||||
|
showNextNotification();
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
emailNotification,
|
||||||
|
setEmailNotification: handleDismiss,
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user