feat: add This week / This month period options and fix comparison labels

This commit is contained in:
Usman Baig
2026-03-11 23:33:24 +01:00
parent 34eca64967
commit b5dd5e7082
3 changed files with 66 additions and 26 deletions

View File

@@ -8,6 +8,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
### Improved
- **New date range options.** The period selector now includes "This week" (Monday to today) and "This month" (1st to today) alongside the existing rolling windows. Your selection is remembered between sessions.
- **Smarter comparison labels.** The "vs …" label under each stat now matches the period you're viewing — "vs yesterday" for today, "vs last week" for this week, "vs last month" for this month, and "vs previous N days" for rolling windows.
- **Refreshed stat headers.** The Unique Visitors, Total Pageviews, Bounce Rate, and Visit Duration stats at the top of the chart have a new look — uppercase labels, the percentage change shown inline next to the number, and an orange underline on whichever metric you're currently graphing.
- **Consistent green and red colors.** The up/down percentage indicators now use the same green and red as the rest of the app, instead of slightly different shades.
- **Scroll Depth is now a radar chart.** The Scroll Depth panel has been redesigned from a bar chart into a radar chart. The four scroll milestones (25%, 50%, 75%, 100%) are plotted as axes, with the filled shape showing how far visitors are getting through your pages at a glance.
- **Polished Goals & Events panel.** The Goals & Events block on your dashboard got a visual refresh to match the style of the Pages, Referrers, and Locations panels. Events are now ranked with a number on the left, counts are shown in a consistent style, and hovering any row reveals what percentage of total events that action accounts for — sliding in smoothly from the right.
- **Smarter bot protection.** The security checks on shared dashboard access and organization settings now use action-specific tokens tied to each page. A token earned on one page can't be reused on another, making it harder for automated tools to bypass the captcha.

View File

@@ -64,6 +64,20 @@ function loadSavedSettings(): {
}
}
function getThisWeekRange(): { start: string; end: string } {
const today = new Date()
const dayOfWeek = today.getDay()
const monday = new Date(today)
monday.setDate(today.getDate() - (dayOfWeek === 0 ? 6 : dayOfWeek - 1))
return { start: formatDate(monday), end: formatDate(today) }
}
function getThisMonthRange(): { start: string; end: string } {
const today = new Date()
const firstOfMonth = new Date(today.getFullYear(), today.getMonth(), 1)
return { start: formatDate(firstOfMonth), end: formatDate(today) }
}
function getInitialDateRange(): { start: string; end: string } {
const settings = loadSavedSettings()
if (settings?.type === 'today') {
@@ -71,10 +85,16 @@ function getInitialDateRange(): { start: string; end: string } {
return { start: today, end: today }
}
if (settings?.type === '7') return getDateRange(7)
if (settings?.type === 'week') return getThisWeekRange()
if (settings?.type === 'month') return getThisMonthRange()
if (settings?.type === 'custom' && settings.dateRange) return settings.dateRange
return getDateRange(30)
}
function getInitialPeriod(): string {
return loadSavedSettings()?.type || '30'
}
export default function SiteDashboardPage() {
@@ -84,6 +104,7 @@ export default function SiteDashboardPage() {
const siteId = params.id as string
// UI state - initialized from localStorage synchronously to avoid double-fetch
const [period, setPeriod] = useState(getInitialPeriod)
const [dateRange, setDateRange] = useState(getInitialDateRange)
const [todayInterval, setTodayInterval] = useState<'minute' | 'hour'>(
() => loadSavedSettings()?.todayInterval || 'hour'
@@ -457,40 +478,44 @@ export default function SiteDashboardPage() {
<Select
variant="input"
className="min-w-[140px]"
value={
dateRange.start === formatDate(new Date()) && dateRange.end === formatDate(new Date())
? 'today'
: dateRange.start === getDateRange(7).start
? '7'
: dateRange.start === getDateRange(30).start
? '30'
: 'custom'
}
value={period}
onChange={(value) => {
if (value === '7') {
const range = getDateRange(7)
setDateRange(range)
saveSettings('7', range)
}
else if (value === '30') {
const range = getDateRange(30)
setDateRange(range)
saveSettings('30', range)
}
else if (value === 'today') {
if (value === 'today') {
const today = formatDate(new Date())
const range = { start: today, end: today }
setDateRange(range)
setPeriod('today')
saveSettings('today', range)
}
else if (value === 'custom') {
} else if (value === '7') {
const range = getDateRange(7)
setDateRange(range)
setPeriod('7')
saveSettings('7', range)
} else if (value === 'week') {
const range = getThisWeekRange()
setDateRange(range)
setPeriod('week')
saveSettings('week', range)
} else if (value === '30') {
const range = getDateRange(30)
setDateRange(range)
setPeriod('30')
saveSettings('30', range)
} else if (value === 'month') {
const range = getThisMonthRange()
setDateRange(range)
setPeriod('month')
saveSettings('month', range)
} else if (value === 'custom') {
setIsDatePickerOpen(true)
}
}}
options={[
{ value: 'today', label: 'Today' },
{ value: '7', label: 'Last 7 days' },
{ value: 'week', label: 'This week' },
{ value: '30', label: 'Last 30 days' },
{ value: 'month', label: 'This month' },
{ value: 'custom', label: 'Custom' },
]}
/>
@@ -514,6 +539,7 @@ export default function SiteDashboardPage() {
prevStats={prevStats}
interval={dateRange.start === dateRange.end ? todayInterval : multiDayInterval}
dateRange={dateRange}
period={period}
todayInterval={todayInterval}
setTodayInterval={setTodayInterval}
multiDayInterval={multiDayInterval}
@@ -613,6 +639,7 @@ export default function SiteDashboardPage() {
onClose={() => setIsDatePickerOpen(false)}
onApply={(range) => {
setDateRange(range)
setPeriod('custom')
saveSettings('custom', range)
setIsDatePickerOpen(false)
}}

View File

@@ -64,6 +64,7 @@ interface ChartProps {
prevStats?: Stats
interval: 'minute' | 'hour' | 'day' | 'month'
dateRange: { start: string, end: string }
period?: string
todayInterval: 'minute' | 'hour'
setTodayInterval: (interval: 'minute' | 'hour') => void
multiDayInterval: 'hour' | 'day'
@@ -145,6 +146,7 @@ export default function Chart({
prevStats,
interval,
dateRange,
period,
todayInterval,
setTodayInterval,
multiDayInterval,
@@ -358,10 +360,17 @@ export default function Chart({
</span>
)}
</div>
<div className="text-xs text-neutral-400 dark:text-neutral-500 mt-1">{(() => {
const days = Math.round((new Date(dateRange.end).getTime() - new Date(dateRange.start).getTime()) / 86400000)
return days === 0 ? 'vs yesterday' : `vs previous ${days} days`
})()}</div>
<div className="text-xs text-neutral-400 dark:text-neutral-500 mt-1">{
period === 'today' ? 'vs yesterday'
: period === 'week' ? 'vs last week'
: period === 'month' ? 'vs last month'
: period === '7' ? 'vs previous 7 days'
: period === '30' ? 'vs previous 30 days'
: (() => {
const days = Math.round((new Date(dateRange.end).getTime() - new Date(dateRange.start).getTime()) / 86400000)
return days === 0 ? 'vs yesterday' : `vs previous ${days} days`
})()
}</div>
{metric === m.key && (
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-brand-orange" />
)}