Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TEC to homepage #500

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9e1c792
add name to contributors list
patriciaahuang Sep 25, 2023
02dd373
add tec tracker to homepage
patriciaahuang Sep 25, 2023
8e32bdd
add tec tracker to homepage
patriciaahuang Sep 25, 2023
bdd92f2
add tec tracker to homepage
patriciaahuang Sep 25, 2023
181cd98
Add Helper Text for Candidate Decider Input Format (#496)
andrew032011 Sep 25, 2023
b8b65cb
Members/csv upload (#485)
oscarwang20 Sep 25, 2023
270cdeb
add name to contributors list
patriciaahuang Sep 25, 2023
67c89e9
add tec tracker to homepage
patriciaahuang Sep 25, 2023
13f821e
add tec tracker to homepage
patriciaahuang Sep 25, 2023
6c16976
add tec tracker to homepage
patriciaahuang Sep 25, 2023
0ccd438
run prettier
patriciaahuang Sep 25, 2023
3530ab5
add name to contributors list
patriciaahuang Sep 25, 2023
0cc929c
add tec tracker to homepage
patriciaahuang Sep 25, 2023
220b9e7
add tec tracker to homepage
patriciaahuang Sep 25, 2023
22463fb
add tec tracker to homepage
patriciaahuang Sep 25, 2023
bdd6330
add name to contributors list
patriciaahuang Sep 25, 2023
e5321c9
add tec tracker to homepage
patriciaahuang Sep 25, 2023
34f6bae
add tec tracker to homepage
patriciaahuang Sep 25, 2023
8caff9b
add tec tracker to homepage
patriciaahuang Sep 25, 2023
06e66c1
add name to contributors list
patriciaahuang Sep 25, 2023
f888985
add tec tracker to homepage
patriciaahuang Sep 25, 2023
9690cf1
add tec tracker to homepage
patriciaahuang Sep 25, 2023
aa443a5
Merge branch 'add-tec-to-homepage' of github.com:cornell-dti/idol int…
patriciaahuang Sep 25, 2023
1b58d6b
add name to contributors list
patriciaahuang Sep 25, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ All the common types used by the packages are defined [here](./common-types/inde
- **Hope Zheng** - Designer
- **Oscar Wang** - Developer
- **Alyssa Zhang** - Developer
- **Patricia Huang** - Developer

### Spring 2023

Expand Down
4 changes: 4 additions & 0 deletions frontend/public/sample_candidate_decider_input.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
NetID,First Name,Last Name,What is your favorite tech stack?
aaa111,Alder,Alderson,MERN
ddd222,Donald,Donaldson,MEAN
axc2,Andrew,Chen,FERN
Binary file added frontend/public/sample_csv.zip
Binary file not shown.
23 changes: 23 additions & 0 deletions frontend/src/components/Admin/AddUser/AddUser.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,14 @@

.fullWidth {
width: 100%;
word-wrap: break-word;
flex: inherit;
}

.halfWidth {
width: 50%;
word-wrap: break-word;
flex: inherit;
}

.userEmail {
Expand All @@ -53,3 +57,22 @@
overflow: hidden;
text-overflow: ellipsis;
}

.errorMessage {
color: red;
margin-top: 1rem;
height: 8rem;
overflow: scroll;
}

.successMessage {
color: green;
margin-top: 1rem;
max-height: 8rem;
overflow: scroll;
}

.wrap {
word-wrap: break-word;
flex: inherit;
}
169 changes: 167 additions & 2 deletions frontend/src/components/Admin/AddUser/AddUser.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { useState } from 'react';
import { Card, Button, Form, Input, Select, TextArea } from 'semantic-ui-react';
import ALL_ROLES from 'common-types/constants';
import csvtojson from 'csvtojson';
import styles from './AddUser.module.css';
import { Member, MembersAPI } from '../../../API/MembersAPI';
import ErrorModal from '../../Modals/ErrorModal';
import { getNetIDFromEmail, getRoleDescriptionFromRoleID, Emitters } from '../../../utils';
import { useMembers } from '../../Common/FirestoreDataProvider';
import { useMembers, useTeams } from '../../Common/FirestoreDataProvider';
import { TeamSearch } from '../../Common/Search/Search';

type CurrentSelectedMember = Omit<Member, 'netid' | 'roleDescription'>;
Expand Down Expand Up @@ -68,12 +70,21 @@ type State = {
readonly isCreatingUser: boolean;
};

type UploadStatus = {
readonly status: 'success' | 'error';
readonly msg: string;
readonly errs?: string[];
};

export default function AddUser(): JSX.Element {
const allMembers = useMembers();
const validSubteams = useTeams().map((t) => t.name);
const [state, setState] = useState<State>({
currentSelectedMember: allMembers[0],
isCreatingUser: false
});
const [csvFile, setCsvFile] = useState<File | undefined>(undefined);
const [uploadStatus, setUploadStatus] = useState<UploadStatus>();

function createNewUser(): void {
setState({
Expand Down Expand Up @@ -125,6 +136,121 @@ export default function AddUser(): JSX.Element {
});
}

function processJson(json: any[]): void {
for (const m of json) {
const netId = getNetIDFromEmail(m.email);
const currMember = allMembers.find((mem) => mem.netid === netId);
if (currMember) {
const updatedMember = {
netid: netId,
email: m.email,
firstName: m.firstName || currMember.firstName,
lastName: m.lastName || currMember.lastName,
pronouns: m.pronouns || currMember.pronouns,
graduation: m.graduation || currMember.graduation,
major: m.major || currMember.major,
doubleMajor: m.doubleMajor || currMember.doubleMajor,
minor: m.minor || currMember.minor,
website: m.website || currMember.website,
linkedin: m.linkedin || currMember.linkedin,
github: m.github || currMember.github,
hometown: m.hometown || currMember.hometown,
about: m.about || currMember.about,
subteams: m.subteam ? [m.subteam] : currMember.subteams,
formerSubteams: m.formerSubteams
? m.formerSubteams.split(', ')
: currMember.formerSubteams,
role: m.role || currMember.role,
roleDescription: getRoleDescriptionFromRoleID(m.role)
} as IdolMember;
MembersAPI.updateMember(updatedMember);
} else {
const updatedMember = {
netid: netId,
email: m.email,
firstName: m.firstName || '',
lastName: m.lastName || '',
pronouns: m.pronouns || '',
graduation: m.graduation || '',
major: m.major || '',
doubleMajor: m.doubleMajor || '',
minor: m.minor || '',
website: m.website || '',
linkedin: m.linkedin || '',
github: m.github || '',
hometown: m.hometown || '',
about: m.about || '',
subteams: m.subteam ? [m.subteam] : [],
formerSubteams: m.formerSubteams ? m.formerSubteams.split(', ') : [],
role: m.role || ('' as Role),
roleDescription: getRoleDescriptionFromRoleID(m.role)
} as IdolMember;
MembersAPI.setMember(updatedMember);
}
}
}

async function uploadUsersCsv(csvFile: File | undefined): Promise<void> {
if (csvFile) {
const csv = await csvFile.text();
const columnHeaders = csv.split('\n')[0].split(',');
if (!columnHeaders.includes('email')) {
setUploadStatus({
status: 'error',
msg: 'Error: CSV must contain an email column'
});
return;
}
if (!columnHeaders.includes('role')) {
setUploadStatus({
status: 'error',
msg: 'Error: CSV must contain a role column'
});
return;
}
const json = await csvtojson().fromString(csv);
const errors = json
.map((m) => {
const [email, role, subteam] = [m.email, m.role, m.subteam];
const formerSubteams: string[] = m.formerSubteams ? m.formerSubteams.split(', ') : [];
const err = [];
if (!email) {
err.push('missing email');
}
if (!role) {
err.push('missing role');
}
if (role && !ALL_ROLES.includes(role as Role)) {
err.push('invalid role');
}
if (subteam && !validSubteams.includes(subteam)) {
err.push('invalid subteam');
}
if (formerSubteams.some((t) => !validSubteams.includes(t))) {
err.push('at least one invalid former subteam');
}
if (formerSubteams.includes(subteam)) {
err.push('subteam cannot be in former subteams');
}
return err.length > 0 ? `Row ${json.indexOf(m) + 1}: ${err.join(', ')}` : '';
})
.filter((err) => err.length > 0);
if (errors.length > 0) {
setUploadStatus({
status: 'error',
msg: `Error: ${errors.length} ${errors.length === 1 ? 'row is' : 'rows are'} invalid!`,
errs: errors
});
} else {
processJson(json);
setUploadStatus({
status: 'success',
msg: `Successfully uploaded ${json.length} members!`
});
}
}
}

function setCurrentlySelectedMember(setter: (m: CurrentSelectedMember) => CurrentSelectedMember) {
setState((s) => {
if (!s.currentSelectedMember) return s;
Expand Down Expand Up @@ -160,7 +286,7 @@ export default function AddUser(): JSX.Element {
))}
</Card.Content>
</div>
<Card.Content extra>
<Card.Content>
<div className={`ui one buttons ${styles.fullWidth}`}>
<Button
basic
Expand All @@ -182,6 +308,45 @@ export default function AddUser(): JSX.Element {
</Button>
</div>
</Card.Content>
<Card.Content>
{csvFile ? (
<div className={`ui one buttons ${styles.fullWidth}`}>
<Button
basic
color="green"
className={styles.fullWidth}
onClick={() => {
uploadUsersCsv(csvFile);
}}
>
{`Upload ${csvFile.name}`}
</Button>
</div>
) : undefined}
<input
className={styles.wrap}
type="file"
accept=".csv"
onChange={(e) => setCsvFile(e.target.files?.[0])}
/>
<a href="/sample_csv.zip">Download sample .csv file</a>
{uploadStatus ? (
<div
className={
uploadStatus.status === 'error' ? styles.errorMessage : styles.successMessage
}
>
<p>{`${uploadStatus.msg}`}</p>
{uploadStatus.errs ? (
<div>
{uploadStatus.errs.map((err) => (
<p>{err}</p>
))}
</div>
) : undefined}
</div>
) : undefined}
</Card.Content>
</Card>
{state.currentSelectedMember !== undefined ? (
<Card className={styles.userEditor}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ const CandidateDeciderInstanceCreator = ({
<Form success={success}>
<Form.Input label="Name" value={name} onChange={(e) => setName(e.target.value)} />
<input type="file" accept=".csv" onChange={handleFileUpload} key={fileInKey || ''} />
<label>
{' '}
Format: .csv with at least a "NetID", "First Name", and "Last Name" column.{' '}
<a href="/sample_candidate_decider_input.csv">Download sample file here.</a>
</label>
<Message>
All leads and IDOL admins have permission to all Candidate Decider instances
</Message>
Expand Down
11 changes: 10 additions & 1 deletion frontend/src/components/Homepage/Homepage/Homepage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,20 @@ const everyoneItems: readonly NavigationCardItem[] = [
description: 'Edit your profile image.',
link: '/forms/profileImage'
},
{ header: 'Sign-In Form', description: 'Sign in to an event.', link: '/forms/signin' },
{
header: 'Sign-In Form',
description: 'Sign in to an event.',
link: '/forms/signin'
},
{
header: 'Shoutouts',
description: 'Give someone a shoutout or view your past given shoutouts.',
link: '/forms/shoutouts'
},
{
header: 'Team Event Credits',
description: 'Track your team event credits.',
link: '/forms/teamEventCredits'
}
];

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,6 @@
"**/*.{ts,js,tsx,scss,css,html,md}": [
"yarn prettier --write"
]
}
},
"dependencies": {}
}
13 changes: 4 additions & 9 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4630,15 +4630,10 @@ camelize@^1.0.0:
resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b"
integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=

caniuse-lite@^1.0.30001202, caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.30001228, caniuse-lite@^1.0.30001261:
version "1.0.30001264"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001264.tgz#88f625a60efb6724c7c62ac698bc8dbd9757e55b"
integrity sha512-Ftfqqfcs/ePiUmyaySsQ4PUsdcYyXG2rfoBVsk3iY1ahHaJEw65vfb7Suzqm+cEkwwPIv/XWkg27iCpRavH4zA==

caniuse-lite@^1.0.30001332:
version "1.0.30001361"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001361.tgz#ba2adb2527566fb96f3ac7c67698ae7fc495a28d"
integrity sha512-ybhCrjNtkFji1/Wto6SSJKkWk6kZgVQsDq5QI83SafsF6FXv2JB4df9eEdH6g8sdGgqTXrFLjAxqBGgYoU3azQ==
caniuse-lite@^1.0.30001202, caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.30001228, caniuse-lite@^1.0.30001261, caniuse-lite@^1.0.30001332:
version "1.0.30001538"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz"
integrity sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw==

capture-stack-trace@^1.0.0:
version "1.0.1"
Expand Down
Loading