@mindbill/react 0.21.0 → 0.21.2
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 +4 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +168 -72
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -153,14 +153,16 @@ The component includes the interaction model, not just the markup:
|
|
|
153
153
|
- a responsive two-column review form (one column on narrow screens);
|
|
154
154
|
- paste-friendly `MM/DD/YYYY` date fields and required-field asterisks;
|
|
155
155
|
- ZIP-to-city/state completion through MindBill's authenticated postal directory;
|
|
156
|
-
- complete, server-backed ICD-10 search, common-injury quick picks, and removable chips;
|
|
156
|
+
- complete, server-backed ICD-10 search with an alphabetized 100-code first page, automatic 100-code scroll paging, common-injury quick picks, and removable chips;
|
|
157
157
|
- canonical claims-administrator search through the authenticated MindBill payer directory;
|
|
158
158
|
- QME, AME, and Psych QME evaluation modes with medical-legal modifier defaults;
|
|
159
159
|
- searchable workers-comp procedure/modifier controls, medical-legal fee-schedule amounts, totals, valid manual CPT/HCPCS entry, and an automatically maintained empty line;
|
|
160
|
-
- removable source documents, a locked auto-attached practice W-9, and click, panel-drop, or whole-page PDF upload.
|
|
160
|
+
- removable source documents with new-tab previews, a locked auto-attached practice W-9, and a full-width click, panel-drop, or whole-page PDF upload area. Every visible source document is included until removed; uploaded PDFs do not require document-type tagging.
|
|
161
161
|
|
|
162
162
|
Partners supply tenant-specific bootstrap data, one short-lived browser session callback, and the final server submission callback. Required fields, validation, ZIP lookup, ICD-10 and payer directories, service-line behavior, attachments, and submission UX stay inside `@mindbill/react`, so every integration receives the same billing workflow. Optional `diagnosisOptions`, `procedureOptions`, `modifierOptions`, and lookup callbacks extend or replace defaults when a partner has licensed or organization-specific data.
|
|
163
163
|
|
|
164
|
+
Validation is component-owned as well. A missing routing payer is highlighted immediately; Submit highlights every invalid control with a specific message, focuses and scrolls to the first problem, and continues validating as the user corrects the form.
|
|
165
|
+
|
|
164
166
|
`BILL_SUBMISSION_REQUIRED_FIELDS` and `validateBillSubmission` expose the same contract for tests and non-visual integrations. The component never creates a draft; its callback fires only when the user submits a locally valid snapshot.
|
|
165
167
|
|
|
166
168
|
`BillReviewForm` remains available for legacy integrations that already own a custom review model. New integrations should use `BillSubmissionForm`.
|
package/dist/index.d.ts
CHANGED
|
@@ -453,6 +453,7 @@ type BillSubmissionSourceAttachment = {
|
|
|
453
453
|
fileName: string;
|
|
454
454
|
documentType: BillSubmissionDocumentType;
|
|
455
455
|
description?: string;
|
|
456
|
+
/** @deprecated Source attachments shown in the form are included until removed. */
|
|
456
457
|
selected?: boolean;
|
|
457
458
|
/** Auto-attached documents such as a practice W-9 cannot be deselected. */
|
|
458
459
|
autoAttached?: boolean;
|
|
@@ -480,7 +481,7 @@ type BillSubmissionFormProps = {
|
|
|
480
481
|
fetch?: typeof globalThis.fetch;
|
|
481
482
|
onSearchClaimsAdministrators?: (query: string, claimNumber?: string) => Promise<BillReviewPayer$1[]>;
|
|
482
483
|
diagnosisOptions?: BillSubmissionDiagnosisOption[];
|
|
483
|
-
onSearchDiagnoses?: (query: string) => Promise<BillSubmissionDiagnosisOption[]>;
|
|
484
|
+
onSearchDiagnoses?: (query: string, limit?: number, offset?: number) => Promise<BillSubmissionDiagnosisOption[]>;
|
|
484
485
|
onLookupPostalCode?: (postalCode: string) => Promise<BillSubmissionPostalPlace | null>;
|
|
485
486
|
procedureOptions?: BillSubmissionProcedureOption[];
|
|
486
487
|
modifierOptions?: BillSubmissionModifierOption[];
|
package/dist/index.js
CHANGED
|
@@ -1320,7 +1320,13 @@ var documentLabels = {
|
|
|
1320
1320
|
var MAX_PDF_BYTES = 25 * 1024 * 1024;
|
|
1321
1321
|
var MAX_UPLOAD_BYTES = 100 * 1024 * 1024;
|
|
1322
1322
|
var MAX_DOCUMENTS = 25;
|
|
1323
|
+
var DIAGNOSIS_PAGE_SIZE = 100;
|
|
1323
1324
|
var EMPTY_ATTACHMENTS = [];
|
|
1325
|
+
function previewUploadedPdf(file) {
|
|
1326
|
+
const url = URL.createObjectURL(file);
|
|
1327
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
1328
|
+
window.setTimeout(() => URL.revokeObjectURL(url), 6e4);
|
|
1329
|
+
}
|
|
1324
1330
|
function blankLine() {
|
|
1325
1331
|
return { code: "", modifiers: [], units: 1 };
|
|
1326
1332
|
}
|
|
@@ -1384,19 +1390,19 @@ function cloneBill(bill) {
|
|
|
1384
1390
|
}
|
|
1385
1391
|
function validateBillSubmission(bill) {
|
|
1386
1392
|
const errors = {};
|
|
1387
|
-
const required = (path, value) => {
|
|
1388
|
-
if (typeof value !== "string" || !value.trim()) errors[path] =
|
|
1393
|
+
const required = (path, value, message) => {
|
|
1394
|
+
if (typeof value !== "string" || !value.trim()) errors[path] = message;
|
|
1389
1395
|
};
|
|
1390
|
-
required("patient.firstName", bill.patient.firstName);
|
|
1391
|
-
required("patient.lastName", bill.patient.lastName);
|
|
1392
|
-
required("patient.dateOfBirth", bill.patient.dateOfBirth);
|
|
1393
|
-
required("patient.address.line1", bill.patient.address.line1);
|
|
1394
|
-
required("patient.address.city", bill.patient.address.city);
|
|
1395
|
-
required("patient.address.state", bill.patient.address.state);
|
|
1396
|
-
required("patient.address.postalCode", bill.patient.address.postalCode);
|
|
1397
|
-
required("claim.claimNumber", bill.claim.claimNumber);
|
|
1398
|
-
required("claim.claimsAdministrator", bill.claim.claimsAdministrator?.name);
|
|
1399
|
-
required("service.date", bill.service.date);
|
|
1396
|
+
required("patient.firstName", bill.patient.firstName, "Enter the patient's first name.");
|
|
1397
|
+
required("patient.lastName", bill.patient.lastName, "Enter the patient's last name.");
|
|
1398
|
+
required("patient.dateOfBirth", bill.patient.dateOfBirth, "Enter the patient's date of birth.");
|
|
1399
|
+
required("patient.address.line1", bill.patient.address.line1, "Enter the patient's street address.");
|
|
1400
|
+
required("patient.address.city", bill.patient.address.city, "Enter the patient's city.");
|
|
1401
|
+
required("patient.address.state", bill.patient.address.state, "Enter the patient's 2-letter state code.");
|
|
1402
|
+
required("patient.address.postalCode", bill.patient.address.postalCode, "Enter the patient's ZIP code.");
|
|
1403
|
+
required("claim.claimNumber", bill.claim.claimNumber, "Enter the claim number.");
|
|
1404
|
+
required("claim.claimsAdministrator", bill.claim.claimsAdministrator?.name, "Select a claims administrator so MindBill can route this bill.");
|
|
1405
|
+
required("service.date", bill.service.date, "Enter the date of service.");
|
|
1400
1406
|
if (bill.claim.claimsAdministrator?.name && !bill.claim.claimsAdministrator.id) {
|
|
1401
1407
|
errors["claim.claimsAdministrator"] = "Select a claims administrator from the payer directory.";
|
|
1402
1408
|
}
|
|
@@ -1405,7 +1411,7 @@ function validateBillSubmission(bill) {
|
|
|
1405
1411
|
const lines = submittedLines(bill.serviceLines);
|
|
1406
1412
|
if (!lines.length) errors.serviceLines = "Add at least one service line";
|
|
1407
1413
|
lines.forEach((line, index) => {
|
|
1408
|
-
required(`serviceLines.${index}.code`, line.code);
|
|
1414
|
+
required(`serviceLines.${index}.code`, line.code, "Select a procedure code.");
|
|
1409
1415
|
if (!Number.isInteger(line.units) || (line.units ?? 0) < 1) errors[`serviceLines.${index}.units`] = "Enter at least 1 unit";
|
|
1410
1416
|
if (bill.billingMode === "professional" && (!Number.isFinite(line.charge) || (line.charge ?? 0) < 0)) errors[`serviceLines.${index}.charge`] = "Enter the billed charge";
|
|
1411
1417
|
});
|
|
@@ -1414,25 +1420,25 @@ function validateBillSubmission(bill) {
|
|
|
1414
1420
|
var css = `
|
|
1415
1421
|
.mbsf{display:grid;gap:20px;color:var(--mb-text);font-family:var(--mb-font);font-size:15px}.mbsf *{box-sizing:border-box}
|
|
1416
1422
|
.mbsf-head,.mbsf-section-head,.mbsf-attach-row,.mbsf-actions{display:flex;align-items:center;justify-content:space-between;gap:16px}.mbsf-title{margin:0;font-size:24px}.mbsf-copy,.mbsf-help{color:var(--mb-muted);margin:5px 0 0}.mbsf-required{font-size:13px;color:var(--mb-muted);white-space:nowrap}.mbsf-star,.mbsf-error{color:var(--mb-danger)}
|
|
1417
|
-
.mbsf-card{min-width:0;margin:0;padding:24px;border:1px solid var(--mb-border);border-radius:var(--mb-radius);background:var(--mb-surface);box-shadow:var(--mb-shadow)}.mbsf-legend{padding:0 10px;font-size:18px;font-weight:760}.mbsf-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px 24px}.mbsf-span{grid-column:1/-1}.mbsf-field{display:grid;align-content:start;gap:7px;min-width:0}.mbsf-label{font-weight:680}.mbsf-input,.mbsf-select{width:100%;min-height:46px;padding:10px 12px;border:1px solid var(--mb-border);border-radius:var(--mb-control-radius);background:var(--mb-input);color:var(--mb-text);font:inherit}.mbsf-input:focus,.mbsf-select:focus{outline:3px solid color-mix(in srgb,var(--mb-accent) 22%,transparent);border-color:var(--mb-accent)}
|
|
1418
|
-
.mbsf-combo{position:relative}.mbsf-menu{position:absolute;z-index:20;top:calc(100% + 5px);left:0;right:0;max-height:min(
|
|
1423
|
+
.mbsf-card{min-width:0;margin:0;padding:24px;border:1px solid var(--mb-border);border-radius:var(--mb-radius);background:var(--mb-surface);box-shadow:var(--mb-shadow)}.mbsf-legend{padding:0 10px;font-size:18px;font-weight:760}.mbsf-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px 24px}.mbsf-span{grid-column:1/-1}.mbsf-field{display:grid;align-content:start;gap:7px;min-width:0}.mbsf-label{font-weight:680}.mbsf-input,.mbsf-select{width:100%;min-height:46px;padding:10px 12px;border:1px solid var(--mb-border);border-radius:var(--mb-control-radius);background:var(--mb-input);color:var(--mb-text);font:inherit}.mbsf-input:focus,.mbsf-select:focus{outline:3px solid color-mix(in srgb,var(--mb-accent) 22%,transparent);border-color:var(--mb-accent)}.mbsf-field[data-invalid=true] .mbsf-input,.mbsf-field[data-invalid=true] .mbsf-select,.mbsf-invalid-control .mbsf-input{border-color:var(--mb-danger);background:color-mix(in srgb,var(--mb-danger) 4%,var(--mb-input))}.mbsf-field[data-invalid=true] .mbsf-input:focus,.mbsf-field[data-invalid=true] .mbsf-select:focus{outline-color:color-mix(in srgb,var(--mb-danger) 24%,transparent)}
|
|
1424
|
+
.mbsf-combo{position:relative}.mbsf-menu{position:absolute;z-index:20;top:calc(100% + 5px);left:0;right:0;max-height:min(360px,46vh);overflow:auto;overscroll-behavior:contain;padding:7px;border:1px solid var(--mb-border);border-radius:12px;background:var(--mb-surface);box-shadow:0 14px 35px rgba(17,38,49,.16)}.mbsf-option{display:grid;width:100%;gap:2px;padding:10px;border:0;border-radius:8px;background:transparent;color:var(--mb-text);font:inherit;text-align:left;cursor:pointer}.mbsf-option:hover,.mbsf-option:focus{background:color-mix(in srgb,var(--mb-accent) 9%,var(--mb-surface))}.mbsf-option small{color:var(--mb-muted)}.mbsf-menu-status{padding:12px;text-align:center;color:var(--mb-muted);font-size:13px}
|
|
1419
1425
|
.mbsf-chips,.mbsf-quick-picks{display:flex;flex-wrap:wrap;gap:7px;margin-bottom:8px}.mbsf-chip,.mbsf-quick-pick{display:inline-flex;align-items:center;gap:7px;padding:5px 9px;border:1px solid var(--mb-border);border-radius:999px;background:var(--mb-input)}.mbsf-chip button{border:0;background:transparent;color:var(--mb-muted);cursor:pointer;font:inherit}.mbsf-quick-pick{color:var(--mb-text);font:inherit;cursor:pointer}.mbsf-quick-pick[data-selected=true]{border-color:var(--mb-accent);color:var(--mb-accent)}
|
|
1420
1426
|
.mbsf-segments{display:grid;grid-template-columns:repeat(3,1fr);border:1px solid var(--mb-border);border-radius:var(--mb-control-radius);overflow:hidden}.mbsf-segment{min-height:44px;border:0;border-right:1px solid var(--mb-border);background:var(--mb-input);color:var(--mb-text);font:inherit;font-weight:700;cursor:pointer}.mbsf-segment:last-child{border-right:0}.mbsf-segment[aria-pressed=true]{background:var(--mb-accent);color:var(--mb-accent-contrast)}
|
|
1421
|
-
.mbsf-lines{margin-top:18px;border:1px solid var(--mb-border);border-radius:var(--mb-control-radius);overflow:visible}.mbsf-line-head,.mbsf-line{display:grid;grid-template-columns:minmax(190px,1.05fr) minmax(220px,1.5fr) 100px 120px 42px;gap:12px;align-items:start;padding:12px}.mbsf-line-head{color:var(--mb-muted);font-size:13px;font-weight:700;border-bottom:1px solid var(--mb-border)}.mbsf-line{border-bottom:1px solid var(--mb-border)}.mbsf-line:last-child{border-bottom:0}.mbsf-money{padding-top:12px;text-align:right;font-variant-numeric:tabular-nums}.mbsf-total{display:flex;justify-content:flex-end;gap:45px;padding:16px 56px 16px 16px;font-size:17px;font-weight:760}
|
|
1422
|
-
.mbsf-icon-btn{width:40px;height:42px;border:0;background:transparent;color:var(--mb-text);font-size:22px;cursor:pointer}.mbsf-secondary{min-height:40px;padding:8px 14px;border:1px solid var(--mb-border);border-radius:var(--mb-control-radius);background:var(--mb-surface);color:var(--mb-text);font:inherit;font-weight:680;cursor:pointer}.mbsf-attach-list{display:grid;gap:10px;margin-bottom:18px}.mbsf-attach-row{padding:14px;border:1px solid var(--mb-border);border-radius:var(--mb-control-radius)}.mbsf-attach-row[data-auto=true]{border-color:color-mix(in srgb,#159447 45%,var(--mb-border));background:color-mix(in srgb,#159447 5%,var(--mb-surface))}.mbsf-file{min-width:0}.mbsf-file strong{overflow-wrap:anywhere}.mbsf-badge{display:inline-block;margin-left:8px;padding:2px 7px;border:1px solid var(--mb-border);border-radius:7px;color:var(--mb-muted);font-size:12px;font-weight:600}.mbsf-drop{display:grid;place-items:center;min-height:
|
|
1423
|
-
@media(max-width:820px){.mbsf{gap:16px}.mbsf-grid{grid-template-columns:1fr}.mbsf-span{grid-column:auto}.mbsf-card{padding:18px 16px}.mbsf-line-head{display:none}.mbsf-line{position:relative;display:grid;grid-template-columns:minmax(0,1fr) 86px;gap:14px;padding:18px 16px}.mbsf-line>div:before{display:block;margin-bottom:6px;color:var(--mb-muted);font-size:12px;font-weight:700;content:attr(data-label)}.mbsf-line>div:nth-child(1),.mbsf-line>div:nth-child(2){grid-column:1/-1}.mbsf-money{align-self:end;padding:0 0 12px;text-align:right}.mbsf-line .mbsf-icon-btn{position:absolute;right:8px;bottom:3px}.mbsf-total{padding:16px 18px;gap:24px}.mbsf-head{align-items:flex-start}.mbsf-segments{grid-template-columns:repeat(3,minmax(0,1fr))}.mbsf-segment{min-width:0;padding:8px 4px;border-right:1px solid var(--mb-border);border-bottom:0;font-size:13px}.mbsf-segment:last-child{border-right:0}.mbsf-attach-row{align-items:flex-start;flex-wrap:wrap}.mbsf-actions{position:sticky;bottom:86px;z-index:10}.mbsf-submit{width:100%}}
|
|
1427
|
+
.mbsf-lines{margin-top:18px;border:1px solid var(--mb-border);border-radius:var(--mb-control-radius);overflow:visible}.mbsf-lines[data-invalid=true]{border-color:var(--mb-danger)}.mbsf-line-head,.mbsf-line{display:grid;grid-template-columns:minmax(190px,1.05fr) minmax(220px,1.5fr) 100px 120px 42px;gap:12px;align-items:start;padding:12px}.mbsf-line-head{color:var(--mb-muted);font-size:13px;font-weight:700;border-bottom:1px solid var(--mb-border)}.mbsf-line{border-bottom:1px solid var(--mb-border)}.mbsf-line:last-child{border-bottom:0}.mbsf-line [data-invalid=true] .mbsf-input{border-color:var(--mb-danger);background:color-mix(in srgb,var(--mb-danger) 4%,var(--mb-input))}.mbsf-money{padding-top:12px;text-align:right;font-variant-numeric:tabular-nums}.mbsf-total{display:flex;justify-content:flex-end;gap:45px;padding:16px 56px 16px 16px;font-size:17px;font-weight:760}
|
|
1428
|
+
.mbsf-icon-btn{width:40px;height:42px;border:0;background:transparent;color:var(--mb-text);font-size:22px;cursor:pointer}.mbsf-secondary{min-height:40px;padding:8px 14px;border:1px solid var(--mb-border);border-radius:var(--mb-control-radius);background:var(--mb-surface);color:var(--mb-text);font:inherit;font-weight:680;cursor:pointer}.mbsf-attach-list{display:grid;gap:10px;margin-bottom:18px}.mbsf-attach-row{padding:14px;border:1px solid var(--mb-border);border-radius:var(--mb-control-radius)}.mbsf-attach-row[data-auto=true]{border-color:color-mix(in srgb,#159447 45%,var(--mb-border));background:color-mix(in srgb,#159447 5%,var(--mb-surface))}.mbsf-attach-main{display:flex;align-items:center;gap:12px;min-width:0;flex:1}.mbsf-attach-actions{display:flex;align-items:center;gap:6px;flex:0 0 auto}.mbsf-file{min-width:0}.mbsf-file strong{overflow-wrap:anywhere}.mbsf-badge{display:inline-block;margin-left:8px;padding:2px 7px;border:1px solid var(--mb-border);border-radius:7px;color:var(--mb-muted);font-size:12px;font-weight:600}.mbsf-drop{display:grid;width:100%;place-items:center;min-height:210px;padding:30px;border:2px dashed color-mix(in srgb,var(--mb-muted) 55%,transparent);border-radius:var(--mb-control-radius);background:color-mix(in srgb,var(--mb-accent) 3%,var(--mb-surface));color:var(--mb-text);font:inherit;text-align:center;cursor:pointer}.mbsf-drop[data-active=true]{border-color:var(--mb-accent);background:color-mix(in srgb,var(--mb-accent) 10%,var(--mb-surface))}.mbsf-alert{padding:12px 14px;border-radius:var(--mb-control-radius);background:color-mix(in srgb,var(--mb-danger) 10%,transparent);color:var(--mb-danger)}.mbsf-actions{justify-content:flex-end}.mbsf-submit{min-width:180px;min-height:48px;padding:11px 24px;border:0;border-radius:var(--mb-control-radius);background:var(--mb-accent);color:var(--mb-accent-contrast);font:inherit;font-weight:780;cursor:pointer}
|
|
1429
|
+
@media(max-width:820px){.mbsf{gap:16px}.mbsf-grid{grid-template-columns:1fr}.mbsf-span{grid-column:auto}.mbsf-card{padding:18px 16px}.mbsf-line-head{display:none}.mbsf-line{position:relative;display:grid;grid-template-columns:minmax(0,1fr) 86px;gap:14px;padding:18px 16px}.mbsf-line>div:before{display:block;margin-bottom:6px;color:var(--mb-muted);font-size:12px;font-weight:700;content:attr(data-label)}.mbsf-line>div:nth-child(1),.mbsf-line>div:nth-child(2){grid-column:1/-1}.mbsf-money{align-self:end;padding:0 0 12px;text-align:right}.mbsf-line .mbsf-icon-btn{position:absolute;right:8px;bottom:3px}.mbsf-total{padding:16px 18px;gap:24px}.mbsf-head{align-items:flex-start}.mbsf-segments{grid-template-columns:repeat(3,minmax(0,1fr))}.mbsf-segment{min-width:0;padding:8px 4px;border-right:1px solid var(--mb-border);border-bottom:0;font-size:13px}.mbsf-segment:last-child{border-right:0}.mbsf-attach-row{align-items:flex-start;flex-wrap:wrap}.mbsf-attach-main{align-items:flex-start}.mbsf-attach-actions{margin-left:auto}.mbsf-drop{min-height:190px;padding:24px 18px}.mbsf-actions{position:sticky;bottom:86px;z-index:10}.mbsf-submit{width:100%}}
|
|
1424
1430
|
`;
|
|
1425
1431
|
function RequiredMark() {
|
|
1426
1432
|
return /* @__PURE__ */ jsx3("span", { className: "mbsf-star", children: " *" });
|
|
1427
1433
|
}
|
|
1428
|
-
function Field2({ label, required, error, span, children }) {
|
|
1429
|
-
return /* @__PURE__ */ jsxs3("label", { className: `mbsf-field${span ? " mbsf-span" : ""}`, children: [
|
|
1434
|
+
function Field2({ label, required, error, path, span, children }) {
|
|
1435
|
+
return /* @__PURE__ */ jsxs3("label", { className: `mbsf-field${span ? " mbsf-span" : ""}`, "data-field-path": path, "data-invalid": Boolean(error), children: [
|
|
1430
1436
|
/* @__PURE__ */ jsxs3("span", { className: "mbsf-label", children: [
|
|
1431
1437
|
label,
|
|
1432
1438
|
required ? /* @__PURE__ */ jsx3(RequiredMark, {}) : null
|
|
1433
1439
|
] }),
|
|
1434
1440
|
children,
|
|
1435
|
-
error ? /* @__PURE__ */ jsx3("small", { className: "mbsf-error", children: error }) : null
|
|
1441
|
+
error ? /* @__PURE__ */ jsx3("small", { className: "mbsf-error", role: "alert", children: error }) : null
|
|
1436
1442
|
] });
|
|
1437
1443
|
}
|
|
1438
1444
|
function TextDateInput({ value, onChange, disabled, required, ariaLabel }) {
|
|
@@ -1454,7 +1460,7 @@ function TextDateInput({ value, onChange, disabled, required, ariaLabel }) {
|
|
|
1454
1460
|
invalid ? /* @__PURE__ */ jsx3("small", { className: "mbsf-error", children: "Use MM/DD/YYYY" }) : null
|
|
1455
1461
|
] });
|
|
1456
1462
|
}
|
|
1457
|
-
function ComboBox({ value, placeholder, options, disabled, loading, onQuery, onSelect, createOption, ariaLabel }) {
|
|
1463
|
+
function ComboBox({ value, placeholder, options, disabled, loading, loadingMore, invalid, onOpen, onQuery, onEndReached, onSelect, createOption, ariaLabel }) {
|
|
1458
1464
|
const [open, setOpen] = useState2(false);
|
|
1459
1465
|
const [query, setQuery] = useState2("");
|
|
1460
1466
|
const visible = useMemo2(() => {
|
|
@@ -1465,24 +1471,50 @@ function ComboBox({ value, placeholder, options, disabled, loading, onQuery, onS
|
|
|
1465
1471
|
}, [createOption, options, query]);
|
|
1466
1472
|
const showMenu = open && (loading || visible.length > 0 || query.trim().length >= 2);
|
|
1467
1473
|
return /* @__PURE__ */ jsxs3("div", { className: "mbsf-combo", children: [
|
|
1468
|
-
/* @__PURE__ */ jsx3("input", { className: "mbsf-input", role: "combobox", "aria-label": ariaLabel, "aria-expanded": showMenu, autoComplete: "off", disabled, placeholder, value: open ? query : value, onFocus: () => {
|
|
1474
|
+
/* @__PURE__ */ jsx3("input", { className: "mbsf-input", role: "combobox", "aria-label": ariaLabel, "aria-invalid": invalid, "aria-expanded": showMenu, autoComplete: "off", disabled, placeholder, value: open ? query : value, onFocus: () => {
|
|
1469
1475
|
setOpen(true);
|
|
1470
1476
|
setQuery("");
|
|
1477
|
+
onOpen?.();
|
|
1471
1478
|
}, onChange: (event) => {
|
|
1472
1479
|
setOpen(true);
|
|
1473
1480
|
setQuery(event.target.value);
|
|
1474
1481
|
onQuery?.(event.target.value);
|
|
1475
1482
|
}, onBlur: () => setTimeout(() => setOpen(false), 120) }),
|
|
1476
|
-
showMenu ? /* @__PURE__ */
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
setQuery("");
|
|
1483
|
+
showMenu ? /* @__PURE__ */ jsxs3("div", { className: "mbsf-menu", role: "listbox", onScroll: (event) => {
|
|
1484
|
+
const menu = event.currentTarget;
|
|
1485
|
+
if (menu.scrollHeight - menu.scrollTop - menu.clientHeight < 120) onEndReached?.();
|
|
1480
1486
|
}, children: [
|
|
1481
|
-
/* @__PURE__ */ jsx3("
|
|
1482
|
-
|
|
1483
|
-
|
|
1487
|
+
loading && !visible.length ? /* @__PURE__ */ jsx3("div", { className: "mbsf-menu-status", children: "Loading codes\u2026" }) : visible.length ? visible.map((option) => /* @__PURE__ */ jsxs3("button", { className: "mbsf-option", type: "button", role: "option", onMouseDown: (event) => event.preventDefault(), onClick: () => {
|
|
1488
|
+
onSelect(option);
|
|
1489
|
+
setOpen(false);
|
|
1490
|
+
setQuery("");
|
|
1491
|
+
}, children: [
|
|
1492
|
+
/* @__PURE__ */ jsx3("strong", { children: option.label }),
|
|
1493
|
+
option.detail ? /* @__PURE__ */ jsx3("small", { children: option.detail }) : null
|
|
1494
|
+
] }, option.id)) : /* @__PURE__ */ jsx3("div", { className: "mbsf-option", children: "No matches" }),
|
|
1495
|
+
loadingMore ? /* @__PURE__ */ jsx3("div", { className: "mbsf-menu-status", children: "Loading more\u2026" }) : null
|
|
1496
|
+
] }) : null
|
|
1484
1497
|
] });
|
|
1485
1498
|
}
|
|
1499
|
+
function focusFirstInvalid(form) {
|
|
1500
|
+
const field = form.querySelector('[data-invalid="true"]');
|
|
1501
|
+
if (!field) return;
|
|
1502
|
+
const control = field.querySelector('input,select,textarea,button,[tabindex]:not([tabindex="-1"])');
|
|
1503
|
+
control?.focus({ preventScroll: true });
|
|
1504
|
+
const fieldRect = field.getBoundingClientRect();
|
|
1505
|
+
let scrollParent = field.parentElement;
|
|
1506
|
+
while (scrollParent) {
|
|
1507
|
+
const style = window.getComputedStyle(scrollParent);
|
|
1508
|
+
if (/(auto|scroll)/.test(style.overflowY) && scrollParent.scrollHeight > scrollParent.clientHeight) break;
|
|
1509
|
+
scrollParent = scrollParent.parentElement;
|
|
1510
|
+
}
|
|
1511
|
+
if (scrollParent) {
|
|
1512
|
+
const parentRect = scrollParent.getBoundingClientRect();
|
|
1513
|
+
scrollParent.scrollTo({ top: Math.max(0, scrollParent.scrollTop + fieldRect.top - parentRect.top - 24), behavior: "smooth" });
|
|
1514
|
+
} else {
|
|
1515
|
+
window.scrollTo({ top: Math.max(0, window.scrollY + fieldRect.top - 24), behavior: "smooth" });
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1486
1518
|
function customProcedureOption(query) {
|
|
1487
1519
|
const code = query.trim().toUpperCase().replace(/\s+/g, "");
|
|
1488
1520
|
if (!/^(?:\d{5}|[A-Z]\d{4}|ML(?:20[0-5]|PRR))$/.test(code)) return null;
|
|
@@ -1493,6 +1525,9 @@ function mergeOptions(defaults, supplied) {
|
|
|
1493
1525
|
supplied?.forEach((item) => map.set(item.code.toUpperCase(), item));
|
|
1494
1526
|
return [...map.values()];
|
|
1495
1527
|
}
|
|
1528
|
+
function mergeDiagnosisOptions(options) {
|
|
1529
|
+
return [...new Map(options.map((item) => [item.code.toUpperCase(), item])).values()].sort((left, right) => left.code.localeCompare(right.code, void 0, { numeric: true }));
|
|
1530
|
+
}
|
|
1496
1531
|
function BillSubmissionForm({
|
|
1497
1532
|
initialBill,
|
|
1498
1533
|
attachments = EMPTY_ATTACHMENTS,
|
|
@@ -1516,19 +1551,26 @@ function BillSubmissionForm({
|
|
|
1516
1551
|
description = "Review the bill details, add attachments, and submit."
|
|
1517
1552
|
}) {
|
|
1518
1553
|
const [bill, setBill] = useState2(() => cloneBill(initialBill));
|
|
1519
|
-
const [selectedIds, setSelectedIds] = useState2(() => attachments.
|
|
1554
|
+
const [selectedIds, setSelectedIds] = useState2(() => attachments.map((item) => item.id));
|
|
1520
1555
|
const [removedSourceIds, setRemovedSourceIds] = useState2([]);
|
|
1521
1556
|
const [uploads, setUploads] = useState2([]);
|
|
1522
1557
|
const [errors, setErrors] = useState2({});
|
|
1558
|
+
const [validationActive, setValidationActive] = useState2(false);
|
|
1523
1559
|
const [formError, setFormError] = useState2(null);
|
|
1524
1560
|
const [submitting, setSubmitting] = useState2(false);
|
|
1525
1561
|
const [payerResults, setPayerResults] = useState2([]);
|
|
1526
1562
|
const [payerLoading, setPayerLoading] = useState2(false);
|
|
1527
|
-
const [diagnosisResults, setDiagnosisResults] = useState2(
|
|
1563
|
+
const [diagnosisResults, setDiagnosisResults] = useState2([]);
|
|
1528
1564
|
const [diagnosisLoading, setDiagnosisLoading] = useState2(false);
|
|
1565
|
+
const [diagnosisLoadingMore, setDiagnosisLoadingMore] = useState2(false);
|
|
1566
|
+
const [diagnosisQuery, setDiagnosisQuery] = useState2(null);
|
|
1567
|
+
const [diagnosisHasMore, setDiagnosisHasMore] = useState2(true);
|
|
1529
1568
|
const [postalStatus, setPostalStatus] = useState2(null);
|
|
1530
1569
|
const [dragActive, setDragActive] = useState2(false);
|
|
1570
|
+
const formRef = useRef2(null);
|
|
1531
1571
|
const fileInput = useRef2(null);
|
|
1572
|
+
const diagnosisRequest = useRef2(0);
|
|
1573
|
+
const diagnosisAppendPending = useRef2(false);
|
|
1532
1574
|
const procedures = useMemo2(() => mergeOptions(DEFAULT_BILL_SUBMISSION_PROCEDURES, procedureOptions), [procedureOptions]);
|
|
1533
1575
|
const modifiers = useMemo2(() => mergeOptions(DEFAULT_BILL_SUBMISSION_MODIFIERS, modifierOptions), [modifierOptions]);
|
|
1534
1576
|
const [evaluationType, setEvaluationType] = useState2(() => initialBill.renderingProvider?.isAme ? "ame" : initialBill.renderingProvider?.specialty?.toLowerCase().includes("psych") ? "psych_qme" : "qme");
|
|
@@ -1536,12 +1578,21 @@ function BillSubmissionForm({
|
|
|
1536
1578
|
const locked = disabled || submitting;
|
|
1537
1579
|
useEffect2(() => {
|
|
1538
1580
|
setBill(cloneBill(initialBill));
|
|
1539
|
-
setSelectedIds(attachments.
|
|
1581
|
+
setSelectedIds(attachments.map((item) => item.id));
|
|
1540
1582
|
setRemovedSourceIds([]);
|
|
1541
1583
|
setUploads([]);
|
|
1542
1584
|
setErrors({});
|
|
1585
|
+
setValidationActive(false);
|
|
1543
1586
|
setFormError(null);
|
|
1587
|
+
setDiagnosisResults([]);
|
|
1588
|
+
setDiagnosisQuery(null);
|
|
1589
|
+
setDiagnosisHasMore(true);
|
|
1590
|
+
diagnosisRequest.current += 1;
|
|
1591
|
+
diagnosisAppendPending.current = false;
|
|
1544
1592
|
}, [initialBill, attachments]);
|
|
1593
|
+
useEffect2(() => {
|
|
1594
|
+
if (validationActive) setErrors(validateBillSubmission(bill).fieldErrors);
|
|
1595
|
+
}, [bill, validationActive]);
|
|
1545
1596
|
const addFiles = useCallback((fileList) => {
|
|
1546
1597
|
const files = Array.from(fileList);
|
|
1547
1598
|
if (!files.length) return;
|
|
@@ -1605,13 +1656,43 @@ function BillSubmissionForm({
|
|
|
1605
1656
|
}
|
|
1606
1657
|
void search(query, bill.claim.claimNumber).then(setPayerResults).catch((caught) => setFormError(caught instanceof Error ? caught.message : "Claims administrator search is unavailable.")).finally(() => setPayerLoading(false));
|
|
1607
1658
|
};
|
|
1608
|
-
const
|
|
1609
|
-
|
|
1659
|
+
const loadDiagnoses = (query, append = false) => {
|
|
1660
|
+
const trimmed = query.trim();
|
|
1610
1661
|
const search = onSearchDiagnoses ?? referenceClient?.searchDiagnosisCodes;
|
|
1611
|
-
if (!search)
|
|
1612
|
-
|
|
1613
|
-
|
|
1662
|
+
if (!search) {
|
|
1663
|
+
setDiagnosisResults(mergeDiagnosisOptions(diagnosisOptions));
|
|
1664
|
+
setDiagnosisHasMore(false);
|
|
1665
|
+
setDiagnosisQuery(trimmed);
|
|
1666
|
+
return;
|
|
1667
|
+
}
|
|
1668
|
+
const continuing = append && diagnosisQuery === trimmed;
|
|
1669
|
+
if (continuing && (!diagnosisHasMore || diagnosisAppendPending.current)) return;
|
|
1670
|
+
const offset = continuing ? diagnosisResults.length : 0;
|
|
1671
|
+
const request = ++diagnosisRequest.current;
|
|
1672
|
+
if (continuing) {
|
|
1673
|
+
diagnosisAppendPending.current = true;
|
|
1674
|
+
setDiagnosisLoadingMore(true);
|
|
1675
|
+
} else {
|
|
1676
|
+
setDiagnosisLoading(true);
|
|
1677
|
+
setDiagnosisLoadingMore(false);
|
|
1678
|
+
setDiagnosisQuery(trimmed);
|
|
1679
|
+
setDiagnosisHasMore(true);
|
|
1680
|
+
}
|
|
1681
|
+
void search(trimmed, DIAGNOSIS_PAGE_SIZE, offset).then((page) => {
|
|
1682
|
+
if (request !== diagnosisRequest.current) return;
|
|
1683
|
+
setDiagnosisResults((current) => mergeDiagnosisOptions(continuing ? [...current, ...page] : page));
|
|
1684
|
+
setDiagnosisHasMore(page.length >= DIAGNOSIS_PAGE_SIZE);
|
|
1685
|
+
}).catch(() => {
|
|
1686
|
+
if (request === diagnosisRequest.current && !continuing) setDiagnosisResults([]);
|
|
1687
|
+
}).finally(() => {
|
|
1688
|
+
if (request !== diagnosisRequest.current) return;
|
|
1689
|
+
diagnosisAppendPending.current = false;
|
|
1690
|
+
setDiagnosisLoading(false);
|
|
1691
|
+
setDiagnosisLoadingMore(false);
|
|
1692
|
+
});
|
|
1614
1693
|
};
|
|
1694
|
+
const searchDiagnoses = (query) => loadDiagnoses(query);
|
|
1695
|
+
const diagnosisChoices = useMemo2(() => mergeDiagnosisOptions([...diagnosisOptions, ...diagnosisResults]), [diagnosisOptions, diagnosisResults]);
|
|
1615
1696
|
const updatePostalCode = (postalCode) => {
|
|
1616
1697
|
setAddress({ postalCode });
|
|
1617
1698
|
setPostalStatus(null);
|
|
@@ -1636,9 +1717,18 @@ function BillSubmissionForm({
|
|
|
1636
1717
|
return charge == null ? line : { ...line, charge };
|
|
1637
1718
|
}) };
|
|
1638
1719
|
const validation = validateBillSubmission(clean);
|
|
1720
|
+
setValidationActive(true);
|
|
1639
1721
|
setErrors(validation.fieldErrors);
|
|
1640
1722
|
setFormError(null);
|
|
1641
|
-
if (!validation.valid)
|
|
1723
|
+
if (!validation.valid) {
|
|
1724
|
+
const count = Object.keys(validation.fieldErrors).length;
|
|
1725
|
+
const first = Object.values(validation.fieldErrors)[0];
|
|
1726
|
+
setFormError(`Fix ${count} highlighted field${count === 1 ? "" : "s"} before submitting. ${first}`);
|
|
1727
|
+
window.requestAnimationFrame(() => {
|
|
1728
|
+
if (formRef.current) focusFirstInvalid(formRef.current);
|
|
1729
|
+
});
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1642
1732
|
if (selectedIds.length + uploads.length > MAX_DOCUMENTS) return setFormError(`A bill can include at most ${MAX_DOCUMENTS} attachments.`);
|
|
1643
1733
|
setSubmitting(true);
|
|
1644
1734
|
try {
|
|
@@ -1649,7 +1739,8 @@ function BillSubmissionForm({
|
|
|
1649
1739
|
setSubmitting(false);
|
|
1650
1740
|
}
|
|
1651
1741
|
}
|
|
1652
|
-
|
|
1742
|
+
const claimsAdministratorError = errors["claim.claimsAdministrator"] ?? (!bill.claim.claimsAdministrator?.id ? "Required for routing \u2014 search and select a claims administrator." : void 0);
|
|
1743
|
+
return /* @__PURE__ */ jsxs3("form", { ref: formRef, className: `${className} mbsf`, style: { ...mindBillAppearanceStyle(appearance), ...style }, onSubmit: (event) => {
|
|
1653
1744
|
event.preventDefault();
|
|
1654
1745
|
void submit();
|
|
1655
1746
|
}, noValidate: true, children: [
|
|
@@ -1667,29 +1758,29 @@ function BillSubmissionForm({
|
|
|
1667
1758
|
/* @__PURE__ */ jsxs3("fieldset", { className: "mbsf-card", disabled: locked, children: [
|
|
1668
1759
|
/* @__PURE__ */ jsx3("legend", { className: "mbsf-legend", children: "Patient" }),
|
|
1669
1760
|
/* @__PURE__ */ jsxs3("div", { className: "mbsf-grid", children: [
|
|
1670
|
-
/* @__PURE__ */ jsx3(Field2, { label: "First name", required: true, error: errors["patient.firstName"], children: text(bill.patient.firstName, (firstName) => setBill((c) => ({ ...c, patient: { ...c.patient, firstName } }))) }),
|
|
1671
|
-
/* @__PURE__ */ jsx3(Field2, { label: "Last name", required: true, error: errors["patient.lastName"], children: text(bill.patient.lastName, (lastName) => setBill((c) => ({ ...c, patient: { ...c.patient, lastName } }))) }),
|
|
1761
|
+
/* @__PURE__ */ jsx3(Field2, { path: "patient.firstName", label: "First name", required: true, error: errors["patient.firstName"], children: text(bill.patient.firstName, (firstName) => setBill((c) => ({ ...c, patient: { ...c.patient, firstName } }))) }),
|
|
1762
|
+
/* @__PURE__ */ jsx3(Field2, { path: "patient.lastName", label: "Last name", required: true, error: errors["patient.lastName"], children: text(bill.patient.lastName, (lastName) => setBill((c) => ({ ...c, patient: { ...c.patient, lastName } }))) }),
|
|
1672
1763
|
/* @__PURE__ */ jsx3(Field2, { label: "Middle name", children: text(bill.patient.middleName, (middleName) => setBill((c) => ({ ...c, patient: { ...c.patient, middleName } }))) }),
|
|
1673
1764
|
/* @__PURE__ */ jsx3(Field2, { label: "Phone (optional)", children: text(bill.patient.phone, (phone) => setBill((c) => ({ ...c, patient: { ...c.patient, phone } })), { type: "tel" }) }),
|
|
1674
|
-
/* @__PURE__ */ jsx3(Field2, { label: "Date of birth", required: true, error: errors["patient.dateOfBirth"], children: /* @__PURE__ */ jsx3(TextDateInput, { ariaLabel: "Date of birth", value: bill.patient.dateOfBirth, disabled: locked, required: true, onChange: (dateOfBirth) => setBill((c) => ({ ...c, patient: { ...c.patient, dateOfBirth } })) }) }),
|
|
1675
|
-
/* @__PURE__ */ jsx3(Field2, { label: "Address", required: true, span: true, error: errors["patient.address.line1"], children: text(bill.patient.address.line1, (line1) => setAddress({ line1 }), { placeholder: "Street address" }) }),
|
|
1676
|
-
/* @__PURE__ */ jsxs3(Field2, { label: "ZIP", required: true, error: errors["patient.address.postalCode"], children: [
|
|
1765
|
+
/* @__PURE__ */ jsx3(Field2, { path: "patient.dateOfBirth", label: "Date of birth", required: true, error: errors["patient.dateOfBirth"], children: /* @__PURE__ */ jsx3(TextDateInput, { ariaLabel: "Date of birth", value: bill.patient.dateOfBirth, disabled: locked, required: true, onChange: (dateOfBirth) => setBill((c) => ({ ...c, patient: { ...c.patient, dateOfBirth } })) }) }),
|
|
1766
|
+
/* @__PURE__ */ jsx3(Field2, { path: "patient.address.line1", label: "Address", required: true, span: true, error: errors["patient.address.line1"], children: text(bill.patient.address.line1, (line1) => setAddress({ line1 }), { placeholder: "Street address" }) }),
|
|
1767
|
+
/* @__PURE__ */ jsxs3(Field2, { path: "patient.address.postalCode", label: "ZIP", required: true, error: errors["patient.address.postalCode"], children: [
|
|
1677
1768
|
text(bill.patient.address.postalCode, updatePostalCode, { maxLength: 10 }),
|
|
1678
1769
|
postalStatus ? /* @__PURE__ */ jsx3("small", { className: "mbsf-help", children: postalStatus }) : null
|
|
1679
1770
|
] }),
|
|
1680
|
-
/* @__PURE__ */ jsx3(Field2, { label: "City", required: true, error: errors["patient.address.city"], children: text(bill.patient.address.city, (city) => setAddress({ city })) }),
|
|
1681
|
-
/* @__PURE__ */ jsx3(Field2, { label: "State", required: true, error: errors["patient.address.state"], children: text(bill.patient.address.state, (state) => setAddress({ state: state.toUpperCase() }), { maxLength: 2 }) })
|
|
1771
|
+
/* @__PURE__ */ jsx3(Field2, { path: "patient.address.city", label: "City", required: true, error: errors["patient.address.city"], children: text(bill.patient.address.city, (city) => setAddress({ city })) }),
|
|
1772
|
+
/* @__PURE__ */ jsx3(Field2, { path: "patient.address.state", label: "State", required: true, error: errors["patient.address.state"], children: text(bill.patient.address.state, (state) => setAddress({ state: state.toUpperCase() }), { maxLength: 2 }) })
|
|
1682
1773
|
] })
|
|
1683
1774
|
] }),
|
|
1684
1775
|
/* @__PURE__ */ jsxs3("fieldset", { className: "mbsf-card", disabled: locked, children: [
|
|
1685
1776
|
/* @__PURE__ */ jsx3("legend", { className: "mbsf-legend", children: "Injury & claim" }),
|
|
1686
1777
|
/* @__PURE__ */ jsxs3("div", { className: "mbsf-grid", children: [
|
|
1687
|
-
/* @__PURE__ */ jsx3(Field2, { label: "Date of service", required: true, error: errors["service.date"], children: /* @__PURE__ */ jsx3(TextDateInput, { ariaLabel: "Date of service", value: bill.service.date, disabled: locked, required: true, onChange: (date2) => setBill((c) => ({ ...c, service: { ...c.service, date: date2 } })) }) }),
|
|
1778
|
+
/* @__PURE__ */ jsx3(Field2, { path: "service.date", label: "Date of service", required: true, error: errors["service.date"], children: /* @__PURE__ */ jsx3(TextDateInput, { ariaLabel: "Date of service", value: bill.service.date, disabled: locked, required: true, onChange: (date2) => setBill((c) => ({ ...c, service: { ...c.service, date: date2 } })) }) }),
|
|
1688
1779
|
/* @__PURE__ */ jsx3(Field2, { label: "Date of injury", children: /* @__PURE__ */ jsx3(TextDateInput, { ariaLabel: "Date of injury", value: bill.claim.dateOfInjury, disabled: locked, onChange: (dateOfInjury) => setBill((c) => ({ ...c, claim: { ...c.claim, dateOfInjury } })) }) }),
|
|
1689
1780
|
/* @__PURE__ */ jsx3(Field2, { label: "Employer (optional)", children: text(bill.claim.employer, (employer) => setBill((c) => ({ ...c, claim: { ...c.claim, employer } }))) }),
|
|
1690
|
-
/* @__PURE__ */ jsx3(Field2, { label: "Claim number", required: true, error: errors["claim.claimNumber"], children: text(bill.claim.claimNumber, (claimNumber) => setBill((c) => ({ ...c, claim: { ...c.claim, claimNumber } }))) }),
|
|
1781
|
+
/* @__PURE__ */ jsx3(Field2, { path: "claim.claimNumber", label: "Claim number", required: true, error: errors["claim.claimNumber"], children: text(bill.claim.claimNumber, (claimNumber) => setBill((c) => ({ ...c, claim: { ...c.claim, claimNumber } }))) }),
|
|
1691
1782
|
/* @__PURE__ */ jsx3(Field2, { label: "WCAB / ADJ number (optional)", children: text(bill.claim.adjNumber, (adjNumber) => setBill((c) => ({ ...c, claim: { ...c.claim, adjNumber } }))) }),
|
|
1692
|
-
/* @__PURE__ */ jsx3(Field2, { label: "Claims administrator", required: true, span: true, error:
|
|
1783
|
+
/* @__PURE__ */ jsx3(Field2, { path: "claim.claimsAdministrator", label: "Claims administrator", required: true, span: true, error: claimsAdministratorError, children: /* @__PURE__ */ jsx3(ComboBox, { ariaLabel: "Claims administrator", invalid: Boolean(claimsAdministratorError), disabled: locked, loading: payerLoading, value: bill.claim.claimsAdministrator?.name ?? "", placeholder: "Search the payer directory\u2026", options: payerResults.map((payer) => ({ id: payer.id, label: payer.name, detail: [payer.hasElectronic ? "Electronic" : "Work comp", ...payer.states ?? []].join(" \xB7 ") })), onQuery: searchPayers, onSelect: (option) => setBill((c) => ({ ...c, claim: { ...c.claim, claimsAdministrator: { id: option.id, name: option.label } } })) }) }),
|
|
1693
1784
|
/* @__PURE__ */ jsx3(Field2, { label: "Injury description (optional)", span: true, children: text(bill.claim.description, (description2) => setBill((c) => ({ ...c, claim: { ...c.claim, description: description2 } }))) }),
|
|
1694
1785
|
/* @__PURE__ */ jsxs3(Field2, { label: "Diagnosis codes (ICD-10)", span: true, children: [
|
|
1695
1786
|
/* @__PURE__ */ jsx3("div", { className: "mbsf-quick-picks", "aria-label": "Common diagnosis codes", children: BILL_SUBMISSION_DIAGNOSIS_QUICK_PICKS.map((option) => {
|
|
@@ -1701,14 +1792,16 @@ function BillSubmissionForm({
|
|
|
1701
1792
|
] }, option.code);
|
|
1702
1793
|
}) }),
|
|
1703
1794
|
/* @__PURE__ */ jsx3("div", { className: "mbsf-chips", children: (bill.diagnoses ?? []).map((code) => {
|
|
1704
|
-
const option = [...BILL_SUBMISSION_DIAGNOSIS_QUICK_PICKS, ...
|
|
1795
|
+
const option = [...BILL_SUBMISSION_DIAGNOSIS_QUICK_PICKS, ...diagnosisChoices].find((item) => item.code === code);
|
|
1705
1796
|
return /* @__PURE__ */ jsxs3("span", { className: "mbsf-chip", children: [
|
|
1706
1797
|
/* @__PURE__ */ jsx3("strong", { children: code }),
|
|
1707
1798
|
option?.description ? ` ${option.description}` : "",
|
|
1708
1799
|
/* @__PURE__ */ jsx3("button", { type: "button", "aria-label": `Remove ${code}`, onClick: () => setBill((c) => ({ ...c, diagnoses: (c.diagnoses ?? []).filter((item) => item !== code) })), children: "\xD7" })
|
|
1709
1800
|
] }, code);
|
|
1710
1801
|
}) }),
|
|
1711
|
-
/* @__PURE__ */ jsx3(ComboBox, { ariaLabel: "Add diagnosis code", disabled: locked, loading: diagnosisLoading, value: "", placeholder: bill.diagnoses?.length ?? 0 ? `${bill.diagnoses.length} selected \u2014 add more\u2026` : "Search ICD-10 codes\u2026", options:
|
|
1802
|
+
/* @__PURE__ */ jsx3(ComboBox, { ariaLabel: "Add diagnosis code", disabled: locked, loading: diagnosisLoading, loadingMore: diagnosisLoadingMore, value: "", placeholder: bill.diagnoses?.length ?? 0 ? `${bill.diagnoses.length} selected \u2014 add more\u2026` : "Search ICD-10 codes\u2026", options: diagnosisChoices.filter((item) => !(bill.diagnoses ?? []).includes(item.code)).map((item) => ({ id: item.code, label: item.code, detail: item.description })), onOpen: () => {
|
|
1803
|
+
if (diagnosisQuery !== "") loadDiagnoses("");
|
|
1804
|
+
}, onQuery: searchDiagnoses, onEndReached: () => loadDiagnoses(diagnosisQuery ?? "", true), onSelect: (option) => setBill((c) => ({ ...c, diagnoses: [.../* @__PURE__ */ new Set([...c.diagnoses ?? [], option.id])] })) })
|
|
1712
1805
|
] })
|
|
1713
1806
|
] })
|
|
1714
1807
|
] }),
|
|
@@ -1733,7 +1826,7 @@ function BillSubmissionForm({
|
|
|
1733
1826
|
/* @__PURE__ */ jsx3("p", { className: "mbsf-help", children: "Sets the evaluator/specialty modifier on medical-legal evaluation lines." }),
|
|
1734
1827
|
/* @__PURE__ */ jsx3("div", { className: "mbsf-segments", role: "group", "aria-label": "Evaluation type", children: [["qme", "QME (default)"], ["ame", "AME"], ["psych_qme", "Psych QME"]].map(([type, label]) => /* @__PURE__ */ jsx3("button", { className: "mbsf-segment", type: "button", "aria-pressed": evaluationType === type, onClick: () => changeEvaluation(type), children: label }, type)) }),
|
|
1735
1828
|
/* @__PURE__ */ jsx3("p", { className: "mbsf-help", children: evaluationType === "ame" ? "Agreed Medical Evaluator \u2014 eligible ML evaluation codes default to modifier -94." : evaluationType === "psych_qme" ? "Psychiatric QME \u2014 eligible ML evaluation codes default to modifier -96 (-95 for ML200)." : "Qualified Medical Evaluator \u2014 eligible ML evaluation codes default to modifier -95." }),
|
|
1736
|
-
/* @__PURE__ */ jsxs3("div", { className: "mbsf-lines", children: [
|
|
1829
|
+
/* @__PURE__ */ jsxs3("div", { className: "mbsf-lines", "data-field-path": "serviceLines", "data-invalid": Boolean(errors.serviceLines), children: [
|
|
1737
1830
|
/* @__PURE__ */ jsxs3("div", { className: "mbsf-line-head", children: [
|
|
1738
1831
|
/* @__PURE__ */ jsx3("span", { children: "Procedure code" }),
|
|
1739
1832
|
/* @__PURE__ */ jsx3("span", { children: "Modifiers" }),
|
|
@@ -1743,13 +1836,13 @@ function BillSubmissionForm({
|
|
|
1743
1836
|
" "
|
|
1744
1837
|
] }),
|
|
1745
1838
|
bill.serviceLines.map((line, index) => /* @__PURE__ */ jsxs3("div", { className: "mbsf-line", children: [
|
|
1746
|
-
/* @__PURE__ */ jsxs3("div", { "data-label": "Procedure code", children: [
|
|
1747
|
-
/* @__PURE__ */ jsx3(ComboBox, { ariaLabel: `Procedure code ${index + 1}`, disabled: locked, value: line.code, placeholder: "Search or enter code\u2026", options: procedures.map((item) => ({ id: item.code, label: item.code, detail: item.description })), createOption: customProcedureOption, onSelect: (option) => {
|
|
1839
|
+
/* @__PURE__ */ jsxs3("div", { "data-label": "Procedure code", "data-field-path": `serviceLines.${index}.code`, "data-invalid": Boolean(errors[`serviceLines.${index}.code`]), children: [
|
|
1840
|
+
/* @__PURE__ */ jsx3(ComboBox, { ariaLabel: `Procedure code ${index + 1}`, invalid: Boolean(errors[`serviceLines.${index}.code`]), disabled: locked, value: line.code, placeholder: "Search or enter code\u2026", options: procedures.map((item) => ({ id: item.code, label: item.code, detail: item.description })), createOption: customProcedureOption, onSelect: (option) => {
|
|
1748
1841
|
const auto = evaluationModifier(evaluationType, option.id);
|
|
1749
1842
|
setLine(index, { code: option.id, ...auto ? { modifiers: [auto, ...(line.modifiers ?? []).filter((item) => !["94", "95", "96"].includes(item.replace(/^-/, "")))] } : line.modifiers ? { modifiers: line.modifiers } : {} });
|
|
1750
1843
|
} }),
|
|
1751
1844
|
line.code ? /* @__PURE__ */ jsx3("small", { className: "mbsf-help", children: procedures.find((item) => item.code === line.code)?.description ?? "Custom CPT, HCPCS, or medical-legal code" }) : null,
|
|
1752
|
-
errors[`serviceLines.${index}.code`] ? /* @__PURE__ */ jsx3("small", { className: "mbsf-error", children: errors[`serviceLines.${index}.code`] }) : null
|
|
1845
|
+
errors[`serviceLines.${index}.code`] ? /* @__PURE__ */ jsx3("small", { className: "mbsf-error", role: "alert", children: errors[`serviceLines.${index}.code`] }) : null
|
|
1753
1846
|
] }),
|
|
1754
1847
|
/* @__PURE__ */ jsxs3("div", { "data-label": "Modifiers", children: [
|
|
1755
1848
|
/* @__PURE__ */ jsx3("div", { className: "mbsf-chips", children: (line.modifiers ?? []).map((modifier) => /* @__PURE__ */ jsxs3("span", { className: "mbsf-chip", children: [
|
|
@@ -1759,9 +1852,9 @@ function BillSubmissionForm({
|
|
|
1759
1852
|
] }, modifier)) }),
|
|
1760
1853
|
/* @__PURE__ */ jsx3(ComboBox, { ariaLabel: `Modifiers ${index + 1}`, disabled: locked, value: "", placeholder: line.modifiers?.length ?? 0 ? `${line.modifiers.length} modifier${line.modifiers.length === 1 ? "" : "s"}` : "Add modifiers\u2026", options: modifiers.filter((item) => !(line.modifiers ?? []).includes(item.code)).map((item) => ({ id: item.code, label: `\u2212${item.code}`, detail: item.description })), onSelect: (option) => setLine(index, { modifiers: [.../* @__PURE__ */ new Set([...line.modifiers ?? [], option.id])] }) })
|
|
1761
1854
|
] }),
|
|
1762
|
-
/* @__PURE__ */ jsxs3("div", { "data-label": "Units", children: [
|
|
1763
|
-
/* @__PURE__ */ jsx3("input", { className: "mbsf-input", "aria-label": `Units ${index + 1}`, type: "number", min: 1, value: line.units ?? 1, onChange: (event) => setLine(index, { units: Number(event.target.value) }) }),
|
|
1764
|
-
errors[`serviceLines.${index}.units`] ? /* @__PURE__ */ jsx3("small", { className: "mbsf-error", children: errors[`serviceLines.${index}.units`] }) : null
|
|
1855
|
+
/* @__PURE__ */ jsxs3("div", { "data-label": "Units", "data-field-path": `serviceLines.${index}.units`, "data-invalid": Boolean(errors[`serviceLines.${index}.units`]), children: [
|
|
1856
|
+
/* @__PURE__ */ jsx3("input", { className: "mbsf-input", "aria-label": `Units ${index + 1}`, "aria-invalid": Boolean(errors[`serviceLines.${index}.units`]), type: "number", min: 1, value: line.units ?? 1, onChange: (event) => setLine(index, { units: Number(event.target.value) }) }),
|
|
1857
|
+
errors[`serviceLines.${index}.units`] ? /* @__PURE__ */ jsx3("small", { className: "mbsf-error", role: "alert", children: errors[`serviceLines.${index}.units`] }) : null
|
|
1765
1858
|
] }),
|
|
1766
1859
|
/* @__PURE__ */ jsx3("div", { className: "mbsf-money", "data-label": "Allowed", children: lineCharge(line) == null ? "\u2014" : lineCharge(line).toLocaleString(void 0, { style: "currency", currency: "USD" }) }),
|
|
1767
1860
|
/* @__PURE__ */ jsx3("button", { className: "mbsf-icon-btn", type: "button", "aria-label": `Remove service line ${index + 1}`, disabled: locked || !lineHasContent(line) && index === bill.serviceLines.length - 1, onClick: () => setBill((c) => ({ ...c, serviceLines: ensureTrailingBillSubmissionLine(c.serviceLines.filter((_, itemIndex) => itemIndex !== index)) })), children: "\xD7" })
|
|
@@ -1771,41 +1864,44 @@ function BillSubmissionForm({
|
|
|
1771
1864
|
/* @__PURE__ */ jsx3("span", { children: total.toLocaleString(void 0, { style: "currency", currency: "USD" }) })
|
|
1772
1865
|
] })
|
|
1773
1866
|
] }),
|
|
1774
|
-
errors.serviceLines ? /* @__PURE__ */ jsx3("p", { className: "mbsf-error", children: errors.serviceLines }) : null
|
|
1867
|
+
errors.serviceLines ? /* @__PURE__ */ jsx3("p", { className: "mbsf-error", role: "alert", children: errors.serviceLines }) : null
|
|
1775
1868
|
] }),
|
|
1776
1869
|
/* @__PURE__ */ jsxs3("fieldset", { className: "mbsf-card", disabled: locked, children: [
|
|
1777
1870
|
/* @__PURE__ */ jsx3("legend", { className: "mbsf-legend", children: "Attachments" }),
|
|
1778
1871
|
/* @__PURE__ */ jsxs3("div", { className: "mbsf-attach-list", children: [
|
|
1779
1872
|
attachments.filter((attachment) => !removedSourceIds.includes(attachment.id)).map((attachment) => {
|
|
1780
1873
|
const auto = attachment.autoAttached || attachment.documentType === "w9";
|
|
1781
|
-
const selected = selectedIds.includes(attachment.id);
|
|
1782
1874
|
const removable = !auto && attachment.removable !== false;
|
|
1783
1875
|
return /* @__PURE__ */ jsxs3("div", { className: "mbsf-attach-row", "data-auto": auto, children: [
|
|
1784
|
-
/* @__PURE__ */ jsxs3("
|
|
1785
|
-
auto ? /* @__PURE__ */ jsx3("span", { "aria-label": "Always attached", role: "img", children: "\u2713" }) :
|
|
1876
|
+
/* @__PURE__ */ jsxs3("div", { className: "mbsf-attach-main", children: [
|
|
1877
|
+
auto ? /* @__PURE__ */ jsx3("span", { "aria-label": "Always attached", role: "img", children: "\u2713" }) : null,
|
|
1786
1878
|
/* @__PURE__ */ jsxs3("span", { className: "mbsf-file", children: [
|
|
1787
1879
|
/* @__PURE__ */ jsx3("strong", { children: attachment.fileName }),
|
|
1788
1880
|
/* @__PURE__ */ jsx3("span", { className: "mbsf-badge", children: auto ? "Auto-attached" : documentLabels[attachment.documentType] }),
|
|
1789
1881
|
/* @__PURE__ */ jsx3("span", { className: "mbsf-help", style: { display: "block" }, children: attachment.description || (auto ? "Included automatically with every bill." : documentLabels[attachment.documentType]) })
|
|
1790
1882
|
] })
|
|
1791
1883
|
] }),
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1884
|
+
/* @__PURE__ */ jsxs3("div", { className: "mbsf-attach-actions", children: [
|
|
1885
|
+
attachment.previewUrl ? /* @__PURE__ */ jsx3("a", { className: "mbsf-secondary", href: attachment.previewUrl, target: "_blank", rel: "noopener noreferrer", children: "Preview" }) : null,
|
|
1886
|
+
removable ? /* @__PURE__ */ jsx3("button", { className: "mbsf-icon-btn", type: "button", "aria-label": `Remove ${attachment.fileName}`, disabled: locked, onClick: () => {
|
|
1887
|
+
setSelectedIds((current) => current.filter((id) => id !== attachment.id));
|
|
1888
|
+
setRemovedSourceIds((current) => [.../* @__PURE__ */ new Set([...current, attachment.id])]);
|
|
1889
|
+
}, children: "\xD7" }) : null
|
|
1890
|
+
] })
|
|
1797
1891
|
] }, attachment.id);
|
|
1798
1892
|
}),
|
|
1799
1893
|
uploads.map((upload, index) => /* @__PURE__ */ jsxs3("div", { className: "mbsf-attach-row", children: [
|
|
1800
|
-
/* @__PURE__ */ jsxs3("span", { className: "mbsf-file", children: [
|
|
1894
|
+
/* @__PURE__ */ jsx3("div", { className: "mbsf-attach-main", children: /* @__PURE__ */ jsxs3("span", { className: "mbsf-file", children: [
|
|
1801
1895
|
/* @__PURE__ */ jsx3("strong", { children: upload.file.name }),
|
|
1802
1896
|
/* @__PURE__ */ jsxs3("span", { className: "mbsf-help", style: { display: "block" }, children: [
|
|
1803
1897
|
(upload.file.size / 1024 / 1024).toFixed(1),
|
|
1804
1898
|
" MB"
|
|
1805
1899
|
] })
|
|
1806
|
-
] }),
|
|
1807
|
-
/* @__PURE__ */
|
|
1808
|
-
|
|
1900
|
+
] }) }),
|
|
1901
|
+
/* @__PURE__ */ jsxs3("div", { className: "mbsf-attach-actions", children: [
|
|
1902
|
+
/* @__PURE__ */ jsx3("button", { className: "mbsf-secondary", type: "button", onClick: () => previewUploadedPdf(upload.file), children: "Preview" }),
|
|
1903
|
+
/* @__PURE__ */ jsx3("button", { className: "mbsf-icon-btn", type: "button", "aria-label": `Remove ${upload.file.name}`, onClick: () => setUploads((current) => current.filter((_, itemIndex) => itemIndex !== index)), children: "\xD7" })
|
|
1904
|
+
] })
|
|
1809
1905
|
] }, `${upload.file.name}-${index}`))
|
|
1810
1906
|
] }),
|
|
1811
1907
|
/* @__PURE__ */ jsx3("input", { ref: fileInput, hidden: true, type: "file", accept: "application/pdf,.pdf", multiple: true, onChange: (event) => {
|