@mj-biz-apps/contracts-ng 0.1.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.
Files changed (94) hide show
  1. package/LICENSE +183 -0
  2. package/dist/lib/data/entity-names.d.ts +29 -0
  3. package/dist/lib/data/entity-names.d.ts.map +1 -0
  4. package/dist/lib/data/entity-names.js +29 -0
  5. package/dist/lib/data/entity-names.js.map +1 -0
  6. package/dist/lib/data/provider.d.ts +44 -0
  7. package/dist/lib/data/provider.d.ts.map +1 -0
  8. package/dist/lib/data/provider.js +48 -0
  9. package/dist/lib/data/provider.js.map +1 -0
  10. package/dist/lib/directives/fk-navigate.directive.d.ts +63 -0
  11. package/dist/lib/directives/fk-navigate.directive.d.ts.map +1 -0
  12. package/dist/lib/directives/fk-navigate.directive.js +99 -0
  13. package/dist/lib/directives/fk-navigate.directive.js.map +1 -0
  14. package/dist/lib/form-panels/contract.panels.d.ts +124 -0
  15. package/dist/lib/form-panels/contract.panels.d.ts.map +1 -0
  16. package/dist/lib/form-panels/contract.panels.js +1167 -0
  17. package/dist/lib/form-panels/contract.panels.js.map +1 -0
  18. package/dist/lib/form-panels/organization.panels.d.ts +23 -0
  19. package/dist/lib/form-panels/organization.panels.d.ts.map +1 -0
  20. package/dist/lib/form-panels/organization.panels.js +118 -0
  21. package/dist/lib/form-panels/organization.panels.js.map +1 -0
  22. package/dist/lib/form-panels/record-files.panel.d.ts +151 -0
  23. package/dist/lib/form-panels/record-files.panel.d.ts.map +1 -0
  24. package/dist/lib/form-panels/record-files.panel.js +1046 -0
  25. package/dist/lib/form-panels/record-files.panel.js.map +1 -0
  26. package/dist/lib/form-panels/supersede.panel.d.ts +68 -0
  27. package/dist/lib/form-panels/supersede.panel.d.ts.map +1 -0
  28. package/dist/lib/form-panels/supersede.panel.js +415 -0
  29. package/dist/lib/form-panels/supersede.panel.js.map +1 -0
  30. package/dist/lib/generated/Entities/mjBizAppsContractsContract/mjbizappscontractscontract.form.component.d.ts +10 -0
  31. package/dist/lib/generated/Entities/mjBizAppsContractsContract/mjbizappscontractscontract.form.component.d.ts.map +1 -0
  32. package/dist/lib/generated/Entities/mjBizAppsContractsContract/mjbizappscontractscontract.form.component.js +196 -0
  33. package/dist/lib/generated/Entities/mjBizAppsContractsContract/mjbizappscontractscontract.form.component.js.map +1 -0
  34. package/dist/lib/generated/Entities/mjBizAppsContractsContractTemplate/mjbizappscontractscontracttemplate.form.component.d.ts +10 -0
  35. package/dist/lib/generated/Entities/mjBizAppsContractsContractTemplate/mjbizappscontractscontracttemplate.form.component.d.ts.map +1 -0
  36. package/dist/lib/generated/Entities/mjBizAppsContractsContractTemplate/mjbizappscontractscontracttemplate.form.component.js +120 -0
  37. package/dist/lib/generated/Entities/mjBizAppsContractsContractTemplate/mjbizappscontractscontracttemplate.form.component.js.map +1 -0
  38. package/dist/lib/generated/Entities/mjBizAppsContractsContractTemplateModification/mjbizappscontractscontracttemplatemodification.form.component.d.ts +10 -0
  39. package/dist/lib/generated/Entities/mjBizAppsContractsContractTemplateModification/mjbizappscontractscontracttemplatemodification.form.component.d.ts.map +1 -0
  40. package/dist/lib/generated/Entities/mjBizAppsContractsContractTemplateModification/mjbizappscontractscontracttemplatemodification.form.component.js +79 -0
  41. package/dist/lib/generated/Entities/mjBizAppsContractsContractTemplateModification/mjbizappscontractscontracttemplatemodification.form.component.js.map +1 -0
  42. package/dist/lib/generated/Entities/mjBizAppsContractsContractTemplateProvision/mjbizappscontractscontracttemplateprovision.form.component.d.ts +10 -0
  43. package/dist/lib/generated/Entities/mjBizAppsContractsContractTemplateProvision/mjbizappscontractscontracttemplateprovision.form.component.d.ts.map +1 -0
  44. package/dist/lib/generated/Entities/mjBizAppsContractsContractTemplateProvision/mjbizappscontractscontracttemplateprovision.form.component.js +79 -0
  45. package/dist/lib/generated/Entities/mjBizAppsContractsContractTemplateProvision/mjbizappscontractscontracttemplateprovision.form.component.js.map +1 -0
  46. package/dist/lib/generated/Entities/mjBizAppsContractsContractTemplateType/mjbizappscontractscontracttemplatetype.form.component.d.ts +10 -0
  47. package/dist/lib/generated/Entities/mjBizAppsContractsContractTemplateType/mjbizappscontractscontracttemplatetype.form.component.d.ts.map +1 -0
  48. package/dist/lib/generated/Entities/mjBizAppsContractsContractTemplateType/mjbizappscontractscontracttemplatetype.form.component.js +86 -0
  49. package/dist/lib/generated/Entities/mjBizAppsContractsContractTemplateType/mjbizappscontractscontracttemplatetype.form.component.js.map +1 -0
  50. package/dist/lib/generated/Entities/mjBizAppsContractsContractType/mjbizappscontractscontracttype.form.component.d.ts +10 -0
  51. package/dist/lib/generated/Entities/mjBizAppsContractsContractType/mjbizappscontractscontracttype.form.component.d.ts.map +1 -0
  52. package/dist/lib/generated/Entities/mjBizAppsContractsContractType/mjbizappscontractscontracttype.form.component.js +100 -0
  53. package/dist/lib/generated/Entities/mjBizAppsContractsContractType/mjbizappscontractscontracttype.form.component.js.map +1 -0
  54. package/dist/lib/generated/generated-forms.module.d.ts +23 -0
  55. package/dist/lib/generated/generated-forms.module.d.ts.map +1 -0
  56. package/dist/lib/generated/generated-forms.module.js +78 -0
  57. package/dist/lib/generated/generated-forms.module.js.map +1 -0
  58. package/dist/lib/pages/configuration.page.d.ts +83 -0
  59. package/dist/lib/pages/configuration.page.d.ts.map +1 -0
  60. package/dist/lib/pages/configuration.page.js +240 -0
  61. package/dist/lib/pages/configuration.page.js.map +1 -0
  62. package/dist/lib/pages/contract-grid.page.d.ts +169 -0
  63. package/dist/lib/pages/contract-grid.page.d.ts.map +1 -0
  64. package/dist/lib/pages/contract-grid.page.js +487 -0
  65. package/dist/lib/pages/contract-grid.page.js.map +1 -0
  66. package/dist/lib/pages/contracts-dashboard.page.d.ts +56 -0
  67. package/dist/lib/pages/contracts-dashboard.page.d.ts.map +1 -0
  68. package/dist/lib/pages/contracts-dashboard.page.js +215 -0
  69. package/dist/lib/pages/contracts-dashboard.page.js.map +1 -0
  70. package/dist/lib/pages/modifications.page.d.ts +96 -0
  71. package/dist/lib/pages/modifications.page.d.ts.map +1 -0
  72. package/dist/lib/pages/modifications.page.js +243 -0
  73. package/dist/lib/pages/modifications.page.js.map +1 -0
  74. package/dist/lib/pages/templates.page.d.ts +120 -0
  75. package/dist/lib/pages/templates.page.d.ts.map +1 -0
  76. package/dist/lib/pages/templates.page.js +258 -0
  77. package/dist/lib/pages/templates.page.js.map +1 -0
  78. package/dist/lib/sections/contracts-sections.component.d.ts +112 -0
  79. package/dist/lib/sections/contracts-sections.component.d.ts.map +1 -0
  80. package/dist/lib/sections/contracts-sections.component.js +412 -0
  81. package/dist/lib/sections/contracts-sections.component.js.map +1 -0
  82. package/dist/lib/sections/section-nav.model.d.ts +78 -0
  83. package/dist/lib/sections/section-nav.model.d.ts.map +1 -0
  84. package/dist/lib/sections/section-nav.model.js +110 -0
  85. package/dist/lib/sections/section-nav.model.js.map +1 -0
  86. package/dist/lib/sections/section-shell.component.d.ts +73 -0
  87. package/dist/lib/sections/section-shell.component.d.ts.map +1 -0
  88. package/dist/lib/sections/section-shell.component.js +170 -0
  89. package/dist/lib/sections/section-shell.component.js.map +1 -0
  90. package/dist/public-api.d.ts +44 -0
  91. package/dist/public-api.d.ts.map +1 -0
  92. package/dist/public-api.js +105 -0
  93. package/dist/public-api.js.map +1 -0
  94. package/package.json +50 -0
