commit d5a16eb6124250d76a9d51eec0c602929e1d5498
Author: Filip Jani <jsem@filek.cz>
Date:   Thu Apr 18 18:21:53 2019 +0200

    Re #7330 grid pro správu uživatelů a formuláře

diff --git a/app/AdminModule/component/User/UserEditForm.latte b/app/AdminModule/component/User/UserEditForm.latte
new file mode 100644
index 0000000..3770db9
--- /dev/null
+++ b/app/AdminModule/component/User/UserEditForm.latte
@@ -0,0 +1,66 @@
+<div class="row">
+    <div class="col-4">
+        {form form class=> 'form'}
+            <div class="alert alert-danger" n:if="$form->hasErrors()">
+                <ul class="ul-alert">
+                    <li n:foreach="$form->errors as $error">{$error}</li>
+                </ul>
+            </div>
+
+            <div class="row mt-2">
+                <div class="col-4">
+                    {label \App\Model\Repository\UserRepository::COLUMN_USERNAME class => 'label-required'}
+                </div>
+                <div class="col-8">
+                    {input \App\Model\Repository\UserRepository::COLUMN_USERNAME class => 'form-control'}
+                </div>
+            </div>
+            <div class="row mt-2">
+                <div class="col-4">
+                    {label \App\Model\Repository\UserRepository::COLUMN_LOGIN class => 'label-required'}
+                </div>
+                <div class="col-8">
+                    {input \App\Model\Repository\UserRepository::COLUMN_LOGIN class => 'form-control'}
+                </div>
+            </div>
+            <div class="row mt-2">
+                <div class="col-4">
+                    {label \App\Model\Repository\UserRepository::COLUMN_PASSWORD class => 'label-required'}
+                </div>
+                <div class="col-8">
+                    {input \App\Model\Repository\UserRepository::COLUMN_PASSWORD class => 'form-control'}
+                </div>
+            </div>
+            <div class="row mt-2">
+                <div class="col-4">
+                    {label \App\AdminModule\Components\UserEditForm::PASSWORD_CONFIRM class => 'label-required'}
+                </div>
+                <div class="col-8">
+                    {input \App\AdminModule\Components\UserEditForm::PASSWORD_CONFIRM class => 'form-control'}
+                </div>
+            </div>
+            <div class="row mt-2">
+                <div class="col-4">
+                    {label \App\Model\Repository\UserRoleRepository::COLUMN_ROLE_ID  class => 'label-required'}
+                </div>
+                <div class="col-8">
+                    {input \App\Model\Repository\UserRoleRepository::COLUMN_ROLE_ID class => 'form-control'}
+                </div>
+            </div>
+            <div class="row mt-2">
+                <div class="col-4">
+                    {label \App\Model\Repository\UserRepository::COLUMN_EMAIL}
+                </div>
+                <div class="col-8">
+                    {input \App\Model\Repository\UserRepository::COLUMN_EMAIL class => 'form-control'}
+                </div>
+            </div>
+
+            <div class="row">
+                <div class="col-8 offset-4">
+                    {input submit class => 'form-control btn btn-success mt-4'}
+                </div>
+            </div>
+        {/form}
+    </div>
+</div>
\ No newline at end of file
diff --git a/app/AdminModule/component/User/UserEditForm.php b/app/AdminModule/component/User/UserEditForm.php
new file mode 100644
index 0000000..fcc3114
--- /dev/null
+++ b/app/AdminModule/component/User/UserEditForm.php
@@ -0,0 +1,189 @@
+<?php
+
+
+namespace App\AdminModule\Components;
+
+
+use App\Enum\EFlashMessage;
+use App\Model\Facade\UserFacade;
+use App\Model\Repository\RoleRepository;
+use App\Model\Repository\UserRepository;
+use App\Model\Repository\UserRoleRepository;
+use Nette\Application\UI\Control;
+use Nette\Application\UI\Form;
+
+class UserEditForm extends Control
+{
+    const PASSWORD_CONFIRM = 'password_confirm';
+
+    /** @var Form  */
+    private $form;
+
+    /** @var int ID uživatele */
+    private $userId;
+
+    /**
+     * @var UserRepository
+     */
+    private $userRepository;
+    /**
+     * @var UserRoleRepository
+     */
+    private $userRoleRepository;
+    /**
+     * @var RoleRepository
+     */
+    private $roleRepository;
+    /**
+     * @var UserFacade
+     */
+    private $userFacade;
+
+    public function __construct(UserRepository $userRepository,
+                                UserRoleRepository $userRoleRepository,
+                                RoleRepository $roleRepository,
+                                UserFacade $userFacade
+    )
+    {
+        parent::__construct();
+
+        $this->roleRepository = $roleRepository;
+
+        $this->userId = NULL;
+        $this->form = new Form;
+        $this->userRepository = $userRepository;
+        $this->userRoleRepository = $userRoleRepository;
+        $this->userFacade = $userFacade;
+    }
+
+    public function render()
+    {
+        $this->template->setFile(__DIR__ . '/UserEditForm.latte');
+        $this->template->render();
+    }
+
+    public function createComponentForm()
+    {
+        $this->form->addText(UserRepository::COLUMN_USERNAME, 'Uživatelské jméno');
+        $this->form->addText(UserRepository::COLUMN_LOGIN, 'Login')
+            ->setRequired(TRUE);
+
+        $password = $this->form->addPassword(UserRepository::COLUMN_PASSWORD, 'Heslo');
+        $passwordConfirm = $this->form->addPassword(self::PASSWORD_CONFIRM, 'Potvrzení hesla')
+            ->addRule(Form::EQUAL, 'Hesla se musejí shodovat.', $this->form[UserRepository::COLUMN_PASSWORD]);
+
+        if (empty($this->userId))
+        {
+            $password->setRequired(TRUE);
+            $passwordConfirm->setRequired(TRUE);
+        } else
+        {
+            $password->setRequired(FALSE);
+            $passwordConfirm->setRequired(FALSE);
+        }
+
+        $this->form->addSelect(UserRoleRepository::COLUMN_ROLE_ID, 'Role uživatele', $this->getRoles())
+            ->setPrompt('- Vyberte roli -')
+            ->addRule(Form::REQUIRED, '%label musí být vybrána.', TRUE);
+        $this->form->addText(UserRepository::COLUMN_EMAIL, 'E-mail')
+            ->addRule(Form::EMAIL, 'Prvek %label musí obsahovat validní e-mail.')
+            ->setRequired(FALSE);
+
+        $this->form->setDefaults($this->getDefaults());
+
+        $submitText = empty($this->userId) ? 'Přidat uživatele' : 'Upravit uživatele';
+        $this->form->addSubmit('submit', $submitText);
+
+        $this->form->onValidate[] = [$this, 'formValidate'];
+        $this->form->onSuccess[] = [$this, 'formSuccess'];
+
+        return $this->form;
+    }
+
+    /**
+     * Validace formuláře
+     *
+     * @param Form $form
+     */
+    public function formValidate(Form $form)
+    {
+        $values = $form->getValues();
+
+        // Vkládáme nového uživatele - kontrola existujícího loginu
+        if (empty($this->userId))
+        {
+            if ($this->userRepository->findByLogin($values->{UserRepository::COLUMN_LOGIN}))
+            {
+                $form->addError('Uživatel se zadaným loginem již existuje.');
+            }
+        }
+    }
+
+    public function formSuccess(Form $form)
+    {
+        $result = $this->userFacade->saveUser($form->getValues(true), $this->userId);
+
+        if ($result)
+        {
+            $this->presenter->flashMessage('Uživatel byl úspěšně upraven.', EFlashMessage::SUCCESS);
+            $this->presenter->redirect('User:edit', ['id' => $result->{UserRepository::COLUMN_ID}]);
+        } else
+        {
+            $this->presenter->flashMessage('Uživatele se nepodařilo upravit.', EFlashMessage::ERROR);
+        }
+    }
+
+    /**
+     * Nastaví uživatele do formuláře, pro předvyplnění hodnot formuláře
+     *
+     * @param int $userId
+     */
+    public function setUser(int $userId)
+    {
+        $this->userId = $userId;
+    }
+
+    /**
+     * Vrací hodnoty z databáze při upravování existujícího uživatele
+     *
+     * @return array
+     */
+    private function getDefaults()
+    {
+        $defaultValues = array();
+
+        if(!empty($this->userId))
+        {
+            $userSelection = $this->userRepository->findById($this->userId);
+            $userRow = $userSelection->fetch();
+
+            if ($userRow)
+            {
+                $defaultValues = $userRow->toArray();
+                $roleRow = $userRow->related(UserRoleRepository::TABLE_NAME, UserRoleRepository::COLUMN_USER_ID)->fetch();
+
+                $defaultValues[UserRoleRepository::COLUMN_ROLE_ID] = $roleRow ? $roleRow->{UserRoleRepository::COLUMN_ROLE_ID} : NULL;
+            }
+        }
+
+        return $defaultValues;
+    }
+
+    /**
+     * Vrací možné role uživatele
+     *
+     * @return array
+     */
+    private function getRoles()
+    {
+        return $this->roleRepository->findAll()->fetchPairs(RoleRepository::COLUMN_ID, RoleRepository::COLUMN_NAME);
+    }
+}
+
+interface IUserEditFormFactory
+{
+    /**
+     * @return UserEditForm
+     */
+    public function create();
+}
\ No newline at end of file
diff --git a/app/AdminModule/component/User/UserGrid.php b/app/AdminModule/component/User/UserGrid.php
new file mode 100644
index 0000000..b48425d
--- /dev/null
+++ b/app/AdminModule/component/User/UserGrid.php
@@ -0,0 +1,75 @@
+<?php
+
+
+namespace App\AdminModule\Components;
+
+
+use App\Model\Repository\UserRepository;
+use App\Utils\DataGrid\DataGrid;
+use Ublaboo\DataGrid\Exception\DataGridException;
+
+class UserGrid extends DataGrid
+{
+    /**
+     * @var UserRepository
+     */
+    private $userRepository;
+
+    public function __construct(UserRepository $userRepository)
+    {
+        $this->userRepository = $userRepository;
+
+        parent::__construct(NULL, NULL);
+    }
+
+    /**
+     * Abstraktní metoda, slouží k nastavení primárního klíče a nastavení datasource
+     *  1. $this->setPrimaryKey();
+     *  2. $this->setDataSource();
+     *
+     * @throws DataGridException
+     */
+    public function init()
+    {
+        $this->setPrimaryKey(UserRepository::COLUMN_ID);
+        $this->setDataSource($this->userRepository->findAll());
+    }
+
+    /**
+     * Definice sloupečků, akcí, vyhledávácích filtrů gridu
+     *
+     * @throws DataGridException
+     */
+    public function define()
+    {
+        // Definice sloupečků
+        $this->addColumnNumber(UserRepository::COLUMN_ID, 'ID')->setDefaultHide(TRUE);
+        $this->addColumnText(UserRepository::COLUMN_USERNAME, 'Uživatel');
+        $this->addColumnText(UserRepository::COLUMN_LOGIN, 'Login');
+        $this->addColumnText(UserRepository::COLUMN_EMAIL, 'Email');
+
+        // Definice filtrů
+        $this->addFilterText(UserRepository::COLUMN_USERNAME, 'Uživatel');
+        $this->addFilterText(UserRepository::COLUMN_LOGIN, 'Login');
+        $this->addFilterText(UserRepository::COLUMN_EMAIL, 'Email');
+
+        // Akce
+        $this->addAction('edit', 'upravit', 'User:edit', ['id' => UserRepository::COLUMN_ID])
+            ->setTitle('Editovat');
+
+        $this->addAction('delete', 'smazat', 'deleteUser!', ['id' => UserRepository::COLUMN_ID])
+            ->setConfirm('Opravdu chcete uživatele "%s" odstranit?', UserRepository::COLUMN_LOGIN)
+            ->setTitle('Odstranit')
+            ->setClass('btn btn-xs btn-danger ajax');
+
+        $this->setDefaultPerPage(20);
+    }
+}
+
+interface IUserGridFactory
+{
+    /**
+     * @return UserGrid
+     */
+    public function create();
+}
\ No newline at end of file
diff --git a/app/AdminModule/presenters/UserPresenter.php b/app/AdminModule/presenters/UserPresenter.php
new file mode 100644
index 0000000..93e04e8
--- /dev/null
+++ b/app/AdminModule/presenters/UserPresenter.php
@@ -0,0 +1,63 @@
+<?php
+
+
+namespace App\AdminModule\Presenters;
+
+
+use App\AdminModule\Components\IUserEditFormFactory;
+use App\AdminModule\Components\IUserGridFactory;
+use App\Model\Facade\UserFacade;
+
+class UserPresenter extends BaseAdminPresenter
+{
+    /**
+     * @var IUserGridFactory
+     */
+    private $userGridFactory;
+    /**
+     * @var IUserEditFormFactory
+     */
+    private $userEditFormFactory;
+    /**
+     * @var UserFacade
+     */
+    private $userFacade;
+
+    public function __construct(IUserGridFactory $userGridFactory,
+                                IUserEditFormFactory $userEditFormFactory,
+                                UserFacade $userFacade
+    )
+    {
+        parent::__construct();
+
+        $this->userGridFactory = $userGridFactory;
+        $this->userEditFormFactory = $userEditFormFactory;
+        $this->userFacade = $userFacade;
+    }
+
+    public function actionAdd(){}
+
+    public function actionEdit(int $id)
+    {
+        $this['userEditForm']->setUser($id);
+    }
+
+    public function handleDeleteUser(int $id)
+    {
+        if ($this->isAjax())
+        {
+            $this->userFacade->deleteUser($id);
+            $this['userGrid']->reload();
+        }
+    }
+
+    public function createComponentUserGrid()
+    {
+        return $this->userGridFactory->create();
+    }
+
+    public function createComponentUserEditForm()
+    {
+        return $this->userEditFormFactory->create();
+    }
+}
\ No newline at end of file
diff --git a/app/AdminModule/templates/@layout.latte b/app/AdminModule/templates/@layout.latte
index f6b30d4..11bcddb 100644
--- a/app/AdminModule/templates/@layout.latte
+++ b/app/AdminModule/templates/@layout.latte
@@ -6,8 +6,8 @@
     <title>{ifset title}{include title|stripHtml} | {/ifset}Old Babylonian Text Corpus</title>
 
     <!-- jQuery -->
