feat: apply new cards design system.

feat: empty status datatables.
fix: edit account.
This commit is contained in:
Ahmed Bouhuolia
2020-11-18 21:55:17 +02:00
parent 0b386a7cb2
commit 128feb73f8
64 changed files with 869 additions and 688 deletions

View File

@@ -42,7 +42,7 @@
"eslint-plugin-react-hooks": "^1.6.1",
"file-loader": "4.3.0",
"flow-bin": "^0.123.0",
"formik": "^2.1.4",
"formik": "^2.2.5",
"fs-extra": "^8.1.0",
"html-webpack-plugin": "4.0.0-beta.11",
"identity-obj-proxy": "3.0.0",
@@ -70,6 +70,7 @@
"react-dev-utils": "^10.2.0",
"react-dom": "^16.12.0",
"react-dropzone": "^11.0.1",
"react-error-boundary": "^3.0.2",
"react-grid-system": "^6.2.3",
"react-hook-form": "^4.9.4",
"react-intl": "^3.12.0",
@@ -130,6 +131,7 @@
},
"devDependencies": {
"@babel/preset-flow": "^7.9.0",
"@welldone-software/why-did-you-render": "^6.0.0-rc.1",
"http-proxy-middleware": "^1.0.0",
"react-query-devtools": "^2.1.1",
"redux-devtools": "^3.5.0"

View File

@@ -2,6 +2,9 @@ import { Classes } from '@blueprintjs/core';
const CLASSES = {
DASHBOARD_DATATABLE: 'dashboard__datatable',
DASHBOARD_CARD: 'dashboard__card',
DASHBOARD_CARD_PAGE: 'dashboard__card--page',
DATATABLE_EDITOR: 'datatable-editor',
DATATABLE_EDITOR_ACTIONS: 'datatable-editor__actions',
DATATABLE_EDITOR_ITEMS_ENTRIES: 'items-entries-table',
@@ -13,7 +16,7 @@ const CLASSES = {
PAGE_FORM_HEADER_BIG_NUMBERS: 'page-form__big-numbers',
PAGE_FORM_TABS: 'page-form__tabs',
PAGE_FORM_BODY: 'page-form__body',
PAGE_FORM_FOOTER: 'page-form__footer',
PAGE_FORM_FLOATING_ACTIONS: 'page-form__floating-actions',

View File

@@ -0,0 +1,17 @@
import React from 'react';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
// Dashboard card.
export default function DashboardCard({ children, page }) {
return (
<div
className={classNames(CLASSES.DASHBOARD_CARD, {
[CLASSES.DASHBOARD_CARD_PAGE]: page,
})}
>
{children}
</div>
);
}

View File

@@ -1,15 +1,18 @@
import React from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import DashboardTopbar from 'components/Dashboard/DashboardTopbar';
import DashboardContentRoute from 'components/Dashboard/DashboardContentRoute';
import DashboardFooter from 'components/Dashboard/DashboardFooter';
import DashboardErrorBoundary from './DashboardErrorBoundary';
export default function() {
export default function () {
return (
<div className="dashboard-content" id="dashboard">
<DashboardTopbar />
<DashboardContentRoute />
<DashboardFooter />
</div>
<ErrorBoundary FallbackComponent={DashboardErrorBoundary}>
<div className="dashboard-content" id="dashboard">
<DashboardTopbar />
<DashboardContentRoute />
<DashboardFooter />
</div>
</ErrorBoundary>
);
}
}

View File

@@ -0,0 +1,12 @@
import React from 'react';
import { Icon } from 'components';
export default function DashboardErrorBoundary({}) {
return (
<div class="dashboard__error-boundary">
<h1>Sorry about that! Something went wrong</h1>
<p>If the problem stuck, please <a href="#">contact us</a> as soon as possible.</p>
<Icon icon="bigcapital" height={30} width={160} />
</div>
)
}

View File

@@ -1,5 +1,4 @@
import React from 'react';
import { connect } from 'react-redux';
import { useHistory } from 'react-router';
import {
Navbar,
@@ -14,7 +13,7 @@ import { FormattedMessage as T } from 'react-intl';
import DashboardTopbarUser from 'components/Dashboard/TopbarUser';
import DashboardBreadcrumbs from 'components/Dashboard/DashboardBreadcrumbs';
import { Icon, If } from 'components';
import { Icon, Hint, If } from 'components';
import withSearch from 'containers/GeneralSearch/withSearch';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
@@ -78,8 +77,18 @@ function DashboardTopbar({
<div class="dashboard__title">
<h1>{pageTitle}</h1>
<If condition={true}>
<div class="dashboard__hint">
<Hint
content={
'It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.'
}
/>
</div>
</If>
<If condition={pageSubtitle}>
<h3>{ pageSubtitle }</h3>
<h3>{pageSubtitle}</h3>
</If>
<If condition={pageSubtitle && editViewId}>

View File

@@ -343,6 +343,7 @@ export default function DataTable({
<div
className={classnames('bigcapital-datatable', className, {
'has-sticky': sticky,
'has-pagination': pagination,
'is-expandable': expandable,
'is-loading': loading,
'has-virtualized-rows': virtualizedRows,

View File

@@ -2,11 +2,15 @@ import React from 'react';
import { Tooltip, Position } from '@blueprintjs/core';
import Icon from './Icon';
export default function FieldHint({ content, position }) {
export default function FieldHint({
content,
position,
iconSize = 12
}) {
return (
<span class="hint">
<Tooltip content={content} position={position}>
<Icon icon="info-circle" iconSize={12} />
<Icon icon="info-circle" iconSize={iconSize} />
</Tooltip>
</span>
);

View File

@@ -0,0 +1,27 @@
import React, { useState } from 'react';
import {
Checkbox as BPCheckbox,
} from '@blueprintjs/core';
export default function CheckboxComponent(props) {
const { field, form, ...rest } = props;
const [value, setValue] = useState(field.value || false);
const handleChange = () => {
const checked = !value;
form.setFieldValue(field.name, checked);
setValue(checked);
};
const handleBlur = () => {
form.setFieldTouched(field.name);
};
const checkboxProps = {
...rest,
onChange: handleChange,
onBlur: handleBlur,
checked: value,
}
return <BPCheckbox {...checkboxProps} />;
}

View File

@@ -38,6 +38,7 @@ import DisplayNameList from './DisplayNameList';
import MoneyInputGroup from './MoneyInputGroup';
import Dragzone from './Dragzone';
import EmptyStatus from './EmptyStatus';
import DashboardCard from './Dashboard/DashboardCard';
const Hint = FieldHint;
@@ -81,5 +82,6 @@ export {
SalutationList,
MoneyInputGroup,
Dragzone,
EmptyStatus
EmptyStatus,
DashboardCard,
};

View File

@@ -444,7 +444,6 @@ function MakeJournalEntriesForm({
},
[changePageSubtitle],
);
console.log(values, 'Val');
return (
<div class="make-journal-entries">
<form onSubmit={handleSubmit}>

View File

@@ -1,19 +1,18 @@
import React, { useEffect, useCallback, useState, useMemo } from 'react';
import React, { useCallback, useMemo } from 'react';
import {
Intent,
Button,
Classes,
Popover,
Tooltip,
Menu,
MenuItem,
MenuDivider,
Position,
Tag,
} from '@blueprintjs/core';
import { withRouter, useParams } from 'react-router-dom';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { useIntl } from 'react-intl';
import moment from 'moment';
import classNames from 'classnames';
import {
DataTable,
@@ -23,9 +22,11 @@ import {
Icon,
LoadingIndicator,
} from 'components';
import { CLASSES } from 'common/classes';
import { useIsValuePassed } from 'hooks';
import ManualJournalsEmptyStatus from './ManualJournalsEmptyStatus';
import { AmountPopoverContent, NoteAccessor, StatusAccessor } from './components';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withManualJournals from 'containers/Accounting/withManualJournals';
@@ -33,44 +34,7 @@ import withManualJournalsActions from 'containers/Accounting/withManualJournalsA
import { compose, saveInvoke } from 'utils';
/**
* Status column accessor.
*/
const StatusAccessor = (row) => {
return (
<Choose>
<Choose.When condition={!!row.status}>
<Tag minimal={true}>
<T id={'published'} />
</Tag>
</Choose.When>
<Choose.Otherwise>
<Tag minimal={true} intent={Intent.WARNING}>
<T id={'draft'} />
</Tag>
</Choose.Otherwise>
</Choose>
);
};
/**
* Note column accessor.
*/
function NoteAccessor(row) {
return (
<If condition={row.description}>
<Tooltip
className={Classes.TOOLTIP_INDICATOR}
content={row.description}
position={Position.LEFT_TOP}
hoverOpenDelay={50}
>
<Icon icon={'file-alt'} iconSize={16} />
</Tooltip>
</If>
);
}
function ManualJournalsDataTable({
// #withManualJournals
@@ -166,7 +130,14 @@ function ManualJournalsDataTable({
{
id: 'amount',
Header: formatMessage({ id: 'amount' }),
accessor: (r) => <Money amount={r.amount} currency={'USD'} />,
accessor: (r) => (
<Tooltip
content={<AmountPopoverContent journalEntries={r.entries} />}
position={Position.RIGHT_BOTTOM}
>
<Money amount={r.amount} currency={'USD'} />
</Tooltip>
),
className: 'amount',
width: 115,
},
@@ -254,37 +225,39 @@ function ManualJournalsDataTable({
const showEmptyStatus = [
manualJournalsCurrentViewId === -1,
manualJournalsCurrentPage.length === 0,
].every(condition => condition === true);
].every((condition) => condition === true);
return (
<LoadingIndicator loading={manualJournalsLoading && !isLoadedBefore}>
<Choose>
<Choose.When condition={showEmptyStatus}>
<ManualJournalsEmptyStatus />
</Choose.When>
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<LoadingIndicator loading={manualJournalsLoading && !isLoadedBefore}>
<Choose>
<Choose.When condition={showEmptyStatus}>
<ManualJournalsEmptyStatus />
</Choose.When>
<Choose.Otherwise>
<DataTable
noInitialFetch={true}
columns={columns}
data={manualJournalsCurrentPage}
onFetchData={handleDataTableFetchData}
manualSortBy={true}
selectionColumn={true}
expandable={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
rowContextMenu={onRowContextMenu}
pagesCount={manualJournalsPagination.pagesCount}
pagination={true}
initialPageSize={manualJournalsTableQuery.page_size}
initialPageIndex={manualJournalsTableQuery.page - 1}
autoResetSortBy={false}
autoResetPage={false}
/>
</Choose.Otherwise>
</Choose>
</LoadingIndicator>
<Choose.Otherwise>
<DataTable
noInitialFetch={true}
columns={columns}
data={manualJournalsCurrentPage}
onFetchData={handleDataTableFetchData}
manualSortBy={true}
selectionColumn={true}
expandable={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
rowContextMenu={onRowContextMenu}
pagesCount={manualJournalsPagination.pagesCount}
pagination={true}
initialPageSize={manualJournalsTableQuery.page_size}
initialPageIndex={manualJournalsTableQuery.page - 1}
autoResetSortBy={false}
autoResetPage={false}
/>
</Choose.Otherwise>
</Choose>
</LoadingIndicator>
</div>
);
}
@@ -304,7 +277,7 @@ export default compose(
manualJournalsLoading,
manualJournalsPagination,
manualJournalsTableQuery,
manualJournalsCurrentViewId
manualJournalsCurrentViewId,
}),
),
)(ManualJournalsDataTable);

View File

@@ -0,0 +1,102 @@
import React from 'react';
import {
Intent,
Classes,
Tooltip,
Position,
Tag,
} from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl';
import { Choose, Money, If, Icon } from 'components';
import withAccountDetails from 'containers/Accounts/withAccountDetail';
import { compose } from 'utils';
const AmountPopoverContentLineRender = ({
journalEntry,
accountId,
// #withAccountDetail
account,
}) => {
const isCredit = !!journalEntry.credit;
const isDebit = !!journalEntry.debit;
return (
<Choose>
<Choose.When condition={isDebit}>
<div>
C. <Money amount={journalEntry.debit} currency={'USD'} /> USD -{' '}
{account.name} <If condition={account.code}>({account.code})</If>
</div>
</Choose.When>
<Choose.When condition={isCredit}>
<div class={'ml1'}>
D. <Money amount={journalEntry.credit} currency={'USD'} /> USD -{' '}
{account.name} <If condition={account.code}>({account.code})</If>
</div>
</Choose.When>
</Choose>
);
};
const AmountPopoverContentLine = compose(withAccountDetails)(
AmountPopoverContentLineRender,
);
export function AmountPopoverContent({ journalEntries }) {
const journalLinesProps = journalEntries.map((journalEntry) => ({
journalEntry,
accountId: journalEntry.account_id,
}));
return (
<div>
{journalLinesProps.map(({ journalEntry, accountId }) => (
<AmountPopoverContentLine
journalEntry={journalEntry}
accountId={accountId}
/>
))}
</div>
);
}
/**
* Status column accessor.
*/
export const StatusAccessor = (row) => {
return (
<Choose>
<Choose.When condition={!!row.status}>
<Tag minimal={true}>
<T id={'published'} />
</Tag>
</Choose.When>
<Choose.Otherwise>
<Tag minimal={true} intent={Intent.WARNING}>
<T id={'draft'} />
</Tag>
</Choose.Otherwise>
</Choose>
);
};
/**
* Note column accessor.
*/
export function NoteAccessor(row) {
return (
<If condition={row.description}>
<Tooltip
className={Classes.TOOLTIP_INDICATOR}
content={row.description}
position={Position.LEFT_TOP}
hoverOpenDelay={50}
>
<Icon icon={'file-alt'} iconSize={16} />
</Tooltip>
</If>
);
}

View File

@@ -12,17 +12,13 @@ import {
} from '@blueprintjs/core';
import { withRouter } from 'react-router';
import { FormattedMessage as T, useIntl } from 'react-intl';
import classnames from 'classnames';
import {
Icon,
DataTable,
Money,
If,
Choose,
} from 'components';
import classNames from 'classnames';
import { Icon, DataTable, Money, If, Choose } from 'components';
import { compose } from 'utils';
import { useUpdateEffect } from 'hooks';
import { CLASSES } from 'common/classes';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withAccounts from 'containers/Accounts/withAccounts';
@@ -30,6 +26,7 @@ import withDialogActions from 'containers/Dialog/withDialogActions';
import withCurrentView from 'containers/Views/withCurrentView';
function NormalCell({ cell }) {
const { formatMessage } = useIntl();
@@ -52,7 +49,7 @@ function NormalCell({ cell }) {
function BalanceCell({ cell }) {
const account = cell.row.original;
return (account.amount) ? (
return account.amount ? (
<span>
<Money amount={account.amount} currency={'USD'} />
</span>
@@ -64,13 +61,14 @@ function BalanceCell({ cell }) {
function InactiveSemafro() {
return (
<Tooltip
content={<T id='inactive' />}
className={classnames(
content={<T id="inactive" />}
className={classNames(
Classes.TOOLTIP_INDICATOR,
'bp3-popover-wrapper--inactive-semafro'
'bp3-popover-wrapper--inactive-semafro',
)}
position={Position.TOP}
hoverOpenDelay={250}>
hoverOpenDelay={250}
>
<div className="inactive-semafro"></div>
</Tooltip>
);
@@ -82,7 +80,7 @@ function AccountNameAccessor(row) {
<Choose>
<Choose.When condition={!!row.description}>
<Tooltip
className={classnames(
className={classNames(
Classes.TOOLTIP_INDICATOR,
'bp3-popover-wrapper--account-desc',
)}
@@ -94,9 +92,7 @@ function AccountNameAccessor(row) {
</Tooltip>
</Choose.When>
<Choose.Otherwise>
{ row.name }
</Choose.Otherwise>
<Choose.Otherwise>{row.name}</Choose.Otherwise>
</Choose>
<If condition={!row.active}>
@@ -159,7 +155,8 @@ function AccountsDataTable({
<Menu>
<MenuItem
icon={<Icon icon="reader-18" />}
text={formatMessage({ id: 'view_details' })} />
text={formatMessage({ id: 'view_details' })}
/>
<MenuDivider />
<MenuItem
icon={<Icon icon="pen-18" />}
@@ -287,20 +284,22 @@ function AccountsDataTable({
);
return (
<DataTable
noInitialFetch={true}
columns={columns}
data={accountsTable}
onFetchData={handleDatatableFetchData}
manualSortBy={true}
selectionColumn={selectionColumn}
expandable={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
loading={accountsLoading && !isMounted}
rowContextMenu={rowContextMenu}
expandColumnSpace={1}
/>
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<DataTable
noInitialFetch={true}
columns={columns}
data={accountsTable}
onFetchData={handleDatatableFetchData}
manualSortBy={true}
selectionColumn={selectionColumn}
expandable={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
loading={accountsLoading && !isMounted}
rowContextMenu={rowContextMenu}
expandColumnSpace={1}
/>
</div>
);
}

View File

@@ -2,6 +2,7 @@ import React, { useCallback } from 'react';
import { useParams, useHistory } from 'react-router-dom';
import { useQuery } from 'react-query';
import { DashboardCard } from 'components';
import CustomerForm from 'containers/Customers/CustomerForm';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
@@ -40,12 +41,7 @@ function Customer({
requestFetchCurrencies(),
);
const handleFormSubmit = useCallback(
(payload) => {
},
[history],
);
const handleFormSubmit = useCallback((payload) => {}, [history]);
const handleCancel = useCallback(() => {
history.goBack();
@@ -60,11 +56,13 @@ function Customer({
}
name={'customer-form'}
>
<CustomerForm
onFormSubmit={handleFormSubmit}
customerId={id}
onCancelForm={handleCancel}
/>
<DashboardCard page>
<CustomerForm
onFormSubmit={handleFormSubmit}
customerId={id}
onCancelForm={handleCancel}
/>
</DashboardCard>
</DashboardInsider>
);
}

View File

@@ -172,9 +172,9 @@ function CustomerForm({
const onSuccess = () => {
AppToaster.show({
message: formatMessage({
id: customer ?
'the_item_customer_has_been_successfully_edited' :
'the_customer_has_been_successfully_created',
id: customer
? 'the_item_customer_has_been_successfully_edited'
: 'the_customer_has_been_successfully_created',
}),
intent: Intent.SUCCESS,
});
@@ -191,7 +191,9 @@ function CustomerForm({
};
if (customer && customer.id) {
requestEditCustomer(customer.id, formValues).then(onSuccess).catch(onError);
requestEditCustomer(customer.id, formValues)
.then(onSuccess)
.catch(onError);
} else {
requestSubmitCustomer(formValues).then(onSuccess).catch(onError);
}
@@ -239,14 +241,12 @@ function CustomerForm({
>
{({ isSubmitting }) => (
<Form>
<div class={classNames(CLASSES.PAGE_FORM_HEADER)}>
<div className={classNames(CLASSES.PAGE_FORM_HEADER_PRIMARY)}>
<CustomerFormPrimarySection />
</div>
<div className={classNames(CLASSES.PAGE_FORM_HEADER_PRIMARY)}>
<CustomerFormPrimarySection />
</div>
<div className={'page-form__after-priamry-section'}>
<CustomerFormAfterPrimarySection />
</div>
<div className={'page-form__after-priamry-section'}>
<CustomerFormAfterPrimarySection />
</div>
<div className={classNames(CLASSES.PAGE_FORM_TABS)}>

View File

@@ -10,9 +10,11 @@ import {
} from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { useIsValuePassed } from 'hooks';
import classNames from 'classnames';
import CustomersEmptyStatus from './CustomersEmptyStatus';
import { DataTable, Icon, Money, Choose, LoadingIndicator } from 'components';
import { CLASSES } from 'common/classes';
import withCustomers from './withCustomers';
import withCustomersActions from './withCustomersActions';
@@ -186,42 +188,41 @@ const CustomerTable = ({
const showEmptyStatus = [
customersCurrentViewId === -1,
customers.length === 0,
].every(condition => condition === true);
].every((condition) => condition === true);
return (
<LoadingIndicator
loading={customersLoading && !isLoadedBefore}
mount={false}
>
<Choose>
<Choose.When condition={showEmptyStatus}>
<CustomersEmptyStatus />
</Choose.When>
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<LoadingIndicator loading={customersLoading && !isLoadedBefore}>
<Choose>
<Choose.When condition={showEmptyStatus}>
<CustomersEmptyStatus />
</Choose.When>
<Choose.Otherwise>
<DataTable
noInitialFetch={true}
columns={columns}
data={customers}
// loading={customersLoading}
onFetchData={handleFetchData}
selectionColumn={true}
expandable={false}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
spinnerProps={{ size: 30 }}
rowContextMenu={rowContextMenu}
pagination={true}
manualSortBy={true}
pagesCount={customerPagination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
initialPageSize={customersTableQuery.page_size}
initialPageIndex={customersTableQuery.page - 1}
/>
</Choose.Otherwise>
</Choose>
</LoadingIndicator>
<Choose.Otherwise>
<DataTable
noInitialFetch={true}
columns={columns}
data={customers}
// loading={customersLoading}
onFetchData={handleFetchData}
selectionColumn={true}
expandable={false}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
spinnerProps={{ size: 30 }}
rowContextMenu={rowContextMenu}
pagination={true}
manualSortBy={true}
pagesCount={customerPagination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
initialPageSize={customersTableQuery.page_size}
initialPageIndex={customersTableQuery.page - 1}
/>
</Choose.Otherwise>
</Choose>
</LoadingIndicator>
</div>
);
};
@@ -238,7 +239,7 @@ export default compose(
customersLoading,
customerPagination,
customersTableQuery,
customersCurrentViewId
customersCurrentViewId,
}),
),
withCustomersActions,

View File

@@ -6,6 +6,7 @@ export default (mapState) => {
const mapped = {
pageTitle: state.dashboard.pageTitle,
pageSubtitle: state.dashboard.pageSubtitle,
pageHint: state.dashboard.pageHint,
editViewId: state.dashboard.topbarEditViewId,
sidebarExpended: state.dashboard.sidebarExpended,
preferencesPageTitle: state.dashboard.preferencesPageTitle,

View File

@@ -14,6 +14,12 @@ const mapActionsToProps = (dispatch) => ({
pageSubtitle,
}),
changePageHint: (pageHint) =>
dispatch({
type: t.CHANGE_DASHBOARD_PAGE_HINT,
payload: { pageHint }
}),
setTopbarEditView: (id) =>
dispatch({
type: t.SET_TOPBAR_EDIT_VIEW,

View File

@@ -88,6 +88,9 @@ function AccountFormDialogContent({
if (errors.find((e) => e.type === 'NOT_UNIQUE_CODE')) {
fields.code = formatMessage({ id: 'account_code_is_not_unique' });
}
if (errors.find((e) => e.type === 'ACCOUNT.NAME.NOT.UNIQUE')) {
fields.name = formatMessage({ id: 'account_name_is_already_used' });
}
return fields;
};

View File

@@ -9,6 +9,9 @@ import {
} from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl';
import moment from 'moment';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import { DataTable, Icon, MoneyExchangeRate } from 'components';
import LoadingIndicator from 'components/LoadingIndicator';
@@ -140,24 +143,26 @@ function ExchangeRateTable({
);
return (
<LoadingIndicator loading={loading} mount={false}>
<DataTable
columns={columns}
data={exchangeRatesList}
onFetchData={handelFetchData}
loading={exchangeRatesLoading && !initialMount}
manualSortBy={true}
selectionColumn={selectionColumn}
expandable={true}
treeGraph={true}
onSelectedRowsChange={handelSelectedRowsChange}
rowContextMenu={rowContextMenu}
pagination={true}
pagesCount={exchangeRatesPageination.pagesCount}
initialPageSize={exchangeRatesPageination.pageSize}
initialPageIndex={exchangeRatesPageination.page - 1}
/>
</LoadingIndicator>
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<LoadingIndicator loading={loading} mount={false}>
<DataTable
columns={columns}
data={exchangeRatesList}
onFetchData={handelFetchData}
loading={exchangeRatesLoading && !initialMount}
manualSortBy={true}
selectionColumn={selectionColumn}
expandable={true}
treeGraph={true}
onSelectedRowsChange={handelSelectedRowsChange}
rowContextMenu={rowContextMenu}
pagination={true}
pagesCount={exchangeRatesPageination.pagesCount}
initialPageSize={exchangeRatesPageination.pageSize}
initialPageIndex={exchangeRatesPageination.page - 1}
/>
</LoadingIndicator>
</div>
);
}

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useCallback, useState, useMemo } from 'react';
import React, { useEffect, useCallback, useMemo } from 'react';
import {
Intent,
Button,
@@ -15,12 +15,15 @@ import { useParams } from 'react-router-dom';
import { withRouter } from 'react-router';
import { FormattedMessage as T, useIntl } from 'react-intl';
import moment from 'moment';
import classNames from 'classnames';
import Icon from 'components/Icon';
import { compose, saveInvoke } from 'utils';
import { useIsValuePassed } from 'hooks';
import { If, Money, Choose, LoadingIndicator } from 'components';
import { CLASSES } from 'common/classes';
import DataTable from 'components/DataTable';
import ExpensesEmptyStatus from './ExpensesEmptyStatus';
@@ -268,40 +271,39 @@ function ExpensesDataTable({
const showEmptyStatus = [
expensesCurrentViewId === -1,
expensesCurrentPage.length === 0
].every(condition => condition === true);
expensesCurrentPage.length === 0,
].every((condition) => condition === true);
return (
<LoadingIndicator
loading={expensesLoading && !isLoadedBefore}
mount={false}
>
<Choose>
<Choose.When condition={showEmptyStatus}>
<ExpensesEmptyStatus />
</Choose.When>
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<LoadingIndicator loading={expensesLoading && !isLoadedBefore}>
<Choose>
<Choose.When condition={showEmptyStatus}>
<ExpensesEmptyStatus />
</Choose.When>
<Choose.Otherwise>
<DataTable
columns={columns}
data={expensesCurrentPage}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
onFetchData={handleFetchData}
rowContextMenu={onRowContextMenu}
pagination={true}
pagesCount={expensesPagination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
initialPageSize={expensesTableQuery.page_size}
initialPageIndex={expensesTableQuery.page - 1}
/>
</Choose.Otherwise>
</Choose>
</LoadingIndicator>
<Choose.Otherwise>
<DataTable
columns={columns}
data={expensesCurrentPage}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
onFetchData={handleFetchData}
rowContextMenu={onRowContextMenu}
pagination={true}
pagesCount={expensesPagination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
initialPageSize={expensesTableQuery.page_size}
initialPageIndex={expensesTableQuery.page - 1}
/>
</Choose.Otherwise>
</Choose>
</LoadingIndicator>
</div>
);
}

View File

@@ -3,6 +3,7 @@ import { useIntl } from 'react-intl';
import { Link } from 'react-router-dom';
import { For } from 'components';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import financialReportMenus from 'config/financialReportsMenu';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
@@ -42,9 +43,11 @@ function FinancialReports({
}, [changePageTitle, formatMessage]);
return (
<div class="financial-reports">
<For render={FinancialReportsSection} of={financialReportMenus} />
</div>
<DashboardInsider name={'financial-reports'}>
<div class="financial-reports">
<For render={FinancialReportsSection} of={financialReportMenus} />
</div>
</DashboardInsider>
);
}

View File

@@ -9,12 +9,15 @@ import {
Position,
} from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl';
import classNames from 'classnames';
import Icon from 'components/Icon';
import LoadingIndicator from 'components/LoadingIndicator';
import { compose } from 'utils';
import DataTable from 'components/DataTable';
import { CLASSES } from 'common/classes';
import withItemCategories from './withItemCategories';
import withDialogActions from 'containers/Dialog/withDialogActions';
@@ -139,21 +142,23 @@ const ItemsCategoryList = ({
);
return (
<LoadingIndicator mount={false}>
<DataTable
noInitialFetch={true}
columns={columns}
data={categoriesList}
onFetchData={handelFetchData}
manualSortBy={true}
selectionColumn={selectionColumn}
expandable={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
loading={categoriesTableLoading}
rowContextMenu={handleRowContextMenu}
/>
</LoadingIndicator>
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<LoadingIndicator mount={false}>
<DataTable
noInitialFetch={true}
columns={columns}
data={categoriesList}
onFetchData={handelFetchData}
manualSortBy={true}
selectionColumn={selectionColumn}
expandable={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
loading={categoriesTableLoading}
rowContextMenu={handleRowContextMenu}
/>
</LoadingIndicator>
</div>
);
};

View File

@@ -1,11 +1,11 @@
import React, { useState, useMemo, useCallback, useEffect } from 'react';
import * as Yup from 'yup';
import { Formik, Form } from 'formik';
import { Intent } from '@blueprintjs/core';
import { queryCache } from 'react-query';
import { useHistory } from 'react-router-dom';
import { useIntl } from 'react-intl';
import classNames from 'classnames';
import { defaultTo } from 'lodash';
import { CLASSES } from 'common/classes';
import AppToaster from 'components/AppToaster';
@@ -22,6 +22,8 @@ import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withSettings from 'containers/Settings/withSettings';
import { compose, transformToForm } from 'utils';
import { transitionItemTypeKeyToLabel } from './utils';
import { EditItemFormSchema, CreateItemFormSchema } from './ItemForm.schema';
const defaultInitialValues = {
active: true,
@@ -83,66 +85,15 @@ function ItemForm({
deleteCallback: requestDeleteMedia,
});
const validationSchema = Yup.object().shape({
active: Yup.boolean(),
name: Yup.string()
.required()
.label(formatMessage({ id: 'item_name_' })),
type: Yup.string()
.trim()
.required()
.label(formatMessage({ id: 'item_type_' })),
sku: Yup.string().trim(),
cost_price: Yup.number().when(['purchasable'], {
is: true,
then: Yup.number()
.required()
.label(formatMessage({ id: 'cost_price_' })),
otherwise: Yup.number().nullable(true),
}),
sell_price: Yup.number().when(['sellable'], {
is: true,
then: Yup.number()
.required()
.label(formatMessage({ id: 'sell_price_' })),
otherwise: Yup.number().nullable(true),
}),
cost_account_id: Yup.number()
.when(['purchasable'], {
is: true,
then: Yup.number().required(),
otherwise: Yup.number().nullable(true),
})
.label(formatMessage({ id: 'cost_account_id' })),
sell_account_id: Yup.number()
.when(['sellable'], {
is: true,
then: Yup.number().required(),
otherwise: Yup.number().nullable(),
})
.label(formatMessage({ id: 'sell_account_id' })),
inventory_account_id: Yup.number()
.when(['type'], {
is: (value) => value === 'inventory',
then: Yup.number().required(),
otherwise: Yup.number().nullable(),
})
.label(formatMessage({ id: 'inventory_account' })),
category_id: Yup.number().positive().nullable(),
stock: Yup.string() || Yup.boolean(),
sellable: Yup.boolean().required(),
purchasable: Yup.boolean().required(),
});
/**
* Initial values in create and edit mode.
*/
const initialValues = useMemo(
() => ({
...defaultInitialValues,
cost_account_id: parseInt(preferredCostAccount),
sell_account_id: parseInt(preferredSellAccount),
inventory_account_id: parseInt(preferredInventoryAccount),
cost_account_id: defaultTo(preferredCostAccount, ''),
sell_account_id: defaultTo(preferredSellAccount, ''),
inventory_account_id: defaultTo(preferredInventoryAccount, ''),
/**
* We only care about the fields in the form. Previously unfilled optional
* values such as `notes` come back from the API as null, so remove those
@@ -150,7 +101,12 @@ function ItemForm({
*/
...transformToForm(itemDetail, defaultInitialValues),
}),
[],
[
itemDetail,
preferredCostAccount,
preferredSellAccount,
preferredInventoryAccount,
],
);
useEffect(() => {
@@ -214,7 +170,7 @@ function ItemForm({
useEffect(() => {
if (itemDetail && itemDetail.type) {
changePageSubtitle(formatMessage({ id: itemDetail.type }));
changePageSubtitle(transitionItemTypeKeyToLabel(itemDetail.type));
}
}, [itemDetail, changePageSubtitle, formatMessage]);
@@ -262,14 +218,14 @@ function ItemForm({
<div class={classNames(CLASSES.PAGE_FORM_ITEM)}>
<Formik
enableReinitialize={true}
validationSchema={validationSchema}
validationSchema={isNewMode ? CreateItemFormSchema : EditItemFormSchema}
initialValues={initialValues}
onSubmit={handleFormSubmit}
>
{({ isSubmitting, handleSubmit }) => (
<Form>
<div class={classNames(CLASSES.PAGE_FORM_BODY)}>
<ItemFormPrimarySection />
<ItemFormPrimarySection itemId={itemId} />
<ItemFormBody />
<ItemFormInventorySection />
</div>
@@ -294,8 +250,10 @@ export default compose(
withDashboardActions,
withMediaActions,
withSettings(({ itemsSettings }) => ({
preferredCostAccount: itemsSettings.preferredCostAccount,
preferredSellAccount: itemsSettings.preferredSellAccount,
preferredInventoryAccount: itemsSettings.preferredInventoryAccount,
preferredCostAccount: parseInt(itemsSettings?.preferredCostAccount),
preferredSellAccount: parseInt(itemsSettings?.preferredSellAccount),
preferredInventoryAccount: parseInt(
itemsSettings?.preferredInventoryAccount,
),
})),
)(ItemForm);

View File

@@ -0,0 +1,56 @@
import * as Yup from 'yup';
import { formatMessage } from 'services/intl';
const Schema = Yup.object().shape({
active: Yup.boolean(),
name: Yup.string()
.required()
.label(formatMessage({ id: 'item_name_' })),
type: Yup.string()
.trim()
.required()
.label(formatMessage({ id: 'item_type_' })),
sku: Yup.string().trim(),
cost_price: Yup.number().when(['purchasable'], {
is: true,
then: Yup.number()
.required()
.label(formatMessage({ id: 'cost_price_' })),
otherwise: Yup.number().nullable(true),
}),
sell_price: Yup.number().when(['sellable'], {
is: true,
then: Yup.number()
.required()
.label(formatMessage({ id: 'sell_price_' })),
otherwise: Yup.number().nullable(true),
}),
cost_account_id: Yup.number()
.when(['purchasable'], {
is: true,
then: Yup.number().required(),
otherwise: Yup.number().nullable(true),
})
.label(formatMessage({ id: 'cost_account_id' })),
sell_account_id: Yup.number()
.when(['sellable'], {
is: true,
then: Yup.number().required(),
otherwise: Yup.number().nullable(),
})
.label(formatMessage({ id: 'sell_account_id' })),
inventory_account_id: Yup.number()
.when(['type'], {
is: (value) => value === 'inventory',
then: Yup.number().required(),
otherwise: Yup.number().nullable(),
})
.label(formatMessage({ id: 'inventory_account' })),
category_id: Yup.number().positive().nullable(),
stock: Yup.string() || Yup.boolean(),
sellable: Yup.boolean().required(),
purchasable: Yup.boolean().required(),
});
export const CreateItemFormSchema = Schema;
export const EditItemFormSchema = Schema;

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { FastField, ErrorMessage } from 'formik';
import { FastField, Field, ErrorMessage } from 'formik';
import {
FormGroup,
Classes,
@@ -27,8 +27,8 @@ function ItemFormBody({ accountsList }) {
<Row>
<Col xs={6}>
{/*------------- Purchasable checbox ------------- */}
<FastField name={'sellable'}>
{({ field, field: { value } }) => (
<FastField name={'sellable'} type="checkbox">
{({ field: { onChange, onBlur, name, checked } }) => (
<FormGroup inline={true} className={'form-group--sellable'}>
<Checkbox
inline={true}
@@ -37,8 +37,10 @@ function ItemFormBody({ accountsList }) {
<T id={'i_sell_this_item'} />
</h3>
}
checked={value}
{...field}
name={'sellable'}
checked={!!checked}
onChange={onChange}
onBlur={onBlur}
/>
</FormGroup>
)}

View File

@@ -56,7 +56,7 @@ export default function ItemFormFloatingActions({
{/*----------- Active ----------*/}
<FastField name={'active'}>
{({ field, field: { value } }) => (
<FormGroup label={' '} inline={true} className={'form-group--active'}>
<FormGroup inline={true} className={'form-group--active'}>
<Checkbox
inline={true}
label={<T id={'active'} />}

View File

@@ -3,6 +3,7 @@ import { useParams, useHistory } from 'react-router-dom';
import { useQuery } from 'react-query';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import DashboardCard from 'components/Dashboard/DashboardCard';
import ItemForm from 'containers/Items/ItemForm';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
@@ -62,11 +63,13 @@ const ItemFormContainer = ({
}
name={'item-form'}
>
<ItemForm
onFormSubmit={handleFormSubmit}
itemId={id}
onCancelForm={handleCancel}
/>
<DashboardCard page>
<ItemForm
onFormSubmit={handleFormSubmit}
itemId={id}
onCancelForm={handleCancel}
/>
</DashboardCard>
</DashboardInsider>
);
};

View File

@@ -25,6 +25,7 @@ import withAccounts from 'containers/Accounts/withAccounts';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import { compose, handleStringChange, inputIntent } from 'utils';
import { transitionItemTypeKeyToLabel } from './utils';
/**
* Item form primary section.
@@ -35,8 +36,12 @@ function ItemFormPrimarySection({
// #withDashboardActions
changePageSubtitle,
// #ownProps
itemId,
}) {
const { formatMessage } = useIntl();
const isNewMode = !itemId;
const itemTypeHintContent = (
<>
@@ -59,48 +64,45 @@ function ItemFormPrimarySection({
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_PRIMARY)}>
{/*----------- Item type ----------*/}
<FastField name={'type'}>
{({ form, field: { value }, meta: { touched, error } }) => (
<FormGroup
medium={true}
label={<T id={'item_type'} />}
labelInfo={
<span>
<FieldRequiredHint />
<Hint
content={itemTypeHintContent}
position={Position.BOTTOM_LEFT}
/>
</span>
}
className={'form-group--item-type'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="item_type" />}
inline={true}
>
<RadioGroup
inline={true}
onChange={handleStringChange((_value) => {
form.setFieldValue('type', _value);
changePageSubtitle(transitionItemTypeKeyToLabel(_value));
})}
selectedValue={value}
disabled={value === 'inventory' && !isNewMode}
>
<Radio label={<T id={'service'} />} value="service" />
<Radio label={<T id={'non_inventory'} />} value="non-inventory" />
<Radio label={<T id={'inventory'} />} value="inventory" />
</RadioGroup>
</FormGroup>
)}
</FastField>
<Row>
<Col xs={7}>
{/*----------- Item type ----------*/}
<FastField name={'type'}>
{({ form, field: { value }, meta: { touched, error } }) => (
<FormGroup
medium={true}
label={<T id={'item_type'} />}
labelInfo={
<span>
<FieldRequiredHint />
<Hint
content={itemTypeHintContent}
position={Position.BOTTOM_LEFT}
/>
</span>
}
className={'form-group--item-type'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="item_type" />}
inline={true}
>
<RadioGroup
inline={true}
onChange={handleStringChange((_value) => {
form.setFieldValue('type', _value);
changePageSubtitle(formatMessage({ id: _value }));
})}
selectedValue={value}
disabled={value === 'inventory'}
>
<Radio label={<T id={'service'} />} value="service" />
<Radio
label={<T id={'non_inventory'} />}
value="non-inventory"
/>
<Radio label={<T id={'inventory'} />} value="inventory" />
</RadioGroup>
</FormGroup>
)}
</FastField>
{/*----------- Item name ----------*/}
<FastField name={'name'}>
{({ field, meta: { error, touched } }) => (

View File

@@ -0,0 +1,10 @@
import { formatMessage } from "services/intl";
export const transitionItemTypeKeyToLabel = (itemTypeKey) => {
const table = {
'service': formatMessage({ id: 'service' }),
'inventory': formatMessage({ id: 'inventory' }),
'non-inventory': formatMessage({ id: 'non_inventory' }),
};
return typeof table[itemTypeKey] === 'string' ? table[itemTypeKey] : '';
};

View File

@@ -1,8 +1,7 @@
import React, { useEffect, useCallback, useState, useMemo } from 'react';
import React, { useEffect, useCallback, useMemo } from 'react';
import {
Intent,
Button,
Classes,
Popover,
Menu,
MenuItem,
@@ -14,9 +13,11 @@ import { useParams } from 'react-router-dom';
import { withRouter } from 'react-router';
import { FormattedMessage as T, useIntl } from 'react-intl';
import moment from 'moment';
import classNames from 'classnames';
import Icon from 'components/Icon';
import { compose, saveInvoke } from 'utils';
import { CLASSES } from 'common/classes';
import { useIsValuePassed } from 'hooks';
import { LoadingIndicator, Choose } from 'components';
@@ -221,34 +222,36 @@ function BillsDataTable({
const showEmptyStatus = [
billsCurrentViewId === -1,
billsCurrentPage.length === 0,
].every(condition => condition === true);
].every((condition) => condition === true);
return (
<LoadingIndicator loading={billsLoading && !isLoadedBefore} mount={false}>
<Choose>
<Choose.When condition={showEmptyStatus}>
<BillsEmptyStatus />
</Choose.When>
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<LoadingIndicator loading={billsLoading && !isLoadedBefore} mount={false}>
<Choose>
<Choose.When condition={showEmptyStatus}>
<BillsEmptyStatus />
</Choose.When>
<Choose.Otherwise>
<DataTable
columns={columns}
data={billsCurrentPage}
onFetchData={handleFetchData}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
rowContextMenu={onRowContextMenu}
pagination={true}
pagesCount={billsPageination.pagesCount}
initialPageSize={billsPageination.pageSize}
initialPageIndex={billsPageination.page - 1}
/>
</Choose.Otherwise>
</Choose>
</LoadingIndicator>
<Choose.Otherwise>
<DataTable
columns={columns}
data={billsCurrentPage}
onFetchData={handleFetchData}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
rowContextMenu={onRowContextMenu}
pagination={true}
pagesCount={billsPageination.pagesCount}
initialPageSize={billsPageination.pageSize}
initialPageIndex={billsPageination.page - 1}
/>
</Choose.Otherwise>
</Choose>
</LoadingIndicator>
</div>
);
}
@@ -264,13 +267,13 @@ export default compose(
billsLoading,
billsPageination,
billsTableQuery,
billsCurrentViewId
billsCurrentViewId,
}) => ({
billsCurrentPage,
billsLoading,
billsPageination,
billsTableQuery,
billsCurrentViewId
billsCurrentViewId,
}),
),
withViewDetails(),

View File

@@ -11,10 +11,12 @@ import {
import { withRouter } from 'react-router';
import { FormattedMessage as T, useIntl } from 'react-intl';
import moment from 'moment';
import classNames from 'classnames';
import { compose, saveInvoke } from 'utils';
import { useIsValuePassed } from 'hooks';
import { CLASSES } from 'common/classes';
import { DataTable, Money, Icon, Choose, LoadingIndicator } from 'components';
import PaymentMadesEmptyStatus from './PaymentMadesEmptyStatus';
@@ -182,36 +184,38 @@ function PaymentMadeDataTable({
const showEmptyStatuts = [
paymentMadeCurrentPage.length === 0,
paymentMadesCurrentViewId === -1,
].every(condition => condition === true);
].every((condition) => condition === true);
return (
<LoadingIndicator loading={paymentMadesLoading && !isLoaded}>
<Choose>
<Choose.When condition={showEmptyStatuts}>
<PaymentMadesEmptyStatus />
</Choose.When>
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<LoadingIndicator loading={paymentMadesLoading && !isLoaded}>
<Choose>
<Choose.When condition={showEmptyStatuts}>
<PaymentMadesEmptyStatus />
</Choose.When>
<Choose.Otherwise>
<DataTable
columns={columns}
data={paymentMadeCurrentPage}
onFetchData={handleDataTableFetchData}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
rowContextMenu={onRowContextMenu}
pagination={true}
pagesCount={paymentMadePageination.pagesCount}
initialPageSize={paymentMadeTableQuery.page_size}
initialPageIndex={paymentMadeTableQuery.page - 1}
autoResetSortBy={false}
autoResetPage={false}
/>
</Choose.Otherwise>
</Choose>
</LoadingIndicator>
<Choose.Otherwise>
<DataTable
columns={columns}
data={paymentMadeCurrentPage}
onFetchData={handleDataTableFetchData}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
rowContextMenu={onRowContextMenu}
pagination={true}
pagesCount={paymentMadePageination.pagesCount}
initialPageSize={paymentMadeTableQuery.page_size}
initialPageIndex={paymentMadeTableQuery.page - 1}
autoResetSortBy={false}
autoResetPage={false}
/>
</Choose.Otherwise>
</Choose>
</LoadingIndicator>
</div>
);
}
@@ -225,13 +229,13 @@ export default compose(
paymentMadesLoading,
paymentMadePageination,
paymentMadeTableQuery,
paymentMadesCurrentViewId
paymentMadesCurrentViewId,
}) => ({
paymentMadeCurrentPage,
paymentMadesLoading,
paymentMadePageination,
paymentMadeTableQuery,
paymentMadesCurrentViewId
paymentMadesCurrentViewId,
}),
),
)(PaymentMadeDataTable);

View File

@@ -63,7 +63,9 @@ function PaymentReceiveFormPage({
fetchAccounts.isFetching ||
// fetchSettings.isFetching ||
fetchCustomers.isFetching
}>
}
name={'payment-receive-form'}
>
<PaymentReceiveForm
paymentReceiveId={paymentReceiveId}
/>

View File

@@ -11,10 +11,13 @@ import {
import { withRouter } from 'react-router';
import { FormattedMessage as T, useIntl } from 'react-intl';
import moment from 'moment';
import classNames from 'classnames';
import { compose, saveInvoke } from 'utils';
import { useIsValuePassed } from 'hooks';
import { CLASSES } from 'common/classes';
import PaymentReceivesEmptyStatus from './PaymentReceivesEmptyStatus';
import { LoadingIndicator, DataTable, Choose, Money, Icon } from 'components';
@@ -186,6 +189,7 @@ function PaymentReceivesDataTable({
].every(condition => condition === true);
return (
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<LoadingIndicator
loading={paymentReceivesLoading && !isLoaded}
mount={false}
@@ -216,6 +220,7 @@ function PaymentReceivesDataTable({
</Choose.Otherwise>
</Choose>
</LoadingIndicator>
</div>
);
}

View File

@@ -11,10 +11,12 @@ import {
import { withRouter } from 'react-router';
import { FormattedMessage as T, useIntl } from 'react-intl';
import moment from 'moment';
import classNames from 'classnames';
import { compose, saveInvoke } from 'utils';
import { useIsValuePassed } from 'hooks';
import { CLASSES } from 'common/classes';
import { Choose, LoadingIndicator, DataTable, Money, Icon } from 'components';
import ReceiptsEmptyStatus from './ReceiptsEmptyStatus';
@@ -192,36 +194,38 @@ function ReceiptsDataTable({
const showEmptyStatus = [
receiptsCurrentViewId === -1,
receiptsCurrentPage.length === 0,
].every(condition => condition === true);
].every((condition) => condition === true);
return (
<LoadingIndicator loading={receiptsLoading && !isLoadedBefore}>
<Choose>
<Choose.When condition={showEmptyStatus}>
<ReceiptsEmptyStatus />
</Choose.When>
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<LoadingIndicator loading={receiptsLoading && !isLoadedBefore}>
<Choose>
<Choose.When condition={showEmptyStatus}>
<ReceiptsEmptyStatus />
</Choose.When>
<Choose.Otherwise>
<DataTable
columns={columns}
data={receiptsCurrentPage}
onFetchData={handleDataTableFetchData}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
rowContextMenu={onRowContextMenu}
pagination={true}
pagesCount={receiptsPagination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
initialPageSize={receiptTableQuery.page_size}
initialPageIndex={receiptTableQuery.page - 1}
/>
</Choose.Otherwise>
</Choose>
</LoadingIndicator>
<Choose.Otherwise>
<DataTable
columns={columns}
data={receiptsCurrentPage}
onFetchData={handleDataTableFetchData}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
rowContextMenu={onRowContextMenu}
pagination={true}
pagesCount={receiptsPagination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
initialPageSize={receiptTableQuery.page_size}
initialPageIndex={receiptTableQuery.page - 1}
/>
</Choose.Otherwise>
</Choose>
</LoadingIndicator>
</div>
);
}
@@ -236,13 +240,13 @@ export default compose(
receiptsLoading,
receiptsPagination,
receiptTableQuery,
receiptsCurrentViewId
receiptsCurrentViewId,
}) => ({
receiptsCurrentPage,
receiptsLoading,
receiptsPagination,
receiptTableQuery,
receiptsCurrentViewId
receiptsCurrentViewId,
}),
),
)(ReceiptsDataTable);

View File

@@ -7,12 +7,17 @@ import App from 'components/App';
import * as serviceWorker from 'serviceWorker';
import createStore from 'store/createStore';
import AppProgress from 'components/NProgress/AppProgress';
import { setLocale } from 'yup';
import {locale} from 'lang/en/locale';
import whyDidYouRender from "@welldone-software/why-did-you-render";
whyDidYouRender(React, {
onlyLogs: true,
titleColor: "green",
diffNameColor: "aqua"
});
setLocale(locale)
ReactDOM.render(
<Provider store={createStore}>
<BrowserRouter>

View File

@@ -213,8 +213,11 @@ export default {
once_delete_this_item_you_will_able_to_restore_it: `Once you delete this item, you won\'t be able to restore the item later. Are you sure you want to delete ?<br /><br />If you're not sure, you can inactivate it instead.`,
the_item_has_been_successfully_deleted:
'The item has been successfully deleted.',
the_item_has_been_created_successfully:
'The item has been created successfully.',
the_item_has_been_successfully_edited:
'The item #{number} has been successfully edited.',
the_item_category_has_been_successfully_created:
'The item category has been successfully created.',
the_item_category_has_been_successfully_edited:
@@ -819,4 +822,5 @@ export default {
the_name_used_before: 'The name is already used.',
the_item_has_associated_transactions: 'The item has associated transactions.',
customer_has_sales_invoices: 'Customer has sales invoices',
account_name_is_already_used: 'Account name is already used.',
};

View File

@@ -4,6 +4,7 @@ import { createReducer } from '@reduxjs/toolkit';
const initialState = {
pageTitle: '',
pageSubtitle: '',
pageHint: '',
preferencesPageTitle: '',
sidebarExpended: true,
dialogs: {},
@@ -20,6 +21,10 @@ export default createReducer(initialState, {
state.pageSubtitle = action.pageSubtitle;
},
[t.CHANGE_DASHBOARD_PAGE_HINT]: (state, action) => {
state.pageHint = action.pageHint;
},
[t.CHANGE_PREFERENCES_PAGE_TITLE]: (state, action) => {
state.preferencesPageTitle = action.pageTitle;
},

View File

@@ -6,6 +6,7 @@ export default {
CLOSE_DIALOG: 'CLOSE_DIALOG',
CLOSE_ALL_DIALOGS: 'CLOSE_ALL_DIALOGS',
CHANGE_DASHBOARD_PAGE_TITLE: 'CHANGE_DASHBOARD_PAGE_TITLE',
CHANGE_DASHBOARD_PAGE_HINT: 'CHANGE_DASHBOARD_PAGE_HINT',
CHANGE_PREFERENCES_PAGE_TITLE: 'CHANGE_PREFERENCES_PAGE_TITLE',
ALTER_DASHBOARD_PAGE_SUBTITLE: 'ALTER_DASHBOARD_PAGE_SUBTITLE',
SET_TOPBAR_EDIT_VIEW: 'SET_TOPBAR_EDIT_VIEW',

View File

@@ -1,4 +1,4 @@
import { omit } from 'lodash';
import { omit, flatten } from 'lodash';
import ApiService from 'services/ApiService';
import t from 'store/types';
@@ -135,7 +135,14 @@ export const fetchManualJournalsTable = ({ query } = {}) => {
});
dispatch({
type: t.MANUAL_JOURNALS_ITEMS_SET,
manual_journals: response.data.manual_journals,
manual_journals: [
...response.data.manual_journals.map((manualJournal) => ({
...manualJournal,
entries: manualJournal.entries.map((entry) => ({
...omit(entry, ['account']),
}))
})),
]
});
dispatch({
type: t.MANUAL_JOURNALS_PAGINATION_SET,
@@ -145,6 +152,12 @@ export const fetchManualJournalsTable = ({ query } = {}) => {
response.data.manual_journals?.viewMeta?.customViewId || -1,
},
});
dispatch({
type: t.ACCOUNTS_ITEMS_SET,
accounts: flatten(response.data.manual_journals?.map(
journal => journal?.entries.map(entry => entry.account),
)),
});
dispatch({
type: t.MANUAL_JOURNALS_TABLE_LOADING,
loading: false,

View File

@@ -204,17 +204,20 @@ body.authentication {
}
// .page-form
// .page-form__header
// .page-form__content
// .page-form__floating-actions
.page-form{
&__header{
padding: 20px;
&__header{
background-color: #fbfbfb;
padding: 30px 20px 20px;
padding-bottom: 6px;
}
&__primary-section{
background-color: #fbfbfb;
padding: 30px 20px 20px;
margin: -20px;
padding-bottom: 6px;
}
&__header-fields{

View File

@@ -6,7 +6,7 @@
.table .thead{
.th{
border-bottom-color: #eaeaea;
border-bottom-color: #D2DDE2;
}
}
}
@@ -25,11 +25,10 @@
padding: 0.6rem 0.5rem;
background: #fafafa;
font-size: 14px;
color: #445165;
color: #58667b;
font-weight: 500;
border-bottom: 1px solid rgb(224, 224, 224);
}
.sort-icon{
width: 0;
height: 0;
@@ -145,7 +144,7 @@
}
}
.tr:hover .td{
background: #fafafa;
background: #f3f7fc;
}
.tr.is-context-menu-active .td{

View File

@@ -1,6 +1,8 @@
.Pane.Pane2 {
overflow: auto;
overflow: auto;
display: flex;
flex-direction: column;
}
.Resizer {

View File

@@ -19,13 +19,20 @@ $form-check-input-indeterminate-bg-image: url("data:image/svg+xml,<svg xmlns='ht
}
// Form
.bp3-label{
label.bp3-label{
color: #353535;
font-weight: 400;
.required{
color: red;
}
.bp3-form-group.bp3-inline &{
margin: 0 10px 0 0;
line-height: 1.6;
padding-top: calc(.3rem + 1px);
padding-bottom: calc(.3rem + 1px);
}
}
.#{$ns}-input{

View File

@@ -1,4 +1,6 @@
.dashboard__insider--bill-form{
background-color: #FFF;
}
.page-form--bill{
$self: '.page-form';

View File

@@ -8,9 +8,11 @@
padding: 0;
}
#{$self}__primary-section{
padding: 30px 22px 0;
margin: -20px -20px 14px;
padding: 10px 0 0;
margin: 0 0 20px;
overflow: hidden;
border-bottom: 1px solid #e4e4e4;
max-width: 1000px;
}
.bp3-form-group{
@@ -41,7 +43,6 @@
&:not(:last-child) {
padding-right: 10px;
}
&.input-group--salutation-list{
width: 25%;
}
@@ -158,8 +159,8 @@
}
#{$self}__floating-actions {
margin-left: -20px;
margin-right: -20px;
margin-left: -40px;
margin-right: -40px;
}
}

View File

@@ -4,12 +4,12 @@
display: flex;
height: 100vh;
&__topbar{
width: 100%;
min-height: 60px;
display: flex;
justify-content: space-between;
background-color: #fff;
border-bottom: 1px solid #F2EFEF;
&-right,
@@ -121,7 +121,7 @@
&,
&-group{
height: 42px;
height: 40px;
}
.#{$ns}-navbar-divider{
@@ -138,7 +138,7 @@
}
&.bp3-minimal:active,
&.bp3-minimal.bp3-active{
background: rgba(167, 182, 194, 0.12);
background: #a7b6c21f;
color: #32304a;
}
@@ -150,7 +150,7 @@
}
}
.#{$ns}-icon{
color: #2A293D;
color: #32304a;
margin-right: 7px;
}
&.#{$ns}-minimal.#{$ns}-intent-danger{
@@ -203,7 +203,7 @@
h1{
font-size: 22px;
color: #333363;
color: #48485c;
font-weight: 400;
margin: 0;
}
@@ -233,13 +233,17 @@
}
}
&__hint{
display: inline-block;
margin-top: 4px;
margin-left: 4px;
}
&__subtitle{
}
&__insider{
margin-bottom: 40px;
flex: 1 0 0;
}
&__offline-badge{
@@ -260,6 +264,7 @@
display: flex;
flex-direction: column;
height: 100%;
min-width: 850px;
&:before{
content: "";
@@ -279,7 +284,9 @@
&__insider{
display: flex;
flex-direction: column;
flex: 1 0 0;
background-color: #FBFBFB;
> .dashboard__loading-indicator{
margin-top: auto;
margin-bottom: auto;
@@ -313,6 +320,23 @@
display: flex;
flex: 1 0 0;
flex-direction: column;
background: #fff;
margin: 20px;
border: 1px solid #d2dce2;
.bigcapital-datatable{
display: flex;
flex-direction: column;
flex: 1 0 0;
.pagination{
margin-top: auto;
}
&:not(.has-pagination){
padding-bottom: 30px;
}
}
.datatable-empty-status{
margin-top: auto;
@@ -323,7 +347,6 @@
&__preferences-topbar{
border-bottom: 1px solid #E5E5E5;
// height: 70px;
height: 65px;
padding: 0 0 0 22px;
display: flex;
@@ -362,6 +385,43 @@
}
}
}
&__card{
border: 1px solid #d2dce2;
background: #fff;
&--page{
flex: 1 0 0;
margin: 20px;
}
}
&__error-boundary{
text-align: center;
margin-top: auto;
margin-bottom: auto;
h1{
font-size: 26px;
font-weight: 600;
margin: 0px 0 10px;
color: #2c3a5d;
}
p{
font-size: 16px;
color: #1f3255;
opacity: 0.8;
}
.bp3-icon{
margin-top: 6px;
path{
fill: #a1afca;
}
}
}
}
.tabs--dashboard-views{
@@ -408,7 +468,7 @@
.navbar--dashboard-views{
box-shadow: 0 0 0;
border-bottom: 1px solid #EAEAEA;
border-bottom: 1px solid #d2dce2;
}
.navbar-omnibar{

View File

@@ -1,3 +1,7 @@
.dashboard__insider--estimate-form{
background-color: #FFF;
}
.estimate-form {
padding-bottom: 30px;
display: flex;
@@ -185,195 +189,3 @@
}
}
// .estimate-form {
// padding-bottom: 30px;
// display: flex;
// flex-direction: column;
// .bp3-form-group {
// margin: 25px 20px 15px;
// width: 100%;
// .bp3-label {
// font-weight: 500;
// font-size: 13px;
// color: #444;
// width: 130px;
// }
// .bp3-form-content {
// // width: 400px;
// width: 45%;
// }
// }
// // .expense-form-footer {
// // display: flex;
// // padding: 30px 25px 0;
// // justify-content: space-between;
// // }
// &__primary-section {
// background: #fbfbfb;
// }
// &__table {
// padding: 15px 15px 0;
// .bp3-form-group {
// margin-bottom: 0;
// }
// .table {
// border: 1px dotted rgb(195, 195, 195);
// border-bottom: transparent;
// border-left: transparent;
// .th,
// .td {
// border-left: 1px dotted rgb(195, 195, 195);
// &.index {
// > span,
// > div {
// text-align: center;
// width: 100%;
// font-weight: 500;
// }
// }
// }
// .thead {
// .tr .th {
// padding: 10px 10px;
// background-color: #f2f5fa;
// font-size: 14px;
// font-weight: 500;
// color: #333;
// }
// }
// .tbody {
// .tr .td {
// padding: 7px;
// border-bottom: 1px dotted rgb(195, 195, 195);
// min-height: 46px;
// &.index {
// background-color: #f2f5fa;
// text-align: center;
// > span {
// margin-top: auto;
// margin-bottom: auto;
// }
// }
// }
// .tr {
// .bp3-form-group .bp3-input,
// .form-group--select-list .bp3-button {
// border-radius: 3px;
// padding-left: 8px;
// padding-right: 8px;
// }
// .bp3-form-group:not(.bp3-intent-danger) .bp3-input,
// .form-group--select-list:not(.bp3-intent-danger) .bp3-button {
// border-color: #e5e5e5;
// }
// &:last-of-type {
// .td {
// border-bottom: transparent;
// .bp3-button,
// .bp3-input-group {
// display: none;
// }
// }
// }
// .td.actions {
// .bp3-button {
// background-color: transparent;
// color: #e68f8e;
// &:hover {
// color: #c23030;
// }
// }
// }
// &.row--total {
// .td.amount {
// font-weight: bold;
// }
// }
// }
// }
// .th {
// color: #444;
// font-weight: 600;
// border-bottom: 1px dotted #666;
// }
// .td {
// border-bottom: 1px dotted #999;
// &.description {
// .bp3-form-group {
// width: 100%;
// }
// }
// }
// .actions.td {
// .bp3-button {
// background: transparent;
// margin: 0;
// }
// }
// }
// }
// &__floating-footer {
// position: fixed;
// bottom: 0;
// width: 100%;
// background: #fff;
// padding: 18px 18px;
// border-top: 1px solid #ececec;
// .has-mini-sidebar & {
// left: 50px;
// }
// }
// .bp3-button {
// &.button--clear-lines {
// background-color: #fcefef;
// }
// }
// .button--clear-lines,
// .button--new-line {
// padding-left: 14px;
// padding-right: 14px;
// }
// .dropzone-container {
// margin-top: 0;
// align-self: flex-end;
// }
// .dropzone {
// width: 300px;
// height: 75px;
// }
// .form-group--description {
// .bp3-label {
// font-weight: 500;
// font-size: 13px;
// color: #444;
// }
// .bp3-form-content {
// // width: 280px;
// textarea {
// width: 450px;
// min-height: 75px;
// }
// }
// }
// }

View File

@@ -1,3 +1,7 @@
.dashboard__insider--estimate-form{
background-color: #FFF;
}
.page-form--estimate{
$self: '.page-form';

View File

@@ -2,6 +2,7 @@
padding-bottom: 80px;
display: flex;
flex-direction: column;
background: #fff;
&__header {
padding: 25px 27px 20px;

View File

@@ -285,15 +285,18 @@
&__list{
display: flex;
flex-flow: wrap;
margin-left: -28px;
margin-left: -20px;
}
&__item{
width: 270px;
margin-bottom: 40px;
margin-left: 28px;
border-top: 2px solid #DDD;
margin-bottom: 20px;
margin-left: 20px;
border: 1px solid #d1dee2;
border-top: 3px solid #d1dee2;
padding-top: 16px;
background: #fff;
padding: 20px;
.title{
font-size: 16px;
@@ -303,6 +306,7 @@
color: rgb(31, 50, 85);
line-height: 1.55;
margin-top: 12px;
margin-bottom: 0;
}
}
}

View File

@@ -1,4 +1,6 @@
.dashboard__insider--invoice-form{
background-color: #FFF;
}
.page-form--invoice{
$self: '.page-form';

View File

@@ -1,5 +1,4 @@
.page-form--item{
$self: '.page-form';
padding: 20px;
@@ -8,9 +7,12 @@
padding: 0;
}
#{$self}__primary-section{
padding: 30px 22px 0;
margin: -20px -20px 24px;
overflow: hidden;
padding-top: 10px;
margin-bottom: 20px;
border-bottom: 1px solid #eaeaea;
padding-bottom: 5px;
max-width: 1000px;
}
#{$self}__body{
@@ -65,8 +67,8 @@
}
#{$self}__floating-actions{
margin-left: -20px;
margin-right: -20px;
margin-left: -40px;
margin-right: -40px;
.form-group--active{
display: inline-block;

View File

@@ -58,4 +58,11 @@
min-width: 75px;
}
}
}
.dashboard__insider{
&--make-journal-page{
background: #fff;
}
}

View File

@@ -9,8 +9,13 @@
}
.tbody{
.amount > span{
font-weight: 600;
.td.amount{
.bp3-popover-target{
border-bottom: 1px solid #e7e7e7;
}
> span{
font-weight: 600;
}
}
.note{
.bp3-icon{

View File

@@ -1,4 +1,6 @@
.dashboard__insider--payment-made{
background-color: #FFF;
}
.page-form--payment-made {
$self: '.page-form';

View File

@@ -1,4 +1,7 @@
.dashboard__insider--payment-receive-form{
background-color: #FFF;
}
.page-form--payment-receive {
$self: '.page-form';

View File

@@ -1,4 +1,6 @@
.dashboard__insider--receipt-form{
background-color: #fff;
}
.page-form--receipt{
$self: '.page-form';

View File

@@ -107,7 +107,7 @@ $sidebar-submenu-item-bg-color: #01287d;
padding-top: 6px;
}
.#{$ns}-menu-item {
padding: 7px 16px;
padding: 8px 16px;
font-size: 15px;
color: $sidebar-submenu-item-color;

View File

@@ -198,7 +198,11 @@ export default class AccountsController extends BaseController{
try {
const account = await this.accountsService.editAccount(tenantId, accountId, accountDTO);
return res.status(200).send({ id: account.id });
return res.status(200).send({
id: account.id,
message: 'The account has been edited successfully',
});
} catch (error) {
next(error);
}

View File

@@ -107,10 +107,12 @@ export default class AccountRepository extends TenantRepository {
* @param {IAccount} account
* @return {void}
*/
async edit(accountId: number, account: IAccount): Promise<void> {
async edit(accountId: number, accountInput: IAccount): Promise<void> {
const { Account } = this.models;
await Account.query().findById(accountId).patch({ ...account });
const account = await Account.query().patchAndFetchById(accountId, { ...accountInput });
this.flushCache();
return account;
}
/**

View File

@@ -203,6 +203,8 @@ export default class AccountsService {
* @param {IAccountDTO} accountDTO
*/
public async editAccount(tenantId: number, accountId: number, accountDTO: IAccountDTO) {
this.logger.info('[account] trying to edit account.', { tenantId, accountId });
const { accountRepository } = this.tenancy.repositories(tenantId);
const oldAccount = await this.getAccountOrThrowError(tenantId, accountId);

View File

@@ -407,6 +407,7 @@ export default class ManualJournalsService implements IManualJournalsService {
this.logger.info('[manual_journals] trying to get manual journals list.', { tenantId, filter });
const { results, pagination } = await ManualJournal.query().onBuild((builder) => {
dynamicList.buildQuery()(builder);
builder.withGraphFetched('entries.account');
}).pagination(filter.page - 1, filter.pageSize);
return {