diff --git a/src/components/ClaimsItemsBuilder.tsx b/src/components/ClaimsItemsBuilder.tsx new file mode 100644 index 0000000..a2c3599 --- /dev/null +++ b/src/components/ClaimsItemsBuilder.tsx @@ -0,0 +1,165 @@ +import { useTranslation } from "react-i18next"; + +import CareIcon from "@/CAREUI/icons/CareIcon"; + +import ButtonV2 from "@/components/Common/ButtonV2"; +import AutocompleteFormField from "@/components/Form/FormFields/Autocomplete"; +import FormField, { FieldLabel } from "@/components/Form/FormFields/FormField"; +import TextFormField from "@/components/Form/FormFields/TextFormField"; +import { + FieldChangeEvent, + FormFieldBaseProps, + useFormFieldPropsResolver, +} from "@/components/Form/FormFields/Utils"; + +import { ITEM_CATEGORIES } from "../constants"; +import { HCXItemModel } from "../types"; +import PMJAYProcedurePackageAutocomplete from "./PMJAYProcedurePackageAutocomplete"; + +type Props = FormFieldBaseProps; + +export default function ClaimsItemsBuilder(props: Props) { + const { t } = useTranslation(); + + const field = useFormFieldPropsResolver(props); + + const handleUpdate = (index: number) => { + return (event: FieldChangeEvent) => { + if (event.name === "hbp") { + field.handleChange( + (props.value || [])?.map((obj, i) => + i === index + ? { + ...obj, + id: event.value.code, + name: event.value.name, + price: event.value.price, + } + : obj, + ), + ); + } else { + field.handleChange( + (props.value || [])?.map((obj, i) => + i === index ? { ...obj, [event.name]: event.value } : obj, + ), + ); + } + }; + }; + + const handleRemove = (index: number) => { + return () => { + field.handleChange((props.value || [])?.filter((obj, i) => i !== index)); + }; + }; + + return ( + +
+ {props.value?.map((obj, index) => { + return ( +
+
+ + {t("claim__item")} {index + 1} + + {!props.disabled && ( + + {t("remove")} + + + )} +
+ +
+ o.display} + optionValue={(o) => o.code} + value={obj.category} + onChange={handleUpdate(index)} + disabled={props.disabled} + errorClassName="hidden" + /> + +
+ {obj.category === "HBP" && !obj.id ? ( + <> + + + ) : ( + <> + + + + handleUpdate(index)({ + name: event.name, + value: parseFloat(event.value), + }) + } + disabled={props.disabled} + errorClassName="hidden" + /> + + )} +
+
+
+ ); + })} +
+
+ ); +} diff --git a/src/components/CreateClaimCard.tsx b/src/components/CreateClaimCard.tsx new file mode 100644 index 0000000..f464059 --- /dev/null +++ b/src/components/CreateClaimCard.tsx @@ -0,0 +1,267 @@ +import { useState } from "react"; +import { useTranslation } from "react-i18next"; + +import CareIcon from "@/CAREUI/icons/CareIcon"; + +import ButtonV2, { Submit } from "@/components/Common/ButtonV2"; +import DialogModal from "@/components/Common/Dialog"; +import { ProcedureType } from "@/components/Common/prescription-builder/ProcedureBuilder"; +import { SelectFormField } from "@/components/Form/FormFields/SelectFormField"; +import PatientInsuranceDetailsEditor from "@/components/HCX/PatientInsuranceDetailsEditor"; +import HCXPolicyEligibilityCheck from "@/components/HCX/PolicyEligibilityCheck"; +import { HCXPolicyModel } from "@/components/HCX/models"; + +import * as Notification from "@/Utils/Notifications"; +import coreRoutes from "@/Utils/request/api"; +import request from "@/Utils/request/request"; +import useQuery from "@/Utils/request/useQuery"; +import { classNames, formatCurrency } from "@/Utils/utils"; + +import routes from "../api"; +import { HCXClaimModel, HCXItemModel } from "../types"; +import ClaimCreatedModal from "./ClaimCreatedModal"; +import ClaimsItemsBuilder from "./ClaimsItemsBuilder"; + +interface Props { + consultationId: string; + patientId: string; + setIsCreating: (creating: boolean) => void; + isCreating: boolean; + use?: "preauthorization" | "claim"; +} + +export default function CreateClaimCard({ + consultationId, + patientId, + setIsCreating, + isCreating, + use = "preauthorization", +}: Props) { + const { t } = useTranslation(); + + const [showAddPolicy, setShowAddPolicy] = useState(false); + const [policy, setPolicy] = useState(); + const [items, setItems] = useState(); + const [itemsError, setItemsError] = useState(); + const [createdClaim, setCreatedClaim] = useState(); + const [use_, setUse_] = useState(use); + + const { res: consultationRes, data: consultationData } = useQuery( + coreRoutes.getConsultation, + { pathParams: { id: consultationId }, prefetch: !!consultationId }, + ); + + const autoFill = async (policy?: HCXPolicyModel) => { + if (!policy) { + setItems([]); + return; + } + + const { res, data: latestApprovedPreAuth } = await request( + routes.hcx.claims.list, + { + query: { + consultation: consultationId, + policy: policy.id, + ordering: "-modified_date", + use: "preauthorization", + outcome: "complete", + limit: 1, + }, + }, + ); + + if (res?.ok && latestApprovedPreAuth?.results.length !== 0) { + setItems(latestApprovedPreAuth?.results[0].items ?? []); + return; + } + if (consultationRes?.ok && Array.isArray(consultationData?.procedure)) { + setItems( + consultationData.procedure.map((obj: ProcedureType) => { + return { + id: obj.procedure ?? "", + name: obj.procedure ?? "", + price: 0.0, + category: "900000", // provider's packages + }; + }), + ); + } else { + setItems([]); + } + }; + + const validate = () => { + if (!policy) { + Notification.Error({ msg: t("select_policy") }); + return false; + } + if (policy?.outcome !== "Complete") { + Notification.Error({ msg: t("select_eligible_policy") }); + return false; + } + if (!items || items.length === 0) { + setItemsError(t("claim__item__add_at_least_one")); + return false; + } + if (items?.some((p) => !p.id || !p.name || p.price === 0 || !p.category)) { + setItemsError(t("claim__item__fill_all_details")); + return false; + } + + return true; + }; + + const handleSubmit = async () => { + if (!validate()) return; + + setIsCreating(true); + + const { res, data } = await request(routes.hcx.claims.create, { + body: { + policy: policy?.id, + items, + consultation: consultationId, + use: use_, + }, + silent: true, + }); + + if (res?.ok && data) { + setItems([]); + setItemsError(undefined); + setPolicy(undefined); + setCreatedClaim(data); + } else { + Notification.Error({ msg: t(`claim__failed_to_create_${use_}`) }); + } + + setIsCreating(false); + }; + + return ( +
+ {createdClaim && ( + setCreatedClaim(undefined)} + /> + )} + setShowAddPolicy(false)} + description={t("edit_policy_description")} + className="w-full max-w-screen-md" + > + setShowAddPolicy(false)} + /> + + + {/* Check Insurance Policy Eligibility */} +
+
+

{t("check_policy_eligibility")}

+ setShowAddPolicy(true)} + ghost + border + > + + {t("edit_policy")} + +
+ { + setPolicy(policy); + autoFill(policy); + }} + /> +
+ + {/* Procedures */} +
+
+

{t("claim__items")}

+ + setItems([...(items ?? []), { name: "", id: "", price: 0 }]) + } + > + + {t("claim__add_item")} + +
+ + {t("select_policy_to_add_items")} + + setItems(value)} + error={itemsError} + /> +
+ {t("total_amount")} :{" "} + {items ? ( + + {formatCurrency( + items.map((p) => p.price).reduce((a, b) => a + b, 0.0), + )} + + ) : ( + "--" + )} +
+
+ +
+ setUse_(value)} + position="below" + className="w-52 max-sm:w-full" + optionLabel={(value) => value.label} + optionValue={(value) => value.id as "preauthorization" | "claim"} + /> + + {isCreating && } + {isCreating + ? t(`claim__creating_${use_}`) + : t(`claim__create_${use_}`)} + +
+
+ ); +} diff --git a/src/components/ManagePatientOptions.tsx b/src/components/ManagePatientOptions.tsx new file mode 100644 index 0000000..cd42ee8 --- /dev/null +++ b/src/components/ManagePatientOptions.tsx @@ -0,0 +1,45 @@ +import { triggerGoal } from "@core/Integrations/Plausible"; +import { Link } from "raviger"; +import { useTranslation } from "react-i18next"; + +import CareIcon from "@/CAREUI/icons/CareIcon"; + +import useAuthUser from "@/hooks/useAuthUser"; + +import { ManagePatientOptionsComponentType } from "@/pluginTypes"; + +const ManagePatientOptions: ManagePatientOptionsComponentType = ({ + patient, + consultation, +}) => { + const { t } = useTranslation(); + const authUser = useAuthUser(); + + if (!consultation) { + return null; + } + + return ( +
+ { + triggerGoal("Patient Card Button Clicked", { + buttonName: t("claims"), + consultationId: consultation?.id, + userId: authUser?.id, + }); + }} + > + + {t("claims")} + +
+ ); +}; + +export default ManagePatientOptions; diff --git a/src/components/PMJAYProcedurePackageAutocomplete.tsx b/src/components/PMJAYProcedurePackageAutocomplete.tsx new file mode 100644 index 0000000..6d33ebd --- /dev/null +++ b/src/components/PMJAYProcedurePackageAutocomplete.tsx @@ -0,0 +1,68 @@ +import { + FormFieldBaseProps, + useFormFieldPropsResolver, +} from "@/components/Form/FormFields/Utils"; + +import { Autocomplete } from "@/components/Form/FormFields/Autocomplete"; +import FormField from "@/components/Form/FormFields/FormField"; +import { mergeQueryOptions } from "@/Utils/utils"; +import routes from "../api"; +import useQuery from "@/Utils/request/useQuery"; +import { useState } from "react"; + +export type PMJAYPackageItem = { + name?: string; + code?: string; + price?: number; + package_name?: string; +}; + +type Props = FormFieldBaseProps; + +export default function PMJAYProcedurePackageAutocomplete(props: Props) { + const field = useFormFieldPropsResolver(props); + + const [query, setQuery] = useState(""); + + const { data, loading } = useQuery(routes.hcx.claims.listPMJYPackages, { + query: { query, limit: 10 }, + }); + + return ( + + ({ + ...o, + price: + o.price && parseFloat(o.price?.toString().replaceAll(",", "")), + })), + data ?? [], + (obj) => obj.code, + )} + optionLabel={optionLabel} + optionDescription={optionDescription} + optionValue={(option) => option} + onQuery={setQuery} + isLoading={loading} + /> + + ); +} + +const optionLabel = (option: PMJAYPackageItem) => { + if (option.name) return option.name; + if (option.package_name) return `${option.package_name} (Package)`; + return "Unknown"; +}; + +const optionDescription = (option: PMJAYPackageItem) => { + const code = option.code || "Unknown"; + const packageName = option.package_name || "Unknown"; + return `Package: ${packageName} (${code})`; +};