Skip to content

Commit

Permalink
switch to native html time input element
Browse files Browse the repository at this point in the history
  • Loading branch information
klaustopher committed Dec 10, 2024
1 parent 978564d commit 5365457
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 42 deletions.
54 changes: 14 additions & 40 deletions frontend/src/stimulus/controllers/dynamic/time-entry.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,16 @@

import { Controller } from '@hotwired/stimulus';
import { parseChronicDuration, outputChronicDuration } from 'core-app/shared/helpers/chronic_duration';
import flatpickr from 'flatpickr';

interface HTMLInputElementWithFlatpickr extends HTMLInputElement {
_flatpickr?:flatpickr.Instance;
}

export default class TimeEntryController extends Controller {
static targets = ['startTimeInput', 'endTimeInput', 'hoursInput'];

declare readonly startTimeInputTarget:HTMLInputElementWithFlatpickr;
declare readonly endTimeInputTarget:HTMLInputElementWithFlatpickr;
declare readonly startTimeInputTarget:HTMLInputElement;
declare readonly endTimeInputTarget:HTMLInputElement;
declare readonly hoursInputTarget:HTMLInputElement;

startTimeInputTargetConnected() {
this.initTimePicker(this.startTimeInputTarget);
}

endTimeInputTargetConnected() {
this.initTimePicker(this.endTimeInputTarget);
timeInputChanged(event:InputEvent) {
this.datesChanged(event.currentTarget as HTMLInputElement);
}

datesChanged(initiatedBy:HTMLInputElement) {
Expand All @@ -57,25 +48,22 @@ export default class TimeEntryController extends Controller {

const startTimeInMinutes = parseInt(startTimeParts[0], 10) * 60 + parseInt(startTimeParts[1], 10);
const endTimeInMinutes = parseInt(endTimeParts[0], 10) * 60 + parseInt(endTimeParts[1], 10);
const hoursInMinutes = Math.round((parseChronicDuration(this.hoursInputTarget.value) || 0) / 60);
let hoursInMinutes = Math.round((parseChronicDuration(this.hoursInputTarget.value) || 0) / 60);

// We calculate the hours field if:
// - We have start & end time and no hours
// - We have start & end time and we have triggered the change from the end time field
if (startTimeInMinutes && endTimeInMinutes && (hoursInMinutes === 0 || initiatedBy === this.endTimeInputTarget)) {
const duration = endTimeInMinutes - startTimeInMinutes;
this.hoursInputTarget.value = outputChronicDuration(duration * 60, { format: 'hours_only' }) || '';
hoursInMinutes = endTimeInMinutes - startTimeInMinutes;
if (hoursInMinutes <= 0) { hoursInMinutes += 24 * 60; }
this.hoursInputTarget.value = outputChronicDuration(hoursInMinutes * 60, { format: 'hours_only' }) || '';
} else if (startTimeInMinutes && hoursInMinutes) {
const newEndTime = startTimeInMinutes + hoursInMinutes;
const newEndTime = (startTimeInMinutes + hoursInMinutes) % (24 * 60);

const targetDate = new Date();
targetDate.setHours(Math.floor(newEndTime / 60));
targetDate.setMinutes(Math.round(newEndTime % 60));
targetDate.setSeconds(0);
this.endTimeInputTarget._flatpickr!.setDate(targetDate); // eslint-disable-line no-underscore-dangle
this.endTimeInputTarget.value = `${Math.floor(newEndTime / 60).toString().padStart(2, '0')}:${Math.round(newEndTime % 60).toString().padStart(2, '0')}`;
}

this.toggleEndTimePlusCaption(startTimeInMinutes + hoursInMinutes);
this.toggleEndTimePlusCaption(startTimeInMinutes, hoursInMinutes);
}

hoursChanged() {
Expand All @@ -93,30 +81,16 @@ export default class TimeEntryController extends Controller {
}
}

toggleEndTimePlusCaption(endTimeInMinutes:number) {
toggleEndTimePlusCaption(startTimeInMinutes:number, hoursInMinutes:number) {
const formControl = this.endTimeInputTarget.closest('.FormControl') as HTMLElement;
formControl.querySelectorAll('.FormControl-caption').forEach((caption) => caption.remove());

if (endTimeInMinutes > (24 * 60)) {
const diffInDays = Math.floor(endTimeInMinutes / (60 * 24));
if (startTimeInMinutes + hoursInMinutes >= (24 * 60)) {
const diffInDays = Math.floor((startTimeInMinutes + hoursInMinutes) / (60 * 24));
const span = document.createElement('span');
span.className = 'FormControl-caption';
span.innerText = `+ ${diffInDays} ${diffInDays === 1 ? 'day' : 'days'}`;
formControl.append(span);
}
}

initTimePicker(field:HTMLInputElement) {
flatpickr(field, {
enableTime: true,
noCalendar: true,
dateFormat: 'H:i',
time_24hr: true,
static: true,
appendTo: document.querySelector('#time-entry-dialog') as HTMLElement,
onChange: () => {
this.datesChanged(field);
},
});
}
}
12 changes: 10 additions & 2 deletions modules/costs/app/components/time_entries/time_entry_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,24 @@ class TimeEntryForm < ApplicationForm

f.group(layout: :horizontal) do |g|
g.text_field name: :start_time,
type: "time",
required: true,
label: TimeEntry.human_attribute_name(:start_time),
value: model.start_timestamp&.strftime("%H:%M"),
data: { "time-entry-target" => "startTimeInput" }
data: {
"time-entry-target" => "startTimeInput",
"action" => "input->time-entry#timeInputChanged"
}

g.text_field name: :end_time,
type: "time",
required: true,
label: TimeEntry.human_attribute_name(:end_time),
value: model.end_timestamp&.strftime("%H:%M"),
data: { "time-entry-target" => "endTimeInput" }
data: {
"time-entry-target" => "endTimeInput",
"action" => "input->time-entry#timeInputChanged"
}
end

f.text_field name: :hours,
Expand Down

0 comments on commit 5365457

Please sign in to comment.