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,