diff --git a/client/package.json b/client/package.json index e88487f62..b992af0d1 100644 --- a/client/package.json +++ b/client/package.json @@ -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" diff --git a/client/src/common/classes.js b/client/src/common/classes.js index 570417074..6b8f3f395 100644 --- a/client/src/common/classes.js +++ b/client/src/common/classes.js @@ -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', diff --git a/client/src/components/Dashboard/DashboardCard.js b/client/src/components/Dashboard/DashboardCard.js new file mode 100644 index 000000000..6818b3124 --- /dev/null +++ b/client/src/components/Dashboard/DashboardCard.js @@ -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 ( +
+ {children} +
+ ); +} diff --git a/client/src/components/Dashboard/DashboardContent.js b/client/src/components/Dashboard/DashboardContent.js index 01b3ae95e..506469501 100644 --- a/client/src/components/Dashboard/DashboardContent.js +++ b/client/src/components/Dashboard/DashboardContent.js @@ -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 ( -
- - - - -
+ +
+ + + +
+
); -} \ No newline at end of file +} diff --git a/client/src/components/Dashboard/DashboardErrorBoundary.js b/client/src/components/Dashboard/DashboardErrorBoundary.js new file mode 100644 index 000000000..111a9e294 --- /dev/null +++ b/client/src/components/Dashboard/DashboardErrorBoundary.js @@ -0,0 +1,12 @@ +import React from 'react'; +import { Icon } from 'components'; + +export default function DashboardErrorBoundary({}) { + return ( +
+

Sorry about that! Something went wrong

+

If the problem stuck, please contact us as soon as possible.

+ +
+ ) +} \ No newline at end of file diff --git a/client/src/components/Dashboard/DashboardTopbar.js b/client/src/components/Dashboard/DashboardTopbar.js index 50326792a..9799e908b 100644 --- a/client/src/components/Dashboard/DashboardTopbar.js +++ b/client/src/components/Dashboard/DashboardTopbar.js @@ -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({

{pageTitle}

+ +
+ +
+
+ -

{ pageSubtitle }

+

{pageSubtitle}

diff --git a/client/src/components/DataTable.js b/client/src/components/DataTable.js index 6db7f3536..d1998c244 100644 --- a/client/src/components/DataTable.js +++ b/client/src/components/DataTable.js @@ -343,6 +343,7 @@ export default function DataTable({
- + ); diff --git a/client/src/components/Forms/Checkbox.tsx b/client/src/components/Forms/Checkbox.tsx new file mode 100644 index 000000000..0bd1b9647 --- /dev/null +++ b/client/src/components/Forms/Checkbox.tsx @@ -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 ; +} \ No newline at end of file diff --git a/client/src/components/index.js b/client/src/components/index.js index 5d7d8e9cc..e58492757 100644 --- a/client/src/components/index.js +++ b/client/src/components/index.js @@ -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, }; diff --git a/client/src/containers/Accounting/MakeJournalEntriesForm.js b/client/src/containers/Accounting/MakeJournalEntriesForm.js index dec6f8304..fb8025875 100644 --- a/client/src/containers/Accounting/MakeJournalEntriesForm.js +++ b/client/src/containers/Accounting/MakeJournalEntriesForm.js @@ -444,7 +444,6 @@ function MakeJournalEntriesForm({ }, [changePageSubtitle], ); - console.log(values, 'Val'); return (
diff --git a/client/src/containers/Accounting/ManualJournalsDataTable.js b/client/src/containers/Accounting/ManualJournalsDataTable.js index 055754c9f..a344485e3 100644 --- a/client/src/containers/Accounting/ManualJournalsDataTable.js +++ b/client/src/containers/Accounting/ManualJournalsDataTable.js @@ -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 ( - - - - - - - - - - - - - ); -}; - -/** - * Note column accessor. - */ -function NoteAccessor(row) { - return ( - - - - - - ); -} function ManualJournalsDataTable({ // #withManualJournals @@ -166,7 +130,14 @@ function ManualJournalsDataTable({ { id: 'amount', Header: formatMessage({ id: 'amount' }), - accessor: (r) => , + accessor: (r) => ( + } + position={Position.RIGHT_BOTTOM} + > + + + ), 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 ( - - - - - +
+ + + + + - - - - - + + + + + +
); } @@ -304,7 +277,7 @@ export default compose( manualJournalsLoading, manualJournalsPagination, manualJournalsTableQuery, - manualJournalsCurrentViewId + manualJournalsCurrentViewId, }), ), )(ManualJournalsDataTable); diff --git a/client/src/containers/Accounting/components.js b/client/src/containers/Accounting/components.js new file mode 100644 index 000000000..207a4167f --- /dev/null +++ b/client/src/containers/Accounting/components.js @@ -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 ( + + +
+ C. USD -{' '} + {account.name} ({account.code}) +
+
+ + +
+ D. USD -{' '} + {account.name} ({account.code}) +
+
+
+ ); +}; + +const AmountPopoverContentLine = compose(withAccountDetails)( + AmountPopoverContentLineRender, +); + +export function AmountPopoverContent({ journalEntries }) { + const journalLinesProps = journalEntries.map((journalEntry) => ({ + journalEntry, + accountId: journalEntry.account_id, + })); + + return ( +
+ {journalLinesProps.map(({ journalEntry, accountId }) => ( + + ))} +
+ ); +} + +/** + * Status column accessor. + */ +export const StatusAccessor = (row) => { + return ( + + + + + + + + + + + + + + ); +}; + +/** + * Note column accessor. + */ +export function NoteAccessor(row) { + return ( + + + + + + ); +} \ No newline at end of file diff --git a/client/src/containers/Accounts/AccountsDataTable.js b/client/src/containers/Accounts/AccountsDataTable.js index 3ee7db643..24b72eca7 100644 --- a/client/src/containers/Accounts/AccountsDataTable.js +++ b/client/src/containers/Accounts/AccountsDataTable.js @@ -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 ? ( @@ -64,13 +61,14 @@ function BalanceCell({ cell }) { function InactiveSemafro() { return ( } - className={classnames( + content={} + className={classNames( Classes.TOOLTIP_INDICATOR, - 'bp3-popover-wrapper--inactive-semafro' + 'bp3-popover-wrapper--inactive-semafro', )} position={Position.TOP} - hoverOpenDelay={250}> + hoverOpenDelay={250} + >
); @@ -82,7 +80,7 @@ function AccountNameAccessor(row) { - - { row.name } - + {row.name} @@ -159,7 +155,8 @@ function AccountsDataTable({ } - text={formatMessage({ id: 'view_details' })} /> + text={formatMessage({ id: 'view_details' })} + /> } @@ -287,20 +284,22 @@ function AccountsDataTable({ ); return ( - +
+ +
); } diff --git a/client/src/containers/Customers/Customer.js b/client/src/containers/Customers/Customer.js index 2bc2b9a5e..7259ff98c 100644 --- a/client/src/containers/Customers/Customer.js +++ b/client/src/containers/Customers/Customer.js @@ -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'} > - + + + ); } diff --git a/client/src/containers/Customers/CustomerForm.js b/client/src/containers/Customers/CustomerForm.js index 55fa87f16..ed38b29aa 100644 --- a/client/src/containers/Customers/CustomerForm.js +++ b/client/src/containers/Customers/CustomerForm.js @@ -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 }) => ( -
-
- -
+
+ +
-
- -
+
+
diff --git a/client/src/containers/Customers/CustomerTable.js b/client/src/containers/Customers/CustomerTable.js index 9fae8acbf..e8595d6a8 100644 --- a/client/src/containers/Customers/CustomerTable.js +++ b/client/src/containers/Customers/CustomerTable.js @@ -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 ( - - - - - +
+ + + + + - - - - - + + + + + +
); }; @@ -238,7 +239,7 @@ export default compose( customersLoading, customerPagination, customersTableQuery, - customersCurrentViewId + customersCurrentViewId, }), ), withCustomersActions, diff --git a/client/src/containers/Dashboard/withDashboard.js b/client/src/containers/Dashboard/withDashboard.js index 3b5738407..bd88b9f31 100644 --- a/client/src/containers/Dashboard/withDashboard.js +++ b/client/src/containers/Dashboard/withDashboard.js @@ -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, diff --git a/client/src/containers/Dashboard/withDashboardActions.js b/client/src/containers/Dashboard/withDashboardActions.js index 46c118313..9316f7a6c 100644 --- a/client/src/containers/Dashboard/withDashboardActions.js +++ b/client/src/containers/Dashboard/withDashboardActions.js @@ -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, diff --git a/client/src/containers/Dialogs/AccountFormDialog/AccountFormDialogContent.js b/client/src/containers/Dialogs/AccountFormDialog/AccountFormDialogContent.js index f23a17af5..6d5c9bafd 100644 --- a/client/src/containers/Dialogs/AccountFormDialog/AccountFormDialogContent.js +++ b/client/src/containers/Dialogs/AccountFormDialog/AccountFormDialogContent.js @@ -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; }; diff --git a/client/src/containers/ExchangeRates/ExchangeRateTable.js b/client/src/containers/ExchangeRates/ExchangeRateTable.js index a54a47f6d..c5eb54d68 100644 --- a/client/src/containers/ExchangeRates/ExchangeRateTable.js +++ b/client/src/containers/ExchangeRates/ExchangeRateTable.js @@ -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 ( - - - +
+ + + +
); } diff --git a/client/src/containers/Expenses/ExpenseDataTable.js b/client/src/containers/Expenses/ExpenseDataTable.js index cb2c9b954..dc8c588f0 100644 --- a/client/src/containers/Expenses/ExpenseDataTable.js +++ b/client/src/containers/Expenses/ExpenseDataTable.js @@ -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 ( - - - - - +
+ + + + + - - - - - + + + + + +
); } diff --git a/client/src/containers/FinancialStatements/FinancialReports.js b/client/src/containers/FinancialStatements/FinancialReports.js index 72ec1655b..546f99a85 100644 --- a/client/src/containers/FinancialStatements/FinancialReports.js +++ b/client/src/containers/FinancialStatements/FinancialReports.js @@ -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 ( -
- -
+ +
+ +
+
); } diff --git a/client/src/containers/Items/ItemCategoriesTable.js b/client/src/containers/Items/ItemCategoriesTable.js index 03e4dba8b..9834066a0 100644 --- a/client/src/containers/Items/ItemCategoriesTable.js +++ b/client/src/containers/Items/ItemCategoriesTable.js @@ -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 ( - - - +
+ + + +
); }; diff --git a/client/src/containers/Items/ItemForm.js b/client/src/containers/Items/ItemForm.js index d9ab4f86a..b101750b2 100644 --- a/client/src/containers/Items/ItemForm.js +++ b/client/src/containers/Items/ItemForm.js @@ -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({
{({ isSubmitting, handleSubmit }) => (
- +
@@ -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); diff --git a/client/src/containers/Items/ItemForm.schema.js b/client/src/containers/Items/ItemForm.schema.js new file mode 100644 index 000000000..138967309 --- /dev/null +++ b/client/src/containers/Items/ItemForm.schema.js @@ -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; \ No newline at end of file diff --git a/client/src/containers/Items/ItemFormBody.js b/client/src/containers/Items/ItemFormBody.js index 3241258cf..764e8d3be 100644 --- a/client/src/containers/Items/ItemFormBody.js +++ b/client/src/containers/Items/ItemFormBody.js @@ -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 }) { {/*------------- Purchasable checbox ------------- */} - - {({ field, field: { value } }) => ( + + {({ field: { onChange, onBlur, name, checked } }) => ( } - checked={value} - {...field} + name={'sellable'} + checked={!!checked} + onChange={onChange} + onBlur={onBlur} /> )} diff --git a/client/src/containers/Items/ItemFormFloatingActions.js b/client/src/containers/Items/ItemFormFloatingActions.js index d53224205..fd9b3ea63 100644 --- a/client/src/containers/Items/ItemFormFloatingActions.js +++ b/client/src/containers/Items/ItemFormFloatingActions.js @@ -56,7 +56,7 @@ export default function ItemFormFloatingActions({ {/*----------- Active ----------*/} {({ field, field: { value } }) => ( - + } diff --git a/client/src/containers/Items/ItemFormPage.js b/client/src/containers/Items/ItemFormPage.js index a1514437c..7c1f71150 100644 --- a/client/src/containers/Items/ItemFormPage.js +++ b/client/src/containers/Items/ItemFormPage.js @@ -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'} > - + + + ); }; diff --git a/client/src/containers/Items/ItemFormPrimarySection.js b/client/src/containers/Items/ItemFormPrimarySection.js index 9479dbfa7..abe955387 100644 --- a/client/src/containers/Items/ItemFormPrimarySection.js +++ b/client/src/containers/Items/ItemFormPrimarySection.js @@ -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 (
+ {/*----------- Item type ----------*/} + + {({ form, field: { value }, meta: { touched, error } }) => ( + } + labelInfo={ + + + + + } + className={'form-group--item-type'} + intent={inputIntent({ error, touched })} + helperText={} + inline={true} + > + { + form.setFieldValue('type', _value); + changePageSubtitle(transitionItemTypeKeyToLabel(_value)); + })} + selectedValue={value} + disabled={value === 'inventory' && !isNewMode} + > + } value="service" /> + } value="non-inventory" /> + } value="inventory" /> + + + )} + + - {/*----------- Item type ----------*/} - - {({ form, field: { value }, meta: { touched, error } }) => ( - } - labelInfo={ - - - - - } - className={'form-group--item-type'} - intent={inputIntent({ error, touched })} - helperText={} - inline={true} - > - { - form.setFieldValue('type', _value); - changePageSubtitle(formatMessage({ id: _value })); - })} - selectedValue={value} - disabled={value === 'inventory'} - > - } value="service" /> - } - value="non-inventory" - /> - } value="inventory" /> - - - )} - - {/*----------- Item name ----------*/} {({ field, meta: { error, touched } }) => ( diff --git a/client/src/containers/Items/utils.js b/client/src/containers/Items/utils.js new file mode 100644 index 000000000..36d880039 --- /dev/null +++ b/client/src/containers/Items/utils.js @@ -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] : ''; +}; \ No newline at end of file diff --git a/client/src/containers/Purchases/Bill/BillsDataTable.js b/client/src/containers/Purchases/Bill/BillsDataTable.js index 50cf57c0b..7fcb20856 100644 --- a/client/src/containers/Purchases/Bill/BillsDataTable.js +++ b/client/src/containers/Purchases/Bill/BillsDataTable.js @@ -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 ( - - - - - +
+ + + + + - - - - - + + + + + +
); } @@ -264,13 +267,13 @@ export default compose( billsLoading, billsPageination, billsTableQuery, - billsCurrentViewId + billsCurrentViewId, }) => ({ billsCurrentPage, billsLoading, billsPageination, billsTableQuery, - billsCurrentViewId + billsCurrentViewId, }), ), withViewDetails(), diff --git a/client/src/containers/Purchases/PaymentMades/PaymentMadeDataTable.js b/client/src/containers/Purchases/PaymentMades/PaymentMadeDataTable.js index d265cce53..7cd1c985e 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentMadeDataTable.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentMadeDataTable.js @@ -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 ( - - - - - +
+ + + + + - - - - - + + + + + +
); } @@ -225,13 +229,13 @@ export default compose( paymentMadesLoading, paymentMadePageination, paymentMadeTableQuery, - paymentMadesCurrentViewId + paymentMadesCurrentViewId, }) => ({ paymentMadeCurrentPage, paymentMadesLoading, paymentMadePageination, paymentMadeTableQuery, - paymentMadesCurrentViewId + paymentMadesCurrentViewId, }), ), )(PaymentMadeDataTable); diff --git a/client/src/containers/Sales/PaymentReceive/PaymentReceiveFormPage.js b/client/src/containers/Sales/PaymentReceive/PaymentReceiveFormPage.js index 6ba860620..37d213498 100644 --- a/client/src/containers/Sales/PaymentReceive/PaymentReceiveFormPage.js +++ b/client/src/containers/Sales/PaymentReceive/PaymentReceiveFormPage.js @@ -63,7 +63,9 @@ function PaymentReceiveFormPage({ fetchAccounts.isFetching || // fetchSettings.isFetching || fetchCustomers.isFetching - }> + } + name={'payment-receive-form'} + > diff --git a/client/src/containers/Sales/PaymentReceive/PaymentReceivesDataTable.js b/client/src/containers/Sales/PaymentReceive/PaymentReceivesDataTable.js index 74f406bf5..1c50f6fef 100644 --- a/client/src/containers/Sales/PaymentReceive/PaymentReceivesDataTable.js +++ b/client/src/containers/Sales/PaymentReceive/PaymentReceivesDataTable.js @@ -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 ( +
+
); } diff --git a/client/src/containers/Sales/Receipt/ReceiptsDataTable.js b/client/src/containers/Sales/Receipt/ReceiptsDataTable.js index f38e1a104..ea16e15a5 100644 --- a/client/src/containers/Sales/Receipt/ReceiptsDataTable.js +++ b/client/src/containers/Sales/Receipt/ReceiptsDataTable.js @@ -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 ( - - - - - +
+ + + + + - - - - - + + + + + +
); } @@ -236,13 +240,13 @@ export default compose( receiptsLoading, receiptsPagination, receiptTableQuery, - receiptsCurrentViewId + receiptsCurrentViewId, }) => ({ receiptsCurrentPage, receiptsLoading, receiptsPagination, receiptTableQuery, - receiptsCurrentViewId + receiptsCurrentViewId, }), ), )(ReceiptsDataTable); diff --git a/client/src/index.js b/client/src/index.js index 3408e641f..c884ec6b9 100644 --- a/client/src/index.js +++ b/client/src/index.js @@ -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( diff --git a/client/src/lang/en/index.js b/client/src/lang/en/index.js index 3db4b3599..235d57ac6 100644 --- a/client/src/lang/en/index.js +++ b/client/src/lang/en/index.js @@ -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 ?

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.', }; diff --git a/client/src/store/dashboard/dashboard.reducer.js b/client/src/store/dashboard/dashboard.reducer.js index 5baf3917e..376931218 100644 --- a/client/src/store/dashboard/dashboard.reducer.js +++ b/client/src/store/dashboard/dashboard.reducer.js @@ -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; }, diff --git a/client/src/store/dashboard/dashboard.types.js b/client/src/store/dashboard/dashboard.types.js index 1813e14cb..74a78aa4a 100644 --- a/client/src/store/dashboard/dashboard.types.js +++ b/client/src/store/dashboard/dashboard.types.js @@ -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', diff --git a/client/src/store/manualJournals/manualJournals.actions.js b/client/src/store/manualJournals/manualJournals.actions.js index a89aef2d7..bf1318f11 100644 --- a/client/src/store/manualJournals/manualJournals.actions.js +++ b/client/src/store/manualJournals/manualJournals.actions.js @@ -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, diff --git a/client/src/style/App.scss b/client/src/style/App.scss index 4394f01d3..7a7fea063 100644 --- a/client/src/style/App.scss +++ b/client/src/style/App.scss @@ -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{ diff --git a/client/src/style/components/data-table.scss b/client/src/style/components/data-table.scss index b67db572d..21acd0f21 100644 --- a/client/src/style/components/data-table.scss +++ b/client/src/style/components/data-table.scss @@ -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{ diff --git a/client/src/style/components/resizer.scss b/client/src/style/components/resizer.scss index 474df64ce..80ff23704 100644 --- a/client/src/style/components/resizer.scss +++ b/client/src/style/components/resizer.scss @@ -1,6 +1,8 @@ .Pane.Pane2 { - overflow: auto; + overflow: auto; + display: flex; + flex-direction: column; } .Resizer { diff --git a/client/src/style/objects/form.scss b/client/src/style/objects/form.scss index ab4894926..53d3f7a50 100644 --- a/client/src/style/objects/form.scss +++ b/client/src/style/objects/form.scss @@ -19,13 +19,20 @@ $form-check-input-indeterminate-bg-image: url("data:image/svg+xml, .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{ diff --git a/client/src/style/pages/estimate.scss b/client/src/style/pages/estimate.scss index fe0f1867b..3011b6328 100644 --- a/client/src/style/pages/estimate.scss +++ b/client/src/style/pages/estimate.scss @@ -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; -// } -// } -// } -// } diff --git a/client/src/style/pages/estimates.scss b/client/src/style/pages/estimates.scss index e8776581f..02e774b69 100644 --- a/client/src/style/pages/estimates.scss +++ b/client/src/style/pages/estimates.scss @@ -1,3 +1,7 @@ +.dashboard__insider--estimate-form{ + background-color: #FFF; +} + .page-form--estimate{ $self: '.page-form'; diff --git a/client/src/style/pages/expense-form.scss b/client/src/style/pages/expense-form.scss index 0bac8a8cf..e22ac2ef4 100644 --- a/client/src/style/pages/expense-form.scss +++ b/client/src/style/pages/expense-form.scss @@ -2,6 +2,7 @@ padding-bottom: 80px; display: flex; flex-direction: column; + background: #fff; &__header { padding: 25px 27px 20px; diff --git a/client/src/style/pages/financial-statements.scss b/client/src/style/pages/financial-statements.scss index ab637f636..b4f6c3951 100644 --- a/client/src/style/pages/financial-statements.scss +++ b/client/src/style/pages/financial-statements.scss @@ -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; } } } \ No newline at end of file diff --git a/client/src/style/pages/invoice-form.scss b/client/src/style/pages/invoice-form.scss index e7075f8c4..d4d598c4a 100644 --- a/client/src/style/pages/invoice-form.scss +++ b/client/src/style/pages/invoice-form.scss @@ -1,4 +1,6 @@ - +.dashboard__insider--invoice-form{ + background-color: #FFF; +} .page-form--invoice{ $self: '.page-form'; diff --git a/client/src/style/pages/items.scss b/client/src/style/pages/items.scss index 3ee60621e..ebd72f422 100644 --- a/client/src/style/pages/items.scss +++ b/client/src/style/pages/items.scss @@ -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; diff --git a/client/src/style/pages/make-journal-entries.scss b/client/src/style/pages/make-journal-entries.scss index 61cf4adf3..9955b4f09 100644 --- a/client/src/style/pages/make-journal-entries.scss +++ b/client/src/style/pages/make-journal-entries.scss @@ -58,4 +58,11 @@ min-width: 75px; } } +} + +.dashboard__insider{ + + &--make-journal-page{ + background: #fff; + } } \ No newline at end of file diff --git a/client/src/style/pages/manual-journals.scss b/client/src/style/pages/manual-journals.scss index 7b70b850e..9065c94f6 100644 --- a/client/src/style/pages/manual-journals.scss +++ b/client/src/style/pages/manual-journals.scss @@ -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{ diff --git a/client/src/style/pages/payment-made.scss b/client/src/style/pages/payment-made.scss index 4f6a6816b..23553848f 100644 --- a/client/src/style/pages/payment-made.scss +++ b/client/src/style/pages/payment-made.scss @@ -1,4 +1,6 @@ - +.dashboard__insider--payment-made{ + background-color: #FFF; +} .page-form--payment-made { $self: '.page-form'; diff --git a/client/src/style/pages/payment-receive.scss b/client/src/style/pages/payment-receive.scss index 9fad22653..79cbc9e5a 100644 --- a/client/src/style/pages/payment-receive.scss +++ b/client/src/style/pages/payment-receive.scss @@ -1,4 +1,7 @@ +.dashboard__insider--payment-receive-form{ + background-color: #FFF; +} .page-form--payment-receive { $self: '.page-form'; diff --git a/client/src/style/pages/receipt-form.scss b/client/src/style/pages/receipt-form.scss index 30dde1d0b..c5edb85b2 100644 --- a/client/src/style/pages/receipt-form.scss +++ b/client/src/style/pages/receipt-form.scss @@ -1,4 +1,6 @@ - +.dashboard__insider--receipt-form{ + background-color: #fff; +} .page-form--receipt{ $self: '.page-form'; diff --git a/client/src/style/views/Sidebar.scss b/client/src/style/views/Sidebar.scss index d46a4512b..01e730b9c 100644 --- a/client/src/style/views/Sidebar.scss +++ b/client/src/style/views/Sidebar.scss @@ -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; diff --git a/server/src/api/controllers/Accounts.ts b/server/src/api/controllers/Accounts.ts index a3126f202..98790f1d9 100644 --- a/server/src/api/controllers/Accounts.ts +++ b/server/src/api/controllers/Accounts.ts @@ -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); } diff --git a/server/src/repositories/AccountRepository.ts b/server/src/repositories/AccountRepository.ts index d8b0ba3c6..078671fc2 100644 --- a/server/src/repositories/AccountRepository.ts +++ b/server/src/repositories/AccountRepository.ts @@ -107,10 +107,12 @@ export default class AccountRepository extends TenantRepository { * @param {IAccount} account * @return {void} */ - async edit(accountId: number, account: IAccount): Promise { + async edit(accountId: number, accountInput: IAccount): Promise { const { Account } = this.models; - await Account.query().findById(accountId).patch({ ...account }); + const account = await Account.query().patchAndFetchById(accountId, { ...accountInput }); this.flushCache(); + + return account; } /** diff --git a/server/src/services/Accounts/AccountsService.ts b/server/src/services/Accounts/AccountsService.ts index bd6a562e2..f83ff3eaa 100644 --- a/server/src/services/Accounts/AccountsService.ts +++ b/server/src/services/Accounts/AccountsService.ts @@ -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); diff --git a/server/src/services/ManualJournals/ManualJournalsService.ts b/server/src/services/ManualJournals/ManualJournalsService.ts index 29159fe5b..da367f843 100644 --- a/server/src/services/ManualJournals/ManualJournalsService.ts +++ b/server/src/services/ManualJournals/ManualJournalsService.ts @@ -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 {