Skip to content

Commit

Permalink
🛠 Upgrade the colors of tailwind input components + 🛠 Tailwind Consul…
Browse files Browse the repository at this point in the history
…tation Form + 🛠 Tailiwnd discharge patient dialog (#4309)

* momentarily copied secondary until #4307 merged

* upgrade textarea colors as per figma

* consultation: replace mui label and textarea w. tw

* tailwind discharge modal

* add hints to discharge modal

* upgrade `SelectMenuV2`

* upgrade `TextFormField`

* upgrade `MultiSelectMenuV2`

* upgrade AutoCompleteAsync colors

* use CareIcons

* upgrade `DateInputV2`

* upgrade PhoneInput style to perfection

* place clear inside the phone input

* fix padding

* fix `SelectMenuV2` conflict

* add missing verified by label

* Add form field comp: `PatientCategorySelect`

* use PatientCategorySelect in consultation form

* cleanup patient category tw-class mess

* tw-comp: `SelectMenuFormField`

* Upgrade consultation form

* tw-comp: `AutocompleteMultiselect`

* tw-comp: `AutocompleteV2` (single select, but not form field yet)

* add hook: `useAsyncOptions` for use in async dropdowns

* tw-comp: `DiagnosisAutocompleteFormField`

* MultiSelect, AutocompleteMulti: option chip + remove

* consultation form: upgrade diag. and prov. diag.

* replicate figma page layout for consultation

* fix form submission

* empty

* rename `SelectMenuFormField` -> `SelectFormField`

* tw-comp: `MultiSelectFormField`

* Deprecate old `DateRangePicker`

* tw-comp: `SymptomsSelect` Form Field

* show helpful descriptions in `SymptomsSelect`

* consultation form use new `SymptomsSelect`

* so far it never felt the date input was getting disabled?

* let the dropdown be near the calendar for less mouse movement

* consultation form, use `DateFormFields`

* hide disabled fields

* introduce style: `cui-input-base`

* migrate select and multiselect to new colors

* migrate text form field and text area

* remove conflicting classes

* BRING BACK ORIGINAL GRAY PALLETE

* update Form Field label color

* ensure cui-input-base is properly applied

* migrate autocomplete

* hack: make online user select to look consistent with the form

* migrate autocomplete async design

* improve consistency on dropdown options

* adds class: `cui-dropdown-base`

* migrate date picker

* check-circle

* `cui-input-base` for prescription builder

* `cui-input-base` for investigation builder

* fix memory loss of diagnosis select

* autocomplete show loading or nothing found

* document: `useAsyncOptions`

* fix issues w. state handling of DiagnosisSelect
  • Loading branch information
rithviknishad committed Dec 22, 2022
1 parent 0333752 commit 397a2ce
Show file tree
Hide file tree
Showing 40 changed files with 1,348 additions and 1,024 deletions.
2 changes: 1 addition & 1 deletion src/CAREUI/interactive/LegendInput.tsx
Expand Up @@ -127,7 +127,7 @@ export default function LegendInput(props: InputProps) {
required={props.required}
autoComplete={props.autoComplete}
className={classNames(
"w-full bg-gray-50 focus:bg-gray-100 cui-input",
"w-full border-gray-300 rounded-md shadow-sm bg-gray-50 focus:bg-gray-100 cui-input",
props.size === "small" && "text-xs px-3 py-2",
(!props.size || !["small", "large"].includes(props.size)) &&
"px-4 py-3",
Expand Down
52 changes: 25 additions & 27 deletions src/Common/constants.tsx
Expand Up @@ -210,20 +210,20 @@ export const MEDICAL_HISTORY_CHOICES: Array<OptionsType> = [
];

export const REVIEW_AT_CHOICES: Array<OptionsType> = [
{ id: 30, text: "30 minutes" },
{ id: 60, text: "1 hour" },
{ id: 120, text: "2 hours" },
{ id: 180, text: "3 hours" },
{ id: 240, text: "4 hours" },
{ id: 360, text: "6 hours" },
{ id: 480, text: "8 hours" },
{ id: 720, text: "12 hours" },
{ id: 1440, text: "24 hours" },
{ id: 2160, text: "36 hours" },
{ id: 2880, text: "48 hours" },
];

export const SYMPTOM_CHOICES: Array<OptionsType> = [
{ id: 30, text: "30 mins" },
{ id: 60, text: "1 hr" },
{ id: 120, text: "2 hr" },
{ id: 180, text: "3 hr" },
{ id: 240, text: "4 hr" },
{ id: 360, text: "6 hr" },
{ id: 480, text: "8 hr" },
{ id: 720, text: "12 hr" },
{ id: 1440, text: "24 hr" },
{ id: 2160, text: "36 hr" },
{ id: 2880, text: "48 hr" },
];

export const SYMPTOM_CHOICES = [
{ id: 1, text: "ASYMPTOMATIC" },
{ id: 2, text: "FEVER" },
{ id: 3, text: "SORE THROAT" },
Expand Down Expand Up @@ -287,20 +287,18 @@ export const ADMITTED_TO = [
{ id: "7", text: "Regular" },
];

export const PATIENT_CATEGORIES = [
{ id: "Comfort", text: "Comfort Care" },
{ id: "Stable", text: "Stable" },
{ id: "Moderate", text: "Slightly Abnormal" },
{ id: "Critical", text: "Critical" },
];
export type PatientCategoryID = "Comfort" | "Stable" | "Moderate" | "Critical";

export const PatientCategoryTailwindClass: Record<PatientCategory, string> = {
"Comfort Care": "patient-comfort",
Stable: "patient-stable",
"Slightly Abnormal": "patient-abnormal",
Critical: "patient-critical",
unknown: "patient-unknown",
};
export const PATIENT_CATEGORIES: {
id: PatientCategoryID;
text: PatientCategory;
twClass: string;
}[] = [
{ id: "Comfort", text: "Comfort Care", twClass: "patient-comfort" },
{ id: "Stable", text: "Stable", twClass: "patient-stable" },
{ id: "Moderate", text: "Slightly Abnormal", twClass: "patient-abnormal" },
{ id: "Critical", text: "Critical", twClass: "patient-critical" },
];

export const PATIENT_FILTER_CATEGORIES = PATIENT_CATEGORIES;

Expand Down
109 changes: 109 additions & 0 deletions src/Common/hooks/useAsyncOptions.ts
@@ -0,0 +1,109 @@
import { debounce } from "lodash";
import { useMemo, useState } from "react";
import { useDispatch } from "react-redux";

interface IUseAsyncOptionsArgs {
debounceInterval?: number;
}

/**
* Hook to implement async autocompletes with ease and typesafety.
*
* See `DiagnosisSelectFormField` for usage.
*
* **Example usage:**
* ```jsx
* const { fetchOptions, isLoading, options } = useAsyncOptions<Model>("id");
*
* return (
* <AutocompleteMultiselect
* ...
* options={options(props.value)}
* isLoading={isLoading}
* onQuery={(query) => fetchOptions(action({ query }))}
* optionValue={(option) => option}
* ...
* />
* );
* ```
*/
export function useAsyncOptions<T extends Record<string, unknown>>(
uniqueKey: keyof T,
args?: IUseAsyncOptionsArgs
) {
const dispatch = useDispatch<any>();
const [queryOptions, setQueryOptions] = useState<T[]>([]);
const [isLoading, setIsLoading] = useState(false);

const fetchOptions = useMemo(
() =>
debounce(async (action: any) => {
setIsLoading(true);
const res = await dispatch(action);
if (res?.data) setQueryOptions(res.data as T[]);
setIsLoading(false);
}, args?.debounceInterval ?? 300),
[dispatch, args?.debounceInterval]
);

const mergeValueWithQueryOptions = (selected?: T[]) => {
if (!selected?.length) return queryOptions;

return [
...queryOptions,
...selected.filter(
(obj) => !queryOptions.some((s) => s[uniqueKey] === obj[uniqueKey])
),
];
};

return {
/**
* Merges query options and selected options.
*
* **Example usage:**
* ```jsx
* const { isLoading } = useAsyncOptions<Model>("id");
*
* <AutocompleteMultiselect
* ...
* isLoading={isLoading}
* ...
* />
* ```
*/
fetchOptions,

/**
* Merges query options and selected options.
*
* **Example usage:**
* ```jsx
* const { options } = useAsyncOptions<Model>("id");
*
* <AutocompleteMultiselect
* ...
* onQuery={(query) => fetchOptions(action({ query }))}
* ...
* />
* ```
*/
isLoading,

/**
* Merges query options and selected options.
*
* **Example usage:**
* ```jsx
* const { options } = useAsyncOptions<Model>("id");
*
* <AutocompleteMultiselect
* ...
* options={options(props.value)}
* ...
* />
* ```
*/
options: mergeValueWithQueryOptions,
};
}
53 changes: 26 additions & 27 deletions src/Components/Common/DateInputV2.tsx
Expand Up @@ -12,6 +12,7 @@ import {
import { DropdownTransition } from "./components/HelperComponents";
import { Popover } from "@headlessui/react";
import { classNames } from "../../Utils/utils";
import CareIcon from "../../CAREUI/icons/CareIcon";

type DatePickerType = "date" | "month" | "year";
export type DatePickerPosition = "LEFT" | "RIGHT" | "CENTER";
Expand Down Expand Up @@ -126,10 +127,8 @@ const DateInputV2: React.FC<Props> = ({
year = datePickerHeaderDate.getFullYear()
) => {
const date = new Date(year, month, day);

if (min) if (date < min) return false;
if (max) if (date > max) return false;

return true;
};

Expand Down Expand Up @@ -183,51 +182,52 @@ const DateInputV2: React.FC<Props> = ({
};

return (
<div className={disabled ? "pointer-events-none opacity-0.8" : ""}>
<div>
<div className="container mx-auto text-black">
<Popover className="relative">
<Popover.Button className="w-full">
<Popover.Button disabled={disabled} className="w-full">
<input type="hidden" name="date" />
<input
type="text"
readOnly
className={`text-sm block py-3 px-4 w-full rounded placeholder:text-gray-500 focus:bg-white border-2 focus:border-primary-400 outline-none !ring-0 transition-all duration-200 ease-in-out ${className}`}
placeholder={placeholder ? placeholder : "Select date"}
disabled={disabled}
className={`cui-input-base cursor-pointer disabled:cursor-not-allowed ${className}`}
placeholder={placeholder || "Select date"}
value={value && format(value, "yyyy-MM-dd")}
/>
<div className="cursor-pointer absolute top-1/2 right-0 p-2 -translate-y-1/2">
<i className="fa-regular fa-calendar text-slate-500"></i>
<div className="absolute top-1/2 right-0 p-2 -translate-y-1/2">
<CareIcon className="care-l-calendar-alt text-lg text-gray-600" />
</div>
</Popover.Button>
<DropdownTransition>
<Popover.Panel
className={classNames(
"z-10 w-72 bg-white border border-slate-300 rounded-lg shadow p-4 absolute top-[110%]",
"cui-dropdown-base divide-y-0 w-72 p-4 absolute mt-0.5",
getPosition()
)}
>
<div className="flex justify-between items-center w-full mb-4">
<button
type="button"
disabled={!isDateWithinConstraints()}
className="transition ease-in-out duration-100 p-2 rounded inline-flex items-center justify-center aspect-square cursor-pointer hover:bg-slate-200"
className="transition ease-in-out duration-100 p-2 rounded inline-flex items-center justify-center aspect-square cursor-pointer hover:bg-gray-300"
onClick={decrement}
>
<i className="fa fa-arrow-left" />
<CareIcon className="care-l-angle-left-b text-lg" />
</button>

<div className="flex items-center justify-center text-sm">
{type === "date" && (
<div
onClick={showMonthPicker}
className="py-1 px-3 font-bold text-slate-900 text-center cursor-pointer hover:bg-slate-200 rounded"
className="py-1 px-3 font-medium text-black text-center cursor-pointer hover:bg-gray-300 rounded"
>
{format(datePickerHeaderDate, "MMMM")}
</div>
)}
<div
onClick={showYearPicker}
className="py-1 px-3 font-bold text-gray-900 cursor-pointer hover:bg-slate-200 rounded"
className="py-1 px-3 font-medium text-black cursor-pointer hover:bg-gray-300 rounded"
>
<p className="text-center">
{type == "year"
Expand All @@ -243,18 +243,18 @@ const DateInputV2: React.FC<Props> = ({
new Date().getFullYear() === year.getFullYear()) ||
!isDateWithinConstraints(getLastDay())
}
className="transition ease-in-out duration-100 h-full p-2 rounded inline-flex items-center justify-center aspect-square cursor-pointer hover:bg-slate-200"
className="transition ease-in-out duration-100 p-2 rounded inline-flex items-center justify-center aspect-square cursor-pointer hover:bg-gray-300"
onClick={increment}
>
<i className="fa fa-arrow-right" />
<CareIcon className="care-l-angle-right-b text-lg" />
</button>
</div>
{type === "date" && (
<>
<div className="flex flex-wrap">
<div className="flex flex-wrap mb-3">
{DAYS.map((day) => (
<div key={day} className="aspect-square w-[14.26%]">
<div className="text-slate-600 font-medium text-center text-sm">
<div className="text-gray-800 font-medium text-center text-sm">
{day}
</div>
</div>
Expand All @@ -272,11 +272,11 @@ const DateInputV2: React.FC<Props> = ({
<div
onClick={setDateValue(d)}
className={classNames(
"cursor-pointer flex items-center justify-center text-center h-full text-sm rounded leading-loose transition ease-in-out duration-100 text-slate-900 hover:bg-slate-200",
value &&
isSelectedDate(d) &&
"bg-primary-500 text-slate-100 font-bold",
!isDateWithinConstraints(d) && "!text-slate-300"
"cursor-pointer flex items-center justify-center text-center h-full text-sm rounded leading-loose transition ease-in-out duration-100 text-black",
value && isSelectedDate(d)
? "bg-primary-500 text-white font-bold"
: "hover:bg-gray-300",
!isDateWithinConstraints(d) && "!text-gray-300"
)}
>
{d}
Expand All @@ -294,10 +294,10 @@ const DateInputV2: React.FC<Props> = ({
<div
key={i}
className={classNames(
"cursor-pointer w-1/4 font-semibold py-4 px-2 text-center text-sm rounded-lg hover:bg-slate-200",
"cursor-pointer w-1/4 font-semibold py-4 px-2 text-center text-sm rounded-lg",
value && isSelectedMonth(i)
? "bg-primary-500 text-white"
: "text-slate-700 hover:bg-primary-600"
: "text-gray-700 hover:bg-gray-300"
)}
onClick={setMonthValue(i)}
>
Expand All @@ -323,10 +323,10 @@ const DateInputV2: React.FC<Props> = ({
<div
key={i}
className={classNames(
"cursor-pointer w-1/4 font-semibold py-4 px-2 text-center text-sm rounded-lg hover:bg-slate-200",
"cursor-pointer w-1/4 font-semibold py-4 px-2 text-center text-sm rounded-lg",
value && isSelectedYear(y)
? "bg-primary-500 text-white"
: "text-slate-700 hover:bg-primary-600"
: "text-gray-700 hover:bg-gray-300"
)}
onClick={setYearValue(y)}
>
Expand All @@ -346,7 +346,6 @@ const DateInputV2: React.FC<Props> = ({

DateInputV2.defaultProps = {
position: "CENTER",
className: "bg-gray-200 border-gray-200",
};

export default DateInputV2;
3 changes: 3 additions & 0 deletions src/Components/Common/DateRangePicker.tsx
Expand Up @@ -22,6 +22,9 @@ interface IDateRangePickerProps {
export const getDate = (value: any) =>
value && moment(value).isValid() ? moment(value) : null;

/**
* Deprecated. Use `DateRangeFormField` or `DateFormField` instead.
*/
export const DateRangePicker: React.FC<IDateRangePickerProps> = ({
label,
endDateId = "end_date",
Expand Down
45 changes: 45 additions & 0 deletions src/Components/Common/DiagnosisSelectFormField.tsx
@@ -0,0 +1,45 @@
import { useAsyncOptions } from "../../Common/hooks/useAsyncOptions";
import { listICD11Diagnosis } from "../../Redux/actions";
import { ICD11DiagnosisModel } from "../Facility/models";
import { AutocompleteMutliSelect } from "../Form/FormFields/AutocompleteMultiselect";
import FormField from "../Form/FormFields/FormField";
import {
FormFieldBaseProps,
resolveFormFieldChangeEventHandler,
} from "../Form/FormFields/Utils";

type Props =
// | ({ multiple?: false | undefined } & FormFieldBaseProps<ICD11DiagnosisModel>) // uncomment when single select form field is required and implemented.
{ multiple: true } & FormFieldBaseProps<ICD11DiagnosisModel[]>;

export function DiagnosisSelectFormField(props: Props) {
const { name } = props;
const handleChange = resolveFormFieldChangeEventHandler(props);

const { fetchOptions, isLoading, options } =
useAsyncOptions<ICD11DiagnosisModel>("id");

if (!props.multiple) {
return (
<div className="bg-danger-500 text-white font-bold">
Component not implemented
</div>
);
}

return (
<FormField props={props}>
<AutocompleteMutliSelect
id={props.id}
disabled={props.disabled}
value={props.value || []}
options={options(props.value)}
optionLabel={(option) => option.label}
optionValue={(option) => option}
onQuery={(query) => fetchOptions(listICD11Diagnosis({ query }, ""))}
isLoading={isLoading}
onChange={(value) => handleChange({ name, value })}
/>
</FormField>
);
}

0 comments on commit 397a2ce

Please sign in to comment.