Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 17 additions & 11 deletions src/components/form/EventForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const EventForm: React.FC<EventFormProps> = ({
const unavailableDates = useAppSelector(unavailableDatesSelector);

return (
<StyledForm onSubmit={handleSubmit}>
<StyledForm className="event-form" onSubmit={handleSubmit}>
<Row>
<Col sm="12" md={{ size: 8, offset: 1 }}>
<FormattedMessage
Expand Down Expand Up @@ -135,10 +135,12 @@ const EventForm: React.FC<EventFormProps> = ({
selectedAddress={selectedAddress}
selectedContractZone={selectedContractZone}
/>
<Row>
<Row className="event-form-section-heading">
<Col sm="12" md={{ size: 8, offset: 1 }}>
<FormattedMessage tagName="h2" id="form.event.title.time" />
<FormattedMessage tagName="p" id="form.event.subtitle.time" />
<div className="event-form-print-skip">
<FormattedMessage tagName="p" id="form.event.subtitle.time" />
</div>
</Col>
</Row>
<DateRange
Expand All @@ -154,10 +156,12 @@ const EventForm: React.FC<EventFormProps> = ({
<Row>
<Col sm="12" md={{ size: 8, offset: 1 }}>
<FormattedMessage tagName="h2" id="form.event.title.contact_person" />
<FormattedMessage
tagName="p"
id="form.event.subtitle.contact_person"
/>
<div className="event-form-print-skip">
<FormattedMessage
tagName="p"
id="form.event.subtitle.contact_person"
/>
</div>
</Col>
</Row>
<Row>
Expand Down Expand Up @@ -222,10 +226,12 @@ const EventForm: React.FC<EventFormProps> = ({
tagName="h2"
id="form.event.title.tools_and_suplies"
/>
<FormattedMessage
tagName="p"
id="form.event.subtitle.tools_and_suplies"
/>
<div className="event-form-print-skip">
<FormattedMessage
tagName="p"
id="form.event.subtitle.tools_and_suplies"
/>
</div>
</Col>
</Row>
<Row>
Expand Down
69 changes: 62 additions & 7 deletions src/components/form/partitions/DateRange.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { addDays, addHours, setHours, startOfDay, subDays } from 'date-fns';
import {
addDays,
addHours,
format,
setHours,
startOfDay,
subDays,
} from 'date-fns';
import { fi, sv } from 'date-fns/locale';
import { FormikErrors, FormikTouched } from 'formik';
import React from 'react';
Expand All @@ -8,7 +15,9 @@ import { useIntl } from 'react-intl';
import { Row, Col } from 'reactstrap';

import DatePicker from '../fields/DatePicker';
import PrintValue from '../fields/PrintValue';
import TimePicker from '../fields/timePicker/TimePicker';
import formatTime from '../../../utils/formatTime';

import 'react-datepicker/dist/react-datepicker.css';
import './dateRange.scss';
Expand Down Expand Up @@ -51,8 +60,7 @@ const DateRange: React.FC<Props> = ({
unavailableDates,
values,
}) => {
const onChange = (id: string) => (value: Date | null) => {
if (!value) return;
const onChange = (id: string) => (value: Date) => {
handleChange({
Comment thread
sentry[bot] marked this conversation as resolved.
target: {
id,
Expand All @@ -77,6 +85,7 @@ const DateRange: React.FC<Props> = ({
(id: string, oldDate: Date | string | undefined) =>
(value: Date | null) => {
if (!value) return;

const oldDateObj = ensureDate(oldDate);
onChange(id)(
oldDateObj ? setHours(value, oldDateObj.getHours()) : addHours(value, 9)
Expand Down Expand Up @@ -139,17 +148,34 @@ const DateRange: React.FC<Props> = ({
);
const dateFormat = getDateFormat(locale);
const timeFormat = getTimeFormat(locale);
const formatDateValue = (value: Date | undefined) =>
value ? format(value, dateFormat) : '';
const formatTimeValue = (value: Date | undefined) =>
value ? formatTime(value, timeFormat, locale) : '';
const formatPrintSummary = (labelId: string, value: string) => (
<p className="print-only date-range-print-summary">
<strong className="date-range-print-summary-label">
{formatMessage({ id: labelId })}
</strong>
<span className="date-range-print-summary-value">{value}</span>
</p>
);

return (
<>
<div className="printable date-range-printable">
<Row>
<Col
className="date-range-field"
sm="12"
md={{ size: 4, offset: 1 }}
id="date_range_start_date_wrapper"
>
<DatePicker
id="date_range_start_date"
// @ts-ignore
chooseDayAriaLabelPrefix={formatMessage({
id: 'form.event.partitions.date_range.dayAriaLabelPrefix',
})}
label="form.event.partitions.date_range.start_date.label"
placeholder="form.event.partitions.date_range.start_date.placeholder"
locale={locale}
Expand All @@ -169,8 +195,9 @@ const DateRange: React.FC<Props> = ({
showMonthDropdown
useShortMonthInDropdown
/>
<PrintValue value={formatDateValue(selectedStartTime)} />
</Col>
<Col sm="12" md={{ size: 4 }}>
<Col className="date-range-field" sm="12" md={{ size: 4 }}>
<TimePicker
id="date_range_start_time"
defaultDate={minDate}
Expand All @@ -186,16 +213,32 @@ const DateRange: React.FC<Props> = ({
timeIntervals={timeIntervals}
touched={!!touched.start_time}
/>
<PrintValue value={formatTimeValue(selectedStartTime)} />
</Col>
</Row>
<div className="print-only date-range-print-summary-row">
{formatPrintSummary(
'form.event.partitions.date_range.start_date.label',
formatDateValue(selectedStartTime)
)}
{formatPrintSummary(
'form.event.partitions.date_range.start_time.label',
formatTimeValue(selectedStartTime)
)}
</div>
<Row>
<Col
className="date-range-field"
sm="12"
md={{ size: 4, offset: 1 }}
id="date_range_end_date_wrapper"
>
<DatePicker
id="date_range_end_date"
// @ts-ignore
chooseDayAriaLabelPrefix={formatMessage({
id: 'form.event.partitions.date_range.dayAriaLabelPrefix',
})}
label="form.event.partitions.date_range.end_date.label"
placeholder="form.event.partitions.date_range.end_date.placeholder"
locale={locale}
Expand All @@ -214,8 +257,9 @@ const DateRange: React.FC<Props> = ({
showMonthDropdown
useShortMonthInDropdown
/>
<PrintValue value={formatDateValue(selectedEndTime)} />
</Col>
<Col sm="12" md={{ size: 4 }}>
<Col className="date-range-field" sm="12" md={{ size: 4 }}>
<TimePicker
id="date_range_end_time"
defaultDate={ensureDate(values.start_time) || minDate}
Expand All @@ -231,9 +275,20 @@ const DateRange: React.FC<Props> = ({
timeIntervals={timeIntervals}
touched={!!touched.end_time}
/>
<PrintValue value={formatTimeValue(selectedEndTime)} />
</Col>
</Row>
</>
<div className="print-only date-range-print-summary-row">
{formatPrintSummary(
'form.event.partitions.date_range.end_date.label',
formatDateValue(selectedEndTime)
)}
{formatPrintSummary(
'form.event.partitions.date_range.end_time.label',
formatTimeValue(selectedEndTime)
)}
</div>
</div>
);
};

Expand Down
5 changes: 5 additions & 0 deletions src/components/form/partitions/Location.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,11 @@ const Location: React.FC<Props> = ({
</Row>
<Row>
<Col sm="12" md={{ size: 8, offset: 1 }} lg={{ size: 8, offset: 1 }}>
<p className="print-only">
{formatMessage({
id: 'form.event.field.trash_location.placeholder',
})}
</p>
Comment on lines +172 to +176

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The print-only paragraph in the Location component renders a static placeholder label instead of the actual user-entered values.maintenance_location.
Severity: MEDIUM

Suggested Fix

Replace the static placeholder in the print-only paragraph with the actual value from the form state. Use the PrintValue component with values.maintenance_location as the value prop, similar to how other input components handle print rendering. This will ensure the user's input is displayed on the printed form.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/components/form/partitions/Location.tsx#L172-L176

Potential issue: The `Location` component's print-only view, intended for the printable
PDF version of the form, incorrectly displays a static placeholder label. It should
instead render the actual value entered by the user, which is stored in
`values.maintenance_location`. This is inconsistent with how other form fields like
`DateRange` are handled in the same pull request, which correctly display the user's
input in the print view. As a result, any information entered into the maintenance
location field will be missing from the printed document.

<Label htmlFor="maintenance_location" srOnly>
{formatMessage({
id: 'form.event.field.trash_location.placeholder',
Expand Down
133 changes: 133 additions & 0 deletions src/components/form/partitions/__tests__/DateRange.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import React from 'react';
import { addDays, setHours, startOfDay, format } from 'date-fns';
import { render, screen } from '@testing-library/react';
import { IntlProvider } from 'react-intl';
import { vi } from 'vitest';

import DateRange from '../DateRange';
import formatTime from '../../../../utils/formatTime';

const datePickerMock = vi.fn();
const timePickerMock = vi.fn();
const printValueMock = vi.fn();

vi.mock('../../fields/DatePicker', () => ({
default: (props: Record<string, unknown>) => {
datePickerMock(props);
return (
<div
data-testid={String(props.id)}
data-exclude-count={
Array.isArray(props.excludeDates) ? props.excludeDates.length : 0
}
/>
);
},
}));

vi.mock('../../fields/timePicker/TimePicker', () => ({
default: (props: Record<string, unknown>) => {
timePickerMock(props);
return <div data-testid={String(props.id)} />;
},
}));

vi.mock('../../fields/PrintValue', () => ({
default: (props: Record<string, unknown>) => {
printValueMock(props);
return <div data-testid="print-value" data-value={String(props.value)} />;
},
}));

const renderComponent = () => {
const startTime = setHours(addDays(startOfDay(new Date()), 8), 9);
const endTime = setHours(addDays(startOfDay(new Date()), 10), 17);
const unavailableDates = [addDays(startOfDay(new Date()), 12)];

return render(
<IntlProvider
locale="en"
messages={{
'form.event.partitions.date_range.dayAriaLabelPrefix': 'Day',
'form.event.partitions.date_range.timeCaption': 'Time',
}}
>
<DateRange
errors={{} as never}
handleBlur={vi.fn()}
handleChange={vi.fn()}
touched={{} as never}
unavailableDates={unavailableDates}
values={
{
start_time: startTime,
end_time: endTime,
} as never
}
/>
</IntlProvider>
);
};

describe('<DateRange />', () => {
it('keeps unavailable dates blocked and renders print-only date and time mirrors', () => {
datePickerMock.mockClear();
timePickerMock.mockClear();
printValueMock.mockClear();

const { container } = renderComponent();

const startPickerProps = datePickerMock.mock.calls[0][0] as {
excludeDates?: Date[];
id: string;
};
const endPickerProps = datePickerMock.mock.calls[1][0] as {
excludeDates?: Date[];
id: string;
};

expect(screen.getByTestId('date_range_start_date')).toBeInTheDocument();
expect(screen.getByTestId('date_range_end_date')).toBeInTheDocument();
expect(startPickerProps.id).toBe('date_range_start_date');
expect(endPickerProps.id).toBe('date_range_end_date');
expect(startPickerProps.excludeDates).toHaveLength(1);
expect(endPickerProps.excludeDates).toHaveLength(1);

const expectedStartDate = format(
setHours(addDays(startOfDay(new Date()), 8), 9),
'dd/MM/yyyy'
);
const expectedStartTime = formatTime(
setHours(addDays(startOfDay(new Date()), 8), 9),
'h:mm a',
'en'
);
const expectedEndDate = format(
setHours(addDays(startOfDay(new Date()), 10), 17),
'dd/MM/yyyy'
);
const expectedEndTime = formatTime(
setHours(addDays(startOfDay(new Date()), 10), 17),
'h:mm a',
'en'
);

expect(screen.getAllByTestId('print-value')).toHaveLength(4);
expect(
container.querySelectorAll('.date-range-print-summary')
).toHaveLength(4);
expect(container.querySelectorAll('.printable')).toHaveLength(1);
expect(printValueMock.mock.calls[0][0]).toMatchObject({
value: expectedStartDate,
});
expect(printValueMock.mock.calls[1][0]).toMatchObject({
value: expectedStartTime,
});
expect(printValueMock.mock.calls[2][0]).toMatchObject({
value: expectedEndDate,
});
expect(printValueMock.mock.calls[3][0]).toMatchObject({
value: expectedEndTime,
});
});
});
Loading
Loading