datastake-daf 0.6.439 → 0.6.441

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.
@@ -14536,149 +14536,206 @@ DAFFooter.propTypes = {
14536
14536
  };
14537
14537
 
14538
14538
  const PAGE_HEIGHT = 1587;
14539
- // margin-top: 20, bottom: 20;
14540
- const FOOTER_HEIGHT = 70;
14541
14539
  const HEADER_HEIGHT = 100;
14540
+ const FOOTER_HEIGHT = 70;
14541
+ const GAP_BETWEEN_BLOCKS = 24;
14542
+ const FILL_RATIO = 0.80;
14543
+ const CONTENT_HEIGHT = PAGE_HEIGHT - HEADER_HEIGHT - FOOTER_HEIGHT;
14542
14544
  const Row = _ref => {
14543
14545
  let {
14544
14546
  widgets,
14545
14547
  i,
14546
14548
  onChangeHeight = () => {}
14547
14549
  } = _ref;
14548
- const ref = React.useRef();
14549
- const [height, setHeight] = React.useState(0);
14550
- React.useEffect(() => {
14551
- const observer = new ResizeObserver(entries => {
14552
- for (const entry of entries) {
14553
- setHeight(entry.contentRect.height);
14554
- }
14555
- });
14556
- observer.observe(ref.current);
14557
- return () => observer.disconnect();
14558
- }, []);
14559
- React.useEffect(() => {
14560
- if (height) {
14561
- onChangeHeight(i, {
14562
- height,
14550
+ const ref = React.useRef(null);
14551
+ React.useLayoutEffect(() => {
14552
+ if (!ref.current) return;
14553
+ const ro = new ResizeObserver(entries => {
14554
+ var _entries$0$contentRec, _entries$;
14555
+ const h = (_entries$0$contentRec = (_entries$ = entries[0]) === null || _entries$ === void 0 || (_entries$ = _entries$.contentRect) === null || _entries$ === void 0 ? void 0 : _entries$.height) !== null && _entries$0$contentRec !== void 0 ? _entries$0$contentRec : 0;
14556
+ if (h) onChangeHeight(i, {
14557
+ height: h,
14563
14558
  ref
14564
14559
  });
14565
- }
14566
- }, [height]);
14560
+ });
14561
+ ro.observe(ref.current);
14562
+ return () => ro.disconnect();
14563
+ }, [i, onChangeHeight]);
14567
14564
  return /*#__PURE__*/jsxRuntime.jsx("section", {
14568
14565
  ref: ref,
14569
14566
  style: widgets.style,
14570
- children: typeof widgets.render === 'function' ? widgets.render() : null
14567
+ children: typeof widgets.render === "function" ? widgets.render() : null
14571
14568
  });
14572
14569
  };
14573
14570
  function PdfView(_ref2) {
14574
14571
  let {
14575
14572
  config = [],
14576
14573
  customClassName,
14577
- title = 'Title',
14578
- imgSrc = '',
14579
- userId = 'IDD-0000000',
14580
- accountId = 'IDD-0000000',
14581
- documentId = 'IDD-0000000',
14582
- downloadId = 'DWL-00000123'
14574
+ title = "Title",
14575
+ imgSrc = "",
14576
+ userId = "IDD-0000000",
14577
+ accountId = "IDD-0000000",
14578
+ documentId = "IDD-0000000",
14579
+ downloadId = "DWL-00000123"
14583
14580
  } = _ref2;
14584
- const [sectionsConfig, setSectionsConfig] = React.useState({});
14585
- const [pages, setPages] = React.useState([1]);
14586
- const doSizing = React.useCallback(() => {
14587
- const keys = Object.keys(sectionsConfig);
14588
- let _pages = [1];
14589
- let _page = 1;
14590
- if (keys.length === config.length) {
14591
- let incrHeight = 0;
14592
- keys.forEach(k => {
14593
- const {
14594
- ref
14595
- } = sectionsConfig[k];
14596
- ref.current.style.marginBottom = '0px';
14597
- // ref.current.style.marginTop = '15px';
14581
+ // Config actually used for rendering (may expand if we split big blocks)
14582
+ const [expandedConfig, setExpandedConfig] = React.useState(config);
14583
+ // index -> {height, ref}
14584
+ const [sections, setSections] = React.useState({});
14585
+ // [[indices for page 1], [indices for page 2], ...]
14586
+ const [pageMap, setPageMap] = React.useState([]);
14587
+
14588
+ // Reset expansion when the external config changes
14589
+ React.useEffect(() => {
14590
+ setExpandedConfig(config);
14591
+ setSections({});
14592
+ setPageMap([]);
14593
+ }, [config]);
14594
+ const onChangeHeight = React.useCallback((index, conf) => {
14595
+ setSections(prev => {
14596
+ var _prev$index;
14597
+ return ((_prev$index = prev[index]) === null || _prev$index === void 0 ? void 0 : _prev$index.height) === conf.height ? prev : _objectSpread2(_objectSpread2({}, prev), {}, {
14598
+ [index]: conf
14598
14599
  });
14599
- keys.forEach((k, i) => {
14600
- const {
14601
- height,
14602
- ref
14603
- } = sectionsConfig[k];
14604
- if (i === 0) {
14605
- ref.current.style.marginTop = "".concat(HEADER_HEIGHT, "px");
14606
- incrHeight += HEADER_HEIGHT;
14607
- }
14608
- const newHeight = incrHeight + height;
14609
- if (i === keys.length - 1) {
14610
- ref.current.style.paddingBottom = '30px';
14611
- }
14612
- if (newHeight > PAGE_HEIGHT - 30 - FOOTER_HEIGHT - HEADER_HEIGHT) {
14613
- const dif = Math.abs(PAGE_HEIGHT - incrHeight);
14614
- ref.current.style.marginTop = '30px';
14615
- _page += 1;
14616
- _pages.push(_page);
14617
- if (sectionsConfig[keys[i - 1]]) {
14618
- const {
14619
- ref: topRef
14620
- } = sectionsConfig[keys[i - 1]];
14621
- topRef.current.style.marginBottom = "".concat(dif + HEADER_HEIGHT - 24, "px");
14622
- incrHeight = height + 24 + HEADER_HEIGHT;
14623
- // console.log('margin', dif);
14600
+ });
14601
+ }, []);
14602
+
14603
+ // --- Step 1: After measurement, split any oversized splittable blocks (one per pass) ---
14604
+ React.useEffect(() => {
14605
+ const keys = Object.keys(sections).map(k => Number(k)).sort((a, b) => a - b);
14606
+ if (keys.length !== expandedConfig.length) return; // not all measured yet
14607
+
14608
+ for (const idx of keys) {
14609
+ var _sections$idx$height, _sections$idx;
14610
+ const h = (_sections$idx$height = (_sections$idx = sections[idx]) === null || _sections$idx === void 0 ? void 0 : _sections$idx.height) !== null && _sections$idx$height !== void 0 ? _sections$idx$height : 0;
14611
+ const item = expandedConfig[idx];
14612
+ const canSplit = (item === null || item === void 0 ? void 0 : item.splittable) && typeof (item === null || item === void 0 ? void 0 : item.splitInto) === "function";
14613
+ if (canSplit && h > CONTENT_HEIGHT) {
14614
+ // Split it and re-measure
14615
+ const chunks = item.splitInto(CONTENT_HEIGHT);
14616
+ const next = [...expandedConfig.slice(0, idx), ...chunks, ...expandedConfig.slice(idx + 1)];
14617
+ setExpandedConfig(next);
14618
+ setSections({}); // force re-measure since indices changed
14619
+ setPageMap([]);
14620
+ return; // do one split per layout pass to avoid loops
14621
+ }
14622
+ }
14623
+ }, [sections, expandedConfig]);
14624
+
14625
+ // --- Step 2: Paginate with per-block hints (priority + keepWithNext) ---
14626
+ const paginate = React.useCallback(() => {
14627
+ const keys = Object.keys(sections).map(k => Number(k)).sort((a, b) => a - b);
14628
+ if (keys.length !== expandedConfig.length) return;
14629
+
14630
+ // Build meta (read hints from config)
14631
+ const meta = keys.map(i => {
14632
+ var _expandedConfig$i$pri, _expandedConfig$i, _expandedConfig$i2;
14633
+ return {
14634
+ i,
14635
+ h: sections[i].height,
14636
+ priority: (_expandedConfig$i$pri = (_expandedConfig$i = expandedConfig[i]) === null || _expandedConfig$i === void 0 ? void 0 : _expandedConfig$i.priority) !== null && _expandedConfig$i$pri !== void 0 ? _expandedConfig$i$pri : 2,
14637
+ // 1 > 2 > 3 (1 is most important)
14638
+ keepWithNext: !!((_expandedConfig$i2 = expandedConfig[i]) !== null && _expandedConfig$i2 !== void 0 && _expandedConfig$i2.keepWithNext)
14639
+ };
14640
+ });
14641
+ const threshold = Math.floor(CONTENT_HEIGHT * FILL_RATIO);
14642
+ const pages = [];
14643
+ let current = [];
14644
+ let used = 0;
14645
+ let carryKeep = false;
14646
+ for (let k = 0; k < meta.length; k++) {
14647
+ const curr = meta[k];
14648
+ const hWithGap = (current.length === 0 ? 0 : GAP_BETWEEN_BLOCKS) + curr.h;
14649
+
14650
+ // Honor keep-with-next from the previous block
14651
+ if (carryKeep) {
14652
+ // start current on a fresh page
14653
+ if (current.length) pages.push(current);
14654
+ current = [curr.i];
14655
+ used = curr.h;
14656
+ carryKeep = false;
14657
+ if (curr.keepWithNext) carryKeep = true;
14658
+ continue;
14659
+ }
14660
+
14661
+ // Oversized singleton: own page
14662
+ if (curr.h > threshold && current.length === 0) {
14663
+ pages.push([curr.i]);
14664
+ current = [];
14665
+ used = 0;
14666
+ carryKeep = false;
14667
+ continue;
14668
+ }
14669
+
14670
+ // Fits on current page?
14671
+ if (current.length === 0 || used + hWithGap <= threshold) {
14672
+ current.push(curr.i);
14673
+ used += hWithGap;
14674
+ } else {
14675
+ // Rebalance: try pushing a lower-priority block from current to next page to make room
14676
+ let moved = false;
14677
+ for (let j = current.length - 1; j >= 0; j--) {
14678
+ const prevIdx = current[j];
14679
+ const prev = meta.find(m => m.i === prevIdx);
14680
+ if (prev.priority > curr.priority) {
14681
+ // lower priority number = higher importance
14682
+ const tentativeUsed = used - (j === 0 ? prev.h : prev.h + GAP_BETWEEN_BLOCKS);
14683
+ if (tentativeUsed + hWithGap <= threshold) {
14684
+ // move prev off this page
14685
+ current.splice(j, 1);
14686
+ used = tentativeUsed;
14687
+ moved = true;
14688
+ break;
14689
+ }
14624
14690
  }
14691
+ }
14692
+ if (!moved) {
14693
+ pages.push(current);
14694
+ current = [curr.i];
14695
+ used = curr.h; // first element on new page (no top gap)
14625
14696
  } else {
14626
- incrHeight = newHeight + 24;
14697
+ // we made space; place current here
14698
+ current.push(curr.i);
14699
+ used += hWithGap;
14627
14700
  }
14628
- // console.groupEnd();
14629
- });
14630
- setPages(_pages);
14701
+ }
14702
+ if (curr.keepWithNext) carryKeep = true;
14631
14703
  }
14632
- }, [sectionsConfig]);
14704
+ if (current.length) pages.push(current);
14705
+ setPageMap(pages); // <-- unlimited pages; grows as needed
14706
+ }, [expandedConfig, sections]);
14633
14707
  React.useEffect(() => {
14634
- doSizing();
14635
- }, [doSizing]);
14636
- const onChangeHeight = React.useCallback((index, conf) => {
14637
- setSectionsConfig(prev => _objectSpread2(_objectSpread2({}, prev), {}, {
14638
- [index]: conf
14639
- }));
14640
- }, []);
14641
- const contClassName = formatClassname(['daf-analysis daf-pdf-view', customClassName]);
14642
- const renderDashboard = React.useCallback(() => {
14643
- return /*#__PURE__*/jsxRuntime.jsx("div", {
14644
- className: "view-content",
14708
+ paginate();
14709
+ }, [paginate]);
14710
+ const contClassName = formatClassname(["daf-analysis daf-pdf-view", customClassName]);
14711
+
14712
+ // Measurement layer (only shown until we have a pageMap)
14713
+ const MeasurementLayer = React.useMemo(() => /*#__PURE__*/jsxRuntime.jsx("div", {
14714
+ className: "view-content",
14715
+ children: /*#__PURE__*/jsxRuntime.jsx("div", {
14716
+ className: "daf-analysis-layout",
14645
14717
  children: /*#__PURE__*/jsxRuntime.jsx("div", {
14646
- className: "daf-analysis-layout",
14647
- children: /*#__PURE__*/jsxRuntime.jsx("div", {
14648
- className: "sections-cont",
14649
- children: config.map((widgets, i) => /*#__PURE__*/jsxRuntime.jsx(Row, {
14650
- widgets: widgets,
14651
- i: i,
14652
- onChangeHeight: onChangeHeight
14653
- }, "dashboard-sections=".concat(i + 1)))
14654
- })
14718
+ className: "sections-cont",
14719
+ children: expandedConfig.map((widgets, i) => /*#__PURE__*/jsxRuntime.jsx(Row, {
14720
+ widgets: widgets,
14721
+ i: i,
14722
+ onChangeHeight: onChangeHeight
14723
+ }, "measure-".concat(i)))
14655
14724
  })
14656
- });
14657
- }, [config, onChangeHeight]);
14658
-
14659
- // <div className="daf-analysis">
14660
- // <Header title={t('Dashboard Title')} />
14661
-
14662
- // <div className="content">
14663
- // <div className="view-content">
14664
- // <div className="daf-analysis-layout">
14665
- // <div className='sections-cont w-pt'>
14666
- // <section>
14667
-
14668
- return /*#__PURE__*/jsxRuntime.jsxs("div", {
14725
+ })
14726
+ }), [expandedConfig, onChangeHeight]);
14727
+ return /*#__PURE__*/jsxRuntime.jsx("div", {
14669
14728
  className: contClassName,
14670
- style: {
14671
- position: 'relative'
14672
- },
14673
- children: [renderDashboard(), pages.map(page => /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
14729
+ children: pageMap.length > 0 ? pageMap.map((indices, pageIdx) => /*#__PURE__*/jsxRuntime.jsxs("div", {
14730
+ className: "pdf-page",
14731
+ style: {
14732
+ height: PAGE_HEIGHT,
14733
+ position: "relative",
14734
+ overflow: "hidden"
14735
+ },
14674
14736
  children: [/*#__PURE__*/jsxRuntime.jsxs("div", {
14675
14737
  style: {
14676
- top: (page - 1) * PAGE_HEIGHT,
14677
- width: '100%',
14678
- height: HEADER_HEIGHT - 24,
14679
- position: 'absolute',
14680
- left: 0,
14681
- zIndex: 1000000
14738
+ height: HEADER_HEIGHT
14682
14739
  },
14683
14740
  className: "flex-row dashboard-header",
14684
14741
  children: [/*#__PURE__*/jsxRuntime.jsx("div", {
@@ -14693,39 +14750,51 @@ function PdfView(_ref2) {
14693
14750
  alt: "logo"
14694
14751
  })
14695
14752
  })]
14696
- }, "headers-".concat(page)), /*#__PURE__*/jsxRuntime.jsxs("div", {
14753
+ }), /*#__PURE__*/jsxRuntime.jsx("div", {
14754
+ className: "pdf-body",
14697
14755
  style: {
14698
- top: page * PAGE_HEIGHT - FOOTER_HEIGHT,
14699
- width: '100%',
14700
- height: FOOTER_HEIGHT,
14701
- position: 'absolute',
14702
- left: 0,
14703
- zIndex: 1000000
14756
+ minHeight: CONTENT_HEIGHT
14757
+ },
14758
+ children: /*#__PURE__*/jsxRuntime.jsx("div", {
14759
+ style: {
14760
+ display: "flex",
14761
+ flexDirection: "column",
14762
+ gap: GAP_BETWEEN_BLOCKS
14763
+ },
14764
+ children: indices.map(i => /*#__PURE__*/jsxRuntime.jsx(Row, {
14765
+ widgets: expandedConfig[i],
14766
+ i: i,
14767
+ onChangeHeight: onChangeHeight
14768
+ }, "row-".concat(i)))
14769
+ })
14770
+ }), /*#__PURE__*/jsxRuntime.jsxs("div", {
14771
+ style: {
14772
+ height: FOOTER_HEIGHT
14704
14773
  },
14705
14774
  className: "dashboard-footer flex-row",
14706
14775
  children: [/*#__PURE__*/jsxRuntime.jsx("div", {
14707
14776
  className: "flex flex-column justify-center",
14708
14777
  children: /*#__PURE__*/jsxRuntime.jsx("h5", {
14709
- children: moment__default["default"]().format('DD MMM YYYY')
14778
+ children: moment__default["default"]().format("DD MMM YYYY")
14710
14779
  })
14711
14780
  }), /*#__PURE__*/jsxRuntime.jsx("div", {
14712
14781
  className: "flex flex-column justify-center",
14713
14782
  children: /*#__PURE__*/jsxRuntime.jsxs("div", {
14714
14783
  className: "flex gap-2",
14715
14784
  children: [/*#__PURE__*/jsxRuntime.jsxs("h5", {
14716
- children: ["User ID:", ' ', /*#__PURE__*/jsxRuntime.jsx("strong", {
14785
+ children: ["User ID: ", /*#__PURE__*/jsxRuntime.jsx("strong", {
14717
14786
  children: userId
14718
14787
  })]
14719
14788
  }), /*#__PURE__*/jsxRuntime.jsxs("h5", {
14720
- children: ["Account ID:", ' ', /*#__PURE__*/jsxRuntime.jsx("strong", {
14789
+ children: ["Account ID: ", /*#__PURE__*/jsxRuntime.jsx("strong", {
14721
14790
  children: accountId
14722
14791
  })]
14723
14792
  }), /*#__PURE__*/jsxRuntime.jsxs("h5", {
14724
- children: ["Document ID:", ' ', /*#__PURE__*/jsxRuntime.jsx("strong", {
14793
+ children: ["Document ID: ", /*#__PURE__*/jsxRuntime.jsx("strong", {
14725
14794
  children: documentId
14726
14795
  })]
14727
14796
  }), /*#__PURE__*/jsxRuntime.jsxs("h5", {
14728
- children: ["Download ID:", ' ', /*#__PURE__*/jsxRuntime.jsx("strong", {
14797
+ children: ["Download ID: ", /*#__PURE__*/jsxRuntime.jsx("strong", {
14729
14798
  children: downloadId
14730
14799
  })]
14731
14800
  })]
@@ -14733,21 +14802,29 @@ function PdfView(_ref2) {
14733
14802
  }), /*#__PURE__*/jsxRuntime.jsx("div", {
14734
14803
  className: "flex flex-column justify-center",
14735
14804
  children: /*#__PURE__*/jsxRuntime.jsx("h5", {
14736
- children: page
14805
+ children: pageIdx + 1
14737
14806
  })
14738
14807
  })]
14739
- }, "footers-".concat(page))]
14740
- }))]
14808
+ })]
14809
+ }, "page-".concat(pageIdx))) : MeasurementLayer
14741
14810
  });
14742
14811
  }
14743
14812
  PdfView.propTypes = {
14744
- config: PropTypes__default["default"].array,
14813
+ config: PropTypes__default["default"].arrayOf(PropTypes__default["default"].shape({
14814
+ render: PropTypes__default["default"].func.isRequired,
14815
+ style: PropTypes__default["default"].object,
14816
+ priority: PropTypes__default["default"].oneOf([1, 2, 3]),
14817
+ keepWithNext: PropTypes__default["default"].bool,
14818
+ splittable: PropTypes__default["default"].bool,
14819
+ splitInto: PropTypes__default["default"].func // (maxHeightPx) => WidgetConfig[]
14820
+ })),
14745
14821
  customClassName: PropTypes__default["default"].any,
14746
14822
  title: PropTypes__default["default"].string,
14747
14823
  imgSrc: PropTypes__default["default"].string,
14748
14824
  userId: PropTypes__default["default"].string,
14749
14825
  accountId: PropTypes__default["default"].string,
14750
- documentId: PropTypes__default["default"].string
14826
+ documentId: PropTypes__default["default"].string,
14827
+ downloadId: PropTypes__default["default"].string
14751
14828
  };
14752
14829
 
14753
14830
  const ajaxSelectFieldData = async (value, config, getApiBaseUrl = () => {}, getAppHeader = () => {}, app, formValues = {}) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datastake-daf",
3
- "version": "0.6.439",
3
+ "version": "0.6.441",
4
4
  "dependencies": {
5
5
  "@ant-design/icons": "^5.2.5",
6
6
  "@antv/g2": "^5.1.1",
@@ -1,207 +1,267 @@
1
1
  import moment from "moment";
2
- import PropTypes from 'prop-types';
3
- import { useCallback, useEffect, useRef, useState } from "react";
2
+ import PropTypes from "prop-types";
3
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
4
4
  import { formatClassname } from "../../../../../helpers/ClassesHelper";
5
5
 
6
6
  const PAGE_HEIGHT = 1587;
7
- // margin-top: 20, bottom: 20;
8
- const FOOTER_HEIGHT = 70;
9
7
  const HEADER_HEIGHT = 100;
8
+ const FOOTER_HEIGHT = 70;
9
+ const GAP_BETWEEN_BLOCKS = 24;
10
+ const FILL_RATIO = 0.80;
10
11
 
11
- const Row = ({ widgets, i, onChangeHeight = () => { } }) => {
12
- const ref = useRef();
13
- const [height, setHeight] = useState(0);
14
-
15
- useEffect(() => {
16
- const observer = new ResizeObserver((entries) => {
17
- for (const entry of entries) {
18
- setHeight(entry.contentRect.height);
19
- }
20
- });
21
-
22
- observer.observe(ref.current);
12
+ const CONTENT_HEIGHT = PAGE_HEIGHT - HEADER_HEIGHT - FOOTER_HEIGHT;
23
13
 
24
- return () => observer.disconnect();
25
- }, []);
14
+ const Row = ({ widgets, i, onChangeHeight = () => {} }) => {
15
+ const ref = useRef(null);
26
16
 
27
- useEffect(() => {
28
- if (height) {
29
- onChangeHeight(i, { height, ref });
30
- }
31
- }, [height])
17
+ useLayoutEffect(() => {
18
+ if (!ref.current) return;
19
+ const ro = new ResizeObserver((entries) => {
20
+ const h = entries[0]?.contentRect?.height ?? 0;
21
+ if (h) onChangeHeight(i, { height: h, ref });
22
+ });
23
+ ro.observe(ref.current);
24
+ return () => ro.disconnect();
25
+ }, [i, onChangeHeight]);
32
26
 
33
27
  return (
34
28
  <section ref={ref} style={widgets.style}>
35
- {typeof widgets.render === 'function' ? widgets.render() : null}
29
+ {typeof widgets.render === "function" ? widgets.render() : null}
36
30
  </section>
37
- )
31
+ );
38
32
  };
39
33
 
40
34
  export default function PdfView({
41
35
  config = [],
42
36
  customClassName,
43
- title = 'Title',
44
- imgSrc = '',
45
- userId = 'IDD-0000000',
46
- accountId = 'IDD-0000000',
47
- documentId = 'IDD-0000000',
48
- downloadId = 'DWL-00000123',
37
+ title = "Title",
38
+ imgSrc = "",
39
+ userId = "IDD-0000000",
40
+ accountId = "IDD-0000000",
41
+ documentId = "IDD-0000000",
42
+ downloadId = "DWL-00000123",
49
43
  }) {
50
- const [sectionsConfig, setSectionsConfig] = useState({});
51
- const [pages, setPages] = useState([1]);
44
+ // Config actually used for rendering (may expand if we split big blocks)
45
+ const [expandedConfig, setExpandedConfig] = useState(config);
46
+ // index -> {height, ref}
47
+ const [sections, setSections] = useState({});
48
+ // [[indices for page 1], [indices for page 2], ...]
49
+ const [pageMap, setPageMap] = useState([]);
52
50
 
53
- const doSizing = useCallback(() => {
54
- const keys = Object.keys(sectionsConfig);
55
- let _pages = [1];
56
- let _page = 1;
51
+ // Reset expansion when the external config changes
52
+ useEffect(() => {
53
+ setExpandedConfig(config);
54
+ setSections({});
55
+ setPageMap([]);
56
+ }, [config]);
57
57
 
58
- if (keys.length === config.length) {
59
- let incrHeight = 0;
58
+ const onChangeHeight = useCallback((index, conf) => {
59
+ setSections((prev) => (prev[index]?.height === conf.height ? prev : { ...prev, [index]: conf }));
60
+ }, []);
60
61
 
61
- keys.forEach(k => {
62
- const { ref } = sectionsConfig[k];
63
- ref.current.style.marginBottom = '0px';
64
- // ref.current.style.marginTop = '15px';
65
- })
62
+ // --- Step 1: After measurement, split any oversized splittable blocks (one per pass) ---
63
+ useEffect(() => {
64
+ const keys = Object.keys(sections)
65
+ .map((k) => Number(k))
66
+ .sort((a, b) => a - b);
66
67
 
67
- keys.forEach((k, i) => {
68
- const { height, ref } = sectionsConfig[k];
68
+ if (keys.length !== expandedConfig.length) return; // not all measured yet
69
69
 
70
- if (i === 0) {
71
- ref.current.style.marginTop = `${HEADER_HEIGHT}px`;
72
- incrHeight += HEADER_HEIGHT;
73
- }
70
+ for (const idx of keys) {
71
+ const h = sections[idx]?.height ?? 0;
72
+ const item = expandedConfig[idx];
73
+ const canSplit = item?.splittable && typeof item?.splitInto === "function";
74
+ if (canSplit && h > CONTENT_HEIGHT) {
75
+ // Split it and re-measure
76
+ const chunks = item.splitInto(CONTENT_HEIGHT);
77
+ const next = [
78
+ ...expandedConfig.slice(0, idx),
79
+ ...chunks,
80
+ ...expandedConfig.slice(idx + 1),
81
+ ];
82
+ setExpandedConfig(next);
83
+ setSections({}); // force re-measure since indices changed
84
+ setPageMap([]);
85
+ return; // do one split per layout pass to avoid loops
86
+ }
87
+ }
88
+ }, [sections, expandedConfig]);
74
89
 
75
- const newHeight = incrHeight + height;
90
+ // --- Step 2: Paginate with per-block hints (priority + keepWithNext) ---
91
+ const paginate = useCallback(() => {
92
+ const keys = Object.keys(sections)
93
+ .map((k) => Number(k))
94
+ .sort((a, b) => a - b);
95
+ if (keys.length !== expandedConfig.length) return;
76
96
 
77
- if (i === keys.length - 1) {
78
- ref.current.style.paddingBottom = '30px';
79
- }
97
+ // Build meta (read hints from config)
98
+ const meta = keys.map((i) => ({
99
+ i,
100
+ h: sections[i].height,
101
+ priority: expandedConfig[i]?.priority ?? 2, // 1 > 2 > 3 (1 is most important)
102
+ keepWithNext: !!expandedConfig[i]?.keepWithNext,
103
+ }));
104
+
105
+ const threshold = Math.floor(CONTENT_HEIGHT * FILL_RATIO);
106
+ const pages = [];
107
+ let current = [];
108
+ let used = 0;
109
+ let carryKeep = false;
80
110
 
81
- if (newHeight > PAGE_HEIGHT - 30 - FOOTER_HEIGHT - HEADER_HEIGHT) {
82
- const dif = Math.abs(PAGE_HEIGHT - incrHeight);
83
- ref.current.style.marginTop = '30px';
84
- _page += 1;
85
- _pages.push(_page);
86
-
87
- if (sectionsConfig[keys[i - 1]]) {
88
- const { ref: topRef } = sectionsConfig[keys[i - 1]];
89
- topRef.current.style.marginBottom = `${dif + HEADER_HEIGHT - 24}px`;
90
- incrHeight = height + 24 + HEADER_HEIGHT;
91
- // console.log('margin', dif);
111
+ for (let k = 0; k < meta.length; k++) {
112
+ const curr = meta[k];
113
+ const hWithGap = (current.length === 0 ? 0 : GAP_BETWEEN_BLOCKS) + curr.h;
114
+
115
+ // Honor keep-with-next from the previous block
116
+ if (carryKeep) {
117
+ // start current on a fresh page
118
+ if (current.length) pages.push(current);
119
+ current = [curr.i];
120
+ used = curr.h;
121
+ carryKeep = false;
122
+ if (curr.keepWithNext) carryKeep = true;
123
+ continue;
124
+ }
125
+
126
+ // Oversized singleton: own page
127
+ if (curr.h > threshold && current.length === 0) {
128
+ pages.push([curr.i]);
129
+ current = [];
130
+ used = 0;
131
+ carryKeep = false;
132
+ continue;
133
+ }
134
+
135
+ // Fits on current page?
136
+ if (current.length === 0 || used + hWithGap <= threshold) {
137
+ current.push(curr.i);
138
+ used += hWithGap;
139
+ } else {
140
+ // Rebalance: try pushing a lower-priority block from current to next page to make room
141
+ let moved = false;
142
+ for (let j = current.length - 1; j >= 0; j--) {
143
+ const prevIdx = current[j];
144
+ const prev = meta.find((m) => m.i === prevIdx);
145
+ if (prev.priority > curr.priority) { // lower priority number = higher importance
146
+ const tentativeUsed = used - (j === 0 ? prev.h : (prev.h + GAP_BETWEEN_BLOCKS));
147
+ if (tentativeUsed + hWithGap <= threshold) {
148
+ // move prev off this page
149
+ current.splice(j, 1);
150
+ used = tentativeUsed;
151
+ moved = true;
152
+ break;
153
+ }
92
154
  }
155
+ }
156
+ if (!moved) {
157
+ pages.push(current);
158
+ current = [curr.i];
159
+ used = curr.h; // first element on new page (no top gap)
93
160
  } else {
94
- incrHeight = newHeight + 24;
161
+ // we made space; place current here
162
+ current.push(curr.i);
163
+ used += hWithGap;
95
164
  }
96
- // console.groupEnd();
97
- })
98
- setPages(_pages);
165
+ }
166
+
167
+ if (curr.keepWithNext) carryKeep = true;
99
168
  }
100
- }, [sectionsConfig]);
101
169
 
102
- useEffect(() => {
103
- doSizing();
104
- }, [doSizing]);
170
+ if (current.length) pages.push(current);
171
+ setPageMap(pages); // <-- unlimited pages; grows as needed
172
+ }, [expandedConfig, sections]);
105
173
 
106
- const onChangeHeight = useCallback((index, conf) => {
107
- setSectionsConfig((prev) => ({ ...prev, [index]: conf }))
108
- }, []);
174
+ useEffect(() => {
175
+ paginate();
176
+ }, [paginate]);
109
177
 
110
- const contClassName = formatClassname(['daf-analysis daf-pdf-view', customClassName]);
178
+ const contClassName = formatClassname(["daf-analysis daf-pdf-view", customClassName]);
111
179
 
112
- const renderDashboard = useCallback(() => {
113
- return (
180
+ // Measurement layer (only shown until we have a pageMap)
181
+ const MeasurementLayer = useMemo(
182
+ () => (
114
183
  <div className="view-content">
115
184
  <div className="daf-analysis-layout">
116
- <div className='sections-cont'>
117
- {config.map((widgets, i) => (
118
- <Row
119
- widgets={widgets}
120
- key={`dashboard-sections=${i + 1}`}
121
- i={i}
122
- onChangeHeight={onChangeHeight}
123
- />
185
+ <div className="sections-cont">
186
+ {expandedConfig.map((widgets, i) => (
187
+ <Row key={`measure-${i}`} widgets={widgets} i={i} onChangeHeight={onChangeHeight} />
124
188
  ))}
125
189
  </div>
126
190
  </div>
127
191
  </div>
128
- );
129
- }, [config, onChangeHeight]);
192
+ ),
193
+ [expandedConfig, onChangeHeight]
194
+ );
130
195
 
131
- // <div className="daf-analysis">
132
- // <Header title={t('Dashboard Title')} />
196
+ return (
197
+ <div className={contClassName}>
198
+ {/* Render paginated pages (unlimited) */}
199
+ {pageMap.length > 0
200
+ ? pageMap.map((indices, pageIdx) => (
201
+ <div
202
+ key={`page-${pageIdx}`}
203
+ className="pdf-page"
204
+ style={{ height: PAGE_HEIGHT, position: "relative", overflow: "hidden" }}
205
+ >
206
+ {/* Header */}
207
+ <div style={{ height: HEADER_HEIGHT }} className="flex-row dashboard-header">
208
+ <div className="flex flex-column justify-center flex-1">
209
+ <h2>{title}</h2>
210
+ </div>
211
+ <div className="flex flex-column justify-center">
212
+ <img src={imgSrc} alt="logo" />
213
+ </div>
214
+ </div>
133
215
 
134
- // <div className="content">
135
- // <div className="view-content">
136
- // <div className="daf-analysis-layout">
137
- // <div className='sections-cont w-pt'>
138
- // <section>
216
+ {/* Body */}
217
+ <div className="pdf-body" style={{ minHeight: CONTENT_HEIGHT }}>
218
+ <div style={{ display: "flex", flexDirection: "column", gap: GAP_BETWEEN_BLOCKS }}>
219
+ {indices.map((i) => (
220
+ <Row key={`row-${i}`} widgets={expandedConfig[i]} i={i} onChangeHeight={onChangeHeight} />
221
+ ))}
222
+ </div>
223
+ </div>
139
224
 
140
- return (
141
- <div className={contClassName} style={{ position: 'relative' }}>
142
- {renderDashboard()}
143
- {pages.map((page) => (
144
- <>
145
- <div
146
- style={{ top: ((page - 1) * PAGE_HEIGHT), width: '100%', height: HEADER_HEIGHT - 24, position: 'absolute', left: 0, zIndex: 1000000 }}
147
- key={`headers-${page}`}
148
- className="flex-row dashboard-header"
149
- >
150
- <div className="flex flex-column justify-center flex-1">
151
- <h2>{title}</h2>
152
- </div>
153
- <div className="flex flex-column justify-center">
154
- <img src={imgSrc} alt="logo" />
155
- </div>
156
- </div>
157
- <div
158
- style={{ top: (page * PAGE_HEIGHT) - FOOTER_HEIGHT, width: '100%', height: FOOTER_HEIGHT, position: 'absolute', left: 0, zIndex: 1000000 }}
159
- key={`footers-${page}`}
160
- className="dashboard-footer flex-row"
161
- >
162
- <div className="flex flex-column justify-center">
163
- <h5>{moment().format('DD MMM YYYY')}</h5>
164
- </div>
165
- <div className="flex flex-column justify-center">
166
- <div className="flex gap-2">
167
- <h5>
168
- User ID:
169
- {' '}
170
- <strong>{userId}</strong>
171
- </h5>
172
- <h5>
173
- Account ID:
174
- {' '}
175
- <strong>{accountId}</strong>
176
- </h5>
177
- <h5>
178
- Document ID:
179
- {' '}
180
- <strong>{documentId}</strong>
181
- </h5>
182
- <h5>
183
- Download ID:
184
- {' '}
185
- <strong>{downloadId}</strong>
186
- </h5>
225
+ {/* Footer */}
226
+ <div style={{ height: FOOTER_HEIGHT }} className="dashboard-footer flex-row">
227
+ <div className="flex flex-column justify-center">
228
+ <h5>{moment().format("DD MMM YYYY")}</h5>
229
+ </div>
230
+ <div className="flex flex-column justify-center">
231
+ <div className="flex gap-2">
232
+ <h5>User ID: <strong>{userId}</strong></h5>
233
+ <h5>Account ID: <strong>{accountId}</strong></h5>
234
+ <h5>Document ID: <strong>{documentId}</strong></h5>
235
+ <h5>Download ID: <strong>{downloadId}</strong></h5>
236
+ </div>
237
+ </div>
238
+ <div className="flex flex-column justify-center">
239
+ <h5>{pageIdx + 1}</h5>
240
+ </div>
187
241
  </div>
188
242
  </div>
189
- <div className="flex flex-column justify-center">
190
- <h5>{page}</h5>
191
- </div>
192
- </div>
193
- </>
194
- ))}
243
+ ))
244
+ : MeasurementLayer}
195
245
  </div>
196
246
  );
197
247
  }
198
248
 
199
249
  PdfView.propTypes = {
200
- config: PropTypes.array,
250
+ config: PropTypes.arrayOf(
251
+ PropTypes.shape({
252
+ render: PropTypes.func.isRequired,
253
+ style: PropTypes.object,
254
+ priority: PropTypes.oneOf([1, 2, 3]),
255
+ keepWithNext: PropTypes.bool,
256
+ splittable: PropTypes.bool,
257
+ splitInto: PropTypes.func, // (maxHeightPx) => WidgetConfig[]
258
+ })
259
+ ),
201
260
  customClassName: PropTypes.any,
202
261
  title: PropTypes.string,
203
262
  imgSrc: PropTypes.string,
204
263
  userId: PropTypes.string,
205
264
  accountId: PropTypes.string,
206
265
  documentId: PropTypes.string,
207
- }
266
+ downloadId: PropTypes.string,
267
+ };