Compare commits

...

60 Commits

Author SHA1 Message Date
a.bouhuolia
98a02396a9 Merge branch 'develop' into main 2022-02-21 15:02:35 +02:00
a.bouhuolia
c7673f57cd dump v1.6.3. 2022-02-21 15:02:08 +02:00
a.bouhuolia
aa39aab93a fix(Billing): display payment methods only if subscription is not active. 2022-02-21 14:56:55 +02:00
a.bouhuolia
8e6b0b496f fix: BIG-337 Display billing page once the organization subscription is inactive. 2022-02-21 14:38:41 +02:00
a.bouhuolia
96635ffa84 Merge branch 'develop' into main 2022-02-18 20:44:42 +02:00
a.bouhuolia
e874b89d2d fix: try to fix styled-components. 2022-02-18 20:32:21 +02:00
a.bouhuolia
60d37e3424 Revert "fix: try to comment FinancialSkeletonTable component."
This reverts commit e3f2c82a38.
2022-02-18 20:10:53 +02:00
a.bouhuolia
8ae39bf04c fix: styled-components components. 2022-02-18 19:52:37 +02:00
a.bouhuolia
e3f2c82a38 fix: try to comment FinancialSkeletonTable component. 2022-02-18 19:29:12 +02:00
a.bouhuolia
68c0678dc3 Merge branch 'develop' into main 2022-02-17 12:10:20 +02:00
a.bouhuolia
030be9652c feat: add BS and PL reports to page of financial reports list. 2022-02-17 11:40:40 +02:00
a.bouhuolia
554527f17d fix(VendorTransaction): column accessor/id. 2022-02-16 18:47:36 +02:00
a.bouhuolia
79144ad4a5 fix: re-structure the system tables reports. 2022-02-16 18:44:10 +02:00
a.bouhuolia
e6fcbfeea6 fix: control report drawer header. 2022-02-16 17:49:28 +02:00
a.bouhuolia
7eacaa0660 fix: financial report data tables. 2022-02-14 14:09:17 +02:00
a.bouhuolia
673808cceb feat: add sticky table head to specific item transactions table. 2022-02-13 17:22:29 +02:00
a.bouhuolia
f27ef2c9b0 fix: style of vendor/customer balance summary. 2022-02-13 17:17:09 +02:00
a.bouhuolia
2986b537d0 fix: financial reports. 2022-02-13 13:21:59 +02:00
a.bouhuolia
b1f07d281f fix: remove un-used stylesheet files. 2022-02-12 20:41:40 +02:00
a.bouhuolia
1b0ffb5574 refactor(CustomerTransaction). 2022-02-12 20:31:14 +02:00
a.bouhuolia
b249335a73 refactor(Cashflow)
refactor(InventoryValuation)
2022-02-12 18:59:34 +02:00
a.bouhuolia
4cc0a8c41e refactor(APAgingSummary)
refactor(ARAgingSummary)
2022-02-12 18:49:48 +02:00
a.bouhuolia
2e7061260e refactor((CustomerTransaction).
refactor(VendorTransaction).
refactor(VendorBalanceSummary).
refactor(CustomerBalanceSummary)
2022-02-12 18:12:08 +02:00
a.bouhuolia
46570c5218 refactor(InventoryValuation).
refactor(InventoryItemDetails).
2022-02-12 17:03:15 +02:00
a.bouhuolia
72a7c4890e refactor(SaleByItem) 2022-02-12 16:41:07 +02:00
a.bouhuolia
a9a877f4fc refactor(PurchaseByItem) 2022-02-12 16:26:36 +02:00
a.bouhuolia
cc42c21f24 refactor(JournalSheet) 2022-02-12 16:07:26 +02:00
a.bouhuolia
2f0322b4fc refactor(GeneralLedger) 2022-02-12 13:17:38 +02:00
a.bouhuolia
b9418d3eb6 refactor(TrialBalanceSheet). 2022-02-12 12:53:23 +02:00
a.bouhuolia
526181aa68 refactor: re-structure financial reports components. 2022-02-12 12:21:05 +02:00
a.bouhuolia
d445fec8c0 refactor(CashflowSheet): refactor the body sheet. 2022-02-10 11:45:27 +02:00
a.bouhuolia
1f81fd213d refactor(TrialBalanceSheet): refactor with body sheet. 2022-02-10 11:44:50 +02:00
a.bouhuolia
83cd7ca893 Merge branch 'feature/comparisons' into develop 2022-02-09 21:51:18 +02:00
a.bouhuolia
d22143c97e Revert "feat(BS|PL): sticky columns in RTL mode."
This reverts commit 200a59d6da.
2022-02-09 21:32:36 +02:00
a.bouhuolia
200a59d6da feat(BS|PL): sticky columns in RTL mode. 2022-02-09 21:26:33 +02:00
a.bouhuolia
8b4d841023 feat(BS|PL): integrate report query with location query. 2022-02-09 21:12:32 +02:00
a.bouhuolia
c361a5852c fix(BS|PL): report query. 2022-02-09 19:50:49 +02:00
a.bouhuolia
b759d7327e feat(App): Horjar code on production envirement only. 2022-02-09 17:11:55 +02:00
elforjani13
93df479c05 feat(PL & PL): add localizing. 2022-02-08 16:54:57 +02:00
a.bouhuolia
d300231838 fix(DataTable): sticky column. 2022-02-05 11:31:57 +02:00
a.bouhuolia
47f6845633 feat(BS&PL): add sticky to account column. 2022-02-03 17:03:24 +02:00
a.bouhuolia
f204b81407 fix(BS|PL): date periods columns alignment. 2022-02-03 16:05:24 +02:00
a.bouhuolia
2c2740ea73 fix(BalanceSheet): highlight total assets and libaiities. 2022-02-02 13:33:54 +02:00
a.bouhuolia
c72802d683 feat(FinancialSheet): add skeleton view. 2022-02-02 12:08:57 +02:00
a.bouhuolia
b4f6d2c7f1 Merge branch 'feature/comparisons' of https://github.com/bigcapitalhq/client into feature/comparisons 2022-02-01 16:59:51 +02:00
a.bouhuolia
4456343eb6 Merge https://github.com/bigcapitalhq/client into feature/comparisons 2022-02-01 16:53:53 +02:00
elforjani13
a3d250cdc8 fix(Balance & P/L Sheet): fix comparison panel. 2022-02-01 00:29:50 +02:00
a.bouhuolia
ce223e7e2f feat(BalanceSheet|P&L): account name min-width. 2022-01-31 01:04:23 +02:00
a.bouhuolia
8d6ed9be41 fix: balance sheet. 2022-01-30 16:05:59 +02:00
a.bouhuolia
e296507a96 chore: remove dubugger point. 2022-01-30 16:01:59 +02:00
a.bouhuolia
3d78d2d051 feat: integrate balance sheet and P&L sheet with new API. 2022-01-30 15:57:27 +02:00
a.bouhuolia
fa455152a3 feat(ProfitLoss): WIP 2022-01-29 21:57:39 +02:00
a.bouhuolia
34501a9a61 fix(BalanceSheet): dynamic columns width. 2022-01-29 21:35:31 +02:00
a.bouhuolia
066df28257 fix(BalanceSheet): columns width based in cells contents. 2022-01-29 21:32:20 +02:00
a.bouhuolia
77d826e6d4 feat(BalanceSheet|ProfitLoss): comparions feature. 2022-01-29 20:46:41 +02:00
elforjani13
735803f1a5 feat(balancesheet): balance sheet comparisons. 2022-01-25 20:12:13 +02:00
elforjani13
354d1e8f75 feat(P&L Comparisons): add P&L comparisons. 2022-01-24 13:55:44 +02:00
elforjani13
225619be60 fix(balanceSheet) balance sheet header. 2022-01-13 16:33:32 +02:00
elforjani13
85a78b3809 feat: style balance sheet comparison. 2022-01-13 16:25:46 +02:00
elforjani13
7a0d506395 feat: balance sheet comparison 2022-01-13 15:25:04 +02:00
155 changed files with 4176 additions and 2111 deletions

View File

@@ -2,6 +2,26 @@
All notable changes to Bigcapital server-side will be in this file. All notable changes to Bigcapital server-side will be in this file.
## [1.6.3] - 21-02-2022
### Fixed
- `BIG-337` Display billing page once the organization subscription is inactive.
## [1.6.2] - 19-02-2022
### Fixed
- fix syled components dependency with imported as default components.
## [1.6.0] - 18-02-2022
### Added
- Balance sheet comparison of previous period (PP).
- Balance sheet comparison of previous year (PY).
- Balance sheet percentage analysis columns and rows basis.
- Profit & loss sheet comparison of preivous period (PP).
- Profit & loss sheet comparison of previous year (PY).
- Profit & loss sheet percentage analysis columns, rows, income and expenses basis.
## [1.5.8] - 13-01-2022 ## [1.5.8] - 13-01-2022
### Added ### Added

View File

@@ -1,6 +1,6 @@
{ {
"name": "bigcapital-client", "name": "bigcapital-client",
"version": "1.5.8", "version": "1.6.3",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@babel/core": "7.8.4", "@babel/core": "7.8.4",
@@ -72,6 +72,7 @@
"postcss-preset-env": "6.7.0", "postcss-preset-env": "6.7.0",
"postcss-rtl": "^1.7.3", "postcss-rtl": "^1.7.3",
"postcss-safe-parser": "4.0.1", "postcss-safe-parser": "4.0.1",
"query-string": "^7.1.1",
"ramda": "^0.27.1", "ramda": "^0.27.1",
"react": "^16.12.0", "react": "^16.12.0",
"react-app-polyfill": "^1.0.6", "react-app-polyfill": "^1.0.6",
@@ -93,7 +94,7 @@
"react-sortablejs": "^2.0.11", "react-sortablejs": "^2.0.11",
"react-split-pane": "^0.1.91", "react-split-pane": "^0.1.91",
"react-table": "^7.6.3", "react-table": "^7.6.3",
"react-table-sticky": "^1.1.2", "react-table-sticky": "^1.1.3",
"react-transition-group": "^4.4.1", "react-transition-group": "^4.4.1",
"react-use": "^13.26.1", "react-use": "^13.26.1",
"react-use-context-menu": "^0.1.4", "react-use-context-menu": "^0.1.4",

View File

@@ -19,6 +19,8 @@
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
--> -->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<% if (process.env.NODE_ENV === 'production') { %>
<!-- Hotjar Tracking Code for https://app.bigcapital.ly/ --> <!-- Hotjar Tracking Code for https://app.bigcapital.ly/ -->
<script> <script>
(function (h, o, t, j, a, r) { (function (h, o, t, j, a, r) {
@@ -35,6 +37,7 @@
a.appendChild(r); a.appendChild(r);
})(window, document, 'https://static.hotjar.com/c/hotjar-', '.js?sv='); })(window, document, 'https://static.hotjar.com/c/hotjar-', '.js?sv=');
</script> </script>
<% } %>
<!-- <!--
Notice the use of %PUBLIC_URL% in the tags above. Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build. It will be replaced with the URL of the `public` folder during the build.

View File

@@ -1,4 +1,3 @@
export * from './TableStyle'; export * from './TableStyle';
export const Align = { Left: 'left', Right: 'right', Center: 'center' };

View File

@@ -1,3 +1 @@
export * from './ButtonLink'; export * from './ButtonLink';

View File

@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import * as R from 'ramda'; import * as R from 'ramda';
import { ButtonLink } from 'components'; import { ButtonLink } from '../Button';
import withDrawerActions from 'containers/Drawer/withDrawerActions'; import withDrawerActions from 'containers/Drawer/withDrawerActions';
function CustomerDrawerLinkComponent({ function CustomerDrawerLinkComponent({

View File

@@ -1,7 +1,8 @@
import React from 'react'; import React from 'react';
import { Ability } from '@casl/ability'; import { Ability } from '@casl/ability';
import { createContextualCan } from '@casl/react'; import { createContextualCan } from '@casl/react';
import { useDashboardMeta } from '../../hooks/query';
import { useDashboardMetaBoot } from './DashboardBoot';
export const AbilityContext = React.createContext(); export const AbilityContext = React.createContext();
export const Can = createContextualCan(AbilityContext.Consumer); export const Can = createContextualCan(AbilityContext.Consumer);
@@ -11,8 +12,8 @@ export const Can = createContextualCan(AbilityContext.Consumer);
*/ */
export function DashboardAbilityProvider({ children }) { export function DashboardAbilityProvider({ children }) {
const { const {
data: { abilities }, meta: { abilities },
} = useDashboardMeta(); } = useDashboardMetaBoot();
// Ability instance. // Ability instance.
const ability = new Ability(abilities); const ability = new Ability(abilities);

View File

@@ -6,18 +6,26 @@ import {
} from '../../hooks/query'; } from '../../hooks/query';
import { useSplashLoading } from '../../hooks/state'; import { useSplashLoading } from '../../hooks/state';
import { useWatch, useWatchImmediate, useWhen } from '../../hooks'; import { useWatch, useWatchImmediate, useWhen } from '../../hooks';
import { useSubscription } from '../../hooks/state';
import { setCookie, getCookie } from '../../utils'; import { setCookie, getCookie } from '../../utils';
/** /**
* Dashboard meta async booting. * Dashboard meta async booting.
* - Fetches the dashboard meta only if the organization subscribe is active.
* - Once the dashboard meta query is loading display dashboard splash screen.
*/ */
export function useDashboardMetaBoot() { export function useDashboardMetaBoot() {
const { isSubscriptionActive } = useSubscription();
const { const {
data: dashboardMeta, data: dashboardMeta,
isLoading: isDashboardMetaLoading, isLoading: isDashboardMetaLoading,
isSuccess: isDashboardMetaSuccess, isSuccess: isDashboardMetaSuccess,
} = useDashboardMeta({ } = useDashboardMeta({
keepPreviousData: true, keepPreviousData: true,
// Avoid run the query if the organization subscription is not active.
enabled: isSubscriptionActive,
}); });
const [startLoading, stopLoading] = useSplashLoading(); const [startLoading, stopLoading] = useSplashLoading();
@@ -30,20 +38,12 @@ export function useDashboardMetaBoot() {
}, isDashboardMetaSuccess); }, isDashboardMetaSuccess);
return { return {
meta: dashboardMeta,
isLoading: isDashboardMetaLoading, isLoading: isDashboardMetaLoading,
isSuccess: isDashboardMetaSuccess
}; };
} }
/**
* Dashboard async booting.
* @returns {{ isLoading: boolean }}
*/
export function useDashboardBoot() {
const { isLoading } = useDashboardMetaBoot();
return { isLoading };
}
/** /**
* Application async booting. * Application async booting.
*/ */

View File

@@ -1,12 +1,12 @@
import React from 'react'; import React from 'react';
import { DashboardAbilityProvider } from '../../components'; import { DashboardAbilityProvider } from '../../components';
import { useDashboardBoot } from './DashboardBoot'; import { useDashboardMetaBoot } from './DashboardBoot';
/** /**
* Dashboard provider. * Dashboard provider.
*/ */
export default function DashboardProvider({ children }) { export default function DashboardProvider({ children }) {
const { isLoading } = useDashboardBoot(); const { isLoading } = useDashboardMetaBoot();
// Avoid display any dashboard component before complete booting. // Avoid display any dashboard component before complete booting.
if (isLoading) { if (isLoading) {

View File

@@ -15,7 +15,7 @@ function TableHeaderCell({ column, index }) {
<div <div
{...column.getHeaderProps({ {...column.getHeaderProps({
className: classNames(column.className || '', 'th', { className: classNames(column.className || '', 'th', {
'align-right': column.align === 'right', [`align-${column.align}`]: column.align,
}), }),
})} })}
> >
@@ -89,12 +89,14 @@ export default function TableHeader() {
return ( return (
<ScrollSyncPane> <ScrollSyncPane>
<div className="thead"> <div className="thead">
{headerGroups.map((headerGroup, index) => ( <div className={'thead-inner'}>
<TableHeaderGroup key={index} headerGroup={headerGroup} /> {headerGroups.map((headerGroup, index) => (
))} <TableHeaderGroup key={index} headerGroup={headerGroup} />
<If condition={progressBarLoading}> ))}
<MaterialProgressBar /> <If condition={progressBarLoading}>
</If> <MaterialProgressBar />
</If>
</div>
</div> </div>
</ScrollSyncPane> </ScrollSyncPane>
); );

View File

@@ -1,4 +1,5 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import clsx from 'classnames';
import TableContext from './TableContext'; import TableContext from './TableContext';
import { Skeleton } from 'components'; import { Skeleton } from 'components';
@@ -8,7 +9,13 @@ function TableHeaderCell({ column }) {
return ( return (
<div <div
{...column.getHeaderProps({ {...column.getHeaderProps({
className: 'th', className: clsx(
'th',
{
[`align-${column.align}`]: column.align,
},
column.className,
),
})} })}
> >
<Skeleton minWidth={skeletonWidthMin} maxWidth={skeletonWidthMax} /> <Skeleton minWidth={skeletonWidthMin} maxWidth={skeletonWidthMax} />

View File

@@ -1,4 +1,5 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import clsx from 'classnames';
import TableContext from './TableContext'; import TableContext from './TableContext';
import { Skeleton } from 'components'; import { Skeleton } from 'components';
@@ -11,7 +12,13 @@ function TableHeaderCell({ column }) {
return ( return (
<div <div
{...column.getHeaderProps({ {...column.getHeaderProps({
className: 'td', className: clsx(
'td',
{
[`align-${column.align}`]: column.align,
},
column.className,
),
})} })}
> >
<Skeleton minWidth={skeletonWidthMin} maxWidth={skeletonWidthMax} /> <Skeleton minWidth={skeletonWidthMin} maxWidth={skeletonWidthMax} />

View File

@@ -0,0 +1,23 @@
import React from 'react';
import styled from 'styled-components';
const FinancialStatementRoot = styled.div``;
const FinancialStatementBodyRoot = styled.div``;
/**
*
* @returns {React.JSX}
*/
export function FinancialReport({ children, className }) {
return <FinancialStatementRoot children={children} className={className} />;
}
/**
*
* @param {React.JSX}
*/
export function FinancialReportBody({ children, className }) {
return (
<FinancialStatementBodyRoot children={children} className={className} />
);
}

View File

@@ -1,103 +0,0 @@
import React, { useMemo, useCallback } from 'react';
import moment from 'moment';
import classnames from 'classnames';
import { FormattedMessage as T } from 'components';
import intl from 'react-intl-universal';
import 'style/pages/FinancialStatements/FinancialSheet.scss';
import { If, LoadingIndicator, MODIFIER } from 'components';
export default function FinancialSheet({
companyName,
sheetType,
fromDate,
toDate,
asDate,
children,
accountingBasis,
name,
loading,
className,
basis,
minimal = false,
fullWidth = false,
currentDate = true,
}) {
const format = 'DD MMMM YYYY';
const formattedFromDate = useMemo(() => moment(fromDate).format(format), [
fromDate,
]);
const formattedToDate = useMemo(() => moment(toDate).format(format), [
toDate,
]);
const formattedAsDate = useMemo(() => moment(asDate).format(format), [
asDate,
]);
const nameModifer = name ? `financial-sheet--${name}` : '';
const methodsLabels = useMemo(
() => ({
cash: intl.get('cash'),
accrual: intl.get('accrual'),
}),
[],
);
const getBasisLabel = useCallback((b) => methodsLabels[b], [methodsLabels]);
const basisLabel = useMemo(() => getBasisLabel(basis), [
getBasisLabel,
basis,
]);
return (
<div
className={classnames('financial-sheet', nameModifer, className, {
[MODIFIER.FINANCIAL_SHEET_MINIMAL]: minimal,
'is-full-width': fullWidth,
})}
>
{loading ? (
<LoadingIndicator loading={loading} spinnerSize={34} />
) : (
<div className={classnames('financial-sheet__inner')}>
<If condition={!!companyName}>
<h1 class="financial-sheet__title">{companyName}</h1>
</If>
<If condition={!!sheetType}>
<h6 class="financial-sheet__sheet-type">{sheetType}</h6>
</If>
<div class="financial-sheet__date">
<If condition={asDate}>
<T id={'as'} /> {formattedAsDate}
</If>
<If condition={fromDate && toDate}>
<T id={'from'} /> {formattedFromDate} | <T id={'to'} />{' '}
{formattedToDate}
</If>
</div>
<div class="financial-sheet__table">{children}</div>
<div class="financial-sheet__accounting-basis">{accountingBasis}</div>
<div class="financial-sheet__footer">
<If condition={basisLabel}>
<span class="financial-sheet__basis">
<T id={'accounting_basis'} /> {basisLabel}
</span>
</If>
<If condition={currentDate}>
<span class="financial-sheet__current-date">
{moment().format('YYYY MMM DD HH:MM')}
</span>
</If>
</div>
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,101 @@
import React, { useMemo, useCallback } from 'react';
import moment from 'moment';
import intl from 'react-intl-universal';
import If from '../Utils/If';
import { FormattedMessage as T } from '../FormattedMessage';
import {
FinancialSheetRoot,
FinancialSheetFooterCurrentTime,
FinancialSheetFooterBasis,
FinancialSheetFooter,
FinancialSheetAccountingBasis,
FinancialSheetTable,
FinancialSheetDate,
FinancialSheetType,
FinancialSheetTitle,
} from './StyledFinancialSheet';
/**
* Financial sheet.
* @returns {React.JSX}
*/
export function FinancialSheet({
companyName,
sheetType,
fromDate,
toDate,
asDate,
children,
accountingBasis,
basis,
minimal = false,
fullWidth = false,
currentDate = true,
className,
}) {
const format = 'DD MMMM YYYY';
const formattedFromDate = useMemo(
() => moment(fromDate).format(format),
[fromDate],
);
const formattedToDate = useMemo(
() => moment(toDate).format(format),
[toDate],
);
const formattedAsDate = useMemo(
() => moment(asDate).format(format),
[asDate],
);
const methodsLabels = useMemo(
() => ({
cash: intl.get('cash'),
accrual: intl.get('accrual'),
}),
[],
);
const getBasisLabel = useCallback((b) => methodsLabels[b], [methodsLabels]);
const basisLabel = useMemo(
() => getBasisLabel(basis),
[getBasisLabel, basis],
);
return (
<FinancialSheetRoot
minimal={minimal}
fullWidth={fullWidth}
className={className}
>
{companyName && <FinancialSheetTitle>{companyName}</FinancialSheetTitle>}
{sheetType && <FinancialSheetType>{sheetType}</FinancialSheetType>}
<FinancialSheetDate>
<If condition={asDate}>
<T id={'as'} /> {formattedAsDate}
</If>
<If condition={fromDate && toDate}>
<T id={'from'} /> {formattedFromDate} | <T id={'to'} />{' '}
{formattedToDate}
</If>
</FinancialSheetDate>
<FinancialSheetTable>{children}</FinancialSheetTable>
<FinancialSheetAccountingBasis>
{accountingBasis}
</FinancialSheetAccountingBasis>
<FinancialSheetFooter>
{basisLabel && (
<FinancialSheetFooterBasis>
<T id={'accounting_basis'} /> {basisLabel}
</FinancialSheetFooterBasis>
)}
{currentDate && (
<FinancialSheetFooterCurrentTime>
{moment().format('YYYY MMM DD HH:MM')}
</FinancialSheetFooterCurrentTime>
)}
</FinancialSheetFooter>
</FinancialSheetRoot>
);
}

View File

@@ -0,0 +1,85 @@
import React from 'react';
import styled from 'styled-components';
import { Align } from 'common';
import { SkeletonText } from 'components';
import DataTable from '../../components/DataTable'
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
import { TableStyle } from 'common';
import {
FinancialSheetRoot,
FinancialSheetTitle,
FinancialSheetType,
FinancialSheetDate,
FinancialSheetTable,
} from './StyledFinancialSheet';
/**
* Financial sheet paper skeleton.
* @returns {React.JSX}
*/
export function FinancialSheetSkeleton({
minimal,
fullWidth,
titleCharsLength,
typeCharsLength,
dateCharsLength,
skeletonTableColumns,
}) {
return (
<FinancialSheetRoot minimal={minimal} fullWidth={fullWidth}>
<FinancialSheetTitle>
<SkeletonText charsLength={titleCharsLength} />
</FinancialSheetTitle>
<FinancialSheetType>
<SkeletonText charsLength={typeCharsLength} />
</FinancialSheetType>
<FinancialSheetDate>
<SkeletonText charsLength={dateCharsLength} />
</FinancialSheetDate>
<FinancialSheetTable>
<FinancialSkeletonTable
columns={skeletonTableColumns}
data={[]}
noInitialFetch={true}
expandable={true}
styleName={TableStyle.Constrant}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
headerLoading={true}
loading={true}
/>
</FinancialSheetTable>
</FinancialSheetRoot>
);
}
FinancialSheetSkeleton.defaultProps = {
titleCharsLength: 20,
typeCharsLength: 40,
dateCharsLength: 20,
skeletonTableColumns: [
{
id: 'skeleton-1',
className: 'skeleton-1',
},
{
id: 'skeleton-2',
className: 'skeleton-2',
align: Align.Right,
},
],
};
const FinancialSkeletonTable = styled(DataTable)`
.table .th .skeleton,
.table .td .skeleton {
margin-top: 4px;
margin-bottom: 4px;
}
`;

View File

@@ -0,0 +1,9 @@
import styled from 'styled-components';
import DataTable from '../DataTable';
export const ReportDataTable = styled(DataTable)`
.table .tbody .tr.no-results:last-of-type .td {
border-bottom: 1px solid #ddd;
}
`;

View File

@@ -0,0 +1,82 @@
import styled from 'styled-components';
export const FinancialSheetRoot = styled.div`
border: 2px solid #f0f0f0;
border-radius: 10px;
min-width: 640px;
width: auto;
padding: 30px 18px;
max-width: 100%;
margin: 35px auto;
min-height: 400px;
display: flex;
flex-direction: column;
background: #fff;
${(props) =>
props.fullWidth &&
`
width: 100%;
margin-top: 25px;`}
${(props) =>
props.minimal &&
`
border: 0;
padding: 0;
margin-top: 20px;
${FinancialSheetTitle} {
font-size: 18px;
color: #333;
}
${FinancialSheetTitle} + ${FinancialSheetDate} {
margin-top: 8px;
}
${FinancialSheetDate} {
margin-top: 20px;
}
`}
`;
export const FinancialSheetTitle = styled.h1`
margin: 0;
font-weight: 400;
font-size: 20px;
color: #464646;
text-align: center;
`;
export const FinancialSheetType = styled.h6`
text-align: center;
margin: 0;
font-size: 16px;
font-weight: 400;
color: #666;
margin-top: 6px;
`;
export const FinancialSheetDate = styled.div`
text-align: center;
color: #666;
margin-top: 6px;
`;
export const FinancialSheetFooter = styled.div`
color: #888;
text-align: center;
margin-top: auto;
padding-top: 18px;
font-size: 13px;
> span + span {
padding-left: 10px;
}
`;
export const FinancialSheetTable = styled.div`
margin-top: 24px;
`;
export const FinancialSheetFooterBasis = styled.span``;
export const FinancialSheetFooterCurrentTime = styled.span``;
export const FinancialSheetAccountingBasis = styled.div``;

View File

@@ -0,0 +1,3 @@
export * from './FinancialSheet';
export * from './FinancialSheetSkeleton';
export * from './ReportDataTable';

View File

@@ -1,15 +1,24 @@
import React from 'react'; import React from 'react';
import className from 'classnames'; import styled from 'styled-components';
import 'style/containers/FinancialStatements/FinancialSheet.scss';
export default function FinancialStatements({ name, children }) { const FinancialStatementRoot = styled.div``;
const FinancialStatementBodyRoot = styled.div``;
/**
*
* @param {*} param0
* @returns
*/
export function FinancialStatement({ children, className }) {
return <FinancialStatementRoot children={children} className={className} />;
}
/**
*
* @param {React.JSX}
*/
export function FinancialStatementBody({ children, className }) {
return ( return (
<div <FinancialStatementBodyRoot children={children} className={className} />
className={className('financial-statement', {
[`financial-statement--${name}`]: name,
})}
>
{children}
</div>
); );
} }

View File

@@ -6,14 +6,36 @@ import { randomNumber } from 'utils';
/** /**
* Skeleton component. * Skeleton component.
*/ */
export default function Skeleton({ export function Skeleton({
Tag = 'span', Tag = 'span',
minWidth = 40, minWidth = 40,
maxWidth = 100, maxWidth = 100,
children,
}) { }) {
const randomWidth = useMemo(() => randomNumber(minWidth, maxWidth), [ const randomWidth = useMemo(
minWidth, () => randomNumber(minWidth, maxWidth),
maxWidth, [minWidth, maxWidth],
]); );
return <Tag className={'skeleton'} style={{ width: `${randomWidth}%` }} />; return (
<Tag
className={'skeleton'}
style={{ width: `${randomWidth}%` }}
children={children}
/>
);
}
export function SkeletonText({
Tag = 'span',
charsLength,
minChars = 40,
maxChars = 100,
}) {
const computedCharLength = useMemo(
() => (charsLength ? charsLength : randomNumber(minChars, maxChars)),
[charsLength, minChars, maxChars],
);
const randamText = 'X'.repeat(computedCharLength);
return <Tag className={'skeleton'}>{randamText}</Tag>;
} }

View File

View File

@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import * as R from 'ramda'; import * as R from 'ramda';
import { ButtonLink } from 'components'; import { ButtonLink } from '../Button';
import withDrawerActions from 'containers/Drawer/withDrawerActions'; import withDrawerActions from 'containers/Drawer/withDrawerActions';
function VendorDrawerLinkComponent({ function VendorDrawerLinkComponent({

View File

@@ -5,9 +5,6 @@ import Choose from './Utils/Choose';
import For from './Utils/For'; import For from './Utils/For';
import { FormattedMessage, FormattedHTMLMessage } from './FormattedMessage'; import { FormattedMessage, FormattedHTMLMessage } from './FormattedMessage';
import ListSelect from './ListSelect'; import ListSelect from './ListSelect';
import FinancialStatement from './FinancialStatement';
// import DynamicFilterValueField from './DynamicFilter/DynamicFilterValueField';
// import DynamicFilterCompatatorField from './DynamicFilter/DynamicFilterCompatatorField';
import ErrorMessage from './ErrorMessage'; import ErrorMessage from './ErrorMessage';
import MODIFIER from './modifiers'; import MODIFIER from './modifiers';
import FieldHint from './FieldHint'; import FieldHint from './FieldHint';
@@ -41,7 +38,6 @@ import InputPrependText from './Forms/InputPrependText';
import PageFormBigNumber from './PageFormBigNumber'; import PageFormBigNumber from './PageFormBigNumber';
import AccountsMultiSelect from './AccountsMultiSelect'; import AccountsMultiSelect from './AccountsMultiSelect';
import ContactsMultiSelect from './ContactsMultiSelect'; import ContactsMultiSelect from './ContactsMultiSelect';
import Skeleton from './Skeleton';
import ContextMenu from './ContextMenu'; import ContextMenu from './ContextMenu';
import TableFastCell from './Datatable/TableFastCell'; import TableFastCell from './Datatable/TableFastCell';
import DashboardContentTable from './Dashboard/DashboardContentTable'; import DashboardContentTable from './Dashboard/DashboardContentTable';
@@ -95,6 +91,10 @@ export * from './Card';
export * from './Customers' export * from './Customers'
export * from './Vendors' export * from './Vendors'
export * from './Table'; export * from './Table';
export * from './Skeleton';
export * from './FinancialStatement';
export * from './FinancialReport';
export * from './FinancialSheet';
const Hint = FieldHint; const Hint = FieldHint;
@@ -110,7 +110,6 @@ export {
T, T,
Money, Money,
ListSelect, ListSelect,
FinancialStatement,
// DynamicFilterValueField, // DynamicFilterValueField,
// DynamicFilterCompatatorField, // DynamicFilterCompatatorField,
MODIFIER, MODIFIER,
@@ -148,7 +147,6 @@ export {
DataTableEditable, DataTableEditable,
ContactsMultiSelect, ContactsMultiSelect,
TableFastCell, TableFastCell,
Skeleton,
ContextMenu, ContextMenu,
DashboardContentTable, DashboardContentTable,
DashboardPageContent, DashboardPageContent,

View File

@@ -70,6 +70,20 @@ export const financialReportMenus = [
subject: AbilitySubject.Report, subject: AbilitySubject.Report,
ability: ReportsAction.READ_AP_AGING_SUMMARY, ability: ReportsAction.READ_AP_AGING_SUMMARY,
}, },
{
title: <T id={'report.balance_sheet_comparison.title'} />,
desc: <T id={'report.balance_sheet_comparison.desc'} />,
link: 'financial-reports/balance-sheet?previousYear=true&previousYearAmountChange=true&previousYearPercentageChange=true',
subject: AbilitySubject.Report,
ability: ReportsAction.READ_BALANCE_SHEET,
},
{
title: <T id={'report.profit_loss_sheet_comparison.title'} />,
desc: <T id={'report.profit_loss_sheet_comparison.desc'} />,
link: '/financial-reports/profit-loss-sheet?previousYear=true&previousYearAmountChange=true&previousYearPercentageChange=true',
subject: AbilitySubject.Report,
ability: ReportsAction.READ_PROFIT_LOSS,
},
], ],
}, },
]; ];

View File

@@ -64,6 +64,7 @@ function BillPaymentTransactions({
}} }}
styleName={TableStyle.Constrant} styleName={TableStyle.Constrant}
TableLoadingRenderer={TableSkeletonRows} TableLoadingRenderer={TableSkeletonRows}
sticky={true}
/> />
); );
} }

View File

@@ -66,6 +66,7 @@ function EstimatePaymentTransactions({
}} }}
styleName={TableStyle.Constrant} styleName={TableStyle.Constrant}
TableLoadingRenderer={TableSkeletonRows} TableLoadingRenderer={TableSkeletonRows}
sticky={true}
/> />
); );
} }

View File

@@ -68,6 +68,7 @@ function InvoicePaymentTransactions({
}} }}
styleName={TableStyle.Constrant} styleName={TableStyle.Constrant}
TableLoadingRenderer={TableSkeletonRows} TableLoadingRenderer={TableSkeletonRows}
sticky={true}
/> />
); );
} }

View File

@@ -66,6 +66,7 @@ function ReceiptPaymentTransactions({
}} }}
styleName={TableStyle.Constrant} styleName={TableStyle.Constrant}
TableLoadingRenderer={TableSkeletonRows} TableLoadingRenderer={TableSkeletonRows}
sticky={true}
/> />
); );
} }

View File

@@ -1,19 +1,19 @@
import React, { useState, useCallback, useEffect } from 'react'; import React, { useState, useCallback, useEffect } from 'react';
import moment from 'moment'; import moment from 'moment';
import 'style/pages/FinancialStatements/APAgingSummary.scss'; import { getDefaultAPAgingSummaryQuery } from './common';
import { FinancialStatement } from 'components'; import { FinancialStatement } from 'components';
import APAgingSummaryHeader from './APAgingSummaryHeader'; import APAgingSummaryHeader from './APAgingSummaryHeader';
import APAgingSummaryActionsBar from './APAgingSummaryActionsBar'; import APAgingSummaryActionsBar from './APAgingSummaryActionsBar';
import APAgingSummaryTable from './APAgingSummaryTable'; import { APAgingSummaryBody } from './APAgingSummaryBody';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import { APAgingSummaryProvider } from './APAgingSummaryProvider'; import { APAgingSummaryProvider } from './APAgingSummaryProvider';
import { APAgingSummarySheetLoadingBar } from './components'; import { APAgingSummarySheetLoadingBar } from './components';
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
import withAPAgingSummaryActions from './withAPAgingSummaryActions'; import withAPAgingSummaryActions from './withAPAgingSummaryActions';
import { compose } from 'utils'; import { compose } from 'utils';
/** /**
@@ -27,11 +27,7 @@ function APAgingSummary({
toggleAPAgingSummaryFilterDrawer: toggleDisplayFilterDrawer, toggleAPAgingSummaryFilterDrawer: toggleDisplayFilterDrawer,
}) { }) {
const [filter, setFilter] = useState({ const [filter, setFilter] = useState({
asDate: moment().endOf('day').format('YYYY-MM-DD'), ...getDefaultAPAgingSummaryQuery(),
agingDaysBefore: 30,
agingPeriods: 3,
vendorsIds: [],
filterByOption: 'without-zero-balance',
}); });
// Handle filter submit. // Handle filter submit.
@@ -50,7 +46,6 @@ function APAgingSummary({
numberFormat, numberFormat,
}); });
}; };
// Hide the report filter drawer once the page unmount. // Hide the report filter drawer once the page unmount.
useEffect( useEffect(
() => () => { () => () => {
@@ -73,18 +68,11 @@ function APAgingSummary({
pageFilter={filter} pageFilter={filter}
onSubmitFilter={handleFilterSubmit} onSubmitFilter={handleFilterSubmit}
/> />
<div className={'financial-statement__body'}> <APAgingSummaryBody organizationName={organizationName} />
<APAgingSummaryTable organizationName={organizationName} />
</div>
</FinancialStatement> </FinancialStatement>
</DashboardPageContent> </DashboardPageContent>
</APAgingSummaryProvider> </APAgingSummaryProvider>
); );
} }
export default compose( export default compose(withAPAgingSummaryActions)(APAgingSummary);
withCurrentOrganization(({ organization }) => ({
organizationName: organization?.name,
})),
withAPAgingSummaryActions,
)(APAgingSummary);

View File

@@ -0,0 +1,37 @@
import React from 'react';
import * as R from 'ramda';
import { FinancialReportBody } from '../FinancialReportPage';
import APAgingSummaryTable from './APAgingSummaryTable';
import { FinancialSheetSkeleton } from '../../../components/FinancialSheet';
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
import { useAPAgingSummaryContext } from './APAgingSummaryProvider';
/**
* AP aging summary body.
* @returns {JSX.Element}
*/
function APAgingSummaryBodyJSX({
// #withCurrentOrganization
organizationName,
}) {
const { isLoading } = useAPAgingSummaryContext();
return (
<FinancialReportBody>
{isLoading ? (
<FinancialSheetSkeleton />
) : (
<APAgingSummaryTable organizationName={organizationName} />
)}
</FinancialReportBody>
);
}
export const APAgingSummaryBody = R.compose(
withCurrentOrganization(({ organization }) => ({
organizationName: organization?.name,
})),
)(APAgingSummaryBodyJSX);

View File

@@ -4,6 +4,7 @@ import { Formik, Form } from 'formik';
import * as Yup from 'yup'; import * as Yup from 'yup';
import moment from 'moment'; import moment from 'moment';
import { Tabs, Tab, Button, Intent } from '@blueprintjs/core'; import { Tabs, Tab, Button, Intent } from '@blueprintjs/core';
import styled from 'styled-components';
import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader'; import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
import APAgingSummaryHeaderGeneral from './APAgingSummaryHeaderGeneral'; import APAgingSummaryHeaderGeneral from './APAgingSummaryHeaderGeneral';
@@ -11,8 +12,8 @@ import APAgingSummaryHeaderGeneral from './APAgingSummaryHeaderGeneral';
import withAPAgingSummary from './withAPAgingSummary'; import withAPAgingSummary from './withAPAgingSummary';
import withAPAgingSummaryActions from './withAPAgingSummaryActions'; import withAPAgingSummaryActions from './withAPAgingSummaryActions';
import { compose } from 'utils';
import { transformToForm } from '../../../utils'; import { transformToForm } from '../../../utils';
import { compose } from 'utils';
/** /**
* AP Aging Summary Report - Drawer Header. * AP Aging Summary Report - Drawer Header.
@@ -72,7 +73,7 @@ function APAgingSummaryHeader({
}; };
return ( return (
<FinancialStatementHeader <APAgingDrawerHeader
isOpen={isFilterDrawerOpen} isOpen={isFilterDrawerOpen}
drawerProps={{ onClose: handleDrawerClose }} drawerProps={{ onClose: handleDrawerClose }}
> >
@@ -99,7 +100,7 @@ function APAgingSummaryHeader({
</div> </div>
</Form> </Form>
</Formik> </Formik>
</FinancialStatementHeader> </APAgingDrawerHeader>
); );
} }
@@ -109,3 +110,9 @@ export default compose(
isFilterDrawerOpen: APAgingSummaryFilterDrawer, isFilterDrawerOpen: APAgingSummaryFilterDrawer,
})), })),
)(APAgingSummaryHeader); )(APAgingSummaryHeader);
const APAgingDrawerHeader = styled(FinancialStatementHeader)`
.bp3-drawer {
max-height: 520px;
}
`;

View File

@@ -1,12 +1,15 @@
import React, { useCallback } from 'react'; import React from 'react';
import { FormattedMessage as T } from 'components';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { DataTable } from 'components'; import styled from 'styled-components';
import FinancialSheet from 'components/FinancialSheet';
import { ReportDataTable, FinancialSheet } from 'components';
import { TableStyle } from 'common';
import { useAPAgingSummaryContext } from './APAgingSummaryProvider'; import { useAPAgingSummaryContext } from './APAgingSummaryProvider';
import { useAPAgingSummaryColumns } from './components'; import { useAPAgingSummaryColumns } from './components';
import { tableRowTypesToClassnames } from 'utils';
/** /**
* AP aging summary table sheet. * AP aging summary table sheet.
*/ */
@@ -14,8 +17,6 @@ export default function APAgingSummaryTable({
//#ownProps //#ownProps
organizationName, organizationName,
}) { }) {
// AP aging summary report content. // AP aging summary report content.
const { const {
APAgingSummary: { tableRows }, APAgingSummary: { tableRows },
@@ -25,24 +26,33 @@ export default function APAgingSummaryTable({
// AP aging summary columns. // AP aging summary columns.
const columns = useAPAgingSummaryColumns(); const columns = useAPAgingSummaryColumns();
const rowClassNames = (row) => [`row-type--${row.original.rowType}`];
return ( return (
<FinancialSheet <FinancialSheet
companyName={organizationName} companyName={organizationName}
name={'payable-aging-summary'}
sheetType={intl.get('payable_aging_summary')} sheetType={intl.get('payable_aging_summary')}
asDate={new Date()} asDate={new Date()}
loading={isAPAgingLoading} loading={isAPAgingLoading}
> >
<DataTable <APAgingSummaryDataTable
className={'bigcapital-datatable--financial-report'}
columns={columns} columns={columns}
data={tableRows} data={tableRows}
rowClassNames={rowClassNames} rowClassNames={tableRowTypesToClassnames}
noInitialFetch={true} noInitialFetch={true}
sticky={true} sticky={true}
styleName={TableStyle.Constrant}
/> />
</FinancialSheet> </FinancialSheet>
); );
} }
const APAgingSummaryDataTable = styled(ReportDataTable)`
.table {
.tbody .tr {
.td {
border-bottom: 0;
padding-top: 0.32rem;
padding-bottom: 0.32rem;
}
}
}
`;

View File

@@ -1,5 +1,16 @@
import moment from 'moment';
import { transformToCamelCase, flatObject } from 'utils'; import { transformToCamelCase, flatObject } from 'utils';
export const transformFilterFormToQuery = (form) => { export const transformFilterFormToQuery = (form) => {
return flatObject(transformToCamelCase(form)); return flatObject(transformToCamelCase(form));
}; };
export const getDefaultAPAgingSummaryQuery = () => {
return {
asDate: moment().endOf('day').format('YYYY-MM-DD'),
agingDaysBefore: 30,
agingPeriods: 3,
vendorsIds: [],
filterByOption: 'without-zero-balance',
}
}

View File

@@ -1,38 +1,29 @@
import React, { useState, useCallback, useEffect } from 'react'; import React, { useState, useCallback, useEffect } from 'react';
import moment from 'moment'; import moment from 'moment';
import 'style/pages/FinancialStatements/ARAgingSummary.scss';
import { FinancialStatement } from 'components'; import { FinancialStatement } from 'components';
import ARAgingSummaryHeader from './ARAgingSummaryHeader'; import ARAgingSummaryHeader from './ARAgingSummaryHeader';
import ARAgingSummaryActionsBar from './ARAgingSummaryActionsBar'; import ARAgingSummaryActionsBar from './ARAgingSummaryActionsBar';
import ARAgingSummaryTable from './ARAgingSummaryTable';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import { ARAgingSummaryProvider } from './ARAgingSummaryProvider'; import { ARAgingSummaryProvider } from './ARAgingSummaryProvider';
import { ARAgingSummarySheetLoadingBar } from './components'; import { ARAgingSummarySheetLoadingBar } from './components';
import { ARAgingSummaryBody } from './ARAgingSummaryBody';
import withARAgingSummaryActions from './withARAgingSummaryActions' import withARAgingSummaryActions from './withARAgingSummaryActions';
import withCurrentOrganization from '../../../containers/Organization/withCurrentOrganization';
import { getDefaultARAgingSummaryQuery } from './common';
import { compose } from 'utils'; import { compose } from 'utils';
/** /**
* A/R aging summary report. * A/R aging summary report.
*/ */
function ReceivableAgingSummarySheet({ function ReceivableAgingSummarySheet({
// #withSettings
organizationName,
// #withARAgingSummaryActions // #withARAgingSummaryActions
toggleARAgingSummaryFilterDrawer: toggleDisplayFilterDrawer toggleARAgingSummaryFilterDrawer: toggleDisplayFilterDrawer,
}) { }) {
const [filter, setFilter] = useState({ const [filter, setFilter] = useState({
asDate: moment().endOf('day').format('YYYY-MM-DD'), ...getDefaultARAgingSummaryQuery(),
agingDaysBefore: 30,
agingPeriods: 3,
customersIds: [],
filterByOption: 'without-zero-balance',
}); });
// Handle filter submit. // Handle filter submit.
@@ -48,11 +39,13 @@ function ReceivableAgingSummarySheet({
const handleNumberFormatSubmit = (numberFormat) => { const handleNumberFormatSubmit = (numberFormat) => {
setFilter({ ...filter, numberFormat }); setFilter({ ...filter, numberFormat });
}; };
// Hide the filter drawer once the page unmount. // Hide the filter drawer once the page unmount.
useEffect(() => () => { useEffect(
toggleDisplayFilterDrawer(false); () => () => {
}, [toggleDisplayFilterDrawer]); toggleDisplayFilterDrawer(false);
},
[toggleDisplayFilterDrawer],
);
return ( return (
<ARAgingSummaryProvider filter={filter}> <ARAgingSummaryProvider filter={filter}>
@@ -63,23 +56,16 @@ function ReceivableAgingSummarySheet({
<ARAgingSummarySheetLoadingBar /> <ARAgingSummarySheetLoadingBar />
<DashboardPageContent> <DashboardPageContent>
<FinancialStatement name={'AR-aging-summary'}> <FinancialStatement>
<ARAgingSummaryHeader <ARAgingSummaryHeader
pageFilter={filter} pageFilter={filter}
onSubmitFilter={handleFilterSubmit} onSubmitFilter={handleFilterSubmit}
/> />
<div class="financial-statement__body"> <ARAgingSummaryBody />
<ARAgingSummaryTable organizationName={organizationName} />
</div>
</FinancialStatement> </FinancialStatement>
</DashboardPageContent> </DashboardPageContent>
</ARAgingSummaryProvider> </ARAgingSummaryProvider>
); );
} }
export default compose( export default compose(withARAgingSummaryActions)(ReceivableAgingSummarySheet);
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
withARAgingSummaryActions
)(ReceivableAgingSummarySheet);

View File

@@ -0,0 +1,36 @@
import React from 'react';
import * as R from 'ramda';
import ARAgingSummaryTable from './ARAgingSummaryTable';
import { FinancialReportBody } from '../FinancialReportPage';
import { FinancialSheetSkeleton } from '../../../components';
import withCurrentOrganization from '../../../containers/Organization/withCurrentOrganization';
import { useARAgingSummaryContext } from './ARAgingSummaryProvider';
/**
* A/R Aging summary body.
* @returns {JSX.Element}
*/
function ARAgingSummaryBodyJSX({
// #withCurrentOrganization
organizationName,
}) {
const { isARAgingLoading } = useARAgingSummaryContext();
return (
<FinancialReportBody>
{isARAgingLoading ? (
<FinancialSheetSkeleton />
) : (
<ARAgingSummaryTable organizationName={organizationName} />
)}
</FinancialReportBody>
);
}
export const ARAgingSummaryBody = R.compose(
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
)(ARAgingSummaryBodyJSX);

View File

@@ -4,6 +4,7 @@ import { Formik, Form } from 'formik';
import * as Yup from 'yup'; import * as Yup from 'yup';
import moment from 'moment'; import moment from 'moment';
import { Tabs, Tab, Button, Intent } from '@blueprintjs/core'; import { Tabs, Tab, Button, Intent } from '@blueprintjs/core';
import styled from 'styled-components';
import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader'; import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
import ARAgingSummaryHeaderGeneral from './ARAgingSummaryHeaderGeneral'; import ARAgingSummaryHeaderGeneral from './ARAgingSummaryHeaderGeneral';
@@ -57,14 +58,12 @@ function ARAgingSummaryHeader({
}, },
defaultValues, defaultValues,
); );
// Handle form submit. // Handle form submit.
const handleSubmit = (values, { setSubmitting }) => { const handleSubmit = (values, { setSubmitting }) => {
onSubmitFilter(values); onSubmitFilter(values);
toggleFilterDrawerDisplay(false); toggleFilterDrawerDisplay(false);
setSubmitting(false); setSubmitting(false);
}; };
// Handle cancel button click. // Handle cancel button click.
const handleCancelClick = () => { const handleCancelClick = () => {
toggleFilterDrawerDisplay(false); toggleFilterDrawerDisplay(false);
@@ -75,7 +74,7 @@ function ARAgingSummaryHeader({
}; };
return ( return (
<FinancialStatementHeader <ARAgingDrawerHeader
isOpen={isFilterDrawerOpen} isOpen={isFilterDrawerOpen}
drawerProps={{ onClose: handleDrawerClose }} drawerProps={{ onClose: handleDrawerClose }}
> >
@@ -103,7 +102,7 @@ function ARAgingSummaryHeader({
</div> </div>
</Form> </Form>
</Formik> </Formik>
</FinancialStatementHeader> </ARAgingDrawerHeader>
); );
} }
@@ -113,3 +112,9 @@ export default compose(
isFilterDrawerOpen: ARAgingSummaryFilterDrawer, isFilterDrawerOpen: ARAgingSummaryFilterDrawer,
})), })),
)(ARAgingSummaryHeader); )(ARAgingSummaryHeader);
const ARAgingDrawerHeader = styled(FinancialStatementHeader)`
.bp3-drawer {
max-height: 520px;
}
`;

