Skip to content

Commit 48123da

Browse files
authored
Merge pull request #6194 from espoon-voltti/out-of-office
Johtajan poissaolojaksojen näkyminen viestin lähettäjille
2 parents 30c5aa8 + a734edd commit 48123da

File tree

44 files changed

+1221
-61
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1221
-61
lines changed

frontend/src/citizen-frontend/generated/api-clients/messaging.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { ReplyToMessageBody } from 'lib-common/generated/api-types/messaging'
1616
import { ThreadReply } from 'lib-common/generated/api-types/messaging'
1717
import { client } from '../../api-client'
1818
import { createUrlSearchParams } from 'lib-common/api'
19+
import { deserializeJsonGetReceiversResponse } from 'lib-common/generated/api-types/messaging'
1920
import { deserializeJsonPagedCitizenMessageThreads } from 'lib-common/generated/api-types/messaging'
2021
import { deserializeJsonThreadReply } from 'lib-common/generated/api-types/messaging'
2122
import { uri } from 'lib-common/uri'
@@ -77,7 +78,7 @@ export async function getReceivers(): Promise<GetReceiversResponse> {
7778
url: uri`/citizen/messages/receivers`.toString(),
7879
method: 'GET'
7980
})
80-
return json
81+
return deserializeJsonGetReceiversResponse(json)
8182
}
8283

8384

frontend/src/citizen-frontend/messages/MessageEditor.tsx

