ui: reorganising side menu and prop/ev lists

This commit is contained in:
nick-delirium 2025-02-18 09:13:15 +01:00
parent 3a07a20195
commit fce6a562fd
No known key found for this signature in database
GPG key ID: 93ABD695DF5FDBA0
7 changed files with 277 additions and 39 deletions

View file

@ -41,6 +41,7 @@ const components: any = {
UserPage: lazy(() => import('Components/DataManagement/UsersEvents/UserPage')),
UsersEventsPage: lazy(() => import('Components/DataManagement/UsersEvents/ListPage')),
EventPage: lazy(() => import('Components/DataManagement/UsersEvents/EventPage')),
PropertiesList: lazy(() => import('Components/DataManagement/Properties/ListPage')),
};
const enhancedComponents: any = {
@ -68,6 +69,7 @@ const enhancedComponents: any = {
UserPage: components.UserPage,
UsersEventsPage: components.UsersEventsPage,
EventPage: components.EventPage,
PropertiesList: components.PropertiesList,
};
const withSiteId = routes.withSiteId;
@ -117,13 +119,6 @@ const SCOPE_SETUP = routes.scopeSetup();
const HIGHLIGHTS_PATH = routes.highlights();
const DATA_MANAGEMENT = {
ACTIVITY: routes.dataManagement.activity(),
USER_PAGE: routes.dataManagement.userPage(),
USERS_EVENTS: routes.dataManagement.usersEvents(),
EVENT_PAGE: routes.dataManagement.eventPage(),
}
function PrivateRoutes() {
const { projectsStore, userStore, integrationsStore } = useStore();
const onboarding = userStore.onboarding;
@ -308,26 +303,40 @@ function PrivateRoutes() {
<Route
exact
strict
path={withSiteId(DATA_MANAGEMENT.ACTIVITY, siteIdList)}
path={withSiteId(routes.dataManagement.activity(), siteIdList)}
component={enhancedComponents.Activity}
/>
<Route
exact
strict
path={withSiteId(DATA_MANAGEMENT.USER_PAGE, siteIdList)}
path={withSiteId(routes.dataManagement.userPage(), siteIdList)}
component={enhancedComponents.UserPage}
/>
<Route
exact
strict
path={withSiteId(DATA_MANAGEMENT.USERS_EVENTS, siteIdList)}
component={enhancedComponents.UsersEventsPage}
path={withSiteId(routes.dataManagement.users(), siteIdList)}
>
<enhancedComponents.UsersEventsPage view={'users'} />
</Route>
<Route
exact
strict
path={withSiteId(routes.dataManagement.events(), siteIdList)}
>
<enhancedComponents.UsersEventsPage view={'events'} />
</Route>
<Route
exact
strict
path={withSiteId(routes.dataManagement.eventPage(), siteIdList)}
component={enhancedComponents.EventPage}
/>
<Route
exact
strict
path={withSiteId(DATA_MANAGEMENT.EVENT_PAGE, siteIdList)}
component={enhancedComponents.EventPage}
path={withSiteId(routes.dataManagement.properties(), siteIdList)}
component={enhancedComponents.PropertiesList}
/>
{Object.entries(routes.redirects).map(([fr, to]) => (
<Redirect key={fr} exact strict from={fr} to={to} />

View file

@ -0,0 +1,232 @@
import React from 'react';
import { Input, Table, Button, Dropdown } from 'antd';
import { MoreOutlined } from '@ant-design/icons';
import { useHistory } from 'react-router-dom';
import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
import { withSiteId, dataManagement } from 'App/routes';
import { Filter, Album } from 'lucide-react';
import { list } from '../Activity/Page';
import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv';
import ColumnsModal from 'Components/DataManagement/Activity/ColumnsModal';
import FullPagination from 'Shared/FullPagination';
import Tabs from 'Shared/Tabs'
function ListPage() {
const [view, setView] = React.useState('users');
const views = [
{
key: 'users',
label: <div className={'text-lg font-medium'}>Users</div>,
},
{
key: 'events',
label: <div className={'text-lg font-medium'}>Events</div>,
},
];
const { projectsStore } = useStore();
const siteId = projectsStore.activeSiteId;
const history = useHistory();
const toUser = (id: string) =>
history.push(withSiteId(dataManagement.userPage(id), siteId));
const toEvent = (id: string) =>
history.push(withSiteId(dataManagement.eventPage(id), siteId));
return (
<div
className="flex flex-col gap-4 rounded-lg border bg-white mx-auto"
style={{ maxWidth: 1360 }}
>
<div className={'flex items-center justify-between border-b px-4 pt-2 '}>
<Tabs
activeKey={view}
onChange={(key) => setView(key)}
items={views}
/>
<div className="flex items-center gap-2">
<Button type={'text'} icon={<Album size={14} />}>
Docs
</Button>
<Input.Search size={'small'} placeholder={'Name, email, ID'} />
</div>
</div>
{view === 'users' ? <UserPropsList toUser={toUser} /> : <EventPropsList toEvent={toEvent} />}
</div>
);
}
function EventPropsList({ toEvent }: { toEvent: (id: string) => void }) {
const columns = [
{
title: 'Property',
dataIndex: 'name',
key: 'name',
showSorterTooltip: { target: 'full-header' },
sorter: (a, b) => a.name.localeCompare(b.name),
},
{
title: 'Display Name',
dataIndex: 'displayName',
key: 'displayName',
showSorterTooltip: { target: 'full-header' },
sorter: (a, b) => a.displayName.localeCompare(b.displayName),
},
{
title: 'Description',
dataIndex: 'description',
key: 'description',
showSorterTooltip: { target: 'full-header' },
sorter: (a, b) => a.description.localeCompare(b.description),
},
{
title: '30 Day Volume',
dataIndex: 'monthVolume',
key: 'monthVolume',
showSorterTooltip: { target: 'full-header' },
sorter: (a, b) => a.monthVolume.localeCompare(b.monthVolume),
},
];
const page = 1;
const total = 100;
const onPageChange = (page: number) => {};
const limit = 10;
return (
<div>
<Table
columns={columns}
dataSource={list}
pagination={false}
onRow={(record) => ({
onClick: () => toEvent(record.eventId),
})}
/>
<FullPagination
page={page}
limit={limit}
total={total}
listLen={list.length}
onPageChange={onPageChange}
entity={'events'}
/>
</div>
);
}
function UserPropsList({ toUser }: { toUser: (id: string) => void }) {
const [editCols, setEditCols] = React.useState(false);
const [hiddenCols, setHiddenCols] = React.useState([]);
const dropdownItems = [
{
label: 'Show/Hide Columns',
key: 'edit-columns',
onClick: () => setTimeout(() => setEditCols(true), 1),
},
];
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
showSorterTooltip: { target: 'full-header' },
sorter: (a, b) => a.name.localeCompare(b.name),
},
{
title: 'Display Name',
dataIndex: 'displayName',
key: 'displayName',
showSorterTooltip: { target: 'full-header' },
sorter: (a, b) => a.displayName.localeCompare(b.displayName),
},
{
title: 'Description',
dataIndex: 'description',
key: 'description',
showSorterTooltip: { target: 'full-header' },
sorter: (a, b) => a.description.localeCompare(b.description),
},
{
title: '# Users',
dataIndex: 'users',
key: 'users',
showSorterTooltip: { target: 'full-header' },
sorter: (a, b) => a.users.localeCompare(b.users),
},
{
title: (
<Dropdown
menu={{ items: dropdownItems }}
trigger={'click'}
placement={'bottomRight'}
>
<div className={'cursor-pointer'}>
<MoreOutlined />
</div>
</Dropdown>
),
dataIndex: '$__opts__$',
key: '$__opts__$',
width: 50,
},
];
const page = 1;
const total = 10;
const onPageChange = (page: number) => {};
const limit = 10;
const list = [];
const onAddFilter = () => console.log('add filter');
const excludeFilterKeys = [];
const excludeCategory = [];
const shownCols = columns.map((col) => ({
...col,
hidden: hiddenCols.includes(col.key),
}));
const onUpdateVisibleCols = (cols: string[]) => {
setHiddenCols((_) => {
return columns
.map((col) =>
cols.includes(col.key) || col.key === '$__opts__$' ? null : col.key
)
.filter(Boolean);
});
setEditCols(false);
};
return (
<div className="flex flex-col">
<div className={'relative'}>
{editCols ? (
<OutsideClickDetectingDiv onClickOutside={() => setEditCols(false)}>
<ColumnsModal
columns={shownCols.filter((col) => col.key !== '$__opts__$')}
onSelect={onUpdateVisibleCols}
hiddenCols={hiddenCols}
topOffset={'top-24 -mt-4'}
/>
</OutsideClickDetectingDiv>
) : null}
<Table
onRow={(record) => ({
onClick: () => toUser(record.userId),
})}
pagination={false}
rowClassName={'cursor-pointer'}
dataSource={[]}
columns={shownCols}
/>
</div>
<FullPagination
page={page}
limit={limit}
total={total}
listLen={list.length}
onPageChange={onPageChange}
entity={'users'}
/>
</div>
);
}
export default observer(ListPage);

View file

@ -12,9 +12,8 @@ import { list } from '../Activity/Page';
import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv';
import ColumnsModal from 'Components/DataManagement/Activity/ColumnsModal';
import FullPagination from 'Shared/FullPagination';
import Tabs from 'Shared/Tabs'
function ListPage() {
function ListPage({ view }: { view: 'users' | 'events' }) {
const { projectsStore } = useStore();
const siteId = projectsStore.activeSiteId;
const history = useHistory();
@ -22,29 +21,14 @@ function ListPage() {
history.push(withSiteId(dataManagement.userPage(id), siteId));
const toEvent = (id: string) =>
history.push(withSiteId(dataManagement.eventPage(id), siteId));
const [view, setView] = React.useState('users');
const views = [
{
key: 'users',
label: <div className={'text-lg font-medium'}>Users</div>,
},
{
key: 'events',
label: <div className={'text-lg font-medium'}>Events</div>,
},
];
return (
<div
className="flex flex-col gap-4 rounded-lg border bg-white mx-auto"
style={{ maxWidth: 1360 }}
>
<div className={'flex items-center justify-between border-b px-4 pt-2 '}>
<Tabs
activeKey={view}
onChange={(key) => setView(key)}
items={views}
/>
<div className={'font-semibold text-lg capitalize'}>{view}</div>
<div className="flex items-center gap-2">
<Button type={'text'} icon={<Album size={14} />}>
Docs

View file

@ -118,7 +118,7 @@ function UserInfo() {
return (
<>
<Breadcrumb items={[
{ label: 'Users', to: dataManagement.usersEvents(), withSiteId: true },
{ label: 'Users', to: dataManagement.users(), withSiteId: true },
{ label: testUser.name },
]} />

View file

@ -98,6 +98,7 @@ function SideMenu(props: Props) {
}
if (item.hidden) return item;
const dataAnalytics = [MENU.ACTIVITY, MENU.USERS, MENU.EVENTS]
const isHidden = [
item.key === MENU.RECOMMENDATIONS &&
modules.includes(MODULES.RECOMMENDATIONS),
@ -109,7 +110,7 @@ function SideMenu(props: Props) {
item.key === MENU.USABILITY_TESTS && modules.includes(MODULES.USABILITY_TESTS),
item.isAdmin && !isAdmin,
item.isEnterprise && !isEnterprise,
(item.key === MENU.ACTIVITY || item.key === MENU.USERS_EVENTS) && isMobile
dataAnalytics.includes(item.key) && isMobile
].some((cond) => cond);
return { ...item, hidden: isHidden };
@ -154,7 +155,9 @@ function SideMenu(props: Props) {
[PREFERENCES_MENU.MODULES]: () => client(CLIENT_TABS.MODULES),
[MENU.HIGHLIGHTS]: () => withSiteId(routes.highlights(''), siteId),
[MENU.ACTIVITY]: () => withSiteId(routes.dataManagement.activity(), siteId),
[MENU.USERS_EVENTS]: () => withSiteId(routes.dataManagement.usersEvents(), siteId),
[MENU.USERS]: () => withSiteId(routes.dataManagement.users(), siteId),
[MENU.EVENTS]: () => withSiteId(routes.dataManagement.events(), siteId),
[MENU.PROPS]: () => withSiteId(routes.dataManagement.properties(), siteId),
};
const handleClick = (item: any) => {

View file

@ -54,8 +54,10 @@ export const enum MENU {
EXIT = 'exit',
SPOTS = 'spots',
ACTIVITY = 'activity',
USER_PAGE = 'user-page',
USERS_EVENTS = 'users-events',
USER = 'user-page',
USERS = 'data-users',
EVENTS = 'data-events',
PROPS = 'data-properties',
}
export const categories: Category[] = [
@ -106,7 +108,12 @@ export const categories: Category[] = [
{
title: 'Data Management',
key: 'data-management',
items: [{ label: 'Activity', key: MENU.ACTIVITY, icon: 'square-mouse-pointer' }, { label: 'Users and Events', key: MENU.USERS_EVENTS, icon: 'square-mouse-pointer' }],
items: [
{ label: 'Activity', key: MENU.ACTIVITY, icon: 'square-mouse-pointer' },
{ label: 'Users', key: MENU.USERS, icon: 'square-mouse-pointer' },
{ label: 'Events', key: MENU.EVENTS, icon: 'square-mouse-pointer' },
{ label: 'Properties', key: MENU.PROPS, icon: 'square-mouse-pointer' },
],
},
{
title: 'Product Optimization',

View file

@ -150,8 +150,10 @@ export const highlights = (): string => '/highlights';
export const dataManagement = {
activity: () => '/data-management/activity',
userPage: (id = ':userId', hash?: string | number) => hashed(`/data-management/user/${id}`, hash),
usersEvents: () => '/data-management/users-and-events',
users: () => '/data-management/users',
events: () => '/data-management/events',
eventPage: (id = ':eventId', hash?: string | number) => hashed(`/data-management/event/${id}`, hash),
properties: () => '/data-management/properties',
}
const REQUIRED_SITE_ID_ROUTES = [
@ -201,7 +203,8 @@ const REQUIRED_SITE_ID_ROUTES = [
dataManagement.activity(),
dataManagement.userPage(''),
dataManagement.usersEvents(),
dataManagement.users(),
dataManagement.events(),
dataManagement.eventPage(''),
];
const routeNeedsSiteId = (path: string): boolean => REQUIRED_SITE_ID_ROUTES.some(r => path.startsWith(r));