From fa931f35b28a2524ecdbc907a918a8585e63fed4 Mon Sep 17 00:00:00 2001 From: N-Pex Date: Tue, 8 Oct 2024 10:34:00 +0200 Subject: [PATCH 1/4] Add timestamps to ticket chat bubbles --- .../tickets/[id]/@detail/_components/TicketDetail.tsx | 6 ++++-- apps/link/app/_graphql/getTicketArticlesQuery.ts | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/link/app/(main)/tickets/[id]/@detail/_components/TicketDetail.tsx b/apps/link/app/(main)/tickets/[id]/@detail/_components/TicketDetail.tsx index d24d34a..ab62874 100644 --- a/apps/link/app/(main)/tickets/[id]/@detail/_components/TicketDetail.tsx +++ b/apps/link/app/(main)/tickets/[id]/@detail/_components/TicketDetail.tsx @@ -109,13 +109,15 @@ export const TicketDetail: FC = ({ id }) => { } model={{ message: article.bodyWithUrls, - sentTime: article.updated_at, + sentTime: article.updatedAt, sender: article.from, direction: article.sender === "Agent" ? "outgoing" : "incoming", position: "single", }} - /> + > + + ))} diff --git a/apps/link/app/_graphql/getTicketArticlesQuery.ts b/apps/link/app/_graphql/getTicketArticlesQuery.ts index c652135..aec391b 100644 --- a/apps/link/app/_graphql/getTicketArticlesQuery.ts +++ b/apps/link/app/_graphql/getTicketArticlesQuery.ts @@ -19,6 +19,7 @@ query getTicketArticles($ticketId: ID!) { emailAddress } } + updatedAt } } } From a6d3d1d8a87e8bcc55463cc2a0e3dff346449122 Mon Sep 17 00:00:00 2001 From: N-Pex Date: Wed, 9 Oct 2024 14:24:00 +0200 Subject: [PATCH 2/4] Remove chat ui library and implement replacement --- .../[id]/@detail/_components/Message.tsx | 20 +++ .../[id]/@detail/_components/MessageList.tsx | 51 ++++++ .../[id]/@detail/_components/TicketDetail.tsx | 152 +++++++++--------- apps/link/app/_styles/global.css | 14 +- apps/link/package.json | 2 - 5 files changed, 161 insertions(+), 78 deletions(-) create mode 100644 apps/link/app/(main)/tickets/[id]/@detail/_components/Message.tsx create mode 100644 apps/link/app/(main)/tickets/[id]/@detail/_components/MessageList.tsx diff --git a/apps/link/app/(main)/tickets/[id]/@detail/_components/Message.tsx b/apps/link/app/(main)/tickets/[id]/@detail/_components/Message.tsx new file mode 100644 index 0000000..67371da --- /dev/null +++ b/apps/link/app/(main)/tickets/[id]/@detail/_components/Message.tsx @@ -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 = ({ message, sentTime, senderName, direction, ...props }) => { + return ( + + {sentTime}{senderName ? ` - ${senderName}` : ''} + + + ) +}; diff --git a/apps/link/app/(main)/tickets/[id]/@detail/_components/MessageList.tsx b/apps/link/app/(main)/tickets/[id]/@detail/_components/MessageList.tsx new file mode 100644 index 0000000..2948101 --- /dev/null +++ b/apps/link/app/(main)/tickets/[id]/@detail/_components/MessageList.tsx @@ -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 = ({ + ticketId, + children, + ...props +}: MessageListProps) => { + const messageListRef = useRef(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) => ( + + ))(({ theme }) => ({ + overflowX: "hidden", + overflowY: "auto", + padding: "20px", + })); + + return ( + + {children} + + ); +}; diff --git a/apps/link/app/(main)/tickets/[id]/@detail/_components/TicketDetail.tsx b/apps/link/app/(main)/tickets/[id]/@detail/_components/TicketDetail.tsx index ab62874..88896d5 100644 --- a/apps/link/app/(main)/tickets/[id]/@detail/_components/TicketDetail.tsx +++ b/apps/link/app/(main)/tickets/[id]/@detail/_components/TicketDetail.tsx @@ -1,18 +1,27 @@ "use client"; -import { FC, useState, useEffect } from "react"; +import { FC, useState, useEffect, useRef, memo } from "react"; 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 "@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 { Message } from "./Message"; +import { MessageList } from "./MessageList"; + +const ChatContainer = styled((props: BoxProps) => )( + ({ theme }) => ({ + overflow: "hidden", + width: "100%", + height: "100%", + display: "flex", + flexDirection: "column", + }), +); + +const ChatHeader = styled((props: any) => )(({ theme }) => ({ + padding: "20px", + textAlign: "center", +})); interface TicketDetailProps { id: string; @@ -26,9 +35,22 @@ export const TicketDetail: FC = ({ id }) => { const [dialogOpen, setDialogOpen] = useState(false); const [articleKind, setArticleKind] = useState("note"); + const messageListRef = useRef(null); + + useEffect(() => { + // Reset remembered state + sessionStorage.setItem("ticketDetailTicket", ""); + sessionStorage.setItem("ticketDetailArticles", ""); + }, [id]) + useEffect(() => { const fetchTicket = async () => { const result = await getTicketAction(id); + const resultRaw = JSON.stringify(result); + if (resultRaw == sessionStorage.getItem("ticketDetailTicket")) { + return; // No change + } + sessionStorage.setItem("ticketDetailTicket", resultRaw); setTicket(result); }; @@ -42,6 +64,11 @@ export const TicketDetail: FC = ({ id }) => { useEffect(() => { const fetchTicketArticles = async () => { const result = await getTicketArticlesAction(id); + const resultRaw = JSON.stringify(result); + if (resultRaw == sessionStorage.getItem("ticketDetailArticles")) { + return; // No change + } + sessionStorage.setItem("ticketDetailArticles", resultRaw); setTicketArticles(result); }; @@ -64,71 +91,52 @@ export const TicketDetail: FC = ({ id }) => { {shouldRender && ( <> - - - - - - - {ticket.title} - - {`Ticket #${ticket.number} (created ${new Date( - ticket.createdAt, - ).toLocaleDateString()})`} - - - - - {ticketArticles.edges.map(({ node: article }: any) => ( - - - - ))} - - + + + + {ticket.title} + + {`Ticket #${ticket.number} (created ${new Date( + ticket.createdAt, + ).toLocaleDateString()})`} + + + {ticketArticles.edges.map(({ node: article }: any) => ( + + ))} + @@ -162,7 +170,7 @@ export const TicketDetail: FC = ({ id }) => { - + Date: Wed, 9 Oct 2024 14:28:45 +0200 Subject: [PATCH 3/4] Update package-lock.json --- package-lock.json | 87 ----------------------------------------------- 1 file changed, 87 deletions(-) diff --git a/package-lock.json b/package-lock.json index fcf25a0..6646f11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -234,8 +234,6 @@ "name": "@link-stack/link", "version": "2.2.0", "dependencies": { - "@chatscope/chat-ui-kit-react": "^2.0.3", - "@chatscope/chat-ui-kit-styles": "^1.4.0", "@emotion/cache": "^11.13.1", "@emotion/react": "^11.13.3", "@emotion/server": "^11.11.0", @@ -1001,32 +999,6 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "license": "MIT" }, - "node_modules/@chatscope/chat-ui-kit-react": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@chatscope/chat-ui-kit-react/-/chat-ui-kit-react-2.0.3.tgz", - "integrity": "sha512-0IkjFskRec7SHrFivOQPiZMie5GLQL+ZnROiIbj4yptbC3aMEMFdHRAZrfqlid3uQx9kYhdtn34wMLh1vVNMLA==", - "license": "MIT", - "dependencies": { - "@chatscope/chat-ui-kit-styles": "^1.2.0", - "@fortawesome/fontawesome-free": "^5.12.1", - "@fortawesome/fontawesome-svg-core": "^1.2.26", - "@fortawesome/free-solid-svg-icons": "^5.12.0", - "@fortawesome/react-fontawesome": "^0.1.8", - "classnames": "^2.2.6", - "prop-types": "^15.7.2" - }, - "peerDependencies": { - "prop-types": "^15.7.2", - "react": "^16.12.0 || ^17.0.0 || ^18.2.0", - "react-dom": "^16.12.0 || ^17.0.0 || ^18.2.0" - } - }, - "node_modules/@chatscope/chat-ui-kit-styles": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@chatscope/chat-ui-kit-styles/-/chat-ui-kit-styles-1.4.0.tgz", - "integrity": "sha512-016mBJD3DESw7Nh+lkKcPd22xG92ghA0VpIXIbjQtmXhC7Ve6wRazTy8z1Ahut+Tbv179+JxrftuMngsj/yV8Q==", - "license": "MIT" - }, "node_modules/@emnapi/runtime": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz", @@ -1655,65 +1627,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@fortawesome/fontawesome-common-types": { - "version": "0.2.36", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz", - "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==", - "hasInstallScript": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/fontawesome-free": { - "version": "5.15.4", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.4.tgz", - "integrity": "sha512-eYm8vijH/hpzr/6/1CJ/V/Eb1xQFW2nnUKArb3z+yUWv7HTwj6M7SP957oMjfZjAHU6qpoNc2wQvIxBLWYa/Jg==", - "hasInstallScript": true, - "license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)", - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/fontawesome-svg-core": { - "version": "1.2.36", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz", - "integrity": "sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "@fortawesome/fontawesome-common-types": "^0.2.36" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/free-solid-svg-icons": { - "version": "5.15.4", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz", - "integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==", - "hasInstallScript": true, - "license": "(CC-BY-4.0 AND MIT)", - "dependencies": { - "@fortawesome/fontawesome-common-types": "^0.2.36" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/react-fontawesome": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.19.tgz", - "integrity": "sha512-Hyb+lB8T18cvLNX0S3llz7PcSOAJMLwiVKBuuzwM/nI5uoBw+gQjnf9il0fR1C3DKOI5Kc79pkJ4/xB0Uw9aFQ==", - "license": "MIT", - "dependencies": { - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "@fortawesome/fontawesome-svg-core": "~1 || ~6", - "react": ">=16.x" - } - }, "node_modules/@graphile/logger": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@graphile/logger/-/logger-0.2.0.tgz", From 8286c180208119e6c8da469ffa5fa543621615f2 Mon Sep 17 00:00:00 2001 From: N-Pex Date: Wed, 9 Oct 2024 15:09:24 +0200 Subject: [PATCH 4/4] Add day markers between articles --- .../[id]/@detail/_components/TicketDetail.tsx | 10 +++++-- apps/link/app/_styles/global.css | 29 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/apps/link/app/(main)/tickets/[id]/@detail/_components/TicketDetail.tsx b/apps/link/app/(main)/tickets/[id]/@detail/_components/TicketDetail.tsx index 88896d5..e4d0b03 100644 --- a/apps/link/app/(main)/tickets/[id]/@detail/_components/TicketDetail.tsx +++ b/apps/link/app/(main)/tickets/[id]/@detail/_components/TicketDetail.tsx @@ -113,7 +113,12 @@ export const TicketDetail: FC = ({ id }) => { ).toLocaleDateString()})`} - {ticketArticles.edges.map(({ node: article }: any) => ( + {ticketArticles.edges.map(({ node: article }: any, index: number) => { + const thisDate = new Date(ticketArticles.edges[index].node.updatedAt).toLocaleDateString(); + const lastDate = index > 0 ? new Date(ticketArticles.edges[index - 1].node.updatedAt).toLocaleDateString() : "" + return ( + <> + {thisDate !== lastDate ? ({thisDate}) : (null)} = ({ id }) => { article.sender === "Agent" ? "outgoing" : "incoming" } /> - ))} + + )})}