New settings layouts. Segment management screen.

This commit is contained in:
Mike Cao 2025-08-07 05:14:35 -07:00
parent 2dbcf63eeb
commit eb7b6978d3
70 changed files with 762 additions and 499 deletions

View file

@ -1,33 +0,0 @@
import { useState } from 'react';
import { ToggleGroup, ToggleGroupItem, Box } from '@umami/react-zen';
export interface FilterButtonsProps {
items: { id: string; label: string }[];
value: string;
onChange?: (value: string) => void;
}
export function FilterButtons({ items, value, onChange }: FilterButtonsProps) {
const [selected, setSelected] = useState(value);
const handleChange = (value: string) => {
setSelected(value);
onChange?.(value);
};
return (
<Box>
<ToggleGroup
value={[selected]}
onChange={e => handleChange(e[0])}
disallowEmptySelection={true}
>
{items.map(({ id, label }) => (
<ToggleGroupItem key={id} id={id}>
{label}
</ToggleGroupItem>
))}
</ToggleGroup>
</Box>
);
}

View file

@ -1,79 +0,0 @@
import { useState } from 'react';
import { Column, Tabs, TabList, Tab, TabPanel, Row, Button } from '@umami/react-zen';
import { useDateRange, useMessages } from '@/components/hooks';
import { FieldFilters } from '@/components/input/FieldFilters';
import { SegmentFilters } from '@/components/input/SegmentFilters';
export interface FilterEditFormProps {
websiteId?: string;
filters: any[];
segmentId?: string;
onChange?: (params: { filters: any[]; segment: any }) => void;
onClose?: () => void;
}
export function FilterEditForm({
websiteId,
filters = [],
segmentId,
onChange,
onClose,
}: FilterEditFormProps) {
const { formatMessage, labels } = useMessages();
const [currentFilters, setCurrentFilters] = useState(filters);
const [currentSegment, setCurrentSegment] = useState(segmentId);
const {
dateRange: { startDate, endDate },
} = useDateRange(websiteId);
const handleReset = () => {
setCurrentFilters([]);
setCurrentSegment(null);
};
const handleSave = () => {
onChange?.({ filters: currentFilters.filter(f => f.value), segment: currentSegment });
onClose?.();
};
const handleSegmentChange = (id: string) => {
setCurrentSegment(id);
};
return (
<Column>
<Tabs>
<TabList>
<Tab id="fields">{formatMessage(labels.fields)}</Tab>
<Tab id="segments">{formatMessage(labels.segments)}</Tab>
</TabList>
<TabPanel id="fields">
<FieldFilters
websiteId={websiteId}
filters={currentFilters}
startDate={startDate}
endDate={endDate}
onSave={setCurrentFilters}
/>
</TabPanel>
<TabPanel id="segments">
<SegmentFilters
websiteId={websiteId}
segmentId={currentSegment}
onSave={handleSegmentChange}
/>
</TabPanel>
</Tabs>
<Row alignItems="center" justifyContent="space-between" gridColumn="span 2" gap>
<Button onPress={handleReset}>{formatMessage(labels.reset)}</Button>
<Row alignItems="center" justifyContent="flex-end" gridColumn="span 2" gap>
<Button onPress={onClose}>{formatMessage(labels.cancel)}</Button>
<Button variant="primary" onPress={handleSave}>
{formatMessage(labels.apply)}
</Button>
</Row>
</Row>
</Column>
);
}

View file

@ -27,7 +27,7 @@ export function PageHeader({
{...props}
>
<Row alignItems="center" gap="3">
{icon && <Icon>{icon}</Icon>}
{icon && <Icon size="md">{icon}</Icon>}
{title && <Heading size="4">{title}</Heading>}
{description && <Text color="muted">{description}</Text>}
</Row>

View file

@ -1,27 +1,68 @@
import { ReactNode } from 'react';
import { Text, NavMenu, NavMenuItem, Icon, Row } from '@umami/react-zen';
import {
Text,
Heading,
NavMenu,
NavMenuItem,
Icon,
Row,
Column,
NavMenuGroup,
} from '@umami/react-zen';
import Link from 'next/link';
export interface SideMenuProps {
items: { id: string; label: string; url: string; icon?: ReactNode }[];
items: { label: string; items: { id: string; label: string; icon?: any; path: string }[] }[];
title?: string;
selectedKey?: string;
allowMinimize?: boolean;
}
export function SideMenu({ items, selectedKey }: SideMenuProps) {
export function SideMenu({ items, title, selectedKey, allowMinimize, children }: SideMenuProps) {
return (
<NavMenu highlightColor="3">
{items.map(({ id, label, url, icon }) => {
return (
<Link key={id} href={url}>
<NavMenuItem isSelected={id === selectedKey}>
<Row alignItems="center" gap>
{icon && <Icon>{icon}</Icon>}
<Text>{label}</Text>
</Row>
</NavMenuItem>
</Link>
);
})}
</NavMenu>
<Column
gap
padding
width="240px"
overflowY="auto"
justifyContent="space-between"
position="sticky"
top="0"
backgroundColor
>
{children}
{title && (
<Row padding>
<Heading size="1">{title}</Heading>
</Row>
)}
<NavMenu muteItems={false} gap="6">
{items.map(({ label, items }) => {
return (
<NavMenuGroup
title={label}
key={label}
gap="1"
allowMinimize={allowMinimize}
marginBottom="3"
>
{items.map(({ id, label, icon, path }) => {
const isSelected = selectedKey === id;
return (
<Link key={id} href={path}>
<NavMenuItem isSelected={isSelected}>
<Row alignItems="center" gap>
<Icon>{icon}</Icon>
<Text>{label}</Text>
</Row>
</NavMenuItem>
</Link>
);
})}
</NavMenuGroup>
);
})}
</NavMenu>
</Column>
);
}