Various improvements Cards, OmniSearch and Cards Listing (#2881)
This commit is contained in:
parent
c851bf0ad1
commit
592923b6d1
23 changed files with 247 additions and 233 deletions
|
|
@ -1,64 +1,67 @@
|
|||
// Components/Dashboard/components/AddToDashboardButton.tsx
|
||||
|
||||
import React from 'react';
|
||||
import {Grid2x2Check} from "lucide-react"
|
||||
import {Button, Modal} from "antd";
|
||||
import Select from "Shared/Select/Select";
|
||||
import {Form} from "UI";
|
||||
import {useStore} from "App/mstore";
|
||||
import { Grid2x2Check } from 'lucide-react';
|
||||
import { Button, Modal } from 'antd';
|
||||
import Select from 'Shared/Select/Select';
|
||||
import { Form } from 'UI';
|
||||
import { useStore } from 'App/mstore';
|
||||
|
||||
interface Props {
|
||||
metricId: string;
|
||||
metricId: string;
|
||||
}
|
||||
|
||||
function AddToDashboardButton({metricId}: Props) {
|
||||
const {dashboardStore} = useStore();
|
||||
const dashboardOptions = dashboardStore.dashboards.map((i: any) => ({
|
||||
key: i.id,
|
||||
label: i.name,
|
||||
value: i.dashboardId,
|
||||
}));
|
||||
const [selectedId, setSelectedId] = React.useState(dashboardOptions[0]?.value);
|
||||
export const showAddToDashboardModal = (metricId: string, dashboardStore: any) => {
|
||||
const dashboardOptions = dashboardStore.dashboards.map((i: any) => ({
|
||||
key: i.id,
|
||||
label: i.name,
|
||||
value: i.dashboardId,
|
||||
}));
|
||||
let selectedId = dashboardOptions[0]?.value;
|
||||
|
||||
const onSave = (close: any) => {
|
||||
const dashboard = dashboardStore.getDashboard(selectedId)
|
||||
if (dashboard) {
|
||||
dashboardStore.addWidgetToDashboard(dashboard, [metricId]).then(close)
|
||||
}
|
||||
const onSave = (close: any) => {
|
||||
const dashboard = dashboardStore.getDashboard(selectedId);
|
||||
if (dashboard) {
|
||||
dashboardStore.addWidgetToDashboard(dashboard, [metricId]).then(close);
|
||||
}
|
||||
};
|
||||
|
||||
const onClick = () => {
|
||||
Modal.confirm({
|
||||
title: 'Add to selected dashboard',
|
||||
icon: null,
|
||||
content: (
|
||||
<Form.Field>
|
||||
<Select
|
||||
options={dashboardOptions}
|
||||
defaultValue={dashboardOptions[0].value}
|
||||
onChange={({value}: any) => setSelectedId(value.value)}
|
||||
/>
|
||||
</Form.Field>
|
||||
),
|
||||
cancelText: 'Cancel',
|
||||
onOk: onSave,
|
||||
okText: 'Add',
|
||||
footer: (_, {OkBtn, CancelBtn}) => (
|
||||
<>
|
||||
<CancelBtn/>
|
||||
<OkBtn/>
|
||||
</>
|
||||
),
|
||||
})
|
||||
}
|
||||
Modal.confirm({
|
||||
title: 'Add to selected dashboard',
|
||||
icon: null,
|
||||
content: (
|
||||
<Form.Field>
|
||||
<Select
|
||||
options={dashboardOptions}
|
||||
defaultValue={selectedId}
|
||||
onChange={({ value }: any) => (selectedId = value.value)}
|
||||
/>
|
||||
</Form.Field>
|
||||
),
|
||||
cancelText: 'Cancel',
|
||||
onOk: onSave,
|
||||
okText: 'Add',
|
||||
footer: (_, { OkBtn, CancelBtn }) => (
|
||||
<>
|
||||
<CancelBtn />
|
||||
<OkBtn />
|
||||
</>
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
type="default"
|
||||
onClick={onClick}
|
||||
icon={<Grid2x2Check size={18}/>}
|
||||
>
|
||||
Add to Dashboard
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
const AddToDashboardButton = ({ metricId }: Props) => {
|
||||
const { dashboardStore } = useStore();
|
||||
|
||||
export default AddToDashboardButton;
|
||||
return (
|
||||
<Button
|
||||
type="default"
|
||||
onClick={() => showAddToDashboardModal(metricId, dashboardStore)}
|
||||
icon={<Grid2x2Check size={18} />}
|
||||
>
|
||||
Add to Dashboard
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddToDashboardButton;
|
||||
|
|
@ -56,7 +56,7 @@ const FilterSeriesHeader = observer(
|
|||
};
|
||||
return (
|
||||
<div
|
||||
className={cn('px-4 h-12 flex items-center relative bg-white border-gray-lighter border-t border-l border-r rounded-t-xl', {
|
||||
className={cn('px-4 ps-2 h-12 flex items-center relative bg-white border-gray-lighter border-t border-l border-r rounded-t-xl', {
|
||||
hidden: props.hidden,
|
||||
'rounded-b-xl': !props.expanded,
|
||||
})}
|
||||
|
|
@ -81,6 +81,8 @@ const FilterSeriesHeader = observer(
|
|||
size="small"
|
||||
disabled={!props.canDelete}
|
||||
icon={<Trash size={14} />}
|
||||
type='text'
|
||||
className='btn-delete-series'
|
||||
/>
|
||||
<Button
|
||||
onClick={props.toggleExpand}
|
||||
|
|
@ -92,6 +94,8 @@ const FilterSeriesHeader = observer(
|
|||
<ChevronDown size={16} />
|
||||
)
|
||||
}
|
||||
type='text'
|
||||
className='btn-toggle-series'
|
||||
/>
|
||||
</Space>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,61 +1,68 @@
|
|||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { Icon } from 'UI';
|
||||
import {Input, Tooltip} from 'antd';
|
||||
import { Input, Tooltip } from 'antd';
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
onUpdate: (name) => void;
|
||||
onUpdate: (name: string) => void;
|
||||
seriesIndex?: number;
|
||||
}
|
||||
|
||||
function SeriesName(props: Props) {
|
||||
const { seriesIndex = 1 } = props;
|
||||
const [editing, setEditing] = useState(false)
|
||||
const [name, setName] = useState(props.name)
|
||||
const ref = useRef<any>(null)
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [name, setName] = useState(props.name);
|
||||
const ref = useRef<any>(null);
|
||||
|
||||
const write = ({ target: { value, name } }) => {
|
||||
setName(value)
|
||||
}
|
||||
const write = ({ target: { value } }) => {
|
||||
setName(value);
|
||||
};
|
||||
|
||||
const onBlur = () => {
|
||||
setEditing(false)
|
||||
props.onUpdate(name)
|
||||
}
|
||||
setEditing(false);
|
||||
props.onUpdate(name);
|
||||
};
|
||||
|
||||
const onKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
setEditing(false);
|
||||
props.onUpdate(name);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (editing) {
|
||||
ref.current.focus()
|
||||
ref.current.focus();
|
||||
}
|
||||
}, [editing])
|
||||
}, [editing]);
|
||||
|
||||
useEffect(() => {
|
||||
setName(props.name)
|
||||
}, [props.name])
|
||||
|
||||
// const { name } = props;
|
||||
setName(props.name);
|
||||
}, [props.name]);
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
{ editing ? (
|
||||
{editing ? (
|
||||
<Input
|
||||
ref={ ref }
|
||||
ref={ref}
|
||||
name="name"
|
||||
value={name}
|
||||
// readOnly={!editing}
|
||||
onChange={write}
|
||||
onBlur={onBlur}
|
||||
onFocus={() => setEditing(true)}
|
||||
className='bg-white'
|
||||
onKeyDown={onKeyDown}
|
||||
className="bg-white text-lg border-transparent rounded-lg font-medium ps-2"
|
||||
/>
|
||||
) : (
|
||||
<div className="text-base h-8 flex items-center border-transparent">{name && name.trim() === '' ? 'Series ' + (seriesIndex + 1) : name }</div>
|
||||
)}
|
||||
|
||||
|
||||
<div className="ml-3 cursor-pointer " onClick={() => setEditing(true)}>
|
||||
<Tooltip title='Rename' placement='bottom'>
|
||||
<Icon name="pencil" size="14" />
|
||||
<Tooltip title="Double click to rename.">
|
||||
<div
|
||||
className="text-lg font-medium h-8 flex items-center border-transparent p-2 hover:bg-teal/10 cursor-pointer rounded-lg input-rename-series"
|
||||
onClick={() => setEditing(true)}
|
||||
data-event='input-rename-series'
|
||||
>
|
||||
{name && name.trim() === '' ? 'Series ' + (seriesIndex + 1) : name}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { PlusOutlined } from '@ant-design/icons';
|
|||
import AddCardSection from '../AddCardSection/AddCardSection';
|
||||
import MetricsSearch from '../MetricsSearch';
|
||||
import Select from 'Shared/Select';
|
||||
import {Select as AntSelect} from 'antd';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { observer, useObserver } from 'mobx-react-lite';
|
||||
import { DROPDOWN_OPTIONS } from 'App/constants/card';
|
||||
|
|
@ -44,9 +45,9 @@ function MetricViewHeader() {
|
|||
|
||||
<div className="border-y px-6 py-1 mt-2 flex items-center w-full justify-between">
|
||||
<div className="items-center flex gap-4">
|
||||
<Select
|
||||
<AntSelect
|
||||
options={[
|
||||
{ label: 'All Types', value: 'all' },
|
||||
{ label: 'All Card Types', value: 'all' },
|
||||
...DROPDOWN_OPTIONS,
|
||||
]}
|
||||
name="type"
|
||||
|
|
@ -54,8 +55,7 @@ function MetricViewHeader() {
|
|||
onChange={({ value }) =>
|
||||
metricStore.updateKey('filter', { ...filter, type: value.value })
|
||||
}
|
||||
plain={true}
|
||||
isSearchable={true}
|
||||
|
||||
/>
|
||||
|
||||
<DashboardDropdown
|
||||
|
|
|
|||
|
|
@ -517,7 +517,8 @@ function WidgetChart(props: Props) {
|
|||
{renderChart()}
|
||||
{props.isPreview && _metric.metricType === TIMESERIES ? (
|
||||
<WidgetDatatable
|
||||
defaultOpen={_metric.viewType === 'table'}
|
||||
//defaultOpen={_metric.viewType === 'table'}
|
||||
defaultOpen={true}
|
||||
data={data}
|
||||
enabledRows={enabledRows}
|
||||
setEnabledRows={setEnabledRows}
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ function WidgetDatatable(props: Props) {
|
|||
size={'small'}
|
||||
type={'default'}
|
||||
onClick={() => setShowTable(!showTable)}
|
||||
className='btn-show-hide-table'
|
||||
>
|
||||
{showTable ? 'Hide Table' : 'Show Table'}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ function RangeGranularity({
|
|||
|
||||
return (
|
||||
<Dropdown menu={menuProps} trigger={['click']}>
|
||||
<Button type='text' variant='text' size='small'>
|
||||
<Button type='text' variant='text' size='small' className='btn-granularity'>
|
||||
<span>{selected}</span>
|
||||
<DownOutlined />
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ const FilterSection = observer(({ metric, excludeFilterKeys }: any) => {
|
|||
if (!canAddSeries) return;
|
||||
metric.addSeries();
|
||||
}}
|
||||
className="w-full cursor-pointer flex items-center py-2 justify-center gap-2"
|
||||
className="w-full cursor-pointer flex items-center py-2 justify-center gap-2 font-medium hover:text-teal"
|
||||
>
|
||||
<PlusIcon size={16} />
|
||||
Add Series
|
||||
|
|
|
|||
|
|
@ -1,92 +1,80 @@
|
|||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { Icon, Tooltip } from 'UI';
|
||||
import { Input } from 'antd';
|
||||
import { Icon } from 'UI';
|
||||
import { Input, Tooltip } from 'antd';
|
||||
import cn from 'classnames';
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
onUpdate: (name: any) => void;
|
||||
onUpdate: (name: string) => void;
|
||||
seriesIndex?: number;
|
||||
canEdit?: boolean
|
||||
canEdit?: boolean;
|
||||
}
|
||||
|
||||
function WidgetName(props: Props) {
|
||||
const { canEdit = true } = props;
|
||||
const [editing, setEditing] = useState(false)
|
||||
const [name, setName] = useState(props.name)
|
||||
const ref = useRef<any>(null)
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [name, setName] = useState(props.name);
|
||||
const ref = useRef<any>(null);
|
||||
|
||||
const write = ({ target: { value } }) => {
|
||||
setName(value)
|
||||
}
|
||||
setName(value);
|
||||
};
|
||||
|
||||
const onBlur = (nameInput?: string) => {
|
||||
setEditing(false)
|
||||
const toUpdate = nameInput || name
|
||||
props.onUpdate(toUpdate && toUpdate.trim() === '' ? 'New Widget' : toUpdate)
|
||||
}
|
||||
setEditing(false);
|
||||
const toUpdate = nameInput || name;
|
||||
props.onUpdate(toUpdate && toUpdate.trim() === '' ? 'New Widget' : toUpdate);
|
||||
};
|
||||
|
||||
const onKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
onBlur(name);
|
||||
}
|
||||
if (e.key === 'Escape' || e.key === 'Esc') {
|
||||
setEditing(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (editing) {
|
||||
ref.current.focus()
|
||||
ref.current.focus();
|
||||
}
|
||||
}, [editing])
|
||||
}, [editing]);
|
||||
|
||||
useEffect(() => {
|
||||
setName(props.name)
|
||||
}, [props.name])
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
onBlur(name)
|
||||
}
|
||||
if (e.key === 'Escape' || e.key === 'Esc') {
|
||||
setEditing(false)
|
||||
}
|
||||
}
|
||||
document.addEventListener('keydown', handler, false)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handler, false)
|
||||
}
|
||||
}, [name])
|
||||
setName(props.name);
|
||||
}, [props.name]);
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
{ editing ? (
|
||||
{editing ? (
|
||||
<Input
|
||||
ref={ ref }
|
||||
ref={ref}
|
||||
name="name"
|
||||
value={name}
|
||||
onChange={write}
|
||||
onBlur={() => onBlur()}
|
||||
onKeyDown={onKeyDown}
|
||||
onFocus={() => setEditing(true)}
|
||||
maxLength={80}
|
||||
className="bg-white text-2xl ps-2 rounded-lg h-8"
|
||||
/>
|
||||
) : (
|
||||
// @ts-ignore
|
||||
<Tooltip delay={200} title="Double click to edit" disabled={!canEdit}>
|
||||
<div
|
||||
onDoubleClick={() => setEditing(true)}
|
||||
className={
|
||||
cn(
|
||||
"text-2xl h-8 flex items-center border-transparent",
|
||||
canEdit && 'cursor-pointer select-none border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium'
|
||||
)
|
||||
}
|
||||
className={cn(
|
||||
"text-2xl h-8 flex items-center p-2 rounded-lg",
|
||||
canEdit && 'cursor-pointer select-none ps-2 hover:bg-teal/10'
|
||||
)}
|
||||
>
|
||||
{ name }
|
||||
{name}
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
)}
|
||||
{ canEdit && <div className="ml-3 cursor-pointer" onClick={() => setEditing(true)}>
|
||||
<Tooltip title='Rename' placement='bottom'>
|
||||
<Icon name="pencil" size="16" />
|
||||
</Tooltip>
|
||||
</div> }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default WidgetName;
|
||||
export default WidgetName;
|
||||
|
|
@ -112,7 +112,7 @@ const SeriesTypeOptions = observer(({ metric }: { metric: any }) => {
|
|||
},
|
||||
}}
|
||||
>
|
||||
<Button type='text' variant='text' size='small'>
|
||||
<Button type='text' variant='text' size='small' className='btn-aggregator'>
|
||||
<Space>
|
||||
{chartIcons[metric.metricOf]}
|
||||
<div>{items[metric.metricOf] || 'Total Sessions'}</div>
|
||||
|
|
@ -179,7 +179,7 @@ const WidgetViewTypeOptions = observer(({ metric }: { metric: any }) => {
|
|||
}}
|
||||
|
||||
>
|
||||
<Button type='text' variant='text' size='small'>
|
||||
<Button type='text' variant='text' size='small' className='btn-visualization-type'>
|
||||
<Space>
|
||||
{chartIcons[metric.viewType]}
|
||||
<div>{chartTypes[metric.viewType]}</div>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
// Components/CardViewMenu.tsx
|
||||
import { useHistory } from 'react-router';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { Button, Dropdown, MenuProps, message, Modal } from 'antd';
|
||||
import { BellIcon, EllipsisVertical, TrashIcon } from 'lucide-react';
|
||||
import { Button, Dropdown, MenuProps, Modal } from 'antd';
|
||||
import { BellIcon, EllipsisVertical, Grid2x2Plus, TrashIcon } from 'lucide-react';
|
||||
import { toast } from 'react-toastify';
|
||||
import React from 'react';
|
||||
import { useModal } from 'Components/ModalContext';
|
||||
import AlertFormModal from 'Components/Alerts/AlertFormModal/AlertFormModal';
|
||||
import { showAddToDashboardModal } from 'Components/Dashboard/components/AddToDashboardButton';
|
||||
|
||||
const CardViewMenu = () => {
|
||||
const history = useHistory();
|
||||
const { alertsStore, metricStore } = useStore();
|
||||
const { alertsStore, metricStore, dashboardStore } = useStore();
|
||||
const widget = metricStore.instance;
|
||||
const { openModal, closeModal } = useModal();
|
||||
|
||||
|
|
@ -18,13 +20,19 @@ const CardViewMenu = () => {
|
|||
const seriesId = (widget.series[0] && widget.series[0].seriesId) || '';
|
||||
alertsStore.init({ query: { left: seriesId } });
|
||||
openModal(<AlertFormModal onClose={closeModal} />, {
|
||||
// title: 'Set Alerts',
|
||||
placement: 'right',
|
||||
width: 620,
|
||||
});
|
||||
};
|
||||
|
||||
const items: MenuProps['items'] = [
|
||||
{
|
||||
key: 'add-to-dashboard',
|
||||
label: 'Add to Dashboard',
|
||||
icon: <Grid2x2Plus size={16} />,
|
||||
disabled: !widget.exists(),
|
||||
onClick: () => showAddToDashboardModal(widget.metricId, dashboardStore),
|
||||
},
|
||||
{
|
||||
key: 'alert',
|
||||
label: 'Set Alerts',
|
||||
|
|
@ -35,7 +43,7 @@ const CardViewMenu = () => {
|
|||
{
|
||||
key: 'remove',
|
||||
label: 'Delete',
|
||||
icon: <TrashIcon size={16} />,
|
||||
icon: <TrashIcon size={15} />,
|
||||
disabled: !widget.exists(),
|
||||
onClick: () => {
|
||||
Modal.confirm({
|
||||
|
|
@ -52,7 +60,7 @@ const CardViewMenu = () => {
|
|||
onOk: () => {
|
||||
metricStore
|
||||
.delete(widget)
|
||||
.then((r) => {
|
||||
.then(() => {
|
||||
history.goBack();
|
||||
})
|
||||
.catch(() => {
|
||||
|
|
@ -64,41 +72,13 @@ const CardViewMenu = () => {
|
|||
},
|
||||
];
|
||||
|
||||
const onClick: MenuProps['onClick'] = ({ key }) => {
|
||||
if (key === 'alert') {
|
||||
message.info('Set Alerts');
|
||||
} else if (key === 'remove') {
|
||||
Modal.confirm({
|
||||
title: 'Are you sure you want to remove this card?',
|
||||
icon: null,
|
||||
// content: 'Bla bla ...',
|
||||
footer: (_, { OkBtn, CancelBtn }) => (
|
||||
<>
|
||||
<CancelBtn />
|
||||
<OkBtn />
|
||||
</>
|
||||
),
|
||||
onOk: () => {
|
||||
metricStore
|
||||
.delete(widget)
|
||||
.then((r) => {
|
||||
history.goBack();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error('Failed to remove card');
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<Dropdown menu={{ items }}>
|
||||
<Button icon={<EllipsisVertical size={16} />} />
|
||||
<Button type='text' icon={<EllipsisVertical size={16} />} className='btn-card-options' />
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default observer(CardViewMenu);
|
||||
export default observer(CardViewMenu);
|
||||
|
|
@ -3,7 +3,7 @@ import cn from 'classnames';
|
|||
import WidgetName from 'Components/Dashboard/components/WidgetName';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import AddToDashboardButton from 'Components/Dashboard/components/AddToDashboardButton';
|
||||
|
||||
import { Button, Space, Tooltip } from 'antd';
|
||||
import CardViewMenu from 'Components/Dashboard/components/WidgetView/CardViewMenu';
|
||||
import { Link2 } from 'lucide-react'
|
||||
|
|
@ -31,11 +31,11 @@ function WidgetViewHeader({ onClick, onSave }: Props) {
|
|||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex justify-between items-center bg-white rounded px-4 py-2 border border-gray-lighter'
|
||||
'flex justify-between items-center bg-white rounded-lg shadow-sm px-4 ps-2 py-2 border border-gray-lighter input-card-title'
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<h1 className="mb-0 text-2xl mr-4 min-w-fit">
|
||||
<h1 className="mb-0 text-2xl mr-4 min-w-fit ">
|
||||
<WidgetName
|
||||
name={widget.name}
|
||||
onUpdate={(name) => metricStore.merge({ name })}
|
||||
|
|
@ -43,20 +43,28 @@ function WidgetViewHeader({ onClick, onSave }: Props) {
|
|||
/>
|
||||
</h1>
|
||||
<Space>
|
||||
<AddToDashboardButton metricId={widget.metricId} />
|
||||
<MetricTypeSelector />
|
||||
<Tooltip title={tooltipText}>
|
||||
<Button disabled={!widget.exists()} onClick={copyUrl} icon={<Link2 size={16} strokeWidth={1} />}></Button>
|
||||
</Tooltip>
|
||||
<Button
|
||||
type="primary"
|
||||
|
||||
<Button
|
||||
type={
|
||||
metricStore.isSaving || (widget.exists() && !widget.hasChanged) ? 'text' : 'primary'
|
||||
}
|
||||
onClick={onSave}
|
||||
loading={metricStore.isSaving}
|
||||
disabled={metricStore.isSaving || (widget.exists() && !widget.hasChanged)}
|
||||
className='font-medium btn-update-card'
|
||||
>
|
||||
{widget.exists() ? 'Update' : 'Create'}
|
||||
</Button>
|
||||
|
||||
<MetricTypeSelector />
|
||||
|
||||
<Tooltip title={tooltipText}>
|
||||
<Button type='text' className='btn-copy-card-url' disabled={!widget.exists()} onClick={copyUrl} icon={<Link2 size={16} strokeWidth={1}/> }></Button>
|
||||
</Tooltip>
|
||||
|
||||
<CardViewMenu />
|
||||
|
||||
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ function FunnelBarData({
|
|||
bottom: 0,
|
||||
background: isFocused
|
||||
? 'rgba(204, 0, 0, 0.3)'
|
||||
: 'repeating-linear-gradient(325deg, lightgray, lightgray 2px, #FFF1F0 2px, #FFF1F0 6px)',
|
||||
: 'repeating-linear-gradient(325deg, lightgray, lightgray 1px, #FFF1F0 1px, #FFF1F0 6px)',
|
||||
cursor: 'pointer',
|
||||
}
|
||||
const horizontalEmptyBarStyle = {
|
||||
|
|
@ -102,7 +102,7 @@ function FunnelBarData({
|
|||
left: 0,
|
||||
background: isFocused
|
||||
? 'rgba(204, 0, 0, 0.3)'
|
||||
: 'repeating-linear-gradient(325deg, lightgray, lightgray 2px, #FFF1F0 2px, #FFF1F0 6px)',
|
||||
: 'repeating-linear-gradient(325deg, lightgray, lightgray 1px, #FFF1F0 1px, #FFF1F0 6px)',
|
||||
cursor: 'pointer',
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import React from 'react';
|
||||
import { Table } from 'antd';
|
||||
import { Table, Tooltip } from 'antd';
|
||||
import type { TableProps } from 'antd';
|
||||
import Widget from 'App/mstore/types/widget';
|
||||
import Funnel from 'App/mstore/types/funnel';
|
||||
import { ItemMenu } from 'UI';
|
||||
import { EllipsisVertical } from 'lucide-react';
|
||||
import { EllipsisVertical, FileDown } from 'lucide-react';
|
||||
import { exportAntCsv } from '../../../utils';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -101,30 +101,27 @@ export function TableExporter({
|
|||
}) {
|
||||
const onClick = () => exportAntCsv(tableColumns, tableData, filename);
|
||||
return (
|
||||
<Tooltip title='Export Data to CSV'>
|
||||
<div
|
||||
className={`absolute ${top ? top : 'top-0'} ${
|
||||
right ? right : '-right-1'
|
||||
}`}
|
||||
>
|
||||
<ItemMenu
|
||||
items={[{ icon: 'pencil', text: 'Export', onClick }]}
|
||||
items={[{ icon: 'download', text: 'Export to CSV', onClick }]}
|
||||
bold
|
||||
customTrigger={
|
||||
<div
|
||||
className={
|
||||
'flex items-center justify-center bg-gray-lighter cursor-pointer hover:bg-gray-light'
|
||||
}
|
||||
style={{
|
||||
height: 38,
|
||||
width: 38,
|
||||
boxShadow: '-2px 0px 3px 0px rgba(0, 0, 0, 0.05)',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
'flex items-center justify-center bg-gradient-to-r from-[#fafafa] to-neutral-200 cursor-pointer rounded-lg h-[38px] w-[38px] btn-export-table-data'
|
||||
}
|
||||
>
|
||||
<EllipsisVertical size={16} />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -134,10 +134,10 @@ export function AutocompleteModal({
|
|||
</>
|
||||
</Loader>
|
||||
<div className={'flex gap-2 items-center pt-2'}>
|
||||
<Button type={'primary'} onClick={applyValues}>
|
||||
<Button type={'primary'} onClick={applyValues} className='btn-apply-event-value'>
|
||||
Apply
|
||||
</Button>
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
<Button onClick={onClose} className='btn-cancel-event-value'>Cancel</Button>
|
||||
</div>
|
||||
</OutsideClickDetectingDiv>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ function FilterItem(props: Props) {
|
|||
<FilterOperator
|
||||
options={filter.sourceOperatorOptions}
|
||||
onChange={onSourceOperatorChange}
|
||||
className="mx-2 flex-shrink-0"
|
||||
className="mx-2 flex-shrink-0 btn-event-operator"
|
||||
value={filter.sourceOperator}
|
||||
isDisabled={filter.operatorDisabled || props.readonly}
|
||||
/>
|
||||
|
|
@ -105,7 +105,7 @@ function FilterItem(props: Props) {
|
|||
<FilterOperator
|
||||
options={filter.operatorOptions}
|
||||
onChange={onOperatorChange}
|
||||
className="mx-2 flex-shrink-0"
|
||||
className="mx-2 flex-shrink-0 btn-sub-event-operator"
|
||||
value={filter.operator}
|
||||
isDisabled={filter.operatorDisabled || props.readonly}
|
||||
/>
|
||||
|
|
@ -157,6 +157,7 @@ function FilterItem(props: Props) {
|
|||
type="text"
|
||||
onClick={props.onRemoveFilter}
|
||||
size="small"
|
||||
className='btn-remove-step'
|
||||
>
|
||||
<CircleMinus size={14} />
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -38,14 +38,15 @@ const EventsOrder = observer(
|
|||
title="Select the operator to be applied between events."
|
||||
placement="bottom"
|
||||
>
|
||||
<div className="text-neutral-500/90 text-sm">Events Order</div>
|
||||
<div className="text-neutral-500/90 text-sm font-normal">Events Order</div>
|
||||
</Tooltip>
|
||||
|
||||
<Dropdown
|
||||
menu={{ items: menuItems, onClick }}
|
||||
trigger={['click']}
|
||||
placement="bottomRight"
|
||||
className="bg-white border border-neutral-200 rounded-lg px-1 py-0.5 hover:border-teal"
|
||||
className="bg-white border font-normal text-sm border-neutral-200 rounded-lg px-1 py-0.5 hover:border-teal btn-events-order"
|
||||
data-event="btn-events-order"
|
||||
>
|
||||
<Button size={'small'}>{selected || 'Select'}</Button>
|
||||
</Dropdown>
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ export const FilterList = observer((props: Props) => {
|
|||
icon={<Filter size={16} strokeWidth={1} />}
|
||||
type="default"
|
||||
size={'small'}
|
||||
className='btn-add-filter'
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
|
|
@ -214,6 +215,7 @@ export const EventsList = observer((props: Props) => {
|
|||
icon={<Plus size={16} strokeWidth={1} />}
|
||||
type="default"
|
||||
size={'small'}
|
||||
className='btn-add-event'
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
|
|
@ -228,27 +230,39 @@ export const EventsList = observer((props: Props) => {
|
|||
actions.map((action, index) => <div key={index}>{action}</div>)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={'flex flex-col'}>
|
||||
<div className={'flex flex-col '}>
|
||||
{filters.map((filter: any, filterIndex: number) =>
|
||||
filter.isEvent ? (
|
||||
<div
|
||||
className={cn(
|
||||
'hover:bg-active-blue px-5 pe-3 gap-2 items-center flex',
|
||||
{
|
||||
'bg-[#f6f6f6]': hoveredItem.i === filterIndex,
|
||||
}
|
||||
)}
|
||||
style={{
|
||||
pointerEvents: 'unset',
|
||||
paddingTop:
|
||||
hoveredItem.i === filterIndex &&
|
||||
hoveredItem.position === 'top'
|
||||
hoveredItem.i === filterIndex && hoveredItem.position === 'top'
|
||||
? '1.5rem'
|
||||
: '0.5rem',
|
||||
paddingBottom:
|
||||
hoveredItem.i === filterIndex &&
|
||||
hoveredItem.position === 'bottom'
|
||||
hoveredItem.i === filterIndex && hoveredItem.position === 'bottom'
|
||||
? '1.5rem'
|
||||
: '0.5rem',
|
||||
marginLeft: '-1.25rem',
|
||||
width: 'calc(100% + 2.5rem)',
|
||||
marginLeft: '-1rem',
|
||||
width: 'calc(100% + 2rem)',
|
||||
alignItems: 'start',
|
||||
borderTop:
|
||||
hoveredItem.i === filterIndex && hoveredItem.position === 'top'
|
||||
? '1px dashed #888'
|
||||
: undefined,
|
||||
borderBottom:
|
||||
hoveredItem.i === filterIndex && hoveredItem.position === 'bottom'
|
||||
? '1px dashed #888'
|
||||
: undefined,
|
||||
}}
|
||||
className={'hover:bg-active-blue px-5 gap-2 items-center flex'}
|
||||
|
||||
id={`${filter.key}-${filterIndex}`}
|
||||
onDragOver={(e) => handleDragOverEv(e, filterIndex)}
|
||||
onDrop={(e) => handleDrop(e)}
|
||||
|
|
@ -257,16 +271,19 @@ export const EventsList = observer((props: Props) => {
|
|||
{!!props.onFilterMove && eventsNum > 1 ? (
|
||||
<div
|
||||
className={
|
||||
'p-2 cursor-grab text-neutral-500/90 hover:bg-gray-lighter px-1 pt-2 rounded-lg'
|
||||
'p-2 cursor-grab text-neutral-500/90 hover:bg-white px-1 pt-2 rounded-lg'
|
||||
}
|
||||
draggable={!!props.onFilterMove}
|
||||
onDragStart={(e) =>
|
||||
handleDragStart(
|
||||
e,
|
||||
filterIndex,
|
||||
`${filter.key}-${filterIndex}`
|
||||
)
|
||||
handleDragStart(e, filterIndex, `${filter.key}-${filterIndex}`)
|
||||
}
|
||||
onDragEnd={() => {
|
||||
setHoveredItem({ i: null, position: null });
|
||||
setDraggedItem(null);
|
||||
}}
|
||||
style={{
|
||||
cursor: draggedInd !== null ? 'grabbing' : 'grab',
|
||||
}}
|
||||
>
|
||||
<GripVertical size={16} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { backgroundClip } from 'html2canvas/dist/types/css/property-descriptors/background-clip';
|
||||
import React from 'react';
|
||||
import Select from 'Shared/Select';
|
||||
|
||||
|
|
@ -5,14 +6,18 @@ const dropdownStyles = {
|
|||
control: (provided: any) => {
|
||||
const obj = {
|
||||
...provided,
|
||||
border: 'solid thin #ddd !important',
|
||||
border: 'solid thin #ddd',
|
||||
boxShadow: 'none !important',
|
||||
cursor: 'pointer',
|
||||
height: '26px',
|
||||
minHeight: '26px',
|
||||
backgroundColor: 'white',
|
||||
borderRadius: '.5rem',
|
||||
'&:hover': {
|
||||
borderColor: 'rgb(115 115 115 / 0.9)',
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
},
|
||||
valueContainer: (provided: any) => ({
|
||||
|
|
@ -79,6 +84,7 @@ function FilterOperator(props: Props) {
|
|||
isDisabled={isDisabled}
|
||||
value={value ? options?.find((i: any) => i.value === value) : null}
|
||||
onChange={({ value }: any) => onChange(null, { name: 'operator', value: value.value })}
|
||||
className='btn-event-operator'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ function FilterSelection(props: Props) {
|
|||
) : (
|
||||
<div
|
||||
className={cn(
|
||||
'rounded-lg py-1 px-2 flex items-center gap-1 cursor-pointer bg-white border border-gray-light text-ellipsis hover:border-neutral-400',
|
||||
'rounded-lg py-1 px-2 flex items-center gap-1 cursor-pointer bg-white border border-gray-light text-ellipsis hover:border-neutral-400 btn-select-event',
|
||||
{ 'opacity-50 pointer-events-none': disabled }
|
||||
)}
|
||||
style={{
|
||||
|
|
@ -70,8 +70,8 @@ function FilterSelection(props: Props) {
|
|||
}}
|
||||
onClick={() => setShowModal(true)}
|
||||
>
|
||||
<div className='text-xs text-neutral-500/90'>{getNewIcon(filter)}</div>
|
||||
<div className={'text-neutral-500/90 flex gap-2'}>{`${filter.category} •`}</div>
|
||||
<div className='text-xs text-neutral-500/90 hover:border-neutral-400 '>{getNewIcon(filter)}</div>
|
||||
<div className={'text-neutral-500/90 flex gap-2 hover:border-neutral-400 '}>{`${filter.category} •`}</div>
|
||||
<div
|
||||
className="rounded-lg overflow-hidden whitespace-nowrap text-ellipsis mr-auto truncate "
|
||||
style={{ textOverflow: 'ellipsis' }}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export default function SubFilterItem(props: Props) {
|
|||
<FilterOperator
|
||||
options={filter.operatorOptions}
|
||||
onChange={onOperatorChange}
|
||||
className="mx-2 flex-shrink-0"
|
||||
className="mx-2 flex-shrink-0 btn-filter-operator"
|
||||
value={filter.operator}
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ function AndDateRange({
|
|||
{comparison ? (
|
||||
<div className={'flex items-center gap-0'}>
|
||||
<Dropdown menu={menuProps} trigger={['click']} className={'px-2 py-1 gap-1'}>
|
||||
<Button type='text' variant='text' className='flex items-center' size='small'>
|
||||
<Button type='text' variant='text' className='flex items-center btn-compare-card-data' size='small'>
|
||||
<span>{`Compare to ${comparisonValue || ''}`}</span>
|
||||
{selectedValue && (
|
||||
<Tooltip title='Reset'>
|
||||
|
|
@ -220,7 +220,7 @@ function AndDateRange({
|
|||
<Button
|
||||
type="text"
|
||||
size='small'
|
||||
className="flex items-center"
|
||||
className="flex items-center btn-card-period-range"
|
||||
icon={useButtonStyle ? <Calendar size={16} /> : null}
|
||||
>
|
||||
{isCustomRange ? customRange : selectedValue?.label}
|
||||
|
|
|
|||
|
|
@ -363,8 +363,8 @@ p {
|
|||
135deg,
|
||||
transparent,
|
||||
transparent 2px,
|
||||
#ccc 2px,
|
||||
#ccc 4px
|
||||
#ccc 1px,
|
||||
#ccc 1px
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue