Compare commits

...

99 Commits

Author SHA1 Message Date
a.bouhuolia
68c0678dc3 Merge branch 'develop' into main 2022-02-17 12:10:20 +02:00
a.bouhuolia
030be9652c feat: add BS and PL reports to page of financial reports list. 2022-02-17 11:40:40 +02:00
a.bouhuolia
554527f17d fix(VendorTransaction): column accessor/id. 2022-02-16 18:47:36 +02:00
a.bouhuolia
79144ad4a5 fix: re-structure the system tables reports. 2022-02-16 18:44:10 +02:00
a.bouhuolia
e6fcbfeea6 fix: control report drawer header. 2022-02-16 17:49:28 +02:00
a.bouhuolia
7eacaa0660 fix: financial report data tables. 2022-02-14 14:09:17 +02:00
a.bouhuolia
673808cceb feat: add sticky table head to specific item transactions table. 2022-02-13 17:22:29 +02:00
a.bouhuolia
f27ef2c9b0 fix: style of vendor/customer balance summary. 2022-02-13 17:17:09 +02:00
a.bouhuolia
2986b537d0 fix: financial reports. 2022-02-13 13:21:59 +02:00
a.bouhuolia
b1f07d281f fix: remove un-used stylesheet files. 2022-02-12 20:41:40 +02:00
a.bouhuolia
1b0ffb5574 refactor(CustomerTransaction). 2022-02-12 20:31:14 +02:00
a.bouhuolia
b249335a73 refactor(Cashflow)
refactor(InventoryValuation)
2022-02-12 18:59:34 +02:00
a.bouhuolia
4cc0a8c41e refactor(APAgingSummary)
refactor(ARAgingSummary)
2022-02-12 18:49:48 +02:00
a.bouhuolia
2e7061260e refactor((CustomerTransaction).
refactor(VendorTransaction).
refactor(VendorBalanceSummary).
refactor(CustomerBalanceSummary)
2022-02-12 18:12:08 +02:00
a.bouhuolia
46570c5218 refactor(InventoryValuation).
refactor(InventoryItemDetails).
2022-02-12 17:03:15 +02:00
a.bouhuolia
72a7c4890e refactor(SaleByItem) 2022-02-12 16:41:07 +02:00
a.bouhuolia
a9a877f4fc refactor(PurchaseByItem) 2022-02-12 16:26:36 +02:00
a.bouhuolia
cc42c21f24 refactor(JournalSheet) 2022-02-12 16:07:26 +02:00
a.bouhuolia
2f0322b4fc refactor(GeneralLedger) 2022-02-12 13:17:38 +02:00
a.bouhuolia
b9418d3eb6 refactor(TrialBalanceSheet). 2022-02-12 12:53:23 +02:00
a.bouhuolia
526181aa68 refactor: re-structure financial reports components. 2022-02-12 12:21:05 +02:00
a.bouhuolia
d445fec8c0 refactor(CashflowSheet): refactor the body sheet. 2022-02-10 11:45:27 +02:00
a.bouhuolia
1f81fd213d refactor(TrialBalanceSheet): refactor with body sheet. 2022-02-10 11:44:50 +02:00
a.bouhuolia
83cd7ca893 Merge branch 'feature/comparisons' into develop 2022-02-09 21:51:18 +02:00
a.bouhuolia
d22143c97e Revert "feat(BS|PL): sticky columns in RTL mode."
This reverts commit 200a59d6da.
2022-02-09 21:32:36 +02:00
a.bouhuolia
200a59d6da feat(BS|PL): sticky columns in RTL mode. 2022-02-09 21:26:33 +02:00
a.bouhuolia
8b4d841023 feat(BS|PL): integrate report query with location query. 2022-02-09 21:12:32 +02:00
a.bouhuolia
c361a5852c fix(BS|PL): report query. 2022-02-09 19:50:49 +02:00
a.bouhuolia
b759d7327e feat(App): Horjar code on production envirement only. 2022-02-09 17:11:55 +02:00
elforjani13
93df479c05 feat(PL & PL): add localizing. 2022-02-08 16:54:57 +02:00
a.bouhuolia
d300231838 fix(DataTable): sticky column. 2022-02-05 11:31:57 +02:00
a.bouhuolia
47f6845633 feat(BS&PL): add sticky to account column. 2022-02-03 17:03:24 +02:00
a.bouhuolia
f204b81407 fix(BS|PL): date periods columns alignment. 2022-02-03 16:05:24 +02:00
a.bouhuolia
2c2740ea73 fix(BalanceSheet): highlight total assets and libaiities. 2022-02-02 13:33:54 +02:00
a.bouhuolia
c72802d683 feat(FinancialSheet): add skeleton view. 2022-02-02 12:08:57 +02:00
a.bouhuolia
b4f6d2c7f1 Merge branch 'feature/comparisons' of https://github.com/bigcapitalhq/client into feature/comparisons 2022-02-01 16:59:51 +02:00
a.bouhuolia
4456343eb6 Merge https://github.com/bigcapitalhq/client into feature/comparisons 2022-02-01 16:53:53 +02:00
elforjani13
a3d250cdc8 fix(Balance & P/L Sheet): fix comparison panel. 2022-02-01 00:29:50 +02:00
a.bouhuolia
ce223e7e2f feat(BalanceSheet|P&L): account name min-width. 2022-01-31 01:04:23 +02:00
a.bouhuolia
8d6ed9be41 fix: balance sheet. 2022-01-30 16:05:59 +02:00
a.bouhuolia
e296507a96 chore: remove dubugger point. 2022-01-30 16:01:59 +02:00
a.bouhuolia
3d78d2d051 feat: integrate balance sheet and P&L sheet with new API. 2022-01-30 15:57:27 +02:00
a.bouhuolia
fa455152a3 feat(ProfitLoss): WIP 2022-01-29 21:57:39 +02:00
a.bouhuolia
34501a9a61 fix(BalanceSheet): dynamic columns width. 2022-01-29 21:35:31 +02:00
a.bouhuolia
066df28257 fix(BalanceSheet): columns width based in cells contents. 2022-01-29 21:32:20 +02:00
a.bouhuolia
77d826e6d4 feat(BalanceSheet|ProfitLoss): comparions feature. 2022-01-29 20:46:41 +02:00
elforjani13
735803f1a5 feat(balancesheet): balance sheet comparisons. 2022-01-25 20:12:13 +02:00
elforjani13
354d1e8f75 feat(P&L Comparisons): add P&L comparisons. 2022-01-24 13:55:44 +02:00
elforjani13
225619be60 fix(balanceSheet) balance sheet header. 2022-01-13 16:33:32 +02:00
elforjani13
85a78b3809 feat: style balance sheet comparison. 2022-01-13 16:25:46 +02:00
a.bouhuolia
da699a766a Merge branch 'develop' into main 2022-01-13 15:40:55 +02:00
a.bouhuolia
a958c6088a chore: dump version. 2022-01-13 15:35:40 +02:00
a.bouhuolia
572d59577c chore: dump version changlogs. 2022-01-13 15:34:54 +02:00
elforjani13
7a0d506395 feat: balance sheet comparison 2022-01-13 15:25:04 +02:00
Ahmed Bouhuolia
21ae8aabfc Merge pull request #22 from bigcapitalhq/BIG-261-hide-convert-to-invoice-button-on-the-context-menu-if-already-converted
fix: `BIG-261` Hide convert to invoice button on the context menu if invoice already converted.
2022-01-13 15:18:49 +02:00
a.bouhuolia
8f267b98e4 fix(ContactBalanceSummary): BIG-288 percentage of column. 2022-01-12 20:15:00 +02:00
a.bouhuolia
dcfe92076b Merge branch 'develop' of https://github.com/bigcapitalhq/client into develop 2022-01-11 18:10:23 +02:00
a.bouhuolia
79cb7f1a1d fix(PaymentReceive): depend on loading state instead fetching. 2022-01-11 18:10:17 +02:00
a.bouhuolia
1380e288e0 fix(BalanceSheet): BIG-281 report alerts re-positioning. 2022-01-11 18:09:12 +02:00
Ahmed Bouhuolia
b055908e12 Merge pull request #19 from bigcapitalhq/BIG-274-duplicated-description-in-payment-receive-details
`BIG-274` Duplicated description in payment receive details.
2022-01-11 17:29:19 +02:00
Ahmed Bouhuolia
20c140474b Merge pull request #20 from bigcapitalhq/BIG-277-separate-customer-and-vendor-activate-inactivate-alerts
BIG-277 Separate customer and vendor.
2022-01-11 17:28:47 +02:00
Ahmed Bouhuolia
9ee5eba92b Merge branch 'develop' into BIG-277-separate-customer-and-vendor-activate-inactivate-alerts 2022-01-11 17:28:30 +02:00
Ahmed Bouhuolia
e755a2a318 Merge pull request #21 from bigcapitalhq/BIG-263-optimize-sales-print-templates
`Big 263` optimize sales print templates
2022-01-11 17:25:44 +02:00
elforjani13
1656691940 feat(PaymentReceivePdf): payment pdf preview. 2022-01-11 14:34:41 +02:00
elforjani13
dd7d11ffb7 feat(CreditNotePdf): credit note pdf preview. 2022-01-11 14:33:51 +02:00
elforjani13
5c847be420 BIG-277 Separate customer and vendor. 2022-01-09 21:18:45 +02:00
a.bouhuolia
da3193195c fix: BIG-274 Duplicated description in payment receive details. 2022-01-09 21:12:54 +02:00
a.bouhuolia
e3141250b6 fix: BIG-261 Hide convert to invoice button on the context menu if already converted. 2022-01-09 13:07:32 +02:00
a.bouhuolia
f1899e1ce1 Merge branch 'develop' into main 2022-01-08 18:20:08 +02:00
elforjani13
76d6cd0eaa fix(MoneyOutForm): BIG-270 publish & draft cashflow. 2022-01-08 18:14:39 +02:00
elforjani13
90b4f86a0d fix(MoneyInForm): BIG-270 publish & draft cashflow. 2022-01-08 18:14:17 +02:00
Ahmed Bouhuolia
5766d25bd1 Merge pull request #18 from bigcapitalhq/BIG-271-hotjar-client-application-integration
`BIG-271` Hotjar client application integration.
2022-01-08 18:06:54 +02:00
elforjani13
6a5d96e869 BIG-271 Hotjar client application integration. 2022-01-08 17:59:30 +02:00
Ahmed Bouhuolia
69f16d1977 Merge pull request #17 from bigcapitalhq/BIG-270-publish-and-draft-cashflow-transaction-should-be-always-publish
BIG-270 Publish and draft cashflow transaction.
2022-01-08 17:02:54 +02:00
elforjani13
9973693a86 BIG-270 Publish and draft cashflow transaction. 2022-01-08 16:59:31 +02:00
Ahmed Bouhuolia
11851d114d Merge pull request #16 from bigcapitalhq/develop
Release 1.5.5
2022-01-04 22:35:49 +02:00
elforjani13
3a3dd7a565 fix(VendorForm): placeholder phone number. 2022-01-04 22:31:01 +02:00
elforjani13
687dda1e7c fix(inviteUserFrom): fix localiztion. 2022-01-04 21:31:58 +02:00
elforjani13
bfa809c831 feat(VendorList): add note accessor & personal phone. 2022-01-04 20:47:43 +02:00
elforjani13
07145e92ab feat(CustomerList): add note accessor & personal phone. 2022-01-04 20:47:28 +02:00
a.bouhuolia
21779007be fix: application version. 2022-01-03 23:14:11 +02:00
a.bouhuolia
4fc1ecdc2d Merge branch 'main' of https://github.com/bigcapitalhq/client into main 2022-01-03 19:42:48 +02:00
a.bouhuolia
c9b5cecf7a Merge branch 'develop' into main 2022-01-03 19:42:23 +02:00
a.bouhuolia
4a46c00a07 chore: bump version. 2022-01-03 19:42:05 +02:00
a.bouhuolia
5c7ac0593d feat(GlobalErrors): localize the global errors. 2022-01-03 18:31:52 +02:00
a.bouhuolia
fa25fb4ede fix(TrialBalanceSheet): expand account name column. 2022-01-03 18:02:24 +02:00
elforjani13
c31e9dcd29 fix: inventory adjustment & contacts drawer. 2022-01-03 14:35:50 +02:00
a.bouhuolia
3566d3b855 Merge branch 'develop' of https://github.com/bigcapitalhq/client into develop 2022-01-03 13:53:40 +02:00
a.bouhuolia
c5da97bce0 chore: changlog. 2022-01-03 13:53:04 +02:00
a.bouhuolia
0cfbe633bd feat: add .env.example file. 2022-01-03 13:53:02 +02:00
a.bouhuolia
55098d7b78 chore: remove repo logo. 2022-01-03 13:53:00 +02:00
a.bouhuolia
78610cd519 chore: upload repo logo. 2022-01-03 13:52:59 +02:00
a.bouhuolia
828a28b976 chore: update package-lock.json. 2022-01-03 13:52:58 +02:00
a.bouhuolia
daf5fc8aba feat: add version numebr on sidebar footer. 2022-01-03 13:49:55 +02:00
elforjani13
430ab95dc3 landed cost localiztion. 2022-01-03 13:38:22 +02:00
Ahmed Bouhuolia
8100a57195 Merge pull request #15 from bigcapitalhq/develop
Merge `develop` to `main`
2022-01-03 12:31:14 +02:00
a.bouhuolia
97d890bcef chore: remove repo logo. 2022-01-03 12:30:15 +02:00
a.bouhuolia
521df8511d chore: upload repo logo. 2022-01-03 12:24:28 +02:00
a.bouhuolia
73ec49b36a chore: update package-lock.json. 2022-01-03 12:06:07 +02:00
202 changed files with 5851 additions and 2290 deletions

1
.env.example Normal file
View File

@@ -0,0 +1 @@
APP_VERSION=$npm_package_version

1
.gitignore vendored
View File

@@ -13,6 +13,7 @@
# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local

View File

@@ -2,8 +2,31 @@
All notable changes to Bigcapital server-side will be in this file.
## [1.5.0] - 20-12-2021
## [1.5.8] - 13-01-2022
### Added
- Add payment receive PDF print.
- Add credit note PDF print.
### Fixed
- fix: Payment receive initial loading state depends on request loading state instead fetching.
- fix: Balance sheet report alert positioning.
- fix: Separate customer and vendor inactivate and activate alerts.
- fix: Hide convert to invoice button if the invoice is already converted.
- fix: Customer and vendor balance summary percentage of column option.
- fix: Remove duplicated details in sales and purchases details drawers.
## [1.5.3] - 03-01-2020
### Fixed
- Localize the global errors.
- Expand account name column on trial balance sheet.
## [1.5.0] - 20-12-2021
### Added
- Add credit note on sales module.
- Add vendor credit on purchases module.
- Optimize landed costs on purchase invoices.
@@ -15,23 +38,29 @@ All notable changes to Bigcapital server-side will be in this file.
- Optimize readonly details style of invoice, receipt, estimate, payment receive,
purchase invoice, expense, manual journal, inventory adjustment and cashflow transaction.
### Changed
### Changed
- Dashboard meta boot and authenticated user request query.
- Optimize Arabic localization.
## [1.4.0] - 11-09-2021
### Added
- Add SMS notification on sale invoice, receipt, customers payments modules.
- Customer quick create in customers list.
- Item quick create in items list.
### Changes
change: BIG-171 alerts in global scope and lazy loading.
change: BIG-171 alerts in global scope and lazy loading.
### Fixed
fix: BIG-140 - Reordering sell, cost and inventory account on item details.
fix: BIG-144 - Typo adjustment dialog success message.
fix: BIG-148 - Items entries ordered by index.
fix: BIG-132 AR/AP aging summary report filter by none transactions/zero contacts.
fix: BIG-140 - Reordering sell, cost and inventory account on item details.
fix: BIG-144 - Typo adjustment dialog success message.
fix: BIG-148 - Items entries ordered by index.
fix: BIG-132 AR/AP aging summary report filter by none transactions/zero contacts.
## [1.2.0-RC] - 03-09-2021
@@ -39,6 +68,7 @@ Here we write upgrading notes for brands. It's a team effort to make them as
straightforward as possible.
### Added
- Add slidable sub-sidebar to improve user experience instead of sub-menu.
- Add Subscription guard to ensure the organization's subscription is active or
redirect all routes to subscription billing page.
@@ -62,14 +92,16 @@ straightforward as possible.
- Inventory adjustment publish action.
- Customers and vendors activate and inactivate action.
- Add refresh button on dashboard actions bar to all datatables resources.
- Add clickable datatable rows to display each row details.
- Add clickable datatable rows to display each row details.
### Changed
- Optimize style of datatable selection checkbox.
- Disable animation in dashboard views tabs.
- Optimize Arabic localization.
### Fixed
- fix: disable submit buttons in pereferences pages.
- fix: inventory adjustment cost field max/min range to avoid out of range error.
- fix: transactions by customers/vendors report localization.

View File

@@ -85,6 +85,9 @@ function getClientEnvironment(publicUrl) {
WDS_SOCKET_HOST: process.env.WDS_SOCKET_HOST,
WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH,
WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT,
// Application version.
VERSION: paths.appVersion
}
);
// Stringify all values so we can feed into webpack DefinePlugin

