cozy-ui 117.0.0 → 117.2.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/CHANGELOG.md +21 -0
- package/package.json +1 -1
- package/react/ActionsMenu/Actions/addToFavorites.js +66 -0
- package/react/ActionsMenu/Actions/download.js +42 -0
- package/react/ActionsMenu/Actions/index.js +3 -0
- package/react/ActionsMenu/Actions/locales/en.json +12 -0
- package/react/ActionsMenu/Actions/locales/fr.json +12 -0
- package/react/ActionsMenu/Actions/removeFromFavorites.js +66 -0
- package/react/NestedSelect/NestedSelect.jsx +23 -12
- package/react/NestedSelect/NestedSelect.md +1 -8
- package/react/NestedSelect/NestedSelect.spec.jsx +15 -12
- package/react/NestedSelect/styles.styl +1 -1
- package/react/QualificationModal/helpers.js +61 -0
- package/react/QualificationModal/helpers.spec.js +32 -0
- package/react/QualificationModal/index.jsx +14 -37
- package/react/QualificationModal/locales/en.json +2 -1
- package/react/QualificationModal/locales/fr.json +2 -1
- package/transpiled/react/ActionsMenu/Actions/addToFavorites.js +144 -0
- package/transpiled/react/ActionsMenu/Actions/download.js +49 -0
- package/transpiled/react/ActionsMenu/Actions/index.js +3 -0
- package/transpiled/react/ActionsMenu/Actions/locales/withActionsLocales.js +24 -0
- package/transpiled/react/ActionsMenu/Actions/removeFromFavorites.js +144 -0
- package/transpiled/react/NestedSelect/NestedSelect.js +33 -15
- package/transpiled/react/QualificationModal/helpers.js +68 -0
- package/transpiled/react/QualificationModal/index.js +12 -40
- package/transpiled/react/QualificationModal/locales/index.js +4 -2
- package/transpiled/react/stylesheet.css +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
# [117.2.0](https://github.com/cozy/cozy-ui/compare/v117.1.0...v117.2.0) (2025-01-30)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **NestedSelect:** Show now result according to searchResult ([debae36](https://github.com/cozy/cozy-ui/commit/debae36))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **NestedSelect:** Add delay on search result ([fc57533](https://github.com/cozy/cozy-ui/commit/fc57533))
|
|
12
|
+
* **NestedSelect:** Replace Input by SearchBar for search options ([cf01651](https://github.com/cozy/cozy-ui/commit/cf01651))
|
|
13
|
+
* **QualificationModal:** Add search bar ([fab5baa](https://github.com/cozy/cozy-ui/commit/fab5baa))
|
|
14
|
+
|
|
15
|
+
# [117.1.0](https://github.com/cozy/cozy-ui/compare/v117.0.0...v117.1.0) (2025-01-21)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* Add Download and Add/Remove favorites actions ([6e4bca4](https://github.com/cozy/cozy-ui/commit/6e4bca4))
|
|
21
|
+
|
|
1
22
|
# [117.0.0](https://github.com/cozy/cozy-ui/compare/v116.0.0...v117.0.0) (2025-01-13)
|
|
2
23
|
|
|
3
24
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react'
|
|
2
|
+
|
|
3
|
+
import { splitFilename } from 'cozy-client/dist/models/file'
|
|
4
|
+
|
|
5
|
+
import { getActionsI18n } from './locales/withActionsLocales'
|
|
6
|
+
import Icon from '../../Icon'
|
|
7
|
+
import StarOutlineIcon from '../../Icons/StarOutline'
|
|
8
|
+
import ListItemIcon from '../../ListItemIcon'
|
|
9
|
+
import ListItemText from '../../ListItemText'
|
|
10
|
+
import ActionsMenuItem from '../ActionsMenuItem'
|
|
11
|
+
|
|
12
|
+
const makeComponent = (label, icon) => {
|
|
13
|
+
const Component = forwardRef((props, ref) => {
|
|
14
|
+
return (
|
|
15
|
+
<ActionsMenuItem {...props} ref={ref}>
|
|
16
|
+
<ListItemIcon>
|
|
17
|
+
<Icon icon={icon} />
|
|
18
|
+
</ListItemIcon>
|
|
19
|
+
<ListItemText primary={label} />
|
|
20
|
+
</ActionsMenuItem>
|
|
21
|
+
)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
Component.displayName = 'addToFavorites'
|
|
25
|
+
|
|
26
|
+
return Component
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const addToFavorites = ({ showAlert }) => {
|
|
30
|
+
const { t } = getActionsI18n()
|
|
31
|
+
const icon = StarOutlineIcon
|
|
32
|
+
const label = t('favorites.add.label')
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
name: 'addToFavorites',
|
|
36
|
+
icon,
|
|
37
|
+
label,
|
|
38
|
+
displayCondition: docs =>
|
|
39
|
+
docs.length > 0 && docs.every(doc => !doc.cozyMetadata?.favorite),
|
|
40
|
+
Component: makeComponent(label, icon),
|
|
41
|
+
action: async (docs, { client }) => {
|
|
42
|
+
try {
|
|
43
|
+
for (const doc of docs) {
|
|
44
|
+
await client.save({
|
|
45
|
+
...doc,
|
|
46
|
+
cozyMetadata: {
|
|
47
|
+
...doc.cozyMetadata,
|
|
48
|
+
favorite: true
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { filename } = splitFilename(docs[0])
|
|
54
|
+
showAlert({
|
|
55
|
+
message: t('favorites.add.success', {
|
|
56
|
+
filename,
|
|
57
|
+
smart_count: docs.length
|
|
58
|
+
}),
|
|
59
|
+
severity: 'success'
|
|
60
|
+
})
|
|
61
|
+
} catch (error) {
|
|
62
|
+
showAlert({ message: t('favorites.error'), severity: 'error' })
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react'
|
|
2
|
+
|
|
3
|
+
import { downloadFile } from 'cozy-client/dist/models/file'
|
|
4
|
+
|
|
5
|
+
import { getActionsI18n } from './locales/withActionsLocales'
|
|
6
|
+
import Icon from '../../Icon'
|
|
7
|
+
import DownloadIcon from '../../Icons/Download'
|
|
8
|
+
import ListItemIcon from '../../ListItemIcon'
|
|
9
|
+
import ListItemText from '../../ListItemText'
|
|
10
|
+
import ActionsMenuItem from '../ActionsMenuItem'
|
|
11
|
+
|
|
12
|
+
const makeComponent = (label, icon) => {
|
|
13
|
+
const Component = forwardRef((props, ref) => {
|
|
14
|
+
return (
|
|
15
|
+
<ActionsMenuItem {...props} ref={ref}>
|
|
16
|
+
<ListItemIcon>
|
|
17
|
+
<Icon icon={icon} />
|
|
18
|
+
</ListItemIcon>
|
|
19
|
+
<ListItemText primary={label} />
|
|
20
|
+
</ActionsMenuItem>
|
|
21
|
+
)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
Component.displayName = 'download'
|
|
25
|
+
|
|
26
|
+
return Component
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const download = ({ encryptedUrl }) => {
|
|
30
|
+
const { t } = getActionsI18n()
|
|
31
|
+
const icon = DownloadIcon
|
|
32
|
+
const label = t('download')
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
name: 'download',
|
|
36
|
+
icon,
|
|
37
|
+
label,
|
|
38
|
+
Component: makeComponent(label, icon),
|
|
39
|
+
action: (docs, { client, webviewIntent }) =>
|
|
40
|
+
downloadFile({ client, file: docs[0], url: encryptedUrl, webviewIntent })
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -5,6 +5,9 @@ export { smsTo } from './smsTo'
|
|
|
5
5
|
export { call } from './call'
|
|
6
6
|
export { emailTo } from './emailTo'
|
|
7
7
|
export { print } from './print'
|
|
8
|
+
export { download } from './download'
|
|
9
|
+
export { addToFavorites } from './addToFavorites'
|
|
10
|
+
export { removeFromFavorites } from './removeFromFavorites'
|
|
8
11
|
export { viewInContacts } from './viewInContacts'
|
|
9
12
|
export { viewInDrive } from './viewInDrive'
|
|
10
13
|
export { copyToClipboard } from './copyToClipboard'
|
|
@@ -5,6 +5,18 @@
|
|
|
5
5
|
"emailTo": "Send an email",
|
|
6
6
|
"smsTo": "Send a message",
|
|
7
7
|
"print": "Print",
|
|
8
|
+
"download": "Download",
|
|
9
|
+
"favorites": {
|
|
10
|
+
"add": {
|
|
11
|
+
"label": "Add to favorites",
|
|
12
|
+
"success": "%{filename} has been added to favorites |||| These items have been added to favorites"
|
|
13
|
+
},
|
|
14
|
+
"remove": {
|
|
15
|
+
"label": "Remove from favorites",
|
|
16
|
+
"success": "%{filename} has been removed from favorites |||| These items have been removed from favorites"
|
|
17
|
+
},
|
|
18
|
+
"error": "An error occurred, please try again."
|
|
19
|
+
},
|
|
8
20
|
"others": "Others",
|
|
9
21
|
"editAttribute": "Edit attribute",
|
|
10
22
|
"copyToClipboard": {
|
|
@@ -5,6 +5,18 @@
|
|
|
5
5
|
"emailTo": "Envoyer un e-mail",
|
|
6
6
|
"smsTo": "Envoyer un message",
|
|
7
7
|
"print": "Imprimer",
|
|
8
|
+
"download": "Télécharger",
|
|
9
|
+
"favorites": {
|
|
10
|
+
"add": {
|
|
11
|
+
"label": "Ajouter aux favoris",
|
|
12
|
+
"success": "%{filename} a été ajouté aux favoris |||| Ces éléments ont été ajoutés aux favoris"
|
|
13
|
+
},
|
|
14
|
+
"remove": {
|
|
15
|
+
"label": "Retirer des favoris",
|
|
16
|
+
"success": "%{filename} a été retiré des favoris |||| Ces éléments ont été retirés des favoris"
|
|
17
|
+
},
|
|
18
|
+
"error": "Une erreur est survenue, merci de réessayer."
|
|
19
|
+
},
|
|
8
20
|
"others": "Autres",
|
|
9
21
|
"editAttribute": "Editer l'attribut",
|
|
10
22
|
"copyToClipboard": {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react'
|
|
2
|
+
|
|
3
|
+
import { splitFilename } from 'cozy-client/dist/models/file'
|
|
4
|
+
|
|
5
|
+
import { getActionsI18n } from './locales/withActionsLocales'
|
|
6
|
+
import Icon from '../../Icon'
|
|
7
|
+
import StarIcon from '../../Icons/Star'
|
|
8
|
+
import ListItemIcon from '../../ListItemIcon'
|
|
9
|
+
import ListItemText from '../../ListItemText'
|
|
10
|
+
import ActionsMenuItem from '../ActionsMenuItem'
|
|
11
|
+
|
|
12
|
+
const makeComponent = (label, icon) => {
|
|
13
|
+
const Component = forwardRef((props, ref) => {
|
|
14
|
+
return (
|
|
15
|
+
<ActionsMenuItem {...props} ref={ref}>
|
|
16
|
+
<ListItemIcon>
|
|
17
|
+
<Icon icon={icon} />
|
|
18
|
+
</ListItemIcon>
|
|
19
|
+
<ListItemText primary={label} />
|
|
20
|
+
</ActionsMenuItem>
|
|
21
|
+
)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
Component.displayName = 'removeFromFavorites'
|
|
25
|
+
|
|
26
|
+
return Component
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const removeFromFavorites = ({ showAlert }) => {
|
|
30
|
+
const { t } = getActionsI18n()
|
|
31
|
+
const icon = StarIcon
|
|
32
|
+
const label = t('favorites.remove.label')
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
name: 'removeFromFavorites',
|
|
36
|
+
icon,
|
|
37
|
+
label,
|
|
38
|
+
displayCondition: docs =>
|
|
39
|
+
docs.length > 0 && docs.every(doc => doc.cozyMetadata?.favorite),
|
|
40
|
+
Component: makeComponent(label, icon),
|
|
41
|
+
action: async (docs, { client }) => {
|
|
42
|
+
try {
|
|
43
|
+
for (const doc of docs) {
|
|
44
|
+
await client.save({
|
|
45
|
+
...doc,
|
|
46
|
+
cozyMetadata: {
|
|
47
|
+
...doc.cozyMetadata,
|
|
48
|
+
favorite: false
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { filename } = splitFilename(docs[0])
|
|
54
|
+
showAlert({
|
|
55
|
+
message: t('favorites.success.remove', {
|
|
56
|
+
filename,
|
|
57
|
+
smart_count: docs.length
|
|
58
|
+
}),
|
|
59
|
+
severity: 'success'
|
|
60
|
+
})
|
|
61
|
+
} catch (error) {
|
|
62
|
+
showAlert({ message: t('favorites.error'), severity: 'error' })
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import cx from 'classnames'
|
|
2
|
+
import debounce from 'lodash/debounce'
|
|
2
3
|
import omit from 'lodash/omit'
|
|
3
4
|
import PropTypes from 'prop-types'
|
|
4
|
-
import React, { useState, useRef } from 'react'
|
|
5
|
+
import React, { useState, useRef, useMemo } from 'react'
|
|
5
6
|
|
|
6
7
|
import ItemRow from './ItemRow'
|
|
7
8
|
import { makeHistory } from './helpers'
|
|
8
9
|
import styles from './styles.styl'
|
|
9
|
-
import Input from '../Input'
|
|
10
10
|
import List from '../List'
|
|
11
|
+
import SearchBar from '../SearchBar'
|
|
11
12
|
import Typography from '../Typography'
|
|
12
13
|
|
|
13
14
|
export { ItemRow }
|
|
@@ -35,9 +36,13 @@ const NestedSelect = ({
|
|
|
35
36
|
const innerRef = useRef()
|
|
36
37
|
const [state, setState] = useState({
|
|
37
38
|
history: makeHistory(options, canSelectParent),
|
|
38
|
-
searchValue: ''
|
|
39
|
-
searchResult: []
|
|
39
|
+
searchValue: ''
|
|
40
40
|
})
|
|
41
|
+
const [searchResult, setSearchResult] = useState(null)
|
|
42
|
+
const delayedSetSearchResult = useMemo(
|
|
43
|
+
() => debounce(setSearchResult, 250),
|
|
44
|
+
[setSearchResult]
|
|
45
|
+
)
|
|
41
46
|
|
|
42
47
|
const handleBack = () => {
|
|
43
48
|
const [item, ...newHistory] = state.history
|
|
@@ -70,16 +75,21 @@ const NestedSelect = ({
|
|
|
70
75
|
if (onSearch) {
|
|
71
76
|
const searchValue = ev.target.value
|
|
72
77
|
const searchResult = onSearch(searchValue)
|
|
73
|
-
setState(state => ({ ...state, searchValue
|
|
78
|
+
setState(state => ({ ...state, searchValue }))
|
|
79
|
+
delayedSetSearchResult(searchResult)
|
|
74
80
|
}
|
|
75
81
|
}
|
|
76
82
|
|
|
83
|
+
const onClear = () => {
|
|
84
|
+
setState(state => ({ ...state, searchValue: '' }))
|
|
85
|
+
delayedSetSearchResult(null)
|
|
86
|
+
}
|
|
87
|
+
|
|
77
88
|
const current = state.history[0]
|
|
78
89
|
const children = current.children || []
|
|
79
90
|
const level = state.history.length - 1
|
|
80
91
|
const parentItem = transformParentItem(omit(current, 'children'))
|
|
81
92
|
|
|
82
|
-
const hasSearchResult = state.searchValue?.length > 0
|
|
83
93
|
const isSelectedWithLevel = item => isSelected(item, level)
|
|
84
94
|
const currentTitle = current.title || title
|
|
85
95
|
|
|
@@ -117,16 +127,17 @@ const NestedSelect = ({
|
|
|
117
127
|
[styles['search-container--without-title']]: !currentTitle
|
|
118
128
|
})}
|
|
119
129
|
>
|
|
120
|
-
<
|
|
130
|
+
<SearchBar
|
|
121
131
|
placeholder={searchOptions.placeholderSearch}
|
|
122
|
-
onChange={onChange}
|
|
123
132
|
value={state.searchValue}
|
|
133
|
+
onChange={onChange}
|
|
134
|
+
onClear={onClear}
|
|
124
135
|
/>
|
|
125
136
|
</div>
|
|
126
137
|
)}
|
|
127
138
|
|
|
128
|
-
{
|
|
129
|
-
|
|
139
|
+
{searchResult ? (
|
|
140
|
+
searchResult.length === 0 ? (
|
|
130
141
|
<Typography
|
|
131
142
|
variant="body1"
|
|
132
143
|
className="u-flex u-flex-justify-center u-mb-1 "
|
|
@@ -134,14 +145,14 @@ const NestedSelect = ({
|
|
|
134
145
|
{searchOptions.noDataLabel}
|
|
135
146
|
</Typography>
|
|
136
147
|
) : (
|
|
137
|
-
|
|
148
|
+
searchResult.map((item, index) => (
|
|
138
149
|
<ItemRow
|
|
139
150
|
radioPosition={radioPosition}
|
|
140
151
|
key={item.key || item.title}
|
|
141
152
|
item={item}
|
|
142
153
|
onClick={handleClickItem}
|
|
143
154
|
isSelected={isSelectedWithLevel(item)}
|
|
144
|
-
isLast={index ===
|
|
155
|
+
isLast={index === searchResult.length - 1}
|
|
145
156
|
ellipsis={ellipsis}
|
|
146
157
|
noDivider={noDivider}
|
|
147
158
|
/>
|
|
@@ -155,14 +155,7 @@ const InteractiveExample = () => {
|
|
|
155
155
|
onSearch: (value) => {
|
|
156
156
|
const options = makeOptions({ withHeaders })
|
|
157
157
|
return options.children.filter(o => o.description && o.description.toLowerCase().includes(value.toLowerCase()))
|
|
158
|
-
}
|
|
159
|
-
displaySearchResultItem: item =>
|
|
160
|
-
<ListItem key={item.id} dense button divider>
|
|
161
|
-
<ListItemText
|
|
162
|
-
primary={item.description}
|
|
163
|
-
ellipsis
|
|
164
|
-
/>
|
|
165
|
-
</ListItem>
|
|
158
|
+
}
|
|
166
159
|
})
|
|
167
160
|
|
|
168
161
|
const handleSelect = item => {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import '@testing-library/jest-dom'
|
|
3
|
-
import { render, fireEvent } from '@testing-library/react'
|
|
3
|
+
import { render, fireEvent, waitFor } from '@testing-library/react'
|
|
4
4
|
|
|
5
5
|
import NestedSelect from './NestedSelect'
|
|
6
|
-
import
|
|
6
|
+
import DemoProvider from '../providers/DemoProvider'
|
|
7
7
|
|
|
8
8
|
describe('NestedSelect', () => {
|
|
9
9
|
const makeOption = text => ({
|
|
@@ -40,7 +40,7 @@ describe('NestedSelect', () => {
|
|
|
40
40
|
const options = makeOptions(itemSelected)
|
|
41
41
|
|
|
42
42
|
return render(
|
|
43
|
-
<
|
|
43
|
+
<DemoProvider>
|
|
44
44
|
<NestedSelect
|
|
45
45
|
canSelectParent={canSelectParent}
|
|
46
46
|
options={options}
|
|
@@ -49,7 +49,7 @@ describe('NestedSelect', () => {
|
|
|
49
49
|
onCancel={onCancel}
|
|
50
50
|
searchOptions={searchOptions}
|
|
51
51
|
/>
|
|
52
|
-
</
|
|
52
|
+
</DemoProvider>
|
|
53
53
|
)
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -159,9 +159,11 @@ describe('NestedSelect', () => {
|
|
|
159
159
|
const searchInput = getByPlaceholderText('Placeholder Search')
|
|
160
160
|
expect(searchInput).toBeTruthy()
|
|
161
161
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
waitFor(() => {
|
|
163
|
+
fireEvent.change(searchInput, { target: { value: 'cozy' } })
|
|
164
|
+
const noData = getByText('No Data Found')
|
|
165
|
+
expect(noData).toBeTruthy()
|
|
166
|
+
})
|
|
165
167
|
})
|
|
166
168
|
|
|
167
169
|
it('should show search results', () => {
|
|
@@ -186,11 +188,12 @@ describe('NestedSelect', () => {
|
|
|
186
188
|
const searchInput = getByPlaceholderText('Placeholder Search')
|
|
187
189
|
expect(searchInput).toBeTruthy()
|
|
188
190
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
191
|
+
waitFor(() => {
|
|
192
|
+
fireEvent.change(searchInput, { target: { value: 'cozy' } })
|
|
193
|
+
expect(queryByText('cozy 1')).toBeTruthy()
|
|
194
|
+
expect(queryByText('cozy 1')).toBeTruthy()
|
|
195
|
+
expect(queryByText('anything')).toBeFalsy()
|
|
196
|
+
})
|
|
194
197
|
})
|
|
195
198
|
})
|
|
196
199
|
})
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { themesList } from 'cozy-client/dist/models/document/documentTypeData'
|
|
4
|
+
import { isQualificationNote } from 'cozy-client/dist/models/document/documentTypeDataHelpers'
|
|
5
|
+
import { getBoundT } from 'cozy-client/dist/models/document/locales'
|
|
6
|
+
|
|
7
|
+
import Icon from '../Icon'
|
|
8
|
+
import FileTypeNoteIcon from '../Icons/FileTypeNote'
|
|
9
|
+
import QualificationIconStack from '../QualificationIconStack'
|
|
10
|
+
|
|
11
|
+
export const makeOptions = lang => {
|
|
12
|
+
const qualifT = getBoundT(lang)
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
children: [
|
|
16
|
+
{
|
|
17
|
+
id: 'none',
|
|
18
|
+
title: qualifT('Scan.themes.none'),
|
|
19
|
+
icon: <QualificationIconStack />
|
|
20
|
+
},
|
|
21
|
+
...themesList.map(theme => ({
|
|
22
|
+
id: theme.id,
|
|
23
|
+
title: qualifT(`Scan.themes.${theme.label}`),
|
|
24
|
+
icon: <QualificationIconStack theme={theme.label} />,
|
|
25
|
+
children: theme.items.map(item => ({
|
|
26
|
+
id: item.label,
|
|
27
|
+
item,
|
|
28
|
+
title: qualifT(`Scan.items.${item.label}`),
|
|
29
|
+
icon: isQualificationNote(item) ? (
|
|
30
|
+
<Icon icon={FileTypeNoteIcon} size={64} />
|
|
31
|
+
) : (
|
|
32
|
+
<QualificationIconStack qualification={item.label} />
|
|
33
|
+
)
|
|
34
|
+
}))
|
|
35
|
+
}))
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const searchOptionsFn = (options, value) => {
|
|
41
|
+
const deepOptions = options.children
|
|
42
|
+
.flatMap(child => child.children)
|
|
43
|
+
.reduce((acc, curr) => {
|
|
44
|
+
if (!!curr && !acc.some(el => el.id === curr.id)) {
|
|
45
|
+
acc.push(curr)
|
|
46
|
+
}
|
|
47
|
+
return acc
|
|
48
|
+
}, [])
|
|
49
|
+
|
|
50
|
+
return deepOptions.filter(deepOption =>
|
|
51
|
+
deepOption.title.toLowerCase().includes(value.toLowerCase())
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const makeSearchOptions = ({ options, title, noDataLabel, t }) => {
|
|
56
|
+
return {
|
|
57
|
+
placeholderSearch: title || t('QualificationModal.title'),
|
|
58
|
+
noDataLabel: noDataLabel || t('QualificationModal.noDataLabel'),
|
|
59
|
+
onSearch: value => searchOptionsFn(options, value)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { searchOptionsFn } from './helpers'
|
|
2
|
+
|
|
3
|
+
describe('searchOptionsFn', () => {
|
|
4
|
+
it('should return only the uniq good result', () => {
|
|
5
|
+
const options = {
|
|
6
|
+
children: [
|
|
7
|
+
{ id: 'none', title: 'None' },
|
|
8
|
+
{
|
|
9
|
+
id: 'theme1',
|
|
10
|
+
title: 'Identity',
|
|
11
|
+
children: [
|
|
12
|
+
{ id: '01', title: 'Identity Photo' },
|
|
13
|
+
{ id: '02', title: 'ID card' }
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: 'theme2',
|
|
18
|
+
title: 'Family',
|
|
19
|
+
children: [
|
|
20
|
+
{ id: '03', title: 'Family record book' },
|
|
21
|
+
{ id: '04', title: 'Birth certificate' },
|
|
22
|
+
{ id: '01', title: 'Identity Photo' }
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const res = searchOptionsFn(options, 'photo')
|
|
29
|
+
|
|
30
|
+
expect(res).toStrictEqual([{ id: '01', title: 'Identity Photo' }])
|
|
31
|
+
})
|
|
32
|
+
})
|
|
@@ -2,54 +2,30 @@ import PropTypes from 'prop-types'
|
|
|
2
2
|
import React, { useMemo } from 'react'
|
|
3
3
|
|
|
4
4
|
import { useClient } from 'cozy-client'
|
|
5
|
-
import { themesList } from 'cozy-client/dist/models/document/documentTypeData'
|
|
6
|
-
import { isQualificationNote } from 'cozy-client/dist/models/document/documentTypeDataHelpers'
|
|
7
|
-
import { getBoundT } from 'cozy-client/dist/models/document/locales'
|
|
8
5
|
import { isSupportedQualification } from 'cozy-client/dist/models/document/qualification'
|
|
9
6
|
|
|
7
|
+
import { makeOptions, makeSearchOptions } from './helpers'
|
|
10
8
|
import { locales } from './locales'
|
|
11
|
-
import Icon from '../Icon'
|
|
12
|
-
import FileTypeNoteIcon from '../Icons/FileTypeNote'
|
|
13
9
|
import NestedSelectResponsive from '../NestedSelect/NestedSelectResponsive'
|
|
14
|
-
import QualificationIconStack from '../QualificationIconStack'
|
|
15
10
|
import { useI18n, useExtendI18n } from '../providers/I18n'
|
|
16
11
|
|
|
17
|
-
const
|
|
18
|
-
const qualifT = getBoundT(lang)
|
|
19
|
-
|
|
20
|
-
return {
|
|
21
|
-
children: [
|
|
22
|
-
{
|
|
23
|
-
id: 'none',
|
|
24
|
-
title: qualifT('Scan.themes.none'),
|
|
25
|
-
icon: <QualificationIconStack />
|
|
26
|
-
},
|
|
27
|
-
...themesList.map(theme => ({
|
|
28
|
-
id: theme.id,
|
|
29
|
-
title: qualifT(`Scan.themes.${theme.label}`),
|
|
30
|
-
icon: <QualificationIconStack theme={theme.label} />,
|
|
31
|
-
children: theme.items.map(item => ({
|
|
32
|
-
id: item.label,
|
|
33
|
-
item,
|
|
34
|
-
title: qualifT(`Scan.items.${item.label}`),
|
|
35
|
-
icon: isQualificationNote(item) ? (
|
|
36
|
-
<Icon icon={FileTypeNoteIcon} size={64} />
|
|
37
|
-
) : (
|
|
38
|
-
<QualificationIconStack qualification={item.label} />
|
|
39
|
-
)
|
|
40
|
-
}))
|
|
41
|
-
}))
|
|
42
|
-
]
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const QualificationModal = ({ file, title, onClose }) => {
|
|
12
|
+
const QualificationModal = ({ file, title, noDataLabel, onClose }) => {
|
|
47
13
|
useExtendI18n(locales)
|
|
48
14
|
const client = useClient()
|
|
49
15
|
const { t, lang } = useI18n()
|
|
50
16
|
|
|
51
17
|
const qualificationLabel = file.metadata?.qualification?.label
|
|
52
18
|
const options = useMemo(() => makeOptions(lang), [lang])
|
|
19
|
+
const searchOptions = useMemo(
|
|
20
|
+
() =>
|
|
21
|
+
makeSearchOptions({
|
|
22
|
+
options,
|
|
23
|
+
title,
|
|
24
|
+
noDataLabel,
|
|
25
|
+
t
|
|
26
|
+
}),
|
|
27
|
+
[options, title, noDataLabel, t]
|
|
28
|
+
)
|
|
53
29
|
|
|
54
30
|
const isSelected = ({ id, item }) => {
|
|
55
31
|
return isSupportedQualification(qualificationLabel)
|
|
@@ -74,11 +50,11 @@ const QualificationModal = ({ file, title, onClose }) => {
|
|
|
74
50
|
|
|
75
51
|
return (
|
|
76
52
|
<NestedSelectResponsive
|
|
77
|
-
title={title || t('QualificationModal.title')}
|
|
78
53
|
options={options}
|
|
79
54
|
noDivider
|
|
80
55
|
document={file}
|
|
81
56
|
isSelected={isSelected}
|
|
57
|
+
searchOptions={searchOptions}
|
|
82
58
|
onSelect={handleClick}
|
|
83
59
|
onClose={onClose}
|
|
84
60
|
/>
|
|
@@ -88,6 +64,7 @@ const QualificationModal = ({ file, title, onClose }) => {
|
|
|
88
64
|
QualificationModal.propTypes = {
|
|
89
65
|
file: PropTypes.object,
|
|
90
66
|
title: PropTypes.string,
|
|
67
|
+
noDataLabel: PropTypes.string,
|
|
91
68
|
onClose: PropTypes.func
|
|
92
69
|
}
|
|
93
70
|
|