Skip to content

Commit

Permalink
Start on using Primer components inside the datepicker modal
Browse files Browse the repository at this point in the history
  • Loading branch information
HDinger committed Dec 5, 2024
1 parent cc72da4 commit c12309a
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<%=
component_wrapper do
component_collection do |collection|
if show_banner?
collection.with_component(Primer::Alpha::Banner.new(scheme: banner_scheme,
full: true,
icon: :info,
description: banner_description,
mb: 3,
classes: "rounded-top-2")) do |banner|
banner.with_action_button(tag: :a, href: banner_link, target: "_blank") { I18n.t("work_packages.datepicker_modal.show_relations") }
banner_title
end
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(mode: :manual),
data: { turbo_stream: true },
label: I18n.t("work_packages.datepicker_modal.mode.manual"),
title: I18n.t("work_packages.datepicker_modal.mode.manual"),
selected: @mode.to_s == MANUAL_MODE)
control.with_item(tag: :a,
href: datepicker_dialog_content_work_package_path(mode: :automatic),
data: { turbo_stream: true },
label: I18n.t("work_packages.datepicker_modal.mode.automatic"),
title: I18n.t("work_packages.datepicker_modal.mode.automatic"),
selected: @mode.to_s == AUTOMATIC_MODE)
end
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")))
end
end
end
end
end
end
end
%>
108 changes: 108 additions & 0 deletions app/components/work_packages/date_picker/dialog_content_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# frozen_string_literal: true

module WorkPackages
module DatePicker
class DialogContentComponent < ApplicationComponent
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable

AUTOMATIC_MODE = "automatic"
MANUAL_MODE = "manual"

def initialize(work_package:, mode: MANUAL_MODE)
super

@work_package = work_package
@mode = mode
end

private

def manually_scheduled?
@work_package.schedule_manually
end

def show_banner?
true # TODO
end

def banner_scheme
manually_scheduled? ? :warning : :default
end

def banner_link
gantt_index_path(
query_props: {
c: %w[id subject type status assignee project startDate dueDate],
tll: '{"left":"startDate","right":"subject","farRight":null}',
tzl: "auto",
t: "id:asc",
tv: true,
hi: true,
f: [
{ "n" => "id", "o" => "=", "v" => all_relational_wp_ids }
]
}.to_json.freeze
)
end

def banner_title
if manually_scheduled?
I18n.t("work_packages.datepicker_modal.banner.title.manually_scheduled")
elsif children.any?
I18n.t("work_packages.datepicker_modal.banner.title.automatic_with_children")
elsif predecessor_relations.any?
I18n.t("work_packages.datepicker_modal.banner.title.automatic_with_predecessor")
end
end

def banner_description
if manually_scheduled?
if children.any?
return I18n.t("work_packages.datepicker_modal.banner.description.manual_with_children")
elsif predecessor_relations.any?
if overlapping_predecessor?
return I18n.t("work_packages.datepicker_modal.banner.description.manual_overlap_with_predecessors")
elsif predecessor_with_large_gap?
return I18n.t("work_packages.datepicker_modal.banner.description.manual_gap_between_predecessors")
end
end
end

I18n.t("work_packages.datepicker_modal.banner.description.click_on_show_relations_to_open_gantt",
button_name: I18n.t("js.work_packages.datepicker_modal.show_relations"))
end

def overlapping_predecessor?
predecessor_work_packages.any? { |wp| wp.due_date.after?(@work_package.start_date) }
end

def predecessor_with_large_gap?
sorted = predecessor_work_packages.sort_by(&:due_date)
sorted.last.due_date.before?(@work_package.start_date - 2)
end

def predecessor_relations
@predecessor_relations ||= @work_package.follows_relations
end

def predecessor_work_packages
@predecessor_work_packages ||= predecessor_relations
.includes(:to)
.map(&:to)
end

def children
@children ||= @work_package.children
end

def all_relational_wp_ids
@work_package
.relations
.pluck(:from_id, :to_id)
.flatten
.uniq
end
end
end
end
24 changes: 22 additions & 2 deletions app/controllers/work_packages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,13 @@ 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
authorization_checked! :index, :show, :export_dialog, :generate_pdf_dialog, :generate_pdf, :datepicker_dialog_content

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 @@ -106,6 +107,25 @@ def generate_pdf
redirect_back(fallback_location: work_package_path(work_package))
end

def datepicker_dialog_content
mode = params.delete :mode || WorkPackages::DatePicker::DialogContentComponent::MANUAL_MODE

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

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

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

Expand Down Expand Up @@ -174,7 +194,7 @@ def per_page_param
end

def project
@project ||= work_package ? work_package.project : nil
@project ||= work_package&.project
end

def work_package
Expand Down
5 changes: 5 additions & 0 deletions app/views/work_packages/datepicker_dialog_content.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<%=
content_tag("turbo-frame", id: "wp-datepicker-dialog--content") do
render(WorkPackages::DatePicker::DialogContentComponent.new(work_package:, mode:))
end
%>
19 changes: 19 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,25 @@ en:
no_results_title_text: There are currently no workflows.

work_packages:
datepicker_modal:
banner:
description:
click_on_show_relations_to_open_gantt: 'Click on "%{button_name}" for Gantt overview.'
manual_gap_between_predecessors: "There is a gap between this and all predecessors."
manual_overlap_with_predecessors: "Overlaps with at least one predecessor."
manual_with_children: "This has child work package but their start dates are ignored."
title:
automatic_with_children: "The dates are determined by child work packages."
automatic_with_predecessor: "The start date is set by a predecessor."
manually_scheduled: "Manually scheduled. Dates not affected by relations."
ignore_non_working_days:
title: "Working days only"
mode:
title: "Scheduling mode"
automatic: "Automatic"
manual: "Manual"
show_relations: "Show relations"

x_descendants:
one: "One descendant work package"
other: "%{count} work package descendants"
Expand Down
9 changes: 0 additions & 9 deletions config/locales/js-en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -983,15 +983,6 @@ en:
comment_send_failed: "An error has occurred. Could not submit the comment."
comment_updated: "The comment was successfully updated."
confirm_edit_cancel: "Are you sure you want to cancel editing the work package?"
datepicker_modal:
automatically_scheduled_parent: "Automatically scheduled. Dates are derived from relations."
manually_scheduled: "Manual scheduling enabled, all relations ignored."
start_date_limited_by_relations: "Available start and finish dates are limited by relations."
changing_dates_affects_follow_relations: "Changing these dates will affect dates of related work packages."
click_on_show_relations_to_open_gantt: 'Click on "%{button_name}" for GANTT overview.'
show_relations: "Show relations"
ignore_non_working_days:
title: "Working days only"
description_filter: "Filter"
description_enter_text: "Enter text"
description_options_hide: "Hide options"
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,7 @@

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"

get "/split_view/update_counter" => "work_packages/split_view#update_counter",
on: :member
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/app/core/path-helper/path-helper.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,10 @@ export class PathHelperService {
return `${this.workPackagePath(workPackageId)}/split_view/update_counter?counter=${counter}`;
}

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

// Work Package Bulk paths

public workPackagesBulkEditPath() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { OpBasicDatePickerModule } from './basic-datepicker.module';
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';

@NgModule({
imports: [
Expand All @@ -27,6 +28,7 @@ import { OpDatePickerSheetComponent } from 'core-app/shared/components/datepicke
OpSpotModule,
OpBasicDatePickerModule,
OpenprojectModalModule,
OpenprojectContentLoaderModule,
],

providers: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,22 @@
tabindex="0"
(submit)="doSave($event)"
>
<op-datepicker-banner
[scheduleManually]="scheduleManually"
class="op-datepicker-modal--banner"
></op-datepicker-banner>
<turbo-frame *ngIf="turboFrameSrc"
[src]="turboFrameSrc"
id="wp-datepicker-dialog--content">
<op-content-loader viewBox="0 0 100 100">
<svg:rect x="0" y="0" width="70" height="5" rx="1"/>

<div class="spot-container op-datepicker-modal--stretch-content">
<div class="op-datepicker-modal--toggle-actions-container">
<svg:rect x="75" y="0" width="25" height="5" rx="1"/>

<svg:rect x="0" y="10" width="100" height="8" rx="1"/>

<svg:rect x="0" y="25" width="100" height="12" rx="1"/>
</op-content-loader>
</turbo-frame>

<!--<div class="spot-container op-datepicker-modal&#45;&#45;stretch-content">
<div class="op-datepicker-modal&#45;&#45;toggle-actions-container">
<op-datepicker-scheduling-toggle
name="scheduleManually"
[(ngModel)]="scheduleManually"
Expand All @@ -27,18 +36,18 @@
></op-datepicker-working-days-toggle>
</div>
<div class="op-datepicker-modal--dates-container">
<div class="op-datepicker-modal&#45;&#45;dates-container">
<spot-form-field
[label]="text.startDate"
data-test-selector="datepicker-start-date"
>
<spot-text-field
slot="input"
name="startDate"
data-test-selector="op-datepicker-modal--start-date-field"
class="op-datepicker-modal--date-field"
data-test-selector="op-datepicker-modal&#45;&#45;start-date-field"
class="op-datepicker-modal&#45;&#45;date-field"
[attr.data-qa-highlighted]="showFieldAsActive('start') || undefined"
[ngClass]="{'op-datepicker-modal--date-field_current' : showFieldAsActive('start')}"
[ngClass]="{'op-datepicker-modal&#45;&#45;date-field_current' : showFieldAsActive('start')}"
[ngModel]="dates.start"
(ngModelChange)="startDateChanged$.next($event)"
[disabled]="!isSchedulable"
Expand All @@ -49,7 +58,7 @@
slot="action"
type="button"
class="spot-link"
[ngClass]="{ 'op-datepicker-modal--hidden-link': !showTodayLink() }"
[ngClass]="{ 'op-datepicker-modal&#45;&#45;hidden-link': !showTodayLink() }"
(click)="setToday('start')"
[textContent]="text.today">
</button>
Expand All @@ -61,10 +70,10 @@
<spot-text-field
slot="input"
name="endDate"
data-test-selector="op-datepicker-modal--end-date-field"
class="op-datepicker-modal--date-field"
data-test-selector="op-datepicker-modal&#45;&#45;end-date-field"
class="op-datepicker-modal&#45;&#45;date-field"
[attr.data-qa-highlighted]="showFieldAsActive('end') || undefined"
[ngClass]="{'op-datepicker-modal--date-field_current' : showFieldAsActive('end')}"
[ngClass]="{'op-datepicker-modal&#45;&#45;date-field_current' : showFieldAsActive('end')}"
[ngModel]="dates.end"
(ngModelChange)="endDateChanged$.next($event)"
[disabled]="!isSchedulable"
Expand All @@ -75,7 +84,7 @@
slot="action"
type="button"
class="spot-link"
[ngClass]="{ 'op-datepicker-modal--hidden-link': !showTodayLink() }"
[ngClass]="{ 'op-datepicker-modal&#45;&#45;hidden-link': !showTodayLink() }"
(click)="setToday('end')"
[textContent]="text.today">
</button>
Expand All @@ -88,10 +97,10 @@
#durationField
slot="input"
name="duration"
data-test-selector="op-datepicker-modal--duration-field"
class="op-datepicker-modal--date-field"
data-test-selector="op-datepicker-modal&#45;&#45;duration-field"
class="op-datepicker-modal&#45;&#45;date-field"
[attr.data-qa-highlighted]=" showFieldAsActive('duration') || undefined"
[ngClass]="{'op-datepicker-modal--date-field_current' : showFieldAsActive('duration')}"
[ngClass]="{'op-datepicker-modal&#45;&#45;date-field_current' : showFieldAsActive('duration')}"
[ngModel]="durationFocused ? duration : displayedDuration"
[showClearButton]="currentlyActivatedDateField === 'duration'"
pattern="\d*"
Expand All @@ -109,7 +118,7 @@
id="flatpickr-input"
hidden
/>
</div>
</div>-->

<div class="spot-action-bar">
<div class="spot-action-bar--right">
Expand Down
Loading

0 comments on commit c12309a

Please sign in to comment.