Skip to content

Commit 6f64ea6

Browse files
committed
Added a toggle for at-work/outside-work/both and a pie chart showing breakdown of responses (with emojis).
1 parent 7712618 commit 6f64ea6

File tree

1 file changed

+197
-33
lines changed

1 file changed

+197
-33
lines changed

web-ui/src/pages/PulseReportPage.jsx

Lines changed: 197 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {
66
BarChart,
77
CartesianGrid,
88
Legend,
9+
Pie,
10+
PieChart,
911
ResponsiveContainer,
1012
Tooltip,
1113
XAxis,
@@ -32,7 +34,8 @@ import {
3234
TextField,
3335
Typography
3436
} from '@mui/material';
35-
37+
import ToggleButton from '@mui/material/ToggleButton';
38+
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
3639
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
3740
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
3841
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
@@ -53,7 +56,7 @@ import './PulseReportPage.css';
5356
// Recharts doesn't support using CSS variables, so we can't
5457
// easily use color variables defined in variables.css.
5558
const ociDarkBlue = '#2c519e';
56-
//const ociLightBlue = '#76c8d4'; // not currently used
59+
const ociLightBlue = '#76c8d4';
5760
// const ociOrange = '#f8b576'; // too light
5861
const orange = '#b26801';
5962

@@ -69,6 +72,12 @@ const propertyMap = {
6972
[ScoreOption.COMBINED]: 'combinedAverage'
7073
};
7174

75+
const ScoreOptionLabel = {
76+
'Internal': 'At Work',
77+
'External': 'Outside Work',
78+
'Combined': 'Both',
79+
};
80+
7281
/*
7382
// Returns a random, integer score between 1 and 5.
7483
// We may want to uncomment this later for testing.
@@ -112,6 +121,7 @@ const PulseReportPage = () => {
112121
const [selectedPulse, setSelectedPulse] = useState(null);
113122
const [showComments, setShowComments] = useState(false);
114123
const [teamMembers, setTeamMembers] = useState([]);
124+
const [pieChartData, setPieChartData] = useState([]);
115125

116126
/*
117127
// This generates random data to use in the line chart.
@@ -210,6 +220,21 @@ const PulseReportPage = () => {
210220
}
211221
}
212222

223+
let pieCounts = [
224+
{name: "veryDissatisfied", value: 0},
225+
{name: "dissatisfied", value: 0},
226+
{name: "neutral", value: 0},
227+
{name: "satisfied", value: 0},
228+
{name: "verySatisfied", value: 0},
229+
];
230+
for(let day of scoreChartDataPoints) {
231+
day.datapoints.forEach(datapoint => {
232+
pieCounts[datapoint.internalScore - 1].value++;
233+
pieCounts[datapoint.externalScore - 1].value++;
234+
});
235+
}
236+
setPieChartData(pieCounts);
237+
213238
setScoreChartData(scoreChartDataPoints.map(day => {
214239
const iScores = {};
215240
const eScores = {};
@@ -319,27 +344,6 @@ const PulseReportPage = () => {
319344
<CardContent>
320345
<div className="average-header row">
321346
<Typography variant="h5">Average Scores</Typography>
322-
<FormControl style={{ width: '8rem' }}>
323-
<TextField
324-
select
325-
size="small"
326-
label="Score Type"
327-
onChange={e => setScoreType(e.target.value)}
328-
sx={{ width: '8rem' }}
329-
value={scoreType}
330-
variant="outlined"
331-
>
332-
<MenuItem value={ScoreOption.INTERNAL}>
333-
{ScoreOption.INTERNAL}
334-
</MenuItem>
335-
<MenuItem value={ScoreOption.EXTERNAL}>
336-
{ScoreOption.EXTERNAL}
337-
</MenuItem>
338-
<MenuItem value={ScoreOption.COMBINED}>
339-
{ScoreOption.COMBINED}
340-
</MenuItem>
341-
</TextField>
342-
</FormControl>
343347
<FormControl style={{ width: '7.5rem' }}>
344348
<TextField
345349
select
@@ -363,7 +367,7 @@ const PulseReportPage = () => {
363367
</Card>
364368
);
365369

366-
const barChart = () => (
370+
const scoreDistributionChart = () => (
367371
<Card>
368372
<CardHeader
369373
title="Distribution of pulse scores for selected team members"
@@ -386,8 +390,20 @@ const PulseReportPage = () => {
386390
<YAxis />
387391
<Tooltip />
388392
<Legend />
389-
<Bar dataKey="internal" fill={ociDarkBlue} />
390-
<Bar dataKey="external" fill={orange} />
393+
{(scoreType == ScoreOption.COMBINED || scoreType == ScoreOption.INTERNAL) &&
394+
<Bar
395+
dataKey="internal"
396+
fill={ociDarkBlue}
397+
name={ScoreOptionLabel[ScoreOption.INTERNAL]}
398+
/>
399+
}
400+
{(scoreType == ScoreOption.COMBINED || scoreType == ScoreOption.EXTERNAL) &&
401+
<Bar
402+
dataKey="external"
403+
fill={orange}
404+
name={ScoreOptionLabel[ScoreOption.EXTERNAL]}
405+
/>
406+
}
391407
</BarChart>
392408
<ExpandMore
393409
expand={expanded}
@@ -443,7 +459,9 @@ const PulseReportPage = () => {
443459
];
444460

445461
const labelToSentiment = (label) => {
446-
const suffix = label.includes("internal") ? "At Work" : "Outside Work";
462+
const suffix = label.includes("internal")
463+
? ScoreOptionLabel[ScoreOption.INTERNAL]
464+
: ScoreOptionLabel[ScoreOption.EXTERNAL];
447465
switch(label.replace("internal", "").replace("external", "")) {
448466
case "VeryDissatisfied":
449467
return <><SentimentVeryDissatisfied/> {suffix}</>;
@@ -465,7 +483,7 @@ const PulseReportPage = () => {
465483
<div className="custom-tooltip">
466484
<p>{label}</p>
467485
{payload.map(p => {
468-
return <div style={{color: `${p.color}`}}>
486+
return <div key={p.dataKey} style={{color: `${p.color}`}}>
469487
{p.value} {p.name.props.children}
470488
</div>;
471489
})}
@@ -498,10 +516,95 @@ const PulseReportPage = () => {
498516
);
499517
};
500518

501-
const lineChart = () => (
519+
const pulseScoresTitle = () => {
520+
let title = "Pulse scores for";
521+
if (scoreType == ScoreOption.COMBINED ||
522+
scoreType == ScoreOption.INTERNAL) {
523+
title += ` "${ScoreOptionLabel[ScoreOption.INTERNAL]}"`;
524+
}
525+
if (scoreType == ScoreOption.COMBINED) {
526+
title += " and";
527+
}
528+
if (scoreType == ScoreOption.COMBINED ||
529+
scoreType == ScoreOption.EXTERNAL) {
530+
title += ` "${ScoreOptionLabel[ScoreOption.EXTERNAL]}"`;
531+
}
532+
return title;
533+
};
534+
535+
const pieLabelToSentiment = (label) => {
536+
switch(label.toLowerCase()) {
537+
case "verydissatisfied":
538+
//return <SentimentVeryDissatisfied />;
539+
return "😦";
540+
case "dissatisfied":
541+
//return <SentimentDissatisfied />;
542+
return "🙁";
543+
case "neutral":
544+
//return <SentimentNeutral />;
545+
return "😐";
546+
case "satisfied":
547+
//return <SentimentSatisfied />;
548+
return "🙂";
549+
case "verysatisfied":
550+
//return <SentimentVerySatisfied />;
551+
return "😀";
552+
}
553+
return "ERROR";
554+
};
555+
556+
const RADIAN = Math.PI / 180;
557+
const renderPieLabel = function({ cx, cy, midAngle, innerRadius, outerRadius,
558+
percent, index, name, value }) {
559+
const radius = innerRadius + (outerRadius - innerRadius) * 0.5;
560+
const x = cx + radius * Math.cos(-midAngle * RADIAN);
561+
const y = cy + radius * Math.sin(-midAngle * RADIAN);
562+
563+
return (
564+
<>
565+
<text x={x} y={y} fill="white" textAnchor={x > cx ? 'start' : 'end'}
566+
dominantBaseline="central">
567+
{pieLabelToSentiment(name)} {value}
568+
</text>
569+
</>
570+
);
571+
};
572+
573+
const titleWords = (text) => {
574+
if (text.match(/^[a-z]+$/)) {
575+
// Uppercase the first letter
576+
text = text[0].toUpperCase() + text.substring(1);
577+
} else {
578+
// Split words and uppercase the first word
579+
let words = text.split(/(?<=[a-z])(?=[A-Z\d])/);
580+
words[0] = words[0][0].toUpperCase() + words[0].substring(1);
581+
text= "";
582+
let separator = "";
583+
for(let word of words) {
584+
text += `${separator}${word}`;
585+
separator = " ";
586+
}
587+
}
588+
return text;
589+
};
590+
591+
const CustomPieTooltip = ({ active, payload, label }) => {
592+
if (active && payload && payload.length) {
593+
return (
594+
<div className="custom-tooltip">
595+
<p className="label">{titleWords(payload[0].name)} : {payload[0].value}</p>
596+
</div>
597+
);
598+
}
599+
600+
return null;
601+
};
602+
603+
const pulseScoresChart = () => (
604+
<>
502605
<Card>
503606
<CardHeader
504-
title={'Pulse scores for "At Work" and "Outside Work"'}
607+
title={pulseScoresTitle()}
505608
titleTypographyProps={{ variant: 'h5', component: 'h2' }}
506609
/>
507610
<CardContent>
@@ -519,7 +622,12 @@ const PulseReportPage = () => {
519622
<YAxis />
520623
<Tooltip />
521624
<Legend />
522-
{dataInfo.map((obj) => {
625+
{dataInfo.filter(o => scoreType == ScoreOption.COMBINED ||
626+
(scoreType == ScoreOption.INTERNAL &&
627+
o.key.includes("internal")) ||
628+
(scoreType == ScoreOption.EXTERNAL &&
629+
o.key.includes("external")))
630+
.map((obj) => {
523631
return <Bar
524632
key={obj.key}
525633
dataKey={obj.key}
@@ -535,6 +643,31 @@ const PulseReportPage = () => {
535643
</ResponsiveContainer>
536644
</CardContent>
537645
</Card>
646+
<Card>
647+
<CardHeader
648+
title="Total Responses"
649+
titleTypographyProps={{ variant: 'h5', component: 'h2' }}
650+
/>
651+
<CardContent>
652+
<ResponsiveContainer width="100%" aspect={3.0}>
653+
<PieChart width={300} height={300}>
654+
<Tooltip
655+
wrapperStyle={{ color: "black", backgroundColor: "white", paddingLeft: "10px", paddingRight: "10px" }}
656+
content={<CustomPieTooltip />}
657+
/>
658+
<Pie
659+
data={pieChartData}
660+
dataKey="value"
661+
nameKey="name"
662+
fill={ociLightBlue}
663+
labelLine={false}
664+
label={renderPieLabel}
665+
/>
666+
</PieChart>
667+
</ResponsiveContainer>
668+
</CardContent>
669+
</Card>
670+
</>
538671
);
539672

540673
const responseSummary = () => {
@@ -584,6 +717,25 @@ const PulseReportPage = () => {
584717
);
585718
};
586719

720+
const toggleLabels = {
721+
left: {
722+
title: ScoreOptionLabel[ScoreOption.INTERNAL],
723+
value: ScoreOption.INTERNAL,
724+
},
725+
center: {
726+
title: ScoreOptionLabel[ScoreOption.Combined],
727+
value: ScoreOption.COMBINED,
728+
},
729+
right: {
730+
title: ScoreOptionLabel[ScoreOption.EXTERNAL],
731+
value: ScoreOption.EXTERNAL,
732+
},
733+
};
734+
735+
const toggleChange = (event, value) => {
736+
setScoreType(value);
737+
};
738+
587739
return selectHasViewPulseReportPermission(state) ? (
588740
<div className="pulse-report-page">
589741
<div className="date-pickers">
@@ -601,6 +753,18 @@ const PulseReportPage = () => {
601753
value={dayjs(dateTo)}
602754
/>
603755
</LocalizationProvider>
756+
<ToggleButtonGroup
757+
value={scoreType}
758+
exclusive
759+
onChange={toggleChange}
760+
>
761+
<ToggleButton value={ScoreOption.INTERNAL}>
762+
{ScoreOptionLabel[ScoreOption.INTERNAL]}</ToggleButton>
763+
<ToggleButton value={ScoreOption.COMBINED}>
764+
{ScoreOptionLabel[ScoreOption.COMBINED]}</ToggleButton>
765+
<ToggleButton value={ScoreOption.EXTERNAL}>
766+
{ScoreOptionLabel[ScoreOption.EXTERNAL]}</ToggleButton>
767+
</ToggleButtonGroup>
604768
</div>
605769

606770
{pulses.length === 0 ? (
@@ -614,9 +778,9 @@ const PulseReportPage = () => {
614778
onChange={handleTeamMembersChange}
615779
selected={teamMembers}
616780
/>
617-
{lineChart()}
781+
{pulseScoresChart()}
618782
{averageScores()}
619-
{barChart()}
783+
{scoreDistributionChart()}
620784
<Modal open={showComments} onClose={() => setShowComments(false)}>
621785
<Card className="feedback-request-enable-edits-modal">
622786
<CardHeader

0 commit comments

Comments
 (0)