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' ;
3639import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs' ;
3740import { DatePicker } from '@mui/x-date-pickers/DatePicker' ;
3841import { 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.
5558const ociDarkBlue = '#2c519e' ;
56- // const ociLightBlue = '#76c8d4'; // not currently used
59+ const ociLightBlue = '#76c8d4' ;
5760// const ociOrange = '#f8b576'; // too light
5861const 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