"use client" import { useEffect, useMemo, useState, useCallback, Suspense, lazy } from 'react' import { useRouter, useSearchParams } from 'next/navigation' import { api, TelemetryDto, PowerOutageSummaryDto } from '@/lib/api' import { persianToGregorian, getCurrentPersianDay, getCurrentPersianYear, getCurrentPersianMonth, getPreviousPersianDay, getNextPersianDay } from '@/lib/date/persian-date' import { formatPersianDate, ensureDateFormat } from '@/lib/format/persian-date' import { TABS, TabType } from '@/features/daily-report' import { detectDataGaps } from '@/features/daily-report/utils' import { BarChart3, CalendarIcon, ChevronRight } from 'lucide-react' import Loading from '@/components/Loading' import { SummaryTab } from '@/components/daily-report' import { fetchForecastWeather, isToday as checkIsToday, WeatherData } from '@/features/weather' import { Tabs, Button } from '@/components/common' import { DateNavigation } from '@/components/navigation' import { usePullToRefresh } from '@/hooks/usePullToRefresh' import { AppShell } from '@/components/layout' // Lazy load heavy components const ChartsTab = lazy(() => import('@/components/daily-report/ChartsTab').then(m => ({ default: m.ChartsTab }))) const WeatherTab = lazy(() => import('@/components/daily-report/WeatherTab').then(m => ({ default: m.WeatherTab }))) const AnalysisTab = lazy(() => import('@/components/daily-report/AnalysisTab').then(m => ({ default: m.AnalysisTab }))) function DailyReportContent() { const router = useRouter() const searchParams = useSearchParams() const [telemetry, setTelemetry] = useState([]) const [loading, setLoading] = useState(true) const [activeTab, setActiveTab] = useState('summary') const [forecastWeather, setForecastWeather] = useState(null) const [forecastWeatherLoading, setForecastWeatherLoading] = useState(false) const [visibleParams, setVisibleParams] = useState(null) const [powerOutageSummary, setPowerOutageSummary] = useState(null) const [powerOutageLoading, setPowerOutageLoading] = useState(false) // Map summary param to chart key const paramToChartKey = useCallback((param: string): string => { const mapping: Record = { temperature: 'temp', humidity: 'hum', gas: 'gas', soil: 'soil', lux: 'lux', } return mapping[param] || param }, []) // Handle card click: switch to charts tab and scroll to chart const handleCardClick = useCallback((param: string) => { const chartKey = paramToChartKey(param) setActiveTab('charts') // Wait for tab to switch and chart to render, then scroll setTimeout(() => { const chartElement = document.getElementById(`chart-${chartKey}`) if (chartElement) { chartElement.scrollIntoView({ behavior: 'smooth', block: 'start' }) } }, 100) }, [paramToChartKey]) const deviceId = Number(searchParams.get('deviceId') ?? '1') const dateParam = searchParams.get('date') ?? formatPersianDate(getCurrentPersianYear(), getCurrentPersianMonth(), getCurrentPersianDay()) const selectedDate = useMemo(() => { if (!dateParam) return null try { const decodedDate = decodeURIComponent(dateParam) // Ensure date is in yyyy/MM/dd format return ensureDateFormat(decodedDate) } catch (error) { console.error('Error decoding date parameter:', error) return null } }, [dateParam]) // Navigate to previous day const goToPreviousDay = useCallback(() => { if (!selectedDate) return const prevDay = getPreviousPersianDay(selectedDate) if (prevDay) { router.push(`/daily-report?deviceId=${deviceId}&date=${encodeURIComponent(prevDay)}`) } }, [selectedDate, deviceId, router]) // Navigate to next day const goToNextDay = useCallback(() => { if (!selectedDate) return const nextDay = getNextPersianDay(selectedDate) if (nextDay) { router.push(`/daily-report?deviceId=${deviceId}&date=${encodeURIComponent(nextDay)}`) } }, [selectedDate, deviceId, router]) // Navigate to calendar to select a date const goToCalendar = useCallback(() => { router.push(`/calendar?deviceId=${deviceId}`) }, [deviceId, router]) const loadData = useCallback(async () => { if (!selectedDate) { setLoading(false) return } setLoading(true) try { const [year, month, day] = selectedDate.split('/').map(Number) const startDate = persianToGregorian(year, month, day) startDate.setHours(0, 0, 0, 0) const endDate = new Date(startDate) endDate.setHours(23, 59, 59, 999) const startUtc = startDate.toISOString() const endUtc = endDate.toISOString() const result = await api.listTelemetry({ deviceId, startUtc, endUtc, pageSize: 100000 }) setTelemetry(result.items) console.log(result.items) // Load visible params from server (controls which parameters are shown) try { const params = await api.getDisplayParameters(deviceId) setVisibleParams(params ?? null) } catch (err) { console.error('Error loading display parameters:', err) setVisibleParams(['gas', 'temperature', 'humidity', 'lux']) } } catch (error) { console.error('Error loading telemetry:', error) } finally { setLoading(false) } }, [deviceId, selectedDate]) // Load power outage summary useEffect(() => { if (!selectedDate) { setPowerOutageSummary(null) return } const loadPowerOutage = async () => { setPowerOutageLoading(true) try { const [year, month, day] = selectedDate.split('/').map(Number) const startDate = persianToGregorian(year, month, day) startDate.setHours(0, 0, 0, 0) const endDate = new Date(startDate) endDate.setHours(23, 59, 59, 999) const startUtc = startDate.toISOString() const endUtc = endDate.toISOString() const summary = await api.getPowerOutageSummary(deviceId, startUtc, endUtc) setPowerOutageSummary(summary) } catch (error) { console.error('Error loading power outage summary:', error) setPowerOutageSummary(null) } finally { setPowerOutageLoading(false) } } loadPowerOutage() }, [deviceId, selectedDate]) // Pull-to-refresh for mobile usePullToRefresh(loadData) useEffect(() => { // Reset states when date or device changes loadData() }, [loadData, deviceId, selectedDate]) // Load forecast weather data when selectedDate is today - lazy load after main data is loaded useEffect(() => { if (!selectedDate || loading) { setForecastWeather(null) setForecastWeatherLoading(false) return } const isTodayDate = checkIsToday(selectedDate) if (isTodayDate) { // Delay fetch to ensure page is fully loaded first setForecastWeatherLoading(true) setForecastWeather(null) // Use setTimeout to ensure page is fully rendered before fetching const timer = setTimeout(() => { fetchForecastWeather() .then(setForecastWeather) .catch((error) => { console.error('Error loading forecast weather:', error) setForecastWeather(null) }) .finally(() => { setForecastWeatherLoading(false) }) }, 100) // Small delay to ensure page is rendered return () => clearTimeout(timer) } else { setForecastWeather(null) setForecastWeatherLoading(false) } }, [selectedDate, loading]) const sortedTelemetry = useMemo(() => { return [...telemetry].sort((a, b) => { const aTime = a.serverTimestampUtc || a.timestampUtc const bTime = b.serverTimestampUtc || b.timestampUtc return new Date(aTime).getTime() - new Date(bTime).getTime() }) }, [telemetry]) // Data arrays const soil = useMemo(() => sortedTelemetry.map(t => Number(t.soilPercent ?? 0)), [sortedTelemetry]) const temp = useMemo(() => sortedTelemetry.map(t => Number(t.temperatureC ?? 0)), [sortedTelemetry]) const hum = useMemo(() => sortedTelemetry.map(t => Number(t.humidityPercent ?? 0)), [sortedTelemetry]) const gas = useMemo(() => sortedTelemetry.map(t => Number(t.gasPPM ?? 0)), [sortedTelemetry]) const lux = useMemo(() => sortedTelemetry.map(t => Number(t.lux ?? 0)), [sortedTelemetry]) // Latest values for live summary const latestValues = useMemo(() => ({ temperature: temp.length > 0 ? temp.at(-1) ?? 0 : 0, humidity: hum.length > 0 ? hum.at(-1) ?? 0 : 0, soil: soil.length > 0 ? soil.at(-1) ?? 0 : 0, gas: gas.length > 0 ? gas.at(-1) ?? 0 : 0, lux: lux.length > 0 ? lux.at(-1) ?? 0 : 0, }), [temp, hum, soil, gas, lux]) // Last updated time from the most recent telemetry entry const lastUpdated = useMemo(() => { if (sortedTelemetry.length === 0) return undefined const lastTimestamp = sortedTelemetry.at(-1)?.serverTimestampUtc || sortedTelemetry.at(-1)?.timestampUtc if (!lastTimestamp) return undefined const date = new Date(lastTimestamp) const hours = String(date.getHours()).padStart(2, '0') const minutes = String(date.getMinutes()).padStart(2, '0') const seconds = String(date.getSeconds()).padStart(2, '0') return `${hours}:${minutes}:${seconds}` }, [sortedTelemetry]) // Detect data gaps in the full day data const dataGaps = useMemo(() => { const timestamps = sortedTelemetry.map(t => t.serverTimestampUtc || t.timestampUtc) return detectDataGaps(timestamps, 30) // 30 minutes threshold }, [sortedTelemetry]) if (loading) { return } if (!selectedDate) { return (
تاریخ انتخاب نشده است
) } return (
{/* Date Navigation Buttons */} {/* Tabs */} {{ summary: , charts: ( }> ), weather: selectedDate ? ( }> ) : null, analysis: selectedDate ? ( }> ) : null, }}
) } export default function DailyReportPage() { return (

در حال بارگذاری گزارش...

}>
) }