@tekkare/romulus 0.1.133 → 0.1.135
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 +1 -1
- package/dist/runtime/components/AlertForm.vue +2 -2
- package/dist/runtime/components/CardActions.vue +9 -1
- package/dist/runtime/components/ChartCard.vue +10 -0
- package/dist/runtime/components/ExportButton.vue +5 -1
- package/dist/runtime/composables/useRmCardActions.d.ts +8 -1
- package/dist/runtime/composables/useRmCardActions.js +6 -1
- package/dist/runtime/composables/useRmExport.js +60 -22
- package/dist/runtime/composables/useRmExportDefaults.d.ts +9 -0
- package/dist/runtime/composables/useRmExportDefaults.js +13 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -110,7 +110,7 @@ const FR = {
|
|
|
110
110
|
kindView: 'Une vue',
|
|
111
111
|
kindViewHint: 'Un écran filtré, aussi fin que vous voulez, mais limité à cette page.',
|
|
112
112
|
kindProduct: 'Une marque',
|
|
113
|
-
kindProductHint: 'Une à {max} marques, suivies partout : prix, avis HAS, indications, ANSM, PUT, MITM, Journal officiel.',
|
|
113
|
+
kindProductHint: 'Une à {max} marques, suivies partout : prix, avis HAS, indications, ANSM, PUT, MITM, Journal officiel, ventes ville et hospitalières.',
|
|
114
114
|
brands: 'Marques surveillées',
|
|
115
115
|
brandsSearch: 'Rechercher',
|
|
116
116
|
brandsHint: 'Cherchez par marque, molécule, classe ATC ou laboratoire. L’alerte couvre tout le hub pour chaque marque retenue.',
|
|
@@ -163,7 +163,7 @@ const EN = {
|
|
|
163
163
|
kindView: 'A view',
|
|
164
164
|
kindViewHint: 'A filtered screen, as precise as you like, but tied to that page.',
|
|
165
165
|
kindProduct: 'A brand',
|
|
166
|
-
kindProductHint: 'One to {max} brands, watched everywhere: prices, HAS opinions, indications, ANSM, PUT, MITM, official journal.',
|
|
166
|
+
kindProductHint: 'One to {max} brands, watched everywhere: prices, HAS opinions, indications, ANSM, PUT, MITM, official journal, retail and hospital sales.',
|
|
167
167
|
brands: 'Watched brands',
|
|
168
168
|
brandsSearch: 'Search',
|
|
169
169
|
brandsHint: 'Search by brand, molecule, ATC class or company. The alert covers the whole hub for each brand kept.',
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
-->
|
|
24
24
|
<script setup lang="ts">
|
|
25
25
|
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
|
26
|
-
import type { RmExportPayload } from '../utils/export'
|
|
26
|
+
import type { RmExportPayload, RmExportSource } from '../utils/export'
|
|
27
27
|
import { rmCardCharts, rmCardOf } from '../utils/cardData'
|
|
28
28
|
import { useRmCardActions } from '../composables/useRmCardActions'
|
|
29
29
|
|
|
@@ -34,6 +34,12 @@ const props = withDefaults(defineProps<{
|
|
|
34
34
|
link?: string
|
|
35
35
|
/** Nom du fichier et de l'onglet. A defaut, le titre lu dans la carte. */
|
|
36
36
|
name?: string
|
|
37
|
+
/**
|
|
38
|
+
* Source citee par le pied de la carte. Dernier recours : la page
|
|
39
|
+
* (`payload.sources`) puis le cadre (`provideRmExportDefaults`) passent
|
|
40
|
+
* devant, parce qu'eux lisent le referentiel et sa date de mise a jour.
|
|
41
|
+
*/
|
|
42
|
+
sources?: RmExportSource[]
|
|
37
43
|
/**
|
|
38
44
|
* Ce que la page a de mieux que le rendu de la carte : ses onglets complets
|
|
39
45
|
* (`sheets`), mais aussi ses chiffres cles, ses sources, son sous-titre. Ce
|
|
@@ -54,6 +60,7 @@ const props = withDefaults(defineProps<{
|
|
|
54
60
|
lang: 'fr',
|
|
55
61
|
link: undefined,
|
|
56
62
|
name: undefined,
|
|
63
|
+
sources: undefined,
|
|
57
64
|
payload: undefined,
|
|
58
65
|
visual: undefined,
|
|
59
66
|
})
|
|
@@ -168,6 +175,7 @@ async function act(action: 'png' | 'svg' | 'csv' | 'xlsx' | 'link') {
|
|
|
168
175
|
lang: props.lang,
|
|
169
176
|
name: props.name,
|
|
170
177
|
link: props.link,
|
|
178
|
+
sources: props.sources,
|
|
171
179
|
payload: props.payload,
|
|
172
180
|
})
|
|
173
181
|
if (action === 'link') {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
-->
|
|
12
12
|
<script setup lang="ts">
|
|
13
13
|
import { computed } from 'vue'
|
|
14
|
+
import type { RmExportPayload } from '../utils/export'
|
|
14
15
|
import { rmDate } from '../utils/format'
|
|
15
16
|
|
|
16
17
|
const props = withDefaults(defineProps<{
|
|
@@ -37,6 +38,12 @@ const props = withDefaults(defineProps<{
|
|
|
37
38
|
* qui est le bon lien pour une carte d'analyse : elle vit dans sa page.
|
|
38
39
|
*/
|
|
39
40
|
link?: string
|
|
41
|
+
/**
|
|
42
|
+
* Ce que la page a de mieux que le rendu de la carte pour son export :
|
|
43
|
+
* onglets complets, sources, sous-titre (cf. RmCardActions). En fonction
|
|
44
|
+
* des qu'une liste monte la carte plusieurs fois.
|
|
45
|
+
*/
|
|
46
|
+
payload?: Partial<RmExportPayload> | (() => Partial<RmExportPayload> | null | undefined | Promise<Partial<RmExportPayload> | null | undefined>)
|
|
40
47
|
/**
|
|
41
48
|
* Etat de la donnee. Les trois variantes se posent dans le CORPS ; la tete
|
|
42
49
|
* et le pied restent stables, pour que le titre et la source restent sous
|
|
@@ -56,6 +63,7 @@ const props = withDefaults(defineProps<{
|
|
|
56
63
|
extra: undefined,
|
|
57
64
|
actions: true,
|
|
58
65
|
link: undefined,
|
|
66
|
+
payload: undefined,
|
|
59
67
|
state: 'ready',
|
|
60
68
|
retryLabel: '',
|
|
61
69
|
emptyActionLabel: '',
|
|
@@ -135,6 +143,8 @@ const verifiedText = computed(() => {
|
|
|
135
143
|
:lang="lang"
|
|
136
144
|
:link="link"
|
|
137
145
|
:name="context ? `${title} (${context})` : title"
|
|
146
|
+
:sources="[{ name: source, date: verifiedText }]"
|
|
147
|
+
:payload="payload"
|
|
138
148
|
@export-png="emit('exportPng')"
|
|
139
149
|
@export-svg="emit('exportSvg')"
|
|
140
150
|
@export-csv="emit('exportCsv')"
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
<script setup lang="ts">
|
|
43
43
|
import { computed, ref } from 'vue'
|
|
44
44
|
import { useRmExport } from '../composables/useRmExport'
|
|
45
|
+
import { rmExportDefaults } from '../composables/useRmExportDefaults'
|
|
45
46
|
import type { RmExportKpi, RmExportSheet, RmExportSource } from '../utils/export'
|
|
46
47
|
|
|
47
48
|
const props = withDefaults(
|
|
@@ -151,6 +152,9 @@ async function write() {
|
|
|
151
152
|
if (held.length) await Promise.all(held)
|
|
152
153
|
|
|
153
154
|
const { run } = useRmExport()
|
|
155
|
+
// Sans sources declarees, celles du cadre : la page les cite deja dans
|
|
156
|
+
// son panneau « Sources », le classeur ne doit pas sortir sans.
|
|
157
|
+
const defaults = rmExportDefaults()
|
|
154
158
|
const result = await run(
|
|
155
159
|
{
|
|
156
160
|
format: 'xlsx',
|
|
@@ -168,7 +172,7 @@ async function write() {
|
|
|
168
172
|
title: props.title || buttonLabel.value,
|
|
169
173
|
subtitle: props.subtitle,
|
|
170
174
|
kpi: props.kpi,
|
|
171
|
-
sources: props.sources,
|
|
175
|
+
sources: props.sources.length ? props.sources : defaults.sources,
|
|
172
176
|
sheets: props.sheets,
|
|
173
177
|
limit: props.limit,
|
|
174
178
|
},
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* d'une declaration de la page : c'est ce qui rend le menu utilisable sur les
|
|
13
13
|
* cartes deja ecrites, sans les rouvrir une par une.
|
|
14
14
|
*/
|
|
15
|
-
import type { RmExportPayload } from '../utils/export.js';
|
|
15
|
+
import type { RmExportPayload, RmExportSource } from '../utils/export.js';
|
|
16
16
|
/** Les actions qu'une carte propose. `link` copie, il ne telecharge pas. */
|
|
17
17
|
export type RmCardAction = 'png' | 'svg' | 'csv' | 'xlsx' | 'link';
|
|
18
18
|
export type RmCardPayload = Partial<RmExportPayload>;
|
|
@@ -24,6 +24,13 @@ export interface RmCardActionContext {
|
|
|
24
24
|
name?: string;
|
|
25
25
|
/** Lien copie par « Copier le lien direct ». A defaut, l'URL courante. */
|
|
26
26
|
link?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Ce que le pied de la carte dit de sa source. Ne sert que si ni la page
|
|
29
|
+
* (`payload`) ni le cadre (`provideRmExportDefaults`) n'ont mieux : le cadre
|
|
30
|
+
* lit le referentiel des sources, le pied d'une carte peut n'en porter
|
|
31
|
+
* qu'un libelle.
|
|
32
|
+
*/
|
|
33
|
+
sources?: RmExportSource[];
|
|
27
34
|
/**
|
|
28
35
|
* Ce que la page a de mieux que le rendu a l'ecran : ses onglets complets,
|
|
29
36
|
* ses chiffres cles, ses sources, son sous-titre. Ce qui est pose ici
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { rmCardCharts, rmCardSheets, rmCardTitle } from "../utils/cardData.js";
|
|
2
2
|
import { useRmExport } from "./useRmExport.js";
|
|
3
|
+
import { rmExportDefaults } from "./useRmExportDefaults.js";
|
|
3
4
|
const FILE_NAME_MAX = 60;
|
|
4
5
|
function fileNameOf(label) {
|
|
5
6
|
const flat = label.normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -42,15 +43,19 @@ export function useRmCardActions() {
|
|
|
42
43
|
async function runCardAction(action, context) {
|
|
43
44
|
const { card, lang = "fr" } = context;
|
|
44
45
|
const provided = await (typeof context.payload === "function" ? context.payload() : context.payload) ?? {};
|
|
46
|
+
const defaults = rmExportDefaults();
|
|
45
47
|
const title = context.name || provided.title || rmCardTitle(card) || "Export";
|
|
48
|
+
const sources = provided.sources?.length ? provided.sources : defaults.sources?.length ? defaults.sources : context.sources;
|
|
46
49
|
const format = action === "link" ? "link" : action;
|
|
47
50
|
const sheets = action === "png" || action === "svg" ? [] : provided.sheets?.length ? provided.sheets : await rmCardSheets(card, title, lang);
|
|
48
51
|
return run(configOf(format, fileNameOf(title)), {
|
|
52
|
+
...defaults,
|
|
49
53
|
title,
|
|
50
54
|
charts: rmCardCharts(card).map((el) => ({ title, el })),
|
|
51
55
|
permalink: absolute(context.link),
|
|
52
56
|
...provided,
|
|
53
|
-
sheets
|
|
57
|
+
sheets,
|
|
58
|
+
sources
|
|
54
59
|
}, lang);
|
|
55
60
|
}
|
|
56
61
|
async function toggleCardFullscreen(card) {
|
|
@@ -2,6 +2,8 @@ import { RM_EXPORT_EXTENSIONS } from "../utils/export.js";
|
|
|
2
2
|
const BRAND_TITLE = "OIP Healthcare";
|
|
3
3
|
const BRAND_LINE = "By Tekkare \u{1F49C}";
|
|
4
4
|
const COVER_SHEET = "Tekkare";
|
|
5
|
+
const CARD_PAGES_MAX = 10;
|
|
6
|
+
const CARD_COUNT_MAX = 2e3;
|
|
5
7
|
function longDate(lang) {
|
|
6
8
|
return (/* @__PURE__ */ new Date()).toLocaleDateString(lang === "en" ? "en-US" : "fr-FR", {
|
|
7
9
|
year: "numeric",
|
|
@@ -9,6 +11,27 @@ function longDate(lang) {
|
|
|
9
11
|
day: "numeric"
|
|
10
12
|
});
|
|
11
13
|
}
|
|
14
|
+
function sourceDate(source, lang) {
|
|
15
|
+
if (!source.date) return null;
|
|
16
|
+
const parsed = new Date(source.date);
|
|
17
|
+
if (Number.isNaN(parsed.getTime())) return String(source.date);
|
|
18
|
+
return parsed.toLocaleDateString(lang === "en" ? "en-US" : "fr-FR", {
|
|
19
|
+
year: "numeric",
|
|
20
|
+
month: "long",
|
|
21
|
+
day: "numeric"
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function sourceLabel(source, lang) {
|
|
25
|
+
const date = sourceDate(source, lang);
|
|
26
|
+
return date ? `${source.name} (${date})` : source.name;
|
|
27
|
+
}
|
|
28
|
+
function sourcesOf(config, payload) {
|
|
29
|
+
const wanted = config.sections.includes("sources") || config.options.includes("sources");
|
|
30
|
+
return wanted ? payload.sources ?? [] : [];
|
|
31
|
+
}
|
|
32
|
+
function escapeXml(text) {
|
|
33
|
+
return text.replace(/[<>&"']/g, (char) => ({ "<": "<", ">": ">", "&": "&", '"': """, "'": "'" })[char] ?? char);
|
|
34
|
+
}
|
|
12
35
|
function rowsOf(sheet, limit) {
|
|
13
36
|
return limit && sheet.data.length > limit ? sheet.data.slice(0, limit) : sheet.data;
|
|
14
37
|
}
|
|
@@ -105,7 +128,7 @@ function printable(text) {
|
|
|
105
128
|
return substituted.replace(/[^\u0000-\u00ff]/g, "").replace(/\s{2,}/g, " ").trim();
|
|
106
129
|
}
|
|
107
130
|
function cardsOf(payload, config, lang) {
|
|
108
|
-
if (payload.cards?.length) return payload.cards.slice(0, payload.cardLimit ??
|
|
131
|
+
if (payload.cards?.length) return payload.cards.slice(0, payload.cardLimit ?? CARD_COUNT_MAX);
|
|
109
132
|
const sheet = payload.sheets[0];
|
|
110
133
|
if (!sheet) return [];
|
|
111
134
|
const pick = (name) => name ? sheet.schema.find((column) => column.column === name) : void 0;
|
|
@@ -115,7 +138,7 @@ function cardsOf(payload, config, lang) {
|
|
|
115
138
|
const head = title ?? columns[0];
|
|
116
139
|
if (!head) return [];
|
|
117
140
|
const fields = columns.filter((column) => column !== head && column !== subtitle);
|
|
118
|
-
return rowsOf(sheet, payload.cardLimit ??
|
|
141
|
+
return rowsOf(sheet, payload.cardLimit ?? CARD_COUNT_MAX).map((row) => ({
|
|
119
142
|
title: cellText(head.value(row), lang) || "\u2014",
|
|
120
143
|
subtitle: subtitle ? cellText(subtitle.value(row), lang) : void 0,
|
|
121
144
|
fields: fields.map((column) => ({ label: column.column, value: cellText(column.value(row), lang) })).filter((field) => field.value !== "")
|
|
@@ -185,15 +208,11 @@ export function useRmExport() {
|
|
|
185
208
|
(filter) => line(`${filter.label} : ${filter.value}`, { fontSize: 16, color: "#b2b2b2" })
|
|
186
209
|
);
|
|
187
210
|
}
|
|
188
|
-
const
|
|
189
|
-
if (
|
|
211
|
+
const sources = sourcesOf(config, payload);
|
|
212
|
+
if (sources.length) {
|
|
190
213
|
rows.push(filler());
|
|
191
|
-
|
|
192
|
-
const date =
|
|
193
|
-
year: "numeric",
|
|
194
|
-
month: "long",
|
|
195
|
-
day: "numeric"
|
|
196
|
-
}) : null;
|
|
214
|
+
sources.forEach((source) => {
|
|
215
|
+
const date = sourceDate(source, lang);
|
|
197
216
|
const update = date ? lang === "en" ? ` - Last update: ${date}` : ` - Derni\xE8re mise \xE0 jour : ${date}` : "";
|
|
198
217
|
line(`${source.emoji ? `${source.emoji} ` : ""}Source : ${source.name}${update}`, { fontSize: 18 });
|
|
199
218
|
});
|
|
@@ -235,6 +254,16 @@ export function useRmExport() {
|
|
|
235
254
|
);
|
|
236
255
|
blocks.push(lines.join("\n"));
|
|
237
256
|
});
|
|
257
|
+
const sources = sourcesOf(config, payload);
|
|
258
|
+
if (sources.length) {
|
|
259
|
+
blocks.push(
|
|
260
|
+
sources.map((source) => {
|
|
261
|
+
const date = sourceDate(source, lang);
|
|
262
|
+
const update = date ? lang === "en" ? ` - Last update: ${date}` : ` - Derni\xE8re mise \xE0 jour : ${date}` : "";
|
|
263
|
+
return `# Source : ${source.name}${update}`;
|
|
264
|
+
}).join("\n")
|
|
265
|
+
);
|
|
266
|
+
}
|
|
238
267
|
const fileName = `${config.fileName}.csv`;
|
|
239
268
|
const blob = new Blob([`\uFEFF${blocks.join("\n\n")}
|
|
240
269
|
`], { type: "text/csv;charset=utf-8" });
|
|
@@ -245,7 +274,8 @@ export function useRmExport() {
|
|
|
245
274
|
if (!charts.length) throw new Error(lang === "en" ? "No chart on this page" : "Aucun graphique sur cette page");
|
|
246
275
|
const scale = 2;
|
|
247
276
|
const pad = 24;
|
|
248
|
-
const
|
|
277
|
+
const sources = sourcesOf(config, payload);
|
|
278
|
+
const head = config.options.includes("logo") ? sources.length ? 88 : 72 : 0;
|
|
249
279
|
const parts = charts.map((svg) => serialize(svg));
|
|
250
280
|
const width = Math.max(...parts.map((part) => part.width)) + pad * 2;
|
|
251
281
|
const height = parts.reduce((total, part) => total + part.height + pad, pad) + head;
|
|
@@ -263,6 +293,9 @@ export function useRmExport() {
|
|
|
263
293
|
context.fillStyle = "#6C6C8A";
|
|
264
294
|
context.font = "12px Figtree, Helvetica, sans-serif";
|
|
265
295
|
context.fillText(`${payload.title} \xB7 ${BRAND_LINE} \xB7 ${longDate(lang)}`, pad, 52);
|
|
296
|
+
if (sources.length) {
|
|
297
|
+
context.fillText(`Source${sources.length > 1 ? "s" : ""} : ${sources.map((source) => sourceLabel(source, lang)).join(" \xB7 ")}`, pad, 68);
|
|
298
|
+
}
|
|
266
299
|
}
|
|
267
300
|
let y = head + pad;
|
|
268
301
|
for (const part of parts) {
|
|
@@ -280,7 +313,8 @@ export function useRmExport() {
|
|
|
280
313
|
const charts = (payload.charts ?? []).map((chart) => svgOf(chart.el)).filter(Boolean);
|
|
281
314
|
if (!charts.length) throw new Error(lang === "en" ? "No chart on this page" : "Aucun graphique sur cette page");
|
|
282
315
|
const pad = 24;
|
|
283
|
-
const
|
|
316
|
+
const sources = sourcesOf(config, payload);
|
|
317
|
+
const head = config.options.includes("logo") ? sources.length ? 88 : 72 : 0;
|
|
284
318
|
const parts = charts.map((svg) => serialize(svg));
|
|
285
319
|
const width = Math.max(...parts.map((part) => part.width)) + pad * 2;
|
|
286
320
|
const height = parts.reduce((total, part) => total + part.height + pad, pad) + head;
|
|
@@ -290,7 +324,7 @@ export function useRmExport() {
|
|
|
290
324
|
y += part.height + pad;
|
|
291
325
|
return markup2;
|
|
292
326
|
}).join("\n");
|
|
293
|
-
const header = head ? `<text x="${pad}" y="32" font-family="Figtree, Helvetica, sans-serif" font-size="20" font-weight="bold" fill="#1A1A2E">${BRAND_TITLE}</text><text x="${pad}" y="52" font-family="Figtree, Helvetica, sans-serif" font-size="12" fill="#6C6C8A">${payload.title} \xB7 ${BRAND_LINE} \xB7 ${longDate(lang)}</text>` : "";
|
|
327
|
+
const header = head ? `<text x="${pad}" y="32" font-family="Figtree, Helvetica, sans-serif" font-size="20" font-weight="bold" fill="#1A1A2E">${BRAND_TITLE}</text><text x="${pad}" y="52" font-family="Figtree, Helvetica, sans-serif" font-size="12" fill="#6C6C8A">${escapeXml(`${payload.title} \xB7 ${BRAND_LINE} \xB7 ${longDate(lang)}`)}</text>` + (sources.length ? `<text x="${pad}" y="68" font-family="Figtree, Helvetica, sans-serif" font-size="12" fill="#6C6C8A">${escapeXml(`Source${sources.length > 1 ? "s" : ""} : ${sources.map((source) => sourceLabel(source, lang)).join(" \xB7 ")}`)}</text>` : "") : "";
|
|
294
328
|
const markup = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"><rect width="${width}" height="${height}" fill="#FFFFFF"/>${header}${nested}</svg>`;
|
|
295
329
|
const fileName = `${config.fileName}.svg`;
|
|
296
330
|
const blob = new Blob([markup], { type: "image/svg+xml;charset=utf-8" });
|
|
@@ -405,13 +439,14 @@ export function useRmExport() {
|
|
|
405
439
|
let rowCount = 0;
|
|
406
440
|
if (config.sections.includes("cards")) {
|
|
407
441
|
const cards = cardsOf(payload, config, lang);
|
|
408
|
-
rowCount = cards.length;
|
|
409
442
|
const pageHeight = doc.internal.pageSize.getHeight();
|
|
410
443
|
const PAD = 6;
|
|
411
444
|
const COUNT_WIDTH = 32;
|
|
412
445
|
const COUNT_GAP = 6;
|
|
413
446
|
const LABEL_WIDTH = 26;
|
|
414
|
-
|
|
447
|
+
const firstCardPage = doc.getNumberOfPages();
|
|
448
|
+
let drawn = 0;
|
|
449
|
+
for (const card of cards) {
|
|
415
450
|
const hasCount = !!card.count?.value;
|
|
416
451
|
const bodyX = margin + PAD + (hasCount ? COUNT_WIDTH + COUNT_GAP : 0);
|
|
417
452
|
const bodyWidth = inner - PAD * 2 - (hasCount ? COUNT_WIDTH + COUNT_GAP : 0);
|
|
@@ -476,6 +511,7 @@ export function useRmExport() {
|
|
|
476
511
|
const countHeight = hasCount ? 9 + (card.count?.label ? 4 : 0) + (card.count?.extra ? 4.5 : 0) : 0;
|
|
477
512
|
const height = PAD + headHeight + 3 + Math.max(bodyHeight, countHeight) + PAD;
|
|
478
513
|
if (y + height > pageHeight - margin) {
|
|
514
|
+
if (doc.getNumberOfPages() - firstCardPage + 1 >= CARD_PAGES_MAX) break;
|
|
479
515
|
doc.addPage();
|
|
480
516
|
y = margin;
|
|
481
517
|
}
|
|
@@ -583,15 +619,17 @@ export function useRmExport() {
|
|
|
583
619
|
cursor += Math.max(field.label.length, field.value.length) * 4.4;
|
|
584
620
|
});
|
|
585
621
|
y += height + 4;
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
622
|
+
drawn += 1;
|
|
623
|
+
}
|
|
624
|
+
rowCount = drawn;
|
|
625
|
+
const total = payload.cards?.length || (payload.sheets[0]?.data.length ?? 0);
|
|
626
|
+
if (total > drawn) {
|
|
589
627
|
doc.setTextColor(...grey);
|
|
590
628
|
doc.setFont("helvetica", "italic");
|
|
591
629
|
doc.setFontSize(8);
|
|
592
630
|
doc.text(
|
|
593
631
|
printable(
|
|
594
|
-
lang === "en" ? `${
|
|
632
|
+
lang === "en" ? `${drawn} of ${total} records shown. Export to Excel for the full set.` : `${drawn} fiches sur ${total}. Passez par l'export Excel pour la totalit\xE9.`
|
|
595
633
|
),
|
|
596
634
|
margin,
|
|
597
635
|
y + 2
|
|
@@ -645,12 +683,12 @@ export function useRmExport() {
|
|
|
645
683
|
y = doc.lastAutoTable?.finalY ?? margin;
|
|
646
684
|
});
|
|
647
685
|
}
|
|
648
|
-
const
|
|
649
|
-
if (
|
|
686
|
+
const sources = sourcesOf(config, payload);
|
|
687
|
+
if (sources.length) {
|
|
650
688
|
doc.setTextColor(...grey);
|
|
651
689
|
doc.setFont("helvetica", "normal");
|
|
652
690
|
doc.setFontSize(8);
|
|
653
|
-
const names =
|
|
691
|
+
const names = sources.map((source) => sourceLabel(source, lang)).join(" \xB7 ");
|
|
654
692
|
const lines = doc.splitTextToSize(printable(`Sources : ${names}`), inner);
|
|
655
693
|
doc.text(lines, margin, Math.min(y + 8, doc.internal.pageSize.getHeight() - margin));
|
|
656
694
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RmExportPayload } from '../utils/export.js';
|
|
2
|
+
export type RmExportDefaultsBuild = () => Partial<RmExportPayload> | null | undefined;
|
|
3
|
+
/**
|
|
4
|
+
* Pose les defauts de la page affichee. A appeler dans le `setup` du cadre
|
|
5
|
+
* (layout) : ils sont retires quand ce cadre est demonte.
|
|
6
|
+
*/
|
|
7
|
+
export declare function provideRmExportDefaults(build: RmExportDefaultsBuild): void;
|
|
8
|
+
/** Les defauts courants, relus a chaque export : ils suivent la navigation. */
|
|
9
|
+
export declare function rmExportDefaults(): Partial<RmExportPayload>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { getCurrentScope, onScopeDispose, shallowRef } from "vue";
|
|
2
|
+
const registry = shallowRef(null);
|
|
3
|
+
export function provideRmExportDefaults(build) {
|
|
4
|
+
registry.value = build;
|
|
5
|
+
if (getCurrentScope()) {
|
|
6
|
+
onScopeDispose(() => {
|
|
7
|
+
if (registry.value === build) registry.value = null;
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function rmExportDefaults() {
|
|
12
|
+
return registry.value?.() ?? {};
|
|
13
|
+
}
|