mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-20 23:36:20 +00:00
cost module web ui:
- add setting options to hide/show columns - Cost classification column - New Cost Classification Form to display active classification library, add/delete classification references
This commit is contained in:
@@ -71,7 +71,7 @@ class WebNamespace(socketio.AsyncNamespace):
|
|||||||
await self.emit("gantt_data", {"blenderId": blenderId, "data": messages["gantt_data"]}, room=sid)
|
await self.emit("gantt_data", {"blenderId": blenderId, "data": messages["gantt_data"]}, room=sid)
|
||||||
if "demo_data" in messages:
|
if "demo_data" in messages:
|
||||||
await self.emit("demo_data", {"blenderId": blenderId, "data": messages["demo_data"]}, room=sid)
|
await self.emit("demo_data", {"blenderId": blenderId, "data": messages["demo_data"]}, room=sid)
|
||||||
if 'cost_items' in messages:
|
if "cost_items" in messages:
|
||||||
await self.emit("cost_items", {"blenderId": blenderId, "data": messages["cost_items"]}, room=sid)
|
await self.emit("cost_items", {"blenderId": blenderId, "data": messages["cost_items"]}, room=sid)
|
||||||
|
|
||||||
async def process_svg(self, file_path):
|
async def process_svg(self, file_path):
|
||||||
@@ -189,12 +189,19 @@ class BlenderNamespace(socketio.AsyncNamespace):
|
|||||||
blender_messages[sid]["quantities"] = data
|
blender_messages[sid]["quantities"] = data
|
||||||
await sio.emit("quantities", {"blenderId": sid, "data": data}, namespace="/web")
|
await sio.emit("quantities", {"blenderId": sid, "data": data}, namespace="/web")
|
||||||
|
|
||||||
|
async def on_classification(self, sid, data):
|
||||||
|
print(f"Classification from Blender client {sid}")
|
||||||
|
blender_messages[sid]["classification"] = data
|
||||||
|
await sio.emit("classification", {"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:
|
||||||
template = f.read()
|
template = f.read()
|
||||||
html_content = pystache.render(template, {"port": sio_port, "version": bonsai_version})
|
html_content = pystache.render(template, {"port": sio_port, "version": bonsai_version})
|
||||||
return web.Response(text=html_content, content_type="text/html")
|
return web.Response(text=html_content, content_type="text/html")
|
||||||
|
|
||||||
|
|
||||||
async def costing(request):
|
async def costing(request):
|
||||||
with open("templates/costing.html", "r") as f:
|
with open("templates/costing.html", "r") as f:
|
||||||
template = f.read()
|
template = f.read()
|
||||||
|
|||||||
@@ -192,7 +192,6 @@ body {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
border: 2px solid #254e3c;
|
border: 2px solid #254e3c;
|
||||||
/* shadow to the top */
|
|
||||||
box-shadow: 0 -5px 5px -0 #254e3c;
|
box-shadow: 0 -5px 5px -0 #254e3c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,10 +306,9 @@ form input, form select {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 10px;
|
line-height: 10px;
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
max-height: 30px;
|
max-height: 30px;
|
||||||
max-width: 250px;
|
max-width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,3 +419,80 @@ td {
|
|||||||
i {
|
i {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.classificationView {
|
||||||
|
border-left: 5px solid #28a745;
|
||||||
|
min-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 5px;
|
||||||
|
max-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
border-color: #28a745;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 3px #28a745;
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectedClassificationText {
|
||||||
|
color: #28a745;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-button {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-button:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
background-color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.classification-cell{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.classification{
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#column-selector {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
max-width: 100%;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
height: 10vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#column-selector label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#column-selector input[type="checkbox"] {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#column-selector label:hover {
|
||||||
|
background-color: var(--highlight-over);
|
||||||
|
}
|
||||||
|
|
||||||
|
#column-selector label:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
@@ -223,6 +223,7 @@ button:hover {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-container {
|
.column-container {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ function connectSocket() {
|
|||||||
socket.on("cost_values", handleCostValuesData);
|
socket.on("cost_values", handleCostValuesData);
|
||||||
socket.on("cost_value", handleCostValueData);
|
socket.on("cost_value", handleCostValueData);
|
||||||
socket.on("quantities", handleEditQuantities);
|
socket.on("quantities", handleEditQuantities);
|
||||||
|
socket.on("classification", handleClassificationData);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleEditQuantities(data) {
|
function handleEditQuantities(data) {
|
||||||
@@ -324,6 +325,7 @@ function handleCostItemsData(data) {
|
|||||||
addSummaryCostItem: addSummaryCostItem,
|
addSummaryCostItem: addSummaryCostItem,
|
||||||
enableEditingQuantities: enableEditingQuantities,
|
enableEditingQuantities: enableEditingQuantities,
|
||||||
addSumCostValue: addSumCostValue,
|
addSumCostValue: addSumCostValue,
|
||||||
|
enableEditingClassification: enableEditingClassification,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -371,6 +373,55 @@ function enableEditingCostValues(costItemId) {
|
|||||||
executeOperator({ type: "enableEditingCostValues", costItemId: costItemId });
|
executeOperator({ type: "enableEditingCostValues", costItemId: costItemId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function enableEditingClassification(costItemId) {
|
||||||
|
executeOperator({
|
||||||
|
type: "enableEditingClassification",
|
||||||
|
costItemId: costItemId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeClassificationReference(costItemId, classificationId) {
|
||||||
|
executeOperator({
|
||||||
|
type: "removeClassificationReference",
|
||||||
|
costItemId: costItemId,
|
||||||
|
classificationId: classificationId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addClassificationReference(
|
||||||
|
costItemId,
|
||||||
|
classificationName,
|
||||||
|
classificationId
|
||||||
|
) {
|
||||||
|
executeOperator({
|
||||||
|
type: "addClassificationReference",
|
||||||
|
costItemId: costItemId,
|
||||||
|
classificationName: classificationName,
|
||||||
|
classificationId: classificationId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClassificationData(data) {
|
||||||
|
const costClassifications =
|
||||||
|
data.data["classification"]["cost_classifications"];
|
||||||
|
const classificationElements =
|
||||||
|
data.data["classification"]["classification_data"];
|
||||||
|
const classificationName = data.data["classification"]["classification_name"];
|
||||||
|
const costItemId = data.data["classification"]["cost_item_id"];
|
||||||
|
|
||||||
|
CostUI.createCostClassificationWindow({
|
||||||
|
classificationName,
|
||||||
|
classificationElements,
|
||||||
|
costClassifications,
|
||||||
|
costItemId,
|
||||||
|
callbacks: {
|
||||||
|
enableEditingClassification: enableEditingClassification,
|
||||||
|
removeClassificationReference: removeClassificationReference,
|
||||||
|
addClassificationReference: addClassificationReference,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function enableEditingQuantities(costItemId) {
|
function enableEditingQuantities(costItemId) {
|
||||||
executeOperator({ type: "enableEditingQuantities", costItemId: costItemId });
|
executeOperator({ type: "enableEditingQuantities", costItemId: costItemId });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,97 +1,6 @@
|
|||||||
export class CostUI {
|
export class CostUI {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
static isCostScheduleLoaded(id) {
|
|
||||||
const existingTable = document.getElementById("cost-items-" + id);
|
|
||||||
return existingTable !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static removeCostSchedule(id) {
|
|
||||||
const table = document.getElementById("cost-items-" + id);
|
|
||||||
table ? table.parentElement.remove() : null;
|
|
||||||
}
|
|
||||||
static createCostTable({ costSchedule, currency, callbacks }) {
|
|
||||||
const isScheduleOfRates = costSchedule.PredefinedType === "SCHEDULEOFRATES";
|
|
||||||
let tableWrapper;
|
|
||||||
const id = costSchedule.id;
|
|
||||||
if (CostUI.isCostScheduleLoaded(id)) {
|
|
||||||
const table = document.getElementById("cost-items-" + id);
|
|
||||||
tableWrapper = table.parentElement;
|
|
||||||
table.remove();
|
|
||||||
} else {
|
|
||||||
tableWrapper = document.createElement("div");
|
|
||||||
tableWrapper.classList.add("table-wrapper");
|
|
||||||
document.getElementById("cost-items").appendChild(tableWrapper);
|
|
||||||
const tableHeader = document.createElement("div");
|
|
||||||
tableHeader.classList.add("form-header");
|
|
||||||
const text = CostUI.Text(
|
|
||||||
costSchedule.Name,
|
|
||||||
"fa-solid fa-money-bill-wave",
|
|
||||||
"x-large"
|
|
||||||
);
|
|
||||||
|
|
||||||
const callback = () => {
|
|
||||||
tableWrapper.remove();
|
|
||||||
CostUI.unhighlightElement("schedule-" + id);
|
|
||||||
};
|
|
||||||
let closeButton = CostUI.createCloseButton(callback);
|
|
||||||
tableHeader.appendChild(text);
|
|
||||||
tableHeader.appendChild(closeButton);
|
|
||||||
tableWrapper.appendChild(tableHeader);
|
|
||||||
}
|
|
||||||
const table = document.createElement("table");
|
|
||||||
table.id = "cost-items-" + id;
|
|
||||||
const tbody = document.createElement("tbody");
|
|
||||||
const columnHeaders = [
|
|
||||||
"ID",
|
|
||||||
"Name",
|
|
||||||
"Quantity",
|
|
||||||
"Unit",
|
|
||||||
"Cost (" + currency + ")",
|
|
||||||
"Total Cost (" + currency + ")",
|
|
||||||
"Actions",
|
|
||||||
];
|
|
||||||
if (isScheduleOfRates) {
|
|
||||||
columnHeaders.splice(2, 1);
|
|
||||||
columnHeaders[3] = "Rate (" + currency + ")";
|
|
||||||
columnHeaders[4] = "Total Rate (" + currency + ")";
|
|
||||||
}
|
|
||||||
const thead = document.createElement("thead");
|
|
||||||
const tr = document.createElement("tr");
|
|
||||||
thead.appendChild(tr);
|
|
||||||
|
|
||||||
for (let i = 0; i < columnHeaders.length; i++) {
|
|
||||||
const th = document.createElement("th");
|
|
||||||
th.textContent = columnHeaders[i];
|
|
||||||
tr.appendChild(th);
|
|
||||||
|
|
||||||
if (i < columnHeaders.length - 1) {
|
|
||||||
const resizer = document.createElement("div");
|
|
||||||
resizer.classList.add("resizer");
|
|
||||||
th.appendChild(resizer);
|
|
||||||
CostUI.addResizer(resizer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
table.appendChild(thead);
|
|
||||||
table.appendChild(tbody);
|
|
||||||
tableWrapper.appendChild(table);
|
|
||||||
|
|
||||||
CostUI.createContextMenu(callbacks);
|
|
||||||
table.get_blender_id = function () {
|
|
||||||
return this.getAttribute("id").split("-")[2];
|
|
||||||
};
|
|
||||||
|
|
||||||
return [table, tbody];
|
|
||||||
}
|
|
||||||
|
|
||||||
static deleteCostItem(costItemId) {
|
|
||||||
const costItemRow = CostUI.getCostItemRow(costItemId);
|
|
||||||
let expandedState = JSON.parse(localStorage.getItem("expandedState")) || {};
|
|
||||||
expandedState = CostUI.deleteCostItemRow(costItemRow, expandedState);
|
|
||||||
localStorage.setItem("expandedState", JSON.stringify(expandedState));
|
|
||||||
}
|
|
||||||
|
|
||||||
static createContextMenu(callbacks) {
|
static createContextMenu(callbacks) {
|
||||||
const contextMenu = document.createElement("div");
|
const contextMenu = document.createElement("div");
|
||||||
contextMenu.id = "context-menu";
|
contextMenu.id = "context-menu";
|
||||||
@@ -197,44 +106,121 @@ export class CostUI {
|
|||||||
? callbacks.enableEditingQuantities(costItemId)
|
? callbacks.enableEditingQuantities(costItemId)
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
if (columnName === "Classification") {
|
||||||
|
callbacks.enableEditingClassification
|
||||||
|
? callbacks.enableEditingClassification(costItemId)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static deleteCostItemRow(targetRow, expandedState) {
|
static getColumnPreferences() {
|
||||||
if (!targetRow) {
|
const preferences = localStorage.getItem("columnPreferences");
|
||||||
return;
|
return preferences
|
||||||
}
|
? JSON.parse(preferences)
|
||||||
delete expandedState[targetRow.id];
|
: {
|
||||||
targetRow.remove();
|
ID: true,
|
||||||
const subRows = document.querySelectorAll(`[parent-id='${targetRow.id}']`);
|
Name: true,
|
||||||
subRows.forEach((subRow) => {
|
Quantity: true,
|
||||||
CostUI.deleteCostItemRow(subRow, expandedState);
|
Unit: true,
|
||||||
});
|
Cost: true,
|
||||||
return expandedState;
|
TotalCost: true,
|
||||||
|
Classification: true,
|
||||||
|
Actions: true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static addResizer(resizer) {
|
static addSettingsMenu() {
|
||||||
let startX, startWidth, th;
|
const settingsMenu = CostUI.Form({
|
||||||
|
id: "settings-menu",
|
||||||
resizer.addEventListener("mousedown", function (e) {
|
name: "Settings",
|
||||||
th = e.target.parentElement;
|
icon: "fa-solid fa-gear",
|
||||||
startX = e.pageX;
|
shouldHide: true,
|
||||||
startWidth = th.offsetWidth;
|
|
||||||
document.addEventListener("mousemove", resizeColumn);
|
|
||||||
document.addEventListener("mouseup", stopResize);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function resizeColumn(e) {
|
const picker = CostUI.createColorPicker();
|
||||||
const newWidth = startWidth + (e.pageX - startX);
|
const tableFontSize = CostUI.createFontSizePicker();
|
||||||
th.style.width = newWidth + "px";
|
const currencyPicker = CostUI.createCurrencyPicker();
|
||||||
}
|
settingsMenu.appendChild(picker);
|
||||||
|
settingsMenu.appendChild(tableFontSize);
|
||||||
|
document.getElementById("settings-menu").style.display = "none";
|
||||||
|
CostUI.applySavedSettings();
|
||||||
|
const columnSelector = CostUI.createColumnSelector();
|
||||||
|
settingsMenu.appendChild(columnSelector);
|
||||||
|
}
|
||||||
|
|
||||||
function stopResize() {
|
static createColumnSelector() {
|
||||||
document.removeEventListener("mousemove", resizeColumn);
|
const columnSelector = document.createElement("div");
|
||||||
document.removeEventListener("mouseup", stopResize);
|
columnSelector.id = "column-selector";
|
||||||
}
|
const columns = ["ID", "Quantity", "Unit", "Classification", "Actions"];
|
||||||
|
columns.forEach((column) => {
|
||||||
|
const label = document.createElement("label");
|
||||||
|
const input = document.createElement("input");
|
||||||
|
input.type = "checkbox";
|
||||||
|
input.dataset.column = column;
|
||||||
|
input.checked = true;
|
||||||
|
label.appendChild(input);
|
||||||
|
label.appendChild(document.createTextNode(column));
|
||||||
|
columnSelector.appendChild(label);
|
||||||
|
});
|
||||||
|
|
||||||
|
columnSelector.querySelectorAll("input").forEach((input) => {
|
||||||
|
input.addEventListener("change", () => {
|
||||||
|
const preferences = CostUI.getColumnPreferences();
|
||||||
|
preferences[input.dataset.column] = input.checked;
|
||||||
|
CostUI.setColumnPreferences(preferences);
|
||||||
|
CostUI.updateColumnVisibility(preferences);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return columnSelector;
|
||||||
|
}
|
||||||
|
|
||||||
|
static setColumnPreferences(preferences) {
|
||||||
|
localStorage.setItem("columnPreferences", JSON.stringify(preferences));
|
||||||
|
}
|
||||||
|
|
||||||
|
static updateColumnVisibility(preferences) {
|
||||||
|
const tables = document.querySelectorAll("table[id^='cost-items-']");
|
||||||
|
tables.forEach((table) => {
|
||||||
|
const columns = table.querySelectorAll("th, td");
|
||||||
|
columns.forEach((column) => {
|
||||||
|
const columnName = column.getAttribute("data-column");
|
||||||
|
if (preferences[columnName] !== undefined) {
|
||||||
|
column.style.display = preferences[columnName] ? "" : "none";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 createRibbon() {
|
||||||
|
CostUI.addSettingsMenu();
|
||||||
|
CostUI.addRibbonButton({
|
||||||
|
text: "Hide Schedules",
|
||||||
|
icon: "fa-regular fa-eye-slash",
|
||||||
|
callback: (button) => {
|
||||||
|
CostUI.toggleSchedulesContainer(button);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
CostUI.addRibbonButton({
|
||||||
|
text: "Settings",
|
||||||
|
icon: "fas fa-cog",
|
||||||
|
callback: () => {
|
||||||
|
CostUI.toggleSettingsMenu();
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static generateColorScheme(baseColor) {
|
static generateColorScheme(baseColor) {
|
||||||
@@ -389,6 +375,16 @@ export class CostUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static isCostScheduleLoaded(id) {
|
||||||
|
const existingTable = document.getElementById("cost-items-" + id);
|
||||||
|
return existingTable !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static removeCostSchedule(id) {
|
||||||
|
const table = document.getElementById("cost-items-" + id);
|
||||||
|
table ? table.parentElement.remove() : null;
|
||||||
|
}
|
||||||
|
|
||||||
static createCostTree({
|
static createCostTree({
|
||||||
costItems,
|
costItems,
|
||||||
container,
|
container,
|
||||||
@@ -420,14 +416,90 @@ export class CostUI {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static format_number(number) {
|
static createCostTable({ costSchedule, currency, callbacks }) {
|
||||||
return new Intl.NumberFormat("en-US", {
|
const preferences = CostUI.getColumnPreferences();
|
||||||
minimumFractionDigits: 2,
|
const isScheduleOfRates = costSchedule.PredefinedType === "SCHEDULEOFRATES";
|
||||||
maximumFractionDigits: 2,
|
let tableWrapper;
|
||||||
useGrouping: true,
|
const id = costSchedule.id;
|
||||||
})
|
if (CostUI.isCostScheduleLoaded(id)) {
|
||||||
.format(number)
|
const table = document.getElementById("cost-items-" + id);
|
||||||
.replace(/,/g, " ");
|
tableWrapper = table.parentElement;
|
||||||
|
table.remove();
|
||||||
|
} else {
|
||||||
|
tableWrapper = document.createElement("div");
|
||||||
|
tableWrapper.classList.add("table-wrapper");
|
||||||
|
document.getElementById("cost-items").appendChild(tableWrapper);
|
||||||
|
const tableHeader = document.createElement("div");
|
||||||
|
tableHeader.classList.add("form-header");
|
||||||
|
const text = CostUI.Text(
|
||||||
|
costSchedule.Name,
|
||||||
|
"fa-solid fa-money-bill-wave",
|
||||||
|
"x-large"
|
||||||
|
);
|
||||||
|
|
||||||
|
const callback = () => {
|
||||||
|
tableWrapper.remove();
|
||||||
|
CostUI.unhighlightElement("schedule-" + id);
|
||||||
|
};
|
||||||
|
let closeButton = CostUI.createCloseButton(callback);
|
||||||
|
tableHeader.appendChild(text);
|
||||||
|
tableHeader.appendChild(closeButton);
|
||||||
|
tableWrapper.appendChild(tableHeader);
|
||||||
|
}
|
||||||
|
const table = document.createElement("table");
|
||||||
|
table.id = "cost-items-" + id;
|
||||||
|
const tbody = document.createElement("tbody");
|
||||||
|
const columnHeaders = [
|
||||||
|
{ name: "ID", visible: preferences.ID },
|
||||||
|
{ name: "Name", visible: preferences.Name },
|
||||||
|
{ name: "Quantity", visible: preferences.Quantity },
|
||||||
|
{ name: "Unit", visible: preferences.Unit },
|
||||||
|
{ name: "Cost (" + currency + ")", visible: preferences.Cost },
|
||||||
|
{ name: "Total Cost (" + currency + ")", visible: preferences.TotalCost },
|
||||||
|
{ name: "Classification", visible: preferences.Classification },
|
||||||
|
{ name: "Actions", visible: preferences.Actions },
|
||||||
|
];
|
||||||
|
if (isScheduleOfRates) {
|
||||||
|
columnHeaders.splice(2, 1);
|
||||||
|
columnHeaders[3] = {
|
||||||
|
name: "Rate (" + currency + ")",
|
||||||
|
visible: preferences.Cost,
|
||||||
|
};
|
||||||
|
columnHeaders[4] = {
|
||||||
|
name: "Total Rate (" + currency + ")",
|
||||||
|
visible: preferences.TotalCost,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const thead = document.createElement("thead");
|
||||||
|
const tr = document.createElement("tr");
|
||||||
|
thead.appendChild(tr);
|
||||||
|
|
||||||
|
for (let i = 0; i < columnHeaders.length; i++) {
|
||||||
|
if (columnHeaders[i].visible) {
|
||||||
|
const th = document.createElement("th");
|
||||||
|
th.textContent = columnHeaders[i].name;
|
||||||
|
th.setAttribute("data-column", columnHeaders[i].name);
|
||||||
|
tr.appendChild(th);
|
||||||
|
|
||||||
|
if (i < columnHeaders.length - 1) {
|
||||||
|
const resizer = document.createElement("div");
|
||||||
|
resizer.classList.add("resizer");
|
||||||
|
th.appendChild(resizer);
|
||||||
|
CostUI.addResizer(resizer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table.appendChild(thead);
|
||||||
|
table.appendChild(tbody);
|
||||||
|
tableWrapper.appendChild(table);
|
||||||
|
|
||||||
|
CostUI.createContextMenu(callbacks);
|
||||||
|
table.get_blender_id = function () {
|
||||||
|
return this.getAttribute("id").split("-")[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
return [table, tbody];
|
||||||
}
|
}
|
||||||
|
|
||||||
static addCostItemRow(
|
static addCostItemRow(
|
||||||
@@ -437,6 +509,7 @@ export class CostUI {
|
|||||||
isScheduleOfRates,
|
isScheduleOfRates,
|
||||||
callbacks = {}
|
callbacks = {}
|
||||||
) {
|
) {
|
||||||
|
const preferences = CostUI.getColumnPreferences();
|
||||||
const totalQuantity = costItem.TotalCostQuantity
|
const totalQuantity = costItem.TotalCostQuantity
|
||||||
? CostUI.format_number(costItem.TotalCostQuantity)
|
? CostUI.format_number(costItem.TotalCostQuantity)
|
||||||
: "-";
|
: "-";
|
||||||
@@ -453,47 +526,85 @@ export class CostUI {
|
|||||||
const identification = costItem.Identification
|
const identification = costItem.Identification
|
||||||
? costItem.Identification
|
? costItem.Identification
|
||||||
: "XXX";
|
: "XXX";
|
||||||
const idCell = CostUI.createTableCell(identification);
|
if (preferences.ID) {
|
||||||
row.appendChild(idCell);
|
const idCell = CostUI.createTableCell(identification);
|
||||||
|
idCell.setAttribute("data-column", "ID");
|
||||||
const expandButton = CostUI.createExpandButton(costItem);
|
row.appendChild(idCell);
|
||||||
const nameCell = CostUI.createNameCell(
|
|
||||||
costItem,
|
|
||||||
nestingLevel,
|
|
||||||
expandButton,
|
|
||||||
callbacks
|
|
||||||
);
|
|
||||||
row.appendChild(nameCell);
|
|
||||||
|
|
||||||
if (isScheduleOfRates) {
|
|
||||||
const unitBasisUnitSymbol = costItem.UnitBasisUnitSymbol
|
|
||||||
? costItem.UnitBasisUnitSymbol
|
|
||||||
: "-";
|
|
||||||
const unitBasisUnitSymbolCell =
|
|
||||||
CostUI.createTableCell(unitBasisUnitSymbol);
|
|
||||||
row.appendChild(unitBasisUnitSymbolCell);
|
|
||||||
} else {
|
|
||||||
const totalCostQuantityCell = CostUI.createTableCell(totalQuantity);
|
|
||||||
row.appendChild(totalCostQuantityCell);
|
|
||||||
totalCostQuantityCell.classList.add("clickable-cell");
|
|
||||||
const unitSymbolCell = CostUI.createTableCell(costItem.UnitSymbol);
|
|
||||||
row.appendChild(unitSymbolCell);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalAppliedValueCell = CostUI.createCostCell(appliedValue);
|
const expandButton = CostUI.createExpandButton(costItem);
|
||||||
totalAppliedValueCell.classList.add("clickable-cell");
|
if (preferences.Name) {
|
||||||
row.appendChild(totalAppliedValueCell);
|
const nameCell = CostUI.createNameCell(
|
||||||
|
costItem,
|
||||||
|
nestingLevel,
|
||||||
|
expandButton,
|
||||||
|
callbacks
|
||||||
|
);
|
||||||
|
nameCell.setAttribute("data-column", "Name");
|
||||||
|
row.appendChild(nameCell);
|
||||||
|
}
|
||||||
|
|
||||||
const totalCostCell = CostUI.createTotalCostCell(
|
if (isScheduleOfRates) {
|
||||||
costItem,
|
if (preferences.Unit) {
|
||||||
isScheduleOfRates,
|
const unitBasisUnitSymbol = costItem.UnitBasisUnitSymbol
|
||||||
callbacks.addSumCostValue
|
? costItem.UnitBasisUnitSymbol
|
||||||
);
|
: "-";
|
||||||
row.appendChild(totalCostCell);
|
const unitBasisUnitSymbolCell =
|
||||||
|
CostUI.createTableCell(unitBasisUnitSymbol);
|
||||||
|
unitBasisUnitSymbolCell.setAttribute("data-column", "Unit");
|
||||||
|
row.appendChild(unitBasisUnitSymbolCell);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (preferences.Quantity) {
|
||||||
|
const totalCostQuantityCell = CostUI.createTableCell(totalQuantity);
|
||||||
|
totalCostQuantityCell.setAttribute("data-column", "Quantity");
|
||||||
|
row.appendChild(totalCostQuantityCell);
|
||||||
|
totalCostQuantityCell.classList.add("clickable-cell");
|
||||||
|
}
|
||||||
|
if (preferences.Unit) {
|
||||||
|
const unitSymbolCell = CostUI.createTableCell(costItem.UnitSymbol);
|
||||||
|
unitSymbolCell.setAttribute("data-column", "Unit");
|
||||||
|
row.appendChild(unitSymbolCell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const actionsCell = CostUI.costItemActions(costItem, callbacks);
|
if (preferences.Cost) {
|
||||||
actionsCell.classList.add("actions-column");
|
const totalAppliedValueCell = CostUI.createCostCell(appliedValue);
|
||||||
row.appendChild(actionsCell);
|
totalAppliedValueCell.setAttribute("data-column", "Cost");
|
||||||
|
totalAppliedValueCell.classList.add("clickable-cell");
|
||||||
|
row.appendChild(totalAppliedValueCell);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferences.TotalCost) {
|
||||||
|
const totalCostCell = CostUI.createTotalCostCell(
|
||||||
|
costItem,
|
||||||
|
isScheduleOfRates,
|
||||||
|
callbacks.addSumCostValue
|
||||||
|
);
|
||||||
|
totalCostCell.setAttribute("data-column", "Total Cost");
|
||||||
|
row.appendChild(totalCostCell);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferences.Classification) {
|
||||||
|
const classifications = costItem.Classification
|
||||||
|
? costItem.Classification
|
||||||
|
: [];
|
||||||
|
const ClassificationCell = document.createElement("td");
|
||||||
|
ClassificationCell.setAttribute("data-column", "Classification");
|
||||||
|
ClassificationCell.classList.add("clickable-cell");
|
||||||
|
row.appendChild(ClassificationCell);
|
||||||
|
const string_List = classifications
|
||||||
|
.map((classification) => classification.Identification)
|
||||||
|
.join(", ");
|
||||||
|
ClassificationCell.textContent = string_List;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferences.Actions) {
|
||||||
|
const actionsCell = CostUI.costItemActions(costItem, callbacks);
|
||||||
|
actionsCell.setAttribute("data-column", "Actions");
|
||||||
|
actionsCell.classList.add("actions-column");
|
||||||
|
row.appendChild(actionsCell);
|
||||||
|
}
|
||||||
|
|
||||||
row.get_id = function () {
|
row.get_id = function () {
|
||||||
return this.getAttribute("id");
|
return this.getAttribute("id");
|
||||||
@@ -508,6 +619,45 @@ export class CostUI {
|
|||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static addResizer(resizer) {
|
||||||
|
let startX, startWidth, th;
|
||||||
|
|
||||||
|
resizer.addEventListener("mousedown", function (e) {
|
||||||
|
th = e.target.parentElement;
|
||||||
|
startX = e.pageX;
|
||||||
|
startWidth = th.offsetWidth;
|
||||||
|
document.addEventListener("mousemove", resizeColumn);
|
||||||
|
document.addEventListener("mouseup", stopResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
function resizeColumn(e) {
|
||||||
|
const newWidth = startWidth + (e.pageX - startX);
|
||||||
|
th.style.width = newWidth + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopResize() {
|
||||||
|
document.removeEventListener("mousemove", resizeColumn);
|
||||||
|
document.removeEventListener("mouseup", stopResize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static format_number(number) {
|
||||||
|
return new Intl.NumberFormat("en-US", {
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
useGrouping: true,
|
||||||
|
})
|
||||||
|
.format(number)
|
||||||
|
.replace(/,/g, " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
static deleteCostItem(costItemId) {
|
||||||
|
const costItemRow = CostUI.getCostItemRow(costItemId);
|
||||||
|
let expandedState = JSON.parse(localStorage.getItem("expandedState")) || {};
|
||||||
|
expandedState = CostUI.deleteCostItemRow(costItemRow, expandedState);
|
||||||
|
localStorage.setItem("expandedState", JSON.stringify(expandedState));
|
||||||
|
}
|
||||||
|
|
||||||
static createCostItemRow(costItem, nestingLevel, parentID) {
|
static createCostItemRow(costItem, nestingLevel, parentID) {
|
||||||
const row = document.createElement("tr");
|
const row = document.createElement("tr");
|
||||||
row.setAttribute("id", costItem.id);
|
row.setAttribute("id", costItem.id);
|
||||||
@@ -518,6 +668,19 @@ export class CostUI {
|
|||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static deleteCostItemRow(targetRow, expandedState) {
|
||||||
|
if (!targetRow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
delete expandedState[targetRow.id];
|
||||||
|
targetRow.remove();
|
||||||
|
const subRows = document.querySelectorAll(`[parent-id='${targetRow.id}']`);
|
||||||
|
subRows.forEach((subRow) => {
|
||||||
|
CostUI.deleteCostItemRow(subRow, expandedState);
|
||||||
|
});
|
||||||
|
return expandedState;
|
||||||
|
}
|
||||||
|
|
||||||
static createExpandButton(row) {
|
static createExpandButton(row) {
|
||||||
const expandButton = document.createElement("i");
|
const expandButton = document.createElement("i");
|
||||||
expandButton.classList.add("action-button");
|
expandButton.classList.add("action-button");
|
||||||
@@ -852,7 +1015,7 @@ export class CostUI {
|
|||||||
);
|
);
|
||||||
cardTitle.classList.add("card-title");
|
cardTitle.classList.add("card-title");
|
||||||
|
|
||||||
const cardButton = CostUI.createButton("Load", "fa-solid fa-repeat");
|
const cardButton = CostUI.createButton("Load", "fa-solid fa-arrows-rotate");
|
||||||
cardButton.addEventListener("click", callback);
|
cardButton.addEventListener("click", callback);
|
||||||
|
|
||||||
cardBody.appendChild(cardTitle);
|
cardBody.appendChild(cardTitle);
|
||||||
@@ -1753,23 +1916,6 @@ export class CostUI {
|
|||||||
return addProductAssignmentsButton;
|
return addProductAssignmentsButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
static addSettingsMenu() {
|
|
||||||
const settingsMenu = CostUI.Form({
|
|
||||||
id: "settings-menu",
|
|
||||||
name: "Settings",
|
|
||||||
icon: "fa-solid fa-gear",
|
|
||||||
shouldHide: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const picker = CostUI.createColorPicker();
|
|
||||||
const tableFontSize = CostUI.createFontSizePicker();
|
|
||||||
const currencyPicker = CostUI.createCurrencyPicker();
|
|
||||||
settingsMenu.appendChild(picker);
|
|
||||||
settingsMenu.appendChild(tableFontSize);
|
|
||||||
document.getElementById("settings-menu").style.display = "none";
|
|
||||||
CostUI.applySavedSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
static createFontSizePicker() {
|
static createFontSizePicker() {
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
const fontSizeText = document.createElement("p");
|
const fontSizeText = document.createElement("p");
|
||||||
@@ -1931,24 +2077,6 @@ export class CostUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static createRibbon() {
|
|
||||||
CostUI.addSettingsMenu();
|
|
||||||
CostUI.addRibbonButton({
|
|
||||||
text: "Hide Schedules",
|
|
||||||
icon: "fa-regular fa-eye-slash",
|
|
||||||
callback: (button) => {
|
|
||||||
CostUI.toggleSchedulesContainer(button);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
CostUI.addRibbonButton({
|
|
||||||
text: "Settings",
|
|
||||||
icon: "fas fa-cog",
|
|
||||||
callback: () => {
|
|
||||||
CostUI.toggleSettingsMenu();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static enableEditingQuantities({
|
static enableEditingQuantities({
|
||||||
costItemId,
|
costItemId,
|
||||||
selectedProducts,
|
selectedProducts,
|
||||||
@@ -2017,17 +2145,6 @@ export class CostUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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({
|
static createSummarySection({
|
||||||
selectedProducts,
|
selectedProducts,
|
||||||
assignedProducts,
|
assignedProducts,
|
||||||
@@ -2354,4 +2471,229 @@ export class CostUI {
|
|||||||
|
|
||||||
return addButton;
|
return addButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static createCostClassificationWindow({
|
||||||
|
classificationName,
|
||||||
|
classificationElements,
|
||||||
|
costClassifications,
|
||||||
|
costItemId,
|
||||||
|
callbacks,
|
||||||
|
}) {
|
||||||
|
const formName = classificationName || "Classification";
|
||||||
|
const formContainer = CostUI.Form({
|
||||||
|
id: "classificationTree",
|
||||||
|
name: formName,
|
||||||
|
icon: "fa-solid fa-sitemap",
|
||||||
|
});
|
||||||
|
this.createLayeredViewPanel(
|
||||||
|
formContainer,
|
||||||
|
classificationElements,
|
||||||
|
formName,
|
||||||
|
costItemId,
|
||||||
|
callbacks
|
||||||
|
);
|
||||||
|
this.classificationList = this.createClassificationList({
|
||||||
|
costClassifications,
|
||||||
|
costItemId,
|
||||||
|
callbacks,
|
||||||
|
});
|
||||||
|
formContainer.appendChild(this.classificationList);
|
||||||
|
}
|
||||||
|
|
||||||
|
static createClassificationList({
|
||||||
|
costClassifications,
|
||||||
|
costItemId,
|
||||||
|
callbacks,
|
||||||
|
}) {
|
||||||
|
const classificationList = document.createElement("div");
|
||||||
|
classificationList.classList.add("classification-list");
|
||||||
|
|
||||||
|
const header = document.createElement("h2");
|
||||||
|
header.textContent = "Classifications";
|
||||||
|
classificationList.appendChild(header);
|
||||||
|
|
||||||
|
const classificationContainer = document.createElement("div");
|
||||||
|
classificationContainer.classList.add("classification-container");
|
||||||
|
classificationList.appendChild(classificationContainer);
|
||||||
|
console.log(costClassifications);
|
||||||
|
if (costClassifications.length === 0) {
|
||||||
|
const text = "No classifications available";
|
||||||
|
const noDataMessage = this.Text(
|
||||||
|
text,
|
||||||
|
"fa-solid fa-exclamation-circle",
|
||||||
|
"large"
|
||||||
|
);
|
||||||
|
classificationContainer.appendChild(noDataMessage);
|
||||||
|
} else {
|
||||||
|
costClassifications.forEach((classification) => {
|
||||||
|
const classificationDiv = document.createElement("div");
|
||||||
|
classificationDiv.classList.add("classification");
|
||||||
|
classificationDiv.textContent =
|
||||||
|
classification.Name + " (" + classification.Identification + ")";
|
||||||
|
|
||||||
|
const removeClassificationReferenceButton = this.createButton(
|
||||||
|
"Remove",
|
||||||
|
"fa-solid fa-trash"
|
||||||
|
);
|
||||||
|
removeClassificationReferenceButton.addEventListener(
|
||||||
|
"click",
|
||||||
|
function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
console.log(
|
||||||
|
"Remove classification reference from",
|
||||||
|
costItemId,
|
||||||
|
"Classificaition Id",
|
||||||
|
classification.id
|
||||||
|
);
|
||||||
|
callbacks.removeClassificationReference(
|
||||||
|
costItemId,
|
||||||
|
classification.id
|
||||||
|
);
|
||||||
|
callbacks.enableEditingClassification(costItemId);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
classificationDiv.appendChild(removeClassificationReferenceButton);
|
||||||
|
classificationContainer.appendChild(classificationDiv);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return classificationList;
|
||||||
|
}
|
||||||
|
|
||||||
|
static createLayeredViewPanel(
|
||||||
|
formContainer,
|
||||||
|
classificationElements,
|
||||||
|
classificationName,
|
||||||
|
costItemId,
|
||||||
|
callbacks
|
||||||
|
) {
|
||||||
|
function loadLevel(items) {
|
||||||
|
container.innerHTML = "";
|
||||||
|
items.forEach((item) => {
|
||||||
|
const itemDiv = document.createElement("div");
|
||||||
|
itemDiv.classList.add("item-container");
|
||||||
|
itemDiv.classList.add("row-container");
|
||||||
|
itemDiv.innerHTML = `<span>${item.Identification}: ${item.Name}</span>`;
|
||||||
|
|
||||||
|
if (item.has_references && item.references.length > 0) {
|
||||||
|
const nextButton = CostUI.createButton(
|
||||||
|
"View",
|
||||||
|
"fa-solid fa-arrow-right"
|
||||||
|
);
|
||||||
|
nextButton.addEventListener("click", function (event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
currentPath.push(item);
|
||||||
|
loadLevel(item.references);
|
||||||
|
backButton.disabled = false;
|
||||||
|
});
|
||||||
|
itemDiv.appendChild(nextButton);
|
||||||
|
}
|
||||||
|
itemDiv.addEventListener("click", function () {
|
||||||
|
const allItems = container.querySelectorAll(".item-container");
|
||||||
|
allItems.forEach((i) => i.classList.remove("selected"));
|
||||||
|
itemDiv.classList.add("selected");
|
||||||
|
selectedClassification.textContent = `${item.Identification}: ${item.Name}`;
|
||||||
|
selectedClassification.currentSelection = item.id;
|
||||||
|
assignClassificationButton.disabled = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
container.appendChild(itemDiv);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function goBack() {
|
||||||
|
currentPath.pop();
|
||||||
|
const parent = currentPath.length
|
||||||
|
? currentPath[currentPath.length - 1].references
|
||||||
|
: data;
|
||||||
|
loadLevel(parent);
|
||||||
|
|
||||||
|
if (currentPath.length === 0) {
|
||||||
|
backButton.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let currentPath = [];
|
||||||
|
let currentData = classificationElements;
|
||||||
|
|
||||||
|
const container = document.createElement("div");
|
||||||
|
container.id = "classificationView";
|
||||||
|
container.classList.add("classificationView");
|
||||||
|
formContainer.appendChild(container);
|
||||||
|
|
||||||
|
if (!classificationElements || classificationElements.length === 0) {
|
||||||
|
console.log("No data available");
|
||||||
|
const text =
|
||||||
|
"No data available - please activate a classification file with BonsaiBIM ";
|
||||||
|
const noDataMessage = this.Text(
|
||||||
|
text,
|
||||||
|
"fa-solid fa-exclamation-circle",
|
||||||
|
"large"
|
||||||
|
);
|
||||||
|
const refreshButton = this.createButton(
|
||||||
|
"Enable Editing Classification",
|
||||||
|
"fa-solid fa-arrows-rotate"
|
||||||
|
);
|
||||||
|
refreshButton.addEventListener("click", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
callbacks.enableEditingClassification(costItemId);
|
||||||
|
});
|
||||||
|
container.appendChild(refreshButton);
|
||||||
|
container.appendChild(noDataMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rowContainer = document.createElement("div");
|
||||||
|
rowContainer.classList.add("row-container");
|
||||||
|
|
||||||
|
const selectedClassificationText = this.Text(
|
||||||
|
"Selected Classification: ",
|
||||||
|
"fa-solid fa-check",
|
||||||
|
"medium"
|
||||||
|
);
|
||||||
|
const selectedClassification = document.createElement("span");
|
||||||
|
selectedClassificationText.classList.add("selectedClassificationText");
|
||||||
|
selectedClassification.classList.add(
|
||||||
|
"selected",
|
||||||
|
"row-container",
|
||||||
|
"item-container"
|
||||||
|
);
|
||||||
|
selectedClassification.id = "selectedClassification";
|
||||||
|
selectedClassification.currentSelection = null;
|
||||||
|
selectedClassificationText.appendChild(selectedClassification);
|
||||||
|
rowContainer.appendChild(selectedClassificationText);
|
||||||
|
|
||||||
|
const assignClassificationButton = this.createButton(
|
||||||
|
"Assign Classification",
|
||||||
|
"fa-solid fa-check"
|
||||||
|
);
|
||||||
|
assignClassificationButton.addEventListener("click", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (selectedClassification.currentSelection) {
|
||||||
|
callbacks.addClassificationReference(
|
||||||
|
costItemId,
|
||||||
|
classificationName,
|
||||||
|
selectedClassification.currentSelection
|
||||||
|
);
|
||||||
|
callbacks.enableEditingClassification(costItemId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assignClassificationButton.disabled = true;
|
||||||
|
rowContainer.appendChild(assignClassificationButton);
|
||||||
|
formContainer.appendChild(rowContainer);
|
||||||
|
|
||||||
|
const backButton = CostUI.createButton("Back", "fa-solid fa-arrow-left");
|
||||||
|
backButton.classList.add("back-button");
|
||||||
|
backButton.addEventListener("click", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (!backButton.disabled) {
|
||||||
|
goBack();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
formContainer.insertBefore(backButton, container);
|
||||||
|
backButton.disabled = true;
|
||||||
|
|
||||||
|
loadLevel(currentData);
|
||||||
|
return container;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from ifcopenshell.util.classification import get_classification_data, get_references
|
||||||
import ifcopenshell.util.cost
|
import ifcopenshell.util.cost
|
||||||
import bpy
|
import bpy
|
||||||
from bonsai.bim.module.web.data import WebData
|
from bonsai.bim.module.web.data import WebData
|
||||||
@@ -42,6 +43,7 @@ from pathlib import Path
|
|||||||
import bonsai.core.sequence
|
import bonsai.core.sequence
|
||||||
import bonsai.core.cost
|
import bonsai.core.cost
|
||||||
from ifc5d.ifc2json import ifc5D2json
|
from ifc5d.ifc2json import ifc5D2json
|
||||||
|
from bonsai.bim.ifc import IfcStore
|
||||||
|
|
||||||
sio = None
|
sio = None
|
||||||
ws_process = None
|
ws_process = None
|
||||||
@@ -499,6 +501,43 @@ class Web(bonsai.core.tool.Web):
|
|||||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item)
|
cost_schedule = tool.Cost.get_cost_schedule(cost_item)
|
||||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||||
cls.load_cost_item_quantities_ui(cost_item)
|
cls.load_cost_item_quantities_ui(cost_item)
|
||||||
|
if operator_data["type"] == "enableEditingClassification":
|
||||||
|
cost_item = ifc_file.by_id(operator_data["costItemId"])
|
||||||
|
cost_classifications = []
|
||||||
|
for reference in get_references(cost_item):
|
||||||
|
cost_classification_data = reference.get_info()
|
||||||
|
del cost_classification_data["ReferencedSource"]
|
||||||
|
cost_classifications.append(cost_classification_data)
|
||||||
|
classification_data, classification_name = get_classification_data(IfcStore.classification_file)
|
||||||
|
data = {
|
||||||
|
"classification_data": classification_data,
|
||||||
|
"classification_name": classification_name,
|
||||||
|
"cost_item_id": cost_item.id(),
|
||||||
|
"cost_classifications": cost_classifications,
|
||||||
|
}
|
||||||
|
cls.send_webui_data(
|
||||||
|
data=data,
|
||||||
|
data_key="classification",
|
||||||
|
event="classification",
|
||||||
|
)
|
||||||
|
if operator_data["type"] == "removeClassificationReference":
|
||||||
|
cost_item = ifc_file.by_id(operator_data["costItemId"])
|
||||||
|
cost_schedule = tool.Cost.get_cost_schedule(cost_item)
|
||||||
|
reference = ifc_file.by_id(operator_data["classificationId"])
|
||||||
|
tool.Ifc.run("classification.remove_reference", products=[cost_item], reference=reference)
|
||||||
|
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||||
|
if operator_data["type"] == "addClassificationReference":
|
||||||
|
cost_item = ifc_file.by_id(operator_data["costItemId"])
|
||||||
|
cost_schedule = tool.Cost.get_cost_schedule(cost_item)
|
||||||
|
classification = None
|
||||||
|
classification_name = operator_data["classificationName"]
|
||||||
|
for element in tool.Ifc.get().by_type("IfcClassification"):
|
||||||
|
if element.Name == classification_name:
|
||||||
|
classification = element
|
||||||
|
break
|
||||||
|
reference = IfcStore.classification_file.by_id(operator_data["classificationId"])
|
||||||
|
tool.Ifc.run("classification.add_reference", products=[cost_item], reference=reference, classification=classification)
|
||||||
|
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load_cost_item_quantities_ui(cls, cost_item: ifcopenshell.entity_instance) -> None:
|
def load_cost_item_quantities_ui(cls, cost_item: ifcopenshell.entity_instance) -> None:
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import ifcopenshell.util.unit
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
import ifcopenshell.util.cost
|
import ifcopenshell.util.cost
|
||||||
import ifcopenshell.util.date
|
import ifcopenshell.util.date
|
||||||
|
from ifcopenshell.util.classification import get_references
|
||||||
|
|
||||||
|
|
||||||
CostItem = dict[str, Any]
|
CostItem = dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
class ifc5D2json:
|
class ifc5D2json:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.json: str = None
|
self.json: str = None
|
||||||
@@ -57,11 +58,21 @@ class ifc5D2json:
|
|||||||
data["id"] = cost_item.id()
|
data["id"] = cost_item.id()
|
||||||
data["IsNestedBy"] = []
|
data["IsNestedBy"] = []
|
||||||
data["IsSum"] = self.check_if_cost_item_is_sum(cost_item)
|
data["IsSum"] = self.check_if_cost_item_is_sum(cost_item)
|
||||||
|
data["Classification"] = self.get_cost_classifications(cost_item)
|
||||||
json_data.append(data)
|
json_data.append(data)
|
||||||
for rel in cost_item.IsNestedBy or []:
|
for rel in cost_item.IsNestedBy or []:
|
||||||
for sub_cost in rel.RelatedObjects:
|
for sub_cost in rel.RelatedObjects:
|
||||||
self.extract_cost_item_data(sub_cost, data["IsNestedBy"])
|
self.extract_cost_item_data(sub_cost, data["IsNestedBy"])
|
||||||
|
|
||||||
|
def get_cost_classifications(self, cost_item: ifcopenshell.entity_instance) -> list:
|
||||||
|
results = []
|
||||||
|
if cost_item:
|
||||||
|
for reference in get_references(cost_item):
|
||||||
|
data = reference.get_info()
|
||||||
|
del data["ReferencedSource"]
|
||||||
|
results.append(data)
|
||||||
|
return results
|
||||||
|
|
||||||
def check_if_cost_item_is_sum(self, cost_item: ifcopenshell.entity_instance) -> bool:
|
def check_if_cost_item_is_sum(self, cost_item: ifcopenshell.entity_instance) -> bool:
|
||||||
cost_values = []
|
cost_values = []
|
||||||
if cost_item.is_a("IfcCostItem"):
|
if cost_item.is_a("IfcCostItem"):
|
||||||
|
|||||||
@@ -65,3 +65,20 @@ def get_inherited_references(reference: Optional[ifcopenshell.entity_instance])
|
|||||||
results.append(reference)
|
results.append(reference)
|
||||||
reference = reference.ReferencedSource
|
reference = reference.ReferencedSource
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
def get_classification_data(file: ifcopenshell.file) -> Optional[tuple[list[dict], str]]:
|
||||||
|
if not file or not file.by_type("IfcClassification"):
|
||||||
|
return [], ""
|
||||||
|
classification = file.by_type("IfcClassification")[0]
|
||||||
|
classification_name = classification.Name
|
||||||
|
|
||||||
|
def process_references(reference):
|
||||||
|
data = reference.get_info()
|
||||||
|
del data["ReferencedSource"]
|
||||||
|
data["referenced_source"] = reference.ReferencedSource.id() if reference.ReferencedSource else None
|
||||||
|
data["has_references"] = bool(reference.HasReferences)
|
||||||
|
data["references"] = [process_references(ref) for ref in reference.HasReferences] if reference.HasReferences else []
|
||||||
|
return data
|
||||||
|
|
||||||
|
classification_data = [process_references(reference) for reference in classification.HasReferences]
|
||||||
|
return classification_data, classification_name
|
||||||
Reference in New Issue
Block a user