mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: improve activity indicators UI + remove all course progress
This commit is contained in:
parent
59bae82ee7
commit
1dd100352b
3 changed files with 157 additions and 41 deletions
|
|
@ -274,7 +274,7 @@ function ActivityClient(props: ActivityClientProps) {
|
||||||
for (let j = 0; j < chapter.activities.length; j++) {
|
for (let j = 0; j < chapter.activities.length; j++) {
|
||||||
let activity = chapter.activities[j]
|
let activity = chapter.activities[j]
|
||||||
if (activity.id === activity_id) {
|
if (activity.id === activity_id) {
|
||||||
return chapter.name
|
return `Chapter ${i + 1} : ${chapter.name}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -570,7 +570,7 @@ function ActivityClient(props: ActivityClientProps) {
|
||||||
<div className="flex flex-1/3 items-center space-x-3">
|
<div className="flex flex-1/3 items-center space-x-3">
|
||||||
<div className="flex flex-col -space-y-1">
|
<div className="flex flex-col -space-y-1">
|
||||||
<p className="font-bold text-gray-700 text-md">
|
<p className="font-bold text-gray-700 text-md">
|
||||||
Chapter : {getChapterNameByActivityId(course, activity.id)}
|
{getChapterNameByActivityId(course, activity.id)}
|
||||||
</p>
|
</p>
|
||||||
<h1 className="font-bold text-gray-950 text-2xl first-letter:uppercase">
|
<h1 className="font-bold text-gray-950 text-2xl first-letter:uppercase">
|
||||||
{activity.name}
|
{activity.name}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,11 @@ import TypeOfContentTitle from '@components/Objects/StyledElements/Titles/TypeOf
|
||||||
import GeneralWrapperStyled from '@components/Objects/StyledElements/Wrappers/GeneralWrapper'
|
import GeneralWrapperStyled from '@components/Objects/StyledElements/Wrappers/GeneralWrapper'
|
||||||
import { getAPIUrl } from '@services/config/config'
|
import { getAPIUrl } from '@services/config/config'
|
||||||
import { swrFetcher } from '@services/utils/ts/requests'
|
import { swrFetcher } from '@services/utils/ts/requests'
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
|
import { removeCourse } from '@services/courses/activity'
|
||||||
|
import { revalidateTags } from '@services/utils/ts/requests'
|
||||||
|
import { useRouter } from 'next/navigation'
|
||||||
|
|
||||||
function Trail(params: any) {
|
function Trail(params: any) {
|
||||||
let orgslug = params.orgslug
|
let orgslug = params.orgslug
|
||||||
|
|
@ -16,28 +19,73 @@ function Trail(params: any) {
|
||||||
const access_token = session?.data?.tokens?.access_token;
|
const access_token = session?.data?.tokens?.access_token;
|
||||||
const org = useOrg() as any
|
const org = useOrg() as any
|
||||||
const orgID = org?.id
|
const orgID = org?.id
|
||||||
const { data: trail, error: error } = useSWR(
|
const router = useRouter()
|
||||||
|
const [isQuittingAll, setIsQuittingAll] = useState(false)
|
||||||
|
const [quittingProgress, setQuittingProgress] = useState(0)
|
||||||
|
|
||||||
|
const { data: trail, error: error, mutate } = useSWR(
|
||||||
`${getAPIUrl()}trail/org/${orgID}/trail`,
|
`${getAPIUrl()}trail/org/${orgID}/trail`,
|
||||||
(url) => swrFetcher(url, access_token)
|
(url) => swrFetcher(url, access_token)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const handleQuitAllCourses = async () => {
|
||||||
|
if (!trail?.runs?.length || isQuittingAll) return;
|
||||||
|
|
||||||
|
setIsQuittingAll(true)
|
||||||
|
const totalCourses = trail.runs.length;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (let i = 0; i < trail.runs.length; i++) {
|
||||||
|
const run = trail.runs[i];
|
||||||
|
await removeCourse(run.course.course_uuid, orgslug, access_token);
|
||||||
|
setQuittingProgress(Math.round(((i + 1) / totalCourses) * 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
await revalidateTags(['courses'], orgslug);
|
||||||
|
router.refresh();
|
||||||
|
await mutate();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error quitting courses:', error);
|
||||||
|
} finally {
|
||||||
|
setIsQuittingAll(false)
|
||||||
|
setQuittingProgress(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => { }, [trail, org])
|
useEffect(() => { }, [trail, org])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GeneralWrapperStyled>
|
<GeneralWrapperStyled>
|
||||||
<TypeOfContentTitle title="Trail" type="tra" />
|
<div className="flex justify-between items-center mb-6">
|
||||||
|
<TypeOfContentTitle title="Trail" type="tra" />
|
||||||
|
{trail?.runs?.length > 0 && (
|
||||||
|
<button
|
||||||
|
onClick={handleQuitAllCourses}
|
||||||
|
disabled={isQuittingAll}
|
||||||
|
className={`px-4 py-2 rounded-lg font-medium text-sm transition-all
|
||||||
|
${isQuittingAll
|
||||||
|
? 'bg-gray-100 text-gray-500 cursor-not-allowed'
|
||||||
|
: 'bg-red-100 text-red-700 hover:bg-red-200'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{isQuittingAll
|
||||||
|
? `Quitting Courses (${quittingProgress}%)`
|
||||||
|
: 'Quit All Courses'
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
{!trail ? (
|
{!trail ? (
|
||||||
<PageLoading></PageLoading>
|
<PageLoading></PageLoading>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{trail.runs.map((run: any) => (
|
{trail.runs.map((run: any) => (
|
||||||
<>
|
<TrailCourseElement
|
||||||
<TrailCourseElement
|
key={run.course.course_uuid}
|
||||||
run={run}
|
run={run}
|
||||||
course={run.course}
|
course={run.course}
|
||||||
orgslug={orgslug}
|
orgslug={orgslug}
|
||||||
/>
|
/>
|
||||||
</>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,33 @@ const ActivityTooltipContent = memo(({
|
||||||
|
|
||||||
ActivityTooltipContent.displayName = 'ActivityTooltipContent';
|
ActivityTooltipContent.displayName = 'ActivityTooltipContent';
|
||||||
|
|
||||||
|
// Add new memoized component for chapter tooltip
|
||||||
|
const ChapterTooltipContent = memo(({
|
||||||
|
chapter,
|
||||||
|
chapterNumber,
|
||||||
|
totalActivities,
|
||||||
|
completedActivities
|
||||||
|
}: {
|
||||||
|
chapter: any,
|
||||||
|
chapterNumber: number,
|
||||||
|
totalActivities: number,
|
||||||
|
completedActivities: number
|
||||||
|
}) => (
|
||||||
|
<div className="bg-white rounded-lg nice-shadow py-3 px-4 min-w-[200px] animate-in fade-in duration-200">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm font-medium text-gray-900">Chapter {chapterNumber}</span>
|
||||||
|
<span className="text-xs bg-gray-100 px-2 py-0.5 rounded-full text-gray-600">
|
||||||
|
{completedActivities}/{totalActivities} completed
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="mt-1">
|
||||||
|
<span className="text-sm text-gray-700">{chapter.name}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
|
||||||
|
ChapterTooltipContent.displayName = 'ChapterTooltipContent';
|
||||||
|
|
||||||
function ActivityIndicators(props: Props) {
|
function ActivityIndicators(props: Props) {
|
||||||
const course = props.course
|
const course = props.course
|
||||||
const orgslug = props.orgslug
|
const orgslug = props.orgslug
|
||||||
|
|
@ -167,6 +194,7 @@ function ActivityIndicators(props: Props) {
|
||||||
return `${black_activity_style}`
|
return `${black_activity_style}`
|
||||||
}, [isActivityDone, isActivityCurrent]);
|
}, [isActivityDone, isActivityCurrent]);
|
||||||
|
|
||||||
|
// Keep the allActivities array for navigation purposes only
|
||||||
const navigateToPrevious = () => {
|
const navigateToPrevious = () => {
|
||||||
if (currentActivityIndex > 0) {
|
if (currentActivityIndex > 0) {
|
||||||
const prevActivity = allActivities[currentActivityIndex - 1]
|
const prevActivity = allActivities[currentActivityIndex - 1]
|
||||||
|
|
@ -183,6 +211,13 @@ function ActivityIndicators(props: Props) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add function to count completed activities in a chapter
|
||||||
|
const getChapterProgress = useMemo(() => (chapterActivities: any[]) => {
|
||||||
|
return chapterActivities.reduce((acc, activity) => {
|
||||||
|
return acc + (isActivityDone(activity) ? 1 : 0)
|
||||||
|
}, 0)
|
||||||
|
}, [isActivityDone]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
{enableNavigation && (
|
{enableNavigation && (
|
||||||
|
|
@ -197,38 +232,71 @@ function ActivityIndicators(props: Props) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center w-full">
|
<div className="flex items-center w-full">
|
||||||
{allActivities.map((activity: any) => {
|
{course.chapters.map((chapter: any, chapterIndex: number) => {
|
||||||
const isDone = isActivityDone(activity)
|
const completedActivities = getChapterProgress(chapter.activities);
|
||||||
const isCurrent = isActivityCurrent(activity)
|
const isChapterComplete = completedActivities === chapter.activities.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolTip
|
<React.Fragment key={chapter.id}>
|
||||||
sideOffset={8}
|
<ToolTip
|
||||||
unstyled
|
sideOffset={8}
|
||||||
content={
|
unstyled
|
||||||
<ActivityTooltipContent
|
content={
|
||||||
activity={activity}
|
<ChapterTooltipContent
|
||||||
isDone={isDone}
|
chapter={chapter}
|
||||||
isCurrent={isCurrent}
|
chapterNumber={chapterIndex + 1}
|
||||||
/>
|
totalActivities={chapter.activities.length}
|
||||||
}
|
completedActivities={completedActivities}
|
||||||
key={activity.activity_uuid}
|
/>
|
||||||
>
|
|
||||||
<Link
|
|
||||||
prefetch={false}
|
|
||||||
href={
|
|
||||||
getUriWithOrg(orgslug, '') +
|
|
||||||
`/course/${courseid}/activity/${activity.activity_uuid.replace(
|
|
||||||
'activity_',
|
|
||||||
''
|
|
||||||
)}`
|
|
||||||
}
|
}
|
||||||
className={`${isCurrent ? 'flex-[2]' : 'flex-1'} mx-1`}
|
|
||||||
>
|
>
|
||||||
<div
|
<div className="mx-2 h-[20px] flex items-center cursor-help">
|
||||||
className={`h-[7px] ${getActivityClass(activity)} rounded-lg transition-all`}
|
<div className={`w-[20px] h-[20px] rounded-full flex items-center justify-center text-xs font-medium transition-colors ${
|
||||||
></div>
|
isChapterComplete
|
||||||
</Link>
|
? 'bg-teal-600 text-white'
|
||||||
</ToolTip>
|
: 'bg-gray-100 text-gray-600'
|
||||||
|
}`}>
|
||||||
|
{chapterIndex + 1}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ToolTip>
|
||||||
|
<div className="flex-1 flex items-center">
|
||||||
|
{chapter.activities.map((activity: any) => {
|
||||||
|
const isDone = isActivityDone(activity)
|
||||||
|
const isCurrent = isActivityCurrent(activity)
|
||||||
|
return (
|
||||||
|
<ToolTip
|
||||||
|
sideOffset={8}
|
||||||
|
unstyled
|
||||||
|
content={
|
||||||
|
<ActivityTooltipContent
|
||||||
|
activity={activity}
|
||||||
|
isDone={isDone}
|
||||||
|
isCurrent={isCurrent}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
key={activity.activity_uuid}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
prefetch={false}
|
||||||
|
href={
|
||||||
|
getUriWithOrg(orgslug, '') +
|
||||||
|
`/course/${courseid}/activity/${activity.activity_uuid.replace(
|
||||||
|
'activity_',
|
||||||
|
''
|
||||||
|
)}`
|
||||||
|
}
|
||||||
|
className={`${isCurrent ? 'flex-[2]' : 'flex-1'} mx-1`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`h-[7px] ${getActivityClass(activity)} rounded-lg transition-all`}
|
||||||
|
></div>
|
||||||
|
</Link>
|
||||||
|
</ToolTip>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue