aboutsummaryrefslogtreecommitdiff
path: root/src/components/input/FilterBar.tsx
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-01-24 13:09:50 +0000
committerFuwn <[email protected]>2026-01-24 13:09:50 +0000
commit396acf3bbbe00a192cb0ea0a9ccf91b1d8d2850b (patch)
treeb9df4ca6a70db45cfffbae6fdd7252e20fb8e93c /src/components/input/FilterBar.tsx
downloadumami-main.tar.xz
umami-main.zip
Initial commitHEADmain
Created from https://vercel.com/new
Diffstat (limited to 'src/components/input/FilterBar.tsx')
-rw-r--r--src/components/input/FilterBar.tsx155
1 files changed, 155 insertions, 0 deletions
diff --git a/src/components/input/FilterBar.tsx b/src/components/input/FilterBar.tsx
new file mode 100644
index 0000000..5a52e56
--- /dev/null
+++ b/src/components/input/FilterBar.tsx
@@ -0,0 +1,155 @@
+import {
+ Button,
+ Dialog,
+ DialogTrigger,
+ Icon,
+ Modal,
+ Row,
+ Text,
+ Tooltip,
+ TooltipTrigger,
+} from '@umami/react-zen';
+import { SegmentEditForm } from '@/app/(main)/websites/[websiteId]/segments/SegmentEditForm';
+import {
+ useFilters,
+ useFormat,
+ useMessages,
+ useNavigation,
+ useWebsiteSegmentQuery,
+} from '@/components/hooks';
+import { Bookmark, X } from '@/components/icons';
+import { isSearchOperator } from '@/lib/params';
+
+export function FilterBar({ websiteId }: { websiteId: string }) {
+ const { formatMessage, labels } = useMessages();
+ const { formatValue } = useFormat();
+ const {
+ router,
+ pathname,
+ updateParams,
+ replaceParams,
+ query: { segment, cohort },
+ } = useNavigation();
+ const { filters, operatorLabels } = useFilters();
+ const { data, isLoading } = useWebsiteSegmentQuery(websiteId, segment || cohort);
+ const canSaveSegment = filters.length > 0 && !segment && !cohort && !pathname.includes('/share');
+
+ const handleCloseFilter = (param: string) => {
+ router.push(updateParams({ [param]: undefined }));
+ };
+
+ const handleResetFilter = () => {
+ router.push(replaceParams());
+ };
+
+ const handleSegmentRemove = (type: string) => {
+ router.push(updateParams({ [type]: undefined }));
+ };
+
+ if (!filters.length && !segment && !cohort) {
+ return null;
+ }
+
+ return (
+ <Row gap alignItems="center" justifyContent="space-between" padding="2" backgroundColor="3">
+ <Row alignItems="center" gap="2" wrap="wrap">
+ {segment && !isLoading && (
+ <FilterItem
+ name="segment"
+ label={formatMessage(labels.segment)}
+ value={data?.name || segment}
+ operator={operatorLabels.eq}
+ onRemove={() => handleSegmentRemove('segment')}
+ />
+ )}
+ {cohort && !isLoading && (
+ <FilterItem
+ name="cohort"
+ label={formatMessage(labels.cohort)}
+ value={data?.name || cohort}
+ operator={operatorLabels.eq}
+ onRemove={() => handleSegmentRemove('cohort')}
+ />
+ )}
+ {filters.map(filter => {
+ const { name, label, operator, value } = filter;
+ const paramValue = isSearchOperator(operator) ? value : formatValue(value, name);
+
+ return (
+ <FilterItem
+ key={name}
+ name={name}
+ label={label}
+ operator={operatorLabels[operator]}
+ value={paramValue}
+ onRemove={(name: string) => handleCloseFilter(name)}
+ />
+ );
+ })}
+ </Row>
+ <Row alignItems="center">
+ <DialogTrigger>
+ {canSaveSegment && (
+ <TooltipTrigger delay={0}>
+ <Button variant="zero">
+ <Icon>
+ <Bookmark />
+ </Icon>
+ </Button>
+ <Tooltip>
+ <Text>{formatMessage(labels.saveSegment)}</Text>
+ </Tooltip>
+ </TooltipTrigger>
+ )}
+ <Modal>
+ <Dialog title={formatMessage(labels.segment)} style={{ width: 800, minHeight: 300 }}>
+ {({ close }) => {
+ return <SegmentEditForm websiteId={websiteId} onClose={close} filters={filters} />;
+ }}
+ </Dialog>
+ </Modal>
+ </DialogTrigger>
+ <TooltipTrigger delay={0}>
+ <Button variant="zero" onPress={handleResetFilter}>
+ <Icon>
+ <X />
+ </Icon>
+ </Button>
+ <Tooltip>
+ <Text>{formatMessage(labels.clearAll)}</Text>
+ </Tooltip>
+ </TooltipTrigger>
+ </Row>
+ </Row>
+ );
+}
+
+const FilterItem = ({ name, label, operator, value, onRemove }) => {
+ return (
+ <Row
+ border
+ padding="2"
+ color
+ backgroundColor
+ borderRadius
+ alignItems="center"
+ justifyContent="space-between"
+ theme="dark"
+ >
+ <Row alignItems="center" gap="4">
+ <Row alignItems="center" gap="2">
+ <Text color="12" weight="bold">
+ {label}
+ </Text>
+ <Text color="11">{operator}</Text>
+ <Text color="12" weight="bold">
+ {value}
+ </Text>
+ </Row>
+ <Icon onClick={() => onRemove(name)} size="xs" style={{ cursor: 'pointer' }}>
+ <X />
+ </Icon>
+ </Row>
+ </Row>
+ );
+};