fix(ui): sessions list sidemenu fix, reload properly, sessions tags filter issue
This commit is contained in:
parent
44d0bb1369
commit
beb3eeac0c
11 changed files with 113 additions and 202 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import withPageTitle from 'HOCs/withPageTitle';
|
||||
import NoSessionsMessage from 'Shared/NoSessionsMessage';
|
||||
import MainSearchBar from 'Shared/MainSearchBar';
|
||||
|
|
@ -8,9 +8,10 @@ import FFlagsList from 'Components/FFlags';
|
|||
import NewFFlag from 'Components/FFlags/NewFFlag';
|
||||
import { Switch, Route } from 'react-router';
|
||||
import { sessions, fflags, withSiteId, newFFlag, fflag, notes, fflagRead, bookmarks } from 'App/routes';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import { withRouter, RouteComponentProps, useLocation } from 'react-router-dom';
|
||||
import FlagView from 'Components/FFlags/FlagView/FlagView';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useStore } from '@/mstore';
|
||||
|
||||
// @ts-ignore
|
||||
interface IProps extends RouteComponentProps {
|
||||
|
|
@ -23,7 +24,16 @@ interface IProps extends RouteComponentProps {
|
|||
}
|
||||
|
||||
function Overview({ match: { params } }: IProps) {
|
||||
const { searchStore } = useStore();
|
||||
const { siteId, fflagId } = params;
|
||||
const location = useLocation();
|
||||
const tab = location.pathname.split('/')[2];
|
||||
searchStore.setActiveTab(tab);
|
||||
|
||||
// useEffect(() => {
|
||||
// // const tab = location.pathname.split('/')[2];
|
||||
// searchStore.setActiveTab(tab);
|
||||
// }, [tab]);
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
|
|
|
|||
|
|
@ -1,87 +0,0 @@
|
|||
import React from 'react';
|
||||
import { SideMenuitem } from 'UI';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import { sessions, fflags, withSiteId, notes, bookmarks } from 'App/routes';
|
||||
import { useStore } from 'App/mstore';
|
||||
|
||||
interface Props {
|
||||
activeTab: string;
|
||||
}
|
||||
|
||||
const TabToUrlMap = {
|
||||
all: sessions() as '/sessions',
|
||||
bookmark: bookmarks() as '/bookmarks',
|
||||
notes: notes() as '/notes',
|
||||
flags: fflags() as '/feature-flags'
|
||||
};
|
||||
|
||||
function OverviewMenu(props: Props & RouteComponentProps) {
|
||||
// @ts-ignore
|
||||
const { history, match: { params: { siteId } }, location } = props;
|
||||
const { searchStore, userStore } = useStore();
|
||||
const isEnterprise = userStore.isEnterprise;
|
||||
const activeTab = searchStore.activeTab.type;
|
||||
|
||||
React.useEffect(() => {
|
||||
const currentLocation = location.pathname;
|
||||
const tab = Object.keys(TabToUrlMap).find((tab: keyof typeof TabToUrlMap) => currentLocation.includes(TabToUrlMap[tab]));
|
||||
if (tab && tab !== activeTab) {
|
||||
searchStore.setActiveTab({ type: tab });
|
||||
}
|
||||
}, [location.pathname]);
|
||||
|
||||
return (
|
||||
<div className={'flex flex-col gap-2 w-full'}>
|
||||
<div className="w-full">
|
||||
<SideMenuitem
|
||||
active={activeTab === 'all'}
|
||||
id="menu-sessions"
|
||||
title="Sessions"
|
||||
iconName="play-circle-bold"
|
||||
onClick={() => {
|
||||
searchStore.setActiveTab({ type: 'all' });
|
||||
!location.pathname.includes(sessions()) && history.push(withSiteId(sessions(), siteId));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<SideMenuitem
|
||||
active={activeTab === 'bookmark'}
|
||||
id="menu-bookmarks"
|
||||
title={`${isEnterprise ? 'Vault' : 'Bookmarks'}`}
|
||||
iconName={isEnterprise ? 'safe' : 'star'}
|
||||
onClick={() => {
|
||||
// props.setActiveTab({ type: 'bookmark' });
|
||||
!location.pathname.includes(bookmarks()) && history.push(withSiteId(bookmarks(), siteId));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<SideMenuitem
|
||||
active={activeTab === 'notes'}
|
||||
id="menu-notes"
|
||||
title="Notes"
|
||||
iconName="stickies"
|
||||
onClick={() => {
|
||||
searchStore.setActiveTab({ type: 'notes' });
|
||||
!location.pathname.includes(notes()) && history.push(withSiteId(notes(), siteId));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<SideMenuitem
|
||||
active={activeTab === 'flags'}
|
||||
id="menu-flags"
|
||||
title="Feature Flags"
|
||||
iconName="toggles"
|
||||
onClick={() => {
|
||||
searchStore.setActiveTab({ type: 'flags' });
|
||||
!location.pathname.includes(fflags()) && history.push(withSiteId(fflags(), siteId));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default withRouter(OverviewMenu);
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './OverviewMenu';
|
||||
|
|
@ -47,7 +47,7 @@ function SessionSearch(props: Props) {
|
|||
|
||||
useEffect(() => {
|
||||
debounceFetch = debounce(() => searchStore.fetchSessions(), 500);
|
||||
void searchStore.fetchSessions(true)
|
||||
// void searchStore.fetchSessions(true)
|
||||
}, []);
|
||||
|
||||
const onAddFilter = (filter: any) => {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ function SessionsTabOverview() {
|
|||
const [query, setQuery] = React.useState('');
|
||||
const { aiFiltersStore, searchStore } = useStore();
|
||||
const appliedFilter = searchStore.instance;
|
||||
const activeTab = searchStore.activeTab.type;
|
||||
const isNotesRoute = searchStore.activeTab.type === 'notes';
|
||||
|
||||
const handleKeyDown = (event: any) => {
|
||||
if (event.key === 'Enter') {
|
||||
|
|
@ -38,7 +38,7 @@ function SessionsTabOverview() {
|
|||
<SessionHeader />
|
||||
<div className="border-b" />
|
||||
<LatestSessionsMessage />
|
||||
{activeTab !== 'notes' ? (
|
||||
{!isNotesRoute ? (
|
||||
<SessionList />
|
||||
) : (
|
||||
<NotesList />
|
||||
|
|
|
|||
|
|
@ -20,16 +20,16 @@ function SessionHeader() {
|
|||
if (activeTab.type === 'notes') {
|
||||
return 'Notes';
|
||||
}
|
||||
if (activeTab.type === 'bookmark') {
|
||||
if (activeTab.type === 'bookmarks') {
|
||||
return isEnterprise ? 'Vault' : 'Bookmarks';
|
||||
}
|
||||
return 'Sessions';
|
||||
}, [activeTab]);
|
||||
}, [activeTab.type, isEnterprise]);
|
||||
|
||||
const onDateChange = (e: any) => {
|
||||
const dateValues = e.toJSON();
|
||||
searchStore.edit(dateValues);
|
||||
searchStore.fetchSessions();
|
||||
void searchStore.fetchSessions(true);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -37,7 +37,7 @@ function SessionHeader() {
|
|||
<h2 className="text-2xl capitalize mr-4">{title}</h2>
|
||||
{activeTab.type !== 'notes' ? (
|
||||
<div className="flex items-center w-full justify-end">
|
||||
{activeTab.type !== 'bookmark' && (
|
||||
{activeTab.type !== 'bookmarks' && (
|
||||
<>
|
||||
<SessionTags />
|
||||
<div className="mr-auto" />
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React, { useEffect } from 'react';
|
|||
import { FilterKey } from 'Types/filter/filterType';
|
||||
import SessionItem from 'Shared/SessionItem';
|
||||
import { NoContent, Loader, Pagination, Button } from 'UI';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { useLocation, withRouter } from 'react-router-dom';
|
||||
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
|
||||
import { numberWithCommas } from 'App/utils';
|
||||
import SessionDateRange from './SessionDateRange';
|
||||
|
|
@ -23,6 +23,10 @@ let sessionStatusTimeOut: any = null;
|
|||
const STATUS_FREQUENCY = 5000;
|
||||
|
||||
function SessionList() {
|
||||
const location = useLocation(); // Get the current URL location
|
||||
const isSessionsRoute = location.pathname.includes('/sessions');
|
||||
const isBookmark = location.pathname.includes('/bookmarks');
|
||||
|
||||
const { projectsStore, sessionStore, customFieldStore, userStore } = useStore();
|
||||
const isEnterprise = userStore.isEnterprise;
|
||||
const isLoggedIn = userStore.isLoggedIn;
|
||||
|
|
@ -39,12 +43,17 @@ function SessionList() {
|
|||
const _filterKeys = filters.map((i: any) => i.key);
|
||||
const hasUserFilter =
|
||||
_filterKeys.includes(FilterKey.USERID) || _filterKeys.includes(FilterKey.USERANONYMOUSID);
|
||||
const isBookmark = activeTab.type === 'bookmark';
|
||||
// const isBookmark = activeTab.type === 'bookmark';
|
||||
const isVault = isBookmark && isEnterprise;
|
||||
const activeSite = projectsStore.active;
|
||||
const hasNoRecordings = !activeSite || !activeSite.recorded;
|
||||
const metaList = customFieldStore.list;
|
||||
|
||||
useEffect(() => {
|
||||
void searchStore.fetchSessions(true, isBookmark);
|
||||
}, [location.pathname]);
|
||||
|
||||
|
||||
const NO_CONTENT = React.useMemo(() => {
|
||||
if (isBookmark && !isEnterprise) {
|
||||
return {
|
||||
|
|
@ -61,7 +70,7 @@ function SessionList() {
|
|||
icon: ICONS.NO_SESSIONS,
|
||||
message: <SessionDateRange />
|
||||
};
|
||||
}, [isBookmark, isVault, activeTab]);
|
||||
}, [isBookmark, isVault, activeTab, location.pathname]);
|
||||
const [statusData, setStatusData] = React.useState<SessionStatus>({ status: 0, count: 0 });
|
||||
|
||||
|
||||
|
|
@ -101,11 +110,11 @@ function SessionList() {
|
|||
}
|
||||
}, [statusData, siteId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (siteId) {
|
||||
void searchStore.fetchSessions();
|
||||
}
|
||||
}, [siteId])
|
||||
// useEffect(() => {
|
||||
// if (siteId) {
|
||||
// void searchStore.fetchSessions();
|
||||
// }
|
||||
// }, [siteId]);
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => {
|
||||
|
|
@ -121,11 +130,11 @@ function SessionList() {
|
|||
const { scrollY } = searchStore;
|
||||
window.scrollTo(0, scrollY);
|
||||
|
||||
if (total === 0 && !loading && !hasNoRecordings) {
|
||||
setTimeout(() => {
|
||||
void searchStore.fetchSessions();
|
||||
}, 100);
|
||||
}
|
||||
// if (total === 0 && !loading && !hasNoRecordings) {
|
||||
// setTimeout(() => {
|
||||
// void searchStore.fetchSessions();
|
||||
// }, 100);
|
||||
// }
|
||||
|
||||
return () => {
|
||||
searchStore.setScrollPosition(window.scrollY);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
import { issues_types, types } from 'Types/session/issue';
|
||||
import { Segmented } from 'antd';
|
||||
import cn from 'classnames';
|
||||
import { Angry, CircleAlert, Skull, WifiOff } from 'lucide-react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import React from 'react';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { Icon } from 'UI';
|
||||
|
||||
interface Tag {
|
||||
name: string;
|
||||
|
|
@ -15,7 +12,6 @@ interface Tag {
|
|||
}
|
||||
|
||||
interface StateProps {
|
||||
|
||||
}
|
||||
|
||||
type Props = StateProps;
|
||||
|
|
@ -25,94 +21,37 @@ const tagIcons = {
|
|||
[types.JS_EXCEPTION]: <CircleAlert size={14} />,
|
||||
[types.BAD_REQUEST]: <WifiOff size={14} />,
|
||||
[types.CLICK_RAGE]: <Angry size={14} />,
|
||||
[types.CRASH]: <Skull size={14} />,
|
||||
[types.CRASH]: <Skull size={14} />
|
||||
} as Record<string, any>;
|
||||
|
||||
const SessionTags: React.FC<Props> = () => {
|
||||
const { projectsStore, sessionStore, searchStore } = useStore();
|
||||
const total = sessionStore.total;
|
||||
const platform = projectsStore.active?.platform || '';
|
||||
const disable = searchStore.activeTab.type === 'all' && total === 0;
|
||||
const activeTab = searchStore.activeTab;
|
||||
const tags = issues_types.filter(
|
||||
(tag) =>
|
||||
tag.type !== 'mouse_thrashing' &&
|
||||
(platform === 'web'
|
||||
? tag.type !== types.TAP_RAGE
|
||||
: tag.type !== types.CLICK_RAGE)
|
||||
);
|
||||
const activeTab = searchStore.activeTags;
|
||||
|
||||
const options = tags.map((tag, i) => ({
|
||||
label: (
|
||||
<div className={'flex items-center gap-2'}>
|
||||
{tag.icon ? (
|
||||
tagIcons[tag.type] ? (
|
||||
tagIcons[tag.type]
|
||||
) : (
|
||||
<Icon
|
||||
name={tag.icon}
|
||||
color={activeTab.type === tag.type ? 'main' : undefined}
|
||||
size="14"
|
||||
className={cn('group-hover:fill-teal')}
|
||||
/>
|
||||
)
|
||||
) : null}
|
||||
<div className={activeTab.type === tag.type ? 'text-main' : ''}>
|
||||
{tag.name}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
value: tag.type,
|
||||
disabled: disable && tag.type !== 'all',
|
||||
}));
|
||||
|
||||
const onPick = (tabValue: string) => {
|
||||
const tab = tags.find((t) => t.type === tabValue);
|
||||
if (tab) {
|
||||
searchStore.setActiveTab(tab);
|
||||
}
|
||||
};
|
||||
return (
|
||||
return total === 0 && (activeTab.length === 0 || activeTab[0] === 'all') ? null : (
|
||||
<div className="flex items-center">
|
||||
<Segmented
|
||||
options={options}
|
||||
value={activeTab.type}
|
||||
onChange={onPick}
|
||||
options={issues_types
|
||||
.filter(
|
||||
(tag) =>
|
||||
tag.type !== 'mouse_thrashing' &&
|
||||
(platform === 'web'
|
||||
? tag.type !== types.TAP_RAGE
|
||||
: tag.type !== types.CLICK_RAGE)
|
||||
)
|
||||
.map((tag: any) => ({
|
||||
value: tag.type,
|
||||
icon: tagIcons[tag.type],
|
||||
label: tag.name
|
||||
}))}
|
||||
value={activeTab[0]}
|
||||
onChange={(value: any) => searchStore.toggleTag(value)}
|
||||
size={'small'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Separate the TagItem into its own memoized component.
|
||||
export const TagItem: React.FC<{
|
||||
isActive: boolean;
|
||||
onClick: () => void;
|
||||
label: string;
|
||||
icon?: string;
|
||||
disabled: boolean;
|
||||
}> = memo(({ isActive, onClick, label, icon, disabled }) => (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
'transition group rounded ml-2 px-2 py-1 flex items-center uppercase text-sm hover:bg-active-blue hover:text-teal',
|
||||
{
|
||||
'bg-active-blue text-teal': isActive,
|
||||
disabled: disabled
|
||||
}
|
||||
)}
|
||||
style={{ height: '36px' }}
|
||||
>
|
||||
{icon && (
|
||||
<Icon
|
||||
name={icon}
|
||||
color={isActive ? 'teal' : 'gray-medium'}
|
||||
size="14"
|
||||
className={cn('group-hover:fill-teal mr-2')}
|
||||
/>
|
||||
)}
|
||||
<span className="leading-none font-medium">{label}</span>
|
||||
</button>
|
||||
));
|
||||
|
||||
export default observer(SessionTags);
|
||||
|
|
|
|||
|
|
@ -139,12 +139,12 @@ function SideMenu(props: Props) {
|
|||
|
||||
React.useEffect(() => {
|
||||
const currentLocation = location.pathname;
|
||||
const tab = Object.keys(TabToUrlMap).find((tab: keyof typeof TabToUrlMap) =>
|
||||
currentLocation.includes(TabToUrlMap[tab])
|
||||
);
|
||||
if (tab && tab !== searchStore.activeTab && siteId) {
|
||||
searchStore.setActiveTab({ type: tab });
|
||||
}
|
||||
// const tab = Object.keys(TabToUrlMap).find((tab: keyof typeof TabToUrlMap) =>
|
||||
// currentLocation.includes(TabToUrlMap[tab])
|
||||
// );
|
||||
// if (tab && tab !== searchStore.activeTab && siteId) {
|
||||
// searchStore.setActiveTab({ type: tab });
|
||||
// }
|
||||
}, [location.pathname]);
|
||||
|
||||
const menuRoutes: any = {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import Filter, { checkFilterValue, IFilter } from 'App/mstore/types/filter';
|
|||
import FilterItem from 'App/mstore/types/filterItem';
|
||||
import { sessionStore } from 'App/mstore';
|
||||
import SavedSearch, { ISavedSearch } from 'App/mstore/types/savedSearch';
|
||||
import { iTag } from '@/services/NotesService';
|
||||
import { issues_types } from 'Types/session/issue';
|
||||
|
||||
const PER_PAGE = 10;
|
||||
|
||||
|
|
@ -48,6 +50,14 @@ export const filterMap = ({
|
|||
filters: filters ? filters.map(filterMap) : []
|
||||
});
|
||||
|
||||
export const TAB_MAP: any = {
|
||||
all: { name: 'All', type: 'all' },
|
||||
sessions: { name: 'Sessions', type: 'sessions' },
|
||||
bookmarks: { name: 'Bookmarks', type: 'bookmarks' },
|
||||
notes: { name: 'Notes', type: 'notes' },
|
||||
recommendations: { name: 'Recommendations', type: 'recommendations' }
|
||||
};
|
||||
|
||||
class SearchStore {
|
||||
filterList = generateFilterOptions(filtersMap);
|
||||
filterListLive = generateFilterOptions(liveFiltersMap);
|
||||
|
|
@ -68,6 +78,7 @@ class SearchStore {
|
|||
total: number = 0;
|
||||
loadingFilterSearch = false;
|
||||
isSaving: boolean = false;
|
||||
activeTags: any[] = [];
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
|
|
@ -79,7 +90,7 @@ class SearchStore {
|
|||
// filters: savedSearch.filter.filters
|
||||
// });
|
||||
console.log('savedSearch.filter.filters', savedSearch.filter.filters);
|
||||
this.edit({ filters: savedSearch.filter ? savedSearch.filter.filters.map((i: FilterItem) => new FilterItem().fromJson(i)) : []});
|
||||
this.edit({ filters: savedSearch.filter ? savedSearch.filter.filters.map((i: FilterItem) => new FilterItem().fromJson(i)) : [] });
|
||||
// this.edit({ filters: savedSearch.filter ? savedSearch.filter.filters : [] });
|
||||
this.currentPage = 1;
|
||||
}
|
||||
|
|
@ -131,12 +142,23 @@ class SearchStore {
|
|||
void this.fetchSessions();
|
||||
}
|
||||
|
||||
setActiveTab(tab: any) {
|
||||
this.activeTab = tab;
|
||||
setActiveTab(tab: string) {
|
||||
this.activeTab = TAB_MAP[tab];
|
||||
// this.activeTab = tab;
|
||||
this.currentPage = 1;
|
||||
// this.fetchSessions();
|
||||
}
|
||||
|
||||
toggleTag(tag?: iTag) {
|
||||
if (!tag) {
|
||||
this.activeTags = [];
|
||||
void this.fetchSessions(true);
|
||||
} else {
|
||||
this.activeTags = [tag];
|
||||
void this.fetchSessions(true);
|
||||
}
|
||||
}
|
||||
|
||||
async removeSavedSearch(id: string): Promise<void> {
|
||||
await searchService.deleteSavedSearch(id);
|
||||
this.savedSearch = new SavedSearch({});
|
||||
|
|
@ -174,6 +196,7 @@ class SearchStore {
|
|||
|
||||
this.savedSearch = new SavedSearch({});
|
||||
sessionStore.clearList();
|
||||
void this.fetchSessions(true);
|
||||
}
|
||||
|
||||
checkForLatestSessions() {
|
||||
|
|
@ -274,13 +297,26 @@ class SearchStore {
|
|||
// TODO
|
||||
}
|
||||
|
||||
async fetchSessions(force: boolean = false): Promise<void> {
|
||||
async fetchSessions(force: boolean = false, bookmarked: boolean = false): Promise<void> {
|
||||
const filter = this.instance.toSearch();
|
||||
|
||||
if (this.activeTags[0] && this.activeTags[0] !== 'all') {
|
||||
const tagFilter = filtersMap[FilterKey.ISSUE];
|
||||
tagFilter.value = [issues_types.find((i: any) => i.type === this.activeTags[0])?.type];
|
||||
delete tagFilter.operatorOptions;
|
||||
delete tagFilter.options;
|
||||
delete tagFilter.placeholder;
|
||||
delete tagFilter.label;
|
||||
delete tagFilter.icon;
|
||||
filter.filters = filter.filters.concat(tagFilter);
|
||||
}
|
||||
|
||||
await sessionStore.fetchSessions({
|
||||
...this.instance.toSearch(),
|
||||
...filter,
|
||||
page: this.currentPage,
|
||||
perPage: this.pageSize,
|
||||
tab: this.activeTab.type,
|
||||
bookmarked: this.activeTab.type === 'bookmark' ? true : undefined
|
||||
bookmarked: bookmarked ? true : undefined
|
||||
}, force);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -431,3 +431,8 @@ p {
|
|||
.ant-segmented-group{
|
||||
gap:0.25rem;
|
||||
}
|
||||
|
||||
.ant-segmented-item-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue