From a274ee73485d1323bad61a5f26eb64d7d6f0346f Mon Sep 17 00:00:00 2001 From: harish Date: Sun, 16 Jun 2024 13:49:36 -0400 Subject: [PATCH 001/264] #1606 buttons --- .../src/pages/TeamsPage/TeamSpecificPage.tsx | 67 ++++++++++++++++--- .../src/pages/TeamsPage/TeamsPage.tsx | 24 ++++++- 2 files changed, 80 insertions(+), 11 deletions(-) diff --git a/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx b/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx index eb396e8daf..c9c6a5c711 100644 --- a/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx +++ b/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx @@ -1,4 +1,4 @@ -import { Grid } from '@mui/material'; +import { Box, Grid, Menu, MenuItem, Stack } from '@mui/material'; import { useSingleTeam } from '../../hooks/teams.hooks'; import { useParams } from 'react-router-dom'; import TeamMembersPageBlock from './TeamMembersPageBlock'; @@ -16,6 +16,7 @@ import { isAdmin } from 'shared'; import { useState } from 'react'; import DeleteTeamModal from './DeleteTeamModal'; import SetTeamTypeModal from './SetTeamTypeModal'; +import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; interface ParamTypes { teamId: string; @@ -27,6 +28,16 @@ const TeamSpecificPage: React.FC = () => { const user = useCurrentUser(); const [showDeleteModal, setShowDeleteModal] = useState(false); const [showTeamTypeModal, setShowTeamTypeModal] = useState(false); + const [anchorEl, setAnchorEl] = useState(null); + const dropdownOpen = Boolean(anchorEl); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleDropdownClose = () => { + setAnchorEl(null); + }; if (isError) return ; if (isLoading || !data) return ; @@ -34,30 +45,66 @@ const TeamSpecificPage: React.FC = () => { const DeleteButton = () => ( } onClick={() => setShowDeleteModal(true)} - sx={{ marginRight: '10px' }} + disabled={!isAdmin(user.role)} > Delete ); const SetTeamTypeButton = () => ( - setShowTeamTypeModal(true)}> + setShowTeamTypeModal(true)} disabled={!isAdmin(user.role)}> Set Team Type ); + const ArchiveTeamButton = () => ( + + Archive Team + + ); + + const UnarchiveTeamButton = () => ( + + Unarchive + + ); + + const TeamActionsDropdown = () => ( +
+ } + variant="contained" + id="team-actions-dropdown" + onClick={handleClick} + > + Actions + + + + {data.dateArchived ? : } + + + + + +
+ ); + return ( - - - - ) + + + {TeamActionsDropdown()} + } title={`Team ${data.teamName}`} previousPages={[{ name: 'Teams', route: routes.TEAMS }]} diff --git a/src/frontend/src/pages/TeamsPage/TeamsPage.tsx b/src/frontend/src/pages/TeamsPage/TeamsPage.tsx index 436679b15a..83285fcad5 100644 --- a/src/frontend/src/pages/TeamsPage/TeamsPage.tsx +++ b/src/frontend/src/pages/TeamsPage/TeamsPage.tsx @@ -3,7 +3,7 @@ * See the LICENSE file in the repository root folder for details. */ -import { Grid } from '@mui/material'; +import { Grid, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material'; import LoadingIndicator from '../../components/LoadingIndicator'; import { useAllTeams } from '../../hooks/teams.hooks'; import ErrorPage from '../ErrorPage'; @@ -13,6 +13,8 @@ import PageLayout from '../../components/PageLayout'; const TeamsPage: React.FC = () => { const { isLoading, isError, data: teams, error } = useAllTeams(); + const archivedTeams = teams?.filter((team) => team.dateArchived); + if (isLoading || !teams) return ; if (isError) return ; @@ -26,6 +28,26 @@ const TeamsPage: React.FC = () => { ))} + + + + + Name + {/* Add more headers as needed */} + + + + {archivedTeams?.map((team) => ( + + + {team.teamName} + + {/* Add more cells for additional columns */} + + ))} + +
+
); }; From f89a30e81f213cd1b805b52ad4a9ae9cd9855762 Mon Sep 17 00:00:00 2001 From: harish Date: Mon, 17 Jun 2024 17:08:49 -0400 Subject: [PATCH 002/264] #1606 chip on team specific page --- src/frontend/src/pages/TeamsPage/TeamPill.tsx | 20 ++++++++++++++ .../src/pages/TeamsPage/TeamSpecificPage.tsx | 8 +++++- .../src/pages/TeamsPage/TeamsPage.tsx | 26 ++----------------- 3 files changed, 29 insertions(+), 25 deletions(-) create mode 100644 src/frontend/src/pages/TeamsPage/TeamPill.tsx diff --git a/src/frontend/src/pages/TeamsPage/TeamPill.tsx b/src/frontend/src/pages/TeamsPage/TeamPill.tsx new file mode 100644 index 0000000000..5e2b9bc1ca --- /dev/null +++ b/src/frontend/src/pages/TeamsPage/TeamPill.tsx @@ -0,0 +1,20 @@ +import { Typography, Box, Chip } from '@mui/material'; +import { red } from '@mui/material/colors'; + +export const TeamPill: React.FC<{ + displayText: string; +}> = ({ displayText }) => { + return ( + + ); +}; diff --git a/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx b/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx index c9c6a5c711..6cd18e2a8b 100644 --- a/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx +++ b/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx @@ -13,10 +13,11 @@ import { Delete } from '@mui/icons-material'; import { NERButton } from '../../components/NERButton'; import { useCurrentUser } from '../../hooks/users.hooks'; import { isAdmin } from 'shared'; -import { useState } from 'react'; +import React, { useState } from 'react'; import DeleteTeamModal from './DeleteTeamModal'; import SetTeamTypeModal from './SetTeamTypeModal'; import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; +import { TeamPill } from './TeamPill'; interface ParamTypes { teamId: string; @@ -107,6 +108,11 @@ const TeamSpecificPage: React.FC = () => { } title={`Team ${data.teamName}`} + chips={ + + + + } previousPages={[{ name: 'Teams', route: routes.TEAMS }]} > diff --git a/src/frontend/src/pages/TeamsPage/TeamsPage.tsx b/src/frontend/src/pages/TeamsPage/TeamsPage.tsx index 83285fcad5..ca1ef7b39f 100644 --- a/src/frontend/src/pages/TeamsPage/TeamsPage.tsx +++ b/src/frontend/src/pages/TeamsPage/TeamsPage.tsx @@ -3,7 +3,7 @@ * See the LICENSE file in the repository root folder for details. */ -import { Grid, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material'; +import { Grid } from '@mui/material'; import LoadingIndicator from '../../components/LoadingIndicator'; import { useAllTeams } from '../../hooks/teams.hooks'; import ErrorPage from '../ErrorPage'; @@ -13,8 +13,6 @@ import PageLayout from '../../components/PageLayout'; const TeamsPage: React.FC = () => { const { isLoading, isError, data: teams, error } = useAllTeams(); - const archivedTeams = teams?.filter((team) => team.dateArchived); - if (isLoading || !teams) return ; if (isError) return ; @@ -28,28 +26,8 @@ const TeamsPage: React.FC = () => { ))} - - - - - Name - {/* Add more headers as needed */} - - - - {archivedTeams?.map((team) => ( - - - {team.teamName} - - {/* Add more cells for additional columns */} - - ))} - -
-
); }; -export default TeamsPage; +export default TeamsPage; \ No newline at end of file From def5ee56ea1a94936ee80a12851d493bb2175955 Mon Sep 17 00:00:00 2001 From: harish Date: Mon, 17 Jun 2024 17:13:54 -0400 Subject: [PATCH 003/264] #1606 linting and prettier --- src/frontend/src/pages/TeamsPage/TeamPill.tsx | 2 +- src/frontend/src/pages/TeamsPage/TeamsPage.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/pages/TeamsPage/TeamPill.tsx b/src/frontend/src/pages/TeamsPage/TeamPill.tsx index 5e2b9bc1ca..4be99ce6f6 100644 --- a/src/frontend/src/pages/TeamsPage/TeamPill.tsx +++ b/src/frontend/src/pages/TeamsPage/TeamPill.tsx @@ -1,4 +1,4 @@ -import { Typography, Box, Chip } from '@mui/material'; +import { Chip } from '@mui/material'; import { red } from '@mui/material/colors'; export const TeamPill: React.FC<{ diff --git a/src/frontend/src/pages/TeamsPage/TeamsPage.tsx b/src/frontend/src/pages/TeamsPage/TeamsPage.tsx index ca1ef7b39f..436679b15a 100644 --- a/src/frontend/src/pages/TeamsPage/TeamsPage.tsx +++ b/src/frontend/src/pages/TeamsPage/TeamsPage.tsx @@ -30,4 +30,4 @@ const TeamsPage: React.FC = () => { ); }; -export default TeamsPage; \ No newline at end of file +export default TeamsPage; From 2479b6ec2355e09b53d44a749ef5db885614c28a Mon Sep 17 00:00:00 2001 From: harish Date: Sat, 29 Jun 2024 13:18:14 -0400 Subject: [PATCH 004/264] #1606 abstract endpoint --- src/backend/src/controllers/teams.controllers.ts | 3 ++- src/backend/src/routes/teams.routes.ts | 2 +- src/backend/src/services/teams.services.ts | 7 +++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/backend/src/controllers/teams.controllers.ts b/src/backend/src/controllers/teams.controllers.ts index dae0d754ee..49fbaeaa20 100644 --- a/src/backend/src/controllers/teams.controllers.ts +++ b/src/backend/src/controllers/teams.controllers.ts @@ -6,8 +6,9 @@ import { getOrganizationId } from '../utils/utils'; export default class TeamsController { static async getAllTeams(req: Request, res: Response, next: NextFunction) { try { + const ignoreArchive = req.params.ignoreArchive === "true" ? true : false; const organizationId = getOrganizationId(req.headers); - const teams = await TeamsService.getAllTeams(organizationId); + const teams = await TeamsService.getAllTeams(organizationId, ignoreArchive); res.status(200).json(teams); } catch (error: unknown) { diff --git a/src/backend/src/routes/teams.routes.ts b/src/backend/src/routes/teams.routes.ts index bf3f237c66..6411d2edd0 100644 --- a/src/backend/src/routes/teams.routes.ts +++ b/src/backend/src/routes/teams.routes.ts @@ -5,7 +5,7 @@ import { nonEmptyString, validateInputs } from '../utils/validation.utils'; const teamsRouter = express.Router(); -teamsRouter.get('/', TeamsController.getAllTeams); +teamsRouter.get('/:ignoreArchive', TeamsController.getAllTeams); teamsRouter.get('/:teamId', TeamsController.getSingleTeam); teamsRouter.post( '/:teamId/set-members', diff --git a/src/backend/src/services/teams.services.ts b/src/backend/src/services/teams.services.ts index 990dee1baf..4e14d12bf1 100644 --- a/src/backend/src/services/teams.services.ts +++ b/src/backend/src/services/teams.services.ts @@ -20,9 +20,12 @@ export default class TeamsService { * @param organizationId The organization the user is currently in * @returns a list of teams */ - static async getAllTeams(organizationId: string): Promise { + static async getAllTeams(organizationId: string, ignoreArchive: boolean): Promise { const teams = await prisma.team.findMany({ - where: { dateArchived: null, organizationId }, + where: { + organizationId, + ...(ignoreArchive ? { dateArchived: null } : { dateArchived: { not: null } }) + }, ...getTeamQueryArgs(organizationId) }); return teams.map(teamTransformer); From 0a73fa2e8451fb62247f27664d57fd827f3f8c14 Mon Sep 17 00:00:00 2001 From: harish Date: Sun, 7 Jul 2024 13:10:01 -0400 Subject: [PATCH 005/264] #1606 archived teams shown on teams page --- .../src/controllers/teams.controllers.ts | 12 ++++++++-- src/backend/src/routes/teams.routes.ts | 4 ++-- src/backend/src/services/teams.services.ts | 4 ++-- src/frontend/src/apis/teams.api.ts | 4 ++-- src/frontend/src/hooks/teams.hooks.ts | 6 ++--- .../src/pages/TeamsPage/TeamSpecificPage.tsx | 20 ++++++++-------- .../src/pages/TeamsPage/TeamsPage.tsx | 23 ++++++++++++++++--- 7 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/backend/src/controllers/teams.controllers.ts b/src/backend/src/controllers/teams.controllers.ts index 49fbaeaa20..bc3ec019a7 100644 --- a/src/backend/src/controllers/teams.controllers.ts +++ b/src/backend/src/controllers/teams.controllers.ts @@ -6,10 +6,18 @@ import { getOrganizationId } from '../utils/utils'; export default class TeamsController { static async getAllTeams(req: Request, res: Response, next: NextFunction) { try { - const ignoreArchive = req.params.ignoreArchive === "true" ? true : false; const organizationId = getOrganizationId(req.headers); - const teams = await TeamsService.getAllTeams(organizationId, ignoreArchive); + const teams = await TeamsService.getAllTeams(organizationId, false); + res.status(200).json(teams); + } catch (error: unknown) { + next(error); + } + } + static async getAllArchivedTeams(req: Request, res: Response, next: NextFunction) { + try { + const organizationId = getOrganizationId(req.headers); + const teams = await TeamsService.getAllTeams(organizationId, true); res.status(200).json(teams); } catch (error: unknown) { next(error); diff --git a/src/backend/src/routes/teams.routes.ts b/src/backend/src/routes/teams.routes.ts index 6411d2edd0..e744bb5392 100644 --- a/src/backend/src/routes/teams.routes.ts +++ b/src/backend/src/routes/teams.routes.ts @@ -4,8 +4,8 @@ import { body } from 'express-validator'; import { nonEmptyString, validateInputs } from '../utils/validation.utils'; const teamsRouter = express.Router(); - -teamsRouter.get('/:ignoreArchive', TeamsController.getAllTeams); +teamsRouter.get('/', TeamsController.getAllTeams); +teamsRouter.get('/archive', TeamsController.getAllArchivedTeams); teamsRouter.get('/:teamId', TeamsController.getSingleTeam); teamsRouter.post( '/:teamId/set-members', diff --git a/src/backend/src/services/teams.services.ts b/src/backend/src/services/teams.services.ts index 4e14d12bf1..a850cef360 100644 --- a/src/backend/src/services/teams.services.ts +++ b/src/backend/src/services/teams.services.ts @@ -20,11 +20,11 @@ export default class TeamsService { * @param organizationId The organization the user is currently in * @returns a list of teams */ - static async getAllTeams(organizationId: string, ignoreArchive: boolean): Promise { + static async getAllTeams(organizationId: string, onlyArchive: boolean): Promise { const teams = await prisma.team.findMany({ where: { organizationId, - ...(ignoreArchive ? { dateArchived: null } : { dateArchived: { not: null } }) + ...(onlyArchive ? { dateArchived: { not: null } } : { dateArchived: null }) }, ...getTeamQueryArgs(organizationId) }); diff --git a/src/frontend/src/apis/teams.api.ts b/src/frontend/src/apis/teams.api.ts index d6e43b277c..018bcc5c0b 100644 --- a/src/frontend/src/apis/teams.api.ts +++ b/src/frontend/src/apis/teams.api.ts @@ -9,8 +9,8 @@ import { apiUrls } from '../utils/urls'; import { CreateTeamPayload } from '../hooks/teams.hooks'; import { teamTransformer } from './transformers/teams.transformers'; -export const getAllTeams = () => { - return axios.get(apiUrls.teams(), { +export const getAllTeams = (onlyArchive: boolean) => { + return axios.get(apiUrls.teams() + (onlyArchive ? '/archive' : ''), { transformResponse: (data) => JSON.parse(data).map(teamTransformer) }); }; diff --git a/src/frontend/src/hooks/teams.hooks.ts b/src/frontend/src/hooks/teams.hooks.ts index ee64bb60ba..48553280ff 100644 --- a/src/frontend/src/hooks/teams.hooks.ts +++ b/src/frontend/src/hooks/teams.hooks.ts @@ -24,9 +24,9 @@ export interface CreateTeamPayload { isFinanceTeam: boolean; } -export const useAllTeams = () => { - return useQuery(['teams'], async () => { - const { data } = await getAllTeams(); +export const useAllTeams = (onlyArchive: boolean) => { + return useQuery(['teams', onlyArchive], async () => { + const { data } = await getAllTeams(onlyArchive); return data; }); }; diff --git a/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx b/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx index 6cd18e2a8b..e3f660fd18 100644 --- a/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx +++ b/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx @@ -60,20 +60,18 @@ const TeamSpecificPage: React.FC = () => {
); - const ArchiveTeamButton = () => ( + interface ArchiveTeamButtonProps { + archive: boolean; + } + + const ArchiveTeamButton: React.FC = ({ archive }) => ( - Archive Team - - ); - - const UnarchiveTeamButton = () => ( - - Unarchive + {archive ? 'Archive Team' : 'Unarchive Team'} ); const TeamActionsDropdown = () => ( -
+ } variant="contained" @@ -90,13 +88,13 @@ const TeamSpecificPage: React.FC = () => { transformOrigin={{ vertical: 'top', horizontal: 'left' }} > - {data.dateArchived ? : } + -
+ ); return ( diff --git a/src/frontend/src/pages/TeamsPage/TeamsPage.tsx b/src/frontend/src/pages/TeamsPage/TeamsPage.tsx index 436679b15a..1ab2f355c0 100644 --- a/src/frontend/src/pages/TeamsPage/TeamsPage.tsx +++ b/src/frontend/src/pages/TeamsPage/TeamsPage.tsx @@ -9,15 +9,22 @@ import { useAllTeams } from '../../hooks/teams.hooks'; import ErrorPage from '../ErrorPage'; import TeamSummary from './TeamSummary'; import PageLayout from '../../components/PageLayout'; +import { FlashOffRounded } from '@mui/icons-material'; const TeamsPage: React.FC = () => { - const { isLoading, isError, data: teams, error } = useAllTeams(); + const { isLoading: teamsLoading, isError: isTeamsError, data: teams, error: teamsError } = useAllTeams(false); - if (isLoading || !teams) return ; + const { isLoading: archivedTeamsLoading, isError: isArchivedTeamsError, data: archivedTeams, error: archivedTeamsError } = useAllTeams(true); + console.log(archivedTeams) - if (isError) return ; + if (teamsLoading || !teams) return ; + if (archivedTeamsLoading || !archivedTeams) return ; + + if (isArchivedTeamsError) return ; + if (isTeamsError) return ; return ( + <> {teams.map((team) => ( @@ -27,6 +34,16 @@ const TeamsPage: React.FC = () => { ))} + + + {archivedTeams.map((archivedTeam) => ( + + + + ))} + + + ); }; From 5ad948b6e777d89c030872b0fd380c8d22a5eff4 Mon Sep 17 00:00:00 2001 From: harish Date: Sun, 7 Jul 2024 13:18:14 -0400 Subject: [PATCH 006/264] #1606 fixing hook calls --- src/frontend/src/components/TeamsDropdown.tsx | 2 +- .../AdminToolsAttendeeDesignReviewInfo.tsx | 4 +- .../AdminToolsPage/TeamConfig/TeamsTools.tsx | 2 +- .../src/pages/GanttPage/GanttChartPage.tsx | 2 +- .../pages/SettingsPage/SettingsDetails.tsx | 2 +- .../SettingsPage/SettingsPreferences.tsx | 2 +- .../src/pages/TeamsPage/TeamSpecificPage.tsx | 2 +- .../src/pages/TeamsPage/TeamsPage.tsx | 46 ++++++++++--------- 8 files changed, 33 insertions(+), 29 deletions(-) diff --git a/src/frontend/src/components/TeamsDropdown.tsx b/src/frontend/src/components/TeamsDropdown.tsx index daf875b572..afcc5b3772 100644 --- a/src/frontend/src/components/TeamsDropdown.tsx +++ b/src/frontend/src/components/TeamsDropdown.tsx @@ -10,7 +10,7 @@ interface TeamDropdownProps { } const TeamDropdown = ({ control, name, multiselect = false }: TeamDropdownProps) => { - const { isLoading, data: teams } = useAllTeams(); + const { isLoading, data: teams } = useAllTeams(false); if (isLoading || !teams) return ; return ( diff --git a/src/frontend/src/pages/AdminToolsPage/AdminToolsAttendeeDesignReviewInfo.tsx b/src/frontend/src/pages/AdminToolsPage/AdminToolsAttendeeDesignReviewInfo.tsx index 52c917172b..166c75823e 100644 --- a/src/frontend/src/pages/AdminToolsPage/AdminToolsAttendeeDesignReviewInfo.tsx +++ b/src/frontend/src/pages/AdminToolsPage/AdminToolsAttendeeDesignReviewInfo.tsx @@ -6,13 +6,13 @@ import LoadingIndicator from '../../components/LoadingIndicator'; import ErrorPage from '../ErrorPage'; import { fullNamePipe } from '../../utils/pipes'; import { useAllUsers } from '../../hooks/users.hooks'; -import { useAllDesignReviews } from '../../hooks/design-reviews.hooks'; +import { useAllDesignReviews } from '../../hooks/design-reviews.hooks' import { DesignReviewStatus } from 'shared'; const AdminToolsAttendeeDesignReviewInfo: React.FC = () => { const [searchQuery, setSearchQuery] = useState(''); - const { data: allTeams, isLoading: teamsIsLoading, isError: teamsIsError, error: teamsError } = useAllTeams(); + const { data: allTeams, isLoading: teamsIsLoading, isError: teamsIsError, error: teamsError } = useAllTeams(false); const { data: allUsers, isLoading: usersIsLoading, isError: usersIsError, error: usersError } = useAllUsers(); const { data: allDesignReviews, diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamsTools.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamsTools.tsx index bc39202d7d..38e824ab9a 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamsTools.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamsTools.tsx @@ -11,7 +11,7 @@ import CreateTeamForm from './CreateTeamForm'; import TeamTypeTable from './TeamTypeTable'; const TeamsTools = () => { - const { data: allTeams, isLoading: allTeamsIsLoading, isError: allTeamsIsError, error: allTeamsError } = useAllTeams(); + const { data: allTeams, isLoading: allTeamsIsLoading, isError: allTeamsIsError, error: allTeamsError } = useAllTeams(false); if (!allTeams || allTeamsIsLoading) return ; diff --git a/src/frontend/src/pages/GanttPage/GanttChartPage.tsx b/src/frontend/src/pages/GanttPage/GanttChartPage.tsx index 8896654ef3..94587a7e26 100644 --- a/src/frontend/src/pages/GanttPage/GanttChartPage.tsx +++ b/src/frontend/src/pages/GanttPage/GanttChartPage.tsx @@ -40,7 +40,7 @@ const GanttChartPage: FC = () => { } = useAllTeamTypes(); const { isLoading: carsIsLoading, isError: carsIsError, data: cars, error: carsError } = useGetAllCars(); - const { isLoading: teamsIsLoading, isError: teamsIsError, data: teams, error: teamsError } = useAllTeams(); + const { isLoading: teamsIsLoading, isError: teamsIsError, data: teams, error: teamsError } = useAllTeams(false); const [searchText, setSearchText] = useState(''); const [showWorkPackagesMap, setShowWorkPackagesMap] = useState>(new Map()); const [addedProjects, setAddedProjects] = useState([]); diff --git a/src/frontend/src/pages/SettingsPage/SettingsDetails.tsx b/src/frontend/src/pages/SettingsPage/SettingsDetails.tsx index 7f1d28e849..6eaefcc7a9 100644 --- a/src/frontend/src/pages/SettingsPage/SettingsDetails.tsx +++ b/src/frontend/src/pages/SettingsPage/SettingsDetails.tsx @@ -22,7 +22,7 @@ const SettingsDetails: React.FC = () => { error: secureSettingsError, data: userSecureSettings } = useCurrentUserSecureSettings(); - const { isLoading: allTeamsIsLoading, isError: allTeamsIsError, data: teams, error: allTeamsError } = useAllTeams(); + const { isLoading: allTeamsIsLoading, isError: allTeamsIsError, data: teams, error: allTeamsError } = useAllTeams(false); if (secureSettingsIsError) return ; if (settingsIsError) return ; diff --git a/src/frontend/src/pages/SettingsPage/SettingsPreferences.tsx b/src/frontend/src/pages/SettingsPage/SettingsPreferences.tsx index 4dd9b70eaa..b2ad113fa8 100644 --- a/src/frontend/src/pages/SettingsPage/SettingsPreferences.tsx +++ b/src/frontend/src/pages/SettingsPage/SettingsPreferences.tsx @@ -75,7 +75,7 @@ const SettingsPreferences: React.FC = () => { error: secureSettingsError, data: userSecureSettings } = useCurrentUserSecureSettings(); - const { isLoading: allTeamsIsLoading, isError: allTeamsIsError, data: teams, error: allTeamsError } = useAllTeams(); + const { isLoading: allTeamsIsLoading, isError: allTeamsIsError, data: teams, error: allTeamsError } = useAllTeams(false); if (secureSettingsIsError) return ; if (settingsIsError) return ; diff --git a/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx b/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx index e3f660fd18..efc0f06995 100644 --- a/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx +++ b/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx @@ -63,7 +63,7 @@ const TeamSpecificPage: React.FC = () => { interface ArchiveTeamButtonProps { archive: boolean; } - + const ArchiveTeamButton: React.FC = ({ archive }) => ( {archive ? 'Archive Team' : 'Unarchive Team'} diff --git a/src/frontend/src/pages/TeamsPage/TeamsPage.tsx b/src/frontend/src/pages/TeamsPage/TeamsPage.tsx index 1ab2f355c0..d5e6f4aba5 100644 --- a/src/frontend/src/pages/TeamsPage/TeamsPage.tsx +++ b/src/frontend/src/pages/TeamsPage/TeamsPage.tsx @@ -9,13 +9,17 @@ import { useAllTeams } from '../../hooks/teams.hooks'; import ErrorPage from '../ErrorPage'; import TeamSummary from './TeamSummary'; import PageLayout from '../../components/PageLayout'; -import { FlashOffRounded } from '@mui/icons-material'; const TeamsPage: React.FC = () => { const { isLoading: teamsLoading, isError: isTeamsError, data: teams, error: teamsError } = useAllTeams(false); - const { isLoading: archivedTeamsLoading, isError: isArchivedTeamsError, data: archivedTeams, error: archivedTeamsError } = useAllTeams(true); - console.log(archivedTeams) + const { + isLoading: archivedTeamsLoading, + isError: isArchivedTeamsError, + data: archivedTeams, + error: archivedTeamsError + } = useAllTeams(true); + console.log(archivedTeams); if (teamsLoading || !teams) return ; if (archivedTeamsLoading || !archivedTeams) return ; @@ -25,25 +29,25 @@ const TeamsPage: React.FC = () => { return ( <> - - - {teams.map((team) => ( - - - - ))} - - - - - {archivedTeams.map((archivedTeam) => ( - - + + + {teams.map((team) => ( + + + + ))} - ))} - - - + + + + {archivedTeams.map((archivedTeam) => ( + + + + ))} + + + ); }; From e75c1e72d4f9325f823ca5e7f00193deade131a9 Mon Sep 17 00:00:00 2001 From: harish Date: Sun, 7 Jul 2024 13:20:03 -0400 Subject: [PATCH 007/264] #1606 prettier --- .../AdminToolsPage/AdminToolsAttendeeDesignReviewInfo.tsx | 2 +- .../src/pages/AdminToolsPage/TeamConfig/TeamsTools.tsx | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/pages/AdminToolsPage/AdminToolsAttendeeDesignReviewInfo.tsx b/src/frontend/src/pages/AdminToolsPage/AdminToolsAttendeeDesignReviewInfo.tsx index 166c75823e..a5d77fbeaa 100644 --- a/src/frontend/src/pages/AdminToolsPage/AdminToolsAttendeeDesignReviewInfo.tsx +++ b/src/frontend/src/pages/AdminToolsPage/AdminToolsAttendeeDesignReviewInfo.tsx @@ -6,7 +6,7 @@ import LoadingIndicator from '../../components/LoadingIndicator'; import ErrorPage from '../ErrorPage'; import { fullNamePipe } from '../../utils/pipes'; import { useAllUsers } from '../../hooks/users.hooks'; -import { useAllDesignReviews } from '../../hooks/design-reviews.hooks' +import { useAllDesignReviews } from '../../hooks/design-reviews.hooks'; import { DesignReviewStatus } from 'shared'; const AdminToolsAttendeeDesignReviewInfo: React.FC = () => { diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamsTools.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamsTools.tsx index 38e824ab9a..fa4cfae18a 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamsTools.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamsTools.tsx @@ -11,7 +11,12 @@ import CreateTeamForm from './CreateTeamForm'; import TeamTypeTable from './TeamTypeTable'; const TeamsTools = () => { - const { data: allTeams, isLoading: allTeamsIsLoading, isError: allTeamsIsError, error: allTeamsError } = useAllTeams(false); + const { + data: allTeams, + isLoading: allTeamsIsLoading, + isError: allTeamsIsError, + error: allTeamsError + } = useAllTeams(false); if (!allTeams || allTeamsIsLoading) return ; From c3fb7c08446946f64f3d200950f239e8651fb554 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Tue, 23 Jul 2024 15:31:13 -0400 Subject: [PATCH 008/264] #2708 added description field to team type and cerated edit team type description field --- .../src/controllers/teams.controllers.ts | 18 ++++++++- .../migration.sql | 6 +++ src/backend/src/prisma/schema.prisma | 2 + src/backend/src/prisma/seed.ts | 6 +-- src/backend/src/routes/teams.routes.ts | 8 ++++ src/backend/src/services/teams.services.ts | 38 ++++++++++++++++++- src/backend/tests/test-utils.ts | 2 +- src/backend/tests/unmocked/team-type.test.ts | 10 +++-- 8 files changed, 80 insertions(+), 10 deletions(-) create mode 100644 src/backend/src/prisma/migrations/20240723190716_added_description_and_image_to_team_type/migration.sql diff --git a/src/backend/src/controllers/teams.controllers.ts b/src/backend/src/controllers/teams.controllers.ts index dae0d754ee..34ddc2d58e 100644 --- a/src/backend/src/controllers/teams.controllers.ts +++ b/src/backend/src/controllers/teams.controllers.ts @@ -134,11 +134,11 @@ export default class TeamsController { static async createTeamType(req: Request, res: Response, next: NextFunction) { try { - const { name, iconName } = req.body; + const { name, iconName, description } = req.body; const submitter = await getCurrentUser(res); const organizationId = getOrganizationId(req.headers); - const createdTeamType = await TeamsService.createTeamType(submitter, name, iconName, organizationId); + const createdTeamType = await TeamsService.createTeamType(submitter, name, iconName, description, organizationId); res.status(200).json(createdTeamType); } catch (error: unknown) { next(error); @@ -169,6 +169,20 @@ export default class TeamsController { } } + static async editTeamTypeDescription(req: Request, res: Response, next: NextFunction) { + try { + const { newDescription } = req.body; + const user = await getCurrentUser(res); + const organizationId = getOrganizationId(req.headers); + + const teamType = await TeamsService.editTeamTypeDescription(user, req.params.teamTypeId, newDescription, organizationId); + res.status(200).json(teamType); + } catch (error: unknown) { + next(error); + } + } + + static async setTeamType(req: Request, res: Response, next: NextFunction) { try { const { teamTypeId } = req.body; diff --git a/src/backend/src/prisma/migrations/20240723190716_added_description_and_image_to_team_type/migration.sql b/src/backend/src/prisma/migrations/20240723190716_added_description_and_image_to_team_type/migration.sql new file mode 100644 index 0000000000..5c2dc93d98 --- /dev/null +++ b/src/backend/src/prisma/migrations/20240723190716_added_description_and_image_to_team_type/migration.sql @@ -0,0 +1,6 @@ +ALTER TABLE "Team_Type" +ADD COLUMN "description" TEXT NOT NULL DEFAULT 'Default description', +ADD COLUMN "image" TEXT; + +ALTER TABLE "Team_Type" +ALTER COLUMN "description" DROP DEFAULT; diff --git a/src/backend/src/prisma/schema.prisma b/src/backend/src/prisma/schema.prisma index 2b4303d7c6..01814a66c0 100644 --- a/src/backend/src/prisma/schema.prisma +++ b/src/backend/src/prisma/schema.prisma @@ -701,6 +701,8 @@ model Team_Type { iconName String designReviews Design_Review[] team Team[] + description String + image String? organizationId String organization Organization @relation(fields: [organizationId], references: [organizationId]) diff --git a/src/backend/src/prisma/seed.ts b/src/backend/src/prisma/seed.ts index 47664150ab..cbd46a343f 100644 --- a/src/backend/src/prisma/seed.ts +++ b/src/backend/src/prisma/seed.ts @@ -241,9 +241,9 @@ const performSeed: () => Promise = async () => { * TEAMS */ /** Creating Team Types */ - const teamType1 = await TeamsService.createTeamType(batman, 'Mechanical', 'YouTubeIcon', organizationId); - const teamType2 = await TeamsService.createTeamType(thomasEmrax, 'Software', 'InstagramIcon', organizationId); - const teamType3 = await TeamsService.createTeamType(cyborg, 'Electrical', 'SettingsIcon', organizationId); + const teamType1 = await TeamsService.createTeamType(batman, 'Mechanical', 'YouTubeIcon', 'Mechanical team', organizationId); + const teamType2 = await TeamsService.createTeamType(thomasEmrax, 'Software', 'InstagramIcon', 'Software team', organizationId); + const teamType3 = await TeamsService.createTeamType(cyborg, 'Electrical', 'SettingsIcon', 'Electrical team', organizationId); /** Creating Teams */ const justiceLeague: Team = await prisma.team.create(dbSeedAllTeams.justiceLeague(batman.userId, organizationId)); diff --git a/src/backend/src/routes/teams.routes.ts b/src/backend/src/routes/teams.routes.ts index bf3f237c66..38c348599f 100644 --- a/src/backend/src/routes/teams.routes.ts +++ b/src/backend/src/routes/teams.routes.ts @@ -51,9 +51,17 @@ teamsRouter.post( '/teamType/create', nonEmptyString(body('name')), nonEmptyString(body('iconName')), + nonEmptyString(body('description')), validateInputs, TeamsController.createTeamType ); +teamsRouter.post( + '/teamType/:teamTypeId/edit-description', + body('newDescription').isString(), + validateInputs, + TeamsController.editTeamTypeDescription +); + teamsRouter.post('/:teamId/set-team-type', nonEmptyString(body('teamTypeId')), validateInputs, TeamsController.setTeamType); export default teamsRouter; diff --git a/src/backend/src/services/teams.services.ts b/src/backend/src/services/teams.services.ts index 990dee1baf..01315a10e8 100644 --- a/src/backend/src/services/teams.services.ts +++ b/src/backend/src/services/teams.services.ts @@ -366,7 +366,13 @@ export default class TeamsService { * @param organizationId The organization the user is currently in * @returns the created team */ - static async createTeamType(submitter: User, name: string, iconName: string, organizationId: string): Promise { + static async createTeamType( + submitter: User, + name: string, + iconName: string, + description: string, + organizationId: string + ): Promise { if (!(await userHasPermission(submitter.userId, organizationId, isAdmin))) { throw new AccessDeniedAdminOnlyException('create a team type'); } @@ -383,6 +389,7 @@ export default class TeamsService { data: { name, iconName, + description, organizationId } }); @@ -418,6 +425,35 @@ export default class TeamsService { return teamTypes; } + /** + * Changes the description of the given teamType to be the new description + * @param user The user who is editing the description + * @param teamTypeId The id for the teamType that is being edited + * @param newDescription the new description for the team + * @param organizationId The organization the user is currently in + * @returns The team with the new description + */ + static async editTeamTypeDescription( + user: User, + teamTypeId: string, + newDescription: string, + organizationId: string + ): Promise { + if (!isUnderWordCount(newDescription, 300)) throw new HttpException(400, 'Description must be less than 300 words'); + + if (!(await userHasPermission(user.userId, organizationId, isAdmin))) + throw new AccessDeniedException('you must be an admin to edit the team types description'); + + const updatedTeamType = await prisma.team_Type.update({ + where: { teamTypeId }, + data: { + description: newDescription + } + }); + + return updatedTeamType; + } + /** * Sets the teamType for a team * @param submitter the user who is setting the team type diff --git a/src/backend/tests/test-utils.ts b/src/backend/tests/test-utils.ts index d35ef9548b..31c1a2ce0a 100644 --- a/src/backend/tests/test-utils.ts +++ b/src/backend/tests/test-utils.ts @@ -328,7 +328,7 @@ export const createTestDesignReview = async () => { if (!head) throw new Error('Failed to find user'); if (!lead) throw new Error('Failed to find user'); await createTestProject(head, organization.organizationId); - const teamType = await TeamsService.createTeamType(head, 'Team1', 'Software', organization.organizationId); + const teamType = await TeamsService.createTeamType(head, 'Team1', 'Software', 'Software team', organization.organizationId); const { designReviewId } = await DesignReviewsService.createDesignReview( lead, '03/25/2027', diff --git a/src/backend/tests/unmocked/team-type.test.ts b/src/backend/tests/unmocked/team-type.test.ts index 4f4c63bc9b..fde59da851 100644 --- a/src/backend/tests/unmocked/team-type.test.ts +++ b/src/backend/tests/unmocked/team-type.test.ts @@ -17,15 +17,15 @@ describe('Team Type Tests', () => { it('Create team type fails if user is not an admin', async () => { await expect( async () => - await TeamsService.createTeamType(await createTestUser(wonderwomanGuest, orgId), 'Team 2', 'Warning icon', orgId) + await TeamsService.createTeamType(await createTestUser(wonderwomanGuest, orgId), 'Team 2', 'Warning icon', '', orgId) ).rejects.toThrow(new AccessDeniedAdminOnlyException('create a team type')); }); it('Create team type fails if there is already another team type with the same name', async () => { - await TeamsService.createTeamType(await createTestUser(supermanAdmin, orgId), 'teamType1', 'YouTubeIcon', orgId); + await TeamsService.createTeamType(await createTestUser(supermanAdmin, orgId), 'teamType1', 'YouTubeIcon', '', orgId); await expect( async () => - await TeamsService.createTeamType(await createTestUser(batmanAppAdmin, orgId), 'teamType1', 'Warning icon', orgId) + await TeamsService.createTeamType(await createTestUser(batmanAppAdmin, orgId), 'teamType1', 'Warning icon', '', orgId) ).rejects.toThrow(new HttpException(400, 'Cannot create a teamType with a name that already exists')); }); @@ -34,6 +34,7 @@ describe('Team Type Tests', () => { await createTestUser(supermanAdmin, orgId), 'teamType3', 'YouTubeIcon', + '', orgId ); @@ -52,12 +53,14 @@ describe('Team Type Tests', () => { await createTestUser(supermanAdmin, orgId), 'teamType1', 'YouTubeIcon', + '', orgId ); const teamType2 = await TeamsService.createTeamType( await createTestUser(batmanAppAdmin, orgId), 'teamType2', 'WarningIcon', + '', orgId ); const result = await TeamsService.getAllTeamTypes(orgId); @@ -71,6 +74,7 @@ describe('Team Type Tests', () => { await createTestUser(supermanAdmin, orgId), 'teamType1', 'YouTubeIcon', + '', orgId ); const result = await TeamsService.getSingleTeamType(teamType1.teamTypeId, orgId); From 0a84e2a727111e402d2d9fa7ba5f43174c44a5ae Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Tue, 23 Jul 2024 15:38:55 -0400 Subject: [PATCH 009/264] #2708 prettier and frontend test --- src/backend/src/controllers/teams.controllers.ts | 8 ++++++-- src/backend/tests/unmocked/team-type.test.ts | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/backend/src/controllers/teams.controllers.ts b/src/backend/src/controllers/teams.controllers.ts index 34ddc2d58e..ebdc286794 100644 --- a/src/backend/src/controllers/teams.controllers.ts +++ b/src/backend/src/controllers/teams.controllers.ts @@ -175,14 +175,18 @@ export default class TeamsController { const user = await getCurrentUser(res); const organizationId = getOrganizationId(req.headers); - const teamType = await TeamsService.editTeamTypeDescription(user, req.params.teamTypeId, newDescription, organizationId); + const teamType = await TeamsService.editTeamTypeDescription( + user, + req.params.teamTypeId, + newDescription, + organizationId + ); res.status(200).json(teamType); } catch (error: unknown) { next(error); } } - static async setTeamType(req: Request, res: Response, next: NextFunction) { try { const { teamTypeId } = req.body; diff --git a/src/backend/tests/unmocked/team-type.test.ts b/src/backend/tests/unmocked/team-type.test.ts index fde59da851..5e74eabb95 100644 --- a/src/backend/tests/unmocked/team-type.test.ts +++ b/src/backend/tests/unmocked/team-type.test.ts @@ -41,6 +41,7 @@ describe('Team Type Tests', () => { expect(result).toEqual({ name: 'teamType3', iconName: 'YouTubeIcon', + description: '', organizationId: orgId, teamTypeId: result.teamTypeId }); From 00d2ee4f9e1bfa80b79a6a5a33dcb1bda2f1d6b7 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Tue, 23 Jul 2024 15:52:21 -0400 Subject: [PATCH 010/264] #2708 added unit tests --- src/backend/tests/unmocked/team-type.test.ts | 67 +++++++++++++++++++- src/shared/src/types/design-review-types.ts | 2 + 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/backend/tests/unmocked/team-type.test.ts b/src/backend/tests/unmocked/team-type.test.ts index 5e74eabb95..446cab6134 100644 --- a/src/backend/tests/unmocked/team-type.test.ts +++ b/src/backend/tests/unmocked/team-type.test.ts @@ -1,5 +1,10 @@ import TeamsService from '../../src/services/teams.services'; -import { AccessDeniedAdminOnlyException, HttpException, NotFoundException } from '../../src/utils/errors.utils'; +import { + AccessDeniedAdminOnlyException, + AccessDeniedException, + HttpException, + NotFoundException +} from '../../src/utils/errors.utils'; import { batmanAppAdmin, supermanAdmin, wonderwomanGuest } from '../test-data/users.test-data'; import { createTestOrganization, createTestUser, resetUsers } from '../test-utils'; @@ -17,7 +22,13 @@ describe('Team Type Tests', () => { it('Create team type fails if user is not an admin', async () => { await expect( async () => - await TeamsService.createTeamType(await createTestUser(wonderwomanGuest, orgId), 'Team 2', 'Warning icon', '', orgId) + await TeamsService.createTeamType( + await createTestUser(wonderwomanGuest, orgId), + 'Team 2', + 'Warning icon', + '', + orgId + ) ).rejects.toThrow(new AccessDeniedAdminOnlyException('create a team type')); }); @@ -25,7 +36,13 @@ describe('Team Type Tests', () => { await TeamsService.createTeamType(await createTestUser(supermanAdmin, orgId), 'teamType1', 'YouTubeIcon', '', orgId); await expect( async () => - await TeamsService.createTeamType(await createTestUser(batmanAppAdmin, orgId), 'teamType1', 'Warning icon', '', orgId) + await TeamsService.createTeamType( + await createTestUser(batmanAppAdmin, orgId), + 'teamType1', + 'Warning icon', + '', + orgId + ) ).rejects.toThrow(new HttpException(400, 'Cannot create a teamType with a name that already exists')); }); @@ -41,6 +58,7 @@ describe('Team Type Tests', () => { expect(result).toEqual({ name: 'teamType3', iconName: 'YouTubeIcon', + image: null, description: '', organizationId: orgId, teamTypeId: result.teamTypeId @@ -89,4 +107,47 @@ describe('Team Type Tests', () => { ); }); }); + + describe('Edit team type description', () => { + it('fails if user is not an admin', async () => { + await expect( + async () => + await TeamsService.editTeamTypeDescription( + await createTestUser(wonderwomanGuest, orgId), + 'id', + 'new description', + orgId + ) + ).rejects.toThrow(new AccessDeniedException('you must be an admin to edit the team types description')); + }); + + it('fails if the new description is over 300 workds', async () => { + await expect( + async () => + await TeamsService.editTeamTypeDescription( + await createTestUser(supermanAdmin, orgId), + 'id', + 'a '.repeat(301), + orgId + ) + ).rejects.toThrow(new HttpException(400, 'Description must be less than 300 words')); + }); + + it('succeds and updates the description', async () => { + const teamType = await TeamsService.createTeamType( + await createTestUser(supermanAdmin, orgId), + 'teamType1', + 'YouTubeIcon', + '', + orgId + ); + const updatedTeamType = await TeamsService.editTeamTypeDescription( + await createTestUser(batmanAppAdmin, orgId), + teamType.teamTypeId, + 'new description', + orgId + ); + expect(updatedTeamType.description).toBe('new description'); + }); + }); }); diff --git a/src/shared/src/types/design-review-types.ts b/src/shared/src/types/design-review-types.ts index dc44b7167d..e0588f4c27 100644 --- a/src/shared/src/types/design-review-types.ts +++ b/src/shared/src/types/design-review-types.ts @@ -36,4 +36,6 @@ export interface TeamType { teamTypeId: string; name: string; iconName: string; + description: string; + image?: string | null; } From 9b423df1e94ef5a25a75db65b0c4a840d51e2c15 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Tue, 23 Jul 2024 15:54:50 -0400 Subject: [PATCH 011/264] #2708 prettier --- src/backend/src/prisma/seed.ts | 24 +++++++++++++++++++++--- src/backend/tests/test-utils.ts | 8 +++++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/backend/src/prisma/seed.ts b/src/backend/src/prisma/seed.ts index cbd46a343f..0ab20b8d27 100644 --- a/src/backend/src/prisma/seed.ts +++ b/src/backend/src/prisma/seed.ts @@ -241,9 +241,27 @@ const performSeed: () => Promise = async () => { * TEAMS */ /** Creating Team Types */ - const teamType1 = await TeamsService.createTeamType(batman, 'Mechanical', 'YouTubeIcon', 'Mechanical team', organizationId); - const teamType2 = await TeamsService.createTeamType(thomasEmrax, 'Software', 'InstagramIcon', 'Software team', organizationId); - const teamType3 = await TeamsService.createTeamType(cyborg, 'Electrical', 'SettingsIcon', 'Electrical team', organizationId); + const teamType1 = await TeamsService.createTeamType( + batman, + 'Mechanical', + 'YouTubeIcon', + 'Mechanical team', + organizationId + ); + const teamType2 = await TeamsService.createTeamType( + thomasEmrax, + 'Software', + 'InstagramIcon', + 'Software team', + organizationId + ); + const teamType3 = await TeamsService.createTeamType( + cyborg, + 'Electrical', + 'SettingsIcon', + 'Electrical team', + organizationId + ); /** Creating Teams */ const justiceLeague: Team = await prisma.team.create(dbSeedAllTeams.justiceLeague(batman.userId, organizationId)); diff --git a/src/backend/tests/test-utils.ts b/src/backend/tests/test-utils.ts index 31c1a2ce0a..a5afc100fb 100644 --- a/src/backend/tests/test-utils.ts +++ b/src/backend/tests/test-utils.ts @@ -328,7 +328,13 @@ export const createTestDesignReview = async () => { if (!head) throw new Error('Failed to find user'); if (!lead) throw new Error('Failed to find user'); await createTestProject(head, organization.organizationId); - const teamType = await TeamsService.createTeamType(head, 'Team1', 'Software', 'Software team', organization.organizationId); + const teamType = await TeamsService.createTeamType( + head, + 'Team1', + 'Software', + 'Software team', + organization.organizationId + ); const { designReviewId } = await DesignReviewsService.createDesignReview( lead, '03/25/2027', From a587bc7857c383459ebcd71286778755161f8991 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Tue, 23 Jul 2024 15:56:04 -0400 Subject: [PATCH 012/264] #2708 typescript check --- .../src/tests/test-support/test-data/design-reviews.stub.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/frontend/src/tests/test-support/test-data/design-reviews.stub.ts b/src/frontend/src/tests/test-support/test-data/design-reviews.stub.ts index 8881a9f6e3..4fbd5f05a3 100644 --- a/src/frontend/src/tests/test-support/test-data/design-reviews.stub.ts +++ b/src/frontend/src/tests/test-support/test-data/design-reviews.stub.ts @@ -10,6 +10,7 @@ import { exampleWbsProject1 } from './wbs-numbers.stub'; export const teamType1: TeamType = { teamTypeId: '1', iconName: 'YouTubeIcon', + description: '', name: 'teamType1' }; From a1955c8bd775c33ed06d9fcb273e84d7af4ddc7f Mon Sep 17 00:00:00 2001 From: harish Date: Wed, 24 Jul 2024 13:31:16 -0400 Subject: [PATCH 013/264] #2708 edit image endpoint for teamType and tests --- .../src/controllers/teams.controllers.ts | 15 ++++ src/backend/src/routes/teams.routes.ts | 6 ++ src/backend/src/services/teams.services.ts | 25 ++++++ src/backend/tests/unmocked/team-type.test.ts | 76 +++++++++++++++++++ 4 files changed, 122 insertions(+) diff --git a/src/backend/src/controllers/teams.controllers.ts b/src/backend/src/controllers/teams.controllers.ts index ebdc286794..1137492e3d 100644 --- a/src/backend/src/controllers/teams.controllers.ts +++ b/src/backend/src/controllers/teams.controllers.ts @@ -28,6 +28,21 @@ export default class TeamsController { } } + static async setImage(req: Request, res: Response, next: NextFunction) { + try { + const file = req.file as Express.Multer.File + const submitter = await getCurrentUser(res); + const organizationId = getOrganizationId(req.headers); + const { teamTypeId } = req.params + + const newImages = await TeamsService.setImage(file, submitter, organizationId, teamTypeId); + + res.status(200).json(newImages); + } catch (error: unknown) { + next(error); + } + } + static async setTeamMembers(req: Request, res: Response, next: NextFunction) { try { const { userIds } = req.body; diff --git a/src/backend/src/routes/teams.routes.ts b/src/backend/src/routes/teams.routes.ts index 38c348599f..6a5fabffc8 100644 --- a/src/backend/src/routes/teams.routes.ts +++ b/src/backend/src/routes/teams.routes.ts @@ -2,8 +2,10 @@ import express from 'express'; import TeamsController from '../controllers/teams.controllers'; import { body } from 'express-validator'; import { nonEmptyString, validateInputs } from '../utils/validation.utils'; +import multer, { memoryStorage } from 'multer'; const teamsRouter = express.Router(); +const upload = multer({ limits: { fileSize: 30000000 }, storage: memoryStorage() }); teamsRouter.get('/', TeamsController.getAllTeams); teamsRouter.get('/:teamId', TeamsController.getSingleTeam); @@ -27,6 +29,10 @@ teamsRouter.post( validateInputs, TeamsController.editDescription ); + +teamsRouter.post('/teamType/:teamTypeId/edit-image', upload.single('image'), TeamsController.setImage); + + teamsRouter.post('/:teamId/set-head', nonEmptyString(body('userId')), validateInputs, TeamsController.setTeamHead); teamsRouter.post('/:teamId/delete', TeamsController.deleteTeam); teamsRouter.post( diff --git a/src/backend/src/services/teams.services.ts b/src/backend/src/services/teams.services.ts index 01315a10e8..807b09b3a6 100644 --- a/src/backend/src/services/teams.services.ts +++ b/src/backend/src/services/teams.services.ts @@ -13,6 +13,7 @@ import { getPrismaQueryUserIds, getUsers, userHasPermission } from '../utils/use import { isUnderWordCount } from 'shared'; import { removeUsersFromList } from '../utils/teams.utils'; import { getTeamQueryArgs } from '../prisma-query-args/teams.query-args'; +import { uploadFile } from '../utils/google-integration.utils'; export default class TeamsService { /** @@ -28,6 +29,30 @@ export default class TeamsService { return teams.map(teamTransformer); } + /** + * Sets team type image + * @param organizationId The organization the user is currently in + * @param teamTypeId The team type that is being updated + * @param image The image that is being placed in the team type + * @param submitter The user that is making the change + * @returns the updated team type + */ + static async setImage(image: Express.Multer.File, submitter: User, organizationId: string, teamTypeId: string) { + if (!(await userHasPermission(submitter.userId, organizationId, isAdmin))) + throw new AccessDeniedAdminOnlyException('update images'); + + const imageData = await uploadFile(image); + + const newTeamType = await prisma.team_Type.update({ + where: { teamTypeId }, + data: { + image: imageData.id + } + }); + + return newTeamType; + } + /** * Gets a team with the given id * @param teamId - id of team to retrieve diff --git a/src/backend/tests/unmocked/team-type.test.ts b/src/backend/tests/unmocked/team-type.test.ts index 446cab6134..6329087107 100644 --- a/src/backend/tests/unmocked/team-type.test.ts +++ b/src/backend/tests/unmocked/team-type.test.ts @@ -1,3 +1,4 @@ +import prisma from '../../src/prisma/prisma'; import TeamsService from '../../src/services/teams.services'; import { AccessDeniedAdminOnlyException, @@ -5,8 +6,14 @@ import { HttpException, NotFoundException } from '../../src/utils/errors.utils'; +import { uploadFile } from '../../src/utils/google-integration.utils'; import { batmanAppAdmin, supermanAdmin, wonderwomanGuest } from '../test-data/users.test-data'; import { createTestOrganization, createTestUser, resetUsers } from '../test-utils'; +import { Mock, vi } from 'vitest'; + +vi.mock('../../src/utils/google-integration.utils', () => ({ + uploadFile: vi.fn() +})); describe('Team Type Tests', () => { let orgId: string; @@ -18,6 +25,75 @@ describe('Team Type Tests', () => { await resetUsers(); }); + describe('Set Image', () => { + const TEST_FILE = { originalname: 'image1.png' } as Express.Multer.File; + + it('Fails if user is not an admin', async () => { + const teamType = await TeamsService.createTeamType( + await createTestUser(supermanAdmin, orgId), + 'teamType1', + 'YouTubeIcon', + '', + orgId + ); + + await expect( + TeamsService.setImage(TEST_FILE, await createTestUser(wonderwomanGuest, orgId), orgId, teamType.teamTypeId) + ).rejects.toThrow(new AccessDeniedAdminOnlyException('update images')); + }); + + it('Fails if an organization does not exist', async () => { + const teamType = await TeamsService.createTeamType( + await createTestUser(supermanAdmin, orgId), + 'teamType1', + 'YouTubeIcon', + '', + orgId + ); + + await expect( + TeamsService.setImage(TEST_FILE, await createTestUser(batmanAppAdmin, orgId), '1', teamType.teamTypeId) + ).rejects.toThrow(new HttpException(400, `Organization with id: 1 not found!`)); + }); + + it('Succeeds and updates all the images', async () => { + const testBatman = await createTestUser(batmanAppAdmin, orgId); + const OTHER_FILE = { originalname: 'image2.png' } as Express.Multer.File; + const teamType = await TeamsService.createTeamType( + await createTestUser(supermanAdmin, orgId), + 'teamType1', + 'YouTubeIcon', + '', + orgId + ); + + (uploadFile as Mock).mockImplementation((file) => { + return Promise.resolve({ id: `uploaded-${file.originalname}` }); + }); + + await TeamsService.setImage(TEST_FILE, testBatman, orgId, teamType.teamTypeId); + + const organization = await prisma.team_Type.findUnique({ + where: { + teamTypeId: teamType.teamTypeId + } + }); + + expect(organization).not.toBeNull(); + expect(organization?.image).toBe('uploaded-image1.png'); + + await TeamsService.setImage(OTHER_FILE, testBatman, orgId, teamType.teamTypeId); + + const updatedTeamType = await prisma.team_Type.findUnique({ + where: { + teamTypeId: teamType.teamTypeId + } + }); + + expect(updatedTeamType?.image).toBe('uploaded-image2.png'); + }); + }); + describe('Create Team Type', () => { it('Create team type fails if user is not an admin', async () => { await expect( From 4c9368c4e3f63173ae6fc05571d5b94b0146c946 Mon Sep 17 00:00:00 2001 From: harish Date: Wed, 24 Jul 2024 13:34:54 -0400 Subject: [PATCH 014/264] #2708 prettier --- src/backend/src/controllers/teams.controllers.ts | 4 ++-- src/backend/src/routes/teams.routes.ts | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/backend/src/controllers/teams.controllers.ts b/src/backend/src/controllers/teams.controllers.ts index 1137492e3d..344b6da9c7 100644 --- a/src/backend/src/controllers/teams.controllers.ts +++ b/src/backend/src/controllers/teams.controllers.ts @@ -30,10 +30,10 @@ export default class TeamsController { static async setImage(req: Request, res: Response, next: NextFunction) { try { - const file = req.file as Express.Multer.File + const file = req.file as Express.Multer.File; const submitter = await getCurrentUser(res); const organizationId = getOrganizationId(req.headers); - const { teamTypeId } = req.params + const { teamTypeId } = req.params; const newImages = await TeamsService.setImage(file, submitter, organizationId, teamTypeId); diff --git a/src/backend/src/routes/teams.routes.ts b/src/backend/src/routes/teams.routes.ts index 6a5fabffc8..39d83bf8b8 100644 --- a/src/backend/src/routes/teams.routes.ts +++ b/src/backend/src/routes/teams.routes.ts @@ -32,7 +32,6 @@ teamsRouter.post( teamsRouter.post('/teamType/:teamTypeId/edit-image', upload.single('image'), TeamsController.setImage); - teamsRouter.post('/:teamId/set-head', nonEmptyString(body('userId')), validateInputs, TeamsController.setTeamHead); teamsRouter.post('/:teamId/delete', TeamsController.deleteTeam); teamsRouter.post( From 5871db9321bd0d31850e79d77acd34756b28919b Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Thu, 25 Jul 2024 13:30:07 -0400 Subject: [PATCH 015/264] #2708 added description to admin tools page --- .../migration.sql | 2 +- src/backend/src/prisma/schema.prisma | 2 +- src/frontend/src/hooks/design-reviews.hooks.ts | 1 + .../TeamConfig/CreateTeamTypeFormModal.tsx | 15 ++++++++++++--- .../AdminToolsPage/TeamConfig/TeamTypeTable.tsx | 9 ++++++++- 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/backend/src/prisma/migrations/20240723190716_added_description_and_image_to_team_type/migration.sql b/src/backend/src/prisma/migrations/20240723190716_added_description_and_image_to_team_type/migration.sql index 5c2dc93d98..2118cefe95 100644 --- a/src/backend/src/prisma/migrations/20240723190716_added_description_and_image_to_team_type/migration.sql +++ b/src/backend/src/prisma/migrations/20240723190716_added_description_and_image_to_team_type/migration.sql @@ -1,6 +1,6 @@ ALTER TABLE "Team_Type" ADD COLUMN "description" TEXT NOT NULL DEFAULT 'Default description', -ADD COLUMN "image" TEXT; +ADD COLUMN "imageFileId" TEXT; ALTER TABLE "Team_Type" ALTER COLUMN "description" DROP DEFAULT; diff --git a/src/backend/src/prisma/schema.prisma b/src/backend/src/prisma/schema.prisma index 01814a66c0..fae90c9901 100644 --- a/src/backend/src/prisma/schema.prisma +++ b/src/backend/src/prisma/schema.prisma @@ -702,7 +702,7 @@ model Team_Type { designReviews Design_Review[] team Team[] description String - image String? + imageFileId String? organizationId String organization Organization @relation(fields: [organizationId], references: [organizationId]) diff --git a/src/frontend/src/hooks/design-reviews.hooks.ts b/src/frontend/src/hooks/design-reviews.hooks.ts index 2a9641035a..1819a20b35 100644 --- a/src/frontend/src/hooks/design-reviews.hooks.ts +++ b/src/frontend/src/hooks/design-reviews.hooks.ts @@ -28,6 +28,7 @@ export interface CreateDesignReviewsPayload { export interface CreateTeamTypePayload { name: string; iconName: string; + description: string; } export const useCreateDesignReviews = () => { diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/CreateTeamTypeFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/CreateTeamTypeFormModal.tsx index 4822ce2590..2ac08bfa0d 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/CreateTeamTypeFormModal.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/CreateTeamTypeFormModal.tsx @@ -12,7 +12,8 @@ import { CreateTeamTypePayload, useCreateTeamType } from '../../../hooks/design- const schema = yup.object().shape({ name: yup.string().required('Material Type is Required'), - iconName: yup.string().required('Icon Name is Required') + iconName: yup.string().required('Icon Name is Required'), + description: yup.string().required('Description is Required') }); interface CreateTeamTypeModalProps { @@ -44,7 +45,8 @@ const CreateTeamTypeModal: React.FC = ({ showModal, ha resolver: yupResolver(schema), defaultValues: { name: '', - iconName: '' + iconName: '', + description: '' } }); @@ -61,7 +63,7 @@ const CreateTeamTypeModal: React.FC = ({ showModal, ha open={showModal} onHide={handleClose} title="New Team Type" - reset={() => reset({ name: '', iconName: '' })} + reset={() => reset({ name: '', iconName: '', description: '' })} handleUseFormSubmit={handleSubmit} onFormSubmit={onSubmit} formId="new-team-type-form" @@ -89,6 +91,13 @@ const CreateTeamTypeModal: React.FC = ({ showModal, ha {errors.iconName?.message} + + + Description + + + {errors.description?.message} + ); }; diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx index aa8b833336..d0b013b909 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx @@ -34,13 +34,20 @@ const TeamTypeTable: React.FC = () => { + + + + {teamType.description} + + + )); return ( setCreateModalShow(false)} /> - + Date: Thu, 25 Jul 2024 13:41:55 -0400 Subject: [PATCH 016/264] #2708 prettier and tests --- src/backend/src/services/teams.services.ts | 2 +- src/backend/tests/unmocked/team-type.test.ts | 4 ++-- .../src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx | 5 ++++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/backend/src/services/teams.services.ts b/src/backend/src/services/teams.services.ts index 807b09b3a6..aff0e95b5b 100644 --- a/src/backend/src/services/teams.services.ts +++ b/src/backend/src/services/teams.services.ts @@ -46,7 +46,7 @@ export default class TeamsService { const newTeamType = await prisma.team_Type.update({ where: { teamTypeId }, data: { - image: imageData.id + imageFileId: imageData.id } }); diff --git a/src/backend/tests/unmocked/team-type.test.ts b/src/backend/tests/unmocked/team-type.test.ts index 6329087107..cb5fd22c6b 100644 --- a/src/backend/tests/unmocked/team-type.test.ts +++ b/src/backend/tests/unmocked/team-type.test.ts @@ -80,7 +80,7 @@ describe('Team Type Tests', () => { }); expect(organization).not.toBeNull(); - expect(organization?.image).toBe('uploaded-image1.png'); + expect(organization?.imageFileId).toBe('uploaded-image1.png'); await TeamsService.setImage(OTHER_FILE, testBatman, orgId, teamType.teamTypeId); @@ -90,7 +90,7 @@ describe('Team Type Tests', () => { } }); - expect(updatedTeamType?.image).toBe('uploaded-image2.png'); + expect(updatedTeamType?.imageFileId).toBe('uploaded-image2.png'); }); }); diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx index d0b013b909..fa23d462f6 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx @@ -47,7 +47,10 @@ const TeamTypeTable: React.FC = () => { return ( setCreateModalShow(false)} /> - + Date: Thu, 25 Jul 2024 14:30:55 -0400 Subject: [PATCH 017/264] #2708 edit team type now works --- .../src/controllers/teams.controllers.ts | 10 +- src/backend/src/routes/teams.routes.ts | 8 +- src/backend/src/services/teams.services.ts | 16 ++- src/backend/tests/unmocked/team-type.test.ts | 16 ++- src/frontend/src/apis/team-types.api.ts | 8 ++ src/frontend/src/hooks/teams.hooks.ts | 20 +++- .../TeamConfig/CreateTeamTypeFormModal.tsx | 101 ++--------------- .../TeamConfig/EditTeamTypeFormModal.tsx | 22 ++++ .../TeamConfig/TeamTypeFormModal.tsx | 105 ++++++++++++++++++ .../TeamConfig/TeamTypeTable.tsx | 15 ++- src/frontend/src/utils/urls.ts | 2 + 11 files changed, 211 insertions(+), 112 deletions(-) create mode 100644 src/frontend/src/pages/AdminToolsPage/TeamConfig/EditTeamTypeFormModal.tsx create mode 100644 src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx diff --git a/src/backend/src/controllers/teams.controllers.ts b/src/backend/src/controllers/teams.controllers.ts index 344b6da9c7..872adfaebb 100644 --- a/src/backend/src/controllers/teams.controllers.ts +++ b/src/backend/src/controllers/teams.controllers.ts @@ -184,16 +184,18 @@ export default class TeamsController { } } - static async editTeamTypeDescription(req: Request, res: Response, next: NextFunction) { + static async editTeamType(req: Request, res: Response, next: NextFunction) { try { - const { newDescription } = req.body; + const { name, iconName, description } = req.body; const user = await getCurrentUser(res); const organizationId = getOrganizationId(req.headers); - const teamType = await TeamsService.editTeamTypeDescription( + const teamType = await TeamsService.editTeamType( user, req.params.teamTypeId, - newDescription, + name, + iconName, + description, organizationId ); res.status(200).json(teamType); diff --git a/src/backend/src/routes/teams.routes.ts b/src/backend/src/routes/teams.routes.ts index 39d83bf8b8..2ecc5fca1c 100644 --- a/src/backend/src/routes/teams.routes.ts +++ b/src/backend/src/routes/teams.routes.ts @@ -62,10 +62,12 @@ teamsRouter.post( ); teamsRouter.post( - '/teamType/:teamTypeId/edit-description', - body('newDescription').isString(), + '/teamType/:teamTypeId/edit', + nonEmptyString(body('name')), + nonEmptyString(body('iconName')), + nonEmptyString(body('description')), validateInputs, - TeamsController.editTeamTypeDescription + TeamsController.editTeamType ); teamsRouter.post('/:teamId/set-team-type', nonEmptyString(body('teamTypeId')), validateInputs, TeamsController.setTeamType); diff --git a/src/backend/src/services/teams.services.ts b/src/backend/src/services/teams.services.ts index aff0e95b5b..996c41a1ff 100644 --- a/src/backend/src/services/teams.services.ts +++ b/src/backend/src/services/teams.services.ts @@ -454,17 +454,21 @@ export default class TeamsService { * Changes the description of the given teamType to be the new description * @param user The user who is editing the description * @param teamTypeId The id for the teamType that is being edited - * @param newDescription the new description for the team + * @param name the new name for the team + * @param iconName the new icon name for the team + * @param description the new description for the team * @param organizationId The organization the user is currently in * @returns The team with the new description */ - static async editTeamTypeDescription( + static async editTeamType( user: User, teamTypeId: string, - newDescription: string, + name: string, + iconName: string, + description: string, organizationId: string ): Promise { - if (!isUnderWordCount(newDescription, 300)) throw new HttpException(400, 'Description must be less than 300 words'); + if (!isUnderWordCount(description, 300)) throw new HttpException(400, 'Description must be less than 300 words'); if (!(await userHasPermission(user.userId, organizationId, isAdmin))) throw new AccessDeniedException('you must be an admin to edit the team types description'); @@ -472,7 +476,9 @@ export default class TeamsService { const updatedTeamType = await prisma.team_Type.update({ where: { teamTypeId }, data: { - description: newDescription + name, + iconName, + description } }); diff --git a/src/backend/tests/unmocked/team-type.test.ts b/src/backend/tests/unmocked/team-type.test.ts index cb5fd22c6b..3604aba7ed 100644 --- a/src/backend/tests/unmocked/team-type.test.ts +++ b/src/backend/tests/unmocked/team-type.test.ts @@ -134,7 +134,7 @@ describe('Team Type Tests', () => { expect(result).toEqual({ name: 'teamType3', iconName: 'YouTubeIcon', - image: null, + imageFileId: null, description: '', organizationId: orgId, teamTypeId: result.teamTypeId @@ -188,9 +188,11 @@ describe('Team Type Tests', () => { it('fails if user is not an admin', async () => { await expect( async () => - await TeamsService.editTeamTypeDescription( + await TeamsService.editTeamType( await createTestUser(wonderwomanGuest, orgId), 'id', + 'new name', + 'new icon', 'new description', orgId ) @@ -200,9 +202,11 @@ describe('Team Type Tests', () => { it('fails if the new description is over 300 workds', async () => { await expect( async () => - await TeamsService.editTeamTypeDescription( + await TeamsService.editTeamType( await createTestUser(supermanAdmin, orgId), 'id', + 'new name', + 'new icon', 'a '.repeat(301), orgId ) @@ -217,12 +221,16 @@ describe('Team Type Tests', () => { '', orgId ); - const updatedTeamType = await TeamsService.editTeamTypeDescription( + const updatedTeamType = await TeamsService.editTeamType( await createTestUser(batmanAppAdmin, orgId), teamType.teamTypeId, + 'new name', + 'new icon', 'new description', orgId ); + expect(updatedTeamType.name).toBe('new name'); + expect(updatedTeamType.iconName).toBe('new icon'); expect(updatedTeamType.description).toBe('new description'); }); }); diff --git a/src/frontend/src/apis/team-types.api.ts b/src/frontend/src/apis/team-types.api.ts index 48507686d0..6e701f0995 100644 --- a/src/frontend/src/apis/team-types.api.ts +++ b/src/frontend/src/apis/team-types.api.ts @@ -3,6 +3,8 @@ * See the LICENSE file in the repository root folder for details. */ +import { TeamType } from 'shared'; +import { CreateTeamTypePayload } from '../hooks/design-reviews.hooks'; import axios from '../utils/axios'; import { apiUrls } from '../utils/urls'; @@ -11,3 +13,9 @@ export const setTeamType = (id: string, teamTypeId: string) => { teamTypeId }); }; + +export const editTeamType = (id: string, payload: CreateTeamTypePayload) => { + return axios.post(apiUrls.teamTypeEdit(id), { + ...payload + }); +}; diff --git a/src/frontend/src/hooks/teams.hooks.ts b/src/frontend/src/hooks/teams.hooks.ts index ee64bb60ba..12749e5aca 100644 --- a/src/frontend/src/hooks/teams.hooks.ts +++ b/src/frontend/src/hooks/teams.hooks.ts @@ -4,7 +4,7 @@ */ import { useQuery, useQueryClient, useMutation } from 'react-query'; -import { Team } from 'shared'; +import { Team, TeamType } from 'shared'; import { getAllTeams, getSingleTeam, @@ -15,6 +15,8 @@ import { createTeam, setTeamLeads } from '../apis/teams.api'; +import { CreateTeamTypePayload } from './design-reviews.hooks'; +import { editTeamType } from '../apis/team-types.api'; export interface CreateTeamPayload { teamName: string; @@ -133,3 +135,19 @@ export const useSetTeamLeads = (teamId: string) => { } ); }; + +export const useEditTeamType = (teamTypeId: string) => { + const queryClient = useQueryClient(); + return useMutation( + ['team-type', 'edit'], + async (formData: CreateTeamTypePayload) => { + const { data } = await editTeamType(teamTypeId, formData); + return data; + }, + { + onSuccess: () => { + queryClient.invalidateQueries(['team-type', 'teams']); + } + } + ); +}; diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/CreateTeamTypeFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/CreateTeamTypeFormModal.tsx index 2ac08bfa0d..c7deaaf06d 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/CreateTeamTypeFormModal.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/CreateTeamTypeFormModal.tsx @@ -1,105 +1,20 @@ -import { useForm } from 'react-hook-form'; -import NERFormModal from '../../../components/NERFormModal'; -import { FormControl, FormLabel, FormHelperText, Tooltip, Typography } from '@mui/material'; -import ReactHookTextField from '../../../components/ReactHookTextField'; -import { useToast } from '../../../hooks/toasts.hooks'; +import TeamTypeFormModal from './TeamTypeFormModal'; +import { useCreateTeamType } from '../../../hooks/design-reviews.hooks'; +import ErrorPage from '../../ErrorPage'; import LoadingIndicator from '../../../components/LoadingIndicator'; -import * as yup from 'yup'; -import { yupResolver } from '@hookform/resolvers/yup'; -import { Box } from '@mui/system'; -import HelpIcon from '@mui/icons-material/Help'; -import { CreateTeamTypePayload, useCreateTeamType } from '../../../hooks/design-reviews.hooks'; - -const schema = yup.object().shape({ - name: yup.string().required('Material Type is Required'), - iconName: yup.string().required('Icon Name is Required'), - description: yup.string().required('Description is Required') -}); interface CreateTeamTypeModalProps { - showModal: boolean; + open: boolean; handleClose: () => void; } -const CreateTeamTypeModal: React.FC = ({ showModal, handleClose }) => { - const toast = useToast(); - const { isLoading, mutateAsync } = useCreateTeamType(); - - const onSubmit = async (data: CreateTeamTypePayload) => { - try { - await mutateAsync(data); - } catch (error: unknown) { - if (error instanceof Error) { - toast.error(error.message); - } - } - handleClose(); - }; - - const { - handleSubmit, - control, - reset, - formState: { errors } - } = useForm({ - resolver: yupResolver(schema), - defaultValues: { - name: '', - iconName: '', - description: '' - } - }); +const CreateTeamTypeModal = ({ open, handleClose }: CreateTeamTypeModalProps) => { + const { isLoading, isError, error, mutateAsync } = useCreateTeamType(); + if (isError) return ; if (isLoading) return ; - const TooltipMessage = () => ( - - Click to view possible icon names. For names with multiple words, seperate them with an _. AttachMoney = attach_money - - ); - - return ( - reset({ name: '', iconName: '', description: '' })} - handleUseFormSubmit={handleSubmit} - onFormSubmit={onSubmit} - formId="new-team-type-form" - showCloseButton - > - - Team Type - - {errors.name?.message} - - - - Icon Name - } placement="right"> - - - - - - - {errors.iconName?.message} - - - - Description - - - {errors.description?.message} - - - ); + return ; }; export default CreateTeamTypeModal; diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/EditTeamTypeFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/EditTeamTypeFormModal.tsx new file mode 100644 index 0000000000..6952b35654 --- /dev/null +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/EditTeamTypeFormModal.tsx @@ -0,0 +1,22 @@ +import { Link, LinkType, TeamType } from 'shared'; +import { useEditTeamType } from '../../../hooks/teams.hooks'; +import ErrorPage from '../../ErrorPage'; +import LoadingIndicator from '../../../components/LoadingIndicator'; +import TeamTypeFormModal from './TeamTypeFormModal'; + +interface EditTeamTypeFormModalProps { + open: boolean; + handleClose: () => void; + teamType: TeamType; +} + +const EditTeamTypeFormModal = ({ open, handleClose, teamType }: EditTeamTypeFormModalProps) => { + const { isLoading, isError, error, mutateAsync } = useEditTeamType(teamType.teamTypeId); + + if (isError) return ; + if (isLoading) return ; + + return ; +}; + +export default EditTeamTypeFormModal; diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx new file mode 100644 index 0000000000..c680d2704e --- /dev/null +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx @@ -0,0 +1,105 @@ +import { useForm } from 'react-hook-form'; +import NERFormModal from '../../../components/NERFormModal'; +import { FormControl, FormLabel, FormHelperText, Tooltip, Typography } from '@mui/material'; +import ReactHookTextField from '../../../components/ReactHookTextField'; +import { useToast } from '../../../hooks/toasts.hooks'; +import LoadingIndicator from '../../../components/LoadingIndicator'; +import * as yup from 'yup'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { Box } from '@mui/system'; +import HelpIcon from '@mui/icons-material/Help'; +import { CreateTeamTypePayload, useCreateTeamType } from '../../../hooks/design-reviews.hooks'; +import { TeamType } from 'shared'; + +interface TeamTypeFormModalProps { + open: boolean; + handleClose: () => void; + defaulValues?: TeamType; + onSubmit: (data: CreateTeamTypePayload) => void; +} + +const schema = yup.object().shape({ + name: yup.string().required('Material Type is Required'), + iconName: yup.string().required('Icon Name is Required'), + description: yup.string().required('Description is Required') +}); + +const CreateTeamTypeModal: React.FC = ({ open, handleClose, defaulValues, onSubmit }) => { + const toast = useToast(); + + const onFormSubmit = async (data: CreateTeamTypePayload) => { + try { + await onSubmit(data); + } catch (error: unknown) { + if (error instanceof Error) { + toast.error(error.message); + } + } + handleClose(); + }; + + const { + handleSubmit, + control, + reset, + formState: { errors } + } = useForm({ + resolver: yupResolver(schema), + defaultValues: { + name: defaulValues?.name ?? '', + iconName: defaulValues?.iconName ?? '', + description: defaulValues?.description ?? '' + } + }); + + const TooltipMessage = () => ( + + Click to view possible icon names. For names with multiple words, seperate them with an _. AttachMoney = attach_money + + ); + + return ( + reset({ name: '', iconName: '', description: '' })} + handleUseFormSubmit={handleSubmit} + onFormSubmit={onFormSubmit} + formId="new-team-type-form" + showCloseButton + > + + Team Type + + {errors.name?.message} + + + + Icon Name + } placement="right"> + + + + + + + {errors.iconName?.message} + + + + Description + + + {errors.description?.message} + + + ); +}; + +export default CreateTeamTypeModal; diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx index fa23d462f6..6bf2ca1a34 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx @@ -6,6 +6,8 @@ import { useState } from 'react'; import AdminToolTable from '../AdminToolTable'; import CreateTeamTypeModal from './CreateTeamTypeFormModal'; import { useAllTeamTypes } from '../../../hooks/design-reviews.hooks'; +import { TeamType } from 'shared'; +import EditTeamTypeFormModal from './EditTeamTypeFormModal'; const TeamTypeTable: React.FC = () => { const { @@ -15,6 +17,7 @@ const TeamTypeTable: React.FC = () => { error: teamTypesError } = useAllTeamTypes(); const [createModalShow, setCreateModalShow] = useState(false); + const [editingTeamType, setEditingTeamType] = useState(); if (!teamTypes || teamTypesIsLoading) { return ; @@ -24,7 +27,7 @@ const TeamTypeTable: React.FC = () => { } const teamTypesTableRows = teamTypes.map((teamType) => ( - + setEditingTeamType(teamType)} sx={{ cursor: 'pointer' }}> {teamType.name} @@ -46,11 +49,19 @@ const TeamTypeTable: React.FC = () => { return ( - setCreateModalShow(false)} /> + setCreateModalShow(false)} /> + {editingTeamType && ( + setEditingTeamType(undefined)} + teamType={editingTeamType} + /> + )} + `${teamsById(id)}/set-leads`; const teamTypes = () => `${teams()}/teamType`; const allTeamTypes = () => `${teamTypes()}/all`; const teamTypesCreate = () => `${teamTypes()}/create`; +const teamTypeEdit = (id: string) => `${teamTypes()}/${id}/edit`; /**************** Description Bullet Endpoints ****************/ const descriptionBullets = () => `${API_URL}/description-bullets`; @@ -241,6 +242,7 @@ export const apiUrls = { allTeamTypes, teamsSetTeamType, teamTypesCreate, + teamTypeEdit, descriptionBulletsCheck, descriptionBulletTypes, From cccc12f956cb1fb6590e6f20c4480d92ee011024 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Thu, 25 Jul 2024 14:38:16 -0400 Subject: [PATCH 018/264] #2708 linting --- .../pages/AdminToolsPage/TeamConfig/EditTeamTypeFormModal.tsx | 2 +- .../src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/EditTeamTypeFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/EditTeamTypeFormModal.tsx index 6952b35654..c5c5249188 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/EditTeamTypeFormModal.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/EditTeamTypeFormModal.tsx @@ -1,4 +1,4 @@ -import { Link, LinkType, TeamType } from 'shared'; +import { TeamType } from 'shared'; import { useEditTeamType } from '../../../hooks/teams.hooks'; import ErrorPage from '../../ErrorPage'; import LoadingIndicator from '../../../components/LoadingIndicator'; diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx index c680d2704e..61142b5d4b 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx @@ -3,12 +3,11 @@ import NERFormModal from '../../../components/NERFormModal'; import { FormControl, FormLabel, FormHelperText, Tooltip, Typography } from '@mui/material'; import ReactHookTextField from '../../../components/ReactHookTextField'; import { useToast } from '../../../hooks/toasts.hooks'; -import LoadingIndicator from '../../../components/LoadingIndicator'; import * as yup from 'yup'; import { yupResolver } from '@hookform/resolvers/yup'; import { Box } from '@mui/system'; import HelpIcon from '@mui/icons-material/Help'; -import { CreateTeamTypePayload, useCreateTeamType } from '../../../hooks/design-reviews.hooks'; +import { CreateTeamTypePayload } from '../../../hooks/design-reviews.hooks'; import { TeamType } from 'shared'; interface TeamTypeFormModalProps { From 328886f58a93aad91deb9b4b766ad6e3ac118f35 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Thu, 25 Jul 2024 15:15:16 -0400 Subject: [PATCH 019/264] #2708 renamed setImage endpoint to setTeamTypeImage --- .../src/controllers/teams.controllers.ts | 30 ++++++------ src/backend/src/routes/teams.routes.ts | 5 +- src/backend/src/services/teams.services.ts | 49 ++++++++++--------- src/backend/tests/unmocked/team-type.test.ts | 8 +-- 4 files changed, 47 insertions(+), 45 deletions(-) diff --git a/src/backend/src/controllers/teams.controllers.ts b/src/backend/src/controllers/teams.controllers.ts index 872adfaebb..c9d174fe7e 100644 --- a/src/backend/src/controllers/teams.controllers.ts +++ b/src/backend/src/controllers/teams.controllers.ts @@ -28,21 +28,6 @@ export default class TeamsController { } } - static async setImage(req: Request, res: Response, next: NextFunction) { - try { - const file = req.file as Express.Multer.File; - const submitter = await getCurrentUser(res); - const organizationId = getOrganizationId(req.headers); - const { teamTypeId } = req.params; - - const newImages = await TeamsService.setImage(file, submitter, organizationId, teamTypeId); - - res.status(200).json(newImages); - } catch (error: unknown) { - next(error); - } - } - static async setTeamMembers(req: Request, res: Response, next: NextFunction) { try { const { userIds } = req.body; @@ -204,6 +189,21 @@ export default class TeamsController { } } + static async setTeamTypeImage(req: Request, res: Response, next: NextFunction) { + try { + const file = req.file as Express.Multer.File; + const submitter = await getCurrentUser(res); + const organizationId = getOrganizationId(req.headers); + const { teamTypeId } = req.params; + + const newImages = await TeamsService.setTeamTypeImage(file, submitter, organizationId, teamTypeId); + + res.status(200).json(newImages); + } catch (error: unknown) { + next(error); + } + } + static async setTeamType(req: Request, res: Response, next: NextFunction) { try { const { teamTypeId } = req.body; diff --git a/src/backend/src/routes/teams.routes.ts b/src/backend/src/routes/teams.routes.ts index 2ecc5fca1c..f180762563 100644 --- a/src/backend/src/routes/teams.routes.ts +++ b/src/backend/src/routes/teams.routes.ts @@ -30,8 +30,6 @@ teamsRouter.post( TeamsController.editDescription ); -teamsRouter.post('/teamType/:teamTypeId/edit-image', upload.single('image'), TeamsController.setImage); - teamsRouter.post('/:teamId/set-head', nonEmptyString(body('userId')), validateInputs, TeamsController.setTeamHead); teamsRouter.post('/:teamId/delete', TeamsController.deleteTeam); teamsRouter.post( @@ -70,5 +68,8 @@ teamsRouter.post( TeamsController.editTeamType ); +teamsRouter.post('/teamType/:teamTypeId/edit-image', upload.single('image'), TeamsController.setTeamTypeImage); + teamsRouter.post('/:teamId/set-team-type', nonEmptyString(body('teamTypeId')), validateInputs, TeamsController.setTeamType); + export default teamsRouter; diff --git a/src/backend/src/services/teams.services.ts b/src/backend/src/services/teams.services.ts index 996c41a1ff..d011031a76 100644 --- a/src/backend/src/services/teams.services.ts +++ b/src/backend/src/services/teams.services.ts @@ -29,30 +29,6 @@ export default class TeamsService { return teams.map(teamTransformer); } - /** - * Sets team type image - * @param organizationId The organization the user is currently in - * @param teamTypeId The team type that is being updated - * @param image The image that is being placed in the team type - * @param submitter The user that is making the change - * @returns the updated team type - */ - static async setImage(image: Express.Multer.File, submitter: User, organizationId: string, teamTypeId: string) { - if (!(await userHasPermission(submitter.userId, organizationId, isAdmin))) - throw new AccessDeniedAdminOnlyException('update images'); - - const imageData = await uploadFile(image); - - const newTeamType = await prisma.team_Type.update({ - where: { teamTypeId }, - data: { - imageFileId: imageData.id - } - }); - - return newTeamType; - } - /** * Gets a team with the given id * @param teamId - id of team to retrieve @@ -485,6 +461,31 @@ export default class TeamsService { return updatedTeamType; } + /** + * Sets team type image + * @param organizationId The organization the user is currently in + * @param teamTypeId The team type that is being updated + * @param image The image that is being placed in the team type + * @param submitter The user that is making the change + * @returns the updated team type + */ + static async setTeamTypeImage(image: Express.Multer.File, submitter: User, organizationId: string, teamTypeId: string) { + if (!(await userHasPermission(submitter.userId, organizationId, isAdmin))) + throw new AccessDeniedAdminOnlyException('update images'); + + const imageData = await uploadFile(image); + + const newTeamType = await prisma.team_Type.update({ + where: { teamTypeId }, + data: { + imageFileId: imageData.id + } + }); + + return newTeamType; + } + + /** * Sets the teamType for a team * @param submitter the user who is setting the team type diff --git a/src/backend/tests/unmocked/team-type.test.ts b/src/backend/tests/unmocked/team-type.test.ts index 3604aba7ed..0c042ceebe 100644 --- a/src/backend/tests/unmocked/team-type.test.ts +++ b/src/backend/tests/unmocked/team-type.test.ts @@ -38,7 +38,7 @@ describe('Team Type Tests', () => { ); await expect( - TeamsService.setImage(TEST_FILE, await createTestUser(wonderwomanGuest, orgId), orgId, teamType.teamTypeId) + TeamsService.setTeamTypeImage(TEST_FILE, await createTestUser(wonderwomanGuest, orgId), orgId, teamType.teamTypeId) ).rejects.toThrow(new AccessDeniedAdminOnlyException('update images')); }); @@ -52,7 +52,7 @@ describe('Team Type Tests', () => { ); await expect( - TeamsService.setImage(TEST_FILE, await createTestUser(batmanAppAdmin, orgId), '1', teamType.teamTypeId) + TeamsService.setTeamTypeImage(TEST_FILE, await createTestUser(batmanAppAdmin, orgId), '1', teamType.teamTypeId) ).rejects.toThrow(new HttpException(400, `Organization with id: 1 not found!`)); }); @@ -71,7 +71,7 @@ describe('Team Type Tests', () => { return Promise.resolve({ id: `uploaded-${file.originalname}` }); }); - await TeamsService.setImage(TEST_FILE, testBatman, orgId, teamType.teamTypeId); + await TeamsService.setTeamTypeImage(TEST_FILE, testBatman, orgId, teamType.teamTypeId); const organization = await prisma.team_Type.findUnique({ where: { @@ -82,7 +82,7 @@ describe('Team Type Tests', () => { expect(organization).not.toBeNull(); expect(organization?.imageFileId).toBe('uploaded-image1.png'); - await TeamsService.setImage(OTHER_FILE, testBatman, orgId, teamType.teamTypeId); + await TeamsService.setTeamTypeImage(OTHER_FILE, testBatman, orgId, teamType.teamTypeId); const updatedTeamType = await prisma.team_Type.findUnique({ where: { From 516f88064fdaf89330e2843fbabfad4fffaa5851 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Thu, 25 Jul 2024 15:35:33 -0400 Subject: [PATCH 020/264] #2709 prettier --- src/backend/src/services/teams.services.ts | 33 +++++++++++----------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/backend/src/services/teams.services.ts b/src/backend/src/services/teams.services.ts index d011031a76..b7e8dadffc 100644 --- a/src/backend/src/services/teams.services.ts +++ b/src/backend/src/services/teams.services.ts @@ -461,7 +461,7 @@ export default class TeamsService { return updatedTeamType; } - /** + /** * Sets team type image * @param organizationId The organization the user is currently in * @param teamTypeId The team type that is being updated @@ -469,22 +469,21 @@ export default class TeamsService { * @param submitter The user that is making the change * @returns the updated team type */ - static async setTeamTypeImage(image: Express.Multer.File, submitter: User, organizationId: string, teamTypeId: string) { - if (!(await userHasPermission(submitter.userId, organizationId, isAdmin))) - throw new AccessDeniedAdminOnlyException('update images'); - - const imageData = await uploadFile(image); - - const newTeamType = await prisma.team_Type.update({ - where: { teamTypeId }, - data: { - imageFileId: imageData.id - } - }); - - return newTeamType; - } - + static async setTeamTypeImage(image: Express.Multer.File, submitter: User, organizationId: string, teamTypeId: string) { + if (!(await userHasPermission(submitter.userId, organizationId, isAdmin))) + throw new AccessDeniedAdminOnlyException('update images'); + + const imageData = await uploadFile(image); + + const newTeamType = await prisma.team_Type.update({ + where: { teamTypeId }, + data: { + imageFileId: imageData.id + } + }); + + return newTeamType; + } /** * Sets the teamType for a team From c9603e6de9e98c5eb531d5c6358663ba1f5a4bb0 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Thu, 25 Jul 2024 19:38:46 -0400 Subject: [PATCH 021/264] #2708 fixed invalid query bug and moved team type apis into respective folders --- src/frontend/src/apis/design-reviews.api.ts | 15 +--- src/frontend/src/apis/team-types.api.ts | 12 +++- .../src/hooks/design-reviews.hooks.ts | 43 +---------- src/frontend/src/hooks/team-types.hooks.ts | 72 ++++++++++++++++++- src/frontend/src/hooks/teams.hooks.ts | 20 +----- .../TeamConfig/CreateTeamTypeFormModal.tsx | 2 +- .../TeamConfig/EditTeamTypeFormModal.tsx | 2 +- .../TeamConfig/TeamTypeFormModal.tsx | 2 +- .../TeamConfig/TeamTypeTable.tsx | 2 +- .../src/pages/CalendarPage/CalendarPage.tsx | 2 +- .../src/pages/GanttPage/GanttChartPage.tsx | 2 +- .../src/pages/TeamsPage/SetTeamTypeModal.tsx | 3 +- 12 files changed, 91 insertions(+), 86 deletions(-) diff --git a/src/frontend/src/apis/design-reviews.api.ts b/src/frontend/src/apis/design-reviews.api.ts index 8611ceefd3..48b53b14fa 100644 --- a/src/frontend/src/apis/design-reviews.api.ts +++ b/src/frontend/src/apis/design-reviews.api.ts @@ -2,7 +2,7 @@ * This file is part of NER's FinishLine and licensed under GNU AGPLv3. * See the LICENSE file in the repository root folder for details. */ -import { CreateTeamTypePayload, EditDesignReviewPayload } from '../hooks/design-reviews.hooks'; +import { EditDesignReviewPayload } from '../hooks/design-reviews.hooks'; import axios from '../utils/axios'; import { DesignReview } from 'shared'; import { apiUrls } from '../utils/urls'; @@ -26,19 +26,6 @@ export const getAllDesignReviews = () => { }); }; -/** - * Gets all the team types - */ -export const getAllTeamTypes = () => { - return axios.get(apiUrls.allTeamTypes(), { - transformResponse: (data) => JSON.parse(data) - }); -}; - -export const createTeamType = async (payload: CreateTeamTypePayload) => { - return axios.post(apiUrls.teamTypesCreate(), payload); -}; - /** * Edit a design review * diff --git a/src/frontend/src/apis/team-types.api.ts b/src/frontend/src/apis/team-types.api.ts index 6e701f0995..d7bdcb11dd 100644 --- a/src/frontend/src/apis/team-types.api.ts +++ b/src/frontend/src/apis/team-types.api.ts @@ -4,9 +4,19 @@ */ import { TeamType } from 'shared'; -import { CreateTeamTypePayload } from '../hooks/design-reviews.hooks'; import axios from '../utils/axios'; import { apiUrls } from '../utils/urls'; +import { CreateTeamTypePayload } from '../hooks/team-types.hooks'; + +export const getAllTeamTypes = () => { + return axios.get(apiUrls.allTeamTypes(), { + transformResponse: (data) => JSON.parse(data) + }); +}; + +export const createTeamType = async (payload: CreateTeamTypePayload) => { + return axios.post(apiUrls.teamTypesCreate(), payload); +}; export const setTeamType = (id: string, teamTypeId: string) => { return axios.post<{ message: string }>(apiUrls.teamsSetTeamType(id), { diff --git a/src/frontend/src/hooks/design-reviews.hooks.ts b/src/frontend/src/hooks/design-reviews.hooks.ts index 1819a20b35..05ccff86f3 100644 --- a/src/frontend/src/hooks/design-reviews.hooks.ts +++ b/src/frontend/src/hooks/design-reviews.hooks.ts @@ -3,16 +3,14 @@ * See the LICENSE file in the repository root folder for details. */ import { useMutation, useQuery, useQueryClient } from 'react-query'; -import { DesignReview, TeamType, WbsNumber, DesignReviewStatus } from 'shared'; +import { DesignReview, WbsNumber, DesignReviewStatus } from 'shared'; import { deleteDesignReview, editDesignReview, createDesignReviews, getAllDesignReviews, - getAllTeamTypes, getSingleDesignReview, markUserConfirmed, - createTeamType } from '../apis/design-reviews.api'; import { useCurrentUser } from './users.hooks'; @@ -25,12 +23,6 @@ export interface CreateDesignReviewsPayload { meetingTimes: number[]; } -export interface CreateTeamTypePayload { - name: string; - iconName: string; - description: string; -} - export const useCreateDesignReviews = () => { const queryClient = useQueryClient(); return useMutation( @@ -94,39 +86,6 @@ export const useEditDesignReview = (designReviewId: string) => { ); }; -/** - * Custom react hook to get all team types - * - * @returns all the team types - */ -export const useAllTeamTypes = () => { - return useQuery(['teamTypes'], async () => { - const { data } = await getAllTeamTypes(); - return data; - }); -}; - -/** - * Custom react hook to create a team type - * - * @returns the team type created - */ -export const useCreateTeamType = () => { - const queryClient = useQueryClient(); - return useMutation( - ['teamTypes', 'create'], - async (teamTypePayload) => { - const { data } = await createTeamType(teamTypePayload); - return data; - }, - { - onSuccess: () => { - queryClient.invalidateQueries(['teamTypes']); - } - } - ); -}; - /** * Custom react hook to delete a design review */ diff --git a/src/frontend/src/hooks/team-types.hooks.ts b/src/frontend/src/hooks/team-types.hooks.ts index f0ffcf05c7..4fdd1cd363 100644 --- a/src/frontend/src/hooks/team-types.hooks.ts +++ b/src/frontend/src/hooks/team-types.hooks.ts @@ -3,9 +3,55 @@ * See the LICENSE file in the repository root folder for details. */ -import { useQueryClient, useMutation } from 'react-query'; -import { setTeamType } from '../apis/team-types.api'; +import { useQueryClient, useMutation, useQuery } from 'react-query'; +import { createTeamType, editTeamType, getAllTeamTypes, setTeamType } from '../apis/team-types.api'; +import { TeamType } from 'shared'; +export interface CreateTeamTypePayload { + name: string; + iconName: string; + description: string; +} + +/** + * Custom react hook to get all team types + * + * @returns all the team types + */ +export const useAllTeamTypes = () => { + return useQuery(['team types'], async () => { + const { data } = await getAllTeamTypes(); + return data; + }); +}; + +/** + * Custom react hook to create a team type + * + * @returns the team type created + */ +export const useCreateTeamType = () => { + const queryClient = useQueryClient(); + return useMutation( + ['team types', 'create'], + async (teamTypePayload) => { + const { data } = await createTeamType(teamTypePayload); + return data; + }, + { + onSuccess: () => { + queryClient.invalidateQueries(['team types']); + } + } + ); +}; + +/** + * Custom react hook to set the team type of a team + * + * @param teamId id of the team to set the team type + * @returns the updated team + */ export const useSetTeamType = (teamId: string) => { const queryClient = useQueryClient(); return useMutation<{ message: string }, Error, string>( @@ -21,3 +67,25 @@ export const useSetTeamType = (teamId: string) => { } ); }; + +/** + * Custome react hook to update a team type + * + * @param teamTypeId id of the team type to edit + * @returns the updated team type + */ +export const useEditTeamType = (teamTypeId: string) => { + const queryClient = useQueryClient(); + return useMutation( + ['team types', 'edit'], + async (formData: CreateTeamTypePayload) => { + const { data } = await editTeamType(teamTypeId, formData); + return data; + }, + { + onSuccess: () => { + queryClient.invalidateQueries(['team types']); + } + } + ); +}; diff --git a/src/frontend/src/hooks/teams.hooks.ts b/src/frontend/src/hooks/teams.hooks.ts index 12749e5aca..ee64bb60ba 100644 --- a/src/frontend/src/hooks/teams.hooks.ts +++ b/src/frontend/src/hooks/teams.hooks.ts @@ -4,7 +4,7 @@ */ import { useQuery, useQueryClient, useMutation } from 'react-query'; -import { Team, TeamType } from 'shared'; +import { Team } from 'shared'; import { getAllTeams, getSingleTeam, @@ -15,8 +15,6 @@ import { createTeam, setTeamLeads } from '../apis/teams.api'; -import { CreateTeamTypePayload } from './design-reviews.hooks'; -import { editTeamType } from '../apis/team-types.api'; export interface CreateTeamPayload { teamName: string; @@ -135,19 +133,3 @@ export const useSetTeamLeads = (teamId: string) => { } ); }; - -export const useEditTeamType = (teamTypeId: string) => { - const queryClient = useQueryClient(); - return useMutation( - ['team-type', 'edit'], - async (formData: CreateTeamTypePayload) => { - const { data } = await editTeamType(teamTypeId, formData); - return data; - }, - { - onSuccess: () => { - queryClient.invalidateQueries(['team-type', 'teams']); - } - } - ); -}; diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/CreateTeamTypeFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/CreateTeamTypeFormModal.tsx index c7deaaf06d..80ad426eeb 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/CreateTeamTypeFormModal.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/CreateTeamTypeFormModal.tsx @@ -1,7 +1,7 @@ import TeamTypeFormModal from './TeamTypeFormModal'; -import { useCreateTeamType } from '../../../hooks/design-reviews.hooks'; import ErrorPage from '../../ErrorPage'; import LoadingIndicator from '../../../components/LoadingIndicator'; +import { useCreateTeamType } from '../../../hooks/team-types.hooks'; interface CreateTeamTypeModalProps { open: boolean; diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/EditTeamTypeFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/EditTeamTypeFormModal.tsx index c5c5249188..a5e9dafbde 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/EditTeamTypeFormModal.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/EditTeamTypeFormModal.tsx @@ -1,8 +1,8 @@ import { TeamType } from 'shared'; -import { useEditTeamType } from '../../../hooks/teams.hooks'; import ErrorPage from '../../ErrorPage'; import LoadingIndicator from '../../../components/LoadingIndicator'; import TeamTypeFormModal from './TeamTypeFormModal'; +import { useEditTeamType } from '../../../hooks/team-types.hooks'; interface EditTeamTypeFormModalProps { open: boolean; diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx index 61142b5d4b..32886618a9 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx @@ -7,8 +7,8 @@ import * as yup from 'yup'; import { yupResolver } from '@hookform/resolvers/yup'; import { Box } from '@mui/system'; import HelpIcon from '@mui/icons-material/Help'; -import { CreateTeamTypePayload } from '../../../hooks/design-reviews.hooks'; import { TeamType } from 'shared'; +import { CreateTeamTypePayload } from '../../../hooks/team-types.hooks'; interface TeamTypeFormModalProps { open: boolean; diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx index 6bf2ca1a34..8db281a64b 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx @@ -5,9 +5,9 @@ import { NERButton } from '../../../components/NERButton'; import { useState } from 'react'; import AdminToolTable from '../AdminToolTable'; import CreateTeamTypeModal from './CreateTeamTypeFormModal'; -import { useAllTeamTypes } from '../../../hooks/design-reviews.hooks'; import { TeamType } from 'shared'; import EditTeamTypeFormModal from './EditTeamTypeFormModal'; +import { useAllTeamTypes } from '../../../hooks/team-types.hooks'; const TeamTypeTable: React.FC = () => { const { diff --git a/src/frontend/src/pages/CalendarPage/CalendarPage.tsx b/src/frontend/src/pages/CalendarPage/CalendarPage.tsx index 1abd1b0d86..87b17a1939 100644 --- a/src/frontend/src/pages/CalendarPage/CalendarPage.tsx +++ b/src/frontend/src/pages/CalendarPage/CalendarPage.tsx @@ -15,9 +15,9 @@ import { useAllDesignReviews } from '../../hooks/design-reviews.hooks'; import ErrorPage from '../ErrorPage'; import { useCurrentUser } from '../../hooks/users.hooks'; import { datePipe } from '../../utils/pipes'; -import { useAllTeamTypes } from '../../hooks/design-reviews.hooks'; import LoadingIndicator from '../../components/LoadingIndicator'; import DRCSummaryModal from './DesignReviewSummaryModal'; +import { useAllTeamTypes } from '../../hooks/team-types.hooks'; const CalendarPage = () => { const theme = useTheme(); diff --git a/src/frontend/src/pages/GanttPage/GanttChartPage.tsx b/src/frontend/src/pages/GanttPage/GanttChartPage.tsx index 578bc65c40..b3707a2824 100644 --- a/src/frontend/src/pages/GanttPage/GanttChartPage.tsx +++ b/src/frontend/src/pages/GanttPage/GanttChartPage.tsx @@ -19,10 +19,10 @@ import { SearchBar } from '../../components/SearchBar'; import GanttChartColorLegend from './GanttChartComponents/GanttChartColorLegend'; import GanttChartFiltersButton from './GanttChartComponents/GanttChartFiltersButton'; import GanttChart from './GanttChart'; -import { useAllTeamTypes } from '../../hooks/design-reviews.hooks'; import { Project, Team, TeamType, WbsElement, WorkPackage } from 'shared'; import { useAllTeams } from '../../hooks/teams.hooks'; import { useGetAllCars } from '../../hooks/cars.hooks'; +import { useAllTeamTypes } from '../../hooks/team-types.hooks'; const GanttChartPage: FC = () => { const query = useQuery(); diff --git a/src/frontend/src/pages/TeamsPage/SetTeamTypeModal.tsx b/src/frontend/src/pages/TeamsPage/SetTeamTypeModal.tsx index 40413a91b5..da4697332a 100644 --- a/src/frontend/src/pages/TeamsPage/SetTeamTypeModal.tsx +++ b/src/frontend/src/pages/TeamsPage/SetTeamTypeModal.tsx @@ -7,8 +7,7 @@ import LoadingIndicator from '../../components/LoadingIndicator'; import ErrorPage from '../ErrorPage'; import NERFormModal from '../../components/NERFormModal'; import { FormControl, FormLabel, Select, MenuItem } from '@mui/material'; -import { useAllTeamTypes } from '../../hooks/design-reviews.hooks'; -import { useSetTeamType } from '../../hooks/team-types.hooks'; +import { useAllTeamTypes, useSetTeamType } from '../../hooks/team-types.hooks'; interface SetTeamTypeInputs { teamId: string; From 4c7044bb2f191ab3b380d7b48e64f443270e296f Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Thu, 25 Jul 2024 19:42:26 -0400 Subject: [PATCH 022/264] #2708 prettier --- src/frontend/src/hooks/design-reviews.hooks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/hooks/design-reviews.hooks.ts b/src/frontend/src/hooks/design-reviews.hooks.ts index 05ccff86f3..182ff94bce 100644 --- a/src/frontend/src/hooks/design-reviews.hooks.ts +++ b/src/frontend/src/hooks/design-reviews.hooks.ts @@ -10,7 +10,7 @@ import { createDesignReviews, getAllDesignReviews, getSingleDesignReview, - markUserConfirmed, + markUserConfirmed } from '../apis/design-reviews.api'; import { useCurrentUser } from './users.hooks'; From 41b1129ec2821f24297d08e357485989cb106f43 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Thu, 25 Jul 2024 19:44:36 -0400 Subject: [PATCH 023/264] #2708 prettier --- src/frontend/src/hooks/team-types.hooks.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/hooks/team-types.hooks.ts b/src/frontend/src/hooks/team-types.hooks.ts index 4fdd1cd363..cf278779de 100644 --- a/src/frontend/src/hooks/team-types.hooks.ts +++ b/src/frontend/src/hooks/team-types.hooks.ts @@ -48,7 +48,7 @@ export const useCreateTeamType = () => { /** * Custom react hook to set the team type of a team - * + * * @param teamId id of the team to set the team type * @returns the updated team */ @@ -70,7 +70,7 @@ export const useSetTeamType = (teamId: string) => { /** * Custome react hook to update a team type - * + * * @param teamTypeId id of the team type to edit * @returns the updated team type */ From a004caaf5021562336a2304f72b11630c36ea4c0 Mon Sep 17 00:00:00 2001 From: harish Date: Thu, 25 Jul 2024 20:19:29 -0400 Subject: [PATCH 024/264] #1606 archive hook --- src/frontend/src/apis/teams.api.ts | 4 ++++ src/frontend/src/hooks/teams.hooks.ts | 14 ++++++++++- .../src/pages/TeamsPage/TeamSpecificPage.tsx | 23 ++++++++++++++++--- src/frontend/src/utils/urls.ts | 2 ++ 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/frontend/src/apis/teams.api.ts b/src/frontend/src/apis/teams.api.ts index 018bcc5c0b..392a3ba199 100644 --- a/src/frontend/src/apis/teams.api.ts +++ b/src/frontend/src/apis/teams.api.ts @@ -39,6 +39,10 @@ export const setTeamHead = (id: string, userId: string) => { }); }; +export const archiveTeam = (id: string) => { + return axios.post(apiUrls.teamsArchive(id)); +} + export const deleteTeam = (id: string) => { return axios.post<{ message: string }>(apiUrls.teamsDelete(id)); }; diff --git a/src/frontend/src/hooks/teams.hooks.ts b/src/frontend/src/hooks/teams.hooks.ts index 48553280ff..17864227ce 100644 --- a/src/frontend/src/hooks/teams.hooks.ts +++ b/src/frontend/src/hooks/teams.hooks.ts @@ -13,7 +13,8 @@ import { setTeamHead, deleteTeam, createTeam, - setTeamLeads + setTeamLeads, + archiveTeam } from '../apis/teams.api'; export interface CreateTeamPayload { @@ -54,6 +55,17 @@ export const useSetTeamMembers = (teamId: string) => { ); }; +export const useArchiveTeam = (teamId: string) => { + const queryClient = useQueryClient(); + return useMutation( + ['teams', 'edit'], + async () => { + const { data } = await archiveTeam(teamId); + return data; + } + ) +} + export const useSetTeamHead = (teamId: string) => { const queryClient = useQueryClient(); return useMutation( diff --git a/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx b/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx index efc0f06995..c4e1dfcf66 100644 --- a/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx +++ b/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx @@ -1,5 +1,5 @@ import { Box, Grid, Menu, MenuItem, Stack } from '@mui/material'; -import { useSingleTeam } from '../../hooks/teams.hooks'; +import { useArchiveTeam, useSingleTeam } from '../../hooks/teams.hooks'; import { useParams } from 'react-router-dom'; import TeamMembersPageBlock from './TeamMembersPageBlock'; import LoadingIndicator from '../../components/LoadingIndicator'; @@ -18,6 +18,7 @@ import DeleteTeamModal from './DeleteTeamModal'; import SetTeamTypeModal from './SetTeamTypeModal'; import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; import { TeamPill } from './TeamPill'; +import { useDeleteDesignReview } from '../../hooks/design-reviews.hooks'; interface ParamTypes { teamId: string; @@ -31,6 +32,7 @@ const TeamSpecificPage: React.FC = () => { const [showTeamTypeModal, setShowTeamTypeModal] = useState(false); const [anchorEl, setAnchorEl] = useState(null); const dropdownOpen = Boolean(anchorEl); + const { mutateAsync: archiveTeam } = useArchiveTeam(teamId); const handleClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); @@ -64,11 +66,26 @@ const TeamSpecificPage: React.FC = () => { archive: boolean; } + const handleArchive = async () => { + try { + await archiveTeam(teamId); + } catch (e: unknown) { + if (e instanceof Error) { + toast.error(e.message, 3000); + } + } + }; + const ArchiveTeamButton: React.FC = ({ archive }) => ( - - {archive ? 'Archive Team' : 'Unarchive Team'} + handleArchive()} + disabled={!isAdmin(user.role)} + > + {archive ? 'Unarchive Team' : 'Archive Team'} ); + const TeamActionsDropdown = () => ( diff --git a/src/frontend/src/utils/urls.ts b/src/frontend/src/utils/urls.ts index c052038fea..a0c66fe803 100644 --- a/src/frontend/src/utils/urls.ts +++ b/src/frontend/src/utils/urls.ts @@ -81,6 +81,7 @@ const teamsDelete = (id: string) => `${teamsById(id)}/delete`; const teamsSetMembers = (id: string) => `${teamsById(id)}/set-members`; const teamsSetTeamType = (id: string) => `${teamsById(id)}/set-team-type`; const teamsSetHead = (id: string) => `${teamsById(id)}/set-head`; +const teamsArchive = (id: string) => `${teamsById(id)}/archive`; const teamsSetDescription = (id: string) => `${teamsById(id)}/edit-description`; const teamsCreate = () => `${teams()}/create`; const teamsSetLeads = (id: string) => `${teamsById(id)}/set-leads`; @@ -225,6 +226,7 @@ export const apiUrls = { teamsById, teamsDelete, teamsSetMembers, + teamsArchive, teamsSetHead, teamsSetDescription, teamsCreate, From e4c8768d63d038397a50b607020d094af9928b21 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Fri, 26 Jul 2024 20:38:10 -0400 Subject: [PATCH 025/264] #2735 started endpoint --- src/frontend/src/apis/team-types.api.ts | 6 ++ src/frontend/src/hooks/team-types.hooks.ts | 22 +++++- .../TeamConfig/TeamTypeFormModal.tsx | 76 +++++++++++++++++-- .../TeamConfig/TeamTypeTable.tsx | 12 ++- src/frontend/src/utils/urls.ts | 4 +- 5 files changed, 111 insertions(+), 9 deletions(-) diff --git a/src/frontend/src/apis/team-types.api.ts b/src/frontend/src/apis/team-types.api.ts index d7bdcb11dd..3e001aadd0 100644 --- a/src/frontend/src/apis/team-types.api.ts +++ b/src/frontend/src/apis/team-types.api.ts @@ -29,3 +29,9 @@ export const editTeamType = (id: string, payload: CreateTeamTypePayload) => { ...payload }); }; + +export const setTeamTypeImage = (file: File, id: string) => { + const formData = new FormData(); + formData.append('image', file); + return axios.post(apiUrls.teamTypeSetImage(id), formData); +}; diff --git a/src/frontend/src/hooks/team-types.hooks.ts b/src/frontend/src/hooks/team-types.hooks.ts index cf278779de..db4a4963f0 100644 --- a/src/frontend/src/hooks/team-types.hooks.ts +++ b/src/frontend/src/hooks/team-types.hooks.ts @@ -4,13 +4,14 @@ */ import { useQueryClient, useMutation, useQuery } from 'react-query'; -import { createTeamType, editTeamType, getAllTeamTypes, setTeamType } from '../apis/team-types.api'; +import { createTeamType, editTeamType, getAllTeamTypes, setTeamType, setTeamTypeImage } from '../apis/team-types.api'; import { TeamType } from 'shared'; export interface CreateTeamTypePayload { name: string; iconName: string; description: string; + image?: File; } /** @@ -89,3 +90,22 @@ export const useEditTeamType = (teamTypeId: string) => { } ); }; + +/** + * Custome react hook to update a team type + * + * @param teamTypeId id of the team type to edit + * @returns the updated team type + */ +/** + * Custom React Hook to upload a new picture. + */ +export const useSetTeamTypeImage = () => { + return useMutation<{ googleFileId: string; name: string }, Error, { file: File; id: string }>( + ['team types', 'edit'], + async (formData: { file: File; id: string }) => { + const { data } = await setTeamTypeImage(formData.file, formData.id); + return data; + } + ); +}; \ No newline at end of file diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx index 32886618a9..ad640d43c9 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx @@ -1,6 +1,6 @@ -import { useForm } from 'react-hook-form'; +import { useFieldArray, useForm } from 'react-hook-form'; import NERFormModal from '../../../components/NERFormModal'; -import { FormControl, FormLabel, FormHelperText, Tooltip, Typography } from '@mui/material'; +import { FormControl, FormLabel, FormHelperText, Tooltip, Typography, Button } from '@mui/material'; import ReactHookTextField from '../../../components/ReactHookTextField'; import { useToast } from '../../../hooks/toasts.hooks'; import * as yup from 'yup'; @@ -8,13 +8,16 @@ import { yupResolver } from '@hookform/resolvers/yup'; import { Box } from '@mui/system'; import HelpIcon from '@mui/icons-material/Help'; import { TeamType } from 'shared'; -import { CreateTeamTypePayload } from '../../../hooks/team-types.hooks'; +import { CreateTeamTypePayload, useSetTeamTypeImage } from '../../../hooks/team-types.hooks'; +import React, { useEffect, useState } from 'react'; +import FileUploadIcon from '@mui/icons-material/FileUpload'; +import LoadingIndicator from '../../../components/LoadingIndicator'; interface TeamTypeFormModalProps { open: boolean; handleClose: () => void; defaulValues?: TeamType; - onSubmit: (data: CreateTeamTypePayload) => void; + onSubmit: (data: CreateTeamTypePayload) => TeamType; } const schema = yup.object().shape({ @@ -25,10 +28,25 @@ const schema = yup.object().shape({ const CreateTeamTypeModal: React.FC = ({ open, handleClose, defaulValues, onSubmit }) => { const toast = useToast(); + const { isLoading: setTeamTypeIsLoading, mutateAsync: setImage } = useSetTeamTypeImage(); + + const [imagePreview, setImagePreview] = useState(null); + + useEffect(() => { + if (defaulValues?.image) { + setImagePreview(defaulValues.image); + } + }, [defaulValues]); + + if (setTeamTypeIsLoading) return ; const onFormSubmit = async (data: CreateTeamTypePayload) => { try { - await onSubmit(data); + const { teamTypeId } = await onSubmit(data); + + if (data.image) { + await setImage({ file: data.image, id: teamTypeId }); + } } catch (error: unknown) { if (error instanceof Error) { toast.error(error.message); @@ -47,7 +65,8 @@ const CreateTeamTypeModal: React.FC = ({ open, handleClo defaultValues: { name: defaulValues?.name ?? '', iconName: defaulValues?.iconName ?? '', - description: defaulValues?.description ?? '' + description: defaulValues?.description ?? '', + image: defaulValues?.image ?? undefined } }); @@ -97,6 +116,51 @@ const CreateTeamTypeModal: React.FC = ({ open, handleClo {errors.description?.message} + + Image + {imagePreview && ( + + )} + + {errors.image?.message} + ); }; diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx index 8db281a64b..b55f99bf44 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx @@ -44,6 +44,16 @@ const TeamTypeTable: React.FC = () => { + + {teamType.image && ( + + )} + )); @@ -58,7 +68,7 @@ const TeamTypeTable: React.FC = () => { /> )} diff --git a/src/frontend/src/utils/urls.ts b/src/frontend/src/utils/urls.ts index 2bc0b78ab8..d17768751b 100644 --- a/src/frontend/src/utils/urls.ts +++ b/src/frontend/src/utils/urls.ts @@ -89,6 +89,7 @@ const teamTypes = () => `${teams()}/teamType`; const allTeamTypes = () => `${teamTypes()}/all`; const teamTypesCreate = () => `${teamTypes()}/create`; const teamTypeEdit = (id: string) => `${teamTypes()}/${id}/edit`; +const teamTypeSetImage = (id: string) => `${teamTypes()}/${id}/edit-image`; /**************** Description Bullet Endpoints ****************/ const descriptionBullets = () => `${API_URL}/description-bullets`; @@ -243,7 +244,8 @@ export const apiUrls = { teamsSetTeamType, teamTypesCreate, teamTypeEdit, - + teamTypeSetImage, + descriptionBulletsCheck, descriptionBulletTypes, editDescriptionBulletType, From b01825a3e87fe39560d298bbff5af2c935788388 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Tue, 30 Jul 2024 10:14:54 -0400 Subject: [PATCH 026/264] #2708 used useHistoryState and useFormPersist for team type modals --- .../TeamConfig/CreateTeamTypeFormModal.tsx | 12 --------- .../TeamConfig/TeamTypeFormModal.tsx | 27 +++++++++++++++---- .../TeamConfig/TeamTypeTable.tsx | 6 ++--- src/frontend/src/utils/form.ts | 3 ++- 4 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/CreateTeamTypeFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/CreateTeamTypeFormModal.tsx index 3de82697bf..80ad426eeb 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/CreateTeamTypeFormModal.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/CreateTeamTypeFormModal.tsx @@ -2,18 +2,6 @@ import TeamTypeFormModal from './TeamTypeFormModal'; import ErrorPage from '../../ErrorPage'; import LoadingIndicator from '../../../components/LoadingIndicator'; import { useCreateTeamType } from '../../../hooks/team-types.hooks'; -import * as yup from 'yup'; -import { yupResolver } from '@hookform/resolvers/yup'; -import { Box } from '@mui/system'; -import HelpIcon from '@mui/icons-material/Help'; -import { CreateTeamTypePayload, useCreateTeamType } from '../../../hooks/design-reviews.hooks'; -import useFormPersist from 'react-hook-form-persist'; -import { FormStorageKey } from '../../../utils/form'; - -const schema = yup.object().shape({ - name: yup.string().required('Material Type is Required'), - iconName: yup.string().required('Icon Name is Required') -}); interface CreateTeamTypeModalProps { open: boolean; diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx index 32886618a9..b4a856d95d 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx @@ -9,6 +9,8 @@ import { Box } from '@mui/system'; import HelpIcon from '@mui/icons-material/Help'; import { TeamType } from 'shared'; import { CreateTeamTypePayload } from '../../../hooks/team-types.hooks'; +import useFormPersist from 'react-hook-form-persist'; +import { FormStorageKey } from '../../../utils/form'; interface TeamTypeFormModalProps { open: boolean; @@ -23,7 +25,7 @@ const schema = yup.object().shape({ description: yup.string().required('Description is Required') }); -const CreateTeamTypeModal: React.FC = ({ open, handleClose, defaulValues, onSubmit }) => { +const TeamTypeFormModal: React.FC = ({ open, handleClose, defaulValues, onSubmit }) => { const toast = useToast(); const onFormSubmit = async (data: CreateTeamTypePayload) => { @@ -41,7 +43,9 @@ const CreateTeamTypeModal: React.FC = ({ open, handleClo handleSubmit, control, reset, - formState: { errors } + formState: { errors }, + watch, + setValue } = useForm({ resolver: yupResolver(schema), defaultValues: { @@ -51,21 +55,34 @@ const CreateTeamTypeModal: React.FC = ({ open, handleClo } }); + const formStorageKey = defaulValues ? FormStorageKey.EDIT_TEAM_TYPE : FormStorageKey.CREATE_TEAM_TYPE; + + useFormPersist(formStorageKey, { + watch, + setValue + }); + const TooltipMessage = () => ( Click to view possible icon names. For names with multiple words, seperate them with an _. AttachMoney = attach_money ); + const handleCancel = () => { + reset({ name: '', iconName: '', description: '' }); + sessionStorage.removeItem(formStorageKey); + handleClose(); + } + return ( reset({ name: '', iconName: '', description: '' })} handleUseFormSubmit={handleSubmit} onFormSubmit={onFormSubmit} - formId="new-team-type-form" + formId="team-type-form" showCloseButton > @@ -101,4 +118,4 @@ const CreateTeamTypeModal: React.FC = ({ open, handleClo ); }; -export default CreateTeamTypeModal; +export default TeamTypeFormModal; diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx index 172329e05f..17e488e3bf 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx @@ -7,7 +7,7 @@ import CreateTeamTypeModal from './CreateTeamTypeFormModal'; import { TeamType } from 'shared'; import EditTeamTypeFormModal from './EditTeamTypeFormModal'; import { useAllTeamTypes } from '../../../hooks/team-types.hooks'; -import { useState } from 'react'; +import { useHistoryState } from '../../../hooks/misc.hooks'; const TeamTypeTable: React.FC = () => { const { @@ -16,8 +16,8 @@ const TeamTypeTable: React.FC = () => { isError: teamTypesIsError, error: teamTypesError } = useAllTeamTypes(); - const [createModalShow, setCreateModalShow] = useState(false); - const [editingTeamType, setEditingTeamType] = useState(); + const [createModalShow, setCreateModalShow] = useHistoryState('', false); + const [editingTeamType, setEditingTeamType] = useHistoryState('', undefined); if (!teamTypes || teamTypesIsLoading) { return ; diff --git a/src/frontend/src/utils/form.ts b/src/frontend/src/utils/form.ts index 17438fba53..23875ae5fb 100644 --- a/src/frontend/src/utils/form.ts +++ b/src/frontend/src/utils/form.ts @@ -88,5 +88,6 @@ export const generateUUID = () => { }; export enum FormStorageKey { - CREATE_TEAM_TYPE = 'CREATE_TEAM_TYPE' + CREATE_TEAM_TYPE = 'CREATE_TEAM_TYPE', + EDIT_TEAM_TYPE = 'EDIT_TEAM_TYPE' } From 0d35159e92b01a661cd46196b0c6d9d521980436 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Tue, 30 Jul 2024 10:17:35 -0400 Subject: [PATCH 027/264] #2708 prettier --- .../src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx index b4a856d95d..83e913b3c8 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx @@ -72,7 +72,7 @@ const TeamTypeFormModal: React.FC = ({ open, handleClose reset({ name: '', iconName: '', description: '' }); sessionStorage.removeItem(formStorageKey); handleClose(); - } + }; return ( Date: Wed, 31 Jul 2024 00:06:59 -0400 Subject: [PATCH 028/264] #2735 updated endpoint to use proper form data --- .../src/controllers/teams.controllers.ts | 50 ++++------ src/backend/src/prisma/seed.ts | 3 + src/backend/src/routes/teams.routes.ts | 8 +- src/backend/src/services/teams.services.ts | 36 ++----- src/backend/tests/unmocked/team-type.test.ts | 95 +++++-------------- src/frontend/src/apis/team-types.api.ts | 28 +++--- src/frontend/src/hooks/team-types.hooks.ts | 51 ++++------ .../TeamConfig/TeamTypeFormModal.tsx | 33 ++----- src/shared/src/types/design-review-types.ts | 2 +- 9 files changed, 101 insertions(+), 205 deletions(-) diff --git a/src/backend/src/controllers/teams.controllers.ts b/src/backend/src/controllers/teams.controllers.ts index c9d174fe7e..5dfb751433 100644 --- a/src/backend/src/controllers/teams.controllers.ts +++ b/src/backend/src/controllers/teams.controllers.ts @@ -132,13 +132,29 @@ export default class TeamsController { } } + static async setTeamType(req: Request, res: Response, next: NextFunction) { + try { + const { teamTypeId } = req.body; + const { teamId } = req.params; + const submitter = await getCurrentUser(res); + const organizationId = getOrganizationId(req.headers); + + const updatedTeam = await TeamsService.setTeamType(submitter, teamId, teamTypeId, organizationId); + + res.status(200).json(updatedTeam); + } catch (error: unknown) { + next(error); + } + } + static async createTeamType(req: Request, res: Response, next: NextFunction) { try { const { name, iconName, description } = req.body; const submitter = await getCurrentUser(res); const organizationId = getOrganizationId(req.headers); + const file = req.file ? req.file as Express.Multer.File : null; - const createdTeamType = await TeamsService.createTeamType(submitter, name, iconName, description, organizationId); + const createdTeamType = await TeamsService.createTeamType(submitter, name, iconName, description, file ? file.path : null, organizationId); res.status(200).json(createdTeamType); } catch (error: unknown) { next(error); @@ -174,6 +190,7 @@ export default class TeamsController { const { name, iconName, description } = req.body; const user = await getCurrentUser(res); const organizationId = getOrganizationId(req.headers); + const file = req.file ? req.file as Express.Multer.File : null; const teamType = await TeamsService.editTeamType( user, @@ -181,6 +198,7 @@ export default class TeamsController { name, iconName, description, + file ? file.path : null, organizationId ); res.status(200).json(teamType); @@ -188,34 +206,4 @@ export default class TeamsController { next(error); } } - - static async setTeamTypeImage(req: Request, res: Response, next: NextFunction) { - try { - const file = req.file as Express.Multer.File; - const submitter = await getCurrentUser(res); - const organizationId = getOrganizationId(req.headers); - const { teamTypeId } = req.params; - - const newImages = await TeamsService.setTeamTypeImage(file, submitter, organizationId, teamTypeId); - - res.status(200).json(newImages); - } catch (error: unknown) { - next(error); - } - } - - static async setTeamType(req: Request, res: Response, next: NextFunction) { - try { - const { teamTypeId } = req.body; - const { teamId } = req.params; - const submitter = await getCurrentUser(res); - const organizationId = getOrganizationId(req.headers); - - const updatedTeam = await TeamsService.setTeamType(submitter, teamId, teamTypeId, organizationId); - - res.status(200).json(updatedTeam); - } catch (error: unknown) { - next(error); - } - } } diff --git a/src/backend/src/prisma/seed.ts b/src/backend/src/prisma/seed.ts index 6105408506..0451a00cde 100644 --- a/src/backend/src/prisma/seed.ts +++ b/src/backend/src/prisma/seed.ts @@ -248,6 +248,7 @@ const performSeed: () => Promise = async () => { 'Mechanical', 'YouTubeIcon', 'Mechanical team', + null, organizationId ); const teamType2 = await TeamsService.createTeamType( @@ -255,6 +256,7 @@ const performSeed: () => Promise = async () => { 'Software', 'InstagramIcon', 'Software team', + null, organizationId ); const teamType3 = await TeamsService.createTeamType( @@ -262,6 +264,7 @@ const performSeed: () => Promise = async () => { 'Electrical', 'SettingsIcon', 'Electrical team', + null, organizationId ); diff --git a/src/backend/src/routes/teams.routes.ts b/src/backend/src/routes/teams.routes.ts index f180762563..546ed0458f 100644 --- a/src/backend/src/routes/teams.routes.ts +++ b/src/backend/src/routes/teams.routes.ts @@ -50,11 +50,14 @@ teamsRouter.get('/teamType/all', TeamsController.getAllTeamTypes); teamsRouter.get('/teamType/:teamTypeId/single', TeamsController.getSingleTeamType); +teamsRouter.post('/:teamId/set-team-type', nonEmptyString(body('teamTypeId')), validateInputs, TeamsController.setTeamType); + teamsRouter.post( '/teamType/create', nonEmptyString(body('name')), nonEmptyString(body('iconName')), nonEmptyString(body('description')), + upload.single('image'), validateInputs, TeamsController.createTeamType ); @@ -64,12 +67,9 @@ teamsRouter.post( nonEmptyString(body('name')), nonEmptyString(body('iconName')), nonEmptyString(body('description')), + upload.single('image'), validateInputs, TeamsController.editTeamType ); -teamsRouter.post('/teamType/:teamTypeId/edit-image', upload.single('image'), TeamsController.setTeamTypeImage); - -teamsRouter.post('/:teamId/set-team-type', nonEmptyString(body('teamTypeId')), validateInputs, TeamsController.setTeamType); - export default teamsRouter; diff --git a/src/backend/src/services/teams.services.ts b/src/backend/src/services/teams.services.ts index b7e8dadffc..bda87f7a17 100644 --- a/src/backend/src/services/teams.services.ts +++ b/src/backend/src/services/teams.services.ts @@ -372,7 +372,8 @@ export default class TeamsService { name: string, iconName: string, description: string, - organizationId: string + filePath: string | null, + organizationId: string, ): Promise { if (!(await userHasPermission(submitter.userId, organizationId, isAdmin))) { throw new AccessDeniedAdminOnlyException('create a team type'); @@ -391,6 +392,7 @@ export default class TeamsService { name, iconName, description, + imageFileId: filePath, organizationId } }); @@ -442,6 +444,7 @@ export default class TeamsService { name: string, iconName: string, description: string, + filePath: string | null, organizationId: string ): Promise { if (!isUnderWordCount(description, 300)) throw new HttpException(400, 'Description must be less than 300 words'); @@ -449,42 +452,23 @@ export default class TeamsService { if (!(await userHasPermission(user.userId, organizationId, isAdmin))) throw new AccessDeniedException('you must be an admin to edit the team types description'); + const currentTeamType = await prisma.team_Type.findUnique({ + where: { teamTypeId } + }); + const updatedTeamType = await prisma.team_Type.update({ where: { teamTypeId }, data: { name, iconName, - description + description, + imageFileId: filePath ? filePath : currentTeamType?.imageFileId } }); return updatedTeamType; } - /** - * Sets team type image - * @param organizationId The organization the user is currently in - * @param teamTypeId The team type that is being updated - * @param image The image that is being placed in the team type - * @param submitter The user that is making the change - * @returns the updated team type - */ - static async setTeamTypeImage(image: Express.Multer.File, submitter: User, organizationId: string, teamTypeId: string) { - if (!(await userHasPermission(submitter.userId, organizationId, isAdmin))) - throw new AccessDeniedAdminOnlyException('update images'); - - const imageData = await uploadFile(image); - - const newTeamType = await prisma.team_Type.update({ - where: { teamTypeId }, - data: { - imageFileId: imageData.id - } - }); - - return newTeamType; - } - /** * Sets the teamType for a team * @param submitter the user who is setting the team type diff --git a/src/backend/tests/unmocked/team-type.test.ts b/src/backend/tests/unmocked/team-type.test.ts index 0c042ceebe..0e124cb6ea 100644 --- a/src/backend/tests/unmocked/team-type.test.ts +++ b/src/backend/tests/unmocked/team-type.test.ts @@ -25,75 +25,6 @@ describe('Team Type Tests', () => { await resetUsers(); }); - describe('Set Image', () => { - const TEST_FILE = { originalname: 'image1.png' } as Express.Multer.File; - - it('Fails if user is not an admin', async () => { - const teamType = await TeamsService.createTeamType( - await createTestUser(supermanAdmin, orgId), - 'teamType1', - 'YouTubeIcon', - '', - orgId - ); - - await expect( - TeamsService.setTeamTypeImage(TEST_FILE, await createTestUser(wonderwomanGuest, orgId), orgId, teamType.teamTypeId) - ).rejects.toThrow(new AccessDeniedAdminOnlyException('update images')); - }); - - it('Fails if an organization does not exist', async () => { - const teamType = await TeamsService.createTeamType( - await createTestUser(supermanAdmin, orgId), - 'teamType1', - 'YouTubeIcon', - '', - orgId - ); - - await expect( - TeamsService.setTeamTypeImage(TEST_FILE, await createTestUser(batmanAppAdmin, orgId), '1', teamType.teamTypeId) - ).rejects.toThrow(new HttpException(400, `Organization with id: 1 not found!`)); - }); - - it('Succeeds and updates all the images', async () => { - const testBatman = await createTestUser(batmanAppAdmin, orgId); - const OTHER_FILE = { originalname: 'image2.png' } as Express.Multer.File; - const teamType = await TeamsService.createTeamType( - await createTestUser(supermanAdmin, orgId), - 'teamType1', - 'YouTubeIcon', - '', - orgId - ); - - (uploadFile as Mock).mockImplementation((file) => { - return Promise.resolve({ id: `uploaded-${file.originalname}` }); - }); - - await TeamsService.setTeamTypeImage(TEST_FILE, testBatman, orgId, teamType.teamTypeId); - - const organization = await prisma.team_Type.findUnique({ - where: { - teamTypeId: teamType.teamTypeId - } - }); - - expect(organization).not.toBeNull(); - expect(organization?.imageFileId).toBe('uploaded-image1.png'); - - await TeamsService.setTeamTypeImage(OTHER_FILE, testBatman, orgId, teamType.teamTypeId); - - const updatedTeamType = await prisma.team_Type.findUnique({ - where: { - teamTypeId: teamType.teamTypeId - } - }); - - expect(updatedTeamType?.imageFileId).toBe('uploaded-image2.png'); - }); - }); - describe('Create Team Type', () => { it('Create team type fails if user is not an admin', async () => { await expect( @@ -103,13 +34,21 @@ describe('Team Type Tests', () => { 'Team 2', 'Warning icon', '', + null, orgId ) ).rejects.toThrow(new AccessDeniedAdminOnlyException('create a team type')); }); it('Create team type fails if there is already another team type with the same name', async () => { - await TeamsService.createTeamType(await createTestUser(supermanAdmin, orgId), 'teamType1', 'YouTubeIcon', '', orgId); + await TeamsService.createTeamType( + await createTestUser(supermanAdmin, orgId), + 'teamType1', + 'YouTubeIcon', + '', + null, + orgId + ); await expect( async () => await TeamsService.createTeamType( @@ -117,6 +56,7 @@ describe('Team Type Tests', () => { 'teamType1', 'Warning icon', '', + null, orgId ) ).rejects.toThrow(new HttpException(400, 'Cannot create a teamType with a name that already exists')); @@ -128,6 +68,7 @@ describe('Team Type Tests', () => { 'teamType3', 'YouTubeIcon', '', + null, orgId ); @@ -142,13 +83,14 @@ describe('Team Type Tests', () => { }); }); - describe('Get all team types works', () => { + describe('Get all team types', () => { it('Get all team types works', async () => { const teamType1 = await TeamsService.createTeamType( await createTestUser(supermanAdmin, orgId), 'teamType1', 'YouTubeIcon', '', + null, orgId ); const teamType2 = await TeamsService.createTeamType( @@ -156,6 +98,7 @@ describe('Team Type Tests', () => { 'teamType2', 'WarningIcon', '', + null, orgId ); const result = await TeamsService.getAllTeamTypes(orgId); @@ -170,6 +113,7 @@ describe('Team Type Tests', () => { 'teamType1', 'YouTubeIcon', '', + null, orgId ); const result = await TeamsService.getSingleTeamType(teamType1.teamTypeId, orgId); @@ -184,7 +128,7 @@ describe('Team Type Tests', () => { }); }); - describe('Edit team type description', () => { + describe('Edit team type', () => { it('fails if user is not an admin', async () => { await expect( async () => @@ -194,6 +138,7 @@ describe('Team Type Tests', () => { 'new name', 'new icon', 'new description', + null, orgId ) ).rejects.toThrow(new AccessDeniedException('you must be an admin to edit the team types description')); @@ -208,17 +153,19 @@ describe('Team Type Tests', () => { 'new name', 'new icon', 'a '.repeat(301), + null, orgId ) ).rejects.toThrow(new HttpException(400, 'Description must be less than 300 words')); }); - it('succeds and updates the description', async () => { + it('succeds and updates the team type', async () => { const teamType = await TeamsService.createTeamType( await createTestUser(supermanAdmin, orgId), 'teamType1', 'YouTubeIcon', '', + null, orgId ); const updatedTeamType = await TeamsService.editTeamType( @@ -227,11 +174,13 @@ describe('Team Type Tests', () => { 'new name', 'new icon', 'new description', + "imageUrl", orgId ); expect(updatedTeamType.name).toBe('new name'); expect(updatedTeamType.iconName).toBe('new icon'); expect(updatedTeamType.description).toBe('new description'); + expect(updatedTeamType.imageFileId).toBe("imageUrl"); }); }); }); diff --git a/src/frontend/src/apis/team-types.api.ts b/src/frontend/src/apis/team-types.api.ts index 3e001aadd0..afc4e6acfa 100644 --- a/src/frontend/src/apis/team-types.api.ts +++ b/src/frontend/src/apis/team-types.api.ts @@ -14,24 +14,30 @@ export const getAllTeamTypes = () => { }); }; -export const createTeamType = async (payload: CreateTeamTypePayload) => { - return axios.post(apiUrls.teamTypesCreate(), payload); -}; - export const setTeamType = (id: string, teamTypeId: string) => { return axios.post<{ message: string }>(apiUrls.teamsSetTeamType(id), { teamTypeId }); }; -export const editTeamType = (id: string, payload: CreateTeamTypePayload) => { - return axios.post(apiUrls.teamTypeEdit(id), { - ...payload - }); +export const createTeamType = async (payload: CreateTeamTypePayload) => { + const formData = new FormData(); + formData.append('name', payload.name); + formData.append('iconName', payload.iconName); + formData.append('description', payload.description); + if (payload.image) { + formData.append('image', payload.image); + } + return axios.post(apiUrls.teamTypesCreate(), formData); }; -export const setTeamTypeImage = (file: File, id: string) => { +export const editTeamType = (id: string, payload: CreateTeamTypePayload) => { const formData = new FormData(); - formData.append('image', file); - return axios.post(apiUrls.teamTypeSetImage(id), formData); + formData.append('name', payload.name); + formData.append('iconName', payload.iconName); + formData.append('description', payload.description); + if (payload.image) { + formData.append('image', payload.image); + } + return axios.post(apiUrls.teamTypeEdit(id), formData); }; diff --git a/src/frontend/src/hooks/team-types.hooks.ts b/src/frontend/src/hooks/team-types.hooks.ts index db4a4963f0..76749fe45e 100644 --- a/src/frontend/src/hooks/team-types.hooks.ts +++ b/src/frontend/src/hooks/team-types.hooks.ts @@ -4,7 +4,7 @@ */ import { useQueryClient, useMutation, useQuery } from 'react-query'; -import { createTeamType, editTeamType, getAllTeamTypes, setTeamType, setTeamTypeImage } from '../apis/team-types.api'; +import { createTeamType, editTeamType, getAllTeamTypes, setTeamType } from '../apis/team-types.api'; import { TeamType } from 'shared'; export interface CreateTeamTypePayload { @@ -27,16 +27,17 @@ export const useAllTeamTypes = () => { }; /** - * Custom react hook to create a team type + * Custom react hook to set the team type of a team * - * @returns the team type created + * @param teamId id of the team to set the team type + * @returns the updated team */ -export const useCreateTeamType = () => { +export const useSetTeamType = (teamId: string) => { const queryClient = useQueryClient(); - return useMutation( - ['team types', 'create'], - async (teamTypePayload) => { - const { data } = await createTeamType(teamTypePayload); + return useMutation<{ message: string }, Error, string>( + ['team types', 'edit'], + async (teamTypeId: string) => { + const { data } = await setTeamType(teamId, teamTypeId); return data; }, { @@ -48,17 +49,16 @@ export const useCreateTeamType = () => { }; /** - * Custom react hook to set the team type of a team + * Custom react hook to create a team type * - * @param teamId id of the team to set the team type - * @returns the updated team + * @returns the team type created */ -export const useSetTeamType = (teamId: string) => { +export const useCreateTeamType = () => { const queryClient = useQueryClient(); - return useMutation<{ message: string }, Error, string>( - ['team types', 'edit'], - async (teamTypeId: string) => { - const { data } = await setTeamType(teamId, teamTypeId); + return useMutation( + ['team types', 'create'], + async (teamTypePayload) => { + const { data } = await createTeamType(teamTypePayload); return data; }, { @@ -90,22 +90,3 @@ export const useEditTeamType = (teamTypeId: string) => { } ); }; - -/** - * Custome react hook to update a team type - * - * @param teamTypeId id of the team type to edit - * @returns the updated team type - */ -/** - * Custom React Hook to upload a new picture. - */ -export const useSetTeamTypeImage = () => { - return useMutation<{ googleFileId: string; name: string }, Error, { file: File; id: string }>( - ['team types', 'edit'], - async (formData: { file: File; id: string }) => { - const { data } = await setTeamTypeImage(formData.file, formData.id); - return data; - } - ); -}; \ No newline at end of file diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx index b56597a084..b90adc7f1b 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx @@ -8,7 +8,7 @@ import { yupResolver } from '@hookform/resolvers/yup'; import { Box } from '@mui/system'; import HelpIcon from '@mui/icons-material/Help'; import { TeamType } from 'shared'; -import { CreateTeamTypePayload, useSetTeamTypeImage } from '../../../hooks/team-types.hooks'; +import { CreateTeamTypePayload } from '../../../hooks/team-types.hooks'; import React, { useEffect, useState } from 'react'; import FileUploadIcon from '@mui/icons-material/FileUpload'; import LoadingIndicator from '../../../components/LoadingIndicator'; @@ -25,30 +25,16 @@ interface TeamTypeFormModalProps { const schema = yup.object().shape({ name: yup.string().required('Material Type is Required'), iconName: yup.string().required('Icon Name is Required'), - description: yup.string().required('Description is Required'), + description: yup.string().required('Description is Required') }); const TeamTypeFormModal: React.FC = ({ open, handleClose, defaulValues, onSubmit }) => { const toast = useToast(); - const { isLoading: setTeamTypeIsLoading, mutateAsync: setImage } = useSetTeamTypeImage(); - - const [imagePreview, setImagePreview] = useState(null); - - useEffect(() => { - if (defaulValues?.image) { - setImagePreview(defaulValues.image); - } - }, [defaulValues]); - - if (setTeamTypeIsLoading) return ; + const [addedImage, setAddedImage] = useState(); const onFormSubmit = async (data: CreateTeamTypePayload) => { try { - const { teamTypeId } = await onSubmit(data); - - if (data.image) { - await setImage({ file: data.image, id: teamTypeId }); - } + await onSubmit(data); } catch (error: unknown) { if (error instanceof Error) { toast.error(error.message); @@ -70,7 +56,7 @@ const TeamTypeFormModal: React.FC = ({ open, handleClose name: defaulValues?.name ?? '', iconName: defaulValues?.iconName ?? '', description: defaulValues?.description ?? '', - image: defaulValues?.image ?? null + image: defaulValues?.imageFileId ?? null } }); @@ -135,9 +121,7 @@ const TeamTypeFormModal: React.FC = ({ open, handleClose Image - {imagePreview && ( - - )} + {addedImage && {addedImage.name}} {errors.image?.message} diff --git a/src/shared/src/types/design-review-types.ts b/src/shared/src/types/design-review-types.ts index e0588f4c27..e879882f72 100644 --- a/src/shared/src/types/design-review-types.ts +++ b/src/shared/src/types/design-review-types.ts @@ -37,5 +37,5 @@ export interface TeamType { name: string; iconName: string; description: string; - image?: string | null; + imageFileId: string | null; } From 7fc50bd8250ad9a0a22f7d744516ab857d207995 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Wed, 31 Jul 2024 00:14:55 -0400 Subject: [PATCH 029/264] #2708 prettier linting, tsc, and tests --- src/backend/src/controllers/teams.controllers.ts | 13 ++++++++++--- src/backend/src/services/teams.services.ts | 3 +-- src/backend/tests/test-utils.ts | 1 + src/backend/tests/unmocked/team-type.test.ts | 8 +++----- .../AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx | 3 +-- .../AdminToolsPage/TeamConfig/TeamTypeTable.tsx | 4 ++-- .../test-support/test-data/design-reviews.stub.ts | 1 + src/frontend/src/utils/urls.ts | 2 +- 8 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/backend/src/controllers/teams.controllers.ts b/src/backend/src/controllers/teams.controllers.ts index 5dfb751433..1bcf3f9bf5 100644 --- a/src/backend/src/controllers/teams.controllers.ts +++ b/src/backend/src/controllers/teams.controllers.ts @@ -152,9 +152,16 @@ export default class TeamsController { const { name, iconName, description } = req.body; const submitter = await getCurrentUser(res); const organizationId = getOrganizationId(req.headers); - const file = req.file ? req.file as Express.Multer.File : null; + const file = req.file ? (req.file as Express.Multer.File) : null; - const createdTeamType = await TeamsService.createTeamType(submitter, name, iconName, description, file ? file.path : null, organizationId); + const createdTeamType = await TeamsService.createTeamType( + submitter, + name, + iconName, + description, + file ? file.path : null, + organizationId + ); res.status(200).json(createdTeamType); } catch (error: unknown) { next(error); @@ -190,7 +197,7 @@ export default class TeamsController { const { name, iconName, description } = req.body; const user = await getCurrentUser(res); const organizationId = getOrganizationId(req.headers); - const file = req.file ? req.file as Express.Multer.File : null; + const file = req.file ? (req.file as Express.Multer.File) : null; const teamType = await TeamsService.editTeamType( user, diff --git a/src/backend/src/services/teams.services.ts b/src/backend/src/services/teams.services.ts index bda87f7a17..08a7bda56b 100644 --- a/src/backend/src/services/teams.services.ts +++ b/src/backend/src/services/teams.services.ts @@ -13,7 +13,6 @@ import { getPrismaQueryUserIds, getUsers, userHasPermission } from '../utils/use import { isUnderWordCount } from 'shared'; import { removeUsersFromList } from '../utils/teams.utils'; import { getTeamQueryArgs } from '../prisma-query-args/teams.query-args'; -import { uploadFile } from '../utils/google-integration.utils'; export default class TeamsService { /** @@ -373,7 +372,7 @@ export default class TeamsService { iconName: string, description: string, filePath: string | null, - organizationId: string, + organizationId: string ): Promise { if (!(await userHasPermission(submitter.userId, organizationId, isAdmin))) { throw new AccessDeniedAdminOnlyException('create a team type'); diff --git a/src/backend/tests/test-utils.ts b/src/backend/tests/test-utils.ts index fdd0894c81..dda34f70d4 100644 --- a/src/backend/tests/test-utils.ts +++ b/src/backend/tests/test-utils.ts @@ -334,6 +334,7 @@ export const createTestDesignReview = async () => { 'Team1', 'Software', 'Software team', + null, organization.organizationId ); const { designReviewId } = await DesignReviewsService.createDesignReview( diff --git a/src/backend/tests/unmocked/team-type.test.ts b/src/backend/tests/unmocked/team-type.test.ts index 0e124cb6ea..5f08516c60 100644 --- a/src/backend/tests/unmocked/team-type.test.ts +++ b/src/backend/tests/unmocked/team-type.test.ts @@ -1,4 +1,3 @@ -import prisma from '../../src/prisma/prisma'; import TeamsService from '../../src/services/teams.services'; import { AccessDeniedAdminOnlyException, @@ -6,10 +5,9 @@ import { HttpException, NotFoundException } from '../../src/utils/errors.utils'; -import { uploadFile } from '../../src/utils/google-integration.utils'; import { batmanAppAdmin, supermanAdmin, wonderwomanGuest } from '../test-data/users.test-data'; import { createTestOrganization, createTestUser, resetUsers } from '../test-utils'; -import { Mock, vi } from 'vitest'; +import { vi } from 'vitest'; vi.mock('../../src/utils/google-integration.utils', () => ({ uploadFile: vi.fn() @@ -174,13 +172,13 @@ describe('Team Type Tests', () => { 'new name', 'new icon', 'new description', - "imageUrl", + 'imageUrl', orgId ); expect(updatedTeamType.name).toBe('new name'); expect(updatedTeamType.iconName).toBe('new icon'); expect(updatedTeamType.description).toBe('new description'); - expect(updatedTeamType.imageFileId).toBe("imageUrl"); + expect(updatedTeamType.imageFileId).toBe('imageUrl'); }); }); }); diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx index b90adc7f1b..0cafc185ed 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx @@ -9,9 +9,8 @@ import { Box } from '@mui/system'; import HelpIcon from '@mui/icons-material/Help'; import { TeamType } from 'shared'; import { CreateTeamTypePayload } from '../../../hooks/team-types.hooks'; -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import FileUploadIcon from '@mui/icons-material/FileUpload'; -import LoadingIndicator from '../../../components/LoadingIndicator'; import useFormPersist from 'react-hook-form-persist'; import { FormStorageKey } from '../../../utils/form'; diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx index af12407ce7..ab86147058 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx @@ -45,10 +45,10 @@ const TeamTypeTable: React.FC = () => { - {teamType.image && ( + {teamType.imageFileId && ( diff --git a/src/frontend/src/tests/test-support/test-data/design-reviews.stub.ts b/src/frontend/src/tests/test-support/test-data/design-reviews.stub.ts index 4fbd5f05a3..f95b48b9a7 100644 --- a/src/frontend/src/tests/test-support/test-data/design-reviews.stub.ts +++ b/src/frontend/src/tests/test-support/test-data/design-reviews.stub.ts @@ -11,6 +11,7 @@ export const teamType1: TeamType = { teamTypeId: '1', iconName: 'YouTubeIcon', description: '', + imageFileId: null, name: 'teamType1' }; diff --git a/src/frontend/src/utils/urls.ts b/src/frontend/src/utils/urls.ts index d17768751b..73349aafe3 100644 --- a/src/frontend/src/utils/urls.ts +++ b/src/frontend/src/utils/urls.ts @@ -245,7 +245,7 @@ export const apiUrls = { teamTypesCreate, teamTypeEdit, teamTypeSetImage, - + descriptionBulletsCheck, descriptionBulletTypes, editDescriptionBulletType, From 8cd687e2fca1abdc088c692cd25804cd34257c05 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Thu, 1 Aug 2024 11:42:15 -0400 Subject: [PATCH 030/264] #2708 spelling and naming fixes --- src/backend/src/services/teams.services.ts | 13 ++-- .../TeamConfig/CreateTeamTypeFormModal.tsx | 6 +- .../TeamConfig/EditTeamTypeFormModal.tsx | 2 +- .../TeamConfig/TeamTypeFormModal.tsx | 67 ++++++++----------- .../TeamConfig/TeamTypeTable.tsx | 4 +- 5 files changed, 44 insertions(+), 48 deletions(-) diff --git a/src/backend/src/services/teams.services.ts b/src/backend/src/services/teams.services.ts index 08a7bda56b..9b74240bda 100644 --- a/src/backend/src/services/teams.services.ts +++ b/src/backend/src/services/teams.services.ts @@ -371,7 +371,7 @@ export default class TeamsService { name: string, iconName: string, description: string, - filePath: string | null, + imageFileId: string | null, organizationId: string ): Promise { if (!(await userHasPermission(submitter.userId, organizationId, isAdmin))) { @@ -391,7 +391,7 @@ export default class TeamsService { name, iconName, description, - imageFileId: filePath, + imageFileId: imageFileId, organizationId } }); @@ -434,6 +434,7 @@ export default class TeamsService { * @param name the new name for the team * @param iconName the new icon name for the team * @param description the new description for the team + * @param imageFileId the new image for the team * @param organizationId The organization the user is currently in * @returns The team with the new description */ @@ -443,7 +444,7 @@ export default class TeamsService { name: string, iconName: string, description: string, - filePath: string | null, + imageFileId: string | null, organizationId: string ): Promise { if (!isUnderWordCount(description, 300)) throw new HttpException(400, 'Description must be less than 300 words'); @@ -455,13 +456,17 @@ export default class TeamsService { where: { teamTypeId } }); + if (!currentTeamType) { + throw new NotFoundException('Team Type', teamTypeId); + } + const updatedTeamType = await prisma.team_Type.update({ where: { teamTypeId }, data: { name, iconName, description, - imageFileId: filePath ? filePath : currentTeamType?.imageFileId + imageFileId: imageFileId ? imageFileId : currentTeamType.imageFileId } }); diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/CreateTeamTypeFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/CreateTeamTypeFormModal.tsx index 80ad426eeb..a538f9d9a1 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/CreateTeamTypeFormModal.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/CreateTeamTypeFormModal.tsx @@ -3,12 +3,12 @@ import ErrorPage from '../../ErrorPage'; import LoadingIndicator from '../../../components/LoadingIndicator'; import { useCreateTeamType } from '../../../hooks/team-types.hooks'; -interface CreateTeamTypeModalProps { +interface CreateTeamTypeFormModalProps { open: boolean; handleClose: () => void; } -const CreateTeamTypeModal = ({ open, handleClose }: CreateTeamTypeModalProps) => { +const CreateTeamTypeFormModal = ({ open, handleClose }: CreateTeamTypeFormModalProps) => { const { isLoading, isError, error, mutateAsync } = useCreateTeamType(); if (isError) return ; @@ -17,4 +17,4 @@ const CreateTeamTypeModal = ({ open, handleClose }: CreateTeamTypeModalProps) => return ; }; -export default CreateTeamTypeModal; +export default CreateTeamTypeFormModal; diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/EditTeamTypeFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/EditTeamTypeFormModal.tsx index a5e9dafbde..2f3493aff3 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/EditTeamTypeFormModal.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/EditTeamTypeFormModal.tsx @@ -16,7 +16,7 @@ const EditTeamTypeFormModal = ({ open, handleClose, teamType }: EditTeamTypeForm if (isError) return ; if (isLoading) return ; - return ; + return ; }; export default EditTeamTypeFormModal; diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx index 0cafc185ed..7fdafad7ea 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx @@ -1,6 +1,6 @@ import { useForm } from 'react-hook-form'; import NERFormModal from '../../../components/NERFormModal'; -import { FormControl, FormLabel, FormHelperText, Tooltip, Typography, Button } from '@mui/material'; +import { FormControl, FormLabel, FormHelperText, Tooltip, Typography, Button, Link } from '@mui/material'; import ReactHookTextField from '../../../components/ReactHookTextField'; import { useToast } from '../../../hooks/toasts.hooks'; import * as yup from 'yup'; @@ -17,28 +17,23 @@ import { FormStorageKey } from '../../../utils/form'; interface TeamTypeFormModalProps { open: boolean; handleClose: () => void; - defaulValues?: TeamType; + defaultValues?: TeamType; onSubmit: (data: CreateTeamTypePayload) => Promise; } const schema = yup.object().shape({ name: yup.string().required('Material Type is Required'), iconName: yup.string().required('Icon Name is Required'), - description: yup.string().required('Description is Required') + description: yup.string().required('Description is Required'), + image: yup.mixed().notRequired() }); -const TeamTypeFormModal: React.FC = ({ open, handleClose, defaulValues, onSubmit }) => { +const TeamTypeFormModal: React.FC = ({ open, handleClose, defaultValues, onSubmit }) => { const toast = useToast(); const [addedImage, setAddedImage] = useState(); const onFormSubmit = async (data: CreateTeamTypePayload) => { - try { - await onSubmit(data); - } catch (error: unknown) { - if (error instanceof Error) { - toast.error(error.message); - } - } + await onSubmit(data); handleClose(); }; @@ -52,14 +47,14 @@ const TeamTypeFormModal: React.FC = ({ open, handleClose } = useForm({ resolver: yupResolver(schema), defaultValues: { - name: defaulValues?.name ?? '', - iconName: defaulValues?.iconName ?? '', - description: defaulValues?.description ?? '', - image: defaulValues?.imageFileId ?? null + name: defaultValues?.name ?? '', + iconName: defaultValues?.iconName ?? '', + description: defaultValues?.description ?? '', + image: defaultValues?.imageFileId ?? null } }); - const formStorageKey = defaulValues ? FormStorageKey.EDIT_TEAM_TYPE : FormStorageKey.CREATE_TEAM_TYPE; + const formStorageKey = defaultValues ? FormStorageKey.EDIT_TEAM_TYPE : FormStorageKey.CREATE_TEAM_TYPE; useFormPersist(formStorageKey, { watch, @@ -78,6 +73,20 @@ const TeamTypeFormModal: React.FC = ({ open, handleClose handleClose(); }; + const handleFileChange = (e: React.ChangeEvent) => { + console.log('file change'); + const file = e.target.files?.[0]; + if (file) { + console.log('file is present'); + if (file.size < 1000000) { + setAddedImage(file); + console.log('set image'); + } else { + toast.error(`Error uploading ${file.name}; file must be less than 1 MB`, 5000); + } + } + }; + return ( = ({ open, handleClose Icon Name } placement="right"> - - + @@ -120,7 +129,6 @@ const TeamTypeFormModal: React.FC = ({ open, handleClose Image - {addedImage && {addedImage.name}} {errors.image?.message} diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx index ab86147058..9a0518f439 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx @@ -3,7 +3,7 @@ import LoadingIndicator from '../../../components/LoadingIndicator'; import ErrorPage from '../../ErrorPage'; import { NERButton } from '../../../components/NERButton'; import AdminToolTable from '../AdminToolTable'; -import CreateTeamTypeModal from './CreateTeamTypeFormModal'; +import CreateTeamTypeFormModal from './CreateTeamTypeFormModal'; import { TeamType } from 'shared'; import EditTeamTypeFormModal from './EditTeamTypeFormModal'; import { useAllTeamTypes } from '../../../hooks/team-types.hooks'; @@ -59,7 +59,7 @@ const TeamTypeTable: React.FC = () => { return ( - setCreateModalShow(false)} /> + setCreateModalShow(false)} /> {editingTeamType && ( Date: Fri, 2 Aug 2024 11:18:56 -0400 Subject: [PATCH 031/264] #2708 linting --- src/backend/src/services/teams.services.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/src/services/teams.services.ts b/src/backend/src/services/teams.services.ts index 9b74240bda..26fe3eda1f 100644 --- a/src/backend/src/services/teams.services.ts +++ b/src/backend/src/services/teams.services.ts @@ -391,7 +391,7 @@ export default class TeamsService { name, iconName, description, - imageFileId: imageFileId, + imageFileId, organizationId } }); From 29e75cea5a3a567b00190c9a4c6de06258e88a66 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Wed, 14 Aug 2024 11:54:41 -0400 Subject: [PATCH 032/264] #2708 created upload image endpoint --- .../src/controllers/teams.controllers.ts | 51 +++++----- src/backend/src/routes/teams.routes.ts | 9 +- src/backend/src/services/teams.services.ts | 34 ++++++- src/frontend/src/apis/team-types.api.ts | 26 ++--- src/frontend/src/hooks/team-types.hooks.ts | 18 +++- .../TeamConfig/TeamTypeFormModal.tsx | 42 +------- .../TeamConfig/TeamTypeTable.tsx | 97 +++++++++++++++++-- src/frontend/src/utils/urls.ts | 2 +- 8 files changed, 181 insertions(+), 98 deletions(-) diff --git a/src/backend/src/controllers/teams.controllers.ts b/src/backend/src/controllers/teams.controllers.ts index 1bcf3f9bf5..23d7c94178 100644 --- a/src/backend/src/controllers/teams.controllers.ts +++ b/src/backend/src/controllers/teams.controllers.ts @@ -2,6 +2,7 @@ import { NextFunction, Request, Response } from 'express'; import TeamsService from '../services/teams.services'; import { getCurrentUser } from '../utils/auth.utils'; import { getOrganizationId } from '../utils/utils'; +import { HttpException } from '../utils/errors.utils'; export default class TeamsController { static async getAllTeams(req: Request, res: Response, next: NextFunction) { @@ -147,27 +148,6 @@ export default class TeamsController { } } - static async createTeamType(req: Request, res: Response, next: NextFunction) { - try { - const { name, iconName, description } = req.body; - const submitter = await getCurrentUser(res); - const organizationId = getOrganizationId(req.headers); - const file = req.file ? (req.file as Express.Multer.File) : null; - - const createdTeamType = await TeamsService.createTeamType( - submitter, - name, - iconName, - description, - file ? file.path : null, - organizationId - ); - res.status(200).json(createdTeamType); - } catch (error: unknown) { - next(error); - } - } - static async getSingleTeamType(req: Request, res: Response, next: NextFunction) { try { const { teamTypeId } = req.params; @@ -192,12 +172,24 @@ export default class TeamsController { } } + static async createTeamType(req: Request, res: Response, next: NextFunction) { + try { + const { name, iconName, description } = req.body; + const submitter = await getCurrentUser(res); + const organizationId = getOrganizationId(req.headers); + + const createdTeamType = await TeamsService.createTeamType(submitter, name, iconName, description, organizationId); + res.status(200).json(createdTeamType); + } catch (error: unknown) { + next(error); + } + } + static async editTeamType(req: Request, res: Response, next: NextFunction) { try { const { name, iconName, description } = req.body; const user = await getCurrentUser(res); const organizationId = getOrganizationId(req.headers); - const file = req.file ? (req.file as Express.Multer.File) : null; const teamType = await TeamsService.editTeamType( user, @@ -205,7 +197,6 @@ export default class TeamsController { name, iconName, description, - file ? file.path : null, organizationId ); res.status(200).json(teamType); @@ -213,4 +204,18 @@ export default class TeamsController { next(error); } } + + static async setTeamTypeImage(req: Request, res: Response, next: NextFunction) { + try { + const { file } = req; + if (!file) throw new HttpException(400, 'Invalid or undefined image data'); + const user = await getCurrentUser(res); + const organizationId = getOrganizationId(req.headers); + + const teamType = await TeamsService.setTeamTypeImage(user, req.params.teamTypeId, file, organizationId); + res.status(200).json(teamType); + } catch (error: unknown) { + next(error); + } + } } diff --git a/src/backend/src/routes/teams.routes.ts b/src/backend/src/routes/teams.routes.ts index 546ed0458f..4d3cf01b43 100644 --- a/src/backend/src/routes/teams.routes.ts +++ b/src/backend/src/routes/teams.routes.ts @@ -57,7 +57,6 @@ teamsRouter.post( nonEmptyString(body('name')), nonEmptyString(body('iconName')), nonEmptyString(body('description')), - upload.single('image'), validateInputs, TeamsController.createTeamType ); @@ -67,9 +66,15 @@ teamsRouter.post( nonEmptyString(body('name')), nonEmptyString(body('iconName')), nonEmptyString(body('description')), - upload.single('image'), validateInputs, TeamsController.editTeamType ); +teamsRouter.post( + 'teamType/:teamTypeId/set-image', + upload.single('image'), + validateInputs, + TeamsController.setTeamTypeImage +) + export default teamsRouter; diff --git a/src/backend/src/services/teams.services.ts b/src/backend/src/services/teams.services.ts index 26fe3eda1f..c007587395 100644 --- a/src/backend/src/services/teams.services.ts +++ b/src/backend/src/services/teams.services.ts @@ -13,6 +13,7 @@ import { getPrismaQueryUserIds, getUsers, userHasPermission } from '../utils/use import { isUnderWordCount } from 'shared'; import { removeUsersFromList } from '../utils/teams.utils'; import { getTeamQueryArgs } from '../prisma-query-args/teams.query-args'; +import { uploadFile } from '../utils/google-integration.utils'; export default class TeamsService { /** @@ -371,7 +372,6 @@ export default class TeamsService { name: string, iconName: string, description: string, - imageFileId: string | null, organizationId: string ): Promise { if (!(await userHasPermission(submitter.userId, organizationId, isAdmin))) { @@ -391,7 +391,6 @@ export default class TeamsService { name, iconName, description, - imageFileId, organizationId } }); @@ -444,7 +443,6 @@ export default class TeamsService { name: string, iconName: string, description: string, - imageFileId: string | null, organizationId: string ): Promise { if (!isUnderWordCount(description, 300)) throw new HttpException(400, 'Description must be less than 300 words'); @@ -465,8 +463,7 @@ export default class TeamsService { data: { name, iconName, - description, - imageFileId: imageFileId ? imageFileId : currentTeamType.imageFileId + description } }); @@ -513,4 +510,31 @@ export default class TeamsService { return teamTransformer(updatedTeam); } + + static async setTeamTypeImage(submitter: User, teamTypeId: string, image: Express.Multer.File, organizationId: string) { + if (!(await userHasPermission(submitter.userId, organizationId, isAdmin))) { + throw new AccessDeniedAdminOnlyException('set a team types image'); + } + + const teamType = await prisma.team_Type.findUnique({ + where: { + teamTypeId + } + }); + + if (!teamType) throw new NotFoundException('Team Type', teamTypeId); + + const imageData = await uploadFile(image); + + const updatedTeamType = await prisma.team_Type.update({ + where: { + teamTypeId + }, + data: { + imageFileId: imageData.id + } + }); + + return updatedTeamType; + } } diff --git a/src/frontend/src/apis/team-types.api.ts b/src/frontend/src/apis/team-types.api.ts index afc4e6acfa..81555c7e5f 100644 --- a/src/frontend/src/apis/team-types.api.ts +++ b/src/frontend/src/apis/team-types.api.ts @@ -20,24 +20,16 @@ export const setTeamType = (id: string, teamTypeId: string) => { }); }; -export const createTeamType = async (payload: CreateTeamTypePayload) => { - const formData = new FormData(); - formData.append('name', payload.name); - formData.append('iconName', payload.iconName); - formData.append('description', payload.description); - if (payload.image) { - formData.append('image', payload.image); - } - return axios.post(apiUrls.teamTypesCreate(), formData); +export const createTeamType = (payload: CreateTeamTypePayload) => { + return axios.post(apiUrls.teamTypesCreate(), payload); }; export const editTeamType = (id: string, payload: CreateTeamTypePayload) => { - const formData = new FormData(); - formData.append('name', payload.name); - formData.append('iconName', payload.iconName); - formData.append('description', payload.description); - if (payload.image) { - formData.append('image', payload.image); - } - return axios.post(apiUrls.teamTypeEdit(id), formData); + return axios.post(apiUrls.teamTypeEdit(id), payload); }; + +export const setTeamTypeImage = (id: string, image: File) => { + const formData = new FormData(); + formData.append('image', image); + return axios.post(apiUrls.teamTypeSetImage(id), formData); +}; \ No newline at end of file diff --git a/src/frontend/src/hooks/team-types.hooks.ts b/src/frontend/src/hooks/team-types.hooks.ts index 76749fe45e..faaae700fe 100644 --- a/src/frontend/src/hooks/team-types.hooks.ts +++ b/src/frontend/src/hooks/team-types.hooks.ts @@ -4,7 +4,7 @@ */ import { useQueryClient, useMutation, useQuery } from 'react-query'; -import { createTeamType, editTeamType, getAllTeamTypes, setTeamType } from '../apis/team-types.api'; +import { createTeamType, editTeamType, getAllTeamTypes, setTeamType, setTeamTypeImage } from '../apis/team-types.api'; import { TeamType } from 'shared'; export interface CreateTeamTypePayload { @@ -90,3 +90,19 @@ export const useEditTeamType = (teamTypeId: string) => { } ); }; + +export const useSetTeamTypeImage = (teamTypeId: string) => { + const queryClient = useQueryClient(); + return useMutation( + ['team types', 'set image'], + async (image: File) => { + const { data } = await setTeamTypeImage(teamTypeId, image); + return data; + }, + { + onSuccess: () => { + queryClient.invalidateQueries(['team types']); + } + } + ); +}; diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx index 7fdafad7ea..4405ded396 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx @@ -24,14 +24,10 @@ interface TeamTypeFormModalProps { const schema = yup.object().shape({ name: yup.string().required('Material Type is Required'), iconName: yup.string().required('Icon Name is Required'), - description: yup.string().required('Description is Required'), - image: yup.mixed().notRequired() + description: yup.string().required('Description is Required') }); const TeamTypeFormModal: React.FC = ({ open, handleClose, defaultValues, onSubmit }) => { - const toast = useToast(); - const [addedImage, setAddedImage] = useState(); - const onFormSubmit = async (data: CreateTeamTypePayload) => { await onSubmit(data); handleClose(); @@ -49,8 +45,7 @@ const TeamTypeFormModal: React.FC = ({ open, handleClose defaultValues: { name: defaultValues?.name ?? '', iconName: defaultValues?.iconName ?? '', - description: defaultValues?.description ?? '', - image: defaultValues?.imageFileId ?? null + description: defaultValues?.description ?? '' } }); @@ -73,20 +68,6 @@ const TeamTypeFormModal: React.FC = ({ open, handleClose handleClose(); }; - const handleFileChange = (e: React.ChangeEvent) => { - console.log('file change'); - const file = e.target.files?.[0]; - if (file) { - console.log('file is present'); - if (file.size < 1000000) { - setAddedImage(file); - console.log('set image'); - } else { - toast.error(`Error uploading ${file.name}; file must be less than 1 MB`, 5000); - } - } - }; - return ( = ({ open, handleClose {errors.description?.message} - - Image - - {errors.image?.message} - ); }; diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx index 9a0518f439..8e8b9011e2 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx @@ -1,4 +1,4 @@ -import { TableRow, TableCell, Box, Typography, Icon } from '@mui/material'; +import { TableRow, TableCell, Box, Typography, Icon, Button } from '@mui/material'; import LoadingIndicator from '../../../components/LoadingIndicator'; import ErrorPage from '../../ErrorPage'; import { NERButton } from '../../../components/NERButton'; @@ -6,8 +6,10 @@ import AdminToolTable from '../AdminToolTable'; import CreateTeamTypeFormModal from './CreateTeamTypeFormModal'; import { TeamType } from 'shared'; import EditTeamTypeFormModal from './EditTeamTypeFormModal'; -import { useAllTeamTypes } from '../../../hooks/team-types.hooks'; -import { useHistoryState } from '../../../hooks/misc.hooks'; +import { useAllTeamTypes, useSetTeamTypeImage } from '../../../hooks/team-types.hooks'; +import { useState } from 'react'; +import FileUploadIcon from '@mui/icons-material/FileUpload'; +import { useToast } from '../../../hooks/toasts.hooks'; const TeamTypeTable: React.FC = () => { const { @@ -16,8 +18,11 @@ const TeamTypeTable: React.FC = () => { isError: teamTypesIsError, error: teamTypesError } = useAllTeamTypes(); - const [createModalShow, setCreateModalShow] = useHistoryState('', false); - const [editingTeamType, setEditingTeamType] = useHistoryState('', undefined); + + const [createModalShow, setCreateModalShow] = useState(false); + const [editingTeamType, setEditingTeamType] = useState(undefined); + const [addedImage, setAddedImage] = useState(); + const toast = useToast(); if (!teamTypes || teamTypesIsLoading) { return ; @@ -26,10 +31,40 @@ const TeamTypeTable: React.FC = () => { return ; } + const onSubmitTeamTypeImage = async (teamTypeId: string) => { + if (addedImage) { + const { mutateAsync } = useSetTeamTypeImage(teamTypeId); + await mutateAsync(addedImage); + toast.success('Image uploaded successfully!', 5000); + setAddedImage(undefined); + } else { + toast.error('No image selected for upload.', 5000); + } + }; + + const handleFileChange = (e: React.ChangeEvent) => { + console.log('file change'); + const file = e.target.files?.[0]; + if (file) { + console.log('file is present'); + if (file.size < 1000000) { + setAddedImage(file); + // console.log('Image uploaded:', file.name); // Debugging output + } else { + toast.error(`Error uploading ${file.name}; file must be less than 1 MB`, 5000); + } + } + }; + const teamTypesTableRows = teamTypes.map((teamType) => ( - setEditingTeamType(teamType)} sx={{ cursor: 'pointer' }}> - {teamType.name} - + + setEditingTeamType(teamType)} sx={{ cursor: 'pointer', border: '2px solid black' }}> + {teamType.name} + + setEditingTeamType(teamType)} + sx={{ cursor: 'pointer', border: '2px solid black', verticalAlign: 'middle' }} + > {teamType.iconName} @@ -37,7 +72,10 @@ const TeamTypeTable: React.FC = () => { - + setEditingTeamType(teamType)} + sx={{ cursor: 'pointer', border: '2px solid black', verticalAlign: 'middle' }} + > {teamType.description} @@ -53,6 +91,47 @@ const TeamTypeTable: React.FC = () => { sx={{ maxWidth: '100%', maxHeight: '200px', mb: 2 }} /> )} + + {addedImage && ( + + {addedImage.name} + + + + )} )); diff --git a/src/frontend/src/utils/urls.ts b/src/frontend/src/utils/urls.ts index 73349aafe3..fe0318b7c4 100644 --- a/src/frontend/src/utils/urls.ts +++ b/src/frontend/src/utils/urls.ts @@ -89,7 +89,7 @@ const teamTypes = () => `${teams()}/teamType`; const allTeamTypes = () => `${teamTypes()}/all`; const teamTypesCreate = () => `${teamTypes()}/create`; const teamTypeEdit = (id: string) => `${teamTypes()}/${id}/edit`; -const teamTypeSetImage = (id: string) => `${teamTypes()}/${id}/edit-image`; +const teamTypeSetImage = (id: string) => `${teamTypes()}/${id}/set-image`; /**************** Description Bullet Endpoints ****************/ const descriptionBullets = () => `${API_URL}/description-bullets`; From 99dfaf960c4b91123df4027a713effe369164f9a Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Wed, 14 Aug 2024 12:22:09 -0400 Subject: [PATCH 033/264] #2708 updated setTeamTypeImage and fixed github actions --- src/backend/src/prisma/seed.ts | 3 --- src/backend/src/routes/teams.routes.ts | 7 +----- src/backend/tests/unmocked/team-type.test.ts | 22 ++----------------- src/frontend/src/apis/team-types.api.ts | 2 +- .../src/hooks/design-reviews.hooks.ts | 2 +- src/frontend/src/hooks/team-types.hooks.ts | 8 +++---- .../TeamConfig/TeamTypeFormModal.tsx | 5 +---- .../TeamConfig/TeamTypeTable.tsx | 5 +++-- 8 files changed, 13 insertions(+), 41 deletions(-) diff --git a/src/backend/src/prisma/seed.ts b/src/backend/src/prisma/seed.ts index d43b76f075..66501dd68c 100644 --- a/src/backend/src/prisma/seed.ts +++ b/src/backend/src/prisma/seed.ts @@ -249,7 +249,6 @@ const performSeed: () => Promise = async () => { 'Mechanical', 'YouTubeIcon', 'Mechanical team', - null, organizationId ); const teamType2 = await TeamsService.createTeamType( @@ -257,7 +256,6 @@ const performSeed: () => Promise = async () => { 'Software', 'InstagramIcon', 'Software team', - null, organizationId ); const teamType3 = await TeamsService.createTeamType( @@ -265,7 +263,6 @@ const performSeed: () => Promise = async () => { 'Electrical', 'SettingsIcon', 'Electrical team', - null, organizationId ); diff --git a/src/backend/src/routes/teams.routes.ts b/src/backend/src/routes/teams.routes.ts index 4d3cf01b43..c54e4ef6ad 100644 --- a/src/backend/src/routes/teams.routes.ts +++ b/src/backend/src/routes/teams.routes.ts @@ -70,11 +70,6 @@ teamsRouter.post( TeamsController.editTeamType ); -teamsRouter.post( - 'teamType/:teamTypeId/set-image', - upload.single('image'), - validateInputs, - TeamsController.setTeamTypeImage -) +teamsRouter.post('teamType/:teamTypeId/set-image', upload.single('image'), validateInputs, TeamsController.setTeamTypeImage); export default teamsRouter; diff --git a/src/backend/tests/unmocked/team-type.test.ts b/src/backend/tests/unmocked/team-type.test.ts index 5f08516c60..c8e3e039e1 100644 --- a/src/backend/tests/unmocked/team-type.test.ts +++ b/src/backend/tests/unmocked/team-type.test.ts @@ -32,21 +32,13 @@ describe('Team Type Tests', () => { 'Team 2', 'Warning icon', '', - null, orgId ) ).rejects.toThrow(new AccessDeniedAdminOnlyException('create a team type')); }); it('Create team type fails if there is already another team type with the same name', async () => { - await TeamsService.createTeamType( - await createTestUser(supermanAdmin, orgId), - 'teamType1', - 'YouTubeIcon', - '', - null, - orgId - ); + await TeamsService.createTeamType(await createTestUser(supermanAdmin, orgId), 'teamType1', 'YouTubeIcon', '', orgId); await expect( async () => await TeamsService.createTeamType( @@ -54,7 +46,6 @@ describe('Team Type Tests', () => { 'teamType1', 'Warning icon', '', - null, orgId ) ).rejects.toThrow(new HttpException(400, 'Cannot create a teamType with a name that already exists')); @@ -66,15 +57,14 @@ describe('Team Type Tests', () => { 'teamType3', 'YouTubeIcon', '', - null, orgId ); expect(result).toEqual({ name: 'teamType3', iconName: 'YouTubeIcon', - imageFileId: null, description: '', + imageFileId: null, organizationId: orgId, teamTypeId: result.teamTypeId }); @@ -88,7 +78,6 @@ describe('Team Type Tests', () => { 'teamType1', 'YouTubeIcon', '', - null, orgId ); const teamType2 = await TeamsService.createTeamType( @@ -96,7 +85,6 @@ describe('Team Type Tests', () => { 'teamType2', 'WarningIcon', '', - null, orgId ); const result = await TeamsService.getAllTeamTypes(orgId); @@ -111,7 +99,6 @@ describe('Team Type Tests', () => { 'teamType1', 'YouTubeIcon', '', - null, orgId ); const result = await TeamsService.getSingleTeamType(teamType1.teamTypeId, orgId); @@ -136,7 +123,6 @@ describe('Team Type Tests', () => { 'new name', 'new icon', 'new description', - null, orgId ) ).rejects.toThrow(new AccessDeniedException('you must be an admin to edit the team types description')); @@ -151,7 +137,6 @@ describe('Team Type Tests', () => { 'new name', 'new icon', 'a '.repeat(301), - null, orgId ) ).rejects.toThrow(new HttpException(400, 'Description must be less than 300 words')); @@ -163,7 +148,6 @@ describe('Team Type Tests', () => { 'teamType1', 'YouTubeIcon', '', - null, orgId ); const updatedTeamType = await TeamsService.editTeamType( @@ -172,13 +156,11 @@ describe('Team Type Tests', () => { 'new name', 'new icon', 'new description', - 'imageUrl', orgId ); expect(updatedTeamType.name).toBe('new name'); expect(updatedTeamType.iconName).toBe('new icon'); expect(updatedTeamType.description).toBe('new description'); - expect(updatedTeamType.imageFileId).toBe('imageUrl'); }); }); }); diff --git a/src/frontend/src/apis/team-types.api.ts b/src/frontend/src/apis/team-types.api.ts index 81555c7e5f..6b805f109e 100644 --- a/src/frontend/src/apis/team-types.api.ts +++ b/src/frontend/src/apis/team-types.api.ts @@ -32,4 +32,4 @@ export const setTeamTypeImage = (id: string, image: File) => { const formData = new FormData(); formData.append('image', image); return axios.post(apiUrls.teamTypeSetImage(id), formData); -}; \ No newline at end of file +}; diff --git a/src/frontend/src/hooks/design-reviews.hooks.ts b/src/frontend/src/hooks/design-reviews.hooks.ts index fd738ac4dc..c801cc968c 100644 --- a/src/frontend/src/hooks/design-reviews.hooks.ts +++ b/src/frontend/src/hooks/design-reviews.hooks.ts @@ -3,7 +3,7 @@ * See the LICENSE file in the repository root folder for details. */ import { useMutation, useQuery, useQueryClient } from 'react-query'; -import { DesignReview, TeamType, WbsNumber, DesignReviewStatus, AvailabilityCreateArgs } from 'shared'; +import { DesignReview, WbsNumber, DesignReviewStatus, AvailabilityCreateArgs } from 'shared'; import { deleteDesignReview, editDesignReview, diff --git a/src/frontend/src/hooks/team-types.hooks.ts b/src/frontend/src/hooks/team-types.hooks.ts index faaae700fe..e4a6e4fc7b 100644 --- a/src/frontend/src/hooks/team-types.hooks.ts +++ b/src/frontend/src/hooks/team-types.hooks.ts @@ -91,12 +91,12 @@ export const useEditTeamType = (teamTypeId: string) => { ); }; -export const useSetTeamTypeImage = (teamTypeId: string) => { +export const useSetTeamTypeImage = () => { const queryClient = useQueryClient(); - return useMutation( + return useMutation( ['team types', 'set image'], - async (image: File) => { - const { data } = await setTeamTypeImage(teamTypeId, image); + async (formData: { file: File; id: string }) => { + const { data } = await setTeamTypeImage(formData.id, formData.file); return data; }, { diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx index 4405ded396..3a1bac505b 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeFormModal.tsx @@ -1,16 +1,13 @@ import { useForm } from 'react-hook-form'; import NERFormModal from '../../../components/NERFormModal'; -import { FormControl, FormLabel, FormHelperText, Tooltip, Typography, Button, Link } from '@mui/material'; +import { FormControl, FormLabel, FormHelperText, Tooltip, Typography, Link } from '@mui/material'; import ReactHookTextField from '../../../components/ReactHookTextField'; -import { useToast } from '../../../hooks/toasts.hooks'; import * as yup from 'yup'; import { yupResolver } from '@hookform/resolvers/yup'; import { Box } from '@mui/system'; import HelpIcon from '@mui/icons-material/Help'; import { TeamType } from 'shared'; import { CreateTeamTypePayload } from '../../../hooks/team-types.hooks'; -import React, { useState } from 'react'; -import FileUploadIcon from '@mui/icons-material/FileUpload'; import useFormPersist from 'react-hook-form-persist'; import { FormStorageKey } from '../../../utils/form'; diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx index 8e8b9011e2..724ab165cc 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx @@ -24,6 +24,8 @@ const TeamTypeTable: React.FC = () => { const [addedImage, setAddedImage] = useState(); const toast = useToast(); + const { mutateAsync: setTeamTypeImage } = useSetTeamTypeImage(); + if (!teamTypes || teamTypesIsLoading) { return ; } @@ -33,8 +35,7 @@ const TeamTypeTable: React.FC = () => { const onSubmitTeamTypeImage = async (teamTypeId: string) => { if (addedImage) { - const { mutateAsync } = useSetTeamTypeImage(teamTypeId); - await mutateAsync(addedImage); + await setTeamTypeImage({ file: addedImage, id: teamTypeId }); toast.success('Image uploaded successfully!', 5000); setAddedImage(undefined); } else { From caadc143d4ed2f9004c9a102ff366ff39c85bfe9 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Wed, 14 Aug 2024 12:23:08 -0400 Subject: [PATCH 034/264] #2708 removed comment --- .../src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx index 724ab165cc..f3965acffb 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx @@ -50,7 +50,7 @@ const TeamTypeTable: React.FC = () => { console.log('file is present'); if (file.size < 1000000) { setAddedImage(file); - // console.log('Image uploaded:', file.name); // Debugging output + // console.log('Image uploaded:', file.name); } else { toast.error(`Error uploading ${file.name}; file must be less than 1 MB`, 5000); } From 01a9d75261f65eb9e729b8e0c03261f39809231e Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Wed, 14 Aug 2024 12:30:52 -0400 Subject: [PATCH 035/264] #2708 fixed tests --- src/backend/tests/test-utils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/backend/tests/test-utils.ts b/src/backend/tests/test-utils.ts index 13ea3a0df3..8aac95d227 100644 --- a/src/backend/tests/test-utils.ts +++ b/src/backend/tests/test-utils.ts @@ -382,7 +382,6 @@ export const createTestDesignReview = async () => { 'Team1', 'Software', 'Software team', - null, organization.organizationId ); const { designReviewId } = await DesignReviewsService.createDesignReview( From 806426ac1c59c6c607bb2ae37320c89fde0f4451 Mon Sep 17 00:00:00 2001 From: harish Date: Wed, 14 Aug 2024 20:05:09 -0400 Subject: [PATCH 036/264] #1606 fixed bug in archive endpoint --- src/backend/src/routes/teams.routes.ts | 2 +- src/frontend/src/apis/teams.api.ts | 4 +++- src/frontend/src/hooks/teams.hooks.ts | 5 +++++ src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx | 3 ++- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/backend/src/routes/teams.routes.ts b/src/backend/src/routes/teams.routes.ts index e744bb5392..4987332703 100644 --- a/src/backend/src/routes/teams.routes.ts +++ b/src/backend/src/routes/teams.routes.ts @@ -39,7 +39,7 @@ teamsRouter.post( validateInputs, TeamsController.createTeam ); -teamsRouter.post('/:teamId/archive'); +teamsRouter.post('/:teamId/archive', TeamsController.archiveTeam); /**************** Team Type Section ****************/ diff --git a/src/frontend/src/apis/teams.api.ts b/src/frontend/src/apis/teams.api.ts index 392a3ba199..8ebebfabb8 100644 --- a/src/frontend/src/apis/teams.api.ts +++ b/src/frontend/src/apis/teams.api.ts @@ -40,8 +40,10 @@ export const setTeamHead = (id: string, userId: string) => { }; export const archiveTeam = (id: string) => { + console.log(apiUrls.teamsArchive(id)) return axios.post(apiUrls.teamsArchive(id)); -} +}; + export const deleteTeam = (id: string) => { return axios.post<{ message: string }>(apiUrls.teamsDelete(id)); diff --git a/src/frontend/src/hooks/teams.hooks.ts b/src/frontend/src/hooks/teams.hooks.ts index 17864227ce..2fa25594e9 100644 --- a/src/frontend/src/hooks/teams.hooks.ts +++ b/src/frontend/src/hooks/teams.hooks.ts @@ -62,6 +62,11 @@ export const useArchiveTeam = (teamId: string) => { async () => { const { data } = await archiveTeam(teamId); return data; + }, + { + onSuccess: () => { + queryClient.invalidateQueries(['teams']); + } } ) } diff --git a/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx b/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx index c4e1dfcf66..3510dc8e24 100644 --- a/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx +++ b/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx @@ -18,13 +18,14 @@ import DeleteTeamModal from './DeleteTeamModal'; import SetTeamTypeModal from './SetTeamTypeModal'; import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; import { TeamPill } from './TeamPill'; -import { useDeleteDesignReview } from '../../hooks/design-reviews.hooks'; +import { useToast } from '../../hooks/toasts.hooks'; interface ParamTypes { teamId: string; } const TeamSpecificPage: React.FC = () => { + const toast = useToast(); const { teamId } = useParams(); const { isLoading, isError, data, error } = useSingleTeam(teamId); const user = useCurrentUser(); From a6f95f2012ae3d48e012b12113ff01ef0f385814 Mon Sep 17 00:00:00 2001 From: harish Date: Wed, 14 Aug 2024 20:32:25 -0400 Subject: [PATCH 037/264] #1606 prettier --- src/frontend/src/apis/teams.api.ts | 3 +-- src/frontend/src/hooks/teams.hooks.ts | 4 ++-- src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx | 7 +------ 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/frontend/src/apis/teams.api.ts b/src/frontend/src/apis/teams.api.ts index 8ebebfabb8..682208f933 100644 --- a/src/frontend/src/apis/teams.api.ts +++ b/src/frontend/src/apis/teams.api.ts @@ -40,11 +40,10 @@ export const setTeamHead = (id: string, userId: string) => { }; export const archiveTeam = (id: string) => { - console.log(apiUrls.teamsArchive(id)) + console.log(apiUrls.teamsArchive(id)); return axios.post(apiUrls.teamsArchive(id)); }; - export const deleteTeam = (id: string) => { return axios.post<{ message: string }>(apiUrls.teamsDelete(id)); }; diff --git a/src/frontend/src/hooks/teams.hooks.ts b/src/frontend/src/hooks/teams.hooks.ts index 2fa25594e9..bc0a48bfb0 100644 --- a/src/frontend/src/hooks/teams.hooks.ts +++ b/src/frontend/src/hooks/teams.hooks.ts @@ -68,8 +68,8 @@ export const useArchiveTeam = (teamId: string) => { queryClient.invalidateQueries(['teams']); } } - ) -} + ); +}; export const useSetTeamHead = (teamId: string) => { const queryClient = useQueryClient(); diff --git a/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx b/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx index 3510dc8e24..f0d637eb48 100644 --- a/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx +++ b/src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx @@ -78,15 +78,10 @@ const TeamSpecificPage: React.FC = () => { }; const ArchiveTeamButton: React.FC = ({ archive }) => ( - handleArchive()} - disabled={!isAdmin(user.role)} - > + handleArchive()} disabled={!isAdmin(user.role)}> {archive ? 'Unarchive Team' : 'Archive Team'} ); - const TeamActionsDropdown = () => ( From 41ff89a338aba7b9084a9b0261fe4989bcbf69b0 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Tue, 3 Sep 2024 10:54:43 -0400 Subject: [PATCH 038/264] #2708 each upload is for its own specific team type --- .../TeamConfig/TeamTypeTable.tsx | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx index f3965acffb..b93d85a9eb 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx @@ -21,7 +21,7 @@ const TeamTypeTable: React.FC = () => { const [createModalShow, setCreateModalShow] = useState(false); const [editingTeamType, setEditingTeamType] = useState(undefined); - const [addedImage, setAddedImage] = useState(); + const [addedImages, setAddedImages] = useState>({}); const toast = useToast(); const { mutateAsync: setTeamTypeImage } = useSetTeamTypeImage(); @@ -34,23 +34,21 @@ const TeamTypeTable: React.FC = () => { } const onSubmitTeamTypeImage = async (teamTypeId: string) => { - if (addedImage) { - await setTeamTypeImage({ file: addedImage, id: teamTypeId }); + const image = addedImages[teamTypeId]; + if (image) { + await setTeamTypeImage({ file: image, id: teamTypeId }); toast.success('Image uploaded successfully!', 5000); - setAddedImage(undefined); + setAddedImages((prev) => ({ ...prev, [teamTypeId]: undefined })); } else { toast.error('No image selected for upload.', 5000); } }; - const handleFileChange = (e: React.ChangeEvent) => { - console.log('file change'); + const handleFileChange = (e: React.ChangeEvent, teamTypeId: string) => { const file = e.target.files?.[0]; if (file) { - console.log('file is present'); if (file.size < 1000000) { - setAddedImage(file); - // console.log('Image uploaded:', file.name); + setAddedImages((prev) => ({ ...prev, [teamTypeId]: file })); } else { toast.error(`Error uploading ${file.name}; file must be less than 1 MB`, 5000); } @@ -104,17 +102,25 @@ const TeamTypeTable: React.FC = () => { }} > Upload - - handleFileChange(e)} /> - + { + console.log(e); + handleFileChange(e, teamType.teamTypeId); + }} + type="file" + id="team-type-image" + accept="image/*" + name="addedImage" + hidden + /> - {addedImage && ( + {addedImages[teamType.teamTypeId] && ( - {addedImage.name} + {addedImages[teamType.teamTypeId]?.name} + {addedImage && ( + + {addedImage.name} + + + + )} + + ); +}; + +export default NERUploadButton; diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx index b93d85a9eb..a38c64cdbe 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx @@ -1,4 +1,4 @@ -import { TableRow, TableCell, Box, Typography, Icon, Button } from '@mui/material'; +import { TableRow, TableCell, Box, Typography, Icon, Button, FormControl, FormLabel } from '@mui/material'; import LoadingIndicator from '../../../components/LoadingIndicator'; import ErrorPage from '../../ErrorPage'; import { NERButton } from '../../../components/NERButton'; @@ -8,8 +8,8 @@ import { TeamType } from 'shared'; import EditTeamTypeFormModal from './EditTeamTypeFormModal'; import { useAllTeamTypes, useSetTeamTypeImage } from '../../../hooks/team-types.hooks'; import { useState } from 'react'; -import FileUploadIcon from '@mui/icons-material/FileUpload'; import { useToast } from '../../../hooks/toasts.hooks'; +import NERUploadButton from '../../../components/NERUploadButton'; const TeamTypeTable: React.FC = () => { const { @@ -21,7 +21,7 @@ const TeamTypeTable: React.FC = () => { const [createModalShow, setCreateModalShow] = useState(false); const [editingTeamType, setEditingTeamType] = useState(undefined); - const [addedImages, setAddedImages] = useState>({}); + const [addedImage, setAddedImage] = useState(undefined); const toast = useToast(); const { mutateAsync: setTeamTypeImage } = useSetTeamTypeImage(); @@ -34,21 +34,20 @@ const TeamTypeTable: React.FC = () => { } const onSubmitTeamTypeImage = async (teamTypeId: string) => { - const image = addedImages[teamTypeId]; - if (image) { - await setTeamTypeImage({ file: image, id: teamTypeId }); + if (addedImage) { + await setTeamTypeImage({ file: addedImage, id: teamTypeId }); toast.success('Image uploaded successfully!', 5000); - setAddedImages((prev) => ({ ...prev, [teamTypeId]: undefined })); + setAddedImage(undefined); } else { toast.error('No image selected for upload.', 5000); } }; - const handleFileChange = (e: React.ChangeEvent, teamTypeId: string) => { + const handleFileChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { if (file.size < 1000000) { - setAddedImages((prev) => ({ ...prev, [teamTypeId]: file })); + setAddedImage(file); } else { toast.error(`Error uploading ${file.name}; file must be less than 1 MB`, 5000); } @@ -90,55 +89,13 @@ const TeamTypeTable: React.FC = () => { sx={{ maxWidth: '100%', maxHeight: '200px', mb: 2 }} /> )} - - {addedImages[teamType.teamTypeId] && ( - - {addedImages[teamType.teamTypeId]?.name} - - - - )} + )); From 65fa965e6e3f72adb3d4e1e5edc3968eea658903 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Sat, 7 Sep 2024 11:59:45 -0400 Subject: [PATCH 049/264] #2708 linting --- .../src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx index a38c64cdbe..ae5d0a8ac7 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx @@ -1,4 +1,4 @@ -import { TableRow, TableCell, Box, Typography, Icon, Button, FormControl, FormLabel } from '@mui/material'; +import { TableRow, TableCell, Box, Typography, Icon } from '@mui/material'; import LoadingIndicator from '../../../components/LoadingIndicator'; import ErrorPage from '../../ErrorPage'; import { NERButton } from '../../../components/NERButton'; From abfef07ca858700df6601eb6ee103dc98c12a7ed Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Wed, 11 Sep 2024 19:38:57 -0400 Subject: [PATCH 050/264] #2708 holy shit --- .../src/components/NERUploadButton.tsx | 59 +++++++++++-------- .../pages/AdminToolsPage/AdminToolsPage.tsx | 18 ++---- .../TeamConfig/TeamTypeTable.tsx | 19 +++--- 3 files changed, 51 insertions(+), 45 deletions(-) diff --git a/src/frontend/src/components/NERUploadButton.tsx b/src/frontend/src/components/NERUploadButton.tsx index 97a3c7a9b4..80ef395dee 100644 --- a/src/frontend/src/components/NERUploadButton.tsx +++ b/src/frontend/src/components/NERUploadButton.tsx @@ -1,4 +1,4 @@ -import { Button, Typography } from '@mui/material'; +import { Button } from '@mui/material'; import { Box } from '@mui/system'; import React from 'react'; import FileUploadIcon from '@mui/icons-material/FileUpload'; @@ -28,7 +28,6 @@ const NERUploadButton = ({ dataTypeId, handleFileChange, onSubmit, addedImage, s Upload { - console.log('e', e); handleFileChange(e); }} type="file" @@ -38,28 +37,40 @@ const NERUploadButton = ({ dataTypeId, handleFileChange, onSubmit, addedImage, s /> {addedImage && ( - - {addedImage.name} - - + + + + + + )} diff --git a/src/frontend/src/pages/AdminToolsPage/AdminToolsPage.tsx b/src/frontend/src/pages/AdminToolsPage/AdminToolsPage.tsx index 9cdf7d1ad1..adb4582193 100644 --- a/src/frontend/src/pages/AdminToolsPage/AdminToolsPage.tsx +++ b/src/frontend/src/pages/AdminToolsPage/AdminToolsPage.tsx @@ -43,19 +43,6 @@ const AdminToolsPage: React.FC = () => { tabs.push({ tabUrlValue: 'miscellaneous', tabName: 'Miscellaneous' }); } - const UserManagementTab = () => { - return isUserAdmin ? ( - - - - - - - ) : ( - - ); - }; - const ProjectConfigurationTab = () => { return isUserAdmin ? ( <> @@ -84,7 +71,10 @@ const AdminToolsPage: React.FC = () => { } > {tabIndex === 0 ? ( - + <> + + {isUserAdmin && } + ) : tabIndex === 1 ? ( ) : tabIndex === 2 ? ( diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx index ae5d0a8ac7..9e4d45b5ee 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx @@ -21,7 +21,7 @@ const TeamTypeTable: React.FC = () => { const [createModalShow, setCreateModalShow] = useState(false); const [editingTeamType, setEditingTeamType] = useState(undefined); - const [addedImage, setAddedImage] = useState(undefined); + const [addedImages, setAddedImages] = useState<{ [key: string]: File | undefined }>({}); const toast = useToast(); const { mutateAsync: setTeamTypeImage } = useSetTeamTypeImage(); @@ -34,20 +34,21 @@ const TeamTypeTable: React.FC = () => { } const onSubmitTeamTypeImage = async (teamTypeId: string) => { + const addedImage = addedImages[teamTypeId]; if (addedImage) { await setTeamTypeImage({ file: addedImage, id: teamTypeId }); toast.success('Image uploaded successfully!', 5000); - setAddedImage(undefined); + setAddedImages((prev) => ({ ...prev, [teamTypeId]: undefined })); } else { toast.error('No image selected for upload.', 5000); } }; - const handleFileChange = (e: React.ChangeEvent) => { + const handleFileChange = (e: React.ChangeEvent, teamTypeId: string) => { const file = e.target.files?.[0]; if (file) { if (file.size < 1000000) { - setAddedImage(file); + setAddedImages((prev) => ({ ...prev, [teamTypeId]: file })); } else { toast.error(`Error uploading ${file.name}; file must be less than 1 MB`, 5000); } @@ -91,10 +92,14 @@ const TeamTypeTable: React.FC = () => { )} handleFileChange(e, teamType.teamTypeId)} onSubmit={onSubmitTeamTypeImage} - addedImage={addedImage} - setAddedImage={setAddedImage} + addedImage={addedImages[teamType.teamTypeId]} + setAddedImage={(newImage) => + setAddedImages((prev) => { + return { ...prev, [teamType.teamTypeId]: newImage } as { [key: string]: File | undefined }; + }) + } /> From c39f00af66deac4ca9dae3e591acaf999e4f9b37 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Wed, 11 Sep 2024 21:43:15 -0400 Subject: [PATCH 051/264] #2708 works --- src/backend/src/controllers/teams.controllers.ts | 1 - src/backend/src/routes/teams.routes.ts | 7 ++++++- src/backend/src/utils/google-integration.utils.ts | 11 ++++------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/backend/src/controllers/teams.controllers.ts b/src/backend/src/controllers/teams.controllers.ts index f460b83830..e12e36954b 100644 --- a/src/backend/src/controllers/teams.controllers.ts +++ b/src/backend/src/controllers/teams.controllers.ts @@ -188,7 +188,6 @@ export default class TeamsController { try { const { file } = req; if (!file) throw new HttpException(400, 'Invalid or undefined image data'); - const teamType = await TeamsService.setTeamTypeImage(req.currentUser, req.params.teamTypeId, file, req.organization); res.status(200).json(teamType); } catch (error: unknown) { diff --git a/src/backend/src/routes/teams.routes.ts b/src/backend/src/routes/teams.routes.ts index c54e4ef6ad..c75a02f91f 100644 --- a/src/backend/src/routes/teams.routes.ts +++ b/src/backend/src/routes/teams.routes.ts @@ -70,6 +70,11 @@ teamsRouter.post( TeamsController.editTeamType ); -teamsRouter.post('teamType/:teamTypeId/set-image', upload.single('image'), validateInputs, TeamsController.setTeamTypeImage); +teamsRouter.post( + '/teamType/:teamTypeId/set-image', + upload.single('image'), + validateInputs, + TeamsController.setTeamTypeImage +); export default teamsRouter; diff --git a/src/backend/src/utils/google-integration.utils.ts b/src/backend/src/utils/google-integration.utils.ts index 95492860c4..cbb8a90f60 100644 --- a/src/backend/src/utils/google-integration.utils.ts +++ b/src/backend/src/utils/google-integration.utils.ts @@ -122,15 +122,12 @@ export const uploadFile = async (fileObject: Express.Multer.File) => { const gError = error as GoogleDriveError; throw new HttpException( gError.code, - `Failed to Upload Receipt(s): ${gError.message}, ${gError.errors.reduce( - (acc: string, curr: GoogleDriveErrorListError) => { - return acc + ' ' + curr.message + ' ' + curr.reason; - }, - '' - )}` + `Failed to Upload : ${gError.message}, ${gError.errors.reduce((acc: string, curr: GoogleDriveErrorListError) => { + return acc + ' ' + curr.message + ' ' + curr.reason; + }, '')}` ); } else if (error instanceof Error) { - throw new HttpException(500, `Failed to Upload Receipt(s): ${error.message}`); + throw new HttpException(500, `Failed to Upload : ${error.message}`); } console.log('error' + error); throw error; From 256eb3123b00de4eb612e21ed6c80446b3b3b787 Mon Sep 17 00:00:00 2001 From: Jonathan Chen Date: Fri, 13 Sep 2024 11:54:35 -0400 Subject: [PATCH 052/264] #1586 - add date picker to delivered modal frontend --- src/frontend/src/apis/finance.api.ts | 7 +- src/frontend/src/hooks/finance.hooks.ts | 10 +- .../MarkDeliveredModal.tsx | 113 ++++++++++++++++++ .../ReimbursementRequestDetailsView.tsx | 33 +---- 4 files changed, 130 insertions(+), 33 deletions(-) create mode 100644 src/frontend/src/pages/FinancePage/ReimbursementRequestDetailPage/MarkDeliveredModal.tsx diff --git a/src/frontend/src/apis/finance.api.ts b/src/frontend/src/apis/finance.api.ts index 666ad7ebf8..058ca42a3a 100644 --- a/src/frontend/src/apis/finance.api.ts +++ b/src/frontend/src/apis/finance.api.ts @@ -7,7 +7,8 @@ import { EditReimbursementRequestPayload, EditVendorPayload, AccountCodePayload, - RefundPayload + RefundPayload, + MarkDeliveredRequestPayload } from '../hooks/finance.hooks'; import axios from '../utils/axios'; import { apiUrls } from '../utils/urls'; @@ -53,8 +54,8 @@ export const createReimbursementRequest = (formData: CreateReimbursementRequestP * @param id id of the reimbursement request being marked as delivered * @returns the updated reimbursement request */ -export const markReimbursementRequestAsDelivered = (id: string) => { - return axios.post(apiUrls.financeMarkAsDelivered(id)); +export const markReimbursementRequestAsDelivered = (id: string, formData: MarkDeliveredRequestPayload) => { + return axios.post(apiUrls.financeMarkAsDelivered(id), formData); }; /** diff --git a/src/frontend/src/hooks/finance.hooks.ts b/src/frontend/src/hooks/finance.hooks.ts index c3ecbe93e4..56600a26e9 100644 --- a/src/frontend/src/hooks/finance.hooks.ts +++ b/src/frontend/src/hooks/finance.hooks.ts @@ -85,6 +85,10 @@ export interface RefundPayload { dateReceived: string; } +export interface MarkDeliveredRequestPayload { + dateDelivered: Date; +} + /** * Custom React Hook to upload a new picture. */ @@ -232,10 +236,10 @@ export const useAllReimbursements = () => { */ export const useMarkReimbursementRequestAsDelivered = (id: string) => { const queryClient = useQueryClient(); - return useMutation( + return useMutation( ['reimbursement-requests', 'edit'], - async () => { - const { data } = await markReimbursementRequestAsDelivered(id); + async (markDeliveredData: MarkDeliveredRequestPayload) => { + const { data } = await markReimbursementRequestAsDelivered(id, markDeliveredData); return data; }, { diff --git a/src/frontend/src/pages/FinancePage/ReimbursementRequestDetailPage/MarkDeliveredModal.tsx b/src/frontend/src/pages/FinancePage/ReimbursementRequestDetailPage/MarkDeliveredModal.tsx new file mode 100644 index 0000000000..cb6726ca5b --- /dev/null +++ b/src/frontend/src/pages/FinancePage/ReimbursementRequestDetailPage/MarkDeliveredModal.tsx @@ -0,0 +1,113 @@ +import { Controller, useForm } from 'react-hook-form'; +import NERFormModal from '../../../components/NERFormModal'; +import { useMarkReimbursementRequestAsDelivered } from '../../../hooks/finance.hooks'; +import { useToast } from '../../../hooks/toasts.hooks'; +import * as yup from 'yup'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { FormControl, FormControlLabel, FormLabel, Radio, RadioGroup } from '@mui/material'; +import { DatePicker } from '@mui/x-date-pickers'; + +const schema = yup.object().shape({ + dateDelivered: yup.date().required('Must provide delivery date.'), + confirmDelivered: yup + .boolean() + .required('Please confirm items delivered.') + .test('is-true', 'Please confirm', (value) => value === true) +}); + +interface MarkDeliveredModalProps { + modalShow: boolean; + onHide: () => void; + reimbursementRequestId: string; +} + +const MarkDeliveredModal = ({ modalShow, onHide, reimbursementRequestId }: MarkDeliveredModalProps) => { + const toast = useToast(); + const { mutateAsync: markDelivered } = useMarkReimbursementRequestAsDelivered(reimbursementRequestId); + + const dateIsInTheFuture = (date: Date) => { + const now = new Date(); + return date > now; + }; + + const { + handleSubmit, + control, + formState: { errors, isValid }, + reset + } = useForm({ + resolver: yupResolver(schema), + defaultValues: { + dateDelivered: new Date(), + confirmDelivered: false + }, + mode: 'onChange' + }); + + const handleMarkDelivered = async (data: { dateDelivered: Date; confirmDelivered: boolean }) => { + if (!data.confirmDelivered) { + toast.error('Delivery not confirmed!'); + } + try { + await markDelivered({ dateDelivered: data.dateDelivered }); + toast.success('Marked as delivered!'); + onHide(); + } catch (e: unknown) { + if (e instanceof Error) { + toast.error(e.message, 3000); + } + } + }; + + return ( + + + Date Final Item Delivered (MM-DD-YYYY) + ( + onChange(date ?? new Date())} + className={'padding: 10'} + value={value} + shouldDisableDate={dateIsInTheFuture} + slotProps={{ textField: { autoComplete: 'off' } }} + /> + )} + /> + Are you sure the items in this reimbursement request have all been delivered? + ( + + } label="Yes" /> + } label="No" /> + {errors.confirmDelivered ? ( +

Please confirm all items delivered before proceeding.

+ ) : null} +
+ )} + /> +
+
+ ); +}; + +export default MarkDeliveredModal; diff --git a/src/frontend/src/pages/FinancePage/ReimbursementRequestDetailPage/ReimbursementRequestDetailsView.tsx b/src/frontend/src/pages/FinancePage/ReimbursementRequestDetailPage/ReimbursementRequestDetailsView.tsx index 4c6875bb32..d7cf6367a7 100644 --- a/src/frontend/src/pages/FinancePage/ReimbursementRequestDetailPage/ReimbursementRequestDetailsView.tsx +++ b/src/frontend/src/pages/FinancePage/ReimbursementRequestDetailPage/ReimbursementRequestDetailsView.tsx @@ -25,7 +25,6 @@ import { useDenyReimbursementRequest, useLeadershipApproveReimbursementRequest, useMarkPendingFinance, - useMarkReimbursementRequestAsDelivered, useMarkReimbursementRequestAsReimbursed, useRequestReimbursementRequestChanges } from '../../../hooks/finance.hooks'; @@ -56,6 +55,7 @@ import SubmitToSaboModal from './SubmitToSaboModal'; import DownloadIcon from '@mui/icons-material/Download'; import ReimbursementRequestStatusPill from '../../../components/ReimbursementRequestStatusPill'; import CheckList from '../../../components/CheckList'; +import MarkDeliveredModal from './MarkDeliveredModal'; interface ReimbursementRequestDetailsViewProps { reimbursementRequest: ReimbursementRequest; @@ -80,7 +80,6 @@ const ReimbursementRequestDetailsView: React.FC { - try { - await markDelivered(); - setShowMarkDelivered(false); - } catch (e: unknown) { - if (e instanceof Error) { - toast.error(e.message, 3000); - } - } - }; - const handleMarkReimbursed = async () => { try { await markReimbursed(); @@ -225,19 +213,6 @@ const ReimbursementRequestDetailsView: React.FC ( - setShowMarkDelivered(false)} - title="Warning!" - cancelText="No" - submitText="Yes" - onSubmit={handleMarkDelivered} - > - Are you sure the items in this reimbursement request have all been delivered? - - ); - const MarkReimbursedModal = () => ( - + setShowMarkDelivered(false)} + reimbursementRequestId={reimbursementRequest.reimbursementRequestId} + /> From 57313f8cebc85b0a5f195c2e4a8d81b868b03404 Mon Sep 17 00:00:00 2001 From: Jonathan Chen Date: Wed, 18 Sep 2024 20:31:50 -0400 Subject: [PATCH 053/264] #1586 - add formid to delivered modal form --- .../ReimbursementRequestDetailPage/MarkDeliveredModal.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/frontend/src/pages/FinancePage/ReimbursementRequestDetailPage/MarkDeliveredModal.tsx b/src/frontend/src/pages/FinancePage/ReimbursementRequestDetailPage/MarkDeliveredModal.tsx index cb6726ca5b..22d268594a 100644 --- a/src/frontend/src/pages/FinancePage/ReimbursementRequestDetailPage/MarkDeliveredModal.tsx +++ b/src/frontend/src/pages/FinancePage/ReimbursementRequestDetailPage/MarkDeliveredModal.tsx @@ -33,7 +33,7 @@ const MarkDeliveredModal = ({ modalShow, onHide, reimbursementRequestId }: MarkD const { handleSubmit, control, - formState: { errors, isValid }, + formState: { errors }, reset } = useForm({ resolver: yupResolver(schema), @@ -68,6 +68,7 @@ const MarkDeliveredModal = ({ modalShow, onHide, reimbursementRequestId }: MarkD reset={reset} handleUseFormSubmit={handleSubmit} onFormSubmit={handleMarkDelivered} + formId={'confirm-delivered-form'} > Date Final Item Delivered (MM-DD-YYYY) From 240665c2077081fe142ed6f678d6c89f605a54ac Mon Sep 17 00:00:00 2001 From: Jonathan Chen Date: Wed, 18 Sep 2024 20:53:47 -0400 Subject: [PATCH 054/264] #1586 - pass date delivered in backend --- .../src/controllers/reimbursement-requests.controllers.ts | 4 +++- .../src/services/reimbursement-requests.services.ts | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/backend/src/controllers/reimbursement-requests.controllers.ts b/src/backend/src/controllers/reimbursement-requests.controllers.ts index f7e83345dd..8c4eb24398 100644 --- a/src/backend/src/controllers/reimbursement-requests.controllers.ts +++ b/src/backend/src/controllers/reimbursement-requests.controllers.ts @@ -325,11 +325,13 @@ export default class ReimbursementRequestsController { static async markReimbursementRequestAsDelivered(req: Request, res: Response, next: NextFunction) { try { const { requestId } = req.params; + const { dateDelivered } = req.body; const updatedRequest = await ReimbursementRequestService.markReimbursementRequestAsDelivered( req.currentUser, requestId, - req.organization + req.organization, + dateDelivered ); return res.status(200).json(updatedRequest); } catch (error: unknown) { diff --git a/src/backend/src/services/reimbursement-requests.services.ts b/src/backend/src/services/reimbursement-requests.services.ts index 2378208978..a17cc9d26c 100644 --- a/src/backend/src/services/reimbursement-requests.services.ts +++ b/src/backend/src/services/reimbursement-requests.services.ts @@ -761,6 +761,7 @@ export default class ReimbursementRequestService { * @param submitter The User marking the request as delivered * @param requestId The ID of the reimbursement request to be marked as delivered * @param organizationId The organization the user is currently in + * @param dateDelivered The date the reimbursed items were delivered * @throws NotFoundException if the id is invalid or not there * @throws AccessDeniedException if the creator of the request is not the submitter * @returns the updated reimbursement request @@ -768,7 +769,8 @@ export default class ReimbursementRequestService { static async markReimbursementRequestAsDelivered( submitter: User, reimbursementRequestId: string, - organization: Organization + organization: Organization, + dateDelivered: Date ) { const reimbursementRequest = await prisma.reimbursement_Request.findUnique({ where: { reimbursementRequestId } @@ -784,9 +786,7 @@ export default class ReimbursementRequestService { const reimbursementRequestDelivered = await prisma.reimbursement_Request.update({ where: { reimbursementRequestId }, - data: { - dateDelivered: new Date() - } + data: { dateDelivered } }); return reimbursementRequestDelivered; From 0f95561e4e257642cf78d63c1e524d2d7818cc51 Mon Sep 17 00:00:00 2001 From: Jalen Wu Date: Thu, 19 Sep 2024 01:05:12 -0400 Subject: [PATCH 055/264] #2806 created getUpcomingWorkPackages util --- src/frontend/src/utils/work-package.utils.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/frontend/src/utils/work-package.utils.ts b/src/frontend/src/utils/work-package.utils.ts index 31ebcb98fa..e2bc0f1bee 100644 --- a/src/frontend/src/utils/work-package.utils.ts +++ b/src/frontend/src/utils/work-package.utils.ts @@ -1,4 +1,4 @@ -import { WbsElement, wbsPipe } from 'shared'; +import { WbsElement, wbsPipe, WorkPackage } from 'shared'; import { WPFormType } from './form'; export const getTitleFromFormType = (formType: WPFormType, wbsElement: WbsElement): string => { @@ -11,3 +11,10 @@ export const getTitleFromFormType = (formType: WPFormType, wbsElement: WbsElemen return `${wbsPipe(wbsElement.wbsNum)} - ${wbsElement.name}`; } }; + +export const getUpcomingWorkPackages = (workPackages: WorkPackage[]): WorkPackage[] => { + const currentTime = new Date(); + const twoWeeks = new Date(); + twoWeeks.setDate(currentTime.getDate() + 14); + return workPackages.filter(({startDate}) => currentTime <= startDate && startDate <= twoWeeks); +} From 80589cf6a99b124542dd11cbd9854fb570cee66f Mon Sep 17 00:00:00 2001 From: Jalen Wu Date: Thu, 19 Sep 2024 01:11:41 -0400 Subject: [PATCH 056/264] #2806 formatting changes --- src/frontend/src/utils/work-package.utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/utils/work-package.utils.ts b/src/frontend/src/utils/work-package.utils.ts index e2bc0f1bee..ecbca537d6 100644 --- a/src/frontend/src/utils/work-package.utils.ts +++ b/src/frontend/src/utils/work-package.utils.ts @@ -16,5 +16,5 @@ export const getUpcomingWorkPackages = (workPackages: WorkPackage[]): WorkPackag const currentTime = new Date(); const twoWeeks = new Date(); twoWeeks.setDate(currentTime.getDate() + 14); - return workPackages.filter(({startDate}) => currentTime <= startDate && startDate <= twoWeeks); -} + return workPackages.filter(({ startDate }) => currentTime <= startDate && startDate <= twoWeeks); +}; From 3473b23fb1ee77e58ef122d1ac55d40258fb174e Mon Sep 17 00:00:00 2001 From: Jonathan Chen Date: Thu, 19 Sep 2024 10:16:46 -0400 Subject: [PATCH 057/264] #1586 - add FE+BE delivery date validation --- .../services/reimbursement-requests.services.ts | 10 ++++++++++ .../MarkDeliveredModal.tsx | 16 ++++++++++++---- .../ReimbursementRequestDetailsView.tsx | 2 +- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/backend/src/services/reimbursement-requests.services.ts b/src/backend/src/services/reimbursement-requests.services.ts index a17cc9d26c..652da688bd 100644 --- a/src/backend/src/services/reimbursement-requests.services.ts +++ b/src/backend/src/services/reimbursement-requests.services.ts @@ -772,6 +772,13 @@ export default class ReimbursementRequestService { organization: Organization, dateDelivered: Date ) { + const startOfDay = (date: Date): Date => { + return new Date(new Date(date).setHours(0, 0, 0, 0)); + }; + const startOfNextDay = (date: Date): Date => { + return new Date(new Date(date).setHours(24, 0, 0, 0)); + }; + const reimbursementRequest = await prisma.reimbursement_Request.findUnique({ where: { reimbursementRequestId } }); @@ -783,6 +790,9 @@ export default class ReimbursementRequestService { throw new AccessDeniedException('Only the creator of the reimbursement request can mark as delivered'); if (reimbursementRequest.organizationId !== organization.organizationId) throw new InvalidOrganizationException('Reimbursement Request'); + if (reimbursementRequest.dateOfExpense && startOfDay(dateDelivered) < startOfDay(reimbursementRequest.dateOfExpense)) + throw new HttpException(400, 'Items cannot be delivered before the expense date.'); + if (startOfNextDay(dateDelivered) > new Date()) throw new HttpException(400, 'Delivery date cannot be in the future.'); const reimbursementRequestDelivered = await prisma.reimbursement_Request.update({ where: { reimbursementRequestId }, diff --git a/src/frontend/src/pages/FinancePage/ReimbursementRequestDetailPage/MarkDeliveredModal.tsx b/src/frontend/src/pages/FinancePage/ReimbursementRequestDetailPage/MarkDeliveredModal.tsx index 22d268594a..45f5bae15d 100644 --- a/src/frontend/src/pages/FinancePage/ReimbursementRequestDetailPage/MarkDeliveredModal.tsx +++ b/src/frontend/src/pages/FinancePage/ReimbursementRequestDetailPage/MarkDeliveredModal.tsx @@ -6,6 +6,7 @@ import * as yup from 'yup'; import { yupResolver } from '@hookform/resolvers/yup'; import { FormControl, FormControlLabel, FormLabel, Radio, RadioGroup } from '@mui/material'; import { DatePicker } from '@mui/x-date-pickers'; +import { ReimbursementRequest } from 'shared'; const schema = yup.object().shape({ dateDelivered: yup.date().required('Must provide delivery date.'), @@ -18,12 +19,19 @@ const schema = yup.object().shape({ interface MarkDeliveredModalProps { modalShow: boolean; onHide: () => void; - reimbursementRequestId: string; + reimbursementRequest: ReimbursementRequest; } -const MarkDeliveredModal = ({ modalShow, onHide, reimbursementRequestId }: MarkDeliveredModalProps) => { +const MarkDeliveredModal = ({ modalShow, onHide, reimbursementRequest }: MarkDeliveredModalProps) => { const toast = useToast(); - const { mutateAsync: markDelivered } = useMarkReimbursementRequestAsDelivered(reimbursementRequestId); + const { mutateAsync: markDelivered } = useMarkReimbursementRequestAsDelivered(reimbursementRequest.reimbursementRequestId); + + const dateIsBeforeExpenseCreated = (date: Date): boolean => { + if (!reimbursementRequest.dateOfExpense) return false; + else { + return date < new Date(new Date(reimbursementRequest.dateOfExpense).setHours(0, 0, 0, 0)); + } + }; const dateIsInTheFuture = (date: Date) => { const now = new Date(); @@ -81,7 +89,7 @@ const MarkDeliveredModal = ({ modalShow, onHide, reimbursementRequestId }: MarkD onChange={(date) => onChange(date ?? new Date())} className={'padding: 10'} value={value} - shouldDisableDate={dateIsInTheFuture} + shouldDisableDate={(date) => dateIsBeforeExpenseCreated(date) || dateIsInTheFuture(date)} slotProps={{ textField: { autoComplete: 'off' } }} /> )} diff --git a/src/frontend/src/pages/FinancePage/ReimbursementRequestDetailPage/ReimbursementRequestDetailsView.tsx b/src/frontend/src/pages/FinancePage/ReimbursementRequestDetailPage/ReimbursementRequestDetailsView.tsx index d7cf6367a7..633dea35e0 100644 --- a/src/frontend/src/pages/FinancePage/ReimbursementRequestDetailPage/ReimbursementRequestDetailsView.tsx +++ b/src/frontend/src/pages/FinancePage/ReimbursementRequestDetailPage/ReimbursementRequestDetailsView.tsx @@ -489,7 +489,7 @@ const ReimbursementRequestDetailsView: React.FC setShowMarkDelivered(false)} - reimbursementRequestId={reimbursementRequest.reimbursementRequestId} + reimbursementRequest={reimbursementRequest} /> From 33315e0d62c46eda5b066dd4038034f126721bc9 Mon Sep 17 00:00:00 2001 From: Alan Eng <147638290+alaneng-neu@users.noreply.github.com> Date: Thu, 19 Sep 2024 14:16:11 -0400 Subject: [PATCH 058/264] Allow users to login to prod without first/last name set on google account --- src/backend/src/services/users.services.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/backend/src/services/users.services.ts b/src/backend/src/services/users.services.ts index 056346caea..14e3f49b24 100644 --- a/src/backend/src/services/users.services.ts +++ b/src/backend/src/services/users.services.ts @@ -194,14 +194,6 @@ export default class UsersService { } }); - if (!payload['given_name']) { - throw new HttpException(400, 'First Name was not Found on Google Account'); - } - - if (!payload['family_name']) { - throw new HttpException(400, 'Last Name was not Found on Google Account'); - } - if (!payload['email']) { throw new HttpException(400, 'Email was not Found on Google Account'); } @@ -211,10 +203,13 @@ export default class UsersService { const emailId = payload['email']!.includes('@husky.neu.edu') ? payload['email']!.split('@')[0] : null; const organization = await prisma.organization.findFirst(); + const firstName = payload['given_name'] ?? payload['email']!.split('@')[0]; // Defaults to id of email + const lastName = payload['family_name'] ?? ''; // Defaults to no last name + const createdUser = await prisma.user.create({ data: { - firstName: payload['given_name'], - lastName: payload['family_name'], + firstName, + lastName, googleAuthId: userId, email: payload['email'], emailId, From 1bfe3acc70461d8cf5705a2ec8b59bb6a04c88f4 Mon Sep 17 00:00:00 2001 From: Chris Pyle Date: Fri, 20 Sep 2024 18:57:19 -0400 Subject: [PATCH 059/264] #2801 component to displayer user's team's work packages --- .../src/pages/HomePage/MemberHomePage.tsx | 2 + .../components/TeamWorkPackageDisplay.tsx | 78 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 src/frontend/src/pages/HomePage/components/TeamWorkPackageDisplay.tsx diff --git a/src/frontend/src/pages/HomePage/MemberHomePage.tsx b/src/frontend/src/pages/HomePage/MemberHomePage.tsx index 00f96da519..e6ff35bf00 100644 --- a/src/frontend/src/pages/HomePage/MemberHomePage.tsx +++ b/src/frontend/src/pages/HomePage/MemberHomePage.tsx @@ -13,6 +13,7 @@ import LoadingIndicator from '../../components/LoadingIndicator'; import ErrorPage from '../ErrorPage'; import PageLayout from '../../components/PageLayout'; import { AuthenticatedUser } from 'shared'; +import TeamWorkPackageDisplay from './components/TeamWorkPackageDisplay'; interface MemberHomePageProps { user: AuthenticatedUser; @@ -29,6 +30,7 @@ const MemberHomePage = ({ user }: MemberHomePageProps) => { Welcome, {user.firstName}! + diff --git a/src/frontend/src/pages/HomePage/components/TeamWorkPackageDisplay.tsx b/src/frontend/src/pages/HomePage/components/TeamWorkPackageDisplay.tsx new file mode 100644 index 0000000000..8caaa669e1 --- /dev/null +++ b/src/frontend/src/pages/HomePage/components/TeamWorkPackageDisplay.tsx @@ -0,0 +1,78 @@ +import WorkPackageCard from './WorkPackageCard'; +import PageBlock from '../../../layouts/PageBlock'; +import { wbsPipe } from '../../../utils/pipes'; +import Box from '@mui/material/Box'; +import { useAllTeams } from '../../../hooks/teams.hooks'; +import { useCurrentUser } from '../../../hooks/users.hooks'; +import LoadingIndicator from '../../../components/LoadingIndicator'; +import ErrorPage from '../../ErrorPage'; +import { useTheme } from '@mui/material'; + +const TeamWorkPackageDisplay: React.FC = () => { + const theme = useTheme(); + const user = useCurrentUser(); + const { isLoading, isError, data: teams, error } = useAllTeams(); + const myTeams = teams?.filter((team) => { + return ( + team.members.some((member) => member.userId === user.userId) || + team.leads.some((member) => member.userId === user.userId) || + team.head.userId === user.userId + ); + }); + + const workPackages = myTeams + ?.map((team) => { + return team.projects.map((project) => { + return project.workPackages; + }); + }) + .flat(2); + + if (!workPackages) return ; + + if (isLoading || !teams) return ; + + if (isError) return ; + + const fullDisplay = ( + + {workPackages.length === 0 + ? `No work packages` + : workPackages.map((wp) => ( + + + + ))} + + ); + + return ( + + {fullDisplay} + + ); +}; + +export default TeamWorkPackageDisplay; From 1540118c5294a3cec0da13e13c2e7d5baec2a107 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Sun, 22 Sep 2024 11:55:06 -0400 Subject: [PATCH 060/264] #2708 fixed image url --- .../src/components/NERUploadButton.tsx | 2 +- .../TeamConfig/TeamTypeTable.tsx | 106 ++++++++++-------- 2 files changed, 58 insertions(+), 50 deletions(-) diff --git a/src/frontend/src/components/NERUploadButton.tsx b/src/frontend/src/components/NERUploadButton.tsx index 80ef395dee..ba716801ad 100644 --- a/src/frontend/src/components/NERUploadButton.tsx +++ b/src/frontend/src/components/NERUploadButton.tsx @@ -57,7 +57,7 @@ const NERUploadButton = ({ dataTypeId, handleFileChange, onSubmit, addedImage, s onClick={() => setAddedImage(undefined)} sx={{ textTransform: 'none', mt: 1, mr: 1 }} > - Remove + Cancel