@mindbill/react 0.12.0 → 0.13.1
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 +3 -1
- package/dist/index.d.ts +23 -89
- package/dist/index.js +49 -309
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@ npm install @mindbill/react @mindbill/node
|
|
|
10
10
|
|
|
11
11
|
`ConnectedBillLifecycle` is the default integration. It owns bill loading, payer lookup, editable review, attachments, submission, status refresh, EORs, payment posting, Second Bill Review, correction/resubmission, and close.
|
|
12
12
|
|
|
13
|
+
Procedure entry is keyboard-first: the form always keeps one empty row after the entered lines, grows as soon as that row is partially filled, and omits the empty row from save and submit payloads.
|
|
14
|
+
|
|
13
15
|
```tsx
|
|
14
16
|
import { ConnectedBillLifecycle } from "@mindbill/react";
|
|
15
17
|
|
|
@@ -37,7 +39,7 @@ Choose a complete preset, then override only the tokens your design system owns.
|
|
|
37
39
|
/>
|
|
38
40
|
```
|
|
39
41
|
|
|
40
|
-
Available presets are `mindbill`, `qme-companion`, and `
|
|
42
|
+
Available presets are `mindbill`, `qme-companion`, `orange-bright`, and `clinical-blue`. Preset names describe visual styles rather than customer or partner brands. Supported overrides include accent, accent text, background, surface, input background, text, muted text, border, font, panel radius, control radius, shadow, danger, success, and warning colors.
|
|
41
43
|
|
|
42
44
|
Add one authenticated route to your app. It verifies that the signed-in user may access the bill, then mints an exact-origin, bill-scoped token. The Partner API key stays on the server.
|
|
43
45
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { MindBillAppearance, MindBillEventDetail, MindBillErrorDetail } from '@mindbill/embed';
|
|
2
2
|
export { MindBillAppearance, MindBillErrorDetail, MindBillEventDetail } from '@mindbill/embed';
|
|
3
3
|
import { CSSProperties, ReactElement, ReactNode } from 'react';
|
|
4
|
+
import { BillLifecycleClientOptions, BillLifecycleData, BillLifecycleClient, BillEorDocument } from '@mindbill/browser';
|
|
5
|
+
export { BillEorDocument, BillLifecycleAction, BillLifecycleActionId, BillLifecycleClient, BillLifecycleClientOptions, BillLifecycleData, BillLifecycleSession, BillLifecycleSessionProvider, BillLifecycleSessionRequest, CloseBillInput, PostBillPaymentInput, SubmitSecondReviewInput, createBillLifecycleClient } from '@mindbill/browser';
|
|
4
6
|
|
|
5
|
-
type MindBillThemePreset = "mindbill" | "qme-companion" | "orange-bright";
|
|
7
|
+
type MindBillThemePreset = "mindbill" | "qme-companion" | "orange-bright" | "clinical-blue";
|
|
6
8
|
type MindBillReactAppearance = MindBillAppearance & {
|
|
7
9
|
/** A complete starting point. Individual tokens below always win. */
|
|
8
10
|
preset?: MindBillThemePreset;
|
|
@@ -65,6 +67,23 @@ declare const mindBillThemePresets: {
|
|
|
65
67
|
readonly warningColor: "#9a5b13";
|
|
66
68
|
readonly fontFamily: "Inter, ui-sans-serif, system-ui, sans-serif";
|
|
67
69
|
};
|
|
70
|
+
readonly "clinical-blue": {
|
|
71
|
+
readonly accentColor: "#1677ff";
|
|
72
|
+
readonly accentTextColor: "#ffffff";
|
|
73
|
+
readonly backgroundColor: "#f5f7fa";
|
|
74
|
+
readonly surfaceColor: "#ffffff";
|
|
75
|
+
readonly inputBackgroundColor: "#ffffff";
|
|
76
|
+
readonly textColor: "#1f2d3d";
|
|
77
|
+
readonly mutedColor: "#66788a";
|
|
78
|
+
readonly borderColor: "#d9e2ec";
|
|
79
|
+
readonly borderRadius: "8px";
|
|
80
|
+
readonly controlRadius: "6px";
|
|
81
|
+
readonly shadow: "0 2px 8px rgba(31,45,61,.06)";
|
|
82
|
+
readonly dangerColor: "#d4380d";
|
|
83
|
+
readonly successColor: "#389e0d";
|
|
84
|
+
readonly warningColor: "#d48806";
|
|
85
|
+
readonly fontFamily: "Inter, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif";
|
|
86
|
+
};
|
|
68
87
|
};
|
|
69
88
|
declare function resolveMindBillAppearance(appearance: MindBillReactAppearance | undefined): MindBillReactAppearance;
|
|
70
89
|
declare function mindBillAppearanceStyle(appearance: MindBillReactAppearance | undefined, style?: CSSProperties): CSSProperties;
|
|
@@ -115,6 +134,8 @@ type BillReviewLineItem = {
|
|
|
115
134
|
charge: number;
|
|
116
135
|
feeSchedule?: number;
|
|
117
136
|
};
|
|
137
|
+
/** Keeps completed/partial lines plus one keyboard-ready empty row. */
|
|
138
|
+
declare function ensureTrailingProcedureLine(lines: readonly BillReviewLineItem[]): BillReviewLineItem[];
|
|
118
139
|
type BillReviewAttachment = {
|
|
119
140
|
id: string;
|
|
120
141
|
filename: string;
|
|
@@ -341,87 +362,6 @@ type ConnectedBillStatusProps = UseBillStatusOptions & {
|
|
|
341
362
|
};
|
|
342
363
|
declare function ConnectedBillStatus({ actions, appearance, className, style, loadingFallback, errorFallback, ...options }: ConnectedBillStatusProps): ReactElement | null;
|
|
343
364
|
|
|
344
|
-
type BillLifecycleActionId = "edit_and_submit" | "correct_and_resubmit" | "second_review" | "independent_bill_review" | "view_eor" | "post_payment" | "close";
|
|
345
|
-
type BillLifecycleAction = {
|
|
346
|
-
id: BillLifecycleActionId;
|
|
347
|
-
label: string;
|
|
348
|
-
enabled: boolean;
|
|
349
|
-
primary?: boolean;
|
|
350
|
-
reason?: string;
|
|
351
|
-
};
|
|
352
|
-
type BillEorDocument = {
|
|
353
|
-
id: string;
|
|
354
|
-
filename: string;
|
|
355
|
-
description: string | null;
|
|
356
|
-
addedAt: string;
|
|
357
|
-
contentUrl: string;
|
|
358
|
-
};
|
|
359
|
-
type BillLifecycleData = BillReviewData & {
|
|
360
|
-
lifecycle: {
|
|
361
|
-
state: string;
|
|
362
|
-
nativeStatus: string;
|
|
363
|
-
submittedAt?: string | null;
|
|
364
|
-
agingDays?: number | null;
|
|
365
|
-
updatedAt?: string | null;
|
|
366
|
-
actions: BillLifecycleAction[];
|
|
367
|
-
};
|
|
368
|
-
eors: BillEorDocument[];
|
|
369
|
-
};
|
|
370
|
-
type BillLifecycleSession = {
|
|
371
|
-
token: string;
|
|
372
|
-
expiresAt?: string;
|
|
373
|
-
apiBaseUrl?: string;
|
|
374
|
-
};
|
|
375
|
-
type BillLifecycleSessionRequest = {
|
|
376
|
-
billId: string;
|
|
377
|
-
component: "bill-review";
|
|
378
|
-
signal: AbortSignal;
|
|
379
|
-
};
|
|
380
|
-
type BillLifecycleSessionProvider = (request: BillLifecycleSessionRequest) => Promise<BillLifecycleSession>;
|
|
381
|
-
type CloseBillInput = {
|
|
382
|
-
reason: string;
|
|
383
|
-
};
|
|
384
|
-
type PostBillPaymentInput = {
|
|
385
|
-
amount: number;
|
|
386
|
-
method: "check" | "eft";
|
|
387
|
-
checkNumber?: string;
|
|
388
|
-
depositDate: string;
|
|
389
|
-
note?: string;
|
|
390
|
-
};
|
|
391
|
-
type SubmitSecondReviewInput = {
|
|
392
|
-
reason: string;
|
|
393
|
-
payerClaimControlNumber: string;
|
|
394
|
-
disputedAmount: number | undefined;
|
|
395
|
-
attachmentIds: string[];
|
|
396
|
-
route: BillSubmissionRoute;
|
|
397
|
-
};
|
|
398
|
-
type BillLifecycleClientOptions = {
|
|
399
|
-
billId: string;
|
|
400
|
-
/** Same-origin server route that mints a short-lived, bill-scoped session. */
|
|
401
|
-
sessionEndpoint?: string | undefined;
|
|
402
|
-
/** Advanced escape hatch for a custom session exchange. */
|
|
403
|
-
getSession?: BillLifecycleSessionProvider | undefined;
|
|
404
|
-
apiBaseUrl?: string | undefined;
|
|
405
|
-
fetch?: typeof globalThis.fetch | undefined;
|
|
406
|
-
};
|
|
407
|
-
type BillLifecycleClient = {
|
|
408
|
-
getLifecycle: (signal?: AbortSignal) => Promise<BillLifecycleData>;
|
|
409
|
-
searchClaimsAdministrators: (query: string, claimNumber?: string) => Promise<BillReviewPayer[]>;
|
|
410
|
-
saveReview: (input: BillReviewSaveInput) => Promise<BillLifecycleData>;
|
|
411
|
-
submitBill: (input: BillReviewSaveInput, route: BillSubmissionRoute) => Promise<BillLifecycleData>;
|
|
412
|
-
addAttachment: (file: File, documentType: BillReviewDocumentType, description?: string) => Promise<BillLifecycleData>;
|
|
413
|
-
removeAttachment: (attachmentId: string) => Promise<BillLifecycleData>;
|
|
414
|
-
getAttachment: (attachmentId: string) => Promise<Blob>;
|
|
415
|
-
getEor: (documentId: string) => Promise<Blob>;
|
|
416
|
-
closeBill: (input: CloseBillInput) => Promise<BillLifecycleData>;
|
|
417
|
-
postPayment: (input: PostBillPaymentInput) => Promise<BillLifecycleData>;
|
|
418
|
-
submitSecondReview: (input: SubmitSecondReviewInput) => Promise<BillLifecycleData>;
|
|
419
|
-
startCorrection: () => Promise<{
|
|
420
|
-
replacementBillId: string;
|
|
421
|
-
data: BillLifecycleData;
|
|
422
|
-
}>;
|
|
423
|
-
clearSession: () => void;
|
|
424
|
-
};
|
|
425
365
|
type UseBillLifecycleOptions = BillLifecycleClientOptions & {
|
|
426
366
|
refreshInterval?: number;
|
|
427
367
|
enabled?: boolean;
|
|
@@ -448,12 +388,6 @@ type UseBillLifecycleResult = {
|
|
|
448
388
|
submitSecondReview: BillLifecycleClient["submitSecondReview"];
|
|
449
389
|
startCorrection: () => Promise<BillLifecycleData>;
|
|
450
390
|
};
|
|
451
|
-
/**
|
|
452
|
-
* Browser-safe client for the complete bill lifecycle. The only long-lived
|
|
453
|
-
* secret remains on the partner server; this client uses a short-lived,
|
|
454
|
-
* origin-bound session scoped to one bill.
|
|
455
|
-
*/
|
|
456
|
-
declare function createBillLifecycleClient({ billId, sessionEndpoint, getSession, apiBaseUrl, fetch: fetchOverride, }: BillLifecycleClientOptions): BillLifecycleClient;
|
|
457
391
|
declare function useBillLifecycle({ billId: providedBillId, sessionEndpoint, getSession, apiBaseUrl, refreshInterval, enabled, initialData, onBillIdChange, fetch: fetchOverride, }: UseBillLifecycleOptions): UseBillLifecycleResult;
|
|
458
392
|
type ConnectedBillLifecycleProps = UseBillLifecycleOptions & {
|
|
459
393
|
appearance?: MindBillReactAppearance;
|
|
@@ -478,4 +412,4 @@ type MindBillWidgetProps = {
|
|
|
478
412
|
declare function MindBillBillTimeline(props: MindBillWidgetProps): ReactElement;
|
|
479
413
|
declare function MindBillBillReview(props: MindBillWidgetProps): ReactElement;
|
|
480
414
|
|
|
481
|
-
export { type
|
|
415
|
+
export { type BillReviewAttachment, type BillReviewBillingProvider, type BillReviewClinician, type BillReviewData, type BillReviewDocumentType, type BillReviewDraft, type BillReviewFeatures, BillReviewForm, type BillReviewFormProps, type BillReviewLineItem, type BillReviewLocation, type BillReviewPayer, type BillReviewSaveInput, type BillStatusAction, type BillStatusClient, type BillStatusClientOptions, type BillStatusData, type BillStatusSession, type BillStatusSessionProvider, type BillStatusSessionRequest, BillStatusSummary, type BillStatusSummaryProps, type BillSubmissionRoute, ConnectedBillLifecycle, type ConnectedBillLifecycleProps, ConnectedBillStatus, type ConnectedBillStatusProps, MindBillBillReview, MindBillBillTimeline, type MindBillReactAppearance, type MindBillThemePreset, type MindBillWidgetProps, type UseBillLifecycleOptions, type UseBillLifecycleResult, type UseBillStatusOptions, type UseBillStatusResult, buildBillReviewSaveInput, createBillStatusClient, ensureTrailingProcedureLine, mindBillAppearanceStyle, mindBillThemePresets, resolveMindBillAppearance, useBillLifecycle, useBillStatus };
|
package/dist/index.js
CHANGED
|
@@ -55,6 +55,23 @@ var mindBillThemePresets = {
|
|
|
55
55
|
successColor: "#16794f",
|
|
56
56
|
warningColor: "#9a5b13",
|
|
57
57
|
fontFamily: "Inter, ui-sans-serif, system-ui, sans-serif"
|
|
58
|
+
},
|
|
59
|
+
"clinical-blue": {
|
|
60
|
+
accentColor: "#1677ff",
|
|
61
|
+
accentTextColor: "#ffffff",
|
|
62
|
+
backgroundColor: "#f5f7fa",
|
|
63
|
+
surfaceColor: "#ffffff",
|
|
64
|
+
inputBackgroundColor: "#ffffff",
|
|
65
|
+
textColor: "#1f2d3d",
|
|
66
|
+
mutedColor: "#66788a",
|
|
67
|
+
borderColor: "#d9e2ec",
|
|
68
|
+
borderRadius: "8px",
|
|
69
|
+
controlRadius: "6px",
|
|
70
|
+
shadow: "0 2px 8px rgba(31,45,61,.06)",
|
|
71
|
+
dangerColor: "#d4380d",
|
|
72
|
+
successColor: "#389e0d",
|
|
73
|
+
warningColor: "#d48806",
|
|
74
|
+
fontFamily: "Inter, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif"
|
|
58
75
|
}
|
|
59
76
|
};
|
|
60
77
|
function resolveMindBillAppearance(appearance) {
|
|
@@ -86,6 +103,19 @@ function mindBillAppearanceStyle(appearance, style) {
|
|
|
86
103
|
// src/native-bill-review.tsx
|
|
87
104
|
import { useEffect, useId, useMemo, useRef, useState } from "react";
|
|
88
105
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
106
|
+
var EMPTY_PROCEDURE_LINE = {
|
|
107
|
+
code: "",
|
|
108
|
+
modifiers: [],
|
|
109
|
+
units: 1,
|
|
110
|
+
charge: 0
|
|
111
|
+
};
|
|
112
|
+
function isEmptyProcedureLine(line) {
|
|
113
|
+
return !line.code.trim() && line.modifiers.length === 0 && line.units === 1;
|
|
114
|
+
}
|
|
115
|
+
function ensureTrailingProcedureLine(lines) {
|
|
116
|
+
const entered = lines.filter((line) => !isEmptyProcedureLine(line)).map((line) => ({ ...line, modifiers: [...line.modifiers] }));
|
|
117
|
+
return entered.length < 50 ? [...entered, { ...EMPTY_PROCEDURE_LINE, modifiers: [] }] : entered;
|
|
118
|
+
}
|
|
89
119
|
var EMPTY_BILLING_PROVIDER = {
|
|
90
120
|
name: "",
|
|
91
121
|
taxId: "",
|
|
@@ -170,7 +200,7 @@ function toDraft(data) {
|
|
|
170
200
|
},
|
|
171
201
|
clinician: { ...EMPTY_CLINICIAN, ...snapshot?.renderingProvider ?? {} },
|
|
172
202
|
location: { ...EMPTY_LOCATION, ...snapshot?.placeOfService ?? {} },
|
|
173
|
-
lineItems: data.bill.lineItems
|
|
203
|
+
lineItems: ensureTrailingProcedureLine(data.bill.lineItems)
|
|
174
204
|
};
|
|
175
205
|
}
|
|
176
206
|
function buildBillReviewSaveInput(draft) {
|
|
@@ -685,15 +715,12 @@ function BillReviewForm({
|
|
|
685
715
|
] })
|
|
686
716
|
] }),
|
|
687
717
|
/* @__PURE__ */ jsxs("section", { className: sectionClass, children: [
|
|
688
|
-
/* @__PURE__ */
|
|
689
|
-
/* @__PURE__ */
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
] }),
|
|
693
|
-
/* @__PURE__ */ jsx("button", { type: "button", className: "mb-native-button quiet", disabled: !editable || draft.lineItems.length >= 50, onClick: () => setDraft((current) => ({ ...current, lineItems: [...current.lineItems, { code: "", modifiers: [], units: 1, charge: 0 }] })), children: "+ Add line" })
|
|
694
|
-
] }),
|
|
718
|
+
/* @__PURE__ */ jsx("div", { className: "mb-native-section-head", children: /* @__PURE__ */ jsxs("div", { children: [
|
|
719
|
+
/* @__PURE__ */ jsx("h3", { children: "Procedure lines" }),
|
|
720
|
+
/* @__PURE__ */ jsx("p", { children: "Choose a billing profile, then review the codes and modifiers. A new row appears as you type; allowed amounts recalculate on save." })
|
|
721
|
+
] }) }),
|
|
695
722
|
features?.codingPresets === false ? null : /* @__PURE__ */ jsxs("div", { className: "mb-native-presets", role: "group", "aria-label": "Evaluation billing profile", children: [
|
|
696
|
-
[["qme", "QME"], ["ame", "AME"], ["psych_qme", "Psych QME"]].map(([value, label]) => /* @__PURE__ */ jsx("button", { type: "button", className: inferPreset(draft.lineItems) === value ? "active" : "", disabled: !editable, onClick: () => setDraft((current) => ({ ...current, lineItems: applyPreset(current.lineItems, value) })), children: label }, value)),
|
|
723
|
+
[["qme", "QME"], ["ame", "AME"], ["psych_qme", "Psych QME"]].map(([value, label]) => /* @__PURE__ */ jsx("button", { type: "button", className: inferPreset(draft.lineItems) === value ? "active" : "", disabled: !editable, onClick: () => setDraft((current) => ({ ...current, lineItems: ensureTrailingProcedureLine(applyPreset(current.lineItems, value)) })), children: label }, value)),
|
|
697
724
|
/* @__PURE__ */ jsx("span", { children: "Sets evaluator modifiers across med-legal lines. You can still edit each line." })
|
|
698
725
|
] }),
|
|
699
726
|
/* @__PURE__ */ jsx("div", { className: "mb-native-lines", children: draft.lineItems.map((line, index) => /* @__PURE__ */ jsxs("div", { className: "mb-native-line", children: [
|
|
@@ -710,15 +737,7 @@ function BillReviewForm({
|
|
|
710
737
|
)[0];
|
|
711
738
|
return { ...item, code, modifiers: presetLine?.modifiers || [] };
|
|
712
739
|
});
|
|
713
|
-
|
|
714
|
-
lineItems.push({
|
|
715
|
-
code: "",
|
|
716
|
-
modifiers: [],
|
|
717
|
-
units: 1,
|
|
718
|
-
charge: 0
|
|
719
|
-
});
|
|
720
|
-
}
|
|
721
|
-
return { ...current, lineItems };
|
|
740
|
+
return { ...current, lineItems: ensureTrailingProcedureLine(lineItems) };
|
|
722
741
|
});
|
|
723
742
|
}, children: [
|
|
724
743
|
line.code && !PROCEDURES.some(([code]) => code === line.code) ? /* @__PURE__ */ jsx("option", { value: line.code, children: line.code }) : null,
|
|
@@ -737,7 +756,7 @@ function BillReviewForm({
|
|
|
737
756
|
/* @__PURE__ */ jsxs("select", { disabled: !editable, value: "", onChange: (event) => {
|
|
738
757
|
const modifier = event.target.value;
|
|
739
758
|
if (!modifier) return;
|
|
740
|
-
setDraft((current) => ({ ...current, lineItems: current.lineItems.map((item, itemIndex) => itemIndex === index ? { ...item, modifiers: [.../* @__PURE__ */ new Set([...item.modifiers, modifier])] } : item) }));
|
|
759
|
+
setDraft((current) => ({ ...current, lineItems: ensureTrailingProcedureLine(current.lineItems.map((item, itemIndex) => itemIndex === index ? { ...item, modifiers: [.../* @__PURE__ */ new Set([...item.modifiers, modifier])] } : item)) }));
|
|
741
760
|
}, children: [
|
|
742
761
|
/* @__PURE__ */ jsx("option", { value: "", children: "Add modifier\u2026" }),
|
|
743
762
|
MODIFIERS.filter(([value]) => !line.modifiers.includes(value) && modifierAppliesToCode(value, line.code)).map(([value, label]) => /* @__PURE__ */ jsxs("option", { value, children: [
|
|
@@ -748,7 +767,7 @@ function BillReviewForm({
|
|
|
748
767
|
] }, value))
|
|
749
768
|
] })
|
|
750
769
|
] }),
|
|
751
|
-
/* @__PURE__ */ jsx("div", { className: "mb-native-chips", children: line.modifiers.map((modifier) => /* @__PURE__ */ jsxs("button", { type: "button", disabled: !editable, onClick: () => setDraft((current) => ({ ...current, lineItems: current.lineItems.map((item, itemIndex) => itemIndex === index ? { ...item, modifiers: item.modifiers.filter((value) => value !== modifier) } : item) })), children: [
|
|
770
|
+
/* @__PURE__ */ jsx("div", { className: "mb-native-chips", children: line.modifiers.map((modifier) => /* @__PURE__ */ jsxs("button", { type: "button", disabled: !editable, onClick: () => setDraft((current) => ({ ...current, lineItems: ensureTrailingProcedureLine(current.lineItems.map((item, itemIndex) => itemIndex === index ? { ...item, modifiers: item.modifiers.filter((value) => value !== modifier) } : item)) })), children: [
|
|
752
771
|
"-",
|
|
753
772
|
modifier,
|
|
754
773
|
" \xD7"
|
|
@@ -756,13 +775,13 @@ function BillReviewForm({
|
|
|
756
775
|
] }),
|
|
757
776
|
/* @__PURE__ */ jsxs("label", { className: "mb-native-field", children: [
|
|
758
777
|
/* @__PURE__ */ jsx("span", { children: "Units" }),
|
|
759
|
-
/* @__PURE__ */ jsx("input", { type: "number", min: "1", required: true, disabled: !editable, value: line.units, onChange: (event) => setDraft((current) => ({ ...current, lineItems: current.lineItems.map((item, itemIndex) => itemIndex === index ? { ...item, units: Number(event.target.value) } : item) })) })
|
|
778
|
+
/* @__PURE__ */ jsx("input", { type: "number", min: "1", required: true, disabled: !editable, value: line.units, onChange: (event) => setDraft((current) => ({ ...current, lineItems: ensureTrailingProcedureLine(current.lineItems.map((item, itemIndex) => itemIndex === index ? { ...item, units: Number(event.target.value) } : item)) })) })
|
|
760
779
|
] }),
|
|
761
780
|
/* @__PURE__ */ jsxs("div", { className: "mb-native-allowed", children: [
|
|
762
781
|
/* @__PURE__ */ jsx("span", { children: "Allowed" }),
|
|
763
782
|
/* @__PURE__ */ jsx("strong", { children: money(line.charge) })
|
|
764
783
|
] }),
|
|
765
|
-
/* @__PURE__ */ jsx("button", { type: "button", className: "mb-native-remove", "aria-label": `Remove ${line.code || "procedure"}`, disabled: !editable
|
|
784
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "mb-native-remove", "aria-label": `Remove ${line.code || "procedure"}`, disabled: !editable, onClick: () => setDraft((current) => ({ ...current, lineItems: ensureTrailingProcedureLine(current.lineItems.filter((_, itemIndex) => itemIndex !== index)) })), children: "\xD7" })
|
|
766
785
|
] }, line.id || index)) })
|
|
767
786
|
] }),
|
|
768
787
|
/* @__PURE__ */ jsxs("section", { className: sectionClass, children: [
|
|
@@ -1170,294 +1189,14 @@ import {
|
|
|
1170
1189
|
useRef as useRef3,
|
|
1171
1190
|
useState as useState3
|
|
1172
1191
|
} from "react";
|
|
1192
|
+
import {
|
|
1193
|
+
DEFAULT_API_BASE_URL as DEFAULT_API_BASE_URL2,
|
|
1194
|
+
DEFAULT_SESSION_ENDPOINT as DEFAULT_SESSION_ENDPOINT2,
|
|
1195
|
+
createBillLifecycleClient
|
|
1196
|
+
} from "@mindbill/browser";
|
|
1197
|
+
import { createBillLifecycleClient as createBillLifecycleClient2 } from "@mindbill/browser";
|
|
1173
1198
|
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1174
|
-
var DEFAULT_API_BASE_URL2 = "https://app.mindbill.org";
|
|
1175
|
-
var DEFAULT_SESSION_ENDPOINT2 = "/api/mindbill/bill-session";
|
|
1176
1199
|
var DEFAULT_REFRESH_INTERVAL2 = 6e4;
|
|
1177
|
-
async function responseError2(response, fallback) {
|
|
1178
|
-
const body = await response.json().catch(() => null);
|
|
1179
|
-
const detail = body && typeof body === "object" ? body.detail ?? body.message ?? body.error : null;
|
|
1180
|
-
return new Error(typeof detail === "string" ? detail : fallback);
|
|
1181
|
-
}
|
|
1182
|
-
function isSessionFresh2(session) {
|
|
1183
|
-
if (!session) return false;
|
|
1184
|
-
if (!session.expiresAt) return true;
|
|
1185
|
-
const expiresAt = new Date(session.expiresAt).getTime();
|
|
1186
|
-
return Number.isFinite(expiresAt) && expiresAt > Date.now() + 3e4;
|
|
1187
|
-
}
|
|
1188
|
-
function normalizeSession2(value) {
|
|
1189
|
-
if (!value || typeof value !== "object") {
|
|
1190
|
-
throw new Error("The billing session endpoint returned an invalid response.");
|
|
1191
|
-
}
|
|
1192
|
-
const candidate = value;
|
|
1193
|
-
const nested = candidate.session ?? candidate.data;
|
|
1194
|
-
const session = nested && typeof nested === "object" ? nested : candidate;
|
|
1195
|
-
if (typeof session.token !== "string" || session.token.length < 8) {
|
|
1196
|
-
throw new Error(
|
|
1197
|
-
"The billing session endpoint did not return a browser session token."
|
|
1198
|
-
);
|
|
1199
|
-
}
|
|
1200
|
-
return {
|
|
1201
|
-
token: session.token,
|
|
1202
|
-
...typeof session.expiresAt === "string" ? { expiresAt: session.expiresAt } : {},
|
|
1203
|
-
...typeof session.apiBaseUrl === "string" ? { apiBaseUrl: session.apiBaseUrl } : {}
|
|
1204
|
-
};
|
|
1205
|
-
}
|
|
1206
|
-
function normalizeLifecycle(value) {
|
|
1207
|
-
if (!value || typeof value !== "object") {
|
|
1208
|
-
throw new Error("The billing service returned an invalid bill lifecycle.");
|
|
1209
|
-
}
|
|
1210
|
-
const data = value;
|
|
1211
|
-
if (!data.bill || !data.patient || !data.injury || !data.lifecycle || !Array.isArray(data.lifecycle.actions) || !Array.isArray(data.eors)) {
|
|
1212
|
-
throw new Error("The billing service returned an invalid bill lifecycle.");
|
|
1213
|
-
}
|
|
1214
|
-
return data;
|
|
1215
|
-
}
|
|
1216
|
-
function idempotencyKey() {
|
|
1217
|
-
if (typeof globalThis.crypto?.randomUUID === "function") {
|
|
1218
|
-
return globalThis.crypto.randomUUID();
|
|
1219
|
-
}
|
|
1220
|
-
return `mb-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
1221
|
-
}
|
|
1222
|
-
function createBillLifecycleClient({
|
|
1223
|
-
billId,
|
|
1224
|
-
sessionEndpoint = DEFAULT_SESSION_ENDPOINT2,
|
|
1225
|
-
getSession,
|
|
1226
|
-
apiBaseUrl = DEFAULT_API_BASE_URL2,
|
|
1227
|
-
fetch: fetchOverride
|
|
1228
|
-
}) {
|
|
1229
|
-
const fetcher = fetchOverride ?? globalThis.fetch;
|
|
1230
|
-
if (typeof fetcher !== "function") {
|
|
1231
|
-
throw new Error("A Fetch API implementation is required.");
|
|
1232
|
-
}
|
|
1233
|
-
let session = null;
|
|
1234
|
-
let sessionRequest = null;
|
|
1235
|
-
const mintSession = async (signal, force = false) => {
|
|
1236
|
-
if (!force && isSessionFresh2(session)) return session;
|
|
1237
|
-
if (!force && sessionRequest) return sessionRequest;
|
|
1238
|
-
const pending = getSession ? getSession({ billId, component: "bill-review", signal }) : fetcher(sessionEndpoint, {
|
|
1239
|
-
method: "POST",
|
|
1240
|
-
credentials: "same-origin",
|
|
1241
|
-
headers: { "content-type": "application/json" },
|
|
1242
|
-
body: JSON.stringify({ billId, component: "bill-review" }),
|
|
1243
|
-
signal
|
|
1244
|
-
}).then(async (response) => {
|
|
1245
|
-
if (!response.ok) {
|
|
1246
|
-
throw await responseError2(
|
|
1247
|
-
response,
|
|
1248
|
-
"The billing session could not be created."
|
|
1249
|
-
);
|
|
1250
|
-
}
|
|
1251
|
-
return response.json();
|
|
1252
|
-
});
|
|
1253
|
-
const request2 = Promise.resolve(pending).then(normalizeSession2).then((nextSession) => {
|
|
1254
|
-
session = nextSession;
|
|
1255
|
-
return nextSession;
|
|
1256
|
-
}).finally(() => {
|
|
1257
|
-
if (sessionRequest === request2) sessionRequest = null;
|
|
1258
|
-
});
|
|
1259
|
-
sessionRequest = request2;
|
|
1260
|
-
return request2;
|
|
1261
|
-
};
|
|
1262
|
-
const request = async (path, init = {}, providedSignal) => {
|
|
1263
|
-
const controller = providedSignal ? null : new AbortController();
|
|
1264
|
-
const signal = providedSignal ?? controller?.signal;
|
|
1265
|
-
if (!signal) throw new Error("An AbortSignal could not be created.");
|
|
1266
|
-
let browserSession = await mintSession(signal);
|
|
1267
|
-
const perform = (current) => {
|
|
1268
|
-
const base = (current.apiBaseUrl ?? apiBaseUrl).replace(/\/$/, "");
|
|
1269
|
-
const headers = new Headers(init.headers);
|
|
1270
|
-
headers.set("authorization", `Bearer ${current.token}`);
|
|
1271
|
-
return fetcher(`${base}${path}`, { ...init, headers, signal });
|
|
1272
|
-
};
|
|
1273
|
-
let response = await perform(browserSession);
|
|
1274
|
-
if (response.status === 401) {
|
|
1275
|
-
session = null;
|
|
1276
|
-
browserSession = await mintSession(signal, true);
|
|
1277
|
-
response = await perform(browserSession);
|
|
1278
|
-
}
|
|
1279
|
-
return response;
|
|
1280
|
-
};
|
|
1281
|
-
const loadLifecycle = async (signal) => {
|
|
1282
|
-
const response = await request("/partner/v2/browser/bill", {}, signal);
|
|
1283
|
-
if (!response.ok) {
|
|
1284
|
-
throw await responseError2(response, "Bill lifecycle could not be loaded.");
|
|
1285
|
-
}
|
|
1286
|
-
const body = await response.json();
|
|
1287
|
-
return normalizeLifecycle(body.data);
|
|
1288
|
-
};
|
|
1289
|
-
const searchClaimsAdministrators = async (query, claimNumber) => {
|
|
1290
|
-
const params = new URLSearchParams();
|
|
1291
|
-
if (query.trim()) params.set("q", query.trim());
|
|
1292
|
-
if (claimNumber?.trim()) params.set("claimNumber", claimNumber.trim());
|
|
1293
|
-
const response = await request(
|
|
1294
|
-
`/partner/v2/browser/claims-administrators?${params.toString()}`
|
|
1295
|
-
);
|
|
1296
|
-
if (!response.ok) {
|
|
1297
|
-
throw await responseError2(
|
|
1298
|
-
response,
|
|
1299
|
-
"Claims administrator search is unavailable."
|
|
1300
|
-
);
|
|
1301
|
-
}
|
|
1302
|
-
const body = await response.json();
|
|
1303
|
-
if (!Array.isArray(body.results)) {
|
|
1304
|
-
throw new Error("Claims administrator search returned an invalid response.");
|
|
1305
|
-
}
|
|
1306
|
-
return body.results.flatMap((value) => {
|
|
1307
|
-
if (!value || typeof value !== "object") return [];
|
|
1308
|
-
const payer = value;
|
|
1309
|
-
if (typeof payer.id !== "string" || typeof payer.name !== "string") {
|
|
1310
|
-
return [];
|
|
1311
|
-
}
|
|
1312
|
-
return [{
|
|
1313
|
-
id: payer.id,
|
|
1314
|
-
name: payer.name,
|
|
1315
|
-
...typeof payer.hasElectronic === "boolean" ? { hasElectronic: payer.hasElectronic } : {},
|
|
1316
|
-
...Array.isArray(payer.states) ? { states: payer.states.filter((state) => typeof state === "string") } : {},
|
|
1317
|
-
...["high", "medium", "directory"].includes(payer.confidence ?? "") ? {
|
|
1318
|
-
confidence: payer.confidence
|
|
1319
|
-
} : {},
|
|
1320
|
-
...typeof payer.recommended === "boolean" ? { recommended: payer.recommended } : {},
|
|
1321
|
-
...Array.isArray(payer.signals) ? {
|
|
1322
|
-
signals: payer.signals.flatMap((signal) => {
|
|
1323
|
-
if (!signal || typeof signal !== "object") return [];
|
|
1324
|
-
const candidate = signal;
|
|
1325
|
-
if (!["name", "claim_number"].includes(String(candidate.kind)) || !["match", "warning"].includes(String(candidate.state)) || typeof candidate.label !== "string") return [];
|
|
1326
|
-
return [{
|
|
1327
|
-
kind: candidate.kind,
|
|
1328
|
-
state: candidate.state,
|
|
1329
|
-
label: candidate.label
|
|
1330
|
-
}];
|
|
1331
|
-
})
|
|
1332
|
-
} : {}
|
|
1333
|
-
}];
|
|
1334
|
-
});
|
|
1335
|
-
};
|
|
1336
|
-
const mutation = async (path, init, fallback) => {
|
|
1337
|
-
const headers = new Headers(init.headers);
|
|
1338
|
-
headers.set("idempotency-key", idempotencyKey());
|
|
1339
|
-
const response = await request(path, { ...init, headers });
|
|
1340
|
-
if (!response.ok) throw await responseError2(response, fallback);
|
|
1341
|
-
return response;
|
|
1342
|
-
};
|
|
1343
|
-
const action = async (input, fallback) => {
|
|
1344
|
-
const response = await mutation(
|
|
1345
|
-
"/partner/v2/browser/actions",
|
|
1346
|
-
{
|
|
1347
|
-
method: "POST",
|
|
1348
|
-
headers: { "content-type": "application/json" },
|
|
1349
|
-
body: JSON.stringify(input)
|
|
1350
|
-
},
|
|
1351
|
-
fallback
|
|
1352
|
-
);
|
|
1353
|
-
const body = await response.json();
|
|
1354
|
-
return normalizeLifecycle(body.data);
|
|
1355
|
-
};
|
|
1356
|
-
const saveReview = async (input) => {
|
|
1357
|
-
await mutation(
|
|
1358
|
-
"/partner/v2/browser/bill",
|
|
1359
|
-
{
|
|
1360
|
-
method: "PATCH",
|
|
1361
|
-
headers: { "content-type": "application/json" },
|
|
1362
|
-
body: JSON.stringify(input)
|
|
1363
|
-
},
|
|
1364
|
-
"Bill changes could not be saved."
|
|
1365
|
-
);
|
|
1366
|
-
return loadLifecycle();
|
|
1367
|
-
};
|
|
1368
|
-
return {
|
|
1369
|
-
clearSession() {
|
|
1370
|
-
session = null;
|
|
1371
|
-
sessionRequest = null;
|
|
1372
|
-
},
|
|
1373
|
-
getLifecycle: loadLifecycle,
|
|
1374
|
-
searchClaimsAdministrators,
|
|
1375
|
-
saveReview,
|
|
1376
|
-
async submitBill(input, route) {
|
|
1377
|
-
await saveReview(input);
|
|
1378
|
-
await mutation(
|
|
1379
|
-
"/partner/v2/browser/submissions",
|
|
1380
|
-
{
|
|
1381
|
-
method: "POST",
|
|
1382
|
-
headers: { "content-type": "application/json" },
|
|
1383
|
-
body: JSON.stringify({ route })
|
|
1384
|
-
},
|
|
1385
|
-
"Bill could not be submitted."
|
|
1386
|
-
);
|
|
1387
|
-
return loadLifecycle();
|
|
1388
|
-
},
|
|
1389
|
-
async addAttachment(file, documentType, description) {
|
|
1390
|
-
const body = new FormData();
|
|
1391
|
-
body.set("file", file);
|
|
1392
|
-
body.set("documentType", documentType);
|
|
1393
|
-
if (description) body.set("description", description);
|
|
1394
|
-
await mutation(
|
|
1395
|
-
"/partner/v2/browser/documents",
|
|
1396
|
-
{ method: "POST", body },
|
|
1397
|
-
"Document could not be attached."
|
|
1398
|
-
);
|
|
1399
|
-
return loadLifecycle();
|
|
1400
|
-
},
|
|
1401
|
-
async removeAttachment(attachmentId) {
|
|
1402
|
-
await mutation(
|
|
1403
|
-
`/partner/v2/browser/documents/${encodeURIComponent(attachmentId)}`,
|
|
1404
|
-
{ method: "DELETE" },
|
|
1405
|
-
"Document could not be removed."
|
|
1406
|
-
);
|
|
1407
|
-
return loadLifecycle();
|
|
1408
|
-
},
|
|
1409
|
-
async getAttachment(attachmentId) {
|
|
1410
|
-
const response = await request(
|
|
1411
|
-
`/partner/v2/browser/documents/${encodeURIComponent(attachmentId)}`
|
|
1412
|
-
);
|
|
1413
|
-
if (!response.ok) {
|
|
1414
|
-
throw await responseError2(response, "Document could not be opened.");
|
|
1415
|
-
}
|
|
1416
|
-
return response.blob();
|
|
1417
|
-
},
|
|
1418
|
-
async getEor(documentId) {
|
|
1419
|
-
const response = await request(
|
|
1420
|
-
`/partner/v2/browser/eors/${encodeURIComponent(documentId)}`
|
|
1421
|
-
);
|
|
1422
|
-
if (!response.ok) throw await responseError2(response, "EOR could not be opened.");
|
|
1423
|
-
return response.blob();
|
|
1424
|
-
},
|
|
1425
|
-
closeBill(input) {
|
|
1426
|
-
return action({ action: "close", ...input }, "Bill could not be closed.");
|
|
1427
|
-
},
|
|
1428
|
-
postPayment(input) {
|
|
1429
|
-
return action(
|
|
1430
|
-
{ action: "post_payment", ...input, checkNumber: input.checkNumber ?? "" },
|
|
1431
|
-
"Payment could not be posted."
|
|
1432
|
-
);
|
|
1433
|
-
},
|
|
1434
|
-
submitSecondReview(input) {
|
|
1435
|
-
return action(
|
|
1436
|
-
{ action: "second_review", ...input },
|
|
1437
|
-
"Second Review could not be submitted."
|
|
1438
|
-
);
|
|
1439
|
-
},
|
|
1440
|
-
async startCorrection() {
|
|
1441
|
-
const response = await mutation(
|
|
1442
|
-
"/partner/v2/browser/actions",
|
|
1443
|
-
{
|
|
1444
|
-
method: "POST",
|
|
1445
|
-
headers: { "content-type": "application/json" },
|
|
1446
|
-
body: JSON.stringify({ action: "start_correction" })
|
|
1447
|
-
},
|
|
1448
|
-
"Correction draft could not be created."
|
|
1449
|
-
);
|
|
1450
|
-
const body = await response.json();
|
|
1451
|
-
if (typeof body.replacementBillId !== "string") {
|
|
1452
|
-
throw new Error("The billing service did not return the correction bill ID.");
|
|
1453
|
-
}
|
|
1454
|
-
return {
|
|
1455
|
-
replacementBillId: body.replacementBillId,
|
|
1456
|
-
data: normalizeLifecycle(body.data)
|
|
1457
|
-
};
|
|
1458
|
-
}
|
|
1459
|
-
};
|
|
1460
|
-
}
|
|
1461
1200
|
function openPdf(blob, filename) {
|
|
1462
1201
|
const url = URL.createObjectURL(blob);
|
|
1463
1202
|
const link = document.createElement("a");
|
|
@@ -2003,8 +1742,9 @@ export {
|
|
|
2003
1742
|
MindBillBillReview,
|
|
2004
1743
|
MindBillBillTimeline,
|
|
2005
1744
|
buildBillReviewSaveInput,
|
|
2006
|
-
createBillLifecycleClient,
|
|
1745
|
+
createBillLifecycleClient2 as createBillLifecycleClient,
|
|
2007
1746
|
createBillStatusClient,
|
|
1747
|
+
ensureTrailingProcedureLine,
|
|
2008
1748
|
mindBillAppearanceStyle,
|
|
2009
1749
|
mindBillThemePresets,
|
|
2010
1750
|
resolveMindBillAppearance,
|