@@ -161,7 +173,7 @@ export function Header({ title, onRefresh }: HeaderProps) {
key={notification.id}
className={`p-3 cursor-pointer flex items-start gap-3 ${!notification.isRead ? 'bg-blue-50/50' : ''
}`}
- onClick={() => handleMarkAsRead(notification.id)}
+ onClick={() => handleNotificationClick(notification)}
>
{notification.title}
@@ -178,9 +190,12 @@ export function Header({ title, onRefresh }: HeaderProps) {
)}
-
- Stay updated with your mentions and tasks
-
+
diff --git a/src/features/fnf/pages/FnFDetails.tsx b/src/features/fnf/pages/FnFDetails.tsx
index 9291f8b..b33d5c7 100644
--- a/src/features/fnf/pages/FnFDetails.tsx
+++ b/src/features/fnf/pages/FnFDetails.tsx
@@ -301,6 +301,7 @@ export function FnFDetails({ fnfId, onBack, currentUser }: FnFDetailsProps) {
url: doc.filePath,
})),
],
+ participants: s.participants || []
};
setFnfCase(finalMapped);
diff --git a/src/features/master/pages/MasterPage.tsx b/src/features/master/pages/MasterPage.tsx
index 7fdab34..4b055fc 100644
--- a/src/features/master/pages/MasterPage.tsx
+++ b/src/features/master/pages/MasterPage.tsx
@@ -26,8 +26,6 @@ import { EmailTemplates } from '@/features/master/components/EmailTemplates';
import { LocationManagement } from '@/features/master/components/LocationManagement';
import { ASMDialog } from '@/features/master/components/ASMDialog';
import { ZMDialog } from '@/features/master/components/ZMDialog';
-import { DDLeadManagement } from '@/features/master/components/DDLeadManagement';
-import { DDLeadDialog } from '@/features/master/components/DDLeadDialog';
import { ZoneDialog } from '@/features/master/components/ZoneDialog';
import { RegionDialog } from '@/features/master/components/RegionDialog';
import { TemplateDialog } from '@/features/master/components/TemplateDialog';
@@ -40,7 +38,7 @@ import { RootState } from '@/store';
export const MasterPage: React.FC = () => {
const { fetchInitialData, fetchAreas } = useMasterData();
const {
- asms, zonalManagerMappings, ddLeads,
+ asms, zonalManagerMappings,
allStates,
allDistricts,
users,
@@ -77,12 +75,6 @@ export const MasterPage: React.FC = () => {
const [selectedZMZone, setSelectedZMZone] = useState('');
const [selectedZMRegions, setSelectedZMRegions] = useState
([]);
- // DD-Lead Management State
- const [showDDLeadDialog, setShowDDLeadDialog] = useState(false);
- const [editingDDLeadId, setEditingDDLeadId] = useState(null);
- const [ddLeadManagerId, setDdLeadManagerId] = useState('');
- const [ddLeadStatus, setDdLeadStatus] = useState<'active' | 'inactive'>('active');
- const [selectedDDLeadZones, setSelectedDDLeadZones] = useState([]);
// Role Management State
const [showRoleDialog, setShowRoleDialog] = useState(false);
@@ -207,13 +199,6 @@ export const MasterPage: React.FC = () => {
setShowZMDialog(true);
};
- const handleEditDDLead = (lead: any) => {
- setEditingDDLeadId(lead.id);
- setDdLeadManagerId(lead.id);
- setDdLeadStatus(lead.status?.toLowerCase() === 'active' ? 'active' : 'inactive');
- setSelectedDDLeadZones(lead.assignedZoneIds || []);
- setShowDDLeadDialog(true);
- };
const handleSaveZM = async () => {
if (!zmManagerId || !selectedZMZone) {
@@ -242,31 +227,6 @@ export const MasterPage: React.FC = () => {
}
};
- const handleSaveDDLead = async () => {
- if (!ddLeadManagerId) {
- toast.error('Manager user is required');
- return;
- }
- try {
- const payload = {
- userId: ddLeadManagerId,
- zoneIds: selectedDDLeadZones,
- status: ddLeadStatus
- };
-
- const res = await (masterService as any).saveDDLead(payload) as any;
- if (res.success) {
- toast.success(`DD Lead ${editingDDLeadId ? 'updated' : 'assigned'} successfully`);
- setShowDDLeadDialog(false);
- fetchInitialData();
- } else {
- toast.error(res.message || 'Failed to save DD Lead');
- }
- } catch (error: any) {
- const msg = error?.response?.data?.message || error?.message || 'Failed to save DD Lead';
- toast.error(msg);
- }
- };
const handleSaveZone = async () => {
@@ -536,14 +496,6 @@ export const MasterPage: React.FC = () => {
{ setEditingASMId(null); setAsmManagerId(''); setSelectedASMZone(selectedZone === 'all' ? '' : selectedZone); setSelectedASMRegion(''); setSelectedASMStates([]); setSelectedASMDistricts([]); setShowASMDialog(true); }}
onEditASM={handleEditASM} onDeleteASM={() => toast.error('ASM deletion restricted')} />
- {
- setEditingDDLeadId(null); setDdLeadManagerId('');
- setDdLeadStatus('active'); setSelectedDDLeadZones([]);
- setShowDDLeadDialog(true);
- }}
- onEditLead={handleEditDDLead}
- onDeleteLead={() => toast.error('DD-Lead deletion restricted')} />
0 ? users : asms} />
@@ -641,19 +593,6 @@ export const MasterPage: React.FC = () => {
onSave={handleSaveZM}
userAssignedData={users.length > 0 ? users.map(u => ({...u, name: u.fullName || u.name, role: u.role?.roleName, roleCode: u.role?.roleCode, allRoles: u.allRoles})) : asms}
/>
- 0 ? users.map(u => ({...u, name: u.fullName || u.name, role: u.role?.roleName, roleCode: u.role?.roleCode, allRoles: u.allRoles, assignedZoneIds: (ddLeads.find(l => l.id === u.id)?.assignedZoneIds || [])})) : asms}
- />
diff --git a/src/features/onboarding/pages/WorkNotesPage.tsx b/src/features/onboarding/pages/WorkNotesPage.tsx
index 962a017..306100c 100644
--- a/src/features/onboarding/pages/WorkNotesPage.tsx
+++ b/src/features/onboarding/pages/WorkNotesPage.tsx
@@ -348,6 +348,16 @@ export function WorkNotesPage(props: Partial) {
if (!appName || appName === 'Application' || appName === 'Resignation') setAppName(data.dealer?.businessName || 'Resignation');
if (!regNumber) setRegNumber(data.resignationId || '');
}
+ } else if (requestType === 'fnf') {
+ const { API } = await import('@/api/API');
+ const res: any = await API.getFnFSettlementById(requestId);
+ if (res.data?.success) {
+ data = res.data.fnf;
+ if (externalParticipants.length === 0 && data.participants) setExternalParticipants(data.participants || []);
+ const dealerName = data.outlet?.dealer?.fullName || data.dealer?.fullName || "F&F Settlement";
+ if (!appName || appName === 'Application' || appName === 'F&F Settlement') setAppName(dealerName);
+ if (!regNumber) setRegNumber(data.settlementId || '');
+ }
}
} catch (error) {
console.error(`Failed to fetch ${requestType} details:`, error);
diff --git a/src/pages/NotificationsPage.tsx b/src/pages/NotificationsPage.tsx
new file mode 100644
index 0000000..c32114a
--- /dev/null
+++ b/src/pages/NotificationsPage.tsx
@@ -0,0 +1,171 @@
+import React, { useState, useEffect } from 'react';
+import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { Badge } from '@/components/ui/badge';
+import { Bell, Check, Clock, ChevronLeft, ChevronRight } from 'lucide-react';
+import { notificationService, Notification } from '@/services/notification.service';
+import { useNavigate } from 'react-router-dom';
+import { formatDistanceToNow } from 'date-fns';
+
+export const NotificationsPage: React.FC = () => {
+ const [notifications, setNotifications] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [page, setPage] = useState(1);
+ const [totalPages, setTotalPages] = useState(1);
+ const limit = 15;
+ const navigate = useNavigate();
+
+ const fetchNotifications = async (currentPage: number) => {
+ try {
+ setLoading(true);
+ const res: any = await notificationService.getNotifications(currentPage, limit);
+ if (res.success) {
+ setNotifications(res.data);
+ if (res.pagination) {
+ setTotalPages(res.pagination.totalPages);
+ }
+ }
+ } catch (error) {
+ console.error('Failed to fetch notifications', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ fetchNotifications(page);
+ }, [page]);
+
+ const handleMarkAsRead = async (id: string, e: React.MouseEvent) => {
+ e.stopPropagation();
+ await notificationService.markAsRead(id);
+ fetchNotifications(page);
+ };
+
+ const handleMarkAllAsRead = async () => {
+ await notificationService.markAllAsRead();
+ fetchNotifications(page);
+ };
+
+ const handleNotificationClick = async (notif: Notification) => {
+ if (!notif.isRead) {
+ await notificationService.markAsRead(notif.id);
+ }
+ if (notif.link) {
+ // Support both relative or absolute links dynamically
+ const url = notif.link;
+ try {
+ const parsedUrl = new URL(url);
+ if (parsedUrl.origin === window.location.origin) {
+ navigate(parsedUrl.pathname + parsedUrl.search + parsedUrl.hash);
+ } else {
+ window.open(url, '_blank');
+ }
+ } catch (e) {
+ navigate(url);
+ }
+ } else {
+ fetchNotifications(page); // refresh list manually if no link
+ }
+ };
+
+ return (
+
+
+
+
+
+ Notifications Center
+
+
View and manage all your alerts
+
+
+
+
+
+
+ {loading ? (
+
+ ) : notifications.length === 0 ? (
+
+
+
No notifications yet
+
When you get updates, they'll show up here.
+
+ ) : (
+
+ {notifications.map(notif => (
+
handleNotificationClick(notif)}
+ >
+
handleMarkAsRead(notif.id, e)}>
+ {!notif.isRead ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ {notif.title}
+
+
+
+ {formatDistanceToNow(new Date(notif.createdAt), { addSuffix: true })}
+
+
+
{notif.message}
+
+
+ ))}
+
+ )}
+
+
+
+ {/* Pagination Controls */}
+ {!loading && totalPages > 1 && (
+
+
+ Showing page {page} of{' '}
+ {totalPages}
+
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default NotificationsPage;
diff --git a/src/services/notification.service.ts b/src/services/notification.service.ts
index f1ad5f2..a3805f8 100644
--- a/src/services/notification.service.ts
+++ b/src/services/notification.service.ts
@@ -14,8 +14,12 @@ export interface Notification {
}
export const notificationService = {
- getNotifications: async () => {
- const response = await client.get(`${API_BASE}/notifications`);
+ getNotifications: async (page?: number, limit?: number) => {
+ let url = `${API_BASE}/notifications`;
+ if (page && limit) {
+ url += `?page=${page}&limit=${limit}`;
+ }
+ const response = await client.get(url);
return response.data;
},