Upload files to "src/components"
This commit is contained in:
parent
91186b4a8a
commit
4498d15e35
165
src/components/ClaimsItemsBuilder.tsx
Normal file
165
src/components/ClaimsItemsBuilder.tsx
Normal file
@ -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<HCXItemModel[]>;
|
||||
|
||||
export default function ClaimsItemsBuilder(props: Props) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const field = useFormFieldPropsResolver(props);
|
||||
|
||||
const handleUpdate = (index: number) => {
|
||||
return (event: FieldChangeEvent<any>) => {
|
||||
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 (
|
||||
<FormField field={field}>
|
||||
<div className="flex flex-col gap-3">
|
||||
{props.value?.map((obj, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="rounded-lg border-2 border-dashed border-secondary-200 p-4"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<FieldLabel className="my-auto !font-bold">
|
||||
{t("claim__item")} {index + 1}
|
||||
</FieldLabel>
|
||||
{!props.disabled && (
|
||||
<ButtonV2
|
||||
variant="danger"
|
||||
type="button"
|
||||
ghost
|
||||
onClick={handleRemove(index)}
|
||||
disabled={props.disabled}
|
||||
>
|
||||
{t("remove")}
|
||||
<CareIcon icon="l-trash-alt" className="text-lg" />
|
||||
</ButtonV2>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="p-2">
|
||||
<AutocompleteFormField
|
||||
className="w-full"
|
||||
required
|
||||
name="category"
|
||||
label={t("claim__item__category")}
|
||||
options={ITEM_CATEGORIES}
|
||||
optionLabel={(o) => o.display}
|
||||
optionValue={(o) => o.code}
|
||||
value={obj.category}
|
||||
onChange={handleUpdate(index)}
|
||||
disabled={props.disabled}
|
||||
errorClassName="hidden"
|
||||
/>
|
||||
|
||||
<div className="mt-2 grid grid-cols-4 gap-2 max-sm:grid-cols-1">
|
||||
{obj.category === "HBP" && !obj.id ? (
|
||||
<>
|
||||
<PMJAYProcedurePackageAutocomplete
|
||||
required
|
||||
className="col-span-full"
|
||||
labelClassName="text-sm text-secondary-700"
|
||||
label={t("claim__item__procedure")}
|
||||
name="hbp"
|
||||
value={obj}
|
||||
onChange={handleUpdate(index)}
|
||||
errorClassName="hidden"
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<TextFormField
|
||||
className="col-span-1"
|
||||
required
|
||||
name="id"
|
||||
label={t("claim__item__id")}
|
||||
placeholder={t("claim__item__id__example")}
|
||||
onChange={handleUpdate(index)}
|
||||
value={obj.id}
|
||||
disabled={props.disabled}
|
||||
errorClassName="hidden"
|
||||
/>
|
||||
<TextFormField
|
||||
className="col-span-2"
|
||||
required
|
||||
name="name"
|
||||
label={t("claim__item__name")}
|
||||
placeholder={t("claim__item__name__example")}
|
||||
value={obj.name}
|
||||
onChange={handleUpdate(index)}
|
||||
disabled={props.disabled}
|
||||
errorClassName="hidden"
|
||||
/>
|
||||
<TextFormField
|
||||
className="col-span-1"
|
||||
required
|
||||
type="number"
|
||||
name="price"
|
||||
min={0}
|
||||
label={t("claim__item__price")}
|
||||
placeholder={t("claim__item__price__example")}
|
||||
value={obj.price?.toString()}
|
||||
onChange={(event) =>
|
||||
handleUpdate(index)({
|
||||
name: event.name,
|
||||
value: parseFloat(event.value),
|
||||
})
|
||||
}
|
||||
disabled={props.disabled}
|
||||
errorClassName="hidden"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</FormField>
|
||||
);
|
||||
}
|
267
src/components/CreateClaimCard.tsx
Normal file
267
src/components/CreateClaimCard.tsx
Normal file
@ -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<HCXPolicyModel>();
|
||||
const [items, setItems] = useState<HCXItemModel[]>();
|
||||
const [itemsError, setItemsError] = useState<string>();
|
||||
const [createdClaim, setCreatedClaim] = useState<HCXClaimModel>();
|
||||
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 (
|
||||
<div className="flex flex-col gap-8">
|
||||
{createdClaim && (
|
||||
<ClaimCreatedModal
|
||||
show
|
||||
claim={createdClaim}
|
||||
onClose={() => setCreatedClaim(undefined)}
|
||||
/>
|
||||
)}
|
||||
<DialogModal
|
||||
title={t("edit_policy")}
|
||||
show={showAddPolicy}
|
||||
onClose={() => setShowAddPolicy(false)}
|
||||
description={t("edit_policy_description")}
|
||||
className="w-full max-w-screen-md"
|
||||
>
|
||||
<PatientInsuranceDetailsEditor
|
||||
patient={patientId}
|
||||
onCancel={() => setShowAddPolicy(false)}
|
||||
/>
|
||||
</DialogModal>
|
||||
|
||||
{/* Check Insurance Policy Eligibility */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between gap-2 pb-4 max-sm:flex-col max-sm:items-start">
|
||||
<h1 className="text-lg font-bold">{t("check_policy_eligibility")}</h1>
|
||||
<ButtonV2
|
||||
id="edit-insurance-policy"
|
||||
className="w-fit"
|
||||
onClick={() => setShowAddPolicy(true)}
|
||||
ghost
|
||||
border
|
||||
>
|
||||
<CareIcon icon="l-edit-alt" className="text-lg" />
|
||||
{t("edit_policy")}
|
||||
</ButtonV2>
|
||||
</div>
|
||||
<HCXPolicyEligibilityCheck
|
||||
patient={patientId}
|
||||
onEligiblePolicySelected={(policy) => {
|
||||
setPolicy(policy);
|
||||
autoFill(policy);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Procedures */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<h1 className="text-left text-lg font-bold">{t("claim__items")}</h1>
|
||||
<ButtonV2
|
||||
type="button"
|
||||
variant="alert"
|
||||
border
|
||||
ghost={items?.length !== 0}
|
||||
disabled={items === undefined || !policy}
|
||||
onClick={() =>
|
||||
setItems([...(items ?? []), { name: "", id: "", price: 0 }])
|
||||
}
|
||||
>
|
||||
<CareIcon icon="l-plus" className="text-lg" />
|
||||
<span>{t("claim__add_item")}</span>
|
||||
</ButtonV2>
|
||||
</div>
|
||||
<span
|
||||
className={classNames(
|
||||
policy ? "opacity-0" : "opacity-100",
|
||||
"text-secondary-700 transition-opacity duration-300 ease-in-out",
|
||||
)}
|
||||
>
|
||||
{t("select_policy_to_add_items")}
|
||||
</span>
|
||||
<ClaimsItemsBuilder
|
||||
disabled={items === undefined || !policy}
|
||||
name="items"
|
||||
value={items}
|
||||
onChange={({ value }) => setItems(value)}
|
||||
error={itemsError}
|
||||
/>
|
||||
<div className="text-right sm:pr-8">
|
||||
{t("total_amount")} :{" "}
|
||||
{items ? (
|
||||
<span className="font-bold tracking-wider">
|
||||
{formatCurrency(
|
||||
items.map((p) => p.price).reduce((a, b) => a + b, 0.0),
|
||||
)}
|
||||
</span>
|
||||
) : (
|
||||
"--"
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex items-center justify-between max-sm:flex-col">
|
||||
<SelectFormField
|
||||
name="use"
|
||||
label={t("claim__use")}
|
||||
labelClassName="max-sm:hidden"
|
||||
options={[
|
||||
{
|
||||
id: "preauthorization",
|
||||
label: t("claim__use__preauthorization"),
|
||||
},
|
||||
{ id: "claim", label: t("claim__use__claim") },
|
||||
]}
|
||||
value={use_}
|
||||
onChange={({ value }) => setUse_(value)}
|
||||
position="below"
|
||||
className="w-52 max-sm:w-full"
|
||||
optionLabel={(value) => value.label}
|
||||
optionValue={(value) => value.id as "preauthorization" | "claim"}
|
||||
/>
|
||||
<Submit
|
||||
disabled={items?.length === 0 || !policy || isCreating}
|
||||
onClick={handleSubmit}
|
||||
className="w-52 max-sm:w-full"
|
||||
>
|
||||
{isCreating && <CareIcon icon="l-spinner" className="animate-spin" />}
|
||||
{isCreating
|
||||
? t(`claim__creating_${use_}`)
|
||||
: t(`claim__create_${use_}`)}
|
||||
</Submit>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
45
src/components/ManagePatientOptions.tsx
Normal file
45
src/components/ManagePatientOptions.tsx
Normal file
@ -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 (
|
||||
<div>
|
||||
<Link
|
||||
className="dropdown-item-primary pointer-events-auto m-2 flex cursor-pointer items-center justify-start gap-2 rounded border-0 p-2 text-sm font-normal transition-all duration-200 ease-in-out"
|
||||
href={`/facility/${patient.facility}/patient/${patient.id}/consultation/${consultation.id}/claims`}
|
||||
onClick={() => {
|
||||
triggerGoal("Patient Card Button Clicked", {
|
||||
buttonName: t("claims"),
|
||||
consultationId: consultation?.id,
|
||||
userId: authUser?.id,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<CareIcon
|
||||
icon="l-copy-landscape"
|
||||
className="text-lg text-primary-500"
|
||||
/>
|
||||
<span>{t("claims")}</span>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ManagePatientOptions;
|
68
src/components/PMJAYProcedurePackageAutocomplete.tsx
Normal file
68
src/components/PMJAYProcedurePackageAutocomplete.tsx
Normal file
@ -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<PMJAYPackageItem>;
|
||||
|
||||
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 (
|
||||
<FormField field={field}>
|
||||
<Autocomplete
|
||||
required
|
||||
id={field.id}
|
||||
disabled={field.disabled}
|
||||
value={field.value}
|
||||
onChange={field.handleChange}
|
||||
options={mergeQueryOptions(
|
||||
(field.value ? [field.value] : []).map((o) => ({
|
||||
...o,
|
||||
price:
|
||||
o.price && parseFloat(o.price?.toString().replaceAll(",", "")),
|
||||
})),
|
||||
data ?? [],
|
||||
(obj) => obj.code,
|
||||
)}
|
||||
optionLabel={optionLabel}
|
||||
optionDescription={optionDescription}
|
||||
optionValue={(option) => option}
|
||||
onQuery={setQuery}
|
||||
isLoading={loading}
|
||||
/>
|
||||
</FormField>
|
||||
);
|
||||
}
|
||||
|
||||
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})`;
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user