version update
parent
558cf8eaba
commit
14d2f3eb68
|
|
@ -32,3 +32,9 @@ node_modules
|
||||||
.env.development
|
.env.development
|
||||||
.env.production
|
.env.production
|
||||||
.env.test
|
.env.test
|
||||||
|
|
||||||
|
# Local configs
|
||||||
|
vite.config.js
|
||||||
|
vite.config.local.js
|
||||||
|
.env.local
|
||||||
|
*.local.*
|
||||||
|
|
@ -3,117 +3,168 @@ import {
|
||||||
TextField, Box, Typography, IconButton, Divider,
|
TextField, Box, Typography, IconButton, Divider,
|
||||||
CircularProgress, Alert, Collapse, Tooltip, Button,
|
CircularProgress, Alert, Collapse, Tooltip, Button,
|
||||||
Card, CardContent, Chip, Dialog, DialogTitle,
|
Card, CardContent, Chip, Dialog, DialogTitle,
|
||||||
DialogContent, DialogActions, Snackbar
|
DialogContent, DialogActions, Snackbar, Table,
|
||||||
|
TableBody, TableCell, TableContainer, TableHead,
|
||||||
|
TableRow, Paper, Badge
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
import SearchIcon from '@mui/icons-material/Search';
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
import EditIcon from '@mui/icons-material/Edit';
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
import SaveIcon from '@mui/icons-material/Save';
|
import SaveIcon from '@mui/icons-material/Save';
|
||||||
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
|
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
const FormulaItem = React.memo(({ data, onEdit }) => {
|
const FormulaItem = React.memo(({ formula, onEdit }) => {
|
||||||
const formatValue = (value) => {
|
const getMetricStatusColor = (found) => {
|
||||||
if (typeof value === 'object' && value !== null) {
|
return found ? 'success' : 'error';
|
||||||
return JSON.stringify(value, null, 2);
|
|
||||||
}
|
|
||||||
return String(value);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getValueColor = (value) => {
|
const formatValue = (value) => {
|
||||||
if (typeof value === 'boolean') return 'primary';
|
if (value === undefined) return 'N/A';
|
||||||
if (typeof value === 'number') return 'secondary';
|
return value.toFixed(2);
|
||||||
if (value === null) return 'default';
|
|
||||||
return 'info';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card sx={{ mb: 2, border: '1px solid', borderColor: 'divider' }}>
|
<Card sx={{ mb: 2, border: '1px solid', borderColor: 'divider' }}>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
{/* Заголовок с ID и статусом метрик */}
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 2 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 2 }}>
|
||||||
|
<Box>
|
||||||
<Typography variant="h6" color="primary">
|
<Typography variant="h6" color="primary">
|
||||||
ID: {data.id || 'Без ID'}
|
{formula.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
ID: {formula.id}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<Badge
|
||||||
|
badgeContent={formula.metadata?.missingMetrics}
|
||||||
|
color="error"
|
||||||
|
sx={{ mr: 1 }}
|
||||||
|
>
|
||||||
|
<Chip
|
||||||
|
label={`${formula.metadata?.foundMetrics || 0}/${formula.metadata?.totalMetrics || 0} метрик`}
|
||||||
|
color={formula.metadata?.missingMetrics === 0 ? "success" : "warning"}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Badge>
|
||||||
<Button
|
<Button
|
||||||
startIcon={<EditIcon />}
|
startIcon={<EditIcon />}
|
||||||
onClick={() => onEdit(data)}
|
onClick={() => onEdit(formula)}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
Редактировать
|
Редактировать
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Typography variant="subtitle1" gutterBottom sx={{ fontWeight: 'bold' }}>
|
{/* Описание */}
|
||||||
{data.data.name}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Typography variant="body2" color="text.secondary" gutterBottom>
|
<Typography variant="body2" color="text.secondary" gutterBottom>
|
||||||
{data.data.desription}
|
{formula.description}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
|
{/* Метрики */}
|
||||||
|
<Box sx={{ mt: 2 }}>
|
||||||
|
<Typography variant="subtitle2" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
Метрики в формуле:
|
||||||
|
{formula.metadata?.missingMetrics > 0 && (
|
||||||
|
<WarningIcon color="warning" fontSize="small" />
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<TableContainer component={Paper} variant="outlined" sx={{ mb: 2 }}>
|
||||||
|
<Table size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Метрика</TableCell>
|
||||||
|
<TableCell>Описание</TableCell>
|
||||||
|
<TableCell align="right">Значение</TableCell>
|
||||||
|
<TableCell>Статус</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{formula.enrichedMetrics?.map((metric, index) => (
|
||||||
|
<TableRow key={index}>
|
||||||
|
<TableCell>
|
||||||
|
<Box>
|
||||||
|
<Typography variant="body2" fontWeight="bold">
|
||||||
|
{metric.originalName}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="caption" color="text.secondary">
|
||||||
|
{metric.prometheusName}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Typography variant="body2">
|
||||||
|
{metric.description}
|
||||||
|
</Typography>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="right">
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
fontWeight="bold"
|
||||||
|
color={metric.found ? 'text.primary' : 'text.disabled'}
|
||||||
|
>
|
||||||
|
{formatValue(metric.currentValue)}
|
||||||
|
</Typography>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Chip
|
||||||
|
icon={metric.found ? <CheckCircleIcon /> : <WarningIcon />}
|
||||||
|
label={metric.found ? 'Найдена' : 'Не найдена'}
|
||||||
|
color={getMetricStatusColor(metric.found)}
|
||||||
|
size="small"
|
||||||
|
variant={metric.found ? "filled" : "outlined"}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Формула */}
|
||||||
<Box sx={{ mt: 2 }}>
|
<Box sx={{ mt: 2 }}>
|
||||||
<Typography variant="subtitle2" gutterBottom>
|
<Typography variant="subtitle2" gutterBottom>
|
||||||
Параметры:
|
Формула с описанием метрик:
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box sx={{ mb: 2 }}>
|
<Box sx={{ mb: 2 }}>
|
||||||
<Chip label="statusarr" color="primary" sx={{ mb: 1 }} />
|
<Typography variant="body2" sx={{
|
||||||
<Typography variant="body2" component="pre" sx={{
|
|
||||||
whiteSpace: 'pre-wrap',
|
whiteSpace: 'pre-wrap',
|
||||||
wordBreak: 'break-word',
|
wordBreak: 'break-word',
|
||||||
fontFamily: 'monospace',
|
backgroundColor: 'primary.light',
|
||||||
fontSize: '0.75rem',
|
color: 'primary.contrastText',
|
||||||
backgroundColor: 'grey.100',
|
p: 2,
|
||||||
p: 1,
|
borderRadius: 1,
|
||||||
borderRadius: 1
|
fontSize: '0.9rem',
|
||||||
|
fontFamily: 'monospace'
|
||||||
}}>
|
}}>
|
||||||
{JSON.stringify(data.data.values?.statusarr, null, 2)}
|
{formula.humanReadableFormula}
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box sx={{ mb: 2 }}>
|
|
||||||
<Chip label="warr" color="secondary" sx={{ mb: 1 }} />
|
|
||||||
<Typography variant="body2" component="pre" sx={{
|
|
||||||
whiteSpace: 'pre-wrap',
|
|
||||||
wordBreak: 'break-word',
|
|
||||||
fontFamily: 'monospace',
|
|
||||||
fontSize: '0.75rem',
|
|
||||||
backgroundColor: 'grey.100',
|
|
||||||
p: 1,
|
|
||||||
borderRadius: 1
|
|
||||||
}}>
|
|
||||||
{JSON.stringify(data.data.values?.warr, null, 2)}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box sx={{ mb: 2 }}>
|
|
||||||
<Chip label="formula" color="success" sx={{ mb: 1 }} />
|
|
||||||
<Typography variant="body2" component="pre" sx={{
|
|
||||||
whiteSpace: 'pre-wrap',
|
|
||||||
wordBreak: 'break-word',
|
|
||||||
fontFamily: 'monospace',
|
|
||||||
fontSize: '0.75rem',
|
|
||||||
backgroundColor: 'success.light',
|
|
||||||
color: 'success.contrastText',
|
|
||||||
p: 1,
|
|
||||||
borderRadius: 1
|
|
||||||
}}>
|
|
||||||
{data.data.formula}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', gap: 1, mt: 2, flexWrap: 'wrap' }}>
|
{/* Веса */}
|
||||||
|
<Box sx={{ mt: 2 }}>
|
||||||
|
<Typography variant="subtitle2" gutterBottom>
|
||||||
|
Веса (warr):
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
||||||
|
{formula.values?.warr?.map((weight, index) => (
|
||||||
<Chip
|
<Chip
|
||||||
label={`Поля: ${Object.keys(data.data || {}).length}`}
|
key={index}
|
||||||
size="small"
|
label={`warr[${index + 1}]: ${weight}`}
|
||||||
variant="outlined"
|
|
||||||
/>
|
|
||||||
<Chip
|
|
||||||
label={`Тип: ${typeof data.data}`}
|
|
||||||
size="small"
|
size="small"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
/>
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -125,7 +176,7 @@ const EditFormulaDialog = ({ open, formula, onClose, onSave }) => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (formula) {
|
if (formula) {
|
||||||
setEditedFormula(formula.data.formula || '');
|
setEditedFormula(formula.formula || '');
|
||||||
}
|
}
|
||||||
}, [formula]);
|
}, [formula]);
|
||||||
|
|
||||||
|
|
@ -138,11 +189,11 @@ const EditFormulaDialog = ({ open, formula, onClose, onSave }) => {
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={onClose} maxWidth="md" fullWidth>
|
<Dialog open={open} onClose={onClose} maxWidth="md" fullWidth>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
Редактирование формулы: {formula?.data.name}
|
Редактирование формулы: {formula?.name}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Typography variant="body2" color="text.secondary" gutterBottom>
|
<Typography variant="body2" color="text.secondary" gutterBottom>
|
||||||
{formula?.data.desription}
|
{formula?.description}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box sx={{ mt: 2, mb: 2 }}>
|
<Box sx={{ mt: 2, mb: 2 }}>
|
||||||
|
|
@ -185,7 +236,6 @@ const EditFormulaDialog = ({ open, formula, onClose, onSave }) => {
|
||||||
const FormulaEditor = () => {
|
const FormulaEditor = () => {
|
||||||
const [formulas, setFormulas] = useState([]);
|
const [formulas, setFormulas] = useState([]);
|
||||||
const [filter, setFilter] = useState('');
|
const [filter, setFilter] = useState('');
|
||||||
const [formulaId, setFormulaId] = useState('');
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [refreshing, setRefreshing] = useState(false);
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
|
@ -197,46 +247,32 @@ const FormulaEditor = () => {
|
||||||
setSnackbar({ open: true, message, severity });
|
setSnackbar({ open: true, message, severity });
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadFormulas = useCallback(async (id = null) => {
|
const loadFormulas = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
const targetId = id || formulaId;
|
const response = await axios.get('http://192.168.2.39:3000/api/enriched-formulas');
|
||||||
const res = await axios.get(`http://192.168.2.39:3000/api/formula/7777/options`);
|
|
||||||
|
|
||||||
console.log('Полученные данные:', res.data);
|
if (Array.isArray(response.data)) {
|
||||||
|
setFormulas(response.data);
|
||||||
let formattedData;
|
showSnackbar(`Загружено ${response.data.length} формул`);
|
||||||
|
|
||||||
if (Array.isArray(res.data)) {
|
|
||||||
formattedData = res.data.map((item, index) => ({
|
|
||||||
id: item.id || `formula_${index + 1}`,
|
|
||||||
data: item
|
|
||||||
}));
|
|
||||||
} else if (typeof res.data === 'object' && res.data !== null) {
|
|
||||||
formattedData = [{
|
|
||||||
id: targetId,
|
|
||||||
data: res.data
|
|
||||||
}];
|
|
||||||
} else {
|
} else {
|
||||||
formattedData = [{
|
throw new Error('Некорректный формат данных');
|
||||||
id: targetId,
|
|
||||||
data: { value: res.data }
|
|
||||||
}];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Форматированные данные:', formattedData);
|
|
||||||
setFormulas(formattedData);
|
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Ошибка при загрузке формул:', err);
|
console.error('Ошибка при загрузке формул:', err);
|
||||||
setError(`Ошибка загрузки: ${err.message}`);
|
const errorMessage = axios.isAxiosError(err)
|
||||||
|
? `Ошибка сервера: ${err.response?.status} - ${err.response?.data?.message || err.message}`
|
||||||
|
: `Ошибка загрузки: ${err.message}`;
|
||||||
|
setError(errorMessage);
|
||||||
|
showSnackbar(errorMessage, 'error');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setRefreshing(false);
|
setRefreshing(false);
|
||||||
}
|
}
|
||||||
}, [formulaId]);
|
}, []);
|
||||||
|
|
||||||
const handleEditFormula = (formula) => {
|
const handleEditFormula = (formula) => {
|
||||||
setEditingFormula(formula);
|
setEditingFormula(formula);
|
||||||
|
|
@ -246,16 +282,17 @@ const FormulaEditor = () => {
|
||||||
try {
|
try {
|
||||||
setSaveLoading(true);
|
setSaveLoading(true);
|
||||||
|
|
||||||
// Обновляем формулу в локальном состоянии
|
await axios.post(`http://192.168.2.39:3000/api/formula/${formulaId}/update`, {
|
||||||
const updatedFormulas = formulas.map(formula =>
|
formula: newFormula
|
||||||
|
});
|
||||||
|
|
||||||
|
setFormulas(prev => prev.map(formula =>
|
||||||
formula.id === formulaId
|
formula.id === formulaId
|
||||||
? { ...formula, data: { ...formula.data, formula: newFormula } }
|
? { ...formula, formula: newFormula }
|
||||||
: formula
|
: formula
|
||||||
);
|
));
|
||||||
|
|
||||||
setFormulas(updatedFormulas);
|
|
||||||
setEditingFormula(null);
|
setEditingFormula(null);
|
||||||
|
|
||||||
showSnackbar('Формула успешно обновлена!');
|
showSnackbar('Формула успешно обновлена!');
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -266,65 +303,25 @@ const FormulaEditor = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSendAllFormulas = async () => {
|
|
||||||
try {
|
|
||||||
setSaveLoading(true);
|
|
||||||
|
|
||||||
// Преобразуем данные обратно в исходный формат для отправки
|
|
||||||
const dataToSend = formulas.map(formula => ({
|
|
||||||
id: formula.data.id,
|
|
||||||
name: formula.data.name,
|
|
||||||
desription: formula.data.desription,
|
|
||||||
values: formula.data.values,
|
|
||||||
formula: formula.data.formula
|
|
||||||
}));
|
|
||||||
|
|
||||||
console.log('Отправляемые данные:', dataToSend);
|
|
||||||
|
|
||||||
const response = await axios.post(
|
|
||||||
'http://192.168.2.39:9999/api/integration/3333',
|
|
||||||
dataToSend,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log('Ответ сервера:', response.data);
|
|
||||||
showSnackbar('Данные успешно отправлены на сервер!');
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Ошибка при отправке данных:', err);
|
|
||||||
showSnackbar(`Ошибка отправки: ${err.message}`, 'error');
|
|
||||||
} finally {
|
|
||||||
setSaveLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const refreshData = useCallback(() => {
|
const refreshData = useCallback(() => {
|
||||||
setRefreshing(true);
|
setRefreshing(true);
|
||||||
loadFormulas();
|
loadFormulas();
|
||||||
}, [loadFormulas]);
|
}, [loadFormulas]);
|
||||||
|
|
||||||
const handleFormulaIdChange = (e) => {
|
|
||||||
setFormulaId(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLoadClick = () => {
|
|
||||||
if (formulaId.trim()) {
|
|
||||||
loadFormulas(formulaId);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const filteredFormulas = formulas.filter(formula =>
|
const filteredFormulas = formulas.filter(formula =>
|
||||||
formula.id.toLowerCase().includes(filter.toLowerCase()) ||
|
formula.id.toLowerCase().includes(filter.toLowerCase()) ||
|
||||||
JSON.stringify(formula.data).toLowerCase().includes(filter.toLowerCase())
|
formula.name.toLowerCase().includes(filter.toLowerCase()) ||
|
||||||
|
formula.description.toLowerCase().includes(filter.toLowerCase()) ||
|
||||||
|
formula.formula.toLowerCase().includes(filter.toLowerCase())
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const totalMetrics = formulas.reduce((sum, formula) => sum + (formula.metadata?.totalMetrics || 0), 0);
|
||||||
|
const foundMetrics = formulas.reduce((sum, formula) => sum + (formula.metadata?.foundMetrics || 0), 0);
|
||||||
|
const missingMetrics = formulas.reduce((sum, formula) => sum + (formula.metadata?.missingMetrics || 0), 0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadFormulas();
|
loadFormulas();
|
||||||
}, []);
|
}, [loadFormulas]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ position: 'relative', p: 2 }}>
|
<Box sx={{ position: 'relative', p: 2 }}>
|
||||||
|
|
@ -353,55 +350,53 @@ const FormulaEditor = () => {
|
||||||
{/* Панель управления */}
|
{/* Панель управления */}
|
||||||
<Box sx={{ mb: 1 }}>
|
<Box sx={{ mb: 1 }}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
||||||
<Typography variant="h3" color="primary">
|
<Typography variant="h4" color="primary" fontWeight="bold">
|
||||||
Редактор формул
|
Редактор формул с метриками
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSendAllFormulas}
|
onClick={refreshData}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="success"
|
startIcon={<RefreshIcon />}
|
||||||
startIcon={<SaveIcon />}
|
disabled={refreshing}
|
||||||
disabled={formulas.length === 0 || saveLoading}
|
|
||||||
>
|
>
|
||||||
{saveLoading ? <CircularProgress size={24} /> : 'Отправить все формулы'}
|
Обновить
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', gap: 2, alignItems: 'flex-end', mb: 2 }}>
|
{/* Статистика */}
|
||||||
<TextField
|
<Box sx={{ display: 'flex', gap: 2, mb: 2, flexWrap: 'wrap' }}>
|
||||||
label="ID формулы"
|
|
||||||
value={formulaId}
|
|
||||||
onChange={handleFormulaIdChange}
|
|
||||||
variant="outlined"
|
|
||||||
placeholder="Например: 7777"
|
|
||||||
sx={{ minWidth: 200 }}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
onClick={handleLoadClick}
|
|
||||||
variant="contained"
|
|
||||||
startIcon={<RefreshIcon />}
|
|
||||||
disabled={!formulaId.trim() || loading}
|
|
||||||
>
|
|
||||||
Загрузить
|
|
||||||
</Button>
|
|
||||||
<Chip
|
<Chip
|
||||||
label={`Найдено: ${formulas.length}`}
|
label={`Формулы: ${formulas.length}`}
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
/>
|
/>
|
||||||
|
<Chip
|
||||||
|
label={`Метрики: ${foundMetrics}/${totalMetrics}`}
|
||||||
|
color={missingMetrics === 0 ? "success" : "warning"}
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
{missingMetrics > 0 && (
|
||||||
|
<Chip
|
||||||
|
label={`Отсутствуют: ${missingMetrics}`}
|
||||||
|
color="error"
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'flex-end', gap: 1 }}>
|
{/* Поиск */}
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'flex-end', gap: 1, mb: 2 }}>
|
||||||
<TextField
|
<TextField
|
||||||
label="Поиск по формулам"
|
label="Поиск по формулам"
|
||||||
fullWidth
|
fullWidth
|
||||||
value={filter}
|
value={filter}
|
||||||
onChange={(e) => setFilter(e.target.value)}
|
onChange={(e) => setFilter(e.target.value)}
|
||||||
variant="standard"
|
variant="outlined"
|
||||||
placeholder="Введите текст для поиска..."
|
placeholder="Введите ID, название или описание..."
|
||||||
|
size="small"
|
||||||
/>
|
/>
|
||||||
<SearchIcon sx={{ color: 'action.active', mb: 1 }} />
|
<SearchIcon sx={{ color: 'action.active', mb: 0.5 }} />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|
@ -409,10 +404,10 @@ const FormulaEditor = () => {
|
||||||
|
|
||||||
{/* Список формул */}
|
{/* Список формул */}
|
||||||
<Box sx={{ maxHeight: '70vh', overflowY: 'auto', pr: 1 }}>
|
<Box sx={{ maxHeight: '70vh', overflowY: 'auto', pr: 1 }}>
|
||||||
{filteredFormulas.map((formula, index) => (
|
{filteredFormulas.map((formula) => (
|
||||||
<FormulaItem
|
<FormulaItem
|
||||||
key={formula.id || index}
|
key={formula.id}
|
||||||
data={formula}
|
formula={formula}
|
||||||
onEdit={handleEditFormula}
|
onEdit={handleEditFormula}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
@ -424,7 +419,7 @@ const FormulaEditor = () => {
|
||||||
py={3}
|
py={3}
|
||||||
variant="h6"
|
variant="h6"
|
||||||
>
|
>
|
||||||
{filter ? 'Формулы не найдены' : 'Загрузите формулы по ID'}
|
{filter ? 'Формулы не найдены' : 'Нет загруженных формул'}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
@ -444,14 +439,9 @@ const FormulaEditor = () => {
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
Всего формул: {formulas.length} • Отфильтровано: {filteredFormulas.length}
|
Всего формул: {formulas.length} • Отфильтровано: {filteredFormulas.length}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button
|
<Typography variant="body2" color={missingMetrics === 0 ? "success.main" : "warning.main"}>
|
||||||
onClick={refreshData}
|
Метрики: {foundMetrics}/{totalMetrics} найдено
|
||||||
startIcon={<RefreshIcon />}
|
</Typography>
|
||||||
disabled={refreshing}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
Обновить
|
|
||||||
</Button>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Диалог редактирования */}
|
{/* Диалог редактирования */}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue