Read/Unread State
Each notification has a read/unread state stored per-user in the WorkDesk database. Unread notifications increment the bell badge count. Read state is updated via explicit user actions — clicking a notification, opening the panel, or marking all read.
What Triggers a Read State Change
| Action | Effect |
|---|---|
| Click a notification in the dropdown panel | That notification marked read; badge count decrements by 1 |
| Open the full Notification Panel page | All visible notifications marked read automatically |
| Click "Mark all read" in the dropdown | All notifications (including unloaded ones) marked read; badge clears |
| Click "Mark all read" in the history page | All notifications in selected filter marked read |
Read State APIs
// Mark a single notification read
POST /api/workdesk/notifications/{notificationId}/read
Authorization: Bearer {token}
// Response: 200 OK
{ "notificationId": "...", "isRead": true, "readAt": "2026-05-25T14:00:00Z" }
// Mark all notifications read
POST /api/workdesk/notifications/read-all
Authorization: Bearer {token}
// Response: 200 OK
{ "markedReadCount": 7, "unreadCount": 0 }
// Fetch notifications filtered by read state
GET /api/workdesk/notifications?isRead=false // unread only
GET /api/workdesk/notifications?isRead=true // read only
GET /api/workdesk/notifications // all
Database Schema
// WorkDesk_Notifications table
CREATE TABLE WorkDesk_Notifications (
NotificationId UNIQUEIDENTIFIER PRIMARY KEY,
UserId UNIQUEIDENTIFIER NOT NULL,
TenantId UNIQUEIDENTIFIER NOT NULL,
Type VARCHAR(50) NOT NULL, -- hil_task_assigned | ...
Title NVARCHAR(200) NOT NULL,
Body NVARCHAR(1000) NOT NULL,
LinkedUrl NVARCHAR(500),
Priority VARCHAR(20) NOT NULL DEFAULT 'normal',
IsRead BIT NOT NULL DEFAULT 0,
ReadAt DATETIME2,
CreatedAt DATETIME2 NOT NULL DEFAULT GETUTCDATE(),
ExpiresAt DATETIME2, -- null = never expires
Metadata NVARCHAR(MAX), -- JSON blob for type-specific data
INDEX IX_Notifications_User (UserId, TenantId, IsRead, CreatedAt DESC)
);
Optimistic UI Updates
When an employee clicks a notification, WorkDesk immediately updates the Zustand store (optimistic update) — the bell badge decrements and the notification appears read — before waiting for the API response. If the API call fails, the store is rolled back and an error toast is shown.
async function markNotificationRead(notificationId: string) {
// Optimistic update
store.markRead(notificationId);
try {
await api.post(`/notifications/${notificationId}/read`);
} catch (error) {
// Rollback on failure
store.markUnread(notificationId);
showErrorToast('Failed to mark notification as read. Please try again.');
}
}
When the user opens a new browser session, WorkDesk loads the current unread count from GET /api/workdesk/notifications/count?isRead=false and initializes the bell badge. This ensures accurate counts after periods of inactivity.