diff --git a/.eslintrc.json b/.eslintrc.json index 0d9213bb..56f4296d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -27,7 +27,8 @@ "strict": "off", "class-methods-use-this": "off", "eqeqeq": "warn", - "radix": "warn" + "radix": "warn", + "max-classes-per-file": ["error", 2] }, "globals": { "API_URLS": true, diff --git a/README.md b/README.md index 37c85957..5c7019cf 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -#Devicehub +# Devicehub Devicehub is a distributed IT Asset Management System focused in reusing devices, created under the project [eReuse.org](https://www.ereuse.org) diff --git a/docs/lots.rst b/docs/lots.rst index e3cc83a3..03943e21 100644 --- a/docs/lots.rst +++ b/docs/lots.rst @@ -9,6 +9,12 @@ dags-with-materialized-paths-using-postgres-ltree/>`_ you have a low-level technical implementation of how lots and their relationships are mapped. +Getting lots +************ + +You can get lots list by ``GET /lots/`` +There are one optional filter ``type``, only works with this 3 values ``temporary``, ``incoming`` and ``outgoing`` + Create lots *********** You create a lot by ``POST /lots/`` a `JSON Lot object /devices/?id=&id=``; idem for removing devices. - Sharing lots ************ Sharing a lot means giving certain permissions to users, like reading diff --git a/ereuse_devicehub/client.py b/ereuse_devicehub/client.py index aaafb9f3..92aa67d2 100644 --- a/ereuse_devicehub/client.py +++ b/ereuse_devicehub/client.py @@ -252,7 +252,7 @@ class UserClientFlask: ) self.headers = headers body = next(body).decode("utf-8") - assert "Unassgined" in body + assert "Unassigned" in body def get( self, diff --git a/ereuse_devicehub/inventory/forms.py b/ereuse_devicehub/inventory/forms.py index f02c2d01..ead43688 100644 --- a/ereuse_devicehub/inventory/forms.py +++ b/ereuse_devicehub/inventory/forms.py @@ -58,16 +58,30 @@ from ereuse_devicehub.resources.tradedocument.models import TradeDocument from ereuse_devicehub.resources.user.models import User DEVICES = { - "All": ["All"], + "All": ["All Devices", "All Components"], "Computer": [ + "All Computers", "Desktop", "Laptop", "Server", ], - "Monitor": ["ComputerMonitor", "Monitor", "TelevisionSet", "Projector"], - "Mobile, tablet & smartphone": ["Mobile", "Tablet", "Smartphone", "Cellphone"], - "DataStorage": ["HardDrive", "SolidStateDrive"], + "Monitor": [ + "All Monitors", + "ComputerMonitor", + "Monitor", + "TelevisionSet", + "Projector", + ], + "Mobile, tablet & smartphone": [ + "All Mobile", + "Mobile", + "Tablet", + "Smartphone", + "Cellphone", + ], + "DataStorage": ["All DataStorage", "HardDrive", "SolidStateDrive"], "Accessories & Peripherals": [ + "All Peripherals", "GraphicCard", "Motherboard", "NetworkAdapter", @@ -81,26 +95,101 @@ DEVICES = { ], } +COMPUTERS = ['Desktop', 'Laptop', 'Server', 'Computer'] + +COMPONENTS = [ + 'GraphicCard', + 'DataStorage', + 'HardDrive', + 'DataStorage', + 'SolidStateDrive', + 'Motherboard', + 'NetworkAdapter', + 'Processor', + 'RamModule', + 'SoundCard', + 'Display', + 'Battery', + 'Camera', +] + +MONITORS = ["ComputerMonitor", "Monitor", "TelevisionSet", "Projector"] +MOBILE = ["Mobile", "Tablet", "Smartphone", "Cellphone"] +DATASTORAGE = ["HardDrive", "SolidStateDrive"] +PERIPHERALS = [ + "GraphicCard", + "Motherboard", + "NetworkAdapter", + "Processor", + "RamModule", + "SoundCard", + "Battery", + "Keyboard", + "Mouse", + "MemoryCardReader", +] + class FilterForm(FlaskForm): filter = SelectField( - '', choices=DEVICES, default="Computer", render_kw={'class': "form-select"} + '', choices=DEVICES, default="All Computers", render_kw={'class': "form-select"} ) - def __init__(self, *args, **kwargs): + def __init__(self, lots, lot_id, *args, **kwargs): super().__init__(*args, **kwargs) + self.lots = lots + self.lot_id = lot_id + self._get_types() + + def _get_types(self): types_of_devices = [item for sublist in DEVICES.values() for item in sublist] dev = request.args.get('filter') - self.device = dev if dev in types_of_devices else None - if self.device: - self.filter.data = self.device + self.device_type = dev if dev in types_of_devices else None + if self.device_type: + self.filter.data = self.device_type def search(self): - if self.device: - return [self.device] + # Filter from lots + if self.lot_id: + self.lot = self.lots.filter(Lot.id == self.lot_id).one() + device_ids = (d.id for d in self.lot.devices) + self.devices = Device.query.filter(Device.id.in_(device_ids)) + else: + self.devices = Device.query.filter(Device.owner_id == g.user.id).filter_by( + lots=None + ) - return ['Desktop', 'Laptop', 'Server', 'Computer'] + filter_type = None + if self.device_type: + filter_type = [self.device_type] + else: + # Case without Filter + filter_type = COMPUTERS + + # Generic Filters + if "All Devices" == self.device_type: + filter_type = None + + if "All Components" == self.device_type: + filter_type = COMPONENTS + + elif "All Monitors" == self.device_type: + filter_type = MONITORS + + elif "All Mobile" == self.device_type: + filter_type = MOBILE + + elif "All DataStorage" == self.device_type: + filter_type = DATASTORAGE + + elif "All Peripherals" == self.device_type: + filter_type = PERIPHERALS + + if filter_type: + self.devices = self.devices.filter(Device.type.in_(filter_type)) + + return self.devices.order_by(Device.updated.desc()) class LotForm(FlaskForm): diff --git a/ereuse_devicehub/inventory/views.py b/ereuse_devicehub/inventory/views.py index 72512e3d..7731d2ad 100644 --- a/ereuse_devicehub/inventory/views.py +++ b/ereuse_devicehub/inventory/views.py @@ -43,8 +43,8 @@ class DeviceListMix(GenericMixView): def get_context(self, lot_id): super().get_context() lots = self.context['lots'] - form_filter = FilterForm() - filter_types = form_filter.search() + form_filter = FilterForm(lots, lot_id) + devices = form_filter.search() lot = None tags = ( Tag.query.filter(Tag.owner_id == current_user.id) @@ -54,10 +54,6 @@ class DeviceListMix(GenericMixView): if lot_id: lot = lots.filter(Lot.id == lot_id).one() - devices = lot.devices - if "All" not in filter_types: - devices = [dev for dev in lot.devices if dev.type in filter_types] - devices = sorted(devices, key=lambda x: x.updated, reverse=True) form_new_action = NewActionForm(lot=lot.id) form_new_allocate = AllocateForm(lot=lot.id) form_new_datawipe = DataWipeForm(lot=lot.id) @@ -67,20 +63,6 @@ class DeviceListMix(GenericMixView): user_from=g.user.email, ) else: - if "All" in filter_types: - devices = ( - Device.query.filter(Device.owner_id == current_user.id) - .filter_by(lots=None) - .order_by(Device.updated.desc()) - ) - else: - devices = ( - Device.query.filter(Device.owner_id == current_user.id) - .filter_by(lots=None) - .filter(Device.type.in_(filter_types)) - .order_by(Device.updated.desc()) - ) - form_new_action = NewActionForm() form_new_allocate = AllocateForm() form_new_datawipe = DataWipeForm() diff --git a/ereuse_devicehub/resources/lot/views.py b/ereuse_devicehub/resources/lot/views.py index dce6af62..b99e622f 100644 --- a/ereuse_devicehub/resources/lot/views.py +++ b/ereuse_devicehub/resources/lot/views.py @@ -29,6 +29,7 @@ class LotView(View): """ format = EnumField(LotFormat, missing=None) search = f.Str(missing=None) + type = f.Str(missing=None) def post(self): l = request.get_json() @@ -88,6 +89,7 @@ class LotView(View): else: query = Lot.query query = self.visibility_filter(query) + query = self.type_filter(query, args) if args['search']: query = query.filter(Lot.name.ilike(args['search'] + '%')) lots = query.paginate(per_page=6 if args['search'] else query.count()) @@ -104,6 +106,21 @@ class LotView(View): Lot.owner_id == g.user.id)) return query + def type_filter(self, query, args): + lot_type = args.get('type') + + # temporary + if lot_type == "temporary": + return query.filter(Lot.trade == None) + + if lot_type == "incoming": + return query.filter(Lot.trade and Trade.user_to == g.user) + + if lot_type == "outgoing": + return query.filter(Lot.trade and Trade.user_from == g.user) + + return query + def query(self, args): query = Lot.query.distinct() return query diff --git a/ereuse_devicehub/static/js/api.js b/ereuse_devicehub/static/js/api.js index 190e5d42..552544d2 100644 --- a/ereuse_devicehub/static/js/api.js +++ b/ereuse_devicehub/static/js/api.js @@ -4,7 +4,7 @@ const Api = { * @returns get lots */ async get_lots() { - const request = await this.doRequest(API_URLS.lots, "GET", null); + const request = await this.doRequest(`${API_URLS.lots}?type=temporary`, "GET", null); if (request != undefined) return request.items; throw request; }, diff --git a/ereuse_devicehub/static/js/main.js b/ereuse_devicehub/static/js/main.js index 55a4951c..996d8b23 100644 --- a/ereuse_devicehub/static/js/main.js +++ b/ereuse_devicehub/static/js/main.js @@ -14,9 +14,9 @@ el = el.trim() if (all) { return [...document.querySelectorAll(el)] - } - return document.querySelector(el) - + } + return document.querySelector(el) + } /** @@ -215,34 +215,6 @@ }, 200); } - /** - * Select all functionality - */ - const btnSelectAll = document.getElementById("SelectAllBTN"); - const tableListCheckboxes = document.querySelectorAll(".deviceSelect"); - - function itemListCheckChanged(event) { - const isAllChecked = Array.from(tableListCheckboxes).map(itm => itm.checked); - if (isAllChecked.every(bool => bool == true)) { - btnSelectAll.checked = true; - btnSelectAll.indeterminate = false; - } else if (isAllChecked.every(bool => bool == false)) { - btnSelectAll.checked = false; - btnSelectAll.indeterminate = false; - } else { - btnSelectAll.indeterminate = true; - } - } - - tableListCheckboxes.forEach(item => { - item.addEventListener("click", itemListCheckChanged); - }) - - btnSelectAll.addEventListener("click", event => { - const checkedState = event.target.checked; - tableListCheckboxes.forEach(ckeckbox => {ckeckbox.checked = checkedState}); - }) - /** * Avoid hide dropdown when user clicked inside */ diff --git a/ereuse_devicehub/static/js/main_inventory.js b/ereuse_devicehub/static/js/main_inventory.js index c230363d..848c72e4 100644 --- a/ereuse_devicehub/static/js/main_inventory.js +++ b/ereuse_devicehub/static/js/main_inventory.js @@ -17,8 +17,119 @@ $(document).ready(() => { // $('#selectLot').selectpicker(); }) +class TableController { + static #tableRows = () => table.activeRows.length > 0 ? table.activeRows : []; + + static #tableRowsPage = () => table.pages[table.rows().dt.currentPage - 1]; + + /** + * @returns Selected inputs from device list + */ + static getSelectedDevices() { + if (this.#tableRows() == undefined) return []; + return this.#tableRows() + .filter(element => element.querySelector("input").checked) + .map(element => element.querySelector("input")) + } + + /** + * @returns Selected inputs in current page from device list + */ + static getAllSelectedDevicesInCurrentPage() { + if (this.#tableRowsPage() == undefined) return []; + return this.#tableRowsPage() + .filter(element => element.querySelector("input").checked) + .map(element => element.querySelector("input")) + } + + /** + * @returns All inputs from device list + */ + static getAllDevices() { + if (this.#tableRows() == undefined) return []; + return this.#tableRows() + .map(element => element.querySelector("input")) + } + + /** + * @returns All inputs from current page in device list + */ + static getAllDevicesInCurrentPage() { + if (this.#tableRowsPage() == undefined) return []; + return this.#tableRowsPage() + .map(element => element.querySelector("input")) + } + + /** + * + * @param {HTMLElement} DOMElements + * @returns Procesed input atributes to an Object class + */ + static ProcessTR(DOMElements) { + return DOMElements.map(element => { + const info = {} + info.checked = element.checked + Object.values(element.attributes).forEach(attrib => { info[attrib.nodeName.replace(/-/g, "_")] = attrib.nodeValue }) + return info + }) + } +} + +/** + * Select all functionality + */ +window.addEventListener("DOMContentLoaded", () => { + const btnSelectAll = document.getElementById("SelectAllBTN"); + const alertInfoDevices = document.getElementById("select-devices-info"); + + function itemListCheckChanged() { + const listDevices = TableController.getAllDevicesInCurrentPage() + const isAllChecked = listDevices.map(itm => itm.checked); + + if (isAllChecked.every(bool => bool == true)) { + btnSelectAll.checked = true; + btnSelectAll.indeterminate = false; + alertInfoDevices.innerHTML = `Selected devices: ${TableController.getSelectedDevices().length} + ${ + TableController.getAllDevices().length != TableController.getSelectedDevices().length + ? `Select all devices (${TableController.getAllDevices().length})` + : "Cancel selection" + }`; + alertInfoDevices.classList.remove("d-none"); + } else if (isAllChecked.every(bool => bool == false)) { + btnSelectAll.checked = false; + btnSelectAll.indeterminate = false; + alertInfoDevices.classList.add("d-none") + } else { + btnSelectAll.indeterminate = true; + alertInfoDevices.classList.add("d-none") + } + } + + TableController.getAllDevices().forEach(item => { + item.addEventListener("click", itemListCheckChanged); + }) + + btnSelectAll.addEventListener("click", event => { + const checkedState = event.target.checked; + TableController.getAllDevicesInCurrentPage().forEach(ckeckbox => { ckeckbox.checked = checkedState }); + itemListCheckChanged() + }) + + alertInfoDevices.addEventListener("click", () => { + const checkState = TableController.getAllDevices().length == TableController.getSelectedDevices().length + TableController.getAllDevices().forEach(ckeckbox => { ckeckbox.checked = !checkState }); + itemListCheckChanged() + }) + + // https://github.com/fiduswriter/Simple-DataTables/wiki/Events + table.on("datatable.page", () => itemListCheckChanged()); + table.on("datatable.perpage", () => itemListCheckChanged()); + table.on("datatable.update", () => itemListCheckChanged()); +}) + function deviceSelect() { - const devices_count = $(".deviceSelect").filter(":checked").length; + const devices_count = TableController.getSelectedDevices().length; get_device_list(); if (devices_count == 0) { $("#addingLotModal .pol").show(); @@ -60,7 +171,7 @@ function deviceSelect() { } function removeLot() { - const devices = $(".deviceSelect"); + const devices = TableController.getAllDevices(); if (devices.length > 0) { $("#btnRemoveLots .text-danger").show(); } else { @@ -70,8 +181,8 @@ function removeLot() { } function removeTag() { - const devices = $(".deviceSelect").filter(":checked"); - const devices_id = $.map(devices, (x) => $(x).attr("data")); + const devices = TableController.getSelectedDevices(); + const devices_id = devices.map(dev => dev.data); if (devices_id.length == 1) { const url = `/inventory/tag/devices/${devices_id[0]}/del/`; window.location.href = url; @@ -81,8 +192,8 @@ function removeTag() { } function addTag() { - const devices = $(".deviceSelect").filter(":checked"); - const devices_id = $.map(devices, (x) => $(x).attr("data")); + const devices = TableController.getSelectedDevices(); + const devices_id = devices.map(dev => dev.data); if (devices_id.length == 1) { $("#addingTagModal .pol").hide(); $("#addingTagModal .btn-primary").show(); @@ -137,7 +248,7 @@ function newDataWipe(action) { } function get_device_list() { - const devices = $(".deviceSelect").filter(":checked"); + const devices = TableController.getSelectedDevices(); /* Insert the correct count of devices in actions form */ const devices_count = devices.length; @@ -156,14 +267,15 @@ function get_device_list() { "Desktop": "", "Laptop": "", }; + list_devices = devices.map((x) => { - let typ = $(devices[x]).data("device-type"); - const manuf = $(devices[x]).data("device-manufacturer"); - const dhid = $(devices[x]).data("device-dhid"); + let typ = $(x).data("device-type"); + const manuf = $(x).data("device-manufacturer"); + const dhid = $(x).data("device-dhid"); if (computer[typ]) { typ = computer[typ]; }; - return `${typ } ${ manuf } ${ dhid}`; + return `${typ} ${manuf} ${dhid}`; }); description = $.map(list_devices, (x) => x).join(", "); @@ -171,9 +283,9 @@ function get_device_list() { } function export_file(type_file) { - const devices = $(".deviceSelect").filter(":checked"); + const devices = TableController.getSelectedDevices(); const devices_id = $.map(devices, (x) => $(x).attr("data-device-dhid")).join(","); - if (devices_id){ + if (devices_id) { const url = `/inventory/export/${type_file}/?ids=${devices_id}`; window.location.href = url; } else { @@ -194,40 +306,29 @@ async function processSelectedDevices() { /** * Manage the actions that will be performed when applying the changes - * @param {*} ev event (Should be a checkbox type) - * @param {string} lotID lot id - * @param {number} deviceID device id + * @param {EventSource} ev event (Should be a checkbox type) + * @param {Lot} lot lot id + * @param {Device[]} selectedDevices device id */ - manage(event, lotID, deviceListID) { + manage(event, lot, selectedDevices) { event.preventDefault(); + const lotID = lot.id; const srcElement = event.srcElement.parentElement.children[0] - const {indeterminate} = srcElement; const checked = !srcElement.checked; - const found = this.list.filter(list => list.lotID == lotID)[0]; - const foundIndex = found != undefined ? this.list.findLastIndex(x => x.lotID == found.lotID) : -1; + const found = this.list.filter(list => list.lot.id == lotID)[0]; if (checked) { - if (found != undefined && found.type == "Remove") { - if (found.isFromIndeterminate == true) { - found.type = "Add"; - this.list[foundIndex] = found; - } else { - this.list = this.list.filter(list => list.lotID != lotID); - } + if (found && found.type == "Remove") { + found.type = "Add"; } else { - this.list.push({ type: "Add", lotID, devices: deviceListID, isFromIndeterminate: indeterminate }); - } - } else if (found != undefined && found.type == "Add") { - if (found.isFromIndeterminate == true) { - found.type = "Remove"; - this.list[foundIndex] = found; - } else { - this.list = this.list.filter(list => list.lotID != lotID); - } - } else { - this.list.push({ type: "Remove", lotID, devices: deviceListID, isFromIndeterminate: indeterminate }); + this.list.push({ type: "Add", lot, devices: selectedDevices }); } + } else if (found && found.type == "Add") { + found.type = "Remove"; + } else { + this.list.push({ type: "Remove", lot, devices: selectedDevices }); + } if (this.list.length > 0) { document.getElementById("ApplyDeviceLots").classList.remove("disabled"); @@ -244,7 +345,7 @@ async function processSelectedDevices() { */ notifyUser(title, toastText, isError) { const toast = document.createElement("div"); - toast.classList = `alert alert-dismissible fade show ${ isError ? "alert-danger" : "alert-success"}`; + toast.classList = `alert alert-dismissible fade show ${isError ? "alert-danger" : "alert-success"}`; toast.attributes["data-autohide"] = !isError; toast.attributes.role = "alert"; toast.style = "margin-left: auto; width: fit-content;"; @@ -268,17 +369,19 @@ async function processSelectedDevices() { this.list.forEach(async action => { if (action.type == "Add") { try { - await Api.devices_add(action.lotID, action.devices); - this.notifyUser("Devices sucefully aded to selected lot/s", "", false); + const devicesIDs = action.devices.filter(dev => !action.lot.devices.includes(dev.id)).map(dev => dev.id) + await Api.devices_add(action.lot.id, devicesIDs); + this.notifyUser("Devices sucefully added to selected lot/s", "", false); } catch (error) { this.notifyUser("Failed to add devices to selected lot/s", error.responseJSON.message, true); } } else if (action.type == "Remove") { try { - await Api.devices_remove(action.lotID, action.devices); + const devicesIDs = action.devices.filter(dev => action.lot.devices.includes(dev.id)).map(dev => dev.id) + await Api.devices_remove(action.lot.id, devicesIDs); this.notifyUser("Devices sucefully removed from selected lot/s", "", false); } catch (error) { - this.notifyUser("Fail to remove devices from selected lot/s", error.responseJSON.message, true); + this.notifyUser("Failed to remove devices from selected lot/s", error.responseJSON.message, true); } } requestCount += 1 @@ -287,6 +390,7 @@ async function processSelectedDevices() { this.list = []; } }) + $("#confirmLotsModal").modal("hide"); // Hide dialog when click "Save changes" document.getElementById("dropDownLotsSelector").classList.remove("show"); } @@ -299,15 +403,22 @@ async function processSelectedDevices() { const tmpDiv = document.createElement("div") tmpDiv.innerHTML = newRequest - const oldTable = Array.from(document.querySelectorAll("table.table > tbody > tr .deviceSelect")).map(x => x.attributes["data-device-dhid"].value) const newTable = Array.from(tmpDiv.querySelectorAll("table.table > tbody > tr .deviceSelect")).map(x => x.attributes["data-device-dhid"].value) - for (let i = 0; i < oldTable.length; i++) { - if (!newTable.includes(oldTable[i])) { - // variable from device_list.html --> See: ereuse_devicehub\templates\inventory\device_list.html (Ln: 411) - table.rows().remove(i) + // https://github.com/fiduswriter/Simple-DataTables/wiki/rows()#removeselect-arraynumber + const rowsToRemove = [] + for (let i = 0; i < table.activeRows.length; i++) { + const row = table.activeRows[i]; + if (!newTable.includes(row.querySelector("input").attributes["data-device-dhid"].value)) { + rowsToRemove.push(i) } } + table.rows().remove(rowsToRemove); + + // Restore state of checkbox + const selectAllBTN = document.getElementById("SelectAllBTN"); + selectAllBTN.checked = false; + selectAllBTN.indeterminate = false; } } @@ -315,14 +426,14 @@ async function processSelectedDevices() { /** * Generates a list item with a correspondient checkbox state - * @param {String} lotID - * @param {String} lotName - * @param {Array} selectedDevicesIDs - * @param {HTMLElement} target + * @param {Object} lot Lot model server + * @param {Device[]} selectedDevices list selected devices + * @param {HTMLElement} elementTarget + * @param {Action[]} actions */ - function templateLot(lot, elementTarget, actions) { + function templateLot(lot, selectedDevices, elementTarget, actions) { elementTarget.innerHTML = "" - const {id, name, state} = lot; + const { id, name, state } = lot; const htmlTemplate = ` `; @@ -345,16 +456,17 @@ async function processSelectedDevices() { break; } - doc.children[0].addEventListener("mouseup", (ev) => actions.manage(ev, id, selectedDevicesIDs)); - doc.children[1].addEventListener("mouseup", (ev) => actions.manage(ev, id, selectedDevicesIDs)); + doc.children[0].addEventListener("mouseup", (ev) => actions.manage(ev, lot, selectedDevices)); + doc.children[1].addEventListener("mouseup", (ev) => actions.manage(ev, lot, selectedDevices)); elementTarget.append(doc); } const listHTML = $("#LotsSelector") // Get selected devices - const selectedDevicesIDs = $.map($(".deviceSelect").filter(":checked"), (x) => parseInt($(x).attr("data"))); - if (selectedDevicesIDs.length <= 0) { + const selectedDevicesID = TableController.ProcessTR(TableController.getSelectedDevices()).map(item => item.data) + + if (selectedDevicesID.length <= 0) { listHTML.html("
  • No devices selected
  • "); return; } @@ -364,24 +476,65 @@ async function processSelectedDevices() { if (eventClickActions) { document.getElementById("ApplyDeviceLots").removeEventListener(eventClickActions); } - eventClickActions = document.getElementById("ApplyDeviceLots").addEventListener("click", () => actions.doActions()); + + eventClickActions = document.getElementById("ApplyDeviceLots").addEventListener("click", () => { + const modal = $("#confirmLotsModal") + modal.modal({ keyboard: false }) + + let list_changes_html = ""; + // {type: ["Remove" | "Add"], "LotID": string, "devices": number[]} + actions.list.forEach(action => { + let type; + let devices; + if (action.type == "Add") { + type = "success"; + devices = action.devices.filter(dev => !action.lot.devices.includes(dev.id)) // Only show affected devices + } else { + type = "danger"; + devices = action.devices.filter(dev => action.lot.devices.includes(dev.id)) // Only show affected devices + } + list_changes_html += ` +
    +
    ${action.lot.name}
    +
    +

    + ${devices.map(item => { + const name = `${item.type} ${item.manufacturer} ${item.model}` + return `${item.devicehubID}`; + }).join(" ")} +

    +
    +
    `; + }) + + modal.find(".modal-body").html(list_changes_html) + + const el = document.getElementById("SaveAllActions") + const elClone = el.cloneNode(true); + el.parentNode.replaceChild(elClone, el); + elClone.addEventListener("click", () => actions.doActions()) + + modal.modal("show") + + // actions.doActions(); + }); document.getElementById("ApplyDeviceLots").classList.add("disabled"); try { listHTML.html("
  • ") - const devices = await Api.get_devices(selectedDevicesIDs); + const selectedDevices = await Api.get_devices(selectedDevicesID); let lots = await Api.get_lots(); lots = lots.map(lot => { - lot.devices = devices + lot.devices = selectedDevices .filter(device => device.lots.filter(devicelot => devicelot.id == lot.id).length > 0) .map(device => parseInt(device.id)); - switch (lot.devices.length) { + switch (lot.devices.length) { case 0: lot.state = "false"; break; - case selectedDevicesIDs.length: + case selectedDevicesID.length: lot.state = "true"; break; default: @@ -392,15 +545,14 @@ async function processSelectedDevices() { return lot; }) - let lotsList = []; - lotsList.push(lots.filter(lot => lot.state == "true").sort((a,b) => a.name.localeCompare(b.name))); - lotsList.push(lots.filter(lot => lot.state == "indetermined").sort((a,b) => a.name.localeCompare(b.name))); - lotsList.push(lots.filter(lot => lot.state == "false").sort((a,b) => a.name.localeCompare(b.name))); + lotsList.push(lots.filter(lot => lot.state == "true").sort((a, b) => a.name.localeCompare(b.name))); + lotsList.push(lots.filter(lot => lot.state == "indetermined").sort((a, b) => a.name.localeCompare(b.name))); + lotsList.push(lots.filter(lot => lot.state == "false").sort((a, b) => a.name.localeCompare(b.name))); lotsList = lotsList.flat(); // flat array listHTML.html(""); - lotsList.forEach(lot => templateLot(lot, listHTML, actions)); + lotsList.forEach(lot => templateLot(lot, selectedDevices, listHTML, actions)); } catch (error) { console.log(error); listHTML.html("
  • Error feching devices and lots
    (see console for more details)
  • "); diff --git a/ereuse_devicehub/templates/inventory/alert_lots_changes.html b/ereuse_devicehub/templates/inventory/alert_lots_changes.html new file mode 100644 index 00000000..fa2613c9 --- /dev/null +++ b/ereuse_devicehub/templates/inventory/alert_lots_changes.html @@ -0,0 +1,19 @@ + + \ No newline at end of file diff --git a/ereuse_devicehub/templates/inventory/device_list.html b/ereuse_devicehub/templates/inventory/device_list.html index b95334f8..ef8f7aef 100644 --- a/ereuse_devicehub/templates/inventory/device_list.html +++ b/ereuse_devicehub/templates/inventory/device_list.html @@ -7,7 +7,7 @@