View File

@@ -1,11 +1,15 @@
import React, { useCallback } from 'react'; import React from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import DataTable from 'components/DataTable'; import styled from 'styled-components';
import FinancialSheet from 'components/FinancialSheet';
import { ReportDataTable, FinancialSheet } from 'components';
import { TableStyle } from 'common';
import { useARAgingSummaryContext } from './ARAgingSummaryProvider'; import { useARAgingSummaryContext } from './ARAgingSummaryProvider';
import { useARAgingSummaryColumns } from './components'; import { useARAgingSummaryColumns } from './components';
import { tableRowTypesToClassnames } from 'utils';
/** /**
* AR aging summary table sheet. * AR aging summary table sheet.
*/ */
@@ -19,24 +23,52 @@ export default function ReceivableAgingSummaryTable({
// AR aging summary columns. // AR aging summary columns.
const columns = useARAgingSummaryColumns(); const columns = useARAgingSummaryColumns();
const rowClassNames = (row) => [`row-type--${row.original.rowType}`];
return ( return (
<FinancialSheet <FinancialSheet
companyName={organizationName} companyName={organizationName}
name={'receivable-aging-summary'}
sheetType={intl.get('receivable_aging_summary')} sheetType={intl.get('receivable_aging_summary')}
asDate={new Date()} asDate={new Date()}
loading={isARAgingLoading} loading={isARAgingLoading}
> >
<DataTable <ARAgingSummaryDataTable
className="bigcapital-datatable--financial-report"
columns={columns} columns={columns}
data={ARAgingSummary.tableRows} data={ARAgingSummary.tableRows}
rowClassNames={rowClassNames} rowClassNames={tableRowTypesToClassnames}
noInitialFetch={true} noInitialFetch={true}
sticky={true} sticky={true}
styleName={TableStyle.Constrant}
/> />
</FinancialSheet> </FinancialSheet>
); );
} }
const ARAgingSummaryDataTable = styled(ReportDataTable)`
.table {
.tbody .tr {
.td {
border-bottom: 0;
padding-top: 0.32rem;
padding-bottom: 0.32rem;
}
&:not(.no-results) {
.td {
border-bottom: 0;
padding-top: 0.4rem;
padding-bottom: 0.4rem;
}
&:not(:first-child) .td {
border-top: 1px solid transparent;
}
&.row_type--total {
font-weight: 500;
.td {
border-top: 1px solid #bbb;
border-bottom: 3px double #333;
}
}
}
}
}
`;

View File

@@ -1,5 +1,19 @@
import moment from 'moment';
import { transformToCamelCase, flatObject } from 'utils'; import { transformToCamelCase, flatObject } from 'utils';
export const transfromFilterFormToQuery = (form) => { export const transfromFilterFormToQuery = (form) => {
return flatObject(transformToCamelCase(form)); return flatObject(transformToCamelCase(form));
}; };
/**
* Retrieves the default A/R aging summary query.
*/
export const getDefaultARAgingSummaryQuery = () => {
return {
asDate: moment().endOf('day').format('YYYY-MM-DD'),
agingDaysBefore: 30,
agingPeriods: 3,
customersIds: [],
filterByOption: 'without-zero-balance',
};
};

View File

@@ -1,9 +1,11 @@
import React from 'react'; import React from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { useARAgingSummaryContext } from './ARAgingSummaryProvider'; import { useARAgingSummaryContext } from './ARAgingSummaryProvider';
import { If, FormattedMessage as T } from 'components';
import { getColumnWidth } from 'utils'; import { getColumnWidth } from 'utils';
import { FormattedMessage as T } from 'components'; import { Align } from 'common';
import { If } from 'components';
import FinancialLoadingBar from '../FinancialLoadingBar'; import FinancialLoadingBar from '../FinancialLoadingBar';
/** /**
@@ -40,6 +42,7 @@ export const useARAgingSummaryColumns = () => {
width: getColumnWidth(tableRows, `current`, { width: getColumnWidth(tableRows, `current`, {
minWidth: 120, minWidth: 120,
}), }),
align: Align.Right
}, },
...agingColumns.map((agingColumn, index) => ({ ...agingColumns.map((agingColumn, index) => ({
Header: agingColumn, Header: agingColumn,
@@ -47,6 +50,7 @@ export const useARAgingSummaryColumns = () => {
width: getColumnWidth(tableRows, `aging-${index}`, { width: getColumnWidth(tableRows, `aging-${index}`, {
minWidth: 120, minWidth: 120,
}), }),
align: Align.Right
})), })),
{ {
Header: <T id={'total'} />, Header: <T id={'total'} />,
@@ -56,6 +60,7 @@ export const useARAgingSummaryColumns = () => {
width: getColumnWidth(tableRows, 'total', { width: getColumnWidth(tableRows, 'total', {
minWidth: 120, minWidth: 120,
}), }),
align: Align.Right
}, },
], ],
[tableRows, agingColumns], [tableRows, agingColumns],

View File

@@ -1,57 +1,47 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect } from 'react';
import moment from 'moment'; import moment from 'moment';
import 'style/pages/FinancialStatements/BalanceSheet.scss';
import { BalanceSheetAlerts, BalanceSheetLoadingBar } from './components'; import { BalanceSheetAlerts, BalanceSheetLoadingBar } from './components';
import { FinancialStatement } from 'components'; import { FinancialStatement } from 'components';
import BalanceSheetHeader from './BalanceSheetHeader'; import BalanceSheetHeader from './BalanceSheetHeader';
import BalanceSheetTable from './BalanceSheetTable';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import BalanceSheetActionsBar from './BalanceSheetActionsBar'; import BalanceSheetActionsBar from './BalanceSheetActionsBar';
import { BalanceSheetProvider } from './BalanceSheetProvider'; import { BalanceSheetProvider } from './BalanceSheetProvider';
import { BalanceSheetBody } from './BalanceSheetBody';
import withBalanceSheetActions from './withBalanceSheetActions'; import withBalanceSheetActions from './withBalanceSheetActions';
import withCurrentOrganization from '../../../containers/Organization/withCurrentOrganization';
import { useBalanceSheetQuery } from './utils';
import { compose } from 'utils'; import { compose } from 'utils';
/** /**
* Balance sheet. * Balance sheet.
* @returns {React.JSX}
*/ */
function BalanceSheet({ function BalanceSheet({
// #withCurrentOrganization
organizationName,
// #withBalanceSheetActions // #withBalanceSheetActions
toggleBalanceSheetFilterDrawer, toggleBalanceSheetFilterDrawer,
}) { }) {
const [filter, setFilter] = useState({ // Balance sheet query.
fromDate: moment().startOf('year').format('YYYY-MM-DD'), const { query, setLocationQuery } = useBalanceSheetQuery();
toDate: moment().endOf('year').format('YYYY-MM-DD'),
basis: 'cash',
displayColumnsType: 'total',
filterByOption: 'without-zero-balance',
});
// Handle re-fetch balance sheet after filter change. // Handle re-fetch balance sheet after filter change.
const handleFilterSubmit = (filter) => { const handleFilterSubmit = (filter) => {
const _filter = { const newFilter = {
...filter, ...filter,
fromDate: moment(filter.fromDate).format('YYYY-MM-DD'), fromDate: moment(filter.fromDate).format('YYYY-MM-DD'),
toDate: moment(filter.toDate).format('YYYY-MM-DD'), toDate: moment(filter.toDate).format('YYYY-MM-DD'),
}; };
setFilter({ ..._filter }); setLocationQuery({ ...newFilter });
}; };
// Handle number format submit. // Handle number format submit.
const handleNumberFormatSubmit = (values) => { const handleNumberFormatSubmit = (values) => {
setFilter({ setLocationQuery({
...filter, ...query,
numberFormat: values, numberFormat: values,
}); });
}; };
// Hides the balance sheet filter drawer once the page unmount. // Hides the balance sheet filter drawer once the page unmount.
useEffect( useEffect(
() => () => { () => () => {
@@ -61,9 +51,9 @@ function BalanceSheet({
); );
return ( return (
<BalanceSheetProvider filter={filter}> <BalanceSheetProvider filter={query}>
<BalanceSheetActionsBar <BalanceSheetActionsBar
numberFormat={filter.numberFormat} numberFormat={query.numberFormat}
onNumberFormatSubmit={handleNumberFormatSubmit} onNumberFormatSubmit={handleNumberFormatSubmit}
/> />
<BalanceSheetLoadingBar /> <BalanceSheetLoadingBar />
@@ -72,22 +62,14 @@ function BalanceSheet({
<DashboardPageContent> <DashboardPageContent>
<FinancialStatement> <FinancialStatement>
<BalanceSheetHeader <BalanceSheetHeader
pageFilter={filter} pageFilter={query}
onSubmitFilter={handleFilterSubmit} onSubmitFilter={handleFilterSubmit}
/> />
<div class="financial-statement__body"> <BalanceSheetBody />
<BalanceSheetTable companyName={organizationName} />
</div>
</FinancialStatement> </FinancialStatement>
</DashboardPageContent> </DashboardPageContent>
</BalanceSheetProvider> </BalanceSheetProvider>
); );
} }
export default compose( export default compose(withBalanceSheetActions)(BalanceSheet);
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
withBalanceSheetActions,
)(BalanceSheet);

View File

@@ -0,0 +1,36 @@
import React from 'react';
import BalanceSheetTable from './BalanceSheetTable';
import { FinancialReportBody } from '../FinancialReportPage';
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
import { useBalanceSheetContext } from './BalanceSheetProvider';
import { FinancialSheetSkeleton } from '../../../components/FinancialSheet';
import { compose } from 'utils';
/**
* Balance sheet body JSX.
* @returns {React.JSX}
*/
function BalanceSheetBodyJSX({
// #withCurrentOrganization
organizationName,
}) {
const { isLoading } = useBalanceSheetContext();
return (
<FinancialReportBody>
{isLoading ? (
<FinancialSheetSkeleton />
) : (
<BalanceSheetTable companyName={organizationName} />
)}
</FinancialReportBody>
);
}
export const BalanceSheetBody = compose(
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
)(BalanceSheetBodyJSX);

View File

@@ -1,19 +1,22 @@
import React from 'react'; import React from 'react';
import { Tabs, Tab, Button, Intent } from '@blueprintjs/core'; import { Tabs, Tab, Button, Intent } from '@blueprintjs/core';
import { FormattedMessage as T } from 'components';
import moment from 'moment'; import moment from 'moment';
import { Formik, Form } from 'formik'; import { Formik, Form } from 'formik';
import styled from 'styled-components';
import { FormattedMessage as T } from 'components';
import withBalanceSheet from './withBalanceSheet'; import withBalanceSheet from './withBalanceSheet';
import withBalanceSheetActions from './withBalanceSheetActions'; import withBalanceSheetActions from './withBalanceSheetActions';
import BalanceSheetHeaderGeneralPanal from './BalanceSheetHeaderGeneralPanal'; import BalanceSheetHeaderGeneralPanal from './BalanceSheetHeaderGeneralPanal';
import BalanceSheetHeaderComparisonPanal from './BalanceSheetHeaderComparisonPanal';
import FinancialStatementHeader from '../../FinancialStatements/FinancialStatementHeader'; import FinancialStatementHeader from '../../FinancialStatements/FinancialStatementHeader';
import { compose, transformToForm } from 'utils'; import { compose, transformToForm } from 'utils';
import { import {
getBalanceSheetHeaderDefaultValues,
getBalanceSheetHeaderValidationSchema, getBalanceSheetHeaderValidationSchema,
getDefaultBalanceSheetQuery,
} from './utils'; } from './utils';
/** /**
@@ -30,7 +33,7 @@ function BalanceSheetHeader({
// #withBalanceSheetActions // #withBalanceSheetActions
toggleBalanceSheetFilterDrawer: toggleFilterDrawer, toggleBalanceSheetFilterDrawer: toggleFilterDrawer,
}) { }) {
const defaultValues = getBalanceSheetHeaderDefaultValues(); const defaultValues = getDefaultBalanceSheetQuery();
// Filter form initial values. // Filter form initial values.
const initialValues = transformToForm( const initialValues = transformToForm(
@@ -42,7 +45,6 @@ function BalanceSheetHeader({
}, },
defaultValues, defaultValues,
); );
// Validation schema. // Validation schema.
const validationSchema = getBalanceSheetHeaderValidationSchema(); const validationSchema = getBalanceSheetHeaderValidationSchema();
@@ -64,9 +66,11 @@ function BalanceSheetHeader({
}; };
return ( return (
<FinancialStatementHeader <BalanceSheetFinancialHeader
isOpen={balanceSheetDrawerFilter} isOpen={balanceSheetDrawerFilter}
drawerProps={{ onClose: handleDrawerClose }} drawerProps={{
onClose: handleDrawerClose,
}}
> >
<Formik <Formik
initialValues={initialValues} initialValues={initialValues}
@@ -80,6 +84,11 @@ function BalanceSheetHeader({
title={<T id={'general'} />} title={<T id={'general'} />}
panel={<BalanceSheetHeaderGeneralPanal />} panel={<BalanceSheetHeaderGeneralPanal />}
/> />
<Tab
id="comparison"
title={<T id={'balance_sheet.comparisons'} />}
panel={<BalanceSheetHeaderComparisonPanal />}
/>
</Tabs> </Tabs>
<div class="financial-header-drawer__footer"> <div class="financial-header-drawer__footer">
@@ -92,7 +101,7 @@ function BalanceSheetHeader({
</div> </div>
</Form> </Form>
</Formik> </Formik>
</FinancialStatementHeader> </BalanceSheetFinancialHeader>
); );
} }
@@ -102,3 +111,9 @@ export default compose(
})), })),
withBalanceSheetActions, withBalanceSheetActions,
)(BalanceSheetHeader); )(BalanceSheetHeader);
const BalanceSheetFinancialHeader = styled(FinancialStatementHeader)`
.bp3-drawer {
max-height: 520px;
}
`;

View File

@@ -0,0 +1,155 @@
import React from 'react';
import { FastField, Field } from 'formik';
import { FormGroup, Checkbox } from '@blueprintjs/core';
import styled from 'styled-components';
import { Row, Col, FieldHint, FormattedMessage as T } from 'components';
import {
handlePreviousYearCheckBoxChange,
handlePreviousYearChangeCheckboxChange,
handlePreviousPeriodCheckBoxChange,
handlePreivousPeriodPercentageCheckboxChange,
handlePreviousYearPercentageCheckboxChange,
handlePreviousPeriodChangeCheckboxChange,
} from './utils';
/**
* Balance sheet header - Comparison panal.
*/
export default function BalanceSheetHeaderComparisonPanal() {
return (
<BalanceSheetComparisonWrap>
{/**----------- Previous Year -----------*/}
<Field name={'previousYear'} type={'checkbox'}>
{({ form, field }) => (
<FormGroup labelInfo={<FieldHint />}>
<Checkbox
inline={true}
label={<T id={'balance_sheet.previous_year'} />}
{...field}
onChange={handlePreviousYearCheckBoxChange(form)}
/>
</FormGroup>
)}
</Field>
<Row>
<Col xs={3}>
<Field name={'previousYearAmountChange'} type={'checkbox'}>
{({ form, field }) => (
<FormGroup labelInfo={<FieldHint />}>
<Checkbox
inline={true}
small={true}
label={<T id={'balance_sheet.total_change'} />}
{...field}
onChange={handlePreviousYearChangeCheckboxChange(form)}
/>
</FormGroup>
)}
</Field>
</Col>
<Col xs={3}>
<FastField name={'previousYearPercentageChange'} type={'checkbox'}>
{({ form, field }) => (
<FormGroup labelInfo={<FieldHint />}>
<Checkbox
inline={true}
label={<T id={'balance_sheet.change'} />}
{...field}
onChange={handlePreviousYearPercentageCheckboxChange(form)}
/>
</FormGroup>
)}
</FastField>
</Col>
</Row>
{/*------------ Previous Period -----------*/}
<FastField name={'previousPeriod'} type={'checkbox'}>
{({ form, field }) => (
<FormGroup labelInfo={<FieldHint />}>
<Checkbox
inline={true}
small={true}
label={<T id={'balance_sheet.previous_period'} />}
{...field}
onChange={handlePreviousPeriodCheckBoxChange(form)}
/>
</FormGroup>
)}
</FastField>
<Row>
<Col xs={3}>
<FastField name={'previousPeriodAmountChange'} type={'checkbox'}>
{({ form, field }) => (
<FormGroup labelInfo={<FieldHint />}>
<Checkbox
inline={true}
small={true}
label={<T id={'balance_sheet.total_change'} />}
{...field}
onChange={handlePreviousPeriodChangeCheckboxChange(form)}
/>
</FormGroup>
)}
</FastField>
</Col>
<Col xs={3}>
<FastField name={'previousPeriodPercentageChange'} type={'checkbox'}>
{({ form, field }) => (
<FormGroup labelInfo={<FieldHint />}>
<Checkbox
inline={true}
label={<T id={'balance_sheet.change'} />}
{...field}
onChange={handlePreivousPeriodPercentageCheckboxChange(form)}
/>
</FormGroup>
)}
</FastField>
</Col>
</Row>
{/**----------- % of Column -----------*/}
<FastField name={'percentageOfColumn'} type={'checkbox'}>
{({ field }) => (
<FormGroup labelInfo={<FieldHint />}>
<Checkbox
inline={true}
small={true}
label={<T id={'balance_sheet.percentage_of_column'} />}
{...field}
/>
</FormGroup>
)}
</FastField>
{/**----------- % of Row -----------*/}
<FastField name={'percentageOfRow'} type={'checkbox'}>
{({ field }) => (
<FormGroup labelInfo={<FieldHint />}>
<Checkbox
inline={true}
small={true}
label={<T id={'balance_sheet.percentage_of_row'} />}
{...field}
/>
</FormGroup>
)}
</FastField>
</BalanceSheetComparisonWrap>
);
}
const BalanceSheetComparisonWrap = styled.div`
.row {
margin-left: 0.15rem;
.col {
min-width: 150px !important;
max-width: 190px !important;
}
}
.bp3-form-group {
margin-bottom: 3px;
}
`;

View File

@@ -1,4 +1,5 @@
import React, { createContext, useContext } from 'react'; import React, { createContext, useContext } from 'react';
import FinancialReportPage from '../FinancialReportPage'; import FinancialReportPage from '../FinancialReportPage';
import { useBalanceSheet } from 'hooks/query'; import { useBalanceSheet } from 'hooks/query';
import { transformFilterFormToQuery } from '../common'; import { transformFilterFormToQuery } from '../common';
@@ -10,7 +11,6 @@ function BalanceSheetProvider({ filter, ...props }) {
const query = React.useMemo(() => transformFilterFormToQuery(filter), [ const query = React.useMemo(() => transformFilterFormToQuery(filter), [
filter, filter,
]); ]);
// Fetches the balance sheet report. // Fetches the balance sheet report.
const { const {
data: balanceSheet, data: balanceSheet,

View File

@@ -1,13 +1,15 @@
import React, { useMemo, useCallback } from 'react'; import React from 'react';
import styled from 'styled-components';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import classNames from 'classnames';
import FinancialSheet from 'components/FinancialSheet'; import { ReportDataTable, FinancialSheet } from 'components';
import DataTable from 'components/DataTable';
import { CellTextSpan } from 'components/Datatable/Cells';
import { useBalanceSheetContext } from './BalanceSheetProvider'; import { useBalanceSheetContext } from './BalanceSheetProvider';
import { defaultExpanderReducer, getColumnWidth } from 'utils'; import { defaultExpanderReducer, tableRowTypesToClassnames } from 'utils';
import { useBalanceSheetColumns } from './components';
import { TableStyle } from 'common';
/** /**
* Balance sheet table. * Balance sheet table.
@@ -16,91 +18,73 @@ export default function BalanceSheetTable({
// #ownProps // #ownProps
companyName, companyName,
}) { }) {
// Balance sheet context. // Balance sheet context.
const { const {
balanceSheet: { tableRows, columns, query }, balanceSheet: { table, query },
isLoading,
} = useBalanceSheetContext(); } = useBalanceSheetContext();
const tableColumns = useMemo( // Retrieve the database columns.
() => [ const tableColumns = useBalanceSheetColumns();
{
Header: intl.get('account_name'), // Retrieve default expanded rows of balance sheet.
accessor: (row) => (row.code ? `${row.name} - ${row.code}` : row.name), const expandedRows = React.useMemo(
className: 'account_name', () => defaultExpanderReducer(table.rows, 3),
textOverview: true, [table],
width: 240,
},
...(query.display_columns_type === 'total'
? [
{
Header: intl.get('total'),
accessor: 'total.formatted_amount',
Cell: CellTextSpan,
className: 'total',
width: 140,
},
]
: []),
...(query.display_columns_type === 'date_periods'
? columns.map((column, index) => ({
id: `date_period_${index}`,
Header: column,
Cell: CellTextSpan,
accessor: `total_periods[${index}].formatted_amount`,
className: classNames('total-period', `total-periods-${index}`),
width: getColumnWidth(
tableRows,
`total_periods.${index}.formatted_amount`,
{ minWidth: 100 },
),
}))
: []),
],
[query, columns, tableRows],
); );
// Calculates the default expanded rows of balance sheet table.
const expandedRows = useMemo(() => defaultExpanderReducer(tableRows, 4), [tableRows]);
const rowClassNames = useCallback((row) => {
const { original } = row;
const rowTypes = Array.isArray(original.row_types)
? original.row_types
: [original.row_types];
return {
...rowTypes.reduce((acc, rowType) => {
acc[`row_type--${rowType}`] = rowType;
return acc;
}, {}),
};
}, []);
return ( return (
<FinancialSheet <FinancialSheet
name="balance-sheet"
companyName={companyName} companyName={companyName}
sheetType={intl.get('balance_sheet')} sheetType={intl.get('balance_sheet')}
fromDate={query.from_date} asDate={query.to_date}
toDate={query.to_date}
basis={query.basis} basis={query.basis}
loading={isLoading}
> >
<DataTable <BalanceSheetDataTable
className="bigcapital-datatable--financial-report"
columns={tableColumns} columns={tableColumns}
data={tableRows} data={table.rows}
rowClassNames={rowClassNames} rowClassNames={tableRowTypesToClassnames}
noInitialFetch={true} noInitialFetch={true}
expandable={true} expandable={true}
expanded={expandedRows} expanded={expandedRows}
expandToggleColumn={1} expandToggleColumn={1}
expandColumnSpace={0.8} expandColumnSpace={0.8}
// sticky={true} headerLoading={true}
sticky={true}
styleName={TableStyle.Constrant}
/> />
</FinancialSheet> </FinancialSheet>
); );
} }
const BalanceSheetDataTable = styled(ReportDataTable)`
.table {
.tbody .tr {
.td {
border-bottom: 0;
padding-top: 0.32rem;
padding-bottom: 0.32rem;
}
&.is-expanded {
.td:not(.name) .cell-inner {
opacity: 0;
}
}
&.row_type--TOTAL {
.td {
font-weight: 500;
border-top: 1px solid #bbb;
}
}
&:last-of-type .td {
border-bottom: 1px solid #bbb;
}
&.row_type--TOTAL.row-id--ASSETS,
&.row_type--TOTAL.row-id--LIABILITY_EQUITY {
.td {
border-bottom: 3px double #000;
}
}
}
}
`;

View File

@@ -1,9 +1,12 @@
import React from 'react'; import React from 'react';
import { Button } from '@blueprintjs/core'; import { Button } from '@blueprintjs/core';
import { Icon, If } from 'components';
import { FormattedMessage as T, Icon, If } from 'components';
import { useBalanceSheetContext } from './BalanceSheetProvider'; import { useBalanceSheetContext } from './BalanceSheetProvider';
import { FormattedMessage as T } from 'components';
import FinancialLoadingBar from '../FinancialLoadingBar'; import FinancialLoadingBar from '../FinancialLoadingBar';
import { FinancialComputeAlert } from '../FinancialReportPage';
import { dynamicColumns } from './dynamicColumns';
/** /**
* Balance sheet alerts. * Balance sheet alerts.
@@ -17,19 +20,18 @@ export function BalanceSheetAlerts() {
refetchBalanceSheet(); refetchBalanceSheet();
}; };
// Can't display any error if the report is loading. // Can't display any error if the report is loading.
if (isLoading) { if (isLoading) return null;
return null;
}
return ( return (
<If condition={balanceSheet.meta.is_cost_compute_running}> <If condition={balanceSheet.meta.is_cost_compute_running}>
<div class="alert-compute-running"> <FinancialComputeAlert>
<Icon icon="info-block" iconSize={12} />{' '} <Icon icon="info-block" iconSize={12} />{' '}
<T id={'just_a_moment_we_re_calculating_your_cost_transactions'} /> <T id={'just_a_moment_we_re_calculating_your_cost_transactions'} />
<Button onClick={handleRecalcReport} minimal={true} small={true}> <Button onClick={handleRecalcReport} minimal={true} small={true}>
<T id={'report.compute_running.refresh'} /> <T id={'report.compute_running.refresh'} />
</Button> </Button>
</div> </FinancialComputeAlert>
</If> </If>
); );
} }
@@ -46,3 +48,18 @@ export function BalanceSheetLoadingBar() {
</If> </If>
); );
} }
/**
* Retrieve balance sheet columns.
*/
export const useBalanceSheetColumns = () => {
// Balance sheet context.
const {
balanceSheet: { table },
} = useBalanceSheetContext();
return React.useMemo(
() => dynamicColumns(table.columns, table.rows),
[table],
);
};

View File

@@ -0,0 +1,333 @@
import * as R from 'ramda';
import { isEmpty } from 'lodash';
import { Align } from 'common';
import { CellTextSpan } from 'components/Datatable/Cells';
import { getColumnWidth } from 'utils';
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 accountNameMapper = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
key: column.key,
Header: column.label,
accessor,
className: column.key,
textOverview: true,
width: Math.max(width, 300),
sticky: Align.Left,
};
});
/**
* Assoc columns to total column.
*/
const assocColumnsToTotalColumn = R.curry((data, column, columnAccessor) => {
const columns = totalColumnsComposer(data, column);
return R.assoc('columns', columns, columnAccessor);
});
/**
* Detarmines whether the given column has children columns.
* @returns {boolean}
*/
const isColumnHasColumns = (column) => !isEmpty(column.children);
/**
*
* @param {*} data
* @param {*} column
* @returns
*/
const dateRangeSoloColumnAttrs = (data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
return {
accessor,
width: getReportColWidth(data, accessor),
};
};
/**
* Date range columns mapper.
*/
const dateRangeMapper = R.curry((data, column) => {
const isDateColumnHasColumns = isColumnHasColumns(column);
const columnAccessor = {
Header: column.label,
key: column.key,
disableSortBy: true,
textOverview: true,
align: isDateColumnHasColumns ? Align.Center : Align.Right,
};
return R.compose(
R.when(
R.always(isDateColumnHasColumns),
assocColumnsToTotalColumn(data, column),
),
R.when(
R.always(!isDateColumnHasColumns),
R.mergeLeft(dateRangeSoloColumnAttrs(data, column)),
),
)(columnAccessor);
});
/**
* Total column mapper.
*/
const totalMapper = R.curry((data, column) => {
const hasChildren = !isEmpty(column.children);
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
const columnAccessor = {
key: column.key,
Header: column.label,
accessor,
textOverview: true,
Cell: CellTextSpan,
width,
disableSortBy: true,
align: hasChildren ? Align.Center : Align.Right,
};
return R.compose(
R.when(R.always(hasChildren), assocColumnsToTotalColumn(data, column)),
)(columnAccessor);
});
/**
* `Percentage of column` column accessor.
*/
const percentageOfColumnAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
Header: column.label,
key: column.key,
accessor,
width,
align: Align.Right,
disableSortBy: true,
textOverview: true,
};
});
/**
* `Percentage of row` column accessor.
*/
const percentageOfRowAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
Header: column.label,
key: column.key,
accessor,
width,
align: Align.Right,
disableSortBy: true,
textOverview: true,
};
});
/**
* Previous year column accessor.
*/
const previousYearAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
Header: column.label,
key: column.key,
accessor,
width,
align: Align.Right,
disableSortBy: true,
textOverview: true,
};
});
/**
* Pervious year change column accessor.
*/
const previousYearChangeAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
Header: column.label,
key: column.key,
accessor,
width,
align: Align.Right,
disableSortBy: true,
textOverview: true,
};
});
/**
* Previous year percentage column accessor.
*/
const previousYearPercentageAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
Header: column.label,
key: column.key,
accessor,
width,
align: Align.Right,
disableSortBy: true,
textOverview: true,
};
});
/**
* Previous period column accessor.
*/
const previousPeriodAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
Header: column.label,
key: column.key,
accessor,
width,
align: Align.Right,
disableSortBy: true,
textOverview: true,
};
});
/**
* Previous period change column accessor.
*/
const previousPeriodChangeAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
Header: column.label,
key: column.key,
accessor,
width,
align: Align.Right,
disableSortBy: true,
textOverview: true,
};
});
/**
* Previous period percentage column accessor.
*/
const previousPeriodPercentageAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
Header: column.label,
key: column.key,
accessor,
width,
align: Align.Right,
disableSortBy: true,
textOverview: true,
};
});
/**
*
* @param {*} column
* @param {*} index
* @returns
*/
const totalColumnsMapper = R.curry((data, column) => {
return R.compose(
R.when(R.pathEq(['key'], 'total'), totalMapper(data)),
// Percetage of column/row.
R.when(
R.pathEq(['key'], 'percentage_of_column'),
percentageOfColumnAccessor(data),
),
R.when(
R.pathEq(['key'], 'percentage_of_row'),
percentageOfRowAccessor(data),
),
// Previous year.
R.when(R.pathEq(['key'], 'previous_year'), previousYearAccessor(data)),
R.when(
R.pathEq(['key'], 'previous_year_change'),
previousYearChangeAccessor(data),
),
R.when(
R.pathEq(['key'], 'previous_year_percentage'),
previousYearPercentageAccessor(data),
),
// Pervious period.
R.when(R.pathEq(['key'], 'previous_period'), previousPeriodAccessor(data)),
R.when(
R.pathEq(['key'], 'previous_period_change'),
previousPeriodChangeAccessor(data),
),
R.when(
R.pathEq(['key'], 'previous_period_percentage'),
previousPeriodPercentageAccessor(data),
),
)(column);
});
/**
* Total sub-columns composer.
*/
const totalColumnsComposer = R.curry((data, column) => {
return R.map(totalColumnsMapper(data), column.children);
});
/**
* Detarmines the given string starts with `date-range` string.
*/
const isMatchesDateRange = (r) => R.match(/^date-range/g, r).length > 0;
/**
* Dynamic column mapper.
*/
const dynamicColumnMapper = R.curry((data, column) => {
const indexTotalMapper = totalMapper(data);
const indexAccountNameMapper = accountNameMapper(data);
const indexDatePeriodMapper = dateRangeMapper(data);
return R.compose(
R.when(R.pathSatisfies(isMatchesDateRange, ['key']), indexDatePeriodMapper),
R.when(R.pathEq(['key'], 'name'), indexAccountNameMapper),
R.when(R.pathEq(['key'], 'total'), indexTotalMapper),
)(column);
});
/**
* Cash flow dynamic columns.
*/
export const dynamicColumns = (columns, data) => {
return R.map(dynamicColumnMapper(data), columns);
};

