@ouestfrance/sipa-bms-ui 8.5.4 → 8.7.0
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/components/form/BmsAutocomplete.vue.d.ts +2 -0
- package/dist/components/form/BmsInputBooleanCheckbox.vue.d.ts +1 -1
- package/dist/components/form/BmsInputCheckboxGroup.vue.d.ts +2 -2
- package/dist/components/form/BmsInputCode.vue.d.ts +2 -2
- package/dist/components/form/BmsInputNumber.vue.d.ts +2 -2
- package/dist/components/form/BmsInputRadio.vue.d.ts +2 -2
- package/dist/components/form/BmsInputText.vue.d.ts +24 -22
- package/dist/components/form/BmsMultiSelect.vue.d.ts +3 -4
- package/dist/components/form/BmsSearch.vue.d.ts +28 -24
- package/dist/components/form/BmsSelect.vue.d.ts +7 -17
- package/dist/components/form/RawAutocomplete.vue.d.ts +17 -21
- package/dist/components/form/RawInputText.vue.d.ts +9 -9
- package/dist/components/form/RawSelect.vue.d.ts +30 -0
- package/dist/components/navigation/UiTenantSwitcher.vue.d.ts +28 -24
- package/dist/components/table/BmsServerTable.vue.d.ts +18 -0
- package/dist/components/table/BmsTable.vue.d.ts +18 -1
- package/dist/components/table/BmsTableFilters.vue.d.ts +47 -25
- package/dist/components/table/UiBmsTableCell.vue.d.ts +7 -0
- package/dist/composables/search.composable.d.ts +1 -0
- package/dist/models/table.model.d.ts +6 -0
- package/dist/plugins/field/FieldDatalist.vue.d.ts +2 -0
- package/dist/plugins/field/field-component.model.d.ts +2 -2
- package/dist/sipa-bms-ui.css +173 -126
- package/dist/sipa-bms-ui.es.js +4875 -4484
- package/dist/sipa-bms-ui.es.js.map +1 -1
- package/dist/sipa-bms-ui.umd.js +4882 -4490
- package/dist/sipa-bms-ui.umd.js.map +1 -1
- package/package.json +11 -11
- package/src/components/form/BmsAutocomplete.vue +3 -0
- package/src/components/form/BmsInputNumber.spec.ts +26 -0
- package/src/components/form/BmsInputNumber.stories.js +20 -3
- package/src/components/form/BmsInputNumber.vue +36 -4
- package/src/components/form/BmsInputRadio.vue +1 -1
- package/src/components/form/BmsInputText.spec.ts +25 -0
- package/src/components/form/BmsInputText.stories.js +28 -3
- package/src/components/form/BmsInputText.vue +73 -12
- package/src/components/form/BmsMultiSelect.vue +67 -30
- package/src/components/form/BmsSelect.vue +60 -57
- package/src/components/form/RawAutocomplete.spec.ts +0 -8
- package/src/components/form/RawAutocomplete.vue +42 -24
- package/src/components/form/RawInputText.vue +14 -21
- package/src/components/form/RawSelect.vue +111 -0
- package/src/components/table/BmsServerTable.vue +18 -3
- package/src/components/table/BmsTable.vue +15 -2
- package/src/components/table/BmsTableFilters.vue +19 -7
- package/src/components/table/UiBmsTable.stories.js +37 -0
- package/src/components/table/UiBmsTableCell.vue +25 -0
- package/src/components/table/UiBmsTableRow.stories.js +30 -0
- package/src/components/table/UiBmsTableRow.vue +3 -2
- package/src/components/utils/BmsRelativeTime.vue +1 -1
- package/src/composables/search.composable.spec.ts +75 -0
- package/src/composables/search.composable.ts +54 -11
- package/src/models/table.model.ts +7 -0
- package/src/plugins/field/FieldComponent.vue +6 -4
- package/src/plugins/field/FieldDatalist.stories.js +0 -9
- package/src/plugins/field/FieldDatalist.vue +16 -13
- package/src/plugins/field/field-component.model.ts +2 -2
- package/src/showroom/pages/autocomplete.vue +22 -1
- package/src/showroom/pages/server-table.vue +53 -22
- package/src/showroom/pages/table.vue +46 -21
- package/dist/plugins/field/FieldDatalist.spec.d.ts +0 -1
- package/src/plugins/field/FieldDatalist.spec.ts +0 -35
|
@@ -4,7 +4,10 @@ import BmsPagination from '@/components/table/BmsPagination.vue';
|
|
|
4
4
|
import UiBmsTable from '@/components/table/UiBmsTable.vue';
|
|
5
5
|
import UiFilterButton from '@/components/table/UiFilterButton.vue';
|
|
6
6
|
import { usePagination } from '@/composables/pagination.composable';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
reflectFiltersToPath,
|
|
9
|
+
useSearch,
|
|
10
|
+
} from '@/composables/search.composable';
|
|
8
11
|
import { useSort } from '@/composables/sort.composable';
|
|
9
12
|
import {
|
|
10
13
|
Filter,
|
|
@@ -151,12 +154,15 @@ const {
|
|
|
151
154
|
isSavedFilterActive,
|
|
152
155
|
resetFilters,
|
|
153
156
|
selectSavedFilter,
|
|
157
|
+
updateFiltersFromProps,
|
|
154
158
|
} = useSearch(props.persistent, props.filters, props.defaultFiltersOpened);
|
|
155
159
|
|
|
156
160
|
const emits = defineEmits<{
|
|
157
161
|
deleteSavedFilter: [value: SavedFilter];
|
|
158
162
|
saveFilter: [value: SavedFilter];
|
|
159
163
|
'update:selectMode': [selectMode: SelectMode];
|
|
164
|
+
filterInput: [{ filterKey: string; value: any; e: InputEvent }];
|
|
165
|
+
filterChange: [{ filterKey: string; value: any }];
|
|
160
166
|
}>();
|
|
161
167
|
|
|
162
168
|
const loading = ref<boolean>(false);
|
|
@@ -196,6 +202,14 @@ watch([currentPage, isMounting], () => {
|
|
|
196
202
|
fetchData();
|
|
197
203
|
}
|
|
198
204
|
});
|
|
205
|
+
watch(
|
|
206
|
+
() => props.filters,
|
|
207
|
+
() => {
|
|
208
|
+
updateFiltersFromProps(props.filters);
|
|
209
|
+
reflectFiltersToPath(filters.value);
|
|
210
|
+
},
|
|
211
|
+
{ deep: true },
|
|
212
|
+
);
|
|
199
213
|
|
|
200
214
|
watch(
|
|
201
215
|
[() => filters.value, () => sort.value, size, search],
|
|
@@ -324,8 +338,9 @@ const onSelectAll = () => emits('update:selectMode', SelectMode.ALL);
|
|
|
324
338
|
:canSaveFilters="canSaveFilters"
|
|
325
339
|
@saveFilter="onSaveFilter"
|
|
326
340
|
@resetFilters="resetFilters"
|
|
327
|
-
|
|
328
|
-
|
|
341
|
+
@filterInput="(filterEvent) => emits('filterInput', filterEvent)"
|
|
342
|
+
@filterChange="(filterEvent) => emits('filterChange', filterEvent)"
|
|
343
|
+
/>
|
|
329
344
|
</div>
|
|
330
345
|
</Transition>
|
|
331
346
|
</template>
|
|
@@ -85,11 +85,14 @@ const {
|
|
|
85
85
|
resetFilters,
|
|
86
86
|
selectSavedFilter,
|
|
87
87
|
resetAllFilters,
|
|
88
|
+
updateFiltersFromProps,
|
|
88
89
|
} = useSearch(props.persistent, props.filters, props.defaultFiltersOpened);
|
|
89
90
|
|
|
90
91
|
const emits = defineEmits<{
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
deleteSavedFilter: [value: SavedFilter];
|
|
93
|
+
saveFilter: [value: SavedFilter];
|
|
94
|
+
filterInput: [{ filterKey: string; value: any; e: InputEvent }];
|
|
95
|
+
filterChange: [{ filterKey: string; value: any }];
|
|
93
96
|
}>();
|
|
94
97
|
|
|
95
98
|
const selectedItems: Ref<unknown[]> = defineModel('selectedItems', {
|
|
@@ -116,6 +119,14 @@ const getFilteredItems = () => {
|
|
|
116
119
|
|
|
117
120
|
const isMounting = ref(true);
|
|
118
121
|
|
|
122
|
+
watch(
|
|
123
|
+
() => props.filters,
|
|
124
|
+
() => {
|
|
125
|
+
updateFiltersFromProps(props.filters);
|
|
126
|
+
},
|
|
127
|
+
{ deep: true },
|
|
128
|
+
);
|
|
129
|
+
|
|
119
130
|
watchEffect(
|
|
120
131
|
async () => {
|
|
121
132
|
isMounting.value =
|
|
@@ -237,6 +248,8 @@ const onSelectAll = () => {
|
|
|
237
248
|
:canSaveFilters="canSaveFilters"
|
|
238
249
|
@saveFilter="onSaveFilter"
|
|
239
250
|
@reset-filters="resetFilters"
|
|
251
|
+
@filterInput="(filterEvent) => emits('filterInput', filterEvent)"
|
|
252
|
+
@filterChange="(filterEvent) => emits('filterChange', filterEvent)"
|
|
240
253
|
/>
|
|
241
254
|
</div>
|
|
242
255
|
</Transition>
|
|
@@ -51,6 +51,14 @@
|
|
|
51
51
|
"
|
|
52
52
|
:valueTo="transformValueForComponent(filter?.valueTo, filter.type)"
|
|
53
53
|
:options="getFilterOptions(filter) as any"
|
|
54
|
+
@input="
|
|
55
|
+
(e: InputEvent) =>
|
|
56
|
+
$emits('filterInput', {
|
|
57
|
+
filterKey: filter.key,
|
|
58
|
+
value: (e.target as HTMLInputElement).value,
|
|
59
|
+
e,
|
|
60
|
+
})
|
|
61
|
+
"
|
|
54
62
|
/>
|
|
55
63
|
</span>
|
|
56
64
|
</div>
|
|
@@ -101,9 +109,11 @@ const props = withDefaults(
|
|
|
101
109
|
);
|
|
102
110
|
|
|
103
111
|
const $emits = defineEmits<{
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
112
|
+
'update:modelValue': [filters: Filter[]];
|
|
113
|
+
saveFilter: [value: SavedFilter];
|
|
114
|
+
resetFilters: [];
|
|
115
|
+
filterInput: [{ filterKey: string; value: any; e: InputEvent }];
|
|
116
|
+
filterChange: [{ filterKey: string; value: any }];
|
|
107
117
|
}>();
|
|
108
118
|
|
|
109
119
|
const savedModalOpened: Ref<boolean> = ref<boolean>(false);
|
|
@@ -112,10 +122,11 @@ const nameInput: Ref<typeof BmsInputText | null> = ref(null);
|
|
|
112
122
|
|
|
113
123
|
const onFilter = (key: string, inputValue: string, valueKey: any): void => {
|
|
114
124
|
let filter = props.modelValue.find((f) => f.key === key);
|
|
125
|
+
let updatedValue;
|
|
115
126
|
if (filter) {
|
|
116
127
|
switch (filter.type) {
|
|
117
128
|
case 'boolean':
|
|
118
|
-
|
|
129
|
+
updatedValue =
|
|
119
130
|
inputValue === 'true' ? true : inputValue === 'false' ? false : null;
|
|
120
131
|
break;
|
|
121
132
|
case 'select':
|
|
@@ -123,13 +134,14 @@ const onFilter = (key: string, inputValue: string, valueKey: any): void => {
|
|
|
123
134
|
case 'betweenDate':
|
|
124
135
|
case 'betweenDateTime':
|
|
125
136
|
case 'betweenNumber':
|
|
126
|
-
|
|
127
|
-
inputValue === 'null' ? null : inputValue;
|
|
137
|
+
updatedValue = inputValue === 'null' ? null : inputValue;
|
|
128
138
|
break;
|
|
129
139
|
default:
|
|
130
|
-
|
|
140
|
+
updatedValue = inputValue;
|
|
131
141
|
break;
|
|
132
142
|
}
|
|
143
|
+
filter[valueKey as keyof Filter] = updatedValue;
|
|
144
|
+
$emits('filterChange', { filterKey: key, value: updatedValue });
|
|
133
145
|
}
|
|
134
146
|
$emits('update:modelValue', props.modelValue);
|
|
135
147
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import UiBmsTable from '@/components/table/UiBmsTable.vue';
|
|
2
2
|
import { Save, Trash } from 'lucide-vue-next';
|
|
3
3
|
import BmsIconButton from '@/components/button/BmsIconButton.vue';
|
|
4
|
+
import { ColumnType } from '@/models/table.model';
|
|
4
5
|
|
|
5
6
|
import template from '@/documentation/template_internal_component.mdx';
|
|
6
7
|
|
|
@@ -379,6 +380,42 @@ SelectableDisabled.args = {
|
|
|
379
380
|
selectableDisabled: true,
|
|
380
381
|
totalSize: 2,
|
|
381
382
|
};
|
|
383
|
+
export const Typed = Template.bind({});
|
|
384
|
+
Typed.args = {
|
|
385
|
+
headers: [
|
|
386
|
+
{
|
|
387
|
+
label: 'Column boolean',
|
|
388
|
+
key: 'col1',
|
|
389
|
+
align: 'start',
|
|
390
|
+
columnType: ColumnType.Boolean,
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
label: 'Column date',
|
|
394
|
+
key: 'col2',
|
|
395
|
+
align: 'center',
|
|
396
|
+
columnType: ColumnType.Date,
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
label: 'Column 3',
|
|
400
|
+
key: 'col3',
|
|
401
|
+
align: 'end',
|
|
402
|
+
},
|
|
403
|
+
],
|
|
404
|
+
items: [
|
|
405
|
+
{
|
|
406
|
+
col1: true,
|
|
407
|
+
col2: '2024-10-10T12:00:00Z',
|
|
408
|
+
col3: 'Value3',
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
col1: false,
|
|
412
|
+
col2: '2025-01-01T18:00:00Z',
|
|
413
|
+
col3: 'Value3',
|
|
414
|
+
},
|
|
415
|
+
],
|
|
416
|
+
hasFilters: true,
|
|
417
|
+
totalSize: 2,
|
|
418
|
+
};
|
|
382
419
|
|
|
383
420
|
const TemplateWithActionColumn = (args) => ({
|
|
384
421
|
components: {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<template v-if="cell.columnType === ColumnType.Boolean">
|
|
3
|
+
<BmsChip v-if="cellValue" :color="ChipColor.Green">Oui</BmsChip>
|
|
4
|
+
<BmsChip v-else>Non</BmsChip>
|
|
5
|
+
</template>
|
|
6
|
+
<template v-else-if="cell.columnType === ColumnType.Date">
|
|
7
|
+
<BmsRelativeTime :relativeTo="relativeToTime" />
|
|
8
|
+
</template>
|
|
9
|
+
<template v-else>
|
|
10
|
+
{{ cellValue }}
|
|
11
|
+
</template>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script setup lang="ts">
|
|
15
|
+
import { ChipColor, ColumnType, TableHeader } from '@/models';
|
|
16
|
+
import _get from 'lodash/get';
|
|
17
|
+
import BmsChip from '../form/BmsChip.vue';
|
|
18
|
+
import { computed } from 'vue';
|
|
19
|
+
import BmsRelativeTime from '../utils/BmsRelativeTime.vue';
|
|
20
|
+
|
|
21
|
+
const props = defineProps<{ cell: TableHeader; item: any }>();
|
|
22
|
+
|
|
23
|
+
const cellValue = computed(() => _get(props.item, props.cell.key) ?? '');
|
|
24
|
+
const relativeToTime = computed(() => new Date(cellValue.value)?.getTime());
|
|
25
|
+
</script>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import UiBmsTableRow from '@/components/table/UiBmsTableRow.vue';
|
|
2
|
+
import { ColumnType } from '@/models/table.model';
|
|
2
3
|
|
|
3
4
|
export default {
|
|
4
5
|
title: 'Composants/table/UiBmsTableRow',
|
|
@@ -141,3 +142,32 @@ IsChildElement.args = {
|
|
|
141
142
|
},
|
|
142
143
|
},
|
|
143
144
|
};
|
|
145
|
+
|
|
146
|
+
export const Typed = Template.bind({});
|
|
147
|
+
Typed.args = {
|
|
148
|
+
headers: [
|
|
149
|
+
{
|
|
150
|
+
label: 'Column boolean',
|
|
151
|
+
key: 'col1',
|
|
152
|
+
align: 'start',
|
|
153
|
+
columnType: ColumnType.Boolean,
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
label: 'Column date',
|
|
157
|
+
key: 'col2',
|
|
158
|
+
align: 'center',
|
|
159
|
+
columnType: ColumnType.Date,
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
label: 'Column 3',
|
|
163
|
+
key: 'col3',
|
|
164
|
+
align: 'end',
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
selectedItems: [],
|
|
168
|
+
item: {
|
|
169
|
+
col1: true,
|
|
170
|
+
col2: '2022-01-01',
|
|
171
|
+
col3: 'Value3',
|
|
172
|
+
},
|
|
173
|
+
};
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
:row="item.childElement"
|
|
38
38
|
:isChildElement="isChildElement"
|
|
39
39
|
>
|
|
40
|
-
|
|
40
|
+
<UiBmsTableCell :item="item.childElement" :cell="cell" />
|
|
41
41
|
</slot>
|
|
42
42
|
</div>
|
|
43
43
|
<div v-else-if="cell?.action" class="bms-table__row__cell--action">
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
:row="item"
|
|
54
54
|
:isChildElement="isChildElement"
|
|
55
55
|
>
|
|
56
|
-
|
|
56
|
+
<UiBmsTableCell :item="item" :cell="cell" />
|
|
57
57
|
</slot>
|
|
58
58
|
</td>
|
|
59
59
|
</slot>
|
|
@@ -68,6 +68,7 @@ import _get from 'lodash/get';
|
|
|
68
68
|
import UiBmsInputCheckbox from '../form/UiBmsInputCheckbox.vue';
|
|
69
69
|
import BmsTooltip from '../feedback/BmsTooltip.vue';
|
|
70
70
|
import { CornerDownRight } from 'lucide-vue-next';
|
|
71
|
+
import UiBmsTableCell from './UiBmsTableCell.vue';
|
|
71
72
|
|
|
72
73
|
interface Props {
|
|
73
74
|
item: any;
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from './search.composable';
|
|
7
7
|
import { defineComponent, nextTick } from 'vue';
|
|
8
8
|
import { mount } from '@vue/test-utils';
|
|
9
|
+
import { Filter } from '@/models';
|
|
9
10
|
|
|
10
11
|
vi.mock('vue-router', () => ({
|
|
11
12
|
useRoute: vi.fn(),
|
|
@@ -1292,5 +1293,79 @@ describe('search and filter composable', () => {
|
|
|
1292
1293
|
]);
|
|
1293
1294
|
});
|
|
1294
1295
|
});
|
|
1296
|
+
|
|
1297
|
+
describe('updateFiltersFromProps', () => {
|
|
1298
|
+
test('should update select options', () => {
|
|
1299
|
+
const wrapper = factory();
|
|
1300
|
+
const filter: Filter = {
|
|
1301
|
+
key: 'filter0',
|
|
1302
|
+
label: 'Zéro',
|
|
1303
|
+
type: 'select',
|
|
1304
|
+
selectOptions: [{ label: 'option1', value: '1' }],
|
|
1305
|
+
value: 1,
|
|
1306
|
+
};
|
|
1307
|
+
|
|
1308
|
+
wrapper.vm.filters = [filter];
|
|
1309
|
+
wrapper.vm.updateFiltersFromProps([{ ...filter, selectOptions: [] }]);
|
|
1310
|
+
expect(wrapper.vm.filters).toStrictEqual([
|
|
1311
|
+
{ ...filter, selectOptions: [], value: null },
|
|
1312
|
+
]);
|
|
1313
|
+
});
|
|
1314
|
+
|
|
1315
|
+
test('should not change filter other than options', () => {
|
|
1316
|
+
const wrapper = factory();
|
|
1317
|
+
const filter: Filter = {
|
|
1318
|
+
key: 'filter0',
|
|
1319
|
+
label: 'Zéro',
|
|
1320
|
+
type: 'select',
|
|
1321
|
+
selectOptions: [{ label: 'option1', value: '1' }],
|
|
1322
|
+
};
|
|
1323
|
+
|
|
1324
|
+
wrapper.vm.filters = [filter];
|
|
1325
|
+
wrapper.vm.updateFiltersFromProps([
|
|
1326
|
+
{
|
|
1327
|
+
key: 'filter0',
|
|
1328
|
+
label: 'new label',
|
|
1329
|
+
type: 'input',
|
|
1330
|
+
selectOptions: [{ label: 'option1', value: '1' }],
|
|
1331
|
+
},
|
|
1332
|
+
]);
|
|
1333
|
+
expect(wrapper.vm.filters).toStrictEqual([filter]);
|
|
1334
|
+
});
|
|
1335
|
+
test('should keep value if is in new options', () => {
|
|
1336
|
+
const wrapper = factory();
|
|
1337
|
+
const filter: Filter = {
|
|
1338
|
+
key: 'filter0',
|
|
1339
|
+
label: 'Zéro',
|
|
1340
|
+
type: 'select',
|
|
1341
|
+
selectOptions: [
|
|
1342
|
+
{ label: 'option1', value: '1' },
|
|
1343
|
+
{ label: 'option2', value: '2' },
|
|
1344
|
+
],
|
|
1345
|
+
};
|
|
1346
|
+
|
|
1347
|
+
wrapper.vm.filters = [filter];
|
|
1348
|
+
wrapper.vm.updateFiltersFromProps([
|
|
1349
|
+
{
|
|
1350
|
+
key: 'filter0',
|
|
1351
|
+
label: 'Zéro',
|
|
1352
|
+
type: 'select',
|
|
1353
|
+
selectOptions: [
|
|
1354
|
+
{ label: 'option1', value: '1' },
|
|
1355
|
+
{ label: 'option3', value: '3' },
|
|
1356
|
+
],
|
|
1357
|
+
},
|
|
1358
|
+
]);
|
|
1359
|
+
expect(wrapper.vm.filters).toStrictEqual([
|
|
1360
|
+
{
|
|
1361
|
+
...filter,
|
|
1362
|
+
selectOptions: [
|
|
1363
|
+
{ label: 'option1', value: '1' },
|
|
1364
|
+
{ label: 'option3', value: '3' },
|
|
1365
|
+
],
|
|
1366
|
+
},
|
|
1367
|
+
]);
|
|
1368
|
+
});
|
|
1369
|
+
});
|
|
1295
1370
|
});
|
|
1296
1371
|
});
|
|
@@ -183,6 +183,7 @@ export const useSearch = (
|
|
|
183
183
|
reflectSearchToUserPref(search.value);
|
|
184
184
|
}
|
|
185
185
|
});
|
|
186
|
+
|
|
186
187
|
watch(
|
|
187
188
|
() => filters,
|
|
188
189
|
() => {
|
|
@@ -233,19 +234,23 @@ export const useSearch = (
|
|
|
233
234
|
});
|
|
234
235
|
};
|
|
235
236
|
|
|
237
|
+
const resetFilterValue = (filter: Filter) => {
|
|
238
|
+
let resetFilter = { ...filter };
|
|
239
|
+
const valueKeys = ['value', 'valueFrom', 'valueTo'];
|
|
240
|
+
valueKeys.forEach((key) => {
|
|
241
|
+
if (
|
|
242
|
+
resetFilter[key as keyof Filter] !== null &&
|
|
243
|
+
resetFilter[key as keyof Filter] !== undefined
|
|
244
|
+
) {
|
|
245
|
+
resetFilter[key as keyof Filter] = null;
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
return resetFilter;
|
|
249
|
+
};
|
|
250
|
+
|
|
236
251
|
const resetFilters = () => {
|
|
237
252
|
filters.value = filters.value.map((filter) => {
|
|
238
|
-
|
|
239
|
-
const valueKeys = ['value', 'valueFrom', 'valueTo'];
|
|
240
|
-
valueKeys.forEach((key) => {
|
|
241
|
-
if (
|
|
242
|
-
resetFilter[key as keyof Filter] !== null &&
|
|
243
|
-
resetFilter[key as keyof Filter] !== undefined
|
|
244
|
-
) {
|
|
245
|
-
resetFilter[key as keyof Filter] = null;
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
return resetFilter;
|
|
253
|
+
return resetFilterValue(filter);
|
|
249
254
|
});
|
|
250
255
|
};
|
|
251
256
|
|
|
@@ -264,6 +269,43 @@ export const useSearch = (
|
|
|
264
269
|
}
|
|
265
270
|
};
|
|
266
271
|
|
|
272
|
+
const updateFiltersFromProps = (updatedFilters: Filter[]) => {
|
|
273
|
+
filters.value = filters.value.map((filter) => {
|
|
274
|
+
const updatedFilter = updatedFilters.find((f) => f.key === filter.key);
|
|
275
|
+
|
|
276
|
+
if (updatedFilter) {
|
|
277
|
+
let mergedFilter = { ...filter };
|
|
278
|
+
|
|
279
|
+
if (updatedFilter.autocompleteOptions)
|
|
280
|
+
mergedFilter.autocompleteOptions = updatedFilter.autocompleteOptions;
|
|
281
|
+
if (updatedFilter.selectOptions)
|
|
282
|
+
mergedFilter.selectOptions = updatedFilter.selectOptions;
|
|
283
|
+
|
|
284
|
+
const value = filter.value || filter.valueFrom || filter.valueTo;
|
|
285
|
+
|
|
286
|
+
const isValueValidSelectOption =
|
|
287
|
+
updatedFilter.type === 'select' &&
|
|
288
|
+
updatedFilter.selectOptions?.find((option) => option.value === value);
|
|
289
|
+
|
|
290
|
+
const isValueValidAutocompleteOption =
|
|
291
|
+
updatedFilter.type === 'autocomplete' &&
|
|
292
|
+
updatedFilter.autocompleteOptions?.find((option) => option === value);
|
|
293
|
+
|
|
294
|
+
if (
|
|
295
|
+
value &&
|
|
296
|
+
!isValueValidSelectOption &&
|
|
297
|
+
!isValueValidAutocompleteOption
|
|
298
|
+
) {
|
|
299
|
+
mergedFilter = resetFilterValue(mergedFilter);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return mergedFilter;
|
|
303
|
+
} else {
|
|
304
|
+
return filter;
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
};
|
|
308
|
+
|
|
267
309
|
return {
|
|
268
310
|
search,
|
|
269
311
|
reflect,
|
|
@@ -277,6 +319,7 @@ export const useSearch = (
|
|
|
277
319
|
resetFilters,
|
|
278
320
|
selectSavedFilter,
|
|
279
321
|
resetAllFilters,
|
|
322
|
+
updateFiltersFromProps,
|
|
280
323
|
};
|
|
281
324
|
};
|
|
282
325
|
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { InputType } from './form.model';
|
|
2
2
|
import { Sort, SortFunction } from './sort.model';
|
|
3
3
|
|
|
4
|
+
export enum ColumnType {
|
|
5
|
+
Boolean = 'Boolean',
|
|
6
|
+
Date = 'Date',
|
|
7
|
+
String = 'String',
|
|
8
|
+
}
|
|
9
|
+
|
|
4
10
|
export interface TableHeader {
|
|
5
11
|
label: string;
|
|
6
12
|
key: string;
|
|
@@ -10,6 +16,7 @@ export interface TableHeader {
|
|
|
10
16
|
sortable?: boolean;
|
|
11
17
|
sortFunction?: SortFunction;
|
|
12
18
|
action?: boolean;
|
|
19
|
+
columnType?: ColumnType;
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
export interface ServerTableRequestParams {
|
|
@@ -24,17 +24,17 @@
|
|
|
24
24
|
</span>
|
|
25
25
|
</div>
|
|
26
26
|
|
|
27
|
-
<div v-if="captions?.length" class="field__captions">
|
|
27
|
+
<div v-if="captions?.length || errors?.length" class="field__captions">
|
|
28
28
|
<div
|
|
29
|
+
v-if="captions?.length"
|
|
29
30
|
v-for="caption in captions"
|
|
30
31
|
:key="getCaptionIdentifier(caption)"
|
|
31
32
|
class="information field__caption"
|
|
32
33
|
>
|
|
33
34
|
<BmsCaption :caption="caption" />
|
|
34
35
|
</div>
|
|
35
|
-
</div>
|
|
36
|
-
<div v-if="errors?.length" class="field__errors">
|
|
37
36
|
<div
|
|
37
|
+
v-if="errors?.length"
|
|
38
38
|
v-for="error in computedErrors"
|
|
39
39
|
:key="error.label"
|
|
40
40
|
class="field__error"
|
|
@@ -186,7 +186,9 @@ const getCaptionIdentifier = (caption: string | Caption): string => {
|
|
|
186
186
|
--field-padding: 0.5em;
|
|
187
187
|
--field-margin: 0;
|
|
188
188
|
--field-label-font-weight: normal;
|
|
189
|
-
|
|
189
|
+
.field__captions {
|
|
190
|
+
margin-top: 0.5rem;
|
|
191
|
+
}
|
|
190
192
|
}
|
|
191
193
|
|
|
192
194
|
&.is-error {
|
|
@@ -27,15 +27,6 @@ Default.args = {
|
|
|
27
27
|
})),
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
export const WithSelectedItem = Template.bind({});
|
|
31
|
-
WithSelectedItem.args = {
|
|
32
|
-
currentSelectedItemIndex: 1,
|
|
33
|
-
options: ['toto', 'titi', 'tutu', 'tirlitititututatoooo'].map((i) => ({
|
|
34
|
-
label: i,
|
|
35
|
-
value: i,
|
|
36
|
-
})),
|
|
37
|
-
};
|
|
38
|
-
|
|
39
30
|
export const CanAddNewOption = Template.bind({});
|
|
40
31
|
CanAddNewOption.args = {
|
|
41
32
|
options: ['toto', 'titi', 'tutu', 'tirlitititututatoooo'].map((i) => ({
|
|
@@ -1,29 +1,21 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<ul
|
|
3
|
-
class="options-list"
|
|
4
|
-
data-testid="select-options"
|
|
5
|
-
@keydown.up="keyUp"
|
|
6
|
-
@keydown.down="keyDown"
|
|
7
|
-
@keydown.enter="keyEnter"
|
|
8
|
-
>
|
|
2
|
+
<ul class="options-list" data-testid="select-options">
|
|
9
3
|
<li
|
|
10
4
|
v-for="(option, index) in options"
|
|
11
5
|
:key="index"
|
|
12
6
|
:data-testid="option.value"
|
|
13
7
|
:class="{
|
|
8
|
+
'datalist-option': true,
|
|
14
9
|
selected: index === currentSelectedItemIndex,
|
|
15
10
|
small,
|
|
16
11
|
}"
|
|
17
|
-
@click.
|
|
12
|
+
@click.stop="onClick(option)"
|
|
18
13
|
>
|
|
19
14
|
<slot name="option" :option="option">
|
|
20
15
|
{{ option.label === null ? 'N/A' : option.label }}
|
|
21
16
|
</slot>
|
|
22
17
|
</li>
|
|
23
|
-
<li
|
|
24
|
-
v-if="displayNewOption"
|
|
25
|
-
@click.prevent="$emits('addNewOption', newOption)"
|
|
26
|
-
>
|
|
18
|
+
<li v-if="displayNewOption" @click.stop="$emits('addNewOption', newOption)">
|
|
27
19
|
<slot name="new-option"> {{ newOption }} (nouvelle option) </slot>
|
|
28
20
|
</li>
|
|
29
21
|
</ul>
|
|
@@ -31,7 +23,7 @@
|
|
|
31
23
|
|
|
32
24
|
<script setup lang="ts">
|
|
33
25
|
import { InputOption } from '@/models';
|
|
34
|
-
import { computed, onMounted, ref } from 'vue';
|
|
26
|
+
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
|
35
27
|
|
|
36
28
|
export interface Props {
|
|
37
29
|
options: InputOption[];
|
|
@@ -52,17 +44,24 @@ const currentSelectedItemIndex = ref<number>(-1);
|
|
|
52
44
|
const $emits = defineEmits<{
|
|
53
45
|
select: [option: any];
|
|
54
46
|
addNewOption: [option: string];
|
|
47
|
+
blur: [];
|
|
55
48
|
}>();
|
|
56
49
|
|
|
57
50
|
const keyDownEventListener = (ev: KeyboardEvent) => {
|
|
58
51
|
if (props.isInputFocused) {
|
|
59
52
|
switch (ev.key) {
|
|
60
53
|
case 'ArrowDown':
|
|
54
|
+
ev.preventDefault();
|
|
61
55
|
return keyDown();
|
|
62
56
|
case 'ArrowUp':
|
|
57
|
+
ev.preventDefault();
|
|
63
58
|
return keyUp();
|
|
64
59
|
case 'Enter':
|
|
60
|
+
ev.preventDefault();
|
|
65
61
|
return keyEnter();
|
|
62
|
+
case 'Escape':
|
|
63
|
+
ev.preventDefault();
|
|
64
|
+
return keyEscape();
|
|
66
65
|
default:
|
|
67
66
|
return;
|
|
68
67
|
}
|
|
@@ -96,6 +95,10 @@ const keyEnter = () => {
|
|
|
96
95
|
}
|
|
97
96
|
};
|
|
98
97
|
|
|
98
|
+
const keyEscape = () => {
|
|
99
|
+
$emits('blur');
|
|
100
|
+
};
|
|
101
|
+
|
|
99
102
|
const onClick = (option: { label: string; value: any }) => {
|
|
100
103
|
$emits('select', option);
|
|
101
104
|
};
|
|
@@ -5,8 +5,8 @@ export interface FieldComponentProps {
|
|
|
5
5
|
required?: boolean;
|
|
6
6
|
optional?: boolean;
|
|
7
7
|
helperText?: string;
|
|
8
|
-
errors?: string
|
|
9
|
-
captions?: string
|
|
8
|
+
errors?: (string | Caption)[];
|
|
9
|
+
captions?: (string | Caption)[];
|
|
10
10
|
disabled?: boolean;
|
|
11
11
|
small?: boolean;
|
|
12
12
|
}
|