Redesigned to match sgs

This commit is contained in:
2025-06-28 22:14:42 +03:00
parent ec2cc72a75
commit 20be42eb0c
7 changed files with 549 additions and 484 deletions

1
.gitignore vendored
View File

@@ -35,3 +35,4 @@ yarn-error.log*
# TypeScript # TypeScript
*.tsbuildinfo *.tsbuildinfo
node_modules

7
issue.txt Normal file
View File

@@ -0,0 +1,7 @@
Error Handling Consistency: Some error handling is just console.error without user feedback or recovery.
Security: No mention of authentication, authorization, or input validation. If this is a production system, these are critical.
Scalability: State is mostly local; for larger apps, consider a state management library (Redux, Zustand, etc.) or React Context for shared/global state.
Code Comments: Some complex logic could benefit from more inline comments for maintainability.
Modularity: Some files (e.g., pages) are quite large and could be split into smaller, focused components.
Environment Variables: API URLs are hardcoded in some places; best practice is to use environment variables for all endpoints.
Accessibility: No explicit mention of accessibility (a11y) practices (e.g., ARIA labels, keyboard navigation).

749
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,6 @@
import { import {
Box, Box,
List, List,
ListItem,
ListItemButton, ListItemButton,
ListItemText, ListItemText,
styled, styled,
@@ -22,36 +21,84 @@ import bgreenLogo from '../../assets/bgreen-logo.png';
const DRAWER_WIDTH = 240; const DRAWER_WIDTH = 240;
const LogoContainer = styled(Box)(() => ({ const LogoContainer = styled(Box)(({ theme }) => ({
padding: '20px', padding: '20px',
borderBottom: '1px solid rgba(255,255,255,0.1)', borderBottom: `1px solid ${theme.palette.divider}`,
background: 'linear-gradient(180deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 100%)', background: theme.palette.background.paper,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
})); }));
const StyledListItemButton = styled(ListItemButton)(() => ({ const LogoText = styled(Typography)(({ theme }) => ({
margin: '4px 8px', color: theme.palette.text.secondary,
borderRadius: 4, fontWeight: 600,
letterSpacing: '0.5px',
}));
const LogoSubText = styled(Typography)(({ theme }) => ({
color: theme.palette.text.disabled,
display: 'block',
marginTop: '-2px',
}));
const StyledListItemButton = styled(ListItemButton)(({ theme }) => ({
borderRadius: '0.6rem',
backgroundColor: theme.palette.background.paper,
margin: '2px 8px',
transition: 'background 0.2s, box-shadow 0.2s',
boxShadow: 'none',
position: 'relative',
overflow: 'visible',
'&.Mui-selected': { '&.Mui-selected': {
backgroundColor: 'rgba(255,255,255,0.1)', background: 'linear-gradient(100deg, #028a4a 60%, #28c76f 100%)',
'&:hover': { color: theme.palette.common.white,
backgroundColor: 'rgba(255,255,255,0.15)', boxShadow: '0 4px 24px 0 rgba(2,138,74,0.18)',
'&::after': {
content: '""',
position: 'absolute',
zIndex: 0,
top: 0,
left: 0,
right: 0,
bottom: 0,
borderRadius: '0.6rem',
boxShadow: '0 0 24px 8px #28c76f33', // subtle green glow
pointerEvents: 'none',
}, },
'& .MuiListItemIcon-root': { '& .MuiListItemIcon-root': {
color: '#ffffff', color: theme.palette.common.white,
zIndex: 1,
}, },
'& .MuiListItemText-primary': { '& .MuiListItemText-primary': {
color: '#ffffff', color: theme.palette.common.white,
fontWeight: 500, fontWeight: 600,
zIndex: 1,
}, },
}, },
'&:hover': { '&:hover': {
backgroundColor: 'rgba(255,255,255,0.05)', backgroundColor: theme.palette.action.hover,
color: theme.palette.text.primary,
boxShadow: 'none',
'& .MuiListItemIcon-root': {
color: theme.palette.text.primary,
},
'& .MuiListItemText-primary': {
color: theme.palette.text.primary,
},
}, },
})); }));
const MenuListContainer = styled(Box)(({ theme }) => ({
background: theme.palette.background.paper,
borderRadius: '0.6rem',
margin: '16px 8px 0 8px',
boxShadow: '0 2px 12px 0 rgba(44,62,80,0.04)',
padding: '4px 0',
display: 'flex',
flexDirection: 'column',
}));
const menuItems = [ const menuItems = [
{ text: 'Home', path: '/', icon: <HomeIcon /> }, { text: 'Home', path: '/', icon: <HomeIcon /> },
{ text: 'Environmental Temperature', path: '/temperature', icon: <ThermostatIcon /> }, { text: 'Environmental Temperature', path: '/temperature', icon: <ThermostatIcon /> },
@@ -92,49 +139,29 @@ const Sidebar = ({ open, onToggle, isMobile }: SidebarProps) => {
}} }}
/> />
<Box> <Box>
<Typography <LogoText variant="h6">B'GREEN</LogoText>
variant="h6" <LogoSubText variant="caption">Monitor System</LogoSubText>
sx={{
color: '#ffffff',
fontWeight: 600,
letterSpacing: '0.5px',
}}
>
B'GREEN
</Typography>
<Typography
variant="caption"
sx={{
color: 'rgba(255,255,255,0.7)',
display: 'block',
marginTop: '-2px',
}}
>
Monitor System
</Typography>
</Box> </Box>
</Box> </Box>
{isMobile && ( {isMobile && (
<IconButton <IconButton
onClick={onToggle} onClick={onToggle}
sx={{ sx={{ color: theme.palette.text.disabled, '&:hover': { color: theme.palette.text.secondary } }}
color: 'rgba(255,255,255,0.7)',
'&:hover': { color: '#ffffff' }
}}
> >
<ChevronLeftIcon /> <ChevronLeftIcon />
</IconButton> </IconButton>
)} )}
</LogoContainer> </LogoContainer>
<List sx={{ flexGrow: 1, mt: 2 }}> <MenuListContainer>
{menuItems.map((item) => ( <List sx={{ flexGrow: 1, p: 0 }}>
<ListItem key={item.text} disablePadding> {menuItems.map((item) => (
<StyledListItemButton <StyledListItemButton
key={item.text}
selected={location.pathname === item.path} selected={location.pathname === item.path}
onClick={() => handleNavigation(item.path)} onClick={() => handleNavigation(item.path)}
> >
<ListItemIcon sx={{ color: 'rgba(255,255,255,0.7)', minWidth: 40 }}> <ListItemIcon sx={{ color: theme.palette.text.secondary, minWidth: 40 }}>
{item.icon} {item.icon}
</ListItemIcon> </ListItemIcon>
<ListItemText <ListItemText
@@ -142,14 +169,14 @@ const Sidebar = ({ open, onToggle, isMobile }: SidebarProps) => {
sx={{ sx={{
'& .MuiListItemText-primary': { '& .MuiListItemText-primary': {
fontSize: '0.9rem', fontSize: '0.9rem',
color: 'rgba(255,255,255,0.7)', color: theme.palette.text.secondary,
}, },
}} }}
/> />
</StyledListItemButton> </StyledListItemButton>
</ListItem> ))}
))} </List>
</List> </MenuListContainer>
<Box <Box
sx={{ sx={{
@@ -185,7 +212,7 @@ const Sidebar = ({ open, onToggle, isMobile }: SidebarProps) => {
display: { xs: 'block' }, display: { xs: 'block' },
'& .MuiDrawer-paper': { '& .MuiDrawer-paper': {
width: DRAWER_WIDTH, width: DRAWER_WIDTH,
backgroundColor: theme.palette.primary.main, backgroundColor: theme.palette.background.default,
border: 'none', border: 'none',
height: '100%', height: '100%',
}, },

View File

@@ -1 +0,0 @@
node_modules/

View File

@@ -47,13 +47,14 @@ import DebugConsole from '../components/DebugConsole';
import MonitoringSystem from './MonitoringSystem'; import MonitoringSystem from './MonitoringSystem';
const StyledCard = styled(Paper)(({ theme }) => ({ const StyledCard = styled(Paper)(({ theme }) => ({
borderRadius: theme.spacing(2), borderRadius: '0.357rem',
padding: theme.spacing(3), padding: theme.spacing(3),
height: '100%', height: '100%',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
backgroundColor: theme.palette.background.paper, backgroundColor: theme.palette.background.paper,
transition: 'transform 0.2s, box-shadow 0.2s', transition: 'transform 0.2s, box-shadow 0.2s',
boxShadow: theme.shadows[3],
'&:hover': { '&:hover': {
transform: 'translateY(-4px)', transform: 'translateY(-4px)',
boxShadow: theme.shadows[8], boxShadow: theme.shadows[8],
@@ -62,7 +63,8 @@ const StyledCard = styled(Paper)(({ theme }) => ({
const StyledSelect = styled(Select)(({ theme }) => ({ const StyledSelect = styled(Select)(({ theme }) => ({
'& .MuiOutlinedInput-notchedOutline': { '& .MuiOutlinedInput-notchedOutline': {
borderRadius: theme.spacing(1.5), borderRadius: '0.357rem',
borderColor: theme.palette.divider,
}, },
'&:hover .MuiOutlinedInput-notchedOutline': { '&:hover .MuiOutlinedInput-notchedOutline': {
borderColor: theme.palette.primary.main, borderColor: theme.palette.primary.main,
@@ -74,15 +76,16 @@ const SectionTitle = styled(Typography)(({ theme }) => ({
alignItems: 'center', alignItems: 'center',
gap: theme.spacing(1), gap: theme.spacing(1),
marginBottom: theme.spacing(3), marginBottom: theme.spacing(3),
color: theme.palette.text.primary, color: theme.palette.text.secondary,
fontWeight: 600, fontWeight: 700,
fontFamily: theme.typography.fontFamily,
})); }));
const IconWrapper = styled(Box)(({ theme }) => ({ const IconWrapper = styled(Box)(({ theme }) => ({
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
gap: theme.spacing(0.5), gap: theme.spacing(0.5),
color: theme.palette.text.secondary, color: theme.palette.text.primary,
marginBottom: theme.spacing(1), marginBottom: theme.spacing(1),
})); }));
@@ -90,19 +93,20 @@ const WeightSlider = styled(Slider)(({ theme }) => ({
'& .MuiSlider-thumb': { '& .MuiSlider-thumb': {
height: 24, height: 24,
width: 24, width: 24,
backgroundColor: '#fff', backgroundColor: theme.palette.background.paper,
border: '2px solid currentColor', border: `2px solid ${theme.palette.primary.main}`,
'&:focus, &:hover, &.Mui-active, &.Mui-focusVisible': { '&:focus, &:hover, &.Mui-active, &.Mui-focusVisible': {
boxShadow: 'inherit', boxShadow: 'inherit',
}, },
}, },
'& .MuiSlider-track': { '& .MuiSlider-track': {
height: 4, height: 4,
backgroundColor: theme.palette.primary.main,
}, },
'& .MuiSlider-rail': { '& .MuiSlider-rail': {
height: 4, height: 4,
opacity: 0.5, opacity: 0.5,
backgroundColor: theme.palette.mode === 'dark' ? '#bfbfbf' : '#d8d8d8', backgroundColor: theme.palette.divider,
}, },
'& .MuiSlider-mark': { '& .MuiSlider-mark': {
backgroundColor: theme.palette.primary.main, backgroundColor: theme.palette.primary.main,
@@ -118,47 +122,36 @@ const WeightSlider = styled(Slider)(({ theme }) => ({
}, },
})); }));
const WeightInput = styled(TextField)(() => ({ const WeightInput = styled(TextField)(({ theme }) => ({
width: 70, width: 70,
'& input': { '& input': {
padding: '8px', padding: '8px',
textAlign: 'center', textAlign: 'center',
fontFamily: theme.typography.fontFamily,
}, },
})); }));
const StatusChip = styled(Chip)(({ theme }) => ({ const StatusChip = styled(Chip)(({ theme }) => ({
borderRadius: theme.spacing(1), borderRadius: '0.357rem',
fontWeight: 500, fontWeight: 600,
fontFamily: theme.typography.fontFamily,
'&.running': { '&.running': {
backgroundColor: theme.palette.success.light, backgroundColor: theme.palette.success.main,
color: theme.palette.success.dark, color: theme.palette.common.white,
}, },
'&.stopped': { '&.stopped': {
backgroundColor: theme.palette.error.light, backgroundColor: theme.palette.error.main,
color: theme.palette.error.dark, color: theme.palette.common.white,
}, },
})); }));
interface Weights {
energy: number;
balance: number;
overload: number;
allocation: number;
}
interface AlertState {
open: boolean;
message: string;
severity: 'success' | 'error' | 'info' | 'warning';
}
const StatusCard = styled(Box)(({ theme }) => ({ const StatusCard = styled(Box)(({ theme }) => ({
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
gap: theme.spacing(1), gap: theme.spacing(1),
padding: theme.spacing(2), padding: theme.spacing(2),
borderRadius: theme.shape.borderRadius * 2, borderRadius: '0.357rem',
backgroundColor: theme.palette.background.paper, backgroundColor: theme.palette.background.paper,
boxShadow: theme.shadows[1], boxShadow: theme.shadows[1],
minWidth: 280, minWidth: 280,
@@ -174,12 +167,9 @@ const StatusIndicator = styled('div')(({ theme }) => ({
height: 3, height: 3,
backgroundColor: 'transparent', backgroundColor: 'transparent',
'&.loading': { '&.loading': {
background: `linear-gradient(90deg, background: `linear-gradient(90deg, ${theme.palette.primary.main}, ${theme.palette.primary.light}, ${theme.palette.primary.main})`,
${theme.palette.primary.main},
${theme.palette.primary.light},
${theme.palette.primary.main})`,
backgroundSize: '200% 100%', backgroundSize: '200% 100%',
animation: '$shimmer 2s infinite', animation: 'shimmer 2s infinite',
}, },
'&.success': { '&.success': {
backgroundColor: theme.palette.success.main, backgroundColor: theme.palette.success.main,

View File

@@ -4,54 +4,94 @@ export const theme = createTheme({
palette: { palette: {
mode: 'light', mode: 'light',
primary: { primary: {
main: '#0B1A33', // Deep navy blue from BLC main: '#028a4a', // B'GREEN primary green
light: '#1e3a6b', light: '#28c76f', // Success green
dark: '#060d19', dark: '#026c39',
}, },
secondary: { secondary: {
main: '#FF5722', // Orange accent from icons main: '#FF1744', // Material-UI Red[500] as secondary
light: '#ff784e', light: '#ff616f',
dark: '#c41c00', dark: '#b2102f',
}, },
background: { background: {
default: '#f5f5f5', default: '#f8f8f8', // Body background
paper: '#ffffff', paper: '#fff',
}, },
text: { text: {
primary: '#0B1A33', primary: '#6e6b7b', // Body text
secondary: '#546e7a', secondary: '#5e5873', // Headings
disabled: '#b9b9c3', // Muted text
}, },
success: {
main: '#28c76f',
},
warning: {
main: '#ffb400',
},
error: {
main: '#FF1744',
},
info: {
main: '#00cfe8',
},
divider: '#ebe9f1', // Border color
}, },
typography: { typography: {
fontFamily: '"Segoe UI", "Roboto", "Helvetica", sans-serif', fontFamily: 'Montserrat, Helvetica, Arial, serif',
fontSize: 14, // 1rem
h1: { h1: {
fontSize: '2.5rem', fontSize: '2rem', // 28px
fontWeight: 700,
color: '#5e5873',
},
h2: {
fontSize: '1.714rem', // 24px
fontWeight: 600, fontWeight: 600,
color: '#0B1A33', color: '#5e5873',
},
h3: {
fontSize: '1.5rem', // 21px
fontWeight: 600,
color: '#5e5873',
},
h4: {
fontSize: '1.286rem', // 18px
fontWeight: 500,
color: '#5e5873',
}, },
h5: { h5: {
fontSize: '1.07rem', // 15px
fontWeight: 500, fontWeight: 500,
color: '#0B1A33', color: '#5e5873',
},
body1: {
fontSize: '1rem',
color: '#6e6b7b',
},
body2: {
fontSize: '0.9rem',
color: '#6e6b7b',
}, },
}, },
components: { components: {
MuiButton: { MuiButton: {
styleOverrides: { styleOverrides: {
root: { root: {
borderRadius: 4, borderRadius: '0.357rem',
textTransform: 'none', textTransform: 'none',
padding: '8px 24px', padding: '8px 24px',
fontWeight: 600,
transition: 'all 0.3s ease-in-out', transition: 'all 0.3s ease-in-out',
'&:hover': { '&:hover': {
transform: 'translateY(-2px)', transform: 'translateY(-2px)',
boxShadow: '0 4px 12px rgba(11, 26, 51, 0.15)', boxShadow: '0 4px 12px rgba(2, 138, 74, 0.15)',
}, },
}, },
contained: { contained: {
background: '#0B1A33', background: '#028a4a',
color: '#ffffff', color: '#fff',
'&:hover': { '&:hover': {
background: '#1e3a6b', background: '#026c39',
}, },
}, },
}, },
@@ -59,18 +99,18 @@ export const theme = createTheme({
MuiCard: { MuiCard: {
styleOverrides: { styleOverrides: {
root: { root: {
borderRadius: 8, borderRadius: '0.357rem',
boxShadow: '0 2px 12px rgba(11, 26, 51, 0.08)', boxShadow: '0 2px 12px rgba(2, 138, 74, 0.08)',
border: '1px solid rgba(11, 26, 51, 0.1)', border: '1px solid #ebe9f1',
}, },
}, },
}, },
MuiPaper: { MuiPaper: {
styleOverrides: { styleOverrides: {
root: { root: {
borderRadius: 8, borderRadius: '0.357rem',
boxShadow: '0 2px 12px rgba(11, 26, 51, 0.08)', boxShadow: '0 2px 12px rgba(2, 138, 74, 0.08)',
border: '1px solid rgba(11, 26, 51, 0.1)', border: '1px solid #ebe9f1',
}, },
}, },
}, },
@@ -79,13 +119,13 @@ export const theme = createTheme({
root: { root: {
'& .MuiOutlinedInput-root': { '& .MuiOutlinedInput-root': {
'& fieldset': { '& fieldset': {
borderColor: 'rgba(11, 26, 51, 0.2)', borderColor: '#ebe9f1',
}, },
'&:hover fieldset': { '&:hover fieldset': {
borderColor: 'rgba(11, 26, 51, 0.3)', borderColor: '#028a4a',
}, },
'&.Mui-focused fieldset': { '&.Mui-focused fieldset': {
borderColor: '#0B1A33', borderColor: '#028a4a',
}, },
}, },
}, },