Skip to content

Commit

Permalink
feat(dashboard): add sort by filter (#237)
Browse files Browse the repository at this point in the history
resolves #186
  • Loading branch information
RaunoT authored Aug 25, 2024
1 parent 79e16ef commit ca5061c
Show file tree
Hide file tree
Showing 17 changed files with 274 additions and 134 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ services:
plex-rewind:
image: ghcr.io/raunot/plex-rewind:latest # :develop for the latest development version
container_name: plex-rewind
# user: 1000:1000 # change to your user and group id if you are running into permissions issues
environment:
- NEXTAUTH_SECRET= # (required) used to encrypt auth JWT token, generate one with `openssl rand -base64 32`
- NEXTAUTH_URL=http://localhost:8383 # (required) change to your domain if you are exposing the app to the internet
Expand Down
55 changes: 4 additions & 51 deletions src/app/_components/AppProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use client'

import { Version } from '@/types'
import { DashboardSearchParams } from '@/types/dashboard'
import { Settings } from '@/types/settings'
import { checkRequiredSettings } from '@/utils/helpers'
import {
Expand All @@ -14,22 +13,9 @@ import { useSession } from 'next-auth/react'
import Image from 'next/image'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { createContext, ReactNode, useEffect, useState } from 'react'
import { ReactNode, useEffect, useState } from 'react'
import stars from '../_assets/stars.png'

type GlobalContextProps = {
isDashboardPersonal: boolean
setIsDashboardPersonal: (isDashboardPersonal: boolean) => void
period: DashboardSearchParams['period']
setPeriod: (period: DashboardSearchParams['period']) => void
}

export const GlobalContext = createContext<GlobalContextProps>({
isDashboardPersonal: false,
setIsDashboardPersonal: () => {},
period: 'custom',
setPeriod: () => {},
})
import GlobalContextProvider from './GlobalContextProvider'

type Props = {
children: ReactNode
Expand All @@ -45,24 +31,6 @@ export default function AppProvider({ children, settings, version }: Props) {
pathname.startsWith('/settings'),
)
const [settingsLink, setSettingsLink] = useState<string>('/settings/general')
const [isDashboardPersonal, setIsDashboardPersonal] = useState<boolean>(
() => {
if (typeof window !== 'undefined') {
return localStorage.getItem('dashboardPersonal') === 'true'
}

return false
},
)
const [period, setPeriod] = useState<DashboardSearchParams['period']>(() => {
if (typeof window !== 'undefined') {
return localStorage.getItem(
'dashboardPeriod',
) as DashboardSearchParams['period']
}

return undefined
})

useEffect(() => {
switch (true) {
Expand All @@ -80,23 +48,8 @@ export default function AppProvider({ children, settings, version }: Props) {
setIsSettings(pathname.startsWith('/settings'))
}, [pathname])

useEffect(() => {
localStorage.setItem('dashboardPersonal', isDashboardPersonal.toString())
}, [isDashboardPersonal])

useEffect(() => {
localStorage.setItem('dashboardPeriod', period || '')
}, [period])

return (
<GlobalContext.Provider
value={{
isDashboardPersonal,
setIsDashboardPersonal,
period,
setPeriod,
}}
>
<GlobalContextProvider>
<main
className={clsx(
'flex h-full min-h-dvh flex-col items-center overflow-x-hidden px-4 py-8 sm:justify-center',
Expand Down Expand Up @@ -145,6 +98,6 @@ export default function AppProvider({ children, settings, version }: Props) {

{children}
</main>
</GlobalContext.Provider>
</GlobalContextProvider>
)
}
91 changes: 91 additions & 0 deletions src/app/_components/GlobalContextProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
'use client'

import { DashboardSearchParams } from '@/types/dashboard'
import { createContext, ReactNode, useEffect, useState } from 'react'

type GlobalContextProps = {
dashboard: {
isPersonal: DashboardSearchParams['personal']
setIsPersonal: (isPersonal: DashboardSearchParams['personal']) => void
period: DashboardSearchParams['period']
setPeriod: (period: DashboardSearchParams['period']) => void
sortBy: DashboardSearchParams['sortBy']
setSortBy: (sortBy: DashboardSearchParams['sortBy']) => void
}
}

export const GlobalContext = createContext<GlobalContextProps>({
dashboard: {
isPersonal: undefined,
setIsPersonal: () => {},
period: 'custom',
setPeriod: () => {},
sortBy: undefined,
setSortBy: () => {},
},
})

type Props = {
children: ReactNode
}

export default function GlobalContextProvider({ children }: Props) {
const [isPersonal, setIsPersonal] = useState<
DashboardSearchParams['personal']
>(() => {
if (typeof window !== 'undefined') {
return localStorage.getItem(
'dashboardPersonal',
) as DashboardSearchParams['personal']
}

return undefined
})
const [period, setPeriod] = useState<DashboardSearchParams['period']>(() => {
if (typeof window !== 'undefined') {
return localStorage.getItem(
'dashboardPeriod',
) as DashboardSearchParams['period']
}

return undefined
})
const [sortBy, setSortBy] = useState<DashboardSearchParams['sortBy']>(() => {
if (typeof window !== 'undefined') {
return localStorage.getItem(
'dashboardSort',
) as DashboardSearchParams['sortBy']
}

return undefined
})

useEffect(() => {
localStorage.setItem('dashboardPersonal', isPersonal || '')
}, [isPersonal])

useEffect(() => {
localStorage.setItem('dashboardPeriod', period || '')
}, [period])

useEffect(() => {
localStorage.setItem('dashboardSort', sortBy || '')
}, [sortBy])

return (
<GlobalContext.Provider
value={{
dashboard: {
isPersonal,
setIsPersonal,
period,
setPeriod,
sortBy,
setSortBy,
},
}}
>
{children}
</GlobalContext.Provider>
)
}
9 changes: 6 additions & 3 deletions src/app/_components/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { signOut, useSession } from 'next-auth/react'
import Image from 'next/image'
import Link from 'next/link'
import { useContext } from 'react'
import { GlobalContext } from './AppProvider'
import { GlobalContext } from './GlobalContextProvider'

type Props = {
settings: Settings
Expand All @@ -22,7 +22,9 @@ type Props = {
export default function Home({ settings, libraries }: Props) {
const { isLoading, handleLogin } = usePlexAuth()
const missingSetting = checkRequiredSettings(settings)
const { isDashboardPersonal, period } = useContext(GlobalContext)
const {
dashboard: { isPersonal, period, sortBy },
} = useContext(GlobalContext)
const { data: session, status } = useSession()
const isLoggedIn = status === 'authenticated'
const dashboardSlug = kebabCase(
Expand All @@ -36,8 +38,9 @@ export default function Home({ settings, libraries }: Props) {
(isLoggedIn || settings.general.isOutsideAccess) &&
dashboardSlug
const dashboardParams = new URLSearchParams({
...(isDashboardPersonal && { personal: 'true' }),
...(isPersonal && { personal: 'true' }),
...(period && { period }),
...(sortBy && settings.dashboard.isSortByPlaysActive && { sortBy }),
})

if (isLoading || status === 'loading') {
Expand Down
11 changes: 9 additions & 2 deletions src/app/dashboard/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,15 @@ async function DashboardContent({ params, searchParams }: Props) {
const session = await getServerSession(authOptions)
const period = getPeriod(searchParams, settings)
const isPersonal = searchParams.personal === 'true'
const sortByPlays =
searchParams.sortBy === 'plays' && settings.dashboard.isSortByPlaysActive
const [items, totalDuration, totalSize, serverId] = await Promise.all([
getItems(library, period.daysAgo, isPersonal && session?.user.id),
getItems(
library,
period.daysAgo,
isPersonal && session?.user.id,
sortByPlays,
),
getTotalDuration(
library,
period.string,
Expand Down Expand Up @@ -82,7 +89,7 @@ export default function DashboardPage({ params, searchParams }: Props) {
return (
<Suspense
fallback={<DashboardLoader />}
key={`period-${searchParams.period}-personal-${searchParams.personal}`}
key={`period-${searchParams.period}-personal-${searchParams.personal}-sortBy-${searchParams.sortBy}`}
>
<DashboardContent params={params} searchParams={searchParams} />
</Suspense>
Expand Down
29 changes: 20 additions & 9 deletions src/app/dashboard/_components/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
UserIcon,
} from '@heroicons/react/24/outline'
import { Suspense } from 'react'
import DashboardPersonalToggle from './DashboardPersonalToggle'
import DashboardFilters from './DashboardFilters'

type Props = {
title: string
Expand All @@ -36,18 +36,27 @@ export default function Dashboard({
settings,
isLoggedIn,
}: Props) {
function renderFilters(className?: string) {
if (type !== 'users' && isLoggedIn) {
return (
<Suspense>
<DashboardFilters
className={className}
isSortByPlaysActive={settings.dashboard.isSortByPlaysActive}
/>
</Suspense>
)
}
}

return (
<>
<div className='mb-2 flex items-start justify-between gap-4 leading-tight'>
<h2 className='-mt-0.5 flex text-xl font-bold sm:-mt-1.5 sm:text-2xl xl:text-3xl'>
<div className='mb-2 flex items-center justify-between gap-4 leading-tight'>
<h2 className='flex items-center text-xl font-bold sm:-mt-1.5 sm:text-2xl xl:text-3xl'>
{getTitleIcon(type)}
{title}
</h2>
{type !== 'users' && isLoggedIn && (
<Suspense>
<DashboardPersonalToggle />
</Suspense>
)}
{renderFilters('hidden sm:flex')}
</div>
<ul className='icon-stats-container mb-1 sm:gap-x-3'>
{totalSize && (
Expand Down Expand Up @@ -96,13 +105,15 @@ export default function Dashboard({
</h2>
</div>
)}

{renderFilters('flex sm:hidden justify-end pt-6')}
</>
)
}

function getTitleIcon(type: string) {
const className =
'mr-1 sm:mr-2 size-8 sm:size-10 stroke-1 text-black shrink-0 pb-1 -ml-1'
'mr-1 sm:mr-2 size-8 sm:size-10 stroke-1 text-black shrink-0 -ml-0.5 sm:-ml-1'

switch (type) {
case 'movie':
Expand Down
Loading

0 comments on commit ca5061c

Please sign in to comment.