mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-10 01:41:59 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2160c0595 | ||
|
|
956a9b58dd | ||
|
|
acb701d618 | ||
|
|
09ff72d302 | ||
|
|
7375512fec | ||
|
|
77e65389a4 | ||
|
|
1972861c97 | ||
|
|
c47acdee03 | ||
|
|
8689962bf3 | ||
|
|
3258159474 | ||
|
|
36bfa573ad |
@@ -70,6 +70,16 @@ export class Item extends TenantBaseModel {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Model search roles.
|
||||
*/
|
||||
static get searchRoles() {
|
||||
return [
|
||||
{ condition: 'or', fieldKey: 'name', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'code', comparator: 'like' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
HttpCode,
|
||||
Param,
|
||||
} from '@nestjs/common';
|
||||
import { Throttle } from '@nestjs/throttler';
|
||||
import { BuildOrganizationService } from './commands/BuildOrganization.service';
|
||||
import {
|
||||
BuildOrganizationDto,
|
||||
@@ -50,7 +51,7 @@ export class OrganizationController {
|
||||
private readonly updateOrganizationService: UpdateOrganizationService,
|
||||
private readonly getBuildOrganizationJobService: GetBuildOrganizationBuildJob,
|
||||
private readonly orgBaseCurrencyLockingService: OrganizationBaseCurrencyLocking,
|
||||
) { }
|
||||
) {}
|
||||
|
||||
@Post('build')
|
||||
@HttpCode(200)
|
||||
@@ -77,6 +78,7 @@ export class OrganizationController {
|
||||
}
|
||||
|
||||
@Get('build/:buildJobId')
|
||||
@Throttle({ default: { limit: 300, ttl: 60000 } }) // 300 req/min
|
||||
@ApiParam({
|
||||
name: 'buildJobId',
|
||||
required: true,
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Controller, Get, Post } from '@nestjs/common';
|
||||
import { Controller, Get, HttpCode } from '@nestjs/common';
|
||||
import { PublicRoute } from '@/modules/Auth/guards/jwt.guard';
|
||||
|
||||
@Controller('/system_db')
|
||||
@Controller('system_db')
|
||||
@PublicRoute()
|
||||
export class SystemDatabaseController {
|
||||
constructor() {}
|
||||
|
||||
@Post()
|
||||
@Get()
|
||||
ping(){
|
||||
|
||||
@HttpCode(200)
|
||||
ping() {
|
||||
return { status: 'ok' };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
SystemKnexConnectionConfigure,
|
||||
} from './SystemDB.constants';
|
||||
import { knexSnakeCaseMappers } from 'objection';
|
||||
import { SystemDatabaseController } from './SystemDB.controller';
|
||||
|
||||
const providers = [
|
||||
{
|
||||
@@ -42,6 +43,7 @@ const providers = [
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
controllers: [SystemDatabaseController],
|
||||
providers: [...providers],
|
||||
exports: [...providers],
|
||||
})
|
||||
|
||||
@@ -10,12 +10,17 @@ const TextStatusRoot = styled.span`
|
||||
${(props) =>
|
||||
props.intent === 'warning' &&
|
||||
`
|
||||
color: #ec5b0a;`}
|
||||
color: #c87619;`}
|
||||
|
||||
${(props) =>
|
||||
props.intent === 'danger' &&
|
||||
`
|
||||
color: #f17377;`}
|
||||
|
||||
${(props) =>
|
||||
props.intent === 'success' &&
|
||||
`
|
||||
color: #2ba01d;`}
|
||||
color: #238551;`}
|
||||
|
||||
${(props) =>
|
||||
props.intent === 'none' &&
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import React, { KeyboardEvent, ReactNode } from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import classNames from 'classnames';
|
||||
import { isUndefined } from 'lodash';
|
||||
import {
|
||||
Overlay,
|
||||
@@ -10,11 +8,14 @@ import {
|
||||
MenuItem,
|
||||
Spinner,
|
||||
Intent,
|
||||
OverlayProps,
|
||||
Button,
|
||||
} from '@blueprintjs/core';
|
||||
import { QueryList } from '@blueprintjs/select';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
|
||||
import { Icon, If, ListSelect, FormattedMessage as T } from '@/components';
|
||||
import { QueryList, ItemRenderer } from '@blueprintjs/select';
|
||||
import { x } from '@xstyled/emotion';
|
||||
import { css } from '@emotion/css';
|
||||
import { Icon, If, FormattedMessage as T } from '@/components';
|
||||
import { Select } from '@blueprintjs-formik/select';
|
||||
import {
|
||||
UniversalSearchProvider,
|
||||
useUniversalSearchContext,
|
||||
@@ -22,59 +23,297 @@ import {
|
||||
import { filterItemsByResourceType } from './utils';
|
||||
import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
|
||||
|
||||
// Resource type from RESOURCES_TYPES constant
|
||||
type ResourceType = string;
|
||||
|
||||
// Search type option item
|
||||
interface SearchTypeOption {
|
||||
key: ResourceType;
|
||||
label: string;
|
||||
}
|
||||
|
||||
// Universal search item
|
||||
interface UniversalSearchItem {
|
||||
id: number | string;
|
||||
_type: ResourceType;
|
||||
text: string;
|
||||
subText?: string;
|
||||
label?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// CSS styles for complex selectors
|
||||
const overlayStyles = css`
|
||||
.bp4-overlay-appear,
|
||||
.bp4-overlay-enter {
|
||||
filter: blur(20px);
|
||||
opacity: 0.2;
|
||||
}
|
||||
.bp4-overlay-appear-active,
|
||||
.bp4-overlay-enter-active {
|
||||
filter: blur(0);
|
||||
opacity: 1;
|
||||
transition:
|
||||
filter 0.2s cubic-bezier(0.4, 1, 0.75, 0.9),
|
||||
opacity 0.2s cubic-bezier(0.4, 1, 0.75, 0.9);
|
||||
}
|
||||
.bp4-overlay-exit {
|
||||
filter: blur(0);
|
||||
opacity: 1;
|
||||
}
|
||||
.bp4-overlay-exit-active {
|
||||
filter: blur(20px);
|
||||
opacity: 0.2;
|
||||
transition:
|
||||
filter 0.2s cubic-bezier(0.4, 1, 0.75, 0.9),
|
||||
opacity 0.2s cubic-bezier(0.4, 1, 0.75, 0.9);
|
||||
}
|
||||
`;
|
||||
|
||||
const containerStyles = css`
|
||||
position: fixed;
|
||||
filter: blur(0);
|
||||
opacity: 1;
|
||||
background-color: var(--color-universal-search-background);
|
||||
border-radius: 3px;
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(16, 22, 26, 0.1),
|
||||
0 4px 8px rgba(16, 22, 26, 0.2),
|
||||
0 18px 46px 6px rgba(16, 22, 26, 0.2);
|
||||
left: calc(50% - 250px);
|
||||
top: 20vh;
|
||||
width: 500px;
|
||||
z-index: 20;
|
||||
|
||||
.bp4-input-group {
|
||||
.bp4-icon {
|
||||
margin: 16px;
|
||||
color: var(--color-universal-search-icon);
|
||||
|
||||
svg {
|
||||
stroke: currentColor;
|
||||
fill: none;
|
||||
fill-rule: evenodd;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-width: 2;
|
||||
--text-opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bp4-input-group .bp4-input {
|
||||
border: 0;
|
||||
box-shadow: 0 0 0 0;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
font-size: 20px;
|
||||
}
|
||||
.bp4-input-group.bp4-large .bp4-input:not(:first-child) {
|
||||
padding-left: 50px !important;
|
||||
}
|
||||
.bp4-input-group.bp4-large .bp4-input:not(:last-child) {
|
||||
padding-right: 130px !important;
|
||||
}
|
||||
|
||||
.bp4-menu {
|
||||
border-top: 1px solid var(--color-universal-search-menu-border);
|
||||
max-height: calc(60vh - 20px);
|
||||
overflow: auto;
|
||||
|
||||
.bp4-menu-item {
|
||||
.bp4-text-muted {
|
||||
font-size: 12px;
|
||||
|
||||
.bp4-icon {
|
||||
color: var(--bp4-gray-600);
|
||||
}
|
||||
}
|
||||
&.bp4-intent-primary {
|
||||
&.bp4-active {
|
||||
background-color: var(--bp4-blue-100);
|
||||
color: var(--bp4-dark-gray-800);
|
||||
|
||||
.bp4-menu-item-label {
|
||||
color: var(--bp4-gray-600);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-label {
|
||||
flex-direction: row;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bp4-input-action {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
|
||||
const inputRightElementsStyles = css`
|
||||
display: flex;
|
||||
margin: 10px;
|
||||
|
||||
.bp4-spinner {
|
||||
margin-right: 6px;
|
||||
}
|
||||
`;
|
||||
|
||||
const footerStyles = css`
|
||||
padding: 12px 12px;
|
||||
border-top: 1px solid var(--color-universal-search-footer-divider);
|
||||
`;
|
||||
|
||||
const actionBaseStyles = css`
|
||||
&:not(:first-of-type) {
|
||||
margin-left: 14px;
|
||||
}
|
||||
|
||||
.bp4-tag {
|
||||
background: var(--color-universal-search-tag-background);
|
||||
color: var(--color-universal-search-tag-text);
|
||||
}
|
||||
`;
|
||||
|
||||
const actionArrowsStyles = css`
|
||||
&:not(:first-of-type) {
|
||||
margin-left: 14px;
|
||||
}
|
||||
|
||||
.bp4-tag {
|
||||
background: var(--color-universal-search-tag-background);
|
||||
color: var(--color-universal-search-tag-text);
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
line-height: 16px;
|
||||
margin-left: 4px;
|
||||
|
||||
svg {
|
||||
fill: var(--color-universal-search-tag-text);
|
||||
height: 100%;
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// UniversalSearchInputRightElements props
|
||||
interface UniversalSearchInputRightElementsProps {
|
||||
/** Callback when search type changes */
|
||||
onSearchTypeChange?: (option: SearchTypeOption) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Universal search input action.
|
||||
*/
|
||||
function UniversalSearchInputRightElements({ onSearchTypeChange }) {
|
||||
const { isLoading, searchType, defaultSearchResource, searchTypeOptions } =
|
||||
function UniversalSearchInputRightElements({
|
||||
onSearchTypeChange,
|
||||
}: UniversalSearchInputRightElementsProps) {
|
||||
const { isLoading, searchType, searchTypeOptions } =
|
||||
useUniversalSearchContext();
|
||||
|
||||
// Find the currently selected item object.
|
||||
const selectedItem = searchTypeOptions.find(
|
||||
(item) => item.key === searchType,
|
||||
);
|
||||
|
||||
// Handle search type option change.
|
||||
const handleSearchTypeChange = (option) => {
|
||||
onSearchTypeChange && onSearchTypeChange(option);
|
||||
const handleSearchTypeChange = (option: SearchTypeOption) => {
|
||||
onSearchTypeChange?.(option);
|
||||
};
|
||||
|
||||
// Item renderer for the select dropdown.
|
||||
const itemRenderer: ItemRenderer<SearchTypeOption> = (
|
||||
item,
|
||||
{ handleClick },
|
||||
) => {
|
||||
return <MenuItem text={item.label} key={item.key} onClick={handleClick} />;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={CLASSES.UNIVERSAL_SEARCH_INPUT_RIGHT_ELEMENTS}>
|
||||
<x.div display="flex" m="10px" className={inputRightElementsStyles}>
|
||||
<If condition={isLoading}>
|
||||
<Spinner tagName="div" intent={Intent.NONE} size={18} value={null} />
|
||||
<Spinner tagName="div" intent={Intent.NONE} size={18} />
|
||||
</If>
|
||||
|
||||
<ListSelect
|
||||
<Select<SearchTypeOption>
|
||||
items={searchTypeOptions}
|
||||
itemRenderer={itemRenderer}
|
||||
onItemSelect={handleSearchTypeChange}
|
||||
selectedValue={selectedItem?.key}
|
||||
valueAccessor={'key'}
|
||||
labelAccessor={'label'}
|
||||
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,
|
||||
}}
|
||||
input={({ activeItem }) => (
|
||||
<Button minimal={true} text={activeItem?.label} />
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</x.div>
|
||||
);
|
||||
}
|
||||
|
||||
// QueryList renderer props
|
||||
interface QueryListRendererProps {
|
||||
/** Current query string */
|
||||
query: string;
|
||||
/** Callback when query changes */
|
||||
handleQueryChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
/** Item list element */
|
||||
itemList: ReactNode;
|
||||
/** Class name */
|
||||
className?: string;
|
||||
/** Handle key down */
|
||||
handleKeyDown?: (event: KeyboardEvent<HTMLDivElement>) => void;
|
||||
/** Handle key up */
|
||||
handleKeyUp?: (event: KeyboardEvent<HTMLDivElement>) => void;
|
||||
}
|
||||
|
||||
// UniversalSearchQueryList props
|
||||
interface UniversalSearchQueryListProps {
|
||||
/** Whether the search is open */
|
||||
isOpen: boolean;
|
||||
/** Whether the search is loading */
|
||||
isLoading: boolean;
|
||||
/** Callback when search type changes */
|
||||
onSearchTypeChange?: (option: SearchTypeOption) => void;
|
||||
/** Current search type */
|
||||
searchType: ResourceType;
|
||||
/** Items to display */
|
||||
items: UniversalSearchItem[];
|
||||
/** Renderer for items */
|
||||
itemRenderer?: ItemRenderer<UniversalSearchItem>;
|
||||
/** Callback when an item is selected */
|
||||
onItemSelect?: (item: UniversalSearchItem, event?: any) => void;
|
||||
/** Current query string */
|
||||
query: string;
|
||||
/** Callback when query changes */
|
||||
onQueryChange?: (query: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Universal search query list.
|
||||
*/
|
||||
function UniversalSearchQueryList(props) {
|
||||
const { isOpen, isLoading, onSearchTypeChange, searchType, ...restProps } =
|
||||
props;
|
||||
|
||||
function UniversalSearchQueryList({
|
||||
isOpen,
|
||||
isLoading,
|
||||
onSearchTypeChange,
|
||||
...restProps
|
||||
}: UniversalSearchQueryListProps) {
|
||||
return (
|
||||
<QueryList
|
||||
{...restProps}
|
||||
<QueryList<UniversalSearchItem>
|
||||
{...(restProps as any)}
|
||||
initialContent={null}
|
||||
renderer={(listProps) => (
|
||||
renderer={(listProps: QueryListRendererProps) => (
|
||||
<UniversalSearchBar
|
||||
isOpen={isOpen}
|
||||
onSearchTypeChange={onSearchTypeChange}
|
||||
@@ -100,47 +339,53 @@ function UniversalSearchQueryList(props) {
|
||||
*/
|
||||
function UniversalQuerySearchActions() {
|
||||
return (
|
||||
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTIONS)}>
|
||||
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTION_SELECT)}>
|
||||
<x.div display="flex">
|
||||
<x.div className={actionBaseStyles}>
|
||||
<Tag>ENTER</Tag>
|
||||
<span class={'text'}>{intl.get('universal_search.enter_text')}</span>
|
||||
</div>
|
||||
<x.span ml="6px">{intl.get('universal_search.enter_text')}</x.span>
|
||||
</x.div>
|
||||
|
||||
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTION_CLOSE)}>
|
||||
<x.div className={actionBaseStyles}>
|
||||
<Tag>ESC</Tag>{' '}
|
||||
<span class={'text'}>{intl.get('universal_search.close_text')}</span>
|
||||
</div>
|
||||
<x.span ml="6px">{intl.get('universal_search.close_text')}</x.span>
|
||||
</x.div>
|
||||
|
||||
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTION_ARROWS)}>
|
||||
<x.div className={actionArrowsStyles}>
|
||||
<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>
|
||||
<x.span ml="6px">{intl.get('universal_seach.navigate_text')}</x.span>
|
||||
</x.div>
|
||||
</x.div>
|
||||
);
|
||||
}
|
||||
|
||||
// UniversalSearchBar props
|
||||
interface UniversalSearchBarProps extends QueryListRendererProps {
|
||||
/** Whether the search is open */
|
||||
isOpen: boolean;
|
||||
/** Callback when search type changes */
|
||||
onSearchTypeChange?: (option: SearchTypeOption) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Universal search input bar with items list.
|
||||
*/
|
||||
function UniversalSearchBar({ isOpen, onSearchTypeChange, ...listProps }) {
|
||||
function UniversalSearchBar({
|
||||
isOpen,
|
||||
onSearchTypeChange,
|
||||
...listProps
|
||||
}: UniversalSearchBarProps) {
|
||||
const { handleKeyDown, handleKeyUp } = listProps;
|
||||
const handlers = isOpen
|
||||
? { onKeyDown: handleKeyDown, onKeyUp: handleKeyUp }
|
||||
: {};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
CLASSES.UNIVERSAL_SEARCH_OMNIBAR,
|
||||
listProps.className,
|
||||
)}
|
||||
{...handlers}
|
||||
>
|
||||
<x.div {...handlers}>
|
||||
<InputGroup
|
||||
large={true}
|
||||
leftIcon={<Icon icon={'universal-search'} iconSize={20} />}
|
||||
@@ -155,17 +400,44 @@ function UniversalSearchBar({ isOpen, onSearchTypeChange, ...listProps }) {
|
||||
autoFocus={true}
|
||||
/>
|
||||
{listProps.itemList}
|
||||
</div>
|
||||
</x.div>
|
||||
);
|
||||
}
|
||||
|
||||
// UniversalSearch props
|
||||
export interface UniversalSearchProps {
|
||||
/** Default search resource type */
|
||||
defaultSearchResource?: ResourceType;
|
||||
/** Controlled search resource type */
|
||||
searchResource?: ResourceType;
|
||||
/** Overlay props */
|
||||
overlayProps?: OverlayProps;
|
||||
/** Whether the search overlay is open */
|
||||
isOpen: boolean;
|
||||
/** Whether the search is loading */
|
||||
isLoading: boolean;
|
||||
/** Callback when search type changes */
|
||||
onSearchTypeChange?: (resource: SearchTypeOption) => void;
|
||||
/** Items to display */
|
||||
items: UniversalSearchItem[];
|
||||
/** Available search type options */
|
||||
searchTypeOptions: SearchTypeOption[];
|
||||
/** Renderer for items */
|
||||
itemRenderer?: ItemRenderer<UniversalSearchItem>;
|
||||
/** Callback when an item is selected */
|
||||
onItemSelect?: (item: UniversalSearchItem, event?: any) => void;
|
||||
/** Current query string */
|
||||
query: string;
|
||||
/** Callback when query changes */
|
||||
onQueryChange?: (query: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Universal search.
|
||||
*/
|
||||
export function UniversalSearch({
|
||||
defaultSearchResource,
|
||||
searchResource,
|
||||
|
||||
overlayProps,
|
||||
isOpen,
|
||||
isLoading,
|
||||
@@ -173,9 +445,9 @@ export function UniversalSearch({
|
||||
items,
|
||||
searchTypeOptions,
|
||||
...queryListProps
|
||||
}) {
|
||||
}: UniversalSearchProps) {
|
||||
// Search type state.
|
||||
const [searchType, setSearchType] = React.useState(
|
||||
const [searchType, setSearchType] = React.useState<ResourceType>(
|
||||
defaultSearchResource || RESOURCES_TYPES.CUSTOMER,
|
||||
);
|
||||
// Handle search resource type controlled mode.
|
||||
@@ -189,9 +461,9 @@ export function UniversalSearch({
|
||||
}, [searchResource, defaultSearchResource]);
|
||||
|
||||
// Handle search type change.
|
||||
const handleSearchTypeChange = (searchTypeResource) => {
|
||||
const handleSearchTypeChange = (searchTypeResource: SearchTypeOption) => {
|
||||
setSearchType(searchTypeResource.key);
|
||||
onSearchTypeChange && onSearchTypeChange(searchTypeResource);
|
||||
onSearchTypeChange?.(searchTypeResource);
|
||||
};
|
||||
// Filters query list items based on the given search type.
|
||||
const filteredItems = filterItemsByResourceType(items, searchType);
|
||||
@@ -200,7 +472,7 @@ export function UniversalSearch({
|
||||
<Overlay
|
||||
hasBackdrop={true}
|
||||
isOpen={isOpen}
|
||||
className={classNames(CLASSES.UNIVERSAL_SEARCH_OVERLAY)}
|
||||
className={overlayStyles}
|
||||
{...overlayProps}
|
||||
>
|
||||
<UniversalSearchProvider
|
||||
@@ -209,7 +481,7 @@ export function UniversalSearch({
|
||||
defaultSearchResource={defaultSearchResource}
|
||||
searchTypeOptions={searchTypeOptions}
|
||||
>
|
||||
<div className={classNames(CLASSES.UNIVERSAL_SEARCH)}>
|
||||
<x.div className={containerStyles}>
|
||||
<UniversalSearchQueryList
|
||||
isOpen={isOpen}
|
||||
isLoading={isLoading}
|
||||
@@ -218,10 +490,10 @@ export function UniversalSearch({
|
||||
{...queryListProps}
|
||||
items={filteredItems}
|
||||
/>
|
||||
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_FOOTER)}>
|
||||
<x.div className={footerStyles}>
|
||||
<UniversalQuerySearchActions />
|
||||
</div>
|
||||
</div>
|
||||
</x.div>
|
||||
</x.div>
|
||||
</UniversalSearchProvider>
|
||||
</Overlay>
|
||||
);
|
||||
|
||||
@@ -1,30 +1,82 @@
|
||||
// @ts-nocheck
|
||||
import React, { createContext } from 'react';
|
||||
import React, { createContext, ReactNode, useContext } from 'react';
|
||||
|
||||
const UniversalSearchContext = createContext();
|
||||
// The resource type value from RESOURCES_TYPES constant
|
||||
type ResourceType = string;
|
||||
|
||||
// Search type option item
|
||||
interface SearchTypeOption {
|
||||
key: ResourceType;
|
||||
label: string;
|
||||
}
|
||||
|
||||
// Context value type
|
||||
interface UniversalSearchContextValue {
|
||||
/** Whether the search is loading */
|
||||
isLoading: boolean;
|
||||
/** Current search type/resource type */
|
||||
searchType: ResourceType;
|
||||
/** Default search resource type */
|
||||
defaultSearchResource?: ResourceType;
|
||||
/** List of available search type options */
|
||||
searchTypeOptions: SearchTypeOption[];
|
||||
}
|
||||
|
||||
// Create the context with undefined as initial value
|
||||
const UniversalSearchContext = createContext<
|
||||
UniversalSearchContextValue | undefined
|
||||
>(undefined);
|
||||
|
||||
// Provider props interface
|
||||
interface UniversalSearchProviderProps {
|
||||
/** Whether the search is loading */
|
||||
isLoading: boolean;
|
||||
/** Default search resource type */
|
||||
defaultSearchResource?: ResourceType;
|
||||
/** Current search type/resource type */
|
||||
searchType: ResourceType;
|
||||
/** List of available search type options */
|
||||
searchTypeOptions: SearchTypeOption[];
|
||||
/** Child elements */
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Universal search data provider.
|
||||
*/
|
||||
function UniversalSearchProvider({
|
||||
export function UniversalSearchProvider({
|
||||
isLoading,
|
||||
defaultSearchResource,
|
||||
searchType,
|
||||
searchTypeOptions,
|
||||
...props
|
||||
}) {
|
||||
children,
|
||||
}: UniversalSearchProviderProps) {
|
||||
// Provider payload.
|
||||
const provider = {
|
||||
const provider: UniversalSearchContextValue = {
|
||||
isLoading,
|
||||
searchType,
|
||||
defaultSearchResource,
|
||||
searchTypeOptions,
|
||||
};
|
||||
|
||||
return <UniversalSearchContext.Provider value={provider} {...props} />;
|
||||
return (
|
||||
<UniversalSearchContext.Provider value={provider}>
|
||||
{children}
|
||||
</UniversalSearchContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
const useUniversalSearchContext = () =>
|
||||
React.useContext(UniversalSearchContext);
|
||||
/**
|
||||
* Hook to access the universal search context.
|
||||
* @throws Error if used outside of UniversalSearchProvider
|
||||
*/
|
||||
export const useUniversalSearchContext = (): UniversalSearchContextValue => {
|
||||
const context = useContext(UniversalSearchContext);
|
||||
|
||||
export { UniversalSearchProvider, useUniversalSearchContext };
|
||||
if (context === undefined) {
|
||||
throw new Error(
|
||||
'useUniversalSearchContext must be used within a UniversalSearchProvider',
|
||||
);
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { ReactNode } from 'react';
|
||||
|
||||
export const If = (props) =>
|
||||
props.condition ? (props.render ? props.render() : props.children) : null;
|
||||
interface IfProps {
|
||||
condition: boolean;
|
||||
children?: ReactNode;
|
||||
render?: () => ReactNode;
|
||||
}
|
||||
|
||||
If.propTypes = {
|
||||
// condition: PropTypes.bool.isRequired,
|
||||
children: PropTypes.node,
|
||||
render: PropTypes.func,
|
||||
};
|
||||
export const If = (props: IfProps): React.ReactElement | null =>
|
||||
props.condition ? (props.render ? <>{props.render()}</> : <>{props.children}</>) : null;
|
||||
|
||||
@@ -32,38 +32,38 @@ export default function MakeJournalFloatingAction() {
|
||||
|
||||
// Handle submit & publish button click.
|
||||
const handleSubmitPublishBtnClick = (event) => {
|
||||
submitForm();
|
||||
setSubmitPayload({ redirect: true, publish: true });
|
||||
submitForm();
|
||||
};
|
||||
|
||||
// Handle submit, publish & new button click.
|
||||
const handleSubmitPublishAndNewBtnClick = (event) => {
|
||||
submitForm();
|
||||
setSubmitPayload({ redirect: false, publish: true, resetForm: true });
|
||||
submitForm();
|
||||
};
|
||||
|
||||
// Handle submit, publish & edit button click.
|
||||
const handleSubmitPublishContinueEditingBtnClick = (event) => {
|
||||
submitForm();
|
||||
setSubmitPayload({ redirect: false, publish: true });
|
||||
submitForm();
|
||||
};
|
||||
|
||||
// Handle submit as draft button click.
|
||||
const handleSubmitDraftBtnClick = (event) => {
|
||||
submitForm();
|
||||
setSubmitPayload({ redirect: true, publish: false });
|
||||
submitForm();
|
||||
};
|
||||
|
||||
// Handle submit as draft & new button click.
|
||||
const handleSubmitDraftAndNewBtnClick = (event) => {
|
||||
submitForm();
|
||||
setSubmitPayload({ redirect: false, publish: false, resetForm: true });
|
||||
submitForm();
|
||||
};
|
||||
|
||||
// Handle submit as draft & continue editing button click.
|
||||
const handleSubmitDraftContinueEditingBtnClick = (event) => {
|
||||
submitForm();
|
||||
setSubmitPayload({ redirect: false, publish: false });
|
||||
submitForm();
|
||||
};
|
||||
|
||||
// Handle cancel button click.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
import { MenuItem, Intent } from '@blueprintjs/core';
|
||||
|
||||
import { formattedAmount } from '@/utils';
|
||||
import { T, Icon, Choose, If } from '@/components';
|
||||
import { T, Icon, Choose, If, TextStatus } from '@/components';
|
||||
|
||||
import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
|
||||
import { AbilitySubject, BillAction } from '@/constants/abilityOption';
|
||||
@@ -41,35 +41,35 @@ export function BillStatus({ bill }) {
|
||||
return (
|
||||
<Choose>
|
||||
<Choose.When condition={bill.is_fully_paid && bill.is_open}>
|
||||
<span class="fully-paid-text">
|
||||
<TextStatus intent={Intent.SUCCESS}>
|
||||
<T id={'paid'} />
|
||||
</span>
|
||||
</TextStatus>
|
||||
</Choose.When>
|
||||
<Choose.When condition={bill.is_open}>
|
||||
<Choose>
|
||||
<Choose.When condition={bill.is_overdue}>
|
||||
<span className={'overdue-status'}>
|
||||
<TextStatus intent={Intent.DANGER}>
|
||||
{intl.get('overdue_by', { overdue: bill.overdue_days })}
|
||||
</span>
|
||||
</TextStatus>
|
||||
</Choose.When>
|
||||
<Choose.Otherwise>
|
||||
<span className={'due-status'}>
|
||||
<TextStatus intent={Intent.WARNING}>
|
||||
{intl.get('due_in', { due: bill.remaining_days })}
|
||||
</span>
|
||||
</TextStatus>
|
||||
</Choose.Otherwise>
|
||||
</Choose>
|
||||
<If condition={bill.is_partially_paid}>
|
||||
<span className="partial-paid">
|
||||
<TextStatus intent={Intent.WARNING}>
|
||||
{intl.get('day_partially_paid', {
|
||||
due: formattedAmount(bill.due_amount, bill.currency_code),
|
||||
})}
|
||||
</span>
|
||||
</TextStatus>
|
||||
</If>
|
||||
</Choose.When>
|
||||
<Choose.Otherwise>
|
||||
<span class="draft">
|
||||
<TextStatus intent={Intent.NONE}>
|
||||
<T id={'draft'} />
|
||||
</span>
|
||||
</TextStatus>
|
||||
</Choose.Otherwise>
|
||||
</Choose>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
import { MenuItem, Intent } from '@blueprintjs/core';
|
||||
|
||||
import { Choose, T, Icon } from '@/components';
|
||||
import { Choose, T, Icon, TextStatus } from '@/components';
|
||||
import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
|
||||
import { AbilitySubject, SaleEstimateAction } from '@/constants/abilityOption';
|
||||
|
||||
@@ -37,28 +37,28 @@ export const EstimateUniversalSearchSelect = withDrawerActions(
|
||||
export const EstimateStatus = ({ estimate }) => (
|
||||
<Choose>
|
||||
<Choose.When condition={estimate.is_delivered && estimate.is_approved}>
|
||||
<span class="approved">
|
||||
<TextStatus intent={Intent.SUCCESS}>
|
||||
<T id={'approved'} />
|
||||
</span>
|
||||
</TextStatus>
|
||||
</Choose.When>
|
||||
<Choose.When condition={estimate.is_delivered && estimate.is_rejected}>
|
||||
<span class="reject">
|
||||
<TextStatus intent={Intent.DANGER}>
|
||||
<T id={'rejected'} />
|
||||
</span>
|
||||
</TextStatus>
|
||||
</Choose.When>
|
||||
<Choose.When
|
||||
condition={
|
||||
estimate.is_delivered && !estimate.is_rejected && !estimate.is_approved
|
||||
}
|
||||
>
|
||||
<span class="delivered">
|
||||
<TextStatus intent={Intent.SUCCESS}>
|
||||
<T id={'delivered'} />
|
||||
</span>
|
||||
</TextStatus>
|
||||
</Choose.When>
|
||||
<Choose.Otherwise>
|
||||
<span class="draft">
|
||||
<TextStatus intent={Intent.NONE}>
|
||||
<T id={'draft'} />
|
||||
</span>
|
||||
</TextStatus>
|
||||
</Choose.Otherwise>
|
||||
</Choose>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
import { MenuItem, Intent } from '@blueprintjs/core';
|
||||
|
||||
import { T, Choose, Icon } from '@/components';
|
||||
import { T, Choose, Icon, TextStatus } from '@/components';
|
||||
import { highlightText } from '@/utils';
|
||||
|
||||
import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
|
||||
@@ -39,29 +39,29 @@ function InvoiceStatus({ customer }) {
|
||||
return (
|
||||
<Choose>
|
||||
<Choose.When condition={customer.is_fully_paid && customer.is_delivered}>
|
||||
<span class="status status-success">
|
||||
<TextStatus intent={Intent.SUCCESS}>
|
||||
<T id={'paid'} />
|
||||
</span>
|
||||
</TextStatus>
|
||||
</Choose.When>
|
||||
|
||||
<Choose.When condition={customer.is_delivered}>
|
||||
<Choose>
|
||||
<Choose.When condition={customer.is_overdue}>
|
||||
<span className={'status status-warning'}>
|
||||
<TextStatus intent={Intent.DANGER}>
|
||||
{intl.get('overdue_by', { overdue: customer.overdue_days })}
|
||||
</span>
|
||||
</TextStatus>
|
||||
</Choose.When>
|
||||
<Choose.Otherwise>
|
||||
<span className={'status status-warning'}>
|
||||
<TextStatus intent={Intent.WARNING}>
|
||||
{intl.get('due_in', { due: customer.remaining_days })}
|
||||
</span>
|
||||
</TextStatus>
|
||||
</Choose.Otherwise>
|
||||
</Choose>
|
||||
</Choose.When>
|
||||
<Choose.Otherwise>
|
||||
<span class="status status--gray">
|
||||
<TextStatus intent={Intent.NONE}>
|
||||
<T id={'draft'} />
|
||||
</span>
|
||||
</TextStatus>
|
||||
</Choose.Otherwise>
|
||||
</Choose>
|
||||
);
|
||||
@@ -94,7 +94,6 @@ export function InvoiceUniversalSearchItem(
|
||||
</>
|
||||
}
|
||||
onClick={handleClick}
|
||||
className={'universal-search__item--invoice'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
|
||||
import { Icon, Choose, T } from '@/components';
|
||||
import { MenuItem, Intent } from '@blueprintjs/core';
|
||||
import { Icon, Choose, T, TextStatus } from '@/components';
|
||||
import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
|
||||
import { AbilitySubject, SaleReceiptAction } from '@/constants/abilityOption';
|
||||
import { withDrawerActions } from '@/containers/Drawer/withDrawerActions';
|
||||
@@ -39,15 +38,15 @@ function ReceiptStatus({ receipt }) {
|
||||
return (
|
||||
<Choose>
|
||||
<Choose.When condition={receipt.is_closed}>
|
||||
<span class="closed">
|
||||
<TextStatus intent={Intent.SUCCESS}>
|
||||
<T id={'closed'} />
|
||||
</span>
|
||||
</TextStatus>
|
||||
</Choose.When>
|
||||
|
||||
<Choose.Otherwise>
|
||||
<span class="draft">
|
||||
<TextStatus intent={Intent.NONE}>
|
||||
<T id={'draft'} />
|
||||
</span>
|
||||
</TextStatus>
|
||||
</Choose.Otherwise>
|
||||
</Choose>
|
||||
);
|
||||
|
||||
@@ -32,19 +32,19 @@ export function useResourceData(type, query, props) {
|
||||
*/
|
||||
function getResourceUrlFromType(type) {
|
||||
const config = {
|
||||
[RESOURCES_TYPES.INVOICE]: '/sales/invoices',
|
||||
[RESOURCES_TYPES.ESTIMATE]: '/sales/estimates',
|
||||
[RESOURCES_TYPES.INVOICE]: '/sale-invoices',
|
||||
[RESOURCES_TYPES.ESTIMATE]: '/sale-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.RECEIPT]: '/sale-receipts',
|
||||
[RESOURCES_TYPES.BILL]: '/bills',
|
||||
[RESOURCES_TYPES.PAYMENT_RECEIVE]: '/payments-received',
|
||||
[RESOURCES_TYPES.PAYMENT_MADE]: '/bill-payments',
|
||||
[RESOURCES_TYPES.CUSTOMER]: '/customers',
|
||||
[RESOURCES_TYPES.VENDOR]: '/vendors',
|
||||
[RESOURCES_TYPES.MANUAL_JOURNAL]: '/manual-journals',
|
||||
[RESOURCES_TYPES.ACCOUNT]: '/accounts',
|
||||
[RESOURCES_TYPES.CREDIT_NOTE]: '/sales/credit_notes',
|
||||
[RESOURCES_TYPES.VENDOR_CREDIT]: '/purchases/vendor-credit',
|
||||
[RESOURCES_TYPES.CREDIT_NOTE]: '/credit-notes',
|
||||
[RESOURCES_TYPES.VENDOR_CREDIT]: '/vendor-credits',
|
||||
};
|
||||
return config[type] || '';
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
@import 'components/Overlay';
|
||||
@import 'components/Menu';
|
||||
@import 'components/SidebarOverlay';
|
||||
@import 'components/UniversalSearch';
|
||||
|
||||
// Pages
|
||||
@import 'pages/view-form';
|
||||
|
||||
@@ -7,6 +7,27 @@ $ns: bp4;
|
||||
--color-primary: #8abbff;
|
||||
--color-danger: red;
|
||||
|
||||
// Green colors
|
||||
--color-green-500: #165a36;
|
||||
--color-green-400: #1c6e42;
|
||||
--color-green-300: #238551;
|
||||
--color-green-200: #32a467;
|
||||
--color-green-100: #72ca9b;
|
||||
|
||||
// Red colors
|
||||
--color-red-500: #8e292c;
|
||||
--color-red-400: #ac2f33;
|
||||
--color-red-300: #cd4246;
|
||||
--color-red-200: #e76a6e;
|
||||
--color-red-100: #fa999c;
|
||||
|
||||
// Orange colors
|
||||
--color-orange-500: #77450d;
|
||||
--color-orange-400: #935610;
|
||||
--color-orange-300: #c87619;
|
||||
--color-orange-200: #ec9a3c;
|
||||
--color-orange-100: #fbb360;
|
||||
|
||||
--color-dark-gray5: #404854;
|
||||
--color-dark-gray4: #383e47;
|
||||
--color-dark-gray3: #2f343c;
|
||||
@@ -301,6 +322,27 @@ body.bp4-dark {
|
||||
--color-primary: #8abbff;
|
||||
--color-danger: rgb(213, 103, 103);
|
||||
|
||||
// Green colors (dark mode - lighter variants)
|
||||
--color-green-500: #72ca9b;
|
||||
--color-green-400: #32a467;
|
||||
--color-green-300: #238551;
|
||||
--color-green-200: #1c6e42;
|
||||
--color-green-100: #165a36;
|
||||
|
||||
// Red colors (dark mode - lighter variants)
|
||||
--color-red-500: #fa999c;
|
||||
--color-red-400: #e76a6e;
|
||||
--color-red-300: #cd4246;
|
||||
--color-red-200: #ac2f33;
|
||||
--color-red-100: #8e292c;
|
||||
|
||||
// Orange colors (dark mode - lighter variants)
|
||||
--color-orange-500: #fbb360;
|
||||
--color-orange-400: #ec9a3c;
|
||||
--color-orange-300: #c87619;
|
||||
--color-orange-200: #935610;
|
||||
--color-orange-100: #77450d;
|
||||
|
||||
--color-dark-gray5: #404854;
|
||||
--color-dark-gray4: #383e47;
|
||||
--color-dark-gray3: #2f343c;
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
.universal-search {
|
||||
position: fixed;
|
||||
filter: blur(0);
|
||||
opacity: 1;
|
||||
background-color: var(--color-universal-search-background);
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 0 0 1px rgba(16, 22, 26, 0.1),
|
||||
0 4px 8px rgba(16, 22, 26, 0.2),
|
||||
0 18px 46px 6px rgba(16, 22, 26, 0.2);
|
||||
left: calc(50% - 250px);
|
||||
top: 20vh;
|
||||
width: 500px;
|
||||
z-index: 20;
|
||||
|
||||
&.bp4-overlay-appear,
|
||||
&.bp4-overlay-enter {
|
||||
filter: blur(20px);
|
||||
opacity: 0.2;
|
||||
}
|
||||
&.bp4-overlay-appear-active,
|
||||
&.bp4-overlay-enter-active {
|
||||
filter: blur(0);
|
||||
opacity: 1;
|
||||
transition-delay: 0;
|
||||
transition-duration: 0.2s;
|
||||
transition-property: filter, opacity;
|
||||
transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);
|
||||
}
|
||||
&.bp4-overlay-exit {
|
||||
filter: blur(0);
|
||||
opacity: 1;
|
||||
}
|
||||
&.bp4-overlay-exit-active {
|
||||
filter: blur(20px);
|
||||
opacity: 0.2;
|
||||
transition-delay: 0;
|
||||
transition-duration: 0.2s;
|
||||
transition-property: filter, opacity;
|
||||
transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);
|
||||
}
|
||||
|
||||
&__omnibar {
|
||||
.bp4-input-group {
|
||||
.bp4-icon {
|
||||
svg {
|
||||
stroke: currentColor;
|
||||
fill: none;
|
||||
fill-rule: evenodd;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bp4-input-group .bp4-input {
|
||||
border: 0;
|
||||
box-shadow: 0 0 0 0;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
font-size: 20px;
|
||||
}
|
||||
.bp4-input-group.bp4-large .bp4-input:not(:first-child) {
|
||||
padding-left: 50px !important;
|
||||
}
|
||||
.bp4-input-group.bp4-large .bp4-input:not(:last-child) {
|
||||
padding-right: 130px !important;
|
||||
}
|
||||
|
||||
.bp4-input-group {
|
||||
.bp4-icon {
|
||||
margin: 16px;
|
||||
color: var(--color-universal-search-icon);
|
||||
|
||||
svg {
|
||||
stroke-width: 2;
|
||||
--text-opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bp4-menu {
|
||||
border-top: 1px solid var(--color-universal-search-menu-border);
|
||||
max-height: calc(60vh - 20px);
|
||||
overflow: auto;
|
||||
|
||||
.bp4-menu-item {
|
||||
.bp4-text-muted {
|
||||
font-size: 12px;
|
||||
|
||||
.bp4-icon {
|
||||
color: #8499a7;
|
||||
}
|
||||
}
|
||||
&.bp4-intent-primary {
|
||||
&.bp4-active {
|
||||
background-color: rgb(235, 241, 246);
|
||||
color: #252b30;
|
||||
|
||||
.bp4-menu-item-label {
|
||||
color: #5c7080;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-label {
|
||||
flex-direction: row;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bp4-input-action {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
&__type-select-overlay {
|
||||
.bp4-button {
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
padding: 12px 12px;
|
||||
border-top: 1px solid var(--color-universal-search-footer-divider);
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__action {
|
||||
&:not(:first-of-type) {
|
||||
margin-left: 14px;
|
||||
}
|
||||
|
||||
.bp4-tag {
|
||||
background: var(--color-universal-search-tag-background);
|
||||
color: var(--color-universal-search-tag-text);
|
||||
}
|
||||
&--arrows {
|
||||
.bp4-tag {
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
line-height: 16px;
|
||||
margin-left: 4px;
|
||||
|
||||
svg {
|
||||
fill: var(--color-universal-search-tag-text);
|
||||
height: 100%;
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
}
|
||||
|
||||
&-input-right-elements {
|
||||
display: flex;
|
||||
margin: 10px;
|
||||
|
||||
.bp4-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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user