new attributes are editable, translation added
This commit is contained in:
parent
8c4e908c58
commit
1c8614efbe
@ -1,6 +1,6 @@
|
|||||||
# redmine_issue_dynamic_edit
|
# redmine_issue_dynamic_edit
|
||||||
|
|
||||||
Add new dropdown elements on detailed issue page to dynamically update issue's status, assignee and priority fields, directly in the details block of the issue.
|
Add new dropdown elements on detailed issue page to dynamically update issue's status, assignee, priority, start and due dates, ratio and estimated time fields, directly in the details block of the issue.
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
@ -18,5 +18,6 @@ This plugin uses [FontAwesome icons](http://fontawesome.io/)
|
|||||||
|
|
||||||
### Changelog
|
### Changelog
|
||||||
|
|
||||||
|
* **v 0.3.0** : start date, due date, ratio and estimated time fields are now dynamically editable. Translation files added (en, fr). Log added in console when AJAX fails
|
||||||
* **v 0.2.0** : fixed "conflict" when trying to add a note after an update from dropdowns. New method used, REST API is not required anymore
|
* **v 0.2.0** : fixed "conflict" when trying to add a note after an update from dropdowns. New method used, REST API is not required anymore
|
||||||
* **v 0.1.0** : initial commit
|
* **v 0.1.0** : initial commit
|
||||||
@ -12,6 +12,11 @@ var cssId = 'fontAwesome';
|
|||||||
head.appendChild(link);
|
head.appendChild(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Update height of progress bar because of dropdown */
|
||||||
|
var dynamicEditSelectHeight = $('.dynamicEditSelect').height();
|
||||||
|
var progressBarHeight = $('table.progress').height();
|
||||||
|
$('table.progress').css({'marginTop' : (dynamicEditSelectHeight - progressBarHeight)/2 + "px" });
|
||||||
|
|
||||||
/* Put new dropdown lists in the detailed info block */
|
/* Put new dropdown lists in the detailed info block */
|
||||||
if($('#statusListDropdown').length > 0) {
|
if($('#statusListDropdown').length > 0) {
|
||||||
var htmlCopy = $('#statusListDropdown').get(0).outerHTML;
|
var htmlCopy = $('#statusListDropdown').get(0).outerHTML;
|
||||||
@ -31,12 +36,63 @@ if($('#prioritiesListDropdown').length > 0) {
|
|||||||
$('.details .attributes .priority.attribute .value').html(htmlCopy);
|
$('.details .attributes .priority.attribute .value').html(htmlCopy);
|
||||||
}
|
}
|
||||||
|
|
||||||
function issueDynamicUpdate(field_name, field_value, cssClass){
|
if($('#doneRatioListDropdown').length > 0) {
|
||||||
|
var htmlCopy = $('#doneRatioListDropdown').get(0).outerHTML;
|
||||||
|
$('#doneRatioListDropdown').remove();
|
||||||
|
$('.details .attributes .progress.attribute .percent').html(htmlCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($('#EstimatedTimeInput').length > 0) {
|
||||||
|
var htmlCopy = $('#EstimatedTimeInput').get(0).outerHTML;
|
||||||
|
$('#EstimatedTimeInput').remove();
|
||||||
|
$('.details .attributes .estimated-hours.attribute .value').html(htmlCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($('#StartDateInput').length > 0) {
|
||||||
|
var htmlCopy = $('#StartDateInput').get(0).outerHTML;
|
||||||
|
$('#StartDateInput').remove();
|
||||||
|
$('.details .attributes .start-date.attribute .value').html(htmlCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($('#DueDateInput').length > 0) {
|
||||||
|
var htmlCopy = $('#DueDateInput').get(0).outerHTML;
|
||||||
|
$('#DueDateInput').remove();
|
||||||
|
$('.details .attributes .due-date.attribute .value').html(htmlCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
function issueDynamicUpdate(field_name, field_value, type, cssClass){
|
||||||
|
|
||||||
|
/* add spin notification */
|
||||||
|
if(type == "progress") { // specific case for progress bar
|
||||||
|
$('.details .attributes .' + cssClass + '.attribute .percent').append(' <i class="fa fa-refresh fa-spin fa-fw"></i>');
|
||||||
|
} else {
|
||||||
$('.details .attributes .' + cssClass + '.attribute .value').append(' <i class="fa fa-refresh fa-spin fa-fw"></i>');
|
$('.details .attributes .' + cssClass + '.attribute .value').append(' <i class="fa fa-refresh fa-spin fa-fw"></i>');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* update value displayed to "move" edit pen icon */
|
||||||
|
$('.details .attributes .' + cssClass + '.attribute .selectedValue span').html(function(){
|
||||||
|
if(type == "select")
|
||||||
|
{
|
||||||
|
return $('.details .attributes .' + cssClass + '.attribute .value select option:selected').html()
|
||||||
|
} else if (type == "input")
|
||||||
|
{
|
||||||
|
return $('.details .attributes .' + cssClass + '.attribute .value input').val()
|
||||||
|
} else if(type == "date")
|
||||||
|
{
|
||||||
|
return "XXXX/XX/XX";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* lost focus on element */
|
||||||
|
if( type != "select")
|
||||||
|
{
|
||||||
|
$('.details .attributes .' + cssClass + '.attribute .value input').blur();
|
||||||
|
}
|
||||||
|
|
||||||
var token = $("meta[name=csrf-token]").attr('content');
|
var token = $("meta[name=csrf-token]").attr('content');
|
||||||
jQuery.ajax({
|
jQuery.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
url: '/issues/bulk_update?back_url=%2Fissues&ids%5B%5D=' + _ISSUE_ID + '&issue%5B' + field_name + '%5D=' + field_value,
|
url: '/issues/bulk_update?back_url=%2F&ids%5B%5D=' + _ISSUE_ID + '&issue%5B' + field_name + '%5D=' + field_value,
|
||||||
data: { "authenticity_token" : token },
|
data: { "authenticity_token" : token },
|
||||||
crossDomain: true,
|
crossDomain: true,
|
||||||
async: false,
|
async: false,
|
||||||
@ -44,38 +100,145 @@ function issueDynamicUpdate(field_name, field_value, cssClass){
|
|||||||
xhr.setRequestHeader("authenticity_token", token);
|
xhr.setRequestHeader("authenticity_token", token);
|
||||||
},
|
},
|
||||||
success: function(msg) {
|
success: function(msg) {
|
||||||
|
/* data updated, remove spin and add success icon for 2sec */
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
$('.details .attributes .' + cssClass + '.attribute .value .selectedValue span').html(
|
$('.details .attributes .' + cssClass + '.attribute i.fa-spin').removeClass('fa-refresh fa-spin').addClass('fa-check statusOk');
|
||||||
$('.details .attributes .' + cssClass + '.attribute .value select option:selected').html()
|
|
||||||
);
|
|
||||||
$('.details .attributes .' + cssClass + '.attribute .value i.fa-spin').remove();
|
|
||||||
$('.details .attributes .' + cssClass + '.attribute .value').append(' <i class="fa fa-check"></i>');
|
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
$('.details .attributes .' + cssClass + '.attribute .value i.fa-check').remove();
|
$('.details .attributes .' + cssClass + '.attribute i.fa-check.statusOk').remove();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
|
if(type == "progress") { // specific case for progress bar, we need to update the progress bar view
|
||||||
|
var progressBar = "<tbody><tr>";
|
||||||
|
var percentTodo = 100 - parseInt(field_value);
|
||||||
|
progressBar += "<td style='width: " + field_value + "%;' class='closed' title='" + field_value + "%'></td>";
|
||||||
|
progressBar += "<td style='width: " + percentTodo + "%;' class='todo'></td>";
|
||||||
|
progressBar += "</tr></tbody>";
|
||||||
|
$('.details .attributes .' + cssClass + '.attribute table.progress').attr('class', 'progress progress-' + field_value).html(progressBar);
|
||||||
|
} else if( type == "date") { // specific case for start date and due date, we have to update min and max date allowed
|
||||||
|
if(field_name == "start_date")
|
||||||
|
{
|
||||||
|
$('body').find('#DueDateInput input').attr('min', field_value);
|
||||||
|
} else if (field_name == "due_date")
|
||||||
|
{
|
||||||
|
$('body').find('#StartDateInput input').attr('max', field_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// update other fields to avoid conflict
|
// update other fields to avoid conflict
|
||||||
$('#issue_lock_version').val(parseInt($('#issue_lock_version').val()) + 1 );
|
$('#issue_lock_version').val(parseInt($('#issue_lock_version').val()) + 1 );
|
||||||
$('#last_journal_id').val(parseInt($('#last_journal_id').val()) + 1 );
|
$('#last_journal_id').val(parseInt($('#last_journal_id').val()) + 1 );
|
||||||
|
if(type == "select")
|
||||||
|
{
|
||||||
$('#issue_' + field_name + ' option').removeAttr('selected').filter('[value=' + field_value + ']').prop('selected', true);
|
$('#issue_' + field_name + ' option').removeAttr('selected').filter('[value=' + field_value + ']').prop('selected', true);
|
||||||
|
} else if (type == "input" || type == "date")
|
||||||
|
{
|
||||||
|
$('#issue_' + field_name).val(field_value);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
error: function(xhr, msg, error) {}
|
error: function(xhr, msg, error) {
|
||||||
|
/* error and no update, info logged into console */
|
||||||
|
console.log('%c -------- Error while updating the issue attribute dynamically -------- ', 'background: #ff0000; color: white; font-weight:900');
|
||||||
|
console.log('%c xhr data: ', 'background: black; color: white;');
|
||||||
|
console.log(xhr);
|
||||||
|
console.log('%c msg data: ', 'background: black; color: white;');
|
||||||
|
console.log(msg);
|
||||||
|
console.log('%c error data: ', 'background: black; color: white;');;
|
||||||
|
console.log(error);
|
||||||
|
console.log('%c ---------------------------------------------------------- ', 'background: #ff0000; color: white; font-weight:900');
|
||||||
|
$('.details .attributes .' + cssClass + '.attribute i.fa-spin').removeClass('fa-refresh fa-spin').addClass('fa-times').html(" Error (check console)");
|
||||||
|
setTimeout(function(){
|
||||||
|
$('.details .attributes .' + cssClass + '.attribute i.fa-times').remove();
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Listeners foreach dropdown */
|
/* Listeners foreach attribute */
|
||||||
|
|
||||||
var domSelectStatus = $('body').find('#statusListDropdown select');
|
var domSelectStatus = $('body').find('#statusListDropdown select');
|
||||||
domSelectStatus.on('change', function(e){
|
domSelectStatus.on('change', function(e){
|
||||||
issueDynamicUpdate('status_id', domSelectStatus.val(), 'status');
|
issueDynamicUpdate('status_id', domSelectStatus.val(), 'select', 'status');
|
||||||
}); /* end on change domSelectStatus */
|
}); /* end on change domSelectStatus */
|
||||||
|
|
||||||
var domSelectPriorities = $('body').find('#prioritiesListDropdown select');
|
var domSelectPriorities = $('body').find('#prioritiesListDropdown select');
|
||||||
domSelectPriorities.on('change', function(e){
|
domSelectPriorities.on('change', function(e){
|
||||||
issueDynamicUpdate('priority_id', domSelectPriorities.val(), 'priority');
|
issueDynamicUpdate('priority_id', domSelectPriorities.val(), 'select', 'priority');
|
||||||
}); /* end on change domSelectPriorities */
|
}); /* end on change domSelectPriorities */
|
||||||
|
|
||||||
var domSelectUsers = $('body').find('#usersListDropdown select');
|
var domSelectUsers = $('body').find('#usersListDropdown select');
|
||||||
domSelectUsers.on('change', function(e){
|
domSelectUsers.on('change', function(e){
|
||||||
issueDynamicUpdate('assigned_to_id', domSelectUsers.val(), 'assigned-to');
|
issueDynamicUpdate('assigned_to_id', domSelectUsers.val(), 'select', 'assigned-to');
|
||||||
}); /* end on change domSelectUsers */
|
}); /* end on change domSelectUsers */
|
||||||
|
|
||||||
|
var domSelectRatio = $('body').find('#doneRatioListDropdown select');
|
||||||
|
domSelectRatio.on('change', function(e){
|
||||||
|
issueDynamicUpdate('done_ratio', domSelectRatio.val(), 'progress', 'progress');
|
||||||
|
}); /* end on change domSelectUsers */
|
||||||
|
|
||||||
|
var domInputEstimatedTime = $('body').find('#EstimatedTimeInput input');
|
||||||
|
$('#EstimatedTimeInput a.btn').on('click', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
$('.estimated-hours .value .error').remove();
|
||||||
|
var estimatedTime = parseFloat(domInputEstimatedTime.val());
|
||||||
|
if(estimatedTime >= 0)
|
||||||
|
{
|
||||||
|
issueDynamicUpdate('estimated_hours', estimatedTime, 'input', 'estimated-hours');
|
||||||
|
} else {
|
||||||
|
/* estimated time must be > 0 */
|
||||||
|
$('.estimated-hours .value').append('<span class="error"><i class="fa fa-exclamation-circle" aria-hidden="true"></i> ' + _TXT_ERROR_POSITIVE_NUMBER + '</span>');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
domInputEstimatedTime.on('keyup', function(e){
|
||||||
|
$('.details .attributes .estimated-hours.attribute .selectedValue span').html(
|
||||||
|
$('.details .attributes .estimated-hours.attribute .value input').val()
|
||||||
|
);
|
||||||
|
if (e.keyCode == 13) {
|
||||||
|
$('#EstimatedTimeInput a.btn').click();
|
||||||
|
}
|
||||||
|
});/* end EstimatedTime */
|
||||||
|
|
||||||
|
var domInputStartDate = $('body').find('#StartDateInput input');
|
||||||
|
$('#StartDateInput a.btn').on('click', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
$('.start-date .value .error').remove();
|
||||||
|
if(new Date(domInputStartDate.val()).getTime() <= new Date($('body').find('#DueDateInput input').val()).getTime())
|
||||||
|
{
|
||||||
|
issueDynamicUpdate('start_date', domInputStartDate.val(), 'date', 'start-date');
|
||||||
|
} else {
|
||||||
|
/* start date must be < due date */
|
||||||
|
$('.start-date .value').append('<span class="error"><i class="fa fa-exclamation-circle" aria-hidden="true"></i> ' + _TXT_ERROR_START_DATE + '</span>');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
domInputStartDate.on('keyup', function(e){
|
||||||
|
if (e.keyCode == 13) {
|
||||||
|
$('#StartDateInput a.btn').click();
|
||||||
|
}
|
||||||
|
});/* end StartDate */
|
||||||
|
|
||||||
|
var domInputDueDate = $('body').find('#DueDateInput input');
|
||||||
|
$('#DueDateInput a.btn').on('click', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
$('.due-date .value .error').remove();
|
||||||
|
if(new Date($('body').find('#StartDateInput input').val()).getTime() <= new Date(domInputDueDate.val()).getTime())
|
||||||
|
{
|
||||||
|
issueDynamicUpdate('due_date', domInputDueDate.val(), 'date', 'due-date');
|
||||||
|
} else {
|
||||||
|
/* start date must be < due date */
|
||||||
|
$('.due-date .value').append('<span class="error"><i class="fa fa-exclamation-circle" aria-hidden="true"></i> ' + _TXT_ERROR_DUE_DATE + '</span>');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
domInputDueDate.on('keyup', function(e){
|
||||||
|
if (e.keyCode == 13) {
|
||||||
|
$('#DueDateInput a.btn').click();
|
||||||
|
}
|
||||||
|
});/* end StartDate */
|
||||||
@ -2,7 +2,77 @@ div.issue .attribute .value {
|
|||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.value .dynamicEditSelect select {
|
/* Progress bar fix */
|
||||||
|
table.progress {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If you want to hide edit (pencil) icon, set opacity to 0 below : */
|
||||||
|
.selectedValue {
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
right: 0;
|
||||||
|
left: 5px;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 1; /* <---------------- */
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute:hover .selectedValue {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectedValue .transparent {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute .error {
|
||||||
|
color: #e74c3c;
|
||||||
|
margin-left: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input Style */
|
||||||
|
.dynamicEditInput {
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 4px;
|
||||||
|
margin-left: -5px;
|
||||||
|
padding-right: 0;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dynamicEditInput input {
|
||||||
|
border: 0px;
|
||||||
|
font: inherit;
|
||||||
|
color: inherit;
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dynamicEditInput .selectedValue {
|
||||||
|
top:0; left:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute:hover .dynamicEditInput {
|
||||||
|
border-color: #e0e2e3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute .btn-primary {
|
||||||
|
border-left: 1px solid #e0e2e3;
|
||||||
|
padding: 4px;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute:hover .btn-primary {
|
||||||
|
pointer-events: auto;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* End Input Style */
|
||||||
|
|
||||||
|
/* Select Style */
|
||||||
|
.dynamicEditSelect select {
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-bottom: 1px solid transparent;
|
border-bottom: 1px solid transparent;
|
||||||
@ -18,37 +88,20 @@ div.issue .attribute .value {
|
|||||||
padding-left:0;
|
padding-left:0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.value .dynamicEditSelect {
|
.dynamicEditSelect {
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
margin-left: -6px; /* padding + border */
|
margin-left: -6px; /* padding + border */
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
|
||||||
|
|
||||||
.value:hover .dynamicEditSelect {
|
|
||||||
border-color: #e0e2e3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value .dynamicEditSelect {
|
|
||||||
position:relative;
|
position:relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.value .dynamicEditSelect .selectedValue {
|
.attribute:hover .dynamicEditSelect {
|
||||||
position: absolute;
|
border-color: #e0e2e3;
|
||||||
top: 2px;
|
|
||||||
right: 0;
|
|
||||||
left: 5px;
|
|
||||||
background: white;
|
|
||||||
pointer-events: none;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.value:hover .dynamicEditSelect .selectedValue {
|
.dynamicEditSelect i.dropdown {
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value .dynamicEditSelect i.dropdown {
|
|
||||||
position:absolute;
|
position:absolute;
|
||||||
right: 5px;
|
right: 5px;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
@ -57,11 +110,12 @@ div.issue .attribute .value {
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.value:hover .dynamicEditSelect i.dropdown {
|
.attribute:hover .dynamicEditSelect i.dropdown {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.value:hover .dynamicEditSelect select option[disabled="disabled"]
|
.dynamicEditSelect select option[disabled="disabled"]
|
||||||
{
|
{
|
||||||
display:none;
|
display:none;
|
||||||
}
|
}
|
||||||
|
/* End Select Style */
|
||||||
5
config/locales/en.yml
Normal file
5
config/locales/en.yml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
en:
|
||||||
|
ide_txt_validation_btn : 'Validate the modification'
|
||||||
|
ide_txt_error_positive_number : 'It must be a positive number'
|
||||||
|
ide_txt_error_start_date : 'Start date must be anterior to due date'
|
||||||
|
ide_txt_error_due_date : 'Due date must be posterior to start date'
|
||||||
5
config/locales/fr.yml
Normal file
5
config/locales/fr.yml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
fr:
|
||||||
|
ide_txt_validation_btn : 'Valider la modification'
|
||||||
|
ide_txt_error_positive_number : 'Le nombre doit être positif'
|
||||||
|
ide_txt_error_start_date : "La date de début doit être antérieure à la date d'échéance"
|
||||||
|
ide_txt_error_due_date : "La date d'échéance doit être postérieure à la date de début"
|
||||||
4
init.rb
4
init.rb
@ -5,8 +5,8 @@ require 'details_issue_hooks'
|
|||||||
Redmine::Plugin.register :redmine_issue_dynamic_edit do
|
Redmine::Plugin.register :redmine_issue_dynamic_edit do
|
||||||
name 'Redmine Dynamic edit Issue plugin'
|
name 'Redmine Dynamic edit Issue plugin'
|
||||||
author 'Hugo Zilliox'
|
author 'Hugo Zilliox'
|
||||||
description 'Allows users to dynamically update issue\'s status, assignee and priority in detailed view using REST API'
|
description 'Allows users to dynamically update issue attributes in detailed view'
|
||||||
version '0.2.0'
|
version '0.3.0'
|
||||||
url 'https://github.com/ilogeek/redmine_issue_dynamic_edit'
|
url 'https://github.com/ilogeek/redmine_issue_dynamic_edit'
|
||||||
author_url 'https://hzilliox.fr'
|
author_url 'https://hzilliox.fr'
|
||||||
end
|
end
|
||||||
|
|||||||
@ -23,10 +23,14 @@ class DetailsIssueHooks < Redmine::Hook::ViewListener
|
|||||||
if (issue)
|
if (issue)
|
||||||
if (User.current.allowed_to?(:edit_issues, project))
|
if (User.current.allowed_to?(:edit_issues, project))
|
||||||
o = ''
|
o = ''
|
||||||
|
# o << issue.to_json
|
||||||
|
|
||||||
|
# Status dropdown
|
||||||
|
o << userCanChangeStatus.to_json
|
||||||
statuses = issue.new_statuses_allowed_to(User.current)
|
statuses = issue.new_statuses_allowed_to(User.current)
|
||||||
if (!statuses.empty?)
|
if (userCanChangeStatus && !statuses.empty?)
|
||||||
o << "<span class='dynamicEditSelect' id='statusListDropdown'>"
|
o << "<span class='dynamicEditSelect' id='statusListDropdown'>"
|
||||||
o << "<div class='selectedValue'><span>#{issue.status}</span> <i class=\"fa fa-pencil fa-fw\" aria-hidden=\"true\"></i></div> "
|
o << "<div class='selectedValue'><span class='transparent'>#{issue.status}</span> <i class=\"fa fa-pencil fa-fw\" aria-hidden=\"true\"></i></div> "
|
||||||
o << "<select data-issue='#{issue_id}'><option disabled='disabled' selected> </option>"
|
o << "<select data-issue='#{issue_id}'><option disabled='disabled' selected> </option>"
|
||||||
statuses.each do |s|
|
statuses.each do |s|
|
||||||
if (s != issue.status)
|
if (s != issue.status)
|
||||||
@ -37,10 +41,13 @@ class DetailsIssueHooks < Redmine::Hook::ViewListener
|
|||||||
end
|
end
|
||||||
o << "</select><i class=\"fa fa-angle-down fa-fw dropdown\" aria-hidden=\"true\"></i></span>"
|
o << "</select><i class=\"fa fa-angle-down fa-fw dropdown\" aria-hidden=\"true\"></i></span>"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Users dropdown
|
||||||
|
# userCanChangeAssignee = User.current.allowed_to?(:edit_assigned_to, @project, :global => true)
|
||||||
assignables = project.assignable_users
|
assignables = project.assignable_users
|
||||||
if (!assignables.empty?)
|
if (!assignables.empty?)
|
||||||
o << "<span class='dynamicEditSelect' id='usersListDropdown'>"
|
o << "<span class='dynamicEditSelect' id='usersListDropdown'>"
|
||||||
o << "<div class='selectedValue'><span>#{issue.assigned_to}</span> <i class=\"fa fa-pencil fa-fw\" aria-hidden=\"true\"></i></div> "
|
o << "<div class='selectedValue'><span class='transparent'>#{issue.assigned_to}</span> <i class=\"fa fa-pencil fa-fw\" aria-hidden=\"true\"></i></div> "
|
||||||
o << "<select data-issue='#{issue_id}'><option disabled='disabled' selected> </option>"
|
o << "<select data-issue='#{issue_id}'><option disabled='disabled' selected> </option>"
|
||||||
assignables.each do |u|
|
assignables.each do |u|
|
||||||
if (u != issue.assigned_to)
|
if (u != issue.assigned_to)
|
||||||
@ -52,10 +59,11 @@ class DetailsIssueHooks < Redmine::Hook::ViewListener
|
|||||||
o << "</select><i class=\"fa fa-angle-down fa-fw dropdown\" aria-hidden=\"true\"></i></span>"
|
o << "</select><i class=\"fa fa-angle-down fa-fw dropdown\" aria-hidden=\"true\"></i></span>"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Priorities dropdown
|
||||||
priorities = IssuePriority.all
|
priorities = IssuePriority.all
|
||||||
if(!priorities.empty?)
|
if(!priorities.empty?)
|
||||||
o << "<span class='dynamicEditSelect' id='prioritiesListDropdown'>"
|
o << "<span class='dynamicEditSelect' id='prioritiesListDropdown'>"
|
||||||
o << "<div class='selectedValue'><span>#{issue.priority}</span> <i class=\"fa fa-pencil fa-fw\" aria-hidden=\"true\"></i></div> "
|
o << "<div class='selectedValue'><span class='transparent'>#{issue.priority}</span> <i class=\"fa fa-pencil fa-fw\" aria-hidden=\"true\"></i></div> "
|
||||||
o << "<select data-issue='#{issue_id}'><option disabled='disabled' selected> </option>"
|
o << "<select data-issue='#{issue_id}'><option disabled='disabled' selected> </option>"
|
||||||
priorities.each do |p|
|
priorities.each do |p|
|
||||||
if (p != issue.priority)
|
if (p != issue.priority)
|
||||||
@ -66,13 +74,66 @@ class DetailsIssueHooks < Redmine::Hook::ViewListener
|
|||||||
end
|
end
|
||||||
o << "</select><i class=\"fa fa-angle-down fa-fw dropdown\" aria-hidden=\"true\"></i></span>"
|
o << "</select><i class=\"fa fa-angle-down fa-fw dropdown\" aria-hidden=\"true\"></i></span>"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# %done dropdown
|
||||||
|
percent = 0
|
||||||
|
o << "<span class='dynamicEditSelect' id='doneRatioListDropdown'>"
|
||||||
|
o << "<div class='selectedValue'><span class='transparent'>#{issue.done_ratio}%</span> <i class=\"fa fa-pencil fa-fw\" aria-hidden=\"true\"></i></div> "
|
||||||
|
o << "<select data-issue='#{issue_id}'><option disabled='disabled' selected> </option>"
|
||||||
|
loop do
|
||||||
|
if (percent == issue.done_ratio)
|
||||||
|
o << "<option value='#{percent}' selected>#{percent}%</option>"
|
||||||
|
else
|
||||||
|
o << "<option value='#{percent}'>#{percent}%</option>"
|
||||||
|
end
|
||||||
|
percent += 10
|
||||||
|
if percent == 110
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
o << "</select><i class=\"fa fa-angle-down fa-fw dropdown\" aria-hidden=\"true\"></i></span>"
|
||||||
|
|
||||||
|
# Estimated_time dropdown
|
||||||
|
o << "<span class='dynamicEditInput' id='EstimatedTimeInput'>"
|
||||||
|
o << " <div class='selectedValue'><span class='transparent'>#{issue.estimated_hours}</span> <i class=\"fa fa-pencil fa-fw\" aria-hidden=\"true\"></i></div> "
|
||||||
|
o << " <input type='text' value='#{issue.estimated_hours}' size='6'/>"
|
||||||
|
o << "<a href='#' class='btn btn-primary' aria-label='" + l(:ide_txt_validation_btn) + "'><i class='fa fa-check fa-fw' aria-hidden='true'></i></a>"
|
||||||
|
o << "</span>"
|
||||||
|
|
||||||
|
# Start date
|
||||||
|
o << "<span class='dynamicEditInput' id='StartDateInput'>"
|
||||||
|
o << " <div class='selectedValue'><span class='transparent'>XXXX/XX/XX</span> <i class=\"fa fa-pencil fa-fw\" aria-hidden=\"true\"></i></div> "
|
||||||
|
o << " <input size=\"10\" value=\"#{issue.start_date}\" type=\"date\" max=\"#{issue.due_date}\">"
|
||||||
|
o << "<a href='#' class='btn btn-primary' aria-label='" + l(:ide_txt_validation_btn) + "'><i class='fa fa-check fa-fw' aria-hidden='true'></i></a>"
|
||||||
|
o << "</span>"
|
||||||
|
o << "<script>"
|
||||||
|
o << "//<![CDATA[\n"
|
||||||
|
o << " $(function() { $('#StartDateInput input').addClass('date').datepickerFallback(datepickerOptions); });\n"
|
||||||
|
o << "//]]>\n"
|
||||||
|
o << "</script>"
|
||||||
|
|
||||||
|
# Due date
|
||||||
|
o << "<span class='dynamicEditInput' id='DueDateInput'>"
|
||||||
|
o << " <div class='selectedValue'><span class='transparent'>XXXX/XX/XX</span> <i class=\"fa fa-pencil fa-fw\" aria-hidden=\"true\"></i></div> "
|
||||||
|
o << " <input size=\"10\" value=\"#{issue.due_date}\" type=\"date\" min=\"#{issue.start_date}\">"
|
||||||
|
o << "<a href='#' class='btn btn-primary' aria-label='" + l(:ide_txt_validation_btn) + "'><i class='fa fa-check fa-fw' aria-hidden='true'></i></a>"
|
||||||
|
o << "</span>"
|
||||||
|
o << "<script>"
|
||||||
|
o << "//<![CDATA[\n"
|
||||||
|
o << " $(function() { $('#DueDateInput input').addClass('date').datepickerFallback(datepickerOptions); });\n"
|
||||||
|
o << "//]]>\n"
|
||||||
|
o << "</script>"
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
o << "<script>"
|
o << "<script>"
|
||||||
|
|
||||||
o << " var _ISSUE_ID = \"#{issue_id}\";"
|
o << " var _ISSUE_ID = \"#{issue_id}\";\n"
|
||||||
o << " var _USER_API_KEY = \"#{User.current.api_key}\";"
|
o << " var _USER_API_KEY = \"#{User.current.api_key}\";\n"
|
||||||
|
o << " var _TXT_ERROR_POSITIVE_NUMBER = \"" + l(:ide_txt_error_positive_number) + "\";\n"
|
||||||
|
o << " var _TXT_ERROR_START_DATE = \"" + l(:ide_txt_error_start_date) + "\";\n"
|
||||||
|
o << " var _TXT_ERROR_DUE_DATE = \"" + l(:ide_txt_error_due_date) + "\";\n"
|
||||||
|
|
||||||
o << "</script>"
|
o << "</script>"
|
||||||
return o
|
return o
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user