From c021e61608e905d8fe8d508d545f93fde632098d Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Sun, 18 Jan 2026 17:34:35 +0100 Subject: [PATCH] feat(analytics): add custom date picker for dashboard --- app/sites/[id]/page.tsx | 15 +++ components/ui/DatePicker.tsx | 183 +++++++++++++++++++++++++++++++++++ 2 files changed, 198 insertions(+) create mode 100644 components/ui/DatePicker.tsx diff --git a/app/sites/[id]/page.tsx b/app/sites/[id]/page.tsx index fab21f2..d0e4206 100644 --- a/app/sites/[id]/page.tsx +++ b/app/sites/[id]/page.tsx @@ -8,6 +8,7 @@ import { formatNumber, formatDuration, getDateRange } from '@/lib/utils/format' import { toast } from 'sonner' import LoadingOverlay from '@/components/LoadingOverlay' import Select from '@/components/ui/Select' +import DatePicker from '@/components/ui/DatePicker' import ContentStats from '@/components/dashboard/ContentStats' import TopReferrers from '@/components/dashboard/TopReferrers' import Locations from '@/components/dashboard/Locations' @@ -38,6 +39,7 @@ export default function SiteDashboardPage() { const [devices, setDevices] = useState([]) const [screenResolutions, setScreenResolutions] = useState([]) const [dateRange, setDateRange] = useState(getDateRange(30)) + const [isDatePickerOpen, setIsDatePickerOpen] = useState(false) useEffect(() => { loadData() @@ -179,6 +181,9 @@ export default function SiteDashboardPage() { const today = new Date().toISOString().split('T')[0] setDateRange({ start: today, end: today }) } + else if (value === 'custom') { + setIsDatePickerOpen(true) + } }} options={[ { value: 'today', label: 'Today' }, @@ -222,6 +227,16 @@ export default function SiteDashboardPage() { + + setIsDatePickerOpen(false)} + onApply={(range) => { + setDateRange(range) + setIsDatePickerOpen(false) + }} + initialRange={dateRange} + /> ) } diff --git a/components/ui/DatePicker.tsx b/components/ui/DatePicker.tsx new file mode 100644 index 0000000..3253e57 --- /dev/null +++ b/components/ui/DatePicker.tsx @@ -0,0 +1,183 @@ +'use client' + +import React, { useState, useEffect } from 'react' +import { ChevronLeftIcon, ChevronRightIcon, Cross2Icon } from '@radix-ui/react-icons' + +interface DateRange { + start: string + end: string +} + +interface DatePickerProps { + isOpen: boolean + onClose: () => void + onApply: (range: DateRange) => void + initialRange: DateRange +} + +export default function DatePicker({ isOpen, onClose, onApply, initialRange }: DatePickerProps) { + const [startDate, setStartDate] = useState(new Date(initialRange.start)) + const [endDate, setEndDate] = useState(new Date(initialRange.end)) + const [currentMonth, setCurrentMonth] = useState(new Date(initialRange.end)) + const [selectingStart, setSelectingStart] = useState(true) + + useEffect(() => { + if (isOpen) { + setStartDate(new Date(initialRange.start)) + setEndDate(new Date(initialRange.end)) + setCurrentMonth(new Date(initialRange.end)) + } + }, [isOpen, initialRange]) + + if (!isOpen) return null + + const getDaysInMonth = (date: Date) => { + const year = date.getFullYear() + const month = date.getMonth() + const days = new Date(year, month + 1, 0).getDate() + const firstDay = new Date(year, month, 1).getDay() + return { days, firstDay } + } + + const { days, firstDay } = getDaysInMonth(currentMonth) + + const handleDateClick = (day: number) => { + const clickedDate = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), day) + + if (selectingStart) { + setStartDate(clickedDate) + // If clicked date is after current end date, reset end date + if (clickedDate > endDate) { + setEndDate(clickedDate) + } + setSelectingStart(false) + } else { + if (clickedDate < startDate) { + setStartDate(clickedDate) + setSelectingStart(false) // Keep selecting start effectively if they clicked before start + } else { + setEndDate(clickedDate) + setSelectingStart(true) // Reset to start for next interaction or just done + } + } + } + + const handleMonthChange = (increment: number) => { + const newMonth = new Date(currentMonth) + newMonth.setMonth(newMonth.getMonth() + increment) + setCurrentMonth(newMonth) + } + + const formatDate = (date: Date) => date.toISOString().split('T')[0] + + const isSelected = (day: number) => { + const date = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), day) + return ( + date.getTime() === startDate.getTime() || + date.getTime() === endDate.getTime() + ) + } + + const isInRange = (day: number) => { + const date = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), day) + return date > startDate && date < endDate + } + + const isToday = (day: number) => { + const today = new Date() + const date = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), day) + return ( + date.getDate() === today.getDate() && + date.getMonth() === today.getMonth() && + date.getFullYear() === today.getFullYear() + ) + } + + return ( +
+
+
+

Select Date Range

+ +
+ +
+ + + {currentMonth.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })} + + +
+ +
+ {['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'].map(day => ( +
{day}
+ ))} +
+ +
+ {Array.from({ length: firstDay }).map((_, i) => ( +
+ ))} + {Array.from({ length: days }).map((_, i) => { + const day = i + 1 + const selected = isSelected(day) + const inRange = isInRange(day) + const today = isToday(day) + + return ( + + ) + })} +
+ +
+
+ + {startDate.toLocaleDateString()} + + {' - '} + + {endDate.toLocaleDateString()} + +
+
+ + +
+
+
+
+ ) +}