mirror of
https://github.com/umami-software/umami.git
synced 2026-02-06 13:47:15 +01:00
New filter bar and filter edit form.
This commit is contained in:
parent
47e89afcb4
commit
bfdd3f9525
19 changed files with 300 additions and 150 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { Icon, Text, Flexbox } from '@umami/react-zen';
|
||||
import { Icon, Text, Column } from '@umami/react-zen';
|
||||
import { Icons } from '@/components/icons';
|
||||
|
||||
export interface EmptyPlaceholderProps {
|
||||
|
|
@ -9,12 +9,12 @@ export interface EmptyPlaceholderProps {
|
|||
|
||||
export function EmptyPlaceholder({ message, children }: EmptyPlaceholderProps) {
|
||||
return (
|
||||
<Flexbox direction="column" alignItems="center" justifyContent="center" gap={60} height={600}>
|
||||
<Column alignItems="center" justifyContent="center" gap="5" height="100%" width="100%">
|
||||
<Icon size="xl">
|
||||
<Icons.Logo />
|
||||
</Icon>
|
||||
<Text size="lg">{message}</Text>
|
||||
<div>{children}</div>
|
||||
</Flexbox>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
86
src/components/common/FilterEditForm.tsx
Normal file
86
src/components/common/FilterEditForm.tsx
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import { useState, Key } from 'react';
|
||||
import { Grid, Row, Column, Label, List, ListItem, Button, Heading } from '@umami/react-zen';
|
||||
import { useFilters, useMessages } from '@/components/hooks';
|
||||
import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder';
|
||||
import { FilterRecord } from '@/components/common/FilterRecord';
|
||||
|
||||
export interface FilterEditFormProps {
|
||||
websiteId?: string;
|
||||
data: any[];
|
||||
onChange?: (filters: { name: string; type: string; operator: string; value: string }[]) => void;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export function FilterEditForm({ data = [], onChange, onClose }: FilterEditFormProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const [filters, setFilters] = useState(data);
|
||||
const { fields } = useFilters();
|
||||
|
||||
const updateFilter = (name: string, props: { [key: string]: any }) => {
|
||||
setFilters(filters =>
|
||||
filters.map(filter => (filter.name === name ? { ...filter, ...props } : filter)),
|
||||
);
|
||||
};
|
||||
|
||||
const handleAdd = (name: Key) => {
|
||||
setFilters(filters.concat({ name, 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) => {
|
||||
setFilters(filters.filter(filter => filter.name !== name));
|
||||
};
|
||||
|
||||
const handleApply = () => {
|
||||
onChange?.(filters.filter(f => f.value));
|
||||
onClose?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid columns="160px 1fr" width="760px" gapY="6">
|
||||
<Row gridColumn="span 2">
|
||||
<Heading>{formatMessage(labels.filters)}</Heading>
|
||||
</Row>
|
||||
<Column border="right" paddingRight="3">
|
||||
<Label>Fields</Label>
|
||||
<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">
|
||||
{filters.map(filter => {
|
||||
return (
|
||||
<FilterRecord
|
||||
key={filter.name}
|
||||
{...filter}
|
||||
onSelect={handleSelect}
|
||||
onRemove={handleRemove}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{!filters.length && <EmptyPlaceholder message="No filters selected." />}
|
||||
</Column>
|
||||
<Row alignItems="center" justifyContent="flex-end" gridColumn="span 2" gap>
|
||||
<Button onPress={onClose}>{formatMessage(labels.cancel)}</Button>
|
||||
<Button variant="primary" onPress={handleApply}>
|
||||
{formatMessage(labels.apply)}
|
||||
</Button>
|
||||
</Row>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
64
src/components/common/FilterRecord.tsx
Normal file
64
src/components/common/FilterRecord.tsx
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import {
|
||||
Grid,
|
||||
Row,
|
||||
Column,
|
||||
TextField,
|
||||
Label,
|
||||
ListItem,
|
||||
Select,
|
||||
Icon,
|
||||
Icons,
|
||||
Button,
|
||||
} from '@umami/react-zen';
|
||||
import { useFilters } from '@/components/hooks';
|
||||
|
||||
export interface FilterRecordProps {
|
||||
name: string;
|
||||
operator: string;
|
||||
value: string;
|
||||
onSelect?: (name: string, value: any) => void;
|
||||
onRemove?: (name: string) => void;
|
||||
onChange?: (name: string, value: string) => void;
|
||||
}
|
||||
|
||||
export function FilterRecord({
|
||||
name,
|
||||
operator,
|
||||
value,
|
||||
onSelect,
|
||||
onRemove,
|
||||
onChange,
|
||||
}: FilterRecordProps) {
|
||||
const { fields, operators } = useFilters();
|
||||
|
||||
return (
|
||||
<Grid columns="1fr auto">
|
||||
<Column>
|
||||
<Label>{fields.find(f => f.name === name)?.label}</Label>
|
||||
<Row gap alignItems="center">
|
||||
<Select
|
||||
items={operators.filter(({ type }) => type === 'string')}
|
||||
selectedKey={operator}
|
||||
onSelectionChange={value => onSelect?.(name, value)}
|
||||
>
|
||||
{({ name, label }: any) => {
|
||||
return (
|
||||
<ListItem key={name} id={name}>
|
||||
{label}
|
||||
</ListItem>
|
||||
);
|
||||
}}
|
||||
</Select>
|
||||
<TextField value={value} onChange={e => onChange?.(name, e.target.value)} />
|
||||
</Row>
|
||||
</Column>
|
||||
<Column justifyContent="flex-end">
|
||||
<Button variant="quiet" onPress={() => onRemove?.(name)}>
|
||||
<Icon>
|
||||
<Icons.Close />
|
||||
</Icon>
|
||||
</Button>
|
||||
</Column>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue