Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 122 additions & 36 deletions src/components/JobCCDashboard/JobAnalytics/JobAnalytics.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ import styles from './JobAnalytics.module.css';
import hasPermission from '../../../utils/permissions';
import { ENDPOINTS } from '../../../utils/URL';

const ROLE_OPTIONS = [
'All Roles',
'Frontend Developer',
'Backend Developer',
'Data Analyst',
'Product Manager',
'UX Designer',
];
// ======================== CONFIG ========================
const CONFIG = {
API: {
Expand Down Expand Up @@ -105,15 +113,15 @@ class AnalyticsService {
return new Promise(resolve => setTimeout(resolve, ms));
}

static async fetchData(dateRange, comparisonPeriod) {
static async fetchData(dateRange, comparisonPeriod, role) {
try {
// TODO: Replace with real API when ready
// const response = await fetch(`${CONFIG.API.ENDPOINTS.ANALYTICS}`, { ... });
// if (!response.ok) throw new Error('Failed to fetch analytics data');
// return await response.json();

await this.simulateApiDelay();
return this.generateMockAnalyticsData(dateRange, comparisonPeriod);
return this.generateMockAnalyticsData(dateRange, comparisonPeriod, role);
} catch (err) {
// eslint-disable-next-line no-console
console.error('Analytics fetch error:', err);
Expand All @@ -129,7 +137,7 @@ class AnalyticsService {
return min + (array[0] % (max - min + 1));
}

static generateMockAnalyticsData(dateRange, comparisonPeriod) {
static generateMockAnalyticsData(dateRange, comparisonPeriod, role = 'All Roles') {
const genSeries = (startDate, endDate, offset = 0) => {
const data = [];
const start = new Date(startDate);
Expand All @@ -138,16 +146,39 @@ class AnalyticsService {
for (let i = 0; i <= diffDays; i += 1) {
const date = new Date(start);
date.setDate(date.getDate() + i);
let roleOffset = 0;
switch (role) {
case 'Frontend Developer':
roleOffset = 50;
break;
case 'Backend Developer':
roleOffset = 30;
break;
case 'Data Analyst':
roleOffset = 20;
break;
case 'Product Manager':
roleOffset = 10;
break;
case 'UX Designer':
roleOffset = 15;
break;
default:
break;
}
data.push({
date: date.toISOString().split('T')[0],
displayDate: date.toLocaleDateString('en-US', CONFIG.DATE_FORMAT.display),

// ✅ Secure dummy simulation values:
users: this.secureRandom(700 + offset, 1000 + offset),
pageViews: this.secureRandom(4000 + offset * 5, 6000 + offset * 5),
users: this.secureRandom(700 + offset + roleOffset, 1000 + offset + roleOffset),
pageViews: this.secureRandom(
4000 + offset * 5 + roleOffset * 10,
6000 + offset * 5 + roleOffset * 10,
),
sessions: this.secureRandom(
Math.floor(600 + offset * 0.8),
Math.floor(1000 + offset * 0.8),
Math.floor(600 + offset * 0.8 + roleOffset * 0.5),
Math.floor(1000 + offset * 0.8 + roleOffset * 0.5),
),
bounceRate: this.secureRandom(35, 55),
avgDuration: this.secureRandom(180, 300),
Expand All @@ -170,18 +201,18 @@ class AnalyticsService {
previousPeriod: genSeries(start, end, 0),
metrics: {
current: {
totalUsers: 23456,
totalPageViews: 145678,
totalSessions: 18934,
avgBounceRate: 42.3,
avgSessionDuration: 245,
totalUsers: 23456 + (role === 'All Roles' ? 0 : this.secureRandom(1000, 3000)), // Simulate role-based user count
totalPageViews: 145678 + (role === 'All Roles' ? 0 : this.secureRandom(5000, 15000)), // Simulate role-based page views
totalSessions: 18934 + (role === 'All Roles' ? 0 : this.secureRandom(800, 2000)), // Simulate role-based sessions
avgBounceRate: 42.3 + (role === 'All Roles' ? 0 : this.secureRandom(-5, 5)), // Simulate role-based bounce rate
avgSessionDuration: 245 + (role === 'All Roles' ? 0 : this.secureRandom(-30, 30)), // Simulate role-based session duration
},
previous: {
totalUsers: 21234,
totalPageViews: 132456,
totalSessions: 17123,
avgBounceRate: 45.7,
avgSessionDuration: 220,
totalUsers: 21234 + (role === 'All Roles' ? 0 : this.secureRandom(1000, 3000)),
totalPageViews: 132456 + (role === 'All Roles' ? 0 : this.secureRandom(5000, 15000)),
totalSessions: 17123 + (role === 'All Roles' ? 0 : this.secureRandom(800, 2000)),
avgBounceRate: 45.7 + (role === 'All Roles' ? 0 : this.secureRandom(-5, 5)),
avgSessionDuration: 220 + (role === 'All Roles' ? 0 : this.secureRandom(-30, 30)),
},
},
deviceBreakdown: [
Expand All @@ -201,7 +232,7 @@ class AnalyticsService {
}

// ======================== HOOKS ========================
function useAnalyticsData(dateRange, comparisonPeriod) {
function useAnalyticsData(dateRange, comparisonPeriod, selectedRole) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
Expand All @@ -210,19 +241,14 @@ function useAnalyticsData(dateRange, comparisonPeriod) {
setLoading(true);
setError(null);
try {
const res = await AnalyticsService.fetchData(dateRange, comparisonPeriod);
const res = await AnalyticsService.fetchData(dateRange, comparisonPeriod, selectedRole);
setData(res);
} catch (e) {
setError(e.message || 'Failed to load analytics');
} finally {
setLoading(false);
}
}, [dateRange, comparisonPeriod]);

useEffect(() => {
fetchData();
}, [fetchData]);

}, [dateRange, comparisonPeriod, selectedRole]);
return { data, loading, error, refetch: fetchData };
}

Expand Down Expand Up @@ -428,12 +454,18 @@ function JobAnalytics({ darkMode, role, hasPermission: hasPerm }) {

const [dateRange, setDateRange] = useState(null);
const [comparisonPeriod, setComparisonPeriod] = useState('previous-month');

const [selectedRole, setSelectedRole] = useState(ROLE_OPTIONS[0]);
const { data: analyticsData, loading, error, refetch } = useAnalyticsData(
dateRange,
comparisonPeriod,
selectedRole,
);

useEffect(() => {
// Refetch data when role changes
refetch();
}, [selectedRole, refetch]);

const mergedData = useMemo(() => {
if (!analyticsData?.currentPeriod || !analyticsData?.previousPeriod) return [];
return analyticsData.currentPeriod.map((d, i) => ({
Expand Down Expand Up @@ -465,20 +497,74 @@ function JobAnalytics({ darkMode, role, hasPermission: hasPerm }) {
};
}, [analyticsData]);

const filteredDeviceBreakdown = useMemo(() => {
if (!analyticsData?.deviceBreakdown) return [];
const multiplier =
selectedRole === 'All Roles' ? 1 : 1 + ROLE_OPTIONS.indexOf(selectedRole) * 0.05;
const dateFactor = dateRange ? 1 + AnalyticsService.secureRandom(0, 10) / 100 : 1;

return analyticsData.deviceBreakdown.map(d => ({
...d,
value: Math.round(d.value * multiplier * dateFactor),
previousValue: Math.round(d.previousValue * multiplier * dateFactor),
sessions: Math.round(d.sessions * multiplier * dateFactor),
}));
}, [analyticsData, selectedRole, dateRange, comparisonPeriod]);

const filteredTrafficSources = useMemo(() => {
if (!analyticsData?.trafficSources) return [];
const multiplier =
selectedRole === 'All Roles' ? 1 : 1 + ROLE_OPTIONS.indexOf(selectedRole) * 0.05;
const dateFactor = dateRange ? 1 + AnalyticsService.secureRandom(0, 10) / 100 : 1;

return analyticsData.trafficSources.map(t => ({
...t,
current: Math.round(t.current * multiplier * dateFactor),
previous: Math.round(t.previous * multiplier * dateFactor),
}));
}, [analyticsData, selectedRole, dateRange, comparisonPeriod]);

const handleResetAndRefresh = () => {
const last30 = DATE_RANGE_PRESETS.last30Days.getValue();

setSelectedRole('All Roles');
setDateRange(last30);
setComparisonPeriod('previous-month');

refetch();
};

const colors = darkMode ? CONFIG.CHART_COLORS.dark : CONFIG.CHART_COLORS;

return (
<div className={styles.page}>
<header className={styles.header}>
<h2 className={styles.title}>Job Analytics</h2>
<button
className={`${styles.btn} ${styles.btnPrimary}`}
onClick={refetch}
disabled={loading}
>
<RefreshCw className={loading ? styles.spin : ''} size={16} />
<span>Refresh</span>
</button>

<div className={styles.headerActions}>
{/* Role Dropdown */}
<select
className={`${styles.input} ${styles.select}`}
value={selectedRole}
onChange={e => setSelectedRole(e.target.value)}
>
{ROLE_OPTIONS.map(roleOption => (
<option key={roleOption} value={roleOption}>
{roleOption}
</option>
))}
</select>

{/* Refresh Button */}
<button
className={`${styles.btn} ${styles.btnPrimary}`}
onClick={handleResetAndRefresh}
disabled={loading}
>
<RefreshCw className={loading ? styles.spin : ''} size={16} />
<span>Refresh</span>
</button>
</div>
</header>

<DateRangeSelector
Expand Down Expand Up @@ -586,7 +672,7 @@ function JobAnalytics({ darkMode, role, hasPermission: hasPerm }) {
<ChartCard title="Traffic Sources" icon={BarChart3}>
<ResponsiveContainer width="100%" height={320}>
<BarChart
data={analyticsData?.trafficSources || []}
data={filteredTrafficSources}
margin={{ top: 10, right: 10, left: 0, bottom: 10 }}
>
<CartesianGrid strokeDasharray="3 3" className={styles.gridStroke} />
Expand Down Expand Up @@ -620,7 +706,7 @@ function JobAnalytics({ darkMode, role, hasPermission: hasPerm }) {
<ResponsiveContainer width="100%" height={320}>
<PieChart>
<Pie
data={analyticsData?.deviceBreakdown || []}
data={filteredDeviceBreakdown}
cx="50%"
cy="50%"
outerRadius={110}
Expand Down
18 changes: 18 additions & 0 deletions src/components/JobCCDashboard/JobAnalytics/JobAnalytics.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -362,3 +362,21 @@
.gridStroke line {
stroke: var(--grid-stroke) !important;
}

.headerActions {
display: flex;
align-items: center;
gap: 12px;
}


.selectedJobIndicator {
max-width: 1600px;
margin: 0 auto 16px;
font-size: 0.9rem;
color: var(--muted);
}

.selectedJobIndicator strong {
color: var(--primary);
}
Loading
Loading