feat(Journal): add branch to topbar & entries columns.

This commit is contained in:
elforjani13
2022-02-28 14:29:56 +02:00
parent cb9c7fcdb6
commit eb340269c0
12 changed files with 288 additions and 9 deletions

View File

@@ -0,0 +1,115 @@
import React from 'react';
import intl from 'react-intl-universal';
import { MenuItem } from '@blueprintjs/core';
import { Suggest } from '@blueprintjs/select';
import { FormattedMessage as T } from 'components';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
/**
* branch suggest field.
* @returns
*/
export default function BranchSuggestField({
branches,
initialBranchId,
selectedBranchId,
defaultSelectText = intl.get('select_branch'),
popoverFill = false,
onBranchSelected,
...suggestProps
}) {
const initialBranch = React.useMemo(
() => branches.some((b) => b.id === initialBranchId),
[initialBranchId, branches],
);
const [selectedBranch, setSelectedBranch] = React.useState(
initialBranch || null,
);
/**
*
* @param {*} branch
* @returns
*/
const branchItemRenderer = (branch, { handleClick, modifiers, query }) => {
return (
<MenuItem
// active={modifiers.active}
disabled={modifiers.disabled}
label={branch.code.toString()}
key={branch.id}
onClick={handleClick}
text={branch.name.toString()}
/>
);
};
/**
*
* @param {*} query
* @param {*} branch
* @param {*} _index
* @param {*} exactMatch
* @returns
*/
const branchItemPredicate = (query, branch, _index, exactMatch) => {
const normalizedTitle = branch.name.toLowerCase();
const normalizedQuery = query.toLowerCase();
if (exactMatch) {
return normalizedTitle === normalizedQuery;
} else {
return `${branch.code}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
}
};
/**
*
* @param {*} branch
* @returns
*/
const brnachItemSelect = React.useCallback(
(branch) => {
if (branch.id) {
setSelectedBranch({ ...branch });
onBranchSelected && onBranchSelected(branch);
}
},
[setSelectedBranch, onBranchSelected],
);
/**
*
* @param {*} inputVaue
* @returns
*/
const branchInputValueRenderer = (inputValue) => {
if (inputValue) {
return inputValue.name.toString();
}
return '';
};
return (
<Suggest
items={branches}
noResults={<MenuItem disabled={true} text={<T id={'no_accounts'} />} />}
itemRenderer={branchItemRenderer}
itemPredicate={branchItemPredicate}
onItemSelect={brnachItemSelect}
selectedItem={selectedBranch}
inputProps={{ placeholder: defaultSelectText }}
resetOnClose={true}
fill={true}
popoverProps={{ minimal: true, boundary: 'window' }}
inputValueRenderer={branchInputValueRenderer}
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
})}
{...suggestProps}
/>
);
}

View File

@@ -0,0 +1,40 @@
import React from 'react';
import { FormGroup, Intent, Classes } from '@blueprintjs/core';
import classNames from 'classnames';
import BranchSuggestField from '../BranchSuggestField';
/**
* Branches list field cell.
* @returns
*/
export default function BranchesListFieldCell({
column: { id },
row: { index, original },
payload: { branches, updateData, errors },
}) {
const handleBranchSelected = React.useCallback(
(branch) => {
updateData(index, 'brnach_id', branch.id);
},
[updateData, index],
);
const error = errors?.[index]?.[id];
return (
<FormGroup
intent={error ? Intent.DANGER : null}
className={classNames(
'form-group--select-list',
'form-group--contacts-list',
Classes.FILL,
)}
>
<BranchSuggestField
branches={branches}
onBranchSelected={handleBranchSelected}
selectedBranchId={original?.contact_id}
/>
</FormGroup>
);
}

View File

@@ -9,6 +9,7 @@ import NumericInputCell from './NumericInputCell';
import CheckBoxFieldCell from './CheckBoxFieldCell'; import CheckBoxFieldCell from './CheckBoxFieldCell';
import SwitchFieldCell from './SwitchFieldCell'; import SwitchFieldCell from './SwitchFieldCell';
import TextAreaCell from './TextAreaCell'; import TextAreaCell from './TextAreaCell';
import BranchesListFieldCell from './BranchesListFieldCell';
export { export {
AccountsListFieldCell, AccountsListFieldCell,
@@ -23,4 +24,5 @@ export {
CheckBoxFieldCell, CheckBoxFieldCell,
SwitchFieldCell, SwitchFieldCell,
TextAreaCell, TextAreaCell,
BranchesListFieldCell,
}; };

View File

@@ -16,6 +16,7 @@ const Schema = Yup.object().shape({
date: Yup.date().required().label(intl.get('date')), date: Yup.date().required().label(intl.get('date')),
currency_code: Yup.string().max(3), currency_code: Yup.string().max(3),
publish: Yup.boolean(), publish: Yup.boolean(),
branch_id: Yup.string(),
reference: Yup.string().nullable().min(1).max(DATATYPES_LENGTH.STRING), reference: Yup.string().nullable().min(1).max(DATATYPES_LENGTH.STRING),
description: Yup.string().min(1).max(DATATYPES_LENGTH.STRING).nullable(), description: Yup.string().min(1).max(DATATYPES_LENGTH.STRING).nullable(),
exchange_rate: Yup.number(), exchange_rate: Yup.number(),

View File

@@ -10,7 +10,7 @@ import { useMakeJournalFormContext } from './MakeJournalProvider';
* Make journal entries field. * Make journal entries field.
*/ */
export default function MakeJournalEntriesField() { export default function MakeJournalEntriesField() {
const { accounts, contacts } = useMakeJournalFormContext(); const { accounts, contacts ,branches } = useMakeJournalFormContext();
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}> <div className={classNames(CLASSES.PAGE_FORM_BODY)}>
@@ -18,6 +18,7 @@ export default function MakeJournalEntriesField() {
name={'entries'} name={'entries'}
contacts={contacts} contacts={contacts}
accounts={accounts} accounts={accounts}
branches={branches}
shouldUpdate={entriesFieldShouldUpdate} shouldUpdate={entriesFieldShouldUpdate}
> >
{({ {({

View File

@@ -17,6 +17,7 @@ import MakeJournalFormFloatingActions from './MakeJournalFormFloatingActions';
import MakeJournalEntriesField from './MakeJournalEntriesField'; import MakeJournalEntriesField from './MakeJournalEntriesField';
import MakeJournalFormFooter from './MakeJournalFormFooter'; import MakeJournalFormFooter from './MakeJournalFormFooter';
import MakeJournalFormDialogs from './MakeJournalFormDialogs'; import MakeJournalFormDialogs from './MakeJournalFormDialogs';
import MakeJournalFormTopBar from './MakeJournalFormTopBar';
import withSettings from 'containers/Settings/withSettings'; import withSettings from 'containers/Settings/withSettings';
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization'; import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
@@ -169,6 +170,7 @@ function MakeJournalEntriesForm({
onSubmit={handleSubmit} onSubmit={handleSubmit}
> >
<Form> <Form>
<MakeJournalFormTopBar />
<MakeJournalEntriesHeader /> <MakeJournalEntriesHeader />
<MakeJournalEntriesField /> <MakeJournalEntriesField />
<MakeJournalFormFooter /> <MakeJournalFormFooter />

View File

@@ -25,11 +25,12 @@ export default function MakeJournalEntriesTable({
minLinesNumber = 4, minLinesNumber = 4,
currencyCode, currencyCode,
}) { }) {
const { accounts, contacts } = useMakeJournalFormContext(); const { accounts, contacts, branches } = useMakeJournalFormContext();
// Memorized data table columns. // Memorized data table columns.
const columns = useJournalTableEntriesColumns(); const columns = useJournalTableEntriesColumns();
// Handles update datatable data. // Handles update datatable data.
const handleUpdateData = (rowIndex, columnId, value) => { const handleUpdateData = (rowIndex, columnId, value) => {
const newRows = compose( const newRows = compose(
@@ -69,6 +70,7 @@ export default function MakeJournalEntriesTable({
updateData: handleUpdateData, updateData: handleUpdateData,
removeRow: handleRemoveRow, removeRow: handleRemoveRow,
contacts, contacts,
branches,
autoFocus: ['account_id', 0], autoFocus: ['account_id', 0],
currencyCode, currencyCode,
}} }}

View File

@@ -0,0 +1,68 @@
import React from 'react';
import intl from 'react-intl-universal';
import { Button, Alignment, NavbarGroup, Classes } from '@blueprintjs/core';
import styled from 'styled-components';
import { useSetPrimaryBranchToForm } from './utils';
import { useFeatureCan } from 'hooks/state';
import {
Icon,
BranchSelect,
FeatureCan,
FormTopbar,
DetailsBarSkeletonBase,
} from 'components';
import { useMakeJournalFormContext } from './MakeJournalProvider';
import { Features } from 'common';
/**
* Make journal form topbar.
* @returns
*/
export default function MakeJournalFormTopBar() {
// Features guard.
const { featureCan } = useFeatureCan();
// Sets the primary branch to form.
useSetPrimaryBranchToForm();
// Can't display the navigation bar if branches feature is not enabled.
if (!featureCan(Features.Branches)) {
return null;
}
return (
<FormTopbar>
<NavbarGroup align={Alignment.LEFT}>
<FeatureCan feature={Features.Branches}>
<MakeJournalFormSelectBranch />
</FeatureCan>
</NavbarGroup>
</FormTopbar>
);
}
function MakeJournalFormSelectBranch() {
// Invoice form context.
const { branches, isBranchesLoading } = useMakeJournalFormContext();
return isBranchesLoading ? (
<DetailsBarSkeletonBase className={Classes.SKELETON} />
) : (
<BranchSelect
name={'branch_id'}
branches={branches}
input={MakeJournalBranchSelectButton}
popoverProps={{ minimal: true }}
/>
);
}
function MakeJournalBranchSelectButton({ label }) {
return (
<Button
text={intl.get('invoice.branch_button.label', { label })}
minimal={true}
small={true}
icon={<Icon icon={'branch-16'} iconSize={16} />}
/>
);
}

View File

@@ -9,6 +9,7 @@ import {
useCreateJournal, useCreateJournal,
useEditJournal, useEditJournal,
useSettings, useSettings,
useBranches,
useSettingsManualJournals, useSettingsManualJournals,
} from 'hooks/query'; } from 'hooks/query';
@@ -42,10 +43,20 @@ function MakeJournalProvider({ journalId, baseCurrency, ...props }) {
// Loading the journal settings. // Loading the journal settings.
const { isLoading: isSettingsLoading } = useSettingsManualJournals(); const { isLoading: isSettingsLoading } = useSettingsManualJournals();
// Fetches the branches list.
const {
data: branches,
isLoading: isBranchesLoading,
isSuccess: isBranchesSuccess,
} = useBranches();
// Submit form payload. // Submit form payload.
const [submitPayload, setSubmitPayload] = useState({}); const [submitPayload, setSubmitPayload] = useState({});
const [selectJournalCurrency, setSelactJournalCurrency] = useState(null); const [selectJournalCurrency, setSelactJournalCurrency] = useState(null);
// Determines whether the warehouse and branches are loading.
const isFeatureLoading = isBranchesLoading;
const isForeignJournal = const isForeignJournal =
!isEqual(selectJournalCurrency?.currency_code, baseCurrency) && !isEqual(selectJournalCurrency?.currency_code, baseCurrency) &&
!isUndefined(selectJournalCurrency?.currency_code); !isUndefined(selectJournalCurrency?.currency_code);
@@ -56,6 +67,7 @@ function MakeJournalProvider({ journalId, baseCurrency, ...props }) {
currencies, currencies,
manualJournal, manualJournal,
baseCurrency, baseCurrency,
branches,
createJournalMutate, createJournalMutate,
editJournalMutate, editJournalMutate,
@@ -64,8 +76,10 @@ function MakeJournalProvider({ journalId, baseCurrency, ...props }) {
isContactsLoading, isContactsLoading,
isCurrenciesLoading, isCurrenciesLoading,
isJournalLoading, isJournalLoading,
isFeatureLoading,
isSettingsLoading, isSettingsLoading,
isForeignJournal, isForeignJournal,
isBranchesSuccess,
isNewMode: !journalId, isNewMode: !journalId,
submitPayload, submitPayload,

View File

@@ -8,8 +8,11 @@ import {
MoneyFieldCell, MoneyFieldCell,
InputGroupCell, InputGroupCell,
ContactsListFieldCell, ContactsListFieldCell,
BranchesListFieldCell,
} from 'components/DataTableCells'; } from 'components/DataTableCells';
import { safeSumBy } from 'utils'; import { safeSumBy } from 'utils';
import { useFeatureCan } from 'hooks/state';
import { Features } from 'common';
/** /**
* Contact header cell. * Contact header cell.
@@ -44,11 +47,7 @@ export function DebitHeaderCell({ payload: { currencyCode } }) {
* Account footer cell. * Account footer cell.
*/ */
function AccountFooterCell({ payload: { currencyCode } }) { function AccountFooterCell({ payload: { currencyCode } }) {
return ( return <span>{intl.get('total_currency', { currency: currencyCode })}</span>;
<span>
{intl.get('total_currency', { currency: currencyCode })}
</span>
);
} }
/** /**
@@ -107,6 +106,8 @@ export const ActionsCellRenderer = ({
* Retrieve columns of make journal entries table. * Retrieve columns of make journal entries table.
*/ */
export const useJournalTableEntriesColumns = () => { export const useJournalTableEntriesColumns = () => {
const { featureCan } = useFeatureCan();
return React.useMemo( return React.useMemo(
() => [ () => [
{ {
@@ -128,7 +129,7 @@ export const useJournalTableEntriesColumns = () => {
className: 'account', className: 'account',
disableSortBy: true, disableSortBy: true,
width: 160, width: 160,
fieldProps: { allowCreate: true } fieldProps: { allowCreate: true },
}, },
{ {
Header: CreditHeaderCell, Header: CreditHeaderCell,
@@ -157,6 +158,19 @@ export const useJournalTableEntriesColumns = () => {
disableSortBy: true, disableSortBy: true,
width: 120, width: 120,
}, },
...(featureCan(Features.Branches)
? [
{
Header: intl.get('branch'),
id: 'branch_id',
accessor: 'branch_id',
Cell: BranchesListFieldCell,
className: 'branch',
disableSortBy: true,
width: 120,
},
]
: []),
{ {
Header: intl.get('note'), Header: intl.get('note'),
accessor: 'note', accessor: 'note',

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { Intent } from '@blueprintjs/core'; import { Intent } from '@blueprintjs/core';
import { sumBy, setWith, toSafeInteger, get } from 'lodash'; import { sumBy, setWith, toSafeInteger, get, first } from 'lodash';
import moment from 'moment'; import moment from 'moment';
import * as R from 'ramda'; import * as R from 'ramda';
import { import {
@@ -14,6 +14,7 @@ import {
import { AppToaster } from 'components'; import { AppToaster } from 'components';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
import { useMakeJournalFormContext } from './MakeJournalProvider';
const ERROR = { const ERROR = {
JOURNAL_NUMBER_ALREADY_EXISTS: 'JOURNAL.NUMBER.ALREADY.EXISTS', JOURNAL_NUMBER_ALREADY_EXISTS: 'JOURNAL.NUMBER.ALREADY.EXISTS',
@@ -44,6 +45,7 @@ export const defaultManualJournal = {
reference: '', reference: '',
currency_code: '', currency_code: '',
publish: '', publish: '',
branch_id: '',
exchange_rate: '', exchange_rate: '',
entries: [...repeatValue(defaultEntry, 4)], entries: [...repeatValue(defaultEntry, 4)],
}; };
@@ -180,6 +182,7 @@ export const entriesFieldShouldUpdate = (newProps, oldProps) => {
return ( return (
newProps.accounts !== oldProps.accounts || newProps.accounts !== oldProps.accounts ||
newProps.contacts !== oldProps.contacts || newProps.contacts !== oldProps.contacts ||
newProps.branches !== oldProps.branches ||
defaultFastFieldShouldUpdate(newProps, oldProps) defaultFastFieldShouldUpdate(newProps, oldProps)
); );
}; };
@@ -193,3 +196,18 @@ export const currenciesFieldShouldUpdate = (newProps, oldProps) => {
defaultFastFieldShouldUpdate(newProps, oldProps) defaultFastFieldShouldUpdate(newProps, oldProps)
); );
}; };
export const useSetPrimaryBranchToForm = () => {
const { setFieldValue } = useFormikContext();
const { branches, isBranchesSuccess } = useMakeJournalFormContext();
React.useEffect(() => {
if (isBranchesSuccess) {
const primaryBranch = branches.find((b) => b.primary) || first(branches);
if (primaryBranch) {
setFieldValue('branch_id', primaryBranch.id);
}
}
}, [isBranchesSuccess, setFieldValue, branches]);
};

View File

@@ -1833,6 +1833,8 @@
"branches.column.address": "Address", "branches.column.address": "Address",
"branches.column.phone_number": "Phone number", "branches.column.phone_number": "Phone number",
"branches.column.code": "Code", "branches.column.code": "Code",
"select_branch": "Select branch",
"branch": "Branch",
"branch.dialog.label_new_branch": "New Branch", "branch.dialog.label_new_branch": "New Branch",
"branch.dialog.label_edit_branch": "New Branch", "branch.dialog.label_edit_branch": "New Branch",
"branch.dialog.label.branch_name": "Branch Name", "branch.dialog.label.branch_name": "Branch Name",