mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-25 09:32:37 +00:00
Cost module web-ui can now:
- assign cost item products from Blender selection - display list of selected products ( with available quantities, subtotals, and highlighting already assigned products) - display and refresh assigned products - highlight loaded cost schedule
This commit is contained in:
@@ -181,6 +181,11 @@ 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):
|
||||||
|
print(f"Selected products from Blender client {sid}")
|
||||||
|
blender_messages[sid]["selected_products"] = data
|
||||||
|
await sio.emit("selected_products", {"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()
|
||||||
|
|||||||
@@ -1,115 +1,135 @@
|
|||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
height: 80%;
|
||||||
user-select: none;
|
border-collapse: collapse;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
td, th {
|
.table-container {
|
||||||
border: 1px solid #ddd;
|
max-height: 70vh;
|
||||||
padding: 8px;
|
margin-top: 1em;
|
||||||
text-align: left;
|
overflow-y: auto;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
form table {
|
||||||
|
height: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
form .table-container {
|
||||||
|
max-height: 50vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 8px;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* first td of a row, must be flex */
|
/* first td of a row, must be flex */
|
||||||
td:first-child {
|
td:first-child {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nested {
|
.nested {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.expand-collapse {
|
.expand-collapse {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.caret-cell {
|
.caret-cell {
|
||||||
border: none;
|
border: none;
|
||||||
background: none;
|
background: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
input, select {
|
input,
|
||||||
background-color: var(--blender-button-background, var(--bg-color));
|
select {
|
||||||
color: var(--blender-button-text, var(--text-color));
|
background-color: var(--blender-button-background, var(--bg-color));
|
||||||
border-color: var(--blender-button-border, var(--border-color));
|
color: var(--blender-button-text, var(--text-color));
|
||||||
transition: filter 0.2s ease;
|
border-color: var(--blender-button-border, var(--border-color));
|
||||||
border-radius: 6px;
|
transition: filter 0.2s ease;
|
||||||
padding: 6px 8px;
|
border-radius: 6px;
|
||||||
font-size: 14px;
|
padding: 6px 8px;
|
||||||
line-height: 20px;
|
font-size: 14px;
|
||||||
transition: border-color 0.2s cubic-bezier(0.3, 0, 0.5, 1);
|
line-height: 20px;
|
||||||
height: inherit;
|
transition: border-color 0.2s cubic-bezier(0.3, 0, 0.5, 1);
|
||||||
margin-left: 0.5em;
|
height: inherit;
|
||||||
margin-right: 0.5em;
|
margin-left: 0.5em;
|
||||||
|
margin-right: 0.5em;
|
||||||
min-width: 50px; /* Minimum width to prevent it from becoming too small */
|
|
||||||
|
|
||||||
|
min-width: 50px; /* Minimum width to prevent it from becoming too small */
|
||||||
}
|
}
|
||||||
|
|
||||||
input:focus {
|
input:focus {
|
||||||
border-color: #28a745;
|
border-color: #28a745;
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: 0 0 0 3px #28a745;
|
box-shadow: 0 0 0 3px #28a745;
|
||||||
}
|
}
|
||||||
|
|
||||||
#cost-items tr:hover, .highlighted {
|
#cost-items tr:hover,
|
||||||
background-color: #ffb936;
|
.highlighted {
|
||||||
color : black;
|
background-color: #ffb936;
|
||||||
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlight {
|
.highlight {
|
||||||
background-color: #ffb936;
|
background-color: #ffb936;
|
||||||
color : black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cost-values-table {
|
.cost-values-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cost-values-table th, .cost-values-table td {
|
.cost-values-table th,
|
||||||
border: 1px solid #ddd;
|
.cost-values-table td {
|
||||||
padding: 8px;
|
border: 1px solid #ddd;
|
||||||
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cost-values-table th {
|
.cost-values-table th {
|
||||||
background-color: #444444;
|
background-color: #444444;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
/* Responsive Styles */
|
/* Responsive Styles */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
table {
|
table {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
td, th {
|
td,
|
||||||
padding: 4px;
|
th {
|
||||||
}
|
padding: 4px;
|
||||||
|
}
|
||||||
input {
|
|
||||||
padding: 2px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
input {
|
||||||
|
padding: 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
table {
|
table {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
td, th {
|
td,
|
||||||
padding: 2px;
|
th {
|
||||||
}
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,130 +1,75 @@
|
|||||||
@import url("./shared.css");
|
@import url("./shared.css");
|
||||||
@import url("./components/card.css");
|
@import url("./components/card.css");
|
||||||
@import url("./components/contextMenu.css");
|
@import url("./components/contextMenu.css");
|
||||||
|
@import url("./components/costTable.css");
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
td:not(:first-child) {
|
|
||||||
text-align: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
border-right: 1px solid #ddd;
|
|
||||||
padding-left: 0.5em;
|
|
||||||
padding-right: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nested {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.expand-collapse {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.caret-cell {
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
input, select {
|
|
||||||
background-color: var(--blender-button-background, var(--bg-color));
|
|
||||||
color: var(--blender-button-text, var(--text-color));
|
|
||||||
border-color: var(--blender-button-border, var(--border-color));
|
|
||||||
transition: filter 0.2s ease;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 6px 8px;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 20px;
|
|
||||||
transition: border-color 0.2s cubic-bezier(0.3, 0, 0.5, 1);
|
|
||||||
height: inherit;
|
|
||||||
margin-left: 0.5em;
|
|
||||||
margin-right: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:focus {
|
|
||||||
border-color: #28a745;
|
|
||||||
outline: none;
|
|
||||||
box-shadow: 0 0 0 3px #28a745;
|
|
||||||
}
|
|
||||||
|
|
||||||
#cost-items tr:hover, .highlighted {
|
|
||||||
background-color: #ffb936;
|
|
||||||
color : black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.highlight {
|
|
||||||
background-color: #ffb936;
|
|
||||||
color : black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-header {
|
.form-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
background-color: #363636;
|
background-color: #363636;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
/* trext size */
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin-left: var(--margin-small);
|
||||||
}
|
}
|
||||||
|
|
||||||
.floating-form {
|
.floating-form {
|
||||||
min-width: 40%;
|
min-width: 40%;
|
||||||
height: auto;
|
max-width: 80%;
|
||||||
position: absolute;
|
max-height: 80vh;
|
||||||
background-color: #363636;
|
height: auto;
|
||||||
border: 1px solid black;
|
position: absolute;
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
background-color: #363636;
|
||||||
z-index: 9999;
|
border: 1px solid black;
|
||||||
cursor: move;
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||||
padding: 20px;
|
z-index: 9999;
|
||||||
user-select: none;
|
cursor: move;
|
||||||
}
|
padding: 20px;
|
||||||
|
user-select: none;
|
||||||
.cost-values-table {
|
overflow-y: auto;
|
||||||
width: 100%;
|
overflow-x: hidden;
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cost-values-table th, .cost-values-table td {
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cost-values-table th {
|
|
||||||
background-color: #444444;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-button {
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
right: 10px;
|
|
||||||
font-size: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-button {
|
.action-button {
|
||||||
background-color: #28a745;
|
background-color: #28a745;
|
||||||
border: none;
|
border: none;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 0.5em 1em;
|
padding: 0.5em 1em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
margin-left: 0.5em;
|
margin: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-button:hover {
|
.action-button:hover {
|
||||||
background-color: #218838;
|
background-color: #218838;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
font-size: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-menu {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
background-color: #363636;
|
||||||
|
border: 1px solid black;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||||
|
z-index: 9999;
|
||||||
|
cursor: move;
|
||||||
|
padding: 20px;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { CostUI } from "./utilities/costui.js";
|
|||||||
const connectedClients = {};
|
const connectedClients = {};
|
||||||
let socket;
|
let socket;
|
||||||
|
|
||||||
let isScheduleLoad = {};
|
let loadedSchedules = {};
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
var defaultTheme = "blender";
|
var defaultTheme = "blender";
|
||||||
@@ -11,7 +11,21 @@ $(document).ready(function () {
|
|||||||
setTheme(theme);
|
setTheme(theme);
|
||||||
|
|
||||||
connectSocket();
|
connectSocket();
|
||||||
CostUI.createColorPicker();
|
|
||||||
|
const settingsMenu = CostUI.Form({
|
||||||
|
id: "settings-menu",
|
||||||
|
name: "Settings",
|
||||||
|
icon: "fa-solid fa-gear",
|
||||||
|
shouldHide: true,
|
||||||
|
});
|
||||||
|
settingsMenu.style.display = "none";
|
||||||
|
const picker = CostUI.createColorPicker();
|
||||||
|
settingsMenu.appendChild(picker);
|
||||||
|
|
||||||
|
const settingsButton = document.getElementById("settings-button");
|
||||||
|
settingsButton.addEventListener("click", function () {
|
||||||
|
settingsMenu.style.display = "block";
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function connectSocket() {
|
function connectSocket() {
|
||||||
@@ -28,6 +42,46 @@ 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelectedProducts(data) {
|
||||||
|
const costItemId = data.data["selected_products"]["cost_item_id"];
|
||||||
|
const products = data.data["selected_products"]["selected_products"];
|
||||||
|
const assigned_products = data.data["selected_products"]["assigned_products"];
|
||||||
|
const quantityNames =
|
||||||
|
data.data["selected_products"]["product_quantity_names"];
|
||||||
|
const formId = "selected-products-" + costItemId;
|
||||||
|
const form = CostUI.Form({
|
||||||
|
id: formId,
|
||||||
|
name: "Selected Products",
|
||||||
|
icon: "fa-solid fa-cart-shopping",
|
||||||
|
});
|
||||||
|
CostUI.highlightElement(costItemId);
|
||||||
|
const selectedProductsTable = CostUI.createProductTable({
|
||||||
|
form,
|
||||||
|
products,
|
||||||
|
quantityNames,
|
||||||
|
costItemId,
|
||||||
|
callbacks: {
|
||||||
|
addProductAssignments: addProductAssignments,
|
||||||
|
getSelectedProducts: getSelectedProducts,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (assigned_products.length > 0) {
|
||||||
|
const assignedProductsText = CostUI.Text(
|
||||||
|
"Assigned Products",
|
||||||
|
"fa-solid fa-box",
|
||||||
|
"x-large"
|
||||||
|
);
|
||||||
|
form.appendChild(assignedProductsText);
|
||||||
|
const assignmentsTable = CostUI.createAssignmentsTable({
|
||||||
|
form,
|
||||||
|
products: assigned_products,
|
||||||
|
quantityNames,
|
||||||
|
costItemId,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePredefinedTypes(data) {
|
function handlePredefinedTypes(data) {
|
||||||
@@ -37,6 +91,15 @@ function handlePredefinedTypes(data) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addProductAssignments({ costItemId, selectedQuantity, productIds }) {
|
||||||
|
executeOperator({
|
||||||
|
type: "addProductAssignments",
|
||||||
|
costItemId: costItemId,
|
||||||
|
propName: selectedQuantity,
|
||||||
|
productIds: productIds,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function addCostSchedule(name, predefinedType) {
|
function addCostSchedule(name, predefinedType) {
|
||||||
executeOperator({
|
executeOperator({
|
||||||
type: "addCostSchedule",
|
type: "addCostSchedule",
|
||||||
@@ -109,7 +172,7 @@ function handleCostValuesData(data) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
CostUI.highlightCostItem(data.data["cost_values"]["cost_item_id"]);
|
CostUI.highlightElement(data.data["cost_values"]["cost_item_id"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addCostValue(costItemId) {
|
function addCostValue(costItemId) {
|
||||||
@@ -200,6 +263,9 @@ function addSummaryCostItem(costScheduleId) {
|
|||||||
|
|
||||||
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;
|
||||||
@@ -210,24 +276,50 @@ function handleCostSchedulesData(data) {
|
|||||||
const costScheduleDiv = document.getElementById("cost-schedules");
|
const costScheduleDiv = document.getElementById("cost-schedules");
|
||||||
costScheduleDiv.innerHTML = "";
|
costScheduleDiv.innerHTML = "";
|
||||||
costSchedules.forEach((costSchedule) => {
|
costSchedules.forEach((costSchedule) => {
|
||||||
costSchedule.UpdateDate = new Date(costSchedule.UpdateDate);
|
const predefinedType = CostUI.Text(
|
||||||
const predefinedType = CostUI.text("Predefined Type: " + costSchedule.PredefinedType)
|
"Predefined Type: " + costSchedule.PredefinedType,
|
||||||
const updatedOn = CostUI.text("Updated On: " + costSchedule.UpdateDate);
|
"fa-solid fa-paperclip"
|
||||||
|
);
|
||||||
|
// simplyfying the date
|
||||||
|
const updatedDate = new Date(costSchedule.UpdateDate).toLocaleDateString();
|
||||||
|
const updatedOn = CostUI.Text(
|
||||||
|
"Updated On: " + updatedDate,
|
||||||
|
"fa-solid fa-pen-nib"
|
||||||
|
);
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
|
|
||||||
div.appendChild(predefinedType);
|
div.appendChild(predefinedType);
|
||||||
div.appendChild(updatedOn);
|
div.appendChild(updatedOn);
|
||||||
const callback = () => loadCostSchedule(costSchedule.id, blenderId);
|
const callback = () => loadCostSchedule(costSchedule.id, blenderId);
|
||||||
|
|
||||||
const card = CostUI.createCard(costSchedule.Name, div, callback);
|
const card = CostUI.createCard(
|
||||||
|
costSchedule.id,
|
||||||
|
costSchedule.Name,
|
||||||
|
div,
|
||||||
|
callback
|
||||||
|
);
|
||||||
costScheduleDiv.append(card);
|
costScheduleDiv.append(card);
|
||||||
});
|
});
|
||||||
|
loadedSchedule ? CostUI.highlightElement("schedule-" + loadedSchedule) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCostItemsData(data) {
|
function handleCostItemsData(data) {
|
||||||
|
const cards = document.querySelectorAll("[id^='schedule-']");
|
||||||
|
cards.forEach((card) => {
|
||||||
|
if (card.id === "schedule-" + data.data["cost_items"]["cost_schedule_id"]) {
|
||||||
|
card.classList.add("highlighted");
|
||||||
|
} else {
|
||||||
|
card.classList.remove("highlighted");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const costScheduleId = data.data["cost_items"]["cost_schedule_id"];
|
||||||
|
loadedSchedules[data.blenderId] = costScheduleId;
|
||||||
|
CostUI.highlightElement("schedule-" + costScheduleId);
|
||||||
|
|
||||||
CostUI.createCostSchedule({
|
CostUI.createCostSchedule({
|
||||||
data: data.data["cost_items"]["cost_items"],
|
data: data.data["cost_items"]["cost_items"],
|
||||||
costScheduleId: data.data["cost_items"]["cost_schedule_id"],
|
costScheduleId: costScheduleId,
|
||||||
blenderID: data.blenderId,
|
blenderID: data.blenderId,
|
||||||
callbacks: {
|
callbacks: {
|
||||||
addCostItem: addCostItem,
|
addCostItem: addCostItem,
|
||||||
@@ -237,10 +329,15 @@ function handleCostItemsData(data) {
|
|||||||
editCostItemName: editCostItemName,
|
editCostItemName: editCostItemName,
|
||||||
enableEditingCostValues: enableEditingCostValues,
|
enableEditingCostValues: enableEditingCostValues,
|
||||||
addSummaryCostItem: addSummaryCostItem,
|
addSummaryCostItem: addSummaryCostItem,
|
||||||
|
getSelectedProducts: getSelectedProducts,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSelectedProducts(costItemId) {
|
||||||
|
executeOperator({ type: "getSelectedProducts", costItemId: costItemId });
|
||||||
|
}
|
||||||
|
|
||||||
function duplicateCostItem(costItemId) {
|
function duplicateCostItem(costItemId) {
|
||||||
executeOperator({ type: "duplicateCostItem", costItemId: costItemId });
|
executeOperator({ type: "duplicateCostItem", costItemId: costItemId });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ function handleWorkScheduleData(data) {
|
|||||||
workSchedules.forEach((workSchedule) => {
|
workSchedules.forEach((workSchedule) => {
|
||||||
const mainContainer = CostUI.text(new Date(workSchedule.CreationDate).toLocaleDateString());
|
const mainContainer = CostUI.text(new Date(workSchedule.CreationDate).toLocaleDateString());
|
||||||
const callback = () => loadWorkSchedule(workSchedule.id);
|
const callback = () => loadWorkSchedule(workSchedule.id);
|
||||||
const card = CostUI.createCard(workSchedule.Name,mainContainer, callback);
|
const card = CostUI.createCard(workSchedule.id, workSchedule.Name,mainContainer, callback);
|
||||||
workSchedulesDiv.append(card);
|
workSchedulesDiv.append(card);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export class CostUI {
|
|||||||
`;
|
`;
|
||||||
document.head.appendChild(style);
|
document.head.appendChild(style);
|
||||||
}
|
}
|
||||||
static createContextMenu(callbacks) {
|
static createContextMenu(callbacks) {
|
||||||
const contextMenu = document.createElement("div");
|
const contextMenu = document.createElement("div");
|
||||||
contextMenu.id = "context-menu";
|
contextMenu.id = "context-menu";
|
||||||
contextMenu.classList.add("context-menu");
|
contextMenu.classList.add("context-menu");
|
||||||
@@ -78,93 +78,107 @@ static createContextMenu(callbacks) {
|
|||||||
<button id="add-cost-item-button">Add sub-cost</button>
|
<button id="add-cost-item-button">Add sub-cost</button>
|
||||||
<button id="duplicate-button">Duplicate</button>
|
<button id="duplicate-button">Duplicate</button>
|
||||||
<button id="delete-cost-item">Delete</button>
|
<button id="delete-cost-item">Delete</button>
|
||||||
|
<button id="assign-selection">Assign Selection</button>
|
||||||
`;
|
`;
|
||||||
document.body.appendChild(contextMenu);
|
document.body.appendChild(contextMenu);
|
||||||
|
|
||||||
document.addEventListener("contextmenu", function (event) {
|
document.addEventListener("contextmenu", function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
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 && targetRow.parentElement.id === "cost-items") {
|
if (targetRow && targetRow.parentElement.id === "cost-items") {
|
||||||
const contextMenu = document.getElementById("context-menu");
|
const contextMenu = document.getElementById("context-menu");
|
||||||
contextMenu.style.display = "block";
|
contextMenu.style.display = "block";
|
||||||
contextMenu.style.left = `${event.pageX}px`;
|
contextMenu.style.left = `${event.pageX}px`;
|
||||||
contextMenu.style.top = `${event.pageY}px`;
|
contextMenu.style.top = `${event.pageY}px`;
|
||||||
|
|
||||||
contextMenu.targetRow = targetRow;
|
contextMenu.targetRow = targetRow;
|
||||||
contextMenu.targetCell = targetCell;
|
contextMenu.targetCell = targetCell;
|
||||||
} else {
|
} else {
|
||||||
document.getElementById("context-menu").style.display = "none";
|
document.getElementById("context-menu").style.display = "none";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("click", function (event) {
|
document.addEventListener("click", function (event) {
|
||||||
const contextMenu = document.getElementById("context-menu");
|
const contextMenu = document.getElementById("context-menu");
|
||||||
if (!contextMenu.contains(event.target)) {
|
if (!contextMenu.contains(event.target)) {
|
||||||
contextMenu.style.display = "none";
|
contextMenu.style.display = "none";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const editCostValuesButton = document.getElementById(
|
const editCostValuesButton = document.getElementById(
|
||||||
"edit-cost-values-button"
|
"edit-cost-values-button"
|
||||||
);
|
);
|
||||||
editCostValuesButton.addEventListener("click", function () {
|
editCostValuesButton.addEventListener("click", function () {
|
||||||
const targetRow = document.getElementById("context-menu").targetRow;
|
const targetRow = document.getElementById("context-menu").targetRow;
|
||||||
const targetCell = document.getElementById("context-menu").targetCell;
|
const targetCell = document.getElementById("context-menu").targetCell;
|
||||||
if (targetRow) {
|
if (targetRow) {
|
||||||
const costItemId = parseInt(targetRow.getAttribute("id"));
|
const costItemId = parseInt(targetRow.getAttribute("id"));
|
||||||
callbacks.enableEditingCostValues
|
callbacks.enableEditingCostValues
|
||||||
? callbacks.enableEditingCostValues(costItemId)
|
? callbacks.enableEditingCostValues(costItemId)
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
document.getElementById("context-menu").style.display = "none";
|
document.getElementById("context-menu").style.display = "none";
|
||||||
});
|
});
|
||||||
|
|
||||||
const addButton = document.getElementById("add-cost-item-button");
|
const addButton = document.getElementById("add-cost-item-button");
|
||||||
if (!addButton.dataset.listenerAdded) {
|
if (!addButton.dataset.listenerAdded) {
|
||||||
function addCostItemHandler(e) {
|
function addCostItemHandler(e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
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.addCostItem ? callbacks.addCostItem(costItemId) : null;
|
callbacks.addCostItem ? callbacks.addCostItem(costItemId) : null;
|
||||||
}
|
|
||||||
document.getElementById("context-menu").style.display = "none";
|
|
||||||
}
|
}
|
||||||
addButton.addEventListener("click", addCostItemHandler);
|
document.getElementById("context-menu").style.display = "none";
|
||||||
addButton.dataset.listenerAdded = "true";
|
}
|
||||||
|
addButton.addEventListener("click", addCostItemHandler);
|
||||||
|
addButton.dataset.listenerAdded = "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteCostItem = document.getElementById("delete-cost-item");
|
const deleteCostItem = document.getElementById("delete-cost-item");
|
||||||
if (deleteCostItem && !deleteCostItem.hasListener) {
|
if (deleteCostItem && !deleteCostItem.hasListener) {
|
||||||
deleteCostItem.addEventListener("click", function () {
|
deleteCostItem.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 =
|
let expandedState =
|
||||||
JSON.parse(localStorage.getItem("expandedState")) || {};
|
JSON.parse(localStorage.getItem("expandedState")) || {};
|
||||||
expandedState = CostUI.deleteRow(targetRow, expandedState);
|
expandedState = CostUI.deleteRow(targetRow, expandedState);
|
||||||
localStorage.setItem("expandedState", JSON.stringify(expandedState));
|
localStorage.setItem("expandedState", JSON.stringify(expandedState));
|
||||||
callbacks.deleteCostItem(costItemId);
|
callbacks.deleteCostItem(costItemId);
|
||||||
}
|
}
|
||||||
document.getElementById("context-menu").style.display = "none";
|
document.getElementById("context-menu").style.display = "none";
|
||||||
});
|
});
|
||||||
deleteCostItem.hasListener = true;
|
deleteCostItem.hasListener = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const duplicateButton = document.getElementById("duplicate-button");
|
const duplicateButton = document.getElementById("duplicate-button");
|
||||||
if (duplicateButton && !duplicateButton.hasListener) {
|
if (duplicateButton && !duplicateButton.hasListener) {
|
||||||
duplicateButton.addEventListener("click", function () {
|
duplicateButton.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"));
|
||||||
callbacks.duplicateCostItem(costItemId);
|
callbacks.duplicateCostItem(costItemId);
|
||||||
}
|
}
|
||||||
document.getElementById("context-menu").style.display = "none";
|
document.getElementById("context-menu").style.display = "none";
|
||||||
});
|
});
|
||||||
duplicateButton.hasListener = true;
|
duplicateButton.hasListener = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
const getSelectedProducts = document.getElementById("assign-selection");
|
||||||
|
if (getSelectedProducts && !getSelectedProducts.hasListener) {
|
||||||
|
getSelectedProducts.addEventListener("click", function () {
|
||||||
|
const targetRow = document.getElementById("context-menu").targetRow;
|
||||||
|
if (targetRow) {
|
||||||
|
const costItemId = parseInt(targetRow.getAttribute("id"));
|
||||||
|
callbacks.getSelectedProducts(costItemId);
|
||||||
|
}
|
||||||
|
document.getElementById("context-menu").style.display = "none";
|
||||||
|
});
|
||||||
|
getSelectedProducts.hasListener = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static deleteRow(targetRow, expandedState) {
|
static deleteRow(targetRow, expandedState) {
|
||||||
if (!targetRow) {
|
if (!targetRow) {
|
||||||
@@ -244,7 +258,6 @@ static createContextMenu(callbacks) {
|
|||||||
const form = CostUI.Form({
|
const form = CostUI.Form({
|
||||||
id: "add-cost-schedule-form",
|
id: "add-cost-schedule-form",
|
||||||
name: name,
|
name: name,
|
||||||
className: "floating-form",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const nameLabel = document.createElement("label");
|
const nameLabel = document.createElement("label");
|
||||||
@@ -279,8 +292,6 @@ static createContextMenu(callbacks) {
|
|||||||
submitButton.classList.add("action-button");
|
submitButton.classList.add("action-button");
|
||||||
form.appendChild(submitButton);
|
form.appendChild(submitButton);
|
||||||
|
|
||||||
document.body.appendChild(form);
|
|
||||||
|
|
||||||
form.addEventListener("submit", function (event) {
|
form.addEventListener("submit", function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
@@ -296,7 +307,7 @@ static createContextMenu(callbacks) {
|
|||||||
|
|
||||||
static createColorPicker() {
|
static createColorPicker() {
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.classList.add("card");
|
div.classList.add("row-container");
|
||||||
const colorPicker = document.createElement("input");
|
const colorPicker = document.createElement("input");
|
||||||
colorPicker.type = "color";
|
colorPicker.type = "color";
|
||||||
colorPicker.id = "color-picker";
|
colorPicker.id = "color-picker";
|
||||||
@@ -305,7 +316,6 @@ static createContextMenu(callbacks) {
|
|||||||
colorText.textContent = "Select a color to change row color:";
|
colorText.textContent = "Select a color to change row color:";
|
||||||
div.appendChild(colorText);
|
div.appendChild(colorText);
|
||||||
div.appendChild(colorPicker);
|
div.appendChild(colorPicker);
|
||||||
document.getElementById("UI").appendChild(div);
|
|
||||||
|
|
||||||
colorPicker.addEventListener("input", function () {
|
colorPicker.addEventListener("input", function () {
|
||||||
const baseColor = colorPicker.value;
|
const baseColor = colorPicker.value;
|
||||||
@@ -314,6 +324,7 @@ static createContextMenu(callbacks) {
|
|||||||
});
|
});
|
||||||
const colorScheme = CostUI.generateColorScheme("#000000");
|
const colorScheme = CostUI.generateColorScheme("#000000");
|
||||||
CostUI.applyColorScheme("cost-items", colorScheme);
|
CostUI.applyColorScheme("cost-items", colorScheme);
|
||||||
|
return div;
|
||||||
}
|
}
|
||||||
|
|
||||||
static createCostSchedule({
|
static createCostSchedule({
|
||||||
@@ -373,7 +384,10 @@ static createContextMenu(callbacks) {
|
|||||||
const totalQuantity = costItem.TotalCostQuantity
|
const totalQuantity = costItem.TotalCostQuantity
|
||||||
? parseFloat(costItem.TotalCostQuantity).toFixed(2)
|
? parseFloat(costItem.TotalCostQuantity).toFixed(2)
|
||||||
: "-";
|
: "-";
|
||||||
const row = CostUI.createTableRow(costItem, nestingLevel, parentID);
|
const appliedValue = costItem.TotalAppliedValue
|
||||||
|
? parseFloat(costItem.TotalAppliedValue).toFixed(2)
|
||||||
|
: "-";
|
||||||
|
const row = CostUI.createCostItemRow(costItem, nestingLevel, parentID);
|
||||||
const expandButton = CostUI.createExpandButton(costItem);
|
const expandButton = CostUI.createExpandButton(costItem);
|
||||||
const nameCell = CostUI.createNameCell(
|
const nameCell = CostUI.createNameCell(
|
||||||
costItem,
|
costItem,
|
||||||
@@ -383,9 +397,7 @@ static createContextMenu(callbacks) {
|
|||||||
);
|
);
|
||||||
const totalCostQuantityCell = CostUI.createTableCell(totalQuantity);
|
const totalCostQuantityCell = CostUI.createTableCell(totalQuantity);
|
||||||
const unitSymbolCell = CostUI.createTableCell(costItem.UnitSymbol);
|
const unitSymbolCell = CostUI.createTableCell(costItem.UnitSymbol);
|
||||||
const totalAppliedValueCell = CostUI.createTableCell(
|
const totalAppliedValueCell = CostUI.createTableCell(appliedValue);
|
||||||
costItem.TotalAppliedValue
|
|
||||||
);
|
|
||||||
const totalCostCell = CostUI.createTotalCostCell(costItem);
|
const totalCostCell = CostUI.createTotalCostCell(costItem);
|
||||||
const flexContainerCell = CostUI.createFlexContainerCell(
|
const flexContainerCell = CostUI.createFlexContainerCell(
|
||||||
costItem,
|
costItem,
|
||||||
@@ -411,7 +423,7 @@ static createContextMenu(callbacks) {
|
|||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
static createTableRow(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);
|
||||||
row.setAttribute("parent-id", parentID);
|
row.setAttribute("parent-id", parentID);
|
||||||
@@ -613,17 +625,17 @@ static createContextMenu(callbacks) {
|
|||||||
return selectButton;
|
return selectButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
static highlightCostItem(id) {
|
static highlightElement(id) {
|
||||||
const row = document.getElementById(id);
|
const element = document.getElementById(id);
|
||||||
if (row) {
|
if (element) {
|
||||||
row.classList.add("highlighted");
|
element.classList.add("highlighted");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static unhighlightCostItem(id) {
|
static unhighlightElement(id) {
|
||||||
const row = document.getElementById(id);
|
const element = document.getElementById(id);
|
||||||
if (row) {
|
if (element) {
|
||||||
row.classList.remove("highlighted");
|
element.classList.remove("highlighted");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -636,15 +648,26 @@ static createContextMenu(callbacks) {
|
|||||||
return ids;
|
return ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
static text(label) {
|
static Text(label, icon = null, size = "medium") {
|
||||||
const text = document.createElement("p");
|
const text = document.createElement("span");
|
||||||
text.textContent = label;
|
text.textContent = label;
|
||||||
return text;
|
if (icon === null) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.classList.add("row-container");
|
||||||
|
const i = document.createElement("i");
|
||||||
|
i.className = icon;
|
||||||
|
div.appendChild(i);
|
||||||
|
div.appendChild(text);
|
||||||
|
div.style.fontSize = size;
|
||||||
|
return div;
|
||||||
}
|
}
|
||||||
|
|
||||||
static createCard(title, mainContainer, callback) {
|
static createCard(id, title, mainContainer, callback) {
|
||||||
const card = document.createElement("div");
|
const card = document.createElement("div");
|
||||||
card.classList.add("card");
|
card.classList.add("card");
|
||||||
|
card.id = "schedule-" + id;
|
||||||
|
|
||||||
const cardBody = document.createElement("div");
|
const cardBody = document.createElement("div");
|
||||||
cardBody.classList.add("card-body");
|
cardBody.classList.add("card-body");
|
||||||
@@ -684,30 +707,26 @@ static createContextMenu(callbacks) {
|
|||||||
|
|
||||||
let formName = "Cost Values: " + costItemName;
|
let formName = "Cost Values: " + costItemName;
|
||||||
const form = CostUI.Form({
|
const form = CostUI.Form({
|
||||||
|
icon: "fa-dollar-sign",
|
||||||
id: formId,
|
id: formId,
|
||||||
name: formName,
|
name: formName,
|
||||||
className: "floating-form",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const table = CostUI.createCostValuesTable(costItemId, [
|
const { tableContainer, table } = CostUI.addTable({
|
||||||
"Type",
|
headers: ["Type", "Category", "Value", ""],
|
||||||
"Category",
|
className: "cost-values-table",
|
||||||
"Value",
|
id: "cost-values-table-" + costItemId,
|
||||||
"",
|
});
|
||||||
]);
|
|
||||||
|
|
||||||
costValues.forEach((costValue) => {
|
costValues.forEach((costValue) => {
|
||||||
const tr = CostUI.createCostvaluesRow(costItemId, costValue, callbacks);
|
const tr = CostUI.createCostvaluesRow(costItemId, costValue, callbacks);
|
||||||
table.appendChild(tr);
|
table.appendChild(tr);
|
||||||
});
|
});
|
||||||
|
|
||||||
form.appendChild(table);
|
|
||||||
|
|
||||||
const addButton = CostUI.createAddCostValueButton(costItemId, callbacks);
|
const addButton = CostUI.createAddCostValueButton(costItemId, callbacks);
|
||||||
|
|
||||||
|
form.appendChild(tableContainer);
|
||||||
form.appendChild(addButton);
|
form.appendChild(addButton);
|
||||||
|
|
||||||
document.body.appendChild(form);
|
|
||||||
|
|
||||||
return form;
|
return form;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -735,10 +754,13 @@ static createContextMenu(callbacks) {
|
|||||||
table.appendChild(tr);
|
table.appendChild(tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
static createCostValuesTable(costItemId, headers) {
|
static addTable({ id, className, headers }) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.classList.add("table-container");
|
||||||
const table = document.createElement("table");
|
const table = document.createElement("table");
|
||||||
table.classList.add("cost-values-table");
|
div.appendChild(table);
|
||||||
table.id = "cost-values-table-" + costItemId;
|
table.classList.add(className);
|
||||||
|
table.id = id;
|
||||||
|
|
||||||
const headerRow = document.createElement("tr");
|
const headerRow = document.createElement("tr");
|
||||||
headers.forEach((headerText) => {
|
headers.forEach((headerText) => {
|
||||||
@@ -746,9 +768,17 @@ static createContextMenu(callbacks) {
|
|||||||
th.textContent = headerText;
|
th.textContent = headerText;
|
||||||
headerRow.appendChild(th);
|
headerRow.appendChild(th);
|
||||||
});
|
});
|
||||||
table.appendChild(headerRow);
|
const thead = document.createElement("thead");
|
||||||
|
thead.appendChild(headerRow);
|
||||||
return table;
|
table.appendChild(thead);
|
||||||
|
table.get_header_row = function () {
|
||||||
|
return headerRow;
|
||||||
|
};
|
||||||
|
table.get_header_by_name = function (name) {
|
||||||
|
const headers = headerRow.querySelectorAll("th");
|
||||||
|
return Array.from(headers).find((header) => header.textContent === name);
|
||||||
|
};
|
||||||
|
return { tableContainer: div, table: table };
|
||||||
}
|
}
|
||||||
|
|
||||||
static createCostvaluesRow(costItemId, costValue, costValueCallbacks) {
|
static createCostvaluesRow(costItemId, costValue, costValueCallbacks) {
|
||||||
@@ -917,19 +947,31 @@ static createContextMenu(callbacks) {
|
|||||||
return addButton;
|
return addButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Form({ id, name, className }) {
|
static Form({ id, name, icon = "fa-solid fa-file", shouldHide = false }) {
|
||||||
const form = document.createElement("form");
|
let form;
|
||||||
form.id = id;
|
if (document.getElementById(id)) {
|
||||||
form.classList.add(className);
|
form = document.getElementById(id);
|
||||||
form.style.position = "absolute";
|
} else {
|
||||||
form.style.top = "50%";
|
form = document.createElement("form");
|
||||||
form.style.left = "50%";
|
form.id = id;
|
||||||
form.style.transform = "translate(-50%, -50%)";
|
form.classList.add("floating-form");
|
||||||
const header = CostUI.createHeader(name);
|
form.style.position = "absolute";
|
||||||
const closeButton = CostUI.createCloseButton(form);
|
form.style.top = "50%";
|
||||||
|
form.style.left = "50%";
|
||||||
|
form.style.transform = "translate(-50%, -50%)";
|
||||||
|
}
|
||||||
|
|
||||||
|
const header = CostUI.createHeader(name, icon);
|
||||||
|
let closeButton;
|
||||||
|
if (shouldHide) {
|
||||||
|
closeButton = CostUI.createHideButton(form);
|
||||||
|
} else {
|
||||||
|
closeButton = CostUI.createCloseButton(form);
|
||||||
|
}
|
||||||
form.appendChild(header);
|
form.appendChild(header);
|
||||||
form.appendChild(closeButton);
|
form.appendChild(closeButton);
|
||||||
CostUI.makeFormDraggable(form);
|
CostUI.makeFormDraggable(form);
|
||||||
|
document.body.appendChild(form);
|
||||||
return form;
|
return form;
|
||||||
}
|
}
|
||||||
static makeFormDraggable(form) {
|
static makeFormDraggable(form) {
|
||||||
@@ -990,23 +1032,389 @@ static createContextMenu(callbacks) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static createHeader(text) {
|
static createHeader(text, icon) {
|
||||||
const header = document.createElement("div");
|
const header = document.createElement("div");
|
||||||
|
const i = document.createElement("i");
|
||||||
|
i.className = icon;
|
||||||
header.classList.add("form-header");
|
header.classList.add("form-header");
|
||||||
header.textContent = text;
|
const span = document.createElement("span");
|
||||||
|
span.textContent = text;
|
||||||
|
header.appendChild(i);
|
||||||
|
header.appendChild(span);
|
||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static createHideButton(form) {
|
||||||
|
const hideButton = document.createElement("span");
|
||||||
|
hideButton.classList.add("close-button");
|
||||||
|
hideButton.innerHTML = "−";
|
||||||
|
hideButton.addEventListener("click", function () {
|
||||||
|
form.style.display = "none";
|
||||||
|
});
|
||||||
|
return hideButton;
|
||||||
|
}
|
||||||
|
|
||||||
static createCloseButton(form) {
|
static createCloseButton(form) {
|
||||||
const closeButton = document.createElement("span");
|
const closeButton = document.createElement("span");
|
||||||
closeButton.classList.add("close-button");
|
closeButton.classList.add("close-button");
|
||||||
closeButton.innerHTML = "×";
|
closeButton.innerHTML = "×";
|
||||||
closeButton.addEventListener("click", function () {
|
closeButton.addEventListener("click", function () {
|
||||||
form.remove();
|
form.remove();
|
||||||
const costItemId = form.id.split("-")[3];
|
const costItemId = form.id.split("-").pop();
|
||||||
CostUI.unhighlightCostItem(costItemId);
|
CostUI.unhighlightElement(costItemId);
|
||||||
});
|
});
|
||||||
|
|
||||||
return closeButton;
|
return closeButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static createProductRow(product, shouldAddQto = false) {
|
||||||
|
const row = document.createElement("tr");
|
||||||
|
|
||||||
|
const cellData = {
|
||||||
|
id: product.info.id,
|
||||||
|
class: product.info.class,
|
||||||
|
name: product.info.name,
|
||||||
|
type: product.info.type,
|
||||||
|
count: "1",
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(cellData)) {
|
||||||
|
const cell = document.createElement("td");
|
||||||
|
cell.textContent = value;
|
||||||
|
row.appendChild(cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldAddQto) {
|
||||||
|
const quantitySets = product.qtos;
|
||||||
|
Object.entries(quantitySets).forEach(([qtoName, qto]) => {
|
||||||
|
Object.entries(qto).forEach(([key, value]) => {
|
||||||
|
if (key !== "id") {
|
||||||
|
const cell = document.createElement("td");
|
||||||
|
// check if value is a number
|
||||||
|
cell.textContent = parseFloat(value).toFixed(2);
|
||||||
|
row.appendChild(cell);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
static addQuantityHeaders(table, products, shouldAddQto = true) {
|
||||||
|
const headerRow = table.get_header_row();
|
||||||
|
const countHeader = table.get_header_by_name("Count");
|
||||||
|
const quantitySums = { count: 0 };
|
||||||
|
const quantityHeaders = { count: countHeader };
|
||||||
|
if (!shouldAddQto) return { quantitySums, quantityHeaders };
|
||||||
|
if (products.length > 0) {
|
||||||
|
const quantitySets = products[0].qtos;
|
||||||
|
Object.entries(quantitySets).forEach(([qtoName, qto]) => {
|
||||||
|
Object.keys(qto).forEach((key) => {
|
||||||
|
if (key !== "id") {
|
||||||
|
const qtoHeader = document.createElement("th");
|
||||||
|
qtoHeader.textContent = key;
|
||||||
|
qtoHeader.style.cursor = "pointer";
|
||||||
|
headerRow.appendChild(qtoHeader);
|
||||||
|
quantitySums[key] = 0;
|
||||||
|
quantityHeaders[key] = qtoHeader;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { quantitySums, quantityHeaders };
|
||||||
|
}
|
||||||
|
|
||||||
|
static getAssinedQuantity(products, costItemId) {
|
||||||
|
let quantityAssigned = null;
|
||||||
|
products.forEach((product) => {
|
||||||
|
const product_id = product.info.id;
|
||||||
|
const assignedQuantities = product.assigned_quantities;
|
||||||
|
if (assignedQuantities.length > 0) {
|
||||||
|
assignedQuantities.forEach((assignedQuantity) => {
|
||||||
|
if (assignedQuantity.cost_item_id === costItemId) {
|
||||||
|
product.isProductAssigned = true;
|
||||||
|
quantityAssigned = assignedQuantity.name;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return quantityAssigned;
|
||||||
|
}
|
||||||
|
|
||||||
|
static calculateSums(qtos, quantitySums) {
|
||||||
|
Object.entries(qtos).forEach(([qtoName, qto]) => {
|
||||||
|
Object.entries(qto).forEach(([key, value]) => {
|
||||||
|
if (key !== "id") {
|
||||||
|
quantitySums[key] += parseFloat(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static countProducts(products, quantitySums) {
|
||||||
|
products.forEach((product) => {
|
||||||
|
quantitySums.count += 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static addSubTotalRow(quantitySums, shouldAddQto = true) {
|
||||||
|
const subtotalRow = document.createElement("tr");
|
||||||
|
|
||||||
|
const cells = [
|
||||||
|
{ textContent: "Subtotal", colSpan: 4, style: { border: "none" } },
|
||||||
|
{ textContent: "", style: { border: "none" } },
|
||||||
|
{ textContent: "", style: { border: "none" } },
|
||||||
|
{ textContent: "", style: { border: "none" } },
|
||||||
|
{ textContent: quantitySums.count },
|
||||||
|
];
|
||||||
|
|
||||||
|
cells.forEach((cellData) => {
|
||||||
|
const cell = document.createElement("td");
|
||||||
|
if (cellData.textContent !== undefined)
|
||||||
|
cell.textContent = cellData.textContent;
|
||||||
|
if (cellData.colSpan !== undefined) cell.colSpan = cellData.colSpan;
|
||||||
|
if (cellData.style !== undefined)
|
||||||
|
Object.assign(cell.style, cellData.style);
|
||||||
|
subtotalRow.appendChild(cell);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (shouldAddQto) {
|
||||||
|
Object.keys(quantitySums).forEach((key) => {
|
||||||
|
if (key !== "count") {
|
||||||
|
const subtotalCell = document.createElement("td");
|
||||||
|
subtotalCell.textContent = quantitySums[key].toFixed(2);
|
||||||
|
subtotalRow.appendChild(subtotalCell);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
subtotalRow.style.fontWeight = "bold";
|
||||||
|
|
||||||
|
return subtotalRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
static createProductTable({
|
||||||
|
form,
|
||||||
|
products,
|
||||||
|
quantityNames,
|
||||||
|
costItemId,
|
||||||
|
callbacks,
|
||||||
|
}) {
|
||||||
|
const { tableContainer, table } = CostUI.addTable({
|
||||||
|
headers: ["Step Id", "Class", "Name", "Type", "Count"],
|
||||||
|
className: "product-table",
|
||||||
|
id: "cost-values-table-" + costItemId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tbody = document.createElement("tbody");
|
||||||
|
table.appendChild(tbody);
|
||||||
|
form.appendChild(tableContainer);
|
||||||
|
|
||||||
|
const quantityAssigned = CostUI.getAssinedQuantity(products, costItemId);
|
||||||
|
const areProductsSameClass = products.every(
|
||||||
|
(product) => product.info.class === products[0].info.class
|
||||||
|
);
|
||||||
|
|
||||||
|
const shouldAddQto = areProductsSameClass ? true : false;
|
||||||
|
const headerRow = table.get_header_row();
|
||||||
|
const { quantitySums, quantityHeaders } = CostUI.addQuantityHeaders(
|
||||||
|
table,
|
||||||
|
products,
|
||||||
|
shouldAddQto
|
||||||
|
);
|
||||||
|
|
||||||
|
if (shouldAddQto) {
|
||||||
|
CostUI.highlightColumn(
|
||||||
|
table,
|
||||||
|
headerRow,
|
||||||
|
quantityHeaders,
|
||||||
|
quantityAssigned
|
||||||
|
);
|
||||||
|
|
||||||
|
products.forEach((product) => {
|
||||||
|
const row = CostUI.createProductRow(product, shouldAddQto);
|
||||||
|
tbody.appendChild(row);
|
||||||
|
|
||||||
|
const isProductAssigned = product.assigned_quantities
|
||||||
|
? product.assigned_quantities.some(
|
||||||
|
(assignedQuantity) => assignedQuantity.cost_item_id === costItemId
|
||||||
|
)
|
||||||
|
: false;
|
||||||
|
isProductAssigned ? row.classList.add("highlighted") : null;
|
||||||
|
|
||||||
|
CostUI.calculateSums(product.qtos, quantitySums);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
products.forEach((product) => {
|
||||||
|
const row = CostUI.createProductRow(product, shouldAddQto);
|
||||||
|
tbody.appendChild(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
CostUI.countProducts(products, quantitySums);
|
||||||
|
|
||||||
|
const subtotalRow = CostUI.addSubTotalRow(quantitySums, shouldAddQto);
|
||||||
|
tbody.appendChild(subtotalRow);
|
||||||
|
|
||||||
|
const quantitySelect = document.createElement("select");
|
||||||
|
|
||||||
|
if (shouldAddQto) {
|
||||||
|
quantityNames.forEach((name) => {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = name;
|
||||||
|
option.text = name;
|
||||||
|
quantitySelect.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = "count";
|
||||||
|
option.text = "count";
|
||||||
|
quantitySelect.appendChild(option);
|
||||||
|
|
||||||
|
quantitySelect.addEventListener("change", (event) => {
|
||||||
|
const selectedQuantity = event.target.value;
|
||||||
|
|
||||||
|
Object.values(quantityHeaders).forEach((header) => {
|
||||||
|
header.classList.remove("highlighted");
|
||||||
|
});
|
||||||
|
table.querySelectorAll("td").forEach((cell) => {
|
||||||
|
cell.classList.remove("highlighted");
|
||||||
|
});
|
||||||
|
|
||||||
|
if (quantityHeaders[selectedQuantity]) {
|
||||||
|
quantityHeaders[selectedQuantity].classList.add("highlighted");
|
||||||
|
const columnIndex = Array.from(headerRow.children).indexOf(
|
||||||
|
quantityHeaders[selectedQuantity]
|
||||||
|
);
|
||||||
|
table.querySelectorAll(`tr`).forEach((row) => {
|
||||||
|
row.children[columnIndex].classList.add("highlighted");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.entries(quantityHeaders).forEach(([key, header]) => {
|
||||||
|
header.addEventListener("click", () => {
|
||||||
|
quantitySelect.value = key;
|
||||||
|
const event = new Event("change");
|
||||||
|
quantitySelect.dispatchEvent(event);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
quantityAssigned
|
||||||
|
? CostUI.highlightColumn(
|
||||||
|
table,
|
||||||
|
headerRow,
|
||||||
|
quantityHeaders,
|
||||||
|
quantityAssigned
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
quantitySelect.value = quantityAssigned ? quantityAssigned : "count";
|
||||||
|
|
||||||
|
const productIds = products.map((product) => product.info.id);
|
||||||
|
callbacks.emptyForm = () => {
|
||||||
|
form.innerHTML = "";
|
||||||
|
};
|
||||||
|
const addProductAssignmentsButton = CostUI.addProductAssignmentsButton(
|
||||||
|
costItemId,
|
||||||
|
productIds,
|
||||||
|
quantitySelect,
|
||||||
|
callbacks
|
||||||
|
);
|
||||||
|
|
||||||
|
form.appendChild(quantitySelect);
|
||||||
|
form.appendChild(addProductAssignmentsButton);
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
static createAssignmentsTable({
|
||||||
|
form,
|
||||||
|
products,
|
||||||
|
costItemId,
|
||||||
|
quantityNames,
|
||||||
|
callbacks,
|
||||||
|
}) {
|
||||||
|
const { tableContainer, table } = CostUI.addTable({
|
||||||
|
headers: ["Step Id", "Class", "Name", "Type", "Count"],
|
||||||
|
className: "product-table",
|
||||||
|
id: "cost-values-table-" + costItemId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tbody = document.createElement("tbody");
|
||||||
|
table.appendChild(tbody);
|
||||||
|
form.appendChild(tableContainer);
|
||||||
|
|
||||||
|
let quantityAssigned = CostUI.getAssinedQuantity(products, costItemId);
|
||||||
|
const shouldAddQto = quantityAssigned ? true : false;
|
||||||
|
const headerRow = table.get_header_row();
|
||||||
|
const { quantitySums, quantityHeaders } = CostUI.addQuantityHeaders(
|
||||||
|
table,
|
||||||
|
products,
|
||||||
|
shouldAddQto
|
||||||
|
);
|
||||||
|
|
||||||
|
shouldAddQto
|
||||||
|
? CostUI.highlightColumn(
|
||||||
|
table,
|
||||||
|
headerRow,
|
||||||
|
quantityHeaders,
|
||||||
|
quantityAssigned
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
products.forEach((product) => {
|
||||||
|
const row = CostUI.createProductRow(product, shouldAddQto);
|
||||||
|
tbody.appendChild(row);
|
||||||
|
shouldAddQto ? CostUI.calculateSums(product.qtos, quantitySums) : null;
|
||||||
|
});
|
||||||
|
|
||||||
|
CostUI.countProducts(products, quantitySums);
|
||||||
|
|
||||||
|
const subtotalRow = CostUI.addSubTotalRow(quantitySums, shouldAddQto);
|
||||||
|
tbody.appendChild(subtotalRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
static highlightColumn(table, headerRow, quantityHeaders, quantityAssigned) {
|
||||||
|
if (quantityAssigned) {
|
||||||
|
if (quantityHeaders[quantityAssigned]) {
|
||||||
|
quantityHeaders[quantityAssigned].classList.add("highlighted");
|
||||||
|
const columnIndex = Array.from(headerRow.children).indexOf(
|
||||||
|
quantityHeaders[quantityAssigned]
|
||||||
|
);
|
||||||
|
table.querySelectorAll(`tr`).forEach((row) => {
|
||||||
|
row.children[columnIndex].classList.add("highlighted");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static addProductAssignmentsButton(
|
||||||
|
costItemId,
|
||||||
|
productIds,
|
||||||
|
quantitySelect,
|
||||||
|
callbacks
|
||||||
|
) {
|
||||||
|
const addProductAssignmentsButton = document.createElement("button");
|
||||||
|
addProductAssignmentsButton.textContent = "Add Product Assignments";
|
||||||
|
addProductAssignmentsButton.addEventListener("click", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
let propName = quantitySelect.value;
|
||||||
|
if (propName === "count") {
|
||||||
|
propName = "";
|
||||||
|
}
|
||||||
|
callbacks.addProductAssignments
|
||||||
|
? callbacks.addProductAssignments({
|
||||||
|
costItemId,
|
||||||
|
selectedQuantity: propName,
|
||||||
|
productIds,
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
callbacks.emptyForm ? callbacks.emptyForm() : null;
|
||||||
|
callbacks.getSelectedProducts(costItemId);
|
||||||
|
});
|
||||||
|
addProductAssignmentsButton.classList.add("action-button");
|
||||||
|
return addProductAssignmentsButton;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,9 @@
|
|||||||
<button id="toggle-theme" onclick="toggleTheme()">
|
<button id="toggle-theme" onclick="toggleTheme()">
|
||||||
<i class="fas fa-moon"></i>
|
<i class="fas fa-moon"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<button id="settings-button">
|
||||||
|
<i class="fa-solid fa-gear"></i>
|
||||||
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
<div id="connected-list-div">
|
<div id="connected-list-div">
|
||||||
<button id="show-connected-button" onclick="toggleClientList()">
|
<button id="show-connected-button" onclick="toggleClientList()">
|
||||||
@@ -91,8 +94,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="cost-schedules" class="row-container"></div>
|
<div id="cost-schedules" class="row-container"></div>
|
||||||
<div id="UI" class="row-container"></div>
|
|
||||||
<div id="cost-items"></div>
|
<div id="cost-items" class="table-container"></div>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<p>BlenderBIM Version: {{version}}</p>
|
<p>BlenderBIM Version: {{version}}</p>
|
||||||
|
|||||||
@@ -318,18 +318,9 @@ class CostItemQuantitiesData:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def product_quantity_names(cls):
|
def product_quantity_names(cls):
|
||||||
total_selected_objects = len(bpy.context.selected_objects)
|
elements = tool.Spatial.get_selected_products()
|
||||||
names = set()
|
names = ifcopenshell.util.cost.get_product_quantity_names(elements)
|
||||||
for obj in bpy.context.selected_objects:
|
return [(n, n, "") for n in names]
|
||||||
element = tool.Ifc.get_entity(obj)
|
|
||||||
if not element:
|
|
||||||
continue
|
|
||||||
potential_names = set()
|
|
||||||
qtos = ifcopenshell.util.element.get_psets(element, qtos_only=True)
|
|
||||||
for qset, quantities in qtos.items():
|
|
||||||
potential_names.update(quantities.keys())
|
|
||||||
names = names.intersection(potential_names) if names else potential_names
|
|
||||||
return [(n, n, "") for n in names if n != "id"]
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def process_quantity_names(cls):
|
def process_quantity_names(cls):
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ class Cost(bonsai.core.tool.Cost):
|
|||||||
unit = ""
|
unit = ""
|
||||||
for quantities in ifcopenshell.util.element.get_psets(product, qtos_only=True).values():
|
for quantities in ifcopenshell.util.element.get_psets(product, qtos_only=True).values():
|
||||||
for qto in tool.Ifc.get().by_id(quantities["id"]).Quantities or []:
|
for qto in tool.Ifc.get().by_id(quantities["id"]).Quantities or []:
|
||||||
for quantity in cost_item.CostQuantities:
|
for quantity in cost_item.CostQuantities or []:
|
||||||
if quantity == qto:
|
if quantity == qto:
|
||||||
selected_quantitites.append(quantity)
|
selected_quantitites.append(quantity)
|
||||||
if not unit:
|
if not unit:
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
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
|
||||||
|
from ifcopenshell.util.element import get_psets, get_type
|
||||||
import bonsai.core.tool
|
import bonsai.core.tool
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
import ifcopenshell.api.sequence
|
import ifcopenshell.api.sequence
|
||||||
@@ -342,6 +343,48 @@ class Web(bonsai.core.tool.Web):
|
|||||||
Args:
|
Args:
|
||||||
operator_data (dict): A dictionary containing the operator data.
|
operator_data (dict): A dictionary containing the operator data.
|
||||||
"""
|
"""
|
||||||
|
def selection_data(cost_item, elements):
|
||||||
|
print(cost_item, elements)
|
||||||
|
if not elements:
|
||||||
|
return []
|
||||||
|
if not cost_item:
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"info": {
|
||||||
|
"id": element.id(),
|
||||||
|
"name": element.Name,
|
||||||
|
"class": element.is_a(),
|
||||||
|
"type": get_type(element).Name if get_type(element) else None,
|
||||||
|
},
|
||||||
|
"qtos": get_psets(element, qtos_only=True),
|
||||||
|
}
|
||||||
|
for element in elements or []
|
||||||
|
]
|
||||||
|
data = []
|
||||||
|
for element in elements or []:
|
||||||
|
psets = get_psets(element, qtos_only=True)
|
||||||
|
element_type = get_type(element)
|
||||||
|
quantities, unit = tool.Cost.get_assigned_quantities(cost_item, element)
|
||||||
|
data.append(
|
||||||
|
{
|
||||||
|
"info": {
|
||||||
|
"id": element.id(),
|
||||||
|
"name": element.Name,
|
||||||
|
"class": element.is_a(),
|
||||||
|
"type": element_type.Name if element_type else None,
|
||||||
|
},
|
||||||
|
"qtos": psets,
|
||||||
|
"assigned_quantities": [{
|
||||||
|
'cost_item_id': cost_item.id(),
|
||||||
|
'product_id': element.id(),
|
||||||
|
"name": q.Name,
|
||||||
|
"value": q[3],
|
||||||
|
"type": q.Unit,
|
||||||
|
} for q in quantities or []],
|
||||||
|
"unit": unit,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return data
|
||||||
ifc_file = tool.Ifc.get()
|
ifc_file = tool.Ifc.get()
|
||||||
if operator_data["type"] == "getPredefinedTypes":
|
if operator_data["type"] == "getPredefinedTypes":
|
||||||
print("getting predefined types")
|
print("getting predefined types")
|
||||||
@@ -349,10 +392,9 @@ class Web(bonsai.core.tool.Web):
|
|||||||
cls.send_webui_data(data=predefined_types, data_key="predefined_types", event="predefined_types")
|
cls.send_webui_data(data=predefined_types, data_key="predefined_types", event="predefined_types")
|
||||||
if operator_data["type"] == "addCostSchedule":
|
if operator_data["type"] == "addCostSchedule":
|
||||||
bonsai.core.cost.add_cost_schedule(
|
bonsai.core.cost.add_cost_schedule(
|
||||||
tool.Ifc, name=operator_data["name"], predefined_type=operator_data["type"]
|
tool.Ifc, name=operator_data["name"], predefined_type=operator_data["predefinedType"]
|
||||||
)
|
)
|
||||||
cost_schedules = ifc_file.by_type("IfcCostSchedule")
|
cost_schedules_json = [cs.get_info(recursive=True) for cs in ifc_file.by_type("IfcCostSchedule")]
|
||||||
cost_schedules_json = [cs.get_info(recursive=True) for cs in cost_schedules]
|
|
||||||
currency = tool.Cost.currency()
|
currency = tool.Cost.currency()
|
||||||
cls.send_webui_data(
|
cls.send_webui_data(
|
||||||
data={"cost_schedules": cost_schedules_json, "currency": currency},
|
data={"cost_schedules": cost_schedules_json, "currency": currency},
|
||||||
@@ -361,8 +403,7 @@ class Web(bonsai.core.tool.Web):
|
|||||||
)
|
)
|
||||||
if operator_data["type"] == "getCostSchedules":
|
if operator_data["type"] == "getCostSchedules":
|
||||||
print("getting cost schedules")
|
print("getting cost schedules")
|
||||||
cost_schedules = ifc_file.by_type("IfcCostSchedule")
|
cost_schedules_json = [cs.get_info(recursive=True) for cs in ifc_file.by_type("IfcCostSchedule")]
|
||||||
cost_schedules_json = [cs.get_info(recursive=True) for cs in cost_schedules]
|
|
||||||
currency = tool.Cost.currency()
|
currency = tool.Cost.currency()
|
||||||
cls.send_webui_data(
|
cls.send_webui_data(
|
||||||
data={"cost_schedules": cost_schedules_json, "currency": currency},
|
data={"cost_schedules": cost_schedules_json, "currency": currency},
|
||||||
@@ -379,11 +420,34 @@ 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":
|
||||||
|
cost_item_id = operator_data["costItemId"]
|
||||||
|
cost_item = ifc_file.by_id(cost_item_id)
|
||||||
|
if not cost_item:
|
||||||
|
print('---> cost item not found')
|
||||||
|
return
|
||||||
|
|
||||||
|
selected_products = list(tool.Spatial.get_selected_products())
|
||||||
|
selected_products_data = selection_data(cost_item, selected_products)
|
||||||
|
|
||||||
|
assigned_products = tool.Cost.get_cost_item_products(cost_item, is_deep=False)
|
||||||
|
assigned_products_data = selection_data(cost_item, assigned_products)
|
||||||
|
|
||||||
|
names = ifcopenshell.util.cost.get_product_quantity_names(selected_products)
|
||||||
|
cls.send_webui_data(data={
|
||||||
|
"selected_products": selected_products_data,
|
||||||
|
'assigned_products': assigned_products_data,
|
||||||
|
"product_quantity_names": names,
|
||||||
|
"cost_item_id": cost_item_id
|
||||||
|
}, data_key="selected_products", event="selected_products")
|
||||||
|
|
||||||
if operator_data["type"] == "addSummaryCostItem":
|
if operator_data["type"] == "addSummaryCostItem":
|
||||||
cost_schedule = ifc_file.by_id(operator_data["costScheduleId"])
|
cost_schedule = ifc_file.by_id(operator_data["costScheduleId"])
|
||||||
bonsai.core.cost.add_summary_cost_item(tool.Ifc, tool.Cost, cost_schedule=cost_schedule)
|
bonsai.core.cost.add_summary_cost_item(tool.Ifc, tool.Cost, cost_schedule=cost_schedule)
|
||||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||||
if operator_data["type"] == "addCostItem":
|
if operator_data["type"] == "addCostItem":
|
||||||
|
if not operator_data["costItemId"]:
|
||||||
|
return
|
||||||
bpy.ops.bim.add_cost_item(cost_item=operator_data["costItemId"])
|
bpy.ops.bim.add_cost_item(cost_item=operator_data["costItemId"])
|
||||||
cost_schedule = tool.Cost.get_cost_schedule(cost_item=tool.Ifc.get().by_id(operator_data["costItemId"]))
|
cost_schedule = tool.Cost.get_cost_schedule(cost_item=tool.Ifc.get().by_id(operator_data["costItemId"]))
|
||||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||||
@@ -443,6 +507,18 @@ class Web(bonsai.core.tool.Web):
|
|||||||
print(value.get_info())
|
print(value.get_info())
|
||||||
tool.Cost.load_cost_schedule_tree()
|
tool.Cost.load_cost_schedule_tree()
|
||||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||||
|
if operator_data["type"] == "addProductAssignments":
|
||||||
|
prop_name = operator_data["propName"]
|
||||||
|
if prop_name == "count":
|
||||||
|
prop_name = ""
|
||||||
|
print(prop_name)
|
||||||
|
bpy.ops.bim.assign_cost_item_quantity(
|
||||||
|
cost_item=operator_data["costItemId"],
|
||||||
|
related_object_type= "PRODUCT",
|
||||||
|
prop_name=prop_name
|
||||||
|
)
|
||||||
|
cost_schedule = tool.Cost.get_cost_schedule(cost_item=tool.Ifc.get().by_id(operator_data["costItemId"]))
|
||||||
|
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load_cost_schedule_web_ui(cls, cost_schedule):
|
def load_cost_schedule_web_ui(cls, cost_schedule):
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import ifcopenshell
|
|||||||
from typing import Optional, Union, Literal, Generator, Any
|
from typing import Optional, Union, Literal, Generator, Any
|
||||||
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
||||||
from ifcopenshell.util.doc import get_predefined_type_doc
|
from ifcopenshell.util.doc import get_predefined_type_doc
|
||||||
|
from ifcopenshell.util.element import get_psets
|
||||||
arithmetic_operator_symbols = {"ADD": "+", "DIVIDE": "/", "MULTIPLY": "*", "SUBTRACT": "-"}
|
arithmetic_operator_symbols = {"ADD": "+", "DIVIDE": "/", "MULTIPLY": "*", "SUBTRACT": "-"}
|
||||||
symbol_arithmetic_operators = {"+": "ADD", "/": "DIVIDE", "*": "MULTIPLY", "-": "SUBTRACT"}
|
symbol_arithmetic_operators = {"+": "ADD", "/": "DIVIDE", "*": "MULTIPLY", "-": "SUBTRACT"}
|
||||||
FILTER_BY_TYPE = Literal["PRODUCT", "RESOURCE", "PROCESS"]
|
FILTER_BY_TYPE = Literal["PRODUCT", "RESOURCE", "PROCESS"]
|
||||||
@@ -240,11 +240,13 @@ def get_cost_item_assignments(
|
|||||||
if not is_deep:
|
if not is_deep:
|
||||||
return get_cost_assignments_by_type(cost_item, filter_by_type)
|
return get_cost_assignments_by_type(cost_item, filter_by_type)
|
||||||
else:
|
else:
|
||||||
return [
|
current_assignments = get_cost_assignments_by_type(cost_item, filter_by_type)
|
||||||
|
nested_assignments = [
|
||||||
product
|
product
|
||||||
for nested_cost_item in get_all_nested_cost_items(cost_item)
|
for nested_cost_item in get_all_nested_cost_items(cost_item)
|
||||||
for product in get_cost_assignments_by_type(nested_cost_item, filter_by_type)
|
for product in get_cost_assignments_by_type(nested_cost_item, filter_by_type)
|
||||||
]
|
]
|
||||||
|
return current_assignments + nested_assignments
|
||||||
|
|
||||||
|
|
||||||
def get_cost_values(cost_item: ifcopenshell.entity_instance) -> list[dict[str, str]]:
|
def get_cost_values(cost_item: ifcopenshell.entity_instance) -> list[dict[str, str]]:
|
||||||
@@ -285,6 +287,17 @@ def get_cost_schedule_types(file):
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def get_product_quantity_names(elements: list[ifcopenshell.entity_instance]) -> list[str]:
|
||||||
|
names = set()
|
||||||
|
for element in elements or []:
|
||||||
|
potential_names = set()
|
||||||
|
qtos = get_psets(element, qtos_only=True)
|
||||||
|
for qset, quantities in qtos.items():
|
||||||
|
potential_names.update(quantities.keys())
|
||||||
|
names = names.intersection(potential_names) if names else potential_names
|
||||||
|
return [n for n in names if n != "id"]
|
||||||
|
|
||||||
|
|
||||||
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