Add journal and undo UI for stock bookings, chore executions and battery charge cycles (closes #63, closes #97)

This commit is contained in:
Bernd Bestel 2018-10-27 17:26:00 +02:00
parent fe83e2fa6f
commit 364f6b2051
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
28 changed files with 541 additions and 92 deletions

View File

@ -24,8 +24,8 @@ class BatteriesApiController extends BaseApiController
try
{
$this->BatteriesService->TrackChargeCycle($args['batteryId'], $trackedTime);
return $this->VoidApiActionResponse($response);
$chargeCycleId = $this->BatteriesService->TrackChargeCycle($args['batteryId'], $trackedTime);
return $this->ApiResponse(array('charge_cycle_id' => $chargeCycleId));
}
catch (\Exception $ex)
{

View File

@ -53,4 +53,12 @@ class BatteriesController extends BaseController
]);
}
}
public function Journal(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'batteriesjournal', [
'chargeCycles' => $this->Database->battery_charge_cycles()->orderBy('tracked_time', 'DESC'),
'batteries' => $this->Database->batteries()->orderBy('name')
]);
}
}

View File

@ -30,8 +30,8 @@ class ChoresApiController extends BaseApiController
try
{
$this->ChoresService->TrackChore($args['choreId'], $trackedTime, $doneBy);
return $this->VoidApiActionResponse($response);
$choreExecutionId = $this->ChoresService->TrackChore($args['choreId'], $trackedTime, $doneBy);
return $this->ApiResponse(array('chore_execution_id' => $choreExecutionId));
}
catch (\Exception $ex)
{

View File

@ -38,10 +38,10 @@ class ChoresController extends BaseController
]);
}
public function Analysis(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
public function Journal(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'choresanalysis', [
'choresLog' => $this->Database->chores_log()->where('undone', 0)->orderBy('tracked_time', 'DESC'),
return $this->AppContainer->view->render($response, 'choresjournal', [
'choresLog' => $this->Database->chores_log()->orderBy('tracked_time', 'DESC'),
'chores' => $this->Database->chores()->orderBy('name'),
'users' => $this->Database->users()->orderBy('username')
]);

View File

@ -60,8 +60,8 @@ class StockApiController extends BaseApiController
try
{
$this->StockService->AddProduct($args['productId'], $args['amount'], $bestBeforeDate, $transactionType, date('Y-m-d'), $price);
return $this->VoidApiActionResponse($response);
$bookingId = $this->StockService->AddProduct($args['productId'], $args['amount'], $bestBeforeDate, $transactionType, date('Y-m-d'), $price);
return $this->ApiResponse(array('booking_id' => $bookingId));
}
catch (\Exception $ex)
{
@ -85,8 +85,8 @@ class StockApiController extends BaseApiController
try
{
$this->StockService->ConsumeProduct($args['productId'], $args['amount'], $spoiled, $transactionType);
return $this->VoidApiActionResponse($response);
$bookingId = $this->StockService->ConsumeProduct($args['productId'], $args['amount'], $spoiled, $transactionType);
return $this->ApiResponse(array('booking_id' => $bookingId));
}
catch (\Exception $ex)
{
@ -104,8 +104,8 @@ class StockApiController extends BaseApiController
try
{
$this->StockService->InventoryProduct($args['productId'], $args['newAmount'], $bestBeforeDate);
return $this->VoidApiActionResponse($response);
$bookingId = $this->StockService->InventoryProduct($args['productId'], $args['newAmount'], $bestBeforeDate);
return $this->ApiResponse(array('booking_id' => $bookingId));
}
catch (\Exception $ex)
{

View File

@ -192,4 +192,13 @@ class StockController extends BaseController
]);
}
}
public function Journal(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'stockjournal', [
'stockLog' => $this->Database->stock_log()->orderBy('row_created_timestamp', 'DESC'),
'products' => $this->Database->products()->orderBy('name'),
'quantityunits' => $this->Database->quantity_units()->orderBy('name')
]);
}
}

View File

