diff --git a/.env.example b/.env.example index 1d258dc..31a79d1 100644 --- a/.env.example +++ b/.env.example @@ -27,6 +27,8 @@ APP_CRONPW=null APP_CRONJOB_MAILLIMIT=5 APP_GITHUB_URL="https://github.com/danielbrendel/hortusfox-web" APP_SERVICE_URL="https://www.hortusfox.com" +APP_ENABLEHISTORY=true +APP_HISTORY_NAME="History" # Photo resize factors PHOTO_RESIZE_FACTOR_DEFAULT=1.0 diff --git a/app/config/routes.php b/app/config/routes.php index 93da11a..520b789 100644 --- a/app/config/routes.php +++ b/app/config/routes.php @@ -66,6 +66,9 @@ return [ array('/admin/location/remove', 'ANY', 'admin@remove_location'), array('/admin/media/logo', 'POST', 'admin@upload_media_logo'), array('/admin/media/background', 'POST', 'admin@upload_media_background'), + array('/history', 'GET', 'index@view_history'), + array('/plants/history/add', 'ANY', 'index@add_to_history'), + array('/plants/history/remove', 'ANY', 'index@remove_from_history'), array('/cronjob/overduetasks', 'GET', 'cronjobs@overdue_tasks'), array('$404', 'ANY', 'error404@index') ]; diff --git a/app/controller/admin.php b/app/controller/admin.php index edce57b..d3023fd 100644 --- a/app/controller/admin.php +++ b/app/controller/admin.php @@ -70,9 +70,11 @@ class AdminController extends BaseController { $onlinetimelimit = (int)$request->params()->query('onlinetimelimit', env('APP_ONLINEMINUTELIMIT')); $chatonlineusers = (bool)$request->params()->query('chatonlineusers', 0); $chattypingindicator = (bool)$request->params()->query('chattypingindicator', 0); + $enablehistory = (bool)$request->params()->query('enablehistory', 0); + $history_name = $request->params()->query('history_name', env('APP_HISTORY_NAME')); $cronpw = $request->params()->query('cronpw', env('APP_CRONPW')); - UtilsModule::saveEnvironment($workspace, $lang, $scroller, $enablechat, $onlinetimelimit, $chatonlineusers, $chattypingindicator, $cronpw); + UtilsModule::saveEnvironment($workspace, $lang, $scroller, $enablechat, $onlinetimelimit, $chatonlineusers, $chattypingindicator, $enablehistory, $history_name, $cronpw); FlashMessage::setMsg('success', __('app.environment_settings_saved')); diff --git a/app/controller/index.php b/app/controller/index.php index 577c7d3..5d7ee7b 100644 --- a/app/controller/index.php +++ b/app/controller/index.php @@ -449,6 +449,10 @@ class IndexController extends BaseController { PlantsModel::removePlant($plant); + if ($location == 0) { + return back(); + } + return redirect('/plants/location/' . $location); } catch (\Exception $e) { FlashMessage::setMsg('error', $e->getMessage()); @@ -1118,4 +1122,72 @@ class IndexController extends BaseController { ]); } } + + /** + * Handles URL: /history + * + * @param Asatru\Controller\ControllerArg $request + * @return Asatru\View\ViewHandler|Asatru\View\RedirectHandler + */ + public function view_history($request) + { + if (!env('APP_ENABLEHISTORY')) { + return redirect('/'); + } + + $limit = $request->params()->query('limit', null); + $sorting = $request->params()->query('sorting', null); + $direction = $request->params()->query('direction', null); + + $user = UserModel::getAuthUser(); + + $history = PlantsModel::getHistory($limit, $sorting, $direction); + + return parent::view(['content', 'history'], [ + 'user' => $user, + 'history' => $history, + 'sorting_types' => PlantsModel::$sorting_list, + 'sorting_dirs' => PlantsModel::$sorting_dir + ]); + } + + /** + * Handles URL: /plants/history/add + * + * @param Asatru\Controller\ControllerArg $request + * @return Asatru\View\RedirectHandler + */ + public function add_to_history($request) + { + try { + $plant = $request->params()->query('plant', null); + + PlantsModel::markHistorical($plant); + + return redirect('/history'); + } catch (\Exception $e) { + FlashMessage::setMsg('error', $e->getMessage()); + return back(); + } + } + + /** + * Handles URL: /plants/history/remove + * + * @param Asatru\Controller\ControllerArg $request + * @return Asatru\View\RedirectHandler + */ + public function remove_from_history($request) + { + try { + $plant = $request->params()->query('plant', null); + + PlantsModel::unmarkHistorical($plant); + + return redirect('/history'); + } catch (\Exception $e) { + FlashMessage::setMsg('error', $e->getMessage()); + return back(); + } + } } diff --git a/app/lang/de/app.php b/app/lang/de/app.php index 38d4cde..62b3f11 100644 --- a/app/lang/de/app.php +++ b/app/lang/de/app.php @@ -196,5 +196,11 @@ return [ 'admin_media' => 'Medien', 'media_logo' => 'Arbeitsraum Logo (.png image)', 'media_background' => 'Arbeitsraum Hintergrundbild (.jpg)', - 'media_saved' => 'Das Asset wurde erfolgreich gespeichert' + 'media_saved' => 'Das Asset wurde erfolgreich gespeichert', + 'enable_history' => 'Historie aktivieren', + 'history_name' => 'Name der Historie', + 'confirmPlantAddHistory' => 'Bitte diese Anweisung bestätigen.', + 'confirmPlantRemoveHistory' => 'Bitte diese Anweisung bestätigen.', + 'sorting_type_history_date' => 'Historien-Datum', + 'restore_from_history' => 'Wiederherstellen' ]; \ No newline at end of file diff --git a/app/lang/en/app.php b/app/lang/en/app.php index a213285..85347d1 100644 --- a/app/lang/en/app.php +++ b/app/lang/en/app.php @@ -196,5 +196,11 @@ return [ 'admin_media' => 'Media', 'media_logo' => 'Workspace logo (.png image)', 'media_background' => 'Workspace background image (.jpg)', - 'media_saved' => 'Media was saved successfully' + 'media_saved' => 'Media was saved successfully', + 'enable_history' => 'Enable history', + 'history_name' => 'History name', + 'confirmPlantAddHistory' => 'Please confirm this action', + 'confirmPlantRemoveHistory' => 'Please confirm this action', + 'sorting_type_history_date' => 'History date', + 'restore_from_history' => 'Restore' ]; \ No newline at end of file diff --git a/app/migrations/PlantsModel.php b/app/migrations/PlantsModel.php index a67721f..63d1c08 100644 --- a/app/migrations/PlantsModel.php +++ b/app/migrations/PlantsModel.php @@ -43,6 +43,8 @@ class PlantsModel_Migration { $this->database->add('light_level VARCHAR(512) NOT NULL'); $this->database->add('health_state VARCHAR(512) NOT NULL DEFAULT \'in_good_standing\''); $this->database->add('notes TEXT NULL'); + $this->database->add('history BOOLEAN NOT NULL DEFAULT 0'); + $this->database->add('history_date TIMESTAMP NULL'); $this->database->add('last_edited_user INT NULL'); $this->database->add('last_edited_date DATETIME NULL'); $this->database->add('created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP'); diff --git a/app/models/PlantsModel.php b/app/models/PlantsModel.php index d30c095..59fa35a 100644 --- a/app/models/PlantsModel.php +++ b/app/models/PlantsModel.php @@ -17,7 +17,8 @@ 'health_state', 'perennial', 'light_level', - 'humidity' + 'humidity', + 'history_date' ]; static $sorting_dir = [ @@ -68,7 +69,7 @@ static::validateSorting($sorting); static::validateDirection($direction); - return static::raw('SELECT * FROM `' . self::tableName() . '` WHERE location = ? ORDER BY ' . $sorting . ' ' . $direction, [$location]); + return static::raw('SELECT * FROM `' . self::tableName() . '` WHERE location = ? AND history = 0 ORDER BY ' . $sorting . ' ' . $direction, [$location]); } catch (\Exception $e) { throw $e; } @@ -115,6 +116,38 @@ } } + /** + * @param $limit + * @param $sorting + * @param $direction + * @return mixed + * @throws \Exception + */ + public static function getHistory($limit = null, $sorting = null, $direction = null) + { + try { + if ($sorting === null) { + $sorting = 'history_date'; + } + + if ($direction === null) { + $direction = 'desc'; + } + + static::validateSorting($sorting); + static::validateDirection($direction); + + $strlimit = ''; + if ($limit) { + $strlimit = ' LIMIT ' . $limit; + } + + return static::raw('SELECT * FROM `' . self::tableName() . '` WHERE history = 1 ORDER BY ' . $sorting . ' ' . $direction . $strlimit); + } catch (\Exception $e) { + throw $e; + } + } + /** * @param $name * @param $location @@ -264,7 +297,7 @@ public static function getCount() { try { - return static::raw('SELECT COUNT(*) as count FROM `' . self::tableName() . '`')->first()->get('count'); + return static::raw('SELECT COUNT(*) as count FROM `' . self::tableName() . '` WHERE history = 0')->first()->get('count'); } catch (\Exception $e) { throw $e; } @@ -355,6 +388,52 @@ } } + /** + * @param $plantId + * @return void + * @throws \Exception + */ + public static function markHistorical($plantId) + { + try { + $user = UserModel::getAuthUser(); + if (!$user) { + throw new \Exception('Invalid user'); + } + + $plant = PlantsModel::getDetails($plantId); + + static::raw('UPDATE `' . self::tableName() . '` SET history = 1, history_date = CURRENT_TIMESTAMP WHERE id = ?', [$plantId]); + + LogModel::addLog($user->get('id'), $plant->get('name'), 'mark_historical', ''); + } catch (\Exception $e) { + throw $e; + } + } + + /** + * @param $plantId + * @return void + * @throws \Exception + */ + public static function unmarkHistorical($plantId) + { + try { + $user = UserModel::getAuthUser(); + if (!$user) { + throw new \Exception('Invalid user'); + } + + $plant = PlantsModel::getDetails($plantId); + + static::raw('UPDATE `' . self::tableName() . '` SET history = 0, history_date = NULL WHERE id = ?', [$plantId]); + + LogModel::addLog($user->get('id'), $plant->get('name'), 'historical_restore', ''); + } catch (\Exception $e) { + throw $e; + } + } + /** * @param $plantId * @return void diff --git a/app/modules/UtilsModule.php b/app/modules/UtilsModule.php index 7c3c955..a6cbfec 100644 --- a/app/modules/UtilsModule.php +++ b/app/modules/UtilsModule.php @@ -265,10 +265,12 @@ class UtilsModule { * @param $onlinetimelimit * @param $chatonlineusers * @param $chattypingindicator + * @param $enablehistory + * @param $history_name * @param $cronpw * @return void */ - public static function saveEnvironment($workspace, $lang, $scroller, $enablechat, $onlinetimelimit, $chatonlineusers, $chattypingindicator, $cronpw) + public static function saveEnvironment($workspace, $lang, $scroller, $enablechat, $onlinetimelimit, $chatonlineusers, $chattypingindicator, $enablehistory, $history_name, $cronpw) { $new_env_settings = [ 'APP_WORKSPACE' => $workspace, @@ -278,6 +280,8 @@ class UtilsModule { 'APP_ONLINEMINUTELIMIT' => $onlinetimelimit, 'APP_SHOWCHATONLINEUSERS' => $chatonlineusers, 'APP_SHOWCHATTYPINGINDICATOR' => $chattypingindicator, + 'APP_ENABLEHISTORY' => $enablehistory, + 'APP_HISTORY_NAME' => $history_name, 'APP_CRONPW' => $cronpw ]; diff --git a/app/resources/js/app.js b/app/resources/js/app.js index 775a34c..b473882 100644 --- a/app/resources/js/app.js +++ b/app/resources/js/app.js @@ -44,6 +44,8 @@ window.vue = new Vue({ confirmPlantRemoval: 'Are you sure you want to remove this plant?', confirmSetAllWatered: 'Are you sure you want to update the last watered date of all these plants?', confirmInventoryItemRemoval: 'Are you sure you want to remove this item?', + confirmPlantAddHistory: 'Please confirm if you want to do this action.', + confirmPlantRemoveHistory: 'Please confirm if you want to do this action.', newChatMessage: 'New', currentlyOnline: 'Currently online: ', chatTypingEnable: false, @@ -196,6 +198,22 @@ window.vue = new Vue({ }); }, + markHistorical: function(plant) { + if (!confirm(window.vue.confirmPlantAddHistory)) { + return; + } + + location.href = window.location.origin + '/plants/history/add?plant=' + plant; + }, + + unmarkHistorical: function(plant) { + if (!confirm(window.vue.confirmPlantRemoveHistory)) { + return; + } + + location.href = window.location.origin + '/plants/history/remove?plant=' + plant; + }, + deletePlant: function(plant, retloc) { if (!confirm(window.vue.confirmPlantRemoval)) { @@ -516,5 +534,13 @@ window.vue = new Vue({ } } }, + + toggleDropdown: function(elem) { + if (elem.classList.contains('is-active')) { + elem.classList.remove('is-active'); + } else { + elem.classList.add('is-active'); + } + }, } }); \ No newline at end of file diff --git a/app/resources/sass/app.scss b/app/resources/sass/app.scss index fb8a883..6a39697 100644 --- a/app/resources/sass/app.scss +++ b/app/resources/sass/app.scss @@ -97,6 +97,10 @@ h2 { width: 100%; } +.is-pointer { + cursor: pointer; +} + .float-right { float: right; } @@ -346,13 +350,29 @@ a.navbar-burger:hover { } } -.plant-card-health-state { +.plant-card-title-with-hint { + padding-top: 7px; +} + +.plant-card-title-first { +} + +.plant-card-title-second { + color: rgb(150, 150, 150); + font-size: 0.8em; +} + +.plant-card-health-state, .plant-card-options { position: absolute; top: 7px; right: 8px; z-index: 2; } +.plant-card-options { + color: rgb(200, 200, 200); +} + .plant-card-health-state i { background-color: rgba(0, 0, 0, 0.5); padding: 5px; @@ -1405,4 +1425,4 @@ a.navbar-burger:hover { .version-info a:hover { color: rgb(132, 255, 123); text-decoration: underline; -} \ No newline at end of file +} diff --git a/app/views/admin.php b/app/views/admin.php index c55d73e..de91300 100644 --- a/app/views/admin.php +++ b/app/views/admin.php @@ -78,6 +78,19 @@ +
+
+  {{ __('app.enable_history') }} +
+
+ +
+ +
+ +
+
+
diff --git a/app/views/details.php b/app/views/details.php index 31eb4ba..8feafd0 100644 --- a/app/views/details.php +++ b/app/views/details.php @@ -230,7 +230,15 @@
- {{ __('app.remove_plant') }} + @if (env('APP_ENABLEHISTORY')) + + {{ env('APP_HISTORY_NAME') }}  + + @endif + + + {{ __('app.remove_plant') }}  +
diff --git a/app/views/history.php b/app/views/history.php new file mode 100644 index 0000000..41ac93d --- /dev/null +++ b/app/views/history.php @@ -0,0 +1,71 @@ +
+
+ +
+
+

