diff --git a/src/features/campaigns/components/ActivityList/index.tsx b/src/features/campaigns/components/ActivityList/index.tsx index 4c9338562d..3fce406340 100644 --- a/src/features/campaigns/components/ActivityList/index.tsx +++ b/src/features/campaigns/components/ActivityList/index.tsx @@ -1,7 +1,7 @@ -import { FilterListOutlined } from '@mui/icons-material'; +import { FilterListOutlined, Pending } from '@mui/icons-material'; import Fuse from 'fuse.js'; -import { useMemo } from 'react'; -import { Box, Card, Divider, Typography } from '@mui/material'; +import { useEffect, useMemo, useRef, useState } from 'react'; +import { Box, BoxProps, Card, Divider, Typography } from '@mui/material'; import CallAssignmentListItem from './items/CallAssignmentListItem'; import EmailListItem from './items/EmailListItem'; @@ -17,12 +17,61 @@ import useClusteredActivities, { CLUSTER_TYPE, } from 'features/campaigns/hooks/useClusteredActivities'; import CanvassAssignmentListItem from './items/CanvassAssignmentListItem'; +import ActivityListItem, { STATUS_COLORS } from './items/ActivityListItem'; interface ActivitiesProps { activities: CampaignActivity[]; orgId: number; } +interface LazyActivitiesBoxProps extends BoxProps { + index: number; +} + +const LazyActivitiesBox = ({ + index, + children, + ...props +}: LazyActivitiesBoxProps) => { + const [inView, setInView] = useState(false); + const boxRef = useRef(); + + useEffect(() => { + if (!boxRef.current) { + return; + } + + const observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setInView(true); + } + }); + }); + + observer.observe(boxRef.current); + }, [boxRef]); + + return inView ? ( + + {index > 0 && } + {children} + + ) : ( + + {index > 0 && } + + + ); +}; + const Activities = ({ activities, orgId }: ActivitiesProps) => { const clustered = useClusteredActivities(activities); @@ -31,52 +80,52 @@ const Activities = ({ activities, orgId }: ActivitiesProps) => { {clustered.map((activity, index) => { if (activity.kind === ACTIVITIES.CALL_ASSIGNMENT) { return ( - - {index > 0 && } + - + ); } else if (activity.kind == ACTIVITIES.CANVASS_ASSIGNMENT) { return ( - - {index > 0 && } + - + ); } else if (isEventCluster(activity)) { return ( - - {index > 0 && } + {activity.kind == CLUSTER_TYPE.SINGLE ? ( ) : ( )} - + ); } else if (activity.kind === ACTIVITIES.SURVEY) { return ( - - {index > 0 && } + - + ); } else if (activity.kind === ACTIVITIES.TASK) { return ( - - {index > 0 && } + - + ); } else if (activity.kind === ACTIVITIES.EMAIL) { return ( - - {index > 0 && } + - + ); } })} diff --git a/src/features/canvassAssignments/components/CanvassAssignmentMap.tsx b/src/features/canvassAssignments/components/CanvassAssignmentMap.tsx index 30d0b17197..2b9be4158f 100644 --- a/src/features/canvassAssignments/components/CanvassAssignmentMap.tsx +++ b/src/features/canvassAssignments/components/CanvassAssignmentMap.tsx @@ -4,6 +4,8 @@ import { Map, FeatureGroup as FeatureGroupType, latLngBounds, + LatLngBounds, + LatLngTuple, } from 'leaflet'; import { makeStyles } from '@mui/styles'; import { GpsNotFixed } from '@mui/icons-material'; @@ -28,6 +30,7 @@ import MapControls from './MapControls'; import objToLatLng from 'features/areas/utils/objToLatLng'; import CanvassAssignmentMapOverlays from './CanvassAssignmentMapOverlays'; import useAllPlaceVisits from '../hooks/useAllPlaceVisits'; +import useLocalStorage from 'zui/hooks/useLocalStorage'; const useStyles = makeStyles(() => ({ '@keyframes ghostMarkerBounce': { @@ -85,6 +88,21 @@ const CanvassAssignmentMap: FC = ({ const crosshairRef = useRef(null); const reactFGref = useRef(null); + const [localStorageBounds, setLocalStorageBounds] = useLocalStorage< + [LatLngTuple, LatLngTuple] | null + >(`mapBounds-${assignment.id}`, null); + + const saveBounds = () => { + const bounds = map?.getBounds(); + + if (bounds) { + setLocalStorageBounds([ + [bounds.getSouth(), bounds.getWest()], + [bounds.getNorth(), bounds.getEast()], + ]); + } + }; + const [zoomed, setZoomed] = useState(false); const selectedPlace = places.find((place) => place.id == selectedPlaceId); @@ -170,6 +188,10 @@ const CanvassAssignmentMap: FC = ({ updateSelection(); }); + map.on('moveend', saveBounds); + + map.on('zoomend', () => saveBounds); + return () => { map.off('move'); map.off('moveend'); @@ -180,7 +202,10 @@ const CanvassAssignmentMap: FC = ({ useEffect(() => { if (map && !zoomed) { - const bounds = reactFGref.current?.getBounds(); + const bounds = localStorageBounds + ? new LatLngBounds(localStorageBounds) + : reactFGref.current?.getBounds(); + if (bounds?.isValid()) { map.fitBounds(bounds); setZoomed(true); diff --git a/src/features/canvassAssignments/components/MyCanvassAssignmentPage.tsx b/src/features/canvassAssignments/components/MyCanvassAssignmentPage.tsx index 1ca8cd01b5..7ffebe87a0 100644 --- a/src/features/canvassAssignments/components/MyCanvassAssignmentPage.tsx +++ b/src/features/canvassAssignments/components/MyCanvassAssignmentPage.tsx @@ -107,7 +107,7 @@ const AssignmentPage: FC<{ assignment: AssignmentWithAreas }> = ({ top: 0, transition: 'left 0.3s', width: '90vw', - zIndex: 9999, + zIndex: 99999, }} > diff --git a/src/features/canvassAssignments/components/PlaceDialog/index.tsx b/src/features/canvassAssignments/components/PlaceDialog/index.tsx index fee629273d..550fe7169b 100644 --- a/src/features/canvassAssignments/components/PlaceDialog/index.tsx +++ b/src/features/canvassAssignments/components/PlaceDialog/index.tsx @@ -183,7 +183,6 @@ const PlaceDialog: FC = ({ responses, }); setShowSparkle(true); - back(); }} /> diff --git a/src/features/canvassAssignments/components/PlaceDialog/pages/Place.tsx b/src/features/canvassAssignments/components/PlaceDialog/pages/Place.tsx index 59dd242472..e6ea53053e 100644 --- a/src/features/canvassAssignments/components/PlaceDialog/pages/Place.tsx +++ b/src/features/canvassAssignments/components/PlaceDialog/pages/Place.tsx @@ -60,7 +60,6 @@ const Place: FC = ({ @@ -72,6 +71,21 @@ const Place: FC = ({ {place.description || 'Empty description'} + + {!!numHouseholds && ( + <> + + {`${numVisitedHouseholds} of ${numHouseholds}`} + + households visited + + )} + {!numHouseholds && ( + + No households registered here yet + + )} + {assignment.reporting_level == 'place' && (