mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-29 03:37:21 +00:00
cost web ui:
- add "Linked rate" column, - assign a cost rate to multiple cost items by selecting linked rate cells & clicking the "assign" button on the chosen cost rate #5369
This commit is contained in:
@@ -1,58 +1,27 @@
|
|||||||
:root.blender .flex-row {
|
.card-container {
|
||||||
display: flex;
|
flex: 1;
|
||||||
flex-direction: row;
|
display: flex;
|
||||||
justify-content: space-between;
|
flex-direction: column;
|
||||||
}
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
/* CSS for the work schedule cards */
|
.card {
|
||||||
:root.blender #work_schedules {
|
background-color: var(--blender-even-row, var(--nav-bg-color));
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-direction: column;
|
||||||
gap: 20px;
|
overflow-y: auto;
|
||||||
padding: 20px;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root.blender .card {
|
.card-header {
|
||||||
border: 1px solid #ddd;
|
background-color: var(--blender-panel-header, var(--bg-color));
|
||||||
border-radius: 8px;
|
border: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
flex-shrink: 0;
|
||||||
width: 300px;
|
}
|
||||||
margin: 10px;
|
|
||||||
transition: transform 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root.blender .card:hover {
|
.card-body {
|
||||||
transform: scale(1.05);
|
background-color: var(--blender-panel-background, var(--bg-color));
|
||||||
}
|
border: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||||
|
overflow-y: auto;
|
||||||
|
flex: 1;
|
||||||
:root.blender .card-title {
|
}
|
||||||
font-size: 1.25rem;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
border-bottom: 1px solid #ddd;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root.blender .card-text {
|
|
||||||
font-size: 1rem;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root.blender .btn-primary {
|
|
||||||
background-color: #007bff;
|
|
||||||
border: none;
|
|
||||||
color: white;
|
|
||||||
padding: 10px 20px;
|
|
||||||
text-align: center;
|
|
||||||
text-decoration: none;
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 1rem;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root.blender .btn-primary:hover {
|
|
||||||
background-color: #0056b3;
|
|
||||||
}
|
|
||||||
@@ -392,7 +392,7 @@ form table {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clickable-cell:hover {
|
.selected-cell, .clickable-cell:hover {
|
||||||
border: 2px solid var(--highlight-over);
|
border: 2px solid var(--highlight-over);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@@ -560,3 +560,10 @@ i {
|
|||||||
background-color: #ccffcc;
|
background-color: #ccffcc;
|
||||||
color: darkgreen;
|
color: darkgreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
.card-body {
|
||||||
|
height: 10vh;
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@import url("./components/card.css");
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--font-family: Arial, sans-serif;
|
--font-family: Arial, sans-serif;
|
||||||
--base-font-size: 16px;
|
--base-font-size: 16px;
|
||||||
@@ -250,33 +252,7 @@ button:hover {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-container {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
background-color: var(--blender-even-row, var(--nav-bg-color));
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow-y: auto;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
background-color: var(--blender-panel-header, var(--bg-color));
|
|
||||||
border: 1px solid var(--blender-tab-outline, var(--border-color));
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-body {
|
|
||||||
background-color: var(--blender-panel-background, var(--bg-color));
|
|
||||||
border: 1px solid var(--blender-tab-outline, var(--border-color));
|
|
||||||
overflow-y: auto;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
li.svg-name {
|
li.svg-name {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { CostUI } from "./utilities/costui.js";
|
|||||||
const connectedClients = {};
|
const connectedClients = {};
|
||||||
let socket;
|
let socket;
|
||||||
|
|
||||||
let loadedSchedules = {};
|
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
var defaultTheme = "blender";
|
var defaultTheme = "blender";
|
||||||
var theme = localStorage.getItem("theme") || defaultTheme;
|
var theme = localStorage.getItem("theme") || defaultTheme;
|
||||||
@@ -58,6 +56,19 @@ function handleEditQuantities(data) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function assignCostValues(costRateId, callback){
|
||||||
|
const selectedCostItems = CostUI.getSelectedCostItems();
|
||||||
|
if (selectedCostItems.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
executeOperator({
|
||||||
|
type: "assignCostValues",
|
||||||
|
costRateId: costRateId,
|
||||||
|
costItemIds: selectedCostItems,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function addSumCostValue(costItemId) {
|
function addSumCostValue(costItemId) {
|
||||||
executeOperator({ type: "addSumCostValue", costItemId: costItemId });
|
executeOperator({ type: "addSumCostValue", costItemId: costItemId });
|
||||||
}
|
}
|
||||||
@@ -276,9 +287,6 @@ function toggleClientList() {
|
|||||||
|
|
||||||
function handleCostSchedulesData(data) {
|
function handleCostSchedulesData(data) {
|
||||||
const blenderId = data.blenderId;
|
const blenderId = data.blenderId;
|
||||||
const loadedSchedule = loadedSchedules[blenderId]
|
|
||||||
? loadedSchedules[blenderId]
|
|
||||||
: null;
|
|
||||||
const costSchedules = data.data["cost_schedules"]["cost_schedules"];
|
const costSchedules = data.data["cost_schedules"]["cost_schedules"];
|
||||||
if (costSchedules.length === 0) {
|
if (costSchedules.length === 0) {
|
||||||
return;
|
return;
|
||||||
@@ -309,17 +317,10 @@ function handleCostSchedulesData(data) {
|
|||||||
);
|
);
|
||||||
costScheduleDiv.append(card);
|
costScheduleDiv.append(card);
|
||||||
});
|
});
|
||||||
loadedSchedule ? CostUI.highlightElement("schedule-" + loadedSchedule) : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCostItemsData(data) {
|
function handleCostItemsData(data) {
|
||||||
const costScheduleId = data.data["cost_items"]["schedule_data"][0]["id"];
|
const costScheduleId = data.data["cost_items"]["schedule_data"][0]["id"];
|
||||||
const cards = document.querySelectorAll("[id^='schedule-']");
|
|
||||||
cards.forEach((card) => {
|
|
||||||
card.classList.remove("highlighted");
|
|
||||||
});
|
|
||||||
CostUI.highlightElement("schedule-" + costScheduleId);
|
|
||||||
loadedSchedules[data.blenderId] = costScheduleId;
|
|
||||||
const currency = data.data["cost_items"]["currency"]
|
const currency = data.data["cost_items"]["currency"]
|
||||||
? data.data["cost_items"]["currency"]["name"]
|
? data.data["cost_items"]["currency"]["name"]
|
||||||
: "Undefined";
|
: "Undefined";
|
||||||
@@ -338,6 +339,7 @@ function handleCostItemsData(data) {
|
|||||||
enableEditingQuantities: enableEditingQuantities,
|
enableEditingQuantities: enableEditingQuantities,
|
||||||
addSumCostValue: addSumCostValue,
|
addSumCostValue: addSumCostValue,
|
||||||
enableEditingClassification: enableEditingClassification,
|
enableEditingClassification: enableEditingClassification,
|
||||||
|
assignCostValues: assignCostValues,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,66 +70,78 @@ export class CostUI {
|
|||||||
handleButtonClick(editCostValuesButton, callbacks.enableEditingCostValues);
|
handleButtonClick(editCostValuesButton, callbacks.enableEditingCostValues);
|
||||||
handleButtonClick(addButton, callbacks.addCostItem);
|
handleButtonClick(addButton, callbacks.addCostItem);
|
||||||
handleButtonClick(deleteCostItemButton, callbacks.deleteCostItem);
|
handleButtonClick(deleteCostItemButton, callbacks.deleteCostItem);
|
||||||
|
|
||||||
addTableListeners(callbacks);
|
|
||||||
|
|
||||||
function addTableListeners(callbacks) {
|
|
||||||
function getColumnNames(tableId) {
|
|
||||||
const table = document.getElementById(tableId);
|
|
||||||
const headerRow = table.querySelector("thead tr");
|
|
||||||
return Array.from(headerRow.children).map((headerCell) =>
|
|
||||||
headerCell.textContent.trim()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
document
|
|
||||||
.getElementById("cost-items")
|
|
||||||
.addEventListener("click", function (event) {
|
|
||||||
const targetRow = event.target.closest("tr");
|
|
||||||
const targetCell = event.target.closest("td");
|
|
||||||
if (targetRow && targetCell) {
|
|
||||||
const columnIndex = Array.from(targetRow.children).indexOf(
|
|
||||||
targetCell
|
|
||||||
);
|
|
||||||
const tableId = targetRow.parentElement.parentElement.id;
|
|
||||||
const costItemId = parseInt(targetRow.getAttribute("id"));
|
|
||||||
const columnName = getColumnNames(tableId)[columnIndex];
|
|
||||||
if (
|
|
||||||
(columnName.includes("Cost") || columnName.includes("Rate")) &&
|
|
||||||
!columnName.includes("Total")
|
|
||||||
) {
|
|
||||||
callbacks.enableEditingCostValues
|
|
||||||
? callbacks.enableEditingCostValues(costItemId)
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
if (columnName === "Quantity") {
|
|
||||||
callbacks.enableEditingQuantities
|
|
||||||
? callbacks.enableEditingQuantities(costItemId)
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
if (columnName === "Classification") {
|
|
||||||
callbacks.enableEditingClassification
|
|
||||||
? callbacks.enableEditingClassification(costItemId)
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static getColumnPreferences() {
|
static addTableCallbacks(callbacks, table) {
|
||||||
const preferences = localStorage.getItem("columnPreferences");
|
if (table.listenersAdded) {
|
||||||
return preferences
|
return;
|
||||||
? JSON.parse(preferences)
|
}
|
||||||
: {
|
|
||||||
ID: true,
|
function getColumnNames(tableId) {
|
||||||
Name: true,
|
const table = document.getElementById(tableId);
|
||||||
Quantity: true,
|
const headerRow = table.querySelector("thead tr");
|
||||||
Unit: true,
|
return Array.from(headerRow.children).map((headerCell) =>
|
||||||
Cost: true,
|
headerCell.textContent.trim()
|
||||||
TotalCost: true,
|
);
|
||||||
Classification: true,
|
}
|
||||||
Actions: true,
|
|
||||||
};
|
function addTableListeners(callbacks) {
|
||||||
|
table.addEventListener("click", function (event) {
|
||||||
|
const targetRow = event.target.closest("tr");
|
||||||
|
const targetCell = event.target.closest("td");
|
||||||
|
if (targetRow && targetCell) {
|
||||||
|
const columnIndex = Array.from(targetRow.children).indexOf(
|
||||||
|
targetCell
|
||||||
|
);
|
||||||
|
const tableId = table.id;
|
||||||
|
const costItemId = parseInt(targetRow.getAttribute("id"));
|
||||||
|
const columnName = getColumnNames(tableId)[columnIndex];
|
||||||
|
|
||||||
|
if (
|
||||||
|
(columnName.includes("Cost") || columnName.includes("Rate")) &&
|
||||||
|
!columnName.includes("Total") &&
|
||||||
|
!columnName.includes("Linked")
|
||||||
|
) {
|
||||||
|
callbacks.enableEditingCostValues
|
||||||
|
? callbacks.enableEditingCostValues(costItemId)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (columnName === "Quantity") {
|
||||||
|
callbacks.enableEditingQuantities
|
||||||
|
? callbacks.enableEditingQuantities(costItemId)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (columnName === "Classification") {
|
||||||
|
callbacks.enableEditingClassification
|
||||||
|
? callbacks.enableEditingClassification(costItemId)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (columnName === "Linked Rate") {
|
||||||
|
if (!targetRow.isRate) {
|
||||||
|
if (targetCell.classList.contains("selected-cell")) {
|
||||||
|
document.selectedCostItems = document.selectedCostItems.filter(
|
||||||
|
(costItem) => costItem !== targetRow.id
|
||||||
|
);
|
||||||
|
targetCell.classList.remove("selected-cell");
|
||||||
|
} else {
|
||||||
|
document.selectedCostItems.push(targetRow.id);
|
||||||
|
targetCell.classList.add("selected-cell");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!document.selectedCostItems) {
|
||||||
|
document.selectedCostItems = [];
|
||||||
|
}
|
||||||
|
document.selectedCostItems = [];
|
||||||
|
addTableListeners(callbacks);
|
||||||
|
table.listenersAdded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static addSettingsMenu() {
|
static addSettingsMenu() {
|
||||||
@@ -154,7 +166,14 @@ export class CostUI {
|
|||||||
static createColumnSelector() {
|
static createColumnSelector() {
|
||||||
const columnSelector = document.createElement("div");
|
const columnSelector = document.createElement("div");
|
||||||
columnSelector.id = "column-selector";
|
columnSelector.id = "column-selector";
|
||||||
const columns = ["ID", "Quantity", "Unit", "Classification", "Actions"];
|
const columns = [
|
||||||
|
"Identification",
|
||||||
|
"Quantity",
|
||||||
|
"Unit",
|
||||||
|
"Classification",
|
||||||
|
"Actions",
|
||||||
|
"Linked Rate",
|
||||||
|
];
|
||||||
columns.forEach((column) => {
|
columns.forEach((column) => {
|
||||||
const label = document.createElement("label");
|
const label = document.createElement("label");
|
||||||
const input = document.createElement("input");
|
const input = document.createElement("input");
|
||||||
@@ -181,6 +200,23 @@ export class CostUI {
|
|||||||
localStorage.setItem("columnPreferences", JSON.stringify(preferences));
|
localStorage.setItem("columnPreferences", JSON.stringify(preferences));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getColumnPreferences() {
|
||||||
|
const preferences = localStorage.getItem("columnPreferences");
|
||||||
|
return preferences
|
||||||
|
? JSON.parse(preferences)
|
||||||
|
: {
|
||||||
|
Identification: true,
|
||||||
|
Name: true,
|
||||||
|
Quantity: true,
|
||||||
|
Unit: true,
|
||||||
|
Cost: true,
|
||||||
|
TotalCost: true,
|
||||||
|
Classification: true,
|
||||||
|
CostRate: true,
|
||||||
|
Actions: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
static updateColumnVisibility(preferences) {
|
static updateColumnVisibility(preferences) {
|
||||||
const tables = document.querySelectorAll("table[id^='cost-items-']");
|
const tables = document.querySelectorAll("table[id^='cost-items-']");
|
||||||
tables.forEach((table) => {
|
tables.forEach((table) => {
|
||||||
@@ -352,6 +388,7 @@ export class CostUI {
|
|||||||
currency,
|
currency,
|
||||||
callbacks,
|
callbacks,
|
||||||
});
|
});
|
||||||
|
this.addTableCallbacks(callbacks, table);
|
||||||
if (costSchedule["cost_items"].length === 0) {
|
if (costSchedule["cost_items"].length === 0) {
|
||||||
const tr = document.createElement("tr");
|
const tr = document.createElement("tr");
|
||||||
const td = document.createElement("td");
|
const td = document.createElement("td");
|
||||||
@@ -439,11 +476,8 @@ export class CostUI {
|
|||||||
const cells = row.querySelectorAll("td, th");
|
const cells = row.querySelectorAll("td, th");
|
||||||
let rowText = [];
|
let rowText = [];
|
||||||
|
|
||||||
console.log();
|
|
||||||
|
|
||||||
if (CostUI.getCostItemRow(row.id)) {
|
if (CostUI.getCostItemRow(row.id)) {
|
||||||
const costItem = CostUI.getCostItemRow(row.id);
|
const costItem = CostUI.getCostItemRow(row.id);
|
||||||
console.log(costItem.nestingLevel);
|
|
||||||
rowText.push(costItem.nestingLevel);
|
rowText.push(costItem.nestingLevel);
|
||||||
} else {
|
} else {
|
||||||
rowText.push("Hierarchy Level");
|
rowText.push("Hierarchy Level");
|
||||||
@@ -510,7 +544,6 @@ export class CostUI {
|
|||||||
|
|
||||||
const callback = () => {
|
const callback = () => {
|
||||||
tableWrapper.remove();
|
tableWrapper.remove();
|
||||||
CostUI.unhighlightElement("schedule-" + id);
|
|
||||||
};
|
};
|
||||||
let closeButton = CostUI.createCloseButton(callback);
|
let closeButton = CostUI.createCloseButton(callback);
|
||||||
tableHeader.appendChild(text);
|
tableHeader.appendChild(text);
|
||||||
@@ -520,26 +553,36 @@ export class CostUI {
|
|||||||
const table = document.createElement("table");
|
const table = document.createElement("table");
|
||||||
table.id = "cost-items-" + id;
|
table.id = "cost-items-" + id;
|
||||||
const tbody = document.createElement("tbody");
|
const tbody = document.createElement("tbody");
|
||||||
const columnHeaders = [
|
let columnHeaders = [
|
||||||
{ name: "ID", visible: preferences.ID },
|
{ name: "Identification", visible: preferences.Identification },
|
||||||
{ name: "Name", visible: preferences.Name },
|
{ name: "Name", visible: preferences.Name },
|
||||||
{ name: "Quantity", visible: preferences.Quantity },
|
{ name: "Quantity", visible: preferences.Quantity },
|
||||||
{ name: "Unit", visible: preferences.Unit },
|
{ name: "Unit", visible: preferences.Unit },
|
||||||
{ name: "Cost (" + currency + ")", visible: preferences.Cost },
|
{ name: "Cost (" + currency + ")", visible: preferences.Cost },
|
||||||
{ name: "Total Cost (" + currency + ")", visible: preferences.TotalCost },
|
{ name: "Total Cost (" + currency + ")", visible: preferences.TotalCost },
|
||||||
{ name: "Classification", visible: preferences.Classification },
|
{ name: "Classification", visible: preferences.Classification },
|
||||||
|
{ name: "Linked Rate", visible: preferences.CostRate },
|
||||||
{ name: "Actions", visible: preferences.Actions },
|
{ name: "Actions", visible: preferences.Actions },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (isScheduleOfRates) {
|
if (isScheduleOfRates) {
|
||||||
columnHeaders.splice(2, 1);
|
columnHeaders = columnHeaders.filter(
|
||||||
columnHeaders[3] = {
|
(header) => header.name !== "Quantity"
|
||||||
name: "Rate (" + currency + ")",
|
);
|
||||||
visible: preferences.Cost,
|
columnHeaders = columnHeaders.filter(
|
||||||
};
|
(header) => header.name !== "Linked Rate"
|
||||||
columnHeaders[4] = {
|
);
|
||||||
name: "Total Rate (" + currency + ")",
|
columnHeaders = columnHeaders.map((header) => {
|
||||||
visible: preferences.TotalCost,
|
if (header.name === "Unit") {
|
||||||
};
|
return { name: "Rate (" + currency + ")", visible: preferences.Cost };
|
||||||
|
} else if (header.name === "Cost (" + currency + ")") {
|
||||||
|
return {
|
||||||
|
name: "Total Rate (" + currency + ")",
|
||||||
|
visible: preferences.TotalCost,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return header;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const thead = document.createElement("thead");
|
const thead = document.createElement("thead");
|
||||||
const tr = document.createElement("tr");
|
const tr = document.createElement("tr");
|
||||||
@@ -597,9 +640,9 @@ export class CostUI {
|
|||||||
const identification = costItem.Identification
|
const identification = costItem.Identification
|
||||||
? costItem.Identification
|
? costItem.Identification
|
||||||
: "XXX";
|
: "XXX";
|
||||||
if (preferences.ID) {
|
if (preferences.Identification) {
|
||||||
const idCell = CostUI.createTableCell(identification);
|
const idCell = CostUI.createTableCell(identification);
|
||||||
idCell.setAttribute("data-column", "ID");
|
idCell.setAttribute("data-column", "Identification");
|
||||||
row.appendChild(idCell);
|
row.appendChild(idCell);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -633,7 +676,11 @@ export class CostUI {
|
|||||||
totalCostQuantityCell.classList.add("clickable-cell");
|
totalCostQuantityCell.classList.add("clickable-cell");
|
||||||
}
|
}
|
||||||
if (preferences.Unit) {
|
if (preferences.Unit) {
|
||||||
const unitSymbolCell = CostUI.createTableCell(costItem.UnitSymbol);
|
let symbol = costItem.UnitSymbol;
|
||||||
|
if (symbol === "-" && costItem.UnitBasisUnitSymbol) {
|
||||||
|
symbol = costItem.UnitBasisUnitSymbol;
|
||||||
|
}
|
||||||
|
const unitSymbolCell = CostUI.createTableCell(symbol);
|
||||||
unitSymbolCell.setAttribute("data-column", "Unit");
|
unitSymbolCell.setAttribute("data-column", "Unit");
|
||||||
row.appendChild(unitSymbolCell);
|
row.appendChild(unitSymbolCell);
|
||||||
}
|
}
|
||||||
@@ -670,8 +717,28 @@ export class CostUI {
|
|||||||
ClassificationCell.textContent = string_List;
|
ClassificationCell.textContent = string_List;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let costRateName = " ";
|
||||||
|
let costItemRateId;
|
||||||
|
if (costItem.CostRate.id !== null) {
|
||||||
|
costItemRateId = costItem.CostRate.id;
|
||||||
|
const costItemRateName = costItem.CostRate.Name;
|
||||||
|
const costItemRateIdentification = costItem.CostRate.Identification;
|
||||||
|
costRateName = costItemRateIdentification + " - " + costItemRateName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferences.CostRate && !isScheduleOfRates) {
|
||||||
|
const costRateCell = CostUI.createTableCell(costRateName);
|
||||||
|
costRateCell.classList.add("rate-cell", "clickable-cell");
|
||||||
|
costRateCell.setAttribute("data-column", "Linked Rate");
|
||||||
|
row.appendChild(costRateCell);
|
||||||
|
}
|
||||||
|
|
||||||
if (preferences.Actions) {
|
if (preferences.Actions) {
|
||||||
const actionsCell = CostUI.costItemActions(costItem, callbacks);
|
const actionsCell = CostUI.costItemActions(
|
||||||
|
costItem,
|
||||||
|
isScheduleOfRates,
|
||||||
|
callbacks
|
||||||
|
);
|
||||||
actionsCell.setAttribute("data-column", "Actions");
|
actionsCell.setAttribute("data-column", "Actions");
|
||||||
actionsCell.classList.add("actions-column");
|
actionsCell.classList.add("actions-column");
|
||||||
row.appendChild(actionsCell);
|
row.appendChild(actionsCell);
|
||||||
@@ -946,7 +1013,12 @@ export class CostUI {
|
|||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
|
|
||||||
static costItemActions(costItem, callbacks) {
|
static getSelectedCostItems() {
|
||||||
|
const selectedCostItems = document.selectedCostItems || [];
|
||||||
|
return selectedCostItems.map((item) => parseInt(item, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
static costItemActions(costItem, isScheduleOfRates, callbacks) {
|
||||||
const divFlex = document.createElement("div");
|
const divFlex = document.createElement("div");
|
||||||
divFlex.classList.add("row-container");
|
divFlex.classList.add("row-container");
|
||||||
const addCostItem = CostUI.addCostItemButton(
|
const addCostItem = CostUI.addCostItemButton(
|
||||||
@@ -965,12 +1037,22 @@ export class CostUI {
|
|||||||
costItem.id,
|
costItem.id,
|
||||||
callbacks.duplicateCostItem
|
callbacks.duplicateCostItem
|
||||||
);
|
);
|
||||||
|
const assignCostItemButton = CostUI.createButton(
|
||||||
[addCostItem, duplicateButton, selectButton, deleteButton].forEach(
|
"Assign",
|
||||||
(button) => {
|
"fa-solid fa-link"
|
||||||
divFlex.appendChild(button);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
assignCostItemButton.addEventListener("click", function () {
|
||||||
|
callbacks.assignCostValues(costItem.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
let buttons = [addCostItem, duplicateButton, selectButton, deleteButton];
|
||||||
|
if (isScheduleOfRates) {
|
||||||
|
buttons.push(assignCostItemButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.forEach((button) => {
|
||||||
|
divFlex.appendChild(button);
|
||||||
|
});
|
||||||
|
|
||||||
const actionsCell = document.createElement("td");
|
const actionsCell = document.createElement("td");
|
||||||
actionsCell.appendChild(divFlex);
|
actionsCell.appendChild(divFlex);
|
||||||
@@ -1107,12 +1189,13 @@ export class CostUI {
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
static createCard(id, title, mainContainer, callback) {
|
static createCard(id, title, cardBody, callback) {
|
||||||
const card = document.createElement("div");
|
const card = document.createElement("div");
|
||||||
card.classList.add("card");
|
card.classList.add("card");
|
||||||
card.id = "schedule-" + id;
|
card.id = "schedule-" + id;
|
||||||
|
|
||||||
const cardBody = document.createElement("div");
|
const cardContainer = document.createElement("div");
|
||||||
|
cardContainer.classList.add("card-body");
|
||||||
cardBody.classList.add("card-body");
|
cardBody.classList.add("card-body");
|
||||||
|
|
||||||
const cardTitle = CostUI.Text(
|
const cardTitle = CostUI.Text(
|
||||||
@@ -1120,15 +1203,16 @@ export class CostUI {
|
|||||||
"fa-solid fa-money-bill-wave",
|
"fa-solid fa-money-bill-wave",
|
||||||
"large"
|
"large"
|
||||||
);
|
);
|
||||||
cardTitle.classList.add("card-title");
|
cardTitle.classList.add("card-header");
|
||||||
|
|
||||||
const cardButton = CostUI.createButton("Load", "fa-solid fa-arrows-rotate");
|
const cardButton = CostUI.createButton("Load", "fa-solid fa-arrows-rotate");
|
||||||
cardButton.addEventListener("click", callback);
|
cardButton.addEventListener("click", callback);
|
||||||
|
cardTitle.appendChild(cardButton);
|
||||||
|
|
||||||
cardBody.appendChild(cardTitle);
|
card.appendChild(cardTitle);
|
||||||
cardBody.appendChild(mainContainer);
|
card.appendChild(cardContainer);
|
||||||
cardBody.appendChild(cardButton);
|
|
||||||
card.appendChild(cardBody);
|
cardContainer.appendChild(cardBody);
|
||||||
|
|
||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
@@ -2714,7 +2798,6 @@ export class CostUI {
|
|||||||
const classificationContainer = document.createElement("div");
|
const classificationContainer = document.createElement("div");
|
||||||
classificationContainer.classList.add("classification-container");
|
classificationContainer.classList.add("classification-container");
|
||||||
classificationList.appendChild(classificationContainer);
|
classificationList.appendChild(classificationContainer);
|
||||||
console.log(costClassifications);
|
|
||||||
if (costClassifications.length === 0) {
|
if (costClassifications.length === 0) {
|
||||||
const text = "No classifications available";
|
const text = "No classifications available";
|
||||||
const noDataMessage = this.Text(
|
const noDataMessage = this.Text(
|
||||||
@@ -2738,12 +2821,6 @@ export class CostUI {
|
|||||||
"click",
|
"click",
|
||||||
function (event) {
|
function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
console.log(
|
|
||||||
"Remove classification reference from",
|
|
||||||
costItemId,
|
|
||||||
"Classificaition Id",
|
|
||||||
classification.id
|
|
||||||
);
|
|
||||||
callbacks.removeClassificationReference(
|
callbacks.removeClassificationReference(
|
||||||
costItemId,
|
costItemId,
|
||||||
classification.id
|
classification.id
|
||||||
@@ -2819,7 +2896,6 @@ export class CostUI {
|
|||||||
formContainer.appendChild(container);
|
formContainer.appendChild(container);
|
||||||
|
|
||||||
if (!classificationElements || classificationElements.length === 0) {
|
if (!classificationElements || classificationElements.length === 0) {
|
||||||
console.log("No data available");
|
|
||||||
const text =
|
const text =
|
||||||
"No data available - please activate a classification file with BonsaiBIM ";
|
"No data available - please activate a classification file with BonsaiBIM ";
|
||||||
const noDataMessage = this.Text(
|
const noDataMessage = this.Text(
|
||||||
|
|||||||
@@ -667,11 +667,7 @@ class Cost(bonsai.core.tool.Cost):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_cost_schedule(cls, cost_item: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
def get_cost_schedule(cls, cost_item: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||||
for rel in cost_item.HasAssignments or []:
|
return ifcopenshell.util.cost.get_cost_schedule(cost_item)
|
||||||
if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcCostSchedule"):
|
|
||||||
return rel.RelatingControl
|
|
||||||
for rel in cost_item.Nests or []:
|
|
||||||
return cls.get_cost_schedule(rel.RelatingObject)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_cost_schedule_active(cls, cost_schedule: ifcopenshell.entity_instance) -> bool:
|
def is_cost_schedule_active(cls, cost_schedule: ifcopenshell.entity_instance) -> bool:
|
||||||
|
|||||||
@@ -594,6 +594,13 @@ class Web(bonsai.core.tool.Web):
|
|||||||
data_key="message",
|
data_key="message",
|
||||||
event="message",
|
event="message",
|
||||||
)
|
)
|
||||||
|
if operator_data["type"] == "assignCostValues":
|
||||||
|
cost_rate = ifc_file.by_id(operator_data["costRateId"])
|
||||||
|
cost_schedule = tool.Cost.get_cost_schedule(ifc_file.by_id(operator_data["costItemIds"][0]))
|
||||||
|
for cost_item_id in operator_data["costItemIds"]:
|
||||||
|
cost_item = ifc_file.by_id(cost_item_id)
|
||||||
|
tool.Ifc.run("cost.assign_cost_value", cost_item=cost_item, cost_rate=cost_rate)
|
||||||
|
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:
|
||||||
|
|||||||
@@ -55,11 +55,17 @@ class ifc5D2json:
|
|||||||
self.extract_cost_item_quantities(cost_item, data)
|
self.extract_cost_item_quantities(cost_item, data)
|
||||||
self.populate_cost_values(cost_item, data)
|
self.populate_cost_values(cost_item, data)
|
||||||
self.populate_nesting_index(cost_item, data)
|
self.populate_nesting_index(cost_item, data)
|
||||||
|
cost_rate = ifcopenshell.util.cost.get_cost_rate(self.file, cost_item)
|
||||||
data = {**data, **cost_item.get_info(recursive=True)}
|
data = {**data, **cost_item.get_info(recursive=True)}
|
||||||
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)
|
data["Classification"] = self.get_cost_classifications(cost_item)
|
||||||
|
data["CostRate"] = {
|
||||||
|
"id": cost_rate.id() if cost_rate else None,
|
||||||
|
"Identification": cost_rate.Identification if cost_rate else None,
|
||||||
|
"Name": cost_rate.Name if cost_rate else None,
|
||||||
|
}
|
||||||
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:
|
||||||
|
|||||||
@@ -307,6 +307,34 @@ def get_product_quantity_names(elements: list[ifcopenshell.entity_instance]) ->
|
|||||||
return [n for n in names if n != "id"]
|
return [n for n in names if n != "id"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_cost_schedule(cost_item: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||||
|
"""Returns the cost schedule of a cost item.
|
||||||
|
"""
|
||||||
|
for rel in cost_item.HasAssignments or []:
|
||||||
|
if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcCostSchedule"):
|
||||||
|
return rel.RelatingControl
|
||||||
|
for rel in cost_item.Nests or []:
|
||||||
|
return get_cost_schedule(rel.RelatingObject)
|
||||||
|
|
||||||
|
|
||||||
|
def get_cost_rate(
|
||||||
|
file: ifcopenshell_wrapper.file, cost_item: ifcopenshell.entity_instance
|
||||||
|
) -> Optional[ifcopenshell.entity_instance]:
|
||||||
|
"""Returns the cost rate of a cost item.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# There is no direct relationship between a cost item and a cost rate in IFC, so we need to infer it, based on the assumption that cost_rate.CostValues == cost_item.CostValues.
|
||||||
|
if get_cost_schedule(cost_item).PredefinedType == "SCHEDULEOFRATES":
|
||||||
|
return None # Cost item is already a cost rate
|
||||||
|
if cost_item.CostValues:
|
||||||
|
potential_rates = file.get_inverse(cost_item.CostValues[0])
|
||||||
|
for potential_rate in potential_rates:
|
||||||
|
schedule = get_cost_schedule(potential_rate)
|
||||||
|
if schedule.PredefinedType == "SCHEDULEOFRATES":
|
||||||
|
return potential_rate
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class CostValueUnserialiser:
|
class CostValueUnserialiser:
|
||||||
def parse(self, formula: str):
|
def parse(self, formula: str):
|
||||||
l = lark.Lark(
|
l = lark.Lark(
|
||||||
|
|||||||
Reference in New Issue
Block a user