Mnoho změn - Zvestovatele zrušeni, Mapy celého obvodu
parent
a546f95b56
commit
fdbaacb357
|
|
@ -34,14 +34,50 @@ final class ObvodFacade
|
|||
return $this->database->fetch($sql, $id);
|
||||
}
|
||||
|
||||
public const SKUPINA_COOKIE = 'obvodSkupina';
|
||||
|
||||
/**
|
||||
* Základní informace o obvodech.
|
||||
* @return Nette\Database\Table\ActiveRow|null
|
||||
* @return array<int, Row>
|
||||
*/
|
||||
public function getObvody(): array
|
||||
public function getObvody(?string $skupina = null): array
|
||||
{
|
||||
$sql = "SELECT * FROM OBVOD";
|
||||
return $this->database->fetchAll($sql );
|
||||
if ($skupina === null || $skupina === '') {
|
||||
return $this->database->fetchAll('SELECT * FROM OBVOD ORDER BY CISLO');
|
||||
}
|
||||
|
||||
return $this->database->fetchAll(
|
||||
'SELECT * FROM OBVOD WHERE SKUPINA = ? ORDER BY CISLO',
|
||||
$skupina,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getSkupiny(): array
|
||||
{
|
||||
$radky = $this->database->fetchAll(
|
||||
"SELECT DISTINCT SKUPINA
|
||||
FROM OBVOD
|
||||
WHERE SKUPINA IS NOT NULL AND LTRIM(RTRIM(SKUPINA)) <> ''
|
||||
ORDER BY SKUPINA",
|
||||
);
|
||||
|
||||
return array_map(static fn(Row $radek): string => (string) $radek->SKUPINA, $radky);
|
||||
}
|
||||
|
||||
public function vyberSkupinu(?string $pozadovana, ?string $ulozena): string
|
||||
{
|
||||
$skupiny = $this->getSkupiny();
|
||||
if ($pozadovana !== null && in_array($pozadovana, $skupiny, true)) {
|
||||
return $pozadovana;
|
||||
}
|
||||
if ($ulozena !== null && in_array($ulozena, $skupiny, true)) {
|
||||
return $ulozena;
|
||||
}
|
||||
|
||||
return $skupiny[0] ?? '';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -28,8 +28,149 @@ final class ZaznamFacade
|
|||
*/
|
||||
public function getZaznamyObvodu(int $obvod): array
|
||||
{
|
||||
$sql = "SELECT * FROM ZAZNAM WHERE OBVOD = ?";
|
||||
$sql = 'SELECT * FROM ZAZNAM WHERE OBVOD = ? AND DELETED = 0 ORDER BY VYDANO DESC, ID DESC';
|
||||
return $this->database->fetchAll($sql, $obvod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Jména zvěstovatelů pro výběr při přidělení.
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function getZvestovatele(): array
|
||||
{
|
||||
$rows = $this->database->fetchAll(
|
||||
'SELECT DISTINCT JMENO FROM ZAZNAM WHERE DELETED = 0 ORDER BY JMENO'
|
||||
);
|
||||
$jmena = [];
|
||||
foreach ($rows as $row) {
|
||||
$jmena[$row->JMENO] = $row->JMENO;
|
||||
}
|
||||
return $jmena;
|
||||
}
|
||||
|
||||
/**
|
||||
* Přidělení, která ještě nebyla vrácena.
|
||||
*/
|
||||
public function getOtevrene(int $obvod): array
|
||||
{
|
||||
return $this->database->fetchAll(
|
||||
'SELECT * FROM ZAZNAM WHERE OBVOD = ? AND DELETED = 0 AND VRACENO IS NULL ORDER BY VYDANO, ID',
|
||||
$obvod
|
||||
);
|
||||
}
|
||||
|
||||
public function getZaznam(int $id): Row|null
|
||||
{
|
||||
return $this->database->fetch('SELECT * FROM ZAZNAM WHERE ID = ? AND DELETED = 0', $id);
|
||||
}
|
||||
|
||||
public function pridej(int $obvod, string $jmeno, \DateTimeInterface $vydano): void
|
||||
{
|
||||
$this->database->query(
|
||||
'INSERT INTO ZAZNAM (OBVOD, JMENO, VYDANO, VRACENO, DELETED) VALUES (?, ?, ?, NULL, 0)',
|
||||
$obvod,
|
||||
$jmeno,
|
||||
$vydano
|
||||
);
|
||||
}
|
||||
|
||||
public function uprav(int $id, int $obvod, string $jmeno, \DateTimeInterface $vydano, ?\DateTimeInterface $vraceno): bool
|
||||
{
|
||||
$zaznam = $this->getZaznam($id);
|
||||
if ($zaznam === null || (int) $zaznam->OBVOD !== $obvod) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->database->query(
|
||||
'UPDATE ZAZNAM SET JMENO = ?, VYDANO = ?, VRACENO = ? WHERE ID = ? AND OBVOD = ? AND DELETED = 0',
|
||||
$jmeno,
|
||||
$vydano,
|
||||
$vraceno,
|
||||
$id,
|
||||
$obvod
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function smaz(int $id, int $obvod): bool
|
||||
{
|
||||
$zaznam = $this->getZaznam($id);
|
||||
if ($zaznam === null || (int) $zaznam->OBVOD !== $obvod) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->database->query(
|
||||
'UPDATE ZAZNAM SET DELETED = 1 WHERE ID = ? AND OBVOD = ? AND DELETED = 0',
|
||||
$id,
|
||||
$obvod
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stáří obvodů podle posledního vrácení a příznak probíhajícího přidělení.
|
||||
* Klíčem je číslo obvodu.
|
||||
*
|
||||
* @return array<int, array{id: int, mesice: int|null, probiha: bool, vraceno: string|null, prideleni: int|null, prideleniDatum: string|null}>
|
||||
*/
|
||||
public function getStariObvodu(): array
|
||||
{
|
||||
$radky = $this->database->fetchAll(
|
||||
'SELECT o.ID, o.CISLO,
|
||||
MAX(CASE WHEN z.VRACENO IS NOT NULL THEN z.VRACENO END) AS POSLEDNI,
|
||||
MIN(CASE WHEN z.VRACENO IS NULL THEN z.VYDANO END) AS PRIDELENO,
|
||||
SUM(CASE WHEN z.ID IS NOT NULL AND z.VRACENO IS NULL THEN 1 ELSE 0 END) AS OTEVRENE
|
||||
FROM OBVOD o
|
||||
LEFT JOIN ZAZNAM z ON z.OBVOD = o.ID AND z.DELETED = 0
|
||||
GROUP BY o.ID, o.CISLO'
|
||||
);
|
||||
|
||||
$dnes = new \DateTimeImmutable('today');
|
||||
$vysledek = [];
|
||||
foreach ($radky as $radek) {
|
||||
$mesice = null;
|
||||
$vraceno = null;
|
||||
if ($radek->POSLEDNI !== null) {
|
||||
$datum = $radek->POSLEDNI instanceof \DateTimeInterface
|
||||
? \DateTimeImmutable::createFromInterface($radek->POSLEDNI)
|
||||
: new \DateTimeImmutable((string) $radek->POSLEDNI);
|
||||
$vraceno = $datum->format('j. n. Y');
|
||||
$rozdil = $datum->diff($dnes);
|
||||
$mesice = $rozdil->invert === 1 ? 0 : ($rozdil->y * 12 + $rozdil->m);
|
||||
}
|
||||
|
||||
$prideleni = null;
|
||||
$prideleniDatum = null;
|
||||
if ($radek->PRIDELENO !== null) {
|
||||
$zacatek = $radek->PRIDELENO instanceof \DateTimeInterface
|
||||
? \DateTimeImmutable::createFromInterface($radek->PRIDELENO)
|
||||
: new \DateTimeImmutable((string) $radek->PRIDELENO);
|
||||
$prideleniDatum = $zacatek->format('j. n. Y');
|
||||
$odPrideleni = $zacatek->diff($dnes);
|
||||
$prideleni = $odPrideleni->invert === 1 ? 0 : ($odPrideleni->y * 12 + $odPrideleni->m);
|
||||
}
|
||||
|
||||
$vysledek[(int) $radek->CISLO] = [
|
||||
'id' => (int) $radek->ID,
|
||||
'mesice' => $mesice,
|
||||
'probiha' => (int) $radek->OTEVRENE > 0,
|
||||
'vraceno' => $vraceno,
|
||||
'prideleni' => $prideleni,
|
||||
'prideleniDatum' => $prideleniDatum,
|
||||
];
|
||||
}
|
||||
|
||||
return $vysledek;
|
||||
}
|
||||
|
||||
public function vrat(int $id, int $obvod, \DateTimeInterface $vraceno): void
|
||||
{
|
||||
$this->database->query(
|
||||
'UPDATE ZAZNAM SET VRACENO = ? WHERE ID = ? AND OBVOD = ? AND DELETED = 0 AND VRACENO IS NULL',
|
||||
$vraceno,
|
||||
$id,
|
||||
$obvod
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
use Nette\Database\Explorer;
|
||||
use Nette\Database\Row;
|
||||
|
||||
/**
|
||||
* Jména zvěstovatelů odvozená ze záznamů.
|
||||
*/
|
||||
final class ZvestovatelFacade
|
||||
{
|
||||
public function __construct(
|
||||
private Explorer $database,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<Row>
|
||||
*/
|
||||
public function getVsechny(): array
|
||||
{
|
||||
return $this->database->fetchAll(
|
||||
'SELECT JMENO, COUNT(*) AS POCET
|
||||
FROM ZAZNAM
|
||||
WHERE DELETED = 0
|
||||
GROUP BY JMENO
|
||||
ORDER BY JMENO'
|
||||
);
|
||||
}
|
||||
|
||||
public function get(string $jmeno): Row|null
|
||||
{
|
||||
return $this->database->fetch(
|
||||
'SELECT JMENO, COUNT(*) AS POCET
|
||||
FROM ZAZNAM
|
||||
WHERE DELETED = 0 AND JMENO = ?
|
||||
GROUP BY JMENO',
|
||||
$jmeno
|
||||
);
|
||||
}
|
||||
|
||||
public function existujeJmeno(string $jmeno, ?string $krome = null): bool
|
||||
{
|
||||
if ($krome === null) {
|
||||
return $this->get($jmeno) !== null;
|
||||
}
|
||||
|
||||
return $this->database->fetchField(
|
||||
'SELECT TOP 1 ID FROM ZAZNAM WHERE DELETED = 0 AND JMENO = ? AND JMENO <> ?',
|
||||
$jmeno,
|
||||
$krome
|
||||
) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Přejmenuje zvěstovatele ve všech jeho záznamech.
|
||||
*/
|
||||
public function uprav(string $puvodni, string $jmeno): void
|
||||
{
|
||||
if ($this->get($puvodni) === null || $puvodni === $jmeno) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->database->query(
|
||||
'UPDATE ZAZNAM SET JMENO = ? WHERE JMENO = ?',
|
||||
$jmeno,
|
||||
$puvodni
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
</script>
|
||||
|
||||
{* naše vlastní styly *}
|
||||
<link href="{$basePath}/css/base.css" rel="stylesheet">
|
||||
<link href="{$basePath}/css/base.css?v=3" rel="stylesheet">
|
||||
|
||||
<title>{ifset title}{include title|stripHtml} | {/ifset}Obvody</title>
|
||||
</head>
|
||||
|
|
@ -54,7 +54,8 @@
|
|||
{if $user->isLoggedIn()}
|
||||
<a class='nav-link' n:href=':Home:'>Obvody</a>
|
||||
<a class='nav-link' n:href=':Mapa:'>Celý obvod</a>
|
||||
<a class='nav-link' n:href=':Obvod:list'>Seznam obvodů</a>
|
||||
<a class='nav-link' n:href=':Obvod:list'>Obvody</a>
|
||||
<a class='nav-link' n:href=':Zvestovatel:'>Zvěstovatelé</a>
|
||||
<a class='nav-link' n:href=":Sign:out">Odhlásit</a>
|
||||
{else}
|
||||
<a class='nav-link' n:href=':Sign:in'>Přihlásit</a>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{* This is the welcome page, you can delete it *}
|
||||
|
||||
{block content}
|
||||
<h3>Seznam obvodů</h3>
|
||||
<h3>Obvody</h3>
|
||||
|
||||
{* Tlačítka: *}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,17 +4,41 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Presentation\Mapa;
|
||||
|
||||
use App\Model\ZaznamFacade;
|
||||
use Nette;
|
||||
use Nette\Utils\Json;
|
||||
|
||||
|
||||
final class MapaPresenter extends Nette\Application\UI\Presenter
|
||||
{
|
||||
public function beforeRender(): void
|
||||
{
|
||||
/** Od tohoto počtu měsíců je obvod ve filtru stáří vykreslený červeně. */
|
||||
public const STUPNICE_MESICU = 24;
|
||||
|
||||
/** Od tohoto počtu měsíců je rozpracovaný obvod vykreslený červeně. */
|
||||
public const STUPNICE_PRIDELENI = 12;
|
||||
|
||||
public function __construct(
|
||||
private ZaznamFacade $zaznam,
|
||||
) {
|
||||
}
|
||||
|
||||
public function renderDefault(int $obvod = null): void
|
||||
public function renderDefault(?int $obvod = null, ?string $filtr = null): void
|
||||
{
|
||||
$povolene = ['obvody', 'stari', 'nedokoncene'];
|
||||
if (!in_array($filtr, $povolene, true)) {
|
||||
$filtr = 'obvody';
|
||||
}
|
||||
|
||||
$stari = $this->zaznam->getStariObvodu();
|
||||
foreach ($stari as &$polozka) {
|
||||
$polozka['url'] = $this->link('Obvod:view', $polozka['id']);
|
||||
}
|
||||
unset($polozka);
|
||||
|
||||
$this->template->obvod_id = $obvod;
|
||||
$this->template->filtr = $filtr;
|
||||
$this->template->maxMesicu = self::STUPNICE_MESICU;
|
||||
$this->template->maxPrideleni = self::STUPNICE_PRIDELENI;
|
||||
$this->template->stariJson = Json::encode($stari, htmlSafe: true, forceObjects: true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
{* Zobrazení obvodu na mapě. *}
|
||||
{block title}Celý obvod{/block}
|
||||
|
||||
{block content}
|
||||
<div id="map"></div>
|
||||
|
||||
<script type="text/javascript">
|
||||
var obvod_id = {$obvod_id};
|
||||
</script>
|
||||
|
||||
<script src="{$basePath}/js/mapa.js"></script>
|
||||
<script src="{$basePath}/js/obvody.js"></script>
|
||||
<div id="map"
|
||||
data-geojson="{$basePath}/data/geo-obvody.geojson"
|
||||
data-max="{$maxMesicu}"
|
||||
data-prideleni="{$maxPrideleni}"
|
||||
data-filtr="{$filtr}"
|
||||
data-obvod="{if $obvod_id}{$obvod_id}{/if}"></div>
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css" />
|
||||
<script src="https://unpkg.com/leaflet.markercluster@1.5.3/dist/leaflet.markercluster.js"></script>
|
||||
<script type="application/json" id="stari-data">{$stariJson|noescape}</script>
|
||||
<script src="{$basePath}/js/stari.js?v=9"></script>
|
||||
{/block}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,15 @@ namespace App\Presentation\Obvod;
|
|||
use App\Model\ObvodFacade;
|
||||
use App\Model\ZaznamFacade;
|
||||
use Nette;
|
||||
use Nette\Application\UI\Form;
|
||||
use Nette\Application\UI\Multiplier;
|
||||
use Nette\Utils\ArrayHash;
|
||||
|
||||
|
||||
final class ObvodPresenter extends Nette\Application\UI\Presenter
|
||||
{
|
||||
private string $skupina = '';
|
||||
|
||||
public function __construct(
|
||||
private ObvodFacade $obvod,
|
||||
private ZaznamFacade $zaznam
|
||||
|
|
@ -21,14 +26,191 @@ final class ObvodPresenter extends Nette\Application\UI\Presenter
|
|||
{
|
||||
}
|
||||
|
||||
public function actionList(?string $skupina = null): void
|
||||
{
|
||||
$this->skupina = $this->obvod->vyberSkupinu(
|
||||
$skupina,
|
||||
$this->getHttpRequest()->getCookie(ObvodFacade::SKUPINA_COOKIE),
|
||||
);
|
||||
if ($skupina !== null && $skupina === $this->skupina && $this->skupina !== '') {
|
||||
$this->getHttpResponse()->setCookie(ObvodFacade::SKUPINA_COOKIE, $this->skupina, '+1 year', '/');
|
||||
}
|
||||
}
|
||||
|
||||
public function renderList(): void
|
||||
{
|
||||
$this->template->obvody = $this->obvod->getObvody();
|
||||
$this->template->skupina = $this->skupina;
|
||||
$this->template->skupiny = $this->obvod->getSkupiny();
|
||||
$this->template->obvody = $this->obvod->getObvody($this->skupina);
|
||||
}
|
||||
|
||||
public function renderView(int $id = null): void
|
||||
{
|
||||
$this->template->obvod = $this->obvod->getObvod($id);
|
||||
$this->template->zaznamy = $this->zaznam->getZaznamyObvodu($id);
|
||||
$this->template->zvestovateleJson = json_encode(
|
||||
array_values($this->zaznam->getZvestovatele()),
|
||||
JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_THROW_ON_ERROR
|
||||
);
|
||||
$this->template->otevrene = $id === null ? [] : $this->zaznam->getOtevrene($id);
|
||||
}
|
||||
|
||||
protected function createComponentPridelitForm(): Form
|
||||
{
|
||||
$form = new Form;
|
||||
$form->addText('zvestovatel', 'Zvěstovatel')
|
||||
->setRequired('Zadejte zvěstovatele.')
|
||||
->setMaxLength(100);
|
||||
$form->addDate('datum', 'Datum přidělení')
|
||||
->setRequired('Zadejte datum přidělení.')
|
||||
->setDefaultValue(new \DateTimeImmutable('today'));
|
||||
$form->addSubmit('send', 'Přidělit');
|
||||
$form->addProtection('Platnost formuláře vypršela, odešlete ho znovu.');
|
||||
$form->onSuccess[] = $this->pridelitFormSucceeded(...);
|
||||
return $form;
|
||||
}
|
||||
|
||||
private function pridelitFormSucceeded(Form $form, ArrayHash $values): void
|
||||
{
|
||||
$id = $this->obvodId();
|
||||
$jmeno = trim((string) $values->zvestovatel);
|
||||
if ($jmeno === '') {
|
||||
$form->addError('Zadejte zvěstovatele.');
|
||||
return;
|
||||
}
|
||||
if (mb_strlen($jmeno) > 100) {
|
||||
$form->addError('Jméno zvěstovatele je příliš dlouhé.');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->zaznam->pridej($id, $jmeno, $this->den($values->datum));
|
||||
$this->flashMessage('Obvod byl přidělen.');
|
||||
$this->redirect('this');
|
||||
}
|
||||
|
||||
protected function createComponentVratitForm(): Form
|
||||
{
|
||||
$form = new Form;
|
||||
$polozky = [];
|
||||
foreach ($this->zaznam->getOtevrene($this->obvodId()) as $zaznam) {
|
||||
$polozky[$zaznam->ID] = $zaznam->JMENO . ' (přiděleno ' . $this->den($zaznam->VYDANO)->format('d.m.Y') . ')';
|
||||
}
|
||||
$form->addSelect('zaznam', 'Záznam', $polozky)
|
||||
->setRequired('Vyberte záznam k vrácení.');
|
||||
$form->addDate('datum', 'Datum vrácení')
|
||||
->setRequired('Zadejte datum vrácení.')
|
||||
->setDefaultValue(new \DateTimeImmutable('today'));
|
||||
$form->addSubmit('send', 'Vrátit');
|
||||
$form->addProtection('Platnost formuláře vypršela, odešlete ho znovu.');
|
||||
$form->onSuccess[] = $this->vratitFormSucceeded(...);
|
||||
return $form;
|
||||
}
|
||||
|
||||
private function vratitFormSucceeded(Form $form, ArrayHash $values): void
|
||||
{
|
||||
$obvodId = $this->obvodId();
|
||||
$zaznam = $this->zaznam->getZaznam((int) $values->zaznam);
|
||||
if ($zaznam === null || (int) $zaznam->OBVOD !== $obvodId || $zaznam->VRACENO !== null) {
|
||||
$form->addError('Tento záznam už nelze vrátit.');
|
||||
return;
|
||||
}
|
||||
|
||||
$vraceno = $this->den($values->datum);
|
||||
if ($this->den($zaznam->VYDANO) > $vraceno) {
|
||||
$form->addError('Datum přidělení nesmí být později než datum vrácení.');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->zaznam->vrat((int) $zaznam->ID, $obvodId, $vraceno);
|
||||
$this->flashMessage('Obvod byl vrácen.');
|
||||
$this->redirect('this');
|
||||
}
|
||||
|
||||
protected function createComponentUpravitForm(): Form
|
||||
{
|
||||
$form = new Form;
|
||||
$form->addHidden('id');
|
||||
$form->addText('zvestovatel', 'Zvěstovatel')
|
||||
->setRequired('Zadejte zvěstovatele.')
|
||||
->setMaxLength(100);
|
||||
$form->addDate('vydano', 'Datum přidělení')
|
||||
->setRequired('Zadejte datum přidělení.');
|
||||
$form->addDate('vraceno', 'Datum vrácení');
|
||||
$form->addSubmit('send', 'Uložit');
|
||||
$form->addProtection('Platnost formuláře vypršela, odešlete ho znovu.');
|
||||
$form->onSuccess[] = $this->upravitFormSucceeded(...);
|
||||
return $form;
|
||||
}
|
||||
|
||||
private function upravitFormSucceeded(Form $form, ArrayHash $values): void
|
||||
{
|
||||
$obvodId = $this->obvodId();
|
||||
$zaznam = $this->zaznam->getZaznam((int) $values->id);
|
||||
if ($zaznam === null || (int) $zaznam->OBVOD !== $obvodId) {
|
||||
$form->addError('Tento záznam už nelze upravit.');
|
||||
return;
|
||||
}
|
||||
|
||||
$jmeno = trim((string) $values->zvestovatel);
|
||||
if ($jmeno === '') {
|
||||
$form->addError('Zadejte zvěstovatele.');
|
||||
return;
|
||||
}
|
||||
if (mb_strlen($jmeno) > 100) {
|
||||
$form->addError('Jméno zvěstovatele je příliš dlouhé.');
|
||||
return;
|
||||
}
|
||||
|
||||
$vydano = $this->den($values->vydano);
|
||||
$vraceno = $values->vraceno !== null && $values->vraceno !== ''
|
||||
? $this->den($values->vraceno)
|
||||
: null;
|
||||
if ($vraceno !== null && $vydano > $vraceno) {
|
||||
$form->addError('Datum přidělení nesmí být později než datum vrácení.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->zaznam->uprav((int) $zaznam->ID, $obvodId, $jmeno, $vydano, $vraceno)) {
|
||||
$form->addError('Tento záznam už nelze upravit.');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->flashMessage('Záznam byl upraven.');
|
||||
$this->redirect('this');
|
||||
}
|
||||
|
||||
protected function createComponentSmazat(): Multiplier
|
||||
{
|
||||
return new Multiplier(function (string $id): Form {
|
||||
$form = new Form;
|
||||
$form->addSubmit('send', 'Smazat');
|
||||
$form->addProtection('Platnost formuláře vypršela, odešlete ho znovu.');
|
||||
$form->onSuccess[] = function () use ($id): void {
|
||||
if ($this->zaznam->smaz((int) $id, $this->obvodId())) {
|
||||
$this->flashMessage('Záznam byl smazán.');
|
||||
} else {
|
||||
$this->flashMessage('Tento záznam už nelze smazat.');
|
||||
}
|
||||
$this->redirect('this');
|
||||
};
|
||||
return $form;
|
||||
});
|
||||
}
|
||||
|
||||
private function obvodId(): int
|
||||
{
|
||||
$id = $this->getParameter('id');
|
||||
if ($id === null || $id === '') {
|
||||
$this->error('Obvod nenalezen.', Nette\Http\IResponse::S404_NotFound);
|
||||
}
|
||||
return (int) $id;
|
||||
}
|
||||
|
||||
private function den(mixed $value): \DateTimeImmutable
|
||||
{
|
||||
$datum = $value instanceof \DateTimeInterface
|
||||
? \DateTimeImmutable::createFromInterface($value)
|
||||
: new \DateTimeImmutable((string) $value);
|
||||
return $datum->setTime(0, 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,31 @@
|
|||
{block content}
|
||||
|
||||
{foreach $obvody as $obvod}
|
||||
<a n:href=":Obvod:view id:$obvod->ID" type="button" class="btn btn-klavesa btn-letter btn-flex box-shadow--6dp">
|
||||
{$obvod->CISLO}
|
||||
</a>
|
||||
{/foreach}
|
||||
<div class="container-fluid p-2">
|
||||
<h3>Obvody</h3>
|
||||
<div class="d-flex flex-wrap gap-2 mb-2" n:if="$skupiny">
|
||||
<div class="btn-group flex-wrap" role="group" aria-label="Filtr skupin">
|
||||
<a n:foreach="$skupiny as $s" n:href="list, skupina: $s" n:class="$skupina === $s ? 'btn btn-klavesa' : 'btn btn-outline-secondary'">{$s}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-sm table-striped mb-1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="text-start">Číslo</th>
|
||||
<th scope="col" class="text-start">Název</th>
|
||||
<th scope="col" class="text-start">Akce</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{foreach $obvody as $obvod}
|
||||
<tr>
|
||||
<td>{$obvod->CISLO}</td>
|
||||
<td>{$obvod->NAZEV}</td>
|
||||
<td>
|
||||
<a n:href=":Obvod:view id:$obvod->ID" class="btn btn-sm btn-klavesa box-shadow--6dp">Otevřít</a>
|
||||
</td>
|
||||
</tr>
|
||||
{/foreach}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
@ -1,12 +1,18 @@
|
|||
{block content}
|
||||
|
||||
<div class="container-fluid p-2">
|
||||
Číslo: {$obvod->CISLO}<br>
|
||||
Název: {$obvod->NAZEV}<br>
|
||||
Skupina: {$obvod->SKUPINA}<br>
|
||||
<a n:href=":Mapa: obvod:$obvod->CISLO" type="button" class="btn btn-klavesa btn-flex box-shadow--6dp">Mapa</a>
|
||||
|
||||
<div id="obvod-nahled"
|
||||
class="box-shadow--6dp my-2"
|
||||
data-obvod="{$obvod->CISLO}"
|
||||
data-geojson="{$basePath}/data/geo-obvody.geojson"></div>
|
||||
<h5>Záznamy:</h5>
|
||||
<a n:href=":Mapa: obvod:$obvod->CISLO" type="button" class="btn btn-klavesa btn-flex box-shadow--6dp">Přidělit</a>
|
||||
<a n:href=":Mapa: obvod:$obvod->CISLO" type="button" class="btn btn-klavesa btn-flex box-shadow--6dp">Vrátit</a>
|
||||
<button type="button" class="btn btn-klavesa btn-flex box-shadow--6dp" data-bs-toggle="modal" data-bs-target="#modalPridelit">Přidělit</button>
|
||||
<button type="button" class="btn btn-klavesa btn-flex box-shadow--6dp" data-bs-toggle="modal" data-bs-target="#modalVratit">Vrátit</button>
|
||||
<table class="table table-sm table-striped mx-auto mb-1">
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
@ -19,14 +25,170 @@ Skupina: {$obvod->SKUPINA}<br>
|
|||
<tbody>
|
||||
{foreach $zaznamy as $zaznam}
|
||||
<tr>
|
||||
<td>{$zaznam->ZAZNAM}</td>
|
||||
<td>{$zaznam->JMENO}</td>
|
||||
<td class="text-start">
|
||||
{date_format($zaznam->VYDANO,"d.m.Y")}
|
||||
</td>
|
||||
<td class="text-start">
|
||||
{date_format($zaznam->VRACENO,"d.m.Y")}
|
||||
{if $zaznam->VRACENO}{date_format($zaznam->VRACENO,"d.m.Y")}{/if}
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex flex-wrap gap-1">
|
||||
<button type="button" class="btn btn-sm btn-klavesa box-shadow--6dp" data-bs-toggle="modal" data-bs-target="#modalUpravit" data-id="{$zaznam->ID}" data-jmeno="{$zaznam->JMENO}" data-vydano="{date_format($zaznam->VYDANO, 'Y-m-d')}" data-vraceno="{if $zaznam->VRACENO}{date_format($zaznam->VRACENO, 'Y-m-d')}{/if}">Upravit</button>
|
||||
{form "smazat-$zaznam->ID", class: 'd-inline'}
|
||||
<button n:name="send" class="btn btn-sm btn-outline-danger" onclick="return confirm('Smazat záznam?')">Smazat</button>
|
||||
{/form}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/foreach}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="modalPridelit" tabindex="-1" aria-labelledby="modalPridelitLabel" aria-hidden="true" data-show-errors="{if $presenter['pridelitForm']->hasErrors()}1{else}0{/if}">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form n:name="pridelitForm">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalPridelitLabel">Přidělit obvod</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Zavřít"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-danger" n:if="$form->hasErrors()" n:foreach="$form->getErrors() as $error">{$error}</div>
|
||||
<div class="mb-2">
|
||||
<label n:name="zvestovatel" class="form-label">Zvěstovatel</label>
|
||||
<div class="naseptavac" data-naseptavac>
|
||||
<input n:name="zvestovatel" class="form-control" autocomplete="off" role="combobox" aria-autocomplete="list" aria-expanded="false" aria-controls="naseptavac-nabidka">
|
||||
<div id="naseptavac-nabidka" class="naseptavac-nabidka" role="listbox" hidden></div>
|
||||
</div>
|
||||
<div class="form-text">Začněte psát jméno. Když v seznamu není, uloží se jako nový zvěstovatel.</div>
|
||||
</div>
|
||||
<div>
|
||||
<label n:name="datum" class="form-label">Datum přidělení</label>
|
||||
<input n:name="datum" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Zrušit</button>
|
||||
<button n:name="send" class="btn btn-klavesa box-shadow--6dp">Přidělit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="modalVratit" tabindex="-1" aria-labelledby="modalVratitLabel" aria-hidden="true" data-show-errors="{if $presenter['vratitForm']->hasErrors()}1{else}0{/if}">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalVratitLabel">Vrátit obvod</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Zavřít"></button>
|
||||
</div>
|
||||
{if count($otevrene) === 0}
|
||||
<div class="modal-body">Tento obvod nemá otevřené přidělení.</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Zavřít</button>
|
||||
</div>
|
||||
{else}
|
||||
<form n:name="vratitForm">
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-danger" n:if="$form->hasErrors()" n:foreach="$form->getErrors() as $error">{$error}</div>
|
||||
<div class="mb-2">
|
||||
<label n:name="zaznam" class="form-label">Záznam</label>
|
||||
<select n:name="zaznam" class="form-select">
|
||||
<option n:foreach="$otevrene as $otevreny" n:name="zaznam:$otevreny->ID">
|
||||
{$otevreny->JMENO} (přiděleno {date_format($otevreny->VYDANO, 'd.m.Y')})
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label n:name="datum" class="form-label">Datum vrácení</label>
|
||||
<input n:name="datum" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Zrušit</button>
|
||||
<button n:name="send" class="btn btn-klavesa box-shadow--6dp">Vrátit</button>
|
||||
</div>
|
||||
</form>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="modalUpravit" tabindex="-1" aria-labelledby="modalUpravitLabel" aria-hidden="true" data-show-errors="{if $presenter['upravitForm']->hasErrors()}1{else}0{/if}">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form n:name="upravitForm">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalUpravitLabel">Upravit záznam</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Zavřít"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-danger" n:if="$form->hasErrors()" n:foreach="$form->getErrors() as $error">{$error}</div>
|
||||
<input n:name="id">
|
||||
<div class="mb-2">
|
||||
<label n:name="zvestovatel" class="form-label">Zvěstovatel</label>
|
||||
<div class="naseptavac" data-naseptavac>
|
||||
<input n:name="zvestovatel" class="form-control" autocomplete="off" role="combobox" aria-autocomplete="list" aria-expanded="false" aria-controls="naseptavac-nabidka-upravit">
|
||||
<div id="naseptavac-nabidka-upravit" class="naseptavac-nabidka" role="listbox" hidden></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label n:name="vydano" class="form-label">Datum přidělení</label>
|
||||
<input n:name="vydano" class="form-control">
|
||||
</div>
|
||||
<div>
|
||||
<label n:name="vraceno" class="form-label">Datum vrácení</label>
|
||||
<input n:name="vraceno" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Zrušit</button>
|
||||
<button n:name="send" class="btn btn-klavesa box-shadow--6dp">Uložit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="application/json" id="zvestovatele-data">{$zvestovateleJson|noescape}</script>
|
||||
<script src="{$basePath}/js/obvod-nahled.js"></script>
|
||||
<script src="{$basePath}/js/naseptavac.js?v=3"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.querySelectorAll('.modal[data-show-errors="1"]').forEach(function (el) {
|
||||
bootstrap.Modal.getOrCreateInstance(el).show();
|
||||
});
|
||||
|
||||
var vratitMin = {
|
||||
{foreach $otevrene as $otevreny}{$otevreny->ID}: {date_format($otevreny->VYDANO, 'Y-m-d')},{/foreach}
|
||||
};
|
||||
var zaznam = document.getElementById('frm-vratitForm-zaznam');
|
||||
var datum = document.getElementById('frm-vratitForm-datum');
|
||||
if (!zaznam || !datum) {
|
||||
return;
|
||||
}
|
||||
var nastavMin = function () {
|
||||
datum.min = vratitMin[zaznam.value] || '';
|
||||
};
|
||||
zaznam.addEventListener('change', nastavMin);
|
||||
nastavMin();
|
||||
});
|
||||
|
||||
var modalUpravit = document.getElementById('modalUpravit');
|
||||
if (modalUpravit) {
|
||||
modalUpravit.addEventListener('show.bs.modal', function (event) {
|
||||
var btn = event.relatedTarget;
|
||||
var form = modalUpravit.querySelector('form');
|
||||
if (!btn || !btn.dataset.id || !form) {
|
||||
return;
|
||||
}
|
||||
form.querySelector('[name="id"]').value = btn.dataset.id;
|
||||
form.querySelector('[name="zvestovatel"]').value = btn.dataset.jmeno || '';
|
||||
form.querySelector('[name="vydano"]').value = btn.dataset.vydano || '';
|
||||
form.querySelector('[name="vraceno"]').value = btn.dataset.vraceno || '';
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Presentation\Stari;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
final class StariPresenter extends Nette\Application\UI\Presenter
|
||||
{
|
||||
public function actionDefault(): void
|
||||
{
|
||||
$this->redirect(':Mapa:default', ['filtr' => 'stari']);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{block title}Stáří obvodů{/block}
|
||||
|
||||
{block content}
|
||||
<div id="map" data-geojson="{$basePath}/data/geo-obvody.geojson" data-max="{$maxMesicu}"></div>
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css" />
|
||||
<script src="https://unpkg.com/leaflet.markercluster@1.5.3/dist/leaflet.markercluster.js"></script>
|
||||
<script type="application/json" id="stari-data">{$stariJson|noescape}</script>
|
||||
<script src="{$basePath}/js/stari.js?v=5"></script>
|
||||
{/block}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Presentation\Zvestovatel;
|
||||
|
||||
use App\Model\ZvestovatelFacade;
|
||||
use Nette;
|
||||
use Nette\Application\UI\Form;
|
||||
use Nette\Utils\ArrayHash;
|
||||
|
||||
|
||||
final class ZvestovatelPresenter extends Nette\Application\UI\Presenter
|
||||
{
|
||||
public function __construct(
|
||||
private ZvestovatelFacade $zvestovatele,
|
||||
) {
|
||||
}
|
||||
|
||||
public function renderDefault(): void
|
||||
{
|
||||
$this->template->zvestovatele = $this->zvestovatele->getVsechny();
|
||||
}
|
||||
|
||||
public function actionEdit(string $jmeno = ''): void
|
||||
{
|
||||
$jmeno = trim($jmeno);
|
||||
if ($this->zvestovatele->get($jmeno) === null) {
|
||||
$this->error('Zvěstovatel nenalezen.', Nette\Http\IResponse::S404_NotFound);
|
||||
}
|
||||
}
|
||||
|
||||
public function renderEdit(string $jmeno = ''): void
|
||||
{
|
||||
$jmeno = trim($jmeno);
|
||||
$this['zvestovatelForm']->setDefaults([
|
||||
'puvodni' => $jmeno,
|
||||
'jmeno' => $jmeno,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function createComponentZvestovatelForm(): Form
|
||||
{
|
||||
$form = new Form;
|
||||
$form->addHidden('puvodni');
|
||||
$form->addText('jmeno', 'Jméno')
|
||||
->setRequired('Zadejte jméno zvěstovatele.')
|
||||
->setMaxLength(100);
|
||||
$form->addSubmit('send', 'Uložit');
|
||||
$form->addProtection('Platnost formuláře vypršela, odešlete ho znovu.');
|
||||
$form->onSuccess[] = $this->zvestovatelFormSucceeded(...);
|
||||
return $form;
|
||||
}
|
||||
|
||||
private function zvestovatelFormSucceeded(Form $form, ArrayHash $values): void
|
||||
{
|
||||
$jmeno = trim((string) $values->jmeno);
|
||||
$puvodni = trim((string) $values->puvodni);
|
||||
if ($jmeno === '') {
|
||||
$form->addError('Zadejte jméno zvěstovatele.');
|
||||
return;
|
||||
}
|
||||
if (mb_strlen($jmeno) > 100) {
|
||||
$form->addError('Jméno zvěstovatele je příliš dlouhé.');
|
||||
return;
|
||||
}
|
||||
if ($this->zvestovatele->get($puvodni) === null) {
|
||||
$form->addError('Zvěstovatel nenalezen.');
|
||||
return;
|
||||
}
|
||||
if ($jmeno !== $puvodni && $this->zvestovatele->existujeJmeno($jmeno, $puvodni)) {
|
||||
$form->addError('Zvěstovatel s tímto jménem už existuje.');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->zvestovatele->uprav($puvodni, $jmeno);
|
||||
$this->flashMessage('Zvěstovatel byl upraven.');
|
||||
$this->redirect('default');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
{block content}
|
||||
|
||||
<div class="container-fluid p-2">
|
||||
<h3>Zvěstovatelé</h3>
|
||||
|
||||
<table class="table table-sm table-striped mb-1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="text-start">Jméno</th>
|
||||
<th scope="col" class="text-start">Záznamy</th>
|
||||
<th scope="col" class="text-start">Akce</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{foreach $zvestovatele as $zvestovatel}
|
||||
<tr>
|
||||
<td>{$zvestovatel->JMENO}</td>
|
||||
<td>{$zvestovatel->POCET}</td>
|
||||
<td>
|
||||
<a n:href="edit, jmeno: $zvestovatel->JMENO" class="btn btn-sm btn-klavesa box-shadow--6dp">Upravit</a>
|
||||
</td>
|
||||
</tr>
|
||||
{/foreach}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{block content}
|
||||
|
||||
<div class="container-fluid p-2">
|
||||
<h3>Upravit zvěstovatele</h3>
|
||||
<form n:name="zvestovatelForm" class="mb-3">
|
||||
<input n:name="puvodni">
|
||||
<div class="alert alert-danger" n:if="$form->hasErrors()" n:foreach="$form->getErrors() as $error">{$error}</div>
|
||||
<div class="mb-2">
|
||||
<label n:name="jmeno" class="form-label">Jméno</label>
|
||||
<input n:name="jmeno" class="form-control">
|
||||
</div>
|
||||
<button n:name="send" class="btn btn-klavesa box-shadow--6dp">Uložit</button>
|
||||
<a n:href="default" class="btn btn-secondary">Zpět</a>
|
||||
</form>
|
||||
</div>
|
||||
211
www/css/base.css
211
www/css/base.css
|
|
@ -7,6 +7,217 @@ html, body, #map {
|
|||
width: 100vw;
|
||||
}
|
||||
|
||||
body:has(#map[data-max]) {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#map[data-max] {
|
||||
flex: 1 1 auto;
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.naseptavac {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.naseptavac input {
|
||||
min-height: 2.75rem;
|
||||
font-size: 1rem;
|
||||
border-radius: 0.8rem;
|
||||
border-color: #d5e3f5;
|
||||
}
|
||||
|
||||
.naseptavac input:focus {
|
||||
border-color: #2652a8;
|
||||
box-shadow: 0 0 0 0.2rem rgba(38, 82, 168, 0.16);
|
||||
}
|
||||
|
||||
.naseptavac-nabidka {
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: calc(100% + 0.35rem);
|
||||
padding: 0.35rem;
|
||||
background: #fff;
|
||||
border: 0;
|
||||
border-radius: 0.9rem;
|
||||
box-shadow: 0 12px 32px rgba(25, 55, 110, 0.18);
|
||||
}
|
||||
|
||||
.naseptavac-polozka {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-height: 2.75rem;
|
||||
text-align: left;
|
||||
border: 0;
|
||||
border-radius: 0.65rem;
|
||||
background: transparent;
|
||||
color: #1a3a6b;
|
||||
padding: 0.55rem 0.8rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.naseptavac-polozka.is-active,
|
||||
.naseptavac-polozka:hover {
|
||||
background: #e3f2fd;
|
||||
color: #2652a8;
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
#modalPridelit .modal-dialog {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
#modalPridelit .modal-content {
|
||||
border: 0;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
.naseptavac-nabidka {
|
||||
position: static;
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
|
||||
.naseptavac input,
|
||||
.naseptavac-polozka {
|
||||
min-height: 3rem;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
}
|
||||
|
||||
.obvod-znacka,
|
||||
.obvod-skupina {
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.obvod-znacka-telo,
|
||||
.obvod-skupina-telo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.obvod-kolecko {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid transparent;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.obvod-badge {
|
||||
margin-top: 2px;
|
||||
padding: 1px 5px 2px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.28);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.obvod-skupina .obvod-kolecko {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.mapa-filtr {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
max-width: calc(100vw - 4rem);
|
||||
padding: 4px;
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.mapa-filtr button {
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: #1a3a6b;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
padding: 0.4rem 0.7rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.mapa-filtr button.is-active {
|
||||
background: #2652a8;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.stari-legenda {
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.25);
|
||||
padding: 8px 10px;
|
||||
width: 210px;
|
||||
color: #1a1a1a;
|
||||
font-size: 12px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.stari-legenda-nadpis {
|
||||
font-weight: 700;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.stari-legenda-skala {
|
||||
height: 10px;
|
||||
border-radius: 5px;
|
||||
background: linear-gradient(to right, rgb(27, 158, 62), rgb(230, 184, 0), rgb(198, 40, 40));
|
||||
}
|
||||
|
||||
.stari-legenda-popisky {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.stari-legenda-seda {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.stari-legenda-seda span {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 2px;
|
||||
background: #9e9e9e;
|
||||
border: 2px solid #616161;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#obvod-nahled {
|
||||
height: 42vh;
|
||||
min-height: 220px;
|
||||
width: 100%;
|
||||
border: 1px solid #90caf9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@media (orientation: portrait) {
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ var osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|||
// Výchozí bod + podkladové mapy:
|
||||
var map = L.map('map', {
|
||||
center: [50.853501146, 14.8425], // Hrádek
|
||||
zoom: 16,
|
||||
layers: [mapyCZ, mapyCZletecka, osm]
|
||||
zoom: 13,
|
||||
layers: [mapyCZ]
|
||||
});
|
||||
|
||||
// Rámeček s výběrem podkladů:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,116 @@
|
|||
(function () {
|
||||
var dataEl = document.getElementById('zvestovatele-data');
|
||||
var jmena = [];
|
||||
|
||||
try {
|
||||
jmena = JSON.parse(dataEl ? dataEl.textContent || '[]' : '[]');
|
||||
} catch (e) {
|
||||
jmena = [];
|
||||
}
|
||||
|
||||
document.querySelectorAll('[data-naseptavac]').forEach(function (root) {
|
||||
var input = root.querySelector('input');
|
||||
var nabidka = root.querySelector('.naseptavac-nabidka');
|
||||
var aktivni = -1;
|
||||
if (!input || !nabidka) {
|
||||
return;
|
||||
}
|
||||
|
||||
function klic(text) {
|
||||
return text.normalize('NFD').replace(/\p{M}/gu, '').toLocaleLowerCase('cs');
|
||||
}
|
||||
|
||||
function shody() {
|
||||
var dotaz = klic(input.value.trim());
|
||||
if (dotaz === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
var zacina = [];
|
||||
var obsahuje = [];
|
||||
jmena.forEach(function (jmeno) {
|
||||
var pozice = klic(jmeno).indexOf(dotaz);
|
||||
if (pozice === 0) {
|
||||
zacina.push(jmeno);
|
||||
} else if (pozice > 0) {
|
||||
obsahuje.push(jmeno);
|
||||
}
|
||||
});
|
||||
|
||||
return zacina.concat(obsahuje).slice(0, 5);
|
||||
}
|
||||
|
||||
function zavri() {
|
||||
nabidka.hidden = true;
|
||||
nabidka.replaceChildren();
|
||||
input.setAttribute('aria-expanded', 'false');
|
||||
aktivni = -1;
|
||||
}
|
||||
|
||||
function oznac() {
|
||||
var polozky = nabidka.querySelectorAll('.naseptavac-polozka');
|
||||
polozky.forEach(function (polozka, index) {
|
||||
polozka.classList.toggle('is-active', index === aktivni);
|
||||
});
|
||||
if (aktivni >= 0 && polozky[aktivni]) {
|
||||
polozky[aktivni].scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
}
|
||||
|
||||
function vykresli() {
|
||||
var polozky = shody();
|
||||
nabidka.replaceChildren();
|
||||
aktivni = -1;
|
||||
if (!polozky.length) {
|
||||
zavri();
|
||||
return;
|
||||
}
|
||||
|
||||
polozky.forEach(function (jmeno) {
|
||||
var tlacitko = document.createElement('button');
|
||||
tlacitko.type = 'button';
|
||||
tlacitko.className = 'naseptavac-polozka';
|
||||
tlacitko.setAttribute('role', 'option');
|
||||
tlacitko.textContent = jmeno;
|
||||
tlacitko.addEventListener('pointerdown', function (event) {
|
||||
event.preventDefault();
|
||||
input.value = jmeno;
|
||||
zavri();
|
||||
});
|
||||
nabidka.appendChild(tlacitko);
|
||||
});
|
||||
|
||||
nabidka.hidden = false;
|
||||
input.setAttribute('aria-expanded', 'true');
|
||||
}
|
||||
|
||||
input.addEventListener('input', vykresli);
|
||||
input.addEventListener('focus', vykresli);
|
||||
input.addEventListener('blur', function () {
|
||||
setTimeout(zavri, 150);
|
||||
});
|
||||
input.addEventListener('keydown', function (event) {
|
||||
var polozky = nabidka.querySelectorAll('.naseptavac-polozka');
|
||||
if (event.key === 'Escape') {
|
||||
zavri();
|
||||
return;
|
||||
}
|
||||
if (nabidka.hidden || !polozky.length) {
|
||||
return;
|
||||
}
|
||||
if (event.key === 'ArrowDown') {
|
||||
event.preventDefault();
|
||||
aktivni = Math.min(aktivni + 1, polozky.length - 1);
|
||||
oznac();
|
||||
} else if (event.key === 'ArrowUp') {
|
||||
event.preventDefault();
|
||||
aktivni = Math.max(aktivni - 1, 0);
|
||||
oznac();
|
||||
} else if (event.key === 'Enter' && aktivni >= 0) {
|
||||
event.preventDefault();
|
||||
input.value = polozky[aktivni].textContent;
|
||||
zavri();
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
// Náhled obvodu v detailu — stejný podklad a rámeček jako na celé mapě.
|
||||
(function () {
|
||||
var el = document.getElementById('obvod-nahled');
|
||||
if (!el || typeof L === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
var obvodId = el.getAttribute('data-obvod');
|
||||
var geojsonUrl = el.getAttribute('data-geojson');
|
||||
if (!obvodId || !geojsonUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
var API_KEY = 'Uw8gm6JPZpnT3GfNnh8adARpQI-WBGy6QaY9g6VhD74';
|
||||
var mapyCZ = L.tileLayer('https://api.mapy.cz/v1/maptiles/basic/256/{z}/{x}/{y}?apikey=' + API_KEY, {
|
||||
minZoom: 0,
|
||||
maxZoom: 19,
|
||||
attribution: '<a href="https://api.mapy.cz/copyright" target="_blank">© Seznam.cz a.s. a další</a>',
|
||||
});
|
||||
|
||||
var map = L.map(el, {
|
||||
center: [50.853501146, 14.8425],
|
||||
zoom: 14,
|
||||
layers: [mapyCZ],
|
||||
scrollWheelZoom: false,
|
||||
});
|
||||
|
||||
var LogoControl = L.Control.extend({
|
||||
options: { position: 'bottomleft' },
|
||||
onAdd: function () {
|
||||
var container = L.DomUtil.create('div');
|
||||
var link = L.DomUtil.create('a', '', container);
|
||||
link.setAttribute('href', 'http://mapy.cz/');
|
||||
link.setAttribute('target', '_blank');
|
||||
link.innerHTML = '<img src="https://api.mapy.cz/img/api/logo.svg" alt="Mapy.cz" />';
|
||||
L.DomEvent.disableClickPropagation(link);
|
||||
return container;
|
||||
},
|
||||
});
|
||||
new LogoControl().addTo(map);
|
||||
|
||||
function style() {
|
||||
return {
|
||||
fillColor: '#FD8D3C',
|
||||
weight: 2,
|
||||
opacity: 1,
|
||||
color: 'blue',
|
||||
dashArray: '3',
|
||||
fillOpacity: 0.05,
|
||||
};
|
||||
}
|
||||
|
||||
$.getJSON(geojsonUrl, function (data) {
|
||||
var layer = L.geoJSON(data, {
|
||||
style: style,
|
||||
filter: function (feature) {
|
||||
return feature.properties && String(feature.properties.number) === String(obvodId);
|
||||
},
|
||||
}).addTo(map);
|
||||
|
||||
if (layer.getLayers().length) {
|
||||
map.fitBounds(layer.getBounds(), { padding: [16, 16] });
|
||||
}
|
||||
map.invalidateSize();
|
||||
});
|
||||
})();
|
||||
|
|
@ -1,20 +1,16 @@
|
|||
// import souboru s obvody:
|
||||
$.ajax({
|
||||
'async': false,
|
||||
'global': false,
|
||||
'url': "../../data/geo-obvody.geojson",
|
||||
'dataType': "json",
|
||||
'success': function (data) {
|
||||
geojsonFeature = data;
|
||||
}
|
||||
});
|
||||
|
||||
var geojson;
|
||||
geojson = L.geoJSON(geojsonFeature, {
|
||||
var geojsonUrl = document.getElementById('map').getAttribute('data-geojson');
|
||||
$.getJSON(geojsonUrl, function (data) {
|
||||
geojson = L.geoJSON(data, {
|
||||
style: style,
|
||||
filter: picnicFilter,
|
||||
onEachFeature: onEachFeature
|
||||
}).addTo(map);
|
||||
}).addTo(map);
|
||||
|
||||
if (geojson.getLayers().length) {
|
||||
map.fitBounds(geojson.getBounds(), { padding: [12, 12], animate: false });
|
||||
}
|
||||
});
|
||||
|
||||
function picnicFilter(feature) {
|
||||
// obvod ID je z latte obvod_id
|
||||
|
|
@ -22,8 +18,7 @@ function picnicFilter(feature) {
|
|||
if (feature.properties.number == obvod_id) return true
|
||||
}
|
||||
|
||||
// center na vybraný obvod:
|
||||
map.flyToBounds(geojson.getBounds());
|
||||
// center na vybraný obvod je v getJSON výše
|
||||
|
||||
|
||||
// geolokace:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,631 @@
|
|||
(function () {
|
||||
var mapaEl = document.getElementById('map');
|
||||
var dataEl = document.getElementById('stari-data');
|
||||
if (!mapaEl || !dataEl || typeof L === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
var stari = JSON.parse(dataEl.textContent || '{}');
|
||||
var maxMesicu = parseInt(mapaEl.getAttribute('data-max'), 10) || 24;
|
||||
var maxPrideleni = parseInt(mapaEl.getAttribute('data-prideleni'), 10) || 12;
|
||||
var geojsonUrl = mapaEl.getAttribute('data-geojson');
|
||||
var obvodId = mapaEl.getAttribute('data-obvod');
|
||||
var rezim = mapaEl.getAttribute('data-filtr');
|
||||
if (rezim !== 'stari' && rezim !== 'nedokoncene') {
|
||||
rezim = 'obvody';
|
||||
}
|
||||
|
||||
var ZELENA = [27, 158, 62];
|
||||
var ZLUTA = [230, 184, 0];
|
||||
var CERVENA = [198, 40, 40];
|
||||
var SEDA = '#9e9e9e';
|
||||
var SEDA_HRANICE = '#616161';
|
||||
var MODRA = '#2652a8';
|
||||
var MODRA_PLOCHA = '#5c8fd6';
|
||||
|
||||
function mix(a, b, t) {
|
||||
return [
|
||||
Math.round(a[0] + (b[0] - a[0]) * t),
|
||||
Math.round(a[1] + (b[1] - a[1]) * t),
|
||||
Math.round(a[2] + (b[2] - a[2]) * t),
|
||||
];
|
||||
}
|
||||
|
||||
function rgb(kanaly) {
|
||||
return 'rgb(' + kanaly[0] + ',' + kanaly[1] + ',' + kanaly[2] + ')';
|
||||
}
|
||||
|
||||
function barvaStari(mesice, stupnice) {
|
||||
var max = stupnice || maxMesicu;
|
||||
var t = mesice == null ? 1 : Math.max(0, Math.min(mesice / max, 1));
|
||||
if (t < 0.5) {
|
||||
return rgb(mix(ZELENA, ZLUTA, t / 0.5));
|
||||
}
|
||||
return rgb(mix(ZLUTA, CERVENA, (t - 0.5) / 0.5));
|
||||
}
|
||||
|
||||
function infoObvodu(feature) {
|
||||
if (!feature.properties || feature.properties.number == null) {
|
||||
return null;
|
||||
}
|
||||
return stari[String(feature.properties.number)] || null;
|
||||
}
|
||||
|
||||
function style(feature) {
|
||||
var info = infoObvodu(feature);
|
||||
if (rezim === 'obvody') {
|
||||
return {
|
||||
fillColor: MODRA_PLOCHA,
|
||||
color: '#1d4f91',
|
||||
weight: 2,
|
||||
opacity: 1,
|
||||
fillOpacity: 0.4,
|
||||
};
|
||||
}
|
||||
if (rezim === 'nedokoncene') {
|
||||
if (info && info.probiha) {
|
||||
var barvaPrideleni = barvaStari(info.prideleni, maxPrideleni);
|
||||
return {
|
||||
fillColor: barvaPrideleni,
|
||||
color: barvaPrideleni,
|
||||
weight: 2,
|
||||
opacity: 1,
|
||||
fillOpacity: 0.55,
|
||||
};
|
||||
}
|
||||
return {
|
||||
fillColor: '#b0bec5',
|
||||
color: '#90a4ae',
|
||||
weight: 1,
|
||||
opacity: 0.4,
|
||||
dashArray: '3',
|
||||
fillOpacity: 0.04,
|
||||
};
|
||||
}
|
||||
var mesice = info ? info.mesice : null;
|
||||
var barva = barvaStari(mesice);
|
||||
if (info && info.probiha) {
|
||||
return {
|
||||
fillColor: SEDA,
|
||||
color: SEDA_HRANICE,
|
||||
weight: 2,
|
||||
opacity: 1,
|
||||
fillOpacity: 0.72,
|
||||
};
|
||||
}
|
||||
return {
|
||||
fillColor: barva,
|
||||
color: barva,
|
||||
weight: 2,
|
||||
opacity: 1,
|
||||
fillOpacity: 0.55,
|
||||
};
|
||||
}
|
||||
|
||||
var API_KEY = 'Uw8gm6JPZpnT3GfNnh8adARpQI-WBGy6QaY9g6VhD74';
|
||||
var mapyCZ = L.tileLayer('https://api.mapy.cz/v1/maptiles/basic/256/{z}/{x}/{y}?apikey=' + API_KEY, {
|
||||
minZoom: 0,
|
||||
maxZoom: 19,
|
||||
attribution: '<a href="https://api.mapy.cz/copyright" target="_blank">© Seznam.cz a.s. a další</a>',
|
||||
});
|
||||
var mapyCZletecka = L.tileLayer('https://api.mapy.cz/v1/maptiles/aerial/256/{z}/{x}/{y}?apikey=' + API_KEY, {
|
||||
minZoom: 0,
|
||||
maxZoom: 19,
|
||||
attribution: '<a href="https://api.mapy.cz/copyright" target="_blank">© Seznam.cz a.s. a další</a>',
|
||||
});
|
||||
var osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 19,
|
||||
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||
});
|
||||
|
||||
var map = L.map(mapaEl, {
|
||||
center: [50.853501146, 14.8425],
|
||||
zoom: 13,
|
||||
layers: [mapyCZ],
|
||||
});
|
||||
|
||||
L.control.layers({
|
||||
'Mapy.cz': mapyCZ,
|
||||
'Mapy.cz (letecká)': mapyCZletecka,
|
||||
'OpenStreetMap': osm,
|
||||
}).addTo(map);
|
||||
|
||||
var LogoControl = L.Control.extend({
|
||||
options: { position: 'bottomleft' },
|
||||
onAdd: function () {
|
||||
var container = L.DomUtil.create('div');
|
||||
var link = L.DomUtil.create('a', '', container);
|
||||
link.setAttribute('href', 'http://mapy.cz/');
|
||||
link.setAttribute('target', '_blank');
|
||||
link.innerHTML = '<img src="https://api.mapy.cz/img/api/logo.svg" alt="Mapy.cz" />';
|
||||
L.DomEvent.disableClickPropagation(link);
|
||||
return container;
|
||||
},
|
||||
});
|
||||
new LogoControl().addTo(map);
|
||||
|
||||
var legendaEl = null;
|
||||
var Legenda = L.Control.extend({
|
||||
options: { position: 'bottomright' },
|
||||
onAdd: function () {
|
||||
legendaEl = L.DomUtil.create('div', 'stari-legenda');
|
||||
legendaEl.innerHTML = obsahLegendy();
|
||||
L.DomEvent.disableClickPropagation(legendaEl);
|
||||
return legendaEl;
|
||||
},
|
||||
});
|
||||
new Legenda().addTo(map);
|
||||
|
||||
var filtrTlacitka = [];
|
||||
var Filtr = L.Control.extend({
|
||||
options: { position: 'topright' },
|
||||
onAdd: function () {
|
||||
var div = L.DomUtil.create('div', 'mapa-filtr');
|
||||
div.innerHTML =
|
||||
'<button type="button" data-rezim="obvody">Obvody</button>' +
|
||||
'<button type="button" data-rezim="stari">Nepropracované</button>' +
|
||||
'<button type="button" data-rezim="nedokoncene">Rozpracované</button>';
|
||||
L.DomEvent.disableClickPropagation(div);
|
||||
filtrTlacitka = div.querySelectorAll('button');
|
||||
filtrTlacitka.forEach(function (tlacitko) {
|
||||
tlacitko.addEventListener('click', function () {
|
||||
nastavRezim(tlacitko.getAttribute('data-rezim'));
|
||||
});
|
||||
});
|
||||
return div;
|
||||
},
|
||||
});
|
||||
new Filtr().addTo(map);
|
||||
|
||||
function textMesicu(mesice) {
|
||||
if (mesice == null) {
|
||||
return '—';
|
||||
}
|
||||
return mesice + ' měs.';
|
||||
}
|
||||
|
||||
function textBadge(mesice) {
|
||||
return mesice == null ? '—' : mesice + ' měs.';
|
||||
}
|
||||
|
||||
function barvaTextu(mesice, stupnice) {
|
||||
var max = stupnice || maxMesicu;
|
||||
var t = mesice == null ? 1 : Math.max(0, Math.min(mesice / max, 1));
|
||||
return t > 0.72 ? '#fff' : '#1a1a1a';
|
||||
}
|
||||
|
||||
function htmlKolecko(text, mesice, sede, stupnice) {
|
||||
var barva = sede ? SEDA : barvaStari(mesice, stupnice);
|
||||
var okraj = sede ? SEDA_HRANICE : barva;
|
||||
var textBarva = sede ? '#1a1a1a' : barvaTextu(mesice, stupnice);
|
||||
return '<div class="obvod-kolecko" style="background:' + barva + ';border-color:' + okraj + ';color:' + textBarva + '">' + text + '</div>';
|
||||
}
|
||||
|
||||
function htmlZnacky(cislo, info) {
|
||||
if (rezim === 'obvody') {
|
||||
return '<div class="obvod-znacka-telo"><div class="obvod-kolecko" style="background:' + MODRA + ';border-color:#1d4f91;color:#fff">' + cislo + '</div></div>';
|
||||
}
|
||||
if (rezim === 'nedokoncene') {
|
||||
var prideleni = info ? info.prideleni : null;
|
||||
var barvaPrideleni = barvaStari(prideleni, maxPrideleni);
|
||||
return '<div class="obvod-znacka-telo">' +
|
||||
htmlKolecko(cislo, prideleni, false, maxPrideleni) +
|
||||
'<div class="obvod-badge" style="background:' + barvaPrideleni + ';color:' + barvaTextu(prideleni, maxPrideleni) + '">' + textBadge(prideleni) + '</div>' +
|
||||
'</div>';
|
||||
}
|
||||
var mesice = info ? info.mesice : null;
|
||||
var html = '<div class="obvod-znacka-telo">' + htmlKolecko(cislo, mesice, !!(info && info.probiha));
|
||||
if (!info || !info.probiha) {
|
||||
var barva = barvaStari(mesice);
|
||||
html += '<div class="obvod-badge" style="background:' + barva + ';color:' + barvaTextu(mesice) + '">' + textBadge(mesice) + '</div>';
|
||||
}
|
||||
return html + '</div>';
|
||||
}
|
||||
|
||||
function htmlSkupiny(pocet, mesice, sede, stupnice) {
|
||||
return '<div class="obvod-skupina-telo" title="' + pocet + ' obvodů">' +
|
||||
htmlKolecko(pocet + 'x', mesice, sede, stupnice) +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
function vytvorSkupinu() {
|
||||
return L.markerClusterGroup({
|
||||
showCoverageOnHover: false,
|
||||
spiderfyOnMaxZoom: true,
|
||||
zoomToBoundsOnClick: true,
|
||||
maxClusterRadius: 46,
|
||||
disableClusteringAtZoom: 17,
|
||||
iconCreateFunction: function (cluster) {
|
||||
var pocet = cluster.getChildCount();
|
||||
if (rezim === 'obvody') {
|
||||
return L.divIcon({
|
||||
className: 'obvod-skupina',
|
||||
html: '<div class="obvod-skupina-telo"><div class="obvod-kolecko" style="background:#1b9e3e;border-color:#146b2b;color:#fff">' + pocet + 'x</div></div>',
|
||||
iconSize: [44, 44],
|
||||
iconAnchor: [22, 22],
|
||||
});
|
||||
}
|
||||
if (rezim === 'nedokoncene') {
|
||||
var nejPrideleni = 0;
|
||||
var bezPrideleni = false;
|
||||
cluster.getAllChildMarkers().forEach(function (marker) {
|
||||
if (marker.odPrideleni == null) {
|
||||
bezPrideleni = true;
|
||||
} else if (marker.odPrideleni > nejPrideleni) {
|
||||
nejPrideleni = marker.odPrideleni;
|
||||
}
|
||||
});
|
||||
return L.divIcon({
|
||||
className: 'obvod-skupina',
|
||||
html: htmlSkupiny(pocet, bezPrideleni ? null : nejPrideleni, false, maxPrideleni),
|
||||
iconSize: [44, 44],
|
||||
iconAnchor: [22, 22],
|
||||
});
|
||||
}
|
||||
var nej = 0;
|
||||
var bezData = false;
|
||||
var maHotovy = false;
|
||||
cluster.getAllChildMarkers().forEach(function (marker) {
|
||||
if (marker.obvodProbiha) {
|
||||
return;
|
||||
}
|
||||
maHotovy = true;
|
||||
if (marker.obvodMesice == null) {
|
||||
bezData = true;
|
||||
} else if (marker.obvodMesice > nej) {
|
||||
nej = marker.obvodMesice;
|
||||
}
|
||||
});
|
||||
var sede = !maHotovy;
|
||||
return L.divIcon({
|
||||
className: 'obvod-skupina',
|
||||
html: htmlSkupiny(pocet, sede || bezData ? null : nej, sede),
|
||||
iconSize: [44, 44],
|
||||
iconAnchor: [22, 22],
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
var znacky = null;
|
||||
|
||||
function popupHtml(cislo, info) {
|
||||
var html = '<strong>Obvod č. ' + cislo + '</strong><br>';
|
||||
if (!info || info.vraceno == null) {
|
||||
html += 'Zatím bez propracování';
|
||||
} else {
|
||||
html += 'Naposledy propracováno ' + info.vraceno + ' (' + textMesicu(info.mesice) + ')';
|
||||
}
|
||||
if (info && info.probiha) {
|
||||
html += '<br>Přiděleno ' + (info.prideleniDatum || '—') + ' (' + textMesicu(info.prideleni) + ')';
|
||||
}
|
||||
if (info && info.url) {
|
||||
html += '<br><a href="' + info.url + '">Otevřít obvod</a>';
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
function jeLatLng(hodnota) {
|
||||
return hodnota && typeof hodnota.lat === 'number' && typeof hodnota.lng === 'number';
|
||||
}
|
||||
|
||||
function prstence(latlngs) {
|
||||
if (!latlngs || !latlngs.length) {
|
||||
return [];
|
||||
}
|
||||
if (jeLatLng(latlngs[0])) {
|
||||
return [latlngs.map(function (bod) { return [bod.lng, bod.lat]; })];
|
||||
}
|
||||
if (jeLatLng(latlngs[0][0])) {
|
||||
return latlngs.map(function (prstenec) {
|
||||
return prstenec.map(function (bod) { return [bod.lng, bod.lat]; });
|
||||
});
|
||||
}
|
||||
var nej = [];
|
||||
var nejPlocha = -1;
|
||||
latlngs.forEach(function (polygon) {
|
||||
var rings = prstence(polygon);
|
||||
var plocha = rings.length ? Math.abs(plochaPrstence(rings[0])) : 0;
|
||||
if (plocha > nejPlocha) {
|
||||
nejPlocha = plocha;
|
||||
nej = rings;
|
||||
}
|
||||
});
|
||||
return nej;
|
||||
}
|
||||
|
||||
function plochaPrstence(prstenec) {
|
||||
var plocha = 0;
|
||||
for (var i = 0, j = prstenec.length - 1; i < prstenec.length; j = i++) {
|
||||
plocha += prstenec[i][0] * prstenec[j][1] - prstenec[j][0] * prstenec[i][1];
|
||||
}
|
||||
return plocha;
|
||||
}
|
||||
|
||||
function vzdalenostUsecky(px, py, a, b) {
|
||||
var x = a[0];
|
||||
var y = a[1];
|
||||
var dx = b[0] - x;
|
||||
var dy = b[1] - y;
|
||||
if (dx !== 0 || dy !== 0) {
|
||||
var t = ((px - x) * dx + (py - y) * dy) / (dx * dx + dy * dy);
|
||||
if (t > 1) {
|
||||
x = b[0];
|
||||
y = b[1];
|
||||
} else if (t > 0) {
|
||||
x += dx * t;
|
||||
y += dy * t;
|
||||
}
|
||||
}
|
||||
dx = px - x;
|
||||
dy = py - y;
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
function vzdalenostPolygonu(x, y, prstenceBodu) {
|
||||
var uvnitr = false;
|
||||
var nejmensi = Infinity;
|
||||
prstenceBodu.forEach(function (prstenec) {
|
||||
for (var i = 0, j = prstenec.length - 1; i < prstenec.length; j = i++) {
|
||||
var a = prstenec[i];
|
||||
var b = prstenec[j];
|
||||
if ((a[1] > y) !== (b[1] > y) && x < ((b[0] - a[0]) * (y - a[1])) / (b[1] - a[1]) + a[0]) {
|
||||
uvnitr = !uvnitr;
|
||||
}
|
||||
nejmensi = Math.min(nejmensi, vzdalenostUsecky(x, y, a, b));
|
||||
}
|
||||
});
|
||||
return (uvnitr ? 1 : -1) * Math.sqrt(nejmensi);
|
||||
}
|
||||
|
||||
function bunka(x, y, h, prstenceBodu) {
|
||||
var d = vzdalenostPolygonu(x, y, prstenceBodu);
|
||||
return { x: x, y: y, h: h, d: d, max: d + h * Math.SQRT2 };
|
||||
}
|
||||
|
||||
function vlozHaldu(halda, bunkaBodu) {
|
||||
halda.push(bunkaBodu);
|
||||
var i = halda.length - 1;
|
||||
while (i > 0) {
|
||||
var rodic = (i - 1) >> 1;
|
||||
if (halda[rodic].max >= halda[i].max) {
|
||||
break;
|
||||
}
|
||||
var tmp = halda[rodic];
|
||||
halda[rodic] = halda[i];
|
||||
halda[i] = tmp;
|
||||
i = rodic;
|
||||
}
|
||||
}
|
||||
|
||||
function vyberHaldu(halda) {
|
||||
var vrchol = halda[0];
|
||||
var posledni = halda.pop();
|
||||
if (halda.length && vrchol !== posledni) {
|
||||
halda[0] = posledni;
|
||||
var i = 0;
|
||||
while (true) {
|
||||
var levy = i * 2 + 1;
|
||||
var pravy = levy + 1;
|
||||
var nej = i;
|
||||
if (levy < halda.length && halda[levy].max > halda[nej].max) {
|
||||
nej = levy;
|
||||
}
|
||||
if (pravy < halda.length && halda[pravy].max > halda[nej].max) {
|
||||
nej = pravy;
|
||||
}
|
||||
if (nej === i) {
|
||||
break;
|
||||
}
|
||||
var tmp = halda[i];
|
||||
halda[i] = halda[nej];
|
||||
halda[nej] = tmp;
|
||||
i = nej;
|
||||
}
|
||||
}
|
||||
return vrchol;
|
||||
}
|
||||
|
||||
function teziste(prstenceBodu) {
|
||||
var prstenec = prstenceBodu[0];
|
||||
var plocha = 0;
|
||||
var x = 0;
|
||||
var y = 0;
|
||||
for (var i = 0, j = prstenec.length - 1; i < prstenec.length; j = i++) {
|
||||
var f = prstenec[i][0] * prstenec[j][1] - prstenec[j][0] * prstenec[i][1];
|
||||
x += (prstenec[i][0] + prstenec[j][0]) * f;
|
||||
y += (prstenec[i][1] + prstenec[j][1]) * f;
|
||||
plocha += f;
|
||||
}
|
||||
if (plocha === 0) {
|
||||
return [prstenec[0][0], prstenec[0][1]];
|
||||
}
|
||||
return [x / (3 * plocha), y / (3 * plocha)];
|
||||
}
|
||||
|
||||
function bodUvnitr(prstenceBodu) {
|
||||
var minX = Infinity;
|
||||
var minY = Infinity;
|
||||
var maxX = -Infinity;
|
||||
var maxY = -Infinity;
|
||||
prstenceBodu[0].forEach(function (bod) {
|
||||
minX = Math.min(minX, bod[0]);
|
||||
minY = Math.min(minY, bod[1]);
|
||||
maxX = Math.max(maxX, bod[0]);
|
||||
maxY = Math.max(maxY, bod[1]);
|
||||
});
|
||||
var sirka = maxX - minX;
|
||||
var vyska = maxY - minY;
|
||||
var krok = Math.min(sirka, vyska);
|
||||
if (krok === 0) {
|
||||
return [minX, minY];
|
||||
}
|
||||
var pul = krok / 2;
|
||||
var halda = [];
|
||||
for (var x = minX; x < maxX; x += krok) {
|
||||
for (var y = minY; y < maxY; y += krok) {
|
||||
vlozHaldu(halda, bunka(x + pul, y + pul, pul, prstenceBodu));
|
||||
}
|
||||
}
|
||||
var stred = teziste(prstenceBodu);
|
||||
var nej = bunka(stred[0], stred[1], 0, prstenceBodu);
|
||||
var obdelnik = bunka(minX + sirka / 2, minY + vyska / 2, 0, prstenceBodu);
|
||||
if (obdelnik.d > nej.d) {
|
||||
nej = obdelnik;
|
||||
}
|
||||
var presnost = Math.max(krok / 40, 0.00005);
|
||||
var pojistka = 0;
|
||||
while (halda.length && pojistka < 8000) {
|
||||
var aktualni = vyberHaldu(halda);
|
||||
pojistka++;
|
||||
if (aktualni.d > nej.d) {
|
||||
nej = aktualni;
|
||||
}
|
||||
if (aktualni.max - nej.d <= presnost) {
|
||||
continue;
|
||||
}
|
||||
var h = aktualni.h / 2;
|
||||
vlozHaldu(halda, bunka(aktualni.x - h, aktualni.y - h, h, prstenceBodu));
|
||||
vlozHaldu(halda, bunka(aktualni.x + h, aktualni.y - h, h, prstenceBodu));
|
||||
vlozHaldu(halda, bunka(aktualni.x - h, aktualni.y + h, h, prstenceBodu));
|
||||
vlozHaldu(halda, bunka(aktualni.x + h, aktualni.y + h, h, prstenceBodu));
|
||||
}
|
||||
return [nej.x, nej.y];
|
||||
}
|
||||
|
||||
function vnitrniBod(layer) {
|
||||
var rings = prstence(layer.getLatLngs());
|
||||
if (!rings.length) {
|
||||
return layer.getBounds().getCenter();
|
||||
}
|
||||
var bod = bodUvnitr(rings);
|
||||
if (vzdalenostPolygonu(bod[0], bod[1], rings) <= 0) {
|
||||
return layer.getBounds().getCenter();
|
||||
}
|
||||
return L.latLng(bod[1], bod[0]);
|
||||
}
|
||||
|
||||
function onEachFeature(feature, layer) {
|
||||
var cislo = feature.properties && feature.properties.number != null ? feature.properties.number : '';
|
||||
var info = infoObvodu(feature);
|
||||
var stred = vnitrniBod(layer);
|
||||
var nedokonceny = !!(info && info.probiha);
|
||||
if (rezim !== 'nedokoncene' || nedokonceny) {
|
||||
var marker = L.marker(stred, {
|
||||
icon: L.divIcon({
|
||||
className: 'obvod-znacka',
|
||||
html: htmlZnacky(cislo, info),
|
||||
iconSize: [58, 46],
|
||||
iconAnchor: [29, 14],
|
||||
}),
|
||||
interactive: false,
|
||||
keyboard: false,
|
||||
});
|
||||
marker.obvodMesice = info ? info.mesice : null;
|
||||
marker.odPrideleni = info ? info.prideleni : null;
|
||||
marker.obvodProbiha = nedokonceny;
|
||||
znacky.addLayer(marker);
|
||||
}
|
||||
|
||||
layer.bindPopup(popupHtml(cislo, info));
|
||||
layer.on({
|
||||
mouseover: function (e) {
|
||||
e.target.setStyle({ weight: 4, fillOpacity: 0.8 });
|
||||
e.target.bringToFront();
|
||||
},
|
||||
mouseout: function (e) {
|
||||
geojson.resetStyle(e.target);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
var geojson = null;
|
||||
var geoData = null;
|
||||
|
||||
function filtrObvodu(feature) {
|
||||
if (!obvodId) {
|
||||
return true;
|
||||
}
|
||||
return feature.properties && String(feature.properties.number) === String(obvodId);
|
||||
}
|
||||
|
||||
function obsahLegendy() {
|
||||
if (rezim === 'nedokoncene') {
|
||||
return '<div class="stari-legenda-nadpis">Měsíce od začátku přidělení</div>' +
|
||||
'<div class="stari-legenda-skala"></div>' +
|
||||
'<div class="stari-legenda-popisky"><span>0</span><span>' + Math.round(maxPrideleni / 2) + '</span><span>' + maxPrideleni + '+</span></div>';
|
||||
}
|
||||
return '<div class="stari-legenda-nadpis">Měsíce od posledního propracování</div>' +
|
||||
'<div class="stari-legenda-skala"></div>' +
|
||||
'<div class="stari-legenda-popisky"><span>0</span><span>' + Math.round(maxMesicu / 2) + '</span><span>' + maxMesicu + '+</span></div>' +
|
||||
'<div class="stari-legenda-seda"><span></span> právě se propracovává</div>';
|
||||
}
|
||||
|
||||
function oznacFiltr() {
|
||||
filtrTlacitka.forEach(function (tlacitko) {
|
||||
tlacitko.classList.toggle('is-active', tlacitko.getAttribute('data-rezim') === rezim);
|
||||
});
|
||||
if (legendaEl) {
|
||||
legendaEl.style.display = rezim === 'obvody' ? 'none' : '';
|
||||
if (rezim !== 'obvody') {
|
||||
legendaEl.innerHTML = obsahLegendy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function prekresli(poprve) {
|
||||
if (geojson) {
|
||||
map.removeLayer(geojson);
|
||||
}
|
||||
if (znacky) {
|
||||
map.removeLayer(znacky);
|
||||
}
|
||||
znacky = vytvorSkupinu();
|
||||
geojson = L.geoJSON(geoData, {
|
||||
style: style,
|
||||
filter: filtrObvodu,
|
||||
onEachFeature: onEachFeature,
|
||||
}).addTo(map);
|
||||
znacky.addTo(map);
|
||||
oznacFiltr();
|
||||
if (poprve && geojson.getLayers().length) {
|
||||
map.fitBounds(geojson.getBounds(), { padding: [12, 12], animate: false });
|
||||
}
|
||||
}
|
||||
|
||||
function nastavRezim(novy) {
|
||||
if (novy !== 'stari' && novy !== 'nedokoncene') {
|
||||
novy = 'obvody';
|
||||
}
|
||||
if (novy === rezim) {
|
||||
return;
|
||||
}
|
||||
rezim = novy;
|
||||
prekresli(false);
|
||||
if (window.history && window.history.replaceState) {
|
||||
var url = new URL(window.location.href);
|
||||
if (rezim === 'obvody') {
|
||||
url.searchParams.delete('filtr');
|
||||
} else {
|
||||
url.searchParams.set('filtr', rezim);
|
||||
}
|
||||
window.history.replaceState(null, '', url);
|
||||
}
|
||||
}
|
||||
|
||||
$.getJSON(geojsonUrl, function (data) {
|
||||
geoData = data;
|
||||
prekresli(true);
|
||||
});
|
||||
|
||||
L.control.locate({
|
||||
flyTo: true,
|
||||
strings: {
|
||||
title: 'Moje poloha',
|
||||
metersUnit: 'metrů',
|
||||
feetUnit: 'feet',
|
||||
popup: 'Nacházíš se v okolí {distance} {unit} od tohoto bodu.',
|
||||
outsideMapBoundsMsg: 'Zdá se, že jsi mimo oblast mapy.',
|
||||
},
|
||||
}).addTo(map);
|
||||
})();
|
||||
Loading…
Reference in New Issue