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
159 changes: 159 additions & 0 deletions src/components/CommunityPortal/Reports/Participation/ChartsSection.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { useSelector } from 'react-redux';
import {
BarChart,
Bar,
XAxis,
YAxis,
Tooltip,
ResponsiveContainer,
LineChart,
Line,
PieChart,
Pie,
Cell,
Legend,
} from 'recharts';

import mockEvents from './mockData';
import styles from './ChartsSection.module.css';

function ChartsSection() {
const darkMode = useSelector(state => state.theme.darkMode);

// Group data by event type
const eventTypeStats = [];
const groups = {};

mockEvents.forEach(evt => {
const key = evt.eventType;

if (!groups[key]) {
groups[key] = { count: 0, noShowSum: 0, dropSum: 0 };
}

groups[key].count++;
groups[key].noShowSum += Number.parseInt(evt.noShowRate, 10);
groups[key].dropSum += Number.parseInt(evt.dropOffRate, 10);
});

Object.entries(groups).forEach(([key, stats]) => {
eventTypeStats.push({
eventType: key,
avgNoShow: Math.round(stats.noShowSum / stats.count),
avgDrop: Math.round(stats.dropSum / stats.count),
});
});

// Monthly trend
const monthlyTrend = {};

mockEvents.forEach(evt => {
const m = new Date(evt.eventDate).getMonth();

if (!monthlyTrend[m]) {
monthlyTrend[m] = { count: 0, noShowSum: 0 };
}

monthlyTrend[m].count++;
monthlyTrend[m].noShowSum += Number.parseInt(evt.noShowRate, 10);
});

const trendData = Object.keys(monthlyTrend).map(m => ({
month: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][m],
avgNoShow: Math.round(monthlyTrend[m].noShowSum / monthlyTrend[m].count),
}));

// Location distribution
const locationGroups = {};

mockEvents.forEach(evt => {
const loc = evt.location;
if (!locationGroups[loc]) locationGroups[loc] = 0;
locationGroups[loc]++;
});

const locationData = Object.keys(locationGroups).map(loc => ({
name: loc,
value: locationGroups[loc],
}));

const pieColors = ['#007bff', '#00b894', '#e17055', '#6c5ce7', '#fdcb6e'];

// Dark-mode-aware tooltip styling so tooltips stay readable in both themes
const tooltipProps = {
contentStyle: {
backgroundColor: darkMode ? '#2c2f33' : '#fff',
border: `1px solid ${darkMode ? '#555' : '#ccc'}`,
borderRadius: 4,
},
labelStyle: { color: darkMode ? '#fff' : '#333' },
itemStyle: { color: darkMode ? '#fff' : '#333' },
cursor: { fill: darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)' },
};

return (
<div className={`${styles.chartsSection} ${darkMode ? styles.chartsSectionDark : ''}`}>
<h3 className={styles.sectionTitle}>Comparative Charts</h3>

{/* Row 1 — Bar Charts */}
<div className={styles.row}>
{/* No-Show Chart */}
<div className={styles.chartBox}>
<h4>No-show rate by event type</h4>
<ResponsiveContainer width="100%" height={250}>
<BarChart data={eventTypeStats}>
<XAxis dataKey="eventType" stroke={darkMode ? '#fff' : '#333'} />
<YAxis stroke={darkMode ? '#fff' : '#333'} />
<Tooltip {...tooltipProps} />
<Bar dataKey="avgNoShow" fill="#FF6B6B" />
</BarChart>
</ResponsiveContainer>
</div>

{/* Drop-Off Chart */}
<div className={styles.chartBox}>
<h4>Drop-off rate by event type</h4>
<ResponsiveContainer width="100%" height={250}>
<BarChart data={eventTypeStats}>
<XAxis dataKey="eventType" stroke={darkMode ? '#fff' : '#333'} />
<YAxis stroke={darkMode ? '#fff' : '#333'} />
<Tooltip {...tooltipProps} />
<Bar dataKey="avgDrop" fill="#4C89FF" />
</BarChart>
</ResponsiveContainer>
</div>
</div>

{/* Row 2 — Line Chart */}
<div className={styles.chartBoxFull}>
<h5>Monthly no-show trend</h5>
<ResponsiveContainer width="100%" height={280}>
<LineChart data={trendData}>
<XAxis dataKey="month" stroke={darkMode ? '#fff' : '#333'} />
<YAxis stroke={darkMode ? '#fff' : '#333'} />
<Tooltip {...tooltipProps} />
<Line type="monotone" dataKey="avgNoShow" stroke="#FF6B6B" strokeWidth={2} />
</LineChart>
</ResponsiveContainer>
</div>

{/* Row 3 — Pie Chart */}
<div className={styles.chartBoxFull}>
<h5>Participation by location</h5>
<ResponsiveContainer width="100%" height={300}>
<PieChart>
<Pie data={locationData} dataKey="value" nameKey="name" outerRadius={110} label>
{locationData.map((entry, index) => (
<Cell key={entry.name} fill={pieColors[index % pieColors.length]} />
))}
</Pie>
<Tooltip {...tooltipProps} />
<Legend />
</PieChart>
</ResponsiveContainer>
</div>
</div>
);
}

export default ChartsSection;
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
.chartsSection {
margin-top: 30px;
background: #fff;
padding: 25px;
border-radius: 10px;
box-shadow: 0 2px 8px rgb(0 0 0 / 8%);
}

