Bootstrap Dropdown with detached dropdown definition

This commit is contained in:
Katharina Bogad 2021-06-25 16:35:20 +02:00
parent a8153a2cf2
commit 5be9faed43
8 changed files with 512 additions and 205 deletions

View File

@ -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
View 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 }

View File

@ -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)

View File

@ -43,4 +43,8 @@
/* Always show modals over everything else */ /* Always show modals over everything else */
.modal { .modal {
z-index: 99998; z-index: 99998;
}
.detached-dropdown-menu {
z-index: 99999;
} }

View 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>

View File

@ -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 }}">

View File

@ -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">

View File

@ -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 }}">