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
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
31 changed files with 653 additions and 622 deletions

View File

@ -2,19 +2,19 @@
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)
{
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');
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
{
$this->HabitsService->TrackHabit($args['habitId'], $trackedTime, $doneBy);
$this->ChoresService->TrackChore($args['choreId'], $trackedTime, $doneBy);
return $this->VoidApiActionResponse($response);
}
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
{
return $this->ApiResponse($this->HabitsService->GetHabitDetails($args['habitId']));
return $this->ApiResponse($this->ChoresService->GetChoreDetails($args['choreId']));
}
catch (\Exception $ex)
{
@ -53,6 +53,6 @@ class HabitsApiController extends BaseApiController
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/Habit"
"$ref": "#/components/schemas/Chore"
},
{
"$ref": "#/components/schemas/Battery"
@ -128,7 +128,7 @@
"$ref": "#/components/schemas/Product"
},
{
"$ref": "#/components/schemas/Habit"
"$ref": "#/components/schemas/Chore"
},
{
"$ref": "#/components/schemas/Battery"
@ -191,7 +191,7 @@
"$ref": "#/components/schemas/Product"
},
{
"$ref": "#/components/schemas/Habit"
"$ref": "#/components/schemas/Chore"
},
{
"$ref": "#/components/schemas/Battery"
@ -274,7 +274,7 @@
"$ref": "#/components/schemas/Product"
},
{
"$ref": "#/components/schemas/Habit"
"$ref": "#/components/schemas/Chore"
},
{
"$ref": "#/components/schemas/Battery"
@ -1041,18 +1041,18 @@
}
}
},
"/habits/track-habit-execution/{habitId}": {
"/chores/track-chore-execution/{choreId}": {
"get": {
"description": "Tracks an execution of the given habit",
"description": "Tracks an execution of the given chore",
"tags": [
"Habits"
"Chores"
],
"parameters": [
{
"in": "path",
"name": "habitId",
"name": "choreId",
"required": true,
"description": "A valid habit id",
"description": "A valid chore id",
"schema": {
"type": "integer"
}
@ -1061,7 +1061,7 @@
"in": "query",
"name": "tracked_time",
"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": {
"type": "date-time"
}
@ -1070,7 +1070,7 @@
"in": "query",
"name": "done_by",
"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": {
"type": "integer"
}
@ -1088,7 +1088,7 @@
}
},
"400": {
"description": "A VoidApiActionResponse object (possible errors are: Not existing habit)",
"description": "A VoidApiActionResponse object (possible errors are: Not existing chore)",
"content": {
"application/json": {
"schema": {
@ -1100,18 +1100,18 @@
}
}
},
"/habits/get-habit-details/{habitId}": {
"/chores/get-chore-details/{choreId}": {
"get": {
"description": "Returns details of the given habit",
"description": "Returns details of the given chore",
"tags": [
"Habits"
"Chores"
],
"parameters": [
{
"in": "path",
"name": "habitId",
"name": "choreId",
"required": true,
"description": "A valid habit id",
"description": "A valid chore id",
"schema": {
"type": "integer"
}
@ -1119,17 +1119,17 @@
],
"responses": {
"200": {
"description": "A HabitDetailsResponse object",
"description": "A ChoreDetailsResponse object",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HabitDetailsResponse"
"$ref": "#/components/schemas/ChoreDetailsResponse"
}
}
}
},
"400": {
"description": "A VoidApiActionResponse object (possible errors are: Not existing habit)",
"description": "A VoidApiActionResponse object (possible errors are: Not existing chore)",
"content": {
"application/json": {
"schema": {
@ -1141,21 +1141,21 @@
}
}
},
"/habits/get-current": {
"/chores/get-current": {
"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": [
"Habits"
"Chores"
],
"responses": {
"200": {
"description": "An array of CurrentHabitResponse objects",
"description": "An array of CurrentChoreResponse objects",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CurrentHabitResponse"
"$ref": "#/components/schemas/CurrentChoreResponse"
}
}
}
@ -1286,7 +1286,7 @@
"type": "string",
"enum": [
"products",
"habits",
"chores",
"batteries",
"locations",
"quantity_units",
@ -1494,20 +1494,20 @@
}
}
},
"HabitDetailsResponse": {
"ChoreDetailsResponse": {
"type": "object",
"properties": {
"habit": {
"$ref": "#/components/schemas/Habit"
"chore": {
"$ref": "#/components/schemas/Chore"
},
"last_tracked": {
"type": "string",
"format": "date-time",
"description": "When this habit was last tracked"
"description": "When this chore was last tracked"
},
"track_count": {
"type": "integer",
"description": "How often this habit was tracked so far"
"description": "How often this chore was tracked so far"
},
"last_done_by": {
"$ref": "#/components/schemas/UserDto"
@ -1521,7 +1521,7 @@
"BatteryDetailsResponse": {
"type": "object",
"properties": {
"habit": {
"chore": {
"$ref": "#/components/schemas/Battery"
},
"last_charged": {
@ -1709,7 +1709,7 @@
}
}
},
"Habit": {
"Chore": {
"type": "object",
"properties": {
"id": {
@ -1737,13 +1737,13 @@
}
}
},
"HabitLogEntry": {
"ChoreLogEntry": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"habit_id": {
"chore_id": {
"type": "integer"
},
"tracked_time": {
@ -1842,10 +1842,10 @@
}
}
},
"CurrentHabitResponse": {
"CurrentChoreResponse": {
"type": "object",
"properties": {
"habit_id": {
"chore_id": {
"type": "integer"
},
"last_tracked_time": {
@ -1855,7 +1855,7 @@
"next_estimated_execution_time": {
"type": "string",
"format": "date-time",
"description": "The next estimated execution time of this habit, 2999-12-31 23:59:59 when the given habit has a period_type of manually"
"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',
'Next best before date' => 'Nächstes MHD',
'Logout' => 'Abmelden',
'Habits overview' => 'Gewohnheiten',
'Chores overview' => 'Hausarbeiten',
'Batteries overview' => 'Batterien',
'Purchase' => 'Einkauf',
'Consume' => 'Verbrauch',
'Inventory' => 'Inventur',
'Shopping list' => 'Einkaufszettel',
'Habit tracking' => 'Gewohnheit-Ausführung',
'Chore tracking' => 'Hausarbeiten-Ausführung',
'Battery tracking' => 'Batterie-Ladzyklus',
'Products' => 'Produkte',
'Locations' => 'Standorte',
'Quantity units' => 'Mengeneinheiten',
'Habits' => 'Gewohnheiten',
'Chores' => 'Hausarbeiten',
'Batteries' => 'Batterien',
'Habit' => 'Gewohnheit',
'Chore' => 'Hausarbeit',
'Next estimated tracking' => 'Nächste geplante Ausführung',
'Last tracked' => 'Zuletzt ausgeführt',
'Battery' => 'Batterie',
@ -41,7 +41,7 @@ return array(
'New amount' => 'Neue Menge',
'Note' => 'Notiz',
'Tracked time' => 'Ausführungszeit',
'Habit overview' => 'Gewohnheit Übersicht',
'Chore overview' => 'Hausarbeit Übersicht',
'Tracked count' => 'Ausführungsanzahl',
'Battery overview' => 'Batterie Übersicht',
'Charge cycles count' => 'Ladezyklen',
@ -68,11 +68,11 @@ return array(
'Create quantity unit' => 'Mengeneinheit erstellen',
'Period type' => 'Periodentyp',
'Period days' => 'Tage/Periode',
'Create habit' => 'Gewohnheit erstellen',
'Create chore' => 'Hausarbeit erstellen',
'Used in' => 'Benutzt in',
'Create battery' => 'Batterie erstellen',
'Edit battery' => 'Batterie bearbeiten',
'Edit habit' => 'Gewohnheit bearbeiten',
'Edit chore' => 'Hausarbeit bearbeiten',
'Edit quantity unit' => 'Mengeneinheit bearbeiten',
'Edit product' => 'Produkt bearbeiten',
'Edit location' => 'Standort bearbeiten',
@ -90,7 +90,7 @@ return array(
'Are you sure to delete battery "#1"?' => 'Battery "#1" wirklich löschen?',
'Yes' => 'Ja',
'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?',
'Create or assign product' => 'Produkt erstellen oder verknüpfen',
'Cancel' => 'Abbrechen',
@ -110,29 +110,29 @@ return array(
'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 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',
'About grocy' => 'Über grocy',
'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 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 habits are overdue to be done' => '#1 Gewohnheiten sind überfällig',
'#1 chores are due to be done within the next #2 days' => '#1 Hausarbeiten stehen in den nächsten #2 Tagen an',
'#1 chores are overdue to be done' => '#1 Hausarbeiten sind überfällig',
'Released on' => 'Veröffentlicht am',
'Consume #3 #1 of #2' => 'Verbrauche #3 #1 #2',
'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',
'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',
'Consume all #1 which are currently in stock' => 'Verbrauche den kompletten Bestand von #1',
'All' => 'Alle',
'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',
'Search' => 'Suche',
'Not logged in' => 'Nicht angemeldet',
'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',
'A name is required' => 'Ein Name ist erforderlich',
'A location is required' => 'Ein Standort ist erforderlich',
@ -183,8 +183,8 @@ return array(
'Done by' => 'Ausgeführt von',
'Last done by' => 'Zuletzt ausgeführt von',
'Unknown' => 'Unbekannt',
'Filter by habit' => 'Nach Gewohnheit filtern',
'Habits analysis' => 'Gewohnheiten Analyse',
'Filter by chore' => 'Nach Hausarbeit filtern',
'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',
'Charge cycle interval (days)' => 'Ladezyklusintervall (Tage)',
'Last price' => 'Letzter Preis',
@ -198,8 +198,8 @@ return array(
'#1 product is below defined min. stock amount' => '#1 Produkt ist unter Mindestbestand',
'Unit' => 'Einheit',
'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 habit is overdue to be done' => '#1 Gewohnheit ist überfällig',
'#1 chore is due to be done within the next #2 days' => '#1 Hausarbeit steht in den nächsten #2 Tagen an',
'#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 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',

View File

@ -9,20 +9,20 @@ return array(
'Amount' => 'quantità',
'Next best before date' => 'Prossima data di scadenza',
'Logout' => 'Logout',
'Habits overview' => 'Riepilogo delle abitudini',
'Chores overview' => 'Riepilogo delle abitudini',
'Batteries overview' => 'Riepilogo delle batterie',
'Purchase' => 'Acquisti',
'Consume' => 'Consumi',
'Inventory' => 'Inventario',
'Shopping list' => 'Lista della spesa',
'Habit tracking' => 'Dati abitudini',
'Chore tracking' => 'Dati abitudini',
'Battery tracking' => 'Dati batterie',
'Products' => 'Prodotti',
'Locations' => 'Posizioni',
'Quantity units' => 'Unità di misura',
'Habits' => 'Abitudini',
'Chores' => 'Abitudini',
'Batteries' => 'Batterie',
'Habit' => 'Abitudine',
'Chore' => 'Abitudine',
'Next estimated tracking' => 'Prossima esecuzione',
'Last tracked' => 'Ultima esecuzione',
'Battery' => 'Batterie',
@ -41,7 +41,7 @@ return array(
'New amount' => 'Nuova quantità',
'Note' => 'Nota',
'Tracked time' => 'Ora di esecuzione',
'Habit overview' => 'Riepilogo dell\'abitudine',
'Chore overview' => 'Riepilogo dell\'abitudine',
'Tracked count' => 'Numero di esecuzioni',
'Battery overview' => 'Riepilogo della batteria',
'Charge cycles count' => 'Numero di ricariche',
@ -68,11 +68,11 @@ return array(
'Create quantity unit' => 'Aggiungi unità di misura',
'Period type' => 'Tipo di ripetizione',
'Period days' => 'Periodo in giorni',
'Create habit' => 'Aggiungi abitudine',
'Create chore' => 'Aggiungi abitudine',
'Used in' => 'Usato in',
'Create battery' => 'Aggiungi batteria',
'Edit battery' => 'Modifica batteria',
'Edit habit' => 'Modifica abitudine',
'Edit chore' => 'Modifica abitudine',
'Edit quantity unit' => 'Modifica unità di misura',
'Edit product' => 'Modifica prodotto',
'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"?',
'Yes' => 'Si',
'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?',
'Create or assign product' => 'Aggiungi o assegna prodotto',
'Cancel' => 'Annulla',
@ -110,29 +110,29 @@ return array(
'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 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',
'About grocy' => 'Riguardo grocy',
'Close' => 'Chiudi',
'#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 habits 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 due to be done within the next #2 days' => '#1 abitudini da eseguire entro #2 giorni',
'#1 chores are overdue to be done' => '#1 abitudini da eseguire',
'Released on' => 'Rilasciato il',
'Consume #3 #1 of #2' => 'Consumati #3 #1 di #2',
'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',
'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',
'Consume all #1 which are currently in stock' => 'Consuma tutto #1 in dispensa',
'All' => 'Tutto',
'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',
'Search' => 'Cerca',
'Not logged in' => 'Non autenticato',
'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',
'A name is required' => 'Inserisci un nome',
'A location is required' => 'Inserisci la posizione',

View File

@ -9,20 +9,20 @@ return array(
'Amount' => 'Antall',
'Next best before date' => 'Kommende best før dato',
'Logout' => 'Logg ut',
'Habits overview' => 'Oversikt Husoppgaver',
'Chores overview' => 'Oversikt Husoppgaver',
'Batteries overview' => 'Oversikt Batteri',
'Purchase' => 'Innkjøp',
'Consume' => 'Forbrukt',
'Inventory' => 'Endre Husholdning',
'Shopping list' => 'Handleliste',
'Habit tracking' => 'Logge Husoppgaver',
'Chore tracking' => 'Logge Husoppgaver',
'Battery tracking' => 'Batteri Ladesyklus',
'Products' => 'Produkter',
'Locations' => 'Lokasjoner',
'Quantity units' => 'Forpakning',
'Habits' => 'Husoppgaver',
'Chores' => 'Husoppgaver',
'Batteries' => 'Batterier',
'Habit' => 'Husoppgave',
'Chore' => 'Husoppgave',
'Next estimated tracking' => 'Neste handling',
'Last tracked' => 'Sist logget',
'Battery' => 'Batteri',
@ -41,7 +41,7 @@ return array(
'New amount' => 'Nytt antall',
'Note' => 'Info',
'Tracked time' => 'Tid utført/ ladet',
'Habit overview' => 'Oversikt Husoppgave',
'Chore overview' => 'Oversikt Husoppgave',
'Tracked count' => 'Antall utførelser/ ladninger',
'Battery overview' => 'Batteri Oversikt',
'Charge cycles count' => 'Antall ladesykluser',
@ -68,11 +68,11 @@ return array(
'Create quantity unit' => 'Opprett forpakning',
'Period type' => 'Gjentakelse',
'Period days' => 'Antall dager for gjentakelse',
'Create habit' => 'Opprett husoppgave',
'Create chore' => 'Opprett husoppgave',
'Used in' => 'Brukt',
'Create battery' => 'Opprett batteri',
'Edit battery' => 'Endre batteri',
'Edit habit' => 'Endre husoppgave',
'Edit chore' => 'Endre husoppgave',
'Edit quantity unit' => 'Endre forpakning',
'Edit product' => 'Endre produkt',
'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"?',
'Yes' => 'Ja',
'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?',
'Create or assign product' => 'Opprett eller tildel til produkt',
'Cancel' => 'Avbryt',
@ -110,29 +110,29 @@ return array(
'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 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',
'About grocy' => 'Om Grocy',
'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 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 habits are overdue to be done' => '#1 husoppgaver har gått over fristen for utførelse',
'#1 chores are due to be done within the next #2 days' => '#1 husoppgaver skal gjøres inne de #2 neste dagene',
'#1 chores are overdue to be done' => '#1 husoppgaver har gått over fristen for utførelse',
'Released on' => 'Utgitt',
'Consume #3 #1 of #2' => 'Forbruk #3 #1 #2',
'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',
'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',
'Consume all #1 which are currently in stock' => 'Konsumér alle #1 som er i husholdningen',
'All' => 'Alle',
'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',
'Search' => 'Søk',
'Not logged in' => 'Ikke logget inn',
'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',
'A name is required' => 'Vennligst fyll inn et navn',
'A location is required' => 'En lokasjon kreves',
@ -183,8 +183,8 @@ return array(
'Done by' => 'Utført av',
'Last done by' => 'Sist utført av',
'Unknown' => 'Ukjent',
'Filter by habit' => 'Filtrér husoppave',
'Habits analysis' => 'Statistikk husoppgaver',
'Filter by chore' => 'Filtrér husoppave',
'Chores analysis' => 'Statistikk husoppgaver',
'0 means suggestions for the next charge cycle are disabled' => '0 betyr neste ladesyklus er avslått',
'Charge cycle interval (days)' => 'Ladesyklysintervall (Dager)',
'Last price' => 'Siste pris',
@ -198,8 +198,8 @@ return array(
'#1 product is below defined min. stock amount' => '#1 Produkt er under minimums husholdningsnivå',
'Unit' => 'Enhet',
'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 habit is overdue to be done' => '#1 husoppgave har gått over fristen for utførelse',
'#1 chore is due to be done within the next #2 days' => '#1 husoppgave skal gjøres inne de #2 neste dagene',
'#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 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',

View File

@ -1,5 +1,5 @@
ALTER TABLE habits_log
ADD done_by_user_id;
ADD done_by_user_id INTEGER;
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,
'order': [[1, 'asc']],
'columnDefs': [
@ -22,16 +22,16 @@ $("#search").on("keyup", function()
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 objectId = $(e.currentTarget).attr('data-habit-id');
var objectName = $(e.currentTarget).attr('data-chore-name');
var objectId = $(e.currentTarget).attr('data-chore-id');
bootbox.confirm({
message: L('Are you sure to delete habit "#1"?', objectName),
message: L('Are you sure to delete chore "#1"?', objectName),
buttons: {
confirm: {
label: L('Yes'),
@ -46,10 +46,10 @@ $(document).on('click', '.habit-delete-button', function (e)
{
if (result === true)
{
Grocy.Api.Get('delete-object/habits/' + objectId,
Grocy.Api.Get('delete-object/chores/' + objectId,
function(result)
{
window.location.href = U('/habits');
window.location.href = U('/chores');
},
function(xhr)
{

View File

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

View File

@ -1,4 +1,4 @@
var habitsOverviewTable = $('#habits-overview-table').DataTable({
var choresOverviewTable = $('#chores-overview-table').DataTable({
'paginate': false,
'order': [[2, 'desc']],
'columnDefs': [
@ -22,55 +22,55 @@ $("#search").on("keyup", function()
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 habitName = $(e.currentTarget).attr('data-habit-name');
var choreId = $(e.currentTarget).attr('data-chore-id');
var choreName = $(e.currentTarget).attr('data-chore-name');
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()
{
Grocy.Api.Get('habits/get-habit-details/' + habitId,
Grocy.Api.Get('chores/get-chore-details/' + choreId,
function(result)
{
var habitRow = $('#habit-' + habitId + '-row');
var nextXDaysThreshold = moment().add($("#info-due-habits").data("next-x-days"), "days");
var choreRow = $('#chore-' + choreId + '-row');
var nextXDaysThreshold = moment().add($("#info-due-chores").data("next-x-days"), "days");
var now = moment();
var nextExecutionTime = moment(result.next_estimated_execution_time);
habitRow.removeClass("table-warning");
habitRow.removeClass("table-danger");
choreRow.removeClass("table-warning");
choreRow.removeClass("table-danger");
if (nextExecutionTime.isBefore(now))
{
habitRow.addClass("table-danger");
choreRow.addClass("table-danger");
}
else if (nextExecutionTime.isBefore(nextXDaysThreshold))
{
habitRow.addClass("table-warning");
choreRow.addClass("table-warning");
}
$('#habit-' + habitId + '-last-tracked-time').parent().effect('highlight', { }, 500);
$('#habit-' + habitId + '-last-tracked-time').fadeOut(500, function()
$('#chore-' + choreId + '-last-tracked-time').parent().effect('highlight', { }, 500);
$('#chore-' + choreId + '-last-tracked-time').fadeOut(500, function()
{
$(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);
$('#habit-' + habitId + '-next-execution-time').fadeOut(500, function()
$('#chore-' + choreId + '-next-execution-time').parent().effect('highlight', { }, 500);
$('#chore-' + choreId + '-next-execution-time').fadeOut(500, function()
{
$(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();
RefreshStatistics();
},
@ -89,8 +89,8 @@ $(document).on('click', '.track-habit-button', function(e)
function RefreshStatistics()
{
var nextXDays = $("#info-due-habits").data("next-x-days");
Grocy.Api.Get('habits/get-current',
var nextXDays = $("#info-due-chores").data("next-x-days");
Grocy.Api.Get('chores/get-current',
function(result)
{
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-overdue-habits").text(Pluralize(overdueCount, L('#1 habit is overdue to be done', overdueCount), L('#1 habits are overdue to be done', overdueCount)));
$("#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-chores").text(Pluralize(overdueCount, L('#1 chore is overdue to be done', overdueCount), L('#1 chores are overdue to be done', overdueCount)));
},
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}/pos/{recipePosId}', '\Grocy\Controllers\RecipesController:RecipePosEditForm');
// Habit routes
$this->get('/habitsoverview', '\Grocy\Controllers\HabitsController:Overview');
$this->get('/habittracking', '\Grocy\Controllers\HabitsController:TrackHabitExecution');
$this->get('/habitsanalysis', '\Grocy\Controllers\HabitsController:Analysis');
// Chore routes
$this->get('/choresoverview', '\Grocy\Controllers\ChoresController:Overview');
$this->get('/choretracking', '\Grocy\Controllers\ChoresController:TrackChoreExecution');
$this->get('/choresanalysis', '\Grocy\Controllers\ChoresController:Analysis');
$this->get('/habits', '\Grocy\Controllers\HabitsController:HabitsList');
$this->get('/habit/{habitId}', '\Grocy\Controllers\HabitsController:HabitEditForm');
$this->get('/chores', '\Grocy\Controllers\ChoresController:ChoresList');
$this->get('/chore/{choreId}', '\Grocy\Controllers\ChoresController:ChoreEditForm');
// Battery routes
$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/consume-recipe/{recipeId}', '\Grocy\Controllers\RecipesApiController:ConsumeRecipe');
// Habits
$this->get('/habits/track-habit-execution/{habitId}', '\Grocy\Controllers\HabitsApiController:TrackHabitExecution');
$this->get('/habits/get-habit-details/{habitId}', '\Grocy\Controllers\HabitsApiController:HabitDetails');
$this->get('/habits/get-current', '\Grocy\Controllers\HabitsApiController:Current');
// Chores
$this->get('/chores/track-chore-execution/{choreId}', '\Grocy\Controllers\ChoresApiController:TrackChoreExecution');
$this->get('/chores/get-chore-details/{choreId}', '\Grocy\Controllers\ChoresApiController:ChoreDetails');
$this->get('/chores/get-current', '\Grocy\Controllers\ChoresApiController:Current');
// Batteries
$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, 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 habits (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('Changed towels in the bathroom')}', 'manually', 5); --1
INSERT INTO chores (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('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')}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->AddMissingProductsToShoppingList();
$habitsService = new HabitsService();
$habitsService->TrackHabit(1, date('Y-m-d H:i:s', strtotime('-5 days')));
$habitsService->TrackHabit(1, date('Y-m-d H:i:s', strtotime('-10 days')));
$habitsService->TrackHabit(1, date('Y-m-d H:i:s', strtotime('-15 days')));
$habitsService->TrackHabit(2, date('Y-m-d H:i:s', strtotime('-10 days')));
$habitsService->TrackHabit(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 = new ChoresService();
$choresService->TrackChore(1, date('Y-m-d H:i:s', strtotime('-5 days')));
$choresService->TrackChore(1, date('Y-m-d H:i:s', strtotime('-10 days')));
$choresService->TrackChore(1, date('Y-m-d H:i:s', strtotime('-15 days')));
$choresService->TrackChore(2, date('Y-m-d H:i:s', strtotime('-10 days')));
$choresService->TrackChore(2, date('Y-m-d H:i:s', strtotime('-20 days')));
$choresService->TrackChore(3, date('Y-m-d H:i:s', strtotime('-17 days')));
$batteriesService = new BatteriesService();
$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')
@if($mode == 'edit')
@section('title', $L('Edit habit'))
@section('title', $L('Edit chore'))
@else
@section('title', $L('Create habit'))
@section('title', $L('Create chore'))
@endif
@section('viewJsName', 'habitform')
@section('viewJsName', 'choreform')
@section('content')
<div class="row">
@ -16,44 +16,44 @@
<script>Grocy.EditMode = '{{ $mode }}';</script>
@if($mode == 'edit')
<script>Grocy.EditObjectId = {{ $habit->id }};</script>
<script>Grocy.EditObjectId = {{ $chore->id }};</script>
@endif
<form id="habit-form" novalidate>
<form id="chore-form" novalidate>
<div class="form-group">
<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>
<div class="form-group">
<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 class="form-group">
<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)
<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
</select>
<div class="invalid-feedback">{{ $L('A period type is required') }}</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(
'id' => 'period_days',
'label' => 'Period days',
'value' => $value,
'min' => '0',
'additionalCssClasses' => 'input-group-habit-period-type',
'additionalCssClasses' => 'input-group-chore-period-type',
'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>
</div>

View File

@ -1,15 +1,15 @@
@extends('layout.default')
@section('title', $L('Habits'))
@section('activeNav', 'habits')
@section('viewJsName', 'habits')
@section('title', $L('Chores'))
@section('activeNav', 'chores')
@section('viewJsName', 'chores')
@section('content')
<div class="row">
<div class="col">
<h1>
@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') }}
</a>
</h1>
@ -25,7 +25,7 @@
<div class="row">
<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>
<tr>
<th>#</th>
@ -36,27 +36,27 @@
</tr>
</thead>
<tbody>
@foreach($habits as $habit)
@foreach($chores as $chore)
<tr>
<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>
</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>
</a>
</td>
<td>
{{ $habit->name }}
{{ $chore->name }}
</td>
<td>
{{ $L($habit->period_type) }}
{{ $L($chore->period_type) }}
</td>
<td>
{{ $habit->period_days }}
{{ $chore->period_days }}
</td>
<td>
{{ $habit->description }}
{{ $chore->description }}
</td>
</tr>
@endforeach

View File

@ -1,8 +1,8 @@
@extends('layout.default')
@section('title', $L('Habits analysis'))
@section('activeNav', 'habitsanalysis')
@section('viewJsName', 'habitsanalysis')
@section('title', $L('Chores analysis'))
@section('activeNav', 'choresanalysis')
@section('viewJsName', 'choresanalysis')
@section('content')
<div class="row">
@ -13,11 +13,11 @@
<div class="row mt-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>
<select class="form-control" id="habit-filter">
<label for="chore-filter">{{ $L('Filter by chore') }}</label> <i class="fas fa-filter"></i>
<select class="form-control" id="chore-filter">
<option value="all">{{ $L('All') }}</option>
@foreach($habits as $habit)
<option value="{{ $habit->id }}">{{ $habit->name }}</option>
@foreach($chores as $chore)
<option value="{{ $chore->id }}">{{ $chore->name }}</option>
@endforeach
</select>
</div>
@ -29,27 +29,27 @@
<div class="row">
<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>
<tr>
<th>{{ $L('Habit') }}</th>
<th>{{ $L('Chore') }}</th>
<th>{{ $L('Tracked time') }}</th>
<th>{{ $L('Done by') }}</th>
</tr>
</thead>
<tbody>
@foreach($habitsLog as $habitLogEntry)
@foreach($choresLog as $choreLogEntry)
<tr>
<td>
{{ FindObjectInArrayByPropertyValue($habits, 'id', $habitLogEntry->habit_id)->name }}
{{ FindObjectInArrayByPropertyValue($chores, 'id', $choreLogEntry->chore_id)->name }}
</td>
<td>
{{ $habitLogEntry->tracked_time }}
<time class="timeago timeago-contextual" datetime="{{ $habitLogEntry->tracked_time }}"></time>
{{ $choreLogEntry->tracked_time }}
<time class="timeago timeago-contextual" datetime="{{ $choreLogEntry->tracked_time }}"></time>
</td>
<td>
@if ($habitLogEntry->done_by_user_id !== null && !empty($habitLogEntry->done_by_user_id))
{{ GetUserDisplayName(FindObjectInArrayByPropertyValue($users, 'id', $habitLogEntry->done_by_user_id)) }}
@if ($choreLogEntry->done_by_user_id !== null && !empty($choreLogEntry->done_by_user_id))
{{ GetUserDisplayName(FindObjectInArrayByPropertyValue($users, 'id', $choreLogEntry->done_by_user_id)) }}
@else
{{ $L('Unknown') }}
@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')
@section('title', $L('Habit tracking'))
@section('activeNav', 'habittracking')
@section('viewJsName', 'habittracking')
@section('title', $L('Chore tracking'))
@section('activeNav', 'choretracking')
@section('viewJsName', 'choretracking')
@section('content')
<div class="row">
<div class="col-xs-12 col-md-6 col-xl-4 pb-3">
<h1>@yield('title')</h1>
<form id="habittracking-form" novalidate>
<form id="choretracking-form" novalidate>
<div class="form-group">
<label for="habit_id">{{ $L('Habit') }}</label>
<select class="form-control combobox" id="habit_id" name="habit_id" required>
<label for="chore_id">{{ $L('Chore') }}</label>
<select class="form-control combobox" id="chore_id" name="chore_id" required>
<option value=""></option>
@foreach($habits as $habit)
<option value="{{ $habit->id }}">{{ $habit->name }}</option>
@foreach($chores as $chore)
<option value="{{ $chore->id }}">{{ $chore->name }}</option>
@endforeach
</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>
@include('components.datetimepicker', array(
@ -39,13 +39,13 @@
'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>
</div>
<div class="col-xs-12 col-md-6 col-xl-4">
@include('components.habitcard')
@include('components.chorecard')
</div>
</div>
@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>
</a>
</li>
<li class="nav-item" data-toggle="tooltip" data-placement="right" title="{{ $L('Habits overview') }}" data-nav-for-page="habitsoverview">
<a class="nav-link discrete-link" href="{{ $U('/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('/choresoverview') }}">
<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>
</li>
<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>
</a>
</li>
<li class="nav-item" data-toggle="tooltip" data-placement="right" title="{{ $L('Habit tracking') }}" data-nav-for-page="habittracking">
<a class="nav-link discrete-link" href="{{ $U('/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('/choretracking') }}">
<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>
</li>
<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>
</a>
</li>
<li data-nav-for-page="habits" data-sub-menu-of="#top-nav-manager-master-data">
<a class="nav-link discrete-link" href="{{ $U('/habits') }}">
<li data-nav-for-page="chores" data-sub-menu-of="#top-nav-manager-master-data">
<a class="nav-link discrete-link" href="{{ $U('/chores') }}">
<i class="fas fa-sync-alt"></i>
<span class="nav-link-text">{{ $L('Habits') }}</span>
<span class="nav-link-text">{{ $L('Chores') }}</span>
</a>
</li>
<li data-nav-for-page="batteries" data-sub-menu-of="#top-nav-manager-master-data">