svg
This commit is contained in:
@ -6,31 +6,31 @@
|
||||
<h1>{{title}}</h1>
|
||||
<a href="{{ path(routesubmit) }}" class="btn btn-success">Ajouter</a>
|
||||
|
||||
<div class="dataTable_wrapper">
|
||||
<table class="table table-striped table-bordered table-hover" id="dataTables" style="width:100%">
|
||||
<thead>
|
||||
<div class="dataTable_wrapper">
|
||||
<table class="table table-striped table-bordered table-hover" id="dataTables" style="width:100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="100px" class="no-sort">Action</th>
|
||||
<th width="70px">Logo</th>
|
||||
<th width="70px">Modifier</th>
|
||||
<th>Nom</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for project in projects %}
|
||||
<tr>
|
||||
<th width="100px" class="no-sort">Action</th>
|
||||
<th width="70px">Logo</th>
|
||||
<th width="70px">Modifier</th>
|
||||
<th>Nom</th>
|
||||
<td>
|
||||
<a href="{{ path(routeupdate,{id:project.id}) }}" class="me-2"><i class="fas fa-file fa-2x"></i></a>
|
||||
<a href="{{ path('app_admin_project_reclone',{id:project.id}) }}" onclick="return confirm('Es-tu sûr de vouloir reinitialiser et reindexer le projet ?');"><i class="fas fa-rotate-right fa-2x"></i></a>
|
||||
</td>
|
||||
<td><img class="avatar" src="{{ asset(project.logo)}}"></td>
|
||||
<td>{{project.title}}</td>
|
||||
<td>{{project.redmine.updated_on}}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for project in projects %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ path(routeupdate,{id:project.id}) }}" class="me-2"><i class="fas fa-file fa-2x"></i></a>
|
||||
<a href="{{ path('app_admin_project_reclone',{id:project.id}) }}" onclick="return confirm('Es-tu sûr de vouloir reinitialiser et reindexer le projet ?');"><i class="fas fa-rotate-right fa-2x"></i></a>
|
||||
</td>
|
||||
<td><img class="avatar" src="{{ asset(project.logo)}}"></td>
|
||||
<td>{{project.title}}</td>
|
||||
<td>{{project.redmine.updated_on}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block localscript %}
|
||||
|
@ -1,57 +1,45 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block body %}
|
||||
{% for status in project.redmine.issue_statuses %}
|
||||
<div class='statusCard statusCard{{status.id}}'>
|
||||
<h2>{{ status.name }}</h2>
|
||||
<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>
|
||||
|
||||
{% for sprint in project.redmine.sprints|reverse %}
|
||||
<div class='sprintCard sprintCard{{sprint.id}}'>
|
||||
<!-- <h3>Sprint = {{ sprint.name }}</h3> -->
|
||||
<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>
|
||||
|
||||
{% for version in project.redmine.versions|reverse %}
|
||||
{% if version.id not in project.hiddenversions %}
|
||||
<div class='versionCard versionCard{{version.id}}'>
|
||||
<!-- <h4>Version = {{ version.name }}</h4> -->
|
||||
<div class='versionBody' data-id='{{status.id}}|{{sprint.id}}|{{version.id}}'></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<div class='versionCard versionCardNone'>
|
||||
<!-- <h4>Version = Aucune</h4> -->
|
||||
<div class='versionBody' data-id='{{status.id}}|{{sprint.id}}|'></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 %}
|
||||
|
||||
<div class='sprintCard sprintCardNone'>
|
||||
<!-- <h3>Sprint Aucun</h3> -->
|
||||
|
||||
{% for version in project.redmine.versions|reverse %}
|
||||
<div class='versionCard versionCard{{version.id}}'>
|
||||
<!-- <h4>Version = {{ version.name }}</h4> -->
|
||||
<div class='versionBody' data-id='{{status.id}}||{{version.id}}'></div>
|
||||
{% 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>
|
||||
{% endfor %}
|
||||
|
||||
<div class='versionCard versionCardNone'>
|
||||
<!-- <h4>Version = Aucune</h4> -->
|
||||
<div class='versionBody' data-id='{{status.id}}||'></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</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 {{issue.color}} 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}}'>
|
||||
#{{issue.id}} = {{issue.redmine.subject}}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
@ -59,7 +47,7 @@
|
||||
// Ranger les issues dans status / sprint / version
|
||||
$(function () {
|
||||
$('.issueCard').each(function () {
|
||||
const id = $(this).data('status')+'|'+$(this).data('sprint')+'|'+$(this).data('version');
|
||||
const id = $(this).data('status');
|
||||
const $column = $(`[data-id='${id}']`);
|
||||
if ($column.length) {
|
||||
$column.append($(this));
|
||||
@ -71,39 +59,19 @@
|
||||
});
|
||||
$('.scrumContainer').css('display', 'flex');
|
||||
|
||||
// Étape 1 : Masquer les versionCard sans enfant visible dans versionBody
|
||||
document.querySelectorAll('.versionCard').forEach(card => {
|
||||
const versionBody = card.querySelector('.versionBody');
|
||||
const children = Array.from(versionBody?.children || []);
|
||||
// É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.style.display = 'none';
|
||||
card.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Étape 2 : Masquer les sprintCard sans versionCard visible
|
||||
document.querySelectorAll('.sprintCard').forEach(sprint => {
|
||||
const visibleVersion = Array.from(sprint.querySelectorAll('.versionCard'))
|
||||
.some(card => card.offsetParent !== null);
|
||||
|
||||
if (!visibleVersion) {
|
||||
sprint.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Étape 3 : Masquer les statusCard sans sprintCard visible
|
||||
document.querySelectorAll('.statusCard').forEach(status => {
|
||||
const visibleSprint = Array.from(status.querySelectorAll('.sprintCard'))
|
||||
.some(sprint => sprint.offsetParent !== null);
|
||||
|
||||
if (!visibleSprint) {
|
||||
status.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Étape 4 : Trier les issues restantes par data-id dans chaque statusCard
|
||||
// Étape 2 : Trier les issues restantes par data-id dans chaque statusCard
|
||||
document.querySelectorAll('.statusCard').forEach(statusCard => {
|
||||
const issues = Array.from(statusCard.querySelectorAll('.issueCard'));
|
||||
|
||||
@ -114,7 +82,7 @@
|
||||
visibleIssues.sort((a, b) => {
|
||||
const idA = a.getAttribute('data-id');
|
||||
const idB = b.getAttribute('data-id');
|
||||
return idA.localeCompare(idB, undefined, { numeric: true, sensitivity: 'base' });
|
||||
return idB.localeCompare(idA, undefined, { numeric: true, sensitivity: 'base' });
|
||||
});
|
||||
|
||||
// Replacer les issues triées dans leur parent immédiat
|
||||
@ -123,6 +91,90 @@
|
||||
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 %}
|
||||
|
Reference in New Issue
Block a user