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.
- package/dist/components/index.js +207 -130
- package/package.json +1 -1
- package/src/@daf/core/components/Dashboard/PdfView/index.jsx +211 -151
package/dist/components/index.js
CHANGED
|
@@ -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
|
-
|
|
14550
|
-
|
|
14551
|
-
const
|
|
14552
|
-
|
|
14553
|
-
|
|
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
|
-
|
|
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 ===
|
|
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 =
|
|
14578
|
-
imgSrc =
|
|
14579
|
-
userId =
|
|
14580
|
-
accountId =
|
|
14581
|
-
documentId =
|
|
14582
|
-
downloadId =
|
|
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
|
-
|
|
14585
|
-
const [
|
|
14586
|
-
|
|
14587
|
-
|
|
14588
|
-
|
|
14589
|
-
|
|
14590
|
-
|
|
14591
|
-
|
|
14592
|
-
|
|
14593
|
-
|
|
14594
|
-
|
|
14595
|
-
|
|
14596
|
-
|
|
14597
|
-
|
|
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
|
-
|
|
14600
|
-
|
|
14601
|
-
|
|
14602
|
-
|
|
14603
|
-
|
|
14604
|
-
|
|
14605
|
-
|
|
14606
|
-
|
|
14607
|
-
|
|
14608
|
-
|
|
14609
|
-
|
|
14610
|
-
|
|
14611
|
-
|
|
14612
|
-
|
|
14613
|
-
|
|
14614
|
-
|
|
14615
|
-
|
|
14616
|
-
|
|
14617
|
-
|
|
14618
|
-
|
|
14619
|
-
|
|
14620
|
-
|
|
14621
|
-
|
|
14622
|
-
|
|
14623
|
-
|
|
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
|
-
|
|
14697
|
+
// we made space; place current here
|
|
14698
|
+
current.push(curr.i);
|
|
14699
|
+
used += hWithGap;
|
|
14627
14700
|
}
|
|
14628
|
-
|
|
14629
|
-
|
|
14630
|
-
setPages(_pages);
|
|
14701
|
+
}
|
|
14702
|
+
if (curr.keepWithNext) carryKeep = true;
|
|
14631
14703
|
}
|
|
14632
|
-
|
|
14704
|
+
if (current.length) pages.push(current);
|
|
14705
|
+
setPageMap(pages); // <-- unlimited pages; grows as needed
|
|
14706
|
+
}, [expandedConfig, sections]);
|
|
14633
14707
|
React.useEffect(() => {
|
|
14634
|
-
|
|
14635
|
-
}, [
|
|
14636
|
-
const
|
|
14637
|
-
|
|
14638
|
-
|
|
14639
|
-
|
|
14640
|
-
|
|
14641
|
-
|
|
14642
|
-
|
|
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: "
|
|
14647
|
-
children: /*#__PURE__*/jsxRuntime.jsx(
|
|
14648
|
-
|
|
14649
|
-
|
|
14650
|
-
|
|
14651
|
-
|
|
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
|
-
}, [
|
|
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
|
-
|
|
14671
|
-
|
|
14672
|
-
|
|
14673
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
14753
|
+
}), /*#__PURE__*/jsxRuntime.jsx("div", {
|
|
14754
|
+
className: "pdf-body",
|
|
14697
14755
|
style: {
|
|
14698
|
-
|
|
14699
|
-
|
|
14700
|
-
|
|
14701
|
-
|
|
14702
|
-
|
|
14703
|
-
|
|
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(
|
|
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:",
|
|
14785
|
+
children: ["User ID: ", /*#__PURE__*/jsxRuntime.jsx("strong", {
|
|
14717
14786
|
children: userId
|
|
14718
14787
|
})]
|
|
14719
14788
|
}), /*#__PURE__*/jsxRuntime.jsxs("h5", {
|
|
14720
|
-
children: ["Account ID:",
|
|
14789
|
+
children: ["Account ID: ", /*#__PURE__*/jsxRuntime.jsx("strong", {
|
|
14721
14790
|
children: accountId
|
|
14722
14791
|
})]
|
|
14723
14792
|
}), /*#__PURE__*/jsxRuntime.jsxs("h5", {
|
|
14724
|
-
children: ["Document ID:",
|
|
14793
|
+
children: ["Document ID: ", /*#__PURE__*/jsxRuntime.jsx("strong", {
|
|
14725
14794
|
children: documentId
|
|
14726
14795
|
})]
|
|
14727
14796
|
}), /*#__PURE__*/jsxRuntime.jsxs("h5", {
|
|
14728
|
-
children: ["Download ID:",
|
|
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:
|
|
14805
|
+
children: pageIdx + 1
|
|
14737
14806
|
})
|
|
14738
14807
|
})]
|
|
14739
|
-
}
|
|
14740
|
-
}))
|
|
14808
|
+
})]
|
|
14809
|
+
}, "page-".concat(pageIdx))) : MeasurementLayer
|
|
14741
14810
|
});
|
|
14742
14811
|
}
|
|
14743
14812
|
PdfView.propTypes = {
|
|
14744
|
-
config: PropTypes__default["default"].
|
|
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,207 +1,267 @@
|
|
|
1
1
|
import moment from "moment";
|
|
2
|
-
import PropTypes from
|
|
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
|
|
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
|
-
|
|
25
|
-
|
|
14
|
+
const Row = ({ widgets, i, onChangeHeight = () => {} }) => {
|
|
15
|
+
const ref = useRef(null);
|
|
26
16
|
|
|
27
|
-
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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 ===
|
|
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 =
|
|
44
|
-
imgSrc =
|
|
45
|
-
userId =
|
|
46
|
-
accountId =
|
|
47
|
-
documentId =
|
|
48
|
-
downloadId =
|
|
37
|
+
title = "Title",
|
|
38
|
+
imgSrc = "",
|
|
39
|
+
userId = "IDD-0000000",
|
|
40
|
+
accountId = "IDD-0000000",
|
|
41
|
+
documentId = "IDD-0000000",
|
|
42
|
+
downloadId = "DWL-00000123",
|
|
49
43
|
}) {
|
|
50
|
-
|
|
51
|
-
const [
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
// Reset expansion when the external config changes
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
setExpandedConfig(config);
|
|
54
|
+
setSections({});
|
|
55
|
+
setPageMap([]);
|
|
56
|
+
}, [config]);
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
const onChangeHeight = useCallback((index, conf) => {
|
|
59
|
+
setSections((prev) => (prev[index]?.height === conf.height ? prev : { ...prev, [index]: conf }));
|
|
60
|
+
}, []);
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
68
|
-
const { height, ref } = sectionsConfig[k];
|
|
68
|
+
if (keys.length !== expandedConfig.length) return; // not all measured yet
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
78
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
161
|
+
// we made space; place current here
|
|
162
|
+
current.push(curr.i);
|
|
163
|
+
used += hWithGap;
|
|
95
164
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (curr.keepWithNext) carryKeep = true;
|
|
99
168
|
}
|
|
100
|
-
}, [sectionsConfig]);
|
|
101
169
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}, [
|
|
170
|
+
if (current.length) pages.push(current);
|
|
171
|
+
setPageMap(pages); // <-- unlimited pages; grows as needed
|
|
172
|
+
}, [expandedConfig, sections]);
|
|
105
173
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}, []);
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
paginate();
|
|
176
|
+
}, [paginate]);
|
|
109
177
|
|
|
110
|
-
const contClassName = formatClassname([
|
|
178
|
+
const contClassName = formatClassname(["daf-analysis daf-pdf-view", customClassName]);
|
|
111
179
|
|
|
112
|
-
|
|
113
|
-
|
|
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=
|
|
117
|
-
{
|
|
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
|
-
|
|
192
|
+
),
|
|
193
|
+
[expandedConfig, onChangeHeight]
|
|
194
|
+
);
|
|
130
195
|
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
190
|
-
|
|
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.
|
|
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
|
+
};
|