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

@@ -23,6 +23,7 @@ import EditReport from './edit-report';
import EventDetails from './event-details';
import OnboardingTroubleshoot from './onboarding-troubleshoot';
import OverviewChartDetails from './overview-chart-details';
import OverviewFilters from './overview-filters';
import RequestPasswordReset from './request-reset-password';
import SaveReport from './save-report';
import ShareOverviewModal from './share-overview-modal';
@@ -52,6 +53,7 @@ const modals = {
OverviewChartDetails: OverviewChartDetails,
AddIntegration: AddIntegration,
AddNotificationRule: AddNotificationRule,
OverviewFilters: OverviewFilters,
CreateInvite: CreateInvite,
};

View File

@@ -0,0 +1,157 @@
import { PureFilterItem } from '@/components/report/sidebar/filters/FilterItem';
import { Button } from '@/components/ui/button';
import { Combobox } from '@/components/ui/combobox';
import { SheetContent } from '@/components/ui/sheet';
import { useEventNames } from '@/hooks/use-event-names';
import {
useEventQueryFilters,
useEventQueryNamesFilter,
} from '@/hooks/use-event-query-filters';
import { useProfileValues } from '@/hooks/use-profile-values';
import { FilterIcon, XIcon } from 'lucide-react';
import type { Options as NuqsOptions } from 'nuqs';
import type {
IChartEventFilter,
IChartEventFilterOperator,
IChartEventFilterValue,
} from '@openpanel/validation';
import { OriginFilter } from '@/components/overview/filters/origin-filter';
import { PropertiesCombobox } from '@/components/report/sidebar/PropertiesCombobox';
import { ComboboxEvents } from '@/components/ui/combobox-events';
import { useAppParams } from '@/hooks/use-app-params';
import { cn } from '@/utils/cn';
import { ModalHeader } from './Modal/Container';
export interface OverviewFiltersProps {
nuqsOptions?: NuqsOptions;
enableEventsFilter?: boolean;
mode?: 'events' | 'profile';
}
export default function OverviewFilters({
nuqsOptions,
enableEventsFilter,
mode,
}: OverviewFiltersProps) {
const { projectId } = useAppParams();
const [filters, setFilter] = useEventQueryFilters(nuqsOptions);
const [event, setEvent] = useEventQueryNamesFilter(nuqsOptions);
const eventNames = useEventNames({ projectId });
const selectedFilters = filters.filter((filter) => filter.value[0] !== null);
return (
<SheetContent className="[&>button.absolute]:hidden">
<ModalHeader title="Filters" />
<div className="flex flex-col gap-4">
<OriginFilter />
{enableEventsFilter && (
<ComboboxEvents
size="lg"
className="w-full"
value={event}
onChange={setEvent}
multiple
items={eventNames}
placeholder="Select event"
maxDisplayItems={2}
searchable
/>
)}
</div>
<div className="flex flex-col gap-2">
<div
className={cn(
'bg-def-200 rounded-lg border',
selectedFilters.length === 0 && 'hidden',
)}
>
{selectedFilters.map((filter) => {
return (
<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);
}}
/>
);
})}
</div>
<PropertiesCombobox
mode={mode}
exclude={[
'properties.*',
'name',
'duration',
'created_at',
'has_profile',
]}
onSelect={(action) => {
setFilter(action.value, [], 'is');
}}
>
{(setOpen) => (
<Button
onClick={() => setOpen((p) => !p)}
variant="outline"
size="lg"
className="w-full"
icon={FilterIcon}
>
Add filter
</Button>
)}
</PropertiesCombobox>
</div>
</SheetContent>
);
}
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

@@ -3,7 +3,7 @@ import { Button } from '@/components/ui/button';
import { useAppParams } from '@/hooks/use-app-params';
import { handleError } from '@/integrations/trpc/react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter } from '@tanstack/react-router';
import { useNavigate } from '@tanstack/react-router';
import { useForm } from 'react-hook-form';
import { toast } from 'sonner';
import type { z } from 'zod';
@@ -22,6 +22,7 @@ type IForm = z.infer<typeof validator>;
export default function ShareOverviewModal() {
const { projectId, organizationId } = useAppParams();
const navigate = useNavigate();
const { register, handleSubmit } = useForm<IForm>({
resolver: zodResolver(validator),
@@ -44,6 +45,16 @@ export default function ShareOverviewModal() {
description: `Your overview is now ${
res.public ? 'public' : 'private'
}`,
action: {
label: 'View',
onClick: () =>
navigate({
to: '/share/overview/$shareId',
params: {
shareId: res.id,
},
}),
},
});
popModal();
},