-    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
-            integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
+    <script src="https://code.jquery.com/jquery-3.4.0.min.js"
+            integrity="sha256-BJeo0qm959uMBGb65z40ejJYGSgR7REI4+CW1fNKwOg="
             crossorigin="anonymous"></script>
 
     <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.js"
@@ -96,9 +96,9 @@
     <!-- <li class="nav-item"><a n:href="Default:default" n:class="nav-link, $presenter->isLinkCurrent('Default:*') ? active" title="Users">
             Users </a></li> -->
 
-    <li class="nav-item"><a href="#" class="nav-link" title="Users">
+    <li class="nav-item"><a n:href="User:default" n:class="nav-link, $presenter->isLinkCurrent('User:*') ? active" title="Users">
             Users </a></li>
-    <li class="nav-item"><a href="#" class="nav-link active" title="Groups">
+    <li class="nav-item"><a href="#" class="nav-link" title="Groups">
             Groups </a></li>
     <li class="nav-item"><a href="#" class="nav-link" title="Rules">
             Rules </a></li>
@@ -126,7 +126,12 @@
 
 <!--Content-->
 <div class="content">
-    <div n:foreach="$flashes as $flash" n:class="alert, 'alert-' . $flash->type">{$flash->message}</div>
+    <div n:foreach="$flashes as $flash" n:class="alert, 'alert-' . $flash->type, alert-dismissible, fade, show" role="alert">
+        {$flash->message}
+        <button type="button" class="close" data-dismiss="alert" aria-label="Close">
+            <span aria-hidden="true">&times;</span>
+        </button>
+    </div>
 
     {include content}
 </div>
diff --git a/app/AdminModule/templates/User/add.latte b/app/AdminModule/templates/User/add.latte
new file mode 100644
index 0000000..5861aed
--- /dev/null
+++ b/app/AdminModule/templates/User/add.latte
@@ -0,0 +1,10 @@
+{block content}
+<div class="row">
+    <div class="col-11">
+        <h4>Administrace - Nový uživatel</h4>
+    </div>
+    <div class="col-1">
+        <a n:href="User:default" class="btn btn-sm btn-success"><span class="fa fa-fw fa-list"></span> Seznam uživatelů</a>
+    </div>
+</div>
+{control userEditForm}
\ No newline at end of file
diff --git a/app/AdminModule/templates/User/default.latte b/app/AdminModule/templates/User/default.latte
new file mode 100644
index 0000000..078228d
--- /dev/null
+++ b/app/AdminModule/templates/User/default.latte
@@ -0,0 +1,14 @@
+{block content}
+<div class="row">
+    <div class="col-11">
+        <h4>Administrace - Seznam uživatelů</h4>
+    </div>
+    <div class="col-1">
+        <a n:href="User:add" class="btn btn-sm btn-success"><span class="fa fa-fw fa-plus"></span> Přidat uživatele</a>
+    </div>
+</div>
+<div class="row">
+    <div class="col-10 offset-1">
+        {control userGrid}
+    </div>
+</div>
diff --git a/app/AdminModule/templates/User/edit.latte b/app/AdminModule/templates/User/edit.latte
new file mode 100644
index 0000000..ecbd821
--- /dev/null
+++ b/app/AdminModule/templates/User/edit.latte
@@ -0,0 +1,10 @@
+{block content}
+<div class="row">
+    <div class="col-11">
+        <h4>Administrace - Editace uživatele</h4>
+    </div>
+    <div class="col-1">
+        <a n:href="User:default" class="btn btn-sm btn-success"><span class="fa fa-fw fa-list"></span> Seznam uživatelů</a>
+    </div>
+</div>
+{control userEditForm}
\ No newline at end of file
diff --git a/app/FrontModule/component/Login/LoginForm.latte b/app/FrontModule/component/Login/LoginForm.latte
index a2f7fa7..f464de2 100644
--- a/app/FrontModule/component/Login/LoginForm.latte
+++ b/app/FrontModule/component/Login/LoginForm.latte
@@ -1,11 +1,13 @@
-<div class="col-3">
-    {form form class => 'form'}
-        {label \App\Model\Repository\UserRepository::COLUMN_LOGIN}
-        {input \App\Model\Repository\UserRepository::COLUMN_LOGIN class => 'form-control'}
+<div class="row">
+    <div class="col-3">
+        {form form class => 'form'}
+            {label \App\Model\Repository\UserRepository::COLUMN_LOGIN}
+            {input \App\Model\Repository\UserRepository::COLUMN_LOGIN class => 'form-control'}
 
-        {label \App\Model\Repository\UserRepository::COLUMN_PASSWORD}
-        {input \App\Model\Repository\UserRepository::COLUMN_PASSWORD class => 'form-control'}
+            {label \App\Model\Repository\UserRepository::COLUMN_PASSWORD}
+            {input \App\Model\Repository\UserRepository::COLUMN_PASSWORD class => 'form-control'}
 
-        {input submit class => 'form-control btn btn-success mt-4'}
-    {/form}
+            {input submit class => 'form-control btn btn-success mt-4'}
+        {/form}
+    </div>
 </div>
\ No newline at end of file
diff --git a/app/FrontModule/templates/@layout.latte b/app/FrontModule/templates/@layout.latte
index 601f21f..3f479b9 100644
--- a/app/FrontModule/templates/@layout.latte
+++ b/app/FrontModule/templates/@layout.latte
@@ -6,8 +6,8 @@
     <title>{ifset title}{include title|stripHtml} | {/ifset}Old Babylonian Text Corpus</title>
 
     <!-- jQuery -->
-    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
-            integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
+    <script src="https://code.jquery.com/jquery-3.4.0.min.js"
+            integrity="sha256-BJeo0qm959uMBGb65z40ejJYGSgR7REI4+CW1fNKwOg="
             crossorigin="anonymous"></script>
 
     <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.js"
diff --git a/app/config/components.neon b/app/config/components.neon
index 2cb199d..6890056 100644
--- a/app/config/components.neon
+++ b/app/config/components.neon
@@ -1,6 +1,8 @@
 services:
     # Gridy
     - App\FrontModule\Components\IExampleGirdFactory
+    - App\AdminModule\Components\IUserGridFactory
 
     # Formuláře
-    - App\FrontModule\Components\ILoginFormFactory
\ No newline at end of file
+    - App\FrontModule\Components\ILoginFormFactory
+    - App\AdminModule\Components\IUserEditFormFactory
\ No newline at end of file
diff --git a/app/config/model.neon b/app/config/model.neon
index b878695..1c933ec 100644
--- a/app/config/model.neon
+++ b/app/config/model.neon
@@ -14,4 +14,6 @@ services:
     - App\Model\Repository\TransliterationRepository
     - App\Model\Repository\UserLogRepository
     - App\Model\Repository\UserRepository
-    - App\Model\Repository\UserRoleRepository
\ No newline at end of file
+    - App\Model\Repository\UserRoleRepository
+
+    - App\Model\Facade\UserFacade
\ No newline at end of file
diff --git a/app/model/facade/UserFacade.php b/app/model/facade/UserFacade.php
new file mode 100644
index 0000000..6ba6df0
--- /dev/null
+++ b/app/model/facade/UserFacade.php
@@ -0,0 +1,85 @@
+<?php
+
+
+namespace App\Model\Facade;
+
+use App\AdminModule\Components\UserEditForm;
+use App\Model\Repository\UserRepository;
+use App\Model\Repository\UserRoleRepository;
+use Nette\Utils\ArrayHash;
+
+class UserFacade
+{
+    /**
+     * @var UserRepository
+     */
+    private $userRepository;
+    /**
+     * @var UserRoleRepository
+     */
+    private $userRoleRepository;
+
+    public function __construct(UserRepository $userRepository,
+                                UserRoleRepository $userRoleRepository
+    )
+    {
+        $this->userRepository = $userRepository;
+        $this->userRoleRepository = $userRoleRepository;
+    }
+
+    /**
+     * Uloží informace o uživateli do DB
+     *
+     * @param array $values : hodnoty z formuláře UserEditForm
+     * @param int|null $userId : ID uživatele, pokud se edituje
+     * @return bool|false|int|\Nette\Database\Table\ActiveRow
+     */
+    public function saveUser(array $values, int $userId = NULL)
+    {
+        $roleId = $values[UserRoleRepository::COLUMN_ROLE_ID];
+        unset($values[UserRoleRepository::COLUMN_ROLE_ID]);
+        unset($values[UserEditForm::PASSWORD_CONFIRM]);
+
+        if (empty($values[UserRepository::COLUMN_PASSWORD]))
+        {
+            unset($values[UserRepository::COLUMN_PASSWORD]);
+        } else
+        {
+            $values[UserRepository::COLUMN_PASSWORD] = md5($values[UserRepository::COLUMN_PASSWORD]);
+        }
+
+        if (!empty($userId))
+        {
+            $userRow = $this->userRepository->findById($userId)->fetch();
+
+            if ($userRow)
+            {
+                $userRow->update($values);
+            }
+
+            $roleRow = $userRow->related(UserRoleRepository::TABLE_NAME, UserRoleRepository::COLUMN_USER_ID)->fetch();
+
+            if ($roleRow)
+            {
+                $roleRow->update([UserRoleRepository::COLUMN_ROLE_ID => $roleId]);
+            }
+        } else
+        {
+            $userRow = $this->userRepository->insert($values);
+            $this->userRoleRepository->addNew($userRow->{UserRepository::COLUMN_ID}, $roleId);
+        }
+
+        return $userRow;
+    }
+
+    /**
+     * Odstraní uživatele
+     *
+     * @param int $userId
+     * @return int
+     */
+    public function deleteUser(int $userId)
+    {
+        return $this->userRepository->delete($userId);
+    }
+}
\ No newline at end of file
diff --git a/app/model/repository/UserRepository.php b/app/model/repository/UserRepository.php
index 50476cc..95d2525 100644
--- a/app/model/repository/UserRepository.php
+++ b/app/model/repository/UserRepository.php
@@ -34,4 +34,15 @@ class UserRepository extends Repository
     {
         return $this->findBy([self::COLUMN_LOGIN => $login])->fetch();
     }
