From da180e76ab1541dc56099908710ee173f5c0d276 Mon Sep 17 00:00:00 2001 From: alireza Date: Wed, 3 Jun 2026 00:46:37 +0330 Subject: [PATCH] fix ui and add powerOutage list --- public/sw.js | 4 +- public/version.json | 2 +- src/app/calendar/page.tsx | 32 ++-- src/app/daily-report/page.tsx | 70 ++++++- src/app/globals.css | 14 +- src/components/Charts.tsx | 2 +- src/components/DashboardCards.tsx | 15 +- src/components/Loading.tsx | 2 +- src/components/UpdateNotification.tsx | 2 +- .../alert-settings/AlertFormModal.tsx | 2 +- .../alert-settings/AlertListItem.tsx | 2 +- .../alert-settings/AlertPreview.tsx | 7 +- .../NotificationTypeSelector.tsx | 4 +- .../alert-settings/RuleFormCard.tsx | 102 ++++++++--- .../alert-settings/TimeTypeSelector.tsx | 4 +- src/components/alerts/WeatherAlertBanner.tsx | 12 +- src/components/calendar/WeekdayHeaders.tsx | 2 +- src/components/calendar/YearSelector.tsx | 2 +- src/components/cards/CalendarDayCell.tsx | 4 +- src/components/cards/DeviceCard.tsx | 18 +- src/components/cards/MonthCard.tsx | 13 +- src/components/common/Button.tsx | 15 +- src/components/common/Card.tsx | 4 +- src/components/common/Dialog.tsx | 6 +- src/components/common/EmptyState.tsx | 8 +- src/components/common/ErrorMessage.tsx | 8 +- src/components/common/IconButton.tsx | 2 +- src/components/common/Modal.tsx | 8 +- src/components/common/PageHeader.tsx | 8 +- src/components/common/SegmentTab.tsx | 4 +- src/components/common/SuccessMessage.tsx | 4 +- src/components/common/Tabs.tsx | 8 +- .../daily-report/LiveSummaryCards.tsx | 142 +++++++++++++++ .../daily-report/PowerOutageCard.tsx | 171 ++++++++++++++++++ src/components/daily-report/SummaryCard.tsx | 10 +- src/components/daily-report/SummaryTab.tsx | 22 ++- src/components/daily-report/index.ts | 2 + .../weather/HistoricalWeather.tsx | 11 +- .../daily-report/weather/TodayWeather.tsx | 89 +++++---- .../daily-report/weather/WeatherCards.tsx | 97 +++++----- src/components/forms/CodeInput.tsx | 2 +- src/components/forms/FormInput.tsx | 2 +- src/components/forms/MobileInput.tsx | 2 +- src/components/forms/SearchInput.tsx | 2 +- src/components/layout/BottomNav.tsx | 14 +- src/components/layout/TopHeader.tsx | 4 +- src/components/navigation/Pagination.tsx | 14 +- src/components/settings/SettingsSection.tsx | 3 +- src/components/utils/ResendButton.tsx | 4 +- src/features/daily-report/types.ts | 3 +- src/lib/api/client.ts | 13 +- src/lib/api/types.ts | 29 +++ 52 files changed, 746 insertions(+), 280 deletions(-) create mode 100644 src/components/daily-report/LiveSummaryCards.tsx create mode 100644 src/components/daily-report/PowerOutageCard.tsx diff --git a/public/sw.js b/public/sw.js index 83bbbb5..e7a877a 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,5 +1,5 @@ -const CACHE_NAME = 'greenhome-1778524870692'; -const STATIC_CACHE_NAME = 'greenhome-static-1778524870692'; +const CACHE_NAME = 'greenhome-1780331120215'; +const STATIC_CACHE_NAME = 'greenhome-static-1780331120215'; // Static assets to cache on install const STATIC_FILES_TO_CACHE = [ diff --git a/public/version.json b/public/version.json index da8a2a6..4ff45ba 100644 --- a/public/version.json +++ b/public/version.json @@ -1,3 +1,3 @@ { - "version": "1778524870692" + "version": "1780331120215" } \ No newline at end of file diff --git a/src/app/calendar/page.tsx b/src/app/calendar/page.tsx index 92cb757..2d88c0b 100644 --- a/src/app/calendar/page.tsx +++ b/src/app/calendar/page.tsx @@ -1,6 +1,6 @@ "use client" -import { useEffect, useMemo, useState } from 'react' -import { useRouter } from 'next/navigation' +import { Suspense, useEffect, useMemo, useState } from 'react' +import { useRouter, useSearchParams } from 'next/navigation' import { api } from '@/lib/api' import { getCurrentPersianYear } from '@/lib/date/persian-date' import { Calendar as CalendarIcon, Database, TrendingUp } from 'lucide-react' @@ -13,22 +13,10 @@ import { AppShell } from '@/components/layout' const monthNames = ['فروردین', 'اردیبهشت', 'خرداد', 'تیر', 'مرداد', 'شهریور', 'مهر', 'آبان', 'آذر', 'دی', 'بهمن', 'اسفند'] -function useQueryParam(name: string) { - const [value, setValue] = useState(null) - - useEffect(() => { - if (typeof window !== 'undefined') { - const params = new URLSearchParams(window.location.search) - setValue(params.get(name)) - } - }, [name]) - - return value -} - -export default function CalendarPage() { +function CalendarContent() { const router = useRouter() - const deviceIdParam = useQueryParam('deviceId') + const searchParams = useSearchParams() + const deviceIdParam = searchParams.get('deviceId') const [deviceId, setDeviceId] = useState(1) const [year, setYear] = useState(getCurrentPersianYear()) const [activeMonths, setActiveMonths] = useState([]) @@ -129,4 +117,12 @@ export default function CalendarPage() { ) -} \ No newline at end of file +} + +export default function CalendarPage() { + return ( + }> + + + ) +} diff --git a/src/app/daily-report/page.tsx b/src/app/daily-report/page.tsx index 1a50f52..a03d5e8 100644 --- a/src/app/daily-report/page.tsx +++ b/src/app/daily-report/page.tsx @@ -1,14 +1,14 @@ "use client" import { useEffect, useMemo, useState, useCallback, Suspense, lazy } from 'react' import { useRouter, useSearchParams } from 'next/navigation' -import { api, TelemetryDto } from '@/lib/api' +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 { SummaryTab, LiveSummaryCards } from '@/components/daily-report' import { fetchForecastWeather, isToday as checkIsToday, WeatherData } from '@/features/weather' import { Tabs, Button } from '@/components/common' import { DateNavigation } from '@/components/navigation' @@ -29,6 +29,8 @@ function DailyReportContent() { 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 => { @@ -131,6 +133,38 @@ function DailyReportContent() { } }, [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) @@ -189,6 +223,27 @@ function DailyReportContent() { 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) @@ -247,7 +302,16 @@ function DailyReportContent() { className="md:mx-0 mx-[-1rem] md:rounded-xl rounded-none" > {{ - summary: , + summary: , + live: , charts: ( }> diff --git a/src/app/globals.css b/src/app/globals.css index b9d0f19..8c59838 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,12 +1,10 @@ @import "tailwindcss"; :root { - /* --background: #ffffff; */ --foreground: #171717; } @theme inline { - /* --color-background: var(--background); */ --color-foreground: var(--foreground); --font-sans: 'Vazirmatn', -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif; --font-mono: 'Vazirmatn', monospace; @@ -14,7 +12,6 @@ @media (prefers-color-scheme: dark) { :root { - /* --background: #0a0a0a; */ --foreground: #ededed; } } @@ -31,12 +28,11 @@ html { } body { - /* background: var(--background); */ - background: linear-gradient(180deg, #eef7ff, #f6fbff); + background: #f8fafc; color: var(--foreground); font-family: 'Vazirmatn', -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif; font-weight: 400; - overscroll-behavior-y: contain; /* Prevent pull-to-refresh on Android */ + overscroll-behavior-y: contain; -webkit-overflow-scrolling: touch; } @@ -44,7 +40,7 @@ body { button, a, [role="button"] { min-height: 44px; min-width: 44px; - touch-action: manipulation; /* Disable double-tap zoom */ + touch-action: manipulation; -webkit-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent; @@ -56,7 +52,7 @@ button, a, [role="button"] { .transition-transform, .transition-opacity { will-change: transform, opacity; - transform: translateZ(0); /* Force GPU acceleration */ + transform: translateZ(0); } .persian-number { @@ -176,4 +172,4 @@ button, a, [role="button"] { .has-bottom-nav { padding-bottom: 4.5rem; } -} \ No newline at end of file +} diff --git a/src/components/Charts.tsx b/src/components/Charts.tsx index bc57a7e..0e8c997 100644 --- a/src/components/Charts.tsx +++ b/src/components/Charts.tsx @@ -64,7 +64,7 @@ type Props = { export function Panel({ title, children, id }: { title: string; children: React.ReactNode; id?: string }) { return ( -
+

{title}

diff --git a/src/components/DashboardCards.tsx b/src/components/DashboardCards.tsx index 46f0a83..91ffe4e 100644 --- a/src/components/DashboardCards.tsx +++ b/src/components/DashboardCards.tsx @@ -77,19 +77,19 @@ export function Card({ title, value, subtitle, icon, color }: CardProps) { const hasStats = subtitle?.includes('حداکثر') && subtitle?.includes('حداقل') return ( -
-
-
-
+
+
+
+
{title.replace(/[💡🫁🌱💧🌡️📊]/g, '').trim()}
{IconComponent && ( -
- +
+
)}
-
{value}
+
{value}
{subtitle && (
{hasStats ? ( @@ -109,7 +109,6 @@ export function Card({ title, value, subtitle, icon, color }: CardProps) {
)}
-
) } diff --git a/src/components/Loading.tsx b/src/components/Loading.tsx index 671b879..b12821b 100644 --- a/src/components/Loading.tsx +++ b/src/components/Loading.tsx @@ -53,7 +53,7 @@ export function Skeleton({ className, width, height, rounded = true }: SkeletonP // Card skeleton for loading states export function CardSkeleton() { return ( -
+
diff --git a/src/components/UpdateNotification.tsx b/src/components/UpdateNotification.tsx index 30b30e1..48bcf76 100644 --- a/src/components/UpdateNotification.tsx +++ b/src/components/UpdateNotification.tsx @@ -98,7 +98,7 @@ export default function UpdateNotification() { return (
-
+
diff --git a/src/components/alert-settings/AlertFormModal.tsx b/src/components/alert-settings/AlertFormModal.tsx index b71a6ce..1336cf3 100644 --- a/src/components/alert-settings/AlertFormModal.tsx +++ b/src/components/alert-settings/AlertFormModal.tsx @@ -125,7 +125,7 @@ export function AlertFormModal({
) diff --git a/src/components/calendar/WeekdayHeaders.tsx b/src/components/calendar/WeekdayHeaders.tsx index d9bf379..3dfb168 100644 --- a/src/components/calendar/WeekdayHeaders.tsx +++ b/src/components/calendar/WeekdayHeaders.tsx @@ -13,7 +13,7 @@ export function WeekdayHeaders({ }: WeekdayHeadersProps) { return (
{weekdays.map((day) => ( diff --git a/src/components/calendar/YearSelector.tsx b/src/components/calendar/YearSelector.tsx index aac0b13..70dd88d 100644 --- a/src/components/calendar/YearSelector.tsx +++ b/src/components/calendar/YearSelector.tsx @@ -21,7 +21,7 @@ export function YearSelector({ انتخاب سال: