mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: implement activity removal from trail and update UI for unmarking activities
This commit is contained in:
parent
b25505465b
commit
1350cb7354
4 changed files with 150 additions and 6 deletions
|
|
@ -10,6 +10,7 @@ from src.services.trail.trail import (
|
||||||
get_user_trails,
|
get_user_trails,
|
||||||
get_user_trail_with_orgid,
|
get_user_trail_with_orgid,
|
||||||
remove_course_from_trail,
|
remove_course_from_trail,
|
||||||
|
remove_activity_from_trail,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -95,3 +96,16 @@ async def api_add_activity_to_trail(
|
||||||
return await add_activity_to_trail(
|
return await add_activity_to_trail(
|
||||||
request, user, activity_uuid, db_session
|
request, user, activity_uuid, db_session
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/remove_activity/{activity_uuid}")
|
||||||
|
async def api_remove_activity_from_trail(
|
||||||
|
request: Request,
|
||||||
|
activity_uuid: str,
|
||||||
|
user=Depends(get_current_user),
|
||||||
|
db_session=Depends(get_db_session),
|
||||||
|
) -> TrailRead:
|
||||||
|
"""
|
||||||
|
Remove Activity from trail
|
||||||
|
"""
|
||||||
|
return await remove_activity_from_trail(request, user, activity_uuid, db_session)
|
||||||
|
|
|
||||||
|
|
@ -282,6 +282,79 @@ async def add_activity_to_trail(
|
||||||
|
|
||||||
return trail_read
|
return trail_read
|
||||||
|
|
||||||
|
async def remove_activity_from_trail(
|
||||||
|
request: Request,
|
||||||
|
user: PublicUser,
|
||||||
|
activity_uuid: str,
|
||||||
|
db_session: Session,
|
||||||
|
) -> TrailRead:
|
||||||
|
# Look for the activity
|
||||||
|
statement = select(Activity).where(Activity.activity_uuid == activity_uuid)
|
||||||
|
activity = db_session.exec(statement).first()
|
||||||
|
|
||||||
|
if not activity:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND, detail="Activity not found"
|
||||||
|
)
|
||||||
|
|
||||||
|
statement = select(Course).where(Course.id == activity.course_id)
|
||||||
|
course = db_session.exec(statement).first()
|
||||||
|
|
||||||
|
if not course:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND, detail="Course not found"
|
||||||
|
)
|
||||||
|
|
||||||
|
statement = select(Trail).where(
|
||||||
|
Trail.org_id == course.org_id, Trail.user_id == user.id
|
||||||
|
)
|
||||||
|
trail = db_session.exec(statement).first()
|
||||||
|
|
||||||
|
if not trail:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND, detail="Trail not found"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Delete the trail step for this activity
|
||||||
|
statement = select(TrailStep).where(
|
||||||
|
TrailStep.activity_id == activity.id,
|
||||||
|
TrailStep.user_id == user.id,
|
||||||
|
TrailStep.trail_id == trail.id
|
||||||
|
)
|
||||||
|
trail_step = db_session.exec(statement).first()
|
||||||
|
|
||||||
|
if trail_step:
|
||||||
|
db_session.delete(trail_step)
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
# Get updated trail data
|
||||||
|
statement = select(TrailRun).where(TrailRun.trail_id == trail.id, TrailRun.user_id == user.id)
|
||||||
|
trail_runs = db_session.exec(statement).all()
|
||||||
|
|
||||||
|
trail_runs = [
|
||||||
|
TrailRunRead(**trail_run.__dict__, course={}, steps=[], course_total_steps=0)
|
||||||
|
for trail_run in trail_runs
|
||||||
|
]
|
||||||
|
|
||||||
|
for trail_run in trail_runs:
|
||||||
|
statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id, TrailStep.user_id == user.id)
|
||||||
|
trail_steps = db_session.exec(statement).all()
|
||||||
|
|
||||||
|
trail_steps = [TrailStep(**trail_step.__dict__) for trail_step in trail_steps]
|
||||||
|
trail_run.steps = trail_steps
|
||||||
|
|
||||||
|
for trail_step in trail_steps:
|
||||||
|
statement = select(Course).where(Course.id == trail_step.course_id)
|
||||||
|
course = db_session.exec(statement).first()
|
||||||
|
trail_step.data = dict(course=course)
|
||||||
|
|
||||||
|
trail_read = TrailRead(
|
||||||
|
**trail.model_dump(),
|
||||||
|
runs=trail_runs,
|
||||||
|
)
|
||||||
|
|
||||||
|
return trail_read
|
||||||
|
|
||||||
|
|
||||||
async def add_course_to_trail(
|
async def add_course_to_trail(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { getAPIUrl, getUriWithOrg } from '@services/config/config'
|
||||||
import Canva from '@components/Objects/Activities/DynamicCanva/DynamicCanva'
|
import Canva from '@components/Objects/Activities/DynamicCanva/DynamicCanva'
|
||||||
import VideoActivity from '@components/Objects/Activities/Video/Video'
|
import VideoActivity from '@components/Objects/Activities/Video/Video'
|
||||||
import { BookOpenCheck, Check, CheckCircle, ChevronDown, ChevronLeft, ChevronRight, FileText, Folder, List, Menu, MoreVertical, UserRoundPen, Video, Layers, ListFilter, ListTree, X, Edit2 } from 'lucide-react'
|
import { BookOpenCheck, Check, CheckCircle, ChevronDown, ChevronLeft, ChevronRight, FileText, Folder, List, Menu, MoreVertical, UserRoundPen, Video, Layers, ListFilter, ListTree, X, Edit2 } from 'lucide-react'
|
||||||
import { markActivityAsComplete } from '@services/courses/activity'
|
import { markActivityAsComplete, unmarkActivityAsComplete } from '@services/courses/activity'
|
||||||
import DocumentPdfActivity from '@components/Objects/Activities/DocumentPdf/DocumentPdf'
|
import DocumentPdfActivity from '@components/Objects/Activities/DocumentPdf/DocumentPdf'
|
||||||
import ActivityIndicators from '@components/Pages/Courses/ActivityIndicators'
|
import ActivityIndicators from '@components/Pages/Courses/ActivityIndicators'
|
||||||
import GeneralWrapperStyled from '@components/Objects/StyledElements/Wrappers/GeneralWrapper'
|
import GeneralWrapperStyled from '@components/Objects/StyledElements/Wrappers/GeneralWrapper'
|
||||||
|
|
@ -28,6 +28,7 @@ import ConfirmationModal from '@components/Objects/StyledElements/ConfirmationMo
|
||||||
import { useMediaQuery } from 'usehooks-ts'
|
import { useMediaQuery } from 'usehooks-ts'
|
||||||
import PaidCourseActivityDisclaimer from '@components/Objects/Courses/CourseActions/PaidCourseActivityDisclaimer'
|
import PaidCourseActivityDisclaimer from '@components/Objects/Courses/CourseActions/PaidCourseActivityDisclaimer'
|
||||||
import { useContributorStatus } from '../../../../../../../../hooks/useContributorStatus'
|
import { useContributorStatus } from '../../../../../../../../hooks/useContributorStatus'
|
||||||
|
import ToolTip from '@components/Objects/StyledElements/Tooltip/Tooltip'
|
||||||
|
|
||||||
interface ActivityClientProps {
|
interface ActivityClientProps {
|
||||||
activityid: string
|
activityid: string
|
||||||
|
|
@ -282,6 +283,26 @@ export function MarkStatus(props: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function unmarkActivityAsCompleteFront() {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
const trail = await unmarkActivityAsComplete(
|
||||||
|
props.orgslug,
|
||||||
|
props.course.course_uuid,
|
||||||
|
props.activity.activity_uuid,
|
||||||
|
session.data?.tokens?.access_token
|
||||||
|
);
|
||||||
|
|
||||||
|
// Mutate the course data to trigger re-render
|
||||||
|
await mutate(`${getAPIUrl()}courses/${props.course.course_uuid}/meta`);
|
||||||
|
router.refresh();
|
||||||
|
} catch (error) {
|
||||||
|
toast.error('Failed to unmark activity as complete');
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const isActivityCompleted = () => {
|
const isActivityCompleted = () => {
|
||||||
let run = props.course.trail.runs.find(
|
let run = props.course.trail.runs.find(
|
||||||
(run: any) => run.course_id == props.course.id
|
(run: any) => run.course_id == props.course.id
|
||||||
|
|
@ -296,12 +317,34 @@ export function MarkStatus(props: {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isActivityCompleted() ? (
|
{isActivityCompleted() ? (
|
||||||
<div className="bg-teal-600 rounded-full px-5 drop-shadow-md flex items-center space-x-2 p-2.5 text-white hover:cursor-pointer transition delay-150 duration-300 ease-in-out">
|
<div className="flex items-center space-x-2">
|
||||||
|
<div className="bg-teal-600 rounded-full px-5 drop-shadow-md flex items-center space-x-2 p-2.5 text-white">
|
||||||
<i>
|
<i>
|
||||||
<Check size={17}></Check>
|
<Check size={17}></Check>
|
||||||
</i>{' '}
|
</i>{' '}
|
||||||
<i className="not-italic text-xs font-bold">Complete</i>
|
<i className="not-italic text-xs font-bold">Complete</i>
|
||||||
</div>
|
</div>
|
||||||
|
<ToolTip
|
||||||
|
content="Unmark as complete"
|
||||||
|
side="top"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`${isLoading ? 'opacity-75 cursor-not-allowed' : ''} bg-red-400 rounded-full p-2 drop-shadow-md flex items-center text-white hover:cursor-pointer transition delay-150 duration-300 ease-in-out`}
|
||||||
|
onClick={!isLoading ? unmarkActivityAsCompleteFront : undefined}
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="animate-spin">
|
||||||
|
<svg className="w-4 h-4" viewBox="0 0 24 24">
|
||||||
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none"></circle>
|
||||||
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<X size={17} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ToolTip>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className={`${isLoading ? 'opacity-75 cursor-not-allowed' : ''} bg-gray-800 rounded-full px-5 drop-shadow-md flex items-center space-x-2 p-2.5 text-white hover:cursor-pointer transition delay-150 duration-300 ease-in-out`}
|
className={`${isLoading ? 'opacity-75 cursor-not-allowed' : ''} bg-gray-800 rounded-full px-5 drop-shadow-md flex items-center space-x-2 p-2.5 text-white hover:cursor-pointer transition delay-150 duration-300 ease-in-out`}
|
||||||
|
|
|
||||||
|
|
@ -36,3 +36,17 @@ export async function markActivityAsComplete(
|
||||||
const res = await errorHandling(result)
|
const res = await errorHandling(result)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function unmarkActivityAsComplete(
|
||||||
|
org_slug: string,
|
||||||
|
course_uuid: string,
|
||||||
|
activity_uuid: string,
|
||||||
|
access_token: any
|
||||||
|
) {
|
||||||
|
const result: any = await fetch(
|
||||||
|
`${getAPIUrl()}trail/remove_activity/${activity_uuid}`,
|
||||||
|
RequestBodyWithAuthHeader('DELETE', null, null, access_token)
|
||||||
|
)
|
||||||
|
const res = await errorHandling(result)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue