@robosystems/report-components 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { BLOCK_ORDER, BLOCK_TITLES, humanize, qname } from './chunk-JTTICHPF.js';
2
- export { BLOCK_ORDER, BLOCK_TITLES, NS, cypherAdapter, humanize, jsonldFileAdapter, parseJsonld, parseStore, qname } from './chunk-JTTICHPF.js';
3
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
- import { useMemo, useState } from 'react';
1
+ import { isExternalFactUrl, BLOCK_ORDER, BLOCK_TITLES, humanize, qname } from './chunk-POSLHOTN.js';
2
+ export { BLOCK_ORDER, BLOCK_TITLES, NS, cypherAdapter, fetchSecReportShell, fetchSecSection, humanize, isExternalFactUrl, jsonldFileAdapter, mergeSecSections, parseJsonld, parseStore, parseStructureDefinition, qname } from './chunk-POSLHOTN.js';
3
+ import { useState, useEffect, useMemo, useRef } from 'react';
4
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
 
6
6
  // src/format.ts
7
7
  function formatMoney(value, opts = {}) {
@@ -81,8 +81,19 @@ function calcSubtotals(model) {
81
81
  for (const assoc of model.calcAssociations) out.add(assoc.parent);
82
82
  return out;
83
83
  }
84
- function structureForBlock(model, blockType) {
85
- return model.structures.find((s) => s.blockType === blockType) ?? null;
84
+ function structureForBlock(model, ib) {
85
+ if (ib.structureId) {
86
+ const byId = model.structures.find((s) => s.id === ib.structureId);
87
+ if (byId) return byId;
88
+ }
89
+ return model.structures.find((s) => s.blockType === ib.blockType) ?? null;
90
+ }
91
+ function blockOrder(model, ib) {
92
+ if (ib.structureId) {
93
+ const s = model.structures.find((x) => x.id === ib.structureId);
94
+ if (s?.order !== void 0) return s.order;
95
+ }
96
+ return BLOCK_ORDER[ib.blockType] ?? 99;
86
97
  }
87
98
  function columnLabel(period) {
88
99
  return formatDate(period.end);
@@ -104,7 +115,7 @@ function deriveColumns(model, facts) {
104
115
  }).map((period) => ({ key: period.end, label: columnLabel(period), period }));
105
116
  }
106
117
  function buildStatement(model, ib, subtotals) {
107
- const structure = structureForBlock(model, ib.blockType);
118
+ const structure = structureForBlock(model, ib);
108
119
  const order = structure ? presentationOrder(model, structure.id) : /* @__PURE__ */ new Map();
109
120
  const facts = model.facts.filter((f) => f.factSet !== null && f.factSet === ib.factSet);
110
121
  const columns = deriveColumns(model, facts);
@@ -117,10 +128,10 @@ function buildStatement(model, ib, subtotals) {
117
128
  if (idx === void 0) continue;
118
129
  let cells = cellsByElement.get(fact.element);
119
130
  if (!cells) {
120
- cells = columns.map(() => ({ value: null, fact: null }));
131
+ cells = columns.map(() => ({ value: null, fact: null, textValue: null }));
121
132
  cellsByElement.set(fact.element, cells);
122
133
  }
123
- cells[idx] = { value: fact.value, fact };
134
+ cells[idx] = { value: fact.value, fact, textValue: fact.textValue ?? null };
124
135
  }
125
136
  const rows = [...cellsByElement.keys()].map((id) => {
126
137
  const entry = order.get(id);
@@ -135,19 +146,27 @@ function buildStatement(model, ib, subtotals) {
135
146
  isSubtotal: subtotals.has(id),
136
147
  cells: cellsByElement.get(id)
137
148
  }));
149
+ const title = ib.structureId ? structure?.structureName ?? ib.label ?? BLOCK_TITLES[ib.blockType] ?? ib.blockType : BLOCK_TITLES[ib.blockType] ?? ib.label ?? ib.blockType;
138
150
  return {
139
151
  ib,
140
152
  blockType: ib.blockType,
141
- title: BLOCK_TITLES[ib.blockType] ?? ib.label ?? ib.blockType,
142
- structureName: structure?.structureName ?? ib.label ?? null,
153
+ title,
154
+ structureName: ib.structureId ? null : structure?.structureName ?? ib.label ?? null,
143
155
  columns,
144
156
  rows
145
157
  };
146
158
  }
159
+ function reportSections(model) {
160
+ return buildStatements(model).map((s) => ({ id: s.ib.id, title: s.title }));
161
+ }
162
+ function sliceReportSection(model, informationBlockId) {
163
+ const ib = model.informationBlocks.find((b) => b.id === informationBlockId);
164
+ return { ...model, informationBlocks: ib ? [ib] : [] };
165
+ }
147
166
  function buildStatements(model) {
148
167
  const subtotals = calcSubtotals(model);
149
168
  return [...model.informationBlocks].sort(
150
- (a, b) => (BLOCK_ORDER[a.blockType] ?? 99) - (BLOCK_ORDER[b.blockType] ?? 99) || a.blockType.localeCompare(b.blockType)
169
+ (a, b) => blockOrder(model, a) - blockOrder(model, b) || a.blockType.localeCompare(b.blockType)
151
170
  ).map((ib) => buildStatement(model, ib, subtotals));
152
171
  }
153
172
  function footCheck(model, statement, elementId, columnIndex) {
@@ -176,6 +195,93 @@ function footCheck(model, statement, elementId, columnIndex) {
176
195
  };
177
196
  }
178
197
  var styles = {
198
+ status: {
199
+ fontFamily: "var(--rs-font-sans, ui-sans-serif, system-ui, sans-serif)",
200
+ fontSize: "0.85rem",
201
+ color: "var(--rs-muted, #6b7280)",
202
+ padding: "0.75rem 0"
203
+ },
204
+ link: { color: "var(--rs-primary-700, #1d4ed8)" },
205
+ frame: { width: "100%", border: "none", display: "block" },
206
+ pre: {
207
+ whiteSpace: "pre-wrap",
208
+ wordBreak: "break-word",
209
+ fontFamily: "var(--rs-font-mono, ui-monospace, monospace)",
210
+ fontSize: "0.85rem",
211
+ color: "var(--rs-text, #111827)",
212
+ margin: 0
213
+ }
214
+ };
215
+ function wrapHtml(body) {
216
+ return `<!doctype html><html><head><meta charset="utf-8"><base target="_blank"><style>
217
+ :root { color-scheme: light; }
218
+ body { margin: 0; color: #111827;
219
+ font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
220
+ font-size: 13px; line-height: 1.45; }
221
+ img, svg { max-width: 100%; height: auto; }
222
+ table { max-width: 100%; border-collapse: collapse; }
223
+ a { color: #1d4ed8; }
224
+ </style></head><body>${body}</body></html>`;
225
+ }
226
+ function SandboxedHtml({ html }) {
227
+ const ref = useRef(null);
228
+ const fit = () => {
229
+ const frame = ref.current;
230
+ const doc = frame?.contentWindow?.document;
231
+ if (!frame || !doc) return;
232
+ try {
233
+ frame.style.height = `${doc.documentElement.scrollHeight}px`;
234
+ } catch {
235
+ }
236
+ };
237
+ return /* @__PURE__ */ jsx(
238
+ "iframe",
239
+ {
240
+ ref,
241
+ title: "Disclosure",
242
+ sandbox: "allow-same-origin",
243
+ srcDoc: wrapHtml(html),
244
+ style: styles.frame,
245
+ onLoad: () => {
246
+ fit();
247
+ setTimeout(fit, 250);
248
+ }
249
+ }
250
+ );
251
+ }
252
+ function ExternalTextBlock({ url }) {
253
+ const [state, setState] = useState("loading");
254
+ const [body, setBody] = useState("");
255
+ const isHtml = /\.html$/i.test(url);
256
+ useEffect(() => {
257
+ let cancelled = false;
258
+ setState("loading");
259
+ fetch(url).then((r) => {
260
+ if (!r.ok) throw new Error(`HTTP ${r.status}`);
261
+ return r.text();
262
+ }).then((text) => {
263
+ if (cancelled) return;
264
+ setBody(text);
265
+ setState("ready");
266
+ }).catch(() => {
267
+ if (!cancelled) setState("error");
268
+ });
269
+ return () => {
270
+ cancelled = true;
271
+ };
272
+ }, [url]);
273
+ if (state === "loading") return /* @__PURE__ */ jsx("div", { style: styles.status, children: "Loading disclosure\u2026" });
274
+ if (state === "error") {
275
+ return /* @__PURE__ */ jsxs("div", { style: styles.status, children: [
276
+ "Could not load this disclosure.",
277
+ " ",
278
+ /* @__PURE__ */ jsx("a", { style: styles.link, href: url, target: "_blank", rel: "noreferrer", children: "Open it directly \u2197" })
279
+ ] });
280
+ }
281
+ if (!isHtml) return /* @__PURE__ */ jsx("pre", { style: styles.pre, children: body });
282
+ return /* @__PURE__ */ jsx(SandboxedHtml, { html: body });
283
+ }
284
+ var styles2 = {
179
285
  panel: {
180
286
  fontFamily: "var(--rs-font-sans, ui-sans-serif, system-ui, sans-serif)",
181
287
  color: "var(--rs-text, #111827)",
@@ -241,7 +347,7 @@ var styles = {
241
347
  };
242
348
  function Field({ k, children }) {
243
349
  return /* @__PURE__ */ jsxs(Fragment, { children: [
244
- /* @__PURE__ */ jsx("span", { style: styles.key, children: k }),
350
+ /* @__PURE__ */ jsx("span", { style: styles2.key, children: k }),
245
351
  /* @__PURE__ */ jsx("span", { children })
246
352
  ] });
247
353
  }
@@ -259,20 +365,20 @@ function FactInspector({
259
365
  const unit = cell.fact?.unit ? report.units[cell.fact.unit] : null;
260
366
  const foot = footCheck(report, statement, element.id, columnIndex);
261
367
  const sym = currencySymbol;
262
- return /* @__PURE__ */ jsxs("div", { style: styles.panel, children: [
263
- /* @__PURE__ */ jsxs("div", { style: styles.header, children: [
264
- /* @__PURE__ */ jsxs("div", { style: styles.headerText, children: [
265
- /* @__PURE__ */ jsx("p", { style: styles.label, children: element.label }),
266
- /* @__PURE__ */ jsx("span", { style: styles.qname, children: element.qname })
368
+ return /* @__PURE__ */ jsxs("div", { style: styles2.panel, children: [
369
+ /* @__PURE__ */ jsxs("div", { style: styles2.header, children: [
370
+ /* @__PURE__ */ jsxs("div", { style: styles2.headerText, children: [
371
+ /* @__PURE__ */ jsx("p", { style: styles2.label, children: element.label }),
372
+ /* @__PURE__ */ jsx("span", { style: styles2.qname, children: element.qname })
267
373
  ] }),
268
- onClose ? /* @__PURE__ */ jsx("button", { style: styles.close, onClick: onClose, "aria-label": "Close", children: "\xD7" }) : null
374
+ onClose ? /* @__PURE__ */ jsx("button", { style: styles2.close, onClick: onClose, "aria-label": "Close", children: "\xD7" }) : null
269
375
  ] }),
270
- /* @__PURE__ */ jsxs("div", { style: styles.grid, children: [
271
- /* @__PURE__ */ jsx(Field, { k: "Value", children: /* @__PURE__ */ jsx("span", { style: styles.mono, children: formatMoney(cell.value, { currencySymbol: sym }) }) }),
376
+ /* @__PURE__ */ jsxs("div", { style: styles2.grid, children: [
377
+ /* @__PURE__ */ jsx(Field, { k: "Value", children: cell.value === null && cell.textValue ? /* @__PURE__ */ jsx("span", { children: cell.textValue }) : /* @__PURE__ */ jsx("span", { style: styles2.mono, children: formatMoney(cell.value, { currencySymbol: sym }) }) }),
272
378
  /* @__PURE__ */ jsxs(Field, { k: "Period", children: [
273
379
  formatPeriod(column.period),
274
380
  " ",
275
- /* @__PURE__ */ jsxs("span", { style: styles.key, children: [
381
+ /* @__PURE__ */ jsxs("span", { style: styles2.key, children: [
276
382
  "(",
277
383
  column.period.type,
278
384
  ")"
@@ -282,12 +388,12 @@ function FactInspector({
282
388
  element.balance ? /* @__PURE__ */ jsx(Field, { k: "Balance", children: element.balance }) : null,
283
389
  cell.fact?.decimals ? /* @__PURE__ */ jsx(Field, { k: "Decimals", children: cell.fact.decimals }) : null
284
390
  ] }),
285
- foot ? /* @__PURE__ */ jsxs("div", { style: styles.footing, children: [
286
- /* @__PURE__ */ jsxs("div", { style: styles.footTitle, children: [
391
+ foot ? /* @__PURE__ */ jsxs("div", { style: styles2.footing, children: [
392
+ /* @__PURE__ */ jsxs("div", { style: styles2.footTitle, children: [
287
393
  "Calculation ",
288
394
  foot.ok ? "\u2713 foots" : "\u26A0 does not foot"
289
395
  ] }),
290
- /* @__PURE__ */ jsxs("div", { style: styles.equation, children: [
396
+ /* @__PURE__ */ jsxs("div", { style: styles2.equation, children: [
291
397
  foot.terms.map((term, i) => {
292
398
  const op = i === 0 ? term.weight < 0 ? "\u2212" : "" : term.weight < 0 ? " \u2212 " : " + ";
293
399
  return /* @__PURE__ */ jsxs("span", { title: term.element.qname, children: [
@@ -309,7 +415,7 @@ function FactInspector({
309
415
  ] }) : null
310
416
  ] });
311
417
  }
312
- var styles2 = {
418
+ var styles3 = {
313
419
  wrap: {
314
420
  fontFamily: "var(--rs-font-sans, ui-sans-serif, system-ui, sans-serif)",
315
421
  color: "var(--rs-text, #111827)",
@@ -355,6 +461,18 @@ var styles2 = {
355
461
  whiteSpace: "nowrap",
356
462
  fontFamily: "var(--rs-font-mono, ui-monospace, SFMono-Regular, monospace)",
357
463
  fontVariantNumeric: "tabular-nums"
464
+ },
465
+ textBlockCell: {
466
+ padding: "0.75rem",
467
+ borderTop: "1px solid var(--rs-border, #e5e7eb)"
468
+ },
469
+ textBlockLabel: {
470
+ fontSize: "0.7rem",
471
+ textTransform: "uppercase",
472
+ letterSpacing: "0.05em",
473
+ fontWeight: 600,
474
+ color: "var(--rs-muted, #6b7280)",
475
+ marginBottom: "0.5rem"
358
476
  }
359
477
  };
360
478
  function StatementTable({
@@ -365,25 +483,34 @@ function StatementTable({
365
483
  }) {
366
484
  const { columns, rows } = statement;
367
485
  const periodCaption = columns.length === 1 ? formatPeriod(columns[0].period) : null;
368
- return /* @__PURE__ */ jsxs("section", { style: styles2.wrap, children: [
369
- /* @__PURE__ */ jsxs("header", { style: styles2.heading, children: [
370
- /* @__PURE__ */ jsx("h3", { style: styles2.title, children: statement.title }),
371
- statement.structureName ? /* @__PURE__ */ jsx("div", { style: styles2.subtitle, children: statement.structureName }) : null,
372
- periodCaption ? /* @__PURE__ */ jsx("div", { style: styles2.subtitle, children: periodCaption }) : null
486
+ return /* @__PURE__ */ jsxs("section", { style: styles3.wrap, children: [
487
+ /* @__PURE__ */ jsxs("header", { style: styles3.heading, children: [
488
+ /* @__PURE__ */ jsx("h3", { style: styles3.title, children: statement.title }),
489
+ statement.structureName ? /* @__PURE__ */ jsx("div", { style: styles3.subtitle, children: statement.structureName }) : null,
490
+ periodCaption ? /* @__PURE__ */ jsx("div", { style: styles3.subtitle, children: periodCaption }) : null
373
491
  ] }),
374
- /* @__PURE__ */ jsxs("table", { style: styles2.table, children: [
492
+ /* @__PURE__ */ jsxs("table", { style: styles3.table, children: [
375
493
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
376
- /* @__PURE__ */ jsx("th", { style: { ...styles2.th, ...styles2.thConcept }, children: "Concept" }),
377
- columns.map((col) => /* @__PURE__ */ jsx("th", { style: { ...styles2.th, ...styles2.thNum }, children: col.label }, col.key))
494
+ /* @__PURE__ */ jsx("th", { style: { ...styles3.th, ...styles3.thConcept }, children: "Concept" }),
495
+ columns.map((col) => /* @__PURE__ */ jsx("th", { style: { ...styles3.th, ...styles3.thNum }, children: col.label }, col.key))
378
496
  ] }) }),
379
497
  /* @__PURE__ */ jsx("tbody", { children: rows.map((row) => {
498
+ const textBlock = row.cells.find(
499
+ (c) => c.value === null && isExternalFactUrl(c.textValue)
500
+ );
501
+ if (textBlock?.textValue) {
502
+ return /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", { colSpan: columns.length + 1, style: styles3.textBlockCell, children: [
503
+ /* @__PURE__ */ jsx("div", { style: styles3.textBlockLabel, title: row.element.qname, children: row.element.label }),
504
+ /* @__PURE__ */ jsx(ExternalTextBlock, { url: textBlock.textValue })
505
+ ] }) }, row.element.id);
506
+ }
380
507
  const isGrandTotal = row.isSubtotal && row.depth === 0;
381
508
  const allZeroOrNull = row.cells.every((c) => c.value === null || c.value === 0);
382
509
  const rowStyle = {
383
510
  backgroundColor: row.isSubtotal ? "var(--rs-subtotal-bg, #f9fafb)" : void 0
384
511
  };
385
512
  const labelStyle = {
386
- ...styles2.labelCell,
513
+ ...styles3.labelCell,
387
514
  paddingLeft: `${row.depth * 20 + 12}px`,
388
515
  fontWeight: row.isSubtotal ? 600 : 400,
389
516
  color: !row.isSubtotal && allZeroOrNull ? "var(--rs-muted, #9ca3af)" : void 0,
@@ -394,20 +521,26 @@ function StatementTable({
394
521
  row.cells.map((cell, i) => {
395
522
  const isSelected = selected !== null && selected.elementId === row.element.id && selected.columnIndex === i;
396
523
  const clickable = onCellClick && cell.fact !== null;
524
+ const isText = cell.value === null && cell.textValue != null && cell.textValue !== "";
397
525
  const numStyle = {
398
- ...styles2.numCell,
526
+ ...styles3.numCell,
399
527
  fontWeight: row.isSubtotal ? 600 : 400,
400
528
  borderBottom: isGrandTotal ? "3px double var(--rs-text, #111827)" : void 0,
401
529
  cursor: clickable ? "pointer" : "default",
402
530
  backgroundColor: isSelected ? "var(--rs-selected-bg, #dbeafe)" : void 0,
403
- color: !row.isSubtotal && (cell.value === null || cell.value === 0) ? "var(--rs-muted, #9ca3af)" : void 0
531
+ color: !isText && !row.isSubtotal && (cell.value === null || cell.value === 0) ? "var(--rs-muted, #9ca3af)" : void 0,
532
+ ...isText ? {
533
+ textAlign: "left",
534
+ whiteSpace: "normal",
535
+ fontFamily: "var(--rs-font-sans, ui-sans-serif, system-ui, sans-serif)"
536
+ } : {}
404
537
  };
405
538
  return /* @__PURE__ */ jsx(
406
539
  "td",
407
540
  {
408
541
  style: numStyle,
409
542
  onClick: clickable ? () => onCellClick?.(row, i) : void 0,
410
- children: formatMoney(cell.value, { currencySymbol })
543
+ children: isText ? cell.textValue : formatMoney(cell.value, { currencySymbol })
411
544
  },
412
545
  columns[i].key
413
546
  );
@@ -417,7 +550,7 @@ function StatementTable({
417
550
  ] })
418
551
  ] });
419
552
  }
420
- var styles3 = {
553
+ var styles4 = {
421
554
  layout: {
422
555
  display: "grid",
423
556
  gridTemplateColumns: "minmax(0, 1fr)",
@@ -438,7 +571,6 @@ var styles3 = {
438
571
  margin: 0,
439
572
  color: "var(--rs-text, #111827)"
440
573
  },
441
- entitySub: { fontSize: "0.85rem", color: "var(--rs-muted, #6b7280)" },
442
574
  panel: { position: "sticky", top: "1rem" }
443
575
  };
444
576
  function ReportView({ report, currencySymbol = "$", inspect = true }) {
@@ -447,16 +579,13 @@ function ReportView({ report, currencySymbol = "$", inspect = true }) {
447
579
  const selected = selection !== null ? statements[selection.statementIndex] : null;
448
580
  const selectedRow = selected?.rows.find((r) => r.element.id === selection?.elementId) ?? null;
449
581
  return /* @__PURE__ */ jsxs("div", { children: [
450
- report.entity ? /* @__PURE__ */ jsxs("header", { style: styles3.entity, children: [
451
- /* @__PURE__ */ jsx("h2", { style: styles3.entityName, children: report.entity.name }),
452
- report.entity.country ? /* @__PURE__ */ jsx("div", { style: styles3.entitySub, children: report.entity.country }) : null
453
- ] }) : null,
582
+ report.entity ? /* @__PURE__ */ jsx("header", { style: styles4.entity, children: /* @__PURE__ */ jsx("h2", { style: styles4.entityName, children: report.entity.name }) }) : null,
454
583
  /* @__PURE__ */ jsxs(
455
584
  "div",
456
585
  {
457
586
  style: {
458
- ...styles3.layout,
459
- ...inspect && selectedRow ? styles3.layoutWithPanel : {}
587
+ ...styles4.layout,
588
+ ...inspect && selectedRow ? styles4.layoutWithPanel : {}
460
589
  },
461
590
  children: [
462
591
  /* @__PURE__ */ jsx("div", { children: statements.map((statement, statementIndex) => /* @__PURE__ */ jsx(
@@ -476,7 +605,7 @@ function ReportView({ report, currencySymbol = "$", inspect = true }) {
476
605
  },
477
606
  statement.ib.id
478
607
  )) }),
479
- inspect && selected && selectedRow && selection ? /* @__PURE__ */ jsx("div", { style: styles3.panel, children: /* @__PURE__ */ jsx(
608
+ inspect && selected && selectedRow && selection ? /* @__PURE__ */ jsx("div", { style: styles4.panel, children: /* @__PURE__ */ jsx(
480
609
  FactInspector,
481
610
  {
482
611
  report,
@@ -493,6 +622,6 @@ function ReportView({ report, currencySymbol = "$", inspect = true }) {
493
622
  ] });
494
623
  }
495
624
 
496
- export { FactInspector, ReportView, StatementTable, buildStatements, calcSubtotals, footCheck, formatDate, formatMoney, formatPeriod, presentationOrder };
625
+ export { ExternalTextBlock, FactInspector, ReportView, StatementTable, buildStatements, calcSubtotals, footCheck, formatDate, formatMoney, formatPeriod, presentationOrder, reportSections, sliceReportSection };
497
626
  //# sourceMappingURL=index.js.map
498
627
  //# sourceMappingURL=index.js.map