mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 01:41:57 +00:00
web ui cost module features:
- improve UI for editing cost quantites - add, edit, delete manual quantities
This commit is contained in:
@@ -20,6 +20,22 @@ body {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.floating-form {
|
||||
width: 50vw;
|
||||
max-height: 80vh;
|
||||
position: absolute;
|
||||
background-color: var(--background-color);
|
||||
border: 1px solid black;
|
||||
z-index: 9999;
|
||||
cursor: move;
|
||||
user-select: none;
|
||||
padding-bottom: var(--padding-medium);
|
||||
}
|
||||
|
||||
[id^="enable-editing-quantities"].floating-form {
|
||||
height: 70vh;
|
||||
}
|
||||
|
||||
.form-header {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
@@ -30,36 +46,44 @@ body {
|
||||
font-size: large;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-radius: 6px 6px 0 0;
|
||||
height: 5%;
|
||||
}
|
||||
|
||||
.floating-form span {
|
||||
padding: var(--padding-small);
|
||||
margin-left: var(--margin-small);
|
||||
.form-container {
|
||||
height: 95%;
|
||||
padding-left: var(--padding-medium);
|
||||
padding-right: var(--padding-medium);
|
||||
}
|
||||
|
||||
.floating-form {
|
||||
width: 50vw;
|
||||
max-height: 80vh;
|
||||
position: absolute;
|
||||
background-color: var(--background-color);
|
||||
border: 1px solid black;
|
||||
z-index: 9999;
|
||||
cursor: move;
|
||||
user-select: none;
|
||||
resize: both;
|
||||
.form-section {
|
||||
margin-bottom: 1em;
|
||||
height: 65%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.form-section h3 {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.summary-section {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.summary-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
.floating-form table tr {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
padding: var(--padding-medium);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
position:relative;
|
||||
background-color: var(--button-background);
|
||||
border: none;
|
||||
color: #fff;
|
||||
@@ -72,6 +96,32 @@ body {
|
||||
transition: background-color 0.3s ease;
|
||||
width: fit-content;
|
||||
margin: 0.5em;
|
||||
z-index: 11000;
|
||||
}
|
||||
|
||||
.action-button::after {
|
||||
content: attr(data-tooltip);
|
||||
position: absolute;
|
||||
bottom: -30px;
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.action-button:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.active-btn {
|
||||
background-color: #887821;
|
||||
/* make a nic shadow */
|
||||
box-shadow: 0 0 10px #887821;
|
||||
}
|
||||
|
||||
.action-button:hover {
|
||||
@@ -141,7 +191,7 @@ body {
|
||||
}
|
||||
|
||||
.floating-form .table-container {
|
||||
max-height: 70%;
|
||||
max-height: 80%;
|
||||
}
|
||||
|
||||
#cost-items {
|
||||
@@ -149,7 +199,6 @@ body {
|
||||
}
|
||||
|
||||
[id^="cost-values-form"] table {
|
||||
/* fixed */
|
||||
max-height: 50%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
@@ -186,6 +235,7 @@ td,
|
||||
th {
|
||||
border-right: 1px solid #ddd;
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
th {
|
||||
@@ -221,15 +271,7 @@ select {
|
||||
color: var(--blender-button-text, var(--text-color));
|
||||
border-color: var(--blender-button-border, var(--border-color));
|
||||
transition: filter 0.2s ease;
|
||||
border-radius: 6px;
|
||||
padding: 6px 8px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
transition: border-color 0.2s cubic-bezier(0.3, 0, 0.5, 1);
|
||||
margin-left: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
font-size: var(--fontSize);
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
@@ -239,6 +281,31 @@ input:focus {
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
#cost-items input, #cost-items
|
||||
select {
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
#cost-items tr:hover,
|
||||
.highlighted {
|
||||
background-color: #28a74657;
|
||||
@@ -332,7 +399,7 @@ form table {
|
||||
}
|
||||
|
||||
.clickable-cell:hover {
|
||||
border: 1px solid #28a746;
|
||||
border: 1px solid #94a728;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
@@ -36,19 +36,51 @@ function handleEditQuantities(data) {
|
||||
const products = data.data["quantities"]["selected_products"];
|
||||
const assigned_products = data.data["quantities"]["assigned_products"];
|
||||
const quantityNames = data.data["quantities"]["product_quantity_names"];
|
||||
const costQuantities = data.data["quantities"]["cost_quantities"];
|
||||
|
||||
CostUI.editQuantities({
|
||||
CostUI.enableEditingQuantities({
|
||||
costItemId: costItemId,
|
||||
selectedProducts: products,
|
||||
assignedProducts: assigned_products,
|
||||
quantityNames: quantityNames,
|
||||
costQuantities: costQuantities,
|
||||
callbacks: {
|
||||
addProductAssignments: addProductAssignments,
|
||||
enableEditingQuantities: enableEditingQuantities,
|
||||
addQuantity: addQuantity,
|
||||
editQuantity: editQuantity,
|
||||
deleteQuantity: deleteQuantity,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function addQuantity(costItemId, ifcClass) {
|
||||
executeOperator({
|
||||
type: "AddCostItemQuantity",
|
||||
costItemId: costItemId,
|
||||
ifcClass: ifcClass,
|
||||
});
|
||||
|
||||
//CostUI.addQuantity(costItemId, quantityName);
|
||||
}
|
||||
|
||||
function editQuantity(costItemId, quantityId, attributes) {
|
||||
executeOperator({
|
||||
type: "editCostItemQuantity",
|
||||
costItemId: costItemId,
|
||||
quantityId: quantityId,
|
||||
attributes: attributes,
|
||||
});
|
||||
}
|
||||
|
||||
function deleteQuantity(costItemId, quantityId) {
|
||||
executeOperator({
|
||||
type: "deleteCostItemQuantity",
|
||||
costItemId: costItemId,
|
||||
quantityId: quantityId,
|
||||
});
|
||||
}
|
||||
|
||||
function handlePredefinedTypes(data) {
|
||||
CostUI.enableAddingCostSchedule(
|
||||
data.data["predefined_types"],
|
||||
|
||||
@@ -793,11 +793,15 @@ export class CostUI {
|
||||
}
|
||||
|
||||
static getRowNameCell(costItemId) {
|
||||
return document.getElementById(costItemId).querySelector("td input");
|
||||
return document.getElementById(costItemId)
|
||||
? document.getElementById(costItemId).querySelector("td input")
|
||||
: null;
|
||||
}
|
||||
|
||||
static getCostItemName(costItemId) {
|
||||
return CostUI.getRowNameCell(costItemId).value;
|
||||
return CostUI.getRowNameCell(costItemId)
|
||||
? CostUI.getRowNameCell(costItemId).value
|
||||
: "Unnamed";
|
||||
}
|
||||
|
||||
static createCostValuesForm({ costItemId, costValues, callbacks }) {
|
||||
@@ -840,7 +844,6 @@ export class CostUI {
|
||||
static addNewCostValueRow(costItemId, costValueId, costValueCallbacks) {
|
||||
const table = CostUI.getCostValuesTable(costItemId);
|
||||
if (!table) {
|
||||
console.log("Cost values table not found for cost item ID:", costItemId);
|
||||
return;
|
||||
}
|
||||
const tr = CostUI.createCostvaluesRow(
|
||||
@@ -908,8 +911,8 @@ export class CostUI {
|
||||
} else if (costValue.applied_value) {
|
||||
costType = "FIXED";
|
||||
}
|
||||
|
||||
const typeCell = CostUI.createTableDropdown("type", costType);
|
||||
const options = ["FIXED", "CATEGORY", "SUM"];
|
||||
const typeCell = CostUI.createTableDropdown("type", options, costType);
|
||||
const dropdown = typeCell.querySelector("select");
|
||||
dropdown.addEventListener("change", function () {
|
||||
const selectedType = this.value;
|
||||
@@ -1012,12 +1015,11 @@ export class CostUI {
|
||||
}
|
||||
}
|
||||
|
||||
static createTableDropdown(name, value = "FIXED") {
|
||||
static createTableDropdown(name, options, value = "FIXED") {
|
||||
const cell = document.createElement("td");
|
||||
const dropdown = document.createElement("select");
|
||||
dropdown.name = name;
|
||||
|
||||
const options = ["FIXED", "CATEGORY", "SUM"];
|
||||
options.forEach((optionValue) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = optionValue;
|
||||
@@ -1306,7 +1308,7 @@ export class CostUI {
|
||||
}
|
||||
|
||||
static createProductTable({
|
||||
form,
|
||||
container,
|
||||
products,
|
||||
quantityNames,
|
||||
costItemId,
|
||||
@@ -1316,7 +1318,7 @@ export class CostUI {
|
||||
const noProductsMessage = document.createElement("p");
|
||||
noProductsMessage.textContent =
|
||||
"Your Blender Selection is empty! Select objects first.";
|
||||
form.appendChild(noProductsMessage);
|
||||
container.appendChild(noProductsMessage);
|
||||
return;
|
||||
}
|
||||
const { tableContainer, table } = CostUI.addTable({
|
||||
@@ -1324,7 +1326,7 @@ export class CostUI {
|
||||
className: "",
|
||||
id: "cost-values-table-" + costItemId,
|
||||
});
|
||||
form.appendChild(tableContainer);
|
||||
container.appendChild(tableContainer);
|
||||
const tbody = document.createElement("tbody");
|
||||
table.appendChild(tbody);
|
||||
|
||||
@@ -1432,7 +1434,7 @@ export class CostUI {
|
||||
|
||||
const productIds = products.map((product) => product.info.id);
|
||||
callbacks.emptyForm = () => {
|
||||
form.innerHTML = "";
|
||||
container.innerHTML = "";
|
||||
};
|
||||
const addProductAssignmentsButton = CostUI.addProductAssignmentsButton(
|
||||
costItemId,
|
||||
@@ -1441,13 +1443,13 @@ export class CostUI {
|
||||
callbacks
|
||||
);
|
||||
|
||||
form.appendChild(quantitySelect);
|
||||
form.appendChild(addProductAssignmentsButton);
|
||||
container.appendChild(quantitySelect);
|
||||
container.appendChild(addProductAssignmentsButton);
|
||||
return table;
|
||||
}
|
||||
|
||||
static createAssignmentsTable({
|
||||
form,
|
||||
container,
|
||||
products,
|
||||
costItemId,
|
||||
quantityNames,
|
||||
@@ -1459,7 +1461,7 @@ export class CostUI {
|
||||
id: "cost-values-table-" + costItemId,
|
||||
});
|
||||
|
||||
form.appendChild(tableContainer);
|
||||
container.appendChild(tableContainer);
|
||||
const tbody = document.createElement("tbody");
|
||||
table.appendChild(tbody);
|
||||
|
||||
@@ -1512,8 +1514,10 @@ export class CostUI {
|
||||
quantitySelect,
|
||||
callbacks
|
||||
) {
|
||||
const addProductAssignmentsButton = document.createElement("button");
|
||||
addProductAssignmentsButton.textContent = "Add Product Assignments";
|
||||
const addProductAssignmentsButton = CostUI.createButton(
|
||||
"Add Product Assignments",
|
||||
"fa-solid fa-plus"
|
||||
);
|
||||
addProductAssignmentsButton.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
let propName = quantitySelect.value;
|
||||
@@ -1528,7 +1532,7 @@ export class CostUI {
|
||||
})
|
||||
: null;
|
||||
callbacks.emptyForm ? callbacks.emptyForm() : null;
|
||||
callbacks.getSelectedProducts(costItemId);
|
||||
callbacks.enableEditingQuantities(costItemId);
|
||||
});
|
||||
addProductAssignmentsButton.classList.add("action-button");
|
||||
return addProductAssignmentsButton;
|
||||
@@ -1730,49 +1734,424 @@ export class CostUI {
|
||||
});
|
||||
}
|
||||
|
||||
static editQuantities({
|
||||
static enableEditingQuantities({
|
||||
costItemId,
|
||||
selectedProducts,
|
||||
quantityNames,
|
||||
assignedProducts,
|
||||
quantityNames,
|
||||
costQuantities,
|
||||
callbacks,
|
||||
}) {
|
||||
const formId = "selected-products-" + costItemId;
|
||||
CostUI.highlightElement(costItemId);
|
||||
const form = CostUI.Form({
|
||||
id: formId,
|
||||
name:
|
||||
"Edit Product Assignments for: " + CostUI.getCostItemName(costItemId),
|
||||
id: "enable-editing-quantities-" + costItemId,
|
||||
name: "Editing Quantities for: " + CostUI.getCostItemName(costItemId),
|
||||
icon: "fa-solid fa-box",
|
||||
});
|
||||
const numberOfProducts = CostUI.Text(
|
||||
"Selection basket : " + selectedProducts.length + " products",
|
||||
"fa-solid fa-cart-shopping",
|
||||
"large"
|
||||
);
|
||||
form.appendChild(numberOfProducts);
|
||||
CostUI.highlightElement(costItemId);
|
||||
const selectedProductsTable = CostUI.createProductTable({
|
||||
form,
|
||||
|
||||
const ribbonBar = CostUI.createRibbonBar();
|
||||
form.appendChild(ribbonBar);
|
||||
|
||||
const selectedProductsSection = CostUI.selectedProductsSection({
|
||||
products: selectedProducts,
|
||||
quantityNames,
|
||||
costItemId,
|
||||
callbacks,
|
||||
});
|
||||
|
||||
if (assignedProducts.length > 0) {
|
||||
const assignedProductsSection = CostUI.assignedProductsSection({
|
||||
products: assignedProducts,
|
||||
quantityNames,
|
||||
costItemId,
|
||||
callbacks,
|
||||
});
|
||||
|
||||
const manualQuantitiesSection = CostUI.manualQuantitiesSection({
|
||||
costItemId,
|
||||
costQuantities,
|
||||
has_assigned_products: assignedProducts.length > 0,
|
||||
callbacks,
|
||||
});
|
||||
|
||||
form.appendChild(selectedProductsSection);
|
||||
form.appendChild(assignedProductsSection);
|
||||
form.appendChild(manualQuantitiesSection);
|
||||
|
||||
const summarySection = CostUI.createSummarySection({
|
||||
selectedProducts,
|
||||
assignedProducts,
|
||||
costQuantities,
|
||||
});
|
||||
form.appendChild(summarySection);
|
||||
|
||||
CostUI.addEventListeners({
|
||||
switchBar: ribbonBar,
|
||||
costItemId,
|
||||
selectedProductsSection,
|
||||
assignedProductsSection,
|
||||
manualQuantitiesSection,
|
||||
});
|
||||
|
||||
const lastActiveSection =
|
||||
localStorage.getItem("lastActiveSection") || "selected-products";
|
||||
const lastActiveButton = document.getElementById(
|
||||
`${lastActiveSection}-btn`
|
||||
);
|
||||
if (lastActiveButton) {
|
||||
lastActiveButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
static createRibbonBar() {
|
||||
const ribbonBar = document.createElement("div");
|
||||
ribbonBar.className = "switch-bar";
|
||||
ribbonBar.innerHTML = `
|
||||
<button class="action-button" id="selected-products-btn">Selected Products</button>
|
||||
<button class="action-button" id="assigned-products-btn">Assigned Products</button>
|
||||
<button class="action-button" id="manual-quantities-btn">Manual Quantities</button>
|
||||
`;
|
||||
return ribbonBar;
|
||||
}
|
||||
|
||||
static createSummarySection({
|
||||
selectedProducts,
|
||||
assignedProducts,
|
||||
costQuantities,
|
||||
}) {
|
||||
const summarySection = document.createElement("div");
|
||||
summarySection.classList.add("summary-section");
|
||||
|
||||
const manualQuantities = costQuantities.quantities.filter(
|
||||
(quantity) => !quantity.fromProduct
|
||||
);
|
||||
const paramaterQuantities = costQuantities.quantities.filter(
|
||||
(quantity) => quantity.fromProduct
|
||||
);
|
||||
|
||||
const table = document.createElement("table");
|
||||
table.classList.add("summary-table");
|
||||
|
||||
const thead = document.createElement("thead");
|
||||
const headerRow = document.createElement("tr");
|
||||
const headers = ["Category", "Count"];
|
||||
headers.forEach((headerText) => {
|
||||
const th = document.createElement("th");
|
||||
th.textContent = headerText;
|
||||
headerRow.appendChild(th);
|
||||
});
|
||||
thead.appendChild(headerRow);
|
||||
table.appendChild(thead);
|
||||
const tbody = document.createElement("tbody");
|
||||
|
||||
const data = [
|
||||
{ category: "Assigned Products", count: assignedProducts.length },
|
||||
{ category: "Manual Quantities", count: manualQuantities.length },
|
||||
{
|
||||
category: "Product derived Quantities",
|
||||
count: paramaterQuantities.length,
|
||||
},
|
||||
];
|
||||
|
||||
data.forEach((item) => {
|
||||
const row = document.createElement("tr");
|
||||
const categoryCell = document.createElement("td");
|
||||
categoryCell.textContent = item.category;
|
||||
const countCell = document.createElement("td");
|
||||
countCell.textContent = item.count;
|
||||
row.appendChild(categoryCell);
|
||||
row.appendChild(countCell);
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
table.appendChild(tbody);
|
||||
summarySection.appendChild(table);
|
||||
|
||||
return summarySection;
|
||||
}
|
||||
|
||||
static addEventListeners({
|
||||
switchBar,
|
||||
costItemId,
|
||||
selectedProductsSection,
|
||||
assignedProductsSection,
|
||||
manualQuantitiesSection,
|
||||
}) {
|
||||
const buttons = switchBar.querySelectorAll(".action-button");
|
||||
|
||||
buttons.forEach((button) => {
|
||||
button.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
const sectionName = button.id.replace("-btn", "");
|
||||
const sectionId = sectionName + "-section-" + costItemId;
|
||||
|
||||
if (selectedProductsSection)
|
||||
selectedProductsSection.style.display = "none";
|
||||
if (assignedProductsSection)
|
||||
assignedProductsSection.style.display = "none";
|
||||
if (manualQuantitiesSection)
|
||||
manualQuantitiesSection.style.display = "none";
|
||||
|
||||
const section = document.getElementById(sectionId);
|
||||
if (section) {
|
||||
section.style.display = "block";
|
||||
}
|
||||
buttons.forEach((btn) => btn.classList.remove("active-btn"));
|
||||
button.classList.add("active-btn");
|
||||
|
||||
localStorage.setItem("lastActiveSection", sectionName);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static getSection(costItemId, sectionName) {
|
||||
return document.getElementById(`${sectionName}-${costItemId}`);
|
||||
}
|
||||
|
||||
static selectedProductsSection({
|
||||
products,
|
||||
quantityNames,
|
||||
costItemId,
|
||||
callbacks,
|
||||
}) {
|
||||
const selectedProductsSection = document.createElement("div");
|
||||
selectedProductsSection.id = "selected-products-section-" + costItemId;
|
||||
selectedProductsSection.classList.add("form-section");
|
||||
const numberOfProducts = CostUI.Text(
|
||||
"Selection basket : " + products.length + " products",
|
||||
"fa-solid fa-cart-shopping",
|
||||
"medium"
|
||||
);
|
||||
selectedProductsSection.appendChild(numberOfProducts);
|
||||
|
||||
const selectedProductsTable = CostUI.createProductTable({
|
||||
container: selectedProductsSection,
|
||||
products: products,
|
||||
quantityNames,
|
||||
costItemId,
|
||||
callbacks,
|
||||
});
|
||||
return selectedProductsSection;
|
||||
}
|
||||
|
||||
static assignedProductsSection({
|
||||
products,
|
||||
quantityNames,
|
||||
costItemId,
|
||||
callbacks,
|
||||
}) {
|
||||
const assignedProductsSection = document.createElement("div");
|
||||
assignedProductsSection.id = "assigned-products-section-" + costItemId;
|
||||
assignedProductsSection.style.display = "none";
|
||||
assignedProductsSection.classList.add("form-section");
|
||||
if (products.length > 0) {
|
||||
let text = " Assigned Products : " + products.length;
|
||||
const assignedProductsText = CostUI.Text(
|
||||
"Assigned Products",
|
||||
text,
|
||||
"fa-solid fa-solid fa-paperclip",
|
||||
"large"
|
||||
"medium"
|
||||
);
|
||||
form.appendChild(assignedProductsText);
|
||||
assignedProductsSection.appendChild(assignedProductsText);
|
||||
const assignmentsTable = CostUI.createAssignmentsTable({
|
||||
form,
|
||||
products: assignedProducts,
|
||||
container: assignedProductsSection,
|
||||
products: products,
|
||||
quantityNames,
|
||||
costItemId,
|
||||
callbacks,
|
||||
});
|
||||
}
|
||||
|
||||
return assignedProductsSection;
|
||||
}
|
||||
|
||||
static manualQuantitiesSection({
|
||||
costItemId,
|
||||
costQuantities,
|
||||
has_assigned_products,
|
||||
callbacks,
|
||||
}) {
|
||||
const manualQuantitiesSection = document.createElement("div");
|
||||
manualQuantitiesSection.classList.add("form-section");
|
||||
manualQuantitiesSection.id = "manual-quantities-section-" + costItemId;
|
||||
manualQuantitiesSection.style.display = "none";
|
||||
|
||||
const title = CostUI.Text(
|
||||
"Manual Quantities",
|
||||
"fa-solid fa-ruler",
|
||||
"medium"
|
||||
);
|
||||
manualQuantitiesSection.appendChild(title);
|
||||
const unitSymbol = costQuantities.unit_symbol;
|
||||
|
||||
let quantityType = costQuantities.quantity_type;
|
||||
|
||||
if (quantityType) {
|
||||
const text = CostUI.Text(
|
||||
quantityType + " (" + unitSymbol + " )",
|
||||
"fa-solid fa-ruler",
|
||||
"small"
|
||||
);
|
||||
manualQuantitiesSection.appendChild(text);
|
||||
} else {
|
||||
// add dropdown to chose quantity type , from "IfcQuantityArea", "IfcQuantityLength", "IfcQuantityVolume", "IfcQuantityCount", "IfcQuantityWeight "
|
||||
const quantityTypes = [
|
||||
"IfcQuantityArea",
|
||||
"IfcQuantityLength",
|
||||
"IfcQuantityVolume",
|
||||
"IfcQuantityCount",
|
||||
"IfcQuantityWeight",
|
||||
];
|
||||
const dropdown = CostUI.createTableDropdown(
|
||||
"type",
|
||||
quantityTypes,
|
||||
"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",
|
||||
"small"
|
||||
);
|
||||
manualQuantitiesSection.appendChild(text);
|
||||
}
|
||||
|
||||
let paramaterQuantities = costQuantities.quantities.filter(
|
||||
(quantity) => quantity.fromProduct
|
||||
);
|
||||
let manualQuantities = costQuantities.quantities.filter(
|
||||
(quantity) => !quantity.fromProduct
|
||||
);
|
||||
|
||||
let headerNames = ["Name", "Value" + " (" + unitSymbol + " )", "Actions"];
|
||||
|
||||
if (manualQuantities.length > 0) {
|
||||
headerNames = [];
|
||||
Object.entries(manualQuantities[0]).forEach(([key, value]) => {
|
||||
if (key !== "id" && key !== "fromProduct") {
|
||||
headerNames.push(key);
|
||||
}
|
||||
});
|
||||
headerNames.push("Actions");
|
||||
}
|
||||
|
||||
const { tableContainer, table } = CostUI.addTable({
|
||||
headers: headerNames,
|
||||
className: "manual-quantities-table",
|
||||
id: "manual-quantities-table-" + costItemId,
|
||||
});
|
||||
|
||||
manualQuantities.forEach((quantity) => {
|
||||
const tr = CostUI.createManualQuantityRow(
|
||||
costItemId,
|
||||
quantity,
|
||||
callbacks
|
||||
);
|
||||
table.appendChild(tr);
|
||||
});
|
||||
const addButton = CostUI.createAddManualQuantityButton(
|
||||
costItemId,
|
||||
quantityType,
|
||||
callbacks
|
||||
);
|
||||
|
||||
manualQuantitiesSection.appendChild(tableContainer);
|
||||
// manualQuantitiesSection.appendChild(tableContainer2);
|
||||
manualQuantitiesSection.appendChild(addButton);
|
||||
return manualQuantitiesSection;
|
||||
}
|
||||
|
||||
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";
|
||||
input.value = value;
|
||||
cell.appendChild(input);
|
||||
tr.appendChild(cell);
|
||||
|
||||
input.addEventListener("keydown", function (e) {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
callbacks.editQuantity(costItemId, quantity.id, {
|
||||
[key]: input.value,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// if key contains value
|
||||
else if (key.toLowerCase().includes("value")) {
|
||||
const cell = document.createElement("td");
|
||||
const input = document.createElement("input");
|
||||
input.type = "number";
|
||||
input.value = value;
|
||||
cell.appendChild(input);
|
||||
|
||||
input.addEventListener("keydown", function (e) {
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
tr.appendChild(cell);
|
||||
} else {
|
||||
const cell = document.createElement("td");
|
||||
cell.textContent = value;
|
||||
tr.appendChild(cell);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add delete button
|
||||
const deleteButton = CostUI.createButton("Delete", "fa-solid fa-trash");
|
||||
const deleteCell = document.createElement("td");
|
||||
deleteButton.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
callbacks.deleteQuantity(costItemId, quantity.id);
|
||||
tr.remove();
|
||||
});
|
||||
deleteCell.appendChild(deleteButton);
|
||||
tr.appendChild(deleteCell);
|
||||
|
||||
return tr;
|
||||
}
|
||||
|
||||
static createAddManualQuantityButton(costItemId, quantityType, callbacks) {
|
||||
const addButton = CostUI.createButton(
|
||||
"Add Manual Quantity",
|
||||
"fa-solid fa-plus"
|
||||
);
|
||||
|
||||
addButton.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
let type = quantityType;
|
||||
if (!type) {
|
||||
// get quantityType from dropdown
|
||||
const qtoSection = document.getElementById(
|
||||
"manual-quantities-section-" + costItemId
|
||||
);
|
||||
const dropdown = qtoSection.querySelector("select");
|
||||
type = dropdown ? dropdown.value : null;
|
||||
}
|
||||
callbacks.addQuantity(costItemId, type);
|
||||
});
|
||||
|
||||
return addButton;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,6 +295,15 @@ class Cost(bonsai.core.tool.Cost):
|
||||
unit = cls.get_quantity_unit_symbol(quantity)
|
||||
return selected_quantitites, unit
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_assigned_product(cls, cost_item, quantity):
|
||||
assigned_products = cls.get_cost_item_assignments(cost_item, filter_by_type="PRODUCT", is_deep=False)
|
||||
for product in assigned_products:
|
||||
assigned_quantities, _ = cls.get_assigned_quantities(cost_item, product)
|
||||
if quantity in assigned_quantities:
|
||||
return product
|
||||
|
||||
@classmethod
|
||||
def get_products(cls, related_object_type: RELATED_OBJECT_TYPE) -> list[ifcopenshell.entity_instance]:
|
||||
if related_object_type == "PRODUCT":
|
||||
@@ -859,7 +868,33 @@ class Cost(bonsai.core.tool.Cost):
|
||||
bpy.ops.bim.connect_websocket_server(page="costing")
|
||||
cost_schedule_data = cls.create_cost_schedule_json(cost_chedule)
|
||||
tool.Web.send_webui_data(
|
||||
data={"cost_items": cost_schedule_data, "cost_schedule_id": cost_chedule.id()},
|
||||
data={
|
||||
"cost_items": cost_schedule_data,
|
||||
"cost_schedule_id": cost_chedule.id(),
|
||||
"currency": cls.currency()
|
||||
},
|
||||
data_key="cost_items",
|
||||
event="cost_items",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_cost_quantities(cls, cost_item: ifcopenshell.entity_instance) -> dict:
|
||||
results = {
|
||||
"quantities": [],
|
||||
"unit_symbol": None,
|
||||
}
|
||||
if not cost_item:
|
||||
return results
|
||||
results["quantity_type"] = cost_item.CostQuantities[0].is_a() if cost_item.CostQuantities else None
|
||||
unit = ifcopenshell.util.unit.get_property_unit(cost_item.CostQuantities[0], tool.Ifc.get()) if cost_item.CostQuantities else None
|
||||
if unit:
|
||||
results["unit_symbol"] = ifcopenshell.util.unit.get_unit_symbol(unit)
|
||||
for quantity in cost_item.CostQuantities or []:
|
||||
assigned_product = cls.get_assigned_product(cost_item, quantity)
|
||||
info = quantity.get_info()
|
||||
info["fromProduct"] = assigned_product.get_info(recursive=True) if assigned_product else None
|
||||
results["quantities"].append(info)
|
||||
if results["quantity_type"] == "IfcQuantityCount":
|
||||
results["unit_symbol"] = "U"
|
||||
return results
|
||||
|
||||
|
||||
@@ -344,52 +344,6 @@ class Web(bonsai.core.tool.Web):
|
||||
operator_data (dict): A dictionary containing the operator data.
|
||||
"""
|
||||
|
||||
def selection_data(cost_item, elements):
|
||||
print(cost_item, elements)
|
||||
if not elements:
|
||||
return []
|
||||
if not cost_item:
|
||||
return [
|
||||
{
|
||||
"info": {
|
||||
"id": element.id(),
|
||||
"name": element.Name,
|
||||
"class": element.is_a(),
|
||||
"type": get_type(element).Name if get_type(element) else None,
|
||||
},
|
||||
"qtos": get_psets(element, qtos_only=True),
|
||||
}
|
||||
for element in elements or []
|
||||
]
|
||||
data = []
|
||||
for element in elements or []:
|
||||
psets = get_psets(element, qtos_only=True)
|
||||
element_type = get_type(element)
|
||||
quantities, unit = tool.Cost.get_assigned_quantities(cost_item, element)
|
||||
data.append(
|
||||
{
|
||||
"info": {
|
||||
"id": element.id(),
|
||||
"name": element.Name,
|
||||
"class": element.is_a(),
|
||||
"type": element_type.Name if element_type else None,
|
||||
},
|
||||
"qtos": psets,
|
||||
"assigned_quantities": [
|
||||
{
|
||||
"cost_item_id": cost_item.id(),
|
||||
"product_id": element.id(),
|
||||
"name": q.Name,
|
||||
"value": q[3],
|
||||
"type": q.Unit,
|
||||
}
|
||||
for q in quantities or []
|
||||
],
|
||||
"unit": unit,
|
||||
}
|
||||
)
|
||||
return data
|
||||
|
||||
ifc_file = tool.Ifc.get()
|
||||
if operator_data["type"] == "getPredefinedTypes":
|
||||
print("getting predefined types")
|
||||
@@ -425,31 +379,6 @@ class Web(bonsai.core.tool.Web):
|
||||
cost_item = tool.Ifc.get().by_id(operator_data["costItemId"])
|
||||
products = tool.Cost.get_cost_item_products(cost_item, is_deep=True)
|
||||
tool.Spatial.select_products(products, unhide=True)
|
||||
if operator_data["type"] == "enableEditingQuantities":
|
||||
cost_item_id = operator_data["costItemId"]
|
||||
cost_item = ifc_file.by_id(cost_item_id)
|
||||
if not cost_item:
|
||||
print("---> cost item not found")
|
||||
return
|
||||
|
||||
selected_products = list(tool.Spatial.get_selected_products())
|
||||
selected_products_data = selection_data(cost_item, selected_products)
|
||||
|
||||
assigned_products = tool.Cost.get_cost_item_products(cost_item, is_deep=False)
|
||||
assigned_products_data = selection_data(cost_item, assigned_products)
|
||||
|
||||
names = ifcopenshell.util.cost.get_product_quantity_names(selected_products)
|
||||
cls.send_webui_data(
|
||||
data={
|
||||
"selected_products": selected_products_data,
|
||||
"assigned_products": assigned_products_data,
|
||||
"product_quantity_names": names,
|
||||
"cost_item_id": cost_item_id,
|
||||
},
|
||||
data_key="quantities",
|
||||
event="quantities",
|
||||
)
|
||||
|
||||
if operator_data["type"] == "addSummaryCostItem":
|
||||
cost_schedule = ifc_file.by_id(operator_data["costScheduleId"])
|
||||
bonsai.core.cost.add_summary_cost_item(tool.Ifc, tool.Cost, cost_schedule=cost_schedule)
|
||||
@@ -520,12 +449,109 @@ class Web(bonsai.core.tool.Web):
|
||||
prop_name = operator_data["propName"]
|
||||
if prop_name == "count":
|
||||
prop_name = ""
|
||||
print(prop_name)
|
||||
bpy.ops.bim.assign_cost_item_quantity(
|
||||
cost_item=operator_data["costItemId"], related_object_type="PRODUCT", prop_name=prop_name
|
||||
)
|
||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item=tool.Ifc.get().by_id(operator_data["costItemId"]))
|
||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||
if operator_data["type"] == "enableEditingQuantities":
|
||||
cost_item_id = operator_data["costItemId"]
|
||||
cost_item = ifc_file.by_id(cost_item_id)
|
||||
cls.enableEditingCostItemQuantities(cost_item)
|
||||
if operator_data["type"] == "AddCostItemQuantity":
|
||||
cost_item_id = operator_data["costItemId"]
|
||||
cost_item = ifc_file.by_id(cost_item_id)
|
||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item)
|
||||
bpy.ops.bim.add_cost_item_quantity(cost_item=cost_item_id, ifc_class=operator_data["ifcClass"])
|
||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||
cls.enableEditingCostItemQuantities(cost_item)
|
||||
if operator_data["type"] == "editCostItemQuantity":
|
||||
cost_item_id = operator_data["costItemId"]
|
||||
cost_item = ifc_file.by_id(cost_item_id)
|
||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item)
|
||||
physical_quantity = tool.Ifc.get().by_id(operator_data["quantityId"])
|
||||
attributes = operator_data["attributes"]
|
||||
tool.Ifc.run("cost.edit_cost_item_quantity", physical_quantity=physical_quantity, attributes=attributes)
|
||||
tool.Cost.load_cost_item_quantities(ifc_file.by_id(operator_data["costItemId"]))
|
||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||
cls.enableEditingCostItemQuantities(cost_item)
|
||||
if operator_data["type"] == "deleteCostItemQuantity":
|
||||
bpy.ops.bim.remove_cost_item_quantity(cost_item=operator_data["costItemId"], physical_quantity=operator_data["quantityId"])
|
||||
cost_item_id = operator_data["costItemId"]
|
||||
cost_item = ifc_file.by_id(cost_item_id)
|
||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item)
|
||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||
cls.enableEditingCostItemQuantities(cost_item)
|
||||
|
||||
@classmethod
|
||||
def enableEditingCostItemQuantities(cls, cost_item):
|
||||
if not cost_item:
|
||||
return
|
||||
selected_products = list(tool.Spatial.get_selected_products())
|
||||
selected_products_data = cls.selection_data(cost_item, selected_products)
|
||||
|
||||
assigned_products = tool.Cost.get_cost_item_products(cost_item, is_deep=False)
|
||||
assigned_products_data = cls.selection_data(cost_item, assigned_products)
|
||||
|
||||
names = ifcopenshell.util.cost.get_product_quantity_names(selected_products)
|
||||
cls.send_webui_data(
|
||||
data={
|
||||
"selected_products": selected_products_data,
|
||||
"assigned_products": assigned_products_data,
|
||||
"product_quantity_names": names,
|
||||
"cost_item_id": cost_item.id(),
|
||||
"cost_quantities": tool.Cost.get_cost_quantities(cost_item),
|
||||
},
|
||||
data_key="quantities",
|
||||
event="quantities",
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def selection_data(cls, cost_item, elements):
|
||||
if not elements:
|
||||
return []
|
||||
if not cost_item:
|
||||
return [
|
||||
{
|
||||
"info": {
|
||||
"id": element.id(),
|
||||
"name": element.Name,
|
||||
"class": element.is_a(),
|
||||
"type": get_type(element).Name if get_type(element) else None,
|
||||
},
|
||||
"qtos": get_psets(element, qtos_only=True),
|
||||
}
|
||||
for element in elements or []
|
||||
]
|
||||
data = []
|
||||
for element in elements or []:
|
||||
psets = get_psets(element, qtos_only=True)
|
||||
element_type = get_type(element)
|
||||
quantities, unit = tool.Cost.get_assigned_quantities(cost_item, element)
|
||||
data.append(
|
||||
{
|
||||
"info": {
|
||||
"id": element.id(),
|
||||
"name": element.Name,
|
||||
"class": element.is_a(),
|
||||
"type": element_type.Name if element_type else None,
|
||||
},
|
||||
"qtos": psets,
|
||||
"assigned_quantities": [
|
||||
{
|
||||
"cost_item_id": cost_item.id(),
|
||||
"product_id": element.id(),
|
||||
"name": q.Name,
|
||||
"value": q[3],
|
||||
"type": q.Unit,
|
||||
}
|
||||
for q in quantities or []
|
||||
],
|
||||
"unit": unit,
|
||||
}
|
||||
)
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def load_cost_schedule_web_ui(cls, cost_schedule):
|
||||
|
||||
Reference in New Issue
Block a user