Updated filter bar.

This commit is contained in:
Mike Cao 2025-04-02 23:18:03 -05:00
parent 34a8fa100c
commit 2b99274895
11 changed files with 1101 additions and 1048 deletions

View file

@ -1,5 +1,5 @@
import classNames from 'classnames';
import { Icon, Icons } from '@umami/react-zen';
import { Icon, Icons, Text } from '@umami/react-zen';
import { ReactNode } from 'react';
import styles from './ChangeLabel.module.css';
@ -38,7 +38,7 @@ export function ChangeLabel({
<Icons.Arrow />
</Icon>
)}
{children || value}
<Text>{children || value}</Text>
</div>
);
}

View file

@ -0,0 +1,144 @@
import { MouseEvent } from 'react';
import {
Button,
Icon,
Icons,
Popover,
MenuTrigger,
Text,
Row,
TooltipTrigger,
Tooltip,
} from '@umami/react-zen';
import {
useDateRange,
useFields,
useNavigation,
useMessages,
useFormat,
useFilters,
} from '@/components/hooks';
import { FieldFilterEditForm } from '@/app/(main)/reports/[reportId]/FieldFilterEditForm';
import { FILTER_COLUMNS, OPERATOR_PREFIXES } from '@/lib/constants';
import { isSearchOperator, parseParameterValue } from '@/lib/params';
import { WebsiteFilterButton } from '@/app/(main)/websites/[websiteId]/WebsiteFilterButton';
export function FilterBar({ websiteId }: { websiteId: string }) {
const { formatMessage, labels } = useMessages();
const { formatValue } = useFormat();
const { dateRange } = useDateRange(websiteId);
const {
router,
renderUrl,
query: { view },
} = useNavigation();
const { fields } = useFields();
const { operatorLabels } = useFilters();
const { startDate, endDate } = dateRange;
const { query } = useNavigation();
const params = Object.keys(query).reduce((obj, key) => {
if (FILTER_COLUMNS[key]) {
obj[key] = query[key];
}
return obj;
}, {});
if (Object.keys(params).filter(key => params[key]).length === 0) {
return null;
}
const handleCloseFilter = (param: string, e: MouseEvent) => {
e.stopPropagation();
router.push(renderUrl({ [param]: undefined }));
};
const handleResetFilter = () => {
router.push(renderUrl({ view }, true));
};
const handleChangeFilter = (
values: { name: string; operator: string; value: string },
close: () => void,
) => {
const { name, operator, value } = values;
const prefix = OPERATOR_PREFIXES[operator];
router.push(renderUrl({ [name]: prefix + value }));
close();
};
return (
<Row
className="dark-theme"
gap="3"
backgroundColor="3"
alignItems="center"
justifyContent="space-between"
paddingY="3"
paddingLeft="5"
paddingRight="2"
border
borderRadius="2"
>
<Row alignItems="center" gap="3" wrap="wrap">
<Text color="11" weight="bold">
{formatMessage(labels.filters)}
</Text>
{Object.keys(params).map(key => {
if (!params[key]) {
return null;
}
const label = fields.find(f => f.name === key)?.label;
const { operator, value } = parseParameterValue(params[key]);
const paramValue = isSearchOperator(operator) ? value : formatValue(value, key);
return (
<MenuTrigger key={key}>
<Button variant="outline">
<Row alignItems="center" gap="2">
<Text weight="bold">{label}</Text>
<Text transform="uppercase" color="muted">
{operatorLabels[operator]}
</Text>
<Text weight="bold">{paramValue}</Text>
<Icon onClick={e => handleCloseFilter(key, e)}>
<Icons.Close />
</Icon>
</Row>
</Button>
<Popover placement="start">
{({ close }: any) => {
return (
<FieldFilterEditForm
label={label}
type="string"
websiteId={websiteId}
name={key}
operator={operator}
defaultValue={value}
startDate={startDate}
endDate={endDate}
onChange={values => handleChangeFilter(values, close)}
/>
);
}}
</Popover>
</MenuTrigger>
);
})}
<WebsiteFilterButton websiteId={websiteId} alignment="center" showText={false} />
</Row>
<TooltipTrigger delay={0}>
<Button variant="quiet" onPress={handleResetFilter}>
<Icon>
<Icons.Close />
</Icon>
</Button>
<Tooltip>
<Text>{formatMessage(labels.clearAll)}</Text>
</Tooltip>
</TooltipTrigger>
</Row>
);
}

View file

@ -1,60 +0,0 @@
.filters {
display: flex;
align-items: center;
gap: 10px;
background: var(--base75);
padding: 10px 20px;
border: 1px solid var(--base400);
border-radius: 8px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.label {
font-weight: 700;
}
.tag {
display: flex;
flex-direction: row;
align-items: center;
gap: 4px;
font-size: 12px;
background: var(--base50);
border: 1px solid var(--base400);
border-radius: var(--border-radius);
box-shadow: 1px 1px 1px var(--base500);
padding: 6px 14px;
cursor: pointer;
}
.tag:hover {
background: var(--base100);
}
.close {
font-weight: 700;
align-self: center;
margin-left: auto;
}
.name,
.value {
color: var(--base700);
font-weight: 700;
}
.operator {
text-transform: lowercase;
font-weight: 900;
}
.icon {
margin-left: 10px;
padding: 2px;
border-radius: 100%;
}
.icon:hover {
background: var(--base200);
}

View file

@ -1,123 +0,0 @@
import { MouseEvent } from 'react';
import { Button, Icon, Icons, Popover, MenuTrigger, Text, Row } from '@umami/react-zen';
import {
useDateRange,
useFields,
useNavigation,
useMessages,
useFormat,
useFilters,
} from '@/components/hooks';
import { FieldFilterEditForm } from '@/app/(main)/reports/[reportId]/FieldFilterEditForm';
import { FILTER_COLUMNS, OPERATOR_PREFIXES } from '@/lib/constants';
import { isSearchOperator, parseParameterValue } from '@/lib/params';
import styles from './FilterTags.module.css';
import { WebsiteFilterButton } from '@/app/(main)/websites/[websiteId]/WebsiteFilterButton';
export function FilterTags({ websiteId }: { websiteId: string }) {
const { formatMessage, labels } = useMessages();
const { formatValue } = useFormat();
const { dateRange } = useDateRange(websiteId);
const {
router,
renderUrl,
query: { view },
} = useNavigation();
const { fields } = useFields();
const { operatorLabels } = useFilters();
const { startDate, endDate } = dateRange;
const { query } = useNavigation();
const params = Object.keys(query).reduce((obj, key) => {
if (FILTER_COLUMNS[key]) {
obj[key] = query[key];
}
return obj;
}, {});
if (Object.keys(params).filter(key => params[key]).length === 0) {
return null;
}
const handleCloseFilter = (param: string, e: MouseEvent) => {
e.stopPropagation();
router.push(renderUrl({ [param]: undefined }));
};
const handleResetFilter = () => {
router.push(renderUrl({ view }, true));
};
const handleChangeFilter = (
values: { name: string; operator: string; value: string },
close: () => void,
) => {
const { name, operator, value } = values;
const prefix = OPERATOR_PREFIXES[operator];
router.push(renderUrl({ [name]: prefix + value }));
close();
};
return (
<Row
gap="3"
backgroundColor="2"
alignItems="center"
paddingX="5"
paddingY="3"
border
borderRadius="2"
wrap="wrap"
>
<Text weight="bold">{formatMessage(labels.filters)}</Text>
{Object.keys(params).map(key => {
if (!params[key]) {
return null;
}
const label = fields.find(f => f.name === key)?.label;
const { operator, value } = parseParameterValue(params[key]);
const paramValue = isSearchOperator(operator) ? value : formatValue(value, key);
return (
<MenuTrigger key={key}>
<Button variant="outline">
<Row alignItems="center" gap="3">
<Text weight="bold">{label}</Text>
<Text>{operatorLabels[operator]}</Text>
<Text weight="bold">{paramValue}</Text>
<Icon onClick={e => handleCloseFilter(key, e)}>
<Icons.Close />
</Icon>
</Row>
</Button>
<Popover placement="start">
{({ close }: any) => {
return (
<FieldFilterEditForm
label={label}
type="string"
websiteId={websiteId}
name={key}
operator={operator}
defaultValue={value}
startDate={startDate}
endDate={endDate}
onChange={values => handleChangeFilter(values, close)}
/>
);
}}
</Popover>
</MenuTrigger>
);
})}
<WebsiteFilterButton websiteId={websiteId} alignment="center" showText={false} />
<Button className={styles.close} variant="quiet" onPress={handleResetFilter}>
<Icon>
<Icons.Close />
</Icon>
<Text>{formatMessage(labels.clearAll)}</Text>
</Button>
</Row>
);
}