diff --git a/src/components/TotalOrgSummary/PieChart.jsx b/src/components/TotalOrgSummary/PieChart.jsx index a55b0f6e38..495033f954 100644 --- a/src/components/TotalOrgSummary/PieChart.jsx +++ b/src/components/TotalOrgSummary/PieChart.jsx @@ -1,44 +1,52 @@ -// // import * as React from 'react'; -// import { DefaultizedPieValueType } from '@mui/x-charts/models'; -// import { PieChart, pieArcLabelClasses } from '@mui/x-charts/PieChart'; +import * as React from 'react'; +import { PieChart, pieArcLabelClasses } from '@mui/x-charts/PieChart'; -// const data = [ -// { label: 'Group A', value: 400, color: '#0088FE' }, -// { label: 'Group B', value: 300, color: '#00C49F' }, -// { label: 'Group C', value: 300, color: '#FFBB28' }, -// { label: 'Group D', value: 200, color: '#FF8042' }, -// ]; +const data = [ + { id: 0, label: 'Administrator', value: 6, color: '#fb0505' }, + { id: 1, label: 'Volunteer', value: 4, color: '#8ebfff' }, + { id: 2, label: 'Owner', value: 3, color: '#f68d42' }, + { id: 3, label: 'Mentor', value: 1, color: '#f2ff00' }, +]; -// const sizing = { -// margin: { right: 5 }, -// width: 200, -// height: 200, -// legend: { hidden: true }, -// }; -// const TOTAL = data.map(item => item.value).reduce((a, b) => a + b, 0); +const TOTAL = data.reduce((sum, item) => sum + item.value, 0); -// const getArcLabel = (params: DefaultizedPieValueType) => { -// const percent = params.value / TOTAL; -// return `${(percent * 100).toFixed(0)}%`; -// }; +const getArcLabel = params => { + const percent = params.value / TOTAL; + return `${params.value}\n(${(percent * 100).toFixed(0)}%)`; +}; -// export default function PieChartWithCustomizedLabel() { -// return ( -// -// ); -// } +export default function RoleDistributionPieChart() { + return ( + + ); +} diff --git a/src/components/TotalOrgSummary/TotalOrgSummary.module.css b/src/components/TotalOrgSummary/TotalOrgSummary.module.css index 7e39bd65bd..f83cce2b7b 100644 --- a/src/components/TotalOrgSummary/TotalOrgSummary.module.css +++ b/src/components/TotalOrgSummary/TotalOrgSummary.module.css @@ -34,7 +34,7 @@ padding-top: 10px !important; padding-bottom: 10px !important; } - + .containerTotalOrgWrapper:global(.mb-5) { margin-bottom: 20px !important; } @@ -215,6 +215,29 @@ color: #fff !important; } +/* Role Distribution slice label overrides */ +.containerTotalOrgWrapper:global(.bg-oxford-blue) + .componentContainer + :global(.role-distribution-label-dark), +.containerTotalOrgWrapper:global(.bg-oxford-blue) + .componentContainer + :global(.role-distribution-label-dark) + tspan { + fill: #000 !important; + color: #000 !important; +} + +.containerTotalOrgWrapper:global(.bg-oxford-blue) + .componentContainer + :global(.role-distribution-label-light), +.containerTotalOrgWrapper:global(.bg-oxford-blue) + .componentContainer + :global(.role-distribution-label-light) + tspan { + fill: #fff !important; + color: #fff !important; +} + .containerTotalOrgWrapper.bg-oxford-blue h3 { color: #fff; } @@ -382,8 +405,6 @@ transform: translateX(4px) !important; } -/* Dark mode dropdown consistency */ - /* Component containers - Clean borderless design */ .componentContainer { margin: 0 0 15px; @@ -696,4 +717,4 @@ } } -/* stylelint-enable no-descending-specificity */ +/* stylelint-enable no-descending-specificity */ \ No newline at end of file diff --git a/src/components/TotalOrgSummary/VolunteerRolesTeamDynamics/RoleDistributionPieChart.jsx b/src/components/TotalOrgSummary/VolunteerRolesTeamDynamics/RoleDistributionPieChart.jsx index 54f126f1dd..fe95b7296b 100644 --- a/src/components/TotalOrgSummary/VolunteerRolesTeamDynamics/RoleDistributionPieChart.jsx +++ b/src/components/TotalOrgSummary/VolunteerRolesTeamDynamics/RoleDistributionPieChart.jsx @@ -1,21 +1,22 @@ +import React from 'react'; import { ResponsiveContainer, PieChart, Pie, Cell, Legend, Tooltip } from 'recharts'; import Loading from '~/components/common/Loading'; +import CustomTooltip from '../../CustomTooltip'; const COLORS = [ - '#2F80ED', // blue - '#56CCF2', // light blue - '#27AE60', // green - '#6FCF97', // light green - '#F2994A', // orange - '#F2C94C', // yellow - '#E14848', // red - '#9B51E0', // purple - '#F765A3', // pink - '#4F4F4F', // dark - '#828282', // grey + '#2F80ED', + '#56CCF2', + '#27AE60', + '#6FCF97', + '#F2994A', + '#F2C94C', + '#E14848', + '#9B51E0', + '#F765A3', + '#4F4F4F', + '#828282', ]; -// Explicit role-to-color mapping to keep key roles visually distinct and stable. const ROLE_COLOR_MAP = { Volunteer: '#8ebfff', Manager: '#27AE60', @@ -25,7 +26,19 @@ const ROLE_COLOR_MAP = { Mentor: '#f2ff00', }; -import CustomTooltip from '../../CustomTooltip'; +const RADIAN = Math.PI / 180; + +const getContrastTextColor = hexColor => { + const hex = hexColor.replace('#', ''); + + const r = parseInt(hex.substring(0, 2), 16); + const g = parseInt(hex.substring(2, 4), 16); + const b = parseInt(hex.substring(4, 6), 16); + + const brightness = (r * 299 + g * 587 + b * 114) / 1000; + + return brightness > 160 ? '#000000' : '#FFFFFF'; +}; const RoleDistributionPieChart = ({ roleDistributionStats = [], isLoading, darkMode }) => { if (isLoading) { @@ -38,111 +51,153 @@ const RoleDistributionPieChart = ({ roleDistributionStats = [], isLoading, darkM ); } - roleDistributionStats.sort((a, b) => b.count - a.count); - const data = roleDistributionStats.map((item, index) => ({ + const sortedStats = [...roleDistributionStats].sort((a, b) => b.count - a.count); + + const data = sortedStats.map((item, index) => ({ name: item._id, value: item.count, - // Use a stable role mapping first, otherwise fallback by index. color: ROLE_COLOR_MAP[item._id] || COLORS[index % COLORS.length], })); + const totalValue = data.reduce((sum, entry) => sum + entry.value, 0); - const RADIAN = Math.PI / 180; - const renderCustomizedLabel = ({ - cx, - cy, - midAngle, - innerRadius, - outerRadius, - percent, - index, - }) => { - const radius = innerRadius + (outerRadius - innerRadius) * 0.5; + const renderLabel = ({ cx, cy, midAngle, innerRadius, outerRadius, percent, index }) => { + if (percent <= 0.01 || !data[index]) return null; + + const slice = data[index]; + + const isSmallSlice = percent < 0.1; + + const labelColor = getContrastTextColor(slice.color); + + const labelClass = + labelColor === '#000000' ? 'role-distribution-label-dark' : 'role-distribution-label-light'; + + const radius = + innerRadius + (outerRadius - innerRadius) * (slice.name === 'Mentor' ? 0.34 : 0.52); + const x = cx + radius * Math.cos(-midAngle * RADIAN); + const y = cy + radius * Math.sin(-midAngle * RADIAN); - return ( - - - {percent > 0.01 && ( - <> - - {`${data[index].value}`} - - - {`(${(percent * 100).toFixed(0)}%)`} - - - )} - - - - TOTAL ROLES - - - {data.length} - + if (isSmallSlice) { + return ( + + {`${slice.value} (${(percent * 100).toFixed(0)}%)`} - - ); - }; - - const renderCustomLegend = props => { - const { payload } = props; // payload is an array of legend items provided by Recharts + ); + } return ( - + + {slice.value} + + + + {`(${(percent * 100).toFixed(0)}%)`} + + ); }; + const renderCustomLegend = ({ payload }) => ( + + ); + return (
-
- +
+ {data.map(entry => ( ))} + + } />