@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
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.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",
@@ -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,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 && (