Skip to content

Admin Panel - UI fΓΌr Stammdaten-Verwaltung ​

Layer: Dev (UI/Frontend) Purpose: Verwaltung globaler Stammdaten aus allen Layern Framework: Symfony 7.3 + EasyAdmin 4 URL: https://appisym.go4family.net/admin


🎯 Was ist das Admin Panel? ​

Das Admin Panel ist ein Web-UI (EasyAdmin) fΓΌr Administratoren zur Verwaltung von globalen Stammdaten, die das System zum Funktionieren benΓΆtigt.

Wichtig zu verstehen: ​

βœ… Admin Panel = UI/Frontend (EasyAdmin CRUDs)
βœ… Im Dev Layer (nur Controller)
βœ… Zeigt Entities aus ALLEN Layern
βœ… FΓΌr Users mit ROLE_ADMIN
βœ… Verwaltet globale Daten (Countries, Languages, Industries, etc.)

❌ NICHT ein separates System
❌ NICHT mit eigenen Entities
❌ NICHT im Infrastructure Layer

πŸ—οΈ Architektur - Wie funktioniert es? ​

Entities bleiben in ihren Layern! ​

Foundation/Geo/
β”œβ”€β”€ Entity/
β”‚   β”œβ”€β”€ Country.php          ← Entity bleibt hier!
β”‚   β”œβ”€β”€ Language.php         ← Entity bleibt hier!
β”‚   └── Currency.php         ← Entity bleibt hier!

Dev/Admin/Controller/
β”œβ”€β”€ DashboardController.php   ← Admin Dashboard
β”œβ”€β”€ CountryCrudController.php ← CRUD fΓΌr Country (zeigt auf Foundation Entity!)
β”œβ”€β”€ LanguageCrudController.php
└── CurrencyCrudController.php

Das Admin Panel erstellt NUR die Controller (UI), NICHT die Entities!


πŸ‘₯ Wer kann das Admin Panel nutzen? ​

Admins = Normale Users (aus Identity-System) mit ROLE_ADMIN

php
// Admins sind Users aus der users-Tabelle
// Mit spezieller Rolle

// User aus Identity-System (Shared Layer)
$user = $userRepository->findById(1);
$user->getRoles(); // ['ROLE_ADMIN']

// Security Check im Admin Panel
#[IsGranted('ROLE_ADMIN')]
class DashboardController extends AbstractDashboardController
{
    // Nur Users mit ROLE_ADMIN dΓΌrfen rein
}

Keine separate admins-Tabelle! Admins sind Users mit ROLE_ADMIN!


πŸ“ Dateistruktur - Was gehΓΆrt wohin? ​

βœ… RICHTIG - Entities in ihren Layern ​

src/Appi/
β”œβ”€β”€ Foundation/
β”‚   └── Geo/
β”‚       β”œβ”€β”€ Entity/
β”‚       β”‚   β”œβ”€β”€ Country.php           # Entity hier!
β”‚       β”‚   β”œβ”€β”€ Language.php          # Entity hier!
β”‚       β”‚   └── Currency.php          # Entity hier!
β”‚       β”œβ”€β”€ Repository/
β”‚       β”‚   β”œβ”€β”€ CountryRepositoryInterface.php
β”‚       β”‚   └── CountryRepository.php
β”‚       └── UseCase/
β”‚           └── CreateCountry/
β”‚               β”œβ”€β”€ CreateCountryCommand.php
β”‚               └── CreateCountryHandler.php
β”‚
β”œβ”€β”€ Core/
β”‚   └── Industry/
β”‚       └── Entity/
β”‚           └── Industry.php          # Entity hier!
β”‚
└── Dev/
    └── Admin/
        └── Controller/
            β”œβ”€β”€ DashboardController.php        # Admin Dashboard
            β”œβ”€β”€ CountryCrudController.php      # CRUD fΓΌr Country
            β”œβ”€β”€ LanguageCrudController.php     # CRUD fΓΌr Language
            β”œβ”€β”€ CurrencyCrudController.php     # CRUD fΓΌr Currency
            └── IndustryCrudController.php     # CRUD fΓΌr Industry

Migrations bleiben auch in ihren Layern:

migrations/
β”œβ”€β”€ 3_foundation/
β”‚   └── Version0001_geo.php           # Countries, Languages, Currencies hier!
└── 4_core/
    └── Version0002_industry.php      # Industries hier!

πŸ”§ Workflow: Neues Modul mit Admin-UI ​

Beispiel: Geo-Modul (Countries, Languages, Currencies) ​

Schritt 1: Entity im richtigen Layer erstellen ​

php
// βœ… Entity bleibt in Foundation/Geo
// src/Appi/Foundation/Geo/Entity/Country.php
namespace App\Appi\Foundation\Geo\Entity;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity(repositoryClass: CountryRepository::class)]
#[ORM\Table(name: 'countries')]
class Country
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private ?int $id = null;

    #[ORM\Column(type: 'string', length: 255)]
    private string $name;

    #[ORM\Column(type: 'string', length: 2)]
    private string $iso2;

    #[ORM\Column(type: 'string', length: 3)]
    private string $iso3;

    // KEIN team_id - globale Daten!

    // Getters/Setters
}

Schritt 2: Migration im richtigen Layer ​

php
// βœ… Migration bleibt in Foundation
// migrations/3_foundation/Version0001_geo.php
namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

final class Version0001_geo extends AbstractMigration
{
    public function up(Schema $schema): void
    {
        // Countries
        $this->addSql('CREATE TABLE countries (
            id SERIAL PRIMARY KEY,
            name VARCHAR(255) NOT NULL,
            iso2 VARCHAR(2) NOT NULL UNIQUE,
            iso3 VARCHAR(3) NOT NULL UNIQUE,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )');

        // Languages
        $this->addSql('CREATE TABLE languages (
            id SERIAL PRIMARY KEY,
            name VARCHAR(255) NOT NULL,
            code VARCHAR(5) NOT NULL UNIQUE,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )');

        // Currencies
        $this->addSql('CREATE TABLE currencies (
            id SERIAL PRIMARY KEY,
            name VARCHAR(255) NOT NULL,
            code VARCHAR(3) NOT NULL UNIQUE,
            symbol VARCHAR(10),
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )');
    }

    public function down(Schema $schema): void
    {
        $this->addSql('DROP TABLE currencies');
        $this->addSql('DROP TABLE languages');
        $this->addSql('DROP TABLE countries');
    }
}

Schritt 3: Repository erstellen ​

php
// src/Appi/Foundation/Geo/Repository/CountryRepositoryInterface.php
namespace App\Appi\Foundation\Geo\Repository;

use App\Appi\Foundation\Geo\Entity\Country;

interface CountryRepositoryInterface
{
    public function save(Country $country): void;
    public function findById(int $id): ?Country;
    public function findAll(): array;
}

// src/Appi/Foundation/Geo/Repository/CountryRepository.php
namespace App\Appi\Foundation\Geo\Repository;

use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;

class CountryRepository extends ServiceEntityRepository implements CountryRepositoryInterface
{
    public function save(Country $country): void
    {
        $this->getEntityManager()->persist($country);
        $this->getEntityManager()->flush();
    }

    // ... weitere Methoden
}

Schritt 4: Admin-CRUD erstellen (im Dev Layer!) ​

php
// βœ… CRUD Controller im Dev Layer
// src/Appi/Dev/Admin/Controller/CountryCrudController.php
namespace App\Appi\Dev\Admin\Controller;

use App\Appi\Foundation\Geo\Entity\Country;  // ← Import aus Foundation!
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;

class CountryCrudController extends AbstractCrudController
{
    public static function getEntityFqcn(): string
    {
        return Country::class;  // ← Zeigt auf Foundation Entity!
    }

    public function configureFields(string $pageName): iterable
    {
        yield TextField::new('name', 'Name');
        yield TextField::new('iso2', 'ISO 2');
        yield TextField::new('iso3', 'ISO 3');
    }

    public function configureCrud(Crud $crud): Crud
    {
        return $crud
            ->setEntityLabelInSingular('Country')
            ->setEntityLabelInPlural('Countries')
            ->setDefaultSort(['name' => 'ASC']);
    }
}

Schritt 5: Dashboard Integration ​

php
// src/Appi/Dev/Admin/Controller/DashboardController.php
namespace App\Appi\Dev\Admin\Controller;

use App\Appi\Foundation\Geo\Entity\Country;
use App\Appi\Foundation\Geo\Entity\Language;
use App\Appi\Foundation\Geo\Entity\Currency;
use App\Appi\Core\Industry\Entity\Industry;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[IsGranted('ROLE_ADMIN')]  // Nur Users mit ROLE_ADMIN
class DashboardController extends AbstractDashboardController
{
    #[Route('/admin', name: 'admin_dashboard')]
    public function index(): Response
    {
        return $this->render('admin/dashboard.html.twig');
    }

    public function configureDashboard(): Dashboard
    {
        return Dashboard::new()
            ->setTitle('Appiyon Admin Panel');
    }

    public function configureMenuItems(): iterable
    {
        yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home');

        // Geo-Daten (aus Foundation Layer)
        yield MenuItem::section('Geo-Daten');
        yield MenuItem::linkToCrud('Countries', 'fa fa-globe', Country::class);
        yield MenuItem::linkToCrud('Languages', 'fa fa-language', Language::class);
        yield MenuItem::linkToCrud('Currencies', 'fa fa-money-bill', Currency::class);

        // Business-Daten (aus Core Layer)
        yield MenuItem::section('Business-Daten');
        yield MenuItem::linkToCrud('Industries', 'fa fa-industry', Industry::class);

        // Weitere Sections...
    }
}

πŸ“‹ Welche Daten gehΓΆren ins Admin Panel? ​

βœ… JA - Diese Daten SOLLTEN im Admin Panel sein: ​

Globale Stammdaten (KEIN team_id):

  • Countries, Languages, Currencies (Foundation/Geo)
  • Industries, Categories, Tags (Core)
  • Contract-Templates (Domain - wenn global)
  • System-Settings
  • Audit-Logs (Read-Only)
  • System-Logs (Read-Only)

Kriterien:

  • βœ… Globale Daten (kein team_id)
  • βœ… MΓΌssen existieren BEVOR System nutzbar ist
  • βœ… Selten geΓ€ndert
  • βœ… Von Admins verwaltet

❌ NEIN - Diese Daten gehΓΆren NICHT ins Admin Panel: ​

Mandanten-/Business-Daten (mit team_id):

  • Teams (Identity-System - via App)
  • Principals/Users (Identity-System - via App)
  • CRM-EintrΓ€ge (Business-Daten - via App)
  • Team-spezifische Items (Dev Layer - via App)
  • Kundendaten (Domain - via App)

Kriterien:

  • ❌ Hat team_id
  • ❌ Business-spezifisch
  • ❌ Von normalen Users verwaltet
  • ❌ HΓ€ufig geΓ€ndert

πŸ” Security & Authentication ​

Admin-Zugriff absichern ​

php
// ALLE Admin-Controller absichern
#[IsGranted('ROLE_ADMIN')]
class DashboardController extends AbstractDashboardController
{
    // Nur Users mit ROLE_ADMIN dΓΌrfen rein
}

#[IsGranted('ROLE_ADMIN')]
class CountryCrudController extends AbstractCrudController
{
    // Auch alle CRUDs absichern
}

Security Config ​

yaml
# config/packages/security.yaml
security:
    providers:
        app_user_provider:
            entity:
                class: App\Appi\Shared\Identity\Entity\User
                property: email

    firewalls:
        admin:
            pattern: ^/admin
            provider: app_user_provider
            form_login:
                login_path: admin_login
                check_path: admin_login
            logout:
                path: admin_logout
                target: admin_login

    access_control:
        - { path: ^/admin/login, roles: PUBLIC_ACCESS }
        - { path: ^/admin, roles: ROLE_ADMIN }

Admins sind normale Users aus dem Identity-System!


🎨 Admin-Panel Template ​

twig
{# templates/admin/dashboard.html.twig #}
{% extends '@EasyAdmin/page/content.html.twig' %}

{% block content_title %}Appiyon Admin Panel{% endblock %}

{% block main %}
    <div class="row">
        <div class="col-md-3">
            <div class="card">
                <div class="card-body">
                    <h5>Countries</h5>
                    <p class="display-4">{{ stats.total_countries }}</p>
                </div>
            </div>
        </div>
        <div class="col-md-3">
            <div class="card">
                <div class="card-body">
                    <h5>Languages</h5>
                    <p class="display-4">{{ stats.total_languages }}</p>
                </div>
            </div>
        </div>
        <div class="col-md-3">
            <div class="card">
                <div class="card-body">
                    <h5>Industries</h5>
                    <p class="display-4">{{ stats.total_industries }}</p>
                </div>
            </div>
        </div>
        <div class="col-md-3">
            <div class="card">
                <div class="card-body">
                    <h5>Total Users</h5>
                    <p class="display-4">{{ stats.total_users }}</p>
                </div>
            </div>
        </div>
    </div>
{% endblock %}

πŸ“‹ Checklist: Modul mit Admin-UI hinzufΓΌgen ​

FΓΌr jedes Modul frage:

  1. Ist es global? (kein team_id) β†’ JA? Weiter!
  2. Muss es vom Admin verwaltet werden? β†’ JA? Weiter!

Dann:

  • [ ] Entity erstellen im richtigen Layer (Foundation/Core/Domain)
  • [ ] Repository erstellen (Interface + Implementation)
  • [ ] Migration erstellen im richtigen Layer-Ordner
  • [ ] Migration ausfΓΌhren + testen
  • [ ] Admin-CRUD erstellen in Dev/Admin/Controller/
  • [ ] Dashboard aktualisieren (MenuItem hinzufΓΌgen)
  • [ ] Security #[IsGranted('ROLE_ADMIN')] prΓΌfen
  • [ ] Testen im Browser: https://appisym.go4family.net/admin
  • [ ] Dokumentation aktualisieren

🚫 HΓ€ufige Fehler vermeiden ​

❌ FALSCH - Entity im Dev Layer erstellen ​

php
// ❌ FALSCH!
// src/Appi/Dev/Admin/Entity/Country.php
namespace App\Appi\Dev\Admin\Entity;

class Country  // NEIN! GehΓΆrt in Foundation!
{
    // ...
}

βœ… RICHTIG - Entity im Foundation Layer ​

php
// βœ… RICHTIG!
// src/Appi/Foundation/Geo/Entity/Country.php
namespace App\Appi\Foundation\Geo\Entity;

class Country  // JA! Richtig!
{
    // ...
}

❌ FALSCH - Separate admins-Tabelle ​

sql
-- ❌ FALSCH!
CREATE TABLE admins (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255),
    password VARCHAR(255)
);

βœ… RICHTIG - Users mit ROLE_ADMIN ​

php
// βœ… RICHTIG!
// Admins sind Users aus Identity-System
$user = new User();
$user->setRoles(['ROLE_ADMIN']);

πŸ“Š Beispiel: VollstΓ€ndige Modul-Struktur ​

Geo-Modul (Countries, Languages, Currencies)

src/Appi/Foundation/Geo/
β”œβ”€β”€ Entity/
β”‚   β”œβ”€β”€ Country.php              ← Entity hier
β”‚   β”œβ”€β”€ Language.php
β”‚   └── Currency.php
β”œβ”€β”€ Repository/
β”‚   β”œβ”€β”€ CountryRepositoryInterface.php
β”‚   β”œβ”€β”€ CountryRepository.php
β”‚   β”œβ”€β”€ LanguageRepositoryInterface.php
β”‚   β”œβ”€β”€ LanguageRepository.php
β”‚   β”œβ”€β”€ CurrencyRepositoryInterface.php
β”‚   └── CurrencyRepository.php
└── UseCase/
    └── (optional)

src/Appi/Dev/Admin/Controller/
β”œβ”€β”€ DashboardController.php       ← Admin Dashboard
β”œβ”€β”€ CountryCrudController.php     ← CRUD fΓΌr Country
β”œβ”€β”€ LanguageCrudController.php    ← CRUD fΓΌr Language
└── CurrencyCrudController.php    ← CRUD fΓΌr Currency

migrations/3_foundation/
└── Version0001_geo.php           ← Migration hier

templates/admin/
└── dashboard.html.twig           ← Dashboard Template

πŸ”— Verwandte Dokumentation ​


πŸ“ Zusammenfassung ​

Admin Panel ist:

  • βœ… Ein Web-UI (EasyAdmin)
  • βœ… Im Dev Layer (nur Controller)
  • βœ… Zeigt Entities aus allen Layern
  • βœ… FΓΌr Users mit ROLE_ADMIN
  • βœ… Verwaltet globale Stammdaten

Admin Panel ist NICHT:

  • ❌ Ein separates System
  • ❌ Mit eigenen Entities
  • ❌ Mit eigener admins-Tabelle
  • ❌ Im Infrastructure Layer

Workflow:

  1. Entity im richtigen Layer (Foundation/Core/Domain)
  2. Migration im richtigen Layer-Ordner
  3. CRUD-Controller im Dev/Admin/Controller
  4. Dashboard aktualisieren
  5. Mit ROLE_ADMIN absichern

Merke:

"Das Admin Panel ist nur das UI - die Entities bleiben in ihren Layern!"

Built with VitePress