diff --git a/front/app/_orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/page.tsx b/front/app/_orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/page.tsx index 7a6fffa7..27afd332 100644 --- a/front/app/_orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/page.tsx +++ b/front/app/_orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/page.tsx @@ -13,6 +13,7 @@ import { Check } from "lucide-react"; import { swrFetcher } from "@services/utils/ts/requests"; import { markActivityAsComplete } from "@services/courses/activity"; import ToolTip from "@components/UI/Tooltip/Tooltip"; +import DocumentPdfActivity from "@components/Pages/Activities/DocumentPdf/DocumentPdf"; function ActivityPage(params: any) { const activityid = params.params.activityid; @@ -77,6 +78,8 @@ function ActivityPage(params: any) { {/* todo : use apis & streams instead of this */} {activity.type == "video" && } + {activity.type == "documentpdf" && } + {course.trail.activities_marked_complete && diff --git a/front/components/Modals/Activities/Create/NewActivity.tsx b/front/components/Modals/Activities/Create/NewActivity.tsx index 9062e2ba..9cc28b5f 100644 --- a/front/components/Modals/Activities/Create/NewActivity.tsx +++ b/front/components/Modals/Activities/Create/NewActivity.tsx @@ -2,15 +2,17 @@ import React, { useState } from "react"; import { ArrowLeftIcon, Cross1Icon } from "@radix-ui/react-icons"; import DynamicPageActivityImage from "public/activities_types/dynamic-page-activity.png"; import VideoPageActivityImage from "public//activities_types/video-page-activity.png"; +import DocumentPdfPageActivityImage from "public//activities_types/documentpdf-page-activity.png"; import { styled, keyframes } from '@stitches/react'; import DynamicCanvaModal from "./NewActivityModal/DynamicCanva"; import VideoModal from "./NewActivityModal/Video"; import Image from "next/image"; +import DocumentPdfModal from "./NewActivityModal/DocumentPdf"; function NewActivityModal({ closeModal, submitActivity, submitFileActivity, chapterId }: any) { const [selectedView, setSelectedView] = useState("home"); - + return ( {selectedView === "home" && ( @@ -27,11 +29,11 @@ function NewActivityModal({ closeModal, submitActivity, submitFileActivity, chap Video Page - { setSelectedView("video") }}> + { setSelectedView("documentpdf") }}> - + - Video Page + PDF Document Page )} @@ -43,6 +45,10 @@ function NewActivityModal({ closeModal, submitActivity, submitFileActivity, chap {selectedView === "video" && ( )} + + {selectedView === "documentpdf" && ( + + )} ); } diff --git a/front/components/Modals/Activities/Create/NewActivityModal/DocumentPdf.tsx b/front/components/Modals/Activities/Create/NewActivityModal/DocumentPdf.tsx new file mode 100644 index 00000000..01310a84 --- /dev/null +++ b/front/components/Modals/Activities/Create/NewActivityModal/DocumentPdf.tsx @@ -0,0 +1,58 @@ +import FormLayout, { ButtonBlack, Flex, FormField, FormLabel, FormMessage, Input, Textarea } from "@components/UI/Form/Form"; +import React, { useState } from "react"; +import * as Form from '@radix-ui/react-form'; +import BarLoader from "react-spinners/BarLoader"; + +function DocumentPdfModal({ submitFileActivity, chapterId }: any) { + const [documentpdf, setDocumentPdf] = React.useState(null) as any; + const [isSubmitting, setIsSubmitting] = useState(false); + const [name, setName] = React.useState(""); + + const handleDocumentPdfChange = (event: React.ChangeEvent) => { + setDocumentPdf(event.target.files[0]); + }; + + const handleNameChange = (event: React.ChangeEvent) => { + setName(event.target.value); + }; + + const handleSubmit = async (e: any) => { + e.preventDefault(); + setIsSubmitting(true); + let status = await submitFileActivity(documentpdf, "documentpdf", { name, type: "documentpdf" }, chapterId); + setIsSubmitting(false); + }; + + return ( + + + + PDF Document name + Please provide a name for your PDF Document activity + + + + + + + + PDF Document file + Please provide a PDF Document for your activity + + + + + + + + + + {isSubmitting ? : "Create activity"} + + + + + ); +} + +export default DocumentPdfModal; diff --git a/front/components/Pages/Activities/DocumentPdf/DocumentPdf.tsx b/front/components/Pages/Activities/DocumentPdf/DocumentPdf.tsx new file mode 100644 index 00000000..4c907a5e --- /dev/null +++ b/front/components/Pages/Activities/DocumentPdf/DocumentPdf.tsx @@ -0,0 +1,75 @@ +import { getBackendUrl } from "@services/config/config"; +import React from "react"; +import styled from "styled-components"; + +function DocumentPdfActivity({ activity, course }: { activity: any; course: any }) { + function getChapterName() { + let chapterName = ""; + let chapterId = activity.chapter_id; + course.chapters.forEach((chapter: any) => { + if (chapter.chapter_id === chapterId) { + chapterName = chapter.name; + } + }); + return chapterName; + } + + return ( + + + Chapter : {getChapterName()} + {activity.name} + + + + + + ); +} + +export default DocumentPdfActivity; + +const DocumentPdfActivityLayout = styled.div` + display: flex; + flex-direction: column; + margin-top: 10px; + background: #141414; + min-width: 100%; + min-height: 1200px; +`; + +const DocumentPdfTitle = styled.div` + display: flex; + width: 1300px; + margin: 0 auto; + padding-top: 20px; + font-size: 24px; + font-weight: 700; + color: #fff; + flex-direction: column; + + p { + font-size: 14px; + padding: 0; + margin: 0; + color: #ffffffaa; + } +`; + +const DocumentPdfPlayerWrapper = styled.div` + display: flex; + width: 1300px; + margin: 0 auto; + justify-content: center; + padding-top: 20px; + + iframe { + width: 1300px; + height: 500px; + border-radius: 7px; + background-color: black; + border: none; + } +`; diff --git a/front/public/activities_types/documentpdf-page-activity.png b/front/public/activities_types/documentpdf-page-activity.png new file mode 100644 index 00000000..64c38e7f Binary files /dev/null and b/front/public/activities_types/documentpdf-page-activity.png differ diff --git a/front/services/courses/activities.ts b/front/services/courses/activities.ts index b745b315..209b5325 100644 --- a/front/services/courses/activities.ts +++ b/front/services/courses/activities.ts @@ -17,13 +17,18 @@ export async function createFileActivity(file: File, type: string, data: any, ch formData.append("coursechapter_id", chapter_id); let org_id = "test"; - - let endpoint = `${getAPIUrl()}activities/video?org_id=${org_id}`; + let endpoint = ""; if (type === "video") { formData.append("name", data.name); formData.append("video_file", file); - endpoint = endpoint; + endpoint = `${getAPIUrl()}activities/video?org_id=${org_id}`; + } else if (type === "documentpdf") { + formData.append("pdf_file", file); + formData.append("name", data.name); + endpoint = `${getAPIUrl()}activities/documentpdf?org_id=${org_id}`; + } else { + // Handle other file types here } const result: any = await fetch(endpoint, RequestBodyForm("POST", formData)); diff --git a/src/routers/courses/activities.py b/src/routers/courses/activities.py index c0cc5f8b..292403be 100644 --- a/src/routers/courses/activities.py +++ b/src/routers/courses/activities.py @@ -1,6 +1,7 @@ from fastapi import APIRouter, Depends, UploadFile, Form, Request from src.services.courses.activities.activities import * from src.security.auth import get_current_user +from src.services.courses.activities.pdf import create_documentpdf_activity from src.services.courses.activities.video import create_video_activity router = APIRouter() @@ -45,7 +46,7 @@ async def api_delete_activity(request: Request, activity_id: str, org_id: str, """ return await delete_activity(request, activity_id, current_user) -# Video play +# Video activity @router.post("/video") @@ -54,3 +55,10 @@ async def api_create_video_activity(request: Request, org_id: str, name: str = Create new activity """ return await create_video_activity(request, name, coursechapter_id, current_user, video_file) + +@router.post("/documentpdf") +async def api_create_documentpdf_activity(request: Request, org_id: str, name: str = Form(), coursechapter_id: str = Form(), current_user: PublicUser = Depends(get_current_user), pdf_file: UploadFile | None = None): + """ + Create new activity + """ + return await create_documentpdf_activity(request, name, coursechapter_id, current_user, pdf_file) diff --git a/src/services/courses/activities/pdf.py b/src/services/courses/activities/pdf.py new file mode 100644 index 00000000..6c4cbccd --- /dev/null +++ b/src/services/courses/activities/pdf.py @@ -0,0 +1,77 @@ +from pydantic import BaseModel +from src.security.security import verify_user_rights_with_roles +from src.services.courses.activities.uploads.pdfs import upload_pdf +from src.services.users.users import PublicUser +from src.services.courses.activities.activities import ActivityInDB +from fastapi import HTTPException, status, UploadFile, Request +from uuid import uuid4 +from datetime import datetime + + +async def create_documentpdf_activity(request: Request, name: str, coursechapter_id: str, current_user: PublicUser, pdf_file: UploadFile | None = None): + activities = request.app.db["activities"] + courses = request.app.db["courses"] + + # generate activity_id + activity_id = str(f"activity_{uuid4()}") + + # get org_id from course + coursechapter = await courses.find_one( + {"chapters_content.coursechapter_id": coursechapter_id}) + + org_id = coursechapter["org_id"] + + # check if pdf_file is not None + if not pdf_file: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Pdf : No pdf file provided") + + if pdf_file.content_type not in ["application/pdf"]: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Pdf : Wrong pdf format") + + # get pdf format + if pdf_file.filename: + pdf_format = pdf_file.filename.split(".")[-1] + + else: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Pdf : No pdf file provided") + + activity_object = ActivityInDB( + org_id=org_id, + activity_id=activity_id, + coursechapter_id=coursechapter_id, + name=name, + type="documentpdf", + content={ + "documentpdf": { + "filename": "documentpdf."+pdf_format, + "activity_id": activity_id, + } + }, + creationDate=str(datetime.now()), + updateDate=str(datetime.now()), + ) + + hasRoleRights = await verify_user_rights_with_roles(request, "create", current_user.user_id, activity_id, element_org_id=org_id) + + if not hasRoleRights: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Roles : Insufficient rights to perform this action") + + # create activity + activity = ActivityInDB(**activity_object.dict()) + await activities.insert_one(activity.dict()) + + # upload pdf + if pdf_file: + # get pdffile format + await upload_pdf(pdf_file, activity_id) + + # todo : choose whether to update the chapter or not + # update chapter + await courses.update_one({"chapters_content.coursechapter_id": coursechapter_id}, { + "$addToSet": {"chapters_content.$.activities": activity_id}}) + + return activity diff --git a/src/services/courses/activities/uploads/pdfs.py b/src/services/courses/activities/uploads/pdfs.py new file mode 100644 index 00000000..1a912df9 --- /dev/null +++ b/src/services/courses/activities/uploads/pdfs.py @@ -0,0 +1,23 @@ +import os + + +async def upload_pdf(pdf_file, activity_id): + contents = pdf_file.file.read() + pdf_format = pdf_file.filename.split(".")[-1] + + if not os.path.exists("content/uploads/documents/documentpdf"): + # create folder + os.makedirs("content/uploads/documents/documentpdf") + + # create folder + os.mkdir(f"content/uploads/documents/documentpdf/{activity_id}") + + try: + with open(f"content/uploads/documents/documentpdf/{activity_id}/documentpdf.{pdf_format}", 'wb') as f: + f.write(contents) + f.close() + + except Exception as e: + return {"message": "There was an error uploading the file"} + finally: + pdf_file.file.close()
Chapter : {getChapterName()}