diff --git a/src/apps/stable/routes/dashboard/activity.tsx b/src/apps/stable/routes/dashboard/activity.tsx index 297a47aec..159096aee 100644 --- a/src/apps/stable/routes/dashboard/activity.tsx +++ b/src/apps/stable/routes/dashboard/activity.tsx @@ -1,13 +1,17 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { FC, useCallback, useEffect, useState } from 'react'; import { getActivityLogApi } from '@jellyfin/sdk/lib/utils/api/activity-log-api'; import { getUserApi } from '@jellyfin/sdk/lib/utils/api/user-api'; 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 Info from '@mui/icons-material/Info'; import Box from '@mui/material/Box'; import Chip from '@mui/material/Chip'; +import ClickAwayListener from '@mui/material/ClickAwayListener'; +import IconButton from '@mui/material/IconButton'; import ToggleButton from '@mui/material/ToggleButton'; import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; +import Tooltip from '@mui/material/Tooltip'; import Typography from '@mui/material/Typography'; import { DataGrid, type GridColDef } from '@mui/x-data-grid'; import { useSearchParams } from 'react-router-dom'; @@ -15,6 +19,7 @@ import { useSearchParams } from 'react-router-dom'; import Page from 'components/Page'; import UserAvatar from 'components/UserAvatar'; import { useApi } from 'hooks/useApi'; +import { parseISO8601Date, toLocaleDateString, toLocaleTimeString } from 'scripts/datetime'; import globalize from 'scripts/globalize'; import { toBoolean } from 'utils/string'; @@ -62,58 +67,65 @@ const LogLevelChip = ({ level }: { level: LogLevel }) => { ); }; +const OverviewCell: FC = ({ Overview, ShortOverview }) => { + const displayValue = ShortOverview ?? Overview; + const [ open, setOpen ] = useState(false); + + const onTooltipClose = useCallback(() => { + setOpen(false); + }, []); + + const onTooltipOpen = useCallback(() => { + setOpen(true); + }, []); + + if (!displayValue) return null; + + return ( + + + {displayValue} + + {ShortOverview && Overview && ( + + + + + + + + )} + + ); +}; + const Activity = () => { const { api } = useApi(); const [ searchParams, setSearchParams ] = useSearchParams(); - const columns: GridColDef[] = [ - { - field: 'Date', - headerName: globalize.translate('LabelDate'), - width: 180, - type: 'dateTime', - valueGetter: ({ row }) => new Date(row.Date) - }, - { - field: 'Severity', - headerName: globalize.translate('LabelLevel'), - width: 110, - renderCell: ({ row }) => ( - row.Severity ? ( - - ) : undefined - ) - }, - { - field: 'User', - headerName: globalize.translate('LabelUser'), - width: 60, - valueGetter: ({ row }) => users[row.UserId]?.Name, - renderCell: ({ row }) => ( - - ) - }, - { - field: 'Name', - headerName: globalize.translate('LabelName'), - width: 200 - }, - { - field: 'Overview', - headerName: globalize.translate('LabelOverview'), - width: 200, - valueGetter: ({ row }) => row.Overview ?? row.ShortOverview - }, - { - field: 'Type', - headerName: globalize.translate('LabelType'), - width: 150 - } - ]; - const [ activityView, setActivityView ] = useState( getActivityView(searchParams.get(VIEW_PARAM))); const [ isLoading, setIsLoading ] = useState(true); @@ -125,6 +137,70 @@ const Activity = () => { const [ rows, setRows ] = useState([]); const [ users, setUsers ] = useState>({}); + const userColDef: GridColDef[] = activityView !== ActivityView.System ? [ + { + field: 'User', + headerName: globalize.translate('LabelUser'), + width: 60, + valueGetter: ({ row }) => users[row.UserId]?.Name, + renderCell: ({ row }) => ( + + ) + } + ] : []; + + const columns: GridColDef[] = [ + { + field: 'Date', + headerName: globalize.translate('LabelDate'), + width: 90, + type: 'date', + valueGetter: ({ value }) => parseISO8601Date(value), + valueFormatter: ({ value }) => toLocaleDateString(value) + }, + { + field: 'Time', + headerName: globalize.translate('LabelTime'), + width: 100, + type: 'dateTime', + valueGetter: ({ row }) => parseISO8601Date(row.Date), + valueFormatter: ({ value }) => toLocaleTimeString(value) + }, + { + field: 'Severity', + headerName: globalize.translate('LabelLevel'), + width: 110, + renderCell: ({ value }) => ( + value ? ( + + ) : undefined + ) + }, + ...userColDef, + { + field: 'Name', + headerName: globalize.translate('LabelName'), + width: 200 + }, + { + field: 'Overview', + headerName: globalize.translate('LabelOverview'), + width: 220, + valueGetter: ({ row }) => row.ShortOverview ?? row.Overview, + renderCell: ({ row }) => ( + + ) + }, + { + field: 'Type', + headerName: globalize.translate('LabelType'), + width: 150 + } + ]; + const onViewChange = useCallback((_e, newView: ActivityView | null) => { if (newView !== null) { setActivityView(newView); @@ -175,6 +251,7 @@ const Activity = () => { setIsLoading(false); }; + setIsLoading(true); fetchActivity() .catch(err => { console.error('[activity] failed to fetch activity log entries', err);