@tekkare/romulus 0.1.166 → 0.1.167

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/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.16.0"
6
6
  },
7
- "version": "0.1.165",
7
+ "version": "0.1.166",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -55,6 +55,16 @@ function xlsxColumnsOf(sheet, keep) {
55
55
  }
56
56
  }));
57
57
  }
58
+ function readableText(value, lang) {
59
+ if (typeof value === "number" && Number.isFinite(value)) {
60
+ return value.toLocaleString(lang === "en" ? "en-US" : "fr-FR", { maximumFractionDigits: 2 });
61
+ }
62
+ return cellText(value, lang);
63
+ }
64
+ function isYearColumn(values) {
65
+ const seen = values.filter((value) => value !== null && value !== void 0 && value !== "");
66
+ return seen.length > 0 && seen.every((value) => typeof value === "number" && Number.isInteger(value) && value >= 1900 && value <= 2100);
67
+ }
58
68
  function cellText(value, lang) {
59
69
  if (value === null || value === void 0) return "";
60
70
  if (value instanceof Date) {
@@ -120,7 +130,10 @@ const PRINTABLE_SUBSTITUTES = [
120
130
  [/…/g, "..."],
121
131
  [/≥/g, ">="],
122
132
  [/≤/g, "<="],
123
- [/[ ]/g, " "]
133
+ // Espaces fines et insecables, dont celle que `toLocaleString('fr-FR')`
134
+ // pose entre les milliers (U+202F) : hors table WinAnsi, elle tombait et
135
+ // « 1 203 981 » sortait « 1203981 ».
136
+ [/[\u00a0\u2007\u2009\u202f]/g, " "]
124
137
  ];
125
138
  function printable(text) {
126
139
  const substituted = PRINTABLE_SUBSTITUTES.reduce(
@@ -140,10 +153,17 @@ function cardsOf(payload, config, lang) {
140
153
  const head = title ?? columns[0];
141
154
  if (!head) return [];
142
155
  const fields = columns.filter((column) => column !== head && column !== subtitle);
143
- return rowsOf(sheet, payload.cardLimit ?? CARD_COUNT_MAX).map((row) => ({
144
- title: cellText(head.value(row), lang) || "\u2014",
145
- subtitle: subtitle ? cellText(subtitle.value(row), lang) : void 0,
146
- fields: fields.map((column) => ({ label: column.column, value: cellText(column.value(row), lang) })).filter((field) => field.value !== "")
156
+ const rows = rowsOf(sheet, payload.cardLimit ?? CARD_COUNT_MAX);
157
+ const plain = new Set(
158
+ fields.filter((column) => isYearColumn(rows.map((row) => column.value(row)))).map((column) => column.column)
159
+ );
160
+ return rows.map((row) => ({
161
+ title: readableText(head.value(row), lang) || "\u2014",
162
+ subtitle: subtitle ? readableText(subtitle.value(row), lang) : void 0,
163
+ fields: fields.map((column) => ({
164
+ label: column.column,
165
+ value: plain.has(column.column) ? cellText(column.value(row), lang) : readableText(column.value(row), lang)
166
+ })).filter((field) => field.value !== "")
147
167
  }));
148
168
  }
149
169
  export function useRmExport() {
@@ -345,107 +365,197 @@ export function useRmExport() {
345
365
  const pageWidth = doc.internal.pageSize.getWidth();
346
366
  const margin = 14;
347
367
  const inner = pageWidth - margin * 2;
368
+ const FOOTER = 12;
369
+ const bottom = doc.internal.pageSize.getHeight() - margin - FOOTER;
370
+ const CHART_MAX_HEIGHT = landscape ? 88 : 104;
348
371
  let y = margin;
349
372
  const violet = [107, 63, 255];
350
373
  const ink = [26, 26, 46];
351
374
  const grey = [108, 108, 138];
375
+ const line = [228, 228, 240];
376
+ const tint = [245, 243, 255];
377
+ const surface = [250, 250, 253];
378
+ function sectionTitle(text) {
379
+ doc.setFillColor(...violet);
380
+ doc.rect(margin, y - 2.6, 2, 2.6, "F");
381
+ doc.setTextColor(...ink);
382
+ doc.setFont("helvetica", "bold");
383
+ doc.setFontSize(8);
384
+ doc.setCharSpace(0.5);
385
+ doc.text(printable(text).toUpperCase(), margin + 4, y);
386
+ doc.setCharSpace(0);
387
+ y += 5;
388
+ }
352
389
  if (config.options.includes("logo")) {
390
+ const bandHeight = 30;
353
391
  doc.setFillColor(...violet);
354
- doc.rect(0, 0, pageWidth, 42, "F");
392
+ doc.rect(0, 0, pageWidth, bandHeight, "F");
355
393
  doc.setTextColor(255, 255, 255);
356
394
  doc.setFont("helvetica", "bold");
357
- doc.setFontSize(22);
358
- doc.text(printable(BRAND_TITLE), margin, 20);
395
+ doc.setFontSize(17);
396
+ doc.text(printable(BRAND_TITLE), margin, 15);
359
397
  doc.setFont("helvetica", "normal");
360
- doc.setFontSize(11);
361
- doc.text(printable(BRAND_LINE), margin, 28);
362
398
  doc.setFontSize(9);
363
- doc.text(longDate(lang), pageWidth - margin, 28, { align: "right" });
399
+ doc.text(printable(BRAND_LINE), margin, 21.5);
400
+ doc.text(longDate(lang), pageWidth - margin, 21.5, { align: "right" });
364
401
  if (payload.logoDataUrl) {
365
- const logoHeight = 8;
366
- doc.addImage(payload.logoDataUrl, "PNG", pageWidth - margin - logoHeight * 7.6, 10, logoHeight * 7.6, logoHeight);
402
+ const logoHeight = 7;
403
+ let logoWidth = logoHeight * 3;
404
+ try {
405
+ const meta = doc.getImageProperties?.(payload.logoDataUrl);
406
+ if (meta?.width && meta?.height) logoWidth = meta.width / meta.height * logoHeight;
407
+ } catch {
408
+ }
409
+ doc.addImage(payload.logoDataUrl, "PNG", pageWidth - margin - logoWidth, 8, logoWidth, logoHeight);
367
410
  }
368
- y = 54;
411
+ y = bandHeight + 12;
369
412
  }
370
413
  doc.setTextColor(...ink);
371
414
  doc.setFont("helvetica", "bold");
372
415
  doc.setFontSize(16);
373
- doc.text(doc.splitTextToSize(printable(payload.title), inner), margin, y);
374
- y += 8;
416
+ const titleLines = doc.splitTextToSize(printable(payload.title), inner);
417
+ doc.text(titleLines, margin, y);
418
+ y += titleLines.length * 7 + 1;
375
419
  if (payload.subtitle) {
376
420
  doc.setFont("helvetica", "normal");
377
421
  doc.setFontSize(10);
378
422
  doc.setTextColor(...grey);
379
423
  const lines = doc.splitTextToSize(printable(payload.subtitle), inner);
380
424
  doc.text(lines, margin, y);
381
- y += lines.length * 5 + 2;
425
+ y += lines.length * 5;
382
426
  }
427
+ doc.setDrawColor(...violet);
428
+ doc.setLineWidth(0.6);
429
+ doc.line(margin, y + 1, margin + 24, y + 1);
430
+ doc.setLineWidth(0.2);
431
+ y += 8;
383
432
  if (payload.kpi?.length && config.sections.includes("kpi")) {
384
- const perRow = Math.min(payload.kpi.length, 4);
385
- const boxWidth = inner / perRow;
386
- payload.kpi.slice(0, 8).forEach((kpi, index) => {
433
+ const shown = payload.kpi.slice(0, 8);
434
+ const perRow = Math.min(shown.length, 4);
435
+ const GAP = 3;
436
+ const boxWidth = (inner - GAP * (perRow - 1)) / perRow;
437
+ const boxHeight = 18;
438
+ const ACCENT = 1.4;
439
+ shown.forEach((kpi, index) => {
387
440
  const column = index % perRow;
388
441
  const row = Math.floor(index / perRow);
389
- const x = margin + column * boxWidth;
390
- const top = y + row * 20;
391
- doc.setFillColor(246, 246, 250);
392
- doc.roundedRect(x + 1, top, boxWidth - 2, 17, 2, 2, "F");
393
- doc.setTextColor(...violet);
442
+ const x = margin + column * (boxWidth + GAP);
443
+ const top = y + row * (boxHeight + GAP);
444
+ doc.setFillColor(255, 255, 255);
445
+ doc.setDrawColor(226, 226, 238);
446
+ doc.setLineWidth(0.2);
447
+ doc.roundedRect(x, top, boxWidth, boxHeight, 1.5, 1.5, "FD");
448
+ doc.setFillColor(...violet);
449
+ doc.rect(x, top + 1.5, ACCENT, boxHeight - 3, "F");
450
+ const textX = x + ACCENT + 4;
451
+ const textWidth = boxWidth - ACCENT - 7;
452
+ doc.setTextColor(...grey);
453
+ doc.setFont("helvetica", "bold");
454
+ doc.setFontSize(6.5);
455
+ doc.text(
456
+ doc.splitTextToSize(printable(kpi.label).toUpperCase(), textWidth)[0] ?? "",
457
+ textX,
458
+ top + 6.5
459
+ );
460
+ doc.setTextColor(...ink);
394
461
  doc.setFont("helvetica", "bold");
395
462
  doc.setFontSize(14);
396
- doc.text(printable(String(kpi.value)), x + boxWidth / 2, top + 8, { align: "center" });
397
- doc.setTextColor(...grey);
398
- doc.setFont("helvetica", "normal");
399
- doc.setFontSize(7);
400
- doc.text(doc.splitTextToSize(printable(kpi.label), boxWidth - 6)[0] ?? "", x + boxWidth / 2, top + 13, { align: "center" });
463
+ const value = printable(String(kpi.value));
464
+ if (doc.getTextWidth(value) > textWidth) doc.setFontSize(11);
465
+ if (doc.getTextWidth(value) > textWidth) doc.setFontSize(9);
466
+ doc.text(value, textX, top + 13.5);
401
467
  });
402
- y += Math.ceil(Math.min(payload.kpi.length, 8) / perRow) * 20 + 4;
468
+ y += Math.ceil(shown.length / perRow) * (boxHeight + GAP) + 5;
403
469
  }
404
470
  if (payload.filters?.length && config.sections.includes("filters")) {
405
- doc.setTextColor(...ink);
406
- doc.setFont("helvetica", "bold");
407
- doc.setFontSize(10);
408
- doc.text(lang === "en" ? "Reading this view" : "Lecture de la vue", margin, y);
409
- y += 5;
410
- doc.setFont("helvetica", "normal");
411
- doc.setFontSize(9);
412
- doc.setTextColor(...grey);
471
+ sectionTitle(lang === "en" ? "Reading this view" : "Lecture de la vue");
413
472
  const applied = payload.filters.map((filter) => `${filter.label} = ${filter.value}`).join(" ; ");
414
473
  const legend = lang === "en" ? `This report reproduces the page as it was displayed, with the following filters: ${applied}.` : `Ce rapport reprend la page telle qu'elle \xE9tait affich\xE9e, avec les filtres suivants : ${applied}.`;
415
- const lines = doc.splitTextToSize(printable(legend), inner);
416
- doc.text(lines, margin, y);
417
- y += lines.length * 4.5 + 4;
474
+ doc.setFont("helvetica", "normal");
475
+ doc.setFontSize(8.5);
476
+ const lines = doc.splitTextToSize(printable(legend), inner - 12);
477
+ const boxHeight = lines.length * 4.4 + 7;
478
+ doc.setFillColor(...tint);
479
+ doc.roundedRect(margin, y - 1, inner, boxHeight, 1.5, 1.5, "F");
480
+ doc.setFillColor(...violet);
481
+ doc.rect(margin, y - 1, 1.2, boxHeight, "F");
482
+ doc.setTextColor(...grey);
483
+ doc.text(lines, margin + 6, y + 4);
484
+ y += boxHeight + 5;
485
+ }
486
+ const sources = sourcesOf(config, payload);
487
+ if (sources.length) {
488
+ sectionTitle("Sources");
489
+ doc.setFont("helvetica", "normal");
490
+ doc.setFontSize(8.5);
491
+ sources.forEach((source) => {
492
+ doc.setFillColor(...violet);
493
+ doc.circle(margin + 1, y - 1, 0.7, "F");
494
+ doc.setTextColor(...grey);
495
+ const lines = doc.splitTextToSize(printable(sourceLabel(source, lang)), inner - 5);
496
+ doc.text(lines, margin + 4, y);
497
+ y += lines.length * 4.4;
498
+ });
499
+ y += 5;
418
500
  }
419
501
  const charts = config.sections.includes("charts") ? payload.charts ?? [] : [];
502
+ if (charts.length) sectionTitle(lang === "en" ? "Charts" : "Graphiques");
420
503
  for (const chart of charts) {
421
504
  const svg = svgOf(chart.el);
422
505
  if (!svg) continue;
423
506
  const part = serialize(svg);
424
507
  const canvas = await raster(part.markup, part.width, part.height, Math.min(2, 1400 / part.width));
425
- const drawWidth = inner;
426
- const drawHeight = part.height / part.width * drawWidth;
427
- if (y + drawHeight > doc.internal.pageSize.getHeight() - margin) {
508
+ let drawWidth = inner;
509
+ let drawHeight = part.height / part.width * drawWidth;
510
+ if (drawHeight > CHART_MAX_HEIGHT) {
511
+ drawHeight = CHART_MAX_HEIGHT;
512
+ drawWidth = part.width / part.height * drawHeight;
513
+ }
514
+ const titleHeight = chart.title ? 7 : 0;
515
+ const cardHeight = titleHeight + drawHeight + 8;
516
+ if (y + cardHeight > bottom) {
428
517
  doc.addPage();
429
518
  y = margin;
430
519
  }
520
+ doc.setFillColor(255, 255, 255);
521
+ doc.setDrawColor(...line);
522
+ doc.setLineWidth(0.2);
523
+ doc.roundedRect(margin, y, inner, cardHeight, 2, 2, "FD");
524
+ let chartY = y + 4;
431
525
  if (chart.title) {
432
526
  doc.setTextColor(...ink);
433
527
  doc.setFont("helvetica", "bold");
434
528
  doc.setFontSize(10);
435
- doc.text(printable(chart.title), margin, y);
436
- y += 5;
529
+ doc.text(printable(chart.title), margin + 5, chartY + 2);
530
+ chartY += titleHeight;
437
531
  }
438
- doc.addImage(canvas.toDataURL("image/png"), "PNG", margin, y, drawWidth, drawHeight, void 0, "FAST");
439
- y += drawHeight + 6;
532
+ doc.addImage(
533
+ canvas.toDataURL("image/png"),
534
+ "PNG",
535
+ margin + (inner - drawWidth) / 2,
536
+ chartY,
537
+ drawWidth,
538
+ drawHeight,
539
+ void 0,
540
+ "FAST"
541
+ );
542
+ y += cardHeight + 6;
440
543
  }
441
544
  let rowCount = 0;
442
545
  if (config.sections.includes("cards")) {
443
546
  const cards = cardsOf(payload, config, lang);
547
+ if (cards.length) {
548
+ if (y + 12 > bottom) {
549
+ doc.addPage();
550
+ y = margin;
551
+ }
552
+ sectionTitle(lang === "en" ? "Records" : "Fiches");
553
+ }
444
554
  const pageHeight = doc.internal.pageSize.getHeight();
445
555
  const PAD = 6;
446
556
  const COUNT_WIDTH = 32;
447
557
  const COUNT_GAP = 6;
448
- const LABEL_WIDTH = 26;
558
+ const LABEL_WIDTH = 34;
449
559
  const firstCardPage = doc.getNumberOfPages();
450
560
  let drawn = 0;
451
561
  for (const card of cards) {
@@ -454,7 +564,7 @@ export function useRmExport() {
454
564
  const bodyWidth = inner - PAD * 2 - (hasCount ? COUNT_WIDTH + COUNT_GAP : 0);
455
565
  doc.setFont("helvetica", "bold");
456
566
  doc.setFontSize(12);
457
- const titleLines = doc.splitTextToSize(printable(card.title), inner - PAD * 2);
567
+ const titleLines2 = doc.splitTextToSize(printable(card.title), inner - PAD * 2);
458
568
  doc.setFont("helvetica", "normal");
459
569
  doc.setFontSize(9);
460
570
  const subtitleLines = card.subtitle ? doc.splitTextToSize(printable(card.subtitle), inner - PAD * 2) : [];
@@ -479,9 +589,9 @@ export function useRmExport() {
479
589
  doc.setFont(typo.mono ? "courier" : "helvetica", typo.bold ? "bold" : "normal");
480
590
  doc.setFontSize(typo.size);
481
591
  const width = doc.getTextWidth(text) + (typo.pill ? 3 : 0);
482
- const line = inline[inline.length - 1];
483
- const used = line ? line.reduce((total2, item) => total2 + item.width + 3, 0) : 0;
484
- if (line && used + width <= bodyWidth) line.push({ text, pill: !!typo.pill, width });
592
+ const line2 = inline[inline.length - 1];
593
+ const used = line2 ? line2.reduce((total2, item) => total2 + item.width + 3, 0) : 0;
594
+ if (line2 && used + width <= bodyWidth) line2.push({ text, pill: !!typo.pill, width });
485
595
  else inline.push([{ text, pill: !!typo.pill, width }]);
486
596
  taken += 1;
487
597
  }
@@ -503,33 +613,32 @@ export function useRmExport() {
503
613
  const label = doc.splitTextToSize(printable(field.label).toUpperCase(), LABEL_WIDTH);
504
614
  doc.setFont("helvetica", "normal");
505
615
  doc.setFontSize(9);
506
- return {
507
- label,
508
- value: doc.splitTextToSize(printable(field.value), bodyWidth - LABEL_WIDTH - 4)
509
- };
616
+ const value = doc.splitTextToSize(printable(field.value), bodyWidth - LABEL_WIDTH - 6);
617
+ return { label, value, right: value.length === 1 };
510
618
  });
511
- const headHeight = titleLines.length * 5.2 + (subtitleLines.length ? subtitleLines.length * 4.2 + 1 : 0) + (card.badges?.length ? 6 : 0);
512
- const bodyHeight = inline.length * 4.8 + stacked.reduce((total2, block) => total2 + block.lines.length * block.typo.lh + 1, 0) + fields.reduce((total2, field) => total2 + Math.max(field.label.length, field.value.length) * 4.4, 0);
619
+ const headHeight = titleLines2.length * 5.2 + (subtitleLines.length ? subtitleLines.length * 4.2 + 1 : 0) + (card.badges?.length ? 6 : 0);
620
+ const bodyHeight = inline.length * 4.8 + stacked.reduce((total2, block) => total2 + block.lines.length * block.typo.lh + 1, 0) + fields.reduce((total2, field) => total2 + Math.max(field.label.length, field.value.length) * 4.4 + 1.4, 0);
513
621
  const countHeight = hasCount ? 9 + (card.count?.label ? 4 : 0) + (card.count?.extra ? 4.5 : 0) : 0;
514
622
  const height = PAD + headHeight + 3 + Math.max(bodyHeight, countHeight) + PAD;
515
- if (y + height > pageHeight - margin) {
623
+ if (y + height > bottom) {
516
624
  if (doc.getNumberOfPages() - firstCardPage + 1 >= CARD_PAGES_MAX) break;
517
625
  doc.addPage();
518
626
  y = margin;
519
627
  }
520
- doc.setDrawColor(232, 232, 240);
628
+ doc.setDrawColor(...line);
521
629
  doc.setFillColor(255, 255, 255);
522
- doc.roundedRect(margin, y, inner, height, 3, 3, "FD");
630
+ doc.setLineWidth(0.2);
631
+ doc.roundedRect(margin, y, inner, height, 2.5, 2.5, "FD");
523
632
  let cursor = y + PAD + 3;
524
633
  doc.setTextColor(...ink);
525
634
  doc.setFont("helvetica", "bold");
526
635
  doc.setFontSize(12);
527
- doc.text(titleLines, margin + PAD, cursor);
528
- cursor += titleLines.length * 5.2;
636
+ doc.text(titleLines2, margin + PAD, cursor);
637
+ cursor += titleLines2.length * 5.2;
529
638
  if (subtitleLines.length) {
530
- doc.setTextColor(...grey);
531
- doc.setFont("helvetica", "normal");
532
- doc.setFontSize(9);
639
+ doc.setTextColor(...violet);
640
+ doc.setFont("helvetica", "bold");
641
+ doc.setFontSize(8.5);
533
642
  doc.text(subtitleLines, margin + PAD, cursor + 1);
534
643
  cursor += subtitleLines.length * 4.2 + 1;
535
644
  }
@@ -582,9 +691,9 @@ export function useRmExport() {
582
691
  );
583
692
  }
584
693
  cursor = bodyTop;
585
- inline.forEach((line) => {
694
+ inline.forEach((line2) => {
586
695
  let itemX = bodyX;
587
- line.forEach((item) => {
696
+ line2.forEach((item) => {
588
697
  if (item.pill) {
589
698
  doc.setFillColor(244, 244, 248);
590
699
  doc.roundedRect(itemX, cursor - 2.6, item.width, 4.6, 1, 1, "F");
@@ -609,7 +718,12 @@ export function useRmExport() {
609
718
  doc.text(block.lines, bodyX, cursor + 1);
610
719
  cursor += block.lines.length * block.typo.lh + 1;
611
720
  });
612
- fields.forEach((field) => {
721
+ const rowRight = margin + inner - PAD;
722
+ fields.forEach((field, index) => {
723
+ if (index) {
724
+ doc.setDrawColor(240, 240, 246);
725
+ doc.line(bodyX, cursor - 2.6, rowRight, cursor - 2.6);
726
+ }
613
727
  doc.setTextColor(...grey);
614
728
  doc.setFont("helvetica", "bold");
615
729
  doc.setFontSize(6.5);
@@ -617,8 +731,9 @@ export function useRmExport() {
617
731
  doc.setTextColor(...ink);
618
732
  doc.setFont("helvetica", "normal");
619
733
  doc.setFontSize(9);
620
- doc.text(field.value, bodyX + LABEL_WIDTH + 4, cursor + 1);
621
- cursor += Math.max(field.label.length, field.value.length) * 4.4;
734
+ if (field.right) doc.text(field.value[0], rowRight, cursor + 1, { align: "right" });
735
+ else doc.text(field.value, bodyX + LABEL_WIDTH + 6, cursor + 1);
736
+ cursor += Math.max(field.label.length, field.value.length) * 4.4 + 1.4;
622
737
  });
623
738
  y += height + 4;
624
739
  drawn += 1;
@@ -648,10 +763,8 @@ export function useRmExport() {
648
763
  if (!config.sections.includes("cards")) rowCount += rows.length;
649
764
  doc.addPage();
650
765
  const wide = doc.internal.pageSize.getWidth() - margin * 2;
651
- doc.setTextColor(...ink);
652
- doc.setFont("helvetica", "bold");
653
- doc.setFontSize(11);
654
- doc.text(printable(sheet.name), margin, margin + 2);
766
+ y = margin + 3;
767
+ sectionTitle(sheet.name);
655
768
  if (all.length > columns.length) {
656
769
  doc.setFont("helvetica", "italic");
657
770
  doc.setFontSize(8);
@@ -661,38 +774,86 @@ export function useRmExport() {
661
774
  lang === "en" ? `${columns.length} of ${all.length} columns. The Excel export carries them all.` : `${columns.length} colonnes sur ${all.length}. L'export Excel les porte toutes.`
662
775
  ),
663
776
  margin,
664
- margin + 7
777
+ y + 1
665
778
  );
779
+ y += 5;
666
780
  }
781
+ const numeric = columns.map((column) => {
782
+ const seen = rows.map((row) => column.value(row)).filter((value) => value !== null && value !== void 0 && value !== "");
783
+ return seen.length > 0 && seen.every((value) => typeof value === "number" && Number.isFinite(value));
784
+ });
785
+ const years = columns.map((column) => isYearColumn(rows.map((row) => column.value(row))));
667
786
  autoTable(doc, {
668
- startY: margin + 10,
787
+ startY: y + 2,
669
788
  head: [columns.map((column) => printable(column.column))],
670
- body: rows.map((row) => columns.map((column) => printable(cellText(column.value(row), lang)))),
789
+ body: rows.map(
790
+ (row) => columns.map(
791
+ (column, index) => printable(
792
+ years[index] ? cellText(column.value(row), lang) : readableText(column.value(row), lang)
793
+ )
794
+ )
795
+ ),
671
796
  // Passe huit colonnes, la police et les marges se resserrent : c'est
672
797
  // ce qui fait tenir un suivi mensuel complet sans casser les mots.
673
798
  styles: {
674
- fontSize: columns.length > 8 ? 5.5 : 6.5,
675
- cellPadding: columns.length > 8 ? 1 : 1.4,
799
+ fontSize: columns.length > 8 ? 6 : 7.5,
800
+ cellPadding: columns.length > 8 ? 1.2 : 1.8,
676
801
  overflow: "linebreak",
677
802
  textColor: ink,
803
+ lineColor: [235, 235, 243],
804
+ lineWidth: 0.1,
678
805
  minCellWidth: wide / columns.length / 2
679
806
  },
680
- headStyles: { fillColor: violet, textColor: [255, 255, 255], fontStyle: "bold", valign: "middle" },
681
- alternateRowStyles: { fillColor: [250, 250, 253] },
682
- margin: { left: margin, right: margin, top: margin, bottom: margin },
807
+ headStyles: {
808
+ fillColor: violet,
809
+ textColor: [255, 255, 255],
810
+ fontStyle: "bold",
811
+ valign: "middle",
812
+ fontSize: columns.length > 8 ? 6 : 7
813
+ },
814
+ // Une colonne de nombres se taille sur son contenu : rognee par une
815
+ // colonne de libelles, « 1 203 981 » se coupait en deux lignes, le
816
+ // separateur de milliers servant de point de rupture.
817
+ columnStyles: Object.fromEntries(
818
+ numeric.map((isNumber, index) => [
819
+ index,
820
+ isNumber ? { halign: "right", cellWidth: "wrap" } : { halign: "left" }
821
+ ])
822
+ ),
823
+ alternateRowStyles: { fillColor: surface },
824
+ // L'entete suit l'alignement de sa colonne : « # Patients » ferre a
825
+ // gauche au-dessus d'une colonne de chiffres ferres a droite, les
826
+ // deux ne se lisaient pas comme la meme colonne.
827
+ didParseCell: (data) => {
828
+ if (data.section === "head" && numeric[data.column.index]) {
829
+ data.cell.styles.halign = "right";
830
+ }
831
+ },
832
+ // Le pied de page est reserve : sans cette marge, la derniere ligne
833
+ // d'un tableau long passait dessous.
834
+ margin: { left: margin, right: margin, top: margin, bottom: margin + FOOTER },
683
835
  tableWidth: wide
684
836
  });
685
837
  y = doc.lastAutoTable?.finalY ?? margin;
686
838
  });
687
839
  }
688
- const sources = sourcesOf(config, payload);
689
- if (sources.length) {
690
- doc.setTextColor(...grey);
840
+ const pages = doc.getNumberOfPages();
841
+ for (let page = 1; page <= pages; page += 1) {
842
+ doc.setPage(page);
843
+ const width = doc.internal.pageSize.getWidth();
844
+ const foot = doc.internal.pageSize.getHeight() - margin + 2;
845
+ doc.setDrawColor(232, 232, 240);
846
+ doc.setLineWidth(0.2);
847
+ doc.line(margin, foot - 4, width - margin, foot - 4);
691
848
  doc.setFont("helvetica", "normal");
692
- doc.setFontSize(8);
693
- const names = sources.map((source) => sourceLabel(source, lang)).join(" \xB7 ");
694
- const lines = doc.splitTextToSize(printable(`Sources : ${names}`), inner);
695
- doc.text(lines, margin, Math.min(y + 8, doc.internal.pageSize.getHeight() - margin));
849
+ doc.setFontSize(7.5);
850
+ doc.setTextColor(...grey);
851
+ const trail = doc.splitTextToSize(
852
+ printable(`${BRAND_TITLE} - ${payload.title}`),
853
+ width - margin * 2 - 20
854
+ )[0] ?? "";
855
+ doc.text(trail, margin, foot);
856
+ doc.text(`${page} / ${pages}`, width - margin, foot, { align: "right" });
696
857
  }
697
858
  const fileName = `${config.fileName}.pdf`;
698
859
  const blob = doc.output("blob");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekkare/romulus",
3
- "version": "0.1.166",
3
+ "version": "0.1.167",
4
4
  "description": "Romulus Design System - Nuxt module by Tekkare",
5
5
  "license": "MIT",
6
6
  "type": "module",