datastake-daf 0.6.421 → 0.6.423

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.
@@ -13303,41 +13303,14 @@ DAFFooter.propTypes = {
13303
13303
  };
13304
13304
 
13305
13305
  const PAGE_HEIGHT = 1587;
13306
- // margin-top: 20, bottom: 20;
13307
13306
  const FOOTER_HEIGHT = 70;
13308
13307
  const HEADER_HEIGHT = 100;
13309
- const Row = _ref => {
13310
- let {
13311
- widgets,
13312
- i,
13313
- onChangeHeight = () => {}
13314
- } = _ref;
13315
- const ref = React.useRef();
13316
- const [height, setHeight] = React.useState(0);
13317
- React.useEffect(() => {
13318
- const observer = new ResizeObserver(entries => {
13319
- for (const entry of entries) {
13320
- setHeight(entry.contentRect.height);
13321
- }
13322
- });
13323
- observer.observe(ref.current);
13324
- return () => observer.disconnect();
13325
- }, []);
13326
- React.useEffect(() => {
13327
- if (height) {
13328
- onChangeHeight(i, {
13329
- height,
13330
- ref
13331
- });
13332
- }
13333
- }, [height]);
13334
- return /*#__PURE__*/jsxRuntime.jsx("section", {
13335
- ref: ref,
13336
- style: widgets.style,
13337
- children: typeof widgets.render === 'function' ? widgets.render() : null
13338
- });
13339
- };
13340
- function PdfView(_ref2) {
13308
+ const PAGE_MARGIN_TOP = 20;
13309
+ const PAGE_MARGIN_BOTTOM = 20;
13310
+ const CONTENT_HEIGHT = 1200; // 1397px
13311
+ const SECTION_GAP = 24;
13312
+ const isHTMLElement = node => node instanceof HTMLElement;
13313
+ function PdfView(_ref) {
13341
13314
  let {
13342
13315
  config = [],
13343
13316
  customClassName,
@@ -13347,105 +13320,224 @@ function PdfView(_ref2) {
13347
13320
  accountId = 'IDD-0000000',
13348
13321
  documentId = 'IDD-0000000',
13349
13322
  downloadId = 'DWL-00000123'
13350
- } = _ref2;
13351
- const [sectionsConfig, setSectionsConfig] = React.useState({});
13323
+ } = _ref;
13352
13324
  const [pages, setPages] = React.useState([1]);
13353
- const doSizing = React.useCallback(() => {
13354
- const keys = Object.keys(sectionsConfig);
13355
- let _pages = [1];
13356
- let _page = 1;
13357
- if (keys.length === config.length) {
13358
- let incrHeight = 0;
13359
- keys.forEach(k => {
13360
- const {
13361
- ref
13362
- } = sectionsConfig[k];
13363
- ref.current.style.marginBottom = '0px';
13364
- // ref.current.style.marginTop = '15px';
13325
+ const contentRef = React.useRef(null);
13326
+ const sectionsRef = React.useRef([]);
13327
+ const resizeTimeoutRef = React.useRef(null);
13328
+ const isCalculatingRef = React.useRef(false);
13329
+ const resetPaginationStyles = React.useCallback(sections => {
13330
+ sections.forEach(section => {
13331
+ if (!section) return;
13332
+ section.style.marginTop = '';
13333
+ section.style.marginBottom = '';
13334
+ section.style.pageBreakBefore = '';
13335
+ section.style.breakBefore = '';
13336
+ section.removeAttribute('data-pdf-page');
13337
+
13338
+ // Reset all children
13339
+ const allElements = section.querySelectorAll('*');
13340
+ allElements.forEach(el => {
13341
+ if (!isHTMLElement(el)) return;
13342
+ el.style.marginTop = '';
13343
+ el.style.marginBottom = '';
13344
+ el.style.pageBreakBefore = '';
13345
+ el.style.breakBefore = '';
13346
+ el.removeAttribute('data-pdf-page');
13365
13347
  });
13366
- keys.forEach((k, i) => {
13367
- const {
13368
- height,
13369
- ref
13370
- } = sectionsConfig[k];
13371
- if (i === 0) {
13372
- ref.current.style.marginTop = "".concat(HEADER_HEIGHT, "px");
13373
- incrHeight += HEADER_HEIGHT;
13374
- }
13375
- const newHeight = incrHeight + height;
13376
- if (i === keys.length - 1) {
13377
- ref.current.style.paddingBottom = '30px';
13378
- }
13379
- if (newHeight > PAGE_HEIGHT - 30 - FOOTER_HEIGHT - HEADER_HEIGHT) {
13380
- const dif = Math.abs(PAGE_HEIGHT - incrHeight);
13381
- ref.current.style.marginTop = '30px';
13382
- _page += 1;
13383
- _pages.push(_page);
13384
- if (sectionsConfig[keys[i - 1]]) {
13385
- const {
13386
- ref: topRef
13387
- } = sectionsConfig[keys[i - 1]];
13388
- topRef.current.style.marginBottom = "".concat(dif + HEADER_HEIGHT - 24, "px");
13389
- incrHeight = height + 24 + HEADER_HEIGHT;
13390
- // console.log('margin', dif);
13348
+ });
13349
+ }, []);
13350
+ const getAllBreakableElements = React.useCallback(section => {
13351
+ const elements = [];
13352
+ const shouldInclude = el => {
13353
+ if (!isHTMLElement(el)) return false;
13354
+ const style = window.getComputedStyle(el);
13355
+ if (style.display === 'inline' || style.display === 'contents') return false;
13356
+ if (style.position === 'absolute' || style.position === 'fixed') return false;
13357
+ if (el.offsetHeight === 0) return false;
13358
+ return true;
13359
+ };
13360
+ const traverse = parent => {
13361
+ Array.from(parent.children).forEach(child => {
13362
+ if (shouldInclude(child)) {
13363
+ elements.push(child);
13364
+ traverse(child);
13365
+ }
13366
+ });
13367
+ };
13368
+ traverse(section);
13369
+ return elements;
13370
+ }, []);
13371
+ const paginateContent = React.useCallback(() => {
13372
+ // Prevent recursive calls
13373
+ if (isCalculatingRef.current) return;
13374
+ const sections = sectionsRef.current.filter(Boolean);
13375
+ if (sections.length === 0) return;
13376
+ isCalculatingRef.current = true;
13377
+ resetPaginationStyles(sections);
13378
+
13379
+ // Force layout recalc
13380
+ sections.forEach(s => s.offsetHeight);
13381
+ let currentPage = 1;
13382
+ let currentPageHeight = 0;
13383
+ sections.forEach((section, sectionIndex) => {
13384
+ // Measure height after reset
13385
+ const sectionHeight = section.offsetHeight;
13386
+ const isFirstOnPage = currentPageHeight === 0;
13387
+ const gapBefore = isFirstOnPage ? 0 : SECTION_GAP;
13388
+ if (currentPageHeight + gapBefore + sectionHeight <= CONTENT_HEIGHT) {
13389
+ // Fits in current page
13390
+ section.setAttribute('data-pdf-page', currentPage.toString());
13391
+ if (isFirstOnPage) {
13392
+ // First item on page
13393
+ if (currentPage === 1) {
13394
+ // Very first section on first page - needs to be below header
13395
+ section.style.marginTop = '0px'; // Container padding handles this
13396
+ } else {
13397
+ // First section on subsequent pages - no margin (container padding handles it)
13398
+ section.style.marginTop = '0px';
13391
13399
  }
13392
13400
  } else {
13393
- incrHeight = newHeight + 24;
13401
+ // Not first item on page - add gap
13402
+ section.style.marginTop = "".concat(SECTION_GAP, "px");
13394
13403
  }
13395
- // console.groupEnd();
13396
- });
13397
- setPages(_pages);
13398
- }
13399
- }, [sectionsConfig]);
13404
+ currentPageHeight += gapBefore + sectionHeight;
13405
+ } else {
13406
+ // Doesn't fit - need to move to next page
13407
+ const remainingSpace = CONTENT_HEIGHT - currentPageHeight;
13408
+
13409
+ // Calculate margin needed to push this section to the next page
13410
+ // remainingSpace = space left on current page
13411
+ // Then we need to skip: footer margin + footer + header margin + header
13412
+ const pushMargin = remainingSpace + PAGE_MARGIN_BOTTOM + FOOTER_HEIGHT + PAGE_MARGIN_TOP + HEADER_HEIGHT;
13413
+
13414
+ // Move entire section to next page
13415
+ currentPage += 1;
13416
+ section.setAttribute('data-pdf-page', currentPage.toString());
13417
+ section.style.pageBreakBefore = 'always';
13418
+ section.style.breakBefore = 'page';
13419
+ section.style.marginTop = "".concat(pushMargin, "px");
13420
+
13421
+ // Start fresh height counter for new page
13422
+ currentPageHeight = sectionHeight;
13423
+
13424
+ // Check if section is too tall and needs internal breaks
13425
+ if (sectionHeight > CONTENT_HEIGHT) {
13426
+ // Try to break within section
13427
+ const innerElements = getAllBreakableElements(section);
13428
+ let pageHeightInSection = 0;
13429
+ let sectionPage = currentPage;
13430
+ for (const el of innerElements) {
13431
+ const rect = el.getBoundingClientRect();
13432
+ const sectionRect = section.getBoundingClientRect();
13433
+ const relativeTop = rect.top - sectionRect.top;
13434
+ const elHeight = rect.height;
13435
+
13436
+ // Check if this element would exceed the page
13437
+ if (pageHeightInSection + elHeight > CONTENT_HEIGHT && relativeTop > 50) {
13438
+ // Calculate remaining space on this page within the section
13439
+ const remainingInSection = CONTENT_HEIGHT - pageHeightInSection;
13440
+ const pushMarginInner = remainingInSection + PAGE_MARGIN_BOTTOM + FOOTER_HEIGHT + PAGE_MARGIN_TOP + HEADER_HEIGHT;
13441
+ sectionPage += 1;
13442
+ el.setAttribute('data-pdf-page', sectionPage.toString());
13443
+ el.style.pageBreakBefore = 'always';
13444
+ el.style.breakBefore = 'page';
13445
+ el.style.marginTop = "".concat(pushMarginInner, "px");
13446
+
13447
+ // Reset page height for new page within section
13448
+ pageHeightInSection = elHeight;
13449
+ } else {
13450
+ pageHeightInSection += elHeight;
13451
+ }
13452
+ }
13453
+
13454
+ // Update current page if we created additional pages
13455
+ if (sectionPage > currentPage) {
13456
+ currentPage = sectionPage;
13457
+ currentPageHeight = pageHeightInSection;
13458
+ }
13459
+ }
13460
+ }
13461
+ });
13462
+ setPages(Array.from({
13463
+ length: currentPage
13464
+ }, (_, idx) => idx + 1));
13465
+
13466
+ // Reset flag after a brief delay to allow next calculation
13467
+ setTimeout(() => {
13468
+ isCalculatingRef.current = false;
13469
+ }, 100);
13470
+ }, [getAllBreakableElements, resetPaginationStyles]);
13471
+ React.useLayoutEffect(() => {
13472
+ // Use requestAnimationFrame to ensure DOM is painted
13473
+ const rafId = requestAnimationFrame(() => {
13474
+ paginateContent();
13475
+ });
13476
+ return () => cancelAnimationFrame(rafId);
13477
+ }, [paginateContent, config]);
13400
13478
  React.useEffect(() => {
13401
- doSizing();
13402
- }, [doSizing]);
13403
- const onChangeHeight = React.useCallback((index, conf) => {
13404
- setSectionsConfig(prev => _objectSpread2(_objectSpread2({}, prev), {}, {
13405
- [index]: conf
13406
- }));
13407
- }, []);
13479
+ const sections = sectionsRef.current.filter(Boolean);
13480
+ if (sections.length === 0) return undefined;
13481
+ const observer = new ResizeObserver(() => {
13482
+ // Debounce the pagination calculation
13483
+ if (resizeTimeoutRef.current) {
13484
+ clearTimeout(resizeTimeoutRef.current);
13485
+ }
13486
+ resizeTimeoutRef.current = setTimeout(() => {
13487
+ // Use requestAnimationFrame to avoid ResizeObserver loop warning
13488
+ requestAnimationFrame(() => {
13489
+ paginateContent();
13490
+ });
13491
+ }, 150);
13492
+ });
13493
+ sections.forEach(section => observer.observe(section));
13494
+ return () => {
13495
+ observer.disconnect();
13496
+ if (resizeTimeoutRef.current) {
13497
+ clearTimeout(resizeTimeoutRef.current);
13498
+ }
13499
+ };
13500
+ }, [paginateContent, config.length]);
13408
13501
  const contClassName = formatClassname(['daf-analysis daf-pdf-view', customClassName]);
13409
- const renderDashboard = React.useCallback(() => {
13410
- return /*#__PURE__*/jsxRuntime.jsx("div", {
13502
+ return /*#__PURE__*/jsxRuntime.jsxs("div", {
13503
+ className: contClassName,
13504
+ style: {
13505
+ position: 'relative',
13506
+ minHeight: "".concat(PAGE_HEIGHT, "px")
13507
+ },
13508
+ children: [/*#__PURE__*/jsxRuntime.jsx("div", {
13411
13509
  className: "view-content",
13412
13510
  children: /*#__PURE__*/jsxRuntime.jsx("div", {
13413
13511
  className: "daf-analysis-layout",
13414
13512
  children: /*#__PURE__*/jsxRuntime.jsx("div", {
13415
13513
  className: "sections-cont",
13416
- children: config.map((widgets, i) => /*#__PURE__*/jsxRuntime.jsx(Row, {
13417
- widgets: widgets,
13418
- i: i,
13419
- onChangeHeight: onChangeHeight
13420
- }, "dashboard-sections=".concat(i + 1)))
13514
+ ref: contentRef,
13515
+ "data-pdf-content-root": "true",
13516
+ style: {
13517
+ marginTop: "".concat(HEADER_HEIGHT + PAGE_MARGIN_TOP, "px"),
13518
+ // paddingBottom: `${FOOTER_HEIGHT + PAGE_MARGIN_BOTTOM}px !important`,
13519
+ position: 'relative'
13520
+ },
13521
+ children: config.map((widgets, index) => /*#__PURE__*/jsxRuntime.jsx("section", {
13522
+ ref: el => {
13523
+ sectionsRef.current[index] = el;
13524
+ },
13525
+ style: widgets.style,
13526
+ "data-pdf-section": "true",
13527
+ children: typeof widgets.render === 'function' ? widgets.render() : null
13528
+ }, "dashboard-section-".concat(index + 1)))
13421
13529
  })
13422
13530
  })
13423
- });
13424
- }, [config, onChangeHeight]);
13425
-
13426
- // <div className="daf-analysis">
13427
- // <Header title={t('Dashboard Title')} />
13428
-
13429
- // <div className="content">
13430
- // <div className="view-content">
13431
- // <div className="daf-analysis-layout">
13432
- // <div className='sections-cont w-pt'>
13433
- // <section>
13434
-
13435
- return /*#__PURE__*/jsxRuntime.jsxs("div", {
13436
- className: contClassName,
13437
- style: {
13438
- position: 'relative'
13439
- },
13440
- children: [renderDashboard(), pages.map(page => /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
13531
+ }), pages.map(page => /*#__PURE__*/jsxRuntime.jsxs(React.Fragment, {
13441
13532
  children: [/*#__PURE__*/jsxRuntime.jsxs("div", {
13442
13533
  style: {
13443
- top: (page - 1) * PAGE_HEIGHT,
13534
+ top: (page - 1) * PAGE_HEIGHT + PAGE_MARGIN_TOP,
13444
13535
  width: '100%',
13445
- height: HEADER_HEIGHT - 24,
13536
+ height: HEADER_HEIGHT,
13446
13537
  position: 'absolute',
13447
13538
  left: 0,
13448
- zIndex: 1000000
13539
+ zIndex: 1000000,
13540
+ pointerEvents: 'none'
13449
13541
  },
13450
13542
  className: "flex-row dashboard-header",
13451
13543
  children: [/*#__PURE__*/jsxRuntime.jsx("div", {
@@ -13460,14 +13552,15 @@ function PdfView(_ref2) {
13460
13552
  alt: "logo"
13461
13553
  })
13462
13554
  })]
13463
- }, "headers-".concat(page)), /*#__PURE__*/jsxRuntime.jsxs("div", {
13555
+ }), /*#__PURE__*/jsxRuntime.jsxs("div", {
13464
13556
  style: {
13465
- top: page * PAGE_HEIGHT - FOOTER_HEIGHT,
13557
+ top: page * PAGE_HEIGHT - FOOTER_HEIGHT - PAGE_MARGIN_BOTTOM,
13466
13558
  width: '100%',
13467
13559
  height: FOOTER_HEIGHT,
13468
13560
  position: 'absolute',
13469
13561
  left: 0,
13470
- zIndex: 1000000
13562
+ zIndex: 1000000,
13563
+ pointerEvents: 'none'
13471
13564
  },
13472
13565
  className: "dashboard-footer flex-row",
13473
13566
  children: [/*#__PURE__*/jsxRuntime.jsx("div", {
@@ -13503,8 +13596,8 @@ function PdfView(_ref2) {
13503
13596
  children: page
13504
13597
  })
13505
13598
  })]
13506
- }, "footers-".concat(page))]
13507
- }))]
13599
+ })]
13600
+ }, "pdf-decor-".concat(page)))]
13508
13601
  });
