Skip to content

Commit

Permalink
feat: add support for multi-calendar dates
Browse files Browse the repository at this point in the history
- use UI library's CalendarInput component for date type form inputs
- specify calendar type from system settings
- add tests
  • Loading branch information
d-rita committed Jul 11, 2024
1 parent 6cf24bc commit a9a65e9
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 1 deletion.
4 changes: 4 additions & 0 deletions src/data-workspace/data-entry-cell/entry-field-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
LongText,
OptionSet,
TrueOnlyCheckbox,
DateInput,
} from '../inputs/index.js'

function InputComponent({ sharedProps, de }) {
Expand Down Expand Up @@ -46,6 +47,9 @@ function InputComponent({ sharedProps, de }) {
case VALUE_TYPES.TRUE_ONLY: {
return <TrueOnlyCheckbox {...sharedProps} />
}
case VALUE_TYPES.DATE: {
return <DateInput {...sharedProps} />
}
default: {
return <GenericInput {...sharedProps} valueType={de.valueType} />
}
Expand Down
79 changes: 79 additions & 0 deletions src/data-workspace/inputs/date-input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { useConfig } from '@dhis2/app-runtime'
import { CalendarInput } from '@dhis2/ui'
import React from 'react'
import { useField } from 'react-final-form'
import { useSetDataValueMutation, useUserInfo } from '../../shared/index.js'
import styles from './inputs.module.css'
import { InputPropTypes } from './utils.js'

export const DateInput = ({
cocId,
deId,
disabled,
fieldname,
form,
locked,
onFocus,
onKeyDown,
}) => {
const { data: userInfo } = useUserInfo()
const keyUiLocale = userInfo?.settings?.keyUiLocale

const { systemInfo } = useConfig()
const { calendar } = systemInfo

const { input, meta } = useField(fieldname, {
subscription: {
value: true,
dirty: true,
valid: true,
data: true,
},
})

const { mutate } = useSetDataValueMutation({ deId, cocId })

const syncData = (value) => {
mutate(
// Empty values need an empty string
{ value: value || '' },
{
onSuccess: () => {
form.mutators.setFieldData(fieldname, {
lastSyncedValue: value,
})
},
}
)
}

const handleChange = (value) => {
// If this value has changed, sync it to server if valid
if (meta.valid && value !== meta.data.lastSyncedValue) {
syncData(value)
}
}

return (
<div onClick={onFocus}>
<CalendarInput
{...input}
className={styles.dateInput}
autoComplete="off"
onKeyDown={onKeyDown}
disabled={disabled}
readOnly={locked}
date={input.value}
calendar={calendar}
onDateSelect={(date) => {
input.onChange(date?.calendarDateString)
handleChange(date?.calendarDateString)
}}
locale={keyUiLocale}
clearable
/>
</div>
)
}

DateInput.propTypes = InputPropTypes
77 changes: 77 additions & 0 deletions src/data-workspace/inputs/date-input.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useConfig } from '@dhis2/app-runtime'
import userEvent from '@testing-library/user-event'
import React from 'react'
import { useUserInfo } from '../../shared/index.js'
import { render } from '../../test-utils/index.js'
import { FinalFormWrapper } from '../final-form-wrapper.js'
import { DateInput } from './date-input.js'

jest.mock('../../shared/use-user-info/use-user-info.js', () => ({
useUserInfo: jest.fn(),
}))

jest.mock('@dhis2/app-runtime', () => {
const originalModule = jest.requireActual('@dhis2/app-runtime')

return {
...originalModule,
useConfig: jest.fn(),
}
})

describe('date input field', () => {
const props = {
cocId: 'HllvX50cXC0',
deId: 'rkAZZFGFEQ7',
disabled: undefined,
fieldname: 'rkAZZFGFEQ7.HllvX50cXC0',
form: {},
locked: false,
onFocus: jest.fn(),
onKeyDown: jest.fn(),
}

beforeEach(() => {
useUserInfo.mockImplementation(() => ({
data: {
settings: {
keyUiLocale: 'en',
},
},
}))
})

afterEach(jest.clearAllMocks)

it('renders date input component', async () => {
useConfig.mockImplementation(() => ({
systemInfo: { calendar: 'gregory' },
}))
const { getByText, getByRole, getByTestId, queryByTestId } = render(
<DateInput {...props} />,
{
wrapper: ({ children }) => (
<FinalFormWrapper dataValueSet={{}}>
{children}
</FinalFormWrapper>
),
}
)
const calendarInputLabel = getByText('Pick a date')
const clearCalendarInputButton = getByText('Clear')
const calendarInput = getByRole('textbox')
let calendar = queryByTestId('calendar')

expect(calendarInputLabel).toBeInTheDocument()
expect(clearCalendarInputButton).toBeInTheDocument()
expect(calendarInput.value).toBe('')
expect(calendar).not.toBeInTheDocument()

await userEvent.click(calendarInput)
calendar = getByTestId('calendar')

expect(calendar).toBeInTheDocument()
})

// todo: test calendar input for different calendars
})
1 change: 0 additions & 1 deletion src/data-workspace/inputs/generic-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
} from './validators.js'

const htmlTypeAttrsByValueType = {
[VALUE_TYPES.DATE]: 'date',
[VALUE_TYPES.DATETIME]: 'datetime-local',
[VALUE_TYPES.EMAIL]: 'email',
[VALUE_TYPES.PHONE_NUMBER]: 'tel',
Expand Down
1 change: 1 addition & 0 deletions src/data-workspace/inputs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from './file-inputs.js'
export * from './long-text.js'
export * from './option-set.js'
export * from './true-only-checkbox.js'
export * from './date-input.js'
export {
createLessThan,
createMoreThan,
Expand Down
6 changes: 6 additions & 0 deletions src/data-workspace/inputs/inputs.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,9 @@
margin-left: 0;
}
}

.dateInput {
background: none;
border: none;
}

0 comments on commit a9a65e9

Please sign in to comment.