diff --git a/CHANGELOG.md b/CHANGELOG.md index 8608f66ab..1d976ad0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,56 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/). + +## [1.0.0-alpha.6] - 2024-09-27 + +### Added +- Added po and sm forms for the weekly checkin for the appropriate teams https://github.com/chingu-x/chingu-dashboard/issues/216 + +### Changed +- Changed how we're accessing meeting data to match changes in backend https://github.com/chingu-x/chingu-dashboard/pull/269 +- Updated dropdown menu in top nav https://github.com/chingu-x/chingu-dashboard/issues/261 + + +### Fixed + +## [1.0.0-alpha.5] - 2024-09-19 + +### Added + + +### Changed +- Made meeting link optional https://github.com/chingu-x/chingu-dashboard/issues/237 + +## [1.0.0-alpha.4] - 2024-09-10 + +### Added + + +### Changed +- Updated how we're retrieving the discord id to display in the directory page https://github.com/chingu-x/chingu-dashboard/issues/202 + + +### Fixed +- Fixed issue with dark mode images being different size https://github.com/chingu-x/chingu-dashboard/issues/200 +- Fixed issue with meeting notes section becoming scrollable instead of expanding when saved https://github.com/chingu-x/chingu-dashboard/issues/248 + + +## [1.0.0-alpha.3] - 2024-09-05 + +### Added +- Added 404 page https://github.com/chingu-x/chingu-dashboard/issues/205 + +### Changed +- Updated top nav and side bar colors along with some other styling changes https://github.com/chingu-x/chingu-dashboard/issues/197 + +### Fixed +- Fixed active states in the sidebar https://github.com/chingu-x/chingu-dashboard/issues/198 +- Fixed spacing issues in the calendar title with longer months wrapping to a newline https://github.com/chingu-x/chingu-dashboard/issues/201 +- Fixed spacing issues in resources page https://github.com/chingu-x/chingu-dashboard/issues/206 +- Fixed overflow issue with features description in the list https://github.com/chingu-x/chingu-dashboard/issues/222 +- Fixed an issue with selecting team members in checkboxes https://github.com/chingu-x/chingu-dashboard/issues/230 + ## [1.0.0-alpha.2] - 2024-08-28 ### Added diff --git a/public/img/empty_ideation_dark.png b/public/img/empty_ideation_dark.png index 0b99d75e2..e626d1a59 100644 Binary files a/public/img/empty_ideation_dark.png and b/public/img/empty_ideation_dark.png differ diff --git a/public/img/empty_ideation_light.png b/public/img/empty_ideation_light.png index ed22e460d..ab00a7f1e 100644 Binary files a/public/img/empty_ideation_light.png and b/public/img/empty_ideation_light.png differ diff --git a/public/img/empty_resources_dark.png b/public/img/empty_resources_dark.png index 2b783dfb6..e626d1a59 100644 Binary files a/public/img/empty_resources_dark.png and b/public/img/empty_resources_dark.png differ diff --git a/public/img/empty_resources_light.png b/public/img/empty_resources_light.png index 820238c0b..ab00a7f1e 100644 Binary files a/public/img/empty_resources_light.png and b/public/img/empty_resources_light.png differ diff --git a/public/img/error_dark.png b/public/img/error_dark.png new file mode 100644 index 000000000..6291047f4 Binary files /dev/null and b/public/img/error_dark.png differ diff --git a/public/img/error_light.png b/public/img/error_light.png new file mode 100644 index 000000000..29d73a48b Binary files /dev/null and b/public/img/error_light.png differ diff --git a/src/app/(main)/dashboard/components/voyage-dashboard/getDashboardData.ts b/src/app/(main)/dashboard/components/voyage-dashboard/getDashboardData.ts index ecbe3e859..36ef73887 100644 --- a/src/app/(main)/dashboard/components/voyage-dashboard/getDashboardData.ts +++ b/src/app/(main)/dashboard/components/voyage-dashboard/getDashboardData.ts @@ -200,7 +200,7 @@ export const getDashboardData = async ( .map((sprint) => fetchMeeting({ sprintNumber: sprint.number, - meetingId: sprint.teamMeetings[0]?.id, + meetingId: sprint.teamMeetings[0], }), ); diff --git a/src/app/(main)/my-voyage/[teamId]/directory/components/DirectoryComponentWrapper.tsx b/src/app/(main)/my-voyage/[teamId]/directory/components/DirectoryComponentWrapper.tsx index 0139b856d..53bf306d5 100644 --- a/src/app/(main)/my-voyage/[teamId]/directory/components/DirectoryComponentWrapper.tsx +++ b/src/app/(main)/my-voyage/[teamId]/directory/components/DirectoryComponentWrapper.tsx @@ -43,11 +43,18 @@ export async function fetchTeamDirectory({ if (res) { updateDirectoryWithCurrentTime(res); - const teamMember = res.voyageTeamMembers; - const elementToSort = teamMember.find( - (element) => element.member.discordId === user?.discordId, + const teamMembers = res.voyageTeamMembers; + const userDiscordId = user?.oAuthProfiles.find( + (profile) => profile.provider.name === "discord", + )?.providerUsername; + const elementToSort = teamMembers.find( + (element) => + element.member.oAuthProfiles.find( + (profile) => profile.provider.name === "discord", + )?.providerUsername === userDiscordId, ); - moveElementToFirst(teamMember, elementToSort); + + moveElementToFirst(teamMembers, elementToSort); } return [res, error]; diff --git a/src/app/(main)/my-voyage/[teamId]/directory/components/TeamMember.tsx b/src/app/(main)/my-voyage/[teamId]/directory/components/TeamMember.tsx index 0abcb7daa..56488ebee 100644 --- a/src/app/(main)/my-voyage/[teamId]/directory/components/TeamMember.tsx +++ b/src/app/(main)/my-voyage/[teamId]/directory/components/TeamMember.tsx @@ -13,12 +13,16 @@ interface TeamMemberProps { export default function TeamMember({ teamMember }: TeamMemberProps) { const user = useUser().voyageTeamMembers; - const { firstName, lastName, discordId, currentTime } = teamMember.member; + const { firstName, lastName, oAuthProfiles, currentTime } = teamMember.member; const { id, hrPerSprint, voyageRole } = teamMember; const isCurrentUser = user.some((user) => user.id === id); const [isEditing, setIsEditing] = useState(false); const newRef = useRef(null); + const discordId = + oAuthProfiles.find((profile) => profile.provider.name === "discord") + ?.providerUsername || ""; + useEffect(() => { document.addEventListener("mousedown", handleOutsideClick); return () => { diff --git a/src/app/(main)/my-voyage/[teamId]/sprints/components/EmptySprintWrapper.tsx b/src/app/(main)/my-voyage/[teamId]/sprints/components/EmptySprintWrapper.tsx index fa0b33d4f..9fac4f77f 100644 --- a/src/app/(main)/my-voyage/[teamId]/sprints/components/EmptySprintWrapper.tsx +++ b/src/app/(main)/my-voyage/[teamId]/sprints/components/EmptySprintWrapper.tsx @@ -24,8 +24,8 @@ import ErrorComponent from "@/components/Error"; function getMeeting(sprints: Sprint[], sprintNumber: number) { const sprint = sprints.find((sprint) => sprint.number === sprintNumber); - if (sprint?.teamMeetings && sprint?.teamMeetings.length > 0) - return sprint.teamMeetings[0]; + if (sprint?.teamMeetingsData && sprint?.teamMeetingsData.length > 0) + return sprint.teamMeetingsData[0]; return null; } diff --git a/src/app/(main)/my-voyage/[teamId]/sprints/components/ProgressStepper.tsx b/src/app/(main)/my-voyage/[teamId]/sprints/components/ProgressStepper.tsx index 89ef4b78f..8c25ef6db 100644 --- a/src/app/(main)/my-voyage/[teamId]/sprints/components/ProgressStepper.tsx +++ b/src/app/(main)/my-voyage/[teamId]/sprints/components/ProgressStepper.tsx @@ -31,7 +31,7 @@ export default function ProgressStepper({ function handleClick(sprintNumber: number) { const meetingId = sprints.find((sprint) => sprint.number === sprintNumber)! - .teamMeetings[0]?.id; + .teamMeetings[0]; if (meetingId) { router.push( diff --git a/src/app/(main)/my-voyage/[teamId]/sprints/components/RedirectToCurrentSprintWrapper.tsx b/src/app/(main)/my-voyage/[teamId]/sprints/components/RedirectToCurrentSprintWrapper.tsx index 96a6acab9..82f199a9c 100644 --- a/src/app/(main)/my-voyage/[teamId]/sprints/components/RedirectToCurrentSprintWrapper.tsx +++ b/src/app/(main)/my-voyage/[teamId]/sprints/components/RedirectToCurrentSprintWrapper.tsx @@ -52,7 +52,6 @@ export default async function RedirectToCurrentSprintWrapper({ const teamId = Number(params.teamId); let currentSprintNumber: number; - let currentMeetingId: number; const [user, error] = await getUser(); @@ -111,12 +110,12 @@ export default async function RedirectToCurrentSprintWrapper({ ); } const { teamMeetings, number } = getCurrentSprint(res!.sprints) as Sprint; + currentSprintNumber = number; - currentMeetingId = teamMeetings[0]?.id; - if (currentMeetingId) { + if (teamMeetings.length !== 0) { redirect( - `/my-voyage/${teamId}/sprints/${currentSprintNumber}/meeting/${currentMeetingId}`, + `/my-voyage/${teamId}/sprints/${currentSprintNumber}/meeting/${teamMeetings[0]}`, ); } else { redirect(`/my-voyage/${teamId}/sprints/${currentSprintNumber}`); diff --git a/src/app/(main)/my-voyage/[teamId]/sprints/components/SprintWrapper.tsx b/src/app/(main)/my-voyage/[teamId]/sprints/components/SprintWrapper.tsx index f22bfe474..053d718c2 100644 --- a/src/app/(main)/my-voyage/[teamId]/sprints/components/SprintWrapper.tsx +++ b/src/app/(main)/my-voyage/[teamId]/sprints/components/SprintWrapper.tsx @@ -126,7 +126,7 @@ export default async function SprintWrapper({ params }: SprintWrapperProps) { const correspondingMeetingId = voyageData.sprints.find( (sprint) => sprint.number === sprintNumber, - )?.teamMeetings[0]?.id; + )?.teamMeetings[0]; if (meetingId === correspondingMeetingId) { const [res, error] = await fetchMeeting({ sprintNumber, meetingId }); diff --git a/src/app/(main)/my-voyage/[teamId]/sprints/components/WeeklyCheckInWrapper.tsx b/src/app/(main)/my-voyage/[teamId]/sprints/components/WeeklyCheckInWrapper.tsx index 2e30a26fe..62c56dd75 100644 --- a/src/app/(main)/my-voyage/[teamId]/sprints/components/WeeklyCheckInWrapper.tsx +++ b/src/app/(main)/my-voyage/[teamId]/sprints/components/WeeklyCheckInWrapper.tsx @@ -13,7 +13,7 @@ import { handleAsync } from "@/utils/handleAsync"; import { type AsyncActionResponse } from "@/utils/handleAsync"; import { getCurrentVoyageData } from "@/utils/getCurrentVoyageData"; import routePaths from "@/utils/routePaths"; -import { Forms } from "@/utils/form/formsEnums"; +import { Forms, UserRole } from "@/utils/form/formsEnums"; import { type Question, type TeamMemberForCheckbox } from "@/utils/form/types"; import { getSprintCheckinIsStatus } from "@/utils/getFormStatus"; import { getCurrentSprint } from "@/utils/getCurrentSprint"; @@ -76,6 +76,11 @@ export default async function WeeklyCheckInWrapper({ let description = ""; let questions = [] as Question[]; + let hasProductOwner = false; + let hasScrumMaster = false; + let isScrumMaster = false; + let isProductOwner = false; + const [user, error] = await getUser(); const { errorResponse, data } = await getCurrentVoyageData({ @@ -142,6 +147,25 @@ export default async function WeeklyCheckInWrapper({ }).voyageTeamMemberId; } + // Check if a team has a product owner or a scrum muster and if a user is a team has a product owner or a scrum muster + hasScrumMaster = !!res.voyageTeamMembers.find( + (member) => + member.voyageRole.name === UserRole.scrumMaster.toString(), + ); + + hasProductOwner = !!res.voyageTeamMembers.find( + (member) => + member.voyageRole.name === UserRole.productOwner.toString(), + ); + + const currentUserRole = res.voyageTeamMembers.find( + (member) => member.id === voyageTeamMemberId, + )?.voyageRole.name; + + isScrumMaster = currentUserRole === UserRole.scrumMaster.toString(); + + isProductOwner = currentUserRole === UserRole.productOwner.toString(); + // Get all teamMembers except for the current user if (voyageTeamMemberId) { teamMembers = res.voyageTeamMembers @@ -164,7 +188,7 @@ export default async function WeeklyCheckInWrapper({ ); } - // Fetch form + // Fetch general checkin form const [formRes, formError] = await fetchFormQuestions({ formId: Forms.checkIn, }); @@ -177,8 +201,49 @@ export default async function WeeklyCheckInWrapper({ /> ); } + if (formRes && formRes?.description) description = formRes.description; if (formRes && formRes?.questions) questions = formRes.questions; + + // Fetch PO checkin questions (form) + if (hasProductOwner && !isProductOwner) { + const [POformRes, POformError] = await fetchFormQuestions({ + formId: Forms.checkinPO, + }); + + if (POformError) { + return ( + + ); + } + + if (POformRes && POformRes?.questions) + questions = [...questions, ...POformRes.questions]; + } + + // Fetch SM checkin questions (form) + if (hasScrumMaster && !isScrumMaster) { + const [SMformRes, SMformError] = await fetchFormQuestions({ + formId: Forms.checkinSM, + }); + + if (SMformError) { + return ( + + ); + } + + if (SMformRes && SMformRes?.questions) + questions = [...questions, ...SMformRes.questions]; + } + + questions = questions.sort((a, b) => a.order - b.order); } } else { redirect(routePaths.dashboardPage()); diff --git a/src/app/(main)/my-voyage/[teamId]/sprints/components/forms/AgendaTopicForm.tsx b/src/app/(main)/my-voyage/[teamId]/sprints/components/forms/AgendaTopicForm.tsx index 484dcd038..5bd2032c7 100644 --- a/src/app/(main)/my-voyage/[teamId]/sprints/components/forms/AgendaTopicForm.tsx +++ b/src/app/(main)/my-voyage/[teamId]/sprints/components/forms/AgendaTopicForm.tsx @@ -160,9 +160,13 @@ export default function AgendaTopicForm() { useEffect(() => { if (sprintNumber && agendaId) { - const topic = sprints - .find((sprint) => sprint.number === sprintNumber) - ?.teamMeetings[0].agendas?.find((topic) => topic.id === agendaId); + const sprint = sprints.find((sprint) => sprint.number === sprintNumber); + + const topic = + sprint?.teamMeetingsData && + sprint.teamMeetingsData[0].agendas?.find( + (topic) => topic.id === agendaId, + ); setTopicData(topic); setEditMode(true); diff --git a/src/app/(main)/my-voyage/[teamId]/sprints/components/forms/MeetingForm.tsx b/src/app/(main)/my-voyage/[teamId]/sprints/components/forms/MeetingForm.tsx index 503efb016..e5e1a68b1 100644 --- a/src/app/(main)/my-voyage/[teamId]/sprints/components/forms/MeetingForm.tsx +++ b/src/app/(main)/my-voyage/[teamId]/sprints/components/forms/MeetingForm.tsx @@ -70,8 +70,7 @@ export default function MeetingForm() { timezone, }), meetingLink: validateTextInput({ - inputName: "Meeting Link", - required: true, + inputName: "Meeting link", isUrl: true, }), }); @@ -117,9 +116,18 @@ export default function MeetingForm() { timeZone: timezone, }); + const newData = + data.meetingLink === "" + ? { description: data.description, title: data.title } + : { + description: data.description, + title: data.title, + meetingLink: data.meetingLink, + }; + if (editMode) { const [res, error] = await editMeetingAction({ - ...data, + ...newData, dateTime, meetingId, sprintNumber, @@ -143,7 +151,7 @@ export default function MeetingForm() { setEditMeetingLoading(false); } } else { - const payload = { ...data, dateTime, teamId, sprintNumber }; + const payload = { ...newData, dateTime, teamId, sprintNumber }; const [res, error] = await addMeetingAction(payload); @@ -169,8 +177,10 @@ export default function MeetingForm() { useEffect(() => { if (params.meetingId) { const meeting = sprints.find( - (sprint) => sprint.teamMeetings[0]?.id === +params.meetingId, - )?.teamMeetings[0]; + (sprint) => + sprint.teamMeetingsData && + sprint.teamMeetingsData[0].id === +params.meetingId, + ); setMeetingData(meeting as Meeting); setEditMode(true); diff --git a/src/app/(main)/my-voyage/[teamId]/sprints/components/meetingOverview/MeetingOverview.tsx b/src/app/(main)/my-voyage/[teamId]/sprints/components/meetingOverview/MeetingOverview.tsx index 6b66806da..b2103cbea 100644 --- a/src/app/(main)/my-voyage/[teamId]/sprints/components/meetingOverview/MeetingOverview.tsx +++ b/src/app/(main)/my-voyage/[teamId]/sprints/components/meetingOverview/MeetingOverview.tsx @@ -34,6 +34,7 @@ export default function MeetingOverview({ className="w-full" > + - + ); } diff --git a/src/components/navbar/DropDownLink.tsx b/src/components/navbar/DropDownLink.tsx deleted file mode 100644 index b5ee72c9d..000000000 --- a/src/components/navbar/DropDownLink.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import Link from "next/link"; - -interface DropDownLinkProps { - title: string; - href?: string; -} - -export default function DropDownLink({ title, href = "#" }: DropDownLinkProps) { - return ( -
  • - - {title} - -
  • - ); -} diff --git a/src/components/sidebar/ExpandButton.tsx b/src/components/sidebar/ExpandButton.tsx index 1427bae42..8e89c2549 100644 --- a/src/components/sidebar/ExpandButton.tsx +++ b/src/components/sidebar/ExpandButton.tsx @@ -12,7 +12,7 @@ export default function ExpandButton({ isOpen, onClick }: ExpandButtonProps) { return ( diff --git a/src/store/features/directory/directorySlice.ts b/src/store/features/directory/directorySlice.ts index 1f6ec6870..e8ddbe91b 100644 --- a/src/store/features/directory/directorySlice.ts +++ b/src/store/features/directory/directorySlice.ts @@ -4,6 +4,8 @@ import { type VoyageStatus, } from "@/store/features/user/userSlice"; +type providerType = "discord"; + interface VoyageTier { id: number; name: string; @@ -20,10 +22,12 @@ interface VoyageMember { firstName: string; lastName: string; avatar: string; - githubId: string | null; - discordId: string | null; - twitterId: string | null; - linkedinId: string | null; + oAuthProfiles: { + provider: { + name: providerType; + }; + providerUsername: string; + }[]; countryCode: string; timezone: string; currentTime: string; @@ -73,10 +77,14 @@ const initialState: DirectoryState = { firstName: "", lastName: "", avatar: "", - githubId: null, - discordId: null, - twitterId: null, - linkedinId: null, + oAuthProfiles: [ + { + provider: { + name: "discord", + }, + providerUsername: "", + }, + ], countryCode: "", timezone: "", currentTime: "", diff --git a/src/store/features/sprint/sprintSlice.ts b/src/store/features/sprint/sprintSlice.ts index 8b64eb63b..1e6284a05 100644 --- a/src/store/features/sprint/sprintSlice.ts +++ b/src/store/features/sprint/sprintSlice.ts @@ -45,7 +45,8 @@ export interface Sprint { number: number; startDate: string; endDate: string; - teamMeetings: Meeting[]; + teamMeetings: number[]; + teamMeetingsData?: Meeting[]; } export interface Voyage { @@ -89,16 +90,11 @@ export const sprintSlice = createSlice({ const updatedSprints = state.voyage.sprints.map((sprint) => { if (sprint.id === sprintId) { - const updatedMeetings = sprint.teamMeetings.map((meeting) => { - if (meeting.id === action.payload.id) { - return action.payload; - } - return meeting; - }); - return { ...sprint, teamMeetings: updatedMeetings }; + return { ...sprint, teamMeetingsData: [action.payload] }; } return sprint; }); + state.voyage.sprints = updatedSprints; state.loading = true; }, diff --git a/src/store/features/user/userSlice.ts b/src/store/features/user/userSlice.ts index e2054105d..97d50c502 100644 --- a/src/store/features/user/userSlice.ts +++ b/src/store/features/user/userSlice.ts @@ -1,6 +1,8 @@ import { type PayloadAction, createSlice } from "@reduxjs/toolkit"; import { clientSignOut } from "@/store/features/auth/authSlice"; +type providerType = "discord"; + export interface VoyageStatus { name: string; } @@ -31,10 +33,12 @@ export interface User { firstName: string; lastName: string; countryCode: string; - discordId: string; - githubId: string; - twitterId: string; - linkedinId: string; + oAuthProfiles: { + provider: { + name: providerType; + }; + providerUsername: string; + }[]; email: string; timezone: string; avatar: string; @@ -48,10 +52,14 @@ const initialState: User = { firstName: "", lastName: "", countryCode: "", - discordId: "", - githubId: "", - twitterId: "", - linkedinId: "", + oAuthProfiles: [ + { + provider: { + name: "discord", + }, + providerUsername: "", + }, + ], email: "", timezone: "", avatar: "", diff --git a/src/utils/form/formsEnums.ts b/src/utils/form/formsEnums.ts index f37943027..308cba151 100644 --- a/src/utils/form/formsEnums.ts +++ b/src/utils/form/formsEnums.ts @@ -1,5 +1,7 @@ export enum Forms { - submitProject = 6, + submitProject = 8, + checkinSM = 5, + checkinPO = 4, checkIn = 3, planning = 2, review = 1, @@ -20,3 +22,9 @@ export enum ReviewQuestions { what_to_improve = 2, what_to_change = 1, } + +export enum UserRole { + developer = "Developer", + productOwner = "Product Owner", + scrumMaster = "Scrum Master", +} diff --git a/src/utils/form/helpers.tsx b/src/utils/form/helpers.tsx index 34f01ebca..306b904b1 100644 --- a/src/utils/form/helpers.tsx +++ b/src/utils/form/helpers.tsx @@ -101,7 +101,7 @@ export function getOptions({ const teamMemberId = member.id; const option = { - id: `teamMember${teamMemberId.toString()}`, + id: `${id}-teamMember${teamMemberId.toString()}`, value: teamMemberId.toString(), label: ( diff --git a/src/utils/getCurrentSprint.ts b/src/utils/getCurrentSprint.ts index 2ea96c1d9..4b1cd9e82 100644 --- a/src/utils/getCurrentSprint.ts +++ b/src/utils/getCurrentSprint.ts @@ -4,7 +4,7 @@ import { type Sprint } from "@/store/features/sprint/sprintSlice"; export const currentDate = process.env.NODE_ENV === "development" - ? new Date(2024, 5, 10, 12) + ? new Date(2024, 5, 1, 12) : new Date(); export function getCurrentSprint(sprints: Sprint[]) { diff --git a/src/utils/routePaths.ts b/src/utils/routePaths.ts index 558ab2502..554d369be 100644 --- a/src/utils/routePaths.ts +++ b/src/utils/routePaths.ts @@ -2,7 +2,7 @@ const routePaths = { dashboardPage() { - return "/"; + return "/dashboard"; }, VoyageMemberDashboardPage(teamId: string) { return `/dashboard/${teamId}`;