diff --git a/src/app/router.tsx b/src/app/router.tsx index 419a2c5..0878910 100644 --- a/src/app/router.tsx +++ b/src/app/router.tsx @@ -1,16 +1,13 @@ import { BrowserRouter, Navigate, Route, Routes, useNavigate } from 'react-router-dom'; +import { EdgeCurrentPage } from '../features/edge-detail/EdgeCurrentPage'; import { EdgeDetailPage } from '../features/edge-detail/EdgeDetailPage'; +import { EdgeHistoryPage } from '../features/edge-detail/EdgeHistoryPage'; import { EdgesDashboard } from '../features/edges-dashboard/EdgesDashboard'; function DashboardRoute() { const navigate = useNavigate(); - return ( - navigate(`/edges/${encodeURIComponent(edgeId)}`)} - onOpenEquipment={(edgeId) => navigate(`/edges/${encodeURIComponent(edgeId)}/equipment`)} - /> - ); + return navigate(`/edges/${encodeURIComponent(edgeId)}`)} />; } export function AppRouter() { @@ -19,12 +16,9 @@ export function AppRouter() { } /> } /> - } /> - } /> - } /> - } /> - {/* Электросхемы временно скрыты до готовности опубликованных схем и diagram-service. */} - {/* } /> */} + } /> + } /> + } /> } /> diff --git a/src/features/current/useCurrentQuery.ts b/src/features/current/useCurrentQuery.ts deleted file mode 100644 index bb13c02..0000000 --- a/src/features/current/useCurrentQuery.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { getCurrent } from '../../entities/current/api'; - -export function useCurrentQuery(edgeId: string, eventsConnected: boolean) { - return useQuery({ - queryKey: ['current', edgeId], - queryFn: () => getCurrent(edgeId), - enabled: Boolean(edgeId), - refetchInterval: eventsConnected ? false : 1_000, - }); -} diff --git a/src/features/edge-detail/EdgeCurrentPage.tsx b/src/features/edge-detail/EdgeCurrentPage.tsx new file mode 100644 index 0000000..d095795 --- /dev/null +++ b/src/features/edge-detail/EdgeCurrentPage.tsx @@ -0,0 +1,49 @@ +import { useMemo, useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { useParams } from 'react-router-dom'; +import { getCurrent } from '../../entities/current/api'; +import { createCurrentTagLabels, filterCurrentItems } from '../current/model'; +import { useCurrentEvents } from '../current/useCurrentEvents'; +import { EdgePageLayout } from './components/EdgePageLayout'; +import { IndicatorsView } from './components/IndicatorsView'; + +export function EdgeCurrentPage() { + const { edgeId = '' } = useParams(); + const [search, setSearch] = useState(''); + const [selectedTags, setSelectedTags] = useState([]); + const currentEventsConnected = useCurrentEvents(edgeId); + const current = useQuery({ + queryKey: ['current', edgeId], + queryFn: () => getCurrent(edgeId), + enabled: Boolean(edgeId), + refetchInterval: currentEventsConnected ? false : 1_000, + }); + const currentItems = useMemo(() => current.data?.items ?? [], [current.data?.items]); + const tagLabels = useMemo(() => createCurrentTagLabels(currentItems), [currentItems]); + const getTagLabel = (tag: string) => tagLabels[tag] ?? tag; + const visibleItems = useMemo(() => filterCurrentItems(currentItems, search), [currentItems, search]); + + const toggleTag = (tag: string) => { + setSelectedTags((prev) => (prev.includes(tag) ? prev.filter((selected) => selected !== tag) : [...prev, tag])); + }; + + return ( + void current.refetch()} + > + + + ); +} diff --git a/src/features/edge-detail/EdgeDetailPage.tsx b/src/features/edge-detail/EdgeDetailPage.tsx index 7a156f6..3d15514 100644 --- a/src/features/edge-detail/EdgeDetailPage.tsx +++ b/src/features/edge-detail/EdgeDetailPage.tsx @@ -1,137 +1,45 @@ -import { useMemo, useState } from 'react'; +import { useMemo } from 'react'; +import { useQuery } from '@tanstack/react-query'; import { useNavigate, useParams } from 'react-router-dom'; -import { useAuth } from '../../auth/authContext'; +import { getCurrent } from '../../entities/current/api'; import { countLiveCurrentItems, - createCurrentTagLabels, - filterCurrentItems, getLatestCurrentUpdatedAt, } from '../current/model'; import { useCurrentEvents } from '../current/useCurrentEvents'; -import { useCurrentQuery } from '../current/useCurrentQuery'; -import { createRange } from '../history/dateRange'; -import { useHistoryQuery } from '../history/useHistoryQuery'; -import { ArchiveView } from './components/ArchiveView'; -import { EdgeSidebar } from './components/EdgeSidebar'; -import { EdgeTopbar } from './components/EdgeTopbar'; -import { EquipmentView } from './components/EquipmentView'; -import { IndicatorsView } from './components/IndicatorsView'; +import { EdgePageLayout } from './components/EdgePageLayout'; import { OverviewView } from './components/OverviewView'; -import type { DetailView } from './types'; -type EdgeDetailPageProps = { - view: DetailView; -}; - -/** Собирает раздел выбранной буровой из вкладок текущих данных, архива и оборудования. */ -export function EdgeDetailPage({ view }: EdgeDetailPageProps) { +export function EdgeDetailPage() { const navigate = useNavigate(); - const auth = useAuth(); const { edgeId = '' } = useParams(); - const [sidebarCollapsed, setSidebarCollapsed] = useState(false); - const [search, setSearch] = useState(''); - const [range, setRange] = useState(() => createRange(24)); - const [selectedTags, setSelectedTags] = useState([]); - const edgePath = `/edges/${encodeURIComponent(edgeId)}`; const currentEventsConnected = useCurrentEvents(edgeId); - const current = useCurrentQuery(edgeId, currentEventsConnected); + const current = useQuery({ + queryKey: ['current', edgeId], + queryFn: () => getCurrent(edgeId), + enabled: Boolean(edgeId), + refetchInterval: currentEventsConnected ? false : 1_000, + }); const currentItems = useMemo(() => current.data?.items ?? [], [current.data?.items]); - - const tagLabels = useMemo(() => createCurrentTagLabels(currentItems), [currentItems]); - const getTagLabel = (tag: string) => tagLabels[tag] ?? tag; - const visibleItems = useMemo(() => filterCurrentItems(currentItems, search), [currentItems, search]); const latestUpdatedAt = useMemo(() => getLatestCurrentUpdatedAt(currentItems), [currentItems]); const liveCount = countLiveCurrentItems(currentItems); - const { query: history, granularity: historyGranularity } = useHistoryQuery(edgeId, selectedTags[0], range); - - const toggleTag = (tag: string) => { - setSelectedTags((prev) => (prev.includes(tag) ? [] : [tag])); - }; - - const selectFirstTags = () => { - setSelectedTags(visibleItems[0] ? [visibleItems[0].tag] : []); - }; - - const selectVisibleTags = () => { - setSelectedTags(visibleItems[0] ? [visibleItems[0].tag] : []); - }; - - const clearVisibleTags = () => { - const visibleTags = new Set(visibleItems.map((item) => item.tag)); - setSelectedTags((prev) => prev.filter((tag) => !visibleTags.has(tag))); - }; return ( -
- setSidebarCollapsed((collapsed) => !collapsed)} + void current.refetch()} + > + navigate(`${edgePath}/archive`)} + onOpenIndicators={() => navigate(`${edgePath}/indicators`)} /> - -
- navigate('/edges')} - onLogout={() => void auth.logout()} - onRefresh={() => void current.refetch()} - /> - - {view === 'overview' ? ( - navigate(`${edgePath}/archive`)} - onOpenIndicators={() => navigate(`${edgePath}/indicators`)} - onOpenEquipment={() => navigate(`${edgePath}/equipment`)} - /> - ) : null} - - {view === 'archive' ? ( - 0} - historyGranulate={history.data?.granulate ?? historyGranularity.granulate} - historyAxis={historyGranularity} - range={range} - onSearchChange={setSearch} - onRangeChange={setRange} - onToggleTag={toggleTag} - onSelectFirstTags={selectFirstTags} - onSelectVisibleTags={selectVisibleTags} - onClearVisibleTags={clearVisibleTags} - onClearTags={() => setSelectedTags([])} - getTagLabel={getTagLabel} - tagLabels={tagLabels} - /> - ) : null} - - {view === 'indicators' ? ( - - ) : null} - - {view === 'equipment' ? : null} -
-
+ ); } diff --git a/src/features/edge-detail/EdgeHistoryPage.tsx b/src/features/edge-detail/EdgeHistoryPage.tsx new file mode 100644 index 0000000..4c79391 --- /dev/null +++ b/src/features/edge-detail/EdgeHistoryPage.tsx @@ -0,0 +1,96 @@ +import { useMemo, useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { useParams } from 'react-router-dom'; +import { getCurrent } from '../../entities/current/api'; +import { getHistory } from '../../entities/history/api'; +import { toIsoFromInput } from '../../utils/format'; +import { getHistoryGranularity } from '../../utils/historyGranularity'; +import { createCurrentTagLabels, filterCurrentItems } from '../current/model'; +import { useCurrentEvents } from '../current/useCurrentEvents'; +import { createRange } from '../history/dateRange'; +import { ArchiveView } from './components/ArchiveView'; +import { EdgePageLayout } from './components/EdgePageLayout'; + +export function EdgeHistoryPage() { + const { edgeId = '' } = useParams(); + const [search, setSearch] = useState(''); + const [range, setRange] = useState(() => createRange(24)); + const [selectedTags, setSelectedTags] = useState([]); + const currentEventsConnected = useCurrentEvents(edgeId); + const current = useQuery({ + queryKey: ['current', edgeId], + queryFn: () => getCurrent(edgeId), + enabled: Boolean(edgeId), + refetchInterval: currentEventsConnected ? false : 1_000, + }); + const currentItems = useMemo(() => current.data?.items ?? [], [current.data?.items]); + const tagLabels = useMemo(() => createCurrentTagLabels(currentItems), [currentItems]); + const getTagLabel = (tag: string) => tagLabels[tag] ?? tag; + const visibleItems = useMemo(() => filterCurrentItems(currentItems, search), [currentItems, search]); + const from = toIsoFromInput(range.from) as string; + const to = toIsoFromInput(range.to) as string; + const historyGranularity = useMemo(() => getHistoryGranularity(from, to), [from, to]); + const selectedTag = selectedTags[0]; + const history = useQuery({ + queryKey: ['history', edgeId, selectedTag, from, to, historyGranularity.granulate], + queryFn: () => + getHistory({ + edge: edgeId, + tag: selectedTag as string, + from, + to, + granulate: historyGranularity.granulate, + }), + enabled: Boolean(edgeId && selectedTag), + refetchInterval: false, + }); + + const toggleTag = (tag: string) => { + setSelectedTags((prev) => (prev.includes(tag) ? [] : [tag])); + }; + + const selectFirstTags = () => { + setSelectedTags(visibleItems[0] ? [visibleItems[0].tag] : []); + }; + + const selectVisibleTags = () => { + setSelectedTags(visibleItems[0] ? [visibleItems[0].tag] : []); + }; + + const clearVisibleTags = () => { + const visibleTags = new Set(visibleItems.map((item) => item.tag)); + setSelectedTags((prev) => prev.filter((tag) => !visibleTags.has(tag))); + }; + + return ( + { + void current.refetch(); + void history.refetch(); + }} + > + 0} + historyGranulate={history.data?.granulate ?? historyGranularity.granulate} + historyAxis={historyGranularity} + range={range} + onSearchChange={setSearch} + onRangeChange={setRange} + onToggleTag={toggleTag} + onSelectFirstTags={selectFirstTags} + onSelectVisibleTags={selectVisibleTags} + onClearVisibleTags={clearVisibleTags} + onClearTags={() => setSelectedTags([])} + getTagLabel={getTagLabel} + tagLabels={tagLabels} + /> + + ); +} diff --git a/src/features/edge-detail/components/EdgePageLayout.tsx b/src/features/edge-detail/components/EdgePageLayout.tsx new file mode 100644 index 0000000..dd3ef1d --- /dev/null +++ b/src/features/edge-detail/components/EdgePageLayout.tsx @@ -0,0 +1,53 @@ +import type { ReactNode } from 'react'; +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useAuth } from '../../../auth/authContext'; +import { EdgeSidebar } from './EdgeSidebar'; +import { EdgeTopbar } from './EdgeTopbar'; +import type { DetailView } from '../types'; + +type EdgePageLayoutProps = { + children: ReactNode; + currentEventsConnected: boolean; + edgeId: string; + onRefresh: () => void; + view: DetailView; +}; + +export function EdgePageLayout({ + children, + currentEventsConnected, + edgeId, + onRefresh, + view, +}: EdgePageLayoutProps) { + const navigate = useNavigate(); + const auth = useAuth(); + const [sidebarCollapsed, setSidebarCollapsed] = useState(false); + const edgePath = `/edges/${encodeURIComponent(edgeId)}`; + + return ( +
+ setSidebarCollapsed((collapsed) => !collapsed)} + /> + +
+ navigate('/edges')} + onLogout={() => void auth.logout()} + onRefresh={onRefresh} + /> + + {children} +
+
+ ); +} diff --git a/src/features/edge-detail/components/EdgeSidebar.tsx b/src/features/edge-detail/components/EdgeSidebar.tsx index c529085..c7d7b7f 100644 --- a/src/features/edge-detail/components/EdgeSidebar.tsx +++ b/src/features/edge-detail/components/EdgeSidebar.tsx @@ -1,4 +1,4 @@ -import { Activity, BarChart3, Gauge, Menu, PanelLeftClose, PanelLeftOpen, Wrench } from 'lucide-react'; +import { Activity, BarChart3, Gauge, Menu, PanelLeftClose, PanelLeftOpen } from 'lucide-react'; import type { DetailView } from '../types'; type EdgeSidebarProps = { @@ -56,14 +56,6 @@ export function EdgeSidebar({ collapsed, edgePath, view, onNavigate, onToggleCol Показатели - ); diff --git a/src/features/edge-detail/components/EquipmentView.tsx b/src/features/edge-detail/components/EquipmentView.tsx deleted file mode 100644 index 862cd20..0000000 --- a/src/features/edge-detail/components/EquipmentView.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { useState } from 'react'; -import { ToirLightIframe } from '../../../components/ToirLightIframe'; - -const ACTIVE_EQUIPMENT_PATH = - '/equipment?filter=%7B%22status%22%3A%5B%22Active%22%5D%7D&displayedFilters=%7B%22status%22%3Atrue%7D'; - -export function EquipmentView() { - const [iframeLoaded, setIframeLoaded] = useState(false); - - return ( -
-
- {!iframeLoaded ? ( -
- - Загрузка интерфейса управления оборудованием... -
- ) : null} - - setIframeLoaded(true)} - /> -
-
- ); -} diff --git a/src/features/edge-detail/components/OverviewView.tsx b/src/features/edge-detail/components/OverviewView.tsx index 7ca83aa..6627dbd 100644 --- a/src/features/edge-detail/components/OverviewView.tsx +++ b/src/features/edge-detail/components/OverviewView.tsx @@ -1,14 +1,12 @@ -import { Activity, BarChart3, CalendarClock, Wrench } from 'lucide-react'; +import { Activity, BarChart3, CalendarClock } from 'lucide-react'; import { formatDateTime } from '../../../utils/format'; type OverviewViewProps = { edgeId: string; latestUpdatedAt?: Date; liveCount: number; - selectedCount: number; totalTags: number; onOpenArchive: () => void; - onOpenEquipment: () => void; onOpenIndicators: () => void; }; @@ -16,10 +14,8 @@ export function OverviewView({ edgeId, latestUpdatedAt, liveCount, - selectedCount, totalTags, onOpenArchive, - onOpenEquipment, onOpenIndicators, }: OverviewViewProps) { return ( @@ -34,8 +30,8 @@ export function OverviewView({ {liveCount}
- Выбрано для графика - {selectedCount} + Архив + 1
Последнее обновление @@ -57,10 +53,6 @@ export function OverviewView({ Архив и график -