favicon + affectation + close

This commit is contained in:
2025-09-08 14:27:40 +02:00
parent 6ca28cf78f
commit 33ea12dc0b
8 changed files with 109 additions and 13 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 736 KiB

View File

@ -68,6 +68,20 @@ class IssueController extends AbstractController
$form = $this->createForm(IssueType::class, $issue, ['mode' => 'update']);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$assignedTo = $form->get('assignedTo')->getData();
try {
$payload = [
'assigned_to_id' => $assignedTo,
];
$user = $this->getUser();
if ($user instanceof \App\Entity\User) {
$apikey = $user->getApikey();
$this->redmineService->updateIssue($id, $payload, $apikey);
}
} catch (\RuntimeException $e) {
}
$em->flush();
return $this->redirectToRoute('app_project_view', ['id' => $issue->getProject()->getId()]);
@ -84,6 +98,41 @@ class IssueController extends AbstractController
]);
}
#[Route('/user/issue/close/{id}', name: 'app_issue_close')]
public function closeIssue(int $id, Request $request, IssueRepository $issueRepository, EntityManagerInterface $em): Response
{
$issue = $issueRepository->find($id);
if (!$issue) {
throw new NotFoundHttpException('La ressource demandée est introuvable.');
}
if (!$issue->getProject()->getUsers()->contains($this->getUser())) {
throw new AccessDeniedException('Vous n\'avez pas accès à cette ressource.');
}
$closedStatus = null;
foreach (array_reverse($issue->getRedmine()['allowed_statuses']) as $status) {
if ($status['is_closed']) {
$closedStatus = $status['id'];
break;
}
}
try {
$payload = [
'status_id' => $closedStatus,
];
$user = $this->getUser();
if ($user instanceof \App\Entity\User) {
$apikey = $user->getApikey();
$this->redmineService->updateIssue($id, $payload, $apikey);
}
} catch (\RuntimeException $e) {
}
return $this->redirectToRoute('app_project_view', ['id' => $issue->getProject()->getId()]);
}
#[Route('/user/issue/order/{id}', name: 'app_issue_order', methods: ['POST'])]
public function orderIssue(int $id, Request $request, IssueRepository $issueRepository, EntityManagerInterface $em): JsonResponse
{

View File

@ -5,6 +5,7 @@ namespace App\Form;
use App\Entity\Issue;
use App\Form\Type\SelbgType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
@ -13,6 +14,17 @@ class IssueType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$issue = $options['data']; // ton entité courante
$project = $issue->getProject();
// Récupérer les assignables
$choices = [];
if ($project && isset($project->getRedmine()['issue_users'])) {
foreach ($project->getRedmine()['issue_users'] as $user) {
$choices[$user['name']] = $user['id'];
}
}
$builder
->add('submit', SubmitType::class, [
'label' => 'Valider',
@ -20,6 +32,15 @@ class IssueType extends AbstractType
])
->add('color', SelbgType::class)
->add('assignedTo', ChoiceType::class, [
'choices' => $choices,
'required' => false,
'placeholder' => 'Aucun intervenant',
'label' => 'Affecté à',
'mapped' => false,
'data' => $issue->getRedmine()['assigned_to']['id'] ?? null,
])
;
}

View File

@ -82,6 +82,7 @@ class RedmineService
$data['project']['issue_statuses'] = $this->getIssueStatuses($apiKey);
$data['project']['issue_priorities'] = $this->getIssuePriorities($apiKey);
$data['project']['issue_users'] = $this->getIssueUsers($id, $apiKey);
$data['project']['versions'] = $this->getProjectVersions($id, $apiKey);
if (empty($data['project']['versions']) && array_key_exists('parent', $data['project'])) {
@ -140,6 +141,37 @@ class RedmineService
}
}
public function getIssueUsers(int $id, string $apiKey): array
{
try {
$response = $this->client->request('GET', $this->baseUrl.'/projects/'.$id.'/memberships.json', [
'headers' => [
'X-Redmine-API-Key' => $apiKey,
'Accept' => 'application/json',
],
]);
if (200 !== $response->getStatusCode()) {
throw new \RuntimeException('Erreur de communication avec Redmine : '.$response->getStatusCode());
}
$data = $response->toArray();
if (!$data['memberships']) {
return [];
}
$users = [];
foreach ($data['memberships'] as $membership) {
if (array_key_exists('user', $membership)) {
array_push($users, $membership['user']);
}
}
return $users;
} catch (TransportExceptionInterface $e) {
throw new \RuntimeException('Erreur de communication avec Redmine : '.$e->getMessage());
}
}
public function getProjectVersions(int $id, string $apiKey): array
{
try {
@ -237,15 +269,6 @@ class RedmineService
}
$issue->setRowsprint($sprintPosition);
// Calcul position issue = plus géré via redmine
/*
if (isset($rissue['sprint'])) {
$issue->setRowissue($rissue['sprint']['position'] ?? 1000000);
} else {
$issue->setRowissue(1000000);
}
*/
$this->em->persist($issue);
$project->setUpdateIssuesAt(new \DateTime());
@ -376,6 +399,7 @@ class RedmineService
'headers' => [
'X-Redmine-API-Key' => $apiKey,
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => ['issue' => $data],
]);

View File

@ -3,8 +3,9 @@
<head>
<meta charset="UTF-8">
<title>{{appName}}{% block title %}{% endblock %}</title>
<link rel="icon" href="">
<link rel="icon" type="image/png" href="{{asset("medias/logo/logo.png")}}">
<link rel="stylesheet" href="{{ asset('lib/bootstrap/css/bootstrap.min.css') }}">
<link rel="stylesheet" href="{{ asset('lib/bootswatch/bootswatch.min.css') }}">
<link rel="stylesheet" href="{{ asset('lib/fontawesome/css/all.min.css') }}">

View File

@ -29,6 +29,7 @@
<div class="card-header">Information</div>
<div class="card-body">
{{ form_row(form.color) }}
{{ form_row(form.assignedTo) }}
</div>
</div>
</div>

View File

@ -18,7 +18,8 @@
{% endif %}
</div>
<div class="d-flex">
<a href="{{path("app_issue_update",{id:issue.id})}}">Modifier</a>
<a class="btn btn-sm btn-success" href="{{path("app_issue_update",{id:issue.id})}}">Modifier / Affecter</a>
<a class="btn btn-sm btn-danger ms-auto" href="{{path("app_issue_close",{id:issue.id})}}" onclick="return confirm('Confirmez-vous la fermeture de cette issue ?')">Fermer</a>
</div>
</div>

View File

@ -4,7 +4,6 @@
{% block body %}
<h1>{{title}}</h1>
{{ form_start(form) }}
{{ form_widget(form.submit) }}