Skip to content

Commit 9d6b91c

Browse files
committed
implemented dark mode for jobAnalytics
1 parent 938d1f7 commit 9d6b91c

3 files changed

Lines changed: 3555 additions & 2056 deletions

File tree

src/components/JobCCDashboard/JobAnalytics/JobAnalytics.jsx

Lines changed: 55 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const ROLE_OPTIONS = [
4444
'Product Manager',
4545
'UX Designer',
4646
];
47+
4748
// ======================== CONFIG ========================
4849
const 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 ========================
107107
class 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 ========================
256255
function LoadingSpinner({ message = 'Loading...' }) {
257256
return (
258257
<div className={styles.loading}>
@@ -370,10 +369,10 @@ const DATE_RANGE_PRESETS = {
370369

371370
function 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 ========================
444443
function 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

Comments
 (0)