mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 15:20:34 +00:00
feat: universal search.
This commit is contained in:
@@ -74,6 +74,8 @@ const CLASSES = {
|
|||||||
UNIVERSAL_SEARCH_OVERLAY: 'universal-search-overlay',
|
UNIVERSAL_SEARCH_OVERLAY: 'universal-search-overlay',
|
||||||
UNIVERSAL_SEARCH_INPUT: 'universal-search__input',
|
UNIVERSAL_SEARCH_INPUT: 'universal-search__input',
|
||||||
UNIVERSAL_SEARCH_INPUT_RIGHT_ELEMENTS: 'universal-search-input-right-elements',
|
UNIVERSAL_SEARCH_INPUT_RIGHT_ELEMENTS: 'universal-search-input-right-elements',
|
||||||
|
UNIVERSAL_SEARCH_TYPE_SELECT_OVERLAY: 'universal-search__type-select-overlay',
|
||||||
|
UNIVERSAL_SEARCH_TYPE_SELECT_BTN: 'universal-search__type-select-btn',
|
||||||
UNIVERSAL_SEARCH_FOOTER: 'universal-search__footer',
|
UNIVERSAL_SEARCH_FOOTER: 'universal-search__footer',
|
||||||
|
|
||||||
UNIVERSAL_SEARCH_ACTIONS: 'universal-search__actions',
|
UNIVERSAL_SEARCH_ACTIONS: 'universal-search__actions',
|
||||||
|
|||||||
14
client/src/common/resourcesTypes.js
Normal file
14
client/src/common/resourcesTypes.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export const RESOURCES_TYPES = {
|
||||||
|
INVOICE: 'invoice',
|
||||||
|
ESTIMATE: 'estimate',
|
||||||
|
RECEIPT: 'receipt',
|
||||||
|
PAYMENT_RECEIVE: 'payment_receive',
|
||||||
|
PAYMENT_MADE: 'payment_made',
|
||||||
|
CUSTOMER: 'customer',
|
||||||
|
VENDOR: 'vendor',
|
||||||
|
ITEM: 'item',
|
||||||
|
BILL: 'bill',
|
||||||
|
EXPENSE: 'expense',
|
||||||
|
MANUAL_JOURNAL: 'manual_journal',
|
||||||
|
ACCOUNT: 'account',
|
||||||
|
};
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import intl from 'react-intl-universal';
|
|
||||||
|
|
||||||
export default [
|
|
||||||
{
|
|
||||||
name: 'Type1',
|
|
||||||
placeholder: 'Id tempor anim culpa esse id laboris.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Type2',
|
|
||||||
placeholder: 'Laborum aliqua eiusmod voluptate aliqua',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -7,7 +7,7 @@ import Sidebar from 'components/Sidebar/Sidebar';
|
|||||||
import DashboardContent from 'components/Dashboard/DashboardContent';
|
import DashboardContent from 'components/Dashboard/DashboardContent';
|
||||||
import DialogsContainer from 'components/DialogsContainer';
|
import DialogsContainer from 'components/DialogsContainer';
|
||||||
import PreferencesPage from 'components/Preferences/PreferencesPage';
|
import PreferencesPage from 'components/Preferences/PreferencesPage';
|
||||||
import Search from 'containers/GeneralSearch/Search';
|
import DashboardUniversalSearch from 'containers/UniversalSearch/DashboardUniversalSearch';
|
||||||
import DashboardSplitPane from 'components/Dashboard/DashboardSplitePane';
|
import DashboardSplitPane from 'components/Dashboard/DashboardSplitePane';
|
||||||
import GlobalHotkeys from './GlobalHotkeys';
|
import GlobalHotkeys from './GlobalHotkeys';
|
||||||
import DashboardProvider from './DashboardProvider';
|
import DashboardProvider from './DashboardProvider';
|
||||||
@@ -35,7 +35,7 @@ export default function Dashboard() {
|
|||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|
||||||
<Search />
|
<DashboardUniversalSearch />
|
||||||
<DialogsContainer />
|
<DialogsContainer />
|
||||||
<GlobalHotkeys />
|
<GlobalHotkeys />
|
||||||
<DrawersContainer />
|
<DrawersContainer />
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
Popover,
|
Popover,
|
||||||
PopoverInteractionKind,
|
PopoverInteractionKind,
|
||||||
Position,
|
Position,
|
||||||
|
Divider,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
import { Icon } from 'components';
|
import { Icon } from 'components';
|
||||||
@@ -17,6 +18,8 @@ import { Icon } from 'components';
|
|||||||
*/
|
*/
|
||||||
export default function DashboardActionViewsList({
|
export default function DashboardActionViewsList({
|
||||||
resourceName,
|
resourceName,
|
||||||
|
allMenuItem,
|
||||||
|
allMenuItemText,
|
||||||
views,
|
views,
|
||||||
onChange,
|
onChange,
|
||||||
}) {
|
}) {
|
||||||
@@ -28,9 +31,28 @@ export default function DashboardActionViewsList({
|
|||||||
<MenuItem onClick={() => handleClickViewItem(view)} text={view.name} />
|
<MenuItem onClick={() => handleClickViewItem(view)} text={view.name} />
|
||||||
));
|
));
|
||||||
|
|
||||||
|
const handleAllTabClick = () => {
|
||||||
|
handleClickViewItem(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = (
|
||||||
|
<Menu>
|
||||||
|
{allMenuItem && (
|
||||||
|
<>
|
||||||
|
<MenuItem
|
||||||
|
onClick={handleAllTabClick}
|
||||||
|
text={allMenuItemText || 'All'}
|
||||||
|
/>
|
||||||
|
<Divider />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{viewsMenuItems}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
content={<Menu>{viewsMenuItems}</Menu>}
|
content={content}
|
||||||
minimal={true}
|
minimal={true}
|
||||||
interactionKind={PopoverInteractionKind.CLICK}
|
interactionKind={PopoverInteractionKind.CLICK}
|
||||||
position={Position.BOTTOM_LEFT}
|
position={Position.BOTTOM_LEFT}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export default function DashboardContentRoute() {
|
|||||||
hint={route.hint}
|
hint={route.hint}
|
||||||
sidebarExpand={route.sidebarExpand}
|
sidebarExpand={route.sidebarExpand}
|
||||||
pageType={route.pageType}
|
pageType={route.pageType}
|
||||||
|
defaultSearchResource={route.defaultSearchResource}
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import React, { useEffect, Suspense } from 'react';
|
import React, { useEffect, Suspense } from 'react';
|
||||||
import { isUndefined } from 'lodash';
|
// import { isUndefined } from 'lodash';
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
import { Spinner } from '@blueprintjs/core';
|
import { Spinner } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
// import withUniversalSearch from '../../containers/UniversalSearch/withUniversalSearch';
|
||||||
|
import withUniversalSearchActions from '../../containers/UniversalSearch/withUniversalSearchActions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dashboard pages wrapper.
|
* Dashboard pages wrapper.
|
||||||
*/
|
*/
|
||||||
@@ -16,12 +19,17 @@ function DashboardPage({
|
|||||||
Component,
|
Component,
|
||||||
name,
|
name,
|
||||||
hint,
|
hint,
|
||||||
|
defaultSearchResource,
|
||||||
|
|
||||||
// #withDashboardActions
|
// #withDashboardActions
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
setDashboardBackLink,
|
setDashboardBackLink,
|
||||||
changePageHint,
|
changePageHint,
|
||||||
toggleSidebarExpand
|
toggleSidebarExpand,
|
||||||
|
|
||||||
|
// #withUniversalSearch
|
||||||
|
setResourceTypeUniversalSearch,
|
||||||
|
resetResourceTypeUniversalSearch,
|
||||||
}) {
|
}) {
|
||||||
// Hydrate the given page title.
|
// Hydrate the given page title.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -38,7 +46,7 @@ function DashboardPage({
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
hint && changePageHint('');
|
hint && changePageHint('');
|
||||||
}
|
};
|
||||||
}, [hint, changePageHint]);
|
}, [hint, changePageHint]);
|
||||||
|
|
||||||
// Hydrate the dashboard back link status.
|
// Hydrate the dashboard back link status.
|
||||||
@@ -61,15 +69,30 @@ function DashboardPage({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
toggleSidebarExpand(sidebarExpand);
|
toggleSidebarExpand(sidebarExpand);
|
||||||
}, [toggleSidebarExpand, sidebarExpand])
|
}, [toggleSidebarExpand, sidebarExpand]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (defaultSearchResource) {
|
||||||
|
setResourceTypeUniversalSearch(defaultSearchResource);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
resetResourceTypeUniversalSearch();
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
defaultSearchResource,
|
||||||
|
resetResourceTypeUniversalSearch,
|
||||||
|
setResourceTypeUniversalSearch,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={CLASSES.DASHBOARD_PAGE}>
|
<div className={CLASSES.DASHBOARD_PAGE}>
|
||||||
<Suspense fallback={
|
<Suspense
|
||||||
|
fallback={
|
||||||
<div class="dashboard__fallback-loading">
|
<div class="dashboard__fallback-loading">
|
||||||
<Spinner size={40} value={null} />
|
<Spinner size={40} value={null} />
|
||||||
</div>
|
</div>
|
||||||
}>
|
}
|
||||||
|
>
|
||||||
<Component />
|
<Component />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
@@ -78,4 +101,6 @@ function DashboardPage({
|
|||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withDashboardActions,
|
withDashboardActions,
|
||||||
|
// withUniversalSearch,
|
||||||
|
withUniversalSearchActions,
|
||||||
)(DashboardPage);
|
)(DashboardPage);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import DashboardBreadcrumbs from 'components/Dashboard/DashboardBreadcrumbs';
|
|||||||
import DashboardBackLink from 'components/Dashboard/DashboardBackLink';
|
import DashboardBackLink from 'components/Dashboard/DashboardBackLink';
|
||||||
import { Icon, Hint, If } from 'components';
|
import { Icon, Hint, If } from 'components';
|
||||||
|
|
||||||
import withUniversalSearch from 'containers/UniversalSearch/withUniversalSearch';
|
import withUniversalSearchActions from 'containers/UniversalSearch/withUniversalSearchActions';
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
import withDashboard from 'containers/Dashboard/withDashboard';
|
import withDashboard from 'containers/Dashboard/withDashboard';
|
||||||
import withSettings from 'containers/Settings/withSettings';
|
import withSettings from 'containers/Settings/withSettings';
|
||||||
@@ -155,7 +155,7 @@ function DashboardTopbar({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withUniversalSearch,
|
withUniversalSearchActions,
|
||||||
withDashboard(({ pageTitle, pageHint, editViewId, sidebarExpended }) => ({
|
withDashboard(({ pageTitle, pageHint, editViewId, sidebarExpended }) => ({
|
||||||
pageTitle,
|
pageTitle,
|
||||||
editViewId,
|
editViewId,
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { Omnibar } from '@blueprintjs/select';
|
|
||||||
import { MenuItem, Spinner } from '@blueprintjs/core';
|
|
||||||
import { FormattedMessage as T, Icon, ListSelect } from 'components';
|
|
||||||
|
|
||||||
import withSearch from 'containers/GeneralSearch/withSearch';
|
|
||||||
|
|
||||||
import { compose } from 'utils';
|
|
||||||
|
|
||||||
function UniversalSearch({
|
|
||||||
results,
|
|
||||||
onClose,
|
|
||||||
|
|
||||||
// withSearch
|
|
||||||
globalSearchShow,
|
|
||||||
closeGlobalSearch,
|
|
||||||
...props
|
|
||||||
}) {
|
|
||||||
const SearchRenderer = (
|
|
||||||
{ name, code, amount },
|
|
||||||
{ handleClick, modifiers, query },
|
|
||||||
) => {
|
|
||||||
return (
|
|
||||||
<MenuItem
|
|
||||||
text={`${name} - ${code}`}
|
|
||||||
label={amount}
|
|
||||||
onClick={handleClick}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
closeGlobalSearch(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Omnibar
|
|
||||||
className={'navbar--omnibar'}
|
|
||||||
items={results}
|
|
||||||
itemRenderer={SearchRenderer}
|
|
||||||
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
|
|
||||||
resetOnSelect={true}
|
|
||||||
onClose={handleClose}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default compose(withSearch)(UniversalSearch);
|
|
||||||
227
client/src/components/UniversalSearch/UniversalSearch.js
Normal file
227
client/src/components/UniversalSearch/UniversalSearch.js
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { isUndefined } from 'lodash';
|
||||||
|
import {
|
||||||
|
Overlay,
|
||||||
|
InputGroup,
|
||||||
|
Tag,
|
||||||
|
MenuItem,
|
||||||
|
Spinner,
|
||||||
|
Intent,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import { QueryList } from '@blueprintjs/select';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
|
||||||
|
import { Icon, If, ListSelect, FormattedMessage as T } from 'components';
|
||||||
|
import {
|
||||||
|
UniversalSearchProvider,
|
||||||
|
useUniversalSearchContext,
|
||||||
|
} from './UniversalSearchProvider';
|
||||||
|
import { filterItemsByResourceType } from './utils';
|
||||||
|
import { RESOURCES_TYPES } from '../../common/resourcesTypes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Universal search input action.
|
||||||
|
*/
|
||||||
|
function UniversalSearchInputRightElements({ onSearchTypeChange }) {
|
||||||
|
const { isLoading, searchType, defaultSearchResource, searchTypeOptions } =
|
||||||
|
useUniversalSearchContext();
|
||||||
|
|
||||||
|
// Handle search type option change.
|
||||||
|
const handleSearchTypeChange = (option) => {
|
||||||
|
onSearchTypeChange && onSearchTypeChange(option);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={CLASSES.UNIVERSAL_SEARCH_INPUT_RIGHT_ELEMENTS}>
|
||||||
|
<If condition={isLoading}>
|
||||||
|
<Spinner tagName="div" intent={Intent.NONE} size={18} value={null} />
|
||||||
|
</If>
|
||||||
|
|
||||||
|
<ListSelect
|
||||||
|
items={searchTypeOptions}
|
||||||
|
onItemSelect={handleSearchTypeChange}
|
||||||
|
filterable={false}
|
||||||
|
initialSelectedItem={defaultSearchResource}
|
||||||
|
selectedItem={searchType}
|
||||||
|
selectedItemProp={'key'}
|
||||||
|
textProp={'label'}
|
||||||
|
// defaultText={intl.get('type')}
|
||||||
|
popoverProps={{
|
||||||
|
minimal: true,
|
||||||
|
captureDismiss: true,
|
||||||
|
className: CLASSES.UNIVERSAL_SEARCH_TYPE_SELECT_OVERLAY,
|
||||||
|
}}
|
||||||
|
buttonProps={{
|
||||||
|
minimal: true,
|
||||||
|
className: CLASSES.UNIVERSAL_SEARCH_TYPE_SELECT_BTN,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Universal search query list.
|
||||||
|
*/
|
||||||
|
function UniversalSearchQueryList(props) {
|
||||||
|
const { isOpen, isLoading, onSearchTypeChange, searchType, ...restProps } =
|
||||||
|
props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<QueryList
|
||||||
|
{...restProps}
|
||||||
|
initialContent={null}
|
||||||
|
renderer={(listProps) => (
|
||||||
|
<UniversalSearchBar
|
||||||
|
isOpen={isOpen}
|
||||||
|
onSearchTypeChange={onSearchTypeChange}
|
||||||
|
{...listProps}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
noResults={
|
||||||
|
!isLoading ? (
|
||||||
|
<MenuItem disabled={true} text={<T id={'no_results'} />} />
|
||||||
|
) : (
|
||||||
|
<MenuItem
|
||||||
|
disabled={true}
|
||||||
|
text={<T id={'universal_search.loading'} />}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Universal query search actions.
|
||||||
|
*/
|
||||||
|
function UniversalQuerySearchActions() {
|
||||||
|
return (
|
||||||
|
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTIONS)}>
|
||||||
|
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTION_SELECT)}>
|
||||||
|
<Tag>ENTER</Tag>
|
||||||
|
<span class={'text'}>{intl.get('universal_search.enter_text')}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTION_CLOSE)}>
|
||||||
|
<Tag>ESC</Tag>{' '}
|
||||||
|
<span class={'text'}>{intl.get('universal_search.close_text')}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTION_ARROWS)}>
|
||||||
|
<Tag>
|
||||||
|
<Icon icon={'arrow-up-24'} iconSize={16} />
|
||||||
|
</Tag>
|
||||||
|
<Tag>
|
||||||
|
<Icon icon={'arrow-down-24'} iconSize={16} />
|
||||||
|
</Tag>
|
||||||
|
<span class="text">{intl.get('universal_seach.navigate_text')}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Universal search input bar with items list.
|
||||||
|
*/
|
||||||
|
function UniversalSearchBar({ isOpen, onSearchTypeChange, ...listProps }) {
|
||||||
|
const { handleKeyDown, handleKeyUp } = listProps;
|
||||||
|
const handlers = isOpen
|
||||||
|
? { onKeyDown: handleKeyDown, onKeyUp: handleKeyUp }
|
||||||
|
: {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
CLASSES.UNIVERSAL_SEARCH_OMNIBAR,
|
||||||
|
listProps.className,
|
||||||
|
)}
|
||||||
|
{...handlers}
|
||||||
|
>
|
||||||
|
<InputGroup
|
||||||
|
autoFocus={true}
|
||||||
|
large={true}
|
||||||
|
leftIcon={<Icon icon={'universal-search'} iconSize={20} />}
|
||||||
|
placeholder={intl.get('universal_search.placeholder')}
|
||||||
|
onChange={listProps.handleQueryChange}
|
||||||
|
value={listProps.query}
|
||||||
|
rightElement={
|
||||||
|
<UniversalSearchInputRightElements
|
||||||
|
onSearchTypeChange={onSearchTypeChange}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{listProps.itemList}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Universal search.
|
||||||
|
*/
|
||||||
|
export function UniversalSearch({
|
||||||
|
defaultSearchResource,
|
||||||
|
searchResource,
|
||||||
|
|
||||||
|
overlayProps,
|
||||||
|
isOpen,
|
||||||
|
isLoading,
|
||||||
|
onSearchTypeChange,
|
||||||
|
items,
|
||||||
|
searchTypeOptions,
|
||||||
|
...queryListProps
|
||||||
|
}) {
|
||||||
|
// Search type state.
|
||||||
|
const [searchType, setSearchType] = React.useState(
|
||||||
|
defaultSearchResource || RESOURCES_TYPES.CUSTOMER,
|
||||||
|
);
|
||||||
|
// Handle search resource type controlled mode.
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (
|
||||||
|
!isUndefined(searchResource) &&
|
||||||
|
searchResource !== defaultSearchResource
|
||||||
|
) {
|
||||||
|
setSearchType(searchResource);
|
||||||
|
}
|
||||||
|
}, [searchResource, defaultSearchResource]);
|
||||||
|
|
||||||
|
// Handle search type change.
|
||||||
|
const handleSearchTypeChange = (searchTypeResource) => {
|
||||||
|
setSearchType(searchTypeResource.key);
|
||||||
|
onSearchTypeChange && onSearchTypeChange(searchTypeResource);
|
||||||
|
};
|
||||||
|
// Filters query list items based on the given search type.
|
||||||
|
const filteredItems = filterItemsByResourceType(items, searchType);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Overlay
|
||||||
|
hasBackdrop={true}
|
||||||
|
isOpen={isOpen}
|
||||||
|
className={classNames(CLASSES.UNIVERSAL_SEARCH_OVERLAY)}
|
||||||
|
{...overlayProps}
|
||||||
|
>
|
||||||
|
<UniversalSearchProvider
|
||||||
|
isLoading={isLoading}
|
||||||
|
searchType={searchType}
|
||||||
|
defaultSearchResource={defaultSearchResource}
|
||||||
|
searchTypeOptions={searchTypeOptions}
|
||||||
|
>
|
||||||
|
<div className={classNames(CLASSES.UNIVERSAL_SEARCH)}>
|
||||||
|
<UniversalSearchQueryList
|
||||||
|
isOpen={isOpen}
|
||||||
|
isLoading={isLoading}
|
||||||
|
searchType={searchType}
|
||||||
|
onSearchTypeChange={handleSearchTypeChange}
|
||||||
|
{...queryListProps}
|
||||||
|
items={filteredItems}
|
||||||
|
/>
|
||||||
|
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_FOOTER)}>
|
||||||
|
<UniversalQuerySearchActions />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</UniversalSearchProvider>
|
||||||
|
</Overlay>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import React, { createContext } from 'react';
|
||||||
|
|
||||||
|
const UniversalSearchContext = createContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Universal search data provider.
|
||||||
|
*/
|
||||||
|
function UniversalSearchProvider({
|
||||||
|
isLoading,
|
||||||
|
defaultSearchResource,
|
||||||
|
searchType,
|
||||||
|
searchTypeOptions,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
// Provider payload.
|
||||||
|
const provider = {
|
||||||
|
isLoading,
|
||||||
|
searchType,
|
||||||
|
defaultSearchResource,
|
||||||
|
searchTypeOptions,
|
||||||
|
};
|
||||||
|
|
||||||
|
return <UniversalSearchContext.Provider value={provider} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useUniversalSearchContext = () =>
|
||||||
|
React.useContext(UniversalSearchContext);
|
||||||
|
|
||||||
|
export { UniversalSearchProvider, useUniversalSearchContext };
|
||||||
4
client/src/components/UniversalSearch/utils.js
Normal file
4
client/src/components/UniversalSearch/utils.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
export const filterItemsByResourceType = (items, type) => {
|
||||||
|
return items.filter((item) => item._type === type);
|
||||||
|
}
|
||||||
@@ -58,7 +58,6 @@ import AccountsSuggestField from './AccountsSuggestField';
|
|||||||
import MaterialProgressBar from './MaterialProgressBar';
|
import MaterialProgressBar from './MaterialProgressBar';
|
||||||
import { MoneyFieldCell } from './DataTableCells';
|
import { MoneyFieldCell } from './DataTableCells';
|
||||||
import Card from './Card';
|
import Card from './Card';
|
||||||
import UniversalSearch from './UniversalSearch';
|
|
||||||
|
|
||||||
import { ItemsMultiSelect } from './Items';
|
import { ItemsMultiSelect } from './Items';
|
||||||
|
|
||||||
@@ -139,5 +138,4 @@ export {
|
|||||||
MoneyFieldCell,
|
MoneyFieldCell,
|
||||||
ItemsMultiSelect,
|
ItemsMultiSelect,
|
||||||
Card,
|
Card,
|
||||||
UniversalSearch,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -54,19 +54,20 @@ function ManualJournalActionsBar({
|
|||||||
const handleBulkDelete = () => {};
|
const handleBulkDelete = () => {};
|
||||||
|
|
||||||
// Handle tab change.
|
// Handle tab change.
|
||||||
const handleTabChange = (customView) => {
|
const handleTabChange = (view) => {
|
||||||
setManualJournalsTableState({ customViewId: customView.id || null });
|
setManualJournalsTableState({ viewSlug: view ? view.slig : null });
|
||||||
};
|
};
|
||||||
// Handle click a refresh Journals
|
// Handle click a refresh Journals
|
||||||
const handleRefreshBtnClick = () => { refresh(); };
|
const handleRefreshBtnClick = () => {
|
||||||
|
refresh();
|
||||||
console.log(manualJournalsFilterConditions, fields, 'XXX');
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
<DashboardActionViewsList
|
<DashboardActionViewsList
|
||||||
resourceName={'manual-journals'}
|
resourceName={'manual-journals'}
|
||||||
|
allMenuItem={true}
|
||||||
views={journalsViews}
|
views={journalsViews}
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
/>
|
/>
|
||||||
@@ -135,5 +136,5 @@ export default compose(
|
|||||||
withManualJournalsActions,
|
withManualJournalsActions,
|
||||||
withManualJournals(({ manualJournalsTableState }) => ({
|
withManualJournals(({ manualJournalsTableState }) => ({
|
||||||
manualJournalsFilterConditions: manualJournalsTableState.filterRoles,
|
manualJournalsFilterConditions: manualJournalsTableState.filterRoles,
|
||||||
}))
|
})),
|
||||||
)(ManualJournalActionsBar);
|
)(ManualJournalActionsBar);
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { RESOURCES_TYPES } from 'common/resourcesTypes';
|
||||||
|
import withDrawerActions from '../Drawer/withDrawerActions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Universal search manual journal item select action.
|
||||||
|
*/
|
||||||
|
function JournalUniversalSearchSelectComponent({
|
||||||
|
// #ownProps
|
||||||
|
resourceType,
|
||||||
|
resourceId,
|
||||||
|
|
||||||
|
// #withDrawerActions
|
||||||
|
openDrawer,
|
||||||
|
}) {
|
||||||
|
if (resourceType === RESOURCES_TYPES.MANUAL_JOURNAL) {
|
||||||
|
openDrawer('journal-drawer', { manualJournalId: resourceId });
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const JournalUniversalSearchSelectAction = withDrawerActions(
|
||||||
|
JournalUniversalSearchSelectComponent,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mappes the manual journal item to search item.
|
||||||
|
*/
|
||||||
|
const manualJournalsToSearch = (manualJournal) => ({
|
||||||
|
text: manualJournal.journal_number,
|
||||||
|
subText: manualJournal.formatted_date,
|
||||||
|
label: manualJournal.formatted_amount,
|
||||||
|
reference: manualJournal,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds universal search invoice configure.
|
||||||
|
*/
|
||||||
|
export const universalSearchJournalBind = () => ({
|
||||||
|
resourceType: RESOURCES_TYPES.MANUAL_JOURNAL,
|
||||||
|
optionItemLabel: 'Manual journal',
|
||||||
|
selectItemAction: JournalUniversalSearchSelectAction,
|
||||||
|
itemSelect: manualJournalsToSearch,
|
||||||
|
});
|
||||||
41
client/src/containers/Accounts/AccountUniversalSearch.js
Normal file
41
client/src/containers/Accounts/AccountUniversalSearch.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { RESOURCES_TYPES } from '../../common/resourcesTypes';
|
||||||
|
import withDrawerActions from '../Drawer/withDrawerActions';
|
||||||
|
|
||||||
|
function AccountUniversalSearchItemSelectComponent({
|
||||||
|
// #ownProps
|
||||||
|
resourceType,
|
||||||
|
resourceId,
|
||||||
|
|
||||||
|
// #withDrawerActions
|
||||||
|
openDrawer,
|
||||||
|
}) {
|
||||||
|
if (resourceType === RESOURCES_TYPES.ACCOUNT) {
|
||||||
|
openDrawer('account-drawer', { accountId: resourceId });
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AccountUniversalSearchItemSelect = withDrawerActions(
|
||||||
|
AccountUniversalSearchItemSelectComponent,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes account item to search item.
|
||||||
|
* @param {*} account
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const accountToSearch = (account) => ({
|
||||||
|
text: `${account.name} - ${account.code}`,
|
||||||
|
label: account.formatted_amount,
|
||||||
|
reference: account,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds universal search account configure.
|
||||||
|
*/
|
||||||
|
export const universalSearchAccountBind = () => ({
|
||||||
|
resourceType: RESOURCES_TYPES.ACCOUNT,
|
||||||
|
optionItemLabel: 'Account',
|
||||||
|
selectItemAction: AccountUniversalSearchItemSelect,
|
||||||
|
itemSelect: accountToSearch,
|
||||||
|
});
|
||||||
@@ -78,8 +78,8 @@ function AccountsActionsBar({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handle tab changing.
|
// Handle tab changing.
|
||||||
const handleTabChange = (customView) => {
|
const handleTabChange = (view) => {
|
||||||
setAccountsTableState({ customViewId: customView.id || null });
|
setAccountsTableState({ viewSlug: view ? view.slug : null });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle inactive switch changing.
|
// Handle inactive switch changing.
|
||||||
@@ -98,6 +98,8 @@ function AccountsActionsBar({
|
|||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
<DashboardActionViewsList
|
<DashboardActionViewsList
|
||||||
resourceName={'accounts'}
|
resourceName={'accounts'}
|
||||||
|
allMenuItem={true}
|
||||||
|
allMenuItemText={<T id={'all_accounts'} />}
|
||||||
views={resourceViews}
|
views={resourceViews}
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { memo } from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
Position,
|
Position,
|
||||||
Classes,
|
Classes,
|
||||||
@@ -6,14 +6,13 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
Menu,
|
Menu,
|
||||||
MenuDivider,
|
MenuDivider,
|
||||||
Intent,
|
Intent
|
||||||
Popover,
|
|
||||||
Button,
|
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { Icon, Money, If } from 'components';
|
import { Icon, Money, If } from 'components';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { safeCallback } from 'utils';
|
import { safeCallback } from 'utils';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accounts table actions menu.
|
* Accounts table actions menu.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -62,9 +62,9 @@ function CustomerActionsBar({
|
|||||||
openAlert('customers-bulk-delete', { customersIds: customersSelectedRows });
|
openAlert('customers-bulk-delete', { customersIds: customersSelectedRows });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTabChange = (viewId) => {
|
const handleTabChange = (view) => {
|
||||||
setCustomersTableState({
|
setCustomersTableState({
|
||||||
customViewId: viewId.id || null,
|
viewSlug: view ? view.slug : null,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// Handle inactive switch changing.
|
// Handle inactive switch changing.
|
||||||
@@ -82,6 +82,8 @@ function CustomerActionsBar({
|
|||||||
<DashboardActionViewsList
|
<DashboardActionViewsList
|
||||||
resourceName={'customers'}
|
resourceName={'customers'}
|
||||||
views={customersViews}
|
views={customersViews}
|
||||||
|
allMenuItem={true}
|
||||||
|
allMenuItemText={<T id={'all'} />}
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
/>
|
/>
|
||||||
<NavbarDivider />
|
<NavbarDivider />
|
||||||
|
|||||||
33
client/src/containers/Customers/CustomersUniversalSearch.js
Normal file
33
client/src/containers/Customers/CustomersUniversalSearch.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { RESOURCES_TYPES } from '../../common/resourcesTypes';
|
||||||
|
import withDrawerActions from '../Drawer/withDrawerActions';
|
||||||
|
|
||||||
|
function CustomerUniversalSearchSelectComponent({ resourceType, resourceId }) {
|
||||||
|
if (resourceType === RESOURCES_TYPES.CUSTOMER) {
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CustomerUniversalSearchSelectAction = withDrawerActions(
|
||||||
|
CustomerUniversalSearchSelectComponent
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes customers to search.
|
||||||
|
* @param {*} contact
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const customersToSearch = (contact) => ({
|
||||||
|
text: contact.display_name,
|
||||||
|
label: contact.formatted_balance,
|
||||||
|
reference: contact,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds universal search invoice configure.
|
||||||
|
*/
|
||||||
|
export const universalSearchCustomerBind = () => ({
|
||||||
|
resourceType: RESOURCES_TYPES.CUSTOMER,
|
||||||
|
optionItemLabel: 'Customers',
|
||||||
|
selectItemAction: CustomerUniversalSearchSelectAction,
|
||||||
|
itemSelect: customersToSearch,
|
||||||
|
});
|
||||||
24
client/src/containers/Expenses/ExpenseUniversalSearch.js
Normal file
24
client/src/containers/Expenses/ExpenseUniversalSearch.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { RESOURCES_TYPES } from 'common/resourcesTypes';
|
||||||
|
import withDrawerActions from '../Drawer/withDrawerActions';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Universal search bill item select action.
|
||||||
|
*/
|
||||||
|
function ExpenseUniversalSearchItemSelectComponent({
|
||||||
|
// #ownProps
|
||||||
|
resourceType,
|
||||||
|
resourceId,
|
||||||
|
|
||||||
|
// #withDrawerActions
|
||||||
|
openDrawer,
|
||||||
|
}) {
|
||||||
|
if (resourceType === RESOURCES_TYPES.EXPENSE) {
|
||||||
|
openDrawer('expense-drawer', { expenseId: resourceId });
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ExpenseUniversalSearchItemSelect = withDrawerActions(
|
||||||
|
ExpenseUniversalSearchItemSelectComponent,
|
||||||
|
);
|
||||||
@@ -37,7 +37,7 @@ function ExpensesActionsBar({
|
|||||||
setExpensesTableState,
|
setExpensesTableState,
|
||||||
|
|
||||||
// #withExpenses
|
// #withExpenses
|
||||||
expensesFilterConditions
|
expensesFilterConditions,
|
||||||
}) {
|
}) {
|
||||||
// History context.
|
// History context.
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@@ -57,9 +57,9 @@ function ExpensesActionsBar({
|
|||||||
const handleBulkDelete = () => {};
|
const handleBulkDelete = () => {};
|
||||||
|
|
||||||
// Handles the tab chaning.
|
// Handles the tab chaning.
|
||||||
const handleTabChange = (viewId) => {
|
const handleTabChange = (view) => {
|
||||||
setExpensesTableState({
|
setExpensesTableState({
|
||||||
customViewId: viewId.id || null,
|
viewSlug: view ? view.slug : null,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -73,6 +73,7 @@ function ExpensesActionsBar({
|
|||||||
<DashboardActionViewsList
|
<DashboardActionViewsList
|
||||||
resourceName={'expenses'}
|
resourceName={'expenses'}
|
||||||
views={expensesViews}
|
views={expensesViews}
|
||||||
|
allMenuItem={true}
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
/>
|
/>
|
||||||
<NavbarDivider />
|
<NavbarDivider />
|
||||||
@@ -140,5 +141,5 @@ export default compose(
|
|||||||
withExpensesActions,
|
withExpensesActions,
|
||||||
withExpenses(({ expensesTableState }) => ({
|
withExpenses(({ expensesTableState }) => ({
|
||||||
expensesFilterConditions: expensesTableState.filterRoles,
|
expensesFilterConditions: expensesTableState.filterRoles,
|
||||||
}))
|
})),
|
||||||
)(ExpensesActionsBar);
|
)(ExpensesActionsBar);
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Intent, Spinner } from '@blueprintjs/core';
|
|
||||||
import {
|
|
||||||
UniversalSearch,
|
|
||||||
ListSelect,
|
|
||||||
If,
|
|
||||||
FormattedMessage as T,
|
|
||||||
} from 'components';
|
|
||||||
import { defaultTo } from 'lodash';
|
|
||||||
import intl from 'react-intl-universal';
|
|
||||||
import { useAccounts } from 'hooks/query';
|
|
||||||
import UniversalSearchOptions from 'common/universalSearchOptions';
|
|
||||||
|
|
||||||
import { compose } from 'utils';
|
|
||||||
|
|
||||||
import withSearch from 'containers/GeneralSearch/withSearch';
|
|
||||||
|
|
||||||
function Search({ globalSearchShow }) {
|
|
||||||
const [query, setQuery] = React.useState();
|
|
||||||
const [labelState, setLabelState] = React.useState();
|
|
||||||
|
|
||||||
const { data: accounts, isFetching: isAccountsFetching } = useAccounts({
|
|
||||||
search_keyword: query,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleClick = (placeholder) => {
|
|
||||||
setLabelState(placeholder);
|
|
||||||
};
|
|
||||||
|
|
||||||
const MenuSelectType = (
|
|
||||||
<div style={{ display: 'flex' }}>
|
|
||||||
<If condition={isAccountsFetching}>
|
|
||||||
<Spinner tagName="div" intent={Intent.NONE} size={20} value={null} />
|
|
||||||
</If>
|
|
||||||
<ListSelect
|
|
||||||
items={UniversalSearchOptions}
|
|
||||||
onItemSelect={(holder) => handleClick(holder)}
|
|
||||||
filterable={false}
|
|
||||||
selectedItem={labelState?.name}
|
|
||||||
selectedItemProp={'name'}
|
|
||||||
textProp={'name'}
|
|
||||||
defaultText={intl.get('type')}
|
|
||||||
popoverProps={{ minimal: false, captureDismiss: true }}
|
|
||||||
buttonProps={{
|
|
||||||
minimal: true,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<UniversalSearch
|
|
||||||
results={accounts}
|
|
||||||
isOpen={globalSearchShow}
|
|
||||||
onQueryChange={(q) => setQuery(q)}
|
|
||||||
inputProps={{
|
|
||||||
rightElement: MenuSelectType,
|
|
||||||
placeholder: `${defaultTo(labelState?.placeholder, '')}`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default compose(withSearch)(Search);
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
import t from 'store/types';
|
|
||||||
|
|
||||||
export const mapStateToProps = (state, props) => ({
|
|
||||||
resultSearch: state.globalSearch.searches,
|
|
||||||
globalSearchShow: state.globalSearch.isOpen,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const mapDispatchToProps = (dispatch) => ({
|
|
||||||
openGlobalSearch: (result) => dispatch({ type: t.OPEN_SEARCH, }),
|
|
||||||
closeGlobalSearch: (result) => dispatch({ type: t.CLOSE_SEARCH }),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps);
|
|
||||||
|
|
||||||
@@ -58,8 +58,8 @@ function ItemsActionsBar({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handle tab changing.
|
// Handle tab changing.
|
||||||
const handleTabChange = (viewId) => {
|
const handleTabChange = (view) => {
|
||||||
setItemsTableState({ customViewId: viewId.id || null });
|
setItemsTableState({ viewSlug: view ? view.slug : null });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle cancel/confirm items bulk.
|
// Handle cancel/confirm items bulk.
|
||||||
@@ -82,6 +82,8 @@ function ItemsActionsBar({
|
|||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
<DashboardActionViewsList
|
<DashboardActionViewsList
|
||||||
resourceName={'items'}
|
resourceName={'items'}
|
||||||
|
allMenuItem={true}
|
||||||
|
allMenuItemText={<T id={'all_items'} />}
|
||||||
views={itemsViews}
|
views={itemsViews}
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
45
client/src/containers/Items/ItemsUniversalSearch.js
Normal file
45
client/src/containers/Items/ItemsUniversalSearch.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { RESOURCES_TYPES } from '../../common/resourcesTypes';
|
||||||
|
import withDrawerActions from '../Drawer/withDrawerActions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Item univrsal search item select action.
|
||||||
|
*/
|
||||||
|
function ItemUniversalSearchSelectComponent({
|
||||||
|
// #ownProps
|
||||||
|
resourceType,
|
||||||
|
resourceId,
|
||||||
|
|
||||||
|
// #withDrawerActions
|
||||||
|
openDrawer,
|
||||||
|
}) {
|
||||||
|
if (resourceType === RESOURCES_TYPES.ITEM) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ItemUniversalSearchSelectAction = withDrawerActions(
|
||||||
|
ItemUniversalSearchSelectComponent,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes items to search.
|
||||||
|
* @param {*} item
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const transfromItemsToSearch = (item) => ({
|
||||||
|
text: item.name,
|
||||||
|
subText: item.code,
|
||||||
|
label: item.type,
|
||||||
|
reference: item,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds universal search invoice configure.
|
||||||
|
*/
|
||||||
|
export const universalSearchItemBind = () => ({
|
||||||
|
resourceType: RESOURCES_TYPES.ITEM,
|
||||||
|
optionItemLabel: 'Items',
|
||||||
|
selectItemAction: ItemUniversalSearchSelectAction,
|
||||||
|
itemSelect: transfromItemsToSearch,
|
||||||
|
});
|
||||||
116
client/src/containers/Purchases/Bills/BillUniversalSearch.js
Normal file
116
client/src/containers/Purchases/Bills/BillUniversalSearch.js
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { MenuItem } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
import { formattedAmount } from 'utils';
|
||||||
|
import { T, Icon, Choose, If } from 'components';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
|
import { RESOURCES_TYPES } from 'common/resourcesTypes';
|
||||||
|
import withDrawerActions from '../../Drawer/withDrawerActions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Universal search bill item select action.
|
||||||
|
*/
|
||||||
|
function BillUniversalSearchSelectComponent({
|
||||||
|
// #ownProps
|
||||||
|
resourceType,
|
||||||
|
resourceId,
|
||||||
|
|
||||||
|
// #withDrawerActions
|
||||||
|
openDrawer,
|
||||||
|
}) {
|
||||||
|
if (resourceType === RESOURCES_TYPES.INVOICE) {
|
||||||
|
openDrawer('bill-drawer', { billId: resourceId });
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BillUniversalSearchSelect = withDrawerActions(
|
||||||
|
BillUniversalSearchSelectComponent,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status accessor.
|
||||||
|
*/
|
||||||
|
export function BillStatus({ bill }) {
|
||||||
|
return (
|
||||||
|
<Choose>
|
||||||
|
<Choose.When condition={bill.is_fully_paid && bill.is_open}>
|
||||||
|
<span class="fully-paid-text">
|
||||||
|
<T id={'paid'} />
|
||||||
|
</span>
|
||||||
|
</Choose.When>
|
||||||
|
<Choose.When condition={bill.is_open}>
|
||||||
|
<Choose>
|
||||||
|
<Choose.When condition={bill.is_overdue}>
|
||||||
|
<span className={'overdue-status'}>
|
||||||
|
{intl.get('overdue_by', { overdue: bill.overdue_days })}
|
||||||
|
</span>
|
||||||
|
</Choose.When>
|
||||||
|
<Choose.Otherwise>
|
||||||
|
<span className={'due-status'}>
|
||||||
|
{intl.get('due_in', { due: bill.remaining_days })}
|
||||||
|
</span>
|
||||||
|
</Choose.Otherwise>
|
||||||
|
</Choose>
|
||||||
|
<If condition={bill.is_partially_paid}>
|
||||||
|
<span className="partial-paid">
|
||||||
|
{intl.get('day_partially_paid', {
|
||||||
|
due: formattedAmount(bill.due_amount, bill.currency_code),
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</If>
|
||||||
|
</Choose.When>
|
||||||
|
<Choose.Otherwise>
|
||||||
|
<span class="draft">
|
||||||
|
<T id={'draft'} />
|
||||||
|
</span>
|
||||||
|
</Choose.Otherwise>
|
||||||
|
</Choose>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bill universal search item.
|
||||||
|
*/
|
||||||
|
export function BillUniversalSearchItem(
|
||||||
|
item,
|
||||||
|
{ handleClick, modifiers, query },
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
active={modifiers.active}
|
||||||
|
text={
|
||||||
|
<div>
|
||||||
|
<div>{item.text}</div>
|
||||||
|
<span class="bp3-text-muted">
|
||||||
|
{item.reference.bill_number}{' '}
|
||||||
|
<Icon icon={'caret-right-16'} iconSize={16} />
|
||||||
|
{item.reference.formatted_bill_date}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<>
|
||||||
|
<div class="amount">{item.reference.formatted_amount}</div>
|
||||||
|
<BillStatus bill={item.reference} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
onClick={handleClick}
|
||||||
|
className={'universal-search__item--bill'}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const billsToSearch = (bill) => ({
|
||||||
|
text: bill.vendor.display_name,
|
||||||
|
reference: bill,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const universalSearchBillBind = () => ({
|
||||||
|
resourceType: RESOURCES_TYPES.BILL,
|
||||||
|
optionItemLabel: 'Bills',
|
||||||
|
selectItemAction: BillUniversalSearchSelect,
|
||||||
|
itemRenderer: BillUniversalSearchItem,
|
||||||
|
itemSelect: billsToSearch,
|
||||||
|
});
|
||||||
@@ -34,7 +34,7 @@ function BillActionsBar({
|
|||||||
setBillsTableState,
|
setBillsTableState,
|
||||||
|
|
||||||
// #withBills
|
// #withBills
|
||||||
billsConditionsRoles
|
billsConditionsRoles,
|
||||||
}) {
|
}) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
@@ -50,13 +50,15 @@ function BillActionsBar({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handle tab change.
|
// Handle tab change.
|
||||||
const handleTabChange = (customView) => {
|
const handleTabChange = (view) => {
|
||||||
setBillsTableState({
|
setBillsTableState({
|
||||||
customViewId: customView.id || null,
|
viewSlug: view ? view.slug : null,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// Handle click a refresh bills
|
// Handle click a refresh bills
|
||||||
const handleRefreshBtnClick = () => { refresh(); };
|
const handleRefreshBtnClick = () => {
|
||||||
|
refresh();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
@@ -64,6 +66,8 @@ function BillActionsBar({
|
|||||||
<DashboardActionViewsList
|
<DashboardActionViewsList
|
||||||
resourceName={'bills'}
|
resourceName={'bills'}
|
||||||
views={billsViews}
|
views={billsViews}
|
||||||
|
allMenuItem={true}
|
||||||
|
allMenuItemText={<T id={'all'} />}
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
/>
|
/>
|
||||||
<NavbarDivider />
|
<NavbarDivider />
|
||||||
@@ -127,6 +131,6 @@ function BillActionsBar({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withBillsActions,
|
withBillsActions,
|
||||||
withBills(({ billsTableState }) => ({
|
withBills(({ billsTableState }) => ({
|
||||||
billsConditionsRoles: billsTableState.filterRoles
|
billsConditionsRoles: billsTableState.filterRoles,
|
||||||
}))
|
})),
|
||||||
)(BillActionsBar);
|
)(BillActionsBar);
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { MenuItem } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
import { Icon } from 'components';
|
||||||
|
|
||||||
|
import { RESOURCES_TYPES } from 'common/resourcesTypes';
|
||||||
|
import withDrawerActions from '../../Drawer/withDrawerActions';
|
||||||
|
|
||||||
|
import { highlightText } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Universal search bill item select action.
|
||||||
|
*/
|
||||||
|
function PaymentMadeUniversalSearchSelectComponent({
|
||||||
|
// #ownProps
|
||||||
|
resourceType,
|
||||||
|
resourceId,
|
||||||
|
|
||||||
|
// #withDrawerActions
|
||||||
|
openDrawer,
|
||||||
|
}) {
|
||||||
|
if (resourceType === RESOURCES_TYPES.PAYMENT_MADE) {
|
||||||
|
openDrawer('payment-made-detail-drawer', { paymentMadeId: resourceId });
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PaymentMadeUniversalSearchSelect = withDrawerActions(
|
||||||
|
PaymentMadeUniversalSearchSelectComponent,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payment made universal search item.
|
||||||
|
*/
|
||||||
|
export function PaymentMadeUniversalSearchItem(
|
||||||
|
item,
|
||||||
|
{ handleClick, modifiers, query },
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
active={modifiers.active}
|
||||||
|
text={
|
||||||
|
<div>
|
||||||
|
<div>{highlightText(item.text, query)}</div>
|
||||||
|
|
||||||
|
<span class="bp3-text-muted">
|
||||||
|
{highlightText(item.reference.payment_number, query)}{' '}
|
||||||
|
<Icon icon={'caret-right-16'} iconSize={16} />
|
||||||
|
{highlightText(item.reference.formatted_payment_date, query)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
label={<div class="amount">{item.reference.formatted_amount}</div>}
|
||||||
|
onClick={handleClick}
|
||||||
|
className={'universal-search__item--payment-made'}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payment made resource item to search item.
|
||||||
|
*/
|
||||||
|
const paymentMadeToSearch = (payment) => ({
|
||||||
|
text: payment.vendor.display_name,
|
||||||
|
subText: payment.formatted_payment_date,
|
||||||
|
label: payment.formatted_amount,
|
||||||
|
reference: payment,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds universal search payment made configure.
|
||||||
|
*/
|
||||||
|
export const universalSearchPaymentMadeBind = () => ({
|
||||||
|
resourceType: RESOURCES_TYPES.PAYMENT_MADE,
|
||||||
|
optionItemLabel: 'Payment made',
|
||||||
|
selectItemAction: PaymentMadeUniversalSearchSelect,
|
||||||
|
itemRenderer: PaymentMadeUniversalSearchItem,
|
||||||
|
itemSelect: paymentMadeToSearch,
|
||||||
|
});
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { MenuItem } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
import { Choose, T, Icon } from 'components';
|
||||||
|
|
||||||
|
import { RESOURCES_TYPES } from "../../../../common/resourcesTypes";
|
||||||
|
import withDrawerActions from "../../../Drawer/withDrawerActions";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimate universal search item select action.
|
||||||
|
*/
|
||||||
|
function EstimateUniversalSearchSelectComponent({
|
||||||
|
// #ownProps
|
||||||
|
resourceType,
|
||||||
|
resourceId,
|
||||||
|
|
||||||
|
// #withDrawerActions
|
||||||
|
openDrawer,
|
||||||
|
}) {
|
||||||
|
if (resourceType === RESOURCES_TYPES.ESTIMATE) {
|
||||||
|
openDrawer('estimate-drawer', { estimateId: resourceId });
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EstimateUniversalSearchSelect = withDrawerActions(
|
||||||
|
EstimateUniversalSearchSelectComponent,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status accessor.
|
||||||
|
*/
|
||||||
|
export const EstimateStatus = ({ estimate }) => (
|
||||||
|
<Choose>
|
||||||
|
<Choose.When condition={estimate.is_delivered && estimate.is_approved}>
|
||||||
|
<span class="approved">
|
||||||
|
<T id={'approved'} />
|
||||||
|
</span>
|
||||||
|
</Choose.When>
|
||||||
|
<Choose.When condition={estimate.is_delivered && estimate.is_rejected}>
|
||||||
|
<span class="reject">
|
||||||
|
<T id={'rejected'} />
|
||||||
|
</span>
|
||||||
|
</Choose.When>
|
||||||
|
<Choose.When
|
||||||
|
condition={
|
||||||
|
estimate.is_delivered && !estimate.is_rejected && !estimate.is_approved
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span class="delivered">
|
||||||
|
<T id={'delivered'} />
|
||||||
|
</span>
|
||||||
|
</Choose.When>
|
||||||
|
<Choose.Otherwise>
|
||||||
|
<span class="draft">
|
||||||
|
<T id={'draft'} />
|
||||||
|
</span>
|
||||||
|
</Choose.Otherwise>
|
||||||
|
</Choose>
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimate universal search item.
|
||||||
|
*/
|
||||||
|
export function EstimateUniversalSearchItem(
|
||||||
|
item,
|
||||||
|
{ handleClick, modifiers, query },
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
text={
|
||||||
|
<div>
|
||||||
|
<div>{item.text}</div>
|
||||||
|
<span class="bp3-text-muted">
|
||||||
|
{item.reference.estimate_number}{' '}
|
||||||
|
<Icon icon={'caret-right-16'} iconSize={16} />
|
||||||
|
{item.reference.formatted_estimate_date}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<>
|
||||||
|
<div class="amount">{item.reference.formatted_amount}</div>
|
||||||
|
<EstimateStatus estimate={item.reference} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
onClick={handleClick}
|
||||||
|
className={'universal-search__item--estimate'}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const transformEstimatesToSearch = (estimate) => ({
|
||||||
|
text: estimate.customer.display_name,
|
||||||
|
label: estimate.formatted_balance,
|
||||||
|
reference: estimate,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const universalSearchEstimateBind = () => ({
|
||||||
|
resourceType: RESOURCES_TYPES.ESTIMATE,
|
||||||
|
optionItemLabel: 'Estimates',
|
||||||
|
selectItemAction: EstimateUniversalSearchSelect,
|
||||||
|
itemRenderer: EstimateUniversalSearchItem,
|
||||||
|
itemSelect: transformEstimatesToSearch
|
||||||
|
});
|
||||||
@@ -35,7 +35,7 @@ function EstimateActionsBar({
|
|||||||
setEstimatesTableState,
|
setEstimatesTableState,
|
||||||
|
|
||||||
// #withEstimates
|
// #withEstimates
|
||||||
estimatesFilterRoles
|
estimatesFilterRoles,
|
||||||
}) {
|
}) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
@@ -51,20 +51,24 @@ function EstimateActionsBar({
|
|||||||
const { refresh } = useRefreshEstimates();
|
const { refresh } = useRefreshEstimates();
|
||||||
|
|
||||||
// Handle tab change.
|
// Handle tab change.
|
||||||
const handleTabChange = (customView) => {
|
const handleTabChange = (view) => {
|
||||||
setEstimatesTableState({
|
setEstimatesTableState({
|
||||||
customViewId: customView.id || null,
|
viewSlug: view ? view.slug : null,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle click a refresh sale estimates
|
// Handle click a refresh sale estimates
|
||||||
const handleRefreshBtnClick = () => { refresh(); };
|
const handleRefreshBtnClick = () => {
|
||||||
|
refresh();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
<DashboardActionViewsList
|
<DashboardActionViewsList
|
||||||
resourceName={'estimates'}
|
resourceName={'estimates'}
|
||||||
|
allMenuItem={true}
|
||||||
|
allMenuItemText={<T id={'all'} />}
|
||||||
views={estimatesViews}
|
views={estimatesViews}
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
121
client/src/containers/Sales/Invoices/InvoiceUniversalSearch.js
Normal file
121
client/src/containers/Sales/Invoices/InvoiceUniversalSearch.js
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { MenuItem } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
import { T, Choose, Icon } from 'components';
|
||||||
|
|
||||||
|
import { highlightText } from 'utils';
|
||||||
|
|
||||||
|
import { RESOURCES_TYPES } from 'common/resourcesTypes';
|
||||||
|
import withDrawerActions from '../../Drawer/withDrawerActions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Universal search invoice item select action.
|
||||||
|
*/
|
||||||
|
function InvoiceUniversalSearchSelectComponent({
|
||||||
|
// #ownProps
|
||||||
|
resourceType,
|
||||||
|
resourceId,
|
||||||
|
|
||||||
|
// #withDrawerActions
|
||||||
|
openDrawer,
|
||||||
|
}) {
|
||||||
|
if (resourceType === RESOURCES_TYPES.INVOICE) {
|
||||||
|
openDrawer('invoice-drawer', { invoiceId: resourceId });
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InvoiceUniversalSearchSelect = withDrawerActions(
|
||||||
|
InvoiceUniversalSearchSelectComponent,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice status.
|
||||||
|
*/
|
||||||
|
function InvoiceStatus(customer) {
|
||||||
|
return (
|
||||||
|
<Choose>
|
||||||
|
<Choose.When condition={customer.is_fully_paid && customer.is_delivered}>
|
||||||
|
<span class="status status-success">
|
||||||
|
<T id={'paid'} />
|
||||||
|
</span>
|
||||||
|
</Choose.When>
|
||||||
|
|
||||||
|
<Choose.When condition={customer.is_delivered}>
|
||||||
|
<Choose>
|
||||||
|
<Choose.When condition={customer.is_overdue}>
|
||||||
|
<span className={'status status-warning'}>
|
||||||
|
{intl.get('overdue_by', { overdue: customer.overdue_days })}
|
||||||
|
</span>
|
||||||
|
</Choose.When>
|
||||||
|
<Choose.Otherwise>
|
||||||
|
<span className={'status status-warning'}>
|
||||||
|
{intl.get('due_in', { due: customer.remaining_days })}
|
||||||
|
</span>
|
||||||
|
</Choose.Otherwise>
|
||||||
|
</Choose>
|
||||||
|
</Choose.When>
|
||||||
|
<Choose.Otherwise>
|
||||||
|
<span class="status status--gray">
|
||||||
|
<T id={'draft'} />
|
||||||
|
</span>
|
||||||
|
</Choose.Otherwise>
|
||||||
|
</Choose>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Universal search invoice item.
|
||||||
|
*/
|
||||||
|
export function InvoiceUniversalSearchItem(
|
||||||
|
item,
|
||||||
|
{ handleClick, modifiers, query },
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
active={modifiers.active}
|
||||||
|
text={
|
||||||
|
<div>
|
||||||
|
<div>{highlightText(item.text, query)}</div>
|
||||||
|
<span class="bp3-text-muted">
|
||||||
|
{highlightText(item.reference.invoice_no, query)}{' '}
|
||||||
|
<Icon icon={'caret-right-16'} iconSize={16} />
|
||||||
|
{highlightText(item.reference.formatted_invoice_date, query)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<>
|
||||||
|
<div class="amount">${item.reference.balance}</div>
|
||||||
|
<InvoiceStatus customer={item.reference} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
onClick={handleClick}
|
||||||
|
className={'universal-search__item--invoice'}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes invoices to search.
|
||||||
|
* @param {*} invoice
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const transformInvoicesToSearch = (invoice) => ({
|
||||||
|
id: invoice.id,
|
||||||
|
text: invoice.customer.display_name,
|
||||||
|
label: invoice.formatted_balance,
|
||||||
|
reference: invoice,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds universal search invoice configure.
|
||||||
|
*/
|
||||||
|
export const universalSearchInvoiceBind = () => ({
|
||||||
|
resourceType: RESOURCES_TYPES.INVOICE,
|
||||||
|
optionItemLabel: 'Invoices',
|
||||||
|
selectItemAction: InvoiceUniversalSearchSelect,
|
||||||
|
itemRenderer: InvoiceUniversalSearchItem,
|
||||||
|
itemSelect: transformInvoicesToSearch,
|
||||||
|
});
|
||||||
@@ -51,8 +51,8 @@ function InvoiceActionsBar({
|
|||||||
const { refresh } = useRefreshInvoices();
|
const { refresh } = useRefreshInvoices();
|
||||||
|
|
||||||
// Handle views tab change.
|
// Handle views tab change.
|
||||||
const handleTabChange = (customView) => {
|
const handleTabChange = (view) => {
|
||||||
setInvoicesTableState({ customViewId: customView.id || null });
|
setInvoicesTableState({ viewSlug: view ? view.slug : null });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle click a refresh sale invoices
|
// Handle click a refresh sale invoices
|
||||||
@@ -64,6 +64,7 @@ function InvoiceActionsBar({
|
|||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
<DashboardActionViewsList
|
<DashboardActionViewsList
|
||||||
|
allMenuItem={true}
|
||||||
resourceName={'invoices'}
|
resourceName={'invoices'}
|
||||||
views={invoicesViews}
|
views={invoicesViews}
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { MenuItem } from '@blueprintjs/core';
|
||||||
|
import { RESOURCES_TYPES } from "../../../common/resourcesTypes";
|
||||||
|
import withDrawerActions from "../../Drawer/withDrawerActions";
|
||||||
|
import { highlightText } from 'utils';
|
||||||
|
import { Icon } from 'components';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payment receive universal search item select action.
|
||||||
|
*/
|
||||||
|
function PaymentReceiveUniversalSearchSelectComponent({
|
||||||
|
// #ownProps
|
||||||
|
resourceType,
|
||||||
|
resourceId,
|
||||||
|
|
||||||
|
// #withDrawerActions
|
||||||
|
openDrawer,
|
||||||
|
}) {
|
||||||
|
if (resourceType === RESOURCES_TYPES.PAYMENT_RECEIVE) {
|
||||||
|
openDrawer('payment-receive-drawer', { paymentReceiveId: resourceId });
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PaymentReceiveUniversalSearchSelect = withDrawerActions(
|
||||||
|
PaymentReceiveUniversalSearchSelectComponent,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payment receive universal search item.
|
||||||
|
*/
|
||||||
|
export function PaymentReceiveUniversalSearchItem(
|
||||||
|
item,
|
||||||
|
{ handleClick, modifiers, query },
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
active={modifiers.active}
|
||||||
|
text={
|
||||||
|
<div>
|
||||||
|
<div>{highlightText(item.text, query)}</div>
|
||||||
|
|
||||||
|
<span class="bp3-text-muted">
|
||||||
|
{highlightText(item.reference.payment_receive_no, query)}{' '}
|
||||||
|
<Icon icon={'caret-right-16'} iconSize={16} />
|
||||||
|
{highlightText(item.reference.formatted_payment_date, query)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
label={<div class="amount">{item.reference.formatted_amount}</div>}
|
||||||
|
onClick={handleClick}
|
||||||
|
className={'universal-search__item--invoice'}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes payment receives to search.
|
||||||
|
* @param {*} payment
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const paymentReceivesToSearch = (payment) => ({
|
||||||
|
text: payment.customer.display_name,
|
||||||
|
subText: payment.formatted_payment_date,
|
||||||
|
label: payment.formatted_amount,
|
||||||
|
reference: payment,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds universal search payment receive configure.
|
||||||
|
*/
|
||||||
|
export const universalSearchPaymentReceiveBind = () => ({
|
||||||
|
resourceType: RESOURCES_TYPES.PAYMENT_RECEIVE,
|
||||||
|
optionItemLabel: 'Payment receive',
|
||||||
|
selectItemAction: PaymentReceiveUniversalSearchSelect,
|
||||||
|
itemRenderer: PaymentReceiveUniversalSearchItem,
|
||||||
|
itemSelect: paymentReceivesToSearch,
|
||||||
|
});
|
||||||
100
client/src/containers/Sales/Receipts/ReceiptUniversalSearch.js
Normal file
100
client/src/containers/Sales/Receipts/ReceiptUniversalSearch.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { MenuItem } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
import { Icon, Choose, T } from 'components';
|
||||||
|
|
||||||
|
import { RESOURCES_TYPES } from "../../../common/resourcesTypes";
|
||||||
|
import withDrawerActions from "../../Drawer/withDrawerActions";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receipt universal search item select action.
|
||||||
|
*/
|
||||||
|
function ReceiptUniversalSearchSelectComponent({
|
||||||
|
// #ownProps
|
||||||
|
resourceType,
|
||||||
|
resourceId,
|
||||||
|
onAction,
|
||||||
|
|
||||||
|
// #withDrawerActions
|
||||||
|
openDrawer,
|
||||||
|
}) {
|
||||||
|
if (resourceType === RESOURCES_TYPES.RECEIPT) {
|
||||||
|
openDrawer('receipt-drawer', { estimateId: resourceId });
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ReceiptUniversalSearchSelect = withDrawerActions(
|
||||||
|
ReceiptUniversalSearchSelectComponent,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status accessor.
|
||||||
|
*/
|
||||||
|
function ReceiptStatus({ receipt }) {
|
||||||
|
return (
|
||||||
|
<Choose>
|
||||||
|
<Choose.When condition={receipt.is_closed}>
|
||||||
|
<span class="closed"><T id={'closed'} /></span>
|
||||||
|
</Choose.When>
|
||||||
|
|
||||||
|
<Choose.Otherwise>
|
||||||
|
<span class="draft"><T id={'draft'} /></span>
|
||||||
|
</Choose.Otherwise>
|
||||||
|
</Choose>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receipt universal search item.
|
||||||
|
*/
|
||||||
|
export function ReceiptUniversalSearchItem(
|
||||||
|
item,
|
||||||
|
{ handleClick, modifiers, query },
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
active={modifiers.active}
|
||||||
|
text={
|
||||||
|
<div>
|
||||||
|
<div>{item.text}</div>
|
||||||
|
<span class="bp3-text-muted">
|
||||||
|
{item.reference.receipt_number}{' '}
|
||||||
|
<Icon icon={'caret-right-16'} iconSize={16} />
|
||||||
|
{item.reference.formatted_receipt_date}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<>
|
||||||
|
<div class="amount">${item.reference.amount}</div>
|
||||||
|
<ReceiptStatus receipt={item.reference} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
onClick={handleClick}
|
||||||
|
className={'universal-search__item--receipt'}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes receipt resource item to search item.
|
||||||
|
*/
|
||||||
|
const transformReceiptsToSearch = (receipt) => ({
|
||||||
|
text: receipt.customer.display_name,
|
||||||
|
label: receipt.formatted_amount,
|
||||||
|
reference: receipt,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receipt universal search bind configuration.
|
||||||
|
*/
|
||||||
|
export const universalSearchReceiptBind = () => ({
|
||||||
|
resourceType: RESOURCES_TYPES.RECEIPT,
|
||||||
|
optionItemLabel: 'Receipts',
|
||||||
|
selectItemAction: ReceiptUniversalSearchSelect,
|
||||||
|
itemRenderer: ReceiptUniversalSearchItem,
|
||||||
|
itemSelect: transformReceiptsToSearch,
|
||||||
|
});
|
||||||
@@ -49,22 +49,24 @@ function ReceiptActionsBar({
|
|||||||
// Sale receipt refresh action.
|
// Sale receipt refresh action.
|
||||||
const { refresh } = useRefreshReceipts();
|
const { refresh } = useRefreshReceipts();
|
||||||
|
|
||||||
const handleTabChange = (customView) => {
|
const handleTabChange = (view) => {
|
||||||
setReceiptsTableState({
|
setReceiptsTableState({
|
||||||
customViewId: customView.id || null,
|
viewSlug: view ? view.slug : null,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle click a refresh sale estimates
|
// Handle click a refresh sale estimates
|
||||||
const handleRefreshBtnClick = () => { refresh(); };
|
const handleRefreshBtnClick = () => {
|
||||||
|
refresh();
|
||||||
console.log(receiptsFilterConditions, fields, 'XXX');
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
<DashboardActionViewsList
|
<DashboardActionViewsList
|
||||||
resourceName={'receipts'}
|
resourceName={'receipts'}
|
||||||
|
allMenuItem={true}
|
||||||
|
allMenuItemText={<T id={'all'} />}
|
||||||
views={receiptsViews}
|
views={receiptsViews}
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import { isUndefined } from 'lodash';
|
||||||
|
|
||||||
|
import { useUniversalSearch } from 'hooks/query';
|
||||||
|
import { UniversalSearch } from 'components';
|
||||||
|
|
||||||
|
import { RESOURCES_TYPES } from 'common/resourcesTypes';
|
||||||
|
import { compose } from 'utils';
|
||||||
|
import withUniversalSearchActions from './withUniversalSearchActions';
|
||||||
|
import withUniversalSearch from './withUniversalSearch';
|
||||||
|
|
||||||
|
import DashboardUniversalSearchItemActions from './DashboardUniversalSearchItemActions';
|
||||||
|
import { DashboardUniversalSearchItem } from './components';
|
||||||
|
|
||||||
|
import DashboardUniversalSearchHotkeys from './DashboardUniversalSearchHotkeys';
|
||||||
|
import { getUniversalSearchTypeOptions } from './utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dashboard universal search.
|
||||||
|
*/
|
||||||
|
function DashboardUniversalSearch({
|
||||||
|
// #withUniversalSearchActions
|
||||||
|
setSelectedItemUniversalSearch,
|
||||||
|
|
||||||
|
// #withUniversalSearch
|
||||||
|
globalSearchShow,
|
||||||
|
closeGlobalSearch,
|
||||||
|
defaultUniversalResourceType,
|
||||||
|
}) {
|
||||||
|
// Search keyword.
|
||||||
|
const [searchKeyword, setSearchKeyword] = React.useState('');
|
||||||
|
|
||||||
|
// Default search type.
|
||||||
|
const [defaultSearchType, setDefaultSearchType] = React.useState(
|
||||||
|
defaultUniversalResourceType || RESOURCES_TYPES.CUSTOMR,
|
||||||
|
);
|
||||||
|
// Search type.
|
||||||
|
const [searchType, setSearchType] = React.useState(defaultSearchType);
|
||||||
|
|
||||||
|
// Sync default search type with default universal resource type.
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (
|
||||||
|
!isUndefined(defaultUniversalResourceType) &&
|
||||||
|
defaultSearchType !== defaultUniversalResourceType
|
||||||
|
) {
|
||||||
|
setSearchType(defaultUniversalResourceType);
|
||||||
|
setDefaultSearchType(defaultUniversalResourceType);
|
||||||
|
}
|
||||||
|
}, [defaultSearchType, defaultUniversalResourceType]);
|
||||||
|
|
||||||
|
// Fetch accounts list according to the given custom view id.
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
remove,
|
||||||
|
isFetching: isSearchFetching,
|
||||||
|
isLoading: isSearchLoading,
|
||||||
|
refetch,
|
||||||
|
} = useUniversalSearch(searchType, searchKeyword, {
|
||||||
|
keepPreviousData: true,
|
||||||
|
enabled: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle query change.
|
||||||
|
const handleQueryChange = (query) => {
|
||||||
|
setSearchKeyword(query);
|
||||||
|
};
|
||||||
|
// Handle search type change.
|
||||||
|
const handleSearchTypeChange = (searchType) => {
|
||||||
|
remove();
|
||||||
|
setSearchType(searchType.key);
|
||||||
|
|
||||||
|
if (searchKeyword && searchType) {
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Handle overlay of universal search close.
|
||||||
|
const handleClose = () => {
|
||||||
|
closeGlobalSearch();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle universal search item select.
|
||||||
|
const handleItemSelect = (item) => {
|
||||||
|
setSelectedItemUniversalSearch(searchType, item.id);
|
||||||
|
closeGlobalSearch();
|
||||||
|
setSearchKeyword('');
|
||||||
|
};
|
||||||
|
const debounceFetch = React.useRef(
|
||||||
|
debounce(() => {
|
||||||
|
refetch();
|
||||||
|
}, 200),
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (searchKeyword) {
|
||||||
|
debounceFetch.current();
|
||||||
|
}
|
||||||
|
}, [searchKeyword]);
|
||||||
|
|
||||||
|
// Handles the overlay once be closed.
|
||||||
|
const handleOverlayClosed = () => {
|
||||||
|
setSearchKeyword('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchTypeOptions = React.useMemo(
|
||||||
|
() => getUniversalSearchTypeOptions(),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="dashboard__universal-search">
|
||||||
|
<UniversalSearch
|
||||||
|
isOpen={globalSearchShow}
|
||||||
|
isLoading={isSearchFetching}
|
||||||
|
items={data}
|
||||||
|
overlayProps={{
|
||||||
|
onClose: handleClose,
|
||||||
|
onClosed: handleOverlayClosed,
|
||||||
|
}}
|
||||||
|
searchResource={searchType}
|
||||||
|
onQueryChange={handleQueryChange}
|
||||||
|
onSearchTypeChange={handleSearchTypeChange}
|
||||||
|
onItemSelect={handleItemSelect}
|
||||||
|
itemRenderer={DashboardUniversalSearchItem}
|
||||||
|
query={searchKeyword}
|
||||||
|
searchTypeOptions={searchTypeOptions}
|
||||||
|
/>
|
||||||
|
<DashboardUniversalSearchItemActions />
|
||||||
|
<DashboardUniversalSearchHotkeys />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withUniversalSearchActions,
|
||||||
|
withUniversalSearch(({ globalSearchShow, defaultUniversalResourceType }) => ({
|
||||||
|
globalSearchShow,
|
||||||
|
defaultUniversalResourceType,
|
||||||
|
})),
|
||||||
|
)(DashboardUniversalSearch);
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { universalSearchInvoiceBind } from '../Sales/Invoices/InvoiceUniversalSearch';
|
||||||
|
import { universalSearchReceiptBind } from '../Sales/Receipts/ReceiptUniversalSearch';
|
||||||
|
import { universalSearchBillBind } from '../Purchases/Bills/BillUniversalSearch';
|
||||||
|
import { universalSearchEstimateBind } from '../Sales/Estimates/EstimatesLanding/EstimateUniversalSearch';
|
||||||
|
import { universalSearchPaymentReceiveBind } from '../Sales/PaymentReceives/PaymentReceiveUniversalSearch';
|
||||||
|
import { universalSearchPaymentMadeBind } from '../Purchases/PaymentMades/PaymentMadeUniversalSearch';
|
||||||
|
import { universalSearchItemBind } from '../Items/ItemsUniversalSearch';
|
||||||
|
import { universalSearchCustomerBind } from '../Customers/CustomersUniversalSearch';
|
||||||
|
import { universalSearchJournalBind } from '../Accounting/ManualJournalUniversalSearch';
|
||||||
|
import { universalSearchAccountBind } from '../Accounts/AccountUniversalSearch';
|
||||||
|
import { universalSearchVendorBind } from '../Vendors/VendorsUniversalSearch';
|
||||||
|
|
||||||
|
// Universal search binds.
|
||||||
|
export const universalSearchBinds = [
|
||||||
|
universalSearchItemBind,
|
||||||
|
universalSearchAccountBind,
|
||||||
|
universalSearchInvoiceBind,
|
||||||
|
universalSearchReceiptBind,
|
||||||
|
universalSearchEstimateBind,
|
||||||
|
universalSearchBillBind,
|
||||||
|
universalSearchPaymentReceiveBind,
|
||||||
|
universalSearchPaymentMadeBind,
|
||||||
|
universalSearchCustomerBind,
|
||||||
|
universalSearchVendorBind,
|
||||||
|
universalSearchJournalBind,
|
||||||
|
];
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import * as R from 'ramda';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
|
||||||
|
import withUniversalSearchActions from './withUniversalSearchActions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Universal search hotkey.
|
||||||
|
*/
|
||||||
|
function DashboardUniversalSearchHotkey({
|
||||||
|
openGlobalSearch,
|
||||||
|
}) {
|
||||||
|
useHotkeys('ctrl+o', (event, handle) => {
|
||||||
|
openGlobalSearch();
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default R.compose(
|
||||||
|
withUniversalSearchActions
|
||||||
|
)(DashboardUniversalSearchHotkey);
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
|
||||||
|
import withUniversalSearch from './withUniversalSearch';
|
||||||
|
|
||||||
|
import { getUniversalSearchItemsActions } from './utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Universal search selected item action based on each resource type.
|
||||||
|
*/
|
||||||
|
function DashboardUniversalSearchItemActions({
|
||||||
|
searchSelectedResourceType,
|
||||||
|
searchSelectedResourceId,
|
||||||
|
}) {
|
||||||
|
const components = getUniversalSearchItemsActions();
|
||||||
|
|
||||||
|
return components.map((COMPONENT) => (
|
||||||
|
<COMPONENT
|
||||||
|
resourceId={searchSelectedResourceId}
|
||||||
|
resourceType={searchSelectedResourceType}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
export default R.compose(
|
||||||
|
withUniversalSearch(
|
||||||
|
({ searchSelectedResourceType, searchSelectedResourceId }) => ({
|
||||||
|
searchSelectedResourceType,
|
||||||
|
searchSelectedResourceId,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)(DashboardUniversalSearchItemActions);
|
||||||
44
client/src/containers/UniversalSearch/components.js
Normal file
44
client/src/containers/UniversalSearch/components.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { MenuItem } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
import { highlightText } from 'utils';
|
||||||
|
import { getUniversalSearchBind } from './utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default univesal search item component.
|
||||||
|
*/
|
||||||
|
function UniversalSearchItemDetail(item, { handleClick, modifiers, query }) {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
active={modifiers.active}
|
||||||
|
disabled={modifiers.disabled}
|
||||||
|
text={
|
||||||
|
<div>
|
||||||
|
<div>{highlightText(item.text, query)}</div>
|
||||||
|
|
||||||
|
{item.subText && (
|
||||||
|
<span class="bp3-text-muted">
|
||||||
|
{highlightText(item.subText, query)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
label={item.label ? highlightText(item.label, query) : ''}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} props
|
||||||
|
* @param {*} actions
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const DashboardUniversalSearchItem = (props, actions) => {
|
||||||
|
const itemRenderer = getUniversalSearchBind(props._type, 'itemRenderer');
|
||||||
|
|
||||||
|
return typeof itemRenderer !== 'undefined'
|
||||||
|
? itemRenderer(props, actions)
|
||||||
|
: UniversalSearchItemDetail(props, actions);
|
||||||
|
};
|
||||||
44
client/src/containers/UniversalSearch/utils.js
Normal file
44
client/src/containers/UniversalSearch/utils.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { get } from 'lodash';
|
||||||
|
import { universalSearchBinds } from './DashboardUniversalSearchBinds';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getUniversalSearchBinds = () => {
|
||||||
|
return universalSearchBinds.map((binder) => binder());
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} resourceType
|
||||||
|
* @param {*} key
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getUniversalSearchBind = (resourceType, key) => {
|
||||||
|
const resourceConfig = getUniversalSearchBinds().find(
|
||||||
|
(meta) => meta.resourceType === resourceType,
|
||||||
|
);
|
||||||
|
return key ? get(resourceConfig, key) : resourceConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getUniversalSearchTypeOptions = () => {
|
||||||
|
return getUniversalSearchBinds().map((bind) => ({
|
||||||
|
key: bind.resourceType,
|
||||||
|
label: bind.optionItemLabel,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getUniversalSearchItemsActions = () => {
|
||||||
|
return getUniversalSearchBinds()
|
||||||
|
.filter((bind) => bind.selectItemAction)
|
||||||
|
.map((bind) => bind.selectItemAction);
|
||||||
|
}
|
||||||
18
client/src/containers/UniversalSearch/withUniversalSearch.js
Normal file
18
client/src/containers/UniversalSearch/withUniversalSearch.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
export default (mapState) => {
|
||||||
|
const mapStateToProps = (state, props) => {
|
||||||
|
const { globalSearch } = state;
|
||||||
|
|
||||||
|
const mapped = {
|
||||||
|
globalSearchShow: globalSearch.isOpen,
|
||||||
|
defaultUniversalResourceType: globalSearch.defaultResourceType,
|
||||||
|
|
||||||
|
searchSelectedResourceType: globalSearch.selectedItem.resourceType,
|
||||||
|
searchSelectedResourceId: globalSearch.selectedItem.resourceId,
|
||||||
|
};
|
||||||
|
return mapState ? mapState(mapped, state, props) : mapped;
|
||||||
|
};
|
||||||
|
|
||||||
|
return connect(mapStateToProps);
|
||||||
|
};
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
import t from 'store/types';
|
||||||
|
import {
|
||||||
|
universalSearchResetResourceType,
|
||||||
|
universalSearchSetResourceType,
|
||||||
|
universalSearchSetSelectedItem,
|
||||||
|
universalSearchResetSelectedItem
|
||||||
|
} from '../../store/search/search.actions';
|
||||||
|
|
||||||
|
export const mapDispatchToProps = (dispatch) => ({
|
||||||
|
openGlobalSearch: () => dispatch({ type: t.OPEN_SEARCH }),
|
||||||
|
closeGlobalSearch: () => dispatch({ type: t.CLOSE_SEARCH }),
|
||||||
|
|
||||||
|
setResourceTypeUniversalSearch: (resourceType) =>
|
||||||
|
dispatch(universalSearchSetResourceType(resourceType)),
|
||||||
|
|
||||||
|
resetResourceTypeUniversalSearch: () =>
|
||||||
|
dispatch(universalSearchResetResourceType()),
|
||||||
|
|
||||||
|
setSelectedItemUniversalSearch: (resourceType, resourceId) =>
|
||||||
|
dispatch(universalSearchSetSelectedItem(resourceType, resourceId)),
|
||||||
|
|
||||||
|
resetSelectedItemUniversalSearch: () =>
|
||||||
|
dispatch(universalSearchResetSelectedItem()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps);
|
||||||
31
client/src/containers/Vendors/VendorsUniversalSearch.js
Normal file
31
client/src/containers/Vendors/VendorsUniversalSearch.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { RESOURCES_TYPES } from '../../common/resourcesTypes';
|
||||||
|
import withDrawerActions from '../Drawer/withDrawerActions';
|
||||||
|
|
||||||
|
function VendorUniversalSearchSelectComponent({ resourceType, resourceId }) {
|
||||||
|
if (resourceType === RESOURCES_TYPES.VENDOR) {
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VendorUniversalSearchSelectAction = withDrawerActions(
|
||||||
|
VendorUniversalSearchSelectComponent
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes vendor resource item to search.
|
||||||
|
*/
|
||||||
|
const vendorToSearch = (contact) => ({
|
||||||
|
text: contact.display_name,
|
||||||
|
label: contact.balance > 0 ? contact.formatted_balance + '' : '',
|
||||||
|
reference: contact,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds universal search invoice configure.
|
||||||
|
*/
|
||||||
|
export const universalSearchVendorBind = () => ({
|
||||||
|
resourceType: RESOURCES_TYPES.VENDOR,
|
||||||
|
optionItemLabel: 'Vendor',
|
||||||
|
selectItemAction: VendorUniversalSearchSelectAction,
|
||||||
|
itemSelect: vendorToSearch,
|
||||||
|
});
|
||||||
131
client/src/hooks/query/GenericResource/index.js
Normal file
131
client/src/hooks/query/GenericResource/index.js
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import { useRequestQuery } from '../../useQueryRequest';
|
||||||
|
import { RESOURCES_TYPES } from 'common/resourcesTypes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} type
|
||||||
|
* @param {string} searchKeyword
|
||||||
|
* @param {*} query
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function useResourceData(type, query, props) {
|
||||||
|
const url = getResourceUrlFromType(type);
|
||||||
|
|
||||||
|
return useRequestQuery(
|
||||||
|
['UNIVERSAL_SEARCH', type, query],
|
||||||
|
{ method: 'get', url, params: query },
|
||||||
|
{
|
||||||
|
select: transformResourceData(type),
|
||||||
|
defaultData: {
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the resource url by the given resource type.
|
||||||
|
* @param {string} type
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function getResourceUrlFromType(type) {
|
||||||
|
const config = {
|
||||||
|
[RESOURCES_TYPES.INVOICE]: '/sales/invoices',
|
||||||
|
[RESOURCES_TYPES.ESTIMATE]: '/sales/estimates',
|
||||||
|
[RESOURCES_TYPES.ITEM]: '/items',
|
||||||
|
[RESOURCES_TYPES.RECEIPT]: '/sales/receipts',
|
||||||
|
[RESOURCES_TYPES.BILL]: '/purchases/bills',
|
||||||
|
[RESOURCES_TYPES.PAYMENT_RECEIVE]: '/sales/payment_receives',
|
||||||
|
[RESOURCES_TYPES.PAYMENT_MADE]: '/purchases/bill_payments',
|
||||||
|
[RESOURCES_TYPES.CUSTOMER]: '/customers',
|
||||||
|
[RESOURCES_TYPES.VENDOR]: '/vendors',
|
||||||
|
[RESOURCES_TYPES.MANUAL_JOURNAL]: '/manual-journals',
|
||||||
|
[RESOURCES_TYPES.ACCOUNT]: '/accounts',
|
||||||
|
};
|
||||||
|
return config[type] || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes invoices to resource data.
|
||||||
|
*/
|
||||||
|
const transformInvoices = (response) => ({
|
||||||
|
items: response.data.sales_invoices,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes items to resource data.
|
||||||
|
*/
|
||||||
|
const transformItems = (response) => ({
|
||||||
|
items: response.data.items,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes payment receives to resource data.
|
||||||
|
*/
|
||||||
|
const transformPaymentReceives = (response) => ({
|
||||||
|
items: response.data.payment_receives,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes customers to resoruce data.
|
||||||
|
*/
|
||||||
|
const transformCustomers = (response) => ({
|
||||||
|
items: response.data.customers,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes customers to resoruce data.
|
||||||
|
*/
|
||||||
|
const transformVendors = (response) => ({
|
||||||
|
items: response.data.vendors,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const transformPaymentMades = (response) => ({
|
||||||
|
items: response.data.bill_payments,
|
||||||
|
});
|
||||||
|
|
||||||
|
const transformSaleReceipts = (response) => ({
|
||||||
|
items: response.data.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
const transformBills = (response) => ({
|
||||||
|
items: response.data.bills,
|
||||||
|
});
|
||||||
|
|
||||||
|
const transformManualJournals = (response) => ({
|
||||||
|
items: response.data.manual_journals,
|
||||||
|
});
|
||||||
|
|
||||||
|
const transformsEstimates = (response) => ({
|
||||||
|
items: response.data.sales_estimates,
|
||||||
|
});
|
||||||
|
|
||||||
|
const transformAccounts = (response) => ({
|
||||||
|
items: response.data.accounts,
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines the transformer based on the given resource type.
|
||||||
|
* @param {string} type - Resource type.
|
||||||
|
*/
|
||||||
|
const transformResourceData = (type) => (response) => {
|
||||||
|
const pairs = {
|
||||||
|
[RESOURCES_TYPES.ESTIMATE]: transformsEstimates,
|
||||||
|
[RESOURCES_TYPES.INVOICE]: transformInvoices,
|
||||||
|
[RESOURCES_TYPES.RECEIPT]: transformSaleReceipts,
|
||||||
|
[RESOURCES_TYPES.ITEM]: transformItems,
|
||||||
|
[RESOURCES_TYPES.PAYMENT_RECEIVE]: transformPaymentReceives,
|
||||||
|
[RESOURCES_TYPES.PAYMENT_MADE]: transformPaymentMades,
|
||||||
|
[RESOURCES_TYPES.CUSTOMER]: transformCustomers,
|
||||||
|
[RESOURCES_TYPES.VENDOR]: transformVendors,
|
||||||
|
[RESOURCES_TYPES.BILL]: transformBills,
|
||||||
|
[RESOURCES_TYPES.MANUAL_JOURNAL]: transformManualJournals,
|
||||||
|
[RESOURCES_TYPES.ACCOUNT]: transformAccounts
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
...pairs[type](response),
|
||||||
|
_type: type,
|
||||||
|
};
|
||||||
|
};
|
||||||
41
client/src/hooks/query/UniversalSearch/UniversalSearch.js
Normal file
41
client/src/hooks/query/UniversalSearch/UniversalSearch.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { getUniversalSearchBind } from '../../../containers/UniversalSearch/utils';
|
||||||
|
import { useResourceData } from '../GenericResource';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes the resource data to search entries based on
|
||||||
|
* the given resource type.
|
||||||
|
* @param {string} type
|
||||||
|
* @param {any} resource
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function transfromResourceDataToSearch(resource) {
|
||||||
|
const selectItem = getUniversalSearchBind(resource._type, 'itemSelect');
|
||||||
|
|
||||||
|
return resource.items
|
||||||
|
.map((item) => ({
|
||||||
|
...selectItem ? selectItem(item) : {},
|
||||||
|
_type: resource._type,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} type
|
||||||
|
* @param {*} searchKeyword
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function useUniversalSearch(type, searchKeyword, props) {
|
||||||
|
const { data, ...restProps } = useResourceData(
|
||||||
|
type,
|
||||||
|
{
|
||||||
|
search_keyword: searchKeyword,
|
||||||
|
},
|
||||||
|
props,
|
||||||
|
);
|
||||||
|
const searchData = transfromResourceDataToSearch(data);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: searchData,
|
||||||
|
...restProps,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -24,3 +24,5 @@ export * from './contacts';
|
|||||||
export * from './subscriptions';
|
export * from './subscriptions';
|
||||||
export * from './organization';
|
export * from './organization';
|
||||||
export * from './landedCost';
|
export * from './landedCost';
|
||||||
|
export * from './UniversalSearch/UniversalSearch';
|
||||||
|
export * from './GenericResource';
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export function useCloseReceipt(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const transformReceipts = (res) => ({
|
const transformReceipts = (res) => ({
|
||||||
receipts: res.data.sale_receipts,
|
receipts: res.data.data,
|
||||||
pagination: transformPagination(res.data.pagination),
|
pagination: transformPagination(res.data.pagination),
|
||||||
filterMeta: res.data.filter_meta,
|
filterMeta: res.data.filter_meta,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1202,6 +1202,7 @@
|
|||||||
"universal_search.enter_text": "To select",
|
"universal_search.enter_text": "To select",
|
||||||
"universal_search.close_text": "To close",
|
"universal_search.close_text": "To close",
|
||||||
"universal_seach.navigate_text": "To navigate",
|
"universal_seach.navigate_text": "To navigate",
|
||||||
|
"universal_search.loading": "Loading...",
|
||||||
"pdf_preview.dialog.title": "PDF Preview",
|
"pdf_preview.dialog.title": "PDF Preview",
|
||||||
"pdf_preview.download.button": "Download",
|
"pdf_preview.download.button": "Download",
|
||||||
"pdf_preview.preview.button": "Preview",
|
"pdf_preview.preview.button": "Preview",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
import React, { lazy } from 'react';
|
import { lazy } from 'react';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
import { RESOURCES_TYPES } from '../common/resourcesTypes';
|
||||||
|
|
||||||
// const BASE_URL = '/dashboard';
|
// const BASE_URL = '/dashboard';
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ export const getDashboardRoutes = () => [
|
|||||||
breadcrumb: intl.get('accounts_chart'),
|
breadcrumb: intl.get('accounts_chart'),
|
||||||
hotkey: 'shift+a',
|
hotkey: 'shift+a',
|
||||||
pageTitle: intl.get('accounts_chart'),
|
pageTitle: intl.get('accounts_chart'),
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.ACCOUNT,
|
||||||
},
|
},
|
||||||
// Accounting.
|
// Accounting.
|
||||||
{
|
{
|
||||||
@@ -24,6 +26,7 @@ export const getDashboardRoutes = () => [
|
|||||||
pageTitle: intl.get('new_journal'),
|
pageTitle: intl.get('new_journal'),
|
||||||
sidebarExpand: false,
|
sidebarExpand: false,
|
||||||
backLink: true,
|
backLink: true,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.MANUAL_JOURNAL,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/manual-journals/:id/edit`,
|
path: `/manual-journals/:id/edit`,
|
||||||
@@ -34,6 +37,7 @@ export const getDashboardRoutes = () => [
|
|||||||
pageTitle: intl.get('edit_journal'),
|
pageTitle: intl.get('edit_journal'),
|
||||||
sidebarExpand: false,
|
sidebarExpand: false,
|
||||||
backLink: true,
|
backLink: true,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.MANUAL_JOURNAL,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/manual-journals`,
|
path: `/manual-journals`,
|
||||||
@@ -43,6 +47,7 @@ export const getDashboardRoutes = () => [
|
|||||||
breadcrumb: intl.get('manual_journals'),
|
breadcrumb: intl.get('manual_journals'),
|
||||||
hotkey: 'shift+m',
|
hotkey: 'shift+m',
|
||||||
pageTitle: intl.get('manual_journals'),
|
pageTitle: intl.get('manual_journals'),
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.MANUAL_JOURNAL,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/items/categories`,
|
path: `/items/categories`,
|
||||||
@@ -51,6 +56,7 @@ export const getDashboardRoutes = () => [
|
|||||||
),
|
),
|
||||||
breadcrumb: intl.get('categories'),
|
breadcrumb: intl.get('categories'),
|
||||||
pageTitle: intl.get('category_list'),
|
pageTitle: intl.get('category_list'),
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.ITEM,
|
||||||
},
|
},
|
||||||
// Items.
|
// Items.
|
||||||
{
|
{
|
||||||
@@ -60,6 +66,7 @@ export const getDashboardRoutes = () => [
|
|||||||
breadcrumb: intl.get('edit_item'),
|
breadcrumb: intl.get('edit_item'),
|
||||||
pageTitle: intl.get('edit_item'),
|
pageTitle: intl.get('edit_item'),
|
||||||
backLink: true,
|
backLink: true,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.ITEM,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/items/new?duplicate=/:id`,
|
path: `/items/new?duplicate=/:id`,
|
||||||
@@ -67,6 +74,7 @@ export const getDashboardRoutes = () => [
|
|||||||
loader: () => import('containers/Items/ItemFormPage'),
|
loader: () => import('containers/Items/ItemFormPage'),
|
||||||
}),
|
}),
|
||||||
breadcrumb: intl.get('duplicate_item'),
|
breadcrumb: intl.get('duplicate_item'),
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.ITEM,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/items/new`,
|
path: `/items/new`,
|
||||||
@@ -76,6 +84,7 @@ export const getDashboardRoutes = () => [
|
|||||||
hotkey: 'ctrl+shift+w',
|
hotkey: 'ctrl+shift+w',
|
||||||
pageTitle: intl.get('new_item'),
|
pageTitle: intl.get('new_item'),
|
||||||
backLink: true,
|
backLink: true,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.ITEM,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/items`,
|
path: `/items`,
|
||||||
@@ -83,6 +92,7 @@ export const getDashboardRoutes = () => [
|
|||||||
breadcrumb: intl.get('items'),
|
breadcrumb: intl.get('items'),
|
||||||
hotkey: 'shift+w',
|
hotkey: 'shift+w',
|
||||||
pageTitle: intl.get('items_list'),
|
pageTitle: intl.get('items_list'),
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.ITEM,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Inventory adjustments.
|
// Inventory adjustments.
|
||||||
@@ -93,6 +103,7 @@ export const getDashboardRoutes = () => [
|
|||||||
),
|
),
|
||||||
breadcrumb: intl.get('inventory_adjustments'),
|
breadcrumb: intl.get('inventory_adjustments'),
|
||||||
pageTitle: intl.get('inventory_adjustment_list'),
|
pageTitle: intl.get('inventory_adjustment_list'),
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.ITEM,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Financial Reports.
|
// Financial Reports.
|
||||||
@@ -107,6 +118,7 @@ export const getDashboardRoutes = () => [
|
|||||||
pageTitle: intl.get('general_ledger'),
|
pageTitle: intl.get('general_ledger'),
|
||||||
backLink: true,
|
backLink: true,
|
||||||
sidebarExpand: false,
|
sidebarExpand: false,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.INVENTORY_ADJUSTMENT,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/financial-reports/balance-sheet`,
|
path: `/financial-reports/balance-sheet`,
|
||||||
@@ -323,6 +335,7 @@ export const getDashboardRoutes = () => [
|
|||||||
pageTitle: intl.get('new_expense'),
|
pageTitle: intl.get('new_expense'),
|
||||||
sidebarExpand: false,
|
sidebarExpand: false,
|
||||||
backLink: true,
|
backLink: true,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.EXPENSE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/expenses/:id/edit`,
|
path: `/expenses/:id/edit`,
|
||||||
@@ -333,6 +346,7 @@ export const getDashboardRoutes = () => [
|
|||||||
pageTitle: intl.get('edit_expense'),
|
pageTitle: intl.get('edit_expense'),
|
||||||
sidebarExpand: false,
|
sidebarExpand: false,
|
||||||
backLink: true,
|
backLink: true,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.EXPENSE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/expenses`,
|
path: `/expenses`,
|
||||||
@@ -342,6 +356,7 @@ export const getDashboardRoutes = () => [
|
|||||||
breadcrumb: intl.get('expenses_list'),
|
breadcrumb: intl.get('expenses_list'),
|
||||||
pageTitle: intl.get('expenses_list'),
|
pageTitle: intl.get('expenses_list'),
|
||||||
hotkey: 'shift+x',
|
hotkey: 'shift+x',
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.EXPENSE,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Customers
|
// Customers
|
||||||
@@ -354,6 +369,7 @@ export const getDashboardRoutes = () => [
|
|||||||
breadcrumb: intl.get('edit_customer'),
|
breadcrumb: intl.get('edit_customer'),
|
||||||
pageTitle: intl.get('edit_customer'),
|
pageTitle: intl.get('edit_customer'),
|
||||||
backLink: true,
|
backLink: true,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.CUSTOMER,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/customers/new`,
|
path: `/customers/new`,
|
||||||
@@ -365,6 +381,7 @@ export const getDashboardRoutes = () => [
|
|||||||
hotkey: 'ctrl+shift+c',
|
hotkey: 'ctrl+shift+c',
|
||||||
pageTitle: intl.get('new_customer'),
|
pageTitle: intl.get('new_customer'),
|
||||||
backLink: true,
|
backLink: true,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.CUSTOMER,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/customers`,
|
path: `/customers`,
|
||||||
@@ -374,6 +391,7 @@ export const getDashboardRoutes = () => [
|
|||||||
breadcrumb: intl.get('customers'),
|
breadcrumb: intl.get('customers'),
|
||||||
hotkey: 'shift+c',
|
hotkey: 'shift+c',
|
||||||
pageTitle: intl.get('customers_list'),
|
pageTitle: intl.get('customers_list'),
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.CUSTOMER,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/customers/contact_duplicate=/:id`,
|
path: `/customers/contact_duplicate=/:id`,
|
||||||
@@ -384,6 +402,7 @@ export const getDashboardRoutes = () => [
|
|||||||
breadcrumb: intl.get('duplicate_customer'),
|
breadcrumb: intl.get('duplicate_customer'),
|
||||||
pageTitle: intl.get('new_customer'),
|
pageTitle: intl.get('new_customer'),
|
||||||
backLink: true,
|
backLink: true,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.CUSTOMER,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Vendors
|
// Vendors
|
||||||
@@ -396,6 +415,7 @@ export const getDashboardRoutes = () => [
|
|||||||
breadcrumb: intl.get('edit_vendor'),
|
breadcrumb: intl.get('edit_vendor'),
|
||||||
pageTitle: intl.get('edit_vendor'),
|
pageTitle: intl.get('edit_vendor'),
|
||||||
backLink: true,
|
backLink: true,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.VENDOR,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/vendors/new`,
|
path: `/vendors/new`,
|
||||||
@@ -407,6 +427,7 @@ export const getDashboardRoutes = () => [
|
|||||||
hotkey: 'ctrl+shift+v',
|
hotkey: 'ctrl+shift+v',
|
||||||
pageTitle: intl.get('new_vendor'),
|
pageTitle: intl.get('new_vendor'),
|
||||||
backLink: true,
|
backLink: true,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.VENDOR,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/vendors`,
|
path: `/vendors`,
|
||||||
@@ -416,6 +437,7 @@ export const getDashboardRoutes = () => [
|
|||||||
breadcrumb: intl.get('vendors'),
|
breadcrumb: intl.get('vendors'),
|
||||||
hotkey: 'shift+v',
|
hotkey: 'shift+v',
|
||||||
pageTitle: intl.get('vendors_list'),
|
pageTitle: intl.get('vendors_list'),
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.VENDOR,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/vendors/contact_duplicate=/:id`,
|
path: `/vendors/contact_duplicate=/:id`,
|
||||||
@@ -426,6 +448,7 @@ export const getDashboardRoutes = () => [
|
|||||||
breadcrumb: intl.get('duplicate_vendor'),
|
breadcrumb: intl.get('duplicate_vendor'),
|
||||||
pageTitle: intl.get('new_vendor'),
|
pageTitle: intl.get('new_vendor'),
|
||||||
backLink: true,
|
backLink: true,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.VENDOR,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Estimates
|
// Estimates
|
||||||
@@ -439,6 +462,7 @@ export const getDashboardRoutes = () => [
|
|||||||
pageTitle: intl.get('edit_estimate'),
|
pageTitle: intl.get('edit_estimate'),
|
||||||
backLink: true,
|
backLink: true,
|
||||||
sidebarExpand: false,
|
sidebarExpand: false,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.ESTIMATE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/invoices/new?from_estimate_id=/:id`,
|
path: `/invoices/new?from_estimate_id=/:id`,
|
||||||
@@ -450,6 +474,7 @@ export const getDashboardRoutes = () => [
|
|||||||
pageTitle: intl.get('new_estimate'),
|
pageTitle: intl.get('new_estimate'),
|
||||||
backLink: true,
|
backLink: true,
|
||||||
sidebarExpand: false,
|
sidebarExpand: false,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.INVOICE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/estimates/new`,
|
path: `/estimates/new`,
|
||||||
@@ -462,6 +487,7 @@ export const getDashboardRoutes = () => [
|
|||||||
pageTitle: intl.get('new_estimate'),
|
pageTitle: intl.get('new_estimate'),
|
||||||
backLink: true,
|
backLink: true,
|
||||||
sidebarExpand: false,
|
sidebarExpand: false,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.ESTIMATE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/estimates`,
|
path: `/estimates`,
|
||||||
@@ -472,6 +498,7 @@ export const getDashboardRoutes = () => [
|
|||||||
breadcrumb: intl.get('estimates_list'),
|
breadcrumb: intl.get('estimates_list'),
|
||||||
hotkey: 'shift+e',
|
hotkey: 'shift+e',
|
||||||
pageTitle: intl.get('estimates_list'),
|
pageTitle: intl.get('estimates_list'),
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.ESTIMATE,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Invoices.
|
// Invoices.
|
||||||
@@ -485,6 +512,7 @@ export const getDashboardRoutes = () => [
|
|||||||
pageTitle: intl.get('edit_invoice'),
|
pageTitle: intl.get('edit_invoice'),
|
||||||
sidebarExpand: false,
|
sidebarExpand: false,
|
||||||
backLink: true,
|
backLink: true,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.INVOICE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/invoices/new`,
|
path: `/invoices/new`,
|
||||||
@@ -497,6 +525,7 @@ export const getDashboardRoutes = () => [
|
|||||||
pageTitle: intl.get('new_invoice'),
|
pageTitle: intl.get('new_invoice'),
|
||||||
sidebarExpand: false,
|
sidebarExpand: false,
|
||||||
backLink: true,
|
backLink: true,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.INVOICE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/invoices`,
|
path: `/invoices`,
|
||||||
@@ -506,6 +535,7 @@ export const getDashboardRoutes = () => [
|
|||||||
breadcrumb: intl.get('invoices_list'),
|
breadcrumb: intl.get('invoices_list'),
|
||||||
hotkey: 'shift+i',
|
hotkey: 'shift+i',
|
||||||
pageTitle: intl.get('invoices_list'),
|
pageTitle: intl.get('invoices_list'),
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.INVOICE,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Sales Receipts.
|
// Sales Receipts.
|
||||||
@@ -519,6 +549,7 @@ export const getDashboardRoutes = () => [
|
|||||||
pageTitle: intl.get('edit_receipt'),
|
pageTitle: intl.get('edit_receipt'),
|
||||||
backLink: true,
|
backLink: true,
|
||||||
sidebarExpand: false,
|
sidebarExpand: false,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.RECEIPT,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/receipts/new`,
|
path: `/receipts/new`,
|
||||||
@@ -531,6 +562,7 @@ export const getDashboardRoutes = () => [
|
|||||||
pageTitle: intl.get('new_receipt'),
|
pageTitle: intl.get('new_receipt'),
|
||||||
backLink: true,
|
backLink: true,
|
||||||
sidebarExpand: false,
|
sidebarExpand: false,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.RECEIPT,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/receipts`,
|
path: `/receipts`,
|
||||||
@@ -540,6 +572,7 @@ export const getDashboardRoutes = () => [
|
|||||||
breadcrumb: intl.get('receipts_list'),
|
breadcrumb: intl.get('receipts_list'),
|
||||||
hotkey: 'shift+r',
|
hotkey: 'shift+r',
|
||||||
pageTitle: intl.get('receipts_list'),
|
pageTitle: intl.get('receipts_list'),
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.RECEIPT,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Payment receives
|
// Payment receives
|
||||||
@@ -555,6 +588,7 @@ export const getDashboardRoutes = () => [
|
|||||||
pageTitle: intl.get('edit_payment_receive'),
|
pageTitle: intl.get('edit_payment_receive'),
|
||||||
backLink: true,
|
backLink: true,
|
||||||
sidebarExpand: false,
|
sidebarExpand: false,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.PAYMENT_RECEIVE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/payment-receives/new`,
|
path: `/payment-receives/new`,
|
||||||
@@ -568,6 +602,7 @@ export const getDashboardRoutes = () => [
|
|||||||
pageTitle: intl.get('new_payment_receive'),
|
pageTitle: intl.get('new_payment_receive'),
|
||||||
backLink: true,
|
backLink: true,
|
||||||
sidebarExpand: false,
|
sidebarExpand: false,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.PAYMENT_RECEIVE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/payment-receives`,
|
path: `/payment-receives`,
|
||||||
@@ -578,6 +613,7 @@ export const getDashboardRoutes = () => [
|
|||||||
),
|
),
|
||||||
breadcrumb: intl.get('payment_receives_list'),
|
breadcrumb: intl.get('payment_receives_list'),
|
||||||
pageTitle: intl.get('payment_receives_list'),
|
pageTitle: intl.get('payment_receives_list'),
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.PAYMENT_RECEIVE,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Bills
|
// Bills
|
||||||
@@ -591,6 +627,7 @@ export const getDashboardRoutes = () => [
|
|||||||
pageTitle: intl.get('edit_bill'),
|
pageTitle: intl.get('edit_bill'),
|
||||||
sidebarExpand: false,
|
sidebarExpand: false,
|
||||||
backLink: true,
|
backLink: true,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.BILL,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/bills/new`,
|
path: `/bills/new`,
|
||||||
@@ -603,6 +640,7 @@ export const getDashboardRoutes = () => [
|
|||||||
pageTitle: intl.get('new_bill'),
|
pageTitle: intl.get('new_bill'),
|
||||||
sidebarExpand: false,
|
sidebarExpand: false,
|
||||||
backLink: true,
|
backLink: true,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.BILL,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/bills`,
|
path: `/bills`,
|
||||||
@@ -612,6 +650,7 @@ export const getDashboardRoutes = () => [
|
|||||||
breadcrumb: intl.get('bills_list'),
|
breadcrumb: intl.get('bills_list'),
|
||||||
hotkey: 'shift+b',
|
hotkey: 'shift+b',
|
||||||
pageTitle: intl.get('bills_list'),
|
pageTitle: intl.get('bills_list'),
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.BILL,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Subscription billing.
|
// Subscription billing.
|
||||||
@@ -633,6 +672,7 @@ export const getDashboardRoutes = () => [
|
|||||||
pageTitle: intl.get('edit_payment_made'),
|
pageTitle: intl.get('edit_payment_made'),
|
||||||
sidebarExpand: false,
|
sidebarExpand: false,
|
||||||
backLink: true,
|
backLink: true,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.PAYMENT_MADE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/payment-mades/new`,
|
path: `/payment-mades/new`,
|
||||||
@@ -646,6 +686,7 @@ export const getDashboardRoutes = () => [
|
|||||||
pageTitle: intl.get('new_payment_made'),
|
pageTitle: intl.get('new_payment_made'),
|
||||||
sidebarExpand: false,
|
sidebarExpand: false,
|
||||||
backLink: true,
|
backLink: true,
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.PAYMENT_MADE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/payment-mades`,
|
path: `/payment-mades`,
|
||||||
@@ -656,6 +697,7 @@ export const getDashboardRoutes = () => [
|
|||||||
),
|
),
|
||||||
breadcrumb: intl.get('payment_made_list'),
|
breadcrumb: intl.get('payment_made_list'),
|
||||||
pageTitle: intl.get('payment_made_list'),
|
pageTitle: intl.get('payment_made_list'),
|
||||||
|
defaultSearchResource: RESOURCES_TYPES.PAYMENT_MADE,
|
||||||
},
|
},
|
||||||
// Homepage
|
// Homepage
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -444,4 +444,28 @@ export default {
|
|||||||
],
|
],
|
||||||
viewBox: '0 0 24 24',
|
viewBox: '0 0 24 24',
|
||||||
},
|
},
|
||||||
|
'universal-search': {
|
||||||
|
path: [
|
||||||
|
'M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z'
|
||||||
|
],
|
||||||
|
viewBox: '0 0 20 20',
|
||||||
|
},
|
||||||
|
"arrow-down-24": {
|
||||||
|
path: [
|
||||||
|
'M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z',
|
||||||
|
],
|
||||||
|
viewBox: '0 0 24 24',
|
||||||
|
},
|
||||||
|
"arrow-up-24": {
|
||||||
|
path: [
|
||||||
|
'M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z'
|
||||||
|
],
|
||||||
|
viewBox: '0 0 24 24',
|
||||||
|
},
|
||||||
|
"caret-right-16": {
|
||||||
|
path: [
|
||||||
|
'M11 8c0-.15-.07-.28-.17-.37l-4-3.5A.495.495 0 006 4.5v7a.495.495 0 00.83.37l4-3.5c.1-.09.17-.22.17-.37z'
|
||||||
|
],
|
||||||
|
viewBox: '0 0 16 16',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,3 +20,36 @@ export function generalSearch(name, result) {
|
|||||||
result,
|
result,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function universalSearchSetResourceType(resourceType) {
|
||||||
|
return {
|
||||||
|
type: t.UNIVERSAL_SEARCH_SET_RESOURCE_TYPE,
|
||||||
|
payload: {
|
||||||
|
resourceType,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function universalSearchResetResourceType() {
|
||||||
|
return {
|
||||||
|
type: t.UNIVERSAL_SEARCH_RESET_RESOURCE_TYPE,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function universalSearchSetSelectedItem(resourceType, resourceId) {
|
||||||
|
return {
|
||||||
|
type: t.UNIVERSAL_SEARCH_SET_ITEM_SELECT,
|
||||||
|
payload: {
|
||||||
|
resourceType,
|
||||||
|
resourceId
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function universalSearchResetSelectedItem() {
|
||||||
|
return {
|
||||||
|
type: t.UNIVERSAL_SEARCH_RESET_ITEM_SELECT,
|
||||||
|
payload: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
import t from 'store/types';
|
import t from 'store/types';
|
||||||
import { createReducer } from '@reduxjs/toolkit';
|
import { createReducer } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
const DEFAULT_RESOURCE_TYPE = 'customer';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
|
defaultResourceType: DEFAULT_RESOURCE_TYPE,
|
||||||
|
selectedItem: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default createReducer(initialState, {
|
export default createReducer(initialState, {
|
||||||
@@ -13,4 +17,23 @@ export default createReducer(initialState, {
|
|||||||
[t.CLOSE_SEARCH]: (state, action) => {
|
[t.CLOSE_SEARCH]: (state, action) => {
|
||||||
state.isOpen = false;
|
state.isOpen = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[t.UNIVERSAL_SEARCH_SET_RESOURCE_TYPE]: (state, action) => {
|
||||||
|
state.defaultResourceType = action.payload.resourceType;
|
||||||
|
},
|
||||||
|
|
||||||
|
[t.UNIVERSAL_SEARCH_RESET_RESOURCE_TYPE]: (state, action) => {
|
||||||
|
state.defaultResourceType = DEFAULT_RESOURCE_TYPE;
|
||||||
|
},
|
||||||
|
|
||||||
|
[t.UNIVERSAL_SEARCH_SET_ITEM_SELECT]: (state, action) => {
|
||||||
|
state.selectedItem = {
|
||||||
|
resourceId: action.payload.resourceId,
|
||||||
|
resourceType: action.payload.resourceType,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
[t.UNIVERSAL_SEARCH_RESET_ITEM_SELECT]: (state, action) => {
|
||||||
|
state.selectedItem = {};
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,4 +2,8 @@ export default {
|
|||||||
SEARCH_TYPE: 'SEARCH_TYPE',
|
SEARCH_TYPE: 'SEARCH_TYPE',
|
||||||
OPEN_SEARCH: 'OPEN_SEARCH',
|
OPEN_SEARCH: 'OPEN_SEARCH',
|
||||||
CLOSE_SEARCH: 'CLOSE_SEARCH',
|
CLOSE_SEARCH: 'CLOSE_SEARCH',
|
||||||
|
UNIVERSAL_SEARCH_SET_RESOURCE_TYPE: 'UNIVERSAL_SEARCH_SET_RESOURCE_TYPE',
|
||||||
|
UNIVERSAL_SEARCH_RESET_RESOURCE_TYPE: 'UNIVERSAL_SEARCH_RESET_RESOURCE_TYPE',
|
||||||
|
UNIVERSAL_SEARCH_SET_ITEM_SELECT: 'UNIVERSAL_SEARCH_SET_ITEM_SELECT',
|
||||||
|
UNIVERSAL_SEARCH_RESET_ITEM_SELECT: 'UNIVERSAL_SEARCH_RESET_ITEM_SELECT'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -226,16 +226,16 @@ html[lang^="ar"] {
|
|||||||
z-index: 9999999;
|
z-index: 9999999;
|
||||||
margin: 6px;
|
margin: 6px;
|
||||||
|
|
||||||
.bp3-button + .bp3-button{
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp3-button{
|
.bp3-button{
|
||||||
border-color: rgba(0, 0, 0, 0.25);
|
border-color: rgba(0, 0, 0, 0.25);
|
||||||
color: rgb(25, 32, 37);
|
color: rgb(25, 32, 37);
|
||||||
min-height: 30px;
|
min-height: 30px;
|
||||||
padding-left: 14px;
|
padding-left: 14px;
|
||||||
padding-right: 14px;
|
padding-right: 14px;
|
||||||
|
|
||||||
|
& + .bp3-button{
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
219
client/src/style/components/UniversalSearch.scss
Normal file
219
client/src/style/components/UniversalSearch.scss
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
.universal-search {
|
||||||
|
position: fixed;
|
||||||
|
filter: blur(0);
|
||||||
|
opacity: 1;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 3px;
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 1px rgba(16, 22, 26, .1),
|
||||||
|
0 4px 8px rgba(16, 22, 26, .2),
|
||||||
|
0 18px 46px 6px rgba(16, 22, 26, .2);
|
||||||
|
left: calc(50% - 250px);
|
||||||
|
top: 20vh;
|
||||||
|
width: 500px;
|
||||||
|
z-index: 20;
|
||||||
|
|
||||||
|
&.bp3-overlay-appear,
|
||||||
|
&.bp3-overlay-enter {
|
||||||
|
filter: blur(20px);
|
||||||
|
opacity: .2
|
||||||
|
}
|
||||||
|
|
||||||
|
&.bp3-overlay-appear-active,
|
||||||
|
&.bp3-overlay-enter-active {
|
||||||
|
filter: blur(0);
|
||||||
|
opacity: 1;
|
||||||
|
transition-delay: 0;
|
||||||
|
transition-duration: .2s;
|
||||||
|
transition-property: filter, opacity;
|
||||||
|
transition-timing-function: cubic-bezier(.4, 1, .75, .9)
|
||||||
|
}
|
||||||
|
|
||||||
|
&.bp3-overlay-exit {
|
||||||
|
filter: blur(0);
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
&.bp3-overlay-exit-active {
|
||||||
|
filter: blur(20px);
|
||||||
|
opacity: .2;
|
||||||
|
transition-delay: 0;
|
||||||
|
transition-duration: .2s;
|
||||||
|
transition-property: filter, opacity;
|
||||||
|
transition-timing-function: cubic-bezier(.4, 1, .75, .9)
|
||||||
|
}
|
||||||
|
|
||||||
|
&__omnibar {
|
||||||
|
|
||||||
|
.bp3-input-group {
|
||||||
|
|
||||||
|
.bp3-icon {
|
||||||
|
|
||||||
|
svg {
|
||||||
|
stroke: currentColor;
|
||||||
|
fill: none;
|
||||||
|
fill-rule: evenodd;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.bp3-input-group .bp3-input {
|
||||||
|
border: 0;
|
||||||
|
box-shadow: 0 0 0 0;
|
||||||
|
height: 50px;
|
||||||
|
line-height: 50px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-input-group.bp3-large .bp3-input:not(:first-child) {
|
||||||
|
padding-left: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-input {}
|
||||||
|
|
||||||
|
.bp3-input-group {
|
||||||
|
|
||||||
|
.bp3-icon {
|
||||||
|
margin: 16px;
|
||||||
|
color: #5c707f;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
stroke-width: 2;
|
||||||
|
--text-opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-menu {
|
||||||
|
border-top: 1px solid #d3dce2;
|
||||||
|
|
||||||
|
.bp3-menu-item {
|
||||||
|
.bp3-text-muted {
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
.bp3-icon {
|
||||||
|
color: #8499a7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.bp3-intent-primary{
|
||||||
|
|
||||||
|
&.bp3-active{
|
||||||
|
background-color: rgb(235, 241, 246);
|
||||||
|
color: #252b30;
|
||||||
|
|
||||||
|
.bp3-menu-item-label{
|
||||||
|
color: #5c7080;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&-label {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-input-action {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&__type-select-overlay {
|
||||||
|
|
||||||
|
.bp3-button {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__footer {
|
||||||
|
padding: 12px 12px;
|
||||||
|
border-top: 1px solid #d3dce2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__actions {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__action {
|
||||||
|
|
||||||
|
&:not(:first-of-type) {
|
||||||
|
margin-left: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-tag {
|
||||||
|
background: #708392;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--arrows {
|
||||||
|
|
||||||
|
.bp3-tag {
|
||||||
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 16px;
|
||||||
|
margin-left: 4px;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: #fff;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__footer {}
|
||||||
|
|
||||||
|
|
||||||
|
&-input-right-elements {
|
||||||
|
display: flex;
|
||||||
|
margin: 10px;
|
||||||
|
|
||||||
|
.bp3-spinner {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
&--invoice,
|
||||||
|
&--estimate,
|
||||||
|
&--bill,
|
||||||
|
&--receipt {
|
||||||
|
|
||||||
|
.amount {
|
||||||
|
color: #252b30;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
font-size: 13px;
|
||||||
|
|
||||||
|
&.status-warning {
|
||||||
|
color: rgb(236, 91, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.status-success {
|
||||||
|
color: #249017;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.universal-search-overlay .bp3-overlay-backdrop {
|
||||||
|
background: rgba(0, 10, 30, 0.3);
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import React from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
@@ -752,3 +753,38 @@ export const RESORUCE_TYPE = {
|
|||||||
ITEMS: 'items',
|
ITEMS: 'items',
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escapeRegExpChars(text) {
|
||||||
|
return text.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function highlightText(text, query) {
|
||||||
|
let lastIndex = 0;
|
||||||
|
const words = query
|
||||||
|
.split(/\s+/)
|
||||||
|
.filter((word) => word.length > 0)
|
||||||
|
.map(escapeRegExpChars);
|
||||||
|
if (words.length === 0) {
|
||||||
|
return [text];
|
||||||
|
}
|
||||||
|
const regexp = new RegExp(words.join('|'), 'gi');
|
||||||
|
const tokens = [];
|
||||||
|
while (true) {
|
||||||
|
const match = regexp.exec(text);
|
||||||
|
if (!match) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const length = match[0].length;
|
||||||
|
const before = text.slice(lastIndex, regexp.lastIndex - length);
|
||||||
|
if (before.length > 0) {
|
||||||
|
tokens.push(before);
|
||||||
|
}
|
||||||
|
lastIndex = regexp.lastIndex;
|
||||||
|
tokens.push(<strong key={lastIndex}>{match[0]}</strong>);
|
||||||
|
}
|
||||||
|
const rest = text.slice(lastIndex);
|
||||||
|
if (rest.length > 0) {
|
||||||
|
tokens.push(rest);
|
||||||
|
}
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
@@ -19,14 +19,16 @@ export default class VendorsController extends ContactsController {
|
|||||||
router() {
|
router() {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post('/', [
|
router.post(
|
||||||
|
'/',
|
||||||
|
[
|
||||||
...this.contactDTOSchema,
|
...this.contactDTOSchema,
|
||||||
...this.contactNewDTOSchema,
|
...this.contactNewDTOSchema,
|
||||||
...this.vendorDTOSchema,
|
...this.vendorDTOSchema,
|
||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.newVendor.bind(this)),
|
asyncMiddleware(this.newVendor.bind(this)),
|
||||||
this.handlerServiceErrors,
|
this.handlerServiceErrors
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/:id/opening_balance',
|
'/:id/opening_balance',
|
||||||
@@ -37,36 +39,38 @@ export default class VendorsController extends ContactsController {
|
|||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.editOpeningBalanceVendor.bind(this)),
|
asyncMiddleware(this.editOpeningBalanceVendor.bind(this)),
|
||||||
this.handlerServiceErrors,
|
this.handlerServiceErrors
|
||||||
);
|
);
|
||||||
router.post('/:id', [
|
router.post(
|
||||||
|
'/:id',
|
||||||
|
[
|
||||||
...this.contactDTOSchema,
|
...this.contactDTOSchema,
|
||||||
...this.contactEditDTOSchema,
|
...this.contactEditDTOSchema,
|
||||||
...this.vendorDTOSchema,
|
...this.vendorDTOSchema,
|
||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.editVendor.bind(this)),
|
asyncMiddleware(this.editVendor.bind(this)),
|
||||||
this.handlerServiceErrors,
|
this.handlerServiceErrors
|
||||||
);
|
);
|
||||||
router.delete('/:id', [
|
router.delete(
|
||||||
...this.specificContactSchema,
|
'/:id',
|
||||||
],
|
[...this.specificContactSchema],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.deleteVendor.bind(this)),
|
asyncMiddleware(this.deleteVendor.bind(this)),
|
||||||
this.handlerServiceErrors,
|
this.handlerServiceErrors
|
||||||
);
|
);
|
||||||
router.get('/:id', [
|
router.get(
|
||||||
...this.specificContactSchema,
|
'/:id',
|
||||||
],
|
[...this.specificContactSchema],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.getVendor.bind(this)),
|
asyncMiddleware(this.getVendor.bind(this)),
|
||||||
this.handlerServiceErrors,
|
this.handlerServiceErrors
|
||||||
);
|
);
|
||||||
router.get('/', [
|
router.get(
|
||||||
...this.vendorsListSchema,
|
'/',
|
||||||
],
|
[...this.vendorsListSchema],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.getVendorsList.bind(this)),
|
asyncMiddleware(this.getVendorsList.bind(this))
|
||||||
);
|
);
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
@@ -102,7 +106,7 @@ export default class VendorsController extends ContactsController {
|
|||||||
query('page_size').optional().isNumeric().toInt(),
|
query('page_size').optional().isNumeric().toInt(),
|
||||||
|
|
||||||
query('inactive_mode').optional().isBoolean().toBoolean(),
|
query('inactive_mode').optional().isBoolean().toBoolean(),
|
||||||
query('search_keyword').optional({ nullable: true }).isString().trim()
|
query('search_keyword').optional({ nullable: true }).isString().trim(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +121,11 @@ export default class VendorsController extends ContactsController {
|
|||||||
const { tenantId, user } = req;
|
const { tenantId, user } = req;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const vendor = await this.vendorsService.newVendor(tenantId, contactDTO, user);
|
const vendor = await this.vendorsService.newVendor(
|
||||||
|
tenantId,
|
||||||
|
contactDTO,
|
||||||
|
user
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: vendor.id,
|
id: vendor.id,
|
||||||
@@ -140,7 +148,12 @@ export default class VendorsController extends ContactsController {
|
|||||||
const { id: contactId } = req.params;
|
const { id: contactId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.vendorsService.editVendor(tenantId, contactId, contactDTO, user);
|
await this.vendorsService.editVendor(
|
||||||
|
tenantId,
|
||||||
|
contactId,
|
||||||
|
contactDTO,
|
||||||
|
user
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: contactId,
|
id: contactId,
|
||||||
@@ -157,24 +170,26 @@ export default class VendorsController extends ContactsController {
|
|||||||
* @param {Response} res -
|
* @param {Response} res -
|
||||||
* @param {NextFunction} next -
|
* @param {NextFunction} next -
|
||||||
*/
|
*/
|
||||||
async editOpeningBalanceVendor(req: Request, res: Response, next: NextFunction) {
|
async editOpeningBalanceVendor(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId, user } = req;
|
const { tenantId, user } = req;
|
||||||
const { id: vendorId } = req.params;
|
const { id: vendorId } = req.params;
|
||||||
const {
|
const { openingBalance, openingBalanceAt } = this.matchedBodyData(req);
|
||||||
openingBalance,
|
|
||||||
openingBalanceAt,
|
|
||||||
} = this.matchedBodyData(req);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.vendorsService.changeOpeningBalance(
|
await this.vendorsService.changeOpeningBalance(
|
||||||
tenantId,
|
tenantId,
|
||||||
vendorId,
|
vendorId,
|
||||||
openingBalance,
|
openingBalance,
|
||||||
openingBalanceAt,
|
openingBalanceAt
|
||||||
);
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: vendorId,
|
id: vendorId,
|
||||||
message: 'The opening balance of the given vendor has been changed successfully.',
|
message:
|
||||||
|
'The opening balance of the given vendor has been changed successfully.',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
@@ -192,7 +207,7 @@ export default class VendorsController extends ContactsController {
|
|||||||
const { id: contactId } = req.params;
|
const { id: contactId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.vendorsService.deleteVendor(tenantId, contactId, user)
|
await this.vendorsService.deleteVendor(tenantId, contactId, user);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: contactId,
|
id: contactId,
|
||||||
@@ -214,9 +229,12 @@ export default class VendorsController extends ContactsController {
|
|||||||
const { id: vendorId } = req.params;
|
const { id: vendorId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const vendor = await this.vendorsService.getVendor(tenantId, vendorId, user)
|
const vendor = await this.vendorsService.getVendor(
|
||||||
|
tenantId,
|
||||||
return res.status(200).send({ vendor });
|
vendorId,
|
||||||
|
user
|
||||||
|
);
|
||||||
|
return res.status(200).send(this.transfromToResponse({ vendor }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
@@ -241,14 +259,11 @@ export default class VendorsController extends ContactsController {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const { vendors, pagination, filterMeta } =
|
||||||
vendors,
|
await this.vendorsService.getVendorsList(tenantId, vendorsFilter);
|
||||||
pagination,
|
|
||||||
filterMeta,
|
|
||||||
} = await this.vendorsService.getVendorsList(tenantId, vendorsFilter);
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
vendors,
|
vendors: this.transfromToResponse(vendors),
|
||||||
pagination: this.transfromToResponse(pagination),
|
pagination: this.transfromToResponse(pagination),
|
||||||
filter_meta: this.transfromToResponse(filterMeta),
|
filter_meta: this.transfromToResponse(filterMeta),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { IExpenseDTO } from 'interfaces';
|
|||||||
import { ServiceError } from 'exceptions';
|
import { ServiceError } from 'exceptions';
|
||||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||||
import { DATATYPES_LENGTH } from 'data/DataTypes';
|
import { DATATYPES_LENGTH } from 'data/DataTypes';
|
||||||
|
import HasItemEntries from 'services/Sales/HasItemsEntries';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class ExpensesController extends BaseController {
|
export default class ExpensesController extends BaseController {
|
||||||
@@ -304,7 +305,7 @@ export default class ExpensesController extends BaseController {
|
|||||||
await this.expensesService.getExpensesList(tenantId, filter);
|
await this.expensesService.getExpensesList(tenantId, filter);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
expenses,
|
expenses: this.transfromToResponse(expenses),
|
||||||
pagination: this.transfromToResponse(pagination),
|
pagination: this.transfromToResponse(pagination),
|
||||||
filter_meta: this.transfromToResponse(filterMeta),
|
filter_meta: this.transfromToResponse(filterMeta),
|
||||||
});
|
});
|
||||||
@@ -328,7 +329,7 @@ export default class ExpensesController extends BaseController {
|
|||||||
tenantId,
|
tenantId,
|
||||||
expenseId
|
expenseId
|
||||||
);
|
);
|
||||||
return res.status(200).send({ expense });
|
return res.status(200).send(this.transfromToResponse({ expense }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ export default class ItemsController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate list query schema
|
* Validate list query schema.
|
||||||
*/
|
*/
|
||||||
get validateListQuerySchema() {
|
get validateListQuerySchema() {
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
manualJournalId
|
manualJournalId
|
||||||
);
|
);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
manual_journal: manualJournal,
|
manual_journal: this.transfromToResponse(manualJournal),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
@@ -301,7 +301,7 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
} = await this.manualJournalsService.getManualJournals(tenantId, filter);
|
} = await this.manualJournalsService.getManualJournals(tenantId, filter);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
manual_journals: manualJournals,
|
manual_journals: this.transfromToResponse(manualJournals),
|
||||||
pagination: this.transfromToResponse(pagination),
|
pagination: this.transfromToResponse(pagination),
|
||||||
filter_meta: this.transfromToResponse(filterMeta),
|
filter_meta: this.transfromToResponse(filterMeta),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ export default class BillsController extends BaseController {
|
|||||||
try {
|
try {
|
||||||
const bill = await this.billsService.getBill(tenantId, billId);
|
const bill = await this.billsService.getBill(tenantId, billId);
|
||||||
|
|
||||||
return res.status(200).send({ bill });
|
return res.status(200).send(this.transfromToResponse({ bill }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
@@ -312,7 +312,7 @@ export default class BillsController extends BaseController {
|
|||||||
await this.billsService.getBills(tenantId, filter);
|
await this.billsService.getBills(tenantId, filter);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
bills,
|
bills: this.transfromToResponse(bills),
|
||||||
pagination: this.transfromToResponse(pagination),
|
pagination: this.transfromToResponse(pagination),
|
||||||
filter_meta: this.transfromToResponse(filterMeta),
|
filter_meta: this.transfromToResponse(filterMeta),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -335,7 +335,7 @@ export default class BillsPayments extends BaseController {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
bill_payments: billPayments,
|
bill_payments: this.transfromToResponse(billPayments),
|
||||||
pagination: this.transfromToResponse(pagination),
|
pagination: this.transfromToResponse(pagination),
|
||||||
filter_meta: this.transfromToResponse(filterMeta),
|
filter_meta: this.transfromToResponse(filterMeta),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -302,6 +302,10 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
);
|
);
|
||||||
// Response formatter.
|
// Response formatter.
|
||||||
res.format({
|
res.format({
|
||||||
|
// JSON content type.
|
||||||
|
[ACCEPT_TYPE.APPLICATION_JSON]: () => {
|
||||||
|
return res.status(200).send(this.transfromToResponse({ estimate }));
|
||||||
|
},
|
||||||
// PDF content type.
|
// PDF content type.
|
||||||
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
|
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
|
||||||
const pdfContent = await this.saleEstimatesPdf.saleEstimatePdf(
|
const pdfContent = await this.saleEstimatesPdf.saleEstimatePdf(
|
||||||
@@ -314,10 +318,6 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
});
|
});
|
||||||
res.send(pdfContent);
|
res.send(pdfContent);
|
||||||
},
|
},
|
||||||
// JSON content type.
|
|
||||||
default: () => {
|
|
||||||
return res.status(200).send(this.transfromToResponse({ estimate }));
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
|
|||||||
@@ -277,6 +277,10 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
);
|
);
|
||||||
// Response formatter.
|
// Response formatter.
|
||||||
res.format({
|
res.format({
|
||||||
|
// JSON content type.
|
||||||
|
[ACCEPT_TYPE.APPLICATION_JSON]: () => {
|
||||||
|
return res.status(200).send(this.transfromToResponse({ saleInvoice }));
|
||||||
|
},
|
||||||
// PDF content type.
|
// PDF content type.
|
||||||
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
|
[ACCEPT_TYPE.APPLICATION_PDF]: async () => {
|
||||||
const pdfContent = await this.saleInvoicePdf.saleInvoicePdf(
|
const pdfContent = await this.saleInvoicePdf.saleInvoicePdf(
|
||||||
@@ -289,10 +293,6 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
});
|
});
|
||||||
res.send(pdfContent);
|
res.send(pdfContent);
|
||||||
},
|
},
|
||||||
// JSON content type.
|
|
||||||
[ACCEPT_TYPE.APPLICATION_JSON]: () => {
|
|
||||||
return res.status(200).send(this.transfromToResponse({ saleInvoice }));
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
|
|||||||
@@ -243,11 +243,13 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { salesReceipts, pagination, filterMeta } =
|
const { data, pagination, filterMeta } =
|
||||||
await this.saleReceiptService.salesReceiptsList(tenantId, filter);
|
await this.saleReceiptService.salesReceiptsList(tenantId, filter);
|
||||||
|
|
||||||
const response = this.transfromToResponse({
|
const response = this.transfromToResponse({
|
||||||
salesReceipts, pagination, filterMeta
|
data,
|
||||||
|
pagination,
|
||||||
|
filterMeta,
|
||||||
});
|
});
|
||||||
return res.status(200).send(response);
|
return res.status(200).send(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -272,6 +274,11 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
);
|
);
|
||||||
|
|
||||||
res.format({
|
res.format({
|
||||||
|
'application/json': () => {
|
||||||
|
return res
|
||||||
|
.status(200)
|
||||||
|
.send(this.transfromToResponse({ saleReceipt }));
|
||||||
|
},
|
||||||
'application/pdf': async () => {
|
'application/pdf': async () => {
|
||||||
const pdfContent = await this.saleReceiptsPdf.saleReceiptPdf(
|
const pdfContent = await this.saleReceiptsPdf.saleReceiptPdf(
|
||||||
tenantId,
|
tenantId,
|
||||||
@@ -283,10 +290,7 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
});
|
});
|
||||||
res.send(pdfContent);
|
res.send(pdfContent);
|
||||||
},
|
},
|
||||||
'application/json': () => {
|
});
|
||||||
return res.status(200).send(this.transfromToResponse({ saleReceipt }));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
|
|||||||
28
server/src/services/Accounts/AccountTransform.ts
Normal file
28
server/src/services/Accounts/AccountTransform.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { Service } from 'typedi';
|
||||||
|
import { IAccount } from 'interfaces';
|
||||||
|
import { Transformer } from 'lib/Transformer/Transformer';
|
||||||
|
import { formatNumber } from 'utils';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class AccountTransformer extends Transformer {
|
||||||
|
/**
|
||||||
|
* Include these attributes to sale invoice object.
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
protected includeAttributes = (): string[] => {
|
||||||
|
return [
|
||||||
|
'formattedAmount',
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve formatted account amount.
|
||||||
|
* @param {IAccount} invoice
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedAmount = (account: IAccount): string => {
|
||||||
|
return formatNumber(account.amount, {
|
||||||
|
currencyCode: account.currencyCode,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ import AccountTypesUtils from 'lib/AccountTypes';
|
|||||||
import { ERRORS } from './constants';
|
import { ERRORS } from './constants';
|
||||||
import { flatToNestedArray } from 'utils';
|
import { flatToNestedArray } from 'utils';
|
||||||
import I18nService from 'services/I18n/I18nService';
|
import I18nService from 'services/I18n/I18nService';
|
||||||
|
import AccountTransformer from './AccountTransform';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class AccountsService {
|
export default class AccountsService {
|
||||||
@@ -41,6 +42,9 @@ export default class AccountsService {
|
|||||||
@Inject()
|
@Inject()
|
||||||
i18nService: I18nService;
|
i18nService: I18nService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
accountTransformer: AccountTransformer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve account type or throws service error.
|
* Retrieve account type or throws service error.
|
||||||
* @param {number} tenantId -
|
* @param {number} tenantId -
|
||||||
@@ -326,7 +330,10 @@ export default class AccountsService {
|
|||||||
*/
|
*/
|
||||||
public async getAccount(tenantId: number, accountId: number) {
|
public async getAccount(tenantId: number, accountId: number) {
|
||||||
const account = await this.getAccountOrThrowError(tenantId, accountId);
|
const account = await this.getAccountOrThrowError(tenantId, accountId);
|
||||||
return this.transformAccountResponse(tenantId, account);
|
|
||||||
|
return this.accountTransformer.transform(
|
||||||
|
this.transformAccountResponse(tenantId, account)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -613,9 +620,7 @@ export default class AccountsService {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
private parseListFilterDTO(filterDTO) {
|
private parseListFilterDTO(filterDTO) {
|
||||||
return R.compose(
|
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||||
this.dynamicListService.parseStringifiedFilter
|
|
||||||
)(filterDTO);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -735,10 +740,12 @@ export default class AccountsService {
|
|||||||
key: 'base_currency',
|
key: 'base_currency',
|
||||||
});
|
});
|
||||||
|
|
||||||
const _accounts = accounts.map((account) => ({
|
const _accounts = this.accountTransformer.transform(
|
||||||
|
accounts.map((account) => ({
|
||||||
...account.toJSON(),
|
...account.toJSON(),
|
||||||
currencyCode: baseCurrency,
|
currencyCode: baseCurrency,
|
||||||
}));
|
}))
|
||||||
|
);
|
||||||
return flatToNestedArray(
|
return flatToNestedArray(
|
||||||
this.i18nService.i18nMapper(_accounts, ['account_type_label'], tenantId),
|
this.i18nService.i18nMapper(_accounts, ['account_type_label'], tenantId),
|
||||||
{
|
{
|
||||||
|
|||||||
43
server/src/services/Contacts/ContactTransformer.ts
Normal file
43
server/src/services/Contacts/ContactTransformer.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { Service, Container } from 'typedi';
|
||||||
|
import { isNull } from 'lodash';
|
||||||
|
import { Transformer } from 'lib/Transformer/Transformer';
|
||||||
|
import { formatNumber } from 'utils';
|
||||||
|
import { IContact } from 'interfaces';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class ContactTransfromer extends Transformer {
|
||||||
|
/**
|
||||||
|
* Retrieve formatted expense amount.
|
||||||
|
* @param {IExpense} expense
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedBalance = (contact: IContact): string => {
|
||||||
|
return formatNumber(contact.balance, {
|
||||||
|
currencyCode: contact.currencyCode,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve formatted expense landed cost amount.
|
||||||
|
* @param {IExpense} expense
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedOpeningBalance = (contact: IContact): string => {
|
||||||
|
return !isNull(contact.openingBalance)
|
||||||
|
? formatNumber(contact.openingBalance, {
|
||||||
|
currencyCode: contact.currencyCode,
|
||||||
|
})
|
||||||
|
: '';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retriecve fromatted date.
|
||||||
|
* @param {IExpense} expense
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedOpeningBalanceAt = (contact: IContact): string => {
|
||||||
|
return !isNull(contact.openingBalanceAt)
|
||||||
|
? this.formatDate(contact.openingBalanceAt)
|
||||||
|
: '';
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { Service } from 'typedi';
|
||||||
|
import ContactTransfromer from '../ContactTransformer';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class CustomerTransfromer extends ContactTransfromer {
|
||||||
|
/**
|
||||||
|
* Include these attributes to expense object.
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
protected includeAttributes = (): string[] => {
|
||||||
|
return [
|
||||||
|
'formattedBalance',
|
||||||
|
'formattedOpeningBalance',
|
||||||
|
'formattedOpeningBalanceAt'
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ import { ServiceError } from 'exceptions';
|
|||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||||
import events from 'subscribers/events';
|
import events from 'subscribers/events';
|
||||||
|
import CustomerTransfromer from './Customers/CustomerTransformer';
|
||||||
|
|
||||||
const ERRORS = {
|
const ERRORS = {
|
||||||
CUSTOMER_HAS_TRANSACTIONS: 'CUSTOMER_HAS_TRANSACTIONS',
|
CUSTOMER_HAS_TRANSACTIONS: 'CUSTOMER_HAS_TRANSACTIONS',
|
||||||
@@ -61,6 +62,9 @@ export default class CustomersService {
|
|||||||
@Inject('SalesEstimates')
|
@Inject('SalesEstimates')
|
||||||
estimatesService: ISalesEstimatesService;
|
estimatesService: ISalesEstimatesService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
customerTransformer: CustomerTransfromer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts customer to contact DTO.
|
* Converts customer to contact DTO.
|
||||||
* @param {ICustomerNewDTO|ICustomerEditDTO} customerDTO
|
* @param {ICustomerNewDTO|ICustomerEditDTO} customerDTO
|
||||||
@@ -262,7 +266,10 @@ export default class CustomersService {
|
|||||||
customerId,
|
customerId,
|
||||||
'customer'
|
'customer'
|
||||||
);
|
);
|
||||||
return this.transformContactToCustomer(contact);
|
return R.compose(
|
||||||
|
this.customerTransformer.transform,
|
||||||
|
this.transformContactToCustomer,
|
||||||
|
)(contact);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
17
server/src/services/Contacts/Vendors/VendorTransformer.ts
Normal file
17
server/src/services/Contacts/Vendors/VendorTransformer.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Service } from 'typedi';
|
||||||
|
import ContactTransfromer from '../ContactTransformer';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class VendorTransfromer extends ContactTransfromer {
|
||||||
|
/**
|
||||||
|
* Include these attributes to expense object.
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
protected includeAttributes = (): string[] => {
|
||||||
|
return [
|
||||||
|
'formattedBalance',
|
||||||
|
'formattedOpeningBalance',
|
||||||
|
'formattedOpeningBalanceAt'
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ import { ServiceError } from 'exceptions';
|
|||||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
import events from 'subscribers/events';
|
import events from 'subscribers/events';
|
||||||
|
import VendorTransfromer from './Vendors/VendorTransformer';
|
||||||
|
|
||||||
const ERRORS = {
|
const ERRORS = {
|
||||||
VENDOR_HAS_TRANSACTIONS: 'VENDOR_HAS_TRANSACTIONS',
|
VENDOR_HAS_TRANSACTIONS: 'VENDOR_HAS_TRANSACTIONS',
|
||||||
@@ -51,6 +52,9 @@ export default class VendorsService {
|
|||||||
@Inject('BillPayments')
|
@Inject('BillPayments')
|
||||||
billPaymentsService: IBillPaymentsService;
|
billPaymentsService: IBillPaymentsService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
vendorTransformer: VendorTransfromer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts vendor to contact DTO.
|
* Converts vendor to contact DTO.
|
||||||
* @param {IVendorNewDTO|IVendorEditDTO} vendorDTO
|
* @param {IVendorNewDTO|IVendorEditDTO} vendorDTO
|
||||||
@@ -151,8 +155,10 @@ export default class VendorsService {
|
|||||||
await this.billsService.validateVendorHasNoBills(tenantId, vendorId);
|
await this.billsService.validateVendorHasNoBills(tenantId, vendorId);
|
||||||
|
|
||||||
// Validate vendor has no paymentys.
|
// Validate vendor has no paymentys.
|
||||||
await this.billPaymentsService.validateVendorHasNoPayments(tenantId, vendorId);
|
await this.billPaymentsService.validateVendorHasNoPayments(
|
||||||
|
tenantId,
|
||||||
|
vendorId
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new ServiceError(ERRORS.VENDOR_HAS_TRANSACTIONS);
|
throw new ServiceError(ERRORS.VENDOR_HAS_TRANSACTIONS);
|
||||||
}
|
}
|
||||||
@@ -196,7 +202,9 @@ export default class VendorsService {
|
|||||||
* @param {number} vendorId
|
* @param {number} vendorId
|
||||||
*/
|
*/
|
||||||
public async getVendor(tenantId: number, vendorId: number) {
|
public async getVendor(tenantId: number, vendorId: number) {
|
||||||
return this.contactService.getContact(tenantId, vendorId, 'vendor');
|
const vendor = this.contactService.getContact(tenantId, vendorId, 'vendor');
|
||||||
|
|
||||||
|
return this.vendorTransformer.transform(vendor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -257,9 +265,7 @@ export default class VendorsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private parseVendorsListFilterDTO(filterDTO) {
|
private parseVendorsListFilterDTO(filterDTO) {
|
||||||
return R.compose(
|
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||||
this.dynamicListService.parseStringifiedFilter
|
|
||||||
)(filterDTO);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -297,7 +303,7 @@ export default class VendorsService {
|
|||||||
.pagination(filter.page - 1, filter.pageSize);
|
.pagination(filter.page - 1, filter.pageSize);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
vendors: results,
|
vendors: this.vendorTransformer.transform(results),
|
||||||
pagination,
|
pagination,
|
||||||
filterMeta: dynamicList.getResponseMeta(),
|
filterMeta: dynamicList.getResponseMeta(),
|
||||||
};
|
};
|
||||||
|
|||||||
62
server/src/services/Expenses/ExpenseTransformer.ts
Normal file
62
server/src/services/Expenses/ExpenseTransformer.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { Service, Container } from 'typedi';
|
||||||
|
import { Transformer } from 'lib/Transformer/Transformer';
|
||||||
|
import { formatNumber } from 'utils';
|
||||||
|
import { IExpense } from 'interfaces';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class ExpenseTransfromer extends Transformer {
|
||||||
|
/**
|
||||||
|
* Include these attributes to expense object.
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
protected includeAttributes = (): string[] => {
|
||||||
|
return [
|
||||||
|
'formattedAmount',
|
||||||
|
'formattedLandedCostAmount',
|
||||||
|
'formattedAllocatedCostAmount',
|
||||||
|
'formattedDate'
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve formatted expense amount.
|
||||||
|
* @param {IExpense} expense
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedAmount = (expense: IExpense): string => {
|
||||||
|
return formatNumber(expense.totalAmount, {
|
||||||
|
currencyCode: expense.currencyCode,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve formatted expense landed cost amount.
|
||||||
|
* @param {IExpense} expense
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedLandedCostAmount = (expense: IExpense): string => {
|
||||||
|
return formatNumber(expense.landedCostAmount, {
|
||||||
|
currencyCode: expense.currencyCode,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve formatted allocated cost amount.
|
||||||
|
* @param {IExpense} expense
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedAllocatedCostAmount = (expense: IExpense): string => {
|
||||||
|
return formatNumber(expense.allocatedCostAmount, {
|
||||||
|
currencyCode: expense.currencyCode,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retriecve fromatted date.
|
||||||
|
* @param {IExpense} expense
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedDate = (expense: IExpense): string => {
|
||||||
|
return this.formatDate(expense.paymentDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ import events from 'subscribers/events';
|
|||||||
import ContactsService from 'services/Contacts/ContactsService';
|
import ContactsService from 'services/Contacts/ContactsService';
|
||||||
import { ACCOUNT_PARENT_TYPE, ACCOUNT_ROOT_TYPE } from 'data/AccountTypes';
|
import { ACCOUNT_PARENT_TYPE, ACCOUNT_ROOT_TYPE } from 'data/AccountTypes';
|
||||||
import EntriesService from 'services/Entries';
|
import EntriesService from 'services/Entries';
|
||||||
|
import ExpenseTransfromer from './ExpenseTransformer';
|
||||||
|
|
||||||
const ERRORS = {
|
const ERRORS = {
|
||||||
EXPENSE_NOT_FOUND: 'expense_not_found',
|
EXPENSE_NOT_FOUND: 'expense_not_found',
|
||||||
@@ -58,6 +59,9 @@ export default class ExpensesService implements IExpensesService {
|
|||||||
@Inject()
|
@Inject()
|
||||||
entriesService: EntriesService;
|
entriesService: EntriesService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
expenseTransfromer: ExpenseTransfromer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the payment account details or returns not found server error in case the
|
* Retrieve the payment account details or returns not found server error in case the
|
||||||
* given account not found on the storage.
|
* given account not found on the storage.
|
||||||
@@ -681,7 +685,7 @@ export default class ExpensesService implements IExpensesService {
|
|||||||
.pagination(filter.page - 1, filter.pageSize);
|
.pagination(filter.page - 1, filter.pageSize);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
expenses: results,
|
expenses: this.expenseTransfromer.transform(results),
|
||||||
pagination,
|
pagination,
|
||||||
filterMeta: dynamicList.getResponseMeta(),
|
filterMeta: dynamicList.getResponseMeta(),
|
||||||
};
|
};
|
||||||
@@ -706,7 +710,7 @@ export default class ExpensesService implements IExpensesService {
|
|||||||
if (!expense) {
|
if (!expense) {
|
||||||
throw new ServiceError(ERRORS.EXPENSE_NOT_FOUND);
|
throw new ServiceError(ERRORS.EXPENSE_NOT_FOUND);
|
||||||
}
|
}
|
||||||
return expense;
|
return this.expenseTransfromer.transform(expense);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { IManualJournal } from 'interfaces';
|
||||||
|
import { Transformer } from 'lib/Transformer/Transformer';
|
||||||
|
import { Service } from 'typedi';
|
||||||
|
import { formatNumber } from 'utils';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class ManualJournalTransfromer extends Transformer {
|
||||||
|
/**
|
||||||
|
* Include these attributes to expense object.
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
protected includeAttributes = (): string[] => {
|
||||||
|
return ['formattedAmount', 'formattedDate', 'formattedPublishedAt'];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve formatted journal amount.
|
||||||
|
* @param {IManualJournal} manualJournal
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedAmount = (manualJorunal: IManualJournal): string => {
|
||||||
|
return formatNumber(manualJorunal.amount, {
|
||||||
|
currencyCode: manualJorunal.currencyCode,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve formatted date.
|
||||||
|
* @param {IManualJournal} manualJournal
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedDate = (manualJorunal: IManualJournal): string => {
|
||||||
|
return this.formatDate(manualJorunal.date);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve formatted published at date.
|
||||||
|
* @param {IManualJournal} manualJournal
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
protected formattedPublishedAt = (manualJorunal: IManualJournal): string => {
|
||||||
|
return this.formatDate(manualJorunal.publishedAt);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ import JournalCommands from 'services/Accounting/JournalCommands';
|
|||||||
import JournalPosterService from 'services/Sales/JournalPosterService';
|
import JournalPosterService from 'services/Sales/JournalPosterService';
|
||||||
import AutoIncrementOrdersService from 'services/Sales/AutoIncrementOrdersService';
|
import AutoIncrementOrdersService from 'services/Sales/AutoIncrementOrdersService';
|
||||||
import { ERRORS } from './constants';
|
import { ERRORS } from './constants';
|
||||||
|
import ManualJournalTransfromer from './ManualJournalTransformer';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class ManualJournalsService implements IManualJournalsService {
|
export default class ManualJournalsService implements IManualJournalsService {
|
||||||
@@ -45,6 +46,9 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
@Inject()
|
@Inject()
|
||||||
autoIncrementOrdersService: AutoIncrementOrdersService;
|
autoIncrementOrdersService: AutoIncrementOrdersService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
manualJournalTransformer: ManualJournalTransfromer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the manual journal existance.
|
* Validates the manual journal existance.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -815,7 +819,7 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
.pagination(filter.page - 1, filter.pageSize);
|
.pagination(filter.page - 1, filter.pageSize);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
manualJournals: results,
|
manualJournals: this.manualJournalTransformer.transform(results),
|
||||||
pagination,
|
pagination,
|
||||||
filterMeta: dynamicService.getResponseMeta(),
|
filterMeta: dynamicService.getResponseMeta(),
|
||||||
};
|
};
|
||||||
@@ -842,7 +846,7 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
.withGraphFetched('transactions')
|
.withGraphFetched('transactions')
|
||||||
.withGraphFetched('media');
|
.withGraphFetched('media');
|
||||||
|
|
||||||
return manualJournal;
|
return this.manualJournalTransformer.transform(manualJournal);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,10 +10,7 @@ export default class PaymentReceiveTransfromer extends Transformer {
|
|||||||
* @returns {Array}
|
* @returns {Array}
|
||||||
*/
|
*/
|
||||||
protected includeAttributes = (): string[] => {
|
protected includeAttributes = (): string[] => {
|
||||||
return [
|
return ['formattedPaymentDate', 'formattedAmount'];
|
||||||
'formattedPaymentDate',
|
|
||||||
'formattedAmount',
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -421,7 +421,7 @@ export default class SalesReceiptService implements ISalesReceiptsService {
|
|||||||
tenantId: number,
|
tenantId: number,
|
||||||
filterDTO: ISaleReceiptFilter
|
filterDTO: ISaleReceiptFilter
|
||||||
): Promise<{
|
): Promise<{
|
||||||
salesReceipts: ISaleReceipt[];
|
data: ISaleReceipt[];
|
||||||
pagination: IPaginationMeta;
|
pagination: IPaginationMeta;
|
||||||
filterMeta: IFilterMeta;
|
filterMeta: IFilterMeta;
|
||||||
}> {
|
}> {
|
||||||
@@ -451,7 +451,7 @@ export default class SalesReceiptService implements ISalesReceiptsService {
|
|||||||
.pagination(filter.page - 1, filter.pageSize);
|
.pagination(filter.page - 1, filter.pageSize);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
salesReceipts: this.saleReceiptTransformer.transform(results),
|
data: this.saleReceiptTransformer.transform(results),
|
||||||
pagination,
|
pagination,
|
||||||
filterMeta: dynamicFilter.getResponseMeta(),
|
filterMeta: dynamicFilter.getResponseMeta(),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user