From f85a67a1ffbab3890cc5e5aedcc44a2f92e09e62 Mon Sep 17 00:00:00 2001 From: Bernd Bestel Date: Sun, 23 Sep 2018 09:22:54 +0200 Subject: [PATCH] Continue working on tasks feature --- controllers/TasksController.php | 29 +++++++- grocy.openapi.json | 3 +- migrations/0036.sql | 8 +-- package.json | 2 + public/viewjs/taskcategories.js | 62 +++++++++++++++++ public/viewjs/taskcategoryform.js | 55 +++++++++++++++ public/viewjs/taskform.js | 4 +- public/viewjs/tasks.js | 98 ++++++++++++++------------- routes.php | 2 + services/DemoDataGeneratorService.php | 7 +- views/layout/default.blade.php | 6 ++ views/stockoverview.blade.php | 4 +- views/taskcategories.blade.php | 59 ++++++++++++++++ views/taskcategoryform.blade.php | 40 +++++++++++ views/taskform.blade.php | 12 +++- views/tasks.blade.php | 33 ++++++--- yarn.lock | 17 ++++- 17 files changed, 370 insertions(+), 71 deletions(-) create mode 100644 public/viewjs/taskcategories.js create mode 100644 public/viewjs/taskcategoryform.js create mode 100644 views/taskcategories.blade.php create mode 100644 views/taskcategoryform.blade.php diff --git a/controllers/TasksController.php b/controllers/TasksController.php index b08f0ad3..24999da5 100644 --- a/controllers/TasksController.php +++ b/controllers/TasksController.php @@ -18,13 +18,14 @@ class TasksController extends BaseController { return $this->AppContainer->view->render($response, 'tasks', [ 'tasks' => $this->Database->tasks()->orderBy('name'), - 'nextXDays' => 5 + 'nextXDays' => 5, + 'taskCategories' => $this->Database->task_categories()->orderBy('name') ]); } public function TaskEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) { - if ($args['taskdId'] == 'new') + if ($args['taskId'] == 'new') { return $this->AppContainer->view->render($response, 'taskform', [ 'mode' => 'create', @@ -40,4 +41,28 @@ class TasksController extends BaseController ]); } } + + public function TaskCategoriesList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) + { + return $this->AppContainer->view->render($response, 'taskcategories', [ + 'taskCategories' => $this->Database->task_categories()->orderBy('name') + ]); + } + + public function TaskCategoryEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) + { + if ($args['categoryId'] == 'new') + { + return $this->AppContainer->view->render($response, 'taskcategoryform', [ + 'mode' => 'create' + ]); + } + else + { + return $this->AppContainer->view->render($response, 'taskcategoryform', [ + 'category' => $this->Database->task_categories($args['categoryId']), + 'mode' => 'edit' + ]); + } + } } diff --git a/grocy.openapi.json b/grocy.openapi.json index ab62d917..65ce2f0d 100644 --- a/grocy.openapi.json +++ b/grocy.openapi.json @@ -1316,7 +1316,8 @@ "shopping_list", "recipes", "recipes_pos", - "tasks" + "tasks", + "task_categories" ] }, "StockTransactionType": { diff --git a/migrations/0036.sql b/migrations/0036.sql index dd9a5c91..0485a635 100644 --- a/migrations/0036.sql +++ b/migrations/0036.sql @@ -2,9 +2,9 @@ CREATE TABLE tasks ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, name TEXT NOT NULL UNIQUE, description TEXT, - due DATETIME, - started TINYINT NOT NULL DEFAULT 0 CHECK(started IN (0, 1)), + due_date DATETIME, done TINYINT NOT NULL DEFAULT 0 CHECK(done IN (0, 1)), + done_date DATETIME, category_id INTEGER, row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) ); @@ -20,5 +20,5 @@ CREATE VIEW tasks_current AS SELECT * FROM tasks -WHERE due IS NULL - OR (due IS NOT NULL AND due > datetime('now', 'localtime')); +WHERE due_date IS NULL + OR (due_date IS NOT NULL AND due_date > datetime('now', 'localtime')); diff --git a/package.json b/package.json index 09d2aeff..e7498946 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,8 @@ "datatables.net-responsive-bs4": "^2.2.3", "datatables.net-select": "^1.2.7", "datatables.net-select-bs4": "^1.2.7", + "datatables.net-rowgroup": "^1.0.4", + "datatables.net-rowgroup-bs4": "^1.0.4", "jquery": "^3.3.1", "jquery-serializejson": "^2.8.1", "jquery-ui-dist": "^1.12.1", diff --git a/public/viewjs/taskcategories.js b/public/viewjs/taskcategories.js new file mode 100644 index 00000000..d182c2a1 --- /dev/null +++ b/public/viewjs/taskcategories.js @@ -0,0 +1,62 @@ +var categoriesTable = $('#task-categories-table').DataTable({ + 'paginate': false, + 'order': [[1, 'asc']], + 'columnDefs': [ + { 'orderable': false, 'targets': 0 } + ], + 'language': JSON.parse(L('datatables_localization')), + 'scrollY': false, + 'colReorder': true, + 'stateSave': true, + 'stateSaveParams': function(settings, data) + { + data.search.search = ""; + } +}); + +$("#search").on("keyup", function() +{ + var value = $(this).val(); + if (value === "all") + { + value = ""; + } + + categoriesTable.search(value).draw(); +}); + +$(document).on('click', '.task-category-delete-button', function (e) +{ + var objectName = $(e.currentTarget).attr('data-category-name'); + var objectId = $(e.currentTarget).attr('data-category-id'); + + bootbox.confirm({ + message: L('Are you sure to delete task category "#1"?', objectName), + buttons: { + confirm: { + label: L('Yes'), + className: 'btn-success' + }, + cancel: { + label: L('No'), + className: 'btn-danger' + } + }, + callback: function(result) + { + if (result === true) + { + Grocy.Api.Get('delete-object/task_categories/' + objectId, + function(result) + { + window.location.href = U('/taskcategories'); + }, + function(xhr) + { + console.error(xhr); + } + ); + } + } + }); +}); diff --git a/public/viewjs/taskcategoryform.js b/public/viewjs/taskcategoryform.js new file mode 100644 index 00000000..00448648 --- /dev/null +++ b/public/viewjs/taskcategoryform.js @@ -0,0 +1,55 @@ +$('#save-task-category-button').on('click', function(e) +{ + e.preventDefault(); + + if (Grocy.EditMode === 'create') + { + Grocy.Api.Post('add-object/task_categories', $('#task-category-form').serializeJSON(), + function(result) + { + window.location.href = U('/taskcategories'); + }, + function(xhr) + { + Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response) + } + ); + } + else + { + Grocy.Api.Post('edit-object/task_categories/' + Grocy.EditObjectId, $('#task-category-form').serializeJSON(), + function(result) + { + window.location.href = U('/taskcategories'); + }, + function(xhr) + { + Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response) + } + ); + } +}); + +$('#task-category-form input').keyup(function (event) +{ + Grocy.FrontendHelpers.ValidateForm('task-category-form'); +}); + +$('#task-category-form input').keydown(function (event) +{ + if (event.keyCode === 13) //Enter + { + if (document.getElementById('task-category-form').checkValidity() === false) //There is at least one validation error + { + event.preventDefault(); + return false; + } + else + { + $('#save-task-category-button').click(); + } + } +}); + +$('#name').focus(); +Grocy.FrontendHelpers.ValidateForm('task-category-form'); diff --git a/public/viewjs/taskform.js b/public/viewjs/taskform.js index 3fd01dd0..547f4987 100644 --- a/public/viewjs/taskform.js +++ b/public/viewjs/taskform.js @@ -30,12 +30,12 @@ } }); -$('#task-form input').keyup(function (event) +$('#task-form input').keyup(function(event) { Grocy.FrontendHelpers.ValidateForm('task-form'); }); -$('#task-form input').keydown(function (event) +$('#task-form input').keydown(function(event) { if (event.keyCode === 13) //Enter { diff --git a/public/viewjs/tasks.js b/public/viewjs/tasks.js index 868dca0f..7315041c 100644 --- a/public/viewjs/tasks.js +++ b/public/viewjs/tasks.js @@ -2,7 +2,8 @@ 'paginate': false, 'order': [[2, 'desc']], 'columnDefs': [ - { 'orderable': false, 'targets': 0 } + { 'orderable': false, 'targets': 0 }, + { 'visible': false, 'targets': 3 } ], 'language': JSON.parse(L('datatables_localization')), 'scrollY': false, @@ -11,6 +12,9 @@ 'stateSaveParams': function(settings, data) { data.search.search = ""; + }, + 'rowGroup': { + dataSrc: 3 } }); @@ -28,56 +32,15 @@ $("#search").on("keyup", function() $(document).on('click', '.do-task-button', function(e) { var taskId = $(e.currentTarget).attr('data-task-id'); + var taskName = $(e.currentTarget).attr('data-task-name'); var doneTime = moment().format('YYYY-MM-DD HH:mm:ss'); - Grocy.Api.Get('tasks/track-task-execution/' + taskId + '?tracked_time=' + trackedTime, + Grocy.Api.Get('tasks/mark-task-done/' + taskId + '?tracked_time=' + doneTime, function() { - Grocy.Api.Get('tasks/get-task-details/' + taskId, - function(result) - { - var taskRow = $('#task-' + taskId + '-row'); - var nextXDaysThreshold = moment().add($("#info-due-tasks").data("next-x-days"), "days"); - var now = moment(); - var nextExecutionTime = moment(result.next_estimated_execution_time); - - taskRow.removeClass("table-warning"); - taskRow.removeClass("table-danger"); - if (nextExecutionTime.isBefore(now)) - { - taskRow.addClass("table-danger"); - } - else if (nextExecutionTime.isBefore(nextXDaysThreshold)) - { - taskRow.addClass("table-warning"); - } - - $('#task-' + taskId + '-last-tracked-time').parent().effect('highlight', { }, 500); - $('#task-' + taskId + '-last-tracked-time').fadeOut(500, function() - { - $(this).text(trackedTime).fadeIn(500); - }); - $('#task-' + taskId + '-last-tracked-time-timeago').attr('datetime', trackedTime); - - if (result.task.period_type == "dynamic-regular") - { - $('#task-' + taskId + '-next-execution-time').parent().effect('highlight', { }, 500); - $('#task-' + taskId + '-next-execution-time').fadeOut(500, function() - { - $(this).text(result.next_estimated_execution_time).fadeIn(500); - }); - $('#task-' + taskId + '-next-execution-time-timeago').attr('datetime', result.next_estimated_execution_time); - } - - toastr.success(L('Tracked execution of task #1 on #2', taskName, trackedTime)); - RefreshContextualTimeago(); - RefreshStatistics(); - }, - function(xhr) - { - console.error(xhr); - } - ); + toastr.success(L('Marked task #1 as completed on #2', taskName, doneTime)); + RefreshContextualTimeago(); + RefreshStatistics(); }, function(xhr) { @@ -86,6 +49,45 @@ $(document).on('click', '.do-task-button', function(e) ); }); +$(document).on('click', '.delete-task-button', function (e) +{ + var objectName = $(e.currentTarget).attr('data-task-name'); + var objectId = $(e.currentTarget).attr('data-task-id'); + + bootbox.confirm({ + message: L('Are you sure to delete task "#1"?', objectName), + buttons: { + confirm: { + label: L('Yes'), + className: 'btn-success' + }, + cancel: { + label: L('No'), + className: 'btn-danger' + } + }, + callback: function(result) + { + if (result === true) + { + Grocy.Api.Get('delete-object/tasks/' + objectId, + function(result) + { + $('#task-' + objectId + '-row').fadeOut(500, function () + { + $(this).remove(); + }); + }, + function(xhr) + { + console.error(xhr); + } + ); + } + } + }); +}); + function RefreshStatistics() { var nextXDays = $("#info-due-tasks").data("next-x-days"); @@ -97,7 +99,7 @@ function RefreshStatistics() var now = moment(); var nextXDaysThreshold = moment().add(nextXDays, "days"); result.forEach(element => { - var date = moment(element.due); + var date = moment(element.due_date); if (date.isBefore(now)) { overdueCount++; diff --git a/routes.php b/routes.php index f6c36585..d6636b84 100644 --- a/routes.php +++ b/routes.php @@ -56,6 +56,8 @@ $app->group('', function() // Task routes $this->get('/tasks', '\Grocy\Controllers\TasksController:Overview'); $this->get('/task/{taskId}', '\Grocy\Controllers\TasksController:TaskEditForm'); + $this->get('/taskcategories', '\Grocy\Controllers\TasksController:TaskCategoriesList'); + $this->get('/taskcategory/{categoryId}', '\Grocy\Controllers\TasksController:TaskCategoryEditForm'); // OpenAPI routes $this->get('/api', '\Grocy\Controllers\OpenApiController:DocumentationUi'); diff --git a/services/DemoDataGeneratorService.php b/services/DemoDataGeneratorService.php index f86103e3..e8bffbde 100644 --- a/services/DemoDataGeneratorService.php +++ b/services/DemoDataGeneratorService.php @@ -91,7 +91,12 @@ class DemoDataGeneratorService extends BaseService INSERT INTO task_categories (name) VALUES ('{$localizationService->Localize('Life')}'); --2 INSERT INTO task_categories (name) VALUES ('{$localizationService->Localize('Projects')}'); --3 - INSERT INTO tasks (name, category_id, due) VALUES ('{$localizationService->Localize('Repair the garage door')}', 1, date(datetime('now', 'localtime'), '+14 day')); + INSERT INTO tasks (name, category_id, due_date) VALUES ('{$localizationService->Localize('Repair the garage door')}', 1, date(datetime('now', 'localtime'), '+14 day')); + INSERT INTO tasks (name, category_id, due_date) VALUES ('{$localizationService->Localize('Task2')}', 1, date(datetime('now', 'localtime'), '+14 day')); + INSERT INTO tasks (name, category_id, due_date) VALUES ('{$localizationService->Localize('Task3')}', 2, date(datetime('now', 'localtime'), '-1 day')); + INSERT INTO tasks (name, category_id, due_date) VALUES ('{$localizationService->Localize('Task4')}', 2, date(datetime('now', 'localtime'), '-1 day')); + INSERT INTO tasks (name, due_date) VALUES ('{$localizationService->Localize('Task5')}', date(datetime('now', 'localtime'), '+3 day')); + INSERT INTO tasks (name, due_date) VALUES ('{$localizationService->Localize('Task6')}', date(datetime('now', 'localtime'), '+4 day')); INSERT INTO migrations (migration) VALUES (-1); "; diff --git a/views/layout/default.blade.php b/views/layout/default.blade.php index bbdeb921..1fc93186 100644 --- a/views/layout/default.blade.php +++ b/views/layout/default.blade.php @@ -158,6 +158,12 @@ {{ $L('Batteries') }} +
  • + + + {{ $L('Task categories') }} + +
  • diff --git a/views/stockoverview.blade.php b/views/stockoverview.blade.php index 2a4f5306..6f7871f8 100644 --- a/views/stockoverview.blade.php +++ b/views/stockoverview.blade.php @@ -43,7 +43,7 @@ {{ $L('Product') }} {{ $L('Amount') }} {{ $L('Next best before date') }} - Hidden location + Hidden location @@ -75,7 +75,7 @@ {{ $currentStockEntry->best_before_date }} - + {{ FindObjectInArrayByPropertyValue($locations, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->location_id)->name }} diff --git a/views/taskcategories.blade.php b/views/taskcategories.blade.php new file mode 100644 index 00000000..3ef055ba --- /dev/null +++ b/views/taskcategories.blade.php @@ -0,0 +1,59 @@ +@extends('layout.default') + +@section('title', $L('Task categories')) +@section('activeNav', 'taskcategories') +@section('viewJsName', 'taskcategories') + +@section('content') +
    +
    +

    + @yield('title') + +  {{ $L('Add') }} + +

    +
    +
    + +
    +
    + + +
    +
    + +
    +
    + + + + + + + + + + @foreach($taskCategories as $taskCategory) + + + + + + @endforeach + +
    #{{ $L('Name') }}{{ $L('Description') }}
    + + + + + + + + {{ $taskCategory->name }} + + {{ $taskCategory->description }} +
    +
    +
    +@stop diff --git a/views/taskcategoryform.blade.php b/views/taskcategoryform.blade.php new file mode 100644 index 00000000..8c3341f8 --- /dev/null +++ b/views/taskcategoryform.blade.php @@ -0,0 +1,40 @@ +@extends('layout.default') + +@if($mode == 'edit') + @section('title', $L('Edit task category')) +@else + @section('title', $L('Create task category')) +@endif + +@section('viewJsName', 'taskcategoryform') + +@section('content') +
    +
    +

    @yield('title')

    + + + + @if($mode == 'edit') + + @endif + +
    + +
    + + +
    {{ $L('A name is required') }}
    +
    + +
    + + +
    + + + +
    +
    +
    +@stop diff --git a/views/taskform.blade.php b/views/taskform.blade.php index 76b5354b..e1c019df 100644 --- a/views/taskform.blade.php +++ b/views/taskform.blade.php @@ -32,14 +32,22 @@ + @php + $initialDueDate = null; + if ($mode == 'edit') + { + $initialDueDate = date('Y-m-d', strtotime($task->due_date)); + } + @endphp @include('components.datetimepicker', array( - 'id' => 'due', + 'id' => 'due_date', 'label' => 'Due', 'format' => 'YYYY-MM-DD', 'initWithNow' => false, + 'initialValue' => $initialDueDate, 'limitEndToNow' => false, 'limitStartToNow' => false, - 'invalidFeedback' => $L('A due dat is required'), + 'invalidFeedback' => $L('A due date is required'), 'nextInputSelector' => '', 'additionalCssClasses' => 'date-only-datetimepicker', 'isRequired' => false diff --git a/views/tasks.blade.php b/views/tasks.blade.php index 19df32ea..6cbcb83f 100644 --- a/views/tasks.blade.php +++ b/views/tasks.blade.php @@ -6,12 +6,23 @@ @push('pageScripts') + + +@endpush + +@push('pageStyles') + @endpush @section('content')
    -

    @yield('title')

    +

    + @yield('title') + + {{ $L('Add') }} + +

    @@ -32,19 +43,22 @@ # {{ $L('Aufgabe') }} {{ $L('Due') }} + Hidden category @foreach($tasks as $task) - + name) }}" - data-task-id="{{ $task->id }}"> + data-task-id="{{ $task->id }}" + data-task-name="{{ $task->name }}"> - name) }}" - data-task-id="{{ $task->id }}"> - + + @@ -54,8 +68,11 @@ {{ $task->name }} - {{ $task->due }} - + {{ $task->due_date }} + + + + @if($task->category_id !== null) {{ FindObjectInArrayByPropertyValue($taskCategories, 'id', $task->category_id)->name }} @else {{ $L('Uncategorized') }}@endif @endforeach diff --git a/yarn.lock b/yarn.lock index 9ed06f62..d7ee2d0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,7 +10,7 @@ version "5.3.1" resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.3.1.tgz#5466b8f31c1f493a96754c1426c25796d0633dd9" -"TagManager@https://github.com/max-favilli/tagmanager.git#3.0.2": +"TagManager@https://github.com/max-favilli/tagmanager.git#3.0.2", "tagmanager@https://github.com/max-favilli/tagmanager.git#3.0.2": version "3.0.1" resolved "https://github.com/max-favilli/tagmanager.git#df9eb9935c8585a392dfc00602f890caf233fa94" dependencies: @@ -107,6 +107,21 @@ datatables.net-responsive@2.2.3, datatables.net-responsive@^2.2.3: datatables.net "^1.10.15" jquery ">=1.7" +datatables.net-rowgroup-bs4@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/datatables.net-rowgroup-bs4/-/datatables.net-rowgroup-bs4-1.0.4.tgz#602f056f9a60bab1b3ac3a36088636f40156b05a" + dependencies: + datatables.net-bs4 "^1.10.15" + datatables.net-rowgroup "1.0.4" + jquery ">=1.7" + +datatables.net-rowgroup@1.0.4, datatables.net-rowgroup@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/datatables.net-rowgroup/-/datatables.net-rowgroup-1.0.4.tgz#2caf979f28747be7d9ab66725b639b73099d8eb0" + dependencies: + datatables.net "^1.10.15" + jquery ">=1.7" + datatables.net-select-bs4@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/datatables.net-select-bs4/-/datatables.net-select-bs4-1.2.7.tgz#5e4ddd8feb412e974b54a15e81b2bb29840ba55b"