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