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

Candidate Decider - Hide Latency from Listeners through Frontend State Updates #495

Merged
merged 10 commits into from
Sep 26, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,7 @@
.showOtherVotes {
margin-left: 1%;
}

.previousNextButtonContainer {
margin-right: 0.5% !important;
}
78 changes: 70 additions & 8 deletions frontend/src/components/Candidate-Decider/CandidateDecider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { Button, Dropdown, Checkbox } from 'semantic-ui-react';
import CandidateDeciderAPI from '../../API/CandidateDeciderAPI';
import ResponsesPanel from './ResponsesPanel';
Expand All @@ -18,7 +18,7 @@ const CandidateDecider: React.FC<CandidateDeciderProps> = ({ uuid }) => {
const [showOtherVotes, setShowOtherVotes] = useState<boolean>(false);

const userInfo = useSelf();
const instance = useCandidateDeciderInstance(uuid);
const [instance, setInstance] = useCandidateDeciderInstance(uuid);

const getRating = () => {
const rating = instance.candidates[currentCandidate].ratings.find(
Expand All @@ -36,6 +36,17 @@ const CandidateDecider: React.FC<CandidateDeciderProps> = ({ uuid }) => {
return '';
};

const [currentRating, setCurrentRating] = useState<Rating>(0);
const [currentComment, setCurrentComment] = useState<string>('');

useEffect(() => {
if (instance.candidates[currentCandidate]) {
setCurrentRating(getRating());
setCurrentComment(getComment());
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentCandidate, instance.candidates]);

const next = () => {
if (currentCandidate === instance.candidates.length - 1) return;
setCurrentCandidate((prev) => prev + 1);
Expand All @@ -48,10 +59,52 @@ const CandidateDecider: React.FC<CandidateDeciderProps> = ({ uuid }) => {

const handleRatingChange = (id: number, rating: Rating) => {
CandidateDeciderAPI.updateRating(instance.uuid, id, rating);
if (userInfo) {
setInstance((instance) => ({
...instance,
candidates: instance.candidates.map((candidate) =>
candidate.id === id
? {
...candidate,
ratings: candidate.ratings.find(
(currRating) => currRating.reviewer.email === userInfo.email
)
? candidate.ratings.map((currRating) =>
currRating.reviewer.email === userInfo.email
? { rating, reviewer: userInfo }
: currRating
)
: [...candidate.ratings, { rating, reviewer: userInfo }]
}
: candidate
)
}));
}
};

const handleCommentChange = (id: number, comment: string) => {
CandidateDeciderAPI.updateComment(instance.uuid, id, comment);
if (userInfo) {
setInstance((instance) => ({
...instance,
candidates: instance.candidates.map((candidate) =>
candidate.id === id
? {
...candidate,
comments: candidate.comments.find(
(currComment) => currComment.reviewer.email === userInfo.email
)
? candidate.comments.map((currComment) =>
currComment.reviewer.email === userInfo.email
? { comment, reviewer: userInfo }
: currComment
)
: [...candidate.comments, { comment, reviewer: userInfo }]
}
: candidate
)
}));
}
};

return instance.candidates.length === 0 ? (
Expand All @@ -76,7 +129,7 @@ const CandidateDecider: React.FC<CandidateDeciderProps> = ({ uuid }) => {
onChange={(_, data) => setCurrentCandidate(data.value as number)}
/>
<span className={styles.ofNum}>of {instance.candidates.length}</span>
<Button.Group>
<Button.Group className={styles.previousNextButtonContainer}>
<Button basic color="blue" disabled={currentCandidate === 0} onClick={previous}>
PREVIOUS
</Button>
Expand All @@ -89,6 +142,16 @@ const CandidateDecider: React.FC<CandidateDeciderProps> = ({ uuid }) => {
NEXT
</Button>
</Button.Group>
<Button
className="ui blue button"
disabled={currentComment === getComment() && currentRating === getRating()}
onClick={() => {
handleRatingChange(currentCandidate, currentRating);
handleCommentChange(currentCandidate, currentComment);
}}
>
Save
</Button>
<Checkbox
className={styles.showOtherVotes}
toggle
Expand All @@ -100,11 +163,10 @@ const CandidateDecider: React.FC<CandidateDeciderProps> = ({ uuid }) => {
<ResponsesPanel
headers={instance.headers}
responses={instance.candidates[currentCandidate].responses}
handleRatingChange={handleRatingChange}
rating={getRating()}
currentCandidate={currentCandidate}
handleCommentChange={handleCommentChange}
comment={getComment()}
currentComment={currentComment}
setCurrentComment={setCurrentComment}
currentRating={currentRating}
setCurrentRating={setCurrentRating}
/>
</div>
<div className={styles.progressContainer}>
Expand Down
80 changes: 27 additions & 53 deletions frontend/src/components/Candidate-Decider/ResponsesPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { useEffect, useState } from 'react';
import { Form, Radio, Button } from 'semantic-ui-react';
import { Dispatch, SetStateAction } from 'react';
import { Form, Radio } from 'semantic-ui-react';
import styles from './ResponsesPanel.module.css';

type Props = {
headers: string[];
responses: string[];
rating: Rating;
handleRatingChange: (id: number, rating: Rating) => void;
comment: string;
handleCommentChange: (id: number, comment: string) => void;
currentCandidate: number;
currentRating: Rating;
setCurrentRating: Dispatch<SetStateAction<Rating>>;
currentComment: string;
setCurrentComment: Dispatch<SetStateAction<string>>;
};

const ratings = [
Expand All @@ -24,11 +23,10 @@ const ratings = [
const ResponsesPanel: React.FC<Props> = ({
headers,
responses,
rating,
handleRatingChange,
currentCandidate,
handleCommentChange,
comment
currentRating,
setCurrentRating,
currentComment,
setCurrentComment
}) => (
<div>
<Form>
Expand All @@ -40,17 +38,13 @@ const ResponsesPanel: React.FC<Props> = ({
name="rating-group"
value={rt.value}
color={rt.color}
checked={rt.value === rating}
onChange={() => handleRatingChange(currentCandidate, rt.value as Rating)}
checked={rt.value === currentRating}
onClick={() => setCurrentRating(rt.value as Rating)}
/>
</Form.Field>
))}
</Form.Group>
<CommentEditor
handleCommentChange={handleCommentChange}
comment={comment}
currentCandidate={currentCandidate}
/>
<CommentEditor currentComment={currentComment} setCurrentComment={setCurrentComment} />
</Form>
{headers.map((header, i) => (
<div key={i} className={styles.questionResponseContainer}>
Expand All @@ -62,40 +56,20 @@ const ResponsesPanel: React.FC<Props> = ({
);

type CommentEditorProps = {
comment: string;
handleCommentChange: (id: number, comment: string) => void;
currentCandidate: number;
currentComment: string;
setCurrentComment: Dispatch<SetStateAction<string>>;
};

const CommentEditor: React.FC<CommentEditorProps> = ({
handleCommentChange,
comment,
currentCandidate
}) => {
const [currentComment, setCurrentComment] = useState<string>(comment);

useEffect(() => setCurrentComment(comment), [comment]);

return (
<div>
<Form.Group inline>
<Form.Input
className="fifteen wide field"
placeholder={'Comment...'}
onChange={(_, data) => setCurrentComment(data.value)}
value={currentComment}
/>
<Button
className="ui blue button"
onClick={() => {
handleCommentChange(currentCandidate, currentComment);
setCurrentComment('');
}}
>
Save
</Button>
</Form.Group>
</div>
);
};
const CommentEditor: React.FC<CommentEditorProps> = ({ currentComment, setCurrentComment }) => (
<div>
<Form.Group inline>
<Form.Input
className="fifteen wide field"
placeholder={'Comment...'}
onChange={(_, data) => setCurrentComment(data.value)}
value={currentComment}
/>
</Form.Group>
</div>
);
export default ResponsesPanel;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, Dispatch, SetStateAction } from 'react';
import { onSnapshot, doc, collection, getDoc, DocumentReference } from 'firebase/firestore';
import { db } from '../../firebase';

Expand Down Expand Up @@ -28,7 +28,9 @@ export type DBCandidateDeciderInstance = {
readonly authorizedRoles: Role[];
isOpen: boolean;
};
const useCandidateDeciderInstance = (uuid: string): CandidateDeciderInstance => {
const useCandidateDeciderInstance = (
uuid: string
): [CandidateDeciderInstance, Dispatch<SetStateAction<CandidateDeciderInstance>>] => {
const [instance, setInstance] = useState<CandidateDeciderInstance>(blankInstance);

useEffect(() => {
Expand Down Expand Up @@ -67,7 +69,7 @@ const useCandidateDeciderInstance = (uuid: string): CandidateDeciderInstance =>
return unsubscribe;
}, [uuid]);

return instance;
return [instance, setInstance];
};

const blankInstance: CandidateDeciderInstance = {
Expand Down
Loading