Refactor App and EmbeddedActiveEquipmentPage for theme support

- Renamed the main component from `App` to `ToirAdmin` for clarity.
- Integrated theme support using `useEmbeddedParentTheme` to dynamically adjust the UI based on the parent theme.
- Updated `EmbeddedActiveEquipmentPage` to utilize the new theme and improved styling for better user experience.
- Added a new utility hook `useEmbeddedParentTheme` to manage theme changes via postMessage from parent origins.
- Enhanced the `vite-env.d.ts` file to include environment variable definitions for parent origins.
This commit is contained in:
Первов Артем
2026-04-21 01:50:21 +03:00
parent 4584a0d581
commit d572647772
4 changed files with 196 additions and 51 deletions

View File

@@ -1,6 +1,8 @@
import Alert from '@mui/material/Alert';
import Box from '@mui/material/Box';
import CircularProgress from '@mui/material/CircularProgress';
import CssBaseline from '@mui/material/CssBaseline';
import Link from '@mui/material/Link';
import Paper from '@mui/material/Paper';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
@@ -9,9 +11,12 @@ import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Typography from '@mui/material/Typography';
import { useEffect, useState } from 'react';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { useEffect, useMemo, useState } from 'react';
import { useEmbeddedParentTheme } from '../embed/useEmbeddedParentTheme';
import { env } from '../config/env';
import { ensureFreshToken, getAccessToken } from '../auth/keycloak';
import { downloadEquipmentAttachmentFile } from '../resources/equipment/attachmentDownload';
type EquipmentRecord = {
id: string;
@@ -19,11 +24,15 @@ type EquipmentRecord = {
serialNumber: string;
dateOfInspection: string | null;
commissionedAt: string | null;
attachment?: {
objectKey?: string;
originalFileName?: string | null;
} | null;
};
function formatDate(value: string | null) {
if (!value) {
return '-';
return '';
}
const date = new Date(value);
@@ -35,6 +44,24 @@ function formatDate(value: string | null) {
}
export function EmbeddedActiveEquipmentPage() {
const paletteMode = useEmbeddedParentTheme();
const muiTheme = useMemo(
() =>
createTheme({
palette: { mode: paletteMode },
components: {
MuiPaper: {
styleOverrides: {
root: {
backgroundImage: 'none',
},
},
},
},
}),
[paletteMode],
);
const [data, setData] = useState<EquipmentRecord[]>([]);
const [total, setTotal] = useState<number | null>(null);
const [isPending, setIsPending] = useState(true);
@@ -105,52 +132,98 @@ export function EmbeddedActiveEquipmentPage() {
};
}, []);
return (
<Box sx={{ minHeight: '100vh', boxSizing: 'border-box', p: { xs: 2, md: 3 }, bgcolor: '#f3f6fa' }}>
<Paper elevation={0} sx={{ overflow: 'hidden', borderRadius: 3, border: '1px solid #d7e0ea' }}>
<Box sx={{ px: 3, py: 2, borderBottom: '1px solid #d7e0ea', bgcolor: '#fff' }}>
<Typography variant="h5" sx={{ fontWeight: 700, color: '#10233a' }}>
Оборудование в эксплуатации
</Typography>
<Typography variant="body2" sx={{ mt: 0.5, color: '#5b7087' }}>
Отображаются записи со статусом `Active`
{typeof total === 'number' ? `: ${total}` : ''}
</Typography>
</Box>
const pageBg =
paletteMode === 'dark' ? 'linear-gradient(180deg, #0d1117 0%, #0a0c10 100%)' : '#f3f6fa';
const headerBg = paletteMode === 'dark' ? '#161b22' : '#fff';
const headerBorder = paletteMode === 'dark' ? '1px solid #30363d' : '1px solid #d7e0ea';
const titleColor = paletteMode === 'dark' ? '#e6edf3' : '#10233a';
const subtitleColor = paletteMode === 'dark' ? '#8b949e' : '#5b7087';
{isPending ? (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 8 }}>
<CircularProgress />
return (
<ThemeProvider theme={muiTheme}>
<CssBaseline />
<Box
sx={{
minHeight: '100vh',
boxSizing: 'border-box',
p: { xs: 2, md: 3 },
bgcolor: pageBg,
}}
>
<Paper
elevation={0}
sx={{
overflow: 'hidden',
borderRadius: 3,
border: headerBorder,
bgcolor: paletteMode === 'dark' ? '#161b22' : '#fff',
}}
>
<Box sx={{ px: 3, py: 2, borderBottom: headerBorder, bgcolor: headerBg }}>
<Typography variant="h5" sx={{ fontWeight: 700, color: titleColor }}>
Оборудование в эксплуатации
</Typography>
<Typography variant="body2" sx={{ mt: 0.5, color: subtitleColor }}>
Отображаются записи со статусом &apos;Active&apos;
{typeof total === 'number' ? `: ${total}` : ''}
</Typography>
</Box>
) : error ? (
<Box sx={{ p: 3 }}>
<Alert severity="error">Не удалось загрузить активное оборудование.</Alert>
</Box>
) : (
<TableContainer>
<Table size="small">
<TableHead>
<TableRow>
<TableCell sx={{ fontWeight: 700 }}>Наименование</TableCell>
<TableCell sx={{ fontWeight: 700 }}>Заводской номер</TableCell>
<TableCell sx={{ fontWeight: 700 }}>Дата изготовления</TableCell>
<TableCell sx={{ fontWeight: 700 }}>Дата поверки</TableCell>
</TableRow>
</TableHead>
<TableBody>
{(data ?? []).map((item) => (
<TableRow key={item.id} hover>
<TableCell>{item.name}</TableCell>
<TableCell>{item.serialNumber}</TableCell>
<TableCell>{formatDate(item.dateOfInspection)}</TableCell>
<TableCell>{formatDate(item.commissionedAt)}</TableCell>
{isPending ? (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 8 }}>
<CircularProgress />
</Box>
) : error ? (
<Box sx={{ p: 3 }}>
<Alert severity="error">Не удалось загрузить активное оборудование.</Alert>
</Box>
) : (
<TableContainer>
<Table size="small">
<TableHead>
<TableRow>
<TableCell sx={{ fontWeight: 700 }}>Наименование</TableCell>
<TableCell sx={{ fontWeight: 700 }}>Заводской номер</TableCell>
<TableCell sx={{ fontWeight: 700 }}>Дата изготовления</TableCell>
<TableCell sx={{ fontWeight: 700 }}>Дата поверки</TableCell>
<TableCell sx={{ fontWeight: 700 }}>Файл</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
</Paper>
</Box>
</TableHead>
<TableBody>
{(data ?? []).map((item) => (
<TableRow key={item.id} hover>
<TableCell>{item.name}</TableCell>
<TableCell>{item.serialNumber}</TableCell>
<TableCell>{formatDate(item.commissionedAt)}</TableCell>
<TableCell>{formatDate(item.dateOfInspection)}</TableCell>
<TableCell>
{item?.attachment?.objectKey ? (
<Link
component="button"
type="button"
underline="hover"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
const label = item.attachment?.originalFileName?.trim() || 'файл';
void downloadEquipmentAttachmentFile(item.id, label).catch(() => {});
}}
sx={{ cursor: 'pointer', verticalAlign: 'inherit' }}
>
{item.attachment?.originalFileName?.trim() || 'Скачать'}
</Link>
) : (
'—'
)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
</Paper>
</Box>
</ThemeProvider>
);
}