openreplay/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx
Shekar Siri 687ab05f22 feat(metrics): implement server-side pagination and sorting
Refactors metrics list view to use server-side pagination and sorting
instead of client-side implementation. This improves performance for
large datasets by reducing client workload and network payload size.

Key changes:
- Add pagination API endpoint in MetricService
- Update MetricStore to handle server pagination
- Refactor ListView component to use server-side sorting
- Remove client-side sorting and pagination logic
2025-03-10 14:58:30 +01:00

143 lines
4.4 KiB
TypeScript

import { observer } from 'mobx-react-lite';
import React, { useEffect, useMemo, useState } from 'react';
import { NoContent, Loader, Pagination } from 'UI';
import { useStore } from 'App/mstore';
import { sliceListPerPage } from 'App/utils';
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
import { Popover, Button } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import ListView from './ListView';
import AddCardSection from '../AddCardSection/AddCardSection';
import { useTranslation } from 'react-i18next';
function MetricsList({
siteId,
onSelectionChange,
inLibrary
}: {
siteId: string;
onSelectionChange?: (selected: any[]) => void;
inLibrary?: boolean;
}) {
const { t } = useTranslation();
const { metricStore, dashboardStore } = useStore();
const metricsSearch = metricStore.filter.query;
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, existingCardIds, onSelectionChange]
);
const loading = metricStore.isLoading;
useEffect(() => {
void metricStore.fetchList();
}, [metricStore.page, metricStore.filter, metricStore.sort]);
useEffect(() => {
if (!onSelectionChange) return;
onSelectionChange(selectedMetrics);
}, [selectedMetrics, onSelectionChange]);
const toggleMetricSelection = (id: any) => {
if (Array.isArray(id)) {
setSelectedMetrics(id);
return;
}
if (selectedMetrics.includes(id)) {
setSelectedMetrics((prev: number[]) => prev.filter((i) => i !== id));
} else {
setSelectedMetrics((prev: number[]) => [...prev, id]);
}
};
const { length } = cards;
useEffect(() => {
metricStore.updateKey('sessionsPage', 1);
}, [metricStore]);
const isFiltered = metricStore.filter.query !== '' || metricStore.filter.type !== 'all';
const searchImageDimensions = { width: 60, height: 'auto' };
const defaultImageDimensions = { width: 600, height: 'auto' };
const emptyImage = isFiltered ? ICONS.NO_RESULTS : ICONS.NO_CARDS;
const imageDimensions = isFiltered
? searchImageDimensions
: defaultImageDimensions;
return (
<NoContent
show={!loading && length === 0}
title={
<div className="flex flex-col items-center justify-center">
<AnimatedSVG name={emptyImage} size={imageDimensions.width} />
<div className="text-center mt-3 text-lg font-medium">
{isFiltered
? t('No matching results')
: t('Unlock insights with data cards')}
</div>
</div>
}
subtext={
isFiltered ? (
''
) : (
<div className="flex flex-col items-center">
<div>
{t('Create and customize cards to analyze trends and user behavior effectively.')}
</div>
<Popover
arrow={false}
overlayInnerStyle={{ padding: 0, borderRadius: '0.75rem' }}
content={<AddCardSection fit inCards />}
trigger="click"
>
<Button
type="primary"
icon={<PlusOutlined />}
className="btn-create-card mt-3"
>
{t('Create Card')}
</Button>
</Popover>
</div>
)
}
>
<ListView
loading={loading}
disableSelection={!onSelectionChange}
siteId={siteId}
list={cards}
inLibrary={inLibrary}
selectedList={selectedMetrics}
// existingCardIds={existingCardIds}
toggleSelection={toggleMetricSelection}
// allSelected={cards.length === selectedMetrics.length}
// toggleAll={({ target: { checked } }) =>
// setSelectedMetrics(
// checked
// ? cards
// .map((i: any) => i.metricId)
// .slice(0, 30 - (existingCardIds?.length || 0))
// : []
// )
// }
/>
</NoContent>
);
}
export default observer(MetricsList);