Add lesson count

feat/ai-courses
Kamran Ahmed 1 month ago
parent 5c99f318e8
commit da8118efc5
  1. 22
      src/components/GenerateCourse/AICourse.tsx
  2. 89
      src/components/GenerateCourse/AICourseCard.tsx
  3. 7
      src/queries/ai-course.ts

@ -1,4 +1,4 @@
import { Loader2Icon, SearchIcon, WandIcon } from 'lucide-react'; import { SearchIcon, WandIcon } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
import { cn } from '../../lib/classname'; import { cn } from '../../lib/classname';
import { isLoggedIn } from '../../lib/jwt'; import { isLoggedIn } from '../../lib/jwt';
@ -6,6 +6,7 @@ import { showLoginPopup } from '../../lib/popup';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { listUserAiCoursesOptions } from '../../queries/ai-course'; import { listUserAiCoursesOptions } from '../../queries/ai-course';
import { queryClient } from '../../stores/query-client'; import { queryClient } from '../../stores/query-client';
import { AICourseCard } from './AICourseCard';
export const difficultyLevels = [ export const difficultyLevels = [
'beginner', 'beginner',
@ -18,7 +19,7 @@ type AICourseProps = {};
export function AICourse(props: AICourseProps) { export function AICourse(props: AICourseProps) {
const [keyword, setKeyword] = useState(''); const [keyword, setKeyword] = useState('');
const [difficulty, setDifficulty] = useState<DifficultyLevel>('intermediate'); const [difficulty, setDifficulty] = useState<DifficultyLevel>('beginner');
const { data: userAiCourses, isLoading: isUserAiCoursesLoading } = useQuery( const { data: userAiCourses, isLoading: isUserAiCoursesLoading } = useQuery(
listUserAiCoursesOptions(), listUserAiCoursesOptions(),
@ -127,8 +128,8 @@ export function AICourse(props: AICourseProps) {
</form> </form>
</div> </div>
<div className="mt-8 rounded-lg border border-gray-200 bg-white p-6"> <div className="mt-8 min-h-[200px] rounded-lg border border-gray-200 bg-white p-6">
<h2 className="mb-4 text-lg font-medium">Your Courses</h2> <h2 className="mb-4 text-lg font-semibold">Your Courses</h2>
{isUserAiCoursesLoading && ( {isUserAiCoursesLoading && (
<div className="h-[92px] animate-pulse rounded-lg border border-gray-200 bg-gray-200"></div> <div className="h-[92px] animate-pulse rounded-lg border border-gray-200 bg-gray-200"></div>
@ -143,18 +144,9 @@ export function AICourse(props: AICourseProps) {
{!isUserAiCoursesLoading && {!isUserAiCoursesLoading &&
userAiCourses && userAiCourses &&
userAiCourses.length > 0 && ( userAiCourses.length > 0 && (
<div className="grid grid-cols-2 gap-2"> <div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
{userAiCourses.map((course) => ( {userAiCourses.map((course) => (
<a <AICourseCard key={course._id} course={course} />
key={course._id}
href={`/ai-tutor/${course.slug}`}
className="group relative flex w-full items-center justify-between overflow-hidden rounded-md border border-gray-300 bg-white px-3 py-2 text-left text-sm transition-all hover:border-gray-400"
>
<span className="flex-grow truncate">{course.title}</span>
<span className="rounded-md bg-gray-100 px-2 py-1 text-xs capitalize text-gray-400">
{course.difficulty}
</span>
</a>
))} ))}
</div> </div>
)} )}

@ -0,0 +1,89 @@
import type { AICourseListItem } from '../../queries/ai-course';
import type { DifficultyLevel } from './AICourse';
import { BookOpen, Calendar } from 'lucide-react';
type AICourseCardProps = {
course: AICourseListItem;
};
export function AICourseCard(props: AICourseCardProps) {
const { course } = props;
// Format date if available
const formattedDate = course.createdAt
? new Date(course.createdAt).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
})
: null;
// Map difficulty to color
const difficultyColor =
{
beginner: 'bg-green-100 text-green-700',
intermediate: 'bg-blue-100 text-blue-700',
advanced: 'bg-purple-100 text-purple-700',
}[course.difficulty as DifficultyLevel] || 'bg-gray-100 text-gray-700';
// Get a short description preview if available
const descriptionPreview = course.data
? JSON.parse(course.data)?.description?.substring(0, 100) +
(JSON.parse(course.data)?.description?.length > 100 ? '...' : '')
: null;
// Calculate progress percentage
const totalTopics = course.lessonCount || 0;
const completedTopics = course.progress?.done?.length || 0;
const progressPercentage =
totalTopics > 0 ? Math.round((completedTopics / totalTopics) * 100) : 0;
return (
<a
href={`/ai-tutor/${course.slug}`}
className="group relative flex w-full flex-col overflow-hidden rounded-md border border-gray-300 bg-white p-4 text-left transition-all hover:border-gray-300 hover:bg-gray-50"
>
<div className="mb-2 flex items-center justify-between">
<span
className={`rounded-full px-2.5 py-1 text-xs font-medium capitalize ${difficultyColor}`}
>
{course.difficulty}
</span>
{formattedDate && (
<span className="flex items-center text-xs text-gray-500">
<Calendar className="mr-1 h-3 w-3" />
{formattedDate}
</span>
)}
</div>
<h3 className="mb-2 text-base font-semibold text-gray-900">
{course.title}
</h3>
{descriptionPreview && (
<p className="mb-3 text-xs text-gray-600">{descriptionPreview}</p>
)}
<div className="mt-auto flex items-center justify-between pt-2">
<div className="flex items-center text-xs text-gray-600">
<BookOpen className="mr-1 h-3.5 w-3.5" />
<span>{totalTopics} topics</span>
</div>
{totalTopics > 0 && (
<div className="flex items-center">
<div className="mr-2 h-1.5 w-16 overflow-hidden rounded-full bg-gray-200">
<div
className="h-full rounded-full bg-blue-600"
style={{ width: `${progressPercentage}%` }}
/>
</div>
<span className="text-xs font-medium text-gray-700">
{progressPercentage}%
</span>
</div>
)}
</div>
</a>
);
}

@ -74,9 +74,12 @@ export function getAiCourseLimitOptions() {
}); });
} }
type ListUserAiCoursesResponse = (AICourseDocument & { export type AICourseListItem = AICourseDocument & {
progress: AICourseProgressDocument; progress: AICourseProgressDocument;
})[]; lessonCount: number;
};
type ListUserAiCoursesResponse = AICourseListItem[];
export function listUserAiCoursesOptions() { export function listUserAiCoursesOptions() {
return { return {

Loading…
Cancel
Save