Skip to content

Commit 60dbb72

Browse files
authored
Unify date controls behind a single range picker (#749)
The date controls had drifted into four different shapes across views — Analytics/Usage's preset + input selector, Activity's segmented control plus a separate prev/next navigator, Trends' inline labelled from/to plus a granularity group, and Insights' mode-coupled inputs. Same job, four costumes: three input heights, three preset vocabularies, two active-state styles. This replaces all of them with one shared `RangePicker`: a popover trigger plus a segmented panel (Relative / Calendar / Custom). Every view renders the identical control. Behavior that isn't date *selection* (Activity's period stepping) lives inside the panel, so the toolbars look the same everywhere. A small selection model (`rangeSelection.ts`) resolves any selection to the `from/to` or `preset/anchor` each store already consumes, so existing behavior is preserved rather than reimplemented: - Analytics and Usage keep rolling-vs-pinned windows, the "All" (earliest-session) preset, and URL persistence. - Activity keeps day/week/month period stepping (now inside the panel) and the future-step guard. - Trends keeps its granularity and normalize controls, relocated out of the date toolbar into the chart panel's header as a minimal "Group by" dropdown — they sat next to the picker and collided with its Calendar tab's day/week/month. - Insights keeps single-day selection (Calendar · Day, the common default) and its insight-type select; the bespoke mode-coupled date inputs are gone. Out of scope by design: Analytics' chart granularity and Activity's bucket stay as their own controls. Folding every resolution toggle into one shared control is a possible follow-up. Where to look: `frontend/src/lib/components/shared/RangePicker.svelte` (the control) and `rangeSelection.ts` (the selection model + date math). Each page's adapter is the small `rangeSelection` / `applyRange` block added to its `*Page.svelte`. `DateRangeSelector`, `RangeControl`, and `RangeNavigator` are deleted. Co-authored-by: Phillip Cloud <cpcloud@users.noreply.github.com>
1 parent f734c69 commit 60dbb72

16 files changed

Lines changed: 1404 additions & 686 deletions

frontend/src/lib/components/activity/ActivityPage.svelte

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@
66
type Automation,
77
} from "../../stores/activity.svelte.js";
88
import { events } from "../../stores/events.svelte.js";
9+
import { sync } from "../../stores/sync.svelte.js";
910
import RefreshControl from "../shared/RefreshControl.svelte";
1011
import ProjectTypeahead from "../layout/ProjectTypeahead.svelte";
1112
import { ChevronDownIcon } from "../../icons.js";
12-
import RangeControl from "./RangeControl.svelte";
13-
import RangeNavigator from "./RangeNavigator.svelte";
13+
import RangePicker from "../shared/RangePicker.svelte";
14+
import {
15+
resolveRange,
16+
type RangeSelection,
17+
} from "../shared/rangeSelection.js";
1418
import SummaryCards from "./SummaryCards.svelte";
1519
import ConcurrencyTimeline from "./ConcurrencyTimeline.svelte";
1620
import SessionsTable from "./SessionsTable.svelte";
@@ -48,6 +52,32 @@
4852
slotFilter = null;
4953
});
5054
55+
const earliestSession = $derived(sync.stats?.earliest_session ?? null);
56+
const today = $derived(localDateStr(new Date()));
57+
58+
// The activity store is the source of truth: day/week/month map to a calendar
59+
// period anchored on `date`; custom maps to from/to. Relative windows have no
60+
// native equivalent here, so applyRange resolves them to a pinned custom range.
61+
const rangeSelection = $derived.by((): RangeSelection => {
62+
if (activity.preset === "custom") {
63+
return { mode: "custom", from: activity.from, to: activity.to };
64+
}
65+
return { mode: "calendar", unit: activity.preset, anchor: activity.date };
66+
});
67+
68+
function applyRange(sel: RangeSelection) {
69+
if (sel.mode === "calendar") {
70+
activity.setPreset(sel.unit);
71+
activity.setDate(sel.anchor);
72+
} else {
73+
const range = resolveRange(sel, earliestSession);
74+
activity.setPreset("custom");
75+
activity.setFrom(range.from);
76+
activity.setTo(range.to);
77+
}
78+
activity.load();
79+
}
80+
5181
function onProjectSelect(value: string) {
5282
activity.setProject(value);
5383
activity.load();
@@ -95,8 +125,13 @@
95125

96126
<div class="activity-page">
97127
<div class="activity-toolbar">
98-
<RangeControl />
99-
<RangeNavigator />
128+
<RangePicker
129+
selection={rangeSelection}
130+
busy={activity.loading}
131+
{earliestSession}
132+
maxDate={today}
133+
onSelect={applyRange}
134+
/>
100135

101136
<ProjectTypeahead
102137
projects={activity.projects}

frontend/src/lib/components/activity/RangeControl.svelte

Lines changed: 0 additions & 57 deletions
This file was deleted.

frontend/src/lib/components/activity/RangeControl.test.ts

Lines changed: 0 additions & 38 deletions
This file was deleted.

frontend/src/lib/components/activity/RangeNavigator.svelte

Lines changed: 0 additions & 147 deletions
This file was deleted.

frontend/src/lib/components/activity/RangeNavigator.test.ts

Lines changed: 0 additions & 59 deletions
This file was deleted.

0 commit comments

Comments
 (0)