- web ui features to add a new cost schedules, and root cost items

- improved ui for cost schedules
- sharing more css across files
- refactor get_predefined_types(cost_schedule)
This commit is contained in:
`myoualid`
2024-09-06 08:55:10 +01:00
parent 541a6e18e6
commit d6098a0b6d
11 changed files with 1456 additions and 1307 deletions
@@ -176,6 +176,11 @@ class BlenderNamespace(socketio.AsyncNamespace):
blender_messages[sid]["cost_value"] = data blender_messages[sid]["cost_value"] = data
await sio.emit("cost_value", {"blenderId": sid, "data": data}, namespace="/web") await sio.emit("cost_value", {"blenderId": sid, "data": data}, namespace="/web")
async def on_predefined_types(self, sid, data):
print(f"Predefined types from Blender client {sid}")
blender_messages[sid]["predefined_types"] = data
await sio.emit("predefined_types", {"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()
@@ -5,24 +5,19 @@
table { table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
/* prevent text from being selceted */
user-select: none; user-select: none;
} }
td:not(:first-child) { td:not(:first-child) {
text-align: center; text-align: center;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
td { td {
border-right: 1px solid #ddd; border-right: 1px solid #ddd;
padding-left: 0.5em; padding-left: 0.5em;
padding-right: 0.5em; padding-right: 0.5em;
} }
.nested { .nested {
@@ -39,22 +34,15 @@ td {
padding: 0; padding: 0;
} }
.flex-container { input, select {
display: flex; background-color: var(--blender-button-background, var(--bg-color));
justify-content: space-between; color: var(--blender-button-text, var(--text-color));
align-items: center; border-color: var(--blender-button-border, var(--border-color));
width: 100%; transition: filter 0.2s ease;
}
input {
width: 100%;
border: 1px solid #e1e4e8;
border-radius: 6px; border-radius: 6px;
background-color:inherit;
padding: 6px 8px; padding: 6px 8px;
font-size: 14px; font-size: 14px;
line-height: 20px; line-height: 20px;
color:inherit;
transition: border-color 0.2s cubic-bezier(0.3, 0, 0.5, 1); transition: border-color 0.2s cubic-bezier(0.3, 0, 0.5, 1);
height: inherit; height: inherit;
margin-left: 0.5em; margin-left: 0.5em;
@@ -62,12 +50,11 @@ input {
} }
input:focus { input:focus {
border-color: #0366d6; border-color: #28a745;
outline: none; outline: none;
box-shadow: 0 0 0 3px rgba(3, 102, 214, 0.3); box-shadow: 0 0 0 3px #28a745;
} }
#cost-items tr:hover, .highlighted { #cost-items tr:hover, .highlighted {
background-color: #ffb936; background-color: #ffb936;
color : black; color : black;
@@ -87,13 +74,17 @@ input:focus {
padding: 10px; padding: 10px;
} }
.cost-values-form { .floating-form {
min-width: 40%;
height: auto;
position: absolute; position: absolute;
background-color: white; background-color: #363636;
border: 1px solid black; border: 1px solid black;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
z-index: 9999; z-index: 9999;
cursor: move; cursor: move;
padding: 20px;
user-select: none;
} }
.cost-values-table { .cost-values-table {
@@ -120,18 +111,20 @@ input:focus {
} }
.action-button { .action-button {
background-color: #28a745; /* GitHub green color */ background-color: #28a745;
border: none; border: none;
color: #fff; /* White text color */ color: #fff;
padding: 0.5em 1em; /* Adjust padding as needed */ padding: 0.5em 1em;
cursor: pointer; cursor: pointer;
border-radius: 6px; /* Slightly rounded corners */ border-radius: 6px;
font-weight: bold; /* Make the text bold */ font-weight: bold;
text-decoration: none; /* Remove underline */ text-decoration: none;
display: inline-block; /* Make the button inline */ display: inline-block;
transition: background-color 0.3s ease; /* Add a smooth transition */ transition: background-color 0.3s ease;
width: fit-content;
margin-left: 0.5em;
} }
.action-button:hover { .action-button:hover {
background-color: #218838; /* Darker green color on hover */ background-color: #218838;
} }
@@ -1,127 +1,5 @@
@import url("./components/card.css"); @import url("./components/card.css");
@import url("./shared.css");
:root {
--font-family: Arial, sans-serif;
--base-font-size: 16px;
--margin-tiny: 0.125rem;
--margin-small: 0.625rem;
--margin-medium: 1.25rem;
--margin-large: 2.5rem;
--padding-tiny: 0.125rem;
--padding-small: 0.625rem;
--padding-medium: 1rem;
--font-size-small: 1rem;
--font-size-large: 1.2rem;
--logo-height: 1.5rem;
--nav-height: 0.625rem;
--border-radius: 0.3rem;
--folder-collapse-font-size: 0.75rem;
--folder-collapse-font-family: Courier, "Courier New", monospace;
--box-shadow: 0 0 0.7rem #5f5f5f66;
}
:root.dark {
color-scheme: dark;
--bg-color: #252525;
--primary-text-color: #e0e0e0;
--secondary-text-color: #c7c7c7;
--nav-bg-color: #121212;
--nav-border-color: #25682a;
--nav-link-color: #fff;
--nav-link-hover-color: #3fb449;
--warning-color: #FFDB8F;
--border-color: #464444;
--hover-bg-color: #3a3a3a;
--highlight-color: #009136;
--task-complete-bg-color: #777777;
--milestone-border-color: #ffffff;
--group-item-bg-color: #4b4b4b;
--input-bg-color: #3b3b3b;
--input-border-color: #000;
--border-color: #464444;
}
:root.light {
color-scheme: light;
--bg-color: #ffffff;
--primary-text-color: #000000;
--nav-bg-color: #f8f8f8;
--nav-border-color: #cccccc;
--nav-link-color: #000000;
--nav-link-hover-color: #38a63d;
--warning-color: #FF4500;
--border-color: #222;
}
*:not(td, tr, td, .gmain) {
border-radius: var(--border-radius) !important;
}
html {
font-size: var(--base-font-size);
}
body {
background-color: var(--blender-window-background, var(--bg-color));
color: var(--blender-text, var(--primary-text-color));
margin: 0;
font-family: var(--font-family);
display: flex;
flex-direction: column;
min-height: 100vh;
}
#container {
flex: 1;
padding: var(--margin-small);
}
h3 {
margin-top: 0;
}
nav {
background-color: var(--blender-top-bar-header, var(--nav-bg-color));
height: var(--nav-height);
padding: var(--padding-medium) 0;
display: flex;
align-items: center;
position: relative;
border-bottom: 2px solid var(--blender-tab-outline, var(--nav-border-color));
}
nav .logo {
margin-left: var(--margin-large);
height: var(--logo-height);
}
nav ul {
list-style-type: none;
margin: 0;
display: flex;
flex: 1;
justify-content: center;
}
nav ul li {
margin-right: var(--margin-large);
}
nav ul li a {
text-decoration: none;
color: var(--blender-text, var(--nav-link-color));
}
nav ul li a:hover,
nav ul li a.active {
color: var(--blender-selected-object, var(--nav-link-hover-color));
}
.warning,
.warning-icon {
color: var(--blender-info-warning, var(--warning-color));
}
.table-description { .table-description {
margin-bottom: var(--margin-medium); margin-bottom: var(--margin-medium);
@@ -194,7 +72,8 @@ nav ul li a.active {
max-height: 0; max-height: 0;
overflow: hidden; overflow: hidden;
padding-left: var(--padding-small); padding-left: var(--padding-small);
transition: max-height 0.2s ease-out, padding 0.2s ease-out, opacity 0.1s ease-out; transition: max-height 0.2s ease-out, padding 0.2s ease-out,
opacity 0.1s ease-out;
background-color: var(--blender-panel-background, var(--nav-bg-color)); background-color: var(--blender-panel-background, var(--nav-bg-color));
margin-top: var(--margin-tiny); margin-top: var(--margin-tiny);
opacity: 0; opacity: 0;
@@ -259,7 +138,6 @@ footer p {
} }
} }
button, button,
select { select {
background-color: var(--blender-button-background, var(--bg-color)); background-color: var(--blender-button-background, var(--bg-color));
@@ -316,7 +194,7 @@ button:hover {
border-color: var(--border-color); border-color: var(--border-color);
} }
:root.dark .gtaskname>div { :root.dark .gtaskname > div {
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
@@ -429,7 +307,6 @@ button:hover {
/* ------------------- for blender generated theme ------------------- */ /* ------------------- for blender generated theme ------------------- */
:root.blender div.gantt, :root.blender div.gantt,
:root.blender .gantt table, :root.blender .gantt table,
:root.blender .gantt td { :root.blender .gantt td {
@@ -447,7 +324,6 @@ button:hover {
border-color: var(--blender-tab-outline); border-color: var(--blender-tab-outline);
} }
/* Headings and cell defaults */ /* Headings and cell defaults */
:root.blender .gtaskheading, :root.blender .gtaskheading,
:root.blender .gchartlbl.gcontainercol .gmajorheading, :root.blender .gchartlbl.gcontainercol .gmajorheading,
@@ -486,7 +362,7 @@ button:hover {
border-color: var(--blender-tab-outline); border-color: var(--blender-tab-outline);
} }
:root.blender .gtaskname>div, :root.blender .gtaskname > div,
:root.blender .gtaskname div, :root.blender .gtaskname div,
:root.blender .gtaskheading div, :root.blender .gtaskheading div,
:root.blender .gtaskname span { :root.blender .gtaskname span {
@@ -509,7 +385,6 @@ button:hover {
border-color: var(--blender-tab-outline); border-color: var(--blender-tab-outline);
} }
/* Highlight row */ /* Highlight row */
:root.blender .gitemhighlight td, :root.blender .gitemhighlight td,
:root.blender .gitemhighlight .gantt-inputtable, :root.blender .gitemhighlight .gantt-inputtable,
@@ -8,37 +8,50 @@
--padding-tiny: 0.125rem; --padding-tiny: 0.125rem;
--padding-small: 0.625rem; --padding-small: 0.625rem;
--padding-medium: 1rem; --padding-medium: 1rem;
--font-size-small: 1rem;
--font-size-large: 1.2rem; --font-size-large: 1.2rem;
--logo-height: 1.5rem; --logo-height: 1.5rem;
--nav-height: 0.625rem; --nav-height: 0.625rem;
--border-radius: 0.3rem; --border-radius: 0.3rem;
--folder-collapse-font-size: 0.75rem;
--folder-collapse-font-family: Courier, "Courier New", monospace;
--box-shadow: 0 0 0.7rem #5f5f5f66;
} }
:root.dark { :root.dark {
color-scheme: dark; color-scheme: dark;
--bg-color: #252525; --bg-color: #252525;
--text-color: #e0e0e0; --primary-text-color: #e0e0e0;
--secondary-text-color: #c7c7c7;
--nav-bg-color: #121212; --nav-bg-color: #121212;
--nav-border-color: #25682a; --nav-border-color: #25682a;
--nav-link-color: #fff; --nav-link-color: #fff;
--nav-link-hover-color: #3fb449; --nav-link-hover-color: #3fb449;
--warning-color: #FFDB8F; --warning-color: #ffdb8f;
--border-color: #464444;
--hover-bg-color: #3a3a3a;
--highlight-color: #009136;
--task-complete-bg-color: #777777;
--milestone-border-color: #ffffff;
--group-item-bg-color: #4b4b4b;
--input-bg-color: #3b3b3b;
--input-border-color: #000;
--border-color: #464444; --border-color: #464444;
} }
:root.light { :root.light {
color-scheme: light; color-scheme: light;
--bg-color: #ffffff; --bg-color: #ffffff;
--text-color: #000000; --primary-text-color: #000000;
--nav-bg-color: #f8f8f8; --nav-bg-color: #f8f8f8;
--nav-border-color: #cccccc; --nav-border-color: #cccccc;
--nav-link-color: #000000; --nav-link-color: #000000;
--nav-link-hover-color: #38a63d; --nav-link-hover-color: #38a63d;
--warning-color: #FF4500; --warning-color: #ff4500;
--border-color: #222; --border-color: #222;
} }
*:not(.tabulator-cell) { *:not(td, tr, td, .gmain) {
border-radius: var(--border-radius) !important; border-radius: var(--border-radius) !important;
} }
@@ -48,7 +61,7 @@ html {
body { body {
background-color: var(--blender-window-background, var(--bg-color)); background-color: var(--blender-window-background, var(--bg-color));
color: var(--blender-text, var(--text-color)); color: var(--blender-text, var(--primary-text-color));
margin: 0; margin: 0;
font-family: var(--font-family); font-family: var(--font-family);
display: flex; display: flex;
@@ -57,9 +70,8 @@ body {
} }
#container { #container {
position: relative;
padding: var(--margin-small);
flex: 1; flex: 1;
padding: var(--margin-small);
} }
h3 { h3 {
@@ -108,14 +120,6 @@ nav ul li a.active {
color: var(--blender-info-warning, var(--warning-color)); color: var(--blender-info-warning, var(--warning-color));
} }
#toggle-theme {
border: none;
background: none;
cursor: pointer;
font-size: var(--font-size-large);
margin-right: var(--margin-medium);
}
#toggle-theme:focus { #toggle-theme:focus {
outline: none; outline: none;
} }
@@ -153,7 +157,8 @@ nav ul li a.active {
max-height: 0; max-height: 0;
overflow: hidden; overflow: hidden;
padding-left: var(--padding-small); padding-left: var(--padding-small);
transition: max-height 0.2s ease-out, padding 0.2s ease-out, opacity 0.1s ease-out; transition: max-height 0.2s ease-out, padding 0.2s ease-out,
opacity 0.1s ease-out;
background-color: var(--blender-panel-background, var(--nav-bg-color)); background-color: var(--blender-panel-background, var(--nav-bg-color));
margin-top: var(--margin-tiny); margin-top: var(--margin-tiny);
opacity: 0; opacity: 0;
@@ -173,7 +178,7 @@ nav ul li a.active {
width: auto; width: auto;
background-color: var(--blender-panel-header, var(--bg-color)); background-color: var(--blender-panel-header, var(--bg-color));
font-size: var(--base-font-size); font-size: var(--base-font-size);
color: var(--blender-text, var(--text-color)) color: var(--blender-text, var(--text-color));
} }
#show-connected-button:hover, #show-connected-button:hover,
@@ -212,3 +217,23 @@ select {
button:hover { button:hover {
filter: brightness(1.2); filter: brightness(1.2);
} }
.row-container {
width: 100%;
border-collapse: collapse;
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
}
.column-container {
display: flex;
flex-direction: column;
justify-content: flex-start;
flex-wrap: wrap;
}
label {
margin-right: var(--margin-small);
margin-bottom: var(--margin-small);
}
@@ -1,8 +1,10 @@
import { CostUI } from './utilities/costui.js'; import { CostUI } from "./utilities/costui.js";
const connectedClients = {}; const connectedClients = {};
let socket; let socket;
let isScheduleLoad = {};
$(document).ready(function () { $(document).ready(function () {
var defaultTheme = "blender"; var defaultTheme = "blender";
var theme = localStorage.getItem("theme") || defaultTheme; var theme = localStorage.getItem("theme") || defaultTheme;
@@ -21,12 +23,28 @@ function connectSocket() {
socket.on("connected_clients", handleConnectedClients); socket.on("connected_clients", handleConnectedClients);
socket.on("theme_data", handleThemeData); socket.on("theme_data", handleThemeData);
socket.on("connect", handleWebConnect); socket.on("connect", handleWebConnect);
socket.on("predefined_types", handlePredefinedTypes);
socket.on("cost_schedules", handleCostSchedulesData); socket.on("cost_schedules", handleCostSchedulesData);
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);
} }
function handlePredefinedTypes(data) {
CostUI.enableAddingCostSchedule(
data.data["predefined_types"],
addCostSchedule
);
}
function addCostSchedule(name, predefinedType) {
executeOperator({
type: "addCostSchedule",
name: name,
predefinedType: predefinedType,
});
}
function handleBlenderConnect(blenderId) { function handleBlenderConnect(blenderId) {
if (!connectedClients.hasOwnProperty(blenderId)) { if (!connectedClients.hasOwnProperty(blenderId)) {
connectedClients[blenderId] = { shown: false, ifc_file: "" }; connectedClients[blenderId] = { shown: false, ifc_file: "" };
@@ -35,7 +53,6 @@ function handleBlenderConnect(blenderId) {
$("#blender-count").text(function (i, text) { $("#blender-count").text(function (i, text) {
return parseInt(text, 10) + 1; return parseInt(text, 10) + 1;
}); });
console.log("Blender connected", blenderId);
} }
function handleBlenderDisconnect(blenderId) { function handleBlenderDisconnect(blenderId) {
@@ -53,24 +70,24 @@ function removeTableElement(blenderId) {
$("#cost-items-" + blenderId).remove(); $("#cost-items-" + blenderId).remove();
} }
const costValueCallbacks = { const costValueCallbacks = {
'editCostValues': editCostValues, editCostValues: editCostValues,
'addCostValue': addCostValue, addCostValue: addCostValue,
'deleteCostValue': deleteCostValue, deleteCostValue: deleteCostValue,
}; };
function handleCostValueData(data) { function handleCostValueData(data) {
const costItemId = data.data["cost_value"]["cost_item_id"]; const costItemId = data.data["cost_value"]["cost_item_id"];
const costValueId = data.data["cost_value"]["cost_value_id"]; const costValueId = data.data["cost_value"]["cost_value_id"];
console.log("Handling cost value data", costItemId, costValueId);
CostUI.addNewCostValueRow(costItemId, costValueId, costValueCallbacks); CostUI.addNewCostValueRow(costItemId, costValueId, costValueCallbacks);
} }
function deleteCostValue(costItemId, costValueId) { function deleteCostValue(costItemId, costValueId) {
executeOperator({ type: "deleteCostValue", costItemId: costItemId, costValueId: costValueId }); executeOperator({
type: "deleteCostValue",
costItemId: costItemId,
costValueId: costValueId,
});
} }
function handleConnectedClients(data) { function handleConnectedClients(data) {
@@ -86,14 +103,13 @@ function handleCostValuesData(data) {
costValues: data.data["cost_values"]["cost_values"], costValues: data.data["cost_values"]["cost_values"],
costItemId: data.data["cost_values"]["cost_item_id"], costItemId: data.data["cost_values"]["cost_item_id"],
callbacks: { callbacks: {
'editCostValues': editCostValues, editCostValues: editCostValues,
'addCostValue': addCostValue, addCostValue: addCostValue,
'deleteCostValue': deleteCostValue, deleteCostValue: deleteCostValue,
}, },
}); });
CostUI.highlightCostItem(data.data["cost_values"]["cost_item_id"]); CostUI.highlightCostItem(data.data["cost_values"]["cost_item_id"]);
} }
function addCostValue(costItemId) { function addCostValue(costItemId) {
@@ -104,7 +120,7 @@ function editCostValues(costItemId, costValues) {
executeOperator({ executeOperator({
type: "editCostValues", type: "editCostValues",
costItemId: costItemId, costItemId: costItemId,
costValues: costValues costValues: costValues,
}); });
} }
@@ -159,7 +175,11 @@ function addCostItem(costItemId) {
} }
function editCostItemName(costItemId, name) { function editCostItemName(costItemId, name) {
executeOperator({ type: "editCostItemName", costItemId: costItemId, name: name }); executeOperator({
type: "editCostItemName",
costItemId: costItemId,
name: name,
});
} }
function selectAssignedElements(costItemId) { function selectAssignedElements(costItemId) {
@@ -167,44 +187,52 @@ function selectAssignedElements(costItemId) {
} }
function handleWebConnect() { function handleWebConnect() {
executeOperator({ type: "getPredefinedTypes" });
getCostSchedules(); getCostSchedules();
} }
function addSummaryCostItem(costScheduleId) {
executeOperator({
type: "addSummaryCostItem",
costScheduleId: costScheduleId,
});
}
function handleCostSchedulesData(data) { function handleCostSchedulesData(data) {
const blenderId = data.blenderId; const blenderId = data.blenderId;
const costSchedules = data.data["cost_schedules"]["cost_schedules"]; const costSchedules = data.data["cost_schedules"]["cost_schedules"];
const currency = data.data["cost_schedules"]["currency"]["name"]; if (costSchedules.length === 0) {
const costScheduleDiv = $("#cost-schedules"); return;
}
const currency = data.data["cost_schedules"]["currency"]
? data.data["cost_schedules"]["currency"]["name"]
: "Undefined";
const costScheduleDiv = document.getElementById("cost-schedules");
costScheduleDiv.innerHTML = "";
costSchedules.forEach((costSchedule) => { costSchedules.forEach((costSchedule) => {
costSchedule.UpdateDate = new Date(costSchedule.UpdateDate); costSchedule.UpdateDate = new Date(costSchedule.UpdateDate);
const mainContainer = CostUI.text("Updated On: " + costSchedule.UpdateDate); const mainContainer = CostUI.text("Updated On: " + costSchedule.UpdateDate);
const callback = () => loadCostSchedule(costSchedule.id, blenderId); const callback = () => loadCostSchedule(costSchedule.id, blenderId);
const card = CostUI.createCard(costSchedule.Name, mainContainer, callback); const card = CostUI.createCard(costSchedule.Name, mainContainer, callback);
costScheduleDiv.append(card); costScheduleDiv.append(card);
}); });
const firstCostSchedule = costSchedules[0];
if (firstCostSchedule) {
loadCostSchedule(firstCostSchedule.id, blenderId);
}
} }
function handleCostItemsData(data) { function handleCostItemsData(data) {
console.log("Handling cost items data", data);
CostUI.createCostSchedule({ CostUI.createCostSchedule({
data: data.data["cost_items"], data: data.data["cost_items"]["cost_items"],
costScheduleId: data.data["cost_items"]["cost_schedule_id"],
blenderID: data.blenderId, blenderID: data.blenderId,
callbacks: { callbacks: {
"addCostItem": addCostItem, addCostItem: addCostItem,
"deleteCostItem": deleteCostItem, deleteCostItem: deleteCostItem,
"duplicateCostItem": duplicateCostItem, duplicateCostItem: duplicateCostItem,
"selectAssignedElements": selectAssignedElements, selectAssignedElements: selectAssignedElements,
'editCostItemName': editCostItemName, editCostItemName: editCostItemName,
'enableEditingCostValues': enableEditingCostValues, enableEditingCostValues: enableEditingCostValues,
} addSummaryCostItem: addSummaryCostItem,
},
}); });
} }
@@ -224,7 +252,10 @@ function executeOperator(operator, blenderId) {
} }
function loadCostSchedule(costScheduleId, blenderId) { function loadCostSchedule(costScheduleId, blenderId) {
executeOperator({ type: "loadCostSchedule", costScheduleId: costScheduleId }, blenderId); executeOperator(
{ type: "loadCostSchedule", costScheduleId: costScheduleId },
blenderId
);
} }
function getCostSchedules(blenderId) { function getCostSchedules(blenderId) {
@@ -232,5 +263,5 @@ function getCostSchedules(blenderId) {
} }
function enableEditingCostValues(costItemId) { function enableEditingCostValues(costItemId) {
executeOperator({ type: "enableEditingCostValues", costItemId: costItemId}); executeOperator({ type: "enableEditingCostValues", costItemId: costItemId });
} }
@@ -2,7 +2,7 @@ export class CostUI {
constructor() {} constructor() {}
static isCostScheduleLoaded(id) { static isCostScheduleLoaded(id) {
const existingTable = document.getElementById('cost-items-' + id); const existingTable = document.getElementById("cost-items-" + id);
return existingTable !== null; return existingTable !== null;
} }
@@ -13,11 +13,18 @@ export class CostUI {
CostUI.isCostScheduleLoaded(id) ? CostUI.removeCostSchedule(id) : null; CostUI.isCostScheduleLoaded(id) ? CostUI.removeCostSchedule(id) : null;
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");
tbody.setAttribute("id", "cost-items"); tbody.setAttribute("id", "cost-items");
const columnHeaders = ["Name", "Quantity", "Unit", "Cost", "Total Cost", "Action"]; const columnHeaders = [
"Name",
"Quantity",
"Unit",
"Cost",
"Total Cost",
"Action",
];
const tr = document.createElement("tr"); const tr = document.createElement("tr");
for (let i = 0; i < columnHeaders.length; i++) { for (let i = 0; i < columnHeaders.length; i++) {
@@ -40,7 +47,7 @@ export class CostUI {
CostUI.addTableStyles(id); CostUI.addTableStyles(id);
CostUI.createContextMenu(callbacks); CostUI.createContextMenu(callbacks);
table.get_blender_id = function() { table.get_blender_id = function () {
return this.getAttribute("id").split("-")[2]; return this.getAttribute("id").split("-")[2];
}; };
@@ -62,8 +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");
@@ -71,11 +77,11 @@ export class CostUI {
<button id="edit-cost-values-button">Edit Cost Values</button> <button id="edit-cost-values-button">Edit Cost Values</button>
<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-button">Delete</button> <button id="delete-cost-item">Delete</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");
@@ -92,21 +98,26 @@ export class CostUI {
} }
}); });
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("edit-cost-values-button"); const editCostValuesButton = document.getElementById(
editCostValuesButton.addEventListener("click", function() { "edit-cost-values-button"
);
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(costItemId) : null; callbacks.enableEditingCostValues
? callbacks.enableEditingCostValues(costItemId)
: null;
} }
document.getElementById("context-menu").style.display = "none";
}); });
const addButton = document.getElementById("add-cost-item-button"); const addButton = document.getElementById("add-cost-item-button");
@@ -124,51 +135,54 @@ export class CostUI {
addButton.dataset.listenerAdded = "true"; addButton.dataset.listenerAdded = "true";
} }
const deleteButton = document.getElementById("delete-button"); const deleteCostItem = document.getElementById("delete-cost-item");
if (deleteButton && !deleteButton.hasListener) { if (deleteCostItem && !deleteCostItem.hasListener) {
deleteButton.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 = JSON.parse(localStorage.getItem('expandedState')) || {}; let expandedState =
expandedState = CostUI.deleteRow(targetRow, expandedState) JSON.parse(localStorage.getItem("expandedState")) || {};
localStorage.setItem('expandedState', JSON.stringify(expandedState)); expandedState = CostUI.deleteRow(targetRow, expandedState);
localStorage.setItem("expandedState", JSON.stringify(expandedState));
callbacks.deleteCostItem(costItemId); callbacks.deleteCostItem(costItemId);
} }
document.getElementById("context-menu").style.display = "none";
}); });
deleteButton.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";
}); });
duplicateButton.hasListener = true; duplicateButton.hasListener = true;
} }
} }
static deleteRow(targetRow, expandedState) { static deleteRow(targetRow, expandedState) {
if (!targetRow) { if (!targetRow) {
return return;
} }
delete expandedState[targetRow.id] delete expandedState[targetRow.id];
targetRow.remove() targetRow.remove();
const subRows = document.querySelectorAll(`[parent-id='${targetRow.id}']`); const subRows = document.querySelectorAll(`[parent-id='${targetRow.id}']`);
subRows.forEach(subRow => { subRows.forEach((subRow) => {
CostUI.deleteRow(subRow, expandedState) CostUI.deleteRow(subRow, expandedState);
}); });
return expandedState return expandedState;
} }
static addResizer(resizer) { static addResizer(resizer) {
let startX, startWidth, th; let startX, startWidth, th;
resizer.addEventListener("mousedown", function(e) { resizer.addEventListener("mousedown", function (e) {
th = e.target.parentElement; th = e.target.parentElement;
startX = e.pageX; startX = e.pageX;
startWidth = th.offsetWidth; startWidth = th.offsetWidth;
@@ -200,9 +214,17 @@ export class CostUI {
const num = parseInt(color.slice(1), 16), const num = parseInt(color.slice(1), 16),
amt = Math.round(2.55 * percent), amt = Math.round(2.55 * percent),
R = (num >> 16) + amt, R = (num >> 16) + amt,
G = (num >> 8 & 0x00FF) + amt, G = ((num >> 8) & 0x00ff) + amt,
B = (num & 0x0000FF) + amt; B = (num & 0x0000ff) + amt;
return `#${(0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 + (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 + (B < 255 ? B < 1 ? 0 : B : 255)).toString(16).slice(1).toUpperCase()}`; return `#${(
0x1000000 +
(R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 +
(G < 255 ? (G < 1 ? 0 : G) : 255) * 0x100 +
(B < 255 ? (B < 1 ? 0 : B) : 255)
)
.toString(16)
.slice(1)
.toUpperCase()}`;
} }
static applyColorScheme(tableId, colorScheme) { static applyColorScheme(tableId, colorScheme) {
@@ -213,18 +235,81 @@ export class CostUI {
}); });
} }
static enableAddingCostSchedule(types, addCostSchedule) {
const addNewSchedule = document.getElementById("add-cost-schedule");
addNewSchedule.addEventListener("click", function () {
const name = "Add New Cost Schedule";
const form = CostUI.Form({
id: "add-cost-schedule-form",
name: name,
className: "floating-form",
});
const nameLabel = document.createElement("label");
nameLabel.textContent = "Name:";
form.appendChild(nameLabel);
form.classList.add("column-container");
const nameInput = document.createElement("input");
nameInput.type = "text";
nameInput.name = "name";
form.appendChild(nameInput);
const typeLabel = document.createElement("label");
typeLabel.textContent = "Type:";
form.appendChild(typeLabel);
const typeSelect = document.createElement("select");
typeSelect.name = "Predefined Type";
console.log(types);
types.forEach((type) => {
const option = document.createElement("option");
option.value = type.name;
option.textContent = type.name;
option.title = type.description;
typeSelect.appendChild(option);
});
form.appendChild(typeSelect);
const submitButton = document.createElement("button");
submitButton.type = "submit";
submitButton.textContent = "Add";
submitButton.classList.add("action-button");
form.appendChild(submitButton);
document.body.appendChild(form);
form.addEventListener("submit", function (event) {
event.preventDefault();
const formData = new FormData(form);
const data = {};
formData.forEach((value, key) => {
data[key] = value;
});
console.log("Data:", data);
addCostSchedule(data.name, data["Predefined Type"]);
form.remove();
});
});
}
static createColorPicker() { static createColorPicker() {
const div = document.createElement("div");
div.classList.add("card");
const colorPicker = document.createElement("input"); const colorPicker = document.createElement("input");
colorPicker.type = "color"; colorPicker.type = "color";
colorPicker.id = "color-picker"; colorPicker.id = "color-picker";
colorPicker.value = "#ff0000"; colorPicker.value = "#ff0000";
const colorText = document.createElement("p"); const colorText = document.createElement("p");
colorText.textContent = "Select a color to change row color:"; colorText.textContent = "Select a color to change row color:";
document.getElementById("UI").appendChild(colorText); div.appendChild(colorText);
document.getElementById("UI").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;
const colorScheme = CostUI.generateColorScheme(baseColor); const colorScheme = CostUI.generateColorScheme(baseColor);
CostUI.applyColorScheme("cost-items", colorScheme); CostUI.applyColorScheme("cost-items", colorScheme);
@@ -233,33 +318,81 @@ export class CostUI {
CostUI.applyColorScheme("cost-items", colorScheme); CostUI.applyColorScheme("cost-items", colorScheme);
} }
static createCostSchedule({ data, blenderID, title, callbacks = {} }) { static createCostSchedule({
data,
costScheduleId,
blenderID,
callbacks = {},
}) {
const [table, tbody] = CostUI.createTable(blenderID, callbacks); const [table, tbody] = CostUI.createTable(blenderID, callbacks);
if (data.length === 0) {
const tr = document.createElement("tr");
const td = document.createElement("td");
td.colSpan = 6;
td.textContent = "No cost items found";
tr.appendChild(td);
tbody.appendChild(tr);
const addSummaryCostItemButton = document.createElement("button");
addSummaryCostItemButton.textContent = "Add Summary Cost Item";
addSummaryCostItemButton.addEventListener("click", function () {
callbacks.addSummaryCostItem
? callbacks.addSummaryCostItem(costScheduleId)
: null;
});
td.appendChild(addSummaryCostItemButton);
addSummaryCostItemButton.classList.add("action-button");
} else {
CostUI.createCostItem(data, tbody, 0, null, callbacks); CostUI.createCostItem(data, tbody, 0, null, callbacks);
CostUI.applyExpandedState(); CostUI.applyExpandedState();
} }
}
static createCostItem(data, container, nestingLevel = 0, parentID = null, callbacks = {}) { static createCostItem(
data.forEach(costItem => { data,
container,
nestingLevel = 0,
parentID = null,
callbacks = {}
) {
data.forEach((costItem) => {
const row = CostUI.createRow(costItem, nestingLevel, parentID, callbacks); const row = CostUI.createRow(costItem, nestingLevel, parentID, callbacks);
container.appendChild(row); container.appendChild(row);
if (costItem.is_nested_by && costItem.is_nested_by.length > 0) { if (costItem.is_nested_by && costItem.is_nested_by.length > 0) {
CostUI.createCostItem(costItem.is_nested_by, container, nestingLevel + 1, costItem.id, callbacks); CostUI.createCostItem(
costItem.is_nested_by,
container,
nestingLevel + 1,
costItem.id,
callbacks
);
} }
}); });
} }
static createRow(costItem, nestingLevel, parentID, callbacks = {}) { static createRow(costItem, nestingLevel, parentID, callbacks = {}) {
const totalQuantity = costItem.TotalCostQuantity ? parseFloat(costItem.TotalCostQuantity).toFixed(2) : '-'; const totalQuantity = costItem.TotalCostQuantity
? parseFloat(costItem.TotalCostQuantity).toFixed(2)
: "-";
const row = CostUI.createTableRow(costItem, nestingLevel, parentID); const row = CostUI.createTableRow(costItem, nestingLevel, parentID);
const expandButton = CostUI.createExpandButton(costItem); const expandButton = CostUI.createExpandButton(costItem);
const nameCell = CostUI.createNameCell(costItem, nestingLevel, expandButton, callbacks); const nameCell = CostUI.createNameCell(
costItem,
nestingLevel,
expandButton,
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(costItem.TotalAppliedValue); const totalAppliedValueCell = CostUI.createTableCell(
costItem.TotalAppliedValue
);
const totalCostCell = CostUI.createTotalCostCell(costItem); const totalCostCell = CostUI.createTotalCostCell(costItem);
const flexContainerCell = CostUI.createFlexContainerCell(costItem, callbacks); const flexContainerCell = CostUI.createFlexContainerCell(
costItem,
callbacks
);
row.appendChild(nameCell); row.appendChild(nameCell);
row.appendChild(totalCostQuantityCell); row.appendChild(totalCostQuantityCell);
@@ -268,11 +401,11 @@ export class CostUI {
row.appendChild(totalCostCell); row.appendChild(totalCostCell);
row.appendChild(flexContainerCell); row.appendChild(flexContainerCell);
row.get_id = function() { row.get_id = function () {
return this.getAttribute("id"); return this.getAttribute("id");
}; };
row.get_parent = function() { row.get_parent = function () {
const parentId = this.getAttribute("parent-id"); const parentId = this.getAttribute("parent-id");
return parentId ? document.getElementById(parentId) : null; return parentId ? document.getElementById(parentId) : null;
}; };
@@ -297,58 +430,53 @@ export class CostUI {
expandButton.classList.add("fa-angle-right"); expandButton.classList.add("fa-angle-right");
if (row.is_nested_by && row.is_nested_by.length > 0) { if (row.is_nested_by && row.is_nested_by.length > 0) {
expandButton.classList.add('fa-angle-right'); expandButton.classList.add("fa-angle-right");
} else { } else {
expandButton.style.visibility = "hidden"; expandButton.style.visibility = "hidden";
} }
expandButton.addEventListener("click", function(event) { expandButton.addEventListener("click", function (event) {
event.stopPropagation(); event.stopPropagation();
let isExpanded = CostUI.isRowExpanded(row.id); let isExpanded = CostUI.isRowExpanded(row.id);
console.log("Row is expanded", isExpanded); console.log("Row is expanded", isExpanded);
if (event.shiftKey) { if (event.shiftKey) {
if (isExpanded) { if (isExpanded) {
CostUI.contractRow(row.id); CostUI.contractRow(row.id, true);
CostUI.updateExpandedState(row.id, false, true); CostUI.updateExpandedState(row.id, false, true);
} else { } else {
CostUI.expandRow(row.id); CostUI.expandRow(row.id, true);
CostUI.updateExpandedState(row.id, true, true); CostUI.updateExpandedState(row.id, true, true);
} }
} else { } else {
if (isExpanded) { if (isExpanded) {
CostUI.contractRow(row.id); CostUI.contractRow(row.id, true);
CostUI.updateExpandedState(row.id, false); CostUI.updateExpandedState(row.id, false);
} } else {
else {
CostUI.expandRow(row.id); CostUI.expandRow(row.id);
CostUI.updateExpandedState(row.id, true); CostUI.updateExpandedState(row.id, true);
} }
} }
CostUI.applyExpandedState();
}); });
return expandButton; return expandButton;
} }
static hideRow(id) { static hideRow(id) {
const row = document.getElementById(id); const row = document.getElementById(id);
if (row) { if (row) {
row.classList.add("nested"); row.classList.add("nested");
} }
} }
static contractRow(parentId, recursive=false) { static contractRow(parentId, recursive = false) {
CostUI.updateToggleButton(parentId, 'contract'); CostUI.updateToggleButton(parentId, "contract");
const rows = document.querySelectorAll(`[parent-id='${parentId}']`); const rows = document.querySelectorAll(`[parent-id='${parentId}']`);
rows.forEach(row => { rows.forEach((row) => {
const childId = row.getAttribute('id'); const childId = row.getAttribute("id");
CostUI.hideRow(childId); CostUI.hideRow(childId);
if (recursive || (childId && CostUI.isRowExpanded(childId))) { if (recursive || (childId && CostUI.isRowExpanded(childId))) {
CostUI.contractRow(childId, recursive) CostUI.contractRow(childId, recursive);
} }
// CostUI.contractRow(childId);
}); });
} }
@@ -359,27 +487,26 @@ export class CostUI {
} }
} }
static updateToggleButton(id, type='contract') { static updateToggleButton(id, type = "contract") {
const toggleButton = document.querySelector(`[id='${id}'] .action-button`); const toggleButton = document.querySelector(`[id='${id}'] .action-button`);
if (!toggleButton) { if (!toggleButton) {
return; return;
} }
if (type === 'expand') { if (type === "expand") {
toggleButton.classList.remove("fa-angle-right"); toggleButton.classList.remove("fa-angle-right");
toggleButton.classList.add("fa-angle-up"); toggleButton.classList.add("fa-angle-up");
} } else {
else {
toggleButton.classList.remove("fa-angle-up"); toggleButton.classList.remove("fa-angle-up");
toggleButton.classList.add("fa-angle-right"); toggleButton.classList.add("fa-angle-right");
} }
} }
static expandRow(parentId, recursive=false) { static expandRow(parentId, recursive = false) {
CostUI.updateExpandedState(parentId, true); CostUI.updateExpandedState(parentId, true);
CostUI.updateToggleButton(parentId, 'expand'); CostUI.updateToggleButton(parentId, "expand");
const rows = document.querySelectorAll(`[parent-id='${parentId}']`); const rows = document.querySelectorAll(`[parent-id='${parentId}']`);
rows.forEach(row => { rows.forEach((row) => {
const childId = row.getAttribute('id'); const childId = row.getAttribute("id");
CostUI.showRow(childId); CostUI.showRow(childId);
if (recursive || (childId && CostUI.isRowExpanded(childId))) { if (recursive || (childId && CostUI.isRowExpanded(childId))) {
CostUI.expandRow(childId, recursive); CostUI.expandRow(childId, recursive);
@@ -387,27 +514,28 @@ export class CostUI {
}); });
} }
static updateExpandedState(id, isExpanded, recursive=false) { static updateExpandedState(id, isExpanded, recursive = false) {
const expandedState = JSON.parse(localStorage.getItem('expandedState')) || {}; const expandedState =
JSON.parse(localStorage.getItem("expandedState")) || {};
expandedState[id] = isExpanded; expandedState[id] = isExpanded;
localStorage.setItem('expandedState', JSON.stringify(expandedState)); localStorage.setItem("expandedState", JSON.stringify(expandedState));
if (recursive) { if (recursive) {
const rows = document.querySelectorAll(`[parent-id='${id}']`); const rows = document.querySelectorAll(`[parent-id='${id}']`);
rows.forEach(row => { rows.forEach((row) => {
CostUI.updateExpandedState(row.id, isExpanded, recursive); CostUI.updateExpandedState(row.id, isExpanded, recursive);
}); });
} }
} }
static applyExpandedState() { static applyExpandedState() {
const expandedState = JSON.parse(localStorage.getItem('expandedState')) || CostUI.getAllCostItemsIds().reduce((acc, id) => { const expandedState =
JSON.parse(localStorage.getItem("expandedState")) ||
CostUI.getAllCostItemsIds().reduce((acc, id) => {
acc[id] = true; acc[id] = true;
return acc; return acc;
}, {}); }, {});
console.log("Expanded state", expandedState); Object.keys(expandedState).forEach((id) => {
Object.keys(expandedState).forEach(id => {
if (expandedState[id]) { if (expandedState[id]) {
CostUI.expandRow(id); CostUI.expandRow(id);
} else { } else {
@@ -417,7 +545,8 @@ export class CostUI {
} }
static isRowExpanded(id) { static isRowExpanded(id) {
const expandedState = JSON.parse(localStorage.getItem('expandedState')) || {}; const expandedState =
JSON.parse(localStorage.getItem("expandedState")) || {};
return expandedState[id] || false; return expandedState[id] || false;
} }
@@ -426,13 +555,17 @@ export class CostUI {
const nameInput = document.createElement("input"); const nameInput = document.createElement("input");
nameInput.value = costItem.name ? costItem.name : "Unnamed"; nameInput.value = costItem.name ? costItem.name : "Unnamed";
nameInput.addEventListener("change", function() { nameInput.addEventListener("change", function () {
callbacks.editCostItemName ? callbacks.editCostItemName(costItem.id, this.value) : null; callbacks.editCostItemName
? callbacks.editCostItemName(costItem.id, this.value)
: null;
}); });
nameInput.addEventListener("keydown", function(event) { nameInput.addEventListener("keydown", function (event) {
if (event.key === "Enter") { if (event.key === "Enter") {
callbacks.editCostItemName ? callbacks.editCostItemName(costItem.id, this.value) : null; callbacks.editCostItemName
? callbacks.editCostItemName(costItem.id, this.value)
: null;
} }
}); });
@@ -453,18 +586,16 @@ export class CostUI {
static createTotalCostCell(costItem) { static createTotalCostCell(costItem) {
const totalCostCell = document.createElement("td"); const totalCostCell = document.createElement("td");
const totalCost = parseFloat(costItem.TotalCost).toFixed(2); const totalCost = parseFloat(costItem.TotalCost).toFixed(2);
totalCostCell.textContent = costItem.is_sum ? totalCost + " (Σ)" : totalCost; totalCostCell.textContent = costItem.is_sum
? totalCost + " (Σ)"
: totalCost;
return totalCostCell; return totalCostCell;
} }
static createFlexContainerCell(costItem, callbacks) { static createFlexContainerCell(costItem, callbacks) {
const divFlex = document.createElement("div"); const divFlex = document.createElement("div");
divFlex.classList.add("flex-container"); divFlex.classList.add("row-container");
const addButton = CostUI.createAddButton(costItem, callbacks);
const selectButton = CostUI.createSelectButton(costItem, callbacks); const selectButton = CostUI.createSelectButton(costItem, callbacks);
divFlex.appendChild(addButton);
divFlex.appendChild(selectButton); divFlex.appendChild(selectButton);
const flexContainerCell = document.createElement("td"); const flexContainerCell = document.createElement("td");
@@ -472,24 +603,15 @@ export class CostUI {
return flexContainerCell; return flexContainerCell;
} }
static createAddButton(costItem, callbacks) {
const addButton = document.createElement("button");
addButton.textContent = "+";
addButton.classList.add("add-button");
addButton.addEventListener("click", function(e) {
e.stopPropagation();
callbacks.addCostItem ? callbacks.addCostItem(costItem.id) : null;
});
return addButton;
}
static createSelectButton(costItem, callbacks) { static createSelectButton(costItem, callbacks) {
const selectButton = document.createElement("button"); const selectButton = document.createElement("button");
selectButton.classList.add("action-button"); selectButton.classList.add("action-button");
selectButton.textContent = "Select"; selectButton.textContent = "Select";
selectButton.addEventListener("click", function(e) { selectButton.addEventListener("click", function (e) {
e.stopPropagation(); e.stopPropagation();
callbacks.selectAssignedElements ? callbacks.selectAssignedElements(costItem.id) : null; callbacks.selectAssignedElements
? callbacks.selectAssignedElements(costItem.id)
: null;
}); });
return selectButton; return selectButton;
} }
@@ -511,14 +633,12 @@ export class CostUI {
static getAllCostItemsIds() { static getAllCostItemsIds() {
const rows = document.querySelectorAll("#cost-items tr"); const rows = document.querySelectorAll("#cost-items tr");
const ids = []; const ids = [];
rows.forEach(row => { rows.forEach((row) => {
ids.push(row.id); ids.push(row.id);
}); });
return ids; return ids;
} }
static text(label) { static text(label) {
const text = document.createElement("p"); const text = document.createElement("p");
text.textContent = label; text.textContent = label;
@@ -563,15 +683,23 @@ export class CostUI {
if (existingForm) { if (existingForm) {
existingForm.remove(); existingForm.remove();
} }
const form = CostUI.createFormElement(formId, "cost-values-form");
const costItemName = CostUI.getCostItemName(costItemId); const costItemName = CostUI.getCostItemName(costItemId);
const header = CostUI.createHeader("Cost Values Form for " + costItemName);
form.appendChild(header);
const table = CostUI.createCostValuesTable(costItemId, ["Type", "Category", "Value", ""]); let formName = "Cost Values: " + costItemName;
const form = CostUI.Form({
id: formId,
name: formName,
className: "floating-form",
});
costValues.forEach(costValue => { const table = CostUI.createCostValuesTable(costItemId, [
"Type",
"Category",
"Value",
"",
]);
costValues.forEach((costValue) => {
const tr = CostUI.createCostvaluesRow(costItemId, costValue, callbacks); const tr = CostUI.createCostvaluesRow(costItemId, costValue, callbacks);
table.appendChild(tr); table.appendChild(tr);
}); });
@@ -581,17 +709,11 @@ export class CostUI {
const addButton = CostUI.createAddCostValueButton(costItemId, callbacks); const addButton = CostUI.createAddCostValueButton(costItemId, callbacks);
form.appendChild(addButton); form.appendChild(addButton);
const closeButton = CostUI.createCloseButton(form);
form.appendChild(closeButton);
CostUI.makeHeaderDraggable(header, form);
document.body.appendChild(form); document.body.appendChild(form);
return form; return form;
} }
static getCostValuesTable(costItemId) { static getCostValuesTable(costItemId) {
return document.getElementById("cost-values-table-" + costItemId); return document.getElementById("cost-values-table-" + costItemId);
} }
@@ -602,9 +724,17 @@ export class CostUI {
console.log("Cost values table not found for cost item ID:", costItemId); console.log("Cost values table not found for cost item ID:", costItemId);
return; return;
} }
console.log("Adding new cost value row", costValueId); const tr = CostUI.createCostvaluesRow(
console.log(table) costItemId,
const tr = CostUI.createCostvaluesRow(costItemId, {"category": "", "name": "", "applied_value": 0, "id": costValueId, "parent": costItemId}, costValueCallbacks); {
category: "",
name: "",
applied_value: 0,
id: costValueId,
parent: costItemId,
},
costValueCallbacks
);
table.appendChild(tr); table.appendChild(tr);
} }
@@ -614,7 +744,7 @@ export class CostUI {
table.id = "cost-values-table-" + costItemId; table.id = "cost-values-table-" + costItemId;
const headerRow = document.createElement("tr"); const headerRow = document.createElement("tr");
headers.forEach(headerText => { headers.forEach((headerText) => {
const th = document.createElement("th"); const th = document.createElement("th");
th.textContent = headerText; th.textContent = headerText;
headerRow.appendChild(th); headerRow.appendChild(th);
@@ -624,9 +754,9 @@ export class CostUI {
return table; return table;
} }
static createCostvaluesRow(costItemId,costValue, costValueCallbacks) { static createCostvaluesRow(costItemId, costValue, costValueCallbacks) {
function cleanLabel(value) { function cleanLabel(value) {
return value.replace(/[^0-9.]/g, ''); return value.replace(/[^0-9.]/g, "");
} }
if (document.getElementById(costValue.id)) { if (document.getElementById(costValue.id)) {
@@ -640,23 +770,23 @@ export class CostUI {
let costType = "FIXED"; let costType = "FIXED";
if (costValue.category === "*") { if (costValue.category === "*") {
costType = "SUM"; costType = "SUM";
} } else if (costValue.category && costValue.category !== "*") {
else if (costValue.category && costValue.category !== "*") {
costType = "CATEGORY"; costType = "CATEGORY";
} } else if (costValue.applied_value) {
else if (costValue.applied_value) {
costType = "FIXED"; costType = "FIXED";
} }
const typeCell = CostUI.createTableDropdown("type", costType); const typeCell = CostUI.createTableDropdown("type", costType);
const dropdown = typeCell.querySelector("select"); const dropdown = typeCell.querySelector("select");
dropdown.addEventListener("change", function() { dropdown.addEventListener("change", function () {
const selectedType = this.value; const selectedType = this.value;
CostUI.updateRowBasedOnType(selectedType, categoryCell, valueCell1); CostUI.updateRowBasedOnType(selectedType, categoryCell, valueCell1);
if (selectedType === "SUM") { if (selectedType === "SUM") {
const costValueId = parseInt(this.closest("tr").getAttribute("id")); const costValueId = parseInt(this.closest("tr").getAttribute("id"));
const appliedValue = parseFloat(valueCell1.querySelector("input").value); const appliedValue = parseFloat(
valueCell1.querySelector("input").value
);
const costCategory = categoryCell.querySelector("input").value; const costCategory = categoryCell.querySelector("input").value;
if (costValueCallbacks.editCostValues) { if (costValueCallbacks.editCostValues) {
@@ -666,24 +796,27 @@ export class CostUI {
costCategory: costCategory, costCategory: costCategory,
appliedValue: appliedValue, appliedValue: appliedValue,
id: costValueId, id: costValueId,
costItemId: costItemId costItemId: costItemId,
} },
]); ]);
} }
} }
}); });
tr.appendChild(typeCell); tr.appendChild(typeCell);
const categoryCell = CostUI.createTableInput("text", "category", costValue.category); const categoryCell = CostUI.createTableInput(
"text",
"category",
costValue.category
);
tr.appendChild(categoryCell); tr.appendChild(categoryCell);
let value let value;
if (costValue.category === "*"){ if (costValue.category === "*") {
value = cleanLabel(costValue.label) value = cleanLabel(costValue.label);
} } else {
else { value = costValue.applied_value;
value = costValue.applied_value
} }
const valueCell1 = CostUI.createTableInput("number", "value", value); const valueCell1 = CostUI.createTableInput("number", "value", value);
tr.appendChild(valueCell1); tr.appendChild(valueCell1);
@@ -695,16 +828,19 @@ export class CostUI {
costCategory: categoryCell.querySelector("input").value, costCategory: categoryCell.querySelector("input").value,
appliedValue: parseFloat(this.value), appliedValue: parseFloat(this.value),
id: costValue.id, id: costValue.id,
costItemId: costItemId costItemId: costItemId,
} };
if (costValueCallbacks.editCostValues) { if (costValueCallbacks.editCostValues) {
costValueCallbacks.editCostValues(costItemId, [costValueData]); costValueCallbacks.editCostValues(costItemId, [costValueData]);
} }
} }
inputValue.addEventListener("change", handleCostValueChange.bind(inputValue, costItemId)); inputValue.addEventListener(
"change",
handleCostValueChange.bind(inputValue, costItemId)
);
inputValue.addEventListener("keydown", function(event) { inputValue.addEventListener("keydown", function (event) {
if (event.key === "Enter") { if (event.key === "Enter") {
event.preventDefault(); event.preventDefault();
handleCostValueChange.call(this, costItemId); handleCostValueChange.call(this, costItemId);
@@ -712,14 +848,14 @@ export class CostUI {
}); });
const deleteCell = document.createElement("td"); const deleteCell = document.createElement("td");
tr.appendChild(deleteCell); tr.appendChild(deleteCell);
const deleteButton = document.createElement("button"); const deleteCostValue = document.createElement("button");
deleteButton.classList.add("action-button"); deleteCostValue.classList.add("action-button");
deleteButton.textContent = "Delete"; deleteCostValue.textContent = "Delete";
deleteButton.addEventListener("click", function() { deleteCostValue.addEventListener("click", function () {
costValueCallbacks.deleteCostValue(costItemId, costValue.id); costValueCallbacks.deleteCostValue(costItemId, costValue.id);
tr.remove(); tr.remove();
}); });
deleteCell.appendChild(deleteButton); deleteCell.appendChild(deleteCostValue);
CostUI.updateRowBasedOnType(costType, categoryCell, valueCell1); CostUI.updateRowBasedOnType(costType, categoryCell, valueCell1);
return tr; return tr;
} }
@@ -743,13 +879,13 @@ export class CostUI {
} }
} }
static createTableDropdown(name, value="FIXED") { static createTableDropdown(name, value = "FIXED") {
const cell = document.createElement("td"); const cell = document.createElement("td");
const dropdown = document.createElement("select"); const dropdown = document.createElement("select");
dropdown.name = name; dropdown.name = name;
const options = ["FIXED", "CATEGORY", "SUM"]; const options = ["FIXED", "CATEGORY", "SUM"];
options.forEach(optionValue => { options.forEach((optionValue) => {
const option = document.createElement("option"); const option = document.createElement("option");
option.value = optionValue; option.value = optionValue;
option.textContent = optionValue; option.textContent = optionValue;
@@ -777,14 +913,14 @@ export class CostUI {
const addButton = document.createElement("button"); const addButton = document.createElement("button");
addButton.classList.add("action-button"); addButton.classList.add("action-button");
addButton.textContent = " + Add Cost Value"; addButton.textContent = " + Add Cost Value";
addButton.addEventListener("click", function(e) { addButton.addEventListener("click", function (e) {
e.preventDefault(); e.preventDefault();
callbacks.addCostValue ? callbacks.addCostValue(costItemId) : null; callbacks.addCostValue ? callbacks.addCostValue(costItemId) : null;
}); });
return addButton; return addButton;
} }
static createFormElement(id, className) { static Form({ id, name, className }) {
const form = document.createElement("form"); const form = document.createElement("form");
form.id = id; form.id = id;
form.classList.add(className); form.classList.add(className);
@@ -792,8 +928,70 @@ export class CostUI {
form.style.top = "50%"; form.style.top = "50%";
form.style.left = "50%"; form.style.left = "50%";
form.style.transform = "translate(-50%, -50%)"; form.style.transform = "translate(-50%, -50%)";
const header = CostUI.createHeader(name);
const closeButton = CostUI.createCloseButton(form);
form.appendChild(header);
form.appendChild(closeButton);
CostUI.makeFormDraggable(form);
return form; return form;
} }
static makeFormDraggable(form) {
let pos1 = 0,
pos2 = 0,
pos3 = 0,
pos4 = 0;
form.onmousedown = function (e) {
if (!e.target.closest("input, select, button")) {
dragMouseDown(e);
}
};
const formElements = form.querySelectorAll("input, select, button");
formElements.forEach((element) => {
element.onmousedown = function (e) {
e.stopImmediatePropagation();
};
});
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
let newTop = form.offsetTop - pos2;
let newLeft = form.offsetLeft - pos1;
const minTop = form.offsetHeight / 2;
const minLeft = form.offsetWidth / 2;
const maxTop = window.innerHeight - form.offsetHeight / 2;
const maxLeft = window.innerWidth - form.offsetWidth / 2;
if (newTop < minTop) newTop = minTop;
if (newTop > maxTop) newTop = maxTop;
if (newLeft < minLeft) newLeft = minLeft;
if (newLeft > maxLeft) newLeft = maxLeft;
form.style.top = newTop + "px";
form.style.left = newLeft + "px";
}
function closeDragElement() {
document.onmouseup = null;
document.onmousemove = null;
}
}
static createHeader(text) { static createHeader(text) {
const header = document.createElement("div"); const header = document.createElement("div");
@@ -806,7 +1004,7 @@ export class CostUI {
const closeButton = document.createElement("span"); const closeButton = document.createElement("span");
closeButton.classList.add("close-button"); closeButton.classList.add("close-button");
closeButton.innerHTML = "&times;"; closeButton.innerHTML = "&times;";
closeButton.addEventListener("click", function() { closeButton.addEventListener("click", function () {
form.remove(); form.remove();
const costItemId = form.id.split("-")[3]; const costItemId = form.id.split("-")[3];
CostUI.unhighlightCostItem(costItemId); CostUI.unhighlightCostItem(costItemId);
@@ -814,24 +1012,4 @@ export class CostUI {
return closeButton; return closeButton;
} }
static makeHeaderDraggable(header, form) {
header.addEventListener("mousedown", function(e) {
let offsetX = e.clientX - form.getBoundingClientRect().left;
let offsetY = e.clientY - form.getBoundingClientRect().top;
function mouseMoveHandler(e) {
form.style.left = `${e.clientX - offsetX}px`;
form.style.top = `${e.clientY - offsetY}px`;
}
function mouseUpHandler() {
document.removeEventListener("mousemove", mouseMoveHandler);
document.removeEventListener("mouseup", mouseUpHandler);
}
document.addEventListener("mousemove", mouseMoveHandler);
document.addEventListener("mouseup", mouseUpHandler);
});
}
} }
@@ -84,8 +84,14 @@
</button> </button>
<div id="client-list"></div> <div id="client-list"></div>
</div> </div>
<div id="cost-schedules"></div> <h1>Costing</h1>
<div id="UI"></div> <div id="" >
<li id="add-cost-schedule" class="action-button fas fa-plus"> </li>
Add New Schedule
</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"></div>
<footer> <footer>
@@ -81,8 +81,8 @@
</button> </button>
<div id="client-list" class="no-print"></div> <div id="client-list" class="no-print"></div>
</div> </div>
<div id="work_schedules"></div> <div id="work_schedules" class="row-container"></div>
<div id="container"></div> <div id="container" ></div>
<footer> <footer>
<p>Bonsai Version: {{version}}</p> <p>Bonsai Version: {{version}}</p>
</footer> </footer>
+3 -13
View File
@@ -279,19 +279,9 @@ class CostSchedulesData:
@classmethod @classmethod
def get_cost_schedule_types(cls): def get_cost_schedule_types(cls):
results = [] types = ifcopenshell.util.cost.get_cost_schedule_types(tool.Ifc.get())
declaration = tool.Ifc().schema().declaration_by_name("IfcCostSchedule") return [(t['name'], t['name'], t['description']) for t in types]
version = tool.Ifc.get_schema()
for attribute in declaration.attributes():
if attribute.name() == "PredefinedType":
results.extend(
[
(e, e, get_predefined_type_doc(version, "IfcCostSchedule", e))
for e in attribute.type_of_attribute().declared_type().enumeration_items()
]
)
break
return results
class CostItemRatesData: class CostItemRatesData:
+33 -2
View File
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>. # along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell.util.cost
import bpy import bpy
from bonsai.bim.module.web.data import WebData from bonsai.bim.module.web.data import WebData
import bonsai.core.tool import bonsai.core.tool
@@ -341,9 +342,26 @@ 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.
""" """
print("Handling cost operator")
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
if operator_data["type"] == "getPredefinedTypes":
print('getting predefined types')
predefined_types = ifcopenshell.util.cost.get_cost_schedule_types(ifc_file)
cls.send_webui_data(data=predefined_types, data_key="predefined_types", event="predefined_types")
if operator_data["type"] == "addCostSchedule":
bonsai.core.cost.add_cost_schedule(
tool.Ifc,
name=operator_data["name"],
predefined_type=operator_data["type"]
)
cost_schedules = ifc_file.by_type("IfcCostSchedule")
cost_schedules_json = [cs.get_info(recursive=True) for cs in cost_schedules]
currency = tool.Cost.currency()
cls.send_webui_data(data={
"cost_schedules": cost_schedules_json,
"currency": currency
}, data_key="cost_schedules", event="cost_schedules")
if operator_data["type"] == "getCostSchedules": if operator_data["type"] == "getCostSchedules":
print('getting cost schedules')
cost_schedules = ifc_file.by_type("IfcCostSchedule") cost_schedules = ifc_file.by_type("IfcCostSchedule")
cost_schedules_json = [cs.get_info(recursive=True) for cs in cost_schedules] cost_schedules_json = [cs.get_info(recursive=True) for cs in cost_schedules]
currency = tool.Cost.currency() currency = tool.Cost.currency()
@@ -353,13 +371,23 @@ class Web(bonsai.core.tool.Web):
event="cost_schedules", event="cost_schedules",
) )
if operator_data["type"] == "loadCostSchedule": if operator_data["type"] == "loadCostSchedule":
print('loading cost schedule')
cost_schedule = ifc_file.by_id(operator_data["costScheduleId"]) cost_schedule = ifc_file.by_id(operator_data["costScheduleId"])
bonsai.core.cost.enable_editing_cost_items(tool.Cost, cost_schedule=cost_schedule) bonsai.core.cost.enable_editing_cost_items(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"] == "selectAssignedElements": if operator_data["type"] == "selectAssignedElements":
print('selecting assigned elements')
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"] == "addSummaryCostItem":
cost_schedule=ifc_file.by_id(operator_data["costScheduleId"])
bonsai.core.cost.add_summary_cost_item(
tool.Ifc,
tool.Cost,
cost_schedule=cost_schedule
)
cls.load_cost_schedule_web_ui(cost_schedule)
if operator_data["type"] == "addCostItem": if operator_data["type"] == "addCostItem":
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"]))
@@ -424,7 +452,10 @@ class Web(bonsai.core.tool.Web):
@classmethod @classmethod
def load_cost_schedule_web_ui(cls, cost_schedule): def load_cost_schedule_web_ui(cls, cost_schedule):
json_data = tool.Cost.create_cost_schedule_json(cost_schedule) json_data = tool.Cost.create_cost_schedule_json(cost_schedule)
cls.send_webui_data(data=json_data, data_key="cost_items", event="cost_items") cls.send_webui_data(data={
"cost_items": json_data,
"cost_schedule_id": cost_schedule.id()
}, data_key="cost_items", event="cost_items")
@classmethod @classmethod
def handle_gantt_operator(cls, operator_data: dict) -> None: def handle_gantt_operator(cls, operator_data: dict) -> None:
@@ -19,7 +19,8 @@
import lark import lark
import ifcopenshell 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
from ifcopenshell.util.doc import get_predefined_type_doc
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"}
@@ -265,6 +266,20 @@ def get_cost_values(cost_item: ifcopenshell.entity_instance) -> list[dict[str, s
print(results) print(results)
return results return results
def get_cost_schedule_types(file):
schema: ifcopenshell_wrapper.schema_definition = ifcopenshell_wrapper.schema_by_name(file.schema)
results = []
declaration = schema.declaration_by_name("IfcCostSchedule")
version = file.schema_identifier
for attribute in declaration.attributes():
if attribute.name() == "PredefinedType":
for enumeration in attribute.type_of_attribute().declared_type().enumeration_items():
results.append({
"name": enumeration,
'description': get_predefined_type_doc(version, "IfcCostSchedule",enumeration),
})
break
return results
class CostValueUnserialiser: class CostValueUnserialiser:
def parse(self, formula: str): def parse(self, formula: str):