added logs list
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
PointElement,
|
||||
} from "chart.js";
|
||||
import { PowerIcon, ThroughputSummaryIcon } from "../../icons/analysis";
|
||||
import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor";
|
||||
|
||||
ChartJS.register(LineElement, CategoryScale, LinearScale, PointElement);
|
||||
|
||||
@@ -142,13 +143,13 @@ const ThroughputSummary = () => {
|
||||
|
||||
<div className="progress-wrapper">
|
||||
{/* Dynamically create progress bars based on shiftUtilization array */}
|
||||
{shiftUtilization.map((shift) => (
|
||||
{shiftUtilization.map((shift, index) => (
|
||||
<div
|
||||
key={shift.shift}
|
||||
className={`progress shift-${shift.shift}`}
|
||||
style={{
|
||||
width: `${shift.percentage}%`,
|
||||
backgroundColor: getRandomColor(),
|
||||
backgroundColor: getAvatarColor(index),
|
||||
}}
|
||||
></div>
|
||||
))}
|
||||
@@ -156,11 +157,11 @@ const ThroughputSummary = () => {
|
||||
|
||||
<div className="progress-indicator">
|
||||
{/* Dynamically create shift indicators with random colors */}
|
||||
{shiftUtilization.map((shift) => (
|
||||
{shiftUtilization.map((shift, index) => (
|
||||
<div className="shift-wrapper" key={shift.shift}>
|
||||
<span
|
||||
className={`indicator shift-${shift.shift}`}
|
||||
style={{ backgroundColor: getRandomColor() }} // Random color for indicator
|
||||
style={{ backgroundColor: getAvatarColor(index) }} // Random color for indicator
|
||||
></span>
|
||||
<label>Shift {shift.shift}</label>
|
||||
</div>
|
||||
|
||||
44
app/src/components/ui/footer/Footer.tsx
Normal file
44
app/src/components/ui/footer/Footer.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from "react";
|
||||
import { HelpIcon } from "../../icons/DashboardIcon";
|
||||
import { useLogger } from "../log/LoggerContext";
|
||||
|
||||
const Footer = () => {
|
||||
const { logs, setIsLogListVisible } = useLogger();
|
||||
|
||||
const lastLog = logs.length > 0 ? logs[logs.length - 1] : null;
|
||||
|
||||
return (
|
||||
<div className="footer-wrapper">
|
||||
<div className="selection-wrapper">
|
||||
<div className="selector-wrapper">
|
||||
<div className="icon"></div>
|
||||
<div className="selector">Selection</div>
|
||||
</div>
|
||||
<div className="selector-wrapper">
|
||||
<div className="icon"></div>
|
||||
<div className="selector">Rotate/Zoom</div>
|
||||
</div>
|
||||
<div className="selector-wrapper">
|
||||
<div className="icon"></div>
|
||||
<div className="selector">Pan/Context Menu</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="logs-wrapper">
|
||||
<div className="logs-detail" onClick={() => setIsLogListVisible(true)}>
|
||||
{lastLog
|
||||
? `[${lastLog.type.toUpperCase()}] ${lastLog.message}`
|
||||
: "No logs yet."}
|
||||
</div>
|
||||
<div className="version">
|
||||
V 0.01
|
||||
<div className="icon">
|
||||
<HelpIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
87
app/src/components/ui/log/LogList.tsx
Normal file
87
app/src/components/ui/log/LogList.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
// LogList.tsx
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
LogListIcon,
|
||||
TickIcon,
|
||||
LogInfoIcon,
|
||||
WarningIcon,
|
||||
ErrorIcon,
|
||||
} 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 <LogInfoIcon />;
|
||||
case "error":
|
||||
return <ErrorIcon />;
|
||||
case "warning":
|
||||
return <WarningIcon />;
|
||||
case "log":
|
||||
default:
|
||||
return <LogInfoIcon />;
|
||||
}
|
||||
};
|
||||
|
||||
const formatTimestamp = (date: Date) => new Date(date).toLocaleTimeString();
|
||||
|
||||
const filteredLogs =
|
||||
selectedTab === "all"
|
||||
? [...logs].reverse()
|
||||
: [...logs].filter((log) => log.type === selectedTab).reverse();
|
||||
|
||||
return (
|
||||
<div className="log-list-container">
|
||||
<div className="log-list-wrapper">
|
||||
<div className="log-header">
|
||||
<div className="log-header-wrapper">
|
||||
<div className="icon">
|
||||
<LogListIcon />
|
||||
</div>
|
||||
<div className="head">Log List</div>
|
||||
</div>
|
||||
<div className="close" onClick={() => setIsLogListVisible(false)}>
|
||||
X
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="log-nav-wrapper">
|
||||
{["all", "info", "warning", "error"].map((type) => (
|
||||
<div
|
||||
key={type}
|
||||
className={`log-nav ${selectedTab === type ? "active" : ""}`}
|
||||
onClick={() => setSelectedTab(type as any)}
|
||||
>
|
||||
{`${type.charAt(0).toUpperCase() + type.slice(1)} Logs`}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Log Entries */}
|
||||
<div className="log-entry-wrapper">
|
||||
{filteredLogs.map((log) => (
|
||||
<div key={log.id} className={`log-entry ${log.type}`}>
|
||||
<div className="tick">
|
||||
<TickIcon />
|
||||
</div>
|
||||
<div className="log-icon">{getLogIcon(log.type)}</div>
|
||||
<div className="log-entry-message">
|
||||
[{formatTimestamp(log.timestamp)}] [{log.type.toUpperCase()}]{" "}
|
||||
{log.message}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LogList;
|
||||
77
app/src/components/ui/log/LoggerContext.tsx
Normal file
77
app/src/components/ui/log/LoggerContext.tsx
Normal file
@@ -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<React.SetStateAction<LogEntry[]>>;
|
||||
isLogListVisible: boolean;
|
||||
setIsLogListVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
log: (message: string) => void;
|
||||
info: (message: string) => void;
|
||||
warn: (message: string) => void;
|
||||
error: (message: string) => void;
|
||||
clear: () => void;
|
||||
}
|
||||
|
||||
const LoggerContext = createContext<LoggerContextValue | undefined>(undefined);
|
||||
|
||||
export const LoggerProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [logs, setLogs] = useState<LogEntry[]>([]);
|
||||
const [isLogListVisible, setIsLogListVisible] = useState<boolean>(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 (
|
||||
<LoggerContext.Provider value={loggerMethods}>
|
||||
{children}
|
||||
</LoggerContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useLogger = () => {
|
||||
const context = useContext(LoggerContext);
|
||||
if (!context) {
|
||||
throw new Error("useLogger must be used within a LoggerProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
71
app/src/components/ui/log/logger.ts
Normal file
71
app/src/components/ui/log/logger.ts
Normal file
@@ -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();
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
SpeedIcon,
|
||||
StartIcon,
|
||||
} from "../../icons/ExportCommonIcons";
|
||||
import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor";
|
||||
|
||||
const SimulationPlayer: React.FC = () => {
|
||||
const { speed, setSpeed } = useAnimationPlaySpeed();
|
||||
@@ -110,15 +111,6 @@ const SimulationPlayer: React.FC = () => {
|
||||
return color;
|
||||
};
|
||||
|
||||
// Store colors for each process item
|
||||
const [processColors, setProcessColors] = useState<string[]>([]);
|
||||
|
||||
// Generate colors on mount or when process changes
|
||||
useEffect(() => {
|
||||
const generatedColors = process.map(() => getRandomColor());
|
||||
setProcessColors(generatedColors);
|
||||
}, []);
|
||||
|
||||
const intervals = [10, 20, 30, 40, 50, 60]; // in minutes
|
||||
const totalSegments = intervals.length;
|
||||
const progress = 20; // percent (example)
|
||||
@@ -328,6 +320,8 @@ const SimulationPlayer: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="processDisplayer">
|
||||
<div className="start-displayer timmer">00:00</div>
|
||||
<div className="end-displayer timmer">24:00</div>
|
||||
<div
|
||||
className="process-player"
|
||||
style={{ left: playerPosition, position: "absolute" }}
|
||||
@@ -343,7 +337,7 @@ const SimulationPlayer: React.FC = () => {
|
||||
className="process"
|
||||
style={{
|
||||
width: `${item.completed}%`,
|
||||
backgroundColor: processColors[index],
|
||||
backgroundColor: getAvatarColor(index),
|
||||
}}
|
||||
></div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user