@ -184,7 +184,7 @@ return array(
'Last done by' => 'Zuletzt ausgeführt von',
'Unknown' => 'Unbekannt',
'Filter by chore' => 'Nach Hausarbeit filtern',
'Chores analysis' => 'Hausarbeiten Analyse',
'Chores journal' => 'Hausarbeitenjournal',
'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',
@ -286,11 +286,31 @@ return array(
'Edit included recipe' => 'Enthaltenes Rezept bearbeiten',
'Group' => 'Gruppe',
'This will be used as a headline to group ingredients together' => 'Dies wird als Überschrift verwendet, um Zutaten zusammenzufassen',
'Journal' => 'Journal',
'Stock journal' => 'Bestandsjournal',
'Filter by product' => 'Nach Produkt filtern',
'Booking time' => 'Buchungszeit',
'Booking type' => 'Buchungsart',
'Undo booking' => 'Buchung rückgängig machen',
'Undone on' => 'Rückgängig gemacht am',
'Batteries journal' => 'Batteriejournal',
'Filter by battery' => 'Nach Batterie filtern',
'Undo charge cycle' => 'Ladezyklus rückgängig machen',
'Undo chore execution' => 'Ausführung rückgängig machen',
'Chore execution successfully undone' => 'Ausführung erfolgreich rückgängig gemacht',
'Undo' => 'Rückgängig machen',
'Booking successfully undone' => 'Buchung erfolgreich rückgängig gemacht',
'Charge cycle successfully undone' => 'Ladezyklus erfolgreich rückgängig gemacht',
//Constants
//Constants - Chore types
'manually' => 'Manuell',
'dynamic-regular' => 'Dynamisch regelmäßig',
//Constants - Stock transaction types
'purchase' => 'Einkauf',
'consume' => 'Verbrauch',
'inventory-correction' => 'Inventur-Korrektur',
//Technical component translations
'timeago_locale' => 'de',
'timeago_nan' => 'vor NaN Jahren',

View File

@ -1,10 +1,15 @@
<?php
return array(
//Constants
//Constants - Chore types
'manually' => 'Manually',
'dynamic-regular' => 'Dynamic regular',
//Constants - Stock transaction types
'purchase' => 'Purchase',
'consume' => 'Consume',
'inventory-correction' => 'Inventory correction',
//Technical component translations
'timeago_locale' => 'en',
'timeago_nan' => 'NaN years ago',

View File

@ -184,7 +184,7 @@ return array(
'Last done by' => 'Sist utført av',
'Unknown' => 'Ukjent',
'Filter by chore' => 'Filtrér husarbeid',
'Chores analysis' => 'Statistikk husarbeid',
'Chores journal' => 'Statistikk husarbeid',
'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',

View File

@ -0,0 +1,70 @@
var batteriesJournalTable = $('#batteries-journal-table').DataTable({
'paginate': true,
'order': [[1, 'desc']],
'columnDefs': [
{ 'orderable': false, 'targets': 0 }
],
'language': JSON.parse(L('datatables_localization')),
'scrollY': false,
'colReorder': true,
'stateSave': true,
'stateSaveParams': function(settings, data)
{
data.search.search = "";
data.columns.forEach(column =>
{
column.search.search = "";
});
}
});
$("#battery-filter").on("change", function()
{
var value = $(this).val();
var text = $("#battery-filter option:selected").text();
if (value === "all")
{
text = "";
}
batteriesJournalTable.column(1).search(text).draw();
});
$("#search").on("keyup", function()
{
var value = $(this).val();
if (value === "all")
{
value = "";
}
batteriesJournalTable.search(value).draw();
});
if (typeof GetUriParam("battery") !== "undefined")
{
$("#battery-filter").val(GetUriParam("battery"));
$("#battery-filter").trigger("change");
}
$(document).on('click', '.undo-battery-execution-button', function(e)
{
e.preventDefault();
var element = $(e.currentTarget);
var chargeCycleId = $(e.currentTarget).attr('data-charge-cycle-id');
Grocy.Api.Get('batteries/undo-charge-cycle/' + chargeCycleId.toString(),
function(result)
{
element.closest("tr").addClass("text-muted");
element.closest(".undo-battery-execution-button").addClass("disabled");
toastr.success(L("Charge cycle successfully undone"));
},
function(xhr)
{
console.error(xhr);
}
);
});

View File

@ -10,7 +10,7 @@
Grocy.Api.Get('batteries/track-charge-cycle/' + jsonForm.battery_id + '?tracked_time=' + $('#tracked_time').find('input').val(),
function(result)
{
toastr.success(L('Tracked charge cylce of battery #1 on #2', batteryDetails.battery.name, $('#tracked_time').find('input').val()));
toastr.success(L('Tracked charge cycle of battery #1 on #2', batteryDetails.battery.name, $('#tracked_time').find('input').val()) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoChargeCycle(' + result.charge_cycle_id + ')"><i class="fas fa-undo"></i> ' + L("Undo") + '</a>');
$('#battery_id').val('');
$('#battery_id_text_input').focus();
@ -86,3 +86,16 @@ $('#tracked_time').find('input').on('keypress', function (e)
Grocy.FrontendHelpers.ValidateForm('batterytracking-form');
});
function UndoChargeCycle(chargeCycleId)
{
Grocy.Api.Get('batteries/undo-charge-cycle/' + chargeCycleId.toString(),
function(result)
{
toastr.success(L("Charge cycle successfully undone"));
},
function(xhr)
{
console.error(xhr);
}
);
};

View File

@ -1,46 +0,0 @@
var choresAnalysisTable = $('#chores-analysis-table').DataTable({
'paginate': false,
'order': [[1, 'desc']],
'language': JSON.parse(L('datatables_localization')),
'scrollY': false,
'colReorder': true,
'stateSave': true,
'stateSaveParams': function(settings, data)
{
data.search.search = "";
data.columns.forEach(column =>
{
column.search.search = "";
});
}
});
$("#chore-filter").on("change", function()
{
var value = $(this).val();
var text = $("#chore-filter option:selected").text();
if (value === "all")
{
text = "";
}
choresAnalysisTable.column(0).search(text).draw();
});
$("#search").on("keyup", function()
{
var value = $(this).val();
if (value === "all")
{
value = "";
}
choresAnalysisTable.search(value).draw();
});
if (typeof GetUriParam("chore") !== "undefined")
{
$("#chore-filter").val(GetUriParam("chore"));
$("#chore-filter").trigger("change");
}

View File

@ -0,0 +1,70 @@
var choresJournalTable = $('#chores-journal-table').DataTable({
'paginate': true,
'order': [[1, 'desc']],
'columnDefs': [
{ 'orderable': false, 'targets': 0 }
],
'language': JSON.parse(L('datatables_localization')),
'scrollY': false,
'colReorder': true,
'stateSave': true,
'stateSaveParams': function(settings, data)
{
data.search.search = "";
data.columns.forEach(column =>
{
column.search.search = "";
});
}
});
$("#chore-filter").on("change", function()
{
var value = $(this).val();
var text = $("#chore-filter option:selected").text();
if (value === "all")
{
text = "";
}
choresJournalTable.column(1).search(text).draw();
});
$("#search").on("keyup", function()
{
var value = $(this).val();
if (value === "all")
{
value = "";
}
choresJournalTable.search(value).draw();
});
if (typeof GetUriParam("chore") !== "undefined")
{
$("#chore-filter").val(GetUriParam("chore"));
$("#chore-filter").trigger("change");
}
$(document).on('click', '.undo-chore-execution-button', function(e)
{
e.preventDefault();
var element = $(e.currentTarget);
var executionId = $(e.currentTarget).attr('data-execution-id');
Grocy.Api.Get('chores/undo-chore-execution/' + executionId.toString(),
function(result)
{
element.closest("tr").addClass("text-muted");
element.closest(".undo-chore-execution-button").addClass("disabled");
toastr.success(L("Chore execution successfully undone"));
},
function(xhr)
{
console.error(xhr);
}
);
});

View File

@ -10,7 +10,7 @@
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()));
toastr.success(L('Tracked execution of chore #1 on #2', choreDetails.chore.name, Grocy.Components.DateTimePicker.GetValue()) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoChoreExecution(' + result.chore_execution_id + ')"><i class="fas fa-undo"></i> ' + L("Undo") + '</a>');
$('#chore_id').val('');
$('#chore_id_text_input').focus();
@ -82,3 +82,17 @@ Grocy.Components.DateTimePicker.GetInputElement().on('keypress', function(e)
{
Grocy.FrontendHelpers.ValidateForm('choretracking-form');
});
function UndoChoreExecution(executionId)
{
Grocy.Api.Get('chores/undo-chore-execution/' + executionId.toString(),
function(result)
{
toastr.success(L("Chore execution successfully undone"));
},
function(xhr)
{
console.error(xhr);
}
);
};

View File

@ -16,7 +16,7 @@
Grocy.Api.Get('stock/consume-product/' + jsonForm.product_id + '/' + jsonForm.amount + '?spoiled=' + spoiled,
function(result)
{
toastr.success(L('Removed #1 #2 of #3 from stock', jsonForm.amount, Pluralize(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name));
toastr.success(L('Removed #1 #2 of #3 from stock', jsonForm.amount, Pluralize(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockBooking(' + result.booking_id + ')"><i class="fas fa-undo"></i> ' + L("Undo") + '</a>');
$('#amount').val(1);
Grocy.Components.ProductPicker.SetValue('');
@ -102,3 +102,17 @@ $('#consume-form input').keydown(function(event)
}
}
});
function UndoStockBooking(bookingId)
{
Grocy.Api.Get('stock/undo-booking/' + bookingId.toString(),
function(result)
{
toastr.success(L("Booking successfully undone"));
},
function(xhr)
{
console.error(xhr);
}
);
};

View File

@ -32,7 +32,7 @@
);
}
toastr.success(L('Stock amount of #1 is now #2 #3', productDetails.product.name, jsonForm.new_amount, Pluralize(jsonForm.new_amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural)));
toastr.success(L('Stock amount of #1 is now #2 #3', productDetails.product.name, jsonForm.new_amount, Pluralize(jsonForm.new_amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural)) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockBooking(' + result.booking_id + ')"><i class="fas fa-undo"></i> ' + L("Undo") + '</a>');
if (addBarcode !== undefined)
{
@ -186,3 +186,17 @@ $('#new_amount').on('keyup', function(e)
);
}
});
function UndoStockBooking(bookingId)
{
Grocy.Api.Get('stock/undo-booking/' + bookingId.toString(),
function(result)
{
toastr.success(L("Booking successfully undone"));
},
function(xhr)
{
console.error(xhr);
}
);
};

View File

@ -40,7 +40,7 @@
);
}
toastr.success(L('Added #1 #2 of #3 to stock', amount, Pluralize(amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name));
toastr.success(L('Added #1 #2 of #3 to stock', amount, Pluralize(amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockBooking(' + result.booking_id + ')"><i class="fas fa-undo"></i> ' + L("Undo") + '</a>');
if (addBarcode !== undefined)
{
@ -171,3 +171,17 @@ $('#amount').on('change', function (e)
{
Grocy.FrontendHelpers.ValidateForm('purchase-form');
});
function UndoStockBooking(bookingId)
{
Grocy.Api.Get('stock/undo-booking/' + bookingId.toString(),
function(result)
{
toastr.success(L("Booking successfully undone"));
},
function(xhr)
{
console.error(xhr);
}
);
};

View File

@ -0,0 +1,70 @@
var stockJournalTable = $('#stock-journal-table').DataTable({
'paginate': true,
'order': [[3, 'desc']],
'columnDefs': [
{ 'orderable': false, 'targets': 0 }
],
'language': JSON.parse(L('datatables_localization')),
'scrollY': false,
'colReorder': true,
'stateSave': true,
'stateSaveParams': function(settings, data)
{
data.search.search = "";
data.columns.forEach(column =>
{
column.search.search = "";
});
}
});
$("#product-filter").on("change", function()
{
var value = $(this).val();
var text = $("#product-filter option:selected").text();
if (value === "all")
{
text = "";
}
stockJournalTable.column(1).search(text).draw();
});
$("#search").on("keyup", function()
{
var value = $(this).val();
if (value === "all")
{
value = "";
}
stockJournalTable.search(value).draw();
});
if (typeof GetUriParam("product") !== "undefined")
{
$("#product-filter").val(GetUriParam("product"));
$("#product-filter").trigger("change");
}
$(document).on('click', '.undo-stock-booking-button', function(e)
{
e.preventDefault();
var element = $(e.currentTarget);
var bookingId = $(e.currentTarget).attr('data-booking-id');
Grocy.Api.Get('stock/undo-booking/' + bookingId.toString(),
function(result)
{
element.closest("tr").addClass("text-muted");
element.closest(".undo-stock-booking-button").addClass("disabled");
toastr.success(L("Booking successfully undone"));
},
function(xhr)
{
console.error(xhr);
}
);
});

View File

@ -35,6 +35,7 @@ $app->group('', function()
$this->get('/productgroup/{productGroupId}', '\Grocy\Controllers\StockController:ProductGroupEditForm');
$this->get('/shoppinglist', '\Grocy\Controllers\StockController:ShoppingList');
$this->get('/shoppinglistitem/{itemId}', '\Grocy\Controllers\StockController:ShoppingListItemEditForm');
$this->get('/stockjournal', '\Grocy\Controllers\StockController:Journal');
// Recipe routes
$this->get('/recipes', '\Grocy\Controllers\RecipesController:Overview');
@ -44,7 +45,7 @@ $app->group('', function()
// 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('/choresjournal', '\Grocy\Controllers\ChoresController:Journal');
$this->get('/chores', '\Grocy\Controllers\ChoresController:ChoresList');
$this->get('/chore/{choreId}', '\Grocy\Controllers\ChoresController:ChoreEditForm');
@ -52,6 +53,7 @@ $app->group('', function()
// Battery routes
$this->get('/batteriesoverview', '\Grocy\Controllers\BatteriesController:Overview');
$this->get('/batterytracking', '\Grocy\Controllers\BatteriesController:TrackChargeCycle');
$this->get('/batteriesjournal', '\Grocy\Controllers\BatteriesController:Journal');
$this->get('/batteries', '\Grocy\Controllers\BatteriesController:BatteriesList');
$this->get('/battery/{batteryId}', '\Grocy\Controllers\BatteriesController:BatteryEditForm');

View File

@ -18,14 +18,14 @@ class BatteriesService extends BaseService
}
$battery = $this->Database->batteries($batteryId);
$batteryChargeCylcesCount = $this->Database->battery_charge_cycles()->where('battery_id = :1 AND undone = 0', $batteryId)->count();
$batteryChargeCyclesCount = $this->Database->battery_charge_cycles()->where('battery_id = :1 AND undone = 0', $batteryId)->count();
$batteryLastChargedTime = $this->Database->battery_charge_cycles()->where('battery_id = :1 AND undone = 0', $batteryId)->max('tracked_time');
$nextChargeTime = $this->Database->batteries_current()->where('battery_id', $batteryId)->min('next_estimated_charge_time');
return array(
'battery' => $battery,
'last_charged' => $batteryLastChargedTime,
'charge_cycles_count' => $batteryChargeCylcesCount,
'charge_cycles_count' => $batteryChargeCyclesCount,
'next_estimated_charge_time' => $nextChargeTime
);
}
@ -43,7 +43,7 @@ class BatteriesService extends BaseService
));
$logRow->save();
return true;
return $this->Database->lastInsertId();
}
private function BatteryExists($batteryId)

View File

@ -63,7 +63,7 @@ class ChoresService extends BaseService
));
$logRow->save();
return true;
return $this->Database->lastInsertId();
}
private function ChoreExists($choreId)

View File

@ -47,8 +47,8 @@ class StockService extends BaseService
$product = $this->Database->products($productId);
$productStockAmount = $this->Database->stock()->where('product_id', $productId)->sum('amount');
$productLastPurchased = $this->Database->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_PURCHASE)->max('purchased_date')->where('undone', 0);
$productLastUsed = $this->Database->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_CONSUME)->max('used_date')->where('undone', 0);
$productLastPurchased = $this->Database->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_PURCHASE)->where('undone', 0)->max('purchased_date');
$productLastUsed = $this->Database->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_CONSUME)->where('undone', 0)->max('used_date');
$nextBestBeforeDate = $this->Database->stock()->where('product_id', $productId)->min('best_before_date');
$quPurchase = $this->Database->quantity_units($product->qu_id_purchase);
$quStock = $this->Database->quantity_units($product->qu_id_stock);
@ -113,6 +113,8 @@ class StockService extends BaseService
));
$logRow->save();
$returnValue = $this->Database->lastInsertId();
$stockRow = $this->Database->stock()->createRow(array(
'product_id' => $productId,
'amount' => $amount,
@ -123,7 +125,7 @@ class StockService extends BaseService
));
$stockRow->save();
return true;
return $returnValue;
}
else
{
@ -197,7 +199,7 @@ class StockService extends BaseService
}
}
return true;
return $this->Database->lastInsertId();
}
else
{
@ -226,7 +228,7 @@ class StockService extends BaseService
$this->ConsumeProduct($productId, $amountToRemove, false, self::TRANSACTION_TYPE_INVENTORY_CORRECTION);
}
return true;
return $this->Database->lastInsertId();
}
public function AddMissingProductsToShoppingList()

View File

@ -0,0 +1,66 @@
@extends('layout.default')
@section('title', $L('Batteries journal'))
@section('activeNav', 'batteriesjournal')
@section('viewJsName', 'batteriesjournal')
@section('content')
<div class="row">
<div class="col">
<h1>@yield('title')</h1>
</div>
</div>
<div class="row my-3">
<div class="col-xs-12 col-md-6 col-xl-3">
<label for="battery-filter">{{ $L('Filter by battery') }}</label> <i class="fas fa-filter"></i>
<select class="form-control" id="battery-filter">
<option value="all">{{ $L('All') }}</option>
@foreach($batteries as $battery)
<option value="{{ $battery->id }}">{{ $battery->name }}</option>
@endforeach
</select>
</div>
<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="batteries-journal-table" class="table table-sm table-striped dt-responsive">
<thead>
<tr>
<th>#</th>
<th>{{ $L('Battery') }}</th>
<th>{{ $L('Tracked time') }}</th>
</tr>
</thead>
<tbody>
@foreach($chargeCycles as $chargeCycleEntry)
<tr class="@if($chargeCycleEntry->undone == 1) text-muted @endif">
<td class="fit-content">
<a class="btn btn-secondary btn-sm undo-battery-execution-button @if($chargeCycleEntry->undone == 1) disabled @endif" href="#" data-charge-cycle-id="{{ $chargeCycleEntry->id }}" data-toggle="tooltip" data-placement="left" title="{{ $L('Undo charge cycle') }}">
<i class="fas fa-undo"></i>
</a>
</td>
<td>
<span class="@if($chargeCycleEntry->undone == 1) text-strike-through @endif">{{ FindObjectInArrayByPropertyValue($batteries, 'id', $chargeCycleEntry->battery_id)->name }}</span>
@if($chargeCycleEntry->undone == 1)
<br>
{{ $L('Undone on') . ' ' . $chargeCycleEntry->undone_timestamp }}
<time class="timeago timeago-contextual" datetime="{{ $chargeCycleEntry->undone_timestamp }}"></time>
@endif
</td>
<td>
{{ $chargeCycleEntry->tracked_time }}
<time class="timeago timeago-contextual" datetime="{{ $chargeCycleEntry->tracked_time }}"></time>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@stop

