350 lines
10 KiB
TypeScript
350 lines
10 KiB
TypeScript
import React from 'react';
|
|
import { EventsList, FilterList } from 'Shared/Filters/FilterList';
|
|
import { Dropdown, Button } from 'antd';
|
|
import { MoreOutlined } from '@ant-design/icons';
|
|
import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv';
|
|
import ColumnsModal from 'Components/DataManagement/Activity/ColumnsModal';
|
|
import Event from './data/Event';
|
|
import { useModal } from 'App/components/Modal';
|
|
import EventDetailsModal from './EventDetailsModal';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import Select from 'Shared/Select';
|
|
import { Link } from 'react-router-dom';
|
|
import { dataManagement, withSiteId } from 'App/routes';
|
|
import { observer } from 'mobx-react-lite';
|
|
import { useStore } from 'App/mstore';
|
|
import FullPagination from 'Shared/FullPagination';
|
|
import AnimatedSVG from 'Shared/AnimatedSVG';
|
|
import DndTable from 'Shared/DNDTable';
|
|
import { Code } from 'lucide-react';
|
|
|
|
const limit = 100;
|
|
|
|
const testEv = new Event({
|
|
name: 'test ev #',
|
|
time: Date.now(),
|
|
defaultFields: {
|
|
userId: '123',
|
|
userLocation: 'NY',
|
|
userEnvironment: 'Mac OS',
|
|
},
|
|
customFields: {},
|
|
isAutoCapture: false,
|
|
sessionId: '123123',
|
|
displayName: 'Test Event',
|
|
description: 'This is A test Event',
|
|
monthQuery: 100,
|
|
monthVolume: 1000,
|
|
});
|
|
const testAutoEv = new Event({
|
|
name: 'auto test ev',
|
|
time: Date.now(),
|
|
defaultFields: {
|
|
userId: '123',
|
|
userLocation: 'NY',
|
|
userEnvironment: 'Mac OS',
|
|
},
|
|
customFields: {},
|
|
isAutoCapture: true,
|
|
sessionId: '123123',
|
|
displayName: 'Test Auto Event',
|
|
description: 'This is A test Auto Event',
|
|
monthQuery: 100,
|
|
monthVolume: 1000,
|
|
});
|
|
export const list = [testEv.toData(), testAutoEv.toData()];
|
|
|
|
const fetcher = async (
|
|
page: number
|
|
): Promise<{ list: any[]; total: number }> => {
|
|
const total = 3000;
|
|
return new Promise((resolve) => {
|
|
resolve({ list, total });
|
|
});
|
|
};
|
|
|
|
const columnOrderKey = '$__activity_columns_order__$';
|
|
|
|
function ActivityPage() {
|
|
const { projectsStore } = useStore();
|
|
const siteId = projectsStore.activeSiteId;
|
|
|
|
const dropdownItems = [
|
|
{
|
|
label: 'Show/Hide Columns',
|
|
key: 'edit-columns',
|
|
onClick: () => setTimeout(() => setEditCols(true), 1),
|
|
},
|
|
];
|
|
const columns = [
|
|
{
|
|
title: 'Event Name',
|
|
dataIndex: 'name',
|
|
key: 'name',
|
|
showSorterTooltip: { target: 'full-header' },
|
|
sorter: (a, b) => a.name.localeCompare(b.name),
|
|
render: (text, row) => (
|
|
<div className={'flex items-center gap-2 code-font'}>
|
|
<Code size={16} />
|
|
{row.$_isAutoCapture && <span className={'text-gray-500'}>[a]</span>}
|
|
<span>{row.name}</span>
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
title: 'Time',
|
|
dataIndex: 'time',
|
|
key: 'time',
|
|
showSorterTooltip: { target: 'full-header' },
|
|
sorter: (a, b) => a.time - b.time,
|
|
},
|
|
{
|
|
title: 'Distinct ID',
|
|
dataIndex: 'userId',
|
|
key: 'userId',
|
|
showSorterTooltip: { target: 'full-header' },
|
|
sorter: (a, b) => a.userId.localeCompare(b.userId),
|
|
render: (text) => (
|
|
<Link
|
|
to={withSiteId(dataManagement.userPage(text), siteId)}
|
|
className={'link'}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
}}
|
|
>
|
|
{text}
|
|
</Link>
|
|
),
|
|
},
|
|
{
|
|
title: 'City',
|
|
dataIndex: 'userLocation',
|
|
key: 'userLocation',
|
|
showSorterTooltip: { target: 'full-header' },
|
|
sorter: (a, b) => a.userLocation.localeCompare(b.userLocation),
|
|
},
|
|
{
|
|
title: 'Environment',
|
|
dataIndex: 'userEnvironment',
|
|
key: 'userEnvironment',
|
|
showSorterTooltip: { target: 'full-header' },
|
|
sorter: (a, b) => a.userEnvironment.localeCompare(b.userEnvironment),
|
|
},
|
|
{
|
|
title: (
|
|
<Dropdown
|
|
menu={{ items: dropdownItems }}
|
|
trigger={'click'}
|
|
placement={'bottomRight'}
|
|
>
|
|
<div className={'cursor-pointer'}>
|
|
<MoreOutlined />
|
|
</div>
|
|
</Dropdown>
|
|
),
|
|
dataIndex: '$__opts__$',
|
|
key: '$__opts__$',
|
|
width: 50,
|
|
},
|
|
];
|
|
|
|
const [page, setPage] = React.useState(1);
|
|
const [cols, setCols] = React.useState(columns);
|
|
const [hiddenCols, setHiddenCols] = React.useState([]);
|
|
const { data, isPending } = useQuery({
|
|
queryKey: ['data', 'events', page],
|
|
queryFn: () => fetcher(page),
|
|
initialData: { list: [], total: 0 },
|
|
});
|
|
const { list, total } = data;
|
|
const appliedFilter = { filters: [] };
|
|
const onAddFilter = () => {};
|
|
const onUpdateFilter = () => {};
|
|
const onRemoveFilter = () => {};
|
|
const onChangeEventsOrder = () => {};
|
|
const saveRequestPayloads = () => {};
|
|
const onFilterMove = () => {};
|
|
const [editCols, setEditCols] = React.useState(false);
|
|
const { showModal, hideModal } = useModal();
|
|
|
|
React.useEffect(() => {
|
|
if (hiddenCols.length) {
|
|
setCols((cols) =>
|
|
cols.map((col) => ({
|
|
...col,
|
|
hidden: hiddenCols.includes(col.key),
|
|
}))
|
|
);
|
|
}
|
|
}, [hiddenCols]);
|
|
React.useEffect(() => {
|
|
const savedColumnOrder = localStorage.getItem(columnOrderKey);
|
|
if (savedColumnOrder) {
|
|
const keys = savedColumnOrder.split(',');
|
|
setCols((cols) => {
|
|
return cols.sort((a, b) => {
|
|
return keys.indexOf(a.key) - keys.indexOf(b.key);
|
|
});
|
|
});
|
|
}
|
|
}, []);
|
|
|
|
const onOrderChange = (newCols) => {
|
|
const order = newCols.map((col) => col.key).join(',');
|
|
localStorage.setItem(columnOrderKey, order);
|
|
|
|
setCols(newCols);
|
|
};
|
|
|
|
const onPageChange = (page: number) => {
|
|
setPage(page);
|
|
};
|
|
|
|
const onItemClick = (ev: Event) => {
|
|
showModal(<EventDetailsModal ev={ev} onClose={hideModal} />, {
|
|
width: 420,
|
|
right: true,
|
|
});
|
|
};
|
|
|
|
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 gap-2'}
|
|
style={{ maxWidth: '1360px', margin: 'auto' }}
|
|
>
|
|
<div className={'shadow rounded-xl'}>
|
|
<EventsList
|
|
filter={appliedFilter}
|
|
onAddFilter={onAddFilter}
|
|
onUpdateFilter={onUpdateFilter}
|
|
onRemoveFilter={onRemoveFilter}
|
|
onChangeEventsOrder={onChangeEventsOrder}
|
|
saveRequestPayloads={saveRequestPayloads}
|
|
onFilterMove={onFilterMove}
|
|
mergeDown
|
|
heading={
|
|
<div
|
|
className={
|
|
'-mx-4 px-4 border-b w-full py-2 font-semibold text-lg'
|
|
}
|
|
style={{ width: 'calc(100% + 2rem)' }}
|
|
>
|
|
Activity
|
|
</div>
|
|
}
|
|
/>
|
|
<FilterList
|
|
mergeUp
|
|
filter={appliedFilter}
|
|
onAddFilter={onAddFilter}
|
|
onUpdateFilter={onUpdateFilter}
|
|
onRemoveFilter={onRemoveFilter}
|
|
onChangeEventsOrder={onChangeEventsOrder}
|
|
saveRequestPayloads={saveRequestPayloads}
|
|
onFilterMove={onFilterMove}
|
|
/>
|
|
</div>
|
|
<div className={'relative'}>
|
|
{editCols ? (
|
|
<OutsideClickDetectingDiv onClickOutside={() => setEditCols(false)}>
|
|
<ColumnsModal
|
|
columns={shownCols.filter((col) => col.key !== '$__opts__$')}
|
|
onSelect={onUpdateVisibleCols}
|
|
hiddenCols={hiddenCols}
|
|
/>
|
|
</OutsideClickDetectingDiv>
|
|
) : null}
|
|
|
|
<div
|
|
className={
|
|
'bg-white rounded-xl shadow border flex flex-col overflow-hidden'
|
|
}
|
|
>
|
|
<div className={'px-4 py-2 flex items-center gap-2'}>
|
|
<div className={'font-semibold text-lg'}>All users activity</div>
|
|
<div className={'ml-auto'} />
|
|
<Select
|
|
options={[
|
|
{ label: 'Past 24 Hours', value: 'DESC' },
|
|
{ label: 'Weekly', value: 'ASC' },
|
|
{ label: 'Other', value: 'Stuff' },
|
|
]}
|
|
defaultValue={'DESC'}
|
|
plain
|
|
onChange={({ value }) => {
|
|
console.log(value);
|
|
}}
|
|
/>
|
|
<Select
|
|
options={[
|
|
{ label: 'Newest', value: 'DESC' },
|
|
{ label: 'Oldest', value: 'ASC' },
|
|
]}
|
|
defaultValue={'DESC'}
|
|
plain
|
|
onChange={({ value }) => {
|
|
console.log(value);
|
|
}}
|
|
/>
|
|
</div>
|
|
{total === 0 ? (
|
|
<div className={'flex items-center justify-center flex-col gap-4'}>
|
|
<AnimatedSVG name={'no-results'} size={56} />
|
|
<div className={'flex items-center gap-2'}>
|
|
<div className={'text-lg font-semibold'}>
|
|
No results in the{' '}
|
|
</div>
|
|
<Select
|
|
options={[
|
|
{ label: 'Past 24 Hours', value: 'DESC' },
|
|
{ label: 'Weekly', value: 'ASC' },
|
|
{ label: 'Other', value: 'Stuff' },
|
|
]}
|
|
defaultValue={'DESC'}
|
|
plain
|
|
onChange={({ value }) => {
|
|
console.log(value);
|
|
}}
|
|
/>
|
|
</div>
|
|
<Button type={'text'}>Refresh</Button>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<DndTable
|
|
loading={isPending}
|
|
onRow={(record) => ({
|
|
onClick: () => onItemClick(record),
|
|
})}
|
|
dataSource={list}
|
|
pagination={false}
|
|
columns={cols}
|
|
onOrderChange={onOrderChange}
|
|
/>
|
|
<FullPagination
|
|
page={page}
|
|
limit={limit}
|
|
total={total}
|
|
listLen={list.length}
|
|
onPageChange={onPageChange}
|
|
entity={'events'}
|
|
/>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default observer(ActivityPage);
|