@@ -44,6 +44,7 @@ const ROLE_OPTIONS = [
4444 'Product Manager' ,
4545 'UX Designer' ,
4646] ;
47+
4748// ======================== CONFIG ========================
4849const CONFIG = {
4950 API : {
@@ -93,7 +94,6 @@ function useMediaQuery(query) {
9394 const m = window . matchMedia ( query ) ;
9495 const listener = ( ) => setMatches ( m . matches ) ;
9596 m . addEventListener ?. ( 'change' , listener ) ;
96- // fallback for older
9797 m . addListener ?. ( listener ) ;
9898 return ( ) => {
9999 m . removeEventListener ?. ( 'change' , listener ) ;
@@ -103,7 +103,7 @@ function useMediaQuery(query) {
103103 return matches ;
104104}
105105
106- // ======================== API SERVICE (Mock + Secure RNG) ========================
106+ // ======================== API SERVICE ========================
107107class AnalyticsService {
108108 static getAuthToken ( ) {
109109 return localStorage . getItem ( 'token' ) || '' ;
@@ -115,11 +115,6 @@ class AnalyticsService {
115115
116116 static async fetchData ( dateRange , comparisonPeriod , role ) {
117117 try {
118- // TODO: Replace with real API when ready
119- // const response = await fetch(`${CONFIG.API.ENDPOINTS.ANALYTICS}`, { ... });
120- // if (!response.ok) throw new Error('Failed to fetch analytics data');
121- // return await response.json();
122-
123118 await this . simulateApiDelay ( ) ;
124119 return this . generateMockAnalyticsData ( dateRange , comparisonPeriod , role ) ;
125120 } catch ( err ) {
@@ -129,7 +124,7 @@ class AnalyticsService {
129124 }
130125 }
131126
132- // ✅ Secure pseudo-random helper for UI demo analytics data only.
127+ // Secure pseudo-random helper for UI demo analytics data only.
133128 // NOTE: Not used for authentication, cryptography, or access control.
134129 static secureRandom ( min , max ) {
135130 const array = new Uint32Array ( 1 ) ;
@@ -169,8 +164,6 @@ class AnalyticsService {
169164 data . push ( {
170165 date : date . toISOString ( ) . split ( 'T' ) [ 0 ] ,
171166 displayDate : date . toLocaleDateString ( 'en-US' , CONFIG . DATE_FORMAT . display ) ,
172-
173- // ✅ Secure dummy simulation values:
174167 users : this . secureRandom ( 700 + offset + roleOffset , 1000 + offset + roleOffset ) ,
175168 pageViews : this . secureRandom (
176169 4000 + offset * 5 + roleOffset * 10 ,
@@ -201,11 +194,11 @@ class AnalyticsService {
201194 previousPeriod : genSeries ( start , end , 0 ) ,
202195 metrics : {
203196 current : {
204- totalUsers : 23456 + ( role === 'All Roles' ? 0 : this . secureRandom ( 1000 , 3000 ) ) , // Simulate role-based user count
205- totalPageViews : 145678 + ( role === 'All Roles' ? 0 : this . secureRandom ( 5000 , 15000 ) ) , // Simulate role-based page views
206- totalSessions : 18934 + ( role === 'All Roles' ? 0 : this . secureRandom ( 800 , 2000 ) ) , // Simulate role-based sessions
207- avgBounceRate : 42.3 + ( role === 'All Roles' ? 0 : this . secureRandom ( - 5 , 5 ) ) , // Simulate role-based bounce rate
208- avgSessionDuration : 245 + ( role === 'All Roles' ? 0 : this . secureRandom ( - 30 , 30 ) ) , // Simulate role-based session duration
197+ totalUsers : 23456 + ( role === 'All Roles' ? 0 : this . secureRandom ( 1000 , 3000 ) ) ,
198+ totalPageViews : 145678 + ( role === 'All Roles' ? 0 : this . secureRandom ( 5000 , 15000 ) ) ,
199+ totalSessions : 18934 + ( role === 'All Roles' ? 0 : this . secureRandom ( 800 , 2000 ) ) ,
200+ avgBounceRate : 42.3 + ( role === 'All Roles' ? 0 : this . secureRandom ( - 5 , 5 ) ) ,
201+ avgSessionDuration : 245 + ( role === 'All Roles' ? 0 : this . secureRandom ( - 30 , 30 ) ) ,
209202 } ,
210203 previous : {
211204 totalUsers : 21234 + ( role === 'All Roles' ? 0 : this . secureRandom ( 1000 , 3000 ) ) ,
@@ -249,10 +242,16 @@ function useAnalyticsData(dateRange, comparisonPeriod, selectedRole) {
249242 setLoading ( false ) ;
250243 }
251244 } , [ dateRange , comparisonPeriod , selectedRole ] ) ;
245+
246+ // FIX: trigger fetch whenever dependencies (dateRange, role, period) change
247+ useEffect ( ( ) => {
248+ fetchData ( ) ;
249+ } , [ fetchData ] ) ;
250+
252251 return { data, loading, error, refetch : fetchData } ;
253252}
254253
255- // ======================== SMALL REUSABLE UIs (Module CSS) ========================
254+ // ======================== SMALL REUSABLE UIs ========================
256255function LoadingSpinner ( { message = 'Loading...' } ) {
257256 return (
258257 < div className = { styles . loading } >
@@ -370,10 +369,10 @@ const DATE_RANGE_PRESETS = {
370369
371370function DateRangeSelector ( { dateRange, setDateRange, comparisonPeriod, setComparisonPeriod } ) {
372371 const [ active , setActive ] = useState ( 'last30Days' ) ;
372+
373373 useEffect ( ( ) => {
374374 if ( ! dateRange ) {
375- const preset = DATE_RANGE_PRESETS . last30Days . getValue ( ) ;
376- setDateRange ( preset ) ;
375+ setDateRange ( DATE_RANGE_PRESETS . last30Days . getValue ( ) ) ;
377376 }
378377 } , [ dateRange , setDateRange ] ) ;
379378
@@ -442,11 +441,6 @@ function DateRangeSelector({ dateRange, setDateRange, comparisonPeriod, setCompa
442441
443442// ======================== MAIN ========================
444443function JobAnalytics ( { darkMode, role, hasPermission : hasPerm } ) {
445- // Theme attribute for global CSS
446- useEffect ( ( ) => {
447- document . documentElement . setAttribute ( 'data-theme' , darkMode ? 'dark' : 'light' ) ;
448- } , [ darkMode ] ) ;
449-
450444 // Permission check (uncomment when backend is ready)
451445 // const canViewAnalytics = hasPerm('getJobReports');
452446 // if (!canViewAnalytics) return <AccessDenied />;
@@ -456,17 +450,13 @@ function JobAnalytics({ darkMode, role, hasPermission: hasPerm }) {
456450 const [ dateRange , setDateRange ] = useState ( null ) ;
457451 const [ comparisonPeriod , setComparisonPeriod ] = useState ( 'previous-month' ) ;
458452 const [ selectedRole , setSelectedRole ] = useState ( ROLE_OPTIONS [ 0 ] ) ;
453+
459454 const { data : analyticsData , loading, error, refetch } = useAnalyticsData (
460455 dateRange ,
461456 comparisonPeriod ,
462457 selectedRole ,
463458 ) ;
464459
465- useEffect ( ( ) => {
466- // Refetch data when role changes
467- refetch ( ) ;
468- } , [ selectedRole , refetch ] ) ;
469-
470460 const mergedData = useMemo ( ( ) => {
471461 if ( ! analyticsData ?. currentPeriod || ! analyticsData ?. previousPeriod ) return [ ] ;
472462 return analyticsData . currentPeriod . map ( ( d , i ) => ( {
@@ -503,47 +493,68 @@ function JobAnalytics({ darkMode, role, hasPermission: hasPerm }) {
503493 const multiplier =
504494 selectedRole === 'All Roles' ? 1 : 1 + ROLE_OPTIONS . indexOf ( selectedRole ) * 0.05 ;
505495 const dateFactor = dateRange ? 1 + AnalyticsService . secureRandom ( 0 , 10 ) / 100 : 1 ;
506-
507496 return analyticsData . deviceBreakdown . map ( d => ( {
508497 ...d ,
509498 value : Math . round ( d . value * multiplier * dateFactor ) ,
510499 previousValue : Math . round ( d . previousValue * multiplier * dateFactor ) ,
511500 sessions : Math . round ( d . sessions * multiplier * dateFactor ) ,
512501 } ) ) ;
513- } , [ analyticsData , selectedRole , dateRange , comparisonPeriod ] ) ;
502+ } , [ analyticsData , selectedRole , dateRange ] ) ;
514503
515504 const filteredTrafficSources = useMemo ( ( ) => {
516505 if ( ! analyticsData ?. trafficSources ) return [ ] ;
517506 const multiplier =
518507 selectedRole === 'All Roles' ? 1 : 1 + ROLE_OPTIONS . indexOf ( selectedRole ) * 0.05 ;
519508 const dateFactor = dateRange ? 1 + AnalyticsService . secureRandom ( 0 , 10 ) / 100 : 1 ;
520-
521509 return analyticsData . trafficSources . map ( t => ( {
522510 ...t ,
523511 current : Math . round ( t . current * multiplier * dateFactor ) ,
524512 previous : Math . round ( t . previous * multiplier * dateFactor ) ,
525513 } ) ) ;
526- } , [ analyticsData , selectedRole , dateRange , comparisonPeriod ] ) ;
514+ } , [ analyticsData , selectedRole , dateRange ] ) ;
527515
528516 const handleResetAndRefresh = ( ) => {
529- const last30 = DATE_RANGE_PRESETS . last30Days . getValue ( ) ;
530-
531517 setSelectedRole ( 'All Roles' ) ;
532- setDateRange ( last30 ) ;
518+ setDateRange ( DATE_RANGE_PRESETS . last30Days . getValue ( ) ) ;
533519 setComparisonPeriod ( 'previous-month' ) ;
534-
535520 refetch ( ) ;
536521 } ;
537522
523+ // Chart colors driven by darkMode prop — already correct
538524 const colors = darkMode ? CONFIG . CHART_COLORS . dark : CONFIG . CHART_COLORS ;
539525
526+ // Recharts injects inline styles into its tooltip, so CSS classes have no effect.
527+ // Pass these props to every <Tooltip> so it respects dark mode.
528+ const tooltipStyle = darkMode
529+ ? {
530+ contentStyle : {
531+ background : '#1f2937' ,
532+ border : '1px solid #374151' ,
533+ borderRadius : 8 ,
534+ color : '#f9fafb' ,
535+ } ,
536+ labelStyle : { color : '#9ca3af' } ,
537+ itemStyle : { color : '#f9fafb' } ,
538+ }
539+ : {
540+ contentStyle : {
541+ background : '#fff' ,
542+ border : '1px solid #e5e7eb' ,
543+ borderRadius : 8 ,
544+ color : '#111827' ,
545+ } ,
546+ labelStyle : { color : '#6b7280' } ,
547+ itemStyle : { color : '#111827' } ,
548+ } ;
549+
540550 return (
541- < div className = { styles . page } >
551+ // FIX: darkMode applied as a CSS class on the root div (same pattern as HoursPledgedChart).
552+ // Removed the document.documentElement side-effect — that bleeds into other pages.
553+ < div className = { `${ styles . page } ${ darkMode ? styles . darkMode : '' } ` } >
542554 < header className = { styles . header } >
543555 < h2 className = { styles . title } > Job Analytics</ h2 >
544556
545557 < div className = { styles . headerActions } >
546- { /* Role Dropdown */ }
547558 < select
548559 className = { `${ styles . input } ${ styles . select } ` }
549560 value = { selectedRole }
@@ -558,7 +569,6 @@ function JobAnalytics({ darkMode, role, hasPermission: hasPerm }) {
558569 ) ) }
559570 </ select >
560571
561- { /* Refresh Button */ }
562572 < button
563573 className = { `${ styles . btn } ${ styles . btnPrimary } ` }
564574 onClick = { handleResetAndRefresh }
@@ -584,7 +594,6 @@ function JobAnalytics({ darkMode, role, hasPermission: hasPerm }) {
584594 < LoadingSpinner message = "Loading analytics data..." />
585595 ) : (
586596 < >
587- { /* Metrics */ }
588597 < section className = { styles . metricsGrid } >
589598 < MetricCard
590599 icon = { Users }
@@ -612,15 +621,14 @@ function JobAnalytics({ darkMode, role, hasPermission: hasPerm }) {
612621 />
613622 </ section >
614623
615- { /* Charts */ }
616624 < section className = { styles . chartsGrid } data-mobile = { isMobile ? '1' : '0' } >
617625 < ChartCard title = "User Trend Comparison" icon = { TrendingUp } >
618626 < ResponsiveContainer width = "100%" height = { 320 } >
619627 < LineChart data = { mergedData } margin = { { top : 10 , right : 10 , left : 0 , bottom : 10 } } >
620628 < CartesianGrid strokeDasharray = "3 3" className = { styles . gridStroke } />
621629 < XAxis dataKey = "displayDate" tick = { { fontSize : 12 } } />
622630 < YAxis tick = { { fontSize : 12 } } domain = { [ 'dataMin - 100' , 'dataMax + 100' ] } />
623- < Tooltip />
631+ < Tooltip { ... tooltipStyle } />
624632 < Legend />
625633 < Line
626634 type = "monotone"
@@ -659,7 +667,7 @@ function JobAnalytics({ darkMode, role, hasPermission: hasPerm }) {
659667 < CartesianGrid strokeDasharray = "3 3" className = { styles . gridStroke } />
660668 < XAxis dataKey = "displayDate" tick = { { fontSize : 12 } } />
661669 < YAxis tick = { { fontSize : 12 } } domain = { [ 'dataMin - 500' , 'dataMax + 500' ] } />
662- < Tooltip />
670+ < Tooltip { ... tooltipStyle } />
663671 < Legend />
664672 < Area
665673 type = "monotone"
@@ -688,7 +696,7 @@ function JobAnalytics({ darkMode, role, hasPermission: hasPerm }) {
688696 height = { 60 }
689697 />
690698 < YAxis tick = { { fontSize : 12 } } domain = { [ 0 , 'dataMax + 500' ] } />
691- < Tooltip />
699+ < Tooltip { ... tooltipStyle } />
692700 < Legend />
693701 < Bar
694702 dataKey = "current"
@@ -728,11 +736,11 @@ function JobAnalytics({ darkMode, role, hasPermission: hasPerm }) {
728736 />
729737 ) ) }
730738 </ Pie >
731- < Tooltip />
739+ < Tooltip { ... tooltipStyle } />
732740 </ PieChart >
733741 </ ResponsiveContainer >
734742 </ ChartCard >
735- { /* Sessions & Bounce Rate Combined Chart */ }
743+
736744 < ChartCard
737745 title = "Sessions & Bounce Rate Analysis"
738746 icon = { Activity }
@@ -756,7 +764,7 @@ function JobAnalytics({ darkMode, role, hasPermission: hasPerm }) {
756764 tick = { { fontSize : 12 } }
757765 domain = { [ 0 , 100 ] }
758766 />
759- < Tooltip />
767+ < Tooltip { ... tooltipStyle } />
760768 < Legend />
761769 < Line
762770 yAxisId = "left"
0 commit comments