mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 13:20:31 +00:00
feat: universal search.
This commit is contained in:
@@ -54,19 +54,20 @@ function ManualJournalActionsBar({
|
||||
const handleBulkDelete = () => {};
|
||||
|
||||
// Handle tab change.
|
||||
const handleTabChange = (customView) => {
|
||||
setManualJournalsTableState({ customViewId: customView.id || null });
|
||||
const handleTabChange = (view) => {
|
||||
setManualJournalsTableState({ viewSlug: view ? view.slig : null });
|
||||
};
|
||||
// Handle click a refresh Journals
|
||||
const handleRefreshBtnClick = () => { refresh(); };
|
||||
|
||||
console.log(manualJournalsFilterConditions, fields, 'XXX');
|
||||
const handleRefreshBtnClick = () => {
|
||||
refresh();
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<DashboardActionViewsList
|
||||
resourceName={'manual-journals'}
|
||||
allMenuItem={true}
|
||||
views={journalsViews}
|
||||
onChange={handleTabChange}
|
||||
/>
|
||||
@@ -135,5 +136,5 @@ export default compose(
|
||||
withManualJournalsActions,
|
||||
withManualJournals(({ manualJournalsTableState }) => ({
|
||||
manualJournalsFilterConditions: manualJournalsTableState.filterRoles,
|
||||
}))
|
||||
})),
|
||||
)(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.
|
||||
const handleTabChange = (customView) => {
|
||||
setAccountsTableState({ customViewId: customView.id || null });
|
||||
const handleTabChange = (view) => {
|
||||
setAccountsTableState({ viewSlug: view ? view.slug : null });
|
||||
};
|
||||
|
||||
// Handle inactive switch changing.
|
||||
@@ -98,6 +98,8 @@ function AccountsActionsBar({
|
||||
<NavbarGroup>
|
||||
<DashboardActionViewsList
|
||||
resourceName={'accounts'}
|
||||
allMenuItem={true}
|
||||
allMenuItemText={<T id={'all_accounts'} />}
|
||||
views={resourceViews}
|
||||
onChange={handleTabChange}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { memo } from 'react';
|
||||
import React from 'react';
|
||||
import {
|
||||
Position,
|
||||
Classes,
|
||||
@@ -6,14 +6,13 @@ import {
|
||||
MenuItem,
|
||||
Menu,
|
||||
MenuDivider,
|
||||
Intent,
|
||||
Popover,
|
||||
Button,
|
||||
Intent
|
||||
} from '@blueprintjs/core';
|
||||
import { Icon, Money, If } from 'components';
|
||||
import intl from 'react-intl-universal';
|
||||
import { safeCallback } from 'utils';
|
||||
|
||||
|
||||
/**
|
||||
* Accounts table actions menu.
|
||||
*/
|
||||
|
||||
@@ -62,9 +62,9 @@ function CustomerActionsBar({
|
||||
openAlert('customers-bulk-delete', { customersIds: customersSelectedRows });
|
||||
};
|
||||
|
||||
const handleTabChange = (viewId) => {
|
||||
const handleTabChange = (view) => {
|
||||
setCustomersTableState({
|
||||
customViewId: viewId.id || null,
|
||||
viewSlug: view ? view.slug : null,
|
||||
});
|
||||
};
|
||||
// Handle inactive switch changing.
|
||||
@@ -82,6 +82,8 @@ function CustomerActionsBar({
|
||||
<DashboardActionViewsList
|
||||
resourceName={'customers'}
|
||||
views={customersViews}
|
||||
allMenuItem={true}
|
||||
allMenuItemText={<T id={'all'} />}
|
||||
onChange={handleTabChange}
|
||||
/>
|
||||
<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,
|
||||
|
||||
// #withExpenses
|
||||
expensesFilterConditions
|
||||
expensesFilterConditions,
|
||||
}) {
|
||||
// History context.
|
||||
const history = useHistory();
|
||||
@@ -57,9 +57,9 @@ function ExpensesActionsBar({
|
||||
const handleBulkDelete = () => {};
|
||||
|
||||
// Handles the tab chaning.
|
||||
const handleTabChange = (viewId) => {
|
||||
const handleTabChange = (view) => {
|
||||
setExpensesTableState({
|
||||
customViewId: viewId.id || null,
|
||||
viewSlug: view ? view.slug : null,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -73,6 +73,7 @@ function ExpensesActionsBar({
|
||||
<DashboardActionViewsList
|
||||
resourceName={'expenses'}
|
||||
views={expensesViews}
|
||||
allMenuItem={true}
|
||||
onChange={handleTabChange}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
@@ -140,5 +141,5 @@ export default compose(
|
||||
withExpensesActions,
|
||||
withExpenses(({ expensesTableState }) => ({
|
||||
expensesFilterConditions: expensesTableState.filterRoles,
|
||||
}))
|
||||
})),
|
||||
)(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.
|
||||
const handleTabChange = (viewId) => {
|
||||
setItemsTableState({ customViewId: viewId.id || null });
|
||||
const handleTabChange = (view) => {
|
||||
setItemsTableState({ viewSlug: view ? view.slug : null });
|
||||
};
|
||||
|
||||
// Handle cancel/confirm items bulk.
|
||||
@@ -82,6 +82,8 @@ function ItemsActionsBar({
|
||||
<NavbarGroup>
|
||||
<DashboardActionViewsList
|
||||
resourceName={'items'}
|
||||
allMenuItem={true}
|
||||
allMenuItemText={<T id={'all_items'} />}
|
||||
views={itemsViews}
|
||||
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,
|
||||
});
|
||||
@@ -130,4 +130,4 @@ export function transformItemsTableState(tableState) {
|
||||
...transformTableStateToQuery(tableState),
|
||||
inactive_mode: tableState.inactiveMode,
|
||||
};
|
||||
}
|
||||
}
|
||||
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,
|
||||
|
||||
// #withBills
|
||||
billsConditionsRoles
|
||||
billsConditionsRoles,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
|
||||
@@ -50,13 +50,15 @@ function BillActionsBar({
|
||||
};
|
||||
|
||||
// Handle tab change.
|
||||
const handleTabChange = (customView) => {
|
||||
const handleTabChange = (view) => {
|
||||
setBillsTableState({
|
||||
customViewId: customView.id || null,
|
||||
viewSlug: view ? view.slug : null,
|
||||
});
|
||||
};
|
||||
// Handle click a refresh bills
|
||||
const handleRefreshBtnClick = () => { refresh(); };
|
||||
const handleRefreshBtnClick = () => {
|
||||
refresh();
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
@@ -64,6 +66,8 @@ function BillActionsBar({
|
||||
<DashboardActionViewsList
|
||||
resourceName={'bills'}
|
||||
views={billsViews}
|
||||
allMenuItem={true}
|
||||
allMenuItemText={<T id={'all'} />}
|
||||
onChange={handleTabChange}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
@@ -127,6 +131,6 @@ function BillActionsBar({
|
||||
export default compose(
|
||||
withBillsActions,
|
||||
withBills(({ billsTableState }) => ({
|
||||
billsConditionsRoles: billsTableState.filterRoles
|
||||
}))
|
||||
billsConditionsRoles: billsTableState.filterRoles,
|
||||
})),
|
||||
)(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,
|
||||
|
||||
// #withEstimates
|
||||
estimatesFilterRoles
|
||||
estimatesFilterRoles,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
|
||||
@@ -51,20 +51,24 @@ function EstimateActionsBar({
|
||||
const { refresh } = useRefreshEstimates();
|
||||
|
||||
// Handle tab change.
|
||||
const handleTabChange = (customView) => {
|
||||
const handleTabChange = (view) => {
|
||||
setEstimatesTableState({
|
||||
customViewId: customView.id || null,
|
||||
viewSlug: view ? view.slug : null,
|
||||
});
|
||||
};
|
||||
|
||||
// Handle click a refresh sale estimates
|
||||
const handleRefreshBtnClick = () => { refresh(); };
|
||||
const handleRefreshBtnClick = () => {
|
||||
refresh();
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<DashboardActionViewsList
|
||||
resourceName={'estimates'}
|
||||
allMenuItem={true}
|
||||
allMenuItemText={<T id={'all'} />}
|
||||
views={estimatesViews}
|
||||
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();
|
||||
|
||||
// Handle views tab change.
|
||||
const handleTabChange = (customView) => {
|
||||
setInvoicesTableState({ customViewId: customView.id || null });
|
||||
const handleTabChange = (view) => {
|
||||
setInvoicesTableState({ viewSlug: view ? view.slug : null });
|
||||
};
|
||||
|
||||
// Handle click a refresh sale invoices
|
||||
@@ -64,6 +64,7 @@ function InvoiceActionsBar({
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<DashboardActionViewsList
|
||||
allMenuItem={true}
|
||||
resourceName={'invoices'}
|
||||
views={invoicesViews}
|
||||
onChange={handleTabChange}
|
||||
|
||||
@@ -210,4 +210,4 @@ export function useInvoicesTableColumns() {
|
||||
],
|
||||
[],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
const { refresh } = useRefreshReceipts();
|
||||
|
||||
const handleTabChange = (customView) => {
|
||||
const handleTabChange = (view) => {
|
||||
setReceiptsTableState({
|
||||
customViewId: customView.id || null,
|
||||
viewSlug: view ? view.slug : null,
|
||||
});
|
||||
};
|
||||
|
||||
// Handle click a refresh sale estimates
|
||||
const handleRefreshBtnClick = () => { refresh(); };
|
||||
|
||||
console.log(receiptsFilterConditions, fields, 'XXX');
|
||||
const handleRefreshBtnClick = () => {
|
||||
refresh();
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<DashboardActionViewsList
|
||||
resourceName={'receipts'}
|
||||
allMenuItem={true}
|
||||
allMenuItemText={<T id={'all'} />}
|
||||
views={receiptsViews}
|
||||
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,
|
||||
});
|
||||
Reference in New Issue
Block a user