diff --git a/app/frontend/javascript/controllers/datetime_controller.js b/app/frontend/javascript/controllers/datetime_controller.js new file mode 100644 index 000000000..e4c2631ff --- /dev/null +++ b/app/frontend/javascript/controllers/datetime_controller.js @@ -0,0 +1,40 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + // Find the form element + const form = this.element.closest("form") + if (!form) return + + // Add a submit event listener to combine date and time before submission + form.addEventListener("submit", this.combineDateTime.bind(this)) + } + + combineDateTime(event) { + // Find all date inputs that have corresponding time inputs + const dateFields = this.element.querySelectorAll('input[data-datetime-part="date"]') + + dateFields.forEach(dateInput => { + const fieldName = dateInput.dataset.datetimeField + const timeInput = this.element.querySelector(`input[data-datetime-part="time"][data-datetime-field="${fieldName}"]`) + + if (timeInput) { + const dateValue = dateInput.value + const timeValue = timeInput.value + + // Only combine if both date and time have values, or if date has a value and field is required + if (dateValue && timeValue) { + // Combine date and time in ISO 8601 format + dateInput.value = `${dateValue}T${timeValue}` + } else if (!dateValue) { + // If date is empty, keep it empty (will trigger validation) + dateInput.value = "" + } else if (dateValue && !timeValue && dateInput.required) { + // If date exists but time is missing and field is required, keep the original date value + // This will allow server-side validation to handle missing time + dateInput.value = dateValue + } + } + }) + } +} diff --git a/app/views/events/_form.html.erb b/app/views/events/_form.html.erb index 8b136279e..3f6c7e8bf 100644 --- a/app/views/events/_form.html.erb +++ b/app/views/events/_form.html.erb @@ -43,19 +43,30 @@ <% end %> -
+
<%= f.label :start_date, "Start time", required: true, class: "block font-medium mb-1" %> - <%= f.text_field :start_date, - type: "datetime-local", - class: - "w-full rounded border-gray-300 shadow-sm px-3 py-2 focus:ring-blue-500 focus:border-blue-500", - value: @event.start_date&.strftime("%Y-%m-%dT%H:%M"), - required: true %> +
+ <%= f.text_field :start_date, + type: "date", + class: + "flex-1 rounded border-gray-300 shadow-sm px-3 py-2 focus:ring-blue-500 focus:border-blue-500", + value: @event.start_date&.strftime("%Y-%m-%d"), + required: true, + data: { datetime_part: "date", datetime_field: "start_date" } %> + + <%= text_field_tag "start_time", + @event.start_date&.strftime("%H:%M"), + type: "time", + class: + "flex-1 rounded border-gray-300 shadow-sm px-3 py-2 focus:ring-blue-500 focus:border-blue-500", + required: true, + data: { datetime_part: "time", datetime_field: "start_date" } %> +
<% if f.object.errors[:start_date].any? %>

Start date @@ -66,12 +77,23 @@

<%= f.label :end_date, "End time", required: true, class: "block font-medium mb-1" %> - <%= f.text_field :end_date, - type: "datetime-local", - class: - "w-full rounded border-gray-300 shadow-sm px-3 py-2 focus:ring-blue-500 focus:border-blue-500", - value: @event.end_date&.strftime("%Y-%m-%dT%H:%M"), - required: true %> +
+ <%= f.text_field :end_date, + type: "date", + class: + "flex-1 rounded border-gray-300 shadow-sm px-3 py-2 focus:ring-blue-500 focus:border-blue-500", + value: @event.end_date&.strftime("%Y-%m-%d"), + required: true, + data: { datetime_part: "date", datetime_field: "end_date" } %> + + <%= text_field_tag "end_time", + @event.end_date&.strftime("%H:%M"), + type: "time", + class: + "flex-1 rounded border-gray-300 shadow-sm px-3 py-2 focus:ring-blue-500 focus:border-blue-500", + required: true, + data: { datetime_part: "time", datetime_field: "end_date" } %> +
<% if f.object.errors[:end_date].any? %>

End date @@ -84,11 +106,21 @@ "Registration close time", class: "block font-medium mb-1" %> - <%= f.text_field :registration_close_date, - type: "datetime-local", - class: - "w-full rounded border-gray-300 shadow-sm px-3 py-2 focus:ring-blue-500 focus:border-blue-500", - value: @event.registration_close_date&.strftime("%Y-%m-%dT%H:%M") %> +

+ <%= f.text_field :registration_close_date, + type: "date", + class: + "flex-1 rounded border-gray-300 shadow-sm px-3 py-2 focus:ring-blue-500 focus:border-blue-500", + value: @event.registration_close_date&.strftime("%Y-%m-%d"), + data: { datetime_part: "date", datetime_field: "registration_close_date" } %> + + <%= text_field_tag "registration_close_time", + @event.registration_close_date&.strftime("%H:%M"), + type: "time", + class: + "flex-1 rounded border-gray-300 shadow-sm px-3 py-2 focus:ring-blue-500 focus:border-blue-500", + data: { datetime_part: "time", datetime_field: "registration_close_date" } %> +
<% if f.object.errors[:registration_close_date].any? %>

diff --git a/spec/views/events/_form.html.erb_spec.rb b/spec/views/events/_form.html.erb_spec.rb index 6953ae02e..bd9b97acc 100644 --- a/spec/views/events/_form.html.erb_spec.rb +++ b/spec/views/events/_form.html.erb_spec.rb @@ -19,9 +19,12 @@ expect(rendered).to have_selector("form") expect(rendered).to have_field("event[title]", with: "Original Title") expect(rendered).to have_selector("#event_rhino_description[type='hidden']", visible: :all) - expect(rendered).to have_selector("input[type='datetime-local'][name='event[start_date]']") - expect(rendered).to have_selector("input[type='datetime-local'][name='event[end_date]']") - expect(rendered).to have_selector("input[type='datetime-local'][name='event[registration_close_date]']") + expect(rendered).to have_selector("input[type='date'][name='event[start_date]']") + expect(rendered).to have_selector("input[type='time'][name='start_time']") + expect(rendered).to have_selector("input[type='date'][name='event[end_date]']") + expect(rendered).to have_selector("input[type='time'][name='end_time']") + expect(rendered).to have_selector("input[type='date'][name='event[registration_close_date]']") + expect(rendered).to have_selector("input[type='time'][name='registration_close_time']") expect(rendered).to have_selector("input[type='checkbox'][name='event[published]']") end