feat: news messages to be displayed on teamers' dashboards
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20240623161718 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE news (id INT AUTO_INCREMENT NOT NULL, status SMALLINT NOT NULL, message LONGTEXT NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('DROP TABLE news');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Administrative\System\News;
|
||||
|
||||
use App\Entity\News;
|
||||
use App\Form\NewsType;
|
||||
use App\Htmx\HxRedirectResponse;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
class CreateController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly LoggerInterface $logger
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/administrative/system/news/create', name: 'app_administrative_system_news_create')]
|
||||
#[IsGranted('ROLE_ADMINISTRATIVE')]
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$news = new News();
|
||||
$form = $this->createForm(NewsType::class, $news, [
|
||||
'hx_post' => $request->getUri(),
|
||||
]);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->entityManager->persist($news);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->addFlash('success', 'News wurden erstellt');
|
||||
$this->logger->info('Create news', [
|
||||
'news_id' => $news->getId(),
|
||||
]);
|
||||
|
||||
return new HxRedirectResponse($this->generateUrl('app_administrative_system_news_index'));
|
||||
}
|
||||
|
||||
return $this->render('administrative/system/news/modal_create.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Administrative\System\News;
|
||||
|
||||
use App\Entity\News;
|
||||
use App\Htmx\HxRedirectResponse;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
class DeleteController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly LoggerInterface $logger
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/administrative/system/news/delete/{id}', name: 'app_administrative_system_news_delete')]
|
||||
#[IsGranted('ROLE_ADMINISTRATIVE')]
|
||||
public function index(News $news, Request $request): Response
|
||||
{
|
||||
if (true === $request->isMethod('POST')) {
|
||||
$this->entityManager->remove($news);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->addFlash('success', 'Die Nachricht wurde gelöscht');
|
||||
$this->logger->info('Delete news', [
|
||||
'news' => $news->getMessage(),
|
||||
]);
|
||||
|
||||
return new HxRedirectResponse($this->generateUrl('app_administrative_system_news_index'));
|
||||
}
|
||||
|
||||
return $this->render('administrative/system/news/modal_delete.html.twig', [
|
||||
'news' => $news,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Administrative\System\News;
|
||||
|
||||
use App\Entity\News;
|
||||
use App\Form\NewsType;
|
||||
use App\Htmx\HxRedirectResponse;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
class EditController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly LoggerInterface $logger
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/administrative/system/news/edit/{id}', name: 'app_administrative_system_news_edit')]
|
||||
#[IsGranted('ROLE_ADMINISTRATIVE')]
|
||||
public function index(News $news, Request $request): Response
|
||||
{
|
||||
$form = $this->createForm(NewsType::class, $news, [
|
||||
'hx_post' => $request->getUri(),
|
||||
]);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->entityManager->persist($news);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->addFlash('success', 'News wurden aktualisiert.');
|
||||
$this->logger->info('Edit news', [
|
||||
'news_id' => $news->getId(),
|
||||
]);
|
||||
|
||||
return new HxRedirectResponse($this->generateUrl('app_administrative_system_news_index'));
|
||||
}
|
||||
|
||||
return $this->render('administrative/system/news/modal_edit.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Administrative\System\News;
|
||||
|
||||
use App\Repository\NewsRepository;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
class IndexController extends AbstractController
|
||||
{
|
||||
public function __construct(private readonly NewsRepository $repository)
|
||||
{
|
||||
}
|
||||
|
||||
#[Route('/administrative/system/news', name: 'app_administrative_system_news_index')]
|
||||
#[IsGranted('ROLE_ADMINISTRATIVE')]
|
||||
public function index(): Response
|
||||
{
|
||||
$news = $this
|
||||
->repository
|
||||
->findBy([], ['createdAt' => 'DESC'])
|
||||
;
|
||||
|
||||
$activeNews = $this
|
||||
->repository
|
||||
->getLatestForDashboard()
|
||||
;
|
||||
|
||||
return $this->render('administrative/system/news/index.html.twig', [
|
||||
'news' => $news,
|
||||
'activeNews' => $activeNews,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use App\Entity\Application;
|
||||
use App\Entity\Assignment;
|
||||
use App\Entity\Disposition;
|
||||
use App\Entity\Feedback;
|
||||
use App\Entity\News;
|
||||
use App\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
@@ -27,6 +28,12 @@ class IndexController extends AbstractController
|
||||
$user = $this->getUser();
|
||||
$teamer = $user->getTeamer();
|
||||
|
||||
$news = $this
|
||||
->entityManager
|
||||
->getRepository(News::class)
|
||||
->getLatestForDashboard()
|
||||
;
|
||||
|
||||
$matchingAssignments = $this
|
||||
->entityManager
|
||||
->getRepository(Assignment::class)
|
||||
@@ -60,6 +67,7 @@ class IndexController extends AbstractController
|
||||
$dispositionDocuments = $this->processDocuments($upcomingDispositions);
|
||||
|
||||
return $this->render('teamer/index.html.twig', [
|
||||
'news' => $news,
|
||||
'teamer' => $teamer,
|
||||
'matchingAssignments' => $matchingAssignments,
|
||||
'recentFeedback' => $recentFeedback,
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Entity\Traits\BlameableEntity;
|
||||
use App\Entity\Traits\TimestampableEntity;
|
||||
use App\Repository\NewsRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: NewsRepository::class)]
|
||||
class News implements TimestampableEntityInterface, BlameableEntityInterface
|
||||
{
|
||||
use TimestampableEntity;
|
||||
use BlameableEntity;
|
||||
|
||||
public const STATUS_DEACTIVATED = 0;
|
||||
public const STATUS_ACTIVE = 1;
|
||||
public const STATUS_IMPORTANT = 2;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(type: 'smallint')]
|
||||
private ?int $status;
|
||||
|
||||
#[ORM\Column(type: Types::TEXT)]
|
||||
private ?string $message = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->status = static::STATUS_DEACTIVATED;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getStatus(): ?int
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
public function setStatus(?int $status): static
|
||||
{
|
||||
$this->status = $status;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMessage(): ?string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
public function setMessage(?string $message): static
|
||||
{
|
||||
$this->message = $message;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\News;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class NewsType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('status', ChoiceType::class, [
|
||||
'label' => 'Status',
|
||||
'choices' => [
|
||||
'label.news.status.0' => News::STATUS_DEACTIVATED,
|
||||
'label.news.status.1' => News::STATUS_ACTIVE,
|
||||
'label.news.status.2' => News::STATUS_IMPORTANT,
|
||||
],
|
||||
])
|
||||
->add('message', TextareaType::class, [
|
||||
'label' => 'Nachricht',
|
||||
'attr' => [
|
||||
'rows' => 5,
|
||||
],
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => News::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -159,6 +159,17 @@ class AdminMenuBuilder extends AbstractMenuBuilder
|
||||
],
|
||||
],
|
||||
]);
|
||||
$settingsMenu->addChild('News', [
|
||||
'route' => 'app_administrative_system_news_index',
|
||||
'linkAttributes' => [
|
||||
'title' => 'News',
|
||||
],
|
||||
'extras' => [
|
||||
'routes' => [
|
||||
['pattern' => '/^app_administrative_system_news_/']
|
||||
],
|
||||
],
|
||||
]);
|
||||
$settingsMenu->addChild('Dokumente', [
|
||||
'route' => 'app_administrative_system_document_index',
|
||||
'linkAttributes' => [
|
||||
|
||||
@@ -94,6 +94,17 @@ class ManagerMenuBuilder extends AbstractMenuBuilder
|
||||
],
|
||||
],
|
||||
]);
|
||||
$settingsMenu->addChild('News', [
|
||||
'route' => 'app_administrative_system_news_index',
|
||||
'linkAttributes' => [
|
||||
'title' => 'News',
|
||||
],
|
||||
'extras' => [
|
||||
'routes' => [
|
||||
['pattern' => '/^app_administrative_system_news_/']
|
||||
],
|
||||
],
|
||||
]);
|
||||
$settingsMenu->addChild('Dokumente', [
|
||||
'route' => 'app_administrative_system_document_index',
|
||||
'linkAttributes' => [
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\News;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<News>
|
||||
*/
|
||||
class NewsRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, News::class);
|
||||
}
|
||||
|
||||
public function getLatestForDashboard(): ?News
|
||||
{
|
||||
$qb = $this->createQueryBuilder('news');
|
||||
|
||||
return $qb
|
||||
->where($qb->expr()->neq('news.status', ':status'))
|
||||
->orderBy('news.status', 'DESC')
|
||||
->addOrderBy('news.createdAt', 'DESC')
|
||||
->setParameter('status', News::STATUS_DEACTIVATED)
|
||||
->setMaxResults(1)
|
||||
->getQuery()
|
||||
->getOneOrNullResult()
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{{ form_start(form) }}
|
||||
<div class="flex flex-col space-y-4 pb-4">
|
||||
{{ form_row(form.status) }}
|
||||
{{ form_row(form.message) }}
|
||||
</div>
|
||||
<button type="submit" class="btn">
|
||||
Speichern
|
||||
</button>
|
||||
{{ form_rest(form) }}
|
||||
{{ form_end(form) }}
|
||||
@@ -0,0 +1,84 @@
|
||||
{% extends 'administrative/layout.html.twig' %}
|
||||
|
||||
{% block title %}News{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="text-2xl font-bold pb-8">
|
||||
News
|
||||
</h1>
|
||||
<div class="data-table-wrapper">
|
||||
<div class="data-table-wrapper__inner">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
{{ icon('eye', 'w-4 h-4') }}
|
||||
</th>
|
||||
<th class="w-1/2">
|
||||
Nachricht
|
||||
</th>
|
||||
<th>
|
||||
Status
|
||||
</th>
|
||||
<th>
|
||||
erstellt
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in news %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if item == activeNews %}
|
||||
<svg viewBox="0 0 5 5" class="{{ html_classes('h-4 w-4 fill-current', { 'text-blue-600': item.status == 1, 'text-red-600': item.status == 2 }) }}">
|
||||
<circle cx="3" cy="3" r="2" />
|
||||
</svg>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.message | u.truncate(48, '...') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ ('label.news.status.' ~ item.status) | trans }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.createdAt | date('d.m.Y') }}
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex items-center space-x-2 justify-end">
|
||||
<button type="button"
|
||||
hx-get="{{ path('app_administrative_system_news_edit', { 'id': item.id }) }}"
|
||||
hx-target="body"
|
||||
hx-swap="beforeend">
|
||||
{{ icon('edit') }}
|
||||
</button>
|
||||
<button type="button"
|
||||
class="text-red-500"
|
||||
hx-get="{{ path('app_administrative_system_news_delete', { 'id': item.id }) }}"
|
||||
hx-target="body"
|
||||
hx-swap="beforeend">
|
||||
{{ icon('delete') }}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
Keine Daten...
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button"
|
||||
class="btn"
|
||||
hx-get="{{ path('app_administrative_system_news_create') }}"
|
||||
hx-target="body"
|
||||
hx-swap="beforeend">
|
||||
Neu
|
||||
</button>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,7 @@
|
||||
{% extends 'htmx_modal.html.twig' %}
|
||||
|
||||
{% block title %}News anlegen{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'administrative/system/news/form.html.twig' %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,7 @@
|
||||
{% extends 'htmx_confirmation_modal.html.twig' %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
Möchtest du die News wirklich löschen?
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,7 @@
|
||||
{% extends 'htmx_modal.html.twig' %}
|
||||
|
||||
{% block title %}News bearbeiten{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'administrative/system/news/form.html.twig' %}
|
||||
{% endblock %}
|
||||
@@ -11,6 +11,14 @@
|
||||
{% if not teamer.photo %}
|
||||
{% include '_partials/_toast.html.twig' with { level: 'info', messages: ['Bitte lade ein Foto von dir hoch'] } %}
|
||||
{% endif %}
|
||||
{% if news is not null %}
|
||||
<div class="{{ html_classes('p-4 mb-4 flex items-start space-x-2 text-gray-100 rounded-md', { 'bg-blue-600': news.status == 1, 'bg-red-600': news.status == 2 }) }}">
|
||||
{{ icon('alert', 'w-5 h-5 flex-shrink-0') }}
|
||||
<div class="leading-relaxed text-base">
|
||||
{{ news.message | nl2br }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
<div class="bg-gray-100 border border-gray-200 rounded-md px-4 py-2">
|
||||
<h2 class="font-bold text-lg pb-2">
|
||||
|
||||
@@ -29,6 +29,11 @@ label:
|
||||
hr: Personalplanung
|
||||
management: Reisemanagement
|
||||
house_management: Hausmanagement
|
||||
news:
|
||||
status:
|
||||
0: deaktiviert
|
||||
1: aktiv
|
||||
2: wichtig
|
||||
|
||||
message:
|
||||
confirm_contract:
|
||||
|
||||
Reference in New Issue
Block a user