Files
poa-pds/Production Display.py
2025-08-06 12:24:45 +05:30

1182 lines
49 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import sys
import requests
import base64
import platform
from PyQt6.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QLabel, QLineEdit, QPushButton,
QToolButton, QSizePolicy, QStackedWidget, QSpacerItem, QTextEdit, QScrollArea,
QMessageBox, QHBoxLayout, QTabWidget, QFileDialog, QComboBox, QDialog
)
from PyQt6.QtCore import Qt, QEvent, QTime, pyqtSlot, QTimer, QUrl, QObject, QDateTime
from PyQt6.QtGui import QPalette, QColor, QPixmap, QFont
from PyQt6.QtWebChannel import QWebChannel
from PyQt6.QtWebEngineWidgets import QWebEngineView
# Placeholder new screens to navigate on button click
class JSBridge(QObject):
@pyqtSlot(str, str, str)
def saveFile(self, base64_data, filename, source):
try:
if base64_data.startswith("data:"):
base64_data = base64_data.split(",", 1)[1]
data = base64.b64decode(base64_data)
path, _ = QFileDialog.getSaveFileName(None, f"Save {source.title()} File", filename, "All Files (*)")
if path:
with open(path, 'wb') as f:
f.write(data)
QMessageBox.information(None, f"{source.title()} Download", f"File saved to:\n{path}")
if platform.system() == "Windows":
os.startfile(path)
elif platform.system() == "Darwin":
os.system(f"open '{path}'")
else:
os.system(f"xdg-open '{path}'")
except Exception as e:
QMessageBox.critical(None, "Save Failed", str(e))
class ProductionDisplayScreen(QWidget):
def __init__(self, stacked, url="https://pds.iotsignin.com/admin"):
super().__init__()
self.stacked = stacked
self.url = url
self.setObjectName("ProductionDisplayScreen")
self.init_ui()
def init_ui(self):
self.channel = QWebChannel()
self.js_bridge = JSBridge()
self.channel.registerObject("pyBridge", self.js_bridge)
# Header
header = QWidget()
hl = QHBoxLayout(header)
hl.setContentsMargins(8, 8, 8, 8)
header.setStyleSheet("background-color: #e6f2ff;")
# Logo
logo = QLabel()
pix = QPixmap(
r"C:\Users\Administrator\Downloads\sfg printing\backup from airline device\SRIMATHI\cri_logo.png.png")
logo.setPixmap(
pix.scaled(90, 70, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation))
hl.addWidget(logo)
hl.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
# Title
title = QLabel("CRI DIGITAL MANUFACTURING SOLUTIONS")
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
title.setStyleSheet("font-weight:bold; font-size:28px;")
hl.addWidget(title)
hl.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
# Back button
self.btn_back = QPushButton("Back")
self.btn_back.setStyleSheet("""
QPushButton {
background-color: #c2185b;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
}
QPushButton:hover {
background-color: #34495E;
}
QPushButton:pressed {
background-color: #1F2D3D;
}
""")
self.btn_back.clicked.connect(self.go_back)
hl.addWidget(self.btn_back)
# Navigation buttons
btn_style = """
QPushButton {
background-color: #c2185b;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
}
QPushButton:hover {
background-color: #34495E;
}
QPushButton:pressed {
background-color: #1F2D3D;
}
"""
self.btn_prev = QPushButton("Previous")
self.btn_refresh = QPushButton("Refresh")
self.btn_next = QPushButton("Next")
self.btn_full = QPushButton("Full Screen")
self.btn_dup = QPushButton("Duplicate")
self.btn_del = QPushButton("Delete")
self.tab_dropdown = QComboBox()
for b in [self.btn_prev, self.btn_refresh, self.btn_next, self.btn_full, self.btn_dup, self.btn_del]:
b.setStyleSheet(btn_style)
hl.addWidget(b)
hl.addWidget(self.tab_dropdown)
# Tabbed WebView
self.tabs = QTabWidget()
self.tabs.setTabsClosable(False)
self.add_tab(self.url)
self.tabs.setStyleSheet("""
QTabWidget::pane { background-color: #ffe6e6; }
QTabBar::tab {
background: #f8d7da;
padding: 6px;
border-radius: 4px;
}
QTabBar::tab:selected {
background: #e6e6fa;
font-weight: bold;
}
""")
# Connections
self.btn_prev.clicked.connect(lambda: self.notify("Going to previous page...", self.go_prev))
self.btn_refresh.clicked.connect(lambda: self.notify("Refreshing...", self.go_refresh))
self.btn_next.clicked.connect(lambda: self.notify("Going to next page...", self.go_next))
self.btn_full.clicked.connect(self.toggle_fullscreen)
self.btn_dup.clicked.connect(self.duplicate_tab)
self.btn_del.clicked.connect(self.delete_tab)
self.tab_dropdown.currentIndexChanged.connect(self.switch_tab)
# Layout
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(header)
layout.addWidget(self.tabs)
def add_tab(self, url):
webview = QWebEngineView()
webview.page().setWebChannel(self.channel)
index = self.tabs.addTab(webview, "Loading…")
self.tab_dropdown.addItem("Loading…")
self.tabs.setCurrentIndex(index)
self.tab_dropdown.setCurrentIndex(index)
webview.page().titleChanged.connect(lambda title, w=webview: self.update_tab_title(w, title))
webview.page().loadFinished.connect(lambda ok, w=webview: self.inject_js(w) if ok else None)
webview.load(QUrl(url))
def update_tab_title(self, webview, title):
i = self.tabs.indexOf(webview)
if i != -1:
self.tabs.setTabText(i, title or "New Tab")
self.tab_dropdown.setItemText(i, title or "New Tab")
def notify(self, message, callback):
dlg = QDialog(self)
dlg.setWindowFlags(Qt.WindowType.FramelessWindowHint)
dlg.setStyleSheet("""
QDialog { background-color: #34495E; border: 2px solid #2C3E50; border-radius: 10px; }
QLabel { color: white; padding: 15px; font-size: 14px; }
""")
lbl = QLabel(message)
lbl.setAlignment(Qt.AlignmentFlag.AlignCenter)
v = QVBoxLayout(dlg)
v.addWidget(lbl)
dlg.resize(250, 80)
dlg.move(self.geometry().center().x() - 125, self.geometry().center().y() - 40)
dlg.show()
QTimer.singleShot(1000, lambda: (dlg.close(), callback()))
def current_webview(self):
return self.tabs.currentWidget()
def go_prev(self):
view = self.current_webview()
if view and view.history().canGoBack():
view.back()
def go_refresh(self):
view = self.current_webview()
if view: view.reload()
def go_next(self):
view = self.current_webview()
if view and view.history().canGoForward():
view.forward()
def toggle_fullscreen(self):
self.setWindowState(self.windowState() ^ Qt.WindowState.WindowFullScreen)
def duplicate_tab(self):
view = self.current_webview()
if view:
self.add_tab(view.url().toString())
def delete_tab(self):
if self.tabs.count() > 1:
index = self.tabs.currentIndex()
self.tabs.removeTab(index)
self.tab_dropdown.removeItem(index)
def switch_tab(self, index):
self.tabs.setCurrentIndex(index)
def go_back(self):
for i in range(self.stacked.count()):
widget = self.stacked.widget(i)
if isinstance(widget, SelectorScreen):
self.stacked.setCurrentWidget(widget)
return
def inject_js(self, webview):
js_code = """
(function () {
if (window._fileDownloadInjected) return;
window._fileDownloadInjected = true;
let _downloadInProgress = false;
function getFileUrlAndType(el) {
let href = el.getAttribute("href") || el.getAttribute("data-url") || "";
if (!href) {
const inner = el.querySelector("a[href]");
if (inner) href = inner.getAttribute("href") || "";
}
let filename = "download.csv";
if (href) {
const parts = href.split('/');
filename = parts[parts.length - 1].split('?')[0] || filename;
}
const isExport = href.includes("/exports") || el.textContent.toLowerCase().includes("export");
const source = isExport ? "export" : "import";
return { href, filename, source };
}
function startDownload(e, href, filename, source) {
if (_downloadInProgress) return;
_downloadInProgress = true;
e.preventDefault();
fetch(href, { credentials: 'include' })
.then(res => {
if (!res.ok) throw new Error("Failed: " + res.status);
return res.blob();
})
.then(blob => {
const reader = new FileReader();
reader.onloadend = () => {
const base64 = reader.result;
if (window.pyBridge && window.pyBridge.saveFile) {
window.pyBridge.saveFile(base64, filename, source);
} else {
alert("❌ pyBridge not available");
}
_downloadInProgress = false;
};
reader.readAsDataURL(blob);
})
.catch(err => {
alert("❌ Download error: " + err.message);
_downloadInProgress = false;
});
}
document.addEventListener("click", function (e) {
const el = e.target.closest("a,button");
if (!el) return;
const { href, filename, source } = getFileUrlAndType(el);
if (href && (href.endsWith(".csv") || href.endsWith(".xlsx") || href.includes("/exports") || href.startsWith("blob:"))) {
startDownload(e, href, filename, source);
}
}, true);
const origCreateObjectURL = URL.createObjectURL;
URL.createObjectURL = function (blob) {
if (_downloadInProgress) return origCreateObjectURL(blob);
_downloadInProgress = true;
try {
const reader = new FileReader();
reader.onloadend = function () {
const base64 = reader.result;
if (window.pyBridge && window.pyBridge.saveFile) {
window.pyBridge.saveFile(base64, "blob_file.csv", "import");
}
_downloadInProgress = false;
};
reader.readAsDataURL(blob);
} catch (e) {
console.error("Blob hook failed", e);
_downloadInProgress = false;
}
return origCreateObjectURL(blob);
};
function waitForBridge() {
if (typeof QWebChannel === 'undefined') return setTimeout(waitForBridge, 100);
new QWebChannel(qt.webChannelTransport, function (channel) {
window.pyBridge = channel.objects.pyBridge;
});
}
waitForBridge();
})();
"""
webview.page().runJavaScript(f"""
var script = document.createElement('script');
script.src = 'qrc:///qtwebchannel/qwebchannel.js';
script.onload = function() {{
{js_code}
}};
document.head.appendChild(script);
""")
class BotScreen(QWidget):
def __init__(self, stacked, module_name=""):
super().__init__()
self.stacked = stacked
self.module_name = module_name
self.charts = []
self.plants = []
self.lines = []
self.filters = []
self.awaiting_chart_choice = False
self.awaiting_plant_choice = False
self.awaiting_line_choice = False
self.awaiting_order_input = False
self.awaiting_filter_choice = False
self.selected_chart = None
self.selected_plant = None
self.selected_line = None
self.selected_order = None
self.selected_filter = None
self.init_ui()
if self.module_name:
self.load_chart_data()
def set_module_name(self, name):
self.module_name = name
self.clear_chat()
self.load_chart_data()
def init_ui(self):
main_layout = QVBoxLayout()
top_bar = QHBoxLayout()
logo = QLabel()
logo.setPixmap(QPixmap(
r'C:\Users\Administrator\Downloads\sfg printing\backup from airline device\SRIMATHI\cri_logo.png.png'
).scaled(40, 40, Qt.AspectRatioMode.KeepAspectRatio))
top_bar.addWidget(logo)
title = QLabel("CRI DIGITAL MANUFACTURING SOLUTIONS \n CRI BOT ASSISTANT")
title.setStyleSheet("font-size: 16px; font-weight: bold; color: black; padding: 10px; border-radius: 8px;")
top_bar.addWidget(title)
top_bar.addStretch()
back_btn = QPushButton("Back")
back_btn.clicked.connect(self.go_back)
top_bar.addWidget(back_btn)
refresh_btn = QPushButton("Refresh")
refresh_btn.clicked.connect(self.load_chart_data)
top_bar.addWidget(refresh_btn)
main_layout.addLayout(top_bar)
self.chart_info = QLabel("Waiting for chart data...")
self.chart_info.setWordWrap(True)
self.chart_info.setStyleSheet(
"background: white; color: black; font-size: 15px; padding: 14px; border-radius: 10px")
main_layout.addWidget(self.chart_info)
self.chat_area = QTextEdit()
self.chat_area.setReadOnly(True)
self.chat_area.setStyleSheet("background-color: white; color: black; padding: 10px; font-size: 14px;")
main_layout.addWidget(self.chat_area)
input_layout = QHBoxLayout()
self.user_input = QLineEdit()
self.user_input.setPlaceholderText("Type a number to select...")
input_layout.addWidget(self.user_input)
send_btn = QPushButton("Send")
send_btn.setStyleSheet(
"padding: 8px; background: #2e7d32; color: white; border-radius: 8px;")
send_btn.clicked.connect(self.send_message)
input_layout.addWidget(send_btn)
main_layout.addLayout(input_layout)
self.setLayout(main_layout)
def load_chart_data(self):
self.chart_info.setText("Loading chart data...")
url = 'https://pds.iotsignin.com/api/get/modulechart-name/data'
headers = {
"Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=",
"module-name": self.module_name
}
try:
response = requests.get(url, headers=headers, timeout=10)
data = response.json()
status = data.get("status_code", "UNKNOWN")
desc = data.get("status_description", [])
self.chart_info.setText(f"API Status: {status}")
self.chat_area.append(f"<b>HTTP:</b> {response.status_code}")
self.chat_area.append(f"<b>Bot Module:</b> {self.module_name}")
if isinstance(desc, list) and desc:
self.charts = desc
else:
self.charts = [
"Production Line Count",
"Production Hourly Count",
"Production Line Stop Count",
"Production Order Count"
]
self.awaiting_chart_choice = True
self.show_choice_menu("chart", self.charts)
except Exception as e:
self.chart_info.setText("Failed to load chart data.")
self.chat_area.append(f"<b>Bot:</b> Error: {e}")
def show_choice_menu(self, item, items_list):
label_map = {
"chart": "chart",
"plant": "plant",
"line": "line",
"order": "production order",
"filter": "date filter"
}
lines = [f"{i + 1}. {name}" for i, name in enumerate(items_list)]
menu_txt = f"<b>Bot:</b> Please select a {label_map[item]}:<br>" + "<br>".join(lines)
self.chat_area.append(menu_txt)
def send_message(self):
msg = self.user_input.text().strip()
self.user_input.clear()
if self.awaiting_chart_choice:
if msg.isdigit():
idx = int(msg) - 1
if 0 <= idx < len(self.charts):
self.selected_chart = self.charts[idx]
self.awaiting_chart_choice = False
self.chat_area.append(f"<b>Bot:</b> ✅ You selected: <b>{self.selected_chart}</b>")
self.fetch_and_display_plants()
else:
self.chat_area.append(f"<b>Bot:</b> ❌ Please select a number from 1 to {len(self.charts)}.")
else:
self.chat_area.append(f"<b>Bot:</b> ❌ Please enter a number (1-{len(self.charts)}).")
return
if self.awaiting_plant_choice:
if msg.isdigit():
idx = int(msg) - 1
if 0 <= idx < len(self.plants):
self.selected_plant = self.plants[idx]
self.awaiting_plant_choice = False
self.chat_area.append(f"<b>Bot:</b> ✅ You selected: <b>{self.selected_plant}</b>")
self.fetch_and_display_lines()
else:
self.chat_area.append(f"<b>Bot:</b> ❌ Please select a number from 1 to {len(self.plants)}.")
else:
self.chat_area.append(f"<b>Bot:</b> ❌ Please enter a number (1-{len(self.plants)}).")
return
if self.awaiting_line_choice:
if msg.isdigit():
idx = int(msg) - 1
if 0 <= idx < len(self.lines):
self.selected_line = self.lines[idx]
self.awaiting_line_choice = False
self.chat_area.append(f"<b>Bot:</b> ✅ You selected: <b>{self.selected_line}</b>")
chart_raw = self.selected_chart
chart_normalized = chart_raw.strip().lower() if chart_raw else ""
if chart_normalized == "production order count":
print(f"[DEBUG] self.selected_chart (raw): '{self.selected_chart}'")
self.awaiting_order_input = True
self.chat_area.append("<b>Bot:</b> Please enter the production order number:")
else:
self.fetch_and_display_filters()
else:
self.chat_area.append(f"<b>Bot:</b> ❌ Please select a number from 1 to {len(self.lines)}.")
else:
self.chat_area.append(f"<b>Bot:</b> ❌ Please enter a number (1-{len(self.lines)}).")
return
if hasattr(self, 'awaiting_order_input') and self.awaiting_order_input:
order = msg.strip()
if not order.isdigit():
self.chat_area.append("<b>Bot:</b> ❌ Please enter a valid production order number (numbers only).")
else:
self.selected_order = order
self.awaiting_order_input = False
self.chat_area.append(f"<b>Bot:</b> ✅ Production order set: <b>{self.selected_order}</b>")
self.fetch_and_display_filters()
return
if self.awaiting_filter_choice:
if msg.isdigit():
idx = int(msg) - 1
if 0 <= idx < len(self.filters):
self.selected_filter = self.filters[idx]
self.awaiting_filter_choice = False
self.chat_area.append(f"<b>Bot:</b> ✅ You selected filter: <b>{self.selected_filter}</b>")
now = QDateTime.currentDateTime()
fmt_date = now.toString("dddd, MMMM d, yyyy, hh:mm AP")
self.chat_area.append(f"<b>Current date:</b> {fmt_date}")
selected = self.selected_chart.strip().lower()
if selected == "production hourly count":
self.fetch_and_display_hourly_count()
elif selected == "production line stop count":
self.fetch_and_display_production_line_stop_count()
elif selected == "production order count":
self.fetch_and_display_production_order_count()
else:
self.fetch_and_display_production_count()
else:
self.chat_area.append(f"<b>Bot:</b> ❌ Please select a number from 1 to {len(self.filters)}.")
else:
self.chat_area.append(f"<b>Bot:</b> ❌ Please enter a valid number (1{len(self.filters)}).")
return
def fetch_and_display_plants(self):
url = 'https://pds.iotsignin.com/api/get/moduleplant-name/data'
headers = {
"Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=",
"plant-name": "Plant List"
}
try:
response = requests.get(url, headers=headers, timeout=10)
data = response.json()
if data.get("status_code") == "SUCCESS":
plants = data.get("status_description", [])
if plants:
self.plants = plants
self.awaiting_plant_choice = True
self.show_choice_menu("plant", plants)
else:
self.chat_area.append("<b>Bot:</b> ❌ No plants found.")
else:
self.chat_area.append(f"<b>Bot:</b> ❌ Error: {data.get('status_description')}")
except Exception as e:
self.chat_area.append(f"<b>Bot:</b> ❌ Failed to load plants: {e}")
def fetch_and_display_lines(self):
url = 'https://pds.iotsignin.com/api/get/module-plantline-name/data'
headers = {
"Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=",
"plant-name": self.selected_plant,
"chart-name": self.selected_chart
}
try:
response = requests.get(url, headers=headers, timeout=10)
data = response.json()
if data.get("status_code") == "SUCCESS":
lines = data.get("status_description", [])
if lines:
self.lines = lines
self.awaiting_line_choice = True
self.show_choice_menu("line", lines)
else:
self.chat_area.append("<b>Bot:</b> ❌ No lines found for the selected plant.")
else:
self.chat_area.append(f"<b>Bot:</b> ❌ Error: {data.get('status_description')}")
except Exception as e:
self.chat_area.append(f"<b>Bot:</b> ❌ Failed to load lines: {e}")
def fetch_and_display_filters(self):
url = 'https://pds.iotsignin.com/api/get/module-line-filter-name/data'
headers = {
"Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=",
"line-name": self.selected_line
}
try:
response = requests.get(url, headers=headers, timeout=10)
data = response.json()
if data.get("status_code") == "SUCCESS":
filters = data.get("status_description", [])
if filters:
self.filters = filters
self.awaiting_filter_choice = True
self.show_choice_menu("filter", filters)
else:
self.chat_area.append("<b>Bot:</b> ❌ No date filters found for the selected line.")
else:
self.chat_area.append(f"<b>Bot:</b> ❌ Error: {data.get('status_description')}")
except Exception as e:
self.chat_area.append(f"<b>Bot:</b> ❌ Failed to load date filters: {e}")
def fetch_and_display_production_count(self):
url = 'https://pds.iotsignin.com/api/get/module-filter-value/data'
headers = {
"Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=",
"module-name": self.module_name,
"chart-name": self.selected_chart,
"plant-name": self.selected_plant,
"line-name": self.selected_line,
"filter-name": self.selected_filter
}
try:
response = requests.get(url, headers=headers, timeout=10)
data = response.json()
if data.get("status_code") == "SUCCESS":
result = data.get("status_description")
if isinstance(result, dict):
self.chat_area.append(f"<b>Bot:</b> 📊 <b>Production Counts (All Lines):</b>")
for ln, count in result.items():
self.chat_area.append(f"{ln}: {count}")
else:
self.chat_area.append(f"<b>Bot:</b> 📈 <b>Production Count:</b> {result}")
else:
self.chat_area.append(
f"<b>Bot:</b> ❌ Error getting production count: {data.get('status_description', 'Unknown')}")
except Exception as e:
self.chat_area.append(f"<b>Bot:</b> ❌ Failed to load production count: {e}")
API_TOKEN = "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE="
def fetch_and_display_hourly_count(self):
url = "https://pds.iotsignin.com/api/get/module-filter-value/data"
headers = {
"Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=",
"Accept": "application/json",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
"module-name": "Production Dashboard",
"chart-name": "Production Hourly Count",
"plant-name": self.selected_plant,
"line-name": self.selected_line,
"filter-name": self.selected_filter
}
try:
response = requests.get(url, headers=headers, timeout=10)
print("📡 [DEBUG] HTTP Status:", response.status_code)
print("📡 [DEBUG] Raw API Response:\n", response.text)
print("-" * 50)
self.chat_area.append(f"<b>Raw API response:</b><br><pre>{response.text}</pre>")
if not response.text.strip():
print("[DEBUG] ❌ Empty response received.")
self.chat_area.append("<b>Bot:</b> ❌ API returned no data.")
return
try:
data = response.json()
except Exception as json_err:
print(f"[DEBUG] ❌ JSON Parse Error: {json_err}")
self.chat_area.append(f"<b>Bot:</b> ❌ Failed to parse data: {json_err}")
return
if data.get("status_code") == "SUCCESS":
now = QDateTime.currentDateTime().toString("dddd, MMMM d, yyyy, h:mm AP t")
self.chat_area.append(
f"<b>📊 Production Hourly Count ({self.selected_filter})</b><br><b>Current date:</b> {now}<br><br>")
for dataset in data.get("datasets", []):
label = dataset.get("label", "Data")
rows = ""
for lbl, val in zip(data.get("labels", []), dataset.get("data", [])):
rows += f"{lbl} ➤ <b>{val}</b><br>"
self.chat_area.append(f"<b>{label}:</b><br>{rows}")
print("[DEBUG] ✅ Hourly count displayed successfully.")
else:
req_status = data.get("status_code")
print(f"[DEBUG] ❌ API returned status {req_status}")
self.chat_area.append(f"<b>Bot:</b> ❌ API returned status <code>{req_status}</code>")
except requests.exceptions.HTTPError as errh:
print("[DEBUG] ❌ HTTP Error:", errh)
self.chat_area.append(f"<b>Bot:</b> ❌ HTTP Error: {errh}")
except requests.exceptions.ConnectionError:
print("[DEBUG] ❌ Connection Error.")
self.chat_area.append("<b>Bot:</b> ❌ Connection error.")
except requests.exceptions.Timeout:
print("[DEBUG] ❌ Timeout Error.")
self.chat_area.append("<b>Bot:</b> ❌ Request timed out.")
except Exception as e:
print(f"[DEBUG] ❌ General Error: {e}")
self.chat_area.append(f"<b>Bot:</b> ❌ Unexpected Error: {e}")
def fetch_and_display_production_line_stop_count(self):
url = "https://pds.iotsignin.com/api/get/module-production-linestop/data"
headers = {
"Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=",
"module-name": "Production Dashboard",
"chart-name": "Production Line Stop",
"plant-name": self.selected_plant,
"line-name": self.selected_line,
"filter-name": self.selected_filter
}
try:
response = requests.get(url, headers=headers, timeout=10)
self.chat_area.append(f"<b>Raw API response:</b><br><pre>{response.text}</pre>")
if response.status_code != 200:
self.chat_area.append(f"<b>Bot:</b> ❌ HTTP error {response.status_code}")
return
if not response.text.strip():
self.chat_area.append(f"<b>Bot:</b> ❌ No response from API.")
return
try:
data = response.json()
except Exception as e:
self.chat_area.append(f"<b>Bot:</b> ❌ Failed to parse API response: {e}")
return
current_date = QDateTime.currentDateTime().toString("dddd, MMMM d, yyyy, h:mm AP t")
if data.get("status_code") == "SUCCESS":
labels = data.get("labels", [])
values = data.get("data", [])
message = f"<b>Production Line Stop Counts</b><br><b>Current date:</b> {current_date}<br><br>"
for label, value in zip(labels, values):
message += f"<b>{label}:</b> {value}<br>"
self.chat_area.append(message)
else:
self.chat_area.append(f"<b>Bot:</b> ❌ No data found for this selection.")
except Exception as e:
self.chat_area.append(f"<b>Bot:</b> ❌ Error fetching report: {e}")
def fetch_and_display_production_order_count(self):
url = "https://pds.iotsignin.com/api/get/module-production-order/data"
headers = {
"Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=",
"module-name": "Production Dashboard",
"chart-name": "Production Order Count",
"plant-name": self.selected_plant,
"line-name": self.selected_line,
"production-order": self.selected_order,
"filter-name": self.selected_filter
}
try:
response = requests.get(url, headers=headers, timeout=10)
print("📡 [DEBUG] HTTP Status:", response.status_code)
print("📡 [DEBUG] Raw API Response:\n", response.text)
print("-" * 50)
self.chat_area.append(f"<b>Raw API response:</b><br><pre>{response.text}</pre>")
if not response.text.strip():
print("[DEBUG] ❌ Empty API response received.")
self.chat_area.append("<b>Bot:</b> ❌ API returned no data.")
return
try:
data = response.json()
except Exception as json_err:
print(f"[DEBUG] ❌ JSON Parse Error: {json_err}")
self.chat_area.append(f"<b>Bot:</b> ❌ Failed to parse data: {json_err}")
return
status = data.get("status_code")
if status == "SUCCESS":
now = QDateTime.currentDateTime().toString("dddd, MMMM d, yyyy, h:mm AP t")
self.chat_area.append(
f"<b>Production Order Count ({self.selected_filter})</b><br><b>Current date:</b> {now}<br><br>")
datasets = data.get("datasets", [])
for dataset in datasets:
label = dataset.get("label", "Data")
self.chat_area.append(f"<b>{label}:</b>")
if isinstance(dataset.get("data"), dict):
for timeslot, count in dataset.get("data", {}).items():
self.chat_area.append(f"{timeslot}: <b>{count}</b><br>")
elif isinstance(dataset.get("data"), list):
labels = data.get("labels", [])
for idx, value in enumerate(dataset.get("data")):
label_text = labels[idx] if idx < len(labels) else f"Data {idx + 1}"
self.chat_area.append(f"{label_text}: <b>{value}</b><br>")
else:
self.chat_area.append(f"Data: <b>{dataset.get('data')}</b>")
print("[DEBUG] ✅ Production order count handled/displayed.")
else:
print(f"[DEBUG] ❌ API status_code not SUCCESS: {status}")
self.chat_area.append(f"<b>Bot:</b> ❌ API returned status <code>{status}</code>")
except requests.exceptions.HTTPError as errh:
print("[DEBUG] ❌ HTTP Error:", errh)
self.chat_area.append(f"<b>Bot:</b> ❌ HTTP Error: {errh}")
except requests.exceptions.ConnectionError:
print("[DEBUG] ❌ Connection Error.")
self.chat_area.append("<b>Bot:</b> ❌ Connection error.")
except requests.exceptions.Timeout:
print("[DEBUG] ❌ Timeout Error.")
self.chat_area.append("<b>Bot:</b> ❌ Request timed out.")
except Exception as e:
print(f"[DEBUG] ❌ General Error: {e}")
self.chat_area.append(f"<b>Bot:</b> ❌ Unexpected Error: {e}")
def clear_chat(self):
self.chat_area.clear()
def go_back(self):
for i in range(self.stacked.count()):
widget = self.stacked.widget(i)
if isinstance(widget, SelectorScreen):
self.stacked.setCurrentWidget(widget)
return
class SelectorScreen(QWidget):
def __init__(self, stacked, username=None):
super().__init__()
self.stacked = stacked
self.username = username or "User"
self.init_ui()
def init_ui(self):
# Set light blue background
palette = self.palette()
palette.setColor(QPalette.ColorRole.Window, QColor("#ADD8E6"))
self.setPalette(palette)
self.setAutoFillBackground(True)
# Set Times New Roman font for entire screen
self.setFont(QFont("Times New Roman"))
self.layout = QVBoxLayout()
self.layout.setContentsMargins(40, 30, 40, 30)
self.layout.setSpacing(15)
self.layout.setAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignHCenter)
# CRI red logo at top
self.cri_logo = QLabel()
cri_logo_path = r'C:\Users\Administrator\Downloads\sfg printing\backup from airline device\SRIMATHI\cri_red_logo.png'
self.cri_logo.setPixmap(QPixmap(cri_logo_path).scaledToWidth(150, Qt.TransformationMode.SmoothTransformation))
self.cri_logo.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.layout.addWidget(self.cri_logo)
# Title
title_label = QLabel("SELECTOR SCREEN")
title_label.setFont(QFont("Times New Roman", 18, QFont.Weight.Bold))
title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.layout.addWidget(title_label)
# Welcome username label
welcome_label = QLabel(f"Welcome, {self.username}!")
welcome_label.setFont(QFont("Times New Roman", 14))
welcome_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.layout.addWidget(welcome_label)
# Instruction label
instruction_label = QLabel("Choose menu to use")
instruction_label.setFont(QFont("Times New Roman", 12))
instruction_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.layout.addWidget(instruction_label)
# Production Display Section
prod_display_title = QLabel("PRODUCTION DISPLAY WEBSITE")
prod_display_title.setFont(QFont("Times New Roman", 14, QFont.Weight.Bold))
prod_display_title.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.layout.addWidget(prod_display_title)
self.production_display_btn = QPushButton("Production Display Website")
self.production_display_btn.setFont(QFont("Times New Roman", 12))
self.production_display_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
self.production_display_btn.setStyleSheet(
"padding: 10px; background-color: #c2185b; color: white; border-radius: 8px;")
self.production_display_btn.clicked.connect(self.open_production_display)
self.layout.addWidget(self.production_display_btn)
# Dashboards Section
dashboards_title = QLabel("DASHBOARDS")
dashboards_title.setFont(QFont("Times New Roman", 14, QFont.Weight.Bold))
dashboards_title.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.layout.addWidget(dashboards_title)
self.production_dashboard_btn = QPushButton("Production Dashboard")
self.production_dashboard_btn.setFont(QFont("Times New Roman", 12))
self.production_dashboard_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
self.production_dashboard_btn.setStyleSheet(
"padding: 10px; background-color: #c2185b; color: white; border-radius: 8px;")
self.production_dashboard_btn.clicked.connect(lambda: self.open_dashboard("Production Dashboard"))
self.layout.addWidget(self.production_dashboard_btn)
self.invoice_dashboard_btn = QPushButton("Invoice Dashboard")
self.invoice_dashboard_btn.setFont(QFont("Times New Roman", 12))
self.invoice_dashboard_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
self.invoice_dashboard_btn.setStyleSheet(
"padding: 10px; background-color: #c2185b; color: white; border-radius: 8px;")
self.invoice_dashboard_btn.clicked.connect(lambda: self.open_dashboard("Invoice Dashboard"))
self.layout.addWidget(self.invoice_dashboard_btn)
# Spacer to push the logo down
self.layout.addSpacerItem(QSpacerItem(20, 20, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding))
# CRI banner logo at bottom
self.cri_banner_logo = QLabel()
banner_logo_path = r'C:\Users\Administrator\Downloads\sfg printing\backup from airline device\SRIMATHI\cri_farm_banner.jpg'
self.cri_banner_logo.setPixmap(
QPixmap(banner_logo_path).scaledToWidth(350, Qt.TransformationMode.SmoothTransformation))
self.cri_banner_logo.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.layout.addWidget(self.cri_banner_logo)
self.setLayout(self.layout)
def open_production_display(self):
# Check if screen already exists
for i in range(self.stacked.count()):
widget = self.stacked.widget(i)
if isinstance(widget, ProductionDisplayScreen):
self.stacked.setCurrentWidget(widget)
return
# Otherwise, add new screen
display_screen = ProductionDisplayScreen(self.stacked)
self.stacked.addWidget(display_screen)
self.stacked.setCurrentWidget(display_screen)
def open_dashboard(self, dashboard_type):
# Check if screen already exists
for i in range(self.stacked.count()):
widget = self.stacked.widget(i)
if isinstance(widget, BotScreen) and widget.module_name == dashboard_type:
self.stacked.setCurrentWidget(widget)
return
# Otherwise, create new screen
bot_screen = BotScreen(self.stacked, dashboard_type)
self.stacked.addWidget(bot_screen)
self.stacked.setCurrentWidget(bot_screen)
def show_error(self, msg):
error = QLabel(f"{msg}")
error.setStyleSheet("color: red;")
self.layout.addWidget(error)
class LoginScreen(QWidget):
def __init__(self, stacked):
super().__init__()
self.stacked = stacked
self.init_ui()
def init_ui(self):
self.showMaximized()
# Light blue background
palette = self.palette()
palette.setColor(QPalette.ColorRole.Window, QColor("#ADD8E6"))
self.setPalette(palette)
self.setAutoFillBackground(True)
layout = QVBoxLayout()
layout.setContentsMargins(40, 30, 40, 30)
layout.setSpacing(20)
layout.setAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignHCenter)
# Logo
logo = QLabel()
logo.setPixmap(QPixmap(
r'C:\Users\Administrator\Downloads\sfg printing\backup from airline device\SRIMATHI\cri_red_logo.png'
).scaled(150, 150, Qt.AspectRatioMode.KeepAspectRatio))
logo.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(logo)
# Title
title_label = QLabel("CRI DIGITAL MANUFACTURING \n SOLUTIONS")
title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
title_label.setStyleSheet("""
font-size: 18px;
font-weight: bold;
color: #002244;
margin-bottom: 10px;
""")
layout.addWidget(title_label)
# Username QLineEdit
self.username = QLineEdit()
self.username.setPlaceholderText("Username (max 25 chars)")
self.username.setMaxLength(25)
self.username.setStyleSheet(self.input_style())
layout.addWidget(self.username)
# Password QLineEdit
self.password = QLineEdit()
self.password.setPlaceholderText("Password (max 25 chars)")
self.password.setMaxLength(25)
self.password.setEchoMode(QLineEdit.EchoMode.Password)
self.password.setStyleSheet(self.input_style())
# Create eye toggle button with emoji inside password field
self.eye_button = QToolButton(self.password)
self.eye_button.setText("🙈") # Initially hidden password
self.eye_button.setCursor(Qt.CursorShape.PointingHandCursor)
self.eye_button.setStyleSheet("border: none; background: transparent; font-size: 18px;")
self.eye_button.setToolTip("Show/Hide password")
self.eye_button.clicked.connect(self.toggle_password_visibility)
# Adjust size and margins in QLineEdit for the eye button
frame_width = self.password.style().pixelMetric(self.password.style().PixelMetric.PM_DefaultFrameWidth)
button_size = 24
self.eye_button.setFixedSize(button_size, button_size)
self.password.setTextMargins(0, 0, button_size + frame_width, 0)
# Position the eye button correctly
self.position_eye_button()
# Reposition on resize or text change
self.password.textChanged.connect(self.position_eye_button)
self.password.installEventFilter(self)
layout.addWidget(self.password)
# Connect Enter key navigation
self.username.returnPressed.connect(self.password.setFocus)
self.password.returnPressed.connect(self.do_login)
# Login button smaller style
login_btn = QPushButton("Login")
login_btn.setStyleSheet("""
QPushButton {
background-color: #E91E63;
color: white;
font-weight: bold;
padding: 6px 15px;
border-radius: 12px;
font-size: 14px;
max-width: 120px;
}
QPushButton:hover {
background-color: #C2185B;
}
QPushButton:pressed {
background-color: #AD1457;
}
""")
login_btn.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
login_btn.clicked.connect(self.do_login)
layout.addWidget(login_btn, alignment=Qt.AlignmentFlag.AlignCenter)
# Secondary image/banner
logo2 = QLabel()
logo2.setPixmap(QPixmap(
r'C:\Users\Administrator\Downloads\sfg printing\backup from airline device\SRIMATHI\cri_farm_banner.jpg'
).scaled(350, 350, Qt.AspectRatioMode.KeepAspectRatio))
logo2.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(logo2)
self.setLayout(layout)
def position_eye_button(self):
frame_width = self.password.style().pixelMetric(self.password.style().PixelMetric.PM_DefaultFrameWidth)
button_size = self.eye_button.width()
self.eye_button.move(
self.password.rect().right() - button_size - frame_width,
(self.password.rect().bottom() - button_size + 1) // 2
)
def eventFilter(self, obj, event):
if obj == self.password and event.type() == QEvent.Type.Resize:
self.position_eye_button()
return super().eventFilter(obj, event)
def toggle_password_visibility(self):
if self.password.echoMode() == QLineEdit.EchoMode.Password:
self.password.setEchoMode(QLineEdit.EchoMode.Normal)
self.eye_button.setText("👁️")
else:
self.password.setEchoMode(QLineEdit.EchoMode.Password)
self.eye_button.setText("🙈")
def input_style(self):
return """
QLineEdit {
background-color: white;
border: 2px solid #ccc;
border-radius: 15px;
padding: 8px 12px;
color: black;
font-size: 14px;
min-width: 300px;
}
QLineEdit:focus {
border: 2px solid purple;
}
"""
def do_login(self):
username = self.username.text().strip()
password = self.password.text().strip()
if not username or not password:
QMessageBox.critical(self, "Input Error", "Please enter both username and password.")
return
url = "https://pds.iotsignin.com/api/testing/user/get-data"
headers = {
"Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=",
"User-Name": username,
"User-Pass": password
}
try:
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
data = response.json()
if "roles" in data:
roles = data["roles"]
roles_str = ', '.join(roles)
info_msg = f"Login successful!"
result = QMessageBox.information(self, "Success", info_msg, QMessageBox.StandardButton.Ok)
if result == QMessageBox.StandardButton.Ok:
selector_screen = SelectorScreen(self.stacked, username)
self.stacked.addWidget(selector_screen)
self.stacked.setCurrentWidget(selector_screen)
elif data.get("status_code") == "ERROR":
description = data.get("status_description", "Unknown error from server.")
QMessageBox.critical(self, "Login Error", description)
else:
QMessageBox.critical(self, "Login Error", "Unexpected response from server.")
except requests.exceptions.RequestException as e:
QMessageBox.critical(self, "Network Error", f"Failed to connect to server:\n{str(e)}")
def main():
app = QApplication(sys.argv)
stacked = QStackedWidget()
login_screen = LoginScreen(stacked)
stacked.addWidget(login_screen)
stacked.setFixedSize(1200, 800)
stacked.show()
sys.exit(app.exec())
if __name__ == '__main__':
main()