Added habit tracking

This commit is contained in:
Bernd Bestel
2017-07-25 20:03:31 +02:00
parent ebe92335a6
commit 1241261ca4
18 changed files with 671 additions and 15 deletions

54
views/habitform.js Normal file
View File

@@ -0,0 +1,54 @@
$('#save-habit-button').on('click', function(e)
{
e.preventDefault();
if (Grocy.EditMode === 'create')
{
Grocy.PostJson('/api/add-object/habits', $('#habit-form').serializeJSON(),
function(result)
{
window.location.href = '/habits';
},
function(xhr)
{
console.error(xhr);
}
);
}
else
{
Grocy.PostJson('/api/edit-object/habits/' + Grocy.EditObjectId, $('#habit-form').serializeJSON(),
function(result)
{
window.location.href = '/habits';
},
function(xhr)
{
console.error(xhr);
}
);
}
});
$(function()
{
$('#name').focus();
$('#habit-form').validator();
$('#habit-form').validator('validate');
});
$('.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('This means it is estimated that a new "execution" of this habit is tracked ' + periodDays.toString() + ' days after the last was tracked.');
$('#habit-period-type-info').show();
}
else
{
$('#habit-period-type-info').hide();
}
});

46
views/habitform.php Normal file
View File

@@ -0,0 +1,46 @@
<div class="col-sm-3 col-sm-offset-3 col-md-4 col-md-offset-2 main">
<h1 class="page-header"><?php echo $title; ?></h1>
<script>Grocy.EditMode = '<?php echo $mode; ?>';</script>
<?php if ($mode == 'edit') : ?>
<script>Grocy.EditObjectId = <?php echo $habit->id; ?>;</script>
<?php endif; ?>
<form id="habit-form">
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" required id="name" name="name" value="<?php if ($mode == 'edit') echo $habit->name; ?>" />
<div class="help-block with-errors"></div>
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea class="form-control" rows="2" id="description" name="description"><?php if ($mode == 'edit') echo $habit->description; ?></textarea>
</div>
<div class="form-group">
<label for="period_type">Location</label>
<select required class="form-control input-group-habit-period-type" id="period_type" name="period_type">
<?php foreach ($periodTypes as $periodType) : ?>
<option <?php if ($mode == 'edit' && $periodType == $habit->period_type) echo 'selected="selected"'; ?> value="<?php echo $periodType; ?>"><?php echo $periodType; ?></option>
<?php endforeach; ?>
</select>
<div class="help-block with-errors"></div>
</div>
<div class="form-group">
<label for="period_days">Period days</label>
<input type="number" class="form-control input-group-habit-period-type" id="period_days" name="period_days" value="<?php if ($mode == 'edit') echo $habit->period_days; ?>" />
<div class="help-block with-errors"></div>
</div>
<p id="habit-period-type-info" class="help-block text-muted"></p>
<button id="save-habit-button" type="submit" class="btn btn-default">Save</button>
</form>
</div>

43
views/habits.js Normal file
View File

@@ -0,0 +1,43 @@
$(document).on('click', '.habit-delete-button', function(e)
{
bootbox.confirm({
message: 'Delete habit <strong>' + $(e.target).attr('data-habit-name') + '</strong>?',
buttons: {
confirm: {
label: 'Yes',
className: 'btn-success'
},
cancel: {
label: 'No',
className: 'btn-danger'
}
},
callback: function(result)
{
if (result === true)
{
Grocy.FetchJson('/api/delete-object/habits/' + $(e.target).attr('data-habit-id'),
function(result)
{
window.location.href = '/habits';
},
function(xhr)
{
console.error(xhr);
}
);
}
}
});
});
$(function()
{
$('#habits-table').DataTable({
'pageLength': 50,
'order': [[1, 'asc']],
'columnDefs': [
{ 'orderable': false, 'targets': 0 }
]
});
});

50
views/habits.php Normal file
View File

@@ -0,0 +1,50 @@
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1 class="page-header">
Habits
<a class="btn btn-default" href="/habit/new" role="button">
<i class="fa fa-plus"></i>&nbsp;Add
</a>
</h1>
<div class="table-responsive">
<table id="habits-table" class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Period type</th>
<th>Period days</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<?php foreach ($habits as $habit) : ?>
<tr>
<td class="fit-content">
<a class="btn btn-info" href="/habit/<?php echo $habit->id; ?>" role="button">
<i class="fa fa-pencil"></i>
</a>
<a class="btn btn-danger habit-delete-button" href="#" role="button" data-habit-id="<?php echo $habit->id; ?>" data-habit-name="<?php echo $habit->name; ?>">
<i class="fa fa-trash"></i>
</a>
</td>
<td>
<?php echo $habit->name; ?>
</td>
<td>
<?php echo $habit->period_type; ?>
</td>
<td>
<?php echo $habit->period_days; ?>
</td>
<td>
<?php echo $habit->description; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>

7
views/habitsoverview.js Normal file
View File

@@ -0,0 +1,7 @@
$(function()
{
$('#habits-overview-table').DataTable({
'pageLength': 50,
'order': [[1, 'desc']]
});
});

38
views/habitsoverview.php Normal file
View File

@@ -0,0 +1,38 @@
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1 class="page-header">Habits overview</h1>
<div class="table-responsive">
<table id="habits-overview-table" class="table table-striped">
<thead>
<tr>
<th>Habit</th>
<th>Next estimated tracking</th>
<th>Last tracked</th>
</tr>
</thead>
<tbody>
<?php foreach ($currentHabits as $curentHabitEntry) : ?>
<tr class="<?php if (GrocyPhpHelper::FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->period_type === GrocyLogicHabits::HABIT_TYPE_DYNAMIC_REGULAR && GrocyLogicHabits::GetNextHabitTime($curentHabitEntry->habit_id) < date('Y-m-d H:i:s')) echo 'error-bg'; ?>">
<td>
<?php echo GrocyPhpHelper::FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->name; ?>
</td>
<td>
<?php if (GrocyPhpHelper::FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->period_type === GrocyLogicHabits::HABIT_TYPE_DYNAMIC_REGULAR): ?>
<?php echo GrocyLogicHabits::GetNextHabitTime($curentHabitEntry->habit_id); ?>
<time class="timeago timeago-contextual" datetime="<?php echo GrocyLogicHabits::GetNextHabitTime($curentHabitEntry->habit_id); ?>"></time>
<?php else: ?>
Whenever you want...
<?php endif; ?>
</td>
<td>
<?php echo $curentHabitEntry->last_tracked_time; ?>
<time class="timeago timeago-contextual" datetime="<?php echo $curentHabitEntry->last_tracked_time; ?>"></time>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>

165
views/habittracking.js Normal file
View File

