Skip to content

Commit 6c2ce1c

Browse files
committed
Add analytics reorder controls
1 parent f39b8c4 commit 6c2ce1c

3 files changed

Lines changed: 76 additions & 24 deletions

File tree

src/components/analytics/AnalyticsLibrary.tsx

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ interface AnalyticsLibraryProps {
1616
onAddFormulaMetric: () => void;
1717
onMoveMetric: (metricId: string, direction: 'up' | 'down') => void;
1818
onAddStreak: () => void;
19+
onMoveStreak: (streakId: string, direction: 'up' | 'down') => void;
1920
onAddChart: () => void;
21+
onMoveChart: (chartId: string, direction: 'up' | 'down') => void;
2022
}
2123

2224
function LibrarySection({
@@ -122,7 +124,9 @@ export function AnalyticsLibrary({
122124
onAddFormulaMetric,
123125
onMoveMetric,
124126
onAddStreak,
125-
onAddChart
127+
onMoveStreak,
128+
onAddChart,
129+
onMoveChart
126130
}: AnalyticsLibraryProps) {
127131
const baseMetrics = metrics.filter((metric) => metric.kind === 'base');
128132
const formulaMetrics = metrics.filter((metric) => metric.kind === 'formula');
@@ -137,19 +141,16 @@ export function AnalyticsLibrary({
137141
{baseMetrics.length === 0 ? (
138142
<p className="text-xs text-muted">No custom metrics yet.</p>
139143
) : (
140-
baseMetrics.map((metric) => (
144+
baseMetrics.map((metric, index) => (
141145
<LibraryItem
142146
key={metric.id}
143147
label={metric.name}
144148
sublabel={metric.base?.measure}
145149
active={selectedItem?.kind === 'metric' && selectedItem.id === metric.id}
146150
onClick={() => onSelect({ kind: 'metric', id: metric.id })}
147151
showMoveControls={showActions}
148-
canMoveUp={baseMetrics.findIndex((candidate) => candidate.id === metric.id) > 0}
149-
canMoveDown={
150-
baseMetrics.findIndex((candidate) => candidate.id === metric.id) <
151-
baseMetrics.length - 1
152-
}
152+
canMoveUp={index > 0}
153+
canMoveDown={index < baseMetrics.length - 1}
153154
onMoveUp={() => onMoveMetric(metric.id, 'up')}
154155
onMoveDown={() => onMoveMetric(metric.id, 'down')}
155156
/>
@@ -165,19 +166,16 @@ export function AnalyticsLibrary({
165166
{formulaMetrics.length === 0 ? (
166167
<p className="text-xs text-muted">No formula metrics yet.</p>
167168
) : (
168-
formulaMetrics.map((metric) => (
169+
formulaMetrics.map((metric, index) => (
169170
<LibraryItem
170171
key={metric.id}
171172
label={metric.name}
172173
sublabel={metric.formula?.operator}
173174
active={selectedItem?.kind === 'metric' && selectedItem.id === metric.id}
174175
onClick={() => onSelect({ kind: 'metric', id: metric.id })}
175176
showMoveControls={showActions}
176-
canMoveUp={formulaMetrics.findIndex((candidate) => candidate.id === metric.id) > 0}
177-
canMoveDown={
178-
formulaMetrics.findIndex((candidate) => candidate.id === metric.id) <
179-
formulaMetrics.length - 1
180-
}
177+
canMoveUp={index > 0}
178+
canMoveDown={index < formulaMetrics.length - 1}
181179
onMoveUp={() => onMoveMetric(metric.id, 'up')}
182180
onMoveDown={() => onMoveMetric(metric.id, 'down')}
183181
/>
@@ -193,18 +191,23 @@ export function AnalyticsLibrary({
193191
{streaks.length === 0 ? (
194192
<p className="text-xs text-muted">No streaks yet.</p>
195193
) : (
196-
streaks.map((streak) => {
194+
streaks.map((streak, index) => {
197195
const requiredMetricCount = 1 + (streak.additionalMetricIds?.length ?? 0);
198196
return (
199-
<LibraryItem
200-
key={streak.id}
201-
label={streak.name}
202-
sublabel={`${streak.period} ${streak.thresholdOperator} ${streak.thresholdValue}${
203-
requiredMetricCount > 1 ? ` · ${requiredMetricCount} metrics (AND)` : ''
204-
}`}
205-
active={selectedItem?.kind === 'streak' && selectedItem.id === streak.id}
206-
onClick={() => onSelect({ kind: 'streak', id: streak.id })}
207-
/>
197+
<LibraryItem
198+
key={streak.id}
199+
label={streak.name}
200+
sublabel={`${streak.period} ${streak.thresholdOperator} ${streak.thresholdValue}${
201+
requiredMetricCount > 1 ? ` · ${requiredMetricCount} metrics (AND)` : ''
202+
}`}
203+
active={selectedItem?.kind === 'streak' && selectedItem.id === streak.id}
204+
onClick={() => onSelect({ kind: 'streak', id: streak.id })}
205+
showMoveControls={showActions}
206+
canMoveUp={index > 0}
207+
canMoveDown={index < streaks.length - 1}
208+
onMoveUp={() => onMoveStreak(streak.id, 'up')}
209+
onMoveDown={() => onMoveStreak(streak.id, 'down')}
210+
/>
208211
);
209212
})
210213
)}
@@ -218,13 +221,18 @@ export function AnalyticsLibrary({
218221
{charts.length === 0 ? (
219222
<p className="text-xs text-muted">No chart views yet.</p>
220223
) : (
221-
charts.map((chart) => (
224+
charts.map((chart, index) => (
222225
<LibraryItem
223226
key={chart.id}
224227
label={chart.name}
225228
sublabel={`${chart.chartType} · ${chart.granularity}`}
226229
active={selectedItem?.kind === 'chart' && selectedItem.id === chart.id}
227230
onClick={() => onSelect({ kind: 'chart', id: chart.id })}
231+
showMoveControls={showActions}
232+
canMoveUp={index > 0}
233+
canMoveDown={index < charts.length - 1}
234+
onMoveUp={() => onMoveChart(chart.id, 'up')}
235+
onMoveDown={() => onMoveChart(chart.id, 'down')}
228236
/>
229237
))
230238
)}

src/pages/AdvancedAnalyticsPage.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,11 @@ export function AdvancedAnalyticsPage() {
120120
const addStreak = useAdvancedAnalyticsStore((state) => state.addStreak);
121121
const updateStreak = useAdvancedAnalyticsStore((state) => state.updateStreak);
122122
const removeStreak = useAdvancedAnalyticsStore((state) => state.removeStreak);
123+
const moveStreak = useAdvancedAnalyticsStore((state) => state.moveStreak);
123124
const addChart = useAdvancedAnalyticsStore((state) => state.addChart);
124125
const updateChart = useAdvancedAnalyticsStore((state) => state.updateChart);
125126
const removeChart = useAdvancedAnalyticsStore((state) => state.removeChart);
127+
const moveChart = useAdvancedAnalyticsStore((state) => state.moveChart);
126128
const replaceDefinitions = useAdvancedAnalyticsStore((state) => state.replaceDefinitions);
127129
const activeTab = useUiStateStore((state) => state.analyticsActiveTab);
128130
const setActiveTab = useUiStateStore((state) => state.setAnalyticsActiveTab);
@@ -613,7 +615,9 @@ export function AdvancedAnalyticsPage() {
613615
onAddFormulaMetric={() => addMetric('formula')}
614616
onMoveMetric={moveMetric}
615617
onAddStreak={addStreak}
618+
onMoveStreak={moveStreak}
616619
onAddChart={addChart}
620+
onMoveChart={moveChart}
617621
/>
618622

619623
<div className="space-y-4">

src/store/useAdvancedAnalyticsStore.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,11 @@ interface AdvancedAnalyticsState {
4242
addStreak: () => void;
4343
updateStreak: (id: string, updater: (streak: AdvancedAnalyticsStreakDefinition) => AdvancedAnalyticsStreakDefinition) => void;
4444
removeStreak: (id: string) => void;
45+
moveStreak: (id: string, direction: 'up' | 'down') => void;
4546
addChart: () => void;
4647
updateChart: (id: string, updater: (chart: AdvancedAnalyticsChartDefinition) => AdvancedAnalyticsChartDefinition) => void;
4748
removeChart: (id: string) => void;
49+
moveChart: (id: string, direction: 'up' | 'down') => void;
4850
replaceDefinitions: (next: {
4951
metrics: AdvancedAnalyticsMetricDefinition[];
5052
streaks: AdvancedAnalyticsStreakDefinition[];
@@ -267,6 +269,25 @@ export const useAdvancedAnalyticsStore = create<AdvancedAnalyticsState>()(
267269
? null
268270
: state.selectedItem
269271
})),
272+
moveStreak: (id, direction) =>
273+
set((state) => {
274+
const currentIndex = state.streaks.findIndex((streak) => streak.id === id);
275+
if (currentIndex < 0) {
276+
return { streaks: state.streaks };
277+
}
278+
279+
const swapIndex = direction === 'up' ? currentIndex - 1 : currentIndex + 1;
280+
if (swapIndex < 0 || swapIndex >= state.streaks.length) {
281+
return { streaks: state.streaks };
282+
}
283+
284+
const nextStreaks = [...state.streaks];
285+
[nextStreaks[currentIndex], nextStreaks[swapIndex]] = [
286+
nextStreaks[swapIndex],
287+
nextStreaks[currentIndex]
288+
];
289+
return { streaks: nextStreaks };
290+
}),
270291
addChart: () =>
271292
set((state) => {
272293
const defaultMetricIds = state.metrics.slice(0, 2).map((metric) => metric.id);
@@ -296,6 +317,25 @@ export const useAdvancedAnalyticsStore = create<AdvancedAnalyticsState>()(
296317
? null
297318
: state.selectedItem
298319
})),
320+
moveChart: (id, direction) =>
321+
set((state) => {
322+
const currentIndex = state.charts.findIndex((chart) => chart.id === id);
323+
if (currentIndex < 0) {
324+
return { charts: state.charts };
325+
}
326+
327+
const swapIndex = direction === 'up' ? currentIndex - 1 : currentIndex + 1;
328+
if (swapIndex < 0 || swapIndex >= state.charts.length) {
329+
return { charts: state.charts };
330+
}
331+
332+
const nextCharts = [...state.charts];
333+
[nextCharts[currentIndex], nextCharts[swapIndex]] = [
334+
nextCharts[swapIndex],
335+
nextCharts[currentIndex]
336+
];
337+
return { charts: nextCharts };
338+
}),
299339
replaceDefinitions: (next) =>
300340
set((state) => ({
301341
metrics: next.metrics,

0 commit comments

Comments
 (0)