feat ui: remake cobrowse subtabs (#2086)

This commit is contained in:
Delirium 2024-04-15 15:37:15 +02:00 committed by GitHub
parent 217201eb5c
commit d008b65ac0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 92 additions and 111 deletions

View file

@ -23,7 +23,6 @@ const components: any = {
FunnelIssueDetails: lazy(() => import('Components/Funnels/FunnelIssueDetails')),
FunnelPagePure: lazy(() => import('Components/Funnels/FunnelPage')),
MultiviewPure: lazy(() => import('Components/Session_/Multiview/Multiview')),
AssistStatsPure: lazy(() => import('Components/AssistStats')),
UsabilityTestingPure: lazy(() => import('Components/UsabilityTesting/UsabilityTesting')),
UsabilityTestEditPure: lazy(() => import('Components/UsabilityTesting/TestEdit')),
UsabilityTestOverviewPure: lazy(() => import('Components/UsabilityTesting/TestOverview')),
@ -41,7 +40,6 @@ const enhancedComponents: any = {
FunnelsDetails: withSiteIdUpdater(components.FunnelDetailsPure),
FunnelIssue: withSiteIdUpdater(components.FunnelIssueDetails),
Multiview: withSiteIdUpdater(components.MultiviewPure),
AssistStats: withSiteIdUpdater(components.AssistStatsPure),
UsabilityTesting: withSiteIdUpdater(components.UsabilityTestingPure),
UsabilityTestEdit: withSiteIdUpdater(components.UsabilityTestEditPure),
UsabilityTestOverview: withSiteIdUpdater(components.UsabilityTestOverviewPure),
@ -192,12 +190,6 @@ function PrivateRoutes(props: Props) {
path={withSiteId(RECORDINGS_PATH, siteIdList)}
component={enhancedComponents.Assist}
/>
<Route
exact
strict
path={withSiteId(ASSIST_STATS_PATH, siteIdList)}
component={enhancedComponents.AssistStats}
/>
<Route
exact
strict

View file

@ -1,37 +1,15 @@
import React from 'react';
import { Switch, Route } from 'react-router';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import {
assist,
recordings,
withSiteId,
} from 'App/routes';
import AssistView from './AssistView'
import Recordings from './RecordingsList/Recordings'
interface Props extends RouteComponentProps {
match: any;
}
function AssistRouter(props: Props) {
const {
match: {
params: { siteId },
},
} = props;
return (
<div className="w-full">
<Switch>
<Route exact strict path={withSiteId(assist(), siteId)}>
<AssistView />
</Route>
<Route exact strict path={withSiteId(recordings(), siteId)}>
<Recordings />
</Route>
</Switch>
<AssistView />
</div>
);
}

View file

@ -1,43 +1,68 @@
import React from 'react';
import { Button } from 'UI';
import SessionSearchField from 'Shared/SessionSearchField';
import { connect } from 'react-redux';
import { edit as editFilter, addFilterByKeyAndValue, clearSearch, fetchFilterSearch } from 'Duck/liveSearch';
import {
addFilterByKeyAndValue,
clearSearch,
edit as editFilter,
fetchFilterSearch,
} from 'Duck/liveSearch';
import { Button } from 'UI';
import { useModal } from 'App/components/Modal';
import SessionSearchField from 'Shared/SessionSearchField';
import AssistStats from '../../AssistStats';
import Recordings from '../RecordingsList/Recordings'
interface Props {
appliedFilter: any;
fetchFilterSearch: any;
addFilterByKeyAndValue: any;
clearSearch: any;
appliedFilter: any;
fetchFilterSearch: any;
addFilterByKeyAndValue: any;
clearSearch: any;
}
function AssistSearchField(props: Props) {
const hasEvents = props.appliedFilter.filters.filter((i: any) => i.isEvent).size > 0;
const hasFilters = props.appliedFilter.filters.filter((i: any) => !i.isEvent).size > 0;
return (
<div className="flex items-center w-full">
<div style={{ width: '60%', marginRight: '10px' }}>
<SessionSearchField />
</div>
<Button
variant="text-primary"
className="ml-auto font-medium"
disabled={!hasFilters && !hasEvents}
onClick={() => props.clearSearch()}
>
Clear Search
</Button>
</div>
);
const hasEvents =
props.appliedFilter.filters.filter((i: any) => i.isEvent).size > 0;
const hasFilters =
props.appliedFilter.filters.filter((i: any) => !i.isEvent).size > 0;
const { showModal, hideModal } = useModal();
const showStats = () => {
showModal(<AssistStats />, { right: true, width: 960 })
}
const showRecords = () => {
showModal(<Recordings />, { right: true, width: 960 })
}
return (
<div className="flex items-center w-full gap-2">
<div style={{ width: '60%' }}>
<SessionSearchField />
</div>
<Button variant="outline" onClick={showRecords}>Training Videos</Button>
<Button variant="outline" onClick={showStats}>Co-Browsing Reports</Button>
<Button
variant="text-primary"
className="ml-auto font-medium"
disabled={!hasFilters && !hasEvents}
onClick={() => props.clearSearch()}
>
Clear Search
</Button>
</div>
);
}
export default connect(
(state: any) => ({
appliedFilter: state.getIn(['liveSearch', 'instance']),
}),
{
fetchFilterSearch,
editFilter,
addFilterByKeyAndValue,
clearSearch,
}
(state: any) => ({
appliedFilter: state.getIn(['liveSearch', 'instance']),
isEnterprise:
state.getIn(['user', 'account', 'edition']) === 'ee' ||
state.getIn(['user', 'authDetails', 'edition']) === 'ee'
}),
{
fetchFilterSearch,
editFilter,
addFilterByKeyAndValue,
clearSearch,
}
)(AssistSearchField);

View file

@ -6,12 +6,10 @@ import RecordingsList from './RecordingsList';
import { useStore } from 'App/mstore';
import { connect } from 'react-redux';
import SelectDateRange from 'Shared/SelectDateRange/SelectDateRange';
import Period from 'Types/app/period';
import { observer } from 'mobx-react-lite';
interface Props {
userId: string;
filter: any;
}
function Recordings(props: Props) {
@ -28,10 +26,10 @@ function Recordings(props: Props) {
};
return (
<div style={{ maxWidth: '1360px', margin: 'auto' }} className='bg-white rounded py-4 border'>
<div style={{ maxWidth: '1360px', margin: 'auto' }} className='bg-white rounded py-4 border h-screen overflow-y-scroll'>
<div className='flex items-center mb-4 justify-between px-6'>
<div className='flex items-baseline mr-3'>
<PageTitle title='Recordings' />
<PageTitle title='Training Videos' />
</div>
<div className='ml-auto flex items-center gap-4'>
<SelectDateRange period={recordingsStore.period} onChange={onDateChange} right={true} />

View file

@ -29,7 +29,7 @@ function RecordingsList() {
<div className="text-center mt-4">
{recordsSearch !== ''
? 'No matching results'
: 'No recordings available'}
: 'No videos have been recorded in your co-browsing sessions.'}
</div>
</div>
}

View file

@ -14,7 +14,6 @@ import Period, { LAST_24_HOURS } from 'Types/app/period';
import SelectDateRange from 'Shared/SelectDateRange/SelectDateRange';
import TeamMembers from 'Components/AssistStats/components/TeamMembers';
import { durationFromMsFormatted, formatTimeOrDate } from 'App/date'
import withPageTitle from 'HOCs/withPageTitle';
import { exportCSVFile } from 'App/utils';
import { assistStatsService } from 'App/services';
@ -206,11 +205,11 @@ function AssistStats() {
};
return (
<div className={'w-full'}>
<div className={'w-full h-screen overflow-y-auto'}>
<div className={'mx-auto p-4 bg-white rounded border'} style={{ maxWidth: 1360 }} id={'pdf-anchor'}>
<div id={'pdf-ignore'} className={'w-full flex items-center mb-2'}>
<Typography.Title style={{ marginBottom: 0 }} level={4}>
Reports
Co-browsing Reports
</Typography.Title>
<div className={'ml-auto flex items-center gap-2'}>
<UserSearch onUserSelect={onUserSelect} />
@ -227,8 +226,7 @@ function AssistStats() {
</Tooltip>
</div>
</div>
<div className={'w-full grid grid-cols-3 gap-2'}>
<div className={'grid grid-cols-3 gap-2 flex-2 col-span-2'}>
<div className={'w-full grid grid-cols-3 gap-2 flex-2 col-span-2'}>
{Object.keys(graphs.currentPeriod).map((i: PeriodKeys) => (
<div className={'bg-white rounded border'}>
<div className={'pt-2 px-2'}>
@ -238,15 +236,15 @@ function AssistStats() {
<div className={'flex gap-1 items-center'}>
<Typography.Title style={{ marginBottom: 0 }} level={5}>
{graphs.currentPeriod[i]
? durationFromMsFormatted(graphs.currentPeriod[i])
: null}
? durationFromMsFormatted(graphs.currentPeriod[i])
: null}
</Typography.Title>
{graphs.previousPeriod[i] ? (
<div
className={
graphs.currentPeriod[i] > graphs.previousPeriod[i]
? 'flex items-center gap-1 text-green'
: 'flex items-center gap-2 text-red'
? 'flex items-center gap-1 text-green'
: 'flex items-center gap-2 text-red'
}
>
<ArrowUpOutlined
@ -268,15 +266,14 @@ function AssistStats() {
</Loader>
</div>
))}
</div>
<div className={'flex-1 col-span-1'}>
<TeamMembers
isLoading={isLoading}
topMembers={topMembers}
onMembersSort={onMembersSort}
membersSort={membersSort}
/>
</div>
</div>
<div className={'w-full mt-2'}>
<TeamMembers
isLoading={isLoading}
topMembers={topMembers}
onMembersSort={onMembersSort}
membersSort={membersSort}
/>
</div>
<div className={'w-full mt-2'}>
<StatsTable
@ -294,4 +291,4 @@ function AssistStats() {
);
}
export default withPageTitle('Reports - OpenReplay')(AssistStats);
export default AssistStats;

View file

@ -74,14 +74,12 @@ function StatsTable({ onSort, isLoading, onPageChange, page, sessions, exportCSV
Export CSV
</Button>
</div>
<div className={'bg-gray-lightest grid grid-cols-8 items-center font-semibold p-4'}>
<Cell size={1}>Date</Cell>
<div className={'bg-gray-lightest grid grid-cols-9 items-center font-semibold p-4'}>
<Cell size={2}>Date</Cell>
<Cell size={2}>Team Members</Cell>
<Cell size={1}>Live Duration</Cell>
<Cell size={1}>Call Duration</Cell>
<Cell size={1}>Remote Duration</Cell>
<Cell size={1} />
{/* SPACER */}
<Cell size={2}>Remote Duration</Cell>
<Cell size={1}>{/* BUTTONS */}</Cell>
</div>
<div className={'bg-white'}>
@ -125,10 +123,10 @@ function StatsTable({ onSort, isLoading, onPageChange, page, sessions, exportCSV
function Row({ session }: { session: AssistStatsSession }) {
return (
<div className={'grid grid-cols-8 p-4 border-b hover:bg-active-blue'}>
<Cell size={1}>{checkForRecent(getDateFromMill(session.timestamp)!, 'LLL dd, hh:mm a')}</Cell>
<div className={'grid grid-cols-9 p-4 border-b hover:bg-active-blue'}>
<Cell size={2}>{checkForRecent(getDateFromMill(session.timestamp)!, 'LLL dd, hh:mm a')}</Cell>
<Cell size={2}>
<div className={'flex gap-2'}>
<div className={'flex gap-2 flex-wrap'}>
{session.teamMembers.map((member) => (
<div className={'p-1 rounded border bg-gray-lightest w-fit'}>{member.name}</div>
))}
@ -136,8 +134,7 @@ function Row({ session }: { session: AssistStatsSession }) {
</Cell>
<Cell size={1}>{durationFromMsFormatted(session.assistDuration)}</Cell>
<Cell size={1}>{durationFromMsFormatted(session.callDuration)}</Cell>
<Cell size={1}>{durationFromMsFormatted(session.controlDuration)}</Cell>
<Cell size={1} />
<Cell size={2}>{durationFromMsFormatted(session.controlDuration)}</Cell>
<Cell size={1}>
<div className={'w-full flex justify-end gap-4'}>
{session.recordings?.length > 0 ? (

View file

@ -23,7 +23,7 @@ export interface Module {
export const modules = [
{
label: 'Cobrowse',
label: 'Co-Browse',
description: 'Enable live session replay, remote control, annotations and webRTC call/video.',
key: MODULES.ASSIST,
icon: 'broadcast'

View file

@ -4,7 +4,7 @@ import cn from 'classnames';
function ModalOverlay({ hideModal, children, left = false, right = false }: any) {
return (
<div className="fixed w-full h-screen" style={{ zIndex: 9999 }}>
<div className="fixed w-full h-screen" style={{ zIndex: 999 }}>
<div onClick={hideModal} className={stl.overlay} style={{ background: 'rgba(0,0,0,0.5)' }} />
<div className={cn(stl.slide, { [stl.slideLeft]: left, [stl.slideRight]: right })}>{children}</div>
</div>

View file

@ -88,7 +88,7 @@ function LiveSessionList(props: Props) {
<div className="flex mb-6 justify-between items-center">
<div className="flex items-center">
<h3 className="text-2xl capitalize mr-2">
<span>Cobrowse</span>
<span>Co-Browse</span>
{/* <span className="ml-2 font-normal color-gray-medium">{numberWithCommas(total)}</span> */}
</h3>

View file

@ -132,6 +132,7 @@ function SelectDateRange(props: Props) {
) {
return false;
}
console.log('outside')
setIsCustom(false);
}}
>

View file

@ -65,11 +65,10 @@ function SideMenu(props: Props) {
if (item.hidden) return item;
const isHidden = [
(item.key === MENU.STATS && modules.includes(MODULES.ASSIST_STATS)),
(item.key === MENU.RECOMMENDATIONS && modules.includes(MODULES.RECOMMENDATIONS)),
(item.key === MENU.FEATURE_FLAGS && modules.includes(MODULES.FEATURE_FLAGS)),
(item.key === MENU.NOTES && modules.includes(MODULES.NOTES)),
(item.key === MENU.LIVE_SESSIONS || item.key === MENU.RECORDINGS || item.key === MENU.STATS) && modules.includes(MODULES.ASSIST),
(item.key === MENU.LIVE_SESSIONS && modules.includes(MODULES.ASSIST)),
(item.key === MENU.SESSIONS && modules.includes(MODULES.OFFLINE_RECORDINGS)),
(item.key === MENU.ALERTS && modules.includes(MODULES.ALERTS)),
(item.isAdmin && !isAdmin),
@ -107,8 +106,6 @@ function SideMenu(props: Props) {
[MENU.VAULT]: () => withSiteId(routes.bookmarks(), siteId),
[MENU.NOTES]: () => withSiteId(routes.notes(), siteId),
[MENU.LIVE_SESSIONS]: () => withSiteId(routes.assist(), siteId),
[MENU.STATS]: () => withSiteId(routes.assistStats(), siteId),
[MENU.RECORDINGS]: () => withSiteId(routes.recordings(), siteId),
[MENU.DASHBOARDS]: () => withSiteId(routes.dashboard(), siteId),
[MENU.CARDS]: () => withSiteId(routes.metrics(), siteId),
[MENU.ALERTS]: () => withSiteId(routes.alerts(), siteId),

View file

@ -42,8 +42,6 @@ export const enum MENU {
BOOKMARKS = 'bookmarks',
NOTES = 'notes',
LIVE_SESSIONS = 'live-sessions',
RECORDINGS = 'recordings',
STATS = 'assist-stats',
DASHBOARDS = 'dashboards',
CARDS = 'cards',
FUNNELS = 'funnels',
@ -70,12 +68,10 @@ export const categories: Category[] = [
]
},
{
title: 'Assist',
title: '',
key: 'assist',
items: [
{ label: 'Cobrowse', key: MENU.LIVE_SESSIONS, icon: 'broadcast' },
{ label: 'Recordings', key: MENU.RECORDINGS, icon: 'record-btn', isEnterprise: true },
{ label: 'Reports', key: MENU.STATS, icon: 'file-bar-graph', isEnterprise: true }
{ label: 'Co-Browse', key: MENU.LIVE_SESSIONS, icon: 'broadcast' },
]
},
{