fix: improvements for frontend
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user