@natoe/colab 0.1.1 → 0.1.3
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.js +539 -296
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +355 -116
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var React4 = require('react');
|
|
4
4
|
var phoenix = require('phoenix');
|
|
5
5
|
var jsxRuntime = require('react/jsx-runtime');
|
|
6
6
|
|
|
7
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
8
|
+
|
|
9
|
+
var React4__default = /*#__PURE__*/_interopDefault(React4);
|
|
10
|
+
|
|
7
11
|
// src/components/CollabPanel.tsx
|
|
8
12
|
|
|
9
13
|
// src/core/constants.ts
|
|
@@ -316,25 +320,25 @@ var CollabSocket = class {
|
|
|
316
320
|
this.config = null;
|
|
317
321
|
}
|
|
318
322
|
};
|
|
319
|
-
var CollabContext =
|
|
323
|
+
var CollabContext = React4.createContext(null);
|
|
320
324
|
function useCollab() {
|
|
321
|
-
const context =
|
|
325
|
+
const context = React4.useContext(CollabContext);
|
|
322
326
|
if (!context) {
|
|
323
327
|
throw new Error("useCollab must be used within a <CollabProvider>");
|
|
324
328
|
}
|
|
325
329
|
return context;
|
|
326
330
|
}
|
|
327
331
|
function CollabProvider({ config, apiBaseUrl, children }) {
|
|
328
|
-
const [socket, setSocket] =
|
|
332
|
+
const [socket, setSocket] = React4.useState(() => {
|
|
329
333
|
if (typeof window === "undefined") return null;
|
|
330
334
|
return new CollabSocket();
|
|
331
335
|
});
|
|
332
|
-
const [unreadCounts, setUnreadCounts] =
|
|
333
|
-
const pendingOrderIds =
|
|
334
|
-
const pendingResolvers =
|
|
335
|
-
const previewCache =
|
|
336
|
-
const batchScheduled =
|
|
337
|
-
|
|
336
|
+
const [unreadCounts, setUnreadCounts] = React4.useState({});
|
|
337
|
+
const pendingOrderIds = React4.useRef(/* @__PURE__ */ new Set());
|
|
338
|
+
const pendingResolvers = React4.useRef(/* @__PURE__ */ new Map());
|
|
339
|
+
const previewCache = React4.useRef(/* @__PURE__ */ new Map());
|
|
340
|
+
const batchScheduled = React4.useRef(false);
|
|
341
|
+
React4.useEffect(() => {
|
|
338
342
|
if (typeof window === "undefined") return;
|
|
339
343
|
let s = socket;
|
|
340
344
|
if (!s) {
|
|
@@ -349,14 +353,14 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
349
353
|
s.connect(config);
|
|
350
354
|
}
|
|
351
355
|
}, [config.socketUrl, config.userId]);
|
|
352
|
-
const authHeaders =
|
|
356
|
+
const authHeaders = React4.useCallback(
|
|
353
357
|
() => ({
|
|
354
358
|
Authorization: `Bearer ${config.getToken()}`,
|
|
355
359
|
"Content-Type": "application/json"
|
|
356
360
|
}),
|
|
357
361
|
[config]
|
|
358
362
|
);
|
|
359
|
-
const flushPreviewBatch =
|
|
363
|
+
const flushPreviewBatch = React4.useCallback(async () => {
|
|
360
364
|
const orderIds = Array.from(pendingOrderIds.current);
|
|
361
365
|
const resolvers = new Map(pendingResolvers.current);
|
|
362
366
|
pendingOrderIds.current.clear();
|
|
@@ -386,7 +390,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
386
390
|
});
|
|
387
391
|
}
|
|
388
392
|
}, [apiBaseUrl, authHeaders, config]);
|
|
389
|
-
const requestPreview =
|
|
393
|
+
const requestPreview = React4.useCallback(
|
|
390
394
|
(orderId) => {
|
|
391
395
|
if (previewCache.current.has(orderId)) {
|
|
392
396
|
return Promise.resolve(previewCache.current.get(orderId) ?? null);
|
|
@@ -404,10 +408,10 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
404
408
|
},
|
|
405
409
|
[flushPreviewBatch]
|
|
406
410
|
);
|
|
407
|
-
const invalidatePreview =
|
|
411
|
+
const invalidatePreview = React4.useCallback((orderId) => {
|
|
408
412
|
previewCache.current.delete(orderId);
|
|
409
413
|
}, []);
|
|
410
|
-
const fetchMessages =
|
|
414
|
+
const fetchMessages = React4.useCallback(
|
|
411
415
|
async (conversationId, options = {}) => {
|
|
412
416
|
const limit = options.limit ?? MESSAGES_PAGE_SIZE;
|
|
413
417
|
const params = new URLSearchParams({ limit: String(limit) });
|
|
@@ -423,7 +427,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
423
427
|
},
|
|
424
428
|
[apiBaseUrl, authHeaders]
|
|
425
429
|
);
|
|
426
|
-
const createConversationWithMessage =
|
|
430
|
+
const createConversationWithMessage = React4.useCallback(
|
|
427
431
|
async (orderId, message, options) => {
|
|
428
432
|
if (!orderId || !orderId.trim()) {
|
|
429
433
|
throw {
|
|
@@ -452,7 +456,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
452
456
|
},
|
|
453
457
|
[apiBaseUrl, authHeaders]
|
|
454
458
|
);
|
|
455
|
-
const addParticipant =
|
|
459
|
+
const addParticipant = React4.useCallback(
|
|
456
460
|
async (conversationId, userId) => {
|
|
457
461
|
const response = await fetch(
|
|
458
462
|
`${apiBaseUrl}/api/natoe-colab/conversations/${conversationId}/participants`,
|
|
@@ -468,7 +472,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
468
472
|
},
|
|
469
473
|
[apiBaseUrl, authHeaders]
|
|
470
474
|
);
|
|
471
|
-
const removeParticipant =
|
|
475
|
+
const removeParticipant = React4.useCallback(
|
|
472
476
|
async (conversationId, userId) => {
|
|
473
477
|
const response = await fetch(
|
|
474
478
|
`${apiBaseUrl}/api/natoe-colab/conversations/${conversationId}/participants/${userId}`,
|
|
@@ -483,7 +487,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
483
487
|
},
|
|
484
488
|
[apiBaseUrl, authHeaders]
|
|
485
489
|
);
|
|
486
|
-
const fetchConversationList =
|
|
490
|
+
const fetchConversationList = React4.useCallback(async () => {
|
|
487
491
|
const response = await fetch(`${apiBaseUrl}/api/natoe-colab/conversations`, {
|
|
488
492
|
headers: authHeaders()
|
|
489
493
|
});
|
|
@@ -492,7 +496,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
492
496
|
}
|
|
493
497
|
return snakeToCamel(await response.json());
|
|
494
498
|
}, [apiBaseUrl, authHeaders]);
|
|
495
|
-
const pinMessage =
|
|
499
|
+
const pinMessage = React4.useCallback(
|
|
496
500
|
async (conversationId, messageId) => {
|
|
497
501
|
const response = await fetch(
|
|
498
502
|
`${apiBaseUrl}/api/natoe-colab/conversations/${conversationId}/messages/${messageId}/pin`,
|
|
@@ -508,7 +512,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
508
512
|
},
|
|
509
513
|
[apiBaseUrl, authHeaders]
|
|
510
514
|
);
|
|
511
|
-
const unpinMessage =
|
|
515
|
+
const unpinMessage = React4.useCallback(
|
|
512
516
|
async (conversationId, messageId) => {
|
|
513
517
|
const response = await fetch(
|
|
514
518
|
`${apiBaseUrl}/api/natoe-colab/conversations/${conversationId}/messages/${messageId}/pin`,
|
|
@@ -523,7 +527,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
523
527
|
},
|
|
524
528
|
[apiBaseUrl, authHeaders]
|
|
525
529
|
);
|
|
526
|
-
const fetchPinnedMessages =
|
|
530
|
+
const fetchPinnedMessages = React4.useCallback(
|
|
527
531
|
async (conversationId) => {
|
|
528
532
|
const response = await fetch(
|
|
529
533
|
`${apiBaseUrl}/api/natoe-colab/conversations/${conversationId}/pinned`,
|
|
@@ -587,25 +591,25 @@ function useConversation({
|
|
|
587
591
|
pinMessage: pinMessageApi,
|
|
588
592
|
unpinMessage: unpinMessageApi
|
|
589
593
|
} = useCollab();
|
|
590
|
-
const [conversation, setConversation] =
|
|
591
|
-
const [messages, setMessages] =
|
|
592
|
-
const [pinnedMessages, setPinnedMessages] =
|
|
593
|
-
const [participants, setParticipants] =
|
|
594
|
-
const [typingUsers, setTypingUsers] =
|
|
595
|
-
const [isLoading, setIsLoading] =
|
|
596
|
-
const [hasMore, setHasMore] =
|
|
597
|
-
const [error, setError] =
|
|
598
|
-
const [isConnected, setIsConnected] =
|
|
599
|
-
const [replyTo, setReplyTo] =
|
|
600
|
-
const joinedConversationId =
|
|
601
|
-
const typingTimers =
|
|
602
|
-
const ensureConversationInFlight =
|
|
603
|
-
const markedReadIdsRef =
|
|
604
|
-
const buildName =
|
|
594
|
+
const [conversation, setConversation] = React4.useState(null);
|
|
595
|
+
const [messages, setMessages] = React4.useState([]);
|
|
596
|
+
const [pinnedMessages, setPinnedMessages] = React4.useState([]);
|
|
597
|
+
const [participants, setParticipants] = React4.useState([]);
|
|
598
|
+
const [typingUsers, setTypingUsers] = React4.useState([]);
|
|
599
|
+
const [isLoading, setIsLoading] = React4.useState(true);
|
|
600
|
+
const [hasMore, setHasMore] = React4.useState(true);
|
|
601
|
+
const [error, setError] = React4.useState(null);
|
|
602
|
+
const [isConnected, setIsConnected] = React4.useState(false);
|
|
603
|
+
const [replyTo, setReplyTo] = React4.useState(null);
|
|
604
|
+
const joinedConversationId = React4.useRef(null);
|
|
605
|
+
const typingTimers = React4.useRef(/* @__PURE__ */ new Map());
|
|
606
|
+
const ensureConversationInFlight = React4.useRef(null);
|
|
607
|
+
const markedReadIdsRef = React4.useRef(/* @__PURE__ */ new Set());
|
|
608
|
+
const buildName = React4.useCallback(
|
|
605
609
|
() => buildChannelName(patientData),
|
|
606
610
|
[patientData]
|
|
607
611
|
);
|
|
608
|
-
const joinChannel =
|
|
612
|
+
const joinChannel = React4.useCallback(
|
|
609
613
|
(conv) => {
|
|
610
614
|
if (joinedConversationId.current === conv.id) return;
|
|
611
615
|
socket.joinConversation(conv.id, {
|
|
@@ -688,7 +692,7 @@ function useConversation({
|
|
|
688
692
|
},
|
|
689
693
|
[socket, config.userId]
|
|
690
694
|
);
|
|
691
|
-
|
|
695
|
+
React4.useEffect(() => {
|
|
692
696
|
let cancelled = false;
|
|
693
697
|
const init = async () => {
|
|
694
698
|
setIsLoading(true);
|
|
@@ -757,7 +761,7 @@ function useConversation({
|
|
|
757
761
|
setIsConnected(false);
|
|
758
762
|
};
|
|
759
763
|
}, [orderId]);
|
|
760
|
-
const ensureConversation =
|
|
764
|
+
const ensureConversation = React4.useCallback(
|
|
761
765
|
async (payload) => {
|
|
762
766
|
if (conversation) return { conv: conversation, persistedByCreate: false };
|
|
763
767
|
if (ensureConversationInFlight.current) {
|
|
@@ -811,7 +815,7 @@ function useConversation({
|
|
|
811
815
|
fetchMessages
|
|
812
816
|
]
|
|
813
817
|
);
|
|
814
|
-
const sendMessage =
|
|
818
|
+
const sendMessage = React4.useCallback(
|
|
815
819
|
async (body) => {
|
|
816
820
|
const payload = {
|
|
817
821
|
body,
|
|
@@ -855,7 +859,7 @@ function useConversation({
|
|
|
855
859
|
},
|
|
856
860
|
[ensureConversation, socket, replyTo, config]
|
|
857
861
|
);
|
|
858
|
-
const sendAudioMessage =
|
|
862
|
+
const sendAudioMessage = React4.useCallback(
|
|
859
863
|
async (audioBlob, duration) => {
|
|
860
864
|
if (!config.onUploadFile) return;
|
|
861
865
|
const url = await config.onUploadFile(audioBlob, "voice-message.webm");
|
|
@@ -871,7 +875,7 @@ function useConversation({
|
|
|
871
875
|
},
|
|
872
876
|
[config, ensureConversation, socket]
|
|
873
877
|
);
|
|
874
|
-
const sendFileMessage =
|
|
878
|
+
const sendFileMessage = React4.useCallback(
|
|
875
879
|
async (file) => {
|
|
876
880
|
if (!config.onUploadFile) return;
|
|
877
881
|
const url = await config.onUploadFile(file, file.name);
|
|
@@ -888,7 +892,7 @@ function useConversation({
|
|
|
888
892
|
},
|
|
889
893
|
[config, ensureConversation, socket]
|
|
890
894
|
);
|
|
891
|
-
const sendDeepLink =
|
|
895
|
+
const sendDeepLink = React4.useCallback(
|
|
892
896
|
async (path, label) => {
|
|
893
897
|
const payload = {
|
|
894
898
|
body: label,
|
|
@@ -901,14 +905,14 @@ function useConversation({
|
|
|
901
905
|
},
|
|
902
906
|
[ensureConversation, socket]
|
|
903
907
|
);
|
|
904
|
-
const sendTypingIndicator =
|
|
908
|
+
const sendTypingIndicator = React4.useCallback(
|
|
905
909
|
(isTyping) => {
|
|
906
910
|
if (!conversation || !isConnected) return;
|
|
907
911
|
socket.sendTyping(conversation.id, isTyping);
|
|
908
912
|
},
|
|
909
913
|
[conversation, isConnected, socket]
|
|
910
914
|
);
|
|
911
|
-
const markAsRead =
|
|
915
|
+
const markAsRead = React4.useCallback(
|
|
912
916
|
(messageId) => {
|
|
913
917
|
if (!conversation || !isConnected) return;
|
|
914
918
|
if (markedReadIdsRef.current.has(messageId)) return;
|
|
@@ -917,7 +921,7 @@ function useConversation({
|
|
|
917
921
|
},
|
|
918
922
|
[conversation, isConnected, socket]
|
|
919
923
|
);
|
|
920
|
-
const loadMoreMessages =
|
|
924
|
+
const loadMoreMessages = React4.useCallback(async () => {
|
|
921
925
|
if (!conversation || !hasMore || isLoading) return;
|
|
922
926
|
const oldestMessage = messages[0];
|
|
923
927
|
const before = oldestMessage?.id;
|
|
@@ -936,7 +940,7 @@ function useConversation({
|
|
|
936
940
|
setIsLoading(false);
|
|
937
941
|
}
|
|
938
942
|
}, [conversation, hasMore, isLoading, messages, fetchMessages, config]);
|
|
939
|
-
const pinMessage =
|
|
943
|
+
const pinMessage = React4.useCallback(
|
|
940
944
|
async (messageId) => {
|
|
941
945
|
if (!conversation) return;
|
|
942
946
|
try {
|
|
@@ -951,7 +955,7 @@ function useConversation({
|
|
|
951
955
|
},
|
|
952
956
|
[conversation, pinMessageApi, config]
|
|
953
957
|
);
|
|
954
|
-
const unpinMessage =
|
|
958
|
+
const unpinMessage = React4.useCallback(
|
|
955
959
|
async (messageId) => {
|
|
956
960
|
if (!conversation) return;
|
|
957
961
|
try {
|
|
@@ -991,10 +995,10 @@ function useConversation({
|
|
|
991
995
|
}
|
|
992
996
|
function useChannelSettings({ conversationId }) {
|
|
993
997
|
const { socket, config } = useCollab();
|
|
994
|
-
const [isUpdating, setIsUpdating] =
|
|
995
|
-
const [error, setError] =
|
|
998
|
+
const [isUpdating, setIsUpdating] = React4.useState(false);
|
|
999
|
+
const [error, setError] = React4.useState(null);
|
|
996
1000
|
const isAdmin = config.userRole === "admin";
|
|
997
|
-
const withLoading =
|
|
1001
|
+
const withLoading = React4.useCallback(
|
|
998
1002
|
async (operation) => {
|
|
999
1003
|
if (!conversationId) return;
|
|
1000
1004
|
setIsUpdating(true);
|
|
@@ -1013,25 +1017,25 @@ function useChannelSettings({ conversationId }) {
|
|
|
1013
1017
|
},
|
|
1014
1018
|
[conversationId, config]
|
|
1015
1019
|
);
|
|
1016
|
-
const inviteUser =
|
|
1020
|
+
const inviteUser = React4.useCallback(
|
|
1017
1021
|
async (payload) => {
|
|
1018
1022
|
await withLoading(() => socket.inviteUser(conversationId, payload));
|
|
1019
1023
|
},
|
|
1020
1024
|
[conversationId, socket, withLoading]
|
|
1021
1025
|
);
|
|
1022
|
-
const removeUser =
|
|
1026
|
+
const removeUser = React4.useCallback(
|
|
1023
1027
|
async (userId) => {
|
|
1024
1028
|
await withLoading(() => socket.removeUser(conversationId, userId));
|
|
1025
1029
|
},
|
|
1026
1030
|
[conversationId, socket, withLoading]
|
|
1027
1031
|
);
|
|
1028
|
-
const leaveChannel =
|
|
1032
|
+
const leaveChannel = React4.useCallback(async () => {
|
|
1029
1033
|
await withLoading(async () => {
|
|
1030
1034
|
await socket.removeUser(conversationId, config.userId);
|
|
1031
1035
|
socket.leaveConversation(conversationId);
|
|
1032
1036
|
});
|
|
1033
1037
|
}, [conversationId, socket, config.userId, withLoading]);
|
|
1034
|
-
const deleteChannel =
|
|
1038
|
+
const deleteChannel = React4.useCallback(async () => {
|
|
1035
1039
|
if (!isAdmin) {
|
|
1036
1040
|
const err = {
|
|
1037
1041
|
code: "FORBIDDEN",
|
|
@@ -1043,7 +1047,7 @@ function useChannelSettings({ conversationId }) {
|
|
|
1043
1047
|
}
|
|
1044
1048
|
await withLoading(() => socket.deleteChannel(conversationId));
|
|
1045
1049
|
}, [conversationId, socket, isAdmin, config, withLoading]);
|
|
1046
|
-
const updateChannel =
|
|
1050
|
+
const updateChannel = React4.useCallback(
|
|
1047
1051
|
async (payload) => {
|
|
1048
1052
|
await withLoading(() => socket.updateChannel(conversationId, payload));
|
|
1049
1053
|
},
|
|
@@ -1062,7 +1066,7 @@ function useChannelSettings({ conversationId }) {
|
|
|
1062
1066
|
}
|
|
1063
1067
|
function useDeepLinks() {
|
|
1064
1068
|
const { config } = useCollab();
|
|
1065
|
-
const parseDeepLink =
|
|
1069
|
+
const parseDeepLink = React4.useCallback((link) => {
|
|
1066
1070
|
if (!link.startsWith(DEEP_LINK_PREFIX)) return null;
|
|
1067
1071
|
const path = link.slice(DEEP_LINK_PREFIX.length);
|
|
1068
1072
|
const parts = path.split("/").filter(Boolean);
|
|
@@ -1074,7 +1078,7 @@ function useDeepLinks() {
|
|
|
1074
1078
|
id: parts[1]
|
|
1075
1079
|
};
|
|
1076
1080
|
}, []);
|
|
1077
|
-
const handleDeepLink =
|
|
1081
|
+
const handleDeepLink = React4.useCallback(
|
|
1078
1082
|
(link) => {
|
|
1079
1083
|
const parsed = parseDeepLink(link);
|
|
1080
1084
|
if (!parsed) return;
|
|
@@ -1088,10 +1092,10 @@ function useDeepLinks() {
|
|
|
1088
1092
|
},
|
|
1089
1093
|
[parseDeepLink, config]
|
|
1090
1094
|
);
|
|
1091
|
-
const containsDeepLink =
|
|
1095
|
+
const containsDeepLink = React4.useCallback((text) => {
|
|
1092
1096
|
return text.includes(DEEP_LINK_PREFIX);
|
|
1093
1097
|
}, []);
|
|
1094
|
-
const extractDeepLinks =
|
|
1098
|
+
const extractDeepLinks = React4.useCallback(
|
|
1095
1099
|
(text) => {
|
|
1096
1100
|
const regex = new RegExp(`${escapeRegex(DEEP_LINK_PREFIX)}[\\w/.-]+`, "g");
|
|
1097
1101
|
const matches = text.match(regex) || [];
|
|
@@ -1155,7 +1159,7 @@ var styles = {
|
|
|
1155
1159
|
},
|
|
1156
1160
|
patientName: {
|
|
1157
1161
|
fontWeight: 600,
|
|
1158
|
-
fontSize: "
|
|
1162
|
+
fontSize: "17px",
|
|
1159
1163
|
color: "#111827"
|
|
1160
1164
|
},
|
|
1161
1165
|
secondaryRow: {
|
|
@@ -1165,8 +1169,8 @@ var styles = {
|
|
|
1165
1169
|
alignItems: "center"
|
|
1166
1170
|
},
|
|
1167
1171
|
secondary: {
|
|
1168
|
-
fontSize: "
|
|
1169
|
-
color: "#
|
|
1172
|
+
fontSize: "13px",
|
|
1173
|
+
color: "#4b5563"
|
|
1170
1174
|
},
|
|
1171
1175
|
actions: {
|
|
1172
1176
|
display: "flex",
|
|
@@ -1174,8 +1178,9 @@ var styles = {
|
|
|
1174
1178
|
marginTop: "8px"
|
|
1175
1179
|
},
|
|
1176
1180
|
dicomButton: {
|
|
1177
|
-
|
|
1178
|
-
|
|
1181
|
+
minHeight: "40px",
|
|
1182
|
+
padding: "8px 16px",
|
|
1183
|
+
fontSize: "14px",
|
|
1179
1184
|
fontWeight: 500,
|
|
1180
1185
|
color: "#ffffff",
|
|
1181
1186
|
backgroundColor: "#2563eb",
|
|
@@ -1184,8 +1189,9 @@ var styles = {
|
|
|
1184
1189
|
cursor: "pointer"
|
|
1185
1190
|
},
|
|
1186
1191
|
settingsButton: {
|
|
1187
|
-
|
|
1188
|
-
|
|
1192
|
+
minHeight: "40px",
|
|
1193
|
+
padding: "8px 16px",
|
|
1194
|
+
fontSize: "14px",
|
|
1189
1195
|
fontWeight: 500,
|
|
1190
1196
|
color: "#374151",
|
|
1191
1197
|
backgroundColor: "#ffffff",
|
|
@@ -1281,7 +1287,7 @@ function SeenByIndicator({
|
|
|
1281
1287
|
senderId,
|
|
1282
1288
|
className
|
|
1283
1289
|
}) {
|
|
1284
|
-
const [expanded, setExpanded] =
|
|
1290
|
+
const [expanded, setExpanded] = React4.useState(false);
|
|
1285
1291
|
if (senderId !== currentUserId) return null;
|
|
1286
1292
|
const seenByOthers = readBy.filter((id) => id !== currentUserId && id !== senderId).map((id) => participants.find((p) => p.userId === id)).filter((p) => !!p);
|
|
1287
1293
|
if (seenByOthers.length === 0) return null;
|
|
@@ -1426,6 +1432,40 @@ var styles4 = {
|
|
|
1426
1432
|
cursor: "pointer"
|
|
1427
1433
|
}
|
|
1428
1434
|
};
|
|
1435
|
+
function FileIcon({ size = 18, color = "currentColor" }) {
|
|
1436
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1437
|
+
"svg",
|
|
1438
|
+
{
|
|
1439
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1440
|
+
width: size,
|
|
1441
|
+
height: size,
|
|
1442
|
+
viewBox: "0 0 24 24",
|
|
1443
|
+
fill: color,
|
|
1444
|
+
"aria-hidden": "true",
|
|
1445
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z" })
|
|
1446
|
+
}
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
function PinIcon({ size = 14, color = "currentColor" }) {
|
|
1450
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1451
|
+
"svg",
|
|
1452
|
+
{
|
|
1453
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1454
|
+
width: size,
|
|
1455
|
+
height: size,
|
|
1456
|
+
viewBox: "0 0 24 24",
|
|
1457
|
+
fill: color,
|
|
1458
|
+
"aria-hidden": "true",
|
|
1459
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 9V4l1 0c.55 0 1-.45 1-1s-.45-1-1-1H7c-.55 0-1 .45-1 1s.45 1 1 1l1 0v5c0 1.66-1.34 3-3 3v2h5.97v7l1 1 1-1v-7H19v-2c-1.66 0-3-1.34-3-3z" })
|
|
1460
|
+
}
|
|
1461
|
+
);
|
|
1462
|
+
}
|
|
1463
|
+
var ROLE_LABELS = {
|
|
1464
|
+
radiologist: "Radiologist",
|
|
1465
|
+
lab: "Imaging Center",
|
|
1466
|
+
physician: "Physician",
|
|
1467
|
+
admin: "Admin"
|
|
1468
|
+
};
|
|
1429
1469
|
function MessageBubble({
|
|
1430
1470
|
message,
|
|
1431
1471
|
isOwn,
|
|
@@ -1441,7 +1481,7 @@ function MessageBubble({
|
|
|
1441
1481
|
pinDisabled = false,
|
|
1442
1482
|
className
|
|
1443
1483
|
}) {
|
|
1444
|
-
const [isHovered, setIsHovered] =
|
|
1484
|
+
const [isHovered, setIsHovered] = React4.useState(false);
|
|
1445
1485
|
if (message.type === "system") {
|
|
1446
1486
|
return /* @__PURE__ */ jsxRuntime.jsx(SystemBubble, { message });
|
|
1447
1487
|
}
|
|
@@ -1465,7 +1505,10 @@ function MessageBubble({
|
|
|
1465
1505
|
...isOwn ? styles5.ownBubble : styles5.otherBubble
|
|
1466
1506
|
},
|
|
1467
1507
|
children: [
|
|
1468
|
-
message.isPinned && /* @__PURE__ */ jsxRuntime.
|
|
1508
|
+
message.isPinned && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles5.pinnedBadge, children: [
|
|
1509
|
+
/* @__PURE__ */ jsxRuntime.jsx(PinIcon, { size: 12 }),
|
|
1510
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Pinned" })
|
|
1511
|
+
] }),
|
|
1469
1512
|
message.replyToSnapshot && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1470
1513
|
ReplyQuoteBlock,
|
|
1471
1514
|
{
|
|
@@ -1560,7 +1603,7 @@ function ImageContent({ mediaUrl, fileName }) {
|
|
|
1560
1603
|
function FileContent({ mediaUrl, fileName }) {
|
|
1561
1604
|
if (!mediaUrl) return null;
|
|
1562
1605
|
return /* @__PURE__ */ jsxRuntime.jsxs("a", { href: mediaUrl, target: "_blank", rel: "noopener noreferrer", style: styles5.fileLink, children: [
|
|
1563
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1606
|
+
/* @__PURE__ */ jsxRuntime.jsx(FileIcon, { size: 20 }),
|
|
1564
1607
|
/* @__PURE__ */ jsxRuntime.jsx("span", { style: styles5.fileName, children: fileName || "Download file" })
|
|
1565
1608
|
] });
|
|
1566
1609
|
}
|
|
@@ -1598,7 +1641,7 @@ function RoleBadge({ role }) {
|
|
|
1598
1641
|
physician: "#059669",
|
|
1599
1642
|
admin: "#dc2626"
|
|
1600
1643
|
};
|
|
1601
|
-
return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles5.roleBadge, backgroundColor: `${colors[role]}15`, color: colors[role] }, children: role });
|
|
1644
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles5.roleBadge, backgroundColor: `${colors[role]}15`, color: colors[role] }, children: ROLE_LABELS[role] ?? role });
|
|
1602
1645
|
}
|
|
1603
1646
|
function formatTime(iso) {
|
|
1604
1647
|
try {
|
|
@@ -1627,7 +1670,7 @@ var styles5 = {
|
|
|
1627
1670
|
maxWidth: "75%"
|
|
1628
1671
|
},
|
|
1629
1672
|
bubble: {
|
|
1630
|
-
padding: "
|
|
1673
|
+
padding: "10px 14px",
|
|
1631
1674
|
borderRadius: "12px",
|
|
1632
1675
|
wordBreak: "break-word"
|
|
1633
1676
|
},
|
|
@@ -1642,11 +1685,13 @@ var styles5 = {
|
|
|
1642
1685
|
borderBottomLeftRadius: "4px"
|
|
1643
1686
|
},
|
|
1644
1687
|
pinnedBadge: {
|
|
1645
|
-
display: "inline-
|
|
1646
|
-
|
|
1688
|
+
display: "inline-flex",
|
|
1689
|
+
alignItems: "center",
|
|
1690
|
+
gap: "4px",
|
|
1691
|
+
fontSize: "12px",
|
|
1647
1692
|
fontWeight: 600,
|
|
1648
1693
|
marginBottom: "4px",
|
|
1649
|
-
|
|
1694
|
+
color: "#6b7280"
|
|
1650
1695
|
},
|
|
1651
1696
|
senderRow: {
|
|
1652
1697
|
display: "flex",
|
|
@@ -1655,24 +1700,25 @@ var styles5 = {
|
|
|
1655
1700
|
marginBottom: "4px"
|
|
1656
1701
|
},
|
|
1657
1702
|
senderName: {
|
|
1658
|
-
fontSize: "
|
|
1703
|
+
fontSize: "13px",
|
|
1659
1704
|
fontWeight: 600,
|
|
1660
1705
|
color: "#374151"
|
|
1661
1706
|
},
|
|
1662
1707
|
roleBadge: {
|
|
1663
|
-
fontSize: "
|
|
1664
|
-
fontWeight:
|
|
1665
|
-
padding: "
|
|
1708
|
+
fontSize: "11px",
|
|
1709
|
+
fontWeight: 600,
|
|
1710
|
+
padding: "2px 8px",
|
|
1666
1711
|
borderRadius: "4px"
|
|
1667
1712
|
},
|
|
1668
1713
|
textBody: {
|
|
1669
1714
|
margin: 0,
|
|
1670
|
-
fontSize: "
|
|
1671
|
-
lineHeight: "1.
|
|
1715
|
+
fontSize: "15px",
|
|
1716
|
+
lineHeight: "1.45"
|
|
1672
1717
|
},
|
|
1673
1718
|
timestamp: {
|
|
1674
|
-
fontSize: "
|
|
1675
|
-
|
|
1719
|
+
fontSize: "12px",
|
|
1720
|
+
color: "inherit",
|
|
1721
|
+
opacity: 0.8,
|
|
1676
1722
|
marginTop: "4px",
|
|
1677
1723
|
textAlign: "right"
|
|
1678
1724
|
},
|
|
@@ -1711,9 +1757,6 @@ var styles5 = {
|
|
|
1711
1757
|
color: "inherit",
|
|
1712
1758
|
padding: "6px 0"
|
|
1713
1759
|
},
|
|
1714
|
-
fileIcon: {
|
|
1715
|
-
fontSize: "18px"
|
|
1716
|
-
},
|
|
1717
1760
|
fileName: {
|
|
1718
1761
|
fontSize: "13px",
|
|
1719
1762
|
textDecoration: "underline"
|
|
@@ -1748,16 +1791,92 @@ var styles5 = {
|
|
|
1748
1791
|
justifyContent: "center",
|
|
1749
1792
|
alignItems: "center",
|
|
1750
1793
|
gap: "8px",
|
|
1751
|
-
padding: "
|
|
1752
|
-
fontSize: "
|
|
1753
|
-
color: "#
|
|
1794
|
+
padding: "6px 16px",
|
|
1795
|
+
fontSize: "13px",
|
|
1796
|
+
color: "#6b7280"
|
|
1754
1797
|
},
|
|
1755
1798
|
systemTime: {
|
|
1756
|
-
fontSize: "
|
|
1757
|
-
color: "#
|
|
1799
|
+
fontSize: "12px",
|
|
1800
|
+
color: "#9ca3af"
|
|
1758
1801
|
}
|
|
1759
1802
|
};
|
|
1760
|
-
|
|
1803
|
+
|
|
1804
|
+
// src/core/dateLabels.ts
|
|
1805
|
+
var SHORT_MONTHS = [
|
|
1806
|
+
"Jan",
|
|
1807
|
+
"Feb",
|
|
1808
|
+
"Mar",
|
|
1809
|
+
"Apr",
|
|
1810
|
+
"May",
|
|
1811
|
+
"Jun",
|
|
1812
|
+
"Jul",
|
|
1813
|
+
"Aug",
|
|
1814
|
+
"Sep",
|
|
1815
|
+
"Oct",
|
|
1816
|
+
"Nov",
|
|
1817
|
+
"Dec"
|
|
1818
|
+
];
|
|
1819
|
+
var LONG_WEEKDAYS = [
|
|
1820
|
+
"Sunday",
|
|
1821
|
+
"Monday",
|
|
1822
|
+
"Tuesday",
|
|
1823
|
+
"Wednesday",
|
|
1824
|
+
"Thursday",
|
|
1825
|
+
"Friday",
|
|
1826
|
+
"Saturday"
|
|
1827
|
+
];
|
|
1828
|
+
var ONE_DAY_MS = 864e5;
|
|
1829
|
+
function parseSafe(iso) {
|
|
1830
|
+
const d = new Date(iso);
|
|
1831
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
1832
|
+
}
|
|
1833
|
+
function startOfDay(d) {
|
|
1834
|
+
return new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
1835
|
+
}
|
|
1836
|
+
function daysBetween(a, b) {
|
|
1837
|
+
return Math.floor((startOfDay(a).getTime() - startOfDay(b).getTime()) / ONE_DAY_MS);
|
|
1838
|
+
}
|
|
1839
|
+
function formatFullDate(d) {
|
|
1840
|
+
return `${d.getDate()} ${SHORT_MONTHS[d.getMonth()]} ${d.getFullYear()}`;
|
|
1841
|
+
}
|
|
1842
|
+
function formatLocaleTime(d) {
|
|
1843
|
+
try {
|
|
1844
|
+
return d.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
|
|
1845
|
+
} catch {
|
|
1846
|
+
const h = d.getHours();
|
|
1847
|
+
const m = d.getMinutes().toString().padStart(2, "0");
|
|
1848
|
+
const suffix = h >= 12 ? "PM" : "AM";
|
|
1849
|
+
const h12 = (h + 11) % 12 + 1;
|
|
1850
|
+
return `${h12}:${m} ${suffix}`;
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
function formatInboxTimestamp(iso) {
|
|
1854
|
+
if (!iso) return "";
|
|
1855
|
+
const d = parseSafe(iso);
|
|
1856
|
+
if (!d) return "";
|
|
1857
|
+
const diffDays = daysBetween(/* @__PURE__ */ new Date(), d);
|
|
1858
|
+
if (diffDays === 0) return formatLocaleTime(d);
|
|
1859
|
+
if (diffDays === 1) return "Yesterday";
|
|
1860
|
+
if (diffDays > 1 && diffDays < 7) return LONG_WEEKDAYS[d.getDay()];
|
|
1861
|
+
return formatFullDate(d);
|
|
1862
|
+
}
|
|
1863
|
+
function formatDayDivider(iso) {
|
|
1864
|
+
if (!iso) return "";
|
|
1865
|
+
const d = parseSafe(iso);
|
|
1866
|
+
if (!d) return "";
|
|
1867
|
+
const diffDays = daysBetween(/* @__PURE__ */ new Date(), d);
|
|
1868
|
+
if (diffDays === 0) return "Today";
|
|
1869
|
+
if (diffDays === 1) return "Yesterday";
|
|
1870
|
+
if (diffDays > 1 && diffDays < 7) return LONG_WEEKDAYS[d.getDay()];
|
|
1871
|
+
return formatFullDate(d);
|
|
1872
|
+
}
|
|
1873
|
+
function dayBucketKey(iso) {
|
|
1874
|
+
if (!iso) return "";
|
|
1875
|
+
const d = parseSafe(iso);
|
|
1876
|
+
if (!d) return "";
|
|
1877
|
+
return `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`;
|
|
1878
|
+
}
|
|
1879
|
+
var MessageList = React4.forwardRef(function MessageList2({
|
|
1761
1880
|
messages,
|
|
1762
1881
|
currentUserId,
|
|
1763
1882
|
participants = [],
|
|
@@ -1775,10 +1894,10 @@ var MessageList = react.forwardRef(function MessageList2({
|
|
|
1775
1894
|
pinDisabled,
|
|
1776
1895
|
className
|
|
1777
1896
|
}, ref) {
|
|
1778
|
-
const containerRef =
|
|
1779
|
-
const bottomRef =
|
|
1780
|
-
const prevMessageCount =
|
|
1781
|
-
|
|
1897
|
+
const containerRef = React4.useRef(null);
|
|
1898
|
+
const bottomRef = React4.useRef(null);
|
|
1899
|
+
const prevMessageCount = React4.useRef(messages.length);
|
|
1900
|
+
React4.useImperativeHandle(
|
|
1782
1901
|
ref,
|
|
1783
1902
|
() => ({
|
|
1784
1903
|
scrollToMessage: (messageId) => {
|
|
@@ -1795,7 +1914,7 @@ var MessageList = react.forwardRef(function MessageList2({
|
|
|
1795
1914
|
}),
|
|
1796
1915
|
[]
|
|
1797
1916
|
);
|
|
1798
|
-
|
|
1917
|
+
React4.useEffect(() => {
|
|
1799
1918
|
if (messages.length > prevMessageCount.current) {
|
|
1800
1919
|
const lastMessage = messages[messages.length - 1];
|
|
1801
1920
|
const isOwnMessage = lastMessage?.senderId === currentUserId;
|
|
@@ -1805,7 +1924,7 @@ var MessageList = react.forwardRef(function MessageList2({
|
|
|
1805
1924
|
}
|
|
1806
1925
|
prevMessageCount.current = messages.length;
|
|
1807
1926
|
}, [messages, currentUserId]);
|
|
1808
|
-
|
|
1927
|
+
React4.useEffect(() => {
|
|
1809
1928
|
if (!onMessageVisible || !containerRef.current) return;
|
|
1810
1929
|
const observer = new IntersectionObserver(
|
|
1811
1930
|
(entries) => {
|
|
@@ -1822,7 +1941,7 @@ var MessageList = react.forwardRef(function MessageList2({
|
|
|
1822
1941
|
messageElements.forEach((el) => observer.observe(el));
|
|
1823
1942
|
return () => observer.disconnect();
|
|
1824
1943
|
}, [messages, onMessageVisible]);
|
|
1825
|
-
const handleScroll =
|
|
1944
|
+
const handleScroll = React4.useCallback(() => {
|
|
1826
1945
|
if (!containerRef.current || !hasMore || isLoading) return;
|
|
1827
1946
|
if (containerRef.current.scrollTop < 50) {
|
|
1828
1947
|
onLoadMore();
|
|
@@ -1842,22 +1961,30 @@ var MessageList = react.forwardRef(function MessageList2({
|
|
|
1842
1961
|
/* @__PURE__ */ jsxRuntime.jsx("p", { style: styles6.emptyTitle, children: "No messages yet" }),
|
|
1843
1962
|
/* @__PURE__ */ jsxRuntime.jsx("p", { style: styles6.emptySubtitle, children: "Start the conversation about this study" })
|
|
1844
1963
|
] }),
|
|
1845
|
-
messages.map((message) =>
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1964
|
+
messages.map((message, index) => {
|
|
1965
|
+
const currentBucket = dayBucketKey(message.insertedAt);
|
|
1966
|
+
const prevBucket = index === 0 ? null : dayBucketKey(messages[index - 1].insertedAt);
|
|
1967
|
+
const showDivider = currentBucket && currentBucket !== prevBucket;
|
|
1968
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(React4__default.default.Fragment, { children: [
|
|
1969
|
+
showDivider && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles6.dayDivider, role: "separator", "aria-label": "Date", children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles6.dayDividerPill, children: formatDayDivider(message.insertedAt) }) }),
|
|
1970
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { "data-message-id": message.id, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1971
|
+
MessageBubble,
|
|
1972
|
+
{
|
|
1973
|
+
message,
|
|
1974
|
+
isOwn: message.senderId === currentUserId,
|
|
1975
|
+
participants,
|
|
1976
|
+
currentUserId,
|
|
1977
|
+
showSeenBy,
|
|
1978
|
+
onDeepLinkClick,
|
|
1979
|
+
onReply,
|
|
1980
|
+
onPin,
|
|
1981
|
+
onUnpin,
|
|
1982
|
+
onCopy,
|
|
1983
|
+
pinDisabled
|
|
1984
|
+
}
|
|
1985
|
+
) })
|
|
1986
|
+
] }, message.id);
|
|
1987
|
+
}),
|
|
1861
1988
|
typingUsers.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles6.typingIndicator, children: [
|
|
1862
1989
|
typingUsers.map((t) => t.userName ?? "Someone").join(", "),
|
|
1863
1990
|
typingUsers.length === 1 ? " is " : " are ",
|
|
@@ -1883,18 +2010,19 @@ var styles6 = {
|
|
|
1883
2010
|
loadingMore: {
|
|
1884
2011
|
textAlign: "center",
|
|
1885
2012
|
padding: "12px",
|
|
1886
|
-
fontSize: "
|
|
1887
|
-
color: "#
|
|
2013
|
+
fontSize: "14px",
|
|
2014
|
+
color: "#6b7280"
|
|
1888
2015
|
},
|
|
1889
2016
|
loadMoreButton: {
|
|
1890
2017
|
display: "block",
|
|
1891
2018
|
margin: "0 auto 12px",
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
2019
|
+
minHeight: "36px",
|
|
2020
|
+
padding: "8px 18px",
|
|
2021
|
+
fontSize: "13px",
|
|
2022
|
+
color: "#374151",
|
|
1895
2023
|
backgroundColor: "transparent",
|
|
1896
|
-
border: "1px solid #
|
|
1897
|
-
borderRadius: "
|
|
2024
|
+
border: "1px solid #d1d5db",
|
|
2025
|
+
borderRadius: "18px",
|
|
1898
2026
|
cursor: "pointer"
|
|
1899
2027
|
},
|
|
1900
2028
|
empty: {
|
|
@@ -1903,22 +2031,38 @@ var styles6 = {
|
|
|
1903
2031
|
alignItems: "center",
|
|
1904
2032
|
justifyContent: "center",
|
|
1905
2033
|
padding: "48px 16px",
|
|
1906
|
-
color: "#
|
|
2034
|
+
color: "#6b7280"
|
|
1907
2035
|
},
|
|
1908
2036
|
emptyTitle: {
|
|
1909
|
-
fontSize: "
|
|
1910
|
-
fontWeight:
|
|
2037
|
+
fontSize: "16px",
|
|
2038
|
+
fontWeight: 600,
|
|
2039
|
+
color: "#374151",
|
|
1911
2040
|
margin: "0 0 4px"
|
|
1912
2041
|
},
|
|
1913
2042
|
emptySubtitle: {
|
|
1914
|
-
fontSize: "
|
|
2043
|
+
fontSize: "14px",
|
|
1915
2044
|
margin: 0
|
|
1916
2045
|
},
|
|
1917
2046
|
typingIndicator: {
|
|
1918
2047
|
padding: "4px 16px 8px",
|
|
1919
|
-
fontSize: "
|
|
1920
|
-
color: "#
|
|
2048
|
+
fontSize: "13px",
|
|
2049
|
+
color: "#6b7280",
|
|
1921
2050
|
fontStyle: "italic"
|
|
2051
|
+
},
|
|
2052
|
+
dayDivider: {
|
|
2053
|
+
display: "flex",
|
|
2054
|
+
justifyContent: "center",
|
|
2055
|
+
margin: "12px 0 8px"
|
|
2056
|
+
},
|
|
2057
|
+
dayDividerPill: {
|
|
2058
|
+
display: "inline-block",
|
|
2059
|
+
padding: "5px 14px",
|
|
2060
|
+
fontSize: "13px",
|
|
2061
|
+
fontWeight: 500,
|
|
2062
|
+
color: "#334155",
|
|
2063
|
+
backgroundColor: "#e2e8f0",
|
|
2064
|
+
borderRadius: "12px",
|
|
2065
|
+
letterSpacing: "0.2px"
|
|
1922
2066
|
}
|
|
1923
2067
|
};
|
|
1924
2068
|
function ReplyPreview({ message, onCancel, className }) {
|
|
@@ -2013,6 +2157,48 @@ var styles7 = {
|
|
|
2013
2157
|
flexShrink: 0
|
|
2014
2158
|
}
|
|
2015
2159
|
};
|
|
2160
|
+
function AttachIcon({ size = 20, color = "currentColor" }) {
|
|
2161
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2162
|
+
"svg",
|
|
2163
|
+
{
|
|
2164
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
2165
|
+
width: size,
|
|
2166
|
+
height: size,
|
|
2167
|
+
viewBox: "0 0 24 24",
|
|
2168
|
+
fill: color,
|
|
2169
|
+
"aria-hidden": "true",
|
|
2170
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z" })
|
|
2171
|
+
}
|
|
2172
|
+
);
|
|
2173
|
+
}
|
|
2174
|
+
function SendIcon({ size = 20, color = "currentColor" }) {
|
|
2175
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2176
|
+
"svg",
|
|
2177
|
+
{
|
|
2178
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
2179
|
+
width: size,
|
|
2180
|
+
height: size,
|
|
2181
|
+
viewBox: "0 0 24 24",
|
|
2182
|
+
fill: color,
|
|
2183
|
+
"aria-hidden": "true",
|
|
2184
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" })
|
|
2185
|
+
}
|
|
2186
|
+
);
|
|
2187
|
+
}
|
|
2188
|
+
function CloseIcon({ size = 18, color = "currentColor" }) {
|
|
2189
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2190
|
+
"svg",
|
|
2191
|
+
{
|
|
2192
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
2193
|
+
width: size,
|
|
2194
|
+
height: size,
|
|
2195
|
+
viewBox: "0 0 24 24",
|
|
2196
|
+
fill: color,
|
|
2197
|
+
"aria-hidden": "true",
|
|
2198
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" })
|
|
2199
|
+
}
|
|
2200
|
+
);
|
|
2201
|
+
}
|
|
2016
2202
|
function MessageInput({
|
|
2017
2203
|
onSendText,
|
|
2018
2204
|
// MIC DISABLED — onSendAudio still accepted but unused.
|
|
@@ -2025,14 +2211,14 @@ function MessageInput({
|
|
|
2025
2211
|
placeholder = "Type a message...",
|
|
2026
2212
|
className
|
|
2027
2213
|
}) {
|
|
2028
|
-
const [text, setText] =
|
|
2029
|
-
const [isSending, setIsSending] =
|
|
2030
|
-
const [fileError, setFileError] =
|
|
2031
|
-
const fileInputRef =
|
|
2032
|
-
const typingTimeoutRef =
|
|
2033
|
-
const isTypingRef =
|
|
2034
|
-
const sendingRef =
|
|
2035
|
-
const handleTyping =
|
|
2214
|
+
const [text, setText] = React4.useState("");
|
|
2215
|
+
const [isSending, setIsSending] = React4.useState(false);
|
|
2216
|
+
const [fileError, setFileError] = React4.useState(null);
|
|
2217
|
+
const fileInputRef = React4.useRef(null);
|
|
2218
|
+
const typingTimeoutRef = React4.useRef(null);
|
|
2219
|
+
const isTypingRef = React4.useRef(false);
|
|
2220
|
+
const sendingRef = React4.useRef(false);
|
|
2221
|
+
const handleTyping = React4.useCallback(() => {
|
|
2036
2222
|
if (!isTypingRef.current) {
|
|
2037
2223
|
isTypingRef.current = true;
|
|
2038
2224
|
onTyping(true);
|
|
@@ -2045,12 +2231,12 @@ function MessageInput({
|
|
|
2045
2231
|
onTyping(false);
|
|
2046
2232
|
}, 2e3);
|
|
2047
2233
|
}, [onTyping]);
|
|
2048
|
-
|
|
2234
|
+
React4.useEffect(() => {
|
|
2049
2235
|
return () => {
|
|
2050
2236
|
if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current);
|
|
2051
2237
|
};
|
|
2052
2238
|
}, []);
|
|
2053
|
-
const handleSendText =
|
|
2239
|
+
const handleSendText = React4.useCallback(async () => {
|
|
2054
2240
|
const trimmed = text.trim();
|
|
2055
2241
|
if (!trimmed || sendingRef.current) return;
|
|
2056
2242
|
sendingRef.current = true;
|
|
@@ -2071,7 +2257,7 @@ function MessageInput({
|
|
|
2071
2257
|
setIsSending(false);
|
|
2072
2258
|
}
|
|
2073
2259
|
}, [text, onSendText, onTyping]);
|
|
2074
|
-
const handleKeyDown =
|
|
2260
|
+
const handleKeyDown = React4.useCallback(
|
|
2075
2261
|
(e) => {
|
|
2076
2262
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
2077
2263
|
e.preventDefault();
|
|
@@ -2080,7 +2266,7 @@ function MessageInput({
|
|
|
2080
2266
|
},
|
|
2081
2267
|
[handleSendText]
|
|
2082
2268
|
);
|
|
2083
|
-
const handleFileSelect =
|
|
2269
|
+
const handleFileSelect = React4.useCallback(
|
|
2084
2270
|
async (e) => {
|
|
2085
2271
|
const file = e.target.files?.[0];
|
|
2086
2272
|
if (!file) return;
|
|
@@ -2101,7 +2287,19 @@ function MessageInput({
|
|
|
2101
2287
|
);
|
|
2102
2288
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: styles8.container, children: [
|
|
2103
2289
|
replyTo && onCancelReply && /* @__PURE__ */ jsxRuntime.jsx(ReplyPreview, { message: replyTo, onCancel: onCancelReply }),
|
|
2104
|
-
fileError && /* @__PURE__ */ jsxRuntime.
|
|
2290
|
+
fileError && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles8.error, role: "alert", children: [
|
|
2291
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: styles8.errorText, children: fileError }),
|
|
2292
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2293
|
+
"button",
|
|
2294
|
+
{
|
|
2295
|
+
type: "button",
|
|
2296
|
+
onClick: () => setFileError(null),
|
|
2297
|
+
"aria-label": "Dismiss error",
|
|
2298
|
+
style: styles8.errorDismiss,
|
|
2299
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, { size: 16 })
|
|
2300
|
+
}
|
|
2301
|
+
)
|
|
2302
|
+
] }),
|
|
2105
2303
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles8.inputBar, children: [
|
|
2106
2304
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2107
2305
|
"button",
|
|
@@ -2109,9 +2307,10 @@ function MessageInput({
|
|
|
2109
2307
|
onClick: () => fileInputRef.current?.click(),
|
|
2110
2308
|
disabled: disabled || isSending,
|
|
2111
2309
|
style: styles8.iconButton,
|
|
2310
|
+
"aria-label": "Attach file",
|
|
2112
2311
|
title: "Attach file",
|
|
2113
2312
|
type: "button",
|
|
2114
|
-
children: "
|
|
2313
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AttachIcon, { size: 22, color: "#374151" })
|
|
2115
2314
|
}
|
|
2116
2315
|
),
|
|
2117
2316
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2144,12 +2343,14 @@ function MessageInput({
|
|
|
2144
2343
|
{
|
|
2145
2344
|
onClick: handleSendText,
|
|
2146
2345
|
disabled: disabled || isSending || !text.trim(),
|
|
2346
|
+
"aria-label": "Send message",
|
|
2347
|
+
title: "Send message",
|
|
2147
2348
|
style: {
|
|
2148
2349
|
...styles8.sendButton,
|
|
2149
2350
|
opacity: text.trim() ? 1 : 0.4
|
|
2150
2351
|
},
|
|
2151
2352
|
type: "button",
|
|
2152
|
-
children: "
|
|
2353
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(SendIcon, { size: 20, color: "#ffffff" })
|
|
2153
2354
|
}
|
|
2154
2355
|
)
|
|
2155
2356
|
] })
|
|
@@ -2161,24 +2362,45 @@ var styles8 = {
|
|
|
2161
2362
|
backgroundColor: "#ffffff"
|
|
2162
2363
|
},
|
|
2163
2364
|
error: {
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2365
|
+
display: "flex",
|
|
2366
|
+
alignItems: "center",
|
|
2367
|
+
justifyContent: "space-between",
|
|
2368
|
+
gap: "8px",
|
|
2369
|
+
padding: "8px 12px 8px 16px",
|
|
2370
|
+
fontSize: "13px",
|
|
2371
|
+
color: "#b91c1c",
|
|
2372
|
+
backgroundColor: "#fef2f2",
|
|
2373
|
+
borderTop: "1px solid #fecaca"
|
|
2374
|
+
},
|
|
2375
|
+
errorText: {
|
|
2376
|
+
flex: 1,
|
|
2377
|
+
minWidth: 0
|
|
2378
|
+
},
|
|
2379
|
+
errorDismiss: {
|
|
2380
|
+
width: "28px",
|
|
2381
|
+
height: "28px",
|
|
2382
|
+
display: "flex",
|
|
2383
|
+
alignItems: "center",
|
|
2384
|
+
justifyContent: "center",
|
|
2385
|
+
backgroundColor: "transparent",
|
|
2386
|
+
border: "none",
|
|
2387
|
+
color: "#b91c1c",
|
|
2388
|
+
borderRadius: "4px",
|
|
2389
|
+
cursor: "pointer",
|
|
2390
|
+
flexShrink: 0
|
|
2168
2391
|
},
|
|
2169
2392
|
inputBar: {
|
|
2170
2393
|
display: "flex",
|
|
2171
2394
|
alignItems: "flex-end",
|
|
2172
|
-
gap: "
|
|
2173
|
-
padding: "
|
|
2395
|
+
gap: "6px",
|
|
2396
|
+
padding: "10px 12px"
|
|
2174
2397
|
},
|
|
2175
2398
|
iconButton: {
|
|
2176
|
-
width: "
|
|
2177
|
-
height: "
|
|
2399
|
+
width: "44px",
|
|
2400
|
+
height: "44px",
|
|
2178
2401
|
display: "flex",
|
|
2179
2402
|
alignItems: "center",
|
|
2180
2403
|
justifyContent: "center",
|
|
2181
|
-
fontSize: "18px",
|
|
2182
2404
|
backgroundColor: "transparent",
|
|
2183
2405
|
border: "none",
|
|
2184
2406
|
borderRadius: "50%",
|
|
@@ -2187,23 +2409,23 @@ var styles8 = {
|
|
|
2187
2409
|
},
|
|
2188
2410
|
textarea: {
|
|
2189
2411
|
flex: 1,
|
|
2190
|
-
padding: "
|
|
2191
|
-
fontSize: "
|
|
2192
|
-
lineHeight: "1.
|
|
2193
|
-
border: "1px solid #
|
|
2194
|
-
borderRadius: "
|
|
2412
|
+
padding: "10px 14px",
|
|
2413
|
+
fontSize: "15px",
|
|
2414
|
+
lineHeight: "1.45",
|
|
2415
|
+
border: "1px solid #d1d5db",
|
|
2416
|
+
borderRadius: "22px",
|
|
2195
2417
|
resize: "none",
|
|
2196
2418
|
outline: "none",
|
|
2197
2419
|
fontFamily: "inherit",
|
|
2198
|
-
maxHeight: "120px"
|
|
2420
|
+
maxHeight: "120px",
|
|
2421
|
+
minHeight: "44px"
|
|
2199
2422
|
},
|
|
2200
2423
|
sendButton: {
|
|
2201
|
-
width: "
|
|
2202
|
-
height: "
|
|
2424
|
+
width: "44px",
|
|
2425
|
+
height: "44px",
|
|
2203
2426
|
display: "flex",
|
|
2204
2427
|
alignItems: "center",
|
|
2205
2428
|
justifyContent: "center",
|
|
2206
|
-
fontSize: "18px",
|
|
2207
2429
|
backgroundColor: "#2563eb",
|
|
2208
2430
|
color: "#ffffff",
|
|
2209
2431
|
border: "none",
|
|
@@ -2376,13 +2598,13 @@ function ChannelSettings({
|
|
|
2376
2598
|
onClose,
|
|
2377
2599
|
className
|
|
2378
2600
|
}) {
|
|
2379
|
-
const [editingName, setEditingName] =
|
|
2380
|
-
const [nameValue, setNameValue] =
|
|
2381
|
-
const [showInvite, setShowInvite] =
|
|
2382
|
-
const [inviteId, setInviteId] =
|
|
2383
|
-
const [inviteName, setInviteName] =
|
|
2384
|
-
const [inviteRole, setInviteRole] =
|
|
2385
|
-
const [confirmDelete, setConfirmDelete] =
|
|
2601
|
+
const [editingName, setEditingName] = React4.useState(false);
|
|
2602
|
+
const [nameValue, setNameValue] = React4.useState(channelName);
|
|
2603
|
+
const [showInvite, setShowInvite] = React4.useState(false);
|
|
2604
|
+
const [inviteId, setInviteId] = React4.useState("");
|
|
2605
|
+
const [inviteName, setInviteName] = React4.useState("");
|
|
2606
|
+
const [inviteRole, setInviteRole] = React4.useState("radiologist");
|
|
2607
|
+
const [confirmDelete, setConfirmDelete] = React4.useState(false);
|
|
2386
2608
|
const handleSaveName = async () => {
|
|
2387
2609
|
if (nameValue.trim() && nameValue !== channelName) {
|
|
2388
2610
|
await onUpdateChannel({ name: nameValue.trim() });
|
|
@@ -2673,7 +2895,7 @@ function PinnedMessagesBar({
|
|
|
2673
2895
|
onUnpin,
|
|
2674
2896
|
className
|
|
2675
2897
|
}) {
|
|
2676
|
-
const [index, setIndex] =
|
|
2898
|
+
const [index, setIndex] = React4.useState(0);
|
|
2677
2899
|
if (pinnedMessages.length === 0) return null;
|
|
2678
2900
|
const safeIndex = Math.min(index, pinnedMessages.length - 1);
|
|
2679
2901
|
const current = pinnedMessages[safeIndex];
|
|
@@ -2832,8 +3054,8 @@ function CollabPanel({
|
|
|
2832
3054
|
style
|
|
2833
3055
|
}) {
|
|
2834
3056
|
const { config } = useCollab();
|
|
2835
|
-
const [showSettings, setShowSettings] =
|
|
2836
|
-
const messageListRef =
|
|
3057
|
+
const [showSettings, setShowSettings] = React4.useState(false);
|
|
3058
|
+
const messageListRef = React4.useRef(null);
|
|
2837
3059
|
const {
|
|
2838
3060
|
conversation,
|
|
2839
3061
|
messages,
|
|
@@ -2994,6 +3216,34 @@ var panelStyles = {
|
|
|
2994
3216
|
margin: 0
|
|
2995
3217
|
}
|
|
2996
3218
|
};
|
|
3219
|
+
function MinimizeIcon({ size = 18, color = "currentColor" }) {
|
|
3220
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3221
|
+
"svg",
|
|
3222
|
+
{
|
|
3223
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3224
|
+
width: size,
|
|
3225
|
+
height: size,
|
|
3226
|
+
viewBox: "0 0 24 24",
|
|
3227
|
+
fill: color,
|
|
3228
|
+
"aria-hidden": "true",
|
|
3229
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 19h12v2H6z" })
|
|
3230
|
+
}
|
|
3231
|
+
);
|
|
3232
|
+
}
|
|
3233
|
+
function ExpandIcon({ size = 18, color = "currentColor" }) {
|
|
3234
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3235
|
+
"svg",
|
|
3236
|
+
{
|
|
3237
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3238
|
+
width: size,
|
|
3239
|
+
height: size,
|
|
3240
|
+
viewBox: "0 0 24 24",
|
|
3241
|
+
fill: color,
|
|
3242
|
+
"aria-hidden": "true",
|
|
3243
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 4h16v16H4V4zm2 4v10h12V8H6z" })
|
|
3244
|
+
}
|
|
3245
|
+
);
|
|
3246
|
+
}
|
|
2997
3247
|
function CollabPopup({
|
|
2998
3248
|
orderId,
|
|
2999
3249
|
patientData,
|
|
@@ -3005,17 +3255,17 @@ function CollabPopup({
|
|
|
3005
3255
|
height = 520,
|
|
3006
3256
|
className
|
|
3007
3257
|
}) {
|
|
3008
|
-
const [position, setPosition] =
|
|
3258
|
+
const [position, setPosition] = React4.useState(
|
|
3009
3259
|
initialPosition || {
|
|
3010
3260
|
x: window.innerWidth - width - 24,
|
|
3011
3261
|
y: Math.max(24, window.innerHeight - height - 104)
|
|
3012
3262
|
}
|
|
3013
3263
|
);
|
|
3014
|
-
const [isDragging, setIsDragging] =
|
|
3015
|
-
const [isMinimized, setIsMinimized] =
|
|
3016
|
-
const dragOffset =
|
|
3017
|
-
const containerRef =
|
|
3018
|
-
const handleMouseDown =
|
|
3264
|
+
const [isDragging, setIsDragging] = React4.useState(false);
|
|
3265
|
+
const [isMinimized, setIsMinimized] = React4.useState(false);
|
|
3266
|
+
const dragOffset = React4.useRef({ x: 0, y: 0 });
|
|
3267
|
+
const containerRef = React4.useRef(null);
|
|
3268
|
+
const handleMouseDown = React4.useCallback(
|
|
3019
3269
|
(e) => {
|
|
3020
3270
|
if (!e.target.closest("[data-drag-handle]")) return;
|
|
3021
3271
|
setIsDragging(true);
|
|
@@ -3027,7 +3277,7 @@ function CollabPopup({
|
|
|
3027
3277
|
},
|
|
3028
3278
|
[position]
|
|
3029
3279
|
);
|
|
3030
|
-
|
|
3280
|
+
React4.useEffect(() => {
|
|
3031
3281
|
if (!isOpen) return;
|
|
3032
3282
|
const handleKey = (e) => {
|
|
3033
3283
|
if (e.key === "Escape") {
|
|
@@ -3037,12 +3287,12 @@ function CollabPopup({
|
|
|
3037
3287
|
window.addEventListener("keydown", handleKey);
|
|
3038
3288
|
return () => window.removeEventListener("keydown", handleKey);
|
|
3039
3289
|
}, [isOpen, onClose]);
|
|
3040
|
-
|
|
3290
|
+
React4.useEffect(() => {
|
|
3041
3291
|
if (!isDragging) return;
|
|
3042
3292
|
const handleMouseMove = (e) => {
|
|
3043
3293
|
setPosition({
|
|
3044
3294
|
x: Math.max(0, Math.min(e.clientX - dragOffset.current.x, window.innerWidth - width)),
|
|
3045
|
-
y: Math.max(0, Math.min(e.clientY - dragOffset.current.y, window.innerHeight - (isMinimized ?
|
|
3295
|
+
y: Math.max(0, Math.min(e.clientY - dragOffset.current.y, window.innerHeight - (isMinimized ? 52 : height)))
|
|
3046
3296
|
});
|
|
3047
3297
|
};
|
|
3048
3298
|
const handleMouseUp = () => {
|
|
@@ -3066,7 +3316,7 @@ function CollabPopup({
|
|
|
3066
3316
|
left: position.x,
|
|
3067
3317
|
top: position.y,
|
|
3068
3318
|
width,
|
|
3069
|
-
height: isMinimized ?
|
|
3319
|
+
height: isMinimized ? 52 : height,
|
|
3070
3320
|
cursor: isDragging ? "grabbing" : "default"
|
|
3071
3321
|
},
|
|
3072
3322
|
onMouseDown: handleMouseDown,
|
|
@@ -3079,12 +3329,23 @@ function CollabPopup({
|
|
|
3079
3329
|
{
|
|
3080
3330
|
onClick: () => setIsMinimized(!isMinimized),
|
|
3081
3331
|
style: styles12.titleButton,
|
|
3332
|
+
"aria-label": isMinimized ? "Expand chat" : "Minimize chat",
|
|
3082
3333
|
title: isMinimized ? "Expand" : "Minimize",
|
|
3083
3334
|
type: "button",
|
|
3084
|
-
children: isMinimized ? "
|
|
3335
|
+
children: isMinimized ? /* @__PURE__ */ jsxRuntime.jsx(ExpandIcon, { size: 16, color: "#cbd5e1" }) : /* @__PURE__ */ jsxRuntime.jsx(MinimizeIcon, { size: 16, color: "#cbd5e1" })
|
|
3085
3336
|
}
|
|
3086
3337
|
),
|
|
3087
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3338
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3339
|
+
"button",
|
|
3340
|
+
{
|
|
3341
|
+
onClick: onClose,
|
|
3342
|
+
style: styles12.titleButton,
|
|
3343
|
+
"aria-label": "Close chat",
|
|
3344
|
+
title: "Close",
|
|
3345
|
+
type: "button",
|
|
3346
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, { size: 16, color: "#cbd5e1" })
|
|
3347
|
+
}
|
|
3348
|
+
)
|
|
3088
3349
|
] })
|
|
3089
3350
|
] }),
|
|
3090
3351
|
!isMinimized && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles12.panelWrapper, children: /* @__PURE__ */ jsxRuntime.jsx(CollabPanel, { orderId, patientData, participantIds }) })
|
|
@@ -3109,15 +3370,15 @@ var styles12 = {
|
|
|
3109
3370
|
display: "flex",
|
|
3110
3371
|
justifyContent: "space-between",
|
|
3111
3372
|
alignItems: "center",
|
|
3112
|
-
padding: "0
|
|
3113
|
-
height: "
|
|
3373
|
+
padding: "0 8px 0 14px",
|
|
3374
|
+
height: "52px",
|
|
3114
3375
|
backgroundColor: "#1e293b",
|
|
3115
3376
|
cursor: "grab",
|
|
3116
3377
|
userSelect: "none",
|
|
3117
3378
|
flexShrink: 0
|
|
3118
3379
|
},
|
|
3119
3380
|
titleText: {
|
|
3120
|
-
fontSize: "
|
|
3381
|
+
fontSize: "15px",
|
|
3121
3382
|
fontWeight: 500,
|
|
3122
3383
|
color: "#ffffff",
|
|
3123
3384
|
overflow: "hidden",
|
|
@@ -3130,17 +3391,16 @@ var styles12 = {
|
|
|
3130
3391
|
flexShrink: 0
|
|
3131
3392
|
},
|
|
3132
3393
|
titleButton: {
|
|
3133
|
-
width: "
|
|
3134
|
-
height: "
|
|
3394
|
+
width: "40px",
|
|
3395
|
+
height: "40px",
|
|
3135
3396
|
display: "flex",
|
|
3136
3397
|
alignItems: "center",
|
|
3137
3398
|
justifyContent: "center",
|
|
3138
3399
|
backgroundColor: "transparent",
|
|
3139
3400
|
border: "none",
|
|
3140
|
-
borderRadius: "
|
|
3401
|
+
borderRadius: "6px",
|
|
3141
3402
|
cursor: "pointer",
|
|
3142
|
-
|
|
3143
|
-
color: "#94a3b8"
|
|
3403
|
+
color: "#cbd5e1"
|
|
3144
3404
|
},
|
|
3145
3405
|
panelWrapper: {
|
|
3146
3406
|
flex: 1,
|
|
@@ -3160,25 +3420,25 @@ function useInlineCollab({
|
|
|
3160
3420
|
invalidatePreview,
|
|
3161
3421
|
createConversationWithMessage
|
|
3162
3422
|
} = useCollab();
|
|
3163
|
-
const [preview, setPreview] =
|
|
3164
|
-
const [messages, setMessages] =
|
|
3165
|
-
const [participants, setParticipants] =
|
|
3166
|
-
const [unreadCount, setUnreadCount] =
|
|
3167
|
-
const [isLoading, setIsLoading] =
|
|
3168
|
-
const [isSubscribed, setIsSubscribed] =
|
|
3169
|
-
const [error, setError] =
|
|
3170
|
-
const elementRef =
|
|
3171
|
-
const observerRef =
|
|
3172
|
-
const subscribedConversationIdRef =
|
|
3173
|
-
const trimToLimit =
|
|
3423
|
+
const [preview, setPreview] = React4.useState(null);
|
|
3424
|
+
const [messages, setMessages] = React4.useState([]);
|
|
3425
|
+
const [participants, setParticipants] = React4.useState([]);
|
|
3426
|
+
const [unreadCount, setUnreadCount] = React4.useState(0);
|
|
3427
|
+
const [isLoading, setIsLoading] = React4.useState(true);
|
|
3428
|
+
const [isSubscribed, setIsSubscribed] = React4.useState(false);
|
|
3429
|
+
const [error, setError] = React4.useState(null);
|
|
3430
|
+
const elementRef = React4.useRef(null);
|
|
3431
|
+
const observerRef = React4.useRef(null);
|
|
3432
|
+
const subscribedConversationIdRef = React4.useRef(null);
|
|
3433
|
+
const trimToLimit = React4.useCallback(
|
|
3174
3434
|
(msgs) => msgs.length > messageLimit ? msgs.slice(msgs.length - messageLimit) : msgs,
|
|
3175
3435
|
[messageLimit]
|
|
3176
3436
|
);
|
|
3177
|
-
const buildName =
|
|
3437
|
+
const buildName = React4.useCallback(
|
|
3178
3438
|
() => buildChannelName(patientData),
|
|
3179
3439
|
[patientData]
|
|
3180
3440
|
);
|
|
3181
|
-
|
|
3441
|
+
React4.useEffect(() => {
|
|
3182
3442
|
let cancelled = false;
|
|
3183
3443
|
const load = async () => {
|
|
3184
3444
|
setIsLoading(true);
|
|
@@ -3204,7 +3464,7 @@ function useInlineCollab({
|
|
|
3204
3464
|
cancelled = true;
|
|
3205
3465
|
};
|
|
3206
3466
|
}, [orderId, requestPreview, trimToLimit]);
|
|
3207
|
-
const subscribe =
|
|
3467
|
+
const subscribe = React4.useCallback(
|
|
3208
3468
|
(conversationId) => {
|
|
3209
3469
|
if (subscribedConversationIdRef.current === conversationId) return;
|
|
3210
3470
|
socket.joinConversation(conversationId, {
|
|
@@ -3228,7 +3488,7 @@ function useInlineCollab({
|
|
|
3228
3488
|
},
|
|
3229
3489
|
[socket, trimToLimit, config.userId]
|
|
3230
3490
|
);
|
|
3231
|
-
const unsubscribe =
|
|
3491
|
+
const unsubscribe = React4.useCallback(() => {
|
|
3232
3492
|
const convId = subscribedConversationIdRef.current;
|
|
3233
3493
|
if (convId) {
|
|
3234
3494
|
socket.leaveConversation(convId);
|
|
@@ -3236,7 +3496,7 @@ function useInlineCollab({
|
|
|
3236
3496
|
setIsSubscribed(false);
|
|
3237
3497
|
}
|
|
3238
3498
|
}, [socket]);
|
|
3239
|
-
const containerRef =
|
|
3499
|
+
const containerRef = React4.useCallback(
|
|
3240
3500
|
(element) => {
|
|
3241
3501
|
if (observerRef.current) {
|
|
3242
3502
|
observerRef.current.disconnect();
|
|
@@ -3262,18 +3522,18 @@ function useInlineCollab({
|
|
|
3262
3522
|
},
|
|
3263
3523
|
[preview, subscribe, unsubscribe]
|
|
3264
3524
|
);
|
|
3265
|
-
|
|
3525
|
+
React4.useEffect(() => {
|
|
3266
3526
|
return () => {
|
|
3267
3527
|
if (observerRef.current) observerRef.current.disconnect();
|
|
3268
3528
|
unsubscribe();
|
|
3269
3529
|
};
|
|
3270
3530
|
}, [unsubscribe]);
|
|
3271
|
-
|
|
3531
|
+
React4.useEffect(() => {
|
|
3272
3532
|
if (preview?.conversationId && elementRef.current && !observerRef.current) {
|
|
3273
3533
|
containerRef(elementRef.current);
|
|
3274
3534
|
}
|
|
3275
3535
|
}, [preview, containerRef]);
|
|
3276
|
-
const dispatchSend =
|
|
3536
|
+
const dispatchSend = React4.useCallback(
|
|
3277
3537
|
async (payload) => {
|
|
3278
3538
|
if (!preview) {
|
|
3279
3539
|
try {
|
|
@@ -3330,7 +3590,7 @@ function useInlineCollab({
|
|
|
3330
3590
|
config
|
|
3331
3591
|
]
|
|
3332
3592
|
);
|
|
3333
|
-
const sendMessage =
|
|
3593
|
+
const sendMessage = React4.useCallback(
|
|
3334
3594
|
async (body) => {
|
|
3335
3595
|
const trimmed = body.trim();
|
|
3336
3596
|
if (!trimmed) return;
|
|
@@ -3338,7 +3598,7 @@ function useInlineCollab({
|
|
|
3338
3598
|
},
|
|
3339
3599
|
[dispatchSend]
|
|
3340
3600
|
);
|
|
3341
|
-
const sendAudioMessage =
|
|
3601
|
+
const sendAudioMessage = React4.useCallback(
|
|
3342
3602
|
async (audioBlob, duration) => {
|
|
3343
3603
|
if (!config.onUploadFile) {
|
|
3344
3604
|
setError("File upload is not configured");
|
|
@@ -3451,10 +3711,10 @@ function RoleDot({ role }) {
|
|
|
3451
3711
|
return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles13.roleDot, backgroundColor: colors[role] } });
|
|
3452
3712
|
}
|
|
3453
3713
|
function InlineAudioPlayer({ url, duration }) {
|
|
3454
|
-
const audioRef =
|
|
3455
|
-
const [isPlaying, setIsPlaying] =
|
|
3456
|
-
const [currentTime, setCurrentTime] =
|
|
3457
|
-
const toggle =
|
|
3714
|
+
const audioRef = React4.useRef(null);
|
|
3715
|
+
const [isPlaying, setIsPlaying] = React4.useState(false);
|
|
3716
|
+
const [currentTime, setCurrentTime] = React4.useState(0);
|
|
3717
|
+
const toggle = React4.useCallback(
|
|
3458
3718
|
(e) => {
|
|
3459
3719
|
e.stopPropagation();
|
|
3460
3720
|
const audio = audioRef.current;
|
|
@@ -3468,7 +3728,7 @@ function InlineAudioPlayer({ url, duration }) {
|
|
|
3468
3728
|
},
|
|
3469
3729
|
[isPlaying]
|
|
3470
3730
|
);
|
|
3471
|
-
|
|
3731
|
+
React4.useEffect(() => {
|
|
3472
3732
|
const audio = audioRef.current;
|
|
3473
3733
|
if (!audio) return;
|
|
3474
3734
|
const onPlay = () => setIsPlaying(true);
|
|
@@ -3544,10 +3804,10 @@ function InlineInputBar({
|
|
|
3544
3804
|
showExpand,
|
|
3545
3805
|
onExpand
|
|
3546
3806
|
}) {
|
|
3547
|
-
const [text, setText] =
|
|
3548
|
-
const [isSending, setIsSending] =
|
|
3549
|
-
const inputRef =
|
|
3550
|
-
const handleSend =
|
|
3807
|
+
const [text, setText] = React4.useState("");
|
|
3808
|
+
const [isSending, setIsSending] = React4.useState(false);
|
|
3809
|
+
const inputRef = React4.useRef(null);
|
|
3810
|
+
const handleSend = React4.useCallback(async () => {
|
|
3551
3811
|
const trimmed = text.trim();
|
|
3552
3812
|
if (!trimmed || isSending) return;
|
|
3553
3813
|
setIsSending(true);
|
|
@@ -3559,7 +3819,7 @@ function InlineInputBar({
|
|
|
3559
3819
|
setIsSending(false);
|
|
3560
3820
|
}
|
|
3561
3821
|
}, [text, isSending, onSend]);
|
|
3562
|
-
const handleKeyDown =
|
|
3822
|
+
const handleKeyDown = React4.useCallback(
|
|
3563
3823
|
(e) => {
|
|
3564
3824
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
3565
3825
|
e.preventDefault();
|
|
@@ -3792,10 +4052,10 @@ var styles13 = {
|
|
|
3792
4052
|
function useConversationList(options) {
|
|
3793
4053
|
const enabled = options?.enabled ?? true;
|
|
3794
4054
|
const { fetchConversationList, config, totalUnread: serverTotalUnread } = useCollab();
|
|
3795
|
-
const [conversations, setConversations] =
|
|
3796
|
-
const [isLoading, setIsLoading] =
|
|
3797
|
-
const [error, setError] =
|
|
3798
|
-
const silentRefresh =
|
|
4055
|
+
const [conversations, setConversations] = React4.useState([]);
|
|
4056
|
+
const [isLoading, setIsLoading] = React4.useState(enabled);
|
|
4057
|
+
const [error, setError] = React4.useState(null);
|
|
4058
|
+
const silentRefresh = React4.useCallback(async () => {
|
|
3799
4059
|
try {
|
|
3800
4060
|
const list = await fetchConversationList();
|
|
3801
4061
|
setConversations(list);
|
|
@@ -3807,7 +4067,7 @@ function useConversationList(options) {
|
|
|
3807
4067
|
});
|
|
3808
4068
|
}
|
|
3809
4069
|
}, [fetchConversationList, config]);
|
|
3810
|
-
const refresh =
|
|
4070
|
+
const refresh = React4.useCallback(async () => {
|
|
3811
4071
|
setIsLoading(true);
|
|
3812
4072
|
setError(null);
|
|
3813
4073
|
try {
|
|
@@ -3825,12 +4085,12 @@ function useConversationList(options) {
|
|
|
3825
4085
|
setIsLoading(false);
|
|
3826
4086
|
}
|
|
3827
4087
|
}, [fetchConversationList, config]);
|
|
3828
|
-
|
|
4088
|
+
React4.useEffect(() => {
|
|
3829
4089
|
if (!enabled) return;
|
|
3830
4090
|
refresh();
|
|
3831
4091
|
}, [enabled, refresh]);
|
|
3832
|
-
const hasHandledInitialUnreadRef =
|
|
3833
|
-
|
|
4092
|
+
const hasHandledInitialUnreadRef = React4.useRef(false);
|
|
4093
|
+
React4.useEffect(() => {
|
|
3834
4094
|
if (!enabled) return;
|
|
3835
4095
|
if (!hasHandledInitialUnreadRef.current) {
|
|
3836
4096
|
hasHandledInitialUnreadRef.current = true;
|
|
@@ -3838,7 +4098,7 @@ function useConversationList(options) {
|
|
|
3838
4098
|
}
|
|
3839
4099
|
silentRefresh();
|
|
3840
4100
|
}, [serverTotalUnread, enabled]);
|
|
3841
|
-
const filter =
|
|
4101
|
+
const filter = React4.useCallback(
|
|
3842
4102
|
(query) => {
|
|
3843
4103
|
const q = query.trim().toLowerCase();
|
|
3844
4104
|
if (!q) return conversations;
|
|
@@ -3903,7 +4163,7 @@ function ConversationListItem({
|
|
|
3903
4163
|
...styles14.time,
|
|
3904
4164
|
...hasUnread ? styles14.timeUnread : {}
|
|
3905
4165
|
},
|
|
3906
|
-
children:
|
|
4166
|
+
children: formatInboxTimestamp(item.lastActivityAt)
|
|
3907
4167
|
}
|
|
3908
4168
|
)
|
|
3909
4169
|
] }),
|
|
@@ -3946,23 +4206,6 @@ function renderLastMessagePreview(message) {
|
|
|
3946
4206
|
}
|
|
3947
4207
|
}
|
|
3948
4208
|
}
|
|
3949
|
-
function formatRelativeTime(iso) {
|
|
3950
|
-
try {
|
|
3951
|
-
const date = new Date(iso);
|
|
3952
|
-
const now = /* @__PURE__ */ new Date();
|
|
3953
|
-
const diffMs = now.getTime() - date.getTime();
|
|
3954
|
-
const diffMin = Math.floor(diffMs / 6e4);
|
|
3955
|
-
const diffHr = Math.floor(diffMs / 36e5);
|
|
3956
|
-
const diffDay = Math.floor(diffMs / 864e5);
|
|
3957
|
-
if (diffMin < 1) return "now";
|
|
3958
|
-
if (diffMin < 60) return `${diffMin}m`;
|
|
3959
|
-
if (diffHr < 24) return `${diffHr}h`;
|
|
3960
|
-
if (diffDay < 7) return `${diffDay}d`;
|
|
3961
|
-
return date.toLocaleDateString([], { month: "short", day: "numeric" });
|
|
3962
|
-
} catch {
|
|
3963
|
-
return "";
|
|
3964
|
-
}
|
|
3965
|
-
}
|
|
3966
4209
|
var styles14 = {
|
|
3967
4210
|
container: {
|
|
3968
4211
|
display: "flex",
|
|
@@ -4028,7 +4271,7 @@ var styles14 = {
|
|
|
4028
4271
|
gap: "8px"
|
|
4029
4272
|
},
|
|
4030
4273
|
name: {
|
|
4031
|
-
fontSize: "
|
|
4274
|
+
fontSize: "15px",
|
|
4032
4275
|
fontWeight: 500,
|
|
4033
4276
|
color: "#111827",
|
|
4034
4277
|
overflow: "hidden",
|
|
@@ -4041,8 +4284,8 @@ var styles14 = {
|
|
|
4041
4284
|
fontWeight: 700
|
|
4042
4285
|
},
|
|
4043
4286
|
time: {
|
|
4044
|
-
fontSize: "
|
|
4045
|
-
color: "#
|
|
4287
|
+
fontSize: "12px",
|
|
4288
|
+
color: "#6b7280",
|
|
4046
4289
|
flexShrink: 0
|
|
4047
4290
|
},
|
|
4048
4291
|
timeUnread: {
|
|
@@ -4056,8 +4299,8 @@ var styles14 = {
|
|
|
4056
4299
|
gap: "8px"
|
|
4057
4300
|
},
|
|
4058
4301
|
preview: {
|
|
4059
|
-
fontSize: "
|
|
4060
|
-
color: "#
|
|
4302
|
+
fontSize: "14px",
|
|
4303
|
+
color: "#4b5563",
|
|
4061
4304
|
overflow: "hidden",
|
|
4062
4305
|
textOverflow: "ellipsis",
|
|
4063
4306
|
whiteSpace: "nowrap",
|
|
@@ -4092,9 +4335,9 @@ function ConversationList({
|
|
|
4092
4335
|
title = "Conversations",
|
|
4093
4336
|
className
|
|
4094
4337
|
}) {
|
|
4095
|
-
const [query, setQuery] =
|
|
4096
|
-
const [showUnreadOnly, setShowUnreadOnly] =
|
|
4097
|
-
const filtered =
|
|
4338
|
+
const [query, setQuery] = React4.useState("");
|
|
4339
|
+
const [showUnreadOnly, setShowUnreadOnly] = React4.useState(false);
|
|
4340
|
+
const filtered = React4.useMemo(() => {
|
|
4098
4341
|
let result = conversations;
|
|
4099
4342
|
if (showUnreadOnly) {
|
|
4100
4343
|
result = result.filter((c) => c.unreadCount > 0);
|
|
@@ -4271,7 +4514,7 @@ function CollabInbox({
|
|
|
4271
4514
|
style
|
|
4272
4515
|
}) {
|
|
4273
4516
|
const { conversations, isLoading, error } = useConversationList();
|
|
4274
|
-
const [selectedId, setSelectedId] =
|
|
4517
|
+
const [selectedId, setSelectedId] = React4.useState(initialConversationId ?? null);
|
|
4275
4518
|
const selected = conversations.find((c) => c.id === selectedId) || null;
|
|
4276
4519
|
const handleSelect = (item) => {
|
|
4277
4520
|
setSelectedId(item.id);
|
|
@@ -4373,12 +4616,12 @@ function useMessages({
|
|
|
4373
4616
|
initialLimit = MESSAGES_PAGE_SIZE
|
|
4374
4617
|
}) {
|
|
4375
4618
|
const { fetchMessages, config } = useCollab();
|
|
4376
|
-
const [messages, setMessages] =
|
|
4377
|
-
const [isLoading, setIsLoading] =
|
|
4378
|
-
const [hasMore, setHasMore] =
|
|
4379
|
-
const [error, setError] =
|
|
4380
|
-
const loadedRef =
|
|
4381
|
-
|
|
4619
|
+
const [messages, setMessages] = React4.useState([]);
|
|
4620
|
+
const [isLoading, setIsLoading] = React4.useState(false);
|
|
4621
|
+
const [hasMore, setHasMore] = React4.useState(true);
|
|
4622
|
+
const [error, setError] = React4.useState(null);
|
|
4623
|
+
const loadedRef = React4.useRef(null);
|
|
4624
|
+
React4.useEffect(() => {
|
|
4382
4625
|
if (!conversationId) {
|
|
4383
4626
|
setMessages([]);
|
|
4384
4627
|
setHasMore(false);
|
|
@@ -4414,7 +4657,7 @@ function useMessages({
|
|
|
4414
4657
|
cancelled = true;
|
|
4415
4658
|
};
|
|
4416
4659
|
}, [conversationId, initialLimit, fetchMessages, config]);
|
|
4417
|
-
const loadMore =
|
|
4660
|
+
const loadMore = React4.useCallback(async () => {
|
|
4418
4661
|
if (!conversationId || !hasMore || isLoading) return;
|
|
4419
4662
|
const oldestId = messages[0]?.id;
|
|
4420
4663
|
setIsLoading(true);
|
|
@@ -4435,17 +4678,17 @@ function useMessages({
|
|
|
4435
4678
|
setIsLoading(false);
|
|
4436
4679
|
}
|
|
4437
4680
|
}, [conversationId, hasMore, isLoading, messages, initialLimit, fetchMessages, config]);
|
|
4438
|
-
const addMessage =
|
|
4681
|
+
const addMessage = React4.useCallback((message) => {
|
|
4439
4682
|
setMessages((prev) => [...prev, message]);
|
|
4440
4683
|
}, []);
|
|
4441
|
-
const updateMessageReadBy =
|
|
4684
|
+
const updateMessageReadBy = React4.useCallback((messageId, userId) => {
|
|
4442
4685
|
setMessages(
|
|
4443
4686
|
(prev) => prev.map(
|
|
4444
4687
|
(msg) => msg.id === messageId && !(msg.readBy ?? []).includes(userId) ? { ...msg, readBy: [...msg.readBy ?? [], userId] } : msg
|
|
4445
4688
|
)
|
|
4446
4689
|
);
|
|
4447
4690
|
}, []);
|
|
4448
|
-
const reset =
|
|
4691
|
+
const reset = React4.useCallback(() => {
|
|
4449
4692
|
setMessages([]);
|
|
4450
4693
|
setHasMore(true);
|
|
4451
4694
|
setError(null);
|
|
@@ -4463,17 +4706,17 @@ function useMessages({
|
|
|
4463
4706
|
};
|
|
4464
4707
|
}
|
|
4465
4708
|
function useAudioRecorder() {
|
|
4466
|
-
const [isRecording, setIsRecording] =
|
|
4467
|
-
const [duration, setDuration] =
|
|
4468
|
-
const [error, setError] =
|
|
4469
|
-
const mediaRecorderRef =
|
|
4470
|
-
const chunksRef =
|
|
4471
|
-
const streamRef =
|
|
4472
|
-
const timerRef =
|
|
4473
|
-
const startTimeRef =
|
|
4474
|
-
const resolveRef =
|
|
4709
|
+
const [isRecording, setIsRecording] = React4.useState(false);
|
|
4710
|
+
const [duration, setDuration] = React4.useState(0);
|
|
4711
|
+
const [error, setError] = React4.useState(null);
|
|
4712
|
+
const mediaRecorderRef = React4.useRef(null);
|
|
4713
|
+
const chunksRef = React4.useRef([]);
|
|
4714
|
+
const streamRef = React4.useRef(null);
|
|
4715
|
+
const timerRef = React4.useRef(null);
|
|
4716
|
+
const startTimeRef = React4.useRef(0);
|
|
4717
|
+
const resolveRef = React4.useRef(null);
|
|
4475
4718
|
const isSupported = typeof window !== "undefined" && typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && !!window.MediaRecorder;
|
|
4476
|
-
const cleanup =
|
|
4719
|
+
const cleanup = React4.useCallback(() => {
|
|
4477
4720
|
if (timerRef.current) {
|
|
4478
4721
|
clearInterval(timerRef.current);
|
|
4479
4722
|
timerRef.current = null;
|
|
@@ -4487,7 +4730,7 @@ function useAudioRecorder() {
|
|
|
4487
4730
|
setIsRecording(false);
|
|
4488
4731
|
setDuration(0);
|
|
4489
4732
|
}, []);
|
|
4490
|
-
const start =
|
|
4733
|
+
const start = React4.useCallback(async () => {
|
|
4491
4734
|
if (!isSupported) {
|
|
4492
4735
|
setError("Audio recording is not supported in this browser");
|
|
4493
4736
|
return;
|
|
@@ -4534,7 +4777,7 @@ function useAudioRecorder() {
|
|
|
4534
4777
|
cleanup();
|
|
4535
4778
|
}
|
|
4536
4779
|
}, [isSupported, cleanup]);
|
|
4537
|
-
const stop =
|
|
4780
|
+
const stop = React4.useCallback(async () => {
|
|
4538
4781
|
return new Promise((resolve) => {
|
|
4539
4782
|
if (!mediaRecorderRef.current || mediaRecorderRef.current.state === "inactive") {
|
|
4540
4783
|
resolve(null);
|
|
@@ -4544,7 +4787,7 @@ function useAudioRecorder() {
|
|
|
4544
4787
|
mediaRecorderRef.current.stop();
|
|
4545
4788
|
});
|
|
4546
4789
|
}, []);
|
|
4547
|
-
const cancel =
|
|
4790
|
+
const cancel = React4.useCallback(() => {
|
|
4548
4791
|
if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
|
|
4549
4792
|
mediaRecorderRef.current.onstop = null;
|
|
4550
4793
|
mediaRecorderRef.current.stop();
|
|
@@ -4565,19 +4808,19 @@ function useAudioRecorder() {
|
|
|
4565
4808
|
}
|
|
4566
4809
|
function useUnreadCount() {
|
|
4567
4810
|
const { socket, totalUnread } = useCollab();
|
|
4568
|
-
const [counts, setCounts] =
|
|
4569
|
-
|
|
4811
|
+
const [counts, setCounts] = React4.useState({});
|
|
4812
|
+
React4.useEffect(() => {
|
|
4570
4813
|
socket.onUnreadCountUpdate((serverCounts) => {
|
|
4571
4814
|
setCounts(serverCounts);
|
|
4572
4815
|
});
|
|
4573
4816
|
}, [socket]);
|
|
4574
|
-
const getCountForConversation =
|
|
4817
|
+
const getCountForConversation = React4.useCallback(
|
|
4575
4818
|
(conversationId) => {
|
|
4576
4819
|
return counts[conversationId] ?? 0;
|
|
4577
4820
|
},
|
|
4578
4821
|
[counts]
|
|
4579
4822
|
);
|
|
4580
|
-
const clearForConversation =
|
|
4823
|
+
const clearForConversation = React4.useCallback((conversationId) => {
|
|
4581
4824
|
setCounts((prev) => {
|
|
4582
4825
|
const next = { ...prev };
|
|
4583
4826
|
delete next[conversationId];
|
|
@@ -4595,10 +4838,10 @@ function usePinnedMessages({
|
|
|
4595
4838
|
initial = []
|
|
4596
4839
|
}) {
|
|
4597
4840
|
const { pinMessage, unpinMessage, fetchPinnedMessages, config } = useCollab();
|
|
4598
|
-
const [pinnedMessages, setPinnedMessages] =
|
|
4599
|
-
const [isLoading, setIsLoading] =
|
|
4600
|
-
const [error, setError] =
|
|
4601
|
-
const refresh =
|
|
4841
|
+
const [pinnedMessages, setPinnedMessages] = React4.useState(initial);
|
|
4842
|
+
const [isLoading, setIsLoading] = React4.useState(false);
|
|
4843
|
+
const [error, setError] = React4.useState(null);
|
|
4844
|
+
const refresh = React4.useCallback(async () => {
|
|
4602
4845
|
if (!conversationId) return;
|
|
4603
4846
|
setIsLoading(true);
|
|
4604
4847
|
setError(null);
|
|
@@ -4612,12 +4855,12 @@ function usePinnedMessages({
|
|
|
4612
4855
|
setIsLoading(false);
|
|
4613
4856
|
}
|
|
4614
4857
|
}, [conversationId, fetchPinnedMessages]);
|
|
4615
|
-
|
|
4858
|
+
React4.useEffect(() => {
|
|
4616
4859
|
if (conversationId && initial.length === 0) {
|
|
4617
4860
|
refresh();
|
|
4618
4861
|
}
|
|
4619
4862
|
}, [conversationId]);
|
|
4620
|
-
const pin =
|
|
4863
|
+
const pin = React4.useCallback(
|
|
4621
4864
|
async (messageId) => {
|
|
4622
4865
|
if (!conversationId) return;
|
|
4623
4866
|
setError(null);
|
|
@@ -4635,7 +4878,7 @@ function usePinnedMessages({
|
|
|
4635
4878
|
},
|
|
4636
4879
|
[conversationId, pinMessage, config]
|
|
4637
4880
|
);
|
|
4638
|
-
const unpin =
|
|
4881
|
+
const unpin = React4.useCallback(
|
|
4639
4882
|
async (messageId) => {
|
|
4640
4883
|
if (!conversationId) return;
|
|
4641
4884
|
setError(null);
|
|
@@ -4653,13 +4896,13 @@ function usePinnedMessages({
|
|
|
4653
4896
|
},
|
|
4654
4897
|
[conversationId, unpinMessage, config]
|
|
4655
4898
|
);
|
|
4656
|
-
const handlePinned =
|
|
4899
|
+
const handlePinned = React4.useCallback((message) => {
|
|
4657
4900
|
setPinnedMessages((prev) => {
|
|
4658
4901
|
if (prev.some((m) => m.id === message.id)) return prev;
|
|
4659
4902
|
return [...prev, message];
|
|
4660
4903
|
});
|
|
4661
4904
|
}, []);
|
|
4662
|
-
const handleUnpinned =
|
|
4905
|
+
const handleUnpinned = React4.useCallback((messageId) => {
|
|
4663
4906
|
setPinnedMessages((prev) => prev.filter((m) => m.id !== messageId));
|
|
4664
4907
|
}, []);
|
|
4665
4908
|
return {
|