ui-soxo-bootstrap-core 2.6.40-dev.17 → 2.6.40-dev.18
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.
|
@@ -122,17 +122,20 @@ export const ExportReactCSV = ({
|
|
|
122
122
|
const pdfDoc = await PDFDocument.create();
|
|
123
123
|
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
|
124
124
|
const boldFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
|
|
125
|
-
const pageSize = [842, 595];
|
|
126
|
-
const margin = 30;
|
|
127
|
-
const rowHeight = 20;
|
|
128
|
-
const titleHeight = 38;
|
|
129
|
-
const fontSize = 8;
|
|
130
|
-
const headerFontSize = 8;
|
|
131
125
|
const headersToUse = headers?.length
|
|
132
126
|
? headers
|
|
133
127
|
: Object.keys(csvData?.[0] || {}).map((key) => ({ label: key, key }));
|
|
128
|
+
const pageSize = headersToUse.length > 12 ? [1190, 842] : [842, 595];
|
|
129
|
+
const margin = 30;
|
|
130
|
+
const minRowHeight = 20;
|
|
131
|
+
const titleHeight = 38;
|
|
132
|
+
const fontSize = headersToUse.length > 12 ? 7 : 8;
|
|
133
|
+
const headerFontSize = headersToUse.length > 12 ? 7 : 8;
|
|
134
|
+
const lineHeight = fontSize + 2;
|
|
135
|
+
const headerLineHeight = headerFontSize + 2;
|
|
136
|
+
const cellPaddingX = 4;
|
|
137
|
+
const cellPaddingY = 5;
|
|
134
138
|
const availableWidth = pageSize[0] - margin * 2;
|
|
135
|
-
const columnWidth = headersToUse.length ? availableWidth / headersToUse.length : availableWidth;
|
|
136
139
|
let page;
|
|
137
140
|
let y;
|
|
138
141
|
|
|
@@ -142,15 +145,173 @@ export const ExportReactCSV = ({
|
|
|
142
145
|
return String(value);
|
|
143
146
|
};
|
|
144
147
|
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
+
const getTextWidth = (text, textFont, size) => textFont.widthOfTextAtSize(sanitizeValue(text), size);
|
|
149
|
+
|
|
150
|
+
const splitLongWord = (word, maxWidth, textFont, size) => {
|
|
151
|
+
const characters = Array.from(word);
|
|
152
|
+
const chunks = [];
|
|
153
|
+
let chunk = '';
|
|
154
|
+
|
|
155
|
+
characters.forEach((character) => {
|
|
156
|
+
const nextChunk = `${chunk}${character}`;
|
|
157
|
+
|
|
158
|
+
if (chunk && getTextWidth(nextChunk, textFont, size) > maxWidth) {
|
|
159
|
+
chunks.push(chunk);
|
|
160
|
+
chunk = character;
|
|
161
|
+
} else {
|
|
162
|
+
chunk = nextChunk;
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
if (chunk) chunks.push(chunk);
|
|
167
|
+
return chunks;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const wrapText = (value, maxWidth, textFont, size) => {
|
|
171
|
+
const text = sanitizeValue(value);
|
|
172
|
+
const usableWidth = Math.max(maxWidth, 1);
|
|
173
|
+
const paragraphs = text.split(/\r?\n/);
|
|
174
|
+
|
|
175
|
+
return paragraphs.flatMap((paragraph) => {
|
|
176
|
+
const words = paragraph.split(/\s+/).filter(Boolean);
|
|
177
|
+
const lines = [];
|
|
178
|
+
let line = '';
|
|
179
|
+
|
|
180
|
+
if (!words.length) return [''];
|
|
181
|
+
|
|
182
|
+
words.forEach((word) => {
|
|
183
|
+
if (getTextWidth(word, textFont, size) > usableWidth) {
|
|
184
|
+
if (line) {
|
|
185
|
+
lines.push(line);
|
|
186
|
+
line = '';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const chunks = splitLongWord(word, usableWidth, textFont, size);
|
|
190
|
+
chunks.forEach((chunk, chunkIndex) => {
|
|
191
|
+
if (chunkIndex === chunks.length - 1) {
|
|
192
|
+
line = chunk;
|
|
193
|
+
} else {
|
|
194
|
+
lines.push(chunk);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const nextLine = line ? `${line} ${word}` : word;
|
|
202
|
+
if (getTextWidth(nextLine, textFont, size) <= usableWidth) {
|
|
203
|
+
line = nextLine;
|
|
204
|
+
} else {
|
|
205
|
+
if (line) lines.push(line);
|
|
206
|
+
line = word;
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
if (line) lines.push(line);
|
|
211
|
+
return lines;
|
|
212
|
+
});
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const getColumnWidths = () => {
|
|
216
|
+
if (!headersToUse.length) return [availableWidth];
|
|
217
|
+
|
|
218
|
+
const sampleRows = (csvData || []).slice(0, 100);
|
|
219
|
+
const minWidths = [];
|
|
220
|
+
const preferredWidths = headersToUse.map((header, index) => {
|
|
221
|
+
const label = sanitizeValue(header.label);
|
|
222
|
+
const compactColumn = label === '#' || ['slno', 'sl no'].includes(label.toLowerCase());
|
|
223
|
+
const minWidth = compactColumn ? 28 : 36;
|
|
224
|
+
const values = [label, ...sampleRows.map((row) => sanitizeValue(row[header.key]))];
|
|
225
|
+
const longestValueWidth = values.reduce((maxWidth, value) => Math.max(maxWidth, getTextWidth(value, font, fontSize)), 0);
|
|
226
|
+
const longestWordWidth = values.reduce((maxWidth, value) => {
|
|
227
|
+
const words = value.split(/\s+/).filter(Boolean);
|
|
228
|
+
return words.reduce((wordMaxWidth, word) => Math.max(wordMaxWidth, getTextWidth(word, font, fontSize)), maxWidth);
|
|
229
|
+
}, 0);
|
|
230
|
+
const headerWidth = getTextWidth(label, boldFont, headerFontSize);
|
|
231
|
+
const preferredWidth = Math.max(
|
|
232
|
+
minWidth,
|
|
233
|
+
Math.min(longestValueWidth + cellPaddingX * 2, compactColumn ? 48 : 130),
|
|
234
|
+
Math.min(longestWordWidth + cellPaddingX * 2, compactColumn ? 48 : 95),
|
|
235
|
+
headerWidth + cellPaddingX * 2
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
minWidths[index] = minWidth;
|
|
239
|
+
return preferredWidth;
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const minTotal = minWidths.reduce((total, width) => total + width, 0);
|
|
243
|
+
const preferredTotal = preferredWidths.reduce((total, width) => total + width, 0);
|
|
244
|
+
|
|
245
|
+
if (preferredTotal <= availableWidth) {
|
|
246
|
+
const extraWidth = availableWidth - preferredTotal;
|
|
247
|
+
return preferredWidths.map((width) => width + (extraWidth * width) / preferredTotal);
|
|
248
|
+
}
|
|
148
249
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
fittedText = fittedText.slice(0, -1);
|
|
250
|
+
if (minTotal >= availableWidth) {
|
|
251
|
+
return minWidths.map((width) => (width * availableWidth) / minTotal);
|
|
152
252
|
}
|
|
153
|
-
|
|
253
|
+
|
|
254
|
+
const flexibleTotal = preferredWidths.reduce((total, width, index) => total + (width - minWidths[index]), 0);
|
|
255
|
+
const extraWidth = availableWidth - minTotal;
|
|
256
|
+
|
|
257
|
+
return preferredWidths.map((width, index) => {
|
|
258
|
+
const flexibleWidth = width - minWidths[index];
|
|
259
|
+
return minWidths[index] + (extraWidth * flexibleWidth) / flexibleTotal;
|
|
260
|
+
});
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const columnWidths = getColumnWidths();
|
|
264
|
+
|
|
265
|
+
const getMaxLineCount = (cellLines) => Math.max(1, ...cellLines.map((lines) => lines.length));
|
|
266
|
+
|
|
267
|
+
const getRowHeight = (cellLines, rowLineHeight) =>
|
|
268
|
+
Math.max(minRowHeight, getMaxLineCount(cellLines) * rowLineHeight + cellPaddingY * 2);
|
|
269
|
+
|
|
270
|
+
const drawTableRow = (cellLines, rowHeight, options) => {
|
|
271
|
+
let x = margin;
|
|
272
|
+
|
|
273
|
+
cellLines.forEach((lines, index) => {
|
|
274
|
+
const width = columnWidths[index] || availableWidth;
|
|
275
|
+
page.drawRectangle({
|
|
276
|
+
x,
|
|
277
|
+
y: y - rowHeight,
|
|
278
|
+
width,
|
|
279
|
+
height: rowHeight,
|
|
280
|
+
color: options.backgroundColor,
|
|
281
|
+
borderColor: options.borderColor,
|
|
282
|
+
borderWidth: 0.5,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
lines.forEach((line, lineIndex) => {
|
|
286
|
+
page.drawText(line, {
|
|
287
|
+
x: x + cellPaddingX,
|
|
288
|
+
y: y - cellPaddingY - options.fontSize - lineIndex * options.lineHeight,
|
|
289
|
+
size: options.fontSize,
|
|
290
|
+
font: options.font,
|
|
291
|
+
color: options.textColor,
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
x += width;
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
y -= rowHeight;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const headerCellLines = headersToUse.map((header, index) =>
|
|
302
|
+
wrapText(header.label, columnWidths[index] - cellPaddingX * 2, boldFont, headerFontSize)
|
|
303
|
+
);
|
|
304
|
+
const headerRowHeight = getRowHeight(headerCellLines, headerLineHeight);
|
|
305
|
+
|
|
306
|
+
const drawHeader = () => {
|
|
307
|
+
drawTableRow(headerCellLines, headerRowHeight, {
|
|
308
|
+
backgroundColor: rgb(0.92, 0.94, 0.96),
|
|
309
|
+
borderColor: rgb(0.72, 0.75, 0.78),
|
|
310
|
+
font: boldFont,
|
|
311
|
+
fontSize: headerFontSize,
|
|
312
|
+
lineHeight: headerLineHeight,
|
|
313
|
+
textColor: rgb(0.1, 0.1, 0.1),
|
|
314
|
+
});
|
|
154
315
|
};
|
|
155
316
|
|
|
156
317
|
const addPage = () => {
|
|
@@ -166,57 +327,67 @@ export const ExportReactCSV = ({
|
|
|
166
327
|
});
|
|
167
328
|
|
|
168
329
|
y -= titleHeight;
|
|
330
|
+
drawHeader();
|
|
331
|
+
};
|
|
169
332
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
333
|
+
const drawDataRow = (row) => {
|
|
334
|
+
const rowFont = row.isSummaryRow ? boldFont : font;
|
|
335
|
+
const cellLines = headersToUse.map((header, index) =>
|
|
336
|
+
wrapText(row[header.key], columnWidths[index] - cellPaddingX * 2, rowFont, fontSize)
|
|
337
|
+
);
|
|
338
|
+
let remainingCellLines = cellLines.map((lines) => (lines.length ? lines : ['']));
|
|
339
|
+
const fullPageDataHeight = pageSize[1] - margin * 2 - titleHeight - headerRowHeight;
|
|
340
|
+
|
|
341
|
+
while (remainingCellLines.some((lines) => lines.length)) {
|
|
342
|
+
const rowHeight = getRowHeight(remainingCellLines, lineHeight);
|
|
343
|
+
const availableHeight = y - margin;
|
|
344
|
+
|
|
345
|
+
if (rowHeight > availableHeight && rowHeight <= fullPageDataHeight) {
|
|
346
|
+
addPage();
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const maxLinesOnPage =
|
|
351
|
+
rowHeight > availableHeight
|
|
352
|
+
? Math.max(1, Math.floor((availableHeight - cellPaddingY * 2) / lineHeight))
|
|
353
|
+
: getMaxLineCount(remainingCellLines);
|
|
354
|
+
const segmentCellLines = remainingCellLines.map((lines) => lines.slice(0, maxLinesOnPage));
|
|
355
|
+
const segmentHeight = getRowHeight(segmentCellLines, lineHeight);
|
|
356
|
+
|
|
357
|
+
if (segmentHeight > availableHeight && availableHeight < fullPageDataHeight) {
|
|
358
|
+
addPage();
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
drawTableRow(segmentCellLines, segmentHeight, {
|
|
363
|
+
backgroundColor: row.isSummaryRow ? rgb(0.97, 0.97, 0.97) : rgb(1, 1, 1),
|
|
364
|
+
borderColor: rgb(0.82, 0.84, 0.86),
|
|
365
|
+
font: rowFont,
|
|
366
|
+
fontSize,
|
|
367
|
+
lineHeight,
|
|
368
|
+
textColor: rgb(0.12, 0.12, 0.12),
|
|
187
369
|
});
|
|
188
|
-
});
|
|
189
370
|
|
|
190
|
-
|
|
371
|
+
remainingCellLines = remainingCellLines.map((lines) => lines.slice(maxLinesOnPage));
|
|
372
|
+
if (remainingCellLines.some((lines) => lines.length)) addPage();
|
|
373
|
+
}
|
|
191
374
|
};
|
|
192
375
|
|
|
193
376
|
addPage();
|
|
194
377
|
|
|
195
378
|
(csvData || []).forEach((row) => {
|
|
196
|
-
|
|
379
|
+
drawDataRow(row);
|
|
380
|
+
});
|
|
197
381
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
color: row.isSummaryRow ? rgb(0.97, 0.97, 0.97) : rgb(1, 1, 1),
|
|
206
|
-
borderColor: rgb(0.82, 0.84, 0.86),
|
|
207
|
-
borderWidth: 0.5,
|
|
208
|
-
});
|
|
209
|
-
page.drawText(fitText(row[header.key], columnWidth - 8, row.isSummaryRow ? boldFont : font, fontSize), {
|
|
210
|
-
x: x + 4,
|
|
211
|
-
y: y + 1,
|
|
212
|
-
size: fontSize,
|
|
213
|
-
font: row.isSummaryRow ? boldFont : font,
|
|
214
|
-
color: rgb(0.12, 0.12, 0.12),
|
|
215
|
-
});
|
|
382
|
+
if (!csvData?.length) {
|
|
383
|
+
page.drawText('No records found', {
|
|
384
|
+
x: margin,
|
|
385
|
+
y: y + 1,
|
|
386
|
+
size: fontSize,
|
|
387
|
+
font,
|
|
388
|
+
color: rgb(0.12, 0.12, 0.12),
|
|
216
389
|
});
|
|
217
|
-
|
|
218
|
-
y -= rowHeight;
|
|
219
|
-
});
|
|
390
|
+
}
|
|
220
391
|
|
|
221
392
|
const pdfBytes = await pdfDoc.save();
|
|
222
393
|
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
|