ui: some improvements for cards list view, funnels and general filter display
This commit is contained in:
parent
fbf7d716a6
commit
3b7d86d8c6
14 changed files with 700 additions and 656 deletions
|
|
@ -49,7 +49,7 @@ function CustomTooltip(props: Props) {
|
|||
};
|
||||
return (
|
||||
<div
|
||||
className={'flex flex-col gap-1 bg-white shadow border rounded p-2 z-30'}
|
||||
className={'flex flex-col gap-1 bg-white shadow border rounded p-2 z-50'}
|
||||
>
|
||||
{transformedArray.map((p, index) => (
|
||||
<React.Fragment key={p.name + index}>
|
||||
|
|
|
|||
|
|
@ -50,6 +50,9 @@ export default {
|
|||
tickFormatterBytes: val => Math.round(val / 1024 / 1024),
|
||||
chartMargins: {left: 0, right: 20, top: 10, bottom: 5},
|
||||
tooltip: {
|
||||
wrapperStyle: {
|
||||
zIndex: 999,
|
||||
},
|
||||
contentStyle: {
|
||||
padding: '5px',
|
||||
background: 'white',
|
||||
|
|
|
|||
|
|
@ -1,31 +1,57 @@
|
|||
import React from 'react';
|
||||
import { ItemMenu } from 'UI';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useStore } from "App/mstore";
|
||||
import { useStore } from 'App/mstore';
|
||||
import { ENTERPRISE_REQUEIRED } from 'App/constants';
|
||||
import { Dropdown, Button } from 'antd';
|
||||
import { EllipsisVertical } from 'lucide-react';
|
||||
import { Icon } from 'UI';
|
||||
|
||||
interface Props {
|
||||
editHandler: (isTitle: boolean) => void;
|
||||
deleteHandler: any;
|
||||
renderReport: any;
|
||||
editHandler: (isTitle: boolean) => void;
|
||||
deleteHandler: any;
|
||||
renderReport: any;
|
||||
}
|
||||
function DashboardOptions(props: Props) {
|
||||
const { userStore } = useStore();
|
||||
const isEnterprise = userStore.isEnterprise;
|
||||
const { editHandler, deleteHandler, renderReport } = props;
|
||||
const menuItems = [
|
||||
{ icon: 'pencil', text: 'Rename', onClick: () => editHandler(true) },
|
||||
{ icon: 'users', text: 'Visibility & Access', onClick: editHandler },
|
||||
{ icon: 'trash', text: 'Delete', onClick: deleteHandler },
|
||||
{ icon: 'pdf-download', text: 'Download Report', onClick: renderReport, disabled: !isEnterprise, tooltipTitle: ENTERPRISE_REQUEIRED }
|
||||
]
|
||||
const { userStore } = useStore();
|
||||
const isEnterprise = userStore.isEnterprise;
|
||||
const { editHandler, deleteHandler, renderReport } = props;
|
||||
|
||||
return (
|
||||
<ItemMenu
|
||||
bold
|
||||
items={menuItems}
|
||||
/>
|
||||
);
|
||||
const menu = {
|
||||
items: [
|
||||
{
|
||||
icon: <Icon name={'pencil'} />,
|
||||
key: 'rename',
|
||||
label: 'Rename',
|
||||
onClick: () => editHandler(true),
|
||||
},
|
||||
{
|
||||
icon: <Icon name={'users'} />,
|
||||
key: 'visibility',
|
||||
label: 'Visibility & Access',
|
||||
onClick: editHandler,
|
||||
},
|
||||
{
|
||||
icon: <Icon name={'trash'} />,
|
||||
key: 'delete',
|
||||
label: 'Delete',
|
||||
onClick: deleteHandler,
|
||||
},
|
||||
{
|
||||
icon: <Icon name={'pdf-download'} />,
|
||||
key: 'download',
|
||||
label: 'Download Report',
|
||||
onClick: renderReport,
|
||||
disabled: !isEnterprise,
|
||||
tooltipTitle: ENTERPRISE_REQUEIRED,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown menu={menu}>
|
||||
<Button id={'ignore-prop'} icon={<EllipsisVertical size={16} />} />
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(DashboardOptions);
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ interface Props {
|
|||
expandable?: boolean;
|
||||
isHeatmap?: boolean;
|
||||
removeEvents?: boolean;
|
||||
defaultClosed?: boolean;
|
||||
}
|
||||
|
||||
function FilterSeries(props: Props) {
|
||||
|
|
@ -131,8 +132,9 @@ function FilterSeries(props: Props) {
|
|||
expandable = false,
|
||||
isHeatmap,
|
||||
removeEvents,
|
||||
defaultClosed,
|
||||
} = props;
|
||||
const [expanded, setExpanded] = useState(hideHeader || !expandable);
|
||||
const [expanded, setExpanded] = useState(!defaultClosed || hideHeader);
|
||||
const { series, seriesIndex } = props;
|
||||
const [prevLength, setPrevLength] = useState(0);
|
||||
|
||||
|
|
@ -140,12 +142,13 @@ function FilterSeries(props: Props) {
|
|||
if (
|
||||
series.filter.filters.length === 1 &&
|
||||
prevLength === 0 &&
|
||||
seriesIndex === 0
|
||||
seriesIndex === 0 &&
|
||||
!defaultClosed
|
||||
) {
|
||||
setExpanded(true);
|
||||
}
|
||||
setPrevLength(series.filter.filters.length);
|
||||
}, [series.filter.filters.length]);
|
||||
}, [series.filter.filters.length, defaultClosed]);
|
||||
|
||||
const onUpdateFilter = (filterIndex: any, filter: any) => {
|
||||
series.filter.updateFilter(filterIndex, filter);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { useStore } from 'App/mstore';
|
|||
import { observer } from 'mobx-react-lite';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useHistory } from 'react-router';
|
||||
import { EllipsisVertical } from "lucide-react";
|
||||
|
||||
interface Props extends RouteComponentProps {
|
||||
metric: any;
|
||||
|
|
@ -175,7 +176,7 @@ const MetricListItem: React.FC<Props> = ({
|
|||
menu={{ items: menuItems, onClick: onMenuClick }}
|
||||
trigger={['click']}
|
||||
>
|
||||
<Button type="text" icon={<MoreOutlined />} />
|
||||
<Button id={'ignore-prop'} icon={<EllipsisVertical size={16} />} />
|
||||
</Dropdown>
|
||||
</div>
|
||||
{renderModal()}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,23 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { PageTitle, Icon } from 'UI';
|
||||
import { Segmented, Button, Popover } from 'antd';
|
||||
import { PageTitle } from 'UI';
|
||||
import { Button, Popover } from 'antd';
|
||||
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';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
function MetricViewHeader() {
|
||||
const { metricStore } = useStore();
|
||||
const filter = metricStore.filter;
|
||||
const [showAddCardModal, setShowAddCardModal] = React.useState(false);
|
||||
const modalBgRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
// Set the default sort order to 'desc'
|
||||
useEffect(() => {
|
||||
// Set the default sort order to 'desc'
|
||||
metricStore.updateKey('sort', { by: 'desc' });
|
||||
}, [metricStore]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between px-6">
|
||||
<div className="flex items-center justify-between px-4 pb-2">
|
||||
<div className="flex items-baseline mr-3">
|
||||
<PageTitle title="Cards" className="" />
|
||||
</div>
|
||||
|
|
@ -31,7 +25,6 @@ function MetricViewHeader() {
|
|||
<Popover arrow={false} overlayInnerStyle={{ padding: 0, borderRadius: '0.75rem' }} content={<AddCardSection fit inCards />} trigger={'click'}>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => setShowAddCardModal(true)}
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
Create Card
|
||||
|
|
@ -42,96 +35,9 @@ function MetricViewHeader() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-y px-6 py-1 mt-2 flex items-center w-full justify-between">
|
||||
<div className="items-center flex gap-4">
|
||||
<AntSelect
|
||||
options={[
|
||||
{ label: 'All Card Types', value: 'all' },
|
||||
...DROPDOWN_OPTIONS,
|
||||
]}
|
||||
name="type"
|
||||
defaultValue={filter.type}
|
||||
onChange={({ value }) =>
|
||||
metricStore.updateKey('filter', { ...filter, type: value.value })
|
||||
}
|
||||
|
||||
/>
|
||||
|
||||
<DashboardDropdown
|
||||
plain={false}
|
||||
onChange={(value: any) =>
|
||||
metricStore.updateKey('filter', { ...filter, dashboard: value })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(MetricViewHeader);
|
||||
|
||||
function DashboardDropdown({
|
||||
onChange,
|
||||
plain = false,
|
||||
}: {
|
||||
plain?: boolean;
|
||||
onChange: (val: any) => void;
|
||||
}) {
|
||||
const { dashboardStore, metricStore } = useStore();
|
||||
const dashboardOptions = dashboardStore.dashboards.map((i, l) => ({
|
||||
key: `${i.dashboardId}_${l}`,
|
||||
label: i.name,
|
||||
value: i.dashboardId,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Select
|
||||
isSearchable={true}
|
||||
placeholder="Filter by Dashboard"
|
||||
plain={plain}
|
||||
options={dashboardOptions}
|
||||
value={metricStore.filter.dashboard}
|
||||
onChange={({ value }: any) => onChange(value)}
|
||||
isMulti={true}
|
||||
color="black"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ListViewToggler() {
|
||||
const { metricStore } = useStore();
|
||||
const listView = useObserver(() => metricStore.listView);
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<Segmented
|
||||
size="small"
|
||||
options={[
|
||||
{
|
||||
label: (
|
||||
<div className={'flex items-center gap-2'}>
|
||||
<Icon name={'list-alt'} color={'inherit'} />
|
||||
<div>List</div>
|
||||
</div>
|
||||
),
|
||||
value: 'list',
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<div className={'flex items-center gap-2'}>
|
||||
<Icon name={'grid'} color={'inherit'} />
|
||||
<div>Grid</div>
|
||||
</div>
|
||||
),
|
||||
value: 'grid',
|
||||
},
|
||||
]}
|
||||
onChange={(val) => {
|
||||
metricStore.updateKey('listView', val === 'list');
|
||||
}}
|
||||
value={listView ? 'list' : 'grid'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import { Checkbox, Table, Typography } from 'antd';
|
|||
import MetricListItem from '../MetricListItem';
|
||||
import { TablePaginationConfig, SorterResult } from 'antd/lib/table/interface';
|
||||
import Widget from 'App/mstore/types/widget';
|
||||
import { LockOutlined, TeamOutlined } from ".store/@ant-design-icons-virtual-981121729b/package";
|
||||
import { Switch, Tag, Tooltip } from ".store/antd-virtual-9b6c8c01be/package";
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
|
|
@ -23,6 +25,8 @@ interface Props {
|
|||
disableSelection?: boolean;
|
||||
allSelected?: boolean;
|
||||
existingCardIds?: number[];
|
||||
showOwn?: boolean;
|
||||
toggleOwn: () => void;
|
||||
}
|
||||
|
||||
const ListView: React.FC<Props> = (props: Props) => {
|
||||
|
|
@ -33,7 +37,9 @@ const ListView: React.FC<Props> = (props: Props) => {
|
|||
toggleSelection,
|
||||
disableSelection = false,
|
||||
allSelected = false,
|
||||
toggleAll
|
||||
toggleAll,
|
||||
showOwn,
|
||||
toggleOwn,
|
||||
} = props;
|
||||
const [sorter, setSorter] = useState<{ field: string; order: 'ascend' | 'descend' }>({
|
||||
field: 'lastModified',
|
||||
|
|
@ -101,6 +107,7 @@ const ListView: React.FC<Props> = (props: Props) => {
|
|||
key: 'title',
|
||||
className: 'cap-first',
|
||||
sorter: true,
|
||||
width: '25%',
|
||||
render: (text: string, metric: Metric) => (
|
||||
<MetricListItem
|
||||
key={metric.metricId}
|
||||
|
|
@ -121,7 +128,7 @@ const ListView: React.FC<Props> = (props: Props) => {
|
|||
dataIndex: 'owner',
|
||||
key: 'owner',
|
||||
className: 'capitalize',
|
||||
width: '30%',
|
||||
width: '16.67%',
|
||||
sorter: true,
|
||||
render: (text: string, metric: Metric) => (
|
||||
<MetricListItem
|
||||
|
|
@ -162,7 +169,38 @@ const ListView: React.FC<Props> = (props: Props) => {
|
|||
// )
|
||||
// },
|
||||
{
|
||||
title: '',
|
||||
title: (
|
||||
<div className={'flex items-center justify-start gap-2'}>
|
||||
<div>Visibility</div>
|
||||
<Tooltip
|
||||
title="Toggle to view your own or team's cards."
|
||||
placement="topRight"
|
||||
>
|
||||
<Switch
|
||||
checked={!showOwn}
|
||||
onChange={() =>
|
||||
toggleOwn()
|
||||
}
|
||||
checkedChildren={'Team'}
|
||||
unCheckedChildren={'Private'}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
),
|
||||
width: '16.67%',
|
||||
dataIndex: 'isPublic',
|
||||
render: (isPublic: boolean) => (
|
||||
<Tag
|
||||
icon={isPublic ? <TeamOutlined /> : <LockOutlined />}
|
||||
bordered={false}
|
||||
className="rounded-lg"
|
||||
>
|
||||
{isPublic ? 'Team' : 'Private'}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Options',
|
||||
key: 'options',
|
||||
className: 'text-right',
|
||||
width: '5%',
|
||||
|
|
@ -183,7 +221,6 @@ const ListView: React.FC<Props> = (props: Props) => {
|
|||
dataSource={paginatedData}
|
||||
rowKey="metricId"
|
||||
onChange={handleTableChange}
|
||||
size='middle'
|
||||
rowSelection={
|
||||
!disableSelection
|
||||
? {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { observer, useObserver } from 'mobx-react-lite';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { NoContent, Pagination, Icon, Loader } from 'UI';
|
||||
import { NoContent, Pagination, Loader } from 'UI';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { sliceListPerPage } from 'App/utils';
|
||||
import GridView from './GridView';
|
||||
|
|
@ -8,24 +8,34 @@ import ListView from './ListView';
|
|||
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
|
||||
|
||||
function MetricsList({
|
||||
siteId,
|
||||
onSelectionChange
|
||||
}: {
|
||||
siteId,
|
||||
onSelectionChange,
|
||||
}: {
|
||||
siteId: string;
|
||||
onSelectionChange?: (selected: any[]) => void;
|
||||
}) {
|
||||
const { metricStore, dashboardStore } = useStore();
|
||||
const metricsSearch = metricStore.filter.query;
|
||||
const listView = useObserver(() => metricStore.listView);
|
||||
const [selectedMetrics, setSelectedMetrics] = useState<any>([]);
|
||||
|
||||
const dashboard = dashboardStore.selectedDashboard;
|
||||
const existingCardIds = useMemo(() => dashboard?.widgets?.map(i => parseInt(i.metricId)), [dashboard]);
|
||||
const cards = useMemo(() => !!onSelectionChange ? metricStore.filteredCards.filter(i => !existingCardIds?.includes(parseInt(i.metricId))) : metricStore.filteredCards, [metricStore.filteredCards]);
|
||||
const existingCardIds = useMemo(
|
||||
() => dashboard?.widgets?.map((i) => parseInt(i.metricId)),
|
||||
[dashboard]
|
||||
);
|
||||
const cards = useMemo(
|
||||
() =>
|
||||
!!onSelectionChange
|
||||
? metricStore.filteredCards.filter(
|
||||
(i) => !existingCardIds?.includes(parseInt(i.metricId))
|
||||
)
|
||||
: metricStore.filteredCards,
|
||||
[metricStore.filteredCards]
|
||||
);
|
||||
const loading = metricStore.isLoading;
|
||||
|
||||
useEffect(() => {
|
||||
metricStore.fetchList();
|
||||
void metricStore.fetchList();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -43,63 +53,52 @@ function MetricsList({
|
|||
}
|
||||
};
|
||||
|
||||
const lenth = cards.length;
|
||||
const length = cards.length;
|
||||
|
||||
useEffect(() => {
|
||||
metricStore.updateKey('sessionsPage', 1);
|
||||
}, []);
|
||||
|
||||
const showOwn = metricStore.filter.showMine;
|
||||
const toggleOwn = () => {
|
||||
metricStore.updateKey('showMine', !showOwn);
|
||||
}
|
||||
return (
|
||||
<Loader loading={loading}>
|
||||
<NoContent
|
||||
show={lenth === 0}
|
||||
show={length === 0}
|
||||
title={
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<AnimatedSVG name={ICONS.NO_CARDS} size={60} />
|
||||
<div className="text-center mt-4 text-lg font-medium">
|
||||
{metricsSearch !== '' ? 'No matching results' : 'You haven\'t created any cards yet'}
|
||||
{metricsSearch !== ''
|
||||
? 'No matching results'
|
||||
: "You haven't created any cards yet"}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
subtext="Utilize cards to visualize key user interactions or product performance metrics."
|
||||
>
|
||||
{listView ? (
|
||||
<ListView
|
||||
disableSelection={!onSelectionChange}
|
||||
siteId={siteId}
|
||||
list={cards}
|
||||
selectedList={selectedMetrics}
|
||||
existingCardIds={existingCardIds}
|
||||
toggleSelection={toggleMetricSelection}
|
||||
allSelected={cards.length === selectedMetrics.length}
|
||||
toggleAll={({ target: { checked, name } }) =>
|
||||
setSelectedMetrics(checked ? cards.map((i: any) => i.metricId).slice(0, 30 - existingCardIds!.length) : [])
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<GridView
|
||||
siteId={siteId}
|
||||
list={sliceListPerPage(cards, metricStore.page - 1, metricStore.pageSize)}
|
||||
selectedList={selectedMetrics}
|
||||
toggleSelection={toggleMetricSelection}
|
||||
/>
|
||||
<div className="w-full flex items-center justify-between py-4 px-6 border-t">
|
||||
<div className="">
|
||||
Showing{' '}
|
||||
<span className="font-semibold">{Math.min(cards.length, metricStore.pageSize)}</span> out
|
||||
of <span className="font-semibold">{cards.length}</span> cards
|
||||
</div>
|
||||
<Pagination
|
||||
page={metricStore.page}
|
||||
total={lenth}
|
||||
onPageChange={(page) => metricStore.updateKey('page', page)}
|
||||
limit={metricStore.pageSize}
|
||||
debounceRequest={100}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<ListView
|
||||
disableSelection={!onSelectionChange}
|
||||
siteId={siteId}
|
||||
list={cards}
|
||||
selectedList={selectedMetrics}
|
||||
existingCardIds={existingCardIds}
|
||||
toggleSelection={toggleMetricSelection}
|
||||
allSelected={cards.length === selectedMetrics.length}
|
||||
toggleAll={({ target: { checked, name } }) =>
|
||||
setSelectedMetrics(
|
||||
checked
|
||||
? cards
|
||||
.map((i: any) => i.metricId)
|
||||
.slice(0, 30 - existingCardIds!.length)
|
||||
: []
|
||||
)
|
||||
}
|
||||
showOwn={showOwn}
|
||||
toggleOwn={toggleOwn}
|
||||
/>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -51,9 +51,9 @@ function RangeGranularity({
|
|||
)
|
||||
}
|
||||
|
||||
const PAST_24_HR_MS = 24 * 60 * 60 * 1000
|
||||
function calculateGranularities(periodDurationMs: number) {
|
||||
const granularities = [
|
||||
{ label: 'By minute', durationMs: 60 * 1000 },
|
||||
{ label: 'Hourly', durationMs: 60 * 60 * 1000 },
|
||||
{ label: 'Daily', durationMs: 24 * 60 * 60 * 1000 },
|
||||
{ label: 'Weekly', durationMs: 7 * 24 * 60 * 60 * 1000 },
|
||||
|
|
@ -62,6 +62,12 @@ function calculateGranularities(periodDurationMs: number) {
|
|||
];
|
||||
|
||||
const result = [];
|
||||
if (periodDurationMs === PAST_24_HR_MS) {
|
||||
// if showing for 1 day, show by minute split as well
|
||||
granularities.push(
|
||||
{ label: 'By minute', durationMs: 60 * 1000 },
|
||||
)
|
||||
}
|
||||
|
||||
for (const granularity of granularities) {
|
||||
if (periodDurationMs >= granularity.durationMs) {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ function WidgetFormNew() {
|
|||
export default observer(WidgetFormNew);
|
||||
|
||||
const FilterSection = observer(({ metric, excludeFilterKeys }: any) => {
|
||||
const defaultClosed = React.useRef(metric.exists())
|
||||
const isTable = metric.metricType === TABLE;
|
||||
const isHeatMap = metric.metricType === HEATMAP;
|
||||
const isFunnel = metric.metricType === FUNNEL;
|
||||
|
|
@ -74,6 +75,7 @@ const FilterSection = observer(({ metric, excludeFilterKeys }: any) => {
|
|||
series={series}
|
||||
onRemoveSeries={() => metric.removeSeries(index)}
|
||||
canDelete={metric.series.length > 1}
|
||||
defaultClosed={defaultClosed.current}
|
||||
emptyMessage={
|
||||
isTable
|
||||
? 'Filter data using any event or attribute. Use Add Step button below to do so.'
|
||||
|
|
|
|||
|
|
@ -63,31 +63,20 @@ function FunnelBarData({
|
|||
index?: number;
|
||||
isHorizontal?: boolean;
|
||||
}) {
|
||||
|
||||
const vertFillBarStyle = {
|
||||
width: `${data.completedPercentageTotal}%`,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: isComp ? Styles.compareColors[2] : Styles.compareColors[1]
|
||||
height: '100%',
|
||||
backgroundColor: isComp ? Styles.compareColors[2] : Styles.compareColors[1],
|
||||
};
|
||||
const horizontalFillBarStyle = {
|
||||
width: '100%',
|
||||
height: `${data.completedPercentageTotal}%`,
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
backgroundColor: isComp ? Styles.compareColors[2] : Styles.compareColors[1]
|
||||
backgroundColor: isComp ? Styles.compareColors[2] : Styles.compareColors[1],
|
||||
}
|
||||
|
||||
const vertEmptyBarStyle = {
|
||||
width: `${100.1 - data.completedPercentageTotal}%`,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
height: '100%',
|
||||
background: isFocused
|
||||
? 'rgba(204, 0, 0, 0.3)'
|
||||
: 'repeating-linear-gradient(325deg, lightgray, lightgray 1px, #FFF1F0 1px, #FFF1F0 6px)',
|
||||
|
|
@ -96,10 +85,6 @@ function FunnelBarData({
|
|||
const horizontalEmptyBarStyle = {
|
||||
height: `${100.1 - data.completedPercentageTotal}%`,
|
||||
width: '100%',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
background: isFocused
|
||||
? 'rgba(204, 0, 0, 0.3)'
|
||||
: 'repeating-linear-gradient(325deg, lightgray, lightgray 1px, #FFF1F0 1px, #FFF1F0 6px)',
|
||||
|
|
@ -120,13 +105,15 @@ function FunnelBarData({
|
|||
borderRadius: isHorizontal ? undefined : '.5rem',
|
||||
overflow: 'hidden',
|
||||
opacity: isComp ? 0.7 : 1,
|
||||
display: 'flex',
|
||||
flexDirection: isHorizontal ? 'column-reverse' : 'row',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="flex items-center"
|
||||
className={cn("flex", isHorizontal ? 'justify-center items-start pt-1' : 'justify-end items-center pr-1')}
|
||||
style={fillBarStyle}
|
||||
>
|
||||
<div className="color-white absolute right-0 flex items-center font-medium mr-2 leading-3">
|
||||
<div className="color-white flex items-center font-medium leading-3">
|
||||
{data.completedPercentageTotal}%
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -64,8 +64,10 @@ const FilterAutoComplete = observer(
|
|||
const filterKey = `${_params.type}${_params.key || ''}`;
|
||||
const topValues = filterStore.topValues[filterKey] || [];
|
||||
|
||||
const loadTopValues = () => {
|
||||
void filterStore.fetchTopValues(_params.type, _params.key);
|
||||
const loadTopValues = async () => {
|
||||
setLoading(true)
|
||||
await filterStore.fetchTopValues(_params.type, _params.key);
|
||||
setLoading(false)
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -78,7 +80,7 @@ const FilterAutoComplete = observer(
|
|||
}
|
||||
}, [topValues, initialFocus]);
|
||||
|
||||
useEffect(loadTopValues, [_params.type]);
|
||||
useEffect(() => { void loadTopValues() }, [_params.type]);
|
||||
|
||||
const loadOptions = async (
|
||||
inputValue: string,
|
||||
|
|
|
|||
|
|
@ -44,17 +44,16 @@ function FilterSelection(props: Props) {
|
|||
<div className="relative flex-shrink-0">
|
||||
<OutsideClickDetectingDiv
|
||||
className="relative"
|
||||
onClickOutside={() =>
|
||||
setTimeout(function () {
|
||||
onClickOutside={() => {
|
||||
setTimeout(() => {
|
||||
setShowModal(false);
|
||||
}, 200)
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
>
|
||||
{children ? (
|
||||
React.cloneElement(children, {
|
||||
onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
setShowModal(true);
|
||||
},
|
||||
disabled: disabled,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue