Skip to content

Commit

Permalink
changing folder + fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Joosakur committed Feb 12, 2025
1 parent b4ff4fc commit 532f3d0
Show file tree
Hide file tree
Showing 9 changed files with 247 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import colors from 'lib-customizations/common'
import { useTranslation } from '../../state/i18n'

import { MessageContext } from './MessageContext'
import { AccountView, isStandardView, View } from './types-view'
import { AccountView, isFolderView, isStandardView, View } from './types-view'

export const MessageBoxRow = styled.div<{ active: boolean }>`
cursor: pointer;
Expand Down Expand Up @@ -59,7 +59,7 @@ export default function MessageBox({
?.unreadCopyCount ?? 0
)
}
if (!isStandardView(view)) {
if (isFolderView(view)) {
return (
unreadCounts.find(({ accountId }) => accountId === account.id)
?.unreadCountByFolder?.[view.id] ?? 0
Expand Down
53 changes: 36 additions & 17 deletions frontend/src/employee-frontend/components/messages/ThreadList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@ import {
MessageThreadId
} from 'lib-common/generated/api-types/shared'
import HelsinkiDateTime from 'lib-common/helsinki-date-time'
import { AsyncIconOnlyButton } from 'lib-components/atoms/buttons/AsyncIconOnlyButton'
import { FixedSpaceRow } from 'lib-components/layout/flex-helpers'
import { MessageCharacteristics } from 'lib-components/messages/MessageCharacteristics'
import { faBoxArchive } from 'lib-icons'

import { archiveThread } from '../../generated/api-clients/messaging'
import { useTranslation } from '../../state/i18n'
import { renderResult } from '../async-rendering'
import EllipsisMenu from '../common/EllipsisMenu'

import {
Hyphen,
Expand Down Expand Up @@ -51,10 +50,16 @@ export type ThreadListItem = {
interface Props {
items: Result<ThreadListItem[]>
accountId: MessageAccountId
onArchive?: () => void
onChangeFolder?: (item: MessageThreadId) => void
onArchived?: () => void
}

export function ThreadList({ items: messages, accountId, onArchive }: Props) {
export function ThreadList({
items: messages,
accountId,
onChangeFolder,
onArchived
}: Props) {
const { i18n } = useTranslation()

return renderResult(messages, (threads) => (
Expand Down Expand Up @@ -83,23 +88,37 @@ export function ThreadList({ items: messages, accountId, onArchive }: Props) {
</Truncated>
</ParticipantsAndPreview>
<FixedSpaceRow>
{onArchive && (
<AsyncIconOnlyButton
icon={faBoxArchive}
aria-label={i18n.common.archive}
data-qa="delete-thread-btn"
className="delete-btn"
onClick={() =>
archiveThreadResult({ accountId, threadId: item.id })
}
onSuccess={onArchive}
stopPropagation
/>
)}
<TypeAndDate>
<MessageCharacteristics type={item.type} urgent={item.urgent} />
{item.timestamp && <Timestamp date={item.timestamp} />}
</TypeAndDate>
{}
<EllipsisMenu
items={[
...(onChangeFolder
? [
{
id: 'change-folder',
label: i18n.messages.changeFolder.button,
onClick: () => onChangeFolder(item.id)
}
]
: []),
...(onArchived
? [
{
id: 'archive',
label: i18n.common.archive,
onClick: () =>
archiveThreadResult({
accountId,
threadId: item.id
}).then(onArchived)
}
]
: [])
]}
/>
</FixedSpaceRow>
</MessageRow>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,38 @@
//
// SPDX-License-Identifier: LGPL-2.1-or-later

import React, { useCallback, useContext, useMemo } from 'react'
import React, { useCallback, useContext, useMemo, useState } from 'react'
import styled from 'styled-components'

import { Loading, Result, Success } from 'lib-common/api'
import { Failure, Loading, Result, Success, wrapResult } from 'lib-common/api'
import {
MessageAccount,
MessageThread
MessageThread,
MessageThreadFolder
} from 'lib-common/generated/api-types/messaging'
import { MessageThreadId } from 'lib-common/generated/api-types/shared'
import {
MessageAccountId,
MessageThreadId
} from 'lib-common/generated/api-types/shared'
import { fromUuid } from 'lib-common/id-type'
import { formatPreferredName } from 'lib-common/names'
import Pagination from 'lib-components/Pagination'
import Select from 'lib-components/atoms/dropdowns/Select'
import { ContentArea } from 'lib-components/layout/Container'
import EmptyMessageFolder from 'lib-components/messages/EmptyMessageFolder'
import { AsyncFormModal } from 'lib-components/molecules/modals/FormModal'
import { H1, H2 } from 'lib-components/typography'
import colors from 'lib-customizations/common'

import { moveThreadToFolder } from '../../generated/api-clients/messaging'
import { useTranslation } from '../../state/i18n'

import { MessageContext } from './MessageContext'
import { SingleThreadView } from './SingleThreadView'
import { ThreadList, ThreadListItem } from './ThreadList'
import { isStandardView, View } from './types-view'
import { isFolderView, isStandardView, View } from './types-view'

const moveThreadToFolderResult = wrapResult(moveThreadToFolder)

const MessagesContainer = styled(ContentArea)`
overflow-y: auto;
Expand Down Expand Up @@ -61,6 +70,7 @@ export default React.memo(function ThreadListContainer({
}: Props) {
const { i18n } = useTranslation()
const {
folders,
receivedMessages,
sentMessages,
messageDrafts,
Expand All @@ -78,7 +88,10 @@ export default React.memo(function ThreadListContainer({
messageCopiesAsThreads
} = useContext(MessageContext)

const onArchive = useCallback(() => {
const [folderChangeTarget, setFolderChangeTarget] =
useState<MessageThreadId | null>(null)

const onMessageMoved = useCallback(() => {
selectThread(undefined)
refreshMessages()
}, [refreshMessages, selectThread])
Expand All @@ -94,7 +107,7 @@ export default React.memo(function ThreadListContainer({
return messageCopies.value.length > 0
} else if (view === 'archive' && archivedMessages.isSuccess) {
return archivedMessages.value.length > 0
} else if (!isStandardView(view) && messagesInFolder.isSuccess) {
} else if (isFolderView(view) && messagesInFolder.isSuccess) {
return messagesInFolder.value.length > 0
} else {
return false
Expand Down Expand Up @@ -206,48 +219,122 @@ export default React.memo(function ThreadListContainer({
: Loading.of<ThreadListItem[]>()
}[isStandardView(view) ? view : 'folder']

if (selectedThread) {
return (
<SingleThreadView
goBack={() => selectThread(undefined)}
thread={selectedThread}
accountId={account.id}
view={view}
onArchived={view === 'received' ? onArchive : undefined}
/>
)
}

return hasMessages ? (
<MessagesContainer opaque>
<H1>
{isStandardView(view)
? i18n.messages.messageList.titles[view]
: view.name}
</H1>
{account.type !== 'PERSONAL' && <H2>{account.name}</H2>}
<ThreadList
items={threadListItems}
accountId={account.id}
onArchive={view === 'received' ? onArchive : undefined}
/>
<Pagination
pages={pages}
currentPage={page}
setPage={setPage}
label={i18n.common.page}
/>
</MessagesContainer>
) : (
<EmptyMessageFolder
loading={
(view === 'received' && receivedMessages.isLoading) ||
(view === 'sent' && sentMessages.isLoading) ||
(view === 'drafts' && messageDrafts.isLoading) ||
(view === 'copies' && messageCopies.isLoading)
const hasFolders = folders.isSuccess && folders.value.length > 0

return (
<>
{folderChangeTarget && folders.isSuccess && (
<FolderChangeModal
accountId={account.id}
threadId={folderChangeTarget}
folders={folders.value}
onSuccess={onMessageMoved}
onClose={() => setFolderChangeTarget(null)}
/>
)}

{selectedThread ? (
<SingleThreadView
goBack={() => selectThread(undefined)}
thread={selectedThread}
accountId={account.id}
view={view}
onArchived={
view === 'received' || isFolderView(view)
? onMessageMoved
: undefined
}
/>
) : hasMessages ? (
<MessagesContainer opaque>
<H1>
{isStandardView(view)
? i18n.messages.messageList.titles[view]
: view.name}
</H1>
{account.type !== 'PERSONAL' && <H2>{account.name}</H2>}
<ThreadList
items={threadListItems}
accountId={account.id}
onArchived={
view === 'received' || isFolderView(view)
? onMessageMoved
: undefined
}
onChangeFolder={
hasFolders &&
(view === 'received' || view === 'sent' || isFolderView(view))
? (id) => setFolderChangeTarget(id)
: undefined
}
/>
<Pagination
pages={pages}
currentPage={page}
setPage={setPage}
label={i18n.common.page}
/>
</MessagesContainer>
) : (
<EmptyMessageFolder
loading={
(view === 'received' && receivedMessages.isLoading) ||
(view === 'sent' && sentMessages.isLoading) ||
(view === 'drafts' && messageDrafts.isLoading) ||
(view === 'copies' && messageCopies.isLoading)
}
iconColor={colors.grayscale.g35}
text={i18n.messages.emptyInbox}
/>
)}
</>
)
})

const FolderChangeModal = React.memo(function FolderChangeModal({
accountId,
threadId,
folders,
onSuccess,
onClose
}: {
accountId: MessageAccountId
threadId: MessageThreadId
folders: MessageThreadFolder[]
onSuccess: () => void
onClose: () => void
}) {
const { i18n } = useTranslation()
const [folder, setFolder] = useState<MessageThreadFolder | null>(null)
return (
<AsyncFormModal
title={i18n.messages.changeFolder.modalTitle}
resolveAction={() =>
folder
? moveThreadToFolderResult({
accountId: accountId,
threadId: threadId,
folderId: folder.id
})
: Promise.resolve(Failure.of({ message: 'No folder selected' }))
}
iconColor={colors.grayscale.g35}
text={i18n.messages.emptyInbox}
/>
resolveLabel={i18n.messages.changeFolder.modalOk}
onSuccess={() => {
onSuccess()
onClose()
}}
rejectAction={onClose}
rejectLabel={i18n.common.cancel}
resolveDisabled={!folder}
>
<Select
items={folders}
selectedItem={folder}
onChange={(value) => setFolder(value)}
getItemLabel={(item) => item.name}
getItemValue={(item) => item.id}
placeholder={i18n.common.select}
/>
</AsyncFormModal>
)
})
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ export const isStandardView = (
view: MessageThreadFolder | string
): view is StandardView => views.some((v) => view === v)

export const isFolderView = (
view: MessageThreadFolder | string
): view is MessageThreadFolder => !isStandardView(view)

export interface AccountView {
account: MessageAccount
view: View
Expand Down
18 changes: 18 additions & 0 deletions frontend/src/employee-frontend/generated/api-clients/messaging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,24 @@ export async function markThreadRead(
}


/**
* Generated from fi.espoo.evaka.messaging.MessageController.moveThreadToFolder
*/
export async function moveThreadToFolder(
request: {
accountId: MessageAccountId,
threadId: MessageThreadId,
folderId: MessageThreadFolderId
}
): Promise<void> {
const { data: json } = await client.request<JsonOf<void>>({
url: uri`/employee/messages/${request.accountId}/threads/${request.threadId}/move-to-folder/${request.folderId}`.toString(),
method: 'PUT'
})
return json
}


/**
* Generated from fi.espoo.evaka.messaging.MessageController.replyToThread
*/
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4609,6 +4609,11 @@ export const fi = {
emptyInbox: 'Tämä kansio on tyhjä',
replyToThread: 'Vastaa viestiin',
archiveThread: 'Arkistoi viestiketju',
changeFolder: {
button: 'Vaihda kansiota',
modalTitle: 'Valitse kansio',
modalOk: 'Siirrä kansioon'
},
unitList: {
title: 'Yksiköt'
},
Expand Down
1 change: 1 addition & 0 deletions service/src/main/kotlin/fi/espoo/evaka/Audit.kt
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ enum class Audit(
MessagingUnreadMessagesRead,
MessagingMarkMessagesReadWrite,
MessagingArchiveMessageWrite,
MessagingChangeFolder,
MessagingMessageReceiversRead,
MessagingReceivedMessagesRead,
MessagingReceivedMessageCopiesRead,
Expand Down
Loading

0 comments on commit 532f3d0

Please sign in to comment.