mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
fix: trail related bugs + general design improvements
This commit is contained in:
parent
53f40f3f34
commit
5b3c2fab24
17 changed files with 128 additions and 71 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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} />
|
||||||
) : (
|
</>
|
||||||
<></>
|
|
||||||
)
|
|
||||||
|
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue