mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-04-15 12:53:41 +02:00
feat: team personal progress only (#5586)
* feat: team personal progress only * fix: default false
This commit is contained in:
parent
32cbfd6699
commit
66e4793032
@ -9,7 +9,7 @@ import { pageProgressMessage } from '../../stores/page';
|
||||
import type { TeamResourceConfig } from './RoadmapSelector';
|
||||
import { Step3 } from './Step3';
|
||||
import { Step4 } from './Step4';
|
||||
import {useToast} from "../../hooks/use-toast";
|
||||
import { useToast } from '../../hooks/use-toast';
|
||||
|
||||
export interface TeamDocument {
|
||||
_id?: string;
|
||||
@ -22,6 +22,7 @@ export interface TeamDocument {
|
||||
linkedIn?: string;
|
||||
};
|
||||
type: ValidTeamType;
|
||||
personalProgressOnly?: boolean;
|
||||
canMemberSendInvite: boolean;
|
||||
teamSize?: ValidTeamSize;
|
||||
createdAt: Date;
|
||||
@ -40,10 +41,10 @@ export function CreateTeamForm() {
|
||||
|
||||
async function loadTeam(
|
||||
teamIdToFetch: string,
|
||||
requiredStepIndex: number | string
|
||||
requiredStepIndex: number | string,
|
||||
) {
|
||||
const { response, error } = await httpGet<TeamDocument>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-get-team/${teamIdToFetch}`
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-get-team/${teamIdToFetch}`,
|
||||
);
|
||||
|
||||
if (error || !response) {
|
||||
@ -70,7 +71,7 @@ export function CreateTeamForm() {
|
||||
|
||||
async function loadTeamResourceConfig(teamId: string) {
|
||||
const { error, response } = await httpGet<TeamResourceConfig>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-get-team-resource-config/${teamId}`
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-get-team-resource-config/${teamId}`,
|
||||
);
|
||||
if (error || !Array.isArray(response)) {
|
||||
console.error(error);
|
||||
@ -96,7 +97,7 @@ export function CreateTeamForm() {
|
||||
}, [teamId, queryStepIndex]);
|
||||
|
||||
const [selectedTeamType, setSelectedTeamType] = useState<ValidTeamType>(
|
||||
team?.type || 'company'
|
||||
team?.type || 'company',
|
||||
);
|
||||
|
||||
const [completedSteps, setCompletedSteps] = useState([0]);
|
||||
@ -191,13 +192,17 @@ export function CreateTeamForm() {
|
||||
|
||||
return (
|
||||
<div className={'mx-auto max-w-[700px] py-1 md:py-6'}>
|
||||
<div className={'mb-3 md:mb-8 pb-3 md:pb-0 border-b md:border-b-0 flex flex-col items-start md:items-center'}>
|
||||
<h1 className={'text-xl md:text-4xl font-bold'}>Create Team</h1>
|
||||
<p className={'mt-1 md:mt-2 text-sm md:text-base text-gray-500'}>
|
||||
<div
|
||||
className={
|
||||
'mb-3 flex flex-col items-start border-b pb-3 md:mb-8 md:items-center md:border-b-0 md:pb-0'
|
||||
}
|
||||
>
|
||||
<h1 className={'text-xl font-bold md:text-4xl'}>Create Team</h1>
|
||||
<p className={'mt-1 text-sm text-gray-500 md:mt-2 md:text-base'}>
|
||||
Complete the steps below to create your team
|
||||
</p>
|
||||
</div>
|
||||
<div className="mb-8 mt-8 hidden sm:flex w-full">
|
||||
<div className="mb-8 mt-8 hidden w-full sm:flex">
|
||||
<Stepper
|
||||
activeIndex={stepIndex}
|
||||
completeSteps={completedSteps}
|
||||
|
@ -46,7 +46,7 @@ export function Step1(props: Step1Props) {
|
||||
const [linkedInUrl, setLinkedInUrl] = useState(team?.links?.linkedIn || '');
|
||||
const [gitHubUrl, setGitHubUrl] = useState(team?.links?.github || '');
|
||||
const [teamSize, setTeamSize] = useState<ValidTeamSize>(
|
||||
team?.teamSize || ('' as any)
|
||||
team?.teamSize || ('' as any),
|
||||
);
|
||||
|
||||
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||
@ -74,7 +74,7 @@ export function Step1(props: Step1Props) {
|
||||
}),
|
||||
roadmapIds: [],
|
||||
bestPracticeIds: [],
|
||||
}
|
||||
},
|
||||
));
|
||||
|
||||
if (error || !response?._id) {
|
||||
@ -96,7 +96,7 @@ export function Step1(props: Step1Props) {
|
||||
teamSize,
|
||||
linkedInUrl: linkedInUrl || undefined,
|
||||
}),
|
||||
}
|
||||
},
|
||||
));
|
||||
|
||||
if (error || (response as any)?.status !== 'ok') {
|
||||
@ -168,7 +168,10 @@ export function Step1(props: Step1Props) {
|
||||
|
||||
{selectedTeamType === 'company' && (
|
||||
<div className="mt-4 flex w-full flex-col">
|
||||
<label htmlFor="website" className="text-sm leading-none text-slate-500">
|
||||
<label
|
||||
htmlFor="website"
|
||||
className="text-sm leading-none text-slate-500"
|
||||
>
|
||||
Company LinkedIn URL
|
||||
</label>
|
||||
<input
|
||||
@ -187,7 +190,10 @@ export function Step1(props: Step1Props) {
|
||||
)}
|
||||
|
||||
<div className="mt-4 flex w-full flex-col">
|
||||
<label htmlFor="website" className="text-sm leading-none text-slate-500">
|
||||
<label
|
||||
htmlFor="website"
|
||||
className="text-sm leading-none text-slate-500"
|
||||
>
|
||||
GitHub Organization URL
|
||||
</label>
|
||||
<input
|
||||
@ -221,11 +227,11 @@ export function Step1(props: Step1Props) {
|
||||
setTeamSize((e.target as HTMLSelectElement).value as any)
|
||||
}
|
||||
>
|
||||
<option value="">
|
||||
Select team size
|
||||
</option>
|
||||
<option value="">Select team size</option>
|
||||
{validTeamSizes.map((size) => (
|
||||
<option key={size} value={size}>{size} people</option>
|
||||
<option key={size} value={size}>
|
||||
{size} people
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
@ -98,38 +98,70 @@ export function TeamActivityPage() {
|
||||
}, [teamId]);
|
||||
|
||||
const { users, activities } = teamActivities?.data;
|
||||
const usersWithActivities = useMemo(() => {
|
||||
const validActivities = activities.filter((activity) => {
|
||||
const validActivities = useMemo(() => {
|
||||
return activities?.filter((activity) => {
|
||||
return (
|
||||
activity.activity.length > 0 &&
|
||||
activity.activity.some((t) => (t?.topicIds?.length || 0) > 0)
|
||||
);
|
||||
});
|
||||
}, [activities]);
|
||||
|
||||
return users
|
||||
.map((user) => {
|
||||
const userActivities = validActivities
|
||||
.filter((activity) => activity.userId === user._id)
|
||||
.flatMap((activity) => activity.activity)
|
||||
.filter((activity) => (activity?.topicIds?.length || 0) > 0)
|
||||
.sort((a, b) => {
|
||||
return (
|
||||
new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
||||
);
|
||||
});
|
||||
const sortedUniqueCreatedAt = useMemo(() => {
|
||||
return new Set(
|
||||
validActivities
|
||||
?.map((activity) => new Date(activity.createdAt).setHours(0, 0, 0, 0))
|
||||
.sort((a, b) => {
|
||||
return new Date(b).getTime() - new Date(a).getTime();
|
||||
}),
|
||||
);
|
||||
}, [validActivities]);
|
||||
|
||||
return {
|
||||
...user,
|
||||
activities: userActivities,
|
||||
};
|
||||
})
|
||||
.filter((user) => user.activities.length > 0)
|
||||
.sort((a, b) => {
|
||||
return (
|
||||
new Date(b.activities[0].updatedAt).getTime() -
|
||||
new Date(a.activities[0].updatedAt).getTime()
|
||||
);
|
||||
});
|
||||
const usersWithActivities = useMemo(() => {
|
||||
const enrichedUsers: {
|
||||
_id: string;
|
||||
name: string;
|
||||
avatar?: string;
|
||||
username?: string;
|
||||
activities: TeamStreamActivity[];
|
||||
}[] = [];
|
||||
|
||||
for (const uniqueCreatedAt of sortedUniqueCreatedAt) {
|
||||
const uniqueActivities = validActivities.filter(
|
||||
(activity) =>
|
||||
new Date(activity.createdAt).setHours(0, 0, 0, 0) === uniqueCreatedAt,
|
||||
);
|
||||
|
||||
const usersWithUniqueActivities = users
|
||||
.map((user) => {
|
||||
const userActivities = uniqueActivities
|
||||
.filter((activity) => activity.userId === user._id)
|
||||
.flatMap((activity) => activity.activity)
|
||||
.filter((activity) => (activity?.topicIds?.length || 0) > 0)
|
||||
.sort((a, b) => {
|
||||
return (
|
||||
new Date(b.updatedAt).getTime() -
|
||||
new Date(a.updatedAt).getTime()
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
...user,
|
||||
activities: userActivities,
|
||||
};
|
||||
})
|
||||
.filter((user) => user.activities.length > 0)
|
||||
.sort((a, b) => {
|
||||
return (
|
||||
new Date(b.activities[0].updatedAt).getTime() -
|
||||
new Date(a.activities[0].updatedAt).getTime()
|
||||
);
|
||||
});
|
||||
|
||||
enrichedUsers.push(...usersWithUniqueActivities);
|
||||
}
|
||||
|
||||
return enrichedUsers;
|
||||
}, [users, activities]);
|
||||
|
||||
if (!teamId) {
|
||||
|
@ -24,6 +24,7 @@ export function UpdateTeamForm() {
|
||||
const [gitHub, setGitHub] = useState('');
|
||||
const [teamType, setTeamType] = useState('');
|
||||
const [teamSize, setTeamSize] = useState('');
|
||||
const [personalProgressOnly, setPersonalProgressOnly] = useState(false);
|
||||
const validTeamSizes = [
|
||||
'0-1',
|
||||
'2-10',
|
||||
@ -55,11 +56,12 @@ export function UpdateTeamForm() {
|
||||
website,
|
||||
type: teamType,
|
||||
gitHubUrl: gitHub || undefined,
|
||||
personalProgressOnly,
|
||||
...(teamType === 'company' && {
|
||||
teamSize,
|
||||
linkedInUrl: linkedIn || undefined,
|
||||
}),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (error) {
|
||||
@ -77,7 +79,7 @@ export function UpdateTeamForm() {
|
||||
|
||||
async function loadTeam() {
|
||||
const { response, error } = await httpGet<TeamDocument>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-get-team/${teamId}`
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-get-team/${teamId}`,
|
||||
);
|
||||
if (error || !response) {
|
||||
console.log(error);
|
||||
@ -90,6 +92,7 @@ export function UpdateTeamForm() {
|
||||
setLinkedIn(response?.links?.linkedIn || '');
|
||||
setGitHub(response?.links?.github || '');
|
||||
setTeamType(response.type);
|
||||
setPersonalProgressOnly(response.personalProgressOnly ?? false);
|
||||
if (response.teamSize) {
|
||||
setTeamSize(response.teamSize);
|
||||
}
|
||||
@ -205,16 +208,14 @@ export function UpdateTeamForm() {
|
||||
<select
|
||||
name="type"
|
||||
id="type"
|
||||
className="mt-2 block w-full rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
|
||||
className="mt-2 block h-[42px] w-full rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
|
||||
disabled={isDisabled}
|
||||
value={teamType || ''}
|
||||
onChange={(e) =>
|
||||
setTeamType((e.target as HTMLSelectElement).value as any)
|
||||
}
|
||||
>
|
||||
<option value="">
|
||||
Select type
|
||||
</option>
|
||||
<option value="">Select type</option>
|
||||
<option value="company">Company</option>
|
||||
<option value="study_group">Study Group</option>
|
||||
</select>
|
||||
@ -231,7 +232,7 @@ export function UpdateTeamForm() {
|
||||
<select
|
||||
name="team-size"
|
||||
id="team-size"
|
||||
className="mt-2 block w-full rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
|
||||
className="mt-2 block h-[42px] w-full rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
|
||||
required={teamType === 'company'}
|
||||
disabled={isDisabled}
|
||||
value={teamSize}
|
||||
@ -249,6 +250,31 @@ export function UpdateTeamForm() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-4 flex h-[42px] w-full items-center rounded-lg border border-gray-300 px-3 py-2 shadow-sm">
|
||||
<label
|
||||
htmlFor="personal-progress-only"
|
||||
className="flex items-center gap-2 text-sm leading-none text-slate-500"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="personal-progress-only"
|
||||
id="personal-progress-only"
|
||||
disabled={isDisabled}
|
||||
checked={personalProgressOnly}
|
||||
onChange={(e) =>
|
||||
setPersonalProgressOnly((e.target as HTMLInputElement).checked)
|
||||
}
|
||||
/>
|
||||
<span>Members can only see their personal progress</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{personalProgressOnly && (
|
||||
<p className="mt-2 rounded-lg border border-orange-300 bg-orange-50 p-2 text-sm text-orange-700">
|
||||
Only admins and managers will be able to see the progress of members
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="mt-4 flex w-full flex-col">
|
||||
<button
|
||||
type="submit"
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "react"
|
||||
|
Loading…
x
Reference in New Issue
Block a user