import QtQuick import QtQuick.Layouts import QtQuick.Controls import QtQuick.Dialogs import ISC // ===== Status Tab — Bento Grid Dashboard ===== // ROW 1: Connection Status (65%) | Cycles Completed (35%) // ROW 2: Wafer & Sensor + Directory (65%) | Total Runtime (35%) // ROW 3: Activity Log (100%, fills remaining) ColumnLayout { id: root anchors.fill: parent anchors.margins: Theme.panelPadding spacing: 8 // ── Reusable Inline Components ── component StatCard : Rectangle { property alias value: valueText.text property alias label: labelText.text property bool active: root.waferDetected property int valSize: active ? Theme.font3xl : Theme.font2xl Layout.fillWidth: true Layout.fillHeight: true color: Theme.cardBackground border.color: Theme.cardBorder border.width: 1 radius: Theme.radiusSm ColumnLayout { anchors.centerIn: parent spacing: 4 Text { id: valueText color: active ? Theme.headingColor : Theme.sideMutedText font.pixelSize: valSize font.weight: Font.Bold font.family: Theme.uiFontFamily Layout.alignment: Qt.AlignHCenter } Text { id: labelText color: Theme.sideMutedText font.pixelSize: Theme.fontXs font.weight: Font.Medium font.letterSpacing: 1.2 font.family: Theme.uiFontFamily Layout.alignment: Qt.AlignHCenter } } } component GridCell : RowLayout { property alias label: labelText.text property alias value: valueText.text property bool active: root.waferDetected spacing: 8 Layout.fillWidth: true Text { id: labelText color: Theme.sideMutedText font.pixelSize: Theme.fontXs font.family: Theme.uiFontFamily Layout.fillWidth: true elide: Text.ElideRight verticalAlignment: Text.AlignVCenter } Text { id: valueText color: active ? Theme.headingColor : Theme.sideMutedText font.pixelSize: Theme.fontSm font.weight: Font.Bold font.family: Theme.uiFontFamily Layout.alignment: Qt.AlignRight verticalAlignment: Text.AlignVCenter } } // ── Aliases for external wiring ── property alias waferFamilyCode: waferInfoFamilyCell.value property alias waferSerialNumber: waferSerialCell.value property alias waferSensorCount: waferSensorsCell.value property alias waferRuntime: runtimeCard.value property alias waferCycles: waferCyclesCell.value property bool waferDetected: { var info = deviceController.lastWaferInfo; return info && info.length > 0 && info[0] !== ""; } property int dataRows: 0 property int dataCols: 0 property string csvPath: "" property bool dataParsed: false readonly property bool isConnected: deviceController.connectionStatus === "Connected" readonly property bool isBusy: deviceController.connectionStatus.endsWith("...") readonly property string portName: deviceController.selectedPort || "—" function cleanFolderUrl(url) { return decodeURIComponent(String(url).replace(/^file:\/\//, "")); } function currentFamilyCode() { var info = deviceController.lastWaferInfo; return info && info.length > 0 ? (info[0] || "") : ""; } function parseAndSavePendingRead() { deviceController.parseAndSaveData(root.currentFamilyCode(), deviceController.selectedPort || ""); } FolderDialog { id: saveDirDialog title: "Choose a folder to save." onAccepted: { deviceController.setSaveDataDir(root.cleanFolderUrl(selectedFolder)); root.parseAndSavePendingRead(); } } // ═══════════════════════════════════════════════════════════════════════ // ROW 1: Connection Status (100%) // ═══════════════════════════════════════════════════════════════════════ RowLayout { Layout.fillWidth: true Layout.preferredHeight: 90 Layout.maximumHeight: 90 spacing: 8 Rectangle { Layout.fillWidth: true Layout.fillHeight: true color: Theme.cardBackground border.color: root.isConnected ? Theme.statusSuccessColor : (root.isBusy ? Theme.statusWarningColor : Theme.statusErrorColor) border.width: 1 radius: Theme.radiusSm ColumnLayout { id: connStatusCol anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter anchors.leftMargin: Theme.panelPadding anchors.rightMargin: Theme.panelPadding spacing: 8 Text { text: "CONNECTION STATUS" color: Theme.sideMutedText font.pixelSize: Theme.fontXs font.weight: Font.Medium font.letterSpacing: 1.2 font.family: Theme.uiFontFamily } RowLayout { Layout.fillWidth: true spacing: 0 ColumnLayout { spacing: 1 Text { text: "HOST PC" color: Theme.sideMutedText font.pixelSize: Theme.fontXs font.family: Theme.uiFontFamily } Text { text: "localhost" color: Theme.headingColor font.pixelSize: Theme.fontMd font.weight: Font.Bold font.family: Theme.uiFontFamily } } Item { Layout.fillWidth: true implicitHeight: 22 Row { anchors.centerIn: parent spacing: 5 Repeater { model: 4 Rectangle { width: 4; height: 4; radius: 2 anchors.verticalCenter: parent.verticalCenter color: root.isConnected ? Theme.statusSuccessColor : (root.isBusy ? Theme.statusWarningColor : Theme.sideMutedText) opacity: (root.isConnected || root.isBusy) ? 1.0 : 0.3 SequentialAnimation on opacity { running: root.isConnected || root.isBusy loops: Animation.Infinite NumberAnimation { to: 0.3; duration: 1400 } NumberAnimation { to: 1.0; duration: 1400 } } } } Rectangle { anchors.verticalCenter: parent.verticalCenter width: statusPillText.implicitWidth + 16 height: 20 radius: 10 color: root.isConnected ? Theme.statusSuccessColor : (root.isBusy ? Theme.statusWarningColor : Theme.statusErrorColor) Text { id: statusPillText anchors.centerIn: parent text: root.isConnected ? "ACTIVE" : (root.isBusy ? deviceController.connectionStatus.replace("...", "").toUpperCase() : "INACTIVE") color: "#111111" font.pixelSize: Theme.fontXs font.weight: Font.Bold font.family: Theme.uiFontFamily font.letterSpacing: 0.6 } } Repeater { model: 4 Rectangle { width: 4; height: 4; radius: 2 anchors.verticalCenter: parent.verticalCenter color: root.isConnected ? Theme.statusSuccessColor : (root.isBusy ? Theme.statusWarningColor : Theme.sideMutedText) opacity: (root.isConnected || root.isBusy) ? 1.0 : 0.3 SequentialAnimation on opacity { running: root.isConnected || root.isBusy loops: Animation.Infinite NumberAnimation { to: 0.3; duration: 1400 } NumberAnimation { to: 1.0; duration: 1400 } } } } } } ColumnLayout { spacing: 1 Text { text: "COM PORT" color: Theme.sideMutedText font.pixelSize: Theme.fontXs font.family: Theme.uiFontFamily Layout.alignment: Qt.AlignRight } Text { text: root.portName color: root.isConnected ? Theme.statusSuccessColor : Theme.headingColor font.pixelSize: Theme.fontMd font.weight: Font.Bold font.family: Theme.uiFontFamily Layout.alignment: Qt.AlignRight } } } } } } // ═══════════════════════════════════════════════════════════════════════ // ROW 2: Wafer & Sensor + Directory (65%) | Total Runtime (35%) // ═══════════════════════════════════════════════════════════════════════ RowLayout { Layout.fillWidth: true Layout.preferredHeight: 90 Layout.maximumHeight: 90 spacing: 8 RowLayout { Layout.preferredWidth: 65 Layout.fillWidth: true Layout.fillHeight: true spacing: 8 Rectangle { Layout.fillWidth: true Layout.fillHeight: true color: Theme.cardBackground border.color: Theme.cardBorder border.width: 1 radius: Theme.radiusSm ColumnLayout { anchors.fill: parent anchors.margins: Theme.panelPadding spacing: 6 ColumnLayout { Layout.fillWidth: true spacing: 2 Text { text: "DIRECTORY" color: Theme.sideMutedText font.pixelSize: Theme.fontMd font.weight: Font.Medium font.letterSpacing: 1.2 font.family: Theme.uiFontFamily } Text { text: deviceController.saveDataDir || "Not configured" color: Theme.bodyColor font.pixelSize: Theme.fontSm font.family: Theme.uiFontFamily font.italic: true opacity: 0.7 elide: Text.ElideMiddle Layout.fillWidth: true } } RowLayout { Layout.fillWidth: true spacing: 8 Rectangle { visible: root.csvPath !== "" width: sessionSavedLabel.implicitWidth + 12 height: 18 radius: 4 color: Theme.fieldBackground border.color: Theme.sideBorder border.width: 1 Text { id: sessionSavedLabel anchors.centerIn: parent text: "SESSION SAVED" color: Theme.sideMutedText font.pixelSize: Theme.fontXs font.weight: Font.Bold font.family: Theme.uiFontFamily font.letterSpacing: 0.6 } } Item { Layout.fillWidth: true } Button { id: browseBtn text: "Save As" font.pixelSize: Theme.fontSm font.weight: Font.Medium font.family: Theme.uiFontFamily implicitHeight: 24 implicitWidth: 70 hoverEnabled: true background: Rectangle { color: browseBtn.hovered ? Theme.buttonNeutralHover : Theme.buttonNeutralBackground border.color: Theme.cardBorder border.width: 1 radius: Theme.radiusSm } contentItem: Text { text: browseBtn.text color: Theme.bodyColor font: browseBtn.font horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } onClicked: saveDirDialog.open() } } } } Rectangle { Layout.fillWidth: true Layout.fillHeight: true color: Theme.cardBackground border.color: Theme.cardBorder border.width: 1 radius: Theme.radiusSm ColumnLayout { anchors.fill: parent anchors.margins: 10 spacing: 4 Text { text: "WAFER & SENSOR DATA" color: Theme.sideMutedText font.pixelSize: Theme.fontXs font.weight: Font.Bold font.family: Theme.uiFontFamily font.letterSpacing: 0.6 Layout.bottomMargin: 2 } GridLayout { Layout.fillWidth: true Layout.fillHeight: true columns: 2 rowSpacing: 4 columnSpacing: 16 GridCell { id: waferInfoFamilyCell label: "Family Code" value: { var info = deviceController.lastWaferInfo; return (info && info.length > 0 && info[0]) ? info[0] : "—"; } } GridCell { id: waferCyclesCell label: "Cycles Completed" value: { var info = deviceController.lastWaferInfo; return (info && info.length > 5 && info[5] !== undefined) ? String(info[5]) : "—"; } active: root.waferDetected } Rectangle { Layout.columnSpan: 2 Layout.fillWidth: true height: 1 color: Theme.cardBorder opacity: 0.5 } GridCell { id: waferSerialCell label: "Serial" value: { var info = deviceController.lastWaferInfo; return (info && info.length > 1 && info[1]) ? info[1] : "—"; } } GridCell { id: waferSensorsCell label: "Sensors" value: { var info = deviceController.lastWaferInfo; return (info && info.length > 2 && info[2]) ? String(info[2]) : "—"; } } } } } } StatCard { id: runtimeCard Layout.preferredWidth: 35 value: { var info = deviceController.lastWaferInfo; return (info && info.length > 4 && info[4] !== undefined) ? info[4] + "s" : "—"; } label: "TOTAL RUNTIME" } } // ═══════════════════════════════════════════════════════════════════════ // ROW 3: Activity Log — ALWAYS visible (fills remaining height) // ═══════════════════════════════════════════════════════════════════════ Rectangle { Layout.fillWidth: true Layout.fillHeight: true color: Theme.cardBackground border.color: Theme.cardBorder border.width: 1 radius: Theme.radiusMd clip: true ColumnLayout { anchors.fill: parent spacing: 0 Rectangle { Layout.fillWidth: true Layout.preferredHeight: 36 color: Theme.subtleSectionBackground RowLayout { anchors.fill: parent anchors.leftMargin: Theme.panelPadding anchors.rightMargin: Theme.panelPadding spacing: 8 Text { text: "📄" font.pixelSize: Theme.fontSm visible: root.csvPath !== "" Layout.alignment: Qt.AlignVCenter } Label { text: root.csvPath !== "" ? root.csvPath.split("/").pop() : "ACTIVITY LOG" color: Theme.bodyColor font.pixelSize: root.csvPath !== "" ? Theme.fontMd : Theme.fontSm font.letterSpacing: root.csvPath !== "" ? 0 : 1.8 font.weight: Font.Medium font.family: Theme.uiFontFamily elide: Text.ElideMiddle Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter } Button { id: refreshSessionBtn text: "Refresh" font.pixelSize: Theme.fontSm font.family: Theme.uiFontFamily implicitHeight: 24 hoverEnabled: true background: Rectangle { color: refreshSessionBtn.hovered ? Theme.buttonNeutralHover : "transparent" radius: Theme.radiusSm } contentItem: Text { text: refreshSessionBtn.text color: Theme.bodyColor font: refreshSessionBtn.font horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } onClicked: deviceController.clearSession() } Button { id: clearLogBtn text: "Clear" font.pixelSize: Theme.fontSm font.family: Theme.uiFontFamily implicitHeight: 24 hoverEnabled: true background: Rectangle { color: clearLogBtn.hovered ? Theme.buttonNeutralHover : "transparent" radius: Theme.radiusSm } contentItem: Text { text: clearLogBtn.text color: Theme.bodyColor font: clearLogBtn.font horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } onClicked: deviceController.clearActivityLog() } } Rectangle { anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right height: 1 color: Theme.cardBorder } } ScrollView { Layout.fillWidth: true Layout.fillHeight: true Layout.margins: Theme.panelPadding clip: true TextArea { id: activityLog width: parent.width text: qsTr("Welcome — use the side rail to detect a wafer, or import data to review.") readOnly: true font.family: "monospace" font.pixelSize: Theme.fontSm wrapMode: TextArea.WordWrap color: Theme.bodyColor background: null } } } Connections { target: deviceController function onActivityLogUpdated(newLog) { activityLog.text = newLog ? newLog : qsTr("Welcome — use the side rail to detect a wafer, or import data to review."); } } } // ═══════════════════════════════════════════════════════════════════════ // SIGNAL HANDLERS // ═══════════════════════════════════════════════════════════════════════ Connections { target: deviceController function onLogMessage(message) { if (message == "CHOOSE_SAVE_DIR") { saveDirDialog.open(); } } function onPortsUpdated() {} function onDetectResult(result) { if (!result) { root.dataParsed = false; root.dataRows = 0; root.dataCols = 0; root.csvPath = ""; } } function onStatusRestored() { if (deviceController.dataRowCount > 0) { root.dataParsed = true; root.dataRows = deviceController.dataRowCount; root.dataCols = deviceController.dataColCount; root.csvPath = ""; } } } Connections { target: deviceController function onReadResult(result) { root.dataParsed = false; root.dataRows = 0; root.dataCols = 0; root.csvPath = ""; if (result && result.success === true) saveDirDialog.open(); } } Connections { target: deviceController function onParsedDataReady(result) { if (result && result.success) { root.dataParsed = true; root.dataRows = result.rows; root.dataCols = result.cols; root.csvPath = result.csv_path || ""; } else { root.dataParsed = false; } } } }