@@ -9,6 +9,22 @@ Craft.Commerce.InventoryLevelsManager = Garnish.Base.extend({
99 containerId : null ,
1010 $container : null ,
1111 adminTableId : null ,
12+ viewPreferences : null ,
13+
14+ // Required columns that cannot be hidden
15+ requiredColumns : [ 'purchasable' , 'sku' ] ,
16+
17+ // Optional columns that can be toggled
18+ optionalColumns : [
19+ 'reserved' ,
20+ 'damaged' ,
21+ 'safety' ,
22+ 'qualityControl' ,
23+ 'committed' ,
24+ 'available' ,
25+ 'onHand' ,
26+ 'incoming' ,
27+ ] ,
1228
1329 init : function ( container , settings ) {
1430 this . containerId = container ;
@@ -25,6 +41,12 @@ Craft.Commerce.InventoryLevelsManager = Garnish.Base.extend({
2541 }
2642 this . $container . data ( 'inventoryLevelsManager' , this ) ;
2743
44+ // Load view preferences from localStorage
45+ this . loadViewPreferences ( ) ;
46+
47+ // Create the view menu first (outside the admin table)
48+ this . initViewMenu ( ) ;
49+
2850 // random id for the admin table
2951 this . adminTableId =
3052 'inventory-admin-table-' + Math . random ( ) . toString ( 36 ) . substring ( 7 ) ;
@@ -35,8 +57,49 @@ Craft.Commerce.InventoryLevelsManager = Garnish.Base.extend({
3557 this . initAdminTable ( ) ;
3658 } ,
3759
38- initAdminTable : function ( ) {
39- this . columns = [
60+ getStorageKey : function ( ) {
61+ return (
62+ 'Craft-' +
63+ Craft . siteUid +
64+ '.Commerce.InventoryLevels.viewPreferences.' +
65+ Craft . userId
66+ ) ;
67+ } ,
68+
69+ loadViewPreferences : function ( ) {
70+ var storageKey = this . getStorageKey ( ) ;
71+ var stored = localStorage . getItem ( storageKey ) ;
72+
73+ if ( stored ) {
74+ try {
75+ this . viewPreferences = JSON . parse ( stored ) ;
76+ } catch ( e ) {
77+ this . viewPreferences = this . getDefaultViewPreferences ( ) ;
78+ }
79+ } else {
80+ this . viewPreferences = this . getDefaultViewPreferences ( ) ;
81+ }
82+ } ,
83+
84+ getDefaultViewPreferences : function ( ) {
85+ // Default: all columns visible
86+ var visibleColumns = { } ;
87+ this . optionalColumns . forEach ( function ( col ) {
88+ visibleColumns [ col ] = true ;
89+ } ) ;
90+
91+ return {
92+ visibleColumns : visibleColumns ,
93+ } ;
94+ } ,
95+
96+ saveViewPreferences : function ( ) {
97+ var storageKey = this . getStorageKey ( ) ;
98+ localStorage . setItem ( storageKey , JSON . stringify ( this . viewPreferences ) ) ;
99+ } ,
100+
101+ getAllColumns : function ( ) {
102+ return [
40103 {
41104 name : 'purchasable' ,
42105 sortField : 'item' ,
@@ -100,6 +163,24 @@ Craft.Commerce.InventoryLevelsManager = Garnish.Base.extend({
100163 title : Craft . t ( 'commerce' , 'Incoming' ) ,
101164 } ,
102165 ] ;
166+ } ,
167+
168+ getVisibleColumns : function ( ) {
169+ var self = this ;
170+ var allColumns = this . getAllColumns ( ) ;
171+
172+ return allColumns . filter ( function ( col ) {
173+ // Required columns are always visible
174+ if ( self . requiredColumns . indexOf ( col . name ) !== - 1 ) {
175+ return true ;
176+ }
177+ // Check view preferences for optional columns
178+ return self . viewPreferences . visibleColumns [ col . name ] !== false ;
179+ } ) ;
180+ } ,
181+
182+ initAdminTable : function ( ) {
183+ this . columns = this . getVisibleColumns ( ) ;
103184
104185 this . adminTable = new Craft . VueAdminTable ( {
105186 columns : this . columns ,
@@ -125,6 +206,173 @@ Craft.Commerce.InventoryLevelsManager = Garnish.Base.extend({
125206 } ) ;
126207 } ,
127208
209+ initViewMenu : function ( ) {
210+ var self = this ;
211+ var allColumns = this . getAllColumns ( ) ;
212+
213+ // Generate unique ID for the menu
214+ this . viewMenuId =
215+ 'inventory-view-menu-' + Math . random ( ) . toString ( 36 ) . substring ( 7 ) ;
216+
217+ // Create a toolbar container for the view button (positioned at top right)
218+ this . $viewToolbar = $ ( '<div/>' , {
219+ class : 'flex inventory-levels-toolbar' ,
220+ } ) . appendTo ( this . $container ) ;
221+
222+ // Spacer to push button to the right
223+ $ ( '<div class="flex-grow"/>' ) . appendTo ( this . $viewToolbar ) ;
224+
225+ // Create the view button (matching Craft's element index view button)
226+ this . $viewBtn = $ ( '<button/>' , {
227+ type : 'button' ,
228+ class : 'btn menubtn' ,
229+ text : Craft . t ( 'commerce' , 'View' ) ,
230+ 'aria-label' : Craft . t ( 'commerce' , 'View settings' ) ,
231+ 'aria-controls' : this . viewMenuId ,
232+ 'data-icon' : 'sliders' ,
233+ } ) . appendTo ( this . $viewToolbar ) ;
234+
235+ // Create the menu container (matching Craft's element-index-view-menu)
236+ this . $viewMenu = $ ( '<div/>' , {
237+ id : this . viewMenuId ,
238+ class : 'menu menu--disclosure element-index-view-menu' ,
239+ 'data-align' : 'right' ,
240+ } ) . appendTo ( Garnish . $bod ) ;
241+
242+ // Build the menu content
243+ this . _buildViewMenu ( allColumns ) ;
244+
245+ // Initialize the disclosure menu (like Craft's element index)
246+ this . viewMenu = new Garnish . DisclosureMenu ( this . $viewBtn ) ;
247+
248+ this . viewMenu . on ( 'show' , function ( ) {
249+ self . $viewBtn . addClass ( 'active' ) ;
250+ } ) ;
251+
252+ this . viewMenu . on ( 'hide' , function ( ) {
253+ self . $viewBtn . removeClass ( 'active' ) ;
254+ } ) ;
255+
256+ // Prevent menu from closing when clicking inside
257+ this . $viewMenu . on ( 'mousedown' , function ( ev ) {
258+ ev . stopPropagation ( ) ;
259+ } ) ;
260+
261+ // Bind events
262+ this . bindViewMenuEvents ( ) ;
263+ } ,
264+
265+ _buildViewMenu : function ( allColumns ) {
266+ var self = this ;
267+
268+ // Meta container (like Craft's view menu)
269+ var $metaContainer = $ ( '<div class="meta"/>' ) . appendTo ( this . $viewMenu ) ;
270+
271+ // Table columns field
272+ this . $tableColumnsField =
273+ this . _createTableColumnsField ( allColumns ) . appendTo ( $metaContainer ) ;
274+
275+ // Footer with close button
276+ var $footerContainer = $ ( '<div/>' , {
277+ class : 'flex menu-footer' ,
278+ } ) . appendTo ( this . $viewMenu ) ;
279+
280+ $ ( '<div class="flex-grow"/>' ) . appendTo ( $footerContainer ) ;
281+
282+ this . $closeBtn = $ ( '<button/>' , {
283+ type : 'button' ,
284+ class : 'btn' ,
285+ text : Craft . t ( 'app' , 'Close' ) ,
286+ } )
287+ . appendTo ( $footerContainer )
288+ . on ( 'click' , function ( ) {
289+ self . viewMenu . hide ( ) ;
290+ } ) ;
291+ } ,
292+
293+ _createTableColumnsField : function ( allColumns ) {
294+ var self = this ;
295+
296+ // Build checkbox options (only for optional columns)
297+ var options = allColumns
298+ . filter ( function ( col ) {
299+ return self . optionalColumns . indexOf ( col . name ) !== - 1 ;
300+ } )
301+ . map ( function ( col ) {
302+ return {
303+ label : col . title ,
304+ value : col . name ,
305+ } ;
306+ } ) ;
307+
308+ // Get currently visible columns
309+ var visibleValues = options
310+ . filter ( function ( opt ) {
311+ return self . viewPreferences . visibleColumns [ opt . value ] !== false ;
312+ } )
313+ . map ( function ( opt ) {
314+ return opt . value ;
315+ } ) ;
316+
317+ // Create checkbox select using Craft's UI helper
318+ this . $tableColumnsContainer = Craft . ui . createCheckboxSelect ( {
319+ options : options ,
320+ values : visibleValues ,
321+ } ) ;
322+
323+ // Wrap in a field
324+ var $field = Craft . ui . createField ( this . $tableColumnsContainer , {
325+ label : Craft . t ( 'commerce' , 'Table Columns' ) ,
326+ fieldset : true ,
327+ } ) ;
328+ $field . addClass ( 'table-columns-field' ) ;
329+
330+ return $field ;
331+ } ,
332+
333+ bindViewMenuEvents : function ( ) {
334+ var self = this ;
335+
336+ // Column visibility change
337+ this . $tableColumnsContainer
338+ . find ( 'input[type="checkbox"]' )
339+ . on ( 'change' , function ( ) {
340+ var $checkbox = $ ( this ) ;
341+ var columnName = $checkbox . val ( ) ;
342+ var isChecked = $checkbox . is ( ':checked' ) ;
343+
344+ self . viewPreferences . visibleColumns [ columnName ] = isChecked ;
345+ self . saveViewPreferences ( ) ;
346+ self . rebuildTable ( ) ;
347+ } ) ;
348+ } ,
349+
350+ rebuildTable : function ( ) {
351+ // Store current search value
352+ var searchValue =
353+ this . $container . find ( '.vue-admin-table input[type="search"]' ) . val ( ) || '' ;
354+
355+ // Remove the old table
356+ this . $adminTable . empty ( ) ;
357+
358+ // Reinitialize with new columns
359+ this . initAdminTable ( ) ;
360+
361+ // Reapply search if there was one
362+ if ( searchValue ) {
363+ var checkSearch = setInterval ( ( ) => {
364+ var $searchInput = this . $container . find (
365+ '.vue-admin-table input[type="search"]'
366+ ) ;
367+ if ( $searchInput . length ) {
368+ clearInterval ( checkSearch ) ;
369+ $searchInput . val ( searchValue ) ;
370+ $searchInput . trigger ( 'input' ) ;
371+ }
372+ } , 100 ) ;
373+ }
374+ } ,
375+
128376 defaultSettings : {
129377 inventoryLocationId : null ,
130378 inventoryItemId : null ,
0 commit comments