Skip to content

Commit

Permalink
SJT-105 Remove separation of stock item and services in billing form
Browse files Browse the repository at this point in the history
  • Loading branch information
Samstar10 committed Nov 28, 2024
1 parent 2cdefbe commit 7ccfa2e
Show file tree
Hide file tree
Showing 10 changed files with 531 additions and 365 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"react-router-dom": "^6.14.1",
"rxjs": "^6.6.7",
"swc-loader": "^0.2.3",
"swr": "^2.2.4",
"turbo": "^1.12.4",
"typescript": "^4.9.5",
"webpack": "^5.88.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/esm-billing-app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ehospital/esm-billing-app",
"version": "1.0.8",
"version": "1.0.9",
"description": "Billing frontend module for use in O3",
"browser": "dist/ehospital-esm-billing-app.js",
"main": "src/index.ts",
Expand Down
187 changes: 187 additions & 0 deletions packages/esm-billing-app/src/autosuggest/autosuggest.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import React, { type HTMLAttributes, useEffect, useRef, useState } from 'react';
import { Layer, Search, type SearchProps } from '@carbon/react';
import classNames from 'classnames';
import styles from './autosuggest.scss';

// FIXME Temporarily included types from Carbon
type InputPropsBase = Omit<HTMLAttributes<HTMLInputElement>, 'onChange'>;

interface SearchProps extends InputPropsBase {
/**
* Specify an optional value for the `autocomplete` property on the underlying
* `<input>`, defaults to "off"
*/
autoComplete?: string;

/**
* Specify an optional className to be applied to the container node
*/
className?: string;

/**
* Specify a label to be read by screen readers on the "close" button
*/
closeButtonLabelText?: string;

/**
* Optionally provide the default value of the `<input>`
*/
defaultValue?: string | number;

/**
* Specify whether the `<input>` should be disabled
*/
disabled?: boolean;

/**
* Specify whether or not ExpandableSearch should render expanded or not
*/
isExpanded?: boolean;

/**
* Specify a custom `id` for the input
*/
id?: string;

/**
* Provide the label text for the Search icon
*/
labelText: React.ReactNode;

/**
* Optional callback called when the search value changes.
*/
onChange?(e: { target: HTMLInputElement; type: 'change' }): void;

/**
* Optional callback called when the search value is cleared.
*/
onClear?(): void;

/**
* Optional callback called when the magnifier icon is clicked in ExpandableSearch.
*/
onExpand?(e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>): void;

/**
* Provide an optional placeholder text for the Search.
* Note: if the label and placeholder differ,
* VoiceOver on Mac will read both
*/
placeholder?: string;

/**
* Rendered icon for the Search.
* Can be a React component class
*/
renderIcon?: React.ComponentType | React.FunctionComponent;

/**
* Specify the role for the underlying `<input>`, defaults to `searchbox`
*/
role?: string;

/**
* Specify the size of the Search
*/
size?: 'sm' | 'md' | 'lg';

/**
* Optional prop to specify the type of the `<input>`
*/
type?: string;

/**
* Specify the value of the `<input>`
*/
value?: string | number;
}

interface AutosuggestProps extends SearchProps {
getDisplayValue: Function;
getFieldValue: Function;
getSearchResults: (query: string) => Promise<any>;
onSuggestionSelected: (field: string, value: string) => void;
invalid?: boolean | undefined;
invalidText?: string | undefined;
}

export const Autosuggest: React.FC<AutosuggestProps> = ({
getDisplayValue,
getFieldValue,
getSearchResults,
onSuggestionSelected,
invalid,
invalidText,
...searchProps
}) => {
const [suggestions, setSuggestions] = useState([]);
const searchBox = useRef(null);
const wrapper = useRef(null);
const { id: name, labelText } = searchProps;

useEffect(() => {
document.addEventListener('mousedown', handleClickOutsideComponent);

return () => {
document.removeEventListener('mousedown', handleClickOutsideComponent);
};
}, [wrapper]);

const handleClickOutsideComponent = (e) => {
if (wrapper.current && !wrapper.current.contains(e.target)) {
setSuggestions([]);
}
};

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const query = e.target.value;
onSuggestionSelected(name, undefined);

if (query) {
getSearchResults(query).then((suggestions) => {
setSuggestions(suggestions);
});
} else {
setSuggestions([]);
}
};

const handleClear = (e: React.ChangeEvent<HTMLInputElement>) => {
onSuggestionSelected(name, undefined);
};

const handleClick = (index: number) => {
const display = getDisplayValue(suggestions[index]);
const value = getFieldValue(suggestions[index]);
searchBox.current.value = display;
onSuggestionSelected(name, value);
setSuggestions([]);
};

return (
<div className={styles.autocomplete} ref={wrapper}>
<label className="cds--label">{labelText}</label>
<Layer className={classNames({ [styles.invalid]: invalid })}>
<Search
id="autosuggest"
onChange={handleChange}
onClear={handleClear}
ref={searchBox}
className={styles.autocompleteSearch}
{...searchProps}
/>
</Layer>
{suggestions.length > 0 && (
<ul className={styles.suggestions}>
{suggestions.map((suggestion, index) => (
<li key={index} onClick={(e) => handleClick(index)} role="presentation">
{getDisplayValue(suggestion)}
</li>
))}
</ul>
)}
{invalid ? <label className={classNames(styles.invalidMsg)}>{invalidText}</label> : <></>}
</div>
);
};
62 changes: 62 additions & 0 deletions packages/esm-billing-app/src/autosuggest/autosuggest.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
@use '@carbon/styles/scss/spacing';
@use '@carbon/styles/scss/type';
@use '@openmrs/esm-styleguide/src/vars' as *;

.label01 {
@include type.type-style('label-01');
}

.suggestions {
position: relative;
border-top-width: 0;
list-style: none;
margin-top: 0;
max-height: 143px;
overflow-y: auto;
padding-left: 0;
width: 100%;
position: absolute;
left: 0;
background-color: #fff;
margin-bottom: 20px;
z-index: 99;
}

.suggestions li {
padding: spacing.$spacing-05;
line-height: 1.29;
color: #525252;
border-bottom: 1px solid #8d8d8d;
}

.suggestions li:hover {
background-color: #e5e5e5;
color: #161616;
cursor: pointer;
}

.suggestions li:not(:last-of-type) {
border-bottom: 1px solid #999;
}

.autocomplete {
position: relative;
}

.autocompleteSearch {
width: 100%;
}

.suggestions a {
color: inherit;
text-decoration: none;
}

.invalid input {
outline: 2px solid var(--cds-support-error, #da1e28);
outline-offset: -2px;
}

.invalidMsg {
color: var(--cds-text-error, #da1e28);
}
Loading

0 comments on commit 7ccfa2e

Please sign in to comment.