feat: export purchases by items to csv/xlsx (#327)

This commit is contained in:
Ahmed Bouhuolia
2024-01-23 12:33:43 +02:00
committed by GitHub
parent 7eb84474a5
commit 429159acf9
17 changed files with 672 additions and 150 deletions

View File

@@ -18,6 +18,7 @@ import withPurchasesByItems from './withPurchasesByItems';
import withPurchasesByItemsActions from './withPurchasesByItemsActions';
import { compose, saveInvoke } from '@/utils';
import { usePurchaseByItemsContext } from './PurchasesByItemsProvider';
import { PurchasesByItemsExportMenu } from './components';
function PurchasesByItemsActionsBar({
// #withPurchasesByItems
@@ -106,11 +107,18 @@ function PurchasesByItemsActionsBar({
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={<PurchasesByItemsExportMenu />}
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>
);

View File

@@ -1,7 +1,7 @@
// @ts-nocheck
import React, { createContext, useContext } from 'react';
import FinancialReportPage from '../FinancialReportPage';
import { usePurchasesByItems } from '@/hooks/query';
import { usePurchasesByItemsTable } from '@/hooks/query';
import { transformFilterFormToQuery } from '../common';
const PurchasesByItemsContext = createContext();
@@ -13,7 +13,7 @@ function PurchasesByItemsProvider({ query, ...props }) {
isFetching,
isLoading,
refetch,
} = usePurchasesByItems(
} = usePurchasesByItemsTable(
{
...transformFilterFormToQuery(query),
},
@@ -26,7 +26,6 @@ function PurchasesByItemsProvider({ query, ...props }) {
purchaseByItems,
isFetching,
isLoading,
refetchSheet: refetch,
};
return (

View File

@@ -6,10 +6,10 @@ import styled from 'styled-components';
import { ReportDataTable, FinancialSheet } from '@/components';
import { usePurchaseByItemsContext } from './PurchasesByItemsProvider';
import { usePurchasesByItemsTableColumns } from './components';
import { tableRowTypesToClassnames } from '@/utils';
import { TableStyle } from '@/constants';
import { usePurchasesByItemsTableColumns } from './dynamicColumns';
/**
* Purchases by items data table.
@@ -17,7 +17,7 @@ import { TableStyle } from '@/constants';
export default function PurchasesByItemsTable({ companyName }) {
// Purchases by items context.
const {
purchaseByItems: { tableRows, query },
purchaseByItems: { table, query },
} = usePurchaseByItemsContext();
// Purchases by items table columns.
@@ -32,7 +32,7 @@ export default function PurchasesByItemsTable({ companyName }) {
>
<PurchasesByItemsDataTable
columns={columns}
data={tableRows}
data={table.rows}
expandable={true}
expandToggleColumn={1}
expandColumnSpace={1}
@@ -58,7 +58,7 @@ const PurchasesByItemsDataTable = styled(ReportDataTable)`
padding-top: 0.36rem;
padding-bottom: 0.36rem;
}
.tr.row_type--total .td {
.tr.row_type--TOTAL .td {
border-top: 1px solid #bbb;
font-weight: 500;
border-bottom: 3px double #000;

View File

@@ -1,69 +1,22 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { useRef } from 'react';
import classNames from 'classnames';
import {
Classes,
Intent,
Menu,
MenuItem,
ProgressBar,
Text,
} from '@blueprintjs/core';
import { If } from '@/components';
import { Align } from '@/constants';
import { CellTextSpan } from '@/components/Datatable/Cells';
import { AppToaster, If, Stack } from '@/components';
import { usePurchaseByItemsContext } from './PurchasesByItemsProvider';
import { getColumnWidth } from '@/utils';
import FinancialLoadingBar from '../FinancialLoadingBar';
/**
* Retrieve purchases by items table columns.
*/
export const usePurchasesByItemsTableColumns = () => {
// purchases by items context.
const {
purchaseByItems: { tableRows },
} = usePurchaseByItemsContext();
return React.useMemo(
() => [
{
Header: intl.get('item_name'),
accessor: (row) => (row.code ? `${row.name} - ${row.code}` : row.name),
className: 'name',
width: 180,
textOverview: true,
},
{
Header: intl.get('quantity_purchased'),
accessor: 'quantity_purchased_formatted',
Cell: CellTextSpan,
className: 'quantity_purchased_formatted',
width: getColumnWidth(tableRows, `quantity_purchased_formatted`, {
minWidth: 150,
}),
textOverview: true,
align: Align.Right,
},
{
Header: intl.get('purchase_amount'),
accessor: 'purchase_cost_formatted',
Cell: CellTextSpan,
className: 'purchase_cost_formatted',
width: getColumnWidth(tableRows, `purchase_cost_formatted`, {
minWidth: 150,
}),
textOverview: true,
align: Align.Right,
},
{
Header: intl.get('average_price'),
accessor: 'average_cost_price_formatted',
Cell: CellTextSpan,
className: 'average_cost_price_formatted',
width: getColumnWidth(tableRows, `average_cost_price_formatted`, {
minWidth: 180,
}),
textOverview: true,
align: Align.Right,
},
],
[tableRows],
);
};
import {
usePurchasesByItemsCsvExport,
usePurchasesByItemsXlsxExport,
} from '@/hooks/query';
/**
* Purchases by items progress loading bar.
@@ -77,3 +30,88 @@ export function PurchasesByItemsLoadingBar() {
</If>
);
}
/**
* Retrieves the purchases by items export menu.
* @returns {JSX.Element}
*/
export const PurchasesByItemsExportMenu = () => {
const toastKey = useRef(null);
const commonToastConfig = {
isCloseButtonShown: true,
timeout: 2000,
};
const { query } = usePurchaseByItemsContext();
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 } = usePurchasesByItemsXlsxExport(query, {
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 } = usePurchasesByItemsCsvExport(query, {
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>
);
};

View File

@@ -0,0 +1,89 @@
// @ts-nocheck
import { getColumnWidth } from '@/utils';
import * as R from 'ramda';
import { Align } from '@/constants';
import { usePurchaseByItemsContext } from './PurchasesByItemsProvider';
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,
};
});
/**
* Item name column accessor.
*/
const itemNameColumnAccessor = R.curry((data, column) => {
return {
...column,
width: 180,
};
});
const dynamiColumnMapper = R.curry((data, column) => {
const _numericColumnAccessor = numericColumnAccessor(data);
const _itemNameColumnAccessor = itemNameColumnAccessor(data);
return R.compose(
R.when(R.pathEq(['key'], 'item_name'), _itemNameColumnAccessor),
R.when(R.pathEq(['key'], 'quantity_purchases'), _numericColumnAccessor),
R.when(R.pathEq(['key'], 'purchase_amount'), _numericColumnAccessor),
R.when(R.pathEq(['key'], 'average_cost'), _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 purchases by items sheet table columns for table component.
*/
export const usePurchasesByItemsTableColumns = () => {
const { purchaseByItems } = usePurchaseByItemsContext();
if (!purchaseByItems) {
throw new Error('Purchases by items context not found');
}
const { table } = purchaseByItems;
return dynamicColumns(table.rows, table.columns);
};