fix: improvements for frontend

This commit is contained in:
Carl-Gerhard Lindesvärd
2025-11-04 11:03:32 +01:00
parent 3474fbd12d
commit b51bc8f3f6
38 changed files with 487 additions and 415 deletions

View File

@@ -3,10 +3,12 @@ import {
useEventQueryFilters,
useEventQueryNamesFilter,
} from '@/hooks/use-event-query-filters';
import { pushModal } from '@/modals';
import type { OverviewFiltersProps } from '@/modals/overview-filters';
import { getPropertyLabel } from '@/translations/properties';
import { cn } from '@/utils/cn';
import { operators } from '@openpanel/constants';
import { X } from 'lucide-react';
import { FilterIcon, X } from 'lucide-react';
import type { Options as NuqsOptions } from 'nuqs';
interface OverviewFiltersButtonsProps {
@@ -14,6 +16,23 @@ interface OverviewFiltersButtonsProps {
nuqsOptions?: NuqsOptions;
}
export function OverviewFilterButton(props: OverviewFiltersProps) {
return (
<Button
variant="outline"
responsive
icon={FilterIcon}
onClick={() =>
pushModal('OverviewFilters', {
...props,
})
}
>
Filters
</Button>
);
}
export function OverviewFiltersButtons({
className,
nuqsOptions,

View File

@@ -1,199 +0,0 @@
import { PureFilterItem } from '@/components/report/sidebar/filters/FilterItem';
import { Button } from '@/components/ui/button';
import { Combobox } from '@/components/ui/combobox';
import { SheetHeader, SheetTitle } from '@/components/ui/sheet';
import { useEventNames } from '@/hooks/use-event-names';
import { useEventProperties } from '@/hooks/use-event-properties';
import {
useEventQueryFilters,
useEventQueryNamesFilter,
} from '@/hooks/use-event-query-filters';
import { useProfileProperties } from '@/hooks/use-profile-properties';
import { useProfileValues } from '@/hooks/use-profile-values';
import { usePropertyValues } from '@/hooks/use-property-values';
import { XIcon } from 'lucide-react';
import type { Options as NuqsOptions } from 'nuqs';
import type {
IChartEventFilter,
IChartEventFilterOperator,
IChartEventFilterValue,
} from '@openpanel/validation';
import { ComboboxEvents } from '@/components/ui/combobox-events';
import { OriginFilter } from './origin-filter';
export interface OverviewFiltersDrawerContentProps {
projectId: string;
nuqsOptions?: NuqsOptions;
enableEventsFilter?: boolean;
mode: 'profiles' | 'events';
}
const excludePropertyFilter = (name: string) => {
return ['*', 'duration', 'created_at', 'has_profile'].includes(name);
};
export function OverviewFiltersDrawerContent({
projectId,
nuqsOptions,
enableEventsFilter,
mode,
}: OverviewFiltersDrawerContentProps) {
const [filters, setFilter] = useEventQueryFilters(nuqsOptions);
const [event, setEvent] = useEventQueryNamesFilter(nuqsOptions);
const eventNames = useEventNames({ projectId });
const eventProperties = useEventProperties({ projectId, event: event[0] });
const profileProperties = useProfileProperties(projectId);
const properties = mode === 'events' ? eventProperties : profileProperties;
return (
<div>
<SheetHeader className="mb-8">
<SheetTitle>Overview filters</SheetTitle>
</SheetHeader>
<div className="mt-8 flex flex-col rounded-md border bg-def-100">
<div className="flex flex-col gap-4 p-4">
<OriginFilter />
{enableEventsFilter && (
<ComboboxEvents
className="w-full"
value={event}
onChange={setEvent}
multiple
items={eventNames.filter(
(item) => !excludePropertyFilter(item.name),
)}
placeholder="Select event"
maxDisplayItems={2}
/>
)}
<Combobox
className="w-full"
onChange={(value) => {
setFilter(value, [], 'is');
}}
value=""
placeholder="Filter by property"
label="What do you want to filter by?"
items={properties
.filter((item) => item !== 'name')
.map((item) => ({
label: item,
value: item,
}))}
searchable
size="lg"
/>
</div>
{filters
.filter((filter) => filter.value[0] !== null)
.map((filter) => {
return mode === 'events' ? (
<PureFilterItem
className="border-t p-4 first:border-0"
eventName="screen_view"
key={filter.name}
filter={filter}
onRemove={() => {
setFilter(filter.name, [], filter.operator);
}}
onChangeValue={(value) => {
setFilter(filter.name, value, filter.operator);
}}
onChangeOperator={(operator) => {
setFilter(filter.name, filter.value, operator);
}}
/>
) : /* TODO: Implement profile filters */
null;
})}
</div>
</div>
);
}
export function FilterOptionEvent({
setFilter,
projectId,
...filter
}: IChartEventFilter & {
projectId: string;
setFilter: (
name: string,
value: IChartEventFilterValue,
operator: IChartEventFilterOperator,
) => void;
}) {
const values = usePropertyValues({
projectId,
event: filter.name === 'path' ? 'screen_view' : 'session_start',
property: filter.name,
});
return (
<div className="flex items-center gap-2">
<div>{filter.name}</div>
<Combobox
className="flex-1"
onChange={(value) => setFilter(filter.name, value, filter.operator)}
placeholder={'Select a value'}
items={values.map((value) => ({
value,
label: value,
}))}
value={String(filter.value[0] ?? '')}
/>
<Button
size="icon"
variant="ghost"
onClick={() =>
setFilter(filter.name, filter.value[0] ?? '', filter.operator)
}
>
<XIcon />
</Button>
</div>
);
}
export function FilterOptionProfile({
setFilter,
projectId,
...filter
}: IChartEventFilter & {
projectId: string;
setFilter: (
name: string,
value: IChartEventFilterValue,
operator: IChartEventFilterOperator,
) => void;
}) {
const values = useProfileValues(projectId, filter.name);
return (
<div className="flex items-center gap-2">
<div>{filter.name}</div>
<Combobox
className="flex-1"
onChange={(value) => setFilter(filter.name, value, filter.operator)}
placeholder={'Select a value'}
items={values.map((value) => ({
value,
label: value,
}))}
value={String(filter.value[0] ?? '')}
/>
<Button
size="icon"
variant="ghost"
onClick={() =>
setFilter(filter.name, filter.value[0] ?? '', filter.operator)
}
>
<XIcon />
</Button>
</div>
);
}

View File

@@ -1,23 +0,0 @@
import { Button } from '@/components/ui/button';
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
import { FilterIcon } from 'lucide-react';
import type { OverviewFiltersDrawerContentProps } from './overview-filters-drawer-content';
import { OverviewFiltersDrawerContent } from './overview-filters-drawer-content';
export function OverviewFiltersDrawer(
props: OverviewFiltersDrawerContentProps,
) {
return (
<Sheet>
<SheetTrigger asChild>
<Button variant="outline" responsive icon={FilterIcon}>
Filters
</Button>
</SheetTrigger>
<SheetContent className="w-full !max-w-lg" side="right">
<OverviewFiltersDrawerContent {...props} />
</SheetContent>
</Sheet>
);
}

View File

@@ -4,46 +4,21 @@ import {
isMinuteIntervalEnabledByRange,
} from '@openpanel/constants';
import { ClockIcon } from 'lucide-react';
import { ReportInterval } from '../report/ReportInterval';
import { Combobox } from '../ui/combobox';
export function OverviewInterval() {
const { interval, setInterval, range } = useOverviewOptions();
const { interval, setInterval, range, startDate, endDate } =
useOverviewOptions();
return (
<Combobox
className="hidden md:flex"
icon={ClockIcon}
placeholder="Interval"
onChange={(value) => {
setInterval(value);
}}
value={interval}
items={[
{
value: 'minute',
label: 'Minute',
disabled: !isMinuteIntervalEnabledByRange(range),
},
{
value: 'hour',
label: 'Hour',
disabled: !isHourIntervalEnabledByRange(range),
},
{
value: 'day',
label: 'Day',
},
{
value: 'week',
label: 'Week',
},
{
value: 'month',
label: 'Month',
disabled:
range === 'today' || range === 'lastHour' || range === '30min',
},
]}
<ReportInterval
interval={interval}
onChange={setInterval}
range={range}
chartType="linear"
startDate={startDate}
endDate={endDate}
/>
);
}