diff --git a/src/backend/src/prisma-query-args/projects.query-args.ts b/src/backend/src/prisma-query-args/projects.query-args.ts index 1c430b3c8a..01bba4116b 100644 --- a/src/backend/src/prisma-query-args/projects.query-args.ts +++ b/src/backend/src/prisma-query-args/projects.query-args.ts @@ -100,6 +100,12 @@ export const getProjectPreviewQueryArgs = (organizationId: string) => abbreviation: true, teams: { select: { + teamType: { + select: { + teamTypeId: true, + name: true + } + }, teamId: true, teamName: true } @@ -132,6 +138,12 @@ export const getProjectOverviewQueryArgs = (organizationId: string) => abbreviation: true, teams: { select: { + teamType: { + select: { + teamTypeId: true, + name: true + } + }, teamId: true, teamName: true } diff --git a/src/backend/src/transformers/projects.transformer.ts b/src/backend/src/transformers/projects.transformer.ts index 85325ae54f..7b068470f8 100644 --- a/src/backend/src/transformers/projects.transformer.ts +++ b/src/backend/src/transformers/projects.transformer.ts @@ -117,6 +117,19 @@ export const projectPreviewTransformer = (project: Prisma.ProjectGetPayload { + if (team.teamType) { + acc.set(team.teamType.teamTypeId, { + name: team.teamType.name, + teamTypeId: team.teamType.teamTypeId + }); + } + return acc; + }, new Map()) + .values() + ), teams: project.teams, workPackages: project.workPackages.map((wp) => ({ ...wp, diff --git a/src/docs-site/scripts/sync-skills.mjs b/src/docs-site/scripts/sync-skills.mjs index bc905547c0..c707431bbf 100644 --- a/src/docs-site/scripts/sync-skills.mjs +++ b/src/docs-site/scripts/sync-skills.mjs @@ -46,7 +46,7 @@ function parseFrontmatter(content) { const restOfContent = content.slice(match[0].length); // Parse YAML - const lines = yamlContent.split('\n'); + const lines = yamlContent.split(/\r?\n/); const metadata = {}; let currentKey = null; let currentValue = ''; diff --git a/src/frontend/src/pages/GuestProjectsPage/GuestProjectsCard.tsx b/src/frontend/src/pages/GuestProjectsPage/GuestProjectsCard.tsx new file mode 100644 index 0000000000..dde9a82f3d --- /dev/null +++ b/src/frontend/src/pages/GuestProjectsPage/GuestProjectsCard.tsx @@ -0,0 +1,115 @@ +import { alpha, Box, Card, CardContent, Chip, Stack, Typography, useTheme, Link } from '@mui/material'; +import { wbsNamePipe, ProjectPreview, wbsPipe, WbsElementStatus } from 'shared'; +import { datePipe } from '../../utils/pipes'; +import { NERButton } from '../../components/NERButton'; +import { useSingleProject } from '../../hooks/projects.hooks'; +import LoadingIndicator from '../../components/LoadingIndicator'; +import ErrorPage from '../ErrorPage'; +import { Link as RouterLink } from 'react-router-dom'; + +interface ProjectCardProps { + project: ProjectPreview; +} + +const GuestProjectsCard: React.FC = ({ project }) => { + const theme = useTheme(); + const { data: singleProject, isLoading, isError, error } = useSingleProject(project.wbsNum); + if (isLoading || !singleProject) return ; + if (isError) return ; + + const activeWorkPackages = project.workPackages.filter((wp) => wp.status === WbsElementStatus.Active); + + return ( + + + + + + + {wbsNamePipe(singleProject)} + + {activeWorkPackages[0]?.stage ? ( + + ) : null} + + + Project Lead:{' '} + {singleProject.lead?.firstName && singleProject.lead?.lastName + ? `${singleProject.lead.firstName} ${singleProject.lead.lastName}` + : 'N/A'} + {' • '} + Project Manager:{' '} + {singleProject.manager?.firstName && singleProject.manager?.lastName + ? `${singleProject.manager.firstName} ${singleProject.manager.lastName}` + : 'N/A'} + + + {datePipe(singleProject.startDate) + + ' ⟝ ' + + singleProject.duration + + ' wks ⟞ ' + + datePipe(singleProject.endDate)} + + + + + {singleProject.summary} + + + + Learn more + + + + + ); +}; + +export default GuestProjectsCard; diff --git a/src/frontend/src/pages/GuestProjectsPage/GuestProjectsPage.tsx b/src/frontend/src/pages/GuestProjectsPage/GuestProjectsPage.tsx new file mode 100644 index 0000000000..c3b6f1587a --- /dev/null +++ b/src/frontend/src/pages/GuestProjectsPage/GuestProjectsPage.tsx @@ -0,0 +1,77 @@ +import { useAllProjects } from '../../hooks/projects.hooks'; +import LoadingIndicator from '../../components/LoadingIndicator'; +import ErrorPage from '../ErrorPage'; +import { Box, useMediaQuery } from '@mui/system'; +import { wbsPipe } from 'shared'; +import PageLayout from '../../components/PageLayout'; +import GuestProjectsCard from './GuestProjectsCard'; +import { useAllTeamTypes } from '../../hooks/team-types.hooks'; +import { Chip } from '@mui/material'; +import { useState } from 'react'; + +const GuestProjectsPage: React.FC = () => { + const { data: allProjects, isLoading, isError, error } = useAllProjects(); + const [selectedTeamTypes, setSelectedTeamTypes] = useState([]); + const isMobilePortrait = useMediaQuery('(max-width:480px)'); + const { + isLoading: teamTypesIsLoading, + isError: teamTypesIsError, + data: teamTypes, + error: teamTypesError + } = useAllTeamTypes(); + + if (isLoading || !allProjects || teamTypesIsLoading || !teamTypes) return ; + if (isError) return ; + if (teamTypesIsError) return ; + + const filteredProjects = allProjects.filter( + (project) => + selectedTeamTypes.length === 0 || project.teamTypes.some((t) => t !== null && selectedTeamTypes.includes(t.name)) + ); + + return ( + + + {teamTypes.map((team) => ( + + setSelectedTeamTypes((prev) => + prev?.includes(team.name) ? prev.filter((t: string) => t !== team.name) : [...(prev || []), team.name] + ) + } + clickable + color={selectedTeamTypes?.includes(team.name) ? 'primary' : 'default'} + sx={{ flexShrink: 0 }} + /> + ))} + + + {filteredProjects.map((p) => ( + + ))} + + + ); +}; + +export default GuestProjectsPage; diff --git a/src/frontend/src/pages/HomePage/components/FeaturedProjects.tsx b/src/frontend/src/pages/HomePage/components/FeaturedProjects.tsx index 1b25ed884d..8b930d5017 100644 --- a/src/frontend/src/pages/HomePage/components/FeaturedProjects.tsx +++ b/src/frontend/src/pages/HomePage/components/FeaturedProjects.tsx @@ -3,15 +3,14 @@ * See the LICENSE file in the repository root folder for details. */ -import FeaturedProjectsCard from './FeaturedProjectsCard'; import { useFeaturedProjects } from '../../../hooks/organizations.hooks'; import ErrorPage from '../../ErrorPage'; -import { wbsPipe } from 'shared'; import LoadingIndicator from '../../../components/LoadingIndicator'; import ScrollablePageBlock from './ScrollablePageBlock'; import EmptyPageBlockDisplay from './EmptyPageBlockDisplay'; import { Box, Stack, useMediaQuery } from '@mui/material'; import { Error } from '@mui/icons-material'; +import GuestProjectsCard from '../../GuestProjectsPage/GuestProjectsCard'; const NoFeaturedProjectsDisplay: React.FC = () => { return ( @@ -51,7 +50,11 @@ const FeaturedProjects: React.FC = () => { {featuredProjects.length === 0 ? ( ) : ( - featuredProjects.map((p) => ) + featuredProjects.map((p) => ( + + + + )) )} diff --git a/src/frontend/src/pages/ProjectsPage/ProjectsPage.tsx b/src/frontend/src/pages/ProjectsPage/ProjectsPage.tsx index c50e69e35f..0d35d580dc 100644 --- a/src/frontend/src/pages/ProjectsPage/ProjectsPage.tsx +++ b/src/frontend/src/pages/ProjectsPage/ProjectsPage.tsx @@ -14,6 +14,7 @@ import { useCurrentUser } from '../../hooks/users.hooks'; import { isGuest } from 'shared'; import { Add } from '@mui/icons-material'; import { useHistory } from 'react-router-dom'; +import GuestProjectsPage from '../GuestProjectsPage/GuestProjectsPage'; /** * Cards of all projects that this user is in their team. @@ -24,6 +25,9 @@ const ProjectsPage: React.FC = () => { const user = useCurrentUser(); const history = useHistory(); + if (isGuest(user.role)) { + return ; + } return (