13509
13602
  }
13510
13603
  PdfView.propTypes = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datastake-daf",
3
- "version": "0.6.421",
3
+ "version": "0.6.423",
4
4
  "dependencies": {
5
5
  "@ant-design/icons": "^5.2.5",
6
6
  "@antv/g2": "^5.1.1",
@@ -189,5 +189,15 @@ export const Primary = {
189
189
  },
190
190
  ],
191
191
  },
192
- }
193
-
192
+ render: (args) => (
193
+ <div style={{
194
+ height: '100vh',
195
+ overflowY: 'auto',
196
+ background: "#f8f8f8",
197
+ padding: "20px",
198
+ }}
199
+ >
200
+ <PdfView {...args} />
201
+ </div>
202
+ ),
203
+ };
@@ -1,41 +1,17 @@
1
1
  import moment from "moment";
2
2
  import PropTypes from 'prop-types';
3
- import { useCallback, useEffect, useRef, useState } from "react";
3
+ import { Fragment, useCallback, useEffect, useLayoutEffect, 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
7
  const FOOTER_HEIGHT = 70;
9
8
  const HEADER_HEIGHT = 100;
9
+ const PAGE_MARGIN_TOP = 20;
10
+ const PAGE_MARGIN_BOTTOM = 20;
11
+ const CONTENT_HEIGHT = 1200; // 1397px
12
+ const SECTION_GAP = 24;
10
13
 
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);
23
-
24
- return () => observer.disconnect();
25
- }, []);
26
-
27
- useEffect(() => {
28
- if (height) {
29
- onChangeHeight(i, { height, ref });
30
- }
31
- }, [height])
32
-
33
- return (
34
- <section ref={ref} style={widgets.style}>
35
- {typeof widgets.render === 'function' ? widgets.render() : null}
36
- </section>
37
- )
38
- };
14
+ const isHTMLElement = (node) => node instanceof HTMLElement;
39
15
 
