svg
This commit is contained in:
@ -3,7 +3,7 @@
|
||||
<div class="mb-0 d-flex" style="align-items:baseline">
|
||||
<h5 style="flex-grow:1">#{{ issue.redmine.id }} – {{ issue.redmine.subject }}</h5>
|
||||
<a href="{{redmineUrl}}/issues/{{issue.id}}" target="_blank" class="btn btn-primary"><i class="fas fa-eye"></i></a>
|
||||
<div class="btn btn-secondary" onClick="$('.issueContainer').hide();$('.scrumContainer').css('padding-left','0px');"><i class="fas fa-window-close"></i></div>
|
||||
<div class="btn btn-secondary" onClick="hideIssue()"><i class="fas fa-window-close"></i></div>
|
||||
|
||||
</div>
|
||||
<small class="text-muted">Projet : {{ issue.redmine.project.name }} • Tracker : {{ issue.redmine.tracker.name }}</small>
|
||||
@ -70,6 +70,8 @@
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{{dump(issue.redmine)}}
|
||||
</div>
|
||||
<br><br>
|
||||
</div>
|
||||
|
@ -24,21 +24,24 @@
|
||||
padding:0px;
|
||||
}
|
||||
|
||||
.scrumContainer {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.issueContainer {
|
||||
position:fixed;
|
||||
width:750px;
|
||||
}
|
||||
|
||||
.filtreContainer{
|
||||
position:fixed;
|
||||
display:flex;
|
||||
width:300px;
|
||||
flex-direction:column;
|
||||
background-color: var(--bs-dark);
|
||||
padding:5px;
|
||||
z-index: 900;
|
||||
}
|
||||
|
||||
.scrumContainer {
|
||||
display:none;
|
||||
padding-left:300px;
|
||||
}
|
||||
|
||||
.containerStatus{
|
||||
@ -128,6 +131,19 @@
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); }
|
||||
25% { transform: scale(1.1); }
|
||||
50% { transform: scale(1); }
|
||||
75% { transform: scale(1.1); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
|
||||
.pulse-highlight {
|
||||
animation: pulse 1.5s ease-in-out 1;
|
||||
z-index: 1000;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
</style>
|
||||
{% endblock %}
|
||||
@ -137,55 +153,61 @@
|
||||
<div class='issueContainer'>
|
||||
</div>
|
||||
|
||||
<div class='filtreContainer'>
|
||||
<label>Issue</label>
|
||||
<input type="number" id="issueSearchInput" class="form-control" placeholder="Rechercher une issue" />
|
||||
|
||||
<label>Statut</label>
|
||||
<select id="statusFilter" class="select2 form-select" multiple="true" tabindex="-1" aria-hidden="true">
|
||||
{% for statut in project.redmine.issue_statuses %}
|
||||
{% if statut.id not in project.hiddenstatuses %}
|
||||
<option value="{{statut.id}}">{{statut.name}}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label>Sprints</label>
|
||||
<select id="sprintFilter" class="select2 form-select" multiple="true" tabindex="-1" aria-hidden="true">
|
||||
<option value="None">Aucun</option>
|
||||
{% for sprint in project.redmine.sprints|reverse %}
|
||||
{% if sprint.id not in project.hiddensprints %}
|
||||
<option value="{{sprint.id}}">{{sprint.name}}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label>Versions</label>
|
||||
<select id="versionFilter" class="select2 form-select" multiple="true" tabindex="-1" aria-hidden="true">
|
||||
<option value="None">Aucune</option>
|
||||
{% for version in project.redmine.versions|reverse %}
|
||||
{% if version.id not in project.hiddenversions %}
|
||||
<option value="{{version.id}}">{{version.name}}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label>Trackers</label>
|
||||
<select id="trackerFilter" class="select2 form-select" multiple="true" tabindex="-1" aria-hidden="true">
|
||||
{% for tracker in project.redmine.trackers %}
|
||||
<option value="tracker{{tracker.id}}">{{tracker.name}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label>Catégories</label>
|
||||
<select id="categoryFilter" class="select2 form-select" multiple="true" tabindex="-1" aria-hidden="true">
|
||||
{% for category in project.redmine.issue_categories %}
|
||||
<option value="category{{category.id}}">{{category.name}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label>Détail Issue</label>
|
||||
<select id="detailFilter" class="select2 form-select" tabindex="-1" aria-hidden="true">
|
||||
<option value="0">Non</option>
|
||||
<option value="1">Oui</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class='scrumContainer'>
|
||||
<div class='filtreContainer'>
|
||||
<label>Statut</label>
|
||||
<select id="statusFilter" class="select2 form-select" multiple="true" tabindex="-1" aria-hidden="true">
|
||||
{% for statut in project.redmine.issue_statuses %}
|
||||
{% if statut.id not in project.hiddenstatuses %}
|
||||
<option value="{{statut.id}}">{{statut.name}}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label>Sprints</label>
|
||||
<select id="sprintFilter" class="select2 form-select" multiple="true" tabindex="-1" aria-hidden="true">
|
||||
<option value="None">Aucun</option>
|
||||
{% for sprint in project.redmine.sprints|reverse %}
|
||||
{% if sprint.id not in project.hiddensprints %}
|
||||
<option value="{{sprint.id}}">{{sprint.name}}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label>Versions</label>
|
||||
<select id="versionFilter" class="select2 form-select" multiple="true" tabindex="-1" aria-hidden="true">
|
||||
<option value="None">Aucune</option>
|
||||
{% for version in project.redmine.versions|reverse %}
|
||||
{% if version.id not in project.hiddenversions %}
|
||||
<option value="{{version.id}}">{{version.name}}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label>Trackers</label>
|
||||
<select id="trackerFilter" class="select2 form-select" multiple="true" tabindex="-1" aria-hidden="true">
|
||||
{% for tracker in project.redmine.trackers %}
|
||||
<option value="tracker{{tracker.id}}">{{tracker.name}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label>Catégories</label>
|
||||
<select id="categoryFilter" class="select2 form-select" multiple="true" tabindex="-1" aria-hidden="true">
|
||||
{% for category in project.redmine.issue_categories %}
|
||||
<option value="category{{category.id}}">{{category.name}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
|
||||
<button id="toggleIssueBody" class="btn btn-secondary btn-sm mt-3">Afficher Détail</button>
|
||||
</div>
|
||||
|
||||
<div class='containerStatus'>
|
||||
{% for status in project.redmine.issue_statuses %}
|
||||
{% if status.id not in project.hiddenstatuses %}
|
||||
@ -250,7 +272,7 @@
|
||||
<div class='issueId'>#{{issue.id}}</div>
|
||||
<div class='issueSubject'>{{issue.redmine.subject}}</div>
|
||||
<div class='issueAction'>
|
||||
<i class='fas fa-eye' onClick='fetchAndRenderIssue({{issue.id}})'></i>
|
||||
<i class='fas fa-eye' onClick='viewIssue({{issue.id}})'></i>
|
||||
<verysmall>{{issue.redmine.sprint.story_points}}</verysmall>
|
||||
</div>
|
||||
</div>
|
||||
@ -267,99 +289,49 @@
|
||||
|
||||
{% block javascripts %}
|
||||
<script>
|
||||
let showIssuebody = false;
|
||||
|
||||
function adjustHeight() {
|
||||
let ele = $(".issueDescription");
|
||||
if (ele.length) {
|
||||
ele.css("height", window.innerHeight - ele.offset().top + "px");
|
||||
}
|
||||
}
|
||||
const projectId = '{{ project.id }}';
|
||||
const viewedIssueKey = `project_${projectId}_viewedIssue`;
|
||||
const filterIds = [
|
||||
'statusFilter',
|
||||
'sprintFilter',
|
||||
'versionFilter',
|
||||
'trackerFilter',
|
||||
'categoryFilter',
|
||||
'detailFilter'
|
||||
];
|
||||
|
||||
function showhide() {
|
||||
// Statut
|
||||
selected = $('#statusFilter').val();
|
||||
if (!selected || selected.length === 0) {
|
||||
$('.statusCard').show();
|
||||
}
|
||||
else {
|
||||
$('.statusCard').hide();
|
||||
selected.forEach(function (id) {
|
||||
$('.statusCard' + id).show();
|
||||
});
|
||||
}
|
||||
// Pulse
|
||||
function highlightAndScroll($element) {
|
||||
if (!$element || !$element.length) return;
|
||||
if (!$element.is(':visible')) return;
|
||||
|
||||
// Sprint
|
||||
selected = $('#sprintFilter').val();
|
||||
if (!selected || selected.length === 0) {
|
||||
$('.sprintCard').show();
|
||||
}
|
||||
else {
|
||||
$('.sprintCard').hide();
|
||||
selected.forEach(function (id) {
|
||||
$('.sprintCard' + id).show();
|
||||
});
|
||||
}
|
||||
|
||||
// Version
|
||||
selected = $('#versionFilter').val();
|
||||
if (!selected || selected.length === 0) {
|
||||
$('.versionCard').show();
|
||||
}
|
||||
else {
|
||||
$('.versionCard').hide();
|
||||
selected.forEach(function (id) {
|
||||
$('.versionCard' + id).show();
|
||||
});
|
||||
}
|
||||
|
||||
// Filtre issue
|
||||
$('.issueCard').hide();
|
||||
|
||||
const selectedTrackers = $('#trackerFilter').val() || [];
|
||||
const selectedCategories = $('#categoryFilter').val() || [];
|
||||
|
||||
//const selectedCategories = []; // ou ['category1'] etc.
|
||||
$('.issueCard').each(function () {
|
||||
const classes = $(this).attr('class').split(/\s+/);
|
||||
|
||||
const hasValidTracker =
|
||||
selectedTrackers.length === 0 ||
|
||||
selectedTrackers.some(tracker => classes.includes(tracker));
|
||||
|
||||
const hasValidCategory =
|
||||
selectedCategories.length === 0 ||
|
||||
selectedCategories.some(category => classes.includes(category));
|
||||
|
||||
if(hasValidTracker&&hasValidCategory) $(this).show();
|
||||
// Scroll vertical (retourne une promesse)
|
||||
const scrollTopPromise = new Promise(resolve => {
|
||||
$('html, body').animate({
|
||||
scrollTop: $element.offset().top - 100
|
||||
}, 500, resolve);
|
||||
});
|
||||
|
||||
// Tracker
|
||||
selected = $('#versionFilter').val();
|
||||
if (!selected || selected.length === 0) {
|
||||
$('.versionCard').show();
|
||||
}
|
||||
else {
|
||||
$('.versionCard').hide();
|
||||
selected.forEach(function (id) {
|
||||
$('.versionCard' + id).show();
|
||||
});
|
||||
}
|
||||
// Scroll horizontal (retourne une promesse)
|
||||
const scrollLeftPromise = new Promise(resolve => {
|
||||
$('html, body').animate({
|
||||
scrollLeft: $element.offset().left - 315
|
||||
}, 500, resolve);
|
||||
});
|
||||
|
||||
// Attendre que les deux scrolls soient terminés
|
||||
Promise.all([scrollTopPromise, scrollLeftPromise]).then(() => {
|
||||
$element.addClass('pulse-highlight');
|
||||
|
||||
setTimeout(() => {
|
||||
$element.removeClass('pulse-highlight');
|
||||
}, 3000); // Ajuster selon la durée de l'animation CSS
|
||||
});
|
||||
}
|
||||
|
||||
function toogleIssueBody() {
|
||||
if (showIssuebody) {
|
||||
$('.issueBody').show();
|
||||
$(this).text('Masquer Détail');
|
||||
} else {
|
||||
$('.issueBody').hide();
|
||||
$(this).text('Afficher Détail');
|
||||
}
|
||||
showIssuebody=!showIssuebody;
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
// Ranger les issues
|
||||
|
||||
|
||||
// Ranger les issues dans status / sprint / version
|
||||
$(function () {
|
||||
$('.issueCard').each(function () {
|
||||
const id = $(this).data('status')+'|'+$(this).data('sprint')+'|'+$(this).data('version');
|
||||
const $column = $(`[data-id='${id}']`);
|
||||
@ -371,28 +343,52 @@
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Afficher le scrum après rangement
|
||||
$('.scrumContainer').css('display', 'flex');
|
||||
|
||||
// Filtre
|
||||
$('#statusFilter').on('change',showhide);
|
||||
$('#sprintFilter').on('change',showhide);
|
||||
$('#versionFilter').on('change',showhide);
|
||||
$('#trackerFilter').on('change',showhide);
|
||||
$('#categoryFilter').on('change',showhide);
|
||||
showhide();
|
||||
|
||||
// issueBody
|
||||
$('#toggleIssueBody').on('click',toogleIssueBody);
|
||||
toogleIssueBody();
|
||||
|
||||
// Ajuste height
|
||||
adjustHeight();
|
||||
window.addEventListener("resize", adjustHeight);
|
||||
});
|
||||
|
||||
function fetchAndRenderIssue(issueId) {
|
||||
// Ajuster les dom en fonction de la window
|
||||
function adjustHeight() {
|
||||
console.log('adjustHeight');
|
||||
|
||||
let ele = document.querySelector(".issueDescription");
|
||||
if (ele) {
|
||||
const top = ele.getBoundingClientRect().top;
|
||||
ele.style.height = (window.innerHeight - top) + "px";
|
||||
}
|
||||
|
||||
ele = document.querySelector(".filtreContainer");
|
||||
if (ele) {
|
||||
const top = ele.getBoundingClientRect().top;
|
||||
ele.style.height = (window.innerHeight - top) + "px";
|
||||
}
|
||||
}
|
||||
window.addEventListener("resize", adjustHeight);
|
||||
window.addEventListener("load", function () {
|
||||
adjustHeight();
|
||||
});
|
||||
|
||||
// Recherche Issue
|
||||
$(function () {
|
||||
$('#issueSearchInput').on('input', function () {
|
||||
const value = $(this).val().trim();
|
||||
if (!value || isNaN(value)) return;
|
||||
|
||||
const $target = $(`.issueCard[data-id="${value}"]`);
|
||||
|
||||
if ($target.length > 0) {
|
||||
highlightAndScroll($target);
|
||||
} else {
|
||||
// Non trouvé → optionnel
|
||||
// hideIssue();
|
||||
console.warn('Issue non trouvée');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Affichage Issue
|
||||
function viewIssue(issueId) {
|
||||
localStorage.setItem(viewedIssueKey, issueId);
|
||||
|
||||
url='{{path('app_issue_view',{id:'xxx'})}}';
|
||||
url=url.replace('xxx',issueId);
|
||||
console.log(url);
|
||||
@ -416,7 +412,117 @@
|
||||
});
|
||||
}
|
||||
|
||||
function hideIssue() {
|
||||
localStorage.removeItem(viewedIssueKey);
|
||||
|
||||
$('.issueContainer').hide();
|
||||
$('.scrumContainer').css('padding-left','0px');
|
||||
}
|
||||
|
||||
// Filtre sur les issues et backup des filtres en localstorage
|
||||
$(function () {
|
||||
// Fonction pour construire une clé unique par projet
|
||||
const getKey = (id) => `project_${projectId}_${id}`;
|
||||
|
||||
// Initialiser les sélections depuis localStorage
|
||||
filterIds.forEach(id => {
|
||||
const saved = localStorage.getItem(getKey(id));
|
||||
if (saved) {
|
||||
const values = JSON.parse(saved);
|
||||
$(`#${id}`).val(values).trigger('change');
|
||||
}
|
||||
});
|
||||
|
||||
// Afficher / Cacher les issues
|
||||
function showhide() {
|
||||
// Backup des filtres en localstorage
|
||||
filterIds.forEach(id => {
|
||||
const selected = $(`#${id}`).val() || [];
|
||||
localStorage.setItem(getKey(id), JSON.stringify(selected));
|
||||
});
|
||||
|
||||
// Statut
|
||||
selected = $('#statusFilter').val();
|
||||
if (!selected || selected.length === 0) {
|
||||
$('.statusCard').show();
|
||||
}
|
||||
else {
|
||||
$('.statusCard').hide();
|
||||
selected.forEach(function (id) {
|
||||
$('.statusCard' + id).show();
|
||||
});
|
||||
}
|
||||
|
||||
// Sprint
|
||||
selected = $('#sprintFilter').val();
|
||||
if (!selected || selected.length === 0) {
|
||||
$('.sprintCard').show();
|
||||
}
|
||||
else {
|
||||
$('.sprintCard').hide();
|
||||
selected.forEach(function (id) {
|
||||
$('.sprintCard' + id).show();
|
||||
});
|
||||
}
|
||||
|
||||
// Version
|
||||
selected = $('#versionFilter').val();
|
||||
if (!selected || selected.length === 0) {
|
||||
$('.versionCard').show();
|
||||
}
|
||||
else {
|
||||
$('.versionCard').hide();
|
||||
selected.forEach(function (id) {
|
||||
$('.versionCard' + id).show();
|
||||
});
|
||||
}
|
||||
|
||||
// Filtre issue
|
||||
$('.issueCard').hide();
|
||||
|
||||
const selectedTrackers = $('#trackerFilter').val() || [];
|
||||
const selectedCategories = $('#categoryFilter').val() || [];
|
||||
|
||||
//const selectedCategories = []; // ou ['category1'] etc.
|
||||
$('.issueCard').each(function () {
|
||||
const classes = $(this).attr('class').split(/\s+/);
|
||||
|
||||
const hasValidTracker =
|
||||
selectedTrackers.length === 0 ||
|
||||
selectedTrackers.some(tracker => classes.includes(tracker));
|
||||
|
||||
const hasValidCategory =
|
||||
selectedCategories.length === 0 ||
|
||||
selectedCategories.some(category => classes.includes(category));
|
||||
|
||||
if(hasValidTracker&&hasValidCategory) $(this).show();
|
||||
});
|
||||
|
||||
// Filtre detail
|
||||
selected = $('#detailFilter').val();
|
||||
if(selected=="0")
|
||||
$('.issueBody').hide();
|
||||
else
|
||||
$('.issueBody').show();
|
||||
|
||||
// Afficher l'issue
|
||||
const savedIssueId = localStorage.getItem(viewedIssueKey);
|
||||
if (savedIssueId) {
|
||||
viewIssue(savedIssueId);
|
||||
}
|
||||
}
|
||||
|
||||
// Écouteurs sur tous les filtres
|
||||
filterIds.forEach(id => {
|
||||
$(`#${id}`).on('change', showhide);
|
||||
});
|
||||
|
||||
// Lancer une première fois après initialisation
|
||||
showhide();
|
||||
});
|
||||
|
||||
|
||||
// Déplacement des issues
|
||||
$(function () {
|
||||
let $sourceContainer = null;
|
||||
let $movedItem = null;
|
||||
|
Reference in New Issue
Block a user