Linked backend data to detectors' meshes.
This commit is contained in:
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user