@@ -0,0 +1,165 @@
$('#save-habittracking-button').on('click', function(e)
{
e.preventDefault();
var jsonForm = $('#habittracking-form').serializeJSON();
Grocy.FetchJson('/api/habits/get-habit-details/' + jsonForm.habit_id,
function (habitDetails)
{
Grocy.FetchJson('/api/habits/track-habit/' + jsonForm.habit_id + '?tracked_time=' + $('#tracked_time').val(),
function(result)
{
toastr.success('Tracked execution of habit ' + habitDetails.habit.name + ' on ' + $('#tracked_time').val());
$('#habit_id').val('');
$('#habit_id_text_input').focus();
$('#habit_id_text_input').val('');
$('#tracked_time').val(moment().format('YYYY-MM-DD HH:mm:ss'));
$('#tracked_time').trigger('change');
$('#habit_id_text_input').trigger('change');
$('#habittracking-form').validator('validate');
},
function(xhr)
{
console.error(xhr);
}
);
},
function(xhr)
{
console.error(xhr);
}
);
});
$('#habit_id').on('change', function(e)
{
var habitId = $(e.target).val();
if (habitId)
{
Grocy.FetchJson('/api/habits/get-habit-details/' + habitId,
function(habitDetails)
{
$('#selected-habit-name').text(habitDetails.habit.name);
$('#selected-habit-last-tracked').text((habitDetails.last_tracked || 'never'));
$('#selected-habit-last-tracked-timeago').text($.timeago(habitDetails.last_tracked || ''));
$('#selected-habit-tracked-count').text((habitDetails.tracked_count || '0'));
Grocy.EmptyElementWhenMatches('#selected-habit-last-tracked-timeago', 'NaN years ago');
},
function(xhr)
{
console.error(xhr);
}
);
}
});
$(function()
{
$('.datetimepicker').datetimepicker(
{
format: 'YYYY-MM-DD HH:mm:ss',
showTodayButton: true,
calendarWeeks: true,
maxDate: moment()
});
$('#tracked_time').val(moment().format('YYYY-MM-DD HH:mm:ss'));
$('#tracked_time').trigger('change');
$('#tracked_time').on('focus', function(e)
{
if ($('#habit_id_text_input').val().length === 0)
{
$('#habit_id_text_input').focus();
}
});
$('.combobox').combobox({
appendId: '_text_input'
});
$('#habit_id').val('');
$('#habit_id_text_input').focus();
$('#habit_id_text_input').val('');
$('#habit_id_text_input').trigger('change');
$('#habittracking-form').validator();
$('#habittracking-form').validator('validate');
$('#habittracking-form input').keydown(function(event)
{
if (event.keyCode === 13) //Enter
{
if ($('#habittracking-form').validator('validate').has('.has-error').length !== 0) //There is at least one validation error
{
event.preventDefault();
return false;
}
}
});
});
$('#tracked_time').on('change', function(e)
{
var value = $('#tracked_time').val();
var now = new Date();
var centuryStart = Number.parseInt(now.getFullYear().toString().substring(0, 2) + '00');
var centuryEnd = Number.parseInt(now.getFullYear().toString().substring(0, 2) + '99');
if (value === 'x' || value === 'X') {
value = '29991231';
}
if (value.length === 4 && !(Number.parseInt(value) > centuryStart && Number.parseInt(value) < centuryEnd))
{
value = (new Date()).getFullYear().toString() + value;
}
if (value.length === 8 && $.isNumeric(value))
{
value = value.replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3');
$('#tracked_time').val(value);
$('#habittracking-form').validator('validate');
}
});
$('#tracked_time').on('keypress', function(e)
{
var element = $(e.target);
var value = element.val();
var dateObj = moment(element.val(), 'YYYY-MM-DD', true);
$('.datepicker').datepicker('hide');
//If input is empty and any arrow key is pressed, set date to today
if (value.length === 0 && (e.keyCode === 38 || e.keyCode === 40 || e.keyCode === 37 || e.keyCode === 39))
{
dateObj = moment(new Date(), 'YYYY-MM-DD', true);
}
if (dateObj.isValid())
{
if (e.keyCode === 38) //Up
{
element.val(dateObj.add(-1, 'days').format('YYYY-MM-DD'));
}
else if (e.keyCode === 40) //Down
{
element.val(dateObj.add(1, 'days').format('YYYY-MM-DD'));
}
else if (e.keyCode === 37) //Left
{
element.val(dateObj.add(-1, 'weeks').format('YYYY-MM-DD'));
}
else if (e.keyCode === 39) //Right
{
element.val(dateObj.add(1, 'weeks').format('YYYY-MM-DD'));
}
}
$('#habittracking-form').validator('validate');
});

42
views/habittracking.php Normal file
View File

