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

View File

@@ -2,6 +2,9 @@ import { Classes } from '@blueprintjs/core';
const CLASSES = { const CLASSES = {
DASHBOARD_DATATABLE: 'dashboard__datatable', DASHBOARD_DATATABLE: 'dashboard__datatable',
DASHBOARD_CARD: 'dashboard__card',
DASHBOARD_CARD_PAGE: 'dashboard__card--page',
DATATABLE_EDITOR: 'datatable-editor', DATATABLE_EDITOR: 'datatable-editor',
DATATABLE_EDITOR_ACTIONS: 'datatable-editor__actions', DATATABLE_EDITOR_ACTIONS: 'datatable-editor__actions',
DATATABLE_EDITOR_ITEMS_ENTRIES: 'items-entries-table', DATATABLE_EDITOR_ITEMS_ENTRIES: 'items-entries-table',
@@ -13,7 +16,7 @@ const CLASSES = {
PAGE_FORM_HEADER_BIG_NUMBERS: 'page-form__big-numbers', PAGE_FORM_HEADER_BIG_NUMBERS: 'page-form__big-numbers',
PAGE_FORM_TABS: 'page-form__tabs', PAGE_FORM_TABS: 'page-form__tabs',
PAGE_FORM_BODY: 'page-form__body', PAGE_FORM_BODY: 'page-form__body',
PAGE_FORM_FOOTER: 'page-form__footer', PAGE_FORM_FOOTER: 'page-form__footer',
PAGE_FORM_FLOATING_ACTIONS: 'page-form__floating-actions', 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 React from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import DashboardTopbar from 'components/Dashboard/DashboardTopbar'; import DashboardTopbar from 'components/Dashboard/DashboardTopbar';
import DashboardContentRoute from 'components/Dashboard/DashboardContentRoute'; import DashboardContentRoute from 'components/Dashboard/DashboardContentRoute';
import DashboardFooter from 'components/Dashboard/DashboardFooter'; import DashboardFooter from 'components/Dashboard/DashboardFooter';
import DashboardErrorBoundary from './DashboardErrorBoundary';
export default function() { export default function () {
return ( return (
<div className="dashboard-content" id="dashboard"> <ErrorBoundary FallbackComponent={DashboardErrorBoundary}>
<DashboardTopbar /> <div className="dashboard-content" id="dashboard">
<DashboardContentRoute /> <DashboardTopbar />
<DashboardContentRoute />
<DashboardFooter /> <DashboardFooter />
</div> </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 React from 'react';
import { connect } from 'react-redux';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import { import {
Navbar, Navbar,
@@ -14,7 +13,7 @@ import { FormattedMessage as T } from 'react-intl';
import DashboardTopbarUser from 'components/Dashboard/TopbarUser'; import DashboardTopbarUser from 'components/Dashboard/TopbarUser';
import DashboardBreadcrumbs from 'components/Dashboard/DashboardBreadcrumbs'; import DashboardBreadcrumbs from 'components/Dashboard/DashboardBreadcrumbs';
import { Icon, If } from 'components'; import { Icon, Hint, If } from 'components';
import withSearch from 'containers/GeneralSearch/withSearch'; import withSearch from 'containers/GeneralSearch/withSearch';
import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withDashboardActions from 'containers/Dashboard/withDashboardActions';
@@ -78,8 +77,18 @@ function DashboardTopbar({
<div class="dashboard__title"> <div class="dashboard__title">
<h1>{pageTitle}</h1> <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}> <If condition={pageSubtitle}>
<h3>{ pageSubtitle }</h3> <h3>{pageSubtitle}</h3>
</If> </If>
<If condition={pageSubtitle && editViewId}> <If condition={pageSubtitle && editViewId}>

View File

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

View File

@@ -2,11 +2,15 @@ import React from 'react';
import { Tooltip, Position } from '@blueprintjs/core'; import { Tooltip, Position } from '@blueprintjs/core';
import Icon from './Icon'; import Icon from './Icon';
export default function FieldHint({ content, position }) { export default function FieldHint({
content,
position,
iconSize = 12
}) {
return ( return (
<span class="hint"> <span class="hint">
<Tooltip content={content} position={position}> <Tooltip content={content} position={position}>
<Icon icon="info-circle" iconSize={12} /> <Icon icon="info-circle" iconSize={iconSize} />
</Tooltip> </Tooltip>
</span> </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 MoneyInputGroup from './MoneyInputGroup';
import Dragzone from './Dragzone'; import Dragzone from './Dragzone';
import EmptyStatus from './EmptyStatus'; import EmptyStatus from './EmptyStatus';
import DashboardCard from './Dashboard/DashboardCard';
const Hint = FieldHint; const Hint = FieldHint;
@@ -81,5 +82,6 @@ export {
SalutationList, SalutationList,
MoneyInputGroup, MoneyInputGroup,
Dragzone, Dragzone,
EmptyStatus EmptyStatus,
DashboardCard,
}; };

View File

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

View File

@@ -1,19 +1,18 @@
import React, { useEffect, useCallback, useState, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import { import {
Intent, Intent,
Button, Button,
Classes,
Popover, Popover,
Tooltip, Tooltip,
Menu, Menu,
MenuItem, MenuItem,
MenuDivider, MenuDivider,
Position, Position,
Tag,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { withRouter, useParams } from 'react-router-dom'; 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 moment from 'moment';
import classNames from 'classnames';
import { import {
DataTable, DataTable,
@@ -23,9 +22,11 @@ import {
Icon, Icon,
LoadingIndicator, LoadingIndicator,
} from 'components'; } from 'components';
import { CLASSES } from 'common/classes';
import { useIsValuePassed } from 'hooks'; import { useIsValuePassed } from 'hooks';
import ManualJournalsEmptyStatus from './ManualJournalsEmptyStatus'; import ManualJournalsEmptyStatus from './ManualJournalsEmptyStatus';
import { AmountPopoverContent, NoteAccessor, StatusAccessor } from './components';
import withDialogActions from 'containers/Dialog/withDialogActions'; import withDialogActions from 'containers/Dialog/withDialogActions';
import withManualJournals from 'containers/Accounting/withManualJournals'; import withManualJournals from 'containers/Accounting/withManualJournals';
@@ -33,44 +34,7 @@ import withManualJournalsActions from 'containers/Accounting/withManualJournalsA
import { compose, saveInvoke } from 'utils'; 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({ function ManualJournalsDataTable({
// #withManualJournals // #withManualJournals
@@ -166,7 +130,14 @@ function ManualJournalsDataTable({
{ {
id: 'amount', id: 'amount',
Header: formatMessage({ 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', className: 'amount',
width: 115, width: 115,
}, },
@@ -254,37 +225,39 @@ function ManualJournalsDataTable({
const showEmptyStatus = [ const showEmptyStatus = [
manualJournalsCurrentViewId === -1, manualJournalsCurrentViewId === -1,
manualJournalsCurrentPage.length === 0, manualJournalsCurrentPage.length === 0,
].every(condition => condition === true); ].every((condition) => condition === true);
return ( return (
<LoadingIndicator loading={manualJournalsLoading && !isLoadedBefore}> <div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<Choose> <LoadingIndicator loading={manualJournalsLoading && !isLoadedBefore}>
<Choose.When condition={showEmptyStatus}> <Choose>
<ManualJournalsEmptyStatus /> <Choose.When condition={showEmptyStatus}>
</Choose.When> <ManualJournalsEmptyStatus />
</Choose.When>
<Choose.Otherwise> <Choose.Otherwise>
<DataTable <DataTable
noInitialFetch={true} noInitialFetch={true}
columns={columns} columns={columns}
data={manualJournalsCurrentPage} data={manualJournalsCurrentPage}
onFetchData={handleDataTableFetchData} onFetchData={handleDataTableFetchData}
manualSortBy={true} manualSortBy={true}
selectionColumn={true} selectionColumn={true}
expandable={true} expandable={true}
sticky={true} sticky={true}
onSelectedRowsChange={handleSelectedRowsChange} onSelectedRowsChange={handleSelectedRowsChange}
rowContextMenu={onRowContextMenu} rowContextMenu={onRowContextMenu}
pagesCount={manualJournalsPagination.pagesCount} pagesCount={manualJournalsPagination.pagesCount}
pagination={true} pagination={true}
initialPageSize={manualJournalsTableQuery.page_size} initialPageSize={manualJournalsTableQuery.page_size}
initialPageIndex={manualJournalsTableQuery.page - 1} initialPageIndex={manualJournalsTableQuery.page - 1}
autoResetSortBy={false} autoResetSortBy={false}
autoResetPage={false} autoResetPage={false}
/> />
</Choose.Otherwise> </Choose.Otherwise>
</Choose> </Choose>
</LoadingIndicator> </LoadingIndicator>
</div>
); );
} }
@@ -304,7 +277,7 @@ export default compose(
manualJournalsLoading, manualJournalsLoading,
manualJournalsPagination, manualJournalsPagination,
manualJournalsTableQuery, manualJournalsTableQuery,
manualJournalsCurrentViewId manualJournalsCurrentViewId,
}), }),
), ),
)(ManualJournalsDataTable); )(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'; } from '@blueprintjs/core';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
import classnames from 'classnames'; import classNames from 'classnames';
import { import { Icon, DataTable, Money, If, Choose } from 'components';
Icon,
DataTable,
Money,
If,
Choose,
} from 'components';
import { compose } from 'utils'; import { compose } from 'utils';
import { useUpdateEffect } from 'hooks'; import { useUpdateEffect } from 'hooks';
import { CLASSES } from 'common/classes';
import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withAccountsActions from 'containers/Accounts/withAccountsActions'; import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withAccounts from 'containers/Accounts/withAccounts'; import withAccounts from 'containers/Accounts/withAccounts';
@@ -30,6 +26,7 @@ import withDialogActions from 'containers/Dialog/withDialogActions';
import withCurrentView from 'containers/Views/withCurrentView'; import withCurrentView from 'containers/Views/withCurrentView';
function NormalCell({ cell }) { function NormalCell({ cell }) {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
@@ -52,7 +49,7 @@ function NormalCell({ cell }) {
function BalanceCell({ cell }) { function BalanceCell({ cell }) {
const account = cell.row.original; const account = cell.row.original;
return (account.amount) ? ( return account.amount ? (
<span> <span>
<Money amount={account.amount} currency={'USD'} /> <Money amount={account.amount} currency={'USD'} />
</span> </span>
@@ -64,13 +61,14 @@ function BalanceCell({ cell }) {
function InactiveSemafro() { function InactiveSemafro() {
return ( return (
<Tooltip <Tooltip
content={<T id='inactive' />} content={<T id="inactive" />}
className={classnames( className={classNames(
Classes.TOOLTIP_INDICATOR, Classes.TOOLTIP_INDICATOR,
'bp3-popover-wrapper--inactive-semafro' 'bp3-popover-wrapper--inactive-semafro',
)} )}
position={Position.TOP} position={Position.TOP}
hoverOpenDelay={250}> hoverOpenDelay={250}
>
<div className="inactive-semafro"></div> <div className="inactive-semafro"></div>
</Tooltip> </Tooltip>
); );
@@ -82,7 +80,7 @@ function AccountNameAccessor(row) {
<Choose> <Choose>
<Choose.When condition={!!row.description}> <Choose.When condition={!!row.description}>
<Tooltip <Tooltip
className={classnames( className={classNames(
Classes.TOOLTIP_INDICATOR, Classes.TOOLTIP_INDICATOR,
'bp3-popover-wrapper--account-desc', 'bp3-popover-wrapper--account-desc',
)} )}
@@ -94,9 +92,7 @@ function AccountNameAccessor(row) {
</Tooltip> </Tooltip>
</Choose.When> </Choose.When>
<Choose.Otherwise> <Choose.Otherwise>{row.name}</Choose.Otherwise>
{ row.name }
</Choose.Otherwise>
</Choose> </Choose>
<If condition={!row.active}> <If condition={!row.active}>
@@ -159,7 +155,8 @@ function AccountsDataTable({
<Menu> <Menu>
<MenuItem <MenuItem
icon={<Icon icon="reader-18" />} icon={<Icon icon="reader-18" />}
text={formatMessage({ id: 'view_details' })} /> text={formatMessage({ id: 'view_details' })}
/>
<MenuDivider /> <MenuDivider />
<MenuItem <MenuItem
icon={<Icon icon="pen-18" />} icon={<Icon icon="pen-18" />}
@@ -287,20 +284,22 @@ function AccountsDataTable({
); );
return ( return (
<DataTable <div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
noInitialFetch={true} <DataTable
columns={columns} noInitialFetch={true}
data={accountsTable} columns={columns}
onFetchData={handleDatatableFetchData} data={accountsTable}
manualSortBy={true} onFetchData={handleDatatableFetchData}
selectionColumn={selectionColumn} manualSortBy={true}
expandable={true} selectionColumn={selectionColumn}
sticky={true} expandable={true}
onSelectedRowsChange={handleSelectedRowsChange} sticky={true}
loading={accountsLoading && !isMounted} onSelectedRowsChange={handleSelectedRowsChange}
rowContextMenu={rowContextMenu} loading={accountsLoading && !isMounted}
expandColumnSpace={1} rowContextMenu={rowContextMenu}
/> expandColumnSpace={1}
/>
</div>
); );
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -88,6 +88,9 @@ function AccountFormDialogContent({
if (errors.find((e) => e.type === 'NOT_UNIQUE_CODE')) { if (errors.find((e) => e.type === 'NOT_UNIQUE_CODE')) {
fields.code = formatMessage({ id: 'account_code_is_not_unique' }); 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; return fields;
}; };

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,11 @@
import React, { useState, useMemo, useCallback, useEffect } from 'react'; import React, { useState, useMemo, useCallback, useEffect } from 'react';
import * as Yup from 'yup';
import { Formik, Form } from 'formik'; import { Formik, Form } from 'formik';
import { Intent } from '@blueprintjs/core'; import { Intent } from '@blueprintjs/core';
import { queryCache } from 'react-query'; import { queryCache } from 'react-query';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { defaultTo } from 'lodash';
import { CLASSES } from 'common/classes'; import { CLASSES } from 'common/classes';
import AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
@@ -22,6 +22,8 @@ import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withSettings from 'containers/Settings/withSettings'; import withSettings from 'containers/Settings/withSettings';
import { compose, transformToForm } from 'utils'; import { compose, transformToForm } from 'utils';
import { transitionItemTypeKeyToLabel } from './utils';
import { EditItemFormSchema, CreateItemFormSchema } from './ItemForm.schema';
const defaultInitialValues = { const defaultInitialValues = {
active: true, active: true,
@@ -83,66 +85,15 @@ function ItemForm({
deleteCallback: requestDeleteMedia, 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. * Initial values in create and edit mode.
*/ */
const initialValues = useMemo( const initialValues = useMemo(
() => ({ () => ({
...defaultInitialValues, ...defaultInitialValues,
cost_account_id: parseInt(preferredCostAccount), cost_account_id: defaultTo(preferredCostAccount, ''),
sell_account_id: parseInt(preferredSellAccount), sell_account_id: defaultTo(preferredSellAccount, ''),
inventory_account_id: parseInt(preferredInventoryAccount), inventory_account_id: defaultTo(preferredInventoryAccount, ''),
/** /**
* We only care about the fields in the form. Previously unfilled optional * 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 * values such as `notes` come back from the API as null, so remove those
@@ -150,7 +101,12 @@ function ItemForm({
*/ */
...transformToForm(itemDetail, defaultInitialValues), ...transformToForm(itemDetail, defaultInitialValues),
}), }),
[], [
itemDetail,
preferredCostAccount,
preferredSellAccount,
preferredInventoryAccount,
],
); );
useEffect(() => { useEffect(() => {
@@ -214,7 +170,7 @@ function ItemForm({
useEffect(() => { useEffect(() => {
if (itemDetail && itemDetail.type) { if (itemDetail && itemDetail.type) {
changePageSubtitle(formatMessage({ id: itemDetail.type })); changePageSubtitle(transitionItemTypeKeyToLabel(itemDetail.type));
} }
}, [itemDetail, changePageSubtitle, formatMessage]); }, [itemDetail, changePageSubtitle, formatMessage]);
@@ -262,14 +218,14 @@ function ItemForm({
<div class={classNames(CLASSES.PAGE_FORM_ITEM)}> <div class={classNames(CLASSES.PAGE_FORM_ITEM)}>
<Formik <Formik
enableReinitialize={true} enableReinitialize={true}
validationSchema={validationSchema} validationSchema={isNewMode ? CreateItemFormSchema : EditItemFormSchema}
initialValues={initialValues} initialValues={initialValues}
onSubmit={handleFormSubmit} onSubmit={handleFormSubmit}
> >
{({ isSubmitting, handleSubmit }) => ( {({ isSubmitting, handleSubmit }) => (
<Form> <Form>
<div class={classNames(CLASSES.PAGE_FORM_BODY)}> <div class={classNames(CLASSES.PAGE_FORM_BODY)}>
<ItemFormPrimarySection /> <ItemFormPrimarySection itemId={itemId} />
<ItemFormBody /> <ItemFormBody />
<ItemFormInventorySection /> <ItemFormInventorySection />
</div> </div>
@@ -294,8 +250,10 @@ export default compose(
withDashboardActions, withDashboardActions,
withMediaActions, withMediaActions,
withSettings(({ itemsSettings }) => ({ withSettings(({ itemsSettings }) => ({
preferredCostAccount: itemsSettings.preferredCostAccount, preferredCostAccount: parseInt(itemsSettings?.preferredCostAccount),
preferredSellAccount: itemsSettings.preferredSellAccount, preferredSellAccount: parseInt(itemsSettings?.preferredSellAccount),
preferredInventoryAccount: itemsSettings.preferredInventoryAccount, preferredInventoryAccount: parseInt(
itemsSettings?.preferredInventoryAccount,
),
})), })),
)(ItemForm); )(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 React from 'react';
import { FastField, ErrorMessage } from 'formik'; import { FastField, Field, ErrorMessage } from 'formik';
import { import {
FormGroup, FormGroup,
Classes, Classes,
@@ -27,8 +27,8 @@ function ItemFormBody({ accountsList }) {
<Row> <Row>
<Col xs={6}> <Col xs={6}>
{/*------------- Purchasable checbox ------------- */} {/*------------- Purchasable checbox ------------- */}
<FastField name={'sellable'}> <FastField name={'sellable'} type="checkbox">
{({ field, field: { value } }) => ( {({ field: { onChange, onBlur, name, checked } }) => (
<FormGroup inline={true} className={'form-group--sellable'}> <FormGroup inline={true} className={'form-group--sellable'}>
<Checkbox <Checkbox
inline={true} inline={true}
@@ -37,8 +37,10 @@ function ItemFormBody({ accountsList }) {
<T id={'i_sell_this_item'} /> <T id={'i_sell_this_item'} />
</h3> </h3>
} }
checked={value} name={'sellable'}
{...field} checked={!!checked}
onChange={onChange}
onBlur={onBlur}
/> />
</FormGroup> </FormGroup>
)} )}

View File

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

View File

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

View File

@@ -25,6 +25,7 @@ import withAccounts from 'containers/Accounts/withAccounts';
import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import { compose, handleStringChange, inputIntent } from 'utils'; import { compose, handleStringChange, inputIntent } from 'utils';
import { transitionItemTypeKeyToLabel } from './utils';
/** /**
* Item form primary section. * Item form primary section.
@@ -35,8 +36,12 @@ function ItemFormPrimarySection({
// #withDashboardActions // #withDashboardActions
changePageSubtitle, changePageSubtitle,
// #ownProps
itemId,
}) { }) {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const isNewMode = !itemId;
const itemTypeHintContent = ( const itemTypeHintContent = (
<> <>
@@ -59,48 +64,45 @@ function ItemFormPrimarySection({
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_PRIMARY)}> <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> <Row>
<Col xs={7}> <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 ----------*/} {/*----------- Item name ----------*/}
<FastField name={'name'}> <FastField name={'name'}>
{({ field, meta: { error, touched } }) => ( {({ 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 { import {
Intent, Intent,
Button, Button,
Classes,
Popover, Popover,
Menu, Menu,
MenuItem, MenuItem,
@@ -14,9 +13,11 @@ import { useParams } from 'react-router-dom';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
import moment from 'moment'; import moment from 'moment';
import classNames from 'classnames';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import { compose, saveInvoke } from 'utils'; import { compose, saveInvoke } from 'utils';
import { CLASSES } from 'common/classes';
import { useIsValuePassed } from 'hooks'; import { useIsValuePassed } from 'hooks';
import { LoadingIndicator, Choose } from 'components'; import { LoadingIndicator, Choose } from 'components';
@@ -221,34 +222,36 @@ function BillsDataTable({
const showEmptyStatus = [ const showEmptyStatus = [
billsCurrentViewId === -1, billsCurrentViewId === -1,
billsCurrentPage.length === 0, billsCurrentPage.length === 0,
].every(condition => condition === true); ].every((condition) => condition === true);
return ( return (
<LoadingIndicator loading={billsLoading && !isLoadedBefore} mount={false}> <div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<Choose> <LoadingIndicator loading={billsLoading && !isLoadedBefore} mount={false}>
<Choose.When condition={showEmptyStatus}> <Choose>
<BillsEmptyStatus /> <Choose.When condition={showEmptyStatus}>
</Choose.When> <BillsEmptyStatus />
</Choose.When>
<Choose.Otherwise> <Choose.Otherwise>
<DataTable <DataTable
columns={columns} columns={columns}
data={billsCurrentPage} data={billsCurrentPage}
onFetchData={handleFetchData} onFetchData={handleFetchData}
manualSortBy={true} manualSortBy={true}
selectionColumn={true} selectionColumn={true}
noInitialFetch={true} noInitialFetch={true}
sticky={true} sticky={true}
onSelectedRowsChange={handleSelectedRowsChange} onSelectedRowsChange={handleSelectedRowsChange}
rowContextMenu={onRowContextMenu} rowContextMenu={onRowContextMenu}
pagination={true} pagination={true}
pagesCount={billsPageination.pagesCount} pagesCount={billsPageination.pagesCount}
initialPageSize={billsPageination.pageSize} initialPageSize={billsPageination.pageSize}
initialPageIndex={billsPageination.page - 1} initialPageIndex={billsPageination.page - 1}
/> />
</Choose.Otherwise> </Choose.Otherwise>
</Choose> </Choose>
</LoadingIndicator> </LoadingIndicator>
</div>
); );
} }
@@ -264,13 +267,13 @@ export default compose(
billsLoading, billsLoading,
billsPageination, billsPageination,
billsTableQuery, billsTableQuery,
billsCurrentViewId billsCurrentViewId,
}) => ({ }) => ({
billsCurrentPage, billsCurrentPage,
billsLoading, billsLoading,
billsPageination, billsPageination,
billsTableQuery, billsTableQuery,
billsCurrentViewId billsCurrentViewId,
}), }),
), ),
withViewDetails(), withViewDetails(),

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,12 +7,17 @@ import App from 'components/App';
import * as serviceWorker from 'serviceWorker'; import * as serviceWorker from 'serviceWorker';
import createStore from 'store/createStore'; import createStore from 'store/createStore';
import AppProgress from 'components/NProgress/AppProgress'; import AppProgress from 'components/NProgress/AppProgress';
import { setLocale } from 'yup';
import {locale} from 'lang/en/locale'; 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( ReactDOM.render(
<Provider store={createStore}> <Provider store={createStore}>
<BrowserRouter> <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.`, 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 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_has_been_successfully_edited:
'The item #{number} 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 created.', 'The item category has been successfully created.',
the_item_category_has_been_successfully_edited: the_item_category_has_been_successfully_edited:
@@ -819,4 +822,5 @@ export default {
the_name_used_before: 'The name is already used.', the_name_used_before: 'The name is already used.',
the_item_has_associated_transactions: 'The item has associated transactions.', the_item_has_associated_transactions: 'The item has associated transactions.',
customer_has_sales_invoices: 'Customer has sales invoices', 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 = { const initialState = {
pageTitle: '', pageTitle: '',
pageSubtitle: '', pageSubtitle: '',
pageHint: '',
preferencesPageTitle: '', preferencesPageTitle: '',
sidebarExpended: true, sidebarExpended: true,
dialogs: {}, dialogs: {},
@@ -20,6 +21,10 @@ export default createReducer(initialState, {
state.pageSubtitle = action.pageSubtitle; state.pageSubtitle = action.pageSubtitle;
}, },
[t.CHANGE_DASHBOARD_PAGE_HINT]: (state, action) => {
state.pageHint = action.pageHint;
},
[t.CHANGE_PREFERENCES_PAGE_TITLE]: (state, action) => { [t.CHANGE_PREFERENCES_PAGE_TITLE]: (state, action) => {
state.preferencesPageTitle = action.pageTitle; state.preferencesPageTitle = action.pageTitle;
}, },

View File

@@ -6,6 +6,7 @@ export default {
CLOSE_DIALOG: 'CLOSE_DIALOG', CLOSE_DIALOG: 'CLOSE_DIALOG',
CLOSE_ALL_DIALOGS: 'CLOSE_ALL_DIALOGS', CLOSE_ALL_DIALOGS: 'CLOSE_ALL_DIALOGS',
CHANGE_DASHBOARD_PAGE_TITLE: 'CHANGE_DASHBOARD_PAGE_TITLE', CHANGE_DASHBOARD_PAGE_TITLE: 'CHANGE_DASHBOARD_PAGE_TITLE',
CHANGE_DASHBOARD_PAGE_HINT: 'CHANGE_DASHBOARD_PAGE_HINT',
CHANGE_PREFERENCES_PAGE_TITLE: 'CHANGE_PREFERENCES_PAGE_TITLE', CHANGE_PREFERENCES_PAGE_TITLE: 'CHANGE_PREFERENCES_PAGE_TITLE',
ALTER_DASHBOARD_PAGE_SUBTITLE: 'ALTER_DASHBOARD_PAGE_SUBTITLE', ALTER_DASHBOARD_PAGE_SUBTITLE: 'ALTER_DASHBOARD_PAGE_SUBTITLE',
SET_TOPBAR_EDIT_VIEW: 'SET_TOPBAR_EDIT_VIEW', 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 ApiService from 'services/ApiService';
import t from 'store/types'; import t from 'store/types';
@@ -135,7 +135,14 @@ export const fetchManualJournalsTable = ({ query } = {}) => {
}); });
dispatch({ dispatch({
type: t.MANUAL_JOURNALS_ITEMS_SET, 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({ dispatch({
type: t.MANUAL_JOURNALS_PAGINATION_SET, type: t.MANUAL_JOURNALS_PAGINATION_SET,
@@ -145,6 +152,12 @@ export const fetchManualJournalsTable = ({ query } = {}) => {
response.data.manual_journals?.viewMeta?.customViewId || -1, 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({ dispatch({
type: t.MANUAL_JOURNALS_TABLE_LOADING, type: t.MANUAL_JOURNALS_TABLE_LOADING,
loading: false, loading: false,

View File

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

View File

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

View File

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

View File

@@ -19,13 +19,20 @@ $form-check-input-indeterminate-bg-image: url("data:image/svg+xml,<svg xmlns='ht
} }
// Form // Form
.bp3-label{ label.bp3-label{
color: #353535; color: #353535;
font-weight: 400; font-weight: 400;
.required{ .required{
color: red; 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{ .#{$ns}-input{

View File

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

View File

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

View File

@@ -4,12 +4,12 @@
display: flex; display: flex;
height: 100vh; height: 100vh;
&__topbar{ &__topbar{
width: 100%; width: 100%;
min-height: 60px; min-height: 60px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
background-color: #fff;
border-bottom: 1px solid #F2EFEF; border-bottom: 1px solid #F2EFEF;
&-right, &-right,
@@ -121,7 +121,7 @@
&, &,
&-group{ &-group{
height: 42px; height: 40px;
} }
.#{$ns}-navbar-divider{ .#{$ns}-navbar-divider{
@@ -138,7 +138,7 @@
} }
&.bp3-minimal:active, &.bp3-minimal:active,
&.bp3-minimal.bp3-active{ &.bp3-minimal.bp3-active{
background: rgba(167, 182, 194, 0.12); background: #a7b6c21f;
color: #32304a; color: #32304a;
} }
@@ -150,7 +150,7 @@
} }
} }
.#{$ns}-icon{ .#{$ns}-icon{
color: #2A293D; color: #32304a;
margin-right: 7px; margin-right: 7px;
} }
&.#{$ns}-minimal.#{$ns}-intent-danger{ &.#{$ns}-minimal.#{$ns}-intent-danger{
@@ -203,7 +203,7 @@
h1{ h1{
font-size: 22px; font-size: 22px;
color: #333363; color: #48485c;
font-weight: 400; font-weight: 400;
margin: 0; margin: 0;
} }
@@ -233,13 +233,17 @@
} }
} }
&__hint{
display: inline-block;
margin-top: 4px;
margin-left: 4px;
}
&__subtitle{ &__subtitle{
} }
&__insider{ &__insider{
margin-bottom: 40px;
flex: 1 0 0;
} }
&__offline-badge{ &__offline-badge{
@@ -260,6 +264,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
min-width: 850px;
&:before{ &:before{
content: ""; content: "";
@@ -279,7 +284,9 @@
&__insider{ &__insider{
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1 0 0;
background-color: #FBFBFB;
> .dashboard__loading-indicator{ > .dashboard__loading-indicator{
margin-top: auto; margin-top: auto;
margin-bottom: auto; margin-bottom: auto;
@@ -313,6 +320,23 @@
display: flex; display: flex;
flex: 1 0 0; flex: 1 0 0;
flex-direction: column; 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{ .datatable-empty-status{
margin-top: auto; margin-top: auto;
@@ -323,7 +347,6 @@
&__preferences-topbar{ &__preferences-topbar{
border-bottom: 1px solid #E5E5E5; border-bottom: 1px solid #E5E5E5;
// height: 70px;
height: 65px; height: 65px;
padding: 0 0 0 22px; padding: 0 0 0 22px;
display: flex; 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{ .tabs--dashboard-views{
@@ -408,7 +468,7 @@
.navbar--dashboard-views{ .navbar--dashboard-views{
box-shadow: 0 0 0; box-shadow: 0 0 0;
border-bottom: 1px solid #EAEAEA; border-bottom: 1px solid #d2dce2;
} }
.navbar-omnibar{ .navbar-omnibar{

View File

@@ -1,3 +1,7 @@
.dashboard__insider--estimate-form{
background-color: #FFF;
}
.estimate-form { .estimate-form {
padding-bottom: 30px; padding-bottom: 30px;
display: flex; 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{ .page-form--estimate{
$self: '.page-form'; $self: '.page-form';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -198,7 +198,11 @@ export default class AccountsController extends BaseController{
try { try {
const account = await this.accountsService.editAccount(tenantId, accountId, accountDTO); 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) { } catch (error) {
next(error); next(error);
} }

View File

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

View File

@@ -203,6 +203,8 @@ export default class AccountsService {
* @param {IAccountDTO} accountDTO * @param {IAccountDTO} accountDTO
*/ */
public async editAccount(tenantId: number, accountId: number, accountDTO: IAccountDTO) { 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 { accountRepository } = this.tenancy.repositories(tenantId);
const oldAccount = await this.getAccountOrThrowError(tenantId, accountId); 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 }); this.logger.info('[manual_journals] trying to get manual journals list.', { tenantId, filter });
const { results, pagination } = await ManualJournal.query().onBuild((builder) => { const { results, pagination } = await ManualJournal.query().onBuild((builder) => {
dynamicList.buildQuery()(builder); dynamicList.buildQuery()(builder);
builder.withGraphFetched('entries.account');
}).pagination(filter.page - 1, filter.pageSize); }).pagination(filter.page - 1, filter.pageSize);
return { return {