fix(ui): metric update in the list

This commit is contained in:
Shekar Siri 2025-02-04 18:23:27 +01:00
parent c59dbbc79d
commit de0c10de56

View file

@ -1,56 +1,55 @@
import React, { useState, useMemo } from 'react';
import { Checkbox, Table, Typography, Switch, Tag, Tooltip } from 'antd';
import MetricListItem from '../MetricListItem';
import {
Table,
Typography,
Tag,
Tooltip,
Input,
Button,
Dropdown,
Modal as AntdModal,
Avatar
} from 'antd';
import { TeamOutlined, LockOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
import { EllipsisVertical } from 'lucide-react';
import { TablePaginationConfig, SorterResult } from 'antd/lib/table/interface';
import { useStore } from 'App/mstore';
import { toast } from 'react-toastify';
import { useHistory } from 'react-router';
import { withSiteId } from 'App/routes';
import { Icon } from 'UI';
import cn from 'classnames';
import { TYPE_ICONS, TYPE_NAMES } from 'App/constants/card';
import Widget from 'App/mstore/types/widget';
import { LockOutlined, TeamOutlined } from "@ant-design/icons";
import classNames from 'classnames';
const { Text } = Typography;
interface Metric {
metricId: number;
name: string;
owner: string;
lastModified: string;
visibility: string;
}
interface Props {
list: Widget[];
siteId: string;
selectedList: number[];
toggleSelection?: (metricId: number | Array<number>) => void;
toggleAll?: (e: any) => void;
toggleSelection?: (metricId: number | number[]) => void;
disableSelection?: boolean;
allSelected?: boolean;
existingCardIds?: number[];
showOwn?: boolean;
toggleOwn: () => void;
inLibrary?: boolean;
}
const ListView: React.FC<Props> = (props: Props) => {
const {
siteId,
list,
selectedList,
toggleSelection,
disableSelection = false,
inLibrary = false
} = props;
const ListView: React.FC<Props> = ({
list,
siteId,
selectedList,
toggleSelection,
disableSelection = false,
inLibrary = false
}) => {
const [sorter, setSorter] = useState<{ field: string; order: 'ascend' | 'descend' }>({
field: 'lastModified',
order: 'descend'
});
const [pagination, setPagination] = useState<TablePaginationConfig>({ current: 1, pageSize: 10 });
const totalMessage = (
<>
Showing <Text strong>{pagination.pageSize * (pagination.current - 1) + 1}</Text> to <Text
strong>{Math.min(pagination.pageSize * pagination.current, list.length)}</Text> of <Text
strong>{list.length}</Text> cards
</>
);
const [editingMetricId, setEditingMetricId] = useState<number | null>(null);
const [newName, setNewName] = useState('');
const { metricStore } = useStore();
const history = useHistory();
const sortedData = useMemo(() => {
return [...list].sort((a, b) => {
@ -59,33 +58,164 @@ const ListView: React.FC<Props> = (props: Props) => {
? new Date(a.lastModified).getTime() - new Date(b.lastModified).getTime()
: new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime();
} else if (sorter.field === 'name') {
return sorter.order === 'ascend' ? a.name?.localeCompare(b.name) : b.name?.localeCompare(a.name);
return sorter.order === 'ascend'
? (a.name?.localeCompare(b.name) || 0)
: (b.name?.localeCompare(a.name) || 0);
} else if (sorter.field === 'owner') {
return sorter.order === 'ascend' ? a.owner?.localeCompare(b.owner) : b.owner?.localeCompare(a.owner);
return sorter.order === 'ascend'
? (a.owner?.localeCompare(b.owner) || 0)
: (b.owner?.localeCompare(a.owner) || 0);
}
return 0;
});
}, [list, sorter]);
const paginatedData = useMemo(() => {
const start = (pagination.current! - 1) * pagination.pageSize!;
const end = start + pagination.pageSize!;
return sortedData.slice(start, end).map(metric => ({ ...metric, key: metric.metricId}));
const start = ((pagination.current || 1) - 1) * (pagination.pageSize || 10);
return sortedData.slice(start, start + (pagination.pageSize || 10));
}, [sortedData, pagination]);
const totalMessage = (
<>
Showing{' '}
<Text strong>
{(pagination.pageSize || 10) * ((pagination.current || 1) - 1) + 1}
</Text>{' '}
to{' '}
<Text strong>
{Math.min((pagination.pageSize || 10) * (pagination.current || 1), list.length)}
</Text>{' '}
of <Text strong>{list.length}</Text> cards
</>
);
const handleTableChange = (
pagination: TablePaginationConfig,
filters: Record<string, (string | number | boolean)[] | null>,
sorter: SorterResult<Metric> | SorterResult<Metric>[]
pag: TablePaginationConfig,
_filters: Record<string, (string | number | boolean)[] | null>,
sorterParam: SorterResult<Widget> | SorterResult<Widget>[]
) => {
const sortResult = sorter as SorterResult<Metric>;
const sortRes = sorterParam as SorterResult<Widget>;
setSorter({
field: sortResult.field as string,
order: sortResult.order as 'ascend' | 'descend'
field: sortRes.field as string,
order: sortRes.order as 'ascend' | 'descend'
});
setPagination(pagination);
setPagination(pag);
};
const parseDate = (dateString: string) => {
let date = new Date(dateString);
if (isNaN(date.getTime())) {
date = new Date(parseInt(dateString, 10));
}
return date;
};
const formatDate = (date: Date) => {
const now = new Date();
const diffTime = Math.abs(now.getTime() - date.getTime());
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
const formatTime = (d: Date) => {
let hours = d.getHours();
const minutes = d.getMinutes().toString().padStart(2, '0');
const ampm = hours >= 12 ? 'PM' : 'AM';
hours = hours % 12 || 12;
return `${hours}:${minutes} ${ampm}`;
};
if (diffDays <= 1) return `Today at ${formatTime(date)}`;
if (diffDays === 2) return `Yesterday at ${formatTime(date)}`;
if (diffDays <= 3) return `${diffDays} days ago at ${formatTime(date)}`;
return `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()} at ${formatTime(date)}`;
};
const MetricTypeIcon: React.FC<{ type: string }> = ({ type }) => (
<Tooltip title={<div className="capitalize">{TYPE_NAMES[type]}</div>}>
<Avatar
src={
<Icon name={TYPE_ICONS[type]} size="16" color="tealx" strokeColor="tealx" />
}
size="default"
className="bg-tealx-lightest text-tealx mr-2 cursor-default avatar-card-list-item"
/>
</Tooltip>
);
const onItemClick = (metric: Widget) => {
if (disableSelection) return;
if (toggleSelection) {
toggleSelection(metric.metricId);
} else {
const path = withSiteId(`/metrics/${metric.metricId}`, siteId);
history.push(path);
}
};
const onMenuClick = async (metric: Widget, { key }: { key: string }) => {
if (key === 'delete') {
AntdModal.confirm({
title: 'Confirm',
content: 'Are you sure you want to permanently delete this card?',
okText: 'Yes, delete',
cancelText: 'No',
onOk: async () => {
await metricStore.delete(metric);
}
});
}
if (key === 'rename') {
setEditingMetricId(metric.metricId);
setNewName(metric.name);
}
};
const onRename = async () => {
const metric = list.find((m) => m.metricId === editingMetricId);
if (!metric) return;
try {
metric.update({ name: newName });
await metricStore.save(metric);
// await metricStore.fetchList();
setEditingMetricId(null);
} catch (e) {
toast.error('Failed to rename card');
}
};
const menuItems = [
{ key: 'rename', icon: <EditOutlined />, label: 'Rename' },
{ key: 'delete', icon: <DeleteOutlined />, label: 'Delete' }
];
const renderTitle = (_text: string, metric: Widget) => (
<div className="flex items-center cursor-pointer" onClick={() => onItemClick(metric)}>
<MetricTypeIcon type={metric.metricType} />
<div className={cn('capitalize-first block', !inLibrary ? 'link' : '')}>
{metric.name}
</div>
</div>
);
const renderOwner = (_text: string, metric: Widget) => <div>{metric.owner}</div>;
const renderLastModified = (_text: string, metric: Widget) => {
const date = parseDate(metric.lastModified);
return formatDate(date);
};
const renderOptions = (_text: string, metric: Widget) => (
<div className="flex justify-end pr-4">
<Dropdown
menu={{ items: menuItems, onClick: (e) => onMenuClick(metric, e) }}
trigger={['click']}
>
<Button
icon={<EllipsisVertical size={16} />}
className="btn-cards-list-item-more-options"
type="text"
/>
</Dropdown>
</div>
);
const columns = [
{
title: 'Title',
@ -93,103 +223,89 @@ const ListView: React.FC<Props> = (props: Props) => {
key: 'title',
className: 'cap-first pl-4',
sorter: true,
width: '25%',
render: (text: string, metric: Metric) => (
<MetricListItem
key={metric.metricId}
metric={metric}
siteId={siteId}
inLibrary={inLibrary}
disableSelection={!inLibrary}
renderColumn="title"
/>
)
width: inLibrary ? '31%' : '25%',
render: renderTitle
},
{
title: 'Owner',
dataIndex: 'owner',
key: 'owner',
className: 'capitalize',
width: '25%',
sorter: true,
render: (text: string, metric: Metric) => (
<MetricListItem
key={metric.metricId}
metric={metric}
siteId={siteId}
renderColumn="owner"
/>
)
width: inLibrary ? '31%' : '25%',
render: renderOwner
},
{
title: 'Last Modified',
dataIndex: 'lastModified',
key: 'lastModified',
sorter: true,
width: '25%',
render: (text: string, metric: Metric) => (
<MetricListItem
key={metric.metricId}
metric={metric}
siteId={siteId}
renderColumn="lastModified"
/>
)
},
width: inLibrary ? '31%' : '25%',
render: renderLastModified
}
];
if (!inLibrary) {
columns.push({
title: '',
key: 'options',
className: 'text-right',
width: '5%',
render: (text: string, metric: Metric) => (
<MetricListItem
key={metric.metricId}
metric={metric}
siteId={siteId}
renderColumn="options"
/>
)
})
} else {
columns.forEach(col => {
col.width = '31%';
})
title: '',
key: 'options',
className: 'text-right',
width: '5%',
render: renderOptions
});
}
return (
<Table
columns={columns}
dataSource={paginatedData}
rowKey="metricId"
onChange={handleTableChange}
onRow={inLibrary ? (record) => ({
onClick: () => disableSelection ? null : toggleSelection?.(record.metricId)
}) : undefined}
rowSelection={
!disableSelection
? {
selectedRowKeys: selectedList,
onChange: (selectedRowKeys) => {
toggleSelection(selectedRowKeys);
},
columnWidth: 16,
}
: undefined
}
pagination={{
current: pagination.current,
pageSize: pagination.pageSize,
total: sortedData.length,
showSizeChanger: false,
className: 'px-4',
showLessItems: true,
showTotal: () => totalMessage,
size: 'small',
simple: 'true',
}}
/>
<>
<Table
columns={columns}
dataSource={paginatedData}
rowKey="metricId"
onChange={handleTableChange}
onRow={
inLibrary
? (record) => ({
onClick: () => {
if (!disableSelection) toggleSelection?.(record.metricId);
}
})
: undefined
}
rowSelection={
!disableSelection
? {
selectedRowKeys: selectedList,
onChange: (keys) => toggleSelection && toggleSelection(keys),
columnWidth: 16
}
: undefined
}
pagination={{
current: pagination.current,
pageSize: pagination.pageSize,
total: sortedData.length,
showSizeChanger: false,
className: 'px-4',
showLessItems: true,
showTotal: () => totalMessage,
size: 'small',
simple: true
}}
/>
<AntdModal
title="Rename Card"
open={editingMetricId !== null}
okText="Save"
cancelText="Cancel"
onOk={onRename}
onCancel={() => setEditingMetricId(null)}
>
<Input
placeholder="Enter new card title"
value={newName}
onChange={(e) => setNewName(e.target.value)}
/>
</AntdModal>
</>
);
};