diff --git a/frontend/src/components/Admin/TeamEvent/TeamEventDashboard.module.css b/frontend/src/components/Admin/TeamEvent/TeamEventDashboard.module.css index b10227281..4a6505251 100644 --- a/frontend/src/components/Admin/TeamEvent/TeamEventDashboard.module.css +++ b/frontend/src/components/Admin/TeamEvent/TeamEventDashboard.module.css @@ -1,12 +1,14 @@ .headerContainer { display: flex; - justify-content: right; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; } -.csvButton { - padding-left: 30%; - padding-right: 5%; - padding-bottom: 0.5em; +.csvButton, +.displayPeriod { + padding: 0.5em 5%; + display: inline-block; } .dashboardContainer { @@ -25,11 +27,11 @@ } .nameCell { - position: sticky; - left: 0; - display: block; + display: flex; + align-items: center; + justify-content: space-between; background-color: white; - min-width: 250px; + gap: 10px; } .nameHeaderCell { @@ -38,13 +40,12 @@ } .remindButton { - position: sticky; - left: 35%; + position: inline-block; + margin-left: 15px; } .notify { - position: absolute; - right: 1%; + right: 10px; } .endOfSemesterCheckbox { diff --git a/frontend/src/components/Admin/TeamEvent/TeamEventDashboard.tsx b/frontend/src/components/Admin/TeamEvent/TeamEventDashboard.tsx index 31533530d..5da33aca2 100644 --- a/frontend/src/components/Admin/TeamEvent/TeamEventDashboard.tsx +++ b/frontend/src/components/Admin/TeamEvent/TeamEventDashboard.tsx @@ -8,11 +8,18 @@ import { REQUIRED_LEAD_TEC_CREDITS, REQUIRED_MEMBER_TEC_CREDITS, REQUIRED_INITIATIVE_CREDITS, - INITIATIVE_EVENTS + INITIATIVE_EVENTS, + TEC_DEADLINES } from '../../../consts'; import styles from './TeamEventDashboard.module.css'; import NotifyMemberModal from '../../Modals/NotifyMemberModal'; +interface Period { + name: string; + start: Date; + deadline: Date; + events: TeamEvent[]; +} const calculateMemberCreditsForEvent = ( member: IdolMember, event: TeamEvent, @@ -45,6 +52,7 @@ const getInitiativeCredits = (member: IdolMember, teamEvents: TeamEvent[]): numb const TeamEventDashboard: React.FC = () => { const [teamEvents, setTeamEvents] = useState([]); const [isLoading, setIsLoading] = useState(true); + const [displayPeriod, setDisplayPeriod] = useState(false); const [endOfSemesterReminder, setEndOfSemesterReminder] = useState(false); const allMembers = useMembers(); @@ -58,6 +66,80 @@ const TeamEventDashboard: React.FC = () => { if (isLoading) return Fetching team event data...; + const getFirstPeriodStart = (): Date => { + const today = new Date(); + const year = today.getFullYear(); + + return today.getMonth() < 7 ? new Date(year, 0, 1) : new Date(year, 7, 1); + }; + + const getPeriodIndex = (date: Date): number => { + for (let i = 0; i < TEC_DEADLINES.length; i += 1) { + if (date <= TEC_DEADLINES[i]) { + return i; + } + } + return TEC_DEADLINES.length - 1; + }; + + const getPeriods = () => { + const periods: Period[] = []; + let i = 0; + TEC_DEADLINES.forEach((date) => { + i += 1; + const periodIndex = getPeriodIndex(new Date(date.getTime() - 24 * 60 * 60 * 1000)); + const periodStart = + periodIndex === 0 ? getFirstPeriodStart() : TEC_DEADLINES[periodIndex - 1]; + const periodEnd = TEC_DEADLINES[periodIndex]; + const events = teamEvents.filter((event) => { + const eventDate = new Date(event.date); + return eventDate > periodStart && eventDate <= periodEnd; + }); + periods.push({ name: `Period ${i}`, start: periodStart, deadline: date, events }); + }); + return periods; + }; + + const periods = getPeriods(); + const getCreditsPerPeriod = (member: IdolMember) => { + const credPerPeriod: number[] = []; + periods.forEach((period: Period) => { + credPerPeriod.push(getTotalCredits(member, period.events)); + }); + return credPerPeriod; + }; + + const getTECPeriod = (submissionDate: Date) => { + const currentPeriodIndex = TEC_DEADLINES.findIndex((date) => submissionDate <= date); + if (currentPeriodIndex === -1) { + return TEC_DEADLINES.length; + } + return currentPeriodIndex; + }; + + const calculateCredits = (prevCredits: number | null, currentCredits: number) => { + if (prevCredits === null) { + return currentCredits < 1 ? 1 - currentCredits : 0; + } + if (prevCredits < 1) { + return currentCredits + prevCredits < 2 ? 2 - prevCredits - currentCredits : 0; + } + + return currentCredits < 1 ? 1 - currentCredits : 0; + }; + + const currentPeriodIndex = getTECPeriod(new Date()); + const membersNeedingNotification = displayPeriod + ? allMembers.filter((member) => { + const currentPeriodCredits = getTotalCredits(member, periods[currentPeriodIndex].events); + const requiredCredits = LEAD_ROLES.includes(member.role) + ? REQUIRED_LEAD_TEC_CREDITS + : calculateCredits(null, currentPeriodCredits); + + return currentPeriodCredits < requiredCredits; + }) + : []; + const handleExportToCsv = () => { const csvData = allMembers.map((member) => { const totalCredits = getTotalCredits(member, teamEvents); @@ -101,7 +183,14 @@ const TeamEventDashboard: React.FC = () => {
Team Event Dashboard
- +
+ +
+
+ +
@@ -109,29 +198,43 @@ const TeamEventDashboard: React.FC = () => { Name - - Remind All (Excludes Advisors) - - } - members={allMembers.filter((member) => { - if (ADVISOR_ROLES.includes(member.role)) return false; - const totalCredits = teamEvents.reduce( - (val, event) => val + calculateTotalCreditsForEvent(member, event), - 0 - ); - return ( - totalCredits < - (LEAD_ROLES.includes(member.role) - ? REQUIRED_LEAD_TEC_CREDITS - : REQUIRED_MEMBER_TEC_CREDITS) - ); - })} - endOfSemesterReminder={endOfSemesterReminder} - type={'tec'} - /> + {displayPeriod && membersNeedingNotification.length > 0 ? ( + + Notify Members Below Required Period Credits + + } + members={membersNeedingNotification} + endOfSemesterReminder={endOfSemesterReminder} + type={'tec'} + /> + ) : ( + + Remind All (Excludes Advisors) + + } + members={allMembers.filter((member) => { + if (ADVISOR_ROLES.includes(member.role)) return false; + const totalCredits = teamEvents.reduce( + (val, event) => val + calculateTotalCreditsForEvent(member, event), + 0 + ); + return ( + totalCredits < + (LEAD_ROLES.includes(member.role) + ? REQUIRED_LEAD_TEC_CREDITS + : REQUIRED_MEMBER_TEC_CREDITS) + ); + })} + endOfSemesterReminder={endOfSemesterReminder} + type={'tec'} + /> + )} { } /> - Total + {!displayPeriod ? 'Total' : 'Required Credits'} {INITIATIVE_EVENTS && Total Initiative Credits} - {teamEvents.map((event) => ( - {event.name} - ))} + {!displayPeriod + ? teamEvents.map((event) => {event.name}) + : periods.map((period) => {period.name})} - {allMembers.map((member) => { - const totalCredits = getTotalCredits(member, teamEvents); - const initiativeCredits = getInitiativeCredits(member, teamEvents); - const totalCreditsMet = - totalCredits >= - (LEAD_ROLES.includes(member.role) - ? REQUIRED_LEAD_TEC_CREDITS - : REQUIRED_MEMBER_TEC_CREDITS); - const initiativeCreditsMet = initiativeCredits >= REQUIRED_INITIATIVE_CREDITS; - - const isAdvisor = ADVISOR_ROLES.includes(member.role); - - return ( - - - {member.firstName} {member.lastName} ({member.netid}) - {!totalCreditsMet && ( - - ) : ( - - ) - } - member={member} - endOfSemesterReminder={endOfSemesterReminder} - type={'tec'} - /> - )} - - {totalCredits} - {INITIATIVE_EVENTS && ( - {initiativeCredits} - )} - {teamEvents.map((event) => { - const numCredits = calculateTotalCreditsForEvent(member, event); - return {numCredits}; - })} - - ); - })} + {!displayPeriod + ? allMembers.map((member) => { + const totalCredits = getTotalCredits(member, teamEvents); + const initiativeCredits = getInitiativeCredits(member, teamEvents); + const totalCreditsMet = + totalCredits >= + (LEAD_ROLES.includes(member.role) + ? REQUIRED_LEAD_TEC_CREDITS + : REQUIRED_MEMBER_TEC_CREDITS); + const initiativeCreditsMet = initiativeCredits >= REQUIRED_INITIATIVE_CREDITS; + + const isAdvisor = ADVISOR_ROLES.includes(member.role); + + return ( + + + {member.firstName} {member.lastName} ({member.netid}) + {!totalCreditsMet && ( + + ) : ( + + ) + } + member={member} + endOfSemesterReminder={endOfSemesterReminder} + type={'tec'} + /> + )} + + {totalCredits} + {INITIATIVE_EVENTS && ( + {initiativeCredits} + )} + {teamEvents.map((event) => { + const numCredits = calculateTotalCreditsForEvent(member, event); + return {numCredits}; + })} + + ); + }) + : allMembers.map((member) => { + const currentPeriodIndex = getTECPeriod(new Date()); + const currentPeriodCredits = getTotalCredits( + member, + periods[currentPeriodIndex].events + ); + const creditsPerPeriod = getCreditsPerPeriod(member); + + const previousPeriodIndex = + currentPeriodIndex > 0 ? currentPeriodIndex - 1 : null; + const previousPeriodCredits = + previousPeriodIndex !== null ? creditsPerPeriod[previousPeriodIndex] : null; + const requiredCredits = calculateCredits( + previousPeriodCredits, + currentPeriodCredits + ); + + const isAdvisor = ADVISOR_ROLES.includes(member.role); + + return ( + + + {member.firstName} {member.lastName} ({member.netid}) + {requiredCredits > 0 && ( + + ) : ( + + ) + } + member={member} + endOfSemesterReminder={endOfSemesterReminder} + type={'tec'} + /> + )} + + {requiredCredits} + {periods.map((period) => { + const numCredits = period.events + .map((event) => calculateTotalCreditsForEvent(member, event)) + .filter((credits) => credits != null) + .reduce((sum, credits) => sum + credits, 0); + + return {numCredits}; + })} + + ); + })}