View File

@@ -48,6 +48,8 @@ const resolveModule = (resolveFn, filePath) => {
return resolveFn(`${filePath}.js`);
};
const appVersion = require(resolveApp('package.json')).version;
// config after eject: we're in ./config/
module.exports = {
dotenv: resolveApp('.env'),
@@ -65,6 +67,7 @@ module.exports = {
proxySetup: resolveApp('src/setupProxy.js'),
appNodeModules: resolveApp('node_modules'),
publicUrlOrPath,
appVersion
};

779
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "bigcapital-client",
"version": "1.2.0",
"version": "1.5.8",
"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",

View File

@@ -2,7 +2,11 @@
<html dir="ltr" lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicons/favicon-32.ico" sizes="32x32">
<link
rel="icon"
href="%PUBLIC_URL%/favicons/favicon-32.ico"
sizes="32x32"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
@@ -15,6 +19,25 @@
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) {
h.hj =
h.hj ||
function () {
(h.hj.q = h.hj.q || []).push(arguments);
};
h._hjSettings = { hjid: 2774528, hjsv: 6 };
a = o.getElementsByTagName('head')[0];
r = o.createElement('script');
r.async = 1;
r.src = t + h._hjSettings.hjid + j + h._hjSettings.hjsv;
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.
@@ -41,7 +64,11 @@
To create a production bundle, use `npm run build` or `yarn build`.
-->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/flexboxgrid/6.3.1/flexboxgrid.min.css" type="text/css" >
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/flexboxgrid/6.3.1/flexboxgrid.min.css"
type="text/css"
/>
<!-- <link href="https://cdn.syncfusion.com/ej2/material.css" rel="stylesheet"> -->
<!-- <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" /> -->
</body>

View File

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

View File

@@ -21,5 +21,5 @@ export const CommercialDocEntriesTable = styled(DataTable)`
`;
export const CommercialDocFooter = styled.div`
margin-top: 25px;
margin-top: 28px;
`;

View File

@@ -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>
);

