Split NPC page: read-only detail + game notes, separate full edit form

- /npcs/{id} (app_npc_show): portrait, all fields shown read-only, and
  the only editable control is the game-notes textarea. Father/Mother
  link through to their own pages.
- /npcs/{id}/edit (app_npc_edit): the full structured form, now WITHOUT
  game notes; saving returns to the detail page.
- NpcType drops gameNotes; new NpcGameNotesType holds just that field.
- The DataTable row link (/npcs/{id}) now lands on the detail page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Frank
2026-09-06 16:17:03 +02:00
co-authored by Claude Sonnet 5
parent 71b5cb6633
commit d43746970b
5 changed files with 148 additions and 44 deletions
+28 -2
View File
@@ -3,6 +3,7 @@
namespace App\Controller;
use App\Entity\Npc;
use App\Form\NpcGameNotesType;
use App\Form\NpcType;
use App\Repository\NpcRepository;
use Doctrine\ORM\EntityManagerInterface;
@@ -65,7 +66,32 @@ class NpcController extends AbstractController
]);
}
#[Route('/npcs/{id}', name: 'app_npc_edit', requirements: ['id' => '\d+'], methods: ['GET', 'POST'])]
/**
* In-play detail page: everything read-only, plus the game-notes field.
*/
#[Route('/npcs/{id}', name: 'app_npc_show', requirements: ['id' => '\d+'], methods: ['GET', 'POST'])]
public function show(Request $request, Npc $npc, EntityManagerInterface $em): Response
{
$form = $this->createForm(NpcGameNotesType::class, $npc);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em->flush();
$this->addFlash('success', 'Game notes saved.');
return $this->redirectToRoute('app_npc_show', ['id' => $npc->getId()]);
}
return $this->render('npc/show.html.twig', [
'npc' => $npc,
'form' => $form,
]);
}
/**
* Full edit of the NPC's structured fields (no game notes).
*/
#[Route('/npcs/{id}/edit', name: 'app_npc_edit', requirements: ['id' => '\d+'], methods: ['GET', 'POST'])]
public function edit(Request $request, Npc $npc, EntityManagerInterface $em): Response
{
$form = $this->createForm(NpcType::class, $npc);
@@ -75,7 +101,7 @@ class NpcController extends AbstractController
$em->flush();
$this->addFlash('success', sprintf('Saved %s.', $npc->getName()));
return $this->redirectToRoute('app_npc_edit', ['id' => $npc->getId()]);
return $this->redirectToRoute('app_npc_show', ['id' => $npc->getId()]);
}
return $this->render('npc/edit.html.twig', [
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace App\Form;
use App\Entity\Npc;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* The only editable thing on the NPC detail page: the DM's live play notes.
*/
class NpcGameNotesType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('gameNotes', TextareaType::class, [
'required' => false,
'label' => false,
'attr' => ['rows' => 14, 'placeholder' => 'What happened with this NPC during play…'],
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults(['data_class' => Npc::class]);
}
}
+2 -6
View File
@@ -51,12 +51,8 @@ class NpcType extends AbstractType
->add('mother', EntityType::class, $parent)
->add('villageStatus', EnumType::class, $enum(VillageStatus::class))
->add('bloodCartInvolvement', EnumType::class, $enum(BloodCartInvolvement::class))
->add('mysteryImportance', EnumType::class, $enum(MysteryImportance::class))
->add('gameNotes', TextareaType::class, [
'required' => false,
'label' => 'Game notes',
'attr' => ['rows' => 10, 'placeholder' => 'What happened with this NPC during play…'],
]);
->add('mysteryImportance', EnumType::class, $enum(MysteryImportance::class));
// gameNotes lives on the detail page, not this form.
}
public function configureOptions(OptionsResolver $resolver): void