Files
ninemine/templates/project/view.html.twig
2025-07-07 21:48:40 +02:00

480 lines
16 KiB
Twig
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends 'base.html.twig' %}
{% block localstyle %}
<style>
small {
font-size:70%;
}
verysmall {
font-size:60%;
}
.navbar {
width: 100%;
position: fixed;
z-index: 1000;
}
main {
padding-top:80px;
}
content {
padding:0px;
}
.scrumContainer {
display:none;
}
.issueContainer {
position:fixed;
width:750px;
}
.filtreContainer{
display:flex;
width:300px;
flex-direction:column;
background-color: var(--bs-dark);
padding:5px;
}
.containerStatus{
display:flex;
//width:10000px;
overflow-x:scroll;
}
.statusCard {
width:300px;
padding: 15px 10px 0px 10px;
}
h2 {
font-size:18px;
text-align: center;
}
.card {
margin-bottom:5px;
}
.sprintCardHeader {
display: flex;
}
.versionCard {
background-color: var(--bs-gray-100);
padding: 5px 5px 0px 5px;
}
.versionBody {
min-height:60px;
}
.versionCard h5 {
color: var(--bs-primary-text-emphasis);
font-size:16px;
padding-left:10px;
}
.issueCard {
padding:5px;
}
.issueHeader {
display: flex;
align-items: center;
}
.issueId{
padding: 0px 5px 0px 2px;
cursor: move;
}
.issueAction{
align-self: baseline;
padding: 0px 2px 0px 5px;
text-align:center;
display: flex;
flex-direction: column;
}
.fa-eye {
cursor: pointer;
}
.issueSubject{
zoom: 70%;
flex-grow: 1;
}
</style>
{% endblock %}
{% block body %}
<div class='issueContainer'>
</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 %}
<div class='statusCard statusCard{{status.id}}'>
<h2>{{ status.name }}</h2>
{% for sprint in project.redmine.sprints|reverse %}
{% if sprint.id not in project.hiddensprints %}
<div class='sprintCard sprintCard{{sprint.id}} card' style='margin-bottom:20px'>
<div class='sprintCardHeader card-header'>
<div style='flex-grow:1'>Sprint = {{ sprint.name }}</div>
{% if sprint.story_points[status.id] is defined %}
<div>{{ sprint.story_points[status.id] }}</div>
{% endif %}
</div>
{% for version in project.redmine.versions|reverse %}
{% if version.id not in project.hiddenversions %}
<div class='versionCard versionCard{{version.id}} card-body'>
<h5>Version = {{ version.name }}</h5>
<div class='versionBody' data-id='{{status.id}}|{{sprint.id}}|{{version.id}}'></div>
</div>
{% endif %}
{% endfor %}
<div class='versionCard versionCardNone card-body'>
<h5>Version = Aucune</h5>
<div class='versionBody' data-id='{{status.id}}|{{sprint.id}}|'></div>
</div>
</div>
{% endif %}
{% endfor %}
<div class='sprintCard sprintCardNone card'>
<div class='card-header'>
Sprint Aucun
</div>
{% for version in project.redmine.versions|reverse %}
{% if version.id not in project.hiddenversions %}
<div class='versionCard versionCard{{version.id}} card-body'>
<h5>Version = {{ version.name }}</h5>
<div class='versionBody' data-id='{{status.id}}||{{version.id}}'></div>
</div>
{% endif %}
{% endfor %}
<div class='versionCard versionCardNone card-body'>
<h5>Version = Aucune</h5>
<div class='versionBody' data-id='{{status.id}}||'></div>
</div>
</div>
</div>
{% endif %}
{% endfor %}
</div>
{% for issue in project.issues %}
<div class="issueCard card tracker{{issue.redmine.tracker.id}} category{{(issue.redmine.category is defined?issue.redmine.category.id:'0') }}" data-status='{{issue.redmine.status.id}}' data-sprint='{{issue.rowsprint}}' data-version='{{(issue.redmine.fixed_version is defined?issue.redmine.fixed_version.id:'')}}' data-id='{{issue.id}}'>
<div class='issueHeader'>
<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>
<verysmall>{{issue.redmine.sprint.story_points}}</verysmall>
</div>
</div>
<div class='issueBody'>
sprint = {{issue.rowsprint}}<br>
version = {{issue.rowversion}}<br>
status = {{issue.redmine.status.name}}
</div>
</div>
{% endfor %}
</div>
{% endblock %}
{% block javascripts %}
<script>
let showIssuebody = false;
function adjustHeight() {
let ele = $(".issueDescription");
if (ele.length) {
ele.css("height", window.innerHeight - ele.offset().top + "px");
}
}
function showhide() {
// 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();
});
// Tracker
selected = $('#versionFilter').val();
if (!selected || selected.length === 0) {
$('.versionCard').show();
}
else {
$('.versionCard').hide();
selected.forEach(function (id) {
$('.versionCard' + id).show();
});
}
}
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
$('.issueCard').each(function () {
const id = $(this).data('status')+'|'+$(this).data('sprint')+'|'+$(this).data('version');
const $column = $(`[data-id='${id}']`);
if ($column.length) {
$column.append($(this));
}
else {
console.log ('no ='+id);
$(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) {
url='{{path('app_issue_view',{id:'xxx'})}}';
url=url.replace('xxx',issueId);
console.log(url);
$.ajax({
url: url,
method: 'GET',
dataType: 'html',
success: function (html) {
$('.issueContainer').html(html);
$('.issueContainer').show();
$('.scrumContainer').css('padding-left','750px');
$('.issueContainer').css('z-index','1000');
$('.issueDescription').animate({scrollTop: 0}, 0);
adjustHeight();
},
error: function (xhr, status, error) {
console.error('Erreur lors du chargement de lissue :', error);
$('.issueContainer').html('<div class="alert alert-danger">Erreur lors du chargement de lissue.</div>');
}
});
}
$(function () {
let $sourceContainer = null;
let $movedItem = null;
let originalIndex = null;
$('.versionBody').sortable({
connectWith: '.versionBody',
items: '.issueCard',
handle: '.issueId',
placeholder: 'issue-placeholder',
forcePlaceholderSize: true,
start: function (event, ui) {
$sourceContainer = ui.item.parent();
$movedItem = ui.item;
originalIndex = ui.item.index();
},
update: function (event, ui) {
console.log("UPDATE");
if (!event.originalEvent) return;
const $targetContainer = $(this);
const sourceId = $sourceContainer.data('id');
const targetId = $targetContainer.data('id');
sendUpdate(sourceId, targetId, $sourceContainer, $targetContainer, $movedItem, originalIndex);
},
});
function sendUpdate(sourceId, targetId, $sourceContainer, $targetContainer, $movedItem, originalIndex) {
const sourceIssues = $sourceContainer.find('.issueCard').map(function () {
return $(this).data('id');
}).get();
const targetIssues = $targetContainer.find('.issueCard').map(function () {
return $(this).data('id');
}).get();
url='{{path('app_issue_order',{id:'xxx'})}}';
url=url.replace('xxx',$movedItem.data('id'));
$.ajax({
url: url,
method: 'POST',
data: {
target: targetId,
targetIssues: targetIssues
},
success: function (response) {
console.log('Déplacement réussi', response);
},
error: function (xhr) {
console.log(xhr);
// Annuler le déplacement
if ($movedItem && $sourceContainer) {
const items = $sourceContainer.children();
if (originalIndex >= items.length) {
$sourceContainer.append($movedItem);
} else {
$movedItem.insertBefore(items.eq(originalIndex));
}
}
const message = xhr.responseJSON?.message || 'Une erreur est survenue lors de la requête.';
$('#ajaxErrorMessage').text(message);
const toast = new bootstrap.Toast(document.getElementById('ajaxErrorToast'), {
delay: 5000
});
toast.show();
}
});
}
});
</script>
{% endblock %}