Skip to content

Commit 388b3db

Browse files
committed
Add view column options to inventory screen
Put options into local storage
1 parent 274b290 commit 388b3db

7 files changed

Lines changed: 264 additions & 6 deletions

File tree

src/web/assets/inventory/InventoryAsset.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ public function registerAssetFiles($view): void
6262
'Available',
6363
'On Hand',
6464
'Incoming',
65+
'View',
66+
'View settings',
67+
'Table Columns',
68+
'Purchasable',
69+
'SKU',
6570
]);
6671
}
6772
}

src/web/assets/inventory/dist/css/inventory.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/web/assets/inventory/dist/css/inventory.css.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/web/assets/inventory/dist/inventory.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/web/assets/inventory/dist/inventory.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/web/assets/inventory/src/css/inventory.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
// Inventory levels toolbar (contains the View button)
2+
.inventory-levels-toolbar {
3+
margin-bottom: var(--m);
4+
}
5+
16
.inventory-headers {
27
.ltr & {
38
justify-content: flex-end;

src/web/assets/inventory/src/js/InventoryLevelsManager.js

Lines changed: 250 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)