40
16
  export default function PdfView({
41
17
  config = [],
@@ -47,104 +23,251 @@ export default function PdfView({
47
23
  documentId = 'IDD-0000000',
48
24
  downloadId = 'DWL-00000123',
49
25
  }) {
50
- const [sectionsConfig, setSectionsConfig] = useState({});
51
26
  const [pages, setPages] = useState([1]);
27
+ const contentRef = useRef(null);
28
+ const sectionsRef = useRef([]);
29
+ const resizeTimeoutRef = useRef(null);
30
+ const isCalculatingRef = useRef(false);
31
+
32
+ const resetPaginationStyles = useCallback((sections) => {
33
+ sections.forEach((section) => {
34
+ if (!section) return;
35
+ section.style.marginTop = '';
36
+ section.style.marginBottom = '';
37
+ section.style.pageBreakBefore = '';
38
+ section.style.breakBefore = '';
39
+ section.removeAttribute('data-pdf-page');
40
+
41
+ // Reset all children
42
+ const allElements = section.querySelectorAll('*');
43
+ allElements.forEach((el) => {
44
+ if (!isHTMLElement(el)) return;
45
+ el.style.marginTop = '';
46
+ el.style.marginBottom = '';
47
+ el.style.pageBreakBefore = '';
48
+ el.style.breakBefore = '';
49
+ el.removeAttribute('data-pdf-page');
50
+ });
51
+ });
52
+ }, []);
53
+
54
+ const getAllBreakableElements = useCallback((section) => {
55
+ const elements = [];
56
+
57
+ const shouldInclude = (el) => {
58
+ if (!isHTMLElement(el)) return false;
59
+ const style = window.getComputedStyle(el);
60
+ if (style.display === 'inline' || style.display === 'contents') return false;
61
+ if (style.position === 'absolute' || style.position === 'fixed') return false;
62
+ if (el.offsetHeight === 0) return false;
63
+ return true;
64
+ };
52
65
 
53
- const doSizing = useCallback(() => {
54
- const keys = Object.keys(sectionsConfig);
55
- let _pages = [1];
56
- let _page = 1;
66
+ const traverse = (parent) => {
67
+ Array.from(parent.children).forEach((child) => {
68
+ if (shouldInclude(child)) {
69
+ elements.push(child);
70
+ traverse(child);
71
+ }
72
+ });
73
+ };
57
74
 
58
- if (keys.length === config.length) {
59
- let incrHeight = 0;
75
+ traverse(section);
76
+ return elements;
77
+ }, []);
60
78
 
61
- keys.forEach(k => {
62
- const { ref } = sectionsConfig[k];
63
- ref.current.style.marginBottom = '0px';
64
- // ref.current.style.marginTop = '15px';
65
- })
79
+ const paginateContent = useCallback(() => {
80
+ // Prevent recursive calls
81
+ if (isCalculatingRef.current) return;
82
+
83
+ const sections = sectionsRef.current.filter(Boolean);
84
+ if (sections.length === 0) return;
66
85
 
67
- keys.forEach((k, i) => {
68
- const { height, ref } = sectionsConfig[k];
86
+ isCalculatingRef.current = true;
69
87
 
70
- if (i === 0) {
71
- ref.current.style.marginTop = `${HEADER_HEIGHT}px`;
72
- incrHeight += HEADER_HEIGHT;
73
- }
88
+ resetPaginationStyles(sections);
74
89
 
75
- const newHeight = incrHeight + height;
90
+ // Force layout recalc
91
+ sections.forEach(s => s.offsetHeight);
76
92
 
77
- if (i === keys.length - 1) {
78
- ref.current.style.paddingBottom = '30px';
79
- }
93
+ let currentPage = 1;
94
+ let currentPageHeight = 0;
95
+ const breaks = [];
80
96
 
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);
97
+ sections.forEach((section, sectionIndex) => {
98
+ // Measure height after reset
99
+ const sectionHeight = section.offsetHeight;
100
+ const isFirstOnPage = currentPageHeight === 0;
101
+ const gapBefore = isFirstOnPage ? 0 : SECTION_GAP;
102
+
103
+ if (currentPageHeight + gapBefore + sectionHeight <= CONTENT_HEIGHT) {
104
+ // Fits in current page
105
+ section.setAttribute('data-pdf-page', currentPage.toString());
106
+
107
+ if (isFirstOnPage) {
108
+ // First item on page
109
+ if (currentPage === 1) {
110
+ // Very first section on first page - needs to be below header
111
+ section.style.marginTop = '0px'; // Container padding handles this
112
+ } else {
113
+ // First section on subsequent pages - no margin (container padding handles it)
114
+ section.style.marginTop = '0px';
92
115
  }
93
116
  } else {
94
- incrHeight = newHeight + 24;
117
+ // Not first item on page - add gap
118
+ section.style.marginTop = `${SECTION_GAP}px`;
119
+ }
120
+ currentPageHeight += gapBefore + sectionHeight;
121
+ } else {
122
+ // Doesn't fit - need to move to next page
123
+ const remainingSpace = CONTENT_HEIGHT - currentPageHeight;
124
+
125
+ // Calculate margin needed to push this section to the next page
126
+ // remainingSpace = space left on current page
127
+ // Then we need to skip: footer margin + footer + header margin + header
128
+ const pushMargin = remainingSpace + PAGE_MARGIN_BOTTOM + FOOTER_HEIGHT + PAGE_MARGIN_TOP + HEADER_HEIGHT;
129
+
130
+ // Move entire section to next page
131
+ currentPage += 1;
132
+ breaks.push({ section, type: 'section', page: currentPage });
133
+ section.setAttribute('data-pdf-page', currentPage.toString());
134
+ section.style.pageBreakBefore = 'always';
135
+ section.style.breakBefore = 'page';
136
+ section.style.marginTop = `${pushMargin}px`;
137
+
138
+ // Start fresh height counter for new page
139
+ currentPageHeight = sectionHeight;
140
+
141
+ // Check if section is too tall and needs internal breaks
142
+ if (sectionHeight > CONTENT_HEIGHT) {
143
+ // Try to break within section
144
+ const innerElements = getAllBreakableElements(section);
145
+ let pageHeightInSection = 0;
146
+ let sectionPage = currentPage;
147
+
148
+ for (const el of innerElements) {
149
+ const rect = el.getBoundingClientRect();
150
+ const sectionRect = section.getBoundingClientRect();
151
+ const relativeTop = rect.top - sectionRect.top;
152
+ const elHeight = rect.height;
153
+
154
+ // Check if this element would exceed the page
155
+ if (pageHeightInSection + elHeight > CONTENT_HEIGHT && relativeTop > 50) {
156
+ // Calculate remaining space on this page within the section
157
+ const remainingInSection = CONTENT_HEIGHT - pageHeightInSection;
158
+ const pushMarginInner = remainingInSection + PAGE_MARGIN_BOTTOM + FOOTER_HEIGHT + PAGE_MARGIN_TOP + HEADER_HEIGHT;
159
+
160
+ sectionPage += 1;
161
+ breaks.push({ element: el, type: 'inner', page: sectionPage });
162
+ el.setAttribute('data-pdf-page', sectionPage.toString());
163
+ el.style.pageBreakBefore = 'always';
164
+ el.style.breakBefore = 'page';
165
+ el.style.marginTop = `${pushMarginInner}px`;
166
+
167
+ // Reset page height for new page within section
168
+ pageHeightInSection = elHeight;
169
+ } else {
170
+ pageHeightInSection += elHeight;
171
+ }
172
+ }
173
+
174
+ // Update current page if we created additional pages
175
+ if (sectionPage > currentPage) {
176
+ currentPage = sectionPage;
177
+ currentPageHeight = pageHeightInSection;
178
+ }
95
179
  }
96
- // console.groupEnd();
97
- })
98
- setPages(_pages);
99
- }
100
- }, [sectionsConfig]);
180
+ }
181
+ });
182
+
183
+ setPages(Array.from({ length: currentPage }, (_, idx) => idx + 1));
184
+
185
+ // Reset flag after a brief delay to allow next calculation
186
+ setTimeout(() => {
187
+ isCalculatingRef.current = false;
188
+ }, 100);
189
+ }, [getAllBreakableElements, resetPaginationStyles]);
190
+
191
+ useLayoutEffect(() => {
192
+ // Use requestAnimationFrame to ensure DOM is painted
193
+ const rafId = requestAnimationFrame(() => {
194
+ paginateContent();
195
+ });
196
+
197
+ return () => cancelAnimationFrame(rafId);
198
+ }, [paginateContent, config]);
101
199
 
102
200
  useEffect(() => {
103
- doSizing();
104
- }, [doSizing]);
201
+ const sections = sectionsRef.current.filter(Boolean);
202
+ if (sections.length === 0) return undefined;
105
203
 
106
- const onChangeHeight = useCallback((index, conf) => {
107
- setSectionsConfig((prev) => ({ ...prev, [index]: conf }))
108
- }, []);
204
+ const observer = new ResizeObserver(() => {
205
+ // Debounce the pagination calculation
206
+ if (resizeTimeoutRef.current) {
207
+ clearTimeout(resizeTimeoutRef.current);
208
+ }
209
+
210
+ resizeTimeoutRef.current = setTimeout(() => {
211
+ // Use requestAnimationFrame to avoid ResizeObserver loop warning
212
+ requestAnimationFrame(() => {
213
+ paginateContent();
214
+ });
215
+ }, 150);
216
+ });
217
+
218
+ sections.forEach(section => observer.observe(section));
219
+
220
+ return () => {
221
+ observer.disconnect();
222
+ if (resizeTimeoutRef.current) {
223
+ clearTimeout(resizeTimeoutRef.current);
224
+ }
225
+ };
226
+ }, [paginateContent, config.length]);
109
227
 
110
228
  const contClassName = formatClassname(['daf-analysis daf-pdf-view', customClassName]);
111
229
 
112
- const renderDashboard = useCallback(() => {
113
- return (
230
+ return (
231
+ <div className={contClassName} style={{ position: 'relative', minHeight: `${PAGE_HEIGHT}px` }}>
114
232
  <div className="view-content">
115
233
  <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
- />
234
+ <div
235
+ className='sections-cont'
236
+ ref={contentRef}
237
+ data-pdf-content-root="true"
238
+ style={{
239
+ marginTop: `${HEADER_HEIGHT + PAGE_MARGIN_TOP}px`,
240
+ // paddingBottom: `${FOOTER_HEIGHT + PAGE_MARGIN_BOTTOM}px !important`,
241
+ position: 'relative',
242
+ }}
243
+ >
244
+ {config.map((widgets, index) => (
245
+ <section
246
+ key={`dashboard-section-${index + 1}`}
247
+ ref={(el) => {
248
+ sectionsRef.current[index] = el;
249
+ }}
250
+ style={widgets.style}
251
+ data-pdf-section="true"
252
+ >
253
+ {typeof widgets.render === 'function' ? widgets.render() : null}
254
+ </section>
124
255
  ))}
125
256
  </div>
126
257
  </div>
127
258
  </div>
128
- );
129
- }, [config, onChangeHeight]);
130
-
131
- // <div className="daf-analysis">
132
- // <Header title={t('Dashboard Title')} />
133
-
134
- // <div className="content">
135
- // <div className="view-content">
136
- // <div className="daf-analysis-layout">
137
- // <div className='sections-cont w-pt'>
138
- // <section>
139
-
140
- return (
141
- <div className={contClassName} style={{ position: 'relative' }}>
142
- {renderDashboard()}
143
259
  {pages.map((page) => (
144
- <>
260
+ <Fragment key={`pdf-decor-${page}`}>
145
261
  <div
146
- style={{ top: ((page - 1) * PAGE_HEIGHT), width: '100%', height: HEADER_HEIGHT - 24, position: 'absolute', left: 0, zIndex: 1000000 }}
147
- key={`headers-${page}`}
262
+ style={{
263
+ top: ((page - 1) * PAGE_HEIGHT) + PAGE_MARGIN_TOP,
264
+ width: '100%',
265
+ height: HEADER_HEIGHT,
266
+ position: 'absolute',
267
+ left: 0,
268
+ zIndex: 1000000,
269
+ pointerEvents: 'none'
270
+ }}
148
271
  className="flex-row dashboard-header"
149
272
  >
150
273
  <div className="flex flex-column justify-center flex-1">
@@ -155,8 +278,15 @@ export default function PdfView({
155
278
  </div>
156
279
  </div>
157
280
  <div
158
- style={{ top: (page * PAGE_HEIGHT) - FOOTER_HEIGHT, width: '100%', height: FOOTER_HEIGHT, position: 'absolute', left: 0, zIndex: 1000000 }}
159
- key={`footers-${page}`}
281
+ style={{
282
+ top: (page * PAGE_HEIGHT) - FOOTER_HEIGHT - PAGE_MARGIN_BOTTOM,
283
+ width: '100%',
284
+ height: FOOTER_HEIGHT,
285
+ position: 'absolute',
286
+ left: 0,
287
+ zIndex: 1000000,
288
+ pointerEvents: 'none'
289
+ }}
160
290
  className="dashboard-footer flex-row"
161
291
  >
162
292
  <div className="flex flex-column justify-center">
@@ -190,7 +320,7 @@ export default function PdfView({
190
320
  <h5>{page}</h5>
191
321
  </div>
192
322
  </div>
193
- </>
323
+ </Fragment>
194
324
  ))}
195
325
  </div>
196
326
  );
@@ -1,330 +0,0 @@
1
- /* Isolated Mapbox GL CSS - Scoped to prevent Leaflet conflicts */
2
-
3
- /* Mapbox GL Core Styles - Scoped with .mapbox-gl-scope */
4
- .mapbox-gl-scope .mapboxgl-map {
5
- font: 12px/20px Helvetica Neue, Arial, Helvetica, sans-serif;
6
- overflow: hidden;
7
- position: relative;
8
- -webkit-tap-highlight-color: rgb(0 0 0/0);
9
- }
10
-
11
- .mapbox-gl-scope .mapboxgl-canvas {
12
- left: 0;
13
- position: absolute;
14
- top: 0;
15
- }
16
-
17
- .mapbox-gl-scope .mapboxgl-map:-webkit-full-screen {
18
- height: 100%;
19
- width: 100%;
20
- }
21
-
22
- .mapbox-gl-scope .mapboxgl-canary {
23
- background-color: salmon;
24
- }
25
-
26
- .mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-interactive,
27
- .mapbox-gl-scope .mapboxgl-ctrl-group button.mapboxgl-ctrl-compass {
28
- cursor: grab;
29
- -webkit-user-select: none;
30
- user-select: none;
31
- }
32
-
33
- .mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-interactive.mapboxgl-track-pointer {
34
- cursor: pointer;
35
- }
36
-
37
- .mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-interactive:active,
38
- .mapbox-gl-scope .mapboxgl-ctrl-group button.mapboxgl-ctrl-compass:active {
39
- cursor: grabbing;
40
- }
41
-
42
- .mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate,
43
- .mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate .mapboxgl-canvas {
44
- touch-action: pan-x pan-y;
45
- }
46
-
47
- .mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-touch-drag-pan,
48
- .mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-touch-drag-pan .mapboxgl-canvas {
49
- touch-action: pinch-zoom;
50
- }
51
-
52
- .mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate.mapboxgl-touch-drag-pan,
53
- .mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate.mapboxgl-touch-drag-pan .mapboxgl-canvas {
54
- touch-action: none;
55
- }
56
-
57
- /* Control positioning */
58
- .mapbox-gl-scope .mapboxgl-ctrl-bottom,
59
- .mapbox-gl-scope .mapboxgl-ctrl-bottom-left,
60
- .mapbox-gl-scope .mapboxgl-ctrl-bottom-right,
61
- .mapbox-gl-scope .mapboxgl-ctrl-left,
62
- .mapbox-gl-scope .mapboxgl-ctrl-right,
63
- .mapbox-gl-scope .mapboxgl-ctrl-top,
64
- .mapbox-gl-scope .mapboxgl-ctrl-top-left,
65
- .mapbox-gl-scope .mapboxgl-ctrl-top-right {
66
- pointer-events: none;
67
- position: absolute;
68
- z-index: 2;
69
- }
70
-
71
- .mapbox-gl-scope .mapboxgl-ctrl-top-left {
72
- left: 0;
73
- top: 0;
74
- }
75
-
76
- .mapbox-gl-scope .mapboxgl-ctrl-top {
77
- left: 50%;
78
- top: 0;
79
- transform: translateX(-50%);
80
- }
81
-
82
- .mapbox-gl-scope .mapboxgl-ctrl-top-right {
83
- right: 0;
84
- top: 0;
85
- }
86
-
87
- .mapbox-gl-scope .mapboxgl-ctrl-right {
88
- right: 0;
89
- top: 50%;
90
- transform: translateY(-50%);
91
- }
92
-
93
- .mapbox-gl-scope .mapboxgl-ctrl-bottom-right {
94
- bottom: 0;
95
- right: 0;
96
- }
97
-
98
- .mapbox-gl-scope .mapboxgl-ctrl-bottom {
99
- bottom: 0;
100
- left: 50%;
101
- transform: translateX(-50%);
102
- }
103
-
104
- .mapbox-gl-scope .mapboxgl-ctrl-bottom-left {
105
- bottom: 0;
106
- left: 0;
107
- }
108
-
109
- .mapbox-gl-scope .mapboxgl-ctrl-left {
110
- left: 0;
111
- top: 50%;
112
- transform: translateY(-50%);
113
- }
114
-
115
- .mapbox-gl-scope .mapboxgl-ctrl {
116
- clear: both;
117
- pointer-events: auto;
118
- transform: translate(0);
119
- }
120
-
121
- .mapbox-gl-scope .mapboxgl-ctrl-top-left .mapboxgl-ctrl {
122
- float: left;
123
- margin: 10px 0 0 10px;
124
- }
125
-
126
- .mapbox-gl-scope .mapboxgl-ctrl-top .mapboxgl-ctrl {
127
- float: left;
128
- margin: 10px 0;
129
- }
130
-
131
- .mapbox-gl-scope .mapboxgl-ctrl-top-right .mapboxgl-ctrl {
132
- float: right;
133
- margin: 10px 10px 0 0;
134
- }
135
-
136
- .mapbox-gl-scope .mapboxgl-ctrl-bottom-right .mapboxgl-ctrl,
137
- .mapbox-gl-scope .mapboxgl-ctrl-right .mapboxgl-ctrl {
138
- float: right;
139
- margin: 0 10px 10px 0;
140
- }
141
-
142
- .mapbox-gl-scope .mapboxgl-ctrl-bottom .mapboxgl-ctrl {
143
- float: left;
144
- margin: 10px 0;
145
- }
146
-
147
- .mapbox-gl-scope .mapboxgl-ctrl-bottom-left .mapboxgl-ctrl,
148
- .mapbox-gl-scope .mapboxgl-ctrl-left .mapboxgl-ctrl {
149
- float: left;
150
- margin: 0 0 10px 10px;
151
- }
152
-
153
- /* Control group styling */
154
- .mapbox-gl-scope .mapboxgl-ctrl-group {
155
- background: #fff;
156
- border-radius: 4px;
157
- }
158
-
159
- .mapbox-gl-scope .mapboxgl-ctrl-group:not(:empty) {
160
- box-shadow: 0 0 0 2px #0000001a;
161
- }
162
-
163
- .mapbox-gl-scope .mapboxgl-ctrl-group button {
164
- background-color: initial;
165
- border: 0;
166
- box-sizing: border-box;
167
- cursor: pointer;
168
- display: block;
169
- height: 29px;
170
- outline: none;
171
- overflow: hidden;
172
- padding: 0;
173
- width: 29px;
174
- }
175
-
176
- .mapbox-gl-scope .mapboxgl-ctrl-group button+button {
177
- border-top: 1px solid #ddd;
178
- }
179
-
180
- .mapbox-gl-scope .mapboxgl-ctrl button .mapboxgl-ctrl-icon {
181
- background-position: 50%;
182
- background-repeat: no-repeat;
183
- display: block;
184
- height: 100%;
185
- width: 100%;
186
- }
187
-
188
- .mapbox-gl-scope .mapboxgl-ctrl-attrib-button:focus,
189
- .mapbox-gl-scope .mapboxgl-ctrl-group button:focus {
190
- box-shadow: 0 0 2px 2px #0096ff;
191
- }
192
-
193
- .mapbox-gl-scope .mapboxgl-ctrl button:disabled {
194
- cursor: not-allowed;
195
- }
196
-
197
- .mapbox-gl-scope .mapboxgl-ctrl button:disabled .mapboxgl-ctrl-icon {
198
- opacity: .25;
199
- }
200
-
201
- .mapbox-gl-scope .mapboxgl-ctrl-group button:first-child {
202
- border-radius: 4px 4px 0 0;
203
- }
204
-
205
- .mapbox-gl-scope .mapboxgl-ctrl-group button:last-child {
206
- border-radius: 0 0 4px 4px;
207
- }
208
-
209
- .mapbox-gl-scope .mapboxgl-ctrl-group button:only-child {
210
- border-radius: inherit;
211
- }
212
-
213
- .mapbox-gl-scope .mapboxgl-ctrl button:not(:disabled):hover {
214
- background-color: #0000000d;
215
- }
216
-
217
- /* Marker styles */
218
- .mapbox-gl-scope .mapboxgl-marker {
219
- position: absolute;
220
- z-index: 1;
221
- }
222
-
223
- .mapbox-gl-scope .mapboxgl-marker svg {
224
- display: block;
225
- }
226
-
227
- /* Popup styles */
228
- .mapbox-gl-scope .mapboxgl-popup {
229
- position: absolute;
230
- text-align: center;
231
- margin-bottom: 20px;
232
- }
233
-
234
- .mapbox-gl-scope .mapboxgl-popup-content-wrapper {
235
- padding: 1px;
236
- text-align: left;
237
- border-radius: 12px;
238
- }
239
-
240
- .mapbox-gl-scope .mapboxgl-popup-content {
241
- margin: 13px 24px 13px 20px;
242
- line-height: 1.3;
243
- font-size: 13px;
244
- min-height: 1px;
245
- }
246
-
247
- .mapbox-gl-scope .mapboxgl-popup-content p {
248
- margin: 17px 0;
249
- }
250
-
251
- .mapbox-gl-scope .mapboxgl-popup-tip-container {
252
- width: 40px;
253
- height: 20px;
254
- position: absolute;
255
- left: 50%;
256
- margin-top: -1px;
257
- margin-left: -20px;
258
- overflow: hidden;
259
- pointer-events: none;
260
- }
261
-
262
- .mapbox-gl-scope .mapboxgl-popup-tip {
263
- width: 17px;
264
- height: 17px;
265
- padding: 1px;
266
- margin: -10px auto 0;
267
- pointer-events: auto;
268
- -webkit-transform: rotate(45deg);
269
- -moz-transform: rotate(45deg);
270
- -ms-transform: rotate(45deg);
271
- transform: rotate(45deg);
272
- }
273
-
274
- .mapbox-gl-scope .mapboxgl-popup-content-wrapper,
275
- .mapbox-gl-scope .mapboxgl-popup-tip {
276
- background: white;
277
- color: #333;
278
- box-shadow: 0 3px 14px rgba(0, 0, 0, 0.4);
279
- }
280
-
281
- .mapbox-gl-scope .mapboxgl-popup-close-button {
282
- position: absolute;
283
- top: 0;
284
- right: 0;
285
- border: none;
286
- text-align: center;
287
- width: 24px;
288
- height: 24px;
289
- font: 16px/24px Tahoma, Verdana, sans-serif;
290
- color: #757575;
291
- text-decoration: none;
292
- background: transparent;
293
- }
294
-
295
- .mapbox-gl-scope .mapboxgl-popup-close-button:hover,
296
- .mapbox-gl-scope .mapboxgl-popup-close-button:focus {
297
- color: #585858;
298
- }
299
-
300
- /* Attribution */
301
- .mapbox-gl-scope .mapboxgl-ctrl-attribution {
302
- background: #fff;
303
- background: rgba(255, 255, 255, 0.8);
304
- margin: 0;
305
- }
306
-
307
- .mapbox-gl-scope .mapboxgl-ctrl-attribution,
308
- .mapbox-gl-scope .mapboxgl-ctrl-scale-line {
309
- padding: 0 5px;
310
- color: #333;
311
- line-height: 1.4;
312
- }
313
-
314
- .mapbox-gl-scope .mapboxgl-ctrl-attribution a {
315
- text-decoration: none;
316
- }
317
-
318
- .mapbox-gl-scope .mapboxgl-ctrl-attribution a:hover,
319
- .mapbox-gl-scope .mapboxgl-ctrl-attribution a:focus {
320
- text-decoration: underline;
321
- }
322
-
323
- /* Hide attribution by default */
324
- .mapbox-gl-scope .mapboxgl-ctrl-attribution {
325
- display: none !important;
326
- }
327
-
328
- .mapbox-gl-scope .mapboxgl-ctrl-logo {
329
- display: none !important;
330
- }