import React, { useEffect, useCallback, useState } from 'react'; import Image from 'next/image'; import useNavigationStore from '@/app/store/navigationStore'; import type { Zone } from '@/app/types'; // Безопасный резолвер src изображения, чтобы избежать ошибок Invalid URL в next/image const resolveImageSrc = (src?: string | null): string => { if (!src || typeof src !== 'string') return '/images/test_image.png'; let s = src.trim(); if (!s) return '/images/test_image.png'; s = s.replace(/\\/g, '/'); const lower = s.toLowerCase(); // Явный плейсхолдер test_image.png маппим на наш статический ресурс if (lower === 'test_image.png' || lower.endsWith('/test_image.png') || lower.includes('/public/images/test_image.png')) { return '/images/test_image.png'; } // Если путь содержит public/images (даже абсолютный путь ФС), переводим в относительный путь сайта if (/\/public\/images\//i.test(s)) { const parts = s.split(/\/public\/images\//i); const rel = parts[1] || ''; return `/images/${rel}`; } // Абсолютные URL и пути, относительные к сайту if (s.startsWith('http://') || s.startsWith('https://')) return s; if (s.startsWith('/')) return s; // Нормализуем относительные имена ресурсов до путей сайта под /images // Убираем ведущий 'public/', если он присутствует s = s.replace(/^public\//i, ''); return s.startsWith('images/') ? `/${s}` : `/images/${s}`; } interface MonitoringProps { onClose?: () => void; onSelectModel?: (modelPath: string) => void; } const Monitoring: React.FC = ({ onClose, onSelectModel }) => { const { currentObject, currentZones, zonesLoading, zonesError, loadZones, currentModelPath } = useNavigationStore(); const [autoSelectedRef, setAutoSelectedRef] = React.useState(false); const handleSelectModel = useCallback((modelPath: string) => { console.log(`[Monitoring] Model selected: ${modelPath}`); console.log(`[Monitoring] onSelectModel callback:`, onSelectModel); onSelectModel?.(modelPath); // Автоматически закрываем панель после выбора модели if (onClose) { setTimeout(() => { console.log('[Monitoring] Auto-closing after model selection'); onClose(); }, 100); } }, [onSelectModel, onClose]); // Загрузка зон при изменении объекта useEffect(() => { const objId = currentObject?.id; if (!objId) return; console.log(`[Monitoring] Loading zones for object ID: ${objId}`); loadZones(objId); }, [currentObject?.id, loadZones]); // Сброс флага при изменении объекта useEffect(() => { setAutoSelectedRef(false); }, [currentObject?.id]) // Сортировка зон по order const sortedZones: Zone[] = React.useMemo(() => { const sorted = (currentZones || []).slice().sort((a: Zone, b: Zone) => { const oa = typeof a.order === 'number' ? a.order : 0; const ob = typeof b.order === 'number' ? b.order : 0; if (oa !== ob) return oa - ob; return (a.name || '').localeCompare(b.name || ''); }); console.log(`[Monitoring] Sorted zones:`, sorted.map(z => ({ id: z.id, name: z.name, model_path: z.model_path }))); return sorted; }, [currentZones]); // Автоматический выбор модели с order=0 при загрузке зон useEffect(() => { // Если уже был автовыбор - пропускаем if (autoSelectedRef || !onSelectModel) return; // Если есть зоны и первая зона (order=0) имеет model_path if (sortedZones.length > 0 && sortedZones[0]?.model_path) { console.log('[Monitoring] Auto-selecting model with order=0:', sortedZones[0].model_path); setAutoSelectedRef(true); onSelectModel(sortedZones[0].model_path); } }, [sortedZones, autoSelectedRef, onSelectModel]) return (

Зоны мониторинга

{onClose && ( )}
{/* UI зон */} {zonesError && (
Ошибка загрузки зон: {zonesError}
)} {zonesLoading && (
Загрузка зон...
)} {sortedZones.length > 0 && ( <> {sortedZones[0] && ( )} {sortedZones.length > 1 && (
{sortedZones.slice(1).map((zone: Zone, idx: number) => ( ))}
)} )} {sortedZones.length === 0 && !zonesError && !zonesLoading && (
Зоны не найдены для выбранного объекта. Проверьте параметр objectId в API /api/get-zones.
)}
); }; export default Monitoring;