fix : code review — correct 15 issues across UX overhaul (phases 1-4)

Critical fixes:
- Make MigrateConstructeurLinks migration no-op (legacy tables already dropped)
- Add explicit ON CONFLICT (id) target in RestoreConstructeurLinks migration
- Replace N+1 queries with 4 bulk GROUP BY in ConstructeurStatsController
- Declare missing versionListRef template ref in machine detail page
- Add missing await on removeMachineDocument, cast activeTab as string

Important fixes:
- Add lang="ts" to ToastContainer and constructeurs page
- Type entityType as union in UsedInSection/useUsedIn
- Remove dead duration param from showError
- Update back-link props to new /catalogues/* URLs (3 pages)
- Replace raw error blocks with EmptyState in component/piece detail pages
- Type handleFillEntity params and machineInfoCardRef

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-05 18:26:05 +02:00
parent 8a841832b2
commit 244bfdc3e4
12 changed files with 92 additions and 136 deletions

View File

@@ -5,10 +5,10 @@ declare(strict_types=1);
namespace App\Controller;
use App\Entity\ComposantConstructeurLink;
use App\Entity\Constructeur;
use App\Entity\MachineConstructeurLink;
use App\Entity\PieceConstructeurLink;
use App\Entity\ProductConstructeurLink;
use App\Repository\ConstructeurRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -19,7 +19,6 @@ final class ConstructeurStatsController extends AbstractController
{
public function __construct(
private readonly EntityManagerInterface $em,
private readonly ConstructeurRepository $constructeurs,
) {}
#[Route('/api/constructeurs/{id}/stats', name: 'api_constructeur_stats', methods: ['GET'])]
@@ -27,7 +26,7 @@ final class ConstructeurStatsController extends AbstractController
{
$this->denyAccessUnlessGranted('ROLE_VIEWER');
$constructeur = $this->constructeurs->find($id);
$constructeur = $this->em->find(Constructeur::class, $id);
if (!$constructeur) {
return new JsonResponse(
['message' => 'Fournisseur introuvable.'],
@@ -35,7 +34,14 @@ final class ConstructeurStatsController extends AbstractController
);
}
return new JsonResponse($this->buildStats($id));
$bulk = $this->fetchAllStats();
return new JsonResponse($bulk[$id] ?? [
'composantCount' => 0,
'pieceCount' => 0,
'machineCount' => 0,
'productCount' => 0,
]);
}
#[Route('/api/constructeurs/stats', name: 'api_constructeurs_stats_bulk', methods: ['GET'], priority: 1)]
@@ -43,42 +49,48 @@ final class ConstructeurStatsController extends AbstractController
{
$this->denyAccessUnlessGranted('ROLE_VIEWER');
return new JsonResponse($this->fetchAllStats());
}
/** @return array<string, array{composantCount: int, pieceCount: int, machineCount: int, productCount: int}> */
private function fetchAllStats(): array
{
$result = [];
// Initialize all constructeurs with zero counts
$allIds = $this->em->createQuery(
'SELECT c.id FROM App\Entity\Constructeur c',
)->getSingleColumnResult();
$result = [];
foreach ($allIds as $id) {
$result[$id] = $this->buildStats($id);
$result[$id] = [
'composantCount' => 0,
'pieceCount' => 0,
'machineCount' => 0,
'productCount' => 0,
];
}
return new JsonResponse($result);
}
/** @return array{composantCount: int, pieceCount: int, machineCount: int, productCount: int} */
private function buildStats(string $constructeurId): array
{
$composantCount = (int) $this->em->createQuery(
'SELECT COUNT(l.id) FROM '.ComposantConstructeurLink::class.' l WHERE l.constructeur = :id',
)->setParameter('id', $constructeurId)->getSingleScalarResult();
$pieceCount = (int) $this->em->createQuery(
'SELECT COUNT(l.id) FROM '.PieceConstructeurLink::class.' l WHERE l.constructeur = :id',
)->setParameter('id', $constructeurId)->getSingleScalarResult();
$machineCount = (int) $this->em->createQuery(
'SELECT COUNT(l.id) FROM '.MachineConstructeurLink::class.' l WHERE l.constructeur = :id',
)->setParameter('id', $constructeurId)->getSingleScalarResult();
$productCount = (int) $this->em->createQuery(
'SELECT COUNT(l.id) FROM '.ProductConstructeurLink::class.' l WHERE l.constructeur = :id',
)->setParameter('id', $constructeurId)->getSingleScalarResult();
return [
'composantCount' => $composantCount,
'pieceCount' => $pieceCount,
'machineCount' => $machineCount,
'productCount' => $productCount,
// 4 bulk queries instead of 4N
$counts = [
'composantCount' => ComposantConstructeurLink::class,
'pieceCount' => PieceConstructeurLink::class,
'machineCount' => MachineConstructeurLink::class,
'productCount' => ProductConstructeurLink::class,
];
foreach ($counts as $key => $entityClass) {
$rows = $this->em->createQuery(
'SELECT IDENTITY(l.constructeur) AS cid, COUNT(l.id) AS cnt FROM '.$entityClass.' l GROUP BY l.constructeur',
)->getResult();
foreach ($rows as $row) {
if (isset($result[$row['cid']])) {
$result[$row['cid']][$key] = (int) $row['cnt'];
}
}
}
return $result;
}
}