Files
HealthLog/src/lib/query-keys/documents.ts
T
Marc-André Bombeck d109d67889 feat(documents): pick documents from Paperless-ngx or Papra in the vault and wherever documents are linked
A settings card under Integrations connects either system and tests it
before saving. An import button beside Upload in Documents, on a
condition's documents card, and beside Add in the visit and vaccination
document pickers opens a sheet that searches by name with tag and date
filters, shows what HealthLog already holds, and imports the ticked
documents one at a time with a result per row. The buttons appear only
for a person in their own record with a connection; strings in all
seven languages.
2026-09-26 13:26:18 +02:00

108 lines
4.9 KiB
TypeScript

/**
* Query keys — the Dokumente vault: the filtered timeline list (keyset-
* paginated), a single document's detail, and the usage/limits read the
* upload path pre-flights against. Part of the centralized factory;
* aggregated in `./index.ts`. Any document write (upload / edit / link /
* delete / restore / bulk) invalidates the `["documents"]` prefix so the
* timeline, an open detail sheet, and the quota bar repaint in lockstep.
*/
import type { InboundDocumentKindValue } from "@/lib/validations/inbound-documents";
/**
* The parameter set that distinguishes one vault timeline view from another —
* mirrors the URL facets (`?q&kind&episode&year`) one-to-one so a deep link
* and an in-page filter change resolve to the same cache bucket. `undefined`
* fields are dropped by TanStack's stable hash, so an all-defaults view and
* an explicit `{ q: undefined, … }` share a key. Sort is pinned server-side
* (filing date, newest first) and is deliberately NOT part of the key.
*/
export interface DocumentVaultFilters {
q?: string;
/** Type facet — OR inside the facet, sorted before keying by the caller. */
kinds?: InboundDocumentKindValue[];
/** Only documents linked to this illness/condition episode. */
episodeId?: string;
/** Only documents linked to this visit. Arrives by deep link from it. */
encounterId?: string;
/** Only documents whose filing date falls in this calendar year. */
year?: number;
}
export const documentKeys = {
documents: () => ["documents"] as const,
inboundDocuments: () => ["documents", "inbound"] as const,
/**
* The parameterised vault list (keyset-paginated via `useInfiniteQuery`).
* The literal `"list"` segment keeps it distinct from a single-document key
* (`inboundDocument(id)`); cuid ids never collide with it. Stays under the
* `["documents", "inbound"]` prefix so prefix invalidation still reaches it.
*/
inboundDocumentList: (filters: DocumentVaultFilters) =>
["documents", "inbound", "list", filters] as const,
inboundDocument: (id: string) => ["documents", "inbound", id] as const,
/**
* The illness-episode detail's compact document preview (single page,
* small limit). Deliberately NOT `inboundDocumentList({ episodeId })` —
* that key belongs to the vault's `useInfiniteQuery` and a plain query
* under the same key would poison the cache with a different shape.
*/
inboundDocumentEpisodePreview: (episodeId: string) =>
["documents", "inbound", "episode-preview", episodeId] as const,
/**
* The link-existing-documents picker (searchable single page). Own key
* for the same shape-collision reason as the episode preview.
*/
inboundDocumentPicker: (q: string) =>
["documents", "inbound", "picker", q] as const,
/**
* The whole vault as a link-picker option list (every page, walked at the
* list route's ceiling), shared by the visit and vaccination forms. Own
* segment because the shape is a flat document array, not a page.
*/
inboundDocumentVaultPicker: () =>
["documents", "inbound", "vault-picker"] as const,
/**
* Storage usage + effective limits (`GET /api/documents/inbound/usage`).
* Invalidated through the `["documents"]` prefix after every upload /
* delete so the quota bar tracks reality.
*/
inboundDocumentUsage: () => ["documents", "inbound", "usage"] as const,
/**
* Document-scoped AI capability probe
* (`GET /api/documents/inbound/capability`). Distinct from the labs
* `ocrCapability` key because it resolves over the DOCUMENT provider order
* (local-first) and carries the per-egress class the vault notice reads.
*/
inboundDocumentAiCapability: () =>
["documents", "inbound", "ai-capability"] as const,
/**
* The per-user "read documents automatically with AI" opt-in
* (`GET/PATCH /api/auth/me/documents-auto-ai-read`). Drives the AI-settings
* toggle; a flip invalidates the document AI capability probe.
*/
documentsAutoAiRead: () => ["documents", "auto-ai-read"] as const,
/**
* The document picker (#1038): its own top-level prefix, deliberately NOT
* under `["documents"]`. Every vault write invalidates that prefix, and a
* search or tag read here is a request to the person's Paperless-ngx or
* Papra instance; a filing action in the vault must not re-send them.
*/
documentSources: () => ["documentSources"] as const,
/** Availability + the person's connections (`GET /api/documents/sources`). */
documentSourcesStatus: () => ["documentSources", "status"] as const,
/** One source's tags for the filter. */
documentSourceTags: (system: string) =>
["documentSources", "tags", system] as const,
/** One page of one source's search. */
documentSourceSearch: (system: string, search: DocumentSourceSearchKey) =>
["documentSources", "search", system, search] as const,
};
/** The facets that make one picker search distinct from another. */
export interface DocumentSourceSearchKey {
q: string;
tag?: string;
from?: string;
to?: string;
}