Skip to content
4 changes: 2 additions & 2 deletions src/Routers/routes/ConsultationRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,11 @@ const consultationRoutes: AppRoutes = {
),
"/facility/:facilityId/patient/:patientId/encounter/:encounterId/report/template/:templateSlug":
({ encounterId, templateSlug }) => (
<ReportViewer encounterId={encounterId} templateSlug={templateSlug} />
<ReportViewer associatingId={encounterId} templateSlug={templateSlug} />
),
"/facility/:facilityId/patient/:patientId/encounter/:encounterId/report/:reportId":
({ encounterId, reportId }) => (
<ReportViewer encounterId={encounterId} reportId={reportId} />
<ReportViewer associatingId={encounterId} reportId={reportId} />
),
"/facility/:facilityId/patient/:patientId/encounter/:encounterId/questionnaire":
({ facilityId, encounterId, patientId }) => (
Expand Down
20 changes: 20 additions & 0 deletions src/Routers/routes/FacilityRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import MedicationDispenseRedirect from "@/pages/Facility/billing/account/compone
import BedAvailabilityDashboard from "@/pages/Facility/BedAvailabilityDashboard";

import { AppRoutes } from "@/Routers/AppRouter";
import ReportViewer from "@/pages/Encounters/ReportViewer";
import TemplateBuilder from "@/pages/Encounters/TemplateBuilder/TemplateBuilder";
import TemplatePage from "@/pages/Encounters/TemplateBuilder/TemplatePage";
import AccountList from "@/pages/Facility/billing/account/AccountList";
Expand All @@ -28,6 +29,7 @@ import DiagnosticReportPrint from "@/pages/Facility/services/diagnosticReports/D
import DiagnosticReportView from "@/pages/Facility/services/diagnosticReports/DiagnosticReportView";
import ServiceRequestShow from "@/pages/Facility/services/serviceRequests/ServiceRequestShow";
import { SettingsLayout } from "@/pages/Facility/settings/layout";
import { ReportType } from "@/types/emr/report/report";

const FacilityRoutes: AppRoutes = {
"/facility": () => <Redirect to="/" />,
Expand Down Expand Up @@ -104,6 +106,24 @@ const FacilityRoutes: AppRoutes = {
facilityId,
accountId,
}) => <PrintChargeItems facilityId={facilityId} accountId={accountId} />,
"/facility/:facilityId/billing/account/:accountId/report/template/:templateSlug":
({ accountId, templateSlug }) => (
<ReportViewer
associatingId={accountId}
templateSlug={templateSlug}
reportType={ReportType.ACCOUNT_REPORT}
/>
),
"/facility/:facilityId/billing/account/:accountId/report/:reportId": ({
accountId,
reportId,
}) => (
<ReportViewer
associatingId={accountId}
reportId={reportId}
reportType={ReportType.ACCOUNT_REPORT}
/>
),
"/facility/:facilityId/billing/account/:accountId/:tab": ({
facilityId,
accountId,
Expand Down
22 changes: 22 additions & 0 deletions src/Routers/routes/PatientRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import { AppRoutes } from "@/Routers/AppRouter";
import { PatientRegistration } from "@/components/Patient/PatientRegistration";
import { ConsentDetailPage } from "@/pages/Encounters/ConsentDetail";
import EncountersOverview from "@/pages/Encounters/EncountersOverview";
import ReportViewer from "@/pages/Encounters/ReportViewer";
import { EncounterProvider } from "@/pages/Encounters/utils/EncounterProvider";
import ClinicalHistoryPage from "@/pages/Patient/History";
import PatientHome from "@/pages/Patient/PatientHome";
import { ReportType } from "@/types/emr/report/report";
import careConfig from "@careConfig";

const ExcalidrawEditor = lazy(
Expand Down Expand Up @@ -164,6 +166,26 @@ const PatientRoutes: AppRoutes = {
fallBackUrl={`/patient/${patientId}`}
/>
),
"/facility/:facilityId/patient/:patientId/report/template/:templateSlug": ({
patientId,
templateSlug,
}) => (
<ReportViewer
associatingId={patientId}
templateSlug={templateSlug}
reportType={ReportType.PATIENT_SUMMARY}
/>
),
"/facility/:facilityId/patient/:patientId/report/:reportId": ({
patientId,
reportId,
}) => (
<ReportViewer
associatingId={patientId}
reportId={reportId}
reportType={ReportType.PATIENT_SUMMARY}
/>
),
};

export default PatientRoutes;
37 changes: 0 additions & 37 deletions src/components/Common/FilePreviewDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ import {
FileReadMinimal,
getVideoMimeType,
} from "@/types/files/file";
import { ShortcutBadge } from "@/Utils/keyboardShortcutComponents";
import { Printer } from "lucide-react";

const PDFViewer = lazy(() => import("@/components/Common/PDFViewer"));
export interface StateInterface {
Expand Down Expand Up @@ -247,41 +245,6 @@ export default function FilePreviewDialog(props: FilePreviewProps) {
)}
</div>
<div className="flex gap-2">
{file_state.extension === "pdf" && (
<Button
variant="outline"
onClick={async () => {
try {
const response = await fetch(fileUrl);
const blob = await response.blob();
const blobUrl = window.URL.createObjectURL(blob);
const iframe = document.createElement("iframe");
iframe.style.display = "none";
iframe.src = blobUrl;

iframe.onload = () => {
try {
iframe.contentWindow?.print();
} catch {
window.open(blobUrl, "_blank");
}
setTimeout(() => {
document.body.removeChild(iframe);
window.URL.revokeObjectURL(blobUrl);
}, 10000);
};

document.body.appendChild(iframe);
} catch {
toast.error(t("print_failed"));
}
}}
>
<Printer className="size-4" />
<span className="hidden sm:block">{t("print")}</span>
<ShortcutBadge actionId="print-button" />
</Button>
)}
{file_state.extension === "pdf" && fileUrl && (
<Button
variant="outline"
Expand Down
1 change: 0 additions & 1 deletion src/components/Files/FilesTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ export const FilesTab = ({
}
facilityId={encounter?.facility?.id}
patientId={patient?.id}
encounterId={encounter?.id}
/>
</TabsContent>

Expand Down
169 changes: 144 additions & 25 deletions src/components/Files/ReportSubTab.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { useQuery } from "@tanstack/react-query";
import dayjs from "dayjs";
import { SearchIcon } from "lucide-react";
import { FileText, Plus, SearchIcon } from "lucide-react";
import { navigate } from "raviger";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";

import { cn } from "@/lib/utils";

Expand All @@ -26,41 +29,66 @@ import {
} from "@/components/ui/table";
import { TooltipComponent } from "@/components/ui/tooltip";

import { PERMISSION_LIST_TEMPLATE } from "@/common/Permissions";
import Loading from "@/components/Common/Loading";
import { FilterBadges, FilterButton } from "@/components/Files/FileFilters";
import { EmptyState } from "@/components/ui/empty-state";
import { usePermissions } from "@/context/PermissionContext";

import useFilters from "@/hooks/useFilters";
import useReportManager from "@/hooks/useReportManager";

import query from "@/Utils/request/query";
import queryClient from "@/Utils/request/queryClient";
import TemplateReportSheet from "@/pages/Encounters/TemplateBuilder/TemplateReportSheet";
import NavigationHelper from "@/components/ui/multi-filter/utils/navigation-helper";
import { useCurrentFacilitySilently } from "@/pages/Facility/utils/useCurrentFacility";
import {
ReportRead,
ReportReadList,
ReportType,
} from "@/types/emr/report/report";
import { navigate } from "raviger";
import { toast } from "sonner";
import templateApi from "@/types/emr/template/templateApi";

function getReportBasePath(
reportType: ReportType | undefined,
associatingId: string,
facilityId?: string,
patientId?: string,
): string | null {
switch (reportType) {
case ReportType.DISCHARGE_SUMMARY:
return facilityId && patientId
? `/facility/${facilityId}/patient/${patientId}/encounter/${associatingId}`
: null;
case ReportType.ACCOUNT_REPORT:
return facilityId
? `/facility/${facilityId}/billing/account/${associatingId}`
: null;
case ReportType.PATIENT_SUMMARY:
return facilityId && patientId
? `/facility/${facilityId}/patient/${patientId}`
: null;
default:
return null;
}
}

interface ReportTabProps {
associatingId: string;
reportType?: ReportType;
facilityId?: string;
patientId?: string;
encounterId?: string;
}

export function ReportSubTab({
associatingId,
reportType,
facilityId,
patientId,
encounterId,
}: ReportTabProps) {
const { t } = useTranslation();
const { facility } = useCurrentFacilitySilently();

const { qParams, updateQuery, Pagination } = useFilters({
limit: 15,
disableCache: true,
Expand All @@ -72,7 +100,6 @@ export function ReportSubTab({
viewFile,
downloadFile,
archiveReport,
refetch,
Dialogs,
} = useReportManager({
associatingId,
Expand All @@ -98,13 +125,15 @@ export function ReportSubTab({
return iconMap[reportType] || "l-file-alt";
};

const canNavigateToPreview = !!(facilityId && patientId && encounterId);

const handleView = (report: ReportReadList) => {
if (canNavigateToPreview) {
navigate(
`/facility/${facilityId}/patient/${patientId}/encounter/${encounterId}/report/${report.id}`,
);
const basePath = getReportBasePath(
reportType,
associatingId,
facilityId ?? facility?.id,
patientId,
);
if (basePath) {
navigate(`${basePath}/report/${report.id}`);
} else {
viewFile(report);
}
Comment thread
abhimanyurajeesh marked this conversation as resolved.
Expand Down Expand Up @@ -374,20 +403,11 @@ export function ReportSubTab({
</Button>
</div>
{facility && (
<TemplateReportSheet
facilityId={facility.id}
<GenerateReportDropdown
facilityId={facilityId ?? facility.id}
patientId={patientId}
associatingId={associatingId}
permissions={facility.permissions ?? []}
reportType={reportType}
trigger={
<Button variant="outline_primary">
<CareIcon icon="l-plus" className="mr-1" />
<span>{t("generate_report")}</span>
</Button>
}
onSuccess={() => {
refetch();
}}
/>
)}
</div>
Expand Down Expand Up @@ -426,3 +446,102 @@ export function ReportSubTab({
</div>
);
}

function GenerateReportDropdown({
facilityId,
patientId,
associatingId,
reportType,
}: {
facilityId: string;
patientId?: string;
associatingId: string;
reportType?: ReportType;
}) {
const { t } = useTranslation();
const { facility } = useCurrentFacilitySilently();
const { hasPermission } = usePermissions();

const canListTemplate = hasPermission(
PERMISSION_LIST_TEMPLATE,
facility?.permissions,
);

const { data: templatesData, isLoading } = useQuery({
queryKey: ["templates", facilityId, reportType],
queryFn: query(templateApi.listTemplates, {
queryParams: {
facility: facilityId,
template_type: reportType,
status: "active",
},
}),
enabled: canListTemplate && !!reportType,
});

const getTemplateUrl = (slug: string) => {
const basePath = getReportBasePath(
reportType,
associatingId,
facilityId,
patientId,
);
return basePath ? `${basePath}/report/template/${slug}` : null;
};

const templates = (templatesData?.results ?? []).flatMap((template) => {
const url = getTemplateUrl(template.slug);
return url ? [{ template, url }] : [];
});

if (!isLoading && templates.length === 0) return null;
Comment thread
abhimanyurajeesh marked this conversation as resolved.

if (isLoading || templates.length === 1) {
return (
<Button
variant="outline_primary"
disabled={isLoading}
onClick={() => templates[0] && navigate(templates[0].url)}
>
{isLoading ? (
<CareIcon icon="l-spinner" className="size-4 animate-spin" />
) : (
<Plus className="size-4" />
)}
<span>{t("generate_report")}</span>
</Button>
);
}

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline_primary">
<Plus className="size-4" />
<span>{t("generate_report")}</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="w-full max-w-[calc(100vw-3rem)] sm:max-w-xs p-0"
>
<div className="px-2 pt-2">
<div className="max-h-[30vh] overflow-y-auto pb-2">
{templates.map(({ template, url }) => {
return (
<DropdownMenuItem
key={template.id}
onClick={() => navigate(url)}
>
<FileText className="size-4 shrink-0" />
<span className="truncate">{template.name}</span>
</DropdownMenuItem>
);
})}
</div>
<NavigationHelper hideRightArrow />
Comment thread
abhimanyurajeesh marked this conversation as resolved.
</div>
</DropdownMenuContent>
</DropdownMenu>
);
}
Loading
Loading