@@ -0,0 +1,42 @@
<div class="col-sm-3 col-sm-offset-3 col-md-3 col-md-offset-2 main">
<h1 class="page-header">Habit tracking</h1>
<form id="habittracking-form">
<div class="form-group">
<label for="habit_id">Habit</label>
<select class="form-control combobox" id="habit_id" name="habit_id" required>
<option value=""></option>
<?php foreach ($habits as $habit) : ?>
<option value="<?php echo $habit->id; ?>"><?php echo $habit->name; ?></option>
<?php endforeach; ?>
</select>
<div id="product-error" class="help-block with-errors"></div>
</div>
<div class="form-group">
<label for="tracked_time">Tracked time</label>
<div class="input-group date datetimepicker">
<input type="text" class="form-control" id="tracked_time" name="tracked_time" required >
<span class="input-group-addon">
<span class="fa fa-calendar"></span>
</span>
</div>
<div class="help-block with-errors"></div>
</div>
<button id="save-habittracking-button" type="submit" class="btn btn-default">OK</button>
</form>
</div>
<div class="col-sm-6 col-md-5 col-lg-3 main well">
<h3>Habit overview <strong><span id="selected-habit-name"></span></strong></h3>
<p>
<strong>Tracked count:</strong> <span id="selected-habit-tracked-count"></span><br />
<strong>Last tracked:</strong> <span id="selected-habit-last-tracked"></span> <time id="selected-habit-last-tracked-timeago" class="timeago timeago-contextual"></time><br />
</p>
</div>

View File

