From 69f0d44513a89b80f75168eb2a050b0c002a6320 Mon Sep 17 00:00:00 2001 From: Janno Stern Date: Sun, 15 Dec 2024 21:44:35 +0200 Subject: [PATCH] Improve chat history --- ...created-to-chat-history-comments-table.sql | 9 + ...ay-name-to-chat-history-comments-table.sql | 4 + DSL/Resql/get-chat-history-comment-by-id.sql | 2 +- DSL/Resql/get-cs-all-ended-chats.sql | 31 +- DSL/Resql/insert-chat-history-comment.sql | 6 +- .../DSL/POST/comments/history.yml | 6 + GUI/src/pages/Chat/ChatHistory/History.scss | 23 +- GUI/src/pages/Chat/ChatHistory/index.tsx | 622 +++++++++++------- GUI/src/types/chat.ts | 11 +- GUI/translations/en/common.json | 8 +- GUI/translations/et/common.json | 6 +- create-migration.sh | 9 +- 12 files changed, 461 insertions(+), 276 deletions(-) create mode 100644 DSL/Liquibase/changelog/20241210105636-add-created-to-chat-history-comments-table.sql create mode 100644 DSL/Liquibase/changelog/20241211105438-add-author-display-name-to-chat-history-comments-table.sql diff --git a/DSL/Liquibase/changelog/20241210105636-add-created-to-chat-history-comments-table.sql b/DSL/Liquibase/changelog/20241210105636-add-created-to-chat-history-comments-table.sql new file mode 100644 index 000000000..57ef0d6fb --- /dev/null +++ b/DSL/Liquibase/changelog/20241210105636-add-created-to-chat-history-comments-table.sql @@ -0,0 +1,9 @@ +-- liquibase formatted sql +-- changeset Janno Stern:20241210105636 +ALTER TABLE chat_history_comments +ADD COLUMN IF NOT EXISTS created TIMESTAMP WITH TIME ZONE; + +-- Update existing rows with a default timestamp (e.g., the current time or a specific value) +UPDATE chat_history_comments +SET created = NOW() +WHERE created IS NULL; diff --git a/DSL/Liquibase/changelog/20241211105438-add-author-display-name-to-chat-history-comments-table.sql b/DSL/Liquibase/changelog/20241211105438-add-author-display-name-to-chat-history-comments-table.sql new file mode 100644 index 000000000..2093ef17f --- /dev/null +++ b/DSL/Liquibase/changelog/20241211105438-add-author-display-name-to-chat-history-comments-table.sql @@ -0,0 +1,4 @@ +-- liquibase formatted sql +-- changeset Janno Stern:20241211105438 +ALTER TABLE chat_history_comments +ADD COLUMN author_display_name TEXT; diff --git a/DSL/Resql/get-chat-history-comment-by-id.sql b/DSL/Resql/get-chat-history-comment-by-id.sql index 62b4ee178..eaad4796d 100644 --- a/DSL/Resql/get-chat-history-comment-by-id.sql +++ b/DSL/Resql/get-chat-history-comment-by-id.sql @@ -1,4 +1,4 @@ -SELECT id, chat_id, comment +SELECT id, chat_id, comment, created, author_display_name FROM chat_history_comments WHERE chat_id = :chatId ORDER BY id DESC diff --git a/DSL/Resql/get-cs-all-ended-chats.sql b/DSL/Resql/get-cs-all-ended-chats.sql index 4670944b8..e54fa5e01 100644 --- a/DSL/Resql/get-cs-all-ended-chats.sql +++ b/DSL/Resql/get-cs-all-ended-chats.sql @@ -3,10 +3,23 @@ WITH MaxChatHistoryComments AS ( FROM chat_history_comments GROUP BY chat_id ), + ChatUser AS ( + SELECT DISTINCT ON (id_code) + id_code, + display_name, + first_name, + last_name + FROM "user" + ORDER BY id_code, id DESC + ), ChatHistoryComments AS ( - SELECT comment, chat_id + SELECT + comment, + chat_id, + created, + author_display_name FROM chat_history_comments - JOIN MaxChatHistoryComments ON id = maxId + JOIN MaxChatHistoryComments ON id = maxId ), MessageWithContent AS ( SELECT @@ -52,7 +65,7 @@ FROM message GROUP BY chat_base_id ), Messages AS ( -SELECT event, updated, chat_base_id +SELECT event, updated, chat_base_id, author_id FROM message JOIN MaxMessages ON id = maxID ), @@ -129,6 +142,11 @@ SELECT c.base_id AS id, c.received_from, c.labels, s.comment, + s.created as comment_added_date, + s.author_display_name as comment_author, + mu.display_name AS user_display_name, + cu.first_name AS customer_support_first_name, + cu.last_name AS customer_support_last_name, LastContentMessage.content AS last_message, (CASE WHEN m.event = '' THEN NULL ELSE LOWER(m.event) END) AS last_message_event, ContactsMessage.content AS contacts_message, @@ -140,16 +158,14 @@ SELECT c.base_id AS id, FROM EndedChatMessages AS c JOIN Messages AS m ON c.base_id = m.chat_base_id LEFT JOIN ChatHistoryComments AS s ON s.chat_id = m.chat_base_id + LEFT JOIN ChatUser AS mu ON mu.id_code = m.author_id + LEFT JOIN ChatUser AS cu ON cu.id_code = c.customer_support_id JOIN LastContentMessage ON c.base_id = LastContentMessage.chat_base_id JOIN FirstContentMessage ON c.base_id = FirstContentMessage.chat_base_id LEFT JOIN ContactsMessage ON ContactsMessage.chat_base_id = c.base_id CROSS JOIN TitleVisibility CROSS JOIN NPS WHERE ( - ( - LENGTH(:customerSupportIds) = 0 OR - c.customer_support_id = ANY(string_to_array(:customerSupportIds, ',')) - ) AND ( :search IS NULL OR :search = '' OR LOWER(c.customer_support_display_name) LIKE LOWER('%' || :search || '%') OR @@ -168,7 +184,6 @@ WHERE ( AND LOWER(msg.content) LIKE LOWER('%' || :search || '%') ) ) - ) ORDER BY CASE WHEN :sorting = 'created asc' THEN FirstContentMessage.created END ASC, CASE WHEN :sorting = 'created desc' THEN FirstContentMessage.created END DESC, diff --git a/DSL/Resql/insert-chat-history-comment.sql b/DSL/Resql/insert-chat-history-comment.sql index 20caef426..e3281de46 100644 --- a/DSL/Resql/insert-chat-history-comment.sql +++ b/DSL/Resql/insert-chat-history-comment.sql @@ -1,3 +1,3 @@ -INSERT INTO "chat_history_comments" (chat_id, comment) -VALUES (:chatId, :comment) -RETURNING id, chat_id, comment; +INSERT INTO "chat_history_comments" (chat_id, comment, created, author_display_name) +VALUES (:chatId, :comment, :created::timestamp with time zone, :authorDisplayName) +RETURNING id, chat_id, comment, created, author_display_name; diff --git a/DSL/Ruuter.private/DSL/POST/comments/history.yml b/DSL/Ruuter.private/DSL/POST/comments/history.yml index be4b631ba..e1cbcd9b0 100644 --- a/DSL/Ruuter.private/DSL/POST/comments/history.yml +++ b/DSL/Ruuter.private/DSL/POST/comments/history.yml @@ -14,11 +14,15 @@ declaration: - field: comment type: string description: "Body field 'comment'" + - field: authorDisplayName + type: string + description: "Body field 'authorDisplayName'" extractRequestData: assign: comment: ${incoming.body.comment} chatId: ${incoming.body.chatId} + authorDisplayName: ${incoming.body.authorDisplayName} setChatComment: call: http.post @@ -27,6 +31,8 @@ setChatComment: body: comment: ${comment} chatId: ${chatId} + created: "${new Date().toISOString()}" + authorDisplayName: ${authorDisplayName} result: res return_result: diff --git a/GUI/src/pages/Chat/ChatHistory/History.scss b/GUI/src/pages/Chat/ChatHistory/History.scss index 42b5f2cb5..9648f041b 100644 --- a/GUI/src/pages/Chat/ChatHistory/History.scss +++ b/GUI/src/pages/Chat/ChatHistory/History.scss @@ -1,3 +1,7 @@ +@import 'src/styles/tools/spacing'; +@import 'src/styles/tools/color'; +@import 'src/styles/settings/variables/typography'; + .card-drawer-container { display: flex; width: 100%; @@ -12,15 +16,28 @@ } .drawer-container { - width: 700px; + flex: 1; height: 100%; - flex-shrink: 0; overflow-y: auto; box-shadow: -2px 0 5px rgba(0, 0, 0, 0.1); + border: 1px solid get-color(black-coral-2); } .drawer-container > * { position: static !important; width: 100%; height: 100%; -} \ No newline at end of file +} + +.side-meta { + background-color: get-color(white); + border: 1px solid get-color(black-coral-2); + color: get-color(black-coral-12); + display: flex; + flex: 0 0 288px; + flex-direction: column; + font-size: $veera-font-size-80; + gap: 4px; + overflow-y: auto; + padding: get-spacing(haapsalu); +} diff --git a/GUI/src/pages/Chat/ChatHistory/index.tsx b/GUI/src/pages/Chat/ChatHistory/index.tsx index ac359bfbb..9388520ad 100644 --- a/GUI/src/pages/Chat/ChatHistory/index.tsx +++ b/GUI/src/pages/Chat/ChatHistory/index.tsx @@ -53,7 +53,6 @@ const ChatHistory: FC = () => { let passedChatId = new URLSearchParams(routerLocation.search).get('chat'); const passedStartDate = params.get('start'); const passedEndDate = params.get('end'); - const passedCustomerSupportIds = params.getAll('customerSupportIds'); const preferences = getFromLocalStorage( CHAT_HISTORY_PREFERENCES_KEY ) as string[]; @@ -63,7 +62,7 @@ const ChatHistory: FC = () => { const [statusChangeModal, setStatusChangeModal] = useState( null ); - const [chatState, setChatState] = useState (statusChangeModal); + const [chatState, setChatState] = useState(statusChangeModal); const [pagination, setPagination] = useState({ pageIndex: searchParams.get('page') ? parseInt(searchParams.get('page') as string) - 1 @@ -85,7 +84,6 @@ const ChatHistory: FC = () => { const [selectedColumns, setSelectedColumns] = useState( preferences ?? [] ); - const [customerSupportAgents, setCustomerSupportAgents] = useState([]); const { control, watch } = useForm<{ startDate: Date | string; @@ -104,7 +102,7 @@ const ChatHistory: FC = () => { : new Date( new Date().getUTCFullYear(), new Date().getUTCMonth(), - new Date().getUTCDate() + new Date().getUTCDate() + 1 ), }, }); @@ -116,7 +114,6 @@ const ChatHistory: FC = () => { getAllEndedChats.mutate({ startDate: format(new Date(startDate), 'yyyy-MM-dd'), endDate: format(new Date(endDate), 'yyyy-MM-dd'), - customerSupportIds: passedCustomerSupportIds, pagination, sorting, search, @@ -134,22 +131,16 @@ const ChatHistory: FC = () => { getAllEndedChats.mutate({ startDate: format(new Date(startDate), 'yyyy-MM-dd'), endDate: format(new Date(endDate), 'yyyy-MM-dd'), - customerSupportIds: passedCustomerSupportIds, pagination, sorting, search, }); }, []); - useEffect(() => { - listCustomerSupportAgents.mutate(); - }, []) - const getAllEndedChats = useMutation({ mutationFn: (data: { startDate: string; endDate: string; - customerSupportIds: string[]; pagination: PaginationState; sorting: SortingState; search: string; @@ -161,16 +152,22 @@ const ChatHistory: FC = () => { } return apiDev.post('agents/chats/ended', { - customerSupportIds: data.customerSupportIds, startDate: data.startDate, endDate: data.endDate, page: pagination.pageIndex + 1, page_size: pagination.pageSize, sorting: sortBy, - search + search, }); }, onSuccess: (res: any) => { + if (selectedChat) + setSelectedChat({ + ...selectedChat, + lastMessageEvent: res.data.response[0].lastMessageEvent, + lastMessageTimestamp: res.data.response[0].lastMessageTimestamp, + userDisplayName: res.data.response[0].userDisplayName, + }); filterChatsList(res.data.response ?? []); setTotalPages(res?.data?.response[0]?.totalPages ?? 1); }, @@ -183,31 +180,15 @@ const ChatHistory: FC = () => { }), onSuccess: (res: any) => { setSelectedChat(res.data.response); - setChatState(res.data.response) + setChatState(res.data.response); }, }); - const listCustomerSupportAgents = useMutation({ - mutationFn: () => apiDev.post('accounts/customer-support-agents', { - page: 0, - page_size: 99999, - sorting: 'name asc', - show_active_only: false, - roles: ["ROLE_CUSTOMER_SUPPORT_AGENT"], - }), - onSuccess: (res: any) => { - setCustomerSupportAgents(res.data.response.map(item => ({ - label: [item.firstName, item.lastName].join(' ').trim(), - value: item.idCode, - }))); - } - }); - const visibleColumnOptions = useMemo( () => [ { label: t('chat.history.startTime'), value: 'created' }, { label: t('chat.history.endTime'), value: 'ended' }, - { label: t('chat.history.csaName'), value: 'customerSupportDisplayName' }, + { label: t('chat.history.csaName'), value: 'customerSupportFullName' }, { label: t('global.name'), value: 'endUserName' }, { label: t('global.idCode'), value: 'endUserId' }, { label: t('chat.history.contact'), value: 'contactsMessage' }, @@ -216,7 +197,7 @@ const ChatHistory: FC = () => { { label: t('chat.history.feedback'), value: 'feedbackText' }, { label: t('global.status'), value: 'status' }, { label: 'ID', value: 'id' }, - { label: 'www', value: 'www' } + { label: 'www', value: 'www' }, ], [t] ); @@ -255,7 +236,6 @@ const ChatHistory: FC = () => { getAllEndedChats.mutate({ startDate: format(new Date(startDate), 'yyyy-MM-dd'), endDate: format(new Date(endDate), 'yyyy-MM-dd'), - customerSupportIds: passedCustomerSupportIds, pagination, sorting, search, @@ -278,14 +258,23 @@ const ChatHistory: FC = () => { }); const chatCommentChangeMutation = useMutation({ - mutationFn: (data: { chatId: string | number; comment: string }) => - apiDev.post('comments/history', data), + mutationFn: (data: { + chatId: string | number; + comment: string; + authorDisplayName: string; + }) => apiDev.post('comments/history', data), onSuccess: (res, { chatId, comment }) => { const updatedChatList = filteredEndedChatsList.map((chat) => chat.id === chatId ? { ...chat, comment } : chat ); filterChatsList(updatedChatList); - if (selectedChat) setSelectedChat({ ...selectedChat, comment }); + if (selectedChat) + setSelectedChat({ + ...selectedChat, + comment, + commentAddedDate: res.data.response[0].created, + commentAuthor: res.data.response[0].authorDisplayName, + }); toast.open({ type: 'success', title: t('global.notification'), @@ -340,14 +329,14 @@ const ChatHistory: FC = () => { }; const wwwView = (props: any) => ( - + - + ); const statusView = (props: any) => { @@ -374,8 +363,8 @@ const ChatHistory: FC = () => { - - + {selectedChat && ( + <> +
+ -

{t('global.removeValidation')}

- + onClose={() => setSelectedChat(null)} + > + +
+
+
+
+

+ ID +

+

{selectedChat.id}

+
+
+

+ {t('chat.endUser')} +

+

{endUserFullName}

+
+ {selectedChat.endUserId && ( +
+

+ {t('chat.endUserId')} +

+

{selectedChat.endUserId ?? ''}

+
+ )} + {selectedChat.endUserEmail && ( +
+

+ {t('chat.endUserEmail')} +

+

{selectedChat.endUserEmail}

+
+ )} + {selectedChat.endUserPhone && ( +
+

+ {t('chat.endUserPhoneNumber')} +

+

{selectedChat.endUserPhone}

+
+ )} + {selectedChat.customerSupportDisplayName && ( +
+

+ {t('chat.csaName')} +

+

{selectedChat.customerSupportDisplayName}

+
+ )} +
+

+ {t('chat.startedAt')} +

+

+ {format( + new Date(selectedChat.created), + 'dd. MMMM Y HH:mm:ss', + { + locale: et, + } + ).toLowerCase()} +

+
+
+

+ {t('chat.device')} +

+

{selectedChat.endUserOs}

+
+
+

+ {t('chat.location')} +

+

{selectedChat.endUserUrl}

+
+ {selectedChat.comment && ( +
+

+ {t('chat.history.comment')} +

+

{selectedChat.comment}

+
+ )} + {selectedChat.commentAuthor && ( +
+

+ {t('chat.history.commentAuthor')} +

+

{selectedChat.commentAuthor}

+
+ )} + {selectedChat.commentAddedDate && ( +
+

+ {t('chat.history.commentAddedDate')} +

+

+ {format( + new Date(selectedChat.commentAddedDate), + 'dd.MM.yyyy' + )} +

+
+ )} + {selectedChat.lastMessageEvent && ( +
+

+ {t('global.status')} +

+

+ {t('chat.plainEvents.' + selectedChat.lastMessageEvent)} +

+
+ )} + {selectedChat.userDisplayName && ( +
+

+ {t('chat.history.statusAdder')} +

+

{selectedChat.userDisplayName}

+
+ )} + {selectedChat.lastMessageTimestamp && ( +
+

+ {t('chat.history.statusAddedDate')} +

+

+ {format( + new Date(selectedChat.lastMessageTimestamp), + 'dd.MM.yyyy' + )} +

+
+ )} +
+ )} - + + + {statusChangeModal && ( + setStatusChangeModal(null)} + footer={ + <> + + + + } + > +

{t('global.removeValidation')}

+
+ )} + ); + + function getUserName() { + return selectedChat?.endUserFirstName !== '' && + selectedChat?.endUserLastName !== '' + ? `${selectedChat?.endUserFirstName} ${selectedChat?.endUserLastName}` + : t('global.anonymous'); + } }; export default withAuthorization(ChatHistory, [ diff --git a/GUI/src/types/chat.ts b/GUI/src/types/chat.ts index 520c88fd1..922affe00 100644 --- a/GUI/src/types/chat.ts +++ b/GUI/src/types/chat.ts @@ -6,10 +6,6 @@ export enum CHAT_STATUS { VALIDATING = 'VALIDATING', } -export enum BACKOFFICE_NAME { - DEFAULT = 'Bürokratt' -} - export enum CHAT_EVENTS { ANSWERED = 'answered', TERMINATED = 'terminated', @@ -61,6 +57,8 @@ export interface Chat { csaTitle?: string | null; customerSupportId?: string; customerSupportDisplayName?: string; + customerSupportFirstName?: string; + customerSupportLastName?: string; endUserId?: string; endUserEmail?: string; endUserPhone?: string; @@ -82,10 +80,13 @@ export interface Chat { forwardedToCsa?: string; receivedFrom?: string; comment?: string; + commentAddedDate?: string; + commentAuthor?: string; labels: string; feedbackText?: string; feedbackRating?: number; nps?: number; + userDisplayName?: string; } export interface GroupedChat { myChats: Chat[]; @@ -116,4 +117,4 @@ export enum MessageSseEvent { export type MessageStatus = { messageId: string | null; readTime: any; -} +}; diff --git a/GUI/translations/en/common.json b/GUI/translations/en/common.json index 511b611c6..0d7c953e8 100644 --- a/GUI/translations/en/common.json +++ b/GUI/translations/en/common.json @@ -162,7 +162,11 @@ "forwarded": "Forwarded", "addACommentToTheConversation": "Add a comment to the conversation", "rating": "Rating", - "feedback": "Feedback" + "feedback": "Feedback", + "commentAuthor": "Commenter", + "commentAddedDate": "Comment added date", + "statusAdder": "Status adder", + "statusAddedDate": "Status added date" }, "plainEvents": { "answered": "Answered", @@ -259,7 +263,7 @@ "messageApproved": "Message approved successfully", "messageChangeFailed": "Message change failed", "header": { - "id" : "Id", + "id": "Id", "chatId": "Chat Id", "message": "Message", "requestedAt": "Requested At", diff --git a/GUI/translations/et/common.json b/GUI/translations/et/common.json index 5b5682429..41cd0184b 100644 --- a/GUI/translations/et/common.json +++ b/GUI/translations/et/common.json @@ -162,7 +162,11 @@ "forwarded": "Suunatud", "addACommentToTheConversation": "Lisa vestlusele kommentaar", "rating": "Hinnang", - "feedback": "Tagasiside" + "feedback": "Tagasiside", + "commentAuthor": "Kommentaari lisaja", + "commentAddedDate": "Kommentaari lisamise kuupäev", + "statusAdder": "Staatuse lisaja", + "statusAddedDate": "Staatuse lisamise kuupäev" }, "plainEvents": { "answered": "Vastatud", diff --git a/create-migration.sh b/create-migration.sh index f91fe7203..a6423322f 100755 --- a/create-migration.sh +++ b/create-migration.sh @@ -5,5 +5,10 @@ if [[ $# -eq 0 ]] ; then exit 0 fi -echo "-- liquibase formatted sql" > "DSL/Liquibase/changelog/`date '+%s'`-$1.sql" -echo "-- changeset $(git config user.name):`date '+%s'`" >> "DSL/Liquibase/changelog/`date '+%s'`-$1.sql" +timestamp=$(date '+%Y%m%d%H%M%S') +migration_file="DSL/Liquibase/changelog/${timestamp}-$1.sql" + +echo "-- liquibase formatted sql" > "$migration_file" +echo "-- changeset $(git config user.name):$timestamp" >> "$migration_file" + +echo "Migration file created: $migration_file"