svg
This commit is contained in:
@ -38,7 +38,7 @@ class IssueController extends AbstractController
|
||||
}
|
||||
|
||||
#[Route('/user/issue/order/{id}', name: 'app_issue_order', methods: ['POST'])]
|
||||
public function orderIssue(int $id, Request $request): JsonResponse
|
||||
public function orderIssue(int $id, Request $request, IssueRepository $issueRepository): JsonResponse
|
||||
{
|
||||
$data = $request->request;
|
||||
|
||||
@ -57,8 +57,8 @@ class IssueController extends AbstractController
|
||||
$allowed = false;
|
||||
if (in_array($id, $targetIssues)) {
|
||||
// Verifier que l'on a la permission de changer de statut
|
||||
$rissue = $this->redmineService->getIssue($id, $this->getParameter('redmineApikey'));
|
||||
foreach ($rissue['allowed_statuses'] as $status) {
|
||||
$issue = $issueRepository->find($id);
|
||||
foreach ($issue->getRedmine()['allowed_statuses'] as $status) {
|
||||
if ($status['id'] == $targetStatus) {
|
||||
$allowed = true;
|
||||
break;
|
||||
|
@ -3,6 +3,8 @@
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\IssueRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: IssueRepository::class)]
|
||||
@ -32,6 +34,25 @@ class Issue
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private Project $project;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: Issue::class, inversedBy: 'childs')]
|
||||
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
|
||||
private ?Issue $parent = null;
|
||||
|
||||
#[ORM\OneToMany(targetEntity: Issue::class, mappedBy: 'parent', cascade: ['persist'])]
|
||||
#[ORM\OrderBy([
|
||||
'rowstatus' => 'ASC',
|
||||
'rowsprint' => 'DESC',
|
||||
'rowversion' => 'DESC',
|
||||
'rowissue' => 'ASC',
|
||||
'id' => 'DESC',
|
||||
])]
|
||||
private Collection $childs;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->childs = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
@ -115,4 +136,24 @@ class Issue
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getParent(): ?Issue
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
public function setParent(?Issue $issue): self
|
||||
{
|
||||
$this->parent = $issue;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Issue>
|
||||
*/
|
||||
public function getChilds(): Collection
|
||||
{
|
||||
return $this->childs;
|
||||
}
|
||||
}
|
||||
|
@ -238,6 +238,15 @@ class RedmineService
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parent
|
||||
foreach ($project->getIssues() as $issue) {
|
||||
$parent = null;
|
||||
if (array_key_exists('parent', $issue->getRedmine())) {
|
||||
$parent = $this->issueRepository->find($issue->getRedmine()['parent']['id']);
|
||||
}
|
||||
$issue->setParent($parent);
|
||||
}
|
||||
}
|
||||
|
||||
public function getProjectIssues(int $projectId, string $apiKey, ?\DateTimeInterface $updatedSince = null): array
|
||||
@ -251,7 +260,7 @@ class RedmineService
|
||||
'project_id' => $projectId,
|
||||
'limit' => $limit,
|
||||
'offset' => $offset,
|
||||
'status_id' => '*', // Inclure toutes les issues
|
||||
'status_id' => '*',
|
||||
];
|
||||
|
||||
if (null !== $updatedSince) {
|
||||
@ -274,6 +283,7 @@ class RedmineService
|
||||
$issues = $data['issues'] ?? [];
|
||||
|
||||
foreach ($issues as $key => $issue) {
|
||||
$issues[$key] = $this->getIssue($issue['id'], $apiKey);
|
||||
$issues[$key]['sprint'] = $this->getIssueAgile($issue['id'], $apiKey);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<div class="mb-0 d-flex" style="align-items:baseline">
|
||||
<h5 style="flex-grow:1">#{{ issue.redmine.id }} – {{ issue.redmine.subject }}</h5>
|
||||
<h5 style="flex-grow:1">#{{ issue.redmine.id }} = {{ issue.redmine.subject }}</h5>
|
||||
<a href="{{redmineUrl}}/issues/{{issue.id}}" target="_blank" class="btn btn-primary"><i class="fas fa-eye"></i></a>
|
||||
<div class="btn btn-secondary" onClick="hideIssue()"><i class="fas fa-window-close"></i></div>
|
||||
|
||||
@ -12,8 +12,10 @@
|
||||
<div class="issueDescription card-body" style="height:500px;overflow-y:auto">
|
||||
<div class="d-flex">
|
||||
<div class="mb-3" style="flex-grow:0.5">
|
||||
<strong>Statut :</strong> {{ issue.redmine.status.name }}<br>
|
||||
<strong>Priorité :</strong> {{ issue.redmine.priority.name }}<br><br>
|
||||
<strong>Tracker =</strong> {{issue.redmine.tracker.name}}<br>
|
||||
<strong>Catégorie =</strong> {{issue.redmine.category is defined?issue.redmine.category.name:''}}<br>
|
||||
<strong>Statut =</strong> {{ issue.redmine.status.name }}<br>
|
||||
<strong>Priorité =</strong> {{ issue.redmine.priority.name }}<br><br>
|
||||
|
||||
{% set sprintName = null %}
|
||||
{% for sprint in issue.project.redmine.sprints %}
|
||||
@ -22,21 +24,23 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if sprintName %}
|
||||
<strong>Sprint :</strong> {{sprintName}} (Position {{ issue.redmine.sprint.position }})<br>
|
||||
<strong>Sprint =</strong> {{sprintName}} (Position {{ issue.redmine.sprint.position }})<br>
|
||||
{% else %}
|
||||
<strong>Sprint :</strong> Aucun (Position {{ issue.redmine.sprint.position }})<br>
|
||||
<strong>Sprint =</strong> Aucun (Position {{ issue.redmine.sprint.position }})<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>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>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>Auteur :</strong> {{ issue.redmine.author.name }}<br>
|
||||
<strong>Progression :</strong> {{ issue.redmine.done_ratio }}%<br>
|
||||
<strong>Créé le :</strong>{{ issue.redmine.created_on|date('d/m/Y H:i') }}<br>
|
||||
<strong>Mis à jour le :</strong>{{ issue.redmine.updated_on|date('d/m/Y H:i') }}<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>
|
||||
<strong>Auteur =</strong> {{ issue.redmine.author.name }}<br>
|
||||
<strong>Progression =</strong> {{ issue.redmine.done_ratio }}%<br>
|
||||
<strong>Créé le =</strong>{{ issue.redmine.created_on|date('d/m/Y H:i') }}<br>
|
||||
<strong>Mis à jour le =</strong>{{ issue.redmine.updated_on|date('d/m/Y H:i') }}<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>Affecté à =</strong> {{(issue.redmine.assigned_to is defined?issue.redmine.assigned_to.name:'')}}<br>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -71,7 +75,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{{dump(issue.redmine)}}
|
||||
{{dump(issue)}}
|
||||
<br><br>
|
||||
</div>
|
||||
<br><br>
|
||||
</div>
|
||||
|
@ -109,9 +109,42 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.issueSubject{
|
||||
.issueTitle{
|
||||
zoom: 70%;
|
||||
flex-grow: 1;
|
||||
cursor: pointer;
|
||||
line-height:18px;
|
||||
}
|
||||
|
||||
.issueParent{
|
||||
font-size:12px;
|
||||
line-height:12px;
|
||||
color:var(--bs-green);
|
||||
}
|
||||
|
||||
.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 {
|
||||
@ -140,11 +173,14 @@
|
||||
}
|
||||
|
||||
.pulse-highlight {
|
||||
animation: pulse 1.5s ease-in-out 1;
|
||||
z-index: 1000;
|
||||
position: relative;
|
||||
animation: pulse 1.5s ease-in-out 1;
|
||||
z-index: 1000;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.through {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@ -205,6 +241,15 @@
|
||||
<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'>
|
||||
@ -270,17 +315,38 @@
|
||||
<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='issueTitle'>
|
||||
{% if issue.parent %}
|
||||
<div class='issueParent' data-id='{{issue.parent.id}}'>#{{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'>
|
||||
sprint = {{issue.rowsprint}}<br>
|
||||
version = {{issue.rowversion}}<br>
|
||||
status = {{issue.redmine.status.name}}
|
||||
<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>Affecté à =</strong> {{(issue.redmine.assigned_to is defined?issue.redmine.assigned_to.name:'')}}</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}}'>
|
||||
<div>#{{child.id}}</div>
|
||||
<div style='padding-left:5px;'>{{child.redmine.subject}}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@ -291,6 +357,8 @@
|
||||
<script>
|
||||
const projectId = '{{ project.id }}';
|
||||
const viewedIssueKey = `project_${projectId}_viewedIssue`;
|
||||
const bodyIssueKey = `project_${projectId}_bodyIssue`;
|
||||
|
||||
const filterIds = [
|
||||
'statusFilter',
|
||||
'sprintFilter',
|
||||
@ -325,7 +393,7 @@
|
||||
|
||||
setTimeout(() => {
|
||||
$element.removeClass('pulse-highlight');
|
||||
}, 3000); // Ajuster selon la durée de l'animation CSS
|
||||
}, 1500); // Ajuster selon la durée de l'animation CSS
|
||||
});
|
||||
}
|
||||
|
||||
@ -385,6 +453,52 @@
|
||||
});
|
||||
});
|
||||
|
||||
// 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);
|
||||
@ -510,6 +624,16 @@
|
||||
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
|
||||
|
Reference in New Issue
Block a user