+20-11
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
FixedSpaceColumn,
3333
FixedSpaceFlexWrap
3434
} from 'lib-components/layout/flex-helpers'
35+
import OutOfOfficeInfo from 'lib-components/messages/OutOfOfficeInfo'
3536
import { ToggleableRecipient } from 'lib-components/messages/ToggleableRecipient'
3637
import FileUpload, {
3738
initialUploadStatus,
@@ -160,16 +161,18 @@ export default React.memo(function MessageEditor({
160161
)
161162

162163
const validAccounts = useMemo(() => {
163-
const accounts = receiverOptions.messageAccounts.filter(
164-
(account) =>
165-
selectedChildrenInSameUnit &&
166-
message.children.some(
167-
(childId) =>
168-
receiverOptions.childrenToMessageAccounts[
169-
childId
170-
]?.newMessage.includes(account.id) ?? false
171-
)
172-
)
164+
const accounts = receiverOptions.messageAccounts
165+
.filter(
166+
(account) =>
167+
selectedChildrenInSameUnit &&
168+
message.children.some(
169+
(childId) =>
170+
receiverOptions.childrenToMessageAccounts[
171+
childId
172+
]?.newMessage.includes(account.account.id) ?? false
173+
)
174+
)
175+
.map((withPresence) => withPresence.account)
173176
const [primary, secondary] = partition(accounts, isPrimaryRecipient)
174177
return { primary, secondary }
175178
}, [selectedChildrenInSameUnit, message.children, receiverOptions])
@@ -318,6 +321,11 @@ export default React.memo(function MessageEditor({
318321
/>
319322
</label>
320323

324+
<OutOfOfficeInfo
325+
selectedAccountIds={recipients.primary.map((a) => a.id)}
326+
accounts={receiverOptions.messageAccounts}
327+
/>
328+
321329
{showSecondaryRecipientSelection && (
322330
<>
323331
<Gap size="xs" />
@@ -337,7 +345,8 @@ export default React.memo(function MessageEditor({
337345
toggleable: true,
338346
selected: recipients.secondary.some(
339347
(acc) => acc.id === recipient.id
340-
)
348+
),
349+
outOfOffice: null
341350
}}
342351
data-qa="secondary-recipient"
343352
onToggleRecipient={(_, selected) =>

frontend/src/citizen-frontend/messages/MessagesPage.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ export default React.memo(function MessagesPage() {
136136
allowedAccounts={
137137
receivers.getOrElse(null)?.childrenToMessageAccounts ?? {}
138138
}
139+
accountDetails={
140+
receivers.getOrElse(null)?.messageAccounts ?? []
141+
}
139142
onThreadDeleted={() => {
140143
onSelectedThreadDeleted()
141144
addTimedNotification({

frontend/src/citizen-frontend/messages/ThreadView.tsx

+9-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import {
1919
ChildMessageAccountAccess,
2020
CitizenMessageThread,
2121
Message,
22-
MessageAccount
22+
MessageAccount,
23+
MessageAccountWithPresence
2324
} from 'lib-common/generated/api-types/messaging'
2425
import {
2526
ChildId,
@@ -215,6 +216,7 @@ interface Props {
215216
accountId: MessageAccountId
216217
thread: CitizenMessageThread.Regular
217218
allowedAccounts: Partial<Record<ChildId, ChildMessageAccountAccess>>
219+
accountDetails: MessageAccountWithPresence[]
218220
closeThread: () => void
219221
onThreadDeleted: () => void
220222
}
@@ -237,6 +239,7 @@ export default React.memo(
237239
children
238240
},
239241
allowedAccounts,
242+
accountDetails,
240243
closeThread,
241244
onThreadDeleted
242245
}: Props,
@@ -246,7 +249,11 @@ export default React.memo(
246249
const { setReplyContent, getReplyContent } = useContext(MessageContext)
247250
const { addTimedNotification } = useContext(NotificationsContext)
248251

249-
const { onToggleRecipient, recipients } = useRecipients(messages, accountId)
252+
const { onToggleRecipient, recipients } = useRecipients(
253+
messages,
254+
accountId,
255+
accountDetails
256+
)
250257
const [replyEditorVisible, useReplyEditorVisible] = useBoolean(false)
251258
const [confirmDelete, setConfirmDelete] = useState(false)
252259

frontend/src/e2e-test/pages/citizen/citizen-messages.ts

+26
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
//
33
// SPDX-License-Identifier: LGPL-2.1-or-later
44

5+
import FiniteDateRange from 'lib-common/finite-date-range'
6+
57
import {
68
Element,
79
ElementCollection,
@@ -37,6 +39,7 @@ export default class CitizenMessagesPage {
3739
#threadUrgent: Element
3840
newMessageButton: Element
3941
fileUpload: Element
42+
#threadOutOfOfficeInfo: Element
4043
constructor(private readonly page: Page) {
4144
this.messageReplyContent = new TextInput(
4245
page.findByDataQa('message-reply-content')
@@ -60,6 +63,9 @@ export default class CitizenMessagesPage {
6063
.findByDataQa('urgent')
6164
this.newMessageButton = page.findAllByDataQa('new-message-btn').first()
6265
this.fileUpload = page.findByDataQa('upload-message-attachment')
66+
this.#threadOutOfOfficeInfo = page
67+
.findByDataQa('thread-reader')
68+
.findByDataQa('out-of-office-info')
6369
}
6470

6571
replyButtonTag = 'message-reply-editor-btn'
@@ -201,6 +207,15 @@ export default class CitizenMessagesPage {
201207

202208
await editor.sendMessage()
203209
}
210+
211+
async assertThreadOutOfOffice(ooo: {
212+
name: string
213+
period: FiniteDateRange
214+
}) {
215+
await this.#threadOutOfOfficeInfo.assertText(
216+
(t) => t.includes(ooo.name) && t.includes(ooo.period.format())
217+
)
218+
}
204219
}
205220

206221
export class CitizenMessageEditor extends Element {
@@ -210,6 +225,7 @@ export class CitizenMessageEditor extends Element {
210225
readonly title = new TextInput(this.findByDataQa('input-title'))
211226
readonly content = new TextInput(this.findByDataQa('input-content'))
212227
readonly #sendMessage = this.findByDataQa('send-message-btn')
228+
readonly #outOfOfficeInfo = this.findByDataQa('out-of-office-info')
213229

214230
secondaryRecipient(name: string) {
215231
return this.find(`[data-qa="secondary-recipient"]`, { hasText: name })
@@ -250,4 +266,14 @@ export class CitizenMessageEditor extends Element {
250266
await this.#sendMessage.click()
251267
await this.waitUntilHidden()
252268
}
269+
270+
async assertOutOfOffice(ooo: { name: string; period: FiniteDateRange }) {
271+
await this.#outOfOfficeInfo.assertText(
272+
(t) => t.includes(ooo.name) && t.includes(ooo.period.format())
273+
)
274+
}
275+
276+
async assertNoOutOfOffice() {
277+
await this.#outOfOfficeInfo.waitUntilHidden()
278+
}
253279
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// SPDX-FileCopyrightText: 2017-2025 City of Espoo
2+
//
3+
// SPDX-License-Identifier: LGPL-2.1-or-later
4+
5+
import FiniteDateRange from 'lib-common/finite-date-range'
6+
import LocalDate from 'lib-common/local-date'
7+
8+
import config from '../../../config'
9+
import { Page, Element, DatePicker } from '../../../utils/page'
10+
11+
export class OutOfOfficePage {
12+
#outOfOffice: Element
13+
#outOfOfficeEditor: Element
14+
#addButton: Element
15+
#saveButton: Element
16+
#editButton: Element
17+
#removeButton: Element
18+
19+
constructor(page: Page) {
20+
this.#outOfOffice = page.findByDataQa('out-of-office-page')
21+
this.#outOfOfficeEditor = page.findByDataQa('out-of-office-editor')
22+
this.#addButton = page.findByDataQa('add-out-of-office')
23+
this.#saveButton = page.findByDataQa('save-out-of-office')
24+
this.#editButton = page.findByDataQa('edit-out-of-office')
25+
this.#removeButton = page.findByDataQa('remove-out-of-office')
26+
}
27+
28+
static async open(page: Page) {
29+
await page.goto(config.employeeUrl + '/out-of-office')
30+
return new OutOfOfficePage(page)
31+
}
32+
33+
async addOutOfOfficePeriod(startDate: LocalDate, endDate: LocalDate) {
34+
await this.#addButton.click()
35+
36+
const startInput = new DatePicker(
37+
this.#outOfOfficeEditor.findAll('input').first()
38+
)
39+
const endInput = new DatePicker(
40+
this.#outOfOfficeEditor.findAll('input').last()
41+
)
42+
43+
await startInput.fill(startDate)
44+
await endInput.fill(endDate)
45+
await this.#saveButton.click()
46+
}
47+
48+
async editStartOfPeriod(newStartDate: LocalDate) {
49+
await this.#editButton.click()
50+
const startInput = new DatePicker(
51+
this.#outOfOfficeEditor.findAll('input').first()
52+
)
53+
await startInput.fill(newStartDate)
54+
await this.#saveButton.click()
55+
}
56+
57+
async removeOutOfOfficePeriod() {
58+
await this.#removeButton.click()
59+
}
60+
61+
async assertNoPeriods() {
62+
await this.#outOfOffice.assertText((t) =>
63+
t.includes('Ei tulevia poissaoloja')
64+
)
65+
}
66+
67+
async assertPeriodExists(startDate: LocalDate, endDate: LocalDate) {
68+
const periodText =
69+
FiniteDateRange.tryCreate(startDate, endDate)?.format() ?? 'error'
70+
await this.#outOfOffice.assertText(
71+
(t) => t.includes(periodText) && !t.includes('Ei tulevia poissaoloja')
72+
)
73+
}
74+
75+
async assertPeriodDoesNotExist(startDate: LocalDate, endDate: LocalDate) {
76+
const periodText =
77+
FiniteDateRange.tryCreate(startDate, endDate)?.format() ?? 'error'
78+
await this.#outOfOffice.assertText((t) => !t.includes(periodText))
79+
}
80+
}

0 commit comments

Comments
 (0)