feat: dedicated dashboard widgets for contracts and invoices
This commit is contained in:
@@ -38,9 +38,12 @@ class IndexController extends AbstractController
|
||||
$availabilities = $this->entityManager->getRepository(Availability::class)->getNew();
|
||||
|
||||
$uploadRepository = $this->entityManager->getRepository(Upload::class);
|
||||
$documents = $uploadRepository->getNew();
|
||||
$newDocumentsCount = $uploadRepository->getCountByStatus(Upload::STATUS_NEW);
|
||||
$pendingDocumentsCount = $uploadRepository->getCountByStatus(Upload::STATUS_PENDING);
|
||||
$newContracts = $uploadRepository->getNewByType(Upload::TYPE_CONTRACT);
|
||||
$newContractsCount = $uploadRepository->getCountByStatusAndType(Upload::STATUS_NEW, Upload::TYPE_CONTRACT);
|
||||
$pendingContractsCount = $uploadRepository->getCountByStatusAndType(Upload::STATUS_PENDING, Upload::TYPE_CONTRACT);
|
||||
$newInvoices = $uploadRepository->getNewByType(Upload::TYPE_INVOICE);
|
||||
$newInvoicesCount = $uploadRepository->getCountByStatusAndType(Upload::STATUS_NEW, Upload::TYPE_INVOICE);
|
||||
$pendingInvoicesCount = $uploadRepository->getCountByStatusAndType(Upload::STATUS_PENDING, Upload::TYPE_INVOICE);
|
||||
|
||||
$dispositionRepository = $this->entityManager->getRepository(Disposition::class);
|
||||
$overdueContracts = $dispositionRepository->findOverdueContracts();
|
||||
@@ -55,9 +58,12 @@ class IndexController extends AbstractController
|
||||
'pendingFeedbacksCount' => $pendingFeedbacksCount,
|
||||
'assignments' => $assignments,
|
||||
'availabilities' => $availabilities,
|
||||
'documents' => $documents,
|
||||
'newDocumentsCount' => $newDocumentsCount,
|
||||
'pendingDocumentsCount' => $pendingDocumentsCount,
|
||||
'newContracts' => $newContracts,
|
||||
'newContractsCount' => $newContractsCount,
|
||||
'pendingContractsCount' => $pendingContractsCount,
|
||||
'newInvoices' => $newInvoices,
|
||||
'newInvoicesCount' => $newInvoicesCount,
|
||||
'pendingInvoicesCount' => $pendingInvoicesCount,
|
||||
'overdueContracts' => $overdueContracts,
|
||||
'newDispositions' => $newDispositions,
|
||||
'overdueFeedbacks' => $overdueFeedbacks,
|
||||
|
||||
@@ -22,16 +22,30 @@ class IndexController extends AbstractController
|
||||
{
|
||||
$dispositions = $this->dispositionRepository->getNew();
|
||||
|
||||
$documents = $this->uploadRepository->getNew();
|
||||
$newDocumentsCount = $this->uploadRepository->getCountByStatus(Upload::STATUS_NEW);
|
||||
$pendingDocumentsCount = $this->uploadRepository->getCountByStatus(Upload::STATUS_PENDING);
|
||||
$newContracts = $this->uploadRepository->getNewByType(Upload::TYPE_CONTRACT);
|
||||
$newContractsCount = $this
|
||||
->uploadRepository
|
||||
->getCountByStatusAndType(Upload::STATUS_NEW, Upload::TYPE_CONTRACT);
|
||||
$pendingContractsCount = $this
|
||||
->uploadRepository
|
||||
->getCountByStatusAndType(Upload::STATUS_PENDING, Upload::TYPE_CONTRACT);
|
||||
$newInvoices = $this->uploadRepository->getNewByType(Upload::TYPE_INVOICE);
|
||||
$newInvoicesCount = $this
|
||||
->uploadRepository
|
||||
->getCountByStatusAndType(Upload::STATUS_NEW, Upload::TYPE_INVOICE);
|
||||
$pendingInvoicesCount = $this
|
||||
->uploadRepository
|
||||
->getCountByStatusAndType(Upload::STATUS_PENDING, Upload::TYPE_INVOICE);
|
||||
$overdueFeedbacks = $this->dispositionRepository->findDispositionsWithOverdueFeedback(7);
|
||||
|
||||
return $this->render('manager/index.html.twig', [
|
||||
'dispositions' => $dispositions,
|
||||
'documents' => $documents,
|
||||
'newDocumentsCount' => $newDocumentsCount,
|
||||
'pendingDocumentsCount' => $pendingDocumentsCount,
|
||||
'newContracts' => $newContracts,
|
||||
'newContractsCount' => $newContractsCount,
|
||||
'pendingContractsCount' => $pendingContractsCount,
|
||||
'newInvoices' => $newInvoices,
|
||||
'newInvoicesCount' => $newInvoicesCount,
|
||||
'pendingInvoicesCount' => $pendingInvoicesCount,
|
||||
'overdueFeedbacks' => $overdueFeedbacks,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ class UploadRepository extends ServiceEntityRepository
|
||||
return $qb->getQuery();
|
||||
}
|
||||
|
||||
public function getNew(int $limit = 5): array
|
||||
public function getNewByType(string $type): array
|
||||
{
|
||||
$qb = $this->createQueryBuilder('upload');
|
||||
|
||||
@@ -158,29 +158,28 @@ class UploadRepository extends ServiceEntityRepository
|
||||
->leftJoin('upload.disposition', 'disposition')
|
||||
->leftJoin('disposition.assignment', 'assignment')
|
||||
->where($qb->expr()->andX(
|
||||
$qb->expr()->in('upload.type', ':type'),
|
||||
$qb->expr()->eq('upload.type', ':type'),
|
||||
$qb->expr()->in('upload.status', ':status')
|
||||
))
|
||||
->orderBy('upload.createdAt', 'DESC')
|
||||
->setParameter('type', [Upload::TYPE_CONTRACT, Upload::TYPE_INVOICE])
|
||||
->setParameter('type', $type)
|
||||
->setParameter('status', [Upload::STATUS_NEW, Upload::STATUS_PENDING])
|
||||
->setMaxResults($limit)
|
||||
->getQuery()
|
||||
->getResult()
|
||||
;
|
||||
}
|
||||
|
||||
public function getCountByStatus(string $status): ?int
|
||||
public function getCountByStatusAndType(string $status, string $type): ?int
|
||||
{
|
||||
$qb = $this->createQueryBuilder('upload');
|
||||
|
||||
return $qb
|
||||
->select($qb->expr()->count('upload'))
|
||||
->where($qb->expr()->andX(
|
||||
$qb->expr()->in('upload.type', ':type'),
|
||||
$qb->expr()->eq('upload.type', ':type'),
|
||||
$qb->expr()->eq('upload.status', ':status')
|
||||
))
|
||||
->setParameter('type', [Upload::TYPE_CONTRACT, Upload::TYPE_INVOICE])
|
||||
->setParameter('type', $type)
|
||||
->setParameter('status', $status)
|
||||
->getQuery()
|
||||
->getSingleScalarResult()
|
||||
|
||||
@@ -53,31 +53,68 @@
|
||||
</div>
|
||||
<div class="bg-gray-100 border border-gray-200 rounded-md px-4 py-2">
|
||||
<h2 class="flex justify-between pb-2">
|
||||
<span class="font-bold text-lg">Neue Dokumente</span>
|
||||
{% if newDocumentsCount > 0 or pendingDocumentsCount > 0 %}
|
||||
<span>{{ newDocumentsCount }} neu, {{ pendingDocumentsCount }} in Bearbeitung</span>
|
||||
<span class="font-bold text-lg">Neue Honorarverträge</span>
|
||||
{% if newContractsCount > 0 or pendingContractsCount > 0 %}
|
||||
<span>{{ newContractsCount }} neu, {{ pendingContractsCount }} in Bearbeitung</span>
|
||||
{% endif %}
|
||||
</h2>
|
||||
<ul class="divide-y divide-gray-200">
|
||||
{% for document in documents %}
|
||||
<li class="py-2 first:pt-0 last:pb-0">
|
||||
<button type="button"
|
||||
class="flex items-center space-x-1 truncate"
|
||||
hx-get="{{ path('app_administrative_document_check', { 'uuid': document.uuid, 'r': return_url() }) }}"
|
||||
hx-target="body"
|
||||
hx-swap="beforeend">
|
||||
{{ icon('download', 'w-4 h-4 shrink-0') }}
|
||||
<span>{{ document.owner.teamer.fullName(true) }}</span>
|
||||
{% include '_partials/_dot.html.twig' %}
|
||||
<span>{{ document.typeLabel }}</span>
|
||||
</button>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="py-2 first:pt-0 last:pb-0">
|
||||
-
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="max-h-64 overflow-y-scroll overflow-x-hidden">
|
||||
<ul class="divide-y divide-gray-200">
|
||||
{% for document in newContracts %}
|
||||
{% set assignment = document.disposition.assignment %}
|
||||
<li class="py-2 first:pt-0 last:pb-0">
|
||||
<button type="button"
|
||||
class="flex items-center space-x-1 truncate"
|
||||
hx-get="{{ path('app_administrative_document_check', { 'uuid': document.uuid, 'r': return_url() }) }}"
|
||||
hx-target="body"
|
||||
hx-swap="beforeend">
|
||||
{{ icon('download', 'w-4 h-4 shrink-0') }}
|
||||
<span>{{ document.owner.teamer.fullName(true) }}</span>
|
||||
{% include '_partials/_dot.html.twig' %}
|
||||
<span class="whitespace-nowrap">{{ assignment.effectivePeriod.start|date('d.m.y') }} - {{ assignment.effectivePeriod.end|date('d.m.y') }}</span>
|
||||
{% include '_partials/_dot.html.twig' %}
|
||||
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
||||
</button>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="py-2 first:pt-0 last:pb-0">
|
||||
-
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-100 border border-gray-200 rounded-md px-4 py-2">
|
||||
<h2 class="flex justify-between pb-2">
|
||||
<span class="font-bold text-lg">Neue Honorarnoten</span>
|
||||
{% if newInvoicesCount > 0 or pendingInvoicesCount > 0 %}
|
||||
<span>{{ newInvoicesCount }} neu, {{ pendingInvoicesCount }} in Bearbeitung</span>
|
||||
{% endif %}
|
||||
</h2>
|
||||
<div class="max-h-64 overflow-y-scroll overflow-x-hidden">
|
||||
<ul class="divide-y divide-gray-200">
|
||||
{% for document in newInvoices %}
|
||||
{% set assignment = document.disposition.assignment %}
|
||||
<li class="py-2 first:pt-0 last:pb-0">
|
||||
<button type="button"
|
||||
class="flex items-center space-x-1 truncate"
|
||||
hx-get="{{ path('app_administrative_document_check', { 'uuid': document.uuid, 'r': return_url() }) }}"
|
||||
hx-target="body"
|
||||
hx-swap="beforeend">
|
||||
{{ icon('download', 'w-4 h-4 shrink-0') }}
|
||||
<span>{{ document.owner.teamer.fullName(true) }}</span>
|
||||
<span class="whitespace-nowrap">{{ assignment.effectivePeriod.start|date('d.m.y') }} - {{ assignment.effectivePeriod.end|date('d.m.y') }}</span>
|
||||
{% include '_partials/_dot.html.twig' %}
|
||||
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
||||
</button>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="py-2 first:pt-0 last:pb-0">
|
||||
-
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-100 border border-gray-200 rounded-md px-4 py-2">
|
||||
<h2 class="font-bold text-lg pb-2">
|
||||
@@ -166,7 +203,7 @@
|
||||
<span>{{ overdueFeedbacksCount }} überfälllig</span>
|
||||
{% endif %}
|
||||
</h2>
|
||||
<div class="max-h-64 overflow-y-scroll">
|
||||
<div class="max-h-64 overflow-y-scroll overflow-x-hidden">
|
||||
<ul class="divide-y divide-gray-200">
|
||||
{% for disposition in overdueFeedbacks %}
|
||||
{% set assignment = disposition.assignment %}
|
||||
@@ -201,7 +238,7 @@
|
||||
<span>{{ overdueContractsCount }} überfälllig</span>
|
||||
{% endif %}
|
||||
</h2>
|
||||
<div class="max-h-64 overflow-y-scroll">
|
||||
<div class="max-h-64 overflow-y-scroll overflow-x-hidden">
|
||||
<ul class="divide-y divide-gray-200">
|
||||
{% for disposition in overdueContracts %}
|
||||
{% set assignment = disposition.assignment %}
|
||||
|
||||
@@ -3,34 +3,69 @@
|
||||
{% block content %}
|
||||
<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">
|
||||
Neue Dokumente
|
||||
<h2 class="flex justify-between pb-2">
|
||||
<span class="font-bold text-lg">Neue Honorarverträge</span>
|
||||
{% if newContractsCount > 0 or pendingContractsCount > 0 %}
|
||||
<span>{{ newContractsCount }} neu, {{ pendingContractsCount }} in Bearbeitung</span>
|
||||
{% endif %}
|
||||
</h2>
|
||||
{% if newDocumentsCount > 0 or pendingDocumentsCount > 0 %}
|
||||
<h3 class="font-bold pb-2">
|
||||
{{ newDocumentsCount }} neu {{ pendingDocumentsCount }} in Bearbeitung
|
||||
</h3>
|
||||
{% endif %}
|
||||
<ul class="divide-y divide-gray-200">
|
||||
{% for document in documents %}
|
||||
<li class="py-2 first:pt-0 last:pb-0 overflow-x-hidden">
|
||||
<button type="button"
|
||||
class="flex items-center space-x-1"
|
||||
hx-get="{{ path('app_administrative_document_check', { 'uuid': document.uuid, 'r': return_url() }) }}"
|
||||
hx-target="body"
|
||||
hx-swap="beforeend">
|
||||
{{ icon('download', 'w-4 h-4 shrink-0') }}
|
||||
<span>{{ document.owner.teamer.fullName(true) }}</span>
|
||||
{% include '_partials/_dot.html.twig' %}
|
||||
<span>{{ document.typeLabel }}</span>
|
||||
</button>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="py-2 first:pt-0 last:pb-0">
|
||||
-
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="max-h-64 overflow-y-scroll overflow-x-hidden">
|
||||
<ul class="divide-y divide-gray-200">
|
||||
{% for document in newContracts %}
|
||||
{% set assignment = document.disposition.assignment %}
|
||||
<li class="py-2 first:pt-0 last:pb-0">
|
||||
<button type="button"
|
||||
class="flex items-center space-x-1 truncate"
|
||||
hx-get="{{ path('app_administrative_document_check', { 'uuid': document.uuid, 'r': return_url() }) }}"
|
||||
hx-target="body"
|
||||
hx-swap="beforeend">
|
||||
{{ icon('download', 'w-4 h-4 shrink-0') }}
|
||||
<span>{{ document.owner.teamer.fullName(true) }}</span>
|
||||
{% include '_partials/_dot.html.twig' %}
|
||||
<span class="whitespace-nowrap">{{ assignment.effectivePeriod.start|date('d.m.y') }} - {{ assignment.effectivePeriod.end|date('d.m.y') }}</span>
|
||||
{% include '_partials/_dot.html.twig' %}
|
||||
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
||||
</button>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="py-2 first:pt-0 last:pb-0">
|
||||
-
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-100 border border-gray-200 rounded-md px-4 py-2">
|
||||
<h2 class="flex justify-between pb-2">
|
||||
<span class="font-bold text-lg">Neue Honorarnoten</span>
|
||||
{% if newInvoicesCount > 0 or pendingInvoicesCount > 0 %}
|
||||
<span>{{ newInvoicesCount }} neu, {{ pendingInvoicesCount }} in Bearbeitung</span>
|
||||
{% endif %}
|
||||
</h2>
|
||||
<div class="max-h-64 overflow-y-scroll overflow-x-hidden">
|
||||
<ul class="divide-y divide-gray-200">
|
||||
{% for document in newInvoices %}
|
||||
{% set assignment = document.disposition.assignment %}
|
||||
<li class="py-2 first:pt-0 last:pb-0">
|
||||
<button type="button"
|
||||
class="flex items-center space-x-1 truncate"
|
||||
hx-get="{{ path('app_administrative_document_check', { 'uuid': document.uuid, 'r': return_url() }) }}"
|
||||
hx-target="body"
|
||||
hx-swap="beforeend">
|
||||
{{ icon('download', 'w-4 h-4 shrink-0') }}
|
||||
<span>{{ document.owner.teamer.fullName(true) }}</span>
|
||||
<span class="whitespace-nowrap">{{ assignment.effectivePeriod.start|date('d.m.y') }} - {{ assignment.effectivePeriod.end|date('d.m.y') }}</span>
|
||||
{% include '_partials/_dot.html.twig' %}
|
||||
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
||||
</button>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="py-2 first:pt-0 last:pb-0">
|
||||
-
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-100 border border-gray-200 rounded-md px-4 py-2">
|
||||
<h2 class="font-bold text-lg pb-2">
|
||||
@@ -53,34 +88,40 @@
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% set overdueFeedbacksCount = overdueFeedbacks|length %}
|
||||
<div class="bg-gray-100 border border-gray-200 rounded-md px-4 py-2">
|
||||
<h2 class="font-bold text-lg pb-2">
|
||||
überfällige Feedbacks
|
||||
<h2 class="flex justify-between pb-2">
|
||||
<span class="font-bold text-lg">Überfällige Feedbacks</span>
|
||||
{% if overdueFeedbacksCount > 0 %}
|
||||
<span>{{ overdueFeedbacksCount }} überfälllig</span>
|
||||
{% endif %}
|
||||
</h2>
|
||||
<ul class="divide-y divide-gray-200">
|
||||
{% for disposition in overdueFeedbacks %}
|
||||
{% set assignment = disposition.assignment %}
|
||||
<li class="py-2 first:pt-0 last:pb-0 overflow-x-hidden">
|
||||
<a href="{{ path('app_administrative_feedback_provide', { 'uuid': disposition.uuid, 'r': return_url() }) }}"
|
||||
class="flex items-center space-x-1">
|
||||
{{ icon('feedback', 'w-4 h-4 shrink-0') }}
|
||||
<span class="whitespace-nowrap">{{ disposition.teamer }}</span>
|
||||
{% if assignment.destination %}
|
||||
{% include '_partials/_dot.html.twig' %}
|
||||
<span class="whitespace-nowrap">{{ assignment.destination }}</span>
|
||||
{% endif %}
|
||||
{% if assignment.jobProfile %}
|
||||
{% include '_partials/_dot.html.twig' %}
|
||||
<span class="whitespace-nowrap">{{ assignment.jobProfile.name }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="py-2 first:pt-0 last:pb-0">
|
||||
-
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="max-h-64 overflow-y-scroll overflow-x-hidden">
|
||||
<ul class="divide-y divide-gray-200">
|
||||
{% for disposition in overdueFeedbacks %}
|
||||
{% set assignment = disposition.assignment %}
|
||||
<li class="py-2 first:pt-0 last:pb-0 overflow-x-hidden">
|
||||
<a href="{{ path('app_administrative_feedback_provide', { 'uuid': disposition.uuid, 'r': return_url() }) }}"
|
||||
class="flex items-center space-x-1">
|
||||
{{ icon('feedback', 'w-4 h-4 shrink-0') }}
|
||||
<span class="whitespace-nowrap">{{ disposition.teamer }}</span>
|
||||
{% if assignment.destination %}
|
||||
{% include '_partials/_dot.html.twig' %}
|
||||
<span class="whitespace-nowrap">{{ assignment.destination }}</span>
|
||||
{% endif %}
|
||||
{% if assignment.jobProfile %}
|
||||
{% include '_partials/_dot.html.twig' %}
|
||||
<span class="whitespace-nowrap">{{ assignment.jobProfile.name }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="py-2 first:pt-0 last:pb-0">
|
||||
-
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user