From 5fa01e42cc844de3f515a81fc671701500f8e2ec Mon Sep 17 00:00:00 2001 From: Nick Raskop Date: Thu, 5 Oct 2023 15:49:06 -0700 Subject: [PATCH 1/3] Export participant data csv & resumes --- .../pages/participant-database/index.tsx | 48 ++++++++++ package.json | 3 + yarn.lock | 93 +++++++++++++++++++ 3 files changed, 144 insertions(+) diff --git a/apps/dashboard/pages/participant-database/index.tsx b/apps/dashboard/pages/participant-database/index.tsx index 9a7fc405..07831300 100644 --- a/apps/dashboard/pages/participant-database/index.tsx +++ b/apps/dashboard/pages/participant-database/index.tsx @@ -15,6 +15,8 @@ import { Button, ParagraphText } from '@hibiscus/ui-kit-2023'; import { getWordCount } from '../../common/utils'; import HackerProfile from '../../components/sponsor-portal/hacker-profile'; import { SponsorServiceAPI } from '../../common/api'; +import { CSVLink } from 'react-csv'; +import JSZip from 'jszip'; const Index = () => { const router = useRouter(); @@ -36,6 +38,8 @@ const Index = () => { const [currentAttendee, setCurrentAttendee] = useState(null); const [modalActive, setModalActive] = useState(false); const [attendeeName, setAttendeeName] = useState(''); + const [baseAttendeeData, setBaseAttendeeData] = useState(); + const zip = new JSZip(); useEffect(() => { async function getFilteredAttendee() { @@ -243,6 +247,50 @@ const Index = () => { return ( + { + for (const attendee of attendees) { + if (attendee.resume) { + const response = await fetch(attendee.resume); + const resumeBlob = await response.blob(); + const attendeeName = attendee.full_name.replace(' ', ''); // Remove whitespaces from name + zip.file(attendeeName + '_Resume', resumeBlob); + } + } + // Generate the zip file asynchronously + zip + .generateAsync({ type: 'blob' }) + .then((content) => { + // 'content' is a Blob containing the zip file data + // Example: Create a download link for the zip file + // Could use something like FileSaver.js (https://github.com/eligrey/FileSaver.js), but didn't want to add extra dependencies + const downloadLink = document.createElement('a'); + downloadLink.href = URL.createObjectURL(content); + downloadLink.download = 'participant_resumes.zip'; + downloadLink.click(); + downloadLink.remove(); + }) + .catch((error) => { + console.error('Error generating zip file:', error); + }); + }} + > + Download participant resumes + + item + ) /* Supplying attendees without resume field */ + } + > + Export participant data to CSV + { router.replace('/sponsor-booth'); diff --git a/package.json b/package.json index a21857ab..57435ec8 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@aws-sdk/client-s3": "^3.241.0", "@aws-sdk/client-ses": "^3.245.0", "@aws-sdk/util-dynamodb": "^3.241.0", + "@babel/helper-remap-async-to-generator": "^7.22.20", "@fontsource/inter": "^4.5.11", "@headlessui/react": "^1.7.7", "@reduxjs/toolkit": "^1.9.1", @@ -43,6 +44,7 @@ "fuse.js": "^6.6.2", "hono": "^3.5.4", "jsonwebtoken": "^8.5.1", + "jszip": "^3.10.1", "meilisearch": "^0.28.0", "mime-types": "^2.1.35", "moment": "^2.29.4", @@ -53,6 +55,7 @@ "nprogress": "^0.2.0", "react": "18.2.0", "react-calendar": "^4.0.0", + "react-csv": "^2.2.2", "react-dom": "18.2.0", "react-flippy": "^1.1.0", "react-gtm-module": "^2.0.11", diff --git a/yarn.lock b/yarn.lock index 5e414f0a..bdfb4272 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1915,6 +1915,14 @@ dependencies: "@babel/highlight" "^7.18.6" +"@babel/code-frame@^7.22.13": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" + integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== + dependencies: + "@babel/highlight" "^7.22.13" + chalk "^2.4.2" + "@babel/compat-data@^7.16.8", "@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.1", "@babel/compat-data@^7.20.5": version "7.20.14" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.14.tgz#4106fc8b755f3e3ee0a0a7c27dde5de1d2b2baf8" @@ -2037,6 +2045,13 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-annotate-as-pure@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" + integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz#acd4edfd7a566d1d51ea975dff38fd52906981bb" @@ -2109,6 +2124,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + "@babel/helper-explode-assignable-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096" @@ -2124,6 +2144,14 @@ "@babel/template" "^7.18.10" "@babel/types" "^7.19.0" +"@babel/helper-function-name@^7.22.5": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + "@babel/helper-hoist-variables@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" @@ -2186,6 +2214,15 @@ "@babel/helper-wrap-function" "^7.18.9" "@babel/types" "^7.18.9" +"@babel/helper-remap-async-to-generator@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz#7b68e1cb4fa964d2996fd063723fb48eca8498e0" + integrity sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-wrap-function" "^7.22.20" + "@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.20.7": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz#243ecd2724d2071532b2c8ad2f0f9f083bcae331" @@ -2224,11 +2261,21 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== +"@babel/helper-string-parser@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" + integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== + "@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + "@babel/helper-validator-option@^7.12.11", "@babel/helper-validator-option@^7.16.7", "@babel/helper-validator-option@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" @@ -2244,6 +2291,15 @@ "@babel/traverse" "^7.20.5" "@babel/types" "^7.20.5" +"@babel/helper-wrap-function@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz#15352b0b9bfb10fc9c76f79f6342c00e3411a569" + integrity sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw== + dependencies: + "@babel/helper-function-name" "^7.22.5" + "@babel/template" "^7.22.15" + "@babel/types" "^7.22.19" + "@babel/helpers@^7.12.13", "@babel/helpers@^7.12.5", "@babel/helpers@^7.16.7", "@babel/helpers@^7.20.7": version "7.20.13" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.13.tgz#e3cb731fb70dc5337134cadc24cbbad31cc87ad2" @@ -2262,6 +2318,15 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/highlight@^7.22.13": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" + integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + "@babel/parser@7.16.4": version "7.16.4" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.4.tgz#d5f92f57cf2c74ffe9b37981c0e72fee7311372e" @@ -2272,6 +2337,11 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.15.tgz#eec9f36d8eaf0948bb88c87a46784b5ee9fd0c89" integrity sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg== +"@babel/parser@^7.22.15": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" + integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.7", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" @@ -3232,6 +3302,15 @@ "@babel/parser" "^7.20.7" "@babel/types" "^7.20.7" +"@babel/template@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" + integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/parser" "^7.22.15" + "@babel/types" "^7.22.15" + "@babel/traverse@^7.1.6", "@babel/traverse@^7.12.11", "@babel/traverse@^7.12.13", "@babel/traverse@^7.12.5", "@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.0", "@babel/traverse@^7.16.10", "@babel/traverse@^7.17.9", "@babel/traverse@^7.20.10", "@babel/traverse@^7.20.12", "@babel/traverse@^7.20.13", "@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.2": version "7.20.13" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.13.tgz#817c1ba13d11accca89478bd5481b2d168d07473" @@ -3257,6 +3336,15 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" +"@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" + integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + "@base2/pretty-print-object@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@base2/pretty-print-object/-/pretty-print-object-1.0.1.tgz#371ba8be66d556812dc7fb169ebc3c08378f69d4" @@ -22648,6 +22736,11 @@ react-calendar@^4.0.0: get-user-locale "^1.2.0" prop-types "^15.6.0" +react-csv@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/react-csv/-/react-csv-2.2.2.tgz#5bbf0d72a846412221a14880f294da9d6def9bfb" + integrity sha512-RG5hOcZKZFigIGE8LxIEV/OgS1vigFQT4EkaHeKgyuCbUAu9Nbd/1RYq++bJcJJ9VOqO/n9TZRADsXNDR4VEpw== + react-docgen-typescript@^2.1.1: version "2.2.2" resolved "https://registry.yarnpkg.com/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz#4611055e569edc071204aadb20e1c93e1ab1659c" From 2214d491036195760020ed2aec41bc206a607116 Mon Sep 17 00:00:00 2001 From: Jiong Yan Yap Date: Tue, 17 Oct 2023 12:57:24 -0700 Subject: [PATCH 2/3] Add styling for download buttons --- .../pages/participant-database/index.tsx | 127 +++++++++++------- 1 file changed, 78 insertions(+), 49 deletions(-) diff --git a/apps/dashboard/pages/participant-database/index.tsx b/apps/dashboard/pages/participant-database/index.tsx index 07831300..3687c31e 100644 --- a/apps/dashboard/pages/participant-database/index.tsx +++ b/apps/dashboard/pages/participant-database/index.tsx @@ -247,57 +247,56 @@ const Index = () => { return ( - { - for (const attendee of attendees) { - if (attendee.resume) { - const response = await fetch(attendee.resume); - const resumeBlob = await response.blob(); - const attendeeName = attendee.full_name.replace(' ', ''); // Remove whitespaces from name - zip.file(attendeeName + '_Resume', resumeBlob); + + { + router.replace('/sponsor-booth'); + }} + > + Illustration + + { + for (const attendee of attendees) { + if (attendee.resume) { + const response = await fetch(attendee.resume); + const resumeBlob = await response.blob(); + const attendeeName = attendee.full_name.replace(' ', ''); // Remove whitespaces from name + zip.file(attendeeName + '_Resume', resumeBlob); + } } + // Generate the zip file asynchronously + zip + .generateAsync({ type: 'blob' }) + .then((content) => { + // 'content' is a Blob containing the zip file data + // Example: Create a download link for the zip file + // Could use something like FileSaver.js (https://github.com/eligrey/FileSaver.js), but didn't want to add extra dependencies + const downloadLink = document.createElement('a'); + downloadLink.href = URL.createObjectURL(content); + downloadLink.download = 'participant_resumes.zip'; + downloadLink.click(); + downloadLink.remove(); + }) + .catch((error) => { + console.error('Error generating zip file:', error); + }); + }} + > + Download participant resumes + + item + ) /* Supplying attendees without resume field */ } - // Generate the zip file asynchronously - zip - .generateAsync({ type: 'blob' }) - .then((content) => { - // 'content' is a Blob containing the zip file data - // Example: Create a download link for the zip file - // Could use something like FileSaver.js (https://github.com/eligrey/FileSaver.js), but didn't want to add extra dependencies - const downloadLink = document.createElement('a'); - downloadLink.href = URL.createObjectURL(content); - downloadLink.download = 'participant_resumes.zip'; - downloadLink.click(); - downloadLink.remove(); - }) - .catch((error) => { - console.error('Error generating zip file:', error); - }); - }} - > - Download participant resumes - - item - ) /* Supplying attendees without resume field */ - } - > - Export participant data to CSV - - { - router.replace('/sponsor-booth'); - }} - > - Illustration - + > + Export participant data to CSV + + + Date: Tue, 24 Oct 2023 10:43:37 -0700 Subject: [PATCH 3/3] Export CSV PR fixes --- .../pages/participant-database/index.tsx | 60 ++++++++++--------- package.json | 1 - 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/apps/dashboard/pages/participant-database/index.tsx b/apps/dashboard/pages/participant-database/index.tsx index 3687c31e..2f7a858b 100644 --- a/apps/dashboard/pages/participant-database/index.tsx +++ b/apps/dashboard/pages/participant-database/index.tsx @@ -38,7 +38,6 @@ const Index = () => { const [currentAttendee, setCurrentAttendee] = useState(null); const [modalActive, setModalActive] = useState(false); const [attendeeName, setAttendeeName] = useState(''); - const [baseAttendeeData, setBaseAttendeeData] = useState(); const zip = new JSZip(); useEffect(() => { @@ -245,6 +244,36 @@ const Index = () => { } } + async function downloadResumePdfs() { + for (const attendee of attendees) { + if (attendee.resume) { + const response = await fetch(attendee.resume); + const resumeBlob = await response.blob(); + const attendeeName = attendee.full_name.replace(' ', ''); // Remove whitespaces from name + // eslint-disable-next-line + const regExp = '[^/]+$'; + const fileType = resumeBlob.type.match(regExp)[0]; + zip.file(attendeeName + '_Resume.' + fileType, resumeBlob); + } + } + // Generate the zip file asynchronously + zip + .generateAsync({ type: 'blob' }) + .then((content) => { + // 'content' is a Blob containing the zip file data + // Example: Create a download link for the zip file + // Could use something like FileSaver.js (https://github.com/eligrey/FileSaver.js), but didn't want to add extra dependencies + const downloadLink = document.createElement('a'); + downloadLink.href = URL.createObjectURL(content); + downloadLink.download = 'participant_resumes.zip'; + downloadLink.click(); + downloadLink.remove(); + }) + .catch((error) => { + console.error('Error generating zip file:', error); + }); + } + return ( @@ -255,34 +284,7 @@ const Index = () => { > Illustration - { - for (const attendee of attendees) { - if (attendee.resume) { - const response = await fetch(attendee.resume); - const resumeBlob = await response.blob(); - const attendeeName = attendee.full_name.replace(' ', ''); // Remove whitespaces from name - zip.file(attendeeName + '_Resume', resumeBlob); - } - } - // Generate the zip file asynchronously - zip - .generateAsync({ type: 'blob' }) - .then((content) => { - // 'content' is a Blob containing the zip file data - // Example: Create a download link for the zip file - // Could use something like FileSaver.js (https://github.com/eligrey/FileSaver.js), but didn't want to add extra dependencies - const downloadLink = document.createElement('a'); - downloadLink.href = URL.createObjectURL(content); - downloadLink.download = 'participant_resumes.zip'; - downloadLink.click(); - downloadLink.remove(); - }) - .catch((error) => { - console.error('Error generating zip file:', error); - }); - }} - > + Download participant resumes