change(ui) - cards - filters

This commit is contained in:
Shekar Siri 2023-01-13 12:56:35 +01:00
parent 1a1ec33d7d
commit eb4c07a876
7 changed files with 193 additions and 82 deletions

View file

@ -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>
);
}

View file

@ -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 = () => {

View file

@ -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}

View file

@ -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;

View file

@ -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">

View file

@ -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,
})
);

View file

@ -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);
}