View File

@@ -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} />

View File

@@ -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} />

View File

@@ -32,6 +32,8 @@ import ReconcileVendorCreditDialog from '../containers/Dialogs/ReconcileVendorCr
import LockingTransactionsDialog from '../containers/Dialogs/LockingTransactionsDialog';
import UnlockingTransactionsDialog from '../containers/Dialogs/UnlockingTransactionsDialog';
import UnlockingPartialTransactionsDialog from '../containers/Dialogs/UnlockingPartialTransactionsDialog';
import CreditNotePdfPreviewDialog from '../containers/Dialogs/CreditNotePdfPreviewDialog';
import PaymentReceivePdfPreviewDialog from '../containers/Dialogs/PaymentReceivePdfPreviewDialog';
/**
* Dialogs container.
@@ -74,6 +76,8 @@ export default function DialogsContainer() {
<UnlockingPartialTransactionsDialog
dialogName={'unlocking-partial-transactions'}
/>
<CreditNotePdfPreviewDialog dialogName={'credit-note-pdf-preview'} />
<PaymentReceivePdfPreviewDialog dialogName={'payment-pdf-preview'} />
</div>
);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,15 +1,24 @@
import React from 'react';
import 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} />
);
}

View File

@@ -1,4 +1,5 @@
import React from 'react';
import SidebarContainer from 'components/Sidebar/SidebarContainer';
import SidebarHead from 'components/Sidebar/SidebarHead';
import SidebarMenu from 'components/Sidebar/SidebarMenu';
@@ -17,7 +18,20 @@ export default function Sidebar({ dashboardContentRef }) {
<SidebarMenu menu={menu} />
</div>
<div class="sidebar__version">0.0.1-beta version.</div>
<SidebarFooterVersion />
</SidebarContainer>
);
}
/**
* Sidebar footer version.
* @returns {React.JSX}
*/
function SidebarFooterVersion() {
const { VERSION } = process.env;
if (!VERSION) {
return null;
}
return <div class="sidebar__version">v{VERSION}</div>;
}

View File

@@ -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>;
}

View File

View File

@@ -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,

View File

@@ -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,
},
],
},
];

View File

@@ -57,9 +57,7 @@ function BillTransactionDeleteAlert({
loading={isLoading}
>
<p>
<T
id={`Once your delete this located landed cost, you won't be able to restore it later, Are your sure you want to delete this transaction?`}
/>
<T id={`landed_cost.once_your_delete_this_located_landed_cost`} />
</p>
</Alert>
);

View File

@@ -0,0 +1,67 @@
import React from 'react';
import { FormattedMessage as T } from 'components';
import intl from 'react-intl-universal';
import { Intent, Alert } from '@blueprintjs/core';
import { AppToaster } from 'components';
import { useActivateContact } from 'hooks/query';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import { compose } from 'utils';
/**
* Customer activate alert.
*/
function CustomerActivateAlert({
name,
// #withAlertStoreConnect
isOpen,
payload: { customerId, service },
// #withAlertActions
closeAlert,
}) {
const { mutateAsync: activateContact, isLoading } = useActivateContact();
// Handle activate constomer alert cancel.
const handleCancelActivateCustomer = () => {
closeAlert(name);
};
// Handle confirm customer activated.
const handleConfirmCustomerActivate = () => {
activateContact(customerId)
.then(() => {
AppToaster.show({
message: intl.get('customer.alert.activated_message'),
intent: Intent.SUCCESS,
});
})
.catch((error) => {})
.finally(() => {
closeAlert(name);
});
};
return (
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'activate'} />}
intent={Intent.WARNING}
isOpen={isOpen}
onCancel={handleCancelActivateCustomer}
loading={isLoading}
onConfirm={handleConfirmCustomerActivate}
>
<p>{intl.get('customer.alert.are_you_sure_want_to_activate_this_customer')}</p>
</Alert>
);
}
export default compose(
withAlertStoreConnect(),
withAlertActions,
)(CustomerActivateAlert);

View File

@@ -0,0 +1,69 @@
import React from 'react';
import { FormattedMessage as T } from 'components';
import intl from 'react-intl-universal';
import { Intent, Alert } from '@blueprintjs/core';
import { AppToaster } from 'components';
import { useInactivateContact } from 'hooks/query';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import { compose } from 'utils';
/**
* customer inactivate alert.
*/
function CustomerInactivateAlert({
name,
// #withAlertStoreConnect
isOpen,
payload: { customerId, service },
// #withAlertActions
closeAlert,
}) {
const { mutateAsync: inactivateContact, isLoading } = useInactivateContact();
// Handle cancel inactivate alert.
const handleCancelInactivateCustomer = () => {
closeAlert(name);
};
// Handle confirm contact Inactive.
const handleConfirmCustomerInactive = () => {
inactivateContact(customerId)
.then(() => {
AppToaster.show({
message: intl.get('the_contact_has_been_inactivated_successfully'),
intent: Intent.SUCCESS,
});
})
.catch((error) => {})
.finally(() => {
closeAlert(name);
});
};
return (
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'inactivate'} />}
intent={Intent.WARNING}
isOpen={isOpen}
onCancel={handleCancelInactivateCustomer}
onConfirm={handleConfirmCustomerInactive}
loading={isLoading}
>
<p>
{intl.get(
'customer.alert.are_you_sure_want_to_inactivate_this_customer',
)}
</p>
</Alert>
);
}
export default compose(
withAlertStoreConnect(),
withAlertActions,
)(CustomerInactivateAlert);

View File

@@ -0,0 +1,69 @@
import React from 'react';
import { FormattedMessage as T } from 'components';
import intl from 'react-intl-universal';
import { Intent, Alert } from '@blueprintjs/core';
import { AppToaster } from 'components';
import { useActivateContact } from 'hooks/query';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import { compose } from 'utils';
/**
* Vendor activate alert.
*/
function VendorActivateAlert({
name,
// #withAlertStoreConnect
isOpen,
payload: { vendorId },
// #withAlertActions
closeAlert,
}) {
const { mutateAsync: activateContact, isLoading } = useActivateContact();
// Handle activate vendor alert cancel.
const handleCancelActivateVendor = () => {
closeAlert(name);
};
// Handle confirm vendor activated.
const handleConfirmVendorActivate = () => {
activateContact(vendorId)
.then(() => {
AppToaster.show({
message: intl.get('vendor.alert.activated_message'),
intent: Intent.SUCCESS,
});
})
.catch((error) => {})
.finally(() => {
closeAlert(name);
});
};
return (
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'activate'} />}
intent={Intent.WARNING}
isOpen={isOpen}
onCancel={handleCancelActivateVendor}
loading={isLoading}
onConfirm={handleConfirmVendorActivate}
>
<p>
{intl.get('vendor.alert.are_you_sure_want_to_activate_this_vendor')}
</p>
</Alert>
);
}
export default compose(
withAlertStoreConnect(),
withAlertActions,
)(VendorActivateAlert);

View File

@@ -0,0 +1,68 @@
import React from 'react';
import { FormattedMessage as T } from 'components';
import intl from 'react-intl-universal';
import { Intent, Alert } from '@blueprintjs/core';
import { AppToaster } from 'components';
import { useInactivateContact } from 'hooks/query';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import { compose } from 'utils';
/**
* Vendor inactivate alert.
*/
function VendorInactivateAlert({
name,
// #withAlertStoreConnect
isOpen,
payload: { vendorId },
// #withAlertActions
closeAlert,
}) {
const { mutateAsync: inactivateContact, isLoading } = useInactivateContact();
// Handle cancel inactivate alert.
const handleCancelInactivateVendor = () => {
closeAlert(name);
};
// Handle confirm contact Inactive.
const handleConfirmVendorInactive = () => {
inactivateContact(vendorId)
.then(() => {
AppToaster.show({
message: intl.get('vendor.alert.inactivated_message'),
intent: Intent.SUCCESS,
});
})
.catch((error) => {})
.finally(() => {
closeAlert(name);
});
};
return (
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'inactivate'} />}
intent={Intent.WARNING}
isOpen={isOpen}
onCancel={handleCancelInactivateVendor}
onConfirm={handleConfirmVendorInactive}
loading={isLoading}
>
<p>
{intl.get('vendor.alert.are_you_sure_want_to_inactivate_this_vendor')}
</p>
</Alert>
);
}
export default compose(
withAlertStoreConnect(),
withAlertActions,
)(VendorInactivateAlert);

View File

@@ -79,6 +79,10 @@ export const handleCashFlowTransactionType = (reference, openDrawer) => {
return openDrawer('refund-vendor-detail-drawer', {
refundTransactionId: reference.reference_id,
});
case 'InventoryAdjustment':
return openDrawer('inventory-adjustment-drawer', {
inventoryId: reference.reference_id,
});
default:
return openDrawer('cashflow-transaction-drawer', {

View File

@@ -3,11 +3,11 @@ import React from 'react';
const CustomerDeleteAlert = React.lazy(() =>
import('../Alerts/Customers/CustomerDeleteAlert'),
);
const ContactActivateAlert = React.lazy(() =>
import('../Alerts/Contacts/ContactActivateAlert'),
const CustomerActivateAlert = React.lazy(() =>
import('../Alerts/Customers/CustomerActivateAlert'),
);
const ContactInactivateAlert = React.lazy(() =>
import('../Alerts/Contacts/ContactInactivateAlert'),
const CustomerInactivateAlert = React.lazy(() =>
import('../Alerts/Customers/CustomerInactivateAlert'),
);
/**
@@ -15,6 +15,6 @@ const ContactInactivateAlert = React.lazy(() =>
*/
export default [
{ name: 'customer-delete', component: CustomerDeleteAlert },
{ name: 'contact-activate', component: ContactActivateAlert },
{ name: 'contact-inactivate', component: ContactInactivateAlert },
{ name: 'customer-activate', component: CustomerActivateAlert },
{ name: 'customer-inactivate', component: CustomerInactivateAlert },
];

View File

@@ -89,15 +89,17 @@ function CustomersTable({
// Handle cancel/confirm inactive.
const handleInactiveCustomer = ({ id, contact_service }) => {
openAlert('contact-inactivate', {
contactId: id,
service: contact_service,
openAlert('customer-inactivate', {
customerId: id,
});
};
// Handle cancel/confirm activate.
const handleActivateCustomer = ({ id, contact_service }) => {
openAlert('contact-activate', { contactId: id, service: contact_service });
openAlert('customer-activate', {
customerId: id,
service: contact_service,
});
};
// Handle view detail contact.

View File

@@ -1,5 +1,13 @@
import React, { useMemo } from 'react';
import { Menu, MenuItem, MenuDivider, Intent } from '@blueprintjs/core';
import {
Menu,
MenuItem,
MenuDivider,
Intent,
Tooltip,
Position,
Classes,
} from '@blueprintjs/core';
import clsx from 'classnames';
import intl from 'react-intl-universal';
@@ -79,7 +87,7 @@ export function ActionsMenu({
* Phone number accessor.
*/
export function PhoneNumberAccessor(row) {
return <div className={'work_phone'}>{row.work_phone}</div>;
return <div className={'work_phone'}>{row.personal_phone}</div>;
}
/**
@@ -89,6 +97,24 @@ export function BalanceAccessor(row) {
return <Money amount={row.closing_balance} currency={row.currency_code} />;
}
/**
* Note column accessor.
*/
export function NoteAccessor(row) {
return (
<If condition={row.note}>
<Tooltip
className={Classes.TOOLTIP_INDICATOR}
content={row.note}
position={Position.LEFT_TOP}
hoverOpenDelay={50}
>
<Icon icon={'file-alt'} iconSize={16} />
</Tooltip>
</If>
);
}
/**
* Retrieve customers table columns.
*/
@@ -123,12 +149,20 @@ export function useCustomersTableColumns() {
},
{
id: 'work_phone',
Header: intl.get('work_phone'),
Header: intl.get('phone_number'),
accessor: PhoneNumberAccessor,
className: 'phone_number',
width: 100,
clickable: true,
},
{
id: 'note',
Header: intl.get('note'),
accessor: NoteAccessor,
disableSortBy: true,
width: 85,
clickable: true,
},
{
id: 'balance',
Header: intl.get('receivable_balance'),

View File

@@ -37,7 +37,7 @@ function AllocateLandedCostFloatingActions({
<DialogFooterActions alignment={'left'}>
{costTransactionEntry && (
<UnallocatedAmount>
Unallocated cost Amount:{' '}
<T id={'landed_cost.dialog.label_unallocated_cost_amount'}/>
<strong>{formattedUnallocatedCostAmount}</strong>
</UnallocatedAmount>
)}

View File

@@ -42,7 +42,10 @@ function AllocateLandedCostForm({
.map((entry) => transformToForm(entry, defaultInitialValues.items[0]));
if (entries.length <= 0) {
AppToaster.show({ message: 'Something wrong!', intent: Intent.DANGER });
AppToaster.show({
message: intl.get('something_wrong'),
intent: Intent.DANGER,
});
return;
}
const form = {
@@ -69,13 +72,14 @@ function AllocateLandedCostForm({
)
) {
AppToaster.show({
message:
'The total located cost is bigger than the transaction line.',
message: intl.get(
'landed_cost.error.the_total_located_cost_is_bigger_than_the_transaction_line',
),
intent: Intent.DANGER,
});
} else {
AppToaster.show({
message: 'Something went wrong!',
message: intl.get('something_went_wrong'),
intent: Intent.DANGER,
});
}

View File

@@ -0,0 +1,47 @@
import React from 'react';
import { AnchorButton } from '@blueprintjs/core';
import { DialogContent, PdfDocumentPreview, T } from 'components';
import { usePdfCreditNote } from 'hooks/query';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
function CreditNotePdfPreviewDialogContent({
subscriptionForm: { creditNoteId },
}) {
const { isLoading, pdfUrl } = usePdfCreditNote(creditNoteId);
return (
<DialogContent>
<div class="dialog__header-actions">
<AnchorButton
href={pdfUrl}
target={'__blank'}
minimal={true}
outlined={true}
>
<T id={'pdf_preview.preview.button'} />
</AnchorButton>
<AnchorButton
href={pdfUrl}
download={'creditNote.pdf'}
minimal={true}
outlined={true}
>
<T id={'pdf_preview.download.button'} />
</AnchorButton>
</div>
<PdfDocumentPreview
height={760}
width={1000}
isLoading={isLoading}
url={pdfUrl}
/>
</DialogContent>
);
}
export default compose(withDialogActions)(CreditNotePdfPreviewDialogContent);

View File

@@ -0,0 +1,42 @@
import React from 'react';
import classNames from 'classnames';
import { T, Dialog, DialogSuspense } from 'components';
import withDialogRedux from 'components/DialogReduxConnect';
import { CLASSES } from 'common/classes';
import { compose } from 'utils';
const PdfPreviewDialogContent = React.lazy(() =>
import('./CreditNotePdfPreviewDialogContent'),
);
/**
* Credit note PDF previwe dialog.
*/
function CreditNotePdfPreviewDialog({
dialogName,
payload = { creditNoteId: null },
isOpen,
}) {
return (
<Dialog
name={dialogName}
title={<T id={'credit_note_preview.dialog.title'} />}
className={classNames(CLASSES.DIALOG_PDF_PREVIEW)}
autoFocus={true}
canEscapeKeyClose={true}
isOpen={isOpen}
style={{ width: '1000px' }}
>
<DialogSuspense>
<PdfPreviewDialogContent
dialogName={dialogName}
subscriptionForm={payload}
/>
</DialogSuspense>
</Dialog>
);
}
export default compose(withDialogRedux())(CreditNotePdfPreviewDialog);

View File

@@ -29,7 +29,7 @@ const defaultInitialValues = {
cashflow_account_id: '',
credit_account_id: '',
description: '',
published: '',
publish: '',
};
function MoneyInForm({
@@ -73,7 +73,7 @@ function MoneyInForm({
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
const form = {
...omit(values, ['currency_code']),
published: submitPayload.publish,
publish: true,
};
setSubmitting(true);
createCashflowTransactionMutate(form)

View File

@@ -14,7 +14,7 @@ const Schema = Yup.object().shape({
.min(3)
.max(DATATYPES_LENGTH.TEXT)
.label(intl.get('description')),
published: Yup.boolean(),
publish: Yup.boolean(),
});
export const CreateMoneyInFormSchema = Schema;

View File

@@ -29,7 +29,7 @@ const defaultInitialValues = {
cashflow_account_id: '',
credit_account_id: '',
description: '',
published: '',
publish: '',
};
function MoneyOutForm({
@@ -73,7 +73,7 @@ function MoneyOutForm({
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
const form = {
...omit(values, ['currency_code']),
published: submitPayload.publish,
publish: true,
};
setSubmitting(true);
createCashflowTransactionMutate(form)

View File

@@ -14,7 +14,7 @@ const Schema = Yup.object().shape({
.min(3)
.max(DATATYPES_LENGTH.TEXT)
.label(intl.get('description')),
published: Yup.boolean(),
publish: Yup.boolean(),
});
export const CreateMoneyOutSchema = Schema;

View File

@@ -0,0 +1,49 @@
import React from 'react';
import { AnchorButton } from '@blueprintjs/core';
import { DialogContent, PdfDocumentPreview, T } from 'components';
import { usePdfPaymentReceive } from 'hooks/query';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
function PaymentReceivePdfPreviewDialogContent({
subscriptionForm: { paymentReceiveId },
}) {
const { isLoading, pdfUrl } = usePdfPaymentReceive(paymentReceiveId);
return (
<DialogContent>
<div class="dialog__header-actions">
<AnchorButton
href={pdfUrl}
target={'__blank'}
minimal={true}
outlined={true}
>
<T id={'pdf_preview.preview.button'} />
</AnchorButton>
<AnchorButton
href={pdfUrl}
download={'payment.pdf'}
minimal={true}
outlined={true}
>
<T id={'pdf_preview.download.button'} />
</AnchorButton>
</div>
<PdfDocumentPreview
height={760}
width={1000}
isLoading={isLoading}
url={pdfUrl}
/>
</DialogContent>
);
}
export default compose(withDialogActions)(
PaymentReceivePdfPreviewDialogContent,
);

View File

@@ -0,0 +1,44 @@
import React from 'react';
import classNames from 'classnames';
import { T, Dialog, DialogSuspense } from 'components';
import withDialogRedux from 'components/DialogReduxConnect';
import { CLASSES } from 'common/classes';
import { compose } from 'utils';
// Lazy loading the content.
const PdfPreviewDialogContent = React.lazy(() =>
import('./PaymentReceivePdfPreviewContent'),
);
/**
* Payment receive PDF preview dialog.
*/
function PaymentReceivePdfPreviewDialog({
dialogName,
payload = { paymentReceiveId: null },
isOpen,
}) {
return (
<Dialog
name={dialogName}
title={<T id={'payment_receive_preview.dialog.title'} />}
className={classNames(CLASSES.DIALOG_PDF_PREVIEW)}
autoFocus={true}
canEscapeKeyClose={true}
isOpen={isOpen}
style={{ width: '1000px' }}
>
<DialogSuspense>
<PdfPreviewDialogContent
dialogName={dialogName}
subscriptionForm={payload}
/>
</DialogSuspense>
</Dialog>
);
}
export default compose(withDialogRedux())(PaymentReceivePdfPreviewDialog);

View File

@@ -65,6 +65,11 @@ function CreditNoteDetailActionsBar({
openDialog('reconcile-credit-note', { creditNoteId });
};
// Handle print credit note.
const handlePrintCreditNote = () => {
openDialog('credit-note-pdf-preview', { creditNoteId });
};
return (
<DrawerActionsBar>
<NavbarGroup>
@@ -88,6 +93,14 @@ function CreditNoteDetailActionsBar({
<NavbarDivider />
</If>
</Can>
<Can I={CreditNoteAction.View} a={AbilitySubject.CreditNote}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="print-16" />}
text={<T id={'print'} />}
onClick={handlePrintCreditNote}
/>
</Can>
<Can I={CreditNoteAction.Delete} a={AbilitySubject.CreditNote}>
<Button
className={Classes.MINIMAL}

View File

@@ -15,9 +15,14 @@ import { useCreditNoteDetailDrawerContext } from './CreditNoteDetailDrawerProvid
*/
export default function CreditNoteDetailFooter() {
const { creditNote } = useCreditNoteDetailDrawerContext();
return (
<CommercialDocFooter>
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
<If condition={creditNote.terms_conditions}>
<DetailItem label={<T id={'note'} />} children={creditNote.note} />
</If>
<If condition={creditNote.terms_conditions}>
<DetailItem label={<T id={'terms_conditions'} />}>
{creditNote.terms_conditions}

View File

@@ -42,6 +42,12 @@ export default function CreditNoteDetailHeader() {
<Row>
<Col xs={6}>
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
<DetailItem
label={intl.get('credit_note.drawer.label_credit_note_date')}
>
<FormatDate value={creditNote.formatted_credit_note_date} />
</DetailItem>
<DetailItem
label={intl.get('credit_note.drawer.label_credit_note_no')}
>
@@ -53,12 +59,6 @@ export default function CreditNoteDetailHeader() {
{creditNote.customer?.display_name}
</CustomerDrawerLink>
</DetailItem>
<DetailItem
label={intl.get('credit_note.drawer.label_credit_note_date')}
>
<FormatDate value={creditNote.formatted_credit_note_date} />
</DetailItem>
</DetailsMenu>
</Col>
@@ -77,11 +77,6 @@ export default function CreditNoteDetailHeader() {
label={intl.get('reference')}
children={defaultTo(creditNote.reference_no, '-')}
/>
<DetailItem
label={intl.get('note')}
children={defaultTo(creditNote.note, '-')}
/>
<DetailItem
label={<T id={'credit_note.drawer.label_created_at'} />}
children={<FormatDate value={creditNote.created_at} />}

View File

@@ -12,7 +12,6 @@ import {
DetailItem,
Row,
Col,
ButtonLink,
CustomerDrawerLink,
} from 'components';
import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider';

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,42 +1,29 @@
import React from 'react';
import styled from 'styled-components';
import {
T,
TotalLines,
TotalLine,
TotalLineBorderStyle,
TotalLineTextStyle,
CommercialDocFooter,
DetailsMenu,
If,
DetailItem,
} from 'components';
import { usePaymentMadeDetailContext } from './PaymentMadeDetailProvider';
/**
* Payment made - Details panel - Footer.
*/
export default function PaymentMadeDetailFooter() {
export function PaymentMadeDetailFooter() {
const { paymentMade } = usePaymentMadeDetailContext();
return (
<PaymentMadeFooterRoot>
<PaymentMadeTotalLines labelColWidth={'180px'} amountColWidth={'180px'}>
<TotalLine
title={<T id={'payment_made.details.subtotal'} />}
value={paymentMade.amount}
borderStyle={TotalLineBorderStyle.SingleDark}
/>
<TotalLine
title={<T id={'payment_made.details.total'} />}
value={paymentMade.formatted_amount}
borderStyle={TotalLineBorderStyle.DoubleDark}
textStyle={TotalLineTextStyle.Bold}
/>
</PaymentMadeTotalLines>
</PaymentMadeFooterRoot>
<CommercialDocFooter>
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
<If condition={paymentMade.statement}>
<DetailItem label={<T id={'payment_made.details.statement'} />}>
{paymentMade.statement}
</DetailItem>
</If>
</DetailsMenu>
</CommercialDocFooter>
);
}
export const PaymentMadeFooterRoot = styled.div``;
export const PaymentMadeTotalLines = styled(TotalLines)`
margin-left: auto;
`;

View File

@@ -34,6 +34,10 @@ export default function PaymentMadeDetailHeader() {
<Row>
<Col xs={6}>
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
<DetailItem
label={intl.get('payment_date')}
children={<FormatDate value={paymentMade.payment_date} />}
/>
<DetailItem
label={intl.get('payment_made.details.payment_number')}
children={defaultTo(paymentMade.payment_number, '-')}
@@ -47,11 +51,6 @@ export default function PaymentMadeDetailHeader() {
label={intl.get('payment_account')}
children={paymentMade.payment_account?.name}
/>
<DetailItem
label={intl.get('payment_date')}
children={<FormatDate value={paymentMade.payment_date} />}
/>
</DetailsMenu>
</Col>
<Col xs={6}>
@@ -61,8 +60,8 @@ export default function PaymentMadeDetailHeader() {
minLabelSize={'180px'}
>
<DetailItem
label={intl.get('description')}
children={defaultTo(paymentMade.statement, '-')}
label={intl.get('reference')}
children={defaultTo(paymentMade.reference, '-')}
/>
<DetailItem
label={intl.get('created_at')}

View File

@@ -20,8 +20,7 @@ function PaymentMadeDetailProvider({ paymentMadeId, ...props }) {
enabled: !!paymentMadeId,
},
);
//provider.
// Provider state.
const provider = {
paymentMadeId,
paymentMade,

View File

@@ -4,7 +4,8 @@ import { CommercialDocBox } from 'components';
import PaymentMadeDetailHeader from './PaymentMadeDetailHeader';
import PaymentMadeDetailTable from './PaymentMadeDetailTable';
import PaymentMadeDetailFooter from './PaymentMadeDetailFooter';
import PaymentMadeDetailTableFooter from './PaymentMadeDetailTableFooter';
import { PaymentMadeDetailFooter } from './PaymentMadeDetailFooter';
/**
* Payment made detail tab.
@@ -15,6 +16,7 @@ export default function PaymentMadeDetailTab() {
<CommercialDocBox>
<PaymentMadeDetailHeader />
<PaymentMadeDetailTable />
<PaymentMadeDetailTableFooter />
<PaymentMadeDetailFooter />
</CommercialDocBox>
);

View File

@@ -0,0 +1,42 @@
import React from 'react';
import styled from 'styled-components';
import {
T,
TotalLines,
TotalLine,
TotalLineBorderStyle,
TotalLineTextStyle,
} from 'components';
import { usePaymentMadeDetailContext } from './PaymentMadeDetailProvider';
/**
* Payment made - Details panel - Footer.
*/
export default function PaymentMadeDetailTableFooter() {
const { paymentMade } = usePaymentMadeDetailContext();
return (
<PaymentMadeFooterRoot>
<PaymentMadeTotalLines labelColWidth={'180px'} amountColWidth={'180px'}>
<TotalLine
title={<T id={'payment_made.details.subtotal'} />}
value={paymentMade.amount}
borderStyle={TotalLineBorderStyle.SingleDark}
/>
<TotalLine
title={<T id={'payment_made.details.total'} />}
value={paymentMade.formatted_amount}
borderStyle={TotalLineBorderStyle.DoubleDark}
textStyle={TotalLineTextStyle.Bold}
/>
</PaymentMadeTotalLines>
</PaymentMadeFooterRoot>
);
}
export const PaymentMadeFooterRoot = styled.div``;
export const PaymentMadeTotalLines = styled(TotalLines)`
margin-left: auto;
`;

View File

@@ -62,6 +62,11 @@ function PaymentReceiveActionsBar({
openDialog('notify-payment-via-sms', { paymentReceiveId });
};
// Handle print payment receive.
const handlePrintPaymentReceive = () => {
openDialog('payment-pdf-preview', { paymentReceiveId });
};
return (
<DrawerActionsBar>
<NavbarGroup>
@@ -74,6 +79,14 @@ function PaymentReceiveActionsBar({
/>
<NavbarDivider />
</Can>
<Can I={PaymentReceiveAction.View} a={AbilitySubject.PaymentReceive}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="print-16" />}
text={<T id={'print'} />}
onClick={handlePrintPaymentReceive}
/>
</Can>
<Can I={PaymentReceiveAction.Delete} a={AbilitySubject.PaymentReceive}>
<Button
className={Classes.MINIMAL}

View File

@@ -10,7 +10,6 @@ import {
DetailItem,
CommercialDocHeader,
CommercialDocTopHeader,
ButtonLink,
CustomerDrawerLink,
} from 'components';
import { usePaymentReceiveDetailContext } from './PaymentReceiveDetailProvider';
@@ -34,6 +33,10 @@ export default function PaymentReceiveDetailHeader() {
<Row>
<Col xs={6}>
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
<DetailItem
label={intl.get('payment_date')}
children={<FormatDate value={paymentReceive.payment_date} />}
/>
<DetailItem
label={intl.get('payment_receive.details.payment_number')}
children={defaultTo(paymentReceive.payment_receive_no, '-')}
@@ -48,10 +51,6 @@ export default function PaymentReceiveDetailHeader() {
label={intl.get('deposit_account')}
children={paymentReceive.deposit_account?.name}
/>
<DetailItem
label={intl.get('payment_date')}
children={<FormatDate value={paymentReceive.payment_date} />}
/>
</DetailsMenu>
</Col>
@@ -62,8 +61,8 @@ export default function PaymentReceiveDetailHeader() {
minLabelSize={'180px'}
>
<DetailItem
label={intl.get('description')}
children={defaultTo(paymentReceive.statement, '')}
label={intl.get('reference')}
children={defaultTo(paymentReceive.reference_no, '-')}
/>
<DetailItem
label={intl.get('created_at')}

View File

@@ -0,0 +1,23 @@
import React from 'react';
import {
CommercialDocFooter,
T,
If,
DetailsMenu,
DetailItem,
} from '../../../components';
import { useVendorCreditDetailDrawerContext } from './VendorCreditDetailDrawerProvider';
export function VendorCreditDetailFooter() {
const { vendorCredit } = useVendorCreditDetailDrawerContext();
return (
<CommercialDocFooter>
<DetailsMenu direction={'horizantal'} minLabelSize={'150px'}>
<If condition={vendorCredit.note}>
<DetailItem label={<T id={'note'} />} children={vendorCredit.note} />
</If>
</DetailsMenu>
</CommercialDocFooter>
);
}

View File

@@ -38,6 +38,11 @@ export default function VendorCreditDetailHeader() {
<Row>
<Col xs={6}>
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
<DetailItem
label={intl.get('vendor_credit.drawer.label_vendor_credit_date')}
>
<FormatDate value={vendorCredit.formatted_vendor_credit_date} />
</DetailItem>
<DetailItem
label={intl.get('vendor_credit.drawer.label_vendor_credit_no')}
>
@@ -49,12 +54,6 @@ export default function VendorCreditDetailHeader() {
{vendorCredit.vendor?.display_name}
</VendorDrawerLink>
</DetailItem>
<DetailItem
label={intl.get('vendor_credit.drawer.label_vendor_credit_date')}
>
<FormatDate value={vendorCredit.formatted_vendor_credit_date} />
</DetailItem>
</DetailsMenu>
</Col>
<Col xs={6}>
@@ -72,10 +71,6 @@ export default function VendorCreditDetailHeader() {
label={intl.get('reference')}
children={defaultTo(vendorCredit.reference_no, '-')}
/>
<DetailItem
label={intl.get('note')}
children={defaultTo(vendorCredit.note, '-')}
/>
<DetailItem
label={<T id={'vendor_credit.drawer.label_created_at'} />}
children={<FormatDate value={vendorCredit.created_at} />}

View File

@@ -5,6 +5,7 @@ import { CommercialDocBox } from 'components';
import VendorCreditDetailHeader from './VendorCreditDetailHeader';
import VendorCreditDetailTable from './VendorCreditDetailTable';
import VendorCreditDetailDrawerFooter from './VendorCreditDetailDrawerFooter';
import { VendorCreditDetailFooter } from './VendorCreditDetailFooter';
/**
* Vendor credit details panel.
@@ -16,6 +17,7 @@ export default function VendorCreditDetailPanel() {
<VendorCreditDetailHeader />
<VendorCreditDetailTable />
<VendorCreditDetailDrawerFooter />
<VendorCreditDetailFooter />
</CommercialDocBox>
);
}

View File

@@ -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);

View File

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

View File

@@ -4,6 +4,7 @@ import { Formik, Form } from 'formik';
import * as Yup from 'yup';
import 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;
}
`;

View File

@@ -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;
}
}
}
`;

View File

@@ -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',
}
}

View File

@@ -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);

View File

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

View File

@@ -4,6 +4,7 @@ import { Formik, Form } from 'formik';
import * as Yup from 'yup';
import 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;
}
`;

View File

@@ -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;
}
}
}
}
}
`;

View File

@@ -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',
};
};

View File

@@ -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],

View File

@@ -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,33 +51,25 @@ function BalanceSheet({
);
return (
<BalanceSheetProvider filter={filter}>
<BalanceSheetProvider filter={query}>
<BalanceSheetActionsBar
numberFormat={filter.numberFormat}
numberFormat={query.numberFormat}
onNumberFormatSubmit={handleNumberFormatSubmit}
/>
<BalanceSheetLoadingBar />
<BalanceSheetAlerts />
<DashboardPageContent>
<FinancialStatement>
<BalanceSheetHeader
pageFilter={filter}
pageFilter={query}
onSubmitFilter={handleFilterSubmit}
/>
<div class="financial-statement__body">
<BalanceSheetTable companyName={organizationName} />
</div>
<BalanceSheetBody />
</FinancialStatement>
</DashboardPageContent>
<BalanceSheetAlerts />
</BalanceSheetProvider>
);
}
export default compose(
withCurrentOrganization(({ organization }) => ({
organizationName: organization.name,
})),
withBalanceSheetActions,
)(BalanceSheet);
export default compose(withBalanceSheetActions)(BalanceSheet);

View File

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

View File

@@ -1,19 +1,22 @@
import React from 'react';
import { 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;
}
`;

View File

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

View File

@@ -1,4 +1,5 @@
import React, { createContext, useContext } from 'react';
import 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,

View File

@@ -1,13 +1,15 @@
import React, { useMemo, useCallback } from 'react';
import React from 'react';
import styled from 'styled-components';
import intl from 'react-intl-universal';
import 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;
}
}
}
}
`;

View File

@@ -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],
);
};

View File

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

View File

@@ -1,7 +1,63 @@
import React from 'react';
import * as R from 'ramda';
import moment from 'moment';
import * as Yup from 'yup';
import 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);
},
);

View File

@@ -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({
@@ -70,24 +61,18 @@ function CashFlowStatement({
/>
<CashFlowStatementLoadingBar />
<CashFlowStatementAlerts />
<DashboardPageContent>
<FinancialStatement>
<CashFlowStatementHeader
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);

View File

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

View File

@@ -12,6 +12,7 @@ import CashFlowStatementGeneralPanel from './CashFlowStatementGeneralPanel';
import withCashFlowStatement from './withCashFlowStatement';
import 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({

View File

@@ -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;
}
}
}
}
}
`;

View File

@@ -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">

View File

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

View File

@@ -1,74 +1,14 @@
import * as R from 'ramda';
import { 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);
};

View File

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

View File

@@ -1,37 +1,30 @@
import React, { useEffect, useState } from 'react';
import 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,
);

View File

@@ -52,7 +52,7 @@ export default function CustomersBalanceSummaryGeneralPanelContent() {
<Row>
<Col xs={5}>
<FastField name={'percentage'} type={'checkbox'}>
<FastField name={'percentage_column'} type={'checkbox'}>
{({ field }) => (
<FormGroup labelInfo={<FieldHint />}>
<Checkbox

View File

@@ -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;
}
`;

View File

@@ -1,48 +1,65 @@
import React, { useMemo, useCallback } from 'react';
import React from 'react';
import intl from 'react-intl-universal';
import classNames from 'classnames';
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: { tableRows },
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={tableRows}
rowClassNames={rowClassNames}
data={table.data}
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;
}
}
}
}
}
`;

View File

@@ -1,37 +1,69 @@
import React from 'react';
import intl from 'react-intl-universal';
import * as R from 'ramda';
import { If } from 'components';
import FinancialLoadingBar from '../FinancialLoadingBar';
import { useCustomersBalanceSummaryContext } from './CustomersBalanceSummaryProvider';
import { Align } from 'common';
/**
* Retrieve customers balance summary columns.
*/
export const useCustomersSummaryColumns = () => {
return React.useMemo(
() => [
{
Header: intl.get('customer_name'),
accessor: 'cells[0].value',
className: 'customer_name',
width: 240,
},
{
Header: intl.get('total'),
accessor: 'cells[1].value',
className: 'total',
width: 140,
},
{
Header: intl.get('percentage_of_column'),
accessor: 'cells[2].value',
className: 'total',
width: 140,
},
],
[],
);
const {
CustomerBalanceSummary: { table },
} = useCustomersBalanceSummaryContext();
return React.useMemo(() => {
return dynamicColumns(table.columns || []);
}, [table.columns]);
};
/**
* Account name column accessor.
*/
const accountNameColumnAccessor = () => ({
Header: intl.get('customer_name'),
accessor: 'cells[0].value',
className: 'customer_name',
width: 240,
});
/**
* Total column accessor.
*/
const totalColumnAccessor = () => ({
Header: intl.get('total'),
accessor: 'cells[1].value',
className: 'total',
width: 140,
align: Align.Right,
});
/**
* Percentage column accessor.
*/
const percentageColumnAccessor = () => ({
Header: intl.get('percentage_of_column'),
accessor: 'cells[2].value',
className: 'total',
width: 140,
align: Align.Right,
});
const dynamicColumns = (columns) => {
return R.map(
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,
),
),
)(columns);
};
/**

View File

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

View File

@@ -1,16 +1,14 @@
import React, { useEffect, useState } from 'react';
import 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);

View File

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

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