diff --git a/src/components/FolderView.tsx b/src/components/FolderView.tsx
index e5f5fae6f..e3aae97c1 100644
--- a/src/components/FolderView.tsx
+++ b/src/components/FolderView.tsx
@@ -1,6 +1,9 @@
'use client';
import { useRouter } from 'next/navigation';
import { ContentCard } from './ContentCard';
+import { useRecoilValue } from 'recoil';
+import { videoCompletionAtom } from '@/store/atoms/videoCompletion';
+import { getFolderContentCompleted } from '@/lib/utils';
export const FolderView = ({
courseContent,
@@ -32,24 +35,33 @@ export const FolderView = ({
updatedRoute += `/${rest[i]}`;
}
// why? because we have to reset the segments or they will be visible always after a video
-
+ const videoCompletion = useRecoilValue(videoCompletionAtom);
return (
- {courseContent.map((content) => (
- {
- router.push(`${updatedRoute}/${content.id}`);
- }}
- markAsCompleted={content.markAsCompleted}
- percentComplete={content.percentComplete}
- />
- ))}
+ {courseContent.map((content) => {
+ let percent = null;
+ let isCompleted = false;
+ if (content.type === 'folder') {
+ percent = getFolderContentCompleted(videoCompletion , content.id);
+ } else if (content.type === 'video') {
+ isCompleted = videoCompletion.find((item) => item.id === content.id)?.isCompleted || false;
+ }
+ return (
+ {
+ router.push(`${updatedRoute}/${content.id}`);
+ }}
+ markAsCompleted={isCompleted}
+ percentComplete={percent}
+ />
+ );
+ })}
);
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
index c95ec05b0..64ce4877e 100644
--- a/src/components/Sidebar.tsx
+++ b/src/components/Sidebar.tsx
@@ -11,8 +11,9 @@ import { Button } from './ui/button';
import { BackArrow } from '@/icons/BackArrow';
import { useRecoilState } from 'recoil';
import { sidebarOpen as sidebarOpenAtom } from '@/store/atoms/sidebar';
-import { useEffect, useState } from 'react';
-import { handleMarkAsCompleted } from '@/lib/utils';
+import { useEffect } from 'react';
+import { VideoCompletionData, convertToVideoCompletionData, handleMarkAsCompleted } from '@/lib/utils';
+import { videoCompletionAtom } from '@/store/atoms/videoCompletion';
export function Sidebar({
courseId,
@@ -23,6 +24,11 @@ export function Sidebar({
}) {
const router = useRouter();
const [sidebarOpen, setSidebarOpen] = useRecoilState(sidebarOpenAtom);
+ const [videoCompletion , setVideoCompletion] = useRecoilState(videoCompletionAtom);
+
+ useEffect(() => {
+ setVideoCompletion(convertToVideoCompletionData(fullCourseContent));
+ } , []);
useEffect(() => {
if (window.innerWidth < 500) {
@@ -101,7 +107,7 @@ export function Sidebar({
{content.type === 'video' ? (
-
+
) : null}
@@ -232,18 +238,23 @@ function NotionIcon() {
}
// Todo: Fix types here
-function Check({ content }: { content: any }) {
- const [completed, setCompleted] = useState(
- content?.videoProgress?.markAsCompleted || false,
- );
+function Check({ content , videoCompletion , setVideoCompletion }: { content: any , videoCompletion : VideoCompletionData[] , setVideoCompletion : any }) {
+ const completed = videoCompletion.length > 0 ? videoCompletion.find((item) => item.id === content.id)?.isCompleted : false;
+
return (
<>
{}}
onClick={async (e) => {
- setCompleted(!completed);
- handleMarkAsCompleted(!completed, content.id);
e.stopPropagation();
+ await handleMarkAsCompleted(!completed, content.id);
+ setVideoCompletion(videoCompletion.map((item : VideoCompletionData) => {
+ if (item.id === content.id) {
+ return {...item , isCompleted: !completed};
+ }
+ return item;
+ }));
}}
type="checkbox"
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
diff --git a/src/components/admin/ContentRendererClient.tsx b/src/components/admin/ContentRendererClient.tsx
index 3dd40d353..9827c580a 100644
--- a/src/components/admin/ContentRendererClient.tsx
+++ b/src/components/admin/ContentRendererClient.tsx
@@ -4,7 +4,9 @@ import { useSearchParams, useRouter } from 'next/navigation';
import { VideoPlayerSegment } from '@/components/VideoPlayerSegment';
import VideoContentChapters from '../VideoContentChapters';
import { useState } from 'react';
-import { handleMarkAsCompleted } from '@/lib/utils';
+import { VideoCompletionData, handleMarkAsCompleted } from '@/lib/utils';
+import { useRecoilValue, useSetRecoilState } from 'recoil';
+import { videoCompletionAtom } from '@/store/atoms/videoCompletion';
export const ContentRendererClient = ({
metadata,
@@ -26,10 +28,8 @@ export const ContentRendererClient = ({
markAsCompleted: boolean;
};
}) => {
- const [contentCompleted, setContentCompleted] = useState(
- content.markAsCompleted,
- );
- const [loadingMarkAs, setLoadingMarkAs] = useState(false);
+ const setVideoCompletion = useSetRecoilState(videoCompletionAtom);
+
const [showChapters, setShowChapters] = useState(
metadata?.segments?.length > 0,
);
@@ -77,17 +77,19 @@ export const ContentRendererClient = ({
setShowChapters((prev) => !prev);
};
- const handleMarkCompleted = async () => {
- setLoadingMarkAs(true);
+ const handleMarkCompleted = async (completed : boolean) => {
const data: any = await handleMarkAsCompleted(
- !contentCompleted,
+ completed,
content.id,
);
-
if (data.contentId) {
- setContentCompleted((prev) => !prev);
+ setVideoCompletion((prev) => prev.map((item : VideoCompletionData) => {
+ if (item.id === content.id) {
+ return {...item , isCompleted: data.markAsCompleted};
+ }
+ return item;
+ }));
}
- setLoadingMarkAs(false);
};
return (
@@ -118,7 +120,7 @@ export const ContentRendererClient = ({
sources: [source],
}}
onVideoEnd={() => {
- setContentCompleted(true);
+ handleMarkCompleted(true);
}}
/>
@@ -128,13 +130,7 @@ export const ContentRendererClient = ({
{content.title}
-
+
@@ -196,3 +192,20 @@ export const ContentRendererClient = ({
);
};
+
+const DisplayMarkAsComplete = ({contentId , handleMarkCompleted} : {contentId : number , handleMarkCompleted : any}) => {
+ const videoCompletion = useRecoilValue(videoCompletionAtom);
+
+ const markAsComplete = videoCompletion.length > 0 ? videoCompletion.find((item) => item.id === contentId)?.isCompleted : false;
+
+ return (
+
+ );
+};
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index b63b64c8a..d8c2b9a05 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -1,4 +1,5 @@
import { CommentFilter, QueryParams } from '@/actions/types';
+import { Folder } from '@/db/course';
import { CommentType, Prisma } from '@prisma/client';
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
@@ -12,6 +13,40 @@ export interface VideoJsPlayer {
eme: () => void;
}
+export const convertToVideoCompletionData = (currentCourse: Folder[]): VideoCompletionData[] => {
+ const videoCompletionData: VideoCompletionData[] = [];
+
+ for (const folder of currentCourse) {
+ for (const child of folder?.children ?? []) {
+ if (child?.type === 'video') {
+ const { id, parentId, videoProgress } = child;
+ const isCompleted = videoProgress?.markAsCompleted ?? false;
+ videoCompletionData.push({ id, parentId, isCompleted });
+ }
+ }
+ }
+
+ return videoCompletionData;
+};
+
+export const getFolderContentCompleted = (videoCompletion : VideoCompletionData[], contentId : number) => {
+ const folderItems = videoCompletion.filter(item => item.parentId === contentId);
+ const totalContentInside = folderItems.length;
+ const totalCompleted = folderItems.filter(item => item.isCompleted).length;
+
+ if (totalContentInside === 0) {
+ return 0;
+ }
+
+ return Math.round((totalCompleted / totalContentInside) * 100);
+};
+
+export interface VideoCompletionData {
+ id : number;
+ parentId : number | null;
+ isCompleted : boolean | undefined;
+}
+
export interface Segment {
start: number;
end: number;
diff --git a/src/store/atoms/videoCompletion.ts b/src/store/atoms/videoCompletion.ts
new file mode 100644
index 000000000..87aa4ad86
--- /dev/null
+++ b/src/store/atoms/videoCompletion.ts
@@ -0,0 +1,7 @@
+import { atom } from 'recoil';
+import { VideoCompletionData } from '@/lib/utils';
+
+export const videoCompletionAtom = atom({
+ key: 'videoCompletionAtom',
+ default: []
+});
\ No newline at end of file