AEB-71: Added 3D navigation in monitoring zones

This commit is contained in:
iv_vuytsik
2025-11-11 10:07:38 +03:00
parent 549a05509b
commit 88653cb07c
27 changed files with 503 additions and 184 deletions

View File

@@ -7,28 +7,49 @@ import useNavigationStore from '../../app/store/navigationStore'
import ChartCard from './ChartCard'
import AreaChart from './AreaChart'
import BarChart from './BarChart'
import DetectorChart from './DetectorChart'
const Dashboard: React.FC = () => {
const router = useRouter()
const { currentObject, setCurrentSubmenu, closeMonitoring, closeFloorNavigation, closeNotifications } = useNavigationStore()
const objectId = currentObject?.id
const objectTitle = currentObject?.title
const [dashboardAlerts, setDashboardAlerts] = useState<any[]>([])
const [chartData, setChartData] = useState<{ timestamp: string; value: number }[]>([])
const [sensorTypes] = useState<Array<{code: string, name: string}>>([
{ code: '', name: 'Все датчики' },
{ code: 'GA', name: 'Инклинометр' },
{ code: 'PE', name: 'Танзометр' },
{ code: 'GLE', name: 'Гидроуровень' }
])
const [selectedSensorType, setSelectedSensorType] = useState<string>('')
const [selectedChartPeriod, setSelectedChartPeriod] = useState<string>('168')
const [selectedTablePeriod, setSelectedTablePeriod] = useState<string>('168')
useEffect(() => {
const loadDashboard = async () => {
try {
const res = await fetch('/api/get-dashboard', { cache: 'no-store' })
const params = new URLSearchParams()
params.append('time_period', selectedChartPeriod)
const res = await fetch(`/api/get-dashboard?${params.toString()}`, { cache: 'no-store' })
if (!res.ok) return
const payload = await res.json()
console.log('[Dashboard] GET /api/get-dashboard', { status: res.status, payload })
const tableData = payload?.data?.table_data ?? []
const arr = (Array.isArray(tableData) ? tableData : [])
.filter((a: any) => (objectTitle ? a.object === objectTitle : true))
setDashboardAlerts(arr as any[])
let tableData = payload?.data?.table_data ?? []
tableData = Array.isArray(tableData) ? tableData : []
if (objectTitle) {
tableData = tableData.filter((a: any) => a.object === objectTitle)
}
if (selectedSensorType && selectedSensorType !== '') {
tableData = tableData.filter((a: any) => {
return a.detector_type?.toLowerCase() === selectedSensorType.toLowerCase()
})
}
setDashboardAlerts(tableData as any[])
const cd = Array.isArray(payload?.data?.chart_data) ? payload.data.chart_data : []
setChartData(cd as any[])
@@ -37,14 +58,52 @@ const Dashboard: React.FC = () => {
}
}
loadDashboard()
}, [objectTitle])
}, [objectTitle, selectedChartPeriod, selectedSensorType])
// Separate effect for table data based on table period
useEffect(() => {
const loadTableData = async () => {
try {
const params = new URLSearchParams()
params.append('time_period', selectedTablePeriod)
const res = await fetch(`/api/get-dashboard?${params.toString()}`, { cache: 'no-store' })
if (!res.ok) return
const payload = await res.json()
console.log('[Dashboard] GET /api/get-dashboard (table)', { status: res.status, payload })
let tableData = payload?.data?.table_data ?? []
tableData = Array.isArray(tableData) ? tableData : []
if (objectTitle) {
tableData = tableData.filter((a: any) => a.object === objectTitle)
}
if (selectedSensorType && selectedSensorType !== '') {
tableData = tableData.filter((a: any) => {
return a.detector_type?.toLowerCase() === selectedSensorType.toLowerCase()
})
}
setDashboardAlerts(tableData as any[])
} catch (e) {
console.error('Failed to load table data:', e)
}
}
loadTableData()
}, [objectTitle, selectedTablePeriod, selectedSensorType])
const handleBackClick = () => {
router.push('/objects')
}
const filteredAlerts = dashboardAlerts.filter((alert: any) => {
if (selectedSensorType === '') return true
return alert.detector_type?.toLowerCase() === selectedSensorType.toLowerCase()
})
// Статусы
const statusCounts = dashboardAlerts.reduce((acc: { critical: number; warning: number; normal: number }, a: any) => {
const statusCounts = filteredAlerts.reduce((acc: { critical: number; warning: number; normal: number }, a: any) => {
if (a.severity === 'critical') acc.critical++
else if (a.severity === 'warning') acc.warning++
else acc.normal++
@@ -58,6 +117,18 @@ const Dashboard: React.FC = () => {
setCurrentSubmenu(null)
router.push('/navigation')
}
const handleSensorTypeChange = (sensorType: string) => {
setSelectedSensorType(sensorType)
}
const handleChartPeriodChange = (period: string) => {
setSelectedChartPeriod(period)
}
const handleTablePeriodChange = (period: string) => {
setSelectedTablePeriod(period)
}
return (
<div className="flex h-screen bg-[#0e111a]">
@@ -87,17 +158,25 @@ const Dashboard: React.FC = () => {
<div className="flex-1 p-6 overflow-auto">
<div className="mb-6">
<h1 className="text-white text-2xl font-semibold mb-6">Объект {objectId?.replace('object_', '')}</h1>
<h1 className="text-white text-2xl font-semibold mb-6">{objectTitle || 'Объект'}</h1>
<div className="flex items-center gap-3 mb-6">
<button
className="flex items-center gap-6 rounded-[10px] px-4 py-[18px] bg-[rgb(22,24,36)] text-white"
>
<span className="text-sm font-medium">Датчики : все</span>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div className="relative">
<select
value={selectedSensorType}
onChange={(e) => handleSensorTypeChange(e.target.value)}
className="flex items-center gap-6 rounded-[10px] px-4 py-[18px] bg-[rgb(22,24,36)] text-white appearance-none pr-8"
>
{sensorTypes.map((type) => (
<option key={type.code} value={type.code}>
{type.name}
</option>
))}
</select>
<svg className="w-4 h-4 absolute right-3 top-1/2 transform -translate-y-1/2 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
</div>
<div className="flex items-center gap-3 ml-auto">
<button
@@ -107,12 +186,20 @@ const Dashboard: React.FC = () => {
<span className="text-sm font-medium">Навигация</span>
</button>
<div className="flex items-center gap-2 bg-[rgb(22,24,36)] rounded-lg px-3 py-2">
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
<div className="relative">
<select
value={selectedChartPeriod}
onChange={(e) => handleChartPeriodChange(e.target.value)}
className="flex items-center gap-2 bg-[rgb(22,24,36)] rounded-lg px-3 py-2 text-white appearance-none pr-8"
>
<option value="24">День</option>
<option value="72">3 дня</option>
<option value="168">Неделя</option>
<option value="720">Месяц</option>
</select>
<svg className="w-4 h-4 absolute right-3 top-1/2 transform -translate-y-1/2 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
<span className="text-white text-sm font-medium">Период</span>
<div className="w-2 h-2 bg-white rounded-full"></div>
</div>
</div>
</div>
@@ -121,14 +208,14 @@ const Dashboard: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-2 gap-[18px]">
<ChartCard
title="Показатель"
subtitle="За последние 6 месяцев"
// subtitle removed
>
<AreaChart data={chartData} />
</ChartCard>
<ChartCard
title="Статистика"
subtitle="Данные за период"
// subtitle removed
>
<BarChart data={chartData?.map((d: any) => ({ value: d.value }))} />
</ChartCard>
@@ -140,12 +227,18 @@ const Dashboard: React.FC = () => {
<div>
<div className="flex items-center justify-between mb-6">
<h2 className="text-white text-2xl font-semibold">Тренды</h2>
<div className="bg-[#161824] rounded-lg px-3 py-2 flex items-center gap-2">
<svg className="w-4 h-4 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z" clipRule="evenodd" />
</svg>
<span className="text-white text-sm font-medium">Месяц</span>
<svg className="w-4 h-4 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
<div className="relative">
<select
value={selectedTablePeriod}
onChange={(e) => handleTablePeriodChange(e.target.value)}
className="bg-[#161824] rounded-lg px-3 py-2 flex items-center gap-2 text-white appearance-none pr-8"
>
<option value="24">День</option>
<option value="72">3 дня</option>
<option value="168">Неделя</option>
<option value="720">Месяц</option>
</select>
<svg className="w-4 h-4 absolute right-3 top-1/2 transform -translate-y-1/2 pointer-events-none" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" />
</svg>
</div>
@@ -165,7 +258,7 @@ const Dashboard: React.FC = () => {
</tr>
</thead>
<tbody>
{dashboardAlerts.map((alert: any) => (
{filteredAlerts.map((alert: any) => (
<tr key={alert.id} className="border-b border-gray-800">
<td className="py-3 text-white text-sm">{alert.name}</td>
<td className="py-3 text-gray-300 text-sm">{alert.message}</td>
@@ -191,7 +284,7 @@ const Dashboard: React.FC = () => {
{/* Статы */}
<div className="mt-6 grid grid-cols-4 gap-4">
<div className="text-center">
<div className="text-2xl font-bold text-white">{dashboardAlerts.length}</div>
<div className="text-2xl font-bold text-white">{filteredAlerts.length}</div>
<div className="text-sm text-gray-400">Всего</div>
</div>
<div className="text-center">
@@ -208,37 +301,6 @@ const Dashboard: React.FC = () => {
</div>
</div>
</div>
{/* Графики с аналитикой */}
<div className="mt-6 grid grid-cols-1 lg:grid-cols-4 gap-[18px]">
<ChartCard
title="Тренды детекторов"
subtitle="За последний месяц"
>
<DetectorChart type="line" data={chartData?.map((d: any) => ({ value: d.value }))} />
</ChartCard>
<ChartCard
title="Статистика по месяцам"
subtitle="Активность детекторов"
>
<DetectorChart type="bar" data={chartData?.map((d: any) => ({ value: d.value }))} />
</ChartCard>
<ChartCard
title="Анализ производительности"
subtitle="Эффективность работы"
>
<DetectorChart type="line" data={chartData?.map((d: any) => ({ value: d.value }))} />
</ChartCard>
<ChartCard
title="Сводка по статусам"
subtitle="Распределение состояний"
>
<DetectorChart type="bar" data={chartData?.map((d: any) => ({ value: d.value }))} />
</ChartCard>
</div>
</div>
</div>
</div>