Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<%=
render(
Primer::OpenProject::DangerDialog.new(
id: DIALOG_ID,
title: t("working_days.dialog.title"),
Comment thread
bsatarnejad marked this conversation as resolved.
confirm_button_text: t("working_days.dialog.confirm_button"),
form_arguments:,
size: :medium_portrait
)
) do |dialog|
dialog.with_confirmation_message do |message|
message.with_heading(tag: :h2) { t("working_days.dialog.question") }
message.with_description_content(t("working_days.dialog.description"))
end

dialog.with_additional_details do
concat hidden_settings_fields
concat(
tag.div do
if removed_non_working_days.any?
concat tag.p(t("working_days.dialog.removed_title"))
concat(
render(
Primer::BaseComponent.new(
tag: :ul,
mb: 3
)
) do
safe_join(removed_non_working_days.map { |non_working_day| tag.li(non_working_day) })
end
)
end

concat tag.p(t("working_days.dialog.warning"))
end
)
end
end
%>
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# 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 Admin
module Settings
module WorkingDays
class ConfirmDialogComponent < ApplicationComponent
include OpTurbo::Streamable

DIALOG_ID = "working-days-change-dialog"

attr_reader :form_values, :removed_non_working_days

def initialize(form_values: {}, removed_non_working_days: [])
super
@form_values = form_values
@removed_non_working_days = removed_non_working_days
end

private

def form_arguments
{
action: helpers.admin_settings_working_days_and_hours_path,
method: :patch,
data: { turbo: false }
}
end

def hidden_settings_fields
hidden_field_tags_for("settings", form_values)
end

def hidden_field_tags_for(name, value)
case value
when Hash
safe_join(
value.flat_map do |key, nested_value|
hidden_field_tags_for("#{name}[#{key}]", nested_value)
end
)
when Array
safe_join(value.map { |array_value| hidden_field_tag("#{name}[]", array_value) })
else
hidden_field_tag(name, value)
end
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,23 @@

module Admin::Settings
class WorkingDaysAndHoursSettingsController < ::Admin::SettingsController
include OpTurbo::ComponentStream

menu_item :working_days_and_hours

def confirm_changes
removed_days = non_working_days_params
.select { |nwd| nwd["_destroy"].present? }
.filter_map { |nwd| removed_non_working_day_date(nwd) }

component = Admin::Settings::WorkingDays::ConfirmDialogComponent.new(
form_values: params.expect(settings: {}).to_h,
removed_non_working_days: removed_days
)

respond_with_dialog(component)
end

def failure_callback(call)
@modified_non_working_days = modified_non_working_days_for(call.result)
flash[:error] = call.message || I18n.t(:notice_internal_server_error)
Expand All @@ -58,10 +73,18 @@ def working_days_params(settings)
end

def non_working_days_params
non_working_days = params[:settings].to_unsafe_hash[:non_working_days_attributes] || {}
non_working_days = params.expect(settings: {})[:non_working_days_attributes] || {}
non_working_days.to_h.values
end

def removed_non_working_day_date(non_working_day_params)
date = NonWorkingDay.find_by(id: non_working_day_params["id"])&.date || non_working_day_params["date"]

I18n.l(date.to_date, format: :long)
rescue Date::Error, NoMethodError
nil
end

def modified_non_working_days_for(result)
return if result.nil?

Expand Down
11 changes: 11 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6161,6 +6161,17 @@ en:
warning: >
Changing which days of the week are considered working days or non-working days
can affect the start and finish days of all work packages and life cycles in all projects in this instance.
dialog:
title: "Change working days"
description: >
Changing which days of the week are considered working days or non-working days
can affect the start and finish days of all work packages and life cycles in all projects in this instance.
removed_title: "You will remove the following days from the non-working days list:"
warning: >
The changes might take some time to take effect. You will be notified when all relevant work
packages and project life cycles have been updated.
question: "Are you sure you want to continue?"
Comment thread
bsatarnejad marked this conversation as resolved.
Outdated
confirm_button: "Save and reschedule"
journal_note:
changed: _**Working days** changed (%{changes})._
days:
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 @@ -227,15 +227,6 @@ en:
new_date: "(new)"
add_non_working_day: "Non-working day"
already_added_error: "A non-working day for this date exists already. There can only be one non-working day created for each unique date."
change_button: "Save and reschedule"
change_title: "Change working days"
removed_title: "You will remove the following days from the non-working days list:"
change_description: "Changing which days of the week are considered working days or non-working days can affect the start and finish days of all work packages and life cycles in all projects in this instance."
warning: >
The changes might take some time to take effect. You will be notified when all relevant work packages and project life cycles have been updated.


