diff --git a/public/locale/en.json b/public/locale/en.json index 88fbb919d3a..f085ef58ff6 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -693,6 +693,7 @@ "and_more_medications": "+{{count}} more medication(s)", "and_more_service_requests": "+{{count}} more service request(s)", "and_the_status_of_request_is": "and the status of request is", + "another_diagnostic_report": "Another Diagnostic Report", "answer": "Answer", "answer_options": "Answer options", "answer_options_description": "Define possible answers for this question", @@ -2537,6 +2538,7 @@ "failed_to_add_service_request": "Failed to add service request", "failed_to_add_to_template": "Failed to add medication to template", "failed_to_apply_template": "Failed to apply template", + "failed_to_approve_diagnostic_report": "Failed to approve diagnostic report: {{error}}", "failed_to_archive_child_tag": "Failed to archive child tag", "failed_to_cancel_invoice": "Failed to cancel invoice", "failed_to_cancel_payment": "Failed to cancel payment", @@ -2544,6 +2546,7 @@ "failed_to_check_appointments": "Failed to check appointments", "failed_to_create_appointment": "Failed to create an appointment", "failed_to_create_category": "Failed to create category", + "failed_to_create_diagnostic_report": "Failed to create diagnostic report: {{error}}", "failed_to_create_invoice": "Failed to create invoice", "failed_to_create_questionnaire": "Failed to create Questionnaire", "failed_to_create_queue": "Failed to create queue", @@ -2566,6 +2569,7 @@ "failed_to_remove_tags": "Failed to remove the tag", "failed_to_restart_encounter": "Failed to restart encounter", "failed_to_revoke_token": "Failed to revoke token", + "failed_to_save_test_results": "Failed to save test results: {{error}}", "failed_to_send_message": "Failed to send message", "failed_to_stop_camera": "Failed to stop camera", "failed_to_unlock_invoice": "Failed to unlock invoice", @@ -5030,7 +5034,7 @@ "result_date": "Result Date", "result_details": "Result details", "result_on": "Result on", - "result_review": "Result Review", + "result_review": "Result Review of {{name}}", "result_value": "Result value", "resume": "Resume", "retake": "Retake", @@ -5062,6 +5066,7 @@ "review_and_finalise_request_description": "Add more items if needed, or approve to mark this delivery as requested.", "review_before": "Review Before", "review_missed": "Review Missed", + "review_test_results": "Review Test Result", "revisit_days_non_negative": "Re-visit allowed days cannot be negative", "revoke": "Revoke", "revoke_token": "Revoke Token", @@ -5527,6 +5532,7 @@ "select_register_patient": "Select/Register Patient", "select_report": "Select Report", "select_report_type": "Select Report Type", + "select_report_type_to_create": "Select a diagnostic report type to create a new report", "select_requester": "Select requester", "select_resource": "Select the resource", "select_resource_category": "Select resource category", @@ -6062,6 +6068,7 @@ "test_results": "Test Results", "test_results_actions": "Test Results actions", "test_results_entry": "Test Results Entry", + "test_results_saved_successfully": "Test results saved successfully", "test_type": "Type of test done", "tested_on": "Tested on", "tests": "Tests", diff --git a/src/hooks/useFileUpload.tsx b/src/hooks/useFileUpload.tsx index 89074727668..ad18399b71e 100644 --- a/src/hooks/useFileUpload.tsx +++ b/src/hooks/useFileUpload.tsx @@ -29,6 +29,7 @@ import fileApi from "@/types/files/fileApi"; export type FileUploadOptions = { multiple?: boolean; type: FileType; + inputId?: string; category?: FileCategory; onUpload?: (file: FileReadMinimal) => void; // if allowed, will fallback to the name of the file if a seperate filename is not defined. @@ -80,6 +81,7 @@ export default function useFileUpload( category = FileCategory.UNSPECIFIED, multiple, allowNameFallback = true, + inputId, } = options; const { t } = useTranslation(); @@ -355,7 +357,7 @@ export default function useFileUpload( const Input = (props: FileInputProps) => ( report.status !== DiagnosticReportStatus.final, + ); + const assignedSpecimenIds = new Set(); const preparePrintAllQRCodes = async () => { @@ -596,22 +600,18 @@ export default function ServiceRequestShow({ )} - {(!diagnosticReports.length || - diagnosticReports[0]?.status !== - DiagnosticReportStatus.final) && ( - - )} - + + {diagnosticReports.length > 0 && ( ; + + facilityId?: string; } // New interface to handle multiple observations per definition @@ -119,56 +122,24 @@ export function DiagnosticReportForm({ activityDefinition, specimens, disableEdit, + facilityId, }: DiagnosticReportFormProps) { const { t } = useTranslation(); - const [observations, setObservations] = useState( - {}, - ); - const [isExpanded, setIsExpanded] = useState(true); + const queryClient = useQueryClient(); + + const [showReportTypeSelect, setShowReportTypeSelect] = useState(false); const [selectedReportCode, setSelectedReportCode] = useState( null, ); - const [openUploadDialog, setOpenUploadDialog] = useState(false); - const [conclusion, setConclusion] = useState(""); - const queryClient = useQueryClient(); - - // Get the latest report if any exists - const latestReport = - diagnosticReports.length > 0 ? diagnosticReports[0] : null; - const hasReport = !!latestReport; // Check if all required specimens are collected const hasCollectedSpecimens = activityDefinition?.specimen_requirements?.length === 0 || specimens.some((specimen) => specimen.status === SpecimenStatus.available); - // Fetch the full diagnostic report to get observations - const { data: fullReport, isLoading: isLoadingReport } = useQuery({ - queryKey: ["diagnosticReport", latestReport?.id], - queryFn: query(diagnosticReportApi.retrieveDiagnosticReport, { - pathParams: { - patient_external_id: patientId, - external_id: latestReport?.id || "", - }, - }), - enabled: !!latestReport?.id, - }); - - // Query to fetch files for the diagnostic report - const { data: files = { results: [], count: 0 } } = useQuery< - PaginatedResponse - >({ - queryKey: ["files", "diagnostic_report", fullReport?.id], - queryFn: query(fileApi.list, { - queryParams: { - file_type: "diagnostic_report", - associating_id: fullReport?.id, - limit: 100, - offset: 0, - }, - }), - enabled: !!fullReport?.id, - }); + const isMultipleDiagnosticReport = + !!activityDefinition?.diagnostic_report_codes && + activityDefinition.diagnostic_report_codes.length > 0; // Creating a new diagnostic report const { mutate: createDiagnosticReport, isPending: isCreatingReport } = @@ -183,35 +154,183 @@ export function DiagnosticReportForm({ queryClient.invalidateQueries({ queryKey: ["serviceRequest"], }); - // Fetch the newly created report queryClient.invalidateQueries({ queryKey: ["diagnosticReport"], }); }, - onError: (err: any) => { + onError: (err: Error) => { toast.error( - `Failed to create diagnostic report: ${err.message || "Unknown error"}`, + t("failed_to_create_diagnostic_report", { + error: err.message || "Unknown error", + }), ); }, }); - // Effect to handle diagnostic reports changes - useEffect(() => { - const latestReport = diagnosticReports[0]; - if (latestReport) { - // If we have a new report, update the UI accordingly - setSelectedReportCode(latestReport.code || null); - setIsExpanded(true); + function handleCreateReport(code?: Code) { + if (!hasCollectedSpecimens) { + toast.error(t("specimen_collection_required")); + return; } - }, [diagnosticReports]); - // Effect to handle fullReport changes - useEffect(() => { - if (fullReport) { - // When we get the full report details, ensure UI is in correct state - setSelectedReportCode(fullReport.code || null); - } - }, [fullReport]); + const category: Code = { + code: "LAB", + display: "Laboratory", + system: "http://terminology.hl7.org/CodeSystem/v2-0074", + }; + + createDiagnosticReport({ + status: DiagnosticReportStatus.preliminary, + category, + service_request: serviceRequestId, + code: code || undefined, + }); + } + + return ( + <> + {diagnosticReports.length > 0 && ( +
+
+ {diagnosticReports.map((report) => ( + + ))} +
+ {isMultipleDiagnosticReport && ( +
+ {showReportTypeSelect ? ( + + ) : ( + + )} +
+ )} +
+ )} + {diagnosticReports.length === 0 && ( + + )} + + ); +} + +function DiagnosticReportItem({ + report, + patientId, + serviceRequestId, + observationDefinitions, + disableEdit, + facilityId, + isMultipleDiagnosticReport, +}: { + report: DiagnosticReportRead; + patientId: string; + serviceRequestId: string; + observationDefinitions: ObservationDefinitionReadSpec[]; + disableEdit: boolean; + facilityId?: string; + isMultipleDiagnosticReport: boolean; +}) { + const { t } = useTranslation(); + const queryClient = useQueryClient(); + const [observations, setObservations] = useState( + {}, + ); + const [isExpanded, setIsExpanded] = useState(true); + const [openUploadDialog, setOpenUploadDialog] = useState(false); + const [conclusion, setConclusion] = useState(report.conclusion || ""); + + const { data: fullReport } = useQuery({ + queryKey: ["diagnosticReport", report.id], + queryFn: query(diagnosticReportApi.retrieveDiagnosticReport, { + pathParams: { + patient_external_id: patientId, + external_id: report.id, + }, + }), + enabled: !!report.id, + }); + + // Query to fetch files for the diagnostic report + const { data: files = { results: [], count: 0 } } = useQuery< + PaginatedResponse + >({ + queryKey: ["files", "diagnostic_report", report.id], + queryFn: query(fileApi.list, { + queryParams: { + file_type: "diagnostic_report", + associating_id: report.id, + limit: 100, + offset: 0, + }, + }), + enabled: !!report.id, + }); // Upserting observations for a diagnostic report const { mutate: upsertObservations, isPending: isUpsertingObservations } = @@ -219,21 +338,23 @@ export function DiagnosticReportForm({ mutationFn: mutate(observationApi.upsertObservations, { pathParams: { patient_external_id: patientId, - external_id: latestReport?.id || "", + external_id: report.id, }, }), onSuccess: () => { - toast.success("Test results saved successfully"); + toast.success(t("test_results_saved_successfully")); queryClient.invalidateQueries({ - queryKey: ["serviceRequest", serviceRequestId], + queryKey: ["serviceRequest", facilityId, serviceRequestId], }); queryClient.invalidateQueries({ - queryKey: ["diagnosticReport", latestReport?.id], + queryKey: ["diagnosticReport", report.id], }); }, - onError: (err: any) => { + onError: (err: Error) => { toast.error( - `Failed to save test results: ${err.message || "Unknown error"}`, + t("failed_to_save_test_results", { + error: err.message || "Unknown error", + }), ); }, }); @@ -243,13 +364,13 @@ export function DiagnosticReportForm({ mutationFn: mutate(diagnosticReportApi.updateDiagnosticReport, { pathParams: { patient_external_id: patientId, - external_id: latestReport?.id || "", + external_id: report.id, }, }), onSuccess: () => { toast.success(t("conclusion_updated_successfully")); queryClient.invalidateQueries({ - queryKey: ["diagnosticReport", latestReport?.id], + queryKey: ["diagnosticReport", report.id], }); setIsExpanded(false); }, @@ -259,14 +380,16 @@ export function DiagnosticReportForm({ }); // Initialize file upload hook + const inputId = `file_upload_diagnostic_report_${report.id}`; const fileUpload = useFileUpload({ - type: "diagnostic_report" as any, + type: "diagnostic_report" as FileType, + inputId, multiple: true, allowedExtensions: BACKEND_ALLOWED_EXTENSIONS, allowNameFallback: false, onUpload: () => { queryClient.invalidateQueries({ - queryKey: ["diagnosticReport", latestReport?.id], + queryKey: ["diagnosticReport", report.id], }); }, compress: false, @@ -289,9 +412,10 @@ export function DiagnosticReportForm({ if (!openUploadDialog) { fileUpload.clearFiles(); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [openUploadDialog]); - // Initialize form with existing observations from the full report + // Initialize form with existing observations from the report useEffect(() => { if (fullReport?.observations && fullReport.observations.length > 0) { const initialObservations: ObservationsByDefinition = {}; @@ -333,10 +457,10 @@ export function DiagnosticReportForm({ }); setObservations(initialObservations); + } - if (fullReport.conclusion) { - setConclusion(fullReport.conclusion); - } + if (fullReport?.conclusion) { + setConclusion(fullReport.conclusion); } }, [fullReport]); @@ -467,36 +591,7 @@ export function DiagnosticReportForm({ }); } - function handleCreateReport() { - // Only create a new report if no reports exist - if (!hasReport) { - if (!hasCollectedSpecimens) { - toast.error(t("specimen_collection_required")); - return; - } - - const category: Code = { - code: "LAB", - display: "Laboratory", - system: "http://terminology.hl7.org/CodeSystem/v2-0074", - }; - - createDiagnosticReport({ - status: DiagnosticReportStatus.preliminary, - category, - service_request: serviceRequestId, - code: selectedReportCode || undefined, - }); - } - } - function handleSubmit() { - if (!hasReport) { - // First create a report if none exists - handleCreateReport(); - return; - } - try { // Check if all observations have values const hasObservationValue = Object.values(observations).some((obsList) => @@ -645,23 +740,21 @@ export function DiagnosticReportForm({ ) .filter((obs): obs is ObservationUpsertRequest => obs !== null); - if (fullReport) { - // Upsert observations - if (formattedObservations.length > 0) { - upsertObservations({ - observations: formattedObservations, - }); - } - - updateDiagnosticReport({ - id: fullReport.id, - status: fullReport.status, - category: fullReport.category, - code: fullReport.code, - note: fullReport.note, - conclusion, + // Upsert observations + if (formattedObservations.length > 0) { + upsertObservations({ + observations: formattedObservations, }); } + + updateDiagnosticReport({ + id: report.id, + status: report.status, + category: report.category, + code: report.code, + note: report.note, + conclusion, + }); } catch (_error) { toast.error(t("error_validating_form")); } @@ -808,23 +901,7 @@ export function DiagnosticReportForm({ ); } - const isSubmitting = - isCreatingReport || isUpsertingObservations || isUpdatingReport; - - // Show loading state while fetching the report - if (hasReport && isLoadingReport) { - return ( - - -
- - - -
-
-
- ); - } + const isSubmitting = isUpsertingObservations || isUpdatingReport; return ( {" "} - {t("test_results_entry")} + {isMultipleDiagnosticReport + ? report.code?.display + : report.service_request?.title}

- {hasReport && fullReport?.created_by && ( -
+
+ {report.created_by && ( - - {formatName(fullReport.created_by)} - -
- )} -
- {hasReport && fullReport && ( - - {t(fullReport.status)} - )} + + {formatName(report.created_by)} + +
+
+ + {t(report.status)} + + ) )} - > -
- - {isErrored ? ( - - {t("marked_for_deletion")} - - ) : ( - !disableEdit && ( - - ) - )} -
+
- {/* For blood pressure and similar observations with components, we may or may not need to show the main value field */} - {!hasComponents && ( -
- {definition.permitted_unit && ( -
- - -
- )} - -
+ {/* For blood pressure and similar observations with components, we may or may not need to show the main value field */} + {!hasComponents && ( +
+ {definition.permitted_unit && ( +
- - handleValueChange( +
+ )} + +
+ + + handleValueChange( + definition.id, + index, + e.target.value, + observationData.unit, + ) + } + placeholder={t("result_value")} + type={ + definition.permitted_data_type === + "decimal" || + definition.permitted_data_type === + "integer" + ? "number" + : "text" + } + disabled={isErrored || disableEdit} + />
+
+ )} + + {/* Render component inputs for multi-component observations */} + {hasComponents && + renderComponentInputs( + definition, + observationData, + index, )} +
+ ); + })} + + {/* Add button for multiple observations */} + +
+ + + ); + })} + +
+ {files?.results && files.results.length > 0 && ( +
+
+ {t("uploaded_files")} +
+ +
+ )} - {/* Render component inputs for multi-component observations */} - {hasComponents && - renderComponentInputs( - definition, - observationData, - index, - )} -
- ); + {report?.status === DiagnosticReportStatus.preliminary && ( + + +
+
+ + +
+ {t("allowed_formats_are", { + formats: + BACKEND_ALLOWED_EXTENSIONS.slice(0, 5).join( + ", ", + ) + + ", " + + t("etc"), })} - - {/* Add button for multiple observations */} -
- - - ); - })} + +
- {fullReport.status !== DiagnosticReportStatus.final && ( + {fileUpload.files.length > 0 && ( + + )} +
+
+
+ )} + {report.status !== DiagnosticReportStatus.final && ( )} + {report?.status === DiagnosticReportStatus.preliminary && ( +
+ +
+ )} +
+
+ + + -
- {fullReport?.status === - DiagnosticReportStatus.preliminary && ( -
- -
- )} + {fileUpload.Dialogues} + + + ); +} - {files?.results && files.results.length > 0 && ( -
-
- {t("uploaded_files")} -
- -
- )} +const CreateDiagnosticReportForm = ({ + activityDefinition, + isCreatingReport, + disableEdit, + serviceRequestId, + handleCreateReport, + hasCollectedSpecimens, + isMultipleDiagnosticReport, +}: { + activityDefinition?: { + diagnostic_report_codes?: Code[]; + classification?: string; + specimen_requirements?: SpecimenDefinitionRead[]; + }; + specimens: SpecimenRead[]; + isCreatingReport: boolean; + disableEdit: boolean; + serviceRequestId: string; + handleCreateReport: (code?: Code) => void; + hasCollectedSpecimens: boolean; + isMultipleDiagnosticReport: boolean; +}) => { + const [isExpanded, setIsExpanded] = useState(false); + const { t } = useTranslation(); - {fullReport?.status === - DiagnosticReportStatus.preliminary && ( - - -
-
- - -
- {t("allowed_formats_are", { - formats: - BACKEND_ALLOWED_EXTENSIONS.slice(0, 5).join( - ", ", - ) + - ", " + - t("etc"), - })} -
- -
+ const [selectedReportCode, setSelectedReportCode] = useState( + null, + ); - {fileUpload.files.length > 0 && ( - - )} -
-
-
- )} + return ( + + + + +
+
+ +

+ {" "} + + {t("test_results_entry")} + +

+
+
+
+
+
- ) : ( -
-
-

- {!hasCollectedSpecimens - ? t("collect_specimen_before_report") - : t("no_test_results_recorded")} +

+ + + + + + +
+
+ +

+ {!hasCollectedSpecimens + ? t("collect_specimen_before_report") + : t("no_test_results_recorded")} +

+ {isMultipleDiagnosticReport && ( +

+ {t("select_report_type_to_create")}

-
-
- {activityDefinition?.diagnostic_report_codes && - activityDefinition.diagnostic_report_codes.length > 0 && ( -
- -
- )} + )} + {!isMultipleDiagnosticReport && ( -
+ )}
- )} + {isMultipleDiagnosticReport && ( +
+
+ + +
+ +
+ + +
+
+ )} +
- - {fileUpload.Dialogues} - ); -} +}; diff --git a/src/pages/Facility/services/serviceRequests/components/DiagnosticReportReview.tsx b/src/pages/Facility/services/serviceRequests/components/DiagnosticReportReview.tsx index 475213daf45..418c922ddeb 100644 --- a/src/pages/Facility/services/serviceRequests/components/DiagnosticReportReview.tsx +++ b/src/pages/Facility/services/serviceRequests/components/DiagnosticReportReview.tsx @@ -7,7 +7,7 @@ import { FileCheck2, } from "lucide-react"; import { Link } from "raviger"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; @@ -22,7 +22,6 @@ import { CollapsibleTrigger, } from "@/components/ui/collapsible"; import { Label } from "@/components/ui/label"; -import { Skeleton } from "@/components/ui/skeleton"; import { Avatar } from "@/components/Common/Avatar"; import ConfirmActionDialog from "@/components/Common/ConfirmActionDialog"; @@ -32,6 +31,7 @@ import mutate from "@/Utils/request/mutate"; import query from "@/Utils/request/query"; import { PaginatedResponse } from "@/Utils/request/types"; import { formatName } from "@/Utils/utils"; +import { Textarea } from "@/components/ui/textarea"; import { DiagnosticReportResultsTable } from "@/pages/Facility/services/diagnosticReports/components/DiagnosticReportResultsTable"; import { DIAGNOSTIC_REPORT_STATUS_COLORS, @@ -58,43 +58,68 @@ export function DiagnosticReportReview({ disableEdit, }: DiagnosticReportReviewProps) { const { t } = useTranslation(); + return ( +
+ {diagnosticReports.some( + (report) => report.status !== DiagnosticReportStatus.final, + ) && ( +

{t("review_test_results")}

+ )} + + {diagnosticReports.map((report) => ( + + ))} +
+ ); +} + +function DiagnosticReportReviewItem({ + report, + facilityId, + patientId, + disableEdit, +}: { + report: DiagnosticReportRead; + facilityId: string; + patientId: string; + disableEdit: boolean; +}) { + const { t } = useTranslation(); + const queryClient = useQueryClient(); const [isExpanded, setIsExpanded] = useState(true); - const [conclusion, setConclusion] = useState(""); + const [conclusion, setConclusion] = useState(report.conclusion || ""); const [showApproveDialog, setShowApproveDialog] = useState(false); - const queryClient = useQueryClient(); - const latestReport = diagnosticReports[0]; - // Fetch the full diagnostic report to get observations - const { data: fullReport, isLoading: isLoadingReport } = useQuery({ - queryKey: ["diagnosticReport", latestReport?.id], + const { data: fullReport } = useQuery({ + queryKey: ["diagnosticReport", report.id], queryFn: query(diagnosticReportApi.retrieveDiagnosticReport, { pathParams: { patient_external_id: patientId, - external_id: latestReport?.id || "", + external_id: report.id, }, }), - enabled: !!latestReport?.id, + enabled: !!report.id, }); - useEffect(() => { - if (fullReport?.conclusion) { - setConclusion(fullReport.conclusion); - } - }, [fullReport]); - const { data: files = { results: [], count: 0 } } = useQuery< PaginatedResponse >({ - queryKey: ["files", "diagnostic_report", fullReport?.id], + queryKey: ["files", "diagnostic_report", report.id], queryFn: query(fileApi.list, { queryParams: { file_type: "diagnostic_report", - associating_id: fullReport?.id, + associating_id: report.id, limit: 100, offset: 0, }, }), - enabled: !!fullReport?.id, + enabled: !!report.id, }); const { mutate: updateDiagnosticReport, isPending: isUpdatingReport } = @@ -102,7 +127,7 @@ export function DiagnosticReportReview({ mutationFn: mutate(diagnosticReportApi.updateDiagnosticReport, { pathParams: { patient_external_id: patientId, - external_id: latestReport?.id || "", + external_id: report.id, }, }), onSuccess: () => { @@ -118,56 +143,39 @@ export function DiagnosticReportReview({ queryKey: ["files"], }); }, - onError: (err: any) => { + onError: (err: Error) => { toast.error( - `Failed to approve diagnostic report: ${err.message || "Unknown error"}`, + t("failed_to_approve_diagnostic_report", { error: err.message }), ); }, }); + // Prefer the full detail (with observations); fall back to the list report + // while the detail request is still loading. + const reportDetail = fullReport ?? report; + const handleApprove = () => { - if (latestReport) { - updateDiagnosticReport({ - ...latestReport, - status: DiagnosticReportStatus.final, - conclusion, - }); - } + updateDiagnosticReport({ + id: reportDetail.id, + status: DiagnosticReportStatus.final, + category: reportDetail.category, + code: reportDetail.code, + note: reportDetail.note, + conclusion: conclusion || reportDetail.conclusion, + }); }; - if (!latestReport) { - return null; - } - - // Show loading state while fetching the report - if (isLoadingReport) { - return ( - - -
- - - -
-
-
- ); - } - - // Don't show the report review if there are no observations and no files and no conclusion - if ( - (!fullReport?.observations || fullReport.observations.length === 0) && + const isReportNotReviewable = + (!reportDetail.observations || reportDetail.observations.length === 0) && (!files?.results || files.results.length === 0) && - !fullReport?.conclusion - ) { - return null; - } + !reportDetail.conclusion; return ( @@ -175,35 +183,41 @@ export function DiagnosticReportReview({
- -

- {" "} - - {t("result_review")} + +

+ {" "} + + {report.code?.display ?? report.service_request?.title}

- {fullReport?.created_by && ( + {isReportNotReviewable && ( + + {t("no_observations_entered")} + + )} + {report.created_by && (
- {formatName(fullReport.created_by)} + {formatName(report.created_by)}
)} - {fullReport && ( - - {t(fullReport.status)} - - )} + + {t(report.status)} +