From 99d4b05a3cf2f6b156ff99c23ed47c3d3c4bbac9 Mon Sep 17 00:00:00 2001 From: Bernd Bestel Date: Thu, 23 Jan 2020 18:58:05 +0100 Subject: [PATCH] Make purchased date on /stockedit editable / Dirty workaround for 2 datetimepickers on the same page (references #506) --- controllers/StockApiController.php | 2 +- grocy.openapi.json | 5 + public/viewjs/components/datetimepicker2.js | 307 ++++++++++++++++++++ public/viewjs/stockedit.js | 29 +- services/StockService.php | 5 +- views/components/datetimepicker2.blade.php | 48 +++ views/stockedit.blade.php | 4 +- 7 files changed, 385 insertions(+), 15 deletions(-) create mode 100644 public/viewjs/components/datetimepicker2.js create mode 100644 views/components/datetimepicker2.blade.php diff --git a/controllers/StockApiController.php b/controllers/StockApiController.php index 371c86d7..5b0995c7 100644 --- a/controllers/StockApiController.php +++ b/controllers/StockApiController.php @@ -147,7 +147,7 @@ class StockApiController extends BaseApiController $locationId = $requestBody['location_id']; } - $bookingId = $this->StockService->EditStockEntry($args['entryId'], $requestBody['amount'], $bestBeforeDate, $locationId, $price, $requestBody['open']); + $bookingId = $this->StockService->EditStockEntry($args['entryId'], $requestBody['amount'], $bestBeforeDate, $locationId, $price, $requestBody['open'], $requestBody['purchased_date']); return $this->ApiResponse($this->Database->stock_log($bookingId)); } catch (\Exception $ex) diff --git a/grocy.openapi.json b/grocy.openapi.json index bd701c4b..5ddbeaa9 100644 --- a/grocy.openapi.json +++ b/grocy.openapi.json @@ -1138,6 +1138,11 @@ "type": "number", "format": "integer", "description": "If omitted, the default location of the product is used" + }, + "purchased_date": { + "type": "string", + "format": "date", + "description": "The date when this stock entry was purchased" } }, "example": { diff --git a/public/viewjs/components/datetimepicker2.js b/public/viewjs/components/datetimepicker2.js new file mode 100644 index 00000000..ce352fa0 --- /dev/null +++ b/public/viewjs/components/datetimepicker2.js @@ -0,0 +1,307 @@ +Grocy.Components.DateTimePicker2 = { }; + +Grocy.Components.DateTimePicker2.GetInputElement = function() +{ + return $('.datetimepicker2').find('input').not(".form-check-input"); +} + +Grocy.Components.DateTimePicker2.GetValue = function() +{ + return Grocy.Components.DateTimePicker2.GetInputElement().val(); +} + +Grocy.Components.DateTimePicker2.SetValue = function(value) +{ + Grocy.Components.DateTimePicker2.GetInputElement().val(value); + Grocy.Components.DateTimePicker2.GetInputElement().trigger('change'); + + // "Click" the shortcut checkbox when the desired value is + // not the shortcut value and it is currently set + var shortcutValue = $("#datetimepicker2-shortcut").data("datetimepicker2-shortcut-value"); + if (value != shortcutValue && $("#datetimepicker2-shortcut").is(":checked")) + { + $("#datetimepicker2-shortcut").click(); + } + + Grocy.Components.DateTimePicker2.GetInputElement().keyup(); +} + +Grocy.Components.DateTimePicker2.Clear = function() +{ + $(".datetimepicker2").datetimepicker("destroy"); + Grocy.Components.DateTimePicker2.Init(); + + Grocy.Components.DateTimePicker2.GetInputElement().val(""); + + // "Click" the shortcut checkbox when the desired value is + // not the shortcut value and it is currently set + value = ""; + var shortcutValue = $("#datetimepicker2-shortcut").data("datetimepicker2-shortcut-value"); + if (value != shortcutValue && $("#datetimepicker2-shortcut").is(":checked")) + { + $("#datetimepicker2-shortcut").click(); + } + + $('#datetimepicker-timeago').text(''); +} + +Grocy.Components.DateTimePicker2.ChangeFormat = function(format) +{ + $(".datetimepicker2").datetimepicker("destroy"); + Grocy.Components.DateTimePicker2.GetInputElement().data("format", format); + Grocy.Components.DateTimePicker2.Init(); + + if (format == "YYYY-MM-DD") + { + Grocy.Components.DateTimePicker2.GetInputElement().addClass("date-only-datetimepicker"); + } + else + { + Grocy.Components.DateTimePicker2.GetInputElement().removeClass("date-only-datetimepicker"); + } +} + +var startDate = null; +if (Grocy.Components.DateTimePicker2.GetInputElement().data('init-with-now') === true) +{ + startDate = moment().format(Grocy.Components.DateTimePicker2.GetInputElement().data('format')); +} +if (Grocy.Components.DateTimePicker2.GetInputElement().data('init-value').length > 0) +{ + startDate = moment(Grocy.Components.DateTimePicker2.GetInputElement().data('init-value')).format(Grocy.Components.DateTimePicker2.GetInputElement().data('format')); +} + +var limitDate = moment('2999-12-31 23:59:59'); +if (Grocy.Components.DateTimePicker2.GetInputElement().data('limit-end-to-now') === true) +{ + limitDate = moment(); +} + +Grocy.Components.DateTimePicker2.Init = function() +{ + $('.datetimepicker').datetimepicker( + { + format: Grocy.Components.DateTimePicker2.GetInputElement().data('format'), + buttons: { + showToday: true, + showClose: true + }, + calendarWeeks: Grocy.CalendarShowWeekNumbers, + maxDate: limitDate, + locale: moment.locale(), + defaultDate: startDate, + useCurrent: false, + icons: { + time: 'far fa-clock', + date: 'far fa-calendar', + up: 'fas fa-arrow-up', + down: 'fas fa-arrow-down', + previous: 'fas fa-chevron-left', + next: 'fas fa-chevron-right', + today: 'fas fa-calendar-check', + clear: 'far fa-trash-alt', + close: 'far fa-times-circle' + }, + sideBySide: true, + keyBinds: { + up: function(widget) { }, + down: function(widget) { }, + 'control up': function(widget) { }, + 'control down': function(widget) { }, + left: function(widget) { }, + right: function(widget) { }, + pageUp: function(widget) { }, + pageDown: function(widget) { }, + enter: function(widget) { }, + escape: function(widget) { }, + 'control space': function(widget) { }, + t: function(widget) { }, + 'delete': function(widget) { } + } + }); +} +Grocy.Components.DateTimePicker2.Init(); + +Grocy.Components.DateTimePicker2.GetInputElement().on('keyup', function(e) +{ + $('.datetimepicker').datetimepicker('hide'); + + var value = Grocy.Components.DateTimePicker2.GetValue(); + 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'); + var format = Grocy.Components.DateTimePicker2.GetInputElement().data('format'); + var nextInputElement = $(Grocy.Components.DateTimePicker2.GetInputElement().data('next-input-selector')); + + //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)) + { + Grocy.Components.DateTimePicker2.SetValue(moment(new Date(), format, true).format(format)); + nextInputElement.focus(); + } + else if (value === 'x' || value === 'X') + { + Grocy.Components.DateTimePicker2.SetValue(moment('2999-12-31 23:59:59').format(format)); + nextInputElement.focus(); + } + else if (value.length === 4 && !(Number.parseInt(value) > centuryStart && Number.parseInt(value) < centuryEnd)) + { + var date = moment((new Date()).getFullYear().toString() + value); + if (date.isBefore(moment())) + { + date.add(1, "year"); + } + Grocy.Components.DateTimePicker2.SetValue(date.format(format)); + nextInputElement.focus(); + } + else if (value.length === 8 && $.isNumeric(value)) + { + Grocy.Components.DateTimePicker2.SetValue(value.replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3')); + nextInputElement.focus(); + } + else if (value.length === 7 && $.isNumeric(value.substring(0, 6)) && (value.substring(6, 7).toLowerCase() === "e" || value.substring(6, 7).toLowerCase() === "+")) + { + var date = moment(value.substring(0, 4) + "-" + value.substring(4, 6) + "-01").endOf("month"); + Grocy.Components.DateTimePicker2.SetValue(date.format(format)); + nextInputElement.focus(); + } + else + { + var dateObj = moment(value, format, true); + if (dateObj.isValid()) + { + if (e.shiftKey) + { + // WITH shift modifier key + + if (e.keyCode === 38) // Up + { + Grocy.Components.DateTimePicker2.SetValue(dateObj.add(-1, 'months').format(format)); + } + else if (e.keyCode === 40) // Down + { + Grocy.Components.DateTimePicker2.SetValue(dateObj.add(1, 'months').format(format)); + } + else if (e.keyCode === 37) // Left + { + Grocy.Components.DateTimePicker2.SetValue(dateObj.add(-1, 'years').format(format)); + } + else if (e.keyCode === 39) // Right + { + Grocy.Components.DateTimePicker2.SetValue(dateObj.add(1, 'years').format(format)); + } + } + else + { + // WITHOUT shift modifier key + + if (e.keyCode === 38) // Up + { + Grocy.Components.DateTimePicker2.SetValue(dateObj.add(-1, 'days').format(format)); + } + else if (e.keyCode === 40) // Down + { + Grocy.Components.DateTimePicker2.SetValue(dateObj.add(1, 'days').format(format)); + } + else if (e.keyCode === 37) // Left + { + Grocy.Components.DateTimePicker2.SetValue(dateObj.add(-1, 'weeks').format(format)); + } + else if (e.keyCode === 39) // Right + { + Grocy.Components.DateTimePicker2.SetValue(dateObj.add(1, 'weeks').format(format)); + } + } + } + } + + //Custom validation + value = Grocy.Components.DateTimePicker2.GetValue(); + dateObj = moment(value, format, true); + var element = Grocy.Components.DateTimePicker2.GetInputElement()[0]; + if (!dateObj.isValid()) + { + if ($(element).hasAttr("required")) + { + element.setCustomValidity("error"); + } + } + else + { + if (Grocy.Components.DateTimePicker2.GetInputElement().data('limit-end-to-now') === true && dateObj.isAfter(moment())) + { + element.setCustomValidity("error"); + } + else if (Grocy.Components.DateTimePicker2.GetInputElement().data('limit-start-to-now') === true && dateObj.isBefore(moment())) + { + element.setCustomValidity("error"); + } + else + { + element.setCustomValidity(""); + } + + var earlierThanLimit = Grocy.Components.DateTimePicker2.GetInputElement().data("earlier-than-limit"); + if (!earlierThanLimit.isEmpty()) + { + if (moment(value).isBefore(moment(earlierThanLimit))) + { + $("#datetimepicker-earlier-than-info").removeClass("d-none"); + } + else + { + $("#datetimepicker-earlier-than-info").addClass("d-none"); + } + } + } + + // "Click" the shortcut checkbox when the shortcut value was + // entered manually and it is currently not set + var shortcutValue = $("#datetimepicker2-shortcut").data("datetimepicker2-shortcut-value"); + if (value == shortcutValue && !$("#datetimepicker2-shortcut").is(":checked")) + { + $("#datetimepicker2-shortcut").click(); + } +}); + +Grocy.Components.DateTimePicker2.GetInputElement().on('input', function(e) +{ + $('#datetimepicker-timeago').attr("datetime", Grocy.Components.DateTimePicker2.GetValue()); + EmptyElementWhenMatches('#datetimepicker-timeago', __t('timeago_nan')); + RefreshContextualTimeago(); +}); + +$('.datetimepicker').on('update.datetimepicker', function(e) +{ + Grocy.Components.DateTimePicker2.GetInputElement().trigger('input'); + Grocy.Components.DateTimePicker2.GetInputElement().trigger('change'); + Grocy.Components.DateTimePicker2.GetInputElement().trigger('keypress'); +}); + +$('.datetimepicker').on('hide.datetimepicker', function(e) +{ + Grocy.Components.DateTimePicker2.GetInputElement().trigger('input'); + Grocy.Components.DateTimePicker2.GetInputElement().trigger('change'); + Grocy.Components.DateTimePicker2.GetInputElement().trigger('keypress'); +}); + +$("#datetimepicker2-shortcut").on("click", function() +{ + if (this.checked) + { + var value = $("#datetimepicker2-shortcut").data("datetimepicker2-shortcut-value"); + Grocy.Components.DateTimePicker2.SetValue(value); + Grocy.Components.DateTimePicker2.GetInputElement().attr("readonly", ""); + $(Grocy.Components.DateTimePicker2.GetInputElement().data('next-input-selector')).focus(); + } + else + { + Grocy.Components.DateTimePicker2.SetValue(""); + Grocy.Components.DateTimePicker2.GetInputElement().removeAttr("readonly"); + Grocy.Components.DateTimePicker2.GetInputElement().focus(); + } + + Grocy.Components.DateTimePicker2.GetInputElement().trigger('input'); + Grocy.Components.DateTimePicker2.GetInputElement().trigger('change'); + Grocy.Components.DateTimePicker2.GetInputElement().trigger('keypress'); +}); diff --git a/public/viewjs/stockedit.js b/public/viewjs/stockedit.js index e7da8594..02d18eef 100644 --- a/public/viewjs/stockedit.js +++ b/public/viewjs/stockedit.js @@ -13,6 +13,7 @@ var jsonData = { }; jsonData.amount = jsonForm.amount; jsonData.best_before_date = Grocy.Components.DateTimePicker.GetValue(); + jsonData.purchased_date = Grocy.Components.DateTimePicker2.GetValue(); if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) { jsonData.location_id = Grocy.Components.LocationPicker.GetValue(); @@ -69,18 +70,25 @@ $('#stockedit-form input').keydown(function(event) } }); -if (Grocy.Components.DateTimePicker) +Grocy.Components.DateTimePicker.GetInputElement().on('change', function(e) { - Grocy.Components.DateTimePicker.GetInputElement().on('change', function(e) - { - Grocy.FrontendHelpers.ValidateForm('stockedit-form'); - }); + Grocy.FrontendHelpers.ValidateForm('stockedit-form'); +}); - Grocy.Components.DateTimePicker.GetInputElement().on('keypress', function(e) - { - Grocy.FrontendHelpers.ValidateForm('stockedit-form'); - }); -} +Grocy.Components.DateTimePicker.GetInputElement().on('keypress', function(e) +{ + Grocy.FrontendHelpers.ValidateForm('stockedit-form'); +}); + +Grocy.Components.DateTimePicker2.GetInputElement().on('change', function(e) +{ + Grocy.FrontendHelpers.ValidateForm('stockedit-form'); +}); + +Grocy.Components.DateTimePicker2.GetInputElement().on('keypress', function(e) +{ + Grocy.FrontendHelpers.ValidateForm('stockedit-form'); +}); var stockRowId = GetUriParam('stockRowId'); Grocy.Api.Get("stock/entry/" + stockRowId, @@ -91,6 +99,7 @@ Grocy.Api.Get("stock/entry/" + stockRowId, $('#price').val(stockEntry.price); $("#open").prop('checked', BoolVal(stockEntry.open)); Grocy.Components.DateTimePicker.SetValue(stockEntry.best_before_date); + Grocy.Components.DateTimePicker2.SetValue(stockEntry.purchased_date); Grocy.Api.Get('stock/products/' + stockEntry.product_id, function (productDetails) diff --git a/services/StockService.php b/services/StockService.php index 255c54fc..d749847b 100644 --- a/services/StockService.php +++ b/services/StockService.php @@ -563,7 +563,7 @@ class StockService extends BaseService return $this->Database->lastInsertId(); } - public function EditStockEntry(int $stockRowId, int $amount, $bestBeforeDate, $locationId, $price, $open) + public function EditStockEntry(int $stockRowId, int $amount, $bestBeforeDate, $locationId, $price, $open, $purchasedDate) { $stockRow = $this->Database->stock()->where('id = :1', $stockRowId)->fetch(); @@ -607,7 +607,8 @@ class StockService extends BaseService 'best_before_date' => $bestBeforeDate, 'location_id' => $locationId, 'opened_date' => $openedDate, - 'open' => $open + 'open' => $open, + 'purchased_date' => $purchasedDate )); $logNewRowForStockUpdate = $this->Database->stock_log()->createRow(array( diff --git a/views/components/datetimepicker2.blade.php b/views/components/datetimepicker2.blade.php new file mode 100644 index 00000000..5a52e4f9 --- /dev/null +++ b/views/components/datetimepicker2.blade.php @@ -0,0 +1,48 @@ +@push('componentScripts') + +@endpush + +@php if(!isset($isRequired)) { $isRequired = true; } @endphp +@php if(!isset($initialValue)) { $initialValue = ''; } @endphp +@php if(empty($earlierThanInfoLimit)) { $earlierThanInfoLimit = ''; } @endphp +@php if(empty($earlierThanInfoText)) { $earlierThanInfoText = ''; } @endphp +@php if(empty($additionalCssClasses)) { $additionalCssClasses = ''; } @endphp +@php if(empty($additionalGroupCssClasses)) { $additionalGroupCssClasses = ''; } @endphp +@php if(empty($invalidFeedback)) { $invalidFeedback = ''; } @endphp +@php if(!isset($isRequired)) { $isRequired = true; } @endphp +@php if(!isset($noNameAttribute)) { $noNameAttribute = false; } @endphp +@php if(!isset($nextInputSelector)) { $nextInputSelector = false; } @endphp +@php if(empty($additionalAttributes)) { $additionalAttributes = ''; } @endphp +@php if(empty($additionalGroupCssClasses)) { $additionalGroupCssClasses = ''; } @endphp + +
+ +
+
+ +
+
+
+
{{ $invalidFeedback }}
+
+
{{ $earlierThanInfoText }}
+ @if(isset($shortcutValue) && isset($shortcutLabel)) +
+ + +
+ @endif +
+
diff --git a/views/stockedit.blade.php b/views/stockedit.blade.php index 420b2cfa..51a2293c 100644 --- a/views/stockedit.blade.php +++ b/views/stockedit.blade.php @@ -69,7 +69,7 @@ @endif - @php /*@include('components.datetimepicker', array( + @include('components.datetimepicker2', array( 'id' => 'purchase_date', 'label' => 'Purchased date', 'format' => 'YYYY-MM-DD', @@ -79,7 +79,7 @@ 'invalidFeedback' => $__t('A purchased date is required'), 'nextInputSelector' => '#save-stockedit-button', 'additionalGroupCssClasses' => 'date-only-datetimepicker' - ))*/ @endphp + ))