Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional Field type - DateField #572

Merged
merged 18 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/app/components/cms/Feedback/Feedback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { FormField } from '@/api/models/cms/Page/FormFields'
import { handler } from '../utils/handler'
import CheckboxField from './Fields/Checkbox/CheckboxField'
import CheckboxesField from './Fields/Checkboxes/CheckboxesField'
import DateField from './Fields/Date/DateField'
import DropdownField from './Fields/Dropdown/DropdownField'
import EmailField from './Fields/Email/EmailField'
import MultilineField from './Fields/Multiline/MultilineField'
Expand Down Expand Up @@ -181,6 +182,10 @@ export const renderFormFields = (
<UrlField label={label} helpText={helpText} cleanName={cleanName} fieldHasError={fieldHasError} />
)}

{fieldType === 'date' && (
<DateField label={label} helpText={helpText} cleanName={cleanName} fieldHasError={fieldHasError} />
)}

{fieldType === 'dropdown' && (
<DropdownField
label={label}
Expand Down
131 changes: 131 additions & 0 deletions src/app/components/cms/Feedback/Fields/Date/DateField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import clsx from 'clsx'
import { useEffect, useState } from 'react'

import { Fieldtype } from '../../Feedback'

interface DateData {
day: string
month: string
year: string
}

export default function DateField({ label, helpText, cleanName, fieldHasError }: Readonly<Fieldtype>) {
const [hiddenDateInput, setHiddenDateInput] = useState<string>('')
const [dateData, setDateData] = useState<DateData>({
day: '',
month: '',
year: '',
})

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target

setDateData((prev) => ({
...prev,
[name]: value,
}))
}

useEffect(() => {
if (dateData.day && dateData.month && dateData.year) {
setHiddenDateInput(`${dateData.day}-${dateData.month}-${dateData.year}`)
} else {
setHiddenDateInput('')
}
}, [dateData])

return (
<div className={clsx('govuk-form-group govuk-!-margin-bottom-9', { 'govuk-form-group--error': fieldHasError })}>
{/* Hidden input field for collecting date in format dd-mm-yyyy */}
<input
aria-label="Unused Hidden Date Input"
className="govuk-visually-hidden"
name={cleanName}
type="text"
value={hiddenDateInput}
/>
<fieldset className="govuk-fieldset" role="group">
<legend className="govuk-fieldset__legend govuk-fieldset__legend--l">
<h2 className="govuk-label-wrapper">
<label
className={clsx('govuk-label govuk-label--m', { 'govuk-error-message': fieldHasError })}
htmlFor={cleanName}
>
{label}
</label>
</h2>
{helpText.length > 0 ? <div className="govuk-hint">{helpText}</div> : null}
</legend>

{fieldHasError ? (
<p id="multiline-error" className="govuk-error-message">
<span className="govuk-visually-hidden">Error:</span> Please enter a valid date
</p>
) : null}

<div className="govuk-date-input" id={cleanName}>
<div className="govuk-date-input__item">
<div className="govuk-form-group">
<label className="govuk-label govuk-date-input__label" htmlFor="day">
Day
</label>
<input
className={clsx('govuk-input govuk-date-input__input govuk-input--width-2', {
'govuk-textarea--error': fieldHasError,
})}
id="day"
name="day" // Bind name directly to the key
value={dateData.day} // Bind to dateData.day
onChange={handleChange}
type="number"
inputMode="numeric"
min={1}
max={31}
/>
</div>
</div>
<div className="govuk-date-input__item">
<div className="govuk-form-group">
<label className="govuk-label govuk-date-input__label" htmlFor="month">
Month
</label>
<input
className={clsx('govuk-input govuk-date-input__input govuk-input--width-2', {
'govuk-textarea--error': fieldHasError,
})}
id="month"
name="month"
value={dateData.month}
onChange={handleChange}
type="number"
inputMode="numeric"
min={1}
max={12}
/>
</div>
</div>
<div className="govuk-date-input__item">
<div className="govuk-form-group">
<label className="govuk-label govuk-date-input__label" htmlFor="year">
Year
</label>
<input
className={clsx('govuk-input govuk-date-input__input govuk-input--width-4', {
'govuk-textarea--error': fieldHasError,
})}
id="year"
name="year"
value={dateData.year}
onChange={handleChange}
type="number"
inputMode="numeric"
min={1900}
max={2100}
/>
</div>
</div>
</div>
</fieldset>
</div>
)
}
82 changes: 82 additions & 0 deletions src/app/components/cms/Feedback/Fields/Date/Datefield.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { render, screen } from '@/config/test-utils'

import DateField from './DateField'

describe('DateField component', () => {
const mockProps = {
label: 'Date of Birth',
helpText: 'Enter a memorable date',
cleanName: 'dob',
}

test('renders DateField component with label, helpText, and cleanName props', () => {
render(<DateField {...mockProps} />)
expect(screen.getByText('Date of Birth')).toBeInTheDocument()
expect(screen.getByText('Enter a memorable date')).toBeInTheDocument()
})

test('renders the correct label text', () => {
render(<DateField {...mockProps} />)
const label = screen.getByText('Date of Birth')
expect(label).toBeInTheDocument()
expect(label).toHaveAttribute('for', 'dob')
})

test('renders help text when provided', () => {
render(<DateField {...mockProps} />)
expect(screen.getByText('Enter a memorable date')).toBeInTheDocument()
})

test('does not render help text when empty', () => {
render(<DateField label="Date of Birth" helpText="" cleanName="dob" />)
const helpText = screen.queryByText('Enter a valid date')
expect(helpText).not.toBeInTheDocument()
})

test('renders day, month, and year input fields', () => {
render(<DateField {...mockProps} />)
const dayInput = screen.getByLabelText(/day/i)
const monthInput = screen.getByLabelText(/month/i)
const yearInput = screen.getByLabelText(/year/i)

expect(dayInput).toBeInTheDocument()
expect(monthInput).toBeInTheDocument()
expect(yearInput).toBeInTheDocument()
})

test('renders labels for Day, Month, and Year', () => {
render(<DateField {...mockProps} />)

// Check for the presence of each label
expect(screen.getByLabelText(/day/i)).toBeInTheDocument()
expect(screen.getByLabelText(/month/i)).toBeInTheDocument()
expect(screen.getByLabelText(/year/i)).toBeInTheDocument()
})

test('input fields have correct attributes', () => {
render(<DateField {...mockProps} />)

const dayInput = screen.getByLabelText(/day/i)
const monthInput = screen.getByLabelText(/month/i)
const yearInput = screen.getByLabelText(/year/i)

expect(dayInput).toHaveAttribute('id', 'day')
expect(monthInput).toHaveAttribute('id', 'month')
expect(yearInput).toHaveAttribute('id', 'year')

expect(dayInput).toHaveAttribute('name', 'day')
expect(monthInput).toHaveAttribute('name', 'month')
expect(yearInput).toHaveAttribute('name', 'year')

expect(dayInput).toHaveAttribute('type', 'number')
expect(monthInput).toHaveAttribute('type', 'number')
expect(yearInput).toHaveAttribute('type', 'number')

expect(dayInput).toHaveAttribute('min', '1')
expect(dayInput).toHaveAttribute('max', '31')
expect(monthInput).toHaveAttribute('min', '1')
expect(monthInput).toHaveAttribute('max', '12')
expect(yearInput).toHaveAttribute('min', '1900')
expect(yearInput).toHaveAttribute('max', '2100')
})
})
13 changes: 13 additions & 0 deletions src/mock-server/handlers/cms/pages/fixtures/page/feedback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,19 @@ export const feedbackMock: PageResponse<PageType.Feedback> = {
choices: '',
default_value: 'true',
},
{
id: 11,
meta: {
type: 'forms.FormField',
},
clean_name: 'enter_a_memorable_date',
label: 'Enter a memorable date',
field_type: 'date',
help_text: 'For example, 31 3 1980',
required: false,
choices: '',
default_value: '',
},
],
confirmation_slug: 'confirmation',
confirmation_panel_title: 'Form submitted',
Expand Down
Loading