Filtering via FilterBar.

This commit is contained in:
Mike Cao 2025-06-19 16:47:18 -07:00
parent da173779e0
commit 0a43ef7b1b
9 changed files with 83 additions and 26 deletions

View file

@ -53,7 +53,7 @@ export function WebsiteChart({
<PageviewsChart <PageviewsChart
key={value} key={value}
data={chartData} data={chartData}
minDate={value === 'all' ? undefined : startDate} minDate={startDate}
maxDate={endDate} maxDate={endDate}
unit={unit} unit={unit}
/> />

View file

@ -1,6 +1,6 @@
import { useState, Key } from 'react'; import { useState, Key } from 'react';
import { Grid, Row, Column, Label, List, ListItem, Button, Heading } from '@umami/react-zen'; import { Grid, Row, Column, Label, List, ListItem, Button, Heading } from '@umami/react-zen';
import { useFilters, useMessages } from '@/components/hooks'; import { useDateRange, useFilters, useMessages } from '@/components/hooks';
import { FilterRecord } from '@/components/common/FilterRecord'; import { FilterRecord } from '@/components/common/FilterRecord';
import { Empty } from '@/components/common/Empty'; import { Empty } from '@/components/common/Empty';
@ -11,10 +11,13 @@ export interface FilterEditFormProps {
onClose?: () => void; onClose?: () => void;
} }
export function FilterEditForm({ data = [], onChange, onClose }: FilterEditFormProps) { export function FilterEditForm({ websiteId, data = [], onChange, onClose }: FilterEditFormProps) {
const { formatMessage, labels, messages } = useMessages(); const { formatMessage, labels, messages } = useMessages();
const [filters, setFilters] = useState(data); const [filters, setFilters] = useState(data);
const { fields } = useFilters(); const { fields } = useFilters();
const {
dateRange: { startDate, endDate },
} = useDateRange(websiteId);
const updateFilter = (name: string, props: { [key: string]: any }) => { const updateFilter = (name: string, props: { [key: string]: any }) => {
setFilters(filters => setFilters(filters =>
@ -66,6 +69,10 @@ export function FilterEditForm({ data = [], onChange, onClose }: FilterEditFormP
return ( return (
<FilterRecord <FilterRecord
key={filter.name} key={filter.name}
websiteId={websiteId}
type={filter.name}
startDate={startDate}
endDate={endDate}
{...filter} {...filter}
onSelect={handleSelect} onSelect={handleSelect}
onRemove={handleRemove} onRemove={handleRemove}

View file

@ -1,8 +1,14 @@
import { Grid, Column, TextField, Label, ListItem, Select, Icon, Button } from '@umami/react-zen'; import { useState } from 'react';
import { useFilters } from '@/components/hooks'; import { Grid, Column, TextField, Label, Select, Icon, Button, ListItem } from '@umami/react-zen';
import { useFilters, useFormat, useWebsiteValuesQuery } from '@/components/hooks';
import { Close } from '@/components/icons'; import { Close } from '@/components/icons';
import { isSearchOperator } from '@/lib/params';
export interface FilterRecordProps { export interface FilterRecordProps {
websiteId: string;
type: string;
startDate: Date;
endDate: Date;
name: string; name: string;
operator: string; operator: string;
value: string; value: string;
@ -12,6 +18,10 @@ export interface FilterRecordProps {
} }
export function FilterRecord({ export function FilterRecord({
websiteId,
type,
startDate,
endDate,
name, name,
operator, operator,
value, value,
@ -20,16 +30,40 @@ export function FilterRecord({
onChange, onChange,
}: FilterRecordProps) { }: FilterRecordProps) {
const { fields, operators } = useFilters(); const { fields, operators } = useFilters();
const [selected, setSelected] = useState(value);
const [search, setSearch] = useState('');
const { formatValue } = useFormat();
const { data, isLoading } = useWebsiteValuesQuery({
websiteId,
type,
search,
startDate,
endDate,
});
const isSearch = isSearchOperator(operator);
const handleSearch = value => {
setSearch(value);
};
const handleSelectOperator = value => {
onSelect?.(name, value);
};
const handleSelectValue = value => {
setSelected(value);
onChange?.(name, value);
};
return ( return (
<> <Column>
<Label>{fields.find(f => f.name === name)?.label}</Label> <Label>{fields.find(f => f.name === name)?.label}</Label>
<Grid columns="1fr auto" gap> <Grid columns="1fr auto" gap>
<Grid columns="200px 1fr" gap> <Grid columns="200px 1fr" gap>
<Select <Select
items={operators.filter(({ type }) => type === 'string')} items={operators.filter(({ type }) => type === 'string')}
value={operator} value={operator}
onSelectionChange={value => onSelect?.(name, value)} onSelectionChange={handleSelectOperator}
> >
{({ name, label }: any) => { {({ name, label }: any) => {
return ( return (
@ -39,7 +73,28 @@ export function FilterRecord({
); );
}} }}
</Select> </Select>
<TextField value={value} onChange={e => onChange?.(name, e.target.value)} /> {isSearch && (
<TextField value={selected} defaultValue={selected} onChange={handleSelectValue} />
)}
{!isSearch && (
<Select
items={data}
value={selected}
onChange={handleSelectValue}
searchValue={search}
onSearch={handleSearch}
isLoading={isLoading}
allowSearch
>
{data?.map(({ value }) => {
return (
<ListItem key={value} id={value}>
{formatValue(value, type)}
</ListItem>
);
})}
</Select>
)}
</Grid> </Grid>
<Column justifyContent="flex-end"> <Column justifyContent="flex-end">
<Button variant="quiet" onPress={() => onRemove?.(name)}> <Button variant="quiet" onPress={() => onRemove?.(name)}>
@ -49,6 +104,6 @@ export function FilterRecord({
</Button> </Button>
</Column> </Column>
</Grid> </Grid>
</> </Column>
); );
} }

View file

@ -3,10 +3,7 @@ import { useApi } from '../useApi';
const selector = (state: { user: any }) => state.user; const selector = (state: { user: any }) => state.user;
export function useLoginQuery(): { export function useLoginQuery() {
user: any;
setUser: (data: any) => void;
} {
const { post, useQuery } = useApi(); const { post, useQuery } = useApi();
const user = useApp(selector); const user = useApp(selector);

View file

@ -27,11 +27,12 @@ export function useDateRange(websiteId?: string) {
const startDate = new Date(mindate); const startDate = new Date(mindate);
const endDate = new Date(maxdate); const endDate = new Date(maxdate);
const unit = getMinimumUnit(startDate, endDate);
dateRange = { dateRange = {
startDate, startDate,
endDate, endDate,
unit: getMinimumUnit(startDate, endDate), unit,
value, value,
}; };
} else { } else {

View file

@ -9,17 +9,14 @@ export interface DateFilterProps {
value: string; value: string;
startDate: Date; startDate: Date;
endDate: Date; endDate: Date;
offset?: number;
className?: string;
onChange?: (value: string) => void; onChange?: (value: string) => void;
showAllTime?: boolean; showAllTime?: boolean;
alignment?: 'start' | 'center' | 'end';
} }
export function DateFilter({ export function DateFilter({
value,
startDate, startDate,
endDate, endDate,
value,
onChange, onChange,
showAllTime = false, showAllTime = false,
}: DateFilterProps) { }: DateFilterProps) {

View file

@ -24,18 +24,19 @@ export function FilterBar() {
} }
return ( return (
<Row gap alignItems="center" justifyContent="space-between" paddingY="3"> <Row gap alignItems="center" justifyContent="space-between" padding backgroundColor="3">
<Row alignItems="center" gap="3" wrap="wrap"> <Row alignItems="center" gap="2" wrap="wrap">
{Object.keys(filters).map(key => { {Object.keys(filters).map(key => {
const filter = filters[key]; const filter = filters[key];
const { name, label, operator, value } = filter; const { name, label, operator, value } = filter;
const paramValue = isSearchOperator(operator) ? value : formatValue(value, key); const paramValue = isSearchOperator(operator) ? value : formatValue(value, name);
return ( return (
<Row <Row
key={name} key={name}
border border
padding padding="2"
color
backgroundColor backgroundColor
borderRadius borderRadius
alignItems="center" alignItems="center"
@ -61,7 +62,7 @@ export function FilterBar() {
})} })}
</Row> </Row>
<TooltipTrigger delay={0}> <TooltipTrigger delay={0}>
<Button variant="quiet" onPress={handleResetFilter}> <Button variant="wrapper" onPress={handleResetFilter} style={{ alignSelf: 'flex-start' }}>
<Icon> <Icon>
<Close /> <Close />
</Icon> </Icon>

View file

@ -78,7 +78,6 @@ export function WebsiteDateFilter({
value={value} value={value}
startDate={startDate} startDate={startDate}
endDate={endDate} endDate={endDate}
offset={offset}
onChange={handleChange} onChange={handleChange}
showAllTime={showAllTime} showAllTime={showAllTime}
/> />

View file

@ -13,7 +13,7 @@ export interface EventsChartProps extends BarChartProps {
export function EventsChart({ websiteId, focusLabel }: EventsChartProps) { export function EventsChart({ websiteId, focusLabel }: EventsChartProps) {
const { const {
dateRange: { startDate, endDate, unit, value }, dateRange: { startDate, endDate, unit },
} = useDateRange(websiteId); } = useDateRange(websiteId);
const { locale } = useLocale(); const { locale } = useLocale();
const { data, isLoading, error } = useWebsiteEventsSeriesQuery(websiteId); const { data, isLoading, error } = useWebsiteEventsSeriesQuery(websiteId);
@ -59,7 +59,7 @@ export function EventsChart({ websiteId, focusLabel }: EventsChartProps) {
{chartData && ( {chartData && (
<BarChart <BarChart
chartData={chartData} chartData={chartData}
minDate={value === 'all' ? undefined : startDate} minDate={startDate}
maxDate={endDate} maxDate={endDate}
unit={unit} unit={unit}
stacked={true} stacked={true}