mirror of
https://github.com/grocy/grocy.git
synced 2026-04-05 20:36:15 +02:00
Bootstrap Dropdown with detached dropdown definition
This commit is contained in:
parent
a8153a2cf2
commit
5be9faed43
|
|
@ -57,6 +57,7 @@ var eslint_config = {
|
||||||
"moment",
|
"moment",
|
||||||
"toastr",
|
"toastr",
|
||||||
"bootbox",
|
"bootbox",
|
||||||
|
"Popper",
|
||||||
],
|
],
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"sourceType": "module",
|
"sourceType": "module",
|
||||||
|
|
|
||||||
239
js/helpers/dropdown.js
Normal file
239
js/helpers/dropdown.js
Normal file
|
|
@ -0,0 +1,239 @@
|
||||||
|
import Popper from "popper.js";
|
||||||
|
|
||||||
|
/* this is basically a 1 on 1 port of Bootstraps'
|
||||||
|
DropdownMenu plug-in, but has its Elements detached.
|
||||||
|
And pobably triggers an event or two less.
|
||||||
|
|
||||||
|
HTML-wise it uses standard bootstrap 4 dropdown syntax,
|
||||||
|
however the button is out of the <div class="dropdown">
|
||||||
|
wrapper, and needs to reference the dropdown menu
|
||||||
|
with a data-detached-element="#someSelector" attribute.
|
||||||
|
|
||||||
|
Also this class is way less generic than Bootstraps,
|
||||||
|
but that's okay.
|
||||||
|
|
||||||
|
Parts of this code are taken from https://github.com/twbs/bootstrap/blob/v4-dev/js/src/dropdown.js
|
||||||
|
which is available under the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key
|
||||||
|
const SPACE_KEYCODE = 32 // KeyboardEvent.which value for space key
|
||||||
|
const TAB_KEYCODE = 9 // KeyboardEvent.which value for tab key
|
||||||
|
const ARROW_UP_KEYCODE = 38 // KeyboardEvent.which value for up arrow key
|
||||||
|
const ARROW_DOWN_KEYCODE = 40 // KeyboardEvent.which value for down arrow key
|
||||||
|
const RIGHT_MOUSE_BUTTON_WHICH = 3 // MouseEvent.which value for the right button (assuming a right-handed mouse)
|
||||||
|
const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEYCODE}|${ARROW_DOWN_KEYCODE}|${ESCAPE_KEYCODE}`)
|
||||||
|
const SELECTOR_VISIBLE_ITEMS = '.dropdown-item:not(.disabled):not(:disabled)'
|
||||||
|
|
||||||
|
class DetachedDropdown
|
||||||
|
{
|
||||||
|
constructor(target, menuElement = null, scope = null)
|
||||||
|
{
|
||||||
|
this.scopeSelector = scope;
|
||||||
|
if (scope != null)
|
||||||
|
{
|
||||||
|
this.scope = $(scope);
|
||||||
|
var jScope = this.scope;
|
||||||
|
this.$scope = (selector) => jScope.find(selector);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.$scope = $;
|
||||||
|
this.scope = $(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$target = this.$scope(target);
|
||||||
|
this.target = this.$target[0];
|
||||||
|
this.menu = menuElement != null ? this.$scope(menuElement) : this.$scope(this.$target.data('target'));
|
||||||
|
this._popper = null;
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
$(document).on('keydown', (event) => self.keydownHandler(event));
|
||||||
|
this.menu.on("click", "form", e =>
|
||||||
|
{
|
||||||
|
e.stopPropagation()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.scope.on("click keyup", (event) => self.clear(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle()
|
||||||
|
{
|
||||||
|
if (this.menu.parent().hasClass('show'))
|
||||||
|
this.hide();
|
||||||
|
|
||||||
|
else
|
||||||
|
this.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
show()
|
||||||
|
{
|
||||||
|
// show always re-shows.
|
||||||
|
this.hide()
|
||||||
|
|
||||||
|
|
||||||
|
this._popper = new Popper(this.target, this.menu, this._getPopperConfig())
|
||||||
|
|
||||||
|
// If this is a touch-enabled device we add extra
|
||||||
|
// empty mouseover listeners to the body's immediate children;
|
||||||
|
// only needed because of broken event delegation on iOS
|
||||||
|
// https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
|
||||||
|
if ('ontouchstart' in document.documentElement)
|
||||||
|
{
|
||||||
|
$(document.body).children().on('mouseover', null, $.noop)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.menu.trigger('focus');
|
||||||
|
this.menu.attr('aria-expanded', true)
|
||||||
|
|
||||||
|
this.menu.toggleClass('show')
|
||||||
|
this.menu.parent().toggleClass('show')
|
||||||
|
}
|
||||||
|
|
||||||
|
hide()
|
||||||
|
{
|
||||||
|
if (this.isDisabled() || !this.menu.parent().hasClass('show'))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If this is a touch-enabled device we remove the extra
|
||||||
|
// empty mouseover listeners we added for iOS support
|
||||||
|
if ('ontouchstart' in document.documentElement)
|
||||||
|
{
|
||||||
|
$(document.body).children().off('mouseover', null, $.noop)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._popper)
|
||||||
|
{
|
||||||
|
this._popper.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.menu.removeClass('show');
|
||||||
|
this.menu.parent().removeClass('show');
|
||||||
|
this.menu.attr('aria-expanded', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
isDisabled()
|
||||||
|
{
|
||||||
|
return this.target.disabled || this.$target.hasClass("disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
_getPopperConfig()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
placement: 'right',
|
||||||
|
modifiers: {
|
||||||
|
offset: '50px',
|
||||||
|
flip: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
preventOverflow: {
|
||||||
|
boundariesElement: 'viewport'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keydownHandler(event)
|
||||||
|
{
|
||||||
|
if (!this.isActive() && event.target.id != this.target.id)
|
||||||
|
return;
|
||||||
|
// If not input/textarea:
|
||||||
|
// - And not a key in REGEXP_KEYDOWN => not a dropdown command
|
||||||
|
// If input/textarea:
|
||||||
|
// - If space key => not a dropdown command
|
||||||
|
// - If key is other than escape
|
||||||
|
// - If key is not up or down => not a dropdown command
|
||||||
|
// - If trigger inside the menu => not a dropdown command
|
||||||
|
if (/input|textarea/i.test(event.target.tagName) ?
|
||||||
|
event.which === SPACE_KEYCODE || event.which !== ESCAPE_KEYCODE &&
|
||||||
|
(event.which !== ARROW_DOWN_KEYCODE && event.which !== ARROW_UP_KEYCODE ||
|
||||||
|
this.menu.length) : !REGEXP_KEYDOWN.test(event.which))
|
||||||
|
{
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isDisabled())
|
||||||
|
{
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isActive() && event.which === ESCAPE_KEYCODE)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
|
if (!this.isActive() || (event.which === ESCAPE_KEYCODE || event.which === SPACE_KEYCODE))
|
||||||
|
{
|
||||||
|
if (event.which === ESCAPE_KEYCODE)
|
||||||
|
{
|
||||||
|
this.menu.trigger('focus')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$target.trigger('click')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = [].slice.call(this.menu[0].querySelectorAll(SELECTOR_VISIBLE_ITEMS))
|
||||||
|
.filter(item => $(item).is(':visible'))
|
||||||
|
|
||||||
|
if (items.length === 0)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = items.indexOf(event.target)
|
||||||
|
|
||||||
|
if (event.which === ARROW_UP_KEYCODE && index > 0)
|
||||||
|
{ // Up
|
||||||
|
index--
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.which === ARROW_DOWN_KEYCODE && index < items.length - 1)
|
||||||
|
{ // Down
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
index = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
items[index].focus()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
isActive()
|
||||||
|
{
|
||||||
|
return this.menu.parent().hasClass('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(event)
|
||||||
|
{
|
||||||
|
if (event && (event.which === RIGHT_MOUSE_BUTTON_WHICH ||
|
||||||
|
(event.type === 'keyup' && event.which !== TAB_KEYCODE)))
|
||||||
|
{
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.menu.parent().hasClass('show'))
|
||||||
|
{
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let parent = this.menu.parent()[0];
|
||||||
|
|
||||||
|
if (event && (event.type === 'click' &&
|
||||||
|
/input|textarea/i.test(event.target.tagName) || event.type === 'keyup' && event.which === TAB_KEYCODE) &&
|
||||||
|
$.contains(parent, event.target))
|
||||||
|
{
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hide();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export { DetachedDropdown }
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { DetachedDropdown } from './dropdown';
|
||||||
|
|
||||||
class GrocyFrontendHelpers
|
class GrocyFrontendHelpers
|
||||||
{
|
{
|
||||||
|
|
@ -5,18 +6,112 @@ class GrocyFrontendHelpers
|
||||||
{
|
{
|
||||||
this.Grocy = Grocy;
|
this.Grocy = Grocy;
|
||||||
this.Api = Api;
|
this.Api = Api;
|
||||||
|
this.scopeSelector = scope;
|
||||||
if (scope != null)
|
if (scope != null)
|
||||||
{
|
{
|
||||||
this.scope = $(scope);
|
this.scope = $(scope);
|
||||||
var jScope = this.scope;
|
var jScope = this.scope;
|
||||||
this.$scope = (selector) => jScope.find(selector);
|
this.$scope = (selector) => jScope.find(selector);
|
||||||
this.scopeSelector = scope;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.$scope = $;
|
this.$scope = $;
|
||||||
this.scope = $(document);
|
this.scope = $(document);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.dropdowns = {}
|
||||||
|
|
||||||
|
this.InitDropdowns();
|
||||||
|
}
|
||||||
|
|
||||||
|
_ApplyTemplate(data, template)
|
||||||
|
{
|
||||||
|
for (let key in data)
|
||||||
|
{
|
||||||
|
// transforms data-product-id to PRODUCT_ID
|
||||||
|
let param = key.replace('data-', '').toUpperCase().replaceAll('-', '_');
|
||||||
|
template = template.replaceAll(param, data[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return template.replace('RETURNTO', '?returnto=' + encodeURIComponent(window.location.pathname));
|
||||||
|
}
|
||||||
|
|
||||||
|
InitDropdowns()
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
this.$scope('[data-toggle="dropdown-detached"]').on('click', function(event)
|
||||||
|
{
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
var button = this;
|
||||||
|
var selector = button.getAttribute('data-target');
|
||||||
|
var $dropdown = self.$scope(selector);
|
||||||
|
|
||||||
|
let dropper = self.dropdowns[button.id];
|
||||||
|
if (dropper !== undefined)
|
||||||
|
{
|
||||||
|
if (dropper.isActive())
|
||||||
|
{
|
||||||
|
dropper.hide();
|
||||||
|
if (dropper.target.id == button.id)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var elements = $dropdown.find('*');
|
||||||
|
var source_data = {};
|
||||||
|
|
||||||
|
for (let i = button.attributes.length - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
let attr = button.attributes[i];
|
||||||
|
if (attr.name.startsWith("data-"))
|
||||||
|
{
|
||||||
|
source_data[attr.name] = attr.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let elem of elements)
|
||||||
|
{
|
||||||
|
for (let i = elem.attributes.length - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
// copy over data-* attributes
|
||||||
|
let attr = elem.attributes[i];
|
||||||
|
if (source_data[attr.name] !== undefined)
|
||||||
|
{
|
||||||
|
elem.setAttribute(attr.name, source_data[attr.name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elem.hasAttribute('data-href'))
|
||||||
|
{
|
||||||
|
elem.setAttribute("href", self._ApplyTemplate(source_data, elem.getAttribute('data-href')))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elem.hasAttribute("data-compute"))
|
||||||
|
{
|
||||||
|
let tArgs = JSON.parse(self._ApplyTemplate(source_data, elem.getAttribute("data-compute")))
|
||||||
|
elem.innerText = self.Grocy.translate(...tArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elem.hasAttribute("data-disable") &&
|
||||||
|
source_data["data-" + elem.getAttribute("data-disable")] !== undefined &&
|
||||||
|
source_data["data-" + elem.getAttribute("data-disable")] === "1")
|
||||||
|
{
|
||||||
|
elem.classList.add("disabled");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
elem.classList.remove("disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dropper === undefined)
|
||||||
|
{
|
||||||
|
dropper = new DetachedDropdown(button, null, this.scopeSelector);
|
||||||
|
self.dropdowns[button.id] = dropper;
|
||||||
|
}
|
||||||
|
dropper.toggle();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Delay(callable, delayMilliseconds)
|
Delay(callable, delayMilliseconds)
|
||||||
|
|
|
||||||
|
|
@ -44,3 +44,7 @@
|
||||||
.modal {
|
.modal {
|
||||||
z-index: 99998;
|
z-index: 99998;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detached-dropdown-menu {
|
||||||
|
z-index: 99999;
|
||||||
|
}
|
||||||
82
views/components/stockentrydropdowncommon.blade.php
Normal file
82
views/components/stockentrydropdowncommon.blade.php
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
<a class="dropdown-item show-as-dialog-link permission-SHOPPINGLIST_ITEMS_ADD"
|
||||||
|
type="button"
|
||||||
|
data-href="{{ $U('/shoppinglistitem/new?embedded&updateexistingproduct&product=PRODUCT_ID') }}">
|
||||||
|
<span class="dropdown-item-icon"><i class="fas fa-shopping-cart"></i></span> <span class="dropdown-item-text">{{ $__t('Add to shopping list') }}</span>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a class="dropdown-item show-as-dialog-link permission-STOCK_PURCHASE"
|
||||||
|
type="button"
|
||||||
|
data-href="{{ $U('/purchase?embedded&product=PRODUCT_ID' ) }}">
|
||||||
|
<span class="dropdown-item-icon"><i class="fas fa-cart-plus"></i></span> <span class="dropdown-item-text">{{ $__t('Purchase') }}</span>
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item show-as-dialog-link permission-STOCK_CONSUME"
|
||||||
|
type="button"
|
||||||
|
data-disable="consume"
|
||||||
|
data-href="{{ $U('/consume?embedded&product=PRODUCT_ID&locationId=LOCATON_ID&stockId=STOCK_ID') }}">
|
||||||
|
<span class="dropdown-item-icon"><i class="fas fa-utensils"></i></span> <span class="dropdown-item-text">{{ $__t('Consume') }}</span>
|
||||||
|
</a>
|
||||||
|
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
|
||||||
|
<a class="dropdown-item show-as-dialog-link permission-STOCK_TRANSFER"
|
||||||
|
data-disable="transfer"
|
||||||
|
type="button"
|
||||||
|
data-href="{{ $U('/transfer?embedded&product=PRODUCT_ID&locationId=LOCATON_ID&stockId=STOCK_ID') }}">
|
||||||
|
<span class="dropdown-item-icon"><i class="fas fa-exchange-alt"></i></span> <span class="dropdown-item-text">{{ $__t('Transfer') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
<a class="dropdown-item show-as-dialog-link permission-STOCK_INVENTORY"
|
||||||
|
type="button"
|
||||||
|
data-href="{{ $U('/inventory?embedded&product=PRODUCT_ID') }}">
|
||||||
|
<span class="dropdown-item-icon"><i class="fas fa-list"></i></span> <span class="dropdown-item-text">{{ $__t('Inventory') }}</span>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a class="dropdown-item product-consume-button product-consume-button-spoiled permission-STOCK_CONSUME"
|
||||||
|
type="button"
|
||||||
|
href="#"
|
||||||
|
data-product-id=""
|
||||||
|
data-product-name=""
|
||||||
|
data-product-qu-name=""
|
||||||
|
data-consume-amount="1"
|
||||||
|
data-stock-id=""
|
||||||
|
data-stockrow-id=""
|
||||||
|
data-location-id=""
|
||||||
|
data-disable="consume">
|
||||||
|
<span class="dropdown-item-text"
|
||||||
|
data-compute='["Consume %1$s of %2$s as spoiled", "1 PRODUCT_QU_NAME" , "PRODUCT_NAME"]'></span>
|
||||||
|
</a>
|
||||||
|
@if(GROCY_FEATURE_FLAG_RECIPES)
|
||||||
|
<a class="dropdown-item"
|
||||||
|
type="button"
|
||||||
|
data-href="{{ $U('/recipes?search=PRODUCT_NAME') }}">
|
||||||
|
<span class="dropdown-item-text">{{ $__t('Search for recipes containing this product') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a class="dropdown-item product-name-cell"
|
||||||
|
data-product-id="xxx"
|
||||||
|
type="button"
|
||||||
|
data-href="#">
|
||||||
|
<span class="dropdown-item-text">{{ $__t('Product overview') }}</span>
|
||||||
|
</a>
|
||||||
|
@if(!isset($skipStockEntries))
|
||||||
|
<a class="dropdown-item show-as-dialog-link"
|
||||||
|
type="button"
|
||||||
|
data-href="{{ $U('/stockentries?embedded&product=PRODUCT_ID') }}"
|
||||||
|
data-product-id="xxx">
|
||||||
|
<span class="dropdown-item-text">{{ $__t('Stock entries') }}</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
<a class="dropdown-item show-as-dialog-link"
|
||||||
|
type="button"
|
||||||
|
data-href="{{ $U('/stockjournal?embedded&product=PRODUCT_ID') }}">
|
||||||
|
<span class="dropdown-item-text">{{ $__t('Stock journal') }}</span>
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item show-as-dialog-link"
|
||||||
|
type="button"
|
||||||
|
data-href="{{ $U('/stockjournal/summary?embedded&product_id=PRODUCT_ID') }}">
|
||||||
|
<span class="dropdown-item-text">{{ $__t('Stock journal summary') }}</span>
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item permission-MASTER_DATA_EDIT"
|
||||||
|
type="button"
|
||||||
|
data-href="{{ $U('/product/PRODUCT_ID') }}RETURNTO">
|
||||||
|
<span class="dropdown-item-text">{{ $__t('Edit product') }}</span>
|
||||||
|
</a>
|
||||||
|
|
@ -45,8 +45,37 @@ $collapsed_flex = $embedded ? '' : 'd-md-flex';
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@php
|
||||||
|
$dt_uniq = uniqid();
|
||||||
|
@endphp
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
<div class="dropdown">
|
||||||
|
<div class="table-inline-menu dropdown-menu detached-dropdown-menu dropdown-menu-right" id="datatable-dropdown{{ $dt_uniq }}">
|
||||||
|
@include('components.stockentrydropdowncommon', [ 'skipStockEntries' => true])
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a class="dropdown-item stockentry-grocycode-link"
|
||||||
|
type="button"
|
||||||
|
data-href="{{ $U('/stockentry/STOCK_ENTRY_ID/grocycode?download=true') }}">
|
||||||
|
{{ $__t('Download stock entry grocycode') }}
|
||||||
|
</a>
|
||||||
|
@if(GROCY_FEATURE_FLAG_LABELPRINTER)
|
||||||
|
<a class="dropdown-item stockentry-grocycode-stockentry-label-print"
|
||||||
|
data-stock-id=""
|
||||||
|
type="button"
|
||||||
|
href="#">
|
||||||
|
{{ $__t('Print stock entry grocycode on label printer') }}
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
<a class="dropdown-item stockentry-label-link"
|
||||||
|
type="button"
|
||||||
|
target="_blank"
|
||||||
|
data-href="{{ $U('/stockentry/STOCK_ENTRY_ID/label') }}">
|
||||||
|
{{ $__t('Open stock entry print label in new window') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<table id="stockentries-table"
|
<table id="stockentries-table"
|
||||||
class="table table-sm table-striped nowrap w-100">
|
class="table table-sm table-striped nowrap w-100">
|
||||||
<thead>
|
<thead>
|
||||||
|
|
@ -119,106 +148,20 @@ $collapsed_flex = $embedded ? '' : 'd-md-flex';
|
||||||
title="{{ $__t('Edit stock entry') }}">
|
title="{{ $__t('Edit stock entry') }}">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown d-inline-block">
|
<button class="btn btn-sm btn-light text-secondary"
|
||||||
<button class="btn btn-sm btn-light text-secondary"
|
type="button"
|
||||||
type="button"
|
id="detached-dropdown-{!! uniqid() !!}"
|
||||||
data-toggle="dropdown"
|
data-toggle="dropdown-detached"
|
||||||
data-boundary="viewport">
|
data-target="#datatable-dropdown{{ $dt_uniq }}"
|
||||||
<i class="fas fa-ellipsis-v"></i>
|
data-product-id="{{ $stockEntry->product_id }}"
|
||||||
</button>
|
data-product-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->name }}"
|
||||||
<div class="dropdown-menu">
|
data-product-qu-name="{{ $__n($stockEntry->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->qu_id_stock)->name_plural) }}"
|
||||||
<a class="dropdown-item show-as-dialog-link"
|
data-transfer="{{ ($currentStockEntry->amount < 1 ? 1 : 0) }}"
|
||||||
type="button"
|
data-consume="{{ ($currentStockEntry->amount < 1 ? 1 : 0) }}"
|
||||||
href="{{ $U('/shoppinglistitem/new?embedded&updateexistingproduct&product=' . $stockEntry->product_id ) }}">
|
data-location-id="{{ $stockEntry->location_id }}"
|
||||||
<i class="fas fa-shopping-cart"></i> {{ $__t('Add to shopping list') }}
|
data-stock-id="{{ $stockEntry->stock_id }}">
|
||||||
</a>
|
<i class="fas fa-ellipsis-v"></i>
|
||||||
<div class="dropdown-divider"></div>
|
</button>
|
||||||
<a class="dropdown-item show-as-dialog-link"
|
|
||||||
type="button"
|
|
||||||
href="{{ $U('/purchase?embedded&product=' . $stockEntry->product_id ) }}">
|
|
||||||
<i class="fas fa-cart-plus"></i> {{ $__t('Purchase') }}
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item show-as-dialog-link"
|
|
||||||
type="button"
|
|
||||||
href="{{ $U('/consume?embedded&product=' . $stockEntry->product_id . '&locationId=' . $stockEntry->location_id . '&stockId=' . $stockEntry->stock_id) }}">
|
|
||||||
<i class="fas fa-utensils"></i> {{ $__t('Consume') }}
|
|
||||||
</a>
|
|
||||||
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
|
|
||||||
<a class="dropdown-item show-as-dialog-link"
|
|
||||||
type="button"
|
|
||||||
href="{{ $U('/transfer?embedded&product=' . $stockEntry->product_id . '&locationId=' . $stockEntry->location_id . '&stockId=' . $stockEntry->stock_id) }}">
|
|
||||||
<i class="fas fa-exchange-alt"></i> {{ $__t('Transfer') }}
|
|
||||||
</a>
|
|
||||||
@endif
|
|
||||||
<a class="dropdown-item show-as-dialog-link"
|
|
||||||
type="button"
|
|
||||||
href="{{ $U('/inventory?embedded&product=' . $stockEntry->product_id ) }}">
|
|
||||||
<i class="fas fa-list"></i> {{ $__t('Inventory') }}
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<a class="dropdown-item stock-consume-button stock-consume-button-spoiled @if($stockEntry->amount < 1) disabled @endif"
|
|
||||||
type="button"
|
|
||||||
href="#"
|
|
||||||
data-product-id="{{ $stockEntry->product_id }}"
|
|
||||||
data-product-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->name }}"
|
|
||||||
data-product-qu-name="{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->qu_id_stock)->name }}"
|
|
||||||
data-stock-id="{{ $stockEntry->stock_id }}"
|
|
||||||
data-stockrow-id="{{ $stockEntry->id }}"
|
|
||||||
data-location-id="{{ $stockEntry->location_id }}"
|
|
||||||
data-consume-amount="1">
|
|
||||||
{{ $__t('Consume this stock entry as spoiled', '1 ' . FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->name) }}
|
|
||||||
</a>
|
|
||||||
@if(GROCY_FEATURE_FLAG_RECIPES)
|
|
||||||
<a class="dropdown-item"
|
|
||||||
type="button"
|
|
||||||
href="{{ $U('/recipes?search=') }}{{ FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->name }}">
|
|
||||||
{{ $__t('Search for recipes containing this product') }}
|
|
||||||
</a>
|
|
||||||
@endif
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<a class="dropdown-item product-name-cell"
|
|
||||||
data-product-id="{{ $stockEntry->product_id }}"
|
|
||||||
type="button"
|
|
||||||
href="#">
|
|
||||||
{{ $__t('Product overview') }}
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item show-as-dialog-link"
|
|
||||||
type="button"
|
|
||||||
href="{{ $U('/stockjournal?embedded&product=') }}{{ $stockEntry->product_id }}">
|
|
||||||
{{ $__t('Stock journal') }}
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item show-as-dialog-link"
|
|
||||||
type="button"
|
|
||||||
href="{{ $U('/stockjournal/summary?embedded&product=') }}{{ $stockEntry->product_id }}">
|
|
||||||
{{ $__t('Stock journal summary') }}
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item"
|
|
||||||
type="button"
|
|
||||||
href="{{ $U('/product/') }}{{ $stockEntry->product_id . '?returnto=/stockentries' }}">
|
|
||||||
{{ $__t('Edit product') }}
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<a class="dropdown-item stockentry-grocycode-link"
|
|
||||||
type="button"
|
|
||||||
href="{{ $U('/stockentry/' . $stockEntry->id . '/grocycode?download=true') }}">
|
|
||||||
{{ $__t('Download stock entry grocycode') }}
|
|
||||||
</a>
|
|
||||||
@if(GROCY_FEATURE_FLAG_LABELPRINTER)
|
|
||||||
<a class="dropdown-item stockentry-grocycode-stockentry-label-print"
|
|
||||||
data-stock-id="{{ $stockEntry->id }}"
|
|
||||||
type="button"
|
|
||||||
href="#">
|
|
||||||
{{ $__t('Print stock entry grocycode on label printer') }}
|
|
||||||
</a>
|
|
||||||
@endif
|
|
||||||
<a class="dropdown-item stockentry-label-link"
|
|
||||||
type="button"
|
|
||||||
target="_blank"
|
|
||||||
href="{{ $U('/stockentry/' . $stockEntry->id . '/label') }}">
|
|
||||||
{{ $__t('Open stock entry print label in new window') }}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
<td class="d-none"
|
<td class="d-none"
|
||||||
data-product-id="{{ $stockEntry->product_id }}">
|
data-product-id="{{ $stockEntry->product_id }}">
|
||||||
|
|
|
||||||
|
|
@ -4,25 +4,30 @@
|
||||||
@section('activeNav', 'stockjournal')
|
@section('activeNav', 'stockjournal')
|
||||||
@section('viewJsName', 'stockjournal')
|
@section('viewJsName', 'stockjournal')
|
||||||
|
|
||||||
|
@php
|
||||||
|
$collapsed_none = $embedded ? '' : 'd-md-none';
|
||||||
|
$collapsed_flex = $embedded ? '' : 'd-md-flex';
|
||||||
|
@endphp
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="title-related-links">
|
<div class="title-related-links">
|
||||||
<h2 class="title">@yield('title')</h2>
|
<h2 class="title">@yield('title')</h2>
|
||||||
<div class="float-right">
|
<div class="float-right">
|
||||||
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
|
<button class="btn btn-outline-dark {{ $collapsed_none }} mt-2 order-1 order-md-3"
|
||||||
type="button"
|
type="button"
|
||||||
data-toggle="collapse"
|
data-toggle="collapse"
|
||||||
data-target="#table-filter-row">
|
data-target="#stockjournal-table-filter-row">
|
||||||
<i class="fas fa-filter"></i>
|
<i class="fas fa-filter"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
|
<button class="btn btn-outline-dark {{ $collapsed_none }} mt-2 order-1 order-md-3"
|
||||||
type="button"
|
type="button"
|
||||||
data-toggle="collapse"
|
data-toggle="collapse"
|
||||||
data-target="#related-links">
|
data-target="#stockjournal-related-links">
|
||||||
<i class="fas fa-ellipsis-v"></i>
|
<i class="fas fa-ellipsis-v"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
|
<div class="related-links collapse {{ $collapsed_flex }} order-2 width-xs-sm-100"
|
||||||
id="related-links">
|
id="stockjournal-related-links">
|
||||||
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right"
|
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right"
|
||||||
href="{{ $U('/stockjournal/summary') }}">
|
href="{{ $U('/stockjournal/summary') }}">
|
||||||
{{ $__t('Journal summary') }}
|
{{ $__t('Journal summary') }}
|
||||||
|
|
@ -32,8 +37,8 @@
|
||||||
|
|
||||||
<hr class="my-2">
|
<hr class="my-2">
|
||||||
|
|
||||||
<div class="row collapse d-md-flex"
|
<div class="row collapse {{ $collapsed_flex }}"
|
||||||
id="table-filter-row">
|
id="stockjournal-table-filter-row">
|
||||||
<div class="col-12 col-md-6 col-xl-2">
|
<div class="col-12 col-md-6 col-xl-2">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,27 @@
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
<div class="dropdown">
|
||||||
|
<div class="table-inline-menu dropdown-menu detached-dropdown-menu dropdown-menu-right" id="datatable-dropdown">
|
||||||
|
@include('components.stockentrydropdowncommon')
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a class="dropdown-item stockentry-grocycode-link"
|
||||||
|
type="button"
|
||||||
|
data-href="{{ $U('/product/PRODUCT_ID/grocycode?download=true') }}">
|
||||||
|
{{ $__t('Download product grocycode') }}
|
||||||
|
</a>
|
||||||
|
@if(GROCY_FEATURE_FLAG_LABELPRINTER)
|
||||||
|
<a class="dropdown-item stockentry-grocycode-product-label-print"
|
||||||
|
data-product-id="xxx"
|
||||||
|
type="button"
|
||||||
|
href="#">
|
||||||
|
{{ $__t('Print product grocycode on label printer') }}
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<table id="stock-overview-table"
|
<table id="stock-overview-table"
|
||||||
class="table table-sm table-striped nowrap w-100">
|
class="table table-sm table-striped nowrap w-100">
|
||||||
<thead>
|
<thead>
|
||||||
|
|
@ -209,102 +230,19 @@
|
||||||
<i class="fas fa-box-open"></i> <span class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->quick_consume_amount }}</span>
|
<i class="fas fa-box-open"></i> <span class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->quick_consume_amount }}</span>
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
<div class="dropdown d-inline-block">
|
<button class="btn btn-sm btn-light text-secondary"
|
||||||
<button class="btn btn-sm btn-light text-secondary"
|
type="button"
|
||||||
type="button"
|
id="detached-dropdown-{!! uniqid() !!}"
|
||||||
data-toggle="dropdown">
|
data-toggle="dropdown-detached"
|
||||||
<i class="fas fa-ellipsis-v"></i>
|
data-target="#datatable-dropdown"
|
||||||
</button>
|
data-product-id="{{ $currentStockEntry->product_id }}"
|
||||||
<div class="table-inline-menu dropdown-menu dropdown-menu-right">
|
data-product-name="{{ $currentStockEntry->product_name }}"
|
||||||
<a class="dropdown-item show-as-dialog-link permission-SHOPPINGLIST_ITEMS_ADD"
|
data-product-qu-name="{{ $currentStockEntry->qu_unit_name }}"
|
||||||
type="button"
|
data-transfer="{{ ($currentStockEntry->amount < 1 ? 1 : 0) }}"
|
||||||
href="{{ $U('/shoppinglistitem/new?embedded&updateexistingproduct&product=' . $currentStockEntry->product_id ) }}">
|
data-consume="{{ ($currentStockEntry->amount < 1 ? 1 : 0) }}"
|
||||||
<span class="dropdown-item-icon"><i class="fas fa-shopping-cart"></i></span> <span class="dropdown-item-text">{{ $__t('Add to shopping list') }}</span>
|
data-location-id="">
|
||||||
</a>
|
<i class="fas fa-ellipsis-v"></i>
|
||||||
<div class="dropdown-divider"></div>
|
</button>
|
||||||
<a class="dropdown-item show-as-dialog-link permission-STOCK_PURCHASE"
|
|
||||||
type="button"
|
|
||||||
href="{{ $U('/purchase?embedded&product=' . $currentStockEntry->product_id ) }}">
|
|
||||||
<span class="dropdown-item-icon"><i class="fas fa-cart-plus"></i></span> <span class="dropdown-item-text">{{ $__t('Purchase') }}</span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item show-as-dialog-link permission-STOCK_CONSUME"
|
|
||||||
type="button"
|
|
||||||
href="{{ $U('/consume?embedded&product=' . $currentStockEntry->product_id ) }}">
|
|
||||||
<span class="dropdown-item-icon"><i class="fas fa-utensils"></i></span> <span class="dropdown-item-text">{{ $__t('Consume') }}</span>
|
|
||||||
</a>
|
|
||||||
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
|
|
||||||
<a class="dropdown-item show-as-dialog-link permission-STOCK_TRANSFER @if($currentStockEntry->amount < 1) disabled @endif"
|
|
||||||
type="button"
|
|
||||||
href="{{ $U('/transfer?embedded&product=' . $currentStockEntry->product_id) }}">
|
|
||||||
<span class="dropdown-item-icon"><i class="fas fa-exchange-alt"></i></span> <span class="dropdown-item-text">{{ $__t('Transfer') }}</span>
|
|
||||||
</a>
|
|
||||||
@endif
|
|
||||||
<a class="dropdown-item show-as-dialog-link permission-STOCK_INVENTORY"
|
|
||||||
type="button"
|
|
||||||
href="{{ $U('/inventory?embedded&product=' . $currentStockEntry->product_id ) }}">
|
|
||||||
<span class="dropdown-item-icon"><i class="fas fa-list"></i></span> <span class="dropdown-item-text">{{ $__t('Inventory') }}</span>
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<a class="dropdown-item product-consume-button product-consume-button-spoiled permission-STOCK_CONSUME @if($currentStockEntry->amount_aggregated < 1) disabled @endif"
|
|
||||||
type="button"
|
|
||||||
href="#"
|
|
||||||
data-product-id="{{ $currentStockEntry->product_id }}"
|
|
||||||
data-product-name="{{ $currentStockEntry->product_name }}"
|
|
||||||
data-product-qu-name="{{ $currentStockEntry->qu_unit_name }}"
|
|
||||||
data-consume-amount="1">
|
|
||||||
<span class="dropdown-item-text">{{ $__t('Consume %1$s of %2$s as spoiled', '1 ' . $currentStockEntry->qu_unit_name, $currentStockEntry->product_name) }}</span>
|
|
||||||
</a>
|
|
||||||
@if(GROCY_FEATURE_FLAG_RECIPES)
|
|
||||||
<a class="dropdown-item"
|
|
||||||
type="button"
|
|
||||||
href="{{ $U('/recipes?search=') }}{{ $currentStockEntry->product_name }}">
|
|
||||||
<span class="dropdown-item-text">{{ $__t('Search for recipes containing this product') }}</span>
|
|
||||||
</a>
|
|
||||||
@endif
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<a class="dropdown-item product-name-cell"
|
|
||||||
data-product-id="{{ $currentStockEntry->product_id }}"
|
|
||||||
type="button"
|
|
||||||
href="#">
|
|
||||||
<span class="dropdown-item-text">{{ $__t('Product overview') }}</span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item show-as-dialog-link"
|
|
||||||
type="button"
|
|
||||||
href="{{ $U('/stockentries?embedded&product=') }}{{ $currentStockEntry->product_id }}"
|
|
||||||
data-product-id="{{ $currentStockEntry->product_id }}">
|
|
||||||
<span class="dropdown-item-text">{{ $__t('Stock entries') }}</span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item show-as-dialog-link"
|
|
||||||
type="button"
|
|
||||||
href="{{ $U('/stockjournal?embedded&product=') }}{{ $currentStockEntry->product_id }}">
|
|
||||||
<span class="dropdown-item-text">{{ $__t('Stock journal') }}</span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item show-as-dialog-link"
|
|
||||||
type="button"
|
|
||||||
href="{{ $U('/stockjournal/summary?embedded&product_id=') }}{{ $currentStockEntry->product_id }}">
|
|
||||||
<span class="dropdown-item-text">{{ $__t('Stock journal summary') }}</span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item permission-MASTER_DATA_EDIT"
|
|
||||||
type="button"
|
|
||||||
href="{{ $U('/product/') }}{{ $currentStockEntry->product_id . '?returnto=%2Fstockoverview' }}">
|
|
||||||
<span class="dropdown-item-text">{{ $__t('Edit product') }}</span>
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<a class="dropdown-item stockentry-grocycode-link"
|
|
||||||
type="button"
|
|
||||||
href="{{ $U('/product/' . $currentStockEntry->product_id . '/grocycode?download=true') }}">
|
|
||||||
{{ $__t('Download product grocycode') }}
|
|
||||||
</a>
|
|
||||||
@if(GROCY_FEATURE_FLAG_LABELPRINTER)
|
|
||||||
<a class="dropdown-item stockentry-grocycode-product-label-print"
|
|
||||||
data-product-id="{{ $currentStockEntry->product_id }}"
|
|
||||||
type="button"
|
|
||||||
href="#">
|
|
||||||
{{ $__t('Print product grocycode on label printer') }}
|
|
||||||
</a>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
<td class="product-name-cell cursor-link"
|
<td class="product-name-cell cursor-link"
|
||||||
data-product-id="{{ $currentStockEntry->product_id }}">
|
data-product-id="{{ $currentStockEntry->product_id }}">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user