751 lines
25 KiB
Twig
751 lines
25 KiB
Twig
{% extends 'base.html.twig' %}
|
||
|
||
{% block localstyle %}
|
||
<style>
|
||
small {
|
||
font-size:70%;
|
||
}
|
||
|
||
verysmall {
|
||
font-size:60%;
|
||
}
|
||
|
||
.navbar {
|
||
width: 100%;
|
||
position: fixed;
|
||
z-index: 1000;
|
||
}
|
||
|
||
|
||
simplemain {
|
||
padding-top:80px;
|
||
}
|
||
|
||
content {
|
||
padding:0px;
|
||
overflow:auto;
|
||
}
|
||
|
||
.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;
|
||
width:10000px;
|
||
}
|
||
|
||
.containerStatus{
|
||
display:flex;
|
||
//width:10000px;
|
||
overflow-x:scroll;
|
||
}
|
||
|
||
.statusCard {
|
||
width:300px;
|
||
padding: 15px 10px 0px 10px;
|
||
}
|
||
|
||
.statusCard 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;
|
||
}
|
||
|
||
.issueTitle{
|
||
zoom: 70%;
|
||
flex-grow: 1;
|
||
cursor: pointer;
|
||
line-height:18px;
|
||
}
|
||
|
||
.issueParent{
|
||
font-size:12px;
|
||
line-height:12px;
|
||
border-bottom: 1px solid;
|
||
padding-bottom:5px;
|
||
margin-bottom:5px;
|
||
font-style:italic;
|
||
}
|
||
|
||
.issueBody {
|
||
font-size:10px;
|
||
display:flex;
|
||
flex-direction: column;
|
||
border-top: 1px solid var(--bs-gray-100);
|
||
margin-top:5px;
|
||
padding-top:5px;
|
||
}
|
||
|
||
.issueChilds {
|
||
font-size:10px;
|
||
display:flex;
|
||
flex-direction: column;
|
||
border-top: 1px solid var(--bs-gray-100);
|
||
margin-top:5px;
|
||
padding-top:5px;
|
||
line-height:11px;
|
||
}
|
||
|
||
.issueChild {
|
||
display:flex;
|
||
cursor: pointer;
|
||
margin-top:5px;
|
||
}
|
||
|
||
.issueContainer table {
|
||
width:100%;
|
||
}
|
||
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-family: sans-serif;
|
||
font-size: 14px;
|
||
}
|
||
|
||
th, td {
|
||
padding: 8px 12px;
|
||
border: 1px solid #ddd;
|
||
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;
|
||
}
|
||
|
||
.through {
|
||
text-decoration: line-through;
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block simplebody %}
|
||
|
||
<div class='issueContainer'>
|
||
</div>
|
||
|
||
<div class='filtreContainer'>
|
||
<label>Issue</label>
|
||
<input type="number" id="issueSearchInput" class="form-control" placeholder="Rechercher une issue" />
|
||
<a href="{{redmineUrl}}/projects/{{project.id}}/issues/new" target="_blank" class="btn btn-primary btn-sm"><i class="fas fa-file"></i> Nouvelle Demande</a>
|
||
|
||
<label style="margin-top:30px">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>
|
||
|
||
<table style="margin-top:30px;">
|
||
{% for sprint in project.redmine.sprints|reverse %}
|
||
{% if sprint.story_points is defined and sprint.id not in project.hiddensprints %}
|
||
<tr><td style="padding:2px">{{sprint.name}}</td><td style="padding:2px; text-align: center;">{{sprint.story_points.total}}</td></tr>
|
||
{% endif %}
|
||
{% endfor %}
|
||
</table>
|
||
|
||
</div>
|
||
|
||
<div class='scrumContainer'>
|
||
{% for status in project.redmine.issue_statuses %}
|
||
{% if status.id not in project.hiddenstatuses %}
|
||
<div class='statusCard statusCard{{status.id}}'>
|
||
<h2>
|
||
{% set label= label(status.name) %}
|
||
{{ label}}
|
||
<br><small>{{ label != status.name ? status.name : ' ' }}</small>
|
||
</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 %}
|
||
|
||
{% for issue in issues %}
|
||
<div class="issueCard {{issue.color}} 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.versionId}}' data-id='{{issue.id}}'>
|
||
<div class='issueHeader'>
|
||
<div class='issueId'>#{{issue.id}}</div>
|
||
<div class='issueTitle'>
|
||
{% if issue.parent %}
|
||
<div class='issueParent' data-id='{{issue.parent.id}}' data-bs-placement="right" data-bs-toggle="tooltip" data-bs-html="true" title="
|
||
<strong>Tracker :</strong> {{issue.parent.redmine.tracker.name}}<br>
|
||
<strong>Catégorie :</strong> {{issue.parent.categoryName}}<br>
|
||
<strong>Statut :</strong> {{issue.parent.redmine.status.name}}<br>
|
||
<strong>Sprint :</strong> {{issue.parent.sprintName}}<br>
|
||
<strong>Version :</strong> {{issue.parent.versionName}}<br>">
|
||
#{{issue.parent.id}} = {{issue.parent.redmine.subject}}
|
||
</div>
|
||
{% endif %}
|
||
|
||
<div class='issueSubject'>
|
||
{{issue.redmine.subject}}
|
||
</div>
|
||
</div>
|
||
<div class='issueAction'>
|
||
<i class='fas fa-eye' onClick='viewIssue({{issue.id}})'></i>
|
||
<verysmall>{{issue.redmine.sprint.story_points}}</verysmall>
|
||
</div>
|
||
</div>
|
||
<div class='issueBody'>
|
||
<div><strong>Tracker =</strong> {{issue.redmine.tracker.name}}</div>
|
||
<div><strong>Catégorie =</strong> {{issue.categoryName}}</div>
|
||
<div><strong>Affecté à =</strong> {{issue.assignedName}}</div>
|
||
<div><strong>Créé le =</strong> {{ issue.redmine.created_on|date('d/m/Y H:i') }}</div>
|
||
<div><strong>Mis à jour le =</strong> {{ issue.redmine.updated_on|date('d/m/Y H:i') }}</div>
|
||
</div>
|
||
|
||
{% if issue.childs is not empty %}
|
||
<div class='issueChilds'>
|
||
{% for child in issue.childs %}
|
||
<div class='issueChild {{issue.redmine.status.is_closed?'through':''}}' data-id='{{child.id}}' data-bs-placement="right" data-bs-toggle="tooltip" data-bs-html="true" title="
|
||
<strong>Tracker :</strong> {{child.redmine.tracker.name}}<br>
|
||
<strong>Catégorie :</strong> {{child.redmine.category is defined?child.redmine.category.name:'Aucune'}}<br>
|
||
<strong>Statut :</strong> {{child.redmine.status.name}}<br>
|
||
<strong>Sprint :</strong> {{child.sprintName}}<br>
|
||
<strong>Version :</strong> {{child.versionName}}<br>">
|
||
<div>#{{child.id}}</div>
|
||
<div style='padding-left:5px;'>{{child.redmine.subject}}</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
|
||
{% endblock %}
|
||
|
||
{% block javascripts %}
|
||
<script>
|
||
const projectId = '{{ project.id }}';
|
||
const viewedIssueKey = `project_${projectId}_viewedIssue`;
|
||
const bodyIssueKey = `project_${projectId}_bodyIssue`;
|
||
|
||
const filterIds = [
|
||
'statusFilter',
|
||
'sprintFilter',
|
||
'versionFilter',
|
||
'trackerFilter',
|
||
'categoryFilter',
|
||
'detailFilter'
|
||
];
|
||
|
||
// Pulse
|
||
function highlightAndScroll($element) {
|
||
if (!$element || !$element.length) return;
|
||
if (!$element.is(':visible')) return;
|
||
|
||
// Scroll vertical (retourne une promesse)
|
||
const scrollTopPromise = new Promise(resolve => {
|
||
$('html, body').animate({
|
||
scrollTop: $element.offset().top - 150
|
||
}, 500, resolve);
|
||
});
|
||
|
||
// Scroll horizontal (retourne une promesse)
|
||
let paddingLeft = parseInt($('.scrumContainer').css('padding-left').replace('px', ''), 10);
|
||
const scrollLeftPromise = new Promise(resolve => {
|
||
$('html, body').animate({
|
||
scrollLeft: $element.offset().left - paddingLeft
|
||
}, 500, resolve);
|
||
});
|
||
|
||
// Attendre que les deux scrolls soient terminés
|
||
Promise.all([scrollTopPromise, scrollLeftPromise]).then(() => {
|
||
$element.addClass('pulse-highlight');
|
||
|
||
setTimeout(() => {
|
||
$element.removeClass('pulse-highlight');
|
||
}, 1500); // Ajuster selon la durée de l'animation CSS
|
||
});
|
||
}
|
||
|
||
|
||
// 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}']`);
|
||
if ($column.length) {
|
||
$column.append($(this));
|
||
}
|
||
else {
|
||
console.log ('no ='+$(this).data('id'));
|
||
$(this).remove();
|
||
}
|
||
});
|
||
$('.scrumContainer').css('display', 'flex');
|
||
});
|
||
|
||
// 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 Body Issue
|
||
function getVisibleIssueIds() {
|
||
const stored = localStorage.getItem(bodyIssueKey);
|
||
return stored ? JSON.parse(stored) : [];
|
||
}
|
||
|
||
function saveVisibleIssueIds(ids) {
|
||
localStorage.setItem(bodyIssueKey, JSON.stringify(ids));
|
||
}
|
||
|
||
// Parent / Child
|
||
$(document).on('click', '.issueParent', function () {
|
||
const value = $(this).data('id');
|
||
const $target = $(`.issueCard[data-id="${value}"]`);
|
||
if ($target.length > 0) {
|
||
highlightAndScroll($target);
|
||
}
|
||
});
|
||
$(document).on('click', '.issueChild', function () {
|
||
const value = $(this).data('id');
|
||
const $target = $(`.issueCard[data-id="${value}"]`);
|
||
if ($target.length > 0) {
|
||
highlightAndScroll($target);
|
||
}
|
||
});
|
||
|
||
// Toggle issueBody
|
||
$(document).on('click', '.issueSubject', function () {
|
||
const $card = $(this).closest('.issueCard');
|
||
const $body = $card.find('.issueBody');
|
||
const issueId = $card.data('id').toString();
|
||
let visibleIds = getVisibleIssueIds();
|
||
|
||
if ($body.is(':visible')) {
|
||
$body.slideUp(200);
|
||
visibleIds = visibleIds.filter(id => id !== issueId);
|
||
} else {
|
||
$body.slideDown(200);
|
||
if (!visibleIds.includes(issueId)) {
|
||
visibleIds.push(issueId);
|
||
}
|
||
}
|
||
|
||
saveVisibleIssueIds(visibleIds);
|
||
});
|
||
|
||
// Affichage Issue
|
||
function viewIssue(issueId) {
|
||
localStorage.setItem(viewedIssueKey, issueId);
|
||
|
||
url='{{path('app_issue_view',{id:'xxx'})}}';
|
||
url=url.replace('xxx',issueId);
|
||
|
||
$.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 l’issue :', error);
|
||
$('.issueContainer').html('<div class="alert alert-danger">Erreur lors du chargement de l’issue.</div>');
|
||
}
|
||
});
|
||
}
|
||
|
||
function hideIssue() {
|
||
localStorage.removeItem(viewedIssueKey);
|
||
|
||
$('.issueContainer').hide();
|
||
$('.scrumContainer').css('padding-left','300px');
|
||
}
|
||
|
||
// 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);
|
||
}
|
||
|
||
// Affiche bodyIssue
|
||
const visibleIds = getVisibleIssueIds();
|
||
visibleIds.forEach(id => {
|
||
const $card = $(`.issueCard[data-id="${id}"]`);
|
||
const $body = $card.find('.issueBody');
|
||
if ($body.length && !$body.is(':visible')) {
|
||
$body.show(); // ou .slideDown(200) si tu veux de l'anim
|
||
}
|
||
});
|
||
}
|
||
|
||
// É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;
|
||
let originalIndex = null;
|
||
|
||
$('.versionBody').sortable({
|
||
connectWith: '.versionBody',
|
||
items: '.issueCard',
|
||
handle: '.issueId',
|
||
placeholder: 'issue-placeholder',
|
||
forcePlaceholderSize: true,
|
||
|
||
scroll: true, // 👈 Active le scroll auto
|
||
scrollSensitivity: 50, // 👈 distance (px) à partir du bord pour déclencher le scroll
|
||
scrollSpeed: 100, // 👈 vitesse de défilement
|
||
|
||
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 %} |