mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-10 09:52:00 +00:00
Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98a02396a9 | ||
|
|
c7673f57cd | ||
|
|
aa39aab93a | ||
|
|
8e6b0b496f | ||
|
|
96635ffa84 | ||
|
|
e874b89d2d | ||
|
|
60d37e3424 | ||
|
|
8ae39bf04c | ||
|
|
e3f2c82a38 | ||
|
|
68c0678dc3 | ||
|
|
030be9652c | ||
|
|
554527f17d | ||
|
|
79144ad4a5 | ||
|
|
e6fcbfeea6 | ||
|
|
7eacaa0660 | ||
|
|
673808cceb | ||
|
|
f27ef2c9b0 | ||
|
|
2986b537d0 | ||
|
|
b1f07d281f | ||
|
|
1b0ffb5574 | ||
|
|
b249335a73 | ||
|
|
4cc0a8c41e | ||
|
|
2e7061260e | ||
|
|
46570c5218 | ||
|
|
72a7c4890e | ||
|
|
a9a877f4fc | ||
|
|
cc42c21f24 | ||
|
|
2f0322b4fc | ||
|
|
b9418d3eb6 | ||
|
|
526181aa68 | ||
|
|
d445fec8c0 | ||
|
|
1f81fd213d | ||
|
|
83cd7ca893 | ||
|
|
d22143c97e | ||
|
|
200a59d6da | ||
|
|
8b4d841023 | ||
|
|
c361a5852c | ||
|
|
b759d7327e | ||
|
|
93df479c05 | ||
|
|
d300231838 | ||
|
|
47f6845633 | ||
|
|
f204b81407 | ||
|
|
2c2740ea73 | ||
|
|
c72802d683 | ||
|
|
b4f6d2c7f1 | ||
|
|
4456343eb6 | ||
|
|
a3d250cdc8 | ||
|
|
ce223e7e2f | ||
|
|
8d6ed9be41 | ||
|
|
e296507a96 | ||
|
|
3d78d2d051 | ||
|
|
fa455152a3 | ||
|
|
34501a9a61 | ||
|
|
066df28257 | ||
|
|
77d826e6d4 | ||
|
|
735803f1a5 | ||
|
|
354d1e8f75 | ||
|
|
225619be60 | ||
|
|
85a78b3809 | ||
|
|
7a0d506395 |
20
CHANGELOG.md
20
CHANGELOG.md
@@ -2,6 +2,26 @@
|
||||
|
||||
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
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bigcapital-client",
|
||||
"version": "1.5.8",
|
||||
"version": "1.6.3",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "7.8.4",
|
||||
@@ -72,6 +72,7 @@
|
||||
"postcss-preset-env": "6.7.0",
|
||||
"postcss-rtl": "^1.7.3",
|
||||
"postcss-safe-parser": "4.0.1",
|
||||
"query-string": "^7.1.1",
|
||||
"ramda": "^0.27.1",
|
||||
"react": "^16.12.0",
|
||||
"react-app-polyfill": "^1.0.6",
|
||||
@@ -93,7 +94,7 @@
|
||||
"react-sortablejs": "^2.0.11",
|
||||
"react-split-pane": "^0.1.91",
|
||||
"react-table": "^7.6.3",
|
||||
"react-table-sticky": "^1.1.2",
|
||||
"react-table-sticky": "^1.1.3",
|
||||
"react-transition-group": "^4.4.1",
|
||||
"react-use": "^13.26.1",
|
||||
"react-use-context-menu": "^0.1.4",
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
|
||||
<% if (process.env.NODE_ENV === 'production') { %>
|
||||
<!-- Hotjar Tracking Code for https://app.bigcapital.ly/ -->
|
||||
<script>
|
||||
(function (h, o, t, j, a, r) {
|
||||
@@ -35,6 +37,7 @@
|
||||
a.appendChild(r);
|
||||
})(window, document, 'https://static.hotjar.com/c/hotjar-', '.js?sv=');
|
||||
</script>
|
||||
<% } %>
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './TableStyle';
|
||||
|
||||
|
||||
|
||||
export * from './TableStyle';
|
||||
export const Align = { Left: 'left', Right: 'right', Center: 'center' };
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
|
||||
|
||||
export * from './ButtonLink';
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { ButtonLink } from 'components';
|
||||
import { ButtonLink } from '../Button';
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
function CustomerDrawerLinkComponent({
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Ability } from '@casl/ability';
|
||||
import { createContextualCan } from '@casl/react';
|
||||
import { useDashboardMeta } from '../../hooks/query';
|
||||
|
||||
import { useDashboardMetaBoot } from './DashboardBoot';
|
||||
|
||||
export const AbilityContext = React.createContext();
|
||||
export const Can = createContextualCan(AbilityContext.Consumer);
|
||||
@@ -11,8 +12,8 @@ export const Can = createContextualCan(AbilityContext.Consumer);
|
||||
*/
|
||||
export function DashboardAbilityProvider({ children }) {
|
||||
const {
|
||||
data: { abilities },
|
||||
} = useDashboardMeta();
|
||||
meta: { abilities },
|
||||
} = useDashboardMetaBoot();
|
||||
|
||||
// Ability instance.
|
||||
const ability = new Ability(abilities);
|
||||
|
||||
@@ -6,18 +6,26 @@ import {
|
||||
} from '../../hooks/query';
|
||||
import { useSplashLoading } from '../../hooks/state';
|
||||
import { useWatch, useWatchImmediate, useWhen } from '../../hooks';
|
||||
import { useSubscription } from '../../hooks/state';
|
||||
import { setCookie, getCookie } from '../../utils';
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
const { isSubscriptionActive } = useSubscription();
|
||||
|
||||
const {
|
||||
data: dashboardMeta,
|
||||
isLoading: isDashboardMetaLoading,
|
||||
isSuccess: isDashboardMetaSuccess,
|
||||
} = useDashboardMeta({
|
||||
keepPreviousData: true,
|
||||
|
||||
// Avoid run the query if the organization subscription is not active.
|
||||
enabled: isSubscriptionActive,
|
||||
});
|
||||
const [startLoading, stopLoading] = useSplashLoading();
|
||||
|
||||
@@ -30,20 +38,12 @@ export function useDashboardMetaBoot() {
|
||||
}, isDashboardMetaSuccess);
|
||||
|
||||
return {
|
||||
meta: dashboardMeta,
|
||||
isLoading: isDashboardMetaLoading,
|
||||
isSuccess: isDashboardMetaSuccess
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Dashboard async booting.
|
||||
* @returns {{ isLoading: boolean }}
|
||||
*/
|
||||
export function useDashboardBoot() {
|
||||
const { isLoading } = useDashboardMetaBoot();
|
||||
|
||||
return { isLoading };
|
||||
}
|
||||
|
||||
/**
|
||||
* Application async booting.
|
||||
*/
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import { DashboardAbilityProvider } from '../../components';
|
||||
import { useDashboardBoot } from './DashboardBoot';
|
||||
import { useDashboardMetaBoot } from './DashboardBoot';
|
||||
|
||||
/**
|
||||
* Dashboard provider.
|
||||
*/
|
||||
export default function DashboardProvider({ children }) {
|
||||
const { isLoading } = useDashboardBoot();
|
||||
const { isLoading } = useDashboardMetaBoot();
|
||||
|
||||
// Avoid display any dashboard component before complete booting.
|
||||
if (isLoading) {
|
||||
|
||||
@@ -15,7 +15,7 @@ function TableHeaderCell({ column, index }) {
|
||||
<div
|
||||
{...column.getHeaderProps({
|
||||
className: classNames(column.className || '', 'th', {
|
||||
'align-right': column.align === 'right',
|
||||
[`align-${column.align}`]: column.align,
|
||||
}),
|
||||
})}
|
||||
>
|
||||
@@ -89,12 +89,14 @@ export default function TableHeader() {
|
||||
return (
|
||||
<ScrollSyncPane>
|
||||
<div className="thead">
|
||||
{headerGroups.map((headerGroup, index) => (
|
||||
<TableHeaderGroup key={index} headerGroup={headerGroup} />
|
||||
))}
|
||||
<If condition={progressBarLoading}>
|
||||
<MaterialProgressBar />
|
||||
</If>
|
||||
<div className={'thead-inner'}>
|
||||
{headerGroups.map((headerGroup, index) => (
|
||||
<TableHeaderGroup key={index} headerGroup={headerGroup} />
|
||||
))}
|
||||
<If condition={progressBarLoading}>
|
||||
<MaterialProgressBar />
|
||||
</If>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollSyncPane>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useContext } from 'react';
|
||||
import clsx from 'classnames';
|
||||
import TableContext from './TableContext';
|
||||
import { Skeleton } from 'components';
|
||||
|
||||
@@ -8,7 +9,13 @@ function TableHeaderCell({ column }) {
|
||||
return (
|
||||
<div
|
||||
{...column.getHeaderProps({
|
||||
className: 'th',
|
||||
className: clsx(
|
||||
'th',
|
||||
{
|
||||
[`align-${column.align}`]: column.align,
|
||||
},
|
||||
column.className,
|
||||
),
|
||||
})}
|
||||
>
|
||||
<Skeleton minWidth={skeletonWidthMin} maxWidth={skeletonWidthMax} />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useContext } from 'react';
|
||||
import clsx from 'classnames';
|
||||
import TableContext from './TableContext';
|
||||
import { Skeleton } from 'components';
|
||||
|
||||
@@ -11,7 +12,13 @@ function TableHeaderCell({ column }) {
|
||||
return (
|
||||
<div
|
||||
{...column.getHeaderProps({
|
||||
className: 'td',
|
||||
className: clsx(
|
||||
'td',
|
||||
{
|
||||
[`align-${column.align}`]: column.align,
|
||||
},
|
||||
column.className,
|
||||
),
|
||||
})}
|
||||
>
|
||||
<Skeleton minWidth={skeletonWidthMin} maxWidth={skeletonWidthMax} />
|
||||
|
||||
23
src/components/FinancialReport/index.js
Normal file
23
src/components/FinancialReport/index.js
Normal 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} />
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
101
src/components/FinancialSheet/FinancialSheet.js
Normal file
101
src/components/FinancialSheet/FinancialSheet.js
Normal 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>
|
||||
);
|
||||
}
|
||||
85
src/components/FinancialSheet/FinancialSheetSkeleton.js
Normal file
85
src/components/FinancialSheet/FinancialSheetSkeleton.js
Normal 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;
|
||||
}
|
||||
`;
|
||||
9
src/components/FinancialSheet/ReportDataTable.js
Normal file
9
src/components/FinancialSheet/ReportDataTable.js
Normal 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;
|
||||
}
|
||||
`;
|
||||
82
src/components/FinancialSheet/StyledFinancialSheet.js
Normal file
82
src/components/FinancialSheet/StyledFinancialSheet.js
Normal 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``;
|
||||
3
src/components/FinancialSheet/index.js
Normal file
3
src/components/FinancialSheet/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './FinancialSheet';
|
||||
export * from './FinancialSheetSkeleton';
|
||||
export * from './ReportDataTable';
|
||||
@@ -1,15 +1,24 @@
|
||||
import React from 'react';
|
||||
import className from 'classnames';
|
||||
import 'style/containers/FinancialStatements/FinancialSheet.scss';
|
||||
import styled from 'styled-components';
|
||||
|
||||
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 (
|
||||
<div
|
||||
className={className('financial-statement', {
|
||||
[`financial-statement--${name}`]: name,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<FinancialStatementBodyRoot children={children} className={className} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,14 +6,36 @@ import { randomNumber } from 'utils';
|
||||
/**
|
||||
* Skeleton component.
|
||||
*/
|
||||
export default function Skeleton({
|
||||
export function Skeleton({
|
||||
Tag = 'span',
|
||||
minWidth = 40,
|
||||
maxWidth = 100,
|
||||
children,
|
||||
}) {
|
||||
const randomWidth = useMemo(() => randomNumber(minWidth, maxWidth), [
|
||||
minWidth,
|
||||
maxWidth,
|
||||
]);
|
||||
return <Tag className={'skeleton'} style={{ width: `${randomWidth}%` }} />;
|
||||
const randomWidth = useMemo(
|
||||
() => randomNumber(minWidth, maxWidth),
|
||||
[minWidth, maxWidth],
|
||||
);
|
||||
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>;
|
||||
}
|
||||
|
||||
0
src/components/SkeletonText.js
Normal file
0
src/components/SkeletonText.js
Normal file
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { ButtonLink } from 'components';
|
||||
import { ButtonLink } from '../Button';
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
function VendorDrawerLinkComponent({
|
||||
|
||||
@@ -5,9 +5,6 @@ import Choose from './Utils/Choose';
|
||||
import For from './Utils/For';
|
||||
import { FormattedMessage, FormattedHTMLMessage } from './FormattedMessage';
|
||||
import ListSelect from './ListSelect';
|
||||
import FinancialStatement from './FinancialStatement';
|
||||
// import DynamicFilterValueField from './DynamicFilter/DynamicFilterValueField';
|
||||
// import DynamicFilterCompatatorField from './DynamicFilter/DynamicFilterCompatatorField';
|
||||
import ErrorMessage from './ErrorMessage';
|
||||
import MODIFIER from './modifiers';
|
||||
import FieldHint from './FieldHint';
|
||||
@@ -41,7 +38,6 @@ import InputPrependText from './Forms/InputPrependText';
|
||||
import PageFormBigNumber from './PageFormBigNumber';
|
||||
import AccountsMultiSelect from './AccountsMultiSelect';
|
||||
import ContactsMultiSelect from './ContactsMultiSelect';
|
||||
import Skeleton from './Skeleton';
|
||||
import ContextMenu from './ContextMenu';
|
||||
import TableFastCell from './Datatable/TableFastCell';
|
||||
import DashboardContentTable from './Dashboard/DashboardContentTable';
|
||||
@@ -95,6 +91,10 @@ export * from './Card';
|
||||
export * from './Customers'
|
||||
export * from './Vendors'
|
||||
export * from './Table';
|
||||
export * from './Skeleton';
|
||||
export * from './FinancialStatement';
|
||||
export * from './FinancialReport';
|
||||
export * from './FinancialSheet';
|
||||
|
||||
const Hint = FieldHint;
|
||||
|
||||
@@ -110,7 +110,6 @@ export {
|
||||
T,
|
||||
Money,
|
||||
ListSelect,
|
||||
FinancialStatement,
|
||||
// DynamicFilterValueField,
|
||||
// DynamicFilterCompatatorField,
|
||||
MODIFIER,
|
||||
@@ -148,7 +147,6 @@ export {
|
||||
DataTableEditable,
|
||||
ContactsMultiSelect,
|
||||
TableFastCell,
|
||||
Skeleton,
|
||||
ContextMenu,
|
||||
DashboardContentTable,
|
||||
DashboardPageContent,
|
||||
|
||||
@@ -70,6 +70,20 @@ export const financialReportMenus = [
|
||||
subject: AbilitySubject.Report,
|
||||
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,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -64,6 +64,7 @@ function BillPaymentTransactions({
|
||||
}}
|
||||
styleName={TableStyle.Constrant}
|
||||
TableLoadingRenderer={TableSkeletonRows}
|
||||
sticky={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ function EstimatePaymentTransactions({
|
||||
}}
|
||||
styleName={TableStyle.Constrant}
|
||||
TableLoadingRenderer={TableSkeletonRows}
|
||||
sticky={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ function InvoicePaymentTransactions({
|
||||
}}
|
||||
styleName={TableStyle.Constrant}
|
||||
TableLoadingRenderer={TableSkeletonRows}
|
||||
sticky={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ function ReceiptPaymentTransactions({
|
||||
}}
|
||||
styleName={TableStyle.Constrant}
|
||||
TableLoadingRenderer={TableSkeletonRows}
|
||||
sticky={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import moment from 'moment';
|
||||
|
||||
import 'style/pages/FinancialStatements/APAgingSummary.scss';
|
||||
|
||||
import { getDefaultAPAgingSummaryQuery } from './common';
|
||||
import { FinancialStatement } from 'components';
|
||||
|
||||
import APAgingSummaryHeader from './APAgingSummaryHeader';
|
||||
import APAgingSummaryActionsBar from './APAgingSummaryActionsBar';
|
||||
import APAgingSummaryTable from './APAgingSummaryTable';
|
||||
import { APAgingSummaryBody } from './APAgingSummaryBody';
|
||||
|
||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||
import { APAgingSummaryProvider } from './APAgingSummaryProvider';
|
||||
import { APAgingSummarySheetLoadingBar } from './components';
|
||||
|
||||
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
|
||||
import withAPAgingSummaryActions from './withAPAgingSummaryActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
@@ -27,11 +27,7 @@ function APAgingSummary({
|
||||
toggleAPAgingSummaryFilterDrawer: toggleDisplayFilterDrawer,
|
||||
}) {
|
||||
const [filter, setFilter] = useState({
|
||||
asDate: moment().endOf('day').format('YYYY-MM-DD'),
|
||||
agingDaysBefore: 30,
|
||||
agingPeriods: 3,
|
||||
vendorsIds: [],
|
||||
filterByOption: 'without-zero-balance',
|
||||
...getDefaultAPAgingSummaryQuery(),
|
||||
});
|
||||
|
||||
// Handle filter submit.
|
||||
@@ -50,7 +46,6 @@ function APAgingSummary({
|
||||
numberFormat,
|
||||
});
|
||||
};
|
||||
|
||||
// Hide the report filter drawer once the page unmount.
|
||||
useEffect(
|
||||
() => () => {
|
||||
@@ -73,18 +68,11 @@ function APAgingSummary({
|
||||
pageFilter={filter}
|
||||
onSubmitFilter={handleFilterSubmit}
|
||||
/>
|
||||
<div className={'financial-statement__body'}>
|
||||
<APAgingSummaryTable organizationName={organizationName} />
|
||||
</div>
|
||||
<APAgingSummaryBody organizationName={organizationName} />
|
||||
</FinancialStatement>
|
||||
</DashboardPageContent>
|
||||
</APAgingSummaryProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withCurrentOrganization(({ organization }) => ({
|
||||
organizationName: organization?.name,
|
||||
})),
|
||||
withAPAgingSummaryActions,
|
||||
)(APAgingSummary);
|
||||
export default compose(withAPAgingSummaryActions)(APAgingSummary);
|
||||
|
||||
@@ -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);
|
||||
@@ -4,6 +4,7 @@ import { Formik, Form } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import moment from 'moment';
|
||||
import { Tabs, Tab, Button, Intent } from '@blueprintjs/core';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
|
||||
import APAgingSummaryHeaderGeneral from './APAgingSummaryHeaderGeneral';
|
||||
@@ -11,8 +12,8 @@ import APAgingSummaryHeaderGeneral from './APAgingSummaryHeaderGeneral';
|
||||
import withAPAgingSummary from './withAPAgingSummary';
|
||||
import withAPAgingSummaryActions from './withAPAgingSummaryActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
import { transformToForm } from '../../../utils';
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* AP Aging Summary Report - Drawer Header.
|
||||
@@ -72,7 +73,7 @@ function APAgingSummaryHeader({
|
||||
};
|
||||
|
||||
return (
|
||||
<FinancialStatementHeader
|
||||
<APAgingDrawerHeader
|
||||
isOpen={isFilterDrawerOpen}
|
||||
drawerProps={{ onClose: handleDrawerClose }}
|
||||
>
|
||||
@@ -99,7 +100,7 @@ function APAgingSummaryHeader({
|
||||
</div>
|
||||
</Form>
|
||||
</Formik>
|
||||
</FinancialStatementHeader>
|
||||
</APAgingDrawerHeader>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -109,3 +110,9 @@ export default compose(
|
||||
isFilterDrawerOpen: APAgingSummaryFilterDrawer,
|
||||
})),
|
||||
)(APAgingSummaryHeader);
|
||||
|
||||
const APAgingDrawerHeader = styled(FinancialStatementHeader)`
|
||||
.bp3-drawer {
|
||||
max-height: 520px;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { DataTable } from 'components';
|
||||
import FinancialSheet from 'components/FinancialSheet';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { ReportDataTable, FinancialSheet } from 'components';
|
||||
import { TableStyle } from 'common';
|
||||
|
||||
import { useAPAgingSummaryContext } from './APAgingSummaryProvider';
|
||||
import { useAPAgingSummaryColumns } from './components';
|
||||
|
||||
import { tableRowTypesToClassnames } from 'utils';
|
||||
|
||||
/**
|
||||
* AP aging summary table sheet.
|
||||
*/
|
||||
@@ -14,8 +17,6 @@ export default function APAgingSummaryTable({
|
||||
//#ownProps
|
||||
organizationName,
|
||||
}) {
|
||||
|
||||
|
||||
// AP aging summary report content.
|
||||
const {
|
||||
APAgingSummary: { tableRows },
|
||||
@@ -25,24 +26,33 @@ export default function APAgingSummaryTable({
|
||||
// AP aging summary columns.
|
||||
const columns = useAPAgingSummaryColumns();
|
||||
|
||||
const rowClassNames = (row) => [`row-type--${row.original.rowType}`];
|
||||
|
||||
return (
|
||||
<FinancialSheet
|
||||
companyName={organizationName}
|
||||
name={'payable-aging-summary'}
|
||||
sheetType={intl.get('payable_aging_summary')}
|
||||
asDate={new Date()}
|
||||
loading={isAPAgingLoading}
|
||||
>
|
||||
<DataTable
|
||||
className={'bigcapital-datatable--financial-report'}
|
||||
<APAgingSummaryDataTable
|
||||
columns={columns}
|
||||
data={tableRows}
|
||||
rowClassNames={rowClassNames}
|
||||
rowClassNames={tableRowTypesToClassnames}
|
||||
noInitialFetch={true}
|
||||
sticky={true}
|
||||
styleName={TableStyle.Constrant}
|
||||
/>
|
||||
</FinancialSheet>
|
||||
);
|
||||
}
|
||||
|
||||
const APAgingSummaryDataTable = styled(ReportDataTable)`
|
||||
.table {
|
||||
.tbody .tr {
|
||||
.td {
|
||||
border-bottom: 0;
|
||||
padding-top: 0.32rem;
|
||||
padding-bottom: 0.32rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
import moment from 'moment';
|
||||
import { transformToCamelCase, flatObject } from 'utils';
|
||||
|
||||
export const transformFilterFormToQuery = (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',
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,29 @@
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import moment from 'moment';
|
||||
|
||||
import 'style/pages/FinancialStatements/ARAgingSummary.scss';
|
||||
|
||||
import { FinancialStatement } from 'components';
|
||||
|
||||
import ARAgingSummaryHeader from './ARAgingSummaryHeader';
|
||||
import ARAgingSummaryActionsBar from './ARAgingSummaryActionsBar';
|
||||
import ARAgingSummaryTable from './ARAgingSummaryTable';
|
||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||
import { ARAgingSummaryProvider } from './ARAgingSummaryProvider';
|
||||
import { ARAgingSummarySheetLoadingBar } from './components';
|
||||
import { ARAgingSummaryBody } from './ARAgingSummaryBody';
|
||||
|
||||
import withARAgingSummaryActions from './withARAgingSummaryActions'
|
||||
import withCurrentOrganization from '../../../containers/Organization/withCurrentOrganization';
|
||||
import withARAgingSummaryActions from './withARAgingSummaryActions';
|
||||
|
||||
import { getDefaultARAgingSummaryQuery } from './common';
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* A/R aging summary report.
|
||||
*/
|
||||
function ReceivableAgingSummarySheet({
|
||||
// #withSettings
|
||||
organizationName,
|
||||
|
||||
// #withARAgingSummaryActions
|
||||
toggleARAgingSummaryFilterDrawer: toggleDisplayFilterDrawer
|
||||
toggleARAgingSummaryFilterDrawer: toggleDisplayFilterDrawer,
|
||||
}) {
|
||||
const [filter, setFilter] = useState({
|
||||
asDate: moment().endOf('day').format('YYYY-MM-DD'),
|
||||
agingDaysBefore: 30,
|
||||
agingPeriods: 3,
|
||||
customersIds: [],
|
||||
filterByOption: 'without-zero-balance',
|
||||
...getDefaultARAgingSummaryQuery(),
|
||||
});
|
||||
|
||||
// Handle filter submit.
|
||||
@@ -48,11 +39,13 @@ function ReceivableAgingSummarySheet({
|
||||
const handleNumberFormatSubmit = (numberFormat) => {
|
||||
setFilter({ ...filter, numberFormat });
|
||||
};
|
||||
|
||||
// Hide the filter drawer once the page unmount.
|
||||
useEffect(() => () => {
|
||||
toggleDisplayFilterDrawer(false);
|
||||
}, [toggleDisplayFilterDrawer]);
|
||||
useEffect(
|
||||
() => () => {
|
||||
toggleDisplayFilterDrawer(false);
|
||||
},
|
||||
[toggleDisplayFilterDrawer],
|
||||
);
|
||||
|
||||
return (
|
||||
<ARAgingSummaryProvider filter={filter}>
|
||||
@@ -63,23 +56,16 @@ function ReceivableAgingSummarySheet({
|
||||
<ARAgingSummarySheetLoadingBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<FinancialStatement name={'AR-aging-summary'}>
|
||||
<FinancialStatement>
|
||||
<ARAgingSummaryHeader
|
||||
pageFilter={filter}
|
||||
onSubmitFilter={handleFilterSubmit}
|
||||
/>
|
||||
<div class="financial-statement__body">
|
||||
<ARAgingSummaryTable organizationName={organizationName} />
|
||||
</div>
|
||||
<ARAgingSummaryBody />
|
||||
</FinancialStatement>
|
||||
</DashboardPageContent>
|
||||
</ARAgingSummaryProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withCurrentOrganization(({ organization }) => ({
|
||||
organizationName: organization.name,
|
||||
})),
|
||||
withARAgingSummaryActions
|
||||
)(ReceivableAgingSummarySheet);
|
||||
export default compose(withARAgingSummaryActions)(ReceivableAgingSummarySheet);
|
||||
|
||||
@@ -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);
|
||||
@@ -4,6 +4,7 @@ import { Formik, Form } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import moment from 'moment';
|
||||
import { Tabs, Tab, Button, Intent } from '@blueprintjs/core';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
|
||||
import ARAgingSummaryHeaderGeneral from './ARAgingSummaryHeaderGeneral';
|
||||
@@ -57,14 +58,12 @@ function ARAgingSummaryHeader({
|
||||
},
|
||||
defaultValues,
|
||||
);
|
||||
|
||||
// Handle form submit.
|
||||
const handleSubmit = (values, { setSubmitting }) => {
|
||||
onSubmitFilter(values);
|
||||
toggleFilterDrawerDisplay(false);
|
||||
setSubmitting(false);
|
||||
};
|
||||
|
||||
// Handle cancel button click.
|
||||
const handleCancelClick = () => {
|
||||
toggleFilterDrawerDisplay(false);
|
||||
@@ -75,7 +74,7 @@ function ARAgingSummaryHeader({
|
||||
};
|
||||
|
||||
return (
|
||||
<FinancialStatementHeader
|
||||
<ARAgingDrawerHeader
|
||||
isOpen={isFilterDrawerOpen}
|
||||
drawerProps={{ onClose: handleDrawerClose }}
|
||||
>
|
||||
@@ -103,7 +102,7 @@ function ARAgingSummaryHeader({
|
||||
</div>
|
||||
</Form>
|
||||
</Formik>
|
||||
</FinancialStatementHeader>
|
||||
</ARAgingDrawerHeader>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -113,3 +112,9 @@ export default compose(
|
||||
isFilterDrawerOpen: ARAgingSummaryFilterDrawer,
|
||||
})),
|
||||
)(ARAgingSummaryHeader);
|
||||
|
||||
const ARAgingDrawerHeader = styled(FinancialStatementHeader)`
|
||||
.bp3-drawer {
|
||||
max-height: 520px;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import DataTable from 'components/DataTable';
|
||||
import FinancialSheet from 'components/FinancialSheet';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { ReportDataTable, FinancialSheet } from 'components';
|
||||
import { TableStyle } from 'common';
|
||||
|
||||
import { useARAgingSummaryContext } from './ARAgingSummaryProvider';
|
||||
import { useARAgingSummaryColumns } from './components';
|
||||
|
||||
import { tableRowTypesToClassnames } from 'utils';
|
||||
|
||||
/**
|
||||
* AR aging summary table sheet.
|
||||
*/
|
||||
@@ -19,24 +23,52 @@ export default function ReceivableAgingSummaryTable({
|
||||
// AR aging summary columns.
|
||||
const columns = useARAgingSummaryColumns();
|
||||
|
||||
const rowClassNames = (row) => [`row-type--${row.original.rowType}`];
|
||||
|
||||
return (
|
||||
<FinancialSheet
|
||||
companyName={organizationName}
|
||||
name={'receivable-aging-summary'}
|
||||
sheetType={intl.get('receivable_aging_summary')}
|
||||
asDate={new Date()}
|
||||
loading={isARAgingLoading}
|
||||
>
|
||||
<DataTable
|
||||
className="bigcapital-datatable--financial-report"
|
||||
<ARAgingSummaryDataTable
|
||||
columns={columns}
|
||||
data={ARAgingSummary.tableRows}
|
||||
rowClassNames={rowClassNames}
|
||||
rowClassNames={tableRowTypesToClassnames}
|
||||
noInitialFetch={true}
|
||||
sticky={true}
|
||||
styleName={TableStyle.Constrant}
|
||||
/>
|
||||
</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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
import moment from 'moment';
|
||||
import { transformToCamelCase, flatObject } from 'utils';
|
||||
|
||||
export const transfromFilterFormToQuery = (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',
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import { useARAgingSummaryContext } from './ARAgingSummaryProvider';
|
||||
import { If, FormattedMessage as T } from 'components';
|
||||
|
||||
import { getColumnWidth } from 'utils';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import { If } from 'components';
|
||||
import { Align } from 'common';
|
||||
import FinancialLoadingBar from '../FinancialLoadingBar';
|
||||
|
||||
/**
|
||||
@@ -40,6 +42,7 @@ export const useARAgingSummaryColumns = () => {
|
||||
width: getColumnWidth(tableRows, `current`, {
|
||||
minWidth: 120,
|
||||
}),
|
||||
align: Align.Right
|
||||
},
|
||||
...agingColumns.map((agingColumn, index) => ({
|
||||
Header: agingColumn,
|
||||
@@ -47,6 +50,7 @@ export const useARAgingSummaryColumns = () => {
|
||||
width: getColumnWidth(tableRows, `aging-${index}`, {
|
||||
minWidth: 120,
|
||||
}),
|
||||
align: Align.Right
|
||||
})),
|
||||
{
|
||||
Header: <T id={'total'} />,
|
||||
@@ -56,6 +60,7 @@ export const useARAgingSummaryColumns = () => {
|
||||
width: getColumnWidth(tableRows, 'total', {
|
||||
minWidth: 120,
|
||||
}),
|
||||
align: Align.Right
|
||||
},
|
||||
],
|
||||
[tableRows, agingColumns],
|
||||
|
||||
@@ -1,57 +1,47 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import moment from 'moment';
|
||||
import 'style/pages/FinancialStatements/BalanceSheet.scss';
|
||||
|
||||
import { BalanceSheetAlerts, BalanceSheetLoadingBar } from './components';
|
||||
import { FinancialStatement } from 'components';
|
||||
|
||||
import BalanceSheetHeader from './BalanceSheetHeader';
|
||||
import BalanceSheetTable from './BalanceSheetTable';
|
||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||
import BalanceSheetActionsBar from './BalanceSheetActionsBar';
|
||||
import { BalanceSheetProvider } from './BalanceSheetProvider';
|
||||
import { BalanceSheetBody } from './BalanceSheetBody';
|
||||
|
||||
import withBalanceSheetActions from './withBalanceSheetActions';
|
||||
import withCurrentOrganization from '../../../containers/Organization/withCurrentOrganization';
|
||||
|
||||
import { useBalanceSheetQuery } from './utils';
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* Balance sheet.
|
||||
* @returns {React.JSX}
|
||||
*/
|
||||
function BalanceSheet({
|
||||
// #withCurrentOrganization
|
||||
organizationName,
|
||||
|
||||
// #withBalanceSheetActions
|
||||
toggleBalanceSheetFilterDrawer,
|
||||
}) {
|
||||
const [filter, setFilter] = useState({
|
||||
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
||||
toDate: moment().endOf('year').format('YYYY-MM-DD'),
|
||||
basis: 'cash',
|
||||
displayColumnsType: 'total',
|
||||
filterByOption: 'without-zero-balance',
|
||||
});
|
||||
// Balance sheet query.
|
||||
const { query, setLocationQuery } = useBalanceSheetQuery();
|
||||
|
||||
// Handle re-fetch balance sheet after filter change.
|
||||
const handleFilterSubmit = (filter) => {
|
||||
const _filter = {
|
||||
const newFilter = {
|
||||
...filter,
|
||||
fromDate: moment(filter.fromDate).format('YYYY-MM-DD'),
|
||||
toDate: moment(filter.toDate).format('YYYY-MM-DD'),
|
||||
};
|
||||
setFilter({ ..._filter });
|
||||
setLocationQuery({ ...newFilter });
|
||||
};
|
||||
|
||||
// Handle number format submit.
|
||||
const handleNumberFormatSubmit = (values) => {
|
||||
setFilter({
|
||||
...filter,
|
||||
setLocationQuery({
|
||||
...query,
|
||||
numberFormat: values,
|
||||
});
|
||||
};
|
||||
|
||||
// Hides the balance sheet filter drawer once the page unmount.
|
||||
useEffect(
|
||||
() => () => {
|
||||
@@ -61,9 +51,9 @@ function BalanceSheet({
|
||||
);
|
||||
|
||||
return (
|
||||
<BalanceSheetProvider filter={filter}>
|
||||
<BalanceSheetProvider filter={query}>
|
||||
<BalanceSheetActionsBar
|
||||
numberFormat={filter.numberFormat}
|
||||
numberFormat={query.numberFormat}
|
||||
onNumberFormatSubmit={handleNumberFormatSubmit}
|
||||
/>
|
||||
<BalanceSheetLoadingBar />
|
||||
@@ -72,22 +62,14 @@ function BalanceSheet({
|
||||
<DashboardPageContent>
|
||||
<FinancialStatement>
|
||||
<BalanceSheetHeader
|
||||
pageFilter={filter}
|
||||
pageFilter={query}
|
||||
onSubmitFilter={handleFilterSubmit}
|
||||
/>
|
||||
<div class="financial-statement__body">
|
||||
<BalanceSheetTable companyName={organizationName} />
|
||||
</div>
|
||||
<BalanceSheetBody />
|
||||
</FinancialStatement>
|
||||
</DashboardPageContent>
|
||||
|
||||
</BalanceSheetProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withCurrentOrganization(({ organization }) => ({
|
||||
organizationName: organization.name,
|
||||
})),
|
||||
withBalanceSheetActions,
|
||||
)(BalanceSheet);
|
||||
export default compose(withBalanceSheetActions)(BalanceSheet);
|
||||
|
||||
@@ -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);
|
||||
@@ -1,19 +1,22 @@
|
||||
import React from 'react';
|
||||
import { Tabs, Tab, Button, Intent } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import moment from 'moment';
|
||||
import { Formik, Form } from 'formik';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { FormattedMessage as T } from 'components';
|
||||
|
||||
import withBalanceSheet from './withBalanceSheet';
|
||||
import withBalanceSheetActions from './withBalanceSheetActions';
|
||||
|
||||
import BalanceSheetHeaderGeneralPanal from './BalanceSheetHeaderGeneralPanal';
|
||||
import BalanceSheetHeaderComparisonPanal from './BalanceSheetHeaderComparisonPanal';
|
||||
import FinancialStatementHeader from '../../FinancialStatements/FinancialStatementHeader';
|
||||
|
||||
import { compose, transformToForm } from 'utils';
|
||||
import {
|
||||
getBalanceSheetHeaderDefaultValues,
|
||||
getBalanceSheetHeaderValidationSchema,
|
||||
getDefaultBalanceSheetQuery,
|
||||
} from './utils';
|
||||
|
||||
/**
|
||||
@@ -30,7 +33,7 @@ function BalanceSheetHeader({
|
||||
// #withBalanceSheetActions
|
||||
toggleBalanceSheetFilterDrawer: toggleFilterDrawer,
|
||||
}) {
|
||||
const defaultValues = getBalanceSheetHeaderDefaultValues();
|
||||
const defaultValues = getDefaultBalanceSheetQuery();
|
||||
|
||||
// Filter form initial values.
|
||||
const initialValues = transformToForm(
|
||||
@@ -42,7 +45,6 @@ function BalanceSheetHeader({
|
||||
},
|
||||
defaultValues,
|
||||
);
|
||||
|
||||
// Validation schema.
|
||||
const validationSchema = getBalanceSheetHeaderValidationSchema();
|
||||
|
||||
@@ -64,9 +66,11 @@ function BalanceSheetHeader({
|
||||
};
|
||||
|
||||
return (
|
||||
<FinancialStatementHeader
|
||||
<BalanceSheetFinancialHeader
|
||||
isOpen={balanceSheetDrawerFilter}
|
||||
drawerProps={{ onClose: handleDrawerClose }}
|
||||
drawerProps={{
|
||||
onClose: handleDrawerClose,
|
||||
}}
|
||||
>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
@@ -80,6 +84,11 @@ function BalanceSheetHeader({
|
||||
title={<T id={'general'} />}
|
||||
panel={<BalanceSheetHeaderGeneralPanal />}
|
||||
/>
|
||||
<Tab
|
||||
id="comparison"
|
||||
title={<T id={'balance_sheet.comparisons'} />}
|
||||
panel={<BalanceSheetHeaderComparisonPanal />}
|
||||
/>
|
||||
</Tabs>
|
||||
|
||||
<div class="financial-header-drawer__footer">
|
||||
@@ -92,7 +101,7 @@ function BalanceSheetHeader({
|
||||
</div>
|
||||
</Form>
|
||||
</Formik>
|
||||
</FinancialStatementHeader>
|
||||
</BalanceSheetFinancialHeader>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -102,3 +111,9 @@ export default compose(
|
||||
})),
|
||||
withBalanceSheetActions,
|
||||
)(BalanceSheetHeader);
|
||||
|
||||
const BalanceSheetFinancialHeader = styled(FinancialStatementHeader)`
|
||||
.bp3-drawer {
|
||||
max-height: 520px;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
`;
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { createContext, useContext } from 'react';
|
||||
|
||||
import FinancialReportPage from '../FinancialReportPage';
|
||||
import { useBalanceSheet } from 'hooks/query';
|
||||
import { transformFilterFormToQuery } from '../common';
|
||||
@@ -10,7 +11,6 @@ function BalanceSheetProvider({ filter, ...props }) {
|
||||
const query = React.useMemo(() => transformFilterFormToQuery(filter), [
|
||||
filter,
|
||||
]);
|
||||
|
||||
// Fetches the balance sheet report.
|
||||
const {
|
||||
data: balanceSheet,
|
||||
|
||||
@@ -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 classNames from 'classnames';
|
||||
|
||||
import FinancialSheet from 'components/FinancialSheet';
|
||||
import DataTable from 'components/DataTable';
|
||||
import { CellTextSpan } from 'components/Datatable/Cells';
|
||||
import { ReportDataTable, FinancialSheet } from 'components';
|
||||
|
||||
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.
|
||||
@@ -16,91 +18,73 @@ export default function BalanceSheetTable({
|
||||
// #ownProps
|
||||
companyName,
|
||||
}) {
|
||||
|
||||
|
||||
// Balance sheet context.
|
||||
const {
|
||||
balanceSheet: { tableRows, columns, query },
|
||||
isLoading,
|
||||
balanceSheet: { table, query },
|
||||
} = useBalanceSheetContext();
|
||||
|
||||
const tableColumns = useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: intl.get('account_name'),
|
||||
accessor: (row) => (row.code ? `${row.name} - ${row.code}` : row.name),
|
||||
className: 'account_name',
|
||||
textOverview: true,
|
||||
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],
|
||||
// Retrieve the database columns.
|
||||
const tableColumns = useBalanceSheetColumns();
|
||||
|
||||
// Retrieve default expanded rows of balance sheet.
|
||||
const expandedRows = React.useMemo(
|
||||
() => defaultExpanderReducer(table.rows, 3),
|
||||
[table],
|
||||
);
|
||||
|
||||
// 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 (
|
||||
<FinancialSheet
|
||||
name="balance-sheet"
|
||||
companyName={companyName}
|
||||
sheetType={intl.get('balance_sheet')}
|
||||
fromDate={query.from_date}
|
||||
toDate={query.to_date}
|
||||
asDate={query.to_date}
|
||||
basis={query.basis}
|
||||
loading={isLoading}
|
||||
>
|
||||
<DataTable
|
||||
className="bigcapital-datatable--financial-report"
|
||||
<BalanceSheetDataTable
|
||||
columns={tableColumns}
|
||||
data={tableRows}
|
||||
rowClassNames={rowClassNames}
|
||||
data={table.rows}
|
||||
rowClassNames={tableRowTypesToClassnames}
|
||||
noInitialFetch={true}
|
||||
expandable={true}
|
||||
expanded={expandedRows}
|
||||
expandToggleColumn={1}
|
||||
expandColumnSpace={0.8}
|
||||
// sticky={true}
|
||||
headerLoading={true}
|
||||
sticky={true}
|
||||
styleName={TableStyle.Constrant}
|
||||
/>
|
||||
</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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import React from 'react';
|
||||
import { Button } from '@blueprintjs/core';
|
||||
import { Icon, If } from 'components';
|
||||
|
||||
import { FormattedMessage as T, Icon, If } from 'components';
|
||||
|
||||
import { useBalanceSheetContext } from './BalanceSheetProvider';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import FinancialLoadingBar from '../FinancialLoadingBar';
|
||||
import { FinancialComputeAlert } from '../FinancialReportPage';
|
||||
import { dynamicColumns } from './dynamicColumns';
|
||||
|
||||
/**
|
||||
* Balance sheet alerts.
|
||||
@@ -17,19 +20,18 @@ export function BalanceSheetAlerts() {
|
||||
refetchBalanceSheet();
|
||||
};
|
||||
// Can't display any error if the report is loading.
|
||||
if (isLoading) {
|
||||
return null;
|
||||
}
|
||||
if (isLoading) return null;
|
||||
|
||||
return (
|
||||
<If condition={balanceSheet.meta.is_cost_compute_running}>
|
||||
<div class="alert-compute-running">
|
||||
<FinancialComputeAlert>
|
||||
<Icon icon="info-block" iconSize={12} />{' '}
|
||||
<T id={'just_a_moment_we_re_calculating_your_cost_transactions'} />
|
||||
|
||||
<Button onClick={handleRecalcReport} minimal={true} small={true}>
|
||||
<T id={'report.compute_running.refresh'} />
|
||||
</Button>
|
||||
</div>
|
||||
</FinancialComputeAlert>
|
||||
</If>
|
||||
);
|
||||
}
|
||||
@@ -46,3 +48,18 @@ export function BalanceSheetLoadingBar() {
|
||||
</If>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve balance sheet columns.
|
||||
*/
|
||||
export const useBalanceSheetColumns = () => {
|
||||
// Balance sheet context.
|
||||
const {
|
||||
balanceSheet: { table },
|
||||
} = useBalanceSheetContext();
|
||||
|
||||
return React.useMemo(
|
||||
() => dynamicColumns(table.columns, table.rows),
|
||||
[table],
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
@@ -1,7 +1,63 @@
|
||||
import React from 'react';
|
||||
import * as R from 'ramda';
|
||||
import moment from 'moment';
|
||||
import * as Yup from 'yup';
|
||||
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 = () => {
|
||||
return {
|
||||
basic: 'cash',
|
||||
@@ -12,6 +68,9 @@ export const getBalanceSheetHeaderDefaultValues = () => {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the balance sheet header validation schema.
|
||||
*/
|
||||
export const getBalanceSheetHeaderValidationSchema = () =>
|
||||
Yup.object().shape({
|
||||
dateRange: Yup.string().optional(),
|
||||
@@ -23,3 +82,83 @@ export const getBalanceSheetHeaderValidationSchema = () =>
|
||||
filterByOption: 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);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import moment from 'moment';
|
||||
import 'style/pages/FinancialStatements/CashFlowStatement.scss';
|
||||
|
||||
import { FinancialStatement } from 'components';
|
||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||
|
||||
import CashFlowStatementHeader from './CashFlowStatementHeader';
|
||||
import CashFlowStatementTable from './CashFlowStatementTable';
|
||||
import CashFlowStatementActionsBar from './CashFlowStatementActionsBar';
|
||||
import { CashFlowStatementBody } from './CashFlowStatementBody';
|
||||
|
||||
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
|
||||
import withCashFlowStatementActions from './withCashFlowStatementActions';
|
||||
import { CashFlowStatementProvider } from './CashFlowStatementProvider';
|
||||
import {
|
||||
@@ -17,26 +15,20 @@ import {
|
||||
CashFlowStatementAlerts,
|
||||
} from './components';
|
||||
|
||||
import { getDefaultCashFlowSheetQuery } from './utils';
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* Cash flow statement.
|
||||
*/
|
||||
function CashFlowStatement({
|
||||
// #withPreferences
|
||||
organizationName,
|
||||
//#withCashStatementActions
|
||||
toggleCashFlowStatementFilterDrawer,
|
||||
}) {
|
||||
// filter
|
||||
const [filter, setFilter] = useState({
|
||||
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
||||
toDate: moment().endOf('year').format('YYYY-MM-DD'),
|
||||
basis: 'cash',
|
||||
displayColumnsType: 'total',
|
||||
filterByOption: 'with-transactions',
|
||||
...getDefaultCashFlowSheetQuery(),
|
||||
});
|
||||
|
||||
// Handle refetch cash flow after filter change.
|
||||
const handleFilterSubmit = (filter) => {
|
||||
const _filter = {
|
||||
@@ -46,7 +38,6 @@ function CashFlowStatement({
|
||||
};
|
||||
setFilter({ ..._filter });
|
||||
};
|
||||
|
||||
// Handle format number submit.
|
||||
const handleNumberFormatSubmit = (values) => {
|
||||
setFilter({
|
||||
@@ -77,18 +68,11 @@ function CashFlowStatement({
|
||||
pageFilter={filter}
|
||||
onSubmitFilter={handleFilterSubmit}
|
||||
/>
|
||||
<div class="financial-statement__body">
|
||||
<CashFlowStatementTable companyName={organizationName} />
|
||||
</div>
|
||||
<CashFlowStatementBody />
|
||||
</FinancialStatement>
|
||||
</DashboardPageContent>
|
||||
</CashFlowStatementProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withCurrentOrganization(({ organization }) => ({
|
||||
organizationName: organization.name,
|
||||
})),
|
||||
withCashFlowStatementActions,
|
||||
)(CashFlowStatement);
|
||||
export default compose(withCashFlowStatementActions)(CashFlowStatement);
|
||||
|
||||
@@ -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);
|
||||
@@ -12,6 +12,7 @@ import CashFlowStatementGeneralPanel from './CashFlowStatementGeneralPanel';
|
||||
import withCashFlowStatement from './withCashFlowStatement';
|
||||
import withCashFlowStatementActions from './withCashFlowStatementActions';
|
||||
|
||||
import { getDefaultCashFlowSheetQuery } from './utils';
|
||||
import { compose, transformToForm } from 'utils';
|
||||
|
||||
/**
|
||||
@@ -29,10 +30,7 @@ function CashFlowStatementHeader({
|
||||
toggleCashFlowStatementFilterDrawer,
|
||||
}) {
|
||||
// Filter form default values.
|
||||
const defaultValues = {
|
||||
fromDate: moment().toDate(),
|
||||
toDate: moment().toDate(),
|
||||
};
|
||||
const defaultValues = getDefaultCashFlowSheetQuery();
|
||||
|
||||
// Initial form values.
|
||||
const initialValues = transformToForm({
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import React, { useMemo } from 'react';
|
||||
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 { useCashFlowStatementContext } from './CashFlowStatementProvider';
|
||||
|
||||
import { defaultExpanderReducer } from 'utils';
|
||||
import { TableStyle } from 'common';
|
||||
import { defaultExpanderReducer, tableRowTypesToClassnames } from 'utils';
|
||||
|
||||
/**
|
||||
* Cash flow statement table.
|
||||
@@ -15,11 +17,8 @@ export default function CashFlowStatementTable({
|
||||
// #ownProps
|
||||
companyName,
|
||||
}) {
|
||||
|
||||
|
||||
const {
|
||||
cashFlowStatement: { tableRows },
|
||||
isCashFlowLoading,
|
||||
query,
|
||||
} = useCashFlowStatementContext();
|
||||
|
||||
@@ -29,35 +28,63 @@ export default function CashFlowStatementTable({
|
||||
() => defaultExpanderReducer(tableRows, 4),
|
||||
[tableRows],
|
||||
);
|
||||
|
||||
const rowClassNames = (row) => {
|
||||
return [
|
||||
`row-type--${row.original.row_types}`,
|
||||
`row-type--${row.original.id}`,
|
||||
];
|
||||
};
|
||||
|
||||
return (
|
||||
<FinancialSheet
|
||||
name="cash-flow-statement"
|
||||
companyName={companyName}
|
||||
sheetType={intl.get('statement_of_cash_flow')}
|
||||
loading={isCashFlowLoading}
|
||||
fromDate={query.from_date}
|
||||
toDate={query.to_date}
|
||||
basis={query.basis}
|
||||
>
|
||||
<DataTable
|
||||
className="bigcapital-datatable--financial-report"
|
||||
<CashflowStatementDataTable
|
||||
columns={columns}
|
||||
data={tableRows}
|
||||
rowClassNames={rowClassNames}
|
||||
rowClassNames={tableRowTypesToClassnames}
|
||||
noInitialFetch={true}
|
||||
expandable={true}
|
||||
expanded={expandedRows}
|
||||
expandToggleColumn={1}
|
||||
expandColumnSpace={0.8}
|
||||
styleName={TableStyle.Constrant}
|
||||
/>
|
||||
</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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import React from 'react';
|
||||
import { Button } from '@blueprintjs/core';
|
||||
|
||||
import { Icon, If } from 'components';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
|
||||
import { dynamicColumns } from './utils';
|
||||
import { useCashFlowStatementContext } from './CashFlowStatementProvider';
|
||||
import FinancialLoadingBar from '../FinancialLoadingBar';
|
||||
|
||||
import { dynamicColumns } from './dynamicColumns';
|
||||
import { useCashFlowStatementContext } from './CashFlowStatementProvider';
|
||||
|
||||
/**
|
||||
* Retrieve cash flow statement columns.
|
||||
*/
|
||||
@@ -49,7 +50,6 @@ export function CashFlowStatementAlerts() {
|
||||
if (isCashFlowLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<If condition={cashFlowStatement.meta.is_cost_compute_running}>
|
||||
<div className="alert-compute-running">
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
@@ -1,74 +1,14 @@
|
||||
import * as R from 'ramda';
|
||||
import { CellTextSpan } from 'components/Datatable/Cells';
|
||||
import { getColumnWidth } from 'utils';
|
||||
import intl from 'react-intl-universal';
|
||||
import moment from 'moment';
|
||||
|
||||
/**
|
||||
* Account name column mapper.
|
||||
* Retrieves the default cashflow sheet query.
|
||||
*/
|
||||
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,
|
||||
});
|
||||
|
||||
/**
|
||||
* 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);
|
||||
export const getDefaultCashFlowSheetQuery = () => {
|
||||
return {
|
||||
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
||||
toDate: moment().endOf('year').format('YYYY-MM-DD'),
|
||||
basis: 'cash',
|
||||
displayColumnsType: 'total',
|
||||
filterByOption: 'with-transactions',
|
||||
};
|
||||
return columns.map(mapper);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
@@ -1,37 +1,30 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import moment from 'moment';
|
||||
|
||||
import 'style/pages/FinancialStatements/ContactsBalanceSummary.scss';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { FinancialStatement } from 'components';
|
||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||
|
||||
import CustomersBalanceSummaryActionsBar from './CustomersBalanceSummaryActionsBar';
|
||||
import CustomersBalanceSummaryHeader from './CustomersBalanceSummaryHeader';
|
||||
import CustomersBalanceSummaryTable from './CustomersBalanceSummaryTable';
|
||||
|
||||
import { CustomerBalanceSummaryBody } from './CustomerBalanceSummaryBody';
|
||||
import { CustomersBalanceLoadingBar } from './components';
|
||||
import { CustomersBalanceSummaryProvider } from './CustomersBalanceSummaryProvider';
|
||||
import withCustomersBalanceSummaryActions from './withCustomersBalanceSummaryActions';
|
||||
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
|
||||
|
||||
import { compose } from 'redux';
|
||||
import { getDefaultCustomersBalanceQuery } from './utils';
|
||||
|
||||
/**
|
||||
* Customers Balance summary.
|
||||
*/
|
||||
function CustomersBalanceSummary({
|
||||
// #withPreferences
|
||||
organizationName,
|
||||
|
||||
// #withCustomersBalanceSummaryActions
|
||||
toggleCustomerBalanceFilterDrawer,
|
||||
}) {
|
||||
const [filter, setFilter] = useState({
|
||||
asDate: moment().endOf('day').format('YYYY-MM-DD'),
|
||||
filterByOption: 'with-transactions',
|
||||
...getDefaultCustomersBalanceQuery(),
|
||||
});
|
||||
|
||||
// Handle re-fetch customers balance summary after filter change.
|
||||
const handleFilterSubmit = (filter) => {
|
||||
const _filter = {
|
||||
@@ -40,7 +33,6 @@ function CustomersBalanceSummary({
|
||||
};
|
||||
setFilter({ ..._filter });
|
||||
};
|
||||
|
||||
// Handle number format.
|
||||
const handleNumberFormat = (values) => {
|
||||
setFilter({
|
||||
@@ -66,23 +58,16 @@ function CustomersBalanceSummary({
|
||||
|
||||
<DashboardPageContent>
|
||||
<FinancialStatement>
|
||||
<div className="financial-statement--balance-summary ">
|
||||
<CustomersBalanceSummaryHeader
|
||||
pageFilter={filter}
|
||||
onSubmitFilter={handleFilterSubmit}
|
||||
/>
|
||||
<div className="financial-statement__body">
|
||||
<CustomersBalanceSummaryTable companyName={organizationName} />
|
||||
</div>
|
||||
</div>
|
||||
<CustomersBalanceSummaryHeader
|
||||
pageFilter={filter}
|
||||
onSubmitFilter={handleFilterSubmit}
|
||||
/>
|
||||
<CustomerBalanceSummaryBody />
|
||||
</FinancialStatement>
|
||||
</DashboardPageContent>
|
||||
</CustomersBalanceSummaryProvider>
|
||||
);
|
||||
}
|
||||
export default compose(
|
||||
withCurrentOrganization(({ organization }) => ({
|
||||
organizationName: organization.name,
|
||||
})),
|
||||
withCustomersBalanceSummaryActions,
|
||||
)(CustomersBalanceSummary);
|
||||
export default R.compose(withCustomersBalanceSummaryActions)(
|
||||
CustomersBalanceSummary,
|
||||
);
|
||||
|
||||
@@ -3,6 +3,8 @@ import * as Yup from 'yup';
|
||||
import { Formik, Form } from 'formik';
|
||||
import moment from 'moment';
|
||||
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';
|
||||
@@ -30,7 +32,6 @@ function CustomersBalanceSummaryHeader({
|
||||
const validationSchema = Yup.object().shape({
|
||||
asDate: Yup.date().required().label('asDate'),
|
||||
});
|
||||
|
||||
// Default form values.
|
||||
const defaultValues = {
|
||||
...pageFilter,
|
||||
@@ -47,21 +48,19 @@ function CustomersBalanceSummaryHeader({
|
||||
},
|
||||
defaultValues,
|
||||
);
|
||||
|
||||
// handle form submit.
|
||||
const handleSubmit = (values, { setSubmitting }) => {
|
||||
onSubmitFilter(values);
|
||||
toggleCustomerBalanceFilterDrawer(false);
|
||||
setSubmitting(false);
|
||||
};
|
||||
|
||||
// handle close drawer.
|
||||
const handleDrawerClose = () => {
|
||||
toggleCustomerBalanceFilterDrawer(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<FinancialStatementHeader
|
||||
<CustomerBalanceDrawerHeader
|
||||
isOpen={customersBalanceDrawerFilter}
|
||||
drawerProps={{ onClose: handleDrawerClose }}
|
||||
>
|
||||
@@ -89,7 +88,7 @@ function CustomersBalanceSummaryHeader({
|
||||
</div>
|
||||
</Form>
|
||||
</Formik>
|
||||
</FinancialStatementHeader>
|
||||
</CustomerBalanceDrawerHeader>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -99,3 +98,9 @@ export default compose(
|
||||
})),
|
||||
withCustomersBalanceSummaryActions,
|
||||
)(CustomersBalanceSummaryHeader);
|
||||
|
||||
const CustomerBalanceDrawerHeader = styled(FinancialStatementHeader)`
|
||||
.bp3-drawer {
|
||||
max-height: 450px;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,45 +1,65 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import FinancialSheet from 'components/FinancialSheet';
|
||||
import DataTable from 'components/DataTable';
|
||||
import { ReportDataTable, FinancialSheet } from 'components';
|
||||
|
||||
import { useCustomersBalanceSummaryContext } from './CustomersBalanceSummaryProvider';
|
||||
import { useCustomersSummaryColumns } from './components';
|
||||
|
||||
import { TableStyle } from 'common';
|
||||
import { tableRowTypesToClassnames } from 'utils';
|
||||
|
||||
/**
|
||||
* customers balance summary table.
|
||||
* Customers balance summary table.
|
||||
*/
|
||||
export default function CustomersBalanceSummaryTable({
|
||||
// #ownProps
|
||||
companyName,
|
||||
}) {
|
||||
const {
|
||||
isCustomersBalanceLoading,
|
||||
CustomerBalanceSummary: { table },
|
||||
} = useCustomersBalanceSummaryContext();
|
||||
|
||||
// Retrieves the customers summary columns.
|
||||
const columns = useCustomersSummaryColumns();
|
||||
|
||||
const rowClassNames = (row) => {
|
||||
return [`row-type--${row.original.row_types}`];
|
||||
};
|
||||
|
||||
return (
|
||||
<FinancialSheet
|
||||
name={'customers-balance-summary'}
|
||||
companyName={companyName}
|
||||
sheetType={intl.get('customers_balance_summary')}
|
||||
asDate={new Date()}
|
||||
loading={isCustomersBalanceLoading}
|
||||
>
|
||||
<DataTable
|
||||
className="bigcapital-datatable--financial-report"
|
||||
<CustomerBalanceDataTable
|
||||
columns={columns}
|
||||
data={table.data}
|
||||
rowClassNames={rowClassNames}
|
||||
rowClassNames={tableRowTypesToClassnames}
|
||||
noInitialFetch={true}
|
||||
styleName={TableStyle.Constrant}
|
||||
/>
|
||||
</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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -6,6 +6,8 @@ import { If } from 'components';
|
||||
import FinancialLoadingBar from '../FinancialLoadingBar';
|
||||
import { useCustomersBalanceSummaryContext } from './CustomersBalanceSummaryProvider';
|
||||
|
||||
import { Align } from 'common';
|
||||
|
||||
/**
|
||||
* Retrieve customers balance summary columns.
|
||||
*/
|
||||
@@ -37,6 +39,7 @@ const totalColumnAccessor = () => ({
|
||||
accessor: 'cells[1].value',
|
||||
className: 'total',
|
||||
width: 140,
|
||||
align: Align.Right,
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -47,6 +50,7 @@ const percentageColumnAccessor = () => ({
|
||||
accessor: 'cells[2].value',
|
||||
className: 'total',
|
||||
width: 140,
|
||||
align: Align.Right,
|
||||
});
|
||||
|
||||
const dynamicColumns = (columns) => {
|
||||
@@ -54,7 +58,10 @@ const dynamicColumns = (columns) => {
|
||||
R.compose(
|
||||
R.when(R.pathEq(['key'], 'name'), accountNameColumnAccessor),
|
||||
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);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import moment from 'moment';
|
||||
|
||||
export const getDefaultCustomersBalanceQuery = () => {
|
||||
return {
|
||||
asDate: moment().endOf('day').format('YYYY-MM-DD'),
|
||||
filterByOption: 'with-transactions',
|
||||
};
|
||||
};
|
||||
@@ -1,16 +1,14 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import moment from 'moment';
|
||||
import 'style/pages/FinancialStatements/ContactsTransactions.scss';
|
||||
|
||||
import { FinancialStatement } from 'components';
|
||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||
|
||||
import CustomersTransactionsHeader from './CustomersTransactionsHeader';
|
||||
import CustomersTransactionsTable from './CustomersTransactionsTable';
|
||||
import CustomersTransactionsActionsBar from './CustomersTransactionsActionsBar';
|
||||
import { CustomersTransactionsBody } from './CustomersTransactionsBody';
|
||||
|
||||
import withCustomersTransactionsActions from './withCustomersTransactionsActions';
|
||||
import withCurrentOrganization from '../../../containers/Organization/withCurrentOrganization';
|
||||
import { CustomersTransactionsLoadingBar } from './components';
|
||||
import { CustomersTransactionsProvider } from './CustomersTransactionsProvider';
|
||||
|
||||
@@ -20,9 +18,6 @@ import { compose } from 'utils';
|
||||
* Customers transactions.
|
||||
*/
|
||||
function CustomersTransactions({
|
||||
// #withPreferences
|
||||
organizationName,
|
||||
|
||||
//#withCustomersTransactionsActions
|
||||
toggleCustomersTransactionsFilterDrawer,
|
||||
}) {
|
||||
@@ -66,24 +61,14 @@ function CustomersTransactions({
|
||||
<CustomersTransactionsLoadingBar />
|
||||
<DashboardPageContent>
|
||||
<FinancialStatement>
|
||||
<div className={'financial-statement--transactions'}>
|
||||
<CustomersTransactionsHeader
|
||||
pageFilter={filter}
|
||||
onSubmitFilter={handleFilterSubmit}
|
||||
/>
|
||||
|
||||
<div class="financial-statement__body">
|
||||
<CustomersTransactionsTable companyName={organizationName} />
|
||||
</div>
|
||||
</div>
|
||||
<CustomersTransactionsHeader
|
||||
pageFilter={filter}
|
||||
onSubmitFilter={handleFilterSubmit}
|
||||
/>
|
||||
<CustomersTransactionsBody />
|
||||
</FinancialStatement>
|
||||
</DashboardPageContent>
|
||||
</CustomersTransactionsProvider>
|
||||
);
|
||||
}
|
||||
export default compose(
|
||||
withCurrentOrganization(({ organization }) => ({
|
||||
organizationName: organization.name,
|
||||
})),
|
||||
withCustomersTransactionsActions,
|
||||
)(CustomersTransactions);
|
||||
export default compose(withCustomersTransactionsActions)(CustomersTransactions);
|
||||
|
||||
@@ -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);
|
||||
@@ -5,6 +5,7 @@ import intl from 'react-intl-universal';
|
||||
import moment from 'moment';
|
||||
import * as Yup from 'yup';
|
||||
import { Formik, Form } from 'formik';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
|
||||
import CustomersTransactionsHeaderGeneralPanel from './CustomersTransactionsHeaderGeneralPanel';
|
||||
@@ -67,7 +68,7 @@ function CustomersTransactionsHeader({
|
||||
};
|
||||
|
||||
return (
|
||||
<FinancialStatementHeader
|
||||
<CustomerTransactionsDrawerHeader
|
||||
isOpen={isFilterDrawerOpen}
|
||||
drawerProps={{ onClose: handleDrawerClose }}
|
||||
>
|
||||
@@ -95,7 +96,7 @@ function CustomersTransactionsHeader({
|
||||
</div>
|
||||
</Form>
|
||||
</Formik>
|
||||
</FinancialStatementHeader>
|
||||
</CustomerTransactionsDrawerHeader>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -105,3 +106,9 @@ export default compose(
|
||||
})),
|
||||
withCustomersTransactionsActions,
|
||||
)(CustomersTransactionsHeader);
|
||||
|
||||
const CustomerTransactionsDrawerHeader = styled(FinancialStatementHeader)`
|
||||
.bp3-drawer {
|
||||
max-height: 450px;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import React, { useMemo } from 'react';
|
||||
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 { useCustomersTransactionsContext } from './CustomersTransactionsProvider';
|
||||
|
||||
import { defaultExpanderReducer } from 'utils';
|
||||
import { defaultExpanderReducer, tableRowTypesToClassnames } from 'utils';
|
||||
import { TableStyle } from 'common';
|
||||
|
||||
/**
|
||||
* Customers transactions table.
|
||||
@@ -18,7 +20,6 @@ export default function CustomersTransactionsTable({
|
||||
// Customers transactions context.
|
||||
const {
|
||||
customersTransactions: { tableRows },
|
||||
isCustomersTransactionsLoading,
|
||||
query,
|
||||
} = useCustomersTransactionsContext();
|
||||
|
||||
@@ -30,30 +31,79 @@ export default function CustomersTransactionsTable({
|
||||
[tableRows],
|
||||
);
|
||||
|
||||
const rowClassNames = (row) => {
|
||||
return [`row-type--${row.original.row_types}`];
|
||||
};
|
||||
|
||||
return (
|
||||
<FinancialSheet
|
||||
name="customer-transactions"
|
||||
companyName={companyName}
|
||||
sheetType={intl.get('customers_transactions')}
|
||||
loading={isCustomersTransactionsLoading}
|
||||
fromDate={query.from_date}
|
||||
toDate={query.to_date}
|
||||
fullWidth={true}
|
||||
>
|
||||
<DataTable
|
||||
className="bigcapital-datatable--financial-report"
|
||||
<CustomersTransactionsDataTable
|
||||
columns={columns}
|
||||
data={tableRows}
|
||||
rowClassNames={rowClassNames}
|
||||
rowClassNames={tableRowTypesToClassnames}
|
||||
noInitialFetch={true}
|
||||
expandable={true}
|
||||
expanded={expandedRows}
|
||||
expandToggleColumn={1}
|
||||
expandColumnSpace={0.8}
|
||||
styleName={TableStyle.Constrant}
|
||||
/>
|
||||
</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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -3,12 +3,13 @@ import intl from 'react-intl-universal';
|
||||
import { If } from 'components';
|
||||
import { useCustomersTransactionsContext } from './CustomersTransactionsProvider';
|
||||
import FinancialLoadingBar from '../FinancialLoadingBar';
|
||||
import { getForceWidth, getColumnWidth } from 'utils';
|
||||
import { getColumnWidth } from 'utils';
|
||||
|
||||
import { Align } from 'common';
|
||||
|
||||
/**
|
||||
* Retrieve customers transactions columns.
|
||||
*/
|
||||
|
||||
export const useCustomersTransactionsColumns = () => {
|
||||
const {
|
||||
customersTransactions: { tableRows },
|
||||
@@ -18,18 +19,8 @@ export const useCustomersTransactionsColumns = () => {
|
||||
() => [
|
||||
{
|
||||
Header: intl.get('customer_name'),
|
||||
accessor: ({ cells }) => {
|
||||
return (
|
||||
<span
|
||||
className={'force-width'}
|
||||
style={{ minWidth: getForceWidth(cells[0].value) }}
|
||||
>
|
||||
{cells[0].value}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
accessor: 'cells[0].value',
|
||||
className: 'customer_name',
|
||||
// textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('account_name'),
|
||||
@@ -59,6 +50,7 @@ export const useCustomersTransactionsColumns = () => {
|
||||
minWidth: 100,
|
||||
magicSpacing: 10,
|
||||
}),
|
||||
align: Align.Right,
|
||||
},
|
||||
{
|
||||
Header: intl.get('debit'),
|
||||
@@ -69,6 +61,7 @@ export const useCustomersTransactionsColumns = () => {
|
||||
minWidth: 100,
|
||||
magicSpacing: 10,
|
||||
}),
|
||||
align: Align.Right,
|
||||
},
|
||||
{
|
||||
Header: intl.get('running_balance'),
|
||||
@@ -79,6 +72,7 @@ export const useCustomersTransactionsColumns = () => {
|
||||
minWidth: 120,
|
||||
magicSpacing: 10,
|
||||
}),
|
||||
align: Align.Right,
|
||||
},
|
||||
],
|
||||
[tableRows],
|
||||
|
||||
@@ -1,18 +1,60 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { DashboardInsider } from 'components';
|
||||
import styled from 'styled-components';
|
||||
import { CLASSES } from 'common/classes';
|
||||
|
||||
import 'style/pages/FinancialStatements/FinancialReportPage.scss';
|
||||
|
||||
/**
|
||||
* Financial report page.
|
||||
*/
|
||||
export default function FinancialReportPage(props) {
|
||||
return (
|
||||
<DashboardInsider
|
||||
<FinancialReportPageRoot
|
||||
{...props}
|
||||
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;
|
||||
`;
|
||||
|
||||
@@ -3,10 +3,15 @@ import classNames from 'classnames';
|
||||
import { Position, Drawer } from '@blueprintjs/core';
|
||||
import 'style/containers/FinancialStatements/DrawerHeader.scss';
|
||||
|
||||
/**
|
||||
* Financial statement header.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export default function FinancialStatementHeader({
|
||||
children,
|
||||
isOpen,
|
||||
drawerProps,
|
||||
className,
|
||||
}) {
|
||||
const timeoutRef = React.useRef();
|
||||
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||
@@ -42,6 +47,7 @@ export default function FinancialStatementHeader({
|
||||
{
|
||||
'is-hidden': !isDrawerOpen,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Drawer
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import moment from 'moment';
|
||||
|
||||
import 'style/pages/FinancialStatements/GeneralLedger.scss';
|
||||
|
||||
import GeneralLedgerTable from './GeneralLedgerTable';
|
||||
import { FinancialStatement } from 'components';
|
||||
import GeneralLedgerHeader from './GeneralLedgerHeader';
|
||||
|
||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||
@@ -13,12 +11,13 @@ import {
|
||||
GeneralLedgerSheetAlerts,
|
||||
GeneralLedgerSheetLoadingBar,
|
||||
} from './components';
|
||||
import { GeneralLedgerBody } from './GeneralLedgerBody';
|
||||
|
||||
import withGeneralLedgerActions from './withGeneralLedgerActions';
|
||||
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
|
||||
|
||||
import { transformFilterFormToQuery } from 'containers/FinancialStatements/common';
|
||||
import { compose } from 'utils';
|
||||
import { getDefaultGeneralLedgerQuery } from './common';
|
||||
|
||||
/**
|
||||
* General Ledger (GL) sheet.
|
||||
@@ -26,15 +25,9 @@ import { compose } from 'utils';
|
||||
function GeneralLedger({
|
||||
// #withGeneralLedgerActions
|
||||
toggleGeneralLedgerFilterDrawer,
|
||||
|
||||
// #withSettings
|
||||
organizationName,
|
||||
}) {
|
||||
const [filter, setFilter] = useState({
|
||||
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
||||
toDate: moment().endOf('year').format('YYYY-MM-DD'),
|
||||
basis: 'accural',
|
||||
filterByOption: 'with-transactions',
|
||||
...getDefaultGeneralLedgerQuery(),
|
||||
});
|
||||
|
||||
// Handle financial statement filter change.
|
||||
@@ -63,29 +56,18 @@ function GeneralLedger({
|
||||
<GeneralLedgerActionsBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<div class="financial-statement financial-statement--general-ledger">
|
||||
<FinancialStatement>
|
||||
<GeneralLedgerHeader
|
||||
pageFilter={filter}
|
||||
onSubmitFilter={handleFilterSubmit}
|
||||
/>
|
||||
<GeneralLedgerSheetLoadingBar />
|
||||
<GeneralLedgerSheetAlerts />
|
||||
|
||||
<div class="financial-statement__body">
|
||||
<GeneralLedgerTable
|
||||
companyName={organizationName}
|
||||
generalLedgerQuery={filter}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<GeneralLedgerBody />
|
||||
</FinancialStatement>
|
||||
</DashboardPageContent>
|
||||
</GeneralLedgerProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withGeneralLedgerActions,
|
||||
withCurrentOrganization(({ organization }) => ({
|
||||
organizationName: organization.name,
|
||||
})),
|
||||
)(GeneralLedger);
|
||||
export default compose(withGeneralLedgerActions)(GeneralLedger);
|
||||
|
||||
@@ -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);
|
||||
@@ -3,6 +3,8 @@ import moment from 'moment';
|
||||
import * as Yup from 'yup';
|
||||
import { Formik, Form } from 'formik';
|
||||
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';
|
||||
@@ -68,7 +70,7 @@ function GeneralLedgerHeader({
|
||||
};
|
||||
|
||||
return (
|
||||
<FinancialStatementHeader
|
||||
<GeneralLedgerDrawerHeader
|
||||
isOpen={isFilterDrawerOpen}
|
||||
drawerProps={{ onClose: handleDrawerClose }}
|
||||
>
|
||||
@@ -97,7 +99,7 @@ function GeneralLedgerHeader({
|
||||
</div>
|
||||
</Form>
|
||||
</Formik>
|
||||
</FinancialStatementHeader>
|
||||
</GeneralLedgerDrawerHeader>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -107,3 +109,9 @@ export default compose(
|
||||
})),
|
||||
withGeneralLedgerActions,
|
||||
)(GeneralLedgerHeader);
|
||||
|
||||
const GeneralLedgerDrawerHeader = styled(FinancialStatementHeader)`
|
||||
.bp3-drawer {
|
||||
max-height: 520px;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { defaultExpanderReducer } from 'utils';
|
||||
import intl from 'react-intl-universal';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import FinancialSheet from 'components/FinancialSheet';
|
||||
import DataTable from 'components/DataTable';
|
||||
import { defaultExpanderReducer, tableRowTypesToClassnames } from 'utils';
|
||||
|
||||
import { FinancialSheet, ReportDataTable } from 'components';
|
||||
import TableVirtualizedListRows from 'components/Datatable/TableVirtualizedRows';
|
||||
import TableFastCell from 'components/Datatable/TableFastCell';
|
||||
|
||||
import { useGeneralLedgerContext } from './GeneralLedgerProvider';
|
||||
import { useGeneralLedgerTableColumns } from './components';
|
||||
|
||||
import { TableStyle } from 'common';
|
||||
|
||||
/**
|
||||
* General ledger table.
|
||||
*/
|
||||
@@ -25,11 +27,10 @@ export default function GeneralLedgerTable({ companyName }) {
|
||||
const columns = useGeneralLedgerTableColumns();
|
||||
|
||||
// Default expanded rows of general ledger table.
|
||||
const expandedRows = useMemo(() => defaultExpanderReducer(tableRows, 1), [
|
||||
tableRows,
|
||||
]);
|
||||
|
||||
const rowClassNames = (row) => [`row-type--${row.original.rowType}`];
|
||||
const expandedRows = useMemo(
|
||||
() => defaultExpanderReducer(tableRows, 1),
|
||||
[tableRows],
|
||||
);
|
||||
|
||||
return (
|
||||
<FinancialSheet
|
||||
@@ -37,16 +38,16 @@ export default function GeneralLedgerTable({ companyName }) {
|
||||
sheetType={intl.get('general_ledger_sheet')}
|
||||
fromDate={query.from_date}
|
||||
toDate={query.to_date}
|
||||
name="general-ledger"
|
||||
loading={isLoading}
|
||||
fullWidth={true}
|
||||
>
|
||||
<DataTable
|
||||
className="bigcapital-datatable--financial-report"
|
||||
noResults={intl.get('this_report_does_not_contain_any_data_between_date_period')}
|
||||
<GeneralLedgerDataTable
|
||||
noResults={intl.get(
|
||||
'this_report_does_not_contain_any_data_between_date_period',
|
||||
)}
|
||||
columns={columns}
|
||||
data={tableRows}
|
||||
rowClassNames={rowClassNames}
|
||||
rowClassNames={tableRowTypesToClassnames}
|
||||
expanded={expandedRows}
|
||||
virtualizedRows={true}
|
||||
fixedItemSize={30}
|
||||
@@ -59,7 +60,59 @@ export default function GeneralLedgerTable({ companyName }) {
|
||||
vListrowHeight={28}
|
||||
vListOverscanRowCount={0}
|
||||
TableCellRenderer={TableFastCell}
|
||||
styleName={TableStyle.Constrant}
|
||||
/>
|
||||
</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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import intl from 'react-intl-universal';
|
||||
import moment from 'moment';
|
||||
|
||||
export const filterAccountsOptions = [
|
||||
{
|
||||
@@ -9,6 +10,20 @@ export const filterAccountsOptions = [
|
||||
{
|
||||
key: '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',
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4,10 +4,12 @@ import { Button } from '@blueprintjs/core';
|
||||
import { Icon, If } from 'components';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
|
||||
import { getForceWidth, getColumnWidth } from 'utils';
|
||||
import { getColumnWidth } from 'utils';
|
||||
import { useGeneralLedgerContext } from './GeneralLedgerProvider';
|
||||
import FinancialLoadingBar from '../FinancialLoadingBar';
|
||||
|
||||
import { Align } from 'common';
|
||||
|
||||
/**
|
||||
* Retrieve the general ledger table columns.
|
||||
*/
|
||||
@@ -21,21 +23,8 @@ export function useGeneralLedgerTableColumns() {
|
||||
() => [
|
||||
{
|
||||
Header: intl.get('date'),
|
||||
accessor: (row) => {
|
||||
if (row.rowType === 'ACCOUNT_ROW') {
|
||||
return (
|
||||
<span
|
||||
className={'force-width'}
|
||||
style={{ minWidth: getForceWidth(row.date) }}
|
||||
>
|
||||
{row.date}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return row.date;
|
||||
},
|
||||
accessor: 'date',
|
||||
className: 'date',
|
||||
// textOverview: true,
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
@@ -73,6 +62,7 @@ export function useGeneralLedgerTableColumns() {
|
||||
magicSpacing: 10,
|
||||
}),
|
||||
textOverview: true,
|
||||
align: Align.Right,
|
||||
},
|
||||
{
|
||||
Header: intl.get('debit'),
|
||||
@@ -83,6 +73,7 @@ export function useGeneralLedgerTableColumns() {
|
||||
magicSpacing: 10,
|
||||
}),
|
||||
textOverview: true,
|
||||
align: Align.Right,
|
||||
},
|
||||
{
|
||||
Header: intl.get('amount'),
|
||||
@@ -93,6 +84,7 @@ export function useGeneralLedgerTableColumns() {
|
||||
magicSpacing: 10,
|
||||
}),
|
||||
textOverview: true,
|
||||
align: Align.Right,
|
||||
},
|
||||
{
|
||||
Header: intl.get('running_balance'),
|
||||
@@ -103,6 +95,7 @@ export function useGeneralLedgerTableColumns() {
|
||||
magicSpacing: 10,
|
||||
}),
|
||||
textOverview: true,
|
||||
align: Align.Right,
|
||||
},
|
||||
],
|
||||
[tableRows],
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import moment from 'moment';
|
||||
import 'style/pages/FinancialStatements/InventoryItemDetails.scss';
|
||||
|
||||
import { FinancialStatement } from 'components';
|
||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||
|
||||
import InventoryItemDetailsActionsBar from './InventoryItemDetailsActionsBar';
|
||||
import InventoryItemDetailsHeader from './InventoryItemDetailsHeader';
|
||||
import InventoryItemDetailsTable from './InventoryItemDetailsTable';
|
||||
|
||||
import withInventoryItemDetailsActions from './withInventoryItemDetailsActions';
|
||||
import withCurrentOrganization from '../../../containers/Organization/withCurrentOrganization';
|
||||
@@ -18,6 +16,7 @@ import {
|
||||
} from './components';
|
||||
|
||||
import { compose } from 'utils';
|
||||
import { InventoryItemDetailsBody } from './InventoryItemDetailsBody';
|
||||
|
||||
/**
|
||||
* inventory item details.
|
||||
@@ -64,19 +63,11 @@ function InventoryItemDetails({
|
||||
|
||||
<DashboardPageContent>
|
||||
<FinancialStatement>
|
||||
<div
|
||||
className={
|
||||
'financial-statement financial-statement--inventory-details'
|
||||
}
|
||||
>
|
||||
<InventoryItemDetailsHeader
|
||||
pageFilter={filter}
|
||||
onSubmitFilter={handleFilterSubmit}
|
||||
/>
|
||||
</div>
|
||||
<div class="financial-statement__body">
|
||||
<InventoryItemDetailsTable companyName={organizationName} />
|
||||
</div>
|
||||
<InventoryItemDetailsHeader
|
||||
pageFilter={filter}
|
||||
onSubmitFilter={handleFilterSubmit}
|
||||
/>
|
||||
<InventoryItemDetailsBody />
|
||||
</FinancialStatement>
|
||||
</DashboardPageContent>
|
||||
</InventoryItemDetailsProvider>
|
||||
|
||||
@@ -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);
|
||||
@@ -3,8 +3,10 @@ import * as Yup from 'yup';
|
||||
import moment from 'moment';
|
||||
import { Formik, Form } from 'formik';
|
||||
import { Tabs, Tab, Button, Intent } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import intl from 'react-intl-universal';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { FormattedMessage as T } from 'components';
|
||||
|
||||
import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
|
||||
import InventoryItemDetailsHeaderGeneralPanel from './InventoryItemDetailsHeaderGeneralPanel';
|
||||
@@ -35,24 +37,23 @@ function InventoryItemDetailsHeader({
|
||||
};
|
||||
|
||||
// Filter form initial values.
|
||||
const initialValues = transformToForm({
|
||||
...pageFilter,
|
||||
fromDate: moment(pageFilter.fromDate).toDate(),
|
||||
toDate: moment(pageFilter.toDate).toDate(),
|
||||
}, defaultValues);
|
||||
const initialValues = transformToForm(
|
||||
{
|
||||
...pageFilter,
|
||||
fromDate: moment(pageFilter.fromDate).toDate(),
|
||||
toDate: moment(pageFilter.toDate).toDate(),
|
||||
},
|
||||
defaultValues,
|
||||
);
|
||||
|
||||
// Validation schema.
|
||||
const validationSchema = Yup.object().shape({
|
||||
fromDate: Yup.date()
|
||||
.required()
|
||||
.label(intl.get('fromDate')),
|
||||
fromDate: Yup.date().required().label(intl.get('fromDate')),
|
||||
toDate: Yup.date()
|
||||
.min(Yup.ref('fromDate'))
|
||||
.required()
|
||||
.label(intl.get('toDate')),
|
||||
});
|
||||
;
|
||||
|
||||
// Handle form submit.
|
||||
const handleSubmit = (values, { setSubmitting }) => {
|
||||
onSubmitFilter(values);
|
||||
@@ -61,10 +62,12 @@ function InventoryItemDetailsHeader({
|
||||
};
|
||||
|
||||
// Handle drawer close action.
|
||||
const handleDrawerClose = () => { toggleFilterDrawer(false); };
|
||||
|
||||
const handleDrawerClose = () => {
|
||||
toggleFilterDrawer(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<FinancialStatementHeader
|
||||
<InventoryItemDetailsDrawerHeader
|
||||
isOpen={isFilterDrawerOpen}
|
||||
drawerProps={{ onClose: handleDrawerClose }}
|
||||
>
|
||||
@@ -91,7 +94,7 @@ function InventoryItemDetailsHeader({
|
||||
</div>
|
||||
</Form>
|
||||
</Formik>
|
||||
</FinancialStatementHeader>
|
||||
</InventoryItemDetailsDrawerHeader>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -101,3 +104,9 @@ export default compose(
|
||||
})),
|
||||
withInventoryItemDetailsActions,
|
||||
)(InventoryItemDetailsHeader);
|
||||
|
||||
const InventoryItemDetailsDrawerHeader = styled(FinancialStatementHeader)`
|
||||
.bp3-drawer {
|
||||
max-height: 400px;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import FinancialSheet from 'components/FinancialSheet';
|
||||
import { DataTable } from 'components';
|
||||
import { ReportDataTable, FinancialSheet } from 'components';
|
||||
import { useInventoryItemDetailsColumns } from './components';
|
||||
import { useInventoryItemDetailsContext } from './InventoryItemDetailsProvider';
|
||||
|
||||
import { defaultExpanderReducer } from 'utils';
|
||||
import { defaultExpanderReducer, tableRowTypesToClassnames } from 'utils';
|
||||
import { TableStyle } from 'common';
|
||||
|
||||
/**
|
||||
* Inventory item detail table.
|
||||
*/
|
||||
export default function InventoryItemDetailsTable({
|
||||
export function InventoryItemDetailsTable({
|
||||
// #ownProps
|
||||
companyName,
|
||||
}) {
|
||||
@@ -28,30 +29,79 @@ export default function InventoryItemDetailsTable({
|
||||
[tableRows],
|
||||
);
|
||||
|
||||
const rowClassNames = (row) => {
|
||||
return [`row-type--${row.original.row_types}`];
|
||||
};
|
||||
|
||||
return (
|
||||
<FinancialSheet
|
||||
name="inventory-item-details"
|
||||
companyName={companyName}
|
||||
sheetType={intl.get('inventory_item_details')}
|
||||
loading={isInventoryItemDetailsLoading}
|
||||
fromDate={query.from_date}
|
||||
toDate={query.to_date}
|
||||
fullWidth={true}
|
||||
>
|
||||
<DataTable
|
||||
className="bigcapital-datatable--financial-report"
|
||||
<InventoryItemDetailsDataTable
|
||||
columns={columns}
|
||||
data={tableRows}
|
||||
rowClassNames={rowClassNames}
|
||||
rowClassNames={tableRowTypesToClassnames}
|
||||
noInitialFetch={true}
|
||||
expandable={true}
|
||||
expanded={expandedRows}
|
||||
expandToggleColumn={1}
|
||||
expandColumnSpace={0.8}
|
||||
styleName={TableStyle.Constrant}
|
||||
/>
|
||||
</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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,17 +1,43 @@
|
||||
import * as R from 'ramda';
|
||||
|
||||
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.
|
||||
*/
|
||||
const columnsMapper = (data, index, column) => ({
|
||||
const columnsMapper = R.curry((data, index, column) => ({
|
||||
id: column.key,
|
||||
key: column.key,
|
||||
Header: column.label,
|
||||
Cell: CellForceWidth,
|
||||
accessor: `cells[${index}].value`,
|
||||
forceWidthAccess: `cells[0].value`,
|
||||
className: column.key,
|
||||
width: getColumnWidth(data, `cells.${index}.key`, {
|
||||
minWidth: 130,
|
||||
@@ -19,7 +45,7 @@ const columnsMapper = (data, index, column) => ({
|
||||
}),
|
||||
disableSortBy: true,
|
||||
textOverview: true,
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* Inventory item details columns.
|
||||
@@ -27,7 +53,17 @@ const columnsMapper = (data, index, column) => ({
|
||||
export const dynamicColumns = (columns, data) => {
|
||||
const mapper = (column, index) => {
|
||||
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);
|
||||
};
|
||||
return columns.map(mapper);
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import moment from 'moment';
|
||||
|
||||
import 'style/pages/FinancialStatements/SalesAndPurchasesSheet.scss';
|
||||
|
||||
import { InventoryValuationProvider } from './InventoryValuationProvider';
|
||||
import InventoryValuationActionsBar from './InventoryValuationActionsBar';
|
||||
import InventoryValuationHeader from './InventoryValuationHeader';
|
||||
import InventoryValuationTable from './InventoryValuationTable';
|
||||
import { InventoryValuationBody } from './InventoryValuationBody';
|
||||
|
||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||
import { InventoryValuationLoadingBar } from './components';
|
||||
@@ -64,15 +62,11 @@ function InventoryValuation({
|
||||
<InventoryValuationLoadingBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<div class="financial-statement financial-statement--inventory-valuation">
|
||||
<InventoryValuationHeader
|
||||
pageFilter={filter}
|
||||
onSubmitFilter={handleFilterSubmit}
|
||||
/>
|
||||
<div class="financial-statement__body">
|
||||
<InventoryValuationTable companyName={organizationName} />
|
||||
</div>
|
||||
</div>
|
||||
<InventoryValuationHeader
|
||||
pageFilter={filter}
|
||||
onSubmitFilter={handleFilterSubmit}
|
||||
/>
|
||||
<InventoryValuationBody />
|
||||
</DashboardPageContent>
|
||||
</InventoryValuationProvider>
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
@@ -1,9 +1,11 @@
|
||||
import React from 'react';
|
||||
import * as Yup from 'yup';
|
||||
import moment from 'moment';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import { Formik, Form } from 'formik';
|
||||
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 InventoryValuationHeaderGeneralPanel from './InventoryValuationHeaderGeneralPanel';
|
||||
@@ -38,11 +40,14 @@ function InventoryValuationHeader({
|
||||
itemsIds: [],
|
||||
};
|
||||
// Initial values.
|
||||
const initialValues = transformToForm({
|
||||
...pageFilter,
|
||||
...defaultValues,
|
||||
asDate: moment(pageFilter.asDate).toDate(),
|
||||
}, defaultValues);
|
||||
const initialValues = transformToForm(
|
||||
{
|
||||
...pageFilter,
|
||||
...defaultValues,
|
||||
asDate: moment(pageFilter.asDate).toDate(),
|
||||
},
|
||||
defaultValues,
|
||||
);
|
||||
|
||||
// Handle the form of header submit.
|
||||
const handleSubmit = (values, { setSubmitting }) => {
|
||||
@@ -62,7 +67,7 @@ function InventoryValuationHeader({
|
||||
};
|
||||
|
||||
return (
|
||||
<FinancialStatementHeader
|
||||
<InventoryValuationDrawerHeader
|
||||
isOpen={isFilterDrawerOpen}
|
||||
drawerProps={{ onClose: handleDrawerClose }}
|
||||
>
|
||||
@@ -89,7 +94,7 @@ function InventoryValuationHeader({
|
||||
</div>
|
||||
</Form>
|
||||
</Formik>
|
||||
</FinancialStatementHeader>
|
||||
</InventoryValuationDrawerHeader>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -99,3 +104,9 @@ export default compose(
|
||||
})),
|
||||
withInventoryValuationActions,
|
||||
)(InventoryValuationHeader);
|
||||
|
||||
const InventoryValuationDrawerHeader = styled(FinancialStatementHeader)`
|
||||
.bp3-drawer {
|
||||
max-height: 450px;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
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 { DataTable } from 'components';
|
||||
import { ReportDataTable, FinancialSheet } from 'components';
|
||||
|
||||
import { useInventoryValuationContext } from './InventoryValuationProvider';
|
||||
import { useInventoryValuationTableColumns } from './components';
|
||||
|
||||
import { tableRowTypesToClassnames } from 'utils';
|
||||
import { TableStyle } from 'common';
|
||||
|
||||
/**
|
||||
* inventory valuation data table.
|
||||
*/
|
||||
@@ -23,41 +26,47 @@ export default function InventoryValuationTable({
|
||||
// inventory valuation table columns.
|
||||
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 (
|
||||
<FinancialSheet
|
||||
<InventoryValuationSheet
|
||||
companyName={companyName}
|
||||
name="inventory-valuation"
|
||||
sheetType={intl.get('inventory_valuation')}
|
||||
asDate={new Date()}
|
||||
loading={isLoading}
|
||||
>
|
||||
<DataTable
|
||||
className="bigcapital-datatable--financial-report"
|
||||
<InventoryValuationDataTable
|
||||
columns={columns}
|
||||
data={tableRows}
|
||||
expandable={true}
|
||||
expandToggleColumn={1}
|
||||
expandColumnSpace={1}
|
||||
sticky={true}
|
||||
rowClassNames={rowClassNames}
|
||||
rowClassNames={tableRowTypesToClassnames}
|
||||
styleName={TableStyle.Constrant}
|
||||
noResults={intl.get(
|
||||
'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;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { getColumnWidth } from 'utils';
|
||||
|
||||
import { If } from 'components';
|
||||
import { CellTextSpan } from 'components/Datatable/Cells';
|
||||
import { useInventoryValuationContext } from './InventoryValuationProvider';
|
||||
import FinancialLoadingBar from '../FinancialLoadingBar';
|
||||
|
||||
import { getColumnWidth } from 'utils';
|
||||
import { Align } from 'common';
|
||||
|
||||
/**
|
||||
* Retrieve inventory valuation table columns.
|
||||
*/
|
||||
|
||||
export const useInventoryValuationTableColumns = () => {
|
||||
|
||||
|
||||
// inventory valuation context
|
||||
const {
|
||||
inventoryValuation: { tableRows },
|
||||
@@ -36,6 +36,7 @@ export const useInventoryValuationTableColumns = () => {
|
||||
minWidth: 120,
|
||||
}),
|
||||
textOverview: true,
|
||||
align: Align.Right,
|
||||
},
|
||||
{
|
||||
Header: intl.get('asset_value'),
|
||||
@@ -46,6 +47,7 @@ export const useInventoryValuationTableColumns = () => {
|
||||
minWidth: 120,
|
||||
}),
|
||||
textOverview: true,
|
||||
align: Align.Right,
|
||||
},
|
||||
{
|
||||
Header: intl.get('average'),
|
||||
@@ -56,6 +58,7 @@ export const useInventoryValuationTableColumns = () => {
|
||||
minWidth: 120,
|
||||
}),
|
||||
textOverview: true,
|
||||
align: Align.Right,
|
||||
},
|
||||
],
|
||||
[tableRows],
|
||||
|
||||
@@ -1,38 +1,30 @@
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
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 JournalActionsBar from './JournalActionsBar';
|
||||
import { JournalSheetProvider } from './JournalProvider';
|
||||
import { JournalSheetLoadingBar, JournalSheetAlerts } from './components';
|
||||
import { JournalBody } from './JournalBody';
|
||||
|
||||
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withJournalActions from './withJournalActions';
|
||||
|
||||
import { getDefaultJournalQuery } from './utils';
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* Journal sheet.
|
||||
*/
|
||||
function Journal({
|
||||
// #withPreferences
|
||||
organizationName,
|
||||
|
||||
// #withJournalActions
|
||||
toggleJournalSheetFilter
|
||||
toggleJournalSheetFilter,
|
||||
}) {
|
||||
const [filter, setFilter] = useState({
|
||||
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
||||
toDate: moment().endOf('year').format('YYYY-MM-DD'),
|
||||
basis: 'accural',
|
||||
...getDefaultJournalQuery(),
|
||||
});
|
||||
|
||||
// Handle financial statement filter change.
|
||||
const handleFilterSubmit = useCallback(
|
||||
(filter) => {
|
||||
@@ -45,41 +37,31 @@ function Journal({
|
||||
},
|
||||
[setFilter],
|
||||
);
|
||||
|
||||
// Hide the journal sheet filter drawer once the page unmount.
|
||||
useEffect(() => () => {
|
||||
toggleJournalSheetFilter(false);
|
||||
}, [toggleJournalSheetFilter]);
|
||||
useEffect(
|
||||
() => () => {
|
||||
toggleJournalSheetFilter(false);
|
||||
},
|
||||
[toggleJournalSheetFilter],
|
||||
);
|
||||
|
||||
return (
|
||||
<JournalSheetProvider query={filter}>
|
||||
<JournalActionsBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<div class="financial-statement financial-statement--journal">
|
||||
<FinancialStatement>
|
||||
<JournalHeader
|
||||
onSubmitFilter={handleFilterSubmit}
|
||||
pageFilter={filter}
|
||||
/>
|
||||
<JournalSheetLoadingBar />
|
||||
<JournalSheetAlerts />
|
||||
|
||||
<div class="financial-statement__body">
|
||||
<JournalTable
|
||||
companyName={organizationName}
|
||||
journalQuery={filter}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<JournalBody />
|
||||
</FinancialStatement>
|
||||
</DashboardPageContent>
|
||||
</JournalSheetProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDashboardActions,
|
||||
withJournalActions,
|
||||
withCurrentOrganization(({ organization }) => ({
|
||||
organizationName: organization.name,
|
||||
})),
|
||||
)(Journal);
|
||||
export default compose(withDashboardActions, withJournalActions)(Journal);
|
||||
|
||||
@@ -8,10 +8,11 @@ import {
|
||||
PopoverInteractionKind,
|
||||
Position,
|
||||
} from '@blueprintjs/core';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import Icon from 'components/Icon';
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import withJournalActions from './withJournalActions';
|
||||
import withJournal from './withJournal';
|
||||
@@ -56,7 +57,7 @@ function JournalActionsBar({
|
||||
className={classNames(Classes.MINIMAL, 'button--table-views')}
|
||||
icon={<Icon icon="cog-16" iconSize={16} />}
|
||||
text={
|
||||
(isFilterDrawerOpen) ? (
|
||||
isFilterDrawerOpen ? (
|
||||
<T id={'hide_customizer'} />
|
||||
) : (
|
||||
<T id={'customize_report'} />
|
||||
@@ -97,7 +98,7 @@ function JournalActionsBar({
|
||||
|
||||
export default compose(
|
||||
withJournal(({ journalSheetDrawerFilter }) => ({
|
||||
isFilterDrawerOpen: journalSheetDrawerFilter
|
||||
isFilterDrawerOpen: journalSheetDrawerFilter,
|
||||
})),
|
||||
withJournalActions,
|
||||
)(JournalActionsBar);
|
||||
|
||||
37
src/containers/FinancialStatements/Journal/JournalBody.js
Normal file
37
src/containers/FinancialStatements/Journal/JournalBody.js
Normal 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);
|
||||
@@ -3,6 +3,8 @@ import moment from 'moment';
|
||||
import { Formik, Form } from 'formik';
|
||||
import { Tab, Tabs, Button, Intent } from '@blueprintjs/core';
|
||||
import * as Yup from 'yup';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { FormattedMessage as T } from 'components';
|
||||
|
||||
import JournalSheetHeaderGeneral from './JournalSheetHeaderGeneral';
|
||||
@@ -55,7 +57,7 @@ function JournalHeader({
|
||||
};
|
||||
|
||||
return (
|
||||
<FinancialStatementHeader
|
||||
<JournalDrawerHeader
|
||||
isOpen={journalSheetDrawerFilter}
|
||||
drawerProps={{ onClose: handleDrawerClose }}
|
||||
>
|
||||
@@ -83,7 +85,7 @@ function JournalHeader({
|
||||
</div>
|
||||
</Form>
|
||||
</Formik>
|
||||
</FinancialStatementHeader>
|
||||
</JournalDrawerHeader>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -93,3 +95,9 @@ export default compose(
|
||||
})),
|
||||
withJournalActions,
|
||||
)(JournalHeader);
|
||||
|
||||
const JournalDrawerHeader = styled(FinancialStatementHeader)`
|
||||
.bp3-drawer {
|
||||
max-height: 350px;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import FinancialSheet from 'components/FinancialSheet';
|
||||
import DataTable from 'components/DataTable';
|
||||
import { useJournalSheetContext } from './JournalProvider';
|
||||
import { ReportDataTable, FinancialSheet } from 'components';
|
||||
import TableVirtualizedListRows from 'components/Datatable/TableVirtualizedRows';
|
||||
import TableFastCell from 'components/Datatable/TableFastCell';
|
||||
import { defaultExpanderReducer } from 'utils';
|
||||
|
||||
import { useJournalTableColumns } from './components';
|
||||
import { useJournalSheetContext } from './JournalProvider';
|
||||
|
||||
export default function JournalSheetTable({
|
||||
// #ownProps
|
||||
onFetchData,
|
||||
companyName,
|
||||
}) {
|
||||
|
||||
import { defaultExpanderReducer, tableRowTypesToClassnames } from 'utils';
|
||||
import { TableStyle } from 'common';
|
||||
|
||||
/**
|
||||
* Journal sheet table.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function JournalTable({ companyName }) {
|
||||
// Journal sheet context.
|
||||
const {
|
||||
journalSheet: { tableRows, query },
|
||||
isLoading
|
||||
isLoading,
|
||||
} = useJournalSheetContext();
|
||||
|
||||
// Retreive the journal table columns.
|
||||
@@ -28,22 +29,6 @@ export default function JournalSheetTable({
|
||||
// Default expanded rows of general journal table.
|
||||
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 (
|
||||
<FinancialSheet
|
||||
companyName={companyName}
|
||||
@@ -52,25 +37,55 @@ export default function JournalSheetTable({
|
||||
toDate={query.to_date}
|
||||
name="journal"
|
||||
loading={isLoading}
|
||||
// minimal={true}
|
||||
fullWidth={true}
|
||||
>
|
||||
<DataTable
|
||||
className="bigcapital-datatable--financial-report"
|
||||
>
|
||||
<JournalDataTable
|
||||
columns={columns}
|
||||
data={tableRows}
|
||||
rowClassNames={rowClassNames}
|
||||
noResults={intl.get('this_report_does_not_contain_any_data_between_date_period')}
|
||||
rowClassNames={tableRowTypesToClassnames}
|
||||
noResults={intl.get(
|
||||
'this_report_does_not_contain_any_data_between_date_period',
|
||||
)}
|
||||
expanded={expandedRows}
|
||||
sticky={true}
|
||||
TableRowsRenderer={TableVirtualizedListRows}
|
||||
// #TableVirtualizedListRows props.
|
||||
vListrowHeight={28}
|
||||
vListOverscanRowCount={2}
|
||||
|
||||
TableCellRenderer={TableFastCell}
|
||||
id={'journal'}
|
||||
styleName={TableStyle.Constrant}
|
||||
/>
|
||||
</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;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -2,15 +2,17 @@ import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import moment from 'moment';
|
||||
import { Button } from '@blueprintjs/core';
|
||||
|
||||
import { Icon, If, FormattedMessage as T } from 'components';
|
||||
import { useJournalSheetContext } from './JournalProvider';
|
||||
import FinancialLoadingBar from '../FinancialLoadingBar';
|
||||
|
||||
import { Align } from 'common';
|
||||
|
||||
/**
|
||||
* Retrieve the journal table columns.
|
||||
*/
|
||||
export const useJournalTableColumns = () => {
|
||||
|
||||
return React.useMemo(
|
||||
() => [
|
||||
{
|
||||
@@ -57,12 +59,12 @@ export const useJournalTableColumns = () => {
|
||||
{
|
||||
Header: intl.get('credit'),
|
||||
accessor: 'formatted_credit',
|
||||
className: 'credit',
|
||||
align: Align.Right,
|
||||
},
|
||||
{
|
||||
Header: intl.get('debit'),
|
||||
accessor: 'formatted_debit',
|
||||
className: 'debit',
|
||||
align: Align.Right,
|
||||
},
|
||||
],
|
||||
[],
|
||||
|
||||
12
src/containers/FinancialStatements/Journal/utils.js
Normal file
12
src/containers/FinancialStatements/Journal/utils.js
Normal 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',
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -5,6 +5,10 @@ import { transformFilterFormToQuery } from '../common';
|
||||
|
||||
const ProfitLossSheetContext = createContext();
|
||||
|
||||
/**
|
||||
* Profit/loss sheet provider.
|
||||
* @returns {React.JSX}
|
||||
*/
|
||||
function ProfitLossSheetProvider({ query, ...props }) {
|
||||
const {
|
||||
data: profitLossSheet,
|
||||
|
||||
@@ -1,57 +1,47 @@
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { compose } from 'utils';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import ProfitLossSheetHeader from './ProfitLossSheetHeader';
|
||||
import ProfitLossSheetTable from './ProfitLossSheetTable';
|
||||
import ProfitLossActionsBar from './ProfitLossActionsBar';
|
||||
|
||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withProfitLossActions from './withProfitLossActions';
|
||||
import withCurrentOrganization from '../../Organization/withCurrentOrganization';
|
||||
|
||||
import 'style/pages/FinancialStatements/ProfitLossSheet.scss';
|
||||
import { useProfitLossSheetQuery } from './utils';
|
||||
import { ProfitLossSheetProvider } from './ProfitLossProvider';
|
||||
import { ProfitLossSheetLoadingBar, ProfitLossSheetAlerts } from './components';
|
||||
import { ProfitLossSheetLoadingBar } from './components';
|
||||
import { ProfitLossBody } from './ProfitLossBody';
|
||||
|
||||
/**
|
||||
* Profit/Loss financial statement sheet.
|
||||
* @returns {React.JSX}
|
||||
*/
|
||||
function ProfitLossSheet({
|
||||
// #withPreferences
|
||||
organizationName,
|
||||
|
||||
// #withProfitLossActions
|
||||
toggleProfitLossFilterDrawer: toggleDisplayFilterDrawer,
|
||||
}) {
|
||||
const [filter, setFilter] = useState({
|
||||
basis: 'cash',
|
||||
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
||||
toDate: moment().endOf('year').format('YYYY-MM-DD'),
|
||||
displayColumnsType: 'total',
|
||||
filterByOption: 'with-transactions',
|
||||
});
|
||||
// Profit/loss sheet query.
|
||||
const { query, setLocationQuery } = useProfitLossSheetQuery();
|
||||
|
||||
// Handle submit filter.
|
||||
const handleSubmitFilter = (filter) => {
|
||||
const _filter = {
|
||||
const newFilter = {
|
||||
...filter,
|
||||
fromDate: moment(filter.fromDate).format('YYYY-MM-DD'),
|
||||
toDate: moment(filter.toDate).format('YYYY-MM-DD'),
|
||||
};
|
||||
setFilter(_filter);
|
||||
setLocationQuery(newFilter);
|
||||
};
|
||||
|
||||
// Handle number format submit.
|
||||
const handleNumberFormatSubmit = (numberFormat) => {
|
||||
setFilter({
|
||||
...filter,
|
||||
setLocationQuery({
|
||||
...query,
|
||||
numberFormat,
|
||||
});
|
||||
};
|
||||
|
||||
// Hide the filter drawer once the page unmount.
|
||||
React.useEffect(
|
||||
() => () => {
|
||||
@@ -61,34 +51,26 @@ function ProfitLossSheet({
|
||||
);
|
||||
|
||||
return (
|
||||
<ProfitLossSheetProvider query={filter}>
|
||||
<ProfitLossSheetProvider query={query}>
|
||||
<ProfitLossActionsBar
|
||||
numberFormat={filter.numberFormat}
|
||||
numberFormat={query.numberFormat}
|
||||
onNumberFormatSubmit={handleNumberFormatSubmit}
|
||||
/>
|
||||
<ProfitLossSheetLoadingBar />
|
||||
<ProfitLossSheetAlerts />
|
||||
{/* <ProfitLossSheetAlerts /> */}
|
||||
|
||||
<DashboardPageContent>
|
||||
<div class="financial-statement">
|
||||
<ProfitLossSheetHeader
|
||||
pageFilter={filter}
|
||||
onSubmitFilter={handleSubmitFilter}
|
||||
/>
|
||||
|
||||
<div class="financial-statement__body">
|
||||
<ProfitLossSheetTable companyName={organizationName} />
|
||||
</div>
|
||||
</div>
|
||||
<ProfitLossSheetHeader
|
||||
pageFilter={query}
|
||||
onSubmitFilter={handleSubmitFilter}
|
||||
/>
|
||||
<ProfitLossBody />
|
||||
</DashboardPageContent>
|
||||
</ProfitLossSheetProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
export default R.compose(
|
||||
withDashboardActions,
|
||||
withProfitLossActions,
|
||||
withCurrentOrganization(({ organization }) => ({
|
||||
organizationName: organization.name,
|
||||
})),
|
||||
)(ProfitLossSheet);
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { Formik, Form } from 'formik';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import intl from 'react-intl-universal';
|
||||
import * as Yup from 'yup';
|
||||
import * as R from 'ramda';
|
||||
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 ProfitLossSheetHeaderGeneralPane from './ProfitLossSheetHeaderGeneralPane';
|
||||
import ProfitLossSheetHeaderComparisonPanel from './ProfitLossSheetHeaderComparisonPanel';
|
||||
|
||||
import withProfitLoss from './withProfitLoss';
|
||||
import withProfitLossActions from './withProfitLossActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
import { useProfitLossHeaderValidationSchema } from './utils';
|
||||
|
||||
/**
|
||||
* Profit/loss header.
|
||||
* @returns {React.JSX}
|
||||
*/
|
||||
function ProfitLossHeader({
|
||||
// #ownProps
|
||||
pageFilter,
|
||||
@@ -26,15 +31,7 @@ function ProfitLossHeader({
|
||||
toggleProfitLossFilterDrawer: toggleFilterDrawer,
|
||||
}) {
|
||||
// Validation schema.
|
||||
const validationSchema = Yup.object().shape({
|
||||
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(),
|
||||
});
|
||||
const validationSchema = useProfitLossHeaderValidationSchema();
|
||||
|
||||
// Initial values.
|
||||
const initialValues = {
|
||||
@@ -42,13 +39,11 @@ function ProfitLossHeader({
|
||||
fromDate: moment(pageFilter.fromDate).toDate(),
|
||||
toDate: moment(pageFilter.toDate).toDate(),
|
||||
};
|
||||
|
||||
// Handle form submit.
|
||||
const handleSubmit = (values, actions) => {
|
||||
onSubmitFilter(values);
|
||||
toggleFilterDrawer(false);
|
||||
};
|
||||
|
||||
// Handles the cancel button click.
|
||||
const handleCancelClick = () => {
|
||||
toggleFilterDrawer(false);
|
||||
@@ -59,7 +54,7 @@ function ProfitLossHeader({
|
||||
};
|
||||
|
||||
return (
|
||||
<FinancialStatementHeader
|
||||
<ProfitLossSheetHeader
|
||||
isOpen={profitLossDrawerFilter}
|
||||
drawerProps={{ onClose: handleDrawerClose }}
|
||||
>
|
||||
@@ -75,6 +70,11 @@ function ProfitLossHeader({
|
||||
title={<T id={'general'} />}
|
||||
panel={<ProfitLossSheetHeaderGeneralPane />}
|
||||
/>
|
||||
<Tab
|
||||
id="comparison"
|
||||
title={<T id={'profit_loss_sheet.comparisons'} />}
|
||||
panel={<ProfitLossSheetHeaderComparisonPanel />}
|
||||
/>
|
||||
</Tabs>
|
||||
|
||||
<div class="financial-header-drawer__footer">
|
||||
@@ -87,13 +87,19 @@ function ProfitLossHeader({
|
||||
</div>
|
||||
</Form>
|
||||
</Formik>
|
||||
</FinancialStatementHeader>
|
||||
</ProfitLossSheetHeader>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
export default R.compose(
|
||||
withProfitLoss(({ profitLossDrawerFilter }) => ({
|
||||
profitLossDrawerFilter,
|
||||
})),
|
||||
withProfitLossActions,
|
||||
)(ProfitLossHeader);
|
||||
|
||||
const ProfitLossSheetHeader = styled(FinancialStatementHeader)`
|
||||
.bp3-drawer {
|
||||
max-height: 520px;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
`;
|
||||
@@ -1,109 +1,80 @@
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import intl from 'react-intl-universal';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import FinancialSheet from 'components/FinancialSheet';
|
||||
import DataTable from 'components/DataTable';
|
||||
import { CellTextSpan } from 'components/Datatable/Cells';
|
||||
import { TableStyle } from 'common';
|
||||
import {
|
||||
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';
|
||||
|
||||
export default function ProfitLossSheetTable({
|
||||
// #ownProps
|
||||
companyName,
|
||||
}) {
|
||||
|
||||
|
||||
// Profit/Loss sheet context.
|
||||
const {
|
||||
profitLossSheet: { tableRows, query, columns },
|
||||
isLoading
|
||||
profitLossSheet: { table, query },
|
||||
} = useProfitLossSheetContext();
|
||||
|
||||
const tableColumns = useMemo(
|
||||
() => [
|
||||
{
|
||||
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,
|
||||
],
|
||||
);
|
||||
// Retrieves the profit/loss table columns.
|
||||
const tableColumns = useProfitLossSheetColumns();
|
||||
|
||||
// Retrieve default expanded rows of balance sheet.
|
||||
const expandedRows = useMemo(
|
||||
() => defaultExpanderReducer(tableRows, 3),
|
||||
[tableRows],
|
||||
const expandedRows = React.useMemo(
|
||||
() => defaultExpanderReducer(table?.rows || [], 3),
|
||||
[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 (
|
||||
<FinancialSheet
|
||||
companyName={companyName}
|
||||
sheetType={<T id={'profit_loss_sheet'} />}
|
||||
fromDate={query.from_date}
|
||||
toDate={query.to_date}
|
||||
name="profit-loss-sheet"
|
||||
loading={isLoading}
|
||||
basis={query.basis}
|
||||
>
|
||||
<DataTable
|
||||
className="bigcapital-datatable--financial-report"
|
||||
<ProfitLossDataTable
|
||||
columns={tableColumns}
|
||||
data={tableRows}
|
||||
data={table.rows}
|
||||
noInitialFetch={true}
|
||||
expanded={expandedRows}
|
||||
rowClassNames={rowClassNames}
|
||||
rowClassNames={tableRowTypesToClassnames}
|
||||
expandable={true}
|
||||
expandToggleColumn={1}
|
||||
|
||||
sticky={true}
|
||||
styleName={TableStyle.Constrant}
|
||||
/>
|
||||
</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;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
19
src/containers/FinancialStatements/ProfitLossSheet/hooks.js
Normal file
19
src/containers/FinancialStatements/ProfitLossSheet/hooks.js
Normal 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
Reference in New Issue
Block a user