Compare commits

...

47 Commits

Author SHA1 Message Date
a.bouhuolia
77f0a767b3 Merge branch 'develop' 2021-09-30 16:41:57 +02:00
a.bouhuolia
8a982e5c7e feat: avoid changing setup plan slug. 2021-09-29 11:22:59 +02:00
a.bouhuolia
2f7564eb9c fix: tweaks in arabic localization. 2021-09-29 00:23:01 +02:00
a.bouhuolia
7c2c362585 Merge branch 'develop' of https://github.com/BigcapitalTech/bigcapital-client into develop 2021-09-29 00:11:34 +02:00
a.bouhuolia
786aad438a BIG-78: feat: prevent table row click action on some given nested elements. 2021-09-29 00:07:06 +02:00
a.bouhuolia
d6c78a9908 BIG-118: feat: hide the pagination before table has minimum items length. 2021-09-28 19:52:01 +02:00
a.bouhuolia
0aca6d9af7 BIG-125: prevent items entries from send amount computed attribute. 2021-09-28 18:37:10 +02:00
elforjani13
696943153d feat: quick payment receive & made in action Bar. 2021-09-28 18:32:30 +02:00
elforjani13
2e437d7b65 fix: style universal search. 2021-09-28 17:23:38 +02:00
elforjani13
5b12c4a433 fix: style universal input search. 2021-09-28 14:25:47 +02:00
a.bouhuolia
96269ccafb feat: remove blueprint config provider. 2021-09-28 13:53:43 +02:00
a.bouhuolia
f556f061cb chore: revert to @blueprint/core. 2021-09-28 13:46:30 +02:00
a.bouhuolia
b98e8aeeb4 chore: remove github token. 2021-09-28 12:27:23 +02:00
a.bouhuolia
17d5bbd9d1 chore: fix dockerfile. 2021-09-28 12:20:36 +02:00
a.bouhuolia
e1ab4e4d65 chore: fix dockerfile. 2021-09-28 12:14:32 +02:00
a.bouhuolia
b86a3a19dc chore: fix dockerfile. 2021-09-28 12:12:28 +02:00
a.bouhuolia
3b2796cb6d chore: fix dockerfile. 2021-09-28 11:59:04 +02:00
a.bouhuolia
15ee32f6a4 chore: fix docker build 2021-09-28 11:55:11 +02:00
a.bouhuolia
90e550c902 chore: add github registry login. 2021-09-28 11:52:05 +02:00
a.bouhuolia
cbc0ccbfb9 chore: add auth token to github registry. 2021-09-28 11:30:21 +02:00
a.bouhuolia
526c46b24d chore: copy npmrc file to docker container. 2021-09-28 11:06:27 +02:00
a.bouhuolia
6041c175fd chore: remove package-lock file. 2021-09-28 11:02:28 +02:00
a.bouhuolia
0f58665a0d chore: fix blueprintjs registry. 2021-09-28 10:58:41 +02:00
a.bouhuolia
91036c3e52 Merge branch 'develop' of https://github.com/BigcapitalTech/bigcapital-client into develop 2021-09-27 20:17:11 +02:00
a.bouhuolia
555e3a2434 feat: upgrade the blueprint core and datetitme. 2021-09-27 20:16:52 +02:00
elforjani13
b87e85d5ff BIG-124: Prevent the search Hotkey from writing to the field. 2021-09-27 12:45:32 +02:00
elforjani13
288225a0c1 feat: add quick payment receive & made in action Bar detail. 2021-09-26 22:08:13 +02:00
elforjani13
71f9fa47d4 BiG-5: Complete, add switch small and medium table row size. 2021-09-26 21:02:53 +02:00
elforjani13
fcace4213c feat: change default page size pagination. 2021-09-26 14:02:26 +02:00
a.bouhuolia
31d2b1b09a fix: switch small and medium table row size. 2021-09-26 13:14:03 +02:00
elforjani13
cd5116dbcb BIG-3: refactoring financial reports filtering. 2021-09-25 21:17:17 +02:00
elforjani13
010b660318 BIG-123: alert delete showing in vendor & customer. 2021-09-25 17:11:08 +02:00
elforjani13
4e99607b06 BIG-3: add filtering non-zero items. 2021-09-24 19:41:23 +02:00
elforjani13
a3f1857e91 Merge branch 'develop' of https://github.com/BigcapitalTech/bigcapital-client into develop 2021-09-23 15:42:14 +02:00
elforjani13
460ee2718e BIG-5: add switching between compact and medium row size. 2021-09-23 15:39:49 +02:00
a.bouhuolia
87938b8f41 Merge branch 'develop' of https://github.com/BigcapitalTech/bigcapital-client into develop 2021-09-23 13:03:18 +02:00
elforjani13
cd70bf1d80 BIG-94: handle estimate converted to invoice. 2021-09-23 13:02:50 +02:00
a.bouhuolia
c4f2ea405c BIG-119: fix formatted item type with universal search item. 2021-09-23 13:02:34 +02:00
a.bouhuolia
d1cb7eb51b Merge branch 'develop' of https://github.com/BigcapitalTech/bigcapital-client into develop 2021-09-23 12:13:24 +02:00
a.bouhuolia
e949b1b0c7 BIG-5: feat switching between compact and medium table row size. 2021-09-23 10:37:54 +02:00
a.bouhuolia
9b7382e222 BIG-18: missing locales in Arabic. 2021-09-23 09:53:59 +02:00
a.bouhuolia
364859a793 BIG-117: fix dashboard redirect all routes to homepage once refresh the page. 2021-09-23 09:44:30 +02:00
elforjani13
fd07306102 fix: inventory adjustment style. 2021-09-22 15:25:30 +02:00
elforjani13
5fc4897663 BIG-116: payment made transcation. 2021-09-22 13:56:43 +02:00
Ahmed Bouhuolia
7b85dfee5d chore: add circleci build and publish workflow. 2021-09-21 22:32:14 +02:00
Ahmed Bouhuolia
14012c4d7e test circleci 2021-09-21 22:25:24 +02:00
elforjani13
ecf56f3b99 fix: table header overflow. 2021-09-21 22:09:41 +02:00
179 changed files with 1959 additions and 18468 deletions

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
@bigcapitalhq:registry=https://npm.pkg.github.com

View File

@@ -6,6 +6,15 @@ WORKDIR /app
COPY ./package.json /app/package.json
COPY ./package-lock.json /app/package-lock.json
COPY ./.npmrc /app/.npmrc
ARG GITHUB_USERNAME
ARG GITHUB_PASS
ARG GITHUB_EMAIL
RUN npm install -g npm-cli-login
RUN npm-cli-login -s @bigcapitalhq -r https://npm.pkg.github.com -u $GITHUB_USERNAME -p $GITHUB_PASS -e $GITHUB_EMAIL
RUN npm install

17639
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,8 +4,8 @@
"private": true,
"dependencies": {
"@babel/core": "7.8.4",
"@blueprintjs/core": "^3.38.1",
"@blueprintjs/datetime": "^3.15.2",
"@blueprintjs/core": "^3.50.2",
"@blueprintjs/datetime": "^3.23.12",
"@blueprintjs/popover2": "^0.11.1",
"@blueprintjs/select": "^3.11.2",
"@blueprintjs/table": "^3.8.3",

View File

@@ -13,3 +13,9 @@ export const TABLES = {
MANUAL_JOURNALS: 'manual_journal',
EXPENSES: 'expenses',
};
export const TABLE_SIZE = {
COMPACT: 'compact',
SMALL: 'small',
MEDIUM: 'medium',
}

View File

@@ -4,8 +4,9 @@ import { DateInput } from '@blueprintjs/datetime';
import moment from 'moment';
import intl from 'react-intl-universal';
import { isUndefined } from 'lodash';
import { useAutofocus } from 'hooks';
import { Choose, ListSelect } from 'components';
import { T, Choose, ListSelect } from 'components';
import { momentFormatter } from 'utils';
function AdvancedFilterEnumerationField({ options, value, ...rest }) {
@@ -19,7 +20,7 @@ function AdvancedFilterEnumerationField({ options, value, ...rest }) {
minimal: true,
captureDismiss: true,
}}
defaultText={`Select an option`}
defaultText={<T id={'filter.select_option'} />}
textProp={'label'}
selectedItemProp={'key'}
{...rest}
@@ -32,8 +33,7 @@ const IFieldType = {
BOOLEAN: 'boolean',
NUMBER: 'number',
DATE: 'date',
}
};
function tansformDateValue(date, defaultValue = null) {
return date ? moment(date).toDate() : defaultValue;
@@ -46,13 +46,13 @@ export default function AdvancedFilterValueField2({
fieldType,
options,
onChange,
isFocus
isFocus,
}) {
const [localValue, setLocalValue] = React.useState(value);
React.useEffect(() => {
if (localValue !== value && !isUndefined(value)) {
setLocalValue(value)
setLocalValue(value);
}
}, [localValue, value]);
@@ -106,10 +106,10 @@ export default function AdvancedFilterValueField2({
position: Position.BOTTOM,
}}
shortcuts={true}
placeholder={'Enter date'}
placeholder={intl.get('filter.enter_date')}
fill={true}
inputProps={{
fill: true
fill: true,
}}
/>
</Choose.When>
@@ -120,7 +120,7 @@ export default function AdvancedFilterValueField2({
<Choose.Otherwise>
<InputGroup
placeholder={intl.get('value')}
placeholder={intl.get('filter.value')}
onChange={handleInputChange}
value={localValue}
inputRef={valueRef}
@@ -128,4 +128,4 @@ export default function AdvancedFilterValueField2({
</Choose.Otherwise>
</Choose>
);
}
}

View File

@@ -6,15 +6,36 @@ import { ReactQueryDevtools } from 'react-query/devtools';
import 'style/App.scss';
import 'moment/locale/ar-ly';
import 'moment/locale/es-us'
import 'moment/locale/es-us';
import AppIntlLoader from './AppIntlLoader';
import PrivateRoute from 'components/Guards/PrivateRoute';
import GlobalErrors from 'containers/GlobalErrors/GlobalErrors';
import DashboardPrivatePages from 'components/Dashboard/PrivatePages';
import Authentication from 'components/Authentication';
import { SplashScreen } from '../components';
import { queryConfig } from '../hooks/query/base'
import { queryConfig } from '../hooks/query/base';
/**
* App inner.
*/
function AppInsider({ history }) {
return (
<div className="App">
<Router history={history}>
<Switch>
<Route path={'/auth'} component={Authentication} />
<Route path={'/'}>
<PrivateRoute component={DashboardPrivatePages} />
</Route>
</Switch>
</Router>
<GlobalErrors />
</div>
);
}
/**
* Core application.
@@ -31,21 +52,10 @@ export default function App() {
<SplashScreen />
<AppIntlLoader>
<div className="App">
<Router history={history}>
<Switch>
<Route path={'/auth'} component={Authentication} />
<Route path={'/'}>
<PrivateRoute component={DashboardPrivatePages} />
</Route>
</Switch>
</Router>
<GlobalErrors />
</div>
<AppInsider history={history} />
</AppIntlLoader>
<ReactQueryDevtools initialIsOpen />
</QueryClientProvider>
);
}
}

View File

@@ -7,9 +7,10 @@ import rtlDetect from 'rtl-detect';
import * as R from 'ramda';
import { AppIntlProvider } from './AppIntlProvider';
import { useSplashLoading } from '../hooks/state';
import { useWatch } from '../hooks';
import withDashboardActions from '../containers/Dashboard/withDashboardActions';
import withDashboard from '../containers/Dashboard/withDashboard';
const SUPPORTED_LOCALES = [
{ name: 'English', value: 'en' },
@@ -63,20 +64,13 @@ function transformMomentLocale(currentLocale) {
}
/**
* Application Intl loader.
* Loads application locales of the given current locale.
* @param {string} currentLocale
* @returns {{ isLoading: boolean }}
*/
function AppIntlLoader({ appIntlIsLoading, setAppIntlIsLoading, children }) {
const [isLocalsLoading, setIsLocalsLoading] = React.useState(true);
const [isYupLoading, setIsYupLoading] = React.useState(true);
// Retrieve the current locale.
const currentLocale = getCurrentLocal();
// Detarmines the document direction based on the given locale.
const isRTL = rtlDetect.isRtlLang(currentLocale);
// Modifies the html document direction
useDocumentDirectionModifier(currentLocale, isRTL);
function useAppLoadLocales(currentLocale) {
const [startLoading, stopLoading] = useSplashLoading();
const [isLoading, setIsLoading] = React.useState(true);
React.useEffect(() => {
// Lodas the locales data file.
@@ -91,33 +85,72 @@ function AppIntlLoader({ appIntlIsLoading, setAppIntlIsLoading, children }) {
})
.then(() => {
moment.locale(transformMomentLocale(currentLocale));
setIsLocalsLoading(false);
setIsLoading(false);
});
}, [currentLocale, setIsLocalsLoading]);
}, [currentLocale, stopLoading]);
// Watches the value to start/stop splash screen.
useWatch(isLoading, (value) => (value ? startLoading() : stopLoading()), {
immediate: true,
});
return { isLoading };
}
/**
* Loads application yup locales based on the given current locale.
* @param {string} currentLocale
* @returns {{ isLoading: boolean }}
*/
function useAppYupLoadLocales(currentLocale) {
const [startLoading, stopLoading] = useSplashLoading();
const [isLoading, setIsLoading] = React.useState(true);
React.useEffect(() => {
loadYupLocales(currentLocale)
.then(({ locale }) => {
setLocale(locale);
setIsYupLoading(false);
setIsLoading(false);
})
.then(() => {});
}, [currentLocale]);
}, [currentLocale, stopLoading]);
React.useEffect(() => {
if (!isLocalsLoading && !isYupLoading) {
setAppIntlIsLoading(false);
}
// Watches the valiue to start/stop splash screen.
useWatch(isLoading, (value) => (value ? startLoading() : stopLoading()), {
immediate: true,
});
return { isLoading };
}
/**
* Application Intl loader.
*/
function AppIntlLoader({ children }) {
// Retrieve the current locale.
const currentLocale = getCurrentLocal();
// Detarmines the document direction based on the given locale.
const isRTL = rtlDetect.isRtlLang(currentLocale);
// Modifies the html document direction
useDocumentDirectionModifier(currentLocale, isRTL);
// Loads yup localization of the given locale.
const { isLoading: isAppYupLocalesLoading } =
useAppYupLoadLocales(currentLocale);
// Loads application locales of the given locale.
const { isLoading: isAppLocalesLoading } = useAppLoadLocales(currentLocale);
// Detarmines whether the app locales loading.
const isLoading = isAppYupLocalesLoading && isAppLocalesLoading;
return (
<AppIntlProvider currentLocale={currentLocale} isRTL={isRTL}>
{appIntlIsLoading ? null : children}
{isLoading ? null : children}
</AppIntlProvider>
);
}
export default R.compose(
withDashboardActions,
withDashboard(({ appIntlIsLoading }) => ({ appIntlIsLoading })),
)(AppIntlLoader);
export default R.compose(withDashboardActions)(AppIntlLoader);

View File

@@ -10,6 +10,7 @@ function AppIntlProvider({ currentLocale, isRTL, children }) {
currentLocale,
isRTL,
isLTR: !isRTL,
direction: isRTL ? 'rtl' : 'ltr',
};
return (

View File

@@ -6,7 +6,6 @@ import authenticationRoutes from 'routes/authentication';
import { FormattedMessage as T } from 'components';
import Icon from 'components/Icon';
import { useIsAuthenticated } from 'hooks/state';
import {AuthenticationBoot} from '../containers/Authentication/AuthenticationBoot';
import 'style/pages/Authentication/Auth.scss';
function PageFade(props) {
@@ -26,7 +25,6 @@ export default function AuthenticationWrapper({ ...rest }) {
) : (
<BodyClassName className={'authentication'}>
<div class="authentication-page">
<AuthenticationBoot />
<a
href={'http://bigcapital.ly'}
className={'authentication-page__goto-bigcapital'}

View File

@@ -0,0 +1,10 @@
import React from 'react';
import { firstLettersArgs } from 'utils';
export default function AvatarCell({ row: { original }, size }) {
return (
<span className="avatar" data-size={size}>
{firstLettersArgs(original?.display_name)}
</span>
);
}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import classNames from 'classnames';
export default function Card({ className, children }) {
return <div className={classNames('card', className)}>{children}</div>;
}

View File

@@ -1,23 +1,30 @@
import React from 'react';
import * as R from 'ramda';
import { useUser, useCurrentOrganization } from 'hooks/query';
import { useUser, useCurrentOrganization } from '../../hooks/query';
import { useSplashLoading } from '../../hooks/state';
import { useWatch, useWhen } from '../../hooks';
import withAuthentication from '../../containers/Authentication/withAuthentication';
import withDashboardActions from '../../containers/Dashboard/withDashboardActions';
import { setCookie, getCookie } from '../../utils';
/**
* Dashboard async booting.
*/
function DashboardBootJSX({ setAppIsLoading, authenticatedUserId }) {
function DashboardBootJSX({ authenticatedUserId }) {
// Fetches the current user's organization.
const { isSuccess: isCurrentOrganizationSuccess, data: organization } =
useCurrentOrganization();
const {
isSuccess: isCurrentOrganizationSuccess,
isLoading: isOrgLoading,
data: organization,
} = useCurrentOrganization();
// Authenticated user.
const { isSuccess: isAuthUserSuccess, data: authUser } =
useUser(authenticatedUserId);
const {
isSuccess: isAuthUserSuccess,
isLoading: isAuthUserLoading,
} = useUser(authenticatedUserId);
// Initial locale cookie value.
const localeCookie = getCookie('locale');
@@ -48,23 +55,39 @@ function DashboardBootJSX({ setAppIsLoading, authenticatedUserId }) {
}
}, [localeCookie, organization]);
React.useEffect(() => {
// Once the all requests complete change the app loading state.
if (
isAuthUserSuccess &&
const [startLoading, stopLoading] = useSplashLoading();
// Splash loading when organization request loading and
// applicaiton still not booted.
useWatch(isOrgLoading, (value) => {
value && !isBooted.current && startLoading();
});
// Splash loading when request authenticated user loading and
// application still not booted yet.
useWatch(isAuthUserLoading, (value) => {
value && !isBooted.current && startLoading();
});
// Stop splash loading once organization request success.
useWatch(isCurrentOrganizationSuccess, (value) => {
value && stopLoading();
});
// Stop splash loading once authenticated user request success.
useWatch(isAuthUserSuccess, (value) => {
value && stopLoading();
});
// Once the all requests complete change the app loading state.
useWhen(
isAuthUserSuccess &&
isCurrentOrganizationSuccess &&
localeCookie === organization?.metadata?.language
) {
setAppIsLoading(false);
localeCookie === organization?.metadata?.language,
() => {
isBooted.current = true;
}
}, [
isAuthUserSuccess,
isCurrentOrganizationSuccess,
organization,
setAppIsLoading,
localeCookie,
]);
},
);
return null;
}
@@ -72,5 +95,4 @@ export const DashboardBoot = R.compose(
withAuthentication(({ authenticatedUserId }) => ({
authenticatedUserId,
})),
withDashboardActions,
)(DashboardBootJSX);

View File

@@ -1,36 +0,0 @@
import React from 'react';
import {
Button,
PopoverInteractionKind,
Popover,
Menu,
MenuItem,
MenuDivider,
Classes
} from '@blueprintjs/core';
import { Icon } from 'components';
export function DashboardRowsHeightButton() {
return (
<Popover
minimal={true}
content={
<Menu>
<MenuDivider title={'Rows height'} />
<MenuItem text="Compact" />
<MenuItem text="Medium" />
</Menu>
}
placement="bottom-start"
modifiers={{
offset: { offset: '0, 4' },
}}
interactionKind={PopoverInteractionKind.CLICK}
>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="rows-height" iconSize={16} />}
/>
</Popover>
);
}

View File

@@ -0,0 +1,70 @@
import React from 'react';
import {
Button,
PopoverInteractionKind,
Popover,
Menu,
MenuItem,
MenuDivider,
Classes,
Tooltip,
Position,
} from '@blueprintjs/core';
import clsx from 'classnames';
import { Icon, T } from 'components';
import Style from './style.module.scss';
/**
* Dashboard rows height button control.
*/
export function DashboardRowsHeightButton({ initialValue, value, onChange }) {
const [localSize, setLocalSize] = React.useState(initialValue);
// Handle menu item click.
const handleItemClick = (size) => (event) => {
setLocalSize(size);
onChange && onChange(size, event);
};
// Button icon name.
const btnIcon = `table-row-${localSize}`;
return (
<Popover
minimal={true}
content={
<Menu className={Style.menu}>
<MenuDivider title={<T id={'dashboard.rows_height'} />} />
<MenuItem
onClick={handleItemClick('small')}
text={<T id={'dashboard.row_small'} />}
/>
<MenuItem
onClick={handleItemClick('medium')}
text={<T id={'dashboard.row_medium'} />}
/>
</Menu>
}
placement="bottom-start"
modifiers={{
offset: { offset: '0, 4' },
}}
interactionKind={PopoverInteractionKind.CLICK}
>
<Tooltip
content={<T id={'dashboard.rows_height'} />}
minimal={true}
position={Position.BOTTOM}
>
<Button
className={clsx(Classes.MINIMAL, Style.button)}
icon={<Icon icon={btnIcon} iconSize={16} />}
/>
</Tooltip>
</Popover>
);
}
DashboardRowsHeightButton.defaultProps = {
initialValue: 'medium',
};

View File

@@ -0,0 +1,12 @@
.menu{
:global .bp3-heading{
font-weight: 400;
opacity: 0.5;
font-size: 12px;
}
}
.button{
min-width: 34px;
}

View File

@@ -4,10 +4,9 @@ import { Switch, Route } from 'react-router';
import Dashboard from 'components/Dashboard/Dashboard';
import SetupWizardPage from 'containers/Setup/WizardSetupPage';
import EnsureOrganizationIsReady from 'components/Guards/EnsureOrganizationIsReady';
import EnsureOrganizationIsNotReady from 'components/Guards/EnsureOrganizationIsNotReady';
import EnsureOrganizationIsReady from '../../components/Guards/EnsureOrganizationIsReady';
import EnsureOrganizationIsNotReady from '../../components/Guards/EnsureOrganizationIsNotReady';
import { PrivatePagesProvider } from './PrivatePagesProvider';
import { DashboardBoot } from '../../components';
import 'style/pages/Dashboard/Dashboard.scss';
@@ -17,8 +16,6 @@ import 'style/pages/Dashboard/Dashboard.scss';
export default function DashboardPrivatePages() {
return (
<PrivatePagesProvider>
<DashboardBoot />
<Switch>
<Route path={'/setup'}>
<EnsureOrganizationIsNotReady>

View File

@@ -1,9 +1,31 @@
import React from 'react';
import * as R from 'ramda';
import { AuthenticatedUser } from './AuthenticatedUser';
import { DashboardBoot } from '../../components';
import withDashboard from '../../containers/Dashboard/withDashboard';
/**
* Private pages provider.
*/
export function PrivatePagesProvider({ children }) {
return <AuthenticatedUser>{children}</AuthenticatedUser>;
function PrivatePagesProviderComponent({
splashScreenCompleted,
// #ownProps
children,
}) {
return (
<AuthenticatedUser>
<DashboardBoot />
{splashScreenCompleted ? children : null}
</AuthenticatedUser>
);
}
export const PrivatePagesProvider = R.compose(
withDashboard(({ splashScreenCompleted }) => ({
splashScreenCompleted,
})),
)(PrivatePagesProviderComponent);

View File

@@ -3,13 +3,12 @@ import * as R from 'ramda';
import BigcapitalLoading from './BigcapitalLoading';
import withDashboard from '../../containers/Dashboard/withDashboard';
function SplashScreenComponent({ appIsLoading, appIntlIsLoading }) {
return appIsLoading || appIntlIsLoading ? <BigcapitalLoading /> : null;
function SplashScreenComponent({ splashScreenLoading }) {
return splashScreenLoading ? <BigcapitalLoading /> : null;
}
export const SplashScreen = R.compose(
withDashboard(({ appIsLoading, appIntlIsLoading }) => ({
appIsLoading,
appIntlIsLoading,
withDashboard(({ splashScreenLoading }) => ({
splashScreenLoading,
})),
)(SplashScreenComponent);

View File

@@ -196,6 +196,9 @@ export default function DataTable(props) {
DataTable.defaultProps = {
pagination: false,
hidePaginationNoPages: true,
size: null,
spinnerProps: { size: 30 },
expandToggleColumn: 1,

View File

@@ -4,9 +4,11 @@ import { If } from 'components';
import { Skeleton } from 'components';
import { useAppIntlContext } from 'components/AppIntlProvider';
import TableContext from './TableContext';
import { saveInvoke } from 'utils';
import { saveInvoke, ignoreEventFromSelectors } from 'utils';
import { isCellLoading } from './utils';
const ROW_CLICK_SELECTORS_INGORED = ['.expand-toggle', '.selection-checkbox'];
/**
* Table cell.
*/
@@ -50,6 +52,9 @@ export default function TableCell({ cell, row, index }) {
}
// Handle cell click action.
const handleCellClick = (event) => {
if (ignoreEventFromSelectors(event, ROW_CLICK_SELECTORS_INGORED)) {
return;
}
saveInvoke(onCellClick, cell, event);
};
@@ -58,7 +63,7 @@ export default function TableCell({ cell, row, index }) {
{...cell.getCellProps({
className: classNames(cell.column.className, 'td', {
'is-text-overview': cell.column.textOverview,
'clickable': cell.column.clickable,
clickable: cell.column.clickable,
'align-right': cell.column.align === 'right',
}),
onClick: handleCellClick,
@@ -83,11 +88,15 @@ export default function TableCell({ cell, row, index }) {
// to build the toggle for expanding a row
}
<If condition={cell.row.canExpand && expandable && isExpandColumn}>
<span {...getToggleRowExpandedProps({ className: 'expand-toggle' })}>
<span
{...getToggleRowExpandedProps({
className: 'expand-toggle',
})}
style={{}}
>
<span
className={classNames({
'arrow-down': isExpanded,
'arrow-right': !isExpanded,
className={classNames('expand-arrow', {
'is-expanded': isExpanded,
})}
/>
</span>

View File

@@ -3,7 +3,7 @@ import { Checkbox } from '@blueprintjs/core';
export default function TableIndeterminateCheckboxRow({ row }) {
return (
<div>
<div class="selection-checkbox">
<Checkbox {...row.getToggleRowSelectedProps()} />
</div>
);

View File

@@ -14,12 +14,15 @@ export default function TablePagination() {
pageCount,
state: { pageIndex, pageSize },
},
props: { pagination, loading, onPaginationChange },
props: { pagination, loading, onPaginationChange, hidePaginationNoPages },
} = useContext(TableContext);
const triggerOnPaginationChange = useCallback((payload) => {
saveInvoke(onPaginationChange, payload)
}, [onPaginationChange]);
const triggerOnPaginationChange = useCallback(
(payload) => {
saveInvoke(onPaginationChange, payload);
},
[onPaginationChange],
);
// Handles the page changing.
const handlePageChange = useCallback(
@@ -45,8 +48,14 @@ export default function TablePagination() {
[gotoPage, setPageSize, triggerOnPaginationChange],
);
// Detarmines when display the pagination.
const showPagination =
pagination &&
((hidePaginationNoPages && pageCount > 1) || !hidePaginationNoPages) &&
!loading;
return (
<If condition={pagination && !loading}>
showPagination && (
<Pagination
currentPage={pageIndex + 1}
total={pageSize * pageCount}
@@ -54,6 +63,6 @@ export default function TablePagination() {
onPageChange={handlePageChange}
onPageSizeChange={handlePageSizeChange}
/>
</If>
)
);
}

View File

@@ -1,5 +1,5 @@
import React, { useContext } from 'react';
import classNames from 'classnames';
import clsx from 'classnames';
import { ScrollSync } from 'react-scroll-sync';
import TableContext from './TableContext';
@@ -9,12 +9,20 @@ import TableContext from './TableContext';
export default function TableWrapper({ children }) {
const {
table: { getTableProps },
props: { sticky, pagination, loading, expandable, virtualizedRows, className },
props: {
sticky,
pagination,
loading,
expandable,
virtualizedRows,
className,
size,
},
} = useContext(TableContext);
return (
<div
className={classNames('bigcapital-datatable', className, {
className={clsx('bigcapital-datatable', className, {
'has-sticky': sticky,
'has-pagination': pagination,
'is-expandable': expandable,
@@ -25,7 +33,9 @@ export default function TableWrapper({ children }) {
<ScrollSync>
<div
{...getTableProps({ style: { minWidth: 'none' } })}
className="table"
className={clsx('table', {
[`table-size--${size}`]: size,
})}
>
{children}
</div>

View File

@@ -6,7 +6,6 @@ import { compose } from 'utils';
import withAuthentication from 'containers/Authentication/withAuthentication';
import withOrganization from 'containers/Organization/withOrganization';
function EnsureOrganizationIsReady({
// #ownProps
children,
@@ -15,10 +14,10 @@ function EnsureOrganizationIsReady({
// #withOrganizationByOrgId
isOrganizationReady,
}) {
return (isOrganizationReady) ? children : (
<Redirect
to={{ pathname: redirectTo }}
/>
return isOrganizationReady ? (
children
) : (
<Redirect to={{ pathname: redirectTo }} />
);
}
@@ -28,4 +27,4 @@ export default compose(
organizationId: props.currentOrganizationId,
})),
withOrganization(({ isOrganizationReady }) => ({ isOrganizationReady })),
)(EnsureOrganizationIsReady);
)(EnsureOrganizationIsReady);

View File

@@ -86,7 +86,7 @@ function Pagination({
currentPage,
total,
size,
pageSizesOptions = [5, 12, 20, 30, 50, 75, 100, 150],
pageSizesOptions = [20, 30, 50, 75, 100, 150],
onPageChange,
onPageSizeChange,
}) {

View File

@@ -141,7 +141,6 @@ function UniversalSearchBar({ isOpen, onSearchTypeChange, ...listProps }) {
{...handlers}
>
<InputGroup
autoFocus={true}
large={true}
leftIcon={<Icon icon={'universal-search'} iconSize={20} />}
placeholder={intl.get('universal_search.placeholder')}

View File

@@ -58,6 +58,7 @@ import AccountsSuggestField from './AccountsSuggestField';
import MaterialProgressBar from './MaterialProgressBar';
import { MoneyFieldCell } from './DataTableCells';
import Card from './Card';
import AvaterCell from './AvaterCell';
import { ItemsMultiSelect } from './Items';
@@ -71,13 +72,13 @@ export * from './PdfPreview';
export * from './Details';
export * from './Drawer/DrawerInsider';
export * from './Drawer/DrawerMainTabs';
export * from './TotalLines/index'
export * from './TotalLines/index';
export * from './Alert';
export * from './Subscriptions';
export * from './Dashboard';
export * from './Drawer';
export * from './Forms';
export * from './MultiSelectTaggable'
export * from './MultiSelectTaggable';
export * from './Utils/FormatNumber';
export * from './Utils/FormatDate';
@@ -150,4 +151,5 @@ export {
MoneyFieldCell,
ItemsMultiSelect,
Card,
AvaterCell,
};

View File

@@ -12,6 +12,7 @@ import { useHistory } from 'react-router-dom';
import {
AdvancedFilterPopover,
DashboardFilterButton,
DashboardRowsHeightButton,
FormattedMessage as T,
} from 'components';
@@ -22,6 +23,8 @@ import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withManualJournalsActions from './withManualJournalsActions';
import withManualJournals from './withManualJournals';
import withSettingsActions from '../../Settings/withSettingsActions';
import withSettings from '../../Settings/withSettings';
import { If, DashboardActionViewsList } from 'components';
@@ -36,6 +39,12 @@ function ManualJournalActionsBar({
// #withManualJournals
manualJournalsFilterConditions,
// #withSettings
manualJournalsTableSize,
// #withSettingsActions
addSetting,
}) {
// History context.
const history = useHistory();
@@ -62,6 +71,11 @@ function ManualJournalActionsBar({
refresh();
};
// Handle table row size change.
const handleTableRowSizeChange = (size) => {
addSetting('manualJournals', 'tableSize', size);
};
return (
<DashboardActionsBar>
<NavbarGroup>
@@ -119,6 +133,12 @@ function ManualJournalActionsBar({
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
<NavbarDivider />
<DashboardRowsHeightButton
initialValue={manualJournalsTableSize}
onChange={handleTableRowSizeChange}
/>
<NavbarDivider />
</NavbarGroup>
<NavbarGroup align={Alignment.RIGHT}>
<Button
@@ -134,7 +154,11 @@ function ManualJournalActionsBar({
export default compose(
withDialogActions,
withManualJournalsActions,
withSettingsActions,
withManualJournals(({ manualJournalsTableState }) => ({
manualJournalsFilterConditions: manualJournalsTableState.filterRoles,
})),
withSettings(({ manualJournalsSettings }) => ({
manualJournalsTableSize: manualJournalsSettings?.tableSize,
})),
)(ManualJournalActionsBar);

View File

@@ -13,6 +13,7 @@ import withManualJournals from './withManualJournals';
import withManualJournalsActions from './withManualJournalsActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import withSettings from '../../Settings/withSettings';
import { useManualJournalsContext } from './ManualJournalsListProvider';
import { useMemorizedColumnsWidths } from 'hooks';
@@ -38,6 +39,9 @@ function ManualJournalsDataTable({
// #ownProps
onSelectedRowsChange,
// #withSettings
manualJournalsTableSize,
}) {
// Manual journals context.
const {
@@ -109,7 +113,6 @@ function ManualJournalsDataTable({
data={manualJournals}
manualSortBy={true}
selectionColumn={true}
expandable={true}
sticky={true}
loading={isManualJournalsLoading}
headerLoading={isManualJournalsLoading}
@@ -125,6 +128,7 @@ function ManualJournalsDataTable({
onCellClick={handleCellClick}
initialColumnsWidths={initialColumnsWidths}
onColumnResizing={handleColumnResizing}
size={manualJournalsTableSize}
payload={{
onDelete: handleDeleteJournal,
onPublish: handlePublishJournal,
@@ -143,4 +147,7 @@ export default compose(
})),
withAlertsActions,
withDrawerActions,
withSettings(({ manualJournalsSettings }) => ({
manualJournalsTableSize: manualJournalsSettings?.tableSize,
})),
)(ManualJournalsDataTable);

View File

@@ -37,6 +37,7 @@ export const useManualJournalsColumns = () => {
className: 'journal_number',
width: 100,
clickable: true,
textOverview: true,
},
{
id: 'journal_type',
@@ -44,6 +45,7 @@ export const useManualJournalsColumns = () => {
accessor: 'journal_type',
width: 110,
clickable: true,
textOverview: true,
},
{
id: 'status',

View File

@@ -17,6 +17,7 @@ import {
If,
DashboardActionViewsList,
DashboardFilterButton,
DashboardRowsHeightButton,
} from 'components';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
@@ -27,7 +28,8 @@ import withDialogActions from 'containers/Dialog/withDialogActions';
import withAccounts from 'containers/Accounts/withAccounts';
import withAlertActions from 'containers/Alert/withAlertActions';
import withAccountsTableActions from './withAccountsTableActions';
import withSettings from '../Settings/withSettings';
import withSettingsActions from '../Settings/withSettingsActions';
import { compose } from 'utils';
/**
@@ -50,6 +52,12 @@ function AccountsActionsBar({
// #ownProps
onFilterChanged,
// #withSettings
accountsTableSize,
// #withSettingsActions
addSetting,
}) {
const { resourceViews, fields } = useAccountsChartContext();
@@ -93,6 +101,10 @@ function AccountsActionsBar({
refresh();
};
// Handle table row size change.
const handleTableRowSizeChange = (size) => {
addSetting('accounts', 'tableSize', size);
};
return (
<DashboardActionsBar>
<NavbarGroup>
@@ -165,6 +177,12 @@ function AccountsActionsBar({
icon={<Icon icon="file-import-16" iconSize={16} />}
text={<T id={'import'} />}
/>
<NavbarDivider />
<DashboardRowsHeightButton
initialValue={accountsTableSize}
onChange={handleTableRowSizeChange}
/>
<NavbarDivider />
<Switch
labelElement={<T id={'inactive'} />}
defaultChecked={accountsInactiveMode}
@@ -185,10 +203,14 @@ function AccountsActionsBar({
export default compose(
withDialogActions,
withAlertActions,
withSettingsActions,
withAccounts(({ accountsSelectedRows, accountsTableState }) => ({
accountsSelectedRows,
accountsInactiveMode: accountsTableState.inactiveMode,
accountsFilterConditions: accountsTableState.filterRoles,
})),
withSettings(({ accountsSettings }) => ({
accountsTableSize: accountsSettings.tableSize,
})),
withAccountsTableActions,
)(AccountsActionsBar);

View File

@@ -10,6 +10,7 @@ import { TABLES } from 'common/tables';
import TableVirtualizedListRows from 'components/Datatable/TableVirtualizedRows';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
import withSettings from '../Settings/withSettings';
import { useAccountsChartContext } from './AccountsChartProvider';
import { useMemorizedColumnsWidths } from '../../hooks';
@@ -30,6 +31,9 @@ function AccountsDataTable({
// #withDrawerActions
openDrawer,
// #withSettings
accountsTableSize,
}) {
const { isAccountsLoading, isAccountsFetching, accounts } =
useAccountsChartContext();
@@ -102,11 +106,12 @@ function AccountsDataTable({
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
// #TableVirtualizedListRows props.
vListrowHeight={42}
vListrowHeight={accountsTableSize == 'small' ? 40 : 42}
vListOverscanRowCount={0}
onCellClick={handleCellClick}
initialColumnsWidths={initialColumnsWidths}
onColumnResizing={handleColumnResizing}
size={accountsTableSize}
payload={{
onEdit: handleEditAccount,
onDelete: handleDeleteAccount,
@@ -123,4 +128,7 @@ export default compose(
withAlertsActions,
withDrawerActions,
withDialogActions,
withSettings(({ accountsSettings }) => ({
accountsTableSize: accountsSettings.tableSize,
})),
)(AccountsDataTable);

View File

@@ -35,7 +35,7 @@ function BillTransactionDeleteAlert({
deleteLandedCostMutate(BillId)
.then(() => {
AppToaster.show({
message: intl.get('bill.action.delete_successfully.landed_cost'),
message: intl.get('landed_cost.action.delete.success_message'),
intent: Intent.SUCCESS,
});
closeAlert(name);
@@ -56,7 +56,11 @@ function BillTransactionDeleteAlert({
onConfirm={handleConfirmLandedCostDelete}
loading={isLoading}
>
<p><T id={`Once your delete this located landed cost, you won't be able to restore it later, Are your sure you want to delete this transaction?`}/></p>
<p>
<T
id={`Once your delete this located landed cost, you won't be able to restore it later, Are your sure you want to delete this transaction?`}
/>
</p>
</Alert>
);
}

View File

@@ -31,7 +31,7 @@ function CustomerBulkDeleteAlert({
closeAlert(name);
};
console.log(customersIds, 'EE');
// Handle confirm customers bulk delete.
const handleConfirmBulkDelete = useCallback(() => {

View File

@@ -1,17 +1,17 @@
import React, { useCallback } from 'react';
import intl from 'react-intl-universal';
import { FormattedMessage as T, FormattedHTMLMessage } from 'components';
import { FormattedMessage as T, FormattedHTMLMessage } from 'components';
import { Intent, Alert } from '@blueprintjs/core';
import { AppToaster } from 'components';
import { transformErrors } from 'containers/Customers/utils';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { useDeleteCustomer } from 'hooks/query';
import { compose } from 'utils';
/**
* Customer delete alert.
*/
@@ -24,12 +24,11 @@ function CustomerDeleteAlert({
// #withAlertActions
closeAlert,
// #withDrawerActions
closeDrawer,
}) {
const {
mutateAsync: deleteCustomerMutate,
isLoading
} = useDeleteCustomer();
const { mutateAsync: deleteCustomerMutate, isLoading } = useDeleteCustomer();
// handle cancel delete alert.
const handleCancelDeleteAlert = () => {
@@ -44,10 +43,17 @@ function CustomerDeleteAlert({
message: intl.get('the_customer_has_been_deleted_successfully'),
intent: Intent.SUCCESS,
});
closeDrawer('customer-details-drawer');
})
.catch(({ response: { data: { errors } } }) => {
transformErrors(errors);
})
.catch(
({
response: {
data: { errors },
},
}) => {
transformErrors(errors);
},
)
.finally(() => {
closeAlert(name);
});
@@ -76,4 +82,5 @@ function CustomerDeleteAlert({
export default compose(
withAlertStoreConnect(),
withAlertActions,
withDrawerActions,
)(CustomerDeleteAlert);

View File

@@ -46,7 +46,24 @@ function EstimateDeleteAlert({
});
closeDrawer('estimate-detail-drawer');
})
.catch(({ errors }) => {})
.catch(
({
response: {
data: { errors },
},
}) => {
if (
errors.find((e) => e.type === 'SALE_ESTIMATE_CONVERTED_TO_INVOICE')
) {
AppToaster.show({
intent: Intent.DANGER,
message: intl.get(
'estimate.delete.error.estimate_converted_to_invoice',
),
});
}
},
)
.finally(() => {
closeAlert(name);
});

View File

@@ -1,6 +1,6 @@
import React, { useCallback } from 'react';
import intl from 'react-intl-universal';
import { FormattedMessage as T, FormattedHTMLMessage } from 'components';
import { FormattedMessage as T, FormattedHTMLMessage } from 'components';
import { Intent, Alert } from '@blueprintjs/core';
import { AppToaster } from 'components';
@@ -9,6 +9,7 @@ import { useDeleteVendor } from 'hooks/query';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { compose } from 'utils';
@@ -24,8 +25,10 @@ function VendorDeleteAlert({
// #withAlertActions
closeAlert,
// #withDrawerActions
closeDrawer,
}) {
const { mutateAsync: deleteVendorMutate, isLoading } = useDeleteVendor();
// Handle cancel delete the vendor.
@@ -41,6 +44,7 @@ function VendorDeleteAlert({
message: intl.get('the_vendor_has_been_deleted_successfully'),
intent: Intent.SUCCESS,
});
closeDrawer('vendor-details-drawer');
})
.catch(
({
@@ -79,4 +83,5 @@ function VendorDeleteAlert({
export default compose(
withAlertStoreConnect(),
withAlertActions,
withDrawerActions,
)(VendorDeleteAlert);

View File

@@ -1,15 +0,0 @@
import React from 'react';
import * as R from 'ramda';
import withDashboardActions from '../../containers/Dashboard/withDashboardActions';
function AuthenticationBootJSX({ setAppIsLoading }) {
React.useEffect(() => {
setAppIsLoading(false);
}, [setAppIsLoading]);
return null;
}
export const AuthenticationBoot = R.compose(withDashboardActions)(
AuthenticationBootJSX,
);

View File

@@ -8,16 +8,17 @@ import {
Switch,
Alignment,
} from '@blueprintjs/core';
import { FormattedMessage as T } from 'components';
import { useHistory } from 'react-router-dom';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import {
If,
Icon,
FormattedMessage as T,
DashboardActionViewsList,
AdvancedFilterPopover,
DashboardFilterButton,
DashboardRowsHeightButton,
} from 'components';
import { useCustomersListContext } from './CustomersListProvider';
@@ -26,6 +27,8 @@ import { useRefreshCustomers } from 'hooks/query/customers';
import withCustomers from './withCustomers';
import withCustomersActions from './withCustomersActions';
import withAlertActions from 'containers/Alert/withAlertActions';
import withSettingsActions from '../../Settings/withSettingsActions';
import withSettings from '../../Settings/withSettings';
import { compose } from 'utils';
@@ -43,6 +46,12 @@ function CustomerActionsBar({
// #withAlertActions
openAlert,
// #withSettings
customersTableSize,
// #withSettingsActions
addSetting,
}) {
// History context.
const history = useHistory();
@@ -74,7 +83,14 @@ function CustomerActionsBar({
};
// Handle click a refresh customers
const handleRefreshBtnClick = () => { refresh(); };
const handleRefreshBtnClick = () => {
refresh();
};
// Handle table row size change.
const handleTableRowSizeChange = (size) => {
addSetting('customers', 'tableSize', size);
};
return (
<DashboardActionsBar>
@@ -130,6 +146,12 @@ function CustomerActionsBar({
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
<NavbarDivider />
<DashboardRowsHeightButton
initialValue={customersTableSize}
onChange={handleTableRowSizeChange}
/>
<NavbarDivider />
<Switch
labelElement={<T id={'inactive'} />}
defaultChecked={accountsInactiveMode}
@@ -149,10 +171,14 @@ function CustomerActionsBar({
export default compose(
withCustomersActions,
withSettingsActions,
withCustomers(({ customersSelectedRows, customersTableState }) => ({
customersSelectedRows,
accountsInactiveMode: customersTableState.inactiveMode,
customersFilterConditions: customersTableState.filterRoles,
})),
withSettings(({ customersSettings }) => ({
customersTableSize: customersSettings?.tableSize,
})),
withAlertActions,
)(CustomerActionsBar);

View File

@@ -14,6 +14,7 @@ import withCustomersActions from './withCustomersActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import withSettings from '../../Settings/withSettings';
import { useCustomersListContext } from './CustomersListProvider';
import { useMemorizedColumnsWidths } from 'hooks';
@@ -38,6 +39,9 @@ function CustomersTable({
// #withDialogActions
openDialog,
// #withSettings
customersTableSize,
}) {
const history = useHistory();
@@ -135,6 +139,7 @@ function CustomersTable({
onCellClick={handleCellClick}
initialColumnsWidths={initialColumnsWidths}
onColumnResizing={handleColumnResizing}
size={customersTableSize}
payload={{
onDelete: handleCustomerDelete,
onEdit: handleCustomerEdit,
@@ -155,4 +160,7 @@ export default compose(
withCustomersActions,
withDrawerActions,
withCustomers(({ customersTableState }) => ({ customersTableState })),
withSettings(({ customersSettings }) => ({
customersTableSize: customersSettings?.tableSize,
})),
)(CustomersTable);

View File

@@ -1,17 +1,12 @@
import React, { useMemo } from 'react';
import {
Menu,
MenuItem,
MenuDivider,
Intent,
} from '@blueprintjs/core';
import { Menu, MenuItem, MenuDivider, Intent } from '@blueprintjs/core';
import clsx from 'classnames';
import intl from 'react-intl-universal';
import { CLASSES } from '../../../common/classes';
import { Icon, Money, If } from 'components';
import { } from 'utils';
import { safeCallback, firstLettersArgs } from 'utils';
import { Icon, Money, If, AvaterCell } from 'components';
import { safeCallback } from 'utils';
/**
* Actions menu.
@@ -69,13 +64,6 @@ export function ActionsMenu({
);
}
/**
* Avatar cell.
*/
export function AvatarCell(row) {
return <span className="avatar">{firstLettersArgs(row.display_name)}</span>;
}
/**
* Phone number accessor.
*/
@@ -99,7 +87,7 @@ export function useCustomersTableColumns() {
{
id: 'avatar',
Header: '',
accessor: AvatarCell,
Cell: AvaterCell,
className: 'avatar',
width: 45,
disableResizing: true,

View File

@@ -11,8 +11,8 @@ export default (mapState) => {
sidebarExpended: state.dashboard.sidebarExpended,
preferencesPageTitle: state.dashboard.preferencesPageTitle,
dashboardBackLink: state.dashboard.backLink,
appIsLoading: state.dashboard.appIsLoading,
appIntlIsLoading: state.dashboard.appIntlIsLoading
splashScreenLoading: state.dashboard.splashScreenLoading > 0,
splashScreenCompleted: state.dashboard.splashScreenLoading === 0,
};
return mapState ? mapState(mapped, state, props) : mapped;
};

View File

@@ -2,9 +2,8 @@ import { connect } from 'react-redux';
import t from 'store/types';
import {
toggleExpendSidebar,
appIsLoading,
appIntlIsLoading
} from 'store/dashboard/dashboard.actions';
import { splashStartLoading, splashStopLoading } from '../../store/dashboard/dashboard.actions';
const mapActionsToProps = (dispatch) => ({
changePageTitle: (pageTitle) =>
@@ -50,15 +49,17 @@ const mapActionsToProps = (dispatch) => ({
dispatch({
type: 'CHANGE_PREFERENCES_PAGE_TITLE',
pageTitle,
}),
}),
setDashboardBackLink: (backLink) =>
dispatch({
type: t.SET_DASHBOARD_BACK_LINK,
payload: { backLink },
}),
setAppIsLoading: (isLoading) => dispatch(appIsLoading(isLoading)),
setAppIntlIsLoading: (isLoading) => dispatch(appIntlIsLoading(isLoading)),
// Splash screen start/stop loading.
splashStartLoading: () => splashStartLoading(),
splashStopLoading: () => splashStopLoading(),
});
export default connect(null, mapActionsToProps);

View File

@@ -21,7 +21,7 @@ export const transformErrors = (errors, { setFieldError }) => {
intl.get('payment_number_is_not_unique'),
);
}
if (getError('INVALID_PAYMENT_AMOUNT')) {
if (getError('INVALID_BILL_PAYMENT_AMOUNT')) {
setFieldError(
'payment_amount',
intl.get('the_payment_amount_bigger_than_invoice_due_amount'),

View File

@@ -16,7 +16,7 @@ import withDialogActions from 'containers/Dialog/withDialogActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { Icon, FormattedMessage as T } from 'components';
import { If, Icon, FormattedMessage as T } from 'components';
import { safeCallback, compose } from 'utils';
@@ -32,7 +32,7 @@ function BillDetailActionsBar({
}) {
const history = useHistory();
const { billId } = useBillDrawerContext();
const { billId, bill } = useBillDrawerContext();
// Handle edit bill.
const onEditBill = () => {
@@ -45,6 +45,11 @@ function BillDetailActionsBar({
openAlert('bill-delete', { billId });
};
// Handle quick bill payment .
const handleQuickBillPayment = () => {
openDialog('quick-payment-made', { billId });
};
return (
<DashboardActionsBar>
<NavbarGroup>
@@ -55,6 +60,15 @@ function BillDetailActionsBar({
onClick={safeCallback(onEditBill)}
/>
<NavbarDivider />
<If condition={bill.is_open && !bill.is_fully_paid}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="quick-payment-16" iconSize={16} />}
text={<T id={'add_payment'} />}
onClick={handleQuickBillPayment}
/>
</If>
<NavbarDivider />
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'trash-16'} iconSize={16} />}

View File

@@ -61,8 +61,8 @@ function CustomerDetailsActionsBar({
const handleDeleteCustomer = () => {
openAlert(`customer-delete`, { contactId: customerId });
closeDrawer('customer-details-drawer');
};
const handleEditContact = () => {
history.push(`/customers/${customerId}/edit`);
closeDrawer('customer-details-drawer');
@@ -108,7 +108,7 @@ function CustomerDetailsActionsBar({
<Button
className={Classes.MINIMAL}
icon={<Icon icon="pen-18" />}
text={intl.get('edit_contact', { name: contact?.contact_service })}
text={intl.get('customer.drawer.action.edit')}
onClick={handleEditContact}
/>
<NavbarDivider />

View File

@@ -19,12 +19,7 @@ function InventoryAdjustmentDetailDrawer({
payload: { inventoryId },
}) {
return (
<Drawer
isOpen={isOpen}
name={name}
style={{ minWidth: '700px', maxWidth: '900px' }}
size={'65%'}
>
<Drawer isOpen={isOpen} name={name} size={'750px'}>
<DrawerSuspense>
<InventoryAdjustmentDrawerContent inventoryId={inventoryId} />
</DrawerSuspense>

View File

@@ -16,7 +16,7 @@ import withDialogActions from 'containers/Dialog/withDialogActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { Icon, FormattedMessage as T } from 'components';
import { If, Icon, FormattedMessage as T } from 'components';
import { compose } from 'utils';
@@ -36,7 +36,7 @@ function InvoiceDetailActionsBar({
const history = useHistory();
// Invoice detail drawer context.
const { invoiceId } = useInvoiceDetailDrawerContext();
const { invoiceId, invoice } = useInvoiceDetailDrawerContext();
// Handle edit sale invoice.
const handleEditInvoice = () => {
@@ -54,6 +54,11 @@ function InvoiceDetailActionsBar({
openDialog('invoice-pdf-preview', { invoiceId });
};
// Handle quick payment invoice.
const handleQuickPaymentInvoice = () => {
openDialog('quick-payment-receive', { invoiceId });
};
return (
<DashboardActionsBar>
<NavbarGroup>
@@ -64,6 +69,15 @@ function InvoiceDetailActionsBar({
onClick={handleEditInvoice}
/>
<NavbarDivider />
<If condition={invoice.is_delivered && !invoice.is_fully_paid}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="quick-payment-16" iconSize={16} />}
text={<T id={'add_payment'} />}
onClick={handleQuickPaymentInvoice}
/>
</If>
<NavbarDivider />
<Button
className={Classes.MINIMAL}
icon={<Icon icon="print-16" />}

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { useManualJournalDrawerContext } from './ManualJournalDrawerProvider';
import { FormatNumber } from '../../../components';
import { T, FormatNumber } from '../../../components';
/**
* Manual journal readonly details footer.
@@ -15,7 +15,9 @@ export default function ManualJournalDrawerFooter() {
<div className="journal-drawer__content-footer">
<div class="total-lines">
<div class="total-lines__line total-lines__line--subtotal">
<div class="title">Subtotal</div>
<div class="title">
<T id={'manual_journal.details.subtotal'} />
</div>
<div class="debit">
<FormatNumber value={amount} />
</div>
@@ -24,7 +26,9 @@ export default function ManualJournalDrawerFooter() {
</div>
</div>
<div class="total-lines__line total-lines__line--total">
<div class="title">TOTAL</div>
<div class="title">
<T id={'manual_journal.details.total'} />
</div>
<div class="debit">{formatted_amount}</div>
<div class="credit">{formatted_amount}</div>
</div>

View File

@@ -43,7 +43,10 @@ export default function ManualJournalDrawerHeader() {
</DetailsMenu>
<div class="journal-drawer__content-description">
<b class="title">Description</b>: {defaultTo(description, '')}
<b class="title">
<T id={'manual_journal.details.description'} />
</b>
: {defaultTo(description, '—')}
</div>
</div>
);

View File

@@ -34,7 +34,7 @@ function PaymentMadeDetailProvider({ paymentMadeId, ...props }) {
} = useTransactionsByReference(
{
reference_id: paymentMadeId,
reference_type: 'paymentMade',
reference_type: 'BillPayment',
},
{ enabled: !!paymentMadeId },
);

View File

@@ -46,7 +46,6 @@ function VendorDetailsActionsBar({
// Handle delete vendor.
const onDeleteContact = () => {
openAlert(`vendor-delete`, { contactId: vendorId });
closeDrawer('vendor-details-drawer');
};
const handleNewInvoiceClick = () => {
@@ -91,7 +90,7 @@ function VendorDetailsActionsBar({
<Button
className={Classes.MINIMAL}
icon={<Icon icon="pen-18" />}
text={<T id={'vendor.drawer.action.edit_vendor'} />}
text={<T id={'vendor.drawer.action.edit'} />}
onClick={safeCallback(onEditContact)}
/>
<NavbarDivider />

View File

@@ -6,7 +6,6 @@ import { Formik, Form } from 'formik';
import classNames from 'classnames';
import { useHistory } from 'react-router-dom';
import { CLASSES } from 'common/classes';
import * as R from 'ramda';
import ExpenseFormHeader from './ExpenseFormHeader';
import ExpenseFormBody from './ExpenseFormBody';
@@ -25,8 +24,13 @@ import {
CreateExpenseFormSchema,
EditExpenseFormSchema,
} from './ExpenseForm.schema';
import { transformErrors, defaultExpense, transformToEditForm } from './utils';
import { compose, orderingLinesIndexes } from 'utils';
import {
transformErrors,
defaultExpense,
transformToEditForm,
transformFormValuesToRequest,
} from './utils';
import { compose } from 'utils';
/**
* Expense form.
@@ -79,15 +83,10 @@ function ExpenseForm({
});
return;
}
// Filter expense entries that has no amount or expense account.
const categories = values.categories.filter(
(category) => category.amount && category.expense_account_id,
);
const form = {
...values,
...transformFormValuesToRequest(values),
publish: submitPayload.publish,
categories: R.compose(orderingLinesIndexes)(categories),
};
// Handle request success.
const handleSuccess = (response) => {

View File

@@ -1,4 +1,5 @@
import { AppToaster } from 'components';
import { Intent } from '@blueprintjs/core';
import moment from 'moment';
import intl from 'react-intl-universal';
import * as R from 'ramda';
@@ -6,11 +7,14 @@ import {
defaultFastFieldShouldUpdate,
transformToForm,
repeatValue,
ensureEntriesHasEmptyLine
ensureEntriesHasEmptyLine,
orderingLinesIndexes
} from 'utils';
const ERROR = {
EXPENSE_ALREADY_PUBLISHED: 'EXPENSE.ALREADY.PUBLISHED',
ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED:
'ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED',
};
// Transform API errors in toasts messages.
@@ -24,6 +28,14 @@ export const transformErrors = (errors, { setErrors }) => {
}),
);
}
if (hasError(ERROR.ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED)) {
setErrors(
AppToaster.show({
intent: Intent.DANGER,
message: 'ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED',
}),
);
}
};
export const MIN_LINES_NUMBER = 4;
@@ -93,3 +105,25 @@ export const accountsFieldShouldUpdate = (newProps, oldProps) => {
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Filter expense entries that has no amount or expense account.
*/
export const filterNonZeroEntries = (categories) => {
return categories.filter(
(category) => category.amount && category.expense_account_id,
);
}
/**
* Transformes the form values to request body.
*/
export const transformFormValuesToRequest = (values) => {
const categories = filterNonZeroEntries(values.categories);
return {
...values,
categories: R.compose(orderingLinesIndexes)(categories),
};
};

View File

@@ -9,16 +9,17 @@ import {
Alignment,
} from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import { FormattedMessage as T } from 'components';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import withDialogActions from 'containers/Dialog/withDialogActions';
import {
If,
DashboardRowsHeightButton,
DashboardActionViewsList,
DashboardFilterButton,
AdvancedFilterPopover,
FormattedMessage as T,
} from 'components';
import { useRefreshExpenses } from 'hooks/query/expenses';
@@ -27,6 +28,9 @@ import { useExpensesListContext } from './ExpensesListProvider';
import withExpensesActions from './withExpensesActions';
import withExpenses from './withExpenses';
import withSettingsActions from '../../Settings/withSettingsActions';
import withSettings from '../../Settings/withSettings';
import { compose } from 'utils';
/**
@@ -38,6 +42,12 @@ function ExpensesActionsBar({
// #withExpenses
expensesFilterConditions,
// #withSettings
expensesTableSize,
// #withSettingsActions
addSetting,
}) {
// History context.
const history = useHistory();
@@ -67,6 +77,11 @@ function ExpensesActionsBar({
const handleRefreshBtnClick = () => {
refresh();
};
// Handle table row size change.
const handleTableRowSizeChange = (size) => {
addSetting('expenses', 'tableSize', size);
};
return (
<DashboardActionsBar>
<NavbarGroup>
@@ -124,6 +139,12 @@ function ExpensesActionsBar({
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
<NavbarDivider />
<DashboardRowsHeightButton
initialValue={expensesTableSize}
onChange={handleTableRowSizeChange}
/>
<NavbarDivider />
</NavbarGroup>
<NavbarGroup align={Alignment.RIGHT}>
<Button
@@ -139,7 +160,11 @@ function ExpensesActionsBar({
export default compose(
withDialogActions,
withExpensesActions,
withSettingsActions,
withExpenses(({ expensesTableState }) => ({
expensesFilterConditions: expensesTableState.filterRoles,
})),
withSettings(({ expenseSettings }) => ({
expensesTableSize: expenseSettings?.tableSize,
})),
)(ExpensesActionsBar);

View File

@@ -16,6 +16,7 @@ import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withExpensesActions from './withExpensesActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import withSettings from '../../Settings/withSettings';
import { ActionsMenu, useExpensesTableColumns } from './components';
@@ -31,6 +32,9 @@ function ExpensesDataTable({
// #withAlertsActions
openAlert,
// #withSettings
expensesTableSize,
}) {
// Expenses list context.
const {
@@ -119,6 +123,7 @@ function ExpensesDataTable({
onCellClick={handleCellClick}
initialColumnsWidths={initialColumnsWidths}
onColumnResizing={handleColumnResizing}
size={expensesTableSize}
payload={{
onPublish: handlePublishExpense,
onDelete: handleDeleteExpense,
@@ -135,4 +140,7 @@ export default compose(
withAlertsActions,
withDrawerActions,
withExpensesActions,
withSettings(({ expenseSettings }) => ({
expensesTableSize: expenseSettings?.tableSize,
})),
)(ExpensesDataTable);

View File

@@ -31,7 +31,7 @@ function BalanceSheet({
toDate: moment().endOf('year').format('YYYY-MM-DD'),
basis: 'cash',
displayColumnsType: 'total',
accountsFilter: 'without-zero-balance',
filterByOption: 'without-zero-balance',
});
// Handle re-fetch balance sheet after filter change.

View File

@@ -2,7 +2,7 @@ import React from 'react';
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
import SelectDisplayColumnsBy from '../SelectDisplayColumnsBy';
import RadiosAccountingBasis from '../RadiosAccountingBasis';
import FinancialAccountsFilter from '../FinancialAccountsFilter';
import FinancialStatementsFilter from '../FinancialStatementsFilter';
/**
* Balance sheet header - General panal.
@@ -12,7 +12,7 @@ export default function BalanceSheetHeaderGeneralTab({}) {
<div>
<FinancialStatementDateRange />
<SelectDisplayColumnsBy />
<FinancialAccountsFilter
<FinancialStatementsFilter
initialSelectedItem={'all-accounts'}
/>
<RadiosAccountingBasis key={'basis'} />

View File

@@ -5,7 +5,7 @@ import moment from 'moment';
export const getBalanceSheetHeaderDefaultValues = () => {
return {
basic: 'cash',
accountsFilter: 'without-zero-balance',
filterByOption: 'without-zero-balance',
displayColumnsType: 'total',
fromDate: moment().toDate(),
toDate: moment().toDate(),
@@ -20,6 +20,6 @@ export const getBalanceSheetHeaderValidationSchema = () =>
.min(Yup.ref('fromDate'))
.required()
.label(intl.get('toDate')),
accountsFilter: Yup.string(),
filterByOption: Yup.string(),
displayColumnsType: Yup.string(),
});

View File

@@ -34,6 +34,7 @@ function CashFlowStatement({
toDate: moment().endOf('year').format('YYYY-MM-DD'),
basis: 'cash',
displayColumnsType: 'total',
filterByOption: 'with-transactions',
});
// Handle refetch cash flow after filter change.

View File

@@ -1,6 +1,6 @@
import React from 'react';
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
import FinancialAccountsFilter from '../FinancialAccountsFilter';
import FinancialStatementsFilter from '../FinancialStatementsFilter';
import RadiosAccountingBasis from '../RadiosAccountingBasis';
import SelectDisplayColumnsBy from '../SelectDisplayColumnsBy';
@@ -13,7 +13,7 @@ export default function CashFlowStatementHeaderGeneralPanel() {
<div>
<FinancialStatementDateRange />
<SelectDisplayColumnsBy />
<FinancialAccountsFilter initialSelectedItem={'all-accounts'} />
<FinancialStatementsFilter initialSelectedItem={'with-transactions'} />
<RadiosAccountingBasis key={'basis'} />
</div>
);

View File

@@ -29,6 +29,7 @@ function CustomersBalanceSummary({
}) {
const [filter, setFilter] = useState({
asDate: moment().endOf('day').format('YYYY-MM-DD'),
filterByOption: 'with-transactions',
});
// Handle re-fetch customers balance summary after filter change.

View File

@@ -5,6 +5,8 @@ import { Classes, FormGroup, Position, Checkbox } from '@blueprintjs/core';
import { ContactsMultiSelect, FormattedMessage as T } from 'components';
import classNames from 'classnames';
import { Row, Col, FieldHint } from 'components';
import { filterCustomersOptions } from '../constants';
import {
momentFormatter,
tansformDateValue,
@@ -12,6 +14,7 @@ import {
handleDateChange,
} from 'utils';
import { useCustomersBalanceSummaryGeneralContext } from './CustomersBalanceSummaryGeneralProvider';
import FinancialStatementsFilter from '../FinancialStatementsFilter';
/**
* Customers balance header - General panel - Content
@@ -65,6 +68,12 @@ export default function CustomersBalanceSummaryGeneralPanelContent() {
</Col>
</Row>
<FinancialStatementsFilter
items={filterCustomersOptions}
label={<T id={'customers.label_filter_customers'} />}
initialSelectedItem={'with-transactions'}
/>
<Row>
<Col xs={5}>
<Field name={'customersIds'}>
@@ -80,7 +89,7 @@ export default function CustomersBalanceSummaryGeneralPanelContent() {
<ContactsMultiSelect
items={customers}
onItemSelect={(contacts) => {
const customersIds = contacts.map(contact => contact.id);
const customersIds = contacts.map((contact) => contact.id);
setFieldValue('customersIds', customersIds);
}}
/>

View File

@@ -26,7 +26,6 @@ function CustomersBalanceSummaryHeader({
// #withCustomersBalanceSummaryActions
toggleCustomerBalanceFilterDrawer,
}) {
// validation schema.
const validationSchema = Yup.object().shape({
asDate: Yup.date().required().label('asDate'),
@@ -34,15 +33,20 @@ function CustomersBalanceSummaryHeader({
// Default form values.
const defaultValues = {
...pageFilter,
asDate: moment().toDate(),
customersIds: [],
};
// Filter form initial values.
const initialValues = transformToForm({
...pageFilter,
asDate: moment(pageFilter.asDate).toDate(),
}, defaultValues);
const initialValues = transformToForm(
{
...defaultValues,
...pageFilter,
asDate: moment(pageFilter.asDate).toDate(),
},
defaultValues,
);
// handle form submit.
const handleSubmit = (values, { setSubmitting }) => {

View File

@@ -30,6 +30,7 @@ function CustomersTransactions({
const [filter, setFilter] = useState({
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'),
filterByOption: 'with-transactions',
});
const handleFilterSubmit = (filter) => {

View File

@@ -30,22 +30,25 @@ function CustomersTransactionsHeader({
}) {
// Default form values.
const defaultValues = {
...pageFilter,
fromDate: moment().toDate(),
toDate: moment().toDate(),
customersIds: [],
};
// Initial form values.
const initialValues = transformToForm({
...pageFilter,
fromDate: moment(pageFilter.fromDate).toDate(),
toDate: moment(pageFilter.toDate).toDate(),
}, defaultValues);
const initialValues = transformToForm(
{
...defaultValues,
...pageFilter,
fromDate: moment(pageFilter.fromDate).toDate(),
toDate: moment(pageFilter.toDate).toDate(),
},
defaultValues,
);
// Validation schema.
const validationSchema = Yup.object().shape({
fromDate: Yup.date()
.required()
.label(intl.get('fromDate')),
fromDate: Yup.date().required().label(intl.get('fromDate')),
toDate: Yup.date()
.min(Yup.ref('fromDate'))
.required()
@@ -59,7 +62,9 @@ function CustomersTransactionsHeader({
setSubmitting(false);
};
// Handle drawer close action.
const handleDrawerClose = () => { toggleFilterDrawer(false); };
const handleDrawerClose = () => {
toggleFilterDrawer(false);
};
return (
<FinancialStatementHeader

View File

@@ -3,12 +3,15 @@ import classNames from 'classnames';
import { Field } from 'formik';
import { Classes, FormGroup } from '@blueprintjs/core';
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
import FinancialStatementsFilter from '../FinancialStatementsFilter';
import {
Row,
Col,
ContactsMultiSelect,
FormattedMessage as T,
} from '../../../components';
import { filterCustomersOptions } from '../constants';
import {
CustomersTransactionsGeneralPanelProvider,
useCustomersTransactionsGeneralPanelContext,
@@ -34,7 +37,11 @@ function CustomersTransactionsHeaderGeneralPanelContent() {
return (
<div>
<FinancialStatementDateRange />
<FinancialStatementsFilter
items={filterCustomersOptions}
label={<T id={'customers.label_filter_customers'} />}
initialSelectedItem={'with-transactions'}
/>
<Row>
<Col xs={5}>
<Field name={'customersIds'}>

View File

@@ -6,7 +6,8 @@ import { Row, Col, Hint } from 'components';
import { momentFormatter, parseDateRangeQuery } from 'utils';
import { DateInput } from '@blueprintjs/datetime';
import intl from 'react-intl-universal';
import { dateRangeOptions } from 'containers/FinancialStatements/common';
import { dateRangeOptions } from './constants';
/**
* Financial statement - Date range select.

View File

@@ -12,17 +12,21 @@ import { FastField } from 'formik';
import { CLASSES } from 'common/classes';
import { Col, Row, ListSelect, MODIFIER } from 'components';
import { filterAccountsOptions } from './common';
import { filterAccountsOptions } from './constants';
export default function FinancialAccountsFilter({ ...restProps }) {
export default function FinancialStatementsFilter({
items = filterAccountsOptions,
label = <T id={'filter_accounts'} />,
...restProps
}) {
const SUBMENU_POPOVER_MODIFIERS = {
flip: { boundariesElement: 'viewport', padding: 20 },
offset: { offset: '0, 10' },
preventOverflow: { boundariesElement: 'viewport', padding: 40 },
};
const filterAccountRenderer = (item, { handleClick, modifiers, query }) => {
const filterRenderer = (item, { handleClick, modifiers, query }) => {
return (
<Tooltip
interactionKind={PopoverInteractionKind.HOVER}
@@ -41,23 +45,23 @@ export default function FinancialAccountsFilter({ ...restProps }) {
return (
<Row>
<Col xs={4}>
<FastField name={'accountsFilter'}>
<FastField name={'filterByOption'}>
{({ form: { setFieldValue }, field: { value } }) => (
<FormGroup
label={<T id={'filter_accounts'} />}
label={label}
className="form-group--select-list bp3-fill"
inline={false}
>
<ListSelect
items={filterAccountsOptions}
itemRenderer={filterAccountRenderer}
items={items}
itemRenderer={filterRenderer}
popoverProps={{ minimal: true }}
filterable={false}
selectedItem={value}
selectedItemProp={'key'}
textProp={'name'}
onItemSelect={(item) => {
setFieldValue('accountsFilter', item.key);
setFieldValue('filterByOption', item.key);
}}
className={classNames(CLASSES.SELECT_LIST_FILL_POPOVER)}
{...restProps}

View File

@@ -34,7 +34,7 @@ function GeneralLedger({
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'),
basis: 'accural',
accountsFilter: 'with-transactions',
filterByOption: 'with-transactions',
});
// Handle financial statement filter change.

View File

@@ -7,7 +7,7 @@ import { AccountsMultiSelect, Row, Col } from 'components';
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
import RadiosAccountingBasis from '../RadiosAccountingBasis';
import FinancialAccountsFilter from '../FinancialAccountsFilter';
import FinancialStatementsFilter from '../FinancialStatementsFilter';
import { GLHeaderGeneralPanelProvider } from './GLHeaderGeneralPaneProvider';
import { filterAccountsOptions } from './common';
@@ -33,9 +33,9 @@ function GLHeaderGeneralPaneContent() {
return (
<React.Fragment>
<FinancialStatementDateRange />
<FinancialAccountsFilter
<FinancialStatementsFilter
items={filterAccountsOptions}
initialSelectedItem={'all-accounts'}
initialSelectedItem={'with-transactions'}
/>
<Row>
<Col xs={4}>

View File

@@ -27,6 +27,7 @@ function InventoryValuation({
}) {
const [filter, setFilter] = useState({
asDate: moment().endOf('day').format('YYYY-MM-DD'),
filterByOption: 'with-transactions',
});
// Handle filter form submit.
@@ -54,7 +55,6 @@ function InventoryValuation({
[toggleInventoryValuationFilterDrawer],
);
return (
<InventoryValuationProvider query={filter}>
<InventoryValuationActionsBar

View File

@@ -33,12 +33,14 @@ function InventoryValuationHeader({
// Default values.
const defaultValues = {
...pageFilter,
asDate: moment().toDate(),
itemsIds: [],
};
// Initial values.
const initialValues = transformToForm({
...pageFilter,
...defaultValues,
asDate: moment(pageFilter.asDate).toDate(),
}, defaultValues);

View File

@@ -11,6 +11,8 @@ import {
Col,
FieldHint,
} from '../../../components';
import { filterInventoryValuationOptions } from '../constants';
import {
momentFormatter,
tansformDateValue,
@@ -21,6 +23,7 @@ import {
InventoryValuationGeneralPanelProvider,
useInventoryValuationGeneralPanelContext,
} from './InventoryValuationHeaderGeneralPanelProvider';
import FinancialStatementsFilter from '../FinancialStatementsFilter';
/**
* Inventory valuation - Drawer Header - General panel.
@@ -42,7 +45,7 @@ function InventoryValuationHeaderGeneralPanelContent() {
return (
<div>
<Row>
<Col xs={5}>
<Col xs={4}>
<FastField name={'asDate'}>
{({ form, field: { value }, meta: { error } }) => (
<FormGroup
@@ -66,9 +69,14 @@ function InventoryValuationHeaderGeneralPanelContent() {
</FastField>
</Col>
</Row>
<FinancialStatementsFilter
items={filterInventoryValuationOptions}
label={<T id={'items.label_filter_items'} />}
initialSelectedItem={'all-items'}
/>
<Row>
<Col xs={5}>
<Col xs={4}>
<Field name={'itemsIds'}>
{({ form: { setFieldValue } }) => (
<FormGroup

View File

@@ -14,10 +14,7 @@ import withCurrentOrganization from '../../Organization/withCurrentOrganization'
import 'style/pages/FinancialStatements/ProfitLossSheet.scss';
import { ProfitLossSheetProvider } from './ProfitLossProvider';
import {
ProfitLossSheetLoadingBar,
ProfitLossSheetAlerts
} from './components';
import { ProfitLossSheetLoadingBar, ProfitLossSheetAlerts } from './components';
/**
* Profit/Loss financial statement sheet.
@@ -34,7 +31,7 @@ function ProfitLossSheet({
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'),
displayColumnsType: 'total',
accountsFilter: 'all-accounts',
filterByOption: 'with-transactions',
});
// Handle submit filter.

View File

@@ -25,18 +25,14 @@ function ProfitLossHeader({
// #withProfitLossActions
toggleProfitLossFilterDrawer: toggleFilterDrawer,
}) {
// Validation schema.
const validationSchema = Yup.object().shape({
fromDate: Yup.date()
.required()
.label(intl.get('from_date')),
fromDate: Yup.date().required().label(intl.get('from_date')),
toDate: Yup.date()
.min(Yup.ref('fromDate'))
.required()
.label(intl.get('to_date')),
accountsFilter: Yup.string(),
filterByOption: Yup.string(),
displayColumnsType: Yup.string(),
});

View File

@@ -3,7 +3,7 @@ import React from 'react';
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
import SelectDisplayColumnsBy from '../SelectDisplayColumnsBy';
import RadiosAccountingBasis from '../RadiosAccountingBasis';
import FinancialAccountsFilter from '../FinancialAccountsFilter';
import FinancialStatementsFilter from '../FinancialStatementsFilter';
/**
* Profit/Loss sheet - Drawer header - General panel.
@@ -13,7 +13,7 @@ export default function ProfitLossSheetHeaderGeneralPane({}) {
<div>
<FinancialStatementDateRange />
<SelectDisplayColumnsBy />
<FinancialAccountsFilter initialSelectedItem={'all-accounts'} />
<FinancialStatementsFilter initialSelectedItem={'with-transactions'} />
<RadiosAccountingBasis key={'basis'} />
</div>
);

View File

@@ -27,6 +27,7 @@ function PurchasesByItems({
const [filter, setFilter] = useState({
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'),
filterByOption: 'with-transactions',
});
// Handle filter form submit.

View File

@@ -9,6 +9,8 @@ import {
} from '../../../components';
import classNames from 'classnames';
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
import FinancialStatementsFilter from '../FinancialStatementsFilter';
import { filterItemsOptions } from '../constants';
import {
PurchasesByItemsGeneralPanelProvider,
@@ -35,6 +37,11 @@ function PurchasesByItemsGeneralPanelContent() {
return (
<div>
<FinancialStatementDateRange />
<FinancialStatementsFilter
items={filterItemsOptions}
label={<T id={'items.label_filter_items'} />}
initialSelectedItem={'with-transactions'}
/>
<Row>
<Col xs={4}>

View File

@@ -47,6 +47,7 @@ function PurchasesByItemsHeader({
// Initial form values.
const initialValues = transformToForm(
{
...defaultValues,
...pageFilter,
fromDate: moment(pageFilter.fromDate).toDate(),
toDate: moment(pageFilter.toDate).toDate(),

View File

@@ -1,7 +1,6 @@
import React, { useEffect, useState, useCallback } from 'react';
import moment from 'moment';
import 'style/pages/FinancialStatements/SalesAndPurchasesSheet.scss';
import { SalesByItemProvider } from './SalesByItemProvider';
@@ -30,6 +29,7 @@ function SalesByItems({
const [filter, setFilter] = useState({
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'),
filterByOption: 'with-transactions',
});
// Handle filter form submit.

View File

@@ -2,10 +2,11 @@ import React from 'react';
import { FormGroup, Classes } from '@blueprintjs/core';
import { Field } from 'formik';
import classNames from 'classnames';
import { get } from 'lodash';
import { filterItemsOptions } from '../constants';
import { Row, Col, ItemsMultiSelect, FormattedMessage as T } from 'components';
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
import FinancialStatementsFilter from '../FinancialStatementsFilter';
import {
SalesByItemGeneralPanelProvider,
useSalesByItemsGeneralPanelContext,
@@ -32,6 +33,12 @@ function SalesByItemsHeaderGeneralPanelContent() {
<div>
<FinancialStatementDateRange />
<FinancialStatementsFilter
items={filterItemsOptions}
label={<T id={'items.label_filter_items'} />}
initialSelectedItem={'with-transactions'}
/>
<Row>
<Col xs={4}>
<Field name={'itemsIds'}>

View File

@@ -3,7 +3,7 @@ import { FormGroup } from '@blueprintjs/core';
import { FastField } from 'formik';
import { FormattedMessage as T } from 'components';
import { Row, Col, ListSelect } from 'components';
import { displayColumnsByOptions } from 'containers/FinancialStatements/common';
import { displayColumnsByOptions } from './constants';
/**
* Financial statement - Display columns by and type select.

View File

@@ -33,7 +33,7 @@ function TrialBalanceSheet({
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'),
basis: 'accural',
accountsFilter: 'all-accounts',
filterByOption: 'with-transactions',
});
// Handle filter form submit.

View File

@@ -1,7 +1,7 @@
import React from 'react';
import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
import RadiosAccountingBasis from '../RadiosAccountingBasis';
import FinancialAccountsFilter from '../FinancialAccountsFilter';
import FinancialStatementsFilter from '../FinancialStatementsFilter';
/**
* Trial balance sheet - Drawer header - General panel.
@@ -12,7 +12,7 @@ export default function TrialBalanceSheetHeaderGeneralPanel({
return (
<div>
<FinancialStatementDateRange />
<FinancialAccountsFilter initialSelectedItem={'all-accounts'} />
<FinancialStatementsFilter initialSelectedItem={'with-transactions'} />
<RadiosAccountingBasis />
</div>
);

View File

@@ -30,6 +30,7 @@ function VendorsBalanceSummary({
}) {
const [filter, setFilter] = useState({
asDate: moment().endOf('day').format('YYYY-MM-DD'),
filterByOption: 'with-transactions',
});
// Handle refetch vendors balance summary.

View File

@@ -33,14 +33,20 @@ function VendorsBalanceSummaryHeader({
// filter form initial values.
const defaultValues = {
...pageFilter,
asDate: moment().toDate(),
vendorsIds: [],
};
// Initial form values.
const initialValues = transformToForm({
...pageFilter,
asDate: moment(pageFilter.asDate).toDate(),
}, defaultValues);
const initialValues = transformToForm(
{
...defaultValues,
...pageFilter,
asDate: moment(pageFilter.asDate).toDate(),
},
defaultValues,
);
// handle form submit.
const handleSubmit = (values, { setSubmitting }) => {

View File

@@ -11,6 +11,8 @@ import {
FieldHint,
FormattedMessage as T,
} from '../../../components';
import { filterVendorsOptions } from '../constants';
import {
momentFormatter,
tansformDateValue,
@@ -18,6 +20,7 @@ import {
handleDateChange,
} from 'utils';
import { useVendorsBalanceSummaryGeneralPanelContext } from './VendorsBalanceSummaryHeaderGeneralProvider';
import FinancialStatementsFilter from '../FinancialStatementsFilter';
/**
* Vendors balance header - General panel - Content.
@@ -71,6 +74,12 @@ export default function VendorsBalanceSummaryHeaderGeneralContent() {
</Col>
</Row>
<FinancialStatementsFilter
items={filterVendorsOptions}
label={<T id={'vendors.label_filter_vendors'} />}
initialSelectedItem={'with-transactions'}
/>
<Row>
<Col xs={4}>
<Field name={'vendorsIds'}>

View File

@@ -31,6 +31,7 @@ function VendorsTransactions({
const [filter, setFilter] = useState({
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
toDate: moment().endOf('year').format('YYYY-MM-DD'),
filterByOption: 'with-transactions',
});
const handleFilterSubmit = (filter) => {

View File

@@ -31,23 +31,26 @@ function VendorsTransactionsHeader({
}) {
// Default form values.
const defaultValues = {
...pageFilter,
fromDate: moment().toDate(),
toDate: moment().toDate(),
vendorsIds: [],
};
// Initial form values.
const initialValues = transformToForm({
...pageFilter,
fromDate: moment(pageFilter.fromDate).toDate(),
toDate: moment(pageFilter.toDate).toDate(),
}, defaultValues);
const initialValues = transformToForm(
{
...defaultValues,
...pageFilter,
fromDate: moment(pageFilter.fromDate).toDate(),
toDate: moment(pageFilter.toDate).toDate(),
},
defaultValues,
);
// Validation schema.
const validationSchema = Yup.object().shape({
fromDate: Yup.date()
.required()
.label(intl.get('fromDate')),
fromDate: Yup.date().required().label(intl.get('fromDate')),
toDate: Yup.date()
.min(Yup.ref('fromDate'))
.required()
@@ -62,7 +65,9 @@ function VendorsTransactionsHeader({
};
// Handle drawer close action.
const handleDrawerClose = () => { toggleFilterDrawer(false); };
const handleDrawerClose = () => {
toggleFilterDrawer(false);
};
return (
<FinancialStatementHeader

View File

@@ -4,12 +4,16 @@ import classNames from 'classnames';
import { Classes, FormGroup } from '@blueprintjs/core';
import FinancialStatementDateRange from '../FinancialStatementDateRange';
import FinancialStatementsFilter from '../FinancialStatementsFilter';
import {
Row,
Col,
ContactsMultiSelect,
FormattedMessage as T,
} from '../../../components';
import { filterVendorsOptions } from '../constants';
import {
VendorsTransactionsGeneralPanelProvider,
useVendorsTransactionsGeneralPanelContext,
@@ -35,7 +39,11 @@ function VendorsTransactionsHeaderGeneralPanelContent() {
return (
<div>
<FinancialStatementDateRange />
<FinancialStatementsFilter
items={filterVendorsOptions}
label={<T id={'vendors.label_filter_vendors'} />}
initialSelectedItem={'all-vendors'}
/>
<Row>
<Col xs={5}>
<Field name={'vendorsIds'}>

View File

@@ -1,72 +1,9 @@
import * as R from 'ramda';
import intl from 'react-intl-universal';
import { displayColumnsByOptions } from './constants';
import { transfromToSnakeCase, flatten } from 'utils';
export const displayColumnsByOptions = [
{ key: 'total', name: intl.get('total'), type: 'total', by: '' },
{
key: 'year',
name: intl.get('date_year'),
type: 'date_periods',
by: 'year',
},
{
key: 'month',
name: intl.get('date_month'),
type: 'date_periods',
by: 'month',
},
{
key: 'week',
name: intl.get('date_week'),
type: 'date_periods',
by: 'month',
},
{
key: 'day',
name: intl.get('date_day'),
type: 'date_periods',
by: 'day',
},
{
key: 'quarter',
name: intl.get('date_quarter'),
type: 'date_periods',
by: 'quarter',
},
];
export const dateRangeOptions = [
{ value: 'today', label: intl.get('today') },
{ value: 'this_week', label: intl.get('this_week') },
{ value: 'this_month', label: intl.get('this_month') },
{ value: 'this_quarter', label: intl.get('this_quarter') },
{ value: 'this_year', label: intl.get('this_year') },
{ value: 'custom', label: intl.get('custom_range') },
];
export const filterAccountsOptions = [
{
key: 'all-accounts',
name: intl.get('all_accounts'),
hint: intl.get('all_accounts_including_with_zero_balance'),
},
{
key: 'without-zero-balance',
name: intl.get('accounts_without_zero_balance'),
hint: intl.get('include_accounts_and_exclude_zero_balance'),
},
{
key: 'with-transactions',
name: intl.get('accounts_with_transactions'),
hint: intl.get(
'include_accounts_once_has_transactions_on_given_date_period',
),
},
];
/**
* Associate display columns by and type properties to query object.
* Associate display columns by and type properties to query object.
*/
export const transformDisplayColumnsType = (form) => {
const columnType = displayColumnsByOptions.find(
@@ -85,17 +22,15 @@ export const transformDisplayColumnsType = (form) => {
const setNoneZeroTransactions = (form) => {
return {
...form,
noneZero: form.accountsFilter === 'without-zero-balance',
noneTransactions: form.accountsFilter === 'with-transactions',
noneZero: form.filterByOption === 'without-zero-balance',
noneTransactions: form.filterByOption === 'with-transactions',
onlyActive: form.filterByOption === 'with-only-active',
};
}
};
// filterByOption
export const transformAccountsFilter = (form) => {
return R.compose(
R.omit(['accountsFilter']),
setNoneZeroTransactions,
)(form)
}
return R.compose(R.omit(['filterByOption']), setNoneZeroTransactions)(form);
};
/**
* Transform filter form to http query.

View File

@@ -0,0 +1,141 @@
import intl from 'react-intl-universal';
export const displayColumnsByOptions = [
{ key: 'total', name: intl.get('total'), type: 'total', by: '' },
{
key: 'year',
name: intl.get('date_year'),
type: 'date_periods',
by: 'year',
},
{
key: 'month',
name: intl.get('date_month'),
type: 'date_periods',
by: 'month',
},
{
key: 'week',
name: intl.get('date_week'),
type: 'date_periods',
by: 'month',
},
{
key: 'day',
name: intl.get('date_day'),
type: 'date_periods',
by: 'day',
},
{
key: 'quarter',
name: intl.get('date_quarter'),
type: 'date_periods',
by: 'quarter',
},
];
export const dateRangeOptions = [
{ value: 'today', label: intl.get('today') },
{ value: 'this_week', label: intl.get('this_week') },
{ value: 'this_month', label: intl.get('this_month') },
{ value: 'this_quarter', label: intl.get('this_quarter') },
{ value: 'this_year', label: intl.get('this_year') },
{ value: 'custom', label: intl.get('custom_range') },
];
export const filterAccountsOptions = [
{
key: 'all-accounts',
name: intl.get('all_accounts'),
hint: intl.get('all_accounts_including_with_zero_balance'),
},
{
key: 'without-zero-balance',
name: intl.get('accounts_without_zero_balance'),
hint: intl.get('include_accounts_and_exclude_zero_balance'),
},
{
key: 'with-transactions',
name: intl.get('accounts_with_transactions'),
hint: intl.get(
'include_accounts_once_has_transactions_on_given_date_period',
),
},
];
export const filterItemsOptions = [
{
key: 'all-items',
name: intl.get('all_items'),
hint: intl.get('items.option_all_items.hint'),
},
{
key: 'with-transactions',
name: intl.get('items.option_with_transactions'),
hint: intl.get('items.option_with_transactions.hint'),
},
{
key: 'with-only-active',
name: intl.get('items.option.only_active'),
},
];
export const filterCustomersOptions = [
{
key: 'all-customers',
name: intl.get('all_customers'),
hint: intl.get('customers.option_all_customers.hint'),
},
{
key: 'without-zero-balance',
name: intl.get('customers.option_without_zero_balance'),
hint: intl.get('customers.option_without_zero_balance.hint'),
},
{
key: 'with-transactions',
name: intl.get('customers.option_with_transactions'),
hint: intl.get('customers.option_with_transactions.hint'),
},
];
export const filterVendorsOptions = [
{
key: 'all-vendors',
name: intl.get('all_vendors'),
hint: intl.get('vendors.option_all_vendors.hint'),
},
{
key: 'without-zero-balance',
name: intl.get('vendors.option_without_zero_balance'),
hint: intl.get('vendors.option_without_zero_balance.hint'),
},
{
key: 'with-transactions',
name: intl.get('vendors.option_with_transactions'),
hint: intl.get('vendors.option_with_transactions.hint'),
},
];
export const filterInventoryValuationOptions = [
{
key: 'all-items',
name: intl.get('all_items'),
hint: intl.get('items.option_all_items.hint'),
},
{
key: 'with-transactions',
name: intl.get('items.option_with_transactions'),
hint: intl.get('items.option_with_transactions.hint'),
},
{
key: 'without-zero-balance',
name: intl.get('items.option_without_zero_balance'),
hint: intl.get('items.option_without_zero_balance.hint'),
},
{
key: 'with-only-active',
name: intl.get('items.option.only_active'),
},
]

View File

@@ -9,7 +9,7 @@ import {
Switch,
Alignment,
} from '@blueprintjs/core';
import { FormattedMessage as T } from 'components';
import { DashboardRowsHeightButton, FormattedMessage as T } from 'components';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import Icon from 'components/Icon';
import {
@@ -25,8 +25,10 @@ import { useRefreshItems } from 'hooks/query/items';
import withItems from 'containers/Items/withItems';
import withItemsActions from './withItemsActions';
import withAlertActions from 'containers/Alert/withAlertActions';
import withSettings from '../Settings/withSettings';
import { compose } from 'utils';
import withSettingsActions from '../Settings/withSettingsActions';
/**
* Items actions bar.
@@ -42,6 +44,12 @@ function ItemsActionsBar({
// #withAlertActions
openAlert,
// #withSettings
itemsTableSize,
// #withSettingsActions
addSetting,
}) {
// Items list context.
const { itemsViews, fields } = useItemsListContext();
@@ -72,10 +80,14 @@ function ItemsActionsBar({
const checked = event.target.checked;
setItemsTableState({ inactiveMode: checked });
};
// Handle refresh button click.
const handleRefreshBtnClick = () => {
refresh();
};
// Handle table row size change.
const handleTableRowSizeChange = (size) => {
addSetting('items', 'tableSize', size);
};
return (
<DashboardActionsBar>
@@ -131,6 +143,12 @@ function ItemsActionsBar({
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
<NavbarDivider />
<DashboardRowsHeightButton
initialValue={itemsTableSize}
onChange={handleTableRowSizeChange}
/>
<NavbarDivider />
<Switch
labelElement={<T id={'inactive'} />}
defaultChecked={itemsInactiveMode}
@@ -150,11 +168,15 @@ function ItemsActionsBar({
}
export default compose(
withSettingsActions,
withItems(({ itemsSelectedRows, itemsTableState }) => ({
itemsSelectedRows,
itemsInactiveMode: itemsTableState.inactiveMode,
itemsFilterRoles: itemsTableState.filterRoles,
})),
withSettings(({ itemsSettings }) => ({
itemsTableSize: itemsSettings.tableSize,
})),
withItemsActions,
withAlertActions,
)(ItemsActionsBar);

View File

@@ -10,11 +10,11 @@ import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
import { TABLES } from 'common/tables';
import withItems from 'containers/Items/withItems';
import withItemsActions from 'containers/Items/withItemsActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import withSettings from '../Settings/withSettings';
import { useItemsListContext } from './ItemsListProvider';
import { useItemsTableColumns, ItemsActionMenuList } from './components';
@@ -37,8 +37,8 @@ function ItemsDataTable({
// #withDrawerActions
openDrawer,
// #withItems
itemsTableState,
// #withSettings
itemsTableSize,
// #ownProps
tableProps,
@@ -146,6 +146,7 @@ function ItemsDataTable({
onCellClick={handleCellClick}
initialColumnsWidths={initialColumnsWidths}
onColumnResizing={handleColumnResizing}
size={itemsTableSize}
payload={{
onDeleteItem: handleDeleteItem,
onEditItem: handleEditItem,
@@ -167,5 +168,7 @@ export default compose(
withAlertsActions,
withDrawerActions,
withDialogActions,
withItems(({ itemsTableState }) => ({ itemsTableState })),
withSettings(({ itemsSettings }) => ({
itemsTableSize: itemsSettings.tableSize,
})),
)(ItemsDataTable);

View File

@@ -34,7 +34,7 @@ const transfromItemsToSearch = (item) => ({
id: item.id,
text: item.name,
subText: item.code,
label: item.type,
label: item.type_formatted,
reference: item,
});

View File

@@ -50,7 +50,7 @@ function ItemsCategoryActionsBar({
});
};
console.log(fields, categoriesFilterConditions, 'XXXX');
return (
<DashboardActionsBar>

View File

@@ -2,7 +2,6 @@ import React, { useMemo } from 'react';
import { Formik, Form } from 'formik';
import { Intent } from '@blueprintjs/core';
import classNames from 'classnames';
import * as R from 'ramda';
import intl from 'react-intl-universal';
import { useHistory } from 'react-router-dom';
import { isEmpty } from 'lodash';
@@ -16,13 +15,14 @@ import BillItemsEntriesEditor from './BillItemsEntriesEditor';
import { AppToaster } from 'components';
import { ERROR } from 'common/errors';
import { useBillFormContext } from './BillFormProvider';
import { compose, safeSumBy } from 'utils';
import {
defaultBill,
filterNonZeroEntries,
transformToEditForm,
transformEntriesToSubmit,
transformFormValuesToRequest,
handleErrors,
} from './utils';
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
@@ -55,23 +55,12 @@ function BillForm({
[bill, base_currency],
);
// Transform response error to fields.
const handleErrors = (errors, { setErrors }) => {
if (errors.some((e) => e.type === ERROR.BILL_NUMBER_EXISTS)) {
setErrors({
bill_number: intl.get('bill_number_exists'),
});
}
};
// Handles form submit.
const handleFormSubmit = (
values,
{ setSubmitting, setErrors, resetForm },
) => {
const entries = values.entries.filter(
(item) => item.item_id && item.quantity,
);
const entries = filterNonZeroEntries(values.entries);
const totalQuantity = safeSumBy(entries, 'quantity');
if (totalQuantity === 0) {
@@ -83,9 +72,8 @@ function BillForm({
return;
}
const form = {
...values,
...transformFormValuesToRequest(values),
open: submitPayload.status,
entries: transformEntriesToSubmit(entries),
};
// Handle the request success.
const onSuccess = (response) => {

View File

@@ -41,6 +41,12 @@ export const defaultBill = {
entries: [...repeatValue(defaultBillEntry, MIN_LINES_NUMBER)],
};
export const ERRORS = {
// Bills
BILL_NUMBER_EXISTS: 'BILL.NUMBER.EXISTS',
ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED:
'ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED',
};
/**
* Transformes the bill to initial values of edit form.
*/
@@ -70,11 +76,33 @@ export const transformToEditForm = (bill) => {
* Transformes bill entries to submit request.
*/
export const transformEntriesToSubmit = (entries) => {
const transformBillEntry = R.curry(transformToForm)(R.__, defaultBillEntry);
const transformBillEntry = R.compose(
R.omit(['amount']),
R.curry(transformToForm)(R.__, defaultBillEntry),
);
return R.compose(orderingLinesIndexes, R.map(transformBillEntry))(entries);
};
/**
* Filters the givne non-zero entries.
*/
export const filterNonZeroEntries = (entries) => {
return entries.filter((item) => item.item_id && item.quantity);
};
/**
* Transformes form values to request body.
*/
export const transformFormValuesToRequest = (values) => {
const entries = filterNonZeroEntries(values.entries);
return {
...values,
entries: transformEntriesToSubmit(entries),
open: false,
};
};
/**
* Handle delete errors.
*/
@@ -118,3 +146,24 @@ export const entriesFieldShouldUpdate = (newProps, oldProps) => {
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
// Transform response error to fields.
export const handleErrors = (errors, { setErrors }) => {
if (errors.some((e) => e.type === ERRORS.BILL_NUMBER_EXISTS)) {
setErrors({
bill_number: intl.get('bill_number_exists'),
});
}
if (
errors.some(
(e) => e.type === ERRORS.ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED,
)
) {
setErrors(
AppToaster.show({
intent: Intent.DANGER,
message: 'ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED',
}),
);
}
};

Some files were not shown because too many files have changed in this diff Show More