Skip to content

Commit

Permalink
Add unit closing date modal
Browse files Browse the repository at this point in the history
  • Loading branch information
patari committed Dec 16, 2024
1 parent 71166f2 commit fd3cf79
Show file tree
Hide file tree
Showing 11 changed files with 302 additions and 23 deletions.
28 changes: 28 additions & 0 deletions frontend/src/e2e-test/pages/employee/units/unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { waitUntilEqual, waitUntilFalse, waitUntilTrue } from '../../../utils'
import {
Checkbox,
Combobox,
DatePicker,
DatePickerDeprecated,
Element,
Modal,
Expand Down Expand Up @@ -49,6 +50,7 @@ export class UnitPage {
#groupsTab: Element
#calendarTab: Element
#applicationProcessTab: Element

constructor(private readonly page: Page) {
this.#unitInfoTab = page.findByDataQa('unit-info-tab')
this.#groupsTab = page.findByDataQa('groups-tab')
Expand Down Expand Up @@ -123,6 +125,7 @@ export class UnitInfoPage {
earlyChildhoodEducationSecretary: AclSection
staffAcl: AclSection
mobileAcl: MobileDevicesSection

constructor(private readonly page: Page) {
this.#unitName = page.findByDataQa('unit-name')
this.#visitingAddress = page.findByDataQa('unit-visiting-address')
Expand Down Expand Up @@ -171,12 +174,15 @@ export class UnitInfoPage {
export class UnitDetailsPage {
#editUnitButton: Element
#unitName: Element
openingAndClosingDates: Element
#unitManagerName: Element
#unitManagerPhone: Element
#unitManagerEmail: Element

constructor(private readonly page: Page) {
this.#editUnitButton = page.findByDataQa('enable-edit-button')
this.#unitName = page.find('[data-qa="unit-editor-container"]').find('h1')
this.openingAndClosingDates = page.findByDataQa('opening-and-closing-dates')
this.#unitManagerName = page.findByDataQa('unit-manager-name')
this.#unitManagerPhone = page.findByDataQa('unit-manager-phone')
this.#unitManagerEmail = page.findByDataQa('unit-manager-email')
Expand Down Expand Up @@ -207,6 +213,13 @@ export class UnitDetailsPage {
return new UnitEditor(this.page)
}

async openClosingDateModal() {
await this.page.findByDataQa('open-closing-date-modal').click()
return new UnitClosingDateModal(
this.page.findByDataQa('unit-closing-date-modal')
)
}

async assertMealTimes(mealTimes: MealTimes) {
for (const [key, value] of Object.entries(mealTimes)) {
await this.page
Expand Down Expand Up @@ -235,6 +248,7 @@ export class UnitEditor {
#unitHandlerAddressInput: TextInput
unitCostCenterInput: TextInput
saveButton: Element

constructor(private readonly page: Page) {
this.#unitNameInput = new TextInput(page.findByDataQa('unit-name-input'))
this.#areaSelect = new Combobox(page.findByDataQa('area-select'))
Expand Down Expand Up @@ -454,6 +468,17 @@ export class UnitEditor {
}
}

export class UnitClosingDateModal extends Modal {
closingDate: DatePicker
closingDateInfo: Element

constructor(locator: Element) {
super(locator)
this.closingDate = new DatePicker(this.findByDataQa('closing-date'))
this.closingDateInfo = this.findByDataQa('closing-date-info')
}
}

class DaycareAclAdditionModal extends Modal {
permanentSelect = new SelectionChip(
this.find('[data-qa="add-daycare-acl-type-permanent"]')
Expand Down Expand Up @@ -807,6 +832,7 @@ export class ApplicationProcessPage {
waitingConfirmation: WaitingConfirmationSection
placementProposals: PlacementProposalsSection
serviceApplications: ServiceApplicationsSection

constructor(private readonly page: Page) {
this.waitingConfirmation = new WaitingConfirmationSection(
page.findByDataQa('waiting-confirmation-section')
Expand Down Expand Up @@ -861,6 +887,7 @@ class ServiceApplicationsSection {
.waitUntilVisible()
await this.page.findAllByDataQa('service-application-row').assertCount(n)
}

applicationRow = (n: number) =>
this.page.findAllByDataQa('service-application-row').nth(n)
applicationChildLink = (n: number) =>
Expand All @@ -870,6 +897,7 @@ class ServiceApplicationsSection {
class PlacementProposalsSection {
#placementProposalTable: Element
#acceptButton: Element

constructor(private readonly page: Page) {
this.#placementProposalTable = page.findByDataQa('placement-proposal-table')
this.#acceptButton = page.findByDataQa('placement-proposals-accept-button')
Expand Down
34 changes: 34 additions & 0 deletions frontend/src/e2e-test/specs/5_employee/unit-editor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,40 @@ describe('Employee - unit details', () => {
const unitDetailsPage = await unitEditorPage.submit()
await unitDetailsPage.assertMealTimes(mealTimes)
})

test('Admin can edit unit closing date', async () => {
const placementStart = today.addMonths(1)
const placementEnd = today.addYears(1)

const child = await Fixture.person().saveChild()
await Fixture.placement({
childId: child.id,
unitId: daycare1.id,
startDate: placementStart,
endDate: placementEnd
}).save()

const unitRow = unitsPage.unitRow(daycare1.id)
const unitPage = await unitRow.openUnit()
const unitInfoPage = await unitPage.openUnitInformation()
const unitDetailsPage = await unitInfoPage.openUnitDetails()
const unitClosingDateModal = await unitDetailsPage.openClosingDateModal()

await unitClosingDateModal.closingDate.fill(placementEnd.subDays(1))
await unitClosingDateModal.click()
await unitClosingDateModal.closingDateInfo.assertTextEquals(
'Valitse myöhäisempi päivä'
)
await unitClosingDateModal.submitButton.assertDisabled(true)

await unitClosingDateModal.closingDate.fill(placementEnd)
await unitClosingDateModal.click()
await unitClosingDateModal.closingDateInfo.waitUntilHidden()
await unitClosingDateModal.submit()
await unitDetailsPage.openingAndClosingDates.assertTextEquals(
`- ${placementEnd.format()}`
)
})
})

describe('Employee - unit editor validations and warnings', () => {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/e2e-test/utils/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ export class DatePicker extends Element {
async fill(date: LocalDate | string) {
const text = typeof date === 'string' ? date : date.format()
await this.#input.fill(text)
await this.#input.press('Escape')
await this.#input.blur()
}

async assertValueEquals(value: string) {
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/employee-frontend/components/unit/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import {
getUnitNotifications,
getUnits,
updateDaycare,
updateGroup
updateGroup,
updateUnitClosingDate
} from '../../generated/api-clients/daycare'
import {
getOccupancyPeriodsSpeculated,
Expand Down Expand Up @@ -225,6 +226,11 @@ export const updateUnitMutation = mutation({
invalidateQueryKeys: ({ daycareId }) => [queryKeys.unit(daycareId)]
})

export const updateUnitClosingDateMutation = mutation({
api: updateUnitClosingDate,
invalidateQueryKeys: ({ unitId }) => [queryKeys.unit(unitId)]
})

export const unitGroupDetailsQuery = query({
api: getUnitGroupDetails,
queryKey: ({ unitId, from, to }) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// SPDX-FileCopyrightText: 2017-2024 City of Espoo
//
// SPDX-License-Identifier: LGPL-2.1-or-later

import max from 'lodash/max'
import React from 'react'
import styled from 'styled-components'

import { localDate } from 'lib-common/form/fields'
import { object, required, validated } from 'lib-common/form/form'
import { useForm, useFormFields } from 'lib-common/form/hooks'
import { Daycare } from 'lib-common/generated/api-types/daycare'
import LocalDate from 'lib-common/local-date'
import { DatePickerF } from 'lib-components/molecules/date-picker/DatePicker'
import { MutateFormModal } from 'lib-components/molecules/modals/FormModal'

import { useTranslation } from '../../../state/i18n'
import { updateUnitClosingDateMutation } from '../queries'

interface UnitClosingDateModalProps {
unit: Daycare
lastPlacementDate: LocalDate | null
onClose: () => void
}

export const closingDateIsBeforeLastPlacementDate = (
closingDate: LocalDate | null,
lastPlacementDate: LocalDate | null | undefined
) =>
closingDate !== null &&
lastPlacementDate !== null &&
lastPlacementDate !== undefined &&
closingDate.isBefore(lastPlacementDate)

const form = validated(
object({
openingDate: localDate(),
closingDate: required(localDate()),
lastPlacementDate: localDate()
}),
({ openingDate, closingDate, lastPlacementDate }) =>
(openingDate !== undefined && closingDate.isBefore(openingDate)) ||
closingDateIsBeforeLastPlacementDate(closingDate, lastPlacementDate)
? { closingDate: 'dateTooEarly' }
: undefined
)

export const UnitClosingDateModal = React.memo(function UnitClosingDateModal({
unit,
lastPlacementDate,
onClose
}: UnitClosingDateModalProps) {
const { i18n, lang } = useTranslation()
const boundForm = useForm(
form,
() => ({
openingDate: localDate.fromDate(unit.openingDate),
closingDate: localDate.fromDate(unit.closingDate, {
minDate: max([unit.openingDate, lastPlacementDate]) ?? undefined
}),
lastPlacementDate: localDate.fromDate(lastPlacementDate)
}),
i18n.validationErrors
)
const { closingDate } = useFormFields(boundForm)

return (
<MutateFormModal
title={i18n.unitEditor.closingDateModal}
resolveLabel={i18n.common.save}
rejectLabel={i18n.common.cancel}
resolveDisabled={!boundForm.isValid()}
resolveMutation={updateUnitClosingDateMutation}
resolveAction={() => ({
unitId: unit.id,
closingDate: closingDate.value()
})}
rejectAction={onClose}
onSuccess={onClose}
data-qa="unit-closing-date-modal"
>
<Center>
<DatePickerF
bind={closingDate}
locale={lang}
hideErrorsBeforeTouched
data-qa="closing-date"
/>
</Center>
</MutateFormModal>
)
})

const Center = styled.div`
display: flex;
justify-content: center;
`
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from 'employee-frontend/components/child-information/daily-service-times/DailyServiceTimesForms'
import { DayOfWeek } from 'employee-frontend/types'
import DateRange from 'lib-common/date-range'
import { useBoolean } from 'lib-common/form/hooks'
import { UpdateStateFn } from 'lib-common/form-state'
import { time } from 'lib-common/form-validation'
import {
Expand Down Expand Up @@ -49,11 +50,16 @@ import { fontWeights, H1, H3 } from 'lib-components/typography'
import { defaultMargins, Gap } from 'lib-components/white-space'
import colors from 'lib-customizations/common'
import { featureFlags, unitProviderTypes } from 'lib-customizations/employee'
import { faPen } from 'lib-icons'
import { faPen, farXmark } from 'lib-icons'

import { Translations, useTranslation } from '../../../state/i18n'
import { FinanceDecisionHandlerOption } from '../../../state/invoicing-ui'

import {
closingDateIsBeforeLastPlacementDate,
UnitClosingDateModal
} from './UnitClosingDateModal'

// CareType is a mix of these two enums
type OnlyCareType = 'DAYCARE' | 'PRESCHOOL' | 'PREPARATORY_EDUCATION' | 'CLUB'
type OnlyDaycareType = 'CENTRE' | 'FAMILY' | 'GROUP_FAMILY'
Expand Down Expand Up @@ -498,13 +504,11 @@ function validateForm(
})
}
if (
form.closingDate != null &&
lastPlacementDate != null &&
form.closingDate.isBefore(lastPlacementDate)
closingDateIsBeforeLastPlacementDate(form.closingDate, lastPlacementDate)
) {
errors.push({
text: i18n.unitEditor.error.closingDateBeforeLastPlacementDate(
lastPlacementDate
lastPlacementDate!
),
key: 'unit-closing-placement'
})
Expand Down Expand Up @@ -971,6 +975,11 @@ export default function UnitEditor(props: Props) {
[form.financeDecisionHandlerId] // eslint-disable-line react-hooks/exhaustive-deps
)

const [
showClosingDateModal,
{ on: closingDateModalOn, off: closingDateModalOff }
] = useBoolean(false)

const getFormData = () => {
const [fields, errors] = validateForm(i18n, props.lastPlacementDate, form)
setValidationErrors(errors)
Expand Down Expand Up @@ -1006,16 +1015,32 @@ export default function UnitEditor(props: Props) {
<TopBar>
<H1 fitted>{props.unit.name}</H1>
{!props.editable && (
<Button
appearance="inline"
icon={faPen}
onClick={onClickEditHandler}
text={i18n.common.edit}
data-qa="enable-edit-button"
/>
<FixedSpaceRow>
<Button
appearance="inline"
icon={faPen}
onClick={onClickEditHandler}
text={i18n.common.edit}
data-qa="enable-edit-button"
/>
<Button
appearance="inline"
icon={farXmark}
onClick={closingDateModalOn}
text={i18n.unitEditor.closingDateModal}
data-qa="open-closing-date-modal"
/>
</FixedSpaceRow>
)}
</TopBar>
)}
{props.unit && showClosingDateModal && (
<UnitClosingDateModal
unit={props.unit}
lastPlacementDate={props.lastPlacementDate}
onClose={closingDateModalOff}
/>
)}
{!props.unit && <H1>{i18n.titles.createUnit}</H1>}
<H3>{i18n.unit.info.title}</H3>
<FormPart>
Expand All @@ -1038,7 +1063,7 @@ export default function UnitEditor(props: Props) {
<FormPart>
<div>{`${i18n.unitEditor.label.openingDate} / ${i18n.unitEditor.label.closingDate}`}</div>
<AlertBoxContainer>
<div>
<div data-qa="opening-and-closing-dates">
{props.editable ? (
<DatePickerDeprecated
date={form.openingDate ?? undefined}
Expand Down
Loading

0 comments on commit fd3cf79

Please sign in to comment.