@@ -20,6 +20,7 @@
<link href="/bower_components/datatables.net-responsive-bs/css/responsive.bootstrap.min.css?v=<?php echo Grocy::GetInstalledVersion(); ?>" rel="stylesheet" />
<link href="/bower_components/toastr/toastr.min.css?v=<?php echo Grocy::GetInstalledVersion(); ?>" rel="stylesheet" />
<link href="/bower_components/tagmanager/tagmanager.css?v=<?php echo Grocy::GetInstalledVersion(); ?>" rel="stylesheet" />
<link href="/bower_components/eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.min.css?v=<?php echo Grocy::GetInstalledVersion(); ?>" rel="stylesheet" />
<link href="/style.css?v=<?php echo Grocy::GetInstalledVersion(); ?>" rel="stylesheet" />
<script src="/bower_components/jquery/dist/jquery.min.js?v=<?php echo Grocy::GetInstalledVersion(); ?>"></script>
@@ -49,9 +50,15 @@
<div id="navbar-mobile" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li data-nav-for-page="dashboard.php">
<a class="discrete-link" href="/"><i class="fa fa-tachometer fa-fw"></i>&nbsp;Dashboard</a>
<li data-nav-for-page="stockoverview.php">
<a class="discrete-link" href="/stockoverview"><i class="fa fa-tachometer fa-fw"></i>&nbsp;Stock overview</a>
</li>
<li data-nav-for-page="habitsoverview.php">
<a class="discrete-link" href="/habitsoverview"><i class="fa fa-tachometer fa-fw"></i>&nbsp;Habits overview</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li data-nav-for-page="purchase.php">
<a class="discrete-link" href="/purchase"><i class="fa fa-shopping-cart fa-fw"></i>&nbsp;Purchase</a>
</li>
@@ -64,6 +71,9 @@
<li data-nav-for-page="shoppinglist.php">
<a class="discrete-link" href="/shoppinglist"><i class="fa fa-shopping-bag fa-fw"></i>&nbsp;Shopping list</a>
</li>
<li data-nav-for-page="habittracking.php">
<a class="discrete-link" href="/habittracking"><i class="fa fa-play fa-fw"></i>&nbsp;Habit tracking</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
@@ -77,6 +87,9 @@
<li data-nav-for-page="quantityunits.php">
<a class="discrete-link" href="/quantityunits"><i class="fa fa-balance-scale fa-fw"></i>&nbsp;Quantity units</a>
</li>
<li data-nav-for-page="habits.php">
<a class="discrete-link" href="/habits"><i class="fa fa-refresh fa-fw"></i>&nbsp;Habits</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
@@ -95,9 +108,15 @@
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
<li data-nav-for-page="dashboard.php">
<a class="discrete-link" href="/"><i class="fa fa-tachometer fa-fw"></i>&nbsp;Dashboard</a>
<li data-nav-for-page="stockoverview.php">
<a class="discrete-link" href="/stockoverview"><i class="fa fa-tachometer fa-fw"></i>&nbsp;Stock overview</a>
</li>
<li data-nav-for-page="habitsoverview.php">
<a class="discrete-link" href="/habitsoverview"><i class="fa fa-tachometer fa-fw"></i>&nbsp;Habits overview</a>
</li>
</ul>
<ul class="nav nav-sidebar">
<li data-nav-for-page="purchase.php">
<a class="discrete-link" href="/purchase"><i class="fa fa-shopping-cart fa-fw"></i>&nbsp;Purchase</a>
</li>
@@ -110,6 +129,9 @@
<li data-nav-for-page="shoppinglist.php">
<a class="discrete-link" href="/shoppinglist"><i class="fa fa-shopping-bag fa-fw"></i>&nbsp;Shopping list</a>
</li>
<li data-nav-for-page="habittracking.php">
<a class="discrete-link" href="/habittracking"><i class="fa fa-play fa-fw"></i>&nbsp;Habit tracking</a>
</li>
</ul>
<ul class="nav nav-sidebar">
@@ -123,6 +145,9 @@
<li data-nav-for-page="quantityunits.php">
<a class="discrete-link" href="/quantityunits"><i class="fa fa-balance-scale fa-fw"></i>&nbsp;Quantity units</a>
</li>
<li data-nav-for-page="habits.php">
<a class="discrete-link" href="/habits"><i class="fa fa-refresh fa-fw"></i>&nbsp;Habits</a>
</li>
</ul>
<div class="nav-copyright nav nav-sidebar">
@@ -162,6 +187,7 @@
<script src="/bower_components/jquery-timeago/jquery.timeago.js?v=<?php echo Grocy::GetInstalledVersion(); ?>"></script>
<script src="/bower_components/toastr/toastr.min.js?v=<?php echo Grocy::GetInstalledVersion(); ?>"></script>
<script src="/bower_components/tagmanager/tagmanager.js?v=<?php echo Grocy::GetInstalledVersion(); ?>"></script>
<script src="/bower_components/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js?v=<?php echo Grocy::GetInstalledVersion(); ?>"></script>
<?php if (file_exists(__DIR__ . '/' . str_replace('.php', '.js', $contentPage))) : ?>
<script src="/views/<?php echo str_replace('.php', '.js', $contentPage) . '?v=' . Grocy::GetInstalledVersion(); ?>"></script>

View File

@@ -1,6 +1,6 @@
$(function()
{
$('#current-stock-table').DataTable({
$('#stock-overview-table').DataTable({
'pageLength': 50,
'order': [[2, 'asc']]
});

View File

@@ -1,8 +1,6 @@
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1 class="page-header">Dashboard</h1>
<h3>Stock overview <span class="text-muded small"><strong><?php echo count($currentStock) ?></strong> products with <strong><?php echo GrocyPhpHelper::SumArrayValue($currentStock, 'amount'); ?></strong> units in stock</span></h3>
<h1 class="page-header">Stock overview <span class="text-muded small"><strong><?php echo count($currentStock) ?></strong> products with <strong><?php echo GrocyPhpHelper::SumArrayValue($currentStock, 'amount'); ?></strong> units in stock</span></h1>
<div class="container-fluid">
<div class="row">
@@ -15,7 +13,7 @@
<div class="discrete-content-separator-2x"></div>
<div class="table-responsive">
<table id="current-stock-table" class="table table-striped">
<table id="stock-overview-table" class="table table-striped">
<thead>
<tr>
<th>Product</th>