From 3e264cbff429fe5649d39d0d327d53bb58797588 Mon Sep 17 00:00:00 2001 From: team 1 Date: Wed, 25 Feb 2026 15:37:29 +0100 Subject: [PATCH] add tag assign to documents batch --- src/Controller/Admin/TagController.php | 73 ++++++++++++- src/Tag/TagService.php | 43 ++++++++ templates/admin/tag/assign.html.twig | 144 +++++++++++++++++++++++++ templates/admin/tag/index.html.twig | 12 ++- 4 files changed, 269 insertions(+), 3 deletions(-) create mode 100644 templates/admin/tag/assign.html.twig diff --git a/src/Controller/Admin/TagController.php b/src/Controller/Admin/TagController.php index 7340dae..f396f34 100644 --- a/src/Controller/Admin/TagController.php +++ b/src/Controller/Admin/TagController.php @@ -4,9 +4,12 @@ declare(strict_types=1); namespace App\Controller\Admin; +use App\Entity\Document; +use App\Entity\DocumentTag; use App\Entity\Tag; use App\Entity\TagRebuildJob; use App\Tag\TagService; +use App\Service\TagRebuildJobService; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -20,10 +23,10 @@ final class TagController extends AbstractController #[Route('', name: 'admin_tags_index', methods: ['GET'])] public function index( EntityManagerInterface $em, - \App\Service\TagRebuildJobService $jobs + TagRebuildJobService $jobs ): Response { - $tags = $em->getRepository(\App\Entity\Tag::class) + $tags = $em->getRepository(Tag::class) ->findBy([], ['label' => 'ASC']); return $this->render('admin/tag/index.html.twig', [ @@ -89,5 +92,71 @@ final class TagController extends AbstractController return $this->redirectToRoute('admin_tags_index'); } + #[Route('/{id}/assign', name: 'admin_tags_assign', methods: ['GET', 'POST'])] + public function assign( + string $id, + EntityManagerInterface $em, + Request $request, + TagService $tagService, + TagRebuildJobService $jobs + ): Response { + $tag = $em->getRepository(Tag::class)->find($id); + if (!$tag instanceof Tag) { + throw $this->createNotFoundException('Tag nicht gefunden.'); + } + + // Alle Dokumente laden + $documents = $em->getRepository(Document::class)->findAll(); + + $documentsData = array_map( + fn(Document $d) => [ + 'id' => (string) $d->getId(), + 'title' => $d->getTitle(), + ], + $documents + ); + + // Aktuell zugewiesene Dokumente ermitteln + $existingRelations = $em + ->getRepository(DocumentTag::class) + ->findBy(['tag' => $tag]); + + $assignedDocIds = array_map( + fn(DocumentTag $dt) => (string) $dt->getDocument()->getId(), + $existingRelations + ); + + if ($request->isMethod('POST')) { + + if (!$this->isCsrfTokenValid( + 'assign_tag_' . $tag->getId(), + $request->request->get('_token') + )) { + throw $this->createAccessDeniedException(); + } + + $selectedIds = $request->request->all('documents') ?? []; + + $tagService->syncTagDocuments($tag, $selectedIds); + + $this->addFlash('success', 'Zuweisungen aktualisiert.'); + + return $this->redirectToRoute('admin_tags_assign', [ + 'id' => $tag->getId() + ]); + } + + return $this->render('admin/tag/assign.html.twig', [ + 'tag' => $tag, + 'documents' => $documentsData, + 'assignedDocIds' => $assignedDocIds, + 'latestJob' => $jobs->getLatestJob(), + 'hasActiveJob' => $jobs->hasActiveJob(), + 'statusRunning' => TagRebuildJob::STATUS_RUNNING, + 'statusQueued' => TagRebuildJob::STATUS_QUEUED, + 'statusCompleted' => TagRebuildJob::STATUS_COMPLETED, + 'statusFailed' => TagRebuildJob::STATUS_FAILED, + ]); + } } \ No newline at end of file diff --git a/src/Tag/TagService.php b/src/Tag/TagService.php index 2dbf4bb..4a83985 100644 --- a/src/Tag/TagService.php +++ b/src/Tag/TagService.php @@ -110,6 +110,49 @@ final class TagService } } + // ========================================================= + // TAG → DOCUMENT SYNC (Bulk Assign) + // ========================================================= + + /** + * Synchronisiert alle Dokumente eines Tags. + * Löst einen Rebuild aus, da document_ids Teil des NDJSON sind. + */ + public function syncTagDocuments(Tag $tag, array $newDocumentIds): void + { + $newDocumentIds = array_unique($newDocumentIds); + + $currentRelations = $this->em + ->getRepository(DocumentTag::class) + ->findBy(['tag' => $tag]); + + $currentDocumentIds = array_map( + fn(DocumentTag $dt) => (string) $dt->getDocument()->getId(), + $currentRelations + ); + + $toAdd = array_diff($newDocumentIds, $currentDocumentIds); + $toRemove = array_diff($currentDocumentIds, $newDocumentIds); + + foreach ($toAdd as $documentId) { + $document = $this->em->getRepository(Document::class)->find($documentId); + if ($document instanceof Document) { + $this->em->persist(new DocumentTag($document, $tag)); + } + } + + foreach ($currentRelations as $relation) { + if (in_array((string) $relation->getDocument()->getId(), $toRemove, true)) { + $this->em->remove($relation); + } + } + + if ($toAdd || $toRemove) { + $this->em->flush(); + $this->triggerRebuildIfIdle(); + } + } + // ========================================================= // INTERNAL HELPERS // ========================================================= diff --git a/templates/admin/tag/assign.html.twig b/templates/admin/tag/assign.html.twig new file mode 100644 index 0000000..995af9b --- /dev/null +++ b/templates/admin/tag/assign.html.twig @@ -0,0 +1,144 @@ +{% extends 'admin/base.html.twig' %} + +{% block title %}Tag zuweisen{% endblock %} + +{% block body %} + +
+

+ Tag: {{ tag.label }} +

+ + + Zurück + +
+ + {# ========================================================= #} + {# LIVE REBUILD STATUS (SSE) #} + {# ========================================================= #} + +
+
+ Status wird geladen… +
+
+ + + + {# ============================= #} + {# Flash Messages #} + {# ============================= #} + + {% for message in app.flashes('success') %} +
+ {{ message }} +
+ {% endfor %} + + {% for message in app.flashes('danger') %} +
+ {{ message }} +
+ {% endfor %} + + {# ============================= #} + {# Tag → Dokumente #} + {# ============================= #} + +
+ + + +
+
+ + + + + + + + + + + {% for doc in documents %} + + + + + {% else %} + + + + {% endfor %} + +
Dokument
+ + + {{ doc.title }} +
+ Keine Dokumente vorhanden. +
+ +
+
+ + + +
+ +{% endblock %} \ No newline at end of file diff --git a/templates/admin/tag/index.html.twig b/templates/admin/tag/index.html.twig index 0306049..91a8df9 100644 --- a/templates/admin/tag/index.html.twig +++ b/templates/admin/tag/index.html.twig @@ -158,16 +158,26 @@ {{ tag.slug }} {{ tag.description ?: '-' }} + + + Zuweisen + +
+ action="{{ path('admin_tags_delete', {id: tag.id}) }}" + style="display:inline-block;"> + +
+ {% else %}