Skip to content

Commit

Permalink
Finished adding functionality to download yearly data for program and…
Browse files Browse the repository at this point in the history
… kitchen outcomes as excel files. Updated axios.get usage to getData.
  • Loading branch information
roshanbellary committed Jan 12, 2025
1 parent 0689838 commit a5499ed
Show file tree
Hide file tree
Showing 14 changed files with 456 additions and 97 deletions.
4 changes: 3 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"axios": "^1.1.2",
"chart.js": "^4.4.5",
"cross-env": "^7.0.3",
"file-saver": "^2.0.5",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-chartjs-2": "^5.2.0",
Expand All @@ -54,7 +55,8 @@
"redux-persist": "^6.0.0",
"styled-components": "^5.3.5",
"typeface-hk-grotesk": "^1.0.0",
"web-vitals": "^3.0.3"
"web-vitals": "^3.0.3",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@mui/styled-engine": "npm:@mui/styled-engine-sc@latest",
Expand Down
235 changes: 235 additions & 0 deletions client/src/AdminDashboard/AdminDataDownloadPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
import React, { useState, useEffect } from 'react';
import {
Grid,
Typography,
Slider,
Button,
Snackbar,
IconButton,
SnackbarCloseReason,
} from '@mui/material';
import { Close } from '@mui/icons-material';
import * as XLSX from 'xlsx';
import { saveAs } from 'file-saver';
import { getData } from '../util/api';

function AdminDataDownloadPage() {
const [selectedYear, setSelectedYear] = useState<number>(
new Date().getFullYear(),
);
const [queryTrigger, setQueryTrigger] = useState<boolean>(false);
const [programDataRetrieved, setProgramDataRetrieved] = useState<any[]>([]);
const [kitchenDataRetrieved, setKitchenDataRetrieved] = useState<any[]>([]);
const [showProgramSnackBar, setShowProgramSnackBar] =
useState<boolean>(false);
const [showKitchenSnackBar, setShowKitchenSnackBar] =
useState<boolean>(false);

useEffect(() => {
const pullProgramData = async () => {
const programData = await getData(
`program_outcomes/year/${selectedYear}`,
);
if (programData) {
const { data } = programData;
if (data && data.length > 0) {
setProgramDataRetrieved(data);
} else {
console.log('no program outcomes found');
setShowProgramSnackBar(true);
setProgramDataRetrieved([]);
}
}
};
const pullKitchenData = async () => {
const kitchenData = await getData(
`kitchen_outcomes/year/${selectedYear}`,
);
if (kitchenData) {
const { data } = kitchenData;
if (data && data.length > 0) {
setKitchenDataRetrieved(data);
} else {
console.log('no kitchen outcomes found');
setShowKitchenSnackBar(true);
setKitchenDataRetrieved([]);
}
}
};
if (queryTrigger) {
pullProgramData();
pullKitchenData();
setQueryTrigger(false);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [queryTrigger]);
useEffect(() => {
const clearDataCollected = () => {
setProgramDataRetrieved([]);
setKitchenDataRetrieved([]);
};
clearDataCollected();
}, [selectedYear]);
const handleProgramDataDownload = () => {
if (programDataRetrieved.length > 0) {
const worksheet = XLSX.utils.json_to_sheet(programDataRetrieved);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, 'Program Data');
const excelBuffer = XLSX.write(workbook, {
bookType: 'xlsx',
type: 'array',
});
const blob = new Blob([excelBuffer], {
type: 'application/octet-stream',
});
saveAs(blob, `ProgramData_${selectedYear}.xlsx`);
}
};

const handleKitchenDataDownload = () => {
if (kitchenDataRetrieved.length > 0) {
const worksheet = XLSX.utils.json_to_sheet(kitchenDataRetrieved);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, 'Kitchen Data');
const excelBuffer = XLSX.write(workbook, {
bookType: 'xlsx',
type: 'array',
});
const blob = new Blob([excelBuffer], {
type: 'application/octet-stream',
});
saveAs(blob, `KitchenData_${selectedYear}.xlsx`);
}
};
const handleKitchenOutcomesClose = (
event: React.SyntheticEvent | Event,
reason?: SnackbarCloseReason,
) => {
if (reason === 'clickaway') {
return;
}

setShowKitchenSnackBar(false);
};
const kitchenOutcomesAction = (
<IconButton
size="small"
aria-label="close"
color="inherit"
onClick={handleKitchenOutcomesClose}
>
<Close fontSize="small" />
</IconButton>
);
const handleProgramOutcomesClose = (
event: React.SyntheticEvent | Event,
reason?: SnackbarCloseReason,
) => {
if (reason === 'clickaway') {
return;
}

setShowProgramSnackBar(false);
};
const programOutcomesAction = (
<IconButton
size="small"
aria-label="close"
color="inherit"
onClick={handleProgramOutcomesClose}
>
<Close fontSize="small" />
</IconButton>
);
return (
<Grid container spacing={2}>
<Grid item xs={12} sx={{ textAlign: 'center' }}>
<Typography variant="h2">Admin Data Download</Typography>
</Grid>
<Grid item xs={3} />
<Grid item xs={6}>
<Slider
sx={{ color: 'black' }}
value={selectedYear}
min={2017}
max={2040}
step={1}
getAriaValueText={(v) => `${v}`}
onChange={(e, v) => {
setSelectedYear(v as number);
}}
valueLabelDisplay="auto"
/>
</Grid>
<Grid item xs={3} />
<Grid item xs={3} />
<Grid
item
xs={6}
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<Button
variant="contained"
onClick={() => {
setQueryTrigger(true);
}}
sx={{ backgroundColor: 'black', color: 'white' }}
>
<Typography variant="subtitle1">
Download Data From Year {selectedYear}
</Typography>
</Button>
</Grid>
<Grid item xs={3} />
<Grid item xs={12} sx={{ marginTop: '50px' }} />
<Grid item xs={12} md={6} sx={{ textAlign: 'center' }}>
{programDataRetrieved.length > 0 && (
<Button
variant="contained"
color="primary"
onClick={handleProgramDataDownload}
>
<Typography variant="subtitle1">
Download Program Outcomes Data as Excel Sheet
</Typography>
</Button>
)}
</Grid>
<Grid item xs={12} md={6} sx={{ textAlign: 'center' }}>
{kitchenDataRetrieved.length > 0 && (
<Button
variant="contained"
color="primary"
onClick={handleKitchenDataDownload}
>
<Typography variant="subtitle1">
Download Kitchen Outcomes Data as Excel Sheet
</Typography>
</Button>
)}
</Grid>
<Snackbar
open={showKitchenSnackBar}
autoHideDuration={6000}
onClose={handleKitchenOutcomesClose}
message="No Kitchen Outcomes Found"
action={kitchenOutcomesAction}
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
/>
<Snackbar
open={showProgramSnackBar}
autoHideDuration={6000}
onClose={handleProgramOutcomesClose}
message="No Program Outcomes Found"
action={programOutcomesAction}
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
/>
</Grid>
);
}

