Renamed habits to chores as this is more what it is about

This commit is contained in:
Bernd Bestel
2018-09-22 13:26:58 +02:00
parent bd3c63218b
commit 4075067a10
31 changed files with 653 additions and 622 deletions

View File

@@ -2,19 +2,19 @@
namespace Grocy\Controllers; namespace Grocy\Controllers;
use \Grocy\Services\HabitsService; use \Grocy\Services\ChoresService;
class HabitsApiController extends BaseApiController class ChoresApiController extends BaseApiController
{ {
public function __construct(\Slim\Container $container) public function __construct(\Slim\Container $container)
{ {
parent::__construct($container); parent::__construct($container);
$this->HabitsService = new HabitsService(); $this->ChoresService = new ChoresService();
} }
protected $HabitsService; protected $ChoresService;
public function TrackHabitExecution(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) public function TrackChoreExecution(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{ {
$trackedTime = date('Y-m-d H:i:s'); $trackedTime = date('Y-m-d H:i:s');
if (isset($request->getQueryParams()['tracked_time']) && !empty($request->getQueryParams()['tracked_time']) && IsIsoDateTime($request->getQueryParams()['tracked_time'])) if (isset($request->getQueryParams()['tracked_time']) && !empty($request->getQueryParams()['tracked_time']) && IsIsoDateTime($request->getQueryParams()['tracked_time']))
@@ -30,7 +30,7 @@ class HabitsApiController extends BaseApiController
try try
{ {
$this->HabitsService->TrackHabit($args['habitId'], $trackedTime, $doneBy); $this->ChoresService->TrackChore($args['choreId'], $trackedTime, $doneBy);
return $this->VoidApiActionResponse($response); return $this->VoidApiActionResponse($response);
} }
catch (\Exception $ex) catch (\Exception $ex)
@@ -39,11 +39,11 @@ class HabitsApiController extends BaseApiController
} }
} }
public function HabitDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) public function ChoreDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{ {
try try
{ {
return $this->ApiResponse($this->HabitsService->GetHabitDetails($args['habitId'])); return $this->ApiResponse($this->ChoresService->GetChoreDetails($args['choreId']));
} }
catch (\Exception $ex) catch (\Exception $ex)
{ {
@@ -53,6 +53,6 @@ class HabitsApiController extends BaseApiController
public function Current(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) public function Current(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{ {
return $this->ApiResponse($this->HabitsService->GetCurrent()); return $this->ApiResponse($this->ChoresService->GetCurrent());
} }
} }

View File

@@ -0,0 +1,68 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\ChoresService;
class ChoresController extends BaseController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->ChoresService = new ChoresService();
}
protected $ChoresService;
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'choresoverview', [
'chores' => $this->Database->chores()->orderBy('name'),
'currentChores' => $this->ChoresService->GetCurrent(),
'nextXDays' => 5
]);
}
public function TrackChoreExecution(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'choretracking', [
'chores' => $this->Database->chores()->orderBy('name'),
'users' => $this->Database->users()->orderBy('username')
]);
}
public function ChoresList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'chores', [
'chores' => $this->Database->chores()->orderBy('name')
]);
}
public function Analysis(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'choresanalysis', [
'choresLog' => $this->Database->chores_log()->orderBy('tracked_time', 'DESC'),
'chores' => $this->Database->chores()->orderBy('name'),
'users' => $this->Database->users()->orderBy('username')
]);
}
public function ChoreEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($args['choredId'] == 'new')
{
return $this->AppContainer->view->render($response, 'choreform', [
'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService'),
'mode' => 'create'
]);
}
else
{
return $this->AppContainer->view->render($response, 'choreform', [
'chore' => $this->Database->chores($args['choreId']),
'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService'),
'mode' => 'edit'
]);
}
}
}

View File

@@ -1,68 +0,0 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\HabitsService;
class HabitsController extends BaseController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->HabitsService = new HabitsService();
}
protected $HabitsService;
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'habitsoverview', [
'habits' => $this->Database->habits()->orderBy('name'),
'currentHabits' => $this->HabitsService->GetCurrent(),
'nextXDays' => 5
]);
}
public function TrackHabitExecution(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'habittracking', [
'habits' => $this->Database->habits()->orderBy('name'),
'users' => $this->Database->users()->orderBy('username')
]);
}
public function HabitsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'habits', [
'habits' => $this->Database->habits()->orderBy('name')
]);
}
public function Analysis(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'habitsanalysis', [
'habitsLog' => $this->Database->habits_log()->orderBy('tracked_time', 'DESC'),
'habits' => $this->Database->habits()->orderBy('name'),
'users' => $this->Database->users()->orderBy('username')
]);
}
public function HabitEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($args['habitId'] == 'new')
{
return $this->AppContainer->view->render($response, 'habitform', [
'periodTypes' => GetClassConstants('\Grocy\Services\HabitsService'),
'mode' => 'create'
]);
}
else
{
return $this->AppContainer->view->render($response, 'habitform', [
'habit' => $this->Database->habits($args['habitId']),
'periodTypes' => GetClassConstants('\Grocy\Services\HabitsService'),
'mode' => 'edit'
]);
}
}
}

View File

@@ -54,7 +54,7 @@
"$ref": "#/components/schemas/Product" "$ref": "#/components/schemas/Product"
}, },
{ {
"$ref": "#/components/schemas/Habit" "$ref": "#/components/schemas/Chore"
}, },
{ {
"$ref": "#/components/schemas/Battery" "$ref": "#/components/schemas/Battery"
@@ -128,7 +128,7 @@
"$ref": "#/components/schemas/Product" "$ref": "#/components/schemas/Product"
}, },
{ {
"$ref": "#/components/schemas/Habit" "$ref": "#/components/schemas/Chore"
}, },
{ {
"$ref": "#/components/schemas/Battery" "$ref": "#/components/schemas/Battery"
@@ -191,7 +191,7 @@
"$ref": "#/components/schemas/Product" "$ref": "#/components/schemas/Product"
}, },
{ {
"$ref": "#/components/schemas/Habit" "$ref": "#/components/schemas/Chore"
}, },
{ {
"$ref": "#/components/schemas/Battery" "$ref": "#/components/schemas/Battery"
@@ -274,7 +274,7 @@
"$ref": "#/components/schemas/Product" "$ref": "#/components/schemas/Product"
}, },
{ {
"$ref": "#/components/schemas/Habit" "$ref": "#/components/schemas/Chore"
}, },
{ {
"$ref": "#/components/schemas/Battery" "$ref": "#/components/schemas/Battery"
@@ -1041,18 +1041,18 @@
} }
} }
}, },
"/habits/track-habit-execution/{habitId}": { "/chores/track-chore-execution/{choreId}": {
"get": { "get": {
"description": "Tracks an execution of the given habit", "description": "Tracks an execution of the given chore",
"tags": [ "tags": [
"Habits" "Chores"
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "habitId", "name": "choreId",
"required": true, "required": true,
"description": "A valid habit id", "description": "A valid chore id",
"schema": { "schema": {
"type": "integer" "type": "integer"
} }
@@ -1061,7 +1061,7 @@
"in": "query", "in": "query",
"name": "tracked_time", "name": "tracked_time",
"required": false, "required": false,
"description": "The time of when the habit was executed, when omitted, the current time is used", "description": "The time of when the chore was executed, when omitted, the current time is used",
"schema": { "schema": {
"type": "date-time" "type": "date-time"
} }
@@ -1070,7 +1070,7 @@
"in": "query", "in": "query",
"name": "done_by", "name": "done_by",
"required": false, "required": false,
"description": "A valid user id of who executed this habit, when omitted, the currently authenticated user will be used", "description": "A valid user id of who executed this chore, when omitted, the currently authenticated user will be used",
"schema": { "schema": {
"type": "integer" "type": "integer"
} }
@@ -1088,7 +1088,7 @@
} }
}, },
"400": { "400": {
"description": "A VoidApiActionResponse object (possible errors are: Not existing habit)", "description": "A VoidApiActionResponse object (possible errors are: Not existing chore)",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
@@ -1100,18 +1100,18 @@
} }
} }
}, },
"/habits/get-habit-details/{habitId}": { "/chores/get-chore-details/{choreId}": {
"get": { "get": {
"description": "Returns details of the given habit", "description": "Returns details of the given chore",
"tags": [ "tags": [
"Habits" "Chores"
], ],
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "habitId", "name": "choreId",
"required": true, "required": true,
"description": "A valid habit id", "description": "A valid chore id",
"schema": { "schema": {
"type": "integer" "type": "integer"
} }
@@ -1119,17 +1119,17 @@
], ],
"responses": { "responses": {
"200": { "200": {
"description": "A HabitDetailsResponse object", "description": "A ChoreDetailsResponse object",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"$ref": "#/components/schemas/HabitDetailsResponse" "$ref": "#/components/schemas/ChoreDetailsResponse"
} }
} }
} }
}, },
"400": { "400": {
"description": "A VoidApiActionResponse object (possible errors are: Not existing habit)", "description": "A VoidApiActionResponse object (possible errors are: Not existing chore)",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
@@ -1141,21 +1141,21 @@
} }
} }
}, },
"/habits/get-current": { "/chores/get-current": {
"get": { "get": {
"description": "Returns all habits incl. the next estimated execution time per habit", "description": "Returns all chores incl. the next estimated execution time per chore",
"tags": [ "tags": [
"Habits" "Chores"
], ],
"responses": { "responses": {
"200": { "200": {
"description": "An array of CurrentHabitResponse objects", "description": "An array of CurrentChoreResponse objects",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/components/schemas/CurrentHabitResponse" "$ref": "#/components/schemas/CurrentChoreResponse"
} }
} }
} }
@@ -1286,7 +1286,7 @@
"type": "string", "type": "string",
"enum": [ "enum": [
"products", "products",
"habits", "chores",
"batteries", "batteries",
"locations", "locations",
"quantity_units", "quantity_units",
@@ -1494,20 +1494,20 @@
} }
} }
}, },
"HabitDetailsResponse": { "ChoreDetailsResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
"habit": { "chore": {
"$ref": "#/components/schemas/Habit" "$ref": "#/components/schemas/Chore"
}, },
"last_tracked": { "last_tracked": {
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"description": "When this habit was last tracked" "description": "When this chore was last tracked"
}, },
"track_count": { "track_count": {
"type": "integer", "type": "integer",
"description": "How often this habit was tracked so far" "description": "How often this chore was tracked so far"
}, },
"last_done_by": { "last_done_by": {
"$ref": "#/components/schemas/UserDto" "$ref": "#/components/schemas/UserDto"
@@ -1521,7 +1521,7 @@
"BatteryDetailsResponse": { "BatteryDetailsResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
"habit": { "chore": {
"$ref": "#/components/schemas/Battery" "$ref": "#/components/schemas/Battery"
}, },
"last_charged": { "last_charged": {
@@ -1709,7 +1709,7 @@
} }
} }
}, },
"Habit": { "Chore": {
"type": "object", "type": "object",
"properties": { "properties": {
"id": { "id": {
@@ -1737,13 +1737,13 @@
} }
} }
}, },
"HabitLogEntry": { "ChoreLogEntry": {
"type": "object", "type": "object",
"properties": { "properties": {
"id": { "id": {
"type": "integer" "type": "integer"
}, },
"habit_id": { "chore_id": {
"type": "integer" "type": "integer"
}, },
"tracked_time": { "tracked_time": {
@@ -1842,10 +1842,10 @@
} }
} }
}, },
"CurrentHabitResponse": { "CurrentChoreResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
"habit_id": { "chore_id": {
"type": "integer" "type": "integer"
}, },
"last_tracked_time": { "last_tracked_time": {
@@ -1855,7 +1855,7 @@
"next_estimated_execution_time": { "next_estimated_execution_time": {
"type": "string", "type": "string",
"format": "date-time", "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" "description": "The next estimated execution time of this chore, 2999-12-31 23:59:59 when the given chore has a period_type of manually"
} }
} }
}, },

View File