@@ -0,0 +1,1046 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ /**
8
+ * @fileoverview `Documents` — the record-scoped file panel MJ does not have yet.
9
+ *
10
+ * CARRIED FORWARD FROM V1 (plan §6.5). The rebuild deleted every other hand-written component in
11
+ * this package; this one survived because the gap it fills is MJ's, not v1's, and it never knew
12
+ * anything about the billing schema. Only its registrations changed — see the bottom of the file.
13
+ *
14
+ * THE GAP THIS FILLS. MJ models record↔file linking properly: `__mj.FileEntityRecordLink` is a
15
+ * polymorphic `EntityID` + `RecordID` pair, so any record can carry any number of files. What it
16
+ * does NOT ship is a way to SEE them on a record. `@memberjunction/ng-file-storage` has
17
+ * `mj-file-browser`, `mj-files-grid` and `mj-files-file-upload`, but all three are **category**
18
+ * scoped; nothing in the MJ Angular tree queries `FileEntityRecordLink` at runtime (the only
19
+ * references are the generated CRUD forms for the link entity itself). So "the files on THIS
20
+ * record" had no UI at all.
21
+ *
22
+ * WHY IT IS A PANEL AND NOT A TAB. `BaseFormPanel` is MJ's own extension point: a panel registers
23
+ * for an entity + slot and the form mounts it automatically, with no edit to the form and nothing
24
+ * for CodeGen to overwrite. That means this panel works identically on every GENERATED form it
25
+ * registers for — and it keeps working when they are regenerated, which is the whole reason v2
26
+ * stopped replacing forms and started contributing panels to them (plan §6.4).
27
+ *
28
+ * BUILT DONATION-SHAPED, ON PURPOSE. Nothing below knows what a contract is. The logic reads
29
+ * `Record.EntityInfo.ID` and `Record.PrimaryKey`, so it is already entity-agnostic — the only
30
+ * contracts-specific thing in the file is *which entities it registers for*, isolated into the
31
+ * one-line subclasses at the bottom. To donate this to MJ base, move the base class and
32
+ * re-register it once with `entity: '*'` (PANELS.md's wildcard); the only new requirement then is
33
+ * self-hiding on forms that have no files, which `HasFiles` already answers.
34
+ *
35
+ * WHY NOT WILDCARD HERE AND NOW: a wildcard panel must render nothing on the ~99% of forms it does
36
+ * not apply to, but a panel that hides when empty can never accept the FIRST file. Registering
37
+ * explicitly for the two entities that carry paper keeps the attach affordance available while
38
+ * staying honest about clutter. That tension is the real design question to settle before donating.
39
+ *
40
+ * @module @mj-biz-apps/contracts-ng
41
+ */
42
+ import { ChangeDetectorRef, Component, inject } from '@angular/core';
43
+ import { CommonModule } from '@angular/common';
44
+ import { FormsModule } from '@angular/forms';
45
+ import { RegisterClassEx } from '@memberjunction/global';
46
+ import { BaseFormPanel, BaseFormsModule } from '@memberjunction/ng-base-forms';
47
+ import { Metadata } from '@memberjunction/core';
48
+ import { FileOpenService } from '@memberjunction/ng-file-storage';
49
+ import { GraphQLFileStorageClient } from '@memberjunction/graphql-dataprovider';
50
+ import { ScopedRunView } from '../data/provider';
51
+ import * as i0 from "@angular/core";
52
+ import * as i1 from "@angular/common";
53
+ import * as i2 from "@angular/forms";
54
+ import * as i3 from "@memberjunction/ng-base-forms";
55
+ function RecordFilesPanelBase_div_2_div_1_button_4_Template(rf, ctx) { if (rf & 1) {
56
+ const _r1 = i0.ɵɵgetCurrentView();
57
+ i0.ɵɵelementStart(0, "button", 13);
58
+ i0.ɵɵlistener("click", function RecordFilesPanelBase_div_2_div_1_button_4_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r1); const f_r2 = i0.ɵɵnextContext().$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.Open(f_r2)); });
59
+ i0.ɵɵtext(1, "Open");
60
+ i0.ɵɵelementEnd();
61
+ } if (rf & 2) {
62
+ const ctx_r2 = i0.ɵɵnextContext(3);
63
+ i0.ɵɵproperty("disabled", ctx_r2.Busy);
64
+ } }
65
+ function RecordFilesPanelBase_div_2_div_1_ng_container_6_Template(rf, ctx) { if (rf & 1) {
66
+ i0.ɵɵelementContainerStart(0);
67
+ i0.ɵɵtext(1);
68
+ i0.ɵɵelementContainerEnd();
69
+ } if (rf & 2) {
70
+ const f_r2 = i0.ɵɵnextContext().$implicit;
71
+ i0.ɵɵadvance();
72
+ i0.ɵɵtextInterpolate1("", f_r2.Status, " \u00B7 ");
73
+ } }
74
+ function RecordFilesPanelBase_div_2_div_1_Template(rf, ctx) { if (rf & 1) {
75
+ i0.ɵɵelementStart(0, "div", 8);
76
+ i0.ɵɵelement(1, "i", 9);
77
+ i0.ɵɵelementStart(2, "span", 10);
78
+ i0.ɵɵtext(3);
79
+ i0.ɵɵelementEnd();
80
+ i0.ɵɵtemplate(4, RecordFilesPanelBase_div_2_div_1_button_4_Template, 2, 1, "button", 11);
81
+ i0.ɵɵelementStart(5, "span", 12);
82
+ i0.ɵɵtemplate(6, RecordFilesPanelBase_div_2_div_1_ng_container_6_Template, 2, 1, "ng-container", 2);
83
+ i0.ɵɵtext(7);
84
+ i0.ɵɵpipe(8, "date");
85
+ i0.ɵɵelementEnd()();
86
+ } if (rf & 2) {
87
+ const f_r2 = ctx.$implicit;
88
+ const ctx_r2 = i0.ɵɵnextContext(2);
89
+ i0.ɵɵadvance(3);
90
+ i0.ɵɵtextInterpolate(f_r2.Name);
91
+ i0.ɵɵadvance();
92
+ i0.ɵɵproperty("ngIf", ctx_r2.CanDownload && f_r2.AccountID);
93
+ i0.ɵɵadvance(2);
94
+ i0.ɵɵproperty("ngIf", f_r2.Status);
95
+ i0.ɵɵadvance();
96
+ i0.ɵɵtextInterpolate1(" ", f_r2.CreatedAt ? i0.ɵɵpipeBind2(8, 4, f_r2.CreatedAt, "mediumDate") : "", " ");
97
+ } }
98
+ function RecordFilesPanelBase_div_2_Template(rf, ctx) { if (rf & 1) {
99
+ i0.ɵɵelementStart(0, "div");
100
+ i0.ɵɵtemplate(1, RecordFilesPanelBase_div_2_div_1_Template, 9, 7, "div", 7);
101
+ i0.ɵɵelementEnd();
102
+ } if (rf & 2) {
103
+ const ctx_r2 = i0.ɵɵnextContext();
104
+ i0.ɵɵadvance();
105
+ i0.ɵɵproperty("ngForOf", ctx_r2.Files);
106
+ } }
107
+ function RecordFilesPanelBase_div_3_Template(rf, ctx) { if (rf & 1) {
108
+ i0.ɵɵelementStart(0, "div", 14);
109
+ i0.ɵɵtext(1, " No documents attached to this record yet. ");
110
+ i0.ɵɵelementEnd();
111
+ } }
112
+ function RecordFilesPanelBase_div_4_Template(rf, ctx) { if (rf & 1) {
113
+ i0.ɵɵelementStart(0, "div", 14);
114
+ i0.ɵɵtext(1, "Loading documents\u2026");
115
+ i0.ɵɵelementEnd();
116
+ } }
117
+ function RecordFilesPanelBase_div_5_p_7_Template(rf, ctx) { if (rf & 1) {
118
+ i0.ɵɵelementStart(0, "p", 6);
119
+ i0.ɵɵtext(1, " No storage account is configured on this MJ instance, so there is nowhere to register a document to. Configure a ");
120
+ i0.ɵɵelementStart(2, "code");
121
+ i0.ɵɵtext(3, "MJ: File Storage Account");
122
+ i0.ɵɵelementEnd();
123
+ i0.ɵɵtext(4, " first \u2014 for SharePoint that also needs an Azure AD app registration, which is an IT task. ");
124
+ i0.ɵɵelementEnd();
125
+ } }
126
+ function RecordFilesPanelBase_div_5_p_8_Template(rf, ctx) { if (rf & 1) {
127
+ i0.ɵɵelementStart(0, "p", 6)(1, "strong");
128
+ i0.ɵɵtext(2, "Register");
129
+ i0.ɵɵelementEnd();
130
+ i0.ɵɵtext(3, " points at a document that is ALREADY in storage \u2014 it creates the record and the link, and moves no bytes. That is the real flow here: executed PDFs arrive in SharePoint by a route MJ knows nothing about (PandaDoc \u2192 HubSpot \u2192 SharePoint), so the job is to find the object and record where it is, not to upload it. ");
131
+ i0.ɵɵelementEnd();
132
+ } }
133
+ function RecordFilesPanelBase_div_5_Template(rf, ctx) { if (rf & 1) {
134
+ const _r4 = i0.ɵɵgetCurrentView();
135
+ i0.ɵɵelementStart(0, "div", 15)(1, "div", 16);
136
+ i0.ɵɵtext(2, "Attach the executed document");
137
+ i0.ɵɵelementEnd();
138
+ i0.ɵɵelementStart(3, "div", 17)(4, "input", 18);
139
+ i0.ɵɵtwoWayListener("ngModelChange", function RecordFilesPanelBase_div_5_Template_input_ngModelChange_4_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); i0.ɵɵtwoWayBindingSet(ctx_r2.RegisterPath, $event) || (ctx_r2.RegisterPath = $event); return i0.ɵɵresetView($event); });
140
+ i0.ɵɵelementEnd();
141
+ i0.ɵɵelementStart(5, "button", 13);
142
+ i0.ɵɵlistener("click", function RecordFilesPanelBase_div_5_Template_button_click_5_listener() { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.RegisterExisting()); });
143
+ i0.ɵɵtext(6, " Register existing ");
144
+ i0.ɵɵelementEnd()();
145
+ i0.ɵɵtemplate(7, RecordFilesPanelBase_div_5_p_7_Template, 5, 0, "p", 5)(8, RecordFilesPanelBase_div_5_p_8_Template, 4, 0, "p", 5);
146
+ i0.ɵɵelementEnd();
147
+ } if (rf & 2) {
148
+ const ctx_r2 = i0.ɵɵnextContext();
149
+ i0.ɵɵadvance(4);
150
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r2.RegisterPath);
151
+ i0.ɵɵproperty("disabled", ctx_r2.Busy);
152
+ i0.ɵɵadvance();
153
+ i0.ɵɵproperty("disabled", ctx_r2.Busy || !ctx_r2.RegisterPath.trim() || !ctx_r2.DefaultAccountID);
154
+ i0.ɵɵadvance(2);
155
+ i0.ɵɵproperty("ngIf", !ctx_r2.DefaultAccountID);
156
+ i0.ɵɵadvance();
157
+ i0.ɵɵproperty("ngIf", ctx_r2.DefaultAccountID);
158
+ } }
159
+ function RecordFilesPanelBase_div_6_Template(rf, ctx) { if (rf & 1) {
160
+ i0.ɵɵelementStart(0, "div", 6)(1, "strong");
161
+ i0.ɵɵtext(2, "Nothing was attached.");
162
+ i0.ɵɵelementEnd();
163
+ i0.ɵɵtext(3);
164
+ i0.ɵɵelementEnd();
165
+ } if (rf & 2) {
166
+ const ctx_r2 = i0.ɵɵnextContext();
167
+ i0.ɵɵadvance(3);
168
+ i0.ɵɵtextInterpolate1(" ", ctx_r2.ActionError, " ");
169
+ } }
170
+ function RecordFilesPanelBase_p_7_Template(rf, ctx) { if (rf & 1) {
171
+ i0.ɵɵelementStart(0, "p", 6)(1, "a", 19);
172
+ i0.ɵɵtext(2, " Open in the signing provider ");
173
+ i0.ɵɵelementEnd();
174
+ i0.ɵɵtext(3, " \u2014 the always-works fallback. It needs no storage configuration at all, so it is the link that still resolves when SharePoint is misconfigured or the document was never filed. ");
175
+ i0.ɵɵelementEnd();
176
+ } if (rf & 2) {
177
+ const ctx_r2 = i0.ɵɵnextContext();
178
+ i0.ɵɵadvance();
179
+ i0.ɵɵproperty("href", ctx_r2.SigningProviderURL, i0.ɵɵsanitizeUrl);
180
+ } }
181
+ function RecordFilesPanelBase_p_8_Template(rf, ctx) { if (rf & 1) {
182
+ i0.ɵɵelementStart(0, "p", 6);
183
+ i0.ɵɵtext(1);
184
+ i0.ɵɵelementEnd();
185
+ } if (rf & 2) {
186
+ const ctx_r2 = i0.ɵɵnextContext();
187
+ i0.ɵɵadvance();
188
+ i0.ɵɵtextInterpolate1(" ", ctx_r2.Files.length, " document(s) are attached to this record. Opening them \u2014 and seeing their names \u2014 needs document permission, which is granted to finance, legal and sales leadership. ");
189
+ } }
190
+ function ContractFilesPanel_div_2_div_1_button_4_Template(rf, ctx) { if (rf & 1) {
191
+ const _r1 = i0.ɵɵgetCurrentView();
192
+ i0.ɵɵelementStart(0, "button", 13);
193
+ i0.ɵɵlistener("click", function ContractFilesPanel_div_2_div_1_button_4_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r1); const f_r2 = i0.ɵɵnextContext().$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.Open(f_r2)); });
194
+ i0.ɵɵtext(1, "Open");
195
+ i0.ɵɵelementEnd();
196
+ } if (rf & 2) {
197
+ const ctx_r2 = i0.ɵɵnextContext(3);
198
+ i0.ɵɵproperty("disabled", ctx_r2.Busy);
199
+ } }
200
+ function ContractFilesPanel_div_2_div_1_ng_container_6_Template(rf, ctx) { if (rf & 1) {
201
+ i0.ɵɵelementContainerStart(0);
202
+ i0.ɵɵtext(1);
203
+ i0.ɵɵelementContainerEnd();
204
+ } if (rf & 2) {
205
+ const f_r2 = i0.ɵɵnextContext().$implicit;
206
+ i0.ɵɵadvance();
207
+ i0.ɵɵtextInterpolate1("", f_r2.Status, " \u00B7 ");
208
+ } }
209
+ function ContractFilesPanel_div_2_div_1_Template(rf, ctx) { if (rf & 1) {
210
+ i0.ɵɵelementStart(0, "div", 8);
211
+ i0.ɵɵelement(1, "i", 9);
212
+ i0.ɵɵelementStart(2, "span", 10);
213
+ i0.ɵɵtext(3);
214
+ i0.ɵɵelementEnd();
215
+ i0.ɵɵtemplate(4, ContractFilesPanel_div_2_div_1_button_4_Template, 2, 1, "button", 11);
216
+ i0.ɵɵelementStart(5, "span", 12);
217
+ i0.ɵɵtemplate(6, ContractFilesPanel_div_2_div_1_ng_container_6_Template, 2, 1, "ng-container", 2);
218
+ i0.ɵɵtext(7);
219
+ i0.ɵɵpipe(8, "date");
220
+ i0.ɵɵelementEnd()();
221
+ } if (rf & 2) {
222
+ const f_r2 = ctx.$implicit;
223
+ const ctx_r2 = i0.ɵɵnextContext(2);
224
+ i0.ɵɵadvance(3);
225
+ i0.ɵɵtextInterpolate(f_r2.Name);
226
+ i0.ɵɵadvance();
227
+ i0.ɵɵproperty("ngIf", ctx_r2.CanDownload && f_r2.AccountID);
228
+ i0.ɵɵadvance(2);
229
+ i0.ɵɵproperty("ngIf", f_r2.Status);
230
+ i0.ɵɵadvance();
231
+ i0.ɵɵtextInterpolate1(" ", f_r2.CreatedAt ? i0.ɵɵpipeBind2(8, 4, f_r2.CreatedAt, "mediumDate") : "", " ");
232
+ } }
233
+ function ContractFilesPanel_div_2_Template(rf, ctx) { if (rf & 1) {
234
+ i0.ɵɵelementStart(0, "div");
235
+ i0.ɵɵtemplate(1, ContractFilesPanel_div_2_div_1_Template, 9, 7, "div", 7);
236
+ i0.ɵɵelementEnd();
237
+ } if (rf & 2) {
238
+ const ctx_r2 = i0.ɵɵnextContext();
239
+ i0.ɵɵadvance();
240
+ i0.ɵɵproperty("ngForOf", ctx_r2.Files);
241
+ } }
242
+ function ContractFilesPanel_div_3_Template(rf, ctx) { if (rf & 1) {
243
+ i0.ɵɵelementStart(0, "div", 14);
244
+ i0.ɵɵtext(1, " No documents attached to this record yet. ");
245
+ i0.ɵɵelementEnd();
246
+ } }
247
+ function ContractFilesPanel_div_4_Template(rf, ctx) { if (rf & 1) {
248
+ i0.ɵɵelementStart(0, "div", 14);
249
+ i0.ɵɵtext(1, "Loading documents\u2026");
250
+ i0.ɵɵelementEnd();
251
+ } }
252
+ function ContractFilesPanel_div_5_p_7_Template(rf, ctx) { if (rf & 1) {
253
+ i0.ɵɵelementStart(0, "p", 6);
254
+ i0.ɵɵtext(1, " No storage account is configured on this MJ instance, so there is nowhere to register a document to. Configure a ");
255
+ i0.ɵɵelementStart(2, "code");
256
+ i0.ɵɵtext(3, "MJ: File Storage Account");
257
+ i0.ɵɵelementEnd();
258
+ i0.ɵɵtext(4, " first \u2014 for SharePoint that also needs an Azure AD app registration, which is an IT task. ");
259
+ i0.ɵɵelementEnd();
260
+ } }
261
+ function ContractFilesPanel_div_5_p_8_Template(rf, ctx) { if (rf & 1) {
262
+ i0.ɵɵelementStart(0, "p", 6)(1, "strong");
263
+ i0.ɵɵtext(2, "Register");
264
+ i0.ɵɵelementEnd();
265
+ i0.ɵɵtext(3, " points at a document that is ALREADY in storage \u2014 it creates the record and the link, and moves no bytes. That is the real flow here: executed PDFs arrive in SharePoint by a route MJ knows nothing about (PandaDoc \u2192 HubSpot \u2192 SharePoint), so the job is to find the object and record where it is, not to upload it. ");
266
+ i0.ɵɵelementEnd();
267
+ } }
268
+ function ContractFilesPanel_div_5_Template(rf, ctx) { if (rf & 1) {
269
+ const _r4 = i0.ɵɵgetCurrentView();
270
+ i0.ɵɵelementStart(0, "div", 15)(1, "div", 16);
271
+ i0.ɵɵtext(2, "Attach the executed document");
272
+ i0.ɵɵelementEnd();
273
+ i0.ɵɵelementStart(3, "div", 17)(4, "input", 18);
274
+ i0.ɵɵtwoWayListener("ngModelChange", function ContractFilesPanel_div_5_Template_input_ngModelChange_4_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); i0.ɵɵtwoWayBindingSet(ctx_r2.RegisterPath, $event) || (ctx_r2.RegisterPath = $event); return i0.ɵɵresetView($event); });
275
+ i0.ɵɵelementEnd();
276
+ i0.ɵɵelementStart(5, "button", 13);
277
+ i0.ɵɵlistener("click", function ContractFilesPanel_div_5_Template_button_click_5_listener() { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.RegisterExisting()); });
278
+ i0.ɵɵtext(6, " Register existing ");
279
+ i0.ɵɵelementEnd()();
280
+ i0.ɵɵtemplate(7, ContractFilesPanel_div_5_p_7_Template, 5, 0, "p", 5)(8, ContractFilesPanel_div_5_p_8_Template, 4, 0, "p", 5);
281
+ i0.ɵɵelementEnd();
282
+ } if (rf & 2) {
283
+ const ctx_r2 = i0.ɵɵnextContext();
284
+ i0.ɵɵadvance(4);
285
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r2.RegisterPath);
286
+ i0.ɵɵproperty("disabled", ctx_r2.Busy);
287
+ i0.ɵɵadvance();
288
+ i0.ɵɵproperty("disabled", ctx_r2.Busy || !ctx_r2.RegisterPath.trim() || !ctx_r2.DefaultAccountID);
289
+ i0.ɵɵadvance(2);
290
+ i0.ɵɵproperty("ngIf", !ctx_r2.DefaultAccountID);
291
+ i0.ɵɵadvance();
292
+ i0.ɵɵproperty("ngIf", ctx_r2.DefaultAccountID);
293
+ } }
294
+ function ContractFilesPanel_div_6_Template(rf, ctx) { if (rf & 1) {
295
+ i0.ɵɵelementStart(0, "div", 6)(1, "strong");
296
+ i0.ɵɵtext(2, "Nothing was attached.");
297
+ i0.ɵɵelementEnd();
298
+ i0.ɵɵtext(3);
299
+ i0.ɵɵelementEnd();
300
+ } if (rf & 2) {
301
+ const ctx_r2 = i0.ɵɵnextContext();
302
+ i0.ɵɵadvance(3);
303
+ i0.ɵɵtextInterpolate1(" ", ctx_r2.ActionError, " ");
304
+ } }
305
+ function ContractFilesPanel_p_7_Template(rf, ctx) { if (rf & 1) {
306
+ i0.ɵɵelementStart(0, "p", 6)(1, "a", 19);
307
+ i0.ɵɵtext(2, " Open in the signing provider ");
308
+ i0.ɵɵelementEnd();
309
+ i0.ɵɵtext(3, " \u2014 the always-works fallback. It needs no storage configuration at all, so it is the link that still resolves when SharePoint is misconfigured or the document was never filed. ");
310
+ i0.ɵɵelementEnd();
311
+ } if (rf & 2) {
312
+ const ctx_r2 = i0.ɵɵnextContext();
313
+ i0.ɵɵadvance();
314
+ i0.ɵɵproperty("href", ctx_r2.SigningProviderURL, i0.ɵɵsanitizeUrl);
315
+ } }
316
+ function ContractFilesPanel_p_8_Template(rf, ctx) { if (rf & 1) {
317
+ i0.ɵɵelementStart(0, "p", 6);
318
+ i0.ɵɵtext(1);
319
+ i0.ɵɵelementEnd();
320
+ } if (rf & 2) {
321
+ const ctx_r2 = i0.ɵɵnextContext();
322
+ i0.ɵɵadvance();
323
+ i0.ɵɵtextInterpolate1(" ", ctx_r2.Files.length, " document(s) are attached to this record. Opening them \u2014 and seeing their names \u2014 needs document permission, which is granted to finance, legal and sales leadership. ");
324
+ } }
325
+ const _c0 = "\n\n\n\n\n\n\n\n .mjc-file-row[_ngcontent-%COMP%] {\n display: flex; align-items: center; gap: var(--mj-space-3);\n padding: var(--mj-space-2) 0;\n font-size: var(--mj-text-sm);\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n .mjc-file-row[_ngcontent-%COMP%]:last-child { border-bottom: none; }\n .mjc-file-row[_ngcontent-%COMP%] .mjc-file-icon[_ngcontent-%COMP%] { color: var(--mj-status-error); }\n .mjc-file-row[_ngcontent-%COMP%] .mjc-file-name[_ngcontent-%COMP%] { font-weight: 600; overflow-wrap: anywhere; }\n .mjc-file-row[_ngcontent-%COMP%] .mjc-file-meta[_ngcontent-%COMP%] {\n margin-left: auto; font-size: var(--mj-text-xs);\n color: var(--mj-text-muted); white-space: nowrap;\n }\n .mjc-reg[_ngcontent-%COMP%] { margin-top: var(--mj-space-3); }\n .mjc-reg__title[_ngcontent-%COMP%] {\n font-size: var(--mj-text-xs); font-weight: 600;\n text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); margin-bottom: var(--mj-space-2);\n }\n .mjc-reg__row[_ngcontent-%COMP%] { display: flex; gap: var(--mj-space-2); align-items: center; flex-wrap: wrap; }\n .mjc-reg__row[_ngcontent-%COMP%] input[_ngcontent-%COMP%] { flex: 1 1 22rem; }";
326
+ function ContractTemplateFilesPanel_div_2_div_1_button_4_Template(rf, ctx) { if (rf & 1) {
327
+ const _r1 = i0.ɵɵgetCurrentView();
328
+ i0.ɵɵelementStart(0, "button", 13);
329
+ i0.ɵɵlistener("click", function ContractTemplateFilesPanel_div_2_div_1_button_4_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r1); const f_r2 = i0.ɵɵnextContext().$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.Open(f_r2)); });
330
+ i0.ɵɵtext(1, "Open");
331
+ i0.ɵɵelementEnd();
332
+ } if (rf & 2) {
333
+ const ctx_r2 = i0.ɵɵnextContext(3);
334
+ i0.ɵɵproperty("disabled", ctx_r2.Busy);
335
+ } }
336
+ function ContractTemplateFilesPanel_div_2_div_1_ng_container_6_Template(rf, ctx) { if (rf & 1) {
337
+ i0.ɵɵelementContainerStart(0);
338
+ i0.ɵɵtext(1);
339
+ i0.ɵɵelementContainerEnd();
340
+ } if (rf & 2) {
341
+ const f_r2 = i0.ɵɵnextContext().$implicit;
342
+ i0.ɵɵadvance();
343
+ i0.ɵɵtextInterpolate1("", f_r2.Status, " \u00B7 ");
344
+ } }
345
+ function ContractTemplateFilesPanel_div_2_div_1_Template(rf, ctx) { if (rf & 1) {
346
+ i0.ɵɵelementStart(0, "div", 8);
347
+ i0.ɵɵelement(1, "i", 9);
348
+ i0.ɵɵelementStart(2, "span", 10);
349
+ i0.ɵɵtext(3);
350
+ i0.ɵɵelementEnd();
351
+ i0.ɵɵtemplate(4, ContractTemplateFilesPanel_div_2_div_1_button_4_Template, 2, 1, "button", 11);
352
+ i0.ɵɵelementStart(5, "span", 12);
353
+ i0.ɵɵtemplate(6, ContractTemplateFilesPanel_div_2_div_1_ng_container_6_Template, 2, 1, "ng-container", 2);
354
+ i0.ɵɵtext(7);
355
+ i0.ɵɵpipe(8, "date");
356
+ i0.ɵɵelementEnd()();
357
+ } if (rf & 2) {
358
+ const f_r2 = ctx.$implicit;
359
+ const ctx_r2 = i0.ɵɵnextContext(2);
360
+ i0.ɵɵadvance(3);
361
+ i0.ɵɵtextInterpolate(f_r2.Name);
362
+ i0.ɵɵadvance();
363
+ i0.ɵɵproperty("ngIf", ctx_r2.CanDownload && f_r2.AccountID);
364
+ i0.ɵɵadvance(2);
365
+ i0.ɵɵproperty("ngIf", f_r2.Status);
366
+ i0.ɵɵadvance();
367
+ i0.ɵɵtextInterpolate1(" ", f_r2.CreatedAt ? i0.ɵɵpipeBind2(8, 4, f_r2.CreatedAt, "mediumDate") : "", " ");
368
+ } }
369
+ function ContractTemplateFilesPanel_div_2_Template(rf, ctx) { if (rf & 1) {
370
+ i0.ɵɵelementStart(0, "div");
371
+ i0.ɵɵtemplate(1, ContractTemplateFilesPanel_div_2_div_1_Template, 9, 7, "div", 7);
372
+ i0.ɵɵelementEnd();
373
+ } if (rf & 2) {
374
+ const ctx_r2 = i0.ɵɵnextContext();
375
+ i0.ɵɵadvance();
376
+ i0.ɵɵproperty("ngForOf", ctx_r2.Files);
377
+ } }
378
+ function ContractTemplateFilesPanel_div_3_Template(rf, ctx) { if (rf & 1) {
379
+ i0.ɵɵelementStart(0, "div", 14);
380
+ i0.ɵɵtext(1, " No documents attached to this record yet. ");
381
+ i0.ɵɵelementEnd();
382
+ } }
383
+ function ContractTemplateFilesPanel_div_4_Template(rf, ctx) { if (rf & 1) {
384
+ i0.ɵɵelementStart(0, "div", 14);
385
+ i0.ɵɵtext(1, "Loading documents\u2026");
386
+ i0.ɵɵelementEnd();
387
+ } }
388
+ function ContractTemplateFilesPanel_div_5_p_7_Template(rf, ctx) { if (rf & 1) {
389
+ i0.ɵɵelementStart(0, "p", 6);
390
+ i0.ɵɵtext(1, " No storage account is configured on this MJ instance, so there is nowhere to register a document to. Configure a ");
391
+ i0.ɵɵelementStart(2, "code");
392
+ i0.ɵɵtext(3, "MJ: File Storage Account");
393
+ i0.ɵɵelementEnd();
394
+ i0.ɵɵtext(4, " first \u2014 for SharePoint that also needs an Azure AD app registration, which is an IT task. ");
395
+ i0.ɵɵelementEnd();
396
+ } }
397
+ function ContractTemplateFilesPanel_div_5_p_8_Template(rf, ctx) { if (rf & 1) {
398
+ i0.ɵɵelementStart(0, "p", 6)(1, "strong");
399
+ i0.ɵɵtext(2, "Register");
400
+ i0.ɵɵelementEnd();
401
+ i0.ɵɵtext(3, " points at a document that is ALREADY in storage \u2014 it creates the record and the link, and moves no bytes. That is the real flow here: executed PDFs arrive in SharePoint by a route MJ knows nothing about (PandaDoc \u2192 HubSpot \u2192 SharePoint), so the job is to find the object and record where it is, not to upload it. ");
402
+ i0.ɵɵelementEnd();
403
+ } }
404
+ function ContractTemplateFilesPanel_div_5_Template(rf, ctx) { if (rf & 1) {
405
+ const _r4 = i0.ɵɵgetCurrentView();
406
+ i0.ɵɵelementStart(0, "div", 15)(1, "div", 16);
407
+ i0.ɵɵtext(2, "Attach the executed document");
408
+ i0.ɵɵelementEnd();
409
+ i0.ɵɵelementStart(3, "div", 17)(4, "input", 18);
410
+ i0.ɵɵtwoWayListener("ngModelChange", function ContractTemplateFilesPanel_div_5_Template_input_ngModelChange_4_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); i0.ɵɵtwoWayBindingSet(ctx_r2.RegisterPath, $event) || (ctx_r2.RegisterPath = $event); return i0.ɵɵresetView($event); });
411
+ i0.ɵɵelementEnd();
412
+ i0.ɵɵelementStart(5, "button", 13);
413
+ i0.ɵɵlistener("click", function ContractTemplateFilesPanel_div_5_Template_button_click_5_listener() { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.RegisterExisting()); });
414
+ i0.ɵɵtext(6, " Register existing ");
415
+ i0.ɵɵelementEnd()();
416
+ i0.ɵɵtemplate(7, ContractTemplateFilesPanel_div_5_p_7_Template, 5, 0, "p", 5)(8, ContractTemplateFilesPanel_div_5_p_8_Template, 4, 0, "p", 5);
417
+ i0.ɵɵelementEnd();
418
+ } if (rf & 2) {
419
+ const ctx_r2 = i0.ɵɵnextContext();
420
+ i0.ɵɵadvance(4);
421
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r2.RegisterPath);
422
+ i0.ɵɵproperty("disabled", ctx_r2.Busy);
423
+ i0.ɵɵadvance();
424
+ i0.ɵɵproperty("disabled", ctx_r2.Busy || !ctx_r2.RegisterPath.trim() || !ctx_r2.DefaultAccountID);
425
+ i0.ɵɵadvance(2);
426
+ i0.ɵɵproperty("ngIf", !ctx_r2.DefaultAccountID);
427
+ i0.ɵɵadvance();
428
+ i0.ɵɵproperty("ngIf", ctx_r2.DefaultAccountID);
429
+ } }
430
+ function ContractTemplateFilesPanel_div_6_Template(rf, ctx) { if (rf & 1) {
431
+ i0.ɵɵelementStart(0, "div", 6)(1, "strong");
432
+ i0.ɵɵtext(2, "Nothing was attached.");
433
+ i0.ɵɵelementEnd();
434
+ i0.ɵɵtext(3);
435
+ i0.ɵɵelementEnd();
436
+ } if (rf & 2) {
437
+ const ctx_r2 = i0.ɵɵnextContext();
438
+ i0.ɵɵadvance(3);
439
+ i0.ɵɵtextInterpolate1(" ", ctx_r2.ActionError, " ");
440
+ } }
441
+ function ContractTemplateFilesPanel_p_7_Template(rf, ctx) { if (rf & 1) {
442
+ i0.ɵɵelementStart(0, "p", 6)(1, "a", 19);
443
+ i0.ɵɵtext(2, " Open in the signing provider ");
444
+ i0.ɵɵelementEnd();
445
+ i0.ɵɵtext(3, " \u2014 the always-works fallback. It needs no storage configuration at all, so it is the link that still resolves when SharePoint is misconfigured or the document was never filed. ");
446
+ i0.ɵɵelementEnd();
447
+ } if (rf & 2) {
448
+ const ctx_r2 = i0.ɵɵnextContext();
449
+ i0.ɵɵadvance();
450
+ i0.ɵɵproperty("href", ctx_r2.SigningProviderURL, i0.ɵɵsanitizeUrl);
451
+ } }
452
+ function ContractTemplateFilesPanel_p_8_Template(rf, ctx) { if (rf & 1) {
453
+ i0.ɵɵelementStart(0, "p", 6);
454
+ i0.ɵɵtext(1);
455
+ i0.ɵɵelementEnd();
456
+ } if (rf & 2) {
457
+ const ctx_r2 = i0.ɵɵnextContext();
458
+ i0.ɵɵadvance();
459
+ i0.ɵɵtextInterpolate1(" ", ctx_r2.Files.length, " document(s) are attached to this record. Opening them \u2014 and seeing their names \u2014 needs document permission, which is granted to finance, legal and sales leadership. ");
460
+ } }
461
+ const PANEL_STYLES = `
462
+ /* SEMANTIC TOKENS ONLY — no hex, no bare px for colour/type/space.
463
+ *
464
+ * This panel came forward from v1 and its styles did not: it carried seven hex FALLBACKS
465
+ * inside var() (\`var(--mj-text-muted, #64748b)\`) and six hardcoded px font sizes. The
466
+ * fallbacks look harmless and are not — they are the value that renders the moment a token
467
+ * name is wrong, so a typo silently pins a light-mode colour into a dark theme instead of
468
+ * failing visibly. Every rule below now resolves through a token or a kit class. */
469
+ .mjc-file-row {
470
+ display: flex; align-items: center; gap: var(--mj-space-3);
471
+ padding: var(--mj-space-2) 0;
472
+ font-size: var(--mj-text-sm);
473
+ border-bottom: 1px solid var(--mj-border-subtle);
474
+ }
475
+ .mjc-file-row:last-child { border-bottom: none; }
476
+ .mjc-file-row .mjc-file-icon { color: var(--mj-status-error); }
477
+ .mjc-file-row .mjc-file-name { font-weight: 600; overflow-wrap: anywhere; }
478
+ .mjc-file-row .mjc-file-meta {
479
+ margin-left: auto; font-size: var(--mj-text-xs);
480
+ color: var(--mj-text-muted); white-space: nowrap;
481
+ }
482
+ .mjc-reg { margin-top: var(--mj-space-3); }
483
+ .mjc-reg__title {
484
+ font-size: var(--mj-text-xs); font-weight: 600;
485
+ text-transform: uppercase; letter-spacing: .04em;
486
+ color: var(--mj-text-muted); margin-bottom: var(--mj-space-2);
487
+ }
488
+ .mjc-reg__row { display: flex; gap: var(--mj-space-2); align-items: center; flex-wrap: wrap; }
489
+ .mjc-reg__row input { flex: 1 1 22rem; }
490
+ `;
491
+ const PANEL_TEMPLATE = `
492
+ <mj-collapsible-panel
493
+ SectionKey="recordFiles"
494
+ SectionName="Documents"
495
+ Icon="fa-solid fa-folder-open"
496
+ [Form]="FormComponent"
497
+ [FormContext]="FormContext"
498
+ >
499
+ <div class="mjc-body">
500
+ <div *ngIf="Files.length">
501
+ <div class="mjc-file-row" *ngFor="let f of Files">
502
+ <i class="fa-solid fa-file-lines mjc-file-icon" aria-hidden="true"></i>
503
+ <span class="mjc-file-name">{{ f.Name }}</span>
504
+ <button type="button" class="mjc-btn mjc-btn--flat mjc-btn--sm" *ngIf="CanDownload && f.AccountID"
505
+ (click)="Open(f)" [disabled]="Busy">Open</button>
506
+ <span class="mjc-file-meta">
507
+ <ng-container *ngIf="f.Status">{{ f.Status }} · </ng-container>
508
+ {{ f.CreatedAt ? (f.CreatedAt | date: 'mediumDate') : '' }}
509
+ </span>
510
+ </div>
511
+ </div>
512
+
513
+ <div class="mjc-empty" *ngIf="!IsLoading && !Files.length">
514
+ No documents attached to this record yet.
515
+ </div>
516
+ <div class="mjc-empty" *ngIf="IsLoading">Loading documents…</div>
517
+
518
+ <div class="mjc-reg" *ngIf="EditMode && CanDownload">
519
+ <div class="mjc-reg__title">Attach the executed document</div>
520
+
521
+ <div class="mjc-reg__row">
522
+ <input type="text" [(ngModel)]="RegisterPath" [disabled]="Busy"
523
+ placeholder="Path in storage, e.g. Contracts/2026/CTR-000004.pdf"
524
+ aria-label="Path of an existing document in storage" />
525
+ <button type="button" class="mjc-btn mjc-btn--flat mjc-btn--sm" (click)="RegisterExisting()"
526
+ [disabled]="Busy || !RegisterPath.trim() || !DefaultAccountID">
527
+ Register existing
528
+ </button>
529
+ </div>
530
+
531
+ <p class="mjc-note" *ngIf="!DefaultAccountID">
532
+ No storage account is configured on this MJ instance, so there is nowhere to register a
533
+ document to. Configure a <code>MJ: File Storage Account</code> first — for SharePoint that
534
+ also needs an Azure AD app registration, which is an IT task.
535
+ </p>
536
+
537
+ <p class="mjc-note" *ngIf="DefaultAccountID">
538
+ <strong>Register</strong> points at a document that is ALREADY in storage — it creates the
539
+ record and the link, and moves no bytes. That is the real flow here: executed PDFs arrive in
540
+ SharePoint by a route MJ knows nothing about (PandaDoc &rarr; HubSpot &rarr; SharePoint), so
541
+ the job is to find the object and record where it is, not to upload it.
542
+ </p>
543
+ </div>
544
+
545
+ <div class="mjc-note" *ngIf="ActionError">
546
+ <strong>Nothing was attached.</strong> {{ ActionError }}
547
+ </div>
548
+
549
+ <p class="mjc-note" *ngIf="SigningProviderURL">
550
+ <a [href]="SigningProviderURL" target="_blank" rel="noopener noreferrer">
551
+ Open in the signing provider
552
+ </a>
553
+ — the always-works fallback. It needs no storage configuration at all, so it is the link that
554
+ still resolves when SharePoint is misconfigured or the document was never filed.
555
+ </p>
556
+
557
+ <p class="mjc-note" *ngIf="!CanDownload && Files.length">
558
+ {{ Files.length }} document(s) are attached to this record. Opening them — and seeing their
559
+ names — needs document permission, which is granted to finance, legal and sales leadership.
560
+ </p>
561
+
562
+ <p class="mjc-note">
563
+ Documents attach through MJ's polymorphic <code>__mj.FileEntityRecordLink</code>
564
+ (<code>EntityID</code> + <code>RecordID</code>) rather than a column on this schema — so one record
565
+ can carry the signed PDF, its exhibits and a countersigned amendment, and no future table needs a
566
+ new column to acquire paper.
567
+ </p>
568
+ </div>
569
+ </mj-collapsible-panel>
570
+ `;
571
+ const ENTITY_FILE_LINKS = 'MJ: File Entity Record Links';
572
+ const ENTITY_FILES = 'MJ: Files';
573
+ const ENTITY_STORAGE_ACCOUNTS = 'MJ: File Storage Accounts';
574
+ /**
575
+ * All of the behaviour. Deliberately knows nothing about contracts — see the file header.
576
+ */
577
+ export class RecordFilesPanelBase extends BaseFormPanel {
578
+ cdr = inject(ChangeDetectorRef);
579
+ fileOpen = inject(FileOpenService);
580
+ Files = [];
581
+ IsLoading = true;
582
+ /** The storage account every action runs against, or null when the instance has none. */
583
+ DefaultAccountID = null;
584
+ /** Path typed into the register box. */
585
+ RegisterPath = '';
586
+ /** True while an action is in flight, so the buttons cannot be double-fired. */
587
+ Busy = false;
588
+ /** The last action failure, shown in the panel rather than a toast that scrolls away. */
589
+ ActionError = null;
590
+ /**
591
+ * Whether this user may DOWNLOAD, as opposed to merely see that documents exist (D-9).
592
+ *
593
+ * ⚠ THE OBVIOUS IMPLEMENTATION IS VACUOUS, and this was written that way first. Reading the HOST
594
+ * RECORD's `CanRead` asks "may they see this contract" — but everyone looking at the panel can
595
+ * already see the contract, so the gate returns true for every viewer and the restriction can
596
+ * never bind. D-9 exists precisely to separate two populations (leadership/finance/legal may
597
+ * download; everyone else sees the record), and gating on record visibility collapses them into
598
+ * one. Caught on review of PR #9.
599
+ *
600
+ * So the gate is the **`MJ: Files` entity's** read permission — a surface genuinely DISTINCT from
601
+ * record visibility. An administrator grants Files read to the three D-9 roles and to nobody else,
602
+ * and the check never mentions contracts, so the panel stays entity-agnostic for donation to MJ.
603
+ *
604
+ * `GetUserPermisions` is the platform's own allow/deny aggregation across the user's roles, so this
605
+ * cannot drift from what the instance actually grants. FALSE on any doubt: a hidden Open button is
606
+ * a nuisance, a leaked executed contract is not.
607
+ */
608
+ get CanDownload() {
609
+ try {
610
+ // D-25 all the way down: the provider answers BOTH questions — who the user is and what
611
+ // the entity's permissions are. An earlier version had the provider in hand and then asked
612
+ // a bare `new Metadata()` for the entity anyway, which is the exact pattern the rule bans.
613
+ const provider = this.FormComponent?.ProviderToUse ?? Metadata.Provider;
614
+ const user = provider?.CurrentUser;
615
+ if (!user)
616
+ return false;
617
+ const files = provider?.EntityByName(ENTITY_FILES);
618
+ return files?.GetUserPermisions(user)?.CanRead === true;
619
+ }
620
+ catch {
621
+ return false;
622
+ }
623
+ }
624
+ /**
625
+ * The signing provider's own URL, when the host record carries one — the always-works fallback.
626
+ *
627
+ * Read generically (`SigningProviderURL`) rather than by typing this panel to ContractEntity,
628
+ * because the panel is deliberately entity-agnostic and intended for donation to MJ. A host entity
629
+ * without the field simply has no fallback link.
630
+ */
631
+ get SigningProviderURL() {
632
+ try {
633
+ const v = this.Record?.Get?.('SigningProviderURL');
634
+ return v ? String(v) : null;
635
+ }
636
+ catch {
637
+ return null;
638
+ }
639
+ }
640
+ get HasFiles() {
641
+ return this.Files.length > 0;
642
+ }
643
+ async ngOnInit() {
644
+ await this.resolveAccount();
645
+ await this.load();
646
+ }
647
+ /**
648
+ * Two reads: the links for this record, then the files they name. It is deliberately two rather
649
+ * than one join — `RunView` is the app's only sanctioned read path (§11.1), and the second read
650
+ * is skipped entirely when a record has no links, which is the common case.
651
+ */
652
+ async load() {
653
+ this.IsLoading = true;
654
+ try {
655
+ const entityID = this.Record?.EntityInfo?.ID;
656
+ const recordID = this.Record?.PrimaryKey?.GetValueByFieldName('ID') ?? this.Record?.PrimaryKey?.Values();
657
+ if (!entityID || recordID == null) {
658
+ this.Files = [];
659
+ return;
660
+ }
661
+ // D-25: the HOST FORM's provider, not the ambient one. A panel showing files for a record
662
+ // must read from wherever that record came from; the flagged bare `new RunView()` was correct
663
+ // only while exactly one provider existed.
664
+ const rv = ScopedRunView(this.FormComponent?.ProviderToUse);
665
+ const links = await rv.RunView({
666
+ EntityName: ENTITY_FILE_LINKS,
667
+ Fields: ['ID', 'FileID'],
668
+ ExtraFilter: `EntityID='${entityID}' AND RecordID='${String(recordID).replace(/'/g, "''")}'`,
669
+ ResultType: 'simple',
670
+ });
671
+ if (!links?.Success || !links.Results.length) {
672
+ this.Files = [];
673
+ return;
674
+ }
675
+ // DECIDED CONSCIOUSLY (PR #9 review): a user without Files read sees the COUNT, not the
676
+ // names. Two reasons. Reading `MJ: Files` is exactly what they lack permission for, so
677
+ // attempting it would either fail or — worse — succeed and leak the filenames the gate is
678
+ // meant to withhold; and a filename is itself disclosure ("Termination Notice - Northwind
679
+ // v3.pdf" tells you plenty). Item 9's done-when is "sees the record but no download", and
680
+ // knowing that two documents exist satisfies it without disclosing what they are.
681
+ if (!this.CanDownload) {
682
+ this.Files = links.Results.map((l) => ({
683
+ LinkID: l.ID,
684
+ FileID: l.FileID,
685
+ Name: 'Document (name withheld)',
686
+ Provider: null,
687
+ Status: null,
688
+ CreatedAt: null,
689
+ ProviderKey: null,
690
+ AccountID: null,
691
+ }));
692
+ return;
693
+ }
694
+ const ids = links.Results.map((l) => `'${l.FileID}'`).join(',');
695
+ const files = await rv.RunView({
696
+ EntityName: ENTITY_FILES,
697
+ Fields: ['ID', 'Name', 'ProviderKey', 'Status', '__mj_CreatedAt', 'ProviderID'],
698
+ ExtraFilter: `ID IN (${ids})`,
699
+ ResultType: 'simple',
700
+ });
701
+ const byID = new Map((files?.Success ? files.Results : []).map((f) => [f.ID, f]));
702
+ this.Files = links.Results.map((l) => {
703
+ const f = byID.get(l.FileID);
704
+ return {
705
+ LinkID: l.ID,
706
+ FileID: l.FileID,
707
+ Name: f?.Name ?? '(file not found)',
708
+ Provider: f?.ProviderKey ?? null,
709
+ Status: f?.Status ?? null,
710
+ CreatedAt: f?.__mj_CreatedAt ?? null,
711
+ ProviderKey: f?.ProviderKey ?? null,
712
+ // The download API takes a storage ACCOUNT, and a File row names a PROVIDER (the
713
+ // type). We resolve the instance's account once in ngOnInit and use it for every
714
+ // row — correct while an instance has one account, which is the case here and is
715
+ // asserted rather than assumed: the Open button is hidden when there is none.
716
+ AccountID: this.DefaultAccountID,
717
+ };
718
+ });
719
+ }
720
+ finally {
721
+ this.IsLoading = false;
722
+ this.cdr.detectChanges();
723
+ }
724
+ }
725
+ /**
726
+ * Resolve the instance's storage account once.
727
+ *
728
+ * A `File` row names a PROVIDER (the type — "SharePoint"); the download and upload APIs take an
729
+ * ACCOUNT (a configured instance of one). Nothing on the link or the file gives us the account, so
730
+ * it is looked up here. When there is none, every affordance that needs storage is HIDDEN rather
731
+ * than shown-and-failing: an Open button that always errors is worse than no button.
732
+ */
733
+ async resolveAccount() {
734
+ try {
735
+ const rv = ScopedRunView(this.FormComponent?.ProviderToUse);
736
+ const accounts = await rv.RunView({
737
+ EntityName: ENTITY_STORAGE_ACCOUNTS,
738
+ Fields: ['ID', 'Name'],
739
+ OrderBy: '__mj_CreatedAt ASC',
740
+ MaxRows: 1,
741
+ ResultType: 'simple',
742
+ });
743
+ this.DefaultAccountID = accounts?.Success && accounts.Results.length ? accounts.Results[0].ID : null;
744
+ }
745
+ catch {
746
+ this.DefaultAccountID = null;
747
+ }
748
+ }
749
+ /**
750
+ * REGISTER an object that is already in storage (§6.5) — the flow this app actually needs.
751
+ *
752
+ * MJ's own `CreateFile` mutation assumes an UPLOAD: it creates the row `Pending` and hands back a
753
+ * pre-auth URL for the browser to push bytes to. Our executed PDFs are already in SharePoint,
754
+ * delivered by PandaDoc → HubSpot → SharePoint, a route MJ knows nothing about. So there is nothing
755
+ * to upload — the work is to create the `MJ: Files` row with the right `ProviderKey` and link it.
756
+ *
757
+ * Done entirely through the ENTITY layer, no GraphQL: two saves, and the second is skipped if the
758
+ * first fails, so a half-registered document (a file row nothing points at) cannot result. It
759
+ * verifies the object EXISTS first — registering a path that is not there produces a record that
760
+ * lies, and a document nobody can open is exactly what this panel exists to prevent.
761
+ */
762
+ async RegisterExisting() {
763
+ const path = this.RegisterPath.trim();
764
+ if (!path || !this.DefaultAccountID)
765
+ return;
766
+ this.Busy = true;
767
+ this.ActionError = null;
768
+ try {
769
+ const provider = this.FormComponent?.ProviderToUse ?? Metadata.Provider;
770
+ const client = new GraphQLFileStorageClient(provider);
771
+ const exists = await client.ObjectExists(this.DefaultAccountID, path);
772
+ if (!exists) {
773
+ this.ActionError =
774
+ `Nothing is stored at "${path}". Check the path — registering a document that is not ` +
775
+ `there would create a record that cannot be opened.`;
776
+ return;
777
+ }
778
+ const entityID = this.Record?.EntityInfo?.ID;
779
+ const recordID = this.Record?.PrimaryKey?.GetValueByFieldName('ID');
780
+ if (!entityID || recordID == null) {
781
+ this.ActionError = 'This record has no primary key yet — save it before attaching a document.';
782
+ return;
783
+ }
784
+ // Constructed through the HOST FORM's provider (D-25), so the file and link rows are
785
+ // written wherever the record being annotated came from.
786
+ const md = this.FormComponent?.ProviderToUse ?? Metadata.Provider;
787
+ const file = await md.GetEntityObject(ENTITY_FILES);
788
+ file.NewRecord();
789
+ file.Set('Name', path.split('/').pop() || path);
790
+ file.Set('ProviderID', await this.resolveProviderID());
791
+ file.Set('ProviderKey', path);
792
+ // 'Uploaded' is the status meaning "the bytes are there". The row was not created BY an
793
+ // upload, but the state it describes is identical, and lying about it as 'Pending' would
794
+ // make the file invisible to anything that filters on readiness.
795
+ file.Set('Status', 'Uploaded');
796
+ if (!(await file.Save())) {
797
+ this.ActionError = file.LatestResult?.Message ?? 'Could not create the file record.';
798
+ return;
799
+ }
800
+ const link = await md.GetEntityObject(ENTITY_FILE_LINKS);
801
+ link.NewRecord();
802
+ link.Set('FileID', file.Get('ID'));
803
+ link.Set('EntityID', entityID);
804
+ link.Set('RecordID', String(recordID));
805
+ if (!(await link.Save())) {
806
+ this.ActionError =
807
+ `The document record was created but could not be linked to this record ` +
808
+ `(${link.LatestResult?.Message ?? 'save failed'}). It will not appear here until it is.`;
809
+ return;
810
+ }
811
+ this.RegisterPath = '';
812
+ await this.load();
813
+ }
814
+ catch (err) {
815
+ this.ActionError = String(err);
816
+ }
817
+ finally {
818
+ this.Busy = false;
819
+ this.cdr.detectChanges();
820
+ }
821
+ }
822
+ /** The provider (type) behind the resolved account — a File row references the provider, not the account. */
823
+ async resolveProviderID() {
824
+ try {
825
+ const rv = ScopedRunView(this.FormComponent?.ProviderToUse);
826
+ const r = await rv.RunView({
827
+ EntityName: ENTITY_STORAGE_ACCOUNTS,
828
+ Fields: ['ProviderID'],
829
+ ExtraFilter: `ID = '${this.DefaultAccountID}'`,
830
+ ResultType: 'simple',
831
+ });
832
+ return r?.Success && r.Results.length ? r.Results[0].ProviderID : null;
833
+ }
834
+ catch {
835
+ return null;
836
+ }
837
+ }
838
+ /**
839
+ * Open a document via a time-limited pre-auth URL.
840
+ *
841
+ * Delegated to MJ's own `FileOpenService`, which mints the URL over GraphQL and is provider-agnostic
842
+ * — the same call opens a SharePoint object, an S3 object or a Box object. Its `Provider` is set from
843
+ * the host form's (D-25), which the service's own docs prescribe for multi-provider clients.
844
+ *
845
+ * The URL is pre-authenticated and time-limited, so the bytes never proxy through MJAPI and the link
846
+ * cannot be shared indefinitely.
847
+ */
848
+ async Open(f) {
849
+ if (!f.AccountID || !f.ProviderKey) {
850
+ this.ActionError = 'This document has no storage path recorded, so there is nothing to open.';
851
+ return;
852
+ }
853
+ this.Busy = true;
854
+ this.ActionError = null;
855
+ try {
856
+ this.fileOpen.Provider = this.FormComponent?.ProviderToUse ?? null;
857
+ const ok = await this.fileOpen.OpenFile(f.AccountID, f.ProviderKey);
858
+ if (!ok)
859
+ this.ActionError = 'The storage provider would not produce a download link for that document.';
860
+ }
861
+ catch (err) {
862
+ this.ActionError = String(err);
863
+ }
864
+ finally {
865
+ this.Busy = false;
866
+ this.cdr.detectChanges();
867
+ }
868
+ }
869
+ static ɵfac = /*@__PURE__*/ (() => { let ɵRecordFilesPanelBase_BaseFactory; return function RecordFilesPanelBase_Factory(__ngFactoryType__) { return (ɵRecordFilesPanelBase_BaseFactory || (ɵRecordFilesPanelBase_BaseFactory = i0.ɵɵgetInheritedFactory(RecordFilesPanelBase)))(__ngFactoryType__ || RecordFilesPanelBase); }; })();
870
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: RecordFilesPanelBase, selectors: [["mjc-record-files-panel"]], features: [i0.ɵɵInheritDefinitionFeature], decls: 20, vars: 9, consts: [["SectionKey", "recordFiles", "SectionName", "Documents", "Icon", "fa-solid fa-folder-open", 3, "Form", "FormContext"], [1, "mjc-body"], [4, "ngIf"], ["class", "mjc-empty", 4, "ngIf"], ["class", "mjc-reg", 4, "ngIf"], ["class", "mjc-note", 4, "ngIf"], [1, "mjc-note"], ["class", "mjc-file-row", 4, "ngFor", "ngForOf"], [1, "mjc-file-row"], ["aria-hidden", "true", 1, "fa-solid", "fa-file-lines", "mjc-file-icon"], [1, "mjc-file-name"], ["type", "button", "class", "mjc-btn mjc-btn--flat mjc-btn--sm", 3, "disabled", "click", 4, "ngIf"], [1, "mjc-file-meta"], ["type", "button", 1, "mjc-btn", "mjc-btn--flat", "mjc-btn--sm", 3, "click", "disabled"], [1, "mjc-empty"], [1, "mjc-reg"], [1, "mjc-reg__title"], [1, "mjc-reg__row"], ["type", "text", "placeholder", "Path in storage, e.g. Contracts/2026/CTR-000004.pdf", "aria-label", "Path of an existing document in storage", 3, "ngModelChange", "ngModel", "disabled"], ["target", "_blank", "rel", "noopener noreferrer", 3, "href"]], template: function RecordFilesPanelBase_Template(rf, ctx) { if (rf & 1) {
871
+ i0.ɵɵelementStart(0, "mj-collapsible-panel", 0)(1, "div", 1);
872
+ i0.ɵɵtemplate(2, RecordFilesPanelBase_div_2_Template, 2, 1, "div", 2)(3, RecordFilesPanelBase_div_3_Template, 2, 0, "div", 3)(4, RecordFilesPanelBase_div_4_Template, 2, 0, "div", 3)(5, RecordFilesPanelBase_div_5_Template, 9, 5, "div", 4)(6, RecordFilesPanelBase_div_6_Template, 4, 1, "div", 5)(7, RecordFilesPanelBase_p_7_Template, 4, 1, "p", 5)(8, RecordFilesPanelBase_p_8_Template, 2, 1, "p", 5);
873
+ i0.ɵɵelementStart(9, "p", 6);
874
+ i0.ɵɵtext(10, " Documents attach through MJ's polymorphic ");
875
+ i0.ɵɵelementStart(11, "code");
876
+ i0.ɵɵtext(12, "__mj.FileEntityRecordLink");
877
+ i0.ɵɵelementEnd();
878
+ i0.ɵɵtext(13, " (");
879
+ i0.ɵɵelementStart(14, "code");
880
+ i0.ɵɵtext(15, "EntityID");
881
+ i0.ɵɵelementEnd();
882
+ i0.ɵɵtext(16, " + ");
883
+ i0.ɵɵelementStart(17, "code");
884
+ i0.ɵɵtext(18, "RecordID");
885
+ i0.ɵɵelementEnd();
886
+ i0.ɵɵtext(19, ") rather than a column on this schema \u2014 so one record can carry the signed PDF, its exhibits and a countersigned amendment, and no future table needs a new column to acquire paper. ");
887
+ i0.ɵɵelementEnd()()();
888
+ } if (rf & 2) {
889
+ i0.ɵɵproperty("Form", ctx.FormComponent)("FormContext", ctx.FormContext);
890
+ i0.ɵɵadvance(2);
891
+ i0.ɵɵproperty("ngIf", ctx.Files.length);
892
+ i0.ɵɵadvance();
893
+ i0.ɵɵproperty("ngIf", !ctx.IsLoading && !ctx.Files.length);
894
+ i0.ɵɵadvance();
895
+ i0.ɵɵproperty("ngIf", ctx.IsLoading);
896
+ i0.ɵɵadvance();
897
+ i0.ɵɵproperty("ngIf", ctx.EditMode && ctx.CanDownload);
898
+ i0.ɵɵadvance();
899
+ i0.ɵɵproperty("ngIf", ctx.ActionError);
900
+ i0.ɵɵadvance();
901
+ i0.ɵɵproperty("ngIf", ctx.SigningProviderURL);
902
+ i0.ɵɵadvance();
903
+ i0.ɵɵproperty("ngIf", !ctx.CanDownload && ctx.Files.length);
904
+ } }, dependencies: [CommonModule, i1.NgForOf, i1.NgIf, FormsModule, i2.DefaultValueAccessor, i2.NgControlStatus, i2.NgModel, BaseFormsModule, i3.MjCollapsiblePanelComponent, i1.DatePipe], styles: [_c0] });
905
+ }
906
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RecordFilesPanelBase, [{
907
+ type: Component,
908
+ args: [{ standalone: true, imports: [CommonModule, FormsModule, BaseFormsModule], selector: 'mjc-record-files-panel', template: PANEL_TEMPLATE, styles: ["\n /* SEMANTIC TOKENS ONLY \u2014 no hex, no bare px for colour/type/space.\n *\n * This panel came forward from v1 and its styles did not: it carried seven hex FALLBACKS\n * inside var() (`var(--mj-text-muted, #64748b)`) and six hardcoded px font sizes. The\n * fallbacks look harmless and are not \u2014 they are the value that renders the moment a token\n * name is wrong, so a typo silently pins a light-mode colour into a dark theme instead of\n * failing visibly. Every rule below now resolves through a token or a kit class. */\n .mjc-file-row {\n display: flex; align-items: center; gap: var(--mj-space-3);\n padding: var(--mj-space-2) 0;\n font-size: var(--mj-text-sm);\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n .mjc-file-row:last-child { border-bottom: none; }\n .mjc-file-row .mjc-file-icon { color: var(--mj-status-error); }\n .mjc-file-row .mjc-file-name { font-weight: 600; overflow-wrap: anywhere; }\n .mjc-file-row .mjc-file-meta {\n margin-left: auto; font-size: var(--mj-text-xs);\n color: var(--mj-text-muted); white-space: nowrap;\n }\n .mjc-reg { margin-top: var(--mj-space-3); }\n .mjc-reg__title {\n font-size: var(--mj-text-xs); font-weight: 600;\n text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); margin-bottom: var(--mj-space-2);\n }\n .mjc-reg__row { display: flex; gap: var(--mj-space-2); align-items: center; flex-wrap: wrap; }\n .mjc-reg__row input { flex: 1 1 22rem; }\n"] }]
909
+ }], null, null); })();
910
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RecordFilesPanelBase, { className: "RecordFilesPanelBase", filePath: "lib/form-panels/record-files.panel.ts", lineNumber: 187 }); })();
911
+ // =====================================================================================
912
+ // The only contracts-specific code in this file: WHICH entities get the panel.
913
+ // One thin subclass per entity, because a class can carry exactly one registration — and each
914
+ // needs its own @Component, so they share the hoisted template/styles rather than duplicating them.
915
+ // =====================================================================================
916
+ let ContractFilesPanel = class ContractFilesPanel extends RecordFilesPanelBase {
917
+ static ɵfac = /*@__PURE__*/ (() => { let ɵContractFilesPanel_BaseFactory; return function ContractFilesPanel_Factory(__ngFactoryType__) { return (ɵContractFilesPanel_BaseFactory || (ɵContractFilesPanel_BaseFactory = i0.ɵɵgetInheritedFactory(ContractFilesPanel)))(__ngFactoryType__ || ContractFilesPanel); }; })();
918
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ContractFilesPanel, selectors: [["mjc-contract-files-panel"]], features: [i0.ɵɵInheritDefinitionFeature], decls: 20, vars: 9, consts: [["SectionKey", "recordFiles", "SectionName", "Documents", "Icon", "fa-solid fa-folder-open", 3, "Form", "FormContext"], [1, "mjc-body"], [4, "ngIf"], ["class", "mjc-empty", 4, "ngIf"], ["class", "mjc-reg", 4, "ngIf"], ["class", "mjc-note", 4, "ngIf"], [1, "mjc-note"], ["class", "mjc-file-row", 4, "ngFor", "ngForOf"], [1, "mjc-file-row"], ["aria-hidden", "true", 1, "fa-solid", "fa-file-lines", "mjc-file-icon"], [1, "mjc-file-name"], ["type", "button", "class", "mjc-btn mjc-btn--flat mjc-btn--sm", 3, "disabled", "click", 4, "ngIf"], [1, "mjc-file-meta"], ["type", "button", 1, "mjc-btn", "mjc-btn--flat", "mjc-btn--sm", 3, "click", "disabled"], [1, "mjc-empty"], [1, "mjc-reg"], [1, "mjc-reg__title"], [1, "mjc-reg__row"], ["type", "text", "placeholder", "Path in storage, e.g. Contracts/2026/CTR-000004.pdf", "aria-label", "Path of an existing document in storage", 3, "ngModelChange", "ngModel", "disabled"], ["target", "_blank", "rel", "noopener noreferrer", 3, "href"]], template: function ContractFilesPanel_Template(rf, ctx) { if (rf & 1) {
919
+ i0.ɵɵelementStart(0, "mj-collapsible-panel", 0)(1, "div", 1);
920
+ i0.ɵɵtemplate(2, ContractFilesPanel_div_2_Template, 2, 1, "div", 2)(3, ContractFilesPanel_div_3_Template, 2, 0, "div", 3)(4, ContractFilesPanel_div_4_Template, 2, 0, "div", 3)(5, ContractFilesPanel_div_5_Template, 9, 5, "div", 4)(6, ContractFilesPanel_div_6_Template, 4, 1, "div", 5)(7, ContractFilesPanel_p_7_Template, 4, 1, "p", 5)(8, ContractFilesPanel_p_8_Template, 2, 1, "p", 5);
921
+ i0.ɵɵelementStart(9, "p", 6);
922
+ i0.ɵɵtext(10, " Documents attach through MJ's polymorphic ");
923
+ i0.ɵɵelementStart(11, "code");
924
+ i0.ɵɵtext(12, "__mj.FileEntityRecordLink");
925
+ i0.ɵɵelementEnd();
926
+ i0.ɵɵtext(13, " (");
927
+ i0.ɵɵelementStart(14, "code");
928
+ i0.ɵɵtext(15, "EntityID");
929
+ i0.ɵɵelementEnd();
930
+ i0.ɵɵtext(16, " + ");
931
+ i0.ɵɵelementStart(17, "code");
932
+ i0.ɵɵtext(18, "RecordID");
933
+ i0.ɵɵelementEnd();
934
+ i0.ɵɵtext(19, ") rather than a column on this schema \u2014 so one record can carry the signed PDF, its exhibits and a countersigned amendment, and no future table needs a new column to acquire paper. ");
935
+ i0.ɵɵelementEnd()()();
936
+ } if (rf & 2) {
937
+ i0.ɵɵproperty("Form", ctx.FormComponent)("FormContext", ctx.FormContext);
938
+ i0.ɵɵadvance(2);
939
+ i0.ɵɵproperty("ngIf", ctx.Files.length);
940
+ i0.ɵɵadvance();
941
+ i0.ɵɵproperty("ngIf", !ctx.IsLoading && !ctx.Files.length);
942
+ i0.ɵɵadvance();
943
+ i0.ɵɵproperty("ngIf", ctx.IsLoading);
944
+ i0.ɵɵadvance();
945
+ i0.ɵɵproperty("ngIf", ctx.EditMode && ctx.CanDownload);
946
+ i0.ɵɵadvance();
947
+ i0.ɵɵproperty("ngIf", ctx.ActionError);
948
+ i0.ɵɵadvance();
949
+ i0.ɵɵproperty("ngIf", ctx.SigningProviderURL);
950
+ i0.ɵɵadvance();
951
+ i0.ɵɵproperty("ngIf", !ctx.CanDownload && ctx.Files.length);
952
+ } }, dependencies: [CommonModule, i1.NgForOf, i1.NgIf, FormsModule, i2.DefaultValueAccessor, i2.NgControlStatus, i2.NgModel, BaseFormsModule, i3.MjCollapsiblePanelComponent, i1.DatePipe], styles: [_c0] });
953
+ };
954
+ ContractFilesPanel = __decorate([
955
+ RegisterClassEx(BaseFormPanel, {
956
+ key: 'contracts:record-files:contract',
957
+ skipNullKeyWarning: true,
958
+ metadata: {
959
+ entity: 'MJ_BizApps_Contracts: Contracts',
960
+ slot: 'after-fields',
961
+ sortKey: 60,
962
+ // contributionKey MUST equal this panel's SectionKey — the container resolves chrome by the
963
+ // former and builds rail groups from the latter. Without a contributionKey at all,
964
+ // `contributionRailKey()` returns null for a panel with no `relatedEntity` and chrome skips it,
965
+ // so Documents could never be a rail item however it was configured.
966
+ contributionKey: 'recordFiles',
967
+ // No `inclusion: 'Primary'` — see the long note in contract.panels.ts: Primary means
968
+ // "promote ahead of Details", which is the opposite of what this form wants.
969
+ },
970
+ })
971
+ ], ContractFilesPanel);
972
+ export { ContractFilesPanel };
973
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ContractFilesPanel, [{
974
+ type: Component,
975
+ args: [{ standalone: true, imports: [CommonModule, FormsModule, BaseFormsModule], selector: 'mjc-contract-files-panel', template: PANEL_TEMPLATE, styles: ["\n /* SEMANTIC TOKENS ONLY \u2014 no hex, no bare px for colour/type/space.\n *\n * This panel came forward from v1 and its styles did not: it carried seven hex FALLBACKS\n * inside var() (`var(--mj-text-muted, #64748b)`) and six hardcoded px font sizes. The\n * fallbacks look harmless and are not \u2014 they are the value that renders the moment a token\n * name is wrong, so a typo silently pins a light-mode colour into a dark theme instead of\n * failing visibly. Every rule below now resolves through a token or a kit class. */\n .mjc-file-row {\n display: flex; align-items: center; gap: var(--mj-space-3);\n padding: var(--mj-space-2) 0;\n font-size: var(--mj-text-sm);\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n .mjc-file-row:last-child { border-bottom: none; }\n .mjc-file-row .mjc-file-icon { color: var(--mj-status-error); }\n .mjc-file-row .mjc-file-name { font-weight: 600; overflow-wrap: anywhere; }\n .mjc-file-row .mjc-file-meta {\n margin-left: auto; font-size: var(--mj-text-xs);\n color: var(--mj-text-muted); white-space: nowrap;\n }\n .mjc-reg { margin-top: var(--mj-space-3); }\n .mjc-reg__title {\n font-size: var(--mj-text-xs); font-weight: 600;\n text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); margin-bottom: var(--mj-space-2);\n }\n .mjc-reg__row { display: flex; gap: var(--mj-space-2); align-items: center; flex-wrap: wrap; }\n .mjc-reg__row input { flex: 1 1 22rem; }\n"] }]
976
+ }], null, null); })();
977
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ContractFilesPanel, { className: "ContractFilesPanel", filePath: "lib/form-panels/record-files.panel.ts", lineNumber: 526 }); })();
978
+ let ContractTemplateFilesPanel = class ContractTemplateFilesPanel extends RecordFilesPanelBase {
979
+ static ɵfac = /*@__PURE__*/ (() => { let ɵContractTemplateFilesPanel_BaseFactory; return function ContractTemplateFilesPanel_Factory(__ngFactoryType__) { return (ɵContractTemplateFilesPanel_BaseFactory || (ɵContractTemplateFilesPanel_BaseFactory = i0.ɵɵgetInheritedFactory(ContractTemplateFilesPanel)))(__ngFactoryType__ || ContractTemplateFilesPanel); }; })();
980
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ContractTemplateFilesPanel, selectors: [["mjc-template-files-panel"]], features: [i0.ɵɵInheritDefinitionFeature], decls: 20, vars: 9, consts: [["SectionKey", "recordFiles", "SectionName", "Documents", "Icon", "fa-solid fa-folder-open", 3, "Form", "FormContext"], [1, "mjc-body"], [4, "ngIf"], ["class", "mjc-empty", 4, "ngIf"], ["class", "mjc-reg", 4, "ngIf"], ["class", "mjc-note", 4, "ngIf"], [1, "mjc-note"], ["class", "mjc-file-row", 4, "ngFor", "ngForOf"], [1, "mjc-file-row"], ["aria-hidden", "true", 1, "fa-solid", "fa-file-lines", "mjc-file-icon"], [1, "mjc-file-name"], ["type", "button", "class", "mjc-btn mjc-btn--flat mjc-btn--sm", 3, "disabled", "click", 4, "ngIf"], [1, "mjc-file-meta"], ["type", "button", 1, "mjc-btn", "mjc-btn--flat", "mjc-btn--sm", 3, "click", "disabled"], [1, "mjc-empty"], [1, "mjc-reg"], [1, "mjc-reg__title"], [1, "mjc-reg__row"], ["type", "text", "placeholder", "Path in storage, e.g. Contracts/2026/CTR-000004.pdf", "aria-label", "Path of an existing document in storage", 3, "ngModelChange", "ngModel", "disabled"], ["target", "_blank", "rel", "noopener noreferrer", 3, "href"]], template: function ContractTemplateFilesPanel_Template(rf, ctx) { if (rf & 1) {
981
+ i0.ɵɵelementStart(0, "mj-collapsible-panel", 0)(1, "div", 1);
982
+ i0.ɵɵtemplate(2, ContractTemplateFilesPanel_div_2_Template, 2, 1, "div", 2)(3, ContractTemplateFilesPanel_div_3_Template, 2, 0, "div", 3)(4, ContractTemplateFilesPanel_div_4_Template, 2, 0, "div", 3)(5, ContractTemplateFilesPanel_div_5_Template, 9, 5, "div", 4)(6, ContractTemplateFilesPanel_div_6_Template, 4, 1, "div", 5)(7, ContractTemplateFilesPanel_p_7_Template, 4, 1, "p", 5)(8, ContractTemplateFilesPanel_p_8_Template, 2, 1, "p", 5);
983
+ i0.ɵɵelementStart(9, "p", 6);
984
+ i0.ɵɵtext(10, " Documents attach through MJ's polymorphic ");
985
+ i0.ɵɵelementStart(11, "code");
986
+ i0.ɵɵtext(12, "__mj.FileEntityRecordLink");
987
+ i0.ɵɵelementEnd();
988
+ i0.ɵɵtext(13, " (");
989
+ i0.ɵɵelementStart(14, "code");
990
+ i0.ɵɵtext(15, "EntityID");
991
+ i0.ɵɵelementEnd();
992
+ i0.ɵɵtext(16, " + ");
993
+ i0.ɵɵelementStart(17, "code");
994
+ i0.ɵɵtext(18, "RecordID");
995
+ i0.ɵɵelementEnd();
996
+ i0.ɵɵtext(19, ") rather than a column on this schema \u2014 so one record can carry the signed PDF, its exhibits and a countersigned amendment, and no future table needs a new column to acquire paper. ");
997
+ i0.ɵɵelementEnd()()();
998
+ } if (rf & 2) {
999
+ i0.ɵɵproperty("Form", ctx.FormComponent)("FormContext", ctx.FormContext);
1000
+ i0.ɵɵadvance(2);
1001
+ i0.ɵɵproperty("ngIf", ctx.Files.length);
1002
+ i0.ɵɵadvance();
1003
+ i0.ɵɵproperty("ngIf", !ctx.IsLoading && !ctx.Files.length);
1004
+ i0.ɵɵadvance();
1005
+ i0.ɵɵproperty("ngIf", ctx.IsLoading);
1006
+ i0.ɵɵadvance();
1007
+ i0.ɵɵproperty("ngIf", ctx.EditMode && ctx.CanDownload);
1008
+ i0.ɵɵadvance();
1009
+ i0.ɵɵproperty("ngIf", ctx.ActionError);
1010
+ i0.ɵɵadvance();
1011
+ i0.ɵɵproperty("ngIf", ctx.SigningProviderURL);
1012
+ i0.ɵɵadvance();
1013
+ i0.ɵɵproperty("ngIf", !ctx.CanDownload && ctx.Files.length);
1014
+ } }, dependencies: [CommonModule, i1.NgForOf, i1.NgIf, FormsModule, i2.DefaultValueAccessor, i2.NgControlStatus, i2.NgModel, BaseFormsModule, i3.MjCollapsiblePanelComponent, i1.DatePipe], styles: [_c0] });
1015
+ };
1016
+ ContractTemplateFilesPanel = __decorate([
1017
+ RegisterClassEx(BaseFormPanel, {
1018
+ key: 'contracts:record-files:template',
1019
+ skipNullKeyWarning: true,
1020
+ metadata: {
1021
+ entity: 'MJ_BizApps_Contracts: Contract Templates',
1022
+ slot: 'after-fields',
1023
+ sortKey: 60,
1024
+ contributionKey: 'recordFiles',
1025
+ // NOT 'Primary' here, unlike on the Contract form — ruled 2026-08-19: the template rail must
1026
+ // read Details, then Provisions.
1027
+ //
1028
+ // MJ's rail band order is fixed: lead contributions → Details → Primary related → More. And a
1029
+ // Primary contribution is ALWAYS a lead (`isLeadContribution` requires inclusion === 'Primary'
1030
+ // with no chromeGroup), so there is no configuration where Documents keeps a first-class rail
1031
+ // item and still sits after Details — Primary here put Documents FIRST.
1032
+ //
1033
+ // 'more' is the honest resolution rather than a compromise: it stays reachable under the More
1034
+ // folder, and a template version's source PDF genuinely is secondary to its clause list. The
1035
+ // Contract form keeps Documents Primary, where paper is the point.
1036
+ chromeGroup: 'more',
1037
+ },
1038
+ })
1039
+ ], ContractTemplateFilesPanel);
1040
+ export { ContractTemplateFilesPanel };
1041
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ContractTemplateFilesPanel, [{
1042
+ type: Component,
1043
+ args: [{ standalone: true, imports: [CommonModule, FormsModule, BaseFormsModule], selector: 'mjc-template-files-panel', template: PANEL_TEMPLATE, styles: ["\n /* SEMANTIC TOKENS ONLY \u2014 no hex, no bare px for colour/type/space.\n *\n * This panel came forward from v1 and its styles did not: it carried seven hex FALLBACKS\n * inside var() (`var(--mj-text-muted, #64748b)`) and six hardcoded px font sizes. The\n * fallbacks look harmless and are not \u2014 they are the value that renders the moment a token\n * name is wrong, so a typo silently pins a light-mode colour into a dark theme instead of\n * failing visibly. Every rule below now resolves through a token or a kit class. */\n .mjc-file-row {\n display: flex; align-items: center; gap: var(--mj-space-3);\n padding: var(--mj-space-2) 0;\n font-size: var(--mj-text-sm);\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n .mjc-file-row:last-child { border-bottom: none; }\n .mjc-file-row .mjc-file-icon { color: var(--mj-status-error); }\n .mjc-file-row .mjc-file-name { font-weight: 600; overflow-wrap: anywhere; }\n .mjc-file-row .mjc-file-meta {\n margin-left: auto; font-size: var(--mj-text-xs);\n color: var(--mj-text-muted); white-space: nowrap;\n }\n .mjc-reg { margin-top: var(--mj-space-3); }\n .mjc-reg__title {\n font-size: var(--mj-text-xs); font-weight: 600;\n text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); margin-bottom: var(--mj-space-2);\n }\n .mjc-reg__row { display: flex; gap: var(--mj-space-2); align-items: center; flex-wrap: wrap; }\n .mjc-reg__row input { flex: 1 1 22rem; }\n"] }]
1044
+ }], null, null); })();
1045
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ContractTemplateFilesPanel, { className: "ContractTemplateFilesPanel", filePath: "lib/form-panels/record-files.panel.ts", lineNumber: 557 }); })();
1046
+ //# sourceMappingURL=record-files.panel.js.map