export default AdminDataDownloadPage;
3 changes: 2 additions & 1 deletion client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import SubmissionsHome from './Intermediate/Submissions.tsx';
import VisualizationHome from './Intermediate/Visualizations.tsx';
import AddOrganization from './AddOrganization/AddOrganization.tsx';
import EditOrganization from './EditOrganization/EditOrganization.tsx';

import AdminDataDownloadPage from './AdminDashboard/AdminDataDownloadPage.tsx';
// Router Configuration
const router = createBrowserRouter([
{
Expand Down Expand Up @@ -74,6 +74,7 @@ const router = createBrowserRouter([
{ path: 'organizations', element: <OrgAdminPage /> },
{ path: 'organizations-add', element: <AddOrganization /> },
{ path: 'organizations-update/:orgId', element: <EditOrganization /> },
{ path: 'data-download', element: <AdminDataDownloadPage /> },
],
},
{
Expand Down
63 changes: 28 additions & 35 deletions client/src/SubmissionForms/KitchenOutcome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
} from '@mui/material';
import { number } from 'prop-types';
import { RootState } from '../util/redux/store';
import { getData } from '../util/api';
import { getData, postData } from '../util/api';

export default function KitchenOutcome() {
// Define the form state type
Expand Down Expand Up @@ -258,15 +258,13 @@ export default function KitchenOutcome() {
}
const handleSubmit = async () => {
if (await validateInputs()) {
axios
.post('http://localhost:4000/api/kitchen_outcomes/add/', formState)
.then((response) => {
console.log('submitted!');
setFormState(noFormState);
})
.catch((error) => {
console.log(error);
});
try {
const response = await postData('kitchen_outcomes/new', formState);
console.log('Kitchen outcome submitted successfully:', response);
setFormState(noFormState);
} catch (error) {
console.error('Error submitting kitchen outcome:', error);
}
}
};
useEffect(() => {
Expand All @@ -291,18 +289,13 @@ export default function KitchenOutcome() {
const fetchOrganizationId = async () => {
if (user.email) {
try {
axios
.get(`http://localhost:4000/api/auth/organization/${user.email}`)
.then((response) => {
const { data } = response;
setFormState({
...formState,
orgId: data,
});
})
.catch((error) => {
console.log(error);
});
const response = await getData(`auth/organization/${user.email}`);
if (response.data) {
const { data } = response;
setFormState({ ...formState, orgId: data });
} else {
console.error('No organization id found for user');
}
} catch (error) {
console.error('Error fetching organization ID:', error);
}
Expand All @@ -315,20 +308,20 @@ export default function KitchenOutcome() {
const fetchOrganizationNameById = async () => {
if (formState.orgId) {
try {
axios
.get(
`http://localhost:4000/api/organization/organization/name/${formState.orgId}`,
)
.then((response) => {
const { data } = response;
setFormState({
...formState,
organizationName: data.organizationName,
});
})
.catch((error) => {
console.log(error);
const response = await getData(
`organization/organization/name/${formState.orgId}`,
);
if (response.data) {
const { data } = response;
setFormState({
...formState,
organizationName: data.organizationName,
});
} else {
console.error(
'No Organization Name found for given organization id',
);
}
} catch (error) {
console.error('Error fetching organization name by ID:', error);
}
Expand Down
Loading

0 comments on commit a5499ed

Please sign in to comment.