1
1
import type { StackScreenProps } from '@react-navigation/stack' ;
2
- import React , { useEffect , useMemo , useState } from 'react' ;
2
+ import React , { useEffect , useMemo , useRef , useState } from 'react' ;
3
3
import { ActivityIndicator , View } from 'react-native' ;
4
4
import { withOnyx } from 'react-native-onyx' ;
5
5
import type { OnyxEntry } from 'react-native-onyx' ;
6
6
import Button from '@components/Button' ;
7
+ import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu' ;
8
+ import type { DropdownOption } from '@components/ButtonWithDropdownMenu/types' ;
9
+ import ConfirmModal from '@components/ConfirmModal' ;
7
10
import HeaderWithBackButton from '@components/HeaderWithBackButton' ;
8
11
import Icon from '@components/Icon' ;
9
12
import * as Expensicons from '@components/Icon/Expensicons' ;
@@ -31,13 +34,15 @@ import ONYXKEYS from '@src/ONYXKEYS';
31
34
import ROUTES from '@src/ROUTES' ;
32
35
import type SCREENS from '@src/SCREENS' ;
33
36
import type * as OnyxTypes from '@src/types/onyx' ;
37
+ import type DeepValueOf from '@src/types/utils/DeepValueOf' ;
34
38
35
39
type PolicyForList = {
36
40
value : string ;
37
41
text : string ;
38
42
keyForList : string ;
39
43
isSelected : boolean ;
40
44
rightElement : React . ReactNode ;
45
+ enabled : boolean ;
41
46
} ;
42
47
43
48
type PolicyOption = ListItem & {
@@ -58,6 +63,8 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) {
58
63
const theme = useTheme ( ) ;
59
64
const { translate} = useLocalize ( ) ;
60
65
const [ selectedTags , setSelectedTags ] = useState < Record < string , boolean > > ( { } ) ;
66
+ const dropdownButtonRef = useRef ( null ) ;
67
+ const [ deleteTagsConfirmModalVisible , setDeleteTagsConfirmModalVisible ] = useState ( false ) ;
61
68
62
69
function fetchTags ( ) {
63
70
Policy . openPolicyTagsPage ( route . params . policyID ) ;
@@ -84,6 +91,7 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) {
84
91
isSelected : ! ! selectedTags [ value . name ] ,
85
92
pendingAction : value . pendingAction ,
86
93
errors : value . errors ?? undefined ,
94
+ enabled : value . enabled ,
87
95
rightElement : (
88
96
< View style = { styles . flexRow } >
89
97
< Text style = { [ styles . textSupporting , styles . alignSelfCenter , styles . pl2 , styles . label ] } >
@@ -103,6 +111,11 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) {
103
111
[ policyTagLists , selectedTags , styles . alignSelfCenter , styles . flexRow , styles . label , styles . p1 , styles . pl2 , styles . textSupporting , theme . icon , translate ] ,
104
112
) ;
105
113
114
+ const tagListKeyedByName = tagList . reduce < Record < string , PolicyForList > > ( ( acc , tag ) => {
115
+ acc [ tag . value ] = tag ;
116
+ return acc ;
117
+ } , { } ) ;
118
+
106
119
const toggleTag = ( tag : PolicyForList ) => {
107
120
setSelectedTags ( ( prev ) => ( {
108
121
...prev ,
@@ -135,29 +148,108 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) {
135
148
Navigation . navigate ( ROUTES . WORKSPACE_TAG_SETTINGS . getRoute ( route . params . policyID , tag . keyForList ) ) ;
136
149
} ;
137
150
151
+ const selectedTagsArray = Object . keys ( selectedTags ) . filter ( ( key ) => selectedTags [ key ] ) ;
152
+
153
+ const handleDeleteTags = ( ) => {
154
+ setSelectedTags ( { } ) ;
155
+ Policy . deletePolicyTags ( route . params . policyID , selectedTagsArray ) ;
156
+ setDeleteTagsConfirmModalVisible ( false ) ;
157
+ } ;
158
+
138
159
const isLoading = ! isOffline && policyTags === undefined ;
139
160
140
- const headerButtons = (
141
- < View style = { [ styles . w100 , styles . flexRow , isSmallScreenWidth && styles . mb3 ] } >
142
- < Button
143
- medium
144
- success
145
- onPress = { navigateToCreateTagPage }
146
- icon = { Expensicons . Plus }
147
- text = { translate ( 'workspace.tags.addTag' ) }
148
- style = { [ styles . mr3 , isSmallScreenWidth && styles . w50 ] }
149
- />
150
- { policyTags && (
161
+ const getHeaderButtons = ( ) => {
162
+ const options : Array < DropdownOption < DeepValueOf < typeof CONST . POLICY . TAGS_BULK_ACTION_TYPES > > > = [ ] ;
163
+
164
+ if ( selectedTagsArray . length > 0 ) {
165
+ options . push ( {
166
+ icon : Expensicons . Trashcan ,
167
+ text : translate ( selectedTagsArray . length === 1 ? 'workspace.tags.deleteTag' : 'workspace.tags.deleteTags' ) ,
168
+ value : CONST . POLICY . TAGS_BULK_ACTION_TYPES . DELETE ,
169
+ onSelected : ( ) => setDeleteTagsConfirmModalVisible ( true ) ,
170
+ } ) ;
171
+
172
+ const enabledTags = selectedTagsArray . filter ( ( tagName ) => tagListKeyedByName ?. [ tagName ] ?. enabled ) ;
173
+ if ( enabledTags . length > 0 ) {
174
+ const tagsToDisable = selectedTagsArray
175
+ . filter ( ( tagName ) => tagListKeyedByName ?. [ tagName ] ?. enabled )
176
+ . reduce < Record < string , { name : string ; enabled : boolean } > > ( ( acc , tagName ) => {
177
+ acc [ tagName ] = {
178
+ name : tagName ,
179
+ enabled : false ,
180
+ } ;
181
+ return acc ;
182
+ } , { } ) ;
183
+
184
+ options . push ( {
185
+ icon : Expensicons . DocumentSlash ,
186
+ text : translate ( enabledTags . length === 1 ? 'workspace.tags.disableTag' : 'workspace.tags.disableTags' ) ,
187
+ value : CONST . POLICY . TAGS_BULK_ACTION_TYPES . DISABLE ,
188
+ onSelected : ( ) => {
189
+ setSelectedTags ( { } ) ;
190
+ Policy . setWorkspaceTagEnabled ( route . params . policyID , tagsToDisable ) ;
191
+ } ,
192
+ } ) ;
193
+ }
194
+
195
+ const disabledTags = selectedTagsArray . filter ( ( tagName ) => ! tagListKeyedByName ?. [ tagName ] ?. enabled ) ;
196
+ if ( disabledTags . length > 0 ) {
197
+ const tagsToEnable = selectedTagsArray
198
+ . filter ( ( tagName ) => ! tagListKeyedByName ?. [ tagName ] ?. enabled )
199
+ . reduce < Record < string , { name : string ; enabled : boolean } > > ( ( acc , tagName ) => {
200
+ acc [ tagName ] = {
201
+ name : tagName ,
202
+ enabled : true ,
203
+ } ;
204
+ return acc ;
205
+ } , { } ) ;
206
+ options . push ( {
207
+ icon : Expensicons . Document ,
208
+ text : translate ( disabledTags . length === 1 ? 'workspace.tags.enableTag' : 'workspace.tags.enableTags' ) ,
209
+ value : CONST . POLICY . TAGS_BULK_ACTION_TYPES . ENABLE ,
210
+ onSelected : ( ) => {
211
+ setSelectedTags ( { } ) ;
212
+ Policy . setWorkspaceTagEnabled ( route . params . policyID , tagsToEnable ) ;
213
+ } ,
214
+ } ) ;
215
+ }
216
+
217
+ return (
218
+ < ButtonWithDropdownMenu
219
+ buttonRef = { dropdownButtonRef }
220
+ onPress = { ( ) => null }
221
+ shouldAlwaysShowDropdownMenu
222
+ pressOnEnter
223
+ buttonSize = { CONST . DROPDOWN_BUTTON_SIZE . MEDIUM }
224
+ customText = { translate ( 'workspace.common.selected' , { selectedNumber : selectedTagsArray . length } ) }
225
+ options = { options }
226
+ style = { [ isSmallScreenWidth && styles . flexGrow1 , isSmallScreenWidth && styles . mb3 ] }
227
+ />
228
+ ) ;
229
+ }
230
+
231
+ return (
232
+ < View style = { [ styles . w100 , styles . flexRow , isSmallScreenWidth && styles . mb3 ] } >
151
233
< Button
152
234
medium
153
- onPress = { navigateToTagsSettings }
154
- icon = { Expensicons . Gear }
155
- text = { translate ( 'common.settings' ) }
156
- style = { [ isSmallScreenWidth && styles . w50 ] }
235
+ success
236
+ onPress = { navigateToCreateTagPage }
237
+ icon = { Expensicons . Plus }
238
+ text = { translate ( 'workspace.tags.addTag' ) }
239
+ style = { [ styles . mr3 , isSmallScreenWidth && styles . w50 ] }
157
240
/>
158
- ) }
159
- </ View >
160
- ) ;
241
+ { policyTags && (
242
+ < Button
243
+ medium
244
+ onPress = { navigateToTagsSettings }
245
+ icon = { Expensicons . Gear }
246
+ text = { translate ( 'common.settings' ) }
247
+ style = { [ isSmallScreenWidth && styles . w50 ] }
248
+ />
249
+ ) }
250
+ </ View >
251
+ ) ;
252
+ } ;
161
253
162
254
return (
163
255
< AdminPolicyAccessOrNotFoundWrapper policyID = { route . params . policyID } >
@@ -173,9 +265,19 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) {
173
265
title = { translate ( 'workspace.common.tags' ) }
174
266
shouldShowBackButton = { isSmallScreenWidth }
175
267
>
176
- { ! isSmallScreenWidth && headerButtons }
268
+ { ! isSmallScreenWidth && getHeaderButtons ( ) }
177
269
</ HeaderWithBackButton >
178
- { isSmallScreenWidth && < View style = { [ styles . pl5 , styles . pr5 ] } > { headerButtons } </ View > }
270
+ < ConfirmModal
271
+ isVisible = { deleteTagsConfirmModalVisible }
272
+ onConfirm = { handleDeleteTags }
273
+ onCancel = { ( ) => setDeleteTagsConfirmModalVisible ( false ) }
274
+ title = { translate ( selectedTagsArray . length === 1 ? 'workspace.tags.deleteTag' : 'workspace.tags.deleteTags' ) }
275
+ prompt = { translate ( selectedTagsArray . length === 1 ? 'workspace.tags.deleteTagConfirmation' : 'workspace.tags.deleteTagsConfirmation' ) }
276
+ confirmText = { translate ( 'common.delete' ) }
277
+ cancelText = { translate ( 'common.cancel' ) }
278
+ danger
279
+ />
280
+ { isSmallScreenWidth && < View style = { [ styles . pl5 , styles . pr5 ] } > { getHeaderButtons ( ) } </ View > }
179
281
< View style = { [ styles . ph5 , styles . pb5 , styles . pt3 ] } >
180
282
< Text style = { [ styles . textNormal , styles . colorMuted ] } > { translate ( 'workspace.tags.subtitle' ) } </ Text >
181
283
</ View >
0 commit comments