Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/backend/src/prisma-query-args/projects.query-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ export const getProjectPreviewQueryArgs = (organizationId: string) =>
abbreviation: true,
teams: {
select: {
teamType: {
select: {
teamTypeId: true,
name: true
}
},
teamId: true,
teamName: true
}
Expand Down Expand Up @@ -132,6 +138,12 @@ export const getProjectOverviewQueryArgs = (organizationId: string) =>
abbreviation: true,
teams: {
select: {
teamType: {
select: {
teamTypeId: true,
name: true
}
},
teamId: true,
teamName: true
}
Expand Down
13 changes: 13 additions & 0 deletions src/backend/src/transformers/projects.transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,19 @@ export const projectPreviewTransformer = (project: Prisma.ProjectGetPayload<Proj
duration: calculateDuration(project.workPackages),
startDate: calculateProjectStartDate(project.workPackages),
abbreviation: project.abbreviation ?? undefined,
teamTypes: Array.from(
project.teams
.reduce((acc, team) => {
if (team.teamType) {
acc.set(team.teamType.teamTypeId, {
name: team.teamType.name,
teamTypeId: team.teamType.teamTypeId
});
}
return acc;
}, new Map<string, { name: string; teamTypeId: string }>())
.values()
),
teams: project.teams,
workPackages: project.workPackages.map((wp) => ({
...wp,
Expand Down
2 changes: 1 addition & 1 deletion src/docs-site/scripts/sync-skills.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '';
Expand Down
115 changes: 115 additions & 0 deletions src/frontend/src/pages/GuestProjectsPage/GuestProjectsCard.tsx
Original file line number Diff line number Diff line change
@@ -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<ProjectCardProps> = ({ project }) => {
const theme = useTheme();
const { data: singleProject, isLoading, isError, error } = useSingleProject(project.wbsNum);
if (isLoading || !singleProject) return <LoadingIndicator />;
if (isError) return <ErrorPage message={error.message} />;

const activeWorkPackages = project.workPackages.filter((wp) => wp.status === WbsElementStatus.Active);

return (
<Card
variant="outlined"
sx={{
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
background: theme.palette.background.paper,
borderRadius: 2
}}
>
<CardContent sx={{ padding: 2, display: 'flex', flexDirection: 'column', flexGrow: 1 }}>
<Stack direction="row" justifyContent="space-between">
<Box width={'100%'}>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography
fontWeight={'regular'}
variant="h5"
sx={{ marginBottom: '0.3rem', fontSize: { xs: '1.15rem', sm: '1.5rem' }, flexGrow: 1 }}
>
{wbsNamePipe(singleProject)}
</Typography>
{activeWorkPackages[0]?.stage ? (
<Chip
size="medium"
variant="filled"
sx={{
marginLeft: 'auto',
fontSize: 12,
bgcolor: alpha(theme.palette.primary.main, 0.45),
color: theme.palette.primary.light
}}
label={activeWorkPackages[0].stage}
/>
) : null}
</Box>
<Typography fontSize={12} color="text.secondary">
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'}
</Typography>
<Typography fontWeight={'regular'} fontSize={{ xs: 14, sm: 16 }}>
{datePipe(singleProject.startDate) +
' ⟝ ' +
singleProject.duration +
' wks ⟞ ' +
datePipe(singleProject.endDate)}
</Typography>
</Box>
</Stack>
<Typography
sx={{
flexGrow: 1,
overflow: 'hidden',
textOverflow: 'ellipsis',
display: '-webkit-box',
WebkitLineClamp: 3,
WebkitBoxOrient: 'vertical'
}}
>
{singleProject.summary}
</Typography>
<Link
component={RouterLink}
to={`/projects/${wbsPipe(project.wbsNum)}`}
sx={{ width: '100%', textDecoration: 'none' }}
>
<NERButton
fullWidth
sx={{
marginTop: 2,
backgroundColor: theme.palette.error.main,
color: theme.palette.error.contrastText,
'&:hover': {
backgroundColor: theme.palette.error.dark
}
}}
>
Learn more
</NERButton>
</Link>
</CardContent>
</Card>
);
};

export default GuestProjectsCard;
77 changes: 77 additions & 0 deletions src/frontend/src/pages/GuestProjectsPage/GuestProjectsPage.tsx
Original file line number Diff line number Diff line change
@@ -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<string[]>([]);
const isMobilePortrait = useMediaQuery('(max-width:480px)');
const {
isLoading: teamTypesIsLoading,
isError: teamTypesIsError,
data: teamTypes,
error: teamTypesError
} = useAllTeamTypes();

if (isLoading || !allProjects || teamTypesIsLoading || !teamTypes) return <LoadingIndicator />;
if (isError) return <ErrorPage message={error.message} />;
if (teamTypesIsError) return <ErrorPage message={teamTypesError.message} />;

const filteredProjects = allProjects.filter(
(project) =>
selectedTeamTypes.length === 0 || project.teamTypes.some((t) => t !== null && selectedTeamTypes.includes(t.name))
);

return (
<PageLayout title="Projects">
<Box
width={'100%'}
display={'flex'}
justifyContent={'center'}
gap={2}
mb={3}
sx={{
overflowX: 'auto',
pb: 1
}}
>
{teamTypes.map((team) => (
<Chip
key={team.name}
label={team.name}
onClick={() =>
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 }}
/>
))}
</Box>
<Box
sx={{
display: 'grid',
gridTemplateColumns: isMobilePortrait ? '1fr' : 'repeat(3, 1fr)',
gap: isMobilePortrait ? 2 : 3,
width: '100%',
px: isMobilePortrait ? 1 : 0
}}
>
{filteredProjects.map((p) => (
<GuestProjectsCard key={wbsPipe(p.wbsNum)} project={p} />
))}
</Box>
</PageLayout>
);
};

export default GuestProjectsPage;
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -51,7 +50,11 @@ const FeaturedProjects: React.FC = () => {
{featuredProjects.length === 0 ? (
<NoFeaturedProjectsDisplay />
) : (
featuredProjects.map((p) => <FeaturedProjectsCard key={wbsPipe(p.wbsNum)} project={p} />)
featuredProjects.map((p) => (
<Box key={p.wbsNum.projectNumber} sx={{ width: isMobilePortrait ? '100%' : 300, flexShrink: 0 }}>
<GuestProjectsCard project={p} />
</Box>
))
)}
</Stack>
</ScrollablePageBlock>
Expand Down
4 changes: 4 additions & 0 deletions src/frontend/src/pages/ProjectsPage/ProjectsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -24,6 +25,9 @@ const ProjectsPage: React.FC = () => {
const user = useCurrentUser();
const history = useHistory();

if (isGuest(user.role)) {
return <GuestProjectsPage />;
}
return (
<PageLayout
title="Projects"
Expand Down
1 change: 1 addition & 0 deletions src/shared/src/types/project-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export interface ProjectPreview extends WbsElementPreview {
abbreviation?: string;
workPackages: WorkPackagePreview[];
teams: { teamName: string; teamId: string }[];
teamTypes: { name: string; teamTypeId: string }[];
}

export interface ProjectOverview extends ProjectPreview {
Expand Down
Loading