parent
625f33a076
commit
8994d1b3b1
4 changed files with 187 additions and 2 deletions
@ -0,0 +1,177 @@ |
||||
import { useEffect, useState } from 'react'; |
||||
import type { TeamMember } from '../TeamProgress/TeamProgressPage'; |
||||
import { httpGet } from '../../lib/http'; |
||||
import { useToast } from '../../hooks/use-toast'; |
||||
import { getUser } from '../../lib/jwt'; |
||||
import { LoadingProgress } from './LoadingProgress'; |
||||
import { ResourceProgress } from '../Activity/ResourceProgress'; |
||||
import { Plus, Minus } from 'lucide-react'; |
||||
import { TeamActivityPage } from '../TeamActivity/TeamActivityPage'; |
||||
import { cn } from '../../lib/classname'; |
||||
|
||||
type TeamDashboardProps = { |
||||
teamId: string; |
||||
}; |
||||
|
||||
export function TeamDashboard(props: TeamDashboardProps) { |
||||
const { teamId } = props; |
||||
|
||||
const toast = useToast(); |
||||
const currentUser = getUser(); |
||||
|
||||
const [isLoading, setIsLoading] = useState(true); |
||||
const [teamMembers, setTeamMembers] = useState<TeamMember[]>([]); |
||||
const [showAllMembers, setShowAllMembers] = useState(false); |
||||
|
||||
async function getTeamProgress() { |
||||
const { response, error } = await httpGet<TeamMember[]>( |
||||
`${import.meta.env.PUBLIC_API_URL}/v1-get-team-progress/${teamId}`, |
||||
); |
||||
if (error || !response) { |
||||
toast.error(error?.message || 'Failed to get team progress'); |
||||
return; |
||||
} |
||||
|
||||
setTeamMembers( |
||||
response.sort((a, b) => { |
||||
if (a.email === currentUser?.email) { |
||||
return -1; |
||||
} |
||||
if (b.email === currentUser?.email) { |
||||
return 1; |
||||
} |
||||
return 0; |
||||
}), |
||||
); |
||||
} |
||||
|
||||
useEffect(() => { |
||||
if (!teamId) { |
||||
return; |
||||
} |
||||
|
||||
getTeamProgress().finally(() => setIsLoading(false)); |
||||
}, [teamId]); |
||||
|
||||
if (!currentUser) { |
||||
return null; |
||||
} |
||||
|
||||
const currentMember = teamMembers.find( |
||||
(member) => member.email === currentUser.email, |
||||
); |
||||
const learningRoadmapsToShow = |
||||
currentMember?.progress?.filter( |
||||
(progress) => progress.resourceType === 'roadmap', |
||||
) || []; |
||||
|
||||
const allMembersWithoutCurrentUser = teamMembers |
||||
.sort((a, b) => { |
||||
if (a.email === currentUser.email) { |
||||
return -1; |
||||
} |
||||
|
||||
if (b.email === currentUser.email) { |
||||
return 1; |
||||
} |
||||
|
||||
return 0; |
||||
}) |
||||
.slice(0, showAllMembers ? teamMembers.length : 15); |
||||
|
||||
return ( |
||||
<section className="mt-8"> |
||||
<h2 className="mb-3 text-xs uppercase text-gray-400">Roadmaps</h2> |
||||
{isLoading && <LoadingProgress />} |
||||
{!isLoading && learningRoadmapsToShow.length > 0 && ( |
||||
<div className="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} |
||||
roadmapSlug={roadmap.roadmapSlug} |
||||
/> |
||||
); |
||||
})} |
||||
</div> |
||||
)} |
||||
|
||||
<h2 className="mb-3 mt-6 text-xs uppercase text-gray-400"> |
||||
Team Members |
||||
</h2> |
||||
{isLoading && <TeamMemberLoading className="mb-6" />} |
||||
{!isLoading && ( |
||||
<div className="mb-6 flex flex-wrap gap-2"> |
||||
{allMembersWithoutCurrentUser.map((member) => { |
||||
const avatar = member?.avatar |
||||
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${member.avatar}` |
||||
: '/images/default-avatar.png'; |
||||
return ( |
||||
<figure |
||||
key={member.email} |
||||
className="relative aspect-square size-8 overflow-hidden rounded-md bg-gray-100" |
||||
> |
||||
<img |
||||
src={avatar} |
||||
alt={member.name || ''} |
||||
className="absolute inset-0 h-full w-full object-cover" |
||||
/> |
||||
<figcaption className="sr-only">{member.name}</figcaption> |
||||
</figure> |
||||
); |
||||
})} |
||||
{teamMembers.length > 0 && ( |
||||
<button |
||||
onClick={() => setShowAllMembers((prev) => !prev)} |
||||
className="flex aspect-square size-8 items-center justify-center rounded-md bg-gray-200 text-gray-600 hover:text-black" |
||||
> |
||||
{showAllMembers ? ( |
||||
<Minus className="size-5" /> |
||||
) : ( |
||||
<Plus className="size-5" /> |
||||
)} |
||||
</button> |
||||
)} |
||||
</div> |
||||
)} |
||||
|
||||
<TeamActivityPage teamId={teamId} /> |
||||
</section> |
||||
); |
||||
} |
||||
|
||||
type TeamMemberLoadingProps = { |
||||
className?: string; |
||||
}; |
||||
|
||||
function TeamMemberLoading(props: TeamMemberLoadingProps) { |
||||
const { className } = props; |
||||
|
||||
return ( |
||||
<div className={cn('flex flex-wrap gap-2', className)}> |
||||
{Array.from({ length: 8 }).map((_, index) => ( |
||||
<div |
||||
key={index} |
||||
className="size-8 animate-pulse rounded-md bg-gray-200" |
||||
></div> |
||||
))} |
||||
</div> |
||||
); |
||||
} |
Loading…
Reference in new issue