-
-
Notifications
You must be signed in to change notification settings - Fork 40.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* fix: change `topicIds` to `topicTitles` * fix: comma and gap * wip: member details page * fix: team member empty state * feat: add pagination * fix: add loading screen
- Loading branch information
1 parent
fb7136e
commit 63ad6fe
Showing
11 changed files
with
303 additions
and
35 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
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
176 changes: 176 additions & 0 deletions
176
src/components/TeamMemberDetails/TeamMemberDetailsPage.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,176 @@ | ||
import { useEffect, useState } from 'react'; | ||
import { httpGet } from '../../lib/http'; | ||
import { pageProgressMessage } from '../../stores/page'; | ||
import { getUrlParams } from '../../lib/browser'; | ||
import { useToast } from '../../hooks/use-toast'; | ||
import type { TeamMemberDocument } from '../TeamMembers/TeamMembersPage'; | ||
import type { UserProgress } from '../TeamProgress/TeamProgressPage'; | ||
import type { TeamActivityStreamDocument } from '../TeamActivity/TeamActivityPage'; | ||
import { ResourceProgress } from '../Activity/ResourceProgress'; | ||
import { ActivityStream } from '../Activity/ActivityStream'; | ||
import { MemberRoleBadge } from '../TeamMembers/RoleBadge'; | ||
import { TeamMemberEmptyPage } from './TeamMemberEmptyPage'; | ||
import { Pagination } from '../Pagination/Pagination'; | ||
|
||
type GetTeamMemberProgressesResponse = TeamMemberDocument & { | ||
name: string; | ||
avatar: string; | ||
email: string; | ||
progresses: UserProgress[]; | ||
}; | ||
|
||
type GetTeamMemberActivityResponse = { | ||
data: TeamActivityStreamDocument[]; | ||
totalCount: number; | ||
totalPages: number; | ||
currPage: number; | ||
perPage: number; | ||
}; | ||
|
||
export function TeamMemberDetailsPage() { | ||
const { t: teamId, m: memberId } = getUrlParams() as { t: string; m: string }; | ||
|
||
const toast = useToast(); | ||
|
||
const [memberProgress, setMemberProgress] = | ||
useState<GetTeamMemberProgressesResponse | null>(null); | ||
const [memberActivity, setMemberActivity] = | ||
useState<GetTeamMemberActivityResponse | null>(null); | ||
const [currPage, setCurrPage] = useState(1); | ||
|
||
const loadMemberProgress = async () => { | ||
const { response, error } = await httpGet<GetTeamMemberProgressesResponse>( | ||
`${import.meta.env.PUBLIC_API_URL}/v1-get-team-member-progresses/${teamId}/${memberId}`, | ||
); | ||
if (error || !response) { | ||
pageProgressMessage.set(''); | ||
toast.error(error?.message || 'Failed to load team member'); | ||
return; | ||
} | ||
|
||
setMemberProgress(response); | ||
}; | ||
|
||
const loadMemberActivity = async (currPage: number = 1) => { | ||
const { response, error } = await httpGet<GetTeamMemberActivityResponse>( | ||
`${import.meta.env.PUBLIC_API_URL}/v1-get-team-member-activity/${teamId}/${memberId}`, | ||
{ | ||
currPage, | ||
}, | ||
); | ||
if (error || !response) { | ||
pageProgressMessage.set(''); | ||
toast.error(error?.message || 'Failed to load team member activity'); | ||
return; | ||
} | ||
|
||
setMemberActivity(response); | ||
setCurrPage(response?.currPage || 1); | ||
}; | ||
|
||
useEffect(() => { | ||
if (!teamId) { | ||
return; | ||
} | ||
|
||
Promise.allSettled([loadMemberProgress(), loadMemberActivity()]).finally( | ||
() => { | ||
pageProgressMessage.set(''); | ||
}, | ||
); | ||
}, [teamId]); | ||
|
||
if (!teamId || !memberId || !memberProgress || !memberActivity) { | ||
return null; | ||
} | ||
|
||
const avatarUrl = memberProgress?.avatar | ||
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${memberProgress?.avatar}` | ||
: '/images/default-avatar.png'; | ||
|
||
return ( | ||
<> | ||
<div> | ||
<div className="flex items-center gap-4"> | ||
<img | ||
src={avatarUrl} | ||
alt={memberProgress?.name} | ||
className="h-24 w-24 rounded-full" | ||
/> | ||
<div> | ||
<MemberRoleBadge | ||
className="sm:inline-flex" | ||
role={memberProgress?.role!} | ||
/> | ||
<h1 className="mt-1 text-2xl font-medium"> | ||
{memberProgress?.name} | ||
</h1> | ||
<p className="text-sm text-gray-500">{memberProgress?.email}</p> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
<hr className="my-8 border-gray-200" /> | ||
|
||
{memberProgress?.progresses && memberProgress?.progresses?.length > 0 ? ( | ||
<> | ||
<h2 className="mb-3 text-xs uppercase text-gray-400"> | ||
Progress Overview | ||
</h2> | ||
<div className="grid grid-cols-1 gap-1.5 sm:grid-cols-2"> | ||
{memberProgress?.progresses?.map((progress) => { | ||
const learningCount = progress.learning || 0; | ||
const doneCount = progress.done || 0; | ||
const totalCount = progress.total || 0; | ||
const skippedCount = progress.skipped || 0; | ||
|
||
return ( | ||
<ResourceProgress | ||
key={progress.resourceId} | ||
isCustomResource={progress.isCustomResource!} | ||
doneCount={doneCount > totalCount ? totalCount : doneCount} | ||
learningCount={ | ||
learningCount > totalCount ? totalCount : learningCount | ||
} | ||
totalCount={totalCount} | ||
skippedCount={skippedCount} | ||
resourceId={progress.resourceId} | ||
resourceType={'roadmap'} | ||
updatedAt={progress.updatedAt} | ||
title={progress.resourceTitle} | ||
roadmapSlug={progress.roadmapSlug} | ||
showActions={false} | ||
/> | ||
); | ||
})} | ||
</div> | ||
</> | ||
) : ( | ||
<TeamMemberEmptyPage teamId={teamId} /> | ||
)} | ||
|
||
{memberActivity?.data && memberActivity?.data?.length > 0 ? ( | ||
<> | ||
<ActivityStream | ||
className="mt-8 p-0 md:m-0 md:mb-4 md:mt-8 md:p-0" | ||
activities={ | ||
memberActivity?.data?.flatMap((act) => act.activity) || [] | ||
} | ||
/> | ||
<Pagination | ||
currPage={currPage} | ||
totalPages={memberActivity?.totalPages || 1} | ||
totalCount={memberActivity?.totalCount || 0} | ||
perPage={memberActivity?.perPage || 10} | ||
onPageChange={(page) => { | ||
pageProgressMessage.set('Loading Activity'); | ||
loadMemberActivity(page).finally(() => { | ||
pageProgressMessage.set(''); | ||
}); | ||
}} | ||
/> | ||
</> | ||
) : null} | ||
</> | ||
); | ||
} |
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 { RoadmapIcon } from '../ReactIcons/RoadmapIcon'; | ||
|
||
type TeamMemberEmptyPageProps = { | ||
teamId: string; | ||
}; | ||
|
||
export function TeamMemberEmptyPage(props: TeamMemberEmptyPageProps) { | ||
const { teamId } = props; | ||
|
||
return ( | ||
<div className="rounded-md"> | ||
<div className="flex flex-col items-center p-7 text-center"> | ||
<RoadmapIcon className="mb-2 h-[60px] w-[60px] opacity-10 sm:h-[120px] sm:w-[120px]" /> | ||
|
||
<h2 className="text-lg font-bold sm:text-xl">No Progress</h2> | ||
<p className="my-1 max-w-[400px] text-balance text-sm text-gray-500 sm:my-2 sm:text-base"> | ||
Progress will appear here as they start tracking their{' '} | ||
<a | ||
href={`/team/roadmaps?t=${teamId}`} | ||
className="mt-4 text-blue-500 hover:underline" | ||
> | ||
Roadmaps | ||
</a>{' '} | ||
progress. | ||
</p> | ||
</div> | ||
</div> | ||
); | ||
} |
Oops, something went wrong.