@mindbill/angular 0.11.1 → 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 +13 -0
- package/fesm2022/mindbill-angular.mjs +200 -2
- package/fesm2022/mindbill-angular.mjs.map +1 -1
- package/package.json +2 -2
- package/types/mindbill-angular.d.ts +36 -4
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
|