3
3
// SPDX-License-Identifier: LGPL-2.1-or-later
4
4
5
5
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
6
- import React , { useCallback , useMemo } from 'react'
6
+ import React , { useCallback , useMemo , useRef } from 'react'
7
7
import styled from 'styled-components'
8
8
9
9
import { Attachment } from 'lib-common/generated/api-types/attachment'
@@ -18,8 +18,7 @@ import {
18
18
import {
19
19
IncomeStatementAttachments ,
20
20
numAttachments
21
- } from 'lib-common/income-statements'
22
- import { scrollToElement } from 'lib-common/utils/scrolling'
21
+ } from 'lib-common/income-statements/attachments'
23
22
import UnorderedList from 'lib-components/atoms/UnorderedList'
24
23
import { Button } from 'lib-components/atoms/buttons/Button'
25
24
import { ContentArea } from 'lib-components/layout/Container'
@@ -45,7 +44,7 @@ import {
45
44
SetStateCallback
46
45
} from './IncomeStatementComponents'
47
46
48
- function attachmentSectionId ( type : IncomeStatementAttachmentType ) : string {
47
+ function attachmentSectionDataQa ( type : IncomeStatementAttachmentType ) : string {
49
48
return `attachment-section-${ type } `
50
49
}
51
50
@@ -58,80 +57,104 @@ export interface AttachmentHandler {
58
57
onDeleted : ( id : AttachmentId ) => void
59
58
getDownloadUrl : ( id : AttachmentId ) => string
60
59
}
60
+ setElement : (
61
+ attachmentType : IncomeStatementAttachmentType ,
62
+ el : HTMLElement | null
63
+ ) => void
64
+ focus : ( attachmentType : IncomeStatementAttachmentType ) => void
61
65
}
62
66
63
67
/** Returns `undefined` if the income statement contains old untyped attachments */
64
- export function makeAttachmentHandler (
68
+ export function useAttachmentHandler (
65
69
id : IncomeStatementId | undefined ,
66
70
attachments : IncomeStatementAttachments ,
67
71
onChange : SetStateCallback < IncomeStatementAttachments >
68
72
) : AttachmentHandler | undefined {
69
- if ( ! attachments . typed ) {
70
- // Has untyped attachments
71
- return undefined
72
- }
73
- const { attachmentsByType } = attachments
74
- return {
75
- hasAttachment : ( attachmentType : IncomeStatementAttachmentType ) =>
76
- ! ! attachmentsByType [ attachmentType ] ?. length ,
77
- fileUploadProps : ( attachmentType : IncomeStatementAttachmentType ) => {
78
- const files = attachmentsByType [ attachmentType ] ?? [ ]
79
- return {
80
- files,
81
- uploadHandler : incomeStatementAttachment ( id , attachmentType ) ,
82
- onUploaded : ( attachment : Attachment ) => {
83
- onChange ( ( prev ) => {
84
- // Should not happen
85
- if ( ! prev . typed ) return prev
73
+ const refs = useRef <
74
+ Partial < Record < IncomeStatementAttachmentType , HTMLElement > >
75
+ > ( { } )
76
+ return useMemo ( ( ) => {
77
+ if ( ! attachments . typed ) {
78
+ // Has untyped attachments
79
+ return undefined
80
+ }
81
+ const { attachmentsByType } = attachments
82
+ return {
83
+ hasAttachment : ( attachmentType : IncomeStatementAttachmentType ) =>
84
+ ! ! attachmentsByType [ attachmentType ] ?. length ,
85
+ fileUploadProps : ( attachmentType : IncomeStatementAttachmentType ) => {
86
+ const files = attachmentsByType [ attachmentType ] ?? [ ]
87
+ return {
88
+ files,
89
+ uploadHandler : incomeStatementAttachment ( id , attachmentType ) ,
90
+ onUploaded : ( attachment : Attachment ) => {
91
+ onChange ( ( prev ) => {
92
+ // Should not happen
93
+ if ( ! prev . typed ) return prev
86
94
87
- const { attachmentsByType } = prev
88
- if ( attachmentsByType [ attachmentType ] ) {
89
- return {
90
- ...prev ,
91
- attachmentsByType : {
92
- ...attachmentsByType ,
93
- [ attachmentType ] : [
94
- ...attachmentsByType [ attachmentType ] ,
95
- attachment
96
- ]
95
+ const { attachmentsByType } = prev
96
+ if ( attachmentsByType [ attachmentType ] ) {
97
+ return {
98
+ ...prev ,
99
+ attachmentsByType : {
100
+ ...attachmentsByType ,
101
+ [ attachmentType ] : [
102
+ ...attachmentsByType [ attachmentType ] ,
103
+ attachment
104
+ ]
105
+ }
97
106
}
98
- }
99
- } else {
100
- return {
101
- ... prev ,
102
- attachmentsByType : {
103
- ... attachmentsByType ,
104
- [ attachmentType ] : [ attachment ]
107
+ } else {
108
+ return {
109
+ ... prev ,
110
+ attachmentsByType : {
111
+ ... attachmentsByType ,
112
+ [ attachmentType ] : [ attachment ]
113
+ }
105
114
}
106
115
}
107
- }
108
- } )
109
- } ,
110
- onDeleted : ( id : AttachmentId ) => {
111
- onChange ( ( prev ) => {
112
- // Should not happen
113
- if ( ! prev . typed ) return prev
116
+ } )
117
+ } ,
118
+ onDeleted : ( id : AttachmentId ) => {
119
+ onChange ( ( prev ) => {
120
+ // Should not happen
121
+ if ( ! prev . typed ) return prev
114
122
115
- const { attachmentsByType } = prev
116
- if ( attachmentsByType [ attachmentType ] ) {
117
- return {
118
- ...prev ,
119
- attachmentsByType : {
120
- ...attachmentsByType ,
121
- [ attachmentType ] : attachmentsByType [ attachmentType ] . filter (
122
- ( a ) => a . id !== id
123
- )
123
+ const { attachmentsByType } = prev
124
+ if ( attachmentsByType [ attachmentType ] ) {
125
+ return {
126
+ ...prev ,
127
+ attachmentsByType : {
128
+ ...attachmentsByType ,
129
+ [ attachmentType ] : attachmentsByType [ attachmentType ] . filter (
130
+ ( a ) => a . id !== id
131
+ )
132
+ }
124
133
}
134
+ } else {
135
+ return prev
125
136
}
126
- } else {
127
- return prev
128
- }
129
- } )
130
- } ,
131
- getDownloadUrl : ( ) => ''
137
+ } )
138
+ } ,
139
+ getDownloadUrl : ( ) => ''
140
+ }
141
+ } ,
142
+ setElement : (
143
+ attachmentType : IncomeStatementAttachmentType ,
144
+ el : HTMLElement | null
145
+ ) => {
146
+ if ( el ) {
147
+ refs . current [ attachmentType ] = el
148
+ } else {
149
+ delete refs . current [ attachmentType ]
150
+ }
151
+ } ,
152
+ focus : ( attachmentType : IncomeStatementAttachmentType ) => {
153
+ const element = refs . current [ attachmentType ]
154
+ if ( element ) element . focus ( )
132
155
}
133
156
}
134
- }
157
+ } , [ attachments , id , onChange ] )
135
158
}
136
159
137
160
export const AttachmentSection = React . memo ( function AttachmentSection ( {
@@ -176,7 +199,8 @@ export const AttachmentSection = React.memo(function AttachmentSection({
176
199
{ labelAndInfo }
177
200
{ ! dense && < Gap size = "xs" /> }
178
201
< FileUpload
179
- id = { attachmentSectionId ( attachmentType ) }
202
+ ref = { ( el ) => attachmentHandler . setElement ( attachmentType , el ) }
203
+ data-qa = { attachmentSectionDataQa ( attachmentType ) }
180
204
{ ...fileUploadProps ( attachmentType ) }
181
205
/>
182
206
</ >
@@ -209,12 +233,7 @@ export const IncomeStatementMissingAttachments = React.memo(
209
233
< Button
210
234
appearance = "link"
211
235
onClick = { ( ) => {
212
- const element = document . getElementById (
213
- attachmentSectionId ( attachmentType )
214
- )
215
- if ( element ) {
216
- scrollToElement ( element , 0 , 'center' )
217
- }
236
+ attachmentHandler . focus ( attachmentType )
218
237
} }
219
238
text = { t . income . attachments . attachmentNames [ attachmentType ] }
220
239
/>
@@ -373,14 +392,10 @@ export const CitizenAttachmentsWithUpload = React.memo(
373
392
onChange : SetStateCallback < IncomeStatementAttachments >
374
393
} ) {
375
394
const t = useTranslation ( )
376
- const attachmentHandler = useMemo (
377
- ( ) =>
378
- makeAttachmentHandler (
379
- incomeStatementId ,
380
- incomeStatementAttachments ,
381
- onChange
382
- ) ,
383
- [ incomeStatementAttachments , incomeStatementId , onChange ]
395
+ const attachmentHandler = useAttachmentHandler (
396
+ incomeStatementId ,
397
+ incomeStatementAttachments ,
398
+ onChange
384
399
)
385
400
386
401
if ( ! incomeStatementAttachments . typed ) {
0 commit comments