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_trail_with_orgid,
|
||||
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(
|
||||
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
|
||||
|
||||
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(
|
||||
request: Request,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { getAPIUrl, getUriWithOrg } from '@services/config/config'
|
|||
import Canva from '@components/Objects/Activities/DynamicCanva/DynamicCanva'
|
||||
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 { markActivityAsComplete } from '@services/courses/activity'
|
||||
import { markActivityAsComplete, unmarkActivityAsComplete } from '@services/courses/activity'
|
||||
import DocumentPdfActivity from '@components/Objects/Activities/DocumentPdf/DocumentPdf'
|
||||
import ActivityIndicators from '@components/Pages/Courses/ActivityIndicators'
|
||||
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 PaidCourseActivityDisclaimer from '@components/Objects/Courses/CourseActions/PaidCourseActivityDisclaimer'
|
||||
import { useContributorStatus } from '../../../../../../../../hooks/useContributorStatus'
|
||||
import ToolTip from '@components/Objects/StyledElements/Tooltip/Tooltip'
|
||||
|
||||
interface ActivityClientProps {
|
||||
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 = () => {
|
||||
let run = props.course.trail.runs.find(
|
||||
(run: any) => run.course_id == props.course.id
|
||||
|
|
@ -296,11 +317,33 @@ export function MarkStatus(props: {
|
|||
return (
|
||||
<>
|
||||
{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">
|
||||
<i>
|
||||
<Check size={17}></Check>
|
||||
</i>{' '}
|
||||
<i className="not-italic text-xs font-bold">Complete</i>
|
||||
<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>
|
||||
<Check size={17}></Check>
|
||||
</i>{' '}
|
||||
<i className="not-italic text-xs font-bold">Complete</i>
|
||||
</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
|
||||
|
|
|
|||
|
|
@ -36,3 +36,17 @@ export async function markActivityAsComplete(
|
|||
const res = await errorHandling(result)
|
||||
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