Skip to content

Commit

Permalink
Make the dialog more usable
Browse files Browse the repository at this point in the history
  • Loading branch information
klaustopher committed Dec 11, 2024
1 parent c4d058e commit 5f3398a
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 130 deletions.
66 changes: 41 additions & 25 deletions frontend/src/app/core/path-helper/path-helper.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class PathHelperService {
return this.appBasePath;
}

public attachmentDownloadPath(attachmentIdentifier:string, slug:string|undefined) {
public attachmentDownloadPath(attachmentIdentifier:string, slug:string | undefined) {
const path = `${this.staticBase}/attachments/${attachmentIdentifier}`;

if (slug) {
Expand All @@ -50,7 +50,7 @@ export class PathHelperService {
return path;
}

public attachmentContentPath(attachmentIdentifier:number|string) {
public attachmentContentPath(attachmentIdentifier:number | string) {
return `${this.staticBase}/attachments/${attachmentIdentifier}/content`;
}

Expand All @@ -66,15 +66,15 @@ export class PathHelperService {
return `${this.ifcModelsPath(projectIdentifier)}/new`;
}

public ifcModelsEditPath(projectIdentifier:string, modelId:number|string) {
public ifcModelsEditPath(projectIdentifier:string, modelId:number | string) {
return `${this.ifcModelsPath(projectIdentifier)}/${modelId}/edit`;
}

public ifcModelsDeletePath(projectIdentifier:string, modelId:number|string) {
public ifcModelsDeletePath(projectIdentifier:string, modelId:number | string) {
return `${this.ifcModelsPath(projectIdentifier)}/${modelId}`;
}

public bimDetailsPath(projectIdentifier:string, workPackageId:string, viewpoint:number|string|null = null) {
public bimDetailsPath(projectIdentifier:string, workPackageId:string, viewpoint:number | string | null = null) {
let path = `${this.projectPath(projectIdentifier)}/bcf/details/${workPackageId}`;

if (viewpoint !== null) {
Expand Down Expand Up @@ -168,7 +168,7 @@ export class PathHelperService {
return `${this.projectPath(projectId)}/wiki`;
}

public projectWorkPackagePath(projectId:string, wpId:string|number) {
public projectWorkPackagePath(projectId:string, wpId:string | number) {
return `${this.projectWorkPackagesPath(projectId)}/${wpId}`;
}

Expand All @@ -180,22 +180,22 @@ export class PathHelperService {
return `${this.projectWorkPackagesPath(projectId)}/new`;
}

public boardsPath(projectIdentifier:string|null) {
public boardsPath(projectIdentifier:string | null) {
if (projectIdentifier) {
return `${this.projectPath(projectIdentifier)}/boards`;
}
return `${this.staticBase}/boards`;
}

public newBoardsPath(projectIdentifier:string|null) {
public newBoardsPath(projectIdentifier:string | null) {
return `${this.boardsPath(projectIdentifier)}/new`;
}

public projectDashboardsPath(projectIdentifier:string) {
return `${this.projectPath(projectIdentifier)}/dashboards`;
}

public timeEntriesPath(workPackageId:string|number) {
public timeEntriesPath(workPackageId:string | number) {
const suffix = '/time_entries';

if (workPackageId) {
Expand All @@ -216,87 +216,87 @@ export class PathHelperService {
return `${this.staticBase}/placeholder_users`;
}

public userPath(id:string|number) {
public userPath(id:string | number) {
return `${this.usersPath()}/${id}`;
}

public userHoverCardPath(id:string|number) {
public userHoverCardPath(id:string | number) {
return `${this.usersPath()}/${id}/hover_card`;
}

public placeholderUserPath(id:string|number) {
public placeholderUserPath(id:string | number) {
return `${this.placeholderUsersPath()}/${id}`;
}

public groupPath(id:string|number) {
public groupPath(id:string | number) {
return `${this.groupsPath()}/${id}`;
}

public rolesPath() {
return `${this.staticBase}/roles`;
}

public rolePath(id:string|number) {
public rolePath(id:string | number) {
return `${this.rolesPath()}/${id}`;
}

public versionsPath() {
return `${this.staticBase}/versions`;
}

public versionEditPath(id:string|number) {
public versionEditPath(id:string | number) {
return `${this.staticBase}/versions/${id}/edit`;
}

public versionShowPath(id:string|number) {
public versionShowPath(id:string | number) {
return `${this.staticBase}/versions/${id}`;
}

public workPackagesPath() {
return `${this.staticBase}/work_packages`;
}

public workPackagePath(id:string|number) {
public workPackagePath(id:string | number) {
return `${this.staticBase}/work_packages/${id}`;
}

public workPackageShortPath(id:string|number) {
public workPackageShortPath(id:string | number) {
return `${this.staticBase}/wp/${id}`;
}

public workPackageCopyPath(workPackageId:string|number) {
public workPackageCopyPath(workPackageId:string | number) {
return `${this.workPackagePath(workPackageId)}/copy`;
}

public workPackageDetailsPath(projectIdentifier:string, workPackageId:string|number, tab?:string) {
public workPackageDetailsPath(projectIdentifier:string, workPackageId:string | number, tab?:string) {
if (tab) {
return `${this.projectWorkPackagePath(projectIdentifier, workPackageId)}/details/${tab}`;
}

return `${this.projectWorkPackagesPath(projectIdentifier)}/details/${workPackageId}`;
}

public workPackageDetailsCopyPath(projectIdentifier:string, workPackageId:string|number) {
public workPackageDetailsCopyPath(projectIdentifier:string, workPackageId:string | number) {
return this.workPackageDetailsPath(projectIdentifier, workPackageId, 'copy');
}

public workPackageSharePath(workPackageId:string|number) {
public workPackageSharePath(workPackageId:string | number) {
return `${this.workPackagePath(workPackageId)}/shares`;
}

public workPackageHoverCardPath(workPackageId:string|number) {
public workPackageHoverCardPath(workPackageId:string | number) {
return `${this.workPackagePath(workPackageId)}/hover_card`;
}

public workPackageProgressModalPath(workPackageId:string|number) {
public workPackageProgressModalPath(workPackageId:string | number) {
if (workPackageId === 'new') {
return `${this.workPackagePath(workPackageId)}/progress/new`;
}

return `${this.workPackagePath(workPackageId)}/progress/edit`;
}

public workPackageUpdateCounterPath(workPackageId:string|number, counter:string) {
public workPackageUpdateCounterPath(workPackageId:string | number, counter:string) {
return `${this.workPackagePath(workPackageId)}/split_view/update_counter?counter=${counter}`;
}

Expand Down Expand Up @@ -325,4 +325,20 @@ export class PathHelperService {
public jobStatusModalPath(jobId:string) {
return `${this.staticBase}/job_statuses/${jobId}/dialog`;
}

public timeEntriesUserTimezoneCaption(userId:string) {
return `${this.staticBase}/time_entries/users/${userId}/tz_caption`;
}

public timeEntriesWorkPackageActicity(workPackageId:string) {
return `${this.staticBase}/time_entries/work_packages/${workPackageId}/time_entry_activities`;
}

public timeEntryWorkPackageDialog(workPackageId:string) {
return `${this.workPackagePath(workPackageId)}/time_entries/dialog`;
}

public timeEntryProjectDialog(projectId:string) {
return `${this.projectPath(projectId)}/time_entries/dialog`;
}
}
27 changes: 24 additions & 3 deletions frontend/src/stimulus/controllers/dynamic/time-entry.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@

import { Controller } from '@hotwired/stimulus';
import { parseChronicDuration, outputChronicDuration } from 'core-app/shared/helpers/chronic_duration';
import { TurboRequestsService } from 'core-app/core/turbo/turbo-requests.service';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';

export default class TimeEntryController extends Controller {
static targets = ['startTimeInput', 'endTimeInput', 'hoursInput'];
Expand All @@ -40,6 +42,21 @@ export default class TimeEntryController extends Controller {
declare readonly hasEndTimeInputTarget:boolean;
declare readonly hoursInputTarget:HTMLInputElement;

private turboRequests:TurboRequestsService;
private pathHelper:PathHelperService;

async connect() {
const context = await window.OpenProject.getPluginContext();
this.turboRequests = context.services.turboRequests;
this.pathHelper = context.services.pathHelperService;
}

userChanged(event:InputEvent) {
const userId = (event.currentTarget as HTMLInputElement).value;

void this.turboRequests.request(this.pathHelper.timeEntriesUserTimezoneCaption(userId), { method: 'GET' });
}

timeInputChanged(event:InputEvent) {
this.datesChanged(event.currentTarget as HTMLInputElement);
}
Expand All @@ -62,7 +79,9 @@ export default class TimeEntryController extends Controller {
if (startTimeInMinutes && endTimeInMinutes && (hoursInMinutes === 0 || initiatedBy === this.endTimeInputTarget)) {
hoursInMinutes = endTimeInMinutes - startTimeInMinutes;
if (hoursInMinutes <= 0) { hoursInMinutes += 24 * 60; }
this.hoursInputTarget.value = outputChronicDuration(hoursInMinutes * 60, { format: 'hours_only' }) || '';

const formattedHours = outputChronicDuration(hoursInMinutes * 60, { format: 'hours_only' }) || '';
this.hoursInputTarget.value = formattedHours;
} else if (startTimeInMinutes && hoursInMinutes) {
const newEndTime = (startTimeInMinutes + hoursInMinutes) % (24 * 60);

Expand Down Expand Up @@ -92,9 +111,11 @@ export default class TimeEntryController extends Controller {

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

if (startTimeInMinutes + hoursInMinutes >= (24 * 60)) {
if (startTimeInMinutes + hoursInMinutes >= 24 * 60) {
const diffInDays = Math.floor((startTimeInMinutes + hoursInMinutes) / (60 * 24));
const span = document.createElement('span');
span.className = 'FormControl-caption';
Expand Down
38 changes: 31 additions & 7 deletions modules/costs/app/components/time_entries/time_entry_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ class TimeEntryForm < ApplicationForm
form do |f|
f.autocompleter(
name: :user_id,
id: "time_entry_user_id",
label: TimeEntry.human_attribute_name(:user),
required: true,
autocomplete_options: {
defaultData: true,
hiddenFieldAction: "change->time-entry#userChanged",
component: "opce-user-autocompleter",
url: ::API::V3::Utilities::PathHelper::ApiV3Path.principals,
filters: [{ name: "type", operator: "=", values: %w[User Group] },
{ name: "member", operator: "=", values: [model.project_id] },
{ name: "status", operator: "=", values: [Principal.statuses[:active], Principal.statuses[:invited]] }],
filters: user_completer_filter_options,
searchKey: "any_name_attribute",
resource: "principals",
focusDirectly: false,
Expand Down Expand Up @@ -57,12 +57,13 @@ class TimeEntryForm < ApplicationForm
f.text_field name: :hours,
required: true,
label: TimeEntry.human_attribute_name(:hours),
value: ChronicDuration.output(model.hours * 3600, format: :hours_only),
value: model.hours.present? ? ChronicDuration.output(model.hours * 3600, format: :hours_only) : "",
data: { "time-entry-target" => "hoursInput",
"action" => "blur->time-entry#hoursChanged keypress.enter->time-entry#hoursKeyEnterPress" }

f.work_package_autocompleter name: :work_package_id,
label: TimeEntry.human_attribute_name(:work_package),
required: true,
autocomplete_options: {
focusDirectly: false,
append_to: "#time-entry-dialog",
Expand All @@ -71,10 +72,20 @@ class TimeEntryForm < ApplicationForm
]
}

f.select_list name: :activity_id, label: TimeEntry.human_attribute_name(:activity), include_blank: true do |list|
f.autocompleter(
name: :activity_id,
label: TimeEntry.human_attribute_name(:activity),
required: false,
include_blank: true,
autocomplete_options: {
focusDirectly: false,
multiple: false,
decorated: true,
append_to: "#time-entry-dialog"
}
) do |select|
activities.each do |activity|
selected = (model.activity_id == activity.id) || (model.activity_id.blank? && activity.is_default?)
list.option(value: activity.id, label: activity.name, selected:)
select.option(value: activity.id, label: activity.name, selected: (model.activity_id == activity.id))
end
end

Expand Down Expand Up @@ -108,5 +119,18 @@ def show_start_and_end_time_fields?
def activities
TimeEntryActivity.active_in_project(project)
end

def user_autocompleter_filter_options
filters = [
{ name: "type", operator: "=", values: %w[User Group] },
{ name: "status", operator: "=", values: [Principal.statuses[:active], Principal.statuses[:invited]] }
]

if model.project_id
filters << { name: "member", operator: "=", values: [model.project_id] }
end

filters
end
end
end
Loading

0 comments on commit 5f3398a

Please sign in to comment.