-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ui): implemented full student dashboard
- Loading branch information
1 parent
3e44f66
commit 9ae3cde
Showing
30 changed files
with
732 additions
and
144 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { Typography } from "@litespace/luna/Typography"; | ||
import React from "react"; | ||
|
||
export const Loader: React.FC<{ | ||
text: string; | ||
}> = ({ text }) => { | ||
return ( | ||
<div className="h-full flex flex-col justify-center items-center gap-10 mt-20"> | ||
<div className="w-[80px] h-[80px] animate-spin flex items-center relative rounded-full justify-center bg-[conic-gradient(from_180deg_at_50%_50%,#1D7C4E_0deg,rgba(17,173,207,0)_360deg)]"> | ||
<div className="w-[60px] h-[60px] bg-white rounded-full" /> | ||
<div className="w-[13px] h-[10px] bg-[rgba(29,124,78,1)] rounded-full absolute bottom-0 left-1/2 -translate-x-1/2" /> | ||
</div> | ||
|
||
<Typography element="h4" weight="bold" className="text-natural-950"> | ||
{text} | ||
</Typography> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import ExclaimationMarkCircle from "@litespace/assets/ExclaimationMarkCircle"; | ||
import { Button, ButtonVariant } from "@litespace/luna/Button"; | ||
import { useFormatMessage } from "@litespace/luna/hooks/intl"; | ||
import { Typography } from "@litespace/luna/Typography"; | ||
import { Void } from "@litespace/types"; | ||
|
||
export const LoadingError: React.FC<{ retry: Void; error: string }> = ({ | ||
retry, | ||
error, | ||
}) => { | ||
const intl = useFormatMessage(); | ||
return ( | ||
<div className="flex flex-col mt-20 gap-10 items-center justify-center"> | ||
<div className="p-[6px] bg-destructive-200 rounded-full"> | ||
<ExclaimationMarkCircle /> | ||
</div> | ||
<Typography | ||
element="h4" | ||
weight="bold" | ||
className="text-natural-950 text-center" | ||
> | ||
{error} | ||
</Typography> | ||
<Button onClick={retry} variant={ButtonVariant.Secondary}> | ||
{intl("global.retry")} | ||
</Button> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { Route } from "@/types/routes"; | ||
import { useFindUserRooms } from "@litespace/headless/chat"; | ||
import { useUser } from "@litespace/headless/context/user"; | ||
import { ChatSummary, type ChatSummaryProps } from "@litespace/luna/Chat"; | ||
import { orUndefined } from "@litespace/sol/utils"; | ||
import { IRoom } from "@litespace/types"; | ||
import dayjs from "dayjs"; | ||
import { useMemo } from "react"; | ||
|
||
function organizeRooms( | ||
list: IRoom.FindUserRoomsApiRecord[] | null | ||
): ChatSummaryProps["rooms"] { | ||
return ( | ||
list?.map((room) => ({ | ||
id: room.roomId, | ||
url: Route.Chat.concat("?room=", room.roomId.toString()), | ||
name: orUndefined(room.otherMember?.name), | ||
image: orUndefined(room.otherMember?.image), | ||
message: room.latestMessage?.text || "TODO", | ||
sentAt: room.latestMessage?.updatedAt || dayjs().toString(), // TODO | ||
read: room.unreadMessagesCount === 0, | ||
})) || [] | ||
); | ||
} | ||
|
||
export const ChatSummaryWrapper = () => { | ||
const { user } = useUser(); | ||
const rooms = useFindUserRooms(user?.id, { size: 4 }); | ||
const organizedRooms = useMemo(() => organizeRooms(rooms.list), [rooms.list]); | ||
|
||
return ( | ||
<ChatSummary | ||
loading={rooms.query.isLoading || rooms.query.isPending} | ||
error={rooms.query.isError} | ||
retry={rooms.query.refetch} | ||
chatsUrl={Route.Chat} | ||
rooms={organizedRooms} | ||
/> | ||
); | ||
}; |
111 changes: 111 additions & 0 deletions
111
apps/nova/src/components/dashboard/PastLessonsWrapper.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import { Route } from "@/types/routes"; | ||
import { useUser } from "@litespace/headless/context/user"; | ||
import { useInfiniteLessons } from "@litespace/headless/lessons"; | ||
import { useFormatMessage } from "@litespace/luna/hooks/intl"; | ||
import { PastLessonsTable } from "@litespace/luna/Lessons"; | ||
import { Typography } from "@litespace/luna/Typography"; | ||
import { ILesson, IUser } from "@litespace/types"; | ||
import { useCallback, useMemo, useState } from "react"; | ||
import BookLesson from "@/components/Lessons/BookLesson"; | ||
import { LoadingError, Loader } from "@litespace/luna/Loading"; | ||
import { InView } from "react-intersection-observer"; | ||
|
||
function organizeLessons( | ||
list: ILesson.FindUserLessonsApiResponse["list"] | null | ||
) { | ||
return ( | ||
list?.map((lesson) => { | ||
const teacher = lesson.members.find( | ||
(member) => member.role === IUser.Role.Tutor | ||
); | ||
return { | ||
id: lesson.lesson.id, | ||
start: lesson.lesson.start, | ||
duration: lesson.lesson.duration, | ||
tutor: { | ||
id: teacher?.userId || 0, | ||
name: teacher?.name || null, | ||
imageUrl: teacher?.image || null, | ||
}, | ||
}; | ||
}) || [] | ||
); | ||
} | ||
|
||
export const PastLessonsWrapper = () => { | ||
const intl = useFormatMessage(); | ||
const [tutor, setTutor] = useState<number | null>(null); | ||
|
||
const closeRebookingDialog = useCallback(() => { | ||
setTutor(null); | ||
}, []); | ||
|
||
const openRebookingDialog = useCallback((tutorId: number) => { | ||
setTutor(tutorId); | ||
}, []); | ||
|
||
const { user } = useUser(); | ||
const lessonsQuery = useInfiniteLessons({ | ||
users: user ? [user?.id] : [], | ||
userOnly: true, | ||
past: true, | ||
future: false, | ||
size: 8, | ||
}); | ||
|
||
const lessons = useMemo( | ||
() => organizeLessons(lessonsQuery.list), | ||
[lessonsQuery.list] | ||
); | ||
|
||
return ( | ||
<div className="grid gap-6 "> | ||
<Typography | ||
element="subtitle-2" | ||
weight="bold" | ||
className="text-natural-950 " | ||
> | ||
{intl("student-dashboard.previous-lessons.title")} | ||
</Typography> | ||
|
||
{lessonsQuery.query.isError ? ( | ||
<LoadingError | ||
error={intl("student-dashboard.error")} | ||
retry={lessonsQuery.query.refetch} | ||
/> | ||
) : null} | ||
|
||
{lessonsQuery.query.isPending || lessonsQuery.query.isLoading ? ( | ||
<div className="[&>*]:mt-0"> | ||
<Loader text={intl("student-dashboard.loading")} /> | ||
</div> | ||
) : null} | ||
|
||
{lessons && | ||
!lessonsQuery.query.isLoading && | ||
!lessonsQuery.query.isError ? ( | ||
<InView | ||
as="div" | ||
onChange={(inView) => { | ||
if (inView && lessonsQuery.query.hasNextPage) lessonsQuery.more(); | ||
}} | ||
className="max-h-[500px] overflow-y-auto scrollbar-thin scrollbar-thumb-border-stronger scrollbar-track-surface-300" | ||
> | ||
<PastLessonsTable | ||
tutorsRoute={Route.Tutors} | ||
onRebook={openRebookingDialog} | ||
lessons={lessons} | ||
/> | ||
</InView> | ||
) : null} | ||
|
||
{tutor ? ( | ||
<BookLesson | ||
close={closeRebookingDialog} | ||
open={!!tutor} | ||
tutorId={tutor} | ||
/> | ||
) : null} | ||
</div> | ||
); | ||
}; |
47 changes: 47 additions & 0 deletions
47
apps/nova/src/components/dashboard/StudentOverviewWrapper.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { useFormatMessage } from "@litespace/luna/hooks/intl"; | ||
import { StudentOverview } from "@litespace/luna/StudentOverview"; | ||
import { Typography } from "@litespace/luna/Typography"; | ||
import { useFindPublicStudentStats } from "@litespace/headless/student"; | ||
import { Loader, LoadingError } from "@litespace/luna/Loading"; | ||
|
||
export const StudentOverviewWrapper = () => { | ||
const intl = useFormatMessage(); | ||
|
||
const statsQuery = useFindPublicStudentStats(); | ||
|
||
return ( | ||
<div className="grid gap-6 justify-items-start"> | ||
<Typography | ||
element="subtitle-2" | ||
weight="bold" | ||
className="text-natural-950" | ||
> | ||
{intl("student-dashboard.overview.title")} | ||
</Typography> | ||
|
||
{statsQuery.isLoading || statsQuery.isPending ? ( | ||
<div className="w-full flex items-start justify-center [&>*]:mt-0"> | ||
<Loader text={intl("student-dashboard.loading")} /> | ||
</div> | ||
) : null} | ||
|
||
{statsQuery.isError ? ( | ||
<div className="w-full flex items-center justify-center [&>*]:mt-0"> | ||
<LoadingError | ||
error={intl("student-dashboard.loading")} | ||
retry={statsQuery.refetch} | ||
/> | ||
</div> | ||
) : null} | ||
|
||
{statsQuery.data ? ( | ||
<StudentOverview | ||
tutorCount={statsQuery.data.tutorCount} | ||
completedLessonCount={statsQuery.data.completedLessonCount} | ||
totalLearningTime={statsQuery.data.totalLearningTime} | ||
totalLessonCount={statsQuery.data.completedLessonCount} | ||
/> | ||
) : null} | ||
</div> | ||
); | ||
}; |
46 changes: 46 additions & 0 deletions
46
apps/nova/src/components/dashboard/UpcomingLessonsSummaryWrapper.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { Route } from "@/types/routes"; | ||
import { useUser } from "@litespace/headless/context/user"; | ||
import { useInfiniteLessons } from "@litespace/headless/lessons"; | ||
import { UpcomingLessonsSummary } from "@litespace/luna/Lessons"; | ||
import { ILesson, IUser } from "@litespace/types"; | ||
import { useMemo } from "react"; | ||
|
||
function organizeUpcomingLessons( | ||
list: ILesson.FindUserLessonsApiResponse["list"] | null | ||
) { | ||
return ( | ||
list?.map((item) => ({ | ||
start: item.lesson.start, | ||
tutorName: | ||
item.members.find((member) => member.role === IUser.Role.Tutor)?.name || | ||
null, | ||
url: Route.Call, | ||
})) || [] | ||
); | ||
} | ||
|
||
export const UpcomingLessonsSummaryWrapper = () => { | ||
const { user } = useUser(); | ||
|
||
const lessonsQuery = useInfiniteLessons({ | ||
users: user ? [user?.id] : [], | ||
userOnly: true, | ||
future: true, | ||
past: false, | ||
size: 4, | ||
}); | ||
const lessons = useMemo( | ||
() => organizeUpcomingLessons(lessonsQuery.list), | ||
[lessonsQuery.list] | ||
); | ||
return ( | ||
<UpcomingLessonsSummary | ||
loading={lessonsQuery.query.isLoading || lessonsQuery.query.isPending} | ||
error={lessonsQuery.query.isError} | ||
retry={lessonsQuery.query.refetch} | ||
lessons={lessons} | ||
lessonsUrl={Route.UpcomingLessons} | ||
tutorsUrl={Route.Tutors} | ||
/> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,21 @@ | ||
import React from "react"; | ||
|
||
import { ChatSummaryWrapper } from "@/components/dashboard/ChatSummaryWrapper"; | ||
import { UpcomingLessonsSummaryWrapper } from "@/components/dashboard/UpcomingLessonsSummaryWrapper"; | ||
import { PastLessonsWrapper } from "@/components/dashboard/PastLessonsWrapper"; | ||
import { StudentOverviewWrapper } from "@/components/dashboard/StudentOverviewWrapper"; | ||
const Dashboard: React.FC = () => { | ||
return <div>Dashboard</div>; | ||
return ( | ||
<div className="grid grid-cols-[66%,33%] p-6 gap-6"> | ||
<div className="flex flex-col gap-6"> | ||
<StudentOverviewWrapper /> | ||
<PastLessonsWrapper /> | ||
</div> | ||
<div className="flex flex-col gap-6"> | ||
<UpcomingLessonsSummaryWrapper /> | ||
<ChatSummaryWrapper /> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default Dashboard; |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.