diff --git a/frontend/app/components/Charts/utils.ts b/frontend/app/components/Charts/utils.ts index 621fe2b85..8a5454991 100644 --- a/frontend/app/components/Charts/utils.ts +++ b/frontend/app/components/Charts/utils.ts @@ -126,16 +126,11 @@ export function customTooltipFormatter(uuid: string) { if (partnerValue !== undefined) { const partnerColor = (window as any).__seriesColorMap?.[uuid]?.[partnerName] || '#999'; - str += `
-
-
-
${partnerName}
-
+ str += `
+
+ ${isPrevious ? 'Current' : 'Previous'} Total: +
${partnerValue ?? '—'}
${buildCompareTag(partnerValue, params.value)} diff --git a/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx b/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx index 348e1b388..567236851 100644 --- a/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx +++ b/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx @@ -1,7 +1,23 @@ import React, { useEffect, useState } from 'react'; import { Icon, Modal } from 'UI'; -import { Tooltip, Input, Button, Dropdown, Menu, Tag, Modal as AntdModal, Form, Avatar } from 'antd'; -import { TeamOutlined, LockOutlined, EditOutlined, DeleteOutlined, MoreOutlined } from '@ant-design/icons'; +import { + Tooltip, + Input, + Button, + Dropdown, + Menu, + Tag, + Modal as AntdModal, + Form, + Avatar, +} from 'antd'; +import { + TeamOutlined, + LockOutlined, + EditOutlined, + DeleteOutlined, + MoreOutlined, +} from '@ant-design/icons'; import { RouteComponentProps } from 'react-router-dom'; import { withSiteId } from 'App/routes'; import { TYPE_ICONS, TYPE_NAMES } from 'App/constants/card'; @@ -9,33 +25,45 @@ 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"; +import { EllipsisVertical } from 'lucide-react'; +import cn from 'classnames' interface Props extends RouteComponentProps { metric: any; siteId: string; - selected?: boolean; toggleSelection?: any; disableSelection?: boolean; renderColumn: string; + inLibrary?: boolean; } function MetricTypeIcon({ type }: any) { return ( {TYPE_NAMES[type]}
}> - } size="default" className="bg-tealx-lightest text-tealx mr-2 cursor-default avatar-card-list-item" /> + + } + size="default" + className="bg-tealx-lightest text-tealx mr-2 cursor-default avatar-card-list-item" + /> ); } const MetricListItem: React.FC = ({ - metric, - siteId, - toggleSelection = () => { - }, - disableSelection = false, - renderColumn - }) => { + metric, + siteId, + toggleSelection = () => {}, + disableSelection = false, + renderColumn, + inLibrary, +}) => { const history = useHistory(); const { metricStore } = useStore(); const [isEdit, setIsEdit] = useState(false); @@ -62,7 +90,7 @@ const MetricListItem: React.FC = ({ cancelText: 'No', onOk: async () => { await metricStore.delete(metric); - } + }, }); } if (key === 'rename') { @@ -127,29 +155,34 @@ const MetricListItem: React.FC = ({ } else if (diffDays <= 3) { return `${diffDays} days ago at ${formatTime(date)}`; } else { - return `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()} at ${formatTime(date)}`; + return `${date.getDate()}/${ + date.getMonth() + 1 + }/${date.getFullYear()} at ${formatTime(date)}`; } }; const menuItems = [ { - key: "rename", + key: 'rename', icon: , - label: "Rename" + label: 'Rename', }, { - key: "delete", + key: 'delete', icon: , - label: "Delete" - } - ] + label: 'Delete', + }, + ]; switch (renderColumn) { case 'title': return ( <> -
+
-
{metric.name}
+
{metric.name}
{renderModal()} @@ -160,7 +193,11 @@ const MetricListItem: React.FC = ({ return (
- {metric.isPublic ? : } + {metric.isPublic ? ( + + ) : ( + + )} {metric.isPublic ? 'Team' : 'Private'}
@@ -171,13 +208,18 @@ const MetricListItem: React.FC = ({ case 'options': return ( <> -
- -
{renderModal()} diff --git a/frontend/app/components/Dashboard/components/MetricsLibraryModal/MetricsLibraryModal.tsx b/frontend/app/components/Dashboard/components/MetricsLibraryModal/MetricsLibraryModal.tsx index a269fd915..0ef6a279e 100644 --- a/frontend/app/components/Dashboard/components/MetricsLibraryModal/MetricsLibraryModal.tsx +++ b/frontend/app/components/Dashboard/components/MetricsLibraryModal/MetricsLibraryModal.tsx @@ -5,6 +5,7 @@ import { Icon } from 'UI'; import { useStore } from 'App/mstore'; import { observer } from 'mobx-react-lite'; import FooterContent from './FooterContent'; +import { Input } from 'antd' interface Props { dashboardId?: number; @@ -46,7 +47,7 @@ function MetricsLibraryModal(props: Props) {
- +
@@ -61,12 +62,11 @@ export default observer(MetricsLibraryModal); function MetricSearch({ onChange }: any) { return (
- -
); diff --git a/frontend/app/components/Dashboard/components/MetricsList/ListView.tsx b/frontend/app/components/Dashboard/components/MetricsList/ListView.tsx index e830ffedc..e0a962f49 100644 --- a/frontend/app/components/Dashboard/components/MetricsList/ListView.tsx +++ b/frontend/app/components/Dashboard/components/MetricsList/ListView.tsx @@ -20,13 +20,14 @@ interface Props { list: Widget[]; siteId: string; selectedList: number[]; - toggleSelection?: (metricId: number) => void; + toggleSelection?: (metricId: number | Array) => void; toggleAll?: (e: any) => void; disableSelection?: boolean; allSelected?: boolean; existingCardIds?: number[]; showOwn?: boolean; toggleOwn: () => void; + inLibrary?: boolean; } const ListView: React.FC = (props: Props) => { @@ -36,10 +37,7 @@ const ListView: React.FC = (props: Props) => { selectedList, toggleSelection, disableSelection = false, - allSelected = false, - toggleAll, - showOwn, - toggleOwn, + inLibrary = false } = props; const [sorter, setSorter] = useState<{ field: string; order: 'ascend' | 'descend' }>({ field: 'lastModified', @@ -72,7 +70,7 @@ const ListView: React.FC = (props: Props) => { const paginatedData = useMemo(() => { const start = (pagination.current! - 1) * pagination.pageSize!; const end = start + pagination.pageSize!; - return sortedData.slice(start, end); + return sortedData.slice(start, end).map(metric => ({ ...metric, key: metric.metricId})); }, [sortedData, pagination]); const handleTableChange = ( @@ -90,19 +88,7 @@ const ListView: React.FC = (props: Props) => { const columns = [ { - title: ( -
- {!disableSelection && ( - - )} - Title -
- ), + title: 'Title', dataIndex: 'name', key: 'title', className: 'cap-first pl-4', @@ -113,12 +99,8 @@ const ListView: React.FC = (props: Props) => { key={metric.metricId} metric={metric} siteId={siteId} - disableSelection={disableSelection} - selected={selectedList.includes(metric.metricId)} - toggleSelection={(e: any) => { - e.stopPropagation(); - toggleSelection && toggleSelection(metric.metricId); - }} + inLibrary={inLibrary} + disableSelection={!inLibrary} renderColumn="title" /> ) @@ -128,7 +110,7 @@ const ListView: React.FC = (props: Props) => { dataIndex: 'owner', key: 'owner', className: 'capitalize', - width: '16.67%', + width: '25%', sorter: true, render: (text: string, metric: Metric) => ( = (props: Props) => { dataIndex: 'lastModified', key: 'lastModified', sorter: true, - width: '16.67%', + width: '25%', render: (text: string, metric: Metric) => ( = (props: Props) => { /> ) }, - { - title: '', - key: 'options', - className: 'text-right', - width: '5%', - render: (text: string, metric: Metric) => ( - - ) - } ]; + if (!inLibrary) { + columns.push({ + title: '', + key: 'options', + className: 'text-right', + width: '5%', + render: (text: string, metric: Metric) => ( + + ) + }) + } else { + columns.forEach(col => { + col.width = '31%'; + }) + } return ( = (props: Props) => { dataSource={paginatedData} rowKey="metricId" onChange={handleTableChange} + onRow={inLibrary ? (record) => ({ + onClick: () => disableSelection ? null : toggleSelection?.(record.metricId) + }) : undefined} rowSelection={ !disableSelection ? { - selectedRowKeys: selectedList.map((id: number) => id.toString()), + selectedRowKeys: selectedList, onChange: (selectedRowKeys) => { - selectedRowKeys.forEach((key: any) => { - toggleSelection && toggleSelection(parseInt(key)); - }); - } + toggleSelection(selectedRowKeys); + }, + columnWidth: 16, } : undefined } diff --git a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx index 7b8883f0b..0400e90a5 100644 --- a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx +++ b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx @@ -10,13 +10,15 @@ import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; function MetricsList({ siteId, onSelectionChange, + inLibrary, }: { siteId: string; onSelectionChange?: (selected: any[]) => void; + inLibrary?: boolean; }) { const { metricStore, dashboardStore } = useStore(); const metricsSearch = metricStore.filter.query; - const listView = metricStore.listView; + const listView = inLibrary ? true : metricStore.listView; const [selectedMetrics, setSelectedMetrics] = useState([]); const dashboard = dashboardStore.selectedDashboard; @@ -47,10 +49,14 @@ function MetricsList({ }, [selectedMetrics]); const toggleMetricSelection = (id: any) => { + if (Array.isArray(id)) { + setSelectedMetrics(id); + return + } if (selectedMetrics.includes(id)) { - setSelectedMetrics(selectedMetrics.filter((i: number) => i !== id)); + setSelectedMetrics((prev) => prev.filter((i: number) => i !== id)); } else { - setSelectedMetrics([...selectedMetrics, id]); + setSelectedMetrics((prev) => [...prev, id]); } }; @@ -89,6 +95,7 @@ function MetricsList({ disableSelection={!onSelectionChange} siteId={siteId} list={cards} + inLibrary={inLibrary} selectedList={selectedMetrics} existingCardIds={existingCardIds} toggleSelection={toggleMetricSelection} diff --git a/frontend/app/components/Dashboard/components/WidgetDatatable/WidgetDatatable.tsx b/frontend/app/components/Dashboard/components/WidgetDatatable/WidgetDatatable.tsx index d4dfea6cc..08637a70d 100644 --- a/frontend/app/components/Dashboard/components/WidgetDatatable/WidgetDatatable.tsx +++ b/frontend/app/components/Dashboard/components/WidgetDatatable/WidgetDatatable.tsx @@ -4,7 +4,7 @@ import type { TableProps } from 'antd'; import { Eye, EyeOff } from 'lucide-react'; import cn from 'classnames'; import React, { useState } from 'react'; -import { TableExporter } from '../../../Funnels/FunnelWidget/FunnelTable'; +import { TableExporter } from 'Components/Funnels/FunnelWidget/FunnelTable'; const initTableProps = [ { @@ -62,15 +62,7 @@ function WidgetDatatable(props: Props) { const [showTable, setShowTable] = useState(props.defaultOpen); const [tableData, setTableData] = useState([]); - const columnNames = new Set(); - /** - * basically we have an array of - * { time: some_date, series1: 1, series2: 2, series3: 3, timestamp: 123456 } - * which we turn into a table where each series of filters = row; - * and each unique time = column - * + average for each row - * [ { seriesName: 'series1', mon: 1, tue: 2, wed: 3, average: 2 }, ... ] - * */ + const columnNames = []; const series = !data.chart[0] ? [] : data.namesMap; @@ -78,15 +70,16 @@ function WidgetDatatable(props: Props) { React.useEffect(() => { if (!data.chart) return; setTableProps(initTableProps); - columnNames.clear(); - data.chart.forEach((p: any) => { - columnNames.add(p.time); - }); // for example: mon, tue, wed, thu, fri, sat, sun - const avg: any = {}; // { seriesName: {itemsCount: 0, total: 0} } - const items: Record[] = []; // as many items (rows) as we have series in filter + columnNames.length = data.chart.length; + // for example: mon, tue, wed, thu, fri, sat, sun + data.chart.forEach((p: any, i) => { + columnNames[i] = p.time; + }); + + // as many items (rows) as we have series in filter + const items: Record[] = []; series.forEach((s, i) => { items.push({ seriesName: s, average: 0, key: s }); - avg[s] = { itemsCount: 0, total: 0 }; }); const tableCols: { title: React.ReactNode; @@ -94,27 +87,30 @@ function WidgetDatatable(props: Props) { key: string; sorter: any; }[] = []; - const uniqueColArr = Array.from(columnNames); - uniqueColArr.forEach((name: string, i) => { + columnNames.forEach((name: string, i) => { tableCols.push({ title: {name}, - dataIndex: name, - key: name, - sorter: (a, b) => a[name] - b[name], + dataIndex: name+'_'+i, + key: name+'_'+i, + sorter: (a, b) => a[name+'_'+i] - b[name+'_'+i], }); - const values = data.chart.filter((p) => p.time === name); + const values = data.chart[i]; series.forEach((s) => { - avg[s].itemsCount += 1; - avg[s].total += values.reduce((acc, curr) => acc + curr[s], 0); const ind = items.findIndex((item) => item.seriesName === s); if (ind === -1) return; - items[ind][name] = values.reduce((acc, curr) => acc + curr[s], 0); + items[ind][name+'_'+i] = values[s]; }); }); - Object.keys(avg).forEach((key) => { - const ind = items.findIndex((item) => item.seriesName === key); - if (ind === -1) return; - items[ind].average = (avg[key].total / avg[key].itemsCount).toFixed(2); + // calculating averages for each row + items.forEach((item) => { + const itemsLen = columnNames.length; + const keys = Object.keys(item).filter(k => !['seriesName', 'key', 'average'].includes(k)); + let sum = 0; + const values = keys.map(k => item[k]); + values.forEach((v) => { + sum += v; + }); + item.average = (sum / itemsLen).toFixed(1); }); setTableProps((prev) => [...prev, ...tableCols]);