refactor: update UniversalSearch components with TypeScript and TextStatus

This commit is contained in:
Ahmed Bouhuolia
2026-02-09 19:26:26 +02:00
parent 1972861c97
commit 7375512fec
10 changed files with 484 additions and 316 deletions

View File

@@ -10,12 +10,17 @@ const TextStatusRoot = styled.span`
${(props) => ${(props) =>
props.intent === 'warning' && props.intent === 'warning' &&
` `
color: #ec5b0a;`} color: #c87619;`}
${(props) =>
props.intent === 'danger' &&
`
color: #f17377;`}
${(props) => ${(props) =>
props.intent === 'success' && props.intent === 'success' &&
` `
color: #2ba01d;`} color: #238551;`}
${(props) => ${(props) =>
props.intent === 'none' && props.intent === 'none' &&

View File

@@ -1,7 +1,5 @@
// @ts-nocheck import React, { KeyboardEvent, ReactNode } from 'react';
import React from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import classNames from 'classnames';
import { isUndefined } from 'lodash'; import { isUndefined } from 'lodash';
import { import {
Overlay, Overlay,
@@ -10,11 +8,14 @@ import {
MenuItem, MenuItem,
Spinner, Spinner,
Intent, Intent,
OverlayProps,
Button,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { QueryList } from '@blueprintjs/select'; import { QueryList, ItemRenderer } from '@blueprintjs/select';
import { CLASSES } from '@/constants/classes'; import { x } from '@xstyled/emotion';
import { css } from '@emotion/css';
import { Icon, If, ListSelect, FormattedMessage as T } from '@/components'; import { Icon, If, FormattedMessage as T } from '@/components';
import { Select } from '@blueprintjs-formik/select';
import { import {
UniversalSearchProvider, UniversalSearchProvider,
useUniversalSearchContext, useUniversalSearchContext,
@@ -22,59 +23,297 @@ import {
import { filterItemsByResourceType } from './utils'; import { filterItemsByResourceType } from './utils';
import { RESOURCES_TYPES } from '@/constants/resourcesTypes'; 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. * Universal search input action.
*/ */
function UniversalSearchInputRightElements({ onSearchTypeChange }) { function UniversalSearchInputRightElements({
const { isLoading, searchType, defaultSearchResource, searchTypeOptions } = onSearchTypeChange,
}: UniversalSearchInputRightElementsProps) {
const { isLoading, searchType, searchTypeOptions } =
useUniversalSearchContext(); useUniversalSearchContext();
// Find the currently selected item object.
const selectedItem = searchTypeOptions.find(
(item) => item.key === searchType,
);
// Handle search type option change. // Handle search type option change.
const handleSearchTypeChange = (option) => { const handleSearchTypeChange = (option: SearchTypeOption) => {
onSearchTypeChange && onSearchTypeChange(option); 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 ( return (
<div className={CLASSES.UNIVERSAL_SEARCH_INPUT_RIGHT_ELEMENTS}> <x.div display="flex" m="10px" className={inputRightElementsStyles}>
<If condition={isLoading}> <If condition={isLoading}>
<Spinner tagName="div" intent={Intent.NONE} size={18} value={null} /> <Spinner tagName="div" intent={Intent.NONE} size={18} />
</If> </If>
<ListSelect <Select<SearchTypeOption>
items={searchTypeOptions} items={searchTypeOptions}
itemRenderer={itemRenderer}
onItemSelect={handleSearchTypeChange} onItemSelect={handleSearchTypeChange}
selectedValue={selectedItem?.key}
valueAccessor={'key'}
labelAccessor={'label'}
filterable={false} filterable={false}
initialSelectedItem={defaultSearchResource}
selectedItem={searchType}
selectedItemProp={'key'}
textProp={'label'}
// defaultText={intl.get('type')}
popoverProps={{ popoverProps={{
minimal: true, minimal: true,
captureDismiss: 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. * Universal search query list.
*/ */
function UniversalSearchQueryList(props) { function UniversalSearchQueryList({
const { isOpen, isLoading, onSearchTypeChange, searchType, ...restProps } = isOpen,
props; isLoading,
onSearchTypeChange,
...restProps
}: UniversalSearchQueryListProps) {
return ( return (
<QueryList <QueryList<UniversalSearchItem>
{...restProps} {...(restProps as any)}
initialContent={null} initialContent={null}
renderer={(listProps) => ( renderer={(listProps: QueryListRendererProps) => (
<UniversalSearchBar <UniversalSearchBar
isOpen={isOpen} isOpen={isOpen}
onSearchTypeChange={onSearchTypeChange} onSearchTypeChange={onSearchTypeChange}
@@ -100,47 +339,53 @@ function UniversalSearchQueryList(props) {
*/ */
function UniversalQuerySearchActions() { function UniversalQuerySearchActions() {
return ( return (
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTIONS)}> <x.div display="flex">
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTION_SELECT)}> <x.div className={actionBaseStyles}>
<Tag>ENTER</Tag> <Tag>ENTER</Tag>
<span class={'text'}>{intl.get('universal_search.enter_text')}</span> <x.span ml="6px">{intl.get('universal_search.enter_text')}</x.span>
</div> </x.div>
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTION_CLOSE)}> <x.div className={actionBaseStyles}>
<Tag>ESC</Tag>{' '} <Tag>ESC</Tag>{' '}
<span class={'text'}>{intl.get('universal_search.close_text')}</span> <x.span ml="6px">{intl.get('universal_search.close_text')}</x.span>
</div> </x.div>
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTION_ARROWS)}> <x.div className={actionArrowsStyles}>
<Tag> <Tag>
<Icon icon={'arrow-up-24'} iconSize={16} /> <Icon icon={'arrow-up-24'} iconSize={16} />
</Tag> </Tag>
<Tag> <Tag>
<Icon icon={'arrow-down-24'} iconSize={16} /> <Icon icon={'arrow-down-24'} iconSize={16} />
</Tag> </Tag>
<span class="text">{intl.get('universal_seach.navigate_text')}</span> <x.span ml="6px">{intl.get('universal_seach.navigate_text')}</x.span>
</div> </x.div>
</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. * Universal search input bar with items list.
*/ */
function UniversalSearchBar({ isOpen, onSearchTypeChange, ...listProps }) { function UniversalSearchBar({
isOpen,
onSearchTypeChange,
...listProps
}: UniversalSearchBarProps) {
const { handleKeyDown, handleKeyUp } = listProps; const { handleKeyDown, handleKeyUp } = listProps;
const handlers = isOpen const handlers = isOpen
? { onKeyDown: handleKeyDown, onKeyUp: handleKeyUp } ? { onKeyDown: handleKeyDown, onKeyUp: handleKeyUp }
: {}; : {};
return ( return (
<div <x.div {...handlers}>
className={classNames(
CLASSES.UNIVERSAL_SEARCH_OMNIBAR,
listProps.className,
)}
{...handlers}
>
<InputGroup <InputGroup
large={true} large={true}
leftIcon={<Icon icon={'universal-search'} iconSize={20} />} leftIcon={<Icon icon={'universal-search'} iconSize={20} />}
@@ -155,17 +400,44 @@ function UniversalSearchBar({ isOpen, onSearchTypeChange, ...listProps }) {
autoFocus={true} autoFocus={true}
/> />
{listProps.itemList} {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. * Universal search.
*/ */
export function UniversalSearch({ export function UniversalSearch({
defaultSearchResource, defaultSearchResource,
searchResource, searchResource,
overlayProps, overlayProps,
isOpen, isOpen,
isLoading, isLoading,
@@ -173,9 +445,9 @@ export function UniversalSearch({
items, items,
searchTypeOptions, searchTypeOptions,
...queryListProps ...queryListProps
}) { }: UniversalSearchProps) {
// Search type state. // Search type state.
const [searchType, setSearchType] = React.useState( const [searchType, setSearchType] = React.useState<ResourceType>(
defaultSearchResource || RESOURCES_TYPES.CUSTOMER, defaultSearchResource || RESOURCES_TYPES.CUSTOMER,
); );
// Handle search resource type controlled mode. // Handle search resource type controlled mode.
@@ -189,9 +461,9 @@ export function UniversalSearch({
}, [searchResource, defaultSearchResource]); }, [searchResource, defaultSearchResource]);
// Handle search type change. // Handle search type change.
const handleSearchTypeChange = (searchTypeResource) => { const handleSearchTypeChange = (searchTypeResource: SearchTypeOption) => {
setSearchType(searchTypeResource.key); setSearchType(searchTypeResource.key);
onSearchTypeChange && onSearchTypeChange(searchTypeResource); onSearchTypeChange?.(searchTypeResource);
}; };
// Filters query list items based on the given search type. // Filters query list items based on the given search type.
const filteredItems = filterItemsByResourceType(items, searchType); const filteredItems = filterItemsByResourceType(items, searchType);
@@ -200,7 +472,7 @@ export function UniversalSearch({
<Overlay <Overlay
hasBackdrop={true} hasBackdrop={true}
isOpen={isOpen} isOpen={isOpen}
className={classNames(CLASSES.UNIVERSAL_SEARCH_OVERLAY)} className={overlayStyles}
{...overlayProps} {...overlayProps}
> >
<UniversalSearchProvider <UniversalSearchProvider
@@ -209,7 +481,7 @@ export function UniversalSearch({
defaultSearchResource={defaultSearchResource} defaultSearchResource={defaultSearchResource}
searchTypeOptions={searchTypeOptions} searchTypeOptions={searchTypeOptions}
> >
<div className={classNames(CLASSES.UNIVERSAL_SEARCH)}> <x.div className={containerStyles}>
<UniversalSearchQueryList <UniversalSearchQueryList
isOpen={isOpen} isOpen={isOpen}
isLoading={isLoading} isLoading={isLoading}
@@ -218,10 +490,10 @@ export function UniversalSearch({
{...queryListProps} {...queryListProps}
items={filteredItems} items={filteredItems}
/> />
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_FOOTER)}> <x.div className={footerStyles}>
<UniversalQuerySearchActions /> <UniversalQuerySearchActions />
</div> </x.div>
</div> </x.div>
</UniversalSearchProvider> </UniversalSearchProvider>
</Overlay> </Overlay>
); );

View File

@@ -1,30 +1,82 @@
// @ts-nocheck import React, { createContext, ReactNode, useContext } from 'react';
import React, { createContext } 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. * Universal search data provider.
*/ */
function UniversalSearchProvider({ export function UniversalSearchProvider({
isLoading, isLoading,
defaultSearchResource, defaultSearchResource,
searchType, searchType,
searchTypeOptions, searchTypeOptions,
...props children,
}) { }: UniversalSearchProviderProps) {
// Provider payload. // Provider payload.
const provider = { const provider: UniversalSearchContextValue = {
isLoading, isLoading,
searchType, searchType,
defaultSearchResource, defaultSearchResource,
searchTypeOptions, 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;
};

View File

@@ -1,10 +1,10 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { MenuItem } from '@blueprintjs/core'; import { MenuItem, Intent } from '@blueprintjs/core';
import { formattedAmount } from '@/utils'; 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 { RESOURCES_TYPES } from '@/constants/resourcesTypes';
import { AbilitySubject, BillAction } from '@/constants/abilityOption'; import { AbilitySubject, BillAction } from '@/constants/abilityOption';
@@ -41,35 +41,35 @@ export function BillStatus({ bill }) {
return ( return (
<Choose> <Choose>
<Choose.When condition={bill.is_fully_paid && bill.is_open}> <Choose.When condition={bill.is_fully_paid && bill.is_open}>
<span class="fully-paid-text"> <TextStatus intent={Intent.SUCCESS}>
<T id={'paid'} /> <T id={'paid'} />
</span> </TextStatus>
</Choose.When> </Choose.When>
<Choose.When condition={bill.is_open}> <Choose.When condition={bill.is_open}>
<Choose> <Choose>
<Choose.When condition={bill.is_overdue}> <Choose.When condition={bill.is_overdue}>
<span className={'overdue-status'}> <TextStatus intent={Intent.DANGER}>
{intl.get('overdue_by', { overdue: bill.overdue_days })} {intl.get('overdue_by', { overdue: bill.overdue_days })}
</span> </TextStatus>
</Choose.When> </Choose.When>
<Choose.Otherwise> <Choose.Otherwise>
<span className={'due-status'}> <TextStatus intent={Intent.WARNING}>
{intl.get('due_in', { due: bill.remaining_days })} {intl.get('due_in', { due: bill.remaining_days })}
</span> </TextStatus>
</Choose.Otherwise> </Choose.Otherwise>
</Choose> </Choose>
<If condition={bill.is_partially_paid}> <If condition={bill.is_partially_paid}>
<span className="partial-paid"> <TextStatus intent={Intent.WARNING}>
{intl.get('day_partially_paid', { {intl.get('day_partially_paid', {
due: formattedAmount(bill.due_amount, bill.currency_code), due: formattedAmount(bill.due_amount, bill.currency_code),
})} })}
</span> </TextStatus>
</If> </If>
</Choose.When> </Choose.When>
<Choose.Otherwise> <Choose.Otherwise>
<span class="draft"> <TextStatus intent={Intent.NONE}>
<T id={'draft'} /> <T id={'draft'} />
</span> </TextStatus>
</Choose.Otherwise> </Choose.Otherwise>
</Choose> </Choose>
); );

View File

@@ -1,9 +1,9 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import intl from 'react-intl-universal'; 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 { RESOURCES_TYPES } from '@/constants/resourcesTypes';
import { AbilitySubject, SaleEstimateAction } from '@/constants/abilityOption'; import { AbilitySubject, SaleEstimateAction } from '@/constants/abilityOption';
@@ -37,28 +37,28 @@ export const EstimateUniversalSearchSelect = withDrawerActions(
export const EstimateStatus = ({ estimate }) => ( export const EstimateStatus = ({ estimate }) => (
<Choose> <Choose>
<Choose.When condition={estimate.is_delivered && estimate.is_approved}> <Choose.When condition={estimate.is_delivered && estimate.is_approved}>
<span class="approved"> <TextStatus intent={Intent.SUCCESS}>
<T id={'approved'} /> <T id={'approved'} />
</span> </TextStatus>
</Choose.When> </Choose.When>
<Choose.When condition={estimate.is_delivered && estimate.is_rejected}> <Choose.When condition={estimate.is_delivered && estimate.is_rejected}>
<span class="reject"> <TextStatus intent={Intent.DANGER}>
<T id={'rejected'} /> <T id={'rejected'} />
</span> </TextStatus>
</Choose.When> </Choose.When>
<Choose.When <Choose.When
condition={ condition={
estimate.is_delivered && !estimate.is_rejected && !estimate.is_approved estimate.is_delivered && !estimate.is_rejected && !estimate.is_approved
} }
> >
<span class="delivered"> <TextStatus intent={Intent.SUCCESS}>
<T id={'delivered'} /> <T id={'delivered'} />
</span> </TextStatus>
</Choose.When> </Choose.When>
<Choose.Otherwise> <Choose.Otherwise>
<span class="draft"> <TextStatus intent={Intent.NONE}>
<T id={'draft'} /> <T id={'draft'} />
</span> </TextStatus>
</Choose.Otherwise> </Choose.Otherwise>
</Choose> </Choose>
); );

View File

@@ -1,9 +1,9 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import intl from 'react-intl-universal'; 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 { highlightText } from '@/utils';
import { RESOURCES_TYPES } from '@/constants/resourcesTypes'; import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
@@ -39,29 +39,29 @@ function InvoiceStatus({ customer }) {
return ( return (
<Choose> <Choose>
<Choose.When condition={customer.is_fully_paid && customer.is_delivered}> <Choose.When condition={customer.is_fully_paid && customer.is_delivered}>
<span class="status status-success"> <TextStatus intent={Intent.SUCCESS}>
<T id={'paid'} /> <T id={'paid'} />
</span> </TextStatus>
</Choose.When> </Choose.When>
<Choose.When condition={customer.is_delivered}> <Choose.When condition={customer.is_delivered}>
<Choose> <Choose>
<Choose.When condition={customer.is_overdue}> <Choose.When condition={customer.is_overdue}>
<span className={'status status-warning'}> <TextStatus intent={Intent.DANGER}>
{intl.get('overdue_by', { overdue: customer.overdue_days })} {intl.get('overdue_by', { overdue: customer.overdue_days })}
</span> </TextStatus>
</Choose.When> </Choose.When>
<Choose.Otherwise> <Choose.Otherwise>
<span className={'status status-warning'}> <TextStatus intent={Intent.WARNING}>
{intl.get('due_in', { due: customer.remaining_days })} {intl.get('due_in', { due: customer.remaining_days })}
</span> </TextStatus>
</Choose.Otherwise> </Choose.Otherwise>
</Choose> </Choose>
</Choose.When> </Choose.When>
<Choose.Otherwise> <Choose.Otherwise>
<span class="status status--gray"> <TextStatus intent={Intent.NONE}>
<T id={'draft'} /> <T id={'draft'} />
</span> </TextStatus>
</Choose.Otherwise> </Choose.Otherwise>
</Choose> </Choose>
); );
@@ -94,7 +94,6 @@ export function InvoiceUniversalSearchItem(
</> </>
} }
onClick={handleClick} onClick={handleClick}
className={'universal-search__item--invoice'}
/> />
); );
} }

View File

@@ -1,9 +1,8 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { MenuItem } from '@blueprintjs/core'; import { MenuItem, Intent } from '@blueprintjs/core';
import { Icon, Choose, T, TextStatus } from '@/components';
import { Icon, Choose, T } from '@/components';
import { RESOURCES_TYPES } from '@/constants/resourcesTypes'; import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
import { AbilitySubject, SaleReceiptAction } from '@/constants/abilityOption'; import { AbilitySubject, SaleReceiptAction } from '@/constants/abilityOption';
import { withDrawerActions } from '@/containers/Drawer/withDrawerActions'; import { withDrawerActions } from '@/containers/Drawer/withDrawerActions';
@@ -39,15 +38,15 @@ function ReceiptStatus({ receipt }) {
return ( return (
<Choose> <Choose>
<Choose.When condition={receipt.is_closed}> <Choose.When condition={receipt.is_closed}>
<span class="closed"> <TextStatus intent={Intent.SUCCESS}>
<T id={'closed'} /> <T id={'closed'} />
</span> </TextStatus>
</Choose.When> </Choose.When>
<Choose.Otherwise> <Choose.Otherwise>
<span class="draft"> <TextStatus intent={Intent.NONE}>
<T id={'draft'} /> <T id={'draft'} />
</span> </TextStatus>
</Choose.Otherwise> </Choose.Otherwise>
</Choose> </Choose>
); );

View File

@@ -31,7 +31,6 @@
@import 'components/Overlay'; @import 'components/Overlay';
@import 'components/Menu'; @import 'components/Menu';
@import 'components/SidebarOverlay'; @import 'components/SidebarOverlay';
@import 'components/UniversalSearch';
// Pages // Pages
@import 'pages/view-form'; @import 'pages/view-form';

View File

@@ -7,6 +7,27 @@ $ns: bp4;
--color-primary: #8abbff; --color-primary: #8abbff;
--color-danger: red; --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-gray5: #404854;
--color-dark-gray4: #383e47; --color-dark-gray4: #383e47;
--color-dark-gray3: #2f343c; --color-dark-gray3: #2f343c;
@@ -301,6 +322,27 @@ body.bp4-dark {
--color-primary: #8abbff; --color-primary: #8abbff;
--color-danger: rgb(213, 103, 103); --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-gray5: #404854;
--color-dark-gray4: #383e47; --color-dark-gray4: #383e47;
--color-dark-gray3: #2f343c; --color-dark-gray3: #2f343c;

View File

@@ -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;
}
}
}
}
}