@datagouv/components-next 1.0.2-dev.152 → 1.0.2-dev.154
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/{Datafair.client-MNAP27A-.js → Datafair.client-CUK3pS6u.js} +1 -1
- package/dist/{JsonPreview.client-C3dG7a_r.js → JsonPreview.client-DXCVNsnV.js} +2 -2
- package/dist/{MapContainer.client-B5YiX3NO.js → MapContainer.client-BTwgsANA.js} +2 -2
- package/dist/{PdfPreview.client-CERt2f57.js → PdfPreview.client-B-W-HknR.js} +2 -2
- package/dist/{Pmtiles.client-D8ug3toD.js → Pmtiles.client-CCNaGHFD.js} +1 -1
- package/dist/{PreviewWrapper.vue_vue_type_script_setup_true_lang--KItAayB.js → PreviewWrapper.vue_vue_type_script_setup_true_lang-Cu7pYpdZ.js} +1 -1
- package/dist/{XmlPreview.client-1IAoOczZ.js → XmlPreview.client-CalqyfB_.js} +3 -3
- package/dist/components-next.js +99 -98
- package/dist/components.css +1 -1
- package/dist/{index-D86-g3i7.js → index-CeKxF_3e.js} +1 -1
- package/dist/{leaflet-src-BjtNBpa8.js → leaflet-src-zZByibj1.js} +1 -1
- package/dist/{main-BdrYl6mr.js → main-B4HZYPjy.js} +9706 -9655
- package/dist/{vue3-xml-viewer.common-BE7Fgsqp.js → vue3-xml-viewer.common-omEYjVTS.js} +1 -1
- package/package.json +1 -1
- package/src/components/TabularExplorer/TabularCell.vue +9 -0
- package/src/components/TabularExplorer/TabularCellPopover.vue +1 -1
- package/src/components/TabularExplorer/TabularExplorer.vue +57 -14
- package/src/components/TabularExplorer/TabularFilterContent.vue +3 -3
- package/src/components/TabularExplorer/types.ts +1 -1
- package/src/functions/tabular.ts +26 -1
package/package.json
CHANGED
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
class="inline-block rounded font-medium px-2 py-0.5 text-xs max-w-full truncate"
|
|
21
21
|
:style="categoryBadgeStyle ? { backgroundColor: categoryBadgeStyle.backgroundColor, color: categoryBadgeStyle.color } : undefined"
|
|
22
22
|
>{{ value }}</span>
|
|
23
|
+
<span
|
|
24
|
+
v-else-if="columnType === 'number' && noNumberFormat"
|
|
25
|
+
:class="compact ? 'font-mono tabular-nums text-xs text-gray-title' : ''"
|
|
26
|
+
>{{ value }}</span>
|
|
23
27
|
<span
|
|
24
28
|
v-else-if="columnType === 'number'"
|
|
25
29
|
:class="compact ? 'font-mono tabular-nums text-xs text-gray-title' : ''"
|
|
@@ -28,6 +32,10 @@
|
|
|
28
32
|
v-else-if="columnType === 'date'"
|
|
29
33
|
:class="compact ? 'font-mono tabular-nums text-xs text-gray-title' : ''"
|
|
30
34
|
>{{ formatCellDate(value) }}</span>
|
|
35
|
+
<span
|
|
36
|
+
v-else-if="columnType === 'year'"
|
|
37
|
+
:class="compact ? 'font-mono tabular-nums text-xs text-gray-title' : ''"
|
|
38
|
+
>{{ value }}</span>
|
|
31
39
|
<span
|
|
32
40
|
v-else
|
|
33
41
|
class="text-gray-title truncate block text-xs"
|
|
@@ -44,6 +52,7 @@ defineProps<{
|
|
|
44
52
|
columnType: ColumnType
|
|
45
53
|
categoryBadgeStyle?: BadgeStyle
|
|
46
54
|
compact?: boolean
|
|
55
|
+
noNumberFormat?: boolean
|
|
47
56
|
}>()
|
|
48
57
|
|
|
49
58
|
const { t } = useTranslation()
|
|
@@ -128,7 +128,7 @@ function filterByValue() {
|
|
|
128
128
|
const val = String(cell.value.value ?? '')
|
|
129
129
|
const col = cell.value.column
|
|
130
130
|
const existing = filters.value[col] ?? {}
|
|
131
|
-
if (cell.value.columnType === 'categorical' || cell.value.columnType === 'text' || cell.value.columnType === 'date') {
|
|
131
|
+
if (cell.value.columnType === 'categorical' || cell.value.columnType === 'text' || cell.value.columnType === 'date' || cell.value.columnType === 'year') {
|
|
132
132
|
const current = existing.in ?? []
|
|
133
133
|
if (!current.includes(val)) {
|
|
134
134
|
filters.value = { ...filters.value, [col]: { ...existing, in: [...current, val] } }
|
|
@@ -374,22 +374,38 @@
|
|
|
374
374
|
<tr
|
|
375
375
|
v-for="(row, i) in allRows"
|
|
376
376
|
:key="row.__id ?? i"
|
|
377
|
-
class="border-b border-gray-default even:bg-gray-lowest-2 hover:bg-gray-100
|
|
378
|
-
@click="emit('rowClick', row)"
|
|
377
|
+
class="border-b border-gray-default even:bg-gray-lowest-2 hover:bg-gray-100"
|
|
379
378
|
>
|
|
380
379
|
<td
|
|
381
380
|
v-for="col in displayedColumns"
|
|
382
381
|
:key="col"
|
|
383
382
|
data-cell
|
|
384
|
-
class="p-2 align-middle whitespace-nowrap border-r border-gray-default last:border-r-0 overflow-hidden
|
|
385
|
-
:class="
|
|
383
|
+
class="p-2 align-middle whitespace-nowrap border-r border-gray-default last:border-r-0 overflow-hidden"
|
|
384
|
+
:class="[
|
|
385
|
+
!props.disablePopover && 'cursor-pointer',
|
|
386
|
+
{ 'text-right font-mono tabular-nums text-sm': getColumnType(col) === 'number' || getColumnType(col) === 'date' || getColumnType(col) === 'year' },
|
|
387
|
+
]"
|
|
386
388
|
:style="columnWidths[col] ? { maxWidth: columnWidths[col] + 'px' } : { maxWidth: '300px' }"
|
|
387
389
|
@click="onCellClick(col, row[col], $event)"
|
|
388
390
|
>
|
|
391
|
+
<a
|
|
392
|
+
v-if="linkColumns?.has(col)"
|
|
393
|
+
:href="getRowHref(row) ?? ''"
|
|
394
|
+
class="link"
|
|
395
|
+
>
|
|
396
|
+
<TabularCell
|
|
397
|
+
:value="row[col]"
|
|
398
|
+
:column-type="getColumnType(col)"
|
|
399
|
+
:category-badge-style="getColumnType(col) === 'categorical' ? getCategoryBadgeStyle(col, String(row[col])) : undefined"
|
|
400
|
+
:no-number-format="props.noFormatColumns?.includes(col)"
|
|
401
|
+
/>
|
|
402
|
+
</a>
|
|
389
403
|
<TabularCell
|
|
404
|
+
v-else
|
|
390
405
|
:value="row[col]"
|
|
391
406
|
:column-type="getColumnType(col)"
|
|
392
407
|
:category-badge-style="getColumnType(col) === 'categorical' ? getCategoryBadgeStyle(col, String(row[col])) : undefined"
|
|
408
|
+
:no-number-format="props.noFormatColumns?.includes(col)"
|
|
393
409
|
/>
|
|
394
410
|
</td>
|
|
395
411
|
</tr>
|
|
@@ -429,9 +445,8 @@
|
|
|
429
445
|
<div
|
|
430
446
|
v-for="(row, i) in allRows"
|
|
431
447
|
:key="row.__id ?? i"
|
|
432
|
-
class="border border-gray-default rounded-lg p-3 space-y-2
|
|
448
|
+
class="border border-gray-default rounded-lg p-3 space-y-2"
|
|
433
449
|
:class="i % 2 === 1 ? 'bg-gray-lowest-2' : 'bg-white'"
|
|
434
|
-
@click="emit('rowClick', row)"
|
|
435
450
|
>
|
|
436
451
|
<div
|
|
437
452
|
v-for="col in mobileVisibleFields(i)"
|
|
@@ -454,11 +469,25 @@
|
|
|
454
469
|
class="min-w-0 pl-4 cursor-pointer"
|
|
455
470
|
@click="onCellClick(col, row[col], $event)"
|
|
456
471
|
>
|
|
472
|
+
<a
|
|
473
|
+
v-if="linkColumns?.has(col)"
|
|
474
|
+
:href="getRowHref(row) ?? ''"
|
|
475
|
+
>
|
|
476
|
+
<TabularCell
|
|
477
|
+
:value="row[col]"
|
|
478
|
+
:column-type="getColumnType(col)"
|
|
479
|
+
:category-badge-style="getColumnType(col) === 'categorical' ? getCategoryBadgeStyle(col, String(row[col])) : undefined"
|
|
480
|
+
compact
|
|
481
|
+
:no-number-format="props.noFormatColumns?.includes(col)"
|
|
482
|
+
/>
|
|
483
|
+
</a>
|
|
457
484
|
<TabularCell
|
|
485
|
+
v-else
|
|
458
486
|
:value="row[col]"
|
|
459
487
|
:column-type="getColumnType(col)"
|
|
460
488
|
:category-badge-style="getColumnType(col) === 'categorical' ? getCategoryBadgeStyle(col, String(row[col])) : undefined"
|
|
461
489
|
compact
|
|
490
|
+
:no-number-format="props.noFormatColumns?.includes(col)"
|
|
462
491
|
/>
|
|
463
492
|
</div>
|
|
464
493
|
</div>
|
|
@@ -618,7 +647,7 @@ import { useFetch } from '../../functions/api'
|
|
|
618
647
|
import { useComponentsConfig } from '../../config'
|
|
619
648
|
import { useTranslation } from '../../composables/useTranslation'
|
|
620
649
|
import { injectTabularProfile } from '../../composables/useTabularProfile'
|
|
621
|
-
import { buildTypeConfig, buildFormatConfig, humanizeFormat, GENERIC_FORMATS, hasFilterForColumn as _hasFilterForColumn, isTruthy, isFalsy, resolveColumnType } from '../../functions/tabular'
|
|
650
|
+
import { buildTypeConfig, buildFormatConfig, humanizeFormat, GENERIC_FORMATS, hasFilterForColumn as _hasFilterForColumn, isTruthy, isFalsy, resolveColumnType, buildGlobalSearchConditions } from '../../functions/tabular'
|
|
622
651
|
import ClientOnly from '../ClientOnly.vue'
|
|
623
652
|
import SimpleBanner from '../SimpleBanner.vue'
|
|
624
653
|
import BrandedButton from '../BrandedButton.vue'
|
|
@@ -637,8 +666,10 @@ const props = defineProps<{
|
|
|
637
666
|
// the host's padding. Used on the standalone explore page where the table
|
|
638
667
|
// should span the whole screen while the toolbar stays inside the container.
|
|
639
668
|
fullBleed?: boolean
|
|
640
|
-
// When set, searches across multiple
|
|
641
|
-
//
|
|
669
|
+
// When set, searches across multiple columns using the Tabular API's or(...)
|
|
670
|
+
// parameter. Text and categorical columns get a __contains filter; number
|
|
671
|
+
// columns get a __exact filter (since __contains is not supported for numbers
|
|
672
|
+
// by the API). Date and boolean columns are excluded.
|
|
642
673
|
// Note: combined via AND with any existing column-specific `contains` filters,
|
|
643
674
|
// so it acts as an additional narrowing constraint, not a replacement.
|
|
644
675
|
globalSearch?: string
|
|
@@ -648,11 +679,23 @@ const props = defineProps<{
|
|
|
648
679
|
// Initial filters applied on mount, e.g. { 'Administration': { contains: 'Ministère' } }.
|
|
649
680
|
// Used when navigating from the detail page badge with query params.
|
|
650
681
|
initialFilters?: Record<string, ColumnFilters>
|
|
682
|
+
// When set, renders <a> tags inside the specified column cells for native
|
|
683
|
+
// browser UX (hover URL, ctrl+click, middle-click).
|
|
684
|
+
rowHref?: { columns: string[], href: (row: TabularRow) => string }
|
|
685
|
+
// Columns listed here render their values raw (no number formatting).
|
|
686
|
+
// Useful for identifier columns.
|
|
687
|
+
noFormatColumns?: string[]
|
|
651
688
|
}>()
|
|
652
689
|
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
|
|
690
|
+
const rowHrefFn = computed(() => props.rowHref?.href ?? null)
|
|
691
|
+
|
|
692
|
+
function getRowHref(row: TabularRow): string | null {
|
|
693
|
+
return rowHrefFn.value?.(row) ?? null
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const linkColumns = computed(() =>
|
|
697
|
+
props.rowHref ? new Set(props.rowHref.columns) : null,
|
|
698
|
+
)
|
|
656
699
|
|
|
657
700
|
const { t } = useTranslation()
|
|
658
701
|
const config = useComponentsConfig()
|
|
@@ -712,8 +755,8 @@ const dataQuery = computed(() => {
|
|
|
712
755
|
}
|
|
713
756
|
}
|
|
714
757
|
if (props.globalSearch && profileData.value?.profile) {
|
|
715
|
-
const
|
|
716
|
-
q.or = '(' +
|
|
758
|
+
const conditions = buildGlobalSearchConditions(allColumns.value, getColumnType, props.globalSearch)
|
|
759
|
+
q.or = '(' + conditions.join(',') + ')'
|
|
717
760
|
}
|
|
718
761
|
return q
|
|
719
762
|
})
|
|
@@ -154,14 +154,14 @@
|
|
|
154
154
|
|
|
155
155
|
<!-- Number range -->
|
|
156
156
|
<form
|
|
157
|
-
v-if="columnType === 'number' && columnProfile"
|
|
157
|
+
v-if="(columnType === 'number' || columnType === 'year') && columnProfile"
|
|
158
158
|
class="px-3 py-2 border-b border-black/10 space-y-2"
|
|
159
159
|
@submit.prevent="applyRange"
|
|
160
160
|
>
|
|
161
161
|
<div class="flex items-center gap-2 text-xs text-gray-plain">
|
|
162
|
-
<span class="tabular-nums">{{ formatNumber(profileMin) }}</span>
|
|
162
|
+
<span class="tabular-nums">{{ columnType === 'year' ? profileMin : formatNumber(profileMin) }}</span>
|
|
163
163
|
<span class="text-gray-medium">—</span>
|
|
164
|
-
<span class="tabular-nums">{{ formatNumber(profileMax) }}</span>
|
|
164
|
+
<span class="tabular-nums">{{ columnType === 'year' ? profileMax : formatNumber(profileMax) }}</span>
|
|
165
165
|
</div>
|
|
166
166
|
<div class="flex items-center gap-2">
|
|
167
167
|
<input
|
|
@@ -62,7 +62,7 @@ export type TabularTopValue = {
|
|
|
62
62
|
count: number
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
export type ColumnType = 'number' | 'categorical' | 'text' | 'date' | 'boolean'
|
|
65
|
+
export type ColumnType = 'number' | 'categorical' | 'text' | 'date' | 'boolean' | 'year'
|
|
66
66
|
|
|
67
67
|
export type ColumnFilters = {
|
|
68
68
|
in?: string[]
|
package/src/functions/tabular.ts
CHANGED
|
@@ -42,12 +42,13 @@ export function buildTypeConfig(t: TranslationFunction): Record<ColumnType, Type
|
|
|
42
42
|
text: { icon: RiText, label: t('Texte') },
|
|
43
43
|
date: { icon: RiCalendarLine, label: t('Date') },
|
|
44
44
|
boolean: { icon: RiCheckboxLine, label: t('Booléen') },
|
|
45
|
+
year: { icon: RiCalendarLine, label: t('Année') },
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
export function resolveColumnType(colInfo: { python_type: string, format?: string }, isCategorical: boolean): ColumnType {
|
|
50
|
+
if (colInfo.format === 'year') return 'year'
|
|
49
51
|
if (['int', 'float'].includes(colInfo.python_type)) return 'number'
|
|
50
|
-
if (colInfo.format === 'year') return 'date'
|
|
51
52
|
if (['date', 'datetime'].includes(colInfo.python_type)) return 'date'
|
|
52
53
|
if (colInfo.python_type === 'bool') return 'boolean'
|
|
53
54
|
if (isCategorical) return 'categorical'
|
|
@@ -158,6 +159,30 @@ export function useFormatTabular() {
|
|
|
158
159
|
const TRUTHY_VALUES = ['true', '1', 'oui', 'yes']
|
|
159
160
|
const FALSY_VALUES = ['false', '0', 'non', 'no']
|
|
160
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Builds the OR conditions for global search across all columns.
|
|
164
|
+
* Text and categorical columns get a `__contains` filter; number columns
|
|
165
|
+
* get a `__exact` filter only when the search value is numeric.
|
|
166
|
+
* Date and boolean columns are excluded.
|
|
167
|
+
*/
|
|
168
|
+
export function buildGlobalSearchConditions(
|
|
169
|
+
allColumns: string[],
|
|
170
|
+
getColumnType: (col: string) => ColumnType,
|
|
171
|
+
searchValue: string,
|
|
172
|
+
): string[] {
|
|
173
|
+
const conditions: string[] = []
|
|
174
|
+
for (const col of allColumns) {
|
|
175
|
+
const type = getColumnType(col)
|
|
176
|
+
if (type === 'text' || type === 'categorical') {
|
|
177
|
+
conditions.push(col + '__contains.' + encodeURIComponent(searchValue))
|
|
178
|
+
}
|
|
179
|
+
else if (type === 'number' && searchValue.length > 0 && isFinite(Number(searchValue))) {
|
|
180
|
+
conditions.push(col + '__exact.' + encodeURIComponent(searchValue))
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return conditions
|
|
184
|
+
}
|
|
185
|
+
|
|
161
186
|
export function isTruthy(value: unknown): boolean {
|
|
162
187
|
if (typeof value === 'boolean') return value
|
|
163
188
|
if (typeof value === 'string') return TRUTHY_VALUES.includes(value.toLowerCase())
|