.chartsSectionDark {
background: #1c2541;
color: #fff;
border: 1px solid #333;
}

.sectionTitle {
font-size: 1.3rem;
font-weight: 600;
margin-bottom: 20px;
color: #333;
}

.row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}

.chartBox {
background: #f8f9fa;
padding: 15px;
border-radius: 10px;
border: 1px solid #ddd;
}

.chartsSectionDark .chartBox {
background: #3a506b;
border: 1px solid #555;
}

.chartBox h4 {
margin-bottom: 10px;
font-size: 1rem;
font-weight: 600;
}

.chartBoxFull {
margin-top: 20px;
background: #f8f9fa;
padding: 15px;
border-radius: 10px;
border: 1px solid #ddd;
}

.chartsSectionDark .chartBoxFull {
background: #3a506b;
border: 1px solid #555;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faArrowLeft } from '@fortawesome/free-solid-svg-icons';
import styles from './Participation.module.css';

function Demographics() {
const darkMode = useSelector(state => state.theme.darkMode);
const history = useHistory();

return (
<div className={`${styles.demographicsPage} ${darkMode ? styles.demographicsPageDark : ''}`}>
<button
type="button"
className={`${styles.backBtn} ${darkMode ? styles.backBtnDark : ''}`}
onClick={() => history.goBack()}
>
<FontAwesomeIcon icon={faArrowLeft} />
Back
</button>

<h2
className={`${styles.demographicsHeader} ${darkMode ? styles.demographicsHeaderDark : ''}`}
>
Demographics Overview
</h2>

<div className={styles.demographicsContent}>
<div className={styles.placeholderBox}>
<p>Charts and breakdowns for age, gender, and location demographics will appear here.</p>
</div>
</div>
</div>
);
}

export default Demographics;
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
/* eslint-disable testing-library/no-node-access */
import { useSelector } from 'react-redux';
import { useRef, useState, useCallback } from 'react';
import { useHistory } from 'react-router-dom';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faFilePdf } from '@fortawesome/free-solid-svg-icons';
import MyCases from './MyCases';
import DropOffTracking from './DropOffTracking';
import NoShowInsights from './NoShowInsights';
import styles from './Participation.module.css';
import ChartsSection from './ChartsSection';

function EventParticipation() {
const darkMode = useSelector(state => state.theme.darkMode);
const history = useHistory();
const exportRef = useRef(null);
const [exporting, setExporting] = useState(false);
const [selectedOrganizer, setSelectedOrganizer] = useState('All Organizers');
Expand All @@ -23,22 +26,22 @@ function EventParticipation() {

// Expand "More" so all visible items are included
const moreBtn = document.querySelector('.more-btn-global');
const toggled = moreBtn?.textContent?.toLowerCase().includes('more') ?? false;
if (toggled) moreBtn.click();
const shouldExpand = moreBtn?.textContent?.toLowerCase().includes('more');
if (shouldExpand) moreBtn.click();

const prevTitle = document.title;
document.title = 'event_participation';

setTimeout(() => {
globalThis.print();
globalThis.window.print();

setTimeout(() => {
if (toggled) moreBtn.click();
if (shouldExpand) moreBtn.click();

delete document.documentElement.dataset.exporting;
document.title = prevTitle;
setExporting(false);
}, 100);
}, 120);
}, 500);
}, [exporting]);

Expand Down Expand Up @@ -92,13 +95,63 @@ function EventParticipation() {
</div>
</header>

<div className={styles.subPageNav}>
<button
className={`${styles.subPageBtn} ${darkMode ? styles.subPageBtnDark : ''}`}
onClick={() => history.push('/communityportal/reports/participation/demographics')}
>
Demographics
</button>
<button
className={`${styles.subPageBtn} ${darkMode ? styles.subPageBtnDark : ''}`}
onClick={() => history.push('/communityportal/reports/participation/personalization')}
>
Personalization
</button>
</div>

<MyCases />
<div className={`${styles.analyticsSection}`}>
<DropOffTracking />
<NoShowInsights />
</div>
<ChartsSection />

{/* ACTIONABLE INSIGHTS SECTION */}
<div
className={`${styles.actionableSection} ${darkMode ? styles.actionableSectionDark : ''}`}
>
<h3 className={styles.actionableHeader}>Actionable insights</h3>

<div className={styles.actionableGrid}>
<div className={styles.actionCard}>
<h4 className={styles.actionTitle}>High no-show rate detected</h4>
<p className={styles.actionDescription}>
Yoga Class events show an unusual increase in no-show percentage this month.
</p>
<span className={styles.actionTrendUp}>↑ 12%</span>
</div>

<div className={styles.actionCard}>
<h4 className={styles.actionTitle}>Weekend events perform better</h4>
<p className={styles.actionDescription}>
Attendance is consistently higher on Saturdays compared to weekdays.
</p>
<span className={styles.actionTrendUp}>↑ 8%</span>
</div>

{/* Print-only footer note */}
<div className={`${styles.printOnly} ${styles.printFooter}`}>
Generated from Event Participation
<div className={styles.actionCard}>
<h4 className={styles.actionTitle}>Drop-off rate reduction opportunity</h4>
<p className={styles.actionDescription}>
Average event drop-off decreases when host reminders are sent earlier.
</p>
<span className={styles.actionTrendDown}>↓ 5%</span>
</div>
</div>
</div>
</div>
</div>
);
}
Expand Down
Loading
Loading