WIP: Implement application process
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 Version20231009102628 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('ALTER TABLE assignment CHANGE benefits benefits LONGTEXT DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE assignment CHANGE benefits benefits JSON NOT NULL COMMENT \'(DC2Type:json)\'');
|
||||
}
|
||||
}
|
||||
@@ -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 Version20231009104620 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('ALTER TABLE assignment ADD show_available_dispositions TINYINT(1) NOT NULL, CHANGE available available_dispositions INT DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE assignment DROP show_available_dispositions, CHANGE available_dispositions available INT DEFAULT NULL');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Admin\Teamer\Availability;
|
||||
|
||||
use App\Controller\Traits\ReturnUrlTrait;
|
||||
use App\Entity\Availability;
|
||||
use App\Entity\Teamer;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
class DeleteController extends AbstractController
|
||||
{
|
||||
use ReturnUrlTrait;
|
||||
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly LoggerInterface $logger
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/admin/teamer/availability/delete/{teamer_uuid}/{availability_id}', name: 'app_admin_teamer_availability_delete')]
|
||||
#[IsGranted('ROLE_ADMINISTRATIVE')]
|
||||
public function index(
|
||||
#[MapEntity(mapping: ['teamer_uuid' => 'uuid'])]
|
||||
Teamer $teamer,
|
||||
#[MapEntity(mapping: ['availability_id' => 'id'])]
|
||||
Availability $availability,
|
||||
Request $request
|
||||
): Response {
|
||||
if (null === $availability->getOwner()) {
|
||||
$teamer->removeAvailability($availability);
|
||||
} elseif ($teamer === $availability->getOwner()) {
|
||||
$this->entityManager->remove($availability);
|
||||
}
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->addFlash('success', 'Der Zeitraum wurde entfernt/gelöscht.');
|
||||
$this->logger->info('Delete availability', [
|
||||
'teamer' => $teamer->getUuid(),
|
||||
]);
|
||||
|
||||
$redirectUrl = $this->getReturnUrl($request, 'app_admin_teamer_profile', ['uuid' => $teamer->getUuid()]);
|
||||
|
||||
return $this->redirect($redirectUrl);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Controller\Admin\Teamer;
|
||||
|
||||
use App\Entity\Teamer;
|
||||
use App\Service\Teamer\AvailabilityProcessor;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
@@ -14,8 +15,13 @@ class AvailabilityController extends AbstractController
|
||||
#[IsGranted('ROLE_ADMINISTRATIVE')]
|
||||
public function index(Teamer $teamer): Response
|
||||
{
|
||||
$processor = new AvailabilityProcessor();
|
||||
$availabilities = $teamer->getAvailabilities()->toArray();
|
||||
$groupedAvailabilities = $processor->groupRecords($availabilities);
|
||||
|
||||
return $this->render('admin/teamer/availability.html.twig', [
|
||||
'teamer' => $teamer,
|
||||
'groupedAvailabilities' => $groupedAvailabilities,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Teamer;
|
||||
|
||||
use App\Entity\Application;
|
||||
use App\Entity\Assignment;
|
||||
use App\Entity\User;
|
||||
use App\Form\TeamerApplicationType;
|
||||
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\Annotation\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
class ApplicationController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly LoggerInterface $logger
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/teamer/application/{uuid}', name: 'app_teamer_application')]
|
||||
#[IsGranted('ROLE_TEAMER')]
|
||||
public function index(Assignment $assignment, Request $request): Response
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $this->getUser();
|
||||
$teamer = $user->getTeamer();
|
||||
$application = new Application($assignment, $teamer);
|
||||
|
||||
$form = $this->createForm(TeamerApplicationType::class, $application);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->entityManager->persist($application);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->addFlash('success', 'Deine Bewerbung wurde entgegengenommen');
|
||||
$this->logger->info('Create application', [
|
||||
'teamer' => $teamer->getUuid(),
|
||||
'application' => $application->getUuid(),
|
||||
]);
|
||||
|
||||
return $this->redirectToRoute('app_teamer_index');
|
||||
}
|
||||
|
||||
return $this->render('teamer/application.html.twig', [
|
||||
'assignment' => $assignment,
|
||||
'teamer' => $teamer,
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Teamer;
|
||||
|
||||
use App\Entity\Assignment;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
class AssignmentController extends AbstractController
|
||||
{
|
||||
#[Route('/teamer/assignment/{uuid}', name: 'app_teamer_assignment')]
|
||||
#[IsGranted('ROLE_TEAMER')]
|
||||
public function index(Assignment $assignment): Response
|
||||
{
|
||||
return $this->render('teamer/assignment.html.twig', [
|
||||
'assignment' => $assignment,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,10 @@
|
||||
namespace App\Controller\Teamer;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Repository\AssignmentRepository;
|
||||
use Knp\Component\Pager\PaginatorInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
@@ -11,12 +14,16 @@ use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
class IndexController extends AbstractController
|
||||
{
|
||||
public function __construct(private readonly ValidatorInterface $validator)
|
||||
{}
|
||||
public function __construct(
|
||||
private readonly ValidatorInterface $validator,
|
||||
private readonly PaginatorInterface $paginator,
|
||||
private readonly AssignmentRepository $assignmentRepository
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/teamer', name: 'app_teamer_index')]
|
||||
#[IsGranted('ROLE_TEAMER')]
|
||||
public function index(): Response
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $this->getUser();
|
||||
@@ -25,7 +32,23 @@ class IndexController extends AbstractController
|
||||
// Validate teamer data to show missing data right away
|
||||
$errors = $this->validator->validate($teamer, null, ['profile_preflight']);
|
||||
|
||||
$query = $this
|
||||
->assignmentRepository
|
||||
->getListQuery()
|
||||
;
|
||||
|
||||
$pagination = $this->paginator->paginate(
|
||||
$query,
|
||||
$request->query->getInt('page', 1),
|
||||
10,
|
||||
[
|
||||
'defaultSortFieldName' => 'assignment.dateFrom',
|
||||
'defaultSortDirection' => 'asc',
|
||||
]
|
||||
);
|
||||
|
||||
return $this->render('teamer/index.html.twig', [
|
||||
'pagination' => $pagination,
|
||||
'errors' => $errors,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
|
||||
namespace App\Controller\Teamer\Profile\Availability;
|
||||
|
||||
use App\Entity\Availability;
|
||||
use App\Entity\User;
|
||||
use App\Repository\AvailabilityRepository;
|
||||
use Carbon\Carbon;
|
||||
use App\Service\Teamer\AvailabilityProcessor;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
@@ -24,39 +23,17 @@ class IndexController extends AbstractController
|
||||
$user = $this->getUser();
|
||||
$teamer = $user->getTeamer();
|
||||
|
||||
$processor = new AvailabilityProcessor();
|
||||
|
||||
$availabilities = $teamer->getAvailabilities()->toArray();
|
||||
$selectedAvailabilities = $this->groupRecords($availabilities);
|
||||
$selectedAvailabilities = $processor->groupRecords($availabilities);
|
||||
|
||||
$availabilities = $this->availabilityRepository->getSelectableForTeamer($teamer);
|
||||
$selectableAvailabilities = $this->groupRecords($availabilities);
|
||||
$selectableAvailabilities = $processor->groupRecords($availabilities);
|
||||
|
||||
return $this->render('teamer/profile/availability/index.html.twig', [
|
||||
'selectedAvailabilities' => $selectedAvailabilities,
|
||||
'selectableAvailabilities' => $selectableAvailabilities,
|
||||
]);
|
||||
}
|
||||
|
||||
private function groupRecords(array $records): array
|
||||
{
|
||||
$selectableAvailabilities = [];
|
||||
|
||||
foreach ($records as $record) {
|
||||
/** @var Availability $record */
|
||||
$dateFrom = $record->getDateFrom();
|
||||
$year = $dateFrom->format('Y');
|
||||
$month = (new Carbon($dateFrom))->locale('de_DE')->monthName;
|
||||
|
||||
if (false === isset($selectableAvailabilities[$year])) {
|
||||
$selectableAvailabilities[$year] = [];
|
||||
}
|
||||
|
||||
if (false === isset($selectableAvailabilities[$year][$month])) {
|
||||
$selectableAvailabilities[$year][$month] = [];
|
||||
}
|
||||
|
||||
$selectableAvailabilities[$year][$month][] = $record;
|
||||
}
|
||||
|
||||
return $selectableAvailabilities;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Traits;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
trait ReturnUrlTrait
|
||||
{
|
||||
public function getReturnUrl(Request $request, string $defaultRoute, array $parameters = []): string
|
||||
{
|
||||
$defaultUrl = $this->generateUrl($defaultRoute, $parameters);
|
||||
|
||||
return rawurldecode($request->query->get('r', $defaultUrl));
|
||||
}
|
||||
}
|
||||
@@ -6,37 +6,57 @@ use App\Entity\Traits\TimestampableEntity;
|
||||
use App\Repository\ApplicationRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
#[ORM\Entity(repositoryClass: ApplicationRepository::class)]
|
||||
class Application implements TimestampableEntityInterface
|
||||
{
|
||||
use TimestampableEntity;
|
||||
|
||||
public const STATUS_NEW = 'new';
|
||||
public const STATUS_PENDING = 'pending';
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'applications')]
|
||||
private ?Assignment $assignment = null;
|
||||
#[ORM\Column(type: 'string', length: 36, unique: true)]
|
||||
private string $uuid;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'applications')]
|
||||
private ?Teamer $teamer = null;
|
||||
private ?Assignment $assignment;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'applications')]
|
||||
private ?Teamer $teamer;
|
||||
|
||||
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||
private ?string $requests = null;
|
||||
|
||||
#[ORM\Column(length: 64)]
|
||||
private ?string $status = null;
|
||||
private ?string $status;
|
||||
|
||||
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||
private ?string $remarks = null;
|
||||
|
||||
public function __construct(Assignment $assignment, Teamer $teamer)
|
||||
{
|
||||
$this->uuid = Uuid::v4();
|
||||
$this->assignment = $assignment;
|
||||
$this->teamer = $teamer;
|
||||
$this->status = static::STATUS_NEW;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getUuid(): string
|
||||
{
|
||||
return $this->uuid;
|
||||
}
|
||||
|
||||
public function getAssignment(): ?Assignment
|
||||
{
|
||||
return $this->assignment;
|
||||
|
||||
+25
-11
@@ -4,7 +4,6 @@ namespace App\Entity;
|
||||
|
||||
use App\Entity\Traits\BlameableEntity;
|
||||
use App\Entity\Traits\TimestampableEntity;
|
||||
use App\Model\BenefitsDto;
|
||||
use App\Repository\AssignmentRepository;
|
||||
use Carbon\CarbonPeriod;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
@@ -38,7 +37,10 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
#[Assert\Range(min: 1, minMessage: 'Die Mindestanzahl ist 1')]
|
||||
private ?int $available = 1;
|
||||
private ?int $availableDispositions = 1;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?bool $showAvailableDispositions = false;
|
||||
|
||||
#[ORM\Column(type: Types::DATE_IMMUTABLE, nullable: true)]
|
||||
private ?\DateTimeImmutable $dateFrom = null;
|
||||
@@ -53,8 +55,8 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?int $pickup = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private array $benefits;
|
||||
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||
private ?string $benefits;
|
||||
|
||||
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||
private ?string $remarks = null;
|
||||
@@ -78,7 +80,7 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
|
||||
$this->fees = new ArrayCollection();
|
||||
$this->applications = new ArrayCollection();
|
||||
$this->dispositions = new ArrayCollection();
|
||||
$this->benefits = BenefitsDto::$defaults;
|
||||
$this->benefits = "Anreise im E&P Reisebus\nUnterkunft\nVerpflegung\nSkipass\n";
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
@@ -115,14 +117,26 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAvailable(): ?int
|
||||
public function getAvailableDispositions(): ?int
|
||||
{
|
||||
return $this->available;
|
||||
return $this->availableDispositions;
|
||||
}
|
||||
|
||||
public function setAvailable(?int $available): static
|
||||
public function setAvailableDispositions(?int $availableDispositions): static
|
||||
{
|
||||
$this->available = $available;
|
||||
$this->availableDispositions = $availableDispositions;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isShowAvailableDispositions(): ?bool
|
||||
{
|
||||
return $this->showAvailableDispositions;
|
||||
}
|
||||
|
||||
public function setShowAvailableDispositions(bool $showAvailableDispositions): static
|
||||
{
|
||||
$this->showAvailableDispositions = $showAvailableDispositions;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -184,12 +198,12 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBenefits(): array
|
||||
public function getBenefits(): ?string
|
||||
{
|
||||
return $this->benefits;
|
||||
}
|
||||
|
||||
public function setBenefits(array $benefits): static
|
||||
public function setBenefits(?string $benefits): static
|
||||
{
|
||||
$this->benefits = $benefits;
|
||||
|
||||
|
||||
@@ -692,6 +692,11 @@ class Teamer implements TimestampableEntityInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasPickup(int $pickupId): bool
|
||||
{
|
||||
return in_array($pickupId, $this->getPickups());
|
||||
}
|
||||
|
||||
public function getLanguage(): ?string
|
||||
{
|
||||
return $this->language;
|
||||
|
||||
@@ -7,11 +7,10 @@ use App\Entity\Destination;
|
||||
use App\Entity\Fee;
|
||||
use App\Entity\JobProfile;
|
||||
use App\Entity\User;
|
||||
use App\Model\BenefitsDto;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
@@ -22,8 +21,12 @@ class AssignmentType extends AbstractType
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('available', IntegerType::class, [
|
||||
'label' => 'Anzahl',
|
||||
->add('availableDispositions', IntegerType::class, [
|
||||
'label' => 'zu vergeben',
|
||||
'required' => false,
|
||||
])
|
||||
->add('showAvailableDispositions', CheckboxType::class, [
|
||||
'label' => 'Anzahl anzeigen',
|
||||
'required' => false,
|
||||
])
|
||||
->add('destination', AutocompleteEntityType::class, [
|
||||
@@ -56,12 +59,12 @@ class AssignmentType extends AbstractType
|
||||
'required' => false,
|
||||
'placeholder' => 'Keine Busbegleitung',
|
||||
])
|
||||
->add('benefits', ChoiceType::class, [
|
||||
->add('benefits', TextareaType::class, [
|
||||
'label' => 'Benefits',
|
||||
'required' => false,
|
||||
'multiple' => true,
|
||||
'expanded' => true,
|
||||
'choices' => array_flip(BenefitsDto::$values),
|
||||
'attr' => [
|
||||
'rows' => 5,
|
||||
],
|
||||
])
|
||||
->add('fees', EntityType::class, [
|
||||
'label' => 'Honorar(e)',
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\Application;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\IsTrue;
|
||||
|
||||
class TeamerApplicationType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
|
||||
/** @var Application $application */
|
||||
$application = $event->getData();
|
||||
|
||||
if (null === $application) {
|
||||
return;
|
||||
}
|
||||
|
||||
$assignment = $application->getAssignment();
|
||||
|
||||
if (null === $assignment->getPickup()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$teamer = $application->getTeamer();
|
||||
|
||||
if (in_array($assignment->getPickup(), $teamer->getPickups())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->getForm()->add('confirmPickup', CheckboxType::class, [
|
||||
'label' => 'Ich bestätige den abweichenden Buszustieg',
|
||||
'mapped' => false,
|
||||
'constraints' => [
|
||||
new IsTrue([
|
||||
'message' => 'Deine Bestätigung ist erforderlich',
|
||||
]),
|
||||
],
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Application::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,14 @@ class TeamerMenuBuilder extends AbstractMenuBuilder
|
||||
'route' => 'app_teamer_index',
|
||||
'title' => 'Einsatzübersicht',
|
||||
'icon' => 'calendar',
|
||||
'hideChildren' => true,
|
||||
'children' => [
|
||||
[
|
||||
'route' => 'app_teamer_assignment',
|
||||
'title' => 'Einsatzdetails',
|
||||
'routeParameters' => $this->getDefaultRouteParameters('uuid'),
|
||||
]
|
||||
],
|
||||
],
|
||||
[
|
||||
'route' => false,
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
class BenefitsDto
|
||||
{
|
||||
public static array $defaults = [
|
||||
'anreise',
|
||||
'unterkunft',
|
||||
'verpflegung',
|
||||
'skipass',
|
||||
];
|
||||
|
||||
public static array $values = [
|
||||
'anreise' => 'Anreise im E&P Reisebus',
|
||||
'unterkunft' => 'Unterkunft',
|
||||
'verpflegung' => 'Verpflegung',
|
||||
'skipass' => 'Skipass',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\Teamer;
|
||||
|
||||
use App\Entity\Availability;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class AvailabilityProcessor
|
||||
{
|
||||
/**
|
||||
* Groups provided unsorted availabilities by year and month
|
||||
* [
|
||||
* '2023' => [
|
||||
* 'Januar' => [
|
||||
* ...
|
||||
* ],
|
||||
* 'Juni' => [
|
||||
* ...
|
||||
* ],
|
||||
* ],
|
||||
* ]
|
||||
*/
|
||||
public function groupRecords(array $records): array
|
||||
{
|
||||
$availabilities = [];
|
||||
|
||||
foreach ($records as $record) {
|
||||
/** @var Availability $record */
|
||||
$dateFrom = $record->getDateFrom();
|
||||
$year = $dateFrom->format('Y');
|
||||
$month = (new Carbon($dateFrom))->locale('de_DE')->monthName;
|
||||
|
||||
if (false === isset($availabilities[$year])) {
|
||||
$availabilities[$year] = [];
|
||||
}
|
||||
|
||||
if (false === isset($availabilities[$year][$month])) {
|
||||
$availabilities[$year][$month] = [];
|
||||
}
|
||||
|
||||
$availabilities[$year][$month][] = $record;
|
||||
}
|
||||
|
||||
return $availabilities;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ class AppExtension extends AbstractExtension
|
||||
public function getFilters(): array
|
||||
{
|
||||
return [
|
||||
new TwigFilter('nl2list', [AppRuntime::class, 'nl2List'], ['is_safe' => ['html']]),
|
||||
new TwigFilter('file_size', [AppRuntime::class, 'formatBytes']),
|
||||
new TwigFilter('file_icon', [AppRuntime::class, 'fileIconFilter'], ['is_safe' => ['html']]),
|
||||
new TwigFilter('date_diff', [AppRuntime::class, 'dateDiffForHumans']),
|
||||
@@ -19,7 +20,6 @@ class AppExtension extends AbstractExtension
|
||||
new TwigFilter('teamer_status_label', [AppRuntime::class, 'teamerStatusLabel']),
|
||||
new TwigFilter('bpn_country_label', [AppRuntime::class, 'bpnCountryLabel']),
|
||||
new TwigFilter('bpn_pickup_label', [AppRuntime::class, 'bpnPickupLabel']),
|
||||
new TwigFilter('bpn_destination_label', [AppRuntime::class, 'bpnDestinationLabel']),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ class AppExtension extends AbstractExtension
|
||||
return [
|
||||
new TwigFunction('icon', [AppRuntime::class, 'renderIcon'], ['needs_environment' => true, 'is_safe' => ['html']]),
|
||||
new TwigFunction('is_current_route', [AppRuntime::class, 'isCurrentRoute']),
|
||||
new TwigFunction('return_url', [AppRuntime::class, 'getEncodedReturnUrl']),
|
||||
new TwigFunction('qa_attribute', [AppRuntime::class, 'renderQaAttribute'], ['is_safe' => ['html']]),
|
||||
];
|
||||
}
|
||||
|
||||
+30
-20
@@ -3,9 +3,7 @@
|
||||
namespace App\Twig;
|
||||
|
||||
use App\BusProNet\DataProvider\CountryDataProvider;
|
||||
use App\BusProNet\DataProvider\HotelDataProvider;
|
||||
use App\BusProNet\DataProvider\PickupDataProvider;
|
||||
use App\BusProNet\Model\Country;
|
||||
use App\Entity\Teamer;
|
||||
use Carbon\Carbon;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
@@ -16,12 +14,11 @@ use Twig\Extra\Intl\IntlExtension;
|
||||
class AppRuntime implements RuntimeExtensionInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly RequestStack $requestStack,
|
||||
private readonly IntlExtension $intlExtension,
|
||||
private readonly RequestStack $requestStack,
|
||||
private readonly IntlExtension $intlExtension,
|
||||
private readonly CountryDataProvider $countries,
|
||||
private readonly PickupDataProvider $pickups,
|
||||
private readonly HotelDataProvider $hotels,
|
||||
private readonly string $environment
|
||||
private readonly PickupDataProvider $pickups,
|
||||
private readonly string $environment
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -47,7 +44,7 @@ class AppRuntime implements RuntimeExtensionInterface
|
||||
return 'unbekannt';
|
||||
}
|
||||
|
||||
return 'nationality' === $property ? $country->getNationality() : $country->getName();
|
||||
return 'nationality' === $property ? $country->getNationality() : $country->getName();
|
||||
}
|
||||
|
||||
public function bpnPickupLabel(int $id): string
|
||||
@@ -61,17 +58,6 @@ class AppRuntime implements RuntimeExtensionInterface
|
||||
return $pickup->getCity();
|
||||
}
|
||||
|
||||
public function bpnDestinationLabel(int $id): string
|
||||
{
|
||||
$hotel = $this->hotels->get($id);
|
||||
|
||||
if (null === $hotel) {
|
||||
return 'unbekannt';
|
||||
}
|
||||
|
||||
return sprintf('%s, %s', $hotel->getName(), $hotel->getCity() ?? '-');
|
||||
}
|
||||
|
||||
public function fileIconFilter(Environment $environment, string $mimeType, ?string $classes = 'w-4 h-4'): string
|
||||
{
|
||||
$icon = match ($mimeType) {
|
||||
@@ -89,7 +75,7 @@ class AppRuntime implements RuntimeExtensionInterface
|
||||
$size = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
$factor = floor((strlen($bytes) - 1) / 3);
|
||||
|
||||
return sprintf("%.{$precision}f", $bytes / (1024 ** $factor)).@$size[$factor];
|
||||
return sprintf("%.{$precision}f", $bytes / (1024 ** $factor)) . @$size[$factor];
|
||||
}
|
||||
|
||||
public function formatMoney(int $amount): string
|
||||
@@ -108,6 +94,19 @@ class AppRuntime implements RuntimeExtensionInterface
|
||||
]);
|
||||
}
|
||||
|
||||
public function nl2List(string $content, string $class = 'list-disc pl-4'): string
|
||||
{
|
||||
$items = explode(PHP_EOL, $content);
|
||||
|
||||
$list = '<ul class="'.$class.'">';
|
||||
foreach ($items as $item) {
|
||||
$list .= '<li>'.$item.'</li>';
|
||||
}
|
||||
$list .= '<ul>';
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
public function isCurrentRoute(string $route): bool
|
||||
{
|
||||
$requestedRoute = $this->requestStack->getMainRequest()->attributes->get('_route');
|
||||
@@ -115,6 +114,17 @@ class AppRuntime implements RuntimeExtensionInterface
|
||||
return $requestedRoute === $route;
|
||||
}
|
||||
|
||||
public function getEncodedReturnUrl(): string
|
||||
{
|
||||
$masterRequest = $this->requestStack->getMainRequest();
|
||||
|
||||
if (null === $masterRequest) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return rawurlencode($masterRequest->getRequestUri());
|
||||
}
|
||||
|
||||
public function renderQaAttribute(string $label, string $value = null): string
|
||||
{
|
||||
if ('test' !== $this->environment) {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
{{ form_start(form) }}
|
||||
<div class="flex flex-col space-y-4 pb-8">
|
||||
<div class="grid lg:grid-cols-4 gap-x-8 gap-y-4">
|
||||
<div class="grid lg:grid-cols-5 gap-x-8 gap-y-4">
|
||||
<div class="lg:col-span-3">
|
||||
{{ form_row(form.destination) }}
|
||||
</div>
|
||||
{{ form_row(form.available) }}
|
||||
{{ form_row(form.availableDispositions) }}
|
||||
{{ form_row(form.showAvailableDispositions) }}
|
||||
</div>
|
||||
<div class="grid lg:grid-cols-2 gap-x-8 gap-y-4">
|
||||
{{ form_row(form.dateFrom) }}
|
||||
|
||||
@@ -11,6 +11,45 @@
|
||||
<h1 class="text-2xl font-bold pb-8">
|
||||
Verfügbarkeit {{ teamer }}
|
||||
</h1>
|
||||
<div class="grid md:grid-cols-2 gap-8 pb-8">
|
||||
{% for year, months in groupedAvailabilities %}
|
||||
<div>
|
||||
<h3 class="text-lg font-bold">
|
||||
{{ year }}
|
||||
</h3>
|
||||
<div class="flex flex-col space-y-3">
|
||||
{% for month, availabilities in months %}
|
||||
<div>
|
||||
<h4 class="font-bold">
|
||||
{{ month }}
|
||||
</h4>
|
||||
{% for availability in availabilities %}
|
||||
<div class="flex items-center space-x-2">
|
||||
<span>{{ availability }}</span>
|
||||
{% if is_granted('DELETE', availability) %}
|
||||
<button type="button"
|
||||
title="Zeitraum entfernen/löschen"
|
||||
{{ stimulus_controller('modal-button', [], [], {'confirmation-modal': '#confirmation-modal'}) }}
|
||||
{{ stimulus_action('modal-button', 'confirmation', null, {
|
||||
'title': 'Bist du sicher?',
|
||||
'content': 'Möchtest du den Zeitraum wirklich löschen?',
|
||||
'target-url': path('app_admin_teamer_availability_delete', { 'teamer_uuid': teamer.uuid, 'availability_id': availability.id, 'r': return_url() })
|
||||
}) }}>
|
||||
{{ icon('delete', 'w-5 h-5') }}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="md:col-span-2 text-sm">
|
||||
{{ teamer.firstName }} hat bisher keine Verfügbarkeiten hinterlegt.
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -75,7 +75,7 @@
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-2 gap-8">
|
||||
<div>
|
||||
<h2 class="text-lg font-bold pb-2">
|
||||
Mögliche Jobprofile
|
||||
@@ -85,6 +85,12 @@
|
||||
<li>
|
||||
{{ profile.name }}
|
||||
</li>
|
||||
{% else %}
|
||||
<li>
|
||||
<div class="text-sm">
|
||||
{{ teamer.firstName }} hat bisher keine Job-Profile ausgewählt.
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -1,42 +1,44 @@
|
||||
<div class="bg-gray-100 border border-gray-300 border-t-0 px-4 py-3 flex items-center justify-between sm:px-4">
|
||||
<div class="flex-1 flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm leading-5 text-gray-700">{{ 'paginator.info'|trans({ '{from}': firstItemNumber, '{to}': lastItemNumber, '{total}': totalCount })|raw }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<nav class="relative z-0 inline-flex">
|
||||
{% if pageCount > 1 %}
|
||||
{% if previous is defined %}
|
||||
<a href="{{ path(route, query|merge({(pageParameterName): previous})) }}" class="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium hover:bg-secondary hover:text-white" aria-label="Previous">
|
||||
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</a>
|
||||
{% if pageCount > 0 %}
|
||||
<div class="bg-gray-100 border border-gray-300 border-t-0 px-4 py-3 flex items-center justify-between sm:px-4">
|
||||
<div class="flex-1 flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm leading-5 text-gray-700">{{ 'paginator.info'|trans({ '{from}': firstItemNumber, '{to}': lastItemNumber, '{total}': totalCount })|raw }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<nav class="relative z-0 inline-flex">
|
||||
{% if pageCount > 1 %}
|
||||
{% if previous is defined %}
|
||||
<a href="{{ path(route, query|merge({(pageParameterName): previous})) }}" class="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium hover:bg-secondary hover:text-white" aria-label="Previous">
|
||||
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if current > 3 %}
|
||||
<span class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700">
|
||||
...
|
||||
</span>
|
||||
{% endif %}
|
||||
{% for page in pagesInRange %}
|
||||
<a href="{{ path(route, query|merge({(pageParameterName): page})) }}" class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm leading-5 font-medium focus:z-10 hover:bg-secondary {% if page == current %}bg-primary text-white hover:text-white{% else %}bg-white text-gray-700 hover:text-white{% endif %}">
|
||||
{{ page }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% if current < pageCount - 3 %}
|
||||
<span class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700">
|
||||
...
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if next is defined %}
|
||||
<a href="{{ path(route, query|merge({(pageParameterName): next})) }}" class="-ml-px relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium hover:bg-secondary hover:text-white" aria-label="Next">
|
||||
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if current > 3 %}
|
||||
<span class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700">
|
||||
...
|
||||
</span>
|
||||
{% endif %}
|
||||
{% for page in pagesInRange %}
|
||||
<a href="{{ path(route, query|merge({(pageParameterName): page})) }}" class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm leading-5 font-medium focus:z-10 hover:bg-secondary {% if page == current %}bg-primary text-white hover:text-white{% else %}bg-white text-gray-700 hover:text-white{% endif %}">
|
||||
{{ page }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% if current < pageCount - 3 %}
|
||||
<span class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700">
|
||||
...
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if next is defined %}
|
||||
<a href="{{ path(route, query|merge({(pageParameterName): next})) }}" class="-ml-px relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium hover:bg-secondary hover:text-white" aria-label="Next">
|
||||
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</nav>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,101 @@
|
||||
{% extends 'teamer/layout.html.twig' %}
|
||||
|
||||
{% block title %}Bewerbung{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="text-2xl font-bold pb-8">
|
||||
Bewerbung
|
||||
</h1>
|
||||
<div class="grid grid-cols-2 gap-8 items-start">
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-3 bg-gray-100 rounded-md p-4">
|
||||
<div class="font-bold text-xl">
|
||||
Jobprofil
|
||||
</div>
|
||||
<div class="font-bold text-xl">
|
||||
{{ assignment.jobProfile.name }}
|
||||
</div>
|
||||
<div>
|
||||
Destination
|
||||
</div>
|
||||
<div>
|
||||
{{ assignment.destination.product }}
|
||||
<br>
|
||||
{{ assignment.destination.hotel }}
|
||||
</div>
|
||||
<div>
|
||||
Einsatztermin
|
||||
</div>
|
||||
<div>
|
||||
{{ assignment.totalPeriod.start|date('d.m.Y') }} - {{ assignment.totalPeriod.end|date('d.m.Y') }}
|
||||
</div>
|
||||
{% if assignment.pickup and assignment.pickupDate %}
|
||||
<div>
|
||||
Busabfahrt
|
||||
</div>
|
||||
<div>
|
||||
{{ assignment.pickupDate|date('d.m.Y') }}
|
||||
</div>
|
||||
<div>
|
||||
Busbegleitung
|
||||
</div>
|
||||
<div>
|
||||
ab {{ assignment.pickup|bpn_pickup_label }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
Du bekommst
|
||||
</div>
|
||||
<div>
|
||||
{{ assignment.benefits|nl2list }}
|
||||
</div>
|
||||
<div>
|
||||
Honorar
|
||||
</div>
|
||||
<div>
|
||||
{% for fee in assignment.fees %}
|
||||
{{ fee.name }} {{ fee.value|format_money }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-xl font-bold pb-2">
|
||||
Voraussetzungen für diesen Einsatz
|
||||
</h2>
|
||||
<ul class="list-disc pl-4 pb-4">
|
||||
{% if assignment.pickup and assignment.pickupDate %}
|
||||
<li>
|
||||
<div class="flex items-center space-x-2">
|
||||
<span>Busbegleitung ab {{ assignment.pickup|bpn_pickup_label }} am {{ assignment.pickupDate|date('d.m.Y') }}</span>
|
||||
{% if teamer.hasPickup(assignment.pickup) %}
|
||||
{{ icon('check', 'w-5 h-5 text-emerald-500') }}
|
||||
{% else %}
|
||||
{{ icon('close', 'w-5 h-5 text-red-500') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% for training in assignment.jobProfile.requiredTrainings %}
|
||||
<li>
|
||||
<div class="flex items-center space-x-2">
|
||||
<span>{{ training.name }} absolviert</span>
|
||||
{% if teamer.trainingAttendance(training) %}
|
||||
{{ icon('check', 'w-5 h-5 text-emerald-500') }}
|
||||
{% else %}
|
||||
{{ icon('close', 'w-5 h-5 text-red-500') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{{ form_start(form) }}
|
||||
{% if form.confirmPickup is defined %}
|
||||
{{ form_row(form.confirmPickup) }}
|
||||
{% endif %}
|
||||
<button type="submit" class="btn">
|
||||
Bewerbung einreichen
|
||||
</button>
|
||||
{{ form_rest(form) }}
|
||||
{{ form_end(form) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,84 @@
|
||||
{% extends 'teamer/layout.html.twig' %}
|
||||
|
||||
{% block title %}Einsatzübersicht{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="text-2xl font-bold pb-8">
|
||||
Einsatzübersicht
|
||||
</h1>
|
||||
<div class="grid grid-cols-2 gap-8 items-start">
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-3 bg-gray-100 rounded-md p-4">
|
||||
<div class="font-bold text-xl">
|
||||
Jobprofil
|
||||
</div>
|
||||
<div class="font-bold text-xl">
|
||||
{{ assignment.jobProfile.name }}
|
||||
</div>
|
||||
<div>
|
||||
Destination
|
||||
</div>
|
||||
<div>
|
||||
{{ assignment.destination.product }}
|
||||
<br>
|
||||
{{ assignment.destination.hotel }}
|
||||
</div>
|
||||
<div>
|
||||
Einsatztermin
|
||||
</div>
|
||||
<div>
|
||||
{{ assignment.totalPeriod.start|date('d.m.Y') }} - {{ assignment.totalPeriod.end|date('d.m.Y') }}
|
||||
</div>
|
||||
{% if assignment.pickup and assignment.pickupDate %}
|
||||
<div>
|
||||
Busabfahrt
|
||||
</div>
|
||||
<div>
|
||||
{{ assignment.pickupDate|date('d.m.Y') }}
|
||||
</div>
|
||||
<div>
|
||||
Busbegleitung
|
||||
</div>
|
||||
<div>
|
||||
ab {{ assignment.pickup|bpn_pickup_label }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
Du bekommst
|
||||
</div>
|
||||
<div>
|
||||
{{ assignment.benefits|nl2list }}
|
||||
</div>
|
||||
<div>
|
||||
Honorar
|
||||
</div>
|
||||
<div>
|
||||
{% for fee in assignment.fees %}
|
||||
{{ fee.name }} {{ fee.value|format_money }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-xl font-bold pb-2">
|
||||
Voraussetzungen für diesen Einsatz
|
||||
</h2>
|
||||
<ul class="list-disc pl-4 pb-4">
|
||||
{% if assignment.pickup and assignment.pickupDate %}
|
||||
<li>
|
||||
Busbegleitung ab {{ assignment.pickup|bpn_pickup_label }} am {{ assignment.pickupDate|date('d.m.Y') }}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% for training in assignment.jobProfile.requiredTrainings %}
|
||||
<li>
|
||||
{{ training.name }} absolviert
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p class="pb-4">
|
||||
Erfüllst du mehrere oder alle Voraussetzungen für diesen Einsatz?
|
||||
</p>
|
||||
<a href="{{ path('app_teamer_application', { 'uuid': assignment.uuid }) }}" class="btn">
|
||||
Hier bewerben!
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,8 +1,11 @@
|
||||
{% extends 'teamer/layout.html.twig' %}
|
||||
|
||||
{% block title %}Mein Dashboard{% endblock %}
|
||||
{% block title %}Einsatzübersicht{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="text-2xl font-bold pb-8">
|
||||
Einsatzübersicht
|
||||
</h1>
|
||||
{% if errors|length %}
|
||||
<div class="border border-red-500 rounded-md p-4 text-red-500 mb-8">
|
||||
<ul>
|
||||
@@ -14,4 +17,76 @@
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="data-table-wrapper">
|
||||
<div class="data-table-wrapper__inner">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
{{ knp_pagination_sortable(pagination, 'Einsatz­beginn', 'assignment.dateFrom') }}
|
||||
</th>
|
||||
<th>
|
||||
{{ knp_pagination_sortable(pagination, 'Einsatz­ende', 'assignment.dateTo') }}
|
||||
</th>
|
||||
<th>
|
||||
{{ knp_pagination_sortable(pagination, 'Jobprofil', 'assignment.jobProfile') }}
|
||||
</th>
|
||||
<th>
|
||||
{{ knp_pagination_sortable(pagination, 'Destination', 'destination.dateFrom') }}
|
||||
</th>
|
||||
<th>
|
||||
{{ knp_pagination_sortable(pagination, 'Busbegleitung ab', 'assignment.pickup') }}
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for assignment in pagination %}
|
||||
<tr>
|
||||
<td>
|
||||
{% set dateFrom = assignment.dateFrom ? assignment.dateFrom : assignment.destination.dateFrom %}
|
||||
{{ dateFrom|date('d.m.Y') }}
|
||||
</td>
|
||||
<td>
|
||||
{% set dateTo = assignment.dateTo ? assignment.dateTo : assignment.destination.dateTo %}
|
||||
{{ dateTo|date('d.m.Y') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ assignment.jobProfile.name }}
|
||||
</td>
|
||||
<td>
|
||||
{{ assignment.destination.product }}
|
||||
<br>
|
||||
{{ assignment.destination.hotel }}
|
||||
</td>
|
||||
<td>
|
||||
{{ assignment.pickup ? assignment.pickup|bpn_pickup_label|default('-') : '-' }}
|
||||
<br>
|
||||
{{ assignment.pickupDate ? assignment.pickupDate|date('d.m.Y') : '' }}
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex items-center space-x-1 justify-end">
|
||||
<a href="{{ path('app_teamer_assignment', { 'uuid': assignment.uuid }) }}">
|
||||
{{ icon('info', 'w-5 h-5') }}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="6">
|
||||
<div class="text-center">
|
||||
Keine Daten
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ knp_pagination_render(pagination) }}
|
||||
</div>
|
||||
</div>
|
||||
<a href="{{ path('app_admin_assignment_create') }}" class="btn">
|
||||
Neu
|
||||
</a>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user