jupiter-dynamic-forms 1.17.6 → 1.17.8

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/dist/index.mjs CHANGED
@@ -569,6 +569,14 @@ class XBRLFormBuilder {
569
569
  }
570
570
  return Math.abs(hash).toString(36).substr(0, 6);
571
571
  }
572
+ static _extractDocumentationLabels(labels) {
573
+ const result = {};
574
+ const docRole = "http://www.xbrl.org/2003/role/documentation";
575
+ labels.filter((l2) => l2.role === docRole).forEach((l2) => {
576
+ result[l2.lang] = l2.label;
577
+ });
578
+ return result;
579
+ }
572
580
  /**
573
581
  * Build form schema from XBRL input data
574
582
  * Creates accordion sections for each presentation role
@@ -627,9 +635,10 @@ class XBRLFormBuilder {
627
635
  const availableColumnIds = columns.map((col) => col.id);
628
636
  const roleInfo = { periodTypes, availableColumnIds, availableColumns: columns };
629
637
  const conceptTrees = [];
638
+ const conceptIdOccurrences = /* @__PURE__ */ new Map();
630
639
  if ((_a = role.presentationLinkbase) == null ? void 0 : _a.concepts) {
631
640
  role.presentationLinkbase.concepts.forEach((concept) => {
632
- const conceptTree = this.buildConceptTree(concept, 0, periodStartDate, periodEndDate, roleInfo, role.id, language);
641
+ const conceptTree = this.buildConceptTree(concept, 0, periodStartDate, periodEndDate, roleInfo, role.id, language, conceptIdOccurrences);
633
642
  if (conceptTree) {
634
643
  conceptTrees.push(conceptTree);
635
644
  }
@@ -663,8 +672,10 @@ class XBRLFormBuilder {
663
672
  /**
664
673
  * Build concept tree from XBRL presentation concept
665
674
  */
666
- static buildConceptTree(concept, level, periodStartDate, periodEndDate, roleInfo, sectionId, language = "en") {
675
+ static buildConceptTree(concept, level, periodStartDate, periodEndDate, roleInfo, sectionId, language = "en", conceptIdOccurrences, ancestorPeriodHint) {
676
+ var _a, _b;
667
677
  const label = this.getPreferredLabel(concept.labels, language);
678
+ const childPeriodHint = ((_a = concept.preferredLabel) == null ? void 0 : _a.includes("periodStartLabel")) ? "start" : ((_b = concept.preferredLabel) == null ? void 0 : _b.includes("periodEndLabel")) ? "end" : ancestorPeriodHint;
668
679
  const fields = [];
669
680
  if (!concept.elementAbstract) {
670
681
  let columnIds = [];
@@ -674,11 +685,11 @@ class XBRLFormBuilder {
674
685
  columnIds = ["duration"];
675
686
  }
676
687
  columnIds.forEach((columnId) => {
677
- var _a;
678
- const column2 = (_a = roleInfo == null ? void 0 : roleInfo.availableColumns) == null ? void 0 : _a.find((c2) => c2.id === columnId);
688
+ var _a2;
689
+ const column2 = (_a2 = roleInfo == null ? void 0 : roleInfo.availableColumns) == null ? void 0 : _a2.find((c2) => c2.id === columnId);
679
690
  const colStartDate = (column2 == null ? void 0 : column2.periodStartDate) || periodStartDate;
680
691
  const colEndDate = (column2 == null ? void 0 : column2.periodEndDate) || periodEndDate;
681
- const field2 = this.createFieldFromConcept(concept, colStartDate, colEndDate, columnId);
692
+ const field2 = this.createFieldFromConcept(concept, colStartDate, colEndDate, columnId, ancestorPeriodHint);
682
693
  if (field2)
683
694
  fields.push(field2);
684
695
  });
@@ -686,7 +697,7 @@ class XBRLFormBuilder {
686
697
  const children = [];
687
698
  if (concept.children && concept.children.length > 0) {
688
699
  concept.children.forEach((child) => {
689
- const childTree = this.buildConceptTree(child, level + 1, periodStartDate, periodEndDate, roleInfo, sectionId, language);
700
+ const childTree = this.buildConceptTree(child, level + 1, periodStartDate, periodEndDate, roleInfo, sectionId, language, conceptIdOccurrences, childPeriodHint);
690
701
  if (childTree) {
691
702
  children.push(childTree);
692
703
  }
@@ -696,9 +707,24 @@ class XBRLFormBuilder {
696
707
  if (!hasContent) {
697
708
  return null;
698
709
  }
710
+ let conceptId = this.createUniqueConceptId(concept, sectionId);
711
+ if (conceptIdOccurrences) {
712
+ const count = conceptIdOccurrences.get(conceptId) ?? 0;
713
+ conceptIdOccurrences.set(conceptId, count + 1);
714
+ if (count > 0) {
715
+ conceptId = `${conceptId}__occ${count + 1}`;
716
+ }
717
+ }
718
+ const conceptMetadata = {
719
+ conceptName: concept.conceptName,
720
+ type: concept.type,
721
+ substitutionGroup: concept.substitutionGroup,
722
+ periodType: concept.periodType,
723
+ balance: concept.balance,
724
+ documentationLabels: this._extractDocumentationLabels(concept.labels)
725
+ };
699
726
  return {
700
- id: this.createUniqueConceptId(concept, sectionId),
701
- // Use unique ID for form management
727
+ id: conceptId,
702
728
  originalConceptId: concept.id,
703
729
  // Store original for submission data
704
730
  name: concept.conceptName,
@@ -709,6 +735,7 @@ class XBRLFormBuilder {
709
735
  level,
710
736
  children,
711
737
  fields,
738
+ conceptMetadata,
712
739
  facts: concept.facts || [],
713
740
  // Pass facts array from presentation concept for pre-population
714
741
  collapsed: level > 0,
@@ -724,7 +751,7 @@ class XBRLFormBuilder {
724
751
  /**
725
752
  * Create form field from XBRL concept
726
753
  */
727
- static createFieldFromConcept(concept, periodStartDate, periodEndDate, forcedColumnId) {
754
+ static createFieldFromConcept(concept, periodStartDate, periodEndDate, forcedColumnId, ancestorPeriodHint) {
728
755
  var _a;
729
756
  const fieldType = this.mapXBRLTypeToFieldType(concept.type);
730
757
  const label = this.getPreferredLabel(concept.labels);
@@ -750,7 +777,7 @@ class XBRLFormBuilder {
750
777
  // Validate and set the period type
751
778
  periodStartDate: periodStartDate || "2025-01-01",
752
779
  periodEndDate: periodEndDate || "2025-12-31",
753
- periodInstantDate: concept.periodType === "instant" ? ((_a = concept.preferredLabel) == null ? void 0 : _a.includes("periodStartLabel")) ? this.subtractOneDay(periodStartDate || "2025-01-01") : periodEndDate || periodStartDate || "2025-01-01" : void 0
780
+ periodInstantDate: concept.periodType === "instant" ? ((_a = concept.preferredLabel) == null ? void 0 : _a.includes("periodStartLabel")) || ancestorPeriodHint === "start" ? this.subtractOneDay(periodStartDate || "2025-01-01") : periodEndDate || periodStartDate || "2025-01-01" : void 0
754
781
  };
755
782
  if (concept.id === "nl-cd_DescriptionLocationNL__a64trl") {
756
783
  console.log(`đŸ—ī¸ [Field Creation] Concept: ${concept.id}, periodType from concept: ${concept.periodType}, Field periodType: ${field2.periodType}, ColumnId: ${columnId}`);
@@ -1595,6 +1622,18 @@ class XBRLFormBuilder {
1595
1622
  }];
1596
1623
  }
1597
1624
  }
1625
+ const conceptInfo$1 = {
1626
+ title: "Concept Information",
1627
+ conceptName: "Concept name",
1628
+ type: "Type",
1629
+ substitutionGroup: "Substitution group",
1630
+ periodType: "Period type",
1631
+ balance: "Balance",
1632
+ documentation: "Documentation",
1633
+ instant: "Instant",
1634
+ duration: "Duration",
1635
+ close: "Close"
1636
+ };
1598
1637
  const form$1 = {
1599
1638
  loading: "Loading form...",
1600
1639
  submit: "Validate",
@@ -1739,6 +1778,7 @@ const error$1 = {
1739
1778
  }
1740
1779
  };
1741
1780
  const enTranslations = {
1781
+ conceptInfo: conceptInfo$1,
1742
1782
  form: form$1,
1743
1783
  filter: filter$1,
1744
1784
  column: column$1,
@@ -1749,6 +1789,18 @@ const enTranslations = {
1749
1789
  xbrlValidation: xbrlValidation$1,
1750
1790
  error: error$1
1751
1791
  };
1792
+ const conceptInfo = {
1793
+ title: "Conceptinformatie",
1794
+ conceptName: "Conceptnaam",
1795
+ type: "Type",
1796
+ substitutionGroup: "Substitutiegroep",
1797
+ periodType: "Periodetype",
1798
+ balance: "Saldo",
1799
+ documentation: "Documentatie",
1800
+ instant: "Moment",
1801
+ duration: "Periode",
1802
+ close: "Sluiten"
1803
+ };
1752
1804
  const form = {
1753
1805
  loading: "Formulier laden...",
1754
1806
  submit: "Valideren",
@@ -1893,6 +1945,7 @@ const error = {
1893
1945
  }
1894
1946
  };
1895
1947
  const nlTranslations = {
1948
+ conceptInfo,
1896
1949
  form,
1897
1950
  filter,
1898
1951
  column,
@@ -2139,7 +2192,7 @@ class DraftStorageService {
2139
2192
  /**
2140
2193
  * Create metadata snapshot from current form state
2141
2194
  */
2142
- createMetadataSnapshot(periodStartDate, periodEndDate, language, selectedRoleIds, allSections, typedMemberData, periodPreferences, periodData, unitData, reportingLanguage = "en", repeatCounts, decimalsData) {
2195
+ createMetadataSnapshot(periodStartDate, periodEndDate, language, selectedRoleIds, allSections, typedMemberData, periodPreferences, periodData, unitData, reportingLanguage = "en", repeatCounts, decimalsData, roleCompletedStates) {
2143
2196
  return {
2144
2197
  periodStartDate,
2145
2198
  periodEndDate,
@@ -2154,6 +2207,7 @@ class DraftStorageService {
2154
2207
  unitData,
2155
2208
  decimalsData,
2156
2209
  repeatCounts,
2210
+ roleCompletedStates,
2157
2211
  schemaVersion: this.STORAGE_VERSION
2158
2212
  };
2159
2213
  }
@@ -4293,6 +4347,7 @@ let JupiterConceptTree = class extends LitElement {
4293
4347
  this.typedMemberData = {};
4294
4348
  this.showAddButton = false;
4295
4349
  this.showRemoveButton = false;
4350
+ this.language = "en";
4296
4351
  this._expanded = true;
4297
4352
  }
4298
4353
  connectedCallback() {
@@ -4362,6 +4417,108 @@ let JupiterConceptTree = class extends LitElement {
4362
4417
  }));
4363
4418
  console.log(`🌲 [ConceptTree] Forwarded period-change event to form-section`);
4364
4419
  }
4420
+ _getDocLabel() {
4421
+ const meta = this.concept.conceptMetadata;
4422
+ if (!meta)
4423
+ return "";
4424
+ const lang = this.language || this.locale.split("-")[0];
4425
+ return meta.documentationLabels[lang] || meta.documentationLabels["en"] || meta.documentationLabels["nl"] || "";
4426
+ }
4427
+ _escapeHtml(str) {
4428
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
4429
+ }
4430
+ _ensureDialogStyles() {
4431
+ if (document.getElementById("jdf-concept-info-dialog-styles"))
4432
+ return;
4433
+ const style = document.createElement("style");
4434
+ style.id = "jdf-concept-info-dialog-styles";
4435
+ style.textContent = `
4436
+ dialog.jdf-concept-info-dialog {
4437
+ border: none; border-radius: 8px; padding: 0;
4438
+ max-width: 480px; width: 90vw;
4439
+ box-shadow: 0 8px 32px rgba(0,0,0,0.2); overflow: hidden;
4440
+ }
4441
+ dialog.jdf-concept-info-dialog::backdrop { background: rgba(0,0,0,0.35); }
4442
+ .jdf-dialog-header {
4443
+ background: #f0f2f5; padding: 14px 20px;
4444
+ display: flex; align-items: center; justify-content: space-between;
4445
+ border-bottom: 1px solid #ddd;
4446
+ }
4447
+ .jdf-dialog-title { font-size: 15px; font-weight: 600; color: #333; margin: 0; font-family: inherit; }
4448
+ .jdf-dialog-close-btn {
4449
+ width: 28px; height: 28px; border: none; background: transparent;
4450
+ font-size: 20px; line-height: 1; cursor: pointer; color: #666;
4451
+ border-radius: 4px; display: flex; align-items: center; justify-content: center;
4452
+ padding: 0; font-family: inherit;
4453
+ }
4454
+ .jdf-dialog-close-btn:hover { background: #ddd; color: #333; }
4455
+ .jdf-dialog-body { padding: 20px; }
4456
+ .jdf-info-table { width: 100%; border-collapse: collapse; font-size: 14px; font-family: inherit; }
4457
+ .jdf-info-table tr:not(:last-child) td { border-bottom: 1px solid #f0f0f0; }
4458
+ .jdf-info-table td { padding: 9px 4px; vertical-align: top; }
4459
+ .jdf-info-label { font-weight: 600; color: #666; width: 40%; white-space: nowrap; padding-right: 16px; }
4460
+ .jdf-info-value { color: #333; word-break: break-all; }
4461
+ .jdf-info-value.doc-text { font-style: italic; color: #555; line-height: 1.5; word-break: normal; }
4462
+ .jdf-period-pill { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 12px; font-weight: 600; }
4463
+ .jdf-period-pill.instant { background: rgba(25,118,210,0.1); color: #1565c0; border: 1px solid rgba(25,118,210,0.3); }
4464
+ .jdf-period-pill.duration { background: rgba(56,142,60,0.1); color: #2e7d32; border: 1px solid rgba(56,142,60,0.3); }
4465
+ .jdf-balance-pill { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 12px; font-weight: 600; text-transform: capitalize; }
4466
+ .jdf-balance-pill.debit { background: rgba(211,47,47,0.1); color: #c62828; border: 1px solid rgba(211,47,47,0.3); }
4467
+ .jdf-balance-pill.credit { background: rgba(56,142,60,0.1); color: #2e7d32; border: 1px solid rgba(56,142,60,0.3); }
4468
+ `;
4469
+ document.head.appendChild(style);
4470
+ }
4471
+ _openInfoDialog(e2) {
4472
+ var _a;
4473
+ e2.stopPropagation();
4474
+ this._ensureDialogStyles();
4475
+ const meta = this.concept.conceptMetadata;
4476
+ const docLabel = this._getDocLabel();
4477
+ const periodType = (meta == null ? void 0 : meta.periodType) || this.concept.periodType;
4478
+ const balance = (meta == null ? void 0 : meta.balance) || this.concept.balance;
4479
+ const periodLabel = periodType === "instant" ? I18n.t("conceptInfo.instant") : I18n.t("conceptInfo.duration");
4480
+ const rows = [
4481
+ { label: I18n.t("conceptInfo.conceptName"), valueHtml: this._escapeHtml((meta == null ? void 0 : meta.conceptName) || this.concept.name) },
4482
+ { label: I18n.t("conceptInfo.type"), valueHtml: this._escapeHtml((meta == null ? void 0 : meta.type) || this.concept.type || "—") }
4483
+ ];
4484
+ if (meta == null ? void 0 : meta.substitutionGroup) {
4485
+ rows.push({ label: I18n.t("conceptInfo.substitutionGroup"), valueHtml: this._escapeHtml(meta.substitutionGroup) });
4486
+ }
4487
+ rows.push({ label: I18n.t("conceptInfo.periodType"), valueHtml: `<span class="jdf-period-pill ${periodType}">${this._escapeHtml(periodLabel)}</span>` });
4488
+ if (balance) {
4489
+ rows.push({ label: I18n.t("conceptInfo.balance"), valueHtml: `<span class="jdf-balance-pill ${balance}">${this._escapeHtml(balance)}</span>` });
4490
+ }
4491
+ if (docLabel) {
4492
+ rows.push({ label: I18n.t("conceptInfo.documentation"), valueHtml: this._escapeHtml(docLabel), isDoc: true });
4493
+ }
4494
+ const rowsHtml = rows.map((r2) => `
4495
+ <tr>
4496
+ <td class="jdf-info-label">${this._escapeHtml(r2.label)}</td>
4497
+ <td class="jdf-info-value${r2.isDoc ? " doc-text" : ""}">${r2.valueHtml}</td>
4498
+ </tr>
4499
+ `).join("");
4500
+ const dialog = document.createElement("dialog");
4501
+ dialog.className = "jdf-concept-info-dialog";
4502
+ dialog.innerHTML = `
4503
+ <div class="jdf-dialog-header">
4504
+ <h3 class="jdf-dialog-title">${this._escapeHtml(I18n.t("conceptInfo.title"))}</h3>
4505
+ <button class="jdf-dialog-close-btn" type="button" aria-label="${this._escapeHtml(I18n.t("conceptInfo.close"))}">×</button>
4506
+ </div>
4507
+ <div class="jdf-dialog-body">
4508
+ <table class="jdf-info-table">${rowsHtml}</table>
4509
+ </div>
4510
+ `;
4511
+ document.body.appendChild(dialog);
4512
+ (_a = dialog.querySelector(".jdf-dialog-close-btn")) == null ? void 0 : _a.addEventListener("click", () => dialog.close());
4513
+ dialog.addEventListener("click", (ev) => {
4514
+ const rect = dialog.getBoundingClientRect();
4515
+ if (ev.clientX < rect.left || ev.clientX > rect.right || ev.clientY < rect.top || ev.clientY > rect.bottom) {
4516
+ dialog.close();
4517
+ }
4518
+ });
4519
+ dialog.addEventListener("close", () => document.body.removeChild(dialog));
4520
+ dialog.showModal();
4521
+ }
4365
4522
  render() {
4366
4523
  const hasChildren = this.concept.children && this.concept.children.length > 0;
4367
4524
  const level = this.concept.level || 0;
@@ -4383,6 +4540,8 @@ let JupiterConceptTree = class extends LitElement {
4383
4540
  ${this.concept.balance ? html`
4384
4541
  <div class="concept-balance ${this.concept.balance}">${this.concept.balance}</div>
4385
4542
  ` : ""}
4543
+ <button class="concept-info-btn" type="button" title="${I18n.t("conceptInfo.title")}"
4544
+ @click="${this._openInfoDialog}">ℹ</button>
4386
4545
  ${this.showAddButton ? html`
4387
4546
  <button class="repeat-btn" type="button" title="Add row"
4388
4547
  @click="${this._handleAddRepeat}">+</button>
@@ -4434,6 +4593,7 @@ let JupiterConceptTree = class extends LitElement {
4434
4593
  </td>
4435
4594
  `;
4436
4595
  })}
4596
+
4437
4597
  `;
4438
4598
  }
4439
4599
  };
@@ -4607,6 +4767,36 @@ JupiterConceptTree.styles = css`
4607
4767
  box-shadow: inset 0 0 0 2px var(--jupiter-error-color, #d32f2f);
4608
4768
  }
4609
4769
 
4770
+ .concept-info-btn {
4771
+ flex-shrink: 0;
4772
+ width: 18px;
4773
+ height: 18px;
4774
+ border-radius: 50%;
4775
+ border: 1px solid transparent;
4776
+ background: transparent;
4777
+ color: var(--jupiter-text-secondary, #888);
4778
+ font-size: 12px;
4779
+ cursor: pointer;
4780
+ display: flex;
4781
+ align-items: center;
4782
+ justify-content: center;
4783
+ opacity: 0;
4784
+ transition: opacity 0.15s, background 0.15s, color 0.15s;
4785
+ padding: 0;
4786
+ margin-left: 4px;
4787
+ line-height: 1;
4788
+ }
4789
+
4790
+ .concept-content:hover .concept-info-btn {
4791
+ opacity: 1;
4792
+ }
4793
+
4794
+ .concept-info-btn:hover {
4795
+ background: var(--jupiter-primary-color, #1976d2);
4796
+ color: #fff;
4797
+ border-color: var(--jupiter-primary-color, #1976d2);
4798
+ }
4799
+
4610
4800
  `;
4611
4801
  __decorateClass$5([
4612
4802
  n2({ type: Object })
@@ -4671,6 +4861,9 @@ __decorateClass$5([
4671
4861
  __decorateClass$5([
4672
4862
  n2({ type: Object })
4673
4863
  ], JupiterConceptTree.prototype, "calculationErrorKeys", 2);
4864
+ __decorateClass$5([
4865
+ n2({ type: String })
4866
+ ], JupiterConceptTree.prototype, "language", 2);
4674
4867
  __decorateClass$5([
4675
4868
  r()
4676
4869
  ], JupiterConceptTree.prototype, "_expanded", 2);
@@ -5340,6 +5533,7 @@ let JupiterFormSection = class extends LitElement {
5340
5533
  this.showFactsOnly = false;
5341
5534
  this.periodStartDate = "";
5342
5535
  this.periodEndDate = "";
5536
+ this.language = "en";
5343
5537
  this._expanded = true;
5344
5538
  this._showAddColumnDialog = false;
5345
5539
  this._sectionPeriodType = "duration";
@@ -5767,6 +5961,7 @@ let JupiterFormSection = class extends LitElement {
5767
5961
  .defaultUnits="${this.defaultUnits}"
5768
5962
  .disabled="${this.disabled}"
5769
5963
  .locale="${this.locale}"
5964
+ .language="${this.language}"
5770
5965
  .expandedConcepts="${this._expandedConcepts}"
5771
5966
  .mode="${this.mode}"
5772
5967
  .masterData="${this.masterData}"
@@ -6284,6 +6479,9 @@ __decorateClass$3([
6284
6479
  __decorateClass$3([
6285
6480
  n2({ type: String })
6286
6481
  ], JupiterFormSection.prototype, "periodEndDate", 2);
6482
+ __decorateClass$3([
6483
+ n2({ type: String })
6484
+ ], JupiterFormSection.prototype, "language", 2);
6287
6485
  __decorateClass$3([
6288
6486
  r()
6289
6487
  ], JupiterFormSection.prototype, "_expanded", 2);
@@ -8203,6 +8401,7 @@ let JupiterDynamicForm = class extends LitElement {
8203
8401
  this._sidePanelCollapsed = false;
8204
8402
  this._adminRoleConfigs = {};
8205
8403
  this._roleBorderStatuses = /* @__PURE__ */ new Map();
8404
+ this._roleCompletedStates = /* @__PURE__ */ new Map();
8206
8405
  this._showRoleContextMenu = false;
8207
8406
  this._contextMenuX = 0;
8208
8407
  this._contextMenuY = 0;
@@ -9921,6 +10120,7 @@ let JupiterDynamicForm = class extends LitElement {
9921
10120
  draftData.forEach((entry, index) => {
9922
10121
  console.log(` [${index}] conceptId: ${entry.conceptId}, columnId: ${entry.columnId}, value: ${entry.value}, unit: ${entry.unit || "none"}`);
9923
10122
  });
10123
+ const roleCompletedStates = this._roleCompletedStates.size > 0 ? Object.fromEntries(this._roleCompletedStates) : void 0;
9924
10124
  const metadata = this._draftStorageService.createMetadataSnapshot(
9925
10125
  this.periodStartDate,
9926
10126
  this.periodEndDate,
@@ -9934,7 +10134,8 @@ let JupiterDynamicForm = class extends LitElement {
9934
10134
  this._unitData,
9935
10135
  this.reportingLanguage,
9936
10136
  this._repeatCounts,
9937
- this._decimalsData
10137
+ this._decimalsData,
10138
+ roleCompletedStates
9938
10139
  );
9939
10140
  const draftPayloadSnapshot = JSON.stringify({
9940
10141
  draftData,
@@ -10064,6 +10265,10 @@ let JupiterDynamicForm = class extends LitElement {
10064
10265
  this._decimalsData = metadata.decimalsData;
10065
10266
  console.log("🔄 Restored per-fact decimals data:", Object.keys(this._decimalsData).length, "concepts");
10066
10267
  }
10268
+ if (metadata.roleCompletedStates) {
10269
+ this._roleCompletedStates = new Map(Object.entries(metadata.roleCompletedStates));
10270
+ console.log("🔄 Restored role completed states:", Object.keys(metadata.roleCompletedStates).length, "roles");
10271
+ }
10067
10272
  if (metadata.periodPreferences) {
10068
10273
  if (this._skipPeriodPreferencesRestore) {
10069
10274
  console.log("â­ī¸ Skipping period preferences restoration - using new filter selections");
@@ -11065,6 +11270,15 @@ let JupiterDynamicForm = class extends LitElement {
11065
11270
  console.log(`â„šī¸ User can re-add this role using the Filter Roles dialog`);
11066
11271
  this._closeRoleContextMenu();
11067
11272
  }
11273
+ _handleRoleCompletedChange(roleId, checked) {
11274
+ if (checked) {
11275
+ this._roleCompletedStates.set(roleId, true);
11276
+ } else {
11277
+ this._roleCompletedStates.delete(roleId);
11278
+ }
11279
+ this._roleCompletedStates = new Map(this._roleCompletedStates);
11280
+ this._handleSaveDraft("auto");
11281
+ }
11068
11282
  _handleSidePanelRoleClick(roleId) {
11069
11283
  if (this._activeSidePanelRoleId && this._activeSidePanelRoleId !== roleId) {
11070
11284
  this._analyzeAndLogRole(this._activeSidePanelRoleId);
@@ -11102,6 +11316,10 @@ let JupiterDynamicForm = class extends LitElement {
11102
11316
  result = "INVALID";
11103
11317
  console.warn(`
11104
11318
  🔴 RESULT: INVALID (contains validation errors)`);
11319
+ } else if (filledCount === 0) {
11320
+ result = "UNTOUCHED";
11321
+ console.warn(`
11322
+ âŦœ RESULT: UNTOUCHED (no data entered)`);
11105
11323
  } else if (emptyCount > 0) {
11106
11324
  result = "INCOMPLETE";
11107
11325
  console.warn(`
@@ -11115,7 +11333,10 @@ let JupiterDynamicForm = class extends LitElement {
11115
11333
  `);
11116
11334
  if (result === "INVALID")
11117
11335
  ;
11118
- else if (result === "INCOMPLETE") {
11336
+ else if (result === "UNTOUCHED") {
11337
+ this._roleBorderStatuses.set(roleId, "untouched");
11338
+ this._roleBorderStatuses = new Map(this._roleBorderStatuses);
11339
+ } else if (result === "INCOMPLETE") {
11119
11340
  this._roleBorderStatuses.set(roleId, "incomplete");
11120
11341
  this._roleBorderStatuses = new Map(this._roleBorderStatuses);
11121
11342
  } else {
@@ -11687,6 +11908,7 @@ let JupiterDynamicForm = class extends LitElement {
11687
11908
  .disabled="${this.disabled || this.readonly}"
11688
11909
  .collapsible="${config.collapsibleSections !== false}"
11689
11910
  .locale="${config.locale || "en-US"}"
11911
+ .language="${this.language}"
11690
11912
  .isFirstSection="${index === 0}"
11691
11913
  .availableDimensions="${this._getAvailableDimensionsForSection(section2.id)}"
11692
11914
  .mode="${this.mode}"
@@ -11777,7 +11999,8 @@ let JupiterDynamicForm = class extends LitElement {
11777
11999
  ${filteredSections.map((section2) => {
11778
12000
  const hasErrors = this._roleHasErrors(section2.id);
11779
12001
  const borderStatus = this._roleBorderStatuses.get(section2.id);
11780
- const statusClass = hasErrors ? "has-errors" : borderStatus === "incomplete" ? "has-empty-fields" : borderStatus === "complete" ? "has-complete-data" : "";
12002
+ const isManuallyCompleted = this._roleCompletedStates.get(section2.id) === true;
12003
+ const statusClass = isManuallyCompleted ? "has-complete-data" : hasErrors ? "has-errors" : borderStatus === "untouched" ? "has-no-data" : borderStatus === "incomplete" ? "has-empty-fields" : borderStatus === "complete" ? "has-complete-data" : "";
11781
12004
  return html`
11782
12005
  <div
11783
12006
  class="side-panel-role-item ${section2.id === this._activeSidePanelRoleId ? "active" : ""} ${statusClass}"
@@ -11830,6 +12053,7 @@ let JupiterDynamicForm = class extends LitElement {
11830
12053
  .collapsible="${false}"
11831
12054
  .hideHeader="${true}"
11832
12055
  .locale="${config.locale || "en-US"}"
12056
+ .language="${this.language}"
11833
12057
  .isFirstSection="${true}"
11834
12058
  .availableDimensions="${this._getAvailableDimensionsForSection(activeSection.id)}"
11835
12059
  .mode="${this.mode}"
@@ -11847,6 +12071,15 @@ let JupiterDynamicForm = class extends LitElement {
11847
12071
  @column-remove="${this._handleColumnRemove}"
11848
12072
  @column-add-request="${this._handleColumnAddRequest}"
11849
12073
  ></jupiter-form-section>
12074
+ <div class="role-completion-row">
12075
+ <input
12076
+ type="checkbox"
12077
+ id="role-complete-${activeSection.id}"
12078
+ .checked="${this._roleCompletedStates.get(activeSection.id) === true}"
12079
+ @change="${(e2) => this._handleRoleCompletedChange(activeSection.id, e2.target.checked)}"
12080
+ >
12081
+ <label for="role-complete-${activeSection.id}">Mark as completed</label>
12082
+ </div>
11850
12083
  ` : html`
11851
12084
  <div class="no-roles-message">
11852
12085
  <h3>${I18n.t("form.noRoleSelected")}</h3>
@@ -12472,6 +12705,14 @@ JupiterDynamicForm.styles = css`
12472
12705
  border-left: 2px solid var(--jupiter-error-color, #d32f2f);
12473
12706
  }
12474
12707
 
12708
+ .side-panel-role-item.has-no-data {
12709
+ border-left: 2px solid var(--jupiter-untouched-color, #757575);
12710
+ }
12711
+
12712
+ .side-panel-role-item.has-no-data.active {
12713
+ border-left: 2px solid var(--jupiter-untouched-color, #757575);
12714
+ }
12715
+
12475
12716
  .side-panel-role-item.has-empty-fields {
12476
12717
  border-left: 2px solid var(--jupiter-warning-color, #ff9800);
12477
12718
  }
@@ -12488,6 +12729,31 @@ JupiterDynamicForm.styles = css`
12488
12729
  border-left: 2px solid var(--jupiter-success-color, #4caf50);
12489
12730
  }
12490
12731
 
12732
+ .role-completion-row {
12733
+ display: flex;
12734
+ align-items: center;
12735
+ gap: 8px;
12736
+ padding: 12px 16px;
12737
+ border-top: 1px solid var(--jupiter-border-color, #e0e0e0);
12738
+ margin-top: 4px;
12739
+ background: var(--jupiter-background, #fff);
12740
+ }
12741
+
12742
+ .role-completion-row input[type="checkbox"] {
12743
+ width: 16px;
12744
+ height: 16px;
12745
+ cursor: pointer;
12746
+ accent-color: var(--jupiter-success-color, #4caf50);
12747
+ flex-shrink: 0;
12748
+ }
12749
+
12750
+ .role-completion-row label {
12751
+ font-size: 14px;
12752
+ color: var(--jupiter-text-secondary, #555);
12753
+ cursor: pointer;
12754
+ user-select: none;
12755
+ }
12756
+
12491
12757
  .side-panel-content {
12492
12758
  flex: 1;
12493
12759
  overflow-y: auto;
@@ -12983,6 +13249,9 @@ __decorateClass([
12983
13249
  __decorateClass([
12984
13250
  r()
12985
13251
  ], JupiterDynamicForm.prototype, "_roleBorderStatuses", 2);
13252
+ __decorateClass([
13253
+ r()
13254
+ ], JupiterDynamicForm.prototype, "_roleCompletedStates", 2);
12986
13255
  __decorateClass([
12987
13256
  r()
12988
13257
  ], JupiterDynamicForm.prototype, "_showRoleContextMenu", 2);