Files
aerbim-ht-monitor/frontend/app/(protected)/reports/page.tsx
2026-02-02 11:00:40 +03:00

177 lines
6.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import React, { useEffect, useState } from 'react'
import { useRouter, useSearchParams } from 'next/navigation'
import Sidebar from '../../../components/ui/Sidebar'
import AnimatedBackground from '../../../components/ui/AnimatedBackground'
import useNavigationStore from '../../store/navigationStore'
import ReportsList from '../../../components/reports/ReportsList'
import ExportMenu from '../../../components/ui/ExportMenu'
import { useSession } from 'next-auth/react'
import DetectorList from '../../../components/alerts/DetectorList'
const ReportsPage: React.FC = () => {
const router = useRouter()
const searchParams = useSearchParams()
const { data: session } = useSession()
const { currentObject, setCurrentObject, selectedDetector } = useNavigationStore()
const [selectedDetectors, setSelectedDetectors] = useState<number[]>([])
const [detectorsData, setDetectorsData] = useState<any>({ detectors: {} })
const urlObjectId = searchParams.get('objectId')
const urlObjectTitle = searchParams.get('objectTitle')
const objectId = currentObject.id || urlObjectId
const objectTitle = currentObject.title || urlObjectTitle
useEffect(() => {
if (urlObjectId && urlObjectTitle && (!currentObject.id || currentObject.id !== urlObjectId)) {
setCurrentObject(urlObjectId, urlObjectTitle)
}
}, [urlObjectId, urlObjectTitle, currentObject.id, setCurrentObject])
// Автовыбор detector id из стора
useEffect(() => {
if (selectedDetector && !selectedDetectors.includes(selectedDetector.detector_id)) {
setSelectedDetectors(prev => [...prev, selectedDetector.detector_id])
}
}, [selectedDetector, selectedDetectors])
useEffect(() => {
const loadDetectors = async () => {
try {
const params = new URLSearchParams()
if (currentObject.id) params.set('objectId', currentObject.id)
const res = await fetch(`/api/get-detectors?${params.toString()}`, { cache: 'no-store' })
if (!res.ok) return
const payload = await res.json()
const data = payload?.data || { detectors: {} }
console.log('[ReportsPage] GET /api/get-detectors', {
url: `/api/get-detectors?${params.toString()}`,
status: res.status,
payload,
parsed: data,
})
setDetectorsData(data)
} catch (e) {
console.error('Failed to load detectors data:', e)
}
}
loadDetectors()
}, [currentObject.id])
const handleBackClick = () => {
router.push('/dashboard')
}
const handleDetectorSelect = (detectorId: number, selected: boolean) => {
if (selected) {
setSelectedDetectors(prev => [...prev, detectorId])
} else {
setSelectedDetectors(prev => prev.filter(id => id !== detectorId))
}
}
const handleExport = async (format: 'csv' | 'pdf', hours: number) => {
try {
const res = await fetch(`/api/get-reports`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(session?.accessToken ? { Authorization: `Bearer ${session.accessToken}` } : {}),
},
body: JSON.stringify({ format: format, hours }),
credentials: 'include',
})
if (!res.ok) {
const errText = await res.text()
console.error('[ReportsPage] POST /api/get-reports failed', { status: res.status, body: errText })
throw new Error(`Export failed: ${res.status} ${errText}`)
}
console.log('[ReportsPage] POST /api/get-reports success', { status: res.status, headers: Object.fromEntries(res.headers.entries()) })
const blob = await res.blob()
const contentDisposition = res.headers.get('Content-Disposition') || res.headers.get('content-disposition')
const filenameMatch = contentDisposition?.match(/filename="(.+)"/)
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').replace(/T/g, '_')
const filename = filenameMatch ? filenameMatch[1] : `detectors_report_${timestamp}.${format}`
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
} catch (error) {
console.error('Error:', error)
}
}
return (
<div className="relative flex h-screen bg-[#0e111a] overflow-hidden">
<AnimatedBackground />
<div className="relative z-20">
<Sidebar
activeItem={9} // Отчёты
/>
</div>
<div className="relative z-10 flex-1 flex flex-col">
<header className="border-b border-gray-700 bg-[#161824] px-6 py-4">
<div className="flex items-center gap-4">
<button
onClick={handleBackClick}
className="text-gray-400 transition-colors hover:text-white"
aria-label="Назад к дашборду"
>
<svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 19l-7-7 7-7"
/>
</svg>
</button>
<nav className="flex items-center gap-2 text-sm">
<span className="text-gray-400">Дашборд</span>
<span className="text-gray-600">/</span>
<span className="text-white">{objectTitle || 'Объект'}</span>
<span className="text-gray-600">/</span>
<span className="text-white">Отчеты</span>
</nav>
</div>
</header>
<div className="flex-1 overflow-auto p-6">
<div className="mb-6">
<div className="mb-6 flex items-center justify-between">
<h1 style={{ fontFamily: 'Inter, sans-serif', fontWeight: 600 }} className="text-2xl text-white">Отчеты по датчикам</h1>
<ExportMenu onExport={handleExport} />
</div>
<div className="mb-6">
<DetectorList
objectId={objectId || undefined}
selectedDetectors={selectedDetectors}
onDetectorSelect={handleDetectorSelect}
initialSearchTerm={selectedDetector?.detector_id.toString() || ''}
/>
</div>
<div className="mt-6">
<ReportsList
detectorsData={detectorsData}
initialSearchTerm={selectedDetector?.detector_id.toString() || ''}
/>
</div>
</div>
</div>
</div>
</div>
)
}
export default ReportsPage