store search in session (#2735)

* allow to detect used query filters
* allow to set label in action buttons
* allow to reset last search from session
* migrate invoice archive to new search system
* display number of used search filters
This commit is contained in:
Kevin Papst
2021-08-26 01:37:40 +02:00
committed by GitHub
parent 56524a9773
commit 98a3fc99a2
55 changed files with 365 additions and 81 deletions

View File

@@ -238,7 +238,6 @@ class InvoiceCreateCommand extends Command
// =============== VALIDATION END ===============
$defaultQuery = new InvoiceQuery();
$defaultQuery->setOrder(InvoiceQuery::ORDER_ASC);
$defaultQuery->setBegin($start);
$defaultQuery->setEnd($end);
$defaultQuery->setCurrentUser($user);

View File

@@ -177,6 +177,15 @@ abstract class AbstractController extends BaseAbstractController implements Serv
return $this->get('session')->get($name);
}
private function removeLastSearch(BaseQuery $query): void
{
$name = 'search_' . $this->getSearchName($query);
if ($this->get('session')->has($name)) {
$this->get('session')->remove($name);
}
}
private function getSearchName(BaseQuery $query): string
{
return substr($query->getName(), 0, 50);
@@ -198,6 +207,13 @@ abstract class AbstractController extends BaseAbstractController implements Serv
throw new \InvalidArgumentException('handleSearchForm() requires an instanceof BaseQuery as form data');
}
if ($request->query->has('resetSearchFilter')) {
$data->resetFilter();
$this->removeLastSearch($data);
return true;
}
$submitData = $request->query->all();
// allow to use forms with block-prefix
if (!empty($formName = $form->getConfig()->getName()) && $request->request->has($formName)) {
@@ -228,6 +244,7 @@ abstract class AbstractController extends BaseAbstractController implements Serv
$submitData = array_merge($sessionSearch, $submitData);
} elseif ($bookmark !== null && !$request->query->has('setDefaultQuery')) {
$submitData = array_merge($bookmark->getContent(), $submitData);
$data->flagAsBookmarkSearch();
}
}
@@ -259,7 +276,9 @@ abstract class AbstractController extends BaseAbstractController implements Serv
}
}
$this->get('session')->set('search_' . $searchName, $params);
if ($request->query->has('performSearch')) {
$this->get('session')->set('search_' . $searchName, $params);
}
// filter stuff, that does not belong in a bookmark
$filter = ['page'];
@@ -270,6 +289,7 @@ abstract class AbstractController extends BaseAbstractController implements Serv
}
if ($request->query->has('setDefaultQuery')) {
$this->removeLastSearch($data);
if ($bookmark === null) {
$bookmark = new Bookmark();
$bookmark->setType(Bookmark::SEARCH_DEFAULT);

View File

@@ -134,11 +134,8 @@ class ExportController extends AbstractController
$end = $this->getDateTimeFactory()->getEndOfMonth();
$query = new ExportQuery();
$query->setOrder(ExportQuery::ORDER_ASC);
$query->setBegin($begin);
$query->setEnd($end);
$query->setState(ExportQuery::STATE_STOPPED);
$query->setExported(ExportQuery::STATE_NOT_EXPORTED);
$query->setCurrentUser($this->getUser());
return $query;

View File

@@ -188,10 +188,8 @@ final class InvoiceController extends AbstractController
$end = $factory->getEndOfMonth();
$query = new InvoiceQuery();
$query->setOrder(InvoiceQuery::ORDER_ASC);
$query->setBegin($begin);
$query->setEnd($end);
$query->setExported(InvoiceQuery::STATE_NOT_EXPORTED);
// limit access to data from teams
$query->setCurrentUser($this->getUser());
@@ -301,11 +299,8 @@ final class InvoiceController extends AbstractController
$query->setCurrentUser($this->getUser());
$form = $this->getArchiveToolbarForm($query);
$form->setData($query);
$form->submit($request->query->all(), false);
if (!$form->isValid()) {
$query->resetByFormError($form->getErrors());
if ($this->handleSearch($form, $request)) {
return $this->redirectToRoute('admin_invoice_list');
}
$invoices = $this->invoiceRepository->getPagerfantaForQuery($query);

View File

@@ -10,6 +10,7 @@
namespace App\Event;
use App\Entity\User;
use App\Repository\Query\BaseQuery;
/**
* This event is triggered once per side load.
@@ -126,9 +127,18 @@ class PageActionsEvent extends ThemeEvent
$this->payload['actions'][$key] = null;
}
public function addSearchToggle(): void
public function addSearchToggle(?BaseQuery $query = null): void
{
$this->addAction('search', ['modal' => '#modal_search']);
$label = null;
if ($query !== null) {
$label = $query->countFilter();
if ($label < 1) {
$label = null;
}
}
$this->addAction('search', ['modal' => '#modal_search', 'label' => $label]);
}
public function addQuickExport(string $url): void

View File

@@ -10,6 +10,7 @@
namespace App\EventSubscriber\Actions;
use App\Event\PageActionsEvent;
use App\Repository\Query\ActivityQuery;
class ActivitiesSubscriber extends AbstractActionsSubscriber
{
@@ -20,7 +21,12 @@ class ActivitiesSubscriber extends AbstractActionsSubscriber
public function onActions(PageActionsEvent $event): void
{
$event->addSearchToggle();
$payload = $event->getPayload();
/** @var ActivityQuery $query */
$query = $payload['query'];
$event->addSearchToggle($query);
$event->addColumnToggle('#modal_activity_admin');
$event->addQuickExport($this->path('activity_export'));

View File

@@ -10,6 +10,7 @@
namespace App\EventSubscriber\Actions;
use App\Event\PageActionsEvent;
use App\Repository\Query\CustomerQuery;
class CustomersSubscriber extends AbstractActionsSubscriber
{
@@ -20,7 +21,12 @@ class CustomersSubscriber extends AbstractActionsSubscriber
public function onActions(PageActionsEvent $event): void
{
$event->addSearchToggle();
$payload = $event->getPayload();
/** @var CustomerQuery $query */
$query = $payload['query'];
$event->addSearchToggle($query);
$event->addColumnToggle('#modal_customer_admin');
$event->addQuickExport($this->path('customer_export'));

View File

@@ -10,6 +10,7 @@
namespace App\EventSubscriber\Actions;
use App\Event\PageActionsEvent;
use App\Repository\Query\InvoiceArchiveQuery;
class InvoiceArchiveSubscriber extends AbstractActionsSubscriber
{
@@ -20,10 +21,16 @@ class InvoiceArchiveSubscriber extends AbstractActionsSubscriber
public function onActions(PageActionsEvent $event): void
{
$payload = $event->getPayload();
/** @var InvoiceArchiveQuery $query */
$query = $payload['query'];
if ($this->isGranted('view_invoice')) {
$event->addBack($this->path('invoice'));
}
$event->addSearchToggle();
$event->addSearchToggle($query);
$event->addColumnToggle('#modal_invoices');
$event->addQuickExport($this->path('invoice_export'));
$event->addHelp($this->documentationLink('invoices.html'));

View File

@@ -10,6 +10,7 @@
namespace App\EventSubscriber\Actions;
use App\Event\PageActionsEvent;
use App\Repository\Query\ProjectQuery;
class ProjectsSubscriber extends AbstractActionsSubscriber
{
@@ -20,7 +21,13 @@ class ProjectsSubscriber extends AbstractActionsSubscriber
public function onActions(PageActionsEvent $event): void
{
$event->addSearchToggle();
$payload = $event->getPayload();
/** @var ProjectQuery $query */
$query = $payload['query'];
$event->addSearchToggle($query);
$event->addColumnToggle('#modal_project_admin');
$event->addQuickExport($this->path('project_export'));

View File

@@ -10,6 +10,7 @@
namespace App\EventSubscriber\Actions;
use App\Event\PageActionsEvent;
use App\Repository\Query\TagQuery;
class TagsSubscriber extends AbstractActionsSubscriber
{
@@ -20,7 +21,12 @@ class TagsSubscriber extends AbstractActionsSubscriber
public function onActions(PageActionsEvent $event): void
{
$event->addSearchToggle();
$payload = $event->getPayload();
/** @var TagQuery $query */
$query = $payload['query'];
$event->addSearchToggle($query);
if ($this->isGranted('manage_tag')) {
$event->addCreate($this->path('tags_create'));

View File

@@ -10,6 +10,7 @@
namespace App\EventSubscriber\Actions;
use App\Event\PageActionsEvent;
use App\Repository\Query\TeamQuery;
class TeamsSubscriber extends AbstractActionsSubscriber
{
@@ -20,7 +21,12 @@ class TeamsSubscriber extends AbstractActionsSubscriber
public function onActions(PageActionsEvent $event): void
{
$event->addSearchToggle();
$payload = $event->getPayload();
/** @var TeamQuery $query */
$query = $payload['query'];
$event->addSearchToggle($query);
if ($this->isGranted('create_team')) {
$event->addCreate($this->path('admin_team_create'), false);

View File

@@ -10,6 +10,7 @@
namespace App\EventSubscriber\Actions;
use App\Event\PageActionsEvent;
use App\Repository\Query\TimesheetQuery;
class TimesheetsSubscriber extends AbstractActionsSubscriber
{
@@ -20,7 +21,12 @@ class TimesheetsSubscriber extends AbstractActionsSubscriber
public function onActions(PageActionsEvent $event): void
{
$event->addSearchToggle();
$payload = $event->getPayload();
/** @var TimesheetQuery $query */
$query = $payload['query'];
$event->addSearchToggle($query);
$event->addColumnToggle('#modal_timesheet');
if ($this->isGranted('export_own_timesheet')) {

View File

@@ -10,6 +10,7 @@
namespace App\EventSubscriber\Actions;
use App\Event\PageActionsEvent;
use App\Repository\Query\TimesheetQuery;
class TimesheetsTeamSubscriber extends AbstractActionsSubscriber
{
@@ -20,7 +21,12 @@ class TimesheetsTeamSubscriber extends AbstractActionsSubscriber
public function onActions(PageActionsEvent $event): void
{
$event->addSearchToggle();
$payload = $event->getPayload();
/** @var TimesheetQuery $query */
$query = $payload['query'];
$event->addSearchToggle($query);
$event->addColumnToggle('#modal_timesheet_admin');
if ($this->isGranted('export_other_timesheet')) {

View File

@@ -10,6 +10,7 @@
namespace App\EventSubscriber\Actions;
use App\Event\PageActionsEvent;
use App\Repository\Query\UserQuery;
class UsersSubscriber extends AbstractActionsSubscriber
{
@@ -20,10 +21,17 @@ class UsersSubscriber extends AbstractActionsSubscriber
public function onActions(PageActionsEvent $event): void
{
$event->addSearchToggle();
$payload = $event->getPayload();
/** @var UserQuery $query */
$query = $payload['query'];
$event->addSearchToggle($query);
if ($event->isIndexView()) {
$event->addColumnToggle('#modal_user_admin');
}
$event->addQuickExport($this->path('user_export'));
if ($this->isGranted('create_user')) {

View File

@@ -27,7 +27,10 @@ class InvoiceArchiveForm extends AbstractToolbarForm
$this->addSearchTermInputField($builder);
$this->addDateRange($builder, ['timezone' => $options['timezone']]);
$this->addCustomerMultiChoice($builder, ['required' => false, 'start_date_param' => null, 'end_date_param' => null, 'ignore_date' => true, 'placeholder' => ''], true);
$builder->add('status', InvoiceStatusType::class);
$builder->add('status', InvoiceStatusType::class, ['required' => false]);
$this->addPageSizeChoice($builder);
$this->addOrder($builder);
$this->addOrderBy($builder, InvoiceArchiveQuery::INVOICE_ARCHIVE_ORDER_ALLOWED);
}
/**

View File

@@ -185,12 +185,19 @@ class InvoiceRepository extends EntityRepository
case 'date':
$orderBy = 'i.createdAt';
break;
case 'customer':
$orderBy = 'i.customer';
case 'number':
$orderBy = 'i.invoiceNumber';
break;
case 'total':
case 'payed':
$orderBy = 'i.paymentDate';
break;
case 'total_rate':
$orderBy = 'i.total';
break;
case 'tax':
case 'status':
$orderBy = 'i.' . $orderBy;
break;
}
$qb->addOrderBy($orderBy, $query->getOrder());

View File

@@ -19,7 +19,7 @@ class ActivityQuery extends ProjectQuery
public const ACTIVITY_ORDER_ALLOWED = ['id', 'name', 'comment', 'customer', 'project', 'budget', 'timeBudget', 'visible'];
/**
* @var Project[]|int[]
* @var array<Project|int>
*/
private $projects = [];
/**
@@ -36,6 +36,9 @@ class ActivityQuery extends ProjectQuery
parent::__construct();
$this->setDefaults([
'orderBy' => 'name',
'projects' => [],
'globalsOnly' => false,
'excludeGlobals' => false,
]);
}

View File

@@ -85,13 +85,17 @@ class BaseQuery
*/
private $searchTerm;
/**
* @var Bookmark
* @var Bookmark|null
*/
private $bookmark;
/**
* @var string|null
*/
private $name;
/**
* @var bool
*/
private $bookmarkSearch = false;
/**
* @param Team[] $teams
@@ -258,13 +262,44 @@ class BaseQuery
{
$method = 'set' . ucfirst($name);
if (method_exists($this, $method)) {
$this->{$method}($value);
} elseif (property_exists($this, $name)) {
$this->$name = $value;
\call_user_func([$this, $method], $value);
return;
}
if (substr($name, -1) === 's') {
$method = 'add' . ucfirst(substr($name, 0, \strlen($name) - 1));
if (method_exists($this, $method) && \is_array($value)) {
foreach ($value as $v) {
\call_user_func([$this, $method], $v);
}
return;
}
}
if (property_exists($this, $name)) {
$this->{$name} = $value;
}
}
protected function get($name)
{
$methods = ['get' . ucfirst($name), 'is' . ucfirst($name), 'has' . ucfirst($name)];
foreach ($methods as $method) {
if (method_exists($this, $method)) {
return \call_user_func([$this, $method]);
}
}
if (property_exists($this, $name)) {
return $this->{$name};
}
}
/**
* You have to add ALL user facing form fields as default!
*
* @param array $defaults
* @return self
*/
@@ -347,4 +382,66 @@ class BaseQuery
return $query;
}
public function isDefaultFilter(string $filter): bool
{
if (!\array_key_exists($filter, $this->defaults)) {
return false;
}
$expectedValue = $this->defaults[$filter];
return $this->matchesFilter($filter, $expectedValue);
}
public function matchesFilter(string $filter, $expectedValue): bool
{
$currentValue = $this->get($filter);
if (\is_object($currentValue)) {
if ($currentValue != $expectedValue) {
return false;
}
} else {
if ($currentValue !== $expectedValue) {
return false;
}
}
return true;
}
public function countFilter(): int
{
$filter = 0;
foreach (array_keys($this->defaults) as $key) {
if ($key === 'page') {
continue;
}
if (!$this->isDefaultFilter($key)) {
$filter++;
}
}
return $filter;
}
public function resetFilter(): void
{
foreach ($this->defaults as $key => $value) {
$this->set($key, $value);
}
}
public function flagAsBookmarkSearch(): void
{
$this->bookmarkSearch = true;
}
public function isBookmarkSearch(): bool
{
return $this->bookmarkSearch;
}
}

View File

@@ -25,6 +25,7 @@ class CustomerQuery extends BaseQuery implements VisibilityInterface
{
$this->setDefaults([
'orderBy' => 'name',
'visibility' => VisibilityInterface::SHOW_VISIBLE,
]);
}
}

View File

@@ -20,6 +20,16 @@ class ExportQuery extends TimesheetQuery
*/
private $markAsExported = false;
public function __construct()
{
parent::__construct();
$this->setDefaults([
'order' => ExportQuery::ORDER_ASC,
'state' => ExportQuery::STATE_STOPPED,
'exported' => ExportQuery::STATE_NOT_EXPORTED,
]);
}
public function getRenderer(): ?string
{
return $this->renderer;

View File

@@ -21,7 +21,9 @@ class InvoiceArchiveQuery extends BaseQuery
use DateRangeTrait;
public const INVOICE_ARCHIVE_ORDER_ALLOWED = [
'date', 'customer', 'total'
'date', 'total_rate',
// TODO other fields have a problem with translation
// 'number', 'tax', 'payed', 'status'
];
/**
@@ -40,6 +42,8 @@ class InvoiceArchiveQuery extends BaseQuery
'orderBy' => 'date',
'order' => self::ORDER_DESC,
'dateRange' => new DateRange(),
'customers' => [],
'status' => [],
]);
}

View File

@@ -29,6 +29,8 @@ class InvoiceQuery extends TimesheetQuery
{
parent::__construct();
$this->setDefaults([
'order' => InvoiceQuery::ORDER_ASC,
'exported' => InvoiceQuery::STATE_NOT_EXPORTED,
'state' => self::STATE_STOPPED,
'billable' => true,
'markAsExported' => false,

View File

@@ -23,15 +23,15 @@ class ProjectQuery extends BaseQuery implements VisibilityInterface
];
/**
* @var array
* @var array<Customer|int>
*/
private $customers = [];
/**
* @var \DateTime
* @var \DateTime|null
*/
private $projectStart;
/**
* @var \DateTime
* @var \DateTime|null
*/
private $projectEnd;
@@ -39,6 +39,10 @@ class ProjectQuery extends BaseQuery implements VisibilityInterface
{
$this->setDefaults([
'orderBy' => 'name',
'customers' => [],
'projectStart' => null,
'projectEnd' => null,
'visibility' => VisibilityInterface::SHOW_VISIBLE,
]);
}

View File

@@ -24,6 +24,7 @@ class TeamQuery extends BaseQuery
{
$this->setDefaults([
'orderBy' => 'name',
'users' => [],
]);
}

View File

@@ -69,7 +69,13 @@ class TimesheetQuery extends ActivityQuery implements BillableInterface
$this->setDefaults([
'order' => self::ORDER_DESC,
'orderBy' => 'begin',
'dateRange' => new DateRange($resetTimes)
'dateRange' => new DateRange($resetTimes),
'exported' => self::STATE_ALL,
'state' => self::STATE_ALL,
'billable' => null,
'tags' => [],
'users' => [],
'activities' => [],
]);
}

View File

@@ -24,12 +24,17 @@ class UserQuery extends BaseQuery implements VisibilityInterface
* @var string|null
*/
private $role;
/**
* @var Team[]
*/
private $searchTeams = [];
public function __construct()
{
$this->setDefaults([
'orderBy' => 'username',
'searchTeams' => [],
'visibility' => VisibilityInterface::SHOW_VISIBLE,
]);
}