mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Refactor cell components
This commit is contained in:
parent
c7ed7ed48f
commit
cb906678e6
8 changed files with 103 additions and 52 deletions
|
@ -4,7 +4,7 @@ import type { Api } from '@jellyfin/sdk';
|
||||||
import { getActivityLogApi } from '@jellyfin/sdk/lib/utils/api/activity-log-api';
|
import { getActivityLogApi } from '@jellyfin/sdk/lib/utils/api/activity-log-api';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { useApi } from './useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
|
|
||||||
const fetchLogEntries = async (
|
const fetchLogEntries = async (
|
||||||
api?: Api,
|
api?: Api,
|
|
@ -0,0 +1,22 @@
|
||||||
|
import IconButton from '@mui/material/IconButton/IconButton';
|
||||||
|
import PermMedia from '@mui/icons-material/PermMedia';
|
||||||
|
import React, { type FC } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import type { ActivityLogEntryCell } from 'apps/dashboard/features/activity/types/ActivityLogEntryCell';
|
||||||
|
import globalize from 'lib/globalize';
|
||||||
|
|
||||||
|
const ActionsCell: FC<ActivityLogEntryCell> = ({ row }) => (
|
||||||
|
row.original.ItemId ? (
|
||||||
|
<IconButton
|
||||||
|
size='large'
|
||||||
|
title={globalize.translate('LabelMediaDetails')}
|
||||||
|
component={Link}
|
||||||
|
to={`/details?id=${row.original.ItemId}`}
|
||||||
|
>
|
||||||
|
<PermMedia fontSize='inherit' />
|
||||||
|
</IconButton>
|
||||||
|
) : undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ActionsCell;
|
|
@ -0,0 +1,13 @@
|
||||||
|
import type { LogLevel } from '@jellyfin/sdk/lib/generated-client/models/log-level';
|
||||||
|
import React, { type FC } from 'react';
|
||||||
|
|
||||||
|
import { ActivityLogEntryCell } from '../types/ActivityLogEntryCell';
|
||||||
|
import LogLevelChip from './LogLevelChip';
|
||||||
|
|
||||||
|
const LogLevelCell: FC<ActivityLogEntryCell> = ({ cell }) => (
|
||||||
|
cell.getValue<LogLevel | undefined>() ? (
|
||||||
|
<LogLevelChip level={cell.getValue<LogLevel>()} />
|
||||||
|
) : undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
export default LogLevelCell;
|
|
@ -1,12 +1,14 @@
|
||||||
import type { ActivityLogEntry } from '@jellyfin/sdk/lib/generated-client/models/activity-log-entry';
|
|
||||||
import Info from '@mui/icons-material/Info';
|
import Info from '@mui/icons-material/Info';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import ClickAwayListener from '@mui/material/ClickAwayListener';
|
import ClickAwayListener from '@mui/material/ClickAwayListener';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import Tooltip from '@mui/material/Tooltip';
|
import Tooltip from '@mui/material/Tooltip';
|
||||||
import React, { FC, useCallback, useState } from 'react';
|
import React, { type FC, useCallback, useState } from 'react';
|
||||||
|
|
||||||
const OverviewCell: FC<ActivityLogEntry> = ({ Overview, ShortOverview }) => {
|
import type { ActivityLogEntryCell } from '../types/ActivityLogEntryCell';
|
||||||
|
|
||||||
|
const OverviewCell: FC<ActivityLogEntryCell> = ({ row }) => {
|
||||||
|
const { ShortOverview, Overview } = row.original;
|
||||||
const displayValue = ShortOverview ?? Overview;
|
const displayValue = ShortOverview ?? Overview;
|
||||||
const [ open, setOpen ] = useState(false);
|
const [ open, setOpen ] = useState(false);
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
import type { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto';
|
||||||
|
import IconButton from '@mui/material/IconButton/IconButton';
|
||||||
|
import React, { type FC } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import UserAvatar from 'components/UserAvatar';
|
||||||
|
|
||||||
|
interface UserAvatarButtonProps {
|
||||||
|
user?: UserDto
|
||||||
|
}
|
||||||
|
|
||||||
|
const UserAvatarButton: FC<UserAvatarButtonProps> = ({ user }) => (
|
||||||
|
user?.Id ? (
|
||||||
|
<IconButton
|
||||||
|
size='large'
|
||||||
|
color='inherit'
|
||||||
|
sx={{ padding: 0 }}
|
||||||
|
title={user.Name || undefined}
|
||||||
|
component={Link}
|
||||||
|
to={`/dashboard/users/profile?userId=${user.Id}`}
|
||||||
|
>
|
||||||
|
<UserAvatar user={user} />
|
||||||
|
</IconButton>
|
||||||
|
) : undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
export default UserAvatarButton;
|
|
@ -0,0 +1,7 @@
|
||||||
|
import type { ActivityLogEntry } from '@jellyfin/sdk/lib/generated-client/models/activity-log-entry';
|
||||||
|
import type { MRT_Cell, MRT_Row } from 'material-react-table';
|
||||||
|
|
||||||
|
export interface ActivityLogEntryCell {
|
||||||
|
cell: MRT_Cell<ActivityLogEntry>
|
||||||
|
row: MRT_Row<ActivityLogEntry>
|
||||||
|
}
|
|
@ -1,34 +1,34 @@
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import type { ActivityLogEntry } from '@jellyfin/sdk/lib/generated-client/models/activity-log-entry';
|
import type { ActivityLogEntry } from '@jellyfin/sdk/lib/generated-client/models/activity-log-entry';
|
||||||
import { LogLevel } from '@jellyfin/sdk/lib/generated-client/models/log-level';
|
|
||||||
import type { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto';
|
import type { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto';
|
||||||
import PermMedia from '@mui/icons-material/PermMedia';
|
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import IconButton from '@mui/material/IconButton';
|
|
||||||
import ToggleButton from '@mui/material/ToggleButton';
|
import ToggleButton from '@mui/material/ToggleButton';
|
||||||
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
|
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import { type MRT_Cell, type MRT_ColumnDef, type MRT_Row, MaterialReactTable, useMaterialReactTable } from 'material-react-table';
|
import { type MRT_ColumnDef, MaterialReactTable, useMaterialReactTable } from 'material-react-table';
|
||||||
import { Link, useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { useLogEntires } from 'apps/dashboard/features/activity/api/useLogEntries';
|
||||||
|
import ActionsCell from 'apps/dashboard/features/activity/components/ActionsCell';
|
||||||
|
import LogLevelCell from 'apps/dashboard/features/activity/components/LogLevelCell';
|
||||||
|
import OverviewCell from 'apps/dashboard/features/activity/components/OverviewCell';
|
||||||
|
import UserAvatarButton from 'apps/dashboard/features/activity/components/UserAvatarButton';
|
||||||
|
import type { ActivityLogEntryCell } from 'apps/dashboard/features/activity/types/ActivityLogEntryCell';
|
||||||
import Page from 'components/Page';
|
import Page from 'components/Page';
|
||||||
import UserAvatar from 'components/UserAvatar';
|
|
||||||
import { useLogEntires } from 'hooks/useLogEntries';
|
|
||||||
import { useUsers } from 'hooks/useUsers';
|
import { useUsers } from 'hooks/useUsers';
|
||||||
import { parseISO8601Date, toLocaleString } from 'scripts/datetime';
|
import { parseISO8601Date, toLocaleString } from 'scripts/datetime';
|
||||||
import globalize from 'lib/globalize';
|
import globalize from 'lib/globalize';
|
||||||
import { toBoolean } from 'utils/string';
|
import { toBoolean } from 'utils/string';
|
||||||
|
|
||||||
import LogLevelChip from '../components/activityTable/LogLevelChip';
|
type UsersRecords = Record<string, UserDto>;
|
||||||
import OverviewCell from '../components/activityTable/OverviewCell';
|
|
||||||
|
|
||||||
const DEFAULT_PAGE_SIZE = 25;
|
const DEFAULT_PAGE_SIZE = 25;
|
||||||
const VIEW_PARAM = 'useractivity';
|
const VIEW_PARAM = 'useractivity';
|
||||||
|
|
||||||
const enum ActivityView {
|
const enum ActivityView {
|
||||||
All,
|
All = 'All',
|
||||||
User,
|
User = 'User',
|
||||||
System
|
System = 'System'
|
||||||
}
|
}
|
||||||
|
|
||||||
const getActivityView = (param: string | null) => {
|
const getActivityView = (param: string | null) => {
|
||||||
|
@ -37,6 +37,12 @@ const getActivityView = (param: string | null) => {
|
||||||
return ActivityView.System;
|
return ActivityView.System;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getUserCell = (users: UsersRecords) => function UserCell({ row }: ActivityLogEntryCell) {
|
||||||
|
return (
|
||||||
|
<UserAvatarButton user={row.original.UserId && users[row.original.UserId] || undefined} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const Activity = () => {
|
const Activity = () => {
|
||||||
const [ searchParams, setSearchParams ] = useSearchParams();
|
const [ searchParams, setSearchParams ] = useSearchParams();
|
||||||
|
|
||||||
|
@ -50,7 +56,6 @@ const Activity = () => {
|
||||||
|
|
||||||
const { data: usersData, isLoading: isUsersLoading } = useUsers();
|
const { data: usersData, isLoading: isUsersLoading } = useUsers();
|
||||||
|
|
||||||
type UsersRecords = Record<string, UserDto>;
|
|
||||||
const users: UsersRecords = useMemo(() => {
|
const users: UsersRecords = useMemo(() => {
|
||||||
if (!usersData) return {};
|
if (!usersData) return {};
|
||||||
|
|
||||||
|
@ -65,6 +70,8 @@ const Activity = () => {
|
||||||
}, {});
|
}, {});
|
||||||
}, [usersData]);
|
}, [usersData]);
|
||||||
|
|
||||||
|
const UserCell = getUserCell(users);
|
||||||
|
|
||||||
const activityParams = useMemo(() => ({
|
const activityParams = useMemo(() => ({
|
||||||
startIndex: pagination.pageIndex * pagination.pageSize,
|
startIndex: pagination.pageIndex * pagination.pageSize,
|
||||||
limit: pagination.pageSize,
|
limit: pagination.pageSize,
|
||||||
|
@ -87,11 +94,7 @@ const Activity = () => {
|
||||||
accessorKey: 'Severity',
|
accessorKey: 'Severity',
|
||||||
header: globalize.translate('LabelLevel'),
|
header: globalize.translate('LabelLevel'),
|
||||||
size: 90,
|
size: 90,
|
||||||
Cell: ({ cell }: { cell: MRT_Cell<ActivityLogEntry> }) => (
|
Cell: LogLevelCell,
|
||||||
cell.getValue<LogLevel | undefined>() ? (
|
|
||||||
<LogLevelChip level={cell.getValue<LogLevel>()} />
|
|
||||||
) : undefined
|
|
||||||
),
|
|
||||||
enableResizing: false,
|
enableResizing: false,
|
||||||
muiTableBodyCellProps: {
|
muiTableBodyCellProps: {
|
||||||
align: 'center'
|
align: 'center'
|
||||||
|
@ -102,20 +105,7 @@ const Activity = () => {
|
||||||
accessorFn: row => row.UserId && users[row.UserId]?.Name,
|
accessorFn: row => row.UserId && users[row.UserId]?.Name,
|
||||||
header: globalize.translate('LabelUser'),
|
header: globalize.translate('LabelUser'),
|
||||||
size: 75,
|
size: 75,
|
||||||
Cell: ({ row }: { row: MRT_Row<ActivityLogEntry> }) => (
|
Cell: UserCell,
|
||||||
row.original.UserId ? (
|
|
||||||
<IconButton
|
|
||||||
size='large'
|
|
||||||
color='inherit'
|
|
||||||
sx={{ padding: 0 }}
|
|
||||||
title={users[row.original.UserId]?.Name || undefined}
|
|
||||||
component={Link}
|
|
||||||
to={`/dashboard/users/profile?userId=${row.original.UserId}`}
|
|
||||||
>
|
|
||||||
<UserAvatar user={users[row.original.UserId]} />
|
|
||||||
</IconButton>
|
|
||||||
) : undefined
|
|
||||||
),
|
|
||||||
enableResizing: false,
|
enableResizing: false,
|
||||||
visibleInShowHideMenu: activityView !== ActivityView.System,
|
visibleInShowHideMenu: activityView !== ActivityView.System,
|
||||||
muiTableBodyCellProps: {
|
muiTableBodyCellProps: {
|
||||||
|
@ -132,9 +122,7 @@ const Activity = () => {
|
||||||
accessorFn: row => row.ShortOverview || row.Overview,
|
accessorFn: row => row.ShortOverview || row.Overview,
|
||||||
header: globalize.translate('LabelOverview'),
|
header: globalize.translate('LabelOverview'),
|
||||||
size: 170,
|
size: 170,
|
||||||
Cell: ({ row }: { row: MRT_Row<ActivityLogEntry> }) => (
|
Cell: OverviewCell
|
||||||
<OverviewCell {...row.original} />
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'Type',
|
accessorKey: 'Type',
|
||||||
|
@ -146,24 +134,13 @@ const Activity = () => {
|
||||||
accessorFn: row => row.ItemId,
|
accessorFn: row => row.ItemId,
|
||||||
header: '',
|
header: '',
|
||||||
size: 60,
|
size: 60,
|
||||||
Cell: ({ row }: { row: MRT_Row<ActivityLogEntry> }) => (
|
Cell: ActionsCell,
|
||||||
row.original.ItemId ? (
|
|
||||||
<IconButton
|
|
||||||
size='large'
|
|
||||||
title={globalize.translate('LabelMediaDetails')}
|
|
||||||
component={Link}
|
|
||||||
to={`/details?id=${row.original.ItemId}`}
|
|
||||||
>
|
|
||||||
<PermMedia fontSize='inherit' />
|
|
||||||
</IconButton>
|
|
||||||
) : undefined
|
|
||||||
),
|
|
||||||
enableColumnActions: false,
|
enableColumnActions: false,
|
||||||
enableColumnFilter: false,
|
enableColumnFilter: false,
|
||||||
enableResizing: false,
|
enableResizing: false,
|
||||||
enableSorting: false
|
enableSorting: false
|
||||||
}
|
}
|
||||||
], [ activityView, users ]);
|
], [ UserCell, activityView, users ]);
|
||||||
|
|
||||||
const onViewChange = useCallback((_e: React.MouseEvent<HTMLElement, MouseEvent>, newView: ActivityView | null) => {
|
const onViewChange = useCallback((_e: React.MouseEvent<HTMLElement, MouseEvent>, newView: ActivityView | null) => {
|
||||||
if (newView !== null) {
|
if (newView !== null) {
|
||||||
|
@ -201,6 +178,9 @@ const Activity = () => {
|
||||||
},
|
},
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
initialState: {
|
||||||
|
density: 'compact'
|
||||||
|
},
|
||||||
state: {
|
state: {
|
||||||
isLoading,
|
isLoading,
|
||||||
pagination,
|
pagination,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue