11import React , { useMemo } from 'react' ;
2+ import PropTypes from 'prop-types' ;
23import {
34 BarChart ,
45 Bar ,
@@ -10,20 +11,20 @@ import {
1011 Label ,
1112} from 'recharts' ;
1213
13- const toNum = ( v , d = 0 ) => {
14- const n = Number ( v ) ;
15- return Number . isFinite ( n ) ? n : d ;
14+ const toNum = ( value ) => {
15+ const n = Number ( value ) ;
16+ return Number . isFinite ( n ) ? n : 0 ;
1617} ;
1718
1819const fmtPct = ( v ) => `${ toNum ( v ) } %` ;
1920const fmtInt = ( v ) => toNum ( v ) . toLocaleString ( ) ;
2021
2122const truncate = ( str , max = 22 ) =>
22- str . length > max ? str . slice ( 0 , max ) + '…' : str ;
23+ typeof str === 'string' && str . length > max ? str . slice ( 0 , max ) + '…' : str ;
2324
2425const CustomTooltip = ( { active, payload, usePercentage, isDark } ) => {
2526 if ( active && payload && payload . length ) {
26- const job = payload [ 0 ] . payload ;
27+ const job = payload [ 0 ] ? .payload || { } ;
2728 return (
2829 < div
2930 className = { `p-2 rounded shadow ${
@@ -33,20 +34,20 @@ const CustomTooltip = ({ active, payload, usePercentage, isDark }) => {
3334 } `}
3435 style = { { fontSize : '0.875rem' } }
3536 >
36- < p > < span style = { { fontWeight : 900 } } > Role:</ span > { job . title } </ p >
37- < p > < span style = { { fontWeight : 900 } } > Conversion Rate:</ span > { fmtPct ( job . conversionRate ) } </ p >
38- < p > < span style = { { fontWeight : 900 } } > Hits:</ span > { fmtInt ( job . hits ) } </ p >
39- < p > < span style = { { fontWeight : 900 } } > Applications:</ span > { fmtInt ( job . applications ) } </ p >
37+ < p > < strong > Role:</ strong > { job . title } </ p >
38+ < p > < strong > Conversion Rate:</ strong > { fmtPct ( job . conversionRate ) } </ p >
39+ < p > < strong > Hits:</ strong > { fmtInt ( job . hits ) } </ p >
40+ < p > < strong > Applications:</ strong > { fmtInt ( job . applications ) } </ p >
4041 </ div >
4142 ) ;
4243 }
4344 return null ;
4445} ;
4546
46- function NonConvertedApplicationsGraph ( { data = [ ] , usePercentage = true , isDark } ) {
47+ function NonConvertedApplicationsGraph ( { data = [ ] , usePercentage, isDark } ) {
4748 const normalized = useMemo (
4849 ( ) =>
49- ( data || [ ] ) . map ( ( d ) => ( {
50+ data . map ( ( d ) => ( {
5051 ...d ,
5152 hits : toNum ( d . hits ) ,
5253 applications : toNum ( d . applications ) ,
@@ -56,23 +57,25 @@ function NonConvertedApplicationsGraph({ data = [], usePercentage = true, isDark
5657 ) ;
5758
5859 const metricKey = usePercentage ? 'conversionRate' : 'applications' ;
60+
5961 const rows = useMemo ( ( ) => {
6062 const sorted = [ ...normalized ] . sort ( ( a , b ) => {
6163 const diff = toNum ( a [ metricKey ] ) - toNum ( b [ metricKey ] ) ;
62- return diff !== 0 ? diff : toNum ( a . conversionRate ) - toNum ( b . conversionRate ) ;
64+ return diff !== 0
65+ ? diff
66+ : toNum ( a . conversionRate ) - toNum ( b . conversionRate ) ;
6367 } ) ;
6468 return sorted . slice ( 0 , 10 ) ;
6569 } , [ normalized , metricKey ] ) ;
6670
6771 const maxValue = useMemo ( ( ) => {
6872 if ( rows . length === 0 ) return 1 ;
69- const m = Math . max ( ...rows . map ( ( r ) => toNum ( r [ metricKey ] , 0 ) ) ) ;
70- return Math . max ( 1 , Math . ceil ( m * 1.05 ) ) ;
73+ const max = Math . max ( ...rows . map ( ( r ) => toNum ( r [ metricKey ] ) ) ) ;
74+ return Math . max ( 1 , Math . ceil ( max * 1.05 ) ) ;
7175 } , [ rows , metricKey ] ) ;
7276
7377 const xDomain = usePercentage ? [ 0 , 100 ] : [ 0 , maxValue ] ;
74- const xTickFormatter = ( v ) => ( usePercentage ? `${ v } %` : fmtInt ( v ) ) ;
75- const labelFormatter = ( v ) => ( usePercentage ? fmtPct ( v ) : fmtInt ( v ) ) ;
78+ const xTickFormatter = usePercentage ? fmtPct : fmtInt ;
7679
7780 return (
7881 < div
@@ -108,7 +111,6 @@ function NonConvertedApplicationsGraph({ data = [], usePercentage = true, isDark
108111 : 'Applications'
109112 }
110113 position = "bottom"
111- offset = { 0 }
112114 fill = { isDark ? '#e2e8f0' : '#374151' }
113115 />
114116 </ XAxis >
@@ -117,15 +119,14 @@ function NonConvertedApplicationsGraph({ data = [], usePercentage = true, isDark
117119 type = "category"
118120 dataKey = "title"
119121 width = { 180 }
120- tickFormatter = { ( v ) => truncate ( v ) }
122+ tickFormatter = { ( v ) => v }
121123 tick = { { fill : isDark ? '#e2e8f0' : '#374151' , fontSize : 12 } }
122124 stroke = { isDark ? '#e2e8f0' : '#374151' }
123125 >
124126 < Label
125127 value = "Job Role"
126128 angle = { - 90 }
127129 position = "left"
128- offset = { - 5 }
129130 fill = { isDark ? '#e2e8f0' : '#374151' }
130131 />
131132 </ YAxis >
@@ -136,7 +137,7 @@ function NonConvertedApplicationsGraph({ data = [], usePercentage = true, isDark
136137 < LabelList
137138 dataKey = { metricKey }
138139 position = "right"
139- formatter = { labelFormatter }
140+ formatter = { xTickFormatter }
140141 style = { {
141142 fill : isDark ? '#FFFFFF' : '#374151' ,
142143 fontWeight : 600 ,
@@ -150,4 +151,17 @@ function NonConvertedApplicationsGraph({ data = [], usePercentage = true, isDark
150151 ) ;
151152}
152153
154+ NonConvertedApplicationsGraph . propTypes = {
155+ data : PropTypes . arrayOf (
156+ PropTypes . shape ( {
157+ title : PropTypes . string ,
158+ hits : PropTypes . number ,
159+ applications : PropTypes . number ,
160+ conversionRate : PropTypes . number ,
161+ } )
162+ ) ,
163+ usePercentage : PropTypes . bool . isRequired ,
164+ isDark : PropTypes . bool . isRequired ,
165+ } ;
166+
153167export default NonConvertedApplicationsGraph ;
0 commit comments