diff --git a/README.md b/README.md index 1f68ff6c8..b43ad5acd 100644 --- a/README.md +++ b/README.md @@ -23,17 +23,11 @@ cd cms ```bash docker run -d \ - --name cms-db \ - --e POSTGRES_USER=myuser \ - +-e POSTGRES_USER=myuser \ -e POSTGRES_PASSWORD=mypassword \ - --e POSTGRES_DB=mydatabase \ - +-e POSTGRES_DB=mydatabase \ -p 5432:5432 \ - postgres ``` diff --git a/src/app/courses/[courseId]/layout.tsx b/src/app/courses/[courseId]/layout.tsx index f134db9b4..df922375a 100644 --- a/src/app/courses/[courseId]/layout.tsx +++ b/src/app/courses/[courseId]/layout.tsx @@ -1,4 +1,5 @@ import { QueryParams } from '@/actions/types'; +import { FilterContent } from '@/components/FilterContent'; import { Sidebar } from '@/components/Sidebar'; import { getFullCourseContent } from '@/db/course'; import { authOptions } from '@/lib/auth'; @@ -46,10 +47,17 @@ const Layout = async ({ } const fullCourseContent = await getFullCourseContent(parseInt(courseId, 10)); - return (
- +
+
+ +
+
+ +
+
+ {children}
); diff --git a/src/components/FilterContent.tsx b/src/components/FilterContent.tsx new file mode 100644 index 000000000..8f3917ef4 --- /dev/null +++ b/src/components/FilterContent.tsx @@ -0,0 +1,82 @@ +'use client'; +import React, { useState, forwardRef } from 'react'; +import { Check, ChevronsUpDown } from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; +import { useRecoilState } from 'recoil'; +import { selectFilter } from '@/store/atoms/filterContent'; +import { + Command, + CommandGroup, + CommandItem, + CommandList, +} from '@/components/ui/command'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover'; + +const allFilters = [ + { value: 'all', label: 'ALL' }, + { value: 'unwatched', label: 'Unwatched' }, + { value: 'watched', label: 'Watched' }, + { value: 'watching', label: 'Watching' }, +]; + +type FilterContentProps = { + // Add any other props here if needed + className?: string; +}; + +export const FilterContent = forwardRef( + (props, ref) => { + const [open, setOpen] = useState(false); + const [value, setValue] = useRecoilState(selectFilter); + + return ( + + + + + + + + + {allFilters.map((filters) => ( + { + setValue(currentValue === value ? '' : currentValue); + setOpen(false); + }} + > + + {filters.label} + + ))} + + + + + + ); + }, +); diff --git a/src/components/FolderView.tsx b/src/components/FolderView.tsx index c9f24c430..c6d8e3370 100644 --- a/src/components/FolderView.tsx +++ b/src/components/FolderView.tsx @@ -1,8 +1,9 @@ 'use client'; import { useRouter } from 'next/navigation'; import { ContentCard } from './ContentCard'; -import { Bookmark } from '@prisma/client'; -import { CourseContentType } from '@/lib/utils'; +import { courseContent, getFilteredContent } from '@/lib/utils'; +import { useRecoilValue } from 'recoil'; +import { selectFilter } from '@/store/atoms/filterContent'; export const FolderView = ({ courseContent, @@ -11,18 +12,7 @@ export const FolderView = ({ }: { courseId: number; rest: string[]; - courseContent: { - type: CourseContentType; - title: string; - image: string; - id: number; - markAsCompleted: boolean; - percentComplete: number | null; - videoFullDuration?: number; - duration?: number; - bookmark: Bookmark | null; - weeklyContentTitles?: string[]; - }[]; + courseContent: courseContent[]; }) => { const router = useRouter(); @@ -39,10 +29,17 @@ export const FolderView = ({ } // why? because we have to reset the segments or they will be visible always after a video + const currentfilter = useRecoilValue(selectFilter); + + const filteredCourseContent = getFilteredContent( + courseContent, + currentfilter, + ); + return (
- {courseContent.map((content) => { + {filteredCourseContent.map((content) => { const videoProgressPercent = content.type === 'video' && content.videoFullDuration && diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index d113df33b..86a7881a8 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -9,7 +9,7 @@ import { } from '@/components/ui/accordion'; import { Play, File, X, Menu } from 'lucide-react'; import { FullCourseContent } from '@/db/course'; -import { useRecoilState } from 'recoil'; +import { useRecoilState, useRecoilValue } from 'recoil'; import { sidebarOpen as sidebarOpenAtom } from '@/store/atoms/sidebar'; import { useEffect, useState, useCallback, useMemo } from 'react'; import { handleMarkAsCompleted } from '@/lib/utils'; @@ -17,7 +17,8 @@ import BookmarkButton from './bookmark/BookmarkButton'; import Link from 'next/link'; import { Button } from './ui/button'; import { AnimatePresence, motion } from 'framer-motion'; - +import { FilterContent } from './FilterContent'; +import { selectFilter } from '@/store/atoms/filterContent'; const sidebarVariants = { open: { width: '100%', @@ -47,7 +48,9 @@ export function Sidebar({ >([]); const sidebarRef = useRef(null); const buttonRef = useRef(null); + const filterRef = useRef(null); const closeSidebar = () => setSidebarOpen(false); + const currentfilter = useRecoilValue(selectFilter); const findPathToContent = useCallback( ( @@ -77,7 +80,8 @@ export function Sidebar({ if ( sidebarRef.current && !sidebarRef.current.contains(event.target as Node) && - !buttonRef.current?.contains(event.target as Node) + !buttonRef.current?.contains(event.target as Node) && + !filterRef.current?.contains(event.target as Node) ) { closeSidebar(); } @@ -133,7 +137,6 @@ export function Sidebar({ (contents: FullCourseContent[]) => { return contents.map((content) => { const isActiveContent = currentActiveContentIds?.includes(content.id); - if (content.children && content.children.length > 0) { return ( -
-
-
- - {content.type === 'video' && } - {content.type === 'notion' && } + (content.type === 'notion' || + filterContent(currentfilter, content)) && ( + +
+
+
+ + {content.type === 'video' && } + {content.type === 'notion' && } +
+
{content.title}
-
{content.title}
+ {content.type === 'video' && ( + + )}
- {content.type === 'video' && ( - - )} -
- + + ) ); }); }, - [currentActiveContentIds, navigateToContent], + [currentActiveContentIds, navigateToContent, currentfilter], ); const memoizedContent = useMemo( @@ -206,9 +212,15 @@ export function Sidebar({ variants={sidebarVariants} className="fixed right-0 top-0 z-[99999] flex h-screen w-full flex-col gap-4 overflow-y-auto rounded-r-lg border-l border-primary/10 bg-neutral-50 dark:bg-neutral-900 md:max-w-[30vw]" > -

+
+ {' '} +

Course Content

+