import type { NotificationAction, NotificationActionError, NotificationActionIntent, NotificationCategories, NotificationCounts, NotificationEntry, NotificationExtraAction, NotificationLabels, NotificationLoadStatus, NotificationMarkAllScope, NotificationOpenEvent, NotificationZoneKey } from "./NotificationSourceProps"; /** Where a decision is: undo is the 6 s window, sending is in flight, resolved has folded away. */ export type NotificationRowStatus = "open" | "undo" | "sending" | "resolved" | "failed" | "denied"; /** The decision the person made, kept while it is undoable, in flight, failed or denied. */ export interface NotificationDecision { /** The decided action's id. */ actionId: string; /** The action's button text, for "Couldn't approve." */ label: string; /** Past tense for the status line, e.g. "Approved". */ doneLabel: string; /** The action's intent; negative decisions read in a neutral tone. */ intent: NotificationActionIntent; /** The comment sent with it. */ reason?: string; } /** What the server asked for when it rejected a decision without a comment. */ export interface NotificationReasonRequest { /** The action to reopen the reason form for. */ actionId: string; /** The server's own words; without them the row shows `labels.reasonRequiredMessage`. */ message?: string; } /** What the overlay adds to one entry. */ export interface NotificationLiveState { /** The list the entry shows in now, after optimistic changes. */ zone: NotificationZoneKey; /** "open" unless a decision is underway or came back. */ status: NotificationRowStatus; /** Set for every status but "open". */ decision?: NotificationDecision; /** Why the decision failed or was denied. */ error?: NotificationActionError; /** The server wants a comment: the row reopens the reason form for this action. */ reasonRequest?: NotificationReasonRequest; /** Folding away: the row animates closed and leaves the accessibility tree. */ isHidden: boolean; /** Still waiting on the person: a Needs-you entry that is open or failed. */ isPending: boolean; } /** One entry with the overlay applied; what every list renders. */ export interface LiveNotificationEntry { /** The app's entry with the optimistic read and archive state applied. */ entry: NotificationEntry; /** Status, list and visibility from the overlay. */ state: NotificationLiveState; } /** Live entries per list, newest first. Hidden entries stay in place so they can animate back. */ export interface NotificationZoneLists { /** Decisions and unread `untilRead` entries. */ needsYou: LiveNotificationEntry[]; /** Everything else that is not archived. */ activity: LiveNotificationEntry[]; /** Archived entries; empty without `onArchive`. */ archived: LiveNotificationEntry[]; } /** Which optional features the source turned on, one per callback. */ export interface NotificationFeatures { /** `onAction` was passed. */ decide: boolean; /** `onMarkRead` was passed. */ markRead: boolean; /** `onMarkAllRead` was passed. */ markAllRead: boolean; /** `onArchive` was passed. */ archive: boolean; /** `onRestore` was passed. */ restore: boolean; /** `onLoadMore` was passed. */ loadMore: boolean; } /** A thread: the newest entry of a `groupKey`, and the older ones behind it. */ export interface NotificationThread { /** The lead entry's id. */ key: string; /** The newest entry. */ lead: LiveNotificationEntry; /** Older entries with the same `groupKey`, newest first; empty for a single entry. */ rest: LiveNotificationEntry[]; } /** One heading and its threads: a category in Needs you, a day in Activity. */ export interface NotificationGroup { /** A category key or a day bucket. */ key: string; /** Null when Needs you holds a single category, so no heading repeats the tab. */ label: string | null; /** Pending entries for a category group; 0 for a day group. */ count: number; /** In list order. */ threads: NotificationThread[]; } /** Activity's day headings. */ export type NotificationDayBucket = "today" | "yesterday" | "thisWeek" | "older"; /** Locale-aware date, time and number formatting; every function takes the current time explicitly. */ export interface NotificationFormatter { /** "now", "5m", "3h", "2d", then a date such as "12 Oct". */ shortAge: (at: number, now: number) => string; /** The spoken form: "just now", "5 minutes ago", then a full date and time. */ longAge: (at: number, now: number) => string; /** "Today, 2:05 pm", "Yesterday, 2:05 pm", or a full date and time. */ absolute: (at: number, now: number) => string; /** The Activity heading an entry falls under. */ dayBucket: (at: number, now: number) => NotificationDayBucket; /** The heading text for a bucket. */ dayLabel: (bucket: NotificationDayBucket) => string; /** Whole days between `at` and `now`, never negative. */ daysSince: (at: number, now: number) => number; /** A count in the locale's digits and grouping. */ number: (value: number) => string; /** The `dateTime` attribute value. */ iso: (at: number) => string; } /** Everything a notification layout can do; each call works whether or not its feature is on, doing nothing when it is off. */ export interface NotificationCenterActions { /** Marks an unread entry read, then calls `onOpen`. */ open: (entry: NotificationEntry, event?: NotificationOpenEvent, action?: NotificationAction) => void; /** Starts one undo window for the ids; several ids make one bulk decision with one toast. */ decide: (ids: string[], action: NotificationAction, reason?: string) => void; /** Undoes the open window this entry belongs to. */ undo: (id: string) => void; /** Undoes the most recent open window; false when there was none. */ undoLast: () => boolean; /** Sends a failed decision again, at once. */ retry: (id: string) => void; /** Clears a denied decision; the entry stays hidden until fresh data drops it. */ dismiss: (id: string) => void; /** Closes a server request for a comment without sending. */ clearReasonRequest: (id: string) => void; /** Read or unread, sent at once; a failure rolls back with a toast. */ setRead: (ids: string[], read: boolean) => void; /** Marks the loaded matches read and sends the scope after the undo window. */ markAllRead: (scope: NotificationMarkAllScope) => void; /** Activity entries only; sent after the undo window. */ archive: (ids: string[]) => void; /** Archived entries back to Activity, sent at once. */ restore: (ids: string[]) => void; /** The source's `extraActions` for an entry, or none. */ extraActionsFor: (entry: NotificationEntry) => NotificationExtraAction[]; /** Runs an extra action and toasts its outcome. */ runExtraAction: (entry: NotificationEntry, action: NotificationExtraAction) => void; /** Calls `onLoadMore`. */ loadMore: () => void; /** Calls `onRetry`. */ retryLoad: () => void; /** Speaks a message through the shared polite live region. */ announce: (message: string) => void; /** Sends every open undo window now, as on page hide. */ flush: () => void; } /** What `useNotificationCenter()` returns: the live lists, counts and every action. */ export interface NotificationCenterValue { /** Live entries per list, newest first. */ lists: NotificationZoneLists; /** Server counts moved by the overlay, never below 0. */ counts: NotificationCounts; /** The source's categories. */ categories: NotificationCategories; /** Which callbacks the source passed. */ features: NotificationFeatures; /** The shared strings, defaults merged with the source's overrides. */ labels: NotificationLabels; /** Formatting in the source's locale. */ format: NotificationFormatter; /** The current time, refreshed every minute so relative times move. */ now: number; /** The source's load state. */ status: NotificationLoadStatus; /** True while offline; every change is disabled. */ isOffline: boolean; /** When the loaded entries were fetched. */ loadedAt?: number; /** As in "Open in hrms". */ appName?: string; /** A next page exists. */ hasMore: boolean; /** The next page is loading. */ isLoadingMore: boolean; /** Every action, stable across renders. */ actions: NotificationCenterActions; /** The live entry for an id, if loaded. */ find: (id: string) => LiveNotificationEntry | undefined; }