From e830805443048b50d553bbf2349ead3dcf55f976 Mon Sep 17 00:00:00 2001 From: Bernd Bestel Date: Sat, 4 Aug 2018 15:44:58 +0200 Subject: [PATCH] Refresh also habit/battery statistics on changes on overview pages (references #26) --- controllers/BatteriesApiController.php | 5 ++ controllers/BatteriesController.php | 14 +--- controllers/HabitsApiController.php | 5 ++ controllers/HabitsController.php | 16 +---- grocy.openapi.json | 94 ++++++++++++++++++++++++-- migrations/0033.sql | 29 ++++++++ public/viewjs/batteriesoverview.js | 35 ++++++++++ public/viewjs/habitsoverview.js | 35 ++++++++++ routes.php | 2 + services/BatteriesService.php | 22 ------ services/HabitsService.php | 23 +------ views/batteriesoverview.blade.php | 10 +-- views/habitsoverview.blade.php | 10 +-- 13 files changed, 214 insertions(+), 86 deletions(-) create mode 100644 migrations/0033.sql diff --git a/controllers/BatteriesApiController.php b/controllers/BatteriesApiController.php index ed8ddb51..82bf3ce4 100644 --- a/controllers/BatteriesApiController.php +++ b/controllers/BatteriesApiController.php @@ -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()); + } } diff --git a/controllers/BatteriesController.php b/controllers/BatteriesController.php index c693badc..ce9f79f2 100644 --- a/controllers/BatteriesController.php +++ b/controllers/BatteriesController.php @@ -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 ]); } diff --git a/controllers/HabitsApiController.php b/controllers/HabitsApiController.php index 292bbfd8..2c1d7d87 100644 --- a/controllers/HabitsApiController.php +++ b/controllers/HabitsApiController.php @@ -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()); + } } diff --git a/controllers/HabitsController.php b/controllers/HabitsController.php index 745b0e97..0d3d517b 100644 --- a/controllers/HabitsController.php +++ b/controllers/HabitsController.php @@ -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 ]); } diff --git a/grocy.openapi.json b/grocy.openapi.json index d30a3e4b..3524e85f 100644 --- a/grocy.openapi.json +++ b/grocy.openapi.json @@ -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": { diff --git a/migrations/0033.sql b/migrations/0033.sql new file mode 100644 index 00000000..dab4ca2d --- /dev/null +++ b/migrations/0033.sql @@ -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; diff --git a/public/viewjs/batteriesoverview.js b/public/viewjs/batteriesoverview.js index 7cdd2c44..814e7436 100644 --- a/public/viewjs/batteriesoverview.js +++ b/public/viewjs/batteriesoverview.js @@ -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(); diff --git a/public/viewjs/habitsoverview.js b/public/viewjs/habitsoverview.js index 882fb53d..1f140fcf 100644 --- a/public/viewjs/habitsoverview.js +++ b/public/viewjs/habitsoverview.js @@ -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(); diff --git a/routes.php b/routes.php index d451314e..ee73ac0e 100644 --- a/routes.php +++ b/routes.php @@ -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([ diff --git a/services/BatteriesService.php b/services/BatteriesService.php index b25ac5c9..8d3ec8fa 100644 --- a/services/BatteriesService.php +++ b/services/BatteriesService.php @@ -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)) diff --git a/services/HabitsService.php b/services/HabitsService.php index 153bacf7..b2b4d7e4 100644 --- a/services/HabitsService.php +++ b/services/HabitsService.php @@ -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)) diff --git a/views/batteriesoverview.blade.php b/views/batteriesoverview.blade.php index c721eb81..b8cf29a7 100644 --- a/views/batteriesoverview.blade.php +++ b/views/batteriesoverview.blade.php @@ -12,8 +12,8 @@

@yield('title')

-

{{ 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)) }}

-

{{ Pluralize($countOverdue, $L('#1 battery is overdue to be charged', $countOverdue), $L('#1 batteries are overdue to be charged', $countOverdue)) }}

+

+

@@ -37,7 +37,7 @@ @foreach($current as $curentBatteryEntry) - + @if(FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->charge_interval_days > 0) - {{ $nextChargeTimes[$curentBatteryEntry->battery_id] }} - + {{ $curentBatteryEntry->next_estimated_charge_time }} + @else ... @endif diff --git a/views/habitsoverview.blade.php b/views/habitsoverview.blade.php index eda73ccd..6cd23abc 100644 --- a/views/habitsoverview.blade.php +++ b/views/habitsoverview.blade.php @@ -12,8 +12,8 @@

@yield('title')

-

{{ 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)) }}

-

{{ Pluralize($countOverdue, $L('#1 habit is overdue to be done', $countOverdue), $L('#1 habits are overdue to be done', $countOverdue)) }}

+

+

@@ -37,7 +37,7 @@ @foreach($currentHabits as $curentHabitEntry) - +
@if(FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->period_type === \Grocy\Services\HabitsService::HABIT_TYPE_DYNAMIC_REGULAR) - {{ $nextHabitTimes[$curentHabitEntry->habit_id] }} - + {{ $curentHabitEntry->next_estimated_execution_time }} + @else ... @endif