-
Notifications
You must be signed in to change notification settings - Fork 477
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issues/8607/review missed #9259
Changes from 13 commits
cb3f18d
b47daad
4972598
7d0a6c2
7bc58ae
b0d6b44
47b4c4a
3e10890
fb0039d
fd158a9
289e4e6
0a450f2
ad568f9
e4bb04b
b77f123
c6e8d75
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,40 @@ | ||
import dayjs from "dayjs"; | ||
import { Link, navigate } from "raviger"; | ||
import { ReactNode, useCallback, useEffect, useState } from "react"; | ||
import { ReactNode, useEffect, useState } from "react"; | ||
import { useTranslation } from "react-i18next"; | ||
|
||
import Chip from "@/CAREUI/display/Chip"; | ||
import CountBlock from "@/CAREUI/display/Count"; | ||
import FilterBadge from "@/CAREUI/display/FilterBadge"; | ||
import RecordMeta from "@/CAREUI/display/RecordMeta"; | ||
import CareIcon from "@/CAREUI/icons/CareIcon"; | ||
import { AdvancedFilterButton } from "@/CAREUI/interactive/FiltersSlideover"; | ||
|
||
import { Badge } from "@/components/ui/badge"; | ||
|
||
import { Avatar } from "@/components/Common/Avatar"; | ||
import ButtonV2 from "@/components/Common/ButtonV2"; | ||
import { ExportMenu } from "@/components/Common/Export"; | ||
import Loading from "@/components/Common/Loading"; | ||
import Page from "@/components/Common/Page"; | ||
import SearchByMultipleFields from "@/components/Common/SearchByMultipleFields"; | ||
import SortDropdownMenu from "@/components/Common/SortDropdown"; | ||
import Tabs from "@/components/Common/Tabs"; | ||
import { ICD11DiagnosisModel } from "@/components/Diagnosis/types"; | ||
import { getDiagnosesByIds } from "@/components/Diagnosis/utils"; | ||
import FacilitiesSelectDialogue from "@/components/ExternalResult/FacilitiesSelectDialogue"; | ||
import DoctorVideoSlideover from "@/components/Facility/DoctorVideoSlideover"; | ||
import { FacilityModel, PatientCategory } from "@/components/Facility/models"; | ||
import { PhoneNumberValidator } from "@/components/Form/FieldValidators"; | ||
import PhoneNumberFormField from "@/components/Form/FormFields/PhoneNumberFormField"; | ||
import { FieldChangeEvent } from "@/components/Form/FormFields/Utils"; | ||
import SearchInput from "@/components/Form/SearchInput"; | ||
import { | ||
DIAGNOSES_FILTER_LABELS, | ||
DiagnosesFilterKey, | ||
FILTER_BY_DIAGNOSES_KEYS, | ||
} from "@/components/Patient/DiagnosesFilter"; | ||
import PatientFilter from "@/components/Patient/PatientFilter"; | ||
import { isPatientMandatoryDataFilled } from "@/components/Patient/Utils"; | ||
|
||
import useAuthUser from "@/hooks/useAuthUser"; | ||
import useFilters from "@/hooks/useFilters"; | ||
|
@@ -27,36 +51,17 @@ import { | |
} from "@/common/constants"; | ||
import { parseOptionId } from "@/common/utils"; | ||
|
||
import { triggerGoal } from "@/Integrations/Plausible"; | ||
import * as Notification from "@/Utils/Notifications"; | ||
import routes from "@/Utils/request/api"; | ||
|
||
import Chip from "../../CAREUI/display/Chip"; | ||
import CountBlock from "../../CAREUI/display/Count"; | ||
import FilterBadge from "../../CAREUI/display/FilterBadge"; | ||
import RecordMeta from "../../CAREUI/display/RecordMeta"; | ||
import CareIcon from "../../CAREUI/icons/CareIcon"; | ||
import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover"; | ||
import { triggerGoal } from "../../Integrations/Plausible"; | ||
import * as Notification from "../../Utils/Notifications"; | ||
import request from "../../Utils/request/request"; | ||
import useQuery from "../../Utils/request/useQuery"; | ||
import request from "@/Utils/request/request"; | ||
import useQuery from "@/Utils/request/useQuery"; | ||
import { | ||
formatPatientAge, | ||
humanizeStrings, | ||
isAntenatal, | ||
parsePhoneNumber, | ||
} from "../../Utils/utils"; | ||
import { ICD11DiagnosisModel } from "../Diagnosis/types"; | ||
import { getDiagnosesByIds } from "../Diagnosis/utils"; | ||
import FacilitiesSelectDialogue from "../ExternalResult/FacilitiesSelectDialogue"; | ||
import DoctorVideoSlideover from "../Facility/DoctorVideoSlideover"; | ||
import { FacilityModel, PatientCategory } from "../Facility/models"; | ||
import { | ||
DIAGNOSES_FILTER_LABELS, | ||
DiagnosesFilterKey, | ||
FILTER_BY_DIAGNOSES_KEYS, | ||
} from "./DiagnosesFilter"; | ||
import PatientFilter from "./PatientFilter"; | ||
import { isPatientMandatoryDataFilled } from "./Utils"; | ||
} from "@/Utils/utils"; | ||
|
||
interface TabPanelProps { | ||
children?: ReactNode; | ||
|
@@ -90,7 +95,6 @@ export const PatientManager = () => { | |
Pagination, | ||
FilterBadges, | ||
resultsPerPage, | ||
clearSearch, | ||
} = useFilters({ | ||
limit: 12, | ||
cacheBlacklist: [ | ||
|
@@ -107,6 +111,30 @@ export const PatientManager = () => { | |
const [diagnoses, setDiagnoses] = useState<ICD11DiagnosisModel[]>([]); | ||
const [showDialog, setShowDialog] = useState<"create" | "list-discharged">(); | ||
const [showDoctors, setShowDoctors] = useState(false); | ||
const [phoneNumber, _setPhoneNumber] = useState(""); | ||
const [emergencyPhoneNumber, _setEmergencyPhoneNumber] = useState(""); | ||
|
||
const setPhoneNumber = (value: string) => { | ||
_setPhoneNumber(value); | ||
const error = PhoneNumberValidator()(value); | ||
if (!error) { | ||
updateQuery({ phone_number: value }); | ||
} | ||
if ((value === "+91" || value === "") && qParams.phone_number) { | ||
updateQuery({ phone_number: null }); | ||
} | ||
}; | ||
|
||
const setEmergencyPhoneNumber = (value: string) => { | ||
_setEmergencyPhoneNumber(value); | ||
const error = PhoneNumberValidator()(value); | ||
if (!error) { | ||
updateQuery({ emergency_phone_number: value }); | ||
} | ||
if ((value === "+91" || value === "") && qParams.emergency_phone_number) { | ||
updateQuery({ emergency_phone_number: null }); | ||
} | ||
}; | ||
|
||
const tabValue = | ||
qParams.last_consultation__new_discharge_reason || | ||
|
@@ -293,6 +321,14 @@ export const PatientManager = () => { | |
|
||
const { loading: isLoading, data } = useQuery(routes.patientList, { | ||
query: params, | ||
onResponse: () => { | ||
if (!params.phone_number) { | ||
_setPhoneNumber("+91"); | ||
} | ||
if (!params.emergency_phone_number) { | ||
_setEmergencyPhoneNumber("+91"); | ||
} | ||
}, | ||
}); | ||
|
||
const getTheCategoryFromId = () => { | ||
|
@@ -596,15 +632,26 @@ export const PatientManager = () => { | |
)} | ||
{patient.review_time && | ||
!patient.last_consultation?.discharge_date && | ||
Number(patient.last_consultation?.review_interval) > 0 && | ||
dayjs().isAfter(patient.review_time) && ( | ||
<Chip | ||
size="small" | ||
variant="danger" | ||
startIcon="l-clock" | ||
text="Review Missed" | ||
/> | ||
Number(patient.last_consultation?.review_interval) > 0 && ( | ||
<Badge | ||
variant={ | ||
dayjs().isAfter(patient.review_time) | ||
? "purple" | ||
: "destructive" | ||
} | ||
className="flex items-center gap-1" | ||
> | ||
<i className="icon-class l-clock"></i> | ||
{dayjs().isAfter(patient.review_time) | ||
? `Review Missed ${Math.abs( | ||
dayjs().diff(dayjs(patient.review_time), "days"), | ||
)} days ago` | ||
: `Review Due in ${Math.abs( | ||
dayjs(patient.review_time).diff(dayjs(), "days"), | ||
)} days`} | ||
</Badge> | ||
)} | ||
|
||
{patient.last_consultation?.is_readmission && ( | ||
<Chip | ||
size="small" | ||
|
@@ -745,74 +792,22 @@ export const PatientManager = () => { | |
); | ||
} | ||
|
||
const queryField = <T,>(name: string, defaultValue?: T) => { | ||
return { | ||
name, | ||
value: qParams[name] || defaultValue, | ||
onChange: (e: FieldChangeEvent<T>) => updateQuery({ [e.name]: e.value }), | ||
}; | ||
}; | ||
|
||
const onlyAccessibleFacility = | ||
permittedFacilities?.count === 1 ? permittedFacilities.results[0] : null; | ||
|
||
const searchOptions = [ | ||
{ | ||
key: "phone_number", | ||
label: "Phone Number", | ||
type: "phone" as const, | ||
placeholder: "Search_by_phone_number", | ||
value: qParams.phone_number || "", | ||
shortcutKey: "p", | ||
}, | ||
{ | ||
key: "name", | ||
label: "Name", | ||
type: "text" as const, | ||
placeholder: "search_by_patient_name", | ||
value: qParams.name || "", | ||
shortcutKey: "n", | ||
}, | ||
{ | ||
key: "patient_no", | ||
label: "IP/OP No", | ||
type: "text" as const, | ||
placeholder: "search_by_patient_no", | ||
value: qParams.patient_no || "", | ||
shortcutKey: "u", | ||
}, | ||
{ | ||
key: "emergency_contact_number", | ||
label: "Emergency Contact Phone Number", | ||
type: "phone" as const, | ||
placeholder: "search_by_emergency_phone_number", | ||
value: qParams.emergency_phone_number || "", | ||
shortcutKey: "e", | ||
}, | ||
]; | ||
|
||
const handleSearch = useCallback( | ||
(key: string, value: string) => { | ||
const updatedQuery = { | ||
phone_number: | ||
key === "phone_number" | ||
? value.length >= 13 || value === "" | ||
? value | ||
: undefined | ||
: undefined, | ||
name: key === "name" ? value : undefined, | ||
patient_no: key === "patient_no" ? value : undefined, | ||
emergency_phone_number: | ||
key === "emergency_contact_number" | ||
? value.length >= 13 || value === "" | ||
? value | ||
: undefined | ||
: undefined, | ||
}; | ||
|
||
updateQuery(updatedQuery); | ||
}, | ||
[updateQuery], | ||
); | ||
|
||
return ( | ||
<Page | ||
title={t("patients")} | ||
hideBack={true} | ||
breadcrumbs={false} | ||
className="px-4 md:px-6" | ||
options={ | ||
<div className="flex w-full flex-col items-center justify-between lg:flex-row"> | ||
<div className="mb-2 flex w-full flex-col items-center lg:mb-0 lg:w-fit lg:flex-row lg:gap-5"> | ||
|
@@ -991,23 +986,59 @@ export const PatientManager = () => { | |
}} | ||
/> | ||
|
||
<div className="mt-4 gap-4 lg:gap-16 flex flex-col lg:flex-row lg:items-center"> | ||
<div id="total-patientcount"> | ||
<CountBlock | ||
text={t("total_patients")} | ||
count={data?.count || 0} | ||
loading={isLoading} | ||
icon="d-patient" | ||
/> | ||
<div className="manualGrid my-4 mb-[-12px] mt-5 grid-cols-1 gap-3 px-2 sm:grid-cols-4 md:px-0"> | ||
<div className="mt-2 flex h-full flex-col gap-3 xl:flex-row"> | ||
<div className="flex-1" id="total-patientcount"> | ||
<CountBlock | ||
text="Total Patients" | ||
count={data?.count || 0} | ||
loading={isLoading} | ||
icon="l-user-injured" | ||
className="pb-12" | ||
/> | ||
</div> | ||
</div> | ||
<div className="col-span-3 w-full"> | ||
<div className="mt-2"> | ||
<div className="mb-4 mt-1 md:flex md:gap-4"> | ||
<SearchInput | ||
label="Search by Patient" | ||
placeholder="Enter patient name" | ||
{...queryField("name")} | ||
className="w-full grow" | ||
/> | ||
<SearchInput | ||
label="Search by IP/OP Number" | ||
placeholder="Enter IP/OP Number" | ||
secondary | ||
{...queryField("patient_no")} | ||
className="w-full grow" | ||
/> | ||
</div> | ||
<div className="mb-4 md:flex md:gap-4"> | ||
<PhoneNumberFormField | ||
label="Search by Primary Number" | ||
{...queryField("phone_number", "+91")} | ||
value={phoneNumber} | ||
onChange={(e) => setPhoneNumber(e.value)} | ||
types={["mobile", "landline"]} | ||
className="w-full grow" | ||
error={((phoneNumber || "+91") === "+91" && "") || undefined} | ||
/> | ||
<PhoneNumberFormField | ||
label="Search by Emergency Number" | ||
{...queryField("emergency_phone_number", "+91")} | ||
value={emergencyPhoneNumber} | ||
onChange={(e) => setEmergencyPhoneNumber(e.value)} | ||
types={["mobile", "landline"]} | ||
className="w-full" | ||
error={ | ||
((emergencyPhoneNumber || "+91") === "+91" && "") || undefined | ||
} | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
<SearchByMultipleFields | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Merge error here; SearchByMultipleFields should be included instead of the individual search fields. |
||
id="patient-search" | ||
options={searchOptions} | ||
onSearch={handleSearch} | ||
clearSearch={clearSearch} | ||
className="w-full" | ||
/> | ||
</div> | ||
<div className="col-span-3 flex flex-wrap"> | ||
<FilterBadges | ||
|
@@ -1125,10 +1156,7 @@ export const PatientManager = () => { | |
/> | ||
</div> | ||
<div> | ||
<PatientFilter | ||
{...advancedFilter} | ||
key={JSON.stringify(advancedFilter.filter)} | ||
/> | ||
<PatientFilter {...advancedFilter} key={window.location.search} /> | ||
<TabPanel value={tabValue} index={0}> | ||
<div className="mb-4">{managePatients}</div> | ||
</TabPanel> | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,38 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { type VariantProps, cva } from "class-variance-authority"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import * as React from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { cn } from "@/lib/utils"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const badgeVariants = cva( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"inline-flex items-center rounded-md border border-gray-200 px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-gray-950 focus:ring-offset-2 dark:border-gray-800 dark:focus:ring-gray-300", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
variants: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
variant: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
default: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"border-transparent bg-gray-900 text-gray-50 shadow hover:bg-gray-900/80 dark:bg-gray-50 dark:text-gray-900 dark:hover:bg-gray-50/80", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
secondary: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"border-transparent bg-gray-100 text-gray-900 hover:bg-gray-100/80 dark:bg-gray-800 dark:text-gray-50 dark:hover:bg-gray-800/80", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
destructive: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"border-transparent bg-red-500 text-gray-50 shadow hover:bg-red-500/80 dark:bg-red-900 dark:text-gray-50 dark:hover:bg-red-900/80", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
outline: "text-gray-950 dark:text-gray-50", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
purple: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"border-transparent bg-purple-200 text-purple-800 shadow hover:bg-purple-300 dark:bg-purple-900 dark:text-purple-200 dark:hover:bg-purple-800", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
defaultVariants: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
variant: "default", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export interface BadgeProps | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
extends React.HTMLAttributes<HTMLDivElement>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
VariantProps<typeof badgeVariants> {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
function Badge({ className, variant, ...props }: BadgeProps) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className={cn(badgeVariants({ variant }), className)} {...props} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export { Badge, badgeVariants }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Enhance accessibility and documentation. The component implementation is clean, but could benefit from improved accessibility and documentation: +/**
+ * Badge component for displaying status indicators.
+ * @param variant - The visual style variant of the badge
+ * @param className - Additional CSS classes to apply
+ */
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div
+ role="status"
+ aria-label={props['aria-label'] || props.children?.toString()}
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
);
} This change:
📝 Committable suggestion
Suggested change
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Merge error; Part of SearchByMultipleFields, and as such should be included.