mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: add delete activity option + redesign edit
This commit is contained in:
parent
8f5dcac35e
commit
142e111c15
7 changed files with 111 additions and 43 deletions
|
|
@ -6,7 +6,7 @@ import ConfirmationModal from '@components/StyledElements/ConfirmationModal/Conf
|
||||||
import { getUriWithOrg } from '@services/config/config';
|
import { getUriWithOrg } from '@services/config/config';
|
||||||
import { deleteCollection } from '@services/courses/collections';
|
import { deleteCollection } from '@services/courses/collections';
|
||||||
import { revalidateTags } from '@services/utils/ts/requests';
|
import { revalidateTags } from '@services/utils/ts/requests';
|
||||||
import { Link, Trash } from 'lucide-react';
|
import { Link, Trash, X } from 'lucide-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
|
|
@ -26,15 +26,17 @@ const CollectionAdminEditsArea = (props: any) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthenticatedClientElement orgId={props.org_id} checkMethod='roles'>
|
<AuthenticatedClientElement orgId={props.org_id} checkMethod='roles'>
|
||||||
<div className="flex space-x-2 py-2">
|
<div className="flex space-x-2 relative top-8 z-20 left-2">
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
confirmationMessage="Are you sure you want to delete this collection?"
|
confirmationMessage="Are you sure you want to delete this collection?"
|
||||||
confirmationButtonText="Delete Collection"
|
confirmationButtonText="Delete Collection"
|
||||||
dialogTitle={"Delete " + props.collection.name + " ?"}
|
dialogTitle={"Delete " + props.collection.name + " ?"}
|
||||||
dialogTrigger={
|
dialogTrigger={
|
||||||
<button className="rounded-md text-sm px-3 font-bold text-red-800 bg-red-200 w-16 flex justify-center items-center" >
|
<div
|
||||||
Delete <Trash size={10}></Trash>
|
className=" hover:cursor-pointer p-1 px-4 bg-red-600 rounded-md"
|
||||||
</button>}
|
rel="noopener noreferrer">
|
||||||
|
<X size={15} className="text-rose-200 font-bold" />
|
||||||
|
</div>}
|
||||||
functionToExecute={() => deleteCollectionUI(props.collection_id)}
|
functionToExecute={() => deleteCollectionUI(props.collection_id)}
|
||||||
status='warning'
|
status='warning'
|
||||||
></ConfirmationModal>
|
></ConfirmationModal>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import { createActivity, createFileActivity, createExternalVideoActivity } from
|
||||||
import { getOrganizationContextInfo } from "@services/organizations/orgs";
|
import { getOrganizationContextInfo } from "@services/organizations/orgs";
|
||||||
import Modal from "@components/StyledElements/Modal/Modal";
|
import Modal from "@components/StyledElements/Modal/Modal";
|
||||||
import { denyAccessToUser } from "@services/utils/react/middlewares/views";
|
import { denyAccessToUser } from "@services/utils/react/middlewares/views";
|
||||||
import { Folders, SaveIcon } from "lucide-react";
|
import { Folders, Hexagon, SaveIcon } from "lucide-react";
|
||||||
import GeneralWrapperStyled from "@components/StyledElements/Wrappers/GeneralWrapper";
|
import GeneralWrapperStyled from "@components/StyledElements/Wrappers/GeneralWrapper";
|
||||||
import { revalidateTags, swrFetcher } from "@services/utils/ts/requests";
|
import { revalidateTags, swrFetcher } from "@services/utils/ts/requests";
|
||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
|
|
@ -81,6 +81,8 @@ function CourseContentEdition(props: any) {
|
||||||
router.refresh();
|
router.refresh();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Submit File Upload
|
// Submit File Upload
|
||||||
const submitFileActivity = async (file: any, type: any, activity: any, chapterId: string) => {
|
const submitFileActivity = async (file: any, type: any, activity: any, chapterId: string) => {
|
||||||
await updateChaptersMetadata(courseid, data);
|
await updateChaptersMetadata(courseid, data);
|
||||||
|
|
@ -301,7 +303,7 @@ function CourseContentEdition(props: any) {
|
||||||
dialogDescription="Add a new chapter to the course"
|
dialogDescription="Add a new chapter to the course"
|
||||||
dialogTrigger={
|
dialogTrigger={
|
||||||
<div className="flex max-w-7xl bg-black text-sm shadow rounded-md items-center text-white justify-center mx-auto space-x-2 p-3 w-72 hover:bg-gray-900 hover:cursor-pointer">
|
<div className="flex max-w-7xl bg-black text-sm shadow rounded-md items-center text-white justify-center mx-auto space-x-2 p-3 w-72 hover:bg-gray-900 hover:cursor-pointer">
|
||||||
<Folders size={16} />
|
<Hexagon size={16} />
|
||||||
<div>Add chapter +</div>
|
<div>Add chapter +</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import TypeOfContentTitle from '@components/StyledElements/Titles/TypeOfContentT
|
||||||
import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement';
|
import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement';
|
||||||
import { getCourseThumbnailMediaDirectory } from '@services/media/media';
|
import { getCourseThumbnailMediaDirectory } from '@services/media/media';
|
||||||
import ConfirmationModal from '@components/StyledElements/ConfirmationModal/ConfirmationModal';
|
import ConfirmationModal from '@components/StyledElements/ConfirmationModal/ConfirmationModal';
|
||||||
|
import { Pencil, X } from 'lucide-react';
|
||||||
|
|
||||||
interface CourseProps {
|
interface CourseProps {
|
||||||
orgslug: string;
|
orgslug: string;
|
||||||
|
|
@ -91,22 +92,26 @@ function Courses(props: CourseProps) {
|
||||||
|
|
||||||
const AdminEditsArea = (props: { orgSlug: string, courseId: string, course: any, deleteCourses: any }) => {
|
const AdminEditsArea = (props: { orgSlug: string, courseId: string, course: any, deleteCourses: any }) => {
|
||||||
return (
|
return (
|
||||||
<AuthenticatedClientElement checkMethod='roles' orgId={props.course.org_id}><div className="flex space-x-2 py-2">
|
<AuthenticatedClientElement checkMethod='roles' orgId={props.course.org_id}><div className="flex space-x-1 relative top-8 z-20 left-2">
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
confirmationButtonText='Delete Course'
|
confirmationButtonText='Delete Course'
|
||||||
confirmationMessage='Are you sure you want to delete this course?'
|
confirmationMessage='Are you sure you want to delete this course?'
|
||||||
dialogTitle={'Delete ' + props.course.name + ' ?'}
|
dialogTitle={'Delete ' + props.course.name + ' ?'}
|
||||||
dialogTrigger={
|
dialogTrigger={
|
||||||
<button className="rounded-md text-sm px-3 font-bold text-red-800 bg-red-200 w-16 flex justify-center items-center" >
|
<div
|
||||||
Delete
|
className=" hover:cursor-pointer p-1 px-4 bg-red-600 rounded-md"
|
||||||
</button>}
|
rel="noopener noreferrer">
|
||||||
|
<X size={15} className="text-rose-200 font-bold" />
|
||||||
|
</div>}
|
||||||
functionToExecute={() => props.deleteCourses(props.courseId)}
|
functionToExecute={() => props.deleteCourses(props.courseId)}
|
||||||
status='warning'
|
status='warning'
|
||||||
></ConfirmationModal>
|
></ConfirmationModal>
|
||||||
<Link href={getUriWithOrg(props.orgSlug, "/course/" + removeCoursePrefix(props.courseId) + "/edit")}>
|
<Link href={getUriWithOrg(props.orgSlug, "/course/" + removeCoursePrefix(props.courseId) + "/edit")}>
|
||||||
<button className="rounded-md text-sm px-3 font-bold text-orange-800 bg-orange-200 w-16 flex justify-center items-center">
|
<div
|
||||||
Edit
|
className=" hover:cursor-pointer p-1 px-4 bg-orange-600 rounded-md"
|
||||||
</button>
|
rel="noopener noreferrer">
|
||||||
|
<Pencil size={15} className="text-orange-200 font-bold" />
|
||||||
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</AuthenticatedClientElement>
|
</AuthenticatedClientElement>
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,73 @@
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Draggable } from "react-beautiful-dnd";
|
import { Draggable } from "react-beautiful-dnd";
|
||||||
import { EyeOpenIcon, Pencil2Icon } from '@radix-ui/react-icons'
|
import { EyeOpenIcon, Pencil2Icon, TrashIcon } from '@radix-ui/react-icons'
|
||||||
import { getUriWithOrg } from "@services/config/config";
|
import { getAPIUrl, getUriWithOrg } from "@services/config/config";
|
||||||
import { FileText, Video, Sparkles, File } from "lucide-react";
|
import { FileText, Video, Sparkles, XSquare, X, Pencil, MoreVertical, Eye } from "lucide-react";
|
||||||
|
import { mutate } from "swr";
|
||||||
|
import { revalidateTags } from "@services/utils/ts/requests";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import ConfirmationModal from "@components/StyledElements/ConfirmationModal/ConfirmationModal";
|
||||||
|
import { deleteActivity } from "@services/courses/activities";
|
||||||
|
|
||||||
function Activity(props: any) {
|
function Activity(props: any) {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
async function removeActivity() {
|
||||||
|
await deleteActivity(props.activity.id);
|
||||||
|
mutate(`${getAPIUrl()}chapters/meta/course_${props.courseid}`);
|
||||||
|
await revalidateTags(['courses'], props.orgslug);
|
||||||
|
router.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Draggable key={props.activity.id} draggableId={props.activity.id} index={props.index}>
|
<Draggable key={props.activity.id} draggableId={props.activity.id} index={props.index}>
|
||||||
{(provided) => (
|
{(provided) => (
|
||||||
<div
|
<div
|
||||||
className="flex flex-row py-2 my-2 rounded-md bg-gray-50 text-gray-500 hover:bg-gray-100 hover:scale-102 hover:shadow space-x-2 w-auto items-center ring-1 ring-inset ring-gray-400/10 shadow-sm transition-all delay-100 duration-75 ease-linear" key={props.activity.id} {...provided.draggableProps} {...provided.dragHandleProps} ref={provided.innerRef}>
|
className="flex flex-row py-2 my-2 rounded-md bg-gray-50 text-gray-500 hover:bg-gray-100 hover:scale-102 hover:shadow space-x-1 w-auto items-center ring-1 ring-inset ring-gray-400/10 shadow-sm transition-all delay-100 duration-75 ease-linear" key={props.activity.id} {...provided.draggableProps} {...provided.dragHandleProps} ref={provided.innerRef}>
|
||||||
<div className="px-3 text-gray-300 space-x-1 w-28" >
|
<div className="px-3 text-gray-300 space-x-1 w-28" >
|
||||||
{props.activity.type === "video" && <><div className="flex space-x-2 items-center"><Video size={16} /> <div className="text-xs bg-gray-200 text-gray-400 font-bold px-2 py-1 rounded-full">Video</div> </div></>}
|
{props.activity.type === "video" && <><div className="flex space-x-2 items-center"><Video size={16} /> <div className="text-xs bg-gray-200 text-gray-400 font-bold px-2 py-1 rounded-full">Video</div> </div></>}
|
||||||
{props.activity.type === "documentpdf" && <><div className="flex space-x-2 items-center"><FileText size={16} /> <div className="text-xs bg-gray-200 text-gray-400 font-bold px-2 py-1 rounded-full">Document</div> </div></>}
|
{props.activity.type === "documentpdf" && <><div className="flex space-x-2 items-center"><FileText size={16} /> <div className="text-xs bg-gray-200 text-gray-400 font-bold px-2 py-1 rounded-full">Document</div> </div></>}
|
||||||
{props.activity.type === "dynamic" && <><div className="flex space-x-2 items-center"><Sparkles size={16} /> <div className="text-xs bg-gray-200 text-gray-400 font-bold px-2 py-1 rounded-full">Dynamic</div> </div></>}
|
{props.activity.type === "dynamic" && <><div className="flex space-x-2 items-center"><Sparkles size={16} /> <div className="text-xs bg-gray-200 text-gray-400 font-bold px-2 py-1 rounded-full">Dynamic</div> </div></>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grow items-center space-x-1 flex">
|
<div className="grow items-center space-x-1 flex">
|
||||||
<p className="first-letter:uppercase"> {props.activity.name} </p>
|
<p className="first-letter:uppercase"> {props.activity.name} </p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row space-x-1 px-3"><Link
|
|
||||||
|
<div className="flex flex-row space-x-2">
|
||||||
|
<Link
|
||||||
href={getUriWithOrg(props.orgslug, "") + `/course/${props.courseid}/activity/${props.activity.id.replace("activity_", "")}`}
|
href={getUriWithOrg(props.orgslug, "") + `/course/${props.courseid}/activity/${props.activity.id.replace("activity_", "")}`}
|
||||||
className=" hover:cursor-pointer p-1 rounded-md "
|
className=" hover:cursor-pointer p-1 px-3 bg-gray-200 rounded-md"
|
||||||
rel="noopener noreferrer">
|
rel="noopener noreferrer">
|
||||||
<EyeOpenIcon className="text-gray-500" />
|
<Eye strokeWidth={2} size={15} className="text-gray-600" />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
{props.activity.type === "dynamic" && <>
|
||||||
<Link
|
<Link
|
||||||
href={getUriWithOrg(props.orgslug, "") + `/course/${props.courseid}/activity/${props.activity.id.replace("activity_", "")}/edit`}
|
href={getUriWithOrg(props.orgslug, "") + `/course/${props.courseid}/activity/${props.activity.id.replace("activity_", "")}/edit`}
|
||||||
className=" hover:cursor-pointer p-1 rounded-md "
|
className=" hover:cursor-pointer p-1 px-3 bg-sky-700 rounded-md items-center"
|
||||||
rel="noopener noreferrer">
|
rel="noopener noreferrer">
|
||||||
<Pencil2Icon className="text-gray-500" />
|
<div className="text-sky-100 font-bold text-xs" >Edit </div>
|
||||||
</Link></div>
|
</Link>
|
||||||
|
</>}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row pr-3 space-x-1 items-center">
|
||||||
|
<MoreVertical size={15} className="text-gray-300" />
|
||||||
|
<ConfirmationModal
|
||||||
|
confirmationMessage="Are you sure you want to delete this activity ?"
|
||||||
|
confirmationButtonText="Delete Activity"
|
||||||
|
dialogTitle={"Delete " + props.activity.name + " ?"}
|
||||||
|
dialogTrigger={
|
||||||
|
<div
|
||||||
|
className=" hover:cursor-pointer p-1 px-5 bg-red-600 rounded-md"
|
||||||
|
rel="noopener noreferrer">
|
||||||
|
<X size={15} className="text-rose-200 font-bold" />
|
||||||
|
</div>}
|
||||||
|
functionToExecute={() => removeActivity()}
|
||||||
|
status='warning'
|
||||||
|
></ConfirmationModal></div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Draggable>
|
</Draggable>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import React from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
|
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
|
||||||
import Activity from "./Activity";
|
import Activity from "./Activity";
|
||||||
import { PlusSquare, Sparkle, Sparkles, Trash, Trash2 } from "lucide-react";
|
import { Folders, Hexagon, MoreVertical, PlusSquare, Sparkle, Sparkles, Trash, Trash2, X } from "lucide-react";
|
||||||
import ConfirmationModal from "@components/StyledElements/ConfirmationModal/ConfirmationModal";
|
import ConfirmationModal from "@components/StyledElements/ConfirmationModal/ConfirmationModal";
|
||||||
|
|
||||||
function Chapter(props: any) {
|
function Chapter(props: any) {
|
||||||
|
|
@ -14,25 +14,34 @@ function Chapter(props: any) {
|
||||||
{...provided.draggableProps}
|
{...provided.draggableProps}
|
||||||
ref={provided.innerRef}
|
ref={provided.innerRef}
|
||||||
// isDragging={snapshot.isDragging}
|
// isDragging={snapshot.isDragging}
|
||||||
className="max-w-screen-2xl mx-auto"
|
className="max-w-screen-2xl mx-auto bg-white px-5"
|
||||||
key={props.info.list.chapter.id}
|
key={props.info.list.chapter.id}
|
||||||
>
|
>
|
||||||
<h3 className="flex space-x-2 pt-3 font-bold text-md items-center">
|
<div className="flex pt-3 pr-3 font-bold text-md items-center space-x-2">
|
||||||
<p>{props.info.list.chapter.name}
|
<div className="flex grow text-lg space-x-3 items-center rounded-md px-3 py-1">
|
||||||
</p>
|
<div className="bg-neutral-100 rounded-md p-2">
|
||||||
|
<Hexagon strokeWidth={3} size={16} className="text-neutral-600 " />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-neutral-700 first-letter:uppercase">{props.info.list.chapter.name}</p>
|
||||||
|
</div>
|
||||||
|
<MoreVertical size={15} className="text-gray-300" />
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
confirmationButtonText="Delete Chapter"
|
confirmationButtonText="Delete Chapter"
|
||||||
confirmationMessage="Are you sure you want to delete this chapter?"
|
confirmationMessage="Are you sure you want to delete this chapter?"
|
||||||
dialogTitle={"Delete " + props.info.list.chapter.name + " ?"}
|
dialogTitle={"Delete " + props.info.list.chapter.name + " ?"}
|
||||||
dialogTrigger={
|
dialogTrigger={
|
||||||
<button className="rounded-md text-sm px-3 font-bold text-red-800 bg-red-200 w-16 flex justify-center items-center" >
|
<div
|
||||||
Delete <Trash size={10}></Trash>
|
className=" hover:cursor-pointer p-1 px-4 bg-red-600 rounded-md shadow flex space-x-1 items-center text-rose-100 text-sm"
|
||||||
</button>}
|
rel="noopener noreferrer">
|
||||||
|
<X size={15} className="text-rose-200 font-bold" />
|
||||||
|
<p>Delete Chapter</p>
|
||||||
|
</div>}
|
||||||
functionToExecute={() => props.deleteChapter(props.info.list.chapter.id)}
|
functionToExecute={() => props.deleteChapter(props.info.list.chapter.id)}
|
||||||
status='warning'
|
status='warning'
|
||||||
></ConfirmationModal>
|
></ConfirmationModal>
|
||||||
|
|
||||||
</h3>
|
</div>
|
||||||
<Droppable key={props.info.list.chapter.id} droppableId={props.info.list.chapter.id} type="activity">
|
<Droppable key={props.info.list.chapter.id} droppableId={props.info.list.chapter.id} type="activity">
|
||||||
{(provided) => (
|
{(provided) => (
|
||||||
<ActivitiesList {...provided.droppableProps} ref={provided.innerRef}>
|
<ActivitiesList {...provided.droppableProps} ref={provided.innerRef}>
|
||||||
|
|
@ -62,8 +71,7 @@ function Chapter(props: any) {
|
||||||
|
|
||||||
const ChapterWrapper = styled.div`
|
const ChapterWrapper = styled.div`
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
padding: 4px;
|
padding: 12px;
|
||||||
background-color: #ffffff9d;
|
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
display: block;
|
display: block;
|
||||||
border-radius: 9px;
|
border-radius: 9px;
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,12 @@ export async function getActivity(activity_id: any, next: any) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteActivity(activity_id: any) {
|
||||||
|
const result = await fetch(`${getAPIUrl()}activities/${activity_id}`, RequestBody("DELETE", null, null));
|
||||||
|
const res = await result.json();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
export async function getActivityWithAuthHeader(activity_id: any, next: any, access_token: string) {
|
export async function getActivityWithAuthHeader(activity_id: any, next: any, access_token: string) {
|
||||||
const result = await fetch(`${getAPIUrl()}activities/activity_${activity_id}`, RequestBodyWithAuthHeader("GET", null, next, access_token));
|
const result = await fetch(`${getAPIUrl()}activities/activity_${activity_id}`, RequestBodyWithAuthHeader("GET", null, next, access_token));
|
||||||
const res = await result.json();
|
const res = await result.json();
|
||||||
|
|
|
||||||
|
|
@ -162,10 +162,18 @@ async def delete_activity(request: Request, activity_id: str, current_user: Publ
|
||||||
status_code=status.HTTP_409_CONFLICT, detail="activity does not exist"
|
status_code=status.HTTP_409_CONFLICT, detail="activity does not exist"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Remove Activity
|
||||||
isDeleted = await activities.delete_one({"activity_id": activity_id})
|
isDeleted = await activities.delete_one({"activity_id": activity_id})
|
||||||
|
|
||||||
if isDeleted:
|
# Remove Activity from chapter
|
||||||
return {"detail": "activity deleted"}
|
courses = request.app.db["courses"]
|
||||||
|
isDeletedFromChapter = await courses.update_one(
|
||||||
|
{"chapters_content.activities": activity_id},
|
||||||
|
{"$pull": {"chapters_content.$.activities": activity_id}},
|
||||||
|
)
|
||||||
|
|
||||||
|
if isDeleted and isDeletedFromChapter:
|
||||||
|
return {"detail": "Activity deleted"}
|
||||||
else:
|
else:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue