@cnamts/synapse 0.0.11-alpha → 0.0.12-alpha
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/design-system-v3.js +3878 -3189
- package/dist/design-system-v3.umd.cjs +1 -1
- package/dist/src/components/Amelipro/types/languages.d.ts +6 -0
- package/dist/src/components/Amelipro/types/types.d.ts +65 -0
- package/dist/src/components/CookieBanner/CookieBanner.d.ts +1 -1
- package/dist/src/components/Customs/SyInputSelect/SyInputSelect.d.ts +2 -0
- package/dist/src/components/Customs/SyTextField/SyTextField.d.ts +29 -23
- package/dist/src/components/Customs/SyTextField/types.d.ts +1 -0
- package/dist/src/components/DatePicker/DatePicker.d.ts +70 -59
- package/dist/src/components/DatePicker/DateTextInput.d.ts +67 -56
- package/dist/src/components/ErrorPage/ErrorPage.d.ts +1 -1
- package/dist/src/components/FileList/FileList.d.ts +1 -0
- package/dist/src/components/FileList/UploadItem/UploadItem.d.ts +1 -1
- package/dist/src/components/FilterSideBar/FilterSideBar.d.ts +31 -0
- package/dist/src/components/FilterSideBar/locales.d.ts +7 -0
- package/dist/src/components/FilterSideBar/tests/FilterSideBar.spec.d.ts +1 -0
- package/dist/src/components/LangBtn/LangBtn.d.ts +2 -2
- package/dist/src/components/NirField/NirField.d.ts +940 -0
- package/dist/src/components/NotificationBar/NotificationBar.d.ts +1 -1
- package/dist/src/components/PasswordField/PasswordField.d.ts +40 -8
- package/dist/src/components/PeriodField/PeriodField.d.ts +142 -120
- package/dist/src/components/PhoneField/PhoneField.d.ts +11 -2
- package/dist/src/components/RatingPicker/EmotionPicker/EmotionPicker.d.ts +1 -1
- package/dist/src/components/RatingPicker/NumberPicker/NumberPicker.d.ts +1 -1
- package/dist/src/components/RatingPicker/StarsPicker/StarsPicker.d.ts +1 -1
- package/dist/src/components/UploadWorkflow/config.d.ts +29 -0
- package/dist/src/components/UploadWorkflow/locales.d.ts +7 -0
- package/dist/src/components/UploadWorkflow/tests/UploadWorkflow.spec.d.ts +1 -0
- package/dist/src/components/UploadWorkflow/types.d.ts +19 -0
- package/dist/src/components/UploadWorkflow/useFileList.d.ts +10 -0
- package/dist/src/components/UploadWorkflow/useFileUploadJourney.d.ts +9 -0
- package/dist/src/components/index.d.ts +2 -0
- package/dist/src/composables/rules/useFieldValidation.d.ts +1 -0
- package/dist/src/composables/validation/tests/useValidation.spec.d.ts +1 -0
- package/dist/src/composables/validation/useValidation.d.ts +39 -0
- package/dist/src/designTokens/index.d.ts +3 -1
- package/dist/src/vuetifyConfig.d.ts +81 -0
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/assets/_elevations.scss +89 -0
- package/src/assets/_fonts.scss +6 -0
- package/src/assets/_radius.scss +86 -0
- package/src/assets/_spacers.scss +149 -0
- package/src/assets/settings.scss +7 -3
- package/src/assets/tokens.scss +32 -29
- package/src/components/Amelipro/types/languages.d.ts +6 -0
- package/src/components/Amelipro/types/types.d.ts +65 -0
- package/src/components/Customs/SyInputSelect/SyInputSelect.stories.ts +65 -0
- package/src/components/Customs/SyInputSelect/SyInputSelect.vue +13 -3
- package/src/components/Customs/SySelect/SySelect.stories.ts +88 -5
- package/src/components/Customs/SySelect/SySelect.vue +36 -10
- package/src/components/Customs/SySelect/tests/SySelect.spec.ts +135 -2
- package/src/components/Customs/SyTextField/SyTextField.stories.ts +576 -85
- package/src/components/Customs/SyTextField/SyTextField.vue +132 -104
- package/src/components/Customs/SyTextField/tests/SyTextField.spec.ts +190 -38
- package/src/components/Customs/SyTextField/types.d.ts +1 -0
- package/src/components/DatePicker/DatePicker.vue +405 -137
- package/src/components/DatePicker/DateTextInput.vue +15 -0
- package/src/components/DatePicker/tests/DatePicker.spec.ts +8 -15
- package/src/components/FileList/FileList.vue +2 -1
- package/src/components/FileList/UploadItem/UploadItem.vue +10 -0
- package/src/components/FileUpload/FileUpload.stories.ts +84 -0
- package/src/components/FileUpload/FileUpload.vue +1 -0
- package/src/components/FileUpload/tests/FileUpload.spec.ts +4 -4
- package/src/components/FilterInline/FilterInline.mdx +180 -34
- package/src/components/FilterInline/FilterInline.stories.ts +363 -6
- package/src/components/FilterSideBar/FilterSideBar.mdx +237 -0
- package/src/components/FilterSideBar/FilterSideBar.stories.ts +798 -0
- package/src/components/FilterSideBar/FilterSideBar.vue +193 -0
- package/src/components/FilterSideBar/locales.ts +8 -0
- package/src/components/FilterSideBar/tests/FilterSideBar.spec.ts +305 -0
- package/src/components/FilterSideBar/tests/__snapshots__/FilterSideBar.spec.ts.snap +39 -0
- package/src/components/HeaderBar/Usages.mdx +1 -1
- package/src/components/NirField/NirField.stories.ts +573 -29
- package/src/components/NirField/NirField.vue +397 -359
- package/src/components/NirField/tests/NirField.spec.ts +88 -52
- package/src/components/NirField/tests//342/200/257dataset/342/200/257.md +12 -0
- package/src/components/NotificationBar/Accessibilite.stories.ts +4 -0
- package/src/components/NotificationBar/NotificationBar.stories.ts +18 -13
- package/src/components/PasswordField/PasswordField.mdx +129 -47
- package/src/components/PasswordField/PasswordField.stories.ts +924 -120
- package/src/components/PasswordField/PasswordField.vue +209 -99
- package/src/components/PasswordField/tests/PasswordField.spec.ts +138 -9
- package/src/components/PeriodField/PeriodField.vue +55 -54
- package/src/components/PhoneField/PhoneField.stories.ts +69 -0
- package/src/components/PhoneField/PhoneField.vue +3 -0
- package/src/components/PhoneField/indicatifs.ts +1 -1
- package/src/components/UploadWorkflow/UploadWorkflow.mdx +75 -0
- package/src/components/UploadWorkflow/UploadWorkflow.stories.ts +943 -0
- package/src/components/UploadWorkflow/UploadWorkflow.vue +230 -0
- package/src/components/UploadWorkflow/config.ts +29 -0
- package/src/components/UploadWorkflow/locales.ts +8 -0
- package/src/components/UploadWorkflow/tests/UploadWorkflow.spec.ts +257 -0
- package/src/components/UploadWorkflow/tests/__snapshots__/UploadWorkflow.spec.ts.snap +54 -0
- package/src/components/UploadWorkflow/types.ts +21 -0
- package/src/components/UploadWorkflow/useFileList.ts +84 -0
- package/src/components/UploadWorkflow/useFileUploadJourney.ts +18 -0
- package/src/components/index.ts +2 -0
- package/src/composables/rules/useFieldValidation.ts +5 -2
- package/src/composables/validation/tests/useValidation.spec.ts +154 -0
- package/src/composables/validation/useValidation.ts +165 -0
- package/src/designTokens/index.ts +4 -0
- package/src/stories/Demarrer/Accueil.mdx +1 -1
- package/src/stories/DesignTokens/ThemePA.mdx +4 -30
- package/src/stories/GuideDuDev/UtiliserLesRules.mdx +319 -76
- package/src/stories/GuideDuDev/moduleDeNotification.mdx +1 -1
- package/src/vuetifyConfig.ts +61 -0
- package/src/composables/useFilterable/__snapshots__/useFilterable.spec.ts.snap +0 -3
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import useCustomizableOptions, {
|
|
3
|
+
type CustomizableOptions,
|
|
4
|
+
} from '@/composables/useCustomizableOptions'
|
|
5
|
+
import { useWidthable, type Widthable } from '@/composables/widthable'
|
|
6
|
+
import { required } from '@/utils/rules/required'
|
|
7
|
+
import { computed, reactive, ref, toRef, watch } from 'vue'
|
|
8
|
+
import DialogBox from '../DialogBox/DialogBox.vue'
|
|
9
|
+
import FileList from '../FileList/FileList.vue'
|
|
10
|
+
import FilePreview from '../FilePreview/FilePreview.vue'
|
|
11
|
+
import FileUpload from '../FileUpload/FileUpload.vue'
|
|
12
|
+
import { config } from './config'
|
|
13
|
+
import { locales as defaultLocales } from './locales'
|
|
14
|
+
import type { FileItem, SelectedFile, UploadItem } from './types'
|
|
15
|
+
import useFileUploadJourney from './useFileUploadJourney'
|
|
16
|
+
import useFileList from './useFileList'
|
|
17
|
+
|
|
18
|
+
const props = withDefaults(
|
|
19
|
+
defineProps<
|
|
20
|
+
Widthable &
|
|
21
|
+
CustomizableOptions & {
|
|
22
|
+
uploadList: UploadItem[]
|
|
23
|
+
sectionTitle?: string
|
|
24
|
+
showFilePreview?: boolean
|
|
25
|
+
locales?: typeof defaultLocales
|
|
26
|
+
}
|
|
27
|
+
>(),
|
|
28
|
+
{
|
|
29
|
+
sectionTitle: undefined,
|
|
30
|
+
showFilePreview: false,
|
|
31
|
+
locales: () => defaultLocales,
|
|
32
|
+
},
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
const emits = defineEmits<{
|
|
36
|
+
(e: 'error', value: string[]): void
|
|
37
|
+
(e: 'update:modelValue', value: SelectedFile[]): void
|
|
38
|
+
}>()
|
|
39
|
+
|
|
40
|
+
const selectedFiles = defineModel<SelectedFile[]>({
|
|
41
|
+
type: Array,
|
|
42
|
+
default: reactive([]),
|
|
43
|
+
})
|
|
44
|
+
watch(
|
|
45
|
+
selectedFiles,
|
|
46
|
+
(value) => {
|
|
47
|
+
emits('update:modelValue', value)
|
|
48
|
+
},
|
|
49
|
+
{ deep: true },
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
const { widthStyles } = useWidthable(props)
|
|
53
|
+
const options = useCustomizableOptions(config, props)
|
|
54
|
+
|
|
55
|
+
const fileUpload = ref<typeof FileUpload>()
|
|
56
|
+
const { selectItems, selectedItem } = useFileUploadJourney(
|
|
57
|
+
toRef(props, 'uploadList'),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
const title = computed(
|
|
61
|
+
() => props.sectionTitle ?? props.locales.title(!!props.uploadList.length),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
const { addOrReplaceFile, resetFile, setItemOnError, filledUploadList }
|
|
65
|
+
= useFileList(selectedFiles, toRef(props, 'uploadList'))
|
|
66
|
+
|
|
67
|
+
// handle FileList
|
|
68
|
+
let inlineSelectedItemId: string | undefined = undefined
|
|
69
|
+
function uploadInline(item: FileItem) {
|
|
70
|
+
inlineSelectedItemId = item.id
|
|
71
|
+
|
|
72
|
+
fileUpload.value!.fileInput!.click()
|
|
73
|
+
}
|
|
74
|
+
function previewFile(file: FileItem & { file?: File }) {
|
|
75
|
+
showPreviewDialog.value = true
|
|
76
|
+
fileToPreview.value = file.file
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// handle FileUpload
|
|
80
|
+
const uploadedFiles = ref<File[]>([])
|
|
81
|
+
|
|
82
|
+
const showFileUpload = computed(
|
|
83
|
+
() => (
|
|
84
|
+
selectedFiles.value.length < props.uploadList.length
|
|
85
|
+
|| props.uploadList.find(uploadItem => uploadItem.state === 'error') !== undefined
|
|
86
|
+
),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
function fileSelected(files: File[]) {
|
|
90
|
+
const fileId
|
|
91
|
+
= inlineSelectedItemId ?? (props.uploadList.length === 1
|
|
92
|
+
? props.uploadList[0].id
|
|
93
|
+
: undefined)
|
|
94
|
+
inlineSelectedItemId = undefined
|
|
95
|
+
|
|
96
|
+
if (props.showFilePreview || fileId === undefined) {
|
|
97
|
+
showSelectDialog.value = true
|
|
98
|
+
selectedItem.value = fileId
|
|
99
|
+
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
addOrReplaceFile(files[0], fileId)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function uploadError(errors: string[]) {
|
|
107
|
+
emits('error', errors)
|
|
108
|
+
if (!inlineSelectedItemId) {
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
setItemOnError(inlineSelectedItemId)
|
|
112
|
+
inlineSelectedItemId = undefined
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// handle DialogBox
|
|
116
|
+
const showSelectDialog = ref(false)
|
|
117
|
+
|
|
118
|
+
function dialogConfirm() {
|
|
119
|
+
if (!selectedItem.value) {
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
addOrReplaceFile(uploadedFiles.value[0], selectedItem.value)
|
|
124
|
+
|
|
125
|
+
showSelectDialog.value = false
|
|
126
|
+
selectedItem.value = undefined
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// handle preview
|
|
130
|
+
const fileToPreview = ref<File>()
|
|
131
|
+
const showPreviewDialog = ref(false)
|
|
132
|
+
</script>
|
|
133
|
+
|
|
134
|
+
<template>
|
|
135
|
+
<div
|
|
136
|
+
:style="widthStyles"
|
|
137
|
+
class="sy-upload-workflow white"
|
|
138
|
+
>
|
|
139
|
+
<slot name="title">
|
|
140
|
+
<h4 class="text-h6 mb-2">
|
|
141
|
+
{{ title }}
|
|
142
|
+
</h4>
|
|
143
|
+
</slot>
|
|
144
|
+
|
|
145
|
+
<FileList
|
|
146
|
+
v-bind="options.fileList"
|
|
147
|
+
:upload-list="filledUploadList"
|
|
148
|
+
@upload="uploadInline"
|
|
149
|
+
@retry="uploadInline"
|
|
150
|
+
@delete="resetFile"
|
|
151
|
+
@preview="previewFile"
|
|
152
|
+
/>
|
|
153
|
+
|
|
154
|
+
<Transition>
|
|
155
|
+
<FileUpload
|
|
156
|
+
v-if="showFileUpload"
|
|
157
|
+
ref="fileUpload"
|
|
158
|
+
v-bind="options.fileUpload"
|
|
159
|
+
v-model="uploadedFiles"
|
|
160
|
+
@error="uploadError"
|
|
161
|
+
@update:model-value="fileSelected"
|
|
162
|
+
/>
|
|
163
|
+
</Transition>
|
|
164
|
+
|
|
165
|
+
<DialogBox
|
|
166
|
+
v-model="showSelectDialog"
|
|
167
|
+
v-bind="options.dialog"
|
|
168
|
+
@cancel="showSelectDialog = false"
|
|
169
|
+
@confirm="dialogConfirm"
|
|
170
|
+
>
|
|
171
|
+
<template #title>
|
|
172
|
+
<slot name="modal-title">
|
|
173
|
+
{{ locales.modalTitle }}
|
|
174
|
+
</slot>
|
|
175
|
+
</template>
|
|
176
|
+
<slot name="modal-description" />
|
|
177
|
+
|
|
178
|
+
<VForm
|
|
179
|
+
v-if="true"
|
|
180
|
+
ref="form"
|
|
181
|
+
v-bind="options.form"
|
|
182
|
+
class="mb-2"
|
|
183
|
+
>
|
|
184
|
+
<VSelect
|
|
185
|
+
v-model="selectedItem"
|
|
186
|
+
v-bind="options.select"
|
|
187
|
+
:items="selectItems"
|
|
188
|
+
item-title="text"
|
|
189
|
+
item-value="value"
|
|
190
|
+
:rules="[required]"
|
|
191
|
+
color="primary"
|
|
192
|
+
/>
|
|
193
|
+
</VForm>
|
|
194
|
+
|
|
195
|
+
<FilePreview
|
|
196
|
+
v-if="showFilePreview"
|
|
197
|
+
:options="options.filePreview"
|
|
198
|
+
:file="uploadedFiles[0]"
|
|
199
|
+
/>
|
|
200
|
+
</DialogBox>
|
|
201
|
+
|
|
202
|
+
<DialogBox
|
|
203
|
+
v-model="showPreviewDialog"
|
|
204
|
+
v-bind="options.previewDialog"
|
|
205
|
+
hide-actions
|
|
206
|
+
>
|
|
207
|
+
<slot name="preview-description" />
|
|
208
|
+
|
|
209
|
+
<FilePreview
|
|
210
|
+
:options="options.filePreview"
|
|
211
|
+
:file="fileToPreview"
|
|
212
|
+
/>
|
|
213
|
+
</DialogBox>
|
|
214
|
+
</div>
|
|
215
|
+
</template>
|
|
216
|
+
|
|
217
|
+
<style lang="scss" scoped>
|
|
218
|
+
.v-enter-active,
|
|
219
|
+
.v-leave-active {
|
|
220
|
+
interpolate-size: allow-keywords;
|
|
221
|
+
transition: height 0.2s ease, opacity 0.2s ease;
|
|
222
|
+
overflow: hidden;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.v-enter-from,
|
|
226
|
+
.v-leave-to {
|
|
227
|
+
height: 0;
|
|
228
|
+
opacity: 0;
|
|
229
|
+
}
|
|
230
|
+
</style>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const config = {
|
|
2
|
+
fileUpload: {
|
|
3
|
+
class: 'mt-6',
|
|
4
|
+
},
|
|
5
|
+
dialog: {
|
|
6
|
+
persistent: true,
|
|
7
|
+
width: '550',
|
|
8
|
+
},
|
|
9
|
+
card: {
|
|
10
|
+
class: 'pa-4',
|
|
11
|
+
},
|
|
12
|
+
select: {
|
|
13
|
+
variant: 'outlined',
|
|
14
|
+
validateOnBlur: true,
|
|
15
|
+
label: 'Nature du fichier',
|
|
16
|
+
},
|
|
17
|
+
layout: {
|
|
18
|
+
wrap: true,
|
|
19
|
+
class: 'mt-2',
|
|
20
|
+
},
|
|
21
|
+
cancelBtn: {
|
|
22
|
+
text: true,
|
|
23
|
+
class: 'mr-4',
|
|
24
|
+
color: 'accent',
|
|
25
|
+
},
|
|
26
|
+
confirmBtn: {
|
|
27
|
+
color: 'accent',
|
|
28
|
+
},
|
|
29
|
+
} as const
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import { vuetify } from '@tests/unit/setup'
|
|
4
|
+
|
|
5
|
+
import UploadWorkflow from '../UploadWorkflow.vue'
|
|
6
|
+
import { locales as fileListLocales } from '@/components/FileList/UploadItem/locales'
|
|
7
|
+
import { locales as FileUploadLocales } from '@/components/FileUpload/locales'
|
|
8
|
+
import { afterEach } from 'node:test'
|
|
9
|
+
import { VSelect } from 'vuetify/components'
|
|
10
|
+
|
|
11
|
+
describe('UploadWorkflow', () => {
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
vi.clearAllMocks()
|
|
14
|
+
document.body.innerHTML = ''
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('render the upload list', async () => {
|
|
18
|
+
const wrapper = mount(UploadWorkflow, {
|
|
19
|
+
props: {
|
|
20
|
+
uploadList: [
|
|
21
|
+
{
|
|
22
|
+
id: 'ID',
|
|
23
|
+
title: 'Carte d\'identité',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'bill',
|
|
27
|
+
title: 'Facture de soin',
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
global: {
|
|
32
|
+
plugins: [vuetify],
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
expect(wrapper.html()).toMatchSnapshot()
|
|
37
|
+
expect(wrapper.findAll('.file-item')).toHaveLength(2)
|
|
38
|
+
expect(wrapper.find('.sy-file-upload').isVisible()).toBeTruthy()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('shows the file in the list when set with the list button', async () => {
|
|
42
|
+
const wrapper = mount(UploadWorkflow, {
|
|
43
|
+
props: {
|
|
44
|
+
uploadList: [
|
|
45
|
+
{
|
|
46
|
+
id: 'ID',
|
|
47
|
+
title: 'Carte d\'identité',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'bill',
|
|
51
|
+
title: 'Facture de soin',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
global: {
|
|
56
|
+
plugins: [vuetify],
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
await wrapper.find('.file-item button').trigger('click')
|
|
61
|
+
|
|
62
|
+
const file: File = new File([''], 'theFilename.pdf', {
|
|
63
|
+
type: 'application/pdf',
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
await wrapper.find('input').trigger('drop', {
|
|
67
|
+
dataTransfer: {
|
|
68
|
+
files: [file],
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
expect(wrapper.find('.file-item').text()).toContain('theFilename.pdf')
|
|
73
|
+
expect(wrapper.emitted('update:modelValue')).toBeTruthy()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('shows the item as error in the list when set with the list button', async () => {
|
|
77
|
+
const wrapper = mount(UploadWorkflow, {
|
|
78
|
+
props: {
|
|
79
|
+
uploadList: [
|
|
80
|
+
{
|
|
81
|
+
id: 'ID',
|
|
82
|
+
title: 'Carte d\'identité',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 'bill',
|
|
86
|
+
title: 'Facture de soin',
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
global: {
|
|
91
|
+
plugins: [vuetify],
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
await wrapper.findAll('.file-item button')[1].trigger('click')
|
|
96
|
+
|
|
97
|
+
const file: File = new File([''], 'theFilename.invalid', {
|
|
98
|
+
type: 'application/invalid',
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
const input = wrapper.find('input')
|
|
102
|
+
input.element.files = [file] as unknown as FileList
|
|
103
|
+
await input.trigger('change')
|
|
104
|
+
|
|
105
|
+
expect(wrapper.emitted('error')).toEqual([
|
|
106
|
+
[
|
|
107
|
+
[
|
|
108
|
+
FileUploadLocales.errorExtension('theFilename.invalid', [
|
|
109
|
+
'pdf',
|
|
110
|
+
'jpg',
|
|
111
|
+
'jpeg',
|
|
112
|
+
'png',
|
|
113
|
+
]),
|
|
114
|
+
],
|
|
115
|
+
],
|
|
116
|
+
])
|
|
117
|
+
expect(wrapper.findAll('.file-item')[1].text()).toContain(
|
|
118
|
+
fileListLocales.error,
|
|
119
|
+
)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('accept the file when we use the FileUpload component with many items in the uploadList', async () => {
|
|
123
|
+
const wrapper = mount(UploadWorkflow, {
|
|
124
|
+
props: {
|
|
125
|
+
uploadList: [
|
|
126
|
+
{
|
|
127
|
+
id: 'ID',
|
|
128
|
+
title: 'Carte d\'identité',
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: 'bill',
|
|
132
|
+
title: 'Facture de soin',
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
global: {
|
|
137
|
+
plugins: [vuetify],
|
|
138
|
+
stubs: {
|
|
139
|
+
VDialog: {
|
|
140
|
+
template: '<div><slot /></div>',
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const file: File = new File([''], 'uploadInField.pdf', {
|
|
147
|
+
type: 'application/pdf',
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
await wrapper.find('input').trigger('drop', {
|
|
151
|
+
dataTransfer: {
|
|
152
|
+
files: [file],
|
|
153
|
+
},
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
wrapper.find('.v-select input').setValue('bill')
|
|
157
|
+
wrapper.findComponent(VSelect).vm.$emit('update:modelValue', 'bill')
|
|
158
|
+
|
|
159
|
+
await wrapper.find('[data-test-id="confirm-btn"]').trigger('click')
|
|
160
|
+
|
|
161
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
162
|
+
expect((wrapper.emitted('update:modelValue')?.[0][0] as any[]).find(item => item.fileName === 'uploadInField.pdf')).toBeTruthy()
|
|
163
|
+
|
|
164
|
+
expect(wrapper.findAll('.file-item')[1].text()).toContain('uploadInField.pdf')
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('replace the selected file', async () => {
|
|
168
|
+
const wrapper = mount(UploadWorkflow, {
|
|
169
|
+
props: {
|
|
170
|
+
uploadList: [
|
|
171
|
+
{
|
|
172
|
+
id: 'ID',
|
|
173
|
+
title: 'Carte d\'identité',
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: 'bill',
|
|
177
|
+
title: 'Facture de soin',
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
modelValue: [
|
|
181
|
+
{
|
|
182
|
+
fileName: 'file1.pdf',
|
|
183
|
+
file: new File([''], 'file1.pdf', {
|
|
184
|
+
type: 'application/pdf',
|
|
185
|
+
}),
|
|
186
|
+
id: 'ID',
|
|
187
|
+
title: 'Carte d\'identité',
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
global: {
|
|
192
|
+
plugins: [vuetify],
|
|
193
|
+
},
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
await wrapper.find('.file-item button').trigger('click')
|
|
197
|
+
|
|
198
|
+
const file: File = new File([''], 'file2.pdf', {
|
|
199
|
+
type: 'application/pdf',
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
await wrapper.find('input').trigger('drop', {
|
|
203
|
+
dataTransfer: {
|
|
204
|
+
files: [file],
|
|
205
|
+
},
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
expect(wrapper.find('.file-item').text()).toContain('file2.pdf')
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('show the preview of an image', async () => {
|
|
212
|
+
const wrapper = mount(UploadWorkflow, {
|
|
213
|
+
props: {
|
|
214
|
+
modelValue: [],
|
|
215
|
+
uploadList: [
|
|
216
|
+
{
|
|
217
|
+
id: 'CERFA1',
|
|
218
|
+
title: 'CERFA 1',
|
|
219
|
+
showPreviewBtn: true,
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: 'CERFA2',
|
|
223
|
+
title: 'CERFA 2',
|
|
224
|
+
showPreviewBtn: true,
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
showFilePreview: false,
|
|
228
|
+
},
|
|
229
|
+
global: {
|
|
230
|
+
plugins: [vuetify],
|
|
231
|
+
stubs: {
|
|
232
|
+
VDialog: {
|
|
233
|
+
template: '<div><slot /></div>',
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
const image = new File(
|
|
240
|
+
[Buffer.from([100, 97, 116, 97, 58, 14, 79, 8, 113, 97, 65, 43, 57, 55, 89, 69, 88, 51, 66, 101, 70, 86, 112, 121, 112, 121, 121, 121, 80, 81, 104])],
|
|
241
|
+
'image.png',
|
|
242
|
+
{ type: 'image/png' },
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
await wrapper.find('.file-item button').trigger('click')
|
|
246
|
+
|
|
247
|
+
await wrapper.find('input').trigger('drop', {
|
|
248
|
+
dataTransfer: {
|
|
249
|
+
files: [image],
|
|
250
|
+
},
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
await wrapper.find('.file-item__action-preview').trigger('click')
|
|
254
|
+
|
|
255
|
+
expect(wrapper.find('.sy-file-preview img').exists()).toBeTruthy()
|
|
256
|
+
})
|
|
257
|
+
})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`UploadWorkflow > render the upload list 1`] = `
|
|
4
|
+
"<div data-v-2a35ee7d="" style="width: 100%;" class="sy-upload-workflow white">
|
|
5
|
+
<h4 data-v-2a35ee7d="" class="text-h6 mb-2">Documents à nous transmettre</h4>
|
|
6
|
+
<ul data-v-1c29bdca="" data-v-2a35ee7d="" class="upload-list" style="width: 100%;">
|
|
7
|
+
<li data-v-55f3ab7e="" data-v-1c29bdca="" class="file-item">
|
|
8
|
+
<div data-v-55f3ab7e="" class="file-item__description">
|
|
9
|
+
<div data-v-55f3ab7e="" class="file-item__content"><span data-v-55f3ab7e="" class="file-item__icon"><!--v-if--><i data-v-55f3ab7e="" class="M13,9V3.5L18.5,9M6,2C4.89,2 4,2.89 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6Z mdi v-icon notranslate v-theme--light text-primary" style="font-size: 24px; height: 24px; width: 24px;" aria-hidden="true"></i></span>
|
|
10
|
+
<div data-v-55f3ab7e="">
|
|
11
|
+
<div data-v-55f3ab7e="" class="file-item__title">Carte d'identité</div>
|
|
12
|
+
<div data-v-55f3ab7e="" class="file-item__name text-base"></div>
|
|
13
|
+
<!--v-if-->
|
|
14
|
+
<!--v-if-->
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
<div data-v-55f3ab7e="" class="file-item__actions"><button data-v-55f3ab7e="" type="button" class="v-btn v-theme--light v-btn--density-default v-btn--size-default v-btn--variant-text file-item__action file-item__action-upload text-primary"><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span><span class="v-btn__prepend"><i data-v-55f3ab7e="" class="M2 12H4V17H20V12H22V17C22 18.11 21.11 19 20 19H4C2.9 19 2 18.11 2 17V12M12 2L6.46 7.46L7.88 8.88L11 5.75V15H13V5.75L16.13 8.88L17.55 7.45L12 2Z mdi v-icon notranslate v-theme--light v-icon--size-default text-primary" aria-hidden="true"></i></span><span class="v-btn__content" data-no-activator=""><span data-v-55f3ab7e="">Importer</span></span>
|
|
18
|
+
<!---->
|
|
19
|
+
<!---->
|
|
20
|
+
</button>
|
|
21
|
+
<!--v-if-->
|
|
22
|
+
<!--v-if-->
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
<!--v-if-->
|
|
26
|
+
</li>
|
|
27
|
+
<li data-v-55f3ab7e="" data-v-1c29bdca="" class="file-item">
|
|
28
|
+
<div data-v-55f3ab7e="" class="file-item__description">
|
|
29
|
+
<div data-v-55f3ab7e="" class="file-item__content"><span data-v-55f3ab7e="" class="file-item__icon"><!--v-if--><i data-v-55f3ab7e="" class="M13,9V3.5L18.5,9M6,2C4.89,2 4,2.89 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6Z mdi v-icon notranslate v-theme--light text-primary" style="font-size: 24px; height: 24px; width: 24px;" aria-hidden="true"></i></span>
|
|
30
|
+
<div data-v-55f3ab7e="">
|
|
31
|
+
<div data-v-55f3ab7e="" class="file-item__title">Facture de soin</div>
|
|
32
|
+
<div data-v-55f3ab7e="" class="file-item__name text-base"></div>
|
|
33
|
+
<!--v-if-->
|
|
34
|
+
<!--v-if-->
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
<div data-v-55f3ab7e="" class="file-item__actions"><button data-v-55f3ab7e="" type="button" class="v-btn v-theme--light v-btn--density-default v-btn--size-default v-btn--variant-text file-item__action file-item__action-upload text-primary"><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span><span class="v-btn__prepend"><i data-v-55f3ab7e="" class="M2 12H4V17H20V12H22V17C22 18.11 21.11 19 20 19H4C2.9 19 2 18.11 2 17V12M12 2L6.46 7.46L7.88 8.88L11 5.75V15H13V5.75L16.13 8.88L17.55 7.45L12 2Z mdi v-icon notranslate v-theme--light v-icon--size-default text-primary" aria-hidden="true"></i></span><span class="v-btn__content" data-no-activator=""><span data-v-55f3ab7e="">Importer</span></span>
|
|
38
|
+
<!---->
|
|
39
|
+
<!---->
|
|
40
|
+
</button>
|
|
41
|
+
<!--v-if-->
|
|
42
|
+
<!--v-if-->
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
<!--v-if-->
|
|
46
|
+
</li>
|
|
47
|
+
</ul>
|
|
48
|
+
<transition-stub data-v-2a35ee7d="" appear="false" persisted="false" css="true"><label data-v-3451b201="" data-v-2a35ee7d="" for="file-upload-v-0" class="sy-file-upload d-block pa-4 mt-6" style="width: 100%;"><input data-v-3451b201="" id="file-upload-v-0" type="file" accept=".pdf, .jpg, .jpeg, .png" class="sy-file-upload-input"><span data-v-5bd3b751="" data-v-3451b201="" class="sy-file-upload-placeholder"><i data-v-5bd3b751="" class="M11 20H6.5Q4.22 20 2.61 18.43 1 16.85 1 14.58 1 12.63 2.17 11.1 3.35 9.57 5.25 9.15 5.88 6.85 7.75 5.43 9.63 4 12 4 14.93 4 16.96 6.04 19 8.07 19 11 20.73 11.2 21.86 12.5 23 13.78 23 15.5 23 17.38 21.69 18.69 20.38 20 18.5 20H13V12.85L14.6 14.4L16 13L12 9L8 13L9.4 14.4L11 12.85Z mdi v-icon notranslate v-theme--light text-primary" style="font-size: 40px; height: 40px; width: 40px;" aria-hidden="true"></i><span data-v-5bd3b751="" class="mt-1 font-weight-medium text-black"><span data-v-5bd3b751="">Déposer votre fichier ici</span></span><span data-v-5bd3b751="" class="mb-2 sy-file-upload-caption">Ou</span><span data-v-5bd3b751="" class="sy-file-upload-btn bg-primary text-white elevation-2">Choisir un fichier</span><span data-v-5bd3b751="" class="mt-4 sy-file-upload-caption">Taille max. : 10 Mo. Formats acceptés : pdf, jpg, jpeg, png</span></span></label></transition-stub>
|
|
49
|
+
<!---->
|
|
50
|
+
<!---->
|
|
51
|
+
<!---->
|
|
52
|
+
<!---->
|
|
53
|
+
</div>"
|
|
54
|
+
`;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FileState } from '@/components/FileList/FileList.vue'
|
|
2
|
+
export type { Item as FileItem } from '@/components/FileList/FileList.vue'
|
|
3
|
+
|
|
4
|
+
export type UploadItem = {
|
|
5
|
+
id: string
|
|
6
|
+
title: string
|
|
7
|
+
state?: FileState | string
|
|
8
|
+
optional?: boolean
|
|
9
|
+
showPreviewBtn?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type SelectedFile = {
|
|
13
|
+
id: string
|
|
14
|
+
title: string
|
|
15
|
+
state?: FileState
|
|
16
|
+
progress?: number
|
|
17
|
+
optional?: boolean
|
|
18
|
+
showPreviewBtn?: boolean
|
|
19
|
+
fileName: string
|
|
20
|
+
file: File
|
|
21
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { computed, ref, type DeepReadonly, type Ref } from 'vue'
|
|
2
|
+
import type { FileState, Item as FileListItem } from '../FileList/FileList.vue'
|
|
3
|
+
import type { FileItem, SelectedFile, UploadItem } from './types'
|
|
4
|
+
|
|
5
|
+
export default function useFileList(
|
|
6
|
+
selectedFiles: Ref<SelectedFile[]>,
|
|
7
|
+
uploadList: DeepReadonly<Ref<UploadItem[]>>,
|
|
8
|
+
) {
|
|
9
|
+
const errorSelectedFiles = ref<string[]>([])
|
|
10
|
+
|
|
11
|
+
function removeFromErrorList(fileId: string) {
|
|
12
|
+
const errorIndex = errorSelectedFiles.value.findIndex(item => item === fileId)
|
|
13
|
+
if (errorIndex !== -1) {
|
|
14
|
+
errorSelectedFiles.value.splice(errorIndex, 1)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function findSelectedFile(fileId: string) {
|
|
19
|
+
return selectedFiles.value.find(item => item.id === fileId)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resetFile(fileItem: FileListItem | SelectedFile) {
|
|
23
|
+
const itemIndex = selectedFiles.value.findIndex(item => item.id === fileItem.id)
|
|
24
|
+
selectedFiles.value.splice(itemIndex, 1)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function replaceFile(file: File, item: SelectedFile, state: FileState = 'success') {
|
|
28
|
+
item.file = file
|
|
29
|
+
item.fileName = file.name
|
|
30
|
+
item.state = state
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function addOrReplaceFile(file: File, fileId: string, state: FileState = 'success') {
|
|
34
|
+
const selectedFile = findSelectedFile(fileId)
|
|
35
|
+
|
|
36
|
+
if (selectedFile) {
|
|
37
|
+
replaceFile(file, selectedFile, state)
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const uploadItemIndex = uploadList.value.findIndex(item => item.id === fileId)
|
|
41
|
+
|
|
42
|
+
selectedFiles.value.push({
|
|
43
|
+
id: uploadList.value[uploadItemIndex].id,
|
|
44
|
+
title: uploadList.value[uploadItemIndex].title,
|
|
45
|
+
state: state,
|
|
46
|
+
optional: !!uploadList.value[uploadItemIndex].optional,
|
|
47
|
+
showPreviewBtn: !!uploadList.value[uploadItemIndex].showPreviewBtn,
|
|
48
|
+
fileName: file.name,
|
|
49
|
+
file,
|
|
50
|
+
})
|
|
51
|
+
removeFromErrorList(fileId)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function setItemOnError(fileId: string) {
|
|
56
|
+
const selectedFile = findSelectedFile(fileId)
|
|
57
|
+
if (selectedFile) {
|
|
58
|
+
resetFile(selectedFile)
|
|
59
|
+
}
|
|
60
|
+
errorSelectedFiles.value.push(fileId)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const filledUploadList = computed<FileItem[]>(() => {
|
|
64
|
+
return uploadList.value.map((uploadItem) => {
|
|
65
|
+
const matchingUploadedItem = findSelectedFile(uploadItem.id)
|
|
66
|
+
const error = errorSelectedFiles.value.includes(uploadItem.id)
|
|
67
|
+
const state = uploadItem.state ?? (error ? 'error' : 'initial')
|
|
68
|
+
|
|
69
|
+
return ({
|
|
70
|
+
...uploadItem,
|
|
71
|
+
state,
|
|
72
|
+
...matchingUploadedItem,
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
addOrReplaceFile,
|
|
79
|
+
replaceFile,
|
|
80
|
+
resetFile,
|
|
81
|
+
setItemOnError,
|
|
82
|
+
filledUploadList,
|
|
83
|
+
}
|
|
84
|
+
}
|