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,11 +1,6 @@
{
"extends": [
"stylelint-config-recommended",
"stylelint-config-css-modules",
"stylelint-config-prettier"
],
"extends": ["stylelint-config-recommended", "stylelint-config-css-modules"],
"rules": {
"no-descending-specificity": null
},
"ignoreFiles": ["**/*.js", "**/*.md"]
}
}

View file

@ -78,7 +78,7 @@
"@react-spring/web": "^9.7.5",
"@tanstack/react-query": "^5.68.0",
"@umami/prisma-client": "^0.16.0",
"@umami/react-zen": "^0.73.0",
"@umami/react-zen": "^0.76.0",
"@umami/redis-client": "^0.27.0",
"bcryptjs": "^2.4.3",
"chalk": "^4.1.2",
@ -178,7 +178,6 @@
"rollup-plugin-postcss": "^4.0.2",
"stylelint": "^15.11.0",
"stylelint-config-css-modules": "^4.4.0",
"stylelint-config-prettier": "^9.0.5",
"stylelint-config-recommended": "^14.0.1",
"tar": "^6.2.1",
"ts-jest": "^29.2.6",

1794
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -38,7 +38,7 @@ export function App({ children }) {
gridRow="2 / 3"
alignItems="center"
overflow="auto"
backgroundColor="1"
backgroundColor="0"
>
<Page>
{children}

View file

@ -17,7 +17,7 @@ export function MenuBar(props: RowProps) {
paddingY="3"
paddingX="3"
paddingRight="5"
backgroundColor="1"
backgroundColor="0"
style={{ borderBottom: '1px solid var(--border-color)' }}
>
<Row>

View file

@ -17,7 +17,7 @@ import { WebsiteTabs } from '@/app/(main)/websites/[websiteId]/WebsiteTabs';
import { useWebsite } from '@/components/hooks/useWebsite';
import { WebsiteFilterButton } from '@/app/(main)/websites/[websiteId]/WebsiteFilterButton';
import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter';
import { FilterTags } from '@/components/metrics/FilterTags';
import { FilterBar } from '@/components/metrics/FilterBar';
import { useMessages } from '@/components/hooks';
export function WebsiteHeader({
@ -73,7 +73,7 @@ export function WebsiteHeader({
</Row>
</Row>
{compareMode && items.map(item => <div key={item.value}>{item.label}</div>)}
<FilterTags websiteId={websiteId} />
<FilterBar websiteId={websiteId} />
<WebsiteTabs websiteId={websiteId} />
</Column>
);

View file

@ -3,7 +3,7 @@ import { Grid } from '@umami/react-zen';
import { Panel } from '@/components/common/Panel';
import { WebsiteHeader } from '../WebsiteHeader';
import { WebsiteMetricsBar } from '../WebsiteMetricsBar';
import { FilterTags } from '@/components/metrics/FilterTags';
import { FilterBar } from '@/components/metrics/FilterBar';
import { WebsiteChart } from '../WebsiteChart';
import { WebsiteCompareTables } from './WebsiteCompareTables';
@ -11,7 +11,7 @@ export function WebsiteComparePage({ websiteId }) {
return (
<Grid gap="3">
<WebsiteHeader websiteId={websiteId} />
<FilterTags websiteId={websiteId} />
<FilterBar websiteId={websiteId} />
<WebsiteMetricsBar websiteId={websiteId} compareMode={true} showFilter={true} />
<Panel>
<WebsiteChart websiteId={websiteId} compareMode={true} />

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>
);
}