Are you sure you want to continue?

work_packages_settings:
warning_progress_calculation_mode_change_from_status_to_field_html: >-
Expand Down
3 changes: 3 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,9 @@
end
end
resource :working_days_and_hours, controller: "/admin/settings/working_days_and_hours_settings", only: %i[show update]
post "working_days_and_hours/confirm_changes",
to: "/admin/settings/working_days_and_hours_settings#confirm_changes",
as: :confirm_changes_admin_settings_working_days_and_hours
resource :users, controller: "/admin/settings/users_settings", only: %i[show update]
resource :date_format, controller: "/admin/settings/date_format_settings", only: %i[show update]
resource :icalendar, controller: "/admin/settings/icalendar_settings", only: %i[show update]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, HostBinding, Injector, Input, OnInit, ViewChild, ViewEncapsulation, inject } from '@angular/core';
import { BannersService } from 'core-app/core/enterprise/banners.service';
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, HostBinding, Input, OnInit, ViewChild, ViewEncapsulation, inject } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { OpModalService } from '../modal/modal.service';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { populateInputsFromDataset } from 'core-app/shared/components/dataset-inputs';
import { FullCalendarComponent } from '@fullcalendar/angular';
import { CalendarOptions, EventInput, EventSourceFuncArg } from '@fullcalendar/core';
import listPlugin from '@fullcalendar/list';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
import { DayResourceService } from 'core-app/core/state/days/day.service';
import { IDay } from 'core-app/core/state/days/day.model';
import { CalendarViewEvent } from 'core-app/features/calendar/op-work-packages-calendar.service';
import { opIconElement } from 'core-app/shared/helpers/op-icon-builder';
import { ConfirmDialogService } from 'core-app/shared/components/modals/confirm-dialog/confirm-dialog.service';
import { ConfirmDialogOptions } from '../modals/confirm-dialog/confirm-dialog.modal';
import { ToastService } from 'core-app/shared/components/toaster/toast.service';
import moment from 'moment-timezone';
import { TurboRequestsService } from 'core-app/core/turbo/turbo-requests.service';
import allLocales from '@fullcalendar/core/locales-all';


