Remove chat ui library and implement replacement

This commit is contained in:
N-Pex 2024-10-09 14:24:00 +02:00
parent fa931f35b2
commit a6d3d1d8a8
5 changed files with 161 additions and 78 deletions

View file

@ -0,0 +1,20 @@
"use client";
import { FC } from "react";
import { Box, BoxProps } from "@mui/material";
interface MessageProps extends BoxProps {
message: string;
sentTime: string;
senderName: string;
direction: "outgoing" | "incoming";
}
export const Message: FC<MessageProps> = ({ message, sentTime, senderName, direction, ...props }) => {
return (
<Box {...props}>
<Box className="cs-message__time">{sentTime}{senderName ? ` - ${senderName}` : ''}</Box>
<Box className="cs-message__content" dangerouslySetInnerHTML={{ __html: message }} />
</Box>
)
};

View file

@ -0,0 +1,51 @@
"use client";
import { FC, useEffect, useRef, useCallback } from "react";
import { Box, styled, BoxProps } from "@mui/material";
interface MessageListProps extends BoxProps {
ticketId: string;
}
export const MessageList: FC<MessageListProps> = ({
ticketId,
children,
...props
}: MessageListProps) => {
const messageListRef = useRef<HTMLElement>(null);
useEffect(() => {
sessionStorage.removeItem("messageListScroll");
}, [ticketId]);
const onRefChange = useCallback(node => {
if (node !== null) {
// DOM node referenced by ref has changed and exists
const scrollTop = parseFloat(sessionStorage.getItem("messageListScroll") ?? "1000000");
node.scrollTop = Math.min(scrollTop, node.scrollHeight - node.clientHeight);
node.onscroll = () => {
sessionStorage.setItem("messageListScroll", node.scrollTop.toPrecision(6));
}
requestAnimationFrame(() => {
node.scrollTo({
top: node.scrollHeight - node.clientHeight,
behavior: "smooth",
});
});
}
}, []);
const InternalMessageList = styled((props: any) => (
<Box key="messageList" ref={onRefChange} {...props} />
))(({ theme }) => ({
overflowX: "hidden",
overflowY: "auto",
padding: "20px",
}));
return (
<InternalMessageList {...props}>
{children}
</InternalMessageList>
);
};

View file

@ -1,18 +1,27 @@
"use client"; "use client";
import { FC, useState, useEffect } from "react"; import { FC, useState, useEffect, useRef, memo } from "react";
import { getTicketAction, getTicketArticlesAction } from "app/_actions/tickets"; import { getTicketAction, getTicketArticlesAction } from "app/_actions/tickets";
import { Grid, Box, Typography } from "@mui/material"; import { Grid, Box, Typography, styled, BoxProps } from "@mui/material";
import { Button, fonts, colors } from "@link-stack/ui"; import { Button, fonts, colors } from "@link-stack/ui";
import "@chatscope/chat-ui-kit-styles/dist/default/styles.min.css";
import {
MainContainer,
ChatContainer,
MessageList,
Message,
ConversationHeader,
} from "@chatscope/chat-ui-kit-react";
import { ArticleCreateDialog } from "./ArticleCreateDialog"; import { ArticleCreateDialog } from "./ArticleCreateDialog";
import { Message } from "./Message";
import { MessageList } from "./MessageList";
const ChatContainer = styled((props: BoxProps) => <Box {...props} />)(
({ theme }) => ({
overflow: "hidden",
width: "100%",
height: "100%",
display: "flex",
flexDirection: "column",
}),
);
const ChatHeader = styled((props: any) => <Box {...props} />)(({ theme }) => ({
padding: "20px",
textAlign: "center",
}));
interface TicketDetailProps { interface TicketDetailProps {
id: string; id: string;
@ -26,9 +35,22 @@ export const TicketDetail: FC<TicketDetailProps> = ({ id }) => {
const [dialogOpen, setDialogOpen] = useState(false); const [dialogOpen, setDialogOpen] = useState(false);
const [articleKind, setArticleKind] = useState("note"); const [articleKind, setArticleKind] = useState("note");
const messageListRef = useRef<HTMLElement>(null);
useEffect(() => {
// Reset remembered state
sessionStorage.setItem("ticketDetailTicket", "");
sessionStorage.setItem("ticketDetailArticles", "");
}, [id])
useEffect(() => { useEffect(() => {
const fetchTicket = async () => { const fetchTicket = async () => {
const result = await getTicketAction(id); const result = await getTicketAction(id);
const resultRaw = JSON.stringify(result);
if (resultRaw == sessionStorage.getItem("ticketDetailTicket")) {
return; // No change
}
sessionStorage.setItem("ticketDetailTicket", resultRaw);
setTicket(result); setTicket(result);
}; };
@ -42,6 +64,11 @@ export const TicketDetail: FC<TicketDetailProps> = ({ id }) => {
useEffect(() => { useEffect(() => {
const fetchTicketArticles = async () => { const fetchTicketArticles = async () => {
const result = await getTicketArticlesAction(id); const result = await getTicketArticlesAction(id);
const resultRaw = JSON.stringify(result);
if (resultRaw == sessionStorage.getItem("ticketDetailArticles")) {
return; // No change
}
sessionStorage.setItem("ticketDetailArticles", resultRaw);
setTicketArticles(result); setTicketArticles(result);
}; };
@ -64,71 +91,52 @@ export const TicketDetail: FC<TicketDetailProps> = ({ id }) => {
<Box sx={{ height: "100%", width: "100%", background: veryLightGray }}> <Box sx={{ height: "100%", width: "100%", background: veryLightGray }}>
{shouldRender && ( {shouldRender && (
<> <>
<MainContainer> <ChatContainer className="cs-main-container">
<ChatContainer> <ChatHeader className="cs-conversation-header">
<ConversationHeader> <Typography
<ConversationHeader.Content> variant="h5"
<Box sx={{
sx={{ fontFamily: poppins.style.fontFamily,
width: "100%", fontWeight: 700,
textAlign: "center", }}
fontWeight: "bold", >
}} {ticket.title}
> </Typography>
<Typography <Typography
variant="h5" variant="h6"
sx={{ sx={{
fontFamily: poppins.style.fontFamily, fontFamily: roboto.style.fontFamily,
fontWeight: 700, fontWeight: 400,
}} }}
> >{`Ticket #${ticket.number} (created ${new Date(
{ticket.title} ticket.createdAt,
</Typography> ).toLocaleDateString()})`}</Typography>
<Typography </ChatHeader>
variant="h6" <MessageList ticketId={id}>
sx={{ {ticketArticles.edges.map(({ node: article }: any) => (
fontFamily: roboto.style.fontFamily, <Message
fontWeight: 400, key={article.id}
}} className={
>{`Ticket #${ticket.number} (created ${new Date( article.internal
ticket.createdAt, ? "internal-note"
).toLocaleDateString()})`}</Typography> : article?.sender?.name === "Agent"
</Box> ? "outgoing-message"
</ConversationHeader.Content> : "incoming-message"
</ConversationHeader> }
<MessageList style={{ marginBottom: 80 }}> message={article.bodyWithUrls}
{ticketArticles.edges.map(({ node: article }: any) => ( sentTime={new Date(article.updatedAt).toLocaleString()}
<Message senderName={article.sender?.name}
key={article.id} direction={
className={ article.sender === "Agent" ? "outgoing" : "incoming"
article.internal }
? "internal-note" />
: article?.sender?.name === "Agent" ))}
? "outgoing-message" </MessageList>
: "incoming-message"
}
model={{
message: article.bodyWithUrls,
sentTime: article.updatedAt,
sender: article.from,
direction:
article.sender === "Agent" ? "outgoing" : "incoming",
position: "single",
}}
>
<Message.Header sender={article.sender?.name} sentTime={new Date(article.updatedAt).toLocaleString()} />
</Message>
))}
</MessageList>
</ChatContainer>
<Box <Box
sx={{ sx={{
height: 80, flex: "0 0 80",
background: veryLightGray, background: veryLightGray,
borderTop: `1px solid ${lightGray}`, borderTop: `1px solid ${lightGray}`,
position: "absolute",
bottom: 0,
width: "100%",
zIndex: 1000, zIndex: 1000,
}} }}
> >
@ -162,7 +170,7 @@ export const TicketDetail: FC<TicketDetailProps> = ({ id }) => {
</Grid> </Grid>
</Grid> </Grid>
</Box> </Box>
</MainContainer> </ChatContainer>
<ArticleCreateDialog <ArticleCreateDialog
ticketID={ticket.internalId} ticketID={ticket.internalId}
open={dialogOpen} open={dialogOpen}

View file

@ -4,6 +4,15 @@ body {
text-size-adjust: none; text-size-adjust: none;
} }
.cs-message__time {
color: #888;
font-family: Roboto;
font-size: 12px;
position: relative;
left: 12px;
top: 12px;
}
.internal-note .cs-message__content { .internal-note .cs-message__content {
background-color: #FFB62088 !important; background-color: #FFB62088 !important;
border: 2px solid #FFB620 !important; border: 2px solid #FFB620 !important;
@ -75,8 +84,6 @@ body {
.cs-conversation-header { .cs-conversation-header {
background-color: #ddd !important; background-color: #ddd !important;
border: 0 !important;
padding: 20px !important;
border-bottom: 1px solid #ccc !important; border-bottom: 1px solid #ccc !important;
} }
@ -89,6 +96,5 @@ body {
} }
.cs-main-container { .cs-main-container {
border: 0 !important; border-right: 1px solid #ccc;
border-right: 1px solid #ccc !important;
} }

View file

@ -10,8 +10,6 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@chatscope/chat-ui-kit-react": "^2.0.3",
"@chatscope/chat-ui-kit-styles": "^1.4.0",
"@emotion/cache": "^11.13.1", "@emotion/cache": "^11.13.1",
"@emotion/react": "^11.13.3", "@emotion/react": "^11.13.3",
"@emotion/server": "^11.11.0", "@emotion/server": "^11.11.0",