diff --git a/src/web/assets/inventory/InventoryAsset.php b/src/web/assets/inventory/InventoryAsset.php
index 6cde1b2bf6..b94a875e40 100644
--- a/src/web/assets/inventory/InventoryAsset.php
+++ b/src/web/assets/inventory/InventoryAsset.php
@@ -62,6 +62,11 @@ public function registerAssetFiles($view): void
'Available',
'On Hand',
'Incoming',
+ 'View',
+ 'View settings',
+ 'Table Columns',
+ 'Purchasable',
+ 'SKU',
]);
}
}
diff --git a/src/web/assets/inventory/dist/css/inventory.css b/src/web/assets/inventory/dist/css/inventory.css
index 0028513110..9fe2c164ab 100644
--- a/src/web/assets/inventory/dist/css/inventory.css
+++ b/src/web/assets/inventory/dist/css/inventory.css
@@ -1,2 +1,2 @@
-.ltr .inventory-headers{-webkit-justify-content:flex-end;justify-content:flex-end}.rtl .inventory-headers{-webkit-justify-content:flex-start;justify-content:flex-start}.ltr .inventory-cell{text-align:right}.ltr .inventory-cell .action-btn{right:calc(var(--touch-target-size)*-1/1.5)}.rtl .inventory-cell{text-align:left}.rtl .inventory-cell .action-btn{left:calc(var(--touch-target-size)*-1/1.5)}#inventory-levels .inventory-cell{position:relative}#inventory-levels .inventory-cell .action-btn{opacity:0;position:absolute;z-index:99}#inventory-levels .inventory-cell .action-btn[aria-expanded=true],#inventory-levels .vue-admin-table tbody tr:hover .inventory-cell .action-btn{opacity:1}
+.inventory-levels-toolbar{margin-bottom:var(--m)}.ltr .inventory-headers{-webkit-justify-content:flex-end;justify-content:flex-end}.rtl .inventory-headers{-webkit-justify-content:flex-start;justify-content:flex-start}.ltr .inventory-cell{text-align:right}.ltr .inventory-cell .action-btn{right:calc(var(--touch-target-size)*-1/1.5)}.rtl .inventory-cell{text-align:left}.rtl .inventory-cell .action-btn{left:calc(var(--touch-target-size)*-1/1.5)}#inventory-levels .inventory-cell{position:relative}#inventory-levels .inventory-cell .action-btn{opacity:0;position:absolute;z-index:99}#inventory-levels .inventory-cell .action-btn[aria-expanded=true],#inventory-levels .vue-admin-table tbody tr:hover .inventory-cell .action-btn{opacity:1}
/*# sourceMappingURL=inventory.css.map*/
\ No newline at end of file
diff --git a/src/web/assets/inventory/dist/css/inventory.css.map b/src/web/assets/inventory/dist/css/inventory.css.map
index 77f1e96a94..1204b0dcfe 100644
--- a/src/web/assets/inventory/dist/css/inventory.css.map
+++ b/src/web/assets/inventory/dist/css/inventory.css.map
@@ -1 +1 @@
-{"version":3,"file":"css/inventory.css","mappings":"AACE,wBACE,0DAGF,wBACE,8DAeF,qBACE,iBAGF,iCACE,4CAKF,qBACE,gBAGF,iCACE,2CAKF,kCACE,kBAGF,8CACE,UACA,kBACA,WAQA,gJACE","sources":["webpack:///./css/inventory.scss"],"sourcesContent":[".inventory-headers {\n .ltr & {\n justify-content: flex-end;\n }\n\n .rtl & {\n justify-content: flex-start;\n }\n}\n\n.inventory-cell {\n .ltr & {\n text-align: right;\n }\n\n .rtl & {\n text-align: left;\n }\n}\n\n.ltr {\n .inventory-cell {\n text-align: right;\n }\n\n .inventory-cell .action-btn {\n right: calc(var(--touch-target-size) * -1 / 1.5);\n }\n}\n\n.rtl {\n .inventory-cell {\n text-align: left;\n }\n\n .inventory-cell .action-btn {\n left: calc(var(--touch-target-size) * -1 / 1.5);\n }\n}\n\n#inventory-levels {\n .inventory-cell {\n position: relative;\n }\n\n .inventory-cell .action-btn {\n opacity: 0;\n position: absolute;\n z-index: 99;\n }\n\n .inventory-cell .action-btn[aria-expanded='true'] {\n opacity: 1;\n }\n\n .vue-admin-table tbody tr:hover {\n .inventory-cell .action-btn {\n opacity: 1;\n }\n }\n}\n"],"names":[],"sourceRoot":""}
\ No newline at end of file
+{"version":3,"file":"css/inventory.css","mappings":"AACA,0BACE,uBAIA,wBACE,0DAGF,wBACE,8DAeF,qBACE,iBAGF,iCACE,4CAKF,qBACE,gBAGF,iCACE,2CAKF,kCACE,kBAGF,8CACE,UACA,kBACA,WAQA,gJACE","sources":["webpack:///./css/inventory.scss"],"sourcesContent":["// Inventory levels toolbar (contains the View button)\n.inventory-levels-toolbar {\n margin-bottom: var(--m);\n}\n\n.inventory-headers {\n .ltr & {\n justify-content: flex-end;\n }\n\n .rtl & {\n justify-content: flex-start;\n }\n}\n\n.inventory-cell {\n .ltr & {\n text-align: right;\n }\n\n .rtl & {\n text-align: left;\n }\n}\n\n.ltr {\n .inventory-cell {\n text-align: right;\n }\n\n .inventory-cell .action-btn {\n right: calc(var(--touch-target-size) * -1 / 1.5);\n }\n}\n\n.rtl {\n .inventory-cell {\n text-align: left;\n }\n\n .inventory-cell .action-btn {\n left: calc(var(--touch-target-size) * -1 / 1.5);\n }\n}\n\n#inventory-levels {\n .inventory-cell {\n position: relative;\n }\n\n .inventory-cell .action-btn {\n opacity: 0;\n position: absolute;\n z-index: 99;\n }\n\n .inventory-cell .action-btn[aria-expanded='true'] {\n opacity: 1;\n }\n\n .vue-admin-table tbody tr:hover {\n .inventory-cell .action-btn {\n opacity: 1;\n }\n }\n}\n"],"names":[],"sourceRoot":""}
\ No newline at end of file
diff --git a/src/web/assets/inventory/dist/inventory.js b/src/web/assets/inventory/dist/inventory.js
index 9726a2bb70..654d2b65bb 100644
--- a/src/web/assets/inventory/dist/inventory.js
+++ b/src/web/assets/inventory/dist/inventory.js
@@ -1,2 +1,2 @@
-!function(){var e={576:function(){function e(t){return e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e(t)}"undefined"===e(Craft.Commerce)&&(Craft.Commerce={}),Craft.Commerce.InventoryLevelsManager=Garnish.Base.extend({settings:null,containerId:null,$container:null,adminTableId:null,init:function(e,t){this.containerId=e,this.setSettings(t,Craft.Commerce.InventoryLevelsManager.defaults),this.$container=$(this.containerId),this.$container.data("inventoryLevelsManager")&&(console.warn("Double-instantiating an Inventory Levels Manager on an element."),this.$container.data("inventoryLevelsManager").destroy()),this.$container.data("inventoryLevelsManager",this),this.adminTableId="inventory-admin-table-"+Math.random().toString(36).substring(7),this.$adminTable=$('
').appendTo(this.$container),this.initAdminTable()},initAdminTable:function(){var e=this;this.columns=[{name:"purchasable",sortField:"item",title:Craft.t("commerce","Purchasable")},{name:"sku",sortField:"sku",title:Craft.t("commerce","SKU")},{name:"reserved",sortField:"reservedTotal",titleClass:"inventory-headers",dataClass:"inventory-cell",title:Craft.t("commerce","Reserved")},{name:"damaged",sortField:"damagedTotal",titleClass:"inventory-headers",dataClass:"inventory-cell",title:Craft.t("commerce","Damaged")},{name:"safety",sortField:"safetyTotal",titleClass:"inventory-headers",dataClass:"inventory-cell",title:Craft.t("commerce","Safety")},{name:"qualityControl",sortField:"qualityControlTotal",titleClass:"inventory-headers",dataClass:"inventory-cell",title:Craft.t("commerce","Quality Control")},{name:"committed",sortField:"committedTotal",titleClass:"inventory-headers",dataClass:"inventory-cell",title:Craft.t("commerce","Committed")},{name:"available",sortField:"availableTotal",titleClass:"inventory-headers",dataClass:"inventory-cell",title:Craft.t("commerce","Available")},{name:"onHand",sortField:"onHandTotal",titleClass:"inventory-headers",dataClass:"inventory-cell",title:Craft.t("commerce","On Hand")},{name:"incoming",sortField:"incomingTotal",titleClass:"inventory-headers",dataClass:"inventory-cell",title:Craft.t("commerce","Incoming")}],this.adminTable=new Craft.VueAdminTable({columns:this.columns,container:"#"+this.adminTableId,checkboxes:!1,allowMultipleSelections:!0,fullPane:!1,perPage:25,tableDataEndpoint:"commerce/inventory/inventory-levels-table-data",onQueryParams:function(t){return t.inventoryLocationId=e.settings.inventoryLocationId,e.settings.inventoryItemId&&(t.inventoryItemId=e.settings.inventoryItemId),t.containerId=e.containerId,t},search:!0,searchPlaceholder:Craft.t("commerce","Search inventory"),emptyMessage:Craft.t("commerce","No inventory found."),padded:!0})},defaultSettings:{inventoryLocationId:null,inventoryItemId:null}})},476:function(){function e(t){return e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e(t)}"undefined"===e(Craft.Commerce)&&(Craft.Commerce={}),Craft.Commerce.InventoryMovementModal=Craft.CpModal.extend({$quantityInput:null,$toInventoryMovementTypeInput:null,init:function(e){this.base("commerce/inventory/edit-movement-modal",e),this.debouncedRefresh=this.debounce(this.refresh,500),this.on("load",this.afterLoad.bind(this))},afterLoad:function(){var e=Craft.namespaceId("inventoryMovement-quantity",this.namespace);this.$quantityInput=this.$container.find("#"+e),this.addListener(this.$quantityInput,"keyup",this.debouncedRefresh);var t=Craft.namespaceId("inventoryMovement-toInventoryTransactionType",this.namespace);this.$toInventoryMovementTypeInput=this.$container.find("#"+t),this.addListener(this.$toInventoryMovementTypeInput,"change",this.refresh)},refresh:function(){var e=this,t=Garnish.getPostData(this.$container),n={data:Craft.expandPostArray(t),headers:{"X-Craft-Namespace":this.namespace}};Craft.sendActionRequest("POST",this.action,n).then((function(t){e.showLoadSpinner(),e.update(t.data).then((function(){e.$quantityInput.trigger("focus"),e.updateSizeAndPosition()})).finally((function(){e.hideLoadSpinner()}))}))},debounce:function(e,t){var n,a=this;return function(){for(var r=arguments.length,i=new Array(r),o=0;on.parts.length&&(a.parts.length=n.parts.length)}else{var o=[];for(r=0;r').appendTo(this.$container),this.initAdminTable()},getStorageKey:function(){return"Craft-"+Craft.siteUid+".Commerce.InventoryLevels.viewPreferences."+Craft.userId},loadViewPreferences:function(){var e=this.getStorageKey(),t=localStorage.getItem(e);if(t)try{this.viewPreferences=JSON.parse(t)}catch(e){this.viewPreferences=this.getDefaultViewPreferences()}else this.viewPreferences=this.getDefaultViewPreferences()},getDefaultViewPreferences:function(){var e={};return this.optionalColumns.forEach((function(t){e[t]=!0})),{visibleColumns:e}},saveViewPreferences:function(){var e=this.getStorageKey();localStorage.setItem(e,JSON.stringify(this.viewPreferences))},getAllColumns:function(){return[{name:"purchasable",sortField:"item",title:Craft.t("commerce","Purchasable")},{name:"sku",sortField:"sku",title:Craft.t("commerce","SKU")},{name:"reserved",sortField:"reservedTotal",titleClass:"inventory-headers",dataClass:"inventory-cell",title:Craft.t("commerce","Reserved")},{name:"damaged",sortField:"damagedTotal",titleClass:"inventory-headers",dataClass:"inventory-cell",title:Craft.t("commerce","Damaged")},{name:"safety",sortField:"safetyTotal",titleClass:"inventory-headers",dataClass:"inventory-cell",title:Craft.t("commerce","Safety")},{name:"qualityControl",sortField:"qualityControlTotal",titleClass:"inventory-headers",dataClass:"inventory-cell",title:Craft.t("commerce","Quality Control")},{name:"committed",sortField:"committedTotal",titleClass:"inventory-headers",dataClass:"inventory-cell",title:Craft.t("commerce","Committed")},{name:"available",sortField:"availableTotal",titleClass:"inventory-headers",dataClass:"inventory-cell",title:Craft.t("commerce","Available")},{name:"onHand",sortField:"onHandTotal",titleClass:"inventory-headers",dataClass:"inventory-cell",title:Craft.t("commerce","On Hand")},{name:"incoming",sortField:"incomingTotal",titleClass:"inventory-headers",dataClass:"inventory-cell",title:Craft.t("commerce","Incoming")}]},getVisibleColumns:function(){var e=this;return this.getAllColumns().filter((function(t){return-1!==e.requiredColumns.indexOf(t.name)||!1!==e.viewPreferences.visibleColumns[t.name]}))},initAdminTable:function(){var e=this;this.columns=this.getVisibleColumns(),this.adminTable=new Craft.VueAdminTable({columns:this.columns,container:"#"+this.adminTableId,checkboxes:!1,allowMultipleSelections:!0,fullPane:!1,perPage:25,tableDataEndpoint:"commerce/inventory/inventory-levels-table-data",onQueryParams:function(t){return t.inventoryLocationId=e.settings.inventoryLocationId,e.settings.inventoryItemId&&(t.inventoryItemId=e.settings.inventoryItemId),t.containerId=e.containerId,t},search:!0,searchPlaceholder:Craft.t("commerce","Search inventory"),emptyMessage:Craft.t("commerce","No inventory found."),padded:!0})},initViewMenu:function(){var e=this,t=this.getAllColumns();this.viewMenuId="inventory-view-menu-"+Math.random().toString(36).substring(7),this.$viewToolbar=$("",{class:"flex inventory-levels-toolbar"}).appendTo(this.$container),$('').appendTo(this.$viewToolbar),this.$viewBtn=$("",{type:"button",class:"btn menubtn",text:Craft.t("commerce","View"),"aria-label":Craft.t("commerce","View settings"),"aria-controls":this.viewMenuId,"data-icon":"sliders"}).appendTo(this.$viewToolbar),this.$viewMenu=$("",{id:this.viewMenuId,class:"menu menu--disclosure element-index-view-menu","data-align":"right"}).appendTo(Garnish.$bod),this._buildViewMenu(t),this.viewMenu=new Garnish.DisclosureMenu(this.$viewBtn),this.viewMenu.on("show",(function(){e.$viewBtn.addClass("active")})),this.viewMenu.on("hide",(function(){e.$viewBtn.removeClass("active")})),this.$viewMenu.on("mousedown",(function(e){e.stopPropagation()})),this.bindViewMenuEvents()},_buildViewMenu:function(e){var t=this,n=$('').appendTo(this.$viewMenu);this.$tableColumnsField=this._createTableColumnsField(e).appendTo(n);var i=$("",{class:"flex menu-footer"}).appendTo(this.$viewMenu);$('').appendTo(i),this.$closeBtn=$("",{type:"button",class:"btn",text:Craft.t("app","Close")}).appendTo(i).on("click",(function(){t.viewMenu.hide()}))},_createTableColumnsField:function(e){var t=this,n=e.filter((function(e){return-1!==t.optionalColumns.indexOf(e.name)})).map((function(e){return{label:e.title,value:e.name}})),i=n.filter((function(e){return!1!==t.viewPreferences.visibleColumns[e.value]})).map((function(e){return e.value}));this.$tableColumnsContainer=Craft.ui.createCheckboxSelect({options:n,values:i});var a=Craft.ui.createField(this.$tableColumnsContainer,{label:Craft.t("commerce","Table Columns"),fieldset:!0});return a.addClass("table-columns-field"),a},bindViewMenuEvents:function(){var e=this;this.$tableColumnsContainer.find('input[type="checkbox"]').on("change",(function(){var t=$(this),n=t.val(),i=t.is(":checked");e.viewPreferences.visibleColumns[n]=i,e.saveViewPreferences(),e.rebuildTable()}))},rebuildTable:function(){var e=this,t=this.$container.find('.vue-admin-table input[type="search"]').val()||"";if(this.$adminTable.empty(),this.initAdminTable(),t)var n=setInterval((function(){var i=e.$container.find('.vue-admin-table input[type="search"]');i.length&&(clearInterval(n),i.val(t),i.trigger("input"))}),100)},defaultSettings:{inventoryLocationId:null,inventoryItemId:null}})},476:function(){function e(t){return e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e(t)}"undefined"===e(Craft.Commerce)&&(Craft.Commerce={}),Craft.Commerce.InventoryMovementModal=Craft.CpModal.extend({$quantityInput:null,$toInventoryMovementTypeInput:null,init:function(e){this.base("commerce/inventory/edit-movement-modal",e),this.debouncedRefresh=this.debounce(this.refresh,500),this.on("load",this.afterLoad.bind(this))},afterLoad:function(){var e=Craft.namespaceId("inventoryMovement-quantity",this.namespace);this.$quantityInput=this.$container.find("#"+e),this.addListener(this.$quantityInput,"keyup",this.debouncedRefresh);var t=Craft.namespaceId("inventoryMovement-toInventoryTransactionType",this.namespace);this.$toInventoryMovementTypeInput=this.$container.find("#"+t),this.addListener(this.$toInventoryMovementTypeInput,"change",this.refresh)},refresh:function(){var e=this,t=Garnish.getPostData(this.$container),n={data:Craft.expandPostArray(t),headers:{"X-Craft-Namespace":this.namespace}};Craft.sendActionRequest("POST",this.action,n).then((function(t){e.showLoadSpinner(),e.update(t.data).then((function(){e.$quantityInput.trigger("focus"),e.updateSizeAndPosition()})).finally((function(){e.hideLoadSpinner()}))}))},debounce:function(e,t){var n,i=this;return function(){for(var a=arguments.length,r=new Array(a),o=0;on.parts.length&&(i.parts.length=n.parts.length)}else{var o=[];for(a=0;a').appendTo(\n this.$container\n );\n\n this.initAdminTable();\n },\n\n initAdminTable: function () {\n this.columns = [\n {\n name: 'purchasable',\n sortField: 'item',\n title: Craft.t('commerce', 'Purchasable'),\n },\n {name: 'sku', sortField: 'sku', title: Craft.t('commerce', 'SKU')},\n {\n name: 'reserved',\n sortField: 'reservedTotal',\n titleClass: 'inventory-headers',\n dataClass: 'inventory-cell',\n title: Craft.t('commerce', 'Reserved'),\n },\n {\n name: 'damaged',\n sortField: 'damagedTotal',\n titleClass: 'inventory-headers',\n dataClass: 'inventory-cell',\n title: Craft.t('commerce', 'Damaged'),\n },\n {\n name: 'safety',\n sortField: 'safetyTotal',\n titleClass: 'inventory-headers',\n dataClass: 'inventory-cell',\n title: Craft.t('commerce', 'Safety'),\n },\n {\n name: 'qualityControl',\n sortField: 'qualityControlTotal',\n titleClass: 'inventory-headers',\n dataClass: 'inventory-cell',\n title: Craft.t('commerce', 'Quality Control'),\n },\n {\n name: 'committed',\n sortField: 'committedTotal',\n titleClass: 'inventory-headers',\n dataClass: 'inventory-cell',\n title: Craft.t('commerce', 'Committed'),\n },\n {\n name: 'available',\n sortField: 'availableTotal',\n titleClass: 'inventory-headers',\n dataClass: 'inventory-cell',\n title: Craft.t('commerce', 'Available'),\n },\n {\n name: 'onHand',\n sortField: 'onHandTotal',\n titleClass: 'inventory-headers',\n dataClass: 'inventory-cell',\n title: Craft.t('commerce', 'On Hand'),\n },\n {\n name: 'incoming',\n sortField: 'incomingTotal',\n titleClass: 'inventory-headers',\n dataClass: 'inventory-cell',\n title: Craft.t('commerce', 'Incoming'),\n },\n ];\n\n this.adminTable = new Craft.VueAdminTable({\n columns: this.columns,\n container: '#' + this.adminTableId,\n checkboxes: false,\n allowMultipleSelections: true,\n fullPane: false,\n perPage: 25,\n tableDataEndpoint: 'commerce/inventory/inventory-levels-table-data',\n onQueryParams: (params) => {\n // Arrow function to maintain 'this' context\n params.inventoryLocationId = this.settings.inventoryLocationId;\n if (this.settings.inventoryItemId) {\n params.inventoryItemId = this.settings.inventoryItemId;\n }\n params.containerId = this.containerId;\n return params;\n },\n search: true,\n searchPlaceholder: Craft.t('commerce', 'Search inventory'),\n emptyMessage: Craft.t('commerce', 'No inventory found.'),\n padded: true,\n });\n },\n\n defaultSettings: {\n inventoryLocationId: null,\n inventoryItemId: null,\n },\n});\n","/* jshint esversion: 6, strict: false */\n/* globals Craft, Garnish, $ */\nif (typeof Craft.Commerce === typeof undefined) {\n Craft.Commerce = {};\n}\n\nCraft.Commerce.InventoryMovementModal = Craft.CpModal.extend({\n $quantityInput: null,\n $toInventoryMovementTypeInput: null,\n\n init: function (settings) {\n this.base('commerce/inventory/edit-movement-modal', settings);\n\n this.debouncedRefresh = this.debounce(this.refresh, 500);\n\n // after load event is triggered on this\n this.on('load', this.afterLoad.bind(this));\n },\n afterLoad: function () {\n const quantityId = Craft.namespaceId(\n 'inventoryMovement-quantity',\n this.namespace\n );\n this.$quantityInput = this.$container.find('#' + quantityId);\n this.addListener(this.$quantityInput, 'keyup', this.debouncedRefresh);\n\n const toTransactionId = Craft.namespaceId(\n 'inventoryMovement-toInventoryTransactionType',\n this.namespace\n );\n this.$toInventoryMovementTypeInput = this.$container.find(\n '#' + toTransactionId\n );\n this.addListener(\n this.$toInventoryMovementTypeInput,\n 'change',\n this.refresh\n );\n },\n refresh: function () {\n let postData = Garnish.getPostData(this.$container);\n let expandedData = Craft.expandPostArray(postData);\n\n let data = {\n data: expandedData,\n headers: {\n 'X-Craft-Namespace': this.namespace,\n },\n };\n\n Craft.sendActionRequest('POST', this.action, data).then((response) => {\n this.showLoadSpinner();\n this.update(response.data)\n .then(() => {\n // focus on the quantity input\n this.$quantityInput.trigger('focus');\n this.updateSizeAndPosition();\n })\n .finally(() => {\n this.hideLoadSpinner();\n });\n });\n },\n debounce: function (func, delay) {\n let timer;\n return (...args) => {\n clearTimeout(timer);\n timer = setTimeout(() => {\n func.apply(this, args);\n }, delay);\n };\n },\n});\n","// style-loader: Adds some css to the DOM by adding a