@natoe/colab 0.1.28 → 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +84 -34
- package/dist/index.d.ts +84 -34
- package/dist/index.js +63 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +63 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -119,25 +119,40 @@ interface PatientData {
|
|
|
119
119
|
bodyParts?: string[];
|
|
120
120
|
}
|
|
121
121
|
/**
|
|
122
|
-
*
|
|
122
|
+
* What a thread is about — and, crucially, what it is SCOPED to.
|
|
123
123
|
*
|
|
124
|
-
* - `case`
|
|
125
|
-
*
|
|
126
|
-
* - `
|
|
124
|
+
* - `case` — the discussion about one clinical order. Scoped to an order,
|
|
125
|
+
* so `orderId` is always set. Lab, radiologist, physician, admin.
|
|
126
|
+
* - `help_lab` — an imaging centre's help channel with Natoe support. Scoped to
|
|
127
|
+
* the LAB, not to any case: it has NO `orderId`, it outlives
|
|
128
|
+
* every order, and there is exactly one per imaging centre.
|
|
127
129
|
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
130
|
+
* `help_lab` was briefly modelled as per-case (one help thread per order). It is
|
|
131
|
+
* now per-lab, so support gets one continuous thread with each imaging centre
|
|
132
|
+
* rather than N short-lived ones scattered across cases. A case can still be
|
|
133
|
+
* referenced from inside the channel; it just no longer scopes it.
|
|
134
|
+
*
|
|
135
|
+
* Isolation — who can see a help channel — is enforced by the backend's
|
|
136
|
+
* participant seeding, never by anything in this package. Treat this as a label
|
|
137
|
+
* for rendering and for addressing the right thread, not as a security boundary.
|
|
132
138
|
*
|
|
133
139
|
* Optional everywhere, defaulting to `case`, so callers written before help
|
|
134
|
-
*
|
|
140
|
+
* channels existed keep their exact previous behaviour.
|
|
135
141
|
*/
|
|
136
|
-
type ConversationKind = 'case' | 'help_lab'
|
|
142
|
+
type ConversationKind = 'case' | 'help_lab';
|
|
137
143
|
declare function isHelpKind(kind?: ConversationKind | string): boolean;
|
|
144
|
+
/**
|
|
145
|
+
* True when a thread is scoped to an order and therefore has patient context to
|
|
146
|
+
* render. Prefer this over testing `orderId` directly: a help channel has no
|
|
147
|
+
* order, so every patient-shaped affordance must be skipped for it.
|
|
148
|
+
*/
|
|
149
|
+
declare function isCaseKind(kind?: ConversationKind | string): boolean;
|
|
138
150
|
interface Conversation {
|
|
139
151
|
id: string;
|
|
140
|
-
|
|
152
|
+
/** Absent on a help channel — that thread belongs to a lab, not an order. */
|
|
153
|
+
orderId?: string;
|
|
154
|
+
/** Set only on a help channel: the imaging centre that owns it. */
|
|
155
|
+
labUserId?: string;
|
|
141
156
|
name: string;
|
|
142
157
|
kind?: ConversationKind;
|
|
143
158
|
picture?: string;
|
|
@@ -146,7 +161,8 @@ interface Conversation {
|
|
|
146
161
|
unreadCount: number;
|
|
147
162
|
createdAt: string;
|
|
148
163
|
updatedAt: string;
|
|
149
|
-
/** Denormalized patient data snapshot — lets UI render without extra fetch
|
|
164
|
+
/** Denormalized patient data snapshot — lets UI render without extra fetch.
|
|
165
|
+
* Always absent on a help channel: there is no patient. */
|
|
150
166
|
patientSnapshot?: PatientData;
|
|
151
167
|
/** Currently pinned messages (max 3) */
|
|
152
168
|
pinnedMessages?: Message[];
|
|
@@ -157,10 +173,12 @@ interface Conversation {
|
|
|
157
173
|
*/
|
|
158
174
|
interface ConversationListItem$1 {
|
|
159
175
|
id: string;
|
|
160
|
-
orderId
|
|
176
|
+
/** Absent on a help channel — see `Conversation.orderId`. */
|
|
177
|
+
orderId?: string;
|
|
178
|
+
labUserId?: string;
|
|
161
179
|
name: string;
|
|
162
|
-
/** Lets the inbox
|
|
163
|
-
*
|
|
180
|
+
/** Lets the inbox render the lab's help channel distinctly from its case
|
|
181
|
+
* threads — they appear as separate rows. */
|
|
164
182
|
kind?: ConversationKind;
|
|
165
183
|
picture?: string;
|
|
166
184
|
unreadCount: number;
|
|
@@ -285,10 +303,15 @@ interface InviteUserPayload {
|
|
|
285
303
|
}
|
|
286
304
|
|
|
287
305
|
interface CollabPanelProps {
|
|
288
|
-
/** Order
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
306
|
+
/** Order this thread belongs to. Required for a `case` thread; omitted for a
|
|
307
|
+
* help channel, which belongs to a lab rather than an order. */
|
|
308
|
+
orderId?: string;
|
|
309
|
+
/** A thread the caller already resolved — how a help channel is opened.
|
|
310
|
+
* Skips the order-keyed preview lookup entirely. */
|
|
311
|
+
conversation?: Conversation | null;
|
|
312
|
+
/** Patient/study data displayed in the header. Absent on a help channel:
|
|
313
|
+
* there is no patient, and the header renders its support variant instead. */
|
|
314
|
+
patientData?: PatientData;
|
|
292
315
|
/** Participant user IDs — only used when creating a brand-new conversation */
|
|
293
316
|
participantIds?: string[];
|
|
294
317
|
/** Which thread on the case. Defaults to the shared `case` thread. */
|
|
@@ -343,13 +366,16 @@ interface CollabPanelProps {
|
|
|
343
366
|
* Full collaboration panel: patient header + pinned bar + message thread + input.
|
|
344
367
|
* Supports reply, pin/unpin, and (optionally) seen-by indicators.
|
|
345
368
|
*/
|
|
346
|
-
declare function CollabPanel({ orderId, patientData, participantIds, kind, showSeenBy, onBack, hidePatientName, hideOpenCase, onConversationChange, themeMode, showSettings: controlledShowSettings, onSettingsChange, className, style, }: CollabPanelProps): react_jsx_runtime.JSX.Element;
|
|
369
|
+
declare function CollabPanel({ orderId, conversation: providedConversation, patientData, participantIds, kind, showSeenBy, onBack, hidePatientName, hideOpenCase, onConversationChange, themeMode, showSettings: controlledShowSettings, onSettingsChange, className, style, }: CollabPanelProps): react_jsx_runtime.JSX.Element;
|
|
347
370
|
|
|
348
371
|
interface CollabPopupProps {
|
|
349
|
-
/** Order
|
|
350
|
-
|
|
372
|
+
/** Order this thread belongs to. Required for a `case` thread; omitted for a
|
|
373
|
+
* help channel, which belongs to a lab rather than an order. */
|
|
374
|
+
orderId?: string;
|
|
375
|
+
/** A thread the caller already resolved — how a help channel is opened. */
|
|
376
|
+
conversation?: Conversation | null;
|
|
351
377
|
/** Patient/study data */
|
|
352
|
-
patientData
|
|
378
|
+
patientData?: PatientData;
|
|
353
379
|
/** Participant user IDs — only used when creating a brand-new conversation */
|
|
354
380
|
participantIds?: string[];
|
|
355
381
|
/** Which thread on the case. Defaults to the shared `case` thread. */
|
|
@@ -383,7 +409,7 @@ interface CollabPopupProps {
|
|
|
383
409
|
* Draggable floating chat popup — drop-in replacement for DraggableChatPopup.
|
|
384
410
|
* Renders a CollabPanel inside a movable, resizable container.
|
|
385
411
|
*/
|
|
386
|
-
declare function CollabPopup({ orderId, patientData, participantIds, kind, isOpen, onClose, onBack, onMinimize, initialPosition, width, height, className, }: CollabPopupProps): react_jsx_runtime.JSX.Element | null;
|
|
412
|
+
declare function CollabPopup({ orderId, conversation: providedConversation, patientData, participantIds, kind, isOpen, onClose, onBack, onMinimize, initialPosition, width, height, className, }: CollabPopupProps): react_jsx_runtime.JSX.Element | null;
|
|
387
413
|
|
|
388
414
|
type ColabInlineMode = 'light' | 'dark';
|
|
389
415
|
interface CollabInlineProps {
|
|
@@ -635,19 +661,36 @@ interface CollabProviderProps {
|
|
|
635
661
|
declare function CollabProvider({ config, apiBaseUrl, children }: CollabProviderProps): react_jsx_runtime.JSX.Element | null;
|
|
636
662
|
|
|
637
663
|
interface UseConversationOptions {
|
|
638
|
-
|
|
639
|
-
|
|
664
|
+
/**
|
|
665
|
+
* The order this thread belongs to. Required for a `case` thread, which is
|
|
666
|
+
* created lazily on first message and resolved by an order-keyed preview.
|
|
667
|
+
* Omitted for a help channel — that belongs to a lab, not an order.
|
|
668
|
+
*/
|
|
669
|
+
orderId?: string;
|
|
670
|
+
/**
|
|
671
|
+
* A thread the caller has ALREADY resolved, opened directly instead of being
|
|
672
|
+
* looked up by order.
|
|
673
|
+
*
|
|
674
|
+
* This is how a help channel opens. The backend's `GET /help_channel` does
|
|
675
|
+
* the get-or-create and hands back the conversation, so there is nothing to
|
|
676
|
+
* look up and nothing to lazily create — passing it here skips the preview
|
|
677
|
+
* request entirely. The preview endpoint is order-keyed and could not serve
|
|
678
|
+
* an order-less thread in any case.
|
|
679
|
+
*/
|
|
680
|
+
conversation?: Conversation | null;
|
|
681
|
+
/** Patient/study context. Absent on a help channel — there is no patient. */
|
|
682
|
+
patientData?: PatientData;
|
|
640
683
|
/** Participant user IDs used only when creating a brand-new conversation */
|
|
641
684
|
participantIds?: string[];
|
|
642
685
|
/** Load full history on mount (default true) */
|
|
643
686
|
loadHistory?: boolean;
|
|
644
687
|
/**
|
|
645
|
-
*
|
|
646
|
-
*
|
|
688
|
+
* What this thread is. Defaults to the shared `case` thread, so every
|
|
689
|
+
* existing caller is unaffected.
|
|
647
690
|
*
|
|
648
|
-
* For
|
|
649
|
-
* support, and `participantIds` is ignored server-side — a help
|
|
650
|
-
*
|
|
691
|
+
* For `help_lab` the backend seats only the imaging centre plus Natoe
|
|
692
|
+
* support, and `participantIds` is ignored server-side — a help channel must
|
|
693
|
+
* never be widened by the client.
|
|
651
694
|
*/
|
|
652
695
|
kind?: ConversationKind;
|
|
653
696
|
}
|
|
@@ -684,7 +727,7 @@ interface UseConversationReturn {
|
|
|
684
727
|
* when the user sends the first message. If a conversation already exists for
|
|
685
728
|
* the given orderId, it's loaded and joined automatically.
|
|
686
729
|
*/
|
|
687
|
-
declare function useConversation({ orderId, patientData, participantIds, loadHistory, kind, }: UseConversationOptions): UseConversationReturn;
|
|
730
|
+
declare function useConversation({ orderId, conversation: providedConversation, patientData, participantIds, loadHistory, kind, }: UseConversationOptions): UseConversationReturn;
|
|
688
731
|
|
|
689
732
|
interface UseConversationListOptions {
|
|
690
733
|
/**
|
|
@@ -950,6 +993,13 @@ interface PatientHeaderProps {
|
|
|
950
993
|
hideName?: boolean;
|
|
951
994
|
/** Override the displayed name — use the stored conversation.name to avoid reconstructing from patientData components */
|
|
952
995
|
displayName?: string;
|
|
996
|
+
/**
|
|
997
|
+
* Render the support variant: a help channel has no patient, no study and no
|
|
998
|
+
* case, so the demographics line and every case-level action are suppressed
|
|
999
|
+
* and the title is the channel name. Without this the header would show a
|
|
1000
|
+
* row of empty fields and a dead "View DICOM" button.
|
|
1001
|
+
*/
|
|
1002
|
+
isSupportChannel?: boolean;
|
|
953
1003
|
className?: string;
|
|
954
1004
|
}
|
|
955
1005
|
/**
|
|
@@ -967,7 +1017,7 @@ interface PatientHeaderProps {
|
|
|
967
1017
|
* When `hideName=true` (CollabPopup wraps the conversation in its own
|
|
968
1018
|
* window title), only the meta + actions row renders.
|
|
969
1019
|
*/
|
|
970
|
-
declare function PatientHeader({ patientData, participants, onOpenDicom, onOpenCase, onOpenSettings, onBack, hideName, displayName, className, }: PatientHeaderProps): react_jsx_runtime.JSX.Element;
|
|
1020
|
+
declare function PatientHeader({ patientData, participants, onOpenDicom, onOpenCase, onOpenSettings, onBack, hideName, displayName, isSupportChannel, className, }: PatientHeaderProps): react_jsx_runtime.JSX.Element;
|
|
971
1021
|
|
|
972
1022
|
interface MessageListHandle {
|
|
973
1023
|
/** Scroll to a specific message by ID (used by pin jump-to) */
|
|
@@ -1230,4 +1280,4 @@ declare const SUPPORTED_IMAGE_TYPES: string[];
|
|
|
1230
1280
|
/** Max file size in bytes (20MB) */
|
|
1231
1281
|
declare const MAX_FILE_SIZE: number;
|
|
1232
1282
|
|
|
1233
|
-
export { AUDIO_MIME_TYPE, type ChannelEvent, ChannelSettings, type ChannelUpdatePayload, type CollabConfig, type CollabError, CollabInbox, CollabInline, CollabPanel, CollabPopup, CollabProvider, CollabSocket, type Conversation, type ConversationKind, ConversationList, ConversationListItem, type ConversationListItem$1 as ConversationListItemData, type ConversationPreview, type CreateConversationResponse, DEEP_LINK_PREFIX, EVENTS, type InviteUserPayload, MAX_FILE_SIZE, MAX_PINNED_MESSAGES, MESSAGES_PAGE_SIZE, MESSAGE_TYPES, type Message, MessageActionsMenu, MessageBubble, MessageInput, MessageList, type MessageListHandle, type MessageSnapshot, type MessageType, type Participant, ParticipantsList, type PatientData, PatientHeader, PinnedMessagesBar, type PreviewBatchResponse, ReplyPreview, ReplyQuoteBlock, SUPPORTED_IMAGE_TYPES, SeenByIndicator, type SendMessagePayload, THEME_DEFAULTS, THEME_VAR, TYPING_DEBOUNCE_MS, type Theme, type TypingEvent, type UserRole, applyThemeOverrides, isHelpKind, useAudioRecorder, useChannelSettings, useCollab, useConversation, useConversationList, useDeepLinks, useInlineCollab, useMessages, usePinnedMessages, useUnreadCount };
|
|
1283
|
+
export { AUDIO_MIME_TYPE, type ChannelEvent, ChannelSettings, type ChannelUpdatePayload, type CollabConfig, type CollabError, CollabInbox, CollabInline, CollabPanel, CollabPopup, CollabProvider, CollabSocket, type Conversation, type ConversationKind, ConversationList, ConversationListItem, type ConversationListItem$1 as ConversationListItemData, type ConversationPreview, type CreateConversationResponse, DEEP_LINK_PREFIX, EVENTS, type InviteUserPayload, MAX_FILE_SIZE, MAX_PINNED_MESSAGES, MESSAGES_PAGE_SIZE, MESSAGE_TYPES, type Message, MessageActionsMenu, MessageBubble, MessageInput, MessageList, type MessageListHandle, type MessageSnapshot, type MessageType, type Participant, ParticipantsList, type PatientData, PatientHeader, PinnedMessagesBar, type PreviewBatchResponse, ReplyPreview, ReplyQuoteBlock, SUPPORTED_IMAGE_TYPES, SeenByIndicator, type SendMessagePayload, THEME_DEFAULTS, THEME_VAR, TYPING_DEBOUNCE_MS, type Theme, type TypingEvent, type UserRole, applyThemeOverrides, isCaseKind, isHelpKind, useAudioRecorder, useChannelSettings, useCollab, useConversation, useConversationList, useDeepLinks, useInlineCollab, useMessages, usePinnedMessages, useUnreadCount };
|
package/dist/index.d.ts
CHANGED
|
@@ -119,25 +119,40 @@ interface PatientData {
|
|
|
119
119
|
bodyParts?: string[];
|
|
120
120
|
}
|
|
121
121
|
/**
|
|
122
|
-
*
|
|
122
|
+
* What a thread is about — and, crucially, what it is SCOPED to.
|
|
123
123
|
*
|
|
124
|
-
* - `case`
|
|
125
|
-
*
|
|
126
|
-
* - `
|
|
124
|
+
* - `case` — the discussion about one clinical order. Scoped to an order,
|
|
125
|
+
* so `orderId` is always set. Lab, radiologist, physician, admin.
|
|
126
|
+
* - `help_lab` — an imaging centre's help channel with Natoe support. Scoped to
|
|
127
|
+
* the LAB, not to any case: it has NO `orderId`, it outlives
|
|
128
|
+
* every order, and there is exactly one per imaging centre.
|
|
127
129
|
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
130
|
+
* `help_lab` was briefly modelled as per-case (one help thread per order). It is
|
|
131
|
+
* now per-lab, so support gets one continuous thread with each imaging centre
|
|
132
|
+
* rather than N short-lived ones scattered across cases. A case can still be
|
|
133
|
+
* referenced from inside the channel; it just no longer scopes it.
|
|
134
|
+
*
|
|
135
|
+
* Isolation — who can see a help channel — is enforced by the backend's
|
|
136
|
+
* participant seeding, never by anything in this package. Treat this as a label
|
|
137
|
+
* for rendering and for addressing the right thread, not as a security boundary.
|
|
132
138
|
*
|
|
133
139
|
* Optional everywhere, defaulting to `case`, so callers written before help
|
|
134
|
-
*
|
|
140
|
+
* channels existed keep their exact previous behaviour.
|
|
135
141
|
*/
|
|
136
|
-
type ConversationKind = 'case' | 'help_lab'
|
|
142
|
+
type ConversationKind = 'case' | 'help_lab';
|
|
137
143
|
declare function isHelpKind(kind?: ConversationKind | string): boolean;
|
|
144
|
+
/**
|
|
145
|
+
* True when a thread is scoped to an order and therefore has patient context to
|
|
146
|
+
* render. Prefer this over testing `orderId` directly: a help channel has no
|
|
147
|
+
* order, so every patient-shaped affordance must be skipped for it.
|
|
148
|
+
*/
|
|
149
|
+
declare function isCaseKind(kind?: ConversationKind | string): boolean;
|
|
138
150
|
interface Conversation {
|
|
139
151
|
id: string;
|
|
140
|
-
|
|
152
|
+
/** Absent on a help channel — that thread belongs to a lab, not an order. */
|
|
153
|
+
orderId?: string;
|
|
154
|
+
/** Set only on a help channel: the imaging centre that owns it. */
|
|
155
|
+
labUserId?: string;
|
|
141
156
|
name: string;
|
|
142
157
|
kind?: ConversationKind;
|
|
143
158
|
picture?: string;
|
|
@@ -146,7 +161,8 @@ interface Conversation {
|
|
|
146
161
|
unreadCount: number;
|
|
147
162
|
createdAt: string;
|
|
148
163
|
updatedAt: string;
|
|
149
|
-
/** Denormalized patient data snapshot — lets UI render without extra fetch
|
|
164
|
+
/** Denormalized patient data snapshot — lets UI render without extra fetch.
|
|
165
|
+
* Always absent on a help channel: there is no patient. */
|
|
150
166
|
patientSnapshot?: PatientData;
|
|
151
167
|
/** Currently pinned messages (max 3) */
|
|
152
168
|
pinnedMessages?: Message[];
|
|
@@ -157,10 +173,12 @@ interface Conversation {
|
|
|
157
173
|
*/
|
|
158
174
|
interface ConversationListItem$1 {
|
|
159
175
|
id: string;
|
|
160
|
-
orderId
|
|
176
|
+
/** Absent on a help channel — see `Conversation.orderId`. */
|
|
177
|
+
orderId?: string;
|
|
178
|
+
labUserId?: string;
|
|
161
179
|
name: string;
|
|
162
|
-
/** Lets the inbox
|
|
163
|
-
*
|
|
180
|
+
/** Lets the inbox render the lab's help channel distinctly from its case
|
|
181
|
+
* threads — they appear as separate rows. */
|
|
164
182
|
kind?: ConversationKind;
|
|
165
183
|
picture?: string;
|
|
166
184
|
unreadCount: number;
|
|
@@ -285,10 +303,15 @@ interface InviteUserPayload {
|
|
|
285
303
|
}
|
|
286
304
|
|
|
287
305
|
interface CollabPanelProps {
|
|
288
|
-
/** Order
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
306
|
+
/** Order this thread belongs to. Required for a `case` thread; omitted for a
|
|
307
|
+
* help channel, which belongs to a lab rather than an order. */
|
|
308
|
+
orderId?: string;
|
|
309
|
+
/** A thread the caller already resolved — how a help channel is opened.
|
|
310
|
+
* Skips the order-keyed preview lookup entirely. */
|
|
311
|
+
conversation?: Conversation | null;
|
|
312
|
+
/** Patient/study data displayed in the header. Absent on a help channel:
|
|
313
|
+
* there is no patient, and the header renders its support variant instead. */
|
|
314
|
+
patientData?: PatientData;
|
|
292
315
|
/** Participant user IDs — only used when creating a brand-new conversation */
|
|
293
316
|
participantIds?: string[];
|
|
294
317
|
/** Which thread on the case. Defaults to the shared `case` thread. */
|
|
@@ -343,13 +366,16 @@ interface CollabPanelProps {
|
|
|
343
366
|
* Full collaboration panel: patient header + pinned bar + message thread + input.
|
|
344
367
|
* Supports reply, pin/unpin, and (optionally) seen-by indicators.
|
|
345
368
|
*/
|
|
346
|
-
declare function CollabPanel({ orderId, patientData, participantIds, kind, showSeenBy, onBack, hidePatientName, hideOpenCase, onConversationChange, themeMode, showSettings: controlledShowSettings, onSettingsChange, className, style, }: CollabPanelProps): react_jsx_runtime.JSX.Element;
|
|
369
|
+
declare function CollabPanel({ orderId, conversation: providedConversation, patientData, participantIds, kind, showSeenBy, onBack, hidePatientName, hideOpenCase, onConversationChange, themeMode, showSettings: controlledShowSettings, onSettingsChange, className, style, }: CollabPanelProps): react_jsx_runtime.JSX.Element;
|
|
347
370
|
|
|
348
371
|
interface CollabPopupProps {
|
|
349
|
-
/** Order
|
|
350
|
-
|
|
372
|
+
/** Order this thread belongs to. Required for a `case` thread; omitted for a
|
|
373
|
+
* help channel, which belongs to a lab rather than an order. */
|
|
374
|
+
orderId?: string;
|
|
375
|
+
/** A thread the caller already resolved — how a help channel is opened. */
|
|
376
|
+
conversation?: Conversation | null;
|
|
351
377
|
/** Patient/study data */
|
|
352
|
-
patientData
|
|
378
|
+
patientData?: PatientData;
|
|
353
379
|
/** Participant user IDs — only used when creating a brand-new conversation */
|
|
354
380
|
participantIds?: string[];
|
|
355
381
|
/** Which thread on the case. Defaults to the shared `case` thread. */
|
|
@@ -383,7 +409,7 @@ interface CollabPopupProps {
|
|
|
383
409
|
* Draggable floating chat popup — drop-in replacement for DraggableChatPopup.
|
|
384
410
|
* Renders a CollabPanel inside a movable, resizable container.
|
|
385
411
|
*/
|
|
386
|
-
declare function CollabPopup({ orderId, patientData, participantIds, kind, isOpen, onClose, onBack, onMinimize, initialPosition, width, height, className, }: CollabPopupProps): react_jsx_runtime.JSX.Element | null;
|
|
412
|
+
declare function CollabPopup({ orderId, conversation: providedConversation, patientData, participantIds, kind, isOpen, onClose, onBack, onMinimize, initialPosition, width, height, className, }: CollabPopupProps): react_jsx_runtime.JSX.Element | null;
|
|
387
413
|
|
|
388
414
|
type ColabInlineMode = 'light' | 'dark';
|
|
389
415
|
interface CollabInlineProps {
|
|
@@ -635,19 +661,36 @@ interface CollabProviderProps {
|
|
|
635
661
|
declare function CollabProvider({ config, apiBaseUrl, children }: CollabProviderProps): react_jsx_runtime.JSX.Element | null;
|
|
636
662
|
|
|
637
663
|
interface UseConversationOptions {
|
|
638
|
-
|
|
639
|
-
|
|
664
|
+
/**
|
|
665
|
+
* The order this thread belongs to. Required for a `case` thread, which is
|
|
666
|
+
* created lazily on first message and resolved by an order-keyed preview.
|
|
667
|
+
* Omitted for a help channel — that belongs to a lab, not an order.
|
|
668
|
+
*/
|
|
669
|
+
orderId?: string;
|
|
670
|
+
/**
|
|
671
|
+
* A thread the caller has ALREADY resolved, opened directly instead of being
|
|
672
|
+
* looked up by order.
|
|
673
|
+
*
|
|
674
|
+
* This is how a help channel opens. The backend's `GET /help_channel` does
|
|
675
|
+
* the get-or-create and hands back the conversation, so there is nothing to
|
|
676
|
+
* look up and nothing to lazily create — passing it here skips the preview
|
|
677
|
+
* request entirely. The preview endpoint is order-keyed and could not serve
|
|
678
|
+
* an order-less thread in any case.
|
|
679
|
+
*/
|
|
680
|
+
conversation?: Conversation | null;
|
|
681
|
+
/** Patient/study context. Absent on a help channel — there is no patient. */
|
|
682
|
+
patientData?: PatientData;
|
|
640
683
|
/** Participant user IDs used only when creating a brand-new conversation */
|
|
641
684
|
participantIds?: string[];
|
|
642
685
|
/** Load full history on mount (default true) */
|
|
643
686
|
loadHistory?: boolean;
|
|
644
687
|
/**
|
|
645
|
-
*
|
|
646
|
-
*
|
|
688
|
+
* What this thread is. Defaults to the shared `case` thread, so every
|
|
689
|
+
* existing caller is unaffected.
|
|
647
690
|
*
|
|
648
|
-
* For
|
|
649
|
-
* support, and `participantIds` is ignored server-side — a help
|
|
650
|
-
*
|
|
691
|
+
* For `help_lab` the backend seats only the imaging centre plus Natoe
|
|
692
|
+
* support, and `participantIds` is ignored server-side — a help channel must
|
|
693
|
+
* never be widened by the client.
|
|
651
694
|
*/
|
|
652
695
|
kind?: ConversationKind;
|
|
653
696
|
}
|
|
@@ -684,7 +727,7 @@ interface UseConversationReturn {
|
|
|
684
727
|
* when the user sends the first message. If a conversation already exists for
|
|
685
728
|
* the given orderId, it's loaded and joined automatically.
|
|
686
729
|
*/
|
|
687
|
-
declare function useConversation({ orderId, patientData, participantIds, loadHistory, kind, }: UseConversationOptions): UseConversationReturn;
|
|
730
|
+
declare function useConversation({ orderId, conversation: providedConversation, patientData, participantIds, loadHistory, kind, }: UseConversationOptions): UseConversationReturn;
|
|
688
731
|
|
|
689
732
|
interface UseConversationListOptions {
|
|
690
733
|
/**
|
|
@@ -950,6 +993,13 @@ interface PatientHeaderProps {
|
|
|
950
993
|
hideName?: boolean;
|
|
951
994
|
/** Override the displayed name — use the stored conversation.name to avoid reconstructing from patientData components */
|
|
952
995
|
displayName?: string;
|
|
996
|
+
/**
|
|
997
|
+
* Render the support variant: a help channel has no patient, no study and no
|
|
998
|
+
* case, so the demographics line and every case-level action are suppressed
|
|
999
|
+
* and the title is the channel name. Without this the header would show a
|
|
1000
|
+
* row of empty fields and a dead "View DICOM" button.
|
|
1001
|
+
*/
|
|
1002
|
+
isSupportChannel?: boolean;
|
|
953
1003
|
className?: string;
|
|
954
1004
|
}
|
|
955
1005
|
/**
|
|
@@ -967,7 +1017,7 @@ interface PatientHeaderProps {
|
|
|
967
1017
|
* When `hideName=true` (CollabPopup wraps the conversation in its own
|
|
968
1018
|
* window title), only the meta + actions row renders.
|
|
969
1019
|
*/
|
|
970
|
-
declare function PatientHeader({ patientData, participants, onOpenDicom, onOpenCase, onOpenSettings, onBack, hideName, displayName, className, }: PatientHeaderProps): react_jsx_runtime.JSX.Element;
|
|
1020
|
+
declare function PatientHeader({ patientData, participants, onOpenDicom, onOpenCase, onOpenSettings, onBack, hideName, displayName, isSupportChannel, className, }: PatientHeaderProps): react_jsx_runtime.JSX.Element;
|
|
971
1021
|
|
|
972
1022
|
interface MessageListHandle {
|
|
973
1023
|
/** Scroll to a specific message by ID (used by pin jump-to) */
|
|
@@ -1230,4 +1280,4 @@ declare const SUPPORTED_IMAGE_TYPES: string[];
|
|
|
1230
1280
|
/** Max file size in bytes (20MB) */
|
|
1231
1281
|
declare const MAX_FILE_SIZE: number;
|
|
1232
1282
|
|
|
1233
|
-
export { AUDIO_MIME_TYPE, type ChannelEvent, ChannelSettings, type ChannelUpdatePayload, type CollabConfig, type CollabError, CollabInbox, CollabInline, CollabPanel, CollabPopup, CollabProvider, CollabSocket, type Conversation, type ConversationKind, ConversationList, ConversationListItem, type ConversationListItem$1 as ConversationListItemData, type ConversationPreview, type CreateConversationResponse, DEEP_LINK_PREFIX, EVENTS, type InviteUserPayload, MAX_FILE_SIZE, MAX_PINNED_MESSAGES, MESSAGES_PAGE_SIZE, MESSAGE_TYPES, type Message, MessageActionsMenu, MessageBubble, MessageInput, MessageList, type MessageListHandle, type MessageSnapshot, type MessageType, type Participant, ParticipantsList, type PatientData, PatientHeader, PinnedMessagesBar, type PreviewBatchResponse, ReplyPreview, ReplyQuoteBlock, SUPPORTED_IMAGE_TYPES, SeenByIndicator, type SendMessagePayload, THEME_DEFAULTS, THEME_VAR, TYPING_DEBOUNCE_MS, type Theme, type TypingEvent, type UserRole, applyThemeOverrides, isHelpKind, useAudioRecorder, useChannelSettings, useCollab, useConversation, useConversationList, useDeepLinks, useInlineCollab, useMessages, usePinnedMessages, useUnreadCount };
|
|
1283
|
+
export { AUDIO_MIME_TYPE, type ChannelEvent, ChannelSettings, type ChannelUpdatePayload, type CollabConfig, type CollabError, CollabInbox, CollabInline, CollabPanel, CollabPopup, CollabProvider, CollabSocket, type Conversation, type ConversationKind, ConversationList, ConversationListItem, type ConversationListItem$1 as ConversationListItemData, type ConversationPreview, type CreateConversationResponse, DEEP_LINK_PREFIX, EVENTS, type InviteUserPayload, MAX_FILE_SIZE, MAX_PINNED_MESSAGES, MESSAGES_PAGE_SIZE, MESSAGE_TYPES, type Message, MessageActionsMenu, MessageBubble, MessageInput, MessageList, type MessageListHandle, type MessageSnapshot, type MessageType, type Participant, ParticipantsList, type PatientData, PatientHeader, PinnedMessagesBar, type PreviewBatchResponse, ReplyPreview, ReplyQuoteBlock, SUPPORTED_IMAGE_TYPES, SeenByIndicator, type SendMessagePayload, THEME_DEFAULTS, THEME_VAR, TYPING_DEBOUNCE_MS, type Theme, type TypingEvent, type UserRole, applyThemeOverrides, isCaseKind, isHelpKind, useAudioRecorder, useChannelSettings, useCollab, useConversation, useConversationList, useDeepLinks, useInlineCollab, useMessages, usePinnedMessages, useUnreadCount };
|
package/dist/index.js
CHANGED
|
@@ -891,6 +891,7 @@ function cleanChannelName(name) {
|
|
|
891
891
|
// src/hooks/useConversation.ts
|
|
892
892
|
function useConversation({
|
|
893
893
|
orderId,
|
|
894
|
+
conversation: providedConversation,
|
|
894
895
|
patientData,
|
|
895
896
|
participantIds = [],
|
|
896
897
|
loadHistory = true,
|
|
@@ -923,6 +924,7 @@ function useConversation({
|
|
|
923
924
|
const markedReadIdsRef = React4.useRef(/* @__PURE__ */ new Set());
|
|
924
925
|
const buildName = React4.useCallback(
|
|
925
926
|
() => {
|
|
927
|
+
if (!patientData) return "";
|
|
926
928
|
const labName = patientData.labName ?? (config.userRole === "lab" ? config.userName : void 0);
|
|
927
929
|
return buildChannelName({ ...patientData, labName });
|
|
928
930
|
},
|
|
@@ -1022,6 +1024,28 @@ function useConversation({
|
|
|
1022
1024
|
setIsLoading(true);
|
|
1023
1025
|
setError(null);
|
|
1024
1026
|
try {
|
|
1027
|
+
if (providedConversation) {
|
|
1028
|
+
setConversation(providedConversation);
|
|
1029
|
+
setParticipants(providedConversation.participants ?? []);
|
|
1030
|
+
joinChannel(providedConversation);
|
|
1031
|
+
if (loadHistory) {
|
|
1032
|
+
const history = await fetchMessages(providedConversation.id);
|
|
1033
|
+
if (cancelled) return;
|
|
1034
|
+
setMessages(history);
|
|
1035
|
+
setHasMore(history.length >= MESSAGES_PAGE_SIZE);
|
|
1036
|
+
setPinnedMessages(history.filter((m) => m.isPinned));
|
|
1037
|
+
}
|
|
1038
|
+
if (!cancelled) setIsLoading(false);
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
if (!orderId) {
|
|
1042
|
+
setConversation(null);
|
|
1043
|
+
setMessages([]);
|
|
1044
|
+
setParticipants([]);
|
|
1045
|
+
setHasMore(false);
|
|
1046
|
+
setIsLoading(false);
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1025
1049
|
const preview = await requestPreview(orderId, kind);
|
|
1026
1050
|
if (cancelled) return;
|
|
1027
1051
|
if (!preview) {
|
|
@@ -1083,6 +1107,12 @@ function useConversation({
|
|
|
1083
1107
|
const ensureConversation = React4.useCallback(
|
|
1084
1108
|
async (payload) => {
|
|
1085
1109
|
if (conversation) return { conv: conversation, persistedByCreate: false };
|
|
1110
|
+
if (!orderId) {
|
|
1111
|
+
throw {
|
|
1112
|
+
code: "NO_CONVERSATION",
|
|
1113
|
+
message: "This conversation is not available yet. Please reopen it and try again."
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1086
1116
|
if (ensureConversationInFlight.current) {
|
|
1087
1117
|
const conv = await ensureConversationInFlight.current;
|
|
1088
1118
|
return { conv, persistedByCreate: false };
|
|
@@ -1570,15 +1600,19 @@ function PatientHeader({
|
|
|
1570
1600
|
onBack,
|
|
1571
1601
|
hideName = false,
|
|
1572
1602
|
displayName,
|
|
1603
|
+
isSupportChannel = false,
|
|
1573
1604
|
className
|
|
1574
1605
|
}) {
|
|
1575
|
-
const hasDicom = !!(patientData.studyId && patientData.storageId);
|
|
1576
|
-
const resolvedName = resolveDisplayName(patientData, displayName);
|
|
1606
|
+
const hasDicom = !isSupportChannel && !!(patientData.studyId && patientData.storageId);
|
|
1607
|
+
const resolvedName = isSupportChannel ? displayName || patientData.patientName || "Natoe Support" : resolveDisplayName(patientData, displayName);
|
|
1577
1608
|
const metaParts = [];
|
|
1578
|
-
if (
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
if (
|
|
1609
|
+
if (isSupportChannel) {
|
|
1610
|
+
metaParts.push("Help and support");
|
|
1611
|
+
}
|
|
1612
|
+
if (!isSupportChannel && patientData.patientAge) metaParts.push(String(patientData.patientAge));
|
|
1613
|
+
if (!isSupportChannel && patientData.patientSex) metaParts.push(String(patientData.patientSex));
|
|
1614
|
+
if (!isSupportChannel && patientData.studyType) metaParts.push(patientData.studyType);
|
|
1615
|
+
if (!isSupportChannel && patientData.bodyParts && patientData.bodyParts.length > 0) {
|
|
1582
1616
|
metaParts.push(patientData.bodyParts.join(", "));
|
|
1583
1617
|
}
|
|
1584
1618
|
const hasPrimaryActions = hasDicom && onOpenDicom || onOpenCase;
|
|
@@ -4535,6 +4569,7 @@ var DARK_THEME_OVERRIDES = {
|
|
|
4535
4569
|
};
|
|
4536
4570
|
function CollabPanel({
|
|
4537
4571
|
orderId,
|
|
4572
|
+
conversation: providedConversation,
|
|
4538
4573
|
patientData,
|
|
4539
4574
|
participantIds,
|
|
4540
4575
|
kind,
|
|
@@ -4576,7 +4611,13 @@ function CollabPanel({
|
|
|
4576
4611
|
loadMoreMessages,
|
|
4577
4612
|
pinMessage,
|
|
4578
4613
|
unpinMessage
|
|
4579
|
-
} = useConversation({
|
|
4614
|
+
} = useConversation({
|
|
4615
|
+
orderId,
|
|
4616
|
+
conversation: providedConversation,
|
|
4617
|
+
patientData,
|
|
4618
|
+
participantIds,
|
|
4619
|
+
kind
|
|
4620
|
+
});
|
|
4580
4621
|
React4.useEffect(() => {
|
|
4581
4622
|
onConversationChange?.(conversation);
|
|
4582
4623
|
}, [conversation, onConversationChange]);
|
|
@@ -4585,11 +4626,11 @@ function CollabPanel({
|
|
|
4585
4626
|
});
|
|
4586
4627
|
const { handleDeepLink } = useDeepLinks();
|
|
4587
4628
|
const handleOpenDicom = () => {
|
|
4588
|
-
if (patientData
|
|
4629
|
+
if (patientData?.studyId && patientData.storageId && config.onOpenDicom) {
|
|
4589
4630
|
config.onOpenDicom(patientData.studyId, patientData.storageId);
|
|
4590
4631
|
}
|
|
4591
4632
|
};
|
|
4592
|
-
const handleOpenCase = !hideOpenCase && config.onOpenCase ? () => config.onOpenCase?.(patientData.orderId, patientData.displayOrderId) : void 0;
|
|
4633
|
+
const handleOpenCase = !hideOpenCase && config.onOpenCase && patientData?.orderId ? () => config.onOpenCase?.(patientData.orderId, patientData.displayOrderId) : void 0;
|
|
4593
4634
|
const handleJumpToMessage = (messageId) => {
|
|
4594
4635
|
messageListRef.current?.scrollToMessage(messageId);
|
|
4595
4636
|
};
|
|
@@ -4662,7 +4703,8 @@ function CollabPanel({
|
|
|
4662
4703
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4663
4704
|
PatientHeader,
|
|
4664
4705
|
{
|
|
4665
|
-
patientData,
|
|
4706
|
+
patientData: patientData ?? { orderId: "", patientName: conversation?.name ?? "Natoe Support" },
|
|
4707
|
+
isSupportChannel: !patientData,
|
|
4666
4708
|
participants,
|
|
4667
4709
|
onOpenDicom: handleOpenDicom,
|
|
4668
4710
|
onOpenCase: handleOpenCase,
|
|
@@ -4793,6 +4835,7 @@ ensureGlobalStyles();
|
|
|
4793
4835
|
var FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
4794
4836
|
function CollabPopup({
|
|
4795
4837
|
orderId,
|
|
4838
|
+
conversation: providedConversation,
|
|
4796
4839
|
patientData,
|
|
4797
4840
|
participantIds,
|
|
4798
4841
|
kind,
|
|
@@ -4819,7 +4862,7 @@ function CollabPopup({
|
|
|
4819
4862
|
const containerRef = React4.useRef(null);
|
|
4820
4863
|
const previouslyFocused = React4.useRef(null);
|
|
4821
4864
|
const titleId = React4.useId();
|
|
4822
|
-
const titleText = cleanChannelName(loadedConversation?.name) || buildChannelName(patientData);
|
|
4865
|
+
const titleText = cleanChannelName(loadedConversation?.name) || (patientData ? buildChannelName(patientData) : "Natoe Support");
|
|
4823
4866
|
const handleMouseDown = React4.useCallback(
|
|
4824
4867
|
(e) => {
|
|
4825
4868
|
if (!e.target.closest("[data-drag-handle]")) return;
|
|
@@ -4924,8 +4967,9 @@ function CollabPopup({
|
|
|
4924
4967
|
}
|
|
4925
4968
|
),
|
|
4926
4969
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles13.titleCenter, children: [
|
|
4927
|
-
/* @__PURE__ */ jsxRuntime.jsx("h2", { id: titleId, style: styles13.patientName, children: patientData
|
|
4970
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { id: titleId, style: styles13.patientName, children: patientData?.patientName || titleText }),
|
|
4928
4971
|
(() => {
|
|
4972
|
+
if (!patientData) return /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles13.subRow, children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles13.metaText, children: "Help and support" }) });
|
|
4929
4973
|
const parts = [];
|
|
4930
4974
|
if (patientData.patientAge) parts.push(String(patientData.patientAge));
|
|
4931
4975
|
if (patientData.patientSex) parts.push(String(patientData.patientSex));
|
|
@@ -4982,6 +5026,7 @@ function CollabPopup({
|
|
|
4982
5026
|
CollabPanel,
|
|
4983
5027
|
{
|
|
4984
5028
|
orderId,
|
|
5029
|
+
conversation: providedConversation,
|
|
4985
5030
|
patientData,
|
|
4986
5031
|
participantIds,
|
|
4987
5032
|
kind,
|
|
@@ -5901,7 +5946,10 @@ function useConversationList(options) {
|
|
|
5901
5946
|
|
|
5902
5947
|
// src/core/types.ts
|
|
5903
5948
|
function isHelpKind(kind) {
|
|
5904
|
-
return kind === "help_lab"
|
|
5949
|
+
return kind === "help_lab";
|
|
5950
|
+
}
|
|
5951
|
+
function isCaseKind(kind) {
|
|
5952
|
+
return kind === void 0 || kind === "case";
|
|
5905
5953
|
}
|
|
5906
5954
|
function ConversationListItem({
|
|
5907
5955
|
item,
|
|
@@ -6537,6 +6585,7 @@ function CollabInbox({
|
|
|
6537
6585
|
);
|
|
6538
6586
|
}
|
|
6539
6587
|
function buildPatientData(item) {
|
|
6588
|
+
if (isHelpKind(item.kind) || !item.orderId) return void 0;
|
|
6540
6589
|
const parsed = parseChannelName(item.name, item.orderId);
|
|
6541
6590
|
if (item.patientSnapshot) {
|
|
6542
6591
|
return {
|
|
@@ -6874,6 +6923,7 @@ exports.THEME_DEFAULTS = THEME_DEFAULTS;
|
|
|
6874
6923
|
exports.THEME_VAR = THEME_VAR;
|
|
6875
6924
|
exports.TYPING_DEBOUNCE_MS = TYPING_DEBOUNCE_MS;
|
|
6876
6925
|
exports.applyThemeOverrides = applyThemeOverrides;
|
|
6926
|
+
exports.isCaseKind = isCaseKind;
|
|
6877
6927
|
exports.isHelpKind = isHelpKind;
|
|
6878
6928
|
exports.useAudioRecorder = useAudioRecorder;
|
|
6879
6929
|
exports.useChannelSettings = useChannelSettings;
|