2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-03 04:00:57 +00:00

Merge branch 'master' into partial-shipment

# Conflicts:
#	InvenTree/build/templates/build/build_base.html
#	InvenTree/order/templates/order/order_base.html
#	InvenTree/order/templates/order/sales_order_base.html
#	InvenTree/order/templates/order/sales_order_detail.html
#	InvenTree/order/templates/order/so_navbar.html
This commit is contained in:
Oliver
2021-10-30 23:44:06 +11:00
251 changed files with 72023 additions and 5855 deletions

View File

@ -111,7 +111,7 @@ function inventreeDocReady() {
modal.modal({
backdrop: 'static',
keyboard: 'false',
keyboard: true,
});
modal.modal('show');
@ -169,12 +169,7 @@ function inventreeDocReady() {
html += '</span>';
if (user_settings.SEARCH_SHOW_STOCK_LEVELS) {
html += partStockLabel(
item.data,
{
label_class: 'label-right',
}
);
html += partStockLabel(item.data);
}
html += '</a>';
@ -195,6 +190,13 @@ function inventreeDocReady() {
$('.brand-icon').each(function(i, obj) {
loadBrandIcon($(this), $(this).attr('brand_name'));
});
// Callback for "admin view" button
$('#admin-button').click(function() {
var url = $(this).attr('url');
location.href = url;
});
}
function isFileTransfer(transfer) {

View File

@ -2,60 +2,26 @@
*/
/* exported
attachNavCallbacks,
enableNavbar,
initNavTree,
loadTree,
activatePanel,
addSidebarHeader,
addSidebarItem,
addSidebarLink,
enableSidebar,
onPanelLoad,
*/
/*
* Attach callbacks to navigation bar elements.
*
* Searches for elements with the class 'nav-toggle'.
* A callback is added to each element,
* to display the matching panel.
*
* The 'id' of the .nav-toggle element should be of the form "select-<x>",
* and point to a matching "panel-<x>"
*/
function attachNavCallbacks(options={}) {
$('.nav-toggle').click(function() {
var el = $(this);
// Find the matching "panel" element
var panelName = el.attr('id').replace('select-', '');
activatePanel(panelName, options);
});
var panelClass = options.name || 'unknown';
/* Look for a default panel to initialize
* First preference = URL parameter e.g. ?display=part-stock
* Second preference = localStorage
* Third preference = default
*/
var defaultPanel = $.urlParam('display') || localStorage.getItem(`inventree-selected-panel-${panelClass}`) || options.default;
if (defaultPanel) {
activatePanel(defaultPanel);
}
}
function activatePanel(panelName, options={}) {
var panelClass = options.name || 'unknown';
* Activate (display) the selected panel
*/
function activatePanel(label, panel_name, options={}) {
// First, cause any other panels to "fade out"
$('.panel-visible').hide();
$('.panel-visible').removeClass('panel-visible');
// Find the target panel
var panel = `#panel-${panelName}`;
var select = `#select-${panelName}`;
var panel = `#panel-${panel_name}`;
var select = `#select-${panel_name}`;
// Check that the selected panel (and select) exist
if ($(panel).length && $(select).length) {
@ -63,22 +29,22 @@ function activatePanel(panelName, options={}) {
} else {
// Either the select or the panel are not displayed!
// Iterate through the available 'select' elements until one matches
panelName = null;
panel_name = null;
$('.nav-toggle').each(function() {
var panel_name = $(this).attr('id').replace('select-', '');
$('.sidebar-selector').each(function() {
var name = $(this).attr('id').replace('select-', '');
if ($(`#panel-${panel_name}`).length && (panelName == null)) {
panelName = panel_name;
if ($(`#panel-${name}`).length && (panel_name == null)) {
panel_name = name;
}
panel = `#panel-${panelName}`;
select = `#select-${panelName}`;
panel = `#panel-${panel_name}`;
select = `#select-${panel_name}`;
});
}
// Save the selected panel
localStorage.setItem(`inventree-selected-panel-${panelClass}`, panelName);
localStorage.setItem(`inventree-selected-panel-${label}`, panel_name);
// Display the panel
$(panel).addClass('panel-visible');
@ -93,9 +59,9 @@ function activatePanel(panelName, options={}) {
$('.list-group-item').removeClass('active');
// Find the associated selector
var selectElement = `#select-${panelName}`;
var selector = `#select-${panel_name}`;
$(selectElement).parent('.list-group-item').addClass('active');
$(selector).addClass('active');
}
@ -117,252 +83,129 @@ function onPanelLoad(panel, callback) {
});
}
function loadTree(url, tree, options={}) {
/* Load the side-nav tree view
Args:
url: URL to request tree data
tree: html ref to treeview
options:
data: data object to pass to the AJAX request
selected: ID of currently selected item
name: name of the tree
*/
var data = {};
if (options.data) {
data = options.data;
}
var key = 'inventree-sidenav-items-';
if (options.name) {
key += options.name;
}
$.ajax({
url: url,
type: 'get',
dataType: 'json',
data: data,
success: function(response) {
if (response.tree) {
$(tree).treeview({
data: response.tree,
enableLinks: true,
showTags: true,
});
if (localStorage.getItem(key)) {
var saved_exp = localStorage.getItem(key).split(',');
// Automatically expand the desired notes
for (var q = 0; q < saved_exp.length; q++) {
$(tree).treeview('expandNode', parseInt(saved_exp[q]));
}
}
// Setup a callback whenever a node is toggled
$(tree).on('nodeExpanded nodeCollapsed', function(event, data) {
// Record the entire list of expanded items
var expanded = $(tree).treeview('getExpanded');
var exp = [];
for (var i = 0; i < expanded.length; i++) {
exp.push(expanded[i].nodeId);
}
// Save the expanded nodes
localStorage.setItem(key, exp);
});
}
},
error: function(xhr, ajaxOptions, thrownError) {
// TODO
}
});
}
/**
* Initialize navigation tree display
* Enable support for sidebar on this page
*/
function initNavTree(options) {
function enableSidebar(label, options={}) {
var resize = true;
// Enable callbacks for sidebar buttons
$('.sidebar-selector').click(function() {
var el = $(this);
if ('resize' in options) {
resize = options.resize;
}
// Find the matching panel element to display
var panel_name = el.attr('id').replace('select-', '');
var label = options.label || 'nav';
var stateLabel = `${label}-tree-state`;
var widthLabel = `${label}-tree-width`;
var treeId = options.treeId || '#sidenav-left';
var toggleId = options.toggleId;
// Initially hide the tree
$(treeId).animate({
width: '0px',
}, 0, function() {
if (resize) {
$(treeId).resizable({
minWidth: '0px',
maxWidth: '500px',
handles: 'e, se',
grid: [5, 5],
stop: function(event, ui) {
var width = Math.round(ui.element.width());
if (width < 75) {
$(treeId).animate({
width: '0px'
}, 50);
localStorage.setItem(stateLabel, 'closed');
} else {
localStorage.setItem(stateLabel, 'open');
localStorage.setItem(widthLabel, `${width}px`);
}
}
});
}
var state = localStorage.getItem(stateLabel);
var width = localStorage.getItem(widthLabel) || '300px';
if (state && state == 'open') {
$(treeId).animate({
width: width,
}, 50);
}
activatePanel(label, panel_name, options);
});
// Register callback for 'toggle' button
if (toggleId) {
$(toggleId).click(function() {
/* Look for a "default" panel to initialize for this page
*
* - First preference = URL parameter e.g. ?display=part-stock
* - Second preference = local storage
* - Third preference = default
*/
var state = localStorage.getItem(stateLabel) || 'closed';
var width = localStorage.getItem(widthLabel) || '300px';
var selected_panel = $.urlParam('display') || localStorage.getItem(`inventree-selected-panel-${label}`) || options.default;
if (state == 'open') {
$(treeId).animate({
width: '0px'
}, 50);
if (selected_panel) {
activatePanel(label, selected_panel);
} else {
// Find the "first" available panel (according to the sidebar)
var selector = $('.sidebar-selector').first();
localStorage.setItem(stateLabel, 'closed');
} else {
$(treeId).animate({
width: width,
}, 50);
if (selector.exists()) {
var panel_name = selector.attr('id').replace('select-', '');
activatePanel(label, panel_name);
}
}
localStorage.setItem(stateLabel, 'open');
}
if (options.hide_toggle) {
// Hide the toggle button if specified
$('#sidebar-toggle').remove();
} else {
$('#sidebar-toggle').click(function() {
// Add callback to "collapse" and "expand" the sidebar
// By default, the menu is "expanded"
var state = localStorage.getItem(`inventree-menu-state-${label}`) || 'expanded';
// We wish to "toggle" the state!
setSidebarState(label, state == 'expanded' ? 'collapsed' : 'expanded');
});
}
// Set the initial state (default = expanded)
var state = localStorage.getItem(`inventree-menu-state-${label}`) || 'expanded';
setSidebarState(label, state);
// Finally, show the sidebar
$('#sidebar').show();
}
/**
* Handle left-hand icon menubar display
/*
* Set the "toggle" state of the sidebar
*/
function enableNavbar(options) {
function setSidebarState(label, state) {
var resize = true;
if ('resize' in options) {
resize = options.resize;
}
var label = options.label || 'nav';
label = `navbar-${label}`;
var stateLabel = `${label}-state`;
var widthLabel = `${label}-width`;
var navId = options.navId || '#sidenav-right';
var toggleId = options.toggleId;
// Extract the saved width for this element
$(navId).animate({
'width': '45px',
'min-width': '45px',
'display': 'block',
}, 50, function() {
// Make the navbar resizable
if (resize) {
$(navId).resizable({
minWidth: options.minWidth || '100px',
maxWidth: options.maxWidth || '500px',
handles: 'e, se',
grid: [5, 5],
stop: function(event, ui) {
// Record the new width
var width = Math.round(ui.element.width());
// Reasonably narrow? Just close it!
if (width <= 75) {
$(navId).animate({
width: '45px'
}, 50);
localStorage.setItem(stateLabel, 'closed');
} else {
localStorage.setItem(widthLabel, `${width}px`);
localStorage.setItem(stateLabel, 'open');
}
}
});
}
var state = localStorage.getItem(stateLabel);
var width = localStorage.getItem(widthLabel) || '250px';
if (state && state == 'open') {
$(navId).animate({
width: width
}, 100);
}
});
// Register callback for 'toggle' button
if (toggleId) {
$(toggleId).click(function() {
var state = localStorage.getItem(stateLabel) || 'closed';
var width = localStorage.getItem(widthLabel) || '250px';
if (state == 'open') {
$(navId).animate({
width: '45px',
minWidth: '45px',
}, 50);
localStorage.setItem(stateLabel, 'closed');
} else {
$(navId).animate({
'width': width
}, 50);
localStorage.setItem(stateLabel, 'open');
}
if (state == 'collapsed') {
$('.sidebar-item-text').animate({
'opacity': 0.0,
'font-size': '0%',
}, 100, function() {
$('.sidebar-item-text').hide();
$('#sidebar-toggle-icon').removeClass('fa-chevron-left').addClass('fa-chevron-right');
});
} else {
$('.sidebar-item-text').show();
$('#sidebar-toggle-icon').removeClass('fa-chevron-right').addClass('fa-chevron-left');
$('.sidebar-item-text').animate({
'opacity': 1.0,
'font-size': '100%',
}, 100);
}
// Save the state of this sidebar
localStorage.setItem(`inventree-menu-state-${label}`, state);
}
/*
* Dynamically construct and insert a sidebar item into the sidebar at runtime.
* This mirrors the templated code in "sidebar_item.html"
*/
function addSidebarItem(options={}) {
var html = `
<a href='#' id='select-${options.label}' title='${options.text}' class='list-group-item sidebar-list-group-item border-end-0 d-inline-block text-truncate sidebar-selector' data-bs-parent='#sidebar'>
<i class='bi bi-bootstrap'></i>
${options.content_before || ''}
<span class='sidebar-item-icon fas ${options.icon}'></span>
<span class='sidebar-item-text' style='display: none;'>${options.text}</span>
${options.content_after || ''}
</a>
`;
$('#sidebar-list-group').append(html);
}
/*
* Dynamicall construct and insert a sidebar header into the sidebar at runtime
* This mirrors the templated code in "sidebar_header.html"
*/
function addSidebarHeader(options={}) {
var html = `
<span title='${options.text}' class="list-group-item sidebar-list-group-item border-end-0 d-inline-block text-truncate" data-bs-parent="#sidebar">
<h6>
<i class="bi bi-bootstrap"></i>
<span class='sidebar-item-text' style='display: none;'>${options.text}</span>
</h6>
</span>
`;
$('#sidebar-list-group').append(html);
}

View File

@ -27,7 +27,7 @@ function loadAttachmentTable(url, options) {
return '{% trans "No attachments found" %}';
},
sortable: true,
search: false,
search: true,
queryParams: options.filters || {},
onPostBody: function() {
// Add callback for 'edit' button
@ -58,12 +58,16 @@ function loadAttachmentTable(url, options) {
var fn = value.toLowerCase();
if (fn.endsWith('.pdf')) {
if (fn.endsWith('.csv')) {
icon = 'fa-file-csv';
} else if (fn.endsWith('.pdf')) {
icon = 'fa-file-pdf';
} else if (fn.endsWith('.xls') || fn.endsWith('.xlsx')) {
icon = 'fa-file-excel';
} else if (fn.endsWith('.doc') || fn.endsWith('.docx')) {
icon = 'fa-file-word';
} else if (fn.endsWith('.zip') || fn.endsWith('.7z')) {
icon = 'fa-file-archive';
} else {
var images = ['.png', '.jpg', '.bmp', '.gif', '.svg', '.tif'];

View File

@ -407,7 +407,7 @@ function loadBomTable(table, options) {
// Display an extra icon if this part is an assembly
if (sub_part.assembly) {
var text = `<span title='{% trans "Open subassembly" %}' class='fas fa-stream label-right'></span>`;
var text = `<span title='{% trans "Open subassembly" %}' class='fas fa-stream float-right'></span>`;
html += renderLink(text, `/part/${row.sub_part}/bom/`);
}
@ -470,7 +470,7 @@ function loadBomTable(table, options) {
var text = value;
if (value == null || value <= 0) {
text = `<span class='label label-warning'>{% trans "No Stock" %}</span>`;
text = `<span class='badge rounded-pill bg-danger'>{% trans "No Stock" %}</span>`;
}
return renderLink(text, url);
@ -612,7 +612,7 @@ function loadBomTable(table, options) {
var bValidate = makeIconButton('fa-check-circle icon-green', 'bom-validate-button', row.pk, '{% trans "Validate BOM Item" %}');
var bValid = `<span title='{% trans "This line has been validated" %}' class='fas fa-check-double icon-green'/>`;
var bValid = makeIconButton('fa-check-double icon-green', 'bom-valid-button', row.pk, '{% trans "This line has been validated" %}', {disabled: true});
var bSubs = makeIconButton('fa-exchange-alt icon-blue', 'bom-substitutes-button', row.pk, '{% trans "Edit substitute parts" %}');

View File

@ -34,8 +34,8 @@ function buildFormFields() {
reference: {
prefix: global_settings.BUILDORDER_REFERENCE_PREFIX,
},
title: {},
part: {},
title: {},
quantity: {},
parent: {
filters: {
@ -937,7 +937,10 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
var progress = makeProgressBar(
allocatedLines,
totalLines
totalLines,
{
max_width: '150px',
}
);
build_progress.html(progress);

View File

@ -325,15 +325,15 @@ function loadCompanyTable(table, url, options={}) {
var html = imageHoverIcon(row.image) + renderLink(value, row.url);
if (row.is_customer) {
html += `<span title='{% trans "Customer" %}' class='fas fa-user-tie label-right'></span>`;
html += `<span title='{% trans "Customer" %}' class='fas fa-user-tie float-right'></span>`;
}
if (row.is_manufacturer) {
html += `<span title='{% trans "Manufacturer" %}' class='fas fa-industry label-right'></span>`;
html += `<span title='{% trans "Manufacturer" %}' class='fas fa-industry float-right'></span>`;
}
if (row.is_supplier) {
html += `<span title='{% trans "Supplier" %}' class='fas fa-building label-right'></span>`;
html += `<span title='{% trans "Supplier" %}' class='fas fa-building float-right'></span>`;
}
return html;
@ -493,15 +493,15 @@ function loadManufacturerPartTable(table, url, options) {
var html = imageHoverIcon(row.part_detail.thumbnail) + renderLink(value, url);
if (row.part_detail.is_template) {
html += `<span class='fas fa-clone label-right' title='{% trans "Template part" %}'></span>`;
html += `<span class='fas fa-clone float-right' title='{% trans "Template part" %}'></span>`;
}
if (row.part_detail.assembly) {
html += `<span class='fas fa-tools label-right' title='{% trans "Assembled part" %}'></span>`;
html += `<span class='fas fa-tools float-right' title='{% trans "Assembled part" %}'></span>`;
}
if (!row.part_detail.active) {
html += `<span class='label label-warning label-right'>{% trans "Inactive" %}</span>`;
html += `<span class='badge badge-right rounded-pill bg-warning'>{% trans "Inactive" %}</span>`;
}
return html;
@ -750,15 +750,15 @@ function loadSupplierPartTable(table, url, options) {
var html = imageHoverIcon(row.part_detail.thumbnail) + renderLink(value, url);
if (row.part_detail.is_template) {
html += `<span class='fas fa-clone label-right' title='{% trans "Template part" %}'></span>`;
html += `<span class='fas fa-clone float-right' title='{% trans "Template part" %}'></span>`;
}
if (row.part_detail.assembly) {
html += `<span class='fas fa-tools label-right' title='{% trans "Assembled part" %}'></span>`;
html += `<span class='fas fa-tools float-right' title='{% trans "Assembled part" %}'></span>`;
}
if (!row.part_detail.active) {
html += `<span class='label label-warning label-right'>{% trans "Inactive" %}</span>`;
html += `<span class='badge badge-right rounded-pill bg-warning'>{% trans "Inactive" %}</span>`;
}
return html;

View File

@ -281,7 +281,7 @@ function setupFilterList(tableKey, table, target) {
// One blank slate, please
element.empty();
element.append(`<button id='reload-${tableKey}' title='{% trans "Reload data" %}' class='btn btn-default filter-tag'><span class='fas fa-redo-alt'></span></button>`);
element.append(`<button id='reload-${tableKey}' title='{% trans "Reload data" %}' class='btn btn-outline-secondary filter-tag'><span class='fas fa-redo-alt'></span></button>`);
// Callback for reloading the table
element.find(`#reload-${tableKey}`).click(function() {
@ -294,10 +294,10 @@ function setupFilterList(tableKey, table, target) {
}
// If there are filters currently "in use", add them in!
element.append(`<button id='${add}' title='{% trans "Add new filter" %}' class='btn btn-default filter-tag'><span class='fas fa-filter'></span></button>`);
element.append(`<button id='${add}' title='{% trans "Add new filter" %}' class='btn btn-outline-secondary filter-tag'><span class='fas fa-filter'></span></button>`);
if (Object.keys(filters).length > 0) {
element.append(`<button id='${clear}' title='{% trans "Clear all filters" %}' class='btn btn-default filter-tag'><span class='fas fa-backspace icon-red'></span></button>`);
element.append(`<button id='${clear}' title='{% trans "Clear all filters" %}' class='btn btn-outline-secondary filter-tag'><span class='fas fa-backspace icon-red'></span></button>`);
}
for (var key in filters) {
@ -320,7 +320,7 @@ function setupFilterList(tableKey, table, target) {
html += generateAvailableFilterList(tableKey);
html += generateFilterInput(tableKey);
html += `<button title='{% trans "Create filter" %}' class='btn btn-default filter-tag' id='${make}'><span class='fas fa-plus'></span></button>`;
html += `<button title='{% trans "Create filter" %}' class='btn btn-outline-secondary filter-tag' id='${make}'><span class='fas fa-plus'></span></button>`;
element.append(html);

View File

@ -2,7 +2,6 @@
{% load inventree_extras %}
/* globals
attachToggle,
createNewModal,
inventreeFormDataUpload,
inventreeGet,
@ -49,6 +48,9 @@
*
*/
// Set global default theme for select2
$.fn.select2.defaults.set('theme', 'bootstrap-5');
/*
* Return true if the OPTIONS specify that the user
* can perform a GET method at the endpoint.
@ -519,11 +521,6 @@ function constructFormBody(fields, options) {
// Attach clear callbacks (if required)
addClearCallbacks(fields, options);
attachToggle(modal);
$(modal + ' .select2-container').addClass('select-full-width');
$(modal + ' .select2-container').css('width', '100%');
modalShowSubmitButton(modal, true);
$(modal).on('click', '#modal-form-submit', function() {
@ -563,13 +560,14 @@ function insertConfirmButton(options) {
var message = options.confirmMessage || '{% trans "Confirm" %}';
var confirm = `
<span style='float: left;'>
${message}
<input id='modal-confirm' name='confirm' type='checkbox'>
</span>`;
var html = `
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="modal-confirm">
<label class="form-check-label" for="modal-confirm">${message}</label>
</div>
`;
$(options.modal).find('#modal-footer-buttons').append(confirm);
$(options.modal).find('#modal-footer-buttons').append(html);
// Disable the 'submit' button
$(options.modal).find('#modal-form-submit').prop('disabled', true);
@ -930,7 +928,7 @@ function clearFormErrors(options) {
$(options.modal).find('.form-error-message').remove();
// Remove the "has error" class
$(options.modal).find('.has-error').removeClass('has-error');
$(options.modal).find('.form-field-error').removeClass('form-field-error');
// Hide the 'non field errors'
$(options.modal).find('#non-field-errors').html('');
@ -1103,8 +1101,8 @@ function handleFormErrors(errors, fields, options) {
*/
function addFieldErrorMessage(field_name, error_text, error_idx, options) {
// Add the 'has-error' class
$(options.modal).find(`#div_id_${field_name}`).addClass('has-error');
// Add the 'form-field-error' class
$(options.modal).find(`#div_id_${field_name}`).addClass('form-field-error');
var field_dom = $(options.modal).find(`#errors-${field_name}`);
@ -1299,7 +1297,7 @@ function addSecondaryModal(field, fields, options) {
var html = `
<span style='float: right;'>
<div type='button' class='btn btn-primary btn-secondary' title='${secondary.title || secondary.label}' id='btn-new-${name}'>
<div type='button' class='btn btn-primary btn-secondary btn-form-secondary' title='${secondary.title || secondary.label}' id='btn-new-${name}'>
${secondary.label || secondary.title}
</div>
</span>`;
@ -1585,7 +1583,6 @@ function initializeChoiceField(field, fields, options) {
select.select2({
dropdownAutoWidth: false,
dropdownParent: $(options.modal),
width: '100%',
});
}
@ -1734,7 +1731,7 @@ function constructField(name, parameters, options) {
<div class='panel-heading form-panel-heading' id='form-panel-heading-${group}'>`;
if (group_options.collapsible) {
html += `
<div data-toggle='collapse' data-target='#form-panel-content-${group}'>
<div data-bs-toggle='collapse' data-bs-target='#form-panel-content-${group}'>
<a href='#'><span id='group-icon-${group}' class='fas fa-angle-up'></span>
`;
} else {
@ -1760,7 +1757,7 @@ function constructField(name, parameters, options) {
var form_classes = 'form-group';
if (parameters.errors) {
form_classes += ' has-error';
form_classes += ' form-field-error';
}
// Optional content to render before the field
@ -1802,7 +1799,7 @@ function constructField(name, parameters, options) {
html += `<div class='input-group'>`;
if (parameters.prefix) {
html += `<span class='input-group-addon'>${parameters.prefix}</span>`;
html += `<span class='input-group-text'>${parameters.prefix}</span>`;
}
}
@ -1812,7 +1809,7 @@ function constructField(name, parameters, options) {
if (!parameters.required) {
html += `
<span class='input-group-addon form-clear' id='clear_${name}' title='{% trans "Clear input" %}'>
<span class='input-group-text form-clear' id='clear_${name}' title='{% trans "Clear input" %}'>
<span class='icon-red fas fa-backspace'></span>
</span>`;
}
@ -1821,7 +1818,11 @@ function constructField(name, parameters, options) {
}
if (parameters.help_text && !options.hideLabels) {
html += constructHelpText(name, parameters, options);
// Boolean values are handled differently!
if (parameters.type != 'boolean') {
html += constructHelpText(name, parameters, options);
}
}
// Div for error messages
@ -1996,7 +1997,6 @@ function constructInputOptions(name, classes, type, parameters) {
switch (parameters.type) {
case 'boolean':
opts.push(`style='display: inline-block; width: 20px; margin-right: 20px;'`);
break;
case 'integer':
case 'float':
@ -2009,6 +2009,15 @@ function constructInputOptions(name, classes, type, parameters) {
if (parameters.multiline) {
return `<textarea ${opts.join(' ')}></textarea>`;
} else if (parameters.type == 'boolean') {
return `
<div class='form-check form-switch'>
<input ${opts.join(' ')}>
<label class='form-check-label' for=''>
<em><small>${parameters.help_text}</small></em>
</label>
</div>
`;
} else {
return `<input ${opts.join(' ')}>`;
}
@ -2032,7 +2041,7 @@ function constructCheckboxInput(name, parameters) {
return constructInputOptions(
name,
'checkboxinput',
'form-check-input',
'checkbox',
parameters
);
@ -2117,7 +2126,7 @@ function constructChoiceInput(name, parameters) {
*/
function constructRelatedFieldInput(name) {
var html = `<select id='id_${name}' class='select form-control' name='${name}' style='width: 100%;'></select>`;
var html = `<select id='id_${name}' class='select form-control' name='${name}'></select>`;
// Don't load any options - they will be filled via an AJAX request
@ -2191,13 +2200,7 @@ function constructRawInput(name, parameters) {
*/
function constructHelpText(name, parameters) {
var style = '';
if (parameters.type == 'boolean') {
style = `style='display: inline-block; margin-left: 25px' `;
}
var html = `<div id='hint_id_${name}' ${style}class='help-block'><i>${parameters.help_text}</i></div>`;
var html = `<div id='hint_id_${name}' class='help-block'><i>${parameters.help_text}</i></div>`;
return html;
}

View File

@ -16,9 +16,9 @@
function yesNoLabel(value) {
if (value) {
return `<span class='label label-green'>{% trans "YES" %}</span>`;
return `<span class='badge rounded-pill bg-success'>{% trans "YES" %}</span>`;
} else {
return `<span class='label label-yellow'>{% trans "NO" %}</span>`;
return `<span class='badge rounded-pill bg-warning'>{% trans "NO" %}</span>`;
}
}
@ -92,7 +92,7 @@ function select2Thumbnail(image) {
*/
function makeIconBadge(icon, title) {
var html = `<span class='fas ${icon} label-right' title='${title}'></span>`;
var html = `<span class='icon-badge fas ${icon} float-right' title='${title}'></span>`;
return html;
}
@ -103,7 +103,7 @@ function makeIconBadge(icon, title) {
*/
function makeIconButton(icon, cls, pk, title, options={}) {
var classes = `btn btn-default btn-glyph ${cls}`;
var classes = `btn btn-outline-secondary ${cls}`;
var id = `${cls}-${pk}`;
@ -182,8 +182,14 @@ function makeProgressBar(value, maximum, opts={}) {
var id = options.id || 'progress-bar';
var style = '';
if (opts.max_width) {
style += `max-width: ${options.max_width}; `;
}
return `
<div id='${id}' class='progress'>
<div id='${id}' class='progress' style='${style}'>
<div class='progress-bar ${extraclass}' role='progressbar' aria-valuenow='${percent}' aria-valuemin='0' aria-valuemax='100' style='width:${percent}%'></div>
<div class='progress-value'>${text}</div>
</div>

View File

@ -47,14 +47,12 @@ function createNewModal(options={}) {
<div class='modal-dialog'>
<div class='modal-content'>
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label='{% trans "Close" %}'>
<span aria-hidden="true">&times;</span>
</button>
<h3 id='modal-title'>
<h4 id='modal-title' class='modal-title'>
<!-- Form title to be injected here -->
</h3>
</h4>
<button type='button' class='btn-close' data-bs-dismiss='modal' aria-label='{% trans "Close" %}'></button>
</div>
<div class='modal-form-content-wrapper'>
<div class='modal-body modal-form-content-wrapper'>
<div id='non-field-errors'>
<!-- Form error messages go here -->
</div>
@ -73,7 +71,8 @@ function createNewModal(options={}) {
<div id='modal-footer-buttons'>
<!-- Extra buttons can be inserted here -->
</div>
<button type='button' class='btn btn-default' id='modal-form-close' data-dismiss='modal'>{% trans "Cancel" %}</button>
<span class='flex-item' style='flex-grow: 1;'></span>
<button type='button' class='btn btn-secondary' id='modal-form-close' data-bs-dismiss='modal'>{% trans "Cancel" %}</button>
<button type='button' class='btn btn-primary' id='modal-form-submit'>{% trans "Submit" %}</button>
</div>
</div>
@ -355,22 +354,6 @@ function partialMatcher(params, data) {
}
function attachToggle(modal) {
/* Attach 'bootstrap-toggle' functionality to any checkbox in the modal.
* This is simple for visual improvement,
* and also larger toggle style buttons are easier to press!
*/
$(modal).find(`input[type='checkbox']`).each(function() {
$(this).bootstrapToggle({
size: 'small',
onstyle: 'success',
offstyle: 'warning',
});
});
}
function attachSelect(modal) {
/* Attach 'select2' functionality to any drop-down list in the modal.
* Provides search filtering for dropdown items
@ -550,14 +533,14 @@ function renderErrorMessage(xhr) {
html += `
<div class='panel-group'>
<div class='panel panel-default'>
<div class='panel'>
<div class='panel panel-heading'>
<div class='panel-title'>
<a data-toggle='collapse' href="#collapse-error-info">{% trans "Show Error Information" %}</a>
<a data-bs-toggle='collapse' href="#collapse-error-info">{% trans "Show Error Information" %}</a>
</div>
</div>
<div class='panel-collapse collapse' id='collapse-error-info'>
<div class='panel-body'>`;
<div class='panel-content'>`;
html += xhr.responseText;
@ -698,7 +681,6 @@ function injectModalForm(modal, form_html) {
*/
$(modal).find('.modal-form-content').html(form_html);
attachSelect(modal);
attachToggle(modal);
}
@ -806,7 +788,7 @@ function insertActionButton(modal, options) {
if (already_present == false) {
var html = `
<span style='float: right;'>
<button name='${options.name}' type='submit' class='btn btn-default modal-form-button' value='${options.name}'>
<button name='${options.name}' type='submit' class='btn btn-outline-secondary modal-form-button' value='${options.name}'>
${options.title}
</button>
</span>`;

View File

@ -94,7 +94,7 @@ function renderStockItem(name, data, parameters, options) {
if (data.serial && data.quantity == 1) {
stock_detail = `{% trans "Serial Number" %}: ${data.serial}`;
} else if (data.quantity == 0) {
stock_detail = `<span class='label-form label-red'>{% trans "No Stock"% }</span>`;
stock_detail = `<span class='badge rounded-pill bg-danger'>{% trans "No Stock"% }</span>`;
} else {
stock_detail = `{% trans "Quantity" %}: ${data.quantity}`;
}
@ -172,7 +172,7 @@ function renderPart(name, data, parameters, options) {
}
if (!data.active) {
extra += `<span class='label-form label-red'>{% trans "Inactive" %}</span>`;
extra += `<span class='badge badge-right rounded-pill bg-danger'>{% trans "Inactive" %}</span>`;
}
html += `

View File

@ -118,6 +118,7 @@ function createPurchaseOrder(options={}) {
prefix: global_settings.PURCHASEORDER_REFERENCE_PREFIX,
},
supplier: {
icon: 'fa-building',
value: options.supplier,
secondary: {
title: '{% trans "Add Supplier" %}',

View File

@ -410,14 +410,12 @@ function toggleStar(options) {
}
function partStockLabel(part, options={}) {
var label_class = options.label_class || 'label-form';
function partStockLabel(part) {
if (part.in_stock) {
return `<span class='label ${label_class} label-green'>{% trans "Stock" %}: ${part.in_stock}</span>`;
return `<span class='badge rounded-pill bg-success'>{% trans "Stock" %}: ${part.in_stock}</span>`;
} else {
return `<span class='label ${label_class} label-red'>{% trans "No Stock" %}</span>`;
return `<span class='badge rounded-pill bg-danger'>{% trans "No Stock" %}</span>`;
}
}
@ -453,7 +451,7 @@ function makePartIcons(part) {
}
if (!part.active) {
html += `<span class='label label-warning label-right'>{% trans "Inactive" %}</span>`;
html += `<span class='badge badge-right rounded-pill bg-warning'>{% trans "Inactive" %}</span>`;
}
return html;
@ -530,7 +528,7 @@ function loadPartVariantTable(table, partId, options={}) {
}
if (!row.active) {
html += `<span class='label label-warning label-right'>{% trans "Inactive" %}</span>`;
html += `<span class='badge badge-right rounded-pill bg-warning'>{% trans "Inactive" %}</span>`;
}
return html;
@ -769,14 +767,10 @@ function partGridTile(part) {
// Rows for table view
var rows = '';
if (part.IPN) {
rows += `<tr><td><b>{% trans "IPN" %}</b></td><td>${part.IPN}</td></tr>`;
}
var stock = `${part.in_stock}`;
if (!part.in_stock) {
stock = `<span class='label label-red'>{% trans "No Stock" %}</label>`;
stock = `<span class='badge rounded-pill bg-danger'>{% trans "No Stock" %}</span>`;
}
rows += `<tr><td><b>{% trans "Stock" %}</b></td><td>${stock}</td></tr>`;
@ -791,8 +785,8 @@ function partGridTile(part) {
var html = `
<div class='product-card card'>
<div class='panel panel-default panel-inventree product-card-panel'>
<div class='card product-card borderless'>
<div class='panel product-card-panel'>
<div class='panel-heading'>
<a href='/part/${part.pk}/'>
<b>${part.full_name}</b>
@ -949,20 +943,20 @@ function loadPartTable(table, url, options={}) {
// Is stock "low" (below the 'minimum_stock' quantity)?
if (row.minimum_stock && row.minimum_stock > value) {
value += `<span class='label label-right label-warning'>{% trans "Low stock" %}</span>`;
value += `<span class='badge badge-right rounded-pill bg-warning'>{% trans "Low stock" %}</span>`;
}
} else if (row.on_order) {
// There is no stock available, but stock is on order
value = `0<span class='label label-right label-primary'>{% trans "On Order" %}: ${row.on_order}</span>`;
value = `0<span class='badge badge-right rounded-pill bg-info'>{% trans "On Order" %}: ${row.on_order}</span>`;
link = '?display=purchase-orders';
} else if (row.building) {
// There is no stock available, but stock is being built
value = `0<span class='label label-right label-info'>{% trans "Building" %}: ${row.building}</span>`;
value = `0<span class='badge badge-right rounded-pill bg-info'>{% trans "Building" %}: ${row.building}</span>`;
link = '?display=build-orders';
} else {
// There is no stock available
value = `0<span class='label label-right label-danger'>{% trans "No Stock" %}</span>`;
value = `0<span class='badge badge-right rounded-pill bg-danger'>{% trans "No Stock" %}</span>`;
}
return renderLink(value, `/part/${row.pk}/${link}`);
@ -989,6 +983,8 @@ function loadPartTable(table, url, options={}) {
}
});
var grid_view = inventreeLoad('part-grid-view') == 1;
$(table).inventreeTable({
url: url,
method: 'get',
@ -1003,8 +999,52 @@ function loadPartTable(table, url, options={}) {
},
columns: columns,
showColumns: true,
showCustomView: false,
showCustomView: grid_view,
showCustomViewButton: false,
onPostBody: function() {
grid_view = inventreeLoad('part-grid-view') == 1;
if (grid_view) {
$('#view-part-list').removeClass('btn-secondary').addClass('btn-outline-secondary');
$('#view-part-grid').removeClass('btn-outline-secondary').addClass('btn-secondary');
} else {
$('#view-part-grid').removeClass('btn-secondary').addClass('btn-outline-secondary');
$('#view-part-list').removeClass('btn-outline-secondary').addClass('btn-secondary');
}
},
buttons: [
{
icon: 'fas fa-bars',
attributes: {
title: '{% trans "Display as list" %}',
id: 'view-part-list',
},
event: () => {
inventreeSave('part-grid-view', 0);
$(table).bootstrapTable(
'refreshOptions',
{
showCustomView: false,
}
);
}
},
{
icon: 'fas fa-th',
attributes: {
title: '{% trans "Display as grid" %}',
id: 'view-part-grid',
},
event: () => {
inventreeSave('part-grid-view', 1);
$(table).bootstrapTable(
'refreshOptions',
{
showCustomView: true,
}
);
}
}
],
customView: function(data) {
var html = '';
@ -1117,14 +1157,88 @@ function loadPartCategoryTable(table, options) {
setupFilterList(filterKey, table, filterListElement);
var tree_view = inventreeLoad('category-tree-view') == 1;
table.inventreeTable({
treeEnable: tree_view,
rootParentId: options.params.parent,
uniqueId: 'pk',
idField: 'pk',
treeShowField: 'name',
parentIdField: 'parent',
method: 'get',
url: options.url || '{% url "api-part-category-list" %}',
queryParams: filters,
sidePagination: 'server',
disablePagination: tree_view,
sidePagination: tree_view ? 'client' : 'server',
serverSort: !tree_view,
search: !tree_view,
name: 'category',
original: original,
showColumns: true,
buttons: [
{
icon: 'fas fa-bars',
attributes: {
title: '{% trans "Display as list" %}',
id: 'view-category-list',
},
event: () => {
inventreeSave('category-tree-view', 0);
table.bootstrapTable(
'refreshOptions',
{
treeEnable: false,
serverSort: true,
search: true,
pagination: true,
}
);
}
},
{
icon: 'fas fa-sitemap',
attributes: {
title: '{% trans "Display as tree" %}',
id: 'view-category-tree',
},
event: () => {
inventreeSave('category-tree-view', 1);
table.bootstrapTable(
'refreshOptions',
{
treeEnable: true,
serverSort: false,
search: false,
pagination: false,
}
);
}
}
],
onPostBody: function() {
tree_view = inventreeLoad('category-tree-view') == 1;
if (tree_view) {
$('#view-category-list').removeClass('btn-secondary').addClass('btn-outline-secondary');
$('#view-category-tree').removeClass('btn-outline-secondary').addClass('btn-secondary');
table.treegrid({
treeColumn: 0,
onChange: function() {
table.bootstrapTable('resetView');
},
onExpand: function() {
}
});
} else {
$('#view-category-tree').removeClass('btn-secondary').addClass('btn-outline-secondary');
$('#view-category-list').removeClass('btn-outline-secondary').addClass('btn-secondary');
}
},
columns: [
{
checkbox: true,
@ -1154,7 +1268,8 @@ function loadPartCategoryTable(table, options) {
{
field: 'pathstring',
title: '{% trans "Path" %}',
switchable: true,
switchable: !tree_view,
visible: !tree_view,
sortable: false,
},
{

View File

@ -450,17 +450,17 @@ function removeStockRow(e) {
}
function passFailBadge(result, align='float-right') {
function passFailBadge(result) {
if (result) {
return `<span class='label label-green ${align}'>{% trans "PASS" %}</span>`;
return `<span class='badge badge-right rounded-pill bg-success'>{% trans "PASS" %}</span>`;
} else {
return `<span class='label label-red ${align}'>{% trans "FAIL" %}</span>`;
return `<span class='badge badge-right rounded-pill bg-danger'>{% trans "FAIL" %}</span>`;
}
}
function noResultBadge(align='float-right') {
return `<span class='label label-blue ${align}'>{% trans "NO RESULT" %}</span>`;
function noResultBadge() {
return `<span class='badge badge-right rounded-pill bg-info'>{% trans "NO RESULT" %}</span>`;
}
function formatDate(row) {
@ -468,11 +468,7 @@ function formatDate(row) {
var html = row.date;
if (row.user_detail) {
html += `<span class='badge'>${row.user_detail.username}</span>`;
}
if (row.attachment) {
html += `<a href='${row.attachment}'><span class='fas fa-file-alt label-right'></span></a>`;
html += `<span class='badge badge-right rounded-pill bg-secondary'>${row.user_detail.username}</span>`;
}
return html;
@ -553,6 +549,15 @@ function loadStockTestResultsTable(table, options) {
{
field: 'value',
title: '{% trans "Value" %}',
formatter: function(value, row) {
var html = value;
if (row.attachment) {
html += `<a href='${row.attachment}'><span class='fas fa-file-alt float-right'></span></a>`;
}
return html;
}
},
{
field: 'notes',
@ -878,16 +883,12 @@ function loadStockTable(table, options) {
}
if (row.quantity <= 0) {
html += `<span class='label label-right label-danger'>{% trans "Depleted" %}</span>`;
html += `<span class='badge rounded-pill bg-danger'>{% trans "Depleted" %}</span>`;
}
return html;
}
};
if (!options.params.ordering) {
col['sortable'] = true;
}
columns.push(col);
@ -1442,7 +1443,19 @@ function loadStockLocationTable(table, options) {
filters[key] = params[key];
}
var tree_view = inventreeLoad('location-tree-view') == 1;
table.inventreeTable({
treeEnable: tree_view,
rootParentId: options.params.parent,
uniqueId: 'pk',
idField: 'pk',
treeShowField: 'name',
parentIdField: 'parent',
disablePagination: tree_view,
sidePagination: tree_view ? 'client' : 'server',
serverSort: !tree_view,
search: !tree_view,
method: 'get',
url: options.url || '{% url "api-location-list" %}',
queryParams: filters,
@ -1450,6 +1463,69 @@ function loadStockLocationTable(table, options) {
name: 'location',
original: original,
showColumns: true,
onPostBody: function() {
tree_view = inventreeLoad('location-tree-view') == 1;
if (tree_view) {
$('#view-location-list').removeClass('btn-secondary').addClass('btn-outline-secondary');
$('#view-location-tree').removeClass('btn-outline-secondary').addClass('btn-secondary');
table.treegrid({
treeColumn: 1,
onChange: function() {
table.bootstrapTable('resetView');
},
onExpand: function() {
}
});
} else {
$('#view-location-tree').removeClass('btn-secondary').addClass('btn-outline-secondary');
$('#view-location-list').removeClass('btn-outline-secondary').addClass('btn-secondary');
}
},
buttons: [
{
icon: 'fas fa-bars',
attributes: {
title: '{% trans "Display as list" %}',
id: 'view-location-list',
},
event: () => {
inventreeSave('location-tree-view', 0);
table.bootstrapTable(
'refreshOptions',
{
treeEnable: false,
serverSort: true,
search: true,
pagination: true,
}
);
}
},
{
icon: 'fas fa-sitemap',
attributes: {
title: '{% trans "Display as tree" %}',
id: 'view-location-tree',
},
event: () => {
inventreeSave('location-tree-view', 1);
table.bootstrapTable(
'refreshOptions',
{
treeEnable: true,
serverSort: false,
search: false,
pagination: false,
}
);
}
}
],
columns: [
{
checkbox: true,
@ -1684,8 +1760,8 @@ function loadStockTrackingTable(table, options) {
formatter: function(value, row, index, field) {
// Manually created entries can be edited or deleted
if (false && !row.system) {
var bEdit = "<button title='{% trans 'Edit tracking entry' %}' class='btn btn-entry-edit btn-default btn-glyph' type='button' url='/stock/track/" + row.pk + "/edit/'><span class='fas fa-edit'/></button>";
var bDel = "<button title='{% trans 'Delete tracking entry' %}' class='btn btn-entry-delete btn-default btn-glyph' type='button' url='/stock/track/" + row.pk + "/delete/'><span class='fas fa-trash-alt icon-red'/></button>";
var bEdit = "<button title='{% trans 'Edit tracking entry' %}' class='btn btn-entry-edit btn-outline-secondary' type='button' url='/stock/track/" + row.pk + "/edit/'><span class='fas fa-edit'/></button>";
var bDel = "<button title='{% trans 'Delete tracking entry' %}' class='btn btn-entry-delete btn-outline-secondary' type='button' url='/stock/track/" + row.pk + "/delete/'><span class='fas fa-trash-alt icon-red'/></button>";
return "<div class='btn-group' role='group'>" + bEdit + bDel + "</div>";
} else {

View File

@ -214,6 +214,8 @@ $.fn.inventreeTable = function(options) {
options.pageList = [25, 50, 100, 250, 'all'];
options.totalField = 'count';
options.dataField = 'results';
} else {
options.pagination = false;
}
// Extract query params
@ -397,3 +399,8 @@ function customGroupSorter(sortName, sortOrder, sortData) {
$.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales['en-US-custom']);
})(jQuery);
$.extend($.fn.treegrid.defaults, {
expanderExpandedClass: 'treegrid-expander-expanded',
expanderCollapsedClass: 'treegrid-expander-collapsed'
});