diff --git a/packages/server/src/utils/xlsx.ts b/packages/server/src/utils/xlsx.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/webapp/src/containers/FinancialStatements/BalanceSheet/BalanceSheetActionsBar.tsx b/packages/webapp/src/containers/FinancialStatements/BalanceSheet/BalanceSheetActionsBar.tsx
index dd50cd541..0421ea2ae 100644
--- a/packages/webapp/src/containers/FinancialStatements/BalanceSheet/BalanceSheetActionsBar.tsx
+++ b/packages/webapp/src/containers/FinancialStatements/BalanceSheet/BalanceSheetActionsBar.tsx
@@ -1,5 +1,4 @@
// @ts-nocheck
-import React from 'react';
import {
NavbarGroup,
Button,
@@ -13,11 +12,12 @@ import classNames from 'classnames';
import { DashboardActionsBar, FormattedMessage as T, Icon } from '@/components';
import NumberFormatDropdown from '@/components/NumberFormatDropdown';
+import { BalanceSheetExportMenu } from './components';
-import { compose, saveInvoke } from '@/utils';
import { useBalanceSheetContext } from './BalanceSheetProvider';
import withBalanceSheet from './withBalanceSheet';
import withBalanceSheetActions from './withBalanceSheetActions';
+import { compose, saveInvoke } from '@/utils';
/**
* Balance sheet - actions bar.
@@ -114,11 +114,18 @@ function BalanceSheetActionsBar({
icon={}
text={}
/>
- }
- text={}
- />
+ }
+ interactionKind={PopoverInteractionKind.CLICK}
+ placement="bottom-start"
+ minimal
+ >
+ }
+ text={}
+ />
+
);
diff --git a/packages/webapp/src/containers/FinancialStatements/BalanceSheet/components.tsx b/packages/webapp/src/containers/FinancialStatements/BalanceSheet/components.tsx
index 08f946f75..a15bbbf47 100644
--- a/packages/webapp/src/containers/FinancialStatements/BalanceSheet/components.tsx
+++ b/packages/webapp/src/containers/FinancialStatements/BalanceSheet/components.tsx
@@ -1,13 +1,32 @@
// @ts-nocheck
-import React from 'react';
-import { Button } from '@blueprintjs/core';
+import React, { useRef } from 'react';
+import {
+ Button,
+ Classes,
+ Intent,
+ Menu,
+ MenuItem,
+ ProgressBar,
+ Text,
+} from '@blueprintjs/core';
+import classNames from 'classnames';
-import { FormattedMessage as T, Icon, If } from '@/components';
+import {
+ FormattedMessage as T,
+ Icon,
+ If,
+ Stack,
+ AppToaster,
+} from '@/components';
import FinancialLoadingBar from '../FinancialLoadingBar';
import { useBalanceSheetContext } from './BalanceSheetProvider';
import { FinancialComputeAlert } from '../FinancialReportPage';
import { dynamicColumns } from './dynamicColumns';
+import {
+ useBalanceSheetCsvExport,
+ useBalanceSheetXlsxExport,
+} from '@/hooks/query';
/**
* Balance sheet alerts.
@@ -66,3 +85,87 @@ export const useBalanceSheetColumns = () => {
[table],
);
};
+
+/**
+ *
+ * @returns
+ */
+export const BalanceSheetExportMenu = () => {
+ const toastKey = useRef(null);
+ const commonToastConfig = {
+ isCloseButtonShown: true,
+ timeout: 2000,
+ };
+
+ const openProgressToast = (amount: number) => {
+ return (
+
+ The report has been exported successfully.
+ = 100,
+ })}
+ intent={amount < 100 ? Intent.PRIMARY : Intent.SUCCESS}
+ value={amount / 100}
+ />
+
+ );
+ };
+
+ // Export the report to xlsx.
+ const { mutateAsync: xlsxExport } = useBalanceSheetXlsxExport({
+ onDownloadProgress: (xlsxExportProgress: number) => {
+ if (!toastKey.current) {
+ toastKey.current = AppToaster.show({
+ message: openProgressToast(xlsxExportProgress),
+ ...commonToastConfig,
+ });
+ } else {
+ AppToaster.show(
+ {
+ message: openProgressToast(xlsxExportProgress),
+ ...commonToastConfig,
+ },
+ toastKey.current,
+ );
+ }
+ },
+ });
+ // Export the report to csv.
+ const { mutateAsync: csvExport } = useBalanceSheetCsvExport({
+ onDownloadProgress: (xlsxExportProgress: number) => {
+ if (!toastKey.current) {
+ toastKey.current = AppToaster.show({
+ message: openProgressToast(xlsxExportProgress),
+ ...commonToastConfig,
+ });
+ } else {
+ AppToaster.show(
+ {
+ message: openProgressToast(xlsxExportProgress),
+ ...commonToastConfig,
+ },
+ toastKey.current,
+ );
+ }
+ },
+ });
+
+ const handleCsvExportBtnClick = () => {
+ csvExport().then(() => {});
+ };
+
+ const handleXlsxExportBtnClick = () => {
+ xlsxExport().then(() => {});
+ };
+
+ return (
+
+ );
+};
diff --git a/packages/webapp/src/hooks/query/financialReports.tsx b/packages/webapp/src/hooks/query/financialReports.tsx
index 3645a6293..bc1ff4bec 100644
--- a/packages/webapp/src/hooks/query/financialReports.tsx
+++ b/packages/webapp/src/hooks/query/financialReports.tsx
@@ -1,16 +1,14 @@
// @ts-nocheck
import { useRequestQuery } from '../useQueryRequest';
import {
- trialBalanceSheetReducer,
generalLedgerTableRowsReducer,
journalTableRowsReducer,
- ARAgingSummaryTableRowsMapper,
- APAgingSummaryTableRowsMapper,
inventoryValuationReducer,
purchasesByItemsReducer,
salesByItemsReducer,
} from '@/containers/FinancialStatements/reducers';
import t from './types';
+import { useDownloadFile } from '../useDownloadFile';
/**
* Retrieve balance sheet.
@@ -33,6 +31,32 @@ export function useBalanceSheet(query, props) {
);
}
+export const useBalanceSheetXlsxExport = (args) => {
+ return useDownloadFile({
+ url: '/financial_statements/balance_sheet',
+ config: {
+ headers: {
+ accept: 'application/xlsx',
+ },
+ },
+ filename: 'balance_sheet.xlsx',
+ ...args
+ });
+};
+
+export const useBalanceSheetCsvExport = (args) => {
+ return useDownloadFile({
+ url: '/financial_statements/balance_sheet',
+ config: {
+ headers: {
+ accept: 'application/csv',
+ },
+ },
+ filename: 'balance_sheet.csv',
+ ...args
+ });
+};
+
/**
* Retrieve trial balance sheet.
*/
diff --git a/packages/webapp/src/hooks/useDownloadFile.ts b/packages/webapp/src/hooks/useDownloadFile.ts
new file mode 100644
index 000000000..3237f2a79
--- /dev/null
+++ b/packages/webapp/src/hooks/useDownloadFile.ts
@@ -0,0 +1,72 @@
+// @ts-nocheck
+import axios, { AxiosError, AxiosRequestConfig } from 'axios';
+import { useState } from 'react';
+import { useMutation } from 'react-query';
+import useApiRequest from './useRequest';
+
+interface IArgs {
+ url: string;
+ filename: string;
+ mime?: string;
+ config?: AxiosRequestConfig;
+ onDownloadProgress?: (progress: number) => void;
+}
+
+export const useDownloadFile = (args: IArgs) => {
+ const apiRequest = useApiRequest();
+
+ const mutation = useMutation(() =>
+ apiRequest
+ .get(args.url, {
+ responseType: 'blob',
+ onDownloadProgress: (ev) => {
+ args.onDownloadProgress &&
+ args.onDownloadProgress(Math.round((ev.loaded * 100) / ev.total));
+ },
+ ...args.config,
+ })
+ .then((res) => {
+ downloadFile(res.data, args.filename, args.mime);
+ return res;
+ }),
+ );
+ return { ...mutation };
+};
+
+export function downloadFile(data, filename, mime, bom) {
+ var blobData = typeof bom !== 'undefined' ? [bom, data] : [data];
+ var blob = new Blob(blobData, { type: mime || 'application/octet-stream' });
+ if (typeof window.navigator.msSaveBlob !== 'undefined') {
+ // IE workaround for "HTML7007: One or more blob URLs were
+ // revoked by closing the blob for which they were created.
+ // These URLs will no longer resolve as the data backing
+ // the URL has been freed."
+ window.navigator.msSaveBlob(blob, filename);
+ } else {
+ var blobURL =
+ window.URL && window.URL.createObjectURL
+ ? window.URL.createObjectURL(blob)
+ : window.webkitURL.createObjectURL(blob);
+ var tempLink = document.createElement('a');
+ tempLink.style.display = 'none';
+ tempLink.href = blobURL;
+ tempLink.setAttribute('download', filename);
+
+ // Safari thinks _blank anchor are pop ups. We only want to set _blank
+ // target if the browser does not support the HTML5 download attribute.
+ // This allows you to download files in desktop safari if pop up blocking
+ // is enabled.
+ if (typeof tempLink.download === 'undefined') {
+ tempLink.setAttribute('target', '_blank');
+ }
+
+ document.body.appendChild(tempLink);
+ tempLink.click();
+
+ // Fixes "webkit blob resource error 1"
+ setTimeout(function () {
+ document.body.removeChild(tempLink);
+ window.URL.revokeObjectURL(blobURL);
+ }, 200);
+ }
+}