@mindbill/angular 0.11.2 → 0.12.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
@@ -153,6 +153,19 @@ The operations surfaces consume a normalized list of bills, so they can be used
153
153
 
154
154
  `summarizeMindBillDashboard`, `buildMindBillReportRows`, `buildMindBillReportCsv`, `buildMindBillStatusAgingMatrix`, and `buildMindBillStatusAgingCsv` are also exported for custom layouts and server-side reporting.
155
155
 
156
+ `mindbill-bill-tasks-dashboard` is the daisyBill-style Bill Tasks worklist: one tone-colored card per task section, rows bucketed by age in days with clickable counts, and a grand-total card. Aggregate your own work items with `buildBillTasksDashboard` from `@mindbill/browser` (types re-exported here) and pass the result as `[data]`:
157
+
158
+ ```html
159
+ <mindbill-bill-tasks-dashboard
160
+ [data]="billTasks"
161
+ heading="Bill Tasks"
162
+ [appearance]="appearance"
163
+ (cellSelected)="openWorklist($event)"
164
+ />
165
+ ```
166
+
167
+ Each emitted cell carries `{ sectionId, rowId, bucketId, refs, count }`, where `bucketId` is `null` for a row's Task Total column and `refs` are the bill references collected by the builder.
168
+
156
169
  ## Organization onboarding and billing settings
157
170
 
