Files
ninemine/templates/project/report.html.twig
2025-07-23 22:35:07 +02:00

181 lines
7.4 KiB
Twig

{% extends 'base.html.twig' %}
{% block body %}
<div class="d-flex">
<div style="width:300px; padding: 0px 10px">
<div id="monthSelectorContainer" style="margin-bottom: 1em;">
<label for="monthSelector"><strong>Choisir un mois :</strong></label><br>
<input type="month" id="monthSelector" style="width: 100%;" value="{{ date|date('Y-m') }}">
</div>
<div id="statusCardSelectorContainer" style="margin-bottom: 1em;">
<label for="statusCardSelector"><strong>Afficher/Masquer les statuts :</strong></label><br>
<select id="statusCardSelector" multiple size="15" style="width: 100%;">
{# Options JS dynamiques #}
</select>
</div>
<button id="copyReportBtn" class="btn btn-primary" style="margin-top: 10px; width: 100%;">📋 Copier le rapport</button>
<div id="copyStatusMsg" style="font-size: 0.9em; color: green; display: none; margin-top: 5px;">
✅ Copié dans le presse-papiers !
</div>
</div>
<div class="report" style="padding-left: 10px">
{% for status in project.redmine.issue_statuses %}
<div class='statusCard statusCard{{status.id}}'>
<h2>{{ status.name }}</h2>
<div class='statusBody' data-id='{{status.id}}'>
</div>
<hr>
</div>
{% endfor %}
{% for issue in issues %}
{% if issue.redmine.updated_on|date('Ymd') >= date|date('Ymd') or (issue.redmine.closed_on is not null and issue.redmine.closed_on|date('Ymd') >= date|date('Ymd')) %}
<div class="issueCard" 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}}'>
#{{issue.id}} = {{issue.redmine.subject}}
</div>
{% endif %}
{% endfor %}
</div>
</div>
{% endblock %}
{% block javascripts %}
<script>
// Ranger les issues dans status / sprint / version
$(function () {
$('.issueCard').each(function () {
const id = $(this).data('status');
const $column = $(`[data-id='${id}']`);
if ($column.length) {
$column.append($(this));
}
else {
console.log ('no ='+$(this).data('id'));
$(this).remove();
}
});
$('.scrumContainer').css('display', 'flex');
// Étape 1 : Masquer les statusCard sans enfant visible dans statusBody
document.querySelectorAll('.statusCard').forEach(card => {
const statusBody = card.querySelector('.statusBody');
const children = Array.from(statusBody?.children || []);
const hasVisibleChild = children.some(child => child.offsetParent !== null);
if (!hasVisibleChild) {
card.remove();
}
});
// Étape 2 : Trier les issues restantes par data-id dans chaque statusCard
document.querySelectorAll('.statusCard').forEach(statusCard => {
const issues = Array.from(statusCard.querySelectorAll('.issueCard'));
// Filtrer les issues réellement visibles (au cas où certaines sont masquées)
const visibleIssues = issues.filter(issue => issue.offsetParent !== null);
// Trier les issues par data-id (tu peux ajuster pour une autre propriété si nécessaire)
visibleIssues.sort((a, b) => {
const idA = a.getAttribute('data-id');
const idB = b.getAttribute('data-id');
return idB.localeCompare(idA, undefined, { numeric: true, sensitivity: 'base' });
});
// Replacer les issues triées dans leur parent immédiat
visibleIssues.forEach(issue => {
const parent = issue.parentElement;
parent.appendChild(issue);
});
});
// Étape 3 : Générer la liste des statusCards visibles dans le sélecteur multiple
const selector = document.getElementById('statusCardSelector');
const statusCards = Array.from(document.querySelectorAll('.statusCard'));
statusCards.forEach(card => {
const statusName = card.querySelector('h2')?.textContent || 'Statut inconnu';
const statusId = card.className.match(/statusCard(\d+)/)?.[1];
if (statusId) {
const option = document.createElement('option');
option.value = statusId;
option.textContent = statusName;
option.selected = card.style.display !== 'none'; // Sélectionner si visible
selector.appendChild(option);
}
});
// Étape 4 : Afficher/Masquer les statusCard selon les options sélectionnées
selector.addEventListener('change', function () {
const selectedValues = Array.from(this.selectedOptions).map(opt => opt.value);
statusCards.forEach(card => {
const statusId = card.className.match(/statusCard(\d+)/)?.[1];
if (!statusId) return;
if (selectedValues.includes(statusId)) {
card.style.display = '';
} else {
card.style.display = 'none';
}
});
});
// Étape 5 : Copier le contenu de .report au clic sur le bouton
document.getElementById('copyReportBtn').addEventListener('click', function () {
const reportEl = document.querySelector('.report');
if (!reportEl) return;
// Créer une sélection virtuelle du contenu HTML visible
const range = document.createRange();
range.selectNodeContents(reportEl);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
try {
// Exécuter la commande de copie
const successful = document.execCommand('copy');
if (successful) {
const msg = document.getElementById('copyStatusMsg');
msg.style.display = 'inline';
setTimeout(() => {
msg.style.display = 'none';
}, 2000);
} else {
alert('Échec de la copie.');
}
} catch (err) {
alert('Erreur lors de la copie : ' + err);
}
// Nettoyer la sélection
selection.removeAllRanges();
});
// Étape 6 : Redirection sur changement de mois
document.getElementById('monthSelector').addEventListener('change', function () {
const selected = this.value; // format YYYY-MM
if (!selected) return;
const parts = selected.split('-');
if (parts.length !== 2) return;
const year = parts[0];
const month = parts[1];
const targetMonth = year + month;
// Redirection vers la route avec le nouveau mois
window.location.href = '{{ path("app_project_report", {"month": "REPLACE_ME"}) }}'.replace('REPLACE_ME', targetMonth);
});
});
</script>
{% endblock %}