Skip to content

Instantly share code, notes, and snippets.

Created August 29, 2016 13:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/f1b44c6c9f8dea45a2ed145bd81a993f to your computer and use it in GitHub Desktop.
Save anonymous/f1b44c6c9f8dea45a2ed145bd81a993f to your computer and use it in GitHub Desktop.
Řešení pro chybějící sportoviště
{block scripts}
{include parent}
<script>
(function ($) {
$(function () {
var reservationsTable = $('#table-of-reservations');
var refreshUrl = reservationsTable.data('url');
var refreshRate = reservationsTable.data('refreshRate');
var automaticallyRefreshTable = reservationsTable.data('automaticallyRefreshTable');
if (automaticallyRefreshTable == true) {
setInterval(function () {
$.nette.ajax({
method: 'GET',
url: refreshUrl
});
}, refreshRate);
}
});
})(window.jQuery);
</script>
{/block}
{block content}
<header>
<div class="container-fluid">
<div class="row">
<div class="col-xs-6 title">
<h1>
<i class="fa fa-clock-o"></i>
<span>Rezervace</span>
</h1>
</div>
<div class="col-xs-6 text-right tools">
</div>
</div>
</div>
</header>
{if $calendar != true}
{var $previousDay = $displayedDate->modify('-1 day')}
{var $nextDay = $displayedDate->modify('+1 day')}
<div class="container-fluid">
<div class="row reservations-navigation">
<div class="col-xs-3">
<a href="{link previousDay!}" class="btn btn-default">
<i class="fa fa-chevron-left"></i>
<small>{$previousDay|completeDate}</small>
</a>
</div>
<div class="col-xs-6 text-center today">
<strong>{$displayedDate|completeDate}</strong>
</div>
<div class="col-xs-3 text-right">
<a href="{link nextDay!}" class="btn btn-default">
<small>{$nextDay|completeDate}</small>
<i class="fa fa-chevron-right"></i>
</a>
</div>
</div>
{control sportsFieldsOverview}
</div>
<div class="modal fade" tabindex="-1" role="dialog" id="reservation_form">
<div class="modal-dialog modal-w-66">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">
{snippet modalTitle}
{if $chosenSportsField !== null}
{$chosenSportsField->getName()}
{else}
Sportoviště nenalezeno
{/if}
{/snippet}
</h4>
</div>
<div class="modal-body">
{snippet modalBody}
{if $chosenSportsField !== null}
{control sportsFieldReservation}
{else}
<p>Vybrané sportoviště nebylo nalezeno</p>
{/if}
{/snippet}
</div>
<div class="modal-footer"></div>
</div>
</div>
</div>
{else}
<a href="{plink this, calendar => null}">zpět</a>
{control calendar}
{/if}
{/block}
<?php
namespace Reservations\Presenters;
use Reservations\Components\ISportsFieldReservationControlFactory;
use Reservations\Components\ISportsFieldsOverviewControlFactory;
use blitzik\Calendar\Factories\HorizontalCalendarCellFactory;
use App\OperationModule\Presenters\OperationBasePresenter;
use Reservations\Components\SportsFieldReservationControl;
use Reservations\Components\SportsFieldsOverviewControl;
use Reservations\Components\ReservationCalendarControl;
use Reservations\Components\SportsFieldRowControl;
use blitzik\Calendar\Generator\CalendarGenerator;
use Reservations\Facades\ReservationFacade;
use Reservations\Facades\SportsFieldFacade;
use Reservations\Queries\SportsFieldQuery;
use blitzik\Calendar\Calendar;
use Reservations\Reservation;
use Reservations\SportsField;
class ReservationPresenter extends OperationBasePresenter
{
/** @persistent */
public $year;
/** @persistent */
public $month;
/** @persistent */
public $day;
/** @persistent */
public $chosenSportsFieldId;
/** @persistent */
public $calendar;
/** @persistent */
public $reservationId;
/** @persistent */
public $clickedHour;
/**
* @var ISportsFieldReservationControlFactory
* @inject
*/
public $sportsFieldReservationControlFactory;
/**
* @var ISportsFieldsOverviewControlFactory
* @inject
*/
public $sportsFieldOverviewControlFactory;
/**
* @var ReservationFacade
* @inject
*/
public $reservationFacade;
/**
* @var SportsFieldFacade
* @inject
*/
public $sportsFieldFacade;
/** @var Reservation */
private $reservation;
/** @var SportsField[] */
private $sportsFields;
/** @var SportsField */
private $sportsField;
/** @var \DateTime */
private $date;
private $automaticallyRefreshReservationTable = false;
private $reservationTableRefreshRate = 35000; // in ms
protected function startup()
{
parent::startup();
$this->reservationFacade->removePendingReservations();
}
public function actionDefault()
{
$this['pageTitle']->setPageTitle('Rezervace sportovišť');
}
public function renderDefault()
{
if ($this->sportsFields === null) {
$this->sportsFields = $this->findSportsFields();
}
if ($this->sportsField === null) {
if ($this->chosenSportsFieldId !== null) {
if (isset($this->sportsFields[$this->chosenSportsFieldId])) {
$this->sportsField = $this->sportsFields[$this->chosenSportsFieldId];
} else {
$this->sportsField = $this->sportsFieldFacade
->getSportsFieldById($this->chosenSportsFieldId);
}
}
}
// -----
$this->template->chosenSportsField = $this->sportsField;
// -----
$this->template->displayedDate = $this->date;
$this->template->calendar = $this->calendar;
}
protected function createComponentSportsFieldsOverview()
{
if ($this->sportsFields === null) {
$this->sportsFields = [];
}
$comp = $this->sportsFieldOverviewControlFactory
->create(
$this->date,
$this->sportsFields
);
$comp->setTableRefreshRate($this->reservationTableRefreshRate);
if ($this->automaticallyRefreshReservationTable === false) {
$comp->doNotRefreshTable();
}
$comp->onFreeTimeClick[] = [$this, 'onSportsFieldFreeTimeClick'];
$comp->onReservedTimeClick[] = [$this, 'onSportsFieldReservedTimeClick'];
return $comp;
}
public function onSportsFieldFreeTimeClick(
SportsFieldsOverviewControl $sportsFieldsOverviewControl,
SportsFieldRowControl $sportsFieldRowControl,
SportsField $sportsField = null,
\DateTimeImmutable $clickedHour
) {
if ($sportsField !== null) {
$this->chosenSportsFieldId = $sportsField->getId();
$this->sportsField = $sportsField;
$this->sportsFields[$sportsField->getId()] = $sportsField;
} else {
$this->chosenSportsFieldId = null; // we won't load sportsField in render method
$this->sportsFields = []; // do not load sportsFields in render method
}
$this->clickedHour = $clickedHour->format('H:i');
$this->redrawControl('modalTitle');
$this->redrawControl('modalBody');
}
public function onSportsFieldReservedTimeClick(
SportsFieldsOverviewControl $sportsFieldsOverviewControl,
SportsFieldRowControl $sportsFieldRowControl,
SportsField $sportsField,
Reservation $reservation
) {
// todo
$this->redrawControl('modalTitle');
$this->redrawControl('modalBody');
}
protected function createComponentSportsFieldReservation()
{
if ($this->sportsField === null) {
$this->sportsField = $this->sportsFieldFacade->getSportsFieldById($this->chosenSportsFieldId);
$this->sportsFields = [];
if ($this->sportsField !== null) {
$this->sportsFields[$this->chosenSportsFieldId] = $this->sportsField;
}
}
$comp = $this->sportsFieldReservationControlFactory
->create($this->date, $this->sportsField);
if ($this->clickedHour !== null) {
$comp->setClickedHour($this->clickedHour);
}
if ($this->reservation !== null) {
$comp->setReservation($this->reservation);
}
$comp->onParticipantResetting[] = function () {
$this->reservationId = null;
};
$comp->onMissingSportsField[] = [$this, 'onMissingSportsField'];
$comp->onSuccessfulReservationCreation[] = [$this, 'onSuccessfulReservationCreation'];
return $comp;
}
public function onSuccessfulReservationCreation(SportsFieldReservationControl $control, SportsField $sportsField)
{
$this->sportsField = null;
$this->sportsFields = [];
$this['sportsFieldsOverview']['sportsFieldRow'][$sportsField->getId()]->redrawControl();
}
public function onMissingSportsField(SportsFieldReservationControl $control)
{
$this->chosenSportsFieldId = null;
$this->sportsField = null;
$this->sportsFields = $this->findSportsFields();
$this['sportsFieldsOverview']->setSportsFields($this->sportsFields);
$this['sportsFieldsOverview']->redrawControl();
}
public function handleBackToOverview()
{
$this->reservationId = null;
$this->redirect(':Reservations:Reservation:default');
}
/**
* @return SportsField[]
*/
private function findSportsFields()
{
return $this->sportsFieldFacade
->findSportsFields(
(new SportsFieldQuery())
->notFrozen()
->indexById()
)->toArray();
}
// ---- calendar
protected function createComponentCalendar()
{
$calendar = new ReservationCalendarControl(Calendar::LANG_CS);
$calendar->setCalendarGenerator(
new CalendarGenerator(new HorizontalCalendarCellFactory(Calendar::MONDAY))
);
$calendar->setMonth($this->month);
$calendar->setYear($this->year);
$calendar->truncateDaysLabelsTo(2);
$calendar->onDaySelection[] = [$this, 'onDaySelection'];
return $calendar;
}
public function onDaySelection($year, $month, $day)
{
$this->year = $year;
$this->month = $month;
$this->day = $day;
$this->calendar = null;
$this->redirect('this');
}
public function handleShowCalendar()
{
$this->calendar = true;
$this->redirect('this');
}
public function handlePreviousDay()
{
$date = $this->date->modify('-1 day');
$this->refreshDateProperties($date);
}
public function handleNextDay()
{
$date = $this->date->modify('+1 day');
$this->refreshDateProperties($date);
}
private function refreshDateProperties(\DateTimeInterface $date)
{
$this->year = $date->format('Y');
$this->month = $date->format('n');
$this->day = $date->format('j');
$this->redirect('this');
}
// -----
public function loadState(array $params)
{
parent::loadState($params);
$dateTime = $this->createDateTime($this->year, $this->month, $this->day);
if ($dateTime === false) {
$dateTime = \DateTimeImmutable::createFromFormat('!Y-n-j', date('Y-n-j'));
}
$this->year = $dateTime->format('Y');
$this->month = $dateTime->format('n');
$this->day = $dateTime->format('j');
$this->date = $dateTime;
}
/**
* @param int $year
* @param int $month
* @param int $day
* @return \DateTimeImmutable
*/
private function createDateTime($year, $month, $day)
{
return \DateTimeImmutable::createFromFormat('!Y-n-j', date(sprintf('%s-%s-%s', $year, $month, $day)));
}
}
{snippet flashMessages}
{control flashMessages}
{/snippet}
{snippet selection}
{if $sportsField === null}
<p>Vybrané sportoviště nebylo nalezeno</p>
{else}
<h3>{$day|completeDate}</h3>
{if $reservation !== null}
<h4>Změna počtu účastníků</h4>
{/if}
{if $billSelection === true}
{include #typeSelection}
{else}
{if $chosenBillId !== null}
<a href="{link intoExistingBill!}" class="ajax">zpět na výběr účtenky</a>
{if $bill === null or $bill->isClosed()}
<p>Zvolená účtenka byla uzavřena nebo neexistuje</p>
{else}
{if $repeatableReservation === false and $singleReservation === false}
{include #reservationTypeSelection}
{elseif $singleReservation === true and $repeatableReservation === false}
{control reservationForm}
{elseif $repeatableReservation == true and $singleReservation === false}
{control repeatableReservationForm}
{else}
{include #reservationTypeSelection}
{/if}
{/if}
{elseif $intoNewBill === false}
{include #back}
<h4>Výběr účtenky</h4>
{if empty($bills)}
<p>Nemáte žádné otevřené účtenky. <a href="{link intoNewBill!}" class="ajax">Vytvořit rezervaci na novou účtenku</a></p>
{else}
<div class="row reservation-bill-list">
{foreach $bills as $bill}
<div class="col-xs-3">
<a href="{link showReservationFormForBill!, billId => $bill->getId()}" class="ajax bill-item">{$bill->getName()}</a>
</div>
{/foreach}
</div>
{/if}
{elseif $intoNewBill === true}
{include #back}
{control reservationIntoNewBill}
{else}
{include #typeSelection}
{/if}
{/if}
{/if}
{define back}
<a href="{link back!}" class="ajax">zpět</a>
{/define}
{define typeSelection}
<div class="row reservation-type-selection">
<div class="col-xs-6">
<a href="{link intoNewBill!}" class="ajax btn btn-default">{if $reservation === null}Rezervace na novou účtenku{else}Přidat účastníky na novou účtenku{/if}</a>
</div>
<div class="col-xs-6">
<a href="{link intoExistingBill!}" class="ajax btn btn-default">{if $reservation === null}Rezervace na otevřenou účtenku{else}Upravit počet účastníků na otevřené účtence{/if}</a>
</div>
</div>
{/define}
{define reservationTypeSelection}
<div class="reservation-type-selection text-center">
<a href="{link showReservationForm!}" class="ajax btn btn-default">Rezervace</a>
<a href="{link ShowRepeatableReservationForm!}" class="ajax btn btn-default">Opakovaná rezervace</a>
</div>
{/define}
{/snippet}
<?php
namespace Reservations\Components;
use CreativeProjects\Components\FlashMessages\FlashMessage;
use CreativeProjects\Components\BaseControl;
use Reservations\Facades\ReservationFacade;
use Reservations\Queries\ReservationQuery;
use Reservations\Traits\THoursCalculation;
use Reservations\SportsField;
use Reservations\Reservation;
use Sales\Facades\BillFacade;
use Sales\Queries\BillQuery;
use Sales\ReservationItem;
use Sales\Bill;
class SportsFieldReservationControl extends BaseControl
{
use THoursCalculation;
public $onSuccessfulReservationCreation;
public $onParticipantResetting;
public $onMissingSportsField;
/** @persistent */
public $billSelection = true;
/** @persistent */
public $intoNewBill = false;
/** @persistent */
public $intoExistingBill = false;
/** @persistent */
public $chosenBillId;
/** @persistent */
public $repeatableReservation = false;
/** @persistent */
public $singleReservation = false;
/** @var IRepeatableReservationFormControlFactory */
private $repeatableReservationFormControlFactory;
/** @var IReservationIntoNewBillControlFactory */
private $reservationIntoNewBillControlFactory;
/** @var IReservationFormControlFactory */
private $reservationFormControlFactory;
/** @var ISportsFieldControlFactory */
private $sportsFieldControlFactory;
/** @var ReservationFacade */
private $reservationFacade;
/** @var Reservation */
private $reservation;
/** @var SportsField|null */
private $sportsField;
/** @var BillFacade */
private $billFacade;
/** @var Bill[] */
private $bills;
/** @var Bill */
private $bill;
/** @var \DateTimeImmutable */
private $day;
/** @var \DateTimeImmutable */
private $clickedHour;
public function __construct(
\DateTimeImmutable $day,
SportsField $sportsField = null,
BillFacade $billFacade,
ReservationFacade $reservationFacade,
ISportsFieldControlFactory $sportsFieldControlFactory,
IReservationFormControlFactory $reservationFormControlFactory,
IReservationIntoNewBillControlFactory $reservationIntoNewBillControlFactory,
IRepeatableReservationFormControlFactory $repeatableReservationFormControlFactory
) {
$this->day = $day;
$this->billFacade = $billFacade;
$this->sportsField = $sportsField;
$this->reservationFacade = $reservationFacade;
$this->sportsFieldControlFactory = $sportsFieldControlFactory;
$this->reservationFormControlFactory = $reservationFormControlFactory;
$this->reservationIntoNewBillControlFactory = $reservationIntoNewBillControlFactory;
$this->repeatableReservationFormControlFactory = $repeatableReservationFormControlFactory;
}
/**
* @param Reservation $reservation
*/
public function setReservation(Reservation $reservation)
{
$this->reservation = $reservation;
}
/**
* @param string $clickedHour e.g "12:00"
*/
public function setClickedHour($clickedHour)
{
$this->clickedHour = \DateTimeImmutable::createFromFormat('Y-m-d H:i', sprintf('%s %s', $this->day->format('Y-m-d'), $clickedHour));
}
public function render()
{
$template = $this->getTemplate();
$template->setFile(__DIR__ . '/sportsFieldReservation.latte');
$template->sportsField = $this->sportsField;
$template->reservation = $this->reservation;
$template->day = $this->day;
$template->currentDate = \DateTimeImmutable::createFromFormat('!Y-n-j', date('Y-n-j'));
$template->chosenBillId = $this->chosenBillId;
$template->billSelection = (bool)$this->billSelection;
$template->intoNewBill = (bool)$this->intoNewBill;
$template->singleReservation = (bool)$this->singleReservation;
$template->repeatableReservation = (bool)$this->repeatableReservation;
if ($this->sportsField !== null and $this->intoExistingBill == true and $this->bills === null) {
$this->bills = $this->findOpenedBills();
}
$template->bills = $this->bills;
if ($this->sportsField !== null and $this->bill === null) {
if ($this->chosenBillId !== null) {
$this->bill = $this->billFacade->getBillById($this->chosenBillId);
}
}
$template->bill = $this->bill;
$template->render();
}
public function handleShowReservationForm()
{
$this->singleReservation = true;
$this->repeatableReservation = false;
if ($this->sportsField === null) {
$this->resetState();
$this->onMissingSportsField($this);
}
$this->refresh('this', ['selection']);
}
public function handleShowRepeatableReservationForm()
{
$this->singleReservation = false;
$this->repeatableReservation = true;
if ($this->sportsField === null) {
$this->resetState();
$this->onMissingSportsField($this);
}
$this->refresh('this', ['selection']);
}
protected function createComponentReservationForm()
{
if ($this->bill === null) {
$this->bill = $this->billFacade->getBillById($this->chosenBillId);
}
$comp = $this->reservationFormControlFactory
->create(
$this->day,
$this->bill,
$this->sportsField
);
if ($this->reservation !== null) {
$comp->setReservation($this->reservation);
}
if ($this->clickedHour !== null) {
$defaultTimes = $this->findFreeReservationDefaultTimes($this->clickedHour);
$comp->setDefaultTimes($defaultTimes['start'], $defaultTimes['end']);
}
$comp->onSuccessfulReservation[] = [$this, 'onSuccessfulReservation'];
$comp->onMissingSportsField[] = function (ReservationFormControl $control) {
$this->resetState();
$this->onMissingSportsField($this);
};
return $comp;
}
public function onSuccessfulReservation(ReservationFormControl $control, ReservationItem $reservationItem)
{
$this->resetState();
$this->flashMessage('Rezervace byla úspěšně uložena', FlashMessage::SUCCESS);
$this->onSuccessfulReservationCreation($this, $this->sportsField);
$this->refresh('this');
}
/**
* @param \DateTimeImmutable $clickedHour
* @return \DateTimeImmutable
*/
private function findFreeReservationDefaultTimes(\DateTimeImmutable $clickedHour)
{
$clickedHourStartTime = \DateTimeImmutable::createFromFormat('Y-m-d H:i', sprintf('%s %s', $this->day->format('Y-m-d'), $clickedHour->format('H:i')));
$clickedHourEndTime = $clickedHourStartTime->modify('+ 1 hour');
$reservations = $this->reservationFacade
->findReservationsInPeriod($clickedHourStartTime, $clickedHourEndTime, $this->sportsField);
if (empty($reservations)){ // žádné rezervace, celá vybraná hodina je dostupná
return ['start' => $clickedHourStartTime, 'end' => $clickedHourEndTime, 'state' => 'ok'];
}
$reservationsCount = count($reservations);
// V jeden čas může být aktivní pouze jedna rezervace.
// Pokud tedy z databáze dostaneme více než jednu rezervaci, není možné,
// aby některá z rezervací pokrývala celý vybraný čas z tabulky rezervací
// Může nastat situace, kdy prodejce vidí volnou hodinu a bude chtít
// vytvořit rezervaci, ale průběhu vybírání účtenky apod. než se dostane k
// formuláři, do kterého se generují defaultní hodnoty času podle existujících
// rezervací, z online databáze se přes synchronizaci vytvoří rezervace a nebude
// tak možné vytvořit rezervaci na lokalhostu. Zde dochází k problému nastavení
// konečného defaultního času, který je popsán v komentářích níže.
// Rezervace jsou seřazeny již z databáze
/** @var Reservation $firstReservation */
$firstReservation = array_shift($reservations);
// začátek a konec největšího volného času mezi rezervacemi
$highestFreeTimeStart = null;
$highestFreeTimeEnd = null;
if ($firstReservation->getStart() < $clickedHourStartTime) {
// todo => mohou nastat 2 situace, první je OK a druhá potřebuje vyřešit
// 1. rezervace končí dřív než končí vybraná hodina ($reservation->getEnd() < $clickedHourEndTime)
// -> OK, mezi koncem rezervace a koncem vybrané hodiny je "nějaký" volný čas
if ($firstReservation->getEnd() < $clickedHourEndTime) { // ve vybrané hodině se může nacházet 1 nebo více rezervací
$highestFreeTimeStart = $firstReservation->getEnd();
if ($reservationsCount === 1) {
// pokud se ve vybrané hodině nachází pouze jedna rezervace, můžeme určit i konečný defaultní čas
$highestFreeTimeEnd = $clickedHourEndTime;
}
} else { // ve vybrané hodině nachází pouze jedna rezervace, protože zabírá celou vybranou hodinu
// 2. konec rezervace přesahuje konec vybrané hodiny ($reservation->getEnd() >= $clickedHourEndTime)
// -> problém, ve vybrané hodině není žádný volný čas.
// *** JAKÝ NASTAVIT DEFAULTNÍ KONEČNÝ ČAS V TOMTO PŘÍPADĚ? ***
// todo jaké nastavit defaultní hodnoty?
// rezervace začíná před počátkem vybrané hodiny a končí až za koncem vybrané hodiny,
// takže nelze stanovit defaultní počáteční čas
$highestFreeTimeStart = $clickedHourStartTime;
// Druhý případ vyřešen tak, že se prostě nastaví konečný čas vybrané hodiny
// a po zpracování rezervace se zobrazí hláška, že termín není volný.
// Po uzavření modálního okna prodejce uvidí, že již není termín volný
$highestFreeTimeEnd = $clickedHourEndTime;
}
} else { // >= rezervace začíná ve stejný čas jako vybraná hodina nebo později.
// nastavíme jako výchozí počáteční čas začátek vybrané hodiny z
// tabulky rezervací, protože první rezervace zasahující do vybraného času
// může začínat ve stejný čas jako čas vybraný nebo později. Proto
// bude výchozí čas začínat počátkem vybraného času z tabulky rezervací.
//$highestFreeTimeStart = $clickedHourStartTime;
//$highestFreeTimeEnd = $firstReservation->getStart(); // může být shodný se začátkem vybrané hodiny
// Pokud rezervace nezačíná ve stejný čas jako vybraná hodina, existuje nějaký volný čas
if ($firstReservation->getStart() != $clickedHourStartTime) {
$highestFreeTimeStart = $clickedHourStartTime;
$highestFreeTimeEnd = $firstReservation->getStart();
} else { // == rezervace začíná ve stejný čas jako vybraná hodina
if ($firstReservation->getEnd() < $clickedHourEndTime) { // ve vybrané hodině se může nacházet více rezervací
$highestFreeTimeStart = $firstReservation->getEnd();
if ($reservationsCount === 1) {
// pokud se ve vybrané hodině nachází pouze jedna rezervace, můžeme určit i konečný defaultní čas
$highestFreeTimeEnd = $clickedHourEndTime;
}
} else { // >= ve vybrané hodině se nachází pouze jedna rezervace a ta zabírá celkový čas
// todo jaké nastavit defaultní hodnoty?
$highestFreeTimeStart = $clickedHourStartTime;
$highestFreeTimeEnd = $clickedHourEndTime;
}
}
}
// konec rezervace procházené naposled
$lastReservationEndTime = $firstReservation->getEnd();
$highestFreeTimePeriod = null;
/** @var Reservation $reservation */
foreach ($reservations as $reservation) {
if ($highestFreeTimeEnd === null) {
$highestFreeTimeEnd = $reservation->getStart();
}
$highestFreeTimePeriod = $highestFreeTimeEnd->diff($highestFreeTimeStart);
$currentFreeTimePeriod = $reservation->getStart()->diff($lastReservationEndTime);
$highestFreeTimePeriodInMinutes = ($highestFreeTimePeriod->h * 60) + $highestFreeTimePeriod->i;
$currentFreeTimePeriodInMinutes = ($currentFreeTimePeriod->h * 60) + $currentFreeTimePeriod->i;
if ($currentFreeTimePeriodInMinutes > $highestFreeTimePeriodInMinutes) {
$highestFreeTimeStart = $lastReservationEndTime;
$highestFreeTimeEnd = $reservation->getStart();
}
$lastReservationEndTime = $reservation->getEnd();
}
return ['start' => $highestFreeTimeStart, 'end' => $highestFreeTimeEnd, 'state' => 'ok'];
}
protected function createComponentRepeatableReservationForm()
{
if ($this->bill === null) {
$this->bill = $this->billFacade->getBillById($this->chosenBillId);
}
$comp = $this->repeatableReservationFormControlFactory
->create(
$this->bill,
$this->day,
$this->sportsField
);
$comp->onSuccessfulRepeatableReservation[] = [$this, 'onSuccessfulRepeatableReservation'];
return $comp;
}
public function onSuccessfulRepeatableReservation(RepeatableReservationFormControl $control)
{
$this->resetState();
$this->flashMessage('Rezervace byli úspěšně uloženy', FlashMessage::SUCCESS);
$this->onSuccessfulReservationCreation($this);
$this->refresh('this');
}
protected function createComponentSportsFieldReservations()
{
$comp = $this->sportsFieldControlFactory
->create($this->day, $this->sportsField);
$reservations = $this->findReservations($this->sportsField);
foreach ($reservations as $reservation) {
$comp->addReservation($reservation);
}
return $comp;
}
public function handleShowReservationFormForBill($billId)
{
$this->resetState();
$this->billSelection = false;
$this->chosenBillId = $billId;
$this->bill = $this->billFacade->getBillById($this->chosenBillId);
if ($this->reservation !== null) {
$this->singleReservation = true;
}
if ($this->sportsField === null) {
$this->resetState();
$this->onMissingSportsField($this);
}
$this->refresh('this', ['selection']);
}
public function handleIntoNewBill()
{
$this->resetState();
$this->billSelection = false;
$this->intoNewBill = true;
if ($this->sportsField === null) {
$this->resetState();
$this->onMissingSportsField($this);
}
$this->refresh('this', ['selection']);
}
public function handleIntoExistingBill()
{
$this->resetState();
$this->billSelection = false;
$this->intoExistingBill = true;
if ($this->sportsField === null) {
$this->resetState();
$this->onMissingSportsField($this);
}
$this->refresh('this', ['selection']);
}
public function handleBack()
{
$this->resetState();
$this->refresh('this', ['selection']);
}
public function handleNewReservation()
{
$this->resetState();
$this->reservation = null;
$this->onParticipantResetting($this);
}
protected function createComponentReservationIntoNewBill()
{
$comp = $this->reservationIntoNewBillControlFactory
->create($this->day, $this->sportsField);
$comp->onSuccessfulBillSaving[] = [$this, 'onSuccessfulBillSaving'];
return $comp;
}
public function onSuccessfulBillSaving(ReservationIntoNewBillControl $control, Bill $bill)
{
$this->resetState();
$this->billSelection = false;
$this->chosenBillId = $bill->getId();
$this->refresh('this', ['selection']);
}
/**
* @param SportsField $sportsField
* @return Reservation[]
*/
private function findReservations(SportsField $sportsField)
{
$start = \DateTimeImmutable::createFromFormat('!Y-m-d H:i:s', $this->day->format('Y-m-d H:i:s'));
$end = \DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $this->day->modify('tomorrow')->format('Y-m-d H:i:s'));
return $this->reservationFacade
->findReservations(
(new ReservationQuery())
->byReservationTime($start, $end, $sportsField)
)->toArray();
}
/**
* @return Bill[]
*/
private function findOpenedBills()
{
return $this->billFacade
->findBills(
(new BillQuery())
->onlyOpened()
)->toArray();
}
private function resetState()
{
$this->chosenBillId = null;
$this->intoNewBill = false;
$this->intoExistingBill = false;
$this->billSelection = true;
$this->singleReservation = false;
$this->repeatableReservation = false;
}
}
interface ISportsFieldReservationControlFactory
{
/**
* @param \DateTimeImmutable $day
* @param SportsField|null $sportsField
* @return SportsFieldReservationControl
*/
public function create(\DateTimeImmutable $day, SportsField $sportsField = null);
}
{snippet row}
{if $sportsField !== null}
<div style="overflow: auto; clear: both;">
<div style="width: 10%;" class="td title">
{$sportsField->getName()|truncate:10}
</div>
<div style="position: relative; float: left; width: 90%;">
{if $isSportsFieldOpened === true}
{var $columnCounter = 0}
{for $i = 0; $i < $numberOfColumns; $i++}
<div style="width:{!$columnLength}%;" class="td">
{if $i >= $startColumn and $i < $endColumn}
<a href="#" data-modal="reservation_form" data-url="{link showDetail, columnNumber => $columnCounter}" class="modal-opener">
<i class="fa fa-clock-o"></i>
</a>
{php $columnCounter++}
{/if}
</div>
{/for}
{else}
<div style="width: 100%;" class="td empty">
Sportoviště je v tento den uzavřeno
</div>
{/if}
{* reservations *}
{snippet reservations}
{if count($reservations)}
{foreach $reservations as $reservation}
{var $reservationID = $reservation->getId()}
{control reservation-$reservationID}
{/foreach}
{/if}
{/snippet}
</div>
</div>
{/if}
{/snippet}
<?php
namespace Reservations\Components;
use CreativeProjects\Components\BaseControl;
use Nette\Application\UI\Multiplier;
use Reservations\Reservation;
use Reservations\SportsField;
class SportsFieldRowControl extends BaseControl
{
public $onEditReservation;
public $onNewReservation;
/** @var IReservationControlFactory */
private $reservationControlFactory;
/** @var int */
private $numberOfColumnsToDisplay;
/** @var Reservation[] */
private $reservations;
/** @var SportsField|null */
private $sportsField;
/** @var \DateTimeInterface */
private $startTime;
/** @var \DateTimeInterface */
private $endTime;
/** @var \DateTimeInterface */
private $day;
public function __construct(
\DateTimeInterface $day,
$numberOfColumnsToDisplay,
SportsField $sportsField = null,
IReservationControlFactory $reservationControlFactory
) {
$this->day = $day;
$this->sportsField = $sportsField;
$this->numberOfColumnsToDisplay = $numberOfColumnsToDisplay;
$this->reservationControlFactory = $reservationControlFactory;
}
public function render()
{
$template = $this->getTemplate();
$template->setFile(__DIR__ . '/sportsFieldRow.latte');
$template->sportsField = $this->sportsField;
$template->reservations = $this->reservations;
if ($this->sportsField !== null and (!isset($this->startTime) or !isset($this->endTime))) {
$this->startTime = $this->sportsField->getOpeningTime($this->day->format('D'));
$this->endTime = $this->sportsField->getClosingTime($this->day->format('D'));
}
$template->numberOfColumns = $this->numberOfColumnsToDisplay;
$template->columnLength = 100 / $this->numberOfColumnsToDisplay;
$startColumn = 1;
$endColumn = $this->numberOfColumnsToDisplay;
$isSportsFieldOpened = true;
if ($this->sportsField !== null and $this->sportsField->isOpenedInWeekDay($this->day)) {
$sportsFieldOpeningTime = $this->sportsField->getOpeningTime($this->day->format('D'));
$sportsFieldClosingTime = $this->sportsField->getClosingTime($this->day->format('D'));
$openingTime = \DateTimeImmutable::createFromFormat('!H', $sportsFieldOpeningTime->format('H'));
$closingTime = \DateTimeImmutable::createFromFormat('!H', $sportsFieldClosingTime->modify('next hour')->format('H'));
$startColumn = $openingTime->diff($this->startTime)->h;
$endColumn = $this->numberOfColumnsToDisplay - $closingTime->diff($this->endTime)->h;
} else {
$isSportsFieldOpened = false;
}
$template->startColumn = $startColumn;
$template->endColumn = $endColumn;
$template->isSportsFieldOpened = $isSportsFieldOpened;
$template->render();
}
protected function createComponentReservation()
{
return new Multiplier(function ($reservationId) {
$comp = $this->reservationControlFactory
->create(
$this->reservations[$reservationId],
$this->startTime,
$this->endTime
);
$comp->onModal[] = [$this, 'onReservationDetail'];
return $comp;
});
}
public function onReservationDetail(ReservationControl $control, Reservation $reservation)
{
$this->onEditReservation($this, $reservation);
}
public function handleShowDetail($columnNumber)
{
$globalStartTime = clone $this->startTime;
$clickedHour = $globalStartTime->modify(sprintf('+ %s hour%s', $columnNumber, $columnNumber < 2 ? null : 's'));
if ($this->sportsField === null) {
$this->redrawControl('row');
}
$this->onNewReservation($this, $clickedHour);
}
/**
* @param Reservation $reservation
*/
public function addReservation(Reservation $reservation)
{
if ($this->sportsField->getId() == $reservation->getSportsFieldId()) {
$this->reservations[$reservation->getId()] = $reservation;
}
}
/**
* @param \DateTimeInterface $startTime
* @param \DateTimeInterface $endTime
*/
public function setGlobalOuterTimes(\DateTimeInterface $startTime, \DateTimeInterface $endTime)
{
$this->startTime = $startTime;
$this->endTime = $endTime;
}
}
interface ISportsFieldRowControlFactory
{
/**
* @param \DateTimeInterface $day,
* @param int $numberOfColumnsToDisplay
* @param SportsField|null $sportsField
* @return SportsFieldRowControl
*/
public function create(\DateTimeInterface $day, $numberOfColumnsToDisplay, SportsField $sportsField = null);
}
<div class="reservations" id="table-of-reservations" data-url="{link refreshTable}" data-automatically-refresh-table="{$automaticallyRefreshReservationTable}" data-refresh-rate="{$reservationTableRefreshRate}">
{snippet reservationsTable}
<div class="header">
<div style="width: 10%;" class="th corner">&nbsp;</div>
<div style="position: relative; float: left; width: 90%;">
<div style="width:{!$columnLength}%;" class="th" n:for="$i = 0; $i < $numberOfColumns; $i++">
{$startTime->modify(sprintf('+%s hour', $i))|date:'H:i'}
</div>
</div>
</div>
<div class="body">
{if count($sportsFields) > 0}
{foreach $sportsFields as $sportsField}
{var $sportsFieldId = $sportsField->getId()}
{control sportsFieldRow-$sportsFieldId}
{/foreach}
{else}
Žádná sportoviště k zobrazení
{/if}
</div>
{/snippet}
</div>
<?php
namespace Reservations\Components;
use CreativeProjects\Components\BaseControl;
use Reservations\Facades\ReservationFacade;
use Reservations\Facades\SportsFieldFacade;
use Reservations\Queries\ReservationQuery;
use Reservations\Traits\THoursCalculation;
use Nette\Application\UI\Multiplier;
use Reservations\Reservation;
use Reservations\SportsField;
class SportsFieldsOverviewControl extends BaseControl
{
use THoursCalculation;
public $onFreeTimeClick;
public $onReservedTimeClick;
/** @var ISportsFieldReservationControlFactory */
private $sportsFieldReservationControlFactory;
/** @var IReservationFormControlFactory */
private $reservationFormControlFactory;
/** @var ISportsFieldRowControlFactory */
private $sportsFieldRowControlFactory;
/** @var ISportsFieldControlFactory */
private $sportsFieldControlFactory;
/** @var SportsFieldFacade */
private $sportsFieldFacade;
/** @var ReservationFacade */
private $reservationFacade;
/** @var SportsField[] */
private $sportsFields;
/** @var Reservation[] */
private $reservations;
/** @var Reservation */
private $reservation;
/** @var \DateTimeInterface */
private $date;
/** @var int */
private $numberOfColumnsToDisplay;
/** @var \DateTimeInterface */
private $startTime;
/** @var \DateTimeInterface */
private $endTime;
private $automaticallyRefreshReservationTable = true;
private $reservationTableRefreshRate = 35000; // in ms (35000ms => 35s)
public function __construct(
\DateTimeInterface $date,
array $sportsFields,
SportsFieldFacade $sportsFieldFacade,
ReservationFacade $reservationFacade,
ISportsFieldControlFactory $sportsFieldControlFactory,
ISportsFieldRowControlFactory $sportsFieldRowControlFactory,
IReservationFormControlFactory $reservationFormControlFactory,
ISportsFieldReservationControlFactory $sportsFieldReservationControlFactory
) {
$this->date = $date;
$this->sportsFields = $sportsFields;
$this->sportsFieldFacade = $sportsFieldFacade;
$this->reservationFacade = $reservationFacade;
$this->sportsFieldControlFactory = $sportsFieldControlFactory;
$this->sportsFieldRowControlFactory = $sportsFieldRowControlFactory;
$this->reservationFormControlFactory = $reservationFormControlFactory;
$this->sportsFieldReservationControlFactory = $sportsFieldReservationControlFactory;
}
public function setSportsFields(array $sportsFields)
{
$this->sportsFields = $sportsFields;
}
public function doNotRefreshTable()
{
$this->automaticallyRefreshReservationTable = false;
}
/**
* @param int $refreshRate in milliseconds
*/
public function setTableRefreshRate($refreshRate)
{
$this->reservationTableRefreshRate = $refreshRate;
}
public function render()
{
$template = $this->getTemplate();
$template->setFile(__DIR__ . '/sportsFieldsOverview.latte');
$sportsFieldCount = count($this->sportsFields);
if ($sportsFieldCount === 1) {
$this->findReservations(reset($this->sportsFields));
} elseif ($sportsFieldCount > 1) {
$this->findReservations();
}
$template->sportsFields = $this->sportsFields;
$template->displayedDate = $this->date;
$template->currentDate = \DateTimeImmutable::createFromFormat('!Y-n-j', date('Y-n-j'));
// -----
if (!isset($this->startTime) or !isset($this->endTime)) {
$outerDateTimes = $this->findOuterDateTimes($this->date, $this->sportsFields, $this->reservations);
$startTime = $outerDateTimes['openingTime'];
$endTime = $outerDateTimes['closingTime'];
$numberOfHours = $this->calcNumberOfHours($startTime, $endTime);
$this->startTime = $startTime;
$this->endTime = $endTime;
$this->numberOfColumnsToDisplay = $numberOfHours;
}
$template->startTime = $this->startTime;
$template->numberOfColumns = $this->numberOfColumnsToDisplay;
$template->columnLength = 100 / $this->numberOfColumnsToDisplay;
$this->template->reservationTableRefreshRate = $this->reservationTableRefreshRate;
$this->template->automaticallyRefreshReservationTable = (int)$this->automaticallyRefreshReservationTable;
$template->render();
}
protected function createComponentSportsFieldRow()
{
return new Multiplier(function ($sportsFieldId) {
$sportsField = null;
if (empty($this->sportsFields)) { // ajax
$sportsField = $this->sportsFieldFacade->getSportsFieldById($sportsFieldId);
$this->sportsFields = [];
if ($sportsField !== null) {
$this->sportsFields[$sportsFieldId] = $sportsField;
$this->findReservations($sportsField);
}
} else {
if (isset($this->sportsFields[$sportsFieldId])) {
if ($this->sportsFields[$sportsFieldId]->isFrozen() !== true) {
$sportsField = $this->sportsFields[$sportsFieldId];
}
}
}
if (!isset($this->startTime) or !isset($this->endTime)) {
$outerDateTimes = $this->findOuterDateTimes($this->date, $this->sportsFields, $this->reservations);
$this->startTime = $outerDateTimes['openingTime'];
$this->endTime = $outerDateTimes['closingTime'];
$this->numberOfColumnsToDisplay = $this->calcNumberOfHours($this->startTime, $this->endTime);
}
$comp = $this->sportsFieldRowControlFactory
->create($this->date, $this->numberOfColumnsToDisplay, $sportsField);
$comp->setGlobalOuterTimes($this->startTime, $this->endTime);
$comp->onNewReservation[] = function (SportsFieldRowControl $control, \DateTimeImmutable $clickedHour) use ($sportsField) {
$this->onFreeTimeClick($this, $control, $sportsField, $clickedHour);
};
$comp->onEditReservation[] = function (SportsFieldRowControl $control, Reservation $reservation) use ($sportsFieldId) {
$this->onReservedTimeClick($this, $control, $this->sportsFields[$sportsFieldId], $reservation);
};
if (!empty($this->reservations)) {
foreach ($this->reservations as $reservation) {
$comp->addReservation($reservation);
}
}
return $comp;
});
}
/*protected function createComponentSportsFieldReservation()
{
if ($this->selectedSportsField === null) {
$this->selectedSportsField = $this->sportsFieldFacade
->getSportsFieldById($this->selectedSportsFieldId);
if ($this->selectedSportsField->isFrozen() or $this->selectedSportsField === null) {
throw new SportsFieldNotFoundException; // todo
}
}
$comp = $this->sportsFieldReservationControlFactory
->create($this->date, $this->selectedSportsField);
if ($this->clickedHour !== null) {
$comp->setClickedHour($this->clickedHour);
}
if ($this->reservationId !== null) {
if ($this->reservation === null) {
$this->reservation = $this->reservationFacade->getReservationById($this->reservationId);
}
$comp->setReservation($this->reservation);
}
$comp->onSuccessfulReservationCreation[] = function () {
$this->sportsFields = null;
$this->reservations = null;
$this['sportsFieldRow'][$this->selectedSportsField->getId()]->redrawControl();
};
return $comp;
}*/
/**
* @param SportsField|null $sportsField
*/
private function findReservations(SportsField $sportsField = null)
{
if ($this->reservations !== null) {
return;
}
$start = \DateTimeImmutable::createFromFormat('!Y-m-d H:i:s', $this->date->format('Y-m-d H:i:s'));
$end = \DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $this->date->modify('tomorrow')->format('Y-m-d H:i:s'));
$this->reservations = $this->reservationFacade
->findReservations(
(new ReservationQuery())
->byReservationTime($start, $end, $sportsField)
)->toArray();
}
/**
* @param \DateTimeInterface $date
* @param array $sportsFields
* @param array $reservations
* @return array [openingTime => \DateTimeImmutable, closingTime => \DateTimeImmutable]
*/
private function findOuterDateTimes(\DateTimeInterface $date, array $sportsFields, array $reservations = null)
{
$defaultOpeningTime = \DateTimeImmutable::createFromFormat('!H:i', '08:00');
$defaultClosingTime = \DateTimeImmutable::createFromFormat('!H:i', '16:00');
if (empty($sportsFields)) {
return [
'openingTime' => $defaultOpeningTime,
'closingTime' => $defaultClosingTime
];
}
$weekDay = $date->format('D');
/** @var SportsField $sf */
$sf = array_shift($sportsFields);
$openingTime = $sf->getOpeningTime($weekDay);
if ($openingTime === null) {
$openingTime = $defaultOpeningTime;
}
$closingTime = $sf->getClosingTime($weekDay);
if ($closingTime === null) {
$closingTime = $defaultClosingTime;
}
/** @var SportsField $sportsField */
foreach ($sportsFields as $sportsField) {
if (!$sportsField->isOpenedInWeekDay($weekDay)) {
continue;
}
$sot = $sportsField->getOpeningTime($weekDay);
if ($sot < $openingTime) {
$openingTime = $sot;
}
$sct = $sportsField->getClosingTime($weekDay);
if ($sct > $closingTime) {
$closingTime = $sct;
}
}
if (!empty($reservations)) {
/** @var Reservation $reservation */
foreach ($reservations as $reservation) {
$rs = \DateTimeImmutable::createFromFormat('!H:i:s', $reservation->getStart('H:i:s'));
if ($rs < $openingTime) {
$openingTime = $rs;
}
$re = \DateTimeImmutable::createFromFormat('!H:i:s', $reservation->getEnd('H:i:s'));
if ($re > $closingTime) {
$closingTime = $re;
}
}
}
$startTime = \DateTimeImmutable::createFromFormat('!d.m.Y H', $openingTime->format('d.m.Y H'));
$endTime = \DateTimeImmutable::createFromFormat('!d.m.Y H', $closingTime->modify('next hour')->format('d.m.Y H'));
return ['openingTime' => $startTime, 'closingTime' => $endTime];
}
public function handleRefreshTable()
{
if ($this->presenter->isAjax()) {
$this->redrawControl('reservationsTable');
} else {
//$this->redirect('this'); // todo redirect?
}
}
}
interface ISportsFieldsOverviewControlFactory
{
/**
* @param \DateTimeInterface $date
* @param array $sportsFields
* @return SportsFieldsOverviewControl
*/
public function create(\DateTimeInterface $date, array $sportsFields);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment