Portal Community

What Triggers a Read State Change

ActionEffect
Click a notification in the dropdown panelThat notification marked read; badge count decrements by 1
Open the full Notification Panel pageAll visible notifications marked read automatically
Click "Mark all read" in the dropdownAll notifications (including unloaded ones) marked read; badge clears
Click "Mark all read" in the history pageAll 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.');
  }
}
Unread Count on New Session

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.