svg
This commit is contained in:
14
public/lib/chart/chart.js
Normal file
14
public/lib/chart/chart.js
Normal file
File diff suppressed because one or more lines are too long
@ -68,7 +68,7 @@ class ProjectController extends AbstractController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/admin/report/{month}', name: 'app_project_report')]
|
#[Route('/user/report/{month}', name: 'app_project_report')]
|
||||||
public function reportProject(int $month, ProjectRepository $projectRepository, IssueRepository $issueRepository): Response
|
public function reportProject(int $month, ProjectRepository $projectRepository, IssueRepository $issueRepository): Response
|
||||||
{
|
{
|
||||||
$date=new \DateTime($month.'01');
|
$date=new \DateTime($month.'01');
|
||||||
@ -83,7 +83,7 @@ class ProjectController extends AbstractController
|
|||||||
'usemenu' => true,
|
'usemenu' => true,
|
||||||
'usesidebar' => false,
|
'usesidebar' => false,
|
||||||
'project' => $project,
|
'project' => $project,
|
||||||
'issues' => $issueRepository->findIssues(null,true),
|
'issues' => $issueRepository->findIssues(null,true,$this->getUser()),
|
||||||
'date' => $date,
|
'date' => $date,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -192,4 +192,47 @@ class Issue
|
|||||||
{
|
{
|
||||||
return $this->childs;
|
return $this->childs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getSprintName(): string
|
||||||
|
{
|
||||||
|
foreach($this->project->getRedmine()["sprints"] as $sprint) {
|
||||||
|
if($this->rowsprint==$sprint["id"]) {
|
||||||
|
return $sprint["name"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "Aucun";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCategoryName(): string
|
||||||
|
{
|
||||||
|
if(isset($this->redmine["category"])) {
|
||||||
|
return $this->redmine["category"]["name"];
|
||||||
|
}
|
||||||
|
return "Aucune";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getVersionName(): string
|
||||||
|
{
|
||||||
|
if(isset($this->redmine["fixed_version"])) {
|
||||||
|
return $this->redmine["fixed_version"]["name"];
|
||||||
|
}
|
||||||
|
return "Aucune";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getVersionId(): string
|
||||||
|
{
|
||||||
|
if(isset($this->redmine["fixed_version"])) {
|
||||||
|
return $this->redmine["fixed_version"]["id"];
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAssignedName(): string
|
||||||
|
{
|
||||||
|
if(isset($this->redmine["assigned_to"])) {
|
||||||
|
return $this->redmine["assigned_to"]["name"];
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
<script src="{{ asset('lib/dropzone/dropzone.min.js') }}"></script>
|
<script src="{{ asset('lib/dropzone/dropzone.min.js') }}"></script>
|
||||||
<script src="{{ asset('lib/imgareaselect/js/jquery.imgareaselect.dev.js') }}"></script>
|
<script src="{{ asset('lib/imgareaselect/js/jquery.imgareaselect.dev.js') }}"></script>
|
||||||
<script src="{{ asset('lib/jqueryui/jquery-ui.min.js') }}"></script>
|
<script src="{{ asset('lib/jqueryui/jquery-ui.min.js') }}"></script>
|
||||||
|
<script src="{{ asset('lib/chart/chart.js') }}"></script>
|
||||||
|
|
||||||
<script src="{{ asset('lib/app/app.js') }}"></script>
|
<script src="{{ asset('lib/app/app.js') }}"></script>
|
||||||
|
|
||||||
@ -61,6 +62,7 @@
|
|||||||
<a class="nav-link px-2" href="{{path('app_admin')}}"><i class="fa-solid fa-cog fa-2x"></i></a>
|
<a class="nav-link px-2" href="{{path('app_admin')}}"><i class="fa-solid fa-cog fa-2x"></i></a>
|
||||||
<a class="nav-link px-2" href="{{path('app_project_report',{month:202501})}}"><i class="fas fa-file-alt fa-2x"></i></a>
|
<a class="nav-link px-2" href="{{path('app_project_report',{month:202501})}}"><i class="fas fa-file-alt fa-2x"></i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<a class="nav-link px-2" href="{{path('app_issue_list')}}"><i class="fas fa-table fa-2x"></i></a>
|
||||||
<a class="nav-link px-2" href="{{path('app_user_profil')}}"><img src="{{asset(app.user.avatar)}}" class="avatar"></a>
|
<a class="nav-link px-2" href="{{path('app_user_profil')}}"><img src="{{asset(app.user.avatar)}}" class="avatar"></a>
|
||||||
<a class="nav-link px-2" href="{{path('app_logout')}}"><i class="fa-solid fa-right-from-bracket fa-2x"></i></a>
|
<a class="nav-link px-2" href="{{path('app_logout')}}"><i class="fa-solid fa-right-from-bracket fa-2x"></i></a>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -16,23 +16,55 @@
|
|||||||
|
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<div style="width:320px; padding: 0px 10px 0px 0px">
|
<div style="width:320px; padding: 0px 10px 0px 0px">
|
||||||
|
<div style="margin-bottom: 1em;">
|
||||||
|
<button id="toggleView" class="btn btn-primary w-100"> Afficher Vue Graphique</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style="margin-bottom: 1em;">
|
<div style="margin-bottom: 1em;">
|
||||||
<label for="projectFilter"><strong>Filtrer par projet :</strong></label><br>
|
<label for="projectFilter"><strong>Filtrer par projet :</strong></label><br>
|
||||||
<select id="projectFilter" multiple style="width: 300px; height: 150px;" class="select2">
|
<select id="projectFilter" multiple style="width: 100%; height: 150px;" class="select2">
|
||||||
{# Options ajoutées dynamiquement #}
|
{# Options ajoutées dynamiquement #}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-bottom: 1em;">
|
<div style="margin-bottom: 1em;">
|
||||||
<label for="statusFilter"><strong>Filtrer par statut :</strong></label><br>
|
<label for="statusFilter"><strong>Filtrer par statut :</strong></label><br>
|
||||||
<select id="statusFilter" multiple style="width: 300px; height: 150px;" class="select2">
|
<select id="statusFilter" multiple style="width: 100%; height: 150px;" class="select2">
|
||||||
|
{# Options JS dynamiques #}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-bottom: 1em;">
|
||||||
|
<label for="sprintFilter"><strong>Filtrer par sprint :</strong></label><br>
|
||||||
|
<select id="sprintFilter" multiple style="width: 100%; height: 150px;" class="select2">
|
||||||
|
{# Options JS dynamiques #}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-bottom: 1em;">
|
||||||
|
<label for="versionFilter"><strong>Filtrer par version :</strong></label><br>
|
||||||
|
<select id="versionFilter" multiple style="width: 100%; height: 150px;" class="select2">
|
||||||
|
{# Options JS dynamiques #}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-bottom: 1em;">
|
||||||
|
<label for="trackerFilter"><strong>Filtrer par tracker :</strong></label><br>
|
||||||
|
<select id="trackerFilter" multiple style="width: 100%; height: 150px;" class="select2">
|
||||||
{# Options JS dynamiques #}
|
{# Options JS dynamiques #}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-bottom: 1em;">
|
<div style="margin-bottom: 1em;">
|
||||||
<label for="categoryFilter"><strong>Filtrer par catégorie :</strong></label><br>
|
<label for="categoryFilter"><strong>Filtrer par catégorie :</strong></label><br>
|
||||||
<select id="categoryFilter" multiple style="width: 300px; height: 150px;" class="select2">
|
<select id="categoryFilter" multiple style="width: 100%; height: 150px;" class="select2">
|
||||||
|
{# Options JS dynamiques #}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-bottom: 1em;">
|
||||||
|
<label for="assignedFilter"><strong>Filtrer par intervenant :</strong></label><br>
|
||||||
|
<select id="assignedFilter" multiple style="width: 100%; height: 150px;" class="select2">
|
||||||
{# Options JS dynamiques #}
|
{# Options JS dynamiques #}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@ -47,7 +79,12 @@
|
|||||||
<th width="130px">Projet</th>
|
<th width="130px">Projet</th>
|
||||||
<th>Nom</th>
|
<th>Nom</th>
|
||||||
<th>Statut</th>
|
<th>Statut</th>
|
||||||
|
<th>Sprint</th>
|
||||||
|
<th>Version</th>
|
||||||
|
<th>Tracker</th>
|
||||||
<th>Catégorie</th>
|
<th>Catégorie</th>
|
||||||
|
<th>Affecté à</th>
|
||||||
|
<th>Point</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -56,13 +93,22 @@
|
|||||||
<td>#{{issue.id}}</td>
|
<td>#{{issue.id}}</td>
|
||||||
<td>{{issue.project.title}}</td>
|
<td>{{issue.project.title}}</td>
|
||||||
<td>{{issue.redmine.subject}}</td>
|
<td>{{issue.redmine.subject}}</td>
|
||||||
<td>{{issue.redmine.status.name}}</td>
|
<td>{{'%02d'|format(issue.rowStatus)}}-{{issue.redmine.status.name}}</td>
|
||||||
<td>{{issue.redmine.category is defined?issue.redmine.category.name:'Aucune'}}</td>
|
<td>{{issue.sprintName}}</td>
|
||||||
|
<td>{{issue.versionName}}</td>
|
||||||
|
<td>{{issue.redmine.tracker.name}}</td>
|
||||||
|
<td>{{issue.categoryName}}</td>
|
||||||
|
<td>{{issue.assignedName}}</td>
|
||||||
|
<td>{{issue.redmine.sprint.story_points}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="chartContainer" style="display:none; margin-top:2em;">
|
||||||
|
<canvas id="statusChart" width="400" height="200"></canvas>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -70,6 +116,26 @@
|
|||||||
{% block localscript %}
|
{% block localscript %}
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
function restoreSelectValue(selectId) {
|
||||||
|
const stored = localStorage.getItem(selectId);
|
||||||
|
if (stored) {
|
||||||
|
try {
|
||||||
|
const values = JSON.parse(stored);
|
||||||
|
$(`#${selectId}`).val(values).trigger('change');
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Erreur en restaurant le ${selectId}`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const idProject=1;
|
||||||
|
const idStatus=3;
|
||||||
|
const idSprint=4;
|
||||||
|
const idVersion=5;
|
||||||
|
const idTracker=6;
|
||||||
|
const idCategory=7;
|
||||||
|
const idAssigned=8;
|
||||||
|
|
||||||
const table = $('#dataTables').DataTable({
|
const table = $('#dataTables').DataTable({
|
||||||
columnDefs: [
|
columnDefs: [
|
||||||
{ "targets": "no-sort", "orderable": false },
|
{ "targets": "no-sort", "orderable": false },
|
||||||
@ -82,18 +148,33 @@
|
|||||||
|
|
||||||
// Étape 1 : Récupérer les filtres possible
|
// Étape 1 : Récupérer les filtres possible
|
||||||
const projectSet = new Set();
|
const projectSet = new Set();
|
||||||
table.column(1).data().each(function(value) {
|
table.column(idProject).data().each(function(value) {
|
||||||
projectSet.add(value);
|
projectSet.add(value);
|
||||||
});
|
});
|
||||||
const statusSet = new Set();
|
const statusSet = new Set();
|
||||||
table.column(3).data().each(function(value) {
|
table.column(idStatus).data().each(function(value) {
|
||||||
statusSet.add(value);
|
statusSet.add(value);
|
||||||
});
|
});
|
||||||
|
const sprintSet = new Set();
|
||||||
|
table.column(idSprint).data().each(function(value) {
|
||||||
|
sprintSet.add(value);
|
||||||
|
});
|
||||||
|
const versionSet = new Set();
|
||||||
|
table.column(idVersion).data().each(function(value) {
|
||||||
|
versionSet.add(value);
|
||||||
|
});
|
||||||
|
const trackerSet = new Set();
|
||||||
|
table.column(idTracker).data().each(function(value) {
|
||||||
|
trackerSet.add(value);
|
||||||
|
});
|
||||||
const categorySet = new Set();
|
const categorySet = new Set();
|
||||||
table.column(4).data().each(function(value) {
|
table.column(idCategory).data().each(function(value) {
|
||||||
categorySet.add(value);
|
categorySet.add(value);
|
||||||
});
|
});
|
||||||
console.log(categorySet);
|
const assignedSet = new Set();
|
||||||
|
table.column(idAssigned).data().each(function(value) {
|
||||||
|
assignedSet.add(value);
|
||||||
|
});
|
||||||
|
|
||||||
// Étape 2 : Remplir dynamiquement les filtres
|
// Étape 2 : Remplir dynamiquement les filtres
|
||||||
const $selectProject = $('#projectFilter');
|
const $selectProject = $('#projectFilter');
|
||||||
@ -114,44 +195,271 @@
|
|||||||
});
|
});
|
||||||
$statusSelect.append(option);
|
$statusSelect.append(option);
|
||||||
});
|
});
|
||||||
|
const $sprintSelect = $('#sprintFilter');
|
||||||
|
Array.from(sprintSet).sort().forEach(function(sprint) {
|
||||||
|
const option = $('<option>', {
|
||||||
|
value: sprint,
|
||||||
|
text: (sprint?sprint:"Aucun"),
|
||||||
|
selected: false
|
||||||
|
});
|
||||||
|
$sprintSelect.append(option);
|
||||||
|
});
|
||||||
|
const $versionSelect = $('#versionFilter');
|
||||||
|
Array.from(versionSet).sort().forEach(function(version) {
|
||||||
|
const option = $('<option>', {
|
||||||
|
value: version,
|
||||||
|
text: (version?version:"Aucune"),
|
||||||
|
selected: false
|
||||||
|
});
|
||||||
|
$versionSelect.append(option);
|
||||||
|
});
|
||||||
|
const $trackerSelect = $('#trackerFilter');
|
||||||
|
Array.from(trackerSet).sort().forEach(function(tracker) {
|
||||||
|
const option = $('<option>', {
|
||||||
|
value: tracker,
|
||||||
|
text: tracker,
|
||||||
|
selected: false
|
||||||
|
});
|
||||||
|
$trackerSelect.append(option);
|
||||||
|
});
|
||||||
const $categorySelect = $('#categoryFilter');
|
const $categorySelect = $('#categoryFilter');
|
||||||
Array.from(categorySet).sort().forEach(function(category) {
|
Array.from(categorySet).sort().forEach(function(category) {
|
||||||
const option = $('<option>', {
|
const option = $('<option>', {
|
||||||
value: category,
|
value: category,
|
||||||
text: category,
|
text: (category?category:"Aucune"),
|
||||||
selected: false
|
selected: false
|
||||||
});
|
});
|
||||||
$categorySelect.append(option);
|
$categorySelect.append(option);
|
||||||
});
|
});
|
||||||
|
const $assignedSelect = $('#assignedFilter');
|
||||||
|
Array.from(assignedSet).sort().forEach(function(assigned) {
|
||||||
|
const option = $('<option>', {
|
||||||
|
value: assigned,
|
||||||
|
text: (assigned?assigned:"Aucun"),
|
||||||
|
selected: false
|
||||||
|
});
|
||||||
|
$assignedSelect.append(option);
|
||||||
|
});
|
||||||
|
|
||||||
// Étape 3 : Ajouter le filtre personnalisé à DataTables
|
// Etape 3 : restaurer les valeurs filtrés stocké en localstorage
|
||||||
|
restoreSelectValue('projectFilter');
|
||||||
|
restoreSelectValue('statusFilter');
|
||||||
|
restoreSelectValue('sprintFilter');
|
||||||
|
restoreSelectValue('versionFilter');
|
||||||
|
restoreSelectValue('trackerFilter');
|
||||||
|
restoreSelectValue('categoryFilter');
|
||||||
|
restoreSelectValue('assignedFilter');
|
||||||
|
|
||||||
|
// Étape 4 : Ajouter le filtre personnalisé à DataTables
|
||||||
$.fn.dataTable.ext.search.push(function(settings, data, dataIndex) {
|
$.fn.dataTable.ext.search.push(function(settings, data, dataIndex) {
|
||||||
const selectedProjects = $('#projectFilter').val();
|
const selectedProjects = $('#projectFilter').val();
|
||||||
const selectedStatuses = $('#statusFilter').val();
|
const selectedStatuses = $('#statusFilter').val();
|
||||||
|
const selectedSprints = $('#sprintFilter').val();
|
||||||
|
const selectedVersions = $('#versionFilter').val();
|
||||||
|
const selectedTrackers = $('#trackerFilter').val();
|
||||||
const selectedCategories = $('#categoryFilter').val();
|
const selectedCategories = $('#categoryFilter').val();
|
||||||
|
const selectedAssigneds = $('#assignedFilter').val();
|
||||||
|
|
||||||
const project = data[1]; // Colonne "Projet"
|
const project = data[idProject];
|
||||||
const status = data[3]; // Colonne "Statut"
|
const status = data[idStatus];
|
||||||
const category = data[4]; // Colonne "Categorie"
|
const sprint = data[idSprint];
|
||||||
|
const version = data[idVersion];
|
||||||
|
const tracker = data[idTracker];
|
||||||
|
const category = data[idCategory];
|
||||||
|
const assigned = data[idAssigned];
|
||||||
|
|
||||||
const projectMatch = !selectedProjects || selectedProjects.length === 0 || selectedProjects.includes(project);
|
const projectMatch = !selectedProjects || selectedProjects.length === 0 || selectedProjects.includes(project);
|
||||||
const statusMatch = !selectedStatuses || selectedStatuses.length === 0 || selectedStatuses.includes(status);
|
const statusMatch = !selectedStatuses || selectedStatuses.length === 0 || selectedStatuses.includes(status);
|
||||||
|
const sprintMatch = !selectedSprints || selectedSprints.length === 0 || selectedSprints.includes(sprint);
|
||||||
|
const versionMatch = !selectedVersions || selectedVersions.length === 0 || selectedVersions.includes(version);
|
||||||
|
const trackerMatch = !selectedTrackers || selectedTrackers.length === 0 || selectedTrackers.includes(tracker);
|
||||||
const categoryMatch = !selectedCategories || selectedCategories.length === 0 || selectedCategories.includes(category);
|
const categoryMatch = !selectedCategories || selectedCategories.length === 0 || selectedCategories.includes(category);
|
||||||
|
const assignedMatch = !selectedAssigneds || selectedAssigneds.length === 0 || selectedAssigneds.includes(assigned);
|
||||||
|
|
||||||
return projectMatch && statusMatch && categoryMatch;
|
return projectMatch && statusMatch && sprintMatch && versionMatch && trackerMatch && categoryMatch && assignedMatch;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Étape 4 : Rafraîchir le tableau quand le select change
|
// Étape 5 : Rafraîchir le tableau quand le select change
|
||||||
$selectProject.on('change', function() {
|
const filterIds = ['projectFilter', 'statusFilter', 'sprintFilter','versionFilter', 'trackerFilter', 'categoryFilter', 'assignedFilter'];
|
||||||
table.draw();
|
filterIds.forEach(function(filterId) {
|
||||||
});
|
$(`#${filterId}`).on('change', function() {
|
||||||
$statusSelect.on('change', function() {
|
const val = $(this).val();
|
||||||
table.draw();
|
localStorage.setItem(filterId, JSON.stringify(val || []));
|
||||||
});
|
table.draw();
|
||||||
$categorySelect.on('change', function() {
|
|
||||||
table.draw();
|
// Si on est en vue graphique, mettre à jour le graphique
|
||||||
});
|
if ($('#chartContainer').is(':visible')) {
|
||||||
|
updateChart();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Etape 6 = chart
|
||||||
|
let chartInstance = null;
|
||||||
|
|
||||||
|
function generateGradientColors(startColor, endColor, steps) {
|
||||||
|
function hexToRgb(hex) {
|
||||||
|
hex = hex.replace(/^#/, '');
|
||||||
|
return {
|
||||||
|
r: parseInt(hex.substring(0, 2), 16),
|
||||||
|
g: parseInt(hex.substring(2, 4), 16),
|
||||||
|
b: parseInt(hex.substring(4, 6), 16)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function rgbToHex(r, g, b) {
|
||||||
|
return '#' + [r, g, b].map(x => {
|
||||||
|
const hex = x.toString(16);
|
||||||
|
return hex.length === 1 ? '0' + hex : hex;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = hexToRgb(startColor);
|
||||||
|
const end = hexToRgb(endColor);
|
||||||
|
const gradient = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < steps; i++) {
|
||||||
|
const r = Math.round(start.r + ((end.r - start.r) * i) / (steps - 1));
|
||||||
|
const g = Math.round(start.g + ((end.g - start.g) * i) / (steps - 1));
|
||||||
|
const b = Math.round(start.b + ((end.b - start.b) * i) / (steps - 1));
|
||||||
|
gradient.push(rgbToHex(r, g, b));
|
||||||
|
}
|
||||||
|
|
||||||
|
return gradient;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#toggleView').on('click', function () {
|
||||||
|
const isTableVisible = $('#dataTables').is(':visible');
|
||||||
|
|
||||||
|
if (isTableVisible) {
|
||||||
|
// Passer en vue graphique
|
||||||
|
$('#dataTables_wrapper').hide();
|
||||||
|
$('#chartContainer').show();
|
||||||
|
$(this).text('Afficher Vue Tableau');
|
||||||
|
localStorage.setItem('currentView', 'chart');
|
||||||
|
|
||||||
|
if (!chartInstance) {
|
||||||
|
drawChart();
|
||||||
|
} else {
|
||||||
|
updateChart();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Passer en vue tableau
|
||||||
|
$('#chartContainer').hide();
|
||||||
|
$('#dataTables_wrapper').show();
|
||||||
|
$(this).text('Afficher Vue Graphique');
|
||||||
|
localStorage.setItem('currentView', 'table');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function drawChart() {
|
||||||
|
const dataByStatus = {};
|
||||||
|
|
||||||
|
// Parcours des lignes filtrées de DataTables
|
||||||
|
table.rows({ filter: 'applied' }).every(function () {
|
||||||
|
const data = this.data();
|
||||||
|
const status = data[idStatus];
|
||||||
|
const points = parseFloat(data[9]) || 0;
|
||||||
|
|
||||||
|
if (!dataByStatus[status]) {
|
||||||
|
dataByStatus[status] = 0;
|
||||||
|
}
|
||||||
|
dataByStatus[status] += points;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tri alphabétique des statuts
|
||||||
|
const sortedLabels = Object.keys(dataByStatus).sort((a, b) =>
|
||||||
|
a.localeCompare(b, 'fr', { sensitivity: 'base' })
|
||||||
|
);
|
||||||
|
|
||||||
|
// Données triées
|
||||||
|
const sortedDataPoints = sortedLabels.map(label => dataByStatus[label]);
|
||||||
|
|
||||||
|
// Dégradé du bleu vers le vert
|
||||||
|
const gradientColors = generateGradientColors('#007bff', '#28a745', sortedLabels.length);
|
||||||
|
|
||||||
|
// Couleurs finales : rouge si "échec", sinon couleur du dégradé
|
||||||
|
const colors = sortedLabels.map((label, index) => {
|
||||||
|
const lowerLabel = label.toLowerCase();
|
||||||
|
if (lowerLabel.includes('échec') || lowerLabel.includes('echec')) {
|
||||||
|
return '#dc3545'; // rouge Bootstrap
|
||||||
|
} else {
|
||||||
|
return gradientColors[index];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Création du graphique Chart.js
|
||||||
|
const ctx = document.getElementById('statusChart').getContext('2d');
|
||||||
|
chartInstance = new Chart(ctx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: sortedLabels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Points par Statut',
|
||||||
|
data: sortedDataPoints,
|
||||||
|
backgroundColor: colors,
|
||||||
|
borderColor: colors,
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false },
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: function (context) {
|
||||||
|
return `${context.dataset.label}: ${context.parsed.y} points`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Total des Points'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Statuts'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateChart() {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.destroy();
|
||||||
|
chartInstance = null;
|
||||||
|
}
|
||||||
|
drawChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
const storedView = localStorage.getItem('currentView');
|
||||||
|
if (storedView === 'chart') {
|
||||||
|
$('#dataTables_wrapper').hide();
|
||||||
|
$('#chartContainer').show();
|
||||||
|
$('#toggleView').text('Afficher Vue Tableau');
|
||||||
|
drawChart();
|
||||||
|
} else {
|
||||||
|
$('#chartContainer').hide();
|
||||||
|
$('#dataTables_wrapper').show();
|
||||||
|
$('#toggleView').text('Afficher Vue Graphique');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
table.draw();
|
||||||
|
updateChart();
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -25,22 +25,11 @@
|
|||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<div class="mb-3" style="flex-grow:0.5">
|
<div class="mb-3" style="flex-grow:0.5">
|
||||||
<strong>Tracker =</strong> {{issue.redmine.tracker.name}}<br>
|
<strong>Tracker =</strong> {{issue.redmine.tracker.name}}<br>
|
||||||
<strong>Catégorie =</strong> {{issue.redmine.category is defined?issue.redmine.category.name:''}}<br>
|
<strong>Catégorie =</strong> {{issue.categoryName}}<br>
|
||||||
<strong>Statut =</strong> {{ issue.redmine.status.name }}<br>
|
<strong>Statut =</strong> {{ issue.redmine.status.name }}<br>
|
||||||
<strong>Priorité =</strong> {{ issue.redmine.priority.name }}<br><br>
|
<strong>Priorité =</strong> {{ issue.redmine.priority.name }}<br><br>
|
||||||
|
<strong>Sprint =</strong> {{issue.sprintName}}<br>
|
||||||
{% set sprintName = null %}
|
<strong>Version Cible =</strong> {{issue.versionName}}<br>
|
||||||
{% for sprint in issue.project.redmine.sprints %}
|
|
||||||
{% if sprint.id == issue.rowsprint %}
|
|
||||||
{% set sprintName = sprint.name %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% if sprintName %}
|
|
||||||
<strong>Sprint =</strong> {{sprintName}}<br>
|
|
||||||
{% else %}
|
|
||||||
<strong>Sprint =</strong> Aucun<br>
|
|
||||||
{% endif %}
|
|
||||||
<strong>Version Cible =</strong> {{(issue.redmine.fixed_version is defined?issue.redmine.fixed_version.name:'Aucune')}}<br>
|
|
||||||
<strong>Story Point =</strong> {{issue.redmine.sprint.story_points}}<br>
|
<strong>Story Point =</strong> {{issue.redmine.sprint.story_points}}<br>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -52,7 +41,7 @@
|
|||||||
<strong>Date de début =</strong> {{ issue.redmine.start_date|date('d/m/Y') }}<br>
|
<strong>Date de début =</strong> {{ issue.redmine.start_date|date('d/m/Y') }}<br>
|
||||||
<strong>Date de fin =</strong> {{ issue.redmine.due_date|date('d/m/Y') }}<br><br>
|
<strong>Date de fin =</strong> {{ issue.redmine.due_date|date('d/m/Y') }}<br><br>
|
||||||
|
|
||||||
<strong>Affecté à =</strong> {{(issue.redmine.assigned_to is defined?issue.redmine.assigned_to.name:'')}}<br>
|
<strong>Affecté à =</strong> {{issue.assignedName}}<br>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -317,24 +317,17 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% for issue in issues %}
|
{% 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.redmine.fixed_version is defined?issue.redmine.fixed_version.id:'')}}' data-id='{{issue.id}}'>
|
<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='issueHeader'>
|
||||||
<div class='issueId'>#{{issue.id}}</div>
|
<div class='issueId'>#{{issue.id}}</div>
|
||||||
<div class='issueTitle'>
|
<div class='issueTitle'>
|
||||||
{% if issue.parent %}
|
{% if issue.parent %}
|
||||||
{% set sprintName = "Aucun" %}
|
|
||||||
{% for sprint in issue.parent.project.redmine.sprints %}
|
|
||||||
{% if sprint.id == issue.parent.rowsprint %}
|
|
||||||
{% set sprintName = sprint.name %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<div class='issueParent' data-id='{{issue.parent.id}}' data-bs-placement="right" data-bs-toggle="tooltip" data-bs-html="true" title="
|
<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>Tracker :</strong> {{issue.parent.redmine.tracker.name}}<br>
|
||||||
<strong>Catégorie :</strong> {{issue.parent.redmine.category is defined?issue.parent.redmine.category.name:'Aucune'}}<br>
|
<strong>Catégorie :</strong> {{issue.parent.categoryName}}<br>
|
||||||
<strong>Statut :</strong> {{issue.parent.redmine.status.name}}<br>
|
<strong>Statut :</strong> {{issue.parent.redmine.status.name}}<br>
|
||||||
<strong>Sprint :</strong> {{sprintName}}<br>
|
<strong>Sprint :</strong> {{issue.parent.sprintName}}<br>
|
||||||
<strong>Version :</strong> {{(issue.parent.redmine.fixed_version is defined?issue.parent.redmine.fixed_version.name:'Aucune')}}<br>">
|
<strong>Version :</strong> {{issue.parent.versionName}}<br>">
|
||||||
#{{issue.parent.id}} = {{issue.parent.redmine.subject}}
|
#{{issue.parent.id}} = {{issue.parent.redmine.subject}}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -350,8 +343,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class='issueBody'>
|
<div class='issueBody'>
|
||||||
<div><strong>Tracker =</strong> {{issue.redmine.tracker.name}}</div>
|
<div><strong>Tracker =</strong> {{issue.redmine.tracker.name}}</div>
|
||||||
<div><strong>Catégorie =</strong> {{issue.redmine.category is defined?issue.redmine.category.name:''}}</div>
|
<div><strong>Catégorie =</strong> {{issue.categoryName}}</div>
|
||||||
<div><strong>Affecté à =</strong> {{(issue.redmine.assigned_to is defined?issue.redmine.assigned_to.name:'')}}</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>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><strong>Mis à jour le =</strong> {{ issue.redmine.updated_on|date('d/m/Y H:i') }}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -359,19 +352,12 @@
|
|||||||
{% if issue.childs is not empty %}
|
{% if issue.childs is not empty %}
|
||||||
<div class='issueChilds'>
|
<div class='issueChilds'>
|
||||||
{% for child in issue.childs %}
|
{% for child in issue.childs %}
|
||||||
{% set sprintName = "Aucun" %}
|
|
||||||
{% for sprint in child.project.redmine.sprints %}
|
|
||||||
{% if sprint.id == child.rowsprint %}
|
|
||||||
{% set sprintName = sprint.name %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<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="
|
<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>Tracker :</strong> {{child.redmine.tracker.name}}<br>
|
||||||
<strong>Catégorie :</strong> {{child.redmine.category is defined?child.redmine.category.name:'Aucune'}}<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>Statut :</strong> {{child.redmine.status.name}}<br>
|
||||||
<strong>Sprint :</strong> {{sprintName}}<br>
|
<strong>Sprint :</strong> {{child.sprintName}}<br>
|
||||||
<strong>Version :</strong> {{(child.redmine.fixed_version is defined?child.redmine.fixed_version.name:'Aucune')}}<br>">
|
<strong>Version :</strong> {{child.versionName}}<br>">
|
||||||
<div>#{{child.id}}</div>
|
<div>#{{child.id}}</div>
|
||||||
<div style='padding-left:5px;'>{{child.redmine.subject}}</div>
|
<div style='padding-left:5px;'>{{child.redmine.subject}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user