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 fitText = (text, width, textFont, size) => {
146
- const sanitizedText = sanitizeValue(text);
147
- if (textFont.widthOfTextAtSize(sanitizedText, size) <= width) return sanitizedText;
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
- let fittedText = sanitizedText;
150
- while (fittedText.length && textFont.widthOfTextAtSize(`${fittedText}...`, size) > width) {
151
- fittedText = fittedText.slice(0, -1);
250
+ if (minTotal >= availableWidth) {
251
+ return minWidths.map((width) => (width * availableWidth) / minTotal);
152
252
  }
153
- return fittedText ? `${fittedText}...` : '';
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
- headersToUse.forEach((header, index) => {
171
- const x = margin + index * columnWidth;
172
- page.drawRectangle({
173
- x,
174
- y: y - 5,
175
- width: columnWidth,
176
- height: rowHeight,
177
- color: rgb(0.92, 0.94, 0.96),
178
- borderColor: rgb(0.72, 0.75, 0.78),
179
- borderWidth: 0.5,
180
- });
181
- page.drawText(fitText(header.label, columnWidth - 8, boldFont, headerFontSize), {
182
- x: x + 4,
183
- y: y + 1,
184
- size: headerFontSize,
185
- font: boldFont,
186
- color: rgb(0.1, 0.1, 0.1),
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
- y -= rowHeight;
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
- if (y < margin + rowHeight) addPage();
379
+ drawDataRow(row);
380
+ });
197
381
 
198
- headersToUse.forEach((header, index) => {
199
- const x = margin + index * columnWidth;
200
- page.drawRectangle({
201
- x,
202
- y: y - 5,
203
- width: columnWidth,
204
- height: rowHeight,
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' });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ui-soxo-bootstrap-core",
3
- "version": "2.6.40-dev.17",
3
+ "version": "2.6.40-dev.18",
4
4
  "description": "All the Core Components for you to start",
5
5
  "keywords": [
6
6
  "all in one"