{{ env('APP_HISTORY_NAME') }}

+ + + + @include('flashmsg.php') + +
+
+ +
+ +
+ +
+ +
+ +
+
+ +
+ @foreach ($history as $plant) +
+
+ + +
+
{{ $plant->get('name') }}
+
{{ date('Y-m-d', strtotime($plant->get('history_date'))) }}
+
+
+
+ @endforeach +
+
+
+ +
+
\ No newline at end of file diff --git a/app/views/layout.php b/app/views/layout.php index 5433a58..11e7aa5 100644 --- a/app/views/layout.php +++ b/app/views/layout.php @@ -780,6 +780,8 @@ window.vue.confirmPhotoRemoval = '{{ __('app.confirmPhotoRemoval') }}'; window.vue.confirmPlantRemoval = '{{ __('app.confirmPlantRemoval') }}'; + window.vue.confirmPlantAddHistory = '{{ __('app.confirmPlantAddHistory') }}'; + window.vue.confirmPlantRemoveHistory = '{{ __('app.confirmPlantRemoveHistory') }}'; window.vue.confirmSetAllWatered = '{{ __('app.confirmSetAllWatered') }}'; window.vue.confirmInventoryItemRemoval = '{{ __('app.confirmInventoryItemRemoval') }}'; window.vue.newChatMessage = '{{ __('app.new') }}'; diff --git a/app/views/navbar.php b/app/views/navbar.php index 8ba7ed0..8e2da0e 100644 --- a/app/views/navbar.php +++ b/app/views/navbar.php @@ -54,6 +54,14 @@
@endif + + @if (env('APP_ENABLEHISTORY')) + + @endif