Skip to content

Commit

Permalink
feat(ojoi-application): Adding addtions as html (#16797)
Browse files Browse the repository at this point in the history
* Addition logic now ready

* Removed unused imports.

* Moved strings to messages

* Update libs/application/templates/official-journal-of-iceland/src/components/additions/Additions.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Fixed PR comments

* Fixed onAddAddition handler

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Nov 12, 2024
1 parent f7726b2 commit 1e266af
Show file tree
Hide file tree
Showing 9 changed files with 394 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
import { useState } from 'react'
import { convertNumberToRoman, getAddition, isAddition } from '../../lib/utils'
import { additionSchema } from '../../lib/dataSchema'
import {
Stack,
Inline,
RadioButton,
Box,
Button,
Text,
} from '@island.is/island-ui/core'
import { HTMLText } from '@island.is/regulations-tools/types'
import { z } from 'zod'
import {
DEBOUNCE_INPUT_TIMER,
DEFAULT_ADDITIONS_COUNT,
MAXIMUM_ADDITIONS_COUNT,
} from '../../lib/constants'
import { HTMLEditor } from '../htmlEditor/HTMLEditor'
import { useApplication } from '../../hooks/useUpdateApplication'
import { getValueViaPath } from '@island.is/application/core'
import { useFormContext } from 'react-hook-form'
import { InputFields, OJOIApplication } from '../../lib/types'
import set from 'lodash/set'
import debounce from 'lodash/debounce'
import { useLocale } from '@island.is/localization'
import { attachments } from '../../lib/messages'

type Props = {
application: OJOIApplication
}

type Addition = z.infer<typeof additionSchema>[number]

export const Additions = ({ application }: Props) => {
const [asRoman, setAsRoman] = useState<boolean>(false)

const { formatMessage: f } = useLocale()
const { setValue } = useFormContext()
const { updateApplication, application: currentApplication } = useApplication(
{
applicationId: application.id,
},
)

const getAdditions = () => {
const additions = getValueViaPath(
currentApplication ? currentApplication.answers : application.answers,
InputFields.advert.additions,
)

return isAddition(additions)
? additions
: [getAddition(DEFAULT_ADDITIONS_COUNT, false)]
}

const onRemoveAddition = (index: number) => {
const filtered = additions.filter((_, i) => i !== index)
const mapped = filtered.map((addition, i) => {
if (addition.type !== 'html') return addition

const title = f(attachments.additions.title, {
index: asRoman ? convertNumberToRoman(i + 1) : i + 1,
})

return {
...addition,
title: title,
}
})

const currentAnswers = structuredClone(currentApplication.answers)

const updatedAnswers = set(
currentAnswers,
InputFields.advert.additions,
mapped,
)

setValue(InputFields.advert.additions, mapped)
updateApplication(updatedAnswers)
}

const onRomanChange = (val: boolean) => {
const handleTitleChange = (addition: Addition, i: number) => {
if (addition.type !== 'html') return addition

const title = f(attachments.additions.title, {
index: asRoman ? convertNumberToRoman(i + 1) : i + 1,
})
return {
...addition,
title: title,
}
}

const currentAnswers = structuredClone(currentApplication.answers)
const updatedAdditions = additions.map(handleTitleChange)

const updatedAnswers = set(
currentAnswers,
InputFields.advert.additions,
updatedAdditions,
)

setAsRoman(val)
setValue(InputFields.advert.additions, updatedAdditions)
updateApplication(updatedAnswers)
}

const onAddAddition = () => {
const currentAnswers = structuredClone(currentApplication.answers)
let currentAdditions = getValueViaPath(
currentAnswers,
InputFields.advert.additions,
)

if (!isAddition(currentAdditions)) {
currentAdditions = []
}

// TS not inferring the type after the check above
if (isAddition(currentAdditions)) {
const newAddition = getAddition(additions.length + 1, asRoman)

const updatedAdditions = [...currentAdditions, newAddition]
const updatedAnswers = set(
currentAnswers,
InputFields.advert.additions,
updatedAdditions,
)

setValue(InputFields.advert.additions, updatedAdditions)
updateApplication(updatedAnswers)
}
}

const onAdditionChange = (index: number, value: string) => {
const currentAnswers = structuredClone(currentApplication.answers)
const updatedAdditions = additions.map((addition, i) =>
i === index ? { ...addition, content: value } : addition,
)

const updatedAnswers = set(
currentAnswers,
InputFields.advert.additions,
updatedAdditions,
)

setValue(InputFields.advert.additions, updatedAdditions)
updateApplication(updatedAnswers)
}

const debouncedAdditionChange = debounce(
onAdditionChange,
DEBOUNCE_INPUT_TIMER,
)

const additionChangeHandler = (index: number, value: string) => {
debouncedAdditionChange.cancel()
debouncedAdditionChange(index, value)
}

const additions = getAdditions()

return (
<Stack space={2}>
<Text variant="h3">{f(attachments.inputs.radio.title.label)}</Text>
<Inline space={2}>
<RadioButton
label={f(attachments.inputs.radio.numeric.label)}
name="asNumbers"
checked={!asRoman}
onChange={() => onRomanChange(false)}
/>
<RadioButton
label={f(attachments.inputs.radio.roman.label)}
name="asRoman"
checked={asRoman}
onChange={() => onRomanChange(true)}
/>
</Inline>
<Stack space={4}>
{additions.map((addition, additionIndex) => {
const currentAddition = additions.at(additionIndex)

const defaultValue = currentAddition?.content || ''
return (
<Box
key={addition.id}
border="standard"
borderRadius="standard"
padding={2}
>
<Stack space={2}>
<Text variant="h3">{addition.title}</Text>
<HTMLEditor
controller={false}
name="addition"
key={addition.id}
value={defaultValue as HTMLText}
onChange={(value) =>
additionChangeHandler(additionIndex, value)
}
/>
<Button
variant="utility"
colorScheme="destructive"
icon="remove"
size="small"
onClick={() => onRemoveAddition(additionIndex)}
>
{f(attachments.buttons.removeAddition)}
</Button>
</Stack>
</Box>
)
})}
<Inline space={2} flexWrap="wrap">
<Button
disabled={additions.length >= MAXIMUM_ADDITIONS_COUNT}
variant="utility"
icon="add"
size="small"
onClick={onAddAddition}
>
{f(attachments.buttons.addAddition)}
</Button>
</Inline>
</Stack>
</Stack>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type Props = {
onChange?: (value: HTMLText) => void
error?: string
readOnly?: boolean
controller?: boolean
}

export const HTMLEditor = ({
Expand All @@ -24,6 +25,7 @@ export const HTMLEditor = ({
onChange,
hideWarnings,
readOnly = false,
controller = true,
}: Props) => {
const [initialValue, setInitalValue] = useState(value)
const valueRef = useRef(() => value)
Expand All @@ -42,7 +44,7 @@ export const HTMLEditor = ({
throw new Error('Not implemented')
}

return (
return controller ? (
<Controller
name={name}
defaultValue={initialValue}
Expand Down Expand Up @@ -82,5 +84,35 @@ export const HTMLEditor = ({
)
}}
/>
) : (
<>
{title && (
<Text marginBottom={2} variant="h5">
{title}
</Text>
)}
<Box
className={editorWrapper({
error: !!error,
})}
>
<Editor
readOnly={readOnly}
hideWarnings={hideWarnings}
elmRef={editorRef}
config={config}
fileUploader={fileUploader}
valueRef={valueRef}
classes={classes}
onChange={() => {
onChange && onChange(valueRef.current())
}}
onBlur={() => {
onChange && onChange(valueRef.current())
}}
/>
</Box>
{error && <div className={errorStyle}>{error}</div>}
</>
)
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { OJOIFieldBaseProps } from '../lib/types'
import { Box, InputFileUpload } from '@island.is/island-ui/core'
import { Box, Button, InputFileUpload, Stack } from '@island.is/island-ui/core'
import { useFileUpload } from '../hooks/useFileUpload'
import { ALLOWED_FILE_TYPES, ApplicationAttachmentType } from '../lib/constants'
import { useLocale } from '@island.is/localization'
import { attachments } from '../lib/messages/attachments'
import { useState } from 'react'
import { Additions } from '../components/additions/Additions'

export const Attachments = ({ application }: OJOIFieldBaseProps) => {
const { formatMessage: f } = useLocale()
Expand All @@ -12,22 +14,43 @@ export const Attachments = ({ application }: OJOIFieldBaseProps) => {
attachmentType: ApplicationAttachmentType.ADDITIONS,
})

const [asAddition, setAsAddition] = useState(true)

return (
<Box>
<InputFileUpload
header={f(attachments.inputs.fileUpload.header)}
description={f(attachments.inputs.fileUpload.description)}
buttonLabel={f(attachments.inputs.fileUpload.buttonLabel)}
fileList={files}
accept={ALLOWED_FILE_TYPES}
onChange={onChange}
onRemove={onRemove}
defaultFileBackgroundColor={{
background: 'blue100',
border: 'blue200',
icon: 'blue200',
}}
/>
</Box>
<Stack space={4}>
<Box>
<Button
icon={asAddition ? 'upload' : 'document'}
iconType="outline"
variant="ghost"
size="small"
onClick={() => setAsAddition((toggle) => !toggle)}
>
{f(
asAddition
? attachments.buttons.asAttachment
: attachments.buttons.asDocument,
)}
</Button>
</Box>
{!asAddition ? (
<Additions application={application} />
) : (
<InputFileUpload
header={f(attachments.inputs.fileUpload.header)}
description={f(attachments.inputs.fileUpload.description)}
buttonLabel={f(attachments.inputs.fileUpload.buttonLabel)}
fileList={files}
accept={ALLOWED_FILE_TYPES}
onChange={onChange}
onRemove={onRemove}
defaultFileBackgroundColor={{
background: 'blue100',
border: 'blue200',
icon: 'blue200',
}}
/>
)}
</Stack>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,5 @@ export const MAXIMUM_REGULAR_SIGNATURE_COUNT = 3
export const MINIMUM_COMMITTEE_SIGNATURE_MEMBER_COUNT = 2
export const DEFAULT_COMMITTEE_SIGNATURE_MEMBER_COUNT = 2
export const MAXIMUM_COMMITTEE_SIGNATURE_MEMBER_COUNT = 10
export const MAXIMUM_ADDITIONS_COUNT = 10
export const DEFAULT_ADDITIONS_COUNT = 1
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ export const memberItemSchema = z
})
.partial()

export const additionSchema = z.array(
z
.object({
id: z.string().optional(),
title: z.string().optional(),
content: z.string().optional(),
type: z.enum(['html', 'file']).optional(),
})
.partial(),
)

export const membersSchema = z.array(memberItemSchema).optional()

export const regularSignatureItemSchema = z
Expand Down Expand Up @@ -59,6 +70,7 @@ const advertSchema = z
categories: z.array(z.string()).optional(),
channels: z.array(channelSchema).optional(),
message: z.string().optional(),
additions: additionSchema.optional(),
})
.partial()

Expand Down
Loading

0 comments on commit 1e266af

Please sign in to comment.