@mindbill/angular 0.8.0 → 0.9.0

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 CHANGED
@@ -126,3 +126,17 @@ The operations surfaces consume a normalized list of bills, so they can be used
126
126
  `mindbill-status-aging-matrix` is the management view billers coming from legacy tools expect: one row per lifecycle status, one column per 0–30 / 31–60 / 61–90 / 91+ aging bucket, clickable counts with outstanding balances, and row/column totals. Every emitted cell carries `{ state, bucket, count, balance, bills }`, so a drill-down never needs a second query. Pin a custom lifecycle ordering with `[stateOrder]`; unknown states append alphabetically.
127
127
 
128
128
  `summarizeMindBillDashboard`, `buildMindBillReportRows`, `buildMindBillReportCsv`, `buildMindBillStatusAgingMatrix`, and `buildMindBillStatusAgingCsv` are also exported for custom layouts and server-side reporting.
129
+
130
+ ## Organization onboarding and billing settings
131
+
132
+ `mindbill-organization-onboarding` captures the practice identity, billing provider, locations, and W-9 once — saved straight to your MindBill organization through the browser session. Set `variant="settings"` for the compact edit-after-setup layout.
133
+
134
+ ```html
135
+ <mindbill-organization-onboarding
136
+ sessionEndpoint="/api/mindbill/session"
137
+ [appearance]="appearance"
138
+ (completed)="enableBillingFeatures()"
139
+ />
140
+ ```
141
+
142
+ The session must be minted with the optional `organization:manage` permission. Each step saves independently through idempotent upserts that never delete records created elsewhere; the review step mirrors MindBill's onboarding checklist.
@@ -1,6 +1,6 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { signal, computed, EventEmitter, effect, Output, Input, Component, Directive, inject, ChangeDetectorRef, HostListener } from '@angular/core';
3
- import { createBillLifecycleClient, createBillReferenceClient, createBillSubmissionClient } from '@mindbill/browser';
3
+ import { createBillLifecycleClient, createBillReferenceClient, createBillSubmissionClient, createOrganizationClient } from '@mindbill/browser';
4
4
  import * as i1 from '@angular/common';
5
5
  import { CommonModule } from '@angular/common';
6
6
  import * as i2 from '@angular/forms';
@@ -275,7 +275,7 @@ function ensureTrailingProcedureLine(lines) {
275
275
  : entered;
276
276
  }
277
277
 
