@tekkare/romulus 0.1.100 → 0.1.101
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
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
RmExportButton - exporter le contenu d'une carte, en un clic.
|
|
3
|
+
|
|
4
|
+
Le pendant court de RmExportPanel. Le panneau pose quatre questions depuis
|
|
5
|
+
le rail parce qu'il exporte un ecran entier ; ce bouton n'en pose aucune
|
|
6
|
+
parce qu'il exporte une carte, dont le lecteur voit deja ce qu'elle
|
|
7
|
+
contient. Poser un assistant sur un resultat de liste demanderait de choisir
|
|
8
|
+
ce qui n'a qu'une reponse.
|
|
9
|
+
|
|
10
|
+
Il ne genere rien lui-meme : le classeur sort de `useRmExport`, le meme que
|
|
11
|
+
le panneau. Page de garde « OIP Healthcare par Tekkare », en-tetes, plafond
|
|
12
|
+
de 32 767 caracteres par cellule : un export de carte et un export de page
|
|
13
|
+
se presentent donc a l'identique devant un client.
|
|
14
|
+
|
|
15
|
+
Le garde-fou du plafond est garde d'Auriga : au-dela de `limit` lignes, une
|
|
16
|
+
modale dit combien la selection contient et combien partiront, plutot que de
|
|
17
|
+
livrer un fichier tronque en silence. Elle ne parait que si la page passe
|
|
18
|
+
`total`, le seul a connaitre le compte reel : `sheets` ne porte que les
|
|
19
|
+
lignes deja chargees.
|
|
20
|
+
|
|
21
|
+
ORIGINE AURIGA
|
|
22
|
+
--------------
|
|
23
|
+
Remplace `<ArgExportXLSX>` (`argExportXLSX.vue`), et le wrapper applicatif
|
|
24
|
+
`AppExportXLSX` qui lui greffait le suivi Posthog.
|
|
25
|
+
|
|
26
|
+
| Auriga | Romulus | Note |
|
|
27
|
+
|--------------------|--------------|----------------------------------------|
|
|
28
|
+
| `name` | `label` | libelle du bouton, pas le nom du fichier |
|
|
29
|
+
| `export-name` | `file-name` | sans extension ni date, ajoutees ici |
|
|
30
|
+
| `subtitle` | `subtitle` | va sur la page de garde |
|
|
31
|
+
| `tabs: Tab[]` | `sheets: RmExportSheet[]` | meme forme : `{ name, schema, data }`, et `schema` garde `{ column, value }` |
|
|
32
|
+
| `kpi` | `kpi` | inchange |
|
|
33
|
+
| `sources` | `sources` | `{ name, date }` |
|
|
34
|
+
| `limit` | `limit` | inchange |
|
|
35
|
+
| `actualAmount` | `total` | `null` devient absent |
|
|
36
|
+
| `style` | `variant` | les noms de RmButton |
|
|
37
|
+
| `beforeExport` | `@before` | evenement plutot que prop fonction ; attendu s'il rend une promesse |
|
|
38
|
+
| `lang` | `lang` | `es` n'est pas repris, personne ne l'appelait |
|
|
39
|
+
| `posthog-key` / `posthog-host` / `posthog-properties` | - | le suivi ne regarde pas un composant de rendu : la page l'accroche a `@export` |
|
|
40
|
+
| `cut` | - | jamais passe par aucun appelant |
|
|
41
|
+
-->
|
|
42
|
+
<script setup lang="ts">
|
|
43
|
+
import { computed, ref } from 'vue'
|
|
44
|
+
import { useRmExport } from '../composables/useRmExport'
|
|
45
|
+
import type { RmExportKpi, RmExportSheet, RmExportSource } from '../utils/export'
|
|
46
|
+
|
|
47
|
+
const props = withDefaults(
|
|
48
|
+
defineProps<{
|
|
49
|
+
/** Nom du fichier, sans extension : la date et `.xlsx` sont ajoutees ici. */
|
|
50
|
+
fileName?: string
|
|
51
|
+
/** Libelle du bouton. */
|
|
52
|
+
label?: string
|
|
53
|
+
/** Titre du document, sur la page de garde. Absent : le libelle sert. */
|
|
54
|
+
title?: string
|
|
55
|
+
subtitle?: string
|
|
56
|
+
/** Les onglets du classeur. Un seul dans le cas courant. */
|
|
57
|
+
sheets?: RmExportSheet[]
|
|
58
|
+
kpi?: RmExportKpi[]
|
|
59
|
+
sources?: RmExportSource[]
|
|
60
|
+
/** Lignes ecrites au plus, par onglet. */
|
|
61
|
+
limit?: number
|
|
62
|
+
/**
|
|
63
|
+
* Lignes que la selection contient vraiment, quand elle depasse ce qui est
|
|
64
|
+
* charge. Au-dela de `limit`, elle declenche la confirmation. Absente,
|
|
65
|
+
* aucune confirmation : le composant ne peut pas deviner qu'il tronque.
|
|
66
|
+
*/
|
|
67
|
+
total?: number
|
|
68
|
+
variant?: string
|
|
69
|
+
size?: 'sm' | 'md' | 'lg'
|
|
70
|
+
lang?: 'fr' | 'en'
|
|
71
|
+
}>(),
|
|
72
|
+
{
|
|
73
|
+
fileName: 'export-tekkare',
|
|
74
|
+
label: '',
|
|
75
|
+
title: '',
|
|
76
|
+
subtitle: '',
|
|
77
|
+
sheets: () => [],
|
|
78
|
+
kpi: () => [],
|
|
79
|
+
sources: () => [],
|
|
80
|
+
limit: 1000,
|
|
81
|
+
total: undefined,
|
|
82
|
+
variant: 'secondary',
|
|
83
|
+
size: 'sm',
|
|
84
|
+
lang: 'fr',
|
|
85
|
+
},
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
const emit = defineEmits<{
|
|
89
|
+
/**
|
|
90
|
+
* Emis avant de construire le classeur, pour la page qui doit d'abord aller
|
|
91
|
+
* chercher les lignes. Rendre une promesse par `until` la fait attendre.
|
|
92
|
+
*/
|
|
93
|
+
before: [hold: (promise: Promise<unknown>) => void]
|
|
94
|
+
/** Emis apres l'ecriture, avec le nom du fichier et le nombre de lignes. */
|
|
95
|
+
export: [result: { fileName: string, rows: number }]
|
|
96
|
+
/** Emis quand l'ecriture echoue. */
|
|
97
|
+
error: [error: unknown]
|
|
98
|
+
}>()
|
|
99
|
+
|
|
100
|
+
const WORDS = {
|
|
101
|
+
fr: {
|
|
102
|
+
label: 'Exporter',
|
|
103
|
+
limitTitle: 'Limite de téléchargement atteinte',
|
|
104
|
+
cancel: 'Annuler',
|
|
105
|
+
confirm: 'Continuer le téléchargement',
|
|
106
|
+
body: (total: number, limit: number) =>
|
|
107
|
+
`Votre sélection contient ${total} éléments. Pour des raisons d’optimisation, seuls les ${limit} premiers seront téléchargés.`,
|
|
108
|
+
},
|
|
109
|
+
en: {
|
|
110
|
+
label: 'Export',
|
|
111
|
+
limitTitle: 'Download limit reached',
|
|
112
|
+
cancel: 'Cancel',
|
|
113
|
+
confirm: 'Continue the download',
|
|
114
|
+
body: (total: number, limit: number) =>
|
|
115
|
+
`Your selection contains ${total} items. For optimisation reasons, only the first ${limit} will be downloaded.`,
|
|
116
|
+
},
|
|
117
|
+
} as const
|
|
118
|
+
|
|
119
|
+
const t = computed(() => WORDS[props.lang] ?? WORDS.fr)
|
|
120
|
+
const buttonLabel = computed(() => props.label || t.value.label)
|
|
121
|
+
|
|
122
|
+
const busy = ref(false)
|
|
123
|
+
const confirming = ref(false)
|
|
124
|
+
|
|
125
|
+
/** Le fichier porte sa date : deux exports du meme ecran ne s'ecrasent pas. */
|
|
126
|
+
const stampedName = computed(() => {
|
|
127
|
+
const today = new Date().toISOString().slice(0, 10)
|
|
128
|
+
return `${props.fileName}-from_${today}`
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
const overLimit = computed(
|
|
132
|
+
() => props.total !== undefined && props.total > props.limit,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
async function start() {
|
|
136
|
+
if (overLimit.value) {
|
|
137
|
+
confirming.value = true
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
await write()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function write() {
|
|
144
|
+
busy.value = true
|
|
145
|
+
try {
|
|
146
|
+
// La page peut avoir a charger ses lignes : elle retient le bouton en
|
|
147
|
+
// passant sa promesse, plutot que le composant ne prenne une prop fonction
|
|
148
|
+
// que la moitie des appelants laissent vide.
|
|
149
|
+
const held: Promise<unknown>[] = []
|
|
150
|
+
emit('before', promise => held.push(promise))
|
|
151
|
+
if (held.length) await Promise.all(held)
|
|
152
|
+
|
|
153
|
+
const { run } = useRmExport()
|
|
154
|
+
const result = await run(
|
|
155
|
+
{
|
|
156
|
+
format: 'xlsx',
|
|
157
|
+
range: 'all',
|
|
158
|
+
sections: [],
|
|
159
|
+
columns: [],
|
|
160
|
+
options: [],
|
|
161
|
+
fileName: stampedName.value,
|
|
162
|
+
layout: '',
|
|
163
|
+
schedule: 'once',
|
|
164
|
+
frequency: '',
|
|
165
|
+
email: false,
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
title: props.title || buttonLabel.value,
|
|
169
|
+
subtitle: props.subtitle,
|
|
170
|
+
kpi: props.kpi,
|
|
171
|
+
sources: props.sources,
|
|
172
|
+
sheets: props.sheets,
|
|
173
|
+
limit: props.limit,
|
|
174
|
+
},
|
|
175
|
+
props.lang,
|
|
176
|
+
)
|
|
177
|
+
emit('export', { fileName: result.fileName, rows: result.rows })
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
emit('error', error)
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
busy.value = false
|
|
184
|
+
confirming.value = false
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
</script>
|
|
188
|
+
|
|
189
|
+
<template>
|
|
190
|
+
<RmButton
|
|
191
|
+
:variant="variant"
|
|
192
|
+
:size="size"
|
|
193
|
+
prefix-icon="FileXls"
|
|
194
|
+
:loading="busy"
|
|
195
|
+
@click="start"
|
|
196
|
+
>
|
|
197
|
+
{{ buttonLabel }}
|
|
198
|
+
</RmButton>
|
|
199
|
+
|
|
200
|
+
<RmModal
|
|
201
|
+
v-model="confirming"
|
|
202
|
+
size="sm"
|
|
203
|
+
mode="free"
|
|
204
|
+
:label="t.limitTitle"
|
|
205
|
+
:close-label="t.cancel"
|
|
206
|
+
>
|
|
207
|
+
<template #header>
|
|
208
|
+
<h2 class="rm-export-button__title">{{ t.limitTitle }}</h2>
|
|
209
|
+
</template>
|
|
210
|
+
|
|
211
|
+
<p class="rm-export-button__body">{{ t.body(total ?? 0, limit) }}</p>
|
|
212
|
+
|
|
213
|
+
<template #footer>
|
|
214
|
+
<RmButton variant="secondary" @click="confirming = false">
|
|
215
|
+
{{ t.cancel }}
|
|
216
|
+
</RmButton>
|
|
217
|
+
<RmButton variant="primary" :loading="busy" @click="write">
|
|
218
|
+
{{ t.confirm }}
|
|
219
|
+
</RmButton>
|
|
220
|
+
</template>
|
|
221
|
+
</RmModal>
|
|
222
|
+
</template>
|
|
223
|
+
|
|
224
|
+
<style scoped>
|
|
225
|
+
.rm-export-button__title{color:var(--text-primary);font-size:var(--font-16);font-weight:700;margin:0}.rm-export-button__body{color:var(--text-secondary);font-size:var(--font-14);line-height:1.6;margin:0}
|
|
226
|
+
</style>
|