Some checks failed
Deploy MyApp on Same Server / build-and-deploy (push) Has been cancelled
- Replace custom page layout with AppShell component for consistent UI - Remove unused PageHeader and UpdateAlertConditionDto imports - Update service worker cache names and version.json to new timestamp
132 lines
5.3 KiB
TypeScript
132 lines
5.3 KiB
TypeScript
"use client"
|
|
import { useEffect, useMemo, useState } from 'react'
|
|
import { useRouter } from 'next/navigation'
|
|
import { api } from '@/lib/api'
|
|
import { getCurrentPersianYear } from '@/lib/date/persian-date'
|
|
import { Calendar as CalendarIcon, Database, TrendingUp } from 'lucide-react'
|
|
import Loading from '@/components/Loading'
|
|
import { Card } from '@/components/common'
|
|
import { YearSelector } from '@/components/calendar'
|
|
import { MonthCard } from '@/components/cards'
|
|
import { StatsCard } from '@/components/cards'
|
|
import { AppShell } from '@/components/layout'
|
|
|
|
const monthNames = ['فروردین', 'اردیبهشت', 'خرداد', 'تیر', 'مرداد', 'شهریور', 'مهر', 'آبان', 'آذر', 'دی', 'بهمن', 'اسفند']
|
|
|
|
function useQueryParam(name: string) {
|
|
const [value, setValue] = useState<string | null>(null)
|
|
|
|
useEffect(() => {
|
|
if (typeof window !== 'undefined') {
|
|
const params = new URLSearchParams(window.location.search)
|
|
setValue(params.get(name))
|
|
}
|
|
}, [name])
|
|
|
|
return value
|
|
}
|
|
|
|
export default function CalendarPage() {
|
|
const router = useRouter()
|
|
const deviceIdParam = useQueryParam('deviceId')
|
|
const [deviceId, setDeviceId] = useState<number>(1)
|
|
const [year, setYear] = useState<number>(getCurrentPersianYear())
|
|
const [activeMonths, setActiveMonths] = useState<number[]>([])
|
|
const [monthDays, setMonthDays] = useState<Record<number, { days: number; records: number }>>({})
|
|
const [loading, setLoading] = useState(true)
|
|
const years = useMemo(() => Array.from({ length: 10 }, (_, i) => getCurrentPersianYear() - 2 + i), [])
|
|
|
|
// وقتی deviceIdParam تغییر کرد، deviceId را به روز کن
|
|
useEffect(() => {
|
|
if (deviceIdParam) {
|
|
setDeviceId(Number(deviceIdParam))
|
|
}
|
|
}, [deviceIdParam])
|
|
|
|
useEffect(() => {
|
|
let mounted = true
|
|
setMonthDays({})
|
|
setActiveMonths([])
|
|
setLoading(true)
|
|
|
|
api.activeMonths(deviceId, year)
|
|
.then(async (months) => {
|
|
if (!mounted) return
|
|
setActiveMonths(months)
|
|
const entries = await Promise.all(months.map(async m => {
|
|
const days = await api.monthDays(deviceId, year, m)
|
|
const records = days.reduce((s, d) => s + d.count, 0)
|
|
return [m, { days: days.length, records }] as const
|
|
}))
|
|
if (!mounted) return
|
|
setMonthDays(Object.fromEntries(entries))
|
|
})
|
|
.catch(console.error)
|
|
.finally(() => {
|
|
if (mounted) setLoading(false)
|
|
})
|
|
return () => { mounted = false }
|
|
}, [deviceId, year])
|
|
|
|
const totalDays = useMemo(() => Object.values(monthDays).reduce((s, v) => s + (v?.days ?? 0), 0), [monthDays])
|
|
const totalRecords = useMemo(() => Object.values(monthDays).reduce((s, v) => s + (v?.records ?? 0), 0), [monthDays])
|
|
|
|
if (loading) {
|
|
return <Loading message="در حال بارگذاری تقویم..." />
|
|
}
|
|
|
|
return (
|
|
<AppShell
|
|
pageTitle="انتخاب سال و ماه"
|
|
pageIcon={CalendarIcon}
|
|
iconGradient="from-green-500 to-green-600"
|
|
>
|
|
<div className="max-w-6xl mx-auto">
|
|
{/* Main Card */}
|
|
<Card padding="lg">
|
|
{/* Year Selector */}
|
|
<YearSelector
|
|
years={years}
|
|
selectedYear={year}
|
|
onYearChange={setYear}
|
|
className="mb-6"
|
|
/>
|
|
|
|
{/* Summary Stats */}
|
|
<div className="bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200 rounded-xl p-4 mb-6">
|
|
<div className="flex items-center justify-center gap-6 flex-wrap">
|
|
<StatsCard
|
|
icon={Database}
|
|
label="روز دارای داده"
|
|
value={totalDays}
|
|
/>
|
|
<StatsCard
|
|
icon={TrendingUp}
|
|
label="رکورد"
|
|
value={totalRecords}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Months Grid */}
|
|
<div className="grid gap-4 md:grid-cols-3 xl:grid-cols-4">
|
|
{monthNames.map((name, idx) => {
|
|
const m = idx + 1
|
|
const isActive = activeMonths.includes(m)
|
|
const stats = monthDays[m]
|
|
return (
|
|
<MonthCard
|
|
key={m}
|
|
name={name}
|
|
isActive={isActive}
|
|
stats={stats}
|
|
onClick={() => isActive && router.replace(`/day-details?deviceId=${deviceId}&year=${year}&month=${m}`)}
|
|
/>
|
|
)
|
|
})}
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
</AppShell>
|
|
)
|
|
} |