mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-08-01 06:50:26 +02:00
Updating personal progress from popup
This commit is contained in:
@@ -1,37 +1,25 @@
|
||||
import { useState } from 'preact/hooks';
|
||||
import type { GroupByRoadmap, TeamMember } from './TeamProgressPage';
|
||||
import { MemberProgressModal } from './MemberProgressModal';
|
||||
import { getUrlParams } from '../../lib/browser';
|
||||
import ExternalLinkIcon from '../../icons/external-link.svg';
|
||||
import { useAuth } from '../../hooks/use-auth';
|
||||
|
||||
type GroupRoadmapItemProps = {
|
||||
roadmap: GroupByRoadmap;
|
||||
onShowResourceProgress: (member: TeamMember, resourceId: string) => void;
|
||||
};
|
||||
|
||||
export function GroupRoadmapItem(props: GroupRoadmapItemProps) {
|
||||
const { onShowResourceProgress } = props;
|
||||
const { members, resourceTitle, resourceId } = props.roadmap;
|
||||
|
||||
const { t: teamId } = getUrlParams();
|
||||
const user = useAuth();
|
||||
|
||||
const [showAll, setShowAll] = useState(false);
|
||||
const [detailResourceId, setDetailResourceId] = useState<string | null>(null);
|
||||
const [selectedMember, setSelectedMember] = useState<TeamMember | null>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
{detailResourceId && (
|
||||
<MemberProgressModal
|
||||
member={selectedMember!}
|
||||
teamId={teamId}
|
||||
resourceId={detailResourceId}
|
||||
resourceType={'roadmap'}
|
||||
onClose={() => {
|
||||
setDetailResourceId(null);
|
||||
setSelectedMember(null);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className="flex h-full min-h-[270px] flex-col rounded-md border">
|
||||
<div className="flex items-center gap-3 border-b p-3">
|
||||
<div className="flex min-w-0 flex-grow items-center justify-between">
|
||||
@@ -59,11 +47,15 @@ export function GroupRoadmapItem(props: GroupRoadmapItemProps) {
|
||||
|
||||
return (
|
||||
<button
|
||||
className={`group relative w-full overflow-hidden rounded-md border p-2 hover:border-gray-300 hover:text-black focus:outline-none ${isMyProgress ? 'border-green-500 hover:border-green-600' : ''}`}
|
||||
className={`group relative w-full overflow-hidden rounded-md border p-2 hover:border-gray-300 hover:text-black focus:outline-none ${
|
||||
isMyProgress ? 'border-green-500 hover:border-green-600' : ''
|
||||
}`}
|
||||
key={member?.member._id}
|
||||
onClick={() => {
|
||||
setDetailResourceId(member?.progress?.resourceId!);
|
||||
setSelectedMember(member.member);
|
||||
onShowResourceProgress(
|
||||
member.member,
|
||||
member.progress?.resourceId!
|
||||
);
|
||||
}}
|
||||
>
|
||||
<span className="relative z-10 flex items-center justify-between gap-1 text-sm">
|
||||
@@ -80,7 +72,9 @@ export function GroupRoadmapItem(props: GroupRoadmapItemProps) {
|
||||
className="h-5 w-5 shrink-0 rounded-full"
|
||||
/>
|
||||
<span className="inline-grid grid-cols-[auto,32px] items-center">
|
||||
<span className="truncate mr-[5px]">{member?.member?.name}</span>
|
||||
<span className="mr-[5px] truncate">
|
||||
{member?.member?.name}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span className="shrink-0 text-xs text-gray-400">
|
||||
@@ -88,7 +82,11 @@ export function GroupRoadmapItem(props: GroupRoadmapItemProps) {
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className={`absolute inset-0 ${isMyProgress ? 'bg-green-100 group-hover:bg-green-200' : 'bg-gray-100 group-hover:bg-gray-200'}`}
|
||||
className={`absolute inset-0 ${
|
||||
isMyProgress
|
||||
? 'bg-green-100 group-hover:bg-green-200'
|
||||
: 'bg-gray-100 group-hover:bg-gray-200'
|
||||
}`}
|
||||
style={{
|
||||
width: `${
|
||||
(member?.progress?.done / member?.progress?.total) * 100
|
||||
|
@@ -1,36 +1,22 @@
|
||||
import type { TeamMember } from './TeamProgressPage';
|
||||
import { useState } from 'preact/hooks';
|
||||
import { MemberProgressModal } from './MemberProgressModal';
|
||||
|
||||
type MemberProgressItemProps = {
|
||||
teamId: string;
|
||||
member: TeamMember;
|
||||
onShowResourceProgress: (resourceId: string) => void;
|
||||
isMyProgress?: boolean;
|
||||
};
|
||||
export function MemberProgressItem(props: MemberProgressItemProps) {
|
||||
const { member, teamId, isMyProgress = false } = props;
|
||||
const { member, onShowResourceProgress, isMyProgress = false } = props;
|
||||
|
||||
const memberProgress = member?.progress?.sort((a, b) => {
|
||||
return b.done - a.done;
|
||||
});
|
||||
|
||||
const [detailResourceId, setDetailResourceId] = useState<string | null>(null);
|
||||
const [showAll, setShowAll] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
{detailResourceId && (
|
||||
<MemberProgressModal
|
||||
member={member}
|
||||
teamId={teamId}
|
||||
resourceId={detailResourceId}
|
||||
resourceType={'roadmap'}
|
||||
onClose={() => {
|
||||
setDetailResourceId(null);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={`flex h-full min-h-[270px] flex-col overflow-hidden rounded-md border`}
|
||||
key={member._id}
|
||||
@@ -52,7 +38,7 @@ export function MemberProgressItem(props: MemberProgressItemProps) {
|
||||
{isMyProgress && (
|
||||
<div className="inline-grid grid-cols-[auto,32px] items-center gap-1.5">
|
||||
<h3 className="truncate font-medium">{member.name}</h3>
|
||||
<span className="rounded-md bg-red-500 py-0.5 px-1 text-xs text-white">
|
||||
<span className="rounded-md bg-red-500 px-1 py-0.5 text-xs text-white">
|
||||
You
|
||||
</span>
|
||||
</div>
|
||||
@@ -65,7 +51,7 @@ export function MemberProgressItem(props: MemberProgressItemProps) {
|
||||
(progress) => {
|
||||
return (
|
||||
<button
|
||||
onClick={() => setDetailResourceId(progress.resourceId)}
|
||||
onClick={() => onShowResourceProgress(progress.resourceId)}
|
||||
className="group relative overflow-hidden rounded-md border p-2 hover:border-gray-300 hover:text-black focus:outline-none"
|
||||
key={progress.resourceId}
|
||||
>
|
||||
|
@@ -7,9 +7,9 @@ import { useKeydown } from '../../hooks/use-keydown';
|
||||
import type { TeamMember } from './TeamProgressPage';
|
||||
import { httpGet } from '../../lib/http';
|
||||
import {
|
||||
renderTopicProgress,
|
||||
ResourceProgressType,
|
||||
ResourceType,
|
||||
renderTopicProgress,
|
||||
updateResourceProgress,
|
||||
} from '../../lib/resource-progress';
|
||||
import CloseIcon from '../../icons/close.svg';
|
||||
@@ -17,8 +17,6 @@ import { useToast } from '../../hooks/use-toast';
|
||||
import { useAuth } from '../../hooks/use-auth';
|
||||
import { pageProgressMessage } from '../../stores/page';
|
||||
import { ProgressHint } from './ProgressHint';
|
||||
import QuestionIcon from '../../icons/question.svg';
|
||||
import { InfoIcon } from '../ReactIcons/InfoIcon';
|
||||
|
||||
export type ProgressMapProps = {
|
||||
member: TeamMember;
|
||||
@@ -26,6 +24,7 @@ export type ProgressMapProps = {
|
||||
resourceId: string;
|
||||
resourceType: 'roadmap' | 'best-practice';
|
||||
onClose: () => void;
|
||||
onShowMyProgress: () => void;
|
||||
};
|
||||
|
||||
type MemberProgressResponse = {
|
||||
@@ -36,7 +35,14 @@ type MemberProgressResponse = {
|
||||
};
|
||||
|
||||
export function MemberProgressModal(props: ProgressMapProps) {
|
||||
const { resourceId, member, resourceType, teamId, onClose } = props;
|
||||
const {
|
||||
resourceId,
|
||||
member,
|
||||
resourceType,
|
||||
onShowMyProgress,
|
||||
teamId,
|
||||
onClose,
|
||||
} = props;
|
||||
const user = useAuth();
|
||||
const isCurrentUser = user?.email === member.email;
|
||||
|
||||
@@ -112,6 +118,7 @@ export function MemberProgressModal(props: ProgressMapProps) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
Promise.all([
|
||||
renderResource(resourceJsonUrl),
|
||||
getMemberProgress(teamId, member._id, resourceType, resourceId),
|
||||
@@ -136,7 +143,7 @@ export function MemberProgressModal(props: ProgressMapProps) {
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, []);
|
||||
}, [member]);
|
||||
|
||||
function updateTopicStatus(topicId: string, newStatus: ResourceProgressType) {
|
||||
if (!resourceId || !resourceType || !isCurrentUser) {
|
||||
@@ -176,18 +183,21 @@ export function MemberProgressModal(props: ProgressMapProps) {
|
||||
if (!targetGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
const groupId = targetGroup.dataset ? targetGroup.dataset.groupId : '';
|
||||
if (!groupId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetGroup.classList.contains('removed')) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
const isCurrentStatusDone = targetGroup.classList.contains('done');
|
||||
const normalizedGroupId = groupId.replace(/^\d+-/, '');
|
||||
|
||||
updateTopicStatus(
|
||||
normalizedGroupId,
|
||||
!isCurrentStatusDone ? 'done' : 'pending'
|
||||
@@ -235,7 +245,7 @@ export function MemberProgressModal(props: ProgressMapProps) {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!isCurrentUser || !containerEl.current) {
|
||||
if (!member || !containerEl.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -246,7 +256,7 @@ export function MemberProgressModal(props: ProgressMapProps) {
|
||||
containerEl.current?.removeEventListener('contextmenu', handleRightClick);
|
||||
containerEl.current?.removeEventListener('click', handleClick);
|
||||
};
|
||||
}, []);
|
||||
}, [member]);
|
||||
|
||||
const removedTopics = memberProgress?.removed || [];
|
||||
const memberDone =
|
||||
@@ -282,19 +292,18 @@ export function MemberProgressModal(props: ProgressMapProps) {
|
||||
{isCurrentUser ? (
|
||||
<div className="mb-5 mt-0 text-left md:mt-4 md:text-center">
|
||||
<h2 className={'mb-1 text-lg font-bold md:text-2xl'}>
|
||||
Your Progress
|
||||
Update Your Progress
|
||||
</h2>
|
||||
<p className={'text-gray-500'}>
|
||||
You can{' '}
|
||||
Follow the{' '}
|
||||
<button
|
||||
className="inline-flex items-center text-blue-600 underline"
|
||||
onClick={() => {
|
||||
setShowProgressHint(true);
|
||||
}}
|
||||
>
|
||||
follow these instructions
|
||||
instructions to update your progress
|
||||
</button>{' '}
|
||||
to update your progress below.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
@@ -308,28 +317,29 @@ export function MemberProgressModal(props: ProgressMapProps) {
|
||||
}
|
||||
>
|
||||
You are looking at {member.name}'s progress.{' '}
|
||||
<a
|
||||
target={'_blank'}
|
||||
href={`/${resourceId}?t=${teamId}`}
|
||||
<button
|
||||
className="text-blue-600 underline"
|
||||
onClick={onShowMyProgress}
|
||||
>
|
||||
View your progress
|
||||
</a>
|
||||
</button>
|
||||
.
|
||||
</p>
|
||||
<p className={'block text-gray-500 md:hidden'}>
|
||||
View your progress
|
||||
<a
|
||||
target={'_blank'}
|
||||
href={`/${resourceId}?t=${teamId}`}
|
||||
<button
|
||||
className="text-blue-600 underline"
|
||||
onClick={onShowMyProgress}
|
||||
>
|
||||
on the roadmap page.
|
||||
</a>
|
||||
View your progress.
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<p class="-mx-4 mb-3 flex items-center justify-start border-b border-t px-4 py-2 text-sm sm:hidden">
|
||||
<p
|
||||
class={`-mx-4 mb-3 flex items-center justify-start border-b border-t px-4 py-2 text-sm sm:hidden ${
|
||||
isLoading ? 'striped-loader' : ''
|
||||
}`}
|
||||
>
|
||||
<span class="mr-2.5 block rounded-sm bg-yellow-200 px-1 py-0.5 text-xs font-medium uppercase text-yellow-900">
|
||||
<span>{progressPercentage}</span>% Done
|
||||
</span>
|
||||
@@ -338,7 +348,11 @@ export function MemberProgressModal(props: ProgressMapProps) {
|
||||
<span>{memberDone}</span> of <span>{memberTotal}</span> done
|
||||
</span>
|
||||
</p>
|
||||
<p class="-mx-4 mb-3 hidden items-center justify-center border-b border-t py-2 text-sm sm:flex">
|
||||
<p
|
||||
class={`-mx-4 mb-3 hidden items-center justify-center border-b border-t py-2 text-sm sm:flex ${
|
||||
isLoading ? 'striped-loader' : ''
|
||||
}`}
|
||||
>
|
||||
<span class="mr-2.5 block rounded-sm bg-yellow-200 px-1 py-0.5 text-xs font-medium uppercase text-yellow-900">
|
||||
<span>{progressPercentage}</span>% Done
|
||||
</span>
|
||||
@@ -385,7 +399,7 @@ export function MemberProgressModal(props: ProgressMapProps) {
|
||||
className="popup-close absolute right-2.5 top-3 ml-auto inline-flex items-center rounded-lg bg-transparent p-1.5 text-sm text-gray-400 hover:bg-gray-200 hover:text-gray-900 sm:hidden"
|
||||
onClick={onClose}
|
||||
>
|
||||
<img src={CloseIcon} className="h-4 w-4" />
|
||||
<img alt={'close'} src={CloseIcon} className="h-4 w-4" />
|
||||
<span class="sr-only">Close modal</span>
|
||||
</button>
|
||||
</div>
|
||||
|
@@ -15,11 +15,12 @@ export function ProgressHint(props: ProgressHintProps) {
|
||||
useKeydown('Escape', () => {
|
||||
onClose();
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="fixed left-0 right-0 top-0 z-50 h-full items-center justify-center overflow-y-auto overflow-x-hidden overscroll-contain bg-black/50">
|
||||
<div className="relative flex h-full w-full items-center justify-center">
|
||||
<div
|
||||
className="relative w-full max-w-lg rounded-md border border-yellow-300 bg-yellow-50 px-3 py-3 text-gray-500"
|
||||
className="relative w-full max-w-lg rounded-md bg-yellow-50 px-3 py-3 text-gray-500"
|
||||
ref={containerEl}
|
||||
>
|
||||
<span className="mb-1.5 block text-xs font-medium uppercase text-green-600">
|
||||
|
@@ -9,6 +9,7 @@ import { $currentTeam } from '../../stores/team';
|
||||
import { GroupRoadmapItem } from './GroupRoadmapItem';
|
||||
import { getUrlParams, setUrlParams } from '../../lib/browser';
|
||||
import { useAuth } from '../../hooks/use-auth';
|
||||
import { MemberProgressModal } from './MemberProgressModal';
|
||||
|
||||
export type UserProgress = {
|
||||
resourceTitle: string;
|
||||
@@ -56,6 +57,11 @@ export function TeamProgressPage() {
|
||||
const currentTeam = useStore($currentTeam);
|
||||
const user = useAuth();
|
||||
|
||||
const [showMemberProgress, setShowMemberProgress] = useState<{
|
||||
resourceId: string;
|
||||
member: TeamMember;
|
||||
}>();
|
||||
|
||||
const [teamMembers, setTeamMembers] = useState<TeamMember[]>([]);
|
||||
const [selectedGrouping, setSelectedGrouping] = useState<
|
||||
'roadmap' | 'member'
|
||||
@@ -88,12 +94,10 @@ export function TeamProgressPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
getTeamProgress().then(
|
||||
() => {
|
||||
pageProgressMessage.set('');
|
||||
setIsLoading(false);
|
||||
}
|
||||
);
|
||||
getTeamProgress().then(() => {
|
||||
pageProgressMessage.set('');
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, [teamId]);
|
||||
|
||||
if (isLoading) {
|
||||
@@ -143,13 +147,34 @@ export function TeamProgressPage() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{showMemberProgress && (
|
||||
<MemberProgressModal
|
||||
member={showMemberProgress.member}
|
||||
teamId={teamId}
|
||||
resourceId={showMemberProgress.resourceId}
|
||||
resourceType={'roadmap'}
|
||||
onClose={() => {
|
||||
setShowMemberProgress(undefined);
|
||||
}}
|
||||
onShowMyProgress={() => {
|
||||
setShowMemberProgress({
|
||||
resourceId: showMemberProgress.resourceId,
|
||||
member: teamMembers.find(
|
||||
(member) => member.email === user?.email
|
||||
)!,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{groupingTypes.map((grouping) => (
|
||||
<button
|
||||
className={`rounded-md border p-1 px-2 text-sm ${selectedGrouping === grouping.value
|
||||
? ' border-gray-400 bg-gray-200 '
|
||||
: ''
|
||||
}`}
|
||||
className={`rounded-md border p-1 px-2 text-sm ${
|
||||
selectedGrouping === grouping.value
|
||||
? ' border-gray-400 bg-gray-200 '
|
||||
: ''
|
||||
}`}
|
||||
onClick={() => setSelectedGrouping(grouping.value)}
|
||||
>
|
||||
{grouping.label}
|
||||
@@ -162,7 +187,16 @@ export function TeamProgressPage() {
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
{groupByRoadmap.map((roadmap) => {
|
||||
return (
|
||||
<GroupRoadmapItem key={roadmap.resourceId} roadmap={roadmap} />
|
||||
<GroupRoadmapItem
|
||||
key={roadmap.resourceId}
|
||||
roadmap={roadmap}
|
||||
onShowResourceProgress={(member, resourceId) => {
|
||||
setShowMemberProgress({
|
||||
resourceId,
|
||||
member,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
@@ -171,9 +205,14 @@ export function TeamProgressPage() {
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
{teamMembers.map((member) => (
|
||||
<MemberProgressItem
|
||||
teamId={teamId}
|
||||
member={member}
|
||||
isMyProgress={member?.email === user?.email}
|
||||
onShowResourceProgress={(resourceId) => {
|
||||
setShowMemberProgress({
|
||||
resourceId,
|
||||
member,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user