Expand All @@ -37,15 +31,10 @@ export interface INonWorkingDay {
export class OpNonWorkingDaysListComponent implements OnInit, AfterViewInit {
readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
protected I18n = inject(I18nService);
readonly bannersService = inject(BannersService);
readonly opModalService = inject(OpModalService);
readonly injector = inject(Injector);
readonly pathHelper = inject(PathHelperService);
readonly apiV3Service = inject(ApiV3Service);
readonly dayService = inject(DayResourceService);
readonly confirmDialogService = inject(ConfirmDialogService);
readonly toast = inject(ToastService);
readonly cdRef = inject(ChangeDetectorRef);
readonly turboRequests = inject(TurboRequestsService);

@ViewChild(FullCalendarComponent) ucCalendar:FullCalendarComponent;

Expand All @@ -59,16 +48,11 @@ export class OpNonWorkingDaysListComponent implements OnInit, AfterViewInit {
already_added_error: this.I18n.t('js.admin.working_days.already_added_error'),
new_date: this.I18n.t('js.admin.working_days.calendar.new_date'),
add_non_working_day: this.I18n.t('js.admin.working_days.add_non_working_day'),
change_description: this.I18n.t('js.admin.working_days.change_description'),
warning: this.I18n.t('js.admin.working_days.warning'),
change_button: this.I18n.t('js.admin.working_days.change_button'),
change_title: this.I18n.t('js.admin.working_days.change_title'),
removed_title: this.I18n.t('js.admin.working_days.removed_title'),
non_working_day_name: this.I18n.t('js.modals.label_name'),
add: this.I18n.t('js.button_add'),
};

form_submitted = false;
formSubmitted = false;

originalNonWorkingDays:INonWorkingDay[] = [];
nonWorkingDays:INonWorkingDay[] = [];
Expand Down Expand Up @@ -100,15 +84,10 @@ export class OpNonWorkingDaysListComponent implements OnInit, AfterViewInit {
anchor.classList.add('fc-list-day-side-text', 'op-non-working-days-list--delete-icon');
anchor.appendChild(opIconElement('icon', 'icon-delete'));

anchor.addEventListener('click', () => {
// Create 4 hidden inputs(id, name, date, _destroy) for the deleted NWD
this.nonWorkingDays = this.nonWorkingDays.map((item) => {
if (item.date === event.id) {
return { ...item, _destroy: true };
}
anchor.addEventListener('click', (clickEvent:Event) => {
clickEvent.preventDefault();

return item;
});
this.markNonWorkingDayForRemoval(event.id);
event.remove();
this.cdRef.detectChanges();
});
Expand All @@ -127,30 +106,49 @@ export class OpNonWorkingDaysListComponent implements OnInit, AfterViewInit {
private listenToFormSubmit() {
const form = this.elementRef.nativeElement.closest('form')!;
form.addEventListener('submit', (evt:Event) => {
if (!this.form_submitted
&& (this.nonWorkingDaysModified() || this.workingDaysModified())) {
this.form_submitted = true;
const target = evt.target as HTMLFormElement;
const options:ConfirmDialogOptions = {
text: {
text: this.text.change_description,
title: this.text.change_title,
button_continue: this.text.change_button,
},
dangerHighlighting: true,
divideContent: true,
refreshOnCancel: true,
showListData: this.removedNonWorkingDays.length > 0,
warningText: this.text.warning,
passedData: this.removedNonWorkingDays,
listTitle: this.text.removed_title,
};
evt.preventDefault();
void this.confirmDialogService.confirm(options).then(() => {
this.form_submitted = false;
target.submit();
});
if (!this.nonWorkingDaysModified() && !this.workingDaysModified()) {
return;
}

evt.preventDefault();

if (this.formSubmitted) {
return;
}

this.formSubmitted = true;
const target = evt.target as HTMLFormElement;

this.openConfirmChangesDialog(target);
});
}

private openConfirmChangesDialog(form:HTMLFormElement):void {
const confirmUrl = `${form.action}/confirm_changes`;
const formData = new FormData(form);
formData.delete('_method');

void this.turboRequests.request(
confirmUrl,
{
method: 'POST',
headers: { Accept: 'text/vnd.turbo-stream.html' },
body: formData,
},
false,
confirmUrl,
).finally(() => {
this.formSubmitted = false;
});
}

private markNonWorkingDayForRemoval(date:string):void {
this.nonWorkingDays = this.nonWorkingDays.map((item) => {
if (item.date === date) {
return { ...item, _destroy: true };
}

return item;
Comment thread
bsatarnejad marked this conversation as resolved.
Outdated
});
}

Expand All @@ -173,13 +171,6 @@ export class OpNonWorkingDaysListComponent implements OnInit, AfterViewInit {
});
}

public get removedNonWorkingDays():string[] {
return this
.nonWorkingDays
.filter((el) => el._destroy)
.map((el) => moment(el.date).format('MMMM DD, YYYY'));
}

// Initializes nonWorkingDays from the API
public calendarEventsFunction(
fetchInfo:EventSourceFuncArg,
Expand Down Expand Up @@ -253,7 +244,7 @@ export class OpNonWorkingDaysListComponent implements OnInit, AfterViewInit {
}

private nonWorkingDaysModified():boolean {
return this.removedNonWorkingDays.length > 0
return this.nonWorkingDays.some((el) => el._destroy)
|| this.modifiedNonWorkingDays.length > 0
|| this.nonWorkingDays.length > this.originalNonWorkingDays.length;
}
Expand Down
Loading
Loading