278
- const THEME$3 = {
278
+ const THEME$4 = {
279
279
  mindbill: { a: "#238dbd", ac: "#fff", bg: "#f3f8fa", s: "#fff", t: "#203743", m: "#657982", b: "#dbe6ea", r: "14px", cr: "8px", font: "Inter,system-ui,sans-serif" },
280
280
  "qme-companion": { a: "#53b5dc", ac: "#173542", bg: "#f2f8fb", s: "#fff", t: "#1d3440", m: "#617783", b: "#d7e5eb", r: "12px", cr: "8px", font: "Inter,system-ui,sans-serif" },
281
281
  "orange-bright": { a: "#f4510b", ac: "#fff", bg: "#fffaf6", s: "#fffefd", t: "#090f1f", m: "#626a73", b: "#e7e1da", r: "16px", cr: "10px", font: "Inter,system-ui,sans-serif" },
@@ -349,7 +349,7 @@ function buildMindBillReportCsv(rows) {
349
349
  class DashboardBase {
350
350
  appearance = { preset: "mindbill" };
351
351
  get themeStyle() {
352
- const base = THEME$3[this.appearance.preset ?? "mindbill"] ?? THEME$3["mindbill"];
352
+ const base = THEME$4[this.appearance.preset ?? "mindbill"] ?? THEME$4["mindbill"];
353
353
  return {
354
354
  "--a": this.appearance.accentColor ?? base["a"], "--ac": this.appearance.accentTextColor ?? base["ac"],
355
355
  "--bg": this.appearance.backgroundColor ?? base["bg"], "--s": this.appearance.surfaceColor ?? base["s"],
@@ -512,7 +512,7 @@ function buildMindBillStatusAgingCsv(bills, stateOrder) {
512
512
  return [header, ...lines].map((line) => line.map(quote).join(",")).join("\n");
513
513
  }
514
514
 
515
- const THEME$2 = {
515
+ const THEME$3 = {
516
516
  mindbill: { a: "#238dbd", ac: "#fff", bg: "#f3f8fa", s: "#fff", t: "#203743", m: "#657982", b: "#dbe6ea", r: "14px", cr: "8px", font: "Inter,system-ui,sans-serif" },
517
517
  "qme-companion": { a: "#53b5dc", ac: "#173542", bg: "#f2f8fb", s: "#fff", t: "#1d3440", m: "#617783", b: "#d7e5eb", r: "12px", cr: "8px", font: "Inter,system-ui,sans-serif" },
518
518
  "orange-bright": { a: "#f4510b", ac: "#fff", bg: "#fffaf6", s: "#fffefd", t: "#090f1f", m: "#626a73", b: "#e7e1da", r: "16px", cr: "10px", font: "Inter,system-ui,sans-serif" },
@@ -531,7 +531,7 @@ class MindBillStatusAgingMatrixComponent {
531
531
  return buildMindBillStatusAgingMatrix(this.bills, this.stateOrder);
532
532
  }
533
533
  get themeStyle() {
534
- const base = THEME$2[this.appearance.preset ?? "mindbill"] ?? THEME$2["mindbill"];
534
+ const base = THEME$3[this.appearance.preset ?? "mindbill"] ?? THEME$3["mindbill"];
535
535
  return {
536
536
  "--a": this.appearance.accentColor ?? base["a"], "--ac": this.appearance.accentTextColor ?? base["ac"],
537
537
  "--bg": this.appearance.backgroundColor ?? base["bg"], "--s": this.appearance.surfaceColor ?? base["s"],
@@ -968,7 +968,7 @@ const QUICK_DIAGNOSES = [
968
968
  { label: "Left knee", code: "M25.562", description: "Pain in left knee" },
969
969
  { label: "Right knee", code: "M25.561", description: "Pain in right knee" },
970
970
  ];
971
- const THEME$1 = {
971
+ const THEME$2 = {
972
972
  mindbill: { a: "#238dbd", ac: "#fff", bg: "#f3f8fa", s: "#fff", t: "#203743", m: "#657982", b: "#dbe6ea", r: "14px", cr: "8px", font: "Inter,system-ui,sans-serif" },
973
973
  "qme-companion": { a: "#53b5dc", ac: "#173542", bg: "#f2f8fb", s: "#fff", t: "#1d3440", m: "#617783", b: "#d7e5eb", r: "12px", cr: "8px", font: "Inter,system-ui,sans-serif" },
974
974
  "orange-bright": { a: "#f4510b", ac: "#fff", bg: "#fffaf6", s: "#fffefd", t: "#090f1f", m: "#626a73", b: "#e7e1da", r: "16px", cr: "10px", font: "Inter,system-ui,sans-serif" },
@@ -1041,7 +1041,7 @@ class MindBillBillSubmissionComponent {
1041
1041
  this.workingAttachments = this.attachments.map((item) => ({ ...item }));
1042
1042
  }
1043
1043
  get themeStyle() {
1044
- const base = THEME$1[this.appearance.preset ?? "mindbill"] ?? THEME$1["mindbill"];
1044
+ const base = THEME$2[this.appearance.preset ?? "mindbill"] ?? THEME$2["mindbill"];
1045
1045
  return {
1046
1046
  "--a": this.appearance.accentColor ?? base["a"], "--ac": this.appearance.accentTextColor ?? base["ac"],
1047
1047
  "--bg": this.appearance.backgroundColor ?? base["bg"], "--s": this.appearance.surfaceColor ?? base["s"],
@@ -1593,7 +1593,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
1593
1593
  args: ["window:drop", ["$event"]]
1594
1594
  }] } });
1595
1595
 
1596
- const THEME = {
1596
+ const THEME$1 = {
1597
1597
  mindbill: { a: "#238dbd", ac: "#fff", b: "#dbe6ea", r: "8px", font: "Inter,system-ui,sans-serif" },
1598
1598
  "qme-companion": { a: "#53b5dc", ac: "#173542", b: "#d7e5eb", r: "8px", font: "Inter,system-ui,sans-serif" },
1599
1599
  "orange-bright": { a: "#f4510b", ac: "#fff", b: "#e7e1da", r: "10px", font: "Inter,system-ui,sans-serif" },
@@ -1611,7 +1611,7 @@ class MindBillBillingManagementButtonComponent {
1611
1611
  errorMessage = "";
1612
1612
  changeDetector = inject(ChangeDetectorRef);
1613
1613
  get themeStyle() {
1614
- const base = THEME[this.appearance.preset ?? "mindbill"] ?? THEME["mindbill"];
1614
+ const base = THEME$1[this.appearance.preset ?? "mindbill"] ?? THEME$1["mindbill"];
1615
1615
  return {
1616
1616
  "--a": this.appearance.accentColor ?? base["a"],
1617
1617
  "--ac": this.appearance.accentTextColor ?? base["ac"],
@@ -1700,9 +1700,342 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
1700
1700
  type: Output
1701
1701
  }] } });
1702
1702
 
1703
+ // Embeddable organization onboarding (INC-1470): practice identity, billing
1704
+ // provider, locations, and the W-9 captured once and saved straight to
1705
+ // MindBill through the partner browser session (organization:manage
1706
+ // permission). variant="settings" stacks every section for edit-after-setup.
1707
+ const THEME = {
1708
+ mindbill: { a: "#238dbd", ac: "#fff", bg: "#f3f8fa", s: "#fff", t: "#203743", m: "#657982", b: "#dbe6ea", r: "14px", cr: "8px", font: "Inter,system-ui,sans-serif" },
1709
+ "qme-companion": { a: "#53b5dc", ac: "#173542", bg: "#f2f8fb", s: "#fff", t: "#1d3440", m: "#617783", b: "#d7e5eb", r: "12px", cr: "8px", font: "Inter,system-ui,sans-serif" },
1710
+ "orange-bright": { a: "#f4510b", ac: "#fff", bg: "#fffaf6", s: "#fffefd", t: "#090f1f", m: "#626a73", b: "#e7e1da", r: "16px", cr: "10px", font: "Inter,system-ui,sans-serif" },
1711
+ "clinical-blue": { a: "#1677ff", ac: "#fff", bg: "#fff", s: "#fff", t: "#1f2d3d", m: "#66788a", b: "#d9e2ec", r: "8px", cr: "6px", font: "Inter,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif" },
1712
+ };
1713
+ async function fileToBase64(file) {
1714
+ const buffer = new Uint8Array(await file.arrayBuffer());
1715
+ let binary = "";
1716
+ for (const byte of buffer)
1717
+ binary += String.fromCharCode(byte);
1718
+ return btoa(binary);
1719
+ }
1720
+ class MindBillOrganizationOnboardingComponent {
1721
+ appearance = { preset: "mindbill" };
1722
+ sessionEndpoint = "/api/mindbill/session";
1723
+ getSession;
1724
+ apiBaseUrl;
1725
+ heading = "Billing setup";
1726
+ description = "Practice identity, locations, and the W-9 — saved once, used on every bill.";
1727
+ variant = "onboarding";
1728
+ saved$ = new EventEmitter();
1729
+ completed = new EventEmitter();
1730
+ organizationError = new EventEmitter();
1731
+ steps = [
1732
+ { id: "practice", label: "Practice & billing" },
1733
+ { id: "locations", label: "Locations" },
1734
+ { id: "w9", label: "W-9" },
1735
+ { id: "review", label: "Review" },
1736
+ ];
1737
+ profile = null;
1738
+ loadError = "";
1739
+ errorMessage = "";
1740
+ step = "practice";
1741
+ saving = false;
1742
+ saved = {};
1743
+ identity = { name: "", legalName: "", taxId: "", npi: "", phone: "", email: "" };
1744
+ provider = { name: "", taxId: "", npi: "", billType: "Professional", phone: "", billingStreet: "", billingCity: "", billingState: "", billingZip: "" };
1745
+ locations = [{ name: "", street: "", city: "", state: "", zip: "", isPrimary: false }];
1746
+ w9File = null;
1747
+ changeDetector = inject(ChangeDetectorRef);
1748
+ completedFired = false;
1749
+ client;
1750
+ ngOnInit() {
1751
+ this.client = createOrganizationClient({
1752
+ sessionEndpoint: this.sessionEndpoint,
1753
+ ...(this.getSession ? { getSession: this.getSession } : {}),
1754
+ ...(this.apiBaseUrl ? { apiBaseUrl: this.apiBaseUrl } : {}),
1755
+ });
1756
+ this.client.getOrganization()
1757
+ .then((profile) => { this.loadError = ""; this.adoptProfile(profile); })
1758
+ .catch((error) => {
1759
+ const failure = error instanceof Error ? error : new Error("The organization could not be loaded.");
1760
+ this.loadError = failure.message;
1761
+ this.organizationError.emit(failure);
1762
+ })
1763
+ .finally(() => this.changeDetector.markForCheck());
1764
+ }
1765
+ get themeStyle() {
1766
+ const base = THEME[this.appearance.preset ?? "mindbill"] ?? THEME["mindbill"];
1767
+ return {
1768
+ "--a": this.appearance.accentColor ?? base["a"], "--ac": this.appearance.accentTextColor ?? base["ac"],
1769
+ "--bg": this.appearance.backgroundColor ?? base["bg"], "--s": this.appearance.surfaceColor ?? base["s"],
1770
+ "--t": this.appearance.textColor ?? base["t"], "--m": this.appearance.mutedColor ?? base["m"],
1771
+ "--b": this.appearance.borderColor ?? base["b"], "--r": this.appearance.borderRadius ?? base["r"],
1772
+ "--cr": this.appearance.controlRadius ?? base["cr"], "--font": this.appearance.fontFamily ?? base["font"],
1773
+ };
1774
+ }
1775
+ adoptProfile(profile) {
1776
+ this.profile = profile;
1777
+ this.identity = {
1778
+ name: profile.practiceIdentity.name ?? this.identity.name,
1779
+ legalName: profile.practiceIdentity.legalName ?? this.identity.legalName,
1780
+ taxId: profile.practiceIdentity.taxId ?? this.identity.taxId,
1781
+ npi: profile.practiceIdentity.npi ?? this.identity.npi,
1782
+ phone: profile.practiceIdentity.phone ?? this.identity.phone,
1783
+ email: profile.practiceIdentity.email ?? this.identity.email,
1784
+ };
1785
+ if (profile.billingProviders[0])
1786
+ this.provider = { billType: "Professional", ...profile.billingProviders[0] };
1787
+ if (profile.locations.length)
1788
+ this.locations = profile.locations.map((item) => ({ ...item }));
1789
+ if (profile.onboarding.complete && !this.completedFired) {
1790
+ this.completedFired = true;
1791
+ this.completed.emit(profile);
1792
+ }
1793
+ }
1794
+ addLocation() {
1795
+ this.locations.push({ name: "", street: "", city: "", state: "", zip: "", isPrimary: false });
1796
+ }
1797
+ setPrimary(index) {
1798
+ this.locations = this.locations.map((item, itemIndex) => ({ ...item, isPrimary: itemIndex === index }));
1799
+ }
1800
+ w9Selected(event) {
1801
+ this.w9File = event.target.files?.[0] ?? null;
1802
+ }
1803
+ async run(work, stepId) {
1804
+ this.saving = true;
1805
+ this.errorMessage = "";
1806
+ try {
1807
+ const profile = await work();
1808
+ this.adoptProfile(profile);
1809
+ this.saved[stepId] = true;
1810
+ this.saved$.emit(profile);
1811
+ if (this.variant === "onboarding") {
1812
+ const order = ["practice", "locations", "w9", "review"];
1813
+ const next = order[order.indexOf(stepId) + 1];
1814
+ if (next)
1815
+ this.step = next;
1816
+ }
1817
+ }
1818
+ catch (error) {
1819
+ const failure = error instanceof Error ? error : new Error("Saving failed.");
1820
+ this.errorMessage = failure.message;
1821
+ this.organizationError.emit(failure);
1822
+ }
1823
+ finally {
1824
+ this.saving = false;
1825
+ this.changeDetector.markForCheck();
1826
+ }
1827
+ }
1828
+ savePractice() {
1829
+ void this.run(() => this.client.saveBillingProfile({
1830
+ practiceIdentity: this.identity,
1831
+ ...(this.provider.name.trim() || this.provider.taxId.trim() || this.provider.npi.trim()
1832
+ ? { billingProviders: [this.provider] }
1833
+ : {}),
1834
+ }), "practice");
1835
+ }
1836
+ saveLocations() {
1837
+ void this.run(() => this.client.saveLocations(this.locations.filter((item) => item.name.trim() || item.street.trim())), "locations");
1838
+ }
1839
+ saveW9() {
1840
+ void this.run(async () => {
1841
+ if (!this.w9File)
1842
+ throw new Error("Choose the practice W-9 PDF first.");
1843
+ return this.client.saveW9({ filename: this.w9File.name, contentBase64: await fileToBase64(this.w9File) });
1844
+ }, "w9");
1845
+ }
1846
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MindBillOrganizationOnboardingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1847
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: MindBillOrganizationOnboardingComponent, isStandalone: true, selector: "mindbill-organization-onboarding", inputs: { appearance: "appearance", sessionEndpoint: "sessionEndpoint", getSession: "getSession", apiBaseUrl: "apiBaseUrl", heading: "heading", description: "description", variant: "variant" }, outputs: { saved$: "saved$", completed: "completed", organizationError: "organizationError" }, ngImport: i0, template: `
1848
+ <section class="mbob" [ngStyle]="themeStyle">
1849
+ <div class="card">
1850
+ <header><h2>{{ heading }}</h2><p>{{ description }}</p></header>
1851
+ @if (loadError) { <div class="error" role="alert">{{ loadError }}</div> }
1852
+
1853
+ @if (variant === 'onboarding') {
1854
+ <div class="steps">
1855
+ @for (item of steps; track item.id) {
1856
+ <button type="button" [class.active]="step === item.id" [class.done]="saved[item.id]" (click)="step = item.id"><i>{{ saved[item.id] ? '✓' : '' }}</i>{{ item.label }}</button>
1857
+ }
1858
+ </div>
1859
+ }
1860
+
1861
+ @if (variant === 'settings' || step === 'practice') {
1862
+ <div class="grid">
1863
+ <label>Practice name<input [(ngModel)]="identity.name" [disabled]="saving"></label>
1864
+ <label>Legal name<input [(ngModel)]="identity.legalName" [disabled]="saving"></label>
1865
+ <label>Tax ID (EIN)<input [(ngModel)]="identity.taxId" placeholder="94-1234567" [disabled]="saving"></label>
1866
+ <label>Group NPI<input [(ngModel)]="identity.npi" [disabled]="saving"></label>
1867
+ <label>Phone<input [(ngModel)]="identity.phone" [disabled]="saving"></label>
1868
+ <label>Email<input [(ngModel)]="identity.email" [disabled]="saving"></label>
1869
+ </div>
1870
+ <h3>Billing provider (pay-to)</h3>
1871
+ <div class="grid">
1872
+ <label>Billing provider name<input [(ngModel)]="provider.name" [disabled]="saving"></label>
1873
+ <label>Billing tax ID<input [(ngModel)]="provider.taxId" [disabled]="saving"></label>
1874
+ <label>Billing NPI<input [(ngModel)]="provider.npi" [disabled]="saving"></label>
1875
+ <label>Billing phone<input [(ngModel)]="provider.phone" [disabled]="saving"></label>
1876
+ <label class="span">Street<input [(ngModel)]="provider.billingStreet" [disabled]="saving"></label>
1877
+ <label>City<input [(ngModel)]="provider.billingCity" [disabled]="saving"></label>
1878
+ <label>State<input [(ngModel)]="provider.billingState" maxlength="2" [disabled]="saving"></label>
1879
+ <label>ZIP<input [(ngModel)]="provider.billingZip" [disabled]="saving"></label>
1880
+ </div>
1881
+ <div class="actions"><span>{{ saved['practice'] ? 'Saved to MindBill.' : 'Saved once, used on every bill.' }}</span><button type="button" class="save" [disabled]="saving" (click)="savePractice()">{{ saving ? 'Saving…' : 'Save practice' }}</button></div>
1882
+ }
1883
+
1884
+ @if (variant === 'settings') { <h3>Locations</h3> }
1885
+ @if (variant === 'settings' || step === 'locations') {
1886
+ @for (location of locations; track $index; let index = $index) {
1887
+ <div class="loc">
1888
+ <label>Name<input [(ngModel)]="location.name" [disabled]="saving"></label>
1889
+ <label>Street<input [(ngModel)]="location.street" [disabled]="saving"></label>
1890
+ <label>City<input [(ngModel)]="location.city" [disabled]="saving"></label>
1891
+ <label>State<input [(ngModel)]="location.state" maxlength="2" [disabled]="saving"></label>
1892
+ <label>ZIP<input [(ngModel)]="location.zip" [disabled]="saving"></label>
1893
+ <label class="primary"><input type="radio" name="mbob-primary" [checked]="location.isPrimary === true" (change)="setPrimary(index)"> Primary</label>
1894
+ <button type="button" class="remove" aria-label="Remove location" (click)="locations.splice(index, 1)">×</button>
1895
+ </div>
1896
+ }
1897
+ <button type="button" class="add" (click)="addLocation()">+ Add location</button>
1898
+ <div class="actions"><span>{{ saved['locations'] ? 'Saved to MindBill.' : 'Where evaluations happen.' }}</span><button type="button" class="save" [disabled]="saving" (click)="saveLocations()">{{ saving ? 'Saving…' : 'Save locations' }}</button></div>
1899
+ }
1900
+
1901
+ @if (variant === 'settings') { <h3>Practice W-9</h3> }
1902
+ @if (variant === 'settings' || step === 'w9') {
1903
+ @if (profile?.w9; as w9) {
1904
+ <div class="w9-current"><div><strong>{{ w9.filename }}</strong><span> · added {{ w9.addDate }}</span></div><span>Rides on every bill automatically</span></div>
1905
+ }
1906
+ <label class="drop">
1907
+ <input type="file" accept="application/pdf,.pdf" (change)="w9Selected($event)">
1908
+ <strong>{{ w9File ? w9File.name : (profile?.w9 ? 'Replace the W-9 PDF' : 'Drop the practice W-9 PDF here, or click to choose') }}</strong>
1909
+ <span>The W-9 is auto-attached to every submission.</span>
1910
+ </label>
1911
+ <div class="actions"><span>{{ saved['w9'] ? 'Saved to MindBill.' : '' }}</span><button type="button" class="save" [disabled]="saving || !w9File" (click)="saveW9()">{{ saving ? 'Uploading…' : 'Save W-9' }}</button></div>
1912
+ }
1913
+
1914
+ @if (variant === 'settings') { <h3>Checklist</h3> }
1915
+ @if (variant === 'settings' || step === 'review') {
1916
+ <div class="check">
1917
+ @for (item of profile?.onboarding?.checklist ?? []; track item.id) {
1918
+ <div class="check-item" [class.complete]="item.complete"><i>{{ item.complete ? '✓' : '•' }}</i><span>{{ item.label }}</span></div>
1919
+ }
1920
+ @if (profile?.onboarding?.complete) { <p class="done-copy">Billing setup is complete — bills submitted from your product carry this profile automatically.</p> }
1921
+ @else { <p class="muted">Items above complete themselves as each section is saved.</p> }
1922
+ </div>
1923
+ }
1924
+
1925
+ @if (errorMessage) { <div class="error" role="alert">{{ errorMessage }}</div> }
1926
+ </div>
1927
+ </section>
1928
+ `, isInline: true, styles: [":host{display:block}.mbob{font-family:var(--font);color:var(--t);font-size:15px}.mbob *{box-sizing:border-box}.card{border:1px solid var(--b);border-radius:var(--r);background:var(--s);padding:24px}header{margin-bottom:18px}header h2{margin:0;font-size:22px}header p{margin:5px 0 0;color:var(--m)}.steps{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:20px}.steps button{display:inline-flex;align-items:center;gap:7px;border:1px solid var(--b);border-radius:999px;background:var(--s);padding:7px 13px;color:var(--m);font:inherit;font-size:13px;font-weight:700;cursor:pointer}.steps button.active{border-color:var(--a);color:var(--a);background:color-mix(in srgb,var(--a) 8%,var(--s))}.steps button.done i{color:#159447;font-style:normal}.grid{display:grid;grid-template-columns:1fr 1fr;gap:16px 22px}.span{grid-column:1/-1}label{display:grid;gap:7px;font-size:13px;font-weight:720}input:not([type=radio]):not([type=file]){width:100%;min-height:44px;border:1px solid var(--b);border-radius:var(--cr);background:var(--s);padding:10px 12px;color:var(--t);font:inherit;font-weight:450}input:focus{outline:3px solid color-mix(in srgb,var(--a) 22%,transparent);border-color:var(--a)}h3{margin:20px 0 12px;padding-top:16px;border-top:1px solid var(--b);font-size:15px}.loc{display:grid;grid-template-columns:1.2fr 1.4fr .9fr .4fr .6fr auto auto;gap:10px;align-items:end;border-top:1px solid color-mix(in srgb,var(--b) 60%,transparent);padding:12px 0}.loc:first-of-type{border-top:0}.primary{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--m);white-space:nowrap;padding-bottom:12px}.remove{border:0;background:transparent;color:var(--m);font-size:20px;cursor:pointer;padding-bottom:8px}.add{border:1px solid var(--b);border-radius:var(--cr);background:var(--s);padding:10px 14px;color:var(--t);font:inherit;font-weight:700;cursor:pointer}.drop{display:grid;place-content:center;gap:6px;min-height:150px;border:2px dashed color-mix(in srgb,var(--m) 55%,transparent);border-radius:var(--cr);text-align:center;cursor:pointer;position:relative}.drop input{position:absolute;inset:0;opacity:0;cursor:pointer}.drop span{color:var(--m);font-size:13px}.w9-current{display:flex;align-items:center;justify-content:space-between;gap:12px;border:1px solid #9fd6b4;border-radius:var(--cr);background:#f3fcf6;padding:12px 14px;margin-bottom:14px}.w9-current span{color:var(--m);font-size:13px}.check{display:grid;gap:9px}.check-item{display:flex;align-items:center;gap:10px;border:1px solid var(--b);border-radius:var(--cr);padding:11px 14px}.check-item.complete{border-color:#9fd6b4;background:#f3fcf6}.check-item i{font-style:normal}.actions{display:flex;justify-content:space-between;align-items:center;gap:14px;margin-top:20px}.actions>span{color:var(--m)}.save{min-width:170px;border:0;border-radius:var(--cr);background:var(--a);color:var(--ac);padding:12px 22px;font:inherit;font-weight:780;cursor:pointer}.save:disabled{opacity:.6;cursor:wait}.error{margin-top:14px;border-left:4px solid #c83c3c;border-radius:var(--cr);background:color-mix(in srgb,#c83c3c 8%,var(--s));padding:12px 14px;color:#c83c3c}.done-copy{color:#159447;font-weight:750}.muted{color:var(--m)}@media(max-width:820px){.grid{grid-template-columns:1fr}.span{grid-column:auto}.loc{grid-template-columns:1fr 1fr;align-items:center}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
1929
+ }
1930
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MindBillOrganizationOnboardingComponent, decorators: [{
1931
+ type: Component,
1932
+ args: [{ selector: "mindbill-organization-onboarding", standalone: true, imports: [CommonModule, FormsModule], template: `
1933
+ <section class="mbob" [ngStyle]="themeStyle">
1934
+ <div class="card">
1935
+ <header><h2>{{ heading }}</h2><p>{{ description }}</p></header>
1936
+ @if (loadError) { <div class="error" role="alert">{{ loadError }}</div> }
1937
+
1938
+ @if (variant === 'onboarding') {
1939
+ <div class="steps">
1940
+ @for (item of steps; track item.id) {
1941
+ <button type="button" [class.active]="step === item.id" [class.done]="saved[item.id]" (click)="step = item.id"><i>{{ saved[item.id] ? '✓' : '' }}</i>{{ item.label }}</button>
1942
+ }
1943
+ </div>
1944
+ }
1945
+
1946
+ @if (variant === 'settings' || step === 'practice') {
1947
+ <div class="grid">
1948
+ <label>Practice name<input [(ngModel)]="identity.name" [disabled]="saving"></label>
1949
+ <label>Legal name<input [(ngModel)]="identity.legalName" [disabled]="saving"></label>
1950
+ <label>Tax ID (EIN)<input [(ngModel)]="identity.taxId" placeholder="94-1234567" [disabled]="saving"></label>
1951
+ <label>Group NPI<input [(ngModel)]="identity.npi" [disabled]="saving"></label>
1952
+ <label>Phone<input [(ngModel)]="identity.phone" [disabled]="saving"></label>
1953
+ <label>Email<input [(ngModel)]="identity.email" [disabled]="saving"></label>
1954
+ </div>
1955
+ <h3>Billing provider (pay-to)</h3>
1956
+ <div class="grid">
1957
+ <label>Billing provider name<input [(ngModel)]="provider.name" [disabled]="saving"></label>
1958
+ <label>Billing tax ID<input [(ngModel)]="provider.taxId" [disabled]="saving"></label>
1959
+ <label>Billing NPI<input [(ngModel)]="provider.npi" [disabled]="saving"></label>
1960
+ <label>Billing phone<input [(ngModel)]="provider.phone" [disabled]="saving"></label>
1961
+ <label class="span">Street<input [(ngModel)]="provider.billingStreet" [disabled]="saving"></label>
1962
+ <label>City<input [(ngModel)]="provider.billingCity" [disabled]="saving"></label>
1963
+ <label>State<input [(ngModel)]="provider.billingState" maxlength="2" [disabled]="saving"></label>
1964
+ <label>ZIP<input [(ngModel)]="provider.billingZip" [disabled]="saving"></label>
1965
+ </div>
1966
+ <div class="actions"><span>{{ saved['practice'] ? 'Saved to MindBill.' : 'Saved once, used on every bill.' }}</span><button type="button" class="save" [disabled]="saving" (click)="savePractice()">{{ saving ? 'Saving…' : 'Save practice' }}</button></div>
1967
+ }
1968
+
1969
+ @if (variant === 'settings') { <h3>Locations</h3> }
1970
+ @if (variant === 'settings' || step === 'locations') {
1971
+ @for (location of locations; track $index; let index = $index) {
1972
+ <div class="loc">
1973
+ <label>Name<input [(ngModel)]="location.name" [disabled]="saving"></label>
1974
+ <label>Street<input [(ngModel)]="location.street" [disabled]="saving"></label>
1975
+ <label>City<input [(ngModel)]="location.city" [disabled]="saving"></label>
1976
+ <label>State<input [(ngModel)]="location.state" maxlength="2" [disabled]="saving"></label>
1977
+ <label>ZIP<input [(ngModel)]="location.zip" [disabled]="saving"></label>
1978
+ <label class="primary"><input type="radio" name="mbob-primary" [checked]="location.isPrimary === true" (change)="setPrimary(index)"> Primary</label>
1979
+ <button type="button" class="remove" aria-label="Remove location" (click)="locations.splice(index, 1)">×</button>
1980
+ </div>
1981
+ }
1982
+ <button type="button" class="add" (click)="addLocation()">+ Add location</button>
1983
+ <div class="actions"><span>{{ saved['locations'] ? 'Saved to MindBill.' : 'Where evaluations happen.' }}</span><button type="button" class="save" [disabled]="saving" (click)="saveLocations()">{{ saving ? 'Saving…' : 'Save locations' }}</button></div>
1984
+ }
1985
+
1986
+ @if (variant === 'settings') { <h3>Practice W-9</h3> }
1987
+ @if (variant === 'settings' || step === 'w9') {
1988
+ @if (profile?.w9; as w9) {
1989
+ <div class="w9-current"><div><strong>{{ w9.filename }}</strong><span> · added {{ w9.addDate }}</span></div><span>Rides on every bill automatically</span></div>
1990
+ }
1991
+ <label class="drop">
1992
+ <input type="file" accept="application/pdf,.pdf" (change)="w9Selected($event)">
1993
+ <strong>{{ w9File ? w9File.name : (profile?.w9 ? 'Replace the W-9 PDF' : 'Drop the practice W-9 PDF here, or click to choose') }}</strong>
1994
+ <span>The W-9 is auto-attached to every submission.</span>
1995
+ </label>
1996
+ <div class="actions"><span>{{ saved['w9'] ? 'Saved to MindBill.' : '' }}</span><button type="button" class="save" [disabled]="saving || !w9File" (click)="saveW9()">{{ saving ? 'Uploading…' : 'Save W-9' }}</button></div>
1997
+ }
1998
+
1999
+ @if (variant === 'settings') { <h3>Checklist</h3> }
2000
+ @if (variant === 'settings' || step === 'review') {
2001
+ <div class="check">
2002
+ @for (item of profile?.onboarding?.checklist ?? []; track item.id) {
2003
+ <div class="check-item" [class.complete]="item.complete"><i>{{ item.complete ? '✓' : '•' }}</i><span>{{ item.label }}</span></div>
2004
+ }
2005
+ @if (profile?.onboarding?.complete) { <p class="done-copy">Billing setup is complete — bills submitted from your product carry this profile automatically.</p> }
2006
+ @else { <p class="muted">Items above complete themselves as each section is saved.</p> }
2007
+ </div>
2008
+ }
2009
+
2010
+ @if (errorMessage) { <div class="error" role="alert">{{ errorMessage }}</div> }
2011
+ </div>
2012
+ </section>
2013
+ `, styles: [":host{display:block}.mbob{font-family:var(--font);color:var(--t);font-size:15px}.mbob *{box-sizing:border-box}.card{border:1px solid var(--b);border-radius:var(--r);background:var(--s);padding:24px}header{margin-bottom:18px}header h2{margin:0;font-size:22px}header p{margin:5px 0 0;color:var(--m)}.steps{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:20px}.steps button{display:inline-flex;align-items:center;gap:7px;border:1px solid var(--b);border-radius:999px;background:var(--s);padding:7px 13px;color:var(--m);font:inherit;font-size:13px;font-weight:700;cursor:pointer}.steps button.active{border-color:var(--a);color:var(--a);background:color-mix(in srgb,var(--a) 8%,var(--s))}.steps button.done i{color:#159447;font-style:normal}.grid{display:grid;grid-template-columns:1fr 1fr;gap:16px 22px}.span{grid-column:1/-1}label{display:grid;gap:7px;font-size:13px;font-weight:720}input:not([type=radio]):not([type=file]){width:100%;min-height:44px;border:1px solid var(--b);border-radius:var(--cr);background:var(--s);padding:10px 12px;color:var(--t);font:inherit;font-weight:450}input:focus{outline:3px solid color-mix(in srgb,var(--a) 22%,transparent);border-color:var(--a)}h3{margin:20px 0 12px;padding-top:16px;border-top:1px solid var(--b);font-size:15px}.loc{display:grid;grid-template-columns:1.2fr 1.4fr .9fr .4fr .6fr auto auto;gap:10px;align-items:end;border-top:1px solid color-mix(in srgb,var(--b) 60%,transparent);padding:12px 0}.loc:first-of-type{border-top:0}.primary{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--m);white-space:nowrap;padding-bottom:12px}.remove{border:0;background:transparent;color:var(--m);font-size:20px;cursor:pointer;padding-bottom:8px}.add{border:1px solid var(--b);border-radius:var(--cr);background:var(--s);padding:10px 14px;color:var(--t);font:inherit;font-weight:700;cursor:pointer}.drop{display:grid;place-content:center;gap:6px;min-height:150px;border:2px dashed color-mix(in srgb,var(--m) 55%,transparent);border-radius:var(--cr);text-align:center;cursor:pointer;position:relative}.drop input{position:absolute;inset:0;opacity:0;cursor:pointer}.drop span{color:var(--m);font-size:13px}.w9-current{display:flex;align-items:center;justify-content:space-between;gap:12px;border:1px solid #9fd6b4;border-radius:var(--cr);background:#f3fcf6;padding:12px 14px;margin-bottom:14px}.w9-current span{color:var(--m);font-size:13px}.check{display:grid;gap:9px}.check-item{display:flex;align-items:center;gap:10px;border:1px solid var(--b);border-radius:var(--cr);padding:11px 14px}.check-item.complete{border-color:#9fd6b4;background:#f3fcf6}.check-item i{font-style:normal}.actions{display:flex;justify-content:space-between;align-items:center;gap:14px;margin-top:20px}.actions>span{color:var(--m)}.save{min-width:170px;border:0;border-radius:var(--cr);background:var(--a);color:var(--ac);padding:12px 22px;font:inherit;font-weight:780;cursor:pointer}.save:disabled{opacity:.6;cursor:wait}.error{margin-top:14px;border-left:4px solid #c83c3c;border-radius:var(--cr);background:color-mix(in srgb,#c83c3c 8%,var(--s));padding:12px 14px;color:#c83c3c}.done-copy{color:#159447;font-weight:750}.muted{color:var(--m)}@media(max-width:820px){.grid{grid-template-columns:1fr}.span{grid-column:auto}.loc{grid-template-columns:1fr 1fr;align-items:center}}\n"] }]
2014
+ }], propDecorators: { appearance: [{
2015
+ type: Input
2016
+ }], sessionEndpoint: [{
2017
+ type: Input
2018
+ }], getSession: [{
2019
+ type: Input
2020
+ }], apiBaseUrl: [{
2021
+ type: Input
2022
+ }], heading: [{
2023
+ type: Input
2024
+ }], description: [{
2025
+ type: Input
2026
+ }], variant: [{
2027
+ type: Input
2028
+ }], saved$: [{
2029
+ type: Output
2030
+ }], completed: [{
2031
+ type: Output
2032
+ }], organizationError: [{
2033
+ type: Output
2034
+ }] } });
2035
+
1703
2036
  /**
1704
2037
  * Generated bundle index. Do not edit.
1705
2038
  */
1706
2039
 
1707
- export { MINDBILL_STATUS_AGING_BUCKETS, MindBillBillAgingSummaryComponent, MindBillBillLifecycleComponent, MindBillBillListComponent, MindBillBillSubmissionComponent, MindBillBillingDashboardComponent, MindBillBillingManagementButtonComponent, MindBillBillingReportComponent, MindBillComboBoxComponent, MindBillDateInputComponent, MindBillLifecycleStore, MindBillStatusAgingMatrixComponent, buildMindBillReportCsv, buildMindBillReportRows, buildMindBillStatusAgingCsv, buildMindBillStatusAgingMatrix, ensureTrailingProcedureLine, formatMindBillSubmissionDate, isEmptyProcedureLine, mindBillAgingBucket, mindBillAgingDays, mindBillCustomProcedureOption, parseMindBillSubmissionDate, summarizeMindBillDashboard };
2040
+ export { MINDBILL_STATUS_AGING_BUCKETS, MindBillBillAgingSummaryComponent, MindBillBillLifecycleComponent, MindBillBillListComponent, MindBillBillSubmissionComponent, MindBillBillingDashboardComponent, MindBillBillingManagementButtonComponent, MindBillBillingReportComponent, MindBillComboBoxComponent, MindBillDateInputComponent, MindBillLifecycleStore, MindBillOrganizationOnboardingComponent, MindBillStatusAgingMatrixComponent, buildMindBillReportCsv, buildMindBillReportRows, buildMindBillStatusAgingCsv, buildMindBillStatusAgingMatrix, ensureTrailingProcedureLine, formatMindBillSubmissionDate, isEmptyProcedureLine, mindBillAgingBucket, mindBillAgingDays, mindBillCustomProcedureOption, parseMindBillSubmissionDate, summarizeMindBillDashboard };
1708
2041
  //# sourceMappingURL=mindbill-angular.mjs.map