158
171
  `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.
@@ -1,6 +1,6 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { signal, computed, Input, Component, EventEmitter, effect, Output, Directive, inject, ChangeDetectorRef, HostListener } from '@angular/core';
3
- import { createBillLifecycleClient, createBillReferenceClient, createBillSubmissionClient, createOrganizationClient } from '@mindbill/browser';
3
+ import { createBillLifecycleClient, BILL_TASKS_AGING_BUCKETS, 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';
@@ -817,6 +817,204 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
817
817
  type: Output
818
818
  }] } });
819
819
 
820
+ const MINDBILL_BILL_TASKS_TONES = {
821
+ violet: "#7c53c3",
822
+ red: "#cf4437",
823
+ blue: "#2f7fd1",
824
+ green: "#2c9a5b",
825
+ amber: "#d9931f",
826
+ neutral: "#8195a1",
827
+ };
828
+ const MINDBILL_BILL_TASKS_PILL_BASES = ["#2c9a5b", "#1f9aa8", "#dcbb28", "#e08a2e", "#d75d8a"];
829
+ function mindBillBillTasksTone(tone) {
830
+ return MINDBILL_BILL_TASKS_TONES[tone] ?? MINDBILL_BILL_TASKS_TONES.neutral;
831
+ }
832
+ function mindBillBillTasksPillBase(index) {
833
+ return MINDBILL_BILL_TASKS_PILL_BASES[index % MINDBILL_BILL_TASKS_PILL_BASES.length];
834
+ }
835
+ /** Click-through payload for a bucket cell (bucketIndex) or a row total (null). */
836
+ function mindBillBillTasksCell(section, row, bucketIndex, buckets) {
837
+ if (bucketIndex === null) {
838
+ return {
839
+ sectionId: section.id,
840
+ rowId: row.id,
841
+ bucketId: null,
842
+ refs: row.refs.flat(),
843
+ count: row.total,
844
+ };
845
+ }
846
+ return {
847
+ sectionId: section.id,
848
+ rowId: row.id,
849
+ bucketId: buckets[bucketIndex]?.id ?? null,
850
+ refs: row.refs[bucketIndex] ?? [],
851
+ count: row.counts[bucketIndex] ?? 0,
852
+ };
853
+ }
854
+
855
+ // daisyBill-style "Bill Tasks" dashboard: one rounded, tone-colored card per
856
+ // task section, rows bucketed by age in days with colored bucket header pills,
857
+ // per-cell click-through counts, and a closing grand-total card. The pure
858
+ // aggregation lives in @mindbill/browser (buildBillTasksDashboard) so React
859
+ // and Angular render the exact same data shape.
860
+ class MindBillBillTasksDashboardComponent {
861
+ data = null;
862
+ buckets = BILL_TASKS_AGING_BUCKETS;
863
+ heading = "";
864
+ grandTotalLabel = "Bill Tasks Total";
865
+ emptyLabel = "No Tasks";
866
+ appearance = { preset: "mindbill" };
867
+ cellSelected = new EventEmitter();
868
+ get themeStyle() {
869
+ return {
870
+ ...mindBillAngularAppearanceStyle(this.appearance),
871
+ "--cols": `minmax(150px,1.6fr) repeat(${this.buckets.length},minmax(84px,1fr)) minmax(84px,1fr)`,
872
+ };
873
+ }
874
+ tone(section) {
875
+ return mindBillBillTasksTone(section.tone);
876
+ }
877
+ pillBackground(index) {
878
+ return `color-mix(in srgb,${mindBillBillTasksPillBase(index)} 20%,var(--s))`;
879
+ }
880
+ pillColor(index) {
881
+ return `color-mix(in srgb,${mindBillBillTasksPillBase(index)} 62%,var(--t))`;
882
+ }
883
+ select(section, row, bucketIndex) {
884
+ this.cellSelected.emit(mindBillBillTasksCell(section, row, bucketIndex, this.buckets));
885
+ }
886
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MindBillBillTasksDashboardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
887
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: MindBillBillTasksDashboardComponent, isStandalone: true, selector: "mindbill-bill-tasks-dashboard", inputs: { data: "data", buckets: "buckets", heading: "heading", grandTotalLabel: "grandTotalLabel", emptyLabel: "emptyLabel", appearance: "appearance" }, outputs: { cellSelected: "cellSelected" }, ngImport: i0, template: `
888
+ <div class="mbtk" [ngStyle]="themeStyle">
889
+ @if (heading) { <header class="heading"><h2>{{ heading }}</h2></header> }
890
+ @for (section of data?.sections ?? []; track section.id) {
891
+ <section class="card" [style.borderColor]="tone(section)" [attr.aria-label]="section.label">
892
+ <div class="grid head" [style.borderBottomColor]="tone(section)">
893
+ <span class="title">
894
+ <i class="dot" [style.background]="tone(section)"></i>
895
+ <span class="title-text"><strong>{{ section.label }}</strong><span class="basis">by {{ section.agingBasisLabel }}</span></span>
896
+ </span>
897
+ @for (bucket of buckets; track bucket.id; let bucketIndex = $index) {
898
+ <span class="pill" [style.background]="pillBackground(bucketIndex)" [style.color]="pillColor(bucketIndex)">{{ bucket.label }}</span>
899
+ }
900
+ <span class="colhead">Task Total</span>
901
+ </div>
902
+ @if (section.empty) {
903
+ <p class="empty">{{ emptyLabel }}</p>
904
+ } @else {
905
+ @for (row of section.rows; track row.id) {
906
+ <div class="grid row">
907
+ <span class="rowlabel">{{ row.label }}</span>
908
+ @for (bucket of buckets; track bucket.id; let bucketIndex = $index) {
909
+ <span class="count">
910
+ @if ((row.counts[bucketIndex] ?? 0) > 0) {
911
+ <button type="button" class="link" (click)="select(section, row, bucketIndex)">{{ row.counts[bucketIndex] }}</button>
912
+ } @else { <span class="zero">0</span> }
913
+ </span>
914
+ }
915
+ <span class="total">
916
+ @if (row.total > 0) {
917
+ <button type="button" class="link" (click)="select(section, row, null)">{{ row.total }}</button>
918
+ } @else { <span class="zero">0</span> }
919
+ </span>
920
+ </div>
921
+ }
922
+ <div class="grid row totals">
923
+ <span class="rowlabel">Total</span>
924
+ @for (bucket of buckets; track bucket.id; let bucketIndex = $index) {
925
+ <span class="count">{{ section.totals[bucketIndex] ?? 0 }}</span>
926
+ }
927
+ <span class="total">{{ section.total }}</span>
928
+ </div>
929
+ }
930
+ </section>
931
+ }
932
+ <section class="card grand" [attr.aria-label]="grandTotalLabel">
933
+ <div class="grid row totals">
934
+ <span class="rowlabel grand-label">{{ grandTotalLabel }}</span>
935
+ @for (bucket of buckets; track bucket.id; let bucketIndex = $index) {
936
+ <span class="count">{{ data?.grandTotals?.[bucketIndex] ?? 0 }}</span>
937
+ }
938
+ <span class="total">{{ data?.grandTotal ?? 0 }}</span>
939
+ </div>
940
+ </section>
941
+ </div>
942
+ `, isInline: true, styles: [":host{display:block}.mbtk{color:var(--t);font-family:var(--font);font-size:14px}.heading{margin-bottom:14px}.heading h2{margin:0;font-size:24px}.card{margin-bottom:16px;border:1.5px solid var(--b);border-radius:var(--r);background:var(--s);overflow:hidden}.grid{display:grid;grid-template-columns:var(--cols);gap:0 10px;align-items:center;padding:0 16px}.head{padding:12px 16px;border-bottom:1px solid var(--b)}.title{display:flex;align-items:center;gap:9px;min-width:0}.dot{flex:0 0 auto;width:11px;height:11px;border-radius:50%}.title-text{min-width:0}.title-text strong{display:block;font-size:15.5px;line-height:1.3}.basis{display:block;color:var(--m);font-size:12px;line-height:1.35}.pill{justify-self:stretch;padding:5px 6px;border-radius:999px;font-size:11px;font-weight:750;line-height:1.25;text-align:center;white-space:nowrap}.colhead{justify-self:stretch;color:var(--m);font-size:11px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;text-align:center;white-space:nowrap}.row{border-top:1px solid color-mix(in srgb,var(--b) 60%,transparent);min-height:44px}.row:first-of-type{border-top:0}.rowlabel{color:var(--m);font-size:13px;line-height:1.35;padding:9px 0;overflow-wrap:anywhere}.count,.total{text-align:center;font-variant-numeric:tabular-nums}.zero{color:var(--m)}.link{border:0;background:none;padding:6px 8px;color:var(--a);font:inherit;font-weight:750;font-variant-numeric:tabular-nums;cursor:pointer;border-radius:6px}.link:hover{text-decoration:underline;background:color-mix(in srgb,var(--a) 8%,transparent)}.totals{background:color-mix(in srgb,var(--bg) 78%,var(--s));border-top:1px solid var(--b);font-weight:760}.totals .rowlabel{color:var(--t);font-weight:760}.empty{margin:0;padding:22px 16px;color:var(--m);text-align:center}.grand{border-color:var(--b)}.grand .row{min-height:52px}.grand-label{font-size:14px;font-weight:800}@media(max-width:760px){.pill,.colhead,.count{display:none}.grid{grid-template-columns:minmax(0,1fr) auto}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
943
+ }
944
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MindBillBillTasksDashboardComponent, decorators: [{
945
+ type: Component,
946
+ args: [{ selector: "mindbill-bill-tasks-dashboard", standalone: true, imports: [CommonModule], template: `
947
+ <div class="mbtk" [ngStyle]="themeStyle">
948
+ @if (heading) { <header class="heading"><h2>{{ heading }}</h2></header> }
949
+ @for (section of data?.sections ?? []; track section.id) {
950
+ <section class="card" [style.borderColor]="tone(section)" [attr.aria-label]="section.label">
951
+ <div class="grid head" [style.borderBottomColor]="tone(section)">
952
+ <span class="title">
953
+ <i class="dot" [style.background]="tone(section)"></i>
954
+ <span class="title-text"><strong>{{ section.label }}</strong><span class="basis">by {{ section.agingBasisLabel }}</span></span>
955
+ </span>
956
+ @for (bucket of buckets; track bucket.id; let bucketIndex = $index) {
957
+ <span class="pill" [style.background]="pillBackground(bucketIndex)" [style.color]="pillColor(bucketIndex)">{{ bucket.label }}</span>
958
+ }
959
+ <span class="colhead">Task Total</span>
960
+ </div>
961
+ @if (section.empty) {
962
+ <p class="empty">{{ emptyLabel }}</p>
963
+ } @else {
964
+ @for (row of section.rows; track row.id) {
965
+ <div class="grid row">
966
+ <span class="rowlabel">{{ row.label }}</span>
967
+ @for (bucket of buckets; track bucket.id; let bucketIndex = $index) {
968
+ <span class="count">
969
+ @if ((row.counts[bucketIndex] ?? 0) > 0) {
970
+ <button type="button" class="link" (click)="select(section, row, bucketIndex)">{{ row.counts[bucketIndex] }}</button>
971
+ } @else { <span class="zero">0</span> }
972
+ </span>
973
+ }
974
+ <span class="total">
975
+ @if (row.total > 0) {
976
+ <button type="button" class="link" (click)="select(section, row, null)">{{ row.total }}</button>
977
+ } @else { <span class="zero">0</span> }
978
+ </span>
979
+ </div>
980
+ }
981
+ <div class="grid row totals">
982
+ <span class="rowlabel">Total</span>
983
+ @for (bucket of buckets; track bucket.id; let bucketIndex = $index) {
984
+ <span class="count">{{ section.totals[bucketIndex] ?? 0 }}</span>
985
+ }
986
+ <span class="total">{{ section.total }}</span>
987
+ </div>
988
+ }
989
+ </section>
990
+ }
991
+ <section class="card grand" [attr.aria-label]="grandTotalLabel">
992
+ <div class="grid row totals">
993
+ <span class="rowlabel grand-label">{{ grandTotalLabel }}</span>
994
+ @for (bucket of buckets; track bucket.id; let bucketIndex = $index) {
995
+ <span class="count">{{ data?.grandTotals?.[bucketIndex] ?? 0 }}</span>
996
+ }
997
+ <span class="total">{{ data?.grandTotal ?? 0 }}</span>
998
+ </div>
999
+ </section>
1000
+ </div>
1001
+ `, styles: [":host{display:block}.mbtk{color:var(--t);font-family:var(--font);font-size:14px}.heading{margin-bottom:14px}.heading h2{margin:0;font-size:24px}.card{margin-bottom:16px;border:1.5px solid var(--b);border-radius:var(--r);background:var(--s);overflow:hidden}.grid{display:grid;grid-template-columns:var(--cols);gap:0 10px;align-items:center;padding:0 16px}.head{padding:12px 16px;border-bottom:1px solid var(--b)}.title{display:flex;align-items:center;gap:9px;min-width:0}.dot{flex:0 0 auto;width:11px;height:11px;border-radius:50%}.title-text{min-width:0}.title-text strong{display:block;font-size:15.5px;line-height:1.3}.basis{display:block;color:var(--m);font-size:12px;line-height:1.35}.pill{justify-self:stretch;padding:5px 6px;border-radius:999px;font-size:11px;font-weight:750;line-height:1.25;text-align:center;white-space:nowrap}.colhead{justify-self:stretch;color:var(--m);font-size:11px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;text-align:center;white-space:nowrap}.row{border-top:1px solid color-mix(in srgb,var(--b) 60%,transparent);min-height:44px}.row:first-of-type{border-top:0}.rowlabel{color:var(--m);font-size:13px;line-height:1.35;padding:9px 0;overflow-wrap:anywhere}.count,.total{text-align:center;font-variant-numeric:tabular-nums}.zero{color:var(--m)}.link{border:0;background:none;padding:6px 8px;color:var(--a);font:inherit;font-weight:750;font-variant-numeric:tabular-nums;cursor:pointer;border-radius:6px}.link:hover{text-decoration:underline;background:color-mix(in srgb,var(--a) 8%,transparent)}.totals{background:color-mix(in srgb,var(--bg) 78%,var(--s));border-top:1px solid var(--b);font-weight:760}.totals .rowlabel{color:var(--t);font-weight:760}.empty{margin:0;padding:22px 16px;color:var(--m);text-align:center}.grand{border-color:var(--b)}.grand .row{min-height:52px}.grand-label{font-size:14px;font-weight:800}@media(max-width:760px){.pill,.colhead,.count{display:none}.grid{grid-template-columns:minmax(0,1fr) auto}}\n"] }]
1002
+ }], propDecorators: { data: [{
1003
+ type: Input
1004
+ }], buckets: [{
1005
+ type: Input
1006
+ }], heading: [{
1007
+ type: Input
1008
+ }], grandTotalLabel: [{
1009
+ type: Input
1010
+ }], emptyLabel: [{
1011
+ type: Input
1012
+ }], appearance: [{
1013
+ type: Input
1014
+ }], cellSelected: [{
1015
+ type: Output
1016
+ }] } });
1017
+
820
1018
  /** Date helpers shared by the Angular submission surfaces. Mirrors the React
821
1019
  * package's parseBillSubmissionDate/formatBillSubmissionDate exactly so both
822
1020
  * frameworks accept and render the same values. */
@@ -2234,5 +2432,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
2234
2432
  * Generated bundle index. Do not edit.
2235
2433
  */
2236
2434
 
2237
- export { MINDBILL_STATUS_AGING_BUCKETS, MindBillBillAgingSummaryComponent, MindBillBillLifecycleComponent, MindBillBillListComponent, MindBillBillRejectionNoticeComponent, MindBillBillSubmissionComponent, MindBillBillingDashboardComponent, MindBillBillingManagementButtonComponent, MindBillBillingReportComponent, MindBillComboBoxComponent, MindBillDateInputComponent, MindBillLifecycleStore, MindBillOrganizationOnboardingComponent, MindBillStatusAgingMatrixComponent, billRejectionIssueSummary, billRejectionIssues, buildMindBillReportCsv, buildMindBillReportRows, buildMindBillStatusAgingCsv, buildMindBillStatusAgingMatrix, ensureTrailingProcedureLine, formatMindBillSubmissionDate, isEmptyProcedureLine, mindBillAgingBucket, mindBillAgingDays, mindBillAngularAppearanceStyle, mindBillCustomProcedureOption, parseMindBillSubmissionDate, summarizeMindBillDashboard };
2435
+ export { MINDBILL_BILL_TASKS_PILL_BASES, MINDBILL_BILL_TASKS_TONES, MINDBILL_STATUS_AGING_BUCKETS, MindBillBillAgingSummaryComponent, MindBillBillLifecycleComponent, MindBillBillListComponent, MindBillBillRejectionNoticeComponent, MindBillBillSubmissionComponent, MindBillBillTasksDashboardComponent, MindBillBillingDashboardComponent, MindBillBillingManagementButtonComponent, MindBillBillingReportComponent, MindBillComboBoxComponent, MindBillDateInputComponent, MindBillLifecycleStore, MindBillOrganizationOnboardingComponent, MindBillStatusAgingMatrixComponent, billRejectionIssueSummary, billRejectionIssues, buildMindBillReportCsv, buildMindBillReportRows, buildMindBillStatusAgingCsv, buildMindBillStatusAgingMatrix, ensureTrailingProcedureLine, formatMindBillSubmissionDate, isEmptyProcedureLine, mindBillAgingBucket, mindBillAgingDays, mindBillAngularAppearanceStyle, mindBillBillTasksCell, mindBillBillTasksPillBase, mindBillBillTasksTone, mindBillCustomProcedureOption, parseMindBillSubmissionDate, summarizeMindBillDashboard };
2238
2436
  //# sourceMappingURL=mindbill-angular.mjs.map