@@ -231,7 +235,7 @@ const AvatarEditModal = ({
<>
{preview || imageUrl ? (
<>
-
+
@@ -349,6 +353,7 @@ const AvatarEditModal = ({
variant="destructive"
onClick={deleteAvatar}
disabled={isProcessing}
+ data-cy="delete-avatar"
>
{t("delete")}
@@ -358,6 +363,7 @@ const AvatarEditModal = ({
variant="outline"
onClick={uploadAvatar}
disabled={isProcessing || !selectedFile}
+ data-cy="save-cover-image"
>
{isProcessing ? (
-
+
{JSON.stringify(data, null, 2)}
diff --git a/src/components/Common/FilePreviewDialog.tsx b/src/components/Common/FilePreviewDialog.tsx
index f5c44865549..7aaabf84dd5 100644
--- a/src/components/Common/FilePreviewDialog.tsx
+++ b/src/components/Common/FilePreviewDialog.tsx
@@ -13,6 +13,8 @@ import {
import { useTranslation } from "react-i18next";
import useKeyboardShortcut from "use-keyboard-shortcut";
+import { cn } from "@/lib/utils";
+
import CareIcon, { IconName } from "@/CAREUI/icons/CareIcon";
import { Button } from "@/components/ui/button";
@@ -27,7 +29,6 @@ import CircularProgress from "@/components/Common/CircularProgress";
import { FileUploadModel } from "@/components/Patient/models";
const PDFViewer = lazy(() => import("@/components/Common/PDFViewer"));
-
export const zoom_values = [
"scale-25",
"scale-50",
@@ -38,7 +39,6 @@ export const zoom_values = [
"scale-175",
"scale-200",
];
-
export interface StateInterface {
open: boolean;
isImage: boolean;
@@ -51,7 +51,6 @@ export interface StateInterface {
id?: string;
associating_id?: string;
}
-
type FilePreviewProps = {
title?: ReactNode;
description?: ReactNode;
@@ -68,7 +67,6 @@ type FilePreviewProps = {
loadFile?: (file: FileUploadModel, associating_id: string) => void;
currentIndex: number;
};
-
const previewExtensions = [
".html",
".htm",
@@ -81,7 +79,6 @@ const previewExtensions = [
".gif",
".webp",
];
-
const FilePreviewDialog = (props: FilePreviewProps) => {
const {
show,
@@ -95,18 +92,15 @@ const FilePreviewDialog = (props: FilePreviewProps) => {
currentIndex,
} = props;
const { t } = useTranslation();
-
const [page, setPage] = useState(1);
const [numPages, setNumPages] = useState(1);
const [index, setIndex] = useState(currentIndex);
const [scale, setScale] = useState(1.0);
-
useEffect(() => {
if (uploadedFiles && show) {
setIndex(currentIndex);
}
}, [uploadedFiles, show, currentIndex]);
-
const handleZoomIn = () => {
const checkFull = file_state.zoom === zoom_values.length;
setFileState({
@@ -115,7 +109,6 @@ const FilePreviewDialog = (props: FilePreviewProps) => {
});
setScale((prevScale) => Math.min(prevScale + 0.25, 2));
};
-
const handleZoomOut = () => {
const checkFull = file_state.zoom === 1;
setFileState({
@@ -124,6 +117,29 @@ const FilePreviewDialog = (props: FilePreviewProps) => {
});
setScale((prevScale) => Math.max(prevScale - 0.25, 0.5));
};
+ const handleRotate = (angle: number) => {
+ setFileState((prev: any) => {
+ const newRotation = (prev.rotation + angle + 360) % 360;
+ return {
+ ...prev,
+ rotation: newRotation,
+ };
+ });
+ };
+
+ function getRotationClass(rotation: number) {
+ const normalizedRotation = rotation % 360;
+ switch (normalizedRotation) {
+ case 90:
+ return "rotate-90";
+ case 180:
+ return "rotate-180";
+ case 270:
+ return "-rotate-90";
+ default:
+ return "";
+ }
+ }
const fileName = file_state?.name
? file_state.name + "." + file_state.extension
@@ -138,12 +154,11 @@ const FilePreviewDialog = (props: FilePreviewProps) => {
!loadFile ||
newIndex < 0 ||
newIndex >= uploadedFiles.length
- )
+ ) {
return;
-
+ }
const nextFile = uploadedFiles[newIndex];
if (!nextFile?.id) return;
-
const associating_id = nextFile.associating_id || "";
loadFile(nextFile, associating_id);
setIndex(newIndex);
@@ -157,24 +172,8 @@ const FilePreviewDialog = (props: FilePreviewProps) => {
onClose?.();
};
- const handleRotate = (rotation: number) => {
- setFileState((prev: any) => ({
- ...prev,
- rotation: prev.rotation + rotation,
- }));
- };
-
- function getRotationClass(rotation: number) {
- let normalizedRotation = ((rotation % 360) + 360) % 360;
- if (normalizedRotation > 180) {
- normalizedRotation -= 360;
- }
- return normalizedRotation === -90
- ? "-rotate-90"
- : `rotate-${normalizedRotation}`;
- }
-
useKeyboardShortcut(["ArrowLeft"], () => index > 0 && handleNext(index - 1));
+
useKeyboardShortcut(
["ArrowRight"],
() => index < (uploadedFiles?.length || 0) - 1 && handleNext(index + 1),
@@ -188,7 +187,6 @@ const FilePreviewDialog = (props: FilePreviewProps) => {
{t("file_preview")}
-
{fileUrl ? (
<>
@@ -251,7 +249,7 @@ const FilePreviewDialog = (props: FilePreviewProps) => {
)}
-
+
{file_state.isImage ? (

{
sandbox=""
title={t("source_file")}
src={fileUrl}
- className="h-[75vh] w-full"
+ className="h-[50vh] md:h-[75vh] w-full"
/>
) : (
@@ -289,7 +287,6 @@ const FilePreviewDialog = (props: FilePreviewProps) => {
)}
-
{uploadedFiles && uploadedFiles.length > 1 && (
)}
-
-
+
+
{file_state.isImage && (
<>
{[
@@ -344,7 +341,10 @@ const FilePreviewDialog = (props: FilePreviewProps) => {
variant="ghost"
key={index}
onClick={button[2] as () => void}
- className="z-50 rounded bg-white/60 px-4 py-2 text-black backdrop-blur transition hover:bg-white/70"
+ className={cn(
+ "z-50 rounded bg-white/60 px-4 py-2 text-black backdrop-blur transition hover:bg-white/70",
+ index > 2 ? "max-md:col-span-3" : "max-md:col-span-2",
+ )}
disabled={button[3] as boolean}
>
{button[1] && (
@@ -387,7 +387,10 @@ const FilePreviewDialog = (props: FilePreviewProps) => {
variant="ghost"
key={index}
onClick={button[2] as () => void}
- className="z-50 rounded bg-white/60 px-4 py-2 text-black backdrop-blur transition hover:bg-white/70"
+ className={cn(
+ "z-50 rounded bg-white/60 px-4 py-2 text-black backdrop-blur transition hover:bg-white/70",
+ index > 2 ? "max-md:col-span-3" : "max-md:col-span-2",
+ )}
disabled={button[3] as boolean}
>
{button[1] && (
@@ -405,7 +408,7 @@ const FilePreviewDialog = (props: FilePreviewProps) => {
>
) : (
-
+
)}
@@ -413,5 +416,4 @@ const FilePreviewDialog = (props: FilePreviewProps) => {
);
};
-
export default FilePreviewDialog;
diff --git a/src/components/Common/Page.tsx b/src/components/Common/Page.tsx
index 0bb287d8637..a4a2ba48a3e 100644
--- a/src/components/Common/Page.tsx
+++ b/src/components/Common/Page.tsx
@@ -31,7 +31,7 @@ export default function Page(props: PageProps) {
// }, [props.collapseSidebar]);
return (
-
+
;
+interface GenericTableProps {
+ headers: HeaderRow[];
+ rows: TableRowType[] | undefined;
+}
+
+export default function PrintTable({ headers, rows }: GenericTableProps) {
+ return (
+
+
+
+
+ {headers.map(({ key, width }, index) => (
+
+ {t(key)}
+
+ ))}
+
+
+
+ {!!rows &&
+ rows.map((row, index) => (
+
+ {headers.map(({ key }) => (
+
+ {row[key] || "-"}
+
+ ))}
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/components/Encounter/CreateEncounterForm.tsx b/src/components/Encounter/CreateEncounterForm.tsx
index a51de24038d..32d7d600fd6 100644
--- a/src/components/Encounter/CreateEncounterForm.tsx
+++ b/src/components/Encounter/CreateEncounterForm.tsx
@@ -17,6 +17,8 @@ import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import * as z from "zod";
+import { cn } from "@/lib/utils";
+
import { Button } from "@/components/ui/button";
import {
Form,
@@ -226,10 +228,12 @@ export default function CreateEncounterForm({
key={value}
type="button"
data-cy={`encounter-type-${value}`}
- className="h-24 w-full justify-start text-lg"
- variant={
- field.value === value ? "default" : "outline"
- }
+ className={cn(
+ "h-24 w-full justify-start text-lg",
+ field.value === value &&
+ "ring-2 ring-primary text-primary",
+ )}
+ variant="outline"
onClick={() => field.onChange(value)}
>
diff --git a/src/components/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx b/src/components/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx
index 2514c7bbdfe..c6fc5667222 100644
--- a/src/components/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx
+++ b/src/components/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx
@@ -197,12 +197,23 @@ function StructuredResponseBadge({
);
}
-function ResponseCard({ item }: { item: QuestionnaireResponse }) {
+function ResponseCard({
+ item,
+ isPrintPreview,
+}: {
+ item: QuestionnaireResponse;
+ isPrintPreview?: boolean;
+}) {
const isStructured = !item.questionnaire;
const structuredType = Object.keys(item.structured_responses || {})[0];
return (
-
+
@@ -317,7 +328,12 @@ export default function QuestionnaireResponsesList({
) : (
{questionnarieResponses?.results?.length === 0 ? (
-
+
{t("no_questionnaire_responses")}
@@ -327,7 +343,11 @@ export default function QuestionnaireResponsesList({
{questionnarieResponses?.results?.map(
(item: QuestionnaireResponse) => (
-
+
),
)}
diff --git a/src/components/Facility/FacilityForm.tsx b/src/components/Facility/FacilityForm.tsx
index c6cabe1de6a..4acae303aaf 100644
--- a/src/components/Facility/FacilityForm.tsx
+++ b/src/components/Facility/FacilityForm.tsx
@@ -144,7 +144,7 @@ export default function FacilityForm({
const handleFeatureChange = (value: string[]) => {
const features = value.map((val) => Number(val));
- form.setValue("features", features);
+ form.setValue("features", features, { shouldDirty: true });
};
const handleGetCurrentLocation = () => {
@@ -152,8 +152,12 @@ export default function FacilityForm({
setIsGettingLocation(true);
navigator.geolocation.getCurrentPosition(
(position) => {
- form.setValue("latitude", position.coords.latitude);
- form.setValue("longitude", position.coords.longitude);
+ form.setValue("latitude", position.coords.latitude, {
+ shouldDirty: true,
+ });
+ form.setValue("longitude", position.coords.longitude, {
+ shouldDirty: true,
+ });
setIsGettingLocation(false);
toast.success(t("location_updated_successfully"));
},
@@ -308,7 +312,6 @@ export default function FacilityForm({
@@ -346,7 +349,9 @@ export default function FacilityForm({
value={form.watch("geo_organization")}
selected={selectedLevels}
onChange={(value) =>
- form.setValue("geo_organization", value)
+ form.setValue("geo_organization", value, {
+ shouldDirty: true,
+ })
}
required
/>
@@ -418,6 +423,7 @@ export default function FacilityForm({
form.setValue(
"latitude",
e.target.value ? Number(e.target.value) : undefined,
+ { shouldDirty: true },
);
}}
data-cy="facility-latitude"
@@ -445,6 +451,7 @@ export default function FacilityForm({
form.setValue(
"longitude",
e.target.value ? Number(e.target.value) : undefined,
+ { shouldDirty: true },
);
}}
data-cy="facility-longitude"
@@ -493,7 +500,9 @@ export default function FacilityForm({
type="submit"
className="w-full"
variant="primary"
- disabled={facilityId ? isUpdatePending : isPending}
+ disabled={
+ facilityId ? isUpdatePending || !form.formState.isDirty : isPending
+ }
data-cy={facilityId ? "update-facility" : "submit-facility"}
>
{facilityId ? (
diff --git a/src/components/Facility/FacilityHome.tsx b/src/components/Facility/FacilityHome.tsx
index edd84bdf56b..2c3e121afd8 100644
--- a/src/components/Facility/FacilityHome.tsx
+++ b/src/components/Facility/FacilityHome.tsx
@@ -8,7 +8,6 @@ import { toast } from "sonner";
import CareIcon from "@/CAREUI/icons/CareIcon";
-import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Markdown } from "@/components/ui/markdown";
@@ -27,6 +26,7 @@ import query from "@/Utils/request/query";
import uploadFile from "@/Utils/request/uploadFile";
import { getAuthorizationHeader } from "@/Utils/request/utils";
import { sleep } from "@/Utils/utils";
+import { FeatureBadge } from "@/pages/Facility/Utils";
import EditFacilitySheet from "@/pages/Organization/components/EditFacilitySheet";
import { FacilityData } from "@/types/facility/facility";
import type {
@@ -35,6 +35,8 @@ import type {
} from "@/types/organization/organization";
import { getOrgLabel } from "@/types/organization/organization";
+import { FacilityMapsLink } from "./FacilityMapLink";
+
type Props = {
facilityId: string;
};
@@ -221,10 +223,6 @@ export const FacilityHome = ({ facilityId }: Props) => {
-
{/*
TODO: add delete facility
@@ -267,7 +265,7 @@ export const FacilityHome = ({ facilityId }: Props) => {
aria-label={t("edit_cover_photo")}
>
@@ -280,15 +278,16 @@ export const FacilityHome = ({ facilityId }: Props) => {
-
+
+
-
+
}
@@ -322,8 +321,13 @@ export const FacilityHome = ({ facilityId }: Props) => {
{t("location_details")}
-
- {/* Add Location Link Here */}
+
+ {facilityData.latitude && facilityData.longitude && (
+
+ )}
@@ -355,33 +359,18 @@ export const FacilityHome = ({ facilityId }: Props) => {
) && (
-
+
{t("features")}
- {facilityData?.features?.map(
- (feature: number) =>
- FACILITY_FEATURE_TYPES.some(
- (f) => f.id === feature,
- ) && (
-
- {getFacilityFeatureIcon(feature)}
-
- {
- FACILITY_FEATURE_TYPES.find(
- (f) => f.id === feature,
- )?.name
- }
-
-
- ),
- )}
+ {facilityData.features?.map((featureId) => (
+
+ ))}
diff --git a/src/components/Facility/FacilityMapLink.tsx b/src/components/Facility/FacilityMapLink.tsx
new file mode 100644
index 00000000000..47a76082fbf
--- /dev/null
+++ b/src/components/Facility/FacilityMapLink.tsx
@@ -0,0 +1,42 @@
+import { SquareArrowOutUpRight } from "lucide-react";
+import { Link } from "raviger";
+import { useTranslation } from "react-i18next";
+
+import { getMapUrl, isAndroidDevice } from "@/Utils/utils";
+
+const isValidLatitude = (latitude: string) => {
+ const lat = parseFloat(latitude.trim());
+ return Number.isFinite(lat) && lat >= -90 && lat <= 90;
+};
+
+const isValidLongitude = (longitude: string) => {
+ const long = parseFloat(longitude.trim());
+ return Number.isFinite(long) && long >= -180 && long <= 180;
+};
+
+export const FacilityMapsLink = ({
+ latitude,
+ longitude,
+}: {
+ latitude: string;
+ longitude: string;
+}) => {
+ const { t } = useTranslation();
+
+ if (!isValidLatitude(latitude) || !isValidLongitude(longitude)) {
+ return null;
+ }
+ const target = isAndroidDevice ? "_self" : "_blank";
+
+ return (
+
+ {t("show_on_map")}
+
+
+ );
+};
diff --git a/src/components/Files/FilesTab.tsx b/src/components/Files/FilesTab.tsx
index 50fb179cd36..3c1abcbfb45 100644
--- a/src/components/Files/FilesTab.tsx
+++ b/src/components/Files/FilesTab.tsx
@@ -251,54 +251,52 @@ export const FilesTab = (props: FilesTabProps) => {
const filetype = getFileType(file);
return (
<>
- {editPermission() && (
-
- {filetype === "AUDIO" && !file.is_archived && (
-
- )}
- {fileManager.isPreviewable(file) && (
-
>
);
};
diff --git a/src/components/Location/LocationHistorySheet.tsx b/src/components/Location/LocationHistorySheet.tsx
deleted file mode 100644
index de0ddb25abb..00000000000
--- a/src/components/Location/LocationHistorySheet.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { useTranslation } from "react-i18next";
-
-import { ScrollArea } from "@/components/ui/scroll-area";
-import {
- Sheet,
- SheetContent,
- SheetHeader,
- SheetTitle,
- SheetTrigger,
-} from "@/components/ui/sheet";
-
-import { LocationHistory } from "@/types/emr/encounter";
-
-import { LocationTree } from "./LocationTree";
-
-interface LocationHistorySheetProps {
- trigger: React.ReactNode;
- history: LocationHistory[];
-}
-
-export function LocationHistorySheet({
- trigger,
- history,
-}: LocationHistorySheetProps) {
- const { t } = useTranslation();
-
- return (
-
- {trigger}
-
-
- {t("location_history")}
-
-
- {history.map((item, index) => (
-
-
-
- ))}
-
-
-
- );
-}
diff --git a/src/components/Location/LocationSearch.tsx b/src/components/Location/LocationSearch.tsx
index 288a283773e..3e741c7c03f 100644
--- a/src/components/Location/LocationSearch.tsx
+++ b/src/components/Location/LocationSearch.tsx
@@ -1,6 +1,6 @@
import { useQuery } from "@tanstack/react-query";
+import { t } from "i18next";
import { useState } from "react";
-import { useTranslation } from "react-i18next";
import {
Command,
@@ -16,6 +16,7 @@ import {
} from "@/components/ui/popover";
import query from "@/Utils/request/query";
+import { stringifyNestedObject } from "@/Utils/utils";
import { LocationList } from "@/types/location/location";
import locationApi from "@/types/location/locationApi";
@@ -34,7 +35,6 @@ export function LocationSearch({
disabled,
value,
}: LocationSearchProps) {
- const { t } = useTranslation();
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
@@ -42,7 +42,7 @@ export function LocationSearch({
queryKey: ["locations", facilityId, mode, search],
queryFn: query(locationApi.list, {
pathParams: { facility_id: facilityId },
- queryParams: { mode, name: search },
+ queryParams: { mode, name: search, form: "bd", available: "true" },
}),
enabled: facilityId !== "preview",
});
@@ -54,7 +54,7 @@ export function LocationSearch({
role="combobox"
aria-expanded={open}
>
- {value?.name || "Select location..."}
+ {stringifyNestedObject(value || { name: "" }) || "Select location..."}
@@ -65,7 +65,7 @@ export function LocationSearch({
className="outline-none border-none ring-0 shadow-none"
onValueChange={setSearch}
/>
- No locations found.
+ {t("no_locations_found")}
{locations?.results.map((location) => (
- {location.name}
-
- {t(`location_form__${location.form}`)}
- {" in "}
- {formatLocationParent(location)}
-
-
- {t(`location_status__${location.status}`)}
-
+ {stringifyNestedObject(location)}
))}
@@ -93,12 +85,3 @@ export function LocationSearch({
);
}
-
-const formatLocationParent = (location: LocationList) => {
- const parents: string[] = [];
- while (location.parent?.name) {
- parents.push(location.parent?.name);
- location = location.parent;
- }
- return parents.reverse().join(" > ");
-};
diff --git a/src/components/Location/LocationSheet.tsx b/src/components/Location/LocationSheet.tsx
new file mode 100644
index 00000000000..047f9d2d7e7
--- /dev/null
+++ b/src/components/Location/LocationSheet.tsx
@@ -0,0 +1,456 @@
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { format, isAfter, isBefore, parseISO } from "date-fns";
+import { useEffect, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { toast } from "sonner";
+
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { ScrollArea } from "@/components/ui/scroll-area";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import {
+ Sheet,
+ SheetContent,
+ SheetHeader,
+ SheetTitle,
+ SheetTrigger,
+} from "@/components/ui/sheet";
+
+import mutate from "@/Utils/request/mutate";
+import { stringifyNestedObject } from "@/Utils/utils";
+import { LocationHistory } from "@/types/emr/encounter";
+import {
+ LocationAssociationStatus,
+ LocationAssociationUpdate,
+} from "@/types/location/association";
+import { LocationList } from "@/types/location/location";
+import locationApi from "@/types/location/locationApi";
+
+import { LocationSearch } from "./LocationSearch";
+import { LocationTree } from "./LocationTree";
+
+interface LocationSheetProps {
+ trigger: React.ReactNode;
+ history: LocationHistory[];
+ facilityId: string;
+ encounterId: string;
+}
+
+interface LocationState extends LocationHistory {
+ displayStatus: LocationAssociationStatus;
+}
+
+interface ValidationError {
+ message: string;
+ field: "start_datetime" | "end_datetime";
+}
+
+// Omit id field for creation
+type LocationAssociationCreate = Omit;
+
+export function LocationSheet({
+ trigger,
+ history,
+ facilityId,
+ encounterId,
+}: LocationSheetProps) {
+ const { t } = useTranslation();
+ const queryClient = useQueryClient();
+
+ const initialState = {
+ location: "",
+ status: "active",
+ start_datetime: format(new Date(), "yyyy-MM-dd'T'HH:mm"),
+ end_datetime: format(new Date(), "yyyy-MM-dd'T'HH:mm"),
+ encounter: encounterId,
+ };
+ const [newLocation, setNewLocation] = useState(initialState);
+
+ const [locations, setLocations] = useState([]);
+
+ useEffect(() => {
+ setLocations(
+ history.map((loc) => ({
+ ...loc,
+ displayStatus: loc.status,
+ end_datetime: loc.status === "active" ? undefined : loc.end_datetime,
+ })),
+ );
+ }, [history]);
+
+ function validateTimes(
+ status: LocationAssociationStatus,
+ startTime: string,
+ endTime?: string,
+ ): ValidationError | null {
+ const now = new Date();
+ const start = parseISO(startTime);
+
+ if (!startTime) {
+ return { message: t("start_time_required"), field: "start_datetime" };
+ }
+
+ if (status !== "active" && !endTime) {
+ return { message: t("end_time_required"), field: "end_datetime" };
+ }
+
+ if (endTime) {
+ const end = parseISO(endTime);
+ if (isBefore(end, start)) {
+ return {
+ message: t("start_time_must_be_before_end_time"),
+ field: "end_datetime",
+ };
+ }
+ }
+
+ if (
+ (status === "planned" || status === "reserved") &&
+ isBefore(start, now)
+ ) {
+ return {
+ message: t("planned_reserved_cannot_be_in_past"),
+ field: "start_datetime",
+ };
+ }
+
+ if (status === "active" && isAfter(start, now)) {
+ return {
+ message: t("active_location_cannot_be_in_future"),
+ field: "start_datetime",
+ };
+ }
+
+ return null;
+ }
+
+ const handleLocationUpdate = (updatedLocation: LocationState) => {
+ setLocations((prevLocations) =>
+ prevLocations.map((loc) =>
+ loc.id === updatedLocation.id
+ ? {
+ ...updatedLocation,
+ end_datetime:
+ updatedLocation.status === "active"
+ ? undefined
+ : updatedLocation.end_datetime,
+ }
+ : loc,
+ ),
+ );
+ };
+
+ const [selectedLocation, setSelectedLocation] = useState(
+ null,
+ );
+
+ const updateAssociation = useMutation({
+ mutationFn: (location: LocationAssociationUpdate) => {
+ const validationError = validateTimes(
+ location.status,
+ location.start_datetime,
+ location.end_datetime,
+ );
+
+ if (validationError) {
+ throw new Error(validationError.message);
+ }
+
+ return mutate(locationApi.updateAssociation, {
+ pathParams: {
+ facility_external_id: facilityId,
+ location_external_id: location.location,
+ external_id: location.id,
+ },
+ })(location);
+ },
+ onSuccess: () => {
+ toast.success(t("location_association_updated_successfully"));
+ queryClient.invalidateQueries({ queryKey: ["encounter", encounterId] });
+ },
+ });
+
+ const { mutate: createAssociation, isPending } = useMutation({
+ mutationFn: (data: LocationAssociationCreate) => {
+ const validationError = validateTimes(
+ data.status,
+ data.start_datetime,
+ data.end_datetime,
+ );
+
+ if (validationError) {
+ throw new Error(validationError.message);
+ }
+
+ return mutate(locationApi.createAssociation, {
+ pathParams: {
+ facility_external_id: facilityId,
+ location_external_id: selectedLocation?.id,
+ },
+ })(data);
+ },
+ onSuccess: () => {
+ toast.success(t("location_association_created_successfully"));
+ queryClient.invalidateQueries({ queryKey: ["encounter", encounterId] });
+ setNewLocation(initialState);
+ setSelectedLocation(null);
+ },
+ });
+
+ const renderLocation = (location: LocationState) => (
+
+
+
+
+
+
+ updateAssociation.mutate({
+ ...location,
+ encounter: encounterId,
+ location: location.location.id,
+ })
+ }
+ disabled={updateAssociation.isPending}
+ >
+ {updateAssociation.isPending ? t("saving") : t("save")}
+
+
+
+
+ {stringifyNestedObject(location.location, " < ")}
+
+
+
+ );
+
+ // Get locations by their original display status
+ const activeLocation = locations.find(
+ (loc) => loc.displayStatus === "active",
+ );
+ const plannedLocations = locations.filter(
+ (loc) => loc.displayStatus === "planned",
+ );
+ const reservedLocations = locations.filter(
+ (loc) => loc.displayStatus === "reserved",
+ );
+
+ return (
+
+ {trigger}
+
+
+
+ {t("update_location")}
+
+
+ {t("manage_patient_location_and_transfers")}
+
+
+
+
+ {/* Active Location */}
+ {activeLocation && renderLocation(activeLocation)}
+
+ {/* Reserved Locations */}
+ {reservedLocations.map((location) => renderLocation(location))}
+
+ {/* Planned Locations */}
+ {plannedLocations.map((location) => renderLocation(location))}
+
+
+
+
+
+
+
+
setSelectedLocation(location)}
+ value={selectedLocation}
+ />
+ {selectedLocation && (
+
+
+
+
+
+ {(newLocation.status === "active" ||
+ newLocation.status === "planned" ||
+ newLocation.status === "reserved") && (
+
+
+
+ setNewLocation((prev) => ({
+ ...prev,
+ start_datetime: e.target.value,
+ }))
+ }
+ className="h-9"
+ />
+
+ )}
+ {newLocation.status !== "active" && (
+
+
+
+ setNewLocation((prev) => ({
+ ...prev,
+ end_datetime: e.target.value,
+ }))
+ }
+ className="h-9"
+ />
+
+ )}
+
{
+ createAssociation({
+ ...newLocation,
+ status:
+ newLocation.status as LocationAssociationStatus,
+ location: selectedLocation.id,
+ });
+ }}
+ className="w-full"
+ disabled={isPending}
+ >
+ {isPending
+ ? t("creating")
+ : t("create_location_association")}
+
+
+ )}
+
+
+ {history.map((item, index) => (
+
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/src/components/Medicine/MedicationAdministration/AdministrationTab.tsx b/src/components/Medicine/MedicationAdministration/AdministrationTab.tsx
index 93d8c430717..cb53325fd11 100644
--- a/src/components/Medicine/MedicationAdministration/AdministrationTab.tsx
+++ b/src/components/Medicine/MedicationAdministration/AdministrationTab.tsx
@@ -8,7 +8,7 @@ import React, { useCallback, useMemo, useState } from "react";
import CareIcon from "@/CAREUI/icons/CareIcon";
import { Button } from "@/components/ui/button";
-import { Card } from "@/components/ui/card";
+import { Card, CardContent } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import {
Popover,
@@ -647,119 +647,130 @@ export const AdministrationTab: React.FC = ({
content = ;
} else {
content = (
-
-
-
- {/* Top row without vertical borders */}
-
-
-
- {lastModifiedDate && (
-
- {t("last_modified")}{" "}
- {formatDistanceToNow(lastModifiedDate)} {t("ago")}
-
- )}
+ <>
+ {!filteredMedications.length && (
+
+
+ {t("no_active_medication_recorded")}
+
+
+ )}
+
+
+
+ {/* Top row without vertical borders */}
+
+
+
+ {lastModifiedDate && (
+
+ {t("last_modified")}{" "}
+ {formatDistanceToNow(lastModifiedDate)} {t("ago")}
+
+ )}
+
+
+
+
+
+
-
+ {visibleSlots.map((slot) => (
+
+ ))}
+
-
+
- {visibleSlots.map((slot) => (
-
- ))}
-
-
-
-
-
-
- {/* Main content with borders */}
-
- {/* Headers */}
-
- {t("medicine")}:
-
- {visibleSlots.map((slot, i) => (
-
- {i === endSlotIndex &&
- slot.date.getTime() === currentDate.getTime() && (
-
- )}
- {slot.label}
+ {/* Main content with borders */}
+
+ {/* Headers */}
+
+ {t("medicine")}:
- ))}
-
-
- {/* Medication rows */}
- {filteredMedications?.map((medication) => (
-
- ))}
+ {visibleSlots.map((slot, i) => (
+
+ {i === endSlotIndex &&
+ slot.date.getTime() === currentDate.getTime() && (
+
+ )}
+ {slot.label}
+
+ ))}
+
+
+ {/* Medication rows */}
+ {filteredMedications?.map((medication) => (
+
+ ))}
+
-
- {stoppedMedications?.results?.length > 0 && !searchQuery.trim() && (
-
setShowStopped(!showStopped)}
- >
-
-
- {showStopped ? t("hide") : t("show")}{" "}
- {`${stoppedMedications?.results?.length} ${t("stopped")}`}{" "}
- {t("prescriptions")}
-
-
- )}
-
-
-
+ {stoppedMedications?.results?.length > 0 && !searchQuery.trim() && (
+
setShowStopped(!showStopped)}
+ >
+
+
+ {showStopped ? t("hide") : t("show")}{" "}
+ {`${stoppedMedications?.results?.length} ${t("stopped")}`}{" "}
+ {t("prescriptions")}
+
+
+ )}
+
+
+
+ >
);
}
return (
-
+
@@ -783,8 +794,9 @@ export const AdministrationTab: React.FC
= ({
setIsSheetOpen(true)}
+ disabled={!activeMedications?.results.length}
>
{t("administer_medicine")}
@@ -828,7 +840,7 @@ export const AdministrationTab: React.FC = ({
});
}
}}
- medications={medications}
+ medications={activeMedications?.results || []}
lastAdministeredDates={lastAdministeredDetails?.dates}
patientId={patientId}
encounterId={encounterId}
diff --git a/src/components/Medicine/MedicationsTable.tsx b/src/components/Medicine/MedicationsTable.tsx
index e347d2a9ef7..d78c736fd28 100644
--- a/src/components/Medicine/MedicationsTable.tsx
+++ b/src/components/Medicine/MedicationsTable.tsx
@@ -1,7 +1,6 @@
-import { useQuery } from "@tanstack/react-query";
import { useTranslation } from "react-i18next";
-import { Skeleton } from "@/components/ui/skeleton";
+import { CardContent } from "@/components/ui/card";
import {
Table,
TableBody,
@@ -13,14 +12,12 @@ import {
import { reverseFrequencyOption } from "@/components/Questionnaire/QuestionTypes/MedicationRequestQuestion";
-import query from "@/Utils/request/query";
import {
INACTIVE_MEDICATION_STATUSES,
MEDICATION_REQUEST_TIMING_OPTIONS,
MedicationRequestDosageInstruction,
MedicationRequestRead,
} from "@/types/emr/medicationRequest";
-import medicationRequestApi from "@/types/emr/medicationRequest/medicationRequestApi";
import { formatDosage, formatSig } from "./utils";
@@ -37,38 +34,22 @@ export function getFrequencyDisplay(
}
interface MedicationsTableProps {
- patientId?: string;
- encounterId?: string;
- medications?: MedicationRequestRead[];
+ medications: MedicationRequestRead[];
}
-export const MedicationsTable = ({
- medications,
- patientId,
- encounterId,
-}: MedicationsTableProps) => {
+export const MedicationsTable = ({ medications }: MedicationsTableProps) => {
const { t } = useTranslation();
- const { data: allMedications, isLoading } = useQuery({
- queryKey: ["medication_requests", patientId, encounterId],
- queryFn: query(medicationRequestApi.list, {
- pathParams: { patientId },
- queryParams: { encounter: encounterId, limit: 50, offset: 0 },
- }),
- enabled: !medications && !!patientId,
- });
-
- if (isLoading) {
+ if (!medications.length) {
return (
-
-
-
+
+
+ {t("no_active_medication_recorded")}
+
+
);
}
- const displayedMedications = !medications
- ? (allMedications?.results ?? [])
- : medications;
return (
@@ -82,7 +63,7 @@ export const MedicationsTable = ({
- {displayedMedications?.map((medication) => {
+ {medications.map((medication) => {
const instruction = medication.dosage_instruction[0];
const frequency = getFrequencyDisplay(instruction?.timing);
const dosage = formatDosage(instruction);
diff --git a/src/components/Patient/EncounterQuestionnaire.tsx b/src/components/Patient/EncounterQuestionnaire.tsx
index 90de3924f2c..10799afe296 100644
--- a/src/components/Patient/EncounterQuestionnaire.tsx
+++ b/src/components/Patient/EncounterQuestionnaire.tsx
@@ -1,13 +1,19 @@
+import { useQuery } from "@tanstack/react-query";
import { t } from "i18next";
import { navigate } from "raviger";
import { Card, CardContent } from "@/components/ui/card";
import Page from "@/components/Common/Page";
+import PatientInfoCard from "@/components/Patient/PatientInfoCard";
import { QuestionnaireForm } from "@/components/Questionnaire/QuestionnaireForm";
import useAppHistory from "@/hooks/useAppHistory";
+import routes from "@/Utils/request/api";
+import query from "@/Utils/request/query";
+import { formatDateTime } from "@/Utils/utils";
+
interface Props {
facilityId: string;
patientId: string;
@@ -24,29 +30,61 @@ export default function EncounterQuestionnaire({
subjectType,
}: Props) {
const { goBack } = useAppHistory();
+ const { data: encounter } = useQuery({
+ queryKey: ["encounter", encounterId],
+ queryFn: query(routes.encounter.get, {
+ pathParams: { id: encounterId ?? "" },
+ queryParams: { facility: facilityId },
+ }),
+ enabled: !!encounterId,
+ });
return (
-
-
- {
- if (encounterId) {
- navigate(
- `/facility/${facilityId}/patient/${patientId}/encounter/${encounterId}/updates`,
- );
- } else {
- navigate(`/patient/${patientId}/updates`);
- }
- }}
- onCancel={() => goBack()}
- />
-
-
+
+ {encounter && (
+
+
{}}
+ disableButtons={true}
+ />
+
+
+
+
+
+ {t("last_modified")}:{" "}
+
+
+ {formatDateTime(encounter.modified_date)}
+
+
+
+
+ )}
+
+
+ {
+ if (encounterId) {
+ navigate(
+ `/facility/${facilityId}/patient/${patientId}/encounter/${encounterId}/updates`,
+ );
+ } else {
+ navigate(`/patient/${patientId}/updates`);
+ }
+ }}
+ onCancel={() => goBack()}
+ />
+
+
+
);
}
diff --git a/src/components/Patient/MedicationStatementList.tsx b/src/components/Patient/MedicationStatementList.tsx
index 112c42414a4..606c00962d7 100644
--- a/src/components/Patient/MedicationStatementList.tsx
+++ b/src/components/Patient/MedicationStatementList.tsx
@@ -1,4 +1,5 @@
import { useQuery } from "@tanstack/react-query";
+import { t } from "i18next";
import { useState } from "react";
import { useTranslation } from "react-i18next";
@@ -35,20 +36,14 @@ import medicationStatementApi from "@/types/emr/medicationStatement/medicationSt
interface MedicationStatementListProps {
patientId: string;
className?: string;
- isPrintPreview?: boolean;
}
interface MedicationRowProps {
statement: MedicationStatementRead;
isEnteredInError?: boolean;
- isPrintPreview?: boolean;
}
-function MedicationRow({
- statement,
- isEnteredInError,
- isPrintPreview = false,
-}: MedicationRowProps) {
+function MedicationRow({ statement, isEnteredInError }: MedicationRowProps) {
const { t } = useTranslation();
return (
@@ -80,26 +75,22 @@ function MedicationRow({
{statement.note ? (
- {isPrintPreview ? (
-
{statement.note}
- ) : (
-
-
-
- {t("see_note")}
-
-
-
-
- {statement.note}
-
-
-
- )}
+
+
+
+ {t("see_note")}
+
+
+
+
+ {statement.note}
+
+
+
) : (
"-"
@@ -121,11 +112,10 @@ function MedicationRow({
export function MedicationStatementList({
patientId,
- className,
- isPrintPreview = false,
+ className = "",
}: MedicationStatementListProps) {
const { t } = useTranslation();
- const [showEnteredInError, setShowEnteredInError] = useState(isPrintPreview);
+ const [showEnteredInError, setShowEnteredInError] = useState(false);
const { data: medications, isLoading } = useQuery({
queryKey: ["medication_statements", patientId],
@@ -136,16 +126,9 @@ export function MedicationStatementList({
if (isLoading) {
return (
-
-
- {t("ongoing_medications")}
-
-
-
-
-
+
+
+
);
}
@@ -160,31 +143,18 @@ export function MedicationStatementList({
if (!filteredMedications?.length) {
return (
-
-
- {t("ongoing_medications")}
-
-
- {t("no_ongoing_medications")}
-
-
+
+ {t("no_ongoing_medications")}
+
);
}
return (
-
-
-
- {t("ongoing_medications")} ({filteredMedications.length})
-
-
-
+
+ <>
@@ -226,7 +196,6 @@ export function MedicationStatementList({
key={statement.id}
statement={statement}
isEnteredInError={statement.status === "entered_in_error"}
- isPrintPreview={isPrintPreview}
/>
))}
@@ -246,7 +215,29 @@ export function MedicationStatementList({
>
)}
-
-
+ >
+
);
}
+
+const MedicationStatementListLayout = ({
+ children,
+ className,
+ medicationsCount,
+}: {
+ children: React.ReactNode;
+ className?: string;
+ medicationsCount?: number | undefined;
+}) => {
+ return (
+
+
+
+ {t("ongoing_medications")}{" "}
+ {medicationsCount ? `(${medicationsCount})` : ""}
+
+
+ {children}
+
+ );
+};
diff --git a/src/components/Patient/PatientDetailsTab/Demography.tsx b/src/components/Patient/PatientDetailsTab/Demography.tsx
index 19b60471dab..b4ed6daac60 100644
--- a/src/components/Patient/PatientDetailsTab/Demography.tsx
+++ b/src/components/Patient/PatientDetailsTab/Demography.tsx
@@ -263,7 +263,6 @@ export const Demography = (props: PatientProps) => {
handleEditClick(subtab.id),
)}
diff --git a/src/components/Patient/PatientHome.tsx b/src/components/Patient/PatientHome.tsx
index 69df69c4143..e703da7e819 100644
--- a/src/components/Patient/PatientHome.tsx
+++ b/src/components/Patient/PatientHome.tsx
@@ -1,9 +1,9 @@
import { useQuery } from "@tanstack/react-query";
-import { Link, navigate } from "raviger";
+import dayjs from "dayjs";
+import { Link } from "raviger";
import { useTranslation } from "react-i18next";
-import CareIcon from "@/CAREUI/icons/CareIcon";
-
+import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Tooltip,
@@ -87,13 +87,28 @@ export const PatientHome = (props: {
name={patientData.name}
/>
+
-
- {patientData.name}
-
+
+
+ {patientData.name}
+
+ {patientData.death_datetime && (
+
+
+ {t("expired_on")}
+ {": "}
+ {dayjs(patientData.death_datetime).format(
+ "DD MMM YYYY, hh:mm A",
+ )}
+
+
+ )}
+
+
{formatPatientAge(patientData, true)},{" "}
{t(`GENDER__${patientData.gender}`)}, {" "}
@@ -108,34 +123,35 @@ export const PatientHome = (props: {
-
- {tabs.map((tab) => (
-
- {t(tab.route)}
-
- ))}
+
+
+ {tabs.map((tab) => (
+
+ {t(tab.route)}
+
+ ))}
+
-
{Tab && (
@@ -229,21 +245,6 @@ export const PatientHome = (props: {
-
- {patientData.death_datetime && (
-
- navigate(`/death_report/${id}`)}
- >
-
- {t("death_report")}
-
-
- )}
-
diff --git a/src/components/Patient/PatientInfoCard.tsx b/src/components/Patient/PatientInfoCard.tsx
index 1212a95300d..71c0f1b51a8 100644
--- a/src/components/Patient/PatientInfoCard.tsx
+++ b/src/components/Patient/PatientInfoCard.tsx
@@ -43,26 +43,32 @@ import {
} from "@/components/ui/popover";
import { Avatar } from "@/components/Common/Avatar";
-import { LocationHistorySheet } from "@/components/Location/LocationHistorySheet";
+import { LocationSheet } from "@/components/Location/LocationSheet";
import { LocationTree } from "@/components/Location/LocationTree";
+import LinkDepartmentsSheet from "@/components/Patient/LinkDepartmentsSheet";
import { PLUGIN_Component } from "@/PluginEngine";
+import dayjs from "@/Utils/dayjs";
import routes from "@/Utils/request/api";
import mutate from "@/Utils/request/mutate";
import { formatDateTime, formatPatientAge } from "@/Utils/utils";
-import { Encounter, completedEncounterStatus } from "@/types/emr/encounter";
+import {
+ Encounter,
+ completedEncounterStatus,
+ inactiveEncounterStatus,
+} from "@/types/emr/encounter";
import { Patient } from "@/types/emr/newPatient";
-
-import LinkDepartmentsSheet from "./LinkDepartmentsSheet";
+import { FacilityOrganization } from "@/types/facilityOrganization/facilityOrganization";
export interface PatientInfoCardProps {
patient: Patient;
encounter: Encounter;
fetchPatientData?: (state: { aborted: boolean }) => void;
+ disableButtons?: boolean;
}
export default function PatientInfoCard(props: PatientInfoCardProps) {
- const { patient, encounter } = props;
+ const { patient, encounter, disableButtons = false } = props;
const { t } = useTranslation();
const queryClient = useQueryClient();
@@ -128,6 +134,17 @@ export default function PatientInfoCard(props: PatientInfoCardProps) {
{formatPatientAge(patient, true)} •{" "}
{t(`GENDER__${patient.gender}`)}
+ {patient.death_datetime && (
+
+
+ {t("expired_on")}
+ {": "}
+ {dayjs(patient.death_datetime).format(
+ "DD MMM YYYY, hh:mm A",
+ )}
+
+
+ )}
@@ -153,6 +170,17 @@ export default function PatientInfoCard(props: PatientInfoCardProps) {
{formatPatientAge(patient, true)} •{" "}
{t(`GENDER__${patient.gender}`)}
+ {patient.death_datetime && (
+
+
+ {t("expired_on")}
+ {": "}
+ {dayjs(patient.death_datetime).format(
+ "DD MMM YYYY, hh:mm A",
+ )}
+
+
+ )}
@@ -331,17 +359,9 @@ export default function PatientInfoCard(props: PatientInfoCardProps) {
facilityId={encounter.facility.id}
trigger={
- {encounter.organizations.map((org) => (
-
-
- {org.name}
-
- ))}
+ {encounter.organizations.map((org) =>
+ organizationBadge(org),
+ )}
{encounter.organizations.length === 0 && (
-
@@ -402,33 +424,41 @@ export default function PatientInfoCard(props: PatientInfoCardProps) {
location={props.encounter.current_location}
/>
-
-
- {t("update_location")}
-
-
+
+ {t("update_location")}
+
+ }
+ history={encounter.location_history}
+ />
) : (
-
-
-
+
+
+ {t("add_location")}
+
+ }
+ history={encounter.location_history}
/>
- {t("add_location")}
-
-
+
+ )
)}
@@ -439,78 +469,97 @@ export default function PatientInfoCard(props: PatientInfoCardProps) {
className="flex flex-col items-center justify-end gap-4 px-4 py-1 2xl:flex-row"
id="consultation-buttons"
>
- {!completedEncounterStatus.includes(encounter.status) && (
-
-
-
-
-
- {t("update")}
-
-
-
-
- {t("actions")}
-
-
- {t("treatment_summary")}
-
-
-
-
- {t("discharge_summary")}
-
-
-
- e.preventDefault()}>
- {t("mark_as_complete")}
+ {!completedEncounterStatus.includes(encounter.status) &&
+ !disableButtons && (
+
+
+
+
+
+ {t("update")}
+
+
+
+
+ {t("actions")}
+
+
+ {t("treatment_summary")}
+
+
+
+
+ {t("discharge_summary")}
+
-
+
+ e.preventDefault()}>
+ {t("mark_as_complete")}
+
+
+
+
+
+
+
+
+ {t("mark_as_complete")}
+
+
+ {t("mark_encounter_as_complete_confirmation")}
+
+
+
-
-
-
-
- {t("mark_as_complete")}
-
- {t("mark_encounter_as_complete_confirmation")}
-
-
-
+
+ {t("cancel")}
-
- {t("cancel")}
-
-
- {t("mark_as_complete")}
-
-
-
-
-
- )}
+
+ {t("mark_as_complete")}
+
+
+
+
+
+ )}
>
);
+
+ function organizationBadge(org: FacilityOrganization) {
+ return (
+
+
+ {org.name}
+
+ );
+ }
}
diff --git a/src/components/Patient/PatientRegistration.tsx b/src/components/Patient/PatientRegistration.tsx
index 6f46b141468..21682b67c89 100644
--- a/src/components/Patient/PatientRegistration.tsx
+++ b/src/components/Patient/PatientRegistration.tsx
@@ -1,5 +1,6 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useMutation, useQuery } from "@tanstack/react-query";
+import { InfoIcon } from "lucide-react";
import { navigate, useNavigationPrompt, useQueryParams } from "raviger";
import { useEffect, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
@@ -77,6 +78,7 @@ export default function PatientRegistration(
const [suppressDuplicateWarning, setSuppressDuplicateWarning] =
useState(!!patientId);
const [selectedLevels, setSelectedLevels] = useState([]);
+ const [isDeceased, setIsDeceased] = useState(false);
const formSchema = useMemo(
() =>
@@ -99,6 +101,7 @@ export default function PatientRegistration(
return parsedDate.isValid() && !parsedDate.isAfter(dayjs());
}, t("enter_valid_dob"))
.optional(),
+ death_datetime: z.string().nullable().optional(),
age: z
.number()
.int()
@@ -261,27 +264,35 @@ export default function PatientRegistration(
setSelectedLevels([
patientQuery.data.geo_organization as unknown as Organization,
]);
+ setIsDeceased(!!patientQuery.data.death_datetime);
form.reset({
- ...patientQuery.data,
+ name: patientQuery.data.name || "",
+ phone_number: patientQuery.data.phone_number || "",
+ emergency_phone_number: patientQuery.data.emergency_phone_number || "",
same_phone_number:
patientQuery.data.phone_number ===
patientQuery.data.emergency_phone_number,
same_address:
patientQuery.data.address === patientQuery.data.permanent_address,
+ gender: patientQuery.data.gender as (typeof GENDERS)[number],
+ blood_group: patientQuery.data.blood_group,
age_or_dob: patientQuery.data.date_of_birth ? "dob" : "age",
+ date_of_birth: patientQuery.data.date_of_birth || undefined,
age:
!patientQuery.data.date_of_birth && patientQuery.data.year_of_birth
? new Date().getFullYear() - patientQuery.data.year_of_birth
: undefined,
- date_of_birth: patientQuery.data.date_of_birth
- ? patientQuery.data.date_of_birth
- : undefined,
+ address: patientQuery.data.address || "",
+ permanent_address: patientQuery.data.permanent_address || "",
+ pincode: patientQuery.data.pincode || undefined,
+ nationality: patientQuery.data.nationality || "India",
geo_organization: (
patientQuery.data.geo_organization as unknown as Organization
)?.id,
+ death_datetime: patientQuery.data.death_datetime || undefined,
} as unknown as z.infer);
}
- }, [patientQuery.data]); // eslint-disable-line react-hooks/exhaustive-deps
+ }, [patientQuery.data]);
const showDuplicate =
!patientPhoneSearch.isLoading &&
@@ -578,6 +589,79 @@ export default function PatientRegistration(
+
+
+
+
+ {t("deceased_status")}
+
+
+ ({t("only_mark_if_applicable")})
+
+
+
+ {
+ setIsDeceased(checked as boolean);
+ form.setValue(
+ "death_datetime",
+ checked ? form.getValues("death_datetime") : null,
+ );
+ }}
+ data-cy="is-deceased-checkbox"
+ />
+
+
+
+
+ {(isDeceased || form.watch("death_datetime")) && (
+
+
+
+
+ {t("deceased_disclaimer")}
+
+
+
+
(
+
+ {t("date_and_time_of_death")}
+
+ {
+ const value = e.target.value || undefined;
+ field.onChange(value);
+ setIsDeceased(!!value);
+ }}
+ max={dayjs().format("YYYY-MM-DDTHH:mm")}
+ id="death-datetime"
+ data-cy="death-datetime-input"
+ />
+
+
+
+ )}
+ />
+
+ )}
+
+
{patientId ? t("save") : t("save_and_continue")}
diff --git a/src/components/Patient/TreatmentSummary.tsx b/src/components/Patient/TreatmentSummary.tsx
index ad87eee91a6..862d7dca5b8 100644
--- a/src/components/Patient/TreatmentSummary.tsx
+++ b/src/components/Patient/TreatmentSummary.tsx
@@ -2,38 +2,133 @@ import careConfig from "@careConfig";
import { useQuery } from "@tanstack/react-query";
import { format } from "date-fns";
import { t } from "i18next";
+import { Loader } from "lucide-react";
import PrintPreview from "@/CAREUI/misc/PrintPreview";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+
+import Loading from "@/components/Common/Loading";
+import PrintTable from "@/components/Common/PrintTable";
import QuestionnaireResponsesList from "@/components/Facility/ConsultationDetails/QuestionnaireResponsesList";
-import { MedicationsTable } from "@/components/Medicine/MedicationsTable";
-import { AllergyList } from "@/components/Patient/allergy/list";
-import { DiagnosisList } from "@/components/Patient/diagnosis/list";
-import { SymptomsList } from "@/components/Patient/symptoms/list";
+import { getFrequencyDisplay } from "@/components/Medicine/MedicationsTable";
+import { formatDosage, formatSig } from "@/components/Medicine/utils";
import api from "@/Utils/request/api";
import query from "@/Utils/request/query";
-import { formatName, formatPatientAge } from "@/Utils/utils";
-
-import { MedicationStatementList } from "./MedicationStatementList";
+import { formatDateTime, formatName, formatPatientAge } from "@/Utils/utils";
+import allergyIntoleranceApi from "@/types/emr/allergyIntolerance/allergyIntoleranceApi";
+import diagnosisApi from "@/types/emr/diagnosis/diagnosisApi";
+import { completedEncounterStatus } from "@/types/emr/encounter";
+import medicationRequestApi from "@/types/emr/medicationRequest/medicationRequestApi";
+import medicationStatementApi from "@/types/emr/medicationStatement/medicationStatementApi";
+import symptomApi from "@/types/emr/symptom/symptomApi";
interface TreatmentSummaryProps {
facilityId: string;
encounterId: string;
+
+ patientId: string;
}
+const SectionLayout = ({
+ children,
+ title,
+}: {
+ title: string;
+ children: React.ReactNode;
+}) => {
+ return (
+
+
+ {title}
+
+ {children}
+
+ );
+};
+
+const EmptyState = ({ message }: { message: string }) => {
+ return (
+
+ {message}
+
+ );
+};
export default function TreatmentSummary({
facilityId,
encounterId,
+ patientId,
}: TreatmentSummaryProps) {
- const { data: encounter } = useQuery({
+ const { data: encounter, isLoading: encounterLoading } = useQuery({
queryKey: ["encounter", encounterId],
queryFn: query(api.encounter.get, {
pathParams: { id: encounterId },
queryParams: { facility: facilityId },
}),
+ enabled: !!encounterId && !!facilityId,
});
+ const { data: allergies, isLoading: allergiesLoading } = useQuery({
+ queryKey: ["allergies", patientId, encounterId],
+ queryFn: query.paginated(allergyIntoleranceApi.getAllergy, {
+ pathParams: { patientId },
+ queryParams: {
+ encounter: (
+ encounter?.status
+ ? completedEncounterStatus.includes(encounter.status)
+ : false
+ )
+ ? encounterId
+ : undefined,
+ },
+ pageSize: 100,
+ }),
+ });
+
+ const { data: symptoms, isLoading: symptomsLoading } = useQuery({
+ queryKey: ["symptoms", patientId, encounterId],
+ queryFn: query.paginated(symptomApi.listSymptoms, {
+ pathParams: { patientId },
+ queryParams: { encounter: encounterId },
+ pageSize: 100,
+ }),
+ enabled: !!patientId && !!encounterId,
+ });
+
+ const { data: diagnoses, isLoading: diagnosesLoading } = useQuery({
+ queryKey: ["diagnosis", patientId, encounterId],
+ queryFn: query.paginated(diagnosisApi.listDiagnosis, {
+ pathParams: { patientId },
+ queryParams: { encounter: encounterId },
+ pageSize: 100,
+ }),
+ enabled: !!patientId && !!encounterId,
+ });
+
+ const { data: medications, isLoading: medicationsLoading } = useQuery({
+ queryKey: ["medication_requests", patientId, encounterId],
+ queryFn: query.paginated(medicationRequestApi.list, {
+ pathParams: { patientId },
+ queryParams: { encounter: encounterId },
+ pageSize: 100,
+ }),
+ enabled: !!encounterId,
+ });
+ const { data: medicationStatement, isLoading: medicationStatementLoading } =
+ useQuery({
+ queryKey: ["medication_statements", patientId],
+ queryFn: query.paginated(medicationStatementApi.list, {
+ pathParams: { patientId },
+ pageSize: 100,
+ }),
+ enabled: !!patientId,
+ });
+
+ if (encounterLoading) {
+ return ;
+ }
+
if (!encounter) {
return (
@@ -42,11 +137,31 @@ export default function TreatmentSummary({
);
}
+ const isLoading =
+ encounterLoading ||
+ allergiesLoading ||
+ diagnosesLoading ||
+ symptomsLoading ||
+ medicationsLoading ||
+ medicationStatementLoading;
+
+ if (isLoading) {
+ return (
+
+
+
+
+
+ );
+ }
+
return (
-
+
{/* Header */}
@@ -68,36 +183,39 @@ export default function TreatmentSummary({
{/* Patient Details */}
-
+
-
+
{t("patient")}
:
- {encounter.patient.name}
+
+ {encounter.patient.name}
+
-
+
{`${t("age")} / ${t("sex")}`}
:
-
+
{`${formatPatientAge(encounter.patient, true)}, ${t(`GENDER__${encounter.patient.gender}`)}`}
-
+
{t("encounter_class")}
:
{t(`encounter_class__${encounter.encounter_class}`)}
-
+
{t("priority")}
:
{t(`encounter_priority__${encounter.priority}`)}
+
{encounter.hospitalization?.admit_source && (
-
+
{t("admission_source")}
:
@@ -108,14 +226,14 @@ export default function TreatmentSummary({
)}
{encounter.hospitalization?.re_admission && (
-
+
{t("readmission")}
:
{t("yes")}
)}
{encounter.hospitalization?.diet_preference && (
-
+
{t("diet_preference")}
:
@@ -126,16 +244,19 @@ export default function TreatmentSummary({
)}
+
+ {/* Right Column */}
-
+
{t("mobile_number")}
:
-
+
{encounter.patient.phone_number}
+
{encounter.period?.start && (
-
+
{t("encounter_date")}
:
@@ -146,22 +267,25 @@ export default function TreatmentSummary({
)}
-
+
+
{t("status")}
:
{t(`encounter_status__${encounter.status}`)}
-
+
+
{t("consulting_doctor")}
:
{formatName(encounter.created_by)}
+
{encounter.external_identifier && (
-
+
{t("external_id")}
:
@@ -169,8 +293,9 @@ export default function TreatmentSummary({
)}
+
{encounter.hospitalization?.discharge_disposition && (
-
+
{t("discharge_disposition")}
@@ -184,51 +309,176 @@ export default function TreatmentSummary({
)}
-
{/* Medical Information */}
{/* Allergies */}
-
+
+ {allergies?.count ? (
+ ({
+ allergen: allergy.code.display,
+ status: t(allergy.clinical_status),
+ criticality: t(allergy.criticality),
+ verification: t(allergy.verification_status),
+ notes: allergy.note,
+ logged_by: formatName(allergy.created_by),
+ }))}
+ />
+ ) : (
+
+ )}
+
{/* Symptoms */}
-
+
+
+ {symptoms?.count ? (
+ ({
+ symptom: symptom.code.display,
+ severity: t(symptom.severity),
+ status: t(symptom.clinical_status),
+ verification: t(symptom.verification_status),
+ onset: symptom.onset?.onset_datetime
+ ? new Date(
+ symptom.onset.onset_datetime,
+ ).toLocaleDateString()
+ : "-",
+ notes: symptom.note,
+ logged_by: formatName(symptom.created_by),
+ }))}
+ />
+ ) : (
+
+ )}
+
{/* Diagnoses */}
-
+
+ {diagnoses?.count ? (
+ ({
+ diagnosis: diagnosis.code.display,
+ status: t(diagnosis.clinical_status),
+ verification: t(diagnosis.verification_status),
+ onset: diagnosis.onset?.onset_datetime
+ ? new Date(
+ diagnosis.onset.onset_datetime,
+ ).toLocaleDateString()
+ : undefined,
+ notes: diagnosis.note,
+ logged_by: formatName(diagnosis.created_by),
+ }))}
+ />
+ ) : (
+
+ )}
+
{/* Medications */}
-
-
- {t("medications")}
-
-
-
-
+
+ {medications?.results.length ? (
+ {
+ const instruction = medication.dosage_instruction[0];
+ const frequency = getFrequencyDisplay(instruction?.timing);
+ const dosage = formatDosage(instruction);
+ const duration =
+ instruction?.timing?.repeat?.bounds_duration;
+ const remarks = formatSig(instruction);
+ const notes = medication.note;
+ return {
+ medicine: medication.medication?.display,
+ status: t(medication.status),
+ dosage: dosage,
+ frequency: instruction?.as_needed_boolean
+ ? `${t("as_needed_prn")} (${instruction?.as_needed_for?.display ?? "-"})`
+ : (frequency?.meaning ?? "-") +
+ (instruction?.additional_instruction?.[0]?.display
+ ? `, ${instruction.additional_instruction[0].display}`
+ : ""),
+ duration: duration
+ ? `${duration.value} ${duration.unit}`
+ : "-",
+ instructions: `${remarks || "-"}${notes ? ` (${t("note")}: ${notes})` : ""}`,
+ };
+ })}
+ />
+ ) : (
+
+ )}
+
- {/* Medication Statements */}
-
+ {/* Medication Statements */}
+
+ {medicationStatement?.results.length ? (
+ ({
+ medication:
+ medication.medication.display ??
+ medication.medication.code,
+ dosage: medication.dosage_text,
+ status: medication.status,
+ medication_taken_between: [
+ medication.effective_period?.start,
+ medication.effective_period?.end,
+ ]
+ .map((date) => formatDateTime(date))
+ .join(" - "),
+ reason: medication.reason,
+ notes: medication.note,
+ logged_by: formatName(medication.created_by),
+ }))}
+ />
+ ) : (
+
+ )}
+
+
{/* Questionnaire Responses Section */}
diff --git a/src/components/Patient/allergy/list.tsx b/src/components/Patient/allergy/list.tsx
index 62695fe02e8..e65a630de03 100644
--- a/src/components/Patient/allergy/list.tsx
+++ b/src/components/Patient/allergy/list.tsx
@@ -50,7 +50,7 @@ interface AllergyListProps {
patientId: string;
encounterId?: string;
className?: string;
- isPrintPreview?: boolean;
+
encounterStatus?: Encounter["status"];
}
@@ -72,10 +72,9 @@ export function AllergyList({
patientId,
encounterId,
className,
- isPrintPreview = false,
encounterStatus,
}: AllergyListProps) {
- const [showEnteredInError, setShowEnteredInError] = useState(isPrintPreview);
+ const [showEnteredInError, setShowEnteredInError] = useState(false);
const { data: allergies, isLoading } = useQuery({
queryKey: ["allergies", patientId, encounterId, encounterStatus],
@@ -178,28 +177,22 @@ export function AllergyList({
{allergy.note && (
- {isPrintPreview ? (
-
- {allergy.note}
-
- ) : (
-
-
-
- {t("see_note")}
-
-
-
-
- {allergy.note}
-
-
-
- )}
+
+
+
+ {t("see_note")}
+
+
+
+
+ {allergy.note}
+
+
+
)}
@@ -223,7 +216,6 @@ export function AllergyList({
patientId={patientId}
encounterId={encounterId}
className={className}
- isPrintPreview={isPrintPreview}
>
@@ -295,43 +287,28 @@ const AllergyListLayout = ({
encounterId,
children,
className,
- isPrintPreview = false,
}: {
facilityId?: string;
patientId: string;
encounterId?: string;
children: ReactNode;
className?: string;
- isPrintPreview?: boolean;
}) => {
return (
-
+
{t("allergies")}
{facilityId && encounterId && (
-
+
{t("edit")}
)}
-
- {children}
-
+ {children}
);
};
diff --git a/src/components/Patient/diagnosis/DiagnosisTable.tsx b/src/components/Patient/diagnosis/DiagnosisTable.tsx
index ac9d93cc878..b80e0b6062a 100644
--- a/src/components/Patient/diagnosis/DiagnosisTable.tsx
+++ b/src/components/Patient/diagnosis/DiagnosisTable.tsx
@@ -26,13 +26,9 @@ import {
interface DiagnosisTableProps {
diagnoses: Diagnosis[];
- isPrintPreview?: boolean;
}
-export function DiagnosisTable({
- diagnoses,
- isPrintPreview = false,
-}: DiagnosisTableProps) {
+export function DiagnosisTable({ diagnoses }: DiagnosisTableProps) {
return (
@@ -100,26 +96,22 @@ export function DiagnosisTable({
{diagnosis.note ? (
- {isPrintPreview ? (
-
{diagnosis.note}
- ) : (
-
-
-
- {t("see_note")}
-
-
-
-
- {diagnosis.note}
-
-
-
- )}
+
+
+
+ {t("see_note")}
+
+
+
+
+ {diagnosis.note}
+
+
+
) : (
"-"
@@ -132,6 +124,7 @@ export function DiagnosisTable({
className="w-4 h-4"
imageUrl={diagnosis.created_by.profile_picture_url}
/>
+
{diagnosis.created_by.username}
diff --git a/src/components/Patient/diagnosis/list.tsx b/src/components/Patient/diagnosis/list.tsx
index 4695f5900f9..d1efbc8ee53 100644
--- a/src/components/Patient/diagnosis/list.tsx
+++ b/src/components/Patient/diagnosis/list.tsx
@@ -21,7 +21,6 @@ interface DiagnosisListProps {
encounterId?: string;
facilityId?: string;
className?: string;
- isPrintPreview?: boolean;
}
export function DiagnosisList({
@@ -29,9 +28,8 @@ export function DiagnosisList({
encounterId,
facilityId,
className,
- isPrintPreview = false,
}: DiagnosisListProps) {
- const [showEnteredInError, setShowEnteredInError] = useState(isPrintPreview);
+ const [showEnteredInError, setShowEnteredInError] = useState(false);
const { data: diagnoses, isLoading } = useQuery({
queryKey: ["diagnosis", patientId, encounterId],
@@ -47,6 +45,7 @@ export function DiagnosisList({
facilityId={facilityId}
patientId={patientId}
encounterId={encounterId}
+ className={className}
>
@@ -71,6 +70,7 @@ export function DiagnosisList({
facilityId={facilityId}
patientId={patientId}
encounterId={encounterId}
+ className={className}
>
{t("no_diagnoses_recorded")}
@@ -85,38 +85,39 @@ export function DiagnosisList({
patientId={patientId}
encounterId={encounterId}
className={className}
- isPrintPreview={isPrintPreview}
>
- diagnosis.verification_status !== "entered_in_error",
- ),
- ...(showEnteredInError
- ? filteredDiagnoses.filter(
- (diagnosis) =>
- diagnosis.verification_status === "entered_in_error",
- )
- : []),
- ]}
- isPrintPreview={isPrintPreview}
- />
+ <>
+
+ diagnosis.verification_status !== "entered_in_error",
+ ),
+ ...(showEnteredInError
+ ? filteredDiagnoses.filter(
+ (diagnosis) =>
+ diagnosis.verification_status === "entered_in_error",
+ )
+ : []),
+ ]}
+ />
- {hasEnteredInErrorRecords && !showEnteredInError && (
- <>
-
-
- setShowEnteredInError(true)}
- className="text-xs underline text-gray-950"
- >
- {t("view_all")}
-
-
- >
- )}
+ {hasEnteredInErrorRecords && !showEnteredInError && (
+ <>
+
+
+ setShowEnteredInError(true)}
+ className="text-xs underline text-gray-950"
+ >
+ {t("view_all")}
+
+
+ >
+ )}
+ >
);
}
@@ -127,42 +128,30 @@ const DiagnosisListLayout = ({
encounterId,
children,
className,
- isPrintPreview = false,
}: {
facilityId?: string;
patientId: string;
encounterId?: string;
children: ReactNode;
className?: string;
- isPrintPreview?: boolean;
}) => {
return (
-
+
{t("diagnoses")}
{facilityId && encounterId && (
-
+
{t("edit")}
)}
-
- {children}
-
+ {children}
);
};
diff --git a/src/components/Patient/symptoms/SymptomTable.tsx b/src/components/Patient/symptoms/SymptomTable.tsx
index 407a3300749..e3b3ec76cbc 100644
--- a/src/components/Patient/symptoms/SymptomTable.tsx
+++ b/src/components/Patient/symptoms/SymptomTable.tsx
@@ -27,13 +27,9 @@ import {
interface SymptomTableProps {
symptoms: Symptom[];
- isPrintPreview?: boolean;
}
-export function SymptomTable({
- symptoms,
- isPrintPreview = false,
-}: SymptomTableProps) {
+export function SymptomTable({ symptoms }: SymptomTableProps) {
return (
@@ -118,26 +114,22 @@ export function SymptomTable({
{symptom.note ? (
- {isPrintPreview ? (
-
{symptom.note}
- ) : (
-
-
-
- {t("see_note")}
-
-
-
-
- {symptom.note}
-
-
-
- )}
+
+
+
+ {t("see_note")}
+
+
+
+
+ {symptom.note}
+
+
+
) : (
"-"
@@ -150,6 +142,7 @@ export function SymptomTable({
className="w-4 h-4"
imageUrl={symptom.created_by.profile_picture_url}
/>
+
{symptom.created_by.username}
diff --git a/src/components/Patient/symptoms/list.tsx b/src/components/Patient/symptoms/list.tsx
index 0f4b558d6a9..c22033bbee9 100644
--- a/src/components/Patient/symptoms/list.tsx
+++ b/src/components/Patient/symptoms/list.tsx
@@ -21,7 +21,6 @@ interface SymptomsListProps {
encounterId?: string;
facilityId?: string;
className?: string;
- isPrintPreview?: boolean;
}
export function SymptomsList({
@@ -29,9 +28,8 @@ export function SymptomsList({
encounterId,
facilityId,
className,
- isPrintPreview = false,
}: SymptomsListProps) {
- const [showEnteredInError, setShowEnteredInError] = useState(isPrintPreview);
+ const [showEnteredInError, setShowEnteredInError] = useState(false);
const { data: symptoms, isLoading } = useQuery({
queryKey: ["symptoms", patientId, encounterId],
@@ -84,7 +82,6 @@ export function SymptomsList({
patientId={patientId}
encounterId={encounterId}
className={className}
- isPrintPreview={isPrintPreview}
>
{hasEnteredInErrorRecords && !showEnteredInError && (
@@ -108,7 +104,7 @@ export function SymptomsList({
variant="ghost"
size="xs"
onClick={() => setShowEnteredInError(true)}
- className="text-xs underline text-gray-500 text-gray-950"
+ className="text-xs underline text-gray-950"
>
{t("view_all")}
@@ -125,43 +121,28 @@ const SymptomListLayout = ({
encounterId,
children,
className,
- isPrintPreview = false,
}: {
facilityId?: string;
patientId: string;
encounterId?: string;
children: ReactNode;
className?: string;
- isPrintPreview?: boolean;
}) => {
return (
-
+
{t("symptoms")}
{facilityId && encounterId && (
-
+
{t("edit")}
)}
-
- {children}
-
+ {children}
);
};
diff --git a/src/components/Questionnaire/CodingEditor.tsx b/src/components/Questionnaire/CodingEditor.tsx
index 6e3fe0aba2c..020bf8d76a8 100644
--- a/src/components/Questionnaire/CodingEditor.tsx
+++ b/src/components/Questionnaire/CodingEditor.tsx
@@ -133,7 +133,7 @@ export function CodingEditor({ code, onChange }: CodingEditorProps) {
diff --git a/src/components/Questionnaire/QuestionTypes/LocationQuestion.tsx b/src/components/Questionnaire/QuestionTypes/LocationQuestion.tsx
deleted file mode 100644
index 9f809b6ed52..00000000000
--- a/src/components/Questionnaire/QuestionTypes/LocationQuestion.tsx
+++ /dev/null
@@ -1,134 +0,0 @@
-import { useQuery } from "@tanstack/react-query";
-import { format } from "date-fns";
-import React, { useEffect, useState } from "react";
-
-import { Input } from "@/components/ui/input";
-
-import { LocationSearch } from "@/components/Location/LocationSearch";
-
-import api from "@/Utils/request/api";
-import query from "@/Utils/request/query";
-import { Encounter } from "@/types/emr/encounter";
-import { LocationAssociationQuestion } from "@/types/location/association";
-import { LocationList } from "@/types/location/location";
-import {
- QuestionnaireResponse,
- ResponseValue,
-} from "@/types/questionnaire/form";
-import { Question } from "@/types/questionnaire/question";
-
-interface LocationQuestionProps {
- question: Question;
- questionnaireResponse: QuestionnaireResponse;
- updateQuestionnaireResponseCB: (
- values: ResponseValue[],
- questionId: string,
- note?: string,
- ) => void;
- disabled?: boolean;
- facilityId: string;
- locationId: string;
- encounterId: string;
-}
-
-export function LocationQuestion({
- questionnaireResponse,
- updateQuestionnaireResponseCB,
- disabled,
- facilityId,
- encounterId,
-}: LocationQuestionProps) {
- const { data: encounter } = useQuery({
- queryKey: ["encounter", encounterId],
- queryFn: query(api.encounter.get, {
- pathParams: { id: encounterId },
- queryParams: { facility: facilityId },
- }),
- });
-
- const [selectedLocation, setSelectedLocation] = useState(
- null,
- );
-
- useEffect(() => {
- if (encounter?.current_location) {
- setSelectedLocation(encounter.current_location);
- }
- }, [encounter]);
-
- const values =
- (questionnaireResponse.values?.[0]
- ?.value as unknown as LocationAssociationQuestion[]) || [];
-
- const association = values[0] ?? {};
-
- const handleUpdateAssociation = (
- updates: Partial,
- ) => {
- const newAssociation: LocationAssociationQuestion = {
- id: association?.id || null,
- encounter: encounterId,
- start_datetime: association?.start_datetime || new Date().toISOString(),
- end_datetime: null,
- status: "active",
- location: association?.location || "",
- meta: {},
- created_by: null,
- updated_by: null,
- ...updates,
- };
-
- updateQuestionnaireResponseCB(
- [{ type: "location_association", value: [newAssociation] }],
- questionnaireResponse.question_id,
- );
- };
-
- const handleLocationSelect = (location: LocationList) => {
- setSelectedLocation(location);
- handleUpdateAssociation({ location: location.id });
- };
-
- return (
-
-
-
-
-
-
-
- {selectedLocation && (
-
-
-
- handleUpdateAssociation({
- start_datetime: new Date(e.target.value).toISOString(),
- })
- }
- disabled={disabled}
- className="h-9"
- />
-
- )}
-
-
- );
-}
diff --git a/src/components/Questionnaire/QuestionTypes/QuestionInput.tsx b/src/components/Questionnaire/QuestionTypes/QuestionInput.tsx
index 25763d3d41a..f23073636d9 100644
--- a/src/components/Questionnaire/QuestionTypes/QuestionInput.tsx
+++ b/src/components/Questionnaire/QuestionTypes/QuestionInput.tsx
@@ -20,7 +20,6 @@ import { ChoiceQuestion } from "./ChoiceQuestion";
import { DateTimeQuestion } from "./DateTimeQuestion";
import { DiagnosisQuestion } from "./DiagnosisQuestion";
import { EncounterQuestion } from "./EncounterQuestion";
-import { LocationQuestion } from "./LocationQuestion";
import { MedicationRequestQuestion } from "./MedicationRequestQuestion";
import { MedicationStatementQuestion } from "./MedicationStatementQuestion";
import { NotesInput } from "./NotesInput";
@@ -186,22 +185,6 @@ export function QuestionInput({
return (
Create an encounter first in order to update it
);
- case "location_association":
- if (encounterId) {
- return (
-
- );
- }
- return (
-
- Location cannot be recorded without an active encounter
-
- );
}
return null;
diff --git a/src/components/Questionnaire/QuestionnaireEditor.tsx b/src/components/Questionnaire/QuestionnaireEditor.tsx
index 4274e7cac00..02af7500342 100644
--- a/src/components/Questionnaire/QuestionnaireEditor.tsx
+++ b/src/components/Questionnaire/QuestionnaireEditor.tsx
@@ -101,7 +101,6 @@ const STRUCTURED_QUESTION_TYPES = [
{ value: "diagnosis", label: "Diagnosis" },
{ value: "encounter", label: "Encounter" },
{ value: "appointment", label: "Appointment" },
- { value: "location_association", label: "Location Association" },
] as const;
interface Organization {
@@ -1383,7 +1382,7 @@ function QuestionEditor({
Question Settings
-
+
Configure the basic behavior: mark as required, allow multiple
entries, or set as read only.
@@ -1429,7 +1428,7 @@ function QuestionEditor({
Data Collection Details
-
+
Specify key collection info: time, performer, body site, and
method.
@@ -1538,7 +1537,7 @@ function QuestionEditor({
Answer Options
-
+
Define possible answers for this question
diff --git a/src/components/Questionnaire/QuestionnaireList.tsx b/src/components/Questionnaire/QuestionnaireList.tsx
index b046f818d10..d3ba62e4f70 100644
--- a/src/components/Questionnaire/QuestionnaireList.tsx
+++ b/src/components/Questionnaire/QuestionnaireList.tsx
@@ -109,7 +109,7 @@ export function QuestionnaireList() {
- navigate(`/admin/questionnaire/${questionnaire.slug}`)
+ navigate(`/admin/questionnaire/${questionnaire.slug}/edit`)
}
className="cursor-pointer hover:bg-gray-50"
>
diff --git a/src/components/Questionnaire/data/StructuredFormData.tsx b/src/components/Questionnaire/data/StructuredFormData.tsx
index 78d10fcb982..918d2a1561f 100644
--- a/src/components/Questionnaire/data/StructuredFormData.tsx
+++ b/src/components/Questionnaire/data/StructuredFormData.tsx
@@ -120,26 +120,6 @@ const symptom_questionnaire: QuestionnaireDetail = {
tags: [],
};
-const location_association_questionnaire: QuestionnaireDetail = {
- id: "location_association",
- slug: "location_association",
- version: "0.0.1",
- title: "Location Association",
- status: "active",
- subject_type: "patient",
- questions: [
- {
- id: "location_association",
- text: "Location Association",
- type: "structured",
- structured_type: "location_association",
- link_id: "1.1",
- required: true,
- },
- ],
- tags: [],
-};
-
export const FIXED_QUESTIONNAIRES: Record = {
encounter: encounterQuestionnaire,
medication_request: medication_request_questionnaire,
@@ -147,5 +127,4 @@ export const FIXED_QUESTIONNAIRES: Record = {
medication_statement: medication_statement_questionnaire,
diagnosis: diagnosis_questionnaire,
symptom: symptom_questionnaire,
- location_association: location_association_questionnaire,
};
diff --git a/src/components/Questionnaire/show.tsx b/src/components/Questionnaire/show.tsx
deleted file mode 100644
index 6c3fda69b27..00000000000
--- a/src/components/Questionnaire/show.tsx
+++ /dev/null
@@ -1,324 +0,0 @@
-import { useMutation, useQuery } from "@tanstack/react-query";
-import { Tags } from "lucide-react";
-import { useNavigate } from "raviger";
-import { useState } from "react";
-
-import { cn } from "@/lib/utils";
-
-import CareIcon from "@/CAREUI/icons/CareIcon";
-
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
-import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogTitle,
-} from "@/components/ui/alert-dialog";
-import { Badge } from "@/components/ui/badge";
-import { Button, buttonVariants } from "@/components/ui/button";
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-
-import Loading from "@/components/Common/Loading";
-
-import mutate from "@/Utils/request/mutate";
-import query from "@/Utils/request/query";
-import type { Question } from "@/types/questionnaire/question";
-import questionnaireApi from "@/types/questionnaire/questionnaireApi";
-
-import CloneQuestionnaireSheet from "./CloneQuestionnaireSheet";
-import ManageQuestionnaireOrganizationsSheet from "./ManageQuestionnaireOrganizationsSheet";
-import ManageQuestionnaireTagsSheet from "./ManageQuestionnaireTagsSheet";
-import { QuestionnaireForm } from "./QuestionnaireForm";
-
-interface QuestionnaireShowProps {
- id: string;
-}
-
-type TabValue = "preview" | "details";
-
-function QuestionItem({
- question,
- depth = 0,
-}: {
- question: Question;
- depth?: number;
-}) {
- const isGroup = question.type === "group";
-
- return (
- 0 ? "border-l border-gray-200" : ""}`}>
-
-
-
{question.text}
-
- {question.type}
- {question.required && Required}
- {question.code && (
-
- {question.code.display}
-
- )}
-
-
-
-
- {isGroup && question.questions && question.questions.length > 0 && (
-
- {question.questions.map((q: Question) => (
-
- ))}
-
- )}
-
- );
-}
-
-export function QuestionnaireShow({ id }: QuestionnaireShowProps) {
- const navigate = useNavigate();
- const [activeTab, setActiveTab] = useState("details");
- const [showDeleteDialog, setShowDeleteDialog] = useState(false);
-
- const {
- data: questionnaire,
- isLoading,
- error,
- } = useQuery({
- queryKey: ["questionnaireDetail", id],
- queryFn: query(questionnaireApi.detail, {
- pathParams: { id },
- }),
- });
-
- const { mutate: deleteQuestionnaire, isPending } = useMutation({
- mutationFn: mutate(questionnaireApi.delete, {
- pathParams: { id },
- }),
- onSuccess: () => {
- navigate("/admin/questionnaire");
- },
- });
-
- const handleDelete = () => {
- deleteQuestionnaire({});
- };
-
- if (isLoading) {
- return ;
- }
-
- if (error) {
- return (
-
-
- Error
-
- Failed to load questionnaire. Please try again later.
-
-
- );
- }
-
- if (!questionnaire) {
- return (
-
-
- Not Found
-
- The requested questionnaire could not be found.
-
-
- );
- }
-
- return (
-
-
-
-
{questionnaire.title}
-
{questionnaire.description}
-
-
-
navigate("/admin/questionnaire")}
- >
-
- Back to List
-
-
navigate(`/admin/questionnaire/${id}/edit`)}>
-
- Edit
-
-
-
-
-
- Manage
-
-
-
-
- e.preventDefault()}>
-
- Manage Organizations
-
- }
- />
- e.preventDefault()}>
-
- Manage Tags
-
- }
- />
- e.preventDefault()}>
-
- Clone Questionnaire
-
- }
- />
-
- setShowDeleteDialog(true)}
- >
-
- Delete Questionnaire
-
-
-
-
-
-
-
- Delete Questionnaire
-
- Are you sure you want to delete this questionnaire? This
- action cannot be undone.
-
-
-
- Cancel
-
- {isPending ? "Deleting..." : "Delete"}
-
-
-
-
-
-
-
-
setActiveTab(value as TabValue)}
- >
-
- Details
- Preview Form
-
-
-
-
-
-
- Details
-
-
-
-
-
-
- Status
-
- -
-
- {questionnaire.status}
-
-
-
-
-
-
- Version
-
- - {questionnaire.version}
-
-
-
-
- Subject Type
-
- - {questionnaire.subject_type}
-
-
-
-
-
-
-
- Questions
-
-
-
- {questionnaire.questions.map((question: Question) => (
-
- ))}
-
-
-
-
-
-
-
-
-
- {questionnaire.title}
-
- {questionnaire.description}
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/components/Questionnaire/structured/handlers.ts b/src/components/Questionnaire/structured/handlers.ts
index 47b020347f9..95995bca0f2 100644
--- a/src/components/Questionnaire/structured/handlers.ts
+++ b/src/components/Questionnaire/structured/handlers.ts
@@ -3,8 +3,6 @@ import {
RequestTypeFor,
} from "@/components/Questionnaire/structured/types";
-import { LocationAssociationQuestion } from "@/types/location/association";
-import locationApi from "@/types/location/locationApi";
import { StructuredQuestionType } from "@/types/questionnaire/question";
interface StructuredHandlerContext {
@@ -159,39 +157,6 @@ export const structuredHandlers: {
];
},
},
- location_association: {
- getRequests: (
- locationAssociations: LocationAssociationQuestion[],
- { facilityId, encounterId },
- ) => {
- if (!locationAssociations.length) {
- return [];
- }
-
- if (!facilityId) {
- throw new Error(
- "Cannot create location association without a facility",
- );
- }
-
- return locationAssociations.map((locationAssociation) => {
- return {
- url: locationApi.createAssociation.path
- .replace("{facility_external_id}", facilityId)
- .replace("{location_external_id}", locationAssociation.location),
- method: locationApi.createAssociation.method,
- body: {
- encounter: encounterId,
- start_datetime: locationAssociation.start_datetime,
- end_datetime: locationAssociation.end_datetime,
- status: locationAssociation.status,
- meta: locationAssociation.meta,
- },
- reference_id: `location_association_${locationAssociation}`,
- };
- });
- },
- },
};
export const getStructuredRequests = (
diff --git a/src/components/Questionnaire/structured/types.ts b/src/components/Questionnaire/structured/types.ts
index 03bc5f87a5b..f8e88017c6a 100644
--- a/src/components/Questionnaire/structured/types.ts
+++ b/src/components/Questionnaire/structured/types.ts
@@ -4,10 +4,6 @@ import { EncounterEditRequest } from "@/types/emr/encounter";
import { MedicationRequest } from "@/types/emr/medicationRequest";
import { MedicationStatementRequest } from "@/types/emr/medicationStatement";
import { SymptomRequest } from "@/types/emr/symptom/symptom";
-import {
- LocationAssociationQuestion,
- LocationAssociationWrite,
-} from "@/types/location/association";
import { StructuredQuestionType } from "@/types/questionnaire/question";
import {
AppointmentCreateRequest,
@@ -23,7 +19,6 @@ export interface StructuredDataMap {
medication_statement: MedicationStatementRequest;
encounter: EncounterEditRequest;
appointment: CreateAppointmentQuestion;
- location_association: LocationAssociationQuestion;
}
// Map structured types to their request types
@@ -35,7 +30,6 @@ export interface StructuredRequestMap {
medication_statement: { datapoints: MedicationStatementRequest[] };
encounter: EncounterEditRequest;
appointment: AppointmentCreateRequest;
- location_association: LocationAssociationWrite;
}
export type RequestTypeFor =
diff --git a/src/components/Resource/ResourceDetails.tsx b/src/components/Resource/ResourceDetails.tsx
index 182cd9ab514..866f8deb4ab 100644
--- a/src/components/Resource/ResourceDetails.tsx
+++ b/src/components/Resource/ResourceDetails.tsx
@@ -144,7 +144,7 @@ export default function ResourceDetails({
navigate(`/facility/${facilityId}/resource/${id}/update`)
}
>
-
+
{t("update_status")}
diff --git a/src/components/Resource/ResourceForm.tsx b/src/components/Resource/ResourceForm.tsx
index bc4981b2ab9..fa443f489e0 100644
--- a/src/components/Resource/ResourceForm.tsx
+++ b/src/components/Resource/ResourceForm.tsx
@@ -202,7 +202,7 @@ export default function ResourceForm({ facilityId, id }: ResourceProps) {
}));
const handleUserChange = (user: UserBase) => {
- form.setValue("assigned_to", user.id);
+ form.setValue("assigned_to", user.id, { shouldDirty: true });
setAssignedToUser(user);
};
@@ -210,9 +210,14 @@ export default function ResourceForm({ facilityId, id }: ResourceProps) {
form.setValue(
"referring_facility_contact_name",
`${authUser.first_name} ${authUser.last_name}`.trim(),
+ { shouldDirty: true },
);
if (authUser.phone_number) {
- form.setValue("referring_facility_contact_number", authUser.phone_number);
+ form.setValue(
+ "referring_facility_contact_number",
+ authUser.phone_number,
+ { shouldDirty: true },
+ );
}
};
@@ -292,7 +297,9 @@ export default function ResourceForm({ facilityId, id }: ResourceProps) {
facilities?.results.find(
(f) => f.id === value,
) ?? null;
- form.setValue("assigned_facility", facility);
+ form.setValue("assigned_facility", facility, {
+ shouldDirty: true,
+ });
}}
/>
@@ -551,7 +558,15 @@ export default function ResourceForm({ facilityId, id }: ResourceProps) {
>
{t("cancel")}
-
+
{isPending && (
setEditAvatar(!editAvatar)}
type="button"
id="change-avatar"
+ data-cy="change-avatar"
disabled
>
{t("change_avatar")}
@@ -138,6 +139,7 @@ export default function UserAvatar({ username }: { username: string }) {
onClick={() => setEditAvatar(!editAvatar)}
type="button"
id="change-avatar"
+ data-cy="change-avatar"
>
{t("change_avatar")}
diff --git a/src/components/Users/UserResetPassword.tsx b/src/components/Users/UserResetPassword.tsx
index fed51b5d5b3..1511f2d0354 100644
--- a/src/components/Users/UserResetPassword.tsx
+++ b/src/components/Users/UserResetPassword.tsx
@@ -101,7 +101,7 @@ export default function UserResetPassword({
variant="primary"
>
{t("update_password")}
diff --git a/src/components/ValueSet/ValueSetForm.tsx b/src/components/ValueSet/ValueSetForm.tsx
index ee4794f48b0..404e05158b4 100644
--- a/src/components/ValueSet/ValueSetForm.tsx
+++ b/src/components/ValueSet/ValueSetForm.tsx
@@ -27,6 +27,8 @@ import {
} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
+import useAppHistory from "@/hooks/useAppHistory";
+
import mutate from "@/Utils/request/mutate";
import {
TERMINOLOGY_SYSTEMS,
@@ -148,9 +150,7 @@ function ConceptFields({
@@ -401,6 +401,8 @@ export function ValueSetForm({
}: ValueSetFormProps) {
const { t } = useTranslation();
+ const { goBack } = useAppHistory();
+
const form = useForm({
resolver: zodResolver(valuesetFormSchema),
defaultValues: {
@@ -490,9 +492,19 @@ export function ValueSetForm({
-
- {isSubmitting ? t("saving") : t("save_valueset")}
-
+
+ goBack("/admin/valuesets")}
+ >
+ {t("cancel")}
+
+
+ {isSubmitting ? t("saving") : t("save_valueset")}
+
+
);
diff --git a/src/components/ui/phone-input.tsx b/src/components/ui/phone-input.tsx
index ba99fdcf042..38813cfc1d4 100644
--- a/src/components/ui/phone-input.tsx
+++ b/src/components/ui/phone-input.tsx
@@ -120,7 +120,11 @@ const CountrySelect = ({
/>
-
+
{
const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
+ const { t } = useTranslation();
if (collapsible === "none") {
return (
@@ -197,6 +204,9 @@ const Sidebar = React.forwardRef<
if (isMobile) {
return (
+
+ {t("sidebar_description")}
+
Sidebar
- {children}
+ {children}
);
@@ -220,7 +230,7 @@ const Sidebar = React.forwardRef<
return (
{isMobile ? (