diff --git a/frontend/app/components/DataManagement/Activity/EventDetailsModal.tsx b/frontend/app/components/DataManagement/Activity/EventDetailsModal.tsx
index 50e183070..2106b1439 100644
--- a/frontend/app/components/DataManagement/Activity/EventDetailsModal.tsx
+++ b/frontend/app/components/DataManagement/Activity/EventDetailsModal.tsx
@@ -10,14 +10,14 @@ function EventDetailsModal({ ev, onClose }: { ev: EventData, onClose: () => void
label: 'All Properties',
value: 'all',
},
+ {
+ label: 'Openreplay Properties',
+ value: 'default',
+ },
{
label: 'Custom Properties',
value: 'custom',
},
- {
- label: 'Default Properties',
- value: 'default',
- }
]
const views = [
diff --git a/frontend/app/components/DataManagement/Activity/Page.tsx b/frontend/app/components/DataManagement/Activity/Page.tsx
index cb56599d1..c087da5cf 100644
--- a/frontend/app/components/DataManagement/Activity/Page.tsx
+++ b/frontend/app/components/DataManagement/Activity/Page.tsx
@@ -1,8 +1,7 @@
import React from 'react';
import { EventsList, FilterList } from 'Shared/Filters/FilterList';
-import { Table, Dropdown } from 'antd';
+import { Dropdown, Button } from 'antd';
import { MoreOutlined } from '@ant-design/icons';
-import { numberWithCommas } from 'App/utils';
import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv';
import ColumnsModal from 'Components/DataManagement/Activity/ColumnsModal';
import Event from './data/Event';
@@ -11,10 +10,12 @@ 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 { dataManagement, withSiteId } from 'App/routes';
import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore';
-import FullPagination from "Shared/FullPagination";
+import FullPagination from 'Shared/FullPagination';
+import AnimatedSVG from 'Shared/AnimatedSVG';
+import DndTable from 'Shared/DNDTable';
const limit = 100;
@@ -61,27 +62,11 @@ const fetcher = async (
});
};
-function ActivityPage() {
- const { projectsStore } = useStore()
- const siteId = projectsStore.activeSiteId;
+const columnOrderKey = '$__activity_columns_order__$';
- const [page, setPage] = React.useState(1);
- 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();
+function ActivityPage() {
+ const { projectsStore } = useStore();
+ const siteId = projectsStore.activeSiteId;
const dropdownItems = [
{
@@ -90,7 +75,6 @@ function ActivityPage() {
onClick: () => setTimeout(() => setEditCols(true), 1),
},
];
-
const columns = [
{
title: 'Event Name',
@@ -100,9 +84,7 @@ function ActivityPage() {
sorter: (a, b) => a.name.localeCompare(b.name),
render: (text, row) => (
- {row.$_isAutoCapture && (
- [auto]
- )}
+ {row.$_isAutoCapture && [a]}
{row.name}
),
@@ -164,7 +146,53 @@ function ActivityPage() {
},
];
+ 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);
@@ -177,10 +205,6 @@ function ActivityPage() {
});
};
- const shownCols = columns.map((col) => ({
- ...col,
- hidden: hiddenCols.includes(col.key),
- }));
const onUpdateVisibleCols = (cols: string[]) => {
setHiddenCols((_) => {
return columns
@@ -271,23 +295,50 @@ function ActivityPage() {
}}
/>
- ({
- onClick: () => onItemClick(record),
- })}
- dataSource={list}
- pagination={false}
- columns={shownCols}
- />
-
+ {total === 0 ? (
+
+
+
+
+ No results in the{' '}
+
+
+
+
+ ) : (
+ <>
+ ({
+ onClick: () => onItemClick(record),
+ })}
+ dataSource={list}
+ pagination={false}
+ columns={cols}
+ onOrderChange={onOrderChange}
+ />
+
+ >
+ )}
diff --git a/frontend/app/components/DataManagement/DataItemPage.tsx b/frontend/app/components/DataManagement/DataItemPage.tsx
new file mode 100644
index 000000000..f14b8bc34
--- /dev/null
+++ b/frontend/app/components/DataManagement/DataItemPage.tsx
@@ -0,0 +1,112 @@
+import React from 'react';
+import { Button, Input } from 'antd';
+import Breadcrumb from 'Shared/Breadcrumb';
+import { Triangle } from './Activity/EventDetailsModal';
+import cn from 'classnames';
+import { EditOutlined } from '@ant-design/icons';
+
+function DataItemPage({
+ sessionId,
+ footer,
+ item,
+ backLink,
+}: {
+ sessionId?: string;
+ footer?: React.ReactNode;
+ item: Record;
+ backLink: { name: string; to: string };
+}) {
+ return (
+
+
+
+
+
+ {item.name}
+
+ {sessionId ? (
+
+ Play Sessions
+
+
+ ) : null}
+
+ {item.fields.map((field) => (
+
null}
+ fieldName={field.name}
+ value={field.value}
+ />
+ ))}
+
+
+ {footer}
+
+ );
+}
+
+function EditableField({
+ onSave,
+ fieldName,
+ value,
+}: {
+ onSave: (value: string) => void;
+ fieldName: string;
+ value: string;
+}) {
+ const [isEdit, setIsEdit] = React.useState(false);
+ return (
+
+
+ {fieldName}
+
+
+ {isEdit ? (
+
+
+
+
+
+
+ ) : (
+
+
{value}
+
setIsEdit(true)}
+ >
+
+
+
+ )}
+
+
+ );
+}
+
+export default DataItemPage;
diff --git a/frontend/app/components/DataManagement/UsersEvents/EventPage.tsx b/frontend/app/components/DataManagement/UsersEvents/EventPage.tsx
index cfa255d70..05b70c48a 100644
--- a/frontend/app/components/DataManagement/UsersEvents/EventPage.tsx
+++ b/frontend/app/components/DataManagement/UsersEvents/EventPage.tsx
@@ -5,6 +5,7 @@ import Event from 'Components/DataManagement/Activity/data/Event';
import { Triangle } from '../Activity/EventDetailsModal';
import cn from 'classnames';
import { EditOutlined } from '@ant-design/icons';
+import DataItemPage from '../DataItemPage';
const testAutoEv = new Event({
name: 'auto test ev',
@@ -30,104 +31,41 @@ function EventPage() {
label: 'All Properties',
value: 'all',
},
+ {
+ label: 'Openreplay Properties',
+ value: 'default',
+ },
{
label: 'Custom Properties',
value: 'custom',
},
- {
- label: 'Default Properties',
- value: 'default',
- }
- ]
- return (
-
-
-
-
-
- {testAutoEv.name}
-
-
- Play Sessions
-
-
-
-
null} fieldName={'Display Name'} value={testAutoEv.displayName} />
- null} fieldName={'Description'} value={testAutoEv.description} />
- null} fieldName={'30 Day Volume'} value={testAutoEv.monthVolume} />
-
+ ];
-
-
-
Event Properties
-
setTab(v)} />
+ const evWithFields = {
+ ...testAutoEv,
+ fields: [
+ { name: 'User ID', value: testAutoEv.defaultFields.userId },
+ { name: 'User Location', value: testAutoEv.defaultFields.userLocation },
+ {
+ name: 'User Environment',
+ value: testAutoEv.defaultFields.userEnvironment,
+ },
+ ],
+ };
+ return (
+
+
+
Event Properties
+
setTab(v)} />
+
-
-
+ }
+ />
);
}
-function EditableField({
- onSave,
- fieldName,
- value,
-}: {
- onSave: (value: string) => void
- fieldName: string
- value: string
-}) {
- const [isEdit, setIsEdit] = React.useState(false);
- return (
-
-
- {fieldName}
-
-
- {isEdit ? (
-
-
-
-
-
-
- ) : (
-
-
{value}
-
setIsEdit(true)}>
-
-
-
- )}
-
-
- );
-}
-
-export default EventPage
\ No newline at end of file
+export default EventPage;
diff --git a/frontend/app/components/shared/DNDTable.tsx b/frontend/app/components/shared/DNDTable.tsx
new file mode 100644
index 000000000..5c58cd3f2
--- /dev/null
+++ b/frontend/app/components/shared/DNDTable.tsx
@@ -0,0 +1,100 @@
+import React, { useRef, useState, useCallback } from 'react';
+import { Table } from 'antd';
+import { useDrag, useDrop, DndProvider } from 'react-dnd';
+import { HTML5Backend } from 'react-dnd-html5-backend';
+import { GripVertical } from 'lucide-react';
+
+const type = 'COLUMN';
+
+function DraggableHeaderCell({ index, moveColumn, children, ...rest }) {
+ const ref = useRef(null);
+ const [{ isDragging }, drag] = useDrag({
+ type,
+ item: { index },
+ collect: (monitor) => ({
+ isDragging: monitor.isDragging(),
+ }),
+ });
+ const [, drop] = useDrop({
+ accept: type,
+ hover: (item, monitor) => {
+ if (!ref.current) return;
+ const dragIndex = item.index;
+ if (dragIndex === index) return;
+ const hoverBoundingRect = ref.current.getBoundingClientRect();
+ const hoverMiddleX =
+ (hoverBoundingRect.right - hoverBoundingRect.left) / 2;
+ const clientOffset = monitor.getClientOffset();
+ const hoverClientX = clientOffset.x - hoverBoundingRect.left;
+ if (dragIndex < index && hoverClientX < hoverMiddleX) return;
+ if (dragIndex > index && hoverClientX > hoverMiddleX) return;
+ moveColumn(dragIndex, index);
+ item.index = index;
+ },
+ });
+ drag(drop(ref));
+ return (
+
+
+ |
+ );
+}
+
+const DNDTable = ({ columns: initCols, onOrderChange, ...tableProps }) => {
+ const [cols, setCols] = useState(initCols);
+
+ const moveColumn = useCallback(
+ (dragIndex, hoverIndex) => {
+ const updated = [...cols];
+ const [removed] = updated.splice(dragIndex, 1);
+ updated.splice(hoverIndex, 0, removed);
+ setCols(updated);
+ onOrderChange?.(updated);
+ },
+ [cols, onOrderChange]
+ );
+
+ const components = {
+ header: {
+ cell: (cellProps) => {
+ const i = cols.findIndex((c) => c.key === cellProps['data-col-key']);
+ const isOptionsCell = cellProps['data-col-key'] === '$__opts__$';
+ return !isOptionsCell && i > -1 ? (
+
+ ) : (
+ |
+ );
+ },
+ },
+ };
+
+ const mergedCols = cols.map((col) => ({
+ ...col,
+ onHeaderCell: () => ({ 'data-col-key': col.key }),
+ }));
+
+ return (
+
+
+
+ );
+};
+
+export default DNDTable;
diff --git a/frontend/app/layout/SideMenu.tsx b/frontend/app/layout/SideMenu.tsx
index 9b7f7384c..93ee0d691 100644
--- a/frontend/app/layout/SideMenu.tsx
+++ b/frontend/app/layout/SideMenu.tsx
@@ -108,7 +108,8 @@ function SideMenu(props: Props) {
item.key === MENU.ALERTS && modules.includes(MODULES.ALERTS),
item.key === MENU.USABILITY_TESTS && modules.includes(MODULES.USABILITY_TESTS),
item.isAdmin && !isAdmin,
- item.isEnterprise && !isEnterprise
+ item.isEnterprise && !isEnterprise,
+ (item.key === MENU.ACTIVITY || item.key === MENU.USERS_EVENTS) && isMobile
].some((cond) => cond);
return { ...item, hidden: isHidden };