Refresh also habit/battery statistics on changes on overview pages (references #26)

This commit is contained in:
Bernd Bestel 2018-08-04 15:44:58 +02:00
parent ca3f28b615
commit e830805443
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
13 changed files with 214 additions and 86 deletions

View File

@ -44,4 +44,9 @@ class BatteriesApiController extends BaseApiController
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
public function Current(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->ApiResponse($this->BatteriesService->GetCurrent());
}
}

View File

@ -16,22 +16,10 @@ class BatteriesController extends BaseController
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$nextChargeTimes = array();
foreach($this->Database->batteries() as $battery)
{
$nextChargeTimes[$battery->id] = $this->BatteriesService->GetNextChargeTime($battery->id);
}
$nextXDays = 5;
$countDueNextXDays = count(FindAllItemsInArrayByValue($nextChargeTimes, date('Y-m-d', strtotime("+$nextXDays days")), '<'));
$countOverdue = count(FindAllItemsInArrayByValue($nextChargeTimes, date('Y-m-d', strtotime('-1 days')), '<'));
return $this->AppContainer->view->render($response, 'batteriesoverview', [
'batteries' => $this->Database->batteries()->orderBy('name'),
'current' => $this->BatteriesService->GetCurrent(),
'nextChargeTimes' => $nextChargeTimes,
'nextXDays' => $nextXDays,
'countDueNextXDays' => $countDueNextXDays - $countOverdue,
'countOverdue' => $countOverdue
'nextXDays' => 5
]);
}

View File

@ -50,4 +50,9 @@ class HabitsApiController extends BaseApiController
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
public function Current(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->ApiResponse($this->HabitsService->GetCurrent());
}
}

View File

@ -16,22 +16,10 @@ class HabitsController extends BaseController
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$nextHabitTimes = array();
foreach($this->Database->habits() as $habit)
{
$nextHabitTimes[$habit->id] = $this->HabitsService->GetNextHabitTime($habit->id);
}
$nextXDays = 5;
$countDueNextXDays = count(FindAllItemsInArrayByValue($nextHabitTimes, date('Y-m-d', strtotime("+$nextXDays days")), '<'));
$countOverdue = count(FindAllItemsInArrayByValue($nextHabitTimes, date('Y-m-d', strtotime('-1 days')), '<'));
return $this->AppContainer->view->render($response, 'habitsoverview', [
'habits' => $this->Database->habits()->orderBy('name'),
'currentHabits' => $this->HabitsService->GetCurrentHabits(),
'nextHabitTimes' => $nextHabitTimes,
'nextXDays' => $nextXDays,
'countDueNextXDays' => $countDueNextXDays - $countOverdue,
'countOverdue' => $countOverdue
'currentHabits' => $this->HabitsService->GetCurrent(),
'nextXDays' => 5
]);
}

View File

