From c607cbc9d7a3112d12b6a33dc2ef9068f484f176 Mon Sep 17 00:00:00 2001 From: myoualid Date: Sun, 8 Sep 2024 15:29:46 +0100 Subject: [PATCH] cost module web ui improvements: -add ribbon bar, - improve settings menu with ability to change font size for tables. - Improve cost schedule spreadsheet layout --- .../data/webui/static/css/components/card.css | 6 +- .../webui/static/css/components/costTable.css | 69 +++-- .../webui/static/css/components/headerBar.css | 47 ++++ .../bonsai/bim/data/webui/static/css/cost.css | 5 +- .../bim/data/webui/static/css/shared.css | 1 - .../bonsai/bim/data/webui/static/js/cost.js | 88 +++++- .../bonsai/bim/data/webui/static/js/gantt.js | 5 +- .../data/webui/static/js/utilities/costui.js | 257 +++++++++++++++++- .../bim/data/webui/templates/costing.html | 47 ++-- 9 files changed, 452 insertions(+), 73 deletions(-) create mode 100644 src/bonsai/bonsai/bim/data/webui/static/css/components/headerBar.css diff --git a/src/bonsai/bonsai/bim/data/webui/static/css/components/card.css b/src/bonsai/bonsai/bim/data/webui/static/css/components/card.css index 152606e72f..111dbda1e4 100644 --- a/src/bonsai/bonsai/bim/data/webui/static/css/components/card.css +++ b/src/bonsai/bonsai/bim/data/webui/static/css/components/card.css @@ -25,18 +25,18 @@ transform: scale(1.05); } -:root.blender .card-body { - padding: 20px; - } :root.blender .card-title { font-size: 1.25rem; margin-bottom: 10px; + border-bottom: 1px solid #ddd; + padding: 10px; } :root.blender .card-text { font-size: 1rem; margin-bottom: 20px; + padding: 10px; } :root.blender .btn-primary { diff --git a/src/bonsai/bonsai/bim/data/webui/static/css/components/costTable.css b/src/bonsai/bonsai/bim/data/webui/static/css/components/costTable.css index f43ddede4c..b407f8e5ec 100644 --- a/src/bonsai/bonsai/bim/data/webui/static/css/components/costTable.css +++ b/src/bonsai/bonsai/bim/data/webui/static/css/components/costTable.css @@ -1,36 +1,72 @@ -table { - width: 100%; - height: 80%; - border-collapse: collapse; - user-select: none; +:root { + --fontSize: 14px; +} +body { + margin: 0; + padding: 0; } .table-container { - max-height: 70vh; margin-top: 1em; - overflow-y: auto; overflow-x: auto; + position: relative; } -form table { - height: 50%; +#cost-items { + height: calc(100vh - var(--margin-small)); } -form .table-container { - max-height: 50vh; + +.table-scrollable-body { + max-height: 70vh; + overflow-y: auto; + display: block; +} + +table { + width: 100%; + border-collapse: collapse; + user-select: none; + table-layout: fixed; + font-size: var(--fontSize); +} + +thead { + position: sticky; + top: 0; + z-index: 1; + background-color: #727272; + font-size: larger; +} + +tbody { + width: 100%; } td, th { - border: 1px solid #ddd; - padding: 8px; - text-align: left; + padding: 0; + margin: 0; + border-left: 1px solid #ddd; + border-right: 1px solid #ddd; + text-align: center; + border-bottom: 0; + border-top: 0; +} + +th { + border-bottom: 1px solid #ddd; } td { align-items: center; } +td, +th { + box-sizing: border-box; /* Include padding and border in the element's total width and height */ +} + /* first td of a row, must be flex */ td:first-child { display: flex; @@ -65,7 +101,7 @@ select { height: inherit; margin-left: 0.5em; margin-right: 0.5em; - + font-size: var(--fontSize); min-width: 50px; /* Minimum width to prevent it from becoming too small */ } @@ -101,6 +137,7 @@ input:focus { background-color: #444444; text-align: center; } + /* Responsive Styles */ @media (max-width: 768px) { table { @@ -132,4 +169,4 @@ input:focus { padding: 1px; font-size: 10px; } -} +} \ No newline at end of file diff --git a/src/bonsai/bonsai/bim/data/webui/static/css/components/headerBar.css b/src/bonsai/bonsai/bim/data/webui/static/css/components/headerBar.css new file mode 100644 index 0000000000..d5767af2ef --- /dev/null +++ b/src/bonsai/bonsai/bim/data/webui/static/css/components/headerBar.css @@ -0,0 +1,47 @@ +.ribbon-bar { + display: flex; + gap: 10px; + margin-bottom: 20px; + border: 1px solid #444; +} + +.ribbon-bar .action-button { + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + border: none; + background-color: #444; + color: #fff; + border-radius: 50%; + cursor: pointer; + transition: background-color 0.3s; + border-left: 1px solid #666; +} + +.ribbon-bar .action-button:hover { + background-color: #666; +} + +.ribbon-bar .action-button::after { + content: attr(data-tooltip); + position: absolute; + bottom: -30px; + left: 50%; + transform: translateX(-50%); + background-color: #333; + color: #fff; + padding: 5px 10px; + border-radius: 5px; + white-space: nowrap; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s; + font-size: 12px; +} + +.ribbon-bar .action-button:hover::after { + opacity: 1; +} diff --git a/src/bonsai/bonsai/bim/data/webui/static/css/cost.css b/src/bonsai/bonsai/bim/data/webui/static/css/cost.css index fc4e82f4a5..27f2b42238 100644 --- a/src/bonsai/bonsai/bim/data/webui/static/css/cost.css +++ b/src/bonsai/bonsai/bim/data/webui/static/css/cost.css @@ -2,6 +2,7 @@ @import url("./components/card.css"); @import url("./components/contextMenu.css"); @import url("./components/costTable.css"); +@import url("./components/headerBar.css"); .form-header { display: flex; @@ -19,8 +20,8 @@ span { } .floating-form { - min-width: 40%; - max-width: 80%; + height: 40vh; + width: 50vw; max-height: 80vh; height: auto; position: absolute; diff --git a/src/bonsai/bonsai/bim/data/webui/static/css/shared.css b/src/bonsai/bonsai/bim/data/webui/static/css/shared.css index 2f0837b90f..21c6c2c519 100644 --- a/src/bonsai/bonsai/bim/data/webui/static/css/shared.css +++ b/src/bonsai/bonsai/bim/data/webui/static/css/shared.css @@ -219,7 +219,6 @@ button:hover { } .row-container { - width: 100%; border-collapse: collapse; display: flex; justify-content: flex-start; diff --git a/src/bonsai/bonsai/bim/data/webui/static/js/cost.js b/src/bonsai/bonsai/bim/data/webui/static/js/cost.js index a8c031281c..335a095a60 100644 --- a/src/bonsai/bonsai/bim/data/webui/static/js/cost.js +++ b/src/bonsai/bonsai/bim/data/webui/static/js/cost.js @@ -11,21 +11,22 @@ $(document).ready(function () { setTheme(theme); connectSocket(); - - const settingsMenu = CostUI.Form({ - id: "settings-menu", - name: "Settings", - icon: "fa-solid fa-gear", - shouldHide: true, + CostUI.addSettingsMenu(); + CostUI.addRibbonButton({ + text: "Hide Schedules", + icon: "fa-regular fa-eye-slash", + callback: (button) => { + CostUI.toggleSchedulesContainer(button); + } }); - 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"; + CostUI.addRibbonButton({ + text: "Settings", + icon: "fas fa-cog", + callback: (button) => { + CostUI.toggleSettingsMenu(button); + } }); + }); function connectSocket() { @@ -85,6 +86,7 @@ function handleSelectedProducts(data) { } function handlePredefinedTypes(data) { + CostUI.enableAddingCostSchedule( data.data["predefined_types"], addCostSchedule @@ -233,6 +235,65 @@ function setTheme(theme) { localStorage.setItem("theme", theme); } +function toggleClientList() { + var clientList = $("#client-list"); + + if (clientList.hasClass("show")) { + clientList.removeClass("show"); + return; + } + + clientList.empty(); + var counter = 0; + + $.each(connectedClients, function (id, client) { + counter++; + + const dropdownIcon = $("") + .addClass("fas fa-chevron-down") + .css("margin-left", "0.5rem"); + + const clientDiv = $("
") + .addClass("client") + .text(client.ifc_file || "Blender " + counter); + + clientDiv.append(dropdownIcon); + + const clientDetailsDiv = $("
").addClass("client-details"); + + if (id) { + const clientId = $("
") + .addClass("client-detail") + .text(`Blender ID: ${id}`); + clientDetailsDiv.append(clientId); + } + + if (!client.has_drawings) { + const clientShown = $("
") + .addClass("client-detail") + .text("No drawings exist for this IFC File yet"); + clientDetailsDiv.append(clientShown); + } else { + const clientNumbers = $("
") + .addClass("client-detail") + .text( + `${allDrawings[client.ifc_file].drawings.length} Drawing(s), + ${allDrawings[client.ifc_file].sheets.length} Sheet(s)` + ); + clientDetailsDiv.append(clientNumbers); + } + + clientDiv.append(clientDetailsDiv); + + clientDiv.on("click", function () { + clientDetailsDiv.toggleClass("show"); + }); + + clientList.append(clientDiv); + }); + clientList.addClass("show"); +} + function addCostItem(costItemId) { executeOperator({ type: "addCostItem", costItemId: costItemId }); } @@ -304,6 +365,7 @@ function handleCostSchedulesData(data) { } function handleCostItemsData(data) { + console.log(data); const cards = document.querySelectorAll("[id^='schedule-']"); cards.forEach((card) => { if (card.id === "schedule-" + data.data["cost_items"]["cost_schedule_id"]) { diff --git a/src/bonsai/bonsai/bim/data/webui/static/js/gantt.js b/src/bonsai/bonsai/bim/data/webui/static/js/gantt.js index aa83615c77..5eaaffa841 100644 --- a/src/bonsai/bonsai/bim/data/webui/static/js/gantt.js +++ b/src/bonsai/bonsai/bim/data/webui/static/js/gantt.js @@ -126,7 +126,10 @@ function handleWorkScheduleData(data) { const workSchedules = data["data"]["work_schedule_info"]; workSchedules.forEach((workSchedule) => { - const mainContainer = CostUI.text(new Date(workSchedule.CreationDate).toLocaleDateString()); + const mainContainer = CostUI.Text( + "Created On: " + new Date(workSchedule.CreationDate).toLocaleDateString(), + "fa-solid fa-pen-nib" + ); const callback = () => loadWorkSchedule(workSchedule.id); const card = CostUI.createCard(workSchedule.id, workSchedule.Name,mainContainer, callback); workSchedulesDiv.append(card); diff --git a/src/bonsai/bonsai/bim/data/webui/static/js/utilities/costui.js b/src/bonsai/bonsai/bim/data/webui/static/js/utilities/costui.js index 876e1affef..435258e5bf 100644 --- a/src/bonsai/bonsai/bim/data/webui/static/js/utilities/costui.js +++ b/src/bonsai/bonsai/bim/data/webui/static/js/utilities/costui.js @@ -25,7 +25,10 @@ export class CostUI { "Total Cost", "Action", ]; + const thead = document.createElement("thead"); const tr = document.createElement("tr"); + thead.appendChild(tr); + for (let i = 0; i < columnHeaders.length; i++) { const th = document.createElement("th"); @@ -41,7 +44,7 @@ export class CostUI { } } - tbody.appendChild(tr); + table.appendChild(thead); table.appendChild(tbody); document.getElementById("cost-items").appendChild(table); @@ -69,7 +72,8 @@ export class CostUI { `; document.head.appendChild(style); } - static createContextMenu(callbacks) { + +static createContextMenu(callbacks) { const contextMenu = document.createElement("div"); contextMenu.id = "context-menu"; contextMenu.classList.add("context-menu"); @@ -144,7 +148,7 @@ export class CostUI { const costItemId = parseInt(targetRow.getAttribute("id")); let expandedState = JSON.parse(localStorage.getItem("expandedState")) || {}; - expandedState = CostUI.deleteRow(targetRow, expandedState); + expandedState = CostUI.deleteCostItemRow(targetRow, expandedState); localStorage.setItem("expandedState", JSON.stringify(expandedState)); callbacks.deleteCostItem(costItemId); } @@ -178,9 +182,51 @@ export class CostUI { }); getSelectedProducts.hasListener = true; } + + // Function to get column names from the table header + function getColumnNames(tableId) { + const table = document.getElementById(tableId); + const headerRow = table.querySelector("thead tr"); + return Array.from(headerRow.children).map(headerCell => headerCell.textContent.trim()); + } + + // Add double-click event listener + document.getElementById("cost-items").addEventListener("dblclick", function (event) { + const targetRow = event.target.closest("tr"); + const targetCell = event.target.closest("td"); + if (targetRow && targetCell) { + const columnIndex = Array.from(targetRow.children).indexOf(targetCell); + const costItemId = parseInt(targetRow.getAttribute("id")); + const cellContent = targetCell.textContent; + + // Get column names from the table header + const columnNames = getColumnNames("cost-items"); + const cellName = columnNames[columnIndex]; + + console.log("Column Name: ", cellName); + console.log("Content: ", cellContent); + + // Implement different behaviors based on the column index + switch (columnIndex) { + case 0: + // Behavior for the first column + callbacks.firstColumnDoubleClick ? callbacks.firstColumnDoubleClick(costItemId) : null; + break; + case 1: + // Behavior for the second column + callbacks.secondColumnDoubleClick ? callbacks.secondColumnDoubleClick(costItemId) : null; + break; + // Add more cases as needed for other columns + default: + // Default behavior + callbacks.defaultDoubleClick ? callbacks.defaultDoubleClick(costItemId) : null; + break; + } + } + }); } - static deleteRow(targetRow, expandedState) { + static deleteCostItemRow(targetRow, expandedState) { if (!targetRow) { return; } @@ -188,7 +234,7 @@ export class CostUI { targetRow.remove(); const subRows = document.querySelectorAll(`[parent-id='${targetRow.id}']`); subRows.forEach((subRow) => { - CostUI.deleteRow(subRow, expandedState); + CostUI.deleteCostItemRow(subRow, expandedState); }); return expandedState; } @@ -258,6 +304,7 @@ export class CostUI { const form = CostUI.Form({ id: "add-cost-schedule-form", name: name, + icon: "fa-solid fa-money-bill-wave", }); const nameLabel = document.createElement("label"); @@ -672,9 +719,8 @@ export class CostUI { const cardBody = document.createElement("div"); cardBody.classList.add("card-body"); - const cardTitle = document.createElement("h5"); + const cardTitle = CostUI.Text(title, "fa-solid fa-money-bill-wave", "large"); cardTitle.classList.add("card-title"); - cardTitle.textContent = title; const cardButton = document.createElement("button"); cardButton.classList.add("action-button"); @@ -1163,10 +1209,10 @@ export class CostUI { const subtotalRow = document.createElement("tr"); const cells = [ + { textContent: "", style: { border: "none" } }, + { textContent: "", style: { border: "none" } }, + { textContent: "", style: { border: "none" } }, { textContent: "Subtotal", colSpan: 4, style: { border: "none" } }, - { textContent: "", style: { border: "none" } }, - { textContent: "", style: { border: "none" } }, - { textContent: "", style: { border: "none" } }, { textContent: quantitySums.count }, ]; @@ -1202,15 +1248,25 @@ export class CostUI { costItemId, callbacks, }) { + if (products.length === 0) { + const noProductsMessage = document.createElement("p"); + noProductsMessage.textContent = "Your Blender Selection is empty! Select objects first."; + form.appendChild(noProductsMessage); + return; + } const { tableContainer, table } = CostUI.addTable({ headers: ["Step Id", "Class", "Name", "Type", "Count"], className: "product-table", id: "cost-values-table-" + costItemId, }); + form.appendChild(tableContainer); + + const scrollableBody = document.createElement("div"); + scrollableBody.classList.add("table-scrollable-body"); const tbody = document.createElement("tbody"); - table.appendChild(tbody); - form.appendChild(tableContainer); + scrollableBody.appendChild(tbody); + table.appendChild(scrollableBody); const quantityAssigned = CostUI.getAssinedQuantity(products, costItemId); const areProductsSameClass = products.every( @@ -1343,9 +1399,12 @@ export class CostUI { id: "cost-values-table-" + costItemId, }); - const tbody = document.createElement("tbody"); - table.appendChild(tbody); form.appendChild(tableContainer); + const scrollableBody = document.createElement("div"); + scrollableBody.classList.add("table-scrollable-body"); + const tbody = document.createElement("tbody"); + scrollableBody.appendChild(tbody); + table.appendChild(scrollableBody); let quantityAssigned = CostUI.getAssinedQuantity(products, costItemId); const shouldAddQto = quantityAssigned ? true : false; @@ -1417,4 +1476,174 @@ export class CostUI { addProductAssignmentsButton.classList.add("action-button"); return addProductAssignmentsButton; } + + static addSettingsMenu() { + if (document.getElementById("settings-menu")) { + return document.getElementById("settings-menu"); + } + const settingsMenu = CostUI.Form({ + id: "settings-menu", + name: "Settings", + icon: "fa-solid fa-gear", + shouldHide: true, + }); + + const picker = CostUI.createColorPicker(); + const tableFontSize = CostUI.createFontSizePicker(); + settingsMenu.appendChild(picker); + settingsMenu.appendChild(tableFontSize); + + settingsMenu.style.display = "none"; + const settingsButton = document.getElementById("settings-button"); + settingsButton.addEventListener("click", function () { + settingsMenu.style.display = "block"; + }); + + // Apply saved settings on page load + CostUI.applySavedSettings(); + } + + static createFontSizePicker() { + const div = document.createElement("div"); + const fontSizeText = document.createElement("p"); + fontSizeText.textContent = "Select a font size for the table:"; + div.appendChild(fontSizeText); + const fontSizePicker = document.createElement("input"); + fontSizePicker.type = "number"; + fontSizePicker.id = "font-size-picker"; + fontSizePicker.value = localStorage.getItem("tableFontSize") || 14; + div.appendChild(fontSizePicker); + fontSizePicker.addEventListener("input", function () { + const fontSize = fontSizePicker.value + "px"; + CostUI.saveFontSize(fontSize); + CostUI.applyFontSizeToAllTables(fontSize); + }); + return div; + } + + static createColorPicker() { + const div = document.createElement("div"); + const colorText = document.createElement("p"); + colorText.textContent = "Select a color for the table:"; + div.appendChild(colorText); + const colorPicker = document.createElement("input"); + colorPicker.type = "color"; + colorPicker.id = "color-picker"; + colorPicker.value = localStorage.getItem("tableColor") || "#ffffff"; + div.appendChild(colorPicker); + colorPicker.addEventListener("input", function () { + const color = colorPicker.value; + CostUI.saveColor(color); + CostUI.applyColorToAllTables(color); + }); + return div; + } + + static saveFontSize(fontSize) { + localStorage.setItem("tableFontSize", fontSize); + } + + static saveColor(color) { + localStorage.setItem("tableColor", color); + } + + static applyFontSizeToAllTables(fontSize) { + document.documentElement.style.setProperty('--fontSize', fontSize); + } + + static applyColorToAllTables(color) { + const tables = document.querySelectorAll("table"); + tables.forEach((table) => { + table.style.backgroundColor = color; + }); + } + + static applySavedSettings() { + console.log("Applying saved settings"); + const savedFontSize = localStorage.getItem("tableFontSize"); + if (savedFontSize) { + CostUI.applyFontSizeToAllTables(savedFontSize); + const fontSizePicker = document.getElementById("font-size-picker"); + if (fontSizePicker) { + fontSizePicker.value = parseInt(savedFontSize, 10); + } + } + + const savedColor = localStorage.getItem("tableColor"); + if (savedColor) { + CostUI.applyColorToAllTables(savedColor); + const colorPicker = document.getElementById("color-picker"); + if (colorPicker) { + colorPicker.value = savedColor; + } + } + } + + static getRibbonBar() { + return document.getElementById("ribbon-bar"); + } + + static addRibbonButton({ text, icon, callback }) { + const ribbonBar = CostUI.getRibbonBar(); + + const button = document.createElement("button"); + button.classList.add("action-button", ...icon.split(" ")); + button.dataset.tooltip = text; // Use dataset to set the tooltip attribute + + button.addEventListener("click", () => callback(button)); + + ribbonBar.appendChild(button); + } + + static toggleSchedulesContainer(button) { + const schedulesContainer = CostUI.getSchedulesContainer(); + const isHidden = schedulesContainer.style.display === "none"; + + if (isHidden) { + CostUI.displaySchedulesContainer(); + button.classList.remove("fa-eye"); + button.classList.add("fa-eye-slash"); + button.dataset.tooltip = "Hide Schedules"; + } else { + CostUI.hideSchedulesContainer(); + button.classList.remove("fa-eye-slash"); + button.classList.add("fa-eye"); + button.dataset.tooltip = "Display Schedules"; + } + } + + static getSchedulesContainer() { + return document.getElementById("cost-schedules"); + } + + static displaySchedulesContainer() { + const schedulesContainer = CostUI.getSchedulesContainer(); + schedulesContainer.style.display = "flex"; + } + + static hideSchedulesContainer() { + const schedulesContainer = CostUI.getSchedulesContainer(); + schedulesContainer.style.display = "none"; + } + + static getSettingsMenu() { + return document.getElementById("settings-menu"); + } + + static toggleSettingsMenu(button) { + const settingsMenu = CostUI.getSettingsMenu(); + const isHidden = settingsMenu.style.display === "none"; + + if (isHidden) { + settingsMenu.style.display = "block"; + button.classList.remove("fa-gear"); + button.classList.add("fa-times"); + button.dataset.tooltip = "Close Settings"; + } else { + settingsMenu.style.display = "none"; + button.classList.remove("fa-times"); + button.classList.add("fa-gear"); + button.dataset.tooltip = "Open Settings"; + } + } } diff --git a/src/bonsai/bonsai/bim/data/webui/templates/costing.html b/src/bonsai/bonsai/bim/data/webui/templates/costing.html index d165b7007d..cdc6d3947c 100644 --- a/src/bonsai/bonsai/bim/data/webui/templates/costing.html +++ b/src/bonsai/bonsai/bim/data/webui/templates/costing.html @@ -4,7 +4,12 @@ BlenderBIM Web UI - + - -
- -
+
+
+ +
-

Costing

-
-
  • - Add New Schedule -
    -
    -
    -