Implement editing and adding of non working times

This commit is contained in:
Klaus Zanders
2026-03-03 16:07:20 +01:00
parent c9f795db8d
commit d3d693f239
26 changed files with 577 additions and 41 deletions
@@ -35,10 +35,15 @@ module Users
include OpPrimer::ComponentHelpers
options non_working_times: [],
year: Date.current.year
year: Date.current.year,
user: nil
private
def can_update?
user.present? && UserNonWorkingTimes::UpdateContract.can_update?(user: User.current, target_user: user)
end
def wrapper_data
{
"controller" => "users--non-working-times",
@@ -78,8 +83,9 @@ module Users
end: (clipped.end_date + 1.day).iso8601,
title: event_title(clipped),
working_days: clipped.working_days_count,
type: "user"
}
type: "user",
edit_url: can_update? ? edit_user_non_working_time_path(user, nwt.id) : nil
}.compact
end
end
@@ -0,0 +1,30 @@
<%= component_wrapper do %>
<%= render(Primer::Alpha::Dialog.new(id: DIALOG_ID, title:)) do |dialog| %>
<% dialog.with_body do %>
<%= render(Users::NonWorkingTimes::FormComponent.new(user:, non_working_time:)) %>
<% end %>
<% dialog.with_footer do %>
<%= render(Primer::Box.new(display: :flex, justify_content: :space_between, flex: 1)) do %>
<%= render(Primer::Box.new) do %>
<% if non_working_time.persisted? && can_delete? %>
<%= render(
Primer::Beta::Button.new(
scheme: :danger,
tag: :a,
href: destroy_url,
data: { turbo_method: :delete, turbo_confirm: t(:text_are_you_sure) }
)
) do %>
<%= t(:button_delete) %>
<% end %>
<% end %>
<% end %>
<%= render(Primer::Box.new(display: :flex, gap: 2)) do %>
<%= render(Primer::Beta::Button.new(data: { "close-dialog-id": DIALOG_ID })) { t(:button_cancel) } %>
<%= render(Primer::Beta::Button.new(scheme: :primary, form: Users::NonWorkingTimes::FormComponent::FORM_ID, type: :submit)) { t(:button_confirm) } %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
@@ -0,0 +1,60 @@
# 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.
#++
module Users
module NonWorkingTimes
class DialogComponent < ApplicationComponent
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers
DIALOG_ID = "non-working-time-dialog"
attr_reader :user, :non_working_time
def initialize(user:, non_working_time:, **)
super(nil, **)
@user = user
@non_working_time = non_working_time
end
def title
non_working_time.persisted? ? t(:button_edit_non_working_time) : t(:button_add_non_working_time)
end
def can_delete?
UserNonWorkingTimes::DeleteContract.can_delete?(user: User.current, target_user: user)
end
def destroy_url
user_non_working_time_path(user, non_working_time)
end
end
end
end
@@ -0,0 +1,74 @@
# 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.
#++
module Users
module NonWorkingTimes
class Form < ApplicationForm
form do |f|
f.group(layout: :horizontal) do |g|
g.single_date_picker(
name: :start_date,
label: I18n.t(:label_start_date),
required: true,
value: model.start_date&.iso8601,
datepicker_options: {
inDialog: Users::NonWorkingTimes::DialogComponent::DIALOG_ID,
data: {
action: "change->users--non-working-times-form#previewWorkingDays"
}
}
)
g.single_date_picker(
name: :end_date,
label: I18n.t(:label_end_date),
required: true,
value: model.end_date&.iso8601,
datepicker_options: {
inDialog: Users::NonWorkingTimes::DialogComponent::DIALOG_ID,
data: {
action: "change->users--non-working-times-form#previewWorkingDays"
}
}
)
g.text_field(
name: :working_days_display,
label: I18n.t(:label_working_days),
disabled: true,
value: model.working_days_count,
datepicker_options: { inDialog: Users::NonWorkingTimes::DialogComponent::DIALOG_ID },
data: { "users--non-working-times-form-target": "workingDaysInput" }
)
end
end
end
end
end
@@ -0,0 +1,20 @@
<%= component_wrapper do %>
<%= primer_form_with(
model: non_working_time,
url: form_url,
method: form_method,
id: FORM_ID,
data: {
controller: "users--non-working-times-form",
"users--non-working-times-form-preview-url-value" => working_days_preview_url
}
) do |f| %>
<%= render(Users::NonWorkingTimes::Form.new(f)) %>
<% if non_working_time.errors.any? %>
<%= render(Primer::Beta::Flash.new(scheme: :danger, mt: 2)) do %>
<%= non_working_time.errors.full_messages.join(", ") %>
<% end %>
<% end %>
<% end %>
<% end %>
@@ -0,0 +1,64 @@
# 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.
#++
module Users
module NonWorkingTimes
class FormComponent < ApplicationComponent
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers
FORM_ID = "non-working-time-form"
attr_reader :user, :non_working_time
def initialize(user:, non_working_time:, **)
super(nil, **)
@user = user
@non_working_time = non_working_time
end
def form_url
if non_working_time.persisted?
user_non_working_time_path(user, non_working_time)
else
user_non_working_times_path(user)
end
end
def form_method
non_working_time.persisted? ? :patch : :post
end
def working_days_preview_url
working_days_preview_user_non_working_times_path(user)
end
end
end
end
@@ -4,8 +4,16 @@
<% end %>
<% user_non_working_times.each do |nwt| %>
<%= render(Primer::Box.new(bg: :accent_emphasis, color: :on_emphasis, border_radius: 2, p: 2, mb: 1)) do %>
<%= range_label(nwt) %>
<% if can_update? %>
<%= link_to edit_href(nwt),
class: "color-bg-accent-emphasis color-fg-on-emphasis rounded-2 p-2 mb-1 d-block",
data: { controller: "async-dialog" } do %>
<%= range_label(nwt) %>
<% end %>
<% else %>
<%= render(Primer::Box.new(bg: :accent_emphasis, color: :on_emphasis, border_radius: 2, p: 2, mb: 1)) do %>
<%= range_label(nwt) %>
<% end %>
<% end %>
<% end %>
<% end %>
@@ -34,7 +34,8 @@ module Users
include OpPrimer::ComponentHelpers
options non_working_times: [],
year: Date.current.year
year: Date.current.year,
user: nil
private
@@ -57,18 +58,28 @@ module Users
total_user_days + global_day_count
end
def can_update?
user.present? && UserNonWorkingTimes::UpdateContract.can_update?(user: User.current, target_user: user)
end
def can_delete?
user.present? && UserNonWorkingTimes::DeleteContract.can_delete?(user: User.current, target_user: user)
end
def edit_href(clipped)
edit_user_non_working_time_path(user, clipped.id)
end
def range_label(clipped)
date_range = format_date_range(clipped.start_date, clipped.end_date)
"#{date_range}: #{I18n.t('label_x_working_days', count: clipped.working_days_count)}"
end
def format_date_range(first, last)
if first.month == last.month && first.year == last.year
"#{I18n.l(first, format: '%b %d')}-#{last.day}, #{first.year}"
elsif first.year == last.year
"#{I18n.l(first, format: '%b %d')} - #{I18n.l(last, format: '%b %d')}, #{first.year}"
if first.year == last.year
"#{I18n.l(first, format: :short)} - #{I18n.l(last, format: :short)}, #{first.year}"
else
"#{I18n.l(first, format: '%b %d, %Y')} - #{I18n.l(last, format: '%b %d, %Y')}"
"#{I18n.l(first, format: :long)} - #{I18n.l(last, format: :long)}"
end
end
end
@@ -15,14 +15,16 @@
I18n.t(:label_today_capitalized)
end
component.with_action_button(
scheme: :primary,
leading_icon: :plus,
label: I18n.t(:button_add_non_working_time),
data: { "turbo-stream" => true },
tag: :a,
href: "#"
) do
t(:button_add_non_working_time)
if can_create?
component.with_action_button(
scheme: :primary,
leading_icon: :plus,
label: I18n.t(:button_add_non_working_time),
data: { controller: "async-dialog" },
tag: :a,
href: new_non_working_time_href
) do
t(:button_add_non_working_time)
end
end
end %>
@@ -31,7 +31,15 @@
module Users
module NonWorkingTimes
class SubHeaderComponent < ApplicationComponent
options :year
options :year, :user
def can_create?
UserNonWorkingTimes::CreateContract.can_create?(user: User.current, target_user: user)
end
def new_non_working_time_href
new_user_non_working_time_path(user)
end
def previous_year_attrs
{
@@ -31,23 +31,24 @@
module Users
module NonWorkingTimes
class YearOverviewComponent < ApplicationComponent
attr_reader :non_working_times, :year
attr_reader :non_working_times, :year, :user
def initialize(year:, non_working_times:, **)
def initialize(year:, non_working_times:, user:, **)
super(**)
@year = year
@non_working_times = non_working_times
@user = user
end
def call
render(Users::NonWorkingTimes::SubHeaderComponent.new(year: year)) +
render(Users::NonWorkingTimes::SubHeaderComponent.new(year:, user:)) +
render(Primer::Alpha::Layout.new(classes: "users-non-working-times-year-overview")) do |layout|
layout.with_main do
render(Users::NonWorkingTimes::CalendarComponent.new(non_working_times: non_working_times, year: year))
render(Users::NonWorkingTimes::CalendarComponent.new(non_working_times: non_working_times, year: year, user:))
end
layout.with_sidebar(col_placement: :end) do
render(Users::NonWorkingTimes::SidebarComponent.new(non_working_times: non_working_times, year: year))
render(Users::NonWorkingTimes::SidebarComponent.new(non_working_times: non_working_times, year: year, user:))
end
end
end
@@ -38,6 +38,11 @@ module UserNonWorkingTimes
def self.model = ::UserNonWorkingTime
def self.can_manage?(user:, target_user:)
user.allowed_globally?(:manage_working_times) ||
(target_user.id == user.id && user.allowed_globally?(:manage_own_working_times))
end
private
def validate_manage_permission
@@ -30,5 +30,8 @@
module UserNonWorkingTimes
class CreateContract < BaseContract
def self.can_create?(user:, target_user:)
can_manage?(user:, target_user:)
end
end
end
@@ -34,5 +34,9 @@ module UserNonWorkingTimes
user.allowed_globally?(:manage_working_times) ||
(model.user_id == user.id && user.allowed_globally?(:manage_own_working_times))
}
def self.can_delete?(user:, target_user:)
BaseContract.can_manage?(user:, target_user:)
end
end
end
@@ -0,0 +1,37 @@
# 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.
#++
module UserNonWorkingTimes
class UpdateContract < BaseContract
def self.can_update?(user:, target_user:)
can_manage?(user:, target_user:)
end
end
end
@@ -30,16 +30,17 @@
class Users::NonWorkingTimesController < ApplicationController
include WorkingTimesAuthorization
include OpTurbo::ComponentStream
layout "admin"
before_action :check_working_times_feature_flag_is_active
authorization_checked! :index, :create, :destroy
authorization_checked! :index, :new, :create, :edit, :update, :destroy, :working_days_preview
before_action :find_user
before_action :authorize_manage_working_times
before_action :find_non_working_time, only: %i[destroy]
before_action :find_non_working_time, only: %i[edit update destroy]
def index
@year = (params[:year].presence || Date.current.year).to_i
@@ -48,32 +49,78 @@ class Users::NonWorkingTimesController < ApplicationController
render "users/edit"
end
def new
@non_working_time = @user.non_working_times.build
respond_with_dialog(
Users::NonWorkingTimes::DialogComponent.new(user: @user, non_working_time: @non_working_time)
)
end
def edit
respond_with_dialog(
Users::NonWorkingTimes::DialogComponent.new(user: @user, non_working_time: @non_working_time)
)
end
def create
call = UserNonWorkingTimes::CreateService
.new(user: current_user)
.call(**non_working_time_params, user: @user)
if call.success?
flash[:notice] = I18n.t(:notice_successful_create)
close_dialog_via_turbo_stream(Users::NonWorkingTimes::DialogComponent::DIALOG_ID)
reload_page_via_turbo_stream
else
flash[:error] = call.errors.full_messages.join(", ")
update_via_turbo_stream(
component: Users::NonWorkingTimes::FormComponent.new(user: @user, non_working_time: call.result),
status: :unprocessable_entity
)
end
redirect_to user_non_working_times_path(@user)
respond_with_turbo_streams
end
def update
call = UserNonWorkingTimes::UpdateService
.new(model: @non_working_time, user: current_user)
.call(**non_working_time_params)
if call.success?
close_dialog_via_turbo_stream(Users::NonWorkingTimes::DialogComponent::DIALOG_ID)
reload_page_via_turbo_stream
else
update_via_turbo_stream(
component: Users::NonWorkingTimes::FormComponent.new(user: @user, non_working_time: call.result),
status: :unprocessable_entity
)
end
respond_with_turbo_streams
end
def destroy
call = UserNonWorkingTimes::DeleteService
.new(model: @user_non_working_time, user: current_user)
.new(model: @non_working_time, user: current_user)
.call
if call.success?
flash[:notice] = I18n.t(:notice_successful_delete)
reload_page_via_turbo_stream
else
flash[:error] = call.errors.full_messages.join(", ")
render_error_flash_message_via_turbo_stream(message: call.errors.full_messages.join(", "))
end
redirect_to user_non_working_times_path(@user)
respond_with_turbo_streams
end
def working_days_preview
start_date = Date.parse(params[:start_date])
end_date = Date.parse(params[:end_date])
nwt = @user.non_working_times.build(start_date:, end_date:)
render json: { working_days: nwt.working_days_count }
rescue ArgumentError, TypeError
head :bad_request
end
private
@@ -85,12 +132,12 @@ class Users::NonWorkingTimesController < ApplicationController
end
def find_non_working_time
@user_non_working_time = @user.non_working_times.find(params[:id])
@non_working_time = @user.non_working_times.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
def non_working_time_params
params.expect(non_working_time: [:date])
params.expect(user_non_working_time: %i[start_date end_date])
end
end
+3
View File
@@ -70,6 +70,8 @@ class UserNonWorkingTime < ApplicationRecord
end
def working_days
return [] if start_date.blank? || end_date.blank?
working_days_in(days)
end
@@ -108,6 +110,7 @@ class UserNonWorkingTime < ApplicationRecord
def no_overlapping_ranges
return unless start_date.present? && end_date.present? && user_id.present?
return if end_date < start_date
errors.add(:start_date, :overlapping_range) if overlapping_range_exists?
end
@@ -0,0 +1,34 @@
# 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.
#++
module UserNonWorkingTimes
class UpdateService < ::BaseServices::Update
end
end
+1 -1
View File
@@ -1,2 +1,2 @@
<%= render(My::WorkingTimesHeaderComponent.new) %>
<%= render(Users::NonWorkingTimes::YearOverviewComponent.new(year: @year, non_working_times: @non_working_times)) %>
<%= render(Users::NonWorkingTimes::YearOverviewComponent.new(year: @year, non_working_times: @non_working_times, user: @user)) %>
@@ -27,7 +27,7 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<%= render(Users::NonWorkingTimes::SubHeaderComponent.new(year: @year)) %>
<%= render(Users::NonWorkingTimes::SubHeaderComponent.new(year: @year, user: @user)) %>
<%= render(Primer::OpenProject::FlexLayout.new(align_items: :flex_start, gap: :normal)) do |layout| %>
<% layout.with_column(flex: 1) do %>
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
#
# Deletes bundled javascript assets and rebuilds them.
# Useful for when your frontend doesn't work (jQuery not defined etc.) for seemingly no reason at all.
die() { yell "$*"; exit 1; }
try() { eval "$@" || die "\n\nFailed to run '$*'"; }
echo "Dropping database"
try "bundle exec rake db:drop"
echo "Deleting structure.sql to recreate a fresh DB from migrations"
try "rm -f db/structure.sql"
echo "Creating database"
try "bundle exec rake db:create"
echo "Migrating database"
try "bundle exec rake db:migrate"
echo "Seeding database"
try "bundle exec rake db:seed"
echo "✔ Done."
+4
View File
@@ -4225,8 +4225,12 @@ en:
label_non_working_days: "Availability calendar"
label_non_working_days_with_count: "Non-working days (%{count})"
label_non_working_days_summary: "Summary"
button_add_non_working_time: "Time off"
button_edit_non_working_time: "Edit time off"
label_continued_from_previous_year: "continued from previous year"
label_continues_into_next_year: "continues into next year"
label_end_date: "Finish date"
label_working_days: "Working days"
label_non_working_times_with_count: "%{year} time off (%{count})"
label_non_working_times_summary: "%{year} summary"
label_total_user_non_working_times: "Personal non-working days"
+5 -1
View File
@@ -921,7 +921,11 @@ Rails.application.routes.draw do
resources :users, constraints: { id: /(\d+|me)/ }, except: :edit do
resources :memberships, controller: "users/memberships", only: %i[update create destroy]
resources :working_hours, controller: "users/working_hours"
resources :non_working_times, controller: "users/non_working_times", only: %i[index create destroy]
resources :non_working_times, controller: "users/non_working_times", only: %i[index new create edit update destroy] do
collection do
get :working_days_preview
end
end
collection do
get "/invite" => "users/invite#start_dialog"
@@ -0,0 +1,62 @@
/*
* -- 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.
* ++
*/
import { Controller } from '@hotwired/stimulus';
export default class NonWorkingTimesFormController extends Controller {
static targets = [
'workingDaysInput',
];
static values = {
previewUrl: String,
};
declare readonly workingDaysInputTarget:HTMLInputElement;
declare readonly hasWorkingDaysInputTarget:boolean;
declare readonly previewUrlValue:string;
previewWorkingDays() {
const startDate = (this.element.querySelector<HTMLInputElement>('#user_non_working_time_start_date')?.value);
const endDate = (this.element.querySelector<HTMLInputElement>('#user_non_working_time_end_date')?.value);
if (!startDate || !endDate) return;
void fetch(`${this.previewUrlValue}?start_date=${startDate}&end_date=${endDate}`, {
headers: { Accept: 'application/json' },
})
.then((r) => r.json() as Promise<{ working_days:number }>)
.then(({ working_days }) => {
if (this.hasWorkingDaysInputTarget) {
this.workingDaysInputTarget.value = String(working_days);
}
});
}
}
@@ -32,6 +32,8 @@ import { Controller } from '@hotwired/stimulus';
import { Calendar } from '@fullcalendar/core';
import multiMonthPlugin from '@fullcalendar/multimonth';
import allLocales from '@fullcalendar/core/locales-all';
import { renderStreamMessage } from '@hotwired/turbo';
import { TurboHelpers } from 'core-turbo/helpers';
interface NonWorkingDayEvent {
date?:string;
@@ -40,6 +42,7 @@ interface NonWorkingDayEvent {
title:string;
type:'global' | 'user';
workingDays?:number;
edit_url?:string;
}
export default class NonWorkingTimesController extends Controller {
@@ -97,11 +100,29 @@ export default class NonWorkingTimesController extends Controller {
startTime: '00:00',
endTime: '24:00',
},
eventClick: (info) => {
const editUrl = info.event.extendedProps.editUrl as string | undefined;
if (editUrl) {
info.jsEvent.preventDefault();
this.openDialog(editUrl);
}
},
});
this.calendar.render();
}
private openDialog(url:string):void {
TurboHelpers.showProgressBar();
void fetch(url, {
headers: { Accept: 'text/vnd.turbo-stream.html' },
})
.then((response) => response.text())
.then((html) => { renderStreamMessage(html); })
.finally(() => { TurboHelpers.hideProgressBar(); });
}
private scrollToToday() {
if (this.yearValue !== new Date().getFullYear()) return;
@@ -126,7 +147,7 @@ export default class NonWorkingTimesController extends Controller {
start: event.start,
end: event.end,
title: event.title,
extendedProps: { workingDays: event.workingDays },
extendedProps: { workingDays: event.workingDays, editUrl: event.edit_url },
classNames: ['non-working-day--user'],
allDay: true,
};
+2
View File
@@ -27,6 +27,7 @@ import LazyPageController from './controllers/dynamic/work-packages/activities-t
import EditablePageHeaderTitleController from './controllers/dynamic/editable-page-header-title.controller';
import WorkingHoursFormController from './controllers/dynamic/users/working-hours-form.controller';
import NonWorkingTimesController from './controllers/dynamic/users/non-working-times.controller';
import NonWorkingTimesFormController from './controllers/dynamic/users/non-working-times-form.controller';
import AutoSubmit from '@stimulus-components/auto-submit';
import RevealController from '@stimulus-components/reveal';
@@ -86,6 +87,7 @@ OpenProjectStimulusApplication.preregister('select-autosize', SelectAutosizeCont
OpenProjectStimulusApplication.preregister('editable-page-header-title', EditablePageHeaderTitleController);
OpenProjectStimulusApplication.preregister('users--working-hours-form', WorkingHoursFormController);
OpenProjectStimulusApplication.preregister('users--non-working-times', NonWorkingTimesController);
OpenProjectStimulusApplication.preregister('users--non-working-times-form', NonWorkingTimesFormController);
OpenProjectStimulusApplication.preregister('check-all', CheckAllController);
OpenProjectStimulusApplication.preregister('checkable', CheckableController);
OpenProjectStimulusApplication.preregister('truncation', TruncationController);