Various style improvements in dashboards

This commit is contained in:
Sudheer Salavadi 2024-06-28 18:22:05 +05:30
parent b7b9a897a7
commit 21229f3ba6
23 changed files with 125 additions and 117 deletions

View file

@ -27,7 +27,7 @@ function CustomMetricOverviewChart(props: Props) {
/>
</div>
</div>
<ResponsiveContainer height={100} width="100%">
<ResponsiveContainer height={100} width="100%" className='rounded-lg overflow-hidden'>
<AreaChart
data={data.chart}
margin={{

View file

@ -72,8 +72,8 @@ function SessionsBy(props: Props) {
{total > 3 && (
<div className="flex">
<Button type="link" onClick={showMore}>
<Space>
{total - 3} more
<Space className='flex font-medium gap-1'>
{total - 5} More
<ArrowRight size={16}/>
</Space>
</Button>

View file

@ -1,6 +1,8 @@
import { useObserver } from 'mobx-react-lite';
import React from 'react';
import { Button, Modal, Form, Icon, Checkbox, Input } from 'UI';
import { Modal, Form, Icon, Checkbox, Input } from 'UI';
import {Button} from 'antd';
import { CloseOutlined } from '@ant-design/icons';
import { useStore } from 'App/mstore'
interface Props {
@ -32,14 +34,13 @@ function DashboardEditModal(props: Props) {
<Modal open={ show } onClose={closeHandler}>
<Modal.Header className="flex items-center justify-between">
<div>{ 'Edit Dashboard' }</div>
<Icon
role="button"
tabIndex="-1"
color="gray-dark"
size="14"
<Button
type='text'
name="close"
onClick={ closeHandler }
icon={<CloseOutlined />}
/>
</Modal.Header>
<Modal.Content>
@ -91,13 +92,13 @@ function DashboardEditModal(props: Props) {
<Modal.Footer>
<div className="-mx-2 px-2">
<Button
variant="primary"
type="primary"
onClick={ onSave }
className="float-left mr-2"
>
Save
</Button>
<Button className="mr-2" onClick={ closeHandler }>{ 'Cancel' }</Button>
<Button type='default' onClick={ closeHandler }>{ 'Cancel' }</Button>
</div>
</Modal.Footer>
</Modal>

View file

@ -61,7 +61,7 @@ function DashboardHeader(props: Props) {
closeHandler={() => setShowEditModal(false)}
focusTitle={focusTitle}
/>
<Breadcrumb
{/* <Breadcrumb
items={[
{
label: 'Dashboards',
@ -69,18 +69,18 @@ function DashboardHeader(props: Props) {
},
{label: (dashboard && dashboard.name) || ''},
]}
/>
/> */}
<div className="flex items-center mb-2 justify-between">
<div className="flex items-center" style={{flex: 3}}>
<PageTitle
title={
// @ts-ignore
<Tooltip delay={100} arrow title="Double click to edit">
<Tooltip delay={0} title="Double click to edit" placement='bottom'>
{dashboard?.name}
</Tooltip>
}
onDoubleClick={() => onEdit(true)}
className="mr-3 select-none border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium cursor-pointer"
className="mr-3 select-none border-b border-b-borderColor-transparent hover:border-dashed hover:border-gray-medium cursor-pointer"
/>
</div>
<div className="flex items-center gap-2" style={{flex: 1, justifyContent: 'end'}}>
@ -112,7 +112,7 @@ function DashboardHeader(props: Props) {
</div>
<div className="pb-4">
{/* @ts-ignore */}
<Tooltip delay={100} arrow title="Double click to edit" className="w-fit !block">
<Tooltip arrow title="Double click to edit" placement='top' className="w-fit !block">
<h2
className="my-2 font-normal w-fit text-disabled-text border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium cursor-pointer"
onDoubleClick={() => onEdit(false)}

View file

@ -1,26 +1,25 @@
import {LockOutlined, TeamOutlined} from '@ant-design/icons';
import {Empty, Switch, Table, TableColumnsType, Tag, Tooltip, Typography} from 'antd';
import {observer} from 'mobx-react-lite';
import { LockOutlined, TeamOutlined } from '@ant-design/icons';
import { Empty, Switch, Table, TableColumnsType, Tag, Tooltip, Typography } from 'antd';
import { observer } from 'mobx-react-lite';
import React from 'react';
import {connect} from 'react-redux';
import {withRouter} from 'react-router-dom';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import {checkForRecent} from 'App/date';
import {useStore} from 'App/mstore';
import { checkForRecent } from 'App/date';
import { useStore } from 'App/mstore';
import Dashboard from 'App/mstore/types/dashboard';
import {dashboardSelected, withSiteId} from 'App/routes';
import { dashboardSelected, withSiteId } from 'App/routes';
import AnimatedSVG, {ICONS} from 'Shared/AnimatedSVG/AnimatedSVG';
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
import CreateDashboardButton from "Components/Dashboard/components/CreateDashboardButton";
import {useHistory} from "react-router";
import { useHistory } from "react-router";
function DashboardList({siteId}: { siteId: string }) {
const {dashboardStore} = useStore();
function DashboardList({ siteId }: { siteId: string }) {
const { dashboardStore } = useStore();
const list = dashboardStore.filteredList;
const dashboardsSearch = dashboardStore.filter.query;
const history = useHistory();
const tableConfig: TableColumnsType<Dashboard> = [
{
title: 'Title',
@ -28,14 +27,6 @@ function DashboardList({siteId}: { siteId: string }) {
width: '25%',
render: (t) => <div className="link capitalize-first">{t}</div>,
},
{
title: 'Description',
ellipsis: {
showTitle: false,
},
width: '25%',
dataIndex: 'description',
},
{
title: 'Last Modified',
dataIndex: 'updatedAt',
@ -53,45 +44,62 @@ function DashboardList({siteId}: { siteId: string }) {
},
{
title: (
<div className={'flex items-center justify-between'}>
<div className={'flex items-center justify-start gap-2'}>
<div>Visibility</div>
<Switch checked={!dashboardStore.filter.showMine} onChange={() =>
dashboardStore.updateKey('filter', {
...dashboardStore.filter,
showMine: !dashboardStore.filter.showMine,
})} checkedChildren={'Public'} unCheckedChildren={'Private'}/>
<Tooltip title='Toggle to view your dashboards or all team dashboards.' placement='topRight'>
<Switch checked={!dashboardStore.filter.showMine} onChange={() =>
dashboardStore.updateKey('filter', {
...dashboardStore.filter,
showMine: !dashboardStore.filter.showMine,
})} checkedChildren={'Team'} unCheckedChildren={'Private'} />
</Tooltip>
</div>
),
width: '16.67%',
dataIndex: 'isPublic',
render: (isPublic: boolean) => (
<Tag icon={isPublic ? <TeamOutlined/> : <LockOutlined/>}>
<Tag icon={isPublic ? <TeamOutlined /> : <LockOutlined />}>
{isPublic ? 'Team' : 'Private'}
</Tag>
),
},
];
const emptyDescription = dashboardsSearch !== '' ? (
<div className="text-center">
<div>
<Typography.Text className="my-2 text-xl font-medium">
No search results found.
</Typography.Text>
<div className="mb-2 text-lg text-gray-500 mt-2 leading-normal">
Try adjusting your search criteria or creating a new dashboard.
</div>
</div>
</div>
) : (
<div className="text-center">
<div>
<Typography.Text className="my-2 text-xl font-medium">
Create your first dashboard.
</Typography.Text>
<div className="mb-2 text-lg text-gray-500 mt-2 leading-normal">
Organize your product and technical insights as cards in dashboards to see the bigger picture.
</div>
<div className="my-4">
<CreateDashboardButton />
</div>
</div>
</div>
);
const emptyImage = dashboardsSearch !== '' ? ICONS.NO_RESULTS : ICONS.NO_DASHBOARDS;
return (
list.length === 0 && !dashboardStore.filter.showMine ? (
<Empty
image={<AnimatedSVG name={dashboardsSearch !== '' ? ICONS.NO_RESULTS : ICONS.NO_DASHBOARDS} size={600}/>}
imageStyle={{height: 300}}
description={(
<div className="text-center">
<div>
<Typography.Text className="my-2 text-xl font-medium">
Create your first dashboard.
</Typography.Text>
<div className="mb-2 text-lg text-gray-500 mt-2 leading-normal">
Organize your product and technical insights as cards in dashboards to see the bigger picture, <br/>take action and improve user experience.
</div>
<div className="my-4">
<CreateDashboardButton/>
</div>
</div>
</div>
)}
image={<AnimatedSVG name={emptyImage} size={600} />}
imageStyle={{ height: 300 }}
description={emptyDescription}
/>
) : (
<Table
@ -112,9 +120,10 @@ function DashboardList({siteId}: { siteId: string }) {
history.push(path);
},
})}
/>)
/>
)
);
}
export default connect((state: any) => ({

View file

@ -52,7 +52,7 @@ function CardsLibrary(props: Props) {
<div className={'col-span-' + metric.config.col}
onClick={() => onItemClick(metric.metricId)}>
<LazyLoad>
<Card hoverable
<Card className='border border-transparent hover:border-indigo-50 hover:shadow-sm rounded-lg'
style={{
border: selectedList.includes(metric.metricId) ? '1px solid #1890ff' : '1px solid #f0f0f0',
}}

View file

@ -15,7 +15,7 @@ function ExCard({
}) {
return (
<div
className={'rounded-lg overflow-hidden border border-transparent p-4 bg-white hover:border-blue hover:shadow-sm relative'}
className={'rounded-lg overflow-hidden border border-transparent p-4 bg-white hover:shadow-sm relative'}
style={{width: '100%', height: height || 286}}
>
<div className="absolute inset-0 z-10 cursor-pointer" onClick={() => onCard(type)}></div>

View file

@ -73,7 +73,7 @@ const SelectCard: React.FC<SelectCardProps> = (props: SelectCardProps) => {
<>
<Space className="items-center justify-between">
<div className="text-xl leading-4 font-medium">
{dashboardId ? (isLibrary ? "Add Card" : "Create Card") : "Select a template to create a card"}
{dashboardId ? (isLibrary ? "Your Library" : "Create Card") : "Select a template to create a card"}
</div>
{isLibrary && (
<Space>
@ -84,7 +84,7 @@ const SelectCard: React.FC<SelectCardProps> = (props: SelectCardProps) => {
) : ''}
<Input.Search
placeholder="Search"
placeholder="Find by card title"
onChange={(value) => setLibraryQuery(value.target.value)}
style={{width: 200}}
/>

View file

@ -59,7 +59,7 @@ function DashboardWidgetGrid(props: Props) {
>
<div className="grid gap-4 grid-cols-4 items-start pb-10" id={props.id}>{smallWidgets.length > 0 ? (
<>
<div className="font-semibold text-xl pt-4 flex items-center gap-2 col-span-4">
<div className="font-medium text-xl pt-4 flex items-center gap-2 col-span-4">
<Icon name="grid-horizontal" size={26}/>
Web Vitals
</div>
@ -85,7 +85,7 @@ function DashboardWidgetGrid(props: Props) {
) : null}
{smallWidgets.length > 0 && regularWidgets.length > 0 ? (
<div className="font-semibold text-xl pt-4 flex items-center gap-2 col-span-4">
<div className="font-medium text-xl pt-4 flex items-center gap-2 col-span-4">
<Icon name="grid-horizontal" size={26}/>
All Cards
</div>

View file

@ -23,8 +23,8 @@ function AddStepButton({series, excludeFilterKeys}: Props) {
onFilterClick={onAddFilter}
excludeFilterKeys={excludeFilterKeys}
>
<Button type="link" className='border-none hover:bg-blue-50' icon={<PlusIcon size={16}/>} size="small">
ADD STEP
<Button type="text" className='border-none text-indigo-600 hover:text-indigo-600 align-bottom' icon={<PlusIcon size={16}/>} size="small">
<span className='font-medium'>Add Step</span>
</Button>
</FilterSelection>
);

View file

@ -1,5 +1,6 @@
import React, { useState, useRef, useEffect } from 'react';
import { Icon } from 'UI';
import {Input} from 'antd';
interface Props {
name: string;
@ -35,15 +36,15 @@ function SeriesName(props: Props) {
return (
<div className="flex items-center">
{ editing ? (
<input
<Input
ref={ ref }
name="name"
className="fluid border-0 -mx-2 px-2 h-8"
value={name}
// readOnly={!editing}
onChange={write}
onBlur={onBlur}
onFocus={() => setEditing(true)}
className='bg-white'
/>
) : (
<div className="text-base h-8 flex items-center border-transparent">{name && name.trim() === '' ? 'Series ' + (seriesIndex + 1) : name }</div>

View file

@ -57,7 +57,7 @@ function FunnelIssues() {
}, [stages.length, drillDownPeriod, filter.filters, depsString, metricStore.sessionsPage]);
return useObserver(() => (
<div className="bg-white rounded p-4 border">
<div className="bg-white rounded-lg mt-4 p-4 border">
<div className="flex">
<h2 className="font-medium text-xl">Most significant issues <span className="font-normal">identified in this funnel</span></h2>
</div>

View file

@ -5,7 +5,7 @@ import {metricOf, issueOptions, issueCategories} from 'App/constants/filterOptio
import {FilterKey} from 'Types/filter/filterType';
import {withSiteId, dashboardMetricDetails, metricDetails} from 'App/routes';
import {Icon, confirm} from 'UI';
import {Card, Input, Space, Button, Segmented} from 'antd';
import {Card, Input, Space, Button, Segmented, Alert} from 'antd';
import {AudioWaveform} from "lucide-react";
import FilterSeries from '../FilterSeries';
import Select from 'Shared/Select';
@ -28,16 +28,13 @@ const AIInput = ({value, setValue, placeholder, onEnter}) => (
placeholder={placeholder}
value={value}
onChange={(e) => setValue(e.target.value)}
className='w-full mb-2'
className='w-full mb-2 bg-white'
onKeyDown={(e) => e.key === 'Enter' && onEnter()}
/>
);
const PredefinedMessage = () => (
<div className='flex items-center my-6 justify-center'>
<Icon name='info-circle' size='18' color='gray-medium'/>
<div className='ml-2'>Filtering and drill-downs will be supported soon for this card type.</div>
</div>
<Alert message="Drilldown or filtering isn't supported on this legacy card." type='warning' showIcon closable className='border-transparent rounded-lg' />
);
const MetricTabs = ({metric, writeOption}: any) => {

View file

@ -4,6 +4,7 @@ import {FilterKey} from 'Types/filter/filterType';
import {useStore} from 'App/mstore';
import {observer} from 'mobx-react-lite';
import {Button, Icon, confirm, Tooltip} from 'UI';
import {Input, Alert} from 'antd'
import FilterSeries from '../FilterSeries';
import Select from 'Shared/Select';
import {withSiteId, dashboardMetricDetails, metricDetails} from 'App/routes';
@ -26,7 +27,6 @@ import {eventKeys} from 'App/types/filter/newFilter';
import {renderClickmapThumbnail} from './renderMap';
import Widget from 'App/mstore/types/widget';
import FilterItem from 'Shared/Filters/FilterItem';
import {Input} from 'antd'
interface Props {
history: any;
@ -261,12 +261,7 @@ function WidgetForm(props: Props) {
)}
{isPredefined && (
<div className='flex items-center my-6 justify-center'>
<Icon name='info-circle' size='18' color='gray-medium'/>
<div className='ml-2'>
Filtering and drill-downs will be supported soon for this card type.
</div>
</div>
<Alert message="Drilldown or filtering isn't supported on this legacy card." type='warning' showIcon closable className='border-transparent rounded-lg' />
)}
{testingKey ? <Input
placeholder="AI Query"

View file

@ -1,5 +1,5 @@
import React from 'react';
import {Card, Space, Typography, Button} from "antd";
import {Card, Space, Typography, Button, Alert} from "antd";
import {useStore} from "App/mstore";
import {eventKeys} from "Types/filter/newFilter";
import {
@ -60,10 +60,10 @@ export default observer(WidgetFormNew);
function DefineSteps({metric, excludeFilterKeys}: any) {
return (
<Space className="px-4 py-2 rounded-lg shadow-sm">
<Typography.Text strong>Define Steps</Typography.Text>
<div className="px-4 py-2 rounded-lg shadow-sm flex items-center ">
<Typography.Text strong>Filter</Typography.Text>
<AddStepButton excludeFilterKeys={excludeFilterKeys} series={metric.series[0]}/>
</Space>
</div>
);
}
@ -172,8 +172,5 @@ const AdditionalFilters = observer(() => {
const PredefinedMessage = () => (
<div className='flex items-center my-6 justify-center'>
<Icon name='info-circle' size='18' color='gray-medium'/>
<div className='ml-2'>Filtering and drill-downs will be supported soon for this card type.</div>
</div>
<Alert message="Drilldown or filtering isn't supported on this legacy card." type='warning' showIcon closable className='border-transparent rounded-lg' />
);

View file

@ -1,5 +1,6 @@
import React, { useState, useRef, useEffect } from 'react';
import { Icon, Tooltip } from 'UI';
import { Input } from 'antd';
import cn from 'classnames';
interface Props {
@ -53,10 +54,9 @@ function WidgetName(props: Props) {
return (
<div className="flex items-center">
{ editing ? (
<input
<Input
ref={ ref }
name="name"
className="rounded fluid border-0 -mx-2 px-2 h-8"
value={name}
onChange={write}
onBlur={() => onBlur()}

View file

@ -115,7 +115,7 @@ function WidgetSessions(props: Props) {
};
return (
<div className={cn(className, 'bg-white p-3 pb-0 rounded border')}>
<div className={cn(className, 'bg-white p-3 pb-0 rounded-lg shadow-sm border mt-3')}>
<div className='flex items-center justify-between'>
<div className='flex items-baseline'>
<h2 className='text-xl'>{metricStore.clickMapSearch ? 'Clicks' : 'Sessions'}</h2>

View file

@ -137,28 +137,34 @@ function WidgetView(props: Props) {
</div>
}
>
<Space direction="vertical" size={20} className="w-full">
<WidgetViewHeader onSave={onSave} undoChanges={undoChanges}/>
<div className="w-full">
<div className='my-3'>
<WidgetViewHeader onSave={onSave} undoChanges={undoChanges}/>
</div>
<WidgetFormNew/>
<div className='my-3'>
<WidgetFormNew/>
</div>
{/*<div className="bg-white rounded border mt-3">*/}
{/* <WidgetForm expanded={expanded} onDelete={onBackHandler} {...props} />*/}
{/*</div>*/}
<WidgetPreview name={widget.name} isEditing={expanded}/>
<div className='my-3'>
<WidgetPreview name={widget.name} isEditing={expanded}/>
{widget.metricOf !== FilterKey.SESSIONS && widget.metricOf !== FilterKey.ERRORS && (
<>
{(widget.metricType === TABLE || widget.metricType === TIMESERIES || widget.metricType === CLICKMAP || widget.metricType === INSIGHTS) &&
<WidgetSessions/>}
{widget.metricType === FUNNEL && <FunnelIssues/>}
</>
)}
{widget.metricOf !== FilterKey.SESSIONS && widget.metricOf !== FilterKey.ERRORS && (
<>
{(widget.metricType === TABLE || widget.metricType === TIMESERIES || widget.metricType === CLICKMAP || widget.metricType === INSIGHTS) &&
<WidgetSessions/>}
{widget.metricType === FUNNEL && <FunnelIssues/>}
</>
)}
{widget.metricType === USER_PATH && <CardIssues/>}
{widget.metricType === RETENTION && <CardUserList/>}
</Space>
{widget.metricType === USER_PATH && <CardIssues/>}
{widget.metricType === RETENTION && <CardUserList/>}
</div>
</div>
</NoContent>
</div>
</Loader>

View file

@ -26,7 +26,8 @@ function WidgetViewHeader({onClick, onSave, undoChanges}: Props) {
<h1 className="mb-0 text-2xl mr-4 min-w-fit">
<WidgetName name={widget.name}
onUpdate={(name) => metricStore.merge({name})}
canEdit={true}/>
canEdit={true}
/>
</h1>
<Space>
<WidgetDateRange label=""/>

View file

@ -21,7 +21,7 @@ function CardMenu({card}: any) {
},
{
key: 'hide',
label: 'Hide',
label: 'Remove',
icon: <EyeOffIcon size={16}/>,
},
];

View file

@ -110,6 +110,7 @@ const Login: React.FC<LoginProps> = ({errors, loading, authDetails, login, setJw
onChange={(e) => setEmail(e.target.value)}
required
icon="envelope"
/>
</Form.Field>
<Form.Field>

View file

@ -47,7 +47,7 @@ function FilterSelection(props: Props) {
})
) : (
<div
className={cn('rounded py-1 px-3 flex items-center cursor-pointer bg-gray-lightest text-ellipsis hover:bg-gray-light-shade', { 'opacity-50 pointer-events-none': disabled })}
className={cn('rounded-lg py-1 px-3 flex items-center cursor-pointer bg-gray-lightest text-ellipsis hover:bg-gray-light-shade', { 'opacity-50 pointer-events-none': disabled })}
style={{ width: '150px', height: '26px', border: 'solid thin #e9e9e9' }}
onClick={() => setShowModal(true)}
>

View file

@ -50,7 +50,7 @@ function Modal(props: Props) {
style={{ zIndex: '9999', backgroundColor: 'rgba(0, 0, 0, 0.2)'}}
onClick={handleClose}
>
<div className="absolute z-10 bg-white rounded border" style={style}>
<div className="absolute z-10 bg-white rounded-lg shadow-sm" style={style}>
{children}
</div>
</div>