@buerokratt-ria/common-gui-components 0.0.25 → 0.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,15 @@ All changes to this project will be documented in this file.
4
4
 
5
5
  ## Template [MajorVersion.MediterraneanVersion.MinorVersion] - DD-MM-YYYY
6
6
 
7
+ ## [0.0.27] - 02-10-2025
8
+
9
+ - Adding missing showTest with default to true if undefined.
10
+
11
+ ## [0.0.26] - 15-09-2025
12
+
13
+ - Added download button do download chat history with currently selected criteriasa
14
+ - Added new optional param to enable this button(disabled by default)
15
+
7
16
  ## [0.0.25] - 01-09-2025
8
17
 
9
18
  - Added test column to display chats mark for test
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buerokratt-ria/common-gui-components",
3
- "version": "0.0.25",
3
+ "version": "0.0.27",
4
4
  "description": "Common GUI components and pre defined templates.",
5
5
  "main": "index.ts",
6
6
  "author": "ExiRai",
@@ -29,6 +29,7 @@
29
29
  "downshift": "^7.0.5",
30
30
  "esbuild": "^0.19.5",
31
31
  "framer-motion": "^8.5.5",
32
+ "file-saver": "^2.0.5",
32
33
  "i18next": "^22.4.5",
33
34
  "i18next-browser-languagedetector": "^7.0.1",
34
35
  "linkify-react": "^4.1.1",
@@ -0,0 +1,31 @@
1
+ import { Buffer } from 'buffer';
2
+
3
+ export const saveFile = async (base64String: string, fileName: `${string}.${string}`, type: string) => {
4
+ const blob = new Blob([Buffer.from(base64String, 'base64')], {
5
+ type: type,
6
+ });
7
+
8
+ const extension = fileName.split('.').pop();
9
+
10
+ if (window.showSaveFilePicker) {
11
+ const handle = await window.showSaveFilePicker({
12
+ suggestedName: fileName,
13
+ types: [
14
+ {
15
+ description: extension!.toUpperCase() + ' file',
16
+ accept: { [type]: [`.${extension}` as `.${string}`] },
17
+ },
18
+ ],
19
+ });
20
+ const writable = await handle.createWritable();
21
+ await writable.write(blob);
22
+ writable.close();
23
+ } else {
24
+ const url = window.URL.createObjectURL(blob);
25
+ const a = document.createElement('a');
26
+ a.href = url;
27
+ a.download = fileName;
28
+ a.click();
29
+ window.URL.revokeObjectURL(url);
30
+ }
31
+ };
@@ -2,10 +2,11 @@ import React, {FC, PropsWithChildren, useEffect, useMemo, useRef, useState} from
2
2
  import {useTranslation} from 'react-i18next';
3
3
  import {useMutation} from '@tanstack/react-query';
4
4
  import {ColumnPinningState, createColumnHelper, PaginationState, SortingState,} from '@tanstack/react-table';
5
- import { format, startOfDay, endOfDay, formatISO } from "date-fns";
5
+ import {endOfDay, format, formatISO, startOfDay} from "date-fns";
6
6
  import {AxiosError} from 'axios';
7
7
  import './History.scss';
8
8
  import {MdOutlineRemoveRedEye} from 'react-icons/md';
9
+ import {CgSpinner} from 'react-icons/cg';
9
10
 
