);
}
-export default MapComponent;
+export default GlobalVolunteerMap;
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 ac34eedc74..33b9abff19 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 }) => {
// Reusable function to sort data and assign colors.
@@ -50,114 +63,153 @@ const RoleDistributionPieChart = ({ roleDistributionStats = [], isLoading, darkM
);
}
- let data = [];
- // This is to handle the case when we are comparing the data with other time periods. In that case, the data structure is different and we need to access the comparison data.
- if (roleDistributionStats?.comparison) {
- data = sortDataAndAssignColors(roleDistributionStats?.comparison);
- }
- // Fallback to the original data structure when we are not comparing the data with other time periods.
- else {
- data = sortDataAndAssignColors(roleDistributionStats);
- }
+ const sortedStats = [...roleDistributionStats].sort((a, b) => b.count - a.count);
+
+ const data = sortedStats.map((item, index) => ({
+ name: item._id,
+ value: item.count,
+ 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 (
-
- {payload.map(entry => {
- // 'entry.value' here corresponds to the 'nameKey' of the Pie (which we set to 'name')
- const itemName = entry.value;
- // Find the original data object to get the count and original color
- const itemData = data.find(d => d.name === itemName);
-
- // If for some reason data is not found, skip rendering this legend item
- if (!itemData) {
- return null;
- }
-
- const { value, color } = itemData; // 'value' is the count
- const percentage = totalValue > 0 ? (value / totalValue) * 100 : 0;
-
- return (
-