From 6f2cc5bdc621e418a69b7ef2fb0b53dd5f197099 Mon Sep 17 00:00:00 2001 From: swve Date: Fri, 11 Nov 2022 20:36:09 +0100 Subject: [PATCH] feat: add elements --- front/components/drags/chapter.tsx | 87 ++++++++++--------- front/components/drags/element.tsx | 2 +- .../new.tsx => CourseEdit/NewChapter.tsx} | 0 .../modals/CourseEdit/NewElement.tsx | 39 +++++++++ front/components/ui/header.tsx | 1 - front/components/ui/layout.tsx | 3 +- front/pages/index.tsx | 2 +- front/pages/login.tsx | 6 +- .../course/[courseid]/[elementid]/edit.tsx | 4 +- .../course/[courseid]/edit/index.tsx | 84 ++++++++++++++---- .../org/[orgslug]/course/[courseid]/index.tsx | 4 +- front/pages/org/[orgslug]/courses/index.tsx | 8 +- .../pages/org/[orgslug]/courses/new/index.tsx | 8 +- front/pages/org/[orgslug]/index.tsx | 6 +- front/pages/organizations/index.tsx | 4 +- front/pages/organizations/new.tsx | 4 +- front/pages/signup.tsx | 6 +- front/services/{ => courses}/chapters.ts | 4 +- front/services/{ => courses}/courses.ts | 2 +- front/services/courses/elements.ts | 27 ++++++ src/services/courses/chapters.py | 28 ++++-- src/services/courses/elements.py | 15 ++-- src/services/roles.py | 1 + 23 files changed, 241 insertions(+), 104 deletions(-) rename front/components/modals/{chapters/new.tsx => CourseEdit/NewChapter.tsx} (100%) create mode 100644 front/components/modals/CourseEdit/NewElement.tsx rename front/services/{ => courses}/chapters.ts (94%) rename front/services/{ => courses}/courses.ts (98%) create mode 100644 front/services/courses/elements.ts diff --git a/front/components/drags/chapter.tsx b/front/components/drags/chapter.tsx index 9b80d4fd..f58608a2 100644 --- a/front/components/drags/chapter.tsx +++ b/front/components/drags/chapter.tsx @@ -1,9 +1,51 @@ import React from "react"; import styled from "styled-components"; import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd"; -import Element, { ElementWrapper } from "./element"; -import Link from "next/link"; -import { motion } from "framer-motion"; +import Element, { ElementWrapper } from "./Element"; + +function Chapter(props: any) { + return ( + + {(provided, snapshot) => ( + +

+ {props.info.list.chapter.name}{" "} + + +

+ + {(provided) => ( + + {props.info.list.elements.map((element: any, index: any) => ( + + ))} + {provided.placeholder} + + )} + +
+ )} +
+ ); +} const ChapterWrapper = styled.div` margin-bottom: 5px; @@ -17,45 +59,6 @@ const ChapterWrapper = styled.div` transition: all 0.2s ease; `; -function Chapter(props: any) { - return ( - - {(provided, snapshot) => ( - - -

- {props.info.list.chapter.name}{" "} - -

- - {(provided) => ( - - {props.info.list.elements.map((element: any, index: any) => ( - - ))} - {provided.placeholder} - - )} - -
- - )} -
- ); -} - const ElementsList = styled.div` padding: 10px; `; diff --git a/front/components/drags/element.tsx b/front/components/drags/element.tsx index 98711de1..fd5397f4 100644 --- a/front/components/drags/element.tsx +++ b/front/components/drags/element.tsx @@ -7,7 +7,7 @@ function Element(props: any) { {(provided) => ( - {props.element.content} +

{props.element.name}

)}
diff --git a/front/components/modals/chapters/new.tsx b/front/components/modals/CourseEdit/NewChapter.tsx similarity index 100% rename from front/components/modals/chapters/new.tsx rename to front/components/modals/CourseEdit/NewChapter.tsx diff --git a/front/components/modals/CourseEdit/NewElement.tsx b/front/components/modals/CourseEdit/NewElement.tsx new file mode 100644 index 00000000..6903a210 --- /dev/null +++ b/front/components/modals/CourseEdit/NewElement.tsx @@ -0,0 +1,39 @@ +import React, { useState } from "react"; +import Modal from "../Modal"; + +function NewElementModal({ closeModal, submitElement, chapterId }: any) { + const [elementName, setElementName] = useState(""); + const [elementDescription, setElementDescription] = useState(""); + + const handleElementNameChange = (e: any) => { + setElementName(e.target.value); + }; + + const handleElementDescriptionChange = (e: any) => { + setElementDescription(e.target.value); + }; + + const handleSubmit = async (e: any) => { + e.preventDefault(); + console.log({ elementName, elementDescription, chapterId }); + submitElement({ + name: elementName, + chapterId: chapterId, + type: "dynamic", + }); + }; + + return ( + +

+ Add New Element +

+
+ +
+ +
+ ); +} + +export default NewElementModal; diff --git a/front/components/ui/header.tsx b/front/components/ui/header.tsx index 8f4c4ecd..012a2dfb 100644 --- a/front/components/ui/header.tsx +++ b/front/components/ui/header.tsx @@ -1,5 +1,4 @@ import React from "react"; -import { Menu } from "./elements/menu"; import Link from 'next/link' import styled from "styled-components"; diff --git a/front/components/ui/layout.tsx b/front/components/ui/layout.tsx index 874f6ac1..99a5668d 100644 --- a/front/components/ui/layout.tsx +++ b/front/components/ui/layout.tsx @@ -1,10 +1,9 @@ import React from "react"; import Head from "next/head"; -import { Header } from "./header"; import styled from "styled-components"; import AuthProvider from "../security/AuthProvider"; import { motion } from "framer-motion"; -import { Menu } from "./elements/menu"; +import { Menu } from "./elements/Menu"; const Layout = (props: any) => { const variants = { diff --git a/front/pages/index.tsx b/front/pages/index.tsx index 72aef1e8..070b98c0 100644 --- a/front/pages/index.tsx +++ b/front/pages/index.tsx @@ -4,7 +4,7 @@ import styled from "styled-components"; import learnhouseBigIcon from "public/learnhouse_bigicon.png"; import Image from "next/image"; import Link from "next/link"; -import { PreAlphaLabel } from "../components/ui/layout"; +import { PreAlphaLabel } from "../components/ui/Layout"; const Home: NextPage = () => { return ( diff --git a/front/pages/login.tsx b/front/pages/login.tsx index e97147ff..fb094af3 100644 --- a/front/pages/login.tsx +++ b/front/pages/login.tsx @@ -1,8 +1,8 @@ import Router from "next/router"; import React from "react"; -import { Header } from "../components/ui/header"; -import Layout from "../components/ui/layout"; -import { Title } from "../components/ui/styles/title"; +import { Header } from "../components/ui/Header"; +import Layout from "../components/ui/Layout"; +import { Title } from "../components/ui/styles/Title"; import { loginAndGetToken } from "../services/auth/auth"; const Login = () => { diff --git a/front/pages/org/[orgslug]/course/[courseid]/[elementid]/edit.tsx b/front/pages/org/[orgslug]/course/[courseid]/[elementid]/edit.tsx index d32088aa..e1cc0253 100644 --- a/front/pages/org/[orgslug]/course/[courseid]/[elementid]/edit.tsx +++ b/front/pages/org/[orgslug]/course/[courseid]/[elementid]/edit.tsx @@ -1,7 +1,7 @@ import { default as React, useEffect, useRef } from "react"; -import Layout from "../../../../../../components/ui/layout"; -import { Title } from "../../../../../../components/ui/styles/title"; +import Layout from "../../../../../../components/ui/Layout"; +import { Title } from "../../../../../../components/ui/styles/Title"; import dynamic from "next/dynamic"; import { AuthContext } from "../../../../../../components/security/AuthProvider"; diff --git a/front/pages/org/[orgslug]/course/[courseid]/edit/index.tsx b/front/pages/org/[orgslug]/course/[courseid]/edit/index.tsx index 7f7f6ace..b85bbf95 100644 --- a/front/pages/org/[orgslug]/course/[courseid]/edit/index.tsx +++ b/front/pages/org/[orgslug]/course/[courseid]/edit/index.tsx @@ -1,27 +1,38 @@ import React from "react"; import { useState, useEffect } from "react"; import styled from "styled-components"; -import { Header } from "../../../../../../components/ui/header"; -import Layout from "../../../../../../components/ui/layout"; -import { Title } from "../../../../../../components/ui/styles/title"; +import { Header } from "../../../../../../components/ui/Header"; +import Layout from "../../../../../../components/ui/Layout"; +import { Title } from "../../../../../../components/ui/styles/Title"; import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd"; import { initialData, initialData2 } from "../../../../../../components/drags/data"; -import Chapter from "../../../../../../components/drags/chapter"; -import { createChapter, deleteChapter, getCourseChaptersMetadata } from "../../../../../../services/chapters"; +import Chapter from "../../../../../../components/drags/Chapter"; +import { createChapter, deleteChapter, getCourseChaptersMetadata } from "../../../../../../services/courses/chapters"; import { useRouter } from "next/router"; -import NewChapterModal from "../../../../../../components/modals/chapters/new"; +import NewChapterModal from "../../../../../../components/modals/CourseEdit/NewChapter"; +import NewElementModal from "../../../../../../components/modals/CourseEdit/NewElement"; +import { createElement } from "../../../../../../services/courses/elements"; function CourseEdit() { const router = useRouter(); + + // Initial Course State const [data, setData] = useState(initialData2) as any; + + // New Chapter Modal State const [newChapterModal, setNewChapterModal] = useState(false) as any; + // New Element Modal State + const [newElementModal, setNewElementModal] = useState(false) as any; + const [newElementModalData, setNewElementModalData] = useState("") as any; + + // Check window availability const [winReady, setwinReady] = useState(false); const { courseid } = router.query; async function getCourseChapters() { const courseChapters = await getCourseChaptersMetadata(courseid); setData(courseChapters); - console.log( "courseChapters" , courseChapters); + console.log("courseChapters", courseChapters); } useEffect(() => { @@ -59,20 +70,44 @@ function CourseEdit() { setNewChapterModal(false); }; + // Submit new element + const submitElement = async (element: any) => { + console.log("submitElement", element); + await createElement(element, element.chapterId); + getCourseChapters(); + setNewElementModal(false); + }; + const deleteChapterUI = async (chapterId: any) => { console.log("deleteChapter", chapterId); await deleteChapter(chapterId); - getCourseChapters(); }; + const openNewElementModal = async (chapterId: any) => { + console.log("openNewElementModal", chapterId); + setNewElementModal(true); + setNewElementModalData(chapterId); + }; + /* + + Modals + */ // Close new chapter modal - const closeModal = () => { + const closeNewChapterModal = () => { setNewChapterModal(false); }; + const closeNewElementModal = () => { + setNewElementModal(false); + }; + + /* + Drag and drop functions + + */ const onDragEnd = (result: any) => { const { destination, source, draggableId, type } = result; console.log(result); @@ -175,21 +210,34 @@ function CourseEdit() {
- Edit Course Chapters <button onClick={()=> {setNewChapterModal(true)}}>+</button> + Edit Course Chapters{" "} + <button + onClick={() => { + setNewChapterModal(true); + }} + > + + + </button> - {newChapterModal && } + {newChapterModal && } + {newElementModal && } +
- {winReady && ( + {winReady && ( {(provided) => ( -
- {getChapters().map((info: any, index: any) => ( - - ))} - {provided.placeholder} -
+ <> +
+ {getChapters().map((info: any, index: any) => ( + <> + + + ))} + {provided.placeholder} +
+ )}
diff --git a/front/pages/org/[orgslug]/course/[courseid]/index.tsx b/front/pages/org/[orgslug]/course/[courseid]/index.tsx index c232c4cf..4946ad80 100644 --- a/front/pages/org/[orgslug]/course/[courseid]/index.tsx +++ b/front/pages/org/[orgslug]/course/[courseid]/index.tsx @@ -1,9 +1,9 @@ import { useRouter } from "next/router"; import React from "react"; import styled from "styled-components"; -import Layout from "../../../../../components/ui/layout"; +import Layout from "../../../../../components/ui/Layout"; import { getAPIUrl, getBackendUrl } from "../../../../../services/config"; -import { getCourse } from "../../../../../services/courses"; +import { getCourse } from "../../../../../services/courses/courses"; import { getOrganizationContextInfo } from "../../../../../services/orgs"; const CourseIdPage = () => { diff --git a/front/pages/org/[orgslug]/courses/index.tsx b/front/pages/org/[orgslug]/courses/index.tsx index e84d7412..c92f2011 100644 --- a/front/pages/org/[orgslug]/courses/index.tsx +++ b/front/pages/org/[orgslug]/courses/index.tsx @@ -2,11 +2,11 @@ import Link from "next/link"; import { useRouter } from "next/router"; import React from "react"; import styled from "styled-components"; -import { Header } from "../../../../components/ui/header"; -import Layout from "../../../../components/ui/layout"; -import { Title } from "../../../../components/ui/styles/title"; +import { Header } from "../../../../components/ui/Header"; +import Layout from "../../../../components/ui/Layout"; +import { Title } from "../../../../components/ui/styles/Title"; import { getBackendUrl } from "../../../../services/config"; -import { deleteCourseFromBackend, getOrgCourses } from "../../../../services/courses"; +import { deleteCourseFromBackend, getOrgCourses } from "../../../../services/courses/courses"; import { getOrganizationContextInfo } from "../../../../services/orgs"; const CoursesIndexPage = () => { diff --git a/front/pages/org/[orgslug]/courses/new/index.tsx b/front/pages/org/[orgslug]/courses/new/index.tsx index 79c13cf3..7221c58f 100644 --- a/front/pages/org/[orgslug]/courses/new/index.tsx +++ b/front/pages/org/[orgslug]/courses/new/index.tsx @@ -1,9 +1,9 @@ import { useRouter } from "next/router"; import React from "react"; -import { Header } from "../../../../../components/ui/header"; -import Layout from "../../../../../components/ui/layout"; -import { Title } from "../../../../../components/ui/styles/title"; -import { createNewCourse } from "../../../../../services/courses"; +import { Header } from "../../../../../components/ui/Header"; +import Layout from "../../../../../components/ui/Layout"; +import { Title } from "../../../../../components/ui/styles/Title"; +import { createNewCourse } from "../../../../../services/courses/courses"; import { getOrganizationContextInfo } from "../../../../../services/orgs"; const NewCoursePage = () => { diff --git a/front/pages/org/[orgslug]/index.tsx b/front/pages/org/[orgslug]/index.tsx index d536a30b..d5220894 100644 --- a/front/pages/org/[orgslug]/index.tsx +++ b/front/pages/org/[orgslug]/index.tsx @@ -1,8 +1,8 @@ import React from "react"; import { useRouter } from "next/router"; -import Layout from "../../../components/ui/layout"; -import { Title } from "../../../components/ui/styles/title"; -import { Header } from "../../../components/ui/header"; +import Layout from "../../../components/ui/Layout"; +import { Title } from "../../../components/ui/styles/Title"; +import { Header } from "../../../components/ui/Header"; import Link from "next/link"; const OrgHomePage = () => { diff --git a/front/pages/organizations/index.tsx b/front/pages/organizations/index.tsx index 0f1dfb8a..ce2b5a25 100644 --- a/front/pages/organizations/index.tsx +++ b/front/pages/organizations/index.tsx @@ -1,8 +1,8 @@ import Link from "next/link"; import React from "react"; import AuthenticatedOnly from "../../components/security/AuthenticatedOnly"; -import Layout from "../../components/ui/layout"; -import { Title } from "../../components/ui/styles/title"; +import Layout from "../../components/ui/Layout"; +import { Title } from "../../components/ui/styles/Title"; import { deleteOrganizationFromBackend, getUserOrganizations } from "../../services/orgs"; const Organizations = () => { diff --git a/front/pages/organizations/new.tsx b/front/pages/organizations/new.tsx index f0d4b91c..072405c9 100644 --- a/front/pages/organizations/new.tsx +++ b/front/pages/organizations/new.tsx @@ -1,6 +1,6 @@ import React from "react"; -import Layout from "../../components/ui/layout"; -import { Title } from "../../components/ui/styles/title"; +import Layout from "../../components/ui/Layout"; +import { Title } from "../../components/ui/styles/Title"; import { createNewOrganization } from "../../services/orgs"; const Organizations = () => { diff --git a/front/pages/signup.tsx b/front/pages/signup.tsx index e35604fb..9aa2d1dc 100644 --- a/front/pages/signup.tsx +++ b/front/pages/signup.tsx @@ -1,7 +1,7 @@ import React from "react"; -import { Header } from "../components/ui/header"; -import Layout from "../components/ui/layout"; -import { Title } from "../components/ui/styles/title"; +import { Header } from "../components/ui/Header"; +import Layout from "../components/ui/Layout"; +import { Title } from "../components/ui/styles/Title"; import { signup } from "../services/auth/auth"; const SignUp = () => { diff --git a/front/services/chapters.ts b/front/services/courses/chapters.ts similarity index 94% rename from front/services/chapters.ts rename to front/services/courses/chapters.ts index 3c16240b..c594b7d6 100644 --- a/front/services/chapters.ts +++ b/front/services/courses/chapters.ts @@ -1,5 +1,5 @@ -import { initialData } from "../components/drags/data"; -import { getAPIUrl } from "./config"; +import { initialData } from "../../components/drags/data"; +import { getAPIUrl } from "../config"; export async function getCourseChaptersMetadata(course_id: any) { const HeadersConfig = new Headers({ "Content-Type": "application/json" }); diff --git a/front/services/courses.ts b/front/services/courses/courses.ts similarity index 98% rename from front/services/courses.ts rename to front/services/courses/courses.ts index f6b0448d..bed9ba75 100644 --- a/front/services/courses.ts +++ b/front/services/courses/courses.ts @@ -1,4 +1,4 @@ -import { getAPIUrl } from "./config"; +import { getAPIUrl } from "../config"; export async function getOrgCourses(org_id: number) { const HeadersConfig = new Headers({ "Content-Type": "application/json" }); diff --git a/front/services/courses/elements.ts b/front/services/courses/elements.ts new file mode 100644 index 00000000..2974e308 --- /dev/null +++ b/front/services/courses/elements.ts @@ -0,0 +1,27 @@ +import { getAPIUrl } from "../config"; + +export async function createElement(data: any, chapter_id: any) { + data.content = {} + console.log("data", data, chapter_id); + + // remove chapter_id from data + delete data.chapterId; + + const HeadersConfig = new Headers({ "Content-Type": "application/json" }); + + const requestOptions: any = { + method: "POST", + headers: HeadersConfig, + redirect: "follow", + credentials: "include", + body: JSON.stringify(data), + }; + + const result: any = await fetch(`${getAPIUrl()}elements/?coursechapter_id=${chapter_id}`, requestOptions) + .then((result) => result.json()) + .catch((error) => console.log("error", error)); + + console.log("result", result); + + return result; + } \ No newline at end of file diff --git a/src/services/courses/chapters.py b/src/services/courses/chapters.py index 706b4904..c8619fea 100644 --- a/src/services/courses/chapters.py +++ b/src/services/courses/chapters.py @@ -4,20 +4,18 @@ from typing import List from uuid import uuid4 from pydantic import BaseModel from src.services.courses.courses import Course, CourseInDB +from src.services.courses.elements import Element, ElementInDB from src.services.database import create_config_collection, check_database, create_database, learnhouseDB, learnhouseDB from src.services.security import verify_user_rights_with_roles from src.services.users import PublicUser from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks, UploadFile, File -class CourseElement(BaseModel): - element_id: str - class CourseChapter(BaseModel): name: str description: str - elements: List[CourseElement] + elements: list class CourseChapterInDB(CourseChapter): @@ -164,13 +162,19 @@ async def get_coursechapters_meta(course_id: str, current_user: PublicUser): await check_database() coursechapters = learnhouseDB["coursechapters"] courses = learnhouseDB["courses"] + elements = learnhouseDB["elements"] coursechapters = coursechapters.find( {"course_id": course_id}).sort("name", 1) + + course = courses.find_one({"course_id": course_id}) course = Course(**course) # type: ignore + # elements + coursechapter_elementIds_global = [] + # chapters chapters = {} for coursechapter in coursechapters: @@ -178,15 +182,27 @@ async def get_coursechapters_meta(course_id: str, current_user: PublicUser): coursechapter_elementIds = [] for element in coursechapter.elements: - coursechapter_elementIds.append(element.element_id) + coursechapter_elementIds.append(element) + coursechapter_elementIds_global.append(element) chapters[coursechapter.coursechapter_id] = { "id": coursechapter.coursechapter_id, "name": coursechapter.name, "elementIds": coursechapter_elementIds } + + # elements + elements_list = {} + for element in elements.find({"element_id": {"$in": coursechapter_elementIds_global}}): + element = ElementInDB(**element) + elements_list[element.element_id] = { + "id": element.element_id, "name": element.name, "type": element.type , "content": element.content + } + + final = { "chapters": chapters, - "chapterOrder": course.chapters + "chapterOrder": course.chapters, + "elements" : elements_list } return final diff --git a/src/services/courses/elements.py b/src/services/courses/elements.py index 3b4de715..58b24202 100644 --- a/src/services/courses/elements.py +++ b/src/services/courses/elements.py @@ -8,11 +8,11 @@ from datetime import datetime #### Classes #################################################### + class Element(BaseModel): name: str - element_type: str - content: str - + type: str + content: object class ElementInDB(Element): @@ -29,9 +29,10 @@ class ElementInDB(Element): #################################################### -async def create_element(element_object: Element, coursechapter_id : str , current_user: PublicUser): +async def create_element(element_object: Element, coursechapter_id: str, current_user: PublicUser): await check_database() elements = learnhouseDB["elements"] + coursechapters = learnhouseDB["coursechapters"] # generate element_id element_id = str(f"element_{uuid4()}") @@ -47,6 +48,10 @@ async def create_element(element_object: Element, coursechapter_id : str , curre datetime.now()), coursechapter_id=coursechapter_id, updateDate=str(datetime.now()), element_id=element_id) elements.insert_one(element.dict()) + # update chapter + coursechapters.update_one({"coursechapter_id": coursechapter_id}, { + "$addToSet": {"elements": element_id}}) + return element @@ -88,7 +93,7 @@ async def update_element(element_object: Element, element_id: str, current_user: datetime_object = datetime.now() updated_course = ElementInDB( - element_id=element_id,coursechapter_id=element["coursechapter_id"] ,creationDate=creationDate, updateDate=str(datetime_object), **element_object.dict()) + element_id=element_id, coursechapter_id=element["coursechapter_id"], creationDate=creationDate, updateDate=str(datetime_object), **element_object.dict()) elements.update_one({"element_id": element_id}, { "$set": updated_course.dict()}) diff --git a/src/services/roles.py b/src/services/roles.py index 060096e3..dcefb122 100644 --- a/src/services/roles.py +++ b/src/services/roles.py @@ -26,6 +26,7 @@ class Elements(BaseModel): collections: List[str] organizations: List[str] coursechapters: List[str] + elements : List[str] class Role(BaseModel):