mirror of
https://github.com/umami-software/umami.git
synced 2026-02-12 16:45:35 +01:00
Filtering via FilterBar.
This commit is contained in:
parent
da173779e0
commit
0a43ef7b1b
9 changed files with 83 additions and 26 deletions
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue