@buerokratt-ria/common-gui-components 0.0.25 → 0.0.26
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,11 @@ 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.26] - 15-09-2025
|
|
8
|
+
|
|
9
|
+
- Added download button do download chat history with currently selected criteriasa
|
|
10
|
+
- Added new optional param to enable this button(disabled by default)
|
|
11
|
+
|
|
7
12
|
## [0.0.25] - 01-09-2025
|
|
8
13
|
|
|
9
14
|
- Added test column to display chats mark for test
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buerokratt-ria/common-gui-components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.26",
|
|
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",
|
package/services/file.ts
ADDED
|
@@ -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,
|
|
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,7 @@ 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 [loading, setLoading] = useState(false);
|
|
114
125
|
|
|
115
126
|
const parseDateParam = (dateString: string | null) => {
|
|
116
127
|
if (!dateString) return new Date();
|
|
@@ -810,6 +821,120 @@ const ChatHistory: FC<PropsWithChildren<HistoryProps>> = ({
|
|
|
810
821
|
);
|
|
811
822
|
};
|
|
812
823
|
|
|
824
|
+
const mapChatsToExportRows = (
|
|
825
|
+
chats: ChatType[],
|
|
826
|
+
allColumns: any[],
|
|
827
|
+
selectedColumns: string[],
|
|
828
|
+
t: (key: string) => string
|
|
829
|
+
): ExportResult => {
|
|
830
|
+
const activeColumns =
|
|
831
|
+
selectedColumns.length > 0
|
|
832
|
+
? allColumns.filter(
|
|
833
|
+
(col) => col.id && col.id !== 'detail' && selectedColumns.includes(col.id)
|
|
834
|
+
)
|
|
835
|
+
: allColumns.filter((col) => col.id && col.id !== 'detail');
|
|
836
|
+
|
|
837
|
+
const headers = activeColumns.map(
|
|
838
|
+
(col) => getColumnTranslation(col.id) || col.header || col.id
|
|
839
|
+
);
|
|
840
|
+
|
|
841
|
+
const rows = chats.map((chat) =>
|
|
842
|
+
activeColumns.map((col) => {
|
|
843
|
+
let rawValue: any = null;
|
|
844
|
+
|
|
845
|
+
if (typeof col.accessorFn === 'function') {
|
|
846
|
+
rawValue = col.accessorFn(chat, 0);
|
|
847
|
+
} else if (typeof col.accessorKey === 'string') {
|
|
848
|
+
rawValue = (chat as any)[col.accessorKey];
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
let processedValue: any = rawValue;
|
|
852
|
+
switch (col.id) {
|
|
853
|
+
case 'created':
|
|
854
|
+
case 'ended':
|
|
855
|
+
processedValue = rawValue
|
|
856
|
+
? format(
|
|
857
|
+
new Date(rawValue),
|
|
858
|
+
'dd.MM.yyyy HH:mm:ss',
|
|
859
|
+
i18n.language === 'et' ? {locale: et} : undefined
|
|
860
|
+
)
|
|
861
|
+
: '';
|
|
862
|
+
break;
|
|
863
|
+
case 'contactsMessage':
|
|
864
|
+
processedValue = rawValue ? t('global.yes') : t('global.no');
|
|
865
|
+
break;
|
|
866
|
+
case 'feedbackRating':
|
|
867
|
+
processedValue = rawValue != null ? `${rawValue}/10` : '';
|
|
868
|
+
break;
|
|
869
|
+
case 'status':
|
|
870
|
+
processedValue =
|
|
871
|
+
chat.status === CHAT_STATUS.ENDED
|
|
872
|
+
? t('chat.plainEvents.' + (chat.lastMessageEvent ?? ''))
|
|
873
|
+
: '';
|
|
874
|
+
break;
|
|
875
|
+
case 'endUserName':
|
|
876
|
+
processedValue = `${chat.endUserFirstName ?? ''} ${chat.endUserLastName ?? ''}`;
|
|
877
|
+
break;
|
|
878
|
+
default:
|
|
879
|
+
processedValue =
|
|
880
|
+
typeof rawValue === 'object' ? JSON.stringify(rawValue) : rawValue ?? '';
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
return processedValue;
|
|
884
|
+
})
|
|
885
|
+
);
|
|
886
|
+
|
|
887
|
+
const chatIds = chats.map((c) => c.id);
|
|
888
|
+
|
|
889
|
+
return {headers, rows, chatIds};
|
|
890
|
+
};
|
|
891
|
+
|
|
892
|
+
|
|
893
|
+
const downloadChatHistory = async () => {
|
|
894
|
+
setLoading(true);
|
|
895
|
+
try {
|
|
896
|
+
let sortBy = 'created desc';
|
|
897
|
+
if (sorting.length > 0) {
|
|
898
|
+
const sortType = sorting[0].desc ? 'desc' : 'asc';
|
|
899
|
+
sortBy = `${sorting[0].id} ${sortType}`;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
const chats = await apiDev.post('agents/chats/ended', {
|
|
903
|
+
customerSupportIds: passedCustomerSupportIds,
|
|
904
|
+
startDate: formatISO(startOfDay(new Date(startDate))),
|
|
905
|
+
endDate: formatISO(endOfDay(new Date(endDate))),
|
|
906
|
+
urls: getDomainsArray(currentDomains),
|
|
907
|
+
page: 1,
|
|
908
|
+
page_size: 1000,
|
|
909
|
+
sorting: sortBy,
|
|
910
|
+
search,
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
const {headers, rows, chatIds} = mapChatsToExportRows(
|
|
914
|
+
chats.data.response,
|
|
915
|
+
endedChatsColumns,
|
|
916
|
+
selectedColumns,
|
|
917
|
+
t
|
|
918
|
+
);
|
|
919
|
+
|
|
920
|
+
const response = await apiDev.post('chats/ended/download', {
|
|
921
|
+
headers, rows, chatIds
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
|
|
925
|
+
await saveFile(
|
|
926
|
+
response.data.base64String,
|
|
927
|
+
'history.xlsx',
|
|
928
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
929
|
+
);
|
|
930
|
+
|
|
931
|
+
} catch (error) {
|
|
932
|
+
console.error('Error getting CSV file:', error);
|
|
933
|
+
} finally {
|
|
934
|
+
setLoading(false);
|
|
935
|
+
}
|
|
936
|
+
};
|
|
937
|
+
|
|
813
938
|
const endUserFullName = getUserName();
|
|
814
939
|
|
|
815
940
|
if (!filteredEndedChatsList) return <>Loading... {{filteredEndedChatsList}} something is wrong </>;
|
|
@@ -820,6 +945,15 @@ const ChatHistory: FC<PropsWithChildren<HistoryProps>> = ({
|
|
|
820
945
|
<h1>{t('chat.history.title')}</h1>
|
|
821
946
|
)}
|
|
822
947
|
|
|
948
|
+
{showDownload && (
|
|
949
|
+
<div>
|
|
950
|
+
<Button appearance={"primary"} onClick={downloadChatHistory}>
|
|
951
|
+
{loading && <CgSpinner className="spinner"/>}
|
|
952
|
+
{!loading && t('files.download_xlsx')}
|
|
953
|
+
</Button>
|
|
954
|
+
</div>
|
|
955
|
+
)}
|
|
956
|
+
|
|
823
957
|
<Card>
|
|
824
958
|
<Track gap={16}>
|
|
825
959
|
{displaySearchBar && (
|
|
Binary file
|