diff --git a/client/src/AddOrganization/AddOrganization.tsx b/client/src/AddOrganization/AddOrganization.tsx index f62c2b8d..7f78310c 100644 --- a/client/src/AddOrganization/AddOrganization.tsx +++ b/client/src/AddOrganization/AddOrganization.tsx @@ -7,6 +7,7 @@ import { Select, MenuItem, Grid, + Snackbar, Typography, } from '@mui/material'; import { textAlign } from '@mui/system'; @@ -30,8 +31,69 @@ export default function AddOrganization() { state: '', zip: '', }; - + const states = [ + { name: 'Alabama', abbreviation: 'AL' }, + { name: 'Alaska', abbreviation: 'AK' }, + { name: 'Arizona', abbreviation: 'AZ' }, + { name: 'Arkansas', abbreviation: 'AR' }, + { name: 'California', abbreviation: 'CA' }, + { name: 'Colorado', abbreviation: 'CO' }, + { name: 'Connecticut', abbreviation: 'CT' }, + { name: 'Delaware', abbreviation: 'DE' }, + { name: 'Florida', abbreviation: 'FL' }, + { name: 'Georgia', abbreviation: 'GA' }, + { name: 'Hawaii', abbreviation: 'HI' }, + { name: 'Idaho', abbreviation: 'ID' }, + { name: 'Illinois', abbreviation: 'IL' }, + { name: 'Indiana', abbreviation: 'IN' }, + { name: 'Iowa', abbreviation: 'IA' }, + { name: 'Kansas', abbreviation: 'KS' }, + { name: 'Kentucky', abbreviation: 'KY' }, + { name: 'Louisiana', abbreviation: 'LA' }, + { name: 'Maine', abbreviation: 'ME' }, + { name: 'Maryland', abbreviation: 'MD' }, + { name: 'Massachusetts', abbreviation: 'MA' }, + { name: 'Michigan', abbreviation: 'MI' }, + { name: 'Minnesota', abbreviation: 'MN' }, + { name: 'Mississippi', abbreviation: 'MS' }, + { name: 'Missouri', abbreviation: 'MO' }, + { name: 'Montana', abbreviation: 'MT' }, + { name: 'Nebraska', abbreviation: 'NE' }, + { name: 'Nevada', abbreviation: 'NV' }, + { name: 'New Hampshire', abbreviation: 'NH' }, + { name: 'New Jersey', abbreviation: 'NJ' }, + { name: 'New Mexico', abbreviation: 'NM' }, + { name: 'New York', abbreviation: 'NY' }, + { name: 'North Carolina', abbreviation: 'NC' }, + { name: 'North Dakota', abbreviation: 'ND' }, + { name: 'Ohio', abbreviation: 'OH' }, + { name: 'Oklahoma', abbreviation: 'OK' }, + { name: 'Oregon', abbreviation: 'OR' }, + { name: 'Pennsylvania', abbreviation: 'PA' }, + { name: 'Rhode Island', abbreviation: 'RI' }, + { name: 'South Carolina', abbreviation: 'SC' }, + { name: 'South Dakota', abbreviation: 'SD' }, + { name: 'Tennessee', abbreviation: 'TN' }, + { name: 'Texas', abbreviation: 'TX' }, + { name: 'Utah', abbreviation: 'UT' }, + { name: 'Vermont', abbreviation: 'VT' }, + { name: 'Virginia', abbreviation: 'VA' }, + { name: 'Washington', abbreviation: 'WA' }, + { name: 'West Virginia', abbreviation: 'WV' }, + { name: 'Wisconsin', abbreviation: 'WI' }, + { name: 'Wyoming', abbreviation: 'WY' }, + ]; const [newOrg, setNewOrg] = useState(defaultState); + type Notification = { + message: string; + open: boolean; + }; + const defaultNotification: Notification = { + message: 'Organization has been successfully added', + open: false, + }; + const [notificationEdit, setNotificationEdit] = + useState(defaultNotification); const validateInputs = () => { return ( newOrg.state.length > 0 && @@ -45,7 +107,10 @@ export default function AddOrganization() { try { console.log(newOrg); const response = await postData('organization/new', newOrg); - console.log('Organization Submitted Successfully: ', response); + if (response.data) { + console.log('Organization Submitted Successfully: ', response); + setNotificationEdit({ ...notificationEdit, open: true }); + } setNewOrg(defaultState); } catch (error) { console.error('Error submitting program outcome:', error); @@ -103,15 +168,25 @@ export default function AddOrganization() { - { - setNewOrg({ ...newOrg, state: e.target.value }); - }} - /> + + State + + @@ -140,6 +215,14 @@ export default function AddOrganization() { + { + setNotificationEdit({ ...notificationEdit, open: false }); + }} + message={notificationEdit.message} + /> ); } diff --git a/client/src/AdminDashboard/OrgAdminPage.tsx b/client/src/AdminDashboard/OrgAdminPage.tsx index e0006d10..1db5c0a6 100644 --- a/client/src/AdminDashboard/OrgAdminPage.tsx +++ b/client/src/AdminDashboard/OrgAdminPage.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { Typography, Grid, Button } from '@mui/material'; -import { useNavigate } from 'react-router-dom'; import ScreenGrid from '../components/ScreenGrid.tsx'; import OrgTable from './OrgTable.tsx'; import InviteUserButton from '../components/buttons/InviteUserButton.tsx'; @@ -10,7 +9,6 @@ import InviteUserButton from '../components/buttons/InviteUserButton.tsx'; * Admin to delete users from admin and promote users to admin. */ function OrgAdminPage() { - const navigate = useNavigate(); return ( @@ -23,21 +21,6 @@ function OrgAdminPage() { - - - ); } diff --git a/client/src/AdminDashboard/OrgTable.tsx b/client/src/AdminDashboard/OrgTable.tsx index 822f8bdf..7b80ac97 100644 --- a/client/src/AdminDashboard/OrgTable.tsx +++ b/client/src/AdminDashboard/OrgTable.tsx @@ -1,9 +1,11 @@ +/* eslint-disable react/jsx-no-useless-fragment */ import React, { useEffect, useState } from 'react'; import CircularProgress from '@mui/material/CircularProgress'; import Select, { SelectChangeEvent } from '@mui/material/Select'; import MenuItem from '@mui/material/MenuItem'; import Snackbar from '@mui/material/Snackbar'; -import { Box, Button } from '@mui/material'; +import { Box, Button, Grid } from '@mui/material'; +import { useNavigate } from 'react-router-dom'; import { deleteData, getData } from '../util/api.tsx'; import { PaginationTable, TColumn } from '../components/PaginationTable.tsx'; @@ -34,6 +36,9 @@ function OrgTable() { { _id: string; organizationName: string }[] >([]); const [selectedOrgId, setSelectedOrgId] = useState('No Org'); + const [selectedOrgName, setSelectedOrgName] = useState( + undefined, + ); const [kitchenOutcomes, setKitchenOutcomes] = useState( [], ); @@ -47,6 +52,7 @@ function OrgTable() { message: '', open: false, }); + const navigate = useNavigate(); const columns: TColumn[] = [ { id: 'date', label: 'Date' }, @@ -74,7 +80,10 @@ function OrgTable() { return; } setSelectedOrgId(organizationId); - + setSelectedOrgName( + // eslint-disable-next-line no-underscore-dangle + organizations.find((org) => org._id === organizationId)?.organizationName, + ); try { const [kitchenRes, programRes] = await Promise.all([ getData(`kitchen_outcomes/get/all/${organizationId}`), @@ -196,8 +205,45 @@ function OrgTable() { ))} + {selectedOrgId !== 'No Org' ? ( + + + + ) : ( + <> + )} {/* Outcomes Table */} + + + {/* Notification Snackbar */} }, { path: 'organizations', element: }, { path: 'organizations-add', element: }, + { path: 'organizations-update/:orgId', element: }, ], }, { diff --git a/client/src/EditOrganization/EditOrganization.tsx b/client/src/EditOrganization/EditOrganization.tsx new file mode 100644 index 00000000..6a17c35f --- /dev/null +++ b/client/src/EditOrganization/EditOrganization.tsx @@ -0,0 +1,256 @@ +import React, { useState, useEffect } from 'react'; +import { + TextField, + Button, + FormControl, + InputLabel, + Select, + MenuItem, + Grid, + Snackbar, + Typography, +} from '@mui/material'; +import { textAlign } from '@mui/system'; +import { useParams } from 'react-router-dom'; +import { postData, getData } from '../util/api'; + +export default function EditOrganization() { + const { orgId } = useParams(); + const states = [ + { name: 'Alabama', abbreviation: 'AL' }, + { name: 'Alaska', abbreviation: 'AK' }, + { name: 'Arizona', abbreviation: 'AZ' }, + { name: 'Arkansas', abbreviation: 'AR' }, + { name: 'California', abbreviation: 'CA' }, + { name: 'Colorado', abbreviation: 'CO' }, + { name: 'Connecticut', abbreviation: 'CT' }, + { name: 'Delaware', abbreviation: 'DE' }, + { name: 'Florida', abbreviation: 'FL' }, + { name: 'Georgia', abbreviation: 'GA' }, + { name: 'Hawaii', abbreviation: 'HI' }, + { name: 'Idaho', abbreviation: 'ID' }, + { name: 'Illinois', abbreviation: 'IL' }, + { name: 'Indiana', abbreviation: 'IN' }, + { name: 'Iowa', abbreviation: 'IA' }, + { name: 'Kansas', abbreviation: 'KS' }, + { name: 'Kentucky', abbreviation: 'KY' }, + { name: 'Louisiana', abbreviation: 'LA' }, + { name: 'Maine', abbreviation: 'ME' }, + { name: 'Maryland', abbreviation: 'MD' }, + { name: 'Massachusetts', abbreviation: 'MA' }, + { name: 'Michigan', abbreviation: 'MI' }, + { name: 'Minnesota', abbreviation: 'MN' }, + { name: 'Mississippi', abbreviation: 'MS' }, + { name: 'Missouri', abbreviation: 'MO' }, + { name: 'Montana', abbreviation: 'MT' }, + { name: 'Nebraska', abbreviation: 'NE' }, + { name: 'Nevada', abbreviation: 'NV' }, + { name: 'New Hampshire', abbreviation: 'NH' }, + { name: 'New Jersey', abbreviation: 'NJ' }, + { name: 'New Mexico', abbreviation: 'NM' }, + { name: 'New York', abbreviation: 'NY' }, + { name: 'North Carolina', abbreviation: 'NC' }, + { name: 'North Dakota', abbreviation: 'ND' }, + { name: 'Ohio', abbreviation: 'OH' }, + { name: 'Oklahoma', abbreviation: 'OK' }, + { name: 'Oregon', abbreviation: 'OR' }, + { name: 'Pennsylvania', abbreviation: 'PA' }, + { name: 'Rhode Island', abbreviation: 'RI' }, + { name: 'South Carolina', abbreviation: 'SC' }, + { name: 'South Dakota', abbreviation: 'SD' }, + { name: 'Tennessee', abbreviation: 'TN' }, + { name: 'Texas', abbreviation: 'TX' }, + { name: 'Utah', abbreviation: 'UT' }, + { name: 'Vermont', abbreviation: 'VT' }, + { name: 'Virginia', abbreviation: 'VA' }, + { name: 'Washington', abbreviation: 'WA' }, + { name: 'West Virginia', abbreviation: 'WV' }, + { name: 'Wisconsin', abbreviation: 'WI' }, + { name: 'Wyoming', abbreviation: 'WY' }, + ]; + type FormState = { + organizationName: string; + status: string; + street: string; + city: string; + state: string; + zip: string; + id: string | undefined; + }; + + const defaultState: FormState = { + organizationName: '', + status: 'Member', + street: '', + city: '', + state: '', + zip: '', + id: orgId, + }; + const [newOrg, setNewOrg] = useState(defaultState); + type Notification = { + message: string; + open: boolean; + }; + const defaultNotification: Notification = { + message: 'Organization Information has been successfully edited', + open: false, + }; + const [notificationEdit, setNotificationEdit] = + useState(defaultNotification); + useEffect(() => { + const getOrgInformation = async () => { + if (orgId) { + const result = await getData(`organization/id/${orgId}`); + console.log(result); + if (result) { + const { data } = result; + setNewOrg({ + ...newOrg, + organizationName: data?.organizationName, + street: data?.street, + city: data?.city, + state: data?.state, + zip: data?.zip, + }); + } else { + console.log('No available organization'); + } + } else { + console.log('No defined organization id'); + } + }; + getOrgInformation(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [orgId]); + const validateInputs = () => { + return ( + newOrg.state.length > 0 && + newOrg.city.length > 0 && + newOrg.zip.length === 5 && + newOrg.organizationName.length > 0 + ); + }; + const handleSubmit = async () => { + if (validateInputs()) { + try { + console.log(newOrg); + const response = await postData('organization/edit', newOrg); + if (response.data) { + console.log('Organization Submitted Successfully: ', response.data); + setNotificationEdit({ ...notificationEdit, open: true }); + } + setNewOrg(defaultState); + } catch (error) { + console.error('Error submitting program outcome:', error); + } + } + }; + return ( +
+ + +

Organization Information

+
+ + + { + setNewOrg({ ...newOrg, organizationName: e.target.value }); + }} + /> + + + + { + setNewOrg({ ...newOrg, street: e.target.value }); + }} + /> + + + + { + setNewOrg({ ...newOrg, city: e.target.value }); + }} + /> + + + + + State + + + + + + { + setNewOrg({ ...newOrg, zip: e.target.value }); + }} + /> + + + + + +
+ { + setNotificationEdit({ ...notificationEdit, open: false }); + }} + message={notificationEdit.message} + /> +
+ ); +} diff --git a/client/src/SubmissionForms/KitchenOutcome.tsx b/client/src/SubmissionForms/KitchenOutcome.tsx index dffe98af..2709bdc2 100644 --- a/client/src/SubmissionForms/KitchenOutcome.tsx +++ b/client/src/SubmissionForms/KitchenOutcome.tsx @@ -108,7 +108,7 @@ export default function KitchenOutcome() { // Initialize formState with the FormState type const noFormState: FormState = { email: user.email, - year: new Date(), + year: new Date(new Date().getFullYear()), orgId: null, shareSurvey: true, responderName: '', diff --git a/client/src/Visualizations/KitchenOutcomeViz.tsx b/client/src/Visualizations/KitchenOutcomeViz.tsx index 74729152..5899a2a2 100644 --- a/client/src/Visualizations/KitchenOutcomeViz.tsx +++ b/client/src/Visualizations/KitchenOutcomeViz.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-underscore-dangle */ import React, { useEffect, useState } from 'react'; import { Grid, @@ -129,11 +130,14 @@ function KitchenOutcomesVisualization() { }; const [surveyData, setSurveyData] = useState(null); - - const [orgList, setOrgList] = useState(null); + type OrgVal = { + name: string; + id: string; + }; + const [orgList, setOrgList] = useState(null); const [yearList, setYearList] = useState([]); const [searchTerm, setSearchTerm] = useState(''); - const [orgName, setOrgName] = useState(''); + const [orgName, setOrgName] = useState(''); const [orgId, setOrgId] = useState(''); const [year, setYear] = useState(''); @@ -143,24 +147,15 @@ function KitchenOutcomesVisualization() { 'Capital Projects', 'Organization Info', ]; - useEffect(() => { - const findOrgId = async () => { - try { - const response = await getData(`organization/name/${orgName}`); - // eslint-disable-next-line no-underscore-dangle - setOrgId(response.data._id); - } catch (error) { - console.error('Error fetching specific organization id', error); - } - }; - findOrgId(); - }, [orgName]); useEffect(() => { const fetchOrgList = async () => { try { const response = await getData(`organization/organizations`); const organizationNames = response.data.map( - (org: { organizationName: string }) => org.organizationName, + (org: { organizationName: string; _id: string }) => ({ + name: org.organizationName, + id: org._id, + }), ); setOrgList(organizationNames); } catch (error) { @@ -174,8 +169,11 @@ function KitchenOutcomesVisualization() { useEffect(() => { const fetchOutcomes = async () => { if (!orgId) return; + if (!year) return; try { const response = await getData(`kitchen_outcomes/${year}/${orgId}`); + console.log(response); + console.log(orgId); setSurveyData(response.data); } catch (error) { console.error('Error fetching data:', error); @@ -189,19 +187,23 @@ function KitchenOutcomesVisualization() { if (!orgId) return; try { const response = await getData(`kitchen_outcomes/get/years/${orgId}`); + console.log('Years response:', response.data); setYearList(response.data); } catch (error) { console.error('Error fetching data:', error); } }; settingYearList(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [orgId]); const handleOrgSelection = async ( event: React.ChangeEvent, ) => { event.preventDefault(); const selectedOrg = event.target.value; - setOrgName(selectedOrg); + setOrgId(selectedOrg); + setYear(''); + setOrgName(orgList?.find((org) => org.id === selectedOrg)?.name); }; const [chartData, setChartData] = useState<{ ageData: { @@ -571,12 +573,12 @@ function KitchenOutcomesVisualization() { select size="small" // Make the field smaller fullWidth - value={orgName} + value={orgId} onChange={handleOrgSelection} > {orgList?.map((org) => ( - - {org} + + {org.name} )) ?? []} @@ -590,6 +592,7 @@ function KitchenOutcomesVisualization() { fullWidth value={year} onChange={(event) => { + console.log(event.target.value); setYear(Number(event.target.value)); }} disabled={!orgName || yearList.length === 0} diff --git a/server/src/controllers/kitchen.outcomes.controller.ts b/server/src/controllers/kitchen.outcomes.controller.ts index 6da96c7d..5cef69e9 100644 --- a/server/src/controllers/kitchen.outcomes.controller.ts +++ b/server/src/controllers/kitchen.outcomes.controller.ts @@ -23,7 +23,9 @@ const getOneKitchenOutcomesController = async ( next(ApiError.missingFields(['year', 'orgId'])); return; } - const yearDate = new Date(year); + console.log('Year', year); + const adjustedYear = parseInt(year, 10) + 1; + const yearDate = new Date(Date.UTC(adjustedYear, 0, 1)); return getOneKitchenOutcomes(yearDate, orgId) .then((kitchenOutcomes: unknown) => { res.status(StatusCode.OK).send(kitchenOutcomes); diff --git a/server/src/controllers/organization.controller.ts b/server/src/controllers/organization.controller.ts index cfee79db..0c14c0e1 100644 --- a/server/src/controllers/organization.controller.ts +++ b/server/src/controllers/organization.controller.ts @@ -8,6 +8,7 @@ import { getOrganizationNameById, getOrganizationById, addOrganization, + editOrganization, } from '../services/organization.service.ts'; const getOrgByName = async ( @@ -104,10 +105,40 @@ const getOrgById = async ( next(ApiError.internal('Unable to retrieve organization by ID')); } }; +const editOrganizationController = async ( + req: express.Request, + res: express.Response, + next: express.NextFunction, +) => { + const { id, organizationName, status, street, city, state, zip } = req.body; + if (!id) { + next(ApiError.missingFields(['id'])); + } + try { + // (alias) editOrganization(status: string, organizationName: string, id: string, street: string, city: string, state: string, zip: string): Promise + // import editOrganization + const org = await editOrganization( + status, + organizationName, + id, + street, + city, + state, + zip, + ); + if (!res) { + next(ApiError.notFound('Organization could not be found and edited')); + } + res.status(StatusCode.OK).send(org); + } catch (error) { + next(ApiError.internal('Unable to retrieve organization by ID')); + } +}; export { getOrgByName, getAll, getOrganizationNameByIdController, getOrgById, addOrganizationController, + editOrganizationController, }; diff --git a/server/src/routes/organization.route.ts b/server/src/routes/organization.route.ts index 6e8fff3d..a42a7467 100644 --- a/server/src/routes/organization.route.ts +++ b/server/src/routes/organization.route.ts @@ -6,6 +6,7 @@ import { getOrgById, getOrganizationNameByIdController, addOrganizationController, + editOrganizationController, } from '../controllers/organization.controller'; import { isAdmin } from '../controllers/admin.middleware'; @@ -15,6 +16,7 @@ router.get('/name/:name', isAuthenticated, getOrgByName); router.get('/organizations', isAuthenticated, getAll); router.get('/id/:id', isAuthenticated, getOrgById); router.post('/new', isAdmin, addOrganizationController); +router.post('/edit', isAdmin, editOrganizationController); router.get( '/organization/name/:id', diff --git a/server/src/services/kitchen.outcomes.service.ts b/server/src/services/kitchen.outcomes.service.ts index e54ed152..54bb3f6b 100644 --- a/server/src/services/kitchen.outcomes.service.ts +++ b/server/src/services/kitchen.outcomes.service.ts @@ -5,10 +5,21 @@ import { } from '../models/kitchen.outcomes.model.ts'; const getOneKitchenOutcomes = async (year: Date, orgId: string) => { + console.log('Year', year.getFullYear()); + const startDate = new Date(Date.UTC(year.getFullYear(), 0, 1)); + const endDate = new Date( + Date.UTC(year.getFullYear(), 11, 31, 23, 59, 59, 999), + ); const outcomes = await KitchenOutcomes.findOne({ orgId, - year, + year: { + $gte: startDate, + $lt: endDate, + }, }).exec(); + console.log(startDate); + console.log(endDate); + console.log('Service - Found kitchen outcomes:', outcomes); return outcomes; }; const getAllKitchenOutcomesByOrg = async (orgId: string) => { diff --git a/server/src/services/organization.service.ts b/server/src/services/organization.service.ts index e71c24b0..df182e25 100644 --- a/server/src/services/organization.service.ts +++ b/server/src/services/organization.service.ts @@ -58,11 +58,40 @@ const addOrganization = async ( } return res; }; - +const editOrganization = async ( + status: string, + organizationName: string, + id: string, + street: string, + city: string, + state: string, + zip: string, +) => { + try { + const res = await Organization.updateOne( + { _id: id }, + { + $set: { + organizationName, + status, + street, + city, + state, + zip, + }, + }, + ); + console.log('Organization correctly updated!'); + return res; + } catch (error) { + throw new Error('Unable to update organization'); + } +}; export { getOrganizationByName, getAllOrganizations, getOrganizationById, getOrganizationNameById, addOrganization, + editOrganization, };