mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-19 06:39:13 +00:00
Cost web ui improvements:
- hovering cost item row displays quick actions - clicking the quantity cells and cost cells prompts form to edit values - further style improvements & refactoring
This commit is contained in:
@@ -183,10 +183,10 @@ class BlenderNamespace(socketio.AsyncNamespace):
|
|||||||
blender_messages[sid]["predefined_types"] = data
|
blender_messages[sid]["predefined_types"] = data
|
||||||
await sio.emit("predefined_types", {"blenderId": sid, "data": data}, namespace="/web")
|
await sio.emit("predefined_types", {"blenderId": sid, "data": data}, namespace="/web")
|
||||||
|
|
||||||
async def on_selected_products(self, sid, data):
|
async def on_quantities(self, sid, data):
|
||||||
print(f"Selected products from Blender client {sid}")
|
print(f"Selected products from Blender client {sid}")
|
||||||
blender_messages[sid]["selected_products"] = data
|
blender_messages[sid]["quantities"] = data
|
||||||
await sio.emit("selected_products", {"blenderId": sid, "data": data}, namespace="/web")
|
await sio.emit("quantities", {"blenderId": sid, "data": data}, namespace="/web")
|
||||||
|
|
||||||
async def schedules(request):
|
async def schedules(request):
|
||||||
with open("templates/index.html", "r") as f:
|
with open("templates/index.html", "r") as f:
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ body {
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.form-container {
|
.form-container {
|
||||||
padding: var(--padding-medium);
|
padding: var(--padding-medium);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@@ -155,7 +154,6 @@ body {
|
|||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[id^="selected-products"] .form-container {
|
[id^="selected-products"] .form-container {
|
||||||
height: 50vh;
|
height: 50vh;
|
||||||
}
|
}
|
||||||
@@ -186,7 +184,6 @@ tbody {
|
|||||||
|
|
||||||
td,
|
td,
|
||||||
th {
|
th {
|
||||||
border-left: 1px solid #ddd;
|
|
||||||
border-right: 1px solid #ddd;
|
border-right: 1px solid #ddd;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@@ -304,8 +301,38 @@ form table {
|
|||||||
max-height: 50%;
|
max-height: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.subtotal-row {
|
.subtotal-row {
|
||||||
background-color: var(--button-hover-background);
|
background-color: var(--button-hover-background);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[id^="cost-items"] th:nth-child(1),
|
||||||
|
[id^="cost-items"] td:nth-child(1) {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
[id^="cost-items"] th:not(:nth-child(1)),
|
||||||
|
[id^="cost-items"] td:not(:nth-child(1)) {
|
||||||
|
width: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
[id^="cost-items"] th:last-child,
|
||||||
|
[id^="cost-items"] td:last-child {
|
||||||
|
width: fit-content;
|
||||||
|
min-width: fit-content;
|
||||||
|
max-width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-column {
|
||||||
|
transition: opacity 0.3s ease-in-out;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[id^="cost-items"] tr:hover .actions-column {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clickable-cell:hover {
|
||||||
|
border: 1px solid #28a746;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,53 +28,25 @@ function connectSocket() {
|
|||||||
socket.on("cost_items", handleCostItemsData);
|
socket.on("cost_items", handleCostItemsData);
|
||||||
socket.on("cost_values", handleCostValuesData);
|
socket.on("cost_values", handleCostValuesData);
|
||||||
socket.on("cost_value", handleCostValueData);
|
socket.on("cost_value", handleCostValueData);
|
||||||
socket.on("selected_products", handleSelectedProducts);
|
socket.on("quantities", handleEditQuantities);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSelectedProducts(data) {
|
function handleEditQuantities(data) {
|
||||||
const costItemId = data.data["selected_products"]["cost_item_id"];
|
const costItemId = data.data["quantities"]["cost_item_id"];
|
||||||
const products = data.data["selected_products"]["selected_products"];
|
const products = data.data["quantities"]["selected_products"];
|
||||||
const assigned_products = data.data["selected_products"]["assigned_products"];
|
const assigned_products = data.data["quantities"]["assigned_products"];
|
||||||
const quantityNames =
|
const quantityNames = data.data["quantities"]["product_quantity_names"];
|
||||||
data.data["selected_products"]["product_quantity_names"];
|
|
||||||
const formId = "selected-products-" + costItemId;
|
CostUI.editQuantities({
|
||||||
const form = CostUI.Form({
|
costItemId: costItemId,
|
||||||
id: formId,
|
selectedProducts: products,
|
||||||
name: "Edit Product Assignments for: " + CostUI.getCostItemName(costItemId),
|
assignedProducts: assigned_products,
|
||||||
icon: "fa-solid fa-box",
|
quantityNames: quantityNames,
|
||||||
});
|
|
||||||
const numberOfProducts = CostUI.Text(
|
|
||||||
"Selection basket : " + products.length + " products",
|
|
||||||
"fa-solid fa-cart-shopping",
|
|
||||||
"large"
|
|
||||||
);
|
|
||||||
form.appendChild(numberOfProducts);
|
|
||||||
CostUI.highlightElement(costItemId);
|
|
||||||
const selectedProductsTable = CostUI.createProductTable({
|
|
||||||
form,
|
|
||||||
products,
|
|
||||||
quantityNames,
|
|
||||||
costItemId,
|
|
||||||
callbacks: {
|
callbacks: {
|
||||||
addProductAssignments: addProductAssignments,
|
addProductAssignments: addProductAssignments,
|
||||||
getSelectedProducts: getSelectedProducts,
|
enableEditingQuantities: enableEditingQuantities,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (assigned_products.length > 0) {
|
|
||||||
const assignedProductsText = CostUI.Text(
|
|
||||||
"Assigned Products",
|
|
||||||
"fa-solid fa-solid fa-paperclip",
|
|
||||||
"large"
|
|
||||||
);
|
|
||||||
form.appendChild(assignedProductsText);
|
|
||||||
const assignmentsTable = CostUI.createAssignmentsTable({
|
|
||||||
form,
|
|
||||||
products: assigned_products,
|
|
||||||
quantityNames,
|
|
||||||
costItemId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePredefinedTypes(data) {
|
function handlePredefinedTypes(data) {
|
||||||
@@ -322,9 +294,6 @@ function handleCostSchedulesData(data) {
|
|||||||
if (costSchedules.length === 0) {
|
if (costSchedules.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const currency = data.data["cost_schedules"]["currency"]
|
|
||||||
? data.data["cost_schedules"]["currency"]["name"]
|
|
||||||
: "Undefined";
|
|
||||||
const costScheduleDiv = document.getElementById("cost-schedules");
|
const costScheduleDiv = document.getElementById("cost-schedules");
|
||||||
costScheduleDiv.innerHTML = "";
|
costScheduleDiv.innerHTML = "";
|
||||||
costSchedules.forEach((costSchedule) => {
|
costSchedules.forEach((costSchedule) => {
|
||||||
@@ -368,8 +337,13 @@ function handleCostItemsData(data) {
|
|||||||
loadedSchedules[data.blenderId] = costScheduleId;
|
loadedSchedules[data.blenderId] = costScheduleId;
|
||||||
CostUI.highlightElement("schedule-" + costScheduleId);
|
CostUI.highlightElement("schedule-" + costScheduleId);
|
||||||
|
|
||||||
|
const currency = data.data["cost_items"]["currency"]
|
||||||
|
? data.data["cost_items"]["currency"]["name"]
|
||||||
|
: "Undefined";
|
||||||
|
|
||||||
CostUI.createCostSchedule({
|
CostUI.createCostSchedule({
|
||||||
data: data.data["cost_items"]["cost_items"],
|
costItems: data.data["cost_items"]["cost_items"],
|
||||||
|
currency: currency,
|
||||||
costScheduleId: costScheduleId,
|
costScheduleId: costScheduleId,
|
||||||
blenderID: data.blenderId,
|
blenderID: data.blenderId,
|
||||||
callbacks: {
|
callbacks: {
|
||||||
@@ -380,13 +354,13 @@ function handleCostItemsData(data) {
|
|||||||
editCostItemName: editCostItemName,
|
editCostItemName: editCostItemName,
|
||||||
enableEditingCostValues: enableEditingCostValues,
|
enableEditingCostValues: enableEditingCostValues,
|
||||||
addSummaryCostItem: addSummaryCostItem,
|
addSummaryCostItem: addSummaryCostItem,
|
||||||
getSelectedProducts: getSelectedProducts,
|
enableEditingQuantities: enableEditingQuantities,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSelectedProducts(costItemId) {
|
function enableEditingQuantities(costItemId) {
|
||||||
executeOperator({ type: "getSelectedProducts", costItemId: costItemId });
|
executeOperator({ type: "enableEditingQuantities", costItemId: costItemId });
|
||||||
}
|
}
|
||||||
|
|
||||||
function duplicateCostItem(costItemId) {
|
function duplicateCostItem(costItemId) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export class CostUI {
|
|||||||
static removeCostSchedule(id) {
|
static removeCostSchedule(id) {
|
||||||
document.getElementById("cost-items-" + id).remove();
|
document.getElementById("cost-items-" + id).remove();
|
||||||
}
|
}
|
||||||
static createTable(id, callbacks) {
|
static createCostTable(id, currency, callbacks) {
|
||||||
CostUI.isCostScheduleLoaded(id) ? CostUI.removeCostSchedule(id) : null;
|
CostUI.isCostScheduleLoaded(id) ? CostUI.removeCostSchedule(id) : null;
|
||||||
|
|
||||||
const table = document.createElement("table");
|
const table = document.createElement("table");
|
||||||
@@ -21,9 +21,9 @@ export class CostUI {
|
|||||||
"Name",
|
"Name",
|
||||||
"Quantity",
|
"Quantity",
|
||||||
"Unit",
|
"Unit",
|
||||||
"Cost",
|
"Cost (" + currency + ")",
|
||||||
"Total Cost",
|
"Total Cost (" + currency + ")",
|
||||||
"Action",
|
"Actions",
|
||||||
];
|
];
|
||||||
const thead = document.createElement("thead");
|
const thead = document.createElement("thead");
|
||||||
const tr = document.createElement("tr");
|
const tr = document.createElement("tr");
|
||||||
@@ -47,7 +47,6 @@ export class CostUI {
|
|||||||
table.appendChild(tbody);
|
table.appendChild(tbody);
|
||||||
document.getElementById("cost-items").appendChild(table);
|
document.getElementById("cost-items").appendChild(table);
|
||||||
|
|
||||||
CostUI.addTableStyles(id);
|
|
||||||
CostUI.createContextMenu(callbacks);
|
CostUI.createContextMenu(callbacks);
|
||||||
table.get_blender_id = function () {
|
table.get_blender_id = function () {
|
||||||
return this.getAttribute("id").split("-")[2];
|
return this.getAttribute("id").split("-")[2];
|
||||||
@@ -56,20 +55,12 @@ export class CostUI {
|
|||||||
return [table, tbody];
|
return [table, tbody];
|
||||||
}
|
}
|
||||||
|
|
||||||
static addTableStyles(id) {
|
static deleteCostItem(costItemId, callback) {
|
||||||
const style = document.createElement("style");
|
const costItemRow = document.getElementById(costItemId);
|
||||||
style.textContent = `
|
let expandedState = JSON.parse(localStorage.getItem("expandedState")) || {};
|
||||||
#cost-items-${id} th:nth-child(1),
|
expandedState = CostUI.deleteCostItemRow(costItemRow, expandedState);
|
||||||
#cost-items-${id} td:nth-child(1) {
|
localStorage.setItem("expandedState", JSON.stringify(expandedState));
|
||||||
width: auto;
|
callback(costItemId);
|
||||||
}
|
|
||||||
#cost-items-${id} th:not(:nth-child(1)),
|
|
||||||
#cost-items-${id} td:not(:nth-child(1)) {
|
|
||||||
width: 100px; /* Set a fixed width for other columns */
|
|
||||||
}
|
|
||||||
|
|
||||||
`;
|
|
||||||
document.head.appendChild(style);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static createContextMenu(callbacks) {
|
static createContextMenu(callbacks) {
|
||||||
@@ -139,21 +130,17 @@ export class CostUI {
|
|||||||
addButton.dataset.listenerAdded = "true";
|
addButton.dataset.listenerAdded = "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteCostItem = document.getElementById("delete-cost-item");
|
const deleteCostItemButton = document.getElementById("delete-cost-item");
|
||||||
if (deleteCostItem && !deleteCostItem.hasListener) {
|
if (deleteCostItemButton && !deleteCostItemButton.hasListener) {
|
||||||
deleteCostItem.addEventListener("click", function () {
|
deleteCostItemButton.addEventListener("click", function () {
|
||||||
const targetRow = document.getElementById("context-menu").targetRow;
|
const targetRow = document.getElementById("context-menu").targetRow;
|
||||||
if (targetRow) {
|
if (targetRow) {
|
||||||
const costItemId = parseInt(targetRow.getAttribute("id"));
|
const costItemId = parseInt(targetRow.getAttribute("id"));
|
||||||
let expandedState =
|
CostUI.deleteCostItem(costItemId, callbacks.deleteCostItem);
|
||||||
JSON.parse(localStorage.getItem("expandedState")) || {};
|
|
||||||
expandedState = CostUI.deleteCostItemRow(targetRow, expandedState);
|
|
||||||
localStorage.setItem("expandedState", JSON.stringify(expandedState));
|
|
||||||
callbacks.deleteCostItem(costItemId);
|
|
||||||
}
|
}
|
||||||
document.getElementById("context-menu").style.display = "none";
|
document.getElementById("context-menu").style.display = "none";
|
||||||
});
|
});
|
||||||
deleteCostItem.hasListener = true;
|
deleteCostItemButton.hasListener = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const duplicateButton = document.getElementById("duplicate-button");
|
const duplicateButton = document.getElementById("duplicate-button");
|
||||||
@@ -175,7 +162,7 @@ export class CostUI {
|
|||||||
const targetRow = document.getElementById("context-menu").targetRow;
|
const targetRow = document.getElementById("context-menu").targetRow;
|
||||||
if (targetRow) {
|
if (targetRow) {
|
||||||
const costItemId = parseInt(targetRow.getAttribute("id"));
|
const costItemId = parseInt(targetRow.getAttribute("id"));
|
||||||
callbacks.getSelectedProducts(costItemId);
|
callbacks.enableEditingQuantities(costItemId);
|
||||||
}
|
}
|
||||||
document.getElementById("context-menu").style.display = "none";
|
document.getElementById("context-menu").style.display = "none";
|
||||||
});
|
});
|
||||||
@@ -192,7 +179,7 @@ export class CostUI {
|
|||||||
|
|
||||||
document
|
document
|
||||||
.getElementById("cost-items")
|
.getElementById("cost-items")
|
||||||
.addEventListener("dblclick", function (event) {
|
.addEventListener("click", function (event) {
|
||||||
const targetRow = event.target.closest("tr");
|
const targetRow = event.target.closest("tr");
|
||||||
const targetCell = event.target.closest("td");
|
const targetCell = event.target.closest("td");
|
||||||
if (targetRow && targetCell) {
|
if (targetRow && targetCell) {
|
||||||
@@ -202,11 +189,16 @@ export class CostUI {
|
|||||||
const costItemId = parseInt(targetRow.getAttribute("id"));
|
const costItemId = parseInt(targetRow.getAttribute("id"));
|
||||||
const columnName = getColumnNames("cost-items")[columnIndex];
|
const columnName = getColumnNames("cost-items")[columnIndex];
|
||||||
|
|
||||||
if (columnName === "Cost") {
|
if (columnName.includes("Cost") && !columnName.includes("Total")) {
|
||||||
callbacks.enableEditingCostValues
|
callbacks.enableEditingCostValues
|
||||||
? callbacks.enableEditingCostValues(costItemId)
|
? callbacks.enableEditingCostValues(costItemId)
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
if (columnName === "Quantity") {
|
||||||
|
callbacks.enableEditingQuantities
|
||||||
|
? callbacks.enableEditingQuantities(costItemId)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -363,13 +355,18 @@ export class CostUI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static createCostSchedule({
|
static createCostSchedule({
|
||||||
data,
|
costItems,
|
||||||
|
currency,
|
||||||
costScheduleId,
|
costScheduleId,
|
||||||
blenderID,
|
blenderID,
|
||||||
callbacks = {},
|
callbacks = {},
|
||||||
}) {
|
}) {
|
||||||
const [table, tbody] = CostUI.createTable(blenderID, callbacks);
|
const [table, tbody] = CostUI.createCostTable(
|
||||||
if (data.length === 0) {
|
blenderID,
|
||||||
|
currency,
|
||||||
|
callbacks
|
||||||
|
);
|
||||||
|
if (costItems.length === 0) {
|
||||||
const tr = document.createElement("tr");
|
const tr = document.createElement("tr");
|
||||||
const td = document.createElement("td");
|
const td = document.createElement("td");
|
||||||
td.colSpan = 6;
|
td.colSpan = 6;
|
||||||
@@ -387,24 +384,29 @@ export class CostUI {
|
|||||||
td.appendChild(addSummaryCostItemButton);
|
td.appendChild(addSummaryCostItemButton);
|
||||||
addSummaryCostItemButton.classList.add("action-button");
|
addSummaryCostItemButton.classList.add("action-button");
|
||||||
} else {
|
} else {
|
||||||
CostUI.createCostItem(data, tbody, 0, null, callbacks);
|
CostUI.createCostTree(costItems, tbody, 0, null, callbacks);
|
||||||
CostUI.applyExpandedState();
|
CostUI.applyExpandedState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static createCostItem(
|
static createCostTree(
|
||||||
data,
|
costItems,
|
||||||
container,
|
container,
|
||||||
nestingLevel = 0,
|
nestingLevel = 0,
|
||||||
parentID = null,
|
parentID = null,
|
||||||
callbacks = {}
|
callbacks = {}
|
||||||
) {
|
) {
|
||||||
data.forEach((costItem) => {
|
costItems.forEach((costItem) => {
|
||||||
const row = CostUI.createRow(costItem, nestingLevel, parentID, callbacks);
|
const row = CostUI.addCostItemRow(
|
||||||
|
costItem,
|
||||||
|
nestingLevel,
|
||||||
|
parentID,
|
||||||
|
callbacks
|
||||||
|
);
|
||||||
container.appendChild(row);
|
container.appendChild(row);
|
||||||
|
|
||||||
if (costItem.is_nested_by && costItem.is_nested_by.length > 0) {
|
if (costItem.is_nested_by && costItem.is_nested_by.length > 0) {
|
||||||
CostUI.createCostItem(
|
CostUI.createCostTree(
|
||||||
costItem.is_nested_by,
|
costItem.is_nested_by,
|
||||||
container,
|
container,
|
||||||
nestingLevel + 1,
|
nestingLevel + 1,
|
||||||
@@ -415,7 +417,7 @@ export class CostUI {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static createRow(costItem, nestingLevel, parentID, callbacks = {}) {
|
static addCostItemRow(costItem, nestingLevel, parentID, callbacks = {}) {
|
||||||
const totalQuantity = costItem.TotalCostQuantity
|
const totalQuantity = costItem.TotalCostQuantity
|
||||||
? parseFloat(costItem.TotalCostQuantity).toFixed(2)
|
? parseFloat(costItem.TotalCostQuantity).toFixed(2)
|
||||||
: "-";
|
: "-";
|
||||||
@@ -431,20 +433,21 @@ export class CostUI {
|
|||||||
callbacks
|
callbacks
|
||||||
);
|
);
|
||||||
const totalCostQuantityCell = CostUI.createTableCell(totalQuantity);
|
const totalCostQuantityCell = CostUI.createTableCell(totalQuantity);
|
||||||
|
totalCostQuantityCell.classList.add("clickable-cell");
|
||||||
const unitSymbolCell = CostUI.createTableCell(costItem.UnitSymbol);
|
const unitSymbolCell = CostUI.createTableCell(costItem.UnitSymbol);
|
||||||
const totalAppliedValueCell = CostUI.createTableCell(appliedValue);
|
const totalAppliedValueCell = CostUI.createTableCell(appliedValue);
|
||||||
|
totalAppliedValueCell.classList.add("clickable-cell");
|
||||||
const totalCostCell = CostUI.createTotalCostCell(costItem);
|
const totalCostCell = CostUI.createTotalCostCell(costItem);
|
||||||
const flexContainerCell = CostUI.createFlexContainerCell(
|
const actionsCell = CostUI.costItemActions(costItem, callbacks);
|
||||||
costItem,
|
|
||||||
callbacks
|
actionsCell.classList.add("actions-column");
|
||||||
);
|
|
||||||
|
|
||||||
row.appendChild(nameCell);
|
row.appendChild(nameCell);
|
||||||
row.appendChild(totalCostQuantityCell);
|
row.appendChild(totalCostQuantityCell);
|
||||||
row.appendChild(unitSymbolCell);
|
row.appendChild(unitSymbolCell);
|
||||||
row.appendChild(totalAppliedValueCell);
|
row.appendChild(totalAppliedValueCell);
|
||||||
row.appendChild(totalCostCell);
|
row.appendChild(totalCostCell);
|
||||||
row.appendChild(flexContainerCell);
|
row.appendChild(actionsCell);
|
||||||
|
|
||||||
row.get_id = function () {
|
row.get_id = function () {
|
||||||
return this.getAttribute("id");
|
return this.getAttribute("id");
|
||||||
@@ -636,30 +639,92 @@ export class CostUI {
|
|||||||
return totalCostCell;
|
return totalCostCell;
|
||||||
}
|
}
|
||||||
|
|
||||||
static createFlexContainerCell(costItem, callbacks) {
|
static costItemActions(costItem, callbacks) {
|
||||||
const divFlex = document.createElement("div");
|
const divFlex = document.createElement("div");
|
||||||
divFlex.classList.add("row-container");
|
divFlex.classList.add("row-container");
|
||||||
const selectButton = CostUI.createSelectButton(costItem, callbacks);
|
const addCostItem = CostUI.addCostItemButton(
|
||||||
divFlex.appendChild(selectButton);
|
costItem.id,
|
||||||
|
callbacks.addCostItem
|
||||||
|
);
|
||||||
|
const selectButton = CostUI.createSelectButton(
|
||||||
|
costItem.id,
|
||||||
|
callbacks.selectAssignedElements
|
||||||
|
);
|
||||||
|
const deleteButton = CostUI.deleteCostItemButton(
|
||||||
|
costItem.id,
|
||||||
|
callbacks.deleteCostItem
|
||||||
|
);
|
||||||
|
const duplicateButton = CostUI.duplicateCostItemButton(
|
||||||
|
costItem.id,
|
||||||
|
callbacks.duplicateCostItem
|
||||||
|
);
|
||||||
|
|
||||||
const flexContainerCell = document.createElement("td");
|
[addCostItem, duplicateButton, selectButton, deleteButton].forEach(
|
||||||
flexContainerCell.appendChild(divFlex);
|
(button) => {
|
||||||
return flexContainerCell;
|
divFlex.appendChild(button);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const actionsCell = document.createElement("td");
|
||||||
|
actionsCell.appendChild(divFlex);
|
||||||
|
return actionsCell;
|
||||||
}
|
}
|
||||||
|
|
||||||
static createSelectButton(costItem, callbacks) {
|
static duplicateCostItemButton(costItemId, duplicateCostItem) {
|
||||||
const selectButton = document.createElement("button");
|
const duplicateButton = CostUI.createButton(
|
||||||
selectButton.classList.add("action-button");
|
"Duplicate",
|
||||||
selectButton.textContent = "Select";
|
"fa-solid fa-copy"
|
||||||
selectButton.addEventListener("click", function (e) {
|
);
|
||||||
e.stopPropagation();
|
duplicateButton.addEventListener(
|
||||||
callbacks.selectAssignedElements
|
"click",
|
||||||
? callbacks.selectAssignedElements(costItem.id)
|
duplicateCostItem.bind(null, costItemId)
|
||||||
: null;
|
);
|
||||||
});
|
return duplicateButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
static addCostItemButton(costItemId, addCostItem) {
|
||||||
|
const addCostItemButton = CostUI.createButton(
|
||||||
|
"Add Sub-Cost",
|
||||||
|
"fa-solid fa-plus"
|
||||||
|
);
|
||||||
|
addCostItemButton.addEventListener(
|
||||||
|
"click",
|
||||||
|
addCostItem.bind(null, costItemId)
|
||||||
|
);
|
||||||
|
return addCostItemButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
static createSelectButton(costItemId, selectAssignedElements) {
|
||||||
|
const selectButton = CostUI.createButton(
|
||||||
|
"Select",
|
||||||
|
"fa-solid fa-arrow-pointer"
|
||||||
|
);
|
||||||
|
selectButton.addEventListener(
|
||||||
|
"click",
|
||||||
|
selectAssignedElements.bind(null, costItemId)
|
||||||
|
);
|
||||||
return selectButton;
|
return selectButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static deleteCostItemButton(costItemId, deleteCostItem) {
|
||||||
|
const deleteButton = CostUI.createButton("Delete", "fa-solid fa-trash");
|
||||||
|
deleteButton.addEventListener(
|
||||||
|
"click",
|
||||||
|
CostUI.deleteCostItem.bind(null, costItemId, deleteCostItem)
|
||||||
|
);
|
||||||
|
return deleteButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
static createButton(text, icon) {
|
||||||
|
const button = document.createElement("button");
|
||||||
|
!icon ? (button.textContent = text) : null;
|
||||||
|
icon
|
||||||
|
? button.classList.add("action-button", ...icon.split(" "))
|
||||||
|
: button.classList.add("action-button");
|
||||||
|
button.dataset.tooltip = text;
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
static highlightElement(id) {
|
static highlightElement(id) {
|
||||||
const element = document.getElementById(id);
|
const element = document.getElementById(id);
|
||||||
if (element) {
|
if (element) {
|
||||||
@@ -1479,6 +1544,7 @@ export class CostUI {
|
|||||||
|
|
||||||
const picker = CostUI.createColorPicker();
|
const picker = CostUI.createColorPicker();
|
||||||
const tableFontSize = CostUI.createFontSizePicker();
|
const tableFontSize = CostUI.createFontSizePicker();
|
||||||
|
const currencyPicker = CostUI.createCurrencyPicker();
|
||||||
settingsMenu.appendChild(picker);
|
settingsMenu.appendChild(picker);
|
||||||
settingsMenu.appendChild(tableFontSize);
|
settingsMenu.appendChild(tableFontSize);
|
||||||
document.getElementById("settings-menu").style.display = "none";
|
document.getElementById("settings-menu").style.display = "none";
|
||||||
@@ -1521,6 +1587,32 @@ export class CostUI {
|
|||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static createCurrencyPicker() {
|
||||||
|
const currency = CostUI.getCurrency();
|
||||||
|
const div = document.createElement("div");
|
||||||
|
const currencyText = document.createElement("p");
|
||||||
|
currencyText.textContent = "Select a currency for the table:";
|
||||||
|
div.appendChild(currencyText);
|
||||||
|
const currencyPicker = document.createElement("input");
|
||||||
|
currencyPicker.type = "text";
|
||||||
|
currencyPicker.id = "currency-picker";
|
||||||
|
currencyPicker.value = currency;
|
||||||
|
div.appendChild(currencyPicker);
|
||||||
|
currencyPicker.addEventListener("input", function () {
|
||||||
|
const currency = currencyPicker.value;
|
||||||
|
CostUI.saveCurrency(currency);
|
||||||
|
});
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
|
||||||
|
static saveCurrency(currency) {
|
||||||
|
localStorage.setItem("tableCurrency", currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getCurrency() {
|
||||||
|
return localStorage.getItem("tableCurrency");
|
||||||
|
}
|
||||||
|
|
||||||
static saveFontSize(fontSize) {
|
static saveFontSize(fontSize) {
|
||||||
localStorage.setItem("tableFontSize", fontSize);
|
localStorage.setItem("tableFontSize", fontSize);
|
||||||
}
|
}
|
||||||
@@ -1637,4 +1729,50 @@ export class CostUI {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static editQuantities({
|
||||||
|
costItemId,
|
||||||
|
selectedProducts,
|
||||||
|
quantityNames,
|
||||||
|
assignedProducts,
|
||||||
|
callbacks,
|
||||||
|
}) {
|
||||||
|
const formId = "selected-products-" + costItemId;
|
||||||
|
const form = CostUI.Form({
|
||||||
|
id: formId,
|
||||||
|
name:
|
||||||
|
"Edit Product Assignments 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,
|
||||||
|
products: selectedProducts,
|
||||||
|
quantityNames,
|
||||||
|
costItemId,
|
||||||
|
callbacks,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (assignedProducts.length > 0) {
|
||||||
|
const assignedProductsText = CostUI.Text(
|
||||||
|
"Assigned Products",
|
||||||
|
"fa-solid fa-solid fa-paperclip",
|
||||||
|
"large"
|
||||||
|
);
|
||||||
|
form.appendChild(assignedProductsText);
|
||||||
|
const assignmentsTable = CostUI.createAssignmentsTable({
|
||||||
|
form,
|
||||||
|
products: assignedProducts,
|
||||||
|
quantityNames,
|
||||||
|
costItemId,
|
||||||
|
callbacks,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -425,7 +425,7 @@ class Web(bonsai.core.tool.Web):
|
|||||||
cost_item = tool.Ifc.get().by_id(operator_data["costItemId"])
|
cost_item = tool.Ifc.get().by_id(operator_data["costItemId"])
|
||||||
products = tool.Cost.get_cost_item_products(cost_item, is_deep=True)
|
products = tool.Cost.get_cost_item_products(cost_item, is_deep=True)
|
||||||
tool.Spatial.select_products(products, unhide=True)
|
tool.Spatial.select_products(products, unhide=True)
|
||||||
if operator_data["type"] == "getSelectedProducts":
|
if operator_data["type"] == "enableEditingQuantities":
|
||||||
cost_item_id = operator_data["costItemId"]
|
cost_item_id = operator_data["costItemId"]
|
||||||
cost_item = ifc_file.by_id(cost_item_id)
|
cost_item = ifc_file.by_id(cost_item_id)
|
||||||
if not cost_item:
|
if not cost_item:
|
||||||
@@ -446,8 +446,8 @@ class Web(bonsai.core.tool.Web):
|
|||||||
"product_quantity_names": names,
|
"product_quantity_names": names,
|
||||||
"cost_item_id": cost_item_id,
|
"cost_item_id": cost_item_id,
|
||||||
},
|
},
|
||||||
data_key="selected_products",
|
data_key="quantities",
|
||||||
event="selected_products",
|
event="quantities",
|
||||||
)
|
)
|
||||||
|
|
||||||
if operator_data["type"] == "addSummaryCostItem":
|
if operator_data["type"] == "addSummaryCostItem":
|
||||||
@@ -531,7 +531,11 @@ class Web(bonsai.core.tool.Web):
|
|||||||
def load_cost_schedule_web_ui(cls, cost_schedule):
|
def load_cost_schedule_web_ui(cls, cost_schedule):
|
||||||
json_data = tool.Cost.create_cost_schedule_json(cost_schedule)
|
json_data = tool.Cost.create_cost_schedule_json(cost_schedule)
|
||||||
cls.send_webui_data(
|
cls.send_webui_data(
|
||||||
data={"cost_items": json_data, "cost_schedule_id": cost_schedule.id()},
|
data={
|
||||||
|
"cost_items": json_data,
|
||||||
|
"cost_schedule_id": cost_schedule.id(),
|
||||||
|
"currency": tool.Cost.currency(),
|
||||||
|
},
|
||||||
data_key="cost_items",
|
data_key="cost_items",
|
||||||
event="cost_items",
|
event="cost_items",
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user