@eka-care/medical-records-ui 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +151 -0
- package/dist/DocumentViewer-WXL7OZXK.mjs +197 -0
- package/dist/DocumentViewer-WXL7OZXK.mjs.map +1 -0
- package/dist/DocumentViewer-Z3GOU5NU.js +197 -0
- package/dist/DocumentViewer-Z3GOU5NU.js.map +1 -0
- package/dist/PdfViewer-EAN6EQAB.js +141 -0
- package/dist/PdfViewer-EAN6EQAB.js.map +1 -0
- package/dist/PdfViewer-WIV5AUJB.mjs +141 -0
- package/dist/PdfViewer-WIV5AUJB.mjs.map +1 -0
- package/dist/index.css +3869 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.mts +390 -0
- package/dist/index.d.ts +390 -0
- package/dist/index.js +4796 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +4796 -0
- package/dist/index.mjs.map +1 -0
- package/dist/styles.css +3869 -0
- package/dist/styles.css.map +1 -0
- package/dist/styles.d.mts +2 -0
- package/dist/styles.d.ts +2 -0
- package/package.json +57 -0
- package/src/index.ts +40 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, MouseEvent } from 'react';
|
|
3
|
+
import { SDKEnvironment, EditRecordRequest, CreateCaseRequest, UpdateCaseRequest } from '@eka-care/medical-records-ts-sdk';
|
|
4
|
+
import * as zustand from 'zustand';
|
|
5
|
+
|
|
6
|
+
type Environment = SDKEnvironment;
|
|
7
|
+
interface DocumentTypeConfig {
|
|
8
|
+
id: string;
|
|
9
|
+
display_name: string;
|
|
10
|
+
hex?: string;
|
|
11
|
+
bg_hex?: string;
|
|
12
|
+
archive?: boolean;
|
|
13
|
+
}
|
|
14
|
+
interface SdkConfig {
|
|
15
|
+
environment?: Environment;
|
|
16
|
+
baseUrl?: string;
|
|
17
|
+
/** Extra headers (client-id, flavour, b-tid, …). */
|
|
18
|
+
defaultHeaders?: Record<string, string>;
|
|
19
|
+
onError?: (error: unknown) => void;
|
|
20
|
+
onUnauthorized?: () => Promise<string | undefined> | string | undefined;
|
|
21
|
+
accessToken?: string;
|
|
22
|
+
transport?: 'native' | 'bridge';
|
|
23
|
+
oid?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface SdkProviderProps {
|
|
27
|
+
config: SdkConfig;
|
|
28
|
+
bid?: string;
|
|
29
|
+
patientId?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Document-type definitions from the host's onboarding config
|
|
32
|
+
* (`mr_document_type`). Drives the code → display-name labels shown across
|
|
33
|
+
* the feature; the SDK's static map is the fallback when a code is absent.
|
|
34
|
+
*/
|
|
35
|
+
documentTypes?: DocumentTypeConfig[];
|
|
36
|
+
children: ReactNode;
|
|
37
|
+
}
|
|
38
|
+
declare function SdkProvider({ config, bid, patientId, documentTypes, children }: SdkProviderProps): react.JSX.Element;
|
|
39
|
+
|
|
40
|
+
type DocumentTypeLabelResolver = (code: string) => string;
|
|
41
|
+
|
|
42
|
+
interface RecordItemProps {
|
|
43
|
+
record: RecordItem;
|
|
44
|
+
selected: boolean;
|
|
45
|
+
selectionMode?: boolean;
|
|
46
|
+
thumbnailUrl?: string | null;
|
|
47
|
+
inContext?: boolean;
|
|
48
|
+
attachDisabled?: boolean;
|
|
49
|
+
selectDisabled?: boolean;
|
|
50
|
+
isUploading?: boolean;
|
|
51
|
+
isUploadFailed?: boolean;
|
|
52
|
+
isAnalysing?: boolean;
|
|
53
|
+
isSmart?: boolean;
|
|
54
|
+
dateDisplay: string;
|
|
55
|
+
onSelect: (id: string) => void;
|
|
56
|
+
onOpen: (id: string) => void;
|
|
57
|
+
onDelete: (id: string) => void;
|
|
58
|
+
onAddToContext: (id: string) => void;
|
|
59
|
+
onCopyNotes: (id: string, anchor?: {
|
|
60
|
+
x: number;
|
|
61
|
+
y: number;
|
|
62
|
+
}) => void;
|
|
63
|
+
}
|
|
64
|
+
interface RecordItem {
|
|
65
|
+
id: string;
|
|
66
|
+
patientId: string;
|
|
67
|
+
title: string;
|
|
68
|
+
typeCode: string;
|
|
69
|
+
type: string;
|
|
70
|
+
thumbnailUrl: string | null;
|
|
71
|
+
createdAtEpoch: number;
|
|
72
|
+
updatedAtEpoch: number;
|
|
73
|
+
tags: string[];
|
|
74
|
+
cases: string[];
|
|
75
|
+
syncState: 'uploading' | 'upload_success' | 'upload_failure' | null;
|
|
76
|
+
fileType: string | null;
|
|
77
|
+
isSmart: boolean;
|
|
78
|
+
isAnalysing: boolean;
|
|
79
|
+
}
|
|
80
|
+
interface UploadQueueItem {
|
|
81
|
+
id: string;
|
|
82
|
+
fileName: string;
|
|
83
|
+
mimeType: string;
|
|
84
|
+
sizeBytes: number;
|
|
85
|
+
progress: number;
|
|
86
|
+
status: 'pending' | 'uploading' | 'completed' | 'upload_failure';
|
|
87
|
+
documentId?: string;
|
|
88
|
+
}
|
|
89
|
+
interface CaseUiItem {
|
|
90
|
+
id: string;
|
|
91
|
+
name: string;
|
|
92
|
+
typeCode: string;
|
|
93
|
+
createdAtEpoch: number;
|
|
94
|
+
updatedAtEpoch: number;
|
|
95
|
+
occurredAtEpoch: number | null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
type RecordsStatus = 'idle' | 'loading' | 'stale' | 'fresh' | 'error';
|
|
99
|
+
interface RecordsSlice {
|
|
100
|
+
byId: Record<string, RecordItem>;
|
|
101
|
+
ids: string[];
|
|
102
|
+
status: RecordsStatus;
|
|
103
|
+
error: string | null;
|
|
104
|
+
pendingIds: Set<string>;
|
|
105
|
+
setAll: (records: RecordItem[]) => void;
|
|
106
|
+
upsert: (record: RecordItem) => void;
|
|
107
|
+
remove: (id: string) => void;
|
|
108
|
+
markPending: (id: string) => void;
|
|
109
|
+
unmarkPending: (id: string) => void;
|
|
110
|
+
setStatus: (status: RecordsStatus) => void;
|
|
111
|
+
setError: (error: string | null) => void;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface CasesSlice {
|
|
115
|
+
byId: Record<string, CaseUiItem>;
|
|
116
|
+
ids: string[];
|
|
117
|
+
status: RecordsStatus;
|
|
118
|
+
error: string | null;
|
|
119
|
+
setAll: (cases: CaseUiItem[]) => void;
|
|
120
|
+
upsert: (caseItem: CaseUiItem) => void;
|
|
121
|
+
remove: (id: string) => void;
|
|
122
|
+
setStatus: (status: RecordsStatus) => void;
|
|
123
|
+
setError: (error: string | null) => void;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
interface UploadSlice {
|
|
127
|
+
queue: UploadQueueItem[];
|
|
128
|
+
add: (item: UploadQueueItem) => void;
|
|
129
|
+
updateProgress: (id: string, progress: number) => void;
|
|
130
|
+
markCompleted: (id: string, documentId: string) => void;
|
|
131
|
+
markFailed: (id: string) => void;
|
|
132
|
+
remove: (id: string) => void;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
interface SelectionSlice {
|
|
136
|
+
recordIds: Set<string>;
|
|
137
|
+
openDetailId: string | null;
|
|
138
|
+
toggleRecord: (id: string, max?: number) => void;
|
|
139
|
+
clearSelection: () => void;
|
|
140
|
+
openDetail: (id: string | null) => void;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
type DatePreset = 'anytime' | 'last7' | 'last30' | 'last3m' | 'custom';
|
|
144
|
+
interface PopoverFilters {
|
|
145
|
+
documentDateFrom: number | null;
|
|
146
|
+
documentDateTo: number | null;
|
|
147
|
+
uploadDateFrom: number | null;
|
|
148
|
+
uploadDateTo: number | null;
|
|
149
|
+
documentDatePreset: DatePreset;
|
|
150
|
+
uploadDatePreset: DatePreset;
|
|
151
|
+
tags: string[];
|
|
152
|
+
}
|
|
153
|
+
interface FiltersSlice {
|
|
154
|
+
type: string | null;
|
|
155
|
+
search: string;
|
|
156
|
+
documentDateFrom: number | null;
|
|
157
|
+
documentDateTo: number | null;
|
|
158
|
+
uploadDateFrom: number | null;
|
|
159
|
+
uploadDateTo: number | null;
|
|
160
|
+
documentDatePreset: DatePreset;
|
|
161
|
+
uploadDatePreset: DatePreset;
|
|
162
|
+
tags: string[];
|
|
163
|
+
sortBy: 'date' | 'name';
|
|
164
|
+
sortDir: 'asc' | 'desc';
|
|
165
|
+
setType: (type: string | null) => void;
|
|
166
|
+
setSearch: (q: string) => void;
|
|
167
|
+
applyPopoverFilters: (filters: PopoverFilters) => void;
|
|
168
|
+
setSortBy: (by: FiltersSlice['sortBy']) => void;
|
|
169
|
+
setSortDir: (dir: FiltersSlice['sortDir']) => void;
|
|
170
|
+
clearPopoverFilters: () => void;
|
|
171
|
+
reset: () => void;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
type SyncStatus = 'online' | 'offline' | 'syncing';
|
|
175
|
+
interface SyncSlice {
|
|
176
|
+
status: SyncStatus;
|
|
177
|
+
pendingSyncCount: number;
|
|
178
|
+
setStatus: (status: SyncStatus) => void;
|
|
179
|
+
incrementPending: () => void;
|
|
180
|
+
decrementPending: () => void;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
type DetailStatus = 'idle' | 'loading' | 'loaded' | 'error';
|
|
184
|
+
interface RecordDetail {
|
|
185
|
+
status: DetailStatus;
|
|
186
|
+
isSmart: boolean;
|
|
187
|
+
/** Verified smart-report fields as `name: value` lines, ready to copy/paste. */
|
|
188
|
+
smartText: string;
|
|
189
|
+
}
|
|
190
|
+
interface DetailsSlice {
|
|
191
|
+
byId: Record<string, RecordDetail>;
|
|
192
|
+
setDetailLoading: (id: string) => void;
|
|
193
|
+
setDetail: (id: string, detail: {
|
|
194
|
+
isSmart: boolean;
|
|
195
|
+
smartText: string;
|
|
196
|
+
}) => void;
|
|
197
|
+
setDetailError: (id: string) => void;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Composite state for a single patient's store. */
|
|
201
|
+
interface PatientState {
|
|
202
|
+
bid: string;
|
|
203
|
+
patientId: string;
|
|
204
|
+
records: RecordsSlice;
|
|
205
|
+
cases: CasesSlice;
|
|
206
|
+
details: DetailsSlice;
|
|
207
|
+
uploadQueue: UploadSlice;
|
|
208
|
+
selection: SelectionSlice;
|
|
209
|
+
filters: FiltersSlice;
|
|
210
|
+
sync: SyncSlice;
|
|
211
|
+
/** Disposal hook fired when SdkProvider unmounts or the patient changes. */
|
|
212
|
+
__cleanup: () => void;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Builds one Zustand store per patient. `subscribeWithSelector` is required so
|
|
217
|
+
* per-slice selectors re-render only on the state they read.
|
|
218
|
+
*/
|
|
219
|
+
declare function createPatientStore(bid: string, patientId: string): Omit<zustand.StoreApi<PatientState>, "subscribe"> & {
|
|
220
|
+
subscribe: {
|
|
221
|
+
(listener: (selectedState: PatientState, previousSelectedState: PatientState) => void): () => void;
|
|
222
|
+
<U>(selector: (state: PatientState) => U, listener: (selectedState: U, previousSelectedState: U) => void, options?: {
|
|
223
|
+
equalityFn?: ((a: U, b: U) => boolean) | undefined;
|
|
224
|
+
fireImmediately?: boolean;
|
|
225
|
+
} | undefined): () => void;
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
type PatientStore = ReturnType<typeof createPatientStore>;
|
|
229
|
+
|
|
230
|
+
interface SdkContextValue {
|
|
231
|
+
bid: string;
|
|
232
|
+
patientId: string;
|
|
233
|
+
store: PatientStore;
|
|
234
|
+
ready: boolean;
|
|
235
|
+
labelOf: DocumentTypeLabelResolver;
|
|
236
|
+
documentTypes: DocumentTypeConfig[];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
declare function useSdk(): SdkContextValue;
|
|
240
|
+
|
|
241
|
+
/** True now that the Core SDK package is installed and wired in. */
|
|
242
|
+
declare const CORE_SDK_AVAILABLE = true;
|
|
243
|
+
/** Clear cached DB data for the bid then drop the client reference. */
|
|
244
|
+
declare function logoutMedicalRecords(bid?: string): Promise<void>;
|
|
245
|
+
|
|
246
|
+
interface RecordsViewProps {
|
|
247
|
+
className?: string;
|
|
248
|
+
onUpload?: () => void;
|
|
249
|
+
allowUpload?: boolean;
|
|
250
|
+
onCopyToNote?: (text: string, anchor?: {
|
|
251
|
+
x: number;
|
|
252
|
+
y: number;
|
|
253
|
+
}) => void | Promise<void>;
|
|
254
|
+
attachedIds?: string[];
|
|
255
|
+
onAttachManyToContext?: (records: Array<{
|
|
256
|
+
documentId: string;
|
|
257
|
+
name: string;
|
|
258
|
+
}>) => void | Promise<void>;
|
|
259
|
+
onRemoveAttachment?: (documentId: string) => void | Promise<void>;
|
|
260
|
+
onToast?: (message: string, type?: 'error' | 'success') => void;
|
|
261
|
+
}
|
|
262
|
+
declare function RecordsView({ className, onUpload, allowUpload, onCopyToNote, attachedIds, onAttachManyToContext, onRemoveAttachment, onToast, }: RecordsViewProps): react.JSX.Element;
|
|
263
|
+
|
|
264
|
+
interface RecordsConnectionActions {
|
|
265
|
+
refresh: () => Promise<void>;
|
|
266
|
+
/** Unix epoch ms of the last successful server refresh, or null before first call. */
|
|
267
|
+
sourceRefreshedAt: number | null;
|
|
268
|
+
editRecord: (documentId: string, data: EditRecordRequest) => Promise<void>;
|
|
269
|
+
deleteRecord: (documentId: string) => Promise<void>;
|
|
270
|
+
}
|
|
271
|
+
declare function useRecordsConnection(): RecordsConnectionActions;
|
|
272
|
+
|
|
273
|
+
interface CasesConnectionActions {
|
|
274
|
+
refresh: () => Promise<void>;
|
|
275
|
+
createCase: (data: Omit<CreateCaseRequest, 'id'> & {
|
|
276
|
+
id?: string;
|
|
277
|
+
}) => Promise<string>;
|
|
278
|
+
updateCase: (caseId: string, data: UpdateCaseRequest) => Promise<void>;
|
|
279
|
+
deleteCase: (caseId: string) => Promise<void>;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Loads cases for the current patient — but only when `enabled` (the Care
|
|
283
|
+
* Context tab is active), so the Date tab never triggers a cases fetch. Loads
|
|
284
|
+
* once per patient (cache-then-network via onStale); a `cases:changed`
|
|
285
|
+
* subscription keeps the store live while enabled.
|
|
286
|
+
*/
|
|
287
|
+
declare function useCasesConnection(enabled?: boolean): CasesConnectionActions;
|
|
288
|
+
|
|
289
|
+
interface UploadFileInput {
|
|
290
|
+
file: File;
|
|
291
|
+
documentType: string;
|
|
292
|
+
documentDateMs: number;
|
|
293
|
+
caseIds?: string[];
|
|
294
|
+
tags?: string[];
|
|
295
|
+
}
|
|
296
|
+
interface UploadConnectionActions {
|
|
297
|
+
upload: (items: UploadFileInput[]) => Promise<void>;
|
|
298
|
+
}
|
|
299
|
+
declare function useUploadConnection(): UploadConnectionActions;
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Tear down UI SDK state: drop the Core SDK client reference. The active patient
|
|
303
|
+
* store is owned by SdkProvider and disposed on unmount, so there's nothing else
|
|
304
|
+
* to clear here. (The Core SDK holds no persistent worker to shut down — auth
|
|
305
|
+
* lives in the host app.)
|
|
306
|
+
*/
|
|
307
|
+
declare function useLogout(): () => void;
|
|
308
|
+
|
|
309
|
+
declare function useRecordById(id: string): RecordItem | undefined;
|
|
310
|
+
declare function useRecordIdRange(start: number, end: number): string[];
|
|
311
|
+
declare function useRecordsCount(): number;
|
|
312
|
+
declare function useRecordsStatus(): RecordsStatus;
|
|
313
|
+
declare function useThumbnailUrl(id: string): string | null;
|
|
314
|
+
declare function useIsSelected(id: string): boolean;
|
|
315
|
+
declare function useCaseById(id: string): CaseUiItem | undefined;
|
|
316
|
+
declare function useCaseIds(): string[];
|
|
317
|
+
declare function useCasesStatus(): RecordsStatus;
|
|
318
|
+
declare function useFilteredRecordIds(): string[];
|
|
319
|
+
|
|
320
|
+
interface SkeletonProps {
|
|
321
|
+
variant: 'card' | 'list-item' | 'text' | 'circle';
|
|
322
|
+
width?: number | string;
|
|
323
|
+
height?: number | string;
|
|
324
|
+
className?: string;
|
|
325
|
+
}
|
|
326
|
+
declare function Skeleton({ variant, width, height, className }: SkeletonProps): react.JSX.Element;
|
|
327
|
+
|
|
328
|
+
interface BadgeProps {
|
|
329
|
+
variant: 'smart' | 'analysing' | 'uploading' | 'upload_failure';
|
|
330
|
+
label: string;
|
|
331
|
+
}
|
|
332
|
+
declare const Badge: react.NamedExoticComponent<BadgeProps>;
|
|
333
|
+
|
|
334
|
+
interface TagChipProps {
|
|
335
|
+
label: string;
|
|
336
|
+
removable?: boolean;
|
|
337
|
+
onRemove?: () => void;
|
|
338
|
+
onClick?: (e: MouseEvent<HTMLSpanElement>) => void;
|
|
339
|
+
}
|
|
340
|
+
declare const TagChip: react.NamedExoticComponent<TagChipProps>;
|
|
341
|
+
|
|
342
|
+
interface IconButtonProps {
|
|
343
|
+
icon: ReactNode;
|
|
344
|
+
'aria-label': string;
|
|
345
|
+
title?: string;
|
|
346
|
+
variant?: 'default' | 'primary' | 'solid' | 'ghost' | 'destructive';
|
|
347
|
+
disabled?: boolean;
|
|
348
|
+
onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
|
349
|
+
}
|
|
350
|
+
declare const IconButton: react.NamedExoticComponent<IconButtonProps>;
|
|
351
|
+
|
|
352
|
+
interface ThumbnailProps {
|
|
353
|
+
url: string | null;
|
|
354
|
+
size?: number;
|
|
355
|
+
alt?: string;
|
|
356
|
+
}
|
|
357
|
+
declare const Thumbnail: react.NamedExoticComponent<ThumbnailProps>;
|
|
358
|
+
|
|
359
|
+
interface FilterChipProps {
|
|
360
|
+
id: string;
|
|
361
|
+
label: string;
|
|
362
|
+
count?: number;
|
|
363
|
+
active: boolean;
|
|
364
|
+
onSelect: (id: string) => void;
|
|
365
|
+
}
|
|
366
|
+
declare const FilterChip: react.NamedExoticComponent<FilterChipProps>;
|
|
367
|
+
|
|
368
|
+
interface SidebarItemProps {
|
|
369
|
+
id: string;
|
|
370
|
+
dayNumber: string;
|
|
371
|
+
weekday: string;
|
|
372
|
+
countLabel: string;
|
|
373
|
+
summary: string;
|
|
374
|
+
selected: boolean;
|
|
375
|
+
onSelect: (id: string) => void;
|
|
376
|
+
}
|
|
377
|
+
declare const SidebarItem: react.NamedExoticComponent<SidebarItemProps>;
|
|
378
|
+
|
|
379
|
+
interface RecordCardProps extends RecordItemProps {
|
|
380
|
+
dense?: boolean;
|
|
381
|
+
}
|
|
382
|
+
declare const RecordCard: react.NamedExoticComponent<RecordCardProps>;
|
|
383
|
+
|
|
384
|
+
declare const RecordRow: react.NamedExoticComponent<RecordItemProps>;
|
|
385
|
+
|
|
386
|
+
declare function EmptyRecordsIcon({ size }: {
|
|
387
|
+
size?: number;
|
|
388
|
+
}): react.JSX.Element;
|
|
389
|
+
|
|
390
|
+
export { Badge, CORE_SDK_AVAILABLE, type CaseUiItem, type DocumentTypeConfig, EmptyRecordsIcon, type Environment, FilterChip, IconButton, RecordCard, type RecordItem, RecordRow, RecordsView, type RecordsViewProps, type SdkConfig, SdkProvider, type SdkProviderProps, SidebarItem, Skeleton, TagChip, Thumbnail, logoutMedicalRecords, useCaseById, useCaseIds, useCasesConnection, useCasesStatus, useFilteredRecordIds, useIsSelected, useLogout, useRecordById, useRecordIdRange, useRecordsConnection, useRecordsCount, useRecordsStatus, useSdk, useThumbnailUrl, useUploadConnection };
|