mirror of
https://github.com/opf/openproject.git
synced 2026-06-13 19:20:00 +00:00
Merge branch 'dev' into feature/75749-rename-ckeditor-macro-to--insert
This commit is contained in:
@@ -59,15 +59,23 @@ module WorkPackages
|
||||
|
||||
def initialize(work_package,
|
||||
focused_field: nil,
|
||||
touched_field_map: {})
|
||||
touched_field_map: {},
|
||||
submit_path: nil)
|
||||
super()
|
||||
|
||||
@work_package = work_package
|
||||
@focused_field = map_field(focused_field)
|
||||
@touched_field_map = touched_field_map
|
||||
@submit_path = submit_path
|
||||
end
|
||||
|
||||
# Defaults to the core progress controller, but callers reusing the modal
|
||||
# from another context (e.g. the resource planner) can point the form at
|
||||
# their own endpoint. The live preview derives its URL from the form
|
||||
# action too (`<action>/preview`), so a single override covers both.
|
||||
def submit_path
|
||||
return @submit_path if @submit_path
|
||||
|
||||
if work_package.new_record?
|
||||
url_for(controller: "work_packages/progress",
|
||||
action: "create")
|
||||
|
||||
@@ -34,7 +34,8 @@ module WorkPackages
|
||||
class ModalBodyComponent < BaseModalComponent # rubocop:disable OpenProject/AddPreviewForViewComponent
|
||||
def initialize(work_package,
|
||||
focused_field: nil,
|
||||
touched_field_map: {})
|
||||
touched_field_map: {},
|
||||
submit_path: nil)
|
||||
super
|
||||
|
||||
@mode = :status_based
|
||||
|
||||
@@ -35,7 +35,8 @@ module WorkPackages
|
||||
class ModalBodyComponent < BaseModalComponent
|
||||
def initialize(work_package,
|
||||
focused_field: nil,
|
||||
touched_field_map: {})
|
||||
touched_field_map: {},
|
||||
submit_path: nil)
|
||||
super
|
||||
|
||||
@mode = :work_based
|
||||
|
||||
@@ -158,6 +158,15 @@ module OpTurbo
|
||||
turbo_streams << OpTurbo::StreamComponent.new(action: :reloadPage, target: nil).render_in(view_context)
|
||||
end
|
||||
|
||||
# Dispatches a `CustomEvent` on `document` from a turbo stream, letting the
|
||||
# server signal a client-side change without knowing which listeners (if
|
||||
# any) react to it. `detail` is serialized and exposed as the event's detail.
|
||||
def dispatch_event_via_turbo_stream(name, detail: {})
|
||||
turbo_streams << OpTurbo::StreamComponent
|
||||
.new(action: :dispatchEvent, target: nil, "event-name": name, detail: detail.to_json)
|
||||
.render_in(view_context)
|
||||
end
|
||||
|
||||
def turbo_streams
|
||||
@turbo_streams ||= []
|
||||
end
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
# 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 WorkPackages
|
||||
module Progress
|
||||
# Form glue shared by every controller that renders the progress modal:
|
||||
# parses the hidden `*_touched` inputs so only fields the user actually
|
||||
# edited are written, picks the mode-appropriate attributes, and builds the
|
||||
# modal body component.
|
||||
module ModalParams
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
ERROR_PRONE_ATTRIBUTES = %i[status_id
|
||||
estimated_hours
|
||||
remaining_hours
|
||||
done_ratio].freeze
|
||||
|
||||
private
|
||||
|
||||
def progress_modal_component(submit_path: nil)
|
||||
modal_class.new(@work_package,
|
||||
focused_field:,
|
||||
touched_field_map:,
|
||||
submit_path:)
|
||||
end
|
||||
|
||||
def modal_class
|
||||
if WorkPackage.status_based_mode?
|
||||
WorkPackages::Progress::StatusBased::ModalBodyComponent
|
||||
else
|
||||
WorkPackages::Progress::WorkBased::ModalBodyComponent
|
||||
end
|
||||
end
|
||||
|
||||
def focused_field
|
||||
params[:field]
|
||||
end
|
||||
|
||||
def set_progress_attributes_to_work_package
|
||||
WorkPackages::SetAttributesService
|
||||
.new(user: current_user,
|
||||
model: @work_package,
|
||||
contract_class:)
|
||||
.call(work_package_progress_params)
|
||||
end
|
||||
|
||||
def contract_class
|
||||
if @work_package.new_record?
|
||||
WorkPackages::CreateContract
|
||||
else
|
||||
WorkPackages::UpdateContract
|
||||
end
|
||||
end
|
||||
|
||||
def work_package_progress_params
|
||||
params.require(:work_package)
|
||||
.slice(*allowed_touched_params)
|
||||
.permit!
|
||||
end
|
||||
|
||||
def allowed_touched_params
|
||||
allowed_params.filter { touched?(it) }
|
||||
end
|
||||
|
||||
def allowed_params
|
||||
if WorkPackage.status_based_mode?
|
||||
%i[estimated_hours status_id]
|
||||
else
|
||||
%i[estimated_hours remaining_hours done_ratio]
|
||||
end
|
||||
end
|
||||
|
||||
def touched?(field)
|
||||
touched_field_map[:"#{field}_touched"]
|
||||
end
|
||||
|
||||
# Tolerates a missing `work_package` param so the modal can be opened
|
||||
# without carrying the form values along (e.g. from a context menu).
|
||||
def touched_field_map
|
||||
(params[:work_package] || ActionController::Parameters.new)
|
||||
.slice("estimated_hours_touched",
|
||||
"remaining_hours_touched",
|
||||
"done_ratio_touched",
|
||||
"status_id_touched")
|
||||
.transform_values { it == "true" }
|
||||
.permit!
|
||||
end
|
||||
|
||||
def extra_error_messages(service_call)
|
||||
errors_not_handled_by_progress_modal = service_call.errors.reject do |error|
|
||||
ERROR_PRONE_ATTRIBUTES.include?(error.attribute)
|
||||
end
|
||||
|
||||
join_flash_messages(errors_not_handled_by_progress_modal.map(&:full_message))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -31,11 +31,7 @@
|
||||
class WorkPackages::ProgressController < ApplicationController
|
||||
include OpTurbo::ComponentStream
|
||||
include FlashMessagesHelper
|
||||
|
||||
ERROR_PRONE_ATTRIBUTES = %i[status_id
|
||||
estimated_hours
|
||||
remaining_hours
|
||||
done_ratio].freeze
|
||||
include WorkPackages::Progress::ModalParams
|
||||
|
||||
layout false
|
||||
authorization_checked! :new, :edit, :preview, :create, :update
|
||||
@@ -132,22 +128,6 @@ class WorkPackages::ProgressController < ApplicationController
|
||||
}
|
||||
end
|
||||
|
||||
def progress_modal_component
|
||||
modal_class.new(@work_package, focused_field:, touched_field_map:)
|
||||
end
|
||||
|
||||
def modal_class
|
||||
if WorkPackage.status_based_mode?
|
||||
WorkPackages::Progress::StatusBased::ModalBodyComponent
|
||||
else
|
||||
WorkPackages::Progress::WorkBased::ModalBodyComponent
|
||||
end
|
||||
end
|
||||
|
||||
def focused_field
|
||||
params[:field]
|
||||
end
|
||||
|
||||
def find_work_package
|
||||
@work_package = WorkPackage.visible.find(params[:work_package_id])
|
||||
end
|
||||
@@ -160,63 +140,7 @@ class WorkPackages::ProgressController < ApplicationController
|
||||
@work_package.clear_changes_information
|
||||
end
|
||||
|
||||
def touched_field_map
|
||||
params.require(:work_package)
|
||||
.slice("estimated_hours_touched",
|
||||
"remaining_hours_touched",
|
||||
"done_ratio_touched",
|
||||
"status_id_touched")
|
||||
.transform_values { it == "true" }
|
||||
.permit!
|
||||
end
|
||||
|
||||
def work_package_progress_params
|
||||
params.require(:work_package)
|
||||
.slice(*allowed_touched_params)
|
||||
.permit!
|
||||
end
|
||||
|
||||
def allowed_touched_params
|
||||
allowed_params.filter { touched?(it) }
|
||||
end
|
||||
|
||||
def allowed_params
|
||||
if WorkPackage.status_based_mode?
|
||||
%i[estimated_hours status_id]
|
||||
else
|
||||
%i[estimated_hours remaining_hours done_ratio]
|
||||
end
|
||||
end
|
||||
|
||||
def touched?(field)
|
||||
touched_field_map[:"#{field}_touched"]
|
||||
end
|
||||
|
||||
def set_progress_attributes_to_work_package
|
||||
WorkPackages::SetAttributesService
|
||||
.new(user: current_user,
|
||||
model: @work_package,
|
||||
contract_class:)
|
||||
.call(work_package_progress_params)
|
||||
end
|
||||
|
||||
def contract_class
|
||||
if @work_package.new_record?
|
||||
WorkPackages::CreateContract
|
||||
else
|
||||
WorkPackages::UpdateContract
|
||||
end
|
||||
end
|
||||
|
||||
def formatted_duration(hours)
|
||||
API::V3::Utilities::DateTimeFormatter.format_duration_from_hours(hours, allow_nil: true)
|
||||
end
|
||||
|
||||
def extra_error_messages(service_call)
|
||||
errors_not_handled_by_progress_modal = service_call.errors.reject do |error|
|
||||
ERROR_PRONE_ATTRIBUTES.include?(error.attribute)
|
||||
end
|
||||
|
||||
join_flash_messages(errors_not_handled_by_progress_modal.map(&:full_message))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -51,8 +51,8 @@ module Import
|
||||
|
||||
def try_to_find_existing_op_users
|
||||
op_attributes = to_op_attributes
|
||||
User.where(login: op_attributes[:login]).or(
|
||||
User.where(mail: op_attributes[:mail])
|
||||
User.by_login(op_attributes[:login]).or(
|
||||
User.where("LOWER(mail) = ?", op_attributes[:mail]&.downcase)
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -113,10 +113,10 @@ module ProjectIdentifiers
|
||||
@historical_identifiers ||= Project.identifier_slugs.historically_reserved.upcased_values.to_set
|
||||
end
|
||||
|
||||
def exceeds_max_length = Project.where("length(identifier) > ?", self.class.max_identifier_length)
|
||||
def contains_non_alphanumeric = Project.where("identifier ~ ?", "[^a-zA-Z0-9_]")
|
||||
def exceeds_max_length = Project.where("length(identifier) > ?", self.class.max_identifier_length)
|
||||
def contains_non_alphanumeric = Project.where("identifier ~ ?", "[^a-zA-Z0-9_]")
|
||||
def does_not_start_with_letter = Project.where("identifier ~ ?", "^[^A-Za-z]") # rubocop:disable Naming/PredicatePrefix
|
||||
def not_fully_uppercased = Project.where("identifier != UPPER(identifier)")
|
||||
def not_fully_uppercased = Project.where("identifier != UPPER(identifier)")
|
||||
|
||||
def collision_error_reason(identifier)
|
||||
if self.class.model_reserved_identifiers.include?(identifier)
|
||||
|
||||
@@ -384,10 +384,16 @@ module Import
|
||||
|
||||
jira_user = Import::JiraUser.find_by(jira_user_key:, jira_import: @jira_import)
|
||||
if jira_user
|
||||
JiraOpenProjectReference.find_by!(
|
||||
jira_entity_class: "Import::JiraUser",
|
||||
reference = JiraOpenProjectReference.find_by(
|
||||
jira_entity_class: jira_user.class.to_s,
|
||||
jira_entity_id: jira_user.id
|
||||
).op_leg
|
||||
)
|
||||
if reference
|
||||
reference.op_leg
|
||||
else
|
||||
raise "Import::JiraOpenProjectReference with jira_entity_class #{jira_user.class} " \
|
||||
"and jira_entity_id #{jira_user.id} not found!"
|
||||
end
|
||||
else
|
||||
raise "Import::JiraUser with jira_user_key #{jira_user_key} not found!"
|
||||
end
|
||||
|
||||
@@ -42,6 +42,7 @@ Rails.application.configure do
|
||||
|
||||
GoodJob::Job
|
||||
.discarded
|
||||
.where.not(error_event: GoodJob::ErrorEvents::RETRIED) # reject discarded jobs that were already retried
|
||||
.where("error LIKE ?", "NameError: uninitialized constant %")
|
||||
.filter do |job|
|
||||
# Only retry jobs with NameError related to the job class name.
|
||||
|
||||
@@ -3996,7 +3996,7 @@ fr:
|
||||
label_enumerations: Énumérations
|
||||
label_enterprise: Entreprise
|
||||
label_enterprise_active_users: "%{current}/%{limit} utilisateurs actifs inscrits"
|
||||
label_enterprise_edition: édition Enterprise
|
||||
label_enterprise_edition: Édition Enterprise
|
||||
label_enterprise_support: Support Enterprise
|
||||
label_environment: Environnement
|
||||
label_estimates_and_progress: Estimations et progression
|
||||
@@ -4526,8 +4526,8 @@ fr:
|
||||
one: 'Temps libre : 1 jour ouvrable'
|
||||
other: 'Temps libre : %{count} jours ouvrables'
|
||||
label_x_items_selected:
|
||||
one: One item selected
|
||||
other: "%{count} items selected"
|
||||
one: Un élément sélectionné
|
||||
other: "%{count} éléments sélectionnés"
|
||||
label_yesterday: hier
|
||||
label_zen_mode: Mode zen
|
||||
label_role_type: Type
|
||||
|
||||
@@ -3416,10 +3416,10 @@ zh-CN:
|
||||
no_results_text: 无结果
|
||||
header:
|
||||
project_select_component:
|
||||
all_projects: All projects
|
||||
favorites: Favorites
|
||||
leave_project: Leave project
|
||||
title: Projects
|
||||
all_projects: 所有项目
|
||||
favorites: 收藏夹
|
||||
leave_project: 退出项目
|
||||
title: 项目
|
||||
toggle_switch:
|
||||
label_on: 开启
|
||||
label_off: 关闭
|
||||
@@ -4466,7 +4466,7 @@ zh-CN:
|
||||
label_x_working_days_time_off:
|
||||
other: 休息时间:%{count} 个工作日
|
||||
label_x_items_selected:
|
||||
other: "%{count} items selected"
|
||||
other: 已选%{count}项
|
||||
label_yesterday: 昨天
|
||||
label_zen_mode: 极简模式
|
||||
label_role_type: 类型
|
||||
|
||||
@@ -49,9 +49,9 @@ Existing integrations such as GitHub and GitLab already support the new identifi
|
||||
|
||||
[See our system admin guide for detailed information on how to manage work package identifiers](../../system-admin-guide/manage-work-packages/work-package-identifiers/).
|
||||
|
||||
#### Releasing unused numerical identifiers
|
||||
#### Releasing unused project identifiers
|
||||
|
||||
When switching from the default numerical sequence to project-based work package identifiers, previously reserved numerical identifiers can be released again if they are no longer needed. This helps administrators avoid unnecessary gaps and keep numerical identifiers available if they later revert to the default sequence.
|
||||
OpenProject allows administrators to release reserved project identifiers that are no longer needed. Please note that this option is currently only available when numerical work package identifiers are enabled.
|
||||
|
||||
> [!NOTE]
|
||||
> Releasing an identifier cannot be undone. External links and integrations using it will stop resolving, and the name becomes available for any new project to claim.
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
@import "../../../modules/grids/app/components/_index.sass"
|
||||
@import "../../../modules/meeting/app/components/_index.sass"
|
||||
@import "../../../modules/overviews/app/components/_index.sass"
|
||||
@import "../../../modules/resource_management/app/components/_index.sass"
|
||||
@import "../../../modules/storages/app/components/_index.sass"
|
||||
@import "../../../modules/wikis/app/components/_index.sass"
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* -- 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 ReloadFrameOnEventController from './reload-frame-on-event.controller';
|
||||
import { setupStimulusTest, type StimulusTestContext } from 'core-stimulus/test-helpers';
|
||||
|
||||
interface ReloadableFrame extends HTMLElement {
|
||||
src:string;
|
||||
reload:() => void;
|
||||
}
|
||||
|
||||
describe('ReloadFrameOnEventController', () => {
|
||||
let ctx:StimulusTestContext;
|
||||
|
||||
// Each frame gets its own reload counter. The controller listens on
|
||||
// `document`, so a counter per frame keeps a test from observing reloads
|
||||
// triggered on another test's (or another frame's) controller.
|
||||
const mountFrame = async ():Promise<{ frame:ReloadableFrame; calls:{ count:number } }> => {
|
||||
await ctx.mount(`
|
||||
<turbo-frame id="frame"
|
||||
data-controller="reload-frame-on-event"
|
||||
data-reload-frame-on-event-event-name-value="resource-allocations:changed"
|
||||
data-reload-frame-on-event-url-value="/planner/view">
|
||||
content
|
||||
</turbo-frame>
|
||||
`);
|
||||
|
||||
const frame = ctx.container.querySelector('#frame') as unknown as ReloadableFrame;
|
||||
const calls = { count: 0 };
|
||||
frame.reload = () => { calls.count += 1; };
|
||||
return { frame, calls };
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
ctx = await setupStimulusTest({
|
||||
controllers: { 'reload-frame-on-event': ReloadFrameOnEventController },
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => ctx.dispose());
|
||||
|
||||
it('points the frame at its url on the first event, then reloads on later events', async () => {
|
||||
const { frame, calls } = await mountFrame();
|
||||
|
||||
document.dispatchEvent(new CustomEvent('resource-allocations:changed'));
|
||||
expect(frame.src).toContain('/planner/view');
|
||||
expect(calls.count).toBe(0);
|
||||
|
||||
document.dispatchEvent(new CustomEvent('resource-allocations:changed'));
|
||||
expect(calls.count).toBe(1);
|
||||
});
|
||||
|
||||
it('ignores unrelated events', async () => {
|
||||
const { frame, calls } = await mountFrame();
|
||||
|
||||
document.dispatchEvent(new CustomEvent('some-other-event'));
|
||||
|
||||
expect(frame.src || '').not.toContain('/planner/view');
|
||||
expect(calls.count).toBe(0);
|
||||
});
|
||||
|
||||
it('stops reacting once disconnected', async () => {
|
||||
const { frame, calls } = await mountFrame();
|
||||
ctx.getController<ReloadFrameOnEventController>('reload-frame-on-event', frame).disconnect();
|
||||
|
||||
document.dispatchEvent(new CustomEvent('resource-allocations:changed'));
|
||||
|
||||
expect(calls.count).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* -- 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';
|
||||
import { FrameElement } from '@hotwired/turbo';
|
||||
|
||||
// Reloads the turbo-frame this controller is attached to whenever the
|
||||
// configured document event fires. The frame starts without a `src` (so it is
|
||||
// not fetched on load); the first event points it at `urlValue`, later events
|
||||
// reload it. Pair the frame with `refresh="morph"` to reload without flicker.
|
||||
export default class ReloadFrameOnEventController extends Controller<FrameElement> {
|
||||
static values = { eventName: String, url: String };
|
||||
|
||||
declare eventNameValue:string;
|
||||
declare urlValue:string;
|
||||
|
||||
private readonly listener = ():void => {
|
||||
if (this.element.src) {
|
||||
void this.element.reload();
|
||||
} else {
|
||||
this.element.src = this.urlValue;
|
||||
}
|
||||
};
|
||||
|
||||
connect():void {
|
||||
document.addEventListener(this.eventNameValue, this.listener);
|
||||
}
|
||||
|
||||
disconnect():void {
|
||||
document.removeEventListener(this.eventNameValue, this.listener);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import KeepScrollPositionController from './controllers/keep-scroll-position.con
|
||||
import PatternInputController from './controllers/pattern-input.controller';
|
||||
import HoverCardTriggerController from './controllers/hover-card-trigger.controller';
|
||||
import ScrollIntoViewController from './controllers/scroll-into-view.controller';
|
||||
import ReloadFrameOnEventController from './controllers/reload-frame-on-event.controller';
|
||||
import CkeditorFocusController from './controllers/ckeditor-focus.controller';
|
||||
import IndexController from './controllers/dynamic/work-packages/activities-tab/index.controller';
|
||||
import AutoScrollingController from './controllers/dynamic/work-packages/activities-tab/auto-scrolling.controller';
|
||||
@@ -77,6 +78,7 @@ OpenProjectStimulusApplication.preregister('work-packages--date-picker--preview'
|
||||
OpenProjectStimulusApplication.preregister('keep-scroll-position', KeepScrollPositionController);
|
||||
OpenProjectStimulusApplication.preregister('pattern-input', PatternInputController);
|
||||
OpenProjectStimulusApplication.preregister('scroll-into-view', ScrollIntoViewController);
|
||||
OpenProjectStimulusApplication.preregister('reload-frame-on-event', ReloadFrameOnEventController);
|
||||
OpenProjectStimulusApplication.preregister('ckeditor-focus', CkeditorFocusController);
|
||||
OpenProjectStimulusApplication.preregister('auto-submit', AutoSubmit);
|
||||
OpenProjectStimulusApplication.preregister('reveal', RevealController);
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { StreamActions, StreamElement } from '@hotwired/turbo';
|
||||
|
||||
export function registerDispatchEventStreamAction() {
|
||||
StreamActions.dispatchEvent = function dispatchEventStreamAction(this:StreamElement) {
|
||||
const name = this.getAttribute('event-name');
|
||||
if (!name) { return; }
|
||||
|
||||
const detail = JSON.parse(this.getAttribute('detail') ?? '{}') as unknown;
|
||||
document.dispatchEvent(new CustomEvent(name, { detail }));
|
||||
};
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { addTurboEventListeners } from './turbo-event-listeners';
|
||||
import { registerFlashStreamAction } from './flash-stream-action';
|
||||
import { registerLiveRegionStreamAction } from './live-region-stream-action';
|
||||
import { registerInputCaptionStreamAction } from './input-caption-stream-action';
|
||||
import { registerDispatchEventStreamAction } from './dispatch-event-stream-action';
|
||||
import { addTurboGlobalListeners } from './turbo-global-listeners';
|
||||
import { applyTurboNavigationPatch } from './turbo-navigation-patch';
|
||||
import { debugLog, whenDebugging } from 'core-app/shared/helpers/debug_output';
|
||||
@@ -43,6 +44,7 @@ registerDialogStreamAction();
|
||||
registerFlashStreamAction();
|
||||
registerLiveRegionStreamAction();
|
||||
registerInputCaptionStreamAction();
|
||||
registerDispatchEventStreamAction();
|
||||
addTurboAngularWrapper();
|
||||
|
||||
StreamActions.reloadPage = function reloadPage() {
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
@import "advanced_form_group"
|
||||
@import "filterable_tree_view"
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# 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 Primer
|
||||
module OpenProject
|
||||
module Forms
|
||||
module Dsl
|
||||
# :nodoc:
|
||||
class FilterableTreeViewInput < Primer::Forms::Dsl::Input
|
||||
attr_reader :name, :label, :block
|
||||
|
||||
def initialize(name:, label: nil, **system_arguments, &block)
|
||||
@name = name
|
||||
@label = label
|
||||
@block = block
|
||||
|
||||
super(**system_arguments)
|
||||
end
|
||||
|
||||
def to_component
|
||||
FilterableTreeView.new(input: self)
|
||||
end
|
||||
|
||||
# :nocov:
|
||||
def type
|
||||
:filterable_tree_view
|
||||
end
|
||||
# :nocov:
|
||||
|
||||
# :nocov:
|
||||
def focusable?
|
||||
true
|
||||
end
|
||||
# :nocov:
|
||||
|
||||
def supports_validation?
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -84,6 +84,10 @@ module Primer
|
||||
add_input SelectPanelInput.new(builder:, form:, **decorate_options(**), &)
|
||||
end
|
||||
|
||||
def filterable_tree_view(**, &)
|
||||
add_input FilterableTreeViewInput.new(builder:, form:, **decorate_options(**), &)
|
||||
end
|
||||
|
||||
def decorate_options(include_help_text: true, help_text_options: {}, **options)
|
||||
if include_help_text && supports_help_texts?(form.model)
|
||||
attribute_name = help_text_options[:attribute_name] || options[:name]
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<div class="FormControl-filterable-tree-view-wrap">
|
||||
<%= content_tag(:fieldset, **@fieldset_arguments) do %>
|
||||
<% if @input.label %>
|
||||
<%= content_tag(:legend, **@input.label_arguments) do %>
|
||||
<%= @input.label %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<div class="mb-2">
|
||||
<%= render(Caption.new(input: @input)) %>
|
||||
</div>
|
||||
<%= render(SpacingWrapper.new) do %>
|
||||
<%=
|
||||
render(Primer::OpenProject::FilterableTreeView.new(**@tree_view_arguments)) do |tree_view|
|
||||
@input.block&.call(tree_view)
|
||||
end
|
||||
%>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -0,0 +1,59 @@
|
||||
# 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 Primer
|
||||
module OpenProject
|
||||
module Forms
|
||||
# :nodoc:
|
||||
class FilterableTreeView < Primer::Forms::BaseComponent
|
||||
delegate :builder, :form, to: :@input
|
||||
|
||||
def initialize(input:)
|
||||
super()
|
||||
|
||||
@input = input
|
||||
@input.add_label_classes("FormControl-label")
|
||||
|
||||
@fieldset_arguments = @input.input_arguments.extract!(:hidden, :class, :classes)
|
||||
Primer::Forms::Utils.classify(@fieldset_arguments)
|
||||
@fieldset_arguments.delete(:class) if @fieldset_arguments[:class].blank?
|
||||
|
||||
@tree_view_arguments = {
|
||||
**@input.input_arguments,
|
||||
form_arguments: {
|
||||
name: @input.name,
|
||||
builder: builder
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
.FormControl-filterable-tree-view-wrap
|
||||
& fieldset
|
||||
padding: 0
|
||||
margin: 0
|
||||
border: 0
|
||||
@@ -31,8 +31,8 @@ cs:
|
||||
label: Git snippets
|
||||
description: Kopírovat úryvky gitu do schránky
|
||||
git_actions:
|
||||
branch_name: Název pobočky
|
||||
commit_message: odevzdat zprávu
|
||||
branch_name: Název větve
|
||||
commit_message: Zpráva commitu
|
||||
cmd: Vytvořit větev s prázdným commitem
|
||||
title: Rychlé snippety pro Git
|
||||
copy_success: "✅ zkopírováno!"
|
||||
|
||||
@@ -78,7 +78,7 @@ module Meetings
|
||||
end
|
||||
|
||||
def all_meetings_item
|
||||
all_filter = [{ invited_user_id: { operator: "*", values: [] } }].to_json
|
||||
all_filter = [].to_json
|
||||
my_meetings_href = polymorphic_path([project, :meetings])
|
||||
query_params = { filters: all_filter }
|
||||
|
||||
|
||||
@@ -402,38 +402,6 @@ cs:
|
||||
confirm_label:
|
||||
enable: Povolit aktualizací e-mailem
|
||||
disable: Zakázat aktualizací e-mailem
|
||||
banner:
|
||||
participants:
|
||||
enabled: 'Všichni účastníci obdrží e-mailem aktualizované pozvánky do kalendáře, když přidáte nebo odeberete účastníky.
|
||||
|
||||
'
|
||||
disabled: 'E-mailové aktualizace kalendářů jsou zakázány. Účastníci nedostanou e-mail s informacemi o přidání nebo odebrání účastníků.
|
||||
|
||||
'
|
||||
draft_disabled: 'Participants will not receive an email informing them when you add or remove participants.
|
||||
|
||||
'
|
||||
onetime:
|
||||
enabled: 'Všichni účastníci obdrží e-mailem aktualizované pozvánky do kalendáře, když přidáte nebo odeberete účastníky.
|
||||
|
||||
'
|
||||
disabled: 'Participants will not receive an email informing them of changes to meeting date, time or participants.
|
||||
|
||||
'
|
||||
occurrence:
|
||||
enabled: 'E-mailové aktualizace kalendářů jsou povoleny pro tuto sérii schůzek. Účastníci dostanou e-mail s informacemi, když provedete změny.
|
||||
|
||||
'
|
||||
disabled: 'E-mailové aktualizace kalendářů jsou zakázány pro tuto sérii schůzek. Účastníci nedostanou e-mail s informacemi, když provedete změny.
|
||||
|
||||
'
|
||||
template:
|
||||
enabled: 'E-mailové aktualizace kalendářů jsou povoleny. Účastníci budou dostávat aktualizované pozvánky e-mailem, když upravíte tuto šablonu či individuální výskyty.
|
||||
|
||||
'
|
||||
disabled: 'E-mailové aktualizace kalendářů jsou zakázány pro tuto sérii schůzek. Účastníci nebudou dostávat aktualizované pozvánky e-mailem, když upravíte tuto šablonu či individuální výskyty.
|
||||
|
||||
'
|
||||
presentation_mode:
|
||||
title: Prezentační mód
|
||||
button_present: Prezentovat
|
||||
|
||||
@@ -130,6 +130,13 @@ RSpec.describe "Meetings", "Index", :js do
|
||||
|
||||
shared_examples "sidebar filtering" do |context:|
|
||||
context "when showing all meetings without invitations" do
|
||||
let!(:meeting_without_participants) do
|
||||
create(:meeting,
|
||||
project:,
|
||||
title: "Meeting without any participants!",
|
||||
start_time: business_day_at_noon + 3.hours).tap { |m| m.participants.delete_all }
|
||||
end
|
||||
|
||||
it "does not show under My meetings, but in All meetings" do
|
||||
meetings_page.visit!
|
||||
meetings_page.expect_no_meetings_listed
|
||||
@@ -137,12 +144,15 @@ RSpec.describe "Meetings", "Index", :js do
|
||||
|
||||
meetings_page.set_sidebar_filter "All meetings"
|
||||
|
||||
# It now includes the ongoing meeting I'm not invited to
|
||||
if context == :global
|
||||
[ongoing_meeting, meeting, tomorrows_meeting, other_project_meeting]
|
||||
else
|
||||
[ongoing_meeting, meeting, tomorrows_meeting]
|
||||
end
|
||||
# It now includes the ongoing meeting I'm not invited to,
|
||||
# as well as meetings without any invited participants
|
||||
expected_meetings =
|
||||
if context == :global
|
||||
[ongoing_meeting, meeting, meeting_without_participants, tomorrows_meeting, other_project_meeting]
|
||||
else
|
||||
[ongoing_meeting, meeting, meeting_without_participants, tomorrows_meeting]
|
||||
end
|
||||
meetings_page.expect_meetings_listed(*expected_meetings)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -182,8 +192,12 @@ RSpec.describe "Meetings", "Index", :js do
|
||||
it "show all past meetings" do
|
||||
meetings_page.expect_meetings_listed_in_table(yesterdays_meeting, meeting, ongoing_meeting)
|
||||
meetings_page.expect_meetings_not_listed(tomorrows_meeting)
|
||||
end
|
||||
|
||||
it "keeps the past filter selected when changing advanced filters (Regression #61875)" do
|
||||
meetings_page.set_sidebar_filter "My meetings"
|
||||
meetings_page.set_quick_filter upcoming: false
|
||||
|
||||
# keeps the past filter selected when changing advanced filters (Regression #61875)" do
|
||||
meetings_page.open_filters
|
||||
meetings_page.remove_filter "invited_user_id"
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
@import "resource_planner_views/work_package_list/table_component"
|
||||
+12
-2
@@ -34,6 +34,16 @@ module ResourceAllocations
|
||||
include OpTurbo::Streamable
|
||||
include OpPrimer::ComponentHelpers
|
||||
|
||||
# `dialog_id` and `submit_label` default to the create wizard's; the edit
|
||||
# dialog passes its own.
|
||||
def initialize(dialog_id: ResourceAllocations::NewDialogComponent::DIALOG_ID,
|
||||
submit_label: I18n.t("resource_management.allocate_resource_dialog.submit"))
|
||||
super
|
||||
|
||||
@dialog_id = dialog_id
|
||||
@submit_label = submit_label
|
||||
end
|
||||
|
||||
def wrapper_key
|
||||
ResourceAllocations::NewDialogComponent::FOOTER_ID
|
||||
end
|
||||
@@ -43,7 +53,7 @@ module ResourceAllocations
|
||||
component_collection do |buttons|
|
||||
buttons.with_component(
|
||||
Primer::Beta::Button.new(
|
||||
data: { "close-dialog-id": ResourceAllocations::NewDialogComponent::DIALOG_ID },
|
||||
data: { "close-dialog-id": @dialog_id },
|
||||
mr: 1
|
||||
)
|
||||
) { I18n.t(:button_cancel) }
|
||||
@@ -54,7 +64,7 @@ module ResourceAllocations
|
||||
form: ResourceAllocations::NewDialogComponent::FORM_ID,
|
||||
type: :submit
|
||||
)
|
||||
) { I18n.t("resource_management.allocate_resource_dialog.submit") }
|
||||
) { @submit_label }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+2
-2
@@ -3,8 +3,8 @@
|
||||
primer_form_with(
|
||||
model: @allocation,
|
||||
scope: :resource_allocation,
|
||||
url: project_resource_allocations_path(@project),
|
||||
method: :post,
|
||||
url: form_url,
|
||||
method: form_method,
|
||||
html: {
|
||||
data: {
|
||||
turbo_stream: true,
|
||||
|
||||
+20
-3
@@ -35,11 +35,16 @@ module ResourceAllocations
|
||||
include OpTurbo::Streamable
|
||||
include OpPrimer::ComponentHelpers
|
||||
|
||||
def initialize(allocation:, project:, allocation_kind:)
|
||||
# `dialog_id` names the dialog hosting the form (autocompleter dropdowns
|
||||
# attach to it): the create wizard's by default, the edit dialog's when
|
||||
# editing a persisted allocation.
|
||||
def initialize(allocation:, project:, allocation_kind:,
|
||||
dialog_id: ResourceAllocations::NewDialogComponent::DIALOG_ID)
|
||||
super
|
||||
@allocation = allocation
|
||||
@project = project
|
||||
@allocation_kind = allocation_kind
|
||||
@dialog_id = dialog_id
|
||||
end
|
||||
|
||||
def wrapper_key
|
||||
@@ -48,12 +53,24 @@ module ResourceAllocations
|
||||
|
||||
private
|
||||
|
||||
attr_reader :dialog_id
|
||||
|
||||
def filter_based?
|
||||
@allocation_kind.to_s == "filter"
|
||||
end
|
||||
|
||||
def dialog_id
|
||||
ResourceAllocations::NewDialogComponent::DIALOG_ID
|
||||
# A persisted allocation submits an update to itself; a new one goes
|
||||
# through the create flow (with its confirmation step).
|
||||
def form_url
|
||||
if @allocation.persisted?
|
||||
project_resource_allocation_path(@project, @allocation)
|
||||
else
|
||||
project_resource_allocations_path(@project)
|
||||
end
|
||||
end
|
||||
|
||||
def form_method
|
||||
@allocation.persisted? ? :patch : :post
|
||||
end
|
||||
|
||||
def form_list_component(form)
|
||||
|
||||
+62
@@ -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.
|
||||
|
||||
++#%>
|
||||
|
||||
<%=
|
||||
render(
|
||||
Primer::Alpha::Dialog.new(
|
||||
id: DIALOG_ID,
|
||||
title:,
|
||||
size: :large,
|
||||
classes: "Overlay--size-large-portrait",
|
||||
data: { "keep-open-on-submit": true }
|
||||
)
|
||||
) do |dialog|
|
||||
dialog.with_header(variant: :large)
|
||||
|
||||
dialog.with_body(classes: "Overlay-body_autocomplete_height") do
|
||||
render(
|
||||
ResourceAllocations::AllocationStep::FormComponent.new(
|
||||
allocation:,
|
||||
project:,
|
||||
allocation_kind:,
|
||||
dialog_id: DIALOG_ID
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
dialog.with_footer do
|
||||
render(
|
||||
ResourceAllocations::AllocationStep::FooterComponent.new(
|
||||
dialog_id: DIALOG_ID,
|
||||
submit_label: I18n.t("resource_management.edit_allocation_dialog.submit")
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
%>
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
# 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 ResourceAllocations
|
||||
# A single-step dialog to edit a persisted allocation, reusing the create
|
||||
# wizard's allocation form (which submits an update for persisted records).
|
||||
class EditDialogComponent < ApplicationComponent
|
||||
include OpTurbo::Streamable
|
||||
include OpPrimer::ComponentHelpers
|
||||
|
||||
DIALOG_ID = "edit-resource-allocation-dialog"
|
||||
|
||||
def initialize(project:, allocation:)
|
||||
super
|
||||
|
||||
@project = project
|
||||
@allocation = allocation
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :project, :allocation
|
||||
|
||||
def allocation_kind
|
||||
allocation.principal_explicit? ? "principal" : "filter"
|
||||
end
|
||||
|
||||
def title
|
||||
I18n.t("resource_management.edit_allocation_dialog.title")
|
||||
end
|
||||
end
|
||||
end
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
<%#-- 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.
|
||||
|
||||
++#%>
|
||||
|
||||
<%= component_wrapper do %>
|
||||
<%= flex_layout do |body| %>
|
||||
<%= body.with_row(mb: 3) do %>
|
||||
<%= render(ResourceAllocations::ProgressComponent.new(work_package:, allocations:)) %>
|
||||
<% end %>
|
||||
<% if allocations.any? %>
|
||||
<%= body.with_row do %>
|
||||
<%= render(Primer::Beta::BorderBox.new) do |box| %>
|
||||
<% allocations.each do |allocation| %>
|
||||
<% box.with_row do %>
|
||||
<%= render ResourceAllocations::ListItemComponent.new(
|
||||
allocation:,
|
||||
project:,
|
||||
visible: visible_principal?(allocation),
|
||||
overbooked: overbooked?(allocation),
|
||||
editable: editable?
|
||||
) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -0,0 +1,68 @@
|
||||
# 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 ResourceAllocations
|
||||
# The body of the work package allocations dialog: the allocation progress
|
||||
# summary and one row per allocation. Streamable so the dialog content can be
|
||||
# refreshed after an allocation changes. Allocations whose principal is not
|
||||
# visible to the current user are still listed, but anonymised.
|
||||
class ListComponent < ApplicationComponent
|
||||
include OpTurbo::Streamable
|
||||
include OpPrimer::ComponentHelpers
|
||||
|
||||
def initialize(project:, work_package:, allocations:, visible_principal_ids:, overbooked_ids: Set.new)
|
||||
super
|
||||
|
||||
@project = project
|
||||
@work_package = work_package
|
||||
@allocations = allocations
|
||||
@visible_principal_ids = visible_principal_ids
|
||||
@overbooked_ids = overbooked_ids
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :project, :work_package, :allocations, :visible_principal_ids, :overbooked_ids
|
||||
|
||||
def visible_principal?(allocation)
|
||||
allocation.principal_id.nil? || visible_principal_ids.include?(allocation.principal_id)
|
||||
end
|
||||
|
||||
def overbooked?(allocation)
|
||||
overbooked_ids.include?(allocation.id)
|
||||
end
|
||||
|
||||
def editable?
|
||||
return @editable if defined?(@editable)
|
||||
|
||||
@editable = User.current.allowed_in_project?(:allocate_user_resources, project)
|
||||
end
|
||||
end
|
||||
end
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
<%#-- 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.
|
||||
|
||||
++#%>
|
||||
|
||||
<%=
|
||||
render(
|
||||
Primer::Alpha::Dialog.new(
|
||||
id: DIALOG_ID,
|
||||
title:,
|
||||
size: :large,
|
||||
classes: "Overlay--size-large-portrait",
|
||||
data: { "keep-open-on-submit": true }
|
||||
)
|
||||
) do |dialog|
|
||||
dialog.with_header(variant: :large)
|
||||
|
||||
dialog.with_body do
|
||||
render(ResourceAllocations::ListComponent.new(project:, work_package:, allocations:, visible_principal_ids:, overbooked_ids:))
|
||||
end
|
||||
|
||||
dialog.with_footer do
|
||||
# `mr: :auto` pushes the allocate action to the left of the footer's
|
||||
# flex row, leaving the close button right-aligned.
|
||||
concat(
|
||||
render(
|
||||
Primer::Beta::Button.new(
|
||||
scheme: :link,
|
||||
tag: :a,
|
||||
href: allocate_resource_path,
|
||||
mr: :auto,
|
||||
data: { controller: "async-dialog" }
|
||||
)
|
||||
) do |button|
|
||||
button.with_leading_visual_icon(icon: :"person-add")
|
||||
t("resource_management.work_package_allocations_dialog.allocate_resource")
|
||||
end
|
||||
)
|
||||
concat(
|
||||
render(Primer::Beta::Button.new(data: { "close-dialog-id": DIALOG_ID })) do
|
||||
t(:button_close)
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
%>
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
# 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 ResourceAllocations
|
||||
# The dialog shell around a work package's allocation list, with a footer to
|
||||
# allocate another resource.
|
||||
class ListDialogComponent < ApplicationComponent
|
||||
include OpTurbo::Streamable
|
||||
include OpPrimer::ComponentHelpers
|
||||
|
||||
DIALOG_ID = "work-package-allocations-dialog"
|
||||
|
||||
def initialize(project:, work_package:, allocations:, visible_principal_ids:, overbooked_ids: Set.new)
|
||||
super
|
||||
|
||||
@project = project
|
||||
@work_package = work_package
|
||||
@allocations = allocations
|
||||
@visible_principal_ids = visible_principal_ids
|
||||
@overbooked_ids = overbooked_ids
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :project, :work_package, :allocations, :visible_principal_ids, :overbooked_ids
|
||||
|
||||
def title
|
||||
I18n.t("resource_management.work_package_allocations_dialog.title")
|
||||
end
|
||||
|
||||
def allocate_resource_path
|
||||
new_project_resource_allocation_path(project, work_package_id: work_package.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
<%#-- 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.
|
||||
|
||||
++#%>
|
||||
|
||||
<%= flex_layout(align_items: :center) do |row| %>
|
||||
<%= row.with_column(mr: 2) do %>
|
||||
<%= render(leading_visual) %>
|
||||
<% end %>
|
||||
<%= row.with_column(flex: 1) do %>
|
||||
<%= render(Primer::Beta::Text.new(font_size: :small)) { name } %>
|
||||
<% end %>
|
||||
<%= row.with_column do %>
|
||||
<%= render(Primer::Beta::Label.new(size: :large)) { duration } %>
|
||||
<% end %>
|
||||
<% if overbooked? %>
|
||||
<%= row.with_column(ml: 2, classes: "d-flex") do %>
|
||||
<%= render Primer::Beta::Octicon.new(
|
||||
icon: :"alert-fill",
|
||||
color: :danger,
|
||||
id: overbooked_icon_id,
|
||||
"aria-label": overbooked_message
|
||||
) %>
|
||||
<%= render Primer::Alpha::Tooltip.new(
|
||||
for_id: overbooked_icon_id,
|
||||
type: :description,
|
||||
text: overbooked_message,
|
||||
direction: :s
|
||||
) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if menu? %>
|
||||
<%= row.with_column(ml: 2) do %>
|
||||
<%= context_menu %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
# 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 ResourceAllocations
|
||||
# A single allocation row in the work package allocations dialog: the member's
|
||||
# avatar and name (or an anonymous placeholder when the principal is not
|
||||
# visible to the current user) and the allocated hours.
|
||||
class ListItemComponent < ApplicationComponent
|
||||
include OpPrimer::ComponentHelpers
|
||||
include AvatarHelper
|
||||
|
||||
AVATAR_SIZE = 24
|
||||
|
||||
# `editable` enables the row's edit/delete menu (the caller checks the
|
||||
# permission). It stays hidden for an anonymised row regardless, since the
|
||||
# edit form would reveal the hidden user.
|
||||
def initialize(allocation:, project:, visible:, overbooked: false, editable: false)
|
||||
super
|
||||
|
||||
@allocation = allocation
|
||||
@project = project
|
||||
@visible = visible
|
||||
@overbooked = overbooked
|
||||
@editable = editable
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :allocation, :project
|
||||
|
||||
def visible?
|
||||
@visible
|
||||
end
|
||||
|
||||
def overbooked?
|
||||
@overbooked
|
||||
end
|
||||
|
||||
def menu?
|
||||
@editable && visible?
|
||||
end
|
||||
|
||||
def context_menu
|
||||
render(Primer::Alpha::ActionMenu.new(size: :small, anchor_align: :end)) do |menu|
|
||||
menu.with_show_button(icon: "kebab-horizontal",
|
||||
"aria-label": t("resource_management.work_package_allocations_dialog.context_menu_label"),
|
||||
scheme: :invisible)
|
||||
|
||||
edit_item(menu)
|
||||
delete_item(menu)
|
||||
end
|
||||
end
|
||||
|
||||
def edit_item(menu)
|
||||
menu.with_item(
|
||||
label: I18n.t(:button_edit),
|
||||
tag: :a,
|
||||
href: helpers.edit_project_resource_allocation_path(project, allocation),
|
||||
content_arguments: { data: { controller: "async-dialog" } }
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: :pencil)
|
||||
end
|
||||
end
|
||||
|
||||
def delete_item(menu)
|
||||
menu.with_item(
|
||||
label: I18n.t(:button_delete),
|
||||
scheme: :danger,
|
||||
href: helpers.project_resource_allocation_path(project, allocation),
|
||||
form_arguments: {
|
||||
method: :delete,
|
||||
data: {
|
||||
turbo_confirm: t("resource_management.work_package_allocations_dialog.delete_confirmation"),
|
||||
turbo_stream: true
|
||||
}
|
||||
}
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: :trash)
|
||||
end
|
||||
end
|
||||
|
||||
def name
|
||||
if allocation.principal
|
||||
visible? ? allocation.principal.name : hidden_label
|
||||
else
|
||||
allocation.filter_name.presence || unassigned_label
|
||||
end
|
||||
end
|
||||
|
||||
# An allocation without a user yet (filter placeholder or lost principal)
|
||||
# shows a person-add icon instead of an avatar. Sized to match the avatar so
|
||||
# the leading column keeps the same width and the row stays aligned.
|
||||
def leading_visual
|
||||
if allocation.principal
|
||||
Primer::OpenProject::AvatarWithFallback.new(size: AVATAR_SIZE, **avatar_options)
|
||||
else
|
||||
Primer::Beta::Octicon.new(icon: :"person-add", size: :medium, color: :muted, "aria-hidden": true)
|
||||
end
|
||||
end
|
||||
|
||||
# Only a visible principal exposes a real avatar (and an identity-revealing
|
||||
# initials/colour seed). A hidden one falls back to a generated avatar keyed
|
||||
# to the allocation, so the user cannot be correlated.
|
||||
def avatar_options
|
||||
if visible?
|
||||
{
|
||||
src: avatar_url(allocation.principal),
|
||||
alt: allocation.principal.name,
|
||||
unique_id: allocation.principal.id
|
||||
}
|
||||
else
|
||||
{
|
||||
alt: name,
|
||||
unique_id: "resource-allocation-#{allocation.id}"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# Formatted per the instance's duration format setting, e.g. "2d 4h".
|
||||
def duration
|
||||
DurationConverter.output(allocation.allocated_hours)
|
||||
end
|
||||
|
||||
def overbooked_icon_id
|
||||
"resource-allocation-overbooked-#{allocation.id}"
|
||||
end
|
||||
|
||||
def overbooked_message
|
||||
t("resource_management.work_package_allocations_dialog.overbooked")
|
||||
end
|
||||
|
||||
def hidden_label
|
||||
t("resource_management.work_package_allocations_dialog.hidden_user")
|
||||
end
|
||||
|
||||
def unassigned_label
|
||||
t("resource_management.work_package_list.allocated_members.unassigned")
|
||||
end
|
||||
end
|
||||
end
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
<%#-- 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.
|
||||
|
||||
++#%>
|
||||
|
||||
<% if work_scheduled? %>
|
||||
<%= flex_layout do |column| %>
|
||||
<%= column.with_row(mb: 1) do %>
|
||||
<%= flex_layout(justify_content: :space_between, align_items: :center) do |line| %>
|
||||
<%= line.with_column do %>
|
||||
<%= render(Primer::Beta::Text.new(font_size: :small)) { summary } %>
|
||||
<% end %>
|
||||
<%= line.with_column do %>
|
||||
<%= render(Primer::Beta::Text.new(font_size: :small, color: :muted)) { percentage_label } %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= column.with_row do %>
|
||||
<%= render(Primer::Beta::ProgressBar.new) do |bar| %>
|
||||
<% bar.with_item(percentage: bar_percentage, bg: bar_color, aria: { label: percentage_label }) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= render Primer::Beta::Octicon.new(
|
||||
icon: :"alert-fill",
|
||||
color: :danger,
|
||||
id: no_work_tooltip_id,
|
||||
"aria-label": no_work_message
|
||||
) %>
|
||||
<%= render Primer::Alpha::Tooltip.new(
|
||||
for_id: no_work_tooltip_id,
|
||||
type: :description,
|
||||
text: no_work_message,
|
||||
direction: :e
|
||||
) %>
|
||||
<% end %>
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
# 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 ResourceAllocations
|
||||
# Renders how much of a work package's scheduled work is covered by resource
|
||||
# allocations: an "12h / 35h" label (allocated / scheduled), a percentage,
|
||||
# and a colored bar. Shared by the work package list column and the
|
||||
# allocations dialog.
|
||||
class ProgressComponent < ApplicationComponent
|
||||
include OpPrimer::ComponentHelpers
|
||||
|
||||
def initialize(work_package:, allocations:)
|
||||
super
|
||||
|
||||
@work_package = work_package
|
||||
@allocations = allocations
|
||||
end
|
||||
|
||||
# Without scheduled work there is nothing to allocate against (and no
|
||||
# sensible denominator), so fall back to the muted placeholder.
|
||||
def work_scheduled?
|
||||
scheduled_hours.positive?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :work_package, :allocations
|
||||
|
||||
def allocated_hours
|
||||
allocations.sum { |allocation| allocation.allocated_time.to_i } / 60.0
|
||||
end
|
||||
|
||||
# Total work: the rolled-up value for a parent, falling back to the work
|
||||
# package's own estimate for a leaf (where `derived_estimated_hours` is nil).
|
||||
def scheduled_hours
|
||||
(work_package.derived_estimated_hours || work_package.estimated_hours).to_f
|
||||
end
|
||||
|
||||
# Share of the scheduled work covered by allocations. Capped at 100 for the
|
||||
# bar width; the raw value still drives the label and over-allocation color.
|
||||
def ratio
|
||||
return 0 unless work_scheduled?
|
||||
|
||||
((allocated_hours / scheduled_hours) * 100).round
|
||||
end
|
||||
|
||||
def bar_percentage
|
||||
ratio.clamp(0, 100)
|
||||
end
|
||||
|
||||
def bar_color
|
||||
if ratio > 100
|
||||
:danger_emphasis
|
||||
elsif ratio == 100
|
||||
:success_emphasis
|
||||
else
|
||||
:accent_emphasis
|
||||
end
|
||||
end
|
||||
|
||||
def summary
|
||||
t("resource_management.allocation.summary",
|
||||
allocated: hours_label(allocated_hours),
|
||||
scheduled: hours_label(scheduled_hours))
|
||||
end
|
||||
|
||||
def percentage_label
|
||||
helpers.number_to_percentage(ratio, precision: 0)
|
||||
end
|
||||
|
||||
def hours_label(hours)
|
||||
t("resource_management.allocation.hours",
|
||||
value: helpers.number_with_precision(hours, precision: 1, strip_insignificant_zeros: true))
|
||||
end
|
||||
|
||||
def no_work_message
|
||||
t("resource_management.allocation.no_work")
|
||||
end
|
||||
|
||||
def no_work_tooltip_id
|
||||
"allocation-no-work-#{work_package.id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
+9
-1
@@ -34,6 +34,14 @@ module ResourceAllocations
|
||||
include OpTurbo::Streamable
|
||||
include OpPrimer::ComponentHelpers
|
||||
|
||||
# `dialog_id` names the dialog the cancel button closes: the create
|
||||
# wizard's by default, the edit dialog's when confirming an update.
|
||||
def initialize(dialog_id: ResourceAllocations::NewDialogComponent::DIALOG_ID)
|
||||
super
|
||||
|
||||
@dialog_id = dialog_id
|
||||
end
|
||||
|
||||
def wrapper_key
|
||||
ResourceAllocations::NewDialogComponent::FOOTER_ID
|
||||
end
|
||||
@@ -58,7 +66,7 @@ module ResourceAllocations
|
||||
footer.with_column(mr: 1) do
|
||||
render(
|
||||
Primer::Beta::Button.new(
|
||||
data: { "close-dialog-id": ResourceAllocations::NewDialogComponent::DIALOG_ID }
|
||||
data: { "close-dialog-id": @dialog_id }
|
||||
)
|
||||
) { I18n.t(:button_cancel) }
|
||||
end
|
||||
|
||||
+2
-2
@@ -84,8 +84,8 @@
|
||||
|
||||
body.with_row(w: :full) do
|
||||
form_with(
|
||||
url: project_resource_allocations_path(@project),
|
||||
method: :post,
|
||||
url: form_url,
|
||||
method: form_method,
|
||||
id: ResourceAllocations::NewDialogComponent::FORM_ID,
|
||||
data: { turbo_stream: true }
|
||||
) do
|
||||
|
||||
+14
@@ -59,6 +59,20 @@ module ResourceAllocations
|
||||
|
||||
private
|
||||
|
||||
# A confirmed resubmit goes back to where the values came from: the
|
||||
# update of a persisted allocation or the create flow for a new one.
|
||||
def form_url
|
||||
if @allocation.persisted?
|
||||
project_resource_allocation_path(@project, @allocation)
|
||||
else
|
||||
project_resource_allocations_path(@project)
|
||||
end
|
||||
end
|
||||
|
||||
def form_method
|
||||
@allocation.persisted? ? :patch : :post
|
||||
end
|
||||
|
||||
def overbooking_heading
|
||||
t("resource_management.allocate_resource_dialog.overbooking.title")
|
||||
end
|
||||
|
||||
+8
-2
@@ -32,12 +32,15 @@ module ResourcePlannerViews
|
||||
class ContentComponent < ApplicationComponent
|
||||
include OpTurbo::Streamable
|
||||
|
||||
def initialize(view:, project:, resource_planner:)
|
||||
def initialize(view:, project:, resource_planner:, work_packages: [], allocations: {}, visible_principal_ids: nil)
|
||||
super
|
||||
|
||||
@view = view
|
||||
@project = project
|
||||
@resource_planner = resource_planner
|
||||
@work_packages = work_packages
|
||||
@allocations = allocations
|
||||
@visible_principal_ids = visible_principal_ids
|
||||
end
|
||||
|
||||
private
|
||||
@@ -48,7 +51,10 @@ module ResourcePlannerViews
|
||||
ResourcePlannerViews::WorkPackageList::ContentComponent.new(
|
||||
view: @view,
|
||||
project: @project,
|
||||
resource_planner: @resource_planner
|
||||
resource_planner: @resource_planner,
|
||||
work_packages: @work_packages,
|
||||
allocations: @allocations,
|
||||
visible_principal_ids: @visible_principal_ids
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
<%#-- 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.
|
||||
|
||||
++#%>
|
||||
|
||||
<% if named? %>
|
||||
<%= flex_layout(align_items: :center) do |container| %>
|
||||
<%= container.with_column(flex_shrink: 0) do %>
|
||||
<%= render(Primer::OpenProject::AvatarStack.new(tooltipped: true, body_arguments: { label: tooltip_label })) do |stack| %>
|
||||
<% avatar_options.each do |options| %>
|
||||
<% stack.with_avatar_with_fallback(**options) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= container.with_column(ml: 2, classes: "min-width-0") do %>
|
||||
<%= render(Primer::Beta::Truncate.new) { lead_name } %>
|
||||
<% end %>
|
||||
<% if additional? %>
|
||||
<%= container.with_column(ml: 1, flex_shrink: 0) do %>
|
||||
<%= render(Primer::Beta::Label.new(scheme: :secondary, inline: true)) { additional_label } %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= render(Primer::Beta::Text.new(color: :muted)) { anonymous_label } %>
|
||||
<% end %>
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
# 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 ResourcePlannerViews::WorkPackageList
|
||||
# Renders the members allocated to a work package as an avatar stack (with the
|
||||
# stack's built-in "+N" overflow). An allocation with an assigned principal
|
||||
# shows that user's avatar; a filter-based allocation with no principal yet
|
||||
# shows a generated avatar derived from its filter name.
|
||||
class AllocatedMembersComponent < ApplicationComponent
|
||||
include OpPrimer::ComponentHelpers
|
||||
include AvatarHelper
|
||||
|
||||
AVATAR_SIZE = 20
|
||||
|
||||
# `visible_principal_ids` are the principals the current user may see. Members
|
||||
# whose principal is not in that set are still counted, but never named or
|
||||
# given a (potentially identity-revealing) avatar. A nil set means no
|
||||
# restriction.
|
||||
def initialize(allocations:, visible_principal_ids: nil)
|
||||
super
|
||||
|
||||
@allocations = allocations
|
||||
@visible_principal_ids = visible_principal_ids
|
||||
end
|
||||
|
||||
def render?
|
||||
allocations.any?
|
||||
end
|
||||
|
||||
# Whether at least one member can be named — a visible user or a filter
|
||||
# placeholder. When false, every member is hidden and only a count is shown.
|
||||
def named?
|
||||
identifiable.any?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :allocations, :visible_principal_ids
|
||||
|
||||
# Members we may reveal: filter placeholders (no user) and allocations whose
|
||||
# principal is visible to the current user.
|
||||
def identifiable
|
||||
@identifiable ||= allocations.select do |allocation|
|
||||
allocation.principal_id.nil? ||
|
||||
visible_principal_ids.nil? ||
|
||||
visible_principal_ids.include?(allocation.principal_id)
|
||||
end
|
||||
end
|
||||
|
||||
def avatar_options
|
||||
identifiable.map { |allocation| avatar_options_for(allocation) }
|
||||
end
|
||||
|
||||
# The name shown beside the stack. The stack's own overflow indicator is not
|
||||
# numeric, so the count of the remaining members is spelled out separately.
|
||||
def lead_name
|
||||
member_name(identifiable.first)
|
||||
end
|
||||
|
||||
# Members beyond the named lead, including those hidden from this user, so
|
||||
# hidden allocations are surfaced as a count rather than silently dropped.
|
||||
def additional_count
|
||||
allocations.size - 1
|
||||
end
|
||||
|
||||
def additional?
|
||||
additional_count.positive?
|
||||
end
|
||||
|
||||
def additional_label
|
||||
t("resource_management.work_package_list.allocated_members.additional", count: additional_count)
|
||||
end
|
||||
|
||||
# Shown when none of the members may be named: a bare count of the members.
|
||||
def anonymous_label
|
||||
t("resource_management.work_package_list.allocated_members.other_users", count: allocations.size)
|
||||
end
|
||||
|
||||
# A real principal resolves to their avatar (falling back to generated
|
||||
# initials when they have no image); otherwise the fallback generates a
|
||||
# deterministic avatar from the member name.
|
||||
def avatar_options_for(allocation)
|
||||
user = allocation.principal
|
||||
|
||||
{
|
||||
src: (avatar_url(user) if user),
|
||||
alt: member_name(allocation),
|
||||
unique_id: user&.id || "resource-allocation-#{allocation.id}",
|
||||
size: AVATAR_SIZE
|
||||
}
|
||||
end
|
||||
|
||||
# Shown in the stack's hover tooltip, since the names are not rendered inline.
|
||||
# Only names members we may reveal.
|
||||
def tooltip_label
|
||||
identifiable.map { |allocation| member_name(allocation) }.join(", ")
|
||||
end
|
||||
|
||||
# The assigned user's name, the filter name for an unassigned filter
|
||||
# allocation, or a generic label for an allocation that lost its principal
|
||||
# (e.g. the assigned user was deleted) — so the avatar always has a label.
|
||||
def member_name(allocation)
|
||||
allocation.principal&.name.presence || allocation.filter_name.presence || unassigned_label
|
||||
end
|
||||
|
||||
def unassigned_label
|
||||
t("resource_management.work_package_list.allocated_members.unassigned")
|
||||
end
|
||||
end
|
||||
end
|
||||
+3
-1
@@ -39,7 +39,9 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
rows: work_packages,
|
||||
view: @view,
|
||||
project: @project,
|
||||
resource_planner: @resource_planner
|
||||
resource_planner: @resource_planner,
|
||||
allocations: @allocations,
|
||||
visible_principal_ids: @visible_principal_ids
|
||||
)
|
||||
%>
|
||||
<% if @view.manually_picked? %>
|
||||
|
||||
+5
-4
@@ -30,18 +30,19 @@
|
||||
|
||||
module ResourcePlannerViews::WorkPackageList
|
||||
class ContentComponent < ApplicationComponent
|
||||
def initialize(view:, project:, resource_planner:)
|
||||
def initialize(view:, project:, resource_planner:, work_packages: [], allocations: {}, visible_principal_ids: nil)
|
||||
super
|
||||
|
||||
@view = view
|
||||
@project = project
|
||||
@resource_planner = resource_planner
|
||||
@work_packages = work_packages
|
||||
@allocations = allocations
|
||||
@visible_principal_ids = visible_principal_ids
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def work_packages
|
||||
@view.effective_query&.results&.work_packages || WorkPackage.none
|
||||
end
|
||||
attr_reader :work_packages, :allocations, :visible_principal_ids
|
||||
end
|
||||
end
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<%#-- 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.
|
||||
|
||||
++#%>
|
||||
|
||||
<%=
|
||||
render(Primer::Alpha::Dialog.new(id: DIALOG_ID, title:, size: :medium_portrait)) do |dialog|
|
||||
dialog.with_header(variant: :large)
|
||||
|
||||
dialog.with_body do
|
||||
helpers.turbo_frame_tag("work_package_progress_modal") do
|
||||
render(modal_component)
|
||||
end
|
||||
end
|
||||
end
|
||||
%>
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
# 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 ResourcePlannerViews::WorkPackageList
|
||||
# Hosts the core progress modal inside a Primer dialog so a work package's
|
||||
# work / remaining work / % complete can be edited from the list. The modal
|
||||
# body keeps the `work_package_progress_modal` turbo frame the live preview
|
||||
# navigates, and carries its own submit button.
|
||||
class EditTotalWorkDialogComponent < ApplicationComponent
|
||||
include OpTurbo::Streamable
|
||||
|
||||
DIALOG_ID = "edit-total-work-dialog"
|
||||
|
||||
def initialize(modal_component:)
|
||||
super
|
||||
|
||||
@modal_component = modal_component
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :modal_component
|
||||
|
||||
def title
|
||||
I18n.t("resource_management.work_package_list.context_menu.edit_total_work")
|
||||
end
|
||||
end
|
||||
end
|
||||
+27
-7
@@ -99,11 +99,13 @@ module ResourcePlannerViews::WorkPackageList
|
||||
end
|
||||
|
||||
def allocation
|
||||
render(Primer::Beta::Text.new(color: :muted)) { allocation_placeholder }
|
||||
render(ResourceAllocations::ProgressComponent.new(work_package:, allocations:))
|
||||
end
|
||||
|
||||
def allocated_members
|
||||
render(Primer::Beta::Text.new(color: :muted)) { allocation_placeholder }
|
||||
return render(Primer::Beta::Text.new(color: :muted)) { allocation_placeholder } if allocations.empty?
|
||||
|
||||
render(AllocatedMembersComponent.new(allocations:, visible_principal_ids: table.visible_principal_ids))
|
||||
end
|
||||
|
||||
def button_links
|
||||
@@ -112,6 +114,10 @@ module ResourcePlannerViews::WorkPackageList
|
||||
|
||||
private
|
||||
|
||||
def allocations
|
||||
@allocations ||= table.allocations_for(work_package)
|
||||
end
|
||||
|
||||
def allocation_placeholder
|
||||
I18n.t("resource_management.work_package_list.allocation_placeholder")
|
||||
end
|
||||
@@ -123,7 +129,7 @@ module ResourcePlannerViews::WorkPackageList
|
||||
scheme: :invisible)
|
||||
|
||||
see_allocation_item(menu)
|
||||
edit_total_work_item(menu)
|
||||
edit_total_work_item(menu) if allowed_to_edit_work?
|
||||
add_user_group_item(menu)
|
||||
|
||||
if manual?
|
||||
@@ -136,15 +142,25 @@ module ResourcePlannerViews::WorkPackageList
|
||||
end
|
||||
|
||||
def see_allocation_item(menu)
|
||||
menu.with_item(label: t("resource_management.work_package_list.context_menu.see_allocation"),
|
||||
disabled: true) do |item|
|
||||
menu.with_item(
|
||||
label: t("resource_management.work_package_list.context_menu.see_allocation"),
|
||||
tag: :a,
|
||||
href: helpers.project_work_package_resource_allocations_path(table.project, work_package),
|
||||
content_arguments: { data: { controller: "async-dialog" } }
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: :hourglass)
|
||||
end
|
||||
end
|
||||
|
||||
def edit_total_work_item(menu)
|
||||
menu.with_item(label: t("resource_management.work_package_list.context_menu.edit_total_work"),
|
||||
disabled: true) do |item|
|
||||
menu.with_item(
|
||||
label: t("resource_management.work_package_list.context_menu.edit_total_work"),
|
||||
tag: :a,
|
||||
href: helpers.edit_project_resource_planner_view_work_package_progress_path(
|
||||
table.project, table.resource_planner, table.view, work_package
|
||||
),
|
||||
content_arguments: { data: { controller: "async-dialog" } }
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: :pencil)
|
||||
end
|
||||
end
|
||||
@@ -211,6 +227,10 @@ module ResourcePlannerViews::WorkPackageList
|
||||
table.manual?
|
||||
end
|
||||
|
||||
def allowed_to_edit_work?
|
||||
User.current.allowed_in_project?(:edit_work_packages, work_package.project)
|
||||
end
|
||||
|
||||
def position_index
|
||||
@position_index ||= table.rows.index { |wp| wp.id == work_package.id }
|
||||
end
|
||||
|
||||
+14
-2
@@ -34,18 +34,26 @@ module ResourcePlannerViews::WorkPackageList
|
||||
|
||||
mobile_columns :subject, :priority
|
||||
|
||||
attr_reader :view, :project, :resource_planner
|
||||
attr_reader :view, :project, :resource_planner, :visible_principal_ids
|
||||
|
||||
def initialize(view:, project:, resource_planner:, **)
|
||||
def initialize(view:, project:, resource_planner:, allocations: {}, visible_principal_ids: nil, **)
|
||||
super(**)
|
||||
|
||||
@view = view
|
||||
@project = project
|
||||
@resource_planner = resource_planner
|
||||
@allocations = allocations
|
||||
@visible_principal_ids = visible_principal_ids
|
||||
end
|
||||
|
||||
def manual? = view.manually_picked?
|
||||
|
||||
# The allocations of a work package, taken from the page-wide map the
|
||||
# controller loaded; shared by the allocation progress and members columns.
|
||||
def allocations_for(work_package)
|
||||
@allocations[work_package.id] || []
|
||||
end
|
||||
|
||||
main_column :subject
|
||||
|
||||
def sortable? = false
|
||||
@@ -54,6 +62,10 @@ module ResourcePlannerViews::WorkPackageList
|
||||
|
||||
def has_actions? = true
|
||||
|
||||
# Scopes this table's styling (see table_component.sass) without touching the
|
||||
# shared border-box grid defaults used by every other table.
|
||||
def container_class = "op-resource-work-package-list"
|
||||
|
||||
def mobile_title
|
||||
I18n.t("resource_management.work_package_list.mobile_title")
|
||||
end
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
@import "helpers"
|
||||
|
||||
// The shared border-box grid top-aligns row content and distributes columns
|
||||
// evenly. The resource planner work package list has a fixed, known set of
|
||||
// columns, so we center the content and hardcode the column widths from the
|
||||
// design. Scoped via the table's container_class so no other table is affected.
|
||||
@media screen and (min-width: $breakpoint-md)
|
||||
.op-resource-work-package-list
|
||||
.op-border-box-grid
|
||||
align-items: center
|
||||
// Column widths as fractions of the 1200px design (376/80/200/200/200),
|
||||
// so they scale with the available width. Only the action button is fixed.
|
||||
// The header and body are independent grids; sharing one template keeps
|
||||
// their tracks aligned. minmax(0, …) lets a track shrink below its content.
|
||||
grid-template-columns: minmax(0, 376fr) minmax(0, 80fr) minmax(0, 200fr) minmax(0, 200fr) minmax(0, 200fr) 40px
|
||||
|
||||
// Subject keeps its main-column semantics (row header role, no truncation)
|
||||
// but must occupy a single track in the explicit template above rather than
|
||||
// spanning two as the shared grid would have it.
|
||||
.op-border-box-grid__header--main-column,
|
||||
.op-border-box-grid__row-item--main-column
|
||||
grid-column: auto
|
||||
|
||||
// Content is centered here, so the top-alignment nudge the shared component
|
||||
// applies to the action button is not wanted.
|
||||
.op-border-box-grid__row-action
|
||||
margin-top: 0
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
# 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 ResourceManagement
|
||||
# Builds the content component for a resource planner view. Loads the view's
|
||||
# work packages and their allocations in one place so the allocation columns
|
||||
# (progress bar and members) share a single query rather than each issuing
|
||||
# their own. Requires @project and @resource_planner to be set.
|
||||
module PlannerViewContent
|
||||
def work_package_list_content(view)
|
||||
work_packages = view.is_a?(ResourceWorkPackageList) ? view.work_packages.to_a : []
|
||||
allocations = ResourceAllocation.allocated_for_work_packages(work_packages)
|
||||
|
||||
ResourcePlannerViews::ContentComponent.new(
|
||||
view:,
|
||||
project: @project,
|
||||
resource_planner: @resource_planner,
|
||||
work_packages:,
|
||||
allocations:,
|
||||
visible_principal_ids: ResourceAllocation.visible_principal_ids(allocations.values.flatten, current_user)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
+142
-13
@@ -35,6 +35,7 @@ module ::ResourceManagement
|
||||
|
||||
before_action :find_project_by_project_id
|
||||
before_action :authorize
|
||||
before_action :find_resource_allocation, only: %i[edit update destroy]
|
||||
|
||||
def new
|
||||
respond_with_dialog ResourceAllocations::NewDialogComponent.new(
|
||||
@@ -54,30 +55,61 @@ module ::ResourceManagement
|
||||
# date picker. Uses the EmptyContract so in-progress input never surfaces
|
||||
# validation errors while the user types.
|
||||
def refresh_form
|
||||
allocation = set_attributes(create_params, contract_class: EmptyContract).result
|
||||
allocation = set_attributes(allocation_params, contract_class: EmptyContract).result
|
||||
replace_via_turbo_stream(
|
||||
component: ResourceAllocations::AllocationStep::ScheduleViolationBannerComponent.new(allocation:)
|
||||
)
|
||||
respond_with_turbo_streams
|
||||
end
|
||||
|
||||
def edit; end
|
||||
def edit
|
||||
respond_with_dialog ResourceAllocations::EditDialogComponent.new(
|
||||
project: @project,
|
||||
allocation: @resource_allocation
|
||||
)
|
||||
end
|
||||
|
||||
def create
|
||||
# The confirmation step's "Back" button resubmits the carried form values
|
||||
# so the editable step can be re-rendered pre-filled.
|
||||
return render_back_step if params[:back].present?
|
||||
|
||||
validation = set_attributes(create_params)
|
||||
validation = set_attributes(allocation_params)
|
||||
return render_allocation_step(validation.result, status: :unprocessable_entity) if validation.failure?
|
||||
return render_warning_step(validation.result) if needs_confirmation?(validation.result)
|
||||
|
||||
persist_allocation
|
||||
end
|
||||
|
||||
def update; end
|
||||
def update
|
||||
# The confirmation step's "Back" button resubmits the carried form values
|
||||
# so the editable step can be re-rendered pre-filled.
|
||||
return render_edit_form(set_update_attributes.result) if params[:back].present?
|
||||
|
||||
def destroy; end
|
||||
validation = set_update_attributes
|
||||
return render_edit_form(validation.result, status: :unprocessable_entity) if validation.failure?
|
||||
|
||||
if needs_confirmation?(validation.result)
|
||||
return render_warning_step(validation.result,
|
||||
dialog_id: ResourceAllocations::EditDialogComponent::DIALOG_ID)
|
||||
end
|
||||
|
||||
persist_update
|
||||
end
|
||||
|
||||
def destroy
|
||||
entity = @resource_allocation.entity
|
||||
call = ResourceAllocations::DeleteService
|
||||
.new(user: current_user, model: @resource_allocation)
|
||||
.call
|
||||
|
||||
if call.success?
|
||||
render_destroy_success(entity)
|
||||
else
|
||||
render_error_flash_message_via_turbo_stream(message: call.errors.full_messages.to_sentence)
|
||||
respond_with_turbo_streams
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -94,7 +126,7 @@ module ::ResourceManagement
|
||||
respond_with_turbo_streams(status:)
|
||||
end
|
||||
|
||||
def render_warning_step(allocation)
|
||||
def render_warning_step(allocation, dialog_id: ResourceAllocations::NewDialogComponent::DIALOG_ID)
|
||||
ranges = overbooked_ranges(allocation)
|
||||
|
||||
replace_via_turbo_stream(
|
||||
@@ -109,7 +141,7 @@ module ::ResourceManagement
|
||||
)
|
||||
)
|
||||
replace_via_turbo_stream(
|
||||
component: ResourceAllocations::WarningStep::FooterComponent.new
|
||||
component: ResourceAllocations::WarningStep::FooterComponent.new(dialog_id:)
|
||||
)
|
||||
respond_with_turbo_streams
|
||||
end
|
||||
@@ -117,17 +149,17 @@ module ::ResourceManagement
|
||||
def persist_allocation
|
||||
call = ResourceAllocations::CreateService
|
||||
.new(user: current_user, model: ResourceAllocation.new)
|
||||
.call(create_params)
|
||||
.call(allocation_params)
|
||||
|
||||
if call.success?
|
||||
render_create_success
|
||||
render_create_success(call.result)
|
||||
else
|
||||
render_allocation_step(call.result, status: :unprocessable_entity)
|
||||
end
|
||||
end
|
||||
|
||||
def render_back_step
|
||||
render_allocation_step(set_attributes(create_params).result)
|
||||
render_allocation_step(set_attributes(allocation_params).result)
|
||||
end
|
||||
|
||||
# A final confirmation step is shown only when the allocation would overbook
|
||||
@@ -149,11 +181,14 @@ module ::ResourceManagement
|
||||
def compute_overbooked_ranges(allocation)
|
||||
return [] unless overbooking_checkable?(allocation)
|
||||
|
||||
# `exclude_id` drops the persisted version of an allocation being edited,
|
||||
# so its old booking does not count against the new one.
|
||||
availability(allocation).overbooking_with(
|
||||
start_date: allocation.start_date,
|
||||
end_date: allocation.end_date,
|
||||
minutes: allocation.allocated_time,
|
||||
work_package_id: allocation.entity_id
|
||||
work_package_id: allocation.entity_id,
|
||||
exclude_id: allocation.id
|
||||
)
|
||||
end
|
||||
|
||||
@@ -183,14 +218,99 @@ module ::ResourceManagement
|
||||
.call(attributes)
|
||||
end
|
||||
|
||||
def render_create_success
|
||||
def render_create_success(allocation)
|
||||
render_success_flash_message_via_turbo_stream(
|
||||
message: I18n.t("resource_management.allocate_resource_dialog.success_message")
|
||||
)
|
||||
close_dialog_via_turbo_stream("##{ResourceAllocations::NewDialogComponent::DIALOG_ID}")
|
||||
refresh_allocations_list(allocation.entity)
|
||||
notify_allocation_change(allocation.entity)
|
||||
respond_with_turbo_streams
|
||||
end
|
||||
|
||||
def set_update_attributes
|
||||
ResourceAllocations::SetAttributesService
|
||||
.new(user: current_user, model: @resource_allocation, contract_class: ResourceAllocations::UpdateContract)
|
||||
.call(allocation_params)
|
||||
end
|
||||
|
||||
def persist_update
|
||||
call = ResourceAllocations::UpdateService
|
||||
.new(user: current_user, model: @resource_allocation)
|
||||
.call(allocation_params)
|
||||
|
||||
if call.success?
|
||||
render_update_success(call.result)
|
||||
else
|
||||
render_edit_form(call.result, status: :unprocessable_entity)
|
||||
end
|
||||
end
|
||||
|
||||
def render_edit_form(allocation, status: :ok)
|
||||
replace_via_turbo_stream(
|
||||
component: ResourceAllocations::AllocationStep::FormComponent.new(
|
||||
allocation:,
|
||||
project: @project,
|
||||
allocation_kind:,
|
||||
dialog_id: ResourceAllocations::EditDialogComponent::DIALOG_ID
|
||||
),
|
||||
status:
|
||||
)
|
||||
replace_via_turbo_stream(
|
||||
component: ResourceAllocations::AllocationStep::FooterComponent.new(
|
||||
dialog_id: ResourceAllocations::EditDialogComponent::DIALOG_ID,
|
||||
submit_label: I18n.t("resource_management.edit_allocation_dialog.submit")
|
||||
)
|
||||
)
|
||||
respond_with_turbo_streams(status:)
|
||||
end
|
||||
|
||||
def render_update_success(allocation)
|
||||
render_success_flash_message_via_turbo_stream(
|
||||
message: I18n.t("resource_management.edit_allocation_dialog.success_message")
|
||||
)
|
||||
close_dialog_via_turbo_stream("##{ResourceAllocations::EditDialogComponent::DIALOG_ID}")
|
||||
refresh_allocations_list(allocation.entity)
|
||||
notify_allocation_change(allocation.entity)
|
||||
respond_with_turbo_streams
|
||||
end
|
||||
|
||||
def render_destroy_success(entity)
|
||||
render_success_flash_message_via_turbo_stream(
|
||||
message: I18n.t("resource_management.work_package_allocations_dialog.delete_success")
|
||||
)
|
||||
refresh_allocations_list(entity)
|
||||
notify_allocation_change(entity)
|
||||
respond_with_turbo_streams
|
||||
end
|
||||
|
||||
# Re-renders the allocation list of the work package's allocations dialog.
|
||||
# The stream is a no-op on the client when that dialog is not open.
|
||||
def refresh_allocations_list(work_package)
|
||||
return unless work_package.is_a?(WorkPackage)
|
||||
|
||||
allocations = ResourceAllocation.allocated_for_work_packages([work_package])[work_package.id] || []
|
||||
replace_via_turbo_stream(
|
||||
component: ResourceAllocations::ListComponent.new(
|
||||
project: @project,
|
||||
work_package:,
|
||||
allocations:,
|
||||
visible_principal_ids: ResourceAllocation.visible_principal_ids(allocations, current_user),
|
||||
overbooked_ids: ResourceAllocation.overbooked_ids(allocations)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
# Announces that an allocation of the work package changed. A resource
|
||||
# planner table open on the page reloads the affected work package in
|
||||
# response; the controller stays unaware of which view (if any) is on
|
||||
# screen. The stream is a harmless no-op when nothing listens.
|
||||
def notify_allocation_change(entity)
|
||||
return unless entity.is_a?(WorkPackage)
|
||||
|
||||
dispatch_event_via_turbo_stream("resource-allocations:changed", detail: { work_package_id: entity.id })
|
||||
end
|
||||
|
||||
def allocation_kind
|
||||
params[:allocation_kind].presence || "principal"
|
||||
end
|
||||
@@ -214,7 +334,16 @@ module ::ResourceManagement
|
||||
.to_h
|
||||
end
|
||||
|
||||
def create_params
|
||||
# Only allocations of work packages reachable by the current user within
|
||||
# the project may be touched; anything else 404s.
|
||||
def find_resource_allocation
|
||||
@resource_allocation = ResourceAllocation
|
||||
.where(entity_type: "WorkPackage",
|
||||
entity_id: WorkPackage.visible(current_user).where(project: @project))
|
||||
.find(params.expect(:id))
|
||||
end
|
||||
|
||||
def allocation_params
|
||||
permitted = params
|
||||
.expect(resource_allocation: %i[principal_id filter_name start_date end_date allocated_hours
|
||||
entity_type entity_id])
|
||||
|
||||
+6
-15
@@ -30,6 +30,7 @@
|
||||
module ::ResourceManagement
|
||||
class ResourcePlannerViewsController < BaseController
|
||||
include OpTurbo::ComponentStream
|
||||
include PlannerViewContent
|
||||
|
||||
menu_item :resource_management
|
||||
|
||||
@@ -46,7 +47,9 @@ module ::ResourceManagement
|
||||
only: %i[new_work_package add_work_package remove_work_package
|
||||
move_work_package reorder_work_package]
|
||||
|
||||
def show; end
|
||||
def show
|
||||
@content_component = work_package_list_content(@view)
|
||||
end
|
||||
|
||||
def new
|
||||
if params[:view_class_name].present?
|
||||
@@ -188,13 +191,7 @@ module ::ResourceManagement
|
||||
end
|
||||
|
||||
def replace_work_package_list
|
||||
replace_via_turbo_stream(
|
||||
component: ResourcePlannerViews::ContentComponent.new(
|
||||
view: @view,
|
||||
project: @project,
|
||||
resource_planner: @resource_planner
|
||||
)
|
||||
)
|
||||
replace_via_turbo_stream(component: work_package_list_content(@view))
|
||||
end
|
||||
|
||||
def render_configure_step(view, status: :ok)
|
||||
@@ -242,13 +239,7 @@ module ::ResourceManagement
|
||||
selected_view: view
|
||||
)
|
||||
)
|
||||
replace_via_turbo_stream(
|
||||
component: ResourcePlannerViews::ContentComponent.new(
|
||||
view:,
|
||||
project: @project,
|
||||
resource_planner: @resource_planner
|
||||
)
|
||||
)
|
||||
replace_via_turbo_stream(component: work_package_list_content(view))
|
||||
close_dialog_via_turbo_stream("##{ResourcePlannerViews::EditDialogComponent::DIALOG_ID}")
|
||||
respond_with_turbo_streams
|
||||
end
|
||||
|
||||
+2
@@ -30,6 +30,7 @@
|
||||
module ::ResourceManagement
|
||||
class ResourcePlannersController < BaseController
|
||||
include OpTurbo::ComponentStream
|
||||
include PlannerViewContent
|
||||
|
||||
menu_item :resource_management
|
||||
|
||||
@@ -47,6 +48,7 @@ module ::ResourceManagement
|
||||
|
||||
def show
|
||||
@view = default_view
|
||||
@content_component = work_package_list_content(@view)
|
||||
render "resource_management/resource_planner_views/show"
|
||||
end
|
||||
|
||||
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
# 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 ::ResourceManagement
|
||||
# Edits a work package's progress (work / remaining work / % complete) from a
|
||||
# resource planner work package list, reusing the core progress modal. The
|
||||
# modal form posts back here so a successful save can close the dialog and
|
||||
# refresh the list inline, instead of going through the Angular-driven core
|
||||
# `WorkPackages::ProgressController` flow.
|
||||
class WorkPackageProgressController < BaseController
|
||||
include OpTurbo::ComponentStream
|
||||
include FlashMessagesHelper
|
||||
include PlannerViewContent
|
||||
include WorkPackages::Progress::ModalParams
|
||||
|
||||
menu_item :resource_management
|
||||
|
||||
layout false
|
||||
|
||||
before_action :find_project_by_project_id
|
||||
before_action :find_resource_planner
|
||||
before_action :find_view
|
||||
before_action :find_work_package
|
||||
before_action :authorize_edit_work_package
|
||||
|
||||
# The `.visible` finders above enforce read access (view_resource_planners /
|
||||
# view_work_packages); `authorize_edit_work_package` gates the edit itself.
|
||||
authorization_checked! :edit, :update, :preview
|
||||
|
||||
def edit
|
||||
respond_with_dialog ResourcePlannerViews::WorkPackageList::EditTotalWorkDialogComponent.new(
|
||||
modal_component: progress_modal_component(submit_path: update_path)
|
||||
)
|
||||
end
|
||||
|
||||
def preview
|
||||
set_progress_attributes_to_work_package
|
||||
|
||||
render template: "work_packages/progress/modal",
|
||||
locals: { progress_modal_component: progress_modal_component(submit_path: update_path) }
|
||||
end
|
||||
|
||||
def update
|
||||
call = WorkPackages::UpdateService
|
||||
.new(user: current_user, model: @work_package)
|
||||
.call(work_package_progress_params)
|
||||
|
||||
call.success? ? render_update_success : render_update_failure(call)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def render_update_success
|
||||
render_success_flash_message_via_turbo_stream(message: I18n.t(:notice_successful_update))
|
||||
close_dialog_via_turbo_stream(
|
||||
"##{ResourcePlannerViews::WorkPackageList::EditTotalWorkDialogComponent::DIALOG_ID}"
|
||||
)
|
||||
replace_via_turbo_stream(component: work_package_list_content(@view))
|
||||
respond_with_turbo_streams
|
||||
end
|
||||
|
||||
def render_update_failure(call)
|
||||
extra_errors = extra_error_messages(call)
|
||||
render_error_flash_message_via_turbo_stream(message: extra_errors) if extra_errors.present?
|
||||
|
||||
# `@work_package` already carries the rejected attributes and errors from
|
||||
# the failed service call, so the modal re-renders with them in place.
|
||||
update_via_turbo_stream(
|
||||
component: progress_modal_component(submit_path: update_path),
|
||||
method: "morph"
|
||||
)
|
||||
respond_with_turbo_streams(status: :unprocessable_entity)
|
||||
end
|
||||
|
||||
def update_path
|
||||
project_resource_planner_view_work_package_progress_path(
|
||||
@project, @resource_planner, @view, @work_package
|
||||
)
|
||||
end
|
||||
|
||||
def find_resource_planner
|
||||
@resource_planner = ResourcePlanner
|
||||
.visible(current_user)
|
||||
.where(project: @project)
|
||||
.with_children
|
||||
.find(params.expect(:resource_planner_id))
|
||||
end
|
||||
|
||||
def find_view
|
||||
@view = @resource_planner.children.find(params.expect(:view_id))
|
||||
end
|
||||
|
||||
def find_work_package
|
||||
@work_package = WorkPackage
|
||||
.visible(current_user)
|
||||
.where(project: @project)
|
||||
.find(params.expect(:work_package_id))
|
||||
end
|
||||
|
||||
def authorize_edit_work_package
|
||||
deny_access unless User.current.allowed_in_project?(:edit_work_packages, @project)
|
||||
end
|
||||
end
|
||||
end
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
# 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 ::ResourceManagement
|
||||
# Lists the allocations of a single work package in a dialog. Visible to users
|
||||
# who may view the work package and hold the `view_resource_planners`
|
||||
# permission in its project.
|
||||
class WorkPackageResourceAllocationsController < BaseController
|
||||
include OpTurbo::ComponentStream
|
||||
|
||||
menu_item :resource_management
|
||||
|
||||
before_action :find_project_by_project_id
|
||||
before_action :find_work_package
|
||||
before_action :authorize
|
||||
|
||||
def index
|
||||
respond_with_dialog ResourceAllocations::ListDialogComponent.new(
|
||||
project: @project,
|
||||
work_package: @work_package,
|
||||
allocations:,
|
||||
visible_principal_ids: ResourceAllocation.visible_principal_ids(allocations, current_user),
|
||||
overbooked_ids: ResourceAllocation.overbooked_ids(allocations)
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def allocations
|
||||
@allocations ||=
|
||||
ResourceAllocation.allocated_for_work_packages([@work_package])[@work_package.id] || []
|
||||
end
|
||||
|
||||
# `WorkPackage.visible` enforces the view-work-package permission; a
|
||||
# non-visible (or out-of-project) id therefore 404s. The
|
||||
# `view_resource_planners` permission is enforced by `authorize`.
|
||||
def find_work_package
|
||||
@work_package = WorkPackage
|
||||
.visible(current_user)
|
||||
.where(project: @project)
|
||||
.find(params.expect(:work_package_id))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -31,6 +31,11 @@
|
||||
class ResourceAllocation < ApplicationRecord
|
||||
ALLOWED_ENTITY_TYPES = %w[WorkPackage].freeze
|
||||
|
||||
# `allocated_time` is stored in minutes in an integer column. Cap it at 5000
|
||||
# hours so an absurdly large input is rejected with a validation error rather
|
||||
# than overflowing the column and raising ActiveModel::RangeError on save.
|
||||
MAX_ALLOCATED_TIME = (5000.hours / 1.minute).to_i
|
||||
|
||||
belongs_to :entity, polymorphic: true, optional: false
|
||||
belongs_to :principal, class_name: "User", optional: true, inverse_of: :resource_allocations
|
||||
belongs_to :requested_by, class_name: "User", optional: true
|
||||
@@ -58,11 +63,69 @@ class ResourceAllocation < ApplicationRecord
|
||||
scope :needs_principal_assignment, -> { where(principal_explicit: false, principal_id: nil) }
|
||||
scope :for_principal, ->(principal) { where(principal:) }
|
||||
|
||||
# The `allocated` allocations for the given work packages, grouped by work
|
||||
# package id and with principals eager-loaded. Loaded once per page so the
|
||||
# allocation columns (progress bar and members) share a single query.
|
||||
def self.allocated_for_work_packages(work_packages)
|
||||
allocated
|
||||
.where(entity_type: "WorkPackage", entity_id: work_packages.map(&:id))
|
||||
.includes(:principal)
|
||||
.order(:id)
|
||||
.group_by(&:entity_id)
|
||||
end
|
||||
|
||||
# The subset of the given allocations' principal ids that `user` may see.
|
||||
# Used to anonymise members the current user is not allowed to know about.
|
||||
def self.visible_principal_ids(allocations, user)
|
||||
principal_ids = allocations.filter_map(&:principal_id).uniq
|
||||
return Set.new if principal_ids.empty?
|
||||
|
||||
Principal.visible(user).where(id: principal_ids).pluck(:id).to_set
|
||||
end
|
||||
|
||||
# The ids of the given allocations that fall into a range in which their
|
||||
# assigned user is overbooked. Users without configured working hours are
|
||||
# skipped — their capacity is unknown, not zero (mirroring the check made
|
||||
# when an allocation is created). The users' working hours and booked
|
||||
# allocations are each fetched in one query; only the per-user capacity
|
||||
# calendar still queries per checked user.
|
||||
def self.overbooked_ids(allocations)
|
||||
checkable = overbooking_checkable_principals(allocations)
|
||||
return Set.new if checkable.empty?
|
||||
|
||||
booked = allocated.for_principal(checkable).group_by(&:principal_id)
|
||||
overbooked = checkable.flat_map { |principal| overbooked_ids_of(principal, booked.fetch(principal.id, [])) }
|
||||
|
||||
overbooked.to_set & allocations.map(&:id)
|
||||
end
|
||||
|
||||
# The ids of all allocations falling into a range in which the user is
|
||||
# overbooked, given the user's booked allocations.
|
||||
def self.overbooked_ids_of(principal, booked)
|
||||
ResourceAllocations::Availability
|
||||
.new(user: principal, allocations: booked)
|
||||
.overbooked_ranges
|
||||
.flat_map { |range| range.items.map(&:id) }
|
||||
end
|
||||
private_class_method :overbooked_ids_of
|
||||
|
||||
# The given allocations' assigned users whose capacity is known, i.e. who
|
||||
# have working hours configured, fetched in a single query.
|
||||
def self.overbooking_checkable_principals(allocations)
|
||||
principals = allocations.filter_map(&:principal).uniq
|
||||
checkable_ids = UserWorkingHours.for_user(principals).distinct.pluck(:user_id).to_set
|
||||
|
||||
principals.select { |principal| checkable_ids.include?(principal.id) }
|
||||
end
|
||||
private_class_method :overbooking_checkable_principals
|
||||
|
||||
validates :state, :start_date, :end_date, presence: true
|
||||
validates :allocated_time,
|
||||
presence: true,
|
||||
numericality: { only_integer: true, greater_than: 0 }
|
||||
|
||||
validate :allocated_time_within_limit
|
||||
|
||||
validates :entity_type,
|
||||
inclusion: { in: ALLOWED_ENTITY_TYPES },
|
||||
allow_blank: true
|
||||
@@ -151,6 +214,17 @@ class ResourceAllocation < ApplicationRecord
|
||||
|
||||
private
|
||||
|
||||
# Capped above to keep `allocated_time` within the integer column. The field is
|
||||
# entered in hours, so the message reports the limit in the same duration
|
||||
# format rather than the raw minutes the column stores.
|
||||
def allocated_time_within_limit
|
||||
return if allocated_time.blank?
|
||||
return if allocated_time <= MAX_ALLOCATED_TIME
|
||||
|
||||
errors.add(:allocated_time, :less_than_or_equal_to,
|
||||
count: DurationConverter.output(MAX_ALLOCATED_TIME / 60.0))
|
||||
end
|
||||
|
||||
def starts_before_entity?
|
||||
entity_start_date.present? && start_date.present? && start_date < entity_start_date
|
||||
end
|
||||
|
||||
@@ -63,6 +63,11 @@ class ResourceWorkPackageList < PersistedView
|
||||
effective_query&.manually_sorted? || false
|
||||
end
|
||||
|
||||
# The work packages selected by this view's query.
|
||||
def work_packages
|
||||
effective_query&.results&.work_packages || WorkPackage.none
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def manual_mode?(filter_mode)
|
||||
|
||||
@@ -40,8 +40,12 @@ module ResourceAllocations
|
||||
# checking a not-yet-persisted allocation.
|
||||
CANDIDATE_ID = :candidate
|
||||
|
||||
def initialize(user:)
|
||||
# `allocations` takes the user's `allocated` allocations when the caller
|
||||
# already loaded them (e.g. in bulk for several users); they are queried
|
||||
# lazily otherwise.
|
||||
def initialize(user:, allocations: nil)
|
||||
@user = user
|
||||
@allocations = allocations
|
||||
end
|
||||
|
||||
def overbooked?
|
||||
|
||||
+22
-1
@@ -41,4 +41,25 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
end %>
|
||||
|
||||
<%= render(ResourcePlanners::SubViewsComponent.new(resource_planner: @resource_planner, selected_view: @view)) %>
|
||||
<%= render(ResourcePlannerViews::ContentComponent.new(view: @view, project: @project, resource_planner: @resource_planner)) %>
|
||||
<%#
|
||||
Reloadable frame so allocation changes made in the dialogs refresh the table
|
||||
in place. The frame has no `src`, so it is not fetched on load — it just holds
|
||||
the server-rendered content. The reload-frame-on-event controller points its
|
||||
`src` at this same view and morphs it in when an allocation of any listed work
|
||||
package changes.
|
||||
%>
|
||||
<%
|
||||
# When the planner does not have a view yet, we cannot add frame data
|
||||
frame_data = if @view
|
||||
{
|
||||
controller: "reload-frame-on-event",
|
||||
"reload-frame-on-event-event-name-value": "resource-allocations:changed",
|
||||
"reload-frame-on-event-url-value": project_resource_planner_view_path(@project, @resource_planner, @view)
|
||||
}
|
||||
else
|
||||
{}
|
||||
end
|
||||
%>
|
||||
<%= turbo_frame_tag("resource-planner-view-content", refresh: :morph, data: frame_data) do %>
|
||||
<%= render(@content_component) %>
|
||||
<% end %>
|
||||
|
||||
@@ -57,6 +57,10 @@ af:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ af:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,23 @@ af:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ ar:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ ar:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,27 @@ ar:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
zero: "%{count} users"
|
||||
one: "%{count} user"
|
||||
two: "%{count} users"
|
||||
few: "%{count} users"
|
||||
many: "%{count} users"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ az:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ az:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,23 @@ az:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ be:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ be:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,25 @@ be:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
few: "%{count} users"
|
||||
many: "%{count} users"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ bg:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ bg:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,23 @@ bg:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ ca:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ ca:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,23 @@ ca:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ ckb-IR:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ ckb-IR:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,23 @@ ckb-IR:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ cs:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ cs:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,25 @@ cs:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
few: "%{count} users"
|
||||
many: "%{count} users"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ da:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ da:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,23 @@ da:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ de:
|
||||
make_private: Als Privat festlegen
|
||||
make_public: Veröffentlichen
|
||||
unfavorite: Aus Favoriten entfernen
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ de:
|
||||
caption: Eine benutzerdefinierte Liste von Artikeln, die Sie manuell hinzufügen und entfernen. Eine Filterung ist nicht möglich.
|
||||
label: Manuell ausgewählt
|
||||
title: Ansicht konfigurieren
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Machen Sie diese Ansicht zu einem Favoriten, um sie im oberen Bereich des Seitenleistenmenüs hinzuzufügen.
|
||||
label_new_resource_planner: Ressourcenplaner erstellen
|
||||
label_resource_planner: Ressourcenplaner
|
||||
@@ -114,9 +122,23 @@ de:
|
||||
user_card:
|
||||
caption: Erstellen Sie eine Ansicht auf der Basis von Benutzern und sehen Sie deren Details und Zuordnung in einer Kachelansicht von Benutzerkarten
|
||||
label: Benutzer-Karten
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Arbeitspaket hinzufügen
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: Es gibt noch keine Arbeitspakete, die den Filtern dieser Ansicht entsprechen.
|
||||
|
||||
@@ -57,6 +57,10 @@ el:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ el:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,23 @@ el:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ eo:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ eo:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,23 @@ eo:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -3,11 +3,11 @@ es:
|
||||
activerecord:
|
||||
attributes:
|
||||
resource_allocation:
|
||||
allocated_hours: Hours
|
||||
allocated_hours: Horas
|
||||
allocated_time: Tiempo asignado
|
||||
end_date: Fecha de finalización
|
||||
entity: Entidad
|
||||
filter_name: Resource filter name
|
||||
filter_name: Nombre del filtro de recursos
|
||||
principal: Asignado
|
||||
start_date: Fecha de inicio
|
||||
state: Estado
|
||||
@@ -57,29 +57,33 @@ es:
|
||||
make_private: Marcar como privado
|
||||
make_public: Marcar como público
|
||||
unfavorite: Eliminar de favoritos
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
caption: Set filter criteria based on user attributes to create a placeholder resource.
|
||||
label: Filter criteria
|
||||
label: Allocation type
|
||||
caption: Establecer criterios de filtro basados en atributos de usuario para crear un recurso de marcador de posición (placeholder).
|
||||
label: Criterios de filtrado
|
||||
label: Tipo de asignación
|
||||
principal:
|
||||
caption: Allocate hours for a specific user.
|
||||
label: User
|
||||
caption: Asignar horas para un usuario específico.
|
||||
label: Usuario
|
||||
outside_dates:
|
||||
description: The selected resource dates, %{resource_dates}, are outside of the work package's dates, %{work_package_dates}.
|
||||
description: Las fechas del recurso seleccionado, %{resource_dates}, están fuera de las fechas del paquete de trabajo, %{work_package_dates}.
|
||||
overbooking:
|
||||
capacity_summary: "%{available} available · %{scheduled} scheduled"
|
||||
description: The selected user, %{user}, would be allocated beyond their working hours during this period.
|
||||
hidden_work: Other allocations
|
||||
schedule_availability: "%{schedule} (%{factor}% available for project work)"
|
||||
schedule_change: until %{date}, then %{schedule}
|
||||
schedule_note: This user works %{schedule}.
|
||||
submit: Allocate overtime
|
||||
title: Do you want to allocate overtime to the user?
|
||||
submit: Allocate
|
||||
success_message: Resource allocated.
|
||||
title: Allocate resource
|
||||
capacity_summary: "%{available} disponible · %{scheduled} programado"
|
||||
description: El usuario seleccionado, %{user}, sería asignado más allá de su horario laboral durante este periodo.
|
||||
hidden_work: Otras asignaciones
|
||||
schedule_availability: "%{schedule} (%{factor}% disponible para trabajo de proyecto)"
|
||||
schedule_change: hasta %{date}, luego %{schedule}
|
||||
schedule_note: Este usuario trabaja %{schedule}.
|
||||
submit: Asignar horas extra
|
||||
title: "¿Quieres asignar horas extra al usuario?"
|
||||
submit: Asignar
|
||||
success_message: Recurso asignado.
|
||||
title: Asignar recurso
|
||||
blankslate:
|
||||
desc: Cree un planificador de recursos para empezar a planificar la capacidad de este proyecto.
|
||||
title: Aún no hay planificadores de recursos
|
||||
@@ -94,6 +98,10 @@ es:
|
||||
caption: Una lista personalizada de elementos que puedes añadir y eliminar manualmente. No se puede filtrar los resultados.
|
||||
label: Selección manual
|
||||
title: Configurar vista
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Convierta esta vista en favorita para añadirla a la sección superior del menú de la barra lateral.
|
||||
label_new_resource_planner: Nuevo planificador de recursos
|
||||
label_resource_planner: Planificador de recursos
|
||||
@@ -114,9 +122,23 @@ es:
|
||||
user_card:
|
||||
caption: Crea una vista basada en los usuarios y consulta sus datos y su asignación en una lista de fichas de usuario
|
||||
label: Lista de tarjetas de usuario
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Añadir paquete de trabajo
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: Todavía no hay paquetes de trabajo que se ajusten a los filtros de esta vista.
|
||||
|
||||
@@ -57,6 +57,10 @@ et:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ et:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,23 @@ et:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ eu:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ eu:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,23 @@ eu:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ fa:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ fa:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,23 @@ fa:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ fi:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ fi:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,23 @@ fi:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ fil:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ fil:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,23 @@ fil:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ fr:
|
||||
make_private: Rendre privé
|
||||
make_public: Rendre public
|
||||
unfavorite: Supprimer des favoris
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ fr:
|
||||
caption: Une liste personnalisée d'éléments que vous ajoutez et supprimez manuellement. Le filtrage n'est pas possible.
|
||||
label: Sélection manuelle
|
||||
title: Configurer la vue
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Faites de cette vue un favori pour l'ajouter à la section supérieure du menu de la barre latérale.
|
||||
label_new_resource_planner: Nouveau planificateur de ressources
|
||||
label_resource_planner: Planificateur de ressources
|
||||
@@ -114,9 +122,23 @@ fr:
|
||||
user_card:
|
||||
caption: Créez une vue basée sur des utilisateurs et affichez leurs informations et leur affectation dans une liste de fiches des utilisateurs
|
||||
label: Liste des fiches des utilisateurs
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Ajouter un lot de travaux
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: Aucun lot de travaux ne correspond encore aux filtres de cette vue.
|
||||
|
||||
@@ -57,6 +57,10 @@ he:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ he:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,25 @@ he:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
two: "%{count} users"
|
||||
many: "%{count} users"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ hi:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ hi:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,23 @@ hi:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ hr:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ hr:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,24 @@ hr:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
few: "%{count} users"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ hu:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ hu:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,23 @@ hu:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ hy:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ hy:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,23 @@ hy:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ id:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ id:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,22 @@ id:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ it:
|
||||
make_private: Rendi privato
|
||||
make_public: Rendi pubblico
|
||||
unfavorite: Rimuovi dai preferiti
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ it:
|
||||
caption: Un elenco personalizzato di elementi che puoi aggiungere e rimuovere manualmente. Il filtraggio non è disponibile.
|
||||
label: Selezionato manualmente
|
||||
title: Configura vista
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Aggiungi questa vista ai preferiti per mostrarla nella sezione superiore del menu laterale.
|
||||
label_new_resource_planner: Nuovo pianificatore di risorse
|
||||
label_resource_planner: Pianificatore di risorse
|
||||
@@ -114,9 +122,23 @@ it:
|
||||
user_card:
|
||||
caption: Crea una vista basata sugli utenti per consultare i loro dettagli e le allocazioni in un elenco di schede utente
|
||||
label: Elenco di schede utenti
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Aggiungi macro-attività
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: Non ci sono ancora macro-attività che corrispondono ai filtri di questa vista.
|
||||
|
||||
@@ -57,6 +57,10 @@ ja:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ ja:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,22 @@ ja:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ ka:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ ka:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,23 @@ ka:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ kk:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ kk:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,23 @@ kk:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ ko:
|
||||
make_private: 비공개로 설정
|
||||
make_public: 공개로 설정
|
||||
unfavorite: 즐겨찾기에서 제거
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ ko:
|
||||
caption: 수동으로 추가 및 제거하는 항목의 사용자 지정 목록입니다. 필터링은 불가능합니다.
|
||||
label: 수동으로 직접 선택함
|
||||
title: 보기 구성
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: 이 보기를 즐겨찾기로 지정하여 사이드바 메뉴의 상단 섹션에 추가합니다.
|
||||
label_new_resource_planner: 새로운 리소스 플래너
|
||||
label_resource_planner: 리소스 플래너
|
||||
@@ -114,9 +122,22 @@ ko:
|
||||
user_card:
|
||||
caption: 사용자에 기반한 보기를 만들고 사용자 카드 목록에서 해당 세부 정보 및 할당을 확인하세요
|
||||
label: 사용자 카드 목록
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: 작업 패키지 추가
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: 아직 이 보기의 필터와 일치하는 작업 패키지가 없습니다.
|
||||
|
||||
@@ -57,6 +57,10 @@ lt:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ lt:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,25 @@ lt:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
few: "%{count} users"
|
||||
many: "%{count} users"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ lv:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ lv:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,24 @@ lv:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
zero: "%{count} users"
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ mn:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ mn:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,23 @@ mn:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
one: "%{count} user"
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
@@ -57,6 +57,10 @@ ms:
|
||||
make_private: Make private
|
||||
make_public: Make public
|
||||
unfavorite: Remove from favorites
|
||||
allocation:
|
||||
hours: "%{value}h"
|
||||
no_work: No work planned on this work package
|
||||
summary: "%{allocated} / %{scheduled}"
|
||||
allocate_resource_dialog:
|
||||
kind:
|
||||
filter:
|
||||
@@ -94,6 +98,10 @@ ms:
|
||||
caption: A custom list of items you manually add and remove. Filtering is not possible.
|
||||
label: Manually hand-picked
|
||||
title: Configure view
|
||||
edit_allocation_dialog:
|
||||
submit: Save
|
||||
success_message: Resource allocation updated.
|
||||
title: Edit allocation
|
||||
favorite_caption: Make this view a favorite to add it on the top section of the sidebar menu.
|
||||
label_new_resource_planner: New resource planner
|
||||
label_resource_planner: Resource planner
|
||||
@@ -114,9 +122,22 @@ ms:
|
||||
user_card:
|
||||
caption: Create a view based on users and see their details and allocation in a list of user cards
|
||||
label: Users card list
|
||||
work_package_allocations_dialog:
|
||||
allocate_resource: Allocate resource
|
||||
context_menu_label: Allocation actions
|
||||
delete_confirmation: Are you sure you want to delete this allocation? This cannot be undone.
|
||||
delete_success: Resource allocation deleted.
|
||||
hidden_user: Hidden user
|
||||
overbooked: The user is allocated beyond their working hours during this period.
|
||||
title: Allocation
|
||||
work_package_list:
|
||||
add_work_package_dialog:
|
||||
title: Add work package
|
||||
allocated_members:
|
||||
additional: "+%{count}"
|
||||
other_users:
|
||||
other: "%{count} users"
|
||||
unassigned: Unassigned
|
||||
allocation_placeholder: "—"
|
||||
blank:
|
||||
description: There are no work packages matching this view's filters yet.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user