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 }) => { - +