This commit is contained in:
sebseb7
2025-12-25 00:24:48 +01:00
parent 0eb05b1cd5
commit 2d7bfe247d
7 changed files with 630 additions and 524 deletions

View File

@@ -1,130 +1,133 @@
import React, { useState, useEffect } from 'react';
import { BrowserRouter, Routes, Route, Link, Navigate, useNavigate } from 'react-router-dom';
import { ThemeProvider, CssBaseline, AppBar, Toolbar, Typography, Button, Box, IconButton, Menu, MenuItem } from '@mui/material';
import React, { Component } from 'react';
import { BrowserRouter, Routes, Route, Link, Navigate } from 'react-router-dom';
import { AppBar, Toolbar, Typography, Button, Box, IconButton, CssBaseline } from '@mui/material';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import SettingsIcon from '@mui/icons-material/Settings';
import ShowChartIcon from '@mui/icons-material/ShowChart';
import DashboardIcon from '@mui/icons-material/Dashboard';
import AccountCircle from '@mui/icons-material/AccountCircle';
import theme from './theme';
import Settings from './components/Settings';
import Chart from './components/Chart';
import Login from './components/Login';
import ViewManager from './components/ViewManager';
import ViewDisplay from './components/ViewDisplay';
function NavBar({ user, onLogout }) {
const navigate = useNavigate();
const [anchorEl, setAnchorEl] = useState(null);
const handleMenu = (event) => setAnchorEl(event.currentTarget);
const handleClose = () => setAnchorEl(null);
const handleLogout = () => {
handleClose();
onLogout();
navigate('/');
};
return (
<AppBar position="static">
<Toolbar>
<Typography variant="h6" component="div" sx={{ flexGrow: 1, cursor: 'pointer' }} onClick={() => navigate('/')}>
TischlerCtrl
</Typography>
<Button color="inherit" startIcon={<DashboardIcon />} onClick={() => navigate('/')}>
Views
</Button>
<Button color="inherit" startIcon={<ShowChartIcon />} onClick={() => navigate('/live')}>
Live
</Button>
<Button color="inherit" startIcon={<SettingsIcon />} onClick={() => navigate('/settings')}>
Settings
</Button>
{user ? (
<div>
<IconButton
size="large"
onClick={handleMenu}
color="inherit"
>
<AccountCircle />
</IconButton>
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuItem disabled>{user.username} ({user.role})</MenuItem>
<MenuItem onClick={handleLogout}>Logout</MenuItem>
</Menu>
</div>
) : (
<Button color="inherit" onClick={() => navigate('/login')}>
Login
</Button>
)}
</Toolbar>
</AppBar>
);
}
export default function App() {
const [user, setUser] = useState(null);
const [selectedChannels, setSelectedChannels] = useState([]);
// Load persistence (User + Settings)
useEffect(() => {
try {
const savedSettings = localStorage.getItem('selectedChannels');
if (savedSettings) setSelectedChannels(JSON.parse(savedSettings));
const savedUser = localStorage.getItem('user');
if (savedUser) setUser(JSON.parse(savedUser));
} catch (e) {
console.error("Failed to load persistence", e);
const darkTheme = createTheme({
palette: {
mode: 'dark',
primary: { main: '#fb4934' }, // Gruvbox red
secondary: { main: '#83a598' }, // Gruvbox blue
background: {
default: '#282828', // Gruvbox dark bg
paper: '#3c3836', // Gruvbox dark lighter
},
text: {
primary: '#ebdbb2',
secondary: '#a89984'
}
}, []);
},
});
const handleLogin = (userData) => {
setUser(userData);
localStorage.setItem('user', JSON.stringify(userData));
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
selectedChannels: [],
user: null, // { username, role, token }
loading: true
};
}
componentDidMount() {
// Load selection from local storage
const saved = localStorage.getItem('selectedChannels');
if (saved) {
try {
this.setState({ selectedChannels: JSON.parse(saved) });
} catch (e) {
console.error("Failed to parse saved channels");
}
}
// Check for existing token
const token = localStorage.getItem('authToken');
const username = localStorage.getItem('authUser');
const role = localStorage.getItem('authRole');
if (token && username) {
this.setState({ user: { username, role, token } });
}
this.setState({ loading: false });
}
handleSelectionChange = (newSelection) => {
this.setState({ selectedChannels: newSelection });
localStorage.setItem('selectedChannels', JSON.stringify(newSelection));
};
const handleLogout = () => {
setUser(null);
localStorage.removeItem('user');
handleLogin = (userData) => {
this.setState({ user: userData });
localStorage.setItem('authToken', userData.token);
localStorage.setItem('authUser', userData.username);
localStorage.setItem('authRole', userData.role);
};
const handleToggleChannel = (id) => {
setSelectedChannels(prev => {
const newSelection = prev.includes(id)
? prev.filter(c => c !== id)
: [...prev, id];
localStorage.setItem('selectedChannels', JSON.stringify(newSelection));
return newSelection;
});
handleLogout = () => {
this.setState({ user: null });
localStorage.removeItem('authToken');
localStorage.removeItem('authUser');
localStorage.removeItem('authRole');
};
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<BrowserRouter>
<Box sx={{ flexGrow: 1, height: '100vh', display: 'flex', flexDirection: 'column' }}>
<NavBar user={user} onLogout={handleLogout} />
render() {
const { selectedChannels, user, loading } = this.state;
// While checking auth, we could show loader, but it's sync here mostly.
return (
<ThemeProvider theme={darkTheme}>
<CssBaseline />
<BrowserRouter>
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', height: '100vh' }}>
<AppBar position="static">
<Toolbar>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
TischlerCtrl
</Typography>
<Button color="inherit" component={Link} to="/" startIcon={<DashboardIcon />}>Views</Button>
<Button color="inherit" component={Link} to="/live" startIcon={<ShowChartIcon />}>Live</Button>
<Button color="inherit" component={Link} to="/settings" startIcon={<SettingsIcon />}>Settings</Button>
{user ? (
<Button color="inherit" onClick={this.handleLogout}>Logout ({user.username})</Button>
) : (
<Button color="inherit" component={Link} to="/login">Login</Button>
)}
</Toolbar>
</AppBar>
<Box component="main" sx={{ flexGrow: 1, overflow: 'auto' }}>
<Routes>
<Route path="/" element={<ViewManager user={user} />} />
<Route path="/live" element={<Chart selectedChannels={selectedChannels} />} />
<Route path="/settings" element={<Settings selectedChannels={selectedChannels} onToggleChannel={handleToggleChannel} />} />
<Route path="/login" element={<Login onLogin={handleLogin} />} />
<Route path="/views/:id" element={<ViewDisplay />} />
<Route path="/live" element={
<Chart
selectedChannels={selectedChannels}
/>
} />
<Route path="/settings" element={
<Settings
selectedChannels={selectedChannels}
onSelectionChange={this.handleSelectionChange}
/>
} />
<Route path="/login" element={<Login onLogin={this.handleLogin} />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</Box>
</Box>
</BrowserRouter>
</ThemeProvider>
);
</BrowserRouter>
</ThemeProvider>
);
}
}