mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 13:50:31 +00:00
Merge pull request #303 from bigcapitalhq/journal-sheet-export
Export general ledger & Journal to CSV and XLSX
This commit is contained in:
@@ -51,8 +51,8 @@ export default function Dashboard() {
|
||||
</Switch>
|
||||
|
||||
<DashboardUniversalSearch />
|
||||
<DialogsContainer />
|
||||
<GlobalHotkeys />
|
||||
<DialogsContainer />
|
||||
<DrawersContainer />
|
||||
<AlertsContainer />
|
||||
</DashboardProvider>
|
||||
|
||||
@@ -32,7 +32,7 @@ function AccountsDataTable({
|
||||
// #withAlertsDialog
|
||||
openAlert,
|
||||
|
||||
// #withDial
|
||||
// #withDialog
|
||||
openDialog,
|
||||
|
||||
// #withDrawerActions
|
||||
|
||||
@@ -8,9 +8,9 @@ import { TransitionGroup, CSSTransition } from 'react-transition-group';
|
||||
import authenticationRoutes from '@/routes/authentication';
|
||||
import { Icon, FormattedMessage as T } from '@/components';
|
||||
import { useIsAuthenticated } from '@/hooks/state';
|
||||
import { AuthMetaBootProvider } from './AuthMetaBoot';
|
||||
|
||||
import '@/style/pages/Authentication/Auth.scss';
|
||||
import { AuthMetaBootProvider } from './AuthMetaBoot';
|
||||
|
||||
export function Authentication() {
|
||||
const to = { pathname: '/' };
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { DashboardActionsBar, FormattedMessage as T, Icon } from '@/components';
|
||||
import { GeneralLedgerSheetExportMenu } from './components';
|
||||
import { useGeneralLedgerContext } from './GeneralLedgerProvider';
|
||||
import { compose } from '@/utils';
|
||||
|
||||
@@ -84,11 +85,18 @@ function GeneralLedgerActionsBar({
|
||||
icon={<Icon icon="print-16" iconSize={16} />}
|
||||
text={<T id={'print'} />}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||
text={<T id={'export'} />}
|
||||
/>
|
||||
<Popover
|
||||
content={<GeneralLedgerSheetExportMenu />}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
placement="bottom-start"
|
||||
minimal
|
||||
>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||
text={<T id={'export'} />}
|
||||
/>
|
||||
</Popover>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
|
||||
@@ -10,7 +10,7 @@ const GeneralLedgerHeaderDimensionsPanelContext = React.createContext();
|
||||
|
||||
/**
|
||||
* General Ledger Header Dimensions Panel provider.
|
||||
* @returns
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
function GeneralLedgerHeaderDimensionsPanelProvider({ query, ...props }) {
|
||||
// Features guard.
|
||||
|
||||
@@ -29,6 +29,7 @@ function GeneralLedgerProvider({ query, ...props }) {
|
||||
sheetRefresh: refetch,
|
||||
isFetching,
|
||||
isLoading,
|
||||
httpRequest: requestQuery
|
||||
};
|
||||
return (
|
||||
<FinancialReportPage name={'general-ledger-sheet'}>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @ts-nocheck
|
||||
import React, { useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import styled from 'styled-components';
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
} from '@/components';
|
||||
|
||||
import { useGeneralLedgerContext } from './GeneralLedgerProvider';
|
||||
import { useGeneralLedgerTableColumns } from './components';
|
||||
import { useGeneralLedgerTableColumns } from './dynamicColumns';
|
||||
|
||||
/**
|
||||
* General ledger table.
|
||||
@@ -21,7 +21,7 @@ import { useGeneralLedgerTableColumns } from './components';
|
||||
export default function GeneralLedgerTable({ companyName }) {
|
||||
// General ledger context.
|
||||
const {
|
||||
generalLedger: { tableRows, query },
|
||||
generalLedger: { query, table },
|
||||
isLoading,
|
||||
} = useGeneralLedgerContext();
|
||||
|
||||
@@ -30,8 +30,8 @@ export default function GeneralLedgerTable({ companyName }) {
|
||||
|
||||
// Default expanded rows of general ledger table.
|
||||
const expandedRows = useMemo(
|
||||
() => defaultExpanderReducer(tableRows, 1),
|
||||
[tableRows],
|
||||
() => defaultExpanderReducer(table.rows, 1),
|
||||
[table.rows],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -48,7 +48,7 @@ export default function GeneralLedgerTable({ companyName }) {
|
||||
'this_report_does_not_contain_any_data_between_date_period',
|
||||
)}
|
||||
columns={columns}
|
||||
data={tableRows}
|
||||
data={table.rows}
|
||||
rowClassNames={tableRowTypesToClassnames}
|
||||
expanded={expandedRows}
|
||||
virtualizedRows={true}
|
||||
@@ -79,23 +79,20 @@ const GeneralLedgerDataTable = styled(ReportDataTable)`
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tr:not(.no-results) .td:not(:first-of-type) {
|
||||
border-left: 1px solid #ececec;
|
||||
}
|
||||
.tr:last-child .td {
|
||||
border-bottom: 1px solid #ececec;
|
||||
}
|
||||
|
||||
.tr.row_type {
|
||||
&--ACCOUNT_ROW {
|
||||
&--ACCOUNT {
|
||||
.td {
|
||||
&.date {
|
||||
font-weight: 500;
|
||||
|
||||
.cell-inner {
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,7 +100,6 @@ const GeneralLedgerDataTable = styled(ReportDataTable)`
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
}
|
||||
|
||||
&--OPENING_BALANCE,
|
||||
&--CLOSING_BALANCE {
|
||||
.amount {
|
||||
|
||||
@@ -1,107 +1,31 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Button } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T, Icon, If } from '@/components';
|
||||
import React, { useRef } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
Button,
|
||||
Classes,
|
||||
Intent,
|
||||
Menu,
|
||||
MenuItem,
|
||||
ProgressBar,
|
||||
Text,
|
||||
} from '@blueprintjs/core';
|
||||
import {
|
||||
FormattedMessage as T,
|
||||
Icon,
|
||||
If,
|
||||
Stack,
|
||||
AppToaster,
|
||||
} from '@/components';
|
||||
|
||||
import { getColumnWidth } from '@/utils';
|
||||
import { useGeneralLedgerContext } from './GeneralLedgerProvider';
|
||||
import FinancialLoadingBar from '../FinancialLoadingBar';
|
||||
|
||||
import { FinancialComputeAlert } from '../FinancialReportPage';
|
||||
import { Align } from '@/constants';
|
||||
|
||||
/**
|
||||
* Retrieve the general ledger table columns.
|
||||
*/
|
||||
export function useGeneralLedgerTableColumns() {
|
||||
// General ledger context.
|
||||
const {
|
||||
generalLedger: { tableRows },
|
||||
} = useGeneralLedgerContext();
|
||||
|
||||
return React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: intl.get('date'),
|
||||
accessor: 'date',
|
||||
className: 'date',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
Header: intl.get('account_name'),
|
||||
accessor: 'name',
|
||||
className: 'name',
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('transaction_type'),
|
||||
accessor: 'reference_type_formatted',
|
||||
className: 'transaction_type',
|
||||
width: 125,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('transaction_number'),
|
||||
accessor: 'reference_id',
|
||||
className: 'transaction_number',
|
||||
width: 100,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('description'),
|
||||
accessor: 'note',
|
||||
className: 'description',
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('credit'),
|
||||
accessor: 'formatted_credit',
|
||||
className: 'credit',
|
||||
width: getColumnWidth(tableRows, 'formatted_credit', {
|
||||
minWidth: 100,
|
||||
magicSpacing: 10,
|
||||
}),
|
||||
textOverview: true,
|
||||
align: Align.Right,
|
||||
},
|
||||
{
|
||||
Header: intl.get('debit'),
|
||||
accessor: 'formatted_debit',
|
||||
className: 'debit',
|
||||
width: getColumnWidth(tableRows, 'formatted_debit', {
|
||||
minWidth: 100,
|
||||
magicSpacing: 10,
|
||||
}),
|
||||
textOverview: true,
|
||||
align: Align.Right,
|
||||
},
|
||||
{
|
||||
Header: intl.get('amount'),
|
||||
accessor: 'formatted_amount',
|
||||
className: 'amount',
|
||||
width: getColumnWidth(tableRows, 'formatted_amount', {
|
||||
minWidth: 100,
|
||||
magicSpacing: 10,
|
||||
}),
|
||||
textOverview: true,
|
||||
align: Align.Right,
|
||||
},
|
||||
{
|
||||
Header: intl.get('running_balance'),
|
||||
accessor: 'formatted_running_balance',
|
||||
className: 'running_balance',
|
||||
width: getColumnWidth(tableRows, 'formatted_running_balance', {
|
||||
minWidth: 100,
|
||||
magicSpacing: 10,
|
||||
}),
|
||||
textOverview: true,
|
||||
align: Align.Right,
|
||||
},
|
||||
],
|
||||
[tableRows],
|
||||
);
|
||||
}
|
||||
import {
|
||||
useGeneralLedgerSheetCsvExport,
|
||||
useGeneralLedgerSheetXlsxExport,
|
||||
} from '@/hooks/query';
|
||||
|
||||
/**
|
||||
* General ledger sheet alerts.
|
||||
@@ -144,3 +68,93 @@ export function GeneralLedgerSheetLoadingBar() {
|
||||
</If>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the G/L sheet export menu.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export const GeneralLedgerSheetExportMenu = () => {
|
||||
const toastKey = useRef(null);
|
||||
const commonToastConfig = {
|
||||
isCloseButtonShown: true,
|
||||
timeout: 2000,
|
||||
};
|
||||
const { httpRequest } = useGeneralLedgerContext();
|
||||
|
||||
const openProgressToast = (amount: number) => {
|
||||
return (
|
||||
<Stack spacing={8}>
|
||||
<Text>The report has been exported successfully.</Text>
|
||||
<ProgressBar
|
||||
className={classNames('toast-progress', {
|
||||
[Classes.PROGRESS_NO_STRIPES]: amount >= 100,
|
||||
})}
|
||||
intent={amount < 100 ? Intent.PRIMARY : Intent.SUCCESS}
|
||||
value={amount / 100}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
// Export the report to xlsx.
|
||||
const { mutateAsync: xlsxExport } = useGeneralLedgerSheetXlsxExport(
|
||||
httpRequest,
|
||||
{
|
||||
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 } = useGeneralLedgerSheetCsvExport(
|
||||
httpRequest,
|
||||
{
|
||||
onDownloadProgress: (xlsxExportProgress: number) => {
|
||||
if (!toastKey.current) {
|
||||
toastKey.current = AppToaster.show({
|
||||
message: openProgressToast(xlsxExportProgress),
|
||||
...commonToastConfig,
|
||||
});
|
||||
} else {
|
||||
AppToaster.show(
|
||||
{
|
||||
message: openProgressToast(xlsxExportProgress),
|
||||
...commonToastConfig,
|
||||
},
|
||||
toastKey.current,
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
// Handle csv export button click.
|
||||
const handleCsvExportBtnClick = () => {
|
||||
csvExport();
|
||||
};
|
||||
// Handle xlsx export button click.
|
||||
const handleXlsxExportBtnClick = () => {
|
||||
xlsxExport();
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
text={'XLSX (Microsoft Excel)'}
|
||||
onClick={handleXlsxExportBtnClick}
|
||||
/>
|
||||
<MenuItem text={'CSV'} onClick={handleCsvExportBtnClick} />
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
// @ts-nocheck
|
||||
import { getColumnWidth } from '@/utils';
|
||||
import * as R from 'ramda';
|
||||
import { useGeneralLedgerContext } from './GeneralLedgerProvider';
|
||||
import { Align } from '@/constants';
|
||||
|
||||
const getTableCellValueAccessor = (index) => `cells[${index}].value`;
|
||||
|
||||
const getReportColWidth = (data, accessor, headerText) => {
|
||||
return getColumnWidth(
|
||||
data,
|
||||
accessor,
|
||||
{ magicSpacing: 10, minWidth: 100 },
|
||||
headerText,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Account name column mapper.
|
||||
*/
|
||||
const commonColumnMapper = R.curry((data, column) => {
|
||||
const accessor = getTableCellValueAccessor(column.cell_index);
|
||||
|
||||
return {
|
||||
key: column.key,
|
||||
Header: column.label,
|
||||
accessor,
|
||||
className: column.key,
|
||||
textOverview: true,
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Numeric columns accessor.
|
||||
*/
|
||||
const numericColumnAccessor = R.curry((data, column) => {
|
||||
const accessor = getTableCellValueAccessor(column.cell_index);
|
||||
const width = getReportColWidth(data, accessor, column.label);
|
||||
|
||||
return {
|
||||
...column,
|
||||
align: Align.Right,
|
||||
width,
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Date column accessor.
|
||||
*/
|
||||
const dateColumnAccessor = R.curry((column) => {
|
||||
return {
|
||||
...column,
|
||||
width: 120,
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Transaction type column accessor.
|
||||
*/
|
||||
const transactionTypeColumnAccessor = (column) => {
|
||||
return {
|
||||
...column,
|
||||
width: 125,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Transaction number column accessor.
|
||||
*/
|
||||
const transactionIdColumnAccessor = (column) => {
|
||||
return {
|
||||
...column,
|
||||
width: 80,
|
||||
};
|
||||
};
|
||||
|
||||
const dynamiColumnMapper = R.curry((data, column) => {
|
||||
const _numericColumnAccessor = numericColumnAccessor(data);
|
||||
|
||||
return R.compose(
|
||||
R.when(R.pathEq(['key'], 'date'), dateColumnAccessor),
|
||||
R.when(
|
||||
R.pathEq(['key'], 'reference_type'),
|
||||
transactionTypeColumnAccessor,
|
||||
),
|
||||
R.when(
|
||||
R.pathEq(['key'], 'reference_number'),
|
||||
transactionIdColumnAccessor,
|
||||
),
|
||||
R.when(R.pathEq(['key'], 'credit'), _numericColumnAccessor),
|
||||
R.when(R.pathEq(['key'], 'debit'), _numericColumnAccessor),
|
||||
R.when(R.pathEq(['key'], 'amount'), _numericColumnAccessor),
|
||||
R.when(R.pathEq(['key'], 'running_balance'), _numericColumnAccessor),
|
||||
commonColumnMapper(data),
|
||||
)(column);
|
||||
});
|
||||
|
||||
/**
|
||||
* Composes the dynamic columns that fetched from request to columns to table component.
|
||||
*/
|
||||
export const dynamicColumns = R.curry((data, columns) => {
|
||||
return R.map(dynamiColumnMapper(data), columns);
|
||||
});
|
||||
|
||||
/**
|
||||
* Retrieves the G/L sheet table columns for table component.
|
||||
*/
|
||||
export const useGeneralLedgerTableColumns = () => {
|
||||
const { generalLedger } = useGeneralLedgerContext();
|
||||
|
||||
if (!generalLedger) {
|
||||
throw new Error('asdfadsf');
|
||||
}
|
||||
const { table } = generalLedger;
|
||||
|
||||
return dynamicColumns(table.rows, table.columns);
|
||||
};
|
||||
@@ -18,6 +18,7 @@ import withJournal from './withJournal';
|
||||
|
||||
import { compose } from '@/utils';
|
||||
import { useJournalSheetContext } from './JournalProvider';
|
||||
import { JournalSheetExportMenu } from './components';
|
||||
|
||||
/**
|
||||
* Journal sheeet - Actions bar.
|
||||
@@ -85,11 +86,18 @@ function JournalActionsBar({
|
||||
icon={<Icon icon="print-16" iconSize={16} />}
|
||||
text={<T id={'print'} />}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||
text={<T id={'export'} />}
|
||||
/>
|
||||
<Popover
|
||||
content={<JournalSheetExportMenu />}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
placement="bottom-start"
|
||||
minimal
|
||||
>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||
text={<T id={'export'} />}
|
||||
/>
|
||||
</Popover>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
|
||||
@@ -28,6 +28,7 @@ function JournalSheetProvider({ query, ...props }) {
|
||||
isLoading,
|
||||
isFetching,
|
||||
refetchSheet: refetch,
|
||||
httpQuery: requestQuery
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -11,10 +11,10 @@ import {
|
||||
TableVirtualizedListRows,
|
||||
} from '@/components';
|
||||
|
||||
import { useJournalTableColumns } from './components';
|
||||
import { useJournalSheetContext } from './JournalProvider';
|
||||
|
||||
import { defaultExpanderReducer, tableRowTypesToClassnames } from '@/utils';
|
||||
import { useJournalSheetColumns } from './dynamicColumns';
|
||||
|
||||
/**
|
||||
* Journal sheet table.
|
||||
@@ -23,12 +23,12 @@ import { defaultExpanderReducer, tableRowTypesToClassnames } from '@/utils';
|
||||
export function JournalTable({ companyName }) {
|
||||
// Journal sheet context.
|
||||
const {
|
||||
journalSheet: { tableRows, query },
|
||||
journalSheet: { table, query },
|
||||
isLoading,
|
||||
} = useJournalSheetContext();
|
||||
|
||||
// Retreive the journal table columns.
|
||||
const columns = useJournalTableColumns();
|
||||
// Retrieves the journal table columns.
|
||||
const columns = useJournalSheetColumns();
|
||||
|
||||
// Default expanded rows of general journal table.
|
||||
const expandedRows = useMemo(() => defaultExpanderReducer([], 1), []);
|
||||
@@ -39,13 +39,13 @@ export function JournalTable({ companyName }) {
|
||||
sheetType={intl.get('journal_sheet')}
|
||||
fromDate={query.from_date}
|
||||
toDate={query.to_date}
|
||||
name="journal"
|
||||
loading={isLoading}
|
||||
fullWidth={true}
|
||||
name="journal"
|
||||
>
|
||||
<JournalDataTable
|
||||
columns={columns}
|
||||
data={tableRows}
|
||||
data={table.rows}
|
||||
rowClassNames={tableRowTypesToClassnames}
|
||||
noResults={intl.get(
|
||||
'this_report_does_not_contain_any_data_between_date_period',
|
||||
@@ -83,10 +83,9 @@ const JournalDataTable = styled(ReportDataTable)`
|
||||
border-bottom: 1px solid #dbdbdb;
|
||||
}
|
||||
}
|
||||
.tr.row_type--TOTAL_ENTRIES {
|
||||
.tr.row_type--TOTAL{
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tr:not(.no-results) {
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
@@ -1,77 +1,31 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import moment from 'moment';
|
||||
import { Button } from '@blueprintjs/core';
|
||||
import React, { useRef } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
Button,
|
||||
Classes,
|
||||
Menu,
|
||||
MenuItem,
|
||||
ProgressBar,
|
||||
Text,
|
||||
Intent,
|
||||
} from '@blueprintjs/core';
|
||||
|
||||
import { Icon, If, FormattedMessage as T } from '@/components';
|
||||
import {
|
||||
AppToaster,
|
||||
Icon,
|
||||
If,
|
||||
Stack,
|
||||
FormattedMessage as T,
|
||||
} from '@/components';
|
||||
import { useJournalSheetContext } from './JournalProvider';
|
||||
import FinancialLoadingBar from '../FinancialLoadingBar';
|
||||
import { FinancialComputeAlert } from '../FinancialReportPage';
|
||||
|
||||
import { Align } from '@/constants';
|
||||
|
||||
/**
|
||||
* Retrieve the journal table columns.
|
||||
*/
|
||||
export const useJournalTableColumns = () => {
|
||||
return React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: intl.get('date'),
|
||||
accessor: (row) =>
|
||||
row.date ? moment(row.date).format('YYYY MMM DD') : '',
|
||||
className: 'date',
|
||||
width: 100,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('transaction_type'),
|
||||
accessor: 'reference_type_formatted',
|
||||
className: 'reference_type_formatted',
|
||||
width: 120,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('num'),
|
||||
accessor: 'transaction_number',
|
||||
className: 'reference_id',
|
||||
width: 70,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('description'),
|
||||
accessor: 'note',
|
||||
className: 'note',
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('acc_code'),
|
||||
accessor: 'account_code',
|
||||
width: 95,
|
||||
className: 'account_code',
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('account'),
|
||||
accessor: 'account_name',
|
||||
className: 'account_name',
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('credit'),
|
||||
accessor: 'formatted_credit',
|
||||
align: Align.Right,
|
||||
},
|
||||
{
|
||||
Header: intl.get('debit'),
|
||||
accessor: 'formatted_debit',
|
||||
align: Align.Right,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
};
|
||||
import {
|
||||
useJournalSheetCsvExport,
|
||||
useJournalSheetXlsxExport,
|
||||
} from '@/hooks/query';
|
||||
|
||||
/**
|
||||
* Journal sheet loading bar.
|
||||
@@ -115,3 +69,87 @@ export function JournalSheetAlerts() {
|
||||
</FinancialComputeAlert>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the journal sheet export menu.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export const JournalSheetExportMenu = () => {
|
||||
const toastKey = useRef(null);
|
||||
const commonToastConfig = {
|
||||
isCloseButtonShown: true,
|
||||
timeout: 2000,
|
||||
};
|
||||
const { httpQuery } = useJournalSheetContext();
|
||||
|
||||
const openProgressToast = (amount: number) => {
|
||||
return (
|
||||
<Stack spacing={8}>
|
||||
<Text>The report has been exported successfully.</Text>
|
||||
<ProgressBar
|
||||
className={classNames('toast-progress', {
|
||||
[Classes.PROGRESS_NO_STRIPES]: amount >= 100,
|
||||
})}
|
||||
intent={amount < 100 ? Intent.PRIMARY : Intent.SUCCESS}
|
||||
value={amount / 100}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
// Exports the report to xlsx.
|
||||
const { mutateAsync: xlsxExport } = useJournalSheetXlsxExport(httpQuery, {
|
||||
onDownloadProgress: (xlsxExportProgress: number) => {
|
||||
if (!toastKey.current) {
|
||||
toastKey.current = AppToaster.show({
|
||||
message: openProgressToast(xlsxExportProgress),
|
||||
...commonToastConfig,
|
||||
});
|
||||
} else {
|
||||
AppToaster.show(
|
||||
{
|
||||
message: openProgressToast(xlsxExportProgress),
|
||||
...commonToastConfig,
|
||||
},
|
||||
toastKey.current,
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
// Exports the report to csv.
|
||||
const { mutateAsync: csvExport } = useJournalSheetCsvExport(httpQuery, {
|
||||
onDownloadProgress: (xlsxExportProgress: number) => {
|
||||
if (!toastKey.current) {
|
||||
toastKey.current = AppToaster.show({
|
||||
message: openProgressToast(xlsxExportProgress),
|
||||
...commonToastConfig,
|
||||
});
|
||||
} else {
|
||||
AppToaster.show(
|
||||
{
|
||||
message: openProgressToast(xlsxExportProgress),
|
||||
...commonToastConfig,
|
||||
},
|
||||
toastKey.current,
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
// Handle csv export button click.
|
||||
const handleCsvExportBtnClick = () => {
|
||||
csvExport();
|
||||
};
|
||||
// Handle xlsx export button click.
|
||||
const handleXlsxExportBtnClick = () => {
|
||||
xlsxExport();
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
text={'XLSX (Microsoft Excel)'}
|
||||
onClick={handleXlsxExportBtnClick}
|
||||
/>
|
||||
<MenuItem text={'CSV'} onClick={handleCsvExportBtnClick} />
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
// @ts-nocheck
|
||||
import { Align } from '@/constants';
|
||||
import { getColumnWidth } from '@/utils';
|
||||
import * as R from 'ramda';
|
||||
import { useJournalSheetContext } from './JournalProvider';
|
||||
|
||||
const getTableCellValueAccessor = (index) => `cells[${index}].value`;
|
||||
|
||||
const getReportColWidth = (data, accessor, headerText) => {
|
||||
return getColumnWidth(
|
||||
data,
|
||||
accessor,
|
||||
{ magicSpacing: 10, minWidth: 100 },
|
||||
headerText,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Common column mapper.
|
||||
*/
|
||||
const commonAccessor = R.curry((data, column) => {
|
||||
const accessor = getTableCellValueAccessor(column.cell_index);
|
||||
|
||||
return {
|
||||
key: column.key,
|
||||
Header: column.label,
|
||||
accessor,
|
||||
className: column.key,
|
||||
textOverview: true,
|
||||
align: Align.Left,
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Numeric columns accessor.
|
||||
*/
|
||||
const numericColumnAccessor = R.curry((data, column) => {
|
||||
const accessor = getTableCellValueAccessor(column.cell_index);
|
||||
const width = getReportColWidth(data, accessor, column.label);
|
||||
|
||||
return {
|
||||
...column,
|
||||
align: Align.Right,
|
||||
width,
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Date column accessor.
|
||||
*/
|
||||
const dateColumnAccessor = (column) => {
|
||||
return {
|
||||
...column,
|
||||
width: 100,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Transaction type column accessor.
|
||||
*/
|
||||
const transactionTypeColumnAccessor = (column) => {
|
||||
return {
|
||||
...column,
|
||||
width: 120,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Transaction number column accessor.
|
||||
*/
|
||||
const transactionNumberColumnAccessor = (column) => {
|
||||
return {
|
||||
...column,
|
||||
width: 70,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Account code column accessor.
|
||||
*/
|
||||
const accountCodeColumnAccessor = (column) => {
|
||||
return {
|
||||
...column,
|
||||
width: 70,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Dynamic column mapper.
|
||||
* @param {} data -
|
||||
* @param {} column -
|
||||
*/
|
||||
const dynamicColumnMapper = R.curry((data, column) => {
|
||||
const _commonAccessor = commonAccessor(data);
|
||||
const _numericColumnAccessor = numericColumnAccessor(data);
|
||||
|
||||
return R.compose(
|
||||
R.when(R.pathEq(['key'], 'date'), dateColumnAccessor),
|
||||
R.when(
|
||||
R.pathEq(['key'], 'transaction_type'),
|
||||
transactionTypeColumnAccessor,
|
||||
),
|
||||
R.when(
|
||||
R.pathEq(['key'], 'transaction_number'),
|
||||
transactionNumberColumnAccessor,
|
||||
),
|
||||
R.when(R.pathEq(['key'], 'account_code'), accountCodeColumnAccessor),
|
||||
R.when(R.pathEq(['key'], 'credit'), _numericColumnAccessor),
|
||||
R.when(R.pathEq(['key'], 'debit'), _numericColumnAccessor),
|
||||
_commonAccessor,
|
||||
)(column);
|
||||
});
|
||||
|
||||
/**
|
||||
* Composes the fetched dynamic columns from the server to the columns to pass it
|
||||
* to the table component.
|
||||
*/
|
||||
export const dynamicColumns = (columns, data) => {
|
||||
return R.map(dynamicColumnMapper(data), columns);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the table columns of journal sheet.
|
||||
*/
|
||||
export const useJournalSheetColumns = () => {
|
||||
const { journalSheet } = useJournalSheetContext();
|
||||
|
||||
if (!journalSheet) {
|
||||
throw new Error('The journal sheet is not loaded');
|
||||
}
|
||||
const { table } = journalSheet;
|
||||
|
||||
return dynamicColumns(table.columns, table.rows);
|
||||
};
|
||||
@@ -1,6 +1,5 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { castArray } from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
||||
import { useAppQueryString } from '@/hooks';
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// @ts-nocheck
|
||||
import { useRequestQuery } from '../useQueryRequest';
|
||||
import {
|
||||
generalLedgerTableRowsReducer,
|
||||
journalTableRowsReducer,
|
||||
inventoryValuationReducer,
|
||||
purchasesByItemsReducer,
|
||||
salesByItemsReducer,
|
||||
@@ -167,21 +165,44 @@ export function useGeneralLedgerSheet(query, props) {
|
||||
method: 'get',
|
||||
url: '/financial_statements/general_ledger',
|
||||
params: query,
|
||||
headers: {
|
||||
Accept: 'application/json+table',
|
||||
},
|
||||
},
|
||||
{
|
||||
select: (res) => ({
|
||||
tableRows: generalLedgerTableRowsReducer(res.data.data),
|
||||
...res.data,
|
||||
}),
|
||||
defaultData: {
|
||||
tableRows: [],
|
||||
data: {},
|
||||
query: {},
|
||||
},
|
||||
select: (res) => res.data,
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
export const useGeneralLedgerSheetXlsxExport = (query, args) => {
|
||||
return useDownloadFile({
|
||||
url: '/financial_statements/general_ledger',
|
||||
config: {
|
||||
headers: {
|
||||
accept: 'application/xlsx',
|
||||
},
|
||||
params: query,
|
||||
},
|
||||
filename: 'general_ledger.xlsx',
|
||||
...args,
|
||||
});
|
||||
};
|
||||
|
||||
export const useGeneralLedgerSheetCsvExport = (query, args) => {
|
||||
return useDownloadFile({
|
||||
url: '/financial_statements/general_ledger',
|
||||
config: {
|
||||
headers: {
|
||||
accept: 'application/csv',
|
||||
},
|
||||
params: query,
|
||||
},
|
||||
filename: 'general_ledger.csv',
|
||||
...args,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve journal sheet.
|
||||
@@ -189,22 +210,49 @@ export function useGeneralLedgerSheet(query, props) {
|
||||
export function useJournalSheet(query, props) {
|
||||
return useRequestQuery(
|
||||
[t.FINANCIAL_REPORT, t.JOURNAL, query],
|
||||
{ method: 'get', url: '/financial_statements/journal', params: query },
|
||||
{
|
||||
select: (res) => ({
|
||||
tableRows: journalTableRowsReducer(res.data.data),
|
||||
...res.data,
|
||||
}),
|
||||
defaultData: {
|
||||
data: {},
|
||||
tableRows: [],
|
||||
query: {},
|
||||
method: 'get',
|
||||
url: '/financial_statements/journal',
|
||||
params: query,
|
||||
headers: {
|
||||
Accept: 'application/json+table',
|
||||
},
|
||||
},
|
||||
{
|
||||
select: (res) => res.data,
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export const useJournalSheetXlsxExport = (query, args) => {
|
||||
return useDownloadFile({
|
||||
url: '/financial_statements/journal',
|
||||
config: {
|
||||
headers: {
|
||||
accept: 'application/xlsx',
|
||||
},
|
||||
params: query,
|
||||
},
|
||||
filename: 'journal.xlsx',
|
||||
...args,
|
||||
});
|
||||
};
|
||||
|
||||
export const useJournalSheetCsvExport = (query, args) => {
|
||||
return useDownloadFile({
|
||||
url: '/financial_statements/journal',
|
||||
config: {
|
||||
headers: {
|
||||
accept: 'application/csv',
|
||||
},
|
||||
params: query,
|
||||
},
|
||||
filename: 'journal.csv',
|
||||
...args,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve A/R aging summary report.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user