@ -568,7 +568,8 @@
"required": false,
"description": "The best before date of the product to add, when omitted, the current date is used",
"schema": {
"type": "date"
"type": "string",
"format": "date"
}
},
{
@ -715,7 +716,8 @@
"required": false,
"description": "The best before date which applies to added products",
"schema": {
"type": "date"
"type": "string",
"format": "date"
}
}
],
@ -842,7 +844,7 @@
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Product"
"$ref": "#/components/schemas/CurrentStockResponse"
}
}
}
@ -1108,6 +1110,29 @@
}
}
},
"/habits/get-current": {
"get": {
"description": "Returns all habits incl. the next estimated execution time per habit",
"tags": [
"Habits"
],
"responses": {
"200": {
"description": "An array of CurrentHabitResponse objects",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CurrentHabitResponse"
}
}
}
}
}
}
}
},
"/batteries/track-charge-cycle/{batteryId}": {
"get": {
"description": "Tracks a charge cycle of the given battery",
@ -1130,7 +1155,8 @@
"required": false,
"description": "The time of when the battery was charged, when omitted, the current time is used",
"schema": {
"type": "date-time"
"type": "string",
"format": "date-time"
}
}
],
@ -1198,6 +1224,29 @@
}
}
}
},
"/batteries/get-current": {
"get": {
"description": "Returns all batteries incl. the next estimated charge time per battery",
"tags": [
"Batteries"
],
"responses": {
"200": {
"description": "An array of CurrentBatteryResponse objects",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CurrentBatteryResponse"
}
}
}
}
}
}
}
}
},
"components": {
@ -1740,11 +1789,46 @@
"type": "integer"
},
"best_before_date": {
"type": "date",
"type": "string",
"format": "date",
"description": "The next best before date for this product"
}
}
},
"CurrentHabitResponse": {
"type": "object",
"properties": {
"habit_id": {
"type": "integer"
},
"last_tracked_time": {
"type": "string",
"format": "date-time"
},
"next_estimated_execution_time": {
"type": "string",
"format": "date-time",
"description": "The next estimated execution time of this habit, 2999-12-31 23:59:59 when the given habit has a period_type of manually"
}
}
},
"CurrentBatteryResponse": {
"type": "object",
"properties": {
"battery_id": {
"type": "integer"
},
"last_tracked_time": {
"type": "string",
"format": "date-time"
},
"next_estimated_charge_time": {
"type": "string",
"format": "date-time",
"description": "The next estimated charge time of this battery, 2999-12-31 23:59:59 when the given battery has no charge_interval_days defined"
}
}
},
"CurrentVolatilStockResponse": {
"type": "object",
"properties": {

29
migrations/0033.sql Normal file
View File

@ -0,0 +1,29 @@
DROP VIEW habits_current;
CREATE VIEW habits_current
AS
SELECT
h.id AS habit_id,
MAX(l.tracked_time) AS last_tracked_time,
CASE h.period_type
WHEN 'manually' THEN '2999-12-31 23:59:59'
WHEN 'dynamic-regular' THEN datetime(MAX(l.tracked_time), '+' || CAST(h.period_days AS TEXT) || ' day')
END AS next_estimated_execution_time
FROM habits h
LEFT JOIN habits_log l
ON h.id = l.habit_id
GROUP BY h.id, h.period_days;
DROP VIEW batteries_current;
CREATE VIEW batteries_current
AS
SELECT
b.id AS battery_id,
MAX(l.tracked_time) AS last_tracked_time,
CASE WHEN b.charge_interval_days = 0
THEN '2999-12-31 23:59:59'
ELSE datetime(MAX(l.tracked_time), '+' || CAST(b.charge_interval_days AS TEXT) || ' day')
END AS next_estimated_charge_time
FROM batteries b
LEFT JOIN battery_charge_cycles l
ON b.id = l.battery_id
GROUP BY b.id, b.charge_interval_days;

View File

@ -38,6 +38,7 @@ $(document).on('click', '.track-charge-cycle-button', function(e)
RefreshContextualTimeago();
toastr.success(L('Tracked charge cylce of battery #1 on #2', batteryName, trackedTime));
RefreshStatistics();
},
function(xhr)
{
@ -45,3 +46,37 @@ $(document).on('click', '.track-charge-cycle-button', function(e)
}
);
});
function RefreshStatistics()
{
var nextXDays = $("#info-due-batteries").data("next-x-days");
Grocy.Api.Get('batteries/get-current',
function(result)
{
var dueCount = 0;
var overdueCount = 0;
var now = moment();
var nextXDaysThreshold = moment().add(nextXDays, "days");
result.forEach(element => {
var date = moment(element.next_estimated_charge_time);
if (date.isBefore(now))
{
overdueCount++;
}
else if (date.isBefore(nextXDaysThreshold))
{
dueCount++;
}
});
$("#info-due-batteries").text(Pluralize(dueCount, L('#1 battery is due to be charged within the next #2 days', dueCount, nextXDays), L('#1 batteries are due to be charged within the next #2 days', dueCount, nextXDays)));
$("#info-overdue-batteries").text(Pluralize(overdueCount, L('#1 battery is overdue to be charged', overdueCount), L('#1 batteries are overdue to be charged', overdueCount)));
},
function(xhr)
{
console.error(xhr);
}
);
}
RefreshStatistics();

View File

@ -38,6 +38,7 @@ $(document).on('click', '.track-habit-button', function(e)
RefreshContextualTimeago();
toastr.success(L('Tracked execution of habit #1 on #2', habitName, trackedTime));
RefreshStatistics();
},
function(xhr)
{
@ -45,3 +46,37 @@ $(document).on('click', '.track-habit-button', function(e)
}
);
});
function RefreshStatistics()
{
var nextXDays = $("#info-due-habits").data("next-x-days");
Grocy.Api.Get('habits/get-current',
function(result)
{
var dueCount = 0;
var overdueCount = 0;
var now = moment();
var nextXDaysThreshold = moment().add(nextXDays, "days");
result.forEach(element => {
var date = moment(element.next_estimated_execution_time);
if (date.isBefore(now))
{
overdueCount++;
}
else if (date.isBefore(nextXDaysThreshold))
{
dueCount++;
}
});
$("#info-due-habits").text(Pluralize(dueCount, L('#1 habit is due to be done within the next #2 days', dueCount, nextXDays), L('#1 habits are due to be done within the next #2 days', dueCount, nextXDays)));
$("#info-overdue-habits").text(Pluralize(overdueCount, L('#1 habit is overdue to be done', overdueCount), L('#1 habits are overdue to be done', overdueCount)));
},
function(xhr)
{
console.error(xhr);
}
);
}
RefreshStatistics();

View File

@ -95,10 +95,12 @@ $app->group('/api', function()
// Habits
$this->get('/habits/track-habit-execution/{habitId}', '\Grocy\Controllers\HabitsApiController:TrackHabitExecution');
$this->get('/habits/get-habit-details/{habitId}', '\Grocy\Controllers\HabitsApiController:HabitDetails');
$this->get('/habits/get-current', '\Grocy\Controllers\HabitsApiController:Current');
// Batteries
$this->get('/batteries/track-charge-cycle/{batteryId}', '\Grocy\Controllers\BatteriesApiController:TrackChargeCycle');
$this->get('/batteries/get-battery-details/{batteryId}', '\Grocy\Controllers\BatteriesApiController:BatteryDetails');
$this->get('/batteries/get-current', '\Grocy\Controllers\BatteriesApiController:Current');
})->add(new ApiKeyAuthMiddleware($appContainer, $appContainer->LoginControllerInstance->GetSessionCookieName(), $appContainer->ApiKeyHeaderName))
->add(JsonMiddleware::class)
->add(new CorsMiddleware([

View File

@ -10,28 +10,6 @@ class BatteriesService extends BaseService
return $this->DatabaseService->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
}
public function GetNextChargeTime(int $batteryId)
{
if (!$this->BatteryExists($batteryId))
{
throw new \Exception('Battery does not exist');
}
$battery = $this->Database->batteries($batteryId);
$batteryLastLogRow = $this->DatabaseService->ExecuteDbQuery("SELECT * from batteries_current WHERE battery_id = $batteryId LIMIT 1")->fetch(\PDO::FETCH_OBJ);
if ($battery->charge_interval_days > 0)
{
return date('Y-m-d H:i:s', strtotime('+' . $battery->charge_interval_days . ' day', strtotime($batteryLastLogRow->last_tracked_time)));
}
else
{
return date('2999-12-31 23:59:59');
}
return null;
}
public function GetBatteryDetails(int $batteryId)
{
if (!$this->BatteryExists($batteryId))

View File

@ -7,33 +7,12 @@ class HabitsService extends BaseService
const HABIT_TYPE_MANUALLY = 'manually';
const HABIT_TYPE_DYNAMIC_REGULAR = 'dynamic-regular';
public function GetCurrentHabits()
public function GetCurrent()
{
$sql = 'SELECT * from habits_current';
return $this->DatabaseService->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
}
public function GetNextHabitTime(int $habitId)
{
if (!$this->HabitExists($habitId))
{
throw new \Exception('Habit does not exist');
}
$habit = $this->Database->habits($habitId);
$habitLastLogRow = $this->DatabaseService->ExecuteDbQuery("SELECT * from habits_current WHERE habit_id = $habitId LIMIT 1")->fetch(\PDO::FETCH_OBJ);
switch($habit->period_type)
{
case self::HABIT_TYPE_MANUALLY:
return date('2999-12-31 23:59:59');
case self::HABIT_TYPE_DYNAMIC_REGULAR:
return date('Y-m-d H:i:s', strtotime('+' . $habit->period_days . ' day', strtotime($habitLastLogRow->last_tracked_time)));
}
return null;
}
public function GetHabitDetails(int $habitId)
{
if (!$this->HabitExists($habitId))

View File

@ -12,8 +12,8 @@
<div class="row">
<div class="col">
<h1>@yield('title')</h1>
<p class="btn btn-lg btn-warning no-real-button responsive-button mr-2">{{ Pluralize($countDueNextXDays, $L('#1 battery is due to be charged within the next #2 days', $countDueNextXDays, $nextXDays), $L('#1 batteries are due to be charged within the next #2 days', $countDueNextXDays, $nextXDays)) }}</p>
<p class="btn btn-lg btn-danger no-real-button responsive-button">{{ Pluralize($countOverdue, $L('#1 battery is overdue to be charged', $countOverdue), $L('#1 batteries are overdue to be charged', $countOverdue)) }}</p>
<p id="info-due-batteries" data-next-x-days="{{ $nextXDays }}" class="btn btn-lg btn-warning no-real-button responsive-button mr-2"></p>
<p id="info-overdue-batteries" class="btn btn-lg btn-danger no-real-button responsive-button"></p>
</div>
</div>
@ -37,7 +37,7 @@
</thead>
<tbody>
@foreach($current as $curentBatteryEntry)
<tr class="@if(FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->charge_interval_days > 0 && $nextChargeTimes[$curentBatteryEntry->battery_id] < date('Y-m-d H:i:s')) table-danger @elseif(FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->charge_interval_days > 0 && $nextChargeTimes[$curentBatteryEntry->battery_id] < date('Y-m-d H:i:s', strtotime('+5 days'))) table-warning @endif">
<tr class="@if(FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->charge_interval_days > 0 && $curentBatteryEntry->next_estimated_charge_time < date('Y-m-d H:i:s')) table-danger @elseif(FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->charge_interval_days > 0 && $curentBatteryEntry->next_estimated_charge_time < date('Y-m-d H:i:s', strtotime("+$nextXDays days"))) table-warning @endif">
<td class="fit-content">
<a class="btn btn-success btn-sm track-charge-cycle-button" href="#" title="{{ $L('Track charge cycle of battery #1', FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->name) }}"
data-battery-id="{{ $curentBatteryEntry->battery_id }}"
@ -54,8 +54,8 @@
</td>
<td>
@if(FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->charge_interval_days > 0)
{{ $nextChargeTimes[$curentBatteryEntry->battery_id] }}
<time class="timeago timeago-contextual" datetime="{{ $nextChargeTimes[$curentBatteryEntry->battery_id] }}"></time>
{{ $curentBatteryEntry->next_estimated_charge_time }}
<time class="timeago timeago-contextual" datetime="{{ $curentBatteryEntry->next_estimated_charge_time }}"></time>
@else
...
@endif

View File

@ -12,8 +12,8 @@
<div class="row">
<div class="col">
<h1>@yield('title')</h1>
<p class="btn btn-lg btn-warning no-real-button responsive-button mr-2">{{ Pluralize($countDueNextXDays, $L('#1 habit is due to be done within the next #2 days', $countDueNextXDays, $nextXDays), $L('#1 habits are due to be done within the next #2 days', $countDueNextXDays, $nextXDays)) }}</p>
<p class="btn btn-lg btn-danger no-real-button responsive-button">{{ Pluralize($countOverdue, $L('#1 habit is overdue to be done', $countOverdue), $L('#1 habits are overdue to be done', $countOverdue)) }}</p>
<p id="info-due-habits" data-next-x-days="{{ $nextXDays }}" class="btn btn-lg btn-warning no-real-button responsive-button mr-2"></p>
<p id="info-overdue-habits" class="btn btn-lg btn-danger no-real-button responsive-button"></p>
</div>
</div>
@ -37,7 +37,7 @@
</thead>
<tbody>
@foreach($currentHabits as $curentHabitEntry)
<tr class="@if(FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->period_type === \Grocy\Services\HabitsService::HABIT_TYPE_DYNAMIC_REGULAR && $nextHabitTimes[$curentHabitEntry->habit_id] < date('Y-m-d H:i:s')) table-danger @elseif(FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->period_type === \Grocy\Services\HabitsService::HABIT_TYPE_DYNAMIC_REGULAR && $nextHabitTimes[$curentHabitEntry->habit_id] < date('Y-m-d H:i:s', strtotime('+5 days'))) table-warning @endif">
<tr class="@if(FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->period_type === \Grocy\Services\HabitsService::HABIT_TYPE_DYNAMIC_REGULAR && $curentHabitEntry->next_estimated_execution_time < date('Y-m-d H:i:s')) table-danger @elseif(FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->period_type === \Grocy\Services\HabitsService::HABIT_TYPE_DYNAMIC_REGULAR && $curentHabitEntry->next_estimated_execution_time < date('Y-m-d H:i:s', strtotime("+$nextXDays days"))) table-warning @endif">
<td class="fit-content">
<a class="btn btn-success btn-sm track-habit-button" href="#" title="{{ $L('Track execution of habit #1', FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->name) }}"
data-habit-id="{{ $curentHabitEntry->habit_id }}"
@ -53,8 +53,8 @@
</td>
<td>
@if(FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->period_type === \Grocy\Services\HabitsService::HABIT_TYPE_DYNAMIC_REGULAR)
{{ $nextHabitTimes[$curentHabitEntry->habit_id] }}
<time class="timeago timeago-contextual" datetime="{{ $nextHabitTimes[$curentHabitEntry->habit_id] }}"></time>
{{ $curentHabitEntry->next_estimated_execution_time }}
<time class="timeago timeago-contextual" datetime="{{ $curentHabitEntry->next_estimated_execution_time }}"></time>
@else
...
@endif