diff --git a/backend/api/account/serializers/alert_serializers.py b/backend/api/account/serializers/alert_serializers.py
index 1104bef..2f1fd49 100644
--- a/backend/api/account/serializers/alert_serializers.py
+++ b/backend/api/account/serializers/alert_serializers.py
@@ -8,11 +8,11 @@ class AlertSerializer(serializers.ModelSerializer):
name = serializers.SerializerMethodField()
object = serializers.SerializerMethodField()
metric_value = serializers.SerializerMethodField()
- sensor_type_name = serializers.SerializerMethodField()
+ detector_type = serializers.SerializerMethodField()
class Meta:
model = Alert
- fields = ('id', 'name', 'object', 'metric_value', 'sensor_type_name', 'message', 'severity', 'created_at', 'resolved')
+ fields = ('id', 'name', 'object', 'metric_value', 'detector_type', 'message', 'severity', 'created_at', 'resolved')
@extend_schema_field(OpenApiTypes.STR)
def get_name(self, obj) -> str:
@@ -22,14 +22,17 @@ class AlertSerializer(serializers.ModelSerializer):
def get_object(self, obj) -> Optional[str]:
zone = obj.sensor.zones.first()
return zone.object.title if zone else None
-
@extend_schema_field(OpenApiTypes.STR)
def get_metric_value(self, obj) -> str:
if obj.metric.value is not None:
unit = obj.sensor.signal_format.unit if obj.sensor.signal_format else ''
return f"{obj.metric.value} {unit}".strip()
return obj.metric.raw_value
-
@extend_schema_field(OpenApiTypes.STR)
- def get_sensor_type_name(self, obj) -> str:
- return obj.sensor_type.name
\ No newline at end of file
+ def get_detector_type(self, obj) -> str:
+ sensor_type = getattr(obj, 'sensor_type', None)
+ if sensor_type is None and hasattr(obj, 'sensor') and obj.sensor:
+ sensor_type = getattr(obj.sensor, 'sensor_type', None)
+ if sensor_type is None:
+ return ''
+ return (getattr(sensor_type, 'code', '') or '').upper()
diff --git a/backend/api/account/serializers/sensor_serializers.py b/backend/api/account/serializers/sensor_serializers.py
index 8ebd7f7..376d5e0 100644
--- a/backend/api/account/serializers/sensor_serializers.py
+++ b/backend/api/account/serializers/sensor_serializers.py
@@ -23,6 +23,7 @@ class NotificationSerializer(serializers.ModelSerializer):
class DetectorSerializer(serializers.ModelSerializer):
detector_id = serializers.SerializerMethodField()
type = serializers.SerializerMethodField()
+ detector_type = serializers.SerializerMethodField()
name = serializers.SerializerMethodField()
object = serializers.SerializerMethodField()
status = serializers.SerializerMethodField()
@@ -32,22 +33,28 @@ class DetectorSerializer(serializers.ModelSerializer):
class Meta:
model = Sensor
- fields = ('detector_id', 'type', 'serial_number', 'name', 'object', 'status', 'zone', 'floor', 'notifications')
+ fields = ('detector_id', 'type', 'detector_type', 'serial_number', 'name', 'object', 'status', 'zone', 'floor', 'notifications')
def get_detector_id(self, obj):
return obj.name or f"{obj.sensor_type.code}-{obj.id}"
def get_type(self, obj):
- # маппинг типов сенсоров на нужные значения
sensor_type_mapping = {
- 'GA': 'fire_detector',
- 'PE': 'pressure_sensor',
- 'GLE': 'gas_detector'
+ 'GA': 'Инклинометр',
+ 'PE': 'Тензометр',
+ 'GLE': 'Гидроуровень',
}
- return sensor_type_mapping.get(obj.sensor_type.code, 'unknown')
+ code = (getattr(obj.sensor_type, 'code', '') or '').upper()
+ return sensor_type_mapping.get(code, (getattr(obj.sensor_type, 'name', '') or ''))
+
+ def get_detector_type(self, obj):
+ return (getattr(obj.sensor_type, 'code', '') or '').upper()
def get_name(self, obj):
- return f"{obj.sensor_type.name} {obj.serial_number}"
+ sensor_type = getattr(obj, 'sensor_type', None) or getattr(obj, 'sensor_type', None)
+ serial = getattr(obj, 'serial_number', '') or ''
+ base_name = getattr(obj, 'name', '') or ''
+ return base_name or f"{getattr(obj.sensor_type, 'code', '')}-{serial}".strip('-')
def get_object(self, obj):
# получаем первую зону датчика и её объект
@@ -64,17 +71,12 @@ class DetectorSerializer(serializers.ModelSerializer):
return 'normal'
def get_zone(self, obj):
- zone = obj.zones.first()
- if zone:
- return zone.name
- return None
+ first_zone = obj.zones.first()
+ return first_zone.name if first_zone else None
def get_floor(self, obj):
- # получаем этаж из зоны
- zone = obj.zones.first()
- if zone:
- return zone.floor
- return None # если датчик не привязали к зоне
+ first_zone = obj.zones.first()
+ return getattr(first_zone, 'floor', None)
class DetectorsResponseSerializer(serializers.Serializer):
detectors = serializers.SerializerMethodField()
diff --git a/backend/api/account/views/sensors_views.py b/backend/api/account/views/sensors_views.py
index 135d055..75ae12d 100644
--- a/backend/api/account/views/sensors_views.py
+++ b/backend/api/account/views/sensors_views.py
@@ -59,4 +59,4 @@ class SensorView(APIView):
except Sensor.DoesNotExist:
return Response(
{"error": "Датчики не найдены"},
- status=status.HTTP_404_NOT_FOUND)
\ No newline at end of file
+ status=status.HTTP_404_NOT_FOUND)
diff --git a/frontend/app/(protected)/alerts/page.tsx b/frontend/app/(protected)/alerts/page.tsx
index 4709ac1..61adfd5 100644
--- a/frontend/app/(protected)/alerts/page.tsx
+++ b/frontend/app/(protected)/alerts/page.tsx
@@ -234,7 +234,7 @@ const AlertsPage: React.FC = () => {
{item.acknowledged ? 'Да' : 'Нет'}
diff --git a/frontend/app/(protected)/navigation/page.tsx b/frontend/app/(protected)/navigation/page.tsx
index b2b527e..69b5c03 100644
--- a/frontend/app/(protected)/navigation/page.tsx
+++ b/frontend/app/(protected)/navigation/page.tsx
@@ -29,6 +29,7 @@ interface DetectorType {
status: string
checked: boolean
type: string
+ detector_type: string
location: string
floor: number
notifications: Array<{
@@ -86,7 +87,8 @@ const NavigationPage: React.FC = () => {
const urlObjectTitle = searchParams.get('objectTitle')
const objectId = currentObject.id || urlObjectId
const objectTitle = currentObject.title || urlObjectTitle
-
+ const [selectedModelPath, setSelectedModelPath] = useState('')
+
const handleModelLoaded = useCallback(() => {
setIsModelReady(true)
setModelError(null)
@@ -173,12 +175,19 @@ const NavigationPage: React.FC = () => {
}
const getStatusText = (status: string) => {
- switch (status) {
- case 'active': return 'Активен'
- case 'inactive': return 'Неактивен'
- case 'error': return 'Ошибка'
- case 'maintenance': return 'Обслуживание'
- default: return 'Неизвестно'
+ const s = (status || '').toLowerCase()
+ switch (s) {
+ case '#b3261e':
+ case 'critical':
+ return 'Критический'
+ case '#fd7c22':
+ case 'warning':
+ return 'Предупреждение'
+ case '#00ff00':
+ case 'normal':
+ return 'Норма'
+ default:
+ return 'Неизвестно'
}
}
@@ -191,18 +200,23 @@ const NavigationPage: React.FC = () => {
{showMonitoring && (
-
+
{
+ setSelectedModelPath(path)
+ setModelError(null)
+ setIsModelReady(false)
+ }}
/>
)}
{showFloorNavigation && (
-
+
{
)}
{showNotifications && (
-
+
{
detector => detector.detector_id === selectedNotification.detector_id
);
return detectorData ? (
-
+
{
null
)}
-
-
+
+
) : (
{
@@ -27,6 +28,7 @@ const ObjectsPage: React.FC = () => {
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const [selectedObjectId, setSelectedObjectId] = useState(null)
+ const router = useRouter()
useEffect(() => {
const loadData = async () => {
@@ -41,7 +43,15 @@ const ObjectsPage: React.FC = () => {
console.log('[ObjectsPage] GET /api/get-objects', { status: res.status, payload })
if (!res.ok) {
- throw new Error(typeof payload === 'string' ? payload : (payload?.error || 'Не удалось получить данные объектов'))
+ const errorMessage = typeof payload === 'string' ? payload : (payload?.error || 'Не удалось получить данные объектов')
+
+ if (errorMessage.includes('Authentication required') || res.status === 401) {
+ console.log('[ObjectsPage] Authentication required, redirecting to login')
+ router.push('/login')
+ return
+ }
+
+ throw new Error(errorMessage)
}
const data = (payload?.data ?? payload) as any
@@ -66,7 +76,7 @@ const ObjectsPage: React.FC = () => {
}
loadData()
- }, [])
+ }, [router])
const handleObjectSelect = (objectId: string) => {
console.log('Object selected:', objectId)
diff --git a/frontend/app/api/big-models/[...path]/route.ts b/frontend/app/api/big-models/[...path]/route.ts
index 06ed5b6..dd98f1b 100644
--- a/frontend/app/api/big-models/[...path]/route.ts
+++ b/frontend/app/api/big-models/[...path]/route.ts
@@ -17,11 +17,21 @@ export async function GET(
const stat = fs.statSync(filePath);
const stream = fs.createReadStream(filePath);
+
+ const ext = path.extname(fileName).toLowerCase();
+ let contentType = 'application/octet-stream';
+ if (ext === '.glb') contentType = 'model/gltf-binary';
+ else if (ext === '.gltf') contentType = 'model/gltf+json';
+ else if (ext === '.bin') contentType = 'application/octet-stream';
+ else if (ext === '.png') contentType = 'image/png';
+ else if (ext === '.jpg' || ext === '.jpeg') contentType = 'image/jpeg';
+ else if (ext === '.webp') contentType = 'image/webp';
+ else if (ext === '.ktx2') contentType = 'image/ktx2';
return new Response(stream as unknown as ReadableStream, {
headers: {
'Content-Length': stat.size.toString(),
- 'Content-Type': 'model/gltf-binary',
+ 'Content-Type': contentType,
},
});
}
\ No newline at end of file
diff --git a/frontend/app/api/big-models/list/route.ts b/frontend/app/api/big-models/list/route.ts
new file mode 100644
index 0000000..c5c99cf
--- /dev/null
+++ b/frontend/app/api/big-models/list/route.ts
@@ -0,0 +1,47 @@
+export const dynamic = 'force-static';
+
+import fs from 'fs';
+import path from 'path';
+
+export async function GET() {
+ try {
+ const dirPath = path.join(process.cwd(), 'assets', 'big-models');
+ if (!fs.existsSync(dirPath)) {
+ return new Response(JSON.stringify({ models: [] }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+
+ const files = fs.readdirSync(dirPath, { withFileTypes: true });
+ const models = files
+ .filter((ent) => ent.isFile() && (ent.name.toLowerCase().endsWith('.glb') || ent.name.toLowerCase().endsWith('.gltf')))
+ .map((ent) => {
+ const filename = ent.name;
+ const base = filename.replace(/\.(glb|gltf)$/i, '');
+ let title = base;
+ title = title.replace(/^AerBIM-Monitor_ASM-HT-Viewer_/i, '');
+ title = title.replace(/_/g, ' ');
+ title = title.replace(/\bLevel\b/gi, 'Уровень');
+ title = title.replace(/\bcustom\s*prop\b/gi, '');
+ title = title.replace(/\bcustom\b/gi, '');
+ title = title.replace(/\bprop\b/gi, '');
+ title = title.replace(/\s{2,}/g, ' ').trim();
+
+ const pathUrl = `/static-models/${filename}`;
+ return { name: title, path: pathUrl };
+ });
+
+ return new Response(JSON.stringify({ models }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ } catch (error) {
+ console.error('[big-models/list] Error listing models:', error);
+ const msg = error instanceof Error ? error.message : String(error);
+ return new Response(JSON.stringify({ error: msg, models: [] }), {
+ status: 500,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+}
\ No newline at end of file
diff --git a/frontend/app/api/get-alerts/route.ts b/frontend/app/api/get-alerts/route.ts
index 936c7ca..9355c19 100644
--- a/frontend/app/api/get-alerts/route.ts
+++ b/frontend/app/api/get-alerts/route.ts
@@ -89,16 +89,21 @@ export async function GET(req: NextRequest) {
return true
}) : list
- const transformed = filtered.map((a) => ({
- id: a.id,
- detector_name: a.name,
- message: a.message,
- type: a.severity,
- location: a.object,
- priority: a.severity,
- acknowledged: a.resolved,
- timestamp: a.created_at,
- }))
+ const transformed = filtered.map((a) => {
+ const severity = String(a?.severity || a?.type || '').toLowerCase()
+ const type = severity === 'critical' ? 'critical' : severity === 'warning' ? 'warning' : 'info'
+ const priority = severity === 'critical' ? 'high' : severity === 'warning' ? 'medium' : 'low'
+ return {
+ id: a.id,
+ detector_name: a.name || a.detector_name,
+ message: a.message,
+ type,
+ location: a.object,
+ priority,
+ acknowledged: typeof a.acknowledged === 'boolean' ? a.acknowledged : !!a.resolved,
+ timestamp: a.timestamp || a.created_at,
+ }
+ })
return NextResponse.json({ success: true, data: transformed })
} catch (error) {
diff --git a/frontend/app/api/get-dashboard/route.ts b/frontend/app/api/get-dashboard/route.ts
index 69f3e07..5456ada 100644
--- a/frontend/app/api/get-dashboard/route.ts
+++ b/frontend/app/api/get-dashboard/route.ts
@@ -36,10 +36,16 @@ export async function GET(req: NextRequest) {
if (!backendUrl) {
return NextResponse.json({ success: false, error: 'BACKEND_URL is not configured' }, { status: 500 })
}
-
+
const url = new URL(req.url)
- const timePeriod = url.searchParams.get('time_period')
- const qs = timePeriod ? `?time_period=${encodeURIComponent(timePeriod)}` : ''
+ const timePeriodRaw = url.searchParams.get('time_period')
+ const allowedPeriods = new Set([24, 72, 168, 720])
+ let timePeriodNum = timePeriodRaw ? Number(timePeriodRaw) : undefined
+ if (Number.isNaN(timePeriodNum)) {
+ timePeriodNum = undefined
+ }
+ const finalTimePeriod = timePeriodNum && allowedPeriods.has(timePeriodNum) ? String(timePeriodNum) : '168'
+ const qs = `?time_period=${encodeURIComponent(finalTimePeriod)}`
const res = await fetch(`${backendUrl}/account/get-dashboard/${qs}`, {
headers: {
diff --git a/frontend/app/api/get-detectors/route.ts b/frontend/app/api/get-detectors/route.ts
index 3ce0ce0..f50ac4c 100644
--- a/frontend/app/api/get-detectors/route.ts
+++ b/frontend/app/api/get-detectors/route.ts
@@ -67,6 +67,20 @@ export async function GET() {
checked: sensor.checked ?? false,
location: sensor.zone ?? '',
serial_number: sensor.serial_number ?? sensor.name ?? '',
+ detector_type: sensor.detector_type ?? '',
+ notifications: Array.isArray(sensor.notifications) ? sensor.notifications.map((n: any) => {
+ const severity = String(n?.severity || n?.type || '').toLowerCase()
+ const type = severity === 'critical' ? 'critical' : severity === 'warning' ? 'warning' : 'info'
+ const priority = severity === 'critical' ? 'high' : severity === 'warning' ? 'medium' : 'low'
+ return {
+ id: n.id,
+ type,
+ message: n.message,
+ timestamp: n.timestamp || n.created_at,
+ acknowledged: typeof n.acknowledged === 'boolean' ? n.acknowledged : !!n.resolved,
+ priority,
+ }
+ }) : []
}
}
diff --git a/frontend/app/api/get-objects/route.ts b/frontend/app/api/get-objects/route.ts
index 11e7067..eaf5e37 100644
--- a/frontend/app/api/get-objects/route.ts
+++ b/frontend/app/api/get-objects/route.ts
@@ -23,11 +23,32 @@ export async function GET(req: NextRequest) {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh: refreshToken }),
})
+
if (refreshRes.ok) {
const refreshed = await refreshRes.json()
accessToken = refreshed.access
+ } else {
+ const errorText = await refreshRes.text()
+ let errorData: { error?: string; detail?: string; code?: string } = {}
+ try {
+ errorData = JSON.parse(errorText)
+ } catch {
+ errorData = { error: errorText }
+ }
+
+ const errorMessage = (errorData.error as string) || (errorData.detail as string) || ''
+ if (typeof errorMessage === 'string' &&
+ (errorMessage.includes('Token is expired') ||
+ errorMessage.includes('expired') ||
+ errorData.code === 'token_not_valid')) {
+ console.warn('Refresh token expired, user needs to re-authenticate')
+ } else {
+ console.error('Token refresh failed:', errorData.error || errorData.detail || 'Unknown error')
+ }
}
- } catch {}
+ } catch (error) {
+ console.error('Error during token refresh:', error)
+ }
}
if (!accessToken) {
@@ -52,7 +73,21 @@ export async function GET(req: NextRequest) {
let payload: any
try { payload = JSON.parse(payloadText) } catch { payload = payloadText }
- if (!objectsRes.ok) {
+ if (!objectsRes.ok) {
+ if (payload && typeof payload === 'object') {
+ if (payload.code === 'token_not_valid' ||
+ (payload.detail && typeof payload.detail === 'string' && (payload.detail.includes('Token is expired') || payload.detail.includes('Given token not valid'))) ||
+ (payload.messages && Array.isArray(payload.messages) && payload.messages.some((msg: any) =>
+ msg.message && typeof msg.message === 'string' && msg.message.includes('Token is expired')
+ ))) {
+ console.warn('Access token expired, user needs to re-authenticate')
+ return NextResponse.json({
+ success: false,
+ error: 'Authentication required - please log in again'
+ }, { status: 401 })
+ }
+ }
+
const err = typeof payload === 'string' ? payload : JSON.stringify(payload)
return NextResponse.json({ success: false, error: `Backend objects error: ${err}` }, { status: objectsRes.status })
}
diff --git a/frontend/app/store/navigationStore.ts b/frontend/app/store/navigationStore.ts
index cfafd20..383d659 100644
--- a/frontend/app/store/navigationStore.ts
+++ b/frontend/app/store/navigationStore.ts
@@ -8,6 +8,7 @@ export interface DetectorType {
object: string
status: string
type: string
+ detector_type: string
location: string
floor: number
checked: boolean
diff --git a/frontend/components/alerts/DetectorList.tsx b/frontend/components/alerts/DetectorList.tsx
index 056c3f2..f45a63a 100644
--- a/frontend/components/alerts/DetectorList.tsx
+++ b/frontend/components/alerts/DetectorList.tsx
@@ -18,6 +18,7 @@ interface RawDetector {
object: string
status: string
type: string
+ detector_type: string
location: string
floor: number
notifications: Array<{
diff --git a/frontend/components/dashboard/AreaChart.tsx b/frontend/components/dashboard/AreaChart.tsx
index f2c76f4..72ae21e 100644
--- a/frontend/components/dashboard/AreaChart.tsx
+++ b/frontend/components/dashboard/AreaChart.tsx
@@ -22,15 +22,7 @@ const AreaChart: React.FC = ({ className = '', data }) => {
const safeData = (Array.isArray(data) && data.length > 0)
? data
- : [
- { value: 5 },
- { value: 3 },
- { value: 7 },
- { value: 2 },
- { value: 6 },
- { value: 4 },
- { value: 8 }
- ]
+ : Array.from({ length: 7 }, () => ({ value: 0 }))
const maxVal = Math.max(...safeData.map(d => d.value || 0), 1)
const stepX = safeData.length > 1 ? width / (safeData.length - 1) : width
diff --git a/frontend/components/dashboard/BarChart.tsx b/frontend/components/dashboard/BarChart.tsx
index 8e90e54..6cf6c8a 100644
--- a/frontend/components/dashboard/BarChart.tsx
+++ b/frontend/components/dashboard/BarChart.tsx
@@ -14,24 +14,9 @@ interface BarChartProps {
}
const BarChart: React.FC = ({ 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)' },
- { value: 45, color: 'rgb(42, 157, 144)' },
- { value: 75, color: 'rgb(42, 157, 144)' },
- { value: 55, color: 'rgb(42, 157, 144)' },
- { value: 85, color: 'rgb(42, 157, 144)' },
- { value: 70, color: 'rgb(42, 157, 144)' },
- { value: 60, color: 'rgb(42, 157, 144)' },
- { value: 95, color: 'rgb(42, 157, 144)' },
- { value: 40, color: 'rgb(42, 157, 144)' },
- { 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
+ : Array.from({ length: 12 }, () => ({ value: 0, color: 'rgb(42, 157, 144)' }))
const maxVal = Math.max(...barData.map(b => b.value || 0), 1)
diff --git a/frontend/components/dashboard/Dashboard.tsx b/frontend/components/dashboard/Dashboard.tsx
index 35a8b31..eea95b8 100644
--- a/frontend/components/dashboard/Dashboard.tsx
+++ b/frontend/components/dashboard/Dashboard.tsx
@@ -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([])
const [chartData, setChartData] = useState<{ timestamp: string; value: number }[]>([])
+ const [sensorTypes] = useState>([
+ { code: '', name: 'Все датчики' },
+ { code: 'GA', name: 'Инклинометр' },
+ { code: 'PE', name: 'Танзометр' },
+ { code: 'GLE', name: 'Гидроуровень' }
+ ])
+ const [selectedSensorType, setSelectedSensorType] = useState('')
+ const [selectedChartPeriod, setSelectedChartPeriod] = useState('168')
+ const [selectedTablePeriod, setSelectedTablePeriod] = useState('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 (
@@ -87,17 +158,25 @@ const Dashboard: React.FC = () => {
- Объект {objectId?.replace('object_', '')}
+ {objectTitle || 'Объект'}
- |