Linked backend data to detectors' meshes.

This commit is contained in:
iv_vuytsik
2025-10-20 11:02:34 +03:00
parent aeda917001
commit 66e2bab683
15 changed files with 546 additions and 156 deletions

View File

@@ -13,33 +13,51 @@ interface AreaChartProps {
data?: ChartDataPoint[]
}
const AreaChart: React.FC<AreaChartProps> = ({ className = '' }) => {
const AreaChart: React.FC<AreaChartProps> = ({ className = '', data }) => {
const width = 635
const height = 200
const paddingBottom = 20
const baselineY = height - paddingBottom
const maxPlotHeight = height - 40
const safeData = (Array.isArray(data) && data.length > 0)
? data
: [
{ value: 5 },
{ value: 3 },
{ value: 7 },
{ value: 2 },
{ value: 6 },
{ value: 4 },
{ value: 8 }
]
const maxVal = Math.max(...safeData.map(d => d.value || 0), 1)
const stepX = safeData.length > 1 ? width / (safeData.length - 1) : width
const points = safeData.map((d, i) => {
const x = i * stepX
const y = baselineY - (Math.min(d.value || 0, maxVal) / maxVal) * maxPlotHeight
return { x, y }
})
const linePath = points.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x},${p.y}`).join(' ')
const areaPath = `${linePath} L${width},${baselineY} L0,${baselineY} Z`
return (
<div className={`w-full h-full ${className}`}>
<svg className="w-full h-full" viewBox="0 0 635 200">
<svg className="w-full h-full" viewBox={`0 0 ${width} ${height}`}>
<defs>
<linearGradient id="areaGradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="rgb(42, 157, 144)" stopOpacity="0.3" />
<stop offset="100%" stopColor="rgb(42, 157, 144)" stopOpacity="0" />
</linearGradient>
</defs>
<path
d="M0,180 L100,120 L200,140 L300,80 L400,60 L500,100 L635,90 L635,200 L0,200 Z"
fill="url(#areaGradient)"
/>
<path
d="M0,180 L100,120 L200,140 L300,80 L400,60 L500,100 L635,90"
stroke="rgb(42, 157, 144)"
strokeWidth="2"
fill="none"
/>
<circle cx="0" cy="180" r="3" fill="rgb(42, 157, 144)" />
<circle cx="100" cy="120" r="3" fill="rgb(42, 157, 144)" />
<circle cx="200" cy="140" r="3" fill="rgb(42, 157, 144)" />
<circle cx="300" cy="80" r="3" fill="rgb(42, 157, 144)" />
<circle cx="400" cy="60" r="3" fill="rgb(42, 157, 144)" />
<circle cx="500" cy="100" r="3" fill="rgb(42, 157, 144)" />
<circle cx="635" cy="90" r="3" fill="rgb(42, 157, 144)" />
<path d={areaPath} fill="url(#areaGradient)" />
<path d={linePath} stroke="rgb(42, 157, 144)" strokeWidth="2" fill="none" />
{points.map((p, i) => (
<circle key={i} cx={p.x} cy={p.y} r="3" fill="rgb(42, 157, 144)" />
))}
</svg>
</div>
)

View File

@@ -13,8 +13,8 @@ interface BarChartProps {
data?: ChartDataPoint[]
}
const BarChart: React.FC<BarChartProps> = ({ className = '' }) => {
const barData = [
const BarChart: React.FC<BarChartProps> = ({ className = '', data }) => {
const defaultData = [
{ value: 80, color: 'rgb(42, 157, 144)' },
{ value: 65, color: 'rgb(42, 157, 144)' },
{ value: 90, color: 'rgb(42, 157, 144)' },
@@ -29,6 +29,12 @@ const BarChart: React.FC<BarChartProps> = ({ className = '' }) => {
{ value: 80, color: 'rgb(42, 157, 144)' }
]
const barData = (Array.isArray(data) && data.length > 0)
? data.map(d => ({ value: d.value, color: d.color || 'rgb(42, 157, 144)' }))
: defaultData
const maxVal = Math.max(...barData.map(b => b.value || 0), 1)
return (
<div className={`w-full h-full ${className}`}>
<svg className="w-full h-full" viewBox="0 0 635 200">
@@ -37,7 +43,7 @@ const BarChart: React.FC<BarChartProps> = ({ className = '' }) => {
const barWidth = 40
const barSpacing = 12
const x = index * (barWidth + barSpacing) + 20
const barHeight = (bar.value / 100) * 160
const barHeight = (bar.value / maxVal) * 160
const y = 180 - barHeight
return (

View File

@@ -15,55 +15,39 @@ const Dashboard: React.FC = () => {
const objectId = currentObject?.id
const objectTitle = currentObject?.title
const [detectorsArray, setDetectorsArray] = useState<any[]>([])
const [dashboardAlerts, setDashboardAlerts] = useState<any[]>([])
const [chartData, setChartData] = useState<{ timestamp: string; value: number }[]>([])
useEffect(() => {
const loadDetectors = async () => {
const loadDashboard = async () => {
try {
const res = await fetch('/api/get-detectors', { cache: 'no-store' })
const res = await fetch('/api/get-dashboard', { cache: 'no-store' })
if (!res.ok) return
const payload = await res.json()
console.log('[Dashboard] GET /api/get-detectors', { status: res.status, payload })
const detectorsData = payload?.data?.detectors ?? {}
const arr = Object.values(detectorsData).filter(
(detector: any) => (objectId ? detector.object === objectId : true)
)
setDetectorsArray(arr as any[])
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[])
const cd = Array.isArray(payload?.data?.chart_data) ? payload.data.chart_data : []
setChartData(cd as any[])
} catch (e) {
console.error('Failed to load detectors:', e)
console.error('Failed to load dashboard:', e)
}
}
loadDetectors()
}, [objectId])
loadDashboard()
}, [objectTitle])
const handleBackClick = () => {
router.push('/objects')
}
interface DetectorData {
detector_id: number
name: string
object: string
status: string
type: string
location: string
floor: number
checked?: boolean
notifications: Array<{
id: number
type: string
message: string
timestamp: string
acknowledged: boolean
priority: string
}>
}
// Статусы
const statusCounts = detectorsArray.reduce((acc: { critical: number; warning: number; normal: number }, detector: DetectorData) => {
if (detector.status === '#b3261e') acc.critical++
else if (detector.status === '#fd7c22') acc.warning++
else if (detector.status === '#00ff00') acc.normal++
const statusCounts = dashboardAlerts.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++
return acc
}, { critical: 0, warning: 0, normal: 0 })
@@ -139,14 +123,14 @@ const Dashboard: React.FC = () => {
title="Показатель"
subtitle="За последние 6 месяцев"
>
<AreaChart />
<AreaChart data={chartData} />
</ChartCard>
<ChartCard
title="Статистика"
subtitle="Данные за период"
>
<BarChart />
<BarChart data={chartData?.map((d: any) => ({ value: d.value }))} />
</ChartCard>
</div>
</div>
@@ -174,36 +158,26 @@ const Dashboard: React.FC = () => {
<thead>
<tr className="border-b border-gray-700">
<th className="text-left text-white font-medium py-3">Детектор</th>
<th className="text-left text-white font-medium py-3">Статус</th>
<th className="text-left text-white font-medium py-3">Местоположение</th>
<th className="text-left text-white font-medium py-3">Проверен</th>
<th className="text-left text-white font-medium py-3">Сообщение</th>
<th className="text-left text-white font-medium py-3">Серьезность</th>
<th className="text-left text-white font-medium py-3">Дата</th>
<th className="text-left text-white font-medium py-3">Решен</th>
</tr>
</thead>
<tbody>
{detectorsArray.map((detector: DetectorData) => (
<tr key={detector.detector_id} className="border-b border-gray-800">
<td className="py-3 text-white text-sm">{detector.name}</td>
{dashboardAlerts.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>
<td className="py-3">
<div className="flex items-center gap-2">
<div
className={`w-3 h-3 rounded-full`}
style={{ backgroundColor: detector.status }}
></div>
<span className="text-sm text-gray-300">
{detector.status === '#b3261e' ? 'Критическое' :
detector.status === '#fd7c22' ? 'Предупреждение' : 'Норма'}
</span>
</div>
<span className={`text-sm ${alert.severity === 'critical' ? 'text-red-500' : alert.severity === 'warning' ? 'text-orange-500' : 'text-green-500'}`}>
{alert.severity === 'critical' ? 'Критическое' : alert.severity === 'warning' ? 'Предупреждение' : 'Норма'}
</span>
</td>
<td className="py-3 text-gray-400 text-sm">{detector.location}</td>
<td className="py-3 text-gray-400 text-sm">{new Date(alert.created_at).toLocaleString()}</td>
<td className="py-3">
{detector.checked ? (
<div className="flex items-center gap-1">
<svg className="w-4 h-4 text-green-500" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
<span className="text-sm text-green-500">Да</span>
</div>
{alert.resolved ? (
<span className="text-sm text-green-500">Да</span>
) : (
<span className="text-sm text-gray-500">Нет</span>
)}
@@ -217,7 +191,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">{detectorsArray.length}</div>
<div className="text-2xl font-bold text-white">{dashboardAlerts.length}</div>
<div className="text-sm text-gray-400">Всего</div>
</div>
<div className="text-center">