diff --git a/client/package.json b/client/package.json index cc265a90..8eeb433a 100644 --- a/client/package.json +++ b/client/package.json @@ -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", @@ -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", diff --git a/client/src/AdminDashboard/AdminDataDownloadPage.tsx b/client/src/AdminDashboard/AdminDataDownloadPage.tsx new file mode 100644 index 00000000..c2bd8a2d --- /dev/null +++ b/client/src/AdminDashboard/AdminDataDownloadPage.tsx @@ -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( + new Date().getFullYear(), + ); + const [queryTrigger, setQueryTrigger] = useState(false); + const [programDataRetrieved, setProgramDataRetrieved] = useState([]); + const [kitchenDataRetrieved, setKitchenDataRetrieved] = useState([]); + const [showProgramSnackBar, setShowProgramSnackBar] = + useState(false); + const [showKitchenSnackBar, setShowKitchenSnackBar] = + useState(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 = ( + + + + ); + const handleProgramOutcomesClose = ( + event: React.SyntheticEvent | Event, + reason?: SnackbarCloseReason, + ) => { + if (reason === 'clickaway') { + return; + } + + setShowProgramSnackBar(false); + }; + const programOutcomesAction = ( + + + + ); + return ( + + + Admin Data Download + + + + `${v}`} + onChange={(e, v) => { + setSelectedYear(v as number); + }} + valueLabelDisplay="auto" + /> + + + + + + + + + + {programDataRetrieved.length > 0 && ( + + )} + + + {kitchenDataRetrieved.length > 0 && ( + + )} + + + + + ); +} + +export default AdminDataDownloadPage; diff --git a/client/src/App.tsx b/client/src/App.tsx index b99b17c7..a3eeb5ce 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -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([ { @@ -74,6 +74,7 @@ const router = createBrowserRouter([ { path: 'organizations', element: }, { path: 'organizations-add', element: }, { path: 'organizations-update/:orgId', element: }, + { path: 'data-download', element: }, ], }, { diff --git a/client/src/SubmissionForms/KitchenOutcome.tsx b/client/src/SubmissionForms/KitchenOutcome.tsx index e7a79e86..aea1564e 100644 --- a/client/src/SubmissionForms/KitchenOutcome.tsx +++ b/client/src/SubmissionForms/KitchenOutcome.tsx @@ -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 @@ -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(() => { @@ -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); } @@ -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); } diff --git a/client/src/SubmissionForms/ProgramOutcome.tsx b/client/src/SubmissionForms/ProgramOutcome.tsx index 111fc303..9559f2d9 100644 --- a/client/src/SubmissionForms/ProgramOutcome.tsx +++ b/client/src/SubmissionForms/ProgramOutcome.tsx @@ -315,18 +315,13 @@ export default function ProgramOutcome() { 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); } @@ -349,20 +344,20 @@ export default function ProgramOutcome() { 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); } diff --git a/client/src/components/buttons/InviteUserButton.tsx b/client/src/components/buttons/InviteUserButton.tsx index 0aa2d2aa..abfd0f5b 100644 --- a/client/src/components/buttons/InviteUserButton.tsx +++ b/client/src/components/buttons/InviteUserButton.tsx @@ -48,7 +48,7 @@ function InviteUserButton() { diff --git a/client/src/util/routes.tsx b/client/src/util/routes.tsx index 0e36cd57..7b33f9ff 100644 --- a/client/src/util/routes.tsx +++ b/client/src/util/routes.tsx @@ -5,6 +5,7 @@ import { PieChart, TableChart, ArrowUpward, + ArrowDownward, Add, } from '@mui/icons-material'; import { useSelector } from 'react-redux'; @@ -50,6 +51,11 @@ function ProtectedRoutesWrapper() { to: '/organizations', }); links.push({ name: 'User Dashboard', icon: Person, to: '/users' }); + links.push({ + name: 'Data Download', + icon: ArrowDownward, + to: '/data-download', + }); } } return !data.error ? ( @@ -98,6 +104,11 @@ function AdminRoutesWrapper() { to: '/organizations', }); links.push({ name: 'User Dashboard', icon: Person, to: '/users' }); + links.push({ + name: 'Data Download', + icon: ArrowDownward, + to: '/data-download', + }); return !data.error ? (
diff --git a/server/src/controllers/kitchen.outcomes.controller.ts b/server/src/controllers/kitchen.outcomes.controller.ts index 26fddb31..7f738d0e 100644 --- a/server/src/controllers/kitchen.outcomes.controller.ts +++ b/server/src/controllers/kitchen.outcomes.controller.ts @@ -2,6 +2,7 @@ import express from 'express'; import ApiError from '../util/apiError.ts'; import StatusCode from '../util/statusCode.ts'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars import { IKitchenOutcomes } from '../models/kitchen.outcomes.model.ts'; import { getOneKitchenOutcomes, @@ -12,6 +13,7 @@ import { deleteKitchenOutcomeById, addKitchenOutcomes, getNetworkAverage, + getAllKitchenOutcomesByYear, calculateAgeAndRaceDistributions, } from '../services/kitchen.outcomes.service.ts'; @@ -81,6 +83,26 @@ const distriController = async ( }; export { distriController }; +const getAllKitchenOutcomesByYearController = async ( + req: express.Request, + res: express.Response, + next: express.NextFunction, +) => { + const { year } = req.params; + if (!year) { + next(ApiError.missingFields(['year'])); + return; + } + const numericalYear = Number(year); + return getAllKitchenOutcomesByYear(numericalYear) + .then((kitchenOutcomes: any[]) => { + res.status(StatusCode.OK).send(kitchenOutcomes); + }) + .catch(() => { + next(ApiError.internal('Unable to retrieve program outcomes by year')); + }); +}; +export { getAllKitchenOutcomesByYearController }; const getNetworkAverageController = async ( req: express.Request, diff --git a/server/src/controllers/program.outcomes.controller.ts b/server/src/controllers/program.outcomes.controller.ts index 3056dcc0..948ae2ca 100644 --- a/server/src/controllers/program.outcomes.controller.ts +++ b/server/src/controllers/program.outcomes.controller.ts @@ -73,9 +73,9 @@ const getAllProgramOutcomesByYearController = async ( next(ApiError.missingFields(['year'])); return; } - const yearDate = new Date(year); - return getAllProgramOutcomesByYear(yearDate) - .then((programOutcomes: IProgramOutcomes[]) => { + const numericalYear = Number(year); + return getAllProgramOutcomesByYear(numericalYear) + .then((programOutcomes: any[]) => { res.status(StatusCode.OK).send(programOutcomes); }) .catch(() => { diff --git a/server/src/models/program.outcomes.model.ts b/server/src/models/program.outcomes.model.ts index 0de190f5..296d785a 100644 --- a/server/src/models/program.outcomes.model.ts +++ b/server/src/models/program.outcomes.model.ts @@ -6,6 +6,10 @@ const ProgramOutcomesSchema = new mongoose.Schema({ ref: 'MemberOrganization', required: false, }, + organizationName: { + type: String, + required: false, + }, year: { type: Date, required: false, @@ -451,6 +455,7 @@ const ProgramOutcomesSchema = new mongoose.Schema({ interface IProgramOutcomes { orgId?: mongoose.Schema.Types.ObjectId; + organizationName?: string; year?: Date; programCostPerTrainee?: number; programDesignedForYouthAndAdults?: boolean; diff --git a/server/src/routes/kitchen.outcomes.route.ts b/server/src/routes/kitchen.outcomes.route.ts index 9bf583f7..aaaff8a4 100644 --- a/server/src/routes/kitchen.outcomes.route.ts +++ b/server/src/routes/kitchen.outcomes.route.ts @@ -12,6 +12,7 @@ import { addKitchenOutcomesController, getNetworkAverageController, distriController, + getAllKitchenOutcomesByYearController, } from '../controllers/kitchen.outcomes.controller.ts'; const router = express.Router(); @@ -21,6 +22,11 @@ router.get( isAuthenticated, distriController, ); +router.get( + '/year/:year', + isAuthenticated, + getAllKitchenOutcomesByYearController, +); // router.get('/:year/:orgName', isAuthenticated, getOneKitchenOutcomesController); router.get('/:year/:orgId', isAuthenticated, getOneKitchenOutcomesController); // no authentication for now diff --git a/server/src/services/kitchen.outcomes.service.ts b/server/src/services/kitchen.outcomes.service.ts index ed7c70b4..60802f79 100644 --- a/server/src/services/kitchen.outcomes.service.ts +++ b/server/src/services/kitchen.outcomes.service.ts @@ -390,6 +390,31 @@ const deleteKitchenOutcomeById = async (id: string) => { throw new Error('Unable to delete kitchen outcome'); } }; +const getAllKitchenOutcomesByYear = async (year: number) => { + const startDate = new Date(Date.UTC(year, 0, 1)); + const endDate = new Date(Date.UTC(year + 1, 0, 1)); + const outcomes = await KitchenOutcomes.find({ + year: { + $gte: startDate, + $lt: endDate, + }, + }).exec(); + if (outcomes.length > 0) { + const parsedOutcomes = outcomes.map((outcome) => { + return { + ...outcome.toObject(), + typeOfMealsServed: outcome.typeOfMealsServed?.join(';') || '', + capitalExpansionProjectNeeds: + outcome.capitalExpansionProjectNeeds?.join(';') || '', + }; + }); + + console.log('Parsed outcomes:', parsedOutcomes); + return parsedOutcomes; + } + console.log('No outcomes found for the specified year.'); + return []; +}; export { getOneKitchenOutcomes, @@ -400,4 +425,5 @@ export { deleteKitchenOutcomeById, addKitchenOutcomes, getNetworkAverage, + getAllKitchenOutcomesByYear, }; diff --git a/server/src/services/program.outcomes.service.ts b/server/src/services/program.outcomes.service.ts index 337658b2..840c4764 100644 --- a/server/src/services/program.outcomes.service.ts +++ b/server/src/services/program.outcomes.service.ts @@ -45,10 +45,32 @@ const getDistinctYearsByOrgId = async (orgId: string): Promise => { return [...new Set(years)].sort((a, b) => b - a); }; -// Get all program outcomes of a given year -const getAllProgramOutcomesByYear = async (year: Date) => { - const outcomes = await ProgramOutcomes.find({ year }).exec(); - return outcomes; +const getAllProgramOutcomesByYear = async (year: number) => { + const startDate = new Date(Date.UTC(year, 0, 1)); + const endDate = new Date(Date.UTC(year + 1, 0, 1)); + const outcomes = await ProgramOutcomes.find({ + year: { + $gte: startDate, + $lt: endDate, + }, + }).exec(); + if (outcomes.length > 0) { + const parsedOutcomes = outcomes.map((outcome) => { + return { + ...outcome.toObject(), + programCertifications: outcome.programCertifications?.join(';') || '', + participantCertifications: + outcome.participantCertifications?.join(';') || '', + jobCategory: outcome.jobCategory?.join(';') || '', + adultCompensation: outcome.adultCompensation?.join(';') || '', + }; + }); + + console.log('Parsed outcomes:', parsedOutcomes); + return parsedOutcomes; + } + console.log('No outcomes found for the specified year.'); + return []; }; const addProgramOutcomes = async (obj: IProgramOutcomes) => { diff --git a/yarn.lock b/yarn.lock index 3174a145..8cc90806 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1586,7 +1586,7 @@ dependencies: regenerator-runtime "^0.14.0" -"@babel/runtime@^7.25.6", "@babel/runtime@^7.26.0": +"@babel/runtime@^7.25.6": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== @@ -1831,7 +1831,7 @@ source-map "^0.5.7" stylis "4.2.0" -"@emotion/cache@^11.11.0", "@emotion/cache@^11.13.0", "@emotion/cache@^11.13.1": +"@emotion/cache@^11.13.0": version "11.13.1" resolved "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz" integrity sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw== @@ -1873,7 +1873,7 @@ "@emotion/weak-memoize" "^0.4.0" hoist-non-react-statics "^3.3.1" -"@emotion/serialize@^1.2.0", "@emotion/serialize@^1.3.0", "@emotion/serialize@^1.3.1", "@emotion/serialize@^1.3.2": +"@emotion/serialize@^1.2.0", "@emotion/serialize@^1.3.0", "@emotion/serialize@^1.3.1": version "1.3.2" resolved "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz" integrity sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA== @@ -2580,29 +2580,7 @@ "@mui/utils" "^6.1.5" prop-types "^15.8.1" -"@mui/styled-engine@^5.16.6": - version "5.16.6" - resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.16.6.tgz#60110c106dd482dfdb7e2aa94fd6490a0a3f8852" - integrity sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g== - dependencies: - "@babel/runtime" "^7.23.9" - "@emotion/cache" "^11.11.0" - csstype "^3.1.3" - prop-types "^15.8.1" - -"@mui/styled-engine@^6.1.5": - version "6.1.6" - resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-6.1.6.tgz#61996621a0297aac16061e1739a738a899613fd6" - integrity sha512-I+yS1cSuSvHnZDBO7e7VHxTWpj+R7XlSZvTC4lS/OIbUNJOMMSd3UDP6V2sfwzAdmdDNBi7NGCRv2SZ6O9hGDA== - dependencies: - "@babel/runtime" "^7.26.0" - "@emotion/cache" "^11.13.1" - "@emotion/serialize" "^1.3.2" - "@emotion/sheet" "^1.4.0" - csstype "^3.1.3" - prop-types "^15.8.1" - -"@mui/styled-engine@npm:@mui/styled-engine-sc@latest": +"@mui/styled-engine@^5.16.6", "@mui/styled-engine@^6.1.5", "@mui/styled-engine@npm:@mui/styled-engine-sc@latest": version "6.1.3" resolved "https://registry.yarnpkg.com/@mui/styled-engine-sc/-/styled-engine-sc-6.1.3.tgz#f7f7db4c0e9d4c9a0d076060c99e47744bd2659b" integrity sha512-Y+BZHQxygyKklawo9eMilthFALQpndcfDdEz0LpoZmkvVtWBD3kAPCyYeSAlqFRFQ++MdiQWlWY8O1R9ORuxJg== @@ -4316,6 +4294,11 @@ adjust-sourcemap-loader@^4.0.0: loader-utils "^2.0.0" regex-parser "^2.2.11" +adler-32@~1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/adler-32/-/adler-32-1.3.1.tgz#1dbf0b36dda0012189a32b3679061932df1821e2" + integrity sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A== + agent-base@6: version "6.0.2" resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" @@ -5137,6 +5120,14 @@ case-sensitive-paths-webpack-plugin@^2.4.0: resolved "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz" integrity sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw== +cfb@~1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cfb/-/cfb-1.2.2.tgz#94e687628c700e5155436dac05f74e08df23bc44" + integrity sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA== + dependencies: + adler-32 "~1.3.0" + crc-32 "~1.2.0" + chalk@5.3.0: version "5.3.0" resolved "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz" @@ -5283,6 +5274,11 @@ coa@^2.0.2: chalk "^2.4.1" q "^1.1.2" +codepage@~1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/codepage/-/codepage-1.15.0.tgz#2e00519024b39424ec66eeb3ec07227e692618ab" + integrity sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA== + collect-v8-coverage@^1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz" @@ -5573,6 +5569,11 @@ cosmiconfig@^7.0.0: path-type "^4.0.0" yaml "^1.10.0" +crc-32@~1.2.0, crc-32@~1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" + integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== + create-jest@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz" @@ -7112,6 +7113,11 @@ file-loader@^6.2.0: loader-utils "^2.0.0" schema-utils "^3.0.0" +file-saver@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38" + integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA== + filelist@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz" @@ -7272,6 +7278,11 @@ forwarded@0.2.0: resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== +frac@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/frac/-/frac-1.1.2.tgz#3d74f7f6478c88a1b5020306d747dc6313c74d0b" + integrity sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA== + fraction.js@^4.3.7: version "4.3.7" resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz" @@ -12306,6 +12317,13 @@ sprintf-js@~1.0.2: resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== +ssf@~0.11.2: + version "0.11.2" + resolved "https://registry.yarnpkg.com/ssf/-/ssf-0.11.2.tgz#0b99698b237548d088fc43cdf2b70c1a7512c06c" + integrity sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g== + dependencies: + frac "~1.1.2" + stable@^0.1.8: version "0.1.8" resolved "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz" @@ -12547,7 +12565,7 @@ style-loader@^3.3.1: resolved "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz" integrity sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w== -styled-components@^5.3.3, styled-components@^5.3.5: +styled-components@^5, styled-components@^5.3.3, styled-components@^5.3.5: version "5.3.11" resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.11.tgz#9fda7bf1108e39bf3f3e612fcc18170dedcd57a8" integrity sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw== @@ -13636,11 +13654,21 @@ winston@^3.14.2: triple-beam "^1.3.0" winston-transport "^4.7.0" +wmf@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wmf/-/wmf-1.0.2.tgz#7d19d621071a08c2bdc6b7e688a9c435298cc2da" + integrity sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw== + word-wrap@^1.2.5, word-wrap@~1.2.3: version "1.2.5" resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== +word@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/word/-/word-0.3.0.tgz#8542157e4f8e849f4a363a288992d47612db9961" + integrity sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA== + workbox-background-sync@6.6.1: version "6.6.1" resolved "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.6.1.tgz" @@ -13861,6 +13889,19 @@ ws@^8.13.0: resolved "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== +xlsx@^0.18.5: + version "0.18.5" + resolved "https://registry.yarnpkg.com/xlsx/-/xlsx-0.18.5.tgz#16711b9113c848076b8a177022799ad356eba7d0" + integrity sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ== + dependencies: + adler-32 "~1.3.0" + cfb "~1.2.1" + codepage "~1.15.0" + crc-32 "~1.2.1" + ssf "~0.11.2" + wmf "~1.0.1" + word "~0.3.0" + xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz"