diff --git a/src/apps/dashboard/AppLayout.tsx b/src/apps/dashboard/AppLayout.tsx index cf3a0da9cf..9030465b32 100644 --- a/src/apps/dashboard/AppLayout.tsx +++ b/src/apps/dashboard/AppLayout.tsx @@ -2,6 +2,8 @@ import AppBar from '@mui/material/AppBar'; import Box from '@mui/material/Box'; import { type Theme } from '@mui/material/styles'; import useMediaQuery from '@mui/material/useMediaQuery'; +import { LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; import React, { FC, useCallback, useEffect, useState } from 'react'; import { Outlet, useLocation } from 'react-router-dom'; @@ -10,6 +12,7 @@ import AppToolbar from 'components/toolbar/AppToolbar'; import ElevationScroll from 'components/ElevationScroll'; import { DRAWER_WIDTH } from 'components/ResponsiveDrawer'; import { useApi } from 'hooks/useApi'; +import { useLocale } from 'hooks/useLocale'; import AppTabs from './components/AppTabs'; import AppDrawer from './components/drawer/AppDrawer'; @@ -23,6 +26,7 @@ export const Component: FC = () => { const [ isDrawerActive, setIsDrawerActive ] = useState(false); const location = useLocation(); const { user } = useApi(); + const { dateFnsLocale } = useLocale(); const isMediumScreen = useMediaQuery((t: Theme) => t.breakpoints.up('md')); const isDrawerAvailable = Boolean(user) @@ -43,52 +47,54 @@ export const Component: FC = () => { }, []); return ( - - - + + + + + + + + + + { + isDrawerAvailable && ( + + ) + } + + - - - - - - - { - isDrawerAvailable && ( - - ) - } - - - - - + + + + - + ); }; diff --git a/src/apps/dashboard/routes/activity.tsx b/src/apps/dashboard/routes/activity.tsx index 630cd4ba95..666d08441c 100644 --- a/src/apps/dashboard/routes/activity.tsx +++ b/src/apps/dashboard/routes/activity.tsx @@ -1,5 +1,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; 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 Box from '@mui/material/Box'; import ToggleButton from '@mui/material/ToggleButton'; @@ -68,7 +69,15 @@ const Activity = () => { [userId]: user }; }, {}); - }, [usersData]); + }, [ usersData ]); + + const userNames = useMemo(() => { + const names: string[] = []; + usersData?.forEach(user => { + if (user.Name) names.push(user.Name); + }); + return names; + }, [ usersData ]); const UserCell = getUserCell(users); @@ -92,8 +101,10 @@ const Activity = () => { enableResizing: false, muiTableBodyCellProps: { align: 'center' - } - }], [ activityView, users, UserCell ]); + }, + filterVariant: 'multi-select', + filterSelectOptions: userNames + }], [ activityView, userNames, users, UserCell ]); const columns = useMemo[]>(() => [ { @@ -101,7 +112,8 @@ const Activity = () => { accessorFn: row => parseISO8601Date(row.Date), header: globalize.translate('LabelTime'), size: 160, - Cell: ({ cell }) => toLocaleString(cell.getValue()) + Cell: ({ cell }) => toLocaleString(cell.getValue()), + filterVariant: 'datetime-range' }, { accessorKey: 'Severity', @@ -111,7 +123,9 @@ const Activity = () => { enableResizing: false, muiTableBodyCellProps: { align: 'center' - } + }, + filterVariant: 'multi-select', + filterSelectOptions: Object.values(LogLevel).map(level => globalize.translate(`LogLevel.${level}`)) }, ...userColumn, { diff --git a/src/hooks/useLocale.tsx b/src/hooks/useLocale.tsx new file mode 100644 index 0000000000..a7b8bc625c --- /dev/null +++ b/src/hooks/useLocale.tsx @@ -0,0 +1,40 @@ +import type { Locale } from 'date-fns'; +import enUS from 'date-fns/locale/en-US'; +import { useEffect, useMemo, useState } from 'react'; + +import { getDefaultLanguage, normalizeLocaleName } from 'lib/globalize'; +import { fetchLocale, normalizeLocale } from 'utils/dateFnsLocale'; + +import { useUserSettings } from './useUserSettings'; + +export function useLocale() { + const { dateTimeLocale: dateTimeSetting, language } = useUserSettings(); + const [ dateFnsLocale, setDateFnsLocale ] = useState(enUS); + + const locale = useMemo(() => ( + normalizeLocaleName(language || getDefaultLanguage()) + ), [ language ]); + + const dateTimeLocale = useMemo(() => ( + dateTimeSetting ? normalizeLocaleName(dateTimeSetting) : locale + ), [ dateTimeSetting, locale ]); + + useEffect(() => { + const fetchDateFnsLocale = async () => { + try { + const dfLocale = await fetchLocale(normalizeLocale(dateTimeLocale)); + setDateFnsLocale(dfLocale); + } catch (err) { + console.warn('[useLocale] failed to fetch dateFns locale', err); + } + }; + + void fetchDateFnsLocale(); + }, [ dateTimeLocale ]); + + return { + locale, + dateTimeLocale, + dateFnsLocale + }; +} diff --git a/src/lib/globalize/index.js b/src/lib/globalize/index.js index aad779a28d..fc76a209df 100644 --- a/src/lib/globalize/index.js +++ b/src/lib/globalize/index.js @@ -25,7 +25,7 @@ export function getCurrentDateTimeLocale() { return currentDateTimeCulture; } -function getDefaultLanguage() { +export function getDefaultLanguage() { const culture = document.documentElement.getAttribute('data-culture'); if (culture) { return culture; @@ -127,7 +127,7 @@ function ensureTranslation(translationInfo, culture) { }); } -function normalizeLocaleName(culture) { +export function normalizeLocaleName(culture) { return culture.replace('_', '-').toLowerCase(); } diff --git a/src/utils/dateFnsLocale.ts b/src/utils/dateFnsLocale.ts index 425ac7b25c..e0e66b05f9 100644 --- a/src/utils/dateFnsLocale.ts +++ b/src/utils/dateFnsLocale.ts @@ -67,11 +67,21 @@ const DEFAULT_LOCALE = 'en-US'; let localeString = DEFAULT_LOCALE; let locale = enUS; +export function fetchLocale(localeName: string) { + return import(`date-fns/locale/${localeName}/index.js`); +} + +export function normalizeLocale(localeName: string) { + return LOCALE_MAP[localeName] + || LOCALE_MAP[localeName.replace(/-.*/, '')] + || DEFAULT_LOCALE; +} + export async function updateLocale(newLocale: string) { console.debug('[dateFnsLocale] updating date-fns locale', newLocale); - localeString = LOCALE_MAP[newLocale] || LOCALE_MAP[newLocale.replace(/-.*/, '')] || DEFAULT_LOCALE; + localeString = normalizeLocale(newLocale); console.debug('[dateFnsLocale] mapped to date-fns locale', localeString); - locale = await import(`date-fns/locale/${localeString}/index.js`); + locale = await fetchLocale(localeString); } export function getLocale() {