change(ui) - cards - filters
This commit is contained in:
parent
1a1ec33d7d
commit
eb4c07a876
7 changed files with 193 additions and 82 deletions
|
|
@ -3,14 +3,17 @@ import { Icon, PageTitle, Button, Link, SegmentSelection } from 'UI';
|
|||
import MetricsSearch from '../MetricsSearch';
|
||||
import Select from 'Shared/Select';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { useObserver } from 'mobx-react-lite';
|
||||
import { observer, useObserver } from 'mobx-react-lite';
|
||||
import { DROPDOWN_OPTIONS, Option } from 'App/constants/card';
|
||||
|
||||
function MetricViewHeader() {
|
||||
const { metricStore } = useStore();
|
||||
const sort = useObserver(() => metricStore.sort);
|
||||
const listView = useObserver(() => metricStore.listView);
|
||||
const sort = metricStore.sort;
|
||||
const listView = metricStore.listView;
|
||||
const filter = metricStore.filter;
|
||||
|
||||
const writeOption = (e: any, { name, value }: any) => {};
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center mb-4 justify-between px-6">
|
||||
|
|
@ -21,16 +24,16 @@ function MetricViewHeader() {
|
|||
<Link to={'/metrics/create'}>
|
||||
<Button variant="primary">New Card</Button>
|
||||
</Link>
|
||||
<SegmentSelection
|
||||
name="viewType"
|
||||
className="mx-3"
|
||||
primary
|
||||
onSelect={ () => metricStore.updateKey('listView', !listView) }
|
||||
value={{ value: listView ? 'list' : 'grid' }}
|
||||
list={ [
|
||||
{ value: 'list', name: '', icon: 'graph-up-arrow' },
|
||||
{ value: 'grid', name: '', icon: 'hash' },
|
||||
]}
|
||||
{/* <SegmentSelection
|
||||
name="viewType"
|
||||
className="mx-3"
|
||||
primary
|
||||
onSelect={() => metricStore.updateKey('listView', !listView)}
|
||||
value={{ value: listView ? 'list' : 'grid' }}
|
||||
list={[
|
||||
{ value: 'list', name: '', icon: 'graph-up-arrow' },
|
||||
{ value: 'grid', name: '', icon: 'hash' },
|
||||
]}
|
||||
/>
|
||||
<div className="mx-2">
|
||||
<Select
|
||||
|
|
@ -42,7 +45,7 @@ function MetricViewHeader() {
|
|||
plain
|
||||
onChange={({ value }) => metricStore.updateKey('sort', { by: value.value })}
|
||||
/>
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="ml-4 w-1/4" style={{ minWidth: 300 }}>
|
||||
<MetricsSearch />
|
||||
</div>
|
||||
|
|
@ -52,8 +55,81 @@ function MetricViewHeader() {
|
|||
<Icon name="info-circle-fill" className="mr-2" size={16} />
|
||||
Create custom Cards to capture key interactions and track KPIs.
|
||||
</div>
|
||||
<div className="border-y px-3 py-1 mt-2 flex items-center w-full justify-between">
|
||||
<ListViewToggler />
|
||||
|
||||
<div className="items-center flex gap-4">
|
||||
<Select
|
||||
options={[{ label: 'All Types', value: 'all' }, ...DROPDOWN_OPTIONS]}
|
||||
name="type"
|
||||
defaultValue={filter.type}
|
||||
onChange={({ value }) => metricStore.updateKey('filter', { ...filter, type: value.value})}
|
||||
plain={true}
|
||||
/>
|
||||
|
||||
<Select
|
||||
options={[
|
||||
{ label: 'Newest', value: 'desc' },
|
||||
{ label: 'Oldest', value: 'asc' },
|
||||
]}
|
||||
name="sort"
|
||||
defaultValue={metricStore.sort.by}
|
||||
onChange={({ value }) => metricStore.updateKey('sort', { by: value.value })}
|
||||
plain={true}
|
||||
/>
|
||||
|
||||
<DashboardDropdown
|
||||
plain={true}
|
||||
onChange={(value: any) => metricStore.updateKey('filter', { ...filter, dashboard: value})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MetricViewHeader;
|
||||
export default observer(MetricViewHeader);
|
||||
|
||||
function DashboardDropdown({ onChange, plain = false }: { plain?: boolean; onChange: any }) {
|
||||
const { dashboardStore, metricStore } = useStore();
|
||||
const dashboardOptions = dashboardStore.dashboards.map((i: any) => ({
|
||||
key: i.id,
|
||||
label: i.name,
|
||||
value: i.dashboardId,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Select
|
||||
isSearchable={true}
|
||||
placeholder="Select Dashboard"
|
||||
plain={plain}
|
||||
options={dashboardOptions}
|
||||
value={metricStore.filter.dashboard}
|
||||
onChange={({ value }: any) => onChange(value)}
|
||||
isMulti={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ListViewToggler({}) {
|
||||
const { metricStore } = useStore();
|
||||
const listView = useObserver(() => metricStore.listView);
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
icon="list-alt"
|
||||
variant={listView ? 'text-primary' : 'text'}
|
||||
onClick={() => metricStore.updateKey('listView', true)}
|
||||
>
|
||||
List
|
||||
</Button>
|
||||
<Button
|
||||
icon="grid"
|
||||
variant={!listView ? 'text-primary' : 'text'}
|
||||
onClick={() => metricStore.updateKey('listView', false)}
|
||||
>
|
||||
Grid
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ function MetricSearch({ onChange }: any) {
|
|||
function SelectedContent({ dashboardId, selected }: any) {
|
||||
const { hideModal } = useModal();
|
||||
const { metricStore, dashboardStore } = useStore();
|
||||
const total = useObserver(() => metricStore.sortedWidgets.length);
|
||||
const total = useObserver(() => metricStore.metrics.length);
|
||||
const dashboard = useMemo(() => dashboardStore.getDashboard(dashboardId), [dashboardId]);
|
||||
|
||||
const addSelectedToDashboard = () => {
|
||||
|
|
|
|||
|
|
@ -17,11 +17,11 @@ function MetricsList({
|
|||
onSelectionChange?: (selected: any[]) => void;
|
||||
}) {
|
||||
const { metricStore } = useStore();
|
||||
const metrics = metricStore.sortedWidgets;
|
||||
const cards = metricStore.filteredCards;
|
||||
const metricsSearch = metricStore.metricsSearch;
|
||||
const listView = useObserver(() => metricStore.listView);
|
||||
const [selectedMetrics, setSelectedMetrics] = useState<any>([]);
|
||||
const sortBy = useObserver(() => metricStore.sort.by);
|
||||
// const sortBy = useObserver(() => metricStore.sort.by);
|
||||
|
||||
useEffect(() => {
|
||||
metricStore.fetchList();
|
||||
|
|
@ -42,17 +42,17 @@ function MetricsList({
|
|||
}
|
||||
};
|
||||
|
||||
const filterByDashboard = (item: Widget, searchRE: RegExp) => {
|
||||
const dashboardsStr = item.dashboards.map((d: any) => d.name).join(' ');
|
||||
return searchRE.test(dashboardsStr);
|
||||
};
|
||||
// const filterByDashboard = (item: Widget, searchRE: RegExp) => {
|
||||
// const dashboardsStr = item.dashboards.map((d: any) => d.name).join(' ');
|
||||
// return searchRE.test(dashboardsStr);
|
||||
// };
|
||||
|
||||
const list =
|
||||
metricsSearch !== ''
|
||||
? filterList(metrics, metricsSearch, ['name', 'metricType', 'owner'], filterByDashboard)
|
||||
: metrics;
|
||||
// const list =
|
||||
// metricsSearch !== ''
|
||||
// ? filterList(metrics, metricsSearch, ['name', 'metricType', 'owner'], filterByDashboard)
|
||||
// : metrics;
|
||||
|
||||
const lenth = list.length;
|
||||
const lenth = cards.length;
|
||||
|
||||
useEffect(() => {
|
||||
metricStore.updateKey('sessionsPage', 1);
|
||||
|
|
@ -74,18 +74,18 @@ function MetricsList({
|
|||
<ListView
|
||||
disableSelection={!onSelectionChange}
|
||||
siteId={siteId}
|
||||
list={sliceListPerPage(list, metricStore.page - 1, metricStore.pageSize)}
|
||||
list={sliceListPerPage(cards, metricStore.page - 1, metricStore.pageSize)}
|
||||
selectedList={selectedMetrics}
|
||||
toggleSelection={toggleMetricSelection}
|
||||
allSelected={list.length === selectedMetrics.length}
|
||||
allSelected={cards.length === selectedMetrics.length}
|
||||
toggleAll={({ target: { checked, name } }) =>
|
||||
setSelectedMetrics(checked ? list.map((i: any) => i.metricId) : [])
|
||||
setSelectedMetrics(checked ? cards.map((i: any) => i.metricId) : [])
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<GridView
|
||||
siteId={siteId}
|
||||
list={sliceListPerPage(list, metricStore.page - 1, metricStore.pageSize)}
|
||||
list={sliceListPerPage(cards, metricStore.page - 1, metricStore.pageSize)}
|
||||
selectedList={selectedMetrics}
|
||||
toggleSelection={toggleMetricSelection}
|
||||
/>
|
||||
|
|
@ -94,8 +94,8 @@ function MetricsList({
|
|||
<div className="w-full flex items-center justify-between py-4 px-6 border-t">
|
||||
<div className="text-disabled-text">
|
||||
Showing{' '}
|
||||
<span className="font-semibold">{Math.min(list.length, metricStore.pageSize)}</span> out
|
||||
of <span className="font-semibold">{list.length}</span> cards
|
||||
<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}
|
||||
|
|
|
|||
|
|
@ -4,31 +4,34 @@ import { useStore } from 'App/mstore';
|
|||
import { Icon } from 'UI';
|
||||
import { debounce } from 'App/utils';
|
||||
|
||||
let debounceUpdate: any = () => {}
|
||||
function MetricsSearch(props) {
|
||||
const { metricStore } = useStore();
|
||||
const [query, setQuery] = useState(metricStore.metricsSearch);
|
||||
useEffect(() => {
|
||||
debounceUpdate = debounce((key, value) => metricStore.updateKey(key, value), 500);
|
||||
}, [])
|
||||
let debounceUpdate: any = () => {};
|
||||
function MetricsSearch() {
|
||||
const { metricStore } = useStore();
|
||||
const [query, setQuery] = useState(metricStore.filter.query);
|
||||
useEffect(() => {
|
||||
debounceUpdate = debounce(
|
||||
(key: any, value: any) => metricStore.updateKey('filter', { ...metricStore.filter, query: value }),
|
||||
500
|
||||
);
|
||||
}, []);
|
||||
|
||||
const write = ({ target: { value } }) => {
|
||||
setQuery(value);
|
||||
debounceUpdate('metricsSearch', value);
|
||||
}
|
||||
|
||||
return useObserver(() => (
|
||||
<div className="relative">
|
||||
<Icon name="search" className="absolute top-0 bottom-0 ml-2 m-auto" size="16" />
|
||||
<input
|
||||
value={query}
|
||||
name="metricsSearch"
|
||||
className="bg-white p-2 border border-borderColor-gray-light-shade rounded w-full pl-10"
|
||||
placeholder="Filter by title, type, dashboard and owner"
|
||||
onChange={write}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
const write = ({ target: { value } }: any) => {
|
||||
setQuery(value);
|
||||
debounceUpdate('metricsSearch', value);
|
||||
};
|
||||
|
||||
return useObserver(() => (
|
||||
<div className="relative">
|
||||
<Icon name="search" className="absolute top-0 bottom-0 ml-2 m-auto" size="16" />
|
||||
<input
|
||||
value={query}
|
||||
name="metricsSearch"
|
||||
className="bg-white p-2 border border-borderColor-gray-light-shade rounded w-full pl-10"
|
||||
placeholder="Filter by title and owner"
|
||||
onChange={write}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
}
|
||||
|
||||
export default MetricsSearch;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import { TYPES, LIBRARY } from 'App/constants/card';
|
||||
import React from 'react';
|
||||
import { DROPDOWN_OPTIONS, Option } from 'App/constants/card';
|
||||
import Select from 'Shared/Select';
|
||||
import { MetricType } from 'App/components/Dashboard/components/MetricTypeItem/MetricTypeItem';
|
||||
import { components } from 'react-select';
|
||||
import CustomDropdownOption from 'Shared/CustomDropdownOption';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
|
@ -22,20 +21,11 @@ interface Props {
|
|||
function MetricTypeDropdown(props: Props) {
|
||||
const { metricStore } = useStore();
|
||||
const metric: any = metricStore.instance;
|
||||
const options: Options[] = useMemo(() => {
|
||||
// TYPES.shift(); // remove "Add from library" item
|
||||
return TYPES.filter((i: MetricType) => i.slug !== LIBRARY).map((i: MetricType) => ({
|
||||
label: i.title,
|
||||
icon: i.icon,
|
||||
value: i.slug,
|
||||
description: i.description,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
const queryCardType = props.query.get('type');
|
||||
if (queryCardType && options.length > 0 && metric.metricType) {
|
||||
const type = options.find((i) => i.value === queryCardType);
|
||||
if (queryCardType && DROPDOWN_OPTIONS.length > 0 && metric.metricType) {
|
||||
const type: Option = DROPDOWN_OPTIONS.find((i) => i.value === queryCardType) as Option;
|
||||
setTimeout(() => onChange(type.value), 0);
|
||||
}
|
||||
}, []);
|
||||
|
|
@ -48,13 +38,16 @@ function MetricTypeDropdown(props: Props) {
|
|||
<Select
|
||||
name="metricType"
|
||||
placeholder="Select Card Type"
|
||||
options={options}
|
||||
value={options.find((i: any) => i.value === metric.metricType) || options[0]}
|
||||
options={DROPDOWN_OPTIONS}
|
||||
value={
|
||||
DROPDOWN_OPTIONS.find((i: any) => i.value === metric.metricType) || DROPDOWN_OPTIONS[0]
|
||||
}
|
||||
onChange={props.onSelect}
|
||||
// onSelect={onSelect}
|
||||
components={{
|
||||
SingleValue: ({ children, ...props }: any) => {
|
||||
const { data: { icon, label } } = props;
|
||||
const {
|
||||
data: { icon, label },
|
||||
} = props;
|
||||
return (
|
||||
<components.SingleValue {...props}>
|
||||
<div className="flex items-center">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { IconNames } from 'App/components/ui/SVG';
|
||||
import { FilterKey } from 'Types/filter/filterType';
|
||||
import { MetricType } from 'App/components/Dashboard/components/MetricTypeItem/MetricTypeItem';
|
||||
|
||||
export interface CardType {
|
||||
title: string;
|
||||
|
|
@ -23,6 +24,13 @@ export const RETENTION = 'retention';
|
|||
export const FEATURE_ADOPTION = 'featureAdoption';
|
||||
export const INSIGHTS = 'insights';
|
||||
|
||||
export interface Option {
|
||||
label: string;
|
||||
icon: string;
|
||||
value: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export const TYPES: CardType[] = [
|
||||
{
|
||||
title: 'Add from Library',
|
||||
|
|
@ -35,9 +43,7 @@ export const TYPES: CardType[] = [
|
|||
icon: 'puzzle-piece',
|
||||
description: 'Track the features that are being used the most.',
|
||||
slug: CLICKMAP,
|
||||
subTypes: [
|
||||
{ title: 'Visited URL', slug: FilterKey.CLICKMAP_URL, description: "" },
|
||||
]
|
||||
subTypes: [{ title: 'Visited URL', slug: FilterKey.CLICKMAP_URL, description: '' }],
|
||||
},
|
||||
{
|
||||
title: 'Timeseries',
|
||||
|
|
@ -230,3 +236,12 @@ export const TYPES: CardType[] = [
|
|||
slug: INSIGHTS,
|
||||
},
|
||||
];
|
||||
|
||||
export const DROPDOWN_OPTIONS = TYPES.filter((i: MetricType) => i.slug !== LIBRARY).map(
|
||||
(i: MetricType) => ({
|
||||
label: i.title,
|
||||
icon: i.icon,
|
||||
value: i.slug,
|
||||
description: i.description,
|
||||
})
|
||||
);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,14 @@ import {
|
|||
INSIGHTS,
|
||||
} from 'App/constants/card';
|
||||
import { clickmapFilter } from 'App/types/filter/newFilter';
|
||||
import { filterList, getRE } from 'App/utils';
|
||||
|
||||
interface MetricFilter {
|
||||
query?: string;
|
||||
showMine?: boolean;
|
||||
type?: string;
|
||||
dashboard?: [];
|
||||
}
|
||||
export default class MetricStore {
|
||||
isLoading: boolean = false;
|
||||
isSaving: boolean = false;
|
||||
|
|
@ -28,6 +35,8 @@ export default class MetricStore {
|
|||
metricsSearch: string = '';
|
||||
sort: any = { by: 'desc' };
|
||||
|
||||
filter: MetricFilter = { type: 'all', dashboard: [], query: '' };
|
||||
|
||||
sessionsPage: number = 1;
|
||||
sessionsPageSize: number = 10;
|
||||
listView?: boolean = true;
|
||||
|
|
@ -46,6 +55,22 @@ export default class MetricStore {
|
|||
);
|
||||
}
|
||||
|
||||
get filteredCards() {
|
||||
const filterRE = this.filter.query ? getRE(this.filter.query, 'i') : null;
|
||||
const dbIds = this.filter.dashboard ? this.filter.dashboard.map((i) => i.value) : [];
|
||||
return this.metrics
|
||||
.filter(
|
||||
(card) =>
|
||||
(this.filter.type === 'all' || card.metricType === this.filter.type) &&
|
||||
(!dbIds.length ||
|
||||
card.dashboards.map((i) => i.dashboardId).some((id) => dbIds.includes(id))) &&
|
||||
(!filterRE || ['name', 'owner'].some((key) => filterRE.test(card[key])))
|
||||
)
|
||||
.sort((a, b) =>
|
||||
this.sort.by === 'desc' ? b.lastModified - a.lastModified : a.lastModified - b.lastModified
|
||||
);
|
||||
}
|
||||
|
||||
// State Actions
|
||||
init(metric?: Widget | null) {
|
||||
this.instance.update(metric || new Widget());
|
||||
|
|
@ -80,14 +105,13 @@ export default class MetricStore {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
Object.assign(this.instance, obj);
|
||||
this.instance.updateKey('hasChanged', updateChangeFlag);
|
||||
}
|
||||
|
||||
changeType(value: string) {
|
||||
const obj: any = { metricType: value };
|
||||
obj.series = this.instance.series
|
||||
obj.series = this.instance.series;
|
||||
|
||||
obj['metricValue'] = [];
|
||||
|
||||
|
|
@ -117,7 +141,7 @@ export default class MetricStore {
|
|||
}
|
||||
|
||||
if (value === CLICKMAP) {
|
||||
obj.series = obj.series.slice(0, 1)
|
||||
obj.series = obj.series.slice(0, 1);
|
||||
if (this.instance.metricType !== CLICKMAP) {
|
||||
obj.series[0].filter.removeFilter(0);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue