Skip to content

Commit

Permalink
Render the form from rails and only the actual datepicker in angular.
Browse files Browse the repository at this point in the history
  • Loading branch information
HDinger committed Dec 10, 2024
1 parent 7195790 commit e55f816
Show file tree
Hide file tree
Showing 12 changed files with 510 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,92 @@
end

collection.with_component(Primer::Alpha::Dialog::Body.new) do
flex_layout do |body|
body.with_row(mb: 3) do
flex_layout(align_items: :flex_end, justify_content: :space_between) do |first_row|
first_row.with_column do
render(Primer::Alpha::FormControl.new(label: I18n.t("work_packages.datepicker_modal.mode.title"))) do |component|
component.with_input do
render(Primer::Alpha::SegmentedControl.new("aria-label": I18n.t("work_packages.datepicker_modal.mode.title"))) do |control|
control.with_item(tag: :a,
href: datepicker_dialog_content_work_package_path(manually_scheduled: true),
data: { turbo_stream: true },
label: I18n.t("work_packages.datepicker_modal.mode.manual"),
title: I18n.t("work_packages.datepicker_modal.mode.manual"),
selected: manually_scheduled)
control.with_item(tag: :a,
href: datepicker_dialog_content_work_package_path(manually_scheduled: false),
data: { turbo_stream: true },
label: I18n.t("work_packages.datepicker_modal.mode.automatic"),
title: I18n.t("work_packages.datepicker_modal.mode.automatic"),
selected: !manually_scheduled)
primer_form_with(
model: work_package,
url: work_package_datepicker_dialog_content_path,
method: :put,
id: DIALOG_FORM_ID,
html: { autocomplete: "off" },
) do |f|
flex_layout do |body|
body.with_row(mb: 3) do
flex_layout(align_items: :flex_end, justify_content: :space_between) do |first_row|
first_row.with_column do
render(Primer::Alpha::FormControl.new(label: I18n.t("work_packages.datepicker_modal.mode.title"))) do |component|
component.with_input do
render(Primer::Alpha::SegmentedControl.new("aria-label": I18n.t("work_packages.datepicker_modal.mode.title"))) do |control|
control.with_item(tag: :a,
href: work_package_datepicker_dialog_content_path(manually_scheduled: true),
data: { turbo_stream: true },
label: I18n.t("work_packages.datepicker_modal.mode.manual"),
title: I18n.t("work_packages.datepicker_modal.mode.manual"),
selected: manually_scheduled)
control.with_item(tag: :a,
href: work_package_datepicker_dialog_content_path(manually_scheduled: false),
data: { turbo_stream: true },
label: I18n.t("work_packages.datepicker_modal.mode.automatic"),
title: I18n.t("work_packages.datepicker_modal.mode.automatic"),
selected: !manually_scheduled)
end
end
end
end

first_row.with_column(mb: 1) do
render(Primer::Alpha::CheckBox.new(name: "ignore_non_working_days",
label: I18n.t("work_packages.datepicker_modal.ignore_non_working_days.title"),
checked: work_package.ignore_non_working_days))
end
end
end

first_row.with_column(mb: 1) do
render(Primer::Alpha::CheckBox.new(name: "name",
label: I18n.t("work_packages.datepicker_modal.ignore_non_working_days.title"),
checked: work_package.ignore_non_working_days))
body.with_row(mb: 3) do
flex_layout(justify_content: :space_between) do |second_row|
second_row.with_column(mr: 3) do
render(Primer::Alpha::TextField.new(name: :start_date,
label: I18n.t("attributes.start_date"),
data: {
focus: focused_field == :start_date,
},
value: work_package.start_date))
end
second_row.with_column(mr: 3) do
render(Primer::Alpha::TextField.new(name: :due_date,
label: I18n.t("attributes.due_date"),
data: {
focus: focused_field == :start_date,
},
value: work_package.due_date))
end
second_row.with_column do
render(Primer::Alpha::TextField.new(name: :duration,
label: I18n.t("activerecord.attributes.work_package.duration"),
value: work_package.duration))
end
end
end

body.with_row(mb: 3) do
helpers.angular_component_tag "opce-wp-modal-date-picker",
inputs: {
start_date: work_package.start_date,
due_date: work_package.due_date,
ignore_non_working_days: work_package.ignore_non_working_days,
schedule_manually: manually_scheduled
}
end
end
end
end

collection.with_component(Primer::Alpha::Dialog::Footer.new) do
component_collection do |footer|
footer.with_component(Primer::ButtonComponent.new) do
I18n.t("button_cancel")
end

footer.with_component(Primer::ButtonComponent.new(scheme: :primary, type: :submit, form: DIALOG_FORM_ID)) do
I18n.t("button_submit")
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,17 @@ class DialogContentComponent < ApplicationComponent
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable

attr_accessor :work_package, :manually_scheduled
DIALOG_FORM_ID = "datepicker-form"

def initialize(work_package:, manually_scheduled: true)
attr_accessor :work_package, :manually_scheduled, :focused_field, :touched_field_map

def initialize(work_package:, manually_scheduled: true, focused_field: :start_date, touched_field_map: {})
super

@work_package = work_package
@manually_scheduled = ActiveModel::Type::Boolean.new.cast(manually_scheduled)
@focused_field = focused_field
@touched_field_map = touched_field_map
end

private
Expand Down
140 changes: 140 additions & 0 deletions app/controllers/work_packages/date_picker_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# frozen_string_literal: true

# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
# ++

class WorkPackages::DatePickerController < ApplicationController
include OpTurbo::ComponentStream

ERROR_PRONE_ATTRIBUTES = %i[start_date
due_date
duration].freeze

layout false

before_action :find_work_package
authorization_checked! :show, :update

attr_accessor :work_package

def show
respond_to do |format|
format.html do
render :show,
locals: { work_package:, manually_scheduled:, params: },
layout: false
end

format.turbo_stream do
replace_via_turbo_stream(
component: WorkPackages::DatePicker::DialogContentComponent.new(work_package:, manually_scheduled:)
)
render turbo_stream: turbo_streams
end
end
end

def update
service_call = WorkPackages::UpdateService
.new(user: current_user,
model: @work_package)
.call(work_package_datepicker_params)

if service_call.success?
respond_to do |format|
format.turbo_stream do
render turbo_stream: []
end
end
else
respond_to do |format|
format.turbo_stream do
# Bundle 422 status code into stream response so
# Angular has context as to the success or failure of
# the request in order to fetch the new set of Work Package
# attributes in the ancestry solely on success.
render turbo_stream: [
turbo_stream.morph("work_package_datepicker_modal", datepicker_modal_component)
], status: :unprocessable_entity
end
end
end
end

private

def progress_modal_component
WorkPackages::DatePicker::DialogContentComponent.new(work_package: @work_package, manually_scheduled:, focused_field:,
touched_field_map:)
end

def focused_field
params[:field]
end

def find_work_package
@work_package = WorkPackage.visible.find(params[:work_package_id])
end

def touched_field_map
params.require(:work_package)
.slice("schedule_manually_touched",
"ignore_non_working_days_touched",
"start_date_touched",
"due_date_touched",
"duration_touched")
.transform_values { _1 == "true" }
.permit!
end

def manually_scheduled
if params[:manually_scheduled].present?
params.delete(:manually_scheduled)
else
work_package.schedule_manually
end
end

def work_package_datepicker_params
params.require(:work_package)
.slice(*allowed_touched_params)
.permit!
end

def allowed_touched_params
allowed_params.filter { touched?(_1) }
end

def allowed_params
%i[schedule_manually ignore_non_working_days start_date due_date duration]
end

def touched?(field)
touched_field_map[:"#{field}_touched"]
end
end
26 changes: 1 addition & 25 deletions app/controllers/work_packages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,12 @@ class WorkPackagesController < ApplicationController

before_action :authorize_on_work_package,
:project, only: %i[show generate_pdf_dialog generate_pdf]
before_action :authorize_on_work_package, only: %i[datepicker_dialog_content]
before_action :load_and_authorize_in_optional_project,
:check_allowed_export,
:protect_from_unauthorized_export, only: %i[index export_dialog]

before_action :authorize, only: :show_conflict_flash_message
authorization_checked! :index, :show, :export_dialog, :generate_pdf_dialog, :generate_pdf, :datepicker_dialog_content
authorization_checked! :index, :show, :export_dialog, :generate_pdf_dialog, :generate_pdf

before_action :load_and_validate_query, only: :index, unless: -> { request.format.html? }
before_action :load_work_packages, only: :index, if: -> { request.format.atom? }
Expand Down Expand Up @@ -107,29 +106,6 @@ def generate_pdf
redirect_back(fallback_location: work_package_path(work_package))
end

def datepicker_dialog_content
manually_scheduled = if params[:manually_scheduled].present?
params.delete(:manually_scheduled)
else
work_package.schedule_manually
end

respond_to do |format|
format.html do
render :datepicker_dialog_content,
locals: { work_package:, manually_scheduled:, params: },
layout: false
end

format.turbo_stream do
replace_via_turbo_stream(
component: WorkPackages::DatePicker::DialogContentComponent.new(work_package:, manually_scheduled:)
)
render turbo_stream: turbo_streams
end
end
end

def show_conflict_flash_message
scheme = params[:scheme]&.to_sym || :danger

Expand Down
3 changes: 2 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,8 @@

get "/export_dialog" => "work_packages#export_dialog", on: :collection, as: "export_dialog"
get :show_conflict_flash_message, on: :collection # we don't need a specific work package for this
get "/datepicker_dialog_content" => "work_packages#datepicker_dialog_content", on: :member, as: "datepicker_dialog_content"

resource :datepicker_dialog_content, controller: "work_packages/date_picker", on: :member, as: "datepicker_dialog_content"

get "/split_view/update_counter" => "work_packages/split_view#update_counter",
on: :member
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ import { appBaseSelector, ApplicationBaseComponent } from 'core-app/core/routing
import { SpotSwitchComponent } from 'core-app/spot/components/switch/switch.component';
import { OPContextMenuService } from 'core-app/shared/components/op-context-menu/op-context-menu.service';
import { CurrentProjectService } from 'core-app/core/current-project/current-project.service';
import { OpWpModalDatePickerComponent } from 'core-app/shared/components/datepicker/wp-modal-date-picker/wp-modal-date-picker.component';

export function initializeServices(injector:Injector) {
return () => {
Expand Down Expand Up @@ -454,6 +455,7 @@ export class OpenProjectModule implements DoBootstrap {
registerCustomElement('opce-timer-account-menu', TimerAccountMenuComponent, { injector });
registerCustomElement('opce-remote-field-updater', RemoteFieldUpdaterComponent, { injector });
registerCustomElement('opce-modal-single-date-picker', OpModalSingleDatePickerComponent, { injector });
registerCustomElement('opce-wp-modal-date-picker', OpWpModalDatePickerComponent, { injector });
registerCustomElement('opce-spot-drop-modal-portal', SpotDropModalPortalComponent, { injector });
registerCustomElement('opce-spot-switch', SpotSwitchComponent, { injector });
registerCustomElement('opce-modal-overlay', OpModalOverlayComponent, { injector });
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/core/path-helper/path-helper.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ export class PathHelperService {
return `${this.workPackagePath(workPackageId)}/split_view/update_counter?counter=${counter}`;
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { OpSpotModule } from 'core-app/spot/spot.module';
import { OpenprojectModalModule } from '../modal/modal.module';
import { OpDatePickerSheetComponent } from 'core-app/shared/components/datepicker/sheet/date-picker-sheet.component';
import { OpenprojectContentLoaderModule } from 'core-app/shared/components/op-content-loader/openproject-content-loader.module';
import { OpWpMultiDateDialogContentComponent } from 'core-app/shared/components/datepicker/wp-multi-date-form/wp-multi-date-dialog-content.component';
import { OpWpModalDatePickerComponent } from 'core-app/shared/components/datepicker/wp-modal-date-picker/wp-modal-date-picker.component';

@NgModule({
imports: [
Expand All @@ -37,6 +39,8 @@ import { OpenprojectContentLoaderModule } from 'core-app/shared/components/op-co
OpWpMultiDateFormComponent,
OpWpSingleDateFormComponent,
OpDatePickerSheetComponent,
OpWpMultiDateDialogContentComponent,
OpWpModalDatePickerComponent,
],

exports: [
Expand All @@ -45,6 +49,8 @@ import { OpenprojectContentLoaderModule } from 'core-app/shared/components/op-co
OpWpSingleDateFormComponent,
OpBasicDatePickerModule,
OpDatePickerSheetComponent,
OpWpMultiDateDialogContentComponent,
OpWpModalDatePickerComponent,
],
})
export class OpDatePickerModule { }
Loading

0 comments on commit e55f816

Please sign in to comment.