import { useCallback } from 'react'; import { environment } from '../environments/environment.ts'; import { useHttpService } from './HttpService.tsx'; import type { MyHistoryEvent } from '../utils/history/collapseHistory'; // - a treating practitioner's view of who accessed and who changed ONE patient's record. // // The scoped counterpart to the org-wide admin audit log and to the patient's own // My History feed. The server gates it on requirePatientAccess, so this only ever returns // rows for a patient the caller has a real care relationship with. // // The route lives on the FOUNDATION gateway, beside GET /R4/AuditEvent - NOT on fhirBaseApi and NOT // on the guardianship gateway where my-history lives. Pointing at the wrong base silently 404s rather // than failing loudly, which has bitten this codebase repeatedly (reference-api-base-url-routing). const API_URL = environment.fhirFoundationApi; /** * Extends the My History row shape rather than redefining it: the server projection is deliberately the * same, so the pure `collapseHistory` module works on both feeds unchanged. */ export interface PatientAccessLogEvent extends MyHistoryEvent { // True when the row was recorded by an org other than the caller's. Only ever set on a CHANGE row - // the server trims cross-org ACCESS rows out entirely, because who at another practice opened the // chart is that practice's internal fact. See utils/patientAccessLog.ts for the full reasoning. fromAnotherOrganization?: boolean; // Why a finished visit was reopened, as typed. The server sends it only on a "Visit reopened" row. reopenReason?: string; } export interface PatientAccessLogResponse { events: PatientAccessLogEvent[]; /** * HOW MANY THE SEARCH RETURNED BEFORE VISIBILITY FILTERING, which is not how many are shown. * * The rows a practitioner sees are what survives an org-scoping filter applied AFTER the page size, * so a full page can arrive and almost none of it be visible. Without this the reader cannot tell * four of four from four of a hundred, and the screen reads as though nothing else happened. */ fetched?: number; /** The search came back full, which is the only evidence available that more exist behind it. */ capped?: boolean; /** How far back the search reached, so the screen can say what it is a page OF. */ windowDays?: number; /** Where the window starts and the oldest time this page reached. */ windowStart?: string; readBackTo?: string; /** Where an older page resumes; absent once the window start is reached or the log cannot go further. */ older?: string; /** The search came back short: nothing older exists in the window. */ reachedStart?: boolean; /** Older events may exist but cannot be stepped to from here. */ stalled?: boolean; } export const usePatientAccessLogService = () => { const { get } = useHttpService(); const fetchPatientAccessLog = useCallback( (patientId: string, request: { signal?: AbortSignal; admit?: () => void } = {}, older?: string): Promise => // `patient` specifically, and never `subject`/`for`: the authorizer's // extractPatientIdFromQueryParams resolves those three in that order, and the handler accepts // only the first so the id it reads is provably the id that was authorized. get( `${API_URL}/patient-access-log?patient=${encodeURIComponent(patientId)}${older ? `&older=${encodeURIComponent(older)}` : ''}`, request, ), [get], ); return { fetchPatientAccessLog }; };