parent
3ceab552f6
commit
9f1b6d107f
5 changed files with 198 additions and 1 deletions
@ -0,0 +1,68 @@ |
||||
import { useEffect, useState, type ReactNode } from 'react'; |
||||
import { httpGet } from '../../lib/http'; |
||||
import type { UserProgress } from '../TeamProgress/TeamProgressPage'; |
||||
import { useToast } from '../../hooks/use-toast'; |
||||
import { useStore } from '@nanostores/react'; |
||||
import { $teamList } from '../../stores/team'; |
||||
import type { TeamListResponse } from '../TeamDropdown/TeamDropdown'; |
||||
import { cn } from '../../lib/classname'; |
||||
import { DashboardTab } from './DashboardTab'; |
||||
import { PersonalDashboard } from './PersonalDashboard'; |
||||
|
||||
type DashboardPageProps = {}; |
||||
|
||||
export function DashboardPage(props: DashboardPageProps) { |
||||
const toast = useToast(); |
||||
const teamList = useStore($teamList); |
||||
|
||||
const [isLoading, setIsLoading] = useState(true); |
||||
const [selectedTeamId, setSelectedTeamId] = useState<string>(); |
||||
|
||||
async function getAllTeams() { |
||||
if (teamList.length > 0) { |
||||
return; |
||||
} |
||||
|
||||
const { response, error } = await httpGet<TeamListResponse>( |
||||
`${import.meta.env.PUBLIC_API_URL}/v1-get-user-teams`, |
||||
); |
||||
if (error || !response) { |
||||
toast.error(error?.message || 'Something went wrong'); |
||||
return; |
||||
} |
||||
|
||||
$teamList.set(response); |
||||
} |
||||
|
||||
useEffect(() => { |
||||
getAllTeams().finally(() => setIsLoading(false)); |
||||
}, []); |
||||
|
||||
return ( |
||||
<div className="container py-6 pb-14"> |
||||
<div className="flex flex-wrap items-center gap-1"> |
||||
<DashboardTab |
||||
label="Personal" |
||||
isActive={!selectedTeamId} |
||||
onClick={() => setSelectedTeamId(undefined)} |
||||
/> |
||||
{teamList.map((team) => ( |
||||
<DashboardTab |
||||
key={team._id} |
||||
label={team.name} |
||||
isActive={team._id === selectedTeamId} |
||||
onClick={() => setSelectedTeamId(team._id)} |
||||
/> |
||||
))} |
||||
<DashboardTab |
||||
label="+ Create Team" |
||||
isActive={false} |
||||
href="/team/new" |
||||
className="border-black bg-black text-white" |
||||
/> |
||||
</div> |
||||
|
||||
{!selectedTeamId && <PersonalDashboard />} |
||||
</div> |
||||
); |
||||
} |
@ -0,0 +1,30 @@ |
||||
import type { ReactNode } from 'react'; |
||||
import { cn } from '../../lib/classname'; |
||||
|
||||
type DashboardTabProps = { |
||||
label: string | ReactNode; |
||||
isActive: boolean; |
||||
onClick?: () => void; |
||||
className?: string; |
||||
href?: string; |
||||
}; |
||||
|
||||
export function DashboardTab(props: DashboardTabProps) { |
||||
const { isActive, onClick, label, className, href } = props; |
||||
|
||||
const Slot = href ? 'a' : 'button'; |
||||
|
||||
return ( |
||||
<Slot |
||||
onClick={onClick} |
||||
className={cn( |
||||
'shrink-0 rounded-md border p-1.5 px-2 text-sm leading-none text-gray-600', |
||||
isActive ? 'border-gray-500 bg-gray-200 text-gray-900' : '', |
||||
className, |
||||
)} |
||||
{...(href ? { href } : {})} |
||||
> |
||||
{label} |
||||
</Slot> |
||||
); |
||||
} |
@ -0,0 +1,91 @@ |
||||
import { useEffect, useState } from 'react'; |
||||
import { httpGet } from '../../lib/http'; |
||||
import type { UserProgress } from '../TeamProgress/TeamProgressPage'; |
||||
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions'; |
||||
import { ResourceProgress } from '../Activity/ResourceProgress'; |
||||
|
||||
type UserDashboardResponse = { |
||||
progresses: UserProgress[]; |
||||
projects: ProjectStatusDocument[]; |
||||
}; |
||||
|
||||
type PersonalDashboardProps = {}; |
||||
|
||||
export function PersonalDashboard(props: PersonalDashboardProps) { |
||||
const [isLoading, setIsLoading] = useState(true); |
||||
const [personalDashboardDetails, setPersonalDashboardDetails] = |
||||
useState<UserDashboardResponse>(); |
||||
|
||||
async function loadProgress() { |
||||
setIsLoading(true); |
||||
|
||||
const { response: progressList, error } = |
||||
await httpGet<UserDashboardResponse>( |
||||
`${import.meta.env.PUBLIC_API_URL}/v1-user-dashboard`, |
||||
); |
||||
|
||||
if (error || !progressList) { |
||||
return; |
||||
} |
||||
|
||||
setPersonalDashboardDetails(progressList); |
||||
} |
||||
|
||||
useEffect(() => { |
||||
loadProgress().finally(() => setIsLoading(false)); |
||||
}, []); |
||||
|
||||
const learningRoadmaps = |
||||
personalDashboardDetails?.progresses?.filter( |
||||
(progress) => progress.resourceType === 'roadmap', |
||||
) || []; |
||||
|
||||
const learningRoadmapsToShow = learningRoadmaps.sort((a, b) => { |
||||
const updatedAtA = new Date(a.updatedAt); |
||||
const updatedAtB = new Date(b.updatedAt); |
||||
|
||||
if (a.isFavorite && !b.isFavorite) { |
||||
return -1; |
||||
} |
||||
|
||||
if (!a.isFavorite && b.isFavorite) { |
||||
return 1; |
||||
} |
||||
|
||||
return updatedAtB.getTime() - updatedAtA.getTime(); |
||||
}); |
||||
|
||||
return ( |
||||
<section className="mt-8"> |
||||
<h2 className="text-xs uppercase text-gray-400"> |
||||
Progress and Bookmarks |
||||
</h2> |
||||
<div className="mt-3 grid grid-cols-1 gap-1.5 sm:grid-cols-3"> |
||||
{learningRoadmapsToShow.map((roadmap) => { |
||||
const learningCount = roadmap.learning || 0; |
||||
const doneCount = roadmap.done || 0; |
||||
const totalCount = roadmap.total || 0; |
||||
const skippedCount = roadmap.skipped || 0; |
||||
|
||||
return ( |
||||
<ResourceProgress |
||||
key={roadmap.resourceId} |
||||
isCustomResource={roadmap?.isCustomResource || false} |
||||
doneCount={doneCount > totalCount ? totalCount : doneCount} |
||||
learningCount={ |
||||
learningCount > totalCount ? totalCount : learningCount |
||||
} |
||||
totalCount={totalCount} |
||||
skippedCount={skippedCount} |
||||
resourceId={roadmap.resourceId} |
||||
resourceType="roadmap" |
||||
updatedAt={roadmap.updatedAt} |
||||
title={roadmap.resourceTitle} |
||||
showActions={false} |
||||
/> |
||||
); |
||||
})} |
||||
</div> |
||||
</section> |
||||
); |
||||
} |
@ -0,0 +1,8 @@ |
||||
--- |
||||
import { DashboardPage } from '../components/Dashboard/DashboardPage'; |
||||
import BaseLayout from '../layouts/BaseLayout.astro'; |
||||
--- |
||||
|
||||
<BaseLayout title='Dashboard' noIndex={true}> |
||||
<DashboardPage client:load /> |
||||
</BaseLayout> |
Loading…
Reference in new issue