diff --git a/CHANGELOG.md b/CHANGELOG.md index c00bbd4ea..1d976ad0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ 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 diff --git a/src/app/(main)/dashboard/components/voyage-dashboard/getDashboardData.ts b/src/app/(main)/dashboard/components/voyage-dashboard/getDashboardData.ts index 4e235a30e..ef0a600a7 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]/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 cdf368a7f..67abcb457 100644 --- a/src/app/(main)/my-voyage/[teamId]/sprints/components/RedirectToCurrentSprintWrapper.tsx +++ b/src/app/(main)/my-voyage/[teamId]/sprints/components/RedirectToCurrentSprintWrapper.tsx @@ -51,7 +51,6 @@ export default async function RedirectToCurrentSprintWrapper({ const teamId = Number(params.teamId); let currentSprintNumber: number; - let currentMeetingId: number; const [user, error] = await getUser(); @@ -110,12 +109,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 4f9e7a93c..b5edac318 100644 --- a/src/app/(main)/my-voyage/[teamId]/sprints/components/SprintWrapper.tsx +++ b/src/app/(main)/my-voyage/[teamId]/sprints/components/SprintWrapper.tsx @@ -125,7 +125,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 fc21649b9..ab645f770 100644 --- a/src/app/(main)/my-voyage/[teamId]/sprints/components/WeeklyCheckInWrapper.tsx +++ b/src/app/(main)/my-voyage/[teamId]/sprints/components/WeeklyCheckInWrapper.tsx @@ -12,7 +12,7 @@ import { CacheTag } from "@/utils/cacheTag"; import { type AsyncActionResponse, handleAsync } 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"; @@ -75,6 +75,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({ @@ -141,6 +146,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 @@ -163,7 +187,7 @@ export default async function WeeklyCheckInWrapper({ ); } - // Fetch form + // Fetch general checkin form const [formRes, formError] = await fetchFormQuestions({ formId: Forms.checkIn, }); @@ -176,8 +200,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 b6329a69c..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 @@ -177,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/sections/Notes.tsx b/src/app/(main)/my-voyage/[teamId]/sprints/components/sections/Notes.tsx index a68113bb5..797f5b5de 100644 --- a/src/app/(main)/my-voyage/[teamId]/sprints/components/sections/Notes.tsx +++ b/src/app/(main)/my-voyage/[teamId]/sprints/components/sections/Notes.tsx @@ -43,7 +43,10 @@ export default function Notes() { } = useSprint(); useEffect(() => { - setData(sprints[sprintNumber - 1].teamMeetings[0].notes); + const sprint = sprints[sprintNumber - 1]; + if (sprint.teamMeetingsData && sprint.teamMeetingsData.length) { + setData(sprint.teamMeetingsData[0].notes); + } }, [sprints, sprintNumber]); const { diff --git a/src/app/(main)/my-voyage/[teamId]/sprints/components/sections/Planning.tsx b/src/app/(main)/my-voyage/[teamId]/sprints/components/sections/Planning.tsx index 9ab2fb24d..3a2f8b831 100644 --- a/src/app/(main)/my-voyage/[teamId]/sprints/components/sections/Planning.tsx +++ b/src/app/(main)/my-voyage/[teamId]/sprints/components/sections/Planning.tsx @@ -52,11 +52,14 @@ export default function Planning() { } = useSprint(); useEffect(() => { - setData( - sprints[sprintNumber - 1].teamMeetings[0].formResponseMeeting?.find( - (form) => form.form.id === Number(Forms.planning), - ), - ); + const sprint = sprints[sprintNumber - 1]; + if (sprint.teamMeetingsData && sprint.teamMeetingsData.length) { + setData( + sprint.teamMeetingsData[0].formResponseMeeting?.find( + (form) => form.form.id === Number(Forms.planning), + ), + ); + } }, [sprints, sprintNumber]); const goal = data?.responseGroup.responses.find( diff --git a/src/app/(main)/my-voyage/[teamId]/sprints/components/sections/Review.tsx b/src/app/(main)/my-voyage/[teamId]/sprints/components/sections/Review.tsx index f6c4a709c..0bd047506 100644 --- a/src/app/(main)/my-voyage/[teamId]/sprints/components/sections/Review.tsx +++ b/src/app/(main)/my-voyage/[teamId]/sprints/components/sections/Review.tsx @@ -56,11 +56,14 @@ export default function Review() { } = useSprint(); useEffect(() => { - setData( - sprints[sprintNumber - 1].teamMeetings[0].formResponseMeeting?.find( - (form) => form.form.id === Number(Forms.review), - ), - ); + const sprint = sprints[sprintNumber - 1]; + if (sprint.teamMeetingsData && sprint.teamMeetingsData.length) { + setData( + sprint.teamMeetingsData[0].formResponseMeeting?.find( + (form) => form.form.id === Number(Forms.review), + ), + ); + } }, [sprints, sprintNumber]); const what_right = data?.responseGroup.responses.find( diff --git a/src/app/(main)/my-voyage/[teamId]/sprints/sprintsService.ts b/src/app/(main)/my-voyage/[teamId]/sprints/sprintsService.ts index 09ca2670c..b42cd3dde 100644 --- a/src/app/(main)/my-voyage/[teamId]/sprints/sprintsService.ts +++ b/src/app/(main)/my-voyage/[teamId]/sprints/sprintsService.ts @@ -95,7 +95,7 @@ export interface SprintsResponse { number: number; startDate: string; endDate: string; - teamMeetings: { id: number }[]; + teamMeetings: number[]; }[]; } diff --git a/src/components/navbar/DropDown.tsx b/src/components/navbar/DropDown.tsx index 25b464af0..f3019e541 100644 --- a/src/components/navbar/DropDown.tsx +++ b/src/components/navbar/DropDown.tsx @@ -1,7 +1,7 @@ "use client"; import { ChevronDownIcon } from "@heroicons/react/24/outline"; -import DropDownLink from "./DropDownLink"; +import Link from "next/link"; import Button from "@/components/Button"; import { useAppDispatch, useUser } from "@/store/hooks"; import { clientSignOut } from "@/store/features/auth/authSlice"; @@ -14,12 +14,18 @@ export default function DropDown({ openState }: { openState?: boolean }) { const activeVoyage = allVoyages?.find( (item) => item.voyageTeam.voyage.status.name === "Active", ); - const currentVoyage = - activeVoyage?.voyageTeam.name ?? - "Please join a voyage to see your status information."; + + const currentVoyage = activeVoyage?.voyageTeam.name + ? `Team - Tier ${activeVoyage.voyageTeam.name + .split("-")[1] + .split("tier")[1] + .toUpperCase()} ${activeVoyage.voyageTeam.name + .split("-")[0] + .toUpperCase()}` + : "Please join a voyage to see your status information."; const closed = "hidden"; const open = - "absolute z-[1] w-44 p-4 [&>*]:mt-2 mt-6 shadow bg-base-100 right-0 border border-neutral rounded-2xl"; + "absolute flex flex-col gap-5 z-[1] w-[250px] p-5 bottom-100 translate-y-[15%] shadow-md bg-base-200 right-0 border border-base-100 rounded-2xl"; async function handleClick() { const [res, error] = await serverSignOut(); @@ -35,7 +41,7 @@ export default function DropDown({ openState }: { openState?: boolean }) { } } - const handleDropDownClick = (event: React.MouseEvent) => { + const handleDropDownClick = (event: React.MouseEvent) => { event.stopPropagation(); }; @@ -47,15 +53,17 @@ export default function DropDown({ openState }: { openState?: boolean }) { > -
    -
  • -

    My Status:

    - {currentVoyage ? ( -

    +

    +

    + My Voyage: +

    + {activeVoyage?.voyageTeam.name ? ( +

    {currentVoyage}

    ) : ( @@ -63,19 +71,27 @@ export default function DropDown({ openState }: { openState?: boolean }) { {currentVoyage}

    )} -
  • - + + + + -
+ ); } 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/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/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/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[]) {