View File

@@ -1,7 +1,63 @@
import React from 'react';
import * as R from 'ramda';
import moment from 'moment';
import * as Yup from 'yup'; import * as Yup from 'yup';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import moment from 'moment';
import { transformToForm } from 'utils';
import { useAppQueryString } from 'hooks';
/**
* Retrieves the default balance sheet query.
* @returns {}
*/
export const getDefaultBalanceSheetQuery = () => ({
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'),
basis: 'cash',
displayColumnsType: 'total',
filterByOption: 'without-zero-balance',
previousYear: false,
previousYearAmountChange: false,
previousYearPercentageChange: false,
previousPeriod: false,
previousPeriodAmountChange: false,
previousPeriodPercentageChange: false,
// Percentage columns.
percentageOfColumn: false,
percentageOfRow: false,
});
/**
* Retrieves the balance sheet query.
*/
export const useBalanceSheetQuery = () => {
// Retrieves location query.
const [locationQuery, setLocationQuery] = useAppQueryString();
// Merges the default filter query with location URL query.
const query = React.useMemo(() => {
const defaultQuery = getDefaultBalanceSheetQuery();
return {
...defaultQuery,
...transformToForm(locationQuery, defaultQuery),
};
}, [locationQuery]);
return {
query,
locationQuery,
setLocationQuery,
};
};
/**
* Retrieves the balance sheet header default values.
*/
export const getBalanceSheetHeaderDefaultValues = () => { export const getBalanceSheetHeaderDefaultValues = () => {
return { return {
basic: 'cash', basic: 'cash',
@@ -12,6 +68,9 @@ export const getBalanceSheetHeaderDefaultValues = () => {
}; };
}; };
/**
* Retrieves the balance sheet header validation schema.
*/
export const getBalanceSheetHeaderValidationSchema = () => export const getBalanceSheetHeaderValidationSchema = () =>
Yup.object().shape({ Yup.object().shape({
dateRange: Yup.string().optional(), dateRange: Yup.string().optional(),
@@ -23,3 +82,83 @@ export const getBalanceSheetHeaderValidationSchema = () =>
filterByOption: Yup.string(), filterByOption: Yup.string(),
displayColumnsType: Yup.string(), displayColumnsType: Yup.string(),
}); });
/**
* Handles previous year checkbox change.
*/
export const handlePreviousYearCheckBoxChange = R.curry((form, event) => {
const isChecked = event.currentTarget.checked;
form.setFieldValue('previousYear', isChecked);
if (!isChecked) {
form.setFieldValue('previousYearAmountChange', isChecked);
form.setFieldValue('previousYearPercentageChange', isChecked);
}
});
/**
* Handles previous period checkbox change.
*/
export const handlePreviousPeriodCheckBoxChange = R.curry((form, event) => {
const isChecked = event.currentTarget.checked;
form.setFieldValue('previousPeriod', isChecked);
if (!isChecked) {
form.setFieldValue('previousPeriodAmountChange', isChecked);
form.setFieldValue('previousPeriodPercentageChange', isChecked);
}
});
/**
* Handles previous year change checkbox change.
*/
export const handlePreviousYearChangeCheckboxChange = R.curry((form, event) => {
const isChecked = event.currentTarget.checked;
if (isChecked) {
form.setFieldValue('previousYear', event.currentTarget.checked);
}
form.setFieldValue('previousYearAmountChange', event.currentTarget.checked);
});
/**
* Handles preivous year percentage checkbox change.
*/
export const handlePreviousYearPercentageCheckboxChange = R.curry(
(form, event) => {
const isChecked = event.currentTarget.checked;
if (isChecked) {
form.setFieldValue('previousYear', event.currentTarget.checked);
}
form.setFieldValue('previousYearPercentageChange', isChecked);
},
);
/**
* Handles previous period percentage checkbox change.
*/
export const handlePreivousPeriodPercentageCheckboxChange = R.curry(
(form, event) => {
const isChecked = event.currentTarget.checked;
if (isChecked) {
form.setFieldValue('previousPeriod', isChecked);
}
form.setFieldValue('previousPeriodPercentageChange', isChecked);
},
);
/**
* Handle previous period change checkbox change.
*/
export const handlePreviousPeriodChangeCheckboxChange = R.curry(
(form, event) => {
const isChecked = event.currentTarget.checked;
if (isChecked) {
form.setFieldValue('previousPeriod', isChecked);
}
form.setFieldValue('previousPeriodAmountChange', isChecked);
},
);

View File

@@ -1,15 +1,13 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import moment from 'moment'; import moment from 'moment';
import 'style/pages/FinancialStatements/CashFlowStatement.scss';
import { FinancialStatement } from 'components'; import { FinancialStatement } from 'components';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import CashFlowStatementHeader from './CashFlowStatementHeader'; import CashFlowStatementHeader from './CashFlowStatementHeader';
import CashFlowStatementTable from './CashFlowStatementTable';
import CashFlowStatementActionsBar from './CashFlowStatementActionsBar'; import CashFlowStatementActionsBar from './CashFlowStatementActionsBar';
import { CashFlowStatementBody } from './CashFlowStatementBody';
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
import withCashFlowStatementActions from './withCashFlowStatementActions'; import withCashFlowStatementActions from './withCashFlowStatementActions';
import { CashFlowStatementProvider } from './CashFlowStatementProvider'; import { CashFlowStatementProvider } from './CashFlowStatementProvider';
import { import {
@@ -17,26 +15,20 @@ import {
CashFlowStatementAlerts, CashFlowStatementAlerts,
} from './components'; } from './components';
import { getDefaultCashFlowSheetQuery } from './utils';
import { compose } from 'utils'; import { compose } from 'utils';
/** /**
* Cash flow statement. * Cash flow statement.
*/ */
function CashFlowStatement({ function CashFlowStatement({
// #withPreferences
organizationName,
//#withCashStatementActions //#withCashStatementActions
toggleCashFlowStatementFilterDrawer, toggleCashFlowStatementFilterDrawer,
}) { }) {
// filter // filter
const [filter, setFilter] = useState({ const [filter, setFilter] = useState({
fromDate: moment().startOf('year').format('YYYY-MM-DD'), ...getDefaultCashFlowSheetQuery(),
toDate: moment().endOf('year').format('YYYY-MM-DD'),
basis: 'cash',
displayColumnsType: 'total',
filterByOption: 'with-transactions',
}); });
// Handle refetch cash flow after filter change. // Handle refetch cash flow after filter change.
const handleFilterSubmit = (filter) => { const handleFilterSubmit = (filter) => {
const _filter = { const _filter = {
@@ -46,7 +38,6 @@ function CashFlowStatement({
}; };
setFilter({ ..._filter }); setFilter({ ..._filter });
}; };
// Handle format number submit. // Handle format number submit.
const handleNumberFormatSubmit = (values) => { const handleNumberFormatSubmit = (values) => {
setFilter({ setFilter({
@@ -77,18 +68,11 @@ function CashFlowStatement({
pageFilter={filter} pageFilter={filter}
onSubmitFilter={handleFilterSubmit} onSubmitFilter={handleFilterSubmit}
/> />
<div class="financial-statement__body"> <CashFlowStatementBody />
<CashFlowStatementTable companyName={organizationName} />
</div>
</FinancialStatement> </FinancialStatement>
</DashboardPageContent> </DashboardPageContent>
</CashFlowStatementProvider> </CashFlowStatementProvider>
); );
} }
export default compose( export default compose(withCashFlowStatementActions)(CashFlowStatement);
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
withCashFlowStatementActions,
)(CashFlowStatement);

View File

@@ -0,0 +1,36 @@
import React from 'react';
import * as R from 'ramda';
import CashFlowStatementTable from './CashFlowStatementTable';
import { FinancialReportBody } from '../FinancialReportPage';
import { FinancialSheetSkeleton } from 'components/FinancialSheet';
import { useCashFlowStatementContext } from './CashFlowStatementProvider';
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
/**
* Cashflow stement body.
* @returns {React.JSX}
*/
function CashFlowStatementBodyJSX({
// #withPreferences
organizationName,
}) {
const { isCashFlowLoading } = useCashFlowStatementContext();
return (
<FinancialReportBody>
{isCashFlowLoading ? (
<FinancialSheetSkeleton />
) : (
<CashFlowStatementTable companyName={organizationName} />
)}
</FinancialReportBody>
);
}
export const CashFlowStatementBody = R.compose(
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
)(CashFlowStatementBodyJSX);

View File

@@ -12,6 +12,7 @@ import CashFlowStatementGeneralPanel from './CashFlowStatementGeneralPanel';
import withCashFlowStatement from './withCashFlowStatement'; import withCashFlowStatement from './withCashFlowStatement';
import withCashFlowStatementActions from './withCashFlowStatementActions'; import withCashFlowStatementActions from './withCashFlowStatementActions';
import { getDefaultCashFlowSheetQuery } from './utils';
import { compose, transformToForm } from 'utils'; import { compose, transformToForm } from 'utils';
/** /**
@@ -29,10 +30,7 @@ function CashFlowStatementHeader({
toggleCashFlowStatementFilterDrawer, toggleCashFlowStatementFilterDrawer,
}) { }) {
// Filter form default values. // Filter form default values.
const defaultValues = { const defaultValues = getDefaultCashFlowSheetQuery();
fromDate: moment().toDate(),
toDate: moment().toDate(),
};
// Initial form values. // Initial form values.
const initialValues = transformToForm({ const initialValues = transformToForm({

View File

@@ -1,12 +1,14 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import styled from 'styled-components';
import { DataTable, FinancialSheet } from 'components';
import { DataTable } from 'components';
import FinancialSheet from 'components/FinancialSheet';
import { useCashFlowStatementColumns } from './components'; import { useCashFlowStatementColumns } from './components';
import { useCashFlowStatementContext } from './CashFlowStatementProvider'; import { useCashFlowStatementContext } from './CashFlowStatementProvider';
import { defaultExpanderReducer } from 'utils'; import { TableStyle } from 'common';
import { defaultExpanderReducer, tableRowTypesToClassnames } from 'utils';
/** /**
* Cash flow statement table. * Cash flow statement table.
@@ -15,11 +17,8 @@ export default function CashFlowStatementTable({
// #ownProps // #ownProps
companyName, companyName,
}) { }) {
const { const {
cashFlowStatement: { tableRows }, cashFlowStatement: { tableRows },
isCashFlowLoading,
query, query,
} = useCashFlowStatementContext(); } = useCashFlowStatementContext();
@@ -29,35 +28,63 @@ export default function CashFlowStatementTable({
() => defaultExpanderReducer(tableRows, 4), () => defaultExpanderReducer(tableRows, 4),
[tableRows], [tableRows],
); );
const rowClassNames = (row) => {
return [
`row-type--${row.original.row_types}`,
`row-type--${row.original.id}`,
];
};
return ( return (
<FinancialSheet <FinancialSheet
name="cash-flow-statement"
companyName={companyName} companyName={companyName}
sheetType={intl.get('statement_of_cash_flow')} sheetType={intl.get('statement_of_cash_flow')}
loading={isCashFlowLoading}
fromDate={query.from_date} fromDate={query.from_date}
toDate={query.to_date} toDate={query.to_date}
basis={query.basis} basis={query.basis}
> >
<DataTable <CashflowStatementDataTable
className="bigcapital-datatable--financial-report"
columns={columns} columns={columns}
data={tableRows} data={tableRows}
rowClassNames={rowClassNames} rowClassNames={tableRowTypesToClassnames}
noInitialFetch={true} noInitialFetch={true}
expandable={true} expandable={true}
expanded={expandedRows} expanded={expandedRows}
expandToggleColumn={1} expandToggleColumn={1}
expandColumnSpace={0.8} expandColumnSpace={0.8}
styleName={TableStyle.Constrant}
/> />
</FinancialSheet> </FinancialSheet>
); );
} }
const CashflowStatementDataTable = styled(DataTable)`
.table {
.tbody {
.tr:not(.no-results) {
.td {
border-bottom: 0;
padding-top: 0.32rem;
padding-bottom: 0.32rem;
}
// &.row-type--AGGREGATE,
&.row_type--ACCOUNTS {
border-top: 1px solid #bbb;
}
&.row-id--CASH_END_PERIOD {
border-bottom: 3px double #333;
}
&.row_type--TOTAL {
font-weight: 500;
&:not(:first-child) .td {
border-top: 1px solid #bbb;
}
}
}
.tr.is-expanded {
.td.total,
.td.date-period {
.cell-inner {
display: none;
}
}
}
}
}
`;

View File

@@ -1,12 +1,13 @@
import React from 'react'; import React from 'react';
import { Button } from '@blueprintjs/core'; import { Button } from '@blueprintjs/core';
import { Icon, If } from 'components'; import { Icon, If } from 'components';
import { FormattedMessage as T } from 'components'; import { FormattedMessage as T } from 'components';
import { dynamicColumns } from './utils';
import { useCashFlowStatementContext } from './CashFlowStatementProvider';
import FinancialLoadingBar from '../FinancialLoadingBar'; import FinancialLoadingBar from '../FinancialLoadingBar';
import { dynamicColumns } from './dynamicColumns';
import { useCashFlowStatementContext } from './CashFlowStatementProvider';
/** /**
* Retrieve cash flow statement columns. * Retrieve cash flow statement columns.
*/ */
@@ -49,7 +50,6 @@ export function CashFlowStatementAlerts() {
if (isCashFlowLoading) { if (isCashFlowLoading) {
return null; return null;
} }
return ( return (
<If condition={cashFlowStatement.meta.is_cost_compute_running}> <If condition={cashFlowStatement.meta.is_cost_compute_running}>
<div className="alert-compute-running"> <div className="alert-compute-running">

View File

@@ -0,0 +1,79 @@
import * as R from 'ramda';
import intl from 'react-intl-universal';
import { Align } from 'common';
import { CellTextSpan } from 'components/Datatable/Cells';
import { getColumnWidth } from 'utils';
/**
* Account name column mapper.
*/
const accountNameMapper = (column) => ({
id: column.key,
key: column.key,
Header: intl.get('account_name'),
accessor: 'cells[0].value',
className: 'account_name',
textOverview: true,
width: 400,
disableSortBy: true,
sticky: Align.Left,
});
/**
* Date range columns mapper.
*/
const dateRangeMapper = (data, index, column) => ({
id: column.key,
Header: column.label,
key: column.key,
accessor: `cells[${index}].value`,
width: getColumnWidth(data, `cells.${index}.value`, {
magicSpacing: 10,
minWidth: 100,
}),
className: `date-period ${column.key}`,
disableSortBy: true,
textOverview: true,
align: Align.Right,
});
/**
* Total column mapper.
*/
const totalMapper = (data, index, column) => ({
key: 'total',
Header: intl.get('total'),
accessor: `cells[${index}].value`,
className: 'total',
textOverview: true,
Cell: CellTextSpan,
width: getColumnWidth(data, `cells[${index}].value`, {
magicSpacing: 10,
minWidth: 100,
}),
disableSortBy: true,
align: Align.Right,
});
/**
* Detarmines the given string starts with `date-range` string.
*/
const isMatchesDateRange = (r) => R.match(/^date-range/g, r).length > 0;
/**
* Cash flow dynamic columns.
*/
export const dynamicColumns = (columns, data) => {
const mapper = (column, index) => {
return R.compose(
R.when(
R.pathSatisfies(isMatchesDateRange, ['key']),
R.curry(dateRangeMapper)(data, index),
),
R.when(R.pathEq(['key'], 'name'), accountNameMapper),
R.when(R.pathEq(['key'], 'total'), R.curry(totalMapper)(data, index)),
)(column);
};
return columns.map(mapper);
};

View File

@@ -1,74 +1,14 @@
import * as R from 'ramda'; import moment from 'moment';
import { CellTextSpan } from 'components/Datatable/Cells';
import { getColumnWidth } from 'utils';
import intl from 'react-intl-universal';
/** /**
* Account name column mapper. * Retrieves the default cashflow sheet query.
*/ */
const accountNameMapper = (column) => ({ export const getDefaultCashFlowSheetQuery = () => {
id: column.key, return {
key: column.key, fromDate: moment().startOf('year').format('YYYY-MM-DD'),
Header: intl.get('account_name'), toDate: moment().endOf('year').format('YYYY-MM-DD'),
accessor: 'cells[0].value', basis: 'cash',
className: 'account_name', displayColumnsType: 'total',
textOverview: true, filterByOption: 'with-transactions',
width: 400,
disableSortBy: true,
});
/**
* Date range columns mapper.
*/
const dateRangeMapper = (data, index, column) => ({
id: column.key,
Header: column.label,
key: column.key,
accessor: `cells[${index}].value`,
width: getColumnWidth(data, `cells.${index}.value`, {
magicSpacing: 10,
minWidth: 100,
}),
className: `date-period ${column.key}`,
disableSortBy: true,
textOverview: true,
});
/**
* Total column mapper.
*/
const totalMapper = (data, index, column) => ({
key: 'total',
Header: intl.get('total'),
accessor: `cells[${index}].value`,
className: 'total',
textOverview: true,
Cell: CellTextSpan,
width: getColumnWidth(data, `cells[${index}].value`, {
magicSpacing: 10,
minWidth: 100,
}),
disableSortBy: true,
});
/**
* Detarmines the given string starts with `date-range` string.
*/
const isMatchesDateRange = (r) => R.match(/^date-range/g, r).length > 0;
/**
* Cash flow dynamic columns.
*/
export const dynamicColumns = (columns, data) => {
const mapper = (column, index) => {
return R.compose(
R.when(
R.pathSatisfies(isMatchesDateRange, ['key']),
R.curry(dateRangeMapper)(data, index),
),
R.when(R.pathEq(['key'], 'name'), accountNameMapper),
R.when(R.pathEq(['key'], 'total'), R.curry(totalMapper)(data, index)),
)(column);
}; };
return columns.map(mapper);
}; };

View File

@@ -0,0 +1,35 @@
import React from 'react';
import * as R from 'ramda';
import { FinancialReportBody } from '../FinancialReportPage';
import CustomersBalanceSummaryTable from './CustomersBalanceSummaryTable';
import { FinancialSheetSkeleton } from '../../../components/FinancialSheet';
import { useCustomersBalanceSummaryContext } from './CustomersBalanceSummaryProvider';
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
/**
* Customer balance summary body.
* @returns {JSX.Element}
*/
function CustomerBalanceSummaryBodyJSX({
// #withPreferences
organizationName,
}) {
const { isCustomersBalanceLoading } = useCustomersBalanceSummaryContext();
return (
<FinancialReportBody>
{isCustomersBalanceLoading ? (
<FinancialSheetSkeleton />
) : (
<CustomersBalanceSummaryTable companyName={organizationName} />
)}
</FinancialReportBody>
);
}
export const CustomerBalanceSummaryBody = R.compose(
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
)(CustomerBalanceSummaryBodyJSX);

View File

@@ -1,37 +1,30 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import moment from 'moment'; import moment from 'moment';
import * as R from 'ramda';
import 'style/pages/FinancialStatements/ContactsBalanceSummary.scss';
import { FinancialStatement } from 'components'; import { FinancialStatement } from 'components';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import CustomersBalanceSummaryActionsBar from './CustomersBalanceSummaryActionsBar'; import CustomersBalanceSummaryActionsBar from './CustomersBalanceSummaryActionsBar';
import CustomersBalanceSummaryHeader from './CustomersBalanceSummaryHeader'; import CustomersBalanceSummaryHeader from './CustomersBalanceSummaryHeader';
import CustomersBalanceSummaryTable from './CustomersBalanceSummaryTable';
import { CustomerBalanceSummaryBody } from './CustomerBalanceSummaryBody';
import { CustomersBalanceLoadingBar } from './components'; import { CustomersBalanceLoadingBar } from './components';
import { CustomersBalanceSummaryProvider } from './CustomersBalanceSummaryProvider'; import { CustomersBalanceSummaryProvider } from './CustomersBalanceSummaryProvider';
import withCustomersBalanceSummaryActions from './withCustomersBalanceSummaryActions'; import withCustomersBalanceSummaryActions from './withCustomersBalanceSummaryActions';
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
import { compose } from 'redux'; import { getDefaultCustomersBalanceQuery } from './utils';
/** /**
* Customers Balance summary. * Customers Balance summary.
*/ */
function CustomersBalanceSummary({ function CustomersBalanceSummary({
// #withPreferences
organizationName,
// #withCustomersBalanceSummaryActions // #withCustomersBalanceSummaryActions
toggleCustomerBalanceFilterDrawer, toggleCustomerBalanceFilterDrawer,
}) { }) {
const [filter, setFilter] = useState({ const [filter, setFilter] = useState({
asDate: moment().endOf('day').format('YYYY-MM-DD'), ...getDefaultCustomersBalanceQuery(),
filterByOption: 'with-transactions',
}); });
// Handle re-fetch customers balance summary after filter change. // Handle re-fetch customers balance summary after filter change.
const handleFilterSubmit = (filter) => { const handleFilterSubmit = (filter) => {
const _filter = { const _filter = {
@@ -40,7 +33,6 @@ function CustomersBalanceSummary({
}; };
setFilter({ ..._filter }); setFilter({ ..._filter });
}; };
// Handle number format. // Handle number format.
const handleNumberFormat = (values) => { const handleNumberFormat = (values) => {
setFilter({ setFilter({
@@ -66,23 +58,16 @@ function CustomersBalanceSummary({
<DashboardPageContent> <DashboardPageContent>
<FinancialStatement> <FinancialStatement>
<div className="financial-statement--balance-summary "> <CustomersBalanceSummaryHeader
<CustomersBalanceSummaryHeader pageFilter={filter}
pageFilter={filter} onSubmitFilter={handleFilterSubmit}
onSubmitFilter={handleFilterSubmit} />
/> <CustomerBalanceSummaryBody />
<div className="financial-statement__body">
<CustomersBalanceSummaryTable companyName={organizationName} />
</div>
</div>
</FinancialStatement> </FinancialStatement>
</DashboardPageContent> </DashboardPageContent>
</CustomersBalanceSummaryProvider> </CustomersBalanceSummaryProvider>
); );
} }
export default compose( export default R.compose(withCustomersBalanceSummaryActions)(
withCurrentOrganization(({ organization }) => ({ CustomersBalanceSummary,
organizationName: organization.name, );
})),
withCustomersBalanceSummaryActions,
)(CustomersBalanceSummary);

View File

@@ -3,6 +3,8 @@ import * as Yup from 'yup';
import { Formik, Form } from 'formik'; import { Formik, Form } from 'formik';
import moment from 'moment'; import moment from 'moment';
import { Tabs, Tab, Button, Intent } from '@blueprintjs/core'; import { Tabs, Tab, Button, Intent } from '@blueprintjs/core';
import styled from 'styled-components';
import { FormattedMessage as T } from 'components'; import { FormattedMessage as T } from 'components';
import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader'; import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
@@ -30,7 +32,6 @@ function CustomersBalanceSummaryHeader({
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
asDate: Yup.date().required().label('asDate'), asDate: Yup.date().required().label('asDate'),
}); });
// Default form values. // Default form values.
const defaultValues = { const defaultValues = {
...pageFilter, ...pageFilter,
@@ -47,21 +48,19 @@ function CustomersBalanceSummaryHeader({
}, },
defaultValues, defaultValues,
); );
// handle form submit. // handle form submit.
const handleSubmit = (values, { setSubmitting }) => { const handleSubmit = (values, { setSubmitting }) => {
onSubmitFilter(values); onSubmitFilter(values);
toggleCustomerBalanceFilterDrawer(false); toggleCustomerBalanceFilterDrawer(false);
setSubmitting(false); setSubmitting(false);
}; };
// handle close drawer. // handle close drawer.
const handleDrawerClose = () => { const handleDrawerClose = () => {
toggleCustomerBalanceFilterDrawer(false); toggleCustomerBalanceFilterDrawer(false);
}; };
return ( return (
<FinancialStatementHeader <CustomerBalanceDrawerHeader
isOpen={customersBalanceDrawerFilter} isOpen={customersBalanceDrawerFilter}
drawerProps={{ onClose: handleDrawerClose }} drawerProps={{ onClose: handleDrawerClose }}
> >
@@ -89,7 +88,7 @@ function CustomersBalanceSummaryHeader({
</div> </div>
</Form> </Form>
</Formik> </Formik>
</FinancialStatementHeader> </CustomerBalanceDrawerHeader>
); );
} }
@@ -99,3 +98,9 @@ export default compose(
})), })),
withCustomersBalanceSummaryActions, withCustomersBalanceSummaryActions,
)(CustomersBalanceSummaryHeader); )(CustomersBalanceSummaryHeader);
const CustomerBalanceDrawerHeader = styled(FinancialStatementHeader)`
.bp3-drawer {
max-height: 450px;
}
`;

View File

@@ -1,45 +1,65 @@
import React from 'react'; import React from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import styled from 'styled-components';
import FinancialSheet from 'components/FinancialSheet'; import { ReportDataTable, FinancialSheet } from 'components';
import DataTable from 'components/DataTable';
import { useCustomersBalanceSummaryContext } from './CustomersBalanceSummaryProvider'; import { useCustomersBalanceSummaryContext } from './CustomersBalanceSummaryProvider';
import { useCustomersSummaryColumns } from './components'; import { useCustomersSummaryColumns } from './components';
import { TableStyle } from 'common';
import { tableRowTypesToClassnames } from 'utils';
/** /**
* customers balance summary table. * Customers balance summary table.
*/ */
export default function CustomersBalanceSummaryTable({ export default function CustomersBalanceSummaryTable({
// #ownProps // #ownProps
companyName, companyName,
}) { }) {
const { const {
isCustomersBalanceLoading,
CustomerBalanceSummary: { table }, CustomerBalanceSummary: { table },
} = useCustomersBalanceSummaryContext(); } = useCustomersBalanceSummaryContext();
// Retrieves the customers summary columns.
const columns = useCustomersSummaryColumns(); const columns = useCustomersSummaryColumns();
const rowClassNames = (row) => {
return [`row-type--${row.original.row_types}`];
};
return ( return (
<FinancialSheet <FinancialSheet
name={'customers-balance-summary'}
companyName={companyName} companyName={companyName}
sheetType={intl.get('customers_balance_summary')} sheetType={intl.get('customers_balance_summary')}
asDate={new Date()} asDate={new Date()}
loading={isCustomersBalanceLoading}
> >
<DataTable <CustomerBalanceDataTable
className="bigcapital-datatable--financial-report"
columns={columns} columns={columns}
data={table.data} data={table.data}
rowClassNames={rowClassNames} rowClassNames={tableRowTypesToClassnames}
noInitialFetch={true} noInitialFetch={true}
styleName={TableStyle.Constrant}
/> />
</FinancialSheet> </FinancialSheet>
); );
} }
const CustomerBalanceDataTable = styled(ReportDataTable)`
.table {
.tbody {
.tr:not(.no-results) {
.td {
border-bottom: 0;
padding-top: 0.4rem;
padding-bottom: 0.4rem;
}
&.row_type--TOTAL {
font-weight: 500;
.td {
border-top: 1px solid #bbb;
border-bottom: 3px double #333;
}
}
}
}
}
`;

View File

@@ -6,6 +6,8 @@ import { If } from 'components';
import FinancialLoadingBar from '../FinancialLoadingBar'; import FinancialLoadingBar from '../FinancialLoadingBar';
import { useCustomersBalanceSummaryContext } from './CustomersBalanceSummaryProvider'; import { useCustomersBalanceSummaryContext } from './CustomersBalanceSummaryProvider';
import { Align } from 'common';
/** /**
* Retrieve customers balance summary columns. * Retrieve customers balance summary columns.
*/ */
@@ -37,6 +39,7 @@ const totalColumnAccessor = () => ({
accessor: 'cells[1].value', accessor: 'cells[1].value',
className: 'total', className: 'total',
width: 140, width: 140,
align: Align.Right,
}); });
/** /**
@@ -47,6 +50,7 @@ const percentageColumnAccessor = () => ({
accessor: 'cells[2].value', accessor: 'cells[2].value',
className: 'total', className: 'total',
width: 140, width: 140,
align: Align.Right,
}); });
const dynamicColumns = (columns) => { const dynamicColumns = (columns) => {
@@ -54,7 +58,10 @@ const dynamicColumns = (columns) => {
R.compose( R.compose(
R.when(R.pathEq(['key'], 'name'), accountNameColumnAccessor), R.when(R.pathEq(['key'], 'name'), accountNameColumnAccessor),
R.when(R.pathEq(['key'], 'total'), totalColumnAccessor), R.when(R.pathEq(['key'], 'total'), totalColumnAccessor),
R.when(R.pathEq(['key'], 'percentage_of_column'), percentageColumnAccessor), R.when(
R.pathEq(['key'], 'percentage_of_column'),
percentageColumnAccessor,
),
), ),
)(columns); )(columns);
}; };

View File

@@ -0,0 +1,8 @@
import moment from 'moment';
export const getDefaultCustomersBalanceQuery = () => {
return {
asDate: moment().endOf('day').format('YYYY-MM-DD'),
filterByOption: 'with-transactions',
};
};

View File

@@ -1,16 +1,14 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import moment from 'moment'; import moment from 'moment';
import 'style/pages/FinancialStatements/ContactsTransactions.scss';
import { FinancialStatement } from 'components'; import { FinancialStatement } from 'components';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import CustomersTransactionsHeader from './CustomersTransactionsHeader'; import CustomersTransactionsHeader from './CustomersTransactionsHeader';
import CustomersTransactionsTable from './CustomersTransactionsTable';
import CustomersTransactionsActionsBar from './CustomersTransactionsActionsBar'; import CustomersTransactionsActionsBar from './CustomersTransactionsActionsBar';
import { CustomersTransactionsBody } from './CustomersTransactionsBody';
import withCustomersTransactionsActions from './withCustomersTransactionsActions'; import withCustomersTransactionsActions from './withCustomersTransactionsActions';
import withCurrentOrganization from '../../../containers/Organization/withCurrentOrganization';
import { CustomersTransactionsLoadingBar } from './components'; import { CustomersTransactionsLoadingBar } from './components';
import { CustomersTransactionsProvider } from './CustomersTransactionsProvider'; import { CustomersTransactionsProvider } from './CustomersTransactionsProvider';
@@ -20,9 +18,6 @@ import { compose } from 'utils';
* Customers transactions. * Customers transactions.
*/ */
function CustomersTransactions({ function CustomersTransactions({
// #withPreferences
organizationName,
//#withCustomersTransactionsActions //#withCustomersTransactionsActions
toggleCustomersTransactionsFilterDrawer, toggleCustomersTransactionsFilterDrawer,
}) { }) {
@@ -66,24 +61,14 @@ function CustomersTransactions({
<CustomersTransactionsLoadingBar /> <CustomersTransactionsLoadingBar />
<DashboardPageContent> <DashboardPageContent>
<FinancialStatement> <FinancialStatement>
<div className={'financial-statement--transactions'}> <CustomersTransactionsHeader
<CustomersTransactionsHeader pageFilter={filter}
pageFilter={filter} onSubmitFilter={handleFilterSubmit}
onSubmitFilter={handleFilterSubmit} />
/> <CustomersTransactionsBody />
<div class="financial-statement__body">
<CustomersTransactionsTable companyName={organizationName} />
</div>
</div>
</FinancialStatement> </FinancialStatement>
</DashboardPageContent> </DashboardPageContent>
</CustomersTransactionsProvider> </CustomersTransactionsProvider>
); );
} }
export default compose( export default compose(withCustomersTransactionsActions)(CustomersTransactions);
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
withCustomersTransactionsActions,
)(CustomersTransactions);

View File

@@ -0,0 +1,36 @@
import React from 'react';
import * as R from 'ramda';
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
import CustomersTransactionsTable from './CustomersTransactionsTable';
import { FinancialReportBody } from '../FinancialReportPage';
import { FinancialSheetSkeleton } from '../../../components/FinancialSheet';
import { useCustomersTransactionsContext } from './CustomersTransactionsProvider';
/**
* Customers transactions body.
*/
function CustomersTransactionsBodyJSX({
// #withCurrentOrganization
organizationName,
}) {
const { isCustomersTransactionsLoading } = useCustomersTransactionsContext();
return (
<FinancialReportBody>
{isCustomersTransactionsLoading ? (
<FinancialSheetSkeleton />
) : (
<CustomersTransactionsTable companyName={organizationName} />
)}
</FinancialReportBody>
);
}
export const CustomersTransactionsBody = R.compose(
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
)(CustomersTransactionsBodyJSX);

View File

@@ -5,6 +5,7 @@ import intl from 'react-intl-universal';
import moment from 'moment'; import moment from 'moment';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { Formik, Form } from 'formik'; import { Formik, Form } from 'formik';
import styled from 'styled-components';
import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader'; import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
import CustomersTransactionsHeaderGeneralPanel from './CustomersTransactionsHeaderGeneralPanel'; import CustomersTransactionsHeaderGeneralPanel from './CustomersTransactionsHeaderGeneralPanel';
@@ -67,7 +68,7 @@ function CustomersTransactionsHeader({
}; };
return ( return (
<FinancialStatementHeader <CustomerTransactionsDrawerHeader
isOpen={isFilterDrawerOpen} isOpen={isFilterDrawerOpen}
drawerProps={{ onClose: handleDrawerClose }} drawerProps={{ onClose: handleDrawerClose }}
> >
@@ -95,7 +96,7 @@ function CustomersTransactionsHeader({
</div> </div>
</Form> </Form>
</Formik> </Formik>
</FinancialStatementHeader> </CustomerTransactionsDrawerHeader>
); );
} }
@@ -105,3 +106,9 @@ export default compose(
})), })),
withCustomersTransactionsActions, withCustomersTransactionsActions,
)(CustomersTransactionsHeader); )(CustomersTransactionsHeader);
const CustomerTransactionsDrawerHeader = styled(FinancialStatementHeader)`
.bp3-drawer {
max-height: 450px;
}
`;

View File

@@ -1,12 +1,14 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import styled from 'styled-components';
import { DataTable, FinancialSheet } from 'components';
import FinancialSheet from 'components/FinancialSheet';
import DataTable from 'components/DataTable';
import { useCustomersTransactionsColumns } from './components'; import { useCustomersTransactionsColumns } from './components';
import { useCustomersTransactionsContext } from './CustomersTransactionsProvider'; import { useCustomersTransactionsContext } from './CustomersTransactionsProvider';
import { defaultExpanderReducer } from 'utils'; import { defaultExpanderReducer, tableRowTypesToClassnames } from 'utils';
import { TableStyle } from 'common';
/** /**
* Customers transactions table. * Customers transactions table.
@@ -18,7 +20,6 @@ export default function CustomersTransactionsTable({
// Customers transactions context. // Customers transactions context.
const { const {
customersTransactions: { tableRows }, customersTransactions: { tableRows },
isCustomersTransactionsLoading,
query, query,
} = useCustomersTransactionsContext(); } = useCustomersTransactionsContext();
@@ -30,30 +31,79 @@ export default function CustomersTransactionsTable({
[tableRows], [tableRows],
); );
const rowClassNames = (row) => {
return [`row-type--${row.original.row_types}`];
};
return ( return (
<FinancialSheet <FinancialSheet
name="customer-transactions"
companyName={companyName} companyName={companyName}
sheetType={intl.get('customers_transactions')} sheetType={intl.get('customers_transactions')}
loading={isCustomersTransactionsLoading}
fromDate={query.from_date} fromDate={query.from_date}
toDate={query.to_date} toDate={query.to_date}
fullWidth={true}
> >
<DataTable <CustomersTransactionsDataTable
className="bigcapital-datatable--financial-report"
columns={columns} columns={columns}
data={tableRows} data={tableRows}
rowClassNames={rowClassNames} rowClassNames={tableRowTypesToClassnames}
noInitialFetch={true} noInitialFetch={true}
expandable={true} expandable={true}
expanded={expandedRows} expanded={expandedRows}
expandToggleColumn={1} expandToggleColumn={1}
expandColumnSpace={0.8} expandColumnSpace={0.8}
styleName={TableStyle.Constrant}
/> />
</FinancialSheet> </FinancialSheet>
); );
} }
const CustomersTransactionsDataTable = styled(DataTable)`
.table {
.tbody {
.tr .td {
padding-top: 0.36rem;
padding-bottom: 0.36rem;
}
.tr:not(.no-results) .td:not(:first-of-type) {
border-left: 1px solid #ececec;
}
.tr:last-child .td {
border-bottom: 1px solid #e0e0e0;
}
.tr.row_type {
&--CUSTOMER {
.td {
&.customer_name {
font-weight: 500;
.cell-inner {
white-space: nowrap;
position: relative;
}
}
}
&:not(:first-child).is-expanded .td {
border-top: 1px solid #ddd;
}
}
&--OPENING_BALANCE,
&--CLOSING_BALANCE {
font-weight: 500;
}
&--CUSTOMER {
&.is-expanded {
.td.running_balance .cell-inner {
display: none;
}
}
&:not(:first-child).is-expanded .td {
border-top: 1px solid #ddd;
}
}
&--CUSTOMER:last-child {
.td {
border-bottom: 1px solid #ddd;
}
}
}
}
}
`;

View File

@@ -3,12 +3,13 @@ import intl from 'react-intl-universal';
import { If } from 'components'; import { If } from 'components';
import { useCustomersTransactionsContext } from './CustomersTransactionsProvider'; import { useCustomersTransactionsContext } from './CustomersTransactionsProvider';
import FinancialLoadingBar from '../FinancialLoadingBar'; import FinancialLoadingBar from '../FinancialLoadingBar';
import { getForceWidth, getColumnWidth } from 'utils'; import { getColumnWidth } from 'utils';
import { Align } from 'common';
/** /**
* Retrieve customers transactions columns. * Retrieve customers transactions columns.
*/ */
export const useCustomersTransactionsColumns = () => { export const useCustomersTransactionsColumns = () => {
const { const {
customersTransactions: { tableRows }, customersTransactions: { tableRows },
@@ -18,18 +19,8 @@ export const useCustomersTransactionsColumns = () => {
() => [ () => [
{ {
Header: intl.get('customer_name'), Header: intl.get('customer_name'),
accessor: ({ cells }) => { accessor: 'cells[0].value',
return (
<span
className={'force-width'}
style={{ minWidth: getForceWidth(cells[0].value) }}
>
{cells[0].value}
</span>
);
},
className: 'customer_name', className: 'customer_name',
// textOverview: true,
}, },
{ {
Header: intl.get('account_name'), Header: intl.get('account_name'),
@@ -59,6 +50,7 @@ export const useCustomersTransactionsColumns = () => {
minWidth: 100, minWidth: 100,
magicSpacing: 10, magicSpacing: 10,
}), }),
align: Align.Right,
}, },
{ {
Header: intl.get('debit'), Header: intl.get('debit'),
@@ -69,6 +61,7 @@ export const useCustomersTransactionsColumns = () => {
minWidth: 100, minWidth: 100,
magicSpacing: 10, magicSpacing: 10,
}), }),
align: Align.Right,
}, },
{ {
Header: intl.get('running_balance'), Header: intl.get('running_balance'),
@@ -79,6 +72,7 @@ export const useCustomersTransactionsColumns = () => {
minWidth: 120, minWidth: 120,
magicSpacing: 10, magicSpacing: 10,
}), }),
align: Align.Right,
}, },
], ],
[tableRows], [tableRows],

View File

@@ -1,18 +1,60 @@
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { DashboardInsider } from 'components'; import { DashboardInsider } from 'components';
import styled from 'styled-components';
import { CLASSES } from 'common/classes'; import { CLASSES } from 'common/classes';
import 'style/pages/FinancialStatements/FinancialReportPage.scss';
/** /**
* Financial report page. * Financial report page.
*/ */
export default function FinancialReportPage(props) { export default function FinancialReportPage(props) {
return ( return (
<DashboardInsider <FinancialReportPageRoot
{...props} {...props}
className={classNames(CLASSES.FINANCIAL_REPORT_INSIDER, props.className)} className={classNames(CLASSES.FINANCIAL_REPORT_INSIDER, props.className)}
/> />
); );
} }
export const FinancialComputeAlert = styled.div`
position: relative;
padding: 8px 20px;
border-radius: 2px;
background-color: #fdecda;
color: #342515;
font-size: 13px;
button {
font-size: 12px;
min-height: 16px;
padding: 0 4px;
&,
&:hover {
color: #824400;
text-decoration: underline;
}
}
svg {
margin-right: 6px;
position: relative;
top: -2px;
fill: #975f19;
}
`;
export const FinancialProgressbar = styled.div`
.progress-materializecss {
top: -2px;
}
`;
export const FinancialReportPageRoot = styled(DashboardInsider)``;
export const FinancialReportBody = styled.div`
padding-left: 15px;
padding-right: 15px;
display: flex;
justify-content: center;
align-items: center;
`;

View File

@@ -3,10 +3,15 @@ import classNames from 'classnames';
import { Position, Drawer } from '@blueprintjs/core'; import { Position, Drawer } from '@blueprintjs/core';
import 'style/containers/FinancialStatements/DrawerHeader.scss'; import 'style/containers/FinancialStatements/DrawerHeader.scss';
/**
* Financial statement header.
* @returns {JSX.Element}
*/
export default function FinancialStatementHeader({ export default function FinancialStatementHeader({
children, children,
isOpen, isOpen,
drawerProps, drawerProps,
className,
}) { }) {
const timeoutRef = React.useRef(); const timeoutRef = React.useRef();
const [isDrawerOpen, setIsDrawerOpen] = useState(false); const [isDrawerOpen, setIsDrawerOpen] = useState(false);
@@ -42,6 +47,7 @@ export default function FinancialStatementHeader({
{ {
'is-hidden': !isDrawerOpen, 'is-hidden': !isDrawerOpen,
}, },
className,
)} )}
> >
<Drawer <Drawer

View File

@@ -1,9 +1,7 @@
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import moment from 'moment'; import moment from 'moment';
import 'style/pages/FinancialStatements/GeneralLedger.scss'; import { FinancialStatement } from 'components';
import GeneralLedgerTable from './GeneralLedgerTable';
import GeneralLedgerHeader from './GeneralLedgerHeader'; import GeneralLedgerHeader from './GeneralLedgerHeader';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
@@ -13,12 +11,13 @@ import {
GeneralLedgerSheetAlerts, GeneralLedgerSheetAlerts,
GeneralLedgerSheetLoadingBar, GeneralLedgerSheetLoadingBar,
} from './components'; } from './components';
import { GeneralLedgerBody } from './GeneralLedgerBody';
import withGeneralLedgerActions from './withGeneralLedgerActions'; import withGeneralLedgerActions from './withGeneralLedgerActions';
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
import { transformFilterFormToQuery } from 'containers/FinancialStatements/common'; import { transformFilterFormToQuery } from 'containers/FinancialStatements/common';
import { compose } from 'utils'; import { compose } from 'utils';
import { getDefaultGeneralLedgerQuery } from './common';
/** /**
* General Ledger (GL) sheet. * General Ledger (GL) sheet.
@@ -26,15 +25,9 @@ import { compose } from 'utils';
function GeneralLedger({ function GeneralLedger({
// #withGeneralLedgerActions // #withGeneralLedgerActions
toggleGeneralLedgerFilterDrawer, toggleGeneralLedgerFilterDrawer,
// #withSettings
organizationName,
}) { }) {
const [filter, setFilter] = useState({ const [filter, setFilter] = useState({
fromDate: moment().startOf('year').format('YYYY-MM-DD'), ...getDefaultGeneralLedgerQuery(),
toDate: moment().endOf('year').format('YYYY-MM-DD'),
basis: 'accural',
filterByOption: 'with-transactions',
}); });
// Handle financial statement filter change. // Handle financial statement filter change.
@@ -63,29 +56,18 @@ function GeneralLedger({
<GeneralLedgerActionsBar /> <GeneralLedgerActionsBar />
<DashboardPageContent> <DashboardPageContent>
<div class="financial-statement financial-statement--general-ledger"> <FinancialStatement>
<GeneralLedgerHeader <GeneralLedgerHeader
pageFilter={filter} pageFilter={filter}
onSubmitFilter={handleFilterSubmit} onSubmitFilter={handleFilterSubmit}
/> />
<GeneralLedgerSheetLoadingBar /> <GeneralLedgerSheetLoadingBar />
<GeneralLedgerSheetAlerts /> <GeneralLedgerSheetAlerts />
<GeneralLedgerBody />
<div class="financial-statement__body"> </FinancialStatement>
<GeneralLedgerTable
companyName={organizationName}
generalLedgerQuery={filter}
/>
</div>
</div>
</DashboardPageContent> </DashboardPageContent>
</GeneralLedgerProvider> </GeneralLedgerProvider>
); );
} }
export default compose( export default compose(withGeneralLedgerActions)(GeneralLedger);
withGeneralLedgerActions,
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
)(GeneralLedger);

View File

@@ -0,0 +1,36 @@
import React from 'react';
import * as R from 'ramda';
import GeneralLedgerTable from './GeneralLedgerTable';
import { FinancialSheetSkeleton } from '../../../components/FinancialSheet';
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
import { FinancialReportBody } from '../FinancialReportPage';
import { useGeneralLedgerContext } from './GeneralLedgerProvider';
/**
* General ledger body JSX.
*/
function GeneralLedgerBodyJSX({
// #withCurrentOrganization
organizationName,
}) {
const { isLoading } = useGeneralLedgerContext();
return (
<FinancialReportBody>
{isLoading ? (
<FinancialSheetSkeleton />
) : (
<GeneralLedgerTable companyName={organizationName} />
)}
</FinancialReportBody>
);
}
export const GeneralLedgerBody = R.compose(
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
)(GeneralLedgerBodyJSX);

View File

@@ -3,6 +3,8 @@ import moment from 'moment';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { Formik, Form } from 'formik'; import { Formik, Form } from 'formik';
import { Tabs, Tab, Button, Intent } from '@blueprintjs/core'; import { Tabs, Tab, Button, Intent } from '@blueprintjs/core';
import styled from 'styled-components';
import { FormattedMessage as T } from 'components'; import { FormattedMessage as T } from 'components';
import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader'; import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
@@ -68,7 +70,7 @@ function GeneralLedgerHeader({
}; };
return ( return (
<FinancialStatementHeader <GeneralLedgerDrawerHeader
isOpen={isFilterDrawerOpen} isOpen={isFilterDrawerOpen}
drawerProps={{ onClose: handleDrawerClose }} drawerProps={{ onClose: handleDrawerClose }}
> >
@@ -97,7 +99,7 @@ function GeneralLedgerHeader({
</div> </div>
</Form> </Form>
</Formik> </Formik>
</FinancialStatementHeader> </GeneralLedgerDrawerHeader>
); );
} }
@@ -107,3 +109,9 @@ export default compose(
})), })),
withGeneralLedgerActions, withGeneralLedgerActions,
)(GeneralLedgerHeader); )(GeneralLedgerHeader);
const GeneralLedgerDrawerHeader = styled(FinancialStatementHeader)`
.bp3-drawer {
max-height: 520px;
}
`;

View File

@@ -1,16 +1,18 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { defaultExpanderReducer } from 'utils';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import styled from 'styled-components';
import FinancialSheet from 'components/FinancialSheet'; import { defaultExpanderReducer, tableRowTypesToClassnames } from 'utils';
import DataTable from 'components/DataTable';
import { FinancialSheet, ReportDataTable } from 'components';
import TableVirtualizedListRows from 'components/Datatable/TableVirtualizedRows'; import TableVirtualizedListRows from 'components/Datatable/TableVirtualizedRows';
import TableFastCell from 'components/Datatable/TableFastCell'; import TableFastCell from 'components/Datatable/TableFastCell';
import { useGeneralLedgerContext } from './GeneralLedgerProvider'; import { useGeneralLedgerContext } from './GeneralLedgerProvider';
import { useGeneralLedgerTableColumns } from './components'; import { useGeneralLedgerTableColumns } from './components';
import { TableStyle } from 'common';
/** /**
* General ledger table. * General ledger table.
*/ */
@@ -25,11 +27,10 @@ export default function GeneralLedgerTable({ companyName }) {
const columns = useGeneralLedgerTableColumns(); const columns = useGeneralLedgerTableColumns();
// Default expanded rows of general ledger table. // Default expanded rows of general ledger table.
const expandedRows = useMemo(() => defaultExpanderReducer(tableRows, 1), [ const expandedRows = useMemo(
tableRows, () => defaultExpanderReducer(tableRows, 1),
]); [tableRows],
);
const rowClassNames = (row) => [`row-type--${row.original.rowType}`];
return ( return (
<FinancialSheet <FinancialSheet
@@ -37,16 +38,16 @@ export default function GeneralLedgerTable({ companyName }) {
sheetType={intl.get('general_ledger_sheet')} sheetType={intl.get('general_ledger_sheet')}
fromDate={query.from_date} fromDate={query.from_date}
toDate={query.to_date} toDate={query.to_date}
name="general-ledger"
loading={isLoading} loading={isLoading}
fullWidth={true} fullWidth={true}
> >
<DataTable <GeneralLedgerDataTable
className="bigcapital-datatable--financial-report" noResults={intl.get(
noResults={intl.get('this_report_does_not_contain_any_data_between_date_period')} 'this_report_does_not_contain_any_data_between_date_period',
)}
columns={columns} columns={columns}
data={tableRows} data={tableRows}
rowClassNames={rowClassNames} rowClassNames={tableRowTypesToClassnames}
expanded={expandedRows} expanded={expandedRows}
virtualizedRows={true} virtualizedRows={true}
fixedItemSize={30} fixedItemSize={30}
@@ -59,7 +60,59 @@ export default function GeneralLedgerTable({ companyName }) {
vListrowHeight={28} vListrowHeight={28}
vListOverscanRowCount={0} vListOverscanRowCount={0}
TableCellRenderer={TableFastCell} TableCellRenderer={TableFastCell}
styleName={TableStyle.Constrant}
/> />
</FinancialSheet> </FinancialSheet>
); );
} }
const GeneralLedgerDataTable = styled(ReportDataTable)`
.tbody {
.tr .td {
padding-top: 0.2rem;
padding-bottom: 0.2rem;
}
.tr.is-expanded {
.td:not(.date) .cell-inner {
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 {
.td {
&.date {
font-weight: 500;
.cell-inner {
white-space: nowrap;
position: relative;
}
}
}
&:not(:first-child).is-expanded .td {
border-top: 1px solid #ddd;
}
}
&--OPENING_BALANCE,
&--CLOSING_BALANCE {
.amount {
font-weight: 500;
}
}
&--CLOSING_BALANCE {
.name {
font-weight: 500;
}
}
}
}
`;

View File

@@ -1,4 +1,5 @@
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import moment from 'moment';
export const filterAccountsOptions = [ export const filterAccountsOptions = [
{ {
@@ -9,6 +10,20 @@ export const filterAccountsOptions = [
{ {
key: 'with-transactions', key: 'with-transactions',
name: intl.get('accounts_with_transactions'), name: intl.get('accounts_with_transactions'),
hint: intl.get('include_accounts_once_has_transactions_on_given_date_period'), hint: intl.get(
'include_accounts_once_has_transactions_on_given_date_period',
),
}, },
]; ];
/**
* Retrieves the default general ledger query.
*/
export const getDefaultGeneralLedgerQuery = () => {
return {
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'),
basis: 'accural',
filterByOption: 'with-transactions',
};
};

View File

@@ -4,10 +4,12 @@ import { Button } from '@blueprintjs/core';
import { Icon, If } from 'components'; import { Icon, If } from 'components';
import { FormattedMessage as T } from 'components'; import { FormattedMessage as T } from 'components';
import { getForceWidth, getColumnWidth } from 'utils'; import { getColumnWidth } from 'utils';
import { useGeneralLedgerContext } from './GeneralLedgerProvider'; import { useGeneralLedgerContext } from './GeneralLedgerProvider';
import FinancialLoadingBar from '../FinancialLoadingBar'; import FinancialLoadingBar from '../FinancialLoadingBar';
import { Align } from 'common';
/** /**
* Retrieve the general ledger table columns. * Retrieve the general ledger table columns.
*/ */
@@ -21,21 +23,8 @@ export function useGeneralLedgerTableColumns() {
() => [ () => [
{ {
Header: intl.get('date'), Header: intl.get('date'),
accessor: (row) => { accessor: 'date',
if (row.rowType === 'ACCOUNT_ROW') {
return (
<span
className={'force-width'}
style={{ minWidth: getForceWidth(row.date) }}
>
{row.date}
</span>
);
}
return row.date;
},
className: 'date', className: 'date',
// textOverview: true,
width: 120, width: 120,
}, },
{ {
@@ -73,6 +62,7 @@ export function useGeneralLedgerTableColumns() {
magicSpacing: 10, magicSpacing: 10,
}), }),
textOverview: true, textOverview: true,
align: Align.Right,
}, },
{ {
Header: intl.get('debit'), Header: intl.get('debit'),
@@ -83,6 +73,7 @@ export function useGeneralLedgerTableColumns() {
magicSpacing: 10, magicSpacing: 10,
}), }),
textOverview: true, textOverview: true,
align: Align.Right,
}, },
{ {
Header: intl.get('amount'), Header: intl.get('amount'),
@@ -93,6 +84,7 @@ export function useGeneralLedgerTableColumns() {
magicSpacing: 10, magicSpacing: 10,
}), }),
textOverview: true, textOverview: true,
align: Align.Right,
}, },
{ {
Header: intl.get('running_balance'), Header: intl.get('running_balance'),
@@ -103,6 +95,7 @@ export function useGeneralLedgerTableColumns() {
magicSpacing: 10, magicSpacing: 10,
}), }),
textOverview: true, textOverview: true,
align: Align.Right,
}, },
], ],
[tableRows], [tableRows],

View File

@@ -1,13 +1,11 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import moment from 'moment'; import moment from 'moment';
import 'style/pages/FinancialStatements/InventoryItemDetails.scss';
import { FinancialStatement } from 'components'; import { FinancialStatement } from 'components';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import InventoryItemDetailsActionsBar from './InventoryItemDetailsActionsBar'; import InventoryItemDetailsActionsBar from './InventoryItemDetailsActionsBar';
import InventoryItemDetailsHeader from './InventoryItemDetailsHeader'; import InventoryItemDetailsHeader from './InventoryItemDetailsHeader';
import InventoryItemDetailsTable from './InventoryItemDetailsTable';
import withInventoryItemDetailsActions from './withInventoryItemDetailsActions'; import withInventoryItemDetailsActions from './withInventoryItemDetailsActions';
import withCurrentOrganization from '../../../containers/Organization/withCurrentOrganization'; import withCurrentOrganization from '../../../containers/Organization/withCurrentOrganization';
@@ -18,6 +16,7 @@ import {
} from './components'; } from './components';
import { compose } from 'utils'; import { compose } from 'utils';
import { InventoryItemDetailsBody } from './InventoryItemDetailsBody';
/** /**
* inventory item details. * inventory item details.
@@ -64,19 +63,11 @@ function InventoryItemDetails({
<DashboardPageContent> <DashboardPageContent>
<FinancialStatement> <FinancialStatement>
<div <InventoryItemDetailsHeader
className={ pageFilter={filter}
'financial-statement financial-statement--inventory-details' onSubmitFilter={handleFilterSubmit}
} />
> <InventoryItemDetailsBody />
<InventoryItemDetailsHeader
pageFilter={filter}
onSubmitFilter={handleFilterSubmit}
/>
</div>
<div class="financial-statement__body">
<InventoryItemDetailsTable companyName={organizationName} />
</div>
</FinancialStatement> </FinancialStatement>
</DashboardPageContent> </DashboardPageContent>
</InventoryItemDetailsProvider> </InventoryItemDetailsProvider>

View File

@@ -0,0 +1,36 @@
import React from 'react';
import * as R from 'ramda';
import { useInventoryItemDetailsContext } from './InventoryItemDetailsProvider';
import { InventoryItemDetailsTable } from './InventoryItemDetailsTable';
import { FinancialReportBody } from '../FinancialReportPage';
import { FinancialSheetSkeleton } from '../../../components/FinancialSheet';
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
/**
* Inventory item details body.
* @returns {JSX.Element}
*/
function InventoryItemDetailsBodyJSX({
// #withCurrentOrganization
organizationName,
}) {
const { isInventoryItemDetailsLoading } = useInventoryItemDetailsContext();
return (
<FinancialReportBody>
{isInventoryItemDetailsLoading ? (
<FinancialSheetSkeleton />
) : (
<InventoryItemDetailsTable companyName={organizationName} />
)}
</FinancialReportBody>
);
}
export const InventoryItemDetailsBody = R.compose(
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
)(InventoryItemDetailsBodyJSX);

View File

@@ -3,8 +3,10 @@ import * as Yup from 'yup';
import moment from 'moment'; import moment from 'moment';
import { Formik, Form } from 'formik'; import { Formik, Form } from 'formik';
import { Tabs, Tab, Button, Intent } from '@blueprintjs/core'; import { Tabs, Tab, Button, Intent } from '@blueprintjs/core';
import { FormattedMessage as T } from 'components';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import styled from 'styled-components';
import { FormattedMessage as T } from 'components';
import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader'; import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
import InventoryItemDetailsHeaderGeneralPanel from './InventoryItemDetailsHeaderGeneralPanel'; import InventoryItemDetailsHeaderGeneralPanel from './InventoryItemDetailsHeaderGeneralPanel';
@@ -35,24 +37,23 @@ function InventoryItemDetailsHeader({
}; };
// Filter form initial values. // Filter form initial values.
const initialValues = transformToForm({ const initialValues = transformToForm(
...pageFilter, {
fromDate: moment(pageFilter.fromDate).toDate(), ...pageFilter,
toDate: moment(pageFilter.toDate).toDate(), fromDate: moment(pageFilter.fromDate).toDate(),
}, defaultValues); toDate: moment(pageFilter.toDate).toDate(),
},
defaultValues,
);
// Validation schema. // Validation schema.
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
fromDate: Yup.date() fromDate: Yup.date().required().label(intl.get('fromDate')),
.required()
.label(intl.get('fromDate')),
toDate: Yup.date() toDate: Yup.date()
.min(Yup.ref('fromDate')) .min(Yup.ref('fromDate'))
.required() .required()
.label(intl.get('toDate')), .label(intl.get('toDate')),
}); });
;
// Handle form submit. // Handle form submit.
const handleSubmit = (values, { setSubmitting }) => { const handleSubmit = (values, { setSubmitting }) => {
onSubmitFilter(values); onSubmitFilter(values);
@@ -61,10 +62,12 @@ function InventoryItemDetailsHeader({
}; };
// Handle drawer close action. // Handle drawer close action.
const handleDrawerClose = () => { toggleFilterDrawer(false); }; const handleDrawerClose = () => {
toggleFilterDrawer(false);
};
return ( return (
<FinancialStatementHeader <InventoryItemDetailsDrawerHeader
isOpen={isFilterDrawerOpen} isOpen={isFilterDrawerOpen}
drawerProps={{ onClose: handleDrawerClose }} drawerProps={{ onClose: handleDrawerClose }}
> >
@@ -91,7 +94,7 @@ function InventoryItemDetailsHeader({
</div> </div>
</Form> </Form>
</Formik> </Formik>
</FinancialStatementHeader> </InventoryItemDetailsDrawerHeader>
); );
} }
@@ -101,3 +104,9 @@ export default compose(
})), })),
withInventoryItemDetailsActions, withInventoryItemDetailsActions,
)(InventoryItemDetailsHeader); )(InventoryItemDetailsHeader);
const InventoryItemDetailsDrawerHeader = styled(FinancialStatementHeader)`
.bp3-drawer {
max-height: 400px;
}
`;

View File

@@ -1,17 +1,18 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import styled from 'styled-components';
import FinancialSheet from 'components/FinancialSheet'; import { ReportDataTable, FinancialSheet } from 'components';
import { DataTable } from 'components';
import { useInventoryItemDetailsColumns } from './components'; import { useInventoryItemDetailsColumns } from './components';
import { useInventoryItemDetailsContext } from './InventoryItemDetailsProvider'; import { useInventoryItemDetailsContext } from './InventoryItemDetailsProvider';
import { defaultExpanderReducer } from 'utils'; import { defaultExpanderReducer, tableRowTypesToClassnames } from 'utils';
import { TableStyle } from 'common';
/** /**
* Inventory item detail table. * Inventory item detail table.
*/ */
export default function InventoryItemDetailsTable({ export function InventoryItemDetailsTable({
// #ownProps // #ownProps
companyName, companyName,
}) { }) {
@@ -28,30 +29,79 @@ export default function InventoryItemDetailsTable({
[tableRows], [tableRows],
); );
const rowClassNames = (row) => {
return [`row-type--${row.original.row_types}`];
};
return ( return (
<FinancialSheet <FinancialSheet
name="inventory-item-details"
companyName={companyName} companyName={companyName}
sheetType={intl.get('inventory_item_details')} sheetType={intl.get('inventory_item_details')}
loading={isInventoryItemDetailsLoading} loading={isInventoryItemDetailsLoading}
fromDate={query.from_date} fromDate={query.from_date}
toDate={query.to_date} toDate={query.to_date}
fullWidth={true}
> >
<DataTable <InventoryItemDetailsDataTable
className="bigcapital-datatable--financial-report"
columns={columns} columns={columns}
data={tableRows} data={tableRows}
rowClassNames={rowClassNames} rowClassNames={tableRowTypesToClassnames}
noInitialFetch={true} noInitialFetch={true}
expandable={true} expandable={true}
expanded={expandedRows} expanded={expandedRows}
expandToggleColumn={1} expandToggleColumn={1}
expandColumnSpace={0.8} expandColumnSpace={0.8}
styleName={TableStyle.Constrant}
/> />
</FinancialSheet> </FinancialSheet>
); );
} }
const InventoryItemDetailsDataTable = styled(ReportDataTable)`
.table {
.tbody {
.tr .td {
padding-top: 0.3rem;
padding-bottom: 0.3rem;
}
.tr:not(.no-results) .td:not(:first-of-type) {
border-left: 1px solid #ececec;
}
.tr:last-child .td {
border-bottom: 1px solid #ddd;
}
.tr.row_type {
&--ITEM {
.td {
&.transaction_type {
border-left-color: transparent;
}
&.date {
.cell-inner {
white-space: nowrap;
position: relative;
}
}
}
&:not(:first-child).is-expanded .td {
border-top: 1px solid #ddd;
}
}
&--ITEM,
&--OPENING_ENTRY,
&--CLOSING_ENTRY {
font-weight: 500;
}
&--ITEM {
&.is-expanded {
.td.value .cell-inner {
display: none;
}
}
}
}
}
}
`;

View File