10
11
  import {
11
12
  Button,
@@ -35,6 +36,7 @@ import {ToastContextType} from "../../../context";
35
36
 
36
37
  import {getDomainsArray} from "../../../utils/multiDomain-utils";
37
38
  import {StoreState} from "../../../store";
39
+ import {saveFile} from "../../../services/file";
38
40
 
39
41
  type HistoryProps = {
40
42
  user: UserInfo | null;
@@ -42,6 +44,7 @@ type HistoryProps = {
42
44
  toastContext: ToastContextType | null;
43
45
  onMessageClick?: (message: any) => void;
44
46
  showComment?: boolean;
47
+ showDownload?: boolean;
45
48
  showEmail?: boolean;
46
49
  showSortingLabel?: boolean;
47
50
  showStatus?: boolean;
@@ -52,12 +55,19 @@ type HistoryProps = {
52
55
  delegatedEndDate?: string;
53
56
  }
54
57
 
58
+ type ExportResult = {
59
+ headers: string[];
60
+ rows: (string | number | null)[][];
61
+ chatIds: string[];
62
+ };
63
+
55
64
  const ChatHistory: FC<PropsWithChildren<HistoryProps>> = ({
56
65
  user,
57
66
  userDomains,
58
67
  toastContext,
59
68
  onMessageClick,
60
69
  showComment = true,
70
+ showDownload = false,
61
71
  showEmail = false,
62
72
  showSortingLabel = false,
63
73
  showStatus = true,
@@ -111,6 +121,9 @@ const ChatHistory: FC<PropsWithChildren<HistoryProps>> = ({
111
121
  const currentDomains = useStore.getState().userDomains;
112
122
  const multiDomainEnabled = import.meta.env.REACT_APP_ENABLE_MULTI_DOMAIN?.toLowerCase() === 'true';
113
123
  const testMessageEnabled = import.meta.env.REACT_APP_SHOW_TEST_MESSAGE?.toLowerCase() === 'true';
124
+ const envVal = import.meta.env.REACT_APP_SHOW_TEST_CONVERSATIONS;
125
+ const showTest = envVal === undefined ? true : envVal.toLowerCase() === 'true';
126
+ const [loading, setLoading] = useState(false);
114
127
 
115
128
  const parseDateParam = (dateString: string | null) => {
116
129
  if (!dateString) return new Date();
@@ -290,6 +303,7 @@ const ChatHistory: FC<PropsWithChildren<HistoryProps>> = ({
290
303
  startDate: formatISO(startOfDay(new Date(data.startDate))),
291
304
  endDate: formatISO(endOfDay(new Date(data.endDate))),
292
305
  urls: getDomainsArray(currentDomains),
306
+ showTest: showTest,
293
307
  page: data.pagination.pageIndex + 1,
294
308
  page_size: data.pagination.pageSize,
295
309
  sorting: sortBy,
@@ -810,6 +824,120 @@ const ChatHistory: FC<PropsWithChildren<HistoryProps>> = ({
810
824
  );
811
825
  };
812
826
 
827
+ const mapChatsToExportRows = (
828
+ chats: ChatType[],
829
+ allColumns: any[],
830
+ selectedColumns: string[],
831
+ t: (key: string) => string
832
+ ): ExportResult => {
833
+ const activeColumns =
834
+ selectedColumns.length > 0
835
+ ? allColumns.filter(
836
+ (col) => col.id && col.id !== 'detail' && selectedColumns.includes(col.id)
837
+ )
838
+ : allColumns.filter((col) => col.id && col.id !== 'detail');
839
+
840
+ const headers = activeColumns.map(
841
+ (col) => getColumnTranslation(col.id) || col.header || col.id
842
+ );
843
+
844
+ const rows = chats.map((chat) =>
845
+ activeColumns.map((col) => {
846
+ let rawValue: any = null;
847
+
848
+ if (typeof col.accessorFn === 'function') {
849
+ rawValue = col.accessorFn(chat, 0);
850
+ } else if (typeof col.accessorKey === 'string') {
851
+ rawValue = (chat as any)[col.accessorKey];
852
+ }
853
+
854
+ let processedValue: any = rawValue;
855
+ switch (col.id) {
856
+ case 'created':
857
+ case 'ended':
858
+ processedValue = rawValue
859
+ ? format(
860
+ new Date(rawValue),
861
+ 'dd.MM.yyyy HH:mm:ss',
862
+ i18n.language === 'et' ? {locale: et} : undefined
863
+ )
864
+ : '';
865
+ break;
866
+ case 'contactsMessage':
867
+ processedValue = rawValue ? t('global.yes') : t('global.no');
868
+ break;
869
+ case 'feedbackRating':
870
+ processedValue = rawValue != null ? `${rawValue}/10` : '';
871
+ break;
872
+ case 'status':
873
+ processedValue =
874
+ chat.status === CHAT_STATUS.ENDED
875
+ ? t('chat.plainEvents.' + (chat.lastMessageEvent ?? ''))
876
+ : '';
877
+ break;
878
+ case 'endUserName':
879
+ processedValue = `${chat.endUserFirstName ?? ''} ${chat.endUserLastName ?? ''}`;
880
+ break;
881
+ default:
882
+ processedValue =
883
+ typeof rawValue === 'object' ? JSON.stringify(rawValue) : rawValue ?? '';
884
+ }
885
+
886
+ return processedValue;
887
+ })
888
+ );
889
+
890
+ const chatIds = chats.map((c) => c.id);
891
+
892
+ return {headers, rows, chatIds};
893
+ };
894
+
895
+
896
+ const downloadChatHistory = async () => {
897
+ setLoading(true);
898
+ try {
899
+ let sortBy = 'created desc';
900
+ if (sorting.length > 0) {
901
+ const sortType = sorting[0].desc ? 'desc' : 'asc';
902
+ sortBy = `${sorting[0].id} ${sortType}`;
903
+ }
904
+
905
+ const chats = await apiDev.post('agents/chats/ended', {
906
+ customerSupportIds: passedCustomerSupportIds,
907
+ startDate: formatISO(startOfDay(new Date(startDate))),
908
+ endDate: formatISO(endOfDay(new Date(endDate))),
909
+ urls: getDomainsArray(currentDomains),
910
+ page: 1,
911
+ page_size: 1000,
912
+ sorting: sortBy,
913
+ search,
914
+ });
915
+
916
+ const {headers, rows, chatIds} = mapChatsToExportRows(
917
+ chats.data.response,
918
+ endedChatsColumns,
919
+ selectedColumns,
920
+ t
921
+ );
922
+
923
+ const response = await apiDev.post('chats/ended/download', {
924
+ headers, rows, chatIds
925
+ });
926
+
927
+
928
+ await saveFile(
929
+ response.data.base64String,
930
+ 'history.xlsx',
931
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
932
+ );
933
+
934
+ } catch (error) {
935
+ console.error('Error getting CSV file:', error);
936
+ } finally {
937
+ setLoading(false);
938
+ }
939
+ };
940
+
813
941
  const endUserFullName = getUserName();
814
942
 
815
943
  if (!filteredEndedChatsList) return <>Loading... {{filteredEndedChatsList}} something is wrong </>;
@@ -820,6 +948,15 @@ const ChatHistory: FC<PropsWithChildren<HistoryProps>> = ({
820
948
  <h1>{t('chat.history.title')}</h1>
821
949
  )}
822
950
 
951
+ {showDownload && (
952
+ <div>
953
+ <Button appearance={"primary"} onClick={downloadChatHistory}>
954
+ {loading && <CgSpinner className="spinner"/>}
955
+ {!loading && t('files.download_xlsx')}
956
+ </Button>
957
+ </div>
958
+ )}
959
+
823
960
  <Card>
824
961
  <Track gap={16}>
825
962
  {displaySearchBar && (