Skip to content

Commit 49ab3e8

Browse files
committed
refactor and cleanup
1 parent 4d9c490 commit 49ab3e8

File tree

9 files changed

+516
-244
lines changed

9 files changed

+516
-244
lines changed

apps/better-admin

apps/dashboard/app/(main)/websites/[id]/_components/analytics-toolbar.tsx

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22

33
import { ArrowClockwiseIcon } from '@phosphor-icons/react';
44
import dayjs from 'dayjs';
5+
import { useAtom } from 'jotai';
56
import { useCallback, useMemo } from 'react';
67
import type { DateRange as DayPickerRange } from 'react-day-picker';
78
import { DateRangePicker } from '@/components/date-range-picker';
89
import { Button } from '@/components/ui/button';
910
import { useDateFilters } from '@/hooks/use-date-filters';
11+
import { addDynamicFilterAtom } from '@/stores/jotai/filterAtoms';
12+
import { AddFilterForm } from './utils/add-filters';
1013

1114
interface AnalyticsToolbarProps {
1215
isRefreshing: boolean;
@@ -24,6 +27,8 @@ export function AnalyticsToolbar({
2427
setDateRangeAction,
2528
} = useDateFilters();
2629

30+
const [, addFilter] = useAtom(addDynamicFilterAtom);
31+
2732
const dayPickerSelectedRange: DayPickerRange | undefined = useMemo(
2833
() => ({
2934
from: currentDateRange.startDate,
@@ -58,11 +63,11 @@ export function AnalyticsToolbar({
5863
);
5964

6065
return (
61-
<div className="mt-3 flex flex-col gap-3 rounded border bg-card p-4 shadow-sm">
66+
<div className="mt-3 flex flex-col gap-2 rounded border bg-card p-3 shadow-sm">
6267
<div className="flex items-center justify-between gap-3">
63-
<div className="flex h-8 overflow-hidden rounded-md border bg-background shadow-sm">
68+
<div className="flex h-8 overflow-hidden rounded border bg-background shadow-sm">
6469
<Button
65-
className={`h-8 cursor-pointer touch-manipulation rounded-none px-2 text-xs sm:px-3 ${currentGranularity === 'daily' ? 'bg-primary/10 font-medium text-primary' : 'text-muted-foreground'}`}
70+
className={`h-8 cursor-pointer touch-manipulation rounded-none px-3 text-sm ${currentGranularity === 'daily' ? 'bg-primary/10 font-medium text-primary' : 'text-muted-foreground'}`}
6671
onClick={() => setCurrentGranularityAtomState('daily')}
6772
size="sm"
6873
title="View daily aggregated data"
@@ -71,7 +76,7 @@ export function AnalyticsToolbar({
7176
Daily
7277
</Button>
7378
<Button
74-
className={`h-8 cursor-pointer touch-manipulation rounded-none px-2 text-xs sm:px-3 ${currentGranularity === 'hourly' ? 'bg-primary/10 font-medium text-primary' : 'text-muted-foreground'}`}
79+
className={`h-8 cursor-pointer touch-manipulation rounded-none px-3 text-sm ${currentGranularity === 'hourly' ? 'bg-primary/10 font-medium text-primary' : 'text-muted-foreground'}`}
7580
onClick={() => setCurrentGranularityAtomState('hourly')}
7681
size="sm"
7782
title="View hourly data (best for 24h periods)"
@@ -81,22 +86,25 @@ export function AnalyticsToolbar({
8186
</Button>
8287
</div>
8388

84-
<Button
85-
aria-label="Refresh data"
86-
className="h-8 w-8"
87-
disabled={isRefreshing}
88-
onClick={onRefresh}
89-
size="icon"
90-
variant="outline"
91-
>
92-
<ArrowClockwiseIcon
93-
aria-hidden="true"
94-
className={`h-4 w-4 ${isRefreshing ? 'animate-spin' : ''}`}
95-
/>
96-
</Button>
89+
<div className="flex items-center gap-2">
90+
<AddFilterForm addFilter={addFilter} buttonText="Filter" />
91+
<Button
92+
aria-label="Refresh data"
93+
className="h-8 w-8"
94+
disabled={isRefreshing}
95+
onClick={onRefresh}
96+
size="icon"
97+
variant="outline"
98+
>
99+
<ArrowClockwiseIcon
100+
aria-hidden="true"
101+
className={`h-4 w-4 ${isRefreshing ? 'animate-spin' : ''}`}
102+
/>
103+
</Button>
104+
</div>
97105
</div>
98106

99-
<div className="flex items-center gap-2 overflow-x-auto rounded-md border bg-background p-1 shadow-sm">
107+
<div className="flex items-center gap-1 overflow-x-auto rounded border bg-background p-1 shadow-sm">
100108
{quickRanges.map((range) => {
101109
const now = new Date();
102110
const start = range.hours
@@ -115,20 +123,19 @@ export function AnalyticsToolbar({
115123

116124
return (
117125
<Button
118-
className={`h-6 cursor-pointer touch-manipulation whitespace-nowrap px-2 text-xs sm:px-2.5 ${isActive ? 'shadow-sm' : 'text-muted-foreground hover:text-foreground'}`}
126+
className={`h-8 cursor-pointer touch-manipulation whitespace-nowrap px-2 font-medium text-xs ${isActive ? 'bg-primary/10 text-primary shadow-sm' : 'text-muted-foreground hover:text-foreground'}`}
119127
key={range.label}
120128
onClick={() => handleQuickRangeSelect(range)}
121129
size="sm"
122130
title={range.fullLabel}
123-
variant={isActive ? 'default' : 'ghost'}
131+
variant={isActive ? 'secondary' : 'ghost'}
124132
>
125-
<span className="sm:hidden">{range.label}</span>
126-
<span className="hidden sm:inline">{range.fullLabel}</span>
133+
{range.label}
127134
</Button>
128135
);
129136
})}
130137

131-
<div className="ml-1 border-border/50 border-l pl-2 sm:pl-3">
138+
<div className="ml-1 border-border/50 border-l pl-2">
132139
<DateRangePicker
133140
className="w-auto"
134141
maxDate={new Date()}

apps/dashboard/app/(main)/websites/[id]/_components/filters-section.tsx

Lines changed: 44 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,21 @@
11
'use client';
22

33
import { type DynamicQueryFilter, filterOptions } from '@databuddy/shared';
4-
import {
5-
FloppyDiskIcon,
6-
FunnelIcon,
7-
PencilIcon,
8-
XIcon,
9-
} from '@phosphor-icons/react';
4+
import { FloppyDiskIcon, PencilIcon, XIcon } from '@phosphor-icons/react';
5+
import { useAtom } from 'jotai';
106
import { useParams } from 'next/navigation';
117
import { useCallback, useState } from 'react';
128
import { Button } from '@/components/ui/button';
13-
import { useFilters } from '@/hooks/use-filters';
9+
1410
import { useSavedFilters } from '@/hooks/use-saved-filters';
11+
import {
12+
dynamicQueryFiltersAtom,
13+
removeDynamicFilterAtom,
14+
} from '@/stores/jotai/filterAtoms';
1515
import { DeleteAllDialog } from './delete-all-dialog';
1616
import { DeleteFilterDialog } from './delete-filter-dialog';
1717
import { SaveFilterDialog } from './save-filter-dialog';
1818
import { SavedFiltersMenu } from './saved-filters-menu';
19-
import { AddFilterForm } from './utils/add-filters';
20-
21-
interface FiltersSectionProps {
22-
selectedFilters: DynamicQueryFilter[];
23-
onFiltersChange: (filters: DynamicQueryFilter[]) => void;
24-
}
2519

2620
function getOperatorSymbol(operator: string): string {
2721
const operatorToSymbolMap: Record<string, string> = {
@@ -38,14 +32,25 @@ function getOperatorSymbol(operator: string): string {
3832
return operatorToSymbolMap[operator] || operator;
3933
}
4034

41-
export function FiltersSection({
42-
selectedFilters,
43-
onFiltersChange,
44-
}: FiltersSectionProps) {
45-
const { addFilter, removeFilter } = useFilters({
46-
filters: selectedFilters,
47-
onFiltersChange,
48-
});
35+
export function FiltersSection() {
36+
const [selectedFilters, setSelectedFilters] = useAtom(
37+
dynamicQueryFiltersAtom
38+
);
39+
const [, removeFilter] = useAtom(removeDynamicFilterAtom);
40+
41+
const handleRemoveFilter = useCallback(
42+
(index: number) => {
43+
if (selectedFilters[index]) {
44+
const filterToRemove = selectedFilters[index];
45+
removeFilter({
46+
field: filterToRemove.field,
47+
operator: filterToRemove.operator,
48+
value: filterToRemove.value,
49+
});
50+
}
51+
},
52+
[selectedFilters, removeFilter]
53+
);
4954

5055
const { id } = useParams();
5156
const websiteId = id as string;
@@ -82,8 +87,8 @@ export function FiltersSection({
8287
const [isDeleteAllDialogOpen, setIsDeleteAllDialogOpen] = useState(false);
8388

8489
const clearAllFilters = useCallback(() => {
85-
onFiltersChange([]);
86-
}, [onFiltersChange]);
90+
setSelectedFilters([]);
91+
}, [setSelectedFilters]);
8792

8893
const handleSaveFilter = useCallback(
8994
(name: string) => {
@@ -116,9 +121,9 @@ export function FiltersSection({
116121

117122
const handleApplyFilter = useCallback(
118123
(filters: DynamicQueryFilter[]) => {
119-
onFiltersChange(filters);
124+
setSelectedFilters(filters);
120125
},
121-
[onFiltersChange]
126+
[setSelectedFilters]
122127
);
123128

124129
const handleDeleteSavedFilter = useCallback(
@@ -164,7 +169,7 @@ export function FiltersSection({
164169
}
165170

166171
// Apply the filter's configuration to current filters
167-
onFiltersChange(filterToEdit.filters);
172+
setSelectedFilters(filterToEdit.filters);
168173

169174
// Set up editing mode with original filters stored
170175
setEditingFilter({
@@ -173,16 +178,16 @@ export function FiltersSection({
173178
originalFilters: [...filterToEdit.filters], // Store original state
174179
});
175180
},
176-
[savedFilters, onFiltersChange]
181+
[savedFilters, setSelectedFilters]
177182
);
178183

179184
const handleCancelEdit = useCallback(() => {
180185
if (editingFilter) {
181186
// Restore original filters
182-
onFiltersChange(editingFilter.originalFilters);
187+
setSelectedFilters(editingFilter.originalFilters);
183188
}
184189
setEditingFilter(null);
185-
}, [editingFilter, onFiltersChange]);
190+
}, [editingFilter, setSelectedFilters]);
186191

187192
const handleSaveEdit = useCallback(() => {
188193
if (!editingFilter || selectedFilters.length === 0) {
@@ -215,8 +220,12 @@ export function FiltersSection({
215220
setIsDeletingAll(false);
216221
}, [deleteAllFilters]);
217222

223+
if (selectedFilters.length === 0) {
224+
return null;
225+
}
226+
218227
return (
219-
<div className="overflow-hidden rounded-lg border bg-card shadow-sm">
228+
<div className="slide-in-from-top-2 animate-in overflow-hidden rounded-lg border bg-card shadow-sm duration-300">
220229
{editingFilter && (
221230
<div className="border-amber-200/50 border-b bg-gradient-to-r from-amber-50/80 to-amber-50/40 px-4 py-3 text-amber-900 text-sm">
222231
<div className="flex items-center justify-between">
@@ -235,7 +244,7 @@ export function FiltersSection({
235244
</div>
236245
<div className="flex shrink-0 gap-2">
237246
<Button
238-
className="h-7 font-medium text-xs"
247+
className="h-8 font-medium text-sm"
239248
data-filter-id={editingFilter.id}
240249
data-total-filters={selectedFilters.length}
241250
data-track="filter_edit_completed"
@@ -247,7 +256,7 @@ export function FiltersSection({
247256
{isSaving ? 'Saving...' : 'Save Changes'}
248257
</Button>
249258
<Button
250-
className="h-7 text-xs"
259+
className="h-8 text-sm"
251260
disabled={isSaving}
252261
onClick={handleCancelEdit}
253262
size="sm"
@@ -256,7 +265,7 @@ export function FiltersSection({
256265
Cancel
257266
</Button>
258267
<Button
259-
className="h-7 text-xs"
268+
className="h-8 text-sm"
260269
onClick={() => {
261270
setIsSaveDialogOpen(true);
262271
}}
@@ -269,21 +278,7 @@ export function FiltersSection({
269278
</div>
270279
</div>
271280
)}
272-
<div className="flex min-h-[52px] flex-wrap items-center gap-3 p-4">
273-
<div className="flex items-center gap-3">
274-
<div className="flex items-center gap-2">
275-
<FunnelIcon className="h-4 w-4 text-primary" weight="duotone" />
276-
<h3 className="font-semibold text-foreground text-sm">
277-
{editingFilter ? 'Edit Filters' : 'Filters'}
278-
</h3>
279-
</div>
280-
{selectedFilters.length > 0 && (
281-
<span className="text-muted-foreground text-xs">
282-
({selectedFilters.length})
283-
</span>
284-
)}
285-
</div>
286-
281+
<div className="flex min-h-[52px] flex-wrap items-center gap-2 p-4">
287282
<div className="flex flex-wrap items-center gap-2">
288283
{selectedFilters.map((filter, index) => {
289284
const fieldLabel = filterOptions.find(
@@ -313,7 +308,7 @@ export function FiltersSection({
313308
<button
314309
aria-label={`Remove filter ${fieldLabel} ${operatorSymbol} ${valueLabel}`}
315310
className="rounded-full p-0.5 text-muted-foreground opacity-60 transition-all hover:bg-destructive/10 hover:text-destructive hover:opacity-100 group-hover:opacity-80"
316-
onClick={() => removeFilter(index)}
311+
onClick={() => handleRemoveFilter(index)}
317312
type="button"
318313
>
319314
<XIcon className="h-3 w-3" weight="bold" />
@@ -368,8 +363,6 @@ export function FiltersSection({
368363
</span>
369364
</div>
370365
)}
371-
372-
<AddFilterForm addFilter={addFilter} buttonText="Add filter" />
373366
</div>
374367
</div>
375368

apps/dashboard/app/(main)/websites/[id]/_components/tabs/overview-tab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -936,7 +936,7 @@ export function WebsiteOverviewTab({
936936
</div>
937937

938938
{/* Chart */}
939-
<div className="rounded border border-sidebar-border bg-sidebar shadow-sm">
939+
<div className="rounded border border-sidebar-border border-b-0 bg-sidebar shadow-sm">
940940
<div className="flex flex-col items-start justify-between gap-3 border-sidebar-border border-b px-4 py-3 sm:flex-row">
941941
<div>
942942
<h2 className="font-semibold text-lg text-sidebar-foreground tracking-tight">

apps/dashboard/app/(main)/websites/[id]/_components/utils/add-filters.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,9 +292,11 @@ function FilterForm({
292292
export function AddFilterForm({
293293
addFilter,
294294
buttonText = 'Filter',
295+
className,
295296
}: {
296297
addFilter: (filter: DynamicQueryFilter) => void;
297298
buttonText?: string;
299+
className?: string;
298300
}) {
299301
const [isOpen, setIsOpen] = useState(false);
300302

@@ -311,7 +313,7 @@ export function AddFilterForm({
311313
aria-expanded={isOpen}
312314
aria-haspopup="menu"
313315
aria-label="Add filter"
314-
className="h-8"
316+
className={className || 'h-8'}
315317
onClick={() => setIsOpen(!isOpen)}
316318
variant="outline"
317319
>

apps/dashboard/app/(main)/websites/[id]/layout.tsx

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@ import { useQueryClient } from '@tanstack/react-query';
44
import { useAtom } from 'jotai';
55
import { useParams, usePathname } from 'next/navigation';
66
import { toast } from 'sonner';
7+
78
import { useTrackingSetup } from '@/hooks/use-tracking-setup';
8-
import {
9-
dynamicQueryFiltersAtom,
10-
isAnalyticsRefreshingAtom,
11-
} from '@/stores/jotai/filterAtoms';
9+
import { isAnalyticsRefreshingAtom } from '@/stores/jotai/filterAtoms';
1210
import { AnalyticsToolbar } from './_components/analytics-toolbar';
1311
import { FiltersSection } from './_components/filters-section';
1412

@@ -22,9 +20,6 @@ export default function WebsiteLayout({ children }: WebsiteLayoutProps) {
2220
const queryClient = useQueryClient();
2321
const { isTrackingSetup } = useTrackingSetup(id as string);
2422
const [isRefreshing, setIsRefreshing] = useAtom(isAnalyticsRefreshingAtom);
25-
const [selectedFilters, setSelectedFilters] = useAtom(
26-
dynamicQueryFiltersAtom
27-
);
2823

2924
const isAssistantPage =
3025
pathname.includes('/assistant') || pathname.includes('/map');
@@ -58,10 +53,7 @@ export default function WebsiteLayout({ children }: WebsiteLayoutProps) {
5853
isRefreshing={isRefreshing}
5954
onRefresh={handleRefresh}
6055
/>
61-
<FiltersSection
62-
onFiltersChange={setSelectedFilters}
63-
selectedFilters={selectedFilters}
64-
/>
56+
<FiltersSection />
6557
</div>
6658
)}
6759

0 commit comments

Comments
 (0)