Added segment filtering to filter form.

This commit is contained in:
Mike Cao 2025-07-27 02:08:19 -07:00
parent 2e69e57445
commit 2ad624ccc8
15 changed files with 301 additions and 193 deletions

View file

@ -0,0 +1,74 @@
import { Key } from 'react';
import { Grid, Column, List, ListItem } from '@umami/react-zen';
import { useDateRange, useFields, useMessages } from '@/components/hooks';
import { FilterRecord } from '@/components/common/FilterRecord';
import { Empty } from '@/components/common/Empty';
export interface FieldFiltersProps {
websiteId: string;
filters: { name: string; operator: string; value: string }[];
onSave?: (data: any) => void;
}
export function FieldFilters({ websiteId, filters, onSave }: FieldFiltersProps) {
const { formatMessage, messages } = useMessages();
const { fields } = useFields();
const {
dateRange: { startDate, endDate },
} = useDateRange(websiteId);
const updateFilter = (name: string, props: Record<string, any>) => {
onSave(filters.map(filter => (filter.name === name ? { ...filter, ...props } : filter)));
};
const handleAdd = (name: Key) => {
onSave(filters.concat({ name: name.toString(), operator: 'eq', value: '' }));
};
const handleChange = (name: string, value: Key) => {
updateFilter(name, { value });
};
const handleSelect = (name: string, operator: Key) => {
updateFilter(name, { operator });
};
const handleRemove = (name: string) => {
onSave(filters.filter(filter => filter.name !== name));
};
return (
<Grid columns="160px 1fr" overflow="hidden" gapY="6">
<Column border="right" paddingRight="3">
<List onAction={handleAdd}>
{fields.map((field: any) => {
const isDisabled = !!filters.find(({ name }) => name === field.name);
return (
<ListItem key={field.name} id={field.name} isDisabled={isDisabled}>
{field.label}
</ListItem>
);
})}
</List>
</Column>
<Column paddingLeft="6" overflow="auto" gapY="4" height="500px" style={{ contain: 'layout' }}>
{filters.map(filter => {
return (
<FilterRecord
key={filter.name}
websiteId={websiteId}
type={filter.name}
startDate={startDate}
endDate={endDate}
{...filter}
onSelect={handleSelect}
onRemove={handleRemove}
onChange={handleChange}
/>
);
})}
{!filters.length && <Empty message={formatMessage(messages.nothingSelected)} />}
</Column>
</Grid>
);
}

View file

@ -1,4 +1,3 @@
import { MouseEvent } from 'react';
import { Button, Icon, Text, Row, TooltipTrigger, Tooltip } from '@umami/react-zen';
import { useNavigation, useMessages, useFormat, useFilters } from '@/components/hooks';
import { Close } from '@/components/icons';
@ -7,57 +6,55 @@ import { isSearchOperator } from '@/lib/params';
export function FilterBar() {
const { formatMessage, labels } = useMessages();
const { formatValue } = useFormat();
const { router, updateParams } = useNavigation();
const {
router,
updateParams,
replaceParams,
query: { segment },
} = useNavigation();
const { filters, operatorLabels } = useFilters();
const handleCloseFilter = (param: string, e: MouseEvent) => {
e.stopPropagation();
const handleCloseFilter = (param: string) => {
router.push(updateParams({ [param]: undefined }));
};
const handleResetFilter = () => {
router.push(updateParams());
router.push(replaceParams());
};
if (!filters.length) {
const handleSegmentRemove = () => {
router.push(updateParams({ segment: undefined }));
};
if (!filters.length && !segment) {
return null;
}
return (
<Row gap alignItems="center" justifyContent="space-between" padding="2" backgroundColor="3">
<Row alignItems="center" gap="2" wrap="wrap">
{Object.keys(filters).map(key => {
const filter = filters[key];
{segment && (
<FilterItem
name="segment"
label={formatMessage(labels.segment)}
value={segment}
operator={operatorLabels.eq}
onRemove={handleSegmentRemove}
/>
)}
{filters.map(filter => {
const { name, label, operator, value } = filter;
const paramValue = isSearchOperator(operator) ? value : formatValue(value, name);
return (
<Row
<FilterItem
key={name}
border
padding="2"
color
backgroundColor
borderRadius
alignItems="center"
justifyContent="space-between"
theme="dark"
>
<Row alignItems="center" gap="4">
<Row alignItems="center" gap="2">
<Text color="12" weight="bold">
{label}
</Text>
<Text color="11">{operatorLabels[operator]}</Text>
<Text color="12" weight="bold">
{paramValue}
</Text>
</Row>
<Icon onClick={e => handleCloseFilter(name, e)} size="xs">
<Close />
</Icon>
</Row>
</Row>
name={name}
label={label}
operator={operatorLabels[operator]}
value={paramValue}
onRemove={name => handleCloseFilter(name)}
/>
);
})}
</Row>
@ -74,3 +71,33 @@ export function FilterBar() {
</Row>
);
}
const FilterItem = ({ name, label, operator, value, onRemove }) => {
return (
<Row
border
padding="2"
color
backgroundColor
borderRadius
alignItems="center"
justifyContent="space-between"
theme="dark"
>
<Row alignItems="center" gap="4">
<Row alignItems="center" gap="2">
<Text color="12" weight="bold">
{label}
</Text>
<Text color="11">{operator}</Text>
<Text color="12" weight="bold">
{value}
</Text>
</Row>
<Icon onClick={() => onRemove(name)} size="xs">
<Close />
</Icon>
</Row>
</Row>
);
};

View file

@ -0,0 +1,36 @@
import { useState } from 'react';
import { List, Column, ListItem } from '@umami/react-zen';
import { useWebsiteSegmentsQuery } from '@/components/hooks';
import { LoadingPanel } from '@/components/common/LoadingPanel';
export interface SegmentFiltersProps {
websiteId: string;
segmentId: string;
onSave?: (data: any) => void;
}
export function SegmentFilters({ websiteId, segmentId, onSave }: SegmentFiltersProps) {
const { data, isLoading } = useWebsiteSegmentsQuery(websiteId, { type: 'segment' });
const [currentSegment, setCurrentSegment] = useState(segmentId);
const handleSave = (id: string) => {
setCurrentSegment(id);
onSave?.(data.find(item => item.id === id));
};
return (
<Column height="400px" gap>
<LoadingPanel data={data} isLoading={isLoading} overflowY="auto">
<List selectionMode="single" value={[currentSegment]} onChange={id => handleSave(id[0])}>
{data?.map(item => {
return (
<ListItem key={item.id} id={item.id}>
{item.name}
</ListItem>
);
})}
</List>
</LoadingPanel>
</Column>
);
}

View file

@ -100,9 +100,9 @@ export function WebsiteDateFilter({
<Icon fillColor>{compare ? <Close /> : <Compare />}</Icon>
</Button>
<Tooltip>{formatMessage(compare ? labels.cancel : labels.compareDates)}</Tooltip>
<ExportButton websiteId={websiteId} />
</TooltipTrigger>
)}
<ExportButton websiteId={websiteId} />
</Row>
);
}