fix: trail related bugs + general design improvements

This commit is contained in:
swve 2023-12-14 16:41:28 +01:00
parent 53f40f3f34
commit 5b3c2fab24
17 changed files with 128 additions and 71 deletions

View file

@ -46,8 +46,12 @@ class TrailRunRead(BaseModel):
course_id: int = Field(default=None, foreign_key="course.id") course_id: int = Field(default=None, foreign_key="course.id")
org_id: int = Field(default=None, foreign_key="organization.id") org_id: int = Field(default=None, foreign_key="organization.id")
user_id: int = Field(default=None, foreign_key="user.id") user_id: int = Field(default=None, foreign_key="user.id")
# course object
course: dict
# timestamps # timestamps
creation_date: str creation_date: str
update_date: str update_date: str
# number of activities in course
course_total_steps: int
steps: list[TrailStep] steps: list[TrailStep]
pass pass

View file

@ -2,6 +2,8 @@ from enum import Enum
from typing import Optional from typing import Optional
from sqlalchemy import JSON, Column from sqlalchemy import JSON, Column
from sqlmodel import Field, SQLModel from sqlmodel import Field, SQLModel
from sqlalchemy import BigInteger, Column, ForeignKey
from sqlmodel import Field, SQLModel
class TrailStepTypeEnum(str, Enum): class TrailStepTypeEnum(str, Enum):
@ -17,7 +19,9 @@ class TrailStep(SQLModel, table=True):
grade: str grade: str
data: dict = Field(default={}, sa_column=Column(JSON)) data: dict = Field(default={}, sa_column=Column(JSON))
# foreign keys # foreign keys
trailrun_id: int = Field(default=None, foreign_key="trailrun.id") trailrun_id: int = Field(
sa_column=Column(BigInteger, ForeignKey("trailrun.id", ondelete="CASCADE"))
)
trail_id: int = Field(default=None, foreign_key="trail.id") trail_id: int = Field(default=None, foreign_key="trail.id")
activity_id: int = Field(default=None, foreign_key="activity.id") activity_id: int = Field(default=None, foreign_key="activity.id")
course_id: int = Field(default=None, foreign_key="course.id") course_id: int = Field(default=None, foreign_key="course.id")

View file

@ -85,7 +85,7 @@ async def api_remove_course_to_trail(
@router.post("/add_activity/{activity_uuid}") @router.post("/add_activity/{activity_uuid}")
async def api_add_activity_to_trail( async def api_add_activity_to_trail(
request: Request, request: Request,
activity_id: int, activity_uuid: str,
user=Depends(get_current_user), user=Depends(get_current_user),
db_session=Depends(get_db_session), db_session=Depends(get_db_session),
) -> TrailRead: ) -> TrailRead:
@ -93,5 +93,5 @@ async def api_add_activity_to_trail(
Add Course to trail Add Course to trail
""" """
return await add_activity_to_trail( return await add_activity_to_trail(
request, user, activity_id, db_session request, user, activity_uuid, db_session
) )

View file

@ -1,5 +1,6 @@
from datetime import datetime from datetime import datetime
from uuid import uuid4 from uuid import uuid4
from src.db.chapter_activities import ChapterActivity
from fastapi import HTTPException, Request, status from fastapi import HTTPException, Request, status
from sqlmodel import Session, select from sqlmodel import Session, select
from src.db.activities import Activity from src.db.activities import Activity
@ -57,9 +58,24 @@ async def get_user_trails(
trail_runs = db_session.exec(statement).all() trail_runs = db_session.exec(statement).all()
trail_runs = [ trail_runs = [
TrailRunRead(**trail_run.__dict__, steps=[]) for trail_run in trail_runs TrailRunRead(**trail_run.__dict__, course={}, steps=[], course_total_steps=0)
for trail_run in trail_runs
] ]
# Add course object and total activities in a course to trail runs
for trail_run in trail_runs:
statement = select(Course).where(Course.id == trail_run.course_id)
course = db_session.exec(statement).first()
trail_run.course = course
# Add number of activities (steps) in a course
statement = select(ChapterActivity).where(
ChapterActivity.course_id == trail_run.course_id
)
course_total_steps = db_session.exec(statement)
# count number of activities in a this list
trail_run.course_total_steps = len(course_total_steps.all())
for trail_run in trail_runs: for trail_run in trail_runs:
statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id) statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id)
trail_steps = db_session.exec(statement).all() trail_steps = db_session.exec(statement).all()
@ -95,9 +111,24 @@ async def get_user_trail_with_orgid(
trail_runs = db_session.exec(statement).all() trail_runs = db_session.exec(statement).all()
trail_runs = [ trail_runs = [
TrailRunRead(**trail_run.__dict__, steps=[]) for trail_run in trail_runs TrailRunRead(**trail_run.__dict__, course={}, steps=[], course_total_steps=0)
for trail_run in trail_runs
] ]
# Add course object and total activities in a course to trail runs
for trail_run in trail_runs:
statement = select(Course).where(Course.id == trail_run.course_id)
course = db_session.exec(statement).first()
trail_run.course = course
# Add number of activities (steps) in a course
statement = select(ChapterActivity).where(
ChapterActivity.course_id == trail_run.course_id
)
course_total_steps = db_session.exec(statement)
# count number of activities in a this list
trail_run.course_total_steps = len(course_total_steps.all())
for trail_run in trail_runs: for trail_run in trail_runs:
statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id) statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id)
trail_steps = db_session.exec(statement).all() trail_steps = db_session.exec(statement).all()
@ -121,11 +152,11 @@ async def get_user_trail_with_orgid(
async def add_activity_to_trail( async def add_activity_to_trail(
request: Request, request: Request,
user: PublicUser, user: PublicUser,
activity_id: int, activity_uuid: str,
db_session: Session, db_session: Session,
) -> TrailRead: ) -> TrailRead:
# Look for the activity # Look for the activity
statement = select(Activity).where(Activity.id == activity_id) statement = select(Activity).where(Activity.activity_uuid == activity_uuid)
activity = db_session.exec(statement).first() activity = db_session.exec(statement).first()
if not activity: if not activity:
@ -133,15 +164,6 @@ async def add_activity_to_trail(
status_code=status.HTTP_404_NOT_FOUND, detail="Activity not found" status_code=status.HTTP_404_NOT_FOUND, detail="Activity not found"
) )
# check if run already exists
statement = select(TrailRun).where(TrailRun.course_id == activity.course_id)
trailrun = db_session.exec(statement).first()
if trailrun:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail="TrailRun already exists"
)
statement = select(Course).where(Course.id == activity.course_id) statement = select(Course).where(Course.id == activity.course_id)
course = db_session.exec(statement).first() course = db_session.exec(statement).first()
@ -179,15 +201,16 @@ async def add_activity_to_trail(
db_session.refresh(trailrun) db_session.refresh(trailrun)
statement = select(TrailStep).where( statement = select(TrailStep).where(
TrailStep.trailrun_id == trailrun.id, TrailStep.activity_id == activity_id TrailStep.trailrun_id == trailrun.id, TrailStep.activity_id == activity.id
) )
trailstep = db_session.exec(statement).first() trailstep = db_session.exec(statement).first()
if not trailstep: if not trailstep:
trailstep = TrailStep( trailstep = TrailStep(
trailrun_id=trailrun.id if trailrun.id is not None else 0, trailrun_id=trailrun.id if trailrun.id is not None else 0,
activity_id=activity_id, activity_id=activity.id,
course_id=course.id if course.id is not None else 0, course_id=course.id if course.id is not None else 0,
trail_id=trail.id if trail.id is not None else 0,
org_id=course.org_id, org_id=course.org_id,
complete=False, complete=False,
teacher_verified=False, teacher_verified=False,
@ -204,7 +227,8 @@ async def add_activity_to_trail(
trail_runs = db_session.exec(statement).all() trail_runs = db_session.exec(statement).all()
trail_runs = [ trail_runs = [
TrailRunRead(**trail_run.__dict__, steps=[]) for trail_run in trail_runs TrailRunRead(**trail_run.__dict__, course={}, steps=[], course_total_steps=0)
for trail_run in trail_runs
] ]
for trail_run in trail_runs: for trail_run in trail_runs:
@ -282,7 +306,7 @@ async def add_course_to_trail(
trail_runs = db_session.exec(statement).all() trail_runs = db_session.exec(statement).all()
trail_runs = [ trail_runs = [
TrailRunRead(**trail_run.__dict__, steps=[]) for trail_run in trail_runs TrailRunRead(**trail_run.__dict__, course={}, steps=[], course_total_steps=0 ) for trail_run in trail_runs
] ]
for trail_run in trail_runs: for trail_run in trail_runs:
@ -338,12 +362,21 @@ async def remove_course_from_trail(
db_session.delete(trail_run) db_session.delete(trail_run)
db_session.commit() db_session.commit()
# Delete all trail steps for this course
statement = select(TrailStep).where(TrailStep.course_id == course.id)
trail_steps = db_session.exec(statement).all()
for trail_step in trail_steps:
db_session.delete(trail_step)
db_session.commit()
statement = select(TrailRun).where(TrailRun.trail_id == trail.id) statement = select(TrailRun).where(TrailRun.trail_id == trail.id)
trail_runs = db_session.exec(statement).all() trail_runs = db_session.exec(statement).all()
trail_runs = [ trail_runs = [
TrailRunRead(**trail_run.__dict__, steps=[]) for trail_run in trail_runs TrailRunRead(**trail_run.__dict__, course={}, steps=[], course_total_steps=0 ) for trail_run in trail_runs
] ]
for trail_run in trail_runs: for trail_run in trail_runs:
statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id) statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id)
trail_steps = db_session.exec(statement).all() trail_steps = db_session.exec(statement).all()

View file

@ -8,7 +8,7 @@ import { Metadata } from "next";
import { cookies } from "next/headers"; import { cookies } from "next/headers";
import Link from "next/link"; import Link from "next/link";
import { getAccessTokenFromRefreshTokenCookie } from "@services/auth/auth"; import { getAccessTokenFromRefreshTokenCookie } from "@services/auth/auth";
import CollectionThumbnail from "@components/Objects/Other/CollectionThumbnail"; import CollectionThumbnail from "@components/Objects/Thumbnails/CollectionThumbnail";
import NewCollectionButton from "@components/StyledElements/Buttons/NewCollectionButton"; import NewCollectionButton from "@components/StyledElements/Buttons/NewCollectionButton";
type MetadataProps = { type MetadataProps = {

View file

@ -57,7 +57,7 @@ function ActivityClient(props: ActivityClientProps) {
<h1 className="font-bold text-gray-950 text-2xl first-letter:uppercase" >{course.name}</h1> <h1 className="font-bold text-gray-950 text-2xl first-letter:uppercase" >{course.name}</h1>
</div> </div>
</div> </div>
<ActivityIndicators course_uuid={courseuuid} current_activity={activityid} orgslug={orgslug} course={course} /> <ActivityIndicators course_uuid={courseuuid} current_activity={activityid} activity={activity} orgslug={orgslug} course={course} />
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div className="flex flex-col -space-y-1"> <div className="flex flex-col -space-y-1">
@ -66,7 +66,7 @@ function ActivityClient(props: ActivityClientProps) {
</div> </div>
<div className="flex space-x-2"> <div className="flex space-x-2">
<AuthenticatedClientElement checkMethod="authentication"> <AuthenticatedClientElement checkMethod="authentication">
<MarkStatus activityid={activityid} course={course} orgslug={orgslug} courseid={courseuuid} /> <MarkStatus activity={activity} activityid={activityid} course={course} orgslug={orgslug} />
</AuthenticatedClientElement> </AuthenticatedClientElement>
</div> </div>
@ -91,23 +91,26 @@ function ActivityClient(props: ActivityClientProps) {
export function MarkStatus(props: { activityid: string, course: any, orgslug: string, courseid: string }) { export function MarkStatus(props: { activity: any, activityid: string, course: any, orgslug: string }) {
const router = useRouter(); const router = useRouter();
console.log(props.course.trail)
async function markActivityAsCompleteFront() { async function markActivityAsCompleteFront() {
const trail = await markActivityAsComplete(props.orgslug, props.courseid, props.activityid); const trail = await markActivityAsComplete(props.orgslug, props.course.course_uuid, 'activity_' + props.activityid);
router.refresh(); router.refresh();
// refresh page (FIX for Next.js BUG)
//window.location.reload();
} }
const isActivityCompleted = () => {
let run = props.course.trail.runs.find((run: any) => run.course_id == props.course.id);
if (run) {
return run.steps.find((step: any) => step.activity_id == props.activity.id);
}
}
console.log('isActivityCompleted', isActivityCompleted());
return ( return (
<>{props.course.trail.activities_marked_complete && <>{ isActivityCompleted() ? (
props.course.trail.activities_marked_complete.includes("activity_" + props.activityid) &&
props.course.trail.status == "ongoing" ? (
<div className="bg-teal-600 rounded-md drop-shadow-md flex flex-col p-3 text-sm text-white hover:cursor-pointer transition delay-150 duration-300 ease-in-out" > <div className="bg-teal-600 rounded-md drop-shadow-md flex flex-col p-3 text-sm text-white hover:cursor-pointer transition delay-150 duration-300 ease-in-out" >
<i> <i>
<Check size={15}></Check> <Check size={15}></Check>

View file

@ -6,7 +6,7 @@ import { useSearchParams } from 'next/navigation';
import GeneralWrapperStyled from '@components/StyledElements/Wrappers/GeneralWrapper'; import GeneralWrapperStyled from '@components/StyledElements/Wrappers/GeneralWrapper';
import TypeOfContentTitle from '@components/StyledElements/Titles/TypeOfContentTitle'; import TypeOfContentTitle from '@components/StyledElements/Titles/TypeOfContentTitle';
import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement'; import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement';
import CourseThumbnail from '@components/Objects/Other/CourseThumbnail'; import CourseThumbnail from '@components/Objects/Thumbnails/CourseThumbnail';
import NewCourseButton from '@components/StyledElements/Buttons/NewCourseButton'; import NewCourseButton from '@components/StyledElements/Buttons/NewCourseButton';
interface CourseProps { interface CourseProps {

View file

@ -9,8 +9,8 @@ import { cookies } from 'next/headers';
import GeneralWrapperStyled from '@components/StyledElements/Wrappers/GeneralWrapper'; import GeneralWrapperStyled from '@components/StyledElements/Wrappers/GeneralWrapper';
import TypeOfContentTitle from '@components/StyledElements/Titles/TypeOfContentTitle'; import TypeOfContentTitle from '@components/StyledElements/Titles/TypeOfContentTitle';
import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth'; import { getAccessTokenFromRefreshTokenCookie } from '@services/auth/auth';
import CourseThumbnail from '@components/Objects/Other/CourseThumbnail'; import CourseThumbnail from '@components/Objects/Thumbnails/CourseThumbnail';
import CollectionThumbnail from '@components/Objects/Other/CollectionThumbnail'; import CollectionThumbnail from '@components/Objects/Thumbnails/CollectionThumbnail';
import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement'; import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement';
import { Plus, PlusCircle } from 'lucide-react'; import { Plus, PlusCircle } from 'lucide-react';
import NewCourseButton from '@components/StyledElements/Buttons/NewCourseButton'; import NewCourseButton from '@components/StyledElements/Buttons/NewCourseButton';

View file

@ -1,4 +1,5 @@
"use client"; "use client";
import { useOrg } from "@components/Contexts/OrgContext";
import PageLoading from "@components/Objects/Loaders/PageLoading"; import PageLoading from "@components/Objects/Loaders/PageLoading";
import TrailCourseElement from "@components/Pages/Trail/TrailCourseElement"; import TrailCourseElement from "@components/Pages/Trail/TrailCourseElement";
import TypeOfContentTitle from "@components/StyledElements/Titles/TypeOfContentTitle"; import TypeOfContentTitle from "@components/StyledElements/Titles/TypeOfContentTitle";
@ -6,13 +7,18 @@ import GeneralWrapperStyled from "@components/StyledElements/Wrappers/GeneralWra
import { getAPIUrl } from "@services/config/config"; import { getAPIUrl } from "@services/config/config";
import { removeCourse } from "@services/courses/activity"; import { removeCourse } from "@services/courses/activity";
import { revalidateTags, swrFetcher } from "@services/utils/ts/requests"; import { revalidateTags, swrFetcher } from "@services/utils/ts/requests";
import React from "react"; import React, { useEffect } from "react";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
function Trail(params: any) { function Trail(params: any) {
let orgslug = params.orgslug; let orgslug = params.orgslug;
const { data: trail, error: error } = useSWR(`${getAPIUrl()}trail/org_slug/${orgslug}/trail`, swrFetcher); const org = useOrg() as any;
const orgID = org?.id;
const { data: trail, error: error } = useSWR(`${getAPIUrl()}trail/org/${orgID}/trail`, swrFetcher);
useEffect(() => {
}
, [trail,org]);
return ( return (
<GeneralWrapperStyled> <GeneralWrapperStyled>
@ -21,12 +27,10 @@ function Trail(params: any) {
<PageLoading></PageLoading> <PageLoading></PageLoading>
) : ( ) : (
<div className="space-y-6"> <div className="space-y-6">
{trail.courses.map((course: any) => ( {trail.runs.map((run: any) => (
!course.masked ? ( <>
<TrailCourseElement key={trail.trail_id} orgslug={orgslug} course={course} /> <TrailCourseElement run={run} course={run.course} orgslug={orgslug} />
) : ( </>
<></>
)
))} ))}

View file

@ -1,7 +1,7 @@
'use client'; 'use client';
import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs' import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'
import CreateCourseModal from '@components/Objects/Modals/Course/Create/CreateCourse'; import CreateCourseModal from '@components/Objects/Modals/Course/Create/CreateCourse';
import CourseThumbnail from '@components/Objects/Other/CourseThumbnail'; import CourseThumbnail from '@components/Objects/Thumbnails/CourseThumbnail';
import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement'; import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement';
import NewCourseButton from '@components/StyledElements/Buttons/NewCourseButton'; import NewCourseButton from '@components/StyledElements/Buttons/NewCourseButton';
import Modal from '@components/StyledElements/Modal/Modal'; import Modal from '@components/StyledElements/Modal/Modal';

View file

@ -69,7 +69,6 @@ function OrgEditGeneral(props: any) {
initialValues={orgValues} initialValues={orgValues}
onSubmit={(values, { setSubmitting }) => { onSubmit={(values, { setSubmitting }) => {
setTimeout(() => { setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false); setSubmitting(false);
updateOrg(values) updateOrg(values)
}, 400); }, 400);

View file

@ -4,7 +4,7 @@ import { useAuth } from '@components/Security/AuthContext';
import ToolTip from '@components/StyledElements/Tooltip/Tooltip' import ToolTip from '@components/StyledElements/Tooltip/Tooltip'
import LearnHouseDashboardLogo from '@public/dashLogo.png'; import LearnHouseDashboardLogo from '@public/dashLogo.png';
import Avvvatars from 'avvvatars-react'; import Avvvatars from 'avvvatars-react';
import { ArrowLeft, Book, Home, School, Settings } from 'lucide-react' import { ArrowLeft, Book, BookCopy, Home, School, Settings } from 'lucide-react'
import Image from 'next/image'; import Image from 'next/image';
import Link from 'next/link' import Link from 'next/link'
import React, { use, useEffect } from 'react' import React, { use, useEffect } from 'react'
@ -52,7 +52,7 @@ function LeftMenu() {
<Link className='bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/dash`} ><Home size={18} /></Link> <Link className='bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/dash`} ><Home size={18} /></Link>
</ToolTip> </ToolTip>
<ToolTip content={"Courses"} slateBlack sideOffset={8} side='right' > <ToolTip content={"Courses"} slateBlack sideOffset={8} side='right' >
<Link className='bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/dash/courses`} ><Book size={18} /></Link> <Link className='bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/dash/courses`} ><BookCopy size={18} /></Link>
</ToolTip> </ToolTip>
<ToolTip content={"Organization"} slateBlack sideOffset={8} side='right' > <ToolTip content={"Organization"} slateBlack sideOffset={8} side='right' >
<Link className='bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/dash/org/settings/general`} ><School size={18} /></Link> <Link className='bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/dash/org/settings/general`} ><School size={18} /></Link>

View file

@ -2,11 +2,12 @@
import { useOrg } from '@components/Contexts/OrgContext'; import { useOrg } from '@components/Contexts/OrgContext';
import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement'; import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement';
import ConfirmationModal from '@components/StyledElements/ConfirmationModal/ConfirmationModal'; import ConfirmationModal from '@components/StyledElements/ConfirmationModal/ConfirmationModal';
import { DotsHorizontalIcon } from '@radix-ui/react-icons';
import { getUriWithOrg } from '@services/config/config'; import { getUriWithOrg } from '@services/config/config';
import { deleteCourseFromBackend } from '@services/courses/courses'; import { deleteCourseFromBackend } from '@services/courses/courses';
import { getCourseThumbnailMediaDirectory } from '@services/media/media'; import { getCourseThumbnailMediaDirectory } from '@services/media/media';
import { revalidateTags } from '@services/utils/ts/requests'; import { revalidateTags } from '@services/utils/ts/requests';
import { FileEdit, X } from 'lucide-react'; import { FileEdit, MoreHorizontal, Settings, X } from 'lucide-react';
import Link from 'next/link'; import Link from 'next/link';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import React, { use, useEffect } from 'react' import React, { use, useEffect } from 'react'
@ -55,12 +56,12 @@ const AdminEditsArea = (props: { orgSlug: string, courseId: string, course: any,
action="update" action="update"
ressourceType="course" ressourceType="course"
checkMethod='roles' orgId={props.course.org_id}> checkMethod='roles' orgId={props.course.org_id}>
<div className="flex space-x-1 absolute justify-center mx-auto z-20 bottom-14 left-1/2 transform -translate-x-1/2"> <div className="flex space-x-2 absolute z-20 bottom-14 right-[15px] transform">
<Link href={getUriWithOrg(props.orgSlug, "/dash/courses/course/" + removeCoursePrefix(props.courseId) + "/general")}> <Link href={getUriWithOrg(props.orgSlug, "/dash/courses/course/" + removeCoursePrefix(props.courseId) + "/general")}>
<div <div
className=" hover:cursor-pointer p-1 px-4 bg-orange-600 rounded-xl items-center justify-center flex shadow-xl" className=" hover:cursor-pointer p-1 px-4 bg-slate-700 rounded-xl items-center flex shadow-xl"
rel="noopener noreferrer"> rel="noopener noreferrer">
<FileEdit size={14} className="text-orange-200 font-bold" /> <Settings size={14} className="text-slate-200 font-bold" />
</div> </div>
</Link> </Link>
<ConfirmationModal <ConfirmationModal

View file

@ -8,6 +8,7 @@ interface Props {
course: any course: any
orgslug: string orgslug: string
course_uuid: string course_uuid: string
activity: any
current_activity?: any current_activity?: any
} }
@ -24,15 +25,11 @@ function ActivityIndicators(props: Props) {
function isActivityDone(activity: any) { function isActivityDone(activity: any) {
const runs = course.trail.runs; let run = props.course.trail.runs.find((run: any) => run.course_id == props.course.id);
for (let run of runs) { if (run) {
for (let step of run.steps) { return run.steps.find((step: any) => step.activity_id == activity.id);
if (step.activity_id === activity.id && step.complete === true) {
return false;
} }
}
}
return false;
} }
function isActivityCurrent(activity: any) { function isActivityCurrent(activity: any) {

View file

@ -1,21 +1,29 @@
'use client'; 'use client';
import { useOrg } from '@components/Contexts/OrgContext';
import { getAPIUrl, getBackendUrl, getUriWithOrg } from '@services/config/config'; import { getAPIUrl, getBackendUrl, getUriWithOrg } from '@services/config/config';
import { removeCourse } from '@services/courses/activity'; import { removeCourse } from '@services/courses/activity';
import { getCourseThumbnailMediaDirectory } from '@services/media/media'; import { getCourseThumbnailMediaDirectory } from '@services/media/media';
import { revalidateTags } from '@services/utils/ts/requests'; import { revalidateTags } from '@services/utils/ts/requests';
import Link from 'next/link'; import Link from 'next/link';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { use, useEffect } from 'react';
import { mutate } from 'swr'; import { mutate } from 'swr';
interface TrailCourseElementProps { interface TrailCourseElementProps {
course: any course: any
run: any
orgslug: string orgslug: string
} }
function TrailCourseElement(props: TrailCourseElementProps) { function TrailCourseElement(props: TrailCourseElementProps) {
const org = useOrg() as any;
const courseid = props.course.course_uuid.replace("course_", "") const courseid = props.course.course_uuid.replace("course_", "")
const course = props.course const course = props.course
const router = useRouter(); const router = useRouter();
const course_total_steps = props.run.course_total_steps
const course_completed_steps = props.run.steps.length
const orgID = org?.id;
const course_progress = Math.round((course_completed_steps / course_total_steps) * 100)
async function quitCourse(course_uuid: string) { async function quitCourse(course_uuid: string) {
// Close activity // Close activity
@ -25,14 +33,18 @@ function TrailCourseElement(props: TrailCourseElementProps) {
router.refresh(); router.refresh();
// Mutate // Mutate
mutate(`${getAPIUrl()}trail/org_slug/${props.orgslug}/trail`); mutate(`${getAPIUrl()}trail/org/${orgID}/trail`);
} }
useEffect(() => {
}
, [props.course, org]);
return ( return (
<div className='trailcoursebox flex p-3 bg-white rounded-xl' style={{ boxShadow: '0px 4px 7px 0px rgba(0, 0, 0, 0.03)' }}> <div className='trailcoursebox flex p-3 bg-white rounded-xl' style={{ boxShadow: '0px 4px 7px 0px rgba(0, 0, 0, 0.03)' }}>
<Link href={getUriWithOrg(props.orgslug, "/course/" + courseid)}> <Link href={getUriWithOrg(props.orgslug, "/course/" + courseid)}>
<div className="course_tumbnail inset-0 ring-1 ring-inset ring-black/10 rounded-lg relative h-[50px] w-[72px] bg-cover bg-center" style={{ backgroundImage: `url(${getCourseThumbnailMediaDirectory(props.course.course_object.org_id, props.course.course_object.course_uuid, props.course.course_object.thumbnail)})`, boxShadow: '0px 4px 7px 0px rgba(0, 0, 0, 0.03)' }}></div> <div className="course_tumbnail inset-0 ring-1 ring-inset ring-black/10 rounded-lg relative h-[50px] w-[72px] bg-cover bg-center" style={{ backgroundImage: `url(${getCourseThumbnailMediaDirectory(org.org_uuid, props.course.course_uuid, props.course.thumbnail_image)})`, boxShadow: '0px 4px 7px 0px rgba(0, 0, 0, 0.03)' }}></div>
</Link> </Link>
<div className="course_meta pl-5 flex-grow space-y-1"> <div className="course_meta pl-5 flex-grow space-y-1">
<div className="course_top"> <div className="course_top">
@ -40,9 +52,9 @@ function TrailCourseElement(props: TrailCourseElementProps) {
<div className="course_basic flex flex-col flex-end -space-y-2"> <div className="course_basic flex flex-col flex-end -space-y-2">
<p className='p-0 font-bold text-sm text-gray-700'>Course</p> <p className='p-0 font-bold text-sm text-gray-700'>Course</p>
<div className="course_progress flex items-center space-x-2"> <div className="course_progress flex items-center space-x-2">
<h2 className='font-bold text-xl'>{course.course_object.name}</h2> <h2 className='font-bold text-xl'>{course.name}</h2>
<div className='bg-slate-300 rounded-full w-[10px] h-[5px]'></div> <div className='bg-slate-300 rounded-full w-[10px] h-[5px]'></div>
<h2>{course.progress}%</h2> <h2>{course_progress}%</h2>
</div> </div>
</div> </div>
<div className="course_actions flex-grow flex flex-row-reverse"> <div className="course_actions flex-grow flex flex-row-reverse">
@ -52,7 +64,7 @@ function TrailCourseElement(props: TrailCourseElementProps) {
</div> </div>
<div className="course_progress indicator w-full"> <div className="course_progress indicator w-full">
<div className="w-full bg-gray-200 rounded-full h-1.5 "> <div className="w-full bg-gray-200 rounded-full h-1.5 ">
<div className={`bg-teal-600 h-1.5 rounded-full`} style={{ width: `${course.progress}%` }} ></div> <div className={`bg-teal-600 h-1.5 rounded-full`} style={{ width: `${course_progress}%` }} ></div>
</div> </div>
</div> </div>

View file

@ -7,19 +7,19 @@ import { getAPIUrl } from "@services/config/config";
*/ */
export async function startCourse(course_uuid: string, org_slug: string) { export async function startCourse(course_uuid: string, org_slug: string) {
const result: any = await fetch(`${getAPIUrl()}trail/add_course/${course_uuid}`, RequestBody("POST", null, null)) const result: any = await fetch(`${getAPIUrl()}trail/add_course/${course_uuid}`, RequestBody("POST", null, null));
const res = await errorHandling(result); const res = await errorHandling(result);
return res; return res;
} }
export async function removeCourse(course_uuid: string, org_slug: string) { export async function removeCourse(course_uuid: string, org_slug: string) {
const result: any = await fetch(`${getAPIUrl()}trail/remove_course/${course_uuid}`, RequestBody("DELETE", null, null)) const result: any = await fetch(`${getAPIUrl()}trail/remove_course/${course_uuid}`, RequestBody("DELETE", null, null));
const res = await errorHandling(result); const res = await errorHandling(result);
return res; return res;
} }
export async function markActivityAsComplete(org_slug: string, course_uuid: string, activity_id: string) { export async function markActivityAsComplete(org_slug: string, course_uuid: string, activity_uuid: string) {
const result: any = await fetch(`${getAPIUrl()}trail/add_activity/${activity_id}`, RequestBody("POST", null, null)) const result: any = await fetch(`${getAPIUrl()}trail/add_activity/${activity_uuid}`, RequestBody("POST", null, null));
const res = await errorHandling(result); const res = await errorHandling(result);
return res; return res;
} }