diff --git a/app/src/app.tsx b/app/src/app.tsx
index f5e19b0..0b6fd3e 100644
--- a/app/src/app.tsx
+++ b/app/src/app.tsx
@@ -3,23 +3,20 @@ import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Dashboard from "./pages/Dashboard";
import Project from "./pages/Project";
import UserAuth from "./pages/UserAuth";
-import ToastProvider from "./components/templates/ToastProvider";
-import "./styles/main.scss"
+import "./styles/main.scss";
+import { LoggerProvider } from "./components/ui/log/LoggerContext";
const App: React.FC = () => {
return (
-
+
- }
- />
+ } />
} />
} />
-
+
);
};
diff --git a/app/src/components/icons/ExportCommonIcons.tsx b/app/src/components/icons/ExportCommonIcons.tsx
index 6925853..4137498 100644
--- a/app/src/components/icons/ExportCommonIcons.tsx
+++ b/app/src/components/icons/ExportCommonIcons.tsx
@@ -814,3 +814,136 @@ export const SpeedIcon = () => {
);
};
+
+export const LogListIcon = () => {
+ return (
+
+ );
+};
+
+export const LogTickIcon = () => {
+ return (
+
+ );
+};
+
+export const LogInfoIcon = () => {
+ return (
+
+ );
+};
+
+export const WarningIcon = () => {
+ return (
+
+ );
+};
+
+export const ErrorIcon = () => {
+ return (
+
+ );
+};
diff --git a/app/src/components/ui/footer/Footer.tsx b/app/src/components/ui/footer/Footer.tsx
new file mode 100644
index 0000000..9f73284
--- /dev/null
+++ b/app/src/components/ui/footer/Footer.tsx
@@ -0,0 +1,67 @@
+import React from "react";
+import { HelpIcon } from "../../icons/DashboardIcon";
+import { useLogger } from "../log/LoggerContext";
+import {
+ LogInfoIcon,
+ ErrorIcon,
+ WarningIcon,
+} from "../../icons/ExportCommonIcons"; // Adjust path as needed
+
+const getLogIcon = (type: string) => {
+ switch (type) {
+ case "info":
+ return ;
+ case "error":
+ return ;
+ case "warning":
+ return ;
+ case "log":
+ default:
+ return ;
+ }
+};
+
+const Footer = () => {
+ const { logs, setIsLogListVisible } = useLogger();
+ const lastLog = logs.length > 0 ? logs[logs.length - 1] : null;
+
+ return (
+
+
+
+
+
setIsLogListVisible(true)}>
+ {lastLog ? (
+ <>
+ {getLogIcon(lastLog.type)}
+ {lastLog.message}
+ >
+ ) : (
+ "No logs yet."
+ )}
+
+
+
+
+ );
+};
+
+export default Footer;
diff --git a/app/src/components/ui/log/LogList.tsx b/app/src/components/ui/log/LogList.tsx
new file mode 100644
index 0000000..1b39e2f
--- /dev/null
+++ b/app/src/components/ui/log/LogList.tsx
@@ -0,0 +1,88 @@
+// LogList.tsx
+import React, { useState } from "react";
+import {
+ LogListIcon,
+ TickIcon,
+ LogInfoIcon,
+ WarningIcon,
+ ErrorIcon,
+ CloseIcon,
+} from "../../icons/ExportCommonIcons"; // Adjust path as needed
+import { useLogger } from "./LoggerContext";
+
+const LogList: React.FC = () => {
+ const { logs, clear, setIsLogListVisible } = useLogger();
+ const [selectedTab, setSelectedTab] = useState<
+ "all" | "info" | "warning" | "error" | "log"
+ >("all");
+
+ const getLogIcon = (type: string) => {
+ switch (type) {
+ case "info":
+ return ;
+ case "error":
+ return ;
+ case "warning":
+ return ;
+ case "log":
+ default:
+ return ;
+ }
+ };
+
+ const formatTimestamp = (date: Date) => new Date(date).toLocaleTimeString();
+
+ const filteredLogs =
+ selectedTab === "all"
+ ? [...logs].reverse()
+ : [...logs].filter((log) => log.type === selectedTab).reverse();
+
+ return (
+
+
+
+
+
setIsLogListVisible(false)}>
+ {/* */}X
+
+
+
+ {/* Tabs */}
+
+ {["all", "info", "warning", "error"].map((type) => (
+
setSelectedTab(type as any)}
+ >
+ {`${type.charAt(0).toUpperCase() + type.slice(1)} Logs`}
+
+ ))}
+
+
+ {/* Log Entries */}
+
+ {filteredLogs.map((log) => (
+
+
+
+
+
{getLogIcon(log.type)}
+
+ [{formatTimestamp(log.timestamp)}] [{log.type.toUpperCase()}]{" "}
+ {log.message}
+
+
+ ))}
+
+
+
+ );
+};
+
+export default LogList;
diff --git a/app/src/components/ui/log/LoggerContext.tsx b/app/src/components/ui/log/LoggerContext.tsx
new file mode 100644
index 0000000..f323b93
--- /dev/null
+++ b/app/src/components/ui/log/LoggerContext.tsx
@@ -0,0 +1,77 @@
+import React, { createContext, useContext, useState, useCallback } from "react";
+
+export type LogType = "log" | "info" | "warning" | "error";
+
+export interface LogEntry {
+ id: string;
+ type: LogType;
+ message: string;
+ timestamp: Date;
+}
+
+interface LoggerContextValue {
+ logs: LogEntry[];
+ setLogs: React.Dispatch>;
+ isLogListVisible: boolean;
+ setIsLogListVisible: React.Dispatch>;
+ log: (message: string) => void;
+ info: (message: string) => void;
+ warn: (message: string) => void;
+ error: (message: string) => void;
+ clear: () => void;
+}
+
+const LoggerContext = createContext(undefined);
+
+export const LoggerProvider: React.FC<{ children: React.ReactNode }> = ({
+ children,
+}) => {
+ const [logs, setLogs] = useState([]);
+ const [isLogListVisible, setIsLogListVisible] = useState(false);
+
+ const generateId = useCallback(
+ () => Math.random().toString(36).substring(2, 9),
+ []
+ );
+
+ const addLog = useCallback(
+ (type: LogType, message: string) => {
+ const newLog: LogEntry = {
+ id: generateId(),
+ type,
+ message,
+ timestamp: new Date(),
+ };
+ setLogs((prevLogs) => [...prevLogs, newLog]);
+ },
+ [generateId]
+ );
+
+ const loggerMethods: LoggerContextValue = {
+ logs,
+ setLogs,
+ isLogListVisible,
+ setIsLogListVisible,
+ log: (message: string) => addLog("log", message),
+ info: (message: string) => addLog("info", message),
+ warn: (message: string) => addLog("warning", message),
+ error: (message: string) => addLog("error", message),
+ clear: () => {
+ setLogs([]);
+ },
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useLogger = () => {
+ const context = useContext(LoggerContext);
+ if (!context) {
+ throw new Error("useLogger must be used within a LoggerProvider");
+ }
+ return context;
+};
diff --git a/app/src/components/ui/log/logger.ts b/app/src/components/ui/log/logger.ts
new file mode 100644
index 0000000..1781e26
--- /dev/null
+++ b/app/src/components/ui/log/logger.ts
@@ -0,0 +1,71 @@
+type LogType = 'log' | 'info' | 'warning' | 'error';
+
+interface LogEntry {
+ type: LogType;
+ message: string;
+ timestamp: Date;
+ context?: string;
+}
+
+class Logger {
+ private static instance: Logger;
+ private logs: LogEntry[] = [];
+ private subscribers: Array<(log: LogEntry) => void> = [];
+
+ private constructor() {}
+
+ public static getInstance(): Logger {
+ if (!Logger.instance) {
+ Logger.instance = new Logger();
+ }
+ return Logger.instance;
+ }
+
+ private notifySubscribers(log: LogEntry) {
+ this.subscribers.forEach(callback => callback(log));
+ }
+
+ private addLog(type: LogType, message: string, context?: string) {
+ const log: LogEntry = { type, message, timestamp: new Date(), context };
+ this.logs.push(log);
+ this.notifySubscribers(log);
+
+ if (process.env.NODE_ENV === 'development') {
+ const logMessage = context ? `[${context}] ${message}` : message;
+ console[type === 'warning' ? 'warn' : type](logMessage);
+ }
+ }
+
+ public log(message: string, context?: string) {
+ this.addLog('log', message, context);
+ }
+
+ public info(message: string, context?: string) {
+ this.addLog('info', message, context);
+ }
+
+ public warning(message: string, context?: string) {
+ this.addLog('warning', message, context);
+ }
+
+ public error(message: string, context?: string) {
+ this.addLog('error', message, context);
+ }
+
+ public getLogs(): LogEntry[] {
+ return [...this.logs];
+ }
+
+ public clear() {
+ this.logs = [];
+ }
+
+ public subscribe(callback: (log: LogEntry) => void) {
+ this.subscribers.push(callback);
+ return () => {
+ this.subscribers = this.subscribers.filter(sub => sub !== callback);
+ };
+ }
+}
+
+export const logger = Logger.getInstance();
diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx
index 9bb7d0b..83e4497 100644
--- a/app/src/pages/Project.tsx
+++ b/app/src/pages/Project.tsx
@@ -27,9 +27,15 @@ import Scene from "../modules/scene/scene";
import { createHandleDrop } from "../modules/visualization/functions/handleUiDrop";
import { useSelectedZoneStore } from "../store/visualization/useZoneStore";
import { useFloatingWidget } from "../store/visualization/useDroppedObjectsStore";
+import { useLogger } from "../components/ui/log/LoggerContext";
+import Footer from "../components/ui/footer/Footer";
+import RenderOverlay from "../components/templates/Overlay";
+import LogList from "../components/ui/log/LogList";
const Project: React.FC = () => {
let navigate = useNavigate();
+ const echo = useLogger();
+
const { activeModule, setActiveModule } = useModuleStore();
const { loadingProgress } = useLoadingProgress();
const { setUserName } = useUserName();
@@ -52,6 +58,7 @@ const Project: React.FC = () => {
setOrganization(Organization);
setUserName(name);
}
+ echo.info("Log in success full");
} else {
navigate("/");
}
@@ -66,6 +73,7 @@ const Project: React.FC = () => {
// collaboration store
const { selectedUser } = useSelectedUserStore();
+ const { isLogListVisible } = useLogger();
// real-time visualization store
const { widgetSubOption } = useWidgetSubOption();
@@ -117,6 +125,12 @@ const Project: React.FC = () => {
{selectedUser && }
+ {isLogListVisible && (
+
+
+
+ )}
+
);
};
diff --git a/app/src/styles/components/footer/footer.scss b/app/src/styles/components/footer/footer.scss
new file mode 100644
index 0000000..0fed13b
--- /dev/null
+++ b/app/src/styles/components/footer/footer.scss
@@ -0,0 +1,57 @@
+.footer-wrapper {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ z-index: 1;
+ display: flex;
+ justify-content: space-between;
+ padding: 12px 24px;
+
+ .selection-wrapper {
+ display: flex;
+ gap: 6px;
+
+ .selector-wrapper {
+ display: flex;
+ gap: 6px;
+ align-items: center;
+ background: var(--background-color);
+ padding: 3px 6px;
+ border-radius: 12px;
+ color: var(--text-color);
+
+ .selector {
+ color: var(--text-color);
+ }
+ }
+ }
+
+ .logs-wrapper {
+ display: flex;
+ gap: 6px;
+
+ .logs-detail,
+ .version {
+
+ border-radius: 12px;
+ background: var(--background-color);
+ padding: 3px 6px;
+ color: var(--text-color);
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ }
+
+ .logs-detail {
+ // background-color: #fff;
+ cursor: pointer;
+ }
+
+ .version {
+ display: flex;
+ gap: 6px;
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/styles/components/logs/logs.scss b/app/src/styles/components/logs/logs.scss
new file mode 100644
index 0000000..ba0f43d
--- /dev/null
+++ b/app/src/styles/components/logs/logs.scss
@@ -0,0 +1,77 @@
+.log-list-container {
+ width: 100vw;
+ height: 100vh;
+ // background: var(--background-color-secondary);
+ // backdrop-filter: blur(2px);
+
+ .log-list-wrapper {
+ height: 50%;
+ min-width: 50%;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ z-index: 5;
+ background: var(--background-color);
+ padding: 14px 12px;
+ border-radius: 15px;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ backdrop-filter: blur(50px);
+
+ .log-header {
+ display: flex;
+ justify-content: space-between;
+
+ .log-header-wrapper {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ }
+
+ .close {
+ // transform: scale(1.5);
+ cursor: pointer;
+ }
+ }
+
+ .log-nav-wrapper {
+ display: flex;
+ gap: 6px;
+
+ .log-nav {
+ padding: 8px 16px;
+ border-radius: 19px;
+ }
+
+ .log-nav.active {
+ background-color: var(--accent-color);
+ color: var(--text-button-color);
+ }
+ }
+
+ .log-entry-wrapper {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ background: var(--background-color);
+ padding: 18px 10px;
+ border-radius: 16px;
+
+ .log-entry {
+ padding: 4px;
+ border-radius: 4px;
+ font-size: var(--font-size-small);
+ display: flex;
+ align-items: center;
+ gap: 6px;
+
+ &:nth-child(odd) {
+ background: var(--background-color);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/styles/components/simulation/simulation.scss b/app/src/styles/components/simulation/simulation.scss
index f40ead1..88f85dc 100644
--- a/app/src/styles/components/simulation/simulation.scss
+++ b/app/src/styles/components/simulation/simulation.scss
@@ -314,6 +314,22 @@
font-size: var(--font-size-tiny);
}
+ .timmer {
+ width: auto;
+ position: absolute;
+ bottom: 0;
+ font-size: var(--font-size-tiny);
+ }
+
+ .start-displayer {
+ left: 8px;
+ }
+
+ .end-displayer {
+ width: auto;
+ right: 8px;
+ }
+
.start-displayer {
bottom: 4px;
left: 16px;
@@ -354,6 +370,12 @@
}
.simulation-player-container.open {
+
+ .start-displayer,
+ .end-displayer {
+ display: none;
+ }
+
.timmer {
display: none;
}
diff --git a/app/src/styles/layout/sidebar.scss b/app/src/styles/layout/sidebar.scss
index f23d08e..f8c022f 100644
--- a/app/src/styles/layout/sidebar.scss
+++ b/app/src/styles/layout/sidebar.scss
@@ -416,7 +416,7 @@
outline: none;
path {
stroke: var(--text-button-color);
- stroke-width: 1.3;
+ strokeWidth: 1.3;
}
}
}
@@ -682,7 +682,7 @@
path {
stroke: var(--accent-color);
- stroke-width: 1.5px;
+ strokeWidth: 1.5px;
}
&:hover {
diff --git a/app/src/styles/main.scss b/app/src/styles/main.scss
index 66a60e7..9fe7222 100644
--- a/app/src/styles/main.scss
+++ b/app/src/styles/main.scss
@@ -27,6 +27,8 @@
@use 'components/confirmationPopUp';
@use 'components/analysis/analysis';
@use 'components/analysis/ROISummary.scss';
+@use 'components/logs/logs';
+@use 'components/footer/footer.scss';
// layout
@use 'layout/loading';
diff --git a/app/src/styles/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss
index 319a75c..abc626a 100644
--- a/app/src/styles/pages/realTimeViz.scss
+++ b/app/src/styles/pages/realTimeViz.scss
@@ -423,7 +423,7 @@
path {
stroke: #f65648;
- stroke-width: 1.3;
+ strokeWidth: 1.3;
}
}
}