diff --git a/apps/judicial-system/web/messages/Core/errors.ts b/apps/judicial-system/web/messages/Core/errors.ts index 406e594e9e3e..3123d4e8745c 100644 --- a/apps/judicial-system/web/messages/Core/errors.ts +++ b/apps/judicial-system/web/messages/Core/errors.ts @@ -12,18 +12,36 @@ export const errors = defineMessages({ description: 'Notaður sem villuskilaboð þegar ekki gengur að uppfæra varnaraðila', }, + updateCivilClaimant: { + id: 'judicial.system.core:errors.update_civil_claimant', + defaultMessage: 'Upp kom villa við að uppfæra kröfuhafa', + description: + 'Notaður sem villuskilaboð þegar ekki gengur að uppfæra kröfuhafa', + }, createDefendant: { id: 'judicial.system.core:errors.create_defendant', defaultMessage: 'Upp kom villa við að stofna nýjan varnaraðila', description: 'Notaður sem villuskilaboð þegar ekki gengur að stofna varnaraðila', }, + createCivilClaimant: { + id: 'judicial.system.core:errors.create_civil_claimant', + defaultMessage: 'Upp kom villa við að stofna nýjan kröfuhafa', + description: + 'Notaður sem villuskilaboð þegar ekki gengur að stofna kröfuhafa', + }, deleteDefendant: { id: 'judicial.system.core:errors.delete_defendant', defaultMessage: 'Upp kom villa við að eyða varnaraðila', description: 'Notaður sem villuskilaboð þegar ekki gengur að eyða varnaraðila', }, + deleteCivilClaimant: { + id: 'judicial.system.core:errors.delete_civil_claimant', + defaultMessage: 'Upp kom villa við að eyða kröfuhafa', + description: + 'Notaður sem villuskilaboð þegar ekki gengur að eyða kröfuhafa', + }, createCase: { id: 'judicial.system.core:errors.create_case', defaultMessage: 'Upp kom villa við að stofnun máls', diff --git a/apps/judicial-system/web/src/components/DefenderInfo/DefenderInfo.tsx b/apps/judicial-system/web/src/components/DefenderInfo/DefenderInfo.tsx index 4b66b1f1b215..6c007e693019 100644 --- a/apps/judicial-system/web/src/components/DefenderInfo/DefenderInfo.tsx +++ b/apps/judicial-system/web/src/components/DefenderInfo/DefenderInfo.tsx @@ -17,8 +17,7 @@ import { TempCase as Case } from '@island.is/judicial-system-web/src/types' import { useCase } from '../../utils/hooks' import RequiredStar from '../RequiredStar/RequiredStar' import { UserContext } from '../UserProvider/UserProvider' -import { BlueBox, SectionHeading } from '..' -import DefenderInput from './DefenderInput' +import { BlueBox, InputAdvocate, SectionHeading } from '..' import DefenderNotFound from './DefenderNotFound' import { defenderInfo } from './DefenderInfo.strings' @@ -94,7 +93,7 @@ const DefenderInfo: FC = ({ workingCase, setWorkingCase }) => { /> {defenderNotFound && } - + {isProsecutionUser(user) && ( <> diff --git a/apps/judicial-system/web/src/components/DefenderInfo/DefenderInput.strings.ts b/apps/judicial-system/web/src/components/Inputs/Input.strings.ts similarity index 68% rename from apps/judicial-system/web/src/components/DefenderInfo/DefenderInput.strings.ts rename to apps/judicial-system/web/src/components/Inputs/Input.strings.ts index 06367562df1b..80f1a51471c2 100644 --- a/apps/judicial-system/web/src/components/DefenderInfo/DefenderInput.strings.ts +++ b/apps/judicial-system/web/src/components/Inputs/Input.strings.ts @@ -1,12 +1,17 @@ import { defineMessages } from 'react-intl' -export const defenderInput = defineMessages({ +export const strings = defineMessages({ nameLabel: { id: 'judicial.system.core:defender_input.name_label', defaultMessage: 'Nafn {sessionArrangements, select, ALL_PRESENT_SPOKESPERSON {talsmanns} other {verjanda}}', description: 'Notaður sem titill á inputi fyrir skipaðan verjanda.', }, + spokespersonNameLabel: { + id: 'judicial.system.core:defender_input.spokesperson_name_label', + defaultMessage: 'Nafn réttargæslumanns', + description: 'Notaður sem titill á inputi fyrir skipaðan verjanda.', + }, namePlaceholder: { id: 'judicial.system.core:defender_input.name_placeholder', defaultMessage: 'Fult nafn', @@ -19,6 +24,11 @@ export const defenderInput = defineMessages({ description: 'Notaður sem titill á inputi fyrir netfang skipaðans verjanda.', }, + spokespersonEmailLabel: { + id: 'judicial.system.core:defender_input.spokesperson_email_label', + defaultMessage: 'Netfang réttargæslumanns', + description: 'Notaður sem titill á inputi fyrir skipaðan verjanda.', + }, emailPlaceholder: { id: 'judicial.system.core:defender_input.email_placeholder', defaultMessage: 'Netfang', @@ -32,6 +42,11 @@ export const defenderInput = defineMessages({ description: 'Notaður sem titill á inputi fyrir símanúmer skipaðans verjanda.', }, + spokespersonPhoneNumberLabel: { + id: 'judicial.system.core:defender_input.spokesperson_phone_number_label', + defaultMessage: 'Símanúmer réttargæslumanns', + description: 'Notaður sem titill á inputi fyrir skipaðan verjanda.', + }, phoneNumberPlaceholder: { id: 'judicial.system.core:defender_input.phone_number_placeholder', defaultMessage: 'Símanúmer', diff --git a/apps/judicial-system/web/src/components/DefenderInfo/DefenderInput.tsx b/apps/judicial-system/web/src/components/Inputs/InputAdvocate.tsx similarity index 55% rename from apps/judicial-system/web/src/components/DefenderInfo/DefenderInput.tsx rename to apps/judicial-system/web/src/components/Inputs/InputAdvocate.tsx index bb0b879fc0bc..e5bfaec3c2fe 100644 --- a/apps/judicial-system/web/src/components/DefenderInfo/DefenderInput.tsx +++ b/apps/judicial-system/web/src/components/Inputs/InputAdvocate.tsx @@ -27,17 +27,20 @@ import { } from '@island.is/judicial-system-web/src/utils/formHelper' import { useCase, + useCivilClaimants, useDefendants, useGetLawyers, } from '@island.is/judicial-system-web/src/utils/hooks' import { Validation } from '@island.is/judicial-system-web/src/utils/validate' -import { defenderInput as m } from './DefenderInput.strings' +import { strings } from './Input.strings' interface Props { - onDefenderNotFound: (defenderNotFound: boolean) => void + onAdvocateNotFound?: (advocateNotFound: boolean) => void disabled?: boolean | null - defendantId?: string | null + clientId?: string | null + advocateType?: 'defender' | 'spokesperson' | 'legal_rights_protector' + isCivilClaim?: boolean } interface PropertyValidation { @@ -48,12 +51,38 @@ interface PropertyValidation { } } -type InputType = 'defenderEmail' | 'defenderPhoneNumber' +type InputType = + | 'defenderEmail' + | 'defenderPhoneNumber' + | 'spokespersonEmail' + | 'spokespersonPhoneNumber' + +/** + * A component that handles setting any kind of legal advocate. In doing so + * there are three things to consider. + * + * 1. In R-cases, a single *defender* is set on the case itself. + * 2. In S-cases, a *defender* or *spokesperson* is set on each defendant, + * depending on what SESSION_ARRANGEMENT is set. + * 3. In S-cases, a *legal rights protector* is set on each civil claimant. + */ +const InputAdvocate: FC = ({ + // A function that runs if an advocate is not found. + onAdvocateNotFound, + + /** + * The id of the client of the advocate. Used to update the advocate info + * of the client. + */ + clientId, + + // The type of advocate being set. See description above. + advocateType, + + // If set to true, the defender info is set on a civil claimant in a case. + isCivilClaim = false, -const DefenderInput: FC = ({ - onDefenderNotFound, disabled, - defendantId, }) => { const { workingCase, setWorkingCase } = useContext(FormContext) const { formatMessage } = useIntl() @@ -61,12 +90,21 @@ const DefenderInput: FC = ({ const { updateCase, setAndSendCaseToServer } = useCase() const { updateDefendant, updateDefendantState, setAndSendDefendantToServer } = useDefendants() + const { + setAndSendCivilClaimantToServer, + updateCivilClaimantState, + updateCivilClaimant, + } = useCivilClaimants() const [emailErrorMessage, setEmailErrorMessage] = useState('') const [phoneNumberErrorMessage, setPhoneNumberErrorMessage] = useState('') const defendantInDefendants = workingCase.defendants?.find( - (defendant) => defendant.id === defendantId, + (defendant) => defendant.id === clientId, + ) + + const civilClaimantInCivilClaimants = workingCase.civilClaimants?.find( + (civilClaimant) => civilClaimant.id === clientId, ) const options = useMemo( @@ -80,7 +118,11 @@ const DefenderInput: FC = ({ ) const handleLawyerChange = useCallback( - (selectedOption: SingleValue) => { + ( + selectedOption: SingleValue, + isCivilClaim: boolean, + clientId?: string | null, + ) => { let updatedLawyer = { defenderName: '', defenderNationalId: '', @@ -88,26 +130,52 @@ const DefenderInput: FC = ({ defenderPhoneNumber: '', } + let updatedSpokesperson = { + spokespersonName: '', + spokespersonNationalId: '', + spokespersonEmail: '', + spokespersonPhoneNumber: '', + } + if (selectedOption) { const { label, value, __isNew__: defenderNotFound } = selectedOption - onDefenderNotFound(defenderNotFound || false) + onAdvocateNotFound && onAdvocateNotFound(defenderNotFound || false) const lawyer = lawyers.find( (l: Lawyer) => l.email === (value as string), ) - updatedLawyer = { defenderName: lawyer ? lawyer.name : label, defenderNationalId: lawyer ? lawyer.nationalId : '', defenderEmail: lawyer ? lawyer.email : '', defenderPhoneNumber: lawyer ? lawyer.phoneNr : '', } + + updatedSpokesperson = { + spokespersonName: lawyer ? lawyer.name : label, + spokespersonNationalId: lawyer ? lawyer.nationalId : '', + spokespersonEmail: lawyer ? lawyer.email : '', + spokespersonPhoneNumber: lawyer ? lawyer.phoneNr : '', + } } - if (defendantId) { + if (isCivilClaim && clientId) { + setAndSendCivilClaimantToServer( + { + ...updatedSpokesperson, + caseId: workingCase.id, + civilClaimantId: clientId, + caseFilesSharedWithSpokesperson: + updatedSpokesperson.spokespersonNationalId + ? civilClaimantInCivilClaimants?.caseFilesSharedWithSpokesperson + : null, + }, + setWorkingCase, + ) + } else if (clientId) { setAndSendDefendantToServer( - { ...updatedLawyer, caseId: workingCase.id, defendantId }, + { ...updatedLawyer, caseId: workingCase.id, defendantId: clientId }, setWorkingCase, ) } else { @@ -119,12 +187,13 @@ const DefenderInput: FC = ({ } }, [ - defendantId, - onDefenderNotFound, + onAdvocateNotFound, lawyers, - setAndSendDefendantToServer, + setAndSendCivilClaimantToServer, workingCase, + civilClaimantInCivilClaimants?.caseFilesSharedWithSpokesperson, setWorkingCase, + setAndSendDefendantToServer, setAndSendCaseToServer, ], ) @@ -132,7 +201,7 @@ const DefenderInput: FC = ({ const propertyValidations = useCallback( (property: InputType) => { const propertyValidation: PropertyValidation = - property === 'defenderEmail' + property === 'defenderEmail' || property === 'spokespersonEmail' ? { validations: ['email-format'], errorMessageHandler: { @@ -154,20 +223,30 @@ const DefenderInput: FC = ({ ) const formatUpdate = useCallback((property: InputType, value: string) => { - return property === 'defenderEmail' - ? { + switch (property) { + case 'defenderEmail': { + return { defenderEmail: value, } - : { - defenderPhoneNumber: value, - } + } + case 'defenderPhoneNumber': { + return { defenderPhoneNumber: value } + } + case 'spokespersonEmail': { + return { spokespersonEmail: value } + } + case 'spokespersonPhoneNumber': { + return { spokespersonPhoneNumber: value } + } + } }, []) const handleLawyerPropertyChange = useCallback( ( - defendantId: string, + clientId: string, property: InputType, value: string, + isCivilClaim: boolean, setWorkingCase: Dispatch>, ) => { let newValue = value @@ -185,21 +264,29 @@ const DefenderInput: FC = ({ propertyValidation.errorMessageHandler.setErrorMessage, ) - updateDefendantState( - { ...update, caseId: workingCase.id, defendantId }, - setWorkingCase, - ) + if (isCivilClaim) { + updateCivilClaimantState( + { ...update, caseId: workingCase.id, civilClaimantId: clientId }, + setWorkingCase, + ) + } else { + updateDefendantState( + { ...update, caseId: workingCase.id, defendantId: clientId }, + setWorkingCase, + ) + } }, - [formatUpdate, propertyValidations, updateDefendantState, workingCase.id], + [ + formatUpdate, + propertyValidations, + updateCivilClaimantState, + updateDefendantState, + workingCase.id, + ], ) const handleLawyerPropertyBlur = useCallback( - ( - caseId: string, - defendantId: string, - property: InputType, - value: string, - ) => { + (caseId: string, clientId: string, property: InputType, value: string) => { const propertyValidation = propertyValidations(property) const update = formatUpdate(property, value) @@ -209,24 +296,48 @@ const DefenderInput: FC = ({ propertyValidation.errorMessageHandler.setErrorMessage, ) - updateDefendant({ ...update, caseId: workingCase.id, defendantId }) + if (isCivilClaim) { + updateCivilClaimant({ ...update, caseId, civilClaimantId: clientId }) + } else { + updateDefendant({ ...update, caseId, defendantId: clientId }) + } }, - [formatUpdate, propertyValidations, updateDefendant, workingCase.id], + [ + formatUpdate, + isCivilClaim, + propertyValidations, + updateCivilClaimant, + updateDefendant, + ], ) return ( <> = ({ hasError={emailErrorMessage !== ''} disabled={Boolean(disabled)} onChange={(event) => { - if (defendantId) { + if (clientId) { handleLawyerPropertyChange( - defendantId, - 'defenderEmail', + clientId, + isCivilClaim ? 'spokespersonEmail' : 'defenderEmail', event.target.value, + isCivilClaim, setWorkingCase, ) } else { @@ -284,11 +405,11 @@ const DefenderInput: FC = ({ } }} onBlur={(event) => { - if (defendantId) { + if (clientId) { handleLawyerPropertyBlur( workingCase.id, - defendantId, - 'defenderEmail', + clientId, + isCivilClaim ? 'spokespersonEmail' : 'defenderEmail', event.target.value, ) } else { @@ -308,17 +429,20 @@ const DefenderInput: FC = ({ mask="999-9999" maskPlaceholder={null} value={ - defendantId + isCivilClaim + ? civilClaimantInCivilClaimants?.spokespersonPhoneNumber || '' + : clientId ? defendantInDefendants?.defenderPhoneNumber || '' : workingCase.defenderPhoneNumber || '' } disabled={Boolean(disabled)} onChange={(event) => { - if (defendantId) { + if (clientId) { handleLawyerPropertyChange( - defendantId, - 'defenderPhoneNumber', + clientId, + isCivilClaim ? 'spokespersonPhoneNumber' : 'defenderPhoneNumber', event.target.value, + isCivilClaim, setWorkingCase, ) } else { @@ -333,11 +457,11 @@ const DefenderInput: FC = ({ } }} onBlur={(event) => { - if (defendantId) { + if (clientId) { handleLawyerPropertyBlur( workingCase.id, - defendantId, - 'defenderPhoneNumber', + clientId, + isCivilClaim ? 'spokespersonPhoneNumber' : 'defenderPhoneNumber', event.target.value, ) } else { @@ -353,15 +477,17 @@ const DefenderInput: FC = ({ }} > @@ -370,4 +496,4 @@ const DefenderInput: FC = ({ ) } -export default DefenderInput +export default InputAdvocate diff --git a/apps/judicial-system/web/src/components/Inputs/InputNationalId.tsx b/apps/judicial-system/web/src/components/Inputs/InputNationalId.tsx index 3f4a2d1f8014..10ffb585cef0 100644 --- a/apps/judicial-system/web/src/components/Inputs/InputNationalId.tsx +++ b/apps/judicial-system/web/src/components/Inputs/InputNationalId.tsx @@ -75,12 +75,8 @@ const InputNationalId: FC = (props) => { } useEffect(() => { - if (value === undefined) { - return - } - setErrorMessage(undefined) - setInputValue(value) + setInputValue(value ?? '') }, [value]) return ( diff --git a/apps/judicial-system/web/src/components/index.ts b/apps/judicial-system/web/src/components/index.ts index 7067e0f5a922..fa0cfce3c06f 100644 --- a/apps/judicial-system/web/src/components/index.ts +++ b/apps/judicial-system/web/src/components/index.ts @@ -23,7 +23,6 @@ export { default as CourtRecordAccordionItem } from './AccordionItems/CourtRecor export { default as DateTime } from './DateTime/DateTime' export { default as Decision } from './Decision/Decision' export { default as DefenderInfo } from './DefenderInfo/DefenderInfo' -export { default as DefenderInput } from './DefenderInfo/DefenderInput' export { default as DefenderNotFound } from './DefenderInfo/DefenderNotFound' export { default as FeatureProvider, @@ -43,6 +42,9 @@ export { default as InfoCardActiveIndictment } from './InfoCard/InfoCardActiveIn export { default as InfoCardClosedIndictment } from './InfoCard/InfoCardClosedIndictment' export { default as CaseScheduledCard } from './BlueBoxWithIcon/CaseScheduledCard' export { default as IndictmentCaseScheduledCard } from './BlueBoxWithIcon/IndictmentCaseScheduledCard' +export { default as InputAdvocate } from './Inputs/InputAdvocate' +export { default as InputName } from './Inputs/InputName' +export { default as InputNationalId } from './Inputs/InputNationalId' export { default as Loading } from './Loading/Loading' export { default as Logo } from './Logo/Logo' export { default as MarkdownWrapper } from './MarkdownWrapper/MarkdownWrapper' diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Defender/SelectDefender.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Defender/SelectDefender.tsx index 411d8654c9c6..824cca080df2 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Defender/SelectDefender.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Defender/SelectDefender.tsx @@ -6,9 +6,9 @@ import { capitalize } from '@island.is/judicial-system/formatters' import { core } from '@island.is/judicial-system-web/messages' import { BlueBox, - DefenderInput, DefenderNotFound, FormContext, + InputAdvocate, } from '@island.is/judicial-system-web/src/components' import { Defendant, @@ -96,10 +96,10 @@ const SelectDefender: FC = ({ defendant }) => { large /> - diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.tsx index 53be6c8953c9..92455ec72667 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.tsx @@ -4,7 +4,7 @@ import router from 'next/router' import { Box } from '@island.is/island-ui/core' import * as constants from '@island.is/judicial-system/consts' -import { core, titles } from '@island.is/judicial-system-web/messages' +import { titles } from '@island.is/judicial-system-web/messages' import { CourtArrangements, CourtCaseInfo, diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Processing/Processing.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Processing/Processing.tsx index 2aa85b5326e0..63d9972e6ca3 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Processing/Processing.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Processing/Processing.tsx @@ -1,17 +1,26 @@ -import { FC, useCallback, useContext, useState } from 'react' +import { FC, useCallback, useContext, useEffect, useState } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' -import { Box, RadioButton, Text, UploadFile } from '@island.is/island-ui/core' +import { + Box, + Button, + Checkbox, + RadioButton, + Text, +} from '@island.is/island-ui/core' import * as constants from '@island.is/judicial-system/consts' import { isTrafficViolationCase } from '@island.is/judicial-system/types' -import { titles } from '@island.is/judicial-system-web/messages' +import { core, titles } from '@island.is/judicial-system-web/messages' import { BlueBox, CommentsInput, FormContentContainer, FormContext, FormFooter, + InputAdvocate, + InputName, + InputNationalId, PageHeader, PageLayout, ProsecutorCaseInfo, @@ -20,17 +29,19 @@ import { } from '@island.is/judicial-system-web/src/components' import RequiredStar from '@island.is/judicial-system-web/src/components/RequiredStar/RequiredStar' import { - CaseFileCategory, CaseState, CaseTransition, + CivilClaimant, DefendantPlea, + UpdateCivilClaimantInput, UpdateDefendantInput, } from '@island.is/judicial-system-web/src/graphql/schema' import { useCase, + useCivilClaimants, useDefendants, + useNationalRegistry, useOnceOn, - useS3Upload, } from '@island.is/judicial-system-web/src/utils/hooks' import { isProcessingStepValidIndictments } from '@island.is/judicial-system-web/src/utils/validate' @@ -49,13 +60,21 @@ const Processing: FC = () => { refreshCase, } = useContext(FormContext) const { updateCase, transitionCase, setAndSendCaseToServer } = useCase() - const { handleRemove } = useS3Upload(workingCase.id) const { formatMessage } = useIntl() const { updateDefendant, updateDefendantState } = useDefendants() + const { + updateCivilClaimant, + updateCivilClaimantState, + createCivilClaimant, + deleteCivilClaimant, + } = useCivilClaimants() const router = useRouter() const isTrafficViolationCaseCheck = isTrafficViolationCase(workingCase) - - const [hasCivilClaimsChoice, setHasCivilClaimsChoice] = useState() + const [civilClaimantNationalIdUpdate, setCivilClaimantNationalIdUpdate] = + useState<{ nationalId: string; civilClaimantId: string }>() + const [hasCivilClaimantChoice, setHasCivilClaimantChoice] = + useState() + const [nationalIdNotFound, setNationalIdNotFound] = useState(false) const initialize = useCallback(async () => { if (!workingCase.court) { @@ -88,21 +107,57 @@ const Processing: FC = () => { }, [router, setWorkingCase, transitionCase, workingCase], ) - const stepIsValid = isProcessingStepValidIndictments(workingCase) + + const { personData } = useNationalRegistry( + civilClaimantNationalIdUpdate?.nationalId, + ) + + const stepIsValid = + isProcessingStepValidIndictments(workingCase) && + nationalIdNotFound === false const handleUpdateDefendant = useCallback( (updatedDefendant: UpdateDefendantInput) => { updateDefendantState(updatedDefendant, setWorkingCase) + updateDefendant(updatedDefendant) + }, + [updateDefendantState, setWorkingCase, updateDefendant], + ) - if (workingCase.id) { - updateDefendant(updatedDefendant) - } + const handleUpdateCivilClaimant = useCallback( + (updatedCivilClaimant: UpdateCivilClaimantInput) => { + updateCivilClaimantState(updatedCivilClaimant, setWorkingCase) + updateCivilClaimant(updatedCivilClaimant) }, - [updateDefendantState, setWorkingCase, workingCase.id, updateDefendant], + [updateCivilClaimant, setWorkingCase, updateCivilClaimantState], ) + const handleCreateCivilClaimantClick = async () => { + addCivilClaimant() + + window.scrollTo(0, document.body.scrollHeight) + } + + const addCivilClaimant = useCallback(async () => { + const civilClaimantId = await createCivilClaimant({ + caseId: workingCase.id, + }) + + setWorkingCase((prevWorkingCase) => ({ + ...prevWorkingCase, + civilClaimants: prevWorkingCase.civilClaimants && [ + ...prevWorkingCase.civilClaimants, + { + id: civilClaimantId, + name: '', + nationalId: '', + } as CivilClaimant, + ], + })) + }, [createCivilClaimant, setWorkingCase, workingCase.id]) + const handleHasCivilClaimsChange = async (hasCivilClaims: boolean) => { - setHasCivilClaimsChoice(hasCivilClaims) + setHasCivilClaimantChoice(hasCivilClaims) setAndSendCaseToServer( [{ hasCivilClaims, force: true }], @@ -110,26 +165,114 @@ const Processing: FC = () => { setWorkingCase, ) - if (hasCivilClaims === false) { - const civilClaims = workingCase.caseFiles?.filter( - (caseFile) => caseFile.category === CaseFileCategory.CIVIL_CLAIM, - ) + if (hasCivilClaims) { + addCivilClaimant() + } else { + removeAllCivilClaimants() + } + } - if (!civilClaims) { + const handleCivilClaimantNationalIdBlur = async ( + nationalId: string, + noNationalId?: boolean | null, + civilClaimantId?: string | null, + ) => { + if (!civilClaimantId) { + return + } + + if (noNationalId) { + handleUpdateCivilClaimant({ + caseId: workingCase.id, + civilClaimantId, + nationalId, + }) + } else { + const cleanNationalId = nationalId ? nationalId.replace('-', '') : '' + setCivilClaimantNationalIdUpdate({ + nationalId: cleanNationalId, + civilClaimantId, + }) + } + } + + const handleCivilClaimantNameBlur = async ( + name: string, + civilClaimantId?: string | null, + ) => { + if (!civilClaimantId) { + return + } + + updateCivilClaimant({ name, civilClaimantId, caseId: workingCase.id }) + } + + const removeAllCivilClaimants = useCallback(async () => { + const promises: Promise[] = [] + + if (!workingCase.civilClaimants) { + return + } + + for (const civilClaimant of workingCase.civilClaimants) { + if (!civilClaimant.id) { return } - setAndSendCaseToServer( - [{ civilDemands: null, force: true }], - workingCase, - setWorkingCase, - ) + promises.push(deleteCivilClaimant(workingCase.id, civilClaimant.id)) + } - for (const civilClaim of civilClaims) { - handleRemove(civilClaim as UploadFile) + const allCivilClaimantsDeleted = await Promise.all(promises) + + if (allCivilClaimantsDeleted.every((deleted) => deleted)) { + setWorkingCase((prev) => ({ ...prev, civilClaimants: [] })) + } + }, [ + deleteCivilClaimant, + setWorkingCase, + workingCase.civilClaimants, + workingCase.id, + ]) + + const removeCivilClaimantById = useCallback( + async (caseId: string, civilClaimantId?: string | null) => { + if (!civilClaimantId) { + return + } + + const deleteSuccess = await deleteCivilClaimant(caseId, civilClaimantId) + + if (!deleteSuccess) { + return } + + const newCivilClaimants = workingCase.civilClaimants?.filter( + (civilClaimant) => civilClaimant.id !== civilClaimantId, + ) + + setWorkingCase((prev) => ({ ...prev, civilClaimants: newCivilClaimants })) + }, + [deleteCivilClaimant, setWorkingCase, workingCase.civilClaimants], + ) + + useEffect(() => { + if (!personData || !personData.items || personData.items.length === 0) { + setNationalIdNotFound(true) + return + } + + setNationalIdNotFound(false) + const update = { + caseId: workingCase.id, + civilClaimantId: civilClaimantNationalIdUpdate?.civilClaimantId || '', + name: personData?.items[0].name, + nationalId: personData.items[0].kennitala, } - } + + handleUpdateCivilClaimant(update) + // We want this hook to run exclusively when personData changes. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [personData]) return ( { > { handleHasCivilClaimsChange(true)} checked={ - hasCivilClaimsChoice === true || - (hasCivilClaimsChoice === undefined && + hasCivilClaimantChoice === true || + (hasCivilClaimantChoice === undefined && workingCase.hasCivilClaims === true) } /> handleHasCivilClaimsChange(false)} checked={ - hasCivilClaimsChoice === false || - (hasCivilClaimsChoice === undefined && + hasCivilClaimantChoice === false || + (hasCivilClaimantChoice === undefined && workingCase.hasCivilClaims === false) } /> @@ -276,6 +419,242 @@ const Processing: FC = () => { + {workingCase.hasCivilClaims && ( + <> + + {workingCase.civilClaimants?.map((civilClaimant, index) => ( + + + {index > 0 && ( + + + + )} + + { + handleUpdateCivilClaimant({ + caseId: workingCase.id, + civilClaimantId: civilClaimant.id, + nationalId: null, + noNationalId: !civilClaimant.noNationalId, + }) + }} + backgroundColor="white" + large + filled + /> + + + { + if (val.length < 11) { + setNationalIdNotFound(false) + } else if (val.length === 11) { + handleCivilClaimantNationalIdBlur( + val, + civilClaimant.noNationalId, + civilClaimant.id, + ) + } + + updateCivilClaimantState( + { + caseId: workingCase.id, + civilClaimantId: civilClaimant.id ?? '', + nationalId: val, + }, + setWorkingCase, + ) + }} + onBlur={(val) => + handleCivilClaimantNationalIdBlur( + val, + civilClaimant.noNationalId, + civilClaimant.id, + ) + } + /> + {civilClaimant.nationalId?.length === 11 && + nationalIdNotFound && ( + + {formatMessage( + core.nationalIdNotFoundInNationalRegistry, + )} + + )} + + + updateCivilClaimantState( + { + caseId: workingCase.id, + civilClaimantId: civilClaimant.id ?? '', + name: val, + }, + setWorkingCase, + ) + } + onBlur={(val) => + handleCivilClaimantNameBlur(val, civilClaimant.id) + } + required + /> + + + + {civilClaimant.hasSpokesperson && ( + <> + + + + handleUpdateCivilClaimant({ + caseId: workingCase.id, + civilClaimantId: civilClaimant.id, + spokespersonIsLawyer: true, + }) + } + checked={Boolean( + civilClaimant.spokespersonIsLawyer, + )} + /> + + + + handleUpdateCivilClaimant({ + caseId: workingCase.id, + civilClaimantId: civilClaimant.id, + spokespersonIsLawyer: false, + }) + } + checked={ + civilClaimant.spokespersonIsLawyer === false + } + /> + + + + + + { + handleUpdateCivilClaimant({ + caseId: workingCase.id, + civilClaimantId: civilClaimant.id, + caseFilesSharedWithSpokesperson: + !civilClaimant.caseFilesSharedWithSpokesperson, + }) + }} + disabled={ + civilClaimant.spokespersonIsLawyer === null || + civilClaimant.spokespersonIsLawyer === undefined + } + tooltip={formatMessage( + strings.civilClaimantShareFilesWithDefenderTooltip, + )} + backgroundColor="white" + large + filled + /> + + )} + + + ))} + + + + + )} { + const { formatMessage } = useIntl() + + const [createCivilClaimantMutation, { loading: isCreatingCivilClaimant }] = + useCreateCivilClaimantMutation() + const [deleteCivilClaimantMutation] = useDeleteCivilClaimantMutation() + const [updateCivilClaimantMutation] = useUpdateCivilClaimantMutation() + + const createCivilClaimant = useCallback( + async (civilClaimant: CreateCivilClaimantInput) => { + try { + if (!isCreatingCivilClaimant) { + const { data } = await createCivilClaimantMutation({ + variables: { + input: civilClaimant, + }, + }) + + if (data) { + return data.createCivilClaimant?.id + } + } + return null + } catch (error) { + toast.error(formatMessage(errors.createCivilClaimant)) + return null + } + }, + [createCivilClaimantMutation, formatMessage, isCreatingCivilClaimant], + ) + + const deleteCivilClaimant = useCallback( + async (caseId: string, civilClaimantId: string) => { + try { + const { data } = await deleteCivilClaimantMutation({ + variables: { input: { caseId, civilClaimantId } }, + }) + + return Boolean(data?.deleteCivilClaimant.deleted) + } catch (error) { + toast.error(formatMessage(errors.deleteCivilClaimant)) + return false + } + }, + [deleteCivilClaimantMutation, formatMessage], + ) + + const updateCivilClaimant = useCallback( + async (updateCivilClaimant: UpdateCivilClaimantInput) => { + try { + const { data } = await updateCivilClaimantMutation({ + variables: { + input: updateCivilClaimant, + }, + }) + + return Boolean(data) + } catch (error) { + toast.error(formatMessage(errors.updateCivilClaimant)) + return false + } + }, + [formatMessage, updateCivilClaimantMutation], + ) + + const updateCivilClaimantState = useCallback( + ( + update: UpdateCivilClaimantInput, + setWorkingCase: Dispatch>, + ) => { + setWorkingCase((prevWorkingCase: Case) => { + if (!prevWorkingCase.civilClaimants) { + return prevWorkingCase + } + const indexOfCivilClaimantToUpdate = + prevWorkingCase.civilClaimants.findIndex( + (civilClaimant) => civilClaimant.id === update.civilClaimantId, + ) + + if (indexOfCivilClaimantToUpdate === -1) { + return prevWorkingCase + } else { + const newCivilClaimants = [...prevWorkingCase.civilClaimants] + + newCivilClaimants[indexOfCivilClaimantToUpdate] = { + ...newCivilClaimants[indexOfCivilClaimantToUpdate], + ...update, + } as CivilClaimant + + return { ...prevWorkingCase, civilClaimants: newCivilClaimants } + } + }) + }, + [], + ) + + const setAndSendCivilClaimantToServer = useCallback( + ( + update: UpdateCivilClaimantInput, + setWorkingCase: Dispatch>, + ) => { + updateCivilClaimantState(update, setWorkingCase) + updateCivilClaimant(update) + }, + [updateCivilClaimant, updateCivilClaimantState], + ) + + return { + createCivilClaimant, + deleteCivilClaimant, + updateCivilClaimant, + updateCivilClaimantState, + setAndSendCivilClaimantToServer, + } +} + +export default useCivilClaimants diff --git a/apps/judicial-system/web/src/utils/hooks/useCivilClaimants/updateCivilClaimant.graphql b/apps/judicial-system/web/src/utils/hooks/useCivilClaimants/updateCivilClaimant.graphql new file mode 100644 index 000000000000..6b589542eb24 --- /dev/null +++ b/apps/judicial-system/web/src/utils/hooks/useCivilClaimants/updateCivilClaimant.graphql @@ -0,0 +1,5 @@ +mutation UpdateCivilClaimant($input: UpdateCivilClaimantInput!) { + updateCivilClaimant(input: $input) { + id + } +} diff --git a/apps/judicial-system/web/src/utils/hooks/useDefendants/index.ts b/apps/judicial-system/web/src/utils/hooks/useDefendants/index.ts index 7b14e6eecd68..66987e7b513b 100644 --- a/apps/judicial-system/web/src/utils/hooks/useDefendants/index.ts +++ b/apps/judicial-system/web/src/utils/hooks/useDefendants/index.ts @@ -50,13 +50,10 @@ const useDefendants = () => { variables: { input: { caseId, defendantId } }, }) - if (data?.deleteDefendant?.deleted) { - return true - } else { - return false - } + return Boolean(data?.deleteDefendant?.deleted) } catch (error) { - formatMessage(errors.deleteDefendant) + toast.error(formatMessage(errors.deleteDefendant)) + return false } }, [deleteDefendantMutation, formatMessage], @@ -71,13 +68,10 @@ const useDefendants = () => { }, }) - if (data) { - return true - } else { - return false - } + return Boolean(data) } catch (error) { toast.error(formatMessage(errors.updateDefendant)) + return false } }, [formatMessage, updateDefendantMutation], diff --git a/apps/judicial-system/web/src/utils/validate.ts b/apps/judicial-system/web/src/utils/validate.ts index dae538e76314..b2162dc1c6da 100644 --- a/apps/judicial-system/web/src/utils/validate.ts +++ b/apps/judicial-system/web/src/utils/validate.ts @@ -271,10 +271,21 @@ export const isProcessingStepValidIndictments = ( workingCase.hasCivilClaims !== null && workingCase.hasCivilClaims !== undefined + const allCivilClaimantsAreValid = workingCase.hasCivilClaims + ? workingCase.civilClaimants?.every( + (civilClaimant) => + civilClaimant.name && + (civilClaimant.noNationalId || + (civilClaimant.nationalId && + civilClaimant.nationalId.replace('-', '').length === 10)), + ) + : true + return Boolean( workingCase.prosecutor && workingCase.court && hasCivilClaimSelected && + allCivilClaimantsAreValid && defendantsAreValid(), ) }