Skip to content

Commit 5d5ebaa

Browse files
author
kim
committed
feat: create and show messages by conversations
1 parent 76522cf commit 5d5ebaa

14 files changed

+514
-166
lines changed

src/config/appData.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,10 @@ export type CommentData = {
1616
content: string;
1717
parent: string | null;
1818
chatbotPromptSettingId?: string;
19+
/**
20+
* Id of the conversation in which the comment belongs to
21+
* Legacy data have an undefined value
22+
*/
23+
conversationId?: string;
1924
};
2025
export type CommentAppData = AppData<CommentData>;

src/modules/analytics/FrequentWords.tsx

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,14 @@ function FrequentWords({
4646

4747
const [selectedCustomWords, setSelectedCustomWords] = useState<string[]>([]);
4848
const [customWord, setCustomWord] = useState('');
49-
const [chatMemberID, setChatMemberID] = useState('');
49+
const [selectedConversation, setSelectedConversation] = useState<null | {
50+
accountId: string;
51+
conversationId?: string;
52+
}>(null);
53+
54+
const closeConversation = () => {
55+
setSelectedConversation(null);
56+
};
5057

5158
const isAllSelected = mostFrequentWords.every(
5259
(ele) => -1 < selectedFrequentWords.indexOf(ele),
@@ -136,7 +143,12 @@ function FrequentWords({
136143
sentence={ele.data.content}
137144
memberName={ele.account.name}
138145
words={[...selectedFrequentWords, ...selectedCustomWords]}
139-
onClick={() => setChatMemberID(ele.account.id)}
146+
onClick={() =>
147+
setSelectedConversation({
148+
accountId: ele.account.id,
149+
conversationId: ele.data.conversationId,
150+
})
151+
}
140152
buttonId={buildCheckWholeMemberChatButtonId(ele.account.id)}
141153
/>
142154
))}
@@ -148,19 +160,17 @@ function FrequentWords({
148160
)
149161
}
150162
</Stack>
151-
{chatMemberID && (
152-
<Dialog
153-
open
154-
onClose={() => {
155-
setChatMemberID('');
156-
}}
157-
>
163+
{selectedConversation && (
164+
<Dialog open onClose={closeConversation}>
158165
<DialogTitle>{t('ANALYTICS_CONVERSATION_MEMBER')}</DialogTitle>
159166
<DialogContent>
160-
<ConversationForUser accountId={chatMemberID} />
167+
<ConversationForUser
168+
accountId={selectedConversation.accountId}
169+
conversationId={selectedConversation.conversationId}
170+
/>
161171
</DialogContent>
162172
<DialogActions>
163-
<Button>{t('CLOSE')}</Button>
173+
<Button onClick={closeConversation}>{t('CLOSE')}</Button>
164174
</DialogActions>
165175
</Dialog>
166176
)}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { ReactNode } from 'react';
2+
3+
import { Stack, styled } from '@mui/material';
4+
5+
import { BIG_BORDER_RADIUS } from '@/constants';
6+
7+
const StyledContainer = styled('div')(({ theme }) => ({
8+
backgroundColor: 'white',
9+
border: 'solid silver 1px',
10+
padding: theme.spacing(3, 0),
11+
borderRadius: BIG_BORDER_RADIUS,
12+
}));
13+
14+
export const ChatbotContainer = ({ children }: { children: ReactNode }) => {
15+
return (
16+
<Stack
17+
sx={{
18+
px: { xs: 2, sm: 10 },
19+
maxWidth: '100ch',
20+
m: 'auto',
21+
height: '100%',
22+
}}
23+
gap={2}
24+
>
25+
<StyledContainer>{children}</StyledContainer>
26+
</Stack>
27+
);
28+
};

src/modules/comment/CommentContainer.tsx

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,16 @@
1-
import { Divider, Stack, SxProps, Theme, styled } from '@mui/material';
1+
import { Divider, Stack, SxProps, Theme } from '@mui/material';
22

33
import { ChatbotPromptSettings } from '@/config/appSetting';
44

5-
import { BIG_BORDER_RADIUS } from '../../constants';
65
import { ChatbotTyping } from '../common/ChatbotTyping';
76
import CommentEditor from '../common/CommentEditor';
87
import CommentThread from '../common/CommentThread';
98
import { Suggestions } from '../common/Suggestions';
109
import { Comment } from '../common/useConversation';
1110
import { useSendMessageAndAskChatbot } from '../common/useSendMessageAndAskChatbot';
11+
import { ChatbotContainer } from './ChatbotContainer';
1212
import ChatbotHeader from './ChatbotHeader';
1313

14-
const Container = styled('div')(({ theme }) => ({
15-
backgroundColor: 'white',
16-
border: 'solid silver 1px',
17-
padding: theme.spacing(3, 0),
18-
borderRadius: BIG_BORDER_RADIUS,
19-
}));
20-
2114
export type CommentContainerProps = Readonly<{
2215
chatbotAvatar?: Blob;
2316
chatbotName: string;
@@ -26,6 +19,7 @@ export type CommentContainerProps = Readonly<{
2619
mode?: 'read' | 'write';
2720
suggestions?: string[];
2821
threadSx?: SxProps<Theme>;
22+
conversationId?: string;
2923
}>;
3024

3125
export function CommentContainer({
@@ -36,14 +30,16 @@ export function CommentContainer({
3630
chatbotAvatar,
3731
mode = 'read',
3832
suggestions,
33+
conversationId,
3934
}: CommentContainerProps) {
4035
const { send, isLoading: isSendingMessageAndAsking } =
4136
useSendMessageAndAskChatbot({
4237
chatbotPrompt,
38+
conversationId,
4339
});
4440

4541
return (
46-
<Container>
42+
<ChatbotContainer>
4743
<Stack gap={3} px={2}>
4844
<Stack role="log" gap={3}>
4945
<ChatbotHeader avatar={chatbotAvatar} name={chatbotName} />
@@ -64,6 +60,6 @@ export function CommentContainer({
6460
)}
6561
</Stack>
6662
</Stack>
67-
</Container>
63+
</ChatbotContainer>
6864
);
6965
}

src/modules/comment/Conversation.tsx

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useTranslation } from 'react-i18next';
22

3-
import { Alert, Box, CircularProgress } from '@mui/material';
3+
import { Alert, CircularProgress } from '@mui/material';
44

55
import { ChatbotPromptSettings } from '@/config/appSetting';
66

@@ -15,6 +15,7 @@ function Conversation({
1515
isLoading,
1616
mode = 'read',
1717
suggestions,
18+
conversationId,
1819
}: Readonly<{
1920
chatbotAvatar?: Blob;
2021
chatbotPrompt?: ChatbotPromptSettings;
@@ -23,31 +24,24 @@ function Conversation({
2324
mode?: CommentContainerProps['mode'];
2425
suggestions?: string[];
2526
threadSx?: CommentContainerProps['threadSx'];
27+
conversationId?: string;
2628
}>) {
2729
const { t } = useTranslation();
2830

2931
if (chatbotPrompt) {
3032
const { chatbotName } = chatbotPrompt;
3133

3234
return (
33-
<Box
34-
sx={{
35-
px: { xs: 2, sm: 10 },
36-
maxWidth: '100ch',
37-
m: 'auto',
38-
height: '100%',
39-
}}
40-
>
41-
<CommentContainer
42-
chatbotAvatar={chatbotAvatar}
43-
chatbotName={chatbotName}
44-
threadSx={threadSx}
45-
comments={comments}
46-
chatbotPrompt={chatbotPrompt}
47-
mode={mode}
48-
suggestions={suggestions}
49-
/>
50-
</Box>
35+
<CommentContainer
36+
chatbotAvatar={chatbotAvatar}
37+
chatbotName={chatbotName}
38+
threadSx={threadSx}
39+
comments={comments}
40+
chatbotPrompt={chatbotPrompt}
41+
mode={mode}
42+
suggestions={suggestions}
43+
conversationId={conversationId}
44+
/>
5145
);
5246
}
5347

src/modules/comment/ConversationForUser.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import Conversation from './Conversation';
33

44
export const ConversationForUser = ({
55
accountId,
6-
}: Readonly<{ accountId: string }>) => {
6+
conversationId,
7+
}: Readonly<{ accountId: string; conversationId?: string }>) => {
78
const { comments, isLoading, chatbotPrompt, chatbotAvatar } = useConversation(
8-
{ accountId },
9+
{ accountId, conversationId },
910
);
1011

1112
return (
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { useTranslation } from 'react-i18next';
2+
3+
import {
4+
Button,
5+
Container,
6+
IconButton,
7+
Stack,
8+
Table,
9+
TableBody,
10+
TableCell,
11+
TableContainer,
12+
TableRow,
13+
Typography,
14+
} from '@mui/material';
15+
16+
import { intlFormat } from 'date-fns';
17+
import groupBy from 'lodash.groupby';
18+
import { MessagesSquareIcon, PlusIcon } from 'lucide-react';
19+
import { v4 } from 'uuid';
20+
21+
import type { CommentData } from '@/config/appData';
22+
import { hooks } from '@/config/queryClient';
23+
import {
24+
TABLE_VIEW_BODY_USERS_CYPRESS,
25+
TABLE_VIEW_TABLE_CYPRESS,
26+
} from '@/config/selectors';
27+
28+
import { ChatbotContainer } from './ChatbotContainer';
29+
import ChatbotHeader from './ChatbotHeader';
30+
31+
const useConversations = () => {
32+
const { i18n } = useTranslation();
33+
const { data: messages = [] } = hooks.useAppData<CommentData>();
34+
35+
if (messages) {
36+
const conversations = groupBy(messages, (c) => c.data.conversationId);
37+
return Object.entries(conversations)
38+
.toSorted((a, b) => (a[0] > b[0] ? 1 : -1))
39+
.map(([id, m]) => {
40+
const sortedMessages = m.toSorted((a, b) =>
41+
a.createdAt > b.createdAt ? 1 : -1,
42+
);
43+
const creationDate = sortedMessages.at(-1)?.createdAt;
44+
45+
return {
46+
id,
47+
name: sortedMessages[0].data.content,
48+
messages: m,
49+
lastMessageDate: creationDate
50+
? intlFormat(
51+
creationDate,
52+
{
53+
year: 'numeric',
54+
month: 'long',
55+
day: 'numeric',
56+
hour: 'numeric',
57+
minute: 'numeric',
58+
},
59+
{ locale: i18n.language },
60+
)
61+
: '',
62+
};
63+
});
64+
}
65+
66+
return [];
67+
};
68+
69+
export function Conversations({
70+
chatbotAvatar,
71+
chatbotName,
72+
onSelect,
73+
}: Readonly<{
74+
chatbotAvatar?: Blob;
75+
chatbotName: string;
76+
onSelect: (id: string) => void;
77+
}>) {
78+
const { t } = useTranslation();
79+
const conversations = useConversations();
80+
81+
const startNewConversation = () => {
82+
onSelect(v4());
83+
};
84+
85+
return (
86+
<Container>
87+
<ChatbotContainer>
88+
<Stack gap={3}>
89+
<ChatbotHeader avatar={chatbotAvatar} name={chatbotName} />
90+
<TableContainer data-cy={TABLE_VIEW_TABLE_CYPRESS}>
91+
<Table aria-label="conversations table">
92+
<TableBody data-cy={TABLE_VIEW_BODY_USERS_CYPRESS}>
93+
{conversations.map((c) => (
94+
<TableRow key={c.id}>
95+
<TableCell>
96+
<Typography
97+
variant="body1"
98+
fontWeight="bold"
99+
noWrap
100+
maxWidth={200}
101+
title={c.name}
102+
>
103+
{c.name}
104+
</Typography>
105+
</TableCell>
106+
<TableCell>
107+
<Typography variant="body2">
108+
{c.lastMessageDate}
109+
</Typography>
110+
</TableCell>
111+
<TableCell align="right">
112+
<IconButton
113+
onClick={() => {
114+
console.debug(`Open conversation ${c.id}`);
115+
onSelect(c.id);
116+
}}
117+
>
118+
<MessagesSquareIcon />
119+
</IconButton>
120+
</TableCell>
121+
</TableRow>
122+
))}
123+
</TableBody>
124+
</Table>
125+
</TableContainer>
126+
<Stack alignItems="center">
127+
<Button
128+
variant="contained"
129+
startIcon={<PlusIcon />}
130+
onClick={startNewConversation}
131+
>
132+
{t('New conversation')}
133+
</Button>
134+
</Stack>
135+
</Stack>
136+
</ChatbotContainer>
137+
</Container>
138+
);
139+
}

0 commit comments

Comments
 (0)