512 lines
28 KiB
Plaintext
512 lines
28 KiB
Plaintext
@using EnVisage.Code
|
||
@using Prevu.Core.Main
|
||
@model string
|
||
@{
|
||
ViewBag.Title = "Roadmap Optimizer";
|
||
Layout = "~/Views/Shared/_Layout.cshtml";
|
||
}
|
||
@section stylesheets
|
||
{
|
||
@Styles.Render(String.Format("{0}/{1}", Constants.C_BUNDLE_STYLES_BASE_PATH, "prevu-rmostyles"))
|
||
}
|
||
@section Scripts
|
||
{
|
||
@Scripts.Render(String.Format("{0}/{1}", Constants.C_BUNDLE_SCRIPTS_BASE_PATH, "flot-scripts"))
|
||
@Scripts.Render(String.Format("{0}/{1}", Constants.C_BUNDLE_SCRIPTS_BASE_PATH, "prevu-angularservices"))
|
||
@Scripts.Render(String.Format("{0}/{1}", Constants.C_BUNDLE_SCRIPTS_BASE_PATH, "prevu-scenariodetails"))
|
||
@Scripts.Render(String.Format("{0}/{1}", Constants.C_BUNDLE_SCRIPTS_BASE_PATH, "prevu-rmopage"))
|
||
|
||
<script>
|
||
function initMixForm() {
|
||
$("div#main-wrapper").attr("data-section", "mixCalendar");
|
||
var datePickerOptions = {
|
||
format: 'm/d/yyyy',
|
||
autoclose: true,
|
||
orientation: $('body').hasClass('right-to-left') ? "auto right" : 'auto auto',
|
||
startDate: '@(Constants.MIN_SELECTABLE_DATE)',
|
||
endDate: '@(Constants.MAX_SELECTABLE_DATE)'
|
||
};
|
||
|
||
$('#bs-datepicker-mix-range').datepicker(datePickerOptions).on('changeDate', function (evt) {
|
||
if (evt.target.id === 'MixStartDate' && !$('#MixEndDate').val()) {
|
||
$('#bs-datepicker-mix-range').data('datepicker').pickers[1].prepopulate(evt.date);
|
||
}
|
||
});
|
||
|
||
$('#mixTeamsAndViews').select2({
|
||
allowClear: true
|
||
});
|
||
$('#mixContributors').select2({
|
||
allowClear: true
|
||
});
|
||
$('#availableMixes').select2({
|
||
allowClear: true
|
||
});
|
||
$('#selectedTeamViews').select2({
|
||
allowClear: true
|
||
});
|
||
$('#selProjects2Add').select2({
|
||
allowClear: true
|
||
});
|
||
$('#mix-edit-scenario-teams-dialog select').select2({
|
||
allowClear: true
|
||
});
|
||
$('#copierProjectId').select2();
|
||
$('#btnSaveMix').on('click', function () {
|
||
showSaveMixDialog();
|
||
});
|
||
|
||
$('#btnActivateMix').on('click', function () {
|
||
showSaveMixDialog();
|
||
});
|
||
var menu = $('#mixMenu');
|
||
$.each($('#mixMenuTemplate').children(), function (i, obj) {
|
||
menu.append($(obj));
|
||
});
|
||
menu.on('click', function (event) {
|
||
event.stopPropagation();
|
||
});
|
||
|
||
$('#divMixMenuTemplate').remove();
|
||
}
|
||
|
||
function showSaveMixDialog() {
|
||
if (!$('#mix-header').valid())
|
||
return;
|
||
|
||
$('#saveMixModal').modal();
|
||
}
|
||
|
||
function canSaveMix() {
|
||
return $('#mix-header').valid() && $('#saveMixForm').valid();
|
||
}
|
||
function HasRaceScore() {
|
||
|
||
}
|
||
function isValidHeader() {
|
||
if ($('#mix-header').valid()) {
|
||
$('#mix-header .validation-summary-errors')
|
||
.addClass('validation-summary-valid')
|
||
.removeClass('validation-summary-errors');
|
||
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
function printPDF(bPrint) {
|
||
blockUI();
|
||
|
||
var mainwrapper = $("#main-wrapper");
|
||
var contentwrapper = $("#content-wrapper");
|
||
var rmopanel = $("#rmo-panel");
|
||
var rmopanelbody = $("#rmo-panel-body");
|
||
|
||
//prepare blocks to be exported to canvas
|
||
mainwrapper.css("transition", "none");
|
||
mainwrapper.css("padding-left", "0px");
|
||
$("#dvBtns1").hide();
|
||
$("#dvBtns2").hide();
|
||
$("#dvBtns3").hide();
|
||
|
||
//calculate required canvas width
|
||
//1. calculate total width of RMO table
|
||
var requiredWidth = 0;
|
||
$("#mix-table tr:first th").each(function (i, e) { requiredWidth += $(e).outerWidth(); });
|
||
//2. add upper elements paddings etc
|
||
requiredWidth += (rmopanelbody.outerWidth() - rmopanelbody.width());//padding of the panel body
|
||
//console.log(rmopanelbody.outerWidth() - rmopanelbody.width());
|
||
requiredWidth += (rmopanel.outerWidth() - rmopanel.innerWidth());//border of the panel
|
||
//console.log(rmopanel.outerWidth() - rmopanel.innerWidth());
|
||
requiredWidth += (contentwrapper.outerWidth() - contentwrapper.width());//padding of the content wrapper
|
||
//console.log(contentwrapper.outerWidth() - contentwrapper.width());
|
||
|
||
//check if we need to resize main wrapper and resize it
|
||
console.log("cw: " + mainwrapper.width() + "; rw: " + requiredWidth);
|
||
if (mainwrapper.width() < requiredWidth)
|
||
mainwrapper.css("width", requiredWidth + "px");
|
||
|
||
//make sure css applied and do the flush-export
|
||
window.setTimeout(function () {
|
||
//scroll the page to upper-left corner to make sure canvas rendered correctly in chrome
|
||
window.scrollTo(0, 0);
|
||
|
||
html2canvas(rmopanel, {
|
||
onrendered: function (canvas) {
|
||
mainwrapper.css("width", "");
|
||
mainwrapper.css("padding-left", "");
|
||
mainwrapper.css("transition", "");
|
||
$("#dvBtns1").show();
|
||
$("#dvBtns2").show();
|
||
$("#dvBtns3").show();
|
||
|
||
//check if we need portrait or landscape
|
||
var landscape = canvas.width > canvas.height;
|
||
|
||
//A4 page dimensions
|
||
var page = {};
|
||
page.w = landscape ? 297 : 210;
|
||
page.h = landscape ? 210 : 297;
|
||
|
||
var hRatio = page.w / canvas.width;
|
||
var vRatio = page.h / canvas.height;
|
||
var ratio = Math.min(hRatio, vRatio);
|
||
var centerShift_x = (page.w - canvas.width * ratio) / 2;
|
||
var centerShift_y = (page.h - canvas.height * ratio) / 2;
|
||
|
||
//console.log("docw: " + docw + "; doch: " + doch + "; imgw: " + imgw + "; imgh: " + imgh);
|
||
var imgData = canvas.toDataURL('image/png');
|
||
var doc = new jsPDF({
|
||
orientation: landscape ? 'l' : 'p',
|
||
format: 'a4',
|
||
unit: 'mm'
|
||
|
||
});
|
||
doc.addImage(imgData, 'PNG', centerShift_x, 0, canvas.width * ratio, canvas.height * ratio);
|
||
unblockUI();
|
||
if (bPrint == true) {
|
||
doc.autoPrint();
|
||
doc.save('autoprint.pdf');
|
||
window.open(doc.output('bloburl'), '_blank');//this triggers popup blocker - not a good solution...
|
||
}
|
||
else {
|
||
var d = new Date();
|
||
var curr_date = d.getDate();
|
||
var curr_month = d.getMonth() + 1;
|
||
var curr_year = d.getFullYear();
|
||
doc.save("Prevu-RMO-" + (curr_year + "-" + curr_month + "-" + curr_date) + ".pdf");
|
||
}
|
||
}
|
||
});
|
||
}, 10);
|
||
|
||
}
|
||
|
||
function loadTeam(teamId) {
|
||
blockUI();
|
||
|
||
if (teamId != null)
|
||
StartEdit('Team', teamId, null, null, 'erorMsgPlaceholder');
|
||
|
||
var itemUrl = teamId != null ? "?Id=" + teamId : "";
|
||
var url = '@Url.Action("EditTeam", "Mix")' + itemUrl;
|
||
|
||
// SA. ENV-1065. Rewrited to $.Ajax to force turn off request caching
|
||
$('#editReload').html("");
|
||
$.ajax({
|
||
url: url,
|
||
cache: false,
|
||
dataType: "html",
|
||
success: function (data) {
|
||
$('#editReload').html(data);
|
||
$('#editTeam')
|
||
.on('hidden.bs.modal', function () {
|
||
if (teamId != null) {
|
||
StopEdit();
|
||
RemoveLock('Team', teamId);
|
||
}
|
||
})
|
||
.on('shown.bs.modal', function () {
|
||
// No actions
|
||
})
|
||
.modal('show');
|
||
|
||
initTeam();
|
||
},
|
||
error: function () {
|
||
showErrorModal();
|
||
},
|
||
complete: function () {
|
||
unblockUI();
|
||
},
|
||
});
|
||
}
|
||
|
||
function editTeam(Id) {
|
||
loadTeam(Id);
|
||
return true;
|
||
}
|
||
|
||
//fix modal force focus
|
||
$.fn.modal.Constructor.prototype.enforceFocus = function () {
|
||
var that = this;
|
||
$(document).on('focusin.modal', function (e) {
|
||
if ($(e.target).hasClass('select2-input')) {
|
||
return true;
|
||
}
|
||
|
||
if (that.$element[0] !== e.target && !that.$element.has(e.target).length) {
|
||
that.$element.focus();
|
||
}
|
||
});
|
||
};
|
||
|
||
init.push(function () {
|
||
saveLastPageVisited(window.location.pathname + window.location.search);
|
||
$(document).on('hide.bs.modal', '#editTeam', function (e) {
|
||
// skip modal hide event from datepickers
|
||
if ($(e.target).attr('id') != 'editTeam')
|
||
return true; // close modal form
|
||
// check that form has been changed
|
||
if (typeof isTeamDataChanged === 'function')
|
||
// if form has been changed
|
||
if (isTeamDataChanged()) {
|
||
// ask user for confirmation of form close
|
||
var msg = "Adding Team form contains unsaved changes, do you really want to close the form?";
|
||
bootbox.confirm(msg, function (result) {
|
||
if (result) {
|
||
if (typeof resetTeamDataChanged === 'function') {
|
||
resetTeamDataChanged();
|
||
}
|
||
$('#editTeam').modal('hide');
|
||
}
|
||
});
|
||
|
||
return false; // DO NOT close modal form
|
||
}
|
||
return true; // close modal form
|
||
});
|
||
|
||
});
|
||
|
||
|
||
</script>
|
||
}
|
||
@section pagemenu
|
||
{
|
||
<ul class="nav navbar-nav navbar-right">
|
||
<li class="dropdown" id="divMixMenu">
|
||
<a href="#" class="dropdown-toggle user-menu" data-toggle="dropdown">
|
||
<i class="fa fa-bars"></i><span>Page Options</span> <i class="fa fa-caret-down"></i>
|
||
</a>
|
||
<ul class="dropdown-menu dropdown-menu-right" id="mixMenu">
|
||
<!--<li><a href="javascript: printPDF(true);"><i class="dropdown-icon fa fa-print"></i> Print Page</a></li>-->
|
||
<li><a href="javascript: printPDF(false);"><i class="dropdown-icon fa fa-file-text-o"></i> Save Page as PDF</a></li>
|
||
<li class="divider"></li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
}
|
||
@{
|
||
var userId = SecurityManager.GetUserPrincipal();
|
||
var userManager = DependencyResolver.Current.GetService<IUserManager>();
|
||
var user = userManager.GetUser(userId);
|
||
var pagePreferences = userManager.GetPagePreferences(Url.Action("Index", "Mix"), "mixCalendar", userId);
|
||
var json = Newtonsoft.Json.JsonConvert.SerializeObject(new
|
||
{
|
||
mixId = Model,
|
||
showAvgTotals = user.PreferredTotalsDisplaying,
|
||
prefs = pagePreferences
|
||
});
|
||
}
|
||
<div>
|
||
<div id="erorMsgPlaceholder"></div>
|
||
<div class="panel" id="rmo-panel">
|
||
<div class="panel-body" id="rmo-panel-body" style="border: 0;">
|
||
<fieldset class="form-group-margin">
|
||
<div id="mixHeader" ng-controller="mixHeaderController" ng-init="init(@json)" init-form ng-cloak>
|
||
@Html.AntiForgeryToken()
|
||
<form style="border: 0;" id="mix-header">
|
||
<div class="row" ng-if="data.AvailableMixes != null && data.AvailableMixes.length > 0">
|
||
<div class="col-sm-6">
|
||
<div class="form-group no-margin-hr">
|
||
<label class="control-label noprint" for="availableMixes">Select existing Mix</label>
|
||
<span class="nodisplay-inline">Mix: </span><select id="availableMixes" name="availableMixes" class="form-control"
|
||
ng-model="data.SelectedMix"
|
||
ng-options="mix.Value as mix.Text for mix in data.AvailableMixes | orderBy:'Text'"></select>
|
||
</div>
|
||
</div>
|
||
<div class="col-sm-6" id="dvBtns1">
|
||
<div class="form-group no-margin-hr">
|
||
<label class="control-label"> </label><br />
|
||
@* Need to show only if selected mix is exists and it is not current opened mix *@
|
||
<button type="button" class="btn btn-primary" ng-click="navigateToMix()" ng-show="data.ShowOpen">Open Mix</button>
|
||
@* SA. ENV-1138. Delete selected, but unopened mix *@
|
||
<button type="button" class="btn btn-danger" ng-click="deleteUnopenedMix()" ng-show="data.ShowOpen">Delete Mix</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="row" ng-if="data.AvailableTeamsAndViews != null && data.AvailableTeamsAndViews.length > 0">
|
||
<div class="col-sm-6">
|
||
<div class="form-group select2-primary no-margin-hr">
|
||
<label class="control-label">Team(s) or View(s)</label>
|
||
<select id="selectedTeamViews" name="selectedTeamViews" class="form-control" multiple="multiple" selected-data-changed
|
||
ng-model="data.SelectedTeamsAndViews"
|
||
ng-options="team.TVName group by team.Group.Name for team in data.AvailableTeamsAndViews | orderBy:['Group.Name','TVName']"></select>
|
||
<span class="help-block field-validation-valid" data-valmsg-for="mixTeamsAndViews" data-valmsg-replace="true"></span>
|
||
</div>
|
||
</div>
|
||
<div class="col-sm-6">
|
||
<div class="form-group no-margin-hr">
|
||
<label class="control-label">Dates</label>
|
||
<div class="input-daterange input-group" id="bs-datepicker-mix-range">
|
||
<div class="input-daterange input-group date floatdaterange" id="mix-dateStart">
|
||
<input type="text" id="MixStartDate" name="MixStartDate" class="form-control"
|
||
data-val="true" data-val-date="The field Start Date must be a date."
|
||
data-val-required="The Start Date field is required."
|
||
ng-model="data.StartDate"
|
||
ng-disabled="!data.IsEditable" />
|
||
</div>
|
||
<div class="pull-left floatdaterange-addon">To</div>
|
||
<div class="input-daterange input-group date floatdaterange" id="mix-dateEnd">
|
||
<input type="text" id="MixEndDate" name="MixEndDate" class="form-control"
|
||
data-val="true" data-val-date="The field End Date must be a date."
|
||
data-val-required="The End Date field is required."
|
||
data-val-dategreaterthanorequal="Mix End Date should not be less than Start Date"
|
||
data-val-dategreaterthanorequal-otherpropertyname="MixStartDate"
|
||
ng-model="data.EndDate"
|
||
ng-disabled="!data.IsEditable" />
|
||
</div>
|
||
</div>
|
||
<span class="help-block field-validation-valid" data-valmsg-for="MixStartDate" data-valmsg-replace="true"></span>
|
||
<span class="help-block field-validation-valid" data-valmsg-for="MixEndDate" data-valmsg-replace="true"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
@* Client-side filters by Cost Centers and Project Roles *@
|
||
<div class="row" ng-show="data.DataLoaded">
|
||
<div class="col-sm-6">
|
||
<div class="form-group no-margin-hr" style="width:100%">
|
||
<div class="form-group select2-primary no-margin-hr" style="width: 100%;">
|
||
<label class="control-label">Cost Centers:</label>
|
||
<select ng-select2 ng-model="data.SelectedCostCenters" allowClear="true"
|
||
placeholder="All available Cost Centers" minimumResultsForSearch="5" multiple="multiple"
|
||
ng-change="filterCostCentersChanged()" option-class-expr="CSSClass" data-val="true">
|
||
<option ng-repeat="option in data.AvailableCostCenters track by $index" value="{{option.Value}}">{{option.Text}}</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-sm-6">
|
||
<div class="form-group no-margin-hr" style="width:100%">
|
||
<div class="form-group select2-primary no-margin-hr" style="width: 100%;">
|
||
<label class="control-label">Project Roles:</label>
|
||
<select ng-select2 ng-model="data.SelectedProjectRoles" allowClear="true"
|
||
placeholder="Select items to be displayed" minimumResultsForSearch="5" multiple="multiple"
|
||
ng-change="filterProjectRolesChanged()" option-class-expr="CSSClass" data-val="true">
|
||
<option ng-repeat="option in data.VisibleProjectRoles track by $index" value="{{option.Value}}">{{option.Text}}</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
@Html.ValidationSummary(false, "The Mix could not be saved due to the following errors:")
|
||
<div class="row" id="dvBtns2">
|
||
<div class="col-sm-12">
|
||
<div class="form-group select2-primary no-margin-hr" id="raceScoreMagnify">
|
||
<button type="button" class="btn btn-primary" id="btnApplyFilter" ng-show="data.IsEditable" ng-disabled="!canApplyFilter()" ng-click="applyFilter()">
|
||
<i class="fa fa-search"></i> Apply Filter
|
||
</button>
|
||
<button type="button" class="btn btn-primary" id="btnDoRace2Res" ng-show="data.IsEditable" ng-disabled="!canDoRaceRes()" ng-click="applyRace2Res()">
|
||
<i class="fa fa-check-circle-o"></i> Race(Resource)
|
||
</button>
|
||
<button type="button" class="btn btn-primary" id="btnDoRace2Team" ng-show="data.IsEditable" ng-disabled="!canDoRaceTeam(true)" ng-click="applyRace2Team()">
|
||
<i class="fa fa-check-circle-o"></i> Race(Team)
|
||
</button>
|
||
|
||
@if (SecurityManager.CheckSecurityObjectPermission(Areas.Teams, AccessLevel.Write))
|
||
{
|
||
<a class="btn btn-success noprint" ng-show="data.IsEditable" onclick="return editTeam(null);">
|
||
<i class="fa fa-plus"></i> Create Team
|
||
</a>
|
||
}
|
||
<button type="button" class="btn btn-success" id="btnSaveMix" ng-show="canSaveMix()" ng-disabled="!data.DataChanged" ng-click="requestMixSaving()">
|
||
<i class="fa fa-save"></i> Save this Mix
|
||
</button>
|
||
<button type="button" class="btn btn-warning" id="btnActivateMix" ng-show="canActivateMix()" ng-click="requestMixActivation()">
|
||
<i class="fa fa-check-circle-o"></i> Activate Mix
|
||
</button>
|
||
<button type="button" class="btn btn-danger" id="btnDeleteMix" ng-show="canDeleteMix()" ng-disabled="data.DisableDelete" ng-click="deleteCurrentMix()">
|
||
<i class="fa fa-trash-o"></i> Delete Mix
|
||
</button>
|
||
<button type="button" class="btn btn-danger" ng-show="canClearMix()" ng-click="navigateToNewMix()">
|
||
<i class="fa fa-times"></i> Clear
|
||
</button>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
<div class="row" ng-if="data.AvailableUsers != null && data.AvailableUsers.length > 0">
|
||
<div class="col-sm-6">
|
||
<div class="select2-primary no-margin-hr" ng-show="canSaveMix()">
|
||
<label class="control-label">Contributors</label>
|
||
<select id="mixContributors" name="mixContributors" class="form-control" data-val="true" data-val-required="You need to select at least one contributor"
|
||
ng-disabled="!data.IsEditable"
|
||
ng-model="data.Users"
|
||
ng-options="user.Value as user.Text for user in data.AvailableUsers | orderBy: 'Text'"
|
||
multiple="multiple"></select>
|
||
<span class="help-block field-validation-valid" data-valmsg-for="mixContributors" data-valmsg-replace="true"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
<div id="saveMixModal" class="modal fade" tabindex="-1" role="dialog" style="display: none;" data-backdrop="static">
|
||
<form id="saveMixForm">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||
<h4 class="modal-title" ng-show="data.SaveMixHasBeenRequested">Save Mix</h4>
|
||
<h4 class="modal-title" ng-show="data.ActivateMixHasBeenRequested">Activate Mix</h4>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="panel-body form-horizontal" ng-show="data.ShowSaveForm">
|
||
<div class="row">
|
||
<div class="col-sm-12">
|
||
<div class="form-group select2-primary no-margin-hr">
|
||
<label class="control-label">Mix Name</label>
|
||
<input type="text" id="Name" name="Name" class="form-control"
|
||
data-val="true" data-val-required="The Name field is required."
|
||
data-val-maxlength="The field Name must be a string or array type with a maximum length of '64'."
|
||
data-val-maxlength-max="64"
|
||
ng-model="data.Name"
|
||
ng-disabled="!data.IsEditable" />
|
||
<span class="help-block field-validation-valid" data-valmsg-for="Name" data-valmsg-replace="true"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
@Html.ValidationSummary(false, "The Mix could not be saved due to the following errors:")
|
||
</div>
|
||
<div class="panel-body form-horizontal" ng-show="!data.ShowSaveForm">
|
||
<div class="row">
|
||
Are you sure you want to activate this mix? This action cannot be undone.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-success" ng-show="data.SaveMixHasBeenRequested" ng-disabled="data.DisableScreen" ng-click="saveChanges()">
|
||
<i class="fa fa-save"></i> Save
|
||
</button>
|
||
<button type="button" class="btn btn-warning" ng-show="data.ActivateMixHasBeenRequested" ng-disabled="data.DisableScreen" ng-click="activateMix()">
|
||
<i class="fa fa-check-circle-o"></i> Activate
|
||
</button>
|
||
<button type="button" class="btn btn-default" data-dismiss="modal" ng-disabled="data.DisableScreen">Cancel</button>
|
||
</div>
|
||
</div>
|
||
<!-- / .modal-content -->
|
||
</div>
|
||
<!-- / .modal-dialog -->
|
||
</form>
|
||
</div>
|
||
<!-- /.modal -->
|
||
</div>
|
||
</fieldset>
|
||
<div>
|
||
@Html.Partial("_mixCalendar", json)
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="editTeam" class="modal fade" data-width="1070" tabindex="-1" role="dialog" data-backdrop="static">
|
||
<div class="modal-content" id="editReload">
|
||
</div>
|
||
<!-- / .modal-content -->
|
||
</div>
|
||
<div id="create-scenario-dialog" class="modal fade" data-width="900" tabindex="-1" role="dialog" data-backdrop="static">
|
||
<div class="modal-content" id="create-scenario-dialog-content">
|
||
</div>
|
||
<!-- / .modal-content -->
|
||
</div>
|
||
<!-- /.modal -->
|