Skip to content

Commit

Permalink
Show out of office info for citizen
Browse files Browse the repository at this point in the history
  • Loading branch information
terolaakso committed Jan 3, 2025
1 parent e5bb497 commit 935b55a
Show file tree
Hide file tree
Showing 15 changed files with 221 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { ReplyToMessageBody } from 'lib-common/generated/api-types/messaging'
import { ThreadReply } from 'lib-common/generated/api-types/messaging'
import { client } from '../../api-client'
import { createUrlSearchParams } from 'lib-common/api'
import { deserializeJsonGetReceiversResponse } from 'lib-common/generated/api-types/messaging'
import { deserializeJsonPagedCitizenMessageThreads } from 'lib-common/generated/api-types/messaging'
import { deserializeJsonThreadReply } from 'lib-common/generated/api-types/messaging'
import { uri } from 'lib-common/uri'
Expand Down Expand Up @@ -77,7 +78,7 @@ export async function getReceivers(): Promise<GetReceiversResponse> {
url: uri`/citizen/messages/receivers`.toString(),
method: 'GET'
})
return json
return deserializeJsonGetReceiversResponse(json)
}


Expand Down
31 changes: 20 additions & 11 deletions frontend/src/citizen-frontend/messages/MessageEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
FixedSpaceColumn,
FixedSpaceFlexWrap
} from 'lib-components/layout/flex-helpers'
import OutOfOfficeInfo from 'lib-components/messages/OutOfOfficeInfo'
import { ToggleableRecipient } from 'lib-components/messages/ToggleableRecipient'
import FileUpload, {
initialUploadStatus,
Expand Down Expand Up @@ -160,16 +161,18 @@ export default React.memo(function MessageEditor({
)

const validAccounts = useMemo(() => {
const accounts = receiverOptions.messageAccounts.filter(
(account) =>
selectedChildrenInSameUnit &&
message.children.some(
(childId) =>
receiverOptions.childrenToMessageAccounts[
childId
]?.newMessage.includes(account.id) ?? false
)
)
const accounts = receiverOptions.messageAccounts
.filter(
(account) =>
selectedChildrenInSameUnit &&
message.children.some(
(childId) =>
receiverOptions.childrenToMessageAccounts[
childId
]?.newMessage.includes(account.account.id) ?? false
)
)
.map((withPresence) => withPresence.account)
const [primary, secondary] = partition(accounts, isPrimaryRecipient)
return { primary, secondary }
}, [selectedChildrenInSameUnit, message.children, receiverOptions])
Expand Down Expand Up @@ -317,6 +320,11 @@ export default React.memo(function MessageEditor({
/>
</label>

<OutOfOfficeInfo
selectedAccountIds={recipients.primary.map((a) => a.id)}
accounts={receiverOptions.messageAccounts}
/>

{showSecondaryRecipientSelection && (
<>
<Gap size="xs" />
Expand All @@ -336,7 +344,8 @@ export default React.memo(function MessageEditor({
toggleable: true,
selected: recipients.secondary.some(
(acc) => acc.id === recipient.id
)
),
outOfOffice: null
}}
data-qa="secondary-recipient"
onToggleRecipient={(_, selected) =>
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/citizen-frontend/messages/MessagesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ export default React.memo(function MessagesPage() {
allowedAccounts={
receivers.getOrElse(null)?.childrenToMessageAccounts ?? {}
}
accountDetails={
receivers.getOrElse(null)?.messageAccounts ?? []
}
onThreadDeleted={() => {
onSelectedThreadDeleted()
addTimedNotification({
Expand Down
11 changes: 9 additions & 2 deletions frontend/src/citizen-frontend/messages/ThreadView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import {
ChildMessageAccountAccess,
CitizenMessageThread,
Message,
MessageAccount
MessageAccount,
MessageAccountWithPresence
} from 'lib-common/generated/api-types/messaging'
import {
ChildId,
Expand Down Expand Up @@ -215,6 +216,7 @@ interface Props {
accountId: MessageAccountId
thread: CitizenMessageThread.Regular
allowedAccounts: Partial<Record<ChildId, ChildMessageAccountAccess>>
accountDetails: MessageAccountWithPresence[]
closeThread: () => void
onThreadDeleted: () => void
}
Expand All @@ -237,6 +239,7 @@ export default React.memo(
children
},
allowedAccounts,
accountDetails,
closeThread,
onThreadDeleted
}: Props,
Expand All @@ -246,7 +249,11 @@ export default React.memo(
const { setReplyContent, getReplyContent } = useContext(MessageContext)
const { addTimedNotification } = useContext(NotificationsContext)

const { onToggleRecipient, recipients } = useRecipients(messages, accountId)
const { onToggleRecipient, recipients } = useRecipients(
messages,
accountId,
accountDetails
)
const [replyEditorVisible, useReplyEditorVisible] = useBoolean(false)
const [confirmDelete, setConfirmDelete] = useState(false)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,11 @@ export function SingleThreadView({
(content: string) => setReplyContent(threadId, content),
[setReplyContent, threadId]
)
const { recipients, onToggleRecipient } = useRecipients(messages, accountId)
const { recipients, onToggleRecipient } = useRecipients(
messages,
accountId,
[]
)

const autoScrollRef = useRef<HTMLSpanElement>(null)
useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,11 @@ const ReceivedThread = React.memo(function ReceivedThread({
const { i18n } = useTranslation()
const { setReplyContent, getReplyContent } = useContext(MessageContext)

const { onToggleRecipient, recipients } = useRecipients(messages, accountId)
const { onToggleRecipient, recipients } = useRecipients(
messages,
accountId,
[]
)
const [replyEditorVisible, setReplyEditorVisible] = useState(
() => getReplyContent(threadId) !== ''
)
Expand Down
27 changes: 26 additions & 1 deletion frontend/src/lib-common/generated/api-types/messaging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

// GENERATED FILE: no manual modifications

import FiniteDateRange from '../../finite-date-range'
import HelsinkiDateTime from '../../helsinki-date-time'
import { ApplicationId } from './shared'
import { AttachmentId } from './shared'
Expand Down Expand Up @@ -120,7 +121,7 @@ export interface DraftContent {
*/
export interface GetReceiversResponse {
childrenToMessageAccounts: Partial<Record<PersonId, ChildMessageAccountAccess>>
messageAccounts: MessageAccount[]
messageAccounts: MessageAccountWithPresence[]
}

/**
Expand Down Expand Up @@ -157,6 +158,14 @@ export interface MessageAccount {
type: AccountType
}

/**
* Generated from fi.espoo.evaka.messaging.MessageAccountWithPresence
*/
export interface MessageAccountWithPresence {
account: MessageAccount
outOfOffice: FiniteDateRange | null
}

/**
* Generated from fi.espoo.evaka.messaging.MessageChild
*/
Expand Down Expand Up @@ -425,6 +434,14 @@ export function deserializeJsonDraftContent(json: JsonOf<DraftContent>): DraftCo
}


export function deserializeJsonGetReceiversResponse(json: JsonOf<GetReceiversResponse>): GetReceiversResponse {
return {
...json,
messageAccounts: json.messageAccounts.map(e => deserializeJsonMessageAccountWithPresence(e))
}
}


export function deserializeJsonMessage(json: JsonOf<Message>): Message {
return {
...json,
Expand All @@ -434,6 +451,14 @@ export function deserializeJsonMessage(json: JsonOf<Message>): Message {
}


export function deserializeJsonMessageAccountWithPresence(json: JsonOf<MessageAccountWithPresence>): MessageAccountWithPresence {
return {
...json,
outOfOffice: (json.outOfOffice != null) ? FiniteDateRange.parseJson(json.outOfOffice) : null
}
}


export function deserializeJsonMessageCopy(json: JsonOf<MessageCopy>): MessageCopy {
return {
...json,
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/lib-components/i18n.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import React, { useContext, useMemo } from 'react'

import FiniteDateRange from 'lib-common/finite-date-range'
import { DocumentStatus } from 'lib-common/generated/api-types/document'

import { FileType } from './molecules/FileUpload'
Expand Down Expand Up @@ -91,6 +92,10 @@ export interface Translations {
type: string
urgent: string
}
outOfOffice: {
singleRecipient: (name: string, period: FiniteDateRange) => string
multipleRecipientsHeader: string
}
}
messageReplyEditor: {
messagePlaceholder: string | undefined
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/lib-components/messages/MessageReplyEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import React, { useCallback } from 'react'
import styled from 'styled-components'

import FiniteDateRange from 'lib-common/finite-date-range'
import { AccountType } from 'lib-common/generated/api-types/messaging'
import { MessageAccountId } from 'lib-common/generated/api-types/shared'
import { type cancelMutation, MutationDescription } from 'lib-common/query'
Expand All @@ -18,6 +19,7 @@ import ButtonContainer from '../layout/ButtonContainer'
import { Label } from '../typography'
import { defaultMargins } from '../white-space'

import OutOfOfficeInfo from './OutOfOfficeInfo'
import { ToggleableRecipient } from './ToggleableRecipient'

const MultiRowTextArea = styled(TextArea)`
Expand All @@ -41,6 +43,7 @@ export interface SelectableAccount {
selected: boolean
toggleable: boolean
type: AccountType
outOfOffice: FiniteDateRange | null
}

interface Props<T, R> {
Expand Down Expand Up @@ -90,6 +93,15 @@ function MessageReplyEditor<T, R>({
labelAdd={i18n.common.add}
/>
))}
<OutOfOfficeInfo
selectedAccountIds={recipients
.filter((r) => r.selected)
.map((r) => r.id)}
accounts={recipients.map((r) => ({
account: { id: r.id, name: r.name, type: r.type },
outOfOffice: r.outOfOffice
}))}
/>
</EditorRow>
<EditorRow>
<Label>{i18n.messages.message}</Label>
Expand Down
75 changes: 75 additions & 0 deletions frontend/src/lib-components/messages/OutOfOfficeInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, { Fragment } from 'react'
import styled, { useTheme } from 'styled-components'

import FiniteDateRange from 'lib-common/finite-date-range'
import { MessageAccountWithPresence } from 'lib-common/generated/api-types/messaging'
import { MessageAccountId } from 'lib-common/generated/api-types/shared'
import LocalDate from 'lib-common/local-date'
import RoundIcon from 'lib-components/atoms/RoundIcon'
import { defaultMargins } from 'lib-components/white-space'
import { faExclamation } from 'lib-icons'

import { useTranslations } from '../i18n'

export interface OutOfOfficeInfoProps {
selectedAccountIds: MessageAccountId[]
accounts: MessageAccountWithPresence[]
}

type OutOfOfficeAccount = Omit<MessageAccountWithPresence, 'outOfOffice'> & {
outOfOffice: FiniteDateRange
}

export default React.memo(function OutOfOfficeInfo({
selectedAccountIds,
accounts
}: OutOfOfficeInfoProps) {
const i18n = useTranslations()
const { colors } = useTheme()

const today = LocalDate.todayInSystemTz()
const outOfOfficeAccounts = accounts.filter(
(account): account is OutOfOfficeAccount =>
selectedAccountIds.includes(account.account.id) &&
account.outOfOffice !== null &&
account.outOfOffice.includes(today)
)

return outOfOfficeAccounts.length > 0 ? (
<OutOfOfficeInfoArea>
<RoundIcon content={faExclamation} color={colors.main.m2} size="s" />
<div>
{outOfOfficeAccounts.length === 1 ? (
i18n.messages.outOfOffice.singleRecipient(
outOfOfficeAccounts[0].account.name,
outOfOfficeAccounts[0].outOfOffice
)
) : (
<Fragment>
<div>{i18n.messages.outOfOffice.multipleRecipientsHeader}</div>
<OutOfOfficeList>
{outOfOfficeAccounts.map((account) => (
<li key={account.account.id}>
{account.account.name}: {account.outOfOffice.format()}
</li>
))}
</OutOfOfficeList>
</Fragment>
)}
</div>
</OutOfOfficeInfoArea>
) : null
})

const OutOfOfficeInfoArea = styled.div`
border: 1px solid ${(p) => p.theme.colors.main.m2};
border-radius: 4px;
padding: ${defaultMargins.s};
display: flex;
gap: ${defaultMargins.xs};
margin-top: ${defaultMargins.s};
`
const OutOfOfficeList = styled.ul`
margin-block: 0;
padding-inline-start: ${defaultMargins.m};
`
4 changes: 4 additions & 0 deletions frontend/src/lib-components/utils/TestContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ export const testTranslations: Translations = {
thread: {
type: '',
urgent: ''
},
outOfOffice: {
singleRecipient: () => '',
multipleRecipientsHeader: ''
}
},
messageReplyEditor: {
Expand Down
Loading

0 comments on commit 935b55a

Please sign in to comment.