feat: team personal progress only (#5586)

* feat: team personal progress only

* fix: default false
pull/5611/head
Arik Chakma 7 months ago committed by GitHub
parent 32cbfd6699
commit 66e4793032
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 23
      src/components/CreateTeam/CreateTeamForm.tsx
  2. 24
      src/components/CreateTeam/Step1.tsx
  3. 84
      src/components/TeamActivity/TeamActivityPage.tsx
  4. 40
      src/components/TeamSettings/UpdateTeamForm.tsx
  5. 1
      tsconfig.json

@ -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]);
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]);
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 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()
);
});
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()
);
});
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…
Cancel
Save