@@ -9,20 +9,20 @@ return array(
'Amount' => 'Menge', 'Amount' => 'Menge',
'Next best before date' => 'Nächstes MHD', 'Next best before date' => 'Nächstes MHD',
'Logout' => 'Abmelden', 'Logout' => 'Abmelden',
'Habits overview' => 'Gewohnheiten', 'Chores overview' => 'Hausarbeiten',
'Batteries overview' => 'Batterien', 'Batteries overview' => 'Batterien',
'Purchase' => 'Einkauf', 'Purchase' => 'Einkauf',
'Consume' => 'Verbrauch', 'Consume' => 'Verbrauch',
'Inventory' => 'Inventur', 'Inventory' => 'Inventur',
'Shopping list' => 'Einkaufszettel', 'Shopping list' => 'Einkaufszettel',
'Habit tracking' => 'Gewohnheit-Ausführung', 'Chore tracking' => 'Hausarbeiten-Ausführung',
'Battery tracking' => 'Batterie-Ladzyklus', 'Battery tracking' => 'Batterie-Ladzyklus',
'Products' => 'Produkte', 'Products' => 'Produkte',
'Locations' => 'Standorte', 'Locations' => 'Standorte',
'Quantity units' => 'Mengeneinheiten', 'Quantity units' => 'Mengeneinheiten',
'Habits' => 'Gewohnheiten', 'Chores' => 'Hausarbeiten',
'Batteries' => 'Batterien', 'Batteries' => 'Batterien',
'Habit' => 'Gewohnheit', 'Chore' => 'Hausarbeit',
'Next estimated tracking' => 'Nächste geplante Ausführung', 'Next estimated tracking' => 'Nächste geplante Ausführung',
'Last tracked' => 'Zuletzt ausgeführt', 'Last tracked' => 'Zuletzt ausgeführt',
'Battery' => 'Batterie', 'Battery' => 'Batterie',
@@ -41,7 +41,7 @@ return array(
'New amount' => 'Neue Menge', 'New amount' => 'Neue Menge',
'Note' => 'Notiz', 'Note' => 'Notiz',
'Tracked time' => 'Ausführungszeit', 'Tracked time' => 'Ausführungszeit',
'Habit overview' => 'Gewohnheit Übersicht', 'Chore overview' => 'Hausarbeit Übersicht',
'Tracked count' => 'Ausführungsanzahl', 'Tracked count' => 'Ausführungsanzahl',
'Battery overview' => 'Batterie Übersicht', 'Battery overview' => 'Batterie Übersicht',
'Charge cycles count' => 'Ladezyklen', 'Charge cycles count' => 'Ladezyklen',
@@ -68,11 +68,11 @@ return array(
'Create quantity unit' => 'Mengeneinheit erstellen', 'Create quantity unit' => 'Mengeneinheit erstellen',
'Period type' => 'Periodentyp', 'Period type' => 'Periodentyp',
'Period days' => 'Tage/Periode', 'Period days' => 'Tage/Periode',
'Create habit' => 'Gewohnheit erstellen', 'Create chore' => 'Hausarbeit erstellen',
'Used in' => 'Benutzt in', 'Used in' => 'Benutzt in',
'Create battery' => 'Batterie erstellen', 'Create battery' => 'Batterie erstellen',
'Edit battery' => 'Batterie bearbeiten', 'Edit battery' => 'Batterie bearbeiten',
'Edit habit' => 'Gewohnheit bearbeiten', 'Edit chore' => 'Hausarbeit bearbeiten',
'Edit quantity unit' => 'Mengeneinheit bearbeiten', 'Edit quantity unit' => 'Mengeneinheit bearbeiten',
'Edit product' => 'Produkt bearbeiten', 'Edit product' => 'Produkt bearbeiten',
'Edit location' => 'Standort bearbeiten', 'Edit location' => 'Standort bearbeiten',
@@ -90,7 +90,7 @@ return array(
'Are you sure to delete battery "#1"?' => 'Battery "#1" wirklich löschen?', 'Are you sure to delete battery "#1"?' => 'Battery "#1" wirklich löschen?',
'Yes' => 'Ja', 'Yes' => 'Ja',
'No' => 'Nein', 'No' => 'Nein',
'Are you sure to delete habit "#1"?' => 'Gewohnheit "#1" wirklich löschen?', 'Are you sure to delete chore "#1"?' => 'Hausarbeit "#1" wirklich löschen?',
'"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" konnte nicht zu einem Produkt aufgelöst werden, wie möchtest du weiter machen?', '"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" konnte nicht zu einem Produkt aufgelöst werden, wie möchtest du weiter machen?',
'Create or assign product' => 'Produkt erstellen oder verknüpfen', 'Create or assign product' => 'Produkt erstellen oder verknüpfen',
'Cancel' => 'Abbrechen', 'Cancel' => 'Abbrechen',
@@ -110,29 +110,29 @@ return array(
'This product is not in stock' => 'Dieses Produkt ist nicht vorrätig', 'This product is not in stock' => 'Dieses Produkt ist nicht vorrätig',
'This means #1 will be added to stock' => 'Das bedeutet #1 wird dem Bestand hinzugefügt', 'This means #1 will be added to stock' => 'Das bedeutet #1 wird dem Bestand hinzugefügt',
'This means #1 will be removed from stock' => 'Das bedeutet #1 wird aus dem Bestand entfernt', 'This means #1 will be removed from stock' => 'Das bedeutet #1 wird aus dem Bestand entfernt',
'This means it is estimated that a new execution of this habit is tracked #1 days after the last was tracked' => 'Das bedeutet, dass eine erneute Ausführung der Gewohnheit #1 Tage nach der letzten Ausführung geplant wird', 'This means it is estimated that a new execution of this chore is tracked #1 days after the last was tracked' => 'Das bedeutet, dass eine erneute Ausführung der Hausarbeit #1 Tage nach der letzten Ausführung geplant wird',
'Removed #1 #2 of #3 from stock' => '#1 #2 #3 aus dem Bestand entfernt', 'Removed #1 #2 of #3 from stock' => '#1 #2 #3 aus dem Bestand entfernt',
'About grocy' => 'Über grocy', 'About grocy' => 'Über grocy',
'Close' => 'Schließen', 'Close' => 'Schließen',
'#1 batteries are due to be charged within the next #2 days' => '#1 Batterien müssen in den nächsten #2 Tagen geladen werden', '#1 batteries are due to be charged within the next #2 days' => '#1 Batterien müssen in den nächsten #2 Tagen geladen werden',
'#1 batteries are overdue to be charged' => '#1 Batterien sind überfällig', '#1 batteries are overdue to be charged' => '#1 Batterien sind überfällig',
'#1 habits are due to be done within the next #2 days' => '#1 Gewohnheiten stehen in den nächsten #2 Tagen an', '#1 chores are due to be done within the next #2 days' => '#1 Hausarbeiten stehen in den nächsten #2 Tagen an',
'#1 habits are overdue to be done' => '#1 Gewohnheiten sind überfällig', '#1 chores are overdue to be done' => '#1 Hausarbeiten sind überfällig',
'Released on' => 'Veröffentlicht am', 'Released on' => 'Veröffentlicht am',
'Consume #3 #1 of #2' => 'Verbrauche #3 #1 #2', 'Consume #3 #1 of #2' => 'Verbrauche #3 #1 #2',
'Added #1 #2 of #3 to stock' => '#1 #2 #3 dem Bestand hinzugefügt', 'Added #1 #2 of #3 to stock' => '#1 #2 #3 dem Bestand hinzugefügt',
'Stock amount of #1 is now #2 #3' => 'Es sind nun #2 #3 #1 im Bestand', 'Stock amount of #1 is now #2 #3' => 'Es sind nun #2 #3 #1 im Bestand',
'Tracked execution of habit #1 on #2' => 'Ausführung von #1 am #2 erfasst', 'Tracked execution of chore #1 on #2' => 'Ausführung von #1 am #2 erfasst',
'Tracked charge cycle of battery #1 on #2' => 'Ladezyklus für Batterie #1 am #2 erfasst', 'Tracked charge cycle of battery #1 on #2' => 'Ladezyklus für Batterie #1 am #2 erfasst',
'Consume all #1 which are currently in stock' => 'Verbrauche den kompletten Bestand von #1', 'Consume all #1 which are currently in stock' => 'Verbrauche den kompletten Bestand von #1',
'All' => 'Alle', 'All' => 'Alle',
'Track charge cycle of battery #1' => 'Erfasse einen Ladezyklus für Batterie #1', 'Track charge cycle of battery #1' => 'Erfasse einen Ladezyklus für Batterie #1',
'Track execution of habit #1' => 'Erfasse eine Ausführung von #1', 'Track execution of chore #1' => 'Erfasse eine Ausführung von #1',
'Filter by location' => 'Nach Standort filtern', 'Filter by location' => 'Nach Standort filtern',
'Search' => 'Suche', 'Search' => 'Suche',
'Not logged in' => 'Nicht angemeldet', 'Not logged in' => 'Nicht angemeldet',
'You have to select a product' => 'Ein Produkt muss ausgewählt werden', 'You have to select a product' => 'Ein Produkt muss ausgewählt werden',
'You have to select a habit' => 'Eine Gewohnheit muss ausgewählt werden', 'You have to select a chore' => 'Eine Hausarbeit muss ausgewählt werden',
'You have to select a battery' => 'Eine Batterie muss ausgewählt werden', 'You have to select a battery' => 'Eine Batterie muss ausgewählt werden',
'A name is required' => 'Ein Name ist erforderlich', 'A name is required' => 'Ein Name ist erforderlich',
'A location is required' => 'Ein Standort ist erforderlich', 'A location is required' => 'Ein Standort ist erforderlich',
@@ -183,8 +183,8 @@ return array(
'Done by' => 'Ausgeführt von', 'Done by' => 'Ausgeführt von',
'Last done by' => 'Zuletzt ausgeführt von', 'Last done by' => 'Zuletzt ausgeführt von',
'Unknown' => 'Unbekannt', 'Unknown' => 'Unbekannt',
'Filter by habit' => 'Nach Gewohnheit filtern', 'Filter by chore' => 'Nach Hausarbeit filtern',
'Habits analysis' => 'Gewohnheiten Analyse', 'Chores analysis' => 'Hausarbeiten Analyse',
'0 means suggestions for the next charge cycle are disabled' => '0 bedeutet dass Vorschläge für den nächsten Ladezyklus deaktiviert sind', '0 means suggestions for the next charge cycle are disabled' => '0 bedeutet dass Vorschläge für den nächsten Ladezyklus deaktiviert sind',
'Charge cycle interval (days)' => 'Ladezyklusintervall (Tage)', 'Charge cycle interval (days)' => 'Ladezyklusintervall (Tage)',
'Last price' => 'Letzter Preis', 'Last price' => 'Letzter Preis',
@@ -198,8 +198,8 @@ return array(
'#1 product is below defined min. stock amount' => '#1 Produkt ist unter Mindestbestand', '#1 product is below defined min. stock amount' => '#1 Produkt ist unter Mindestbestand',
'Unit' => 'Einheit', 'Unit' => 'Einheit',
'Units' => 'Einheiten', 'Units' => 'Einheiten',
'#1 habit is due to be done within the next #2 days' => '#1 Gewohnheit steht in den nächsten #2 Tagen an', '#1 chore is due to be done within the next #2 days' => '#1 Hausarbeit steht in den nächsten #2 Tagen an',
'#1 habit is overdue to be done' => '#1 Gewohnheit ist überfällig', '#1 chore is overdue to be done' => '#1 Hausarbeit ist überfällig',
'#1 battery is due to be charged within the next #2 days' => '#1 Batterie muss in den nächsten #2 Tagen geladen werden', '#1 battery is due to be charged within the next #2 days' => '#1 Batterie muss in den nächsten #2 Tagen geladen werden',
'#1 battery is overdue to be charged' => '#1 Batterie ist überfällig', '#1 battery is overdue to be charged' => '#1 Batterie ist überfällig',
'#1 unit was automatically added and will apply in addition to the amount entered here' => '#1 Einheit wurde automatisch hinzugefügt und gilt zusätzlich der hier eingegebenen Menge', '#1 unit was automatically added and will apply in addition to the amount entered here' => '#1 Einheit wurde automatisch hinzugefügt und gilt zusätzlich der hier eingegebenen Menge',

View File

@@ -9,20 +9,20 @@ return array(
'Amount' => 'quantità', 'Amount' => 'quantità',
'Next best before date' => 'Prossima data di scadenza', 'Next best before date' => 'Prossima data di scadenza',
'Logout' => 'Logout', 'Logout' => 'Logout',
'Habits overview' => 'Riepilogo delle abitudini', 'Chores overview' => 'Riepilogo delle abitudini',
'Batteries overview' => 'Riepilogo delle batterie', 'Batteries overview' => 'Riepilogo delle batterie',
'Purchase' => 'Acquisti', 'Purchase' => 'Acquisti',
'Consume' => 'Consumi', 'Consume' => 'Consumi',
'Inventory' => 'Inventario', 'Inventory' => 'Inventario',
'Shopping list' => 'Lista della spesa', 'Shopping list' => 'Lista della spesa',
'Habit tracking' => 'Dati abitudini', 'Chore tracking' => 'Dati abitudini',
'Battery tracking' => 'Dati batterie', 'Battery tracking' => 'Dati batterie',
'Products' => 'Prodotti', 'Products' => 'Prodotti',
'Locations' => 'Posizioni', 'Locations' => 'Posizioni',
'Quantity units' => 'Unità di misura', 'Quantity units' => 'Unità di misura',
'Habits' => 'Abitudini', 'Chores' => 'Abitudini',
'Batteries' => 'Batterie', 'Batteries' => 'Batterie',
'Habit' => 'Abitudine', 'Chore' => 'Abitudine',
'Next estimated tracking' => 'Prossima esecuzione', 'Next estimated tracking' => 'Prossima esecuzione',
'Last tracked' => 'Ultima esecuzione', 'Last tracked' => 'Ultima esecuzione',
'Battery' => 'Batterie', 'Battery' => 'Batterie',
@@ -41,7 +41,7 @@ return array(
'New amount' => 'Nuova quantità', 'New amount' => 'Nuova quantità',
'Note' => 'Nota', 'Note' => 'Nota',
'Tracked time' => 'Ora di esecuzione', 'Tracked time' => 'Ora di esecuzione',
'Habit overview' => 'Riepilogo dell\'abitudine', 'Chore overview' => 'Riepilogo dell\'abitudine',
'Tracked count' => 'Numero di esecuzioni', 'Tracked count' => 'Numero di esecuzioni',
'Battery overview' => 'Riepilogo della batteria', 'Battery overview' => 'Riepilogo della batteria',
'Charge cycles count' => 'Numero di ricariche', 'Charge cycles count' => 'Numero di ricariche',
@@ -68,11 +68,11 @@ return array(
'Create quantity unit' => 'Aggiungi unità di misura', 'Create quantity unit' => 'Aggiungi unità di misura',
'Period type' => 'Tipo di ripetizione', 'Period type' => 'Tipo di ripetizione',
'Period days' => 'Periodo in giorni', 'Period days' => 'Periodo in giorni',
'Create habit' => 'Aggiungi abitudine', 'Create chore' => 'Aggiungi abitudine',
'Used in' => 'Usato in', 'Used in' => 'Usato in',
'Create battery' => 'Aggiungi batteria', 'Create battery' => 'Aggiungi batteria',
'Edit battery' => 'Modifica batteria', 'Edit battery' => 'Modifica batteria',
'Edit habit' => 'Modifica abitudine', 'Edit chore' => 'Modifica abitudine',
'Edit quantity unit' => 'Modifica unità di misura', 'Edit quantity unit' => 'Modifica unità di misura',
'Edit product' => 'Modifica prodotto', 'Edit product' => 'Modifica prodotto',
'Edit location' => 'Modifica posizione', 'Edit location' => 'Modifica posizione',
@@ -90,7 +90,7 @@ return array(
'Are you sure to delete battery "#1"?' => 'Sei sicuro di voler eliminare la batteria "#1"?', 'Are you sure to delete battery "#1"?' => 'Sei sicuro di voler eliminare la batteria "#1"?',
'Yes' => 'Si', 'Yes' => 'Si',
'No' => 'No', 'No' => 'No',
'Are you sure to delete habit "#1"?' => 'Sei sicuro di voler eliminare l\'abitudine "#1"?', 'Are you sure to delete chore "#1"?' => 'Sei sicuro di voler eliminare l\'abitudine "#1"?',
'"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" non è stato associato a nessun prodotto, vuoi procedere?', '"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" non è stato associato a nessun prodotto, vuoi procedere?',
'Create or assign product' => 'Aggiungi o assegna prodotto', 'Create or assign product' => 'Aggiungi o assegna prodotto',
'Cancel' => 'Annulla', 'Cancel' => 'Annulla',
@@ -110,29 +110,29 @@ return array(
'This product is not in stock' => 'Questo prodotto non è in dispensa', 'This product is not in stock' => 'Questo prodotto non è in dispensa',
'This means #1 will be added to stock' => '#1 sarà aggiunto alla dispensa', 'This means #1 will be added to stock' => '#1 sarà aggiunto alla dispensa',
'This means #1 will be removed from stock' => '#1 sarà rimosso dalla dispensa', 'This means #1 will be removed from stock' => '#1 sarà rimosso dalla dispensa',
'This means it is estimated that a new execution of this habit is tracked #1 days after the last was tracked' => 'L\'esecuzione dell\'abitudine è #1 giorni dopo la precedente', 'This means it is estimated that a new execution of this chore is tracked #1 days after the last was tracked' => 'L\'esecuzione dell\'abitudine è #1 giorni dopo la precedente',
'Removed #1 #2 of #3 from stock' => '#1 #2 su #3 rimossi dalla dispensa', 'Removed #1 #2 of #3 from stock' => '#1 #2 su #3 rimossi dalla dispensa',
'About grocy' => 'Riguardo grocy', 'About grocy' => 'Riguardo grocy',
'Close' => 'Chiudi', 'Close' => 'Chiudi',
'#1 batteries are due to be charged within the next #2 days' => '#1 batterie da ricaricare entro #2 giorni', '#1 batteries are due to be charged within the next #2 days' => '#1 batterie da ricaricare entro #2 giorni',
'#1 batteries are overdue to be charged' => '#1 batterie devono essere ricaricate', '#1 batteries are overdue to be charged' => '#1 batterie devono essere ricaricate',
'#1 habits are due to be done within the next #2 days' => '#1 abitudini da eseguire entro #2 giorni', '#1 chores are due to be done within the next #2 days' => '#1 abitudini da eseguire entro #2 giorni',
'#1 habits are overdue to be done' => '#1 abitudini da eseguire', '#1 chores are overdue to be done' => '#1 abitudini da eseguire',
'Released on' => 'Rilasciato il', 'Released on' => 'Rilasciato il',
'Consume #3 #1 of #2' => 'Consumati #3 #1 di #2', 'Consume #3 #1 of #2' => 'Consumati #3 #1 di #2',
'Added #1 #2 of #3 to stock' => 'Aggiunti #1 #2 di #3', 'Added #1 #2 of #3 to stock' => 'Aggiunti #1 #2 di #3',
'Stock amount of #1 is now #2 #3' => 'La quantità in dispensa di #1 è ora #2 #3', 'Stock amount of #1 is now #2 #3' => 'La quantità in dispensa di #1 è ora #2 #3',
'Tracked execution of habit #1 on #2' => 'Esecuzione dell\'abitudine #1 registrata il #2', 'Tracked execution of chore #1 on #2' => 'Esecuzione dell\'abitudine #1 registrata il #2',
'Tracked charge cycle of battery #1 on #2' => 'Ricarica della batteria #1 effettuata il #2', 'Tracked charge cycle of battery #1 on #2' => 'Ricarica della batteria #1 effettuata il #2',
'Consume all #1 which are currently in stock' => 'Consuma tutto #1 in dispensa', 'Consume all #1 which are currently in stock' => 'Consuma tutto #1 in dispensa',
'All' => 'Tutto', 'All' => 'Tutto',
'Track charge cycle of battery #1' => 'Registra la ricarica della batteria #1', 'Track charge cycle of battery #1' => 'Registra la ricarica della batteria #1',
'Track execution of habit #1' => 'Registra l\'esecuzione dell\'abitudine #1', 'Track execution of chore #1' => 'Registra l\'esecuzione dell\'abitudine #1',
'Filter by location' => 'Filtra per posizione', 'Filter by location' => 'Filtra per posizione',
'Search' => 'Cerca', 'Search' => 'Cerca',
'Not logged in' => 'Non autenticato', 'Not logged in' => 'Non autenticato',
'You have to select a product' => 'Devi selezionare un prodotto', 'You have to select a product' => 'Devi selezionare un prodotto',
'You have to select a habit' => 'Devi selezionare un\'abitudine', 'You have to select a chore' => 'Devi selezionare un\'abitudine',
'You have to select a battery' => 'Devi selezionare una batteria', 'You have to select a battery' => 'Devi selezionare una batteria',
'A name is required' => 'Inserisci un nome', 'A name is required' => 'Inserisci un nome',
'A location is required' => 'Inserisci la posizione', 'A location is required' => 'Inserisci la posizione',

View File

@@ -9,20 +9,20 @@ return array(
'Amount' => 'Antall', 'Amount' => 'Antall',
'Next best before date' => 'Kommende best før dato', 'Next best before date' => 'Kommende best før dato',
'Logout' => 'Logg ut', 'Logout' => 'Logg ut',
'Habits overview' => 'Oversikt Husoppgaver', 'Chores overview' => 'Oversikt Husoppgaver',
'Batteries overview' => 'Oversikt Batteri', 'Batteries overview' => 'Oversikt Batteri',
'Purchase' => 'Innkjøp', 'Purchase' => 'Innkjøp',
'Consume' => 'Forbrukt', 'Consume' => 'Forbrukt',
'Inventory' => 'Endre Husholdning', 'Inventory' => 'Endre Husholdning',
'Shopping list' => 'Handleliste', 'Shopping list' => 'Handleliste',
'Habit tracking' => 'Logge Husoppgaver', 'Chore tracking' => 'Logge Husoppgaver',
'Battery tracking' => 'Batteri Ladesyklus', 'Battery tracking' => 'Batteri Ladesyklus',
'Products' => 'Produkter', 'Products' => 'Produkter',
'Locations' => 'Lokasjoner', 'Locations' => 'Lokasjoner',
'Quantity units' => 'Forpakning', 'Quantity units' => 'Forpakning',
'Habits' => 'Husoppgaver', 'Chores' => 'Husoppgaver',
'Batteries' => 'Batterier', 'Batteries' => 'Batterier',
'Habit' => 'Husoppgave', 'Chore' => 'Husoppgave',
'Next estimated tracking' => 'Neste handling', 'Next estimated tracking' => 'Neste handling',
'Last tracked' => 'Sist logget', 'Last tracked' => 'Sist logget',
'Battery' => 'Batteri', 'Battery' => 'Batteri',
@@ -41,7 +41,7 @@ return array(
'New amount' => 'Nytt antall', 'New amount' => 'Nytt antall',
'Note' => 'Info', 'Note' => 'Info',
'Tracked time' => 'Tid utført/ ladet', 'Tracked time' => 'Tid utført/ ladet',
'Habit overview' => 'Oversikt Husoppgave', 'Chore overview' => 'Oversikt Husoppgave',
'Tracked count' => 'Antall utførelser/ ladninger', 'Tracked count' => 'Antall utførelser/ ladninger',
'Battery overview' => 'Batteri Oversikt', 'Battery overview' => 'Batteri Oversikt',
'Charge cycles count' => 'Antall ladesykluser', 'Charge cycles count' => 'Antall ladesykluser',
@@ -68,11 +68,11 @@ return array(
'Create quantity unit' => 'Opprett forpakning', 'Create quantity unit' => 'Opprett forpakning',
'Period type' => 'Gjentakelse', 'Period type' => 'Gjentakelse',
'Period days' => 'Antall dager for gjentakelse', 'Period days' => 'Antall dager for gjentakelse',
'Create habit' => 'Opprett husoppgave', 'Create chore' => 'Opprett husoppgave',
'Used in' => 'Brukt', 'Used in' => 'Brukt',
'Create battery' => 'Opprett batteri', 'Create battery' => 'Opprett batteri',
'Edit battery' => 'Endre batteri', 'Edit battery' => 'Endre batteri',
'Edit habit' => 'Endre husoppgave', 'Edit chore' => 'Endre husoppgave',
'Edit quantity unit' => 'Endre forpakning', 'Edit quantity unit' => 'Endre forpakning',
'Edit product' => 'Endre produkt', 'Edit product' => 'Endre produkt',
'Edit location' => 'Endre lokasjon', 'Edit location' => 'Endre lokasjon',
@@ -90,7 +90,7 @@ return array(
'Are you sure to delete battery "#1"?' => 'Er du sikker du ønsker å slette Batteri "#1"?', 'Are you sure to delete battery "#1"?' => 'Er du sikker du ønsker å slette Batteri "#1"?',
'Yes' => 'Ja', 'Yes' => 'Ja',
'No' => 'Nei', 'No' => 'Nei',
'Are you sure to delete habit "#1"?' => 'Er du sikker på du ønsker å slette husoppgave "#1"?', 'Are you sure to delete chore "#1"?' => 'Er du sikker på du ønsker å slette husoppgave "#1"?',
'"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" kunne ikke bli tildelt et produkt, hvordan ønsker du å fortsette?', '"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" kunne ikke bli tildelt et produkt, hvordan ønsker du å fortsette?',
'Create or assign product' => 'Opprett eller tildel til produkt', 'Create or assign product' => 'Opprett eller tildel til produkt',
'Cancel' => 'Avbryt', 'Cancel' => 'Avbryt',
@@ -110,29 +110,29 @@ return array(
'This product is not in stock' => 'Dette produktet er ikke i husholdningen', 'This product is not in stock' => 'Dette produktet er ikke i husholdningen',
'This means #1 will be added to stock' => 'Dette betyr at #1 vil bli lagt til i husholdningen', 'This means #1 will be added to stock' => 'Dette betyr at #1 vil bli lagt til i husholdningen',
'This means #1 will be removed from stock' => 'Dette betyr at #1 vil bli fjernet fra husholdningen', 'This means #1 will be removed from stock' => 'Dette betyr at #1 vil bli fjernet fra husholdningen',
'This means it is estimated that a new execution of this habit is tracked #1 days after the last was tracked' => 'Dette betyr at det er estimert at den nye utførelsen av denne husoppgaven er logget #1 dag etter den sist var logget', 'This means it is estimated that a new execution of this chore is tracked #1 days after the last was tracked' => 'Dette betyr at det er estimert at den nye utførelsen av denne husoppgaven er logget #1 dag etter den sist var logget',
'Removed #1 #2 of #3 from stock' => 'Fjernet #1 #2 #3 fra husholdningen', 'Removed #1 #2 of #3 from stock' => 'Fjernet #1 #2 #3 fra husholdningen',
'About grocy' => 'Om Grocy', 'About grocy' => 'Om Grocy',
'Close' => 'Lukk', 'Close' => 'Lukk',
'#1 batteries are due to be charged within the next #2 days' => '#1 Batteri må lades innen de #2 neste dagene', '#1 batteries are due to be charged within the next #2 days' => '#1 Batteri må lades innen de #2 neste dagene',
'#1 batteries are overdue to be charged' => '#1 Batteri har gått over fristen for å bli ladet opp', '#1 batteries are overdue to be charged' => '#1 Batteri har gått over fristen for å bli ladet opp',
'#1 habits are due to be done within the next #2 days' => '#1 husoppgaver skal gjøres inne de #2 neste dagene', '#1 chores are due to be done within the next #2 days' => '#1 husoppgaver skal gjøres inne de #2 neste dagene',
'#1 habits are overdue to be done' => '#1 husoppgaver har gått over fristen for utførelse', '#1 chores are overdue to be done' => '#1 husoppgaver har gått over fristen for utførelse',
'Released on' => 'Utgitt', 'Released on' => 'Utgitt',
'Consume #3 #1 of #2' => 'Forbruk #3 #1 #2', 'Consume #3 #1 of #2' => 'Forbruk #3 #1 #2',
'Added #1 #2 of #3 to stock' => '#1 #2 #3 lagt til i husholdningen', 'Added #1 #2 of #3 to stock' => '#1 #2 #3 lagt til i husholdningen',
'Stock amount of #1 is now #2 #3' => 'Husholdning antall #1 er nå #2 #3', 'Stock amount of #1 is now #2 #3' => 'Husholdning antall #1 er nå #2 #3',
'Tracked execution of habit #1 on #2' => 'Utførte husoppgave "#1" den #2', 'Tracked execution of chore #1 on #2' => 'Utførte husoppgave "#1" den #2',
'Tracked charge cycle of battery #1 on #2' => 'Ladet #1 den #2', 'Tracked charge cycle of battery #1 on #2' => 'Ladet #1 den #2',
'Consume all #1 which are currently in stock' => 'Konsumér alle #1 som er i husholdningen', 'Consume all #1 which are currently in stock' => 'Konsumér alle #1 som er i husholdningen',
'All' => 'Alle', 'All' => 'Alle',
'Track charge cycle of battery #1' => '#1 ladet', 'Track charge cycle of battery #1' => '#1 ladet',
'Track execution of habit #1' => 'Utfør husoppgave #1', 'Track execution of chore #1' => 'Utfør husoppgave #1',
'Filter by location' => 'Filtrér etter lokasjon', 'Filter by location' => 'Filtrér etter lokasjon',
'Search' => 'Søk', 'Search' => 'Søk',
'Not logged in' => 'Ikke logget inn', 'Not logged in' => 'Ikke logget inn',
'You have to select a product' => 'Du må velge et produkt', 'You have to select a product' => 'Du må velge et produkt',
'You have to select a habit' => 'Du må velge en husoppgaven', 'You have to select a chore' => 'Du må velge en husoppgaven',
'You have to select a battery' => 'Du må velge et batteri', 'You have to select a battery' => 'Du må velge et batteri',
'A name is required' => 'Vennligst fyll inn et navn', 'A name is required' => 'Vennligst fyll inn et navn',
'A location is required' => 'En lokasjon kreves', 'A location is required' => 'En lokasjon kreves',
@@ -183,8 +183,8 @@ return array(
'Done by' => 'Utført av', 'Done by' => 'Utført av',
'Last done by' => 'Sist utført av', 'Last done by' => 'Sist utført av',
'Unknown' => 'Ukjent', 'Unknown' => 'Ukjent',
'Filter by habit' => 'Filtrér husoppave', 'Filter by chore' => 'Filtrér husoppave',
'Habits analysis' => 'Statistikk husoppgaver', 'Chores analysis' => 'Statistikk husoppgaver',
'0 means suggestions for the next charge cycle are disabled' => '0 betyr neste ladesyklus er avslått', '0 means suggestions for the next charge cycle are disabled' => '0 betyr neste ladesyklus er avslått',
'Charge cycle interval (days)' => 'Ladesyklysintervall (Dager)', 'Charge cycle interval (days)' => 'Ladesyklysintervall (Dager)',
'Last price' => 'Siste pris', 'Last price' => 'Siste pris',
@@ -198,8 +198,8 @@ return array(
'#1 product is below defined min. stock amount' => '#1 Produkt er under minimums husholdningsnivå', '#1 product is below defined min. stock amount' => '#1 Produkt er under minimums husholdningsnivå',
'Unit' => 'Enhet', 'Unit' => 'Enhet',
'Units' => 'Enheter', 'Units' => 'Enheter',
'#1 habit is due to be done within the next #2 days' => '#1 husoppgave skal gjøres inne de #2 neste dagene', '#1 chore is due to be done within the next #2 days' => '#1 husoppgave skal gjøres inne de #2 neste dagene',
'#1 habit is overdue to be done' => '#1 husoppgave har gått over fristen for utførelse', '#1 chore is overdue to be done' => '#1 husoppgave har gått over fristen for utførelse',
'#1 battery is due to be charged within the next #2 days' => '#1 Batteri må lades innen #2 dager', '#1 battery is due to be charged within the next #2 days' => '#1 Batteri må lades innen #2 dager',
'#1 battery is overdue to be charged' => '#1 Batteri har gått over fristen for å lades', '#1 battery is overdue to be charged' => '#1 Batteri har gått over fristen for å lades',
'#1 unit was automatically added and will apply in addition to the amount entered here' => '#1 enhet ble automatisk lagt til i tillegg til hva som blir skrevet inn her', '#1 unit was automatically added and will apply in addition to the amount entered here' => '#1 enhet ble automatisk lagt til i tillegg til hva som blir skrevet inn her',

View File

@@ -1,5 +1,5 @@
ALTER TABLE habits_log ALTER TABLE habits_log
ADD done_by_user_id; ADD done_by_user_id INTEGER;
DROP TABLE api_keys; DROP TABLE api_keys;

31
migrations/0035.sql Normal file
View File

@@ -0,0 +1,31 @@
ALTER TABLE habits RENAME TO chores;
CREATE TABLE chores_log (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
chore_id INTEGER NOT NULL,
tracked_time DATETIME,
done_by_user_id INTEGER,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
);
INSERT INTO chores_log
(chore_id, tracked_time, done_by_user_id, row_created_timestamp)
SELECT habit_id, tracked_time, done_by_user_id, row_created_timestamp
FROM habits_log;
DROP TABLE habits_log;
DROP VIEW habits_current;
CREATE VIEW chores_current
AS
SELECT
h.id AS chore_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 chores h
LEFT JOIN chores_log l
ON h.id = l.chore_id
GROUP BY h.id, h.period_days;

View File

@@ -0,0 +1,71 @@
$('#save-chore-button').on('click', function(e)
{
e.preventDefault();
if (Grocy.EditMode === 'create')
{
Grocy.Api.Post('add-object/chores', $('#chore-form').serializeJSON(),
function(result)
{
window.location.href = U('/chores');
},
function(xhr)
{
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
}
);
}
else
{
Grocy.Api.Post('edit-object/chores/' + Grocy.EditObjectId, $('#chore-form').serializeJSON(),
function(result)
{
window.location.href = U('/chores');
},
function(xhr)
{
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
}
);
}
});
$('#chore-form input').keyup(function(event)
{
Grocy.FrontendHelpers.ValidateForm('chore-form');
});
$('#chore-form input').keydown(function(event)
{
if (event.keyCode === 13) //Enter
{
if (document.getElementById('chore-form').checkValidity() === false) //There is at least one validation error
{
event.preventDefault();
return false;
}
else
{
$('#save-chore-button').click();
}
}
});
$('#name').focus();
Grocy.FrontendHelpers.ValidateForm('chore-form');
$('.input-group-chore-period-type').on('change', function(e)
{
var periodType = $('#period_type').val();
var periodDays = $('#period_days').val();
if (periodType === 'dynamic-regular')
{
$('#chore-period-type-info').text(L('This means it is estimated that a new execution of this chore is tracked #1 days after the last was tracked', periodDays.toString()));
$('#chore-period-type-info').removeClass('d-none');
}
else
{
$('#chore-period-type-info').addClass('d-none');
}
});

View File

@@ -1,4 +1,4 @@
var habitsTable = $('#habits-table').DataTable({ var choresTable = $('#chores-table').DataTable({
'paginate': false, 'paginate': false,
'order': [[1, 'asc']], 'order': [[1, 'asc']],
'columnDefs': [ 'columnDefs': [
@@ -22,16 +22,16 @@ $("#search").on("keyup", function()
value = ""; value = "";
} }
habitsTable.search(value).draw(); choresTable.search(value).draw();
}); });
$(document).on('click', '.habit-delete-button', function (e) $(document).on('click', '.chore-delete-button', function (e)
{ {
var objectName = $(e.currentTarget).attr('data-habit-name'); var objectName = $(e.currentTarget).attr('data-chore-name');
var objectId = $(e.currentTarget).attr('data-habit-id'); var objectId = $(e.currentTarget).attr('data-chore-id');
bootbox.confirm({ bootbox.confirm({
message: L('Are you sure to delete habit "#1"?', objectName), message: L('Are you sure to delete chore "#1"?', objectName),
buttons: { buttons: {
confirm: { confirm: {
label: L('Yes'), label: L('Yes'),
@@ -46,10 +46,10 @@ $(document).on('click', '.habit-delete-button', function (e)
{ {
if (result === true) if (result === true)
{ {
Grocy.Api.Get('delete-object/habits/' + objectId, Grocy.Api.Get('delete-object/chores/' + objectId,
function(result) function(result)
{ {
window.location.href = U('/habits'); window.location.href = U('/chores');
}, },
function(xhr) function(xhr)
{ {

View File

@@ -1,4 +1,4 @@
var habitsAnalysisTable = $('#habits-analysis-table').DataTable({ var choresAnalysisTable = $('#chores-analysis-table').DataTable({
'paginate': false, 'paginate': false,
'order': [[1, 'desc']], 'order': [[1, 'desc']],
'language': JSON.parse(L('datatables_localization')), 'language': JSON.parse(L('datatables_localization')),
@@ -11,16 +11,16 @@
} }
}); });
$("#habit-filter").on("change", function() $("#chore-filter").on("change", function()
{ {
var value = $(this).val(); var value = $(this).val();
var text = $("#habit-filter option:selected").text(); var text = $("#chore-filter option:selected").text();
if (value === "all") if (value === "all")
{ {
text = ""; text = "";
} }
habitsAnalysisTable.column(0).search(text).draw(); choresAnalysisTable.column(0).search(text).draw();
}); });
$("#search").on("keyup", function() $("#search").on("keyup", function()
@@ -31,11 +31,11 @@ $("#search").on("keyup", function()
value = ""; value = "";
} }
habitsAnalysisTable.search(value).draw(); choresAnalysisTable.search(value).draw();
}); });
if (typeof GetUriParam("habit") !== "undefined") if (typeof GetUriParam("chore") !== "undefined")
{ {
$("#habit-filter").val(GetUriParam("habit")); $("#chore-filter").val(GetUriParam("chore"));
$("#habit-filter").trigger("change"); $("#chore-filter").trigger("change");
} }

View File

@@ -1,4 +1,4 @@
var habitsOverviewTable = $('#habits-overview-table').DataTable({ var choresOverviewTable = $('#chores-overview-table').DataTable({
'paginate': false, 'paginate': false,
'order': [[2, 'desc']], 'order': [[2, 'desc']],
'columnDefs': [ 'columnDefs': [
@@ -22,55 +22,55 @@ $("#search").on("keyup", function()
value = ""; value = "";
} }
habitsOverviewTable.search(value).draw(); choresOverviewTable.search(value).draw();
}); });
$(document).on('click', '.track-habit-button', function(e) $(document).on('click', '.track-chore-button', function(e)
{ {
var habitId = $(e.currentTarget).attr('data-habit-id'); var choreId = $(e.currentTarget).attr('data-chore-id');
var habitName = $(e.currentTarget).attr('data-habit-name'); var choreName = $(e.currentTarget).attr('data-chore-name');
var trackedTime = moment().format('YYYY-MM-DD HH:mm:ss'); var trackedTime = moment().format('YYYY-MM-DD HH:mm:ss');
Grocy.Api.Get('habits/track-habit-execution/' + habitId + '?tracked_time=' + trackedTime, Grocy.Api.Get('chores/track-chore-execution/' + choreId + '?tracked_time=' + trackedTime,
function() function()
{ {
Grocy.Api.Get('habits/get-habit-details/' + habitId, Grocy.Api.Get('chores/get-chore-details/' + choreId,
function(result) function(result)
{ {
var habitRow = $('#habit-' + habitId + '-row'); var choreRow = $('#chore-' + choreId + '-row');
var nextXDaysThreshold = moment().add($("#info-due-habits").data("next-x-days"), "days"); var nextXDaysThreshold = moment().add($("#info-due-chores").data("next-x-days"), "days");
var now = moment(); var now = moment();
var nextExecutionTime = moment(result.next_estimated_execution_time); var nextExecutionTime = moment(result.next_estimated_execution_time);
habitRow.removeClass("table-warning"); choreRow.removeClass("table-warning");
habitRow.removeClass("table-danger"); choreRow.removeClass("table-danger");
if (nextExecutionTime.isBefore(now)) if (nextExecutionTime.isBefore(now))
{ {
habitRow.addClass("table-danger"); choreRow.addClass("table-danger");
} }
else if (nextExecutionTime.isBefore(nextXDaysThreshold)) else if (nextExecutionTime.isBefore(nextXDaysThreshold))
{ {
habitRow.addClass("table-warning"); choreRow.addClass("table-warning");
} }
$('#habit-' + habitId + '-last-tracked-time').parent().effect('highlight', { }, 500); $('#chore-' + choreId + '-last-tracked-time').parent().effect('highlight', { }, 500);
$('#habit-' + habitId + '-last-tracked-time').fadeOut(500, function() $('#chore-' + choreId + '-last-tracked-time').fadeOut(500, function()
{ {
$(this).text(trackedTime).fadeIn(500); $(this).text(trackedTime).fadeIn(500);
}); });
$('#habit-' + habitId + '-last-tracked-time-timeago').attr('datetime', trackedTime); $('#chore-' + choreId + '-last-tracked-time-timeago').attr('datetime', trackedTime);
if (result.habit.period_type == "dynamic-regular") if (result.chore.period_type == "dynamic-regular")
{ {
$('#habit-' + habitId + '-next-execution-time').parent().effect('highlight', { }, 500); $('#chore-' + choreId + '-next-execution-time').parent().effect('highlight', { }, 500);
$('#habit-' + habitId + '-next-execution-time').fadeOut(500, function() $('#chore-' + choreId + '-next-execution-time').fadeOut(500, function()
{ {
$(this).text(result.next_estimated_execution_time).fadeIn(500); $(this).text(result.next_estimated_execution_time).fadeIn(500);
}); });
$('#habit-' + habitId + '-next-execution-time-timeago').attr('datetime', result.next_estimated_execution_time); $('#chore-' + choreId + '-next-execution-time-timeago').attr('datetime', result.next_estimated_execution_time);
} }
toastr.success(L('Tracked execution of habit #1 on #2', habitName, trackedTime)); toastr.success(L('Tracked execution of chore #1 on #2', choreName, trackedTime));
RefreshContextualTimeago(); RefreshContextualTimeago();
RefreshStatistics(); RefreshStatistics();
}, },
@@ -89,8 +89,8 @@ $(document).on('click', '.track-habit-button', function(e)
function RefreshStatistics() function RefreshStatistics()
{ {
var nextXDays = $("#info-due-habits").data("next-x-days"); var nextXDays = $("#info-due-chores").data("next-x-days");
Grocy.Api.Get('habits/get-current', Grocy.Api.Get('chores/get-current',
function(result) function(result)
{ {
var dueCount = 0; var dueCount = 0;
@@ -109,8 +109,8 @@ function RefreshStatistics()
} }
}); });
$("#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-due-chores").text(Pluralize(dueCount, L('#1 chore is due to be done within the next #2 days', dueCount, nextXDays), L('#1 chores 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))); $("#info-overdue-chores").text(Pluralize(overdueCount, L('#1 chore is overdue to be done', overdueCount), L('#1 chores are overdue to be done', overdueCount)));
}, },
function(xhr) function(xhr)
{ {

View File

@@ -0,0 +1,82 @@
$('#save-choretracking-button').on('click', function(e)
{
e.preventDefault();
var jsonForm = $('#choretracking-form').serializeJSON();
Grocy.Api.Get('chores/get-chore-details/' + jsonForm.chore_id,
function (choreDetails)
{
Grocy.Api.Get('chores/track-chore-execution/' + jsonForm.chore_id + '?tracked_time=' + Grocy.Components.DateTimePicker.GetValue() + "&done_by=" + Grocy.Components.UserPicker.GetValue(),
function(result)
{
toastr.success(L('Tracked execution of chore #1 on #2', choreDetails.chore.name, Grocy.Components.DateTimePicker.GetValue()));
$('#chore_id').val('');
$('#chore_id_text_input').focus();
$('#chore_id_text_input').val('');
Grocy.Components.DateTimePicker.SetValue(moment().format('YYYY-MM-DD HH:mm:ss'));
$('#chore_id_text_input').trigger('change');
Grocy.FrontendHelpers.ValidateForm('choretracking-form');
},
function(xhr)
{
console.error(xhr);
}
);
},
function(xhr)
{
console.error(xhr);
}
);
});
$('#chore_id').on('change', function(e)
{
var input = $('#chore_id_text_input').val().toString();
$('#chore_id_text_input').val(input);
$('#chore_id').data('combobox').refresh();
var choreId = $(e.target).val();
if (choreId)
{
Grocy.Components.ChoreCard.Refresh(choreId);
Grocy.Components.DateTimePicker.GetInputElement().focus();
Grocy.FrontendHelpers.ValidateForm('choretracking-form');
}
});
$('.combobox').combobox({
appendId: '_text_input'
});
$('#chore_id_text_input').focus();
$('#chore_id_text_input').trigger('change');
Grocy.FrontendHelpers.ValidateForm('choretracking-form');
$('#choretracking-form input').keyup(function (event)
{
Grocy.FrontendHelpers.ValidateForm('choretracking-form');
});
$('#choretracking-form input').keydown(function(event)
{
if (event.keyCode === 13) //Enter
{
if (document.getElementById('choretracking-form').checkValidity() === false) //There is at least one validation error
{
event.preventDefault();
return false;
}
else
{
$('#save-choretracking-button').click();
}
}
});
Grocy.Components.DateTimePicker.GetInputElement().on('keypress', function(e)
{
Grocy.FrontendHelpers.ValidateForm('choretracking-form');
});

View File

@@ -0,0 +1,21 @@
Grocy.Components.ChoreCard = { };
Grocy.Components.ChoreCard.Refresh = function(choreId)
{
Grocy.Api.Get('chores/get-chore-details/' + choreId,
function(choreDetails)
{
$('#chorecard-chore-name').text(choreDetails.chore.name);
$('#chorecard-chore-last-tracked').text((choreDetails.last_tracked || L('never')));
$('#chorecard-chore-last-tracked-timeago').text($.timeago(choreDetails.last_tracked || ''));
$('#chorecard-chore-tracked-count').text((choreDetails.tracked_count || '0'));
$('#chorecard-chore-last-done-by').text((choreDetails.last_done_by.display_name || L('Unknown')));
EmptyElementWhenMatches('#chorecard-chore-last-tracked-timeago', L('timeago_nan'));
},
function(xhr)
{
console.error(xhr);
}
);
};

View File

@@ -1,21 +0,0 @@
Grocy.Components.HabitCard = { };
Grocy.Components.HabitCard.Refresh = function(habitId)
{
Grocy.Api.Get('habits/get-habit-details/' + habitId,
function(habitDetails)
{
$('#habitcard-habit-name').text(habitDetails.habit.name);
$('#habitcard-habit-last-tracked').text((habitDetails.last_tracked || L('never')));
$('#habitcard-habit-last-tracked-timeago').text($.timeago(habitDetails.last_tracked || ''));
$('#habitcard-habit-tracked-count').text((habitDetails.tracked_count || '0'));
$('#habitcard-habit-last-done-by').text((habitDetails.last_done_by.display_name || L('Unknown')));
EmptyElementWhenMatches('#habitcard-habit-last-tracked-timeago', L('timeago_nan'));
},
function(xhr)
{
console.error(xhr);
}
);
};

View File

@@ -1,71 +0,0 @@
$('#save-habit-button').on('click', function(e)
{
e.preventDefault();
if (Grocy.EditMode === 'create')
{
Grocy.Api.Post('add-object/habits', $('#habit-form').serializeJSON(),
function(result)
{
window.location.href = U('/habits');
},
function(xhr)
{
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
}
);
}
else
{
Grocy.Api.Post('edit-object/habits/' + Grocy.EditObjectId, $('#habit-form').serializeJSON(),
function(result)
{
window.location.href = U('/habits');
},
function(xhr)
{
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
}
);
}
});
$('#habit-form input').keyup(function(event)
{
Grocy.FrontendHelpers.ValidateForm('habit-form');
});
$('#habit-form input').keydown(function(event)
{
if (event.keyCode === 13) //Enter
{
if (document.getElementById('habit-form').checkValidity() === false) //There is at least one validation error
{
event.preventDefault();
return false;
}
else
{
$('#save-habit-button').click();
}
}
});
$('#name').focus();
Grocy.FrontendHelpers.ValidateForm('habit-form');
$('.input-group-habit-period-type').on('change', function(e)
{
var periodType = $('#period_type').val();
var periodDays = $('#period_days').val();
if (periodType === 'dynamic-regular')
{
$('#habit-period-type-info').text(L('This means it is estimated that a new execution of this habit is tracked #1 days after the last was tracked', periodDays.toString()));
$('#habit-period-type-info').removeClass('d-none');
}
else
{
$('#habit-period-type-info').addClass('d-none');
}
});

View File

@@ -1,82 +0,0 @@
$('#save-habittracking-button').on('click', function(e)
{
e.preventDefault();
var jsonForm = $('#habittracking-form').serializeJSON();
Grocy.Api.Get('habits/get-habit-details/' + jsonForm.habit_id,
function (habitDetails)
{
Grocy.Api.Get('habits/track-habit-execution/' + jsonForm.habit_id + '?tracked_time=' + Grocy.Components.DateTimePicker.GetValue() + "&done_by=" + Grocy.Components.UserPicker.GetValue(),
function(result)
{
toastr.success(L('Tracked execution of habit #1 on #2', habitDetails.habit.name, Grocy.Components.DateTimePicker.GetValue()));
$('#habit_id').val('');
$('#habit_id_text_input').focus();
$('#habit_id_text_input').val('');
Grocy.Components.DateTimePicker.SetValue(moment().format('YYYY-MM-DD HH:mm:ss'));
$('#habit_id_text_input').trigger('change');
Grocy.FrontendHelpers.ValidateForm('habittracking-form');
},
function(xhr)
{
console.error(xhr);
}
);
},
function(xhr)
{
console.error(xhr);
}
);
});
$('#habit_id').on('change', function(e)
{
var input = $('#habit_id_text_input').val().toString();
$('#habit_id_text_input').val(input);
$('#habit_id').data('combobox').refresh();
var habitId = $(e.target).val();
if (habitId)
{
Grocy.Components.HabitCard.Refresh(habitId);
Grocy.Components.DateTimePicker.GetInputElement().focus();
Grocy.FrontendHelpers.ValidateForm('habittracking-form');
}
});
$('.combobox').combobox({
appendId: '_text_input'
});
$('#habit_id_text_input').focus();
$('#habit_id_text_input').trigger('change');
Grocy.FrontendHelpers.ValidateForm('habittracking-form');
$('#habittracking-form input').keyup(function (event)
{
Grocy.FrontendHelpers.ValidateForm('habittracking-form');
});
$('#habittracking-form input').keydown(function(event)
{
if (event.keyCode === 13) //Enter
{
if (document.getElementById('habittracking-form').checkValidity() === false) //There is at least one validation error
{
event.preventDefault();
return false;
}
else
{
$('#save-habittracking-button').click();
}
}
});
Grocy.Components.DateTimePicker.GetInputElement().on('keypress', function(e)
{
Grocy.FrontendHelpers.ValidateForm('habittracking-form');
});

View File

@@ -38,13 +38,13 @@ $app->group('', function()
$this->get('/recipe/{recipeId}', '\Grocy\Controllers\RecipesController:RecipeEditForm'); $this->get('/recipe/{recipeId}', '\Grocy\Controllers\RecipesController:RecipeEditForm');
$this->get('/recipe/{recipeId}/pos/{recipePosId}', '\Grocy\Controllers\RecipesController:RecipePosEditForm'); $this->get('/recipe/{recipeId}/pos/{recipePosId}', '\Grocy\Controllers\RecipesController:RecipePosEditForm');
// Habit routes // Chore routes
$this->get('/habitsoverview', '\Grocy\Controllers\HabitsController:Overview'); $this->get('/choresoverview', '\Grocy\Controllers\ChoresController:Overview');
$this->get('/habittracking', '\Grocy\Controllers\HabitsController:TrackHabitExecution'); $this->get('/choretracking', '\Grocy\Controllers\ChoresController:TrackChoreExecution');
$this->get('/habitsanalysis', '\Grocy\Controllers\HabitsController:Analysis'); $this->get('/choresanalysis', '\Grocy\Controllers\ChoresController:Analysis');
$this->get('/habits', '\Grocy\Controllers\HabitsController:HabitsList'); $this->get('/chores', '\Grocy\Controllers\ChoresController:ChoresList');
$this->get('/habit/{habitId}', '\Grocy\Controllers\HabitsController:HabitEditForm'); $this->get('/chore/{choreId}', '\Grocy\Controllers\ChoresController:ChoreEditForm');
// Battery routes // Battery routes
$this->get('/batteriesoverview', '\Grocy\Controllers\BatteriesController:Overview'); $this->get('/batteriesoverview', '\Grocy\Controllers\BatteriesController:Overview');
@@ -93,10 +93,10 @@ $app->group('/api', function()
$this->get('/recipes/add-not-fulfilled-products-to-shopping-list/{recipeId}', '\Grocy\Controllers\RecipesApiController:AddNotFulfilledProductsToShoppingList'); $this->get('/recipes/add-not-fulfilled-products-to-shopping-list/{recipeId}', '\Grocy\Controllers\RecipesApiController:AddNotFulfilledProductsToShoppingList');
$this->get('/recipes/consume-recipe/{recipeId}', '\Grocy\Controllers\RecipesApiController:ConsumeRecipe'); $this->get('/recipes/consume-recipe/{recipeId}', '\Grocy\Controllers\RecipesApiController:ConsumeRecipe');
// Habits // Chores
$this->get('/habits/track-habit-execution/{habitId}', '\Grocy\Controllers\HabitsApiController:TrackHabitExecution'); $this->get('/chores/track-chore-execution/{choreId}', '\Grocy\Controllers\ChoresApiController:TrackChoreExecution');
$this->get('/habits/get-habit-details/{habitId}', '\Grocy\Controllers\HabitsApiController:HabitDetails'); $this->get('/chores/get-chore-details/{choreId}', '\Grocy\Controllers\ChoresApiController:ChoreDetails');
$this->get('/habits/get-current', '\Grocy\Controllers\HabitsApiController:Current'); $this->get('/chores/get-current', '\Grocy\Controllers\ChoresApiController:Current');
// Batteries // Batteries
$this->get('/batteries/track-charge-cycle/{batteryId}', '\Grocy\Controllers\BatteriesApiController:TrackChargeCycle'); $this->get('/batteries/track-charge-cycle/{batteryId}', '\Grocy\Controllers\BatteriesApiController:TrackChargeCycle');

View File

@@ -0,0 +1,74 @@
<?php
namespace Grocy\Services;
class ChoresService extends BaseService
{
const CHORE_TYPE_MANUALLY = 'manually';
const CHORE_TYPE_DYNAMIC_REGULAR = 'dynamic-regular';
public function GetCurrent()
{
$sql = 'SELECT * from chores_current';
return $this->DatabaseService->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
}
public function GetChoreDetails(int $choreId)
{
if (!$this->ChoreExists($choreId))
{
throw new \Exception('Chore does not exist');
}
$chore = $this->Database->chores($choreId);
$choreTrackedCount = $this->Database->chores_log()->where('chore_id', $choreId)->count();
$choreLastTrackedTime = $this->Database->chores_log()->where('chore_id', $choreId)->max('tracked_time');
$nextExeuctionTime = $this->Database->chores_current()->where('chore_id', $choreId)->min('next_estimated_execution_time');
$lastChoreLogRow = $this->Database->chores_log()->where('chore_id = :1 AND tracked_time = :2', $choreId, $choreLastTrackedTime)->fetch();
$lastDoneByUser = null;
if ($lastChoreLogRow !== null && !empty($lastChoreLogRow))
{
$usersService = new UsersService();
$users = $usersService->GetUsersAsDto();
$lastDoneByUser = FindObjectInArrayByPropertyValue($users, 'id', $lastChoreLogRow->done_by_user_id);
}
return array(
'chore' => $chore,
'last_tracked' => $choreLastTrackedTime,
'tracked_count' => $choreTrackedCount,
'last_done_by' => $lastDoneByUser,
'next_estimated_execution_time' => $nextExeuctionTime
);
}
public function TrackChore(int $choreId, string $trackedTime, $doneBy = GROCY_USER_ID)
{
if (!$this->ChoreExists($choreId))
{
throw new \Exception('Chore does not exist');
}
$userRow = $this->Database->users()->where('id = :1', $doneBy)->fetch();
if ($userRow === null)
{
throw new \Exception('User does not exist');
}
$logRow = $this->Database->chores_log()->createRow(array(
'chore_id' => $choreId,
'tracked_time' => $trackedTime,
'done_by_user_id' => $doneBy
));
$logRow->save();
return true;
}
private function ChoreExists($choreId)
{
$choreRow = $this->Database->chores()->where('id = :1', $choreId)->fetch();
return $choreRow !== null;
}
}

View File

@@ -78,9 +78,9 @@ class DemoDataGeneratorService extends BaseService
INSERT INTO recipes_pos (recipe_id, product_id, amount, qu_id, only_check_single_unit_in_stock) VALUES (4, 21, 200, 8, 1); INSERT INTO recipes_pos (recipe_id, product_id, amount, qu_id, only_check_single_unit_in_stock) VALUES (4, 21, 200, 8, 1);
INSERT INTO recipes_pos (recipe_id, product_id, amount, qu_id, only_check_single_unit_in_stock) VALUES (4, 22, 200, 8, 1); INSERT INTO recipes_pos (recipe_id, product_id, amount, qu_id, only_check_single_unit_in_stock) VALUES (4, 22, 200, 8, 1);
INSERT INTO habits (name, period_type, period_days) VALUES ('{$localizationService->Localize('Changed towels in the bathroom')}', 'manually', 5); --1 INSERT INTO chores (name, period_type, period_days) VALUES ('{$localizationService->Localize('Changed towels in the bathroom')}', 'manually', 5); --1
INSERT INTO habits (name, period_type, period_days) VALUES ('{$localizationService->Localize('Cleaned the kitchen floor')}', 'dynamic-regular', 7); --2 INSERT INTO chores (name, period_type, period_days) VALUES ('{$localizationService->Localize('Cleaned the kitchen floor')}', 'dynamic-regular', 7); --2
INSERT INTO habits (name, period_type, period_days) VALUES ('{$localizationService->Localize('Lawn mowed in the garden')}', 'dynamic-regular', 21); --3 INSERT INTO chores (name, period_type, period_days) VALUES ('{$localizationService->Localize('Lawn mowed in the garden')}', 'dynamic-regular', 21); --3
INSERT INTO batteries (name, description, used_in) VALUES ('{$localizationService->Localize('Battery')}1', '{$localizationService->Localize('Warranty ends')} 2023', '{$localizationService->Localize('TV remote control')}'); --1 INSERT INTO batteries (name, description, used_in) VALUES ('{$localizationService->Localize('Battery')}1', '{$localizationService->Localize('Warranty ends')} 2023', '{$localizationService->Localize('TV remote control')}'); --1
INSERT INTO batteries (name, description, used_in) VALUES ('{$localizationService->Localize('Battery')}2', '{$localizationService->Localize('Warranty ends')} 2022', '{$localizationService->Localize('Alarm clock')}'); --2 INSERT INTO batteries (name, description, used_in) VALUES ('{$localizationService->Localize('Battery')}2', '{$localizationService->Localize('Warranty ends')} 2022', '{$localizationService->Localize('Alarm clock')}'); --2
@@ -164,13 +164,13 @@ class DemoDataGeneratorService extends BaseService
$stockService->AddProduct(22, 1, date('Y-m-d', strtotime('+200 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice()); $stockService->AddProduct(22, 1, date('Y-m-d', strtotime('+200 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice());
$stockService->AddMissingProductsToShoppingList(); $stockService->AddMissingProductsToShoppingList();
$habitsService = new HabitsService(); $choresService = new ChoresService();
$habitsService->TrackHabit(1, date('Y-m-d H:i:s', strtotime('-5 days'))); $choresService->TrackChore(1, date('Y-m-d H:i:s', strtotime('-5 days')));
$habitsService->TrackHabit(1, date('Y-m-d H:i:s', strtotime('-10 days'))); $choresService->TrackChore(1, date('Y-m-d H:i:s', strtotime('-10 days')));
$habitsService->TrackHabit(1, date('Y-m-d H:i:s', strtotime('-15 days'))); $choresService->TrackChore(1, date('Y-m-d H:i:s', strtotime('-15 days')));
$habitsService->TrackHabit(2, date('Y-m-d H:i:s', strtotime('-10 days'))); $choresService->TrackChore(2, date('Y-m-d H:i:s', strtotime('-10 days')));
$habitsService->TrackHabit(2, date('Y-m-d H:i:s', strtotime('-20 days'))); $choresService->TrackChore(2, date('Y-m-d H:i:s', strtotime('-20 days')));
$habitsService->TrackHabit(3, date('Y-m-d H:i:s', strtotime('-17 days'))); $choresService->TrackChore(3, date('Y-m-d H:i:s', strtotime('-17 days')));
$batteriesService = new BatteriesService(); $batteriesService = new BatteriesService();
$batteriesService->TrackChargeCycle(1, date('Y-m-d H:i:s', strtotime('-200 days'))); $batteriesService->TrackChargeCycle(1, date('Y-m-d H:i:s', strtotime('-200 days')));

View File

@@ -1,74 +0,0 @@
<?php
namespace Grocy\Services;
class HabitsService extends BaseService
{
const HABIT_TYPE_MANUALLY = 'manually';
const HABIT_TYPE_DYNAMIC_REGULAR = 'dynamic-regular';
public function GetCurrent()
{
$sql = 'SELECT * from habits_current';
return $this->DatabaseService->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
}
public function GetHabitDetails(int $habitId)
{
if (!$this->HabitExists($habitId))
{
throw new \Exception('Habit does not exist');
}
$habit = $this->Database->habits($habitId);
$habitTrackedCount = $this->Database->habits_log()->where('habit_id', $habitId)->count();
$habitLastTrackedTime = $this->Database->habits_log()->where('habit_id', $habitId)->max('tracked_time');
$nextExeuctionTime = $this->Database->habits_current()->where('habit_id', $habitId)->min('next_estimated_execution_time');
$lastHabitLogRow = $this->Database->habits_log()->where('habit_id = :1 AND tracked_time = :2', $habitId, $habitLastTrackedTime)->fetch();
$lastDoneByUser = null;
if ($lastHabitLogRow !== null && !empty($lastHabitLogRow))
{
$usersService = new UsersService();
$users = $usersService->GetUsersAsDto();
$lastDoneByUser = FindObjectInArrayByPropertyValue($users, 'id', $lastHabitLogRow->done_by_user_id);
}
return array(
'habit' => $habit,
'last_tracked' => $habitLastTrackedTime,
'tracked_count' => $habitTrackedCount,
'last_done_by' => $lastDoneByUser,
'next_estimated_execution_time' => $nextExeuctionTime
);
}
public function TrackHabit(int $habitId, string $trackedTime, $doneBy = GROCY_USER_ID)
{
if (!$this->HabitExists($habitId))
{
throw new \Exception('Habit does not exist');
}
$userRow = $this->Database->users()->where('id = :1', $doneBy)->fetch();
if ($userRow === null)
{
throw new \Exception('User does not exist');
}
$logRow = $this->Database->habits_log()->createRow(array(
'habit_id' => $habitId,
'tracked_time' => $trackedTime,
'done_by_user_id' => $doneBy
));
$logRow->save();
return true;
}
private function HabitExists($habitId)
{
$habitRow = $this->Database->habits()->where('id = :1', $habitId)->fetch();
return $habitRow !== null;
}
}

View File

@@ -1,12 +1,12 @@
@extends('layout.default') @extends('layout.default')
@if($mode == 'edit') @if($mode == 'edit')
@section('title', $L('Edit habit')) @section('title', $L('Edit chore'))
@else @else
@section('title', $L('Create habit')) @section('title', $L('Create chore'))
@endif @endif
@section('viewJsName', 'habitform') @section('viewJsName', 'choreform')
@section('content') @section('content')
<div class="row"> <div class="row">
@@ -16,44 +16,44 @@
<script>Grocy.EditMode = '{{ $mode }}';</script> <script>Grocy.EditMode = '{{ $mode }}';</script>
@if($mode == 'edit') @if($mode == 'edit')
<script>Grocy.EditObjectId = {{ $habit->id }};</script> <script>Grocy.EditObjectId = {{ $chore->id }};</script>
@endif @endif
<form id="habit-form" novalidate> <form id="chore-form" novalidate>
<div class="form-group"> <div class="form-group">
<label for="name">{{ $L('Name') }}</label> <label for="name">{{ $L('Name') }}</label>
<input type="text" class="form-control" required id="name" name="name" value="@if($mode == 'edit'){{ $habit->name }}@endif"> <input type="text" class="form-control" required id="name" name="name" value="@if($mode == 'edit'){{ $chore->name }}@endif">
<div class="invalid-feedback">{{ $L('A name is required') }}</div> <div class="invalid-feedback">{{ $L('A name is required') }}</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="description">{{ $L('Description') }}</label> <label for="description">{{ $L('Description') }}</label>
<textarea class="form-control" rows="2" id="description" name="description">@if($mode == 'edit'){{ $habit->description }}@endif</textarea> <textarea class="form-control" rows="2" id="description" name="description">@if($mode == 'edit'){{ $chore->description }}@endif</textarea>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="period_type">{{ $L('Period type') }}</label> <label for="period_type">{{ $L('Period type') }}</label>
<select required class="form-control input-group-habit-period-type" id="period_type" name="period_type"> <select required class="form-control input-group-chore-period-type" id="period_type" name="period_type">
@foreach($periodTypes as $periodType) @foreach($periodTypes as $periodType)
<option @if($mode == 'edit' && $periodType == $habit->period_type) selected="selected" @endif value="{{ $periodType }}">{{ $L($periodType) }}</option> <option @if($mode == 'edit' && $periodType == $chore->period_type) selected="selected" @endif value="{{ $periodType }}">{{ $L($periodType) }}</option>
@endforeach @endforeach
</select> </select>
<div class="invalid-feedback">{{ $L('A period type is required') }}</div> <div class="invalid-feedback">{{ $L('A period type is required') }}</div>
</div> </div>
@php if($mode == 'edit') { $value = $habit->period_days; } else { $value = 0; } @endphp @php if($mode == 'edit') { $value = $chore->period_days; } else { $value = 0; } @endphp
@include('components.numberpicker', array( @include('components.numberpicker', array(
'id' => 'period_days', 'id' => 'period_days',
'label' => 'Period days', 'label' => 'Period days',
'value' => $value, 'value' => $value,
'min' => '0', 'min' => '0',
'additionalCssClasses' => 'input-group-habit-period-type', 'additionalCssClasses' => 'input-group-chore-period-type',
'invalidFeedback' => $L('This cannot be negative'), 'invalidFeedback' => $L('This cannot be negative'),
'additionalHtmlElements' => '<p id="habit-period-type-info" class="form-text text-muted small d-none"></p>' 'additionalHtmlElements' => '<p id="chore-period-type-info" class="form-text text-muted small d-none"></p>'
)) ))
<button id="save-habit-button" type="submit" class="btn btn-success">{{ $L('Save') }}</button> <button id="save-chore-button" type="submit" class="btn btn-success">{{ $L('Save') }}</button>
</form> </form>
</div> </div>

View File

@@ -1,15 +1,15 @@
@extends('layout.default') @extends('layout.default')
@section('title', $L('Habits')) @section('title', $L('Chores'))
@section('activeNav', 'habits') @section('activeNav', 'chores')
@section('viewJsName', 'habits') @section('viewJsName', 'chores')
@section('content') @section('content')
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<h1> <h1>
@yield('title') @yield('title')
<a class="btn btn-outline-dark" href="{{ $U('/habit/new') }}"> <a class="btn btn-outline-dark" href="{{ $U('/chore/new') }}">
<i class="fas fa-plus"></i>&nbsp;{{ $L('Add') }} <i class="fas fa-plus"></i>&nbsp;{{ $L('Add') }}
</a> </a>
</h1> </h1>
@@ -25,7 +25,7 @@
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<table id="habits-table" class="table table-sm table-striped dt-responsive"> <table id="chores-table" class="table table-sm table-striped dt-responsive">
<thead> <thead>
<tr> <tr>
<th>#</th> <th>#</th>
@@ -36,27 +36,27 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@foreach($habits as $habit) @foreach($chores as $chore)
<tr> <tr>
<td class="fit-content"> <td class="fit-content">
<a class="btn btn-info btn-sm" href="{{ $U('/habit/') }}{{ $habit->id }}"> <a class="btn btn-info btn-sm" href="{{ $U('/chore/') }}{{ $chore->id }}">
<i class="fas fa-edit"></i> <i class="fas fa-edit"></i>
</a> </a>
<a class="btn btn-danger btn-sm habit-delete-button" href="#" data-habit-id="{{ $habit->id }}" data-habit-name="{{ $habit->name }}"> <a class="btn btn-danger btn-sm chore-delete-button" href="#" data-chore-id="{{ $chore->id }}" data-chore-name="{{ $chore->name }}">
<i class="fas fa-trash"></i> <i class="fas fa-trash"></i>
</a> </a>
</td> </td>
<td> <td>
{{ $habit->name }} {{ $chore->name }}
</td> </td>
<td> <td>
{{ $L($habit->period_type) }} {{ $L($chore->period_type) }}
</td> </td>
<td> <td>
{{ $habit->period_days }} {{ $chore->period_days }}
</td> </td>
<td> <td>
{{ $habit->description }} {{ $chore->description }}
</td> </td>
</tr> </tr>
@endforeach @endforeach

View File

@@ -1,8 +1,8 @@
@extends('layout.default') @extends('layout.default')
@section('title', $L('Habits analysis')) @section('title', $L('Chores analysis'))
@section('activeNav', 'habitsanalysis') @section('activeNav', 'choresanalysis')
@section('viewJsName', 'habitsanalysis') @section('viewJsName', 'choresanalysis')
@section('content') @section('content')
<div class="row"> <div class="row">
@@ -13,11 +13,11 @@
<div class="row mt-3"> <div class="row mt-3">
<div class="col-xs-12 col-md-6 col-xl-3"> <div class="col-xs-12 col-md-6 col-xl-3">
<label for="habit-filter">{{ $L('Filter by habit') }}</label> <i class="fas fa-filter"></i> <label for="chore-filter">{{ $L('Filter by chore') }}</label> <i class="fas fa-filter"></i>
<select class="form-control" id="habit-filter"> <select class="form-control" id="chore-filter">
<option value="all">{{ $L('All') }}</option> <option value="all">{{ $L('All') }}</option>
@foreach($habits as $habit) @foreach($chores as $chore)
<option value="{{ $habit->id }}">{{ $habit->name }}</option> <option value="{{ $chore->id }}">{{ $chore->name }}</option>
@endforeach @endforeach
</select> </select>
</div> </div>
@@ -29,27 +29,27 @@
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<table id="habits-analysis-table" class="table table-sm table-striped dt-responsive"> <table id="chores-analysis-table" class="table table-sm table-striped dt-responsive">
<thead> <thead>
<tr> <tr>
<th>{{ $L('Habit') }}</th> <th>{{ $L('Chore') }}</th>
<th>{{ $L('Tracked time') }}</th> <th>{{ $L('Tracked time') }}</th>
<th>{{ $L('Done by') }}</th> <th>{{ $L('Done by') }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@foreach($habitsLog as $habitLogEntry) @foreach($choresLog as $choreLogEntry)
<tr> <tr>
<td> <td>
{{ FindObjectInArrayByPropertyValue($habits, 'id', $habitLogEntry->habit_id)->name }} {{ FindObjectInArrayByPropertyValue($chores, 'id', $choreLogEntry->chore_id)->name }}
</td> </td>
<td> <td>
{{ $habitLogEntry->tracked_time }} {{ $choreLogEntry->tracked_time }}
<time class="timeago timeago-contextual" datetime="{{ $habitLogEntry->tracked_time }}"></time> <time class="timeago timeago-contextual" datetime="{{ $choreLogEntry->tracked_time }}"></time>
</td> </td>
<td> <td>
@if ($habitLogEntry->done_by_user_id !== null && !empty($habitLogEntry->done_by_user_id)) @if ($choreLogEntry->done_by_user_id !== null && !empty($choreLogEntry->done_by_user_id))
{{ GetUserDisplayName(FindObjectInArrayByPropertyValue($users, 'id', $habitLogEntry->done_by_user_id)) }} {{ GetUserDisplayName(FindObjectInArrayByPropertyValue($users, 'id', $choreLogEntry->done_by_user_id)) }}
@else @else
{{ $L('Unknown') }} {{ $L('Unknown') }}
@endif @endif

View File

@@ -0,0 +1,72 @@
@extends('layout.default')
@section('title', $L('Chores overview'))
@section('activeNav', 'choresoverview')
@section('viewJsName', 'choresoverview')
@push('pageScripts')
<script src="{{ $U('/node_modules/jquery-ui-dist/jquery-ui.min.js?v=', true) }}{{ $version }}"></script>
@endpush
@section('content')
<div class="row">
<div class="col">
<h1>@yield('title')</h1>
<p id="info-due-chores" data-next-x-days="{{ $nextXDays }}" class="btn btn-lg btn-warning no-real-button responsive-button mr-2"></p>
<p id="info-overdue-chores" class="btn btn-lg btn-danger no-real-button responsive-button"></p>
</div>
</div>
<div class="row mt-3">
<div class="col-xs-12 col-md-6 col-xl-3">
<label for="search">{{ $L('Search') }}</label> <i class="fas fa-search"></i>
<input type="text" class="form-control" id="search">
</div>
</div>
<div class="row">
<div class="col">
<table id="chores-overview-table" class="table table-sm table-striped dt-responsive">
<thead>
<tr>
<th>#</th>
<th>{{ $L('Chore') }}</th>
<th>{{ $L('Next estimated tracking') }}</th>
<th>{{ $L('Last tracked') }}</th>
</tr>
</thead>
<tbody>
@foreach($currentChores as $curentChoreEntry)
<tr id="chore-{{ $curentChoreEntry->chore_id }}-row" class="@if(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type === \Grocy\Services\ChoresService::CHORE_TYPE_DYNAMIC_REGULAR && $curentChoreEntry->next_estimated_execution_time < date('Y-m-d H:i:s')) table-danger @elseif(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type === \Grocy\Services\ChoresService::CHORE_TYPE_DYNAMIC_REGULAR && $curentChoreEntry->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-chore-button" href="#" data-toggle="tooltip" title="{{ $L('Track execution of chore #1', FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->name) }}"
data-chore-id="{{ $curentChoreEntry->chore_id }}"
data-chore-name="{{ FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->name }}">
<i class="fas fa-play"></i>
</a>
<a class="btn btn-info btn-sm" href="{{ $U('/choresanalysis?chore=') }}{{ $curentChoreEntry->chore_id }}">
<i class="fas fa-chart-line"></i>
</a>
</td>
<td>
{{ FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->name }}
</td>
<td>
@if(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type === \Grocy\Services\ChoresService::CHORE_TYPE_DYNAMIC_REGULAR)
<span id="chore-{{ $curentChoreEntry->chore_id }}-next-execution-time">{{ $curentChoreEntry->next_estimated_execution_time }}</span>
<time id="chore-{{ $curentChoreEntry->chore_id }}-next-execution-time-timeago" class="timeago timeago-contextual" datetime="{{ $curentChoreEntry->next_estimated_execution_time }}"></time>
@else
...
@endif
</td>
<td>
<span id="chore-{{ $curentChoreEntry->chore_id }}-last-tracked-time">{{ $curentChoreEntry->last_tracked_time }}</span>
<time id="chore-{{ $curentChoreEntry->chore_id }}-last-tracked-time-timeago" class="timeago timeago-contextual" datetime="{{ $curentChoreEntry->last_tracked_time }}"></time>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@stop

View File

@@ -1,25 +1,25 @@
@extends('layout.default') @extends('layout.default')
@section('title', $L('Habit tracking')) @section('title', $L('Chore tracking'))
@section('activeNav', 'habittracking') @section('activeNav', 'choretracking')
@section('viewJsName', 'habittracking') @section('viewJsName', 'choretracking')
@section('content') @section('content')
<div class="row"> <div class="row">
<div class="col-xs-12 col-md-6 col-xl-4 pb-3"> <div class="col-xs-12 col-md-6 col-xl-4 pb-3">
<h1>@yield('title')</h1> <h1>@yield('title')</h1>
<form id="habittracking-form" novalidate> <form id="choretracking-form" novalidate>
<div class="form-group"> <div class="form-group">
<label for="habit_id">{{ $L('Habit') }}</label> <label for="chore_id">{{ $L('Chore') }}</label>
<select class="form-control combobox" id="habit_id" name="habit_id" required> <select class="form-control combobox" id="chore_id" name="chore_id" required>
<option value=""></option> <option value=""></option>
@foreach($habits as $habit) @foreach($chores as $chore)
<option value="{{ $habit->id }}">{{ $habit->name }}</option> <option value="{{ $chore->id }}">{{ $chore->name }}</option>
@endforeach @endforeach
</select> </select>
<div class="invalid-feedback">{{ $L('You have to select a habit') }}</div> <div class="invalid-feedback">{{ $L('You have to select a chore') }}</div>
</div> </div>
@include('components.datetimepicker', array( @include('components.datetimepicker', array(
@@ -39,13 +39,13 @@
'prefillByUserId' => GROCY_USER_ID 'prefillByUserId' => GROCY_USER_ID
)) ))
<button id="save-habittracking-button" type="submit" class="btn btn-success">{{ $L('OK') }}</button> <button id="save-choretracking-button" type="submit" class="btn btn-success">{{ $L('OK') }}</button>
</form> </form>
</div> </div>
<div class="col-xs-12 col-md-6 col-xl-4"> <div class="col-xs-12 col-md-6 col-xl-4">
@include('components.habitcard') @include('components.chorecard')
</div> </div>
</div> </div>
@stop @stop

View File

@@ -0,0 +1,15 @@
@push('componentScripts')
<script src="{{ $U('/viewjs/components/chorecard.js', true) }}?v={{ $version }}"></script>
@endpush
<div class="card">
<div class="card-header">
<i class="fas fa-refresh"></i> {{ $L('Chore overview') }}
</div>
<div class="card-body">
<h3><span id="chorecard-chore-name"></span></h3>
<strong>{{ $L('Tracked count') }}:</strong> <span id="chorecard-chore-tracked-count"></span><br>
<strong>{{ $L('Last tracked') }}:</strong> <span id="chorecard-chore-last-tracked"></span> <time id="chorecard-chore-last-tracked-timeago" class="timeago timeago-contextual"></time><br>
<strong>{{ $L('Last done by') }}:</strong> <span id="chorecard-chore-last-done-by"></span>
</div>
</div>

View File

@@ -1,15 +0,0 @@
@push('componentScripts')
<script src="{{ $U('/viewjs/components/habitcard.js', true) }}?v={{ $version }}"></script>
@endpush
<div class="card">
<div class="card-header">
<i class="fas fa-refresh"></i> {{ $L('Habit overview') }}
</div>
<div class="card-body">
<h3><span id="habitcard-habit-name"></span></h3>
<strong>{{ $L('Tracked count') }}:</strong> <span id="habitcard-habit-tracked-count"></span><br>
<strong>{{ $L('Last tracked') }}:</strong> <span id="habitcard-habit-last-tracked"></span> <time id="habitcard-habit-last-tracked-timeago" class="timeago timeago-contextual"></time><br>
<strong>{{ $L('Last done by') }}:</strong> <span id="habitcard-habit-last-done-by"></span>
</div>
</div>

View File

@@ -1,72 +0,0 @@
@extends('layout.default')
@section('title', $L('Habits overview'))
@section('activeNav', 'habitsoverview')
@section('viewJsName', 'habitsoverview')
@push('pageScripts')
<script src="{{ $U('/node_modules/jquery-ui-dist/jquery-ui.min.js?v=', true) }}{{ $version }}"></script>
@endpush
@section('content')
<div class="row">
<div class="col">
<h1>@yield('title')</h1>
<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>
<div class="row mt-3">
<div class="col-xs-12 col-md-6 col-xl-3">
<label for="search">{{ $L('Search') }}</label> <i class="fas fa-search"></i>
<input type="text" class="form-control" id="search">
</div>
</div>
<div class="row">
<div class="col">
<table id="habits-overview-table" class="table table-sm table-striped dt-responsive">
<thead>
<tr>
<th>#</th>
<th>{{ $L('Habit') }}</th>
<th>{{ $L('Next estimated tracking') }}</th>
<th>{{ $L('Last tracked') }}</th>
</tr>
</thead>
<tbody>
@foreach($currentHabits as $curentHabitEntry)
<tr id="habit-{{ $curentHabitEntry->habit_id }}-row" 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="#" data-toggle="tooltip" title="{{ $L('Track execution of habit #1', FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->name) }}"
data-habit-id="{{ $curentHabitEntry->habit_id }}"
data-habit-name="{{ FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->name }}">
<i class="fas fa-play"></i>
</a>
<a class="btn btn-info btn-sm" href="{{ $U('/habitsanalysis?habit=') }}{{ $curentHabitEntry->habit_id }}">
<i class="fas fa-chart-line"></i>
</a>
</td>
<td>
{{ FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->name }}
</td>
<td>
@if(FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->period_type === \Grocy\Services\HabitsService::HABIT_TYPE_DYNAMIC_REGULAR)
<span id="habit-{{ $curentHabitEntry->habit_id }}-next-execution-time">{{ $curentHabitEntry->next_estimated_execution_time }}</span>
<time id="habit-{{ $curentHabitEntry->habit_id }}-next-execution-time-timeago" class="timeago timeago-contextual" datetime="{{ $curentHabitEntry->next_estimated_execution_time }}"></time>
@else
...
@endif
</td>
<td>
<span id="habit-{{ $curentHabitEntry->habit_id }}-last-tracked-time">{{ $curentHabitEntry->last_tracked_time }}</span>
<time id="habit-{{ $curentHabitEntry->habit_id }}-last-tracked-time-timeago" class="timeago timeago-contextual" datetime="{{ $curentHabitEntry->last_tracked_time }}"></time>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@stop

View File

@@ -60,10 +60,10 @@
<span class="nav-link-text">{{ $L('Stock overview') }}</span> <span class="nav-link-text">{{ $L('Stock overview') }}</span>
</a> </a>
</li> </li>
<li class="nav-item" data-toggle="tooltip" data-placement="right" title="{{ $L('Habits overview') }}" data-nav-for-page="habitsoverview"> <li class="nav-item" data-toggle="tooltip" data-placement="right" title="{{ $L('Chores overview') }}" data-nav-for-page="choresoverview">
<a class="nav-link discrete-link" href="{{ $U('/habitsoverview') }}"> <a class="nav-link discrete-link" href="{{ $U('/choresoverview') }}">
<i class="fas fa-tachometer-alt"></i> <i class="fas fa-tachometer-alt"></i>
<span class="nav-link-text">{{ $L('Habits overview') }}</span> <span class="nav-link-text">{{ $L('Chores overview') }}</span>
</a> </a>
</li> </li>
<li class="nav-item" data-toggle="tooltip" data-placement="right" title="{{ $L('Batteries overview') }}" data-nav-for-page="batteriesoverview"> <li class="nav-item" data-toggle="tooltip" data-placement="right" title="{{ $L('Batteries overview') }}" data-nav-for-page="batteriesoverview">
@@ -103,10 +103,10 @@
<span class="nav-link-text">{{ $L('Inventory') }}</span> <span class="nav-link-text">{{ $L('Inventory') }}</span>
</a> </a>
</li> </li>
<li class="nav-item" data-toggle="tooltip" data-placement="right" title="{{ $L('Habit tracking') }}" data-nav-for-page="habittracking"> <li class="nav-item" data-toggle="tooltip" data-placement="right" title="{{ $L('Chore tracking') }}" data-nav-for-page="choretracking">
<a class="nav-link discrete-link" href="{{ $U('/habittracking') }}"> <a class="nav-link discrete-link" href="{{ $U('/choretracking') }}">
<i class="fas fa-play"></i> <i class="fas fa-play"></i>
<span class="nav-link-text">{{ $L('Habit tracking') }}</span> <span class="nav-link-text">{{ $L('Chore tracking') }}</span>
</a> </a>
</li> </li>
<li class="nav-item" data-toggle="tooltip" data-placement="right" title="{{ $L('Battery tracking') }}" data-nav-for-page="batterytracking"> <li class="nav-item" data-toggle="tooltip" data-placement="right" title="{{ $L('Battery tracking') }}" data-nav-for-page="batterytracking">
@@ -140,10 +140,10 @@
<span class="nav-link-text">{{ $L('Quantity units') }}</span> <span class="nav-link-text">{{ $L('Quantity units') }}</span>
</a> </a>
</li> </li>
<li data-nav-for-page="habits" data-sub-menu-of="#top-nav-manager-master-data"> <li data-nav-for-page="chores" data-sub-menu-of="#top-nav-manager-master-data">
<a class="nav-link discrete-link" href="{{ $U('/habits') }}"> <a class="nav-link discrete-link" href="{{ $U('/chores') }}">
<i class="fas fa-sync-alt"></i> <i class="fas fa-sync-alt"></i>
<span class="nav-link-text">{{ $L('Habits') }}</span> <span class="nav-link-text">{{ $L('Chores') }}</span>
</a> </a>
</li> </li>
<li data-nav-for-page="batteries" data-sub-menu-of="#top-nav-manager-master-data"> <li data-nav-for-page="batteries" data-sub-menu-of="#top-nav-manager-master-data">