diff --git a/client/src/common/homepageOptions.js b/client/src/common/homepageOptions.js new file mode 100644 index 000000000..8eb280ce9 --- /dev/null +++ b/client/src/common/homepageOptions.js @@ -0,0 +1,56 @@ +export const shortcutBox = [ + { + title: 'Request time Off', + iconColor: '#F3FEFA', + description: + 'Cupidatat nulla minim sit duis duis laboris. Sint exercitation.', + }, + { + title: 'Benefits', + iconColor: '#F5F3FE', + description: + 'Cupidatat nulla minim sit duis duis laboris. Sint exercitation.', + }, + { + title: 'Schedule a one-on-one', + iconColor: '#F2F9FF', + description: + 'Cupidatat nulla minim sit duis duis laboris. Sint exercitation.', + }, + { + title: 'Payroll', + iconColor: '#FFFCED', + description: + 'Cupidatat nulla minim sit duis duis laboris. Sint exercitation.', + }, + { + title: 'Submit an expense', + iconColor: '#FDF1F1', + description: + 'Cupidatat nulla minim sit duis duis laboris. Sint exercitation.', + }, + { + title: 'Training', + iconColor: '#EFF1FE', + description: + 'Cupidatat nulla minim sit duis duis laboris. Sint exercitation.', + }, +]; + +export const announcementLists = [ + { + title: 'Office closed on July 2nd', + description: + 'Incididunt Lorem ad sunt proident nulla exercitation consectetur reprehenderit labore qui.', + }, + { + title: 'New Password policy ', + description: + 'Incididunt Lorem ad sunt proident nulla exercitation consectetur reprehenderit labore qui.', + }, + { + title: 'Office closed on July 2nd', + description: + 'Incididunt Lorem ad sunt proident nulla exercitation consectetur reprehenderit labore qui.', + }, +]; diff --git a/client/src/components/Dashboard/Dashboard.js b/client/src/components/Dashboard/Dashboard.js index c00bb07d4..9e116d70e 100644 --- a/client/src/components/Dashboard/Dashboard.js +++ b/client/src/components/Dashboard/Dashboard.js @@ -14,6 +14,7 @@ import Search from 'containers/GeneralSearch/Search'; import DashboardSplitPane from 'components/Dashboard/DashboardSplitePane'; import GlobalHotkeys from './GlobalHotkeys'; import withSettingsActions from 'containers/Settings/withSettingsActions'; +import DrawersContainer from 'components/DrawersContainer'; import { compose } from 'utils'; @@ -47,6 +48,7 @@ function Dashboard({ + ); } diff --git a/client/src/components/DrawersContainer.js b/client/src/components/DrawersContainer.js new file mode 100644 index 000000000..f7f326347 --- /dev/null +++ b/client/src/components/DrawersContainer.js @@ -0,0 +1,16 @@ +import React from 'react'; +import EstimateDrawer from 'containers/Sales/Estimate/EstimateDrawer'; +import InvoiceDrawer from 'containers/Sales/Invoice/InvoiceDrawer'; +import ReceiptDrawer from 'containers/Sales/Receipt/ReceiptDrawer'; +import PaymentReceive from 'containers/Sales/PaymentReceive/PaymentReceiveDrawer'; + +export default function DrawersContainer() { + return ( +
+ + + + +
+ ); +} diff --git a/client/src/config/sidebarMenu.js b/client/src/config/sidebarMenu.js index 4f4d90032..7e4ca62aa 100644 --- a/client/src/config/sidebarMenu.js +++ b/client/src/config/sidebarMenu.js @@ -1,4 +1,4 @@ - import React from 'react'; +import React from 'react'; import { FormattedMessage as T } from 'react-intl'; export default [ @@ -161,11 +161,11 @@ export default [ href: '/financial-reports/profit-loss-sheet', }, { - text: 'A/R Aging Summary', + text: , href: '/financial-reports/receivable-aging-summary', }, { - text: 'A/P Aging Summary', + text: , href: '/financial-reports/payable-aging-summary', }, ], diff --git a/client/src/containers/Drawer/withDrawerActions.js b/client/src/containers/Drawer/withDrawerActions.js new file mode 100644 index 000000000..5842f3390 --- /dev/null +++ b/client/src/containers/Drawer/withDrawerActions.js @@ -0,0 +1,15 @@ +import { connect } from 'react-redux'; +import t from 'store/types'; + +export const mapStateToProps = (state, props) => { + return {}; +}; + +export const mapDispatchToProps = (dispatch) => ({ + openDrawer: (name, payload) => + dispatch({ type: t.OPEN_DRAWER, name, payload }), + closeDrawer: (name, payload) => + dispatch({ type: t.CLOSE_DRAWER, name, payload }), +}); + +export default connect(null, mapDispatchToProps); diff --git a/client/src/containers/Drawer/withDrawers.js b/client/src/containers/Drawer/withDrawers.js new file mode 100644 index 000000000..ee074bdc9 --- /dev/null +++ b/client/src/containers/Drawer/withDrawers.js @@ -0,0 +1,19 @@ +import { connect } from 'react-redux'; +import { + isDrawerOpenFactory, + getDrawerPayloadFactory, +} from 'store/dashboard/dashboard.selectors'; + +export default (mapState) => { + const isDrawerOpen = isDrawerOpenFactory(); + const getDrawerPayload = getDrawerPayloadFactory(); + + const mapStateToProps = (state, props) => { + const mapped = { + isOpen: isDrawerOpen(state, props), + payload: getDrawerPayload(state, props), + }; + return mapState ? mapState(mapped) : mapped; + }; + return connect(mapStateToProps); +}; diff --git a/client/src/containers/Drawers/DrawerTemplate.js b/client/src/containers/Drawers/DrawerTemplate.js new file mode 100644 index 000000000..2f9799009 --- /dev/null +++ b/client/src/containers/Drawers/DrawerTemplate.js @@ -0,0 +1,29 @@ +import React from 'react'; +import { FormattedMessage as T, useIntl } from 'react-intl'; +import { Position, Drawer } from '@blueprintjs/core'; + +export default function DrawerTemplate({ + children, + isOpen, + isClose, + drawerProps, +}) { + return ( +
+ } + position={Position.RIGHT} + canOutsideClickClose={true} + canEscapeKeyClose={true} + size={'65%'} + onClose={isClose} + {...drawerProps} + > + {children} + +
+ ); +} diff --git a/client/src/containers/Drawers/PaperTemplate.js b/client/src/containers/Drawers/PaperTemplate.js new file mode 100644 index 000000000..037ccd4d3 --- /dev/null +++ b/client/src/containers/Drawers/PaperTemplate.js @@ -0,0 +1,123 @@ +import React from 'react'; +import { Icon } from 'components'; +import 'style/components/Drawer/DrawerTemplate.scss'; + +export default function PaperTemplate({ labels: propLabels }) { + const labels = { + name: 'Estimate', + billedTo: 'Billed to', + date: 'Estimate date', + refNo: 'Estimate No.', + billedFrom: 'Billed from', + amount: 'Estimate amount', + dueDate: 'Due date', + ...propLabels, + }; + + return ( +
+
+
+
+

{labels.name}

+

info@bigcapital.ly

+
+ +
+ +
+
+ {labels.billedTo} +

Joe Biden

+
+
+ {labels.date} + +

1/1/2022

+
+
+ {labels.refNo} +

IN-2022

+
+
+ {labels.amount} +

6,000 LYD

+
+
+ {labels.billedFrom} +

Donald Trump

+
+
+ {labels.dueDate} +

25/03/2022

+
+
+ +
+
+ Description + Rate + Qty + Total +
+
+ + Nulla commodo magnanon dolor excepteur nisi aute laborum. + + 1 + 1 + 100 LYD +
+ +
+ + Nulla comm non dolor excepteur elit dolore eiusmod nisi aute + laborum. + + 1 + 1 + 100 LYD +
+
+ + Nulla comm non dolor excepteur elit dolore eiusmod nisi aute + laborum. + + 1 + 1 + 100 LYD +
+
+ + Nulla comm non dolor excepteur elit dolore eiusmod nisi aute + laborum. + + 1 + 1 + 100 LYD +
+
+ + Nulla comm non dolor excepteur elit dolore eiusmod nisi aute + laborum. + + 1 + 1 + 100 LYD +
+
+ +
+
+

Conditions and terms

+
+
    +
  • Est excepteur laboris do sit dolore sit exercitation non.
  • +
  • Lorem duis aliqua minim elit cillum.
  • +
  • Dolor ad quis Lorem ut mollit consectetur.
  • +
+
+
+
+ ); +} diff --git a/client/src/containers/Drawers/PaymentPaperTemplate.js b/client/src/containers/Drawers/PaymentPaperTemplate.js new file mode 100644 index 000000000..c5eb4a3e5 --- /dev/null +++ b/client/src/containers/Drawers/PaymentPaperTemplate.js @@ -0,0 +1,102 @@ +import React from 'react'; +import { Icon } from 'components'; +import 'style/components/Drawer/DrawerTemplate.scss'; + +export default function PaymentPaperTemplate({ labels: propLabels }) { + const labels = { + title: 'Payment receive', + billedTo: 'Billed to', + paymentDate: 'Payment date', + paymentNo: 'Payment No.', + billedFrom: 'Billed from', + referenceNo: 'Reference No', + amountReceived: 'Amount received', + ...propLabels, + }; + + return ( +
+
+
+
+

{labels.title}

+

info@bigcapital.ly

+
+ +
+ +
+
+ {labels.billedTo} +

Step Currency

+
+
+ {labels.paymentDate} +

1/1/2022

+
+
+ {labels.paymentNo} +

IN-2022

+
+
+ {labels.amountReceived} +

60,000 USD

+
+
+ {labels.billedFrom} +

Klay Thompson

+
+
+ {labels.referenceNo} +

+
+
+
+
+ + Invoice number + + + Invoice date + + + Invoice amount + + + Payment amount + +
+ +
+ + INV-1 + + + 12 Jan 2021 + + + 50,000 USD + + + 1000 USD + +
+
+ + INV-2{' '} + + + 12 Jan 2021 + + + 50,000 USD + + + 1000 USD + +
+
+
+
+ ); +} diff --git a/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummary.js b/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummary.js new file mode 100644 index 000000000..79b6e7025 --- /dev/null +++ b/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummary.js @@ -0,0 +1,128 @@ +import React, { useEffect, useState, useCallback } from 'react'; +import { useIntl } from 'react-intl'; +import { queryCache, useQuery } from 'react-query'; +import moment from 'moment'; + +import { FinancialStatement } from 'components'; + +import DashboardInsider from 'components/Dashboard/DashboardInsider'; +import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; + +import APAgingSummaryActionsBar from './APAgingSummaryActionsBar'; +import APAgingSummaryHeader from './APAgingSummaryHeader'; +import APAgingSummaryTable from './APAgingSummaryTable'; + +import withSettings from 'containers/Settings/withSettings'; +import withDashboardActions from 'containers/Dashboard/withDashboardActions'; +import withAPAgingSummaryActions from './withAPAgingSummaryActions'; +import withAPAgingSummary from './withAPAgingSummary'; +import { transformFilterFormToQuery } from './common'; + +import { compose } from 'utils'; + +import 'style/pages/FinancialStatements/ARAgingSummary.scss'; + +/** + * AP aging summary report. + */ +function APAgingSummary({ + // #withSettings + organizationName, + + // #withDashboardActions + changePageTitle, + setDashboardBackLink, + + // #withAPAgingSummary + APAgingSummaryRefresh, + + // #withAPAgingSummaryActions + requestPayableAgingSummary, + refreshAPAgingSummary, + toggleFilterAPAgingSummary, +}) { + const { formatMessage } = useIntl(); + + const [query, setQuery] = useState({ + asDate: moment().endOf('day').format('YYYY-MM-DD'), + agingBeforeDays: 30, + agingPeriods: 3, + }); + + // handle fetching payable aging summary report. + const fetchAPAgingSummarySheet = useQuery( + ['payable-aging-summary', query], + (key, _query) => + requestPayableAgingSummary({ + ...transformFilterFormToQuery(_query), + }), + { enable: true }, + ); + + useEffect(() => { + changePageTitle(formatMessage({ id: 'payable_aging_summary' })); + }, [changePageTitle, formatMessage]); + + useEffect(() => { + if (APAgingSummaryRefresh) { + queryCache.invalidateQueries('payable-aging-summary'); + refreshAPAgingSummary(false); + } + }, [APAgingSummaryRefresh, refreshAPAgingSummary]); + + useEffect(() => { + setDashboardBackLink(true); + return () => { + setDashboardBackLink(false); + }; + }, [setDashboardBackLink]); + + const handleFilterSubmit = (filter) => { + const _filter = { + ...filter, + asDate: moment(filter.asDate).format('YYYY-MM-DD'), + }; + setQuery(_filter); + refreshAPAgingSummary(true); + toggleFilterAPAgingSummary(false); + }; + + const handleNumberFormatSubmit = (numberFormat) => { + setQuery({ + ...query, + numberFormat, + }); + refreshAPAgingSummary(true); + }; + + return ( + + + + + +
+ +
+
+
+
+ ); +} + +export default compose( + withDashboardActions, + withAPAgingSummaryActions, + withSettings(({ organizationSettings }) => ({ + organizationName: organizationSettings.name, + })), + withAPAgingSummary(({ APAgingSummaryRefresh }) => ({ + APAgingSummaryRefresh, + })), +)(APAgingSummary); diff --git a/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryActionsBar.js b/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryActionsBar.js new file mode 100644 index 000000000..5426ab7af --- /dev/null +++ b/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryActionsBar.js @@ -0,0 +1,123 @@ +import React from 'react'; +import { + NavbarDivider, + NavbarGroup, + Classes, + Button, + Popover, + PopoverInteractionKind, + Position, +} from '@blueprintjs/core'; +import { safeInvoke } from '@blueprintjs/core/lib/esm/common/utils'; + +import { FormattedMessage as T } from 'react-intl'; +import classNames from 'classnames'; + +import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; +import { Icon } from 'components'; +import NumberFormatDropdown from 'components/NumberFormatDropdown'; + +import withAPAgingSummary from './withAPAgingSummary'; +import withARAgingSummaryActions from './withAPAgingSummaryActions'; + +import { compose } from 'utils'; + +/** + * AP Aging summary sheet - Actions bar. + */ +function APAgingSummaryActionsBar({ + //#withPayableAgingSummary + payableAgingFilter, + payableAgingLoading, + + //#withARAgingSummaryActions + toggleFilterAPAgingSummary, + refreshAPAgingSummary, + + //#ownProps + numberFormat, + onNumberFormatSubmit, +}) { + const handleFilterToggleClick = () => toggleFilterAPAgingSummary(); + + // handle recalculate report button. + const handleRecalculateReport = () => refreshAPAgingSummary(true); + + // handle number format submit. + const handleNumberFormatSubmit = (numberFormat) => + safeInvoke(onNumberFormatSubmit, numberFormat); + + return ( + + + + + + + + + ); +} + +export default compose( + withAPAgingSummaryActions, + withAPAgingSummary(({ payableAgingSummaryFilter }) => ({ + payableAgingFilter: payableAgingSummaryFilter, + })), +)(APAgingSummaryHeader); diff --git a/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryHeaderGeneral.js b/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryHeaderGeneral.js new file mode 100644 index 000000000..4d72cfeb7 --- /dev/null +++ b/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryHeaderGeneral.js @@ -0,0 +1,77 @@ +import React from 'react'; +import { FastField } from 'formik'; +import { DateInput } from '@blueprintjs/datetime'; +import { Intent, FormGroup, InputGroup, Position } from '@blueprintjs/core'; +import { FormattedMessage as T } from 'react-intl'; +import { Row, Col, FieldHint } from 'components'; +import { + momentFormatter, + tansformDateValue, + inputIntent, + handleDateChange, +} from 'utils'; + +/** + * AP Aging Summary - Drawer Header - General Fields. + */ +export default function APAgingSummaryHeaderGeneral() { + return ( +
+ + + + {({ form, field: { value }, meta: { error } }) => ( + } + labelInfo={} + fill={true} + intent={inputIntent({ error })} + > + { + form.setFieldValue('asDate', selectedDate); + })} + popoverProps={{ position: Position.BOTTOM, minimal: true }} + minimal={true} + fill={true} + /> + + )} + + + + + + + {({ field, meta: { error } }) => ( + } + labelInfo={} + intent={inputIntent({ error })} + > + + + )} + + + + + + + {({ field, meta: { error } }) => ( + } + labelInfo={} + intent={inputIntent({ error })} + > + + + )} + + + +
+ ); +} diff --git a/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryTable.js b/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryTable.js new file mode 100644 index 000000000..303d7c28b --- /dev/null +++ b/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryTable.js @@ -0,0 +1,103 @@ +import React, { useMemo, useCallback } from 'react'; +import { FormattedMessage as T, useIntl } from 'react-intl'; +import { DataTable } from 'components'; +import FinancialSheet from 'components/FinancialSheet'; + +import withAPAgingSummary from './withAPAgingSummary'; + +import { compose, getColumnWidth } from 'utils'; + +/** + * AP aging summary table sheet. + */ +function APAgingSummaryTable({ + //#withPayableAgingSummary + payableAgingColumns, + payableAgingRows, + payableAgingLoading, + + //#ownProps + organizationName, +}) { + const { formatMessage } = useIntl(); + const agingColumns = useMemo( + () => + payableAgingColumns.map((agingColumn) => { + return `${agingColumn.before_days} - ${ + agingColumn.to_days || 'And Over' + }`; + }), + [payableAgingColumns], + ); + + const columns = useMemo( + () => [ + { + Header: , + accessor: 'name', + className: 'name', + width: 240, + sticky: 'left', + textOverview: true, + }, + { + Header: , + accessor: 'current', + className: 'current', + width: getColumnWidth(payableAgingRows, `current`, { + minWidth: 120, + }), + }, + + ...agingColumns.map((agingColumn, index) => ({ + Header: agingColumn, + accessor: `aging-${index}`, + width: getColumnWidth(payableAgingRows, `aging-${index}`, { + minWidth: 120, + }), + })), + { + Header: , + accessor: 'total', + width: getColumnWidth(payableAgingRows, 'total', { + minWidth: 120, + }), + }, + ], + [payableAgingRows], + ); + const rowClassNames = (row) => [`row-type--${row.original.rowType}`]; + + return ( + + + + ); +} + +export default compose( + withAPAgingSummary( + ({ + payableAgingSummaryLoading, + payableAgingSummaryColumns, + payableAgingSummaryRows, + }) => ({ + payableAgingLoading: payableAgingSummaryLoading, + payableAgingColumns: payableAgingSummaryColumns, + payableAgingRows: payableAgingSummaryRows, + }), + ), +)(APAgingSummaryTable); diff --git a/client/src/containers/FinancialStatements/APAgingSummary/common.js b/client/src/containers/FinancialStatements/APAgingSummary/common.js new file mode 100644 index 000000000..0cb3aaea0 --- /dev/null +++ b/client/src/containers/FinancialStatements/APAgingSummary/common.js @@ -0,0 +1,5 @@ +import { transformToCamelCase, flatObject } from 'utils'; + +export const transformFilterFormToQuery = (form) => { + return flatObject(transformToCamelCase(form)); +}; \ No newline at end of file diff --git a/client/src/containers/FinancialStatements/APAgingSummary/withAPAgingSummary.js b/client/src/containers/FinancialStatements/APAgingSummary/withAPAgingSummary.js new file mode 100644 index 000000000..9a0bed6b4 --- /dev/null +++ b/client/src/containers/FinancialStatements/APAgingSummary/withAPAgingSummary.js @@ -0,0 +1,35 @@ +import { connect } from 'react-redux'; +import { + getFinancialSheetFactory, + getFinancialSheetColumnsFactory, + getFinancialSheetTableRowsFactory, +} from 'store/financialStatement/financialStatements.selectors'; + +export default (mapState) => { + const mapStateToProps = (state, props) => { + const getAPAgingSheet = getFinancialSheetFactory('payableAgingSummary'); + const getAPAgingSheetColumns = getFinancialSheetColumnsFactory( + 'payableAgingSummary', + ); + const getAPAgingSheetRows = getFinancialSheetTableRowsFactory( + 'payableAgingSummary', + ); + + const { + loading, + filter, + refresh, + } = state.financialStatements.payableAgingSummary; + + const mapped = { + payableAgingSummarySheet: getAPAgingSheet(state, props), + payableAgingSummaryColumns: getAPAgingSheetColumns(state, props), + payableAgingSummaryRows: getAPAgingSheetRows(state, props), + payableAgingSummaryLoading: loading, + payableAgingSummaryFilter: filter, + APAgingSummaryRefresh: refresh, + }; + return mapState ? mapState(mapped, state, props) : mapped; + }; + return connect(mapStateToProps); +}; diff --git a/client/src/containers/FinancialStatements/APAgingSummary/withAPAgingSummaryActions.js b/client/src/containers/FinancialStatements/APAgingSummary/withAPAgingSummaryActions.js new file mode 100644 index 000000000..87cefd3d8 --- /dev/null +++ b/client/src/containers/FinancialStatements/APAgingSummary/withAPAgingSummaryActions.js @@ -0,0 +1,18 @@ +import { connect } from 'react-redux'; +import { + fetchPayableAginSummary, + payableAgingSummaryRefresh, +} from 'store/financialStatement/financialStatements.actions'; + +const mapActionsToProps = (dispatch) => ({ + requestPayableAgingSummary: (query) => + dispatch(fetchPayableAginSummary({ query })), + refreshAPAgingSummary: (refresh) => + dispatch(payableAgingSummaryRefresh(refresh)), + toggleFilterAPAgingSummary: () => + dispatch({ + type: 'PAYABLE_AGING_SUMMARY_FILTER_TOGGLE', + }), +}); + +export default connect(null, mapActionsToProps); diff --git a/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummary.js b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummary.js index c5831ff86..7224d2fa4 100644 --- a/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummary.js +++ b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummary.js @@ -29,16 +29,13 @@ function ReceivableAgingSummarySheet({ }); // Handle filter submit. - const handleFilterSubmit = useCallback( - (filter) => { - const _filter = { - ...filter, - asDate: moment(filter.asDate).format('YYYY-MM-DD'), - }; - setFilter(_filter); - }, - [], - ); + const handleFilterSubmit = useCallback((filter) => { + const _filter = { + ...filter, + asDate: moment(filter.asDate).format('YYYY-MM-DD'), + }; + setFilter(_filter); + }, []); // Handle number format submit. const handleNumberFormatSubmit = (numberFormat) => { @@ -49,8 +46,8 @@ function ReceivableAgingSummarySheet({ - + onNumberFormatSubmit={handleNumberFormatSubmit} + />
- +
diff --git a/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryActionsBar.js b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryActionsBar.js index e5a363887..14ed6fe7e 100644 --- a/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryActionsBar.js +++ b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryActionsBar.js @@ -48,7 +48,6 @@ function ARAgingSummaryActionsBar({ const handleNumberFormatSubmit = (numberFormat) => { safeInvoke(onNumberFormatSubmit, numberFormat); }; - return ( @@ -98,7 +97,7 @@ function ARAgingSummaryActionsBar({ className={Classes.MINIMAL} text={} icon={} - /> + />