@datagouv/components-next 0.0.7 → 0.0.9
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/README.md +34 -14
- package/assets/json/vector.json +2377 -0
- package/assets/main.css +3 -0
- package/assets/swagger-themes/newspaper.css +1669 -0
- package/assets/tailwind.config.js +1 -1
- package/dist/JsonPreview.client-BIz1_EiB.js +92 -0
- package/dist/MapContainer.client-ZDwr4Q_I.js +78276 -0
- package/dist/PdfPreview.client-BTTMM27i.js +112 -0
- package/dist/Pmtiles.client-4kOoUQcR.js +22377 -0
- package/dist/Swagger.client-Q7a5wb51.js +4 -0
- package/dist/XmlPreview.client-BYIIkDqf.js +84 -0
- package/dist/components-next.css +52 -1
- package/dist/components-next.js +42 -41
- package/dist/components.css +1 -1
- package/dist/main-CLUk9Jj7.js +105843 -0
- package/dist/pdf-vue3-BZh6kzke.js +273 -0
- package/dist/pdf.min-f72cfa08-DAetWL3M.js +9501 -0
- package/dist/{text-clamp.esm-DurZFOvT.js → text-clamp.esm-DP59tec5.js} +1 -1
- package/dist/vue3-json-viewer-DIQzFF6K.js +1089 -0
- package/dist/vue3-xml-viewer.common-BmKw6vER.js +5437 -0
- package/package.json +7 -5
- package/src/components/AvatarWithName.vue +6 -2
- package/src/components/BannerAction.vue +1 -1
- package/src/components/BrandedButton.vue +13 -8
- package/src/components/CopyButton.vue +7 -7
- package/src/components/DataserviceCard.vue +54 -23
- package/src/components/DatasetCard.vue +36 -24
- package/src/components/DatasetInformationPanel.vue +19 -18
- package/src/components/DatasetQuality.vue +21 -18
- package/src/components/DatasetQualityInline.vue +1 -1
- package/src/components/DatasetQualityItem.vue +3 -3
- package/src/components/DatasetQualityItemWarning.vue +2 -2
- package/src/components/DatasetQualityScore.vue +2 -2
- package/src/components/DatasetQualityTooltipContent.vue +29 -29
- package/src/components/DescriptionDetails.vue +2 -2
- package/src/components/ExtraAccordion.vue +10 -7
- package/src/components/OrganizationCard.vue +9 -4
- package/src/components/OrganizationNameWithCertificate.vue +25 -11
- package/src/components/Pagination.vue +26 -15
- package/src/components/ReadMore.vue +2 -2
- package/src/components/ResourceAccordion/DataStructure.vue +2 -2
- package/src/components/ResourceAccordion/EditButton.vue +10 -6
- package/src/components/ResourceAccordion/JsonPreview.client.vue +153 -0
- package/src/components/ResourceAccordion/MapContainer.client.vue +137 -0
- package/src/components/ResourceAccordion/Metadata.vue +33 -54
- package/src/components/ResourceAccordion/PdfPreview.client.vue +189 -0
- package/src/components/ResourceAccordion/Pmtiles.client.vue +166 -0
- package/src/components/ResourceAccordion/Preview.vue +39 -37
- package/src/components/ResourceAccordion/ResourceAccordion.vue +141 -63
- package/src/components/ResourceAccordion/ResourceIcon.vue +7 -1
- package/src/components/ResourceAccordion/SchemaBadge.vue +26 -26
- package/src/components/ResourceAccordion/{Swagger.vue → Swagger.client.vue} +1 -1
- package/src/components/ResourceAccordion/XmlPreview.client.vue +143 -0
- package/src/components/ReuseCard.vue +10 -7
- package/src/components/ReuseDetails.vue +3 -3
- package/src/components/SimpleBanner.vue +7 -4
- package/src/components/SmallChart.vue +23 -9
- package/src/components/StatBox.vue +92 -10
- package/src/config.ts +6 -2
- package/src/functions/api.ts +18 -18
- package/src/functions/dates.ts +81 -74
- package/src/functions/helpers.ts +5 -4
- package/src/functions/organizations.ts +5 -5
- package/src/functions/resources.ts +34 -5
- package/src/functions/schemas.ts +4 -3
- package/src/functions/tabularApi.ts +1 -1
- package/src/main.ts +10 -11
- package/src/types/badges.ts +3 -3
- package/src/types/contact_point.ts +5 -5
- package/src/types/dataservices.ts +16 -2
- package/src/types/datasets.ts +20 -2
- package/src/types/frequency.ts +5 -5
- package/src/types/granularity.ts +12 -4
- package/src/types/harvest.ts +2 -2
- package/src/types/licenses.ts +8 -8
- package/src/types/organizations.ts +6 -0
- package/src/types/resources.ts +3 -3
- package/src/types/reuses.ts +3 -1
- package/src/types/site.ts +8 -0
- package/src/types/ui.ts +2 -2
- package/src/types/users.ts +24 -8
- package/src/types/vue3-xml-viewer.d.ts +10 -0
- package/dist/Swagger-DjysB-OI.js +0 -67851
- package/dist/en-DCRve7vN.js +0 -613
- package/dist/fr-DCOnbL-p.js +0 -613
- package/dist/locales/de.js +0 -155
- package/dist/locales/en.js +0 -155
- package/dist/locales/es.js +0 -155
- package/dist/locales/fr.js +0 -155
- package/dist/locales/it.js +0 -155
- package/dist/locales/pt.js +0 -155
- package/dist/locales/sr.js +0 -155
- package/dist/main-CPW2vNLE.js +0 -32008
- package/src/components/DescriptionList/DescriptionDetails.stories.ts +0 -43
- package/src/components/DescriptionList/DescriptionList.stories.ts +0 -47
- package/src/components/DescriptionList/DescriptionTerm.stories.ts +0 -28
- package/src/locales/de.json +0 -154
- package/src/locales/en.json +0 -154
- package/src/locales/es.json +0 -154
- package/src/locales/fr.json +0 -154
- package/src/locales/it.json +0 -154
- package/src/locales/pt.json +0 -154
- package/src/locales/sr.json +0 -154
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="fr-text--xs">
|
|
3
|
+
<div v-if="xmlData">
|
|
4
|
+
<XmlViewer :xml="xmlData" />
|
|
5
|
+
</div>
|
|
6
|
+
<div
|
|
7
|
+
v-else-if="loading"
|
|
8
|
+
class="text-gray-medium"
|
|
9
|
+
>
|
|
10
|
+
{{ $t("Chargement de l'aperçu XML...") }}
|
|
11
|
+
</div>
|
|
12
|
+
<SimpleBanner
|
|
13
|
+
v-else-if="fileTooLarge"
|
|
14
|
+
type="warning"
|
|
15
|
+
class="flex items-center space-x-2"
|
|
16
|
+
>
|
|
17
|
+
<RiErrorWarningLine class="shink-0 size-6" />
|
|
18
|
+
<span>{{ fileSizeBytes
|
|
19
|
+
? $t("Fichier XML trop volumineux pour l'aperçu. Pour consulter le fichier complet, téléchargez-le en cliquant sur le bouton bleu ou depuis l'onglet Téléchargements.")
|
|
20
|
+
: $t("L'aperçu n'est pas disponible car la taille du fichier est inconnue. Pour consulter le fichier complet, téléchargez-le en cliquant sur le bouton bleu ou depuis l'onglet Téléchargements.")
|
|
21
|
+
}}</span>
|
|
22
|
+
</SimpleBanner>
|
|
23
|
+
<SimpleBanner
|
|
24
|
+
v-else-if="error === 'network'"
|
|
25
|
+
type="warning"
|
|
26
|
+
class="flex items-center space-x-2"
|
|
27
|
+
>
|
|
28
|
+
<RiErrorWarningLine class="shink-0 size-6" />
|
|
29
|
+
<span>{{ $t("Ce fichier XML ne peut pas être prévisualisé, peut-être parce qu'il est hébergé sur un autre site qui ne l'autorise pas. Pour le consulter, téléchargez-le en cliquant sur le bouton bleu ou depuis l'onglet Téléchargements.") }}</span>
|
|
30
|
+
</SimpleBanner>
|
|
31
|
+
<SimpleBanner
|
|
32
|
+
v-else-if="error"
|
|
33
|
+
type="warning"
|
|
34
|
+
class="flex items-center space-x-2"
|
|
35
|
+
>
|
|
36
|
+
<RiErrorWarningLine class="shink-0 size-6" />
|
|
37
|
+
<span>{{ $t("Erreur lors du chargement de l'aperçu XML.") }}</span>
|
|
38
|
+
</SimpleBanner>
|
|
39
|
+
</div>
|
|
40
|
+
</template>
|
|
41
|
+
|
|
42
|
+
<script setup lang="ts">
|
|
43
|
+
import { computed, defineAsyncComponent, onMounted, ref } from 'vue'
|
|
44
|
+
import { RiErrorWarningLine } from '@remixicon/vue'
|
|
45
|
+
|
|
46
|
+
import { useComponentsConfig } from '../../config'
|
|
47
|
+
import SimpleBanner from '../SimpleBanner.vue'
|
|
48
|
+
import type { Resource } from '../../types/resources'
|
|
49
|
+
|
|
50
|
+
const XmlViewer = defineAsyncComponent(() =>
|
|
51
|
+
import('vue3-xml-viewer').then((module) => {
|
|
52
|
+
return module.default || module.XmlViewer
|
|
53
|
+
}),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
const props = defineProps<{
|
|
57
|
+
resource: Resource
|
|
58
|
+
}>()
|
|
59
|
+
|
|
60
|
+
const config = useComponentsConfig()
|
|
61
|
+
|
|
62
|
+
const xmlData = ref<string | null>(null)
|
|
63
|
+
const loading = ref(false)
|
|
64
|
+
const error = ref<string | null>(null)
|
|
65
|
+
const fileTooLarge = ref(false)
|
|
66
|
+
|
|
67
|
+
const fileSizeBytes = computed(() => {
|
|
68
|
+
// Check if resource has filesize
|
|
69
|
+
if (props.resource.filesize) {
|
|
70
|
+
return props.resource.filesize
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Check if resource has content-length in extras (from API metadata)
|
|
74
|
+
const contentLength = props.resource.extras?.['analysis:content-length']
|
|
75
|
+
if (contentLength && typeof contentLength === 'number') {
|
|
76
|
+
return contentLength
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return null
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const shouldLoadXml = computed(() => {
|
|
83
|
+
const size = fileSizeBytes.value
|
|
84
|
+
if (!size) {
|
|
85
|
+
// If we don't know the size, don't risk loading a potentially huge file
|
|
86
|
+
return false
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check if maxXmlPreviewSize is configured
|
|
90
|
+
if (!config.maxXmlPreviewSize) {
|
|
91
|
+
// If no limit is set, don't load unknown files
|
|
92
|
+
return false
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Convert maxXmlPreviewSize from characters to bytes (rough estimate)
|
|
96
|
+
// Assuming average 1 byte per character for XML
|
|
97
|
+
const maxSizeBytes = config.maxXmlPreviewSize
|
|
98
|
+
|
|
99
|
+
return size <= maxSizeBytes
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const fetchXmlData = async () => {
|
|
103
|
+
// Check if file is too large or size is unknown before making the request
|
|
104
|
+
if (!shouldLoadXml.value) {
|
|
105
|
+
fileTooLarge.value = true
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
loading.value = true
|
|
110
|
+
error.value = null
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const response = await fetch(props.resource.url)
|
|
114
|
+
// const response = await fetch('/test-data.xml') // For testing locally without CORS issues
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
throw new Error(`HTTP error! status: ${response.status}`)
|
|
117
|
+
}
|
|
118
|
+
const data = await response.text()
|
|
119
|
+
|
|
120
|
+
// Use the XML data as string - let the XML viewer handle large files
|
|
121
|
+
xmlData.value = data
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
console.error('Error loading XML:', err)
|
|
125
|
+
|
|
126
|
+
if (err instanceof TypeError) {
|
|
127
|
+
error.value = 'network'
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
error.value = 'generic'
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
xmlData.value = null
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
loading.value = false
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
onMounted(() => {
|
|
141
|
+
fetchXmlData()
|
|
142
|
+
})
|
|
143
|
+
</script>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<div class="order-1 flex flex-col px-4 py-1 h-full -mx-8">
|
|
5
5
|
<h3 class="font-bold text-base mt-1 mb-0 truncate">
|
|
6
6
|
<AppLink
|
|
7
|
-
class="text-gray-title"
|
|
7
|
+
class="text-gray-title overflow-hidden"
|
|
8
8
|
:to="reuseUrl"
|
|
9
9
|
>
|
|
10
10
|
{{ reuse.title }}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
>
|
|
19
19
|
<AppLink
|
|
20
20
|
v-if="organizationUrl"
|
|
21
|
-
class="
|
|
21
|
+
class="link overflow-hidden"
|
|
22
22
|
:to="organizationUrl"
|
|
23
23
|
>
|
|
24
24
|
<OrganizationNameWithCertificate
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
class="mr-1 truncate"
|
|
36
36
|
>{{ ownerName }}</span>
|
|
37
37
|
<RiSubtractLine class="size-4 flex-none fill-gray-medium" />
|
|
38
|
-
<span class="block flex-none">{{ t('
|
|
38
|
+
<span class="block flex-none">{{ t('publié {date}', { date: formatRelativeIfRecentDate(reuse.created_at, { dateStyle: 'medium' }) }) }}</span>
|
|
39
39
|
</p>
|
|
40
40
|
<ReuseDetails :reuse />
|
|
41
41
|
</div>
|
|
@@ -58,13 +58,13 @@
|
|
|
58
58
|
<li v-if="reuse.private">
|
|
59
59
|
<p class="fr-badge fr-badge--sm fr-badge--mention-grey text-gray-medium">
|
|
60
60
|
<RiLockLine class="size-3.5 mr-0.5" />
|
|
61
|
-
{{ t('
|
|
61
|
+
{{ t('Brouillon') }}
|
|
62
62
|
</p>
|
|
63
63
|
</li>
|
|
64
64
|
<li v-if="reuse.archived">
|
|
65
65
|
<p class="fr-badge fr-badge--sm fr-badge--mention-grey text-gray-medium">
|
|
66
66
|
<RiLockLine class="size-3.5 mr-0.5" />
|
|
67
|
-
{{ t('
|
|
67
|
+
{{ t('Archivé') }}
|
|
68
68
|
</p>
|
|
69
69
|
</li>
|
|
70
70
|
</ul>
|
|
@@ -77,7 +77,7 @@ import { RiLockLine, RiSubtractLine } from '@remixicon/vue'
|
|
|
77
77
|
import { computed } from 'vue'
|
|
78
78
|
import { useI18n } from 'vue-i18n'
|
|
79
79
|
import type { RouteLocationRaw } from 'vue-router'
|
|
80
|
-
import {
|
|
80
|
+
import { useFormatDate } from '../functions/dates'
|
|
81
81
|
import { getOwnerName } from '../functions/owned'
|
|
82
82
|
import type { Reuse } from '../types/reuses'
|
|
83
83
|
import AppLink from './AppLink.vue'
|
|
@@ -91,7 +91,7 @@ const props = defineProps<{
|
|
|
91
91
|
* The reuseUrl is a route location object to allow Vue Router to navigate to the details of a reuse.
|
|
92
92
|
* It is used as a separate prop to allow other sites using the package to define their own reuse pages.
|
|
93
93
|
*/
|
|
94
|
-
reuseUrl
|
|
94
|
+
reuseUrl?: RouteLocationRaw
|
|
95
95
|
|
|
96
96
|
/**
|
|
97
97
|
* The organizationUrl is an optional route location object to allow Vue Router to navigate to the details of the organization linked to tha reuse.
|
|
@@ -101,6 +101,9 @@ const props = defineProps<{
|
|
|
101
101
|
}>()
|
|
102
102
|
|
|
103
103
|
const { t } = useI18n()
|
|
104
|
+
const { formatRelativeIfRecentDate } = useFormatDate()
|
|
104
105
|
|
|
105
106
|
const ownerName = computed(() => getOwnerName(props.reuse))
|
|
107
|
+
|
|
108
|
+
const reuseUrl = computed(() => props.reuseUrl || props.reuse.page)
|
|
106
109
|
</script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="flex flex-wrap items-center gap-
|
|
2
|
+
<div class="flex flex-wrap items-center gap-1">
|
|
3
3
|
<p class="text-sm mb-0">
|
|
4
4
|
{{ reuseType }}
|
|
5
5
|
</p>
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
/>
|
|
10
10
|
<p
|
|
11
11
|
class="text-sm mb-0 flex items-center gap-0.5"
|
|
12
|
-
:aria-label="t('{n}
|
|
12
|
+
:aria-label="t('{n} vues | {n} vue | {n} vues', reuse.metrics.views)"
|
|
13
13
|
>
|
|
14
14
|
<RiEyeLine
|
|
15
15
|
aria-hidden="true"
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
</p>
|
|
19
19
|
<p
|
|
20
20
|
class="text-sm mb-0 flex items-center gap-0.5"
|
|
21
|
-
:aria-label="t('{n}
|
|
21
|
+
:aria-label="t('{n} abonnés | {n} abonné | {n} abonnés', reuse.metrics.followers)"
|
|
22
22
|
>
|
|
23
23
|
<RiStarLine
|
|
24
24
|
aria-hidden="true"
|
|
@@ -11,14 +11,17 @@
|
|
|
11
11
|
import { computed } from 'vue'
|
|
12
12
|
|
|
13
13
|
const props = defineProps<{
|
|
14
|
-
type: 'primary' | 'warning' | 'gray'
|
|
14
|
+
type: 'primary' | 'primary-frame' | 'warning' | 'gray' | 'danger' | 'success'
|
|
15
15
|
}>()
|
|
16
16
|
|
|
17
17
|
const classes = computed(() => {
|
|
18
18
|
return {
|
|
19
|
-
primary: 'bg-datagouv-lightest text-datagouv-dark',
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
'primary': 'bg-datagouv-lightest text-datagouv-dark',
|
|
20
|
+
'primary-frame': 'bg-gray-50 border border-datagouv',
|
|
21
|
+
'warning': 'bg-warning3-lightest text-warning3-dark',
|
|
22
|
+
'gray': 'bg-gray-some text-gray-plain',
|
|
23
|
+
'success': 'bg-success-lightest text-success-dark',
|
|
24
|
+
'danger': 'bg-danger-lightest text-danger-dark',
|
|
22
25
|
}[props.type]
|
|
23
26
|
})
|
|
24
27
|
</script>
|
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
<div>
|
|
3
3
|
<canvas
|
|
4
4
|
ref="canvasRef"
|
|
5
|
-
width="
|
|
6
|
-
height="
|
|
5
|
+
:width="width*2"
|
|
6
|
+
:height="height*2"
|
|
7
|
+
:style="{ width: width + 'px', height: height + 'px' }"
|
|
7
8
|
/>
|
|
8
|
-
<div
|
|
9
|
-
|
|
9
|
+
<div
|
|
10
|
+
v-if="showAxisLabel"
|
|
11
|
+
class="flex flex-wrap justify-between"
|
|
12
|
+
>
|
|
13
|
+
<p class="text-[0.625rem] m-0 text-gray-medium">
|
|
10
14
|
{{ startDate }}
|
|
11
15
|
</p>
|
|
12
|
-
<p class="text-
|
|
16
|
+
<p class="text-[0.625rem] m-0 text-gray-medium">
|
|
13
17
|
{{ endDate }}
|
|
14
18
|
</p>
|
|
15
19
|
</div>
|
|
@@ -27,10 +31,16 @@ const LIGHT_COLOR_WITH_OPACITY = 'rgba(182, 207, 251, 0.7)'
|
|
|
27
31
|
|
|
28
32
|
const props = withDefaults(defineProps<{
|
|
29
33
|
data: Record<string, number>
|
|
30
|
-
|
|
34
|
+
height?: number
|
|
31
35
|
lastWithLowEmphasis?: boolean
|
|
36
|
+
showAxisLabel?: boolean
|
|
37
|
+
type: 'line' | 'bar'
|
|
38
|
+
width?: number
|
|
32
39
|
}>(), {
|
|
40
|
+
height: 30,
|
|
33
41
|
lastWithLowEmphasis: false,
|
|
42
|
+
showAxisLabel: true,
|
|
43
|
+
width: 120,
|
|
34
44
|
})
|
|
35
45
|
|
|
36
46
|
const last = (ctx, value) => {
|
|
@@ -85,8 +95,10 @@ const startDate = computed(() => months.value.length ? getMonthYear(months.value
|
|
|
85
95
|
const endDate = computed(() => months.value.length ? getMonthYear(months.value[months.value.length - 1]) : null)
|
|
86
96
|
|
|
87
97
|
const OPTIONS = {
|
|
88
|
-
animation:
|
|
89
|
-
|
|
98
|
+
animation: true,
|
|
99
|
+
devicePixelRatio: 1,
|
|
100
|
+
responsive: false,
|
|
101
|
+
clip: false,
|
|
90
102
|
plugins: {
|
|
91
103
|
legend: {
|
|
92
104
|
display: false,
|
|
@@ -105,6 +117,7 @@ const OPTIONS = {
|
|
|
105
117
|
backgroundColor: COLOR,
|
|
106
118
|
borderColor: COLOR,
|
|
107
119
|
borderJoinStyle: 'round',
|
|
120
|
+
borderWidth: 3,
|
|
108
121
|
tension: 0.35,
|
|
109
122
|
},
|
|
110
123
|
point: {
|
|
@@ -125,7 +138,8 @@ const OPTIONS = {
|
|
|
125
138
|
},
|
|
126
139
|
layout: {
|
|
127
140
|
padding: {
|
|
128
|
-
top:
|
|
141
|
+
top: 2, // half border width
|
|
142
|
+
bottom: 2,
|
|
129
143
|
},
|
|
130
144
|
},
|
|
131
145
|
} satisfies ChartOptions
|
|
@@ -1,13 +1,87 @@
|
|
|
1
1
|
<template>
|
|
2
|
+
<div v-if="size === 'sm'">
|
|
3
|
+
<h3 class="text-sm font-bold m-0">
|
|
4
|
+
{{ title }}
|
|
5
|
+
</h3>
|
|
6
|
+
<div class="flex flex-wrap items-center">
|
|
7
|
+
<ContentLoader
|
|
8
|
+
v-if="summary === null"
|
|
9
|
+
:width="39"
|
|
10
|
+
:height="24"
|
|
11
|
+
:speed="2"
|
|
12
|
+
primary-color="#f3f3f3"
|
|
13
|
+
secondary-color="#ecebeb"
|
|
14
|
+
>
|
|
15
|
+
<rect
|
|
16
|
+
x="0"
|
|
17
|
+
y="0"
|
|
18
|
+
rx="3"
|
|
19
|
+
ry="3"
|
|
20
|
+
width="39"
|
|
21
|
+
height="24"
|
|
22
|
+
/>
|
|
23
|
+
</ContentLoader>
|
|
24
|
+
<p
|
|
25
|
+
v-else
|
|
26
|
+
class="font-extrabold leading-none m-0"
|
|
27
|
+
>
|
|
28
|
+
{{ summarize(summary, 2) }}
|
|
29
|
+
</p>
|
|
30
|
+
<ContentLoader
|
|
31
|
+
v-if="data === null"
|
|
32
|
+
:width="77"
|
|
33
|
+
:height="19"
|
|
34
|
+
:speed="2"
|
|
35
|
+
primary-color="#f3f3f3"
|
|
36
|
+
secondary-color="#ecebeb"
|
|
37
|
+
class="ml-2"
|
|
38
|
+
>
|
|
39
|
+
<rect
|
|
40
|
+
x="0"
|
|
41
|
+
y="0"
|
|
42
|
+
rx="3"
|
|
43
|
+
ry="3"
|
|
44
|
+
width="77"
|
|
45
|
+
height="19"
|
|
46
|
+
/>
|
|
47
|
+
</ContentLoader>
|
|
48
|
+
<div
|
|
49
|
+
v-else-if="changesThisYear"
|
|
50
|
+
class="ml-2"
|
|
51
|
+
>
|
|
52
|
+
<SmallChart
|
|
53
|
+
:type
|
|
54
|
+
:data
|
|
55
|
+
:last-with-low-emphasis="true"
|
|
56
|
+
:show-axis-label="false"
|
|
57
|
+
:height="19"
|
|
58
|
+
:width="77"
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
<template v-if="lastValue && lastMonth">
|
|
63
|
+
<p class="mt-1 mb-0 text-xs">
|
|
64
|
+
{{ $t('depuis juillet 2022') }}
|
|
65
|
+
</p>
|
|
66
|
+
<p class="mt-1 mb-0 text-xs text-success-darkest">
|
|
67
|
+
<strong>
|
|
68
|
+
+ {{ summarize(lastValue, 2) }}
|
|
69
|
+
</strong>
|
|
70
|
+
{{ t(" en ") }}
|
|
71
|
+
{{ formatDate(lastMonth, { dateStyle: undefined, year: 'numeric', month: 'short', day: undefined }) }}
|
|
72
|
+
</p>
|
|
73
|
+
</template>
|
|
74
|
+
</div>
|
|
2
75
|
<div
|
|
76
|
+
v-else
|
|
3
77
|
:class="{
|
|
4
78
|
'text-gray-medium': !changesThisYear && !summary,
|
|
5
79
|
}"
|
|
6
80
|
>
|
|
7
|
-
<h3 class="
|
|
81
|
+
<h3 class="text-sm m-0">
|
|
8
82
|
{{ title }}
|
|
9
83
|
</h3>
|
|
10
|
-
<div class="
|
|
84
|
+
<div class="flex flex-wrap items-center">
|
|
11
85
|
<ContentLoader
|
|
12
86
|
v-if="summary === null"
|
|
13
87
|
:width="92"
|
|
@@ -27,7 +101,7 @@
|
|
|
27
101
|
</ContentLoader>
|
|
28
102
|
<p
|
|
29
103
|
v-else
|
|
30
|
-
class="
|
|
104
|
+
class="font-extrabold text-[2rem] leading-none m-0"
|
|
31
105
|
>
|
|
32
106
|
{{ summarize(summary, 2) }}
|
|
33
107
|
</p>
|
|
@@ -38,7 +112,7 @@
|
|
|
38
112
|
:speed="2"
|
|
39
113
|
primary-color="#f3f3f3"
|
|
40
114
|
secondary-color="#ecebeb"
|
|
41
|
-
class="
|
|
115
|
+
class="ml-2"
|
|
42
116
|
>
|
|
43
117
|
<rect
|
|
44
118
|
x="0"
|
|
@@ -51,7 +125,7 @@
|
|
|
51
125
|
</ContentLoader>
|
|
52
126
|
<div
|
|
53
127
|
v-else-if="changesThisYear"
|
|
54
|
-
class="
|
|
128
|
+
class="ml-2"
|
|
55
129
|
>
|
|
56
130
|
<SmallChart
|
|
57
131
|
:type
|
|
@@ -62,12 +136,12 @@
|
|
|
62
136
|
</div>
|
|
63
137
|
<p
|
|
64
138
|
v-if="lastValue && lastMonth"
|
|
65
|
-
class="
|
|
139
|
+
class="mt-2 font-normal text-transform-none fr-badge fr-badge--no-icon fr-badge--success"
|
|
66
140
|
>
|
|
67
|
-
<strong class="
|
|
141
|
+
<strong class="mr-1">
|
|
68
142
|
+ {{ summarize(lastValue, 2) }}
|
|
69
143
|
</strong>
|
|
70
|
-
{{ t("
|
|
144
|
+
{{ t(" en ") }}
|
|
71
145
|
{{ formatDate(lastMonth, { dateStyle: undefined, year: 'numeric', month: 'short', day: undefined }) }}
|
|
72
146
|
</p>
|
|
73
147
|
</div>
|
|
@@ -77,7 +151,7 @@
|
|
|
77
151
|
import { computed } from 'vue'
|
|
78
152
|
import { useI18n } from 'vue-i18n'
|
|
79
153
|
import { ContentLoader } from 'vue-content-loader'
|
|
80
|
-
import {
|
|
154
|
+
import { useFormatDate } from '../functions/dates'
|
|
81
155
|
import { summarize } from '../functions/helpers'
|
|
82
156
|
import SmallChart from './SmallChart.vue'
|
|
83
157
|
|
|
@@ -85,10 +159,12 @@ const props = defineProps<{
|
|
|
85
159
|
title: string
|
|
86
160
|
data: Record<string, number> | null
|
|
87
161
|
type: 'line' | 'bar'
|
|
88
|
-
|
|
162
|
+
size?: 'sm'
|
|
163
|
+
summary?: number | null
|
|
89
164
|
}>()
|
|
90
165
|
|
|
91
166
|
const { t } = useI18n()
|
|
167
|
+
const { formatDate } = useFormatDate()
|
|
92
168
|
|
|
93
169
|
const months = computed(() => props.data ? Object.keys(props.data) : [])
|
|
94
170
|
const values = computed(() => props.data ? Object.values(props.data) : [])
|
|
@@ -97,4 +173,10 @@ const lastMonthIndex = computed(() => lastMonth.value ? months.value.indexOf(las
|
|
|
97
173
|
const lastValue = computed(() => lastMonthIndex.value !== null ? values.value[lastMonthIndex.value] : null)
|
|
98
174
|
|
|
99
175
|
const changesThisYear = computed(() => Math.max(...values.value) > 0)
|
|
176
|
+
const summary = computed(() => {
|
|
177
|
+
if (props.summary !== undefined) return props.summary
|
|
178
|
+
if (!props.data) return null
|
|
179
|
+
|
|
180
|
+
return Object.values(props.data).reduce((a, b) => a + b, 0)
|
|
181
|
+
})
|
|
100
182
|
</script>
|
package/src/config.ts
CHANGED
|
@@ -8,15 +8,19 @@ export type PluginConfig = {
|
|
|
8
8
|
devApiKey?: string | null
|
|
9
9
|
staticUrl: string
|
|
10
10
|
datasetQualityGuideUrl?: string
|
|
11
|
-
schemaValidataUrl
|
|
12
|
-
schemaDocumentationUrl
|
|
11
|
+
schemaValidataUrl?: string
|
|
12
|
+
schemaDocumentationUrl?: string
|
|
13
13
|
tabularApiUrl?: string
|
|
14
14
|
tabularApiPageSize?: number
|
|
15
15
|
tabularAllowRemote?: boolean
|
|
16
16
|
tabularApiDataserviceId?: string
|
|
17
|
+
pmtilesViewerBaseUrl?: string | null // Base URL of a pmtiles viewer (ex: https://pmtiles.io/#url=)
|
|
17
18
|
customUseFetch?: UseFetchFunction | null
|
|
18
19
|
textClamp?: string | Component | null
|
|
19
20
|
appLink?: Component | null
|
|
21
|
+
maxJsonPreviewSize?: number // Maximum size of JSON to preview in characters
|
|
22
|
+
maxPdfPreviewSize?: number // Maximum size of PDF to preview in bytes
|
|
23
|
+
maxXmlPreviewSize?: number // Maximum size of XML to preview in characters
|
|
20
24
|
i18n?: {
|
|
21
25
|
global: {
|
|
22
26
|
mergeLocaleMessage: (locale: string, messages: unknown) => void
|
package/src/functions/api.ts
CHANGED
|
@@ -10,7 +10,7 @@ export async function useFetch<DataT, ErrorT = never>(
|
|
|
10
10
|
): Promise<AsyncData<DataT, ErrorT>> {
|
|
11
11
|
const config = useComponentsConfig()
|
|
12
12
|
|
|
13
|
-
const {
|
|
13
|
+
const { locale } = useI18n()
|
|
14
14
|
|
|
15
15
|
if (config.customUseFetch) {
|
|
16
16
|
return await config.customUseFetch(url, options)
|
|
@@ -20,7 +20,7 @@ export async function useFetch<DataT, ErrorT = never>(
|
|
|
20
20
|
const error: Ref<ErrorT | null> = ref(null)
|
|
21
21
|
const status = ref<AsyncDataRequestStatus>('idle')
|
|
22
22
|
|
|
23
|
-
const execute = async (
|
|
23
|
+
const execute = async (_opts?: AsyncDataExecuteOptions) => {
|
|
24
24
|
const urlValue = toValue(url)
|
|
25
25
|
if (!urlValue) return
|
|
26
26
|
status.value = 'pending'
|
|
@@ -42,26 +42,26 @@ export async function useFetch<DataT, ErrorT = never>(
|
|
|
42
42
|
options.params['lang'] = locale.value
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
|
-
async onResponseError(
|
|
45
|
+
async onResponseError() {
|
|
46
46
|
// TODO redirect to login outside Nuxt?
|
|
47
47
|
// if (response.status === 401) {
|
|
48
48
|
// await nuxtApp.runWithContext(() => navigateTo(localePath('/login')))
|
|
49
49
|
// }
|
|
50
50
|
|
|
51
|
-
let message
|
|
52
|
-
try {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
catch (e) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
51
|
+
// let message
|
|
52
|
+
// try {
|
|
53
|
+
// if ('error' in response._data) {
|
|
54
|
+
// message = response._data.error
|
|
55
|
+
// }
|
|
56
|
+
// else if ('message' in response._data) {
|
|
57
|
+
// message = response._data.message
|
|
58
|
+
// }
|
|
59
|
+
// }
|
|
60
|
+
// catch (e) {
|
|
61
|
+
// console.error(e)
|
|
62
|
+
// // eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
63
|
+
// message = t(`L'API a retourné une erreur inattendue`)
|
|
64
|
+
// }
|
|
65
65
|
|
|
66
66
|
// TODO Toast outside Nuxt
|
|
67
67
|
// toast.error(message)
|
|
@@ -82,7 +82,7 @@ export async function useFetch<DataT, ErrorT = never>(
|
|
|
82
82
|
|
|
83
83
|
return {
|
|
84
84
|
data,
|
|
85
|
-
refresh: async (
|
|
85
|
+
refresh: async (_opts?: AsyncDataExecuteOptions) => {
|
|
86
86
|
execute()
|
|
87
87
|
},
|
|
88
88
|
execute,
|