mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
Merge pull request #76 from learnhouse/swve/eng-6-document-pdf-activity
Add document pdf activity type
This commit is contained in:
commit
bca9371af0
9 changed files with 263 additions and 8 deletions
|
|
@ -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" && <VideoActivity course={course} activity={activity} />}
|
||||
|
||||
{activity.type == "documentpdf" && <DocumentPdfActivity course={course} activity={activity} />}
|
||||
|
||||
<ActivityMarkerWrapper>
|
||||
|
||||
{course.trail.activities_marked_complete &&
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div>
|
||||
{selectedView === "home" && (
|
||||
|
|
@ -27,11 +29,11 @@ function NewActivityModal({ closeModal, submitActivity, submitFileActivity, chap
|
|||
</ActivityTypeImage>
|
||||
<ActivityTypeTitle>Video Page</ActivityTypeTitle>
|
||||
</ActivityOption>
|
||||
<ActivityOption onClick={() => { setSelectedView("video") }}>
|
||||
<ActivityOption onClick={() => { setSelectedView("documentpdf") }}>
|
||||
<ActivityTypeImage>
|
||||
<Image alt="Video Page" src={VideoPageActivityImage}></Image>
|
||||
<Image alt="Document PDF Page" src={DocumentPdfPageActivityImage}></Image>
|
||||
</ActivityTypeImage>
|
||||
<ActivityTypeTitle>Video Page</ActivityTypeTitle>
|
||||
<ActivityTypeTitle>PDF Document Page</ActivityTypeTitle>
|
||||
</ActivityOption>
|
||||
</ActivityChooserWrapper>
|
||||
)}
|
||||
|
|
@ -43,6 +45,10 @@ function NewActivityModal({ closeModal, submitActivity, submitFileActivity, chap
|
|||
{selectedView === "video" && (
|
||||
<VideoModal submitFileActivity={submitFileActivity} chapterId={chapterId} />
|
||||
)}
|
||||
|
||||
{selectedView === "documentpdf" && (
|
||||
<DocumentPdfModal submitFileActivity={submitFileActivity} chapterId={chapterId} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<any>) => {
|
||||
setDocumentPdf(event.target.files[0]);
|
||||
};
|
||||
|
||||
const handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
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 (
|
||||
<FormLayout onSubmit={handleSubmit}>
|
||||
<FormField name="documentpdf-activity-name">
|
||||
<Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}>
|
||||
<FormLabel>PDF Document name</FormLabel>
|
||||
<FormMessage match="valueMissing">Please provide a name for your PDF Document activity</FormMessage>
|
||||
</Flex>
|
||||
<Form.Control asChild>
|
||||
<Input onChange={handleNameChange} type="text" required />
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
<FormField name="documentpdf-activity-file">
|
||||
<Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}>
|
||||
<FormLabel>PDF Document file</FormLabel>
|
||||
<FormMessage match="valueMissing">Please provide a PDF Document for your activity</FormMessage>
|
||||
</Flex>
|
||||
<Form.Control asChild>
|
||||
<input type="file" onChange={handleDocumentPdfChange} required />
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
|
||||
<Flex css={{ marginTop: 25, justifyContent: 'flex-end' }}>
|
||||
<Form.Submit asChild>
|
||||
<ButtonBlack type="submit" css={{ marginTop: 10 }}>
|
||||
{isSubmitting ? <BarLoader cssOverride={{borderRadius:60,}} width={60} color="#ffffff" /> : "Create activity"}
|
||||
</ButtonBlack>
|
||||
</Form.Submit>
|
||||
</Flex>
|
||||
</FormLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export default DocumentPdfModal;
|
||||
|
|
@ -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 (
|
||||
<DocumentPdfActivityLayout>
|
||||
<DocumentPdfTitle>
|
||||
<p>Chapter : {getChapterName()}</p>
|
||||
{activity.name}
|
||||
</DocumentPdfTitle>
|
||||
<DocumentPdfPlayerWrapper>
|
||||
<iframe
|
||||
src={`${getBackendUrl()}content/uploads/documents/documentpdf/${activity.content.documentpdf.activity_id}/${activity.content.documentpdf.filename}`}
|
||||
/>
|
||||
</DocumentPdfPlayerWrapper>
|
||||
</DocumentPdfActivityLayout>
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
`;
|
||||
BIN
front/public/activities_types/documentpdf-page-activity.png
Normal file
BIN
front/public/activities_types/documentpdf-page-activity.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 620 B |
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
77
src/services/courses/activities/pdf.py
Normal file
77
src/services/courses/activities/pdf.py
Normal file
|
|
@ -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
|
||||
23
src/services/courses/activities/uploads/pdfs.py
Normal file
23
src/services/courses/activities/uploads/pdfs.py
Normal file
|
|
@ -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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue