Compare commits

...
Sign in to create a new pull request.

2 commits

Author SHA1 Message Date
Shekar Siri
c21f7219f6 feat(ui): custom events manage (wip) 2024-08-29 10:24:07 +05:30
Shekar Siri
b24f1f688a feat(ui): widget options 2024-08-26 13:21:13 +05:30
15 changed files with 372 additions and 22 deletions

View file

@ -14,6 +14,7 @@ import Notifications from './Notifications';
import Roles from './Roles';
import SessionsListingSettings from 'Components/Client/SessionsListingSettings';
import Modules from 'Components/Client/Modules';
import CustomEventsList from 'Components/Client/CustomEvents/CustomEventsList';
@withRouter
export default class Client extends React.PureComponent {
@ -38,6 +39,7 @@ export default class Client extends React.PureComponent {
<Route exact strict path={clientRoute(CLIENT_TABS.MANAGE_ROLES)} component={Roles} />
<Route exact strict path={clientRoute(CLIENT_TABS.AUDIT)} component={AuditView} />
<Route exact strict path={clientRoute(CLIENT_TABS.MODULES)} component={Modules} />
<Route exact strict path={clientRoute(CLIENT_TABS.CUSTOM_EVENTS)} component={CustomEventsList} />
<Redirect to={clientRoute(CLIENT_TABS.PROFILE)} />
</Switch>
);

View file

@ -0,0 +1,26 @@
import React from 'react';
import { Button, Form, Input } from 'antd';
import { FormField } from 'semantic-ui-react';
interface Props {
event?: any;
}
function CustomEventForm(props: Props) {
return (
<div>
<Form layout="vertical">
<FormField>
<label>Name</label>
<Input />
</FormField>
<Button type="primary">
Submit
</Button>
</Form>
</div>
);
}
export default CustomEventForm;

View file

@ -0,0 +1,17 @@
import React from 'react';
interface Props {
event: any;
}
function CustomEventItem(props: Props) {
return (
<div className="flex">
<div>{props.event.name}</div>
<div>{props.event.user?.name}</div>
{/*<div>{props.event.created_at}</div>*/}
</div>
);
}
export default CustomEventItem;

View file

@ -0,0 +1,78 @@
import React, { useEffect } from 'react';
import cn from 'classnames';
import styles from 'Components/Client/Webhooks/webhooks.module.css';
import { Button, Divider, Icon, Loader, NoContent } from 'UI';
import AnimatedSVG from 'Shared/AnimatedSVG';
import { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
import CustomEventItem from 'Components/Client/CustomEvents/CustomEventItem';
import { useStore } from 'App/mstore';
import { useModal } from 'Components/ModalContext';
import CustomEventForm from 'Components/Client/CustomEvents/CustomEventForm';
import { List } from 'antd';
function CustomEventsList() {
const [loading, setLoading] = React.useState(false);
const { customEventStore: store } = useStore();
const { openModal } = useModal();
// useEffect(() => {
// setLoading(true);
// store.fetchAll({}).finally(() => {
// setLoading(false);
// });
// setTimeout(() => {
// setLoading(false);
// }, 2000);
// }, []);
const init = () => {
console.log('init');
showEvent();
};
const showEvent = (event?: any) => {
openModal(<CustomEventForm event={event} />, {
title: event ? 'Edit Event' : 'Add Event',
width: 400
});
};
return (
<div className="bg-white rounded-lg shadow-sm border p-5">
<div className={cn(styles.tabHeader)}>
<h3 className={cn(styles.tabTitle, 'text-2xl')}>{'Events'}</h3>
<Button className="ml-auto" variant="primary" onClick={() => init()}>Add Event</Button>
</div>
<div className="text-base text-disabled-text flex items-center my-3 px-5">
<Icon name="info-circle-fill" className="mr-2" size={16} />
Leverage webhook notifications on alerts to trigger custom callbacks.
</div>
<Loader loading={loading}>
<NoContent
title={
<div className="flex flex-col items-center justify-center">
<AnimatedSVG name={ICONS.NO_WEBHOOKS} size={60} />
<div className="text-center my-4">None added yet</div>
</div>
}
size="small"
show={store.list.length === 0}
>
<List dataSource={store.list} renderItem={(event) => (
<List.Item onClick={showEvent}>
<CustomEventItem event={event} />
</List.Item>
)} />
</NoContent>
</Loader>
</div>
);
}
const mapStateToProps = (state: any) => ({
sites: state.getIn(['site', 'list'])
});
export default CustomEventsList;

View file

@ -117,7 +117,7 @@ function WidgetChart(props: Props) {
useEffect(() => {
_metric.updateKey('page', 1);
loadPage();
}, [drillDownPeriod, period, depsString, metric.metricType, metric.metricOf, metric.viewType, metric.metricValue, metric.startType]);
}, [drillDownPeriod, period, depsString, metric.metricType, metric.metricOf, metric.viewType, metric.metricValue, metric.startType, metric.metricFormat]);
useEffect(loadPage, [_metric.page]);

View file

@ -0,0 +1,60 @@
import React from 'react';
import { HEATMAP, TABLE, USER_PATH } from 'App/constants/card';
import { Select, Space, Switch } from 'antd';
import { useStore } from 'App/mstore';
import ClickMapRagePicker from 'Components/Dashboard/components/ClickMapRagePicker/ClickMapRagePicker';
import { FilterKey } from 'Types/filter/filterType';
interface Props {
}
function WidgetOptions(props: Props) {
const { metricStore, dashboardStore } = useStore();
const metric: any = metricStore.instance;
const handleChange = (value: any) => {
console.log(`selected ${value}`);
metric.update({ metricFormat: value });
};
return (
<div>
{metric.metricType === USER_PATH && (
<a
href="#"
onClick={(e) => {
e.preventDefault();
metric.update({ hideExcess: !metric.hideExcess });
}}
>
<Space>
<Switch checked={metric.hideExcess} size="small" />
<span className="mr-4 color-gray-medium">
Hide Minor Paths
</span>
</Space>
</a>
)}
{metric.metricType === TABLE && metric.metricOf != FilterKey.USERID && (
<Select
defaultValue={metric.metricFormat}
style={{ width: 120 }}
onChange={handleChange}
options={[
{ value: 'sessionCount', label: 'Sessions' },
{ value: 'userCount', label: 'Users' }
]}
/>
)}
{metric.metricType === HEATMAP ? (
<ClickMapRagePicker />
) : null}
</div>
);
}
export default WidgetOptions;

View file

@ -8,6 +8,7 @@ import { useStore } from 'App/mstore';
import ClickMapRagePicker from 'Components/Dashboard/components/ClickMapRagePicker';
import WidgetWrapper from '../WidgetWrapper';
import WidgetOptions from 'Components/Dashboard/components/WidgetOptions';
interface Props {
className?: string;
@ -28,22 +29,24 @@ function WidgetPreview(props: Props) {
<div className="flex items-center justify-between px-4 pt-2">
<h2 className="text-xl">{props.name}</h2>
<div className="flex items-center">
{metric.metricType === USER_PATH && (
<a
href="#"
onClick={(e) => {
e.preventDefault();
metric.update({ hideExcess: !metric.hideExcess });
}}
>
<Space>
<Switch checked={metric.hideExcess} size="small" />
<span className="mr-4 color-gray-medium">
Hide Minor Paths
</span>
</Space>
</a>
)}
<WidgetOptions />
{/*{metric.metricType === USER_PATH && (*/}
{/* <a*/}
{/* href="#"*/}
{/* onClick={(e) => {*/}
{/* e.preventDefault();*/}
{/* metric.update({ hideExcess: !metric.hideExcess });*/}
{/* }}*/}
{/* >*/}
{/* <Space>*/}
{/* <Switch checked={metric.hideExcess} size="small" />*/}
{/* <span className="mr-4 color-gray-medium">*/}
{/* Hide Minor Paths*/}
{/* </span>*/}
{/* </Space>*/}
{/* </a>*/}
{/*)}*/}
{/*{isTimeSeries && (*/}
{/* <>*/}
@ -101,10 +104,7 @@ function WidgetPreview(props: Props) {
{/*</>*/}
{/*)}*/}
<div className="mx-4" />
{metric.metricType === HEATMAP ? (
<ClickMapRagePicker />
) : null}
{/* add to dashboard */}
{/*{metric.exists() && (*/}

View file

@ -124,7 +124,8 @@ function SideMenu(props: Props) {
[PREFERENCES_MENU.TEAM]: () => client(CLIENT_TABS.MANAGE_USERS),
[PREFERENCES_MENU.NOTIFICATIONS]: () => client(CLIENT_TABS.NOTIFICATIONS),
[PREFERENCES_MENU.BILLING]: () => client(CLIENT_TABS.BILLING),
[PREFERENCES_MENU.MODULES]: () => client(CLIENT_TABS.MODULES)
[PREFERENCES_MENU.MODULES]: () => client(CLIENT_TABS.MODULES),
[PREFERENCES_MENU.CUSTOM_EVENTS]: () => client(CLIENT_TABS.CUSTOM_EVENTS),
};
const handleClick = (item: any) => {

View file

@ -26,6 +26,7 @@ export const enum PREFERENCES_MENU {
INTEGRATIONS = 'integrations',
METADATA = 'metadata',
WEBHOOKS = 'webhooks',
CUSTOM_EVENTS = 'custom-events',
MODULES = 'modules',
PROJECTS = 'projects',
ROLES_ACCESS = 'roles-access',
@ -134,6 +135,7 @@ export const preferences: Category[] = [
{ label: 'Integrations', key: PREFERENCES_MENU.INTEGRATIONS, icon: 'plug' },
{ label: 'Metadata', key: PREFERENCES_MENU.METADATA, icon: 'tags' },
{ label: 'Webhooks', key: PREFERENCES_MENU.WEBHOOKS, icon: 'link-45deg' },
{ label: 'Custom Events', key: PREFERENCES_MENU.CUSTOM_EVENTS, icon: 'calendar' },
{ label: 'Modules', key: PREFERENCES_MENU.MODULES, icon: 'puzzle' },
{ label: 'Projects', key: PREFERENCES_MENU.PROJECTS, icon: 'folder2' },
{

View file

@ -0,0 +1,80 @@
import { makeAutoObservable, runInAction } from 'mobx';
import { customEventService } from 'App/services';
import Audit from 'MOBX/types/audit';
import CustomEvent from 'App/mstore/types/customEvent';
export default class CustomEventsStore {
list: CustomEvent[] = [
CustomEvent.fromJson({
id: '1',
name: 'Event 1',
description: 'Description 1',
createdAt: new Date(),
updatedAt: new Date(),
user: {
id: '1',
name: 'User 1'
}
}),
CustomEvent.fromJson({
id: '2',
name: 'Event 2',
description: 'Description 2',
createdAt: new Date(),
updatedAt: new Date(),
user: {
id: '1',
name: 'User 1'
}
}),
CustomEvent.fromJson({
id: '3',
name: 'Event 3',
description: 'Description 3',
createdAt: new Date(),
updatedAt: new Date(),
user: {
id: '1',
name: 'User 1'
}
})
];
constructor() {
makeAutoObservable(this);
}
fetchAll = async (params: any = {}) => {
return new Promise((resolve, reject) => {
customEventService.fetchAll(params).then((response: any) => {
runInAction(() => {
this.list = response.data.map((item: any) => CustomEvent.fromJson(item));
});
resolve(this.list);
}).catch(error => {
reject(error);
}).finally(() => {
// this.isLoading = false;
});
});
};
createCustomEvent = async (data: any) => {
return new Promise((resolve, reject) => {
customEventService.create(data).then(response => {
runInAction(() => {
this.list.push(CustomEvent.fromJson(response.data));
});
resolve(response);
}).catch(error => {
reject(error);
});
});
};
updateCustomEvent = async (data: any) => {
};
}

View file

@ -24,6 +24,7 @@ import AiFiltersStore from "./aiFiltersStore";
import SpotStore from "./spotStore";
import LoginStore from "./loginStore";
import FilterStore from "./filterStore";
import CustomEventStore from "./customEventsStore"
export class RootStore {
dashboardStore: DashboardStore;
@ -49,6 +50,7 @@ export class RootStore {
spotStore: SpotStore;
loginStore: LoginStore;
filterStore: FilterStore;
customEventStore: CustomEventStore;
constructor() {
this.dashboardStore = new DashboardStore();
@ -74,6 +76,7 @@ export class RootStore {
this.spotStore = new SpotStore();
this.loginStore = new LoginStore();
this.filterStore = new FilterStore();
this.customEventStore = new CustomEventStore();
}
initClient() {

View file

@ -0,0 +1,49 @@
interface User {
id: string;
name: string;
}
interface ICustomEvent {
id: string;
name: string;
user: User;
created_at: Date;
}
export default class CustomEvent implements ICustomEvent {
id: string;
name: string;
user: User;
created_at: Date;
constructor(id: string, name: string, user: User, created_at: Date) {
this.id = id;
this.name = name;
this.user = user;
this.created_at = created_at;
}
static fromJson(json: any): CustomEvent {
return new CustomEvent(
json.id,
json.name,
{
id: json.user?.id,
name: json.user?.name
},
new Date(json.created_at)
);
}
toJson(): any {
return {
id: this.id,
name: this.name,
user: {
id: this.user.id,
name: this.user.name
},
created_at: this.created_at.toISOString()
};
}
}

View file

@ -67,6 +67,7 @@ export const CLIENT_TABS = {
SITES: 'projects',
CUSTOM_FIELDS: 'metadata',
WEBHOOKS: 'webhooks',
CUSTOM_EVENTS: 'custom-events',
NOTIFICATIONS: 'notifications',
AUDIT: 'audit',
BILLING: 'billing',

View file

@ -0,0 +1,28 @@
import BaseService from './BaseService';
export default class CustomEventService extends BaseService {
async fetchAll(params: {}): Promise<[]> {
return this.client.get('/custom-events', params)
.then(r => r.json()).then(j => j.data);
}
async create(data: {}): Promise<{}> {
return this.client.post('/custom-events', data)
.then(r => r.json()).then(j => j.data);
}
async update(data: {}): Promise<{}> {
return this.client.put(`/custom-events/${data.id}`, data)
.then(r => r.json()).then(j => j.data);
}
async delete(id: string): Promise<{}> {
return this.client.delete(`/custom-events/${id}`)
.then(r => r.json()).then(j => j.data);
}
async fetchOne(id: string): Promise<{}> {
return this.client.get(`/custom-events/${id}`)
.then(r => r.json()).then(j => j.data);
}
}

View file

@ -20,6 +20,7 @@ import WebhookService from './WebhookService';
import SpotService from './spotService';
import LoginService from "./loginService";
import FilterService from "./FilterService";
import CustomEventService from 'App/services/CustomEventService';
export const dashboardService = new DashboardService();
export const metricService = new MetricService();
@ -42,6 +43,7 @@ export const aiService = new AiService();
export const spotService = new SpotService();
export const loginService = new LoginService();
export const filterService = new FilterService();
export const customEventService = new CustomEventService();
export const services = [
dashboardService,
@ -65,4 +67,5 @@ export const services = [
spotService,
loginService,
filterService,
customEventService,
];