Skip to content

Commit

Permalink
add work week to team planner
Browse files Browse the repository at this point in the history
  • Loading branch information
ulferts committed Nov 10, 2022
1 parent a7030de commit 844778b
Show file tree
Hide file tree
Showing 12 changed files with 304 additions and 112 deletions.
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,19 @@
data-qa-selector="add-existing-pane"
>
</op-add-existing-pane>

<button
class="button button_no-margin 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>
</button>
</ng-container>

<div
Expand Down
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 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),
}));
}
}
18 changes: 16 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: 7rem

.router--team-planner
#content
height: 100%
Expand Down Expand Up @@ -31,10 +33,20 @@
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

.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 +86,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

0 comments on commit 844778b

Please sign in to comment.