change(ui) - card fixes and other misc improvements

This commit is contained in:
Shekar Siri 2023-01-24 12:06:11 +01:00
parent 36eed6220f
commit d34112c54f
19 changed files with 216 additions and 184 deletions

View file

@ -37,7 +37,7 @@ const SlackForm = (props: Props) => {
<div className="shrink-0" style={{ width: '350px' }}>
<div className="flex items-center p-5">
<h3 className="text-2xl mr-3">Slack</h3>
<Button rounded={true} icon="plus" variant="outline" onClick={onNew}/>
<Button rounded={true} icon="plus" iconSize={24} variant="outline" onClick={onNew}/>
</div>
<SlackChannelList onEdit={onEdit} />
</div>

View file

@ -65,7 +65,7 @@ class TeamsAddForm extends React.PureComponent<Props> {
name="endpoint"
value={instance.endpoint}
onChange={this.write}
placeholder="Slack webhook URL"
placeholder="Teams webhook URL"
type="text"
/>
</Form.Field>

View file

@ -20,7 +20,7 @@ function TeamsChannelList(props: { list: any, edit: (inst: any) => any, onEdit:
<div className="text-base text-left">
Integrate MS Teams with OpenReplay and share insights with the rest of the team, directly from the recording page.
</div>
<DocLink className="mt-4 text-base" label="Integrate Slack" url="https://docs.openreplay.com/integrations/slack" />
<DocLink className="mt-4 text-base" label="Integrate MS Teams" url="https://docs.openreplay.com/integrations/ms-teams" />
</div>
}
size="small"

View file

@ -37,7 +37,7 @@ const MSTeams = (props: Props) => {
<div className="shrink-0" style={{ width: '350px' }}>
<div className="flex items-center p-5">
<h3 className="text-2xl mr-3">Microsoft Teams</h3>
<Button rounded={true} icon="plus" variant="outline" onClick={onNew}/>
<Button rounded={true} icon="plus" iconSize={24} variant="outline" onClick={onNew}/>
</div>
<TeamsChannelList onEdit={onEdit} />
</div>

View file

@ -1,83 +1,74 @@
import { useObserver } from "mobx-react-lite";
import React from "react";
import SessionItem from "Shared/SessionItem";
import { Pagination, NoContent } from "UI";
import { useStore } from "App/mstore";
import { observer, useObserver } from 'mobx-react-lite';
import React, { useMemo } from 'react';
import SessionItem from 'Shared/SessionItem';
import { Pagination, NoContent } from 'UI';
import { useStore } from 'App/mstore';
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
import Session from 'App/mstore/types/session';
interface Props {
metric: any;
isTemplate?: boolean;
isEdit?: boolean;
data: any;
metric: any;
isTemplate?: boolean;
isEdit?: boolean;
data: any;
}
function CustomMetricTableSessions(props: Props) {
const { isEdit = false, metric, data } = props;
const { dashboardStore } = useStore();
const period = dashboardStore.period;
const { isEdit = false, metric, data } = props;
return useObserver(() => (
<NoContent
show={
!metric ||
!data ||
!data.sessions ||
data.sessions.length === 0
}
size="small"
title={
<div className="flex items-center justify-center flex-col">
<AnimatedSVG name={ICONS.NO_SESSIONS} size={170} />
<div className="mt-2" />
<div className="text-center text-gray-600">No relevant sessions found for the selected time period.</div>
</div>
}
>
<div className="pb-4">
{data.sessions &&
data.sessions.map((session: any, index: any) => (
<div
className="border-b last:border-none"
key={session.sessionId}
>
<SessionItem session={session} />
</div>
))}
const sessions = useMemo(() => {
return data && data.sessions ? data.sessions.map((session: any) => new Session().fromJson(session)) : [];
}, []);
{isEdit && (
<div className="mt-6 flex items-center justify-center">
<Pagination
page={metric.page}
totalPages={Math.ceil(
data.total / metric.limit
)}
onPageChange={(page: any) =>
metric.updateKey("page", page)
}
limit={data.total}
debounceRequest={500}
/>
</div>
)}
{!isEdit && (
<ViewMore total={data.total} limit={metric.limit} />
)}
return useObserver(() => (
<NoContent
show={!metric || !data || !sessions || sessions.length === 0}
size="small"
title={
<div className="flex items-center justify-center flex-col">
<AnimatedSVG name={ICONS.NO_SESSIONS} size={170} />
<div className="mt-2" />
<div className="text-center text-gray-600">
No relevant sessions found for the selected time period.
</div>
</div>
}
>
<div className="pb-4">
{sessions &&
sessions.map((session: any, index: any) => (
<div className="border-b last:border-none" key={session.sessionId}>
<SessionItem session={session} />
</div>
</NoContent>
));
))}
{isEdit && (
<div className="mt-6 flex items-center justify-center">
<Pagination
page={metric.page}
totalPages={Math.ceil(data.total / metric.limit)}
onPageChange={(page: any) => metric.updateKey('page', page)}
limit={data.total}
debounceRequest={500}
/>
</div>
)}
{!isEdit && <ViewMore total={data.total} limit={metric.limit} />}
</div>
</NoContent>
));
}
export default CustomMetricTableSessions;
export default observer(CustomMetricTableSessions);
const ViewMore = ({ total, limit }: any) =>
total > limit && (
<div className="mt-4 flex items-center justify-center cursor-pointer w-fit mx-auto">
<div className="text-center">
<div className="color-teal text-lg">
All <span className="font-medium">{total}</span> sessions
</div>
</div>
total > limit ? (
<div className="mt-4 flex items-center justify-center cursor-pointer w-fit mx-auto">
<div className="text-center">
<div className="color-teal text-lg">
All <span className="font-medium">{total}</span> sessions
</div>
);
</div>
</div>
) : null;

View file

@ -86,6 +86,7 @@ function DashboardHeader(props: Props) {
showModal(<AddCardModal dashboardId={dashboardId} siteId={siteId} />, { right: true })
}
icon="plus"
iconSize={24}
>
Add Card
</Button>

View file

@ -11,27 +11,34 @@ interface Props extends RouteComponentProps {
siteId: string;
selected?: boolean;
toggleSelection?: any;
disableSelection?: boolean
disableSelection?: boolean;
}
function MetricTypeIcon({ type }: any) {
const [card, setCard] = useState<any>('');
useEffect(() => {
const t = TYPES.find(i => i.slug === type);
setCard(t)
}, [type])
const t = TYPES.find((i) => i.slug === type);
setCard(t);
}, [type]);
return (
<Tooltip delay={0} title={<div className="capitalize">{card.title}</div>} >
<Tooltip delay={0} title={<div className="capitalize">{card.title}</div>}>
<div className="w-9 h-9 rounded-full bg-tealx-lightest flex items-center justify-center mr-2">
{ card.icon && <Icon name={card.icon} size="16" color="tealx" /> }
{card.icon && <Icon name={card.icon} size="16" color="tealx" />}
</div>
</Tooltip>
);
}
function MetricListItem(props: Props) {
const { metric, history, siteId, selected, toggleSelection = () => {}, disableSelection = false } = props;
const {
metric,
history,
siteId,
selected,
toggleSelection = () => {},
disableSelection = false,
} = props;
const onItemClick = (e: React.MouseEvent) => {
if (!disableSelection) {
@ -40,7 +47,7 @@ function MetricListItem(props: Props) {
const path = withSiteId(`/metrics/${metric.metricId}`, siteId);
history.push(path);
};
return (
<div
className="grid grid-cols-12 py-4 border-t select-none items-center hover:bg-active-blue cursor-pointer px-6"
@ -56,10 +63,10 @@ function MetricListItem(props: Props) {
onClick={toggleSelection}
/>
)}
<div className="flex items-center">
<MetricTypeIcon type={metric.metricType} />
<div className={ cn("capitalize-first", { "link" : disableSelection })}>{metric.name}</div>
<div className={cn('capitalize-first', { link: disableSelection })}>{metric.name}</div>
</div>
</div>
<div className="col-span-4">{metric.owner}</div>

View file

@ -26,7 +26,7 @@ function MetricTypeItem(props: Props) {
<Tooltip disabled={!disabled} title="This feature requires an enterprise license." delay={0}>
<div
className={cn(
'rounded color-gray-darkest flex items-start border border-transparent p-4 hover:bg-active-blue hover:!border-active-blue-border cursor-pointer group hover-color-teal',
'rounded color-gray-darkest flex items-start border border-transparent p-4 hover:bg-active-blue cursor-pointer group hover-color-teal',
{ 'opacity-30 pointer-events-none': disabled }
)}
onClick={onClick}

View file

@ -1,14 +1,19 @@
import React from 'react';
import WidgetWrapper from 'App/components/Dashboard/components/WidgetWrapper';
interface Props {
import { withRouter, RouteComponentProps } from 'react-router-dom';
import { withSiteId } from 'App/routes';
interface Props extends RouteComponentProps {
list: any;
siteId: any;
selectedList: any;
toggleSelection?: (metricId: any) => void;
}
function GridView(props: Props) {
const { siteId, list, selectedList, toggleSelection } = props;
const { siteId, list, selectedList, history } = props;
const onItemClick = (metricId: number) => {
const path = withSiteId(`/metrics/${metricId}`, siteId);
history.push(path);
};
return (
<div className="grid grid-cols-4 gap-4 m-4 items-start">
{list.map((metric: any) => (
@ -17,9 +22,9 @@ function GridView(props: Props) {
key={metric.metricId}
widget={metric}
active={selectedList.includes(metric.metricId)}
// isTemplate={true}
isTemplate={true}
isWidget={metric.metricType === 'predefined'}
// onClick={() => toggleSelection(parseInt(metric.metricId))}
onClick={() => onItemClick(parseInt(metric.metricId))}
/>
</React.Fragment>
))}
@ -27,4 +32,4 @@ function GridView(props: Props) {
);
}
export default GridView;
export default withRouter(GridView);

View file

@ -148,7 +148,7 @@ function WidgetForm(props: Props) {
{metric.metricType === INSIGHTS && (
<>
<span className="mx-3">issue category</span>
<span className="mx-3">of</span>
<Select
name="metricValue"
options={issueCategories}

View file

@ -71,7 +71,7 @@ function WidgetWrapper(props: Props & RouteComponentProps) {
});
const onDelete = async () => {
dashboardStore.deleteDashboardWidget(dashboard?.dashboardId, widget.widgetId);
dashboardStore.deleteDashboardWidget(dashboard?.dashboardId!, widget.widgetId);
};
const onChartClick = () => {
@ -119,53 +119,59 @@ function WidgetWrapper(props: Props & RouteComponentProps) {
{'Cannot drill down system provided metrics'}
</div>
)}
<Tooltip disabled={!isTemplate} title="Click to select">
{addOverlay && <TemplateOverlay onClick={onChartClick} isTemplate={isTemplate} />}
<div
className={cn('p-3 pb-4 flex items-center justify-between', {
'cursor-move': !isTemplate && isWidget,
})}
>
{!props.hideName ? (
<div className="capitalize-first w-full font-medium"><TextEllipsis text={widget.name} /></div>
) : null}
{isWidget && (
<div className="flex items-center" id="no-print">
{!isPredefined && isTimeSeries && (
<>
<AlertButton seriesId={widget.series[0] && widget.series[0].seriesId} />
<div className="mx-2" />
</>
)}
{!isTemplate && (
<ItemMenu
items={[
{
text:
widget.metricType === 'predefined'
? 'Cannot edit system generated metrics'
: 'Edit',
onClick: onChartClick,
disabled: widget.metricType === 'predefined',
},
{
text: 'Hide',
onClick: onDelete,
},
]}
/>
)}
</div>
)}
</div>
<LazyLoad offset={!isTemplate ? 100 : 600}>
<div className="px-4" onClick={onChartClick}>
<WidgetChart isPreview={isPreview} metric={widget} isTemplate={isTemplate} isWidget={isWidget} />
{addOverlay && <TemplateOverlay onClick={onChartClick} isTemplate={isTemplate} />}
<div
className={cn('p-3 pb-4 flex items-center justify-between', {
'cursor-move': !isTemplate && isWidget,
})}
>
{!props.hideName ? (
<div className="capitalize-first w-full font-medium">
<TextEllipsis text={widget.name} />
</div>
</LazyLoad>
</Tooltip>
) : null}
{isWidget && (
<div className="flex items-center" id="no-print">
{!isPredefined && isTimeSeries && (
<>
<AlertButton seriesId={widget.series[0] && widget.series[0].seriesId} />
<div className="mx-2" />
</>
)}
{!isTemplate && (
<ItemMenu
items={[
{
text:
widget.metricType === 'predefined'
? 'Cannot edit system generated metrics'
: 'Edit',
onClick: onChartClick,
disabled: widget.metricType === 'predefined',
},
{
text: 'Hide',
onClick: onDelete,
},
]}
/>
)}
</div>
)}
</div>
<LazyLoad offset={!isTemplate ? 100 : 600}>
<div className="px-4" onClick={onChartClick}>
<WidgetChart
isPreview={isPreview}
metric={widget}
isTemplate={isTemplate}
isWidget={isWidget}
/>
</div>
</LazyLoad>
</div>
);
}

View file

@ -16,7 +16,7 @@ function CustomDropdownOption(props: Props) {
<Tooltip disabled={!disabled} title="This feature requires an enterprise license." delay={0}>
<div
className={cn(
'group p-2 flex item-start border border-transparent rounded hover:border-teal hover:!bg-active-blue !leading-0',
'cursor-pointer group p-2 flex item-start border border-transparent rounded hover:!bg-active-blue !leading-0',
{ 'opacity-30': disabled }
)}
>

View file

@ -12,6 +12,7 @@ import PlayLink from './PlayLink';
import ErrorBars from './ErrorBars';
import { assist as assistRoute, liveSession, sessions as sessionsRoute, isRoute } from 'App/routes';
import { capitalize } from 'App/utils';
import { Duration } from 'luxon';
const ASSIST_ROUTE = assistRoute();
const ASSIST_LIVE_SESSION = liveSession();
@ -27,7 +28,7 @@ interface Props {
userDisplayName: string;
userCountry: string;
startedAt: number;
duration: string;
duration: Duration;
eventsCount: number;
errorsCount: number;
pagesCount: number;
@ -98,8 +99,9 @@ function SessionItem(props: RouteComponentProps & Props) {
const hasUserId = userId || userAnonymousId;
const isSessions = isRoute(SESSIONS_ROUTE, location.pathname);
const isAssist =
isRoute(ASSIST_ROUTE, location.pathname) || isRoute(ASSIST_LIVE_SESSION, location.pathname)
|| location.pathname.includes('multiview');
isRoute(ASSIST_ROUTE, location.pathname) ||
isRoute(ASSIST_LIVE_SESSION, location.pathname) ||
location.pathname.includes('multiview');
const isLastPlayed = lastPlayedSessionId === sessionId;
const _metaList = Object.keys(metadata)
@ -213,7 +215,7 @@ function SessionItem(props: RouteComponentProps & Props) {
id="play-button"
data-viewed={viewed}
>
{live && session.isCallActive && session.agentIds.length > 0 ? (
{live && session.isCallActive && session.agentIds!.length > 0 ? (
<div className="mr-4">
<Label className="bg-gray-lightest p-1 px-2 rounded-lg">
<span className="color-gray-medium text-xs" style={{ whiteSpace: 'nowrap' }}>

View file

@ -32,41 +32,25 @@ export default (props: Props) => {
...rest
} = props;
let classes = ['relative flex items-center h-10 px-3 rounded tracking-wide whitespace-nowrap'];
let iconColor = variant === 'text' || variant === 'default' ? 'gray-dark' : 'teal';
if (variant === 'default') {
classes.push('bg-white hover:bg-gray-light border border-gray-light');
}
const variantClasses = {
default: 'bg-white hover:bg-gray-light border border-gray-light',
primary: 'bg-teal color-white hover:bg-teal-dark',
green: 'bg-green color-white hover:bg-green-dark',
text: 'bg-transparent color-gray-dark hover:bg-gray-light-shade hover:!text-teal hover-fill-teal',
'text-primary': 'bg-transparent color-teal hover:bg-teal-light hover:color-teal-dark',
'text-red': 'bg-transparent color-red hover:bg-teal-light',
outline: 'bg-white color-teal border border-teal hover:bg-teal-light',
};
if (variant === 'primary') {
classes.push('bg-teal color-white hover:bg-teal-dark');
}
if (variant === 'green') {
classes.push('bg-green color-white hover:bg-green-dark');
iconColor = 'white';
}
if (variant === 'text') {
classes.push('bg-transparent color-gray-dark hover:bg-gray-light-shade');
}
if (variant === 'text-primary') {
classes.push('bg-transparent color-teal hover:bg-teal-light hover:color-teal-dark');
}
if (variant === 'text-red') {
classes.push('bg-transparent color-red hover:bg-teal-light');
}
if (variant === 'outline') {
classes.push('bg-white color-teal border border-teal hover:bg-teal-light');
}
if (disabled) {
classes.push('opacity-40 pointer-events-none');
}
let classes = cn(
'relative flex items-center h-10 px-3 rounded tracking-wide whitespace-nowrap',
variantClasses[variant],
{ 'opacity-40 pointer-events-none': disabled },
{ '!rounded-full h-10 w-10 justify-center': rounded },
className
);
if (variant === 'primary') {
iconColor = 'white';
@ -75,10 +59,6 @@ export default (props: Props) => {
iconColor = 'red';
}
if (rounded) {
classes = classes.map((c) => c.replace('rounded', 'rounded-full h-10 w-10 justify-center'));
}
const render = () => (
<button {...rest} type={type} className={cn(classes, className)}>
{icon && (

File diff suppressed because one or more lines are too long

View file

@ -37,6 +37,42 @@
.fill-borderColor { fill: $borderColor }
.fill-transparent { fill: $transparent }
.fill-figmaColors { fill: $figmaColors }
.hover-fill-main:hover svg { fill: $main }
.hover-fill-gray-light-shade:hover svg { fill: $gray-light-shade }
.hover-fill-gray-lightest:hover svg { fill: $gray-lightest }
.hover-fill-gray-light:hover svg { fill: $gray-light }
.hover-fill-gray-bg:hover svg { fill: $gray-bg }
.hover-fill-gray-medium:hover svg { fill: $gray-medium }
.hover-fill-gray-dark:hover svg { fill: $gray-dark }
.hover-fill-gray-darkest:hover svg { fill: $gray-darkest }
.hover-fill-teal:hover svg { fill: $teal }
.hover-fill-teal-dark:hover svg { fill: $teal-dark }
.hover-fill-teal-light:hover svg { fill: $teal-light }
.hover-fill-tealx:hover svg { fill: $tealx }
.hover-fill-tealx-light:hover svg { fill: $tealx-light }
.hover-fill-tealx-light-border:hover svg { fill: $tealx-light-border }
.hover-fill-tealx-lightest:hover svg { fill: $tealx-lightest }
.hover-fill-orange:hover svg { fill: $orange }
.hover-fill-yellow:hover svg { fill: $yellow }
.hover-fill-yellow2:hover svg { fill: $yellow2 }
.hover-fill-orange-dark:hover svg { fill: $orange-dark }
.hover-fill-green:hover svg { fill: $green }
.hover-fill-green2:hover svg { fill: $green2 }
.hover-fill-green-dark:hover svg { fill: $green-dark }
.hover-fill-red:hover svg { fill: $red }
.hover-fill-red2:hover svg { fill: $red2 }
.hover-fill-red-lightest:hover svg { fill: $red-lightest }
.hover-fill-blue:hover svg { fill: $blue }
.hover-fill-blue2:hover svg { fill: $blue2 }
.hover-fill-active-blue:hover svg { fill: $active-blue }
.hover-fill-bg-blue:hover svg { fill: $bg-blue }
.hover-fill-active-blue-border:hover svg { fill: $active-blue-border }
.hover-fill-pink:hover svg { fill: $pink }
.hover-fill-light-blue-bg:hover svg { fill: $light-blue-bg }
.hover-fill-white:hover svg { fill: $white }
.hover-fill-borderColor:hover svg { fill: $borderColor }
.hover-fill-transparent:hover svg { fill: $transparent }
.hover-fill-figmaColors:hover svg { fill: $figmaColors }
/* color */
.color-main { color: $main }

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-plus-lg" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 2a.5.5 0 0 1 .5.5v5h5a.5.5 0 0 1 0 1h-5v5a.5.5 0 0 1-1 0v-5h-5a.5.5 0 0 1 0-1h5v-5A.5.5 0 0 1 8 2Z"/>
</svg>

After

Width:  |  Height:  |  Size: 225 B

View file

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 8 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="M4,0 C4.27614237,0 4.5,0.223857625 4.5,0.5 L4.5,3.5 L7.5,3.5 C7.77614237,3.5 8,3.72385763 8,4 C8,4.27614237 7.77614237,4.5 7.5,4.5 L4.5,4.5 L4.5,7.5 C4.5,7.77614237 4.27614237,8 4,8 C3.72385763,8 3.5,7.77614237 3.5,7.5 L3.5,4.5 L0.5,4.5 C0.223857625,4.5 0,4.27614237 0,4 C0,3.72385763 0.223857625,3.5 0.5,3.5 L3.5,3.5 L3.5,0.5 C3.5,0.223857625 3.72385763,0 4,0 Z" id="Path"></path>
<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-plus" viewBox="0 0 16 16">
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/>
</svg>

Before

Width:  |  Height:  |  Size: 556 B

After

Width:  |  Height:  |  Size: 202 B

View file

@ -8,6 +8,7 @@ fs.writeFileSync('app/styles/colors-autogen.css', `/* Auto-generated, DO NOT EDI
/* fill */
${ colors.map(color => `.fill-${ color } { fill: $${ color } }`).join('\n') }
${ colors.map(color => `.hover-fill-${ color }:hover svg { fill: $${ color } }`).join('\n') }
/* color */
${ colors.map(color => `.color-${ color } { color: $${ color } }`).join('\n') }