Upload files to "src/components"

This commit is contained in:
gitohn 2025-03-16 10:14:01 +00:00
parent 91186b4a8a
commit 4498d15e35
4 changed files with 545 additions and 0 deletions

View 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>
);
}

View 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>
);
}

View 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;

View 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})`;
};