Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/44702 team planner add work week option to visible time scale #11601

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions frontend/src/app/core/days/weekday.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ export class WeekdayService {
return !!(this.weekdays || []).find((wd) => wd.day === isoDayOfWeek && !wd.working);
}

public get nonWorkingDays():IWeekday[] {
return this.weekdays.filter((day) => !day.working);
}

loadWeekdays():Observable<IWeekday[]> {
if (this.weekdays) {
return of(this.weekdays);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@
data-qa-selector="add-existing-pane"
>
</op-add-existing-pane>

<button
ulferts marked this conversation as resolved.
Show resolved Hide resolved
type="button"
class="button op-team-planner--view-select-dropdown"
[title]="currentViewTitle"
opTeamPlannerViewSelectDropdown
[viewOptions]="viewOptions"
(viewSelected)="switchView($event)"
data-qa-selector="op-team-planner--view-select-dropdown">
<span class="button--text ellipsis"
[textContent]="currentViewTitle"
aria-hidden="true"></span>
<op-icon icon-classes="button--icon icon-small icon-pulldown hidden-for-mobile"></op-icon>
b12f marked this conversation as resolved.
Show resolved Hide resolved
</button>
</ng-container>

<div
Expand Down Expand Up @@ -165,3 +179,4 @@
</ng-container>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ $op-team-planner-resource-width: 180px
&--add-existing-toggle
position: absolute

&--view-select-dropdown
position: absolute
right: 0

&--footer
grid-area: footer
display: flex
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ import {
EventContentArg,
EventDropArg,
EventInput,
RawOptionsFromRefiners,
ViewOptionRefiners,
} from '@fullcalendar/core';
import {
BehaviorSubject,
Expand Down Expand Up @@ -117,6 +119,9 @@ import { OpWorkPackagesCalendarService } from 'core-app/features/calendar/op-wor
import { DeviceService } from 'core-app/core/browser/device.service';
import { WeekdayService } from 'core-app/core/days/weekday.service';

export type TeamPlannerViewOptionKey = 'resourceTimelineWorkWeek' | 'resourceTimelineWeek' | 'resourceTimelineTwoWeeks';
export type TeamPlannerViewOptions = { [K in TeamPlannerViewOptionKey]:RawOptionsFromRefiners<Required<ViewOptionRefiners>> };

@Component({
selector: 'op-team-planner',
templateUrl: './team-planner.component.html',
Expand Down Expand Up @@ -286,6 +291,7 @@ export class TeamPlannerComponent extends UntilDestroyedMixin implements OnInit,
add_assignee: this.I18n.t('js.team_planner.add_assignee'),
remove_assignee: this.I18n.t('js.team_planner.remove_assignee'),
noData: this.I18n.t('js.team_planner.no_data'),
work_week: this.I18n.t('js.team_planner.work_week'),
two_weeks: this.I18n.t('js.team_planner.two_weeks'),
one_week: this.I18n.t('js.team_planner.one_week'),
today: this.I18n.t('js.team_planner.today'),
Expand All @@ -307,6 +313,55 @@ export class TeamPlannerComponent extends UntilDestroyedMixin implements OnInit,

isMobile = this.deviceService.isMobile;

private initialCalendarView = this.workPackagesCalendar.initialView || 'resourceTimelineWorkWeek';

private viewOptionDefaults = {
type: 'resourceTimeline',
slotDuration: { days: 1 },
resourceAreaColumns: [
{
field: 'title',
headerContent: {
html: `<span aria-label="${this.text.assignee}" class="spot-icon spot-icon_user"></span> <span class="hidden-for-mobile">${this.text.assignee}</span>`,
},
},
],
};

public viewOptions:TeamPlannerViewOptions = {
resourceTimelineWorkWeek: {
...this.viewOptionDefaults,
...{
duration: { weeks: 1 },
slotLabelFormat: [
{ weekday: 'long', day: '2-digit' },
],
buttonText: this.text.work_week,
},
},
resourceTimelineWeek: {
...this.viewOptionDefaults,
...{
duration: { weeks: 1 },
slotLabelFormat: [
{ weekday: 'long', day: '2-digit' },
],
buttonText: this.text.one_week,
},
},
resourceTimelineTwoWeeks: {
...this.viewOptionDefaults,
...{
buttonText: this.text.two_weeks,
duration: { weeks: 2 },
dateIncrement: { weeks: 1 },
slotLabelFormat: [
{ weekday: 'short', day: '2-digit' },
],
},
},
};

constructor(
private $state:StateService,
private configuration:ConfigurationService,
Expand Down Expand Up @@ -428,49 +483,21 @@ export class TeamPlannerComponent extends UntilDestroyedMixin implements OnInit,
plugins: [resourceTimelinePlugin, interactionPlugin],
titleFormat: { year: 'numeric', month: 'long', day: 'numeric' },
buttonText: { today: this.text.today },
initialView: this.workPackagesCalendar.initialView || 'resourceTimelineWeek',
initialView: this.initialCalendarView,
headerToolbar: {
left: '',
center: 'title',
right: 'prev,next today resourceTimelineWeek,resourceTimelineTwoWeeks',
right: 'prev,next today',
},
views: {
resourceTimelineWeek: {
type: 'resourceTimeline',
buttonText: this.text.one_week,
duration: { weeks: 1 },
slotDuration: { days: 1 },
slotLabelFormat: [
{ weekday: 'long', day: '2-digit' },
],
resourceAreaColumns: [
{
field: 'title',
headerContent: {
html: `<span class="spot-icon spot-icon_user"></span> <span class="hidden-for-mobile">${this.text.assignee}</span>`,
},
},
],
},
resourceTimelineTwoWeeks: {
type: 'resourceTimeline',
buttonText: this.text.two_weeks,
slotDuration: { days: 1 },
duration: { weeks: 2 },
dateIncrement: { weeks: 1 },
slotLabelFormat: [
{ weekday: 'short', day: '2-digit' },
],
resourceAreaColumns: [
{
field: 'title',
headerContent: {
html: `<span class="spot-icon spot-icon_user"></span> <span class="hidden-for-mobile">${this.text.assignee}</span>`,
},
},
],
views: _.merge(
{},
this.viewOptions,
{
resourceTimelineWorkWeek: {
hiddenDays: this.weekdayService.nonWorkingDays.map((weekday) => weekday.day % 7), // The OP days are 1 based but this needs to be 0 based.
},
},
},
),
// Ensure we show the skeleton from the beginning
progressiveEventRendering: true,
eventSources: [
Expand Down Expand Up @@ -607,6 +634,14 @@ export class TeamPlannerComponent extends UntilDestroyedMixin implements OnInit,
void this.workPackagesCalendar.updateTimeframe(fetchInfo, this.projectIdentifier);
}

public switchView(key:TeamPlannerViewOptionKey):void {
this.ucCalendar.getApi().changeView(key);
}

public get currentViewTitle():string {
return this.viewOptions[((this.ucCalendar && this.ucCalendar.getApi().view.type) || this.initialCalendarView) as TeamPlannerViewOptionKey].buttonText as string;
}

/**
* Clear loading and show successful toast if we were reloading the page
* @private
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { OPSharedModule } from 'core-app/shared/shared.module';
import { AddExistingPaneComponent } from './add-work-packages/add-existing-pane.component';
import { OpenprojectContentLoaderModule } from 'core-app/shared/components/op-content-loader/openproject-content-loader.module';
import { TeamPlannerSidemenuComponent } from 'core-app/features/team-planner/team-planner/sidemenu/team-planner-sidemenu.component';
import { TeamPlannerViewSelectMenuDirective } from 'core-app/features/team-planner/team-planner/view-select/view-select-menu.directive';

@NgModule({
declarations: [
Expand All @@ -23,6 +24,7 @@ import { TeamPlannerSidemenuComponent } from 'core-app/features/team-planner/tea
AddAssigneeComponent,
AddExistingPaneComponent,
TeamPlannerSidemenuComponent,
TeamPlannerViewSelectMenuDirective,
],
imports: [
OPSharedModule,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* --copyright
* OpenProject is an open source project management software.
* Copyright (C) 2010-2022 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 {
Directive,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { OpContextMenuTrigger } from 'core-app/shared/components/op-context-menu/handlers/op-context-menu-trigger.directive';
import {
TeamPlannerViewOptionKey,
TeamPlannerViewOptions,
} from 'core-app/features/team-planner/team-planner/planner/team-planner.component';
import { OpContextMenuItem } from 'core-app/shared/components/op-context-menu/op-context-menu.types';

@Directive({
selector: '[opTeamPlannerViewSelectDropdown]',
})
export class TeamPlannerViewSelectMenuDirective extends OpContextMenuTrigger {
@Input() public viewOptions:NonNullable<TeamPlannerViewOptions>;

@Output() public viewSelected = new EventEmitter<TeamPlannerViewOptionKey>();

public get locals():{ showAnchorRight?:boolean, contextMenuId?:string, items:OpContextMenuItem[] } {
return {
items: this.buildItems(),
contextMenuId: 'op-team-planner--view-select-dropdown',
};
}

private selected(key:TeamPlannerViewOptionKey):boolean {
this.viewSelected.emit(key);
// Done to satisfy the interface.
return true;
}

private buildItems():OpContextMenuItem[] {
return Object.entries(this.viewOptions).map(([key, viewOption]) => ({
linkText: viewOption.buttonText as string,
onClick: () => this.selected(key as TeamPlannerViewOptionKey),
}));
}
}
19 changes: 17 additions & 2 deletions frontend/src/global_styles/content/modules/_team_planner.sass
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
@import "../../../app/features/team-planner/team-planner/assignee/tp-assignee"

$view-select-dropdown-width: 8rem

.router--team-planner
#content
height: 100%
Expand Down Expand Up @@ -31,10 +33,21 @@
background: white
z-index: 5
padding-left: 138px
// Necessary for the button to switch between views which is hacked in via absolute positioning.
padding-right: $view-select-dropdown-width + 0.5rem
padding-bottom: 1.5rem
margin: 0 !important

&--add-existing-toggle
&--view-select-dropdown
width: $view-select-dropdown-width
display: flex
margin: 0

.button--text
flex-grow: 1
text-align: left

&--add-existing-toggle, &--view-select-dropdown
z-index: 6

.fc-scrollgrid
Expand Down Expand Up @@ -74,4 +87,6 @@
justify-content: center !important
.fc-datagrid-cell-cushion
padding: 12px 12px !important


#op-team-planner--view-select-dropdown
min-width: $view-select-dropdown-width
1 change: 1 addition & 0 deletions modules/team_planner/config/locales/js-en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ en:
remove_assignee: 'Remove assignee'
two_weeks: '2-week'
one_week: '1-week'
work_week: 'Work week'
today: 'Today'
drag_here_to_remove: 'Drag here to remove assignee and start and end dates.'
cannot_drag_here: 'Cannot remove the work package due to permissions or editing restrictions.'
Expand Down
Loading