*/ private const RAZENI = [ 'cislo-vzestupne' => 'Číslo vzestupně', 'cislo-sestupne' => 'Číslo sestupně', 'rozpracovani-sestupne' => 'Rozpracování sestupně', 'rozpracovani-vzestupne' => 'Rozpracování vzestupně', 'nepropracovani-vzestupne' => 'Nepropracování vzestupně', 'nepropracovani-sestupne' => 'Nepropracování sestupně', ]; private string $skupina = ''; private string $razeni = 'cislo-vzestupne'; public function __construct( private ObvodFacade $obvod, private ZaznamFacade $zaznam, private LoginFacade $uzivatele, private SborFacade $sbor, private LogFacade $log, ) { } public function beforeRender(): void { parent::beforeRender(); } public function actionList(?string $skupina = null, ?string $razeni = 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', '/'); } if ($razeni !== null && isset(self::RAZENI[$razeni])) { $this->razeni = $razeni; $this->getHttpResponse()->setCookie(self::RAZENI_COOKIE, $razeni, '+1 year', '/'); } else { $ulozeny = $this->getHttpRequest()->getCookie(self::RAZENI_COOKIE); $this->razeni = is_string($ulozeny) && isset(self::RAZENI[$ulozeny]) ? $ulozeny : 'cislo-vzestupne'; } } public function renderList(): void { $stari = $this->zaznam->getStariObvodu(); $obvody = []; foreach ($this->obvod->getObvody($this->skupina) as $obvod) { $info = $stari[(int) $obvod->CISLO] ?? null; $rozpracovan = $info !== null && $info['probiha']; $obvody[] = (object) [ 'ID' => (int) $obvod->ID, 'CISLO' => (int) $obvod->CISLO, 'NAZEV' => (string) $obvod->NAZEV, 'stav' => $this->stavObvodu($info), 'rozpracovani' => $rozpracovan ? (int) ($info['prideleni'] ?? 0) : null, 'nepropracovani' => !$rozpracovan && $info !== null && $info['mesice'] !== null ? (int) $info['mesice'] : null, ]; } $this->seradObvody($obvody); $this->template->skupina = $this->skupina; $this->template->skupiny = $this->obvod->getSkupiny(); $this->template->razeni = $this->razeni; $this->template->razeniMoznosti = self::RAZENI; $this->template->obvody = $obvody; } public function renderView(int $id = null): void { $sousede = $id === null ? ['predchozi' => null, 'dalsi' => null] : $this->obvod->getSousede($id); $this->template->obvod = $this->obvod->getObvod($id); $this->template->predchozi = $sousede['predchozi']; $this->template->dalsi = $sousede['dalsi']; $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); } public function actionHranice(int $id): void { $this->overSpravce(); if ($this->obvod->getObvod($id) === null) { $this->error('Obvod nenalezen.', Nette\Http\IResponse::S404_NotFound); } } public function renderHranice(int $id): void { $this->template->obvod = $this->obvod->getObvod($id); $this->template->sborJson = $this->sbor->getGeoJson() ?? 'null'; } protected function createComponentHraniceForm(): Form { $form = new Form; $form->addHidden('geo'); $form->addSubmit('send', 'Uložit'); $form->addProtection('Platnost formuláře vypršela, odešlete ho znovu.'); $form->onSuccess[] = $this->hraniceFormSucceeded(...); return $form; } private function hraniceFormSucceeded(Form $form, ArrayHash $values): void { $this->overSpravce(); $id = $this->obvodId(); try { $this->obvod->ulozHranici($id, (string) $values->geo); } catch (\InvalidArgumentException $e) { $form->addError($e->getMessage()); return; } $this->log->hranice($this->cisloObvodu($id)); $this->flashMessage('Hranice obvodu byly uloženy.'); $this->redirect('view', ['id' => $id]); } private function overSpravce(): void { if ($this->uzivatele->jeAdmin((int) $this->getUser()->getId())) { return; } $this->flashMessage('Hranice obvodu může upravovat jen správce.', 'error'); $this->redirect('view', ['id' => $this->getParameter('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; } $datum = $this->den($values->datum); $this->zaznam->pridej($id, $jmeno, $datum); $this->log->prideleno($this->cisloObvodu($id), $jmeno, $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->log->vraceno($this->cisloObvodu($obvodId), (string) $zaznam->JMENO, $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->log->upraven($this->cisloObvodu($obvodId), $jmeno, $vydano, $vraceno); $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 { $obvodId = $this->obvodId(); $zaznam = $this->zaznam->getZaznam((int) $id); if ($zaznam !== null && $this->zaznam->smaz((int) $id, $obvodId)) { $vraceno = $zaznam->VRACENO === null ? null : $this->den($zaznam->VRACENO); $this->log->smazan( $this->cisloObvodu($obvodId), (string) $zaznam->JMENO, $this->den($zaznam->VYDANO), $vraceno, ); $this->flashMessage('Záznam byl smazán.'); } else { $this->flashMessage('Tento záznam už nelze smazat.'); } $this->redirect('this'); }; return $form; }); } /** * @param list $obvody */ private function seradObvody(array &$obvody): void { $razeni = $this->razeni; $sestupne = str_ends_with($razeni, 'sestupne'); $klic = match (true) { str_starts_with($razeni, 'rozpracovani') => 'rozpracovani', str_starts_with($razeni, 'nepropracovani') => 'nepropracovani', default => null, }; usort($obvody, function (object $a, object $b) use ($sestupne, $klic): int { if ($klic === null) { $poradi = $a->CISLO <=> $b->CISLO; return $sestupne ? -$poradi : $poradi; } $prvni = $a->{$klic}; $druhy = $b->{$klic}; if ($prvni === null && $druhy === null) { return $a->CISLO <=> $b->CISLO; } if ($prvni === null) { return 1; } if ($druhy === null) { return -1; } $poradi = $prvni <=> $druhy; if ($poradi === 0) { return $a->CISLO <=> $b->CISLO; } return $sestupne ? -$poradi : $poradi; }); } /** * @param array{mesice: int|null, probiha: bool, prideleni: int|null}|null $info * @return object{popisek: string, barva: string, text: string} */ private function stavObvodu(?array $info): object { if ($info !== null && $info['probiha']) { $mesice = $info['prideleni']; return (object) [ 'popisek' => 'Rozpracován ' . $this->textMesicu($mesice), 'barva' => $this->cssBarva($this->barvaStari($mesice, MapaPresenter::STUPNICE_PRIDELENI)), 'text' => $this->cssBarva($this->barvaTextu($mesice, MapaPresenter::STUPNICE_PRIDELENI)), ]; } if ($info !== null && $info['mesice'] !== null) { return (object) [ 'popisek' => 'Nepropracován ' . $this->textMesicu($info['mesice']), 'barva' => $this->cssBarva($this->barvaStari($info['mesice'], MapaPresenter::STUPNICE_MESICU)), 'text' => $this->cssBarva($this->barvaTextu($info['mesice'], MapaPresenter::STUPNICE_MESICU)), ]; } return (object) [ 'popisek' => 'Bez záznamu', 'barva' => '#b0bec5', 'text' => '#1a1a1a', ]; } private function cssBarva(string $barva): string { if (preg_match('/^rgb\((?:25[0-5]|2[0-4]\d|1?\d?\d),(?:25[0-5]|2[0-4]\d|1?\d?\d),(?:25[0-5]|2[0-4]\d|1?\d?\d)\)$/', $barva)) { return $barva; } if (preg_match('/^#[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?$/', $barva)) { return $barva; } return '#b0bec5'; } private function textMesicu(?int $mesice): string { return $mesice === null ? '—' : $mesice . ' měs.'; } private function barvaStari(?int $mesice, int $max): string { $t = $mesice === null || $max <= 0 ? 1 : max(0, min($mesice / $max, 1)); $zelena = [27, 158, 62]; $zluta = [230, 184, 0]; $cervena = [198, 40, 40]; $kanaly = $t < 0.5 ? $this->mixBarvy($zelena, $zluta, $t / 0.5) : $this->mixBarvy($zluta, $cervena, ($t - 0.5) / 0.5); return 'rgb(' . implode(',', $kanaly) . ')'; } private function barvaTextu(?int $mesice, int $max): string { $t = $mesice === null || $max <= 0 ? 1 : max(0, min($mesice / $max, 1)); return $t > 0.72 ? '#fff' : '#1a1a1a'; } /** * @param array{0: int, 1: int, 2: int} $a * @param array{0: int, 1: int, 2: int} $b * @return array{0: int, 1: int, 2: int} */ private function mixBarvy(array $a, array $b, float $t): array { return [ (int) round($a[0] + ($b[0] - $a[0]) * $t), (int) round($a[1] + ($b[1] - $a[1]) * $t), (int) round($a[2] + ($b[2] - $a[2]) * $t), ]; } private function cisloObvodu(int $id): int { $obvod = $this->obvod->getObvod($id); if ($obvod === null) { $this->error('Obvod nenalezen.', Nette\Http\IResponse::S404_NotFound); } return (int) $obvod->CISLO; } 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); } }