View File

@ -11,7 +11,11 @@
@section('content')
<div class="row">
<div class="col">
<h1>@yield('title')</h1>
<h1>@yield('title')
<a class="btn btn-outline-dark responsive-button" href="{{ $U('/batteriesjournal') }}">
<i class="fas fa-file-alt"></i> {{ $L('Journal') }}
</a>
</h1>
<p id="info-due-batteries" data-status-filter="duesoon" data-next-x-days="{{ $nextXDays }}" class="btn btn-lg btn-warning status-filter-button responsive-button mr-2"></p>
<p id="info-overdue-batteries" data-status-filter="overdue" class="btn btn-lg btn-danger status-filter-button responsive-button"></p>
</div>
@ -53,6 +57,9 @@
data-battery-name="{{ FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->name }}">
<i class="fas fa-fire"></i>
</a>
<a class="btn btn-info btn-sm" href="{{ $U('/batteriesjournal?battery=') }}{{ $curentBatteryEntry->battery_id }}">
<i class="fas fa-file-alt"></i>
</a>
</td>
<td>
{{ FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->name }}

View File

@ -1,8 +1,8 @@
@extends('layout.default')
@section('title', $L('Chores analysis'))
@section('activeNav', 'choresanalysis')
@section('viewJsName', 'choresanalysis')
@section('title', $L('Chores journal'))
@section('activeNav', 'choresjournal')
@section('viewJsName', 'choresjournal')
@section('content')
<div class="row">
@ -11,7 +11,7 @@
</div>
</div>
<div class="row mt-3">
<div class="row my-3">
<div class="col-xs-12 col-md-6 col-xl-3">
<label for="chore-filter">{{ $L('Filter by chore') }}</label> <i class="fas fa-filter"></i>
<select class="form-control" id="chore-filter">
@ -29,9 +29,10 @@
<div class="row">
<div class="col">
<table id="chores-analysis-table" class="table table-sm table-striped dt-responsive">
<table id="chores-journal-table" class="table table-sm table-striped dt-responsive">
<thead>
<tr>
<th>#</th>
<th>{{ $L('Chore') }}</th>
<th>{{ $L('Tracked time') }}</th>
<th>{{ $L('Done by') }}</th>
@ -39,9 +40,19 @@
</thead>
<tbody>
@foreach($choresLog as $choreLogEntry)
<tr>
<tr class="@if($choreLogEntry->undone == 1) text-muted @endif">
<td class="fit-content">
<a class="btn btn-secondary btn-sm undo-chore-execution-button @if($choreLogEntry->undone == 1) disabled @endif" href="#" data-execution-id="{{ $choreLogEntry->id }}" data-toggle="tooltip" data-placement="left" title="{{ $L('Undo chore execution') }}">
<i class="fas fa-undo"></i>
</a>
</td>
<td>
{{ FindObjectInArrayByPropertyValue($chores, 'id', $choreLogEntry->chore_id)->name }}
<span class="@if($choreLogEntry->undone == 1) text-strike-through @endif">{{ FindObjectInArrayByPropertyValue($chores, 'id', $choreLogEntry->chore_id)->name }}</span>
@if($choreLogEntry->undone == 1)
<br>
{{ $L('Undone on') . ' ' . $choreLogEntry->undone_timestamp }}
<time class="timeago timeago-contextual" datetime="{{ $choreLogEntry->undone_timestamp }}"></time>
@endif
</td>
<td>
{{ $choreLogEntry->tracked_time }}

View File

@ -11,7 +11,11 @@
@section('content')
<div class="row">
<div class="col">
<h1>@yield('title')</h1>
<h1>@yield('title')
<a class="btn btn-outline-dark responsive-button" href="{{ $U('/choresjournal') }}">
<i class="fas fa-file-alt"></i> {{ $L('Journal') }}
</a>
</h1>
<p id="info-due-chores" data-status-filter="duesoon" data-next-x-days="{{ $nextXDays }}" class="btn btn-lg btn-warning status-filter-button responsive-button mr-2"></p>
<p id="info-overdue-chores" data-status-filter="overdue" class="btn btn-lg btn-danger status-filter-button responsive-button"></p>
</div>
@ -53,8 +57,8 @@
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 class="btn btn-info btn-sm" href="{{ $U('/choresjournal?chore=') }}{{ $curentChoreEntry->chore_id }}">
<i class="fas fa-file-alt"></i>
</a>
</td>
<td>

View File

@ -0,0 +1,74 @@
@extends('layout.default')
@section('title', $L('Stock journal'))
@section('activeNav', 'stockjournal')
@section('viewJsName', 'stockjournal')
@section('content')
<div class="row">
<div class="col">
<h1>@yield('title')</h1>
</div>
</div>
<div class="row my-3">
<div class="col-xs-12 col-md-6 col-xl-3">
<label for="product-filter">{{ $L('Filter by product') }}</label> <i class="fas fa-filter"></i>
<select class="form-control" id="product-filter">
<option value="all">{{ $L('All') }}</option>
@foreach($products as $product)
<option value="{{ $product->id }}">{{ $product->name }}</option>
@endforeach
</select>
</div>
<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="stock-journal-table" class="table table-sm table-striped dt-responsive">
<thead>
<tr>
<th>#</th>
<th>{{ $L('Product') }}</th>
<th>{{ $L('Amount') }}</th>
<th>{{ $L('Booking time') }}</th>
<th>{{ $L('Booking type') }}</th>
</tr>
</thead>
<tbody>
@foreach($stockLog as $stockLogEntry)
<tr class="@if($stockLogEntry->undone == 1) text-muted @endif">
<td class="fit-content">
<a class="btn btn-secondary btn-sm undo-stock-booking-button @if($stockLogEntry->undone == 1) disabled @endif" href="#" data-booking-id="{{ $stockLogEntry->id }}" data-toggle="tooltip" data-placement="left" title="{{ $L('Undo booking') }}">
<i class="fas fa-undo"></i>
</a>
</td>
<td>
<span class="@if($stockLogEntry->undone == 1) text-strike-through @endif">{{ FindObjectInArrayByPropertyValue($products, 'id', $stockLogEntry->product_id)->name }}</span>
@if($stockLogEntry->undone == 1)
<br>
{{ $L('Undone on') . ' ' . $stockLogEntry->undone_timestamp }}
<time class="timeago timeago-contextual" datetime="{{ $stockLogEntry->undone_timestamp }}"></time>
@endif
</td>
<td>
{{ $stockLogEntry->amount }} {{ Pluralize($stockLogEntry->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $stockLogEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $stockLogEntry->product_id)->qu_id_stock)->name_plural) }}
</td>
<td>
{{ $stockLogEntry->row_created_timestamp }}
<time class="timeago timeago-contextual" datetime="{{ $stockLogEntry->row_created_timestamp }}"></time>
</td>
<td>
{{ $L($stockLogEntry->transaction_type) }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@stop

View File

@ -19,7 +19,12 @@
@section('content')
<div class="row">
<div class="col">
<h1>@yield('title') <small id="info-current-stock" class="text-muted"></small></h1>
<h1>@yield('title')
<small id="info-current-stock" class="text-muted"></small>
<a class="btn btn-outline-dark responsive-button" href="{{ $U('/stockjournal') }}">
<i class="fas fa-file-alt"></i> {{ $L('Journal') }}
</a>
</h1>
<p id="info-expiring-products" data-next-x-days="{{ $nextXDays }}" data-status-filter="expiring" class="btn btn-lg btn-warning status-filter-button responsive-button mr-2"></p>
<p id="info-expired-products" data-status-filter="expired" class="btn btn-lg btn-danger status-filter-button responsive-button mr-2"></p>
<p id="info-missing-products" data-status-filter="belowminstockamount" class="btn btn-lg btn-info status-filter-button responsive-button"></p>
@ -92,6 +97,9 @@
data-consume-amount="{{ $currentStockEntry->amount }}">
<i class="fas fa-utensils"></i> {{ $L('All') }}
</a>
<a class="btn btn-info btn-sm" href="{{ $U('/stockjournal?product=') }}{{ $currentStockEntry->product_id }}">
<i class="fas fa-file-alt"></i>
</a>
</td>
<td class="product-name-cell" data-product-id="{{ $currentStockEntry->product_id }}">
{{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }} <i class="fas fa-info text-muted"></i>