diff --git a/package-lock.json b/package-lock.json index 07a71f206..f568d3778 100644 --- a/package-lock.json +++ b/package-lock.json @@ -138,7 +138,7 @@ "webpack": "~5.96.1", "webpack-bundle-analyzer": "~4.10.2", "webpack-cli": "~5.1.4", - "xlsx": "~0.18.5" + "xlsx": "file:src/main/resources/lib/vendor/xlsx-0.20.3.tgz" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -6913,15 +6913,6 @@ "node": ">=0.4.0" } }, - "node_modules/adler-32": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", - "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -9094,19 +9085,6 @@ "node": ">=4" } }, - "node_modules/cfb": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", - "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", - "dev": true, - "dependencies": { - "adler-32": "~1.3.0", - "crc-32": "~1.2.0" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -9312,15 +9290,6 @@ "node": ">= 0.12.0" } }, - "node_modules/codepage": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", - "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, "node_modules/collect-v8-coverage": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", @@ -11771,15 +11740,6 @@ "node": ">= 6" } }, - "node_modules/frac": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", - "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -19109,18 +19069,6 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, - "node_modules/ssf": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", - "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", - "dev": true, - "dependencies": { - "frac": "~1.1.2" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -21388,24 +21336,6 @@ "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "dev": true }, - "node_modules/wmf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", - "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/word": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", - "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -21580,19 +21510,10 @@ } }, "node_modules/xlsx": { - "version": "0.18.5", - "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", - "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "version": "0.20.3", + "resolved": "file:src/main/resources/lib/vendor/xlsx-0.20.3.tgz", + "integrity": "sha512-oLDq3jw7AcLqKWH2AhCpVTZl8mf6X2YReP+Neh0SJUzV/BdZYjth94tG5toiMB1PPrYtxOCfaoUCkvtuH+3AJA==", "dev": true, - "dependencies": { - "adler-32": "~1.3.0", - "cfb": "~1.2.1", - "codepage": "~1.15.0", - "crc-32": "~1.2.1", - "ssf": "~0.11.2", - "wmf": "~1.0.1", - "word": "~0.3.0" - }, "bin": { "xlsx": "bin/xlsx.njs" }, @@ -26316,12 +26237,6 @@ "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", "dev": true }, - "adler-32": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", - "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", - "dev": true - }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -28056,16 +27971,6 @@ "integrity": "sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==", "dev": true }, - "cfb": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", - "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", - "dev": true, - "requires": { - "adler-32": "~1.3.0", - "crc-32": "~1.2.0" - } - }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -28216,12 +28121,6 @@ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true }, - "codepage": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", - "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", - "dev": true - }, "collect-v8-coverage": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", @@ -30026,12 +29925,6 @@ "mime-types": "^2.1.12" } }, - "frac": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", - "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", - "dev": true - }, "fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -35434,15 +35327,6 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, - "ssf": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", - "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", - "dev": true, - "requires": { - "frac": "~1.1.2" - } - }, "stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -36940,18 +36824,6 @@ "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "dev": true }, - "wmf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", - "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", - "dev": true - }, - "word": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", - "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", - "dev": true - }, "wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -37072,19 +36944,9 @@ "requires": {} }, "xlsx": { - "version": "0.18.5", - "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", - "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", - "dev": true, - "requires": { - "adler-32": "~1.3.0", - "cfb": "~1.2.1", - "codepage": "~1.15.0", - "crc-32": "~1.2.1", - "ssf": "~0.11.2", - "wmf": "~1.0.1", - "word": "~0.3.0" - } + "version": "file:src/main/resources/lib/vendor/xlsx-0.20.3.tgz", + "integrity": "sha512-oLDq3jw7AcLqKWH2AhCpVTZl8mf6X2YReP+Neh0SJUzV/BdZYjth94tG5toiMB1PPrYtxOCfaoUCkvtuH+3AJA==", + "dev": true }, "xmlhttprequest-ssl": { "version": "2.1.1", diff --git a/package.json b/package.json index 979e2a171..bcc14b510 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,7 @@ "webpack": "~5.96.1", "webpack-bundle-analyzer": "~4.10.2", "webpack-cli": "~5.1.4", - "xlsx": "~0.18.5" + "xlsx": "file:src/main/resources/lib/vendor/xlsx-0.20.3.tgz" }, "browserslist": [ "last 2 version", diff --git a/src/main/resources/lib/ssb/utils/tableExportUtils.ts b/src/main/resources/lib/ssb/utils/tableExportUtils.ts new file mode 100644 index 000000000..f8cfa3854 --- /dev/null +++ b/src/main/resources/lib/ssb/utils/tableExportUtils.ts @@ -0,0 +1,44 @@ +import * as XLSX from 'xlsx' +import { Title } from '/lib/types/xmlParser' + +interface TableExport { + tableElement: HTMLTableElement + fileName?: Title | string +} + +const sheetName = 'Sheet1' + +const createTableWorkbook = (tableElement: TableExport['tableElement']) => { + return XLSX.utils.table_to_book(tableElement, { + sheet: sheetName, + raw: true, + }) +} + +export const exportTableToExcel = ({ tableElement, fileName }: TableExport): void => { + const sheetFileName = fileName ? `${fileName}.xlsx` : 'tabell.xlsx' + const workbook = createTableWorkbook(tableElement) + + const worksheet = workbook.Sheets[sheetName] + Object.keys(worksheet).flatMap((cell) => { + // Check if SheetJS worksheet cell object has a value and that the cell isn't a SheetJS special property/metadata + if (cell && cell[0] !== '!') { + const cellValue = worksheet[cell].v.replace(/\s/g, '').replace(/,/g, '.') + if (cellValue !== '') { + if (!isNaN(cellValue)) { + worksheet[cell].v = parseFloat(cellValue) + worksheet[cell].t = 'n' + } + } + } + }) + + XLSX.writeFile(workbook, sheetFileName) +} + +export const exportTableToCSV = ({ tableElement, fileName }: TableExport): void => { + const sheetFileName = fileName ? `${fileName}.csv` : 'tabell.csv' + const workbook = createTableWorkbook(tableElement) + + XLSX.writeFile(workbook, sheetFileName, { bookType: 'csv', type: 'string' }) +} diff --git a/src/main/resources/lib/types/partTypes/table.ts b/src/main/resources/lib/types/partTypes/table.ts index ddc9573a7..2ce90a082 100644 --- a/src/main/resources/lib/types/partTypes/table.ts +++ b/src/main/resources/lib/types/partTypes/table.ts @@ -6,7 +6,6 @@ export interface TableProps { downloadTableLabel: string downloadTableTitle: object downloadTableOptions: DropdownItems - displayName: string table: Partial & { language: string | undefined } @@ -14,7 +13,6 @@ export interface TableProps { standardSymbol: TableStandardSymbolLink | undefined sources: SourceList sourceLabel: string - iconUrl: string showPreviewDraft: boolean paramShowDraft: boolean draftExist: boolean | undefined @@ -24,6 +22,7 @@ export interface TableProps { statBankWebUrl: string hiddenTitle: string | undefined checkIsOverflowing?: boolean + useNewTableExport: boolean } export interface TableStandardSymbolLink { diff --git a/src/main/resources/lib/vendor/xlsx-0.20.3.tgz b/src/main/resources/lib/vendor/xlsx-0.20.3.tgz new file mode 100644 index 000000000..b9df84a5c Binary files /dev/null and b/src/main/resources/lib/vendor/xlsx-0.20.3.tgz differ diff --git a/src/main/resources/main.es6 b/src/main/resources/main.es6 index 96bb76a07..2f55696b7 100644 --- a/src/main/resources/main.es6 +++ b/src/main/resources/main.es6 @@ -71,6 +71,10 @@ try { feature: 'show-popup-survey', enabled: false, }, + { + feature: 'new-table-export', + enabled: false, + }, ], }, ]) diff --git a/src/main/resources/react4xp/table/Table.tsx b/src/main/resources/react4xp/table/Table.tsx index a7f9c1218..5ef339650 100644 --- a/src/main/resources/react4xp/table/Table.tsx +++ b/src/main/resources/react4xp/table/Table.tsx @@ -15,6 +15,7 @@ import { NumericFormat } from 'react-number-format' import { Alert } from 'react-bootstrap' import { type TableProps } from '/lib/types/partTypes/table' import { PreliminaryData, type TableCellUniform } from '/lib/types/xmlParser' +import { exportTableToExcel, exportTableToCSV } from '/lib/ssb/utils/tableExportUtils' declare global { interface Window { @@ -23,20 +24,39 @@ declare global { } function Table(props: TableProps) { - const [currentTable, setCurrentTable] = useState( - props.paramShowDraft && props.draftExist ? props.tableDraft : props.table - ) - const [fetchUnPublished, setFetchUnPublished] = useState(props.paramShowDraft) - const showPreviewToggle = - props.showPreviewDraft && (!props.pageTypeStatistic || (props.paramShowDraft && props.pageTypeStatistic)) + const { + downloadTableLabel, + downloadTableTitle, + downloadTableOptions, + table, + tableDraft, + standardSymbol, + sources, + sourceLabel, + showPreviewDraft, + paramShowDraft, + draftExist, + pageTypeStatistic, + sourceListTables, + sourceTableLabel, + statBankWebUrl, + hiddenTitle, + checkIsOverflowing, + useNewTableExport, + } = props + + const [currentTable, setCurrentTable] = useState(paramShowDraft && draftExist ? tableDraft : table) + const [fetchUnPublished, setFetchUnPublished] = useState(paramShowDraft) + const showPreviewToggle = showPreviewDraft && (!pageTypeStatistic || (paramShowDraft && pageTypeStatistic)) const tableWrapperRef = useRef(null) + const tableRef = useRef(null) function trimValue(value: string | number) { return typeof value === 'string' ? value.trim() : value } function formatNumber(value: string | number) { - const language = props.table.language + const language = table.language const decimalSeparator = language === 'en' ? '.' : ',' value = trimValue(value) if (value && (typeof value === 'number' || !isNaN(Number(value)))) { @@ -56,28 +76,29 @@ function Table(props: TableProps) { } function addDownloadTableDropdown(mobile: boolean) { - if (props.downloadTableLabel && props.downloadTableTitle && props.downloadTableOptions) { - const downloadTable = (item: { id: string }) => { - if (item.id === 'downloadTableAsCSV') downloadTableAsCSV() - if (item.id === 'downloadTableAsXLSX') downloadTableAsExcel() - } + if (!downloadTableLabel || !downloadTableTitle || !downloadTableOptions) { + return null + } - return ( -
- -
- ) + const downloadTable = (item: { id: string }) => { + if (item.id === 'downloadTableAsCSV') downloadTableAsCSV() + if (item.id === 'downloadTableAsXLSX') downloadTableAsExcel() } - return null + + return ( +
+ +
+ ) } function downloadTableAsCSV() { - if (window.downloadTableFile) { + if (window.downloadTableFile && !useNewTableExport) { window.downloadTableFile(tableWrapperRef.current, { type: 'csv', fileName: 'tabell', @@ -86,10 +107,13 @@ function Table(props: TableProps) { tfootSelector: '', }) } + if (tableRef?.current && useNewTableExport) { + exportTableToCSV({ tableElement: tableRef.current, fileName: table?.caption ?? table?.caption?.content }) + } } function downloadTableAsExcel() { - if (window.downloadTableFile) { + if (window.downloadTableFile && !useNewTableExport) { window.downloadTableFile(tableWrapperRef.current, { type: 'xlsx', fileName: 'tabell', @@ -105,6 +129,9 @@ function Table(props: TableProps) { }, }) } + if (tableRef?.current && useNewTableExport) { + exportTableToExcel({ tableElement: tableRef.current, fileName: table?.caption ?? table?.caption?.content }) + } } function addCaption() { @@ -122,13 +149,14 @@ function Table(props: TableProps) { } function createTable() { - const { tableClass } = props.table + const { tableClass } = table return ( {currentTable.thead?.map((t, index) => ( @@ -327,10 +355,10 @@ function Table(props: TableProps) { } function addStandardSymbols() { - if (props.standardSymbol && props.standardSymbol.href && props.standardSymbol.text) { + if (standardSymbol && standardSymbol.href && standardSymbol.text) { return ( - - {props.standardSymbol.text} + + {standardSymbol.text} ) } @@ -338,7 +366,7 @@ function Table(props: TableProps) { } function addPreviewButton() { - if (showPreviewToggle && !props.pageTypeStatistic) { + if (showPreviewToggle && !pageTypeStatistic) { return (