diff --git a/src/apps/dashboard/routes/activity/index.tsx b/src/apps/dashboard/routes/activity/index.tsx
index fa11a45bd3ee..0bee4bc81cd0 100644
--- a/src/apps/dashboard/routes/activity/index.tsx
+++ b/src/apps/dashboard/routes/activity/index.tsx
@@ -1,13 +1,11 @@
import parseISO from 'date-fns/parseISO';
-import React, { useCallback, useEffect, useMemo, useState } from 'react';
-import type { ActivityLogEntry } from '@jellyfin/sdk/lib/generated-client/models/activity-log-entry';
+import React, { useCallback, useMemo, useState } from 'react';
import { LogLevel } from '@jellyfin/sdk/lib/generated-client/models/log-level';
+import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order';
import { useTheme } from '@mui/material/styles';
import ToggleButton from '@mui/material/ToggleButton';
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
-import { type MRT_ColumnDef, type MRT_Theme, useMaterialReactTable } from 'material-react-table';
-import { useSearchParams } from 'react-router-dom';
-
+import { type MRT_ColumnDef, type MRT_Theme, type MRT_ColumnFiltersState, type MRT_SortingState, useMaterialReactTable } from 'material-react-table';
import DateTimeCell from 'apps/dashboard/components/table/DateTimeCell';
import TablePage, { DEFAULT_TABLE_OPTIONS } from 'apps/dashboard/components/table/TablePage';
import { useLogEntries } from 'apps/dashboard/features/activity/api/useLogEntries';
@@ -18,10 +16,10 @@ import UserAvatarButton from 'apps/dashboard/components/UserAvatarButton';
import type { ActivityLogEntryCell } from 'apps/dashboard/features/activity/types/ActivityLogEntryCell';
import { type UsersRecords, useUsersDetails } from 'hooks/useUsers';
import globalize from 'lib/globalize';
-import { toBoolean } from 'utils/string';
+import type { ActivityLogEntry } from '@jellyfin/sdk/lib/generated-client/models/activity-log-entry';
+import { ActivityLogSortBy } from '@jellyfin/sdk/lib/generated-client/models/activity-log-sort-by';
const DEFAULT_PAGE_SIZE = 25;
-const VIEW_PARAM = 'useractivity';
const enum ActivityView {
All = 'All',
@@ -29,12 +27,6 @@ const enum ActivityView {
System = 'System'
}
-const getActivityView = (param: string | null) => {
- if (param === null) return ActivityView.All;
- if (toBoolean(param)) return ActivityView.User;
- return ActivityView.System;
-};
-
const getUserCell = (users: UsersRecords) => function UserCell({ row }: ActivityLogEntryCell) {
return (
@@ -42,12 +34,13 @@ const getUserCell = (users: UsersRecords) => function UserCell({ row }: Activity
};
export const Component = () => {
- const [ searchParams, setSearchParams ] = useSearchParams();
+ const [columnFilters, setColumnFilters] = useState([]);
+ const [activityView, setActivityView] = useState(
+ 'All');
- const [ activityView, setActivityView ] = useState(
- getActivityView(searchParams.get(VIEW_PARAM)));
+ const [sorting, setSorting] = useState([{ id: 'Date', desc: true }]);
- const [ pagination, setPagination ] = useState({
+ const [pagination, setPagination] = useState({
pageIndex: 0,
pageSize: DEFAULT_PAGE_SIZE
});
@@ -58,19 +51,56 @@ export const Component = () => {
const UserCell = getUserCell(users);
- const activityParams = useMemo(() => ({
- startIndex: pagination.pageIndex * pagination.pageSize,
- limit: pagination.pageSize,
- hasUserId: activityView !== ActivityView.All ? activityView === ActivityView.User : undefined
- }), [activityView, pagination.pageIndex, pagination.pageSize]);
+ const activityParams = useMemo(() => {
+ const getFilter = (id: string) => columnFilters.find(f => f.id === id)?.value;
+ const sortFields: ActivityLogSortBy[] = [];
+ const sortOrders: SortOrder[] = [];
+
+ const mapSortField = (id: string): ActivityLogSortBy => {
+ switch (id) {
+ case 'Date': return ActivityLogSortBy.DateCreated;
+ case 'Severity': return ActivityLogSortBy.LogSeverity;
+ case 'Name': return ActivityLogSortBy.Name;
+ case 'Type': return ActivityLogSortBy.Type;
+ case 'Overview': return ActivityLogSortBy.ShortOverview;
+ case 'User': return ActivityLogSortBy.Username;
+ default: return ActivityLogSortBy.DateCreated;
+ }
+ };
+
+ if (sorting.length === 0) {
+ sortFields.push(ActivityLogSortBy.DateCreated);
+ sortOrders.push(SortOrder.Descending);
+ } else {
+ sorting.forEach(sort => {
+ sortFields.push(mapSortField(sort.id));
+ sortOrders.push(sort.desc ? SortOrder.Descending : SortOrder.Ascending);
+ });
+ }
+
+ return {
+ startIndex: pagination.pageIndex * pagination.pageSize,
+ limit: pagination.pageSize,
+
+ name: getFilter('Name') as string || undefined,
+ type: getFilter('Type') as string || undefined,
+ shortOverview: getFilter('Overview') as string || undefined,
+ username: getFilter('User') as string || undefined,
+ severity: getFilter('Severity') as LogLevel || undefined,
+ minDate: (getFilter('Date') as string[] | undefined)?.[0] ?? undefined,
+ maxDate: (getFilter('Date') as string[] | undefined)?.[1] ?? undefined,
+ sortBy: sortFields,
+ sortOrder: sortOrders
+ };
+ }, [pagination, columnFilters, sorting]);
const { data, isLoading: isLogEntriesLoading } = useLogEntries(activityParams);
const logEntries = useMemo(() => (
data?.Items || []
- ), [ data ]);
+ ), [data]);
const rowCount = useMemo(() => (
data?.TotalRecordCount || 0
- ), [ data ]);
+ ), [data]);
const isLoading = isUsersLoading || isLogEntriesLoading;
@@ -79,15 +109,15 @@ export const Component = () => {
id: 'User',
accessorFn: row => row.UserId && users[row.UserId]?.Name,
header: globalize.translate('LabelUser'),
- size: 75,
+ size: 100,
Cell: UserCell,
enableResizing: false,
muiTableBodyCellProps: {
align: 'center'
},
- filterVariant: 'multi-select',
+ filterVariant: 'select',
filterSelectOptions: userNames
- }], [ activityView, userNames, users, UserCell ]);
+ }], [activityView, userNames, users, UserCell]);
const columns = useMemo[]>(() => [
{
@@ -96,7 +126,9 @@ export const Component = () => {
header: globalize.translate('LabelTime'),
size: 160,
Cell: DateTimeCell,
- filterVariant: 'datetime-range'
+ filterVariant: 'datetime-range',
+ grow: true,
+ maxSize: 320
},
{
accessorKey: 'Severity',
@@ -107,26 +139,34 @@ export const Component = () => {
muiTableBodyCellProps: {
align: 'center'
},
- filterVariant: 'multi-select',
- filterSelectOptions: Object.values(LogLevel).map(level => globalize.translate(`LogLevel.${level}`))
+ filterVariant: 'select',
+ filterSelectOptions: Object.values(LogLevel).map(level => ({
+ text: globalize.translate(`LogLevel.${level}`),
+ value: level
+ }))
},
...userColumn,
{
accessorKey: 'Name',
header: globalize.translate('LabelName'),
- size: 270
+ size: 270,
+ grow: true
},
{
id: 'Overview',
accessorFn: row => row.ShortOverview || row.Overview,
header: globalize.translate('LabelOverview'),
size: 170,
- Cell: OverviewCell
+ Cell: OverviewCell,
+ grow: true,
+ maxSize: 220
},
{
accessorKey: 'Type',
header: globalize.translate('LabelType'),
- size: 150
+ size: 150,
+ grow: true,
+ maxSize: 220
},
{
id: 'Actions',
@@ -139,7 +179,7 @@ export const Component = () => {
enableResizing: false,
enableSorting: false
}
- ], [ userColumn ]);
+ ], [userColumn]);
const onViewChange = useCallback((_e: React.MouseEvent, newView: ActivityView | null) => {
if (newView !== null) {
@@ -147,23 +187,11 @@ export const Component = () => {
}
}, []);
- useEffect(() => {
- const currentViewParam = getActivityView(searchParams.get(VIEW_PARAM));
- if (currentViewParam !== activityView) {
- if (activityView === ActivityView.All) {
- searchParams.delete(VIEW_PARAM);
- } else {
- searchParams.set(VIEW_PARAM, `${activityView === ActivityView.User}`);
- }
- setSearchParams(searchParams);
- }
- }, [ activityView, searchParams, setSearchParams ]);
-
// NOTE: We need to provide a custom theme due to a MRT bug causing the initial theme to always be used
// https://github.com/KevinVandy/material-react-table/issues/1429
const mrtTheme = useMemo>(() => ({
baseBackgroundColor: theme.palette.background.paper
- }), [ theme ]);
+ }), [theme]);
const table = useMaterialReactTable({
...DEFAULT_TABLE_OPTIONS,
@@ -178,9 +206,18 @@ export const Component = () => {
},
state: {
isLoading,
- pagination
+ columnFilters,
+ pagination,
+ sorting
},
+ manualFiltering: true,
+ manualSorting: true,
+ onColumnFiltersChange: setColumnFilters,
+ onSortingChange: setSorting,
+ enableMultiSort: true,
+ enableGlobalFilter: false,
+
// Server pagination
manualPagination: true,
onPaginationChange: setPagination,