+
+    /**
+     * Nalezne uživatele v DB podle jeho ID
+     *
+     * @param int $id
+     * @return \Nette\Database\Table\Selection
+     */
+    public function findById(int $id)
+    {
+        return $this->findBy([self::COLUMN_ID => $id]);
+    }
 }
\ No newline at end of file
diff --git a/app/model/repository/UserRoleRepository.php b/app/model/repository/UserRoleRepository.php
index 610d2bc..5a49689 100644
--- a/app/model/repository/UserRoleRepository.php
+++ b/app/model/repository/UserRoleRepository.php
@@ -16,4 +16,21 @@ class UserRoleRepository extends Repository
     /** Sloupečky tabulky */
     const COLUMN_USER_ID = 'user_id';
     const COLUMN_ROLE_ID = 'role_id';
+
+    /**
+     * Přiřadí roli novému uživateli
+     *
+     * @param int $userId
+     * @param int $roleId
+     * @return bool|int|\Nette\Database\Table\ActiveRow
+     */
+    public function addNew(int $userId, int $roleId)
+    {
+        return $this->insert(
+            [
+                UserRoleRepository::COLUMN_USER_ID => $userId,
+                UserRoleRepository::COLUMN_ROLE_ID => $roleId
+            ]
+        );
+    }
 }
\ No newline at end of file
diff --git a/www/css/admin/style.css b/www/css/admin/style.css
index 05148b5..4863163 100644
--- a/www/css/admin/style.css
+++ b/www/css/admin/style.css
@@ -106,4 +106,12 @@ li.nav-item .active{
     background-color: #003366;
     border-color: #003366;
     color: #FAFAFA;
+}
+
+.label-required{
+    font-weight: bold;
+}
+
+.ul-alert{
+    margin-bottom: 0;
 }
\ No newline at end of file
