From 45708ee287757a08e22b08d4b4292fda320efbfa Mon Sep 17 00:00:00 2001 From: myoualid Date: Wed, 18 Sep 2024 20:34:31 +0100 Subject: [PATCH] Cost module web UI: - quick button to add a SUM cost value - style improvements --- .../bonsai/bim/data/webui/static/css/cost.css | 42 ++++--- .../bonsai/bim/data/webui/static/js/cost.js | 7 +- .../data/webui/static/js/utilities/costui.js | 118 ++++++++++-------- src/bonsai/bonsai/tool/web.py | 11 ++ 4 files changed, 110 insertions(+), 68 deletions(-) diff --git a/src/bonsai/bonsai/bim/data/webui/static/css/cost.css b/src/bonsai/bonsai/bim/data/webui/static/css/cost.css index eac13d1945..039ad8e4c1 100644 --- a/src/bonsai/bonsai/bim/data/webui/static/css/cost.css +++ b/src/bonsai/bonsai/bim/data/webui/static/css/cost.css @@ -8,6 +8,7 @@ --background-color: #363636; --button-background: #28a745; --button-hover-background: #218838; + --highlight-over: #ED5700; --scrollbar-track-color: #f1f1f1; --scrollbar-thumb-color: #888; --scrollbar-thumb-hover-color: #555; @@ -95,8 +96,7 @@ body { display: inline-block; transition: background-color 0.3s ease; width: fit-content; - margin: 0.5em; - z-index: 11000; + margin: 0.25em; } .action-button::after { @@ -116,11 +116,12 @@ body { .action-button:hover::after { opacity: 1; + border-color: var(--highlight-over); + border-width: 2px; } .active-btn { background-color: #887821; - /* make a nic shadow */ box-shadow: 0 0 10px #887821; } @@ -246,7 +247,7 @@ td { align-items: center; } -td:first-child { +#cost-items td:first-child { display: flex; align-items: center; } @@ -281,29 +282,26 @@ input:focus { color: #ddd; } -#cost-items input, #cost-items -select { +#cost-items input{ border-radius: 6px; padding: 6px 8px; font-size: 14px; line-height: 20px; - margin-left: 0.5em; - margin-right: 0.5em; font-size: var(--fontSize); min-width: 50px; } -form input { +form input, form select { border-radius: 6px; padding: 3px 4px; font-size: 12px; line-height: 10px; - margin-left: 0.2em; - margin-right: 0.2em; font-size: inherit; - height: inherit; - width: inherit; - box-sizing: border-box; + width: 100%; + height: 100%; + max-height: 30px; + max-width: 250px; + box-sizing: border-box; } #cost-items tr:hover, @@ -399,7 +397,17 @@ form table { } .clickable-cell:hover { - border: 1px solid #94a728; - border-radius: 6px; + border: 2px solid var(--highlight-over); cursor: pointer; -} \ No newline at end of file +} + +td { + position: relative; +} + +.append-right { + right: 0; + top: 50%; + position: absolute; + transform: translateY(-50%); +} diff --git a/src/bonsai/bonsai/bim/data/webui/static/js/cost.js b/src/bonsai/bonsai/bim/data/webui/static/js/cost.js index be078a8d3c..e1c4ed376a 100644 --- a/src/bonsai/bonsai/bim/data/webui/static/js/cost.js +++ b/src/bonsai/bonsai/bim/data/webui/static/js/cost.js @@ -54,14 +54,16 @@ function handleEditQuantities(data) { }); } +function addSumCostValue(costItemId) { + executeOperator({ type: "addSumCostValue", costItemId: costItemId }); +} + function addQuantity(costItemId, ifcClass) { executeOperator({ type: "AddCostItemQuantity", costItemId: costItemId, ifcClass: ifcClass, }); - - //CostUI.addQuantity(costItemId, quantityName); } function editQuantity(costItemId, quantityId, attributes) { @@ -387,6 +389,7 @@ function handleCostItemsData(data) { enableEditingCostValues: enableEditingCostValues, addSummaryCostItem: addSummaryCostItem, enableEditingQuantities: enableEditingQuantities, + addSumCostValue: addSumCostValue, }, }); } diff --git a/src/bonsai/bonsai/bim/data/webui/static/js/utilities/costui.js b/src/bonsai/bonsai/bim/data/webui/static/js/utilities/costui.js index 7a99b57c8a..53055f2e65 100644 --- a/src/bonsai/bonsai/bim/data/webui/static/js/utilities/costui.js +++ b/src/bonsai/bonsai/bim/data/webui/static/js/utilities/costui.js @@ -32,7 +32,6 @@ export class CostUI { for (let i = 0; i < columnHeaders.length; i++) { const th = document.createElement("th"); th.textContent = columnHeaders[i]; - th.style.position = "relative"; tr.appendChild(th); if (i < columnHeaders.length - 1) { @@ -417,12 +416,22 @@ export class CostUI { }); } + static format_number(number) { + return new Intl.NumberFormat("en-US", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + useGrouping: true, + }) + .format(number) + .replace(/,/g, " "); + } + static addCostItemRow(costItem, nestingLevel, parentID, callbacks = {}) { const totalQuantity = costItem.TotalCostQuantity - ? parseFloat(costItem.TotalCostQuantity).toFixed(2) + ? CostUI.format_number(costItem.TotalCostQuantity) : "-"; const appliedValue = costItem.TotalAppliedValue - ? parseFloat(costItem.TotalAppliedValue).toFixed(2) + ? CostUI.format_number(costItem.TotalAppliedValue) : "-"; const row = CostUI.createCostItemRow(costItem, nestingLevel, parentID); const expandButton = CostUI.createExpandButton(costItem); @@ -435,9 +444,14 @@ export class CostUI { const totalCostQuantityCell = CostUI.createTableCell(totalQuantity); totalCostQuantityCell.classList.add("clickable-cell"); const unitSymbolCell = CostUI.createTableCell(costItem.UnitSymbol); - const totalAppliedValueCell = CostUI.createTableCell(appliedValue); + + const totalAppliedValueCell = CostUI.createCostCell(appliedValue); totalAppliedValueCell.classList.add("clickable-cell"); - const totalCostCell = CostUI.createTotalCostCell(costItem); + + const totalCostCell = CostUI.createTotalCostCell( + costItem, + callbacks.addSumCostValue + ); const actionsCell = CostUI.costItemActions(costItem, callbacks); actionsCell.classList.add("actions-column"); @@ -630,15 +644,37 @@ export class CostUI { return cell; } - static createTotalCostCell(costItem) { + static createCostCell(appliedValue) { + return CostUI.createTableCell(appliedValue); + } + + static createTotalCostCell(costItem, callback) { const totalCostCell = document.createElement("td"); - const totalCost = parseFloat(costItem.TotalCost).toFixed(2); - totalCostCell.textContent = costItem.is_sum - ? totalCost + " (Σ)" - : totalCost; + const totalCost = CostUI.format_number(costItem.TotalCost); + totalCostCell.textContent = totalCost; + if (costItem.is_sum) { + totalCostCell.textContent = totalCost + " *"; + } else if (!costItem.TotalCost) { + const button = CostUI.createButton("Sum", "fa-solid fa-calculator"); + button.addEventListener("click", function () { + const costItemId = parseInt(this.closest("tr").getAttribute("id")); + callback(costItemId); + }); + const onHoverElement = CostUI.onHoverElement(); + onHoverElement.appendChild(button); + totalCostCell.appendChild(onHoverElement); + } + return totalCostCell; } + static onHoverElement() { + const div = document.createElement("div"); + div.classList.add("actions-column"); + div.classList.add("append-right"); + return div; + } + static costItemActions(costItem, callbacks) { const divFlex = document.createElement("div"); divFlex.classList.add("row-container"); @@ -779,9 +815,7 @@ export class CostUI { ); cardTitle.classList.add("card-title"); - const cardButton = document.createElement("button"); - cardButton.classList.add("action-button"); - cardButton.textContent = "Load"; + const cardButton = CostUI.createButton("Load", "fa-solid fa-repeat"); cardButton.addEventListener("click", callback); cardBody.appendChild(cardTitle); @@ -912,8 +946,13 @@ export class CostUI { costType = "FIXED"; } const options = ["FIXED", "CATEGORY", "SUM"]; - const typeCell = CostUI.createTableDropdown("type", options, costType); - const dropdown = typeCell.querySelector("select"); + const typeCell = document.createElement("td"); + const dropdown = CostUI.createTableDropdown({ + name: "type", + options: options, + defaultValue: costType, + }); + typeCell.appendChild(dropdown); dropdown.addEventListener("change", function () { const selectedType = this.value; CostUI.updateRowBasedOnType(selectedType, categoryCell, valueCell1); @@ -982,16 +1021,16 @@ export class CostUI { handleCostValueChange.call(this, costItemId); } }); + const deleteCell = document.createElement("td"); tr.appendChild(deleteCell); - const deleteCostValue = document.createElement("button"); - deleteCostValue.classList.add("action-button"); - deleteCostValue.textContent = "Delete"; - deleteCostValue.addEventListener("click", function () { + const dleteButton = CostUI.createButton("Delete", "fa-solid fa-trash"); + dleteButton.addEventListener("click", function () { costValueCallbacks.deleteCostValue(costItemId, costValue.id); tr.remove(); }); - deleteCell.appendChild(deleteCostValue); + + deleteCell.appendChild(dleteButton); CostUI.updateRowBasedOnType(costType, categoryCell, valueCell1); return tr; } @@ -1015,23 +1054,19 @@ export class CostUI { } } - static createTableDropdown(name, options, value = "FIXED") { - const cell = document.createElement("td"); + static createTableDropdown({ name, options, defaultValue = "FIXED" }) { const dropdown = document.createElement("select"); dropdown.name = name; - options.forEach((optionValue) => { const option = document.createElement("option"); option.value = optionValue; option.textContent = optionValue; - if (optionValue === value) { + if (optionValue === defaultValue) { option.selected = true; } dropdown.appendChild(option); }); - - cell.appendChild(dropdown); - return cell; + return dropdown; } static createTableInput(type, name, value) { @@ -1045,9 +1080,7 @@ export class CostUI { } static createAddCostValueButton(costItemId, callbacks) { - const addButton = document.createElement("button"); - addButton.classList.add("action-button"); - addButton.textContent = " + Add Cost Value"; + const addButton = CostUI.createButton("Add Cost Value", "fa-solid fa-plus"); addButton.addEventListener("click", function (e) { e.preventDefault(); callbacks.addCostValue ? callbacks.addCostValue(costItemId) : null; @@ -1992,7 +2025,6 @@ export class CostUI { ); manualQuantitiesSection.appendChild(text); } else { - // add dropdown to chose quantity type , from "IfcQuantityArea", "IfcQuantityLength", "IfcQuantityVolume", "IfcQuantityCount", "IfcQuantityWeight " const quantityTypes = [ "IfcQuantityArea", "IfcQuantityLength", @@ -2000,18 +2032,15 @@ export class CostUI { "IfcQuantityCount", "IfcQuantityWeight", ]; - const dropdown = CostUI.createTableDropdown( - "type", - quantityTypes, - "IfcQuantityArea" - ); + const dropdown = CostUI.createTableDropdown({ + name: "type", + options: quantityTypes, + defaultValue: "IfcQuantityArea", + }); dropdown.id = "quantity-type-dropdown-" + costItemId; manualQuantitiesSection.appendChild(dropdown); } - - // if quantity is ty IfcQuantityCount, and the costQuantities if (quantityType === "IfcQuantityCount" && has_assigned_products) { - // write text that one of the quantities is assigned to the product selection const text = CostUI.Text( " One of the quantities is assigned to the product selection", "fa-solid fa-warning", @@ -2060,7 +2089,6 @@ export class CostUI { ); manualQuantitiesSection.appendChild(tableContainer); - // manualQuantitiesSection.appendChild(tableContainer2); manualQuantitiesSection.appendChild(addButton); return manualQuantitiesSection; } @@ -2068,14 +2096,9 @@ export class CostUI { static createManualQuantityRow(costItemId, quantity, callbacks) { const tr = document.createElement("tr"); tr.id = quantity.id; - - // get quantity keys and values to create the table row cells - // Label cell - Object.entries(quantity).forEach(([key, value]) => { if (key !== "id" && key !== "fromProduct") { if (key === "Name") { - // create input const cell = document.createElement("td"); const input = document.createElement("input"); input.type = "text"; @@ -2093,7 +2116,6 @@ export class CostUI { }); } - // if key contains value else if (key.toLowerCase().includes("value")) { const cell = document.createElement("td"); const input = document.createElement("input"); @@ -2105,7 +2127,7 @@ export class CostUI { if (e.key === "Enter") { e.preventDefault(); callbacks.editQuantity(costItemId, quantity.id, { - [key]: parseFloat(input.value), // Dynamically set the key and parse the value as a double + [key]: parseFloat(input.value), }); } }); @@ -2118,7 +2140,6 @@ export class CostUI { } }); - // Add delete button const deleteButton = CostUI.createButton("Delete", "fa-solid fa-trash"); const deleteCell = document.createElement("td"); deleteButton.addEventListener("click", function (e) { @@ -2142,7 +2163,6 @@ export class CostUI { e.preventDefault(); let type = quantityType; if (!type) { - // get quantityType from dropdown const qtoSection = document.getElementById( "manual-quantities-section-" + costItemId ); diff --git a/src/bonsai/bonsai/tool/web.py b/src/bonsai/bonsai/tool/web.py index d459ac1e3c..2564c0fb6b 100644 --- a/src/bonsai/bonsai/tool/web.py +++ b/src/bonsai/bonsai/tool/web.py @@ -420,6 +420,17 @@ class Web(bonsai.core.tool.Web): data_key="cost_value", event="cost_value", ) + if operator_data["type"] == "addSumCostValue": + cost_item = ifc_file.by_id(operator_data["costItemId"]) + value = ifcopenshell.api.cost.add_cost_value( + ifc_file, + parent=cost_item, + ) + ifcopenshell.api.cost.edit_cost_value(file=ifc_file, cost_value=value, attributes={"Category": "*"}) + cost_schedule = tool.Cost.get_cost_schedule(cost_item=cost_item) + tool.Cost.load_cost_schedule_tree() + cls.load_cost_schedule_web_ui(cost_schedule) + if operator_data["type"] == "deleteCostValue": bpy.ops.bim.remove_cost_value(parent=operator_data["costItemId"], cost_value=operator_data["costValueId"]) cost_item = ifc_file.by_id(operator_data["costItemId"])