Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
36 changes: 24 additions & 12 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 Expand Up @@ -274,7 +280,13 @@ const EventForm: React.FC<EventFormProps> = ({
/>
</Col>
</Row>
<Row>
<Row
className={`event-form-print-note ${
values.additional_information.trim()
? ''
: 'event-form-print-note--empty'
}`}
Comment thread
ToivoMattilaHelsinki marked this conversation as resolved.
Outdated
>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
<Col sm="12" md={{ size: 8, offset: 1 }} lg={{ size: 8, offset: 1 }}>
<Input
type="textarea"
Expand Down
122 changes: 122 additions & 0 deletions src/components/form/__tests__/EventForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import React from 'react';
import { render } from '@testing-library/react';
import { IntlProvider } from 'react-intl';
import { vi } from 'vitest';

import EventForm from '../EventForm';

vi.mock('../../../store/hooks', () => ({
useAppSelector: (selector: unknown) => {
if (typeof selector === 'function') {
return selector({
geo: {
addressCoordinates: null,
selectedAddress: null,
selectedContractZone: { id: 1 },
unavailableDates: [],
},
});
}

return undefined;
},
}));

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

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

vi.mock('../partitions/DateRange', () => ({
default: () => <div data-testid="date-range" />,
}));

vi.mock('../partitions/Location', () => ({
default: () => <div data-testid="location" />,
}));

const renderComponent = (additionalInformation: string) =>
render(
<IntlProvider
locale="fi"
messages={{
'form.event.title.name_and_description': 'Tapahtuman perustiedot',
'form.event.title.time': 'Aika',
'form.event.title.contact_person': 'Yhteyshenkilö',
'form.event.title.tools_and_suplies': 'Työkalut ja tarvikkeet',
'form.event.subtitle.time':
'Tapahtuman alkamispäivä voi olla tästä päivästä viikko eteenpäin.',
'form.event.subtitle.contact_person':
'Järjestäjän yhteystiedot tarvitaan urakoitsijoita varten.',
'form.event.subtitle.tools_and_suplies':
'Ilmoita tarvittavat työkalut. Urakoitsijat hoitavat oikean määrän tarvikkeita paikan päälle.',
}}
>
<EventForm
errors={{} as never}
handleBlur={vi.fn()}
handleChange={vi.fn()}
handleSubmit={vi.fn()}
touched={{} as never}
values={
{
name: '',
description: '',
estimated_attendee_count: undefined,
targets: '',
location: undefined,
organizer_first_name: '',
organizer_last_name: '',
organizer_email: '',
organizer_phone: '',
large_trash_bag_count: undefined,
small_trash_bag_count: undefined,
trash_picker_count: undefined,
maintenance_location: '',
additional_information: additionalInformation,
start_time: '',
end_time: '',
} as never
}
/>
</IntlProvider>
);

describe('<EventForm />', () => {
it('renders additional information as a printable field when populated', () => {
const { container } = renderComponent('Muita lisätietoja toimitukselle');

const additionalInformation = container.querySelector(
'[data-testid="additional_information"]'
);

expect(additionalInformation).toBeInTheDocument();
expect(
additionalInformation?.closest('.event-form-print-note')
).toBeInTheDocument();
expect(
additionalInformation?.closest('.event-form-print-skip')
).not.toBeInTheDocument();
expect(
additionalInformation?.closest('.event-form-print-note--empty')
).not.toBeInTheDocument();
});

it('hides empty additional information from the printable layout', () => {
const { container } = renderComponent('');

expect(
container.querySelector('[data-testid="additional_information"]')
).toBeInTheDocument();
expect(
container.querySelector('.event-form-print-note--empty')
).toBeInTheDocument();
});
});
75 changes: 64 additions & 11 deletions src/components/form/partitions/DateRange.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { addDays, addHours, setHours, startOfDay, subDays } from 'date-fns';
import { fi, sv } from 'date-fns/locale';
import {
addDays,
addHours,
format,
setHours,
startOfDay,
subDays,
} from 'date-fns';
import fi from 'date-fns/locale/fi';

Check failure on line 9 in src/components/form/partitions/DateRange.tsx

View workflow job for this annotation

GitHub Actions / common / Lint and build

Module '"/home/runner/work/linked-volunteering-ui/linked-volunteering-ui/node_modules/.pnpm/date-fns@4.4.0/node_modules/date-fns/locale/fi"' has no default export. Did you mean to use 'import { fi } from "/home/runner/work/linked-volunteering-ui/linked-volunteering-ui/node_modules/.pnpm/date-fns@4.4.0/node_modules/date-fns/locale/fi"' instead?
import sv from 'date-fns/locale/sv';

Check failure on line 10 in src/components/form/partitions/DateRange.tsx

View workflow job for this annotation

GitHub Actions / common / Lint and build

Module '"/home/runner/work/linked-volunteering-ui/linked-volunteering-ui/node_modules/.pnpm/date-fns@4.4.0/node_modules/date-fns/locale/sv"' has no default export. Did you mean to use 'import { sv } from "/home/runner/work/linked-volunteering-ui/linked-volunteering-ui/node_modules/.pnpm/date-fns@4.4.0/node_modules/date-fns/locale/sv"' instead?
import { FormikErrors, FormikTouched } from 'formik';
import React from 'react';
import { Event } from '../../../store/types';
Expand All @@ -8,7 +16,9 @@
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 +61,7 @@
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 @@ -74,9 +83,7 @@
};

const handleDateChange =
(id: string, oldDate: Date | string | undefined) =>
(value: Date | null) => {
if (!value) return;
(id: string, oldDate: Date | string | undefined) => (value: Date) => {
const oldDateObj = ensureDate(oldDate);
onChange(id)(
oldDateObj ? setHours(value, oldDateObj.getHours()) : addHours(value, 9)
Expand Down Expand Up @@ -139,23 +146,40 @@
);
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}
error={errors.start_time}
touched={!!touched.start_time}
onChange={handleDateChange('start_time', values.start_time)}

Check failure on line 182 in src/components/form/partitions/DateRange.tsx

View workflow job for this annotation

GitHub Actions / common / Lint and build

Type '(value: Date) => void' is not assignable to type '((date: Date | null, event?: KeyboardEvent<HTMLElement> | MouseEvent<HTMLElement, MouseEvent> | undefined) => void) | ((date: [...], event?: KeyboardEvent<...> | ... 1 more ... | undefined) => void) | ((dates: Date[] | null, event?: KeyboardEvent<...> | ... 1 more ... | undefined) => void) | undefined'.
onBlur={onBlur('start_time')}
highlightDates={[now]}
selected={selectedStartTime}
Expand All @@ -169,8 +193,9 @@
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,22 +211,38 @@
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}
error={errors.end_time}
touched={!!touched.end_time}
onChange={handleDateChange('end_time', values.end_time)}

Check failure on line 245 in src/components/form/partitions/DateRange.tsx

View workflow job for this annotation

GitHub Actions / common / Lint and build

Type '(value: Date) => void' is not assignable to type '((date: Date | null, event?: KeyboardEvent<HTMLElement> | MouseEvent<HTMLElement, MouseEvent> | undefined) => void) | ((date: [...], event?: KeyboardEvent<...> | ... 1 more ... | undefined) => void) | ((dates: Date[] | null, event?: KeyboardEvent<...> | ... 1 more ... | undefined) => void) | undefined'.
onBlur={onBlur('end_time')}
selected={selectedEndTime}
dateFormat={dateFormat}
Expand All @@ -214,8 +255,9 @@
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 +273,20 @@
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
Loading
Loading