@@ -1,17 +1,43 @@
import * as R from 'ramda'; import * as R from 'ramda';
import { getColumnWidth } from 'utils'; import { getColumnWidth } from 'utils';
import { CellForceWidth } from '../../../components'; import { Align } from 'common';
const itemNameOrDateColumn = R.curry((data, index, column) => ({
id: column.key,
key: column.key,
Header: column.label,
accessor: `cells[${index}].value`,
className: column.key,
width: getColumnWidth(data, `cells.${index}.key`, {
minWidth: 130,
magicSpacing: 10,
}),
disableSortBy: true,
}));
const numericColumn = R.curry((data, index, column) => ({
id: column.key,
key: column.key,
Header: column.label,
accessor: `cells[${index}].value`,
className: column.key,
width: getColumnWidth(data, `cells.${index}.key`, {
minWidth: 130,
magicSpacing: 10,
}),
disableSortBy: true,
align: Align.Right,
}));
/** /**
* columns mapper. * columns mapper.
*/ */
const columnsMapper = (data, index, column) => ({ const columnsMapper = R.curry((data, index, column) => ({
id: column.key, id: column.key,
key: column.key, key: column.key,
Header: column.label, Header: column.label,
Cell: CellForceWidth,
accessor: `cells[${index}].value`, accessor: `cells[${index}].value`,
forceWidthAccess: `cells[0].value`,
className: column.key, className: column.key,
width: getColumnWidth(data, `cells.${index}.key`, { width: getColumnWidth(data, `cells.${index}.key`, {
minWidth: 130, minWidth: 130,
@@ -19,7 +45,7 @@ const columnsMapper = (data, index, column) => ({
}), }),
disableSortBy: true, disableSortBy: true,
textOverview: true, textOverview: true,
}); }));
/** /**
* Inventory item details columns. * Inventory item details columns.
@@ -27,7 +53,17 @@ const columnsMapper = (data, index, column) => ({
export const dynamicColumns = (columns, data) => { export const dynamicColumns = (columns, data) => {
const mapper = (column, index) => { const mapper = (column, index) => {
return R.compose( return R.compose(
R.when(R.pathEq(['key']), R.curry(columnsMapper)(data, index)), R.cond([
[R.pathEq(['key'], 'date'), itemNameOrDateColumn(data, index)],
[R.pathEq(['key'], 'running_quantity'), numericColumn(data, index)],
[R.pathEq(['key'], 'profit_margin'), numericColumn(data, index)],
[R.pathEq(['key'], 'running_value'), numericColumn(data, index)],
[R.pathEq(['key'], 'quantity'), numericColumn(data, index)],
[R.pathEq(['key'], 'rate'), numericColumn(data, index)],
[R.pathEq(['key'], 'total'), numericColumn(data, index)],
[R.pathEq(['key'], 'value'), numericColumn(data, index)],
[R.T, columnsMapper(data, index)],
]),
)(column); )(column);
}; };
return columns.map(mapper); return columns.map(mapper);

View File

@@ -1,12 +1,10 @@
import React, { useEffect, useState, useCallback } from 'react'; import React, { useEffect, useState, useCallback } from 'react';
import moment from 'moment'; import moment from 'moment';
import 'style/pages/FinancialStatements/SalesAndPurchasesSheet.scss';
import { InventoryValuationProvider } from './InventoryValuationProvider'; import { InventoryValuationProvider } from './InventoryValuationProvider';
import InventoryValuationActionsBar from './InventoryValuationActionsBar'; import InventoryValuationActionsBar from './InventoryValuationActionsBar';
import InventoryValuationHeader from './InventoryValuationHeader'; import InventoryValuationHeader from './InventoryValuationHeader';
import InventoryValuationTable from './InventoryValuationTable'; import { InventoryValuationBody } from './InventoryValuationBody';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import { InventoryValuationLoadingBar } from './components'; import { InventoryValuationLoadingBar } from './components';
@@ -64,15 +62,11 @@ function InventoryValuation({
<InventoryValuationLoadingBar /> <InventoryValuationLoadingBar />
<DashboardPageContent> <DashboardPageContent>
<div class="financial-statement financial-statement--inventory-valuation"> <InventoryValuationHeader
<InventoryValuationHeader pageFilter={filter}
pageFilter={filter} onSubmitFilter={handleFilterSubmit}
onSubmitFilter={handleFilterSubmit} />
/> <InventoryValuationBody />
<div class="financial-statement__body">
<InventoryValuationTable companyName={organizationName} />
</div>
</div>
</DashboardPageContent> </DashboardPageContent>
</InventoryValuationProvider> </InventoryValuationProvider>
); );

View File

@@ -0,0 +1,36 @@
import React from 'react';
import * as R from 'ramda';
import InventoryValuationTable from './InventoryValuationTable';
import { useInventoryValuationContext } from './InventoryValuationProvider';
import { FinancialReportBody } from '../FinancialReportPage';
import { FinancialSheetSkeleton } from '../../../components/FinancialSheet';
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
/**
* Inventory valuation body.
* @returns {JSX.Element}
*/
function InventoryValuationBodyJSX({
// #withCurrentOrganization
organizationName,
}) {
const { isLoading } = useInventoryValuationContext();
return (
<FinancialReportBody>
{isLoading ? (
<FinancialSheetSkeleton />
) : (
<InventoryValuationTable companyName={organizationName} />
)}
</FinancialReportBody>
);
}
export const InventoryValuationBody = R.compose(
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
)(InventoryValuationBodyJSX);

View File

@@ -1,9 +1,11 @@
import React from 'react'; import React from 'react';
import * as Yup from 'yup'; import * as Yup from 'yup';
import moment from 'moment'; import moment from 'moment';
import { FormattedMessage as T } from 'components';
import { Formik, Form } from 'formik'; import { Formik, Form } from 'formik';
import { Tabs, Tab, Button, Intent } from '@blueprintjs/core'; import { Tabs, Tab, Button, Intent } from '@blueprintjs/core';
import styled from 'styled-components';
import { FormattedMessage as T } from 'components';
import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader'; import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
import InventoryValuationHeaderGeneralPanel from './InventoryValuationHeaderGeneralPanel'; import InventoryValuationHeaderGeneralPanel from './InventoryValuationHeaderGeneralPanel';
@@ -38,11 +40,14 @@ function InventoryValuationHeader({
itemsIds: [], itemsIds: [],
}; };
// Initial values. // Initial values.
const initialValues = transformToForm({ const initialValues = transformToForm(
...pageFilter, {
...defaultValues, ...pageFilter,
asDate: moment(pageFilter.asDate).toDate(), ...defaultValues,
}, defaultValues); asDate: moment(pageFilter.asDate).toDate(),
},
defaultValues,
);
// Handle the form of header submit. // Handle the form of header submit.
const handleSubmit = (values, { setSubmitting }) => { const handleSubmit = (values, { setSubmitting }) => {
@@ -62,7 +67,7 @@ function InventoryValuationHeader({
}; };
return ( return (
<FinancialStatementHeader <InventoryValuationDrawerHeader
isOpen={isFilterDrawerOpen} isOpen={isFilterDrawerOpen}
drawerProps={{ onClose: handleDrawerClose }} drawerProps={{ onClose: handleDrawerClose }}
> >
@@ -89,7 +94,7 @@ function InventoryValuationHeader({
</div> </div>
</Form> </Form>
</Formik> </Formik>
</FinancialStatementHeader> </InventoryValuationDrawerHeader>
); );
} }
@@ -99,3 +104,9 @@ export default compose(
})), })),
withInventoryValuationActions, withInventoryValuationActions,
)(InventoryValuationHeader); )(InventoryValuationHeader);
const InventoryValuationDrawerHeader = styled(FinancialStatementHeader)`
.bp3-drawer {
max-height: 450px;
}
`;

View File

@@ -1,12 +1,15 @@
import React from 'react'; import React from 'react';
import intl, { init } from 'react-intl-universal'; import intl from 'react-intl-universal';
import styled from 'styled-components';
import FinancialSheet from 'components/FinancialSheet'; import { ReportDataTable, FinancialSheet } from 'components';
import { DataTable } from 'components';
import { useInventoryValuationContext } from './InventoryValuationProvider'; import { useInventoryValuationContext } from './InventoryValuationProvider';
import { useInventoryValuationTableColumns } from './components'; import { useInventoryValuationTableColumns } from './components';
import { tableRowTypesToClassnames } from 'utils';
import { TableStyle } from 'common';
/** /**
* inventory valuation data table. * inventory valuation data table.
*/ */
@@ -23,41 +26,47 @@ export default function InventoryValuationTable({
// inventory valuation table columns. // inventory valuation table columns.
const columns = useInventoryValuationTableColumns(); const columns = useInventoryValuationTableColumns();
const rowClassNames = (row) => {
const { original } = row;
const rowTypes = Array.isArray(original.rowType)
? original.rowType
: [original.rowType];
return {
...rowTypes.reduce((acc, rowType) => {
acc[`row_type--${rowType}`] = rowType;
return acc;
}, {}),
};
};
return ( return (
<FinancialSheet <InventoryValuationSheet
companyName={companyName} companyName={companyName}
name="inventory-valuation"
sheetType={intl.get('inventory_valuation')} sheetType={intl.get('inventory_valuation')}
asDate={new Date()} asDate={new Date()}
loading={isLoading} loading={isLoading}
> >
<DataTable <InventoryValuationDataTable
className="bigcapital-datatable--financial-report"
columns={columns} columns={columns}
data={tableRows} data={tableRows}
expandable={true} expandable={true}
expandToggleColumn={1} expandToggleColumn={1}
expandColumnSpace={1} expandColumnSpace={1}
sticky={true} sticky={true}
rowClassNames={rowClassNames} rowClassNames={tableRowTypesToClassnames}
styleName={TableStyle.Constrant}
noResults={intl.get( noResults={intl.get(
'there_were_no_inventory_transactions_during_the_selected_date_range', 'there_were_no_inventory_transactions_during_the_selected_date_range',
)} )}
/> />
</FinancialSheet> </InventoryValuationSheet>
); );
} }
const InventoryValuationSheet = styled(FinancialSheet)`
min-width: 850px;
`;
const InventoryValuationDataTable = styled(ReportDataTable)`
.table {
.tbody {
.tr .td {
border-bottom: 0;
padding-top: 0.4rem;
padding-bottom: 0.4rem;
}
.tr.row_type--total .td {
border-top: 1px solid #bbb;
font-weight: 500;
border-bottom: 3px double #000;
}
}
}
`;

View File

@@ -1,18 +1,18 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { getColumnWidth } from 'utils';
import { If } from 'components'; import { If } from 'components';
import { CellTextSpan } from 'components/Datatable/Cells'; import { CellTextSpan } from 'components/Datatable/Cells';
import { useInventoryValuationContext } from './InventoryValuationProvider'; import { useInventoryValuationContext } from './InventoryValuationProvider';
import FinancialLoadingBar from '../FinancialLoadingBar'; import FinancialLoadingBar from '../FinancialLoadingBar';
import { getColumnWidth } from 'utils';
import { Align } from 'common';
/** /**
* Retrieve inventory valuation table columns. * Retrieve inventory valuation table columns.
*/ */
export const useInventoryValuationTableColumns = () => { export const useInventoryValuationTableColumns = () => {
// inventory valuation context // inventory valuation context
const { const {
inventoryValuation: { tableRows }, inventoryValuation: { tableRows },
@@ -36,6 +36,7 @@ export const useInventoryValuationTableColumns = () => {
minWidth: 120, minWidth: 120,
}), }),
textOverview: true, textOverview: true,
align: Align.Right,
}, },
{ {
Header: intl.get('asset_value'), Header: intl.get('asset_value'),
@@ -46,6 +47,7 @@ export const useInventoryValuationTableColumns = () => {
minWidth: 120, minWidth: 120,
}), }),
textOverview: true, textOverview: true,
align: Align.Right,
}, },
{ {
Header: intl.get('average'), Header: intl.get('average'),
@@ -56,6 +58,7 @@ export const useInventoryValuationTableColumns = () => {
minWidth: 120, minWidth: 120,
}), }),
textOverview: true, textOverview: true,
align: Align.Right,
}, },
], ],
[tableRows], [tableRows],

View File

@@ -1,38 +1,30 @@
import React, { useState, useCallback, useEffect } from 'react'; import React, { useState, useCallback, useEffect } from 'react';
import moment from 'moment'; import moment from 'moment';
import 'style/pages/FinancialStatements/Journal.scss'; import { FinancialStatement, DashboardPageContent } from 'components';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import JournalTable from './JournalTable';
import JournalHeader from './JournalHeader'; import JournalHeader from './JournalHeader';
import JournalActionsBar from './JournalActionsBar'; import JournalActionsBar from './JournalActionsBar';
import { JournalSheetProvider } from './JournalProvider'; import { JournalSheetProvider } from './JournalProvider';
import { JournalSheetLoadingBar, JournalSheetAlerts } from './components'; import { JournalSheetLoadingBar, JournalSheetAlerts } from './components';
import { JournalBody } from './JournalBody';
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withJournalActions from './withJournalActions'; import withJournalActions from './withJournalActions';
import { getDefaultJournalQuery } from './utils';
import { compose } from 'utils'; import { compose } from 'utils';
/** /**
* Journal sheet. * Journal sheet.
*/ */
function Journal({ function Journal({
// #withPreferences
organizationName,
// #withJournalActions // #withJournalActions
toggleJournalSheetFilter toggleJournalSheetFilter,
}) { }) {
const [filter, setFilter] = useState({ const [filter, setFilter] = useState({
fromDate: moment().startOf('year').format('YYYY-MM-DD'), ...getDefaultJournalQuery(),
toDate: moment().endOf('year').format('YYYY-MM-DD'),
basis: 'accural',
}); });
// Handle financial statement filter change. // Handle financial statement filter change.
const handleFilterSubmit = useCallback( const handleFilterSubmit = useCallback(
(filter) => { (filter) => {
@@ -45,41 +37,31 @@ function Journal({
}, },
[setFilter], [setFilter],
); );
// Hide the journal sheet filter drawer once the page unmount. // Hide the journal sheet filter drawer once the page unmount.
useEffect(() => () => { useEffect(
toggleJournalSheetFilter(false); () => () => {
}, [toggleJournalSheetFilter]); toggleJournalSheetFilter(false);
},
[toggleJournalSheetFilter],
);
return ( return (
<JournalSheetProvider query={filter}> <JournalSheetProvider query={filter}>
<JournalActionsBar /> <JournalActionsBar />
<DashboardPageContent> <DashboardPageContent>
<div class="financial-statement financial-statement--journal"> <FinancialStatement>
<JournalHeader <JournalHeader
onSubmitFilter={handleFilterSubmit} onSubmitFilter={handleFilterSubmit}
pageFilter={filter} pageFilter={filter}
/> />
<JournalSheetLoadingBar /> <JournalSheetLoadingBar />
<JournalSheetAlerts /> <JournalSheetAlerts />
<JournalBody />
<div class="financial-statement__body"> </FinancialStatement>
<JournalTable
companyName={organizationName}
journalQuery={filter}
/>
</div>
</div>
</DashboardPageContent> </DashboardPageContent>
</JournalSheetProvider> </JournalSheetProvider>
); );
} }
export default compose( export default compose(withDashboardActions, withJournalActions)(Journal);
withDashboardActions,
withJournalActions,
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
)(Journal);

View File

@@ -8,10 +8,11 @@ import {
PopoverInteractionKind, PopoverInteractionKind,
Position, Position,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import classNames from 'classnames';
import { FormattedMessage as T } from 'components'; import { FormattedMessage as T } from 'components';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import classNames from 'classnames';
import withJournalActions from './withJournalActions'; import withJournalActions from './withJournalActions';
import withJournal from './withJournal'; import withJournal from './withJournal';
@@ -56,7 +57,7 @@ function JournalActionsBar({
className={classNames(Classes.MINIMAL, 'button--table-views')} className={classNames(Classes.MINIMAL, 'button--table-views')}
icon={<Icon icon="cog-16" iconSize={16} />} icon={<Icon icon="cog-16" iconSize={16} />}
text={ text={
(isFilterDrawerOpen) ? ( isFilterDrawerOpen ? (
<T id={'hide_customizer'} /> <T id={'hide_customizer'} />
) : ( ) : (
<T id={'customize_report'} /> <T id={'customize_report'} />
@@ -97,7 +98,7 @@ function JournalActionsBar({
export default compose( export default compose(
withJournal(({ journalSheetDrawerFilter }) => ({ withJournal(({ journalSheetDrawerFilter }) => ({
isFilterDrawerOpen: journalSheetDrawerFilter isFilterDrawerOpen: journalSheetDrawerFilter,
})), })),
withJournalActions, withJournalActions,
)(JournalActionsBar); )(JournalActionsBar);

View File

@@ -0,0 +1,37 @@
import React from 'react';
import * as R from 'ramda';
import { FinancialReportBody } from '../FinancialReportPage';
import { FinancialSheetSkeleton } from '../../../components/FinancialSheet';
import { JournalTable } from './JournalTable';
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
import { useJournalSheetContext } from './JournalProvider';
/**
* Journal report body.
* @returns {JSX.Element}
*/
function JournalBodyJSX({
// #withCurrentOrganization
organizationName,
}) {
const { isLoading } = useJournalSheetContext();
return (
<FinancialReportBody>
{isLoading ? (
<FinancialSheetSkeleton />
) : (
<JournalTable companyName={organizationName} />
)}
</FinancialReportBody>
);
}
export const JournalBody = R.compose(
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
)(JournalBodyJSX);

View File

@@ -3,6 +3,8 @@ import moment from 'moment';
import { Formik, Form } from 'formik'; import { Formik, Form } from 'formik';
import { Tab, Tabs, Button, Intent } from '@blueprintjs/core'; import { Tab, Tabs, Button, Intent } from '@blueprintjs/core';
import * as Yup from 'yup'; import * as Yup from 'yup';
import styled from 'styled-components';
import { FormattedMessage as T } from 'components'; import { FormattedMessage as T } from 'components';
import JournalSheetHeaderGeneral from './JournalSheetHeaderGeneral'; import JournalSheetHeaderGeneral from './JournalSheetHeaderGeneral';
@@ -55,7 +57,7 @@ function JournalHeader({
}; };
return ( return (
<FinancialStatementHeader <JournalDrawerHeader
isOpen={journalSheetDrawerFilter} isOpen={journalSheetDrawerFilter}
drawerProps={{ onClose: handleDrawerClose }} drawerProps={{ onClose: handleDrawerClose }}
> >
@@ -83,7 +85,7 @@ function JournalHeader({
</div> </div>
</Form> </Form>
</Formik> </Formik>
</FinancialStatementHeader> </JournalDrawerHeader>
); );
} }
@@ -93,3 +95,9 @@ export default compose(
})), })),
withJournalActions, withJournalActions,
)(JournalHeader); )(JournalHeader);
const JournalDrawerHeader = styled(FinancialStatementHeader)`
.bp3-drawer {
max-height: 350px;
}
`;

View File

@@ -1,25 +1,26 @@
import React, { useCallback, useMemo } from 'react'; import React, { useMemo } from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import styled from 'styled-components';
import FinancialSheet from 'components/FinancialSheet'; import { ReportDataTable, FinancialSheet } from 'components';
import DataTable from 'components/DataTable';
import { useJournalSheetContext } from './JournalProvider';
import TableVirtualizedListRows from 'components/Datatable/TableVirtualizedRows'; import TableVirtualizedListRows from 'components/Datatable/TableVirtualizedRows';
import TableFastCell from 'components/Datatable/TableFastCell'; import TableFastCell from 'components/Datatable/TableFastCell';
import { defaultExpanderReducer } from 'utils';
import { useJournalTableColumns } from './components'; import { useJournalTableColumns } from './components';
import { useJournalSheetContext } from './JournalProvider';
export default function JournalSheetTable({ import { defaultExpanderReducer, tableRowTypesToClassnames } from 'utils';
// #ownProps import { TableStyle } from 'common';
onFetchData,
companyName,
}) {
/**
* Journal sheet table.
* @returns {JSX.Element}
*/
export function JournalTable({ companyName }) {
// Journal sheet context. // Journal sheet context.
const { const {
journalSheet: { tableRows, query }, journalSheet: { tableRows, query },
isLoading isLoading,
} = useJournalSheetContext(); } = useJournalSheetContext();
// Retreive the journal table columns. // Retreive the journal table columns.
@@ -28,22 +29,6 @@ export default function JournalSheetTable({
// Default expanded rows of general journal table. // Default expanded rows of general journal table.
const expandedRows = useMemo(() => defaultExpanderReducer([], 1), []); const expandedRows = useMemo(() => defaultExpanderReducer([], 1), []);
const rowClassNames = useCallback((row) => {
const { original } = row;
const rowTypes = Array.isArray(original.rowType)
? original.rowType
: [original.rowType];
return {
...rowTypes.reduce((acc, rowType) => {
acc[`row_type--${rowType}`] = rowType;
return acc;
}, {}),
};
}, []);
return ( return (
<FinancialSheet <FinancialSheet
companyName={companyName} companyName={companyName}
@@ -52,25 +37,55 @@ export default function JournalSheetTable({
toDate={query.to_date} toDate={query.to_date}
name="journal" name="journal"
loading={isLoading} loading={isLoading}
// minimal={true}
fullWidth={true} fullWidth={true}
> >
<DataTable <JournalDataTable
className="bigcapital-datatable--financial-report"
columns={columns} columns={columns}
data={tableRows} data={tableRows}
rowClassNames={rowClassNames} rowClassNames={tableRowTypesToClassnames}
noResults={intl.get('this_report_does_not_contain_any_data_between_date_period')} noResults={intl.get(
'this_report_does_not_contain_any_data_between_date_period',
)}
expanded={expandedRows} expanded={expandedRows}
sticky={true} sticky={true}
TableRowsRenderer={TableVirtualizedListRows} TableRowsRenderer={TableVirtualizedListRows}
// #TableVirtualizedListRows props. // #TableVirtualizedListRows props.
vListrowHeight={28} vListrowHeight={28}
vListOverscanRowCount={2} vListOverscanRowCount={2}
TableCellRenderer={TableFastCell} TableCellRenderer={TableFastCell}
id={'journal'} id={'journal'}
styleName={TableStyle.Constrant}
/> />
</FinancialSheet> </FinancialSheet>
); );
} }
const JournalDataTable = styled(ReportDataTable)`
.table {
.tbody {
.tr:not(.no-results) .td {
padding: 0.3rem 0.4rem;
color: #000;
border-bottom-color: transparent;
min-height: 28px;
border-left: 1px solid #ececec;
&:first-of-type {
border-left: 0;
}
}
.tr:not(.no-results):last-child {
.td {
border-bottom: 1px solid #dbdbdb;
}
}
.tr.row_type--TOTAL_ENTRIES {
font-weight: 600;
}
.tr:not(.no-results) {
height: 28px;
}
}
}
`;

View File

@@ -2,15 +2,17 @@ import React from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import moment from 'moment'; import moment from 'moment';
import { Button } from '@blueprintjs/core'; import { Button } from '@blueprintjs/core';
import { Icon, If, FormattedMessage as T } from 'components'; import { Icon, If, FormattedMessage as T } from 'components';
import { useJournalSheetContext } from './JournalProvider'; import { useJournalSheetContext } from './JournalProvider';
import FinancialLoadingBar from '../FinancialLoadingBar'; import FinancialLoadingBar from '../FinancialLoadingBar';
import { Align } from 'common';
/** /**
* Retrieve the journal table columns. * Retrieve the journal table columns.
*/ */
export const useJournalTableColumns = () => { export const useJournalTableColumns = () => {
return React.useMemo( return React.useMemo(
() => [ () => [
{ {
@@ -57,12 +59,12 @@ export const useJournalTableColumns = () => {
{ {
Header: intl.get('credit'), Header: intl.get('credit'),
accessor: 'formatted_credit', accessor: 'formatted_credit',
className: 'credit', align: Align.Right,
}, },
{ {
Header: intl.get('debit'), Header: intl.get('debit'),
accessor: 'formatted_debit', accessor: 'formatted_debit',
className: 'debit', align: Align.Right,
}, },
], ],
[], [],

View File

@@ -0,0 +1,12 @@
import moment from 'moment';
/**
* Retrieves the default journal report query.
*/
export const getDefaultJournalQuery = () => {
return {
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'),
basis: 'accural',
}
}

View File

@@ -0,0 +1,36 @@
import React from 'react';
import { FinancialSheetSkeleton } from '../../../components/FinancialSheet';
import ProfitLossSheetTable from './ProfitLossSheetTable';
import { FinancialReportBody } from '../FinancialReportPage';
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
import { useProfitLossSheetContext } from './ProfitLossProvider';
import { compose } from 'utils';
/**
* @returns {React.JSX}
*/
function ProfitLossBodyJSX({
// #withPreferences
organizationName,
}) {
const { isLoading } = useProfitLossSheetContext();
return (
<FinancialReportBody>
{isLoading ? (
<FinancialSheetSkeleton />
) : (
<ProfitLossSheetTable companyName={organizationName} />
)}
</FinancialReportBody>
);
}
export const ProfitLossBody = compose(
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
)(ProfitLossBodyJSX);

View File

@@ -5,6 +5,10 @@ import { transformFilterFormToQuery } from '../common';
const ProfitLossSheetContext = createContext(); const ProfitLossSheetContext = createContext();
/**
* Profit/loss sheet provider.
* @returns {React.JSX}
*/
function ProfitLossSheetProvider({ query, ...props }) { function ProfitLossSheetProvider({ query, ...props }) {
const { const {
data: profitLossSheet, data: profitLossSheet,

View File

@@ -1,57 +1,47 @@
import React, { useState } from 'react'; import React from 'react';
import moment from 'moment'; import moment from 'moment';
import { compose } from 'utils'; import * as R from 'ramda';
import ProfitLossSheetHeader from './ProfitLossSheetHeader'; import ProfitLossSheetHeader from './ProfitLossSheetHeader';
import ProfitLossSheetTable from './ProfitLossSheetTable';
import ProfitLossActionsBar from './ProfitLossActionsBar'; import ProfitLossActionsBar from './ProfitLossActionsBar';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withProfitLossActions from './withProfitLossActions'; import withProfitLossActions from './withProfitLossActions';
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
import 'style/pages/FinancialStatements/ProfitLossSheet.scss'; import { useProfitLossSheetQuery } from './utils';
import { ProfitLossSheetProvider } from './ProfitLossProvider'; import { ProfitLossSheetProvider } from './ProfitLossProvider';
import { ProfitLossSheetLoadingBar, ProfitLossSheetAlerts } from './components'; import { ProfitLossSheetLoadingBar } from './components';
import { ProfitLossBody } from './ProfitLossBody';
/** /**
* Profit/Loss financial statement sheet. * Profit/Loss financial statement sheet.
* @returns {React.JSX}
*/ */
function ProfitLossSheet({ function ProfitLossSheet({
// #withPreferences
organizationName,
// #withProfitLossActions // #withProfitLossActions
toggleProfitLossFilterDrawer: toggleDisplayFilterDrawer, toggleProfitLossFilterDrawer: toggleDisplayFilterDrawer,
}) { }) {
const [filter, setFilter] = useState({ // Profit/loss sheet query.
basis: 'cash', const { query, setLocationQuery } = useProfitLossSheetQuery();
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'),
displayColumnsType: 'total',
filterByOption: 'with-transactions',
});
// Handle submit filter. // Handle submit filter.
const handleSubmitFilter = (filter) => { const handleSubmitFilter = (filter) => {
const _filter = { const newFilter = {
...filter, ...filter,
fromDate: moment(filter.fromDate).format('YYYY-MM-DD'), fromDate: moment(filter.fromDate).format('YYYY-MM-DD'),
toDate: moment(filter.toDate).format('YYYY-MM-DD'), toDate: moment(filter.toDate).format('YYYY-MM-DD'),
}; };
setFilter(_filter); setLocationQuery(newFilter);
}; };
// Handle number format submit. // Handle number format submit.
const handleNumberFormatSubmit = (numberFormat) => { const handleNumberFormatSubmit = (numberFormat) => {
setFilter({ setLocationQuery({
...filter, ...query,
numberFormat, numberFormat,
}); });
}; };
// Hide the filter drawer once the page unmount. // Hide the filter drawer once the page unmount.
React.useEffect( React.useEffect(
() => () => { () => () => {
@@ -61,34 +51,26 @@ function ProfitLossSheet({
); );
return ( return (
<ProfitLossSheetProvider query={filter}> <ProfitLossSheetProvider query={query}>
<ProfitLossActionsBar <ProfitLossActionsBar
numberFormat={filter.numberFormat} numberFormat={query.numberFormat}
onNumberFormatSubmit={handleNumberFormatSubmit} onNumberFormatSubmit={handleNumberFormatSubmit}
/> />
<ProfitLossSheetLoadingBar /> <ProfitLossSheetLoadingBar />
<ProfitLossSheetAlerts /> {/* <ProfitLossSheetAlerts /> */}
<DashboardPageContent> <DashboardPageContent>
<div class="financial-statement"> <ProfitLossSheetHeader
<ProfitLossSheetHeader pageFilter={query}
pageFilter={filter} onSubmitFilter={handleSubmitFilter}
onSubmitFilter={handleSubmitFilter} />
/> <ProfitLossBody />
<div class="financial-statement__body">
<ProfitLossSheetTable companyName={organizationName} />
</div>
</div>
</DashboardPageContent> </DashboardPageContent>
</ProfitLossSheetProvider> </ProfitLossSheetProvider>
); );
} }
export default compose( export default R.compose(
withDashboardActions, withDashboardActions,
withProfitLossActions, withProfitLossActions,
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
)(ProfitLossSheet); )(ProfitLossSheet);

View File

@@ -1,19 +1,24 @@
import React, { useEffect } from 'react'; import React from 'react';
import moment from 'moment'; import moment from 'moment';
import { Formik, Form } from 'formik'; import { Formik, Form } from 'formik';
import { FormattedMessage as T } from 'components'; import * as R from 'ramda';
import intl from 'react-intl-universal';
import * as Yup from 'yup';
import { Tabs, Tab, Button, Intent } from '@blueprintjs/core'; import { Tabs, Tab, Button, Intent } from '@blueprintjs/core';
import styled from 'styled-components';
import { FormattedMessage as T } from 'components';
import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader'; import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
import ProfitLossSheetHeaderGeneralPane from './ProfitLossSheetHeaderGeneralPane'; import ProfitLossSheetHeaderGeneralPane from './ProfitLossSheetHeaderGeneralPane';
import ProfitLossSheetHeaderComparisonPanel from './ProfitLossSheetHeaderComparisonPanel';
import withProfitLoss from './withProfitLoss'; import withProfitLoss from './withProfitLoss';
import withProfitLossActions from './withProfitLossActions'; import withProfitLossActions from './withProfitLossActions';
import { compose } from 'utils'; import { useProfitLossHeaderValidationSchema } from './utils';
/**
* Profit/loss header.
* @returns {React.JSX}
*/
function ProfitLossHeader({ function ProfitLossHeader({
// #ownProps // #ownProps
pageFilter, pageFilter,
@@ -26,15 +31,7 @@ function ProfitLossHeader({
toggleProfitLossFilterDrawer: toggleFilterDrawer, toggleProfitLossFilterDrawer: toggleFilterDrawer,
}) { }) {
// Validation schema. // Validation schema.
const validationSchema = Yup.object().shape({ const validationSchema = useProfitLossHeaderValidationSchema();
fromDate: Yup.date().required().label(intl.get('from_date')),
toDate: Yup.date()
.min(Yup.ref('fromDate'))
.required()
.label(intl.get('to_date')),
filterByOption: Yup.string(),
displayColumnsType: Yup.string(),
});
// Initial values. // Initial values.
const initialValues = { const initialValues = {
@@ -42,13 +39,11 @@ function ProfitLossHeader({
fromDate: moment(pageFilter.fromDate).toDate(), fromDate: moment(pageFilter.fromDate).toDate(),
toDate: moment(pageFilter.toDate).toDate(), toDate: moment(pageFilter.toDate).toDate(),
}; };
// Handle form submit. // Handle form submit.
const handleSubmit = (values, actions) => { const handleSubmit = (values, actions) => {
onSubmitFilter(values); onSubmitFilter(values);
toggleFilterDrawer(false); toggleFilterDrawer(false);
}; };
// Handles the cancel button click. // Handles the cancel button click.
const handleCancelClick = () => { const handleCancelClick = () => {
toggleFilterDrawer(false); toggleFilterDrawer(false);
@@ -59,7 +54,7 @@ function ProfitLossHeader({
}; };
return ( return (
<FinancialStatementHeader <ProfitLossSheetHeader
isOpen={profitLossDrawerFilter} isOpen={profitLossDrawerFilter}
drawerProps={{ onClose: handleDrawerClose }} drawerProps={{ onClose: handleDrawerClose }}
> >
@@ -75,6 +70,11 @@ function ProfitLossHeader({
title={<T id={'general'} />} title={<T id={'general'} />}
panel={<ProfitLossSheetHeaderGeneralPane />} panel={<ProfitLossSheetHeaderGeneralPane />}
/> />
<Tab
id="comparison"
title={<T id={'profit_loss_sheet.comparisons'} />}
panel={<ProfitLossSheetHeaderComparisonPanel />}
/>
</Tabs> </Tabs>
<div class="financial-header-drawer__footer"> <div class="financial-header-drawer__footer">
@@ -87,13 +87,19 @@ function ProfitLossHeader({
</div> </div>
</Form> </Form>
</Formik> </Formik>
</FinancialStatementHeader> </ProfitLossSheetHeader>
); );
} }
export default compose( export default R.compose(
withProfitLoss(({ profitLossDrawerFilter }) => ({ withProfitLoss(({ profitLossDrawerFilter }) => ({
profitLossDrawerFilter, profitLossDrawerFilter,
})), })),
withProfitLossActions, withProfitLossActions,
)(ProfitLossHeader); )(ProfitLossHeader);
const ProfitLossSheetHeader = styled(FinancialStatementHeader)`
.bp3-drawer {
max-height: 520px;
}
`;

View File

@@ -0,0 +1,183 @@
import React from 'react';
import { FastField, Field } from 'formik';
import { FormGroup, Checkbox } from '@blueprintjs/core';
import styled from 'styled-components';
import { FormattedMessage as T } from 'components';
import { Row, Col, FieldHint } from '../../../components';
import {
handlePreviousYearCheckBoxChange,
handlePreviousPeriodCheckBoxChange,
handlePreviousYearChangeCheckboxChange,
handlePreviousYearPercentageCheckboxChange,
handlePreviousPeriodChangeCheckboxChange,
handlePreviousPeriodPercentageCheckboxChange,
} from './utils';
/**
* ProfitLoss sheet header -comparison panel.
*/
export default function ProfitLossSheetHeaderComparisonPanel() {
return (
<ProfitLossSheetComparisonWrap>
{/**----------- Previous Year -----------*/}
<FastField name={'previousYear'} type={'checkbox'}>
{({ form, field }) => (
<FormGroup labelInfo={<FieldHint />}>
<Checkbox
inline={true}
small={true}
label={<T id={'profit_loss_sheet.previous_year'} />}
{...field}
onChange={handlePreviousYearCheckBoxChange(form)}
/>
</FormGroup>
)}
</FastField>
<Row>
<Col xs={3}>
<FastField name={'previousYearAmountChange'} type={'checkbox'}>
{({ form, field }) => (
<FormGroup labelInfo={<FieldHint />}>
<Checkbox
inline={true}
small={true}
label={<T id={'profit_loss_sheet.total_change'} />}
{...field}
onChange={handlePreviousYearChangeCheckboxChange(form)}
/>
</FormGroup>
)}
</FastField>
</Col>
<Col xs={3}>
<FastField name={'previousYearPercentageChange'} type={'checkbox'}>
{({ form, field }) => (
<FormGroup labelInfo={<FieldHint />}>
<Checkbox
inline={true}
small={true}
label={<T id={'profit_loss_sheet.perentage_change'} />}
{...field}
onChange={handlePreviousYearPercentageCheckboxChange(form)}
/>
</FormGroup>
)}
</FastField>
</Col>
</Row>
{/**----------- Previous Period (PP) -----------*/}
<FastField name={'previousPeriod'} type={'checkbox'}>
{({ form, field }) => (
<FormGroup labelInfo={<FieldHint />}>
<Checkbox
inline={true}
small={true}
label={<T id={'profit_loss_sheet.previous_period'} />}
{...field}
onChange={handlePreviousPeriodCheckBoxChange(form)}
/>
</FormGroup>
)}
</FastField>
<Row>
<Col xs={3}>
<FastField name={'previousPeriodAmountChange'} type={'checkbox'}>
{({ form, field }) => (
<FormGroup labelInfo={<FieldHint />}>
<Checkbox
inline={true}
small={true}
label={<T id={'profit_loss_sheet.total_change'} />}
{...field}
onChange={handlePreviousPeriodChangeCheckboxChange(form)}
/>
</FormGroup>
)}
</FastField>
</Col>
<Col xs={3}>
<FastField name={'previousPeriodPercentageChange'} type={'checkbox'}>
{({ form, field }) => (
<FormGroup labelInfo={<FieldHint />}>
<Checkbox
inline={true}
small={true}
label={<T id={'profit_loss_sheet.perentage_change'} />}
{...field}
onChange={handlePreviousPeriodPercentageCheckboxChange(form)}
/>
</FormGroup>
)}
</FastField>
</Col>
</Row>
{/**----------- % of Column -----------*/}
<FastField name={'percentageColumn'} type={'checkbox'}>
{({ field }) => (
<FormGroup labelInfo={<FieldHint />}>
<Checkbox
inline={true}
small={true}
label={<T id={'profit_loss_sheet.percentage_of_column'} />}
{...field}
/>
</FormGroup>
)}
</FastField>
{/**----------- % of Row -----------*/}
<FastField name={'percentageRow'} type={'checkbox'}>
{({ field }) => (
<FormGroup labelInfo={<FieldHint />}>
<Checkbox
inline={true}
small={true}
label={<T id={'profit_loss_sheet.percentage_of_row'} />}
{...field}
/>
</FormGroup>
)}
</FastField>
{/**----------- % of Expense -----------*/}
<FastField name={'percentageExpense'} type={'checkbox'}>
{({ field }) => (
<FormGroup labelInfo={<FieldHint />}>
<Checkbox
inline={true}
small={true}
label={<T id={'profit_loss_sheet.percentage_of_expense'} />}
{...field}
/>
</FormGroup>
)}
</FastField>
{/**----------- % of Income -----------*/}
<FastField name={'percentageIncome'} type={'checkbox'}>
{({ field }) => (
<FormGroup labelInfo={<FieldHint />}>
<Checkbox
inline={true}
small={true}
label={<T id={'profit_loss_sheet.percentage_of_income'} />}
{...field}
/>
</FormGroup>
)}
</FastField>
</ProfitLossSheetComparisonWrap>
);
}
const ProfitLossSheetComparisonWrap = styled.div`
.row {
margin-left: 0.15rem;
.col {
min-width: 150px !important;
max-width: 190px !important;
}
}
.bp3-form-group {
margin-bottom: 3px;
}
`;

View File

@@ -1,109 +1,80 @@
import React, { useMemo, useCallback } from 'react'; import React from 'react';
import { FormattedMessage as T } from 'components'; import styled from 'styled-components';
import intl from 'react-intl-universal';
import FinancialSheet from 'components/FinancialSheet'; import { TableStyle } from 'common';
import DataTable from 'components/DataTable'; import {
import { CellTextSpan } from 'components/Datatable/Cells'; ReportDataTable,
FinancialSheet,
FormattedMessage as T,
} from 'components';
import { defaultExpanderReducer, getColumnWidth } from 'utils'; import { tableRowTypesToClassnames, defaultExpanderReducer } from 'utils';
import { useProfitLossSheetColumns } from './hooks';
import { useProfitLossSheetContext } from './ProfitLossProvider'; import { useProfitLossSheetContext } from './ProfitLossProvider';
export default function ProfitLossSheetTable({ export default function ProfitLossSheetTable({
// #ownProps // #ownProps
companyName, companyName,
}) { }) {
// Profit/Loss sheet context. // Profit/Loss sheet context.
const { const {
profitLossSheet: { tableRows, query, columns }, profitLossSheet: { table, query },
isLoading
} = useProfitLossSheetContext(); } = useProfitLossSheetContext();
const tableColumns = useMemo( // Retrieves the profit/loss table columns.
() => [ const tableColumns = useProfitLossSheetColumns();
{
Header: intl.get('account'),
accessor: (row) => (row.code ? `${row.name} - ${row.code}` : row.name),
className: 'name',
textOverview: true,
width: 240,
},
...(query.display_columns_type === 'total'
? [
{
Header: intl.get('total'),
Cell: CellTextSpan,
accessor: 'total.formatted_amount',
className: 'total',
width: 140,
},
]
: []),
...(query.display_columns_type === 'date_periods'
? columns.map((column, index) => ({
id: `date_period_${index}`,
Header: column,
Cell: CellTextSpan,
accessor: `total_periods[${index}].formatted_amount`,
width: getColumnWidth(
tableRows,
`total_periods.${index}.formatted_amount`,
{ minWidth: 100 },
),
className: 'total-period',
}))
: []),
],
[
query.display_columns_type,
tableRows,
columns,
],
);
// Retrieve default expanded rows of balance sheet. // Retrieve default expanded rows of balance sheet.
const expandedRows = useMemo( const expandedRows = React.useMemo(
() => defaultExpanderReducer(tableRows, 3), () => defaultExpanderReducer(table?.rows || [], 3),
[tableRows], [table],
); );
// Retrieve conditional datatable row classnames.
const rowClassNames = useCallback((row) => {
const { original } = row;
const rowTypes = Array.isArray(original.rowTypes) ? original.rowTypes : [];
return {
...rowTypes.reduce((acc, rowType) => {
acc[`row_type--${rowType}`] = rowType;
return acc;
}, {}),
};
}, []);
return ( return (
<FinancialSheet <FinancialSheet
companyName={companyName} companyName={companyName}
sheetType={<T id={'profit_loss_sheet'} />} sheetType={<T id={'profit_loss_sheet'} />}
fromDate={query.from_date} fromDate={query.from_date}
toDate={query.to_date} toDate={query.to_date}
name="profit-loss-sheet"
loading={isLoading}
basis={query.basis} basis={query.basis}
> >
<DataTable <ProfitLossDataTable
className="bigcapital-datatable--financial-report"
columns={tableColumns} columns={tableColumns}
data={tableRows} data={table.rows}
noInitialFetch={true} noInitialFetch={true}
expanded={expandedRows} expanded={expandedRows}
rowClassNames={rowClassNames} rowClassNames={tableRowTypesToClassnames}
expandable={true} expandable={true}
expandToggleColumn={1} expandToggleColumn={1}
sticky={true} sticky={true}
styleName={TableStyle.Constrant}
/> />
</FinancialSheet> </FinancialSheet>
); );
} }
const ProfitLossDataTable = styled(ReportDataTable)`
.table {
.tbody .tr {
.td {
border-bottom: 0;
padding-top: 0.32rem;
padding-bottom: 0.32rem;
}
&.is-expanded {
.td:not(.name) .cell-inner {
opacity: 0;
}
}
&.row_type--TOTAL {
.td {
font-weight: 500;
border-top: 1px solid #bbb;
}
}
&:last-of-type .td {
border-bottom: 3px double #000;
}
}
}
`;

View File

@@ -0,0 +1,383 @@
import * as R from 'ramda';
import { isEmpty } from 'lodash';
import { Align } from 'common';
import { CellTextSpan } from 'components/Datatable/Cells';
import { getColumnWidth } from 'utils';
const getTableCellValueAccessor = (index) => `cells[${index}].value`;
const getReportColWidth = (data, accessor, labelText) => {
return getColumnWidth(
data,
accessor,
{ magicSpacing: 10, minWidth: 100 },
labelText,
);
};
const isNodeHasChildren = (node) => !isEmpty(node.children);
/**
* `Percentage of income` column accessor.
*/
const percentageOfIncomeAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
Header: column.label,
key: column.key,
accessor,
width,
align: Align.Right,
disableSortBy: true,
textOverview: true,
};
});
/**
* `Percentage of expense` column accessor.
*/
const percentageOfExpenseAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
Header: column.label,
key: column.key,
accessor,
width,
align: Align.Right,
disableSortBy: true,
textOverview: true,
};
});
/**
* `Percentage of column` column accessor.
*/
const percentageOfColumnAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
Header: column.label,
key: column.key,
accessor,
width,
align: Align.Right,
disableSortBy: true,
textOverview: true,
};
});
/**
* `Percentage of row` column accessor.
*/
const percentageOfRowAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
Header: column.label,
key: column.key,
accessor,
width,
align: Align.Right,
disableSortBy: true,
textOverview: true,
};
});
/**
* Previous year column accessor.
*/
const previousYearAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
Header: column.label,
key: column.key,
accessor,
width,
align: Align.Right,
disableSortBy: true,
textOverview: true,
};
});
/**
* Pervious year change column accessor.
*/
const previousYearChangeAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
Header: column.label,
key: column.key,
accessor,
width,
align: Align.Right,
disableSortBy: true,
textOverview: true,
};
});
/**
* Previous year percentage column accessor.
*/
const previousYearPercentageAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
Header: column.label,
key: column.key,
accessor,
width,
align: Align.Right,
disableSortBy: true,
textOverview: true,
};
});
/**
* Previous period column accessor.
*/
const previousPeriodAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
Header: column.label,
key: column.key,
accessor,
width,
align: Align.Right,
disableSortBy: true,
textOverview: true,
};
});
/**
* Previous period change column accessor.
*/
const previousPeriodChangeAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
Header: column.label,
key: column.key,
accessor,
width,
align: Align.Right,
disableSortBy: true,
textOverview: true,
};
});
/**
* Previous period percentage column accessor.
*/
const previousPeriodPercentageAccessor = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
Header: column.label,
key: column.key,
accessor,
width,
align: Align.Right,
disableSortBy: true,
textOverview: true,
};
});
/**
*
* @param {*} column
* @param {*} index
* @returns
*/
const totalColumnsMapper = R.curry((data, column) => {
return R.compose(
R.when(R.pathEq(['key'], 'total'), totalColumn(data)),
// Percetage of column/row.
R.when(
R.pathEq(['key'], 'percentage_column'),
percentageOfColumnAccessor(data),
),
R.when(R.pathEq(['key'], 'percentage_row'), percentageOfRowAccessor(data)),
R.when(
R.pathEq(['key'], 'percentage_income'),
percentageOfIncomeAccessor(data),
),
R.when(
R.pathEq(['key'], 'percentage_expenses'),
percentageOfExpenseAccessor(data),
),
// Previous year.
R.when(R.pathEq(['key'], 'previous_year'), previousYearAccessor(data)),
R.when(
R.pathEq(['key'], 'previous_year_change'),
previousYearChangeAccessor(data),
),
R.when(
R.pathEq(['key'], 'previous_year_percentage'),
previousYearPercentageAccessor(data),
),
// Pervious period.
R.when(R.pathEq(['key'], 'previous_period'), previousPeriodAccessor(data)),
R.when(
R.pathEq(['key'], 'previous_period_change'),
previousPeriodChangeAccessor(data),
),
R.when(
R.pathEq(['key'], 'previous_period_percentage'),
previousPeriodPercentageAccessor(data),
),
)(column);
});
/**
* Total sub-columns composer.
*/
const totalColumnsComposer = R.curry((data, column) => {
return R.map(totalColumnsMapper(data), column.children);
});
/**
* Assoc columns to total column.
*/
const assocColumnsToTotalColumn = R.curry((data, column, columnAccessor) => {
const columns = totalColumnsComposer(data, column);
return R.assoc('columns', columns, columnAccessor);
});
/**
* Retrieves the total column.
*/
const totalColumn = R.curry((data, column) => {
const hasChildren = isNodeHasChildren(column);
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
key: column.key,
Header: column.label,
accessor,
textOverview: true,
Cell: CellTextSpan,
width,
disableSortBy: true,
align: hasChildren ? Align.Center : Align.Right,
};
});
/**
*
*/
const totalColumnCompose = R.curry((data, column) => {
const hasChildren = isNodeHasChildren(column);
return R.compose(
R.when(R.always(hasChildren), assocColumnsToTotalColumn(data, column)),
totalColumn(data),
)(column);
});
/**
* Account name column mapper.
*/
const accountNameColumn = R.curry((data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
const width = getReportColWidth(data, accessor, column.label);
return {
key: column.key,
Header: column.label,
accessor,
className: column.key,
textOverview: true,
width: Math.max(width, 300),
sticky: Align.Left,
};
});
/**
*
* @param {*} data
* @param {*} column
* @returns
*/
const dateRangeSoloColumnAttrs = (data, column) => {
const accessor = getTableCellValueAccessor(column.cell_index);
return {
accessor,
width: getReportColWidth(data, accessor),
};
};
/**
* Retrieves date range column.
*/
const dateRangeColumn = R.curry((data, column) => {
const isDateColumnHasColumns = isNodeHasChildren(column);
const columnAccessor = {
Header: column.label,
key: column.key,
disableSortBy: true,
textOverview: true,
align: isDateColumnHasColumns ? Align.Center : Align.Right,
};
return R.compose(
R.when(
R.always(isDateColumnHasColumns),
assocColumnsToTotalColumn(data, column),
),
R.when(
R.always(!isDateColumnHasColumns),
R.mergeLeft(dateRangeSoloColumnAttrs(data, column)),
),
)(columnAccessor);
});
/**
* Detarmines the given string starts with `date-range` string.
*/
const isMatchesDateRange = (r) => R.match(/^date-range/g, r).length > 0;
/**
*
* @param {} data
* @param {} column
*/
const dynamicColumnMapper = R.curry((data, column) => {
const indexTotalColumn = totalColumnCompose(data);
const indexAccountNameColumn = accountNameColumn(data);
const indexDatePeriodMapper = dateRangeColumn(data);
return R.compose(
R.when(R.pathSatisfies(isMatchesDateRange, ['key']), indexDatePeriodMapper),
R.when(R.pathEq(['key'], 'name'), indexAccountNameColumn),
R.when(R.pathEq(['key'], 'total'), indexTotalColumn),
)(column);
});
/**
*
* @param {*} columns
* @param {*} data
* @returns
*/
export const dynamicColumns = (columns, data) => {
return R.map(dynamicColumnMapper(data), columns);
};

View File

@@ -0,0 +1,19 @@
import React from 'react';
import { dynamicColumns } from './dynamicColumns';
import { useProfitLossSheetContext } from './ProfitLossProvider';
/**
* Retrieves the profit/loss table columns.
* @returns
*/
export const useProfitLossSheetColumns = () => {
const {
profitLossSheet: { table },
} = useProfitLossSheetContext();
return React.useMemo(
() => dynamicColumns(table.columns || [], table.rows || []),
[table],
);
};

Some files were not shown because too many files have changed in this diff Show More