From d572647772abc62a8a511858997c0310f269182e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B2=D0=BE=D0=B2=20=D0=90=D1=80=D1=82?= =?UTF-8?q?=D0=B5=D0=BC?= Date: Tue, 21 Apr 2026 01:50:21 +0300 Subject: [PATCH] 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. --- client/src/App.tsx | 26 ++- client/src/embed/useEmbeddedParentTheme.ts | 48 +++++ .../src/pages/EmbeddedActiveEquipmentPage.tsx | 165 +++++++++++++----- client/src/vite-env.d.ts | 8 + 4 files changed, 196 insertions(+), 51 deletions(-) create mode 100644 client/src/embed/useEmbeddedParentTheme.ts diff --git a/client/src/App.tsx b/client/src/App.tsx index 012f516..f57cd22 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,6 +1,9 @@ +import { createTheme } from '@mui/material/styles'; +import { useMemo } from 'react'; import { Admin, Resource } from 'react-admin'; import { authProvider } from './auth/authProvider'; import { dataProvider } from './dataProvider'; +import { useEmbeddedParentTheme } from './embed/useEmbeddedParentTheme'; import { EmbeddedActiveEquipmentPage } from './pages/EmbeddedActiveEquipmentPage'; import { EquipmentCreate } from './resources/equipment/EquipmentCreate'; import { EquipmentEdit } from './resources/equipment/EquipmentEdit'; @@ -11,13 +14,18 @@ import { EquipmentStatusChangeEdit } from './resources/equipment-status-change/E import { EquipmentStatusChangeList } from './resources/equipment-status-change/EquipmentStatusChangeList'; import { EquipmentStatusChangeShow } from './resources/equipment-status-change/EquipmentStatusChangeShow'; -function App() { - if (window.location.pathname === '/embedded/equipment-active') { - return ; - } +function ToirAdmin() { + const paletteMode = useEmbeddedParentTheme(); + const theme = useMemo( + () => + createTheme({ + palette: { mode: paletteMode }, + }), + [paletteMode], + ); return ( - + ; + } + + return ; +} + export default App; diff --git a/client/src/embed/useEmbeddedParentTheme.ts b/client/src/embed/useEmbeddedParentTheme.ts new file mode 100644 index 0000000..3d3cee3 --- /dev/null +++ b/client/src/embed/useEmbeddedParentTheme.ts @@ -0,0 +1,48 @@ +import { useEffect, useState } from 'react'; + +export type EmbedPaletteMode = 'light' | 'dark'; + +const MESSAGE_TYPE = 'greact-theme' as const; + +function parseThemeFromSearch(): EmbedPaletteMode { + if (typeof window === 'undefined') { + return 'light'; + } + const t = new URLSearchParams(window.location.search).get('theme'); + return t === 'dark' ? 'dark' : 'light'; +} + +/** Comma-separated list of parent origins allowed to send theme via postMessage. If unset, all origins are accepted (theme-only payload). */ +function getAllowedOrigins(): string[] { + const raw = import.meta.env.VITE_TOIR_EMBED_PARENT_ORIGINS; + if (typeof raw === 'string' && raw.trim()) { + return raw + .split(',') + .map((s) => s.trim()) + .filter(Boolean); + } + return []; +} + +export function useEmbeddedParentTheme(): EmbedPaletteMode { + const [mode, setMode] = useState(parseThemeFromSearch); + + useEffect(() => { + const allowed = getAllowedOrigins(); + const handler = (event: MessageEvent) => { + if (allowed.length > 0 && !allowed.includes(event.origin)) { + return; + } + const data = event.data as { type?: string; theme?: string } | null; + if (data && typeof data === 'object' && data.type === MESSAGE_TYPE) { + if (data.theme === 'dark' || data.theme === 'light') { + setMode(data.theme); + } + } + }; + window.addEventListener('message', handler); + return () => window.removeEventListener('message', handler); + }, []); + + return mode; +} diff --git a/client/src/pages/EmbeddedActiveEquipmentPage.tsx b/client/src/pages/EmbeddedActiveEquipmentPage.tsx index a33b3f2..f31ce48 100644 --- a/client/src/pages/EmbeddedActiveEquipmentPage.tsx +++ b/client/src/pages/EmbeddedActiveEquipmentPage.tsx @@ -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([]); const [total, setTotal] = useState(null); const [isPending, setIsPending] = useState(true); @@ -105,52 +132,98 @@ export function EmbeddedActiveEquipmentPage() { }; }, []); - return ( - - - - - Оборудование в эксплуатации - - - Отображаются записи со статусом `Active` - {typeof total === 'number' ? `: ${total}` : ''} - - + 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 ? ( - - + return ( + + + + + + + Оборудование в эксплуатации + + + Отображаются записи со статусом 'Active' + {typeof total === 'number' ? `: ${total}` : ''} + - ) : error ? ( - - Не удалось загрузить активное оборудование. - - ) : ( - - - - - Наименование - Заводской номер - Дата изготовления - Дата поверки - - - - {(data ?? []).map((item) => ( - - {item.name} - {item.serialNumber} - {formatDate(item.dateOfInspection)} - {formatDate(item.commissionedAt)} + + {isPending ? ( + + + + ) : error ? ( + + Не удалось загрузить активное оборудование. + + ) : ( + +
+ + + Наименование + Заводской номер + Дата изготовления + Дата поверки + Файл - ))} - -
-
- )} -
-
+ + + {(data ?? []).map((item) => ( + + {item.name} + {item.serialNumber} + {formatDate(item.commissionedAt)} + {formatDate(item.dateOfInspection)} + + {item?.attachment?.objectKey ? ( + { + 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() || 'Скачать'} + + ) : ( + '—' + )} + + + ))} + + + + )} +
+
+ ); } diff --git a/client/src/vite-env.d.ts b/client/src/vite-env.d.ts index 11f02fe..bf0c063 100644 --- a/client/src/vite-env.d.ts +++ b/client/src/vite-env.d.ts @@ -1 +1,9 @@ /// + +interface ImportMetaEnv { + readonly VITE_TOIR_EMBED_PARENT_ORIGINS?: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +}