Skip to content

Commit

Permalink
Add uploads field to logbook prior notification form
Browse files Browse the repository at this point in the history
  • Loading branch information
ivangabriele committed Aug 30, 2024
1 parent 9aee6bb commit a25bd9e
Show file tree
Hide file tree
Showing 9 changed files with 208 additions and 11 deletions.
1 change: 1 addition & 0 deletions frontend/src/api/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export enum HttpStatusCode {

export enum RtkCacheTagType {
PriorNotification = 'PriorNotification',
PriorNotificationDocuments = 'PriorNotificationDocuments',
PriorNotificationTypes = 'PriorNotificationTypes',
PriorNotifications = 'PriorNotifications',
PriorNotificationsToVerify = 'PriorNotificationsToVerify',
Expand Down
46 changes: 46 additions & 0 deletions frontend/src/auth/hooks/useAuthRequestHeaders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { sha256 } from '@utils/sha256'
import { getOIDCConfig } from 'auth/getOIDCConfig'
import { getOIDCUser } from 'auth/getOIDCUser'
import { useCallback, useEffect, useState } from 'react'

const { IS_OIDC_ENABLED } = getOIDCConfig()

/**
* Hook to get API request headers required for OIDC authentication.
*
* If the returned headers are `undefined`,
* it means that you need to wait for the headers to be updated before making your request.
* This is because the headers are updated asynchronously (`await sha256(nextToken)`).
*/
export function useAuthRequestHeaders(): Record<string, string> | undefined {
const [headers, setHeaders] = useState<Record<string, string> | undefined>(undefined)

const user = getOIDCUser()
const token = user?.access_token

const updateHeaders = useCallback(async (nextToken: string | undefined) => {
if (!IS_OIDC_ENABLED) {
setHeaders({})

return
}
if (!nextToken) {
setHeaders(undefined)

return
}

const nextHeaders = {
authorization: `Bearer ${nextToken}`,
...(crypto?.subtle ? { 'x-correlation-id': await sha256(nextToken) } : {})
}

setHeaders(nextHeaders)
}, [])

useEffect(() => {
updateHeaders(token)
}, [token, updateHeaders])

return headers
}
10 changes: 10 additions & 0 deletions frontend/src/features/PriorNotification/PriorNotification.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,16 @@ export namespace PriorNotification {
name: string
}

export type Upload = {
createdAt: string
fileName: string
id: string
isManualPriorNotification: boolean
mimeType: string
reportId: string
updatedAt: string
}

export enum PurposeCode {
ACS = 'ACS',
ECY = 'ECY',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { useCallback, useMemo, useState } from 'react'
import styled from 'styled-components'
import { useDebouncedCallback } from 'use-debounce'

import { UploadFiles } from '../shared/UploadFiles'

import type { PriorNotification } from '@features/PriorNotification/PriorNotification.types'

type FormProps = Readonly<{
Expand Down Expand Up @@ -76,6 +78,10 @@ export function Form({ detail, initialFormValues }: FormProps) {
</FieldGroup>

{isSuperUser && <AuthorTrigramInput label="Par" name="authorTrigram" readOnly={isReadOnly} />}

<hr />

<UploadFiles isManualPriorNotification={false} reportId={detail.reportId} />
</>
</Formik>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
useGetPriorNotificationPdfExistenceQuery
} from '@features/PriorNotification/priorNotificationApi'
import { Accent, Button, customDayjs, Dropdown, Icon } from '@mtes-mct/monitor-ui'
import { downloadAsPdf } from '@utils/downloadAsPdf'
import { downloadFile } from '@utils/downloadFile'
import printJS from 'print-js'
import { useMemo } from 'react'

Expand Down Expand Up @@ -71,9 +71,9 @@ export function DownloadButton({
const response = await monitorfishApiKy.get(url)
const blob = await response.blob()
const generationDate = response.headers.get('x-generation-date')
const fileName = `preavis_debarquement_${generationDate}`
const fileName = `preavis_debarquement_${generationDate}.pdf`

downloadAsPdf(fileName, blob)
downloadFile(fileName, 'application/pdf', blob)
}

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { monitorfishApiKy } from '@api/api'
import { RtkCacheTagType } from '@api/constants'
import {
priorNotificationApi,
useGetPriorNotificationUploadsQuery
} from '@features/PriorNotification/priorNotificationApi'
import { useMainAppDispatch } from '@hooks/useMainAppDispatch'
import { useKey } from '@mtes-mct/monitor-ui'
import { assertNotNullish } from '@utils/assertNotNullish'
import { downloadFile } from '@utils/downloadFile'
import { useAuthRequestHeaders } from 'auth/hooks/useAuthRequestHeaders'
import { useCallback, useMemo } from 'react'
import { Uploader } from 'rsuite'
import styled from 'styled-components'

import type { FileType } from 'rsuite/esm/Uploader'

type UploadFilesProps = Readonly<{
isManualPriorNotification: boolean
reportId: string
}>
export function UploadFiles({ isManualPriorNotification, reportId }: UploadFilesProps) {
const dispatch = useMainAppDispatch()
const headers = useAuthRequestHeaders()

const action = `/bff/v1/prior_notifications/${reportId}/uploads?isManualPriorNotification=${isManualPriorNotification}`
const { data: uploads } = useGetPriorNotificationUploadsQuery(reportId)

const key = useKey([uploads])
const uploadAsFileTypes: FileType[] | undefined = useMemo(
() =>
uploads
? uploads.map(upload => ({
fileKey: upload.id,
mimeType: upload.mimeType,
name: upload.fileName
}))
: undefined,
[uploads]
)

const download = useCallback(
async (fileType: FileType) => {
assertNotNullish(fileType.fileKey)
assertNotNullish(fileType.name)

const url = `/bff/v1/prior_notifications/${reportId}/uploads/${fileType.fileKey}`
const response = await monitorfishApiKy.get(url)
const blob = await response.blob()

downloadFile(fileType.name, (fileType as any).mimeType, blob)
},
[reportId]
)

const refetch = useCallback(() => {
dispatch(priorNotificationApi.util.invalidateTags([RtkCacheTagType.PriorNotificationDocuments]))
}, [dispatch])

const remove = useCallback(
async (file: FileType) => {
await dispatch(
priorNotificationApi.endpoints.deletePriorNotificationUpload.initiate({
priorNotificationUploadId: file.fileKey,
reportId
})
).unwrap()
},
[dispatch, reportId]
)

if (!headers || !uploadAsFileTypes) {
return <></>
}

return (
<Wrapper>
<Uploader
key={key}
action={action}
defaultFileList={uploadAsFileTypes}
draggable
headers={headers}
onPreview={download}
onRemove={remove}
onSuccess={refetch}
>
<File>Glissez ou déposez des fichier à ajouter au préavis.</File>
</Uploader>
</Wrapper>
)
}

const Wrapper = styled.div`
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 8px;
> .rs-uploader {
> .rs-uploader-file-items {
> .rs-uploader-file-item {
> .rs-uploader-file-item-panel {
cursor: pointer;
}
}
}
}
`

const File = styled.div`
font-style: italic;
color: ${p => p.theme.color.slateGray};
padding: 24px 90px;
`
21 changes: 20 additions & 1 deletion frontend/src/features/PriorNotification/priorNotificationApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import type { LogbookMessage } from '@features/Logbook/LogbookMessage.types'
const COMPUTE_PRIOR_NOTIFICATION_ERROR_MESSAGE =
"Nous n'avons pas pu calculer note de risque, segments ou types pour ce préavis."
const CREATE_PRIOR_NOTIFICATION_ERROR_MESSAGE = "Nous n'avons pas pu créé le préavis."
const DELETE_PRIOR_NOTIFICATION_UPLOAD_ERROR_MESSAGE = "Nous n'avons pas pu supprimer ce document attaché."
const GET_PRIOR_NOTIFICATION_UPLOADS_ERROR_MESSAGE =
"Nous n'avons pas pu récupérer les documents attachés à ce préavis."
const UPDATE_PRIOR_NOTIFICATION_ERROR_MESSAGE = "Nous n'avons pas pu modifier le préavis."
const GET_PRIOR_NOTIFICATION_DETAIL_ERROR_MESSAGE = "Nous n'avons pas pu récupérer le préavis."
const GET_PRIOR_NOTIFICATIONS_ERROR_MESSAGE = "Nous n'avons pas pu récupérer la liste des préavis."
Expand Down Expand Up @@ -54,6 +57,15 @@ export const priorNotificationApi = monitorfishApi.injectEndpoints({
transformErrorResponse: response => new FrontendApiError(CREATE_PRIOR_NOTIFICATION_ERROR_MESSAGE, response)
}),

deletePriorNotificationUpload: builder.mutation<void, { priorNotificationUploadId; reportId: string }>({
invalidatesTags: [{ type: RtkCacheTagType.PriorNotificationDocuments }],
query: ({ priorNotificationUploadId, reportId }) => ({
method: 'DELETE',
url: `/prior_notifications/${reportId}/uploads/${priorNotificationUploadId}`
}),
transformErrorResponse: response => new FrontendApiError(DELETE_PRIOR_NOTIFICATION_UPLOAD_ERROR_MESSAGE, response)
}),

getPriorNotificationDetail: builder.query<
PriorNotification.Detail,
PriorNotification.Identifier & {
Expand Down Expand Up @@ -117,6 +129,12 @@ export const priorNotificationApi = monitorfishApi.injectEndpoints({
transformErrorResponse: response => new FrontendApiError(GET_PRIOR_NOTIFICATION_TYPES_ERROR_MESSAGE, response)
}),

getPriorNotificationUploads: builder.query<PriorNotification.Upload[], string>({
providesTags: () => [{ type: RtkCacheTagType.PriorNotificationDocuments }],
query: reportId => `/prior_notifications/${reportId}/uploads`,
transformErrorResponse: response => new FrontendApiError(GET_PRIOR_NOTIFICATION_UPLOADS_ERROR_MESSAGE, response)
}),

invalidatePriorNotification: builder.mutation<
PriorNotification.Detail,
PriorNotification.Identifier & {
Expand Down Expand Up @@ -203,5 +221,6 @@ export const {
useGetPriorNotificationPdfExistenceQuery,
useGetPriorNotificationsQuery,
useGetPriorNotificationsToVerifyQuery,
useGetPriorNotificationTypesQuery
useGetPriorNotificationTypesQuery,
useGetPriorNotificationUploadsQuery
} = priorNotificationApi
7 changes: 0 additions & 7 deletions frontend/src/utils/downloadAsPdf.ts

This file was deleted.

7 changes: 7 additions & 0 deletions frontend/src/utils/downloadFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function downloadFile(fileName: string, mimeType: string, blob: Blob) {
const link = document.createElement('a')
link.href = URL.createObjectURL(new Blob([blob], { type: mimeType }))
link.download = fileName

link.click()
}

0 comments on commit a25bd9e

Please sign in to comment.