diff --git a/demand-capacity-mgmt-backend/src/main/java/org/eclipse/tractusx/demandcapacitymgmt/demandcapacitymgmtbackend/services/impl/DemandServiceImpl.java b/demand-capacity-mgmt-backend/src/main/java/org/eclipse/tractusx/demandcapacitymgmt/demandcapacitymgmtbackend/services/impl/DemandServiceImpl.java index 337c65b0..647a5c5b 100644 --- a/demand-capacity-mgmt-backend/src/main/java/org/eclipse/tractusx/demandcapacitymgmt/demandcapacitymgmtbackend/services/impl/DemandServiceImpl.java +++ b/demand-capacity-mgmt-backend/src/main/java/org/eclipse/tractusx/demandcapacitymgmt/demandcapacitymgmtbackend/services/impl/DemandServiceImpl.java @@ -166,7 +166,7 @@ public MaterialDemandResponse updateDemand( } ); - triggerDemandAlertsIfNeeded(demandId, userID, demand); + //triggerDemandAlertsIfNeeded(demandId, userID, demand); demand = materialDemandRepository.save(demand); postLogs(demandId, "MATERIAL DEMAND Updated", EventType.GENERAL_EVENT, userID); diff --git a/demand-capacity-mgmt-frontend/package.json b/demand-capacity-mgmt-frontend/package.json index 8a8aa515..55fff6b4 100644 --- a/demand-capacity-mgmt-frontend/package.json +++ b/demand-capacity-mgmt-frontend/package.json @@ -31,6 +31,7 @@ "prop-types": "^15.8.1", "qs": "^6.11.2", "react": "^18.2.0", + "react-datepicker": "^4.23.0", "react-dom": "^18.2.0", "react-icons": "^4.10.1", "react-native": "^0.72.4", diff --git a/demand-capacity-mgmt-frontend/src/components/capacitygroup/CapacityGroupChronogram.tsx b/demand-capacity-mgmt-frontend/src/components/capacitygroup/CapacityGroupChronogram.tsx index e2d258b7..46c1bce3 100644 --- a/demand-capacity-mgmt-frontend/src/components/capacitygroup/CapacityGroupChronogram.tsx +++ b/demand-capacity-mgmt-frontend/src/components/capacitygroup/CapacityGroupChronogram.tsx @@ -20,50 +20,57 @@ * ******************************************************************************** */ import { useEffect, useRef, useState } from "react"; -import { - Bar, - BarChart, - Brush, - CartesianGrid, - ComposedChart, - Legend, - Line, - ReferenceArea, - Tooltip, - XAxis, - YAxis -} from "recharts"; +import { Bar, BarChart, Brush, CartesianGrid, ComposedChart, Legend, Line, ReferenceArea, Tooltip, XAxis, YAxis } from "recharts"; import { CapacityGroupData, SingleCapacityGroup } from "../../interfaces/capacitygroup_interfaces"; import { DemandProp } from "../../interfaces/demand_interfaces"; +import { getWeekNumber } from "../../util/WeeksUtils"; interface CapacityGroupChronogramProps { - capacityGroup: SingleCapacityGroup | null; + capacityGroup: SingleCapacityGroup | null | undefined; materialDemands: DemandProp[] | null; + startDate: Date; + endDate: Date; } function CapacityGroupChronogram(props: CapacityGroupChronogramProps) { + const { capacityGroup, materialDemands, startDate, endDate } = props; type SelectedRangeType = { start: string | null; end: string | null; - }; + } + const rawCapacities = capacityGroup?.capacities || []; - const [capacityGroup] = useState(props.capacityGroup); - const [demands, setDemands] = useState(props.materialDemands); - useEffect(() => { - // Update the component's state when materialDemands prop changes - setDemands(props.materialDemands); - }, [props.materialDemands]); + // Generate a list of dates between the minDate and maxDate + const generatedDates: string[] = []; + const currentDate = new Date(startDate); + while (currentDate <= endDate) { + generatedDates.push(currentDate.toISOString().split('T')[0]); + currentDate.setDate(currentDate.getDate() + 7); + } + // Check for missing dates in the capacities list + const missingDates = generatedDates.filter( + (date) => !rawCapacities.find((c) => c.calendarWeek === date) + ); + + // Create capacity entries with actualCapacity and maximumCapacity as 0 for missing dates + const missingCapacities = missingDates.map((date) => ({ + calendarWeek: date, + actualCapacity: 0, + maximumCapacity: 0, + })); + + // Merge missing capacities with the original capacities list + const processedCapacities = [...rawCapacities, ...missingCapacities]; - const rawCapacities = capacityGroup?.capacities || []; // Calculate demand sums by week const demandSumsByWeek: { [key: string]: number } = {}; - if (demands) { - demands.forEach((demand) => { + if (materialDemands) { + materialDemands.forEach((demand) => { demand.demandSeries?.forEach((demandSeries) => { demandSeries.demandSeriesValues.forEach((demandSeriesValue) => { const week = demandSeriesValue.calendarWeek; @@ -73,34 +80,34 @@ function CapacityGroupChronogram(props: CapacityGroupChronogramProps) { }); } - // Create a mapping of demand sums by calendarWeek - const demandSumsMap: { [key: string]: number } = {}; - Object.keys(demandSumsByWeek).forEach((week) => { - const simplifiedDate = new Date(week).toISOString().split('T')[0]; - demandSumsMap[simplifiedDate] = demandSumsByWeek[week]; - }); - - // Create data for the chart by matching calendarWeek with demand sums - const data: CapacityGroupData[] = rawCapacities.map((d) => { - const simplifiedDate = new Date(d.calendarWeek).toISOString().split('T')[0]; - return { - ...d, - Demand: demandSumsMap[simplifiedDate] || 0, - dateEpoch: new Date(simplifiedDate).getTime(), - calendarWeek: simplifiedDate, - }; - }).sort((a, b) => a.dateEpoch - b.dateEpoch); + const demandSumsMapRef = useRef<{ [key: string]: number }>({}); - const getWeekNumber = (d: Date) => { - d = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())); - d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7)); - const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); + useEffect(() => { + Object.keys(demandSumsByWeek).forEach((week) => { + const simplifiedDate = new Date(week).toISOString().split('T')[0]; + demandSumsMapRef.current[simplifiedDate] = demandSumsByWeek[week]; + }); + }, [demandSumsByWeek, startDate, endDate]); - // Convert the dates to milliseconds for the arithmetic operation - return Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1) / 7); - - }; + const [filteredData, setFilteredData] = useState([]); + useEffect(() => { + if (startDate && endDate) { + const newData = processedCapacities.map((d) => { + const simplifiedDate = new Date(d.calendarWeek).toISOString().split('T')[0]; + return { + ...d, + Demand: demandSumsMapRef.current[simplifiedDate] || 0, + dateEpoch: new Date(simplifiedDate).getTime(), + calendarWeek: simplifiedDate, + }; + }).sort((a, b) => a.dateEpoch - b.dateEpoch); + + setFilteredData(newData.filter((d) => { + return d.dateEpoch >= startDate.getTime() && d.dateEpoch <= endDate.getTime(); + })); + } + }, [demandSumsMapRef, startDate, endDate]); const weekTickFormatter = (tick: string) => { const dateParts = tick.split("-").map((part) => parseInt(part, 10)); @@ -160,21 +167,40 @@ function CapacityGroupChronogram(props: CapacityGroupChronogramProps) { useEffect(() => { const interval = setInterval(() => { if (brushIndexesRef.current?.startIndex !== undefined && brushIndexesRef.current?.endIndex !== undefined) { - const start = data[brushIndexesRef.current.startIndex].calendarWeek; - const end = data[brushIndexesRef.current.endIndex].calendarWeek; + const start = filteredData[brushIndexesRef.current.startIndex].calendarWeek; + const end = filteredData[brushIndexesRef.current.endIndex].calendarWeek; setSelectedRange({ start, end }); } }, timer.current); return () => clearInterval(interval); - }, [data]); + }, [filteredData]); + + const [containerWidth, setContainerWidth] = useState(0); + + useEffect(() => { + const handleResize = () => { + const container = document.getElementById('chart-container'); + if (container) { + setContainerWidth(container.clientWidth); + } + }; + + window.addEventListener('resize', handleResize); + handleResize(); // Initial width + + return () => { + window.removeEventListener('resize', handleResize); + }; + }, []); + return ( -
+
-
-
-
-
- - - -
-
-
- - - ); -} - -export default CapacityGroupDetails; \ No newline at end of file diff --git a/demand-capacity-mgmt-frontend/src/components/capacitygroup/CapacityGroupSumView.tsx b/demand-capacity-mgmt-frontend/src/components/capacitygroup/CapacityGroupSumView.tsx index 19a6e00a..b3c951b2 100644 --- a/demand-capacity-mgmt-frontend/src/components/capacitygroup/CapacityGroupSumView.tsx +++ b/demand-capacity-mgmt-frontend/src/components/capacitygroup/CapacityGroupSumView.tsx @@ -20,92 +20,58 @@ * ******************************************************************************** */ -import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; +import { addWeeks, formatISO, getISOWeek, startOfDay, subWeeks } from 'date-fns'; +import React, { useContext, useEffect, useMemo, useState } from 'react'; import { OverlayTrigger, Tooltip } from 'react-bootstrap'; +import DatePicker from 'react-datepicker'; +import { FaArrowDown, FaArrowRight, FaRegCalendarCheck } from 'react-icons/fa'; import '../../../src/index.css'; import { DemandCategoryContext } from '../../contexts/DemandCategoryProvider'; - -import { addDays, addMonths, addWeeks, format, getISOWeek, startOfMonth } from 'date-fns'; -import { FaArrowDown, FaArrowRight } from 'react-icons/fa'; import { SingleCapacityGroup } from '../../interfaces/capacitygroup_interfaces'; import { DemandProp } from "../../interfaces/demand_interfaces"; +import { generateWeeksForDateRange, getWeekDates } from '../../util/WeeksUtils'; interface WeeklyViewProps { capacityGroup: SingleCapacityGroup | null | undefined; materialDemands: DemandProp[] | null; + updateParentDateRange: (start: Date, end: Date) => void; } +const CapacityGroupSumView: React.FC = ({ capacityGroup, + materialDemands, + updateParentDateRange +}) => { -function getISOWeekMonday(year: number, isoWeek: number): Date { - const january4 = new Date(year, 0, 4); - const diff = (isoWeek - 1) * 7 + (1 - january4.getDay()); - return addDays(january4, diff); -} + const { demandcategories } = useContext(DemandCategoryContext) || {}; -function getWeeksInMonth(year: number, monthIndex: number): number[] { - const firstDayOfMonth = startOfMonth(new Date(year, monthIndex)); - const nextMonth = startOfMonth(addMonths(firstDayOfMonth, 1)); + const currentDate = startOfDay(new Date()); + const defaultStartDateString = formatISO(subWeeks(currentDate, 8), { representation: 'date' }); + const defaultEndDateString = formatISO(addWeeks(currentDate, 53), { representation: 'date' }); - let weeks = []; - let currentDay = firstDayOfMonth; + const [startDate, setStartDate] = useState(new Date(defaultStartDateString)); + const [endDate, setEndDate] = useState(new Date(defaultEndDateString)); - while (currentDay < nextMonth) { - weeks.push(getISOWeek(currentDay)); - currentDay = addWeeks(currentDay, 1); - } + const [weeksForDateRange, setWeeksForDateRange] = useState< + { name: string; year: number; weeks: number[]; monthIndex: number }[] + >([]); - return weeks; -} - -const CapacityGroupSumView: React.FC = ({ capacityGroup, materialDemands }) => { - - const { demandcategories } = useContext(DemandCategoryContext) || {}; - const currentYear = new Date().getFullYear(); - - const monthsCurrentYear = Array.from({ length: 12 }, (_, monthIndex) => { - const monthStart = new Date(currentYear, monthIndex, 1); - const monthName = format(monthStart, 'MMM'); - const weeks = getWeeksInMonth(currentYear, monthIndex); - - - return { - name: monthName, - year: currentYear, - weeks: weeks, - monthIndex: monthIndex, - }; - }); - - const monthsPreviousYear = Array.from({ length: 1 }, (_, monthIndex) => { - const monthStart = new Date(currentYear - 1, monthIndex + 11, 1); - const monthName = format(monthStart, 'MMM'); - const weeks = getWeeksInMonth(currentYear - 1, monthIndex + 11); - - return { - name: monthName, - year: currentYear - 1, - weeks: weeks, - monthIndex: monthIndex + 11, - }; - }); - - const monthsNextYear = Array.from({ length: 1 }, (_, monthIndex) => { - const monthStart = new Date(currentYear + 1, monthIndex, 1); - const monthName = format(monthStart, 'MMM'); - const weeks = getWeeksInMonth(currentYear + 1, monthIndex); - - return { - name: monthName, - year: currentYear + 1, - weeks: weeks, - monthIndex: monthIndex, - }; - }); - - const totalWeeksPreviousYear = monthsPreviousYear.reduce((total, month) => total + month.weeks.length, 0); - const totalWeeksCurrentYear = monthsCurrentYear.reduce((total, month) => total + month.weeks.length, 0); - const totalWeeksNextYear = monthsNextYear.reduce((total, month) => total + month.weeks.length, 0); + const handleStartDateChange = (date: Date | null) => { + if (date) { + const adjustedEndDate = endDate && date.getTime() > endDate.getTime() ? date : endDate; + setStartDate(date); + setEndDate(adjustedEndDate); + setWeeksForDateRange(generateWeeksForDateRange(date, adjustedEndDate)); + } + }; + const handleEndDateChange = (date: Date | null) => { + if (date) { + const adjustedStartDate = startDate && date.getTime() < startDate.getTime() ? date : startDate; + setStartDate(adjustedStartDate); + setEndDate(date); + setWeeksForDateRange(generateWeeksForDateRange(adjustedStartDate, date)); + } + }; //Mapping of categories const idToNumericIdMap: Record = {}; @@ -127,79 +93,43 @@ const CapacityGroupSumView: React.FC = ({ capacityGroup, materi })); }; - const demandSumsByWeek = useMemo(() => { - return {} as Record; // Initialize your object here - }, []); - - const computedDemandSums: Record = useMemo(() => { + const { demandSums, computedSums } = useMemo(() => { + const demandSums: Record> = {}; + const computedSums: Record> = {}; - // Populate demandSumsByWeek if (capacityGroup && materialDemands) { materialDemands.forEach((demand) => { demand.demandSeries?.forEach((demandSeries) => { demandSeries.demandSeriesValues.forEach((demandSeriesValue) => { + const year = new Date(demandSeriesValue.calendarWeek).getFullYear(); const week = getISOWeek(new Date(demandSeriesValue.calendarWeek)); - demandSumsByWeek[week] = (demandSumsByWeek[week] || 0) + demandSeriesValue.demand; - }); - }); - }); - } - const computedSums: Record = {}; + // Populate demandSums + if (!demandSums[year]) { + demandSums[year] = {}; + } + demandSums[year][week] = (demandSums[year][week] || 0) + demandSeriesValue.demand; - for (const week in demandSumsByWeek) { - computedSums[week] = 0; - materialDemands?.forEach((demand) => { - demand.demandSeries?.forEach((demandSeries) => { - demandSeries.demandSeriesValues.forEach((demandSeriesValue) => { - const seriesWeek = getISOWeek(new Date(demandSeriesValue.calendarWeek)); - if (seriesWeek.toString() === week) { - computedSums[week] += demandSeriesValue.demand; + // Populate computedSums + if (!computedSums[year]) { + computedSums[year] = {}; + } + if (!computedSums[year][week]) { + computedSums[year][week] = 0; } + computedSums[year][week] += demandSeriesValue.demand; }); }); }); } - return computedSums; - }, [capacityGroup, materialDemands, demandSumsByWeek]); + return { demandSums, computedSums }; + }, [capacityGroup, materialDemands]); - const demandSums = useMemo(() => { - - - // Populate demandSumsByWeek - if (capacityGroup && materialDemands) { - materialDemands.forEach((demand) => { - demand.demandSeries?.forEach((demandSeries) => { - demandSeries.demandSeriesValues.forEach((demandSeriesValue) => { - const week = getISOWeek(new Date(demandSeriesValue.calendarWeek)); - demandSumsByWeek[week] = (demandSumsByWeek[week] || 0) + demandSeriesValue.demand; - }); - }); - }); - } - - // Iterate over demandSumsByWeek to populate computedDemandSums - for (const week in demandSumsByWeek) { - computedDemandSums[week] = 0; - materialDemands?.forEach((demand) => { - demand.demandSeries?.forEach((demandSeries) => { - demandSeries.demandSeriesValues.forEach((demandSeriesValue) => { - const seriesWeek = getISOWeek(new Date(demandSeriesValue.calendarWeek)); - if (seriesWeek.toString() === week) { - computedDemandSums[week] += demandSeriesValue.demand; - } - }); - }); - }); - } - - return computedDemandSums; - }, [capacityGroup, materialDemands, computedDemandSums, demandSumsByWeek]); // Calculate demand sums for each demand name - const demandSumsByDemandAndWeek: Record> = {}; + const demandSumsByDemandAndWeek: Record>> = {}; if (capacityGroup && materialDemands) { materialDemands.forEach((demand) => { @@ -208,47 +138,33 @@ const CapacityGroupSumView: React.FC = ({ capacityGroup, materi demand.demandSeries?.forEach((demandSeries) => { demandSeries.demandSeriesValues.forEach((demandSeriesValue) => { + const year = new Date(demandSeriesValue.calendarWeek).getFullYear(); const week = getISOWeek(new Date(demandSeriesValue.calendarWeek)); - const demandSum = demandSeriesValue.demand; - demandSumsByDemandAndWeek[demandName][week] = (demandSumsByDemandAndWeek[demandName][week] || 0) + demandSum; + + if (!demandSumsByDemandAndWeek[demandName][year]) { + demandSumsByDemandAndWeek[demandName][year] = {}; + } + + demandSumsByDemandAndWeek[demandName][year][week] = (demandSumsByDemandAndWeek[demandName][year][week] || 0) + demandSeriesValue.demand; }); }); }); } - /*To focus on the first value on the table*/ - const firstNonZeroDemandRef = useRef(null); - - useEffect(() => { - let firstNonZeroDemandWeek: number | null = null; - - // Iterate over demandSums object to find the first non-zero demand week - for (const week in demandSums) { - if (demandSums[week] !== 0) { - firstNonZeroDemandWeek = parseInt(week); - break; - } - } - - if (firstNonZeroDemandWeek !== null) { - const cellElement = document.getElementById(`cell-${firstNonZeroDemandWeek}`); - - // Check if the element exists before focusing - if (cellElement && firstNonZeroDemandRef.current) { - // Focus on the first non-zero demand sum cell - cellElement.focus(); - cellElement.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' }); - } - } - }, [demandSums]); - // Batch update actualCapacityMap - const actualCapacityMap: Record = useMemo(() => { - const capacityMap: Record = {}; + // Batch update actualCapacityMap with year mapping + const actualCapacityMap: Record> = useMemo(() => { + const capacityMap: Record> = {}; if (capacityGroup && capacityGroup.capacities) { capacityGroup.capacities.forEach((capacity) => { + const year = new Date(capacity.calendarWeek).getFullYear(); const week = getISOWeek(new Date(capacity.calendarWeek)); - capacityMap[week] = capacity.actualCapacity; + + if (!capacityMap[year]) { + capacityMap[year] = {}; + } + + capacityMap[year][week] = capacity.actualCapacity; }); } return capacityMap; @@ -259,148 +175,133 @@ const CapacityGroupSumView: React.FC = ({ capacityGroup, materi const deltaMap: Record> = useMemo(() => { const calculatedDeltaMap: Record> = {}; - // Calculate deltas for the previous year - monthsPreviousYear.forEach((month) => { - calculatedDeltaMap[month.year] = calculatedDeltaMap[month.year] || {}; - month.weeks.forEach((week) => { - calculatedDeltaMap[month.year][week] = - (actualCapacityMap[week] || 0) - (computedDemandSums[week] || 0); - }); - }); - - // Calculate deltas for the current year - monthsCurrentYear.forEach((month) => { - calculatedDeltaMap[month.year] = calculatedDeltaMap[month.year] || {}; - month.weeks.forEach((week) => { - calculatedDeltaMap[month.year][week] = - (actualCapacityMap[week] || 0) - (computedDemandSums[week] || 0); - }); - }); + // Calculate deltas for each month in weeksForDateRange + weeksForDateRange.forEach((month) => { + calculatedDeltaMap[month.year] = calculatedDeltaMap[month.year] || {}; // Set up year if not present - // Calculate deltas for the next year - monthsNextYear.forEach((month) => { - calculatedDeltaMap[month.year] = calculatedDeltaMap[month.year] || {}; + // Ensure the correct assignment of capacity and demand sums by week month.weeks.forEach((week) => { calculatedDeltaMap[month.year][week] = - (actualCapacityMap[week] || 0) - (computedDemandSums[week] || 0); + (actualCapacityMap[month.year]?.[week] || 0) - + (computedSums[month.year]?.[week] || 0); }); }); return calculatedDeltaMap; - }, [computedDemandSums, actualCapacityMap, monthsCurrentYear, monthsNextYear, monthsPreviousYear]); // Empty dependency array ensures that this useMemo runs only once - + }, [computedSums, actualCapacityMap, weeksForDateRange]); - // Function to get the beginning and end dates of the week - const getWeekDates = (year: number, month: string, week: number) => { - const startDate = getISOWeekMonday(year, week); - const endDate = new Date(startDate); - endDate.setDate(endDate.getDate() + 6); // Assuming weeks end on Saturdays - - return { - startDate: startDate.toDateString(), - endDate: endDate.toDateString(), - }; - }; + useEffect(() => { + setWeeksForDateRange(generateWeeksForDateRange(startDate, endDate)); + updateParentDateRange(startDate, endDate); + }, [startDate, endDate]); return ( -
+
+
+
+
Data Range
+
+ handleStartDateChange(date)} + selectsStart + startDate={startDate} + endDate={endDate} + placeholderText="Select a Start Date" + showYearDropdown + showMonthDropdown + showWeekNumbers + /> + - + handleEndDateChange(date)} + selectsEnd + startDate={startDate} + endDate={endDate} + minDate={startDate} + placeholderText="Select a End Date" + showMonthDropdown + showYearDropdown + showWeekNumbers + /> +
+
+
- - - + + {weeksForDateRange.reduce((acc: { year: number; weeks: number }[], monthData) => { + const existingYearIndex = acc.findIndex((data) => data.year === monthData.year); + if (existingYearIndex === -1) { + acc.push({ year: monthData.year as number, weeks: monthData.weeks.length as number }); + } else { + acc[existingYearIndex].weeks += monthData.weeks.length; + } + return acc; + }, []).map((yearData, index) => ( + + ))} - {monthsPreviousYear.map((month) => ( - - ))} - {monthsCurrentYear.map((month) => ( - - ))} - {monthsNextYear.map((month) => ( - ))} + + - {monthsPreviousYear.map((month) => - month.weeks.map((week) => ( - - )) - )} - {monthsCurrentYear.map((month) => - month.weeks.map((week) => ( - - )) - )} - {monthsNextYear.map((month) => - month.weeks.map((week) => ( - - )) - )} + + ); + })} @@ -409,21 +310,25 @@ const CapacityGroupSumView: React.FC = ({ capacityGroup, materi {expandedDemandRows['total'] ? : } Demands (Sum) - {monthsPreviousYear.concat(monthsCurrentYear, monthsNextYear).map((month) => - month.weeks.map((week) => ( - - )) + {weeksForDateRange.map((month) => + month.weeks.map((week) => { + const demandSum = demandSums[month.year]?.[week] || 0; + const computedSum = computedSums[month.year]?.[week] || 0; + + return ( + + ); + }) )} + {expandedDemandRows['total'] && ( <> {capacityGroup && @@ -439,10 +344,10 @@ const CapacityGroupSumView: React.FC = ({ capacityGroup, materi {expandedDemandRows[demand.id] ? : } {demand.materialDescriptionCustomer} - {monthsPreviousYear.concat(monthsCurrentYear, monthsNextYear).map((month) => + {weeksForDateRange.map((month) => month.weeks.map((week) => { const demandSum = - demandSumsByDemandAndWeek[demand.materialDescriptionCustomer]?.[week] || null; + demandSumsByDemandAndWeek[demand.materialDescriptionCustomer]?.[month.year]?.[week] || null; return ( ); }) @@ -479,6 +392,7 @@ const CapacityGroupSumView: React.FC = ({ capacityGroup, materi ))} )} + ))} @@ -487,28 +401,29 @@ const CapacityGroupSumView: React.FC = ({ capacityGroup, materi - {monthsPreviousYear.concat(monthsCurrentYear, monthsNextYear).map((month) => - month.weeks.map((week) => ( - - )) - )} + ))} - {monthsPreviousYear.concat(monthsCurrentYear, monthsNextYear).map((month) => + {weeksForDateRange.map((month) => month.weeks.map((week) => { const matchingCapacity = capacityGroup?.capacities.find((capacity) => { const capacityWeek = new Date(capacity.calendarWeek); - return getISOWeek(capacityWeek) === week; + return getISOWeek(capacityWeek) === week && capacityWeek.getFullYear() === month.year; }); const actualCapacity = matchingCapacity?.actualCapacity ?? '-'; return ( - ); @@ -519,16 +434,16 @@ const CapacityGroupSumView: React.FC = ({ capacityGroup, materi - {monthsPreviousYear.concat(monthsCurrentYear, monthsNextYear).map((month) => + {weeksForDateRange.map((month) => month.weeks.map((week) => { const matchingCapacity = capacityGroup?.capacities.find((capacity) => { const capacityWeek = new Date(capacity.calendarWeek); - return getISOWeek(capacityWeek) === week; + return getISOWeek(capacityWeek) === week && capacityWeek.getFullYear() === month.year; }); const maximumCapacity = matchingCapacity?.maximumCapacity ?? '-'; return ( - ); @@ -539,29 +454,43 @@ const CapacityGroupSumView: React.FC = ({ capacityGroup, materi - {monthsPreviousYear.concat(monthsCurrentYear, monthsNextYear).map((month) => - month.weeks.map((week) => ( - - )) - )} + ))} + - {monthsPreviousYear.concat(monthsCurrentYear, monthsNextYear).map((month) => - month.weeks.map((week) => ( - - )) + {weeksForDateRange.map((month) => + month.weeks.map((week) => { + const deltaValue = deltaMap[month.year]?.[week]; + const deltaClass = + deltaValue !== undefined && deltaValue !== null + ? deltaValue < 0 + ? 'bg-light-red' + : deltaValue > 0 + ? 'bg-light-green' + : '' + : ''; + + return ( + + ); + }) )} +
- {currentYear - 1} - - {currentYear} - - {currentYear + 1} - + {yearData.year} +
- {month.name} - - {month.name} - - {month.name} + {/* Render headers based on data */} + {weeksForDateRange.map((monthData) => ( + + {monthData.name}
- - {`Week ${week} - ${getWeekDates(month.year, month.name, week).startDate} to ${getWeekDates( - month.year, - month.name, - week - ).endDate}`} - - } - > - {week} - - - - {`Week ${week} - ${getWeekDates(month.year, month.name, week).startDate} to ${getWeekDates( - month.year, - month.name, - week - ).endDate}`} - - } - > - {week} - - + {weeksForDateRange.reduce((acc, curr) => acc.concat(curr.weeks), []).map((week) => { + // Find the relevant monthData for the current week + const monthData = weeksForDateRange.find((month) => month.weeks.includes(week)); + + if (!monthData) { + // Handle the case where monthData is not found + return null; + } + + return ( + - {`Week ${week} - ${getWeekDates(month.year, month.name, week).startDate} to ${getWeekDates( - month.year, - month.name, + + {`Week ${week} - ${getWeekDates(monthData.year, monthData.name, week).startDate} to ${getWeekDates( + monthData.year, + monthData.name, week ).endDate}`} } > - {week} + {week}
- {demandSums[week] !== 0 ? demandSums[week] : '-'} {/*Todo Stylize */} - + {demandSum !== 0 ? computedSum : '-'} +
{demandSum !== null ? (demandSum || 0) : '-'} @@ -461,15 +366,23 @@ const CapacityGroupSumView: React.FC = ({ capacityGroup, materi {demandSeries.demandCategory.demandCategoryName} - {monthsPreviousYear.concat(monthsCurrentYear, monthsNextYear).map((month) => + {weeksForDateRange.map((month) => month.weeks.map((week) => { - const demandValue = demandSeries.demandSeriesValues.find( - (demandValue) => getISOWeek(new Date(demandValue.calendarWeek)) === week - ); + const demandValue = demandSeries.demandSeriesValues.find((demandValue) => { + const valueDate = new Date(demandValue.calendarWeek); + const valueYear = valueDate.getFullYear(); + const valueWeek = getISOWeek(valueDate); + return valueYear === month.year && valueWeek === week; + }); + const demandSum = demandValue?.demand || null; + return ( - - {demandSum !== null ? (demandSum || 0) : '-'} + + {demandSum !== null ? demandSum || 0 : '-'}
-
+ + {weeksForDateRange + .reduce((acc, curr) => acc.concat(curr.weeks), []) + .map((weekNumber) => ( + {' '}
Actual Capacity
+ {actualCapacity.toString()}
Maximum Capacity
+ {maximumCapacity.toString()}
-
+ {weeksForDateRange + .reduce((acc, curr) => acc.concat(curr.weeks), []) + .map((weekNumber) => ( + {' '}
Delta
0 ? 'bg-light-green' : ''}`} - > - {deltaMap[month.year][week] > 0 ? `+${deltaMap[month.year][week]}` : deltaMap[month.year][week]} - + {typeof deltaValue === 'number' && deltaValue > 0 ? `+${deltaValue}` : deltaValue ?? '-'} +
diff --git a/demand-capacity-mgmt-frontend/src/components/capacitygroup/CapacityGroupsList.tsx b/demand-capacity-mgmt-frontend/src/components/capacitygroup/CapacityGroupsList.tsx index 08743d42..63cfd324 100644 --- a/demand-capacity-mgmt-frontend/src/components/capacitygroup/CapacityGroupsList.tsx +++ b/demand-capacity-mgmt-frontend/src/components/capacitygroup/CapacityGroupsList.tsx @@ -200,9 +200,19 @@ const CapacityGroupsList: React.FC = () => { {capacitygroup.name} - {capacitygroup.customerBPNL} - {capacitygroup.customerName} - {capacitygroup.supplierBNPL} + {user?.role === 'SUPPLIER' && ( + <> + {capacitygroup.customerBPNL} + {capacitygroup.customerName} + + )} + + {user?.role === 'CUSTOMER' && ( + <> + {capacitygroup.supplierBNPL} + + )} + {capacitygroup.numberOfMaterials} {capacitygroup.linkStatus === EventType.TODO ? ( diff --git a/demand-capacity-mgmt-frontend/src/components/capacitygroup/CapacityGroupsTableHeaders.tsx b/demand-capacity-mgmt-frontend/src/components/capacitygroup/CapacityGroupsTableHeaders.tsx index 3fab7071..25b55540 100644 --- a/demand-capacity-mgmt-frontend/src/components/capacitygroup/CapacityGroupsTableHeaders.tsx +++ b/demand-capacity-mgmt-frontend/src/components/capacitygroup/CapacityGroupsTableHeaders.tsx @@ -22,6 +22,7 @@ import React from 'react'; import { BiCaretDown, BiCaretUp } from 'react-icons/bi'; +import { useUser } from '../../contexts/UserContext'; type CapacityGroupsTableProps = { sortColumn: string; @@ -30,7 +31,10 @@ type CapacityGroupsTableProps = { capacitygroupsItems: React.ReactNode; }; + + const CapacityGroupsTable: React.FC = ({ sortColumn, sortOrder, handleSort, capacitygroupsItems }) => { + const { user } = useUser(); return ( @@ -47,21 +51,30 @@ const CapacityGroupsTable: React.FC = ({ sortColumn, s {sortColumn === 'name' && sortOrder === 'asc' && } {sortColumn === 'name' && sortOrder === 'desc' && } - - - + {user?.role === 'SUPPLIER' && ( + <> + + + + )} + + {user?.role === 'CUSTOMER' && ( + <> + + + )}
handleSort('customerBPNL')}> - Customer BPNL - {sortColumn === 'customerBPNL' && sortOrder === 'asc' && } - {sortColumn === 'customerBPNL' && sortOrder === 'desc' && } - handleSort('customerName')}> - Customer Name - {sortColumn === 'customerName' && sortOrder === 'asc' && } - {sortColumn === 'customerName' && sortOrder === 'desc' && } - handleSort('supplierBNPL')}> - Supplier BPNL - {sortColumn === 'supplierBNPL' && sortOrder === 'asc' && } - {sortColumn === 'supplierBNPL' && sortOrder === 'desc' && } - handleSort('customerBPNL')}> + Customer BPNL + {sortColumn === 'customerBPNL' && sortOrder === 'asc' && } + {sortColumn === 'customerBPNL' && sortOrder === 'desc' && } + handleSort('customerName')}> + Customer Name + {sortColumn === 'customerName' && sortOrder === 'asc' && } + {sortColumn === 'customerName' && sortOrder === 'desc' && } + handleSort('supplierBNPL')}> + Supplier BPNL + {sortColumn === 'supplierBNPL' && sortOrder === 'asc' && } + {sortColumn === 'supplierBNPL' && sortOrder === 'desc' && } + handleSort('numberOfMaterials')}> # of Materials {sortColumn === 'numberOfMaterials' && sortOrder === 'asc' && } diff --git a/demand-capacity-mgmt-frontend/src/components/demands/DemandsOverview.tsx b/demand-capacity-mgmt-frontend/src/components/demands/DemandsOverview.tsx index ffcbe173..82e9242f 100644 --- a/demand-capacity-mgmt-frontend/src/components/demands/DemandsOverview.tsx +++ b/demand-capacity-mgmt-frontend/src/components/demands/DemandsOverview.tsx @@ -22,71 +22,30 @@ import moment from 'moment'; import 'moment-weekday-calc'; -import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; +import React, { useCallback, useContext, useEffect, useState } from 'react'; import { Button, ButtonGroup, OverlayTrigger, ToggleButton, Tooltip } from 'react-bootstrap'; import { DemandCategoryContext } from '../../contexts/DemandCategoryProvider'; import { DemandContext } from '../../contexts/DemandContextProvider'; import '../../index.css'; import { Demand, DemandCategory, DemandProp, DemandSeriesValue, MaterialDemandSery } from '../../interfaces/demand_interfaces'; - -import { format, getISOWeek, } from 'date-fns'; +import { addWeeks, formatISO, getISOWeek, subWeeks } from 'date-fns'; +import { startOfDay } from 'date-fns/esm'; +import DatePicker from 'react-datepicker'; +import 'react-datepicker/dist/react-datepicker.css'; +import { FaRegCalendarCheck } from 'react-icons/fa'; import { useNavigate } from 'react-router-dom'; import { useUser } from '../../contexts/UserContext'; +import { generateWeeksForDateRange, getISOWeekMonday } from '../../util/WeeksUtils'; import { LoadingGatheringDataMessage } from '../common/LoadingMessages'; interface WeeklyViewProps { demandId: string; } -function getISOWeekMonday(year: number, isoWeek: number): moment.Moment { - return moment().year(year).isoWeek(isoWeek).startOf('isoWeek'); -} - -function getYearOfWeek(date: moment.Moment): number { - return date.add(3, 'days').year(); -} - - -function getWeeksInMonth(year: number, monthIndex: number, knownNextMonthWeeks?: Set): number[] { - const weeks: Set = new Set(); - - const firstDayOfMonth = moment().year(year).month(monthIndex).startOf('month'); - const lastDayOfMonth = moment().year(year).month(monthIndex).endOf('month'); - // Fetch weeks of the next month if not provided. - if (!knownNextMonthWeeks && monthIndex < 11) { - knownNextMonthWeeks = new Set(getWeeksInMonth(year, monthIndex + 1)); - } - - let currentDay = firstDayOfMonth; - while (currentDay <= lastDayOfMonth) { - const weekNum = currentDay.week(); - const isoWeekYear = getYearOfWeek(currentDay); - - // If the month is January and the week year is the previous year, skip it - if (monthIndex === 0 && isoWeekYear < year) { - currentDay = currentDay.add(1, 'days'); - continue; - } - - // If it's the last week of the month and it's also in the next month, skip it. - if (currentDay.isAfter(moment(new Date(year, monthIndex, 24))) && knownNextMonthWeeks?.has(weekNum)) { - currentDay = currentDay.add(1, 'days'); - continue; - } - - weeks.add(weekNum); - currentDay = currentDay.add(1, 'days'); - } - return Array.from(weeks).sort((a, b) => a - b); -} - - - const WeeklyView: React.FC = ({ demandId }) => { const { updateDemand } = useContext(DemandContext)!; const { demandcategories } = useContext(DemandCategoryContext) ?? {}; - const currentYear = new Date().getFullYear(); const [editMode, setEditMode] = useState(false); const [isLoading, setIsLoading] = useState(true); const { user } = useUser(); @@ -111,50 +70,40 @@ const WeeklyView: React.FC = ({ demandId }) => { } }, [demandId, getDemandbyId, setDemandData, navigate]); - useEffect(() => { - fetchDemandData(); - }, [fetchDemandData]); + const currentDate = startOfDay(new Date()); + const defaultStartDateString = formatISO(subWeeks(currentDate, 8), { representation: 'date' }); + const defaultEndDateString = formatISO(addWeeks(currentDate, 53), { representation: 'date' }); - const monthsPreviousYear = Array.from({ length: 1 }, (_, monthIndex) => { - const monthStart = new Date(currentYear - 1, monthIndex + 11, 1); - const monthName = format(monthStart, 'MMM'); // <-- This line - let weeks = getWeeksInMonth(currentYear - 1, monthIndex + 11); - return { - name: monthName, - year: currentYear - 1, - weeks: weeks, - monthIndex: monthIndex + 11, - }; - }); + const [startDate, setStartDate] = useState(new Date(defaultStartDateString)); + const [endDate, setEndDate] = useState(new Date(defaultEndDateString)); + const [weeksForDateRange, setWeeksForDateRange] = useState< + { name: string; year: number; weeks: number[]; monthIndex: number }[] + >([]); - const monthsCurrentYear = Array.from({ length: 12 }, (_, monthIndex) => { - const monthStart = new Date(currentYear, monthIndex, 1); - const monthName = format(monthStart, 'MMM'); - let weeks = getWeeksInMonth(currentYear, monthIndex); - return { - name: monthName, - year: currentYear, - weeks: weeks, - monthIndex: monthIndex, - }; - }); + const handleStartDateChange = (date: Date | null) => { + if (date) { + const adjustedEndDate = endDate && date.getTime() > endDate.getTime() ? date : endDate; + setStartDate(date); + setEndDate(adjustedEndDate); + setWeeksForDateRange(generateWeeksForDateRange(date, adjustedEndDate)); + } + }; - const monthsNextYear = Array.from({ length: 1 }, (_, monthIndex) => { - const monthStart = new Date(currentYear + 1, monthIndex, 1); - const monthName = format(monthStart, 'MMM'); - const weeks = getWeeksInMonth(currentYear + 1, monthIndex); - return { - name: monthName, - year: currentYear + 1, - weeks: weeks, - monthIndex: monthIndex, - }; - }); + const handleEndDateChange = (date: Date | null) => { + if (date) { + const adjustedStartDate = startDate && date.getTime() < startDate.getTime() ? date : startDate; + setStartDate(adjustedStartDate); + setEndDate(date); + setWeeksForDateRange(generateWeeksForDateRange(adjustedStartDate, date)); + } + }; - const totalWeeksPreviousYear = monthsPreviousYear.reduce((total, month) => total + month.weeks.length, 0); - const totalWeeksCurrentYear = monthsCurrentYear.reduce((total, month) => total + month.weeks.length, 0); - const totalWeeksNextYear = monthsNextYear.reduce((total, month) => total + month.weeks.length, 0); + + useEffect(() => { + fetchDemandData(); + setWeeksForDateRange(generateWeeksForDateRange(startDate, endDate)); + }, [fetchDemandData, startDate, endDate]); // Object to store the demand values based on year, month, and week type DemandValuesMap = Record>>; @@ -181,9 +130,6 @@ const WeeklyView: React.FC = ({ demandId }) => { }; }; - - - useEffect(() => { const newDemandValuesMap: DemandValuesMap = {}; @@ -192,7 +138,7 @@ const WeeklyView: React.FC = ({ demandId }) => { series.demandSeriesValues.forEach((value) => { const date = moment(value.calendarWeek); - const year = moment().year(); + const year = date.year(); const week = date.week(); if (!newDemandValuesMap[categoryId]) { @@ -208,41 +154,10 @@ const WeeklyView: React.FC = ({ demandId }) => { setDemandValuesMap(newDemandValuesMap); }, [demandData]); - - const tableRef = useRef(null); - const [shouldScroll, setShouldScroll] = useState(false); - - useEffect(() => { - if (shouldScroll && tableRef.current && Object.keys(demandValuesMap).length > 0) { - for (const categoryId in demandValuesMap) { - const categoryData: Record> = demandValuesMap[categoryId]; - const years = Object.keys(categoryData); - for (const year of years) { - const weeksWithData = Object.keys(categoryData[year]); - if (weeksWithData.length > 0) { - const firstWeekWithData = parseInt(weeksWithData[0], 10); - const weekHeaderCell = tableRef.current!.querySelector(`#week-${firstWeekWithData}`); - if (weekHeaderCell) { - weekHeaderCell.scrollIntoView({ - behavior: 'smooth', - block: 'center', - inline: 'center', - }); - } - } - } - } - // Reset the shouldScroll state to prevent continuous scrolling - setShouldScroll(false); - } - }, [demandValuesMap, shouldScroll]); - - // Use another useEffect to listen for changes in the tableRef - useEffect(() => { - if (tableRef.current) { - setShouldScroll(true); - } - }, [demandValuesMap, tableRef]); + /*useEffect(() => { + console.log(demandValuesMap) + console.log(weeksForDateRange) + }, [demandValuesMap]);*/ const handleSave = async () => { @@ -257,19 +172,13 @@ const WeeklyView: React.FC = ({ demandId }) => { // Check if there is data for this category in demandValuesMap if (demandValuesMap[categoryId]) { - // Loop through months and weeks to populate demandSeriesValues - monthsCurrentYear.forEach((month) => { - month.weeks.forEach((week) => { - const isoWeekMonday = getISOWeekMonday(month.year, week); - isoWeekMonday.format('YYYY-MM-dd') - // Get the Monday of the ISO week - const demand = demandValuesMap[categoryId]?.[month.year]?.[week]; - if (demand !== undefined) { - demandSeriesValues.push({ - calendarWeek: isoWeekMonday.format('YYYY-MM-DD'), - demand: demand, - }); - } + Object.entries(demandValuesMap[categoryId]).forEach(([year, yearData]) => { + Object.entries(yearData).forEach(([week, demand]) => { + const isoWeekMonday = getISOWeekMonday(parseInt(year), parseInt(week)); + demandSeriesValues.push({ + calendarWeek: isoWeekMonday.format('YYYY-MM-DD'), + demand: demand, + }); }); }); @@ -303,6 +212,7 @@ const WeeklyView: React.FC = ({ demandId }) => { unitMeasureId: demandData.unitMeasureId.id, }; + console.log(updatedDemand) // Perform save operation with updatedDemandData if (filteredUpdatedDemandSeries.length > 0) { try { @@ -380,54 +290,101 @@ const WeeklyView: React.FC = ({ demandId }) => { )} +
+
+
Data Range
+
+ handleStartDateChange(date)} + selectsStart + startDate={startDate} + endDate={endDate} + placeholderText="Select a Start Date" + showYearDropdown + showMonthDropdown + showWeekNumbers + /> + - + handleEndDateChange(date)} + selectsEnd + startDate={startDate} + endDate={endDate} + minDate={startDate} + placeholderText="Select a End Date" + showMonthDropdown + showYearDropdown + showWeekNumbers + /> +
+
+
-
+
- +
- - - + + {weeksForDateRange.reduce((acc: { year: number; weeks: number }[], monthData) => { + const existingYearIndex = acc.findIndex((data) => data.year === monthData.year); + if (existingYearIndex === -1) { + acc.push({ year: monthData.year as number, weeks: monthData.weeks.length as number }); + } else { + acc[existingYearIndex].weeks += monthData.weeks.length; + } + return acc; + }, []).map((yearData, index) => ( + + ))} - {monthsPreviousYear.map((month) => ( - - ))} - {monthsCurrentYear.map((month) => ( - - ))} - {monthsNextYear.map((month) => ( - ))} + + - {[monthsPreviousYear, monthsCurrentYear, monthsNextYear].reduce((acc, curr) => acc.concat(curr), []).map((month) => - month.weeks.map((week) => ( - - )) - )} + + ); + })} - {demandcategories?.sort((a, b) => a.id.localeCompare(b.id)) - .map((category: DemandCategory) => ( - - - {monthsPreviousYear.concat(monthsCurrentYear, monthsNextYear).map((month) => ( - - {month.weeks.map((week: number) => ( - + + {weeksForDateRange.map((month) => ( + month.weeks.map((week: number, index: number) => ( + - ))} - - ))} - - ))} + + return { + ...prevDemandValuesMap, + [category.id]: categoryMap, + }; + }); + }} + /> + ) : ( + + {demandValuesMap[category.id]?.[month.year]?.[week] !== undefined + ? demandValuesMap[category.id]?.[month.year]?.[week] === 0 + ? '0' + : demandValuesMap[category.id]?.[month.year]?.[week] + : ''} + + )} + + )) + ))} + + ))} +
- {currentYear - 1} - - {currentYear} - - {currentYear + 1} - + {yearData.year} +
- {month.name} - - {month.name} - - {month.name} + {/* Render headers based on data */} + {weeksForDateRange.map((monthData) => ( + + {monthData.name}
+ {weeksForDateRange.reduce((acc, curr) => acc.concat(curr.weeks), []).map((week) => { + // Find the relevant monthData for the current week + const monthData = weeksForDateRange.find((month) => month.weeks.includes(week)); + + if (!monthData) { + // Handle the case where monthData is not found + return null; + } + + return ( + - {`Week ${week} - ${getWeekDates(month.year, month.name, week).startDate} to ${getWeekDates( - month.year, - month.name, + + {`Week ${week} - ${getWeekDates(monthData.year, monthData.name, week).startDate} to ${getWeekDates( + monthData.year, + monthData.name, week ).endDate}`} @@ -436,72 +393,71 @@ const WeeklyView: React.FC = ({ demandId }) => { {week}
-
{category.demandCategoryName}
-
- {editMode ? ( - a.id.localeCompare(b.id)).map((category: DemandCategory) => ( +
+
{category.demandCategoryName}
+
+ {editMode ? ( + { + const inputValue = event.target.value; + const numericValue = inputValue.replace(/\D/g, ''); // Remove non-numeric characters + + setDemandValuesMap((prevDemandValuesMap) => { + const categoryMap = { + ...(prevDemandValuesMap[category.id] || {}), + [month.year]: { + ...(prevDemandValuesMap[category.id]?.[month.year] || {}), + }, + }; + + if (inputValue === '' || numericValue === '0') { + delete categoryMap[month.year]?.[week]; + + if (Object.keys(categoryMap[month.year]).length === 0) { + delete categoryMap[month.year]; + } + } else if (/^[0-9]\d*$/.test(numericValue)) { + categoryMap[month.year][week] = parseInt(numericValue, 10); } - onChange={(event) => { - const inputValue = event.target.value; - const numericValue = inputValue.replace(/\D/g, ''); // Remove non-numeric characters - - setDemandValuesMap((prevDemandValuesMap) => { - const categoryMap = { - ...(prevDemandValuesMap[category.id] || {}), - [month.year]: { - ...(prevDemandValuesMap[category.id]?.[month.year] || {}), - }, - }; - - if (inputValue === '' || numericValue === '0') { - delete categoryMap[month.year]?.[week]; - - if (Object.keys(categoryMap[month.year]).length === 0) { - delete categoryMap[month.year]; - } - } else if (/^[0-9]\d*$/.test(numericValue)) { - categoryMap[month.year][week] = parseInt(numericValue, 10); - } - - return { - ...prevDemandValuesMap, - [category.id]: categoryMap, - }; - }); - }} - /> - ) : ( - - {demandValuesMap[category.id]?.[month.year]?.[week] !== undefined - ? demandValuesMap[category.id]?.[month.year]?.[week] === 0 - ? '0' - : demandValuesMap[category.id]?.[month.year]?.[week] - : ''} - - )} -
diff --git a/demand-capacity-mgmt-frontend/src/components/pages/CapacityGroupDetailsPage.tsx b/demand-capacity-mgmt-frontend/src/components/pages/CapacityGroupDetailsPage.tsx index 20916867..36f88efa 100644 --- a/demand-capacity-mgmt-frontend/src/components/pages/CapacityGroupDetailsPage.tsx +++ b/demand-capacity-mgmt-frontend/src/components/pages/CapacityGroupDetailsPage.tsx @@ -51,6 +51,11 @@ function CapacityGroupDetailsPage() { const [capacityGroupEvents, setcapacityGroupEvents] = useState([]); const navigate = useNavigate() + + const [startDate, setStartDate] = useState(new Date()); + const [endDate, setEndDate] = useState(new Date()); + + useEffect(() => { if (id) { (async () => { @@ -90,7 +95,10 @@ function CapacityGroupDetailsPage() { } }, [id, getCapacityGroupById, fetchFilteredEvents, navigate, getDemandbyId]); - + function updateParentDateRange(start: Date, end: Date) { + setStartDate(start); + setEndDate(end); + } const memoizedComponent = useMemo(() => { if (!capacityGroup) { @@ -121,8 +129,18 @@ function CapacityGroupDetailsPage() { }} > - - + +
+ +
@@ -137,7 +155,7 @@ function CapacityGroupDetailsPage() {
); - }, [capacityGroup, capacityGroupEvents, materialDemands, activeTab]); + }, [capacityGroup, capacityGroupEvents, materialDemands, activeTab, startDate, endDate]); return memoizedComponent; } diff --git a/demand-capacity-mgmt-frontend/src/components/pages/CapacityGroupPage.tsx b/demand-capacity-mgmt-frontend/src/components/pages/CapacityGroupPage.tsx index b0e19b2e..e64b106e 100644 --- a/demand-capacity-mgmt-frontend/src/components/pages/CapacityGroupPage.tsx +++ b/demand-capacity-mgmt-frontend/src/components/pages/CapacityGroupPage.tsx @@ -28,16 +28,16 @@ function CapacityGroupPage() { return ( <> -
-
-
-
- - - +
+
+
+
+ + + +
-
); diff --git a/demand-capacity-mgmt-frontend/src/components/pages/FavoritesPage.tsx b/demand-capacity-mgmt-frontend/src/components/pages/FavoritesPage.tsx index f244c1a0..2b01e0e8 100644 --- a/demand-capacity-mgmt-frontend/src/components/pages/FavoritesPage.tsx +++ b/demand-capacity-mgmt-frontend/src/components/pages/FavoritesPage.tsx @@ -24,11 +24,11 @@ import { Tab, Tabs } from "react-bootstrap"; import { BsFillBookmarkStarFill } from "react-icons/bs"; import { FavoritesContext } from "../../contexts/FavoritesContextProvider"; import { LoadingMessage } from "../common/LoadingMessages"; +import FavoritesTableAddressBook from "../favorites/FavoritesTableAddressBook"; import FavoritesTableCapacityGroup from "../favorites/FavoritesTableCapacityGroup"; import FavoriteTableCompanies from "../favorites/FavoritesTableCompanies"; import FavoritesTableEvents from "../favorites/FavoritesTableEvents"; import FavoriteTableMaterialDemands from "../favorites/FavoritesTableMaterialDemands"; -import FavoritesTableAddressBook from "../favorites/FavoritesTableAddressBook"; const FavoritesPage: React.FC = () => { const { favorites } = useContext(FavoritesContext)!; diff --git a/demand-capacity-mgmt-frontend/src/custom-bootstrap.scss b/demand-capacity-mgmt-frontend/src/custom-bootstrap.scss index 1aa7a26c..e0375cae 100644 --- a/demand-capacity-mgmt-frontend/src/custom-bootstrap.scss +++ b/demand-capacity-mgmt-frontend/src/custom-bootstrap.scss @@ -26,6 +26,14 @@ $theme-colors: ( info: $theme1-info, ); +$container-max-widths: ( + sm: 540px, + md: 720px, + lg: 960px, + xl: 1200px, + xxl: 1650px +); + @import '~bootstrap/scss/functions'; @import '~bootstrap/scss/mixins'; @import '~bootstrap/scss/root'; @@ -34,6 +42,9 @@ $theme-colors: ( @import '~bootstrap/scss/buttons'; @import '~bootstrap/scss/dropdown'; @import '~bootstrap/scss/badge'; +@import '~bootstrap/scss/bootstrap'; + + // Override button text color .btn { diff --git a/demand-capacity-mgmt-frontend/src/index.css b/demand-capacity-mgmt-frontend/src/index.css index b1241652..47d22220 100644 --- a/demand-capacity-mgmt-frontend/src/index.css +++ b/demand-capacity-mgmt-frontend/src/index.css @@ -130,6 +130,24 @@ code { font-size: small; } +/*Data Range Selectors*/ + +.data-range-container { + display: flex; + /* Adjust this height as needed */ +} + +.pop-out-section { + padding: 10px; +} + +.text-muted { + color: #6c757d; + /* Adjust text color */ + font-size: 14px; +} + + /*Sum View Tables*/ .table-container { overflow-x: auto; @@ -182,7 +200,6 @@ code { position: sticky; padding: 0; left: 0; - z-index: 1; } .sticky-header-content { diff --git a/demand-capacity-mgmt-frontend/src/util/WeeksUtils.tsx b/demand-capacity-mgmt-frontend/src/util/WeeksUtils.tsx new file mode 100644 index 00000000..461cde61 --- /dev/null +++ b/demand-capacity-mgmt-frontend/src/util/WeeksUtils.tsx @@ -0,0 +1,114 @@ +/* + * ******************************************************************************* + * Copyright (c) 2023 BMW AG + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ******************************************************************************** + */ + +import moment from "moment"; + +export function getISOWeekMonday(year: number, isoWeek: number): moment.Moment { + return moment().year(year).isoWeek(isoWeek).startOf('isoWeek'); +} + +export function getYearOfWeek(date: moment.Moment): number { + return date.add(3, 'days').year(); +} + +export const getWeekNumber = (date: Date) => { + const momentDate = moment(date); + return momentDate.isoWeek(); +}; + +export function getWeeksInMonth(year: number, monthIndex: number, knownNextMonthWeeks?: Set): number[] { + const weeks: Set = new Set(); + + const firstDayOfMonth = moment().year(year).month(monthIndex).startOf('month'); + const lastDayOfMonth = moment().year(year).month(monthIndex).endOf('month'); + // Fetch weeks of the next month if not provided. + if (!knownNextMonthWeeks && monthIndex < 11) { + knownNextMonthWeeks = new Set(getWeeksInMonth(year, monthIndex + 1)); + } + + let currentDay = firstDayOfMonth; + while (currentDay <= lastDayOfMonth) { + const weekNum = currentDay.week(); + const isoWeekYear = getYearOfWeek(currentDay); + + // If the month is January and the week year is the previous year, skip it + if (monthIndex === 0 && isoWeekYear < year) { + currentDay = currentDay.add(1, 'days'); + continue; + } + + // If it's the last week of the month and it's also in the next month, skip it. + if (currentDay.isAfter(moment(new Date(year, monthIndex, 24))) && knownNextMonthWeeks?.has(weekNum)) { + currentDay = currentDay.add(1, 'days'); + continue; + } + + weeks.add(weekNum); + currentDay = currentDay.add(1, 'days'); + } + return Array.from(weeks).sort((a, b) => a - b); +} + + +export const generateWeeksForDateRange = (start: Date, end: Date) => { + const weeks: { name: string; year: number; weeks: number[]; monthIndex: number }[] = []; + let currentDate = moment(start).startOf('isoWeek'); + + while (currentDate.isSameOrBefore(moment(end), 'day')) { + const year = currentDate.year(); + const monthName = currentDate.format('MMMM'); + const weeksInMonth = getWeeksInMonth(year, currentDate.month()); + + const monthIndex = currentDate.month(); + + const existingMonthIndex = weeks.findIndex((monthData) => monthData.year === year && monthData.monthIndex === monthIndex); + + if (existingMonthIndex === -1) { + weeks.push({ + name: monthName, + year, + weeks: weeksInMonth, + monthIndex, + }); + } else { + weeks[existingMonthIndex].weeks.push(...weeksInMonth); + } + + currentDate.add(1, 'month'); + } + + return weeks; +}; + + +// Function to get the beginning and end dates of the week +export const getWeekDates = (year: number, month: string, week: number) => { + const startDate = getISOWeekMonday(year, week); + + const endDate = new Date(startDate.toDate()); + endDate.setDate(endDate.getDate() + 6); // Assuming weeks end on Saturdays + + return { + startDate: startDate.toString(), + endDate: endDate.toDateString(), + }; +}; diff --git a/demand-capacity-mgmt-frontend/yarn.lock b/demand-capacity-mgmt-frontend/yarn.lock index 8e30021c..4e67277b 100644 --- a/demand-capacity-mgmt-frontend/yarn.lock +++ b/demand-capacity-mgmt-frontend/yarn.lock @@ -4535,7 +4535,7 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@^2.2.5, classnames@^2.3.2: +classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== @@ -10572,6 +10572,18 @@ react-bootstrap@^2.8.0: uncontrollable "^7.2.1" warning "^4.0.3" +react-datepicker@^4.23.0: + version "4.23.0" + resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.23.0.tgz#0da50b3815b3350b7df6cf0dbc63e37c6c3079b4" + integrity sha512-w+msqlOZ14v6H1UknTKtZw/dw9naFMgAOspf59eY130gWpvy5dvKj/bgsFICDdvxB7PtKWxDcbGlAqCloY1d2A== + dependencies: + "@popperjs/core" "^2.11.8" + classnames "^2.2.6" + date-fns "^2.30.0" + prop-types "^15.7.2" + react-onclickoutside "^6.13.0" + react-popper "^2.3.0" + react-dev-utils@^12.0.1: version "12.0.1" resolved "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz" @@ -10719,7 +10731,12 @@ react-oauth2-auth-code-flow@^1.0.2: react "^16.13.1" react-oauth-flow "^1.2.0" -react-popper@^2.2.5: +react-onclickoutside@^6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz#e165ea4e5157f3da94f4376a3ab3e22a565f4ffc" + integrity sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A== + +react-popper@^2.2.5, react-popper@^2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz" integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==