diff --git a/client/src/SubmissionForms/KitchenOutcome.tsx b/client/src/SubmissionForms/KitchenOutcome.tsx index 99022ae5..e7a79e86 100644 --- a/client/src/SubmissionForms/KitchenOutcome.tsx +++ b/client/src/SubmissionForms/KitchenOutcome.tsx @@ -27,6 +27,7 @@ import { } from '@mui/material'; import { number } from 'prop-types'; import { RootState } from '../util/redux/store'; +import { getData } from '../util/api'; export default function KitchenOutcome() { // Define the form state type @@ -106,6 +107,8 @@ export default function KitchenOutcome() { const [genderOpen, setGenderOpen] = useState(false); const [racialOpen, setRacialOpen] = useState(false); const [orgOpen, setOrgOpen] = useState(false); + const [yearOpen, setYearOpen] = useState(false); + const [yearError, setYearError] = useState(''); // Initialize formState with the FormState type const noFormState: FormState = { email: user.email, @@ -174,7 +177,7 @@ export default function KitchenOutcome() { { label: 'November', value: '11' }, { label: 'December', value: '12' }, ]; - function validateInputs() { + async function validateInputs() { const racialPercentageSum = formState.mealsAmericanIndian + formState.mealsAsian + @@ -202,6 +205,35 @@ export default function KitchenOutcome() { formState.mealsSeniors + formState.mealsAgeUnknown; let works = true; + + const year = formState.year.getFullYear(); + + if ( + Number.isNaN(year) || + year < 2017 || + year > new Date().getFullYear() + 5 + ) { + works = false; + setYearError('The year is too old or too far in the future.'); + setYearOpen(true); + } else { + try { + console.log('Checking year:', year); + const response = await getData( + `kitchen_outcomes/${year - 2}/${formState.orgId}`, + ); + console.log('Response:', response); + console.log(response.data != null); + if (response.data != null && response.data !== '') { + works = false; + setYearError('A submission for this year already exists.'); + setYearOpen(true); + } + } catch (error) { + console.error('Error checking year:', error); + } + } + if (racialPercentageSum !== 100) { works = false; setRacialOpen(true); @@ -225,7 +257,7 @@ export default function KitchenOutcome() { return works; } const handleSubmit = async () => { - if (validateInputs()) { + if (await validateInputs()) { axios .post('http://localhost:4000/api/kitchen_outcomes/add/', formState) .then((response) => { @@ -1329,6 +1361,19 @@ export default function KitchenOutcome() { // eslint-disable-next-line react/jsx-no-useless-fragment <> )} + {yearOpen ? ( + { + setYearOpen(false); + }} + > + {yearError} + + ) : ( + // eslint-disable-next-line react/jsx-no-useless-fragment + <> + )}
(null); const [yearList, setYearList] = useState([]); const [searchTerm, setSearchTerm] = useState(''); @@ -145,6 +147,7 @@ function KitchenOutcomesVisualization() { const [year, setYear] = useState(''); const [mealType, setMealType] = useState('All'); const [mealRange, setMealRange] = useState('All'); + const [yearRange, setYearRange] = useState([2017, currentYear]); // Default range const tabNames = [ 'Hunger Relief', @@ -182,6 +185,11 @@ function KitchenOutcomesVisualization() { setMealRange(event.target.value); }; + const handleYearRangeChange = (event: Event, newValue: number | number[]) => { + setYearRange(newValue as number[]); + // Trigger network average calculation with new filter + }; + useEffect(() => { const fetchOrgList = async () => { try { @@ -764,6 +772,7 @@ function KitchenOutcomesVisualization() { const fetchAllNetworkAverages = async ( selectedYear: number, + yearRangeFilter: number[], mealTypeFilter: string, mealRangeFilter: string, ) => { @@ -792,8 +801,9 @@ function KitchenOutcomesVisualization() { console.log( `trying to get network avg route with ${field} ${selectedYear} ${mealTypeFilter} ${mealRangeFilter}`, ); + const [startYear, endYear] = yearRangeFilter; const response = await getData( - `kitchen_outcomes/network-average/${field}/${selectedYear}/${mealTypeFilter}/${mealRangeFilter}`, + `kitchen_outcomes/network-average/${field}/${startYear}/${endYear}/${mealTypeFilter}/${mealRangeFilter}`, ); averages[field] = response.data.average; } catch (error) { @@ -805,8 +815,9 @@ function KitchenOutcomesVisualization() { try { console.log('trying to call route with year: ', selectedYear); + const [startYear, endYear] = yearRangeFilter; const response2 = await getData( - `kitchen_outcomes/distri/${selectedYear}/${mealTypeFilter}/${mealRangeFilter}`, + `kitchen_outcomes/distri/${startYear}/${endYear}/${mealTypeFilter}/${mealRangeFilter}`, ); console.log('response data: ', response2.data); const ageRaceData = response2.data; @@ -825,9 +836,9 @@ function KitchenOutcomesVisualization() { useEffect(() => { if (year) { - fetchAllNetworkAverages(Number(year), mealType, mealRange); + fetchAllNetworkAverages(Number(year), yearRange, mealType, mealRange); } - }, [year, mealType, mealRange]); + }, [year, yearRange, mealType, mealRange]); return ( @@ -919,6 +930,17 @@ function KitchenOutcomesVisualization() { ))} + + Year Range + + {/* Tabs - now left justified */} diff --git a/server/src/controllers/kitchen.outcomes.controller.ts b/server/src/controllers/kitchen.outcomes.controller.ts index 2cd4d234..82a70f10 100644 --- a/server/src/controllers/kitchen.outcomes.controller.ts +++ b/server/src/controllers/kitchen.outcomes.controller.ts @@ -20,39 +20,54 @@ const distriController = async ( res: express.Response, next: express.NextFunction, ) => { - const { year, mealType, mealRange } = req.params; + const { startYear, endYear, mealType, mealRange } = req.params; - if (!year) { - next(ApiError.missingFields(['year'])); + if (!startYear) { + next(ApiError.missingFields(['startYear'])); + return; + } + + if (!endYear) { + next(ApiError.missingFields(['endYear'])); return; } try { - const yearNum = parseInt(year, 10); - if (isNaN(yearNum)) { + const startYearNum = parseInt(startYear, 10); + if (isNaN(startYearNum)) { + next(ApiError.badRequest('Invalid year format')); + return; + } + + const endYearNum = parseInt(endYear, 10); + if (isNaN(endYearNum)) { next(ApiError.badRequest('Invalid year format')); return; } console.log( - 'calculating age and race distribution controller for year:', - year, + 'calculating age and race distribution controller for startYear:', + startYear, + 'endYear:', + endYear, ); const ageDistribution = await calculateAgeAndRaceDistributions( - yearNum, + startYearNum, + endYearNum, mealType, mealRange, ); res.status(StatusCode.OK).json({ - year: yearNum, + startYear: startYearNum, + endYear: endYearNum, ageDistribution, }); } catch (error) { next( ApiError.internal( - `Unable to calculate age distribution for year ${year}`, + `Unable to calculate age distribution for year ${startYear} to ${endYear}`, ), ); } @@ -65,30 +80,46 @@ const getNetworkAverageController = async ( res: express.Response, next: express.NextFunction, ) => { - const { field, year, mealType, mealRange } = req.params; + const { field, startYear, endYear, mealType, mealRange } = req.params; - if (!field || !year || !mealType || !mealRange) { - next(ApiError.missingFields(['field', 'year', 'mealType', 'mealRange'])); + if (!field || !startYear || !endYear || !mealType || !mealRange) { + next( + ApiError.missingFields([ + 'field', + 'startYear', + 'endYear', + 'mealType', + 'mealRange', + ]), + ); return; } try { - const yearNum = parseInt(year, 10); - if (isNaN(yearNum)) { + const startYearNum = parseInt(startYear, 10); + if (isNaN(startYearNum)) { + next(ApiError.badRequest('Invalid year format')); + return; + } + + const endYearNum = parseInt(endYear, 10); + if (isNaN(endYearNum)) { next(ApiError.badRequest('Invalid year format')); return; } const average = await getNetworkAverage( field, - yearNum, + startYearNum, + endYearNum, mealType, mealRange, ); res.status(StatusCode.OK).json({ field, - year: yearNum, + startYear: startYearNum, + endYear: endYearNum, average: average ?? null, }); } catch (error) { diff --git a/server/src/routes/kitchen.outcomes.route.ts b/server/src/routes/kitchen.outcomes.route.ts index 2ecee7f2..628c9c41 100644 --- a/server/src/routes/kitchen.outcomes.route.ts +++ b/server/src/routes/kitchen.outcomes.route.ts @@ -17,7 +17,7 @@ import { const router = express.Router(); router.get( - '/distri/:year/:mealType/:mealRange', + '/distri/:startYear/:endYear/:mealType/:mealRange', isAuthenticated, distriController, ); @@ -43,7 +43,7 @@ router.delete('/delete/:id', isAdmin, deleteKitchenOutcomeByIdController); router.post('/add/', isAuthenticated, addKitchenOutcomesController); router.get( - '/network-average/:field/:year/:mealType/:mealRange', + '/network-average/:field/:startYear/:endYear/:mealType/:mealRange', isAuthenticated, getNetworkAverageController, ); diff --git a/server/src/services/kitchen.outcomes.service.ts b/server/src/services/kitchen.outcomes.service.ts index 5ccae016..cae5f5e3 100644 --- a/server/src/services/kitchen.outcomes.service.ts +++ b/server/src/services/kitchen.outcomes.service.ts @@ -5,17 +5,20 @@ import { } from '../models/kitchen.outcomes.model.ts'; const calculateAgeAndRaceDistributions = async ( - year: number, + startYear: number, + endYear: number, mealType: string, mealRange: string, ) => { try { console.log( - 'calculating age and race distribution service for year:', - year, + 'calculating age and race distribution service for start year:', + startYear, + 'end year:', + endYear, ); - const startDate = new Date(Date.UTC(year, 0, 1)); - const endDate = new Date(Date.UTC(year + 1, 0, 1)); + const startDate = new Date(Date.UTC(startYear, 0, 1)); + const endDate = new Date(Date.UTC(endYear + 1, 0, 1)); const matchConditions: any = { year: { $gte: startDate, $lt: endDate }, @@ -146,17 +149,19 @@ export { calculateAgeAndRaceDistributions }; const getNetworkAverage = async ( field: string, - year: number, + startYear: number, + endYear: number, mealType: string, mealRange: string, ): Promise => { - const startDate = new Date(Date.UTC(year, 0, 1)); - const endDate = new Date(Date.UTC(year + 1, 0, 1)); + const startDate = new Date(Date.UTC(startYear, 0, 1)); + const endDate = new Date(Date.UTC(endYear + 1, 0, 1)); try { console.log('Service - Getting network average for:', { field, - year, + startYear, + endYear, startDate, endDate, mealType,