From 8b6ad42230aface8ce1c40e1529d4d6d35b8c92b Mon Sep 17 00:00:00 2001 From: "arshakyan.narek" Date: Wed, 3 Jul 2024 19:55:56 +0400 Subject: [PATCH 01/12] feat(Export): add export helper --- configs/rollup.config.js | 3 +- package.json | 5 + src/index.ts | 1 + src/lib/atoms/Export/Export.stories.tsx | 167 ++++++++++++++++++++++ src/lib/atoms/Export/exportHelper.ts | 177 ++++++++++++++++++++++++ src/lib/atoms/Export/index.tsx | 1 + 6 files changed, 353 insertions(+), 1 deletion(-) create mode 100644 src/lib/atoms/Export/Export.stories.tsx create mode 100644 src/lib/atoms/Export/exportHelper.ts create mode 100644 src/lib/atoms/Export/index.tsx diff --git a/configs/rollup.config.js b/configs/rollup.config.js index a489f481..5d04207e 100644 --- a/configs/rollup.config.js +++ b/configs/rollup.config.js @@ -28,7 +28,8 @@ const TSComponentsList = [ 'Button', 'Image', 'Rating', - 'InteractiveWidget' + 'InteractiveWidget', + 'Export' ]; const getInputs = (name, dir) => { diff --git a/package.json b/package.json index 2c745489..b278f355 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "@storybook/react-webpack5": "^7.6.15", "@storybook/theming": "^7.6.15", "@types/enzyme-adapter-react-16": "^1.0.9", + "@types/file-saver": "^2.0.7", "@types/jest": "^29.5.11", "autoprefixer": "^10.4.13", "babel-jest": "^29.7.0", @@ -141,6 +142,7 @@ "semantic-release": "^19.0.5", "storybook": "^7.6.15", "storybook-dark-mode": "^3.0.3", + "string-to-html": "^1.3.4", "style-loader": "^3.3.1", "stylelint": "^14.14.0", "stylelint-config-prettier": "^9.0.3", @@ -160,13 +162,16 @@ "dayjs": "^1.11.5", "draft-js": "^0.11.7", "draftjs-to-html": "^0.9.1", + "exceljs": "^4.4.0", "file-saver": "^2.0.5", "highcharts": "^10.2.1", "highcharts-react-official": "^3.1.0", "html-to-draftjs": "^1.5.0", + "html-to-image": "^1.11.11", "i": "^0.3.7", "immer": "^9.0.18", "jodit-react": "^1.3.23", + "jspdf": "^2.5.1", "npm": "^9.4.1", "pure-react-carousel": "^1.30.1", "qrcode.react": "^3.1.0", diff --git a/src/index.ts b/src/index.ts index fa0afdcb..09fda904 100755 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ // Atoms export { default as TextLink } from './lib/atoms/TextLink'; export { default as Button } from './lib/atoms/Button'; +export { default as Export } from './lib/atoms/Export'; export { default as Label } from './lib/atoms/Label'; export { default as Icon } from './lib/atoms/Icon'; export { default as Switcher } from './lib/atoms/Switcher'; diff --git a/src/lib/atoms/Export/Export.stories.tsx b/src/lib/atoms/Export/Export.stories.tsx new file mode 100644 index 00000000..7b7e4290 --- /dev/null +++ b/src/lib/atoms/Export/Export.stories.tsx @@ -0,0 +1,167 @@ +import React, { useRef, useState } from 'react'; + +// Helpers +import { args } from '../../../../stories/assets/storybook.globals'; +import * as ExportHelper from './exportHelper'; +import Button from '../Button/Button'; +import InteractiveWidget from '../../molecules/InteractiveWidget/InteractiveWidget'; +import RichEditor from '../../organisms/RichEditor'; +import toHtml from 'string-to-html'; // Components +const meta = { + title: 'Atoms/Export', + argTypes: {}, + args: {} +}; + +export default meta; + +export const ExelFormats = () => { + const [currentFunction, setCurrentFunction] = useState<'xls' | 'csv' | 'xlsx'>('xlsx'); + const createExel: ExportHelper.IData[] = [ + { + test1: { + value: 'TestWithStyle', + style: { + bold: true, + color: '#7ad165', + fontSize: 18, + italic: true + } + }, + test2: 'test', + test3: 'test' + }, + { + test1: 'test', + test2: 'test', + test3: { + value: 'Styled value', + style: { + background: '#e33832', + fontSize: 5 + } + } + } + ]; + + const exportHandler = () => { + ExportHelper[currentFunction](createExel, [ + { header: 'test1', key: 'test1' }, + { header: 'test2', key: 'test2' }, + { header: 'test3', key: 'test3' } + ]); + }; + + return ( +
+
+
 {JSON.stringify(createExel, null, 2)} 
+
{' '} + + {' '} +
+ ); +}; + +export const ExportAsImage = () => { + const ref = useRef(null); + const [imageType, setImageType] = useState('toJpeg'); + + const exportHandler = () => { + if (ref.current) { + ExportHelper.exportImage(ref.current, imageType); + } + }; + + return ( + <> +
+
+ + +
+
+ +
+
+ + +
+
+ + + + ); +}; + +export const ExportAsPdf = () => { + const ref = useRef(null); + + const exportHandler = () => { + if (ref.current) { + ExportHelper.pdf(ref.current); + } + }; + const getData = (e) => { + const div = document.createElement('div'); + div.style.width = '400px'; + div.style.margin = '5px'; + div.appendChild(toHtml(e)); + ref.current = div; + }; + return ( +
+ {/**@ts-ignore */} + + {/**@ts-ignore */} + +
+ ); +}; diff --git a/src/lib/atoms/Export/exportHelper.ts b/src/lib/atoms/Export/exportHelper.ts new file mode 100644 index 00000000..07955985 --- /dev/null +++ b/src/lib/atoms/Export/exportHelper.ts @@ -0,0 +1,177 @@ +import React from 'react'; +import jsPDF from 'jspdf'; +import * as ImageExporter from 'html-to-image'; +import ExcelJS from 'exceljs'; +import { saveAs } from 'file-saver'; + +//TODO: change file location after tests + +const transformData = { + font: {} as Record, + fontSize(size: number) { + this.font.size = size; + + return { font: this.font }; + }, + color(color) { + if (!this.font.color) { + this.font.color = {}; + } + this.font.color = { argb: color.replace('#', '') }; + return { font: this.font }; + }, + bold(isBold: boolean) { + this.font.bold = isBold; + return { font: this.font }; + }, + italic(isItalic: boolean) { + this.font.italic = isItalic; + return { font: this.font }; + }, + underline(isUnderline: boolean) { + this.font.underline = isUnderline; + return { font: this.font }; + }, + background: (color: `#${string}`) => ({ + fill: { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: color.replace('#', '') } + } + }) +}; + +interface IDataWithStyle { + style: { + fontSize?: number; + color?: `#${string}`; + bold?: boolean; + italic?: boolean; + underline?: boolean; + background?: `#${string}`; + }; + value: string | number; +} + +export interface IData { + [keys: string]: IDataWithStyle | string; +} + +export const pdf = (HTMLelement: HTMLElement, fileName: string = 'document') => { + const doc = new jsPDF({ + format: 'a4', + unit: 'px' + }); + + doc.html(HTMLelement, { + callback(doc) { + doc.save(fileName); + }, + x: 10, + y: 10 + }); +}; + +export type ImageFormats = Exclude< + keyof typeof ImageExporter, + 'toPixelData' | 'toBlob' | 'toCanvas' | 'toBlob' | 'getFontEmbedCSS' +>; +export const exportImage = async ( + HTMLelement: HTMLElement, + format: ImageFormats = 'toPng', + + fileName: string = 'document' +) => { + try { + const request = await ImageExporter[format](HTMLelement, { cacheBust: true, height: 1000 }); + const link = document.createElement('a'); + link.download = fileName; + link.href = request; + link.click(); + } catch (error) { + alert((error as Error).message); + } +}; + +const tableFormats = async ( + data: IData[], + header?: Partial[], + fileName: string = 'document', + type: 'xlsx' | 'csv' = 'xlsx' +) => { + const workbook = new ExcelJS.Workbook(); + const worksheet = workbook.addWorksheet('Sheet1'); + + if (!header) { + const getHeaderKeys = data.reduce((aggr: string[], val) => { + aggr = [...Object.keys(val), ...aggr]; + return aggr; + }, []); + + const createHeder = [...new Set(getHeaderKeys)].reduce((aggr: Record[], val) => { + aggr.push({ header: val, key: val }); + return aggr; + }, []); + worksheet.columns = createHeder; + } else { + worksheet.columns = header; + } + const allIndex: Set> = new Set(); + let transformedStyles = {}; + let allRow: Record = {}; + data.forEach((items, rowIndex) => { + let rows = {}; + Object.keys(items).forEach((element, colIndex) => { + const isObject = typeof items[element] === 'object'; + rows[element] = isObject ? (items[element] as IDataWithStyle).value : items[element]; + allIndex.add({ element: items[element], colIndex: colIndex, rowIndex: rowIndex }); + if (isObject) { + const currentElement = items[element] as IDataWithStyle; + transformedStyles = { + ...transformedStyles, + [currentElement.value]: { + ...transformedStyles[currentElement.value], + style: currentElement.style + } + }; + } + }); + const row = worksheet.addRow(rows); + allRow[rowIndex] = row; + }); + + Object.keys(transformedStyles).forEach((el) => { + let styles = transformedStyles[el].style; + transformData.font = {}; + Object.keys(styles).forEach((key) => { + transformedStyles[el].style = { ...transformedStyles[el].style, ...transformData[key](styles[key]) }; + delete transformedStyles[el].style[key]; + }); + }); + + allIndex.forEach((el) => { + if (typeof el.element === 'object') { + allRow[el.rowIndex as number].getCell(((el.colIndex as number) + 1) as number).style = + transformedStyles[el.element.value].style; + console.log(transformedStyles[el.element.value].style); + } + }); + + let buffer; + if (type === 'csv') { + buffer = await workbook.csv.writeBuffer(); + } else { + buffer = await workbook.xlsx.writeBuffer(); + } + + const blob = new Blob([buffer], { + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + }); + saveAs(blob, `${fileName}.${type}`); +}; + +export const xlsx = (data: IData[], header?: Partial[], documentName?: string) => + tableFormats(data, header, documentName); + +export const csv = (data: IData[], header?: Partial[], documentName?: string) => + tableFormats(data, header, documentName, 'csv'); diff --git a/src/lib/atoms/Export/index.tsx b/src/lib/atoms/Export/index.tsx new file mode 100644 index 00000000..1836aff7 --- /dev/null +++ b/src/lib/atoms/Export/index.tsx @@ -0,0 +1 @@ +export * as default from './exportHelper'; From 2875d72acfc269586c0893d34ccba1beefe5a7c7 Mon Sep 17 00:00:00 2001 From: "arshakyan.narek" Date: Thu, 4 Jul 2024 10:44:54 +0400 Subject: [PATCH 02/12] fix(Export): remove console.log --- src/lib/atoms/Export/exportHelper.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/atoms/Export/exportHelper.ts b/src/lib/atoms/Export/exportHelper.ts index 07955985..f66ca0d9 100644 --- a/src/lib/atoms/Export/exportHelper.ts +++ b/src/lib/atoms/Export/exportHelper.ts @@ -151,9 +151,8 @@ const tableFormats = async ( allIndex.forEach((el) => { if (typeof el.element === 'object') { - allRow[el.rowIndex as number].getCell(((el.colIndex as number) + 1) as number).style = + allRow[el.rowIndex as number].getCell((el.colIndex as number) + 1).style = transformedStyles[el.element.value].style; - console.log(transformedStyles[el.element.value].style); } }); From b84c3eb44809c013aadafb0d7d2a4fe18aedd327 Mon Sep 17 00:00:00 2001 From: "arshakyan.narek" Date: Thu, 4 Jul 2024 12:25:08 +0400 Subject: [PATCH 03/12] fix(Export): add chart for example and fix type issue --- src/lib/atoms/Export/Export.stories.tsx | 22 +++++++++-- src/lib/atoms/Export/exportHelper.ts | 51 ++++++++++++------------- 2 files changed, 44 insertions(+), 29 deletions(-) diff --git a/src/lib/atoms/Export/Export.stories.tsx b/src/lib/atoms/Export/Export.stories.tsx index 7b7e4290..472499a3 100644 --- a/src/lib/atoms/Export/Export.stories.tsx +++ b/src/lib/atoms/Export/Export.stories.tsx @@ -7,6 +7,7 @@ import Button from '../Button/Button'; import InteractiveWidget from '../../molecules/InteractiveWidget/InteractiveWidget'; import RichEditor from '../../organisms/RichEditor'; import toHtml from 'string-to-html'; // Components +import ColumnChart from '../../molecules/Charts/ColumnChart'; const meta = { title: 'Atoms/Export', argTypes: {}, @@ -17,7 +18,7 @@ export default meta; export const ExelFormats = () => { const [currentFunction, setCurrentFunction] = useState<'xls' | 'csv' | 'xlsx'>('xlsx'); - const createExel: ExportHelper.IData[] = [ + const createExel: ExportHelper.DataType[] = [ { test1: { value: 'TestWithStyle', @@ -64,7 +65,6 @@ export const ExelFormats = () => { > - diff --git a/src/lib/atoms/Export/exportHelper.ts b/src/lib/atoms/Export/exportHelper.ts index f66ca0d9..d0023a53 100644 --- a/src/lib/atoms/Export/exportHelper.ts +++ b/src/lib/atoms/Export/exportHelper.ts @@ -6,6 +6,25 @@ import { saveAs } from 'file-saver'; //TODO: change file location after tests +interface IDataWithStyle { + style: { + fontSize?: number; + color?: `#${string}`; + bold?: boolean; + italic?: boolean; + underline?: boolean; + background?: `#${string}`; + }; + value: string | number; +} + +export type DataType = Record; + +export type ImageFormats = Exclude< + keyof typeof ImageExporter, + 'toPixelData' | 'toBlob' | 'toCanvas' | 'toBlob' | 'getFontEmbedCSS' +>; + const transformData = { font: {} as Record, fontSize(size: number) { @@ -13,7 +32,7 @@ const transformData = { return { font: this.font }; }, - color(color) { + color(color: `#${string}`) { if (!this.font.color) { this.font.color = {}; } @@ -41,22 +60,6 @@ const transformData = { }) }; -interface IDataWithStyle { - style: { - fontSize?: number; - color?: `#${string}`; - bold?: boolean; - italic?: boolean; - underline?: boolean; - background?: `#${string}`; - }; - value: string | number; -} - -export interface IData { - [keys: string]: IDataWithStyle | string; -} - export const pdf = (HTMLelement: HTMLElement, fileName: string = 'document') => { const doc = new jsPDF({ format: 'a4', @@ -72,10 +75,6 @@ export const pdf = (HTMLelement: HTMLElement, fileName: string = 'document') => }); }; -export type ImageFormats = Exclude< - keyof typeof ImageExporter, - 'toPixelData' | 'toBlob' | 'toCanvas' | 'toBlob' | 'getFontEmbedCSS' ->; export const exportImage = async ( HTMLelement: HTMLElement, format: ImageFormats = 'toPng', @@ -94,7 +93,7 @@ export const exportImage = async ( }; const tableFormats = async ( - data: IData[], + data: DataType[], header?: Partial[], fileName: string = 'document', type: 'xlsx' | 'csv' = 'xlsx' @@ -107,7 +106,6 @@ const tableFormats = async ( aggr = [...Object.keys(val), ...aggr]; return aggr; }, []); - const createHeder = [...new Set(getHeaderKeys)].reduce((aggr: Record[], val) => { aggr.push({ header: val, key: val }); return aggr; @@ -116,6 +114,7 @@ const tableFormats = async ( } else { worksheet.columns = header; } + const allIndex: Set> = new Set(); let transformedStyles = {}; let allRow: Record = {}; @@ -156,7 +155,7 @@ const tableFormats = async ( } }); - let buffer; + let buffer: ExcelJS.Buffer; if (type === 'csv') { buffer = await workbook.csv.writeBuffer(); } else { @@ -169,8 +168,8 @@ const tableFormats = async ( saveAs(blob, `${fileName}.${type}`); }; -export const xlsx = (data: IData[], header?: Partial[], documentName?: string) => +export const xlsx = (data: DataType[], header?: Partial[], documentName?: string) => tableFormats(data, header, documentName); -export const csv = (data: IData[], header?: Partial[], documentName?: string) => +export const csv = (data: DataType[], header?: Partial[], documentName?: string) => tableFormats(data, header, documentName, 'csv'); From 2dd5f4819bde60ecadbfd18f94a5582c24fb4514 Mon Sep 17 00:00:00 2001 From: "arshakyan.narek" Date: Wed, 10 Jul 2024 10:47:44 +0400 Subject: [PATCH 04/12] fix(Export): wrap all functions in try catch and fix bug with table in exel --- src/lib/atoms/Export/Export.stories.tsx | 47 +++++-- src/lib/atoms/Export/exportHelper.ts | 166 +++++++++++++----------- 2 files changed, 124 insertions(+), 89 deletions(-) diff --git a/src/lib/atoms/Export/Export.stories.tsx b/src/lib/atoms/Export/Export.stories.tsx index 472499a3..d804b95a 100644 --- a/src/lib/atoms/Export/Export.stories.tsx +++ b/src/lib/atoms/Export/Export.stories.tsx @@ -8,6 +8,7 @@ import InteractiveWidget from '../../molecules/InteractiveWidget/InteractiveWidg import RichEditor from '../../organisms/RichEditor'; import toHtml from 'string-to-html'; // Components import ColumnChart from '../../molecules/Charts/ColumnChart'; + const meta = { title: 'Atoms/Export', argTypes: {}, @@ -21,36 +22,59 @@ export const ExelFormats = () => { const createExel: ExportHelper.DataType[] = [ { test1: { + value: 'Zzzzz', + style: { + color: '#e91e63', + fontSize: 10 + } + }, + test2: { value: 'TestWithStyle', style: { bold: true, - color: '#7ad165', - fontSize: 18, + color: '#e91e63', + fontSize: 30, italic: true } }, - test2: 'test', test3: 'test' }, { - test1: 'test', + test1: { + value: 'Zzzzz2', + style: { + color: '#e91e63', + fontSize: 30, + italic: true, + bold: true + } + }, test2: 'test', + test4: 'test4', + test3: { value: 'Styled value', style: { - background: '#e33832', - fontSize: 5 + background: '#e91e63', + fontSize: 10 + } + } + }, + { + test5: 'loyality', + test2: 'loyality', + test3: { + value: 'ffdggdgd', + style: { + background: '#e91e63', + fontSize: 25 } } } ]; const exportHandler = () => { - ExportHelper[currentFunction](createExel, [ - { header: 'test1', key: 'test1' }, - { header: 'test2', key: 'test2' }, - { header: 'test3', key: 'test3' } - ]); + ExportHelper[currentFunction](createExel); }; return ( @@ -167,6 +191,7 @@ export const ExportAsPdf = () => { div.appendChild(toHtml(e)); ref.current = div; }; + return (
{/**@ts-ignore */} diff --git a/src/lib/atoms/Export/exportHelper.ts b/src/lib/atoms/Export/exportHelper.ts index d0023a53..9466b2f7 100644 --- a/src/lib/atoms/Export/exportHelper.ts +++ b/src/lib/atoms/Export/exportHelper.ts @@ -1,10 +1,11 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import jsPDF from 'jspdf'; import * as ImageExporter from 'html-to-image'; import ExcelJS from 'exceljs'; import { saveAs } from 'file-saver'; - +import html2canvas from 'html2canvas'; //TODO: change file location after tests +import ReactToPrint from 'react-to-print'; interface IDataWithStyle { style: { @@ -22,7 +23,7 @@ export type DataType = Record; export type ImageFormats = Exclude< keyof typeof ImageExporter, - 'toPixelData' | 'toBlob' | 'toCanvas' | 'toBlob' | 'getFontEmbedCSS' + 'toPixelData' | 'toBlob' | 'toCanvas' | 'getFontEmbedCSS' >; const transformData = { @@ -61,24 +62,27 @@ const transformData = { }; export const pdf = (HTMLelement: HTMLElement, fileName: string = 'document') => { - const doc = new jsPDF({ - format: 'a4', - unit: 'px' - }); + try { + const doc = new jsPDF({ + format: 'a4', + unit: 'px' + }); - doc.html(HTMLelement, { - callback(doc) { - doc.save(fileName); - }, - x: 10, - y: 10 - }); + doc.html(HTMLelement, { + callback(doc) { + doc.save(fileName); + }, + x: 10, + y: 10 + }); + } catch (error) { + return error; + } }; export const exportImage = async ( HTMLelement: HTMLElement, format: ImageFormats = 'toPng', - fileName: string = 'document' ) => { try { @@ -88,7 +92,7 @@ export const exportImage = async ( link.href = request; link.click(); } catch (error) { - alert((error as Error).message); + return error; } }; @@ -98,74 +102,80 @@ const tableFormats = async ( fileName: string = 'document', type: 'xlsx' | 'csv' = 'xlsx' ) => { - const workbook = new ExcelJS.Workbook(); - const worksheet = workbook.addWorksheet('Sheet1'); - - if (!header) { - const getHeaderKeys = data.reduce((aggr: string[], val) => { - aggr = [...Object.keys(val), ...aggr]; - return aggr; - }, []); - const createHeder = [...new Set(getHeaderKeys)].reduce((aggr: Record[], val) => { - aggr.push({ header: val, key: val }); - return aggr; - }, []); - worksheet.columns = createHeder; - } else { - worksheet.columns = header; - } - - const allIndex: Set> = new Set(); - let transformedStyles = {}; - let allRow: Record = {}; - data.forEach((items, rowIndex) => { - let rows = {}; - Object.keys(items).forEach((element, colIndex) => { - const isObject = typeof items[element] === 'object'; - rows[element] = isObject ? (items[element] as IDataWithStyle).value : items[element]; - allIndex.add({ element: items[element], colIndex: colIndex, rowIndex: rowIndex }); - if (isObject) { - const currentElement = items[element] as IDataWithStyle; - transformedStyles = { - ...transformedStyles, - [currentElement.value]: { - ...transformedStyles[currentElement.value], - style: currentElement.style - } - }; - } + try { + const workbook = new ExcelJS.Workbook(); + const worksheet = workbook.addWorksheet('Sheet1'); + let transformedStyles = {}; + let allRow: Record = {}; + let dataFromHeader: string[] = []; + if (!header) { + const getHeaderKeys = data.reduce((aggr: string[], val) => { + aggr = [...aggr, ...Object.keys(val)]; + return aggr; + }, []); + const createHeder = [...new Set(getHeaderKeys)].reduce((aggr: Record[], val) => { + dataFromHeader.push(val); + aggr.push({ header: val, key: val }); + return aggr; + }, []); + worksheet.columns = createHeder; + } else { + header.forEach((el) => { + dataFromHeader.push(el.key!); + }); + worksheet.columns = header; + } + const allIndex: Set> = new Set(); + data.forEach((items, rowIndex) => { + let rows = {}; + dataFromHeader.forEach((element, colIndex) => { + const isObject = typeof items[element] === 'object'; + rows[element] = isObject ? (items[element] as IDataWithStyle).value : items[element]; + allIndex.add({ element: items[element], colIndex: colIndex + 1, rowIndex: rowIndex }); + + if (isObject) { + const currentElement = items[element] as IDataWithStyle; + transformedStyles = { + ...transformedStyles, + [currentElement.value]: { + ...transformedStyles[currentElement.value], + style: currentElement.style + } + }; + } + }); + const row = worksheet.addRow(rows); + allRow[rowIndex] = row; }); - const row = worksheet.addRow(rows); - allRow[rowIndex] = row; - }); - Object.keys(transformedStyles).forEach((el) => { - let styles = transformedStyles[el].style; - transformData.font = {}; - Object.keys(styles).forEach((key) => { - transformedStyles[el].style = { ...transformedStyles[el].style, ...transformData[key](styles[key]) }; - delete transformedStyles[el].style[key]; + Object.keys(transformedStyles).forEach((el) => { + let styles = transformedStyles[el].style; + transformData.font = {}; + Object.keys(styles).forEach((key) => { + transformedStyles[el].style = { ...transformedStyles[el].style, ...transformData[key](styles[key]) }; + delete transformedStyles[el].style[key]; + }); }); - }); - allIndex.forEach((el) => { - if (typeof el.element === 'object') { - allRow[el.rowIndex as number].getCell((el.colIndex as number) + 1).style = - transformedStyles[el.element.value].style; + allIndex.forEach((el) => { + if (typeof el.element === 'object') { + allRow[el.rowIndex as number].getCell(el.colIndex as number).style = + transformedStyles[el.element.value].style; + } + }); + let buffer: ExcelJS.Buffer; + if (type === 'csv') { + buffer = await workbook.csv.writeBuffer(); + } else { + buffer = await workbook.xlsx.writeBuffer(); } - }); - - let buffer: ExcelJS.Buffer; - if (type === 'csv') { - buffer = await workbook.csv.writeBuffer(); - } else { - buffer = await workbook.xlsx.writeBuffer(); + const blob = new Blob([buffer], { + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + }); + saveAs(blob, `${fileName}.${type}`); + } catch (error) { + return error; } - - const blob = new Blob([buffer], { - type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' - }); - saveAs(blob, `${fileName}.${type}`); }; export const xlsx = (data: DataType[], header?: Partial[], documentName?: string) => From f03fc551165eeed90747fde6ef1f0472c9a8ac47 Mon Sep 17 00:00:00 2001 From: "arshakyan.narek" Date: Wed, 10 Jul 2024 10:50:42 +0400 Subject: [PATCH 05/12] fix(Export): remove unused imports --- src/lib/atoms/Export/exportHelper.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/atoms/Export/exportHelper.ts b/src/lib/atoms/Export/exportHelper.ts index 9466b2f7..4c83520d 100644 --- a/src/lib/atoms/Export/exportHelper.ts +++ b/src/lib/atoms/Export/exportHelper.ts @@ -1,11 +1,9 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import jsPDF from 'jspdf'; import * as ImageExporter from 'html-to-image'; import ExcelJS from 'exceljs'; import { saveAs } from 'file-saver'; -import html2canvas from 'html2canvas'; //TODO: change file location after tests -import ReactToPrint from 'react-to-print'; interface IDataWithStyle { style: { From 39a2a1a48a2b1457b29c91bfe37628cf733e955a Mon Sep 17 00:00:00 2001 From: "arshakyan.narek" Date: Thu, 11 Jul 2024 14:49:17 +0400 Subject: [PATCH 06/12] feat(Export): add functionality to style the table header --- src/lib/atoms/Export/Export.stories.tsx | 27 ++++++- src/lib/atoms/Export/exportHelper.ts | 95 +++++++++++++++++-------- 2 files changed, 88 insertions(+), 34 deletions(-) diff --git a/src/lib/atoms/Export/Export.stories.tsx b/src/lib/atoms/Export/Export.stories.tsx index d804b95a..e247c716 100644 --- a/src/lib/atoms/Export/Export.stories.tsx +++ b/src/lib/atoms/Export/Export.stories.tsx @@ -19,6 +19,26 @@ export default meta; export const ExelFormats = () => { const [currentFunction, setCurrentFunction] = useState<'xls' | 'csv' | 'xlsx'>('xlsx'); + + const exelHeader: ExportHelper.ITableHeader[] = [ + { + header: 'Test2', + key: 'test2', + style: { + fontSize: 10, + color: '#e91e63' + } + }, + { + header: 'Test5', + key: 'test5', + style: { + fontSize: 20, + color: '#e91e63' + } + } + ]; + const createExel: ExportHelper.DataType[] = [ { test1: { @@ -74,13 +94,14 @@ export const ExelFormats = () => { ]; const exportHandler = () => { - ExportHelper[currentFunction](createExel); + ExportHelper[currentFunction](createExel, exelHeader); }; return (
-
-
 {JSON.stringify(createExel, null, 2)} 
+
+ Data:
 {JSON.stringify(createExel, null, 2)} 
+ Header:
 {JSON.stringify(exelHeader, null, 2)} 
{' '} diff --git a/src/lib/atoms/Export/exportHelper.ts b/src/lib/atoms/Export/exportHelper.ts index 6d75d84c..ac72ae91 100644 --- a/src/lib/atoms/Export/exportHelper.ts +++ b/src/lib/atoms/Export/exportHelper.ts @@ -6,23 +6,6 @@ import { saveAs } from 'file-saver'; import autoTable, { ColumnInput } from 'jspdf-autotable'; //TODO: change file location after tests -interface IDataWithStyle { - style: { - fontSize?: number; - color?: `#${string}`; - bold?: boolean; - italic?: boolean; - underline?: boolean; - background?: `#${string}`; - }; - value: string | number; -} - -export type ImageFormats = Exclude< - keyof typeof ImageExporter, - 'toPixelData' | 'toBlob' | 'toCanvas' | 'getFontEmbedCSS' ->; - export const exportToTablePdf = ( data: Record[], columns?: Record[], @@ -72,6 +55,7 @@ export const pdf = (HTMLelement: HTMLElement, fileName: string = 'document') => } }; +export type ImageFormats = 'toPng' | 'toJpeg'; export const exportImage = async ( HTMLelement: HTMLElement, format: ImageFormats = 'toPng', @@ -88,6 +72,17 @@ export const exportImage = async ( } }; +interface IDataWithStyle { + style: { + fontSize?: number; + color?: `#${string}`; + bold?: boolean; + italic?: boolean; + underline?: boolean; + background?: `#${string}`; + }; + value: string | number; +} export interface ITableHeader extends Partial> { style?: IDataWithStyle['style']; header: string; @@ -178,16 +173,17 @@ const tableFormats = async ( }; if (!header) { - const getHeaderKeys = data.reduce((aggr: string[], val) => { - aggr = [...aggr, ...Object.keys(val)]; - return aggr; - }, []); - const createHeder = [...new Set(getHeaderKeys)].reduce((aggr: Record[], val) => { - dataFromHeader.push(val); - aggr.push({ header: val, key: val }); - return aggr; - }, []); - worksheet.columns = createHeder; + const createHeder = data + .reduce((aggr: string[], val) => { + aggr = [...aggr, ...Object.keys(val)]; + return aggr; + }, []) + .map((val) => { + dataFromHeader.push(val); + return { header: val, key: val }; + }); + + worksheet.columns = [...new Set(createHeder)]; } else { const headerWithoutStyles = header.map((el) => { dataFromHeader.push(el.key!); @@ -240,6 +236,5 @@ const tableFormats = async ( export const xlsx = (data: DataType[], header?: ITableHeader[], documentName?: string) => tableFormats(data, header, documentName); - export const csv = (data: DataType[], header?: ITableHeader[], documentName?: string) => tableFormats(data, header, documentName, 'csv'); From 588b8b10627996800a2b5435f2c5642882d75f6d Mon Sep 17 00:00:00 2001 From: "arshakyan.narek" Date: Mon, 15 Jul 2024 16:17:16 +0400 Subject: [PATCH 09/12] fix(Export): change variable name --- package.json | 1 - src/lib/atoms/Export/exportHelper.ts | 13 +++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index d06c665d..59a7b6ca 100644 --- a/package.json +++ b/package.json @@ -193,7 +193,6 @@ "react-swipeable": "^7.0.0", "react-tiny-popover": "^3.4.5", "react-tiny-popover-latest": "npm:react-tiny-popover@^6.0.4", - "react-to-print": "^2.15.1", "react-virtualized": "^9.21.1", "resize-observer-polyfill": "^1.5.1", "xlsx": "^0.18.5" diff --git a/src/lib/atoms/Export/exportHelper.ts b/src/lib/atoms/Export/exportHelper.ts index ac72ae91..80d7fdc5 100644 --- a/src/lib/atoms/Export/exportHelper.ts +++ b/src/lib/atoms/Export/exportHelper.ts @@ -133,12 +133,14 @@ const tableFormats = async ( type: 'xlsx' | 'csv' = 'xlsx' ) => { try { + let transformedStyles = {}; + const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet(); - let transformedStyles = {}; - let allRow: Record = {}; - let dataFromHeader: string[] = []; + const allRows: Record = {}; + const dataFromHeader: string[] = []; const allIndex: AllIndexType = new Set(); + const transformedData = (transformedStyles: Record>) => { Object.keys(transformedStyles).forEach((el) => { let styles = transformedStyles[el].style; @@ -157,7 +159,7 @@ const tableFormats = async ( const transformAllIndex = (allIndex: AllIndexType) => { allIndex.forEach((el) => { if (typeof el.element === 'object') { - allRow[el.rowIndex as number].getCell(el.colIndex as number).style = + allRows[el.rowIndex as number].getCell(el.colIndex as number).style = transformedStyles[el.element.value].style; } }); @@ -210,12 +212,11 @@ const tableFormats = async ( if (isObject) { const currentElement = items[element] as IDataWithStyle; - fillTransformedStyles(currentElement.value, currentElement.style); } }); const row = worksheet.addRow(rows); - allRow[rowIndex] = row; + allRows[rowIndex] = row; }); transformedData(transformedStyles); transformAllIndex(allIndex); From 075762d08840fe765a0472407ffc5fd4b6ef44d2 Mon Sep 17 00:00:00 2001 From: "arshakyan.narek" Date: Wed, 11 Sep 2024 16:31:54 +0400 Subject: [PATCH 10/12] fix(exportHelper): improve code --- src/lib/atoms/Export/exportHelper.ts | 37 +++++++++++++++------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/lib/atoms/Export/exportHelper.ts b/src/lib/atoms/Export/exportHelper.ts index 80d7fdc5..0d2f80f2 100644 --- a/src/lib/atoms/Export/exportHelper.ts +++ b/src/lib/atoms/Export/exportHelper.ts @@ -93,7 +93,7 @@ export type DataType = Record; type AllIndexType = Set>; const transformData = { - font: {} as Record, + font: {}, fontSize(size: number) { this.font.size = size; @@ -126,6 +126,23 @@ const transformData = { } }) }; + +const transformedData = (transformedStyles: Record>) => { + Object.keys(transformedStyles).forEach((el) => { + let styles = transformedStyles[el].style; + transformData.font = {}; + Object.keys(styles).forEach((key) => { + if (typeof transformData[key] === 'function') { + transformedStyles[el].style = { + ...transformedStyles[el].style, + ...transformData[key](styles[key]) + }; + } + delete transformedStyles[el].style[key]; + }); + }); +}; + const tableFormats = async ( data: DataType[], header?: ITableHeader[], @@ -141,21 +158,6 @@ const tableFormats = async ( const dataFromHeader: string[] = []; const allIndex: AllIndexType = new Set(); - const transformedData = (transformedStyles: Record>) => { - Object.keys(transformedStyles).forEach((el) => { - let styles = transformedStyles[el].style; - transformData.font = {}; - Object.keys(styles).forEach((key) => { - if (typeof transformData[key] === 'function') { - transformedStyles[el].style = { - ...transformedStyles[el].style, - ...transformData[key](styles[key]) - }; - } - delete transformedStyles[el].style[key]; - }); - }); - }; const transformAllIndex = (allIndex: AllIndexType) => { allIndex.forEach((el) => { if (typeof el.element === 'object') { @@ -164,6 +166,7 @@ const tableFormats = async ( } }); }; + const fillTransformedStyles = (key: string | number, style: IDataWithStyle['style']) => { transformedStyles = { ...transformedStyles, @@ -184,7 +187,6 @@ const tableFormats = async ( dataFromHeader.push(val); return { header: val, key: val }; }); - worksheet.columns = [...new Set(createHeder)]; } else { const headerWithoutStyles = header.map((el) => { @@ -237,5 +239,6 @@ const tableFormats = async ( export const xlsx = (data: DataType[], header?: ITableHeader[], documentName?: string) => tableFormats(data, header, documentName); + export const csv = (data: DataType[], header?: ITableHeader[], documentName?: string) => tableFormats(data, header, documentName, 'csv'); From 332636e423148fd5c48803b3aa488545cfa7424e Mon Sep 17 00:00:00 2001 From: "arshakyan.narek" Date: Wed, 11 Sep 2024 17:29:24 +0400 Subject: [PATCH 11/12] fix(exportHelper): remove unusable packages --- package.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/package.json b/package.json index 59a7b6ca..0863e2f3 100644 --- a/package.json +++ b/package.json @@ -158,9 +158,6 @@ "yargs": "^17.7.2" }, "dependencies": { - "@ironsoftware/ironpdf": "^2024.6.4", - "@ironsoftware/ironpdf-engine-windows-x64": "^2024.6.4", - "@ironsoftware/ironpdf-engine-windows-x86": "^2024.6.4", "classnames": "^2.3.2", "dayjs": "^1.11.5", "draft-js": "^0.11.7", @@ -171,7 +168,6 @@ "highcharts-react-official": "^3.1.0", "html-to-draftjs": "^1.5.0", "html-to-image": "^1.11.11", - "html2canvas": "^1.4.1", "i": "^0.3.7", "immer": "^9.0.18", "jodit-react": "^1.3.23", From 800567077925f8cabc9bc0726a8160f7df19140b Mon Sep 17 00:00:00 2001 From: "arshakyan.narek" Date: Wed, 11 Sep 2024 18:48:31 +0400 Subject: [PATCH 12/12] fix(ExportHelper): unification headers fix --- src/lib/atoms/Export/Export.stories.tsx | 30 ++++++++++++--------- src/lib/atoms/Export/exportHelper.ts | 36 +++++++++++-------------- 2 files changed, 32 insertions(+), 34 deletions(-) diff --git a/src/lib/atoms/Export/Export.stories.tsx b/src/lib/atoms/Export/Export.stories.tsx index 62bc4486..903267ce 100644 --- a/src/lib/atoms/Export/Export.stories.tsx +++ b/src/lib/atoms/Export/Export.stories.tsx @@ -39,6 +39,15 @@ export const ExelFormats = () => { color: '#e91e63', bold: true } + }, + { + header: 'Zzzz', + key: 'test1', + style: { + fontSize: 20, + color: '#e91e63', + bold: true + } } ]; @@ -47,17 +56,17 @@ export const ExelFormats = () => { test1: { value: 'Zzzzz', style: { - color: '#e91e63', + color: '#bd8faa', fontSize: 10 } }, + test5: 'loyality', + test2: { value: 'TestWithStyle', style: { - bold: true, color: '#e91e63', - fontSize: 30, - italic: true + fontSize: 10 } }, test3: 'test' @@ -66,19 +75,14 @@ export const ExelFormats = () => { test1: { value: 'Zzzzz2', style: { - color: '#e91e63', - fontSize: 30, - italic: true, - bold: true + fontSize: 10 } }, test2: 'test', test4: 'test4', - test3: { value: 'Styled value', style: { - background: '#e91e63', fontSize: 10 } } @@ -89,8 +93,8 @@ export const ExelFormats = () => { test3: { value: 'ffdggdgd', style: { - background: '#e91e63', - fontSize: 25 + fontSize: 10, + bold: true } } } @@ -148,7 +152,7 @@ export const ExportTableAsPdf = () => { ]; const exportHandler = () => { - ExportHelper.exportToTablePdf(createExel, exelHeader); + ExportHelper.exportToTablePdf(createExel); }; return ( diff --git a/src/lib/atoms/Export/exportHelper.ts b/src/lib/atoms/Export/exportHelper.ts index 0d2f80f2..1009a3d7 100644 --- a/src/lib/atoms/Export/exportHelper.ts +++ b/src/lib/atoms/Export/exportHelper.ts @@ -6,6 +6,13 @@ import { saveAs } from 'file-saver'; import autoTable, { ColumnInput } from 'jspdf-autotable'; //TODO: change file location after tests +const createUniqueArr = (data: object[]) => { + return data.reduce((aggr: string[], val) => { + aggr = [...aggr, ...Object.keys(val)]; + return [...new Set(aggr)]; + }, []); +}; + export const exportToTablePdf = ( data: Record[], columns?: Record[], @@ -18,15 +25,10 @@ export const exportToTablePdf = ( const pdfColumns = columns ? columns.map((column) => ({ header: column.header, dataKey: column.key })) - : data - .reduce((aggr: string[], val) => { - aggr = [...aggr, ...Object.keys(val)]; - return aggr; - }, []) - .map((el) => ({ header: el, dataKey: el })); + : createUniqueArr(data).map((el) => ({ header: el, dataKey: el })); autoTable(doc, { - columns: [...new Set(pdfColumns)] as ColumnInput[], + columns: pdfColumns as ColumnInput[], body: data }); @@ -151,13 +153,11 @@ const tableFormats = async ( ) => { try { let transformedStyles = {}; - const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet(); const allRows: Record = {}; const dataFromHeader: string[] = []; const allIndex: AllIndexType = new Set(); - const transformAllIndex = (allIndex: AllIndexType) => { allIndex.forEach((el) => { if (typeof el.element === 'object') { @@ -166,7 +166,6 @@ const tableFormats = async ( } }); }; - const fillTransformedStyles = (key: string | number, style: IDataWithStyle['style']) => { transformedStyles = { ...transformedStyles, @@ -176,18 +175,12 @@ const tableFormats = async ( } }; }; - if (!header) { - const createHeder = data - .reduce((aggr: string[], val) => { - aggr = [...aggr, ...Object.keys(val)]; - return aggr; - }, []) - .map((val) => { - dataFromHeader.push(val); - return { header: val, key: val }; - }); - worksheet.columns = [...new Set(createHeder)]; + const createHeder = createUniqueArr(data).map((val) => { + dataFromHeader.push(val); + return { header: val, key: val }; + }); + worksheet.columns = createHeder; } else { const headerWithoutStyles = header.map((el) => { dataFromHeader.push(el.key!); @@ -205,6 +198,7 @@ const tableFormats = async ( } }); } + data.forEach((items, rowIndex) => { let rows = {}; dataFromHeader.forEach((element, colIndex) => {