diff --git a/src/features/areaAssignments/components/OrganizerMapRenderer.tsx b/src/features/areaAssignments/components/OrganizerMapRenderer.tsx
index 659d8c05c..dc9d7d16b 100644
--- a/src/features/areaAssignments/components/OrganizerMapRenderer.tsx
+++ b/src/features/areaAssignments/components/OrganizerMapRenderer.tsx
@@ -22,6 +22,7 @@ import { ZetkinArea } from 'features/areas/types';
import { assigneesFilterContext } from './OrganizerMapFilters/AssigneeFilterContext';
import isPointInsidePolygon from '../../canvass/utils/isPointInsidePolygon';
import { getBoundSize } from '../../canvass/utils/getBoundSize';
+import { ZetkinPerson } from '../../../utils/types/zetkin';
const LocationMarker: FC<{
areaAssId: string;
@@ -169,6 +170,158 @@ type OrganizerMapRendererProps = {
sessions: ZetkinAreaAssignmentSession[];
};
+function HouseholdOverlayMarker(props: {
+ numberOfHouseholds: number;
+ numberOfLocations: number;
+}) {
+ return (
+
+ {props.numberOfLocations}
+
+ {props.numberOfHouseholds}
+
+ );
+}
+
+function ProgressOverlayMarker(props: {
+ successfulVisitsColorPercent: number;
+ visitsColorPercent: number;
+}) {
+ const theme = useTheme();
+
+ return (
+
+
+
+ );
+}
+
+function NumberOverlayMarker(props: { value: number }) {
+ const theme = useTheme();
+
+ return (
+
+ {props.value}
+
+ );
+}
+
+function AssigneeOverlayMarker({
+ organizationID,
+ people,
+ zoom,
+}: {
+ organizationID: number;
+ people: ZetkinPerson[];
+ zoom: number;
+}) {
+ return (
+ = 16 ? '95px' : '65px'}
+ >
+ {people.map((person, index) => {
+ if (index <= 4) {
+ return (
+
+ = 16 ? 'sm' : 'xs'}
+ url={`/api/orgs/${organizationID}/people/${person.id}/avatar`}
+ />
+
+ );
+ } else if (index == 5) {
+ return (
+ = 16 ? '30px' : '20px'}
+ justifyContent="center"
+ padding={1}
+ sx={{ boxShadow: '0 0 8px rgba(0,0,0,0.3)' }}
+ width={zoom >= 16 ? '30px' : '20px'}
+ >
+ = 16 ? 14 : 11}
+ >{`+${people.length - 5}`}
+
+ );
+ } else {
+ return null;
+ }
+ })}
+
+ );
+}
+
const OrganizerMapRenderer: FC = ({
areas,
areaStats,
@@ -285,6 +438,28 @@ const OrganizerMapRenderer: FC = ({
}
});
+ const filteredAreas = areas
+ .map((area) => {
+ const people = sessions
+ .filter((session) => session.area.id == area.id)
+ .map((session) => session.assignee);
+ const hasPeople = !!people.length;
+ return { ...area, hasPeople };
+ })
+ .filter((area) => {
+ // Right now there is only one kind of filter
+ if (assigneesFilter === null) {
+ return true;
+ }
+
+ if (area.hasPeople && assigneesFilter == 'unassigned') {
+ return false;
+ } else if (!area.hasPeople && assigneesFilter == 'assigned') {
+ return false;
+ }
+ return true;
+ });
+
return (
<>
@@ -297,7 +472,7 @@ const OrganizerMapRenderer: FC = ({
reactFGref.current = fgRef;
}}
>
- {areas
+ {filteredAreas
.sort((a0, a1) => {
// Always render selected last, so that it gets
// rendered on top of the unselected ones in case
@@ -316,46 +491,13 @@ const OrganizerMapRenderer: FC = ({
.map((area) => {
const selected = selectedId == area.id;
- const mid: [number, number] = [0, 0];
- if (area.points.length) {
- area.points
- .map((input) => {
- if ('lat' in input && 'lng' in input) {
- return [input.lat as number, input.lng as number];
- } else {
- return input;
- }
- })
- .forEach((point) => {
- mid[0] += point[0];
- mid[1] += point[1];
- });
-
- mid[0] /= area.points.length;
- mid[1] /= area.points.length;
- }
-
- const detailed = zoom >= 15;
-
- const people = sessions
- .filter((session) => session.area.id == area.id)
- .map((session) => session.assignee);
-
- const hasPeople = !!people.length;
-
- if (hasPeople && assigneesFilter == 'unassigned') {
- return null;
- } else if (!hasPeople && assigneesFilter == 'assigned') {
- return null;
- }
-
// The key changes when selected, to force redraw of polygon
// to reflect new state through visual style
const key =
area.id +
(selected ? '-selected' : '-default') +
`-${areaStyle}` +
- (hasPeople ? '-assigned' : '');
+ (area.hasPeople ? '-assigned' : '');
const stats = areaStats.stats.find(
(stat) => stat.areaId == area.id
@@ -365,8 +507,6 @@ const OrganizerMapRenderer: FC = ({
locationsByAreaId[area.id].forEach(
(location) => (numberOfHouseholds += location.households.length)
);
- const numberOfLocations = locationsByAreaId[area.id].length;
-
const householdColorPercent =
(numberOfHouseholds / highestHousholds) * 100;
@@ -374,185 +514,32 @@ const OrganizerMapRenderer: FC = ({
? (stats.num_visited_households / stats.num_households) * 100
: 0;
- const successfulVisitsColorPercent = stats?.num_households
- ? (stats.num_successful_visited_households /
- stats.num_households) *
- 100
- : 0;
-
return (
- <>
- {overlayStyle == 'households' && (
-
-
-
- {numberOfLocations}
-
-
-
- {numberOfHouseholds}
-
-
-
- )}
- {overlayStyle == 'progress' && stats && (
-
-
-
-
-
+ {
+ onSelectedIdChange(selected ? '' : area.id);
+ },
+ }}
+ fillColor={getAreaColor(
+ area.hasPeople,
+ householdColorPercent,
+ visitsColorPercent
)}
- {overlayStyle == 'assignees' && hasPeople && (
-
- {detailed && (
- = 16 ? '95px' : '65px'}
- >
- {people.map((person, index) => {
- if (index <= 4) {
- return (
-
- = 16 ? 'sm' : 'xs'}
- url={`/api/orgs/${assignment.organization.id}/people/${person.id}/avatar`}
- />
-
- );
- } else if (index == 5) {
- return (
- = 16 ? '30px' : '20px'}
- justifyContent="center"
- padding={1}
- sx={{ boxShadow: '0 0 8px rgba(0,0,0,0.3)' }}
- width={zoom >= 16 ? '30px' : '20px'}
- >
- = 16 ? 14 : 11}
- >{`+${people.length - 5}`}
-
- );
- } else {
- return null;
- }
- })}
-
- )}
- {!detailed && (
-
- {people.length}
-
- )}
-
- )}
- {
- onSelectedIdChange(selected ? '' : area.id);
- },
- }}
- fillColor={getAreaColor(
- hasPeople,
- householdColorPercent,
- visitsColorPercent
- )}
- fillOpacity={1}
- interactive={areaStyle != 'hide'}
- positions={area.points}
- weight={selected ? 5 : 2}
- />
- >
+ fillOpacity={1}
+ interactive={areaStyle != 'hide'}
+ positions={area.points}
+ weight={selected ? 5 : 2}
+ />
);
})}
- {locationStyle != 'hide' &&
- locations.map((location) => {
+
+ {locationStyle != 'hide' && (
+
+ {locations.map((location) => {
//Find ids of area/s that the location is in
const areaIds: string[] = [];
areas.forEach((area) => {
@@ -616,6 +603,99 @@ const OrganizerMapRenderer: FC = ({
/>
);
})}
+
+ )}
+
+ {filteredAreas.map((area) => {
+ const mid: [number, number] = [0, 0];
+ if (area.points.length) {
+ area.points
+ .map((input) => {
+ if ('lat' in input && 'lng' in input) {
+ return [input.lat as number, input.lng as number];
+ } else {
+ return input;
+ }
+ })
+ .forEach((point) => {
+ mid[0] += point[0];
+ mid[1] += point[1];
+ });
+
+ mid[0] /= area.points.length;
+ mid[1] /= area.points.length;
+ }
+
+ const detailed = zoom >= 15;
+
+ const people = sessions
+ .filter((session) => session.area.id == area.id)
+ .map((session) => session.assignee);
+
+ const stats = areaStats.stats.find((stat) => stat.areaId == area.id);
+
+ let numberOfHouseholds = 0;
+ locationsByAreaId[area.id].forEach(
+ (location) => (numberOfHouseholds += location.households.length)
+ );
+ const numberOfLocations = locationsByAreaId[area.id].length;
+
+ const visitsColorPercent = stats?.num_households
+ ? (stats.num_visited_households / stats.num_households) * 100
+ : 0;
+
+ const successfulVisitsColorPercent = stats?.num_households
+ ? (stats.num_successful_visited_households / stats.num_households) *
+ 100
+ : 0;
+
+ const markerToRender = () => {
+ if (overlayStyle === 'households') {
+ return (
+
+ );
+ }
+ if (overlayStyle == 'progress') {
+ return (
+
+ );
+ }
+ if (overlayStyle === 'assignees' && area.hasPeople) {
+ if (detailed) {
+ return (
+
+ );
+ }
+ return ;
+ }
+ return null;
+ };
+
+ const marker = markerToRender();
+ if (marker === null) {
+ return null;
+ }
+ return (
+
+ {marker}
+
+ );
+ })}
>
);