diff --git a/frontend/app/components/shared/SessionItem/SessionItem.tsx b/frontend/app/components/shared/SessionItem/SessionItem.tsx index 9f42d4c0a..6df332290 100644 --- a/frontend/app/components/shared/SessionItem/SessionItem.tsx +++ b/frontend/app/components/shared/SessionItem/SessionItem.tsx @@ -1,8 +1,8 @@ import cn from 'classnames'; import { Duration } from 'luxon'; import { observer } from 'mobx-react-lite'; -import React from 'react'; -import { RouteComponentProps, withRouter } from 'react-router-dom'; +import React, { useState, useCallback, useMemo } from 'react'; +import { RouteComponentProps, useHistory, withRouter } from 'react-router-dom'; import { durationFormatted, formatTimeOrDate } from 'App/date'; import { useStore } from 'App/mstore'; @@ -10,16 +10,17 @@ import { assist as assistRoute, isRoute, liveSession, - sessions as sessionsRoute, + sessions as sessionsRoute } from 'App/routes'; import { capitalize } from 'App/utils'; -import { Avatar, CountryFlag, Icon, Label, TextEllipsis, Tooltip } from 'UI'; +import { Avatar, CountryFlag, Icon, Label, TextEllipsis } from 'UI'; import Counter from './Counter'; import ErrorBars from './ErrorBars'; import PlayLink from './PlayLink'; import SessionMetaList from './SessionMetaList'; import stl from './sessionItem.module.css'; +import { Tooltip } from 'antd'; const ASSIST_ROUTE = assistRoute(); const ASSIST_LIVE_SESSION = liveSession(); @@ -59,7 +60,6 @@ interface Props { hasUserFilter?: boolean; disableUser?: boolean; metaList?: Array; - // showActive?: boolean; lastPlayedSessionId?: string; live?: boolean; onClick?: any; @@ -75,14 +75,16 @@ interface Props { const PREFETCH_STATE = { none: 0, loading: 1, - fetched: 2, + fetched: 2 }; function SessionItem(props: RouteComponentProps & Props) { + const { location } = useHistory(); const { settingsStore, sessionStore } = useStore(); const { timezone, shownTimezone } = settingsStore.sessionSettings; - const [prefetchState, setPrefetched] = React.useState(PREFETCH_STATE.none); + const [prefetchState, setPrefetched] = useState(PREFETCH_STATE.none); + // Destructure all props at the top const { session, onUserClick = () => null, @@ -95,6 +97,10 @@ function SessionItem(props: RouteComponentProps & Props) { ignoreAssist = false, bookmarked = false, query, + // location, + isDisabled, + live: propsLive, + isAdd } = props; const { @@ -113,41 +119,56 @@ function SessionItem(props: RouteComponentProps & Props) { viewed, userDeviceType, userNumericHash, - live, + live: sessionLive, metadata, issueTypes, active, platform, timezone: userTimezone, + isCallActive, + agentIds } = session; - const location = props.location; - const queryParams = Object.fromEntries(new URLSearchParams(location.search)); + // Memoize derived values + const queryParams = useMemo( + () => Object.fromEntries(new URLSearchParams(location.search)), + [location.search] + ); + const isMobile = platform !== 'web'; - const formattedDuration = durationFormatted(duration); + const formattedDuration = useMemo(() => durationFormatted(duration), [duration]); const hasUserId = userId || userAnonymousId; - const isSessions = isRoute(SESSIONS_ROUTE, location.pathname); - const isAssist = - (!ignoreAssist && - (isRoute(ASSIST_ROUTE, location.pathname) || - isRoute(ASSIST_LIVE_SESSION, location.pathname) || - location.pathname.includes('multiview'))) || - props.live; + + const isSessions = useMemo( + () => isRoute(SESSIONS_ROUTE, location.pathname), + [location.pathname] + ); + + const isAssist = useMemo(() => { + return ( + (!ignoreAssist && + (isRoute(ASSIST_ROUTE, location.pathname) || + isRoute(ASSIST_LIVE_SESSION, location.pathname) || + location.pathname.includes('multiview'))) || + propsLive + ); + }, [ignoreAssist, location.pathname, propsLive]); const isLastPlayed = lastPlayedSessionId === sessionId; + const live = sessionLive || propsLive; + const isMultiviewDisabled = isDisabled && location.pathname.includes('multiview'); - const _metaList = Object.keys(metadata).map((key) => { - const value = metadata[key]; - return { label: key, value }; - }); + // Memoize metadata list creation + const _metaList = useMemo(() => { + return Object.keys(metadata).map((key) => ({ + label: key, + value: metadata[key] + })); + }, [metadata]); - const handleHover = async () => { - if ( - prefetchState !== PREFETCH_STATE.none || - props.live || - isAssist || - isMobile - ) + // Memoize event handlers + const handleHover = useCallback(async () => { + if (prefetchState !== PREFETCH_STATE.none || live || isAssist || isMobile) return; setPrefetched(PREFETCH_STATE.loading); @@ -157,24 +178,69 @@ function SessionItem(props: RouteComponentProps & Props) { } catch (e) { console.error('Error while prefetching first mob', e); } - }; - const populateData = () => { - if ( - props.live || - isAssist || - prefetchState === PREFETCH_STATE.none || - isMobile - ) { + }, [prefetchState, live, isAssist, isMobile, sessionStore, sessionId]); + + const populateData = useCallback(() => { + if (live || isAssist || prefetchState === PREFETCH_STATE.none || isMobile) { return; } - sessionStore.prefetchSession(session); - }; + }, [live, isAssist, prefetchState, isMobile, sessionStore, session]); + + const handleUserClick = useCallback(() => { + if (!disableUser && !hasUserFilter && hasUserId) { + onUserClick(userId, userAnonymousId); + } + }, [disableUser, hasUserFilter, hasUserId, onUserClick, userId, userAnonymousId]); + + const handleAddClick = useCallback(() => { + if (!isDisabled && onClick) { + onClick(); + } + }, [isDisabled, onClick]); + + // Memoize time formatting + const formattedTime = useMemo(() => { + const timezoneToUse = + shownTimezone === 'user' && userTimezone + ? { + label: userTimezone.split('+').join(' +'), + value: userTimezone.split(':')[0] + } + : timezone; + + return formatTimeOrDate(startedAt, timezoneToUse); + }, [startedAt, shownTimezone, userTimezone, timezone]); + + // Memoize tooltip content + const timeTooltipContent = useMemo(() => { + return ( +
+ + Local Time: {formatTimeOrDate(startedAt, timezone, true)}{' '} + {timezone.label} + + {userTimezone ? ( + + User's Time:{' '} + {formatTimeOrDate( + startedAt, + { + label: userTimezone.split('+').join(' +'), + value: userTimezone.split(':')[0] + }, + true + )}{' '} + {userTimezone} + + ) : null} +
+ ); + }, [startedAt, timezone, userTimezone]); + return (
- !disableUser && !hasUserFilter && hasUserId - ? onUserClick(userId, userAnonymousId) - : null - } + onClick={handleUserClick} > - - Local Time:{' '} - {formatTimeOrDate(startedAt, timezone, true)}{' '} - {timezone.label} - - {userTimezone ? ( - - User's Time:{' '} - {formatTimeOrDate( - startedAt, - { - label: userTimezone.split('+').join(' +'), - value: userTimezone.split(':')[0], - }, - true - )}{' '} - {userTimezone} - - ) : null} -
- } + disabled={isDisabled} + title={timeTooltipContent} className="w-fit !block" >
@@ -290,7 +322,7 @@ function SessionItem(props: RouteComponentProps & Props) { )}
- {live || props.live ? ( + {live ? ( ) : ( formattedDuration @@ -311,27 +343,31 @@ function SessionItem(props: RouteComponentProps & Props) { />
- {userBrowser ? ( + {userBrowser && ( - ) : null} - {userOs && userBrowser ? ( + )} + {userOs && userBrowser && ( - ) : null} - - - - + )} + {userOs && ( + + + + )} + {userOs && ( + + )} - {live && session.isCallActive && session.agentIds!.length > 0 ? ( + {live && isCallActive && agentIds && agentIds.length > 0 && (
- ) : null} + )} {isSessions && (
{isLastPlayed && ( @@ -385,10 +421,10 @@ function SessionItem(props: RouteComponentProps & Props) { )}
)} - {props.isAdd ? ( + {isAdd ? (
(props.isDisabled ? null : props.onClick())} + onClick={handleAddClick} >
@@ -402,7 +438,7 @@ function SessionItem(props: RouteComponentProps & Props) { onClick={onClick} queryParams={queryParams} query={query} - beforeOpen={props.live || isAssist ? undefined : populateData} + beforeOpen={live || isAssist ? undefined : populateData} /> )}
diff --git a/frontend/app/mstore/searchStore.ts b/frontend/app/mstore/searchStore.ts index 4f5a35be7..1e0a00544 100644 --- a/frontend/app/mstore/searchStore.ts +++ b/frontend/app/mstore/searchStore.ts @@ -237,6 +237,29 @@ class SearchStore { void this.fetchSessions(true); } + checkForLatestSessionCount() { + const filter = this.instance.toSearch(); + if (this.latestRequestTime) { + const period = Period({ + rangeName: CUSTOM_RANGE, + start: this.latestRequestTime, + end: Date.now(), + }); + const newTimestamps: any = period.toJSON(); + filter.startDate = newTimestamps.startDate; + filter.endDate = newTimestamps.endDate; + } + // TODO - dedicated API endpoint to get the count of latest sessions, or show X+ sessions + delete filter.limit; + delete filter.page; + searchService.checkLatestSessions(filter).then((response: any) => { + console.log('response', response); + // runInAction(() => { + // this.latestList = List(response); + // }); + }); + } + checkForLatestSessions() { const filter = this.instance.toSearch(); if (this.latestRequestTime) { @@ -395,7 +418,7 @@ class SearchStore { } } - this.latestRequestTime = Date.now(); + this.latestRequestTime = filter.startDate; this.latestList = List(); this